PINK Phase 0: FET -$5,990 fix batch — leverage-free PnL, true fill prices, reconcile baseline anchors

Defects fix (FET -$5,990 replay, 2026-06-11):
- realized_pnl() and mark_price(): PnL = qty × Δprice, side-signed; no ×leverage inflation (was 3× every leg).
- BingX MARKET fill events carry true fill price (avgPrice/lastFillPrice), never the order's nominal price (protective bound ±20-25% from mark, poisoned PnL to -$5,990 on a +$164 round-trip).
- Fill routing by ORDER IDENTITY first, FSM state second — late entry-remainder fills during EXIT_WORKING no longer misclassify as exits.
- Entry basis = VWAP across entry fills, not last fill price.
- reconcile_from_slots / restore_state: re-anchor _last_settled_pnl / _slot_was_closed to adopted slot state (cross-restart double-book of carried PnL).
- ACCOUNT_UPDATE with wallet_balance=0 dropped (margin-only frames no longer zero e_available_margin).
- Foreign-fill skip on shared VST account (PRODGREEN collision filter).
- exec_router TTL: entry-requote venue-truth gate (recent own fill + live exchange position probes prevent double-entry).
- bingx_direct: openOrders fetched BEFORE positions (sequential ordering prevents dangerous tear → double-entries).
- Dual-leverage translation via map_internal_conviction_to_exchange_leverage() (strategy conviction → integer at-exchange leverage, bankers rounding).
- BLUE-parity alpha components wired: asset picker (IRP universe ranking) + alpha sizer (cubic-convex dynamic leverage, 0.5-8.0 range).
- ch_writer: date_time_input_format=best_effort on insert URLs; flush error logging at WARNING with counter.
- blue_parity.price_of(): hyphen-tolerant fallback (FET-USDT → FETUSDT).
- Fill test updated to incremental filled_size semantics (BingX WS lastFilledQty).
- Env-override base URLs, supervisord autorestart, per-asset DC histories, single-slot invariant, fill-attribution filter.

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
Codex
2026-06-11 20:53:49 +02:00
parent 9e210b5a02
commit 2c9da8f592
10 changed files with 929 additions and 50 deletions

View File

@@ -451,8 +451,26 @@ class BingxVenueAdapter(VenueAdapter):
include_history=False: all_orders/all_fills require a symbol (symbol=None
skips them anyway), so include_history=True was fetching nothing extra.
"""
# FILL VISIBILITY (2026-06-10): when the kernel slot owns an asset,
# fetch symbol-scoped history (all_orders + all_fills) so a maker
# entry that FILLED — and therefore left openOrders — reaches the FSM
# as a FULL_FILL event. With symbol=None the snapshot skips history
# entirely: the FSM stayed fill-blind (slot size 0 in ENTRY_WORKING),
# the DecisionEngine saw "no position", and re-entered → the live
# double-entries at 15:20 and 17:24 UTC.
recon_symbol = None
kernel = getattr(self, "_kernel_ref", None)
if kernel is not None:
try:
slot = kernel.slot(0)
if not slot.is_free() and getattr(slot, "asset", ""):
recon_symbol = str(slot.asset)
except Exception:
recon_symbol = None
try:
snapshot = await self.backend.refresh_state(None, include_history=False)
snapshot = await self.backend.refresh_state(
recon_symbol, include_history=recon_symbol is not None
)
except Exception as exc:
import logging as _log
_log.getLogger(__name__).warning("reconcile: refresh_state failed: %s", exc)
@@ -564,7 +582,13 @@ class BingxVenueAdapter(VenueAdapter):
venue_client_id=client_order_id,
side=intent.side,
asset=intent.asset,
price=safe_float(_row_float(ack_row, "avgPrice", "ap", "price", "lastFillPrice", default=getattr(receipt, "price", 0.0)), 0.0),
# FILL price must be a TRUE fill price (avgPrice/lastFillPrice).
# Never fall back to the order's nominal "price" or the submit
# receipt price: for BingX MARKET orders that is the protective
# bound (±20-25% from mark) — it poisoned realized PnL on every
# market fill (FET $5,990 mis-book, 2026-06-11). 0.0 = unknown;
# the kernel refuses to compute PnL from a missing price.
price=safe_float(_row_float(ack_row, "avgPrice", "ap", "lastFillPrice", "L", default=0.0), 0.0),
size=float(intent.target_size or 0.0),
filled_size=float(filled_size),
remaining_size=float(remaining_size),
@@ -680,6 +704,13 @@ class BingxVenueAdapter(VenueAdapter):
filled = _row_float(row, "executedQty", "cumFilledQty", "filledQty", "z", "lastFilledQty", default=0.0)
if filled <= 0.0 and kind in {KernelEventKind.PARTIAL_FILL, KernelEventKind.FULL_FILL}:
filled = size
# For FILL events only true fill-price fields qualify; the nominal
# "price" is the MARKET bound price on BingX and must never feed PnL.
# Non-fill events (ACK/CANCEL/REJECT) may keep it as informational.
if kind in {KernelEventKind.PARTIAL_FILL, KernelEventKind.FULL_FILL}:
row_price = _row_float(row, "avgPrice", "ap", "lastFillPrice", "L", default=0.0)
else:
row_price = _row_float(row, "avgPrice", "ap", "price", "lastFillPrice", default=0.0)
return VenueEvent(
timestamp=datetime.now(timezone.utc),
event_id=_event_id(self._event_seq),
@@ -691,7 +722,7 @@ class BingxVenueAdapter(VenueAdapter):
venue_client_id=_row_text(row, "clientOrderID", "clientOrderId", "c", default=""),
side=_trade_side_from_row(row),
asset=_row_text(row, "symbol", default=""),
price=safe_float(_row_float(row, "avgPrice", "ap", "price", "lastFillPrice", default=0.0), 0.0),
price=safe_float(row_price, 0.0),
size=abs(float(size or 0.0)),
filled_size=abs(float(filled or 0.0)),
remaining_size=max(0.0, abs(float(size or 0.0)) - abs(float(filled or 0.0))),
@@ -715,7 +746,9 @@ class BingxVenueAdapter(VenueAdapter):
venue_client_id=_row_text(row, "clientOrderID", "clientOrderId", "c", default=""),
side=_trade_side_from_row(row),
asset=_row_text(row, "symbol", default=""),
price=safe_float(_row_float(row, "lastFillPrice", "L", "price", "ap", default=0.0), 0.0),
# True fill-price fields only — nominal "price" excluded (MARKET
# bound-price poisoning; see _events_from_submit note).
price=safe_float(_row_float(row, "lastFillPrice", "L", "avgPrice", "ap", default=0.0), 0.0),
size=abs(_row_float(row, "executedQty", "z", "lastFilledQty", default=0.0)),
filled_size=abs(_row_float(row, "lastFilledQty", "l", "z", default=0.0)),
remaining_size=max(0.0, abs(_row_float(row, "executedQty", "z", "lastFilledQty", default=0.0)) - abs(_row_float(row, "lastFilledQty", "l", "z", default=0.0))),