from __future__ import annotations import json import random import threading from dataclasses import dataclass import importlib.util from pathlib import Path import pytest _MOD_PATH = Path("/mnt/dolphinng5_predict/prod/nautilus_event_trader.py") _SPEC = importlib.util.spec_from_file_location("nautilus_event_trader_mod", _MOD_PATH) assert _SPEC and _SPEC.loader mod = importlib.util.module_from_spec(_SPEC) _SPEC.loader.exec_module(mod) # type: ignore[arg-type] @dataclass class _Pos: trade_id: str asset: str entry_price: float notional: float current_price: float = 0.0 pnl_pct: float = 0.0 class _ExitMgr: def __init__(self): self._positions: dict[str, dict] = {} class _Eng: def __init__(self, pos: _Pos | None): self.position = pos self.capital = 25_000.0 self.exit_manager = _ExitMgr() if pos: self.exit_manager._positions[pos.trade_id] = {"dummy": True} class _Map: def __init__(self): self._d = {"blue_runtime_commands": "[]"} self._lock = threading.Lock() def blocking(self): return self def get(self, key): with self._lock: return self._d.get(key) def put(self, key, val): with self._lock: self._d[key] = val class _F: def add_done_callback(self, _cb): return None return _F() def _mk_trader(): t = object.__new__(mod.DolphinLiveTrader) t.eng_lock = threading.Lock() t.control_map = _Map() t._processed_retract_commands = mod.deque(maxlen=5000) t._processed_retract_set = set() t._pending_entries = {} t.current_day = "2026-05-12" t.bar_idx = 100 return t def _install_open_position(t, *, trade_id="T", asset="STXUSDT", entry_price=1.0, notional=1000.0): p = _Pos(trade_id, asset, entry_price, notional, current_price=entry_price) t.eng = _Eng(p) t._pending_entries[trade_id] = { "trade_id": trade_id, "asset": asset, "side": "SHORT", "entry_price": entry_price, "entry_bar": 90, "entry_date": "2026-05-12", "notional": notional, "notional_entry": notional, "retraction_legs": 0, "realized_pnl_legs_total": 0.0, } t._pending_entries[trade_id].update(t._chain_state_for_pending( trade_id, t._pending_entries[trade_id], chain_mode="LIVE", chain_head_leg_id=f"{trade_id}:open", chain_prev_leg_id="", chain_seq=0, )) def test_fuzz_retraction_invariants_hold_under_random_command_stream(monkeypatch): monkeypatch.setattr(mod, "ch_put", lambda *_args, **_kwargs: None) monkeypatch.setattr(mod, "_ch_ts_us", lambda: 123) rng = random.Random(20260512) t = _mk_trader() _install_open_position(t, trade_id="T-FUZZ", asset="STXUSDT", entry_price=1.0, notional=10_000.0) seen_ids: set[str] = set() baseline_cap = t.eng.capital for i in range(2500): if t.eng.position is None: break px = max(0.00001, 1.0 + rng.uniform(-0.25, 0.25)) # Mix valid and invalid commands. frac_choice = rng.choice([ rng.uniform(0.01, 1.0), # valid 0.0, # invalid -0.1, # invalid 1.2, # invalid ]) # inject duplicate ids often if i > 0 and rng.random() < 0.2: cid = rng.choice(tuple(seen_ids)) if seen_ids else f"c-{i}" else: cid = f"c-{i}-{rng.randint(0, 999)}" seen_ids.add(cid) # wrong trade ids sometimes tid = "T-FUZZ" if rng.random() < 0.8 else f"OTHER-{i}" pending = t._pending_entries["T-FUZZ"] cmd = { "command_id": cid, "trade_id": tid, "action": "RETRACT", "fraction": frac_choice, "reason": "HOTKEY_RETRACT", "source": "fuzz", "chain_root_trade_id": pending["chain_root_trade_id"], "chain_head_leg_id": pending["chain_head_leg_id"], "chain_prev_leg_id": pending["chain_prev_leg_id"], "chain_seq": pending["chain_seq"], "chain_token": pending["chain_token"], } t.control_map.put("blue_runtime_commands", json.dumps([cmd])) t._process_runtime_commands({"STXUSDT": px}) if t.eng.position is not None: n = float(t.eng.position.notional) assert n >= -1e-8 # never exceed original notional assert n <= 10_000.0 + 1e-8 p = t._pending_entries["T-FUZZ"] assert int(p.get("retraction_legs", 0) or 0) >= 0 # Capital must stay finite and deterministic. assert t.eng.capital == pytest.approx(float(t.eng.capital)) assert abs(t.eng.capital - baseline_cap) < 1e7 def test_fuzz_concurrent_queue_submission_and_drain(monkeypatch): monkeypatch.setattr(mod, "ch_put", lambda *_args, **_kwargs: None) monkeypatch.setattr(mod, "_ch_ts_us", lambda: 123) rng = random.Random(777) t = _mk_trader() _install_open_position(t, trade_id="T-RACE", asset="DASHUSDT", entry_price=10.0, notional=5000.0) def producer(start: int, count: int): for i in range(start, start + count): with t.control_map._lock: raw = t.control_map._d.get("blue_runtime_commands", "[]") q = json.loads(raw) if raw else [] q.append({ "command_id": f"p-{i}", "trade_id": "T-RACE" if rng.random() < 0.9 else "OTHER", "action": "RETRACT", "fraction": rng.uniform(0.01, 1.0), "reason": "HOTKEY_RETRACT", "source": "race", "chain_root_trade_id": t._pending_entries["T-RACE"]["chain_root_trade_id"], "chain_head_leg_id": t._pending_entries["T-RACE"]["chain_head_leg_id"], "chain_prev_leg_id": t._pending_entries["T-RACE"]["chain_prev_leg_id"], "chain_seq": t._pending_entries["T-RACE"]["chain_seq"], "chain_token": t._pending_entries["T-RACE"]["chain_token"], }) t.control_map._d["blue_runtime_commands"] = json.dumps(q[-200:]) threads = [threading.Thread(target=producer, args=(k * 120, 120)) for k in range(4)] for th in threads: th.start() for th in threads: th.join() # Drain repeatedly; must not throw and must preserve invariants. for _ in range(50): if t.eng.position is None: break t._process_runtime_commands({"DASHUSDT": rng.uniform(8.0, 12.0)}) if t.eng.position is not None: assert t.eng.position.notional >= -1e-8 assert t.eng.position.notional <= 5000.0 + 1e-8