| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465 |
- from dataclasses import replace
- import math
- from okx_codex_trader.models import Candle
- from scripts.run_bb_squeeze_t_gated_observer import EMPTY_STATE, frame_from_candles, signal_from_frame
- def pair_frame(close_multiplier: float = 1.0, btc_last: float = 120.0):
- eth = []
- btc = []
- for index in range(1_200):
- eth_price = 100.0 + ((5.0 if index < 1_000 else 0.05) * math.sin(index / 3.0))
- open_price = eth_price
- high = eth_price
- low = eth_price
- close = eth_price
- if index == 1_199:
- close = 100.0 * close_multiplier
- high = max(open_price, close)
- low = min(open_price, close)
- btc_price = 100.0 + (index * 0.01)
- if index == 1_199:
- btc_price = btc_last
- ts = 1_700_000_000_000 + (index * 900_000)
- eth.append(Candle("ETH-USDT-SWAP", ts, open_price, high, low, close, 1_000.0))
- btc.append(Candle("BTC-USDT-SWAP", ts, btc_price, btc_price, btc_price, btc_price, 1_000.0))
- eth_frame = frame_from_candles(eth)
- btc_frame = frame_from_candles(btc)[["ts", "close"]].rename(columns={"close": "btc_close"})
- return eth_frame.merge(btc_frame, on="ts", how="inner").sort_values("ts").reset_index(drop=True)
- def test_entry_long_requires_btc_up_filter() -> None:
- frame = pair_frame(close_multiplier=1.02, btc_last=120.0)
- next_state, signal = signal_from_frame(frame, EMPTY_STATE)
- assert signal["signal"] == "entry_long"
- assert signal["target_side"] == "long"
- assert next_state.active_side == "long"
- def test_seen_candle_replays_without_new_signal() -> None:
- frame = pair_frame(close_multiplier=1.02, btc_last=120.0)
- state = replace(EMPTY_STATE, last_candle_ts=int(frame.iloc[-1]["ts"]))
- _, signal = signal_from_frame(frame, state)
- assert signal["signal"] == "state_replay"
- def test_reentry_short_uses_btc_against_gate() -> None:
- frame = pair_frame(close_multiplier=1.0, btc_last=120.0)
- state = replace(
- EMPTY_STATE,
- last_candle_ts=int(frame.iloc[-2]["ts"]),
- reentry_side="short",
- reentry_anchor_price=99.0,
- reentry_until_ts=int(frame.iloc[-1]["ts"]) + 900_000,
- )
- next_state, signal = signal_from_frame(frame, state)
- assert signal["signal"] == "reentry_short"
- assert signal["target_side"] == "short"
- assert next_state.active_side == "short"
|