138 lines
5.0 KiB
Python
138 lines
5.0 KiB
Python
|
|
"""V2b: ScriptedVenue — directive semantics + kernel CANCEL mapping."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import time
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
sys.path.insert(0, "/mnt/dolphinng5_predict")
|
||
|
|
sys.path.insert(0, "/mnt/dolphinng5_predict/nautilus_dolphin")
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from prod.clean_arch.dita_v2.contracts import (
|
||
|
|
KernelCommandType,
|
||
|
|
KernelEventKind,
|
||
|
|
KernelIntent,
|
||
|
|
TradeSide,
|
||
|
|
TradeStage,
|
||
|
|
)
|
||
|
|
from prod.clean_arch.violet.scripted_venue import Directive, ScriptedVenue
|
||
|
|
|
||
|
|
|
||
|
|
def _intent(tid: str, action=KernelCommandType.ENTER, *, order_type="LIMIT",
|
||
|
|
limit_price=99.5, ref=100.0) -> KernelIntent:
|
||
|
|
return KernelIntent(
|
||
|
|
timestamp=datetime.now(timezone.utc),
|
||
|
|
intent_id=tid, trade_id=tid, slot_id=0,
|
||
|
|
asset="BTCUSDT", side=TradeSide.SHORT, action=action,
|
||
|
|
reference_price=ref, target_size=1.0, leverage=1.0,
|
||
|
|
exit_leg_ratios=(1.0,), reason="test", metadata={},
|
||
|
|
stage=TradeStage.INTENT_CREATED,
|
||
|
|
order_type=order_type, limit_price=limit_price,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_rest_then_expire_acks_only_and_cancel_pops():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T1", Directive.REST_THEN_EXPIRE)
|
||
|
|
events = v.submit(_intent("T1"))
|
||
|
|
assert [e.kind for e in events] == [KernelEventKind.ORDER_ACK]
|
||
|
|
assert len(v.open_orders()) == 1
|
||
|
|
order = v.open_orders()[0]
|
||
|
|
assert order.internal_trade_id == "T1"
|
||
|
|
out = v.cancel(order)
|
||
|
|
assert [e.kind for e in out] == [KernelEventKind.CANCEL_ACK]
|
||
|
|
assert v.open_orders() == []
|
||
|
|
assert v.reconcile() == [] # nothing ever falls due
|
||
|
|
|
||
|
|
|
||
|
|
def test_rest_then_fill_releases_via_reconcile_at_limit_price():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T2", Directive.REST_THEN_FILL, fill_delay_ms=10.0)
|
||
|
|
v.submit(_intent("T2", limit_price=99.25))
|
||
|
|
assert v.reconcile() == [] # not due yet
|
||
|
|
time.sleep(0.02)
|
||
|
|
fills = v.reconcile()
|
||
|
|
assert [e.kind for e in fills] == [KernelEventKind.FULL_FILL]
|
||
|
|
assert fills[0].price == 99.25 # maker fills at the limit
|
||
|
|
assert fills[0].filled_size == 1.0
|
||
|
|
assert v.reconcile() == [] # released exactly once
|
||
|
|
|
||
|
|
|
||
|
|
def test_post_only_reject():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T3", Directive.POST_ONLY_REJECT)
|
||
|
|
events = v.submit(_intent("T3"))
|
||
|
|
assert [e.kind for e in events] == [KernelEventKind.ORDER_REJECT]
|
||
|
|
assert v.open_orders() == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_fill_races_cancel():
|
||
|
|
"""Cancel is rejected and the fill surfaces on the next reconcile —
|
||
|
|
the late-WS-fill-beats-cancel race."""
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T4", Directive.FILL_RACES_CANCEL)
|
||
|
|
v.submit(_intent("T4"))
|
||
|
|
order = v.open_orders()[0]
|
||
|
|
out = v.cancel(order)
|
||
|
|
assert [e.kind for e in out] == [KernelEventKind.CANCEL_REJECT]
|
||
|
|
fills = v.reconcile()
|
||
|
|
assert [e.kind for e in fills] == [KernelEventKind.FULL_FILL]
|
||
|
|
assert fills[0].trade_id == "T4"
|
||
|
|
|
||
|
|
|
||
|
|
def test_cancel_reject_directive():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T5", Directive.CANCEL_REJECT)
|
||
|
|
v.submit(_intent("T5"))
|
||
|
|
out = v.cancel(v.open_orders()[0])
|
||
|
|
assert [e.kind for e in out] == [KernelEventKind.CANCEL_REJECT]
|
||
|
|
assert v.reconcile() == [] # no phantom fill
|
||
|
|
|
||
|
|
|
||
|
|
def test_prefix_lookup_covers_retries():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
v.set_directive("T6", Directive.REST_THEN_EXPIRE)
|
||
|
|
v.set_directive("T6-r1", Directive.IMMEDIATE_FILL)
|
||
|
|
assert [e.kind for e in v.submit(_intent("T6"))] == [KernelEventKind.ORDER_ACK]
|
||
|
|
kinds = [e.kind for e in v.submit(_intent("T6-r1"))]
|
||
|
|
assert KernelEventKind.FULL_FILL in kinds # own directive wins
|
||
|
|
kinds_m = [e.kind for e in v.submit(_intent("T6-m"))]
|
||
|
|
assert kinds_m == [KernelEventKind.ORDER_ACK] # falls back to T6 prefix
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_directive_is_parent_default():
|
||
|
|
v = ScriptedVenue()
|
||
|
|
kinds = [e.kind for e in v.submit(_intent("T7"))]
|
||
|
|
assert KernelEventKind.FULL_FILL in kinds
|
||
|
|
|
||
|
|
|
||
|
|
def test_kernel_cancel_reaches_venue_with_right_order():
|
||
|
|
"""Full kernel drive: ENTER (resting LIMIT) then CANCEL via
|
||
|
|
process_intent — the CANCEL must reach ScriptedVenue.cancel with the
|
||
|
|
venue_order_id of the resting quote and pop it."""
|
||
|
|
from prod.clean_arch.dita_v2.launcher import build_launcher_bundle
|
||
|
|
|
||
|
|
venue = ScriptedVenue()
|
||
|
|
venue.set_directive("T-CXL", Directive.REST_THEN_EXPIRE)
|
||
|
|
bundle = build_launcher_bundle(venue_mode="MOCK", max_slots=1, venue=venue)
|
||
|
|
kernel = bundle.kernel
|
||
|
|
|
||
|
|
kernel.process_intent(_intent("T-CXL"))
|
||
|
|
assert venue.submits == ["T-CXL"]
|
||
|
|
assert len(venue.open_orders()) == 1
|
||
|
|
resting_oid = venue.open_orders()[0].venue_order_id
|
||
|
|
|
||
|
|
cancel = _intent("T-CXL", action=KernelCommandType.CANCEL,
|
||
|
|
order_type="MARKET", limit_price=0.0)
|
||
|
|
kernel.process_intent(cancel)
|
||
|
|
assert venue.cancels == ["T-CXL"]
|
||
|
|
assert venue.open_orders() == [] # CANCEL_ACK popped it
|
||
|
|
assert resting_oid # mapping existed end-to-end
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(pytest.main([__file__, "-v"]))
|