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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user