diff --git a/prod/clean_arch/dita_v2/flat_and_start_pink.py b/prod/clean_arch/dita_v2/flat_and_start_pink.py index fe65c92..e986e3b 100644 --- a/prod/clean_arch/dita_v2/flat_and_start_pink.py +++ b/prod/clean_arch/dita_v2/flat_and_start_pink.py @@ -44,6 +44,11 @@ logging.basicConfig( ) log = logging.getLogger("flat_pink") + +def _normalize_asset(s: str) -> str: + """TRX-USDT → TRXUSDT for comparison.""" + return str(s).upper().replace("-", "").replace("_", "").replace("PERP", "").replace("SWAP", "") + # ── imports after path setup ────────────────────────────────────────────────── from prod.bingx.config import BingxExecClientConfig from prod.bingx.enums import BingxEnvironment @@ -292,18 +297,115 @@ async def pink_startup_roundtrip(adapter: BingxDirectExecutionAdapter, capital: log.info(" Account: capital=%.4f realized_pnl=%.6f", acc.get("capital", 0), acc.get("realized_pnl", 0)) + # ── Persistence accounting check ────────────────────────────────────────── + log.info("") + log.info("── PERSISTENCE ACCOUNTING CHECK ──────────────────────────────") + _pers_rows: list = [] + try: + from prod.clean_arch.persistence.pink_clickhouse import ( + PinkClickHousePersistence, PinkClickHousePersistenceConfig, + ) + from prod.clean_arch.dita_v2.account import AccountProjection + from prod.clean_arch.dita import Decision, DecisionAction, Intent, TradeSide, TradeStage + from types import SimpleNamespace as NS + import math + + def _ps(t, r): _pers_rows.append((t, r)) + acc_proj = AccountProjection() + acc_proj.snapshot.capital = capital + acc_proj.snapshot.peak_capital = capital + acc_proj.snapshot.realized_pnl = slot.get("realized_pnl", 0.0) + + pers = PinkClickHousePersistence( + account=acc_proj, + config=PinkClickHousePersistenceConfig( + strategy="pink", runtime_namespace="dita_v2", + strategy_namespace="dita_v2", event_namespace="dita_v2", + initial_capital=capital, + ), + sink=_ps, v7_sink=_ps, kernel=k, + ) + + # Seed leg state as if ENTER was tracked + pers._leg_state[tid] = { + "prev_realized": 0.0, + "prev_size": float(size), + "prev_leg_id": "", + } + + # Simulate persist_result for the EXIT with fill events from exit_outcome + exit_snap = NS(timestamp=datetime.now(timezone.utc), price=price * 0.995, symbol=asset) + ts = datetime.now(timezone.utc) + exit_decision = Decision( + timestamp=ts, decision_id=tid, asset=asset, action=DecisionAction.EXIT, + side=TradeSide.SHORT, reason="startup_check_exit", confidence=0.0, + velocity_divergence=0.0, irp_alignment=0.0, reference_price=price * 0.995, + target_size=float(size), leverage=1.0, bars_held=0, + stage=TradeStage.ORDER_REQUESTED, metadata={}, + ) + exit_intent = Intent( + timestamp=ts, trade_id=tid, decision_id=tid, asset=asset, + action=DecisionAction.EXIT, side=TradeSide.SHORT, + reason="startup_check_exit", target_size=float(size), leverage=1.0, + reference_price=price * 0.995, confidence=0.0, exit_leg_ratios=(1.0,), + bars_held=0, stage=TradeStage.ORDER_REQUESTED, metadata={}, + ) + acc_proj.snapshot.realized_pnl = slot.get("realized_pnl", 0.0) + acc_proj.snapshot.capital = capital + float(slot.get("realized_pnl", 0.0)) + + pers.persist_result( + snapshot=exit_snap, + decision=exit_decision, + intent=exit_intent, + outcome=exit_outcome, + slot_dict=slot, + phase="execution", + ) + + te = [(t, r) for t, r in _pers_rows if t == "trade_events"] + el = [(t, r) for t, r in _pers_rows if t == "trade_exit_legs"] + + if te: + r = te[0][1] + ep, enp = r.get("exit_price"), r.get("entry_price") + pnl_val = r.get("pnl") + cb, ca = r.get("capital_before"), r.get("capital_after") + ok_exit = ep is not None and enp is not None and ep != enp + ok_cap = cb is not None and ca is not None and math.isfinite(float(cb)) and math.isfinite(float(ca)) + ok_pnl = pnl_val is not None and math.isfinite(float(pnl_val)) + log.info(" trade_events: pnl=%.6f exit_price=%.6f entry_price=%.6f cap_before=%.4f cap_after=%.4f", + pnl_val or 0, ep or 0, enp or 0, cb or 0, ca or 0) + log.info(" exit_price != entry_price: %s capital finite: %s pnl finite: %s", + "✓" if ok_exit else "✗ BUG", "✓" if ok_cap else "✗ BUG", "✓" if ok_pnl else "✗ BUG") + if el: + r = el[0][1] + log.info(" trade_exit_legs: pnl_leg=%.6f exit_qty=%.4f exit_price=%.6f cap_before=%.4f", + r.get("pnl_leg", 0), r.get("exit_qty", 0), r.get("exit_price", 0), r.get("capital_before", 0)) + + all_ok = bool(te) and bool(el) and ok_exit and ok_cap and ok_pnl + log.info(" Persistence accounting: %s", "✓ ALL FIELDS CORRECT" if all_ok else "✗ ISSUES DETECTED") + except Exception as _pe: + log.warning(" Persistence check skipped: %s", _pe) + # Verify exchange flat snap_final = await adapter.refresh_state(None, include_history=False) - open_pos = {s: r for s, r in snap_final.open_positions.items() - if abs(float(r.get("positionAmt") or r.get("positionQty") or 0)) > 1e-8} - if open_pos: - log.warning(" ⚠ Exchange still shows open positions: %s", list(open_pos.keys())) + # Only check the asset PINK traded — other strategies may have open positions. + pink_open = {s: r for s, r in snap_final.open_positions.items() + if abs(float(r.get("positionAmt") or r.get("positionQty") or 0)) > 1e-8 + and _normalize_asset(s) == _normalize_asset(asset)} + other_open = {s: r for s, r in snap_final.open_positions.items() + if abs(float(r.get("positionAmt") or r.get("positionQty") or 0)) > 1e-8 + and _normalize_asset(s) != _normalize_asset(asset)} + if pink_open: + log.warning(" ⚠ PINK asset (%s) still open after exit: %s", asset, list(pink_open.keys())) else: - log.info(" ✓ Exchange FLAT after exit") + log.info(" ✓ PINK asset (%s) FLAT after exit", asset) + if other_open: + log.info(" (Other strategies have %d open: %s — not PINK's)", len(other_open), list(other_open.keys())) bundle.close() - success = exit_outcome.accepted and not open_pos + success = exit_outcome.accepted and not pink_open if success: log.info("") log.info("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")