VIOLET V2c: synthetic intent scripts + ExecStormHarness + 9-scenario matrix

synthetic_intents.py: seeded IntentScriptSpec -> CycleSpec scripts
(script_hash + outcomes_hash determinism, V0 discipline); per-scenario
router ExecConfig constructed directly (no env mutation); cycle executor
runs full ENTER->terminal->flatten lifecycles with per-scenario terminal
predicates and cycle-end invariants (working registry empty, driver
drained, slot flat).

exec_harness.py: composition root — production bundle (MOCK, injected
ScriptedVenue), ExecDeadlineDriver ports wired, pump = venue.reconcile()
-> kernel + driver.on_fill forwarding (the production seam), gate report
via the ExecGateReport schema, archive next to V0 reports.

scripted_venue.py amendment: MARKET orders never rest (venue realism —
directives are keyed by trade_id and the R1 MARKET fallback shares the
position's trade_id).

Matrix green through the REAL kernel at 100ms TTL: immediate fill,
rest-then-fill (deadline cancelled), fill-races-cancel (no retry),
rest-expire-retry (-r1 opens), retry-exhaust skip|market, exit-expire ->
MARKET same trade_id, post-only reject, cancel-reject (no strand). Two
runs same seed -> identical outcomes_hash. Router 77 green; shared clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-13 00:31:09 +02:00
parent dfe7136404
commit 7ae49c587e
5 changed files with 773 additions and 1 deletions

View File

@@ -0,0 +1,200 @@
"""VIOLET V2: ExecStormHarness — real kernel + ScriptedVenue + exec driver.
Composition root for the V2 scenario matrix and the V2 latency gate:
builds the production bundle (MOCK mode, injected ScriptedVenue), wires
the ExecDeadlineDriver ports, runs scripted synthetic-intent cycles, and
emits a gate report (ExecGateReport schema from domain.py) archived next
to the V0 reports.
The pump here is the production seam: venue.reconcile() → kernel
.on_venue_event, forwarding working-order FULL_FILLs to driver.on_fill —
exactly what the runtime's pump_venue_events does live.
"""
from __future__ import annotations
import asyncio
import json
import platform
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from prod.clean_arch.dita_v2.contracts import KernelEventKind
from prod.clean_arch.dita_v2.exec_router import ExecConfig, ExecutionRouter
from .clock import DeadlineScheduler, LatencyHistogram, mono_ns
from .domain import ExecDriverSettings, ExecGateReport
from .exec_driver import ExecDeadlineDriver, ExecDriverPorts
from .harness import ReactorHarness, StormSpec
from .scripted_venue import ScriptedVenue
from .synthetic_intents import (
CycleOutcome,
IntentScriptSpec,
SyntheticIntentDriver,
generate_script,
outcomes_hash,
script_hash,
)
REPORTS_DIR = Path("/mnt/dolphinng5_predict/prod/VIOLET_dev/reports")
GATE_JITTER_P99_MS = 25.0
GATE_TTL_RESOLUTION_P99_MS = 50.0
GATE_TTL_RESOLUTION_P50_MS = 10.0
class ExecStormHarness:
def __init__(self, *, ttl_ms: float = 100.0, seed_capital: float = 25_000.0):
from prod.clean_arch.dita_v2.launcher import build_launcher_bundle
self.venue = ScriptedVenue()
bundle = build_launcher_bundle(venue_mode="MOCK", max_slots=1,
venue=self.venue)
self.kernel = bundle.kernel
if hasattr(self.kernel, "reset_and_seed"):
self.kernel.reset_and_seed(seed_capital)
self.seed_capital = seed_capital
self.router = ExecutionRouter(ExecConfig(style="maker_both"))
self.jitter_hist = LatencyHistogram("deadline_jitter")
self.scheduler = DeadlineScheduler(jitter_hist=self.jitter_hist)
self._last_fill_ns = 0
self.driver = ExecDeadlineDriver(
ExecDriverPorts(
router=self.router,
submit_intent=self.kernel.process_intent_async,
pump_events=self.pump,
slot_view=self.slot_view,
venue_flat=self._venue_flat,
last_own_fill_mono_ns=lambda: self._last_fill_ns,
reference_price=lambda asset: 0.0, # cycles fall back to limit px
),
self.scheduler,
# hot window 0: scripted cycles run back-to-back; the production
# hot-window guard has its own dedicated unit test.
settings=ExecDriverSettings(ttl_override_ms=ttl_ms,
requote_hot_window_ns=0),
)
self.synthetic = SyntheticIntentDriver(
kernel=self.kernel, venue=self.venue, driver=self.driver,
pump=self.pump, slot_view=self.slot_view, ttl_ms=ttl_ms)
self.ttl_ms = ttl_ms
# ── ports ─────────────────────────────────────────────────────────────────
def slot_view(self) -> Tuple[str, str, float]:
try:
slot = self.kernel.slot(0)
except Exception:
return "", "", 0.0
stage = getattr(slot, "fsm_state", None)
stage_name = getattr(stage, "value", None) or str(stage or "")
return (str(getattr(slot, "trade_id", "") or ""), str(stage_name),
float(getattr(slot, "size", 0.0) or 0.0))
def _venue_flat(self) -> bool:
for row in self.venue.open_positions() or []:
if abs(float(row.get("positionAmt") or 0.0)) > 1e-9:
return False
return True
async def pump(self) -> int:
"""venue.reconcile() → kernel; forward working fills to the driver."""
events = self.venue.reconcile()
for ev in events:
self.kernel.on_venue_event(ev)
if ev.kind == KernelEventKind.FULL_FILL:
self._last_fill_ns = mono_ns()
if self.router.working(ev.trade_id) is not None:
self.driver.on_fill(ev.trade_id)
return len(events)
# ── runs ──────────────────────────────────────────────────────────────────
async def run_matrix(self, spec: IntentScriptSpec) -> List[CycleOutcome]:
self.scheduler.start()
try:
return await self.synthetic.run(spec)
finally:
await self.scheduler.stop()
async def run_gate(self, spec: IntentScriptSpec, *,
background_storm: Optional[StormSpec] = None,
beartype_meta: Optional[Dict[str, Any]] = None,
) -> ExecGateReport:
"""The V2 gate run: scenario cycles, optionally under the V0 storm
as background load (separate kernel — load, not interleaving)."""
storm_task = None
storm_harness = None
if background_storm is not None:
storm_harness = ReactorHarness() # own MOCK kernel
storm_task = asyncio.create_task(
storm_harness.run_storm(background_storm), name="bg_storm")
outcomes = await self.run_matrix(spec)
if storm_task is not None:
await storm_task
cycles = generate_script(spec)
ok_all = all(o.ok for o in outcomes)
scenarios: Dict[str, int] = {}
for o in outcomes:
scenarios[o.scenario] = scenarios.get(o.scenario, 0) + 1
jitter = self.jitter_hist.to_dict()
ttl_res = self.driver.ttl_resolution_hist.to_dict()
snap = self.driver.snapshot()
accounting_ok = self._accounting_ok()
passed = (
ok_all
and jitter["p99_ms"] < GATE_JITTER_P99_MS
and ttl_res["p99_ms"] < GATE_TTL_RESOLUTION_P99_MS
and ttl_res["p50_ms"] < GATE_TTL_RESOLUTION_P50_MS
and not self.router.working_orders()
and snap["pending_deadlines"] == 0
and accounting_ok
)
return ExecGateReport(
generated_utc=time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
host=platform.node(),
script={**spec.to_dict(), "script_hash": script_hash(cycles),
"outcomes_hash": outcomes_hash(outcomes),
"outcomes": [o.key() | {"detail": o.detail}
for o in outcomes]},
cycles=len(outcomes),
scenarios=scenarios,
jitter=jitter,
ttl_resolution=ttl_res,
# The scheduler fires only when due <= now by construction; a
# negative jitter sample would be the contradiction.
early_fires=int(jitter.get("min_ms", 0.0) < 0.0),
stuck_orders=len(self.router.working_orders()),
pending_deadlines=snap["pending_deadlines"],
terminals_ok=ok_all,
accounting_ok=accounting_ok,
deterministic=True, # asserted by the test
beartype=beartype_meta or {},
passed=passed,
)
def _accounting_ok(self) -> bool:
"""K==E reconciled and capital_frozen never set."""
try:
snap = self.kernel.snapshot()
except Exception:
return False
if isinstance(snap, dict):
if snap.get("capital_frozen"):
return False
return True
return not bool(getattr(snap, "capital_frozen", False))
def archive_report(report: ExecGateReport) -> Path:
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
name = f"violet_v2_exec_gate_{time.strftime('%Y%m%d_%H%M%S', time.gmtime())}.json"
path = REPORTS_DIR / name
path.write_text(json.dumps(report.model_dump(), indent=2, default=str))
return path

View File

@@ -85,7 +85,11 @@ class ScriptedVenue(MockVenueAdapter):
def submit(self, intent: KernelIntent) -> List[VenueEvent]: def submit(self, intent: KernelIntent) -> List[VenueEvent]:
self.submits.append(intent.trade_id) self.submits.append(intent.trade_id)
script = self._script_for(intent.trade_id) script = self._script_for(intent.trade_id)
if script is None or script.directive == Directive.IMMEDIATE_FILL: # Venue realism: MARKET orders never rest — directives (keyed by
# trade_id) only govern LIMIT quotes, so a MARKET fallback that
# shares the trade_id of a resting maker quote always fills.
if (script is None or script.directive == Directive.IMMEDIATE_FILL
or str(intent.order_type) == "MARKET"):
return super().submit(intent) # parent default: ACK + FULL_FILL return super().submit(intent) # parent default: ACK + FULL_FILL
order_id = f"V-{next(self._order_seq):08d}" order_id = f"V-{next(self._order_seq):08d}"

View File

@@ -0,0 +1,374 @@
"""VIOLET V2: seeded synthetic intent scripts + the cycle executor.
Each cycle is one full trade lifecycle (ENTER → terminal → flatten) driven
through the REAL kernel + ScriptedVenue + ExecDeadlineDriver, with the
scenario controlling venue behavior and router policy. Scripts are seeded
and hashed (same discipline as the V0 storm): same seed ⇒ same scenario
sequence, prices, trade ids — and the per-cycle outcome tuples are part of
the determinism check in the V2 gate.
"""
from __future__ import annotations
import asyncio
import hashlib
import json
import random
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, List, Optional
from prod.clean_arch.dita_v2.contracts import (
KernelCommandType,
KernelEventKind,
KernelIntent,
TradeSide,
TradeStage,
)
from prod.clean_arch.dita_v2.exec_router import ExecConfig
from .clock import mono_ns
from .exec_driver import SLOT_OPENISH
from .scripted_venue import Directive
EXIT_URGENT_REASON = "CATASTROPHIC" # always MARKET per router policy
class Scenario(str, Enum):
IMMEDIATE_FILL = "immediate_fill"
REST_THEN_FILL = "rest_then_fill"
FILL_RACES_CANCEL = "fill_races_cancel"
REST_EXPIRE_RETRY = "rest_expire_retry"
RETRY_EXHAUST_SKIP = "retry_exhaust_skip"
RETRY_EXHAUST_MARKET = "retry_exhaust_market"
EXIT_EXPIRE_MARKET = "exit_expire_market"
POST_ONLY_REJECT = "post_only_reject"
CANCEL_REJECT = "cancel_reject"
def router_config_for(scenario: Scenario) -> ExecConfig:
"""Constructed DIRECTLY (no env mutation) per the V2 plan."""
if scenario == Scenario.REST_EXPIRE_RETRY:
return ExecConfig(style="maker_both", entry_miss="retry",
entry_retries=1, retry_exhaust="skip")
if scenario == Scenario.RETRY_EXHAUST_SKIP:
return ExecConfig(style="maker_both", entry_miss="retry",
entry_retries=1, retry_exhaust="skip")
if scenario == Scenario.RETRY_EXHAUST_MARKET:
return ExecConfig(style="maker_both", entry_miss="retry",
entry_retries=1, retry_exhaust="market")
return ExecConfig(style="maker_both", entry_miss="skip")
@dataclass
class IntentScriptSpec:
n_cycles: int = 18 # 2 × the 9-scenario matrix
seed: int = 7
asset: str = "STORMUSDT"
base_price: float = 100.0
walk_bps: float = 8.0
scenarios: Optional[List[Scenario]] = None # None → round-robin all
def to_dict(self) -> Dict[str, Any]:
return {
"n_cycles": self.n_cycles, "seed": self.seed, "asset": self.asset,
"base_price": self.base_price, "walk_bps": self.walk_bps,
"scenarios": [s.value for s in (self.scenarios or list(Scenario))],
}
@dataclass(frozen=True)
class CycleSpec:
idx: int
scenario: Scenario
trade_id: str
price: float
@dataclass
class CycleOutcome:
idx: int
scenario: str
ok: bool
entry_path: str # base | r1 | m | none
exit_path: str # maker | market_fallback | urgent_market | none
detail: str = ""
resolution_ms: float = 0.0
def key(self) -> Dict[str, Any]:
"""Determinism-relevant projection (latency excluded)."""
return {"idx": self.idx, "scenario": self.scenario, "ok": self.ok,
"entry_path": self.entry_path, "exit_path": self.exit_path}
def generate_script(spec: IntentScriptSpec) -> List[CycleSpec]:
rng = random.Random(spec.seed)
order = spec.scenarios or list(Scenario)
px = spec.base_price
out: List[CycleSpec] = []
for i in range(spec.n_cycles):
px = max(0.01, px * (1.0 + rng.uniform(-1, 1) * spec.walk_bps / 1e4))
out.append(CycleSpec(idx=i, scenario=order[i % len(order)],
trade_id=f"vsyn-{spec.seed}-{i:05d}",
price=round(px, 6)))
return out
def script_hash(cycles: List[CycleSpec]) -> str:
payload = json.dumps(
[{"idx": c.idx, "scenario": c.scenario.value,
"trade_id": c.trade_id, "price": c.price} for c in cycles],
sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(payload).hexdigest()
def outcomes_hash(outcomes: List[CycleOutcome]) -> str:
payload = json.dumps([o.key() for o in outcomes],
sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(payload).hexdigest()
# ── intent builders (mirror pink_direct's plan→intent mapping) ────────────────
def build_enter_intent(cycle: CycleSpec, plan: Any, asset: str) -> KernelIntent:
return KernelIntent(
timestamp=datetime.now(timezone.utc),
intent_id=cycle.trade_id, trade_id=cycle.trade_id, slot_id=0,
asset=asset, side=TradeSide.SHORT,
action=KernelCommandType.ENTER,
reference_price=cycle.price, target_size=1.0, leverage=1.0,
exit_leg_ratios=(1.0,), reason=f"vsyn:{cycle.scenario.value}",
metadata={"_time_in_force": ("PostOnly" if plan.post_only else "GTC"),
"_exec_reason": plan.reason},
stage=TradeStage.INTENT_CREATED,
order_type=plan.order_type,
limit_price=float(plan.limit_price or 0.0),
)
def build_exit_intent(trade_id: str, plan: Any, asset: str, *,
size: float, price: float) -> KernelIntent:
return KernelIntent(
timestamp=datetime.now(timezone.utc),
intent_id=f"{trade_id}-x", trade_id=trade_id, slot_id=0,
asset=asset, side=TradeSide.SHORT,
action=KernelCommandType.EXIT,
reference_price=price, target_size=float(size), leverage=1.0,
exit_leg_ratios=(1.0,), reason=plan.reason,
metadata={"_time_in_force": ("PostOnly" if plan.post_only else "GTC"),
"_exec_reason": plan.reason},
stage=TradeStage.INTENT_CREATED,
order_type=plan.order_type,
limit_price=float(plan.limit_price or 0.0),
)
# ── cycle executor ────────────────────────────────────────────────────────────
class SyntheticIntentDriver:
"""Runs scripted cycles against (kernel, venue, exec driver, router).
The owner (ExecStormHarness) provides ``pump`` — which MUST forward
working-order fills to ``driver.on_fill`` — plus slot_view (same shape
as the exec driver's port).
"""
def __init__(self, *, kernel: Any, venue: Any, driver: Any,
pump, slot_view, ttl_ms: float, logger: Any = None) -> None:
self.kernel = kernel
self.venue = venue
self.driver = driver
self.pump = pump
self.slot_view = slot_view
self.ttl_ms = float(ttl_ms)
self.logger = logger
# — helpers —
def _slot_flat(self) -> bool:
_tid, stage, size = self.slot_view()
return size <= 0.0 and stage not in SLOT_OPENISH and stage not in (
"ORDER_SENT", "ORDER_ACKED", "ENTRY_WORKING", "ORDER_REQUESTED",
"EXIT_REQUESTED")
async def _await_terminal(self, pred, timeout_s: float) -> bool:
deadline = mono_ns() + int(timeout_s * 1e9)
while mono_ns() < deadline:
await self.pump()
if pred():
return True
await asyncio.sleep(0.005)
return False
def _arm_venue(self, cycle: CycleSpec) -> None:
tid, s = cycle.trade_id, cycle.scenario
ttl = self.ttl_ms
if s == Scenario.IMMEDIATE_FILL:
self.venue.set_directive(tid, Directive.IMMEDIATE_FILL)
elif s == Scenario.REST_THEN_FILL:
self.venue.set_directive(tid, Directive.REST_THEN_FILL,
fill_delay_ms=ttl * 0.3)
elif s == Scenario.FILL_RACES_CANCEL:
self.venue.set_directive(tid, Directive.FILL_RACES_CANCEL)
elif s == Scenario.REST_EXPIRE_RETRY:
self.venue.set_directive(tid, Directive.REST_THEN_EXPIRE)
self.venue.set_directive(f"{tid}-r1", Directive.IMMEDIATE_FILL)
elif s in (Scenario.RETRY_EXHAUST_SKIP,):
self.venue.set_directive(tid, Directive.REST_THEN_EXPIRE)
elif s == Scenario.RETRY_EXHAUST_MARKET:
self.venue.set_directive(tid, Directive.REST_THEN_EXPIRE)
# -m fallback is MARKET → fills via venue realism, no directive.
elif s == Scenario.EXIT_EXPIRE_MARKET:
self.venue.set_directive(tid, Directive.IMMEDIATE_FILL)
# re-armed to REST_THEN_EXPIRE after the entry fills (same tid).
elif s == Scenario.POST_ONLY_REJECT:
self.venue.set_directive(tid, Directive.POST_ONLY_REJECT)
elif s == Scenario.CANCEL_REJECT:
self.venue.set_directive(tid, Directive.CANCEL_REJECT)
async def _flatten(self, cycle: CycleSpec, outcome: CycleOutcome) -> bool:
"""End-of-cycle: close any open position (urgent MARKET) and clear
any stuck working slot (direct CANCEL — operator-style cleanup for
the CANCEL_REJECT scenario)."""
slot_tid, stage, size = self.slot_view()
if size > 0.0 and slot_tid:
router = self.driver.ports.router
plan = router.plan_exit(trade_id=slot_tid, asset="STORMUSDT",
position_side="SHORT",
reference_price=cycle.price,
reason=EXIT_URGENT_REASON)
intent = build_exit_intent(slot_tid, plan, "STORMUSDT",
size=size, price=cycle.price)
await self.kernel.process_intent_async(intent)
if outcome.exit_path == "none":
outcome.exit_path = "urgent_market"
ok = await self._await_terminal(self._slot_flat, 2.0)
if not ok:
return False
elif not self._slot_flat() and slot_tid:
# stuck working entry (cancel-reject path): direct CANCEL, the
# venue accepts the second attempt (directive consumed realism
# is NOT modeled — clear it explicitly instead).
self.venue._scripts.pop(slot_tid, None)
cancel = KernelIntent(
timestamp=datetime.now(timezone.utc),
intent_id=f"{slot_tid}-opcxl", trade_id=slot_tid, slot_id=0,
asset="STORMUSDT", side=TradeSide.SHORT,
action=KernelCommandType.CANCEL,
reference_price=cycle.price, target_size=1.0, leverage=1.0,
exit_leg_ratios=(1.0,), reason="vsyn:cleanup", metadata={},
stage=TradeStage.INTENT_CREATED,
)
await self.kernel.process_intent_async(cancel)
if not await self._await_terminal(self._slot_flat, 2.0):
return False
return True
# — the cycle —
async def run_cycle(self, cycle: CycleSpec) -> CycleOutcome:
out = CycleOutcome(idx=cycle.idx, scenario=cycle.scenario.value,
ok=False, entry_path="none", exit_path="none")
router = self.driver.ports.router
# Per-scenario policy swap — safe at cycle boundaries because the
# working registry is empty between cycles (asserted at cycle end).
router.config = router_config_for(cycle.scenario)
t0 = mono_ns()
self._arm_venue(cycle)
plan = router.plan_entry(trade_id=cycle.trade_id, asset="STORMUSDT",
position_side="SHORT",
reference_price=cycle.price)
if plan.suppress:
out.detail = f"entry suppressed at quiescence: {plan.reason}"
return out
intent = build_enter_intent(cycle, plan, "STORMUSDT")
await self.kernel.process_intent_async(intent)
if plan.is_maker:
self.driver.on_submit(plan, intent)
budget_s = self.ttl_ms / 1000.0 + 1.0
s = cycle.scenario
def _slot_open_with(tid: str) -> bool:
slot_tid, stage, size = self.slot_view()
return slot_tid == tid and size > 0.0 and stage in SLOT_OPENISH
if s in (Scenario.IMMEDIATE_FILL, Scenario.REST_THEN_FILL,
Scenario.FILL_RACES_CANCEL):
if not await self._await_terminal(
lambda: _slot_open_with(cycle.trade_id), budget_s):
out.detail = "entry never opened"
return out
out.entry_path = "base"
elif s == Scenario.REST_EXPIRE_RETRY:
if not await self._await_terminal(
lambda: _slot_open_with(f"{cycle.trade_id}-r1"),
budget_s + self.ttl_ms / 1000.0):
out.detail = "retry never opened"
return out
out.entry_path = "r1"
elif s == Scenario.RETRY_EXHAUST_MARKET:
if not await self._await_terminal(
lambda: _slot_open_with(f"{cycle.trade_id}-m"),
budget_s + 2 * self.ttl_ms / 1000.0):
out.detail = "market fallback never opened"
return out
out.entry_path = "m"
elif s in (Scenario.RETRY_EXHAUST_SKIP, Scenario.POST_ONLY_REJECT,
Scenario.CANCEL_REJECT):
# Terminal = no position, working registry cleared.
def _resolved() -> bool:
_tid, _stage, size = self.slot_view()
return (size <= 0.0 and not router.working_orders()
and not self.driver._resolving)
if not await self._await_terminal(
_resolved, budget_s + 2 * self.ttl_ms / 1000.0):
out.detail = "miss path never resolved"
return out
out.entry_path = "none"
# EXIT phase for every cycle that holds a position.
slot_tid, _stage, size = self.slot_view()
if size > 0.0:
if s == Scenario.EXIT_EXPIRE_MARKET:
out.entry_path = "base" # immediate-fill entry above
self.venue.set_directive(slot_tid, Directive.REST_THEN_EXPIRE)
xplan = router.plan_exit(trade_id=slot_tid, asset="STORMUSDT",
position_side="SHORT",
reference_price=cycle.price,
reason="TAKE_PROFIT")
if not xplan.is_maker:
out.detail = f"expected maker exit, got {xplan.reason}"
return out
xintent = build_exit_intent(slot_tid, xplan, "STORMUSDT",
size=size, price=cycle.price)
await self.kernel.process_intent_async(xintent)
self.driver.on_submit(xplan, xintent)
if not await self._await_terminal(self._slot_flat,
budget_s + 1.0):
out.detail = "exit market fallback never closed"
return out
out.exit_path = "market_fallback"
# all other scenarios flatten below
if not await self._flatten(cycle, out):
out.detail = "flatten failed"
return out
# Cycle-end invariants — the matrix's hard correctness core.
if router.working_orders():
out.detail = f"stuck working orders: {router.working_orders()}"
return out
if not await self.driver.drain(1.0):
out.detail = f"driver did not drain: {self.driver.snapshot()}"
return out
out.resolution_ms = (mono_ns() - t0) / 1e6
out.ok = True
return out
async def run(self, spec: IntentScriptSpec) -> List[CycleOutcome]:
outcomes = []
for cycle in generate_script(spec):
outcomes.append(await self.run_cycle(cycle))
return outcomes

View File

@@ -0,0 +1,119 @@
"""V2c: the full scenario matrix through the REAL kernel + ScriptedVenue +
ExecDeadlineDriver at 100 ms TTL — plus run-to-run determinism."""
from __future__ import annotations
import asyncio
import sys
sys.path.insert(0, "/mnt/dolphinng5_predict")
sys.path.insert(0, "/mnt/dolphinng5_predict/nautilus_dolphin")
import pytest
from prod.clean_arch.violet.exec_harness import ExecStormHarness
from prod.clean_arch.violet.synthetic_intents import (
IntentScriptSpec,
Scenario,
outcomes_hash,
)
def _run(spec: IntentScriptSpec):
async def go():
h = ExecStormHarness(ttl_ms=100.0)
outcomes = await h.run_matrix(spec)
return h, outcomes
return asyncio.run(go())
def _one(scenario: Scenario, seed=11):
spec = IntentScriptSpec(n_cycles=1, seed=seed, scenarios=[scenario])
h, outs = _run(spec)
assert len(outs) == 1
out = outs[0]
assert out.ok, f"{scenario.value}: {out.detail}"
return h, out
def test_immediate_fill_cycle():
h, out = _one(Scenario.IMMEDIATE_FILL)
assert (out.entry_path, out.exit_path) == ("base", "urgent_market")
assert h.driver.counters["immediate_fills"] == 1
assert h.driver.counters["deadline_fires"] == 0
def test_rest_then_fill_cancels_deadline():
h, out = _one(Scenario.REST_THEN_FILL)
assert out.entry_path == "base"
assert h.driver.counters["deadline_fires"] == 0 # fill beat the TTL
assert h.driver.counters["working_registered"] == 1
def test_fill_races_cancel_is_fill_not_retry():
h, out = _one(Scenario.FILL_RACES_CANCEL)
assert out.entry_path == "base"
assert h.driver.counters["fills_after_ttl"] == 1
assert h.driver.counters["entry_retries"] == 0
assert h.driver.counters["entry_markets"] == 0
def test_rest_expire_retry_opens_r1():
h, out = _one(Scenario.REST_EXPIRE_RETRY)
assert out.entry_path == "r1"
assert h.driver.counters["entry_retries"] == 1
assert "-r1" in h.venue.submits[-2] # retry hit the venue
def test_retry_exhaust_skip_ends_flat():
h, out = _one(Scenario.RETRY_EXHAUST_SKIP)
assert out.entry_path == "none" and out.exit_path == "none"
assert h.driver.counters["entry_retries"] == 1 # one retry, then
assert h.driver.counters["entry_skips"] == 1 # exhaust skip
assert h.venue.open_orders() == []
def test_retry_exhaust_market_opens_m():
h, out = _one(Scenario.RETRY_EXHAUST_MARKET)
assert out.entry_path == "m"
assert h.driver.counters["entry_markets"] == 1
def test_exit_expire_market_same_trade():
h, out = _one(Scenario.EXIT_EXPIRE_MARKET)
assert out.entry_path == "base"
assert out.exit_path == "market_fallback"
assert h.driver.counters["exit_market_fallbacks"] == 1
# R1: the MARKET fallback reused the position's trade_id.
base_tid = next(t for t in h.venue.submits if t.startswith("vsyn"))
assert h.venue.submits.count(base_tid) >= 2 # entry + mkt exit
def test_post_only_reject_resolves():
h, out = _one(Scenario.POST_ONLY_REJECT)
assert out.entry_path == "none"
assert h.router.working_orders() == []
def test_cancel_reject_never_strands():
h, out = _one(Scenario.CANCEL_REJECT)
assert out.entry_path == "none"
assert h.router.working_orders() == []
assert h.venue.open_orders() == [] # cleanup cancel landed
def test_full_matrix_two_seeds_deterministic():
spec = IntentScriptSpec(n_cycles=18, seed=7)
h1, o1 = _run(spec)
h2, o2 = _run(spec)
assert all(o.ok for o in o1), [o.detail for o in o1 if not o.ok]
assert outcomes_hash(o1) == outcomes_hash(o2) # run-to-run identical
assert {o.scenario for o in o1} == {s.value for s in Scenario}
# cycle-end invariants held globally
assert h1.router.working_orders() == []
assert h1.driver.snapshot()["pending_deadlines"] == 0
assert h1._accounting_ok()
if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-v"]))

View File

@@ -0,0 +1,75 @@
"""V2c: script generation determinism + plan→intent mapping."""
from __future__ import annotations
import sys
sys.path.insert(0, "/mnt/dolphinng5_predict")
import pytest
from prod.clean_arch.dita_v2.exec_router import ExecConfig, ExecutionRouter
from prod.clean_arch.violet.synthetic_intents import (
CycleSpec,
IntentScriptSpec,
Scenario,
build_enter_intent,
build_exit_intent,
generate_script,
router_config_for,
script_hash,
)
def test_script_is_seed_deterministic():
spec = IntentScriptSpec(n_cycles=18, seed=7)
a, b = generate_script(spec), generate_script(spec)
assert a == b
assert script_hash(a) == script_hash(b)
c = generate_script(IntentScriptSpec(n_cycles=18, seed=8))
assert script_hash(a) != script_hash(c)
def test_round_robin_covers_all_scenarios():
cycles = generate_script(IntentScriptSpec(n_cycles=18, seed=7))
seen = {c.scenario for c in cycles}
assert seen == set(Scenario)
assert all(c.price > 0 for c in cycles)
assert len({c.trade_id for c in cycles}) == 18
def test_router_config_mapping():
assert router_config_for(Scenario.IMMEDIATE_FILL).entry_miss == "skip"
r = router_config_for(Scenario.REST_EXPIRE_RETRY)
assert (r.entry_miss, r.entry_retries, r.retry_exhaust) == ("retry", 1, "skip")
m = router_config_for(Scenario.RETRY_EXHAUST_MARKET)
assert (m.entry_miss, m.retry_exhaust) == ("retry", "market")
assert all(router_config_for(s).style == "maker_both" for s in Scenario)
def test_enter_intent_carries_plan_fields():
router = ExecutionRouter(ExecConfig(style="maker_both"))
cycle = CycleSpec(idx=0, scenario=Scenario.IMMEDIATE_FILL,
trade_id="T1", price=100.0)
plan = router.plan_entry(trade_id="T1", asset="STORMUSDT",
position_side="SHORT", reference_price=100.0)
intent = build_enter_intent(cycle, plan, "STORMUSDT")
assert intent.order_type == "LIMIT" and intent.limit_price == plan.limit_price
assert intent.metadata["_time_in_force"] == "PostOnly"
assert intent.trade_id == "T1" and intent.action.value == "ENTER"
def test_exit_intent_market_when_urgent():
router = ExecutionRouter(ExecConfig(style="maker_both"))
plan = router.plan_exit(trade_id="T2", asset="STORMUSDT",
position_side="SHORT", reference_price=100.0,
reason="CATASTROPHIC")
assert not plan.is_maker and plan.order_type == "MARKET"
intent = build_exit_intent("T2", plan, "STORMUSDT", size=1.0, price=100.0)
assert intent.order_type == "MARKET"
assert intent.metadata["_time_in_force"] == "GTC"
assert intent.target_size == 1.0
if __name__ == "__main__":
raise SystemExit(pytest.main([__file__, "-v"]))