76 lines
2.3 KiB
Python
76 lines
2.3 KiB
Python
|
|
"""Crash/restart reconcile convergence tests for PINK → DITAv2."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
import unittest
|
||
|
|
|
||
|
|
from prod.clean_arch.dita_v2 import (
|
||
|
|
ExecutionKernel,
|
||
|
|
InMemoryControlPlane,
|
||
|
|
InMemoryZincPlane,
|
||
|
|
KernelCommandType,
|
||
|
|
KernelIntent,
|
||
|
|
MockVenueAdapter,
|
||
|
|
MockVenueScenario,
|
||
|
|
TradeSide,
|
||
|
|
TradeSlot,
|
||
|
|
TradeStage,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class TestRestartReconcile(unittest.TestCase):
|
||
|
|
"""Verify exchange-led state convergence after simulated crash/restart."""
|
||
|
|
|
||
|
|
def setUp(self):
|
||
|
|
self.control = InMemoryControlPlane()
|
||
|
|
self.venue = MockVenueAdapter() # deterministic mock
|
||
|
|
self.kernel = ExecutionKernel(
|
||
|
|
max_slots=2,
|
||
|
|
control_plane=self.control,
|
||
|
|
venue=self.venue,
|
||
|
|
zinc_plane=InMemoryZincPlane(),
|
||
|
|
)
|
||
|
|
|
||
|
|
def _enter_position(self) -> None:
|
||
|
|
intent = KernelIntent(
|
||
|
|
timestamp=datetime.now(timezone.utc),
|
||
|
|
intent_id="entry-001",
|
||
|
|
trade_id="trade-001",
|
||
|
|
slot_id=0,
|
||
|
|
asset="BTCUSDT",
|
||
|
|
side=TradeSide.SHORT,
|
||
|
|
action=KernelCommandType.ENTER,
|
||
|
|
reference_price=65000.0,
|
||
|
|
target_size=0.01,
|
||
|
|
leverage=2.0,
|
||
|
|
reason="restart_test_entry",
|
||
|
|
)
|
||
|
|
self.kernel.process_intent(intent)
|
||
|
|
|
||
|
|
def test_entry_opens_slot(self):
|
||
|
|
self._enter_position()
|
||
|
|
slot = self.kernel.slot(0)
|
||
|
|
self.assertTrue(slot.is_open(),
|
||
|
|
f"Expected open slot after entry, got {slot.fsm_state}")
|
||
|
|
|
||
|
|
def test_reconcile_with_empty_does_not_crash(self):
|
||
|
|
self._enter_position()
|
||
|
|
# Reconcile with empty list — no-op
|
||
|
|
outcome = self.kernel.reconcile_from_slots([])
|
||
|
|
self.assertIsNotNone(outcome,
|
||
|
|
"Reconcile should return an outcome")
|
||
|
|
|
||
|
|
def test_capital_seed_after_reconcile(self):
|
||
|
|
self._enter_position()
|
||
|
|
capital_before = self.kernel.account.snapshot.capital
|
||
|
|
self.assertGreater(capital_before, 0)
|
||
|
|
self.kernel.reconcile_from_slots([])
|
||
|
|
capital_after = self.kernel.account.snapshot.capital
|
||
|
|
self.assertEqual(capital_after, capital_before,
|
||
|
|
"Capital should not change during reconcile")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
unittest.main()
|