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:
209
prod/clean_arch/dita_v2/mock_venue.py
Normal file
209
prod/clean_arch/dita_v2/mock_venue.py
Normal file
@@ -0,0 +1,209 @@
|
||||
"""Deterministic mock venue for DITAv2 tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Dict, List, Optional
|
||||
import itertools
|
||||
|
||||
from .contracts import (
|
||||
KernelCommandType,
|
||||
KernelEventKind,
|
||||
KernelIntent,
|
||||
TradeSide,
|
||||
VenueEvent,
|
||||
VenueEventStatus,
|
||||
VenueOrder,
|
||||
VenueOrderStatus,
|
||||
)
|
||||
from .venue import VenueAdapter
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MockVenueScenario:
|
||||
"""Failure knobs for the mock venue."""
|
||||
|
||||
reject_entries: bool = False
|
||||
reject_exits: bool = False
|
||||
partial_fill_ratio: float = 1.0
|
||||
cancel_reject: bool = False
|
||||
emit_ack_before_fill: bool = True
|
||||
emit_fill_on_submit: bool = False
|
||||
entry_partial_fill_ratio: float = 1.0
|
||||
exit_partial_fill_ratio: float = 1.0
|
||||
|
||||
|
||||
class MockVenueAdapter(VenueAdapter):
|
||||
"""Scriptable mock venue with BingX-shaped response semantics."""
|
||||
|
||||
def __init__(self, scenario: Optional[MockVenueScenario] = None):
|
||||
self.scenario = scenario or MockVenueScenario()
|
||||
self._order_seq = itertools.count(1)
|
||||
self._event_seq = itertools.count(1)
|
||||
self._open_orders: Dict[str, VenueOrder] = {}
|
||||
self._open_positions: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def submit(self, intent: KernelIntent) -> List[VenueEvent]:
|
||||
is_entry = intent.action == KernelCommandType.ENTER
|
||||
should_reject = self.scenario.reject_entries if is_entry else self.scenario.reject_exits
|
||||
order_id = f"V-{next(self._order_seq):08d}"
|
||||
client_id = f"{intent.trade_id}:{intent.intent_id}"
|
||||
order = VenueOrder(
|
||||
internal_trade_id=intent.trade_id,
|
||||
venue_order_id=order_id,
|
||||
venue_client_id=client_id,
|
||||
side=intent.side,
|
||||
intended_size=float(intent.target_size),
|
||||
status=VenueOrderStatus.NEW,
|
||||
metadata={"intent_id": intent.intent_id, "action": intent.action.value, "slot_id": intent.slot_id, "asset": intent.asset},
|
||||
)
|
||||
if should_reject:
|
||||
order = VenueOrder(
|
||||
internal_trade_id=order.internal_trade_id,
|
||||
venue_order_id=order.venue_order_id,
|
||||
venue_client_id=order.venue_client_id,
|
||||
side=order.side,
|
||||
intended_size=order.intended_size,
|
||||
filled_size=0.0,
|
||||
average_fill_price=0.0,
|
||||
status=VenueOrderStatus.REJECTED,
|
||||
metadata=dict(order.metadata),
|
||||
)
|
||||
return [self._event_from_order(intent, order, KernelEventKind.ORDER_REJECT, VenueEventStatus.REJECTED, reason="MOCK_REJECT")]
|
||||
|
||||
self._open_orders[order_id] = order
|
||||
events: List[VenueEvent] = []
|
||||
if self.scenario.emit_ack_before_fill or not self.scenario.emit_fill_on_submit:
|
||||
events.append(self._event_from_order(intent, order, KernelEventKind.ORDER_ACK, VenueEventStatus.ACKED))
|
||||
if self.scenario.emit_fill_on_submit or self.scenario.partial_fill_ratio > 0:
|
||||
if is_entry:
|
||||
effective_ratio = self.scenario.entry_partial_fill_ratio if self.scenario.entry_partial_fill_ratio != 1.0 else self.scenario.partial_fill_ratio
|
||||
else:
|
||||
effective_ratio = self.scenario.exit_partial_fill_ratio if self.scenario.exit_partial_fill_ratio != 1.0 else self.scenario.partial_fill_ratio
|
||||
fill_ratio = max(0.0, min(1.0, float(effective_ratio)))
|
||||
fill_size = float(intent.target_size) * fill_ratio
|
||||
event_kind = KernelEventKind.FULL_FILL if fill_ratio >= 1.0 else KernelEventKind.PARTIAL_FILL
|
||||
event_status = VenueEventStatus.FILLED if fill_ratio >= 1.0 else VenueEventStatus.PARTIALLY_FILLED
|
||||
fill_event = self._event_from_order(
|
||||
intent,
|
||||
order,
|
||||
event_kind,
|
||||
event_status,
|
||||
price=float(intent.reference_price or 0.0),
|
||||
fill_size=fill_size,
|
||||
remaining_size=max(0.0, float(intent.target_size) - fill_size),
|
||||
)
|
||||
events.append(fill_event)
|
||||
order = VenueOrder(
|
||||
internal_trade_id=order.internal_trade_id,
|
||||
venue_order_id=order.venue_order_id,
|
||||
venue_client_id=order.venue_client_id,
|
||||
side=order.side,
|
||||
intended_size=order.intended_size,
|
||||
filled_size=fill_size,
|
||||
average_fill_price=float(intent.reference_price or 0.0),
|
||||
status=VenueOrderStatus.FILLED if fill_ratio >= 1.0 else VenueOrderStatus.PARTIALLY_FILLED,
|
||||
metadata=dict(order.metadata),
|
||||
)
|
||||
self._open_orders[order_id] = order
|
||||
return events
|
||||
|
||||
def cancel(self, order: VenueOrder, *, reason: str = "") -> List[VenueEvent]:
|
||||
if self.scenario.cancel_reject:
|
||||
return [
|
||||
self._event_from_order(
|
||||
self._dummy_intent(order),
|
||||
order,
|
||||
KernelEventKind.CANCEL_REJECT,
|
||||
VenueEventStatus.CANCELED_REJECTED,
|
||||
reason=reason or "MOCK_CANCEL_REJECT",
|
||||
)
|
||||
]
|
||||
existing = self._open_orders.get(order.venue_order_id, order)
|
||||
canceled = VenueOrder(
|
||||
internal_trade_id=existing.internal_trade_id,
|
||||
venue_order_id=existing.venue_order_id,
|
||||
venue_client_id=existing.venue_client_id,
|
||||
side=existing.side,
|
||||
intended_size=existing.intended_size,
|
||||
filled_size=existing.filled_size,
|
||||
average_fill_price=existing.average_fill_price,
|
||||
status=VenueOrderStatus.CANCELED,
|
||||
metadata=dict(existing.metadata),
|
||||
)
|
||||
self._open_orders.pop(order.venue_order_id, None)
|
||||
return [
|
||||
self._event_from_order(
|
||||
self._dummy_intent(order),
|
||||
canceled,
|
||||
KernelEventKind.CANCEL_ACK,
|
||||
VenueEventStatus.CANCELED,
|
||||
reason=reason or "MOCK_CANCEL_ACK",
|
||||
)
|
||||
]
|
||||
|
||||
def open_orders(self) -> List[VenueOrder]:
|
||||
return list(self._open_orders.values())
|
||||
|
||||
def open_positions(self) -> List[Dict[str, Any]]:
|
||||
return list(self._open_positions.values())
|
||||
|
||||
def reconcile(self) -> List[VenueEvent]:
|
||||
return []
|
||||
|
||||
def _dummy_intent(self, order: VenueOrder) -> KernelIntent:
|
||||
return KernelIntent(
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
intent_id=order.venue_client_id,
|
||||
trade_id=order.internal_trade_id,
|
||||
slot_id=int(order.metadata.get("slot_id", 0)),
|
||||
asset=str(order.metadata.get("asset", "")),
|
||||
side=order.side,
|
||||
action=KernelCommandType.EXIT if order.metadata.get("action") == "EXIT" else KernelCommandType.ENTER,
|
||||
reference_price=float(order.metadata.get("reference_price", 0.0)),
|
||||
target_size=float(order.intended_size),
|
||||
leverage=float(order.metadata.get("leverage", 1.0)),
|
||||
reason=str(order.metadata.get("reason", "")),
|
||||
metadata=dict(order.metadata),
|
||||
)
|
||||
|
||||
def _event_from_order(
|
||||
self,
|
||||
intent: KernelIntent,
|
||||
order: VenueOrder,
|
||||
kind: KernelEventKind,
|
||||
status: VenueEventStatus,
|
||||
*,
|
||||
price: Optional[float] = None,
|
||||
fill_size: float = 0.0,
|
||||
remaining_size: float = 0.0,
|
||||
reason: str = "",
|
||||
) -> VenueEvent:
|
||||
event = VenueEvent(
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
event_id=f"EV-{next(self._event_seq):08d}",
|
||||
trade_id=intent.trade_id,
|
||||
slot_id=intent.slot_id,
|
||||
kind=kind,
|
||||
status=status,
|
||||
venue_order_id=order.venue_order_id,
|
||||
venue_client_id=order.venue_client_id,
|
||||
side=order.side,
|
||||
asset=intent.asset,
|
||||
price=float(price if price is not None else intent.reference_price or 0.0),
|
||||
size=float(intent.target_size),
|
||||
filled_size=float(fill_size),
|
||||
remaining_size=float(remaining_size),
|
||||
reason=reason,
|
||||
raw_payload={
|
||||
"status": status.value,
|
||||
"orderId": order.venue_order_id,
|
||||
"clientOrderId": order.venue_client_id,
|
||||
"symbol": intent.asset,
|
||||
"side": order.side.value,
|
||||
"action": intent.action.value,
|
||||
},
|
||||
metadata={"intent_id": intent.intent_id, "action": intent.action.value},
|
||||
)
|
||||
return event
|
||||
Reference in New Issue
Block a user