PINK: flat_and_start_pink — persistence check + asset-scoped flatness test

Startup roundtrip now verifies persistence accounting inline:
  - Wires PinkClickHousePersistence to a mock sink after EXIT
  - Checks trade_events: exit_price != entry_price, pnl finite, capital finite
  - Checks trade_exit_legs: pnl_leg, exit_qty, exit_price all populated
  - Logs ALL FIELDS CORRECT / ISSUES DETECTED

Flatness check scoped to PINK's traded asset only (other strategies
may have open positions; those are reported informational, not failure).
Adds _normalize_asset() for TRX-USDT → TRXUSDT symbol normalization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-05 09:38:59 +02:00
parent f7ee491f15
commit 714913bab6

View File

@@ -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("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")