Snapshot PINK DITAv2 system + Sprint 0 flaw-fix verification
First commit of the previously-untracked PINK-on-DITAv2 migration system (execution moves to the Rust kernel; policy stays on legacy DITA, so Alpha Engine algorithmic integrity is preserved). BLUE is untouched. Sprint 0 (safety snapshot + flaw-fix verification, MARKET single-leg scope): - Verified Rust FSM fixes (flaws 2,4,10,11,13) by source read of lib.rs. - Hardened 5 vacuous/guarded assertions in test_flaws.py so each flaw test genuinely exercises its fix. Most important: Flaw 5 now asserts capital moves by EXACTLY realized PnL (was entering/exiting at the same price). - Offline suites: 533 passed, 0 failed (35 flaws + 402 kernel/accounting/ bridge + 96 runtime/persistence/multi-exit/restart/seams). - GATE PASS: MARKET-path-critical flaws 1,2,5 confirmed fixed + green. - Added SPRINT0_FLAW_VERIFICATION.md report and _rust_kernel/.gitignore (excludes Rust target/ build artifacts). LIMIT/partial-fill remain explicitly out of scope (MARKET-only bring-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
202
prod/tests/test_multi_exit_retraction_fuzz.py
Normal file
202
prod/tests/test_multi_exit_retraction_fuzz.py
Normal file
@@ -0,0 +1,202 @@
|
||||
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
|
||||
Reference in New Issue
Block a user