"""ExecutionRouter — unit, adversarial and fuzz tests (pure policy layer). Invariants under test (the non-negotiables from exec_router's docstring): R1 exits are never skipped / suppressed except the working-dup guard R2 one working ENTER maximum; duplicate ENTER plans are suppressed R3 retries are bounded by entry_retries, then retry_exhaust applies R4 default config (no env) == pure taker == legacy behavior R5 hooks can never crash the policy path nor strand an exit """ from __future__ import annotations import itertools import os import random import unittest from unittest import mock from hypothesis import given, settings, strategies as st from prod.clean_arch.dita_v2.exec_router import ( DEFAULT_TICKS, ExecConfig, ExecutionPlan, ExecutionRouter, MAKER_EXIT_REASONS, MissAction, ) class FakeClock: def __init__(self, t: float = 1000.0): self.t = t def __call__(self) -> float: return self.t def tick(self, dt: float) -> None: self.t += dt def make_router(clock=None, **cfg) -> ExecutionRouter: return ExecutionRouter(ExecConfig(**cfg), clock=clock or FakeClock()) # ───────────────────────────────────────────────────────────────────────────── # Config parsing # ───────────────────────────────────────────────────────────────────────────── class TestExecConfig(unittest.TestCase): def test_defaults_are_taker(self): cfg = ExecConfig() self.assertEqual(cfg.style, "taker") self.assertFalse(cfg.maker_entry) self.assertFalse(cfg.maker_exit) def test_from_env_defaults(self): with mock.patch.dict(os.environ, {}, clear=True): cfg = ExecConfig.from_env() self.assertEqual(cfg.style, "taker") self.assertEqual(cfg.entry_miss, "skip") self.assertEqual(cfg.entry_retries, 1) self.assertTrue(cfg.post_only) def test_from_env_full(self): env = { "DOLPHIN_PINK_EXEC_STYLE": "maker_both", "DOLPHIN_PINK_MAKER_ENTRY_TTL_S": "12.5", "DOLPHIN_PINK_MAKER_EXIT_TTL_S": "3", "DOLPHIN_PINK_MAKER_ENTRY_MISS": "retry", "DOLPHIN_PINK_MAKER_ENTRY_RETRIES": "2", "DOLPHIN_PINK_MAKER_RETRY_EXHAUST": "market", "DOLPHIN_PINK_MAKER_OFFSET_TICKS": "3", "DOLPHIN_PINK_MAKER_MAX_SPREAD_BPS": "7.5", "DOLPHIN_PINK_POST_ONLY": "0", "DOLPHIN_PINK_TICK_SIZE_FOOUSDT": "0.025", } with mock.patch.dict(os.environ, env, clear=True): cfg = ExecConfig.from_env() self.assertEqual(cfg.style, "maker_both") self.assertEqual(cfg.entry_ttl_s, 12.5) self.assertEqual(cfg.exit_ttl_s, 3.0) self.assertEqual(cfg.entry_miss, "retry") self.assertEqual(cfg.entry_retries, 2) self.assertEqual(cfg.retry_exhaust, "market") self.assertEqual(cfg.offset_ticks, 3) self.assertEqual(cfg.max_spread_bps, 7.5) self.assertFalse(cfg.post_only) self.assertEqual(cfg.tick_overrides["FOOUSDT"], 0.025) def test_from_env_garbage_falls_back(self): env = { "DOLPHIN_PINK_EXEC_STYLE": "yolo", "DOLPHIN_PINK_MAKER_ENTRY_TTL_S": "not-a-number", "DOLPHIN_PINK_MAKER_ENTRY_MISS": "explode", "DOLPHIN_PINK_MAKER_ENTRY_RETRIES": "-5", "DOLPHIN_PINK_MAKER_OFFSET_TICKS": "9999", "DOLPHIN_PINK_TICK_SIZE_BADUSDT": "zero", } with mock.patch.dict(os.environ, env, clear=True): cfg = ExecConfig.from_env() self.assertEqual(cfg.style, "taker") self.assertEqual(cfg.entry_ttl_s, 8.0) self.assertEqual(cfg.entry_miss, "skip") self.assertEqual(cfg.entry_retries, 0) # clamped up from -5 self.assertEqual(cfg.offset_ticks, 100) # clamped down self.assertNotIn("BADUSDT", cfg.tick_overrides) def test_from_env_empty_strings(self): env = {"DOLPHIN_PINK_EXEC_STYLE": "", "DOLPHIN_PINK_MAKER_ENTRY_TTL_S": " "} with mock.patch.dict(os.environ, env, clear=True): cfg = ExecConfig.from_env() self.assertEqual(cfg.style, "taker") self.assertEqual(cfg.entry_ttl_s, 8.0) # ───────────────────────────────────────────────────────────────────────────── # Pricing # ───────────────────────────────────────────────────────────────────────────── class TestPricing(unittest.TestCase): def test_sell_quotes_above_reference(self): r = make_router(style="maker_both") px = r.maker_price(asset="BTCUSDT", order_side="SELL", reference_price=61000.0) self.assertAlmostEqual(px, 61000.1) def test_buy_quotes_below_reference(self): r = make_router(style="maker_both") px = r.maker_price(asset="BTCUSDT", order_side="BUY", reference_price=61000.0) self.assertAlmostEqual(px, 60999.9) def test_offset_ticks_respected(self): r = make_router(style="maker_both", offset_ticks=5) px = r.maker_price(asset="BTCUSDT", order_side="SELL", reference_price=61000.0) self.assertAlmostEqual(px, 61000.5) def test_unknown_symbol_uses_fraction(self): r = make_router(style="maker_both") px = r.maker_price(asset="NEWUSDT", order_side="SELL", reference_price=100.0) self.assertGreater(px, 100.0) self.assertLess(px, 100.01) def test_zero_reference_returns_zero(self): r = make_router(style="maker_both") self.assertEqual(r.maker_price(asset="BTCUSDT", order_side="SELL", reference_price=0.0), 0.0) self.assertEqual(r.maker_price(asset="BTCUSDT", order_side="BUY", reference_price=-5.0), 0.0) def test_buy_price_never_nonpositive(self): r = make_router(style="maker_both", offset_ticks=100) # tiny price, huge offset → clamped to >= one tick px = r.maker_price(asset="SHIBUSDT", order_side="BUY", reference_price=2e-9) self.assertGreater(px, 0.0) def test_order_side_mapping(self): self.assertEqual(ExecutionRouter.order_side("ENTER", "SHORT"), "SELL") self.assertEqual(ExecutionRouter.order_side("ENTER", "LONG"), "BUY") self.assertEqual(ExecutionRouter.order_side("EXIT", "SHORT"), "BUY") self.assertEqual(ExecutionRouter.order_side("EXIT", "LONG"), "SELL") # ───────────────────────────────────────────────────────────────────────────── # Entry planning # ───────────────────────────────────────────────────────────────────────────── class TestPlanEntry(unittest.TestCase): def test_taker_style_market(self): r = make_router() # default taker p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertEqual(p.order_type, "MARKET") self.assertFalse(p.is_maker) self.assertFalse(p.suppress) def test_maker_entry_limit_postonly(self): r = make_router(style="maker_entry") p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertEqual(p.order_type, "LIMIT") self.assertTrue(p.is_maker) self.assertTrue(p.post_only) self.assertAlmostEqual(p.limit_price, 61000.1) self.assertEqual(p.ttl_s, 8.0) def test_maker_exit_style_does_not_affect_entry(self): r = make_router(style="maker_exit") p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertEqual(p.order_type, "MARKET") def test_bad_reference_price_degrades_to_market(self): r = make_router(style="maker_both") for bad in (0.0, -1.0): p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=bad) self.assertEqual(p.order_type, "MARKET") self.assertTrue(p.sane()) def test_spread_gate(self): r = make_router(style="maker_both", max_spread_bps=5.0) wide = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0, spread_bps=6.0) self.assertEqual(wide.order_type, "MARKET") tight = r.plan_entry(trade_id="t2", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0, spread_bps=4.9) self.assertEqual(tight.order_type, "LIMIT") unknown = r.plan_entry(trade_id="t3", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0, spread_bps=None) self.assertEqual(unknown.order_type, "LIMIT") def test_duplicate_entry_suppressed_while_working(self): r = make_router(style="maker_entry") p1 = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=p1) p2 = r.plan_entry(trade_id="t2", asset="BTCUSDT", position_side="SHORT", reference_price=61001.0) self.assertTrue(p2.suppress) self.assertIn("working_entry_exists", p2.reason) # after fill the guard releases r.note_fill("t1") p3 = r.plan_entry(trade_id="t3", asset="BTCUSDT", position_side="SHORT", reference_price=61002.0) self.assertFalse(p3.suppress) # ───────────────────────────────────────────────────────────────────────────── # Exit planning — RULE 1 # ───────────────────────────────────────────────────────────────────────────── class TestPlanExit(unittest.TestCase): def test_take_profit_is_maker_eligible(self): r = make_router(style="maker_exit") p = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT") self.assertEqual(p.order_type, "LIMIT") self.assertTrue(p.post_only) # SHORT exit = BUY → below reference self.assertAlmostEqual(p.limit_price, 60899.9) self.assertEqual(p.ttl_s, 5.0) def test_urgent_reasons_always_market(self): r = make_router(style="maker_both") for reason in ("CATASTROPHIC_LOSS", "MAX_HOLD", "MEAN_REVERSION", "anything_else", "", None): p = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason=reason) self.assertEqual(p.order_type, "MARKET", f"reason={reason!r}") self.assertFalse(p.suppress) def test_exit_never_suppressed_fresh(self): for style in ("taker", "maker_entry", "maker_exit", "maker_both"): r = make_router(style=style) p = r.plan_exit(trade_id="x", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT") self.assertFalse(p.suppress, f"style={style}") self.assertTrue(p.sane()) def test_duplicate_nonurgent_exit_suppressed_while_working(self): r = make_router(style="maker_exit") p1 = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT") r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=p1) p2 = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60899.0, reason="TAKE_PROFIT") self.assertTrue(p2.suppress) def test_urgent_exit_preempts_working_quote(self): r = make_router(style="maker_exit") p1 = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT") r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=p1) p2 = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61200.0, reason="CATASTROPHIC_LOSS") self.assertFalse(p2.suppress) self.assertEqual(p2.order_type, "MARKET") self.assertTrue(p2.metadata.get("preempt_working")) def test_bad_reference_price_exit_still_market(self): r = make_router(style="maker_both") p = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=0.0, reason="TAKE_PROFIT") self.assertEqual(p.order_type, "MARKET") self.assertTrue(p.sane()) def test_wide_spread_exit_degrades_to_market_not_skip(self): r = make_router(style="maker_exit", max_spread_bps=2.0) p = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT", spread_bps=50.0) self.assertEqual(p.order_type, "MARKET") self.assertFalse(p.suppress) # ───────────────────────────────────────────────────────────────────────────── # Registry + TTL + miss policy — RULE 2 / RULE 3 # ───────────────────────────────────────────────────────────────────────────── class TestRegistryAndMiss(unittest.TestCase): def _maker_plan(self, action="ENTER", ttl=8.0): return ExecutionPlan(order_type="LIMIT", limit_price=61000.1, post_only=True, ttl_s=ttl, is_maker=True, action=action, reason="t") def test_expiry_with_fake_clock(self): clk = FakeClock() r = make_router(clock=clk, style="maker_entry") r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) self.assertEqual(r.expired(), []) clk.tick(7.9) self.assertEqual(r.expired(), []) clk.tick(0.2) self.assertEqual([w.trade_id for w in r.expired()], ["t1"]) def test_note_fill_and_cancel_idempotent(self): r = make_router(style="maker_entry") r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) r.note_fill("t1") self.assertIsNone(r.working("t1")) r.note_fill("t1") # no-op r.note_cancel("t1") # no-op self.assertEqual(r.counters["fills_working"], 1) self.assertEqual(r.counters["cancels"], 0) def test_miss_skip(self): r = make_router(style="maker_entry", entry_miss="skip") wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) self.assertEqual(r.entry_miss_action(wo), MissAction.SKIP) def test_miss_market(self): r = make_router(style="maker_entry", entry_miss="market") wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) self.assertEqual(r.entry_miss_action(wo), MissAction.MARKET) def test_retry_bounded_then_exhaust_skip(self): r = make_router(style="maker_entry", entry_miss="retry", entry_retries=2, retry_exhaust="skip") wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) # miss 1 → retry self.assertEqual(r.entry_miss_action(wo), MissAction.RETRY) tid2, plan2 = r.retry_plan(wo, reference_price=61010.0) self.assertEqual(tid2, "t1-r1") self.assertEqual(plan2.order_type, "LIMIT") r.note_cancel("t1") wo2 = r.register_working(trade_id=tid2, asset="BTCUSDT", position_side="SHORT", plan=plan2, base_trade_id="t1", retry_n=1) # miss 2 → retry (retries=2) self.assertEqual(r.entry_miss_action(wo2), MissAction.RETRY) tid3, plan3 = r.retry_plan(wo2, reference_price=61020.0) self.assertEqual(tid3, "t1-r2") r.note_cancel(tid2) wo3 = r.register_working(trade_id=tid3, asset="BTCUSDT", position_side="SHORT", plan=plan3, base_trade_id="t1", retry_n=2) # miss 3 → exhausted → skip self.assertEqual(r.entry_miss_action(wo3), MissAction.SKIP) def test_retry_exhaust_market(self): r = make_router(style="maker_entry", entry_miss="retry", entry_retries=0, retry_exhaust="market") wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) self.assertEqual(r.entry_miss_action(wo), MissAction.MARKET) def test_retry_plan_insane_price_degrades_to_market(self): r = make_router(style="maker_entry", entry_miss="retry") wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) _tid, plan = r.retry_plan(wo, reference_price=0.0) self.assertEqual(plan.order_type, "MARKET") self.assertTrue(plan.sane()) def test_market_fallback_ids(self): r = make_router(style="maker_both") woe = r.register_working(trade_id="e1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan("ENTER")) tid, plan = r.market_fallback_plan(woe) self.assertEqual(tid, "e1-m") # ENTER: fresh id self.assertEqual(plan.order_type, "MARKET") r.note_cancel("e1") wox = r.register_working(trade_id="x1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan("EXIT", ttl=5.0)) tid2, plan2 = r.market_fallback_plan(wox) self.assertEqual(tid2, "x1") # EXIT: same id — stays on position self.assertEqual(plan2.order_type, "MARKET") def test_snapshot_shape(self): r = make_router(style="maker_both") r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=self._maker_plan()) snap = r.snapshot() self.assertEqual(snap["style"], "maker_both") self.assertEqual(len(snap["working"]), 1) self.assertIn("counters", snap) # ───────────────────────────────────────────────────────────────────────────── # Hooks — RULE 5 # ───────────────────────────────────────────────────────────────────────────── class TestHooks(unittest.TestCase): def test_pre_submit_can_mutate_plan(self): r = make_router(style="maker_entry") def widen(plan, ctx): if isinstance(plan, ExecutionPlan) and plan.is_maker: from dataclasses import replace as _r return _r(plan, limit_price=plan.limit_price + 1.0) return plan r.register_hook("pre_submit", widen) p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertAlmostEqual(p.limit_price, 61001.1) def test_insane_hook_plan_ignored(self): r = make_router(style="maker_entry") r.register_hook("pre_submit", lambda plan, ctx: ExecutionPlan(order_type="LIMIT", limit_price=0.0)) p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertTrue(p.sane()) self.assertAlmostEqual(p.limit_price, 61000.1) def test_hook_exception_isolated(self): r = make_router(style="maker_entry") def boom(plan, ctx): raise RuntimeError("plugin gone wild") r.register_hook("pre_submit", boom) p = r.plan_entry(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=61000.0) self.assertEqual(p.order_type, "LIMIT") self.assertEqual(r.counters["hook_errors"], 1) def test_hook_cannot_suppress_exit(self): r = make_router(style="maker_exit") r.register_hook("pre_submit", lambda plan, ctx: ExecutionPlan(action="EXIT", suppress=True)) p = r.plan_exit(trade_id="t1", asset="BTCUSDT", position_side="SHORT", reference_price=60900.0, reason="TAKE_PROFIT") self.assertFalse(p.suppress) self.assertEqual(p.order_type, "MARKET") def test_unregister(self): r = make_router(style="maker_entry") calls = [] un = r.register_hook("on_fill", lambda wo, ctx: calls.append(1)) r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=ExecutionPlan(order_type="LIMIT", limit_price=1.0, ttl_s=5, is_maker=True, action="ENTER")) r.note_fill("t1") self.assertEqual(len(calls), 1) un() r.register_working(trade_id="t2", asset="BTCUSDT", position_side="SHORT", plan=ExecutionPlan(order_type="LIMIT", limit_price=1.0, ttl_s=5, is_maker=True, action="ENTER")) r.note_fill("t2") self.assertEqual(len(calls), 1) def test_unknown_stage_raises(self): r = make_router() with self.assertRaises(ValueError): r.register_hook("nonsense", lambda *a: None) def test_lifecycle_hooks_fire(self): r = make_router(style="maker_entry", entry_miss="skip") seen = [] for stage in ("on_working", "on_miss", "on_cancel"): r.register_hook(stage, lambda x, ctx, s=stage: seen.append(s)) wo = r.register_working(trade_id="t1", asset="BTCUSDT", position_side="SHORT", plan=ExecutionPlan(order_type="LIMIT", limit_price=1.0, ttl_s=5, is_maker=True, action="ENTER")) r.entry_miss_action(wo) r.note_cancel("t1") self.assertEqual(seen, ["on_working", "on_miss", "on_cancel"]) # ───────────────────────────────────────────────────────────────────────────── # Fuzz — property-based (hypothesis) # ───────────────────────────────────────────────────────────────────────────── class TestFuzz(unittest.TestCase): @given( style=st.sampled_from(["taker", "maker_entry", "maker_exit", "maker_both"]), ref=st.floats(min_value=-1e9, max_value=1e9, allow_nan=False, allow_infinity=False), spread=st.one_of(st.none(), st.floats(min_value=-10, max_value=10_000, allow_nan=False, allow_infinity=False)), side=st.sampled_from(["SHORT", "LONG", "weird", ""]), asset=st.sampled_from(list(DEFAULT_TICKS) + ["UNKNOWNUSDT", ""]), ) @settings(max_examples=300, deadline=None) def test_plan_entry_always_sane(self, style, ref, spread, side, asset): r = make_router(style=style) p = r.plan_entry(trade_id="f1", asset=asset, position_side=side, reference_price=ref, spread_bps=spread) assert p.sane(), p if p.order_type == "LIMIT": assert p.limit_price > 0.0 assert p.ttl_s > 0.0 @given( style=st.sampled_from(["taker", "maker_entry", "maker_exit", "maker_both"]), ref=st.floats(min_value=-1e9, max_value=1e9, allow_nan=False, allow_infinity=False), reason=st.one_of(st.none(), st.text(max_size=20), st.sampled_from(sorted(MAKER_EXIT_REASONS) + ["CATASTROPHIC_LOSS", "MAX_HOLD"])), side=st.sampled_from(["SHORT", "LONG"]), ) @settings(max_examples=300, deadline=None) def test_plan_exit_never_skips(self, style, ref, reason, side): r = make_router(style=style) p = r.plan_exit(trade_id="f1", asset="BTCUSDT", position_side=side, reference_price=ref, reason=reason) assert p.sane(), p assert not p.suppress # no working order registered → never suppressed @given(prices=st.lists(st.floats(min_value=1e-9, max_value=1e7, allow_nan=False, allow_infinity=False), min_size=1, max_size=20), offset=st.integers(min_value=0, max_value=100), asset=st.sampled_from(list(DEFAULT_TICKS) + ["XUSDT"])) @settings(max_examples=200, deadline=None) def test_maker_price_side_correct(self, prices, offset, asset): r = make_router(style="maker_both", offset_ticks=offset) for ref in prices: sell = r.maker_price(asset=asset, order_side="SELL", reference_price=ref) buy = r.maker_price(asset=asset, order_side="BUY", reference_price=ref) assert sell >= ref assert 0.0 < buy <= ref or buy > 0.0 # buy clamps to >= 1 tick # ───────────────────────────────────────────────────────────────────────────── # Chaos — randomized lifecycle sequences with invariants (seeded) # ───────────────────────────────────────────────────────────────────────────── class TestChaosLifecycle(unittest.TestCase): def test_random_sequences_hold_invariants(self): for seed in range(40): rng = random.Random(seed) clk = FakeClock() r = make_router( clock=clk, style=rng.choice(["maker_entry", "maker_exit", "maker_both"]), entry_miss=rng.choice(["skip", "retry", "market"]), entry_retries=rng.randint(0, 3), retry_exhaust=rng.choice(["skip", "market"]), ) ids = itertools.count() for _step in range(200): op = rng.randrange(6) clk.tick(rng.random() * 3) if op == 0: tid = f"e{next(ids)}" p = r.plan_entry(trade_id=tid, asset="BTCUSDT", position_side=rng.choice(["SHORT", "LONG"]), reference_price=rng.uniform(0, 70000)) if p.is_maker and not p.suppress: r.register_working(trade_id=tid, asset="BTCUSDT", position_side="SHORT", plan=p) elif op == 1: tid = f"x{next(ids)}" p = r.plan_exit(trade_id=tid, asset="BTCUSDT", position_side=rng.choice(["SHORT", "LONG"]), reference_price=rng.uniform(0, 70000), reason=rng.choice(["TAKE_PROFIT", "MAX_HOLD", "CATASTROPHIC_LOSS", "junk"])) assert p.sane() if p.is_maker and not p.suppress: r.register_working(trade_id=tid, asset="BTCUSDT", position_side="SHORT", plan=p) elif op == 2 and r.working_orders(): r.note_fill(rng.choice(r.working_orders()).trade_id) elif op == 3 and r.working_orders(): r.note_cancel(rng.choice(r.working_orders()).trade_id) elif op == 4: for wo in r.expired(): act = r.entry_miss_action(wo) if wo.action == "ENTER" else None r.note_cancel(wo.trade_id) if act == MissAction.RETRY: tid2, plan2 = r.retry_plan(wo, reference_price=rng.uniform(1, 70000)) if plan2.is_maker: r.register_working(trade_id=tid2, asset="BTCUSDT", position_side="SHORT", plan=plan2, base_trade_id=wo.base_trade_id, retry_n=wo.retry_n + 1) elif act == MissAction.MARKET or (wo.action == "EXIT"): r.market_fallback_plan(wo) else: r.snapshot() # INVARIANT R2: at most one working ENTER at any time entries = [w for w in r.working_orders() if w.action == "ENTER"] assert len(entries) <= 1, f"seed={seed}: {entries}" # INVARIANT R3: retry numbering bounded for w in r.working_orders(): assert w.retry_n <= r.config.entry_retries + 1 if __name__ == "__main__": unittest.main()