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

@@ -27,8 +27,13 @@ class DecisionEngine:
It does not size orders or own exchange state.
"""
def __init__(self, config: Optional[DecisionConfig] = None):
def __init__(self, config: Optional[DecisionConfig] = None, sizer: Optional[object] = None):
self.config = config or DecisionConfig()
# Optional BLUE-parity sizer (PinkAlphaSizer / AlphaBetSizer-shaped:
# calculate_size(capital=..., vel_div=...) → {fraction, leverage, ...}).
# None preserves the legacy linear-confidence sizing exactly — other
# consumers of this engine (main.py, trading_engine.py) are unaffected.
self.sizer = sizer
def decide(
self,
@@ -76,9 +81,30 @@ class DecisionEngine:
# vol_ok gate — scan bridge marks low-volume periods; block ENTERs when absent
if snapshot.scan_payload and not snapshot.scan_payload.get("vol_ok", True):
return self._hold(snapshot, context, fields, reason="VOL_GATE")
confidence = min(1.0, max(0.05, abs(fields.vdiv / self.config.vel_div_threshold)))
leverage = min(self.config.max_leverage, max(1.0, 1.0 + confidence * (self.config.max_leverage - 1.0)))
target_exposure = context.capital * self.config.capital_fraction * leverage
sizing_meta: dict = {}
if self.sizer is not None:
# BLUE-parity sizing (SYSTEM BIBLE §6): cubic-convex dynamic
# leverage + alpha-layer fraction via AlphaBetSizer kernels.
size_result = self.sizer.calculate_size(capital=context.capital, vel_div=fields.vdiv)
leverage = float(size_result["leverage"])
fraction = float(size_result["fraction"])
target_exposure = context.capital * fraction * leverage
breakdown = size_result.get("breakdown") or {}
confidence = min(1.0, max(0.05, float(breakdown.get("strength_score", 0.0))))
sizing_meta = {
"eff_fraction": fraction,
"strength_score": breakdown.get("strength_score"),
"signal_bucket": breakdown.get("signal_bucket"),
"bucket_idx": size_result.get("bucket_idx"),
"sizing": "alpha_bet_sizer_cubic_v1",
}
else:
# Legacy DITAv2 formula. NOTE: an ENTER requires vdiv < threshold,
# so this confidence is always ≥ 1.0 → clamped → leverage pinned at
# max_leverage. Kept verbatim for non-PINK consumers.
confidence = min(1.0, max(0.05, abs(fields.vdiv / self.config.vel_div_threshold)))
leverage = min(self.config.max_leverage, max(1.0, 1.0 + confidence * (self.config.max_leverage - 1.0)))
target_exposure = context.capital * self.config.capital_fraction * leverage
target_size = target_exposure / fields.price if fields.price > 0 else 0.0
our_leverage = compute_our_leverage(notional=target_exposure, capital=context.capital)
tp_base_pct = float(self.config.fixed_tp_pct)
@@ -102,6 +128,7 @@ class DecisionEngine:
"tp_effective_pct": tp_effective_pct,
"our_leverage": our_leverage,
"tp_curve": "soft_leverage_curve_v1",
**sizing_meta,
},
)