PINK kernel fix-of-fixes: slot-PnL repair plumbing
Review of PINK_ACCOUNTING_EXEC_FIX execution found the Phase 3.2 repair path triply broken: (1) !closed guard blocked repair on terminal fills — the common price-less case; (2) wrapper on_account_event was a raw FFI passthrough so repairs never settled into published capital; (3) live FILL_SETTLED carried no slot_id and realized_pnl=0 (pre-folded) — repair was dead code. Fixes: repair allowed on closed slots (flag+dedup keep it idempotent); wrapper settles the baseline diff on FILL_SETTLED-with-slot_id; dedicated repair_realized_pnl field avoids double-folding the K-ledger; _FakeKernelAccount fixture mirrors the Phase 1 anchor_to_exchange contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1035,13 +1035,29 @@ impl KernelCore {
|
||||
// realized_pnl and clear the flag.
|
||||
if let Some(slot_id) = parsed.get("slot_id").and_then(|v| v.as_u64()) {
|
||||
let sid = slot_id as usize;
|
||||
if sid < self.slots.len() && !self.slots[sid].closed {
|
||||
// NOTE: closed slots MUST be repairable — the most common
|
||||
// price-less leg is the TERMINAL fill, where the slot is
|
||||
// already CLOSED by the time FILL_SETTLED arrives. The
|
||||
// was_skipped flag + account-event dedup keep this
|
||||
// idempotent; a `!closed` guard would permanently lose
|
||||
// exactly the legs the repair exists for.
|
||||
if sid < self.slots.len() {
|
||||
let was_skipped = self.slots[sid].metadata
|
||||
.get("realized_skipped_no_price")
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(false);
|
||||
if was_skipped && realized.is_finite() && realized != 0.0 {
|
||||
self.slots[sid].realized_pnl += realized;
|
||||
// The K-fold field (`realized_pnl`) is 0 on the live
|
||||
// path because PREDICTED_FILL already folded it — the
|
||||
// slot repair uses its own field so the same number
|
||||
// is never double-folded into k_realized_pnl.
|
||||
// Falls back to `realized_pnl` for callers that only
|
||||
// send the legacy shape.
|
||||
let repair = parsed
|
||||
.get("repair_realized_pnl")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(realized);
|
||||
if was_skipped && repair.is_finite() && repair != 0.0 {
|
||||
self.slots[sid].realized_pnl += repair;
|
||||
self.slots[sid].metadata.insert(
|
||||
"realized_skipped_no_price".to_string(),
|
||||
Value::Bool(false),
|
||||
|
||||
@@ -1156,7 +1156,33 @@ class ExecutionKernel:
|
||||
available_capital (E rules when present), k_capital, event_seq,
|
||||
capital_frozen (bool), duplicate_event (bool if deduplicated).
|
||||
"""
|
||||
return _get_rust().on_account_event(self._backend, event)
|
||||
result = _get_rust().on_account_event(self._backend, event)
|
||||
# Settle slot-level repairs into published capital. The Rust
|
||||
# FILL_SETTLED handler may add exchange realized PnL to a slot whose
|
||||
# exit leg booked 0 (price-less fill); the settle-baseline diff
|
||||
# otherwise only runs in on_venue_event, so a repair — especially on
|
||||
# an already-CLOSED slot (terminal fill, the common case) — would
|
||||
# never reach AccountProjection.capital.
|
||||
try:
|
||||
kind = str(event.get("kind", "") or "").upper()
|
||||
sid_raw = event.get("slot_id")
|
||||
if kind == "FILL_SETTLED" and sid_raw is not None:
|
||||
sid = int(sid_raw)
|
||||
if 0 <= sid < self.max_slots:
|
||||
slot = self._get_slot(sid)
|
||||
incremental = slot.realized_pnl - self._last_settled_pnl.get(sid, 0.0)
|
||||
if abs(incremental) > 1e-12:
|
||||
self.account.settle(incremental)
|
||||
self._last_settled_pnl[sid] = slot.realized_pnl
|
||||
self.account.observe_slots(
|
||||
[self._get_slot(i) for i in range(self.max_slots)]
|
||||
)
|
||||
current = self._get_slot(sid)
|
||||
self.projection.write_slot(current)
|
||||
self.zinc_plane.write_slot(current)
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Snapshot / restore — session-to-session state continuity
|
||||
|
||||
@@ -672,6 +672,14 @@ class PinkDirectRuntime:
|
||||
"realized_pnl": 0.0, # already folded above
|
||||
"fee": event.fee, # negative = rebate
|
||||
"is_maker": event.is_maker,
|
||||
# Slot-level PnL repair plumbing (Phase 3.2): the
|
||||
# kernel repairs a price-less exit leg
|
||||
# (realized_skipped_no_price) from the exchange's
|
||||
# realized figure. Separate field so the K-fold is
|
||||
# never double-counted; slot 0 = single-slot kernel
|
||||
# and the fill ownership filter already passed.
|
||||
"slot_id": 0,
|
||||
"repair_realized_pnl": float(event.realized_pnl or 0.0),
|
||||
})
|
||||
# Gap 2: log settled fee with WS_SETTLED provenance so
|
||||
# downstream can reconcile against the ESTIMATED_TAKER row.
|
||||
|
||||
Reference in New Issue
Block a user