PINK Phases 1-4: E-anchored capital, atomic snapshot, sizer feedback, kernel hardening

Phase 1: account.py anchor_to_exchange, capital_source provenance, settle
includes fees in capital delta.
Phase 2: atomic snapshot swap, CH provenance DDL (08_provenance.sql),
naive-UTC timestamps, ch_writer wait_for_async_insert=1 for all tables,
head-of-line stuck-row logging at WARNING per 100 attempts.
Phase 3: sizer feedback uses slot realized_pnl (not capital delta),
FILL_SETTLED repairs slot-level PnL for price-less exit legs.
Phase 4: resolve_slot returns Option<usize>, UNRESOLVED_SLOT diagnostic.
bars_held clamped to max(0, ...) at row-build time.
This commit is contained in:
Codex
2026-06-11 21:44:24 +02:00
parent 2c9da8f592
commit d280407327
6 changed files with 793 additions and 45 deletions

View File

@@ -32,6 +32,18 @@ Writer = Callable[[str, dict[str, Any]], None]
_log = logging.getLogger(__name__)
def _naive_utc_ts(ts: Any) -> str:
"""Emit naive-UTC microsecond ISO timestamp (no +00:00 suffix)."""
if hasattr(ts, "isoformat"):
raw = ts.isoformat(timespec="microseconds")
if raw.endswith("+00:00"):
raw = raw[:-6]
elif raw.endswith("Z"):
raw = raw[:-1]
return raw
return str(ts).replace("+00:00", "").replace("Z", "")
def _json_safe(value: Any) -> Any:
if isinstance(value, Enum):
return value.value
@@ -277,7 +289,7 @@ class PinkClickHousePersistence:
ReplacingMergeTree on event_seq) keeps the latest row only.
"""
from datetime import datetime, timezone
ts_val = ts or datetime.now(timezone.utc).isoformat()
ts_val = _naive_utc_ts(ts) if ts is not None else str(datetime.now(timezone.utc).isoformat()).replace("+00:00", "")
self._sink("reconcile_events", {
"timestamp": ts_val if isinstance(ts_val, str) else ts_val.isoformat(),
"runtime_namespace": self.config.runtime_namespace,
@@ -290,6 +302,10 @@ class PinkClickHousePersistence:
"explanation": str(explanation),
})
def _capital_source(self) -> str:
snap = self.account.snapshot
return str(getattr(snap, "capital_source", "") or "")
def _capital(self) -> float:
return float(self.account.snapshot.capital or 0.0)
@@ -681,7 +697,7 @@ class PinkClickHousePersistence:
self._sink(
"anomaly_events",
{
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"decision_id": decision.decision_id,
"trade_id": intent.trade_id,
"symbol": intent.asset,
@@ -741,7 +757,7 @@ class PinkClickHousePersistence:
*, anomaly: str, origin: str = "ditav2_kernel", detail: Any = "",
) -> None:
self._sink("anomaly_events", {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"decision_id": decision.decision_id,
"trade_id": intent.trade_id,
"symbol": intent.asset,
@@ -758,7 +774,7 @@ class PinkClickHousePersistence:
price = _safe_float(decision.reference_price, 0.0)
quantity = _safe_float(intent.target_size, 0.0)
row = {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"strategy": self.config.strategy,
"runtime_namespace": self.config.runtime_namespace,
"strategy_namespace": self.config.strategy_namespace,
@@ -777,7 +793,7 @@ class PinkClickHousePersistence:
"leverage": _safe_float(intent.leverage, 1.0),
"bar_idx": 0,
"decision_seq": self._trade_seq(),
"bars_held": int(intent.bars_held or 0),
"bars_held": max(0, int(intent.bars_held or 0)),
"action": decision.action.value,
"reason": decision.reason,
"pnl_pct": 0.0,
@@ -814,7 +830,7 @@ class PinkClickHousePersistence:
open_notional = _notional(self._slot_size(slot_dict), self._slot_entry_price(slot_dict)) if is_open else 0.0
drawdown_pct = 0.0 if peak_cap <= 0 else max(0.0, (peak_cap - capital) / peak_cap)
row = {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"event_type": event_type or stage.value,
"strategy": self.config.strategy,
"posture": self._posture(slot_dict),
@@ -843,6 +859,7 @@ class PinkClickHousePersistence:
"reason": None if intent is None else intent.reason,
"stage": stage.value,
}),
"capital_source": self._capital_source(),
# Phase 4: kernel atomic account versioning
"account_event_seq": self._account_event_seq(),
"reconcile_status": self._kernel_account().get("reconcile_status", "OK"),
@@ -875,7 +892,7 @@ class PinkClickHousePersistence:
asset = intent.asset
side = intent.side
row = {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"trade_id": trade_id,
"asset": asset,
"direction": _direction(side),
@@ -909,7 +926,7 @@ class PinkClickHousePersistence:
leverage = 0.0 if capital <= 0 else open_notional / capital
drawdown = 0.0 if peak_cap <= 0 else max(0.0, (peak_cap - capital) / peak_cap)
row = {
"ts": snapshot.timestamp.isoformat(timespec="milliseconds"),
"ts": _naive_utc_ts(snapshot.timestamp),
"capital": capital,
"roi_pct": 0.0 if self.config.initial_capital <= 0 else ((capital / self.config.initial_capital) - 1.0) * 100.0,
"dd_pct": drawdown * 100.0,
@@ -933,6 +950,8 @@ class PinkClickHousePersistence:
"remaining_notional_capacity": max(0.0, self.config.max_account_leverage * capital - open_notional),
"max_account_leverage": self.config.max_account_leverage,
"ledger_authority": self.config.ledger_authority,
"capital_source": self._capital_source(),
"account_event_seq": self._account_event_seq(),
}
self._sink("status_snapshots", row)
@@ -1002,7 +1021,7 @@ class PinkClickHousePersistence:
exit_leg_id = f"{trade_id}:leg{leg_index}"
self._sink("trade_exit_legs", {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"date": snapshot.timestamp.date().isoformat(),
"strategy": self.config.strategy,
"trade_id": trade_id,
@@ -1031,7 +1050,8 @@ class PinkClickHousePersistence:
"pnl_pct_leg": pnl_pct_leg,
"pnl_leg": pnl_leg,
"pnl_realized_total": cur_realized,
"bars_held": int(intent.bars_held or 0),
"pnl_source": "", # updated by FILL_SETTLED override (Phase 3)
"bars_held": max(0, int(intent.bars_held or 0)),
# Gap 1/2/3: per-leg friction
"fee_leg": fill_fee,
"fee_source": fill_fee_source,
@@ -1084,7 +1104,7 @@ class PinkClickHousePersistence:
conviction = float(intent.confidence or decision.confidence or 0.0)
metadata = intent.metadata if intent is not None else (decision.metadata if decision is not None else {})
row = {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"date": snapshot.timestamp.date().isoformat(),
"strategy": self.config.strategy,
"trade_id": intent.trade_id,
@@ -1095,6 +1115,7 @@ class PinkClickHousePersistence:
"quantity": quantity,
"pnl": pnl,
"pnl_pct": pnl_pct,
"pnl_source": "", # updated by FILL_SETTLED override (Phase 3)
"exit_reason": intent.reason,
"vel_div_entry": float(decision.velocity_divergence or 0.0),
"boost_at_entry": 1.0,
@@ -1125,7 +1146,7 @@ class PinkClickHousePersistence:
"drawdown_at_entry": 0.0 if self._peak_capital() <= 0 else max(0.0, (self._peak_capital() - capital_before) / self._peak_capital()),
"open_positions_count": 0,
"scan_uuid": decision.decision_id,
"bars_held": int(intent.bars_held or 0),
"bars_held": max(0, int(intent.bars_held or 0)),
"entry_payload_json": _json_text({"decision": _decision_summary(decision), "intent": _intent_summary(intent)}),
"exit_payload_json": _json_text({"outcome": _outcome_summary(outcome), "slot": _json_safe(slot_dict)}),
"execution_payload_json": _json_text({"outcome": _outcome_summary(outcome)}),
@@ -1156,7 +1177,7 @@ class PinkClickHousePersistence:
market_state: Mapping[str, Any] | None = None,
) -> None:
self._sink("trade_reconstruction", {
"ts": snapshot.timestamp.isoformat(),
"ts": _naive_utc_ts(snapshot.timestamp),
"trade_id": trade_id,
"event_type": event_type,
"event_id": event_id,