from __future__ import annotations from dataclasses import dataclass from datetime import datetime, timezone import math import pytest from prod.clean_arch.dita_v2 import ( BackendMode, ControlUpdate, ExecutionKernel, InMemoryControlPlane, InMemoryZincPlane, KernelCommandType, KernelControlSnapshot, KernelDiagnosticCode, KernelEventKind, KernelIntent, KernelMode, KernelVerbosity, MemoryKernelJournal, TradeSide, TradeSlot, TradeStage, VenueEvent, VenueEventStatus, VenueOrder, VenueOrderStatus, ) class NoopVenueAdapter: def submit(self, intent): # type: ignore[override] return [] def cancel(self, order, *, reason: str = ""): # type: ignore[override] return [] def open_orders(self): # type: ignore[override] return [] def open_positions(self): # type: ignore[override] return [] def reconcile(self): # type: ignore[override] return [] @dataclass(frozen=True) class IntentGuardCase: name: str slot_id: int seed_state: str action: KernelCommandType trade_id: str intent_trade_id: str expected_state: TradeStage expected_code: KernelDiagnosticCode expected_accepted: bool @dataclass(frozen=True) class DuplicateCase: name: str seed_state: str first_kind: KernelEventKind second_kind: KernelEventKind expected_state: TradeStage event_factory_name: str @dataclass(frozen=True) class StaleCase: name: str second_kind: KernelEventKind same_event_id_as_initial: bool expected_accepted: bool @dataclass(frozen=True) class ZincMirrorCase: name: str op: str @dataclass(frozen=True) class SlotRigorCase: name: str op: str def _build_kernel(slot_count: int = 3) -> tuple[ExecutionKernel, MemoryKernelJournal, InMemoryZincPlane]: journal = MemoryKernelJournal() zinc = InMemoryZincPlane() kernel = ExecutionKernel( max_slots=slot_count, control_plane=InMemoryControlPlane( KernelControlSnapshot( mode=KernelMode.DEBUG, verbosity=KernelVerbosity.TRACE, backend_mode=BackendMode.MOCK, debug_clickhouse_enabled=True, trace_transitions=True, mirror_to_hazelcast=True, ) ), venue=NoopVenueAdapter(), journal=journal, zinc_plane=zinc, ) return kernel, journal, zinc def _make_entry_order(trade_id: str, slot_id: int, *, size: float = 1.0, status: VenueOrderStatus = VenueOrderStatus.NEW) -> VenueOrder: return VenueOrder( internal_trade_id=trade_id, venue_order_id=f"V-ENTRY-{slot_id}-{trade_id}", venue_client_id=f"{trade_id}:entry:{slot_id}", side=TradeSide.SHORT, intended_size=size, filled_size=size if status == VenueOrderStatus.FILLED else 0.0, average_fill_price=100.0, status=status, metadata={"slot_id": slot_id}, ) def _make_exit_order(trade_id: str, slot_id: int, *, size: float, status: VenueOrderStatus = VenueOrderStatus.NEW) -> VenueOrder: return VenueOrder( internal_trade_id=trade_id, venue_order_id=f"V-EXIT-{slot_id}-{trade_id}", venue_client_id=f"{trade_id}:exit:{slot_id}", side=TradeSide.SHORT, intended_size=size, filled_size=size if status == VenueOrderStatus.FILLED else 0.0, average_fill_price=99.0, status=status, metadata={"slot_id": slot_id}, ) def _seed_free_slot(slot_id: int) -> TradeSlot: return TradeSlot(slot_id=slot_id) def _seed_entry_working_slot(trade_id: str, slot_id: int) -> TradeSlot: return TradeSlot( slot_id=slot_id, trade_id=trade_id, asset="BTCUSDT", side=TradeSide.SHORT, entry_price=0.0, size=0.0, initial_size=0.0, leverage=2.0, entry_time=datetime.now(timezone.utc), exit_leg_ratios=(1.0,), active_leg_index=0, active_entry_order=_make_entry_order(trade_id, slot_id, status=VenueOrderStatus.NEW), fsm_state=TradeStage.ENTRY_WORKING, ) def _seed_position_open_slot(trade_id: str, slot_id: int, *, size: float = 1.0, side: TradeSide = TradeSide.SHORT) -> TradeSlot: return TradeSlot( slot_id=slot_id, trade_id=trade_id, asset="BTCUSDT", side=side, entry_price=100.0, size=size, initial_size=size, leverage=2.0, entry_time=datetime.now(timezone.utc), exit_leg_ratios=(0.5, 0.5), active_leg_index=0, active_entry_order=_make_entry_order(trade_id, slot_id, size=size, status=VenueOrderStatus.FILLED), fsm_state=TradeStage.POSITION_OPEN, ) def _seed_exit_working_slot(trade_id: str, slot_id: int, *, size: float = 1.0) -> TradeSlot: slot = _seed_position_open_slot(trade_id, slot_id, size=size) slot.active_exit_order = _make_exit_order(trade_id, slot_id, size=slot.next_exit_ratio() * size, status=VenueOrderStatus.NEW) slot.fsm_state = TradeStage.EXIT_WORKING return slot def _seed_closed_slot(trade_id: str, slot_id: int) -> TradeSlot: return TradeSlot( slot_id=slot_id, trade_id=trade_id, asset="BTCUSDT", side=TradeSide.SHORT, entry_price=100.0, size=0.0, initial_size=1.0, leverage=2.0, entry_time=datetime.now(timezone.utc), closed=True, fsm_state=TradeStage.CLOSED, ) def _make_intent( *, trade_id: str, slot_id: int, action: KernelCommandType, leverage: float = 2.0, size: float = 1.0, side: TradeSide = TradeSide.SHORT, reason: str = "HARNESS", ) -> KernelIntent: return KernelIntent( timestamp=datetime.now(timezone.utc), intent_id=f"intent-{trade_id}-{action.value}-{slot_id}", trade_id=trade_id, slot_id=slot_id, asset="BTCUSDT", side=side, action=action, reference_price=100.0, target_size=size, leverage=leverage, exit_leg_ratios=(0.5, 0.5) if action == KernelCommandType.EXIT else (1.0,), reason=reason, ) def _make_event( slot: TradeSlot, *, kind: KernelEventKind, event_id: str, filled_size: float = 0.0, reason: str = "", slot_id: int | None = None, ) -> VenueEvent: order = slot.active_exit_order or slot.active_entry_order venue_order_id = order.venue_order_id if order else f"V-{kind.value}-{slot.slot_id}" venue_client_id = order.venue_client_id if order else f"{slot.trade_id}:client:{slot.slot_id}" status = { KernelEventKind.ORDER_ACK: VenueEventStatus.ACKED, KernelEventKind.ORDER_REJECT: VenueEventStatus.REJECTED, KernelEventKind.PARTIAL_FILL: VenueEventStatus.PARTIALLY_FILLED, KernelEventKind.FULL_FILL: VenueEventStatus.FILLED, KernelEventKind.CANCEL_ACK: VenueEventStatus.CANCELED, KernelEventKind.CANCEL_REJECT: VenueEventStatus.CANCELED_REJECTED, KernelEventKind.MARK_PRICE: VenueEventStatus.ACKED, KernelEventKind.RECONCILE: VenueEventStatus.ACKED, KernelEventKind.CONTROL: VenueEventStatus.ACKED, }[kind] return VenueEvent( timestamp=datetime.now(timezone.utc), event_id=event_id, trade_id=slot.trade_id, slot_id=slot.slot_id if slot_id is None else slot_id, kind=kind, status=status, venue_order_id=venue_order_id, venue_client_id=venue_client_id, side=slot.side if slot.side != TradeSide.FLAT else TradeSide.SHORT, asset=slot.asset or "BTCUSDT", price=99.0 if kind == KernelEventKind.MARK_PRICE else 100.0, size=max(slot.size, 1.0), filled_size=filled_size, remaining_size=max(0.0, max(slot.size, 1.0) - filled_size), reason=reason, ) INTENT_GUARD_CASES = [ IntentGuardCase("invalid_negative_enter", -1, "free", KernelCommandType.ENTER, "trade-neg", "trade-neg", TradeStage.IDLE, KernelDiagnosticCode.INVALID_SLOT_ID, False), IntentGuardCase("invalid_high_exit", 99, "free", KernelCommandType.EXIT, "trade-high", "trade-high", TradeStage.IDLE, KernelDiagnosticCode.INVALID_SLOT_ID, False), IntentGuardCase("unsupported_control", 0, "free", KernelCommandType.CONTROL, "trade-control", "trade-control", TradeStage.IDLE, KernelDiagnosticCode.UNSUPPORTED_INTENT, False), IntentGuardCase("free_exit", 0, "free", KernelCommandType.EXIT, "trade-free-exit", "trade-free-exit", TradeStage.IDLE, KernelDiagnosticCode.NO_OPEN_POSITION, False), IntentGuardCase("free_cancel", 0, "free", KernelCommandType.CANCEL, "trade-free-cancel", "trade-free-cancel", TradeStage.IDLE, KernelDiagnosticCode.NO_ACTIVE_EXIT_ORDER, False), IntentGuardCase("busy_enter_different_trade", 0, "position_open", KernelCommandType.ENTER, "trade-open", "trade-new", TradeStage.POSITION_OPEN, KernelDiagnosticCode.SLOT_BUSY, False), IntentGuardCase("same_trade_enter_allowed", 0, "position_open", KernelCommandType.ENTER, "trade-open", "trade-open", TradeStage.ORDER_REQUESTED, KernelDiagnosticCode.OK, True), IntentGuardCase("closed_exit", 0, "closed", KernelCommandType.EXIT, "trade-closed", "trade-closed", TradeStage.CLOSED, KernelDiagnosticCode.NO_OPEN_POSITION, False), IntentGuardCase("open_reconcile", 0, "position_open", KernelCommandType.RECONCILE, "trade-reconcile", "trade-reconcile", TradeStage.STALE_STATE_RECONCILING, KernelDiagnosticCode.STALE_STATE_RECONCILE, True), IntentGuardCase("free_mark_price", 0, "free", KernelCommandType.MARK_PRICE, "trade-mark", "trade-mark", TradeStage.IDLE, KernelDiagnosticCode.OK, True), ] DUPLICATE_CASES = [ DuplicateCase("entry_ack_duplicate", "entry_working", KernelEventKind.ORDER_ACK, KernelEventKind.ORDER_ACK, TradeStage.ENTRY_WORKING, "ack"), DuplicateCase("entry_partial_duplicate", "entry_working", KernelEventKind.PARTIAL_FILL, KernelEventKind.PARTIAL_FILL, TradeStage.ENTRY_WORKING, "partial-entry"), DuplicateCase("entry_full_duplicate", "entry_working", KernelEventKind.FULL_FILL, KernelEventKind.FULL_FILL, TradeStage.POSITION_OPEN, "full-entry"), DuplicateCase("exit_ack_duplicate", "exit_working", KernelEventKind.CANCEL_ACK, KernelEventKind.CANCEL_ACK, TradeStage.POSITION_OPEN, "ack-exit"), DuplicateCase("exit_partial_duplicate", "exit_working", KernelEventKind.PARTIAL_FILL, KernelEventKind.PARTIAL_FILL, TradeStage.EXIT_WORKING, "partial-exit"), DuplicateCase("exit_full_duplicate", "exit_working", KernelEventKind.FULL_FILL, KernelEventKind.FULL_FILL, TradeStage.CLOSED, "full-exit"), DuplicateCase("cancel_reject_duplicate", "exit_working", KernelEventKind.CANCEL_REJECT, KernelEventKind.CANCEL_REJECT, TradeStage.EXIT_WORKING, "reject-exit"), DuplicateCase("mark_price_duplicate", "position_open", KernelEventKind.MARK_PRICE, KernelEventKind.MARK_PRICE, TradeStage.POSITION_OPEN, "mark"), DuplicateCase("reconcile_duplicate", "position_open", KernelEventKind.RECONCILE, KernelEventKind.RECONCILE, TradeStage.STALE_STATE_RECONCILING, "reconcile"), DuplicateCase("entry_reject_duplicate", "entry_working", KernelEventKind.ORDER_REJECT, KernelEventKind.ORDER_REJECT, TradeStage.IDLE, "reject-entry"), ] STALE_CASES = [ StaleCase("stale_ack", KernelEventKind.ORDER_ACK, False, False), StaleCase("stale_reject", KernelEventKind.ORDER_REJECT, False, False), StaleCase("stale_partial", KernelEventKind.PARTIAL_FILL, False, False), StaleCase("stale_full", KernelEventKind.FULL_FILL, False, False), StaleCase("stale_cancel_ack", KernelEventKind.CANCEL_ACK, False, False), StaleCase("stale_cancel_reject", KernelEventKind.CANCEL_REJECT, False, False), StaleCase("stale_mark_price", KernelEventKind.MARK_PRICE, False, False), StaleCase("stale_control", KernelEventKind.CONTROL, False, False), StaleCase("stale_reconcile", KernelEventKind.RECONCILE, False, True), StaleCase("stale_duplicate_precedence", KernelEventKind.ORDER_ACK, True, False), ] ZINC_MIRROR_CASES = [ ZincMirrorCase("intent_published_on_enter", "intent"), ZincMirrorCase("invalid_slot_intent_still_publishes", "invalid_intent"), ZincMirrorCase("slot_write_updates_state_region", "direct_write"), ZincMirrorCase("venue_event_updates_state_region", "venue_event"), ZincMirrorCase("control_update_writes_region", "control_update"), ZincMirrorCase("snapshot_reflects_control", "snapshot"), ZincMirrorCase("reconcile_from_slots_writes_all", "reconcile"), ZincMirrorCase("free_slot_selects_first_free", "free_slot"), ZincMirrorCase("read_slots_sorted", "sorted_read"), ZincMirrorCase("slot_overwrite_replaces_previous_state", "overwrite"), ] SLOT_RIGOR_CASES = [ SlotRigorCase("idle_slot_is_free", "idle_free"), SlotRigorCase("closed_slot_is_free", "closed_free"), SlotRigorCase("entry_working_is_not_free", "entry_not_free"), SlotRigorCase("open_slot_is_not_free", "open_not_free"), SlotRigorCase("mark_price_zero_is_noop", "mark_zero"), SlotRigorCase("mark_price_negative_is_noop", "mark_negative"), SlotRigorCase("mark_price_nan_is_noop", "mark_nan"), SlotRigorCase("short_price_rise_negative_pnl", "short_rise"), SlotRigorCase("short_price_drop_positive_pnl", "short_drop"), SlotRigorCase("exit_leg_consume_and_clamp", "exit_leg"), ] def _seed_for_intent_case(kernel: ExecutionKernel, case: IntentGuardCase) -> None: if case.seed_state == "free": return if case.seed_state == "entry_working": kernel._set_slot(_seed_entry_working_slot(case.trade_id, case.slot_id)) return if case.seed_state == "position_open": kernel._set_slot(_seed_position_open_slot(case.trade_id, case.slot_id)) return if case.seed_state == "exit_working": kernel._set_slot(_seed_exit_working_slot(case.trade_id, case.slot_id)) return if case.seed_state == "closed": kernel._set_slot(_seed_closed_slot(case.trade_id, case.slot_id)) return raise AssertionError(case.seed_state) def _seed_for_duplicate_case(kernel: ExecutionKernel, case: DuplicateCase) -> TradeSlot: if case.seed_state == "entry_working": slot = _seed_entry_working_slot(f"trade-{case.name}", 0) elif case.seed_state == "exit_working": slot = _seed_exit_working_slot(f"trade-{case.name}", 0) elif case.seed_state == "position_open": slot = _seed_position_open_slot(f"trade-{case.name}", 0) elif case.seed_state == "stale": slot = _seed_position_open_slot(f"trade-{case.name}", 0) else: raise AssertionError(case.seed_state) kernel._set_slot(slot) return kernel._get_slot(0) def _seed_for_stale_case(kernel: ExecutionKernel) -> TradeSlot: slot = _seed_position_open_slot("trade-stale", 0) kernel._set_slot(slot) return kernel._get_slot(0) def _seed_for_zinc_case(kernel: ExecutionKernel, case: ZincMirrorCase) -> None: if case.op == "intent": return if case.op == "invalid_intent": return if case.op == "direct_write": kernel._set_slot(_seed_position_open_slot("trade-write", 0)) return if case.op == "venue_event": kernel._set_slot(_seed_entry_working_slot("trade-event", 0)) return if case.op == "control_update": return if case.op == "snapshot": return if case.op == "reconcile": return if case.op == "free_slot": kernel._set_slot(_seed_position_open_slot("trade-free", 0)) kernel._set_slot(_seed_free_slot(1)) return if case.op == "sorted_read": return if case.op == "overwrite": return raise AssertionError(case.op) @pytest.mark.parametrize("case", INTENT_GUARD_CASES, ids=lambda case: case.name) def test_kernel_intent_guard_matrix(case: IntentGuardCase) -> None: kernel, _, zinc = _build_kernel() _seed_for_intent_case(kernel, case) intent = _make_intent( trade_id=case.intent_trade_id, slot_id=case.slot_id, action=case.action, leverage=-3.0 if case.name == "same_trade_enter_allowed" else 2.5, size=1.0, reason=case.name, ) outcome = kernel.process_intent(intent) assert outcome.accepted is case.expected_accepted assert outcome.diagnostic_code == case.expected_code assert outcome.state == case.expected_state if case.slot_id >= 0 and case.slot_id < kernel.max_slots: assert zinc.intent_region assert zinc.intent_region[-1].intent_id == intent.intent_id if case.name == "same_trade_enter_allowed": current = kernel.slot(case.slot_id).to_dict() assert current["fsm_state"] == TradeStage.ORDER_REQUESTED.value assert current["leverage"] == 1.0 @pytest.mark.parametrize("case", DUPLICATE_CASES, ids=lambda case: case.name) def test_kernel_duplicate_event_matrix(case: DuplicateCase) -> None: kernel, _, _ = _build_kernel() slot = _seed_for_duplicate_case(kernel, case) fill_size = slot.size or 1.0 if case.seed_state == "exit_working" and case.first_kind == KernelEventKind.PARTIAL_FILL: fill_size = max(0.1, fill_size * 0.4) first_event = _make_event(slot, kind=case.first_kind, event_id=f"dup-{case.name}", filled_size=fill_size) first = kernel.on_venue_event(first_event) second = kernel.on_venue_event(first_event) assert first.diagnostic_code in { KernelDiagnosticCode.OK, KernelDiagnosticCode.STALE_STATE_RECONCILE, KernelDiagnosticCode.ENTRY_ORDER_REJECTED, KernelDiagnosticCode.EXIT_ORDER_REJECTED, KernelDiagnosticCode.ORDER_REJECTED, KernelDiagnosticCode.CANCEL_REJECTED, } assert second.diagnostic_code == KernelDiagnosticCode.DUPLICATE_EVENT assert second.state == case.expected_state assert second.accepted is True assert kernel.slot(0).to_dict()["seen_event_ids"].count(first_event.event_id) == 1 @pytest.mark.parametrize("case", STALE_CASES, ids=lambda case: case.name) def test_kernel_stale_state_matrix(case: StaleCase) -> None: kernel, _, _ = _build_kernel() slot = _seed_for_stale_case(kernel) initial = _make_event(slot, kind=KernelEventKind.RECONCILE, event_id="stale-entry", filled_size=slot.size or 1.0) initial_outcome = kernel.on_venue_event(initial) assert initial_outcome.diagnostic_code == KernelDiagnosticCode.OK assert kernel.slot(0).fsm_state == TradeStage.STALE_STATE_RECONCILING if case.same_event_id_as_initial: event = _make_event(kernel._get_slot(0), kind=case.second_kind, event_id="stale-entry", filled_size=slot.size or 1.0, reason=case.name) else: event = _make_event(kernel._get_slot(0), kind=case.second_kind, event_id=f"stale-{case.name}", filled_size=slot.size or 1.0, reason=case.name) outcome = kernel.on_venue_event(event) if case.same_event_id_as_initial: assert outcome.diagnostic_code == KernelDiagnosticCode.DUPLICATE_EVENT assert outcome.accepted is True else: assert outcome.diagnostic_code == KernelDiagnosticCode.STALE_STATE_RECONCILE assert outcome.accepted is case.expected_accepted assert outcome.state == TradeStage.STALE_STATE_RECONCILING assert kernel.slot(0).fsm_state == TradeStage.STALE_STATE_RECONCILING @pytest.mark.parametrize("case", ZINC_MIRROR_CASES, ids=lambda case: case.name) def test_kernel_zinc_mirror_matrix(case: ZincMirrorCase) -> None: kernel, _, zinc = _build_kernel() _seed_for_zinc_case(kernel, case) if case.op == "intent": intent = _make_intent(trade_id="trade-intent", slot_id=0, action=KernelCommandType.ENTER, size=1.25) outcome = kernel.process_intent(intent) assert outcome.accepted is True assert zinc.intent_region assert zinc.intent_region[-1].intent_id == intent.intent_id assert zinc.read_slots()[0].trade_id == "trade-intent" elif case.op == "invalid_intent": intent = _make_intent(trade_id="trade-invalid", slot_id=-1, action=KernelCommandType.EXIT, size=1.0) outcome = kernel.process_intent(intent) assert outcome.diagnostic_code == KernelDiagnosticCode.INVALID_SLOT_ID assert len(zinc.intent_region) == 1 assert zinc.intent_region[-1].intent_id == intent.intent_id elif case.op == "direct_write": slot = _seed_position_open_slot("trade-write", 0, size=1.5) kernel._set_slot(slot) mirrored = zinc.read_slots()[0] assert mirrored.trade_id == "trade-write" assert mirrored.size == 1.5 assert mirrored.fsm_state == TradeStage.POSITION_OPEN elif case.op == "venue_event": slot = kernel._get_slot(0) event = _make_event(slot, kind=KernelEventKind.FULL_FILL, event_id="zinc-fill", filled_size=slot.size or 1.0) outcome = kernel.on_venue_event(event) assert outcome.diagnostic_code == KernelDiagnosticCode.OK mirrored = zinc.read_slots()[0] assert mirrored.fsm_state == TradeStage.POSITION_OPEN assert mirrored.seen_event_ids == ("zinc-fill",) elif case.op == "control_update": snapshot = kernel.update_control( ControlUpdate( mode=KernelMode.DEBUG, verbosity=KernelVerbosity.TRACE, trace_transitions=True, mirror_to_hazelcast=False, ) ) assert snapshot.mode == KernelMode.DEBUG assert zinc.read_control().mode == KernelMode.DEBUG assert zinc.read_control().trace_transitions is True elif case.op == "snapshot": kernel.update_control(ControlUpdate(mode=KernelMode.DEBUG, verbosity=KernelVerbosity.VERBOSE)) payload = kernel.snapshot() assert payload["control"]["mode"] == KernelMode.DEBUG.value assert payload["control"]["verbosity"] == KernelVerbosity.VERBOSE.value elif case.op == "reconcile": slots = [ _seed_position_open_slot("trade-a", 2), _seed_closed_slot("trade-b", 0), _seed_free_slot(1), ] outcome = kernel.reconcile_from_slots(slots) assert outcome.diagnostic_code == KernelDiagnosticCode.RECONCILED mirrored_ids = [slot.slot_id for slot in zinc.read_slots()] assert mirrored_ids == [0, 1, 2] elif case.op == "free_slot": assert kernel.free_slot().slot_id == 1 elif case.op == "sorted_read": kernel._set_slot(_seed_position_open_slot("trade-c", 2)) kernel._set_slot(_seed_position_open_slot("trade-a", 0)) kernel._set_slot(_seed_position_open_slot("trade-b", 1)) ids = [slot.slot_id for slot in zinc.read_slots()] assert ids == [0, 1, 2] elif case.op == "overwrite": kernel._set_slot(_seed_position_open_slot("trade-old", 0, size=1.0)) kernel._set_slot(_seed_position_open_slot("trade-new", 0, size=2.0)) mirrored = zinc.read_slots()[0] assert mirrored.trade_id == "trade-new" assert mirrored.size == 2.0 assert mirrored.initial_size == 2.0 else: # pragma: no cover - exhaustive raise AssertionError(case.op) @pytest.mark.parametrize("case", SLOT_RIGOR_CASES, ids=lambda case: case.name) def test_trade_slot_state_machine_rigor_matrix(case: SlotRigorCase) -> None: if case.op == "idle_free": slot = TradeSlot(slot_id=0) assert slot.is_free() is True assert slot.is_open() is False elif case.op == "closed_free": slot = _seed_closed_slot("trade-closed", 0) assert slot.is_free() is True assert slot.is_open() is False elif case.op == "entry_not_free": slot = _seed_entry_working_slot("trade-entry", 0) assert slot.is_free() is False assert slot.is_open() is True elif case.op == "open_not_free": slot = _seed_position_open_slot("trade-open", 0) assert slot.is_free() is False assert slot.is_open() is True elif case.op == "mark_zero": slot = _seed_position_open_slot("trade-mark", 0, size=1.0) slot.mark_price(0.0) assert slot.unrealized_pnl == 0.0 elif case.op == "mark_negative": slot = _seed_position_open_slot("trade-mark", 0, size=1.0) slot.mark_price(-10.0) assert slot.unrealized_pnl == 0.0 elif case.op == "mark_nan": slot = _seed_position_open_slot("trade-mark", 0, size=1.0) slot.mark_price(float("nan")) assert slot.unrealized_pnl == 0.0 elif case.op == "short_rise": slot = _seed_position_open_slot("trade-short-rise", 0, size=1.0, side=TradeSide.SHORT) slot.mark_price(110.0) assert slot.unrealized_pnl < 0.0 elif case.op == "short_drop": slot = _seed_position_open_slot("trade-short-drop", 0, size=1.0, side=TradeSide.SHORT) slot.mark_price(90.0) assert slot.unrealized_pnl > 0.0 elif case.op == "exit_leg": slot = _seed_position_open_slot("trade-leg", 0, size=1.0) slot.exit_leg_ratios = (0.25, 0.75) first = slot.consume_exit_leg() second = slot.consume_exit_leg() third = slot.consume_exit_leg() assert first == 0.25 assert second == 0.75 assert third == 1.0 assert slot.active_leg_index == 2 assert slot.next_exit_ratio() == 1.0 else: # pragma: no cover - exhaustive raise AssertionError(case.op)