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>
210 lines
8.4 KiB
Python
210 lines
8.4 KiB
Python
"""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
|