Files
siloqy/prod/tests/test_post_win_long_overlay.py

196 lines
5.2 KiB
Python
Raw Normal View History

2026-05-08 19:54:13 +02:00
from datetime import datetime, timedelta, timezone
from adaptive_exit.post_win_long_overlay import (
PostWinExecutionFSM,
PostWinExecutionFSMConfig,
PostWinFlipTrigger,
)
def _ts(seconds: int = 0) -> datetime:
return datetime(2026, 5, 8, 12, 0, tzinfo=timezone.utc) + timedelta(seconds=seconds)
def test_big_win_arms_one_slot_and_resets_after_consumption():
overlay = PostWinExecutionFSM()
armed = overlay.observe_closed_trade(
trade_id="t1",
asset="ALGOUSDT",
side="SHORT",
pnl=398.0,
pnl_pct=0.004,
leverage=2.0,
closed_ts=_ts(),
)
assert armed.action == "ARMED"
assert armed.reason == "big_win"
assert overlay.pending_slots == 1
tag = overlay.tag_next_entry(asset="DASHUSDT", entry_ts=_ts(30))
assert tag.action == "TAG"
assert tag.side == "LONG"
assert tag.consumed_slot == 1
assert tag.reset is True
assert overlay.pending_slots == 0
after = overlay.tag_next_entry(asset="TRXUSDT", entry_ts=_ts(60))
assert after.action == "PASS"
assert after.side == "SHORT"
def test_big_win_high_lev_arms_two_slots_then_resets():
overlay = PostWinExecutionFSM()
armed = overlay.observe_closed_trade(
trade_id="t2",
asset="VETUSDT",
side="SHORT",
pnl=573.0,
pnl_pct=0.0148,
leverage=9.0,
closed_ts=_ts(),
)
assert armed.action == "ARMED"
assert armed.reason == "big_win_high_lev"
assert overlay.pending_slots == 2
first = overlay.tag_next_entry(asset="STXUSDT", entry_ts=_ts(10))
assert first.side == "LONG"
assert first.consumed_slot == 1
assert first.reset is False
assert overlay.pending_slots == 1
second = overlay.tag_next_entry(asset="TRXUSDT", entry_ts=_ts(20))
assert second.side == "LONG"
assert second.consumed_slot == 2
assert second.reset is True
assert overlay.pending_slots == 0
third = overlay.tag_next_entry(asset="ATOMUSDT", entry_ts=_ts(30))
assert third.side == "SHORT"
def test_small_dollar_high_return_arms_one_slot():
overlay = PostWinExecutionFSM()
armed = overlay.observe_closed_trade(
trade_id="t3",
asset="ETCUSDT",
side="SHORT",
pnl=149.0,
pnl_pct=0.0075,
leverage=0.8,
closed_ts=_ts(),
)
assert armed.action == "ARMED"
assert armed.reason == "small_dollar_high_return"
assert overlay.tag_next_entry(asset="LTCUSDT", entry_ts=_ts(10)).side == "LONG"
assert overlay.tag_next_entry(asset="BNBUSDT", entry_ts=_ts(20)).side == "SHORT"
def test_rearm_attempt_while_slots_active_is_ignored_and_does_not_extend_counter():
overlay = PostWinExecutionFSM()
overlay.observe_closed_trade(
trade_id="first",
asset="ALGOUSDT",
side="SHORT",
pnl=500.0,
pnl_pct=0.010,
leverage=9.0,
closed_ts=_ts(),
)
ignored = overlay.observe_closed_trade(
trade_id="second",
asset="VETUSDT",
side="SHORT",
pnl=900.0,
pnl_pct=0.020,
leverage=9.0,
closed_ts=_ts(5),
)
assert ignored.action == "IGNORED"
assert ignored.reason == "active_arm_no_rearm"
assert overlay.ignored_rearm_attempts == 1
assert overlay.pending_slots == 2
assert overlay.tag_next_entry(asset="A", entry_ts=_ts(10)).side == "LONG"
assert overlay.tag_next_entry(asset="B", entry_ts=_ts(20)).side == "LONG"
assert overlay.tag_next_entry(asset="C", entry_ts=_ts(30)).side == "SHORT"
def test_overlay_flipped_trade_outcome_cannot_rearm():
overlay = PostWinExecutionFSM()
ignored = overlay.observe_closed_trade(
trade_id="long-flip",
asset="DASHUSDT",
side="LONG",
pnl=1000.0,
pnl_pct=0.03,
leverage=9.0,
closed_ts=_ts(),
was_overlay_flip=True,
)
assert ignored.action == "IGNORED"
assert ignored.reason == "overlay_flip_outcome"
assert overlay.pending_slots == 0
def test_arm_expires_by_optional_ttl_without_consuming_slot():
overlay = PostWinExecutionFSM(PostWinExecutionFSMConfig(max_arm_age_sec=60.0))
overlay.observe_closed_trade(
trade_id="ttl",
asset="VETUSDT",
side="SHORT",
pnl=500.0,
pnl_pct=0.01,
leverage=9.0,
closed_ts=_ts(),
)
tag = overlay.tag_next_entry(asset="LATEUSDT", entry_ts=_ts(61))
assert tag.action == "PASS"
assert tag.side == "SHORT"
assert overlay.pending_slots == 0
assert overlay.expired_arms == 1
def test_future_expansion_supports_more_than_two_slots():
overlay = PostWinExecutionFSM(
PostWinExecutionFSMConfig(
rules=(
PostWinFlipTrigger(
name="future_three_slot_rule",
slots=3,
min_pnl_abs=100.0,
strict_min_pnl_abs=True,
),
)
)
)
overlay.observe_closed_trade(
trade_id="three",
asset="XRPUSDT",
side="SHORT",
pnl=101.0,
pnl_pct=0.001,
leverage=1.0,
closed_ts=_ts(),
)
assert [overlay.tag_next_entry(asset=str(i), entry_ts=_ts(i)).side for i in range(1, 5)] == [
"LONG",
"LONG",
"LONG",
"SHORT",
]