launch_dolphin_violet.py: own namespaces hard-set (CH dolphin_violet, HZ DOLPHIN_STATE_VIOLET/PNL, Zinc prefix violet, DOLPHIN-VIOLET-001); own credentials (BINGX_VIOLET_API_KEY/SECRET) — DARK idle with periodic WARNING until provisioned; CH preflight SELECT-probes the required tables and NEVER creates (DDL-before-code); kernel snapshot path repointed away from PINK's fixed /tmp/.pink_kernel_state.json; mainnet hard-disabled; observe loop never calls runtime.step(). ObserveOnlyVenue: submit/cancel raise ObserveOnlyViolation with full attribute delegation — the kernel's venue-submit-failure rollback converts a refusal into a synthetic REJECT (slot back to IDLE), proven against the real kernel. FeedDivergenceMonitor: per-asset scan-vs-venue divergence rows (bookTicker WS via prod/bingx/market_stream, REST fallback) with stale-mid suppression and plane seq propagation — the FET 0.2176-vs-0.1878 detector; runs even DARK (public data). Supervisord [program:dolphin_violet] autostart=false, no keys in conf by design. Violet package: 42 tests green + V0 gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
"""V1: ObserveOnlyVenue — refusal + delegation contract."""
|
|
|
|
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.observe_guard import ObserveOnlyVenue, ObserveOnlyViolation
|
|
|
|
|
|
class _Inner:
|
|
def __init__(self):
|
|
self.connected = False
|
|
self.calls = []
|
|
|
|
def submit(self, intent):
|
|
self.calls.append(("submit", intent))
|
|
return ["should-never-happen"]
|
|
|
|
async def submit_async(self, intent):
|
|
self.calls.append(("submit_async", intent))
|
|
return ["should-never-happen"]
|
|
|
|
def cancel(self, order, reason=""):
|
|
self.calls.append(("cancel", order))
|
|
return []
|
|
|
|
async def cancel_async(self, order, reason=""):
|
|
self.calls.append(("cancel_async", order))
|
|
return []
|
|
|
|
async def connect(self):
|
|
self.connected = True
|
|
|
|
async def subscribe(self):
|
|
if False: # pragma: no cover — generator shape
|
|
yield None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_submit_and_cancel_raise_and_never_reach_inner():
|
|
inner = _Inner()
|
|
guard = ObserveOnlyVenue(inner)
|
|
with pytest.raises(ObserveOnlyViolation):
|
|
guard.submit({"x": 1})
|
|
with pytest.raises(ObserveOnlyViolation):
|
|
guard.cancel(object())
|
|
with pytest.raises(ObserveOnlyViolation):
|
|
await guard.submit_async({})
|
|
with pytest.raises(ObserveOnlyViolation):
|
|
await guard.cancel_async(object())
|
|
assert inner.calls == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_attribute_get_delegates():
|
|
inner = _Inner()
|
|
guard = ObserveOnlyVenue(inner)
|
|
assert guard.connected is False
|
|
await guard.connect()
|
|
assert inner.connected is True and guard.connected is True
|
|
|
|
|
|
def test_attribute_set_delegates_to_inner():
|
|
"""The runtime does `venue._kernel_ref = kernel` — assignment must land
|
|
on the wrapped adapter, not the guard."""
|
|
inner = _Inner()
|
|
guard = ObserveOnlyVenue(inner)
|
|
sentinel = object()
|
|
guard._kernel_ref = sentinel
|
|
assert getattr(inner, "_kernel_ref") is sentinel
|
|
assert not hasattr(type(guard), "_kernel_ref")
|
|
|
|
|
|
def test_wrapped_mock_venue_full_kernel_drive_never_submits():
|
|
"""Real ExecutionKernel over a wrapped MOCK venue: an ENTER intent is
|
|
refused at the venue boundary; the kernel's venue-submit-failure
|
|
rollback converts the refusal into a synthetic REJECT (slot returns to
|
|
IDLE) and the inner venue NEVER sees the order."""
|
|
from prod.clean_arch.dita_v2.launcher import build_launcher_bundle
|
|
from prod.clean_arch.dita_v2.contracts import (
|
|
KernelCommandType,
|
|
KernelIntent,
|
|
TradeSide,
|
|
TradeStage,
|
|
)
|
|
from datetime import datetime, timezone
|
|
|
|
bundle = build_launcher_bundle(venue_mode="MOCK", max_slots=1)
|
|
kernel = bundle.kernel
|
|
inner = kernel.venue
|
|
submitted = []
|
|
orig_submit = inner.submit
|
|
inner.submit = lambda *a, **k: submitted.append(a) or orig_submit(*a, **k)
|
|
kernel.venue = ObserveOnlyVenue(inner)
|
|
|
|
intent = KernelIntent(
|
|
timestamp=datetime.now(timezone.utc),
|
|
intent_id="i-1", trade_id="T-OBS-1", slot_id=0,
|
|
asset="BTCUSDT", side=TradeSide.SHORT,
|
|
action=KernelCommandType.ENTER,
|
|
reference_price=100.0, target_size=1.0, leverage=1.0,
|
|
exit_leg_ratios=(1.0,), reason="test", metadata={},
|
|
stage=TradeStage.INTENT_CREATED,
|
|
)
|
|
outcome = kernel.process_intent(intent)
|
|
assert submitted == [] # nothing reached the venue
|
|
assert kernel.slot(0).is_free() # FSM rolled back to IDLE
|
|
assert any(
|
|
"VENUE_SUBMIT_ERROR" in str(e.reason)
|
|
for e in outcome.emitted_events
|
|
), "expected the synthetic REJECT from the guard refusal"
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(pytest.main([__file__, "-v"]))
|