PINK: reconcile guard — refuse to silently drop orphan positions on restart

_reconcile_position_slot passed all N BingX positions (all slot_id=0) to
reconcile_from_slots; with N>1 the kernel silently took one and forgot the
rest. Now: sort by size desc, take only the largest, log ERROR naming every
ignored orphan symbol. Caller must flatten exchange to 0 before restarting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-06 18:12:07 +02:00
parent 10a44d86b1
commit a3169b762d

View File

@@ -180,6 +180,7 @@ def _reconcile_position_slot(
# Build TradeSlot[] from exchange positions
from prod.clean_arch.dita_v2.contracts import TradeSlot, TradeSide
_log = logging.getLogger(__name__)
reconciled = []
if positions:
for row in positions if isinstance(positions, list) else (
@@ -234,6 +235,19 @@ def _reconcile_position_slot(
reconciled.append(slot)
if reconciled:
if len(reconciled) > 1:
# Single-slot kernel: multiple open positions = orphan contamination from
# prior retry-duplicate bug. Take the largest by size so the kernel can
# exit it; the rest must be flattened manually before restart.
reconciled.sort(key=lambda s: float(s.size or 0), reverse=True)
orphan_syms = [s.asset for s in reconciled[1:]]
_log.error(
"RECONCILE WARNING: %d BingX positions found for single slot_id=%d. "
"Taking largest (%s size=%.4f). ORPHANS IGNORED (must flatten manually): %s",
len(reconciled), slot_id, reconciled[0].asset, float(reconciled[0].size or 0),
orphan_syms,
)
reconciled = reconciled[:1]
kernel.reconcile_from_slots(reconciled)
else:
# No open positions — ensure slot is idle