test_run_bb_squeeze_t_gated_observer.py 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. from dataclasses import replace
  2. import math
  3. from okx_codex_trader.models import Candle
  4. from scripts.run_bb_squeeze_t_gated_observer import EMPTY_STATE, frame_from_candles, signal_from_frame
  5. def pair_frame(close_multiplier: float = 1.0, btc_last: float = 120.0):
  6. eth = []
  7. btc = []
  8. for index in range(1_200):
  9. eth_price = 100.0 + ((5.0 if index < 1_000 else 0.05) * math.sin(index / 3.0))
  10. open_price = eth_price
  11. high = eth_price
  12. low = eth_price
  13. close = eth_price
  14. if index == 1_199:
  15. close = 100.0 * close_multiplier
  16. high = max(open_price, close)
  17. low = min(open_price, close)
  18. btc_price = 100.0 + (index * 0.01)
  19. if index == 1_199:
  20. btc_price = btc_last
  21. ts = 1_700_000_000_000 + (index * 900_000)
  22. eth.append(Candle("ETH-USDT-SWAP", ts, open_price, high, low, close, 1_000.0))
  23. btc.append(Candle("BTC-USDT-SWAP", ts, btc_price, btc_price, btc_price, btc_price, 1_000.0))
  24. eth_frame = frame_from_candles(eth)
  25. btc_frame = frame_from_candles(btc)[["ts", "close"]].rename(columns={"close": "btc_close"})
  26. return eth_frame.merge(btc_frame, on="ts", how="inner").sort_values("ts").reset_index(drop=True)
  27. def test_entry_long_requires_btc_up_filter() -> None:
  28. frame = pair_frame(close_multiplier=1.02, btc_last=120.0)
  29. next_state, signal = signal_from_frame(frame, EMPTY_STATE)
  30. assert signal["signal"] == "entry_long"
  31. assert signal["target_side"] == "long"
  32. assert next_state.active_side == "long"
  33. def test_seen_candle_replays_without_new_signal() -> None:
  34. frame = pair_frame(close_multiplier=1.02, btc_last=120.0)
  35. state = replace(EMPTY_STATE, last_candle_ts=int(frame.iloc[-1]["ts"]))
  36. _, signal = signal_from_frame(frame, state)
  37. assert signal["signal"] == "state_replay"
  38. def test_reentry_short_uses_btc_against_gate() -> None:
  39. frame = pair_frame(close_multiplier=1.0, btc_last=120.0)
  40. state = replace(
  41. EMPTY_STATE,
  42. last_candle_ts=int(frame.iloc[-2]["ts"]),
  43. reentry_side="short",
  44. reentry_anchor_price=99.0,
  45. reentry_until_ts=int(frame.iloc[-1]["ts"]) + 900_000,
  46. )
  47. next_state, signal = signal_from_frame(frame, state)
  48. assert signal["signal"] == "reentry_short"
  49. assert signal["target_side"] == "short"
  50. assert next_state.active_side == "short"