PINK: fix trade_seq always-zero — unique trade_id per position
trade_seq was missing from the kernel account snapshot dict, so the intent engine always computed trade_id = "BTCUSDT-T-000000000001". The Rust FSM SLOT_BUSY guard only fires on *different* trade_ids; with the same ID it resets the slot and submits a new exchange order on each ENTER signal tick (~86 duplicate orders observed in one session). Fix: - Add _slot_was_closed dict to ExecutionKernel; set False on ENTER accepted (both sync/async), True on on_venue_event when slot.closed - Increment account.snapshot.trade_seq on the IDLE→CLOSED transition - Expose trade_seq in snapshot()["account"] so DecisionContext carries the correct counter → intent engine generates unique IDs per trade 451/451 tests green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -668,6 +668,7 @@ class ExecutionKernel:
|
||||
self._backend = _get_rust().create(self.max_slots)
|
||||
self._control_snapshot = self.control_plane.read()
|
||||
self._last_settled_pnl: Dict[int, float] = {}
|
||||
self._slot_was_closed: Dict[int, bool] = {}
|
||||
self.projection.write_control(self._control_snapshot)
|
||||
self.zinc_plane.update_control(self._control_snapshot)
|
||||
self.state = KernelStateView(self)
|
||||
@@ -796,6 +797,7 @@ class ExecutionKernel:
|
||||
self.state.refresh()
|
||||
if intent.action == KernelCommandType.ENTER and outcome.accepted:
|
||||
self._last_settled_pnl[intent.slot_id] = 0.0
|
||||
self._slot_was_closed[intent.slot_id] = False
|
||||
emitted_events = []
|
||||
all_venue_transitions: List[KernelTransition] = []
|
||||
if outcome.accepted and intent.action in {KernelCommandType.ENTER, KernelCommandType.EXIT}:
|
||||
@@ -943,6 +945,7 @@ class ExecutionKernel:
|
||||
self.state.refresh()
|
||||
if intent.action == KernelCommandType.ENTER and outcome.accepted:
|
||||
self._last_settled_pnl[intent.slot_id] = 0.0
|
||||
self._slot_was_closed[intent.slot_id] = False
|
||||
emitted_events: List[VenueEvent] = []
|
||||
all_venue_transitions: List[KernelTransition] = []
|
||||
if outcome.accepted and intent.action in {KernelCommandType.ENTER, KernelCommandType.EXIT}:
|
||||
@@ -1040,6 +1043,14 @@ class ExecutionKernel:
|
||||
if abs(incremental_pnl) > 1e-12:
|
||||
self.account.settle(incremental_pnl)
|
||||
self._last_settled_pnl[slot.slot_id] = slot.realized_pnl
|
||||
# Increment trade_seq when a slot transitions from open → closed so
|
||||
# the next ENTER gets a unique trade_id. Without this increment the
|
||||
# intent engine always generates "…-T-000000000001" and the Rust FSM
|
||||
# resets (not SLOT_BUSYs) the slot on every duplicate ENTER signal.
|
||||
was_closed = self._slot_was_closed.get(slot.slot_id, True)
|
||||
if slot.closed and not was_closed:
|
||||
self.account.snapshot.trade_seq += 1
|
||||
self._slot_was_closed[slot.slot_id] = slot.closed
|
||||
slots = [self._get_slot(i) for i in range(self.max_slots)]
|
||||
self.account.observe_slots(slots)
|
||||
current = self._get_slot(slot.slot_id)
|
||||
@@ -1192,6 +1203,7 @@ class ExecutionKernel:
|
||||
"open_positions": self.account.snapshot.open_positions,
|
||||
"open_notional": self.account.snapshot.open_notional,
|
||||
"leverage": self.account.snapshot.leverage,
|
||||
"trade_seq": self.account.snapshot.trade_seq,
|
||||
# V2 — kernel atomic K/E account (E rules; K is parallel check)
|
||||
"k_capital": rust_account.get("k_capital", self.account.snapshot.capital),
|
||||
"k_realized_pnl": rust_account.get("k_realized_pnl", self.account.snapshot.realized_pnl),
|
||||
|
||||
Reference in New Issue
Block a user