First commit of the previously-untracked PINK-on-DITAv2 migration system (execution moves to the Rust kernel; policy stays on legacy DITA, so Alpha Engine algorithmic integrity is preserved). BLUE is untouched. Sprint 0 (safety snapshot + flaw-fix verification, MARKET single-leg scope): - Verified Rust FSM fixes (flaws 2,4,10,11,13) by source read of lib.rs. - Hardened 5 vacuous/guarded assertions in test_flaws.py so each flaw test genuinely exercises its fix. Most important: Flaw 5 now asserts capital moves by EXACTLY realized PnL (was entering/exiting at the same price). - Offline suites: 533 passed, 0 failed (35 flaws + 402 kernel/accounting/ bridge + 96 runtime/persistence/multi-exit/restart/seams). - GATE PASS: MARKET-path-critical flaws 1,2,5 confirmed fixed + green. - Added SPRINT0_FLAW_VERIFICATION.md report and _rust_kernel/.gitignore (excludes Rust target/ build artifacts). LIMIT/partial-fill remain explicitly out of scope (MARKET-only bring-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
657 lines
27 KiB
Python
657 lines
27 KiB
Python
"""PINK ClickHouse persistence — DITAv2-backed, reads capital from kernel.
|
|
|
|
Row families preserved (same schema, no new columns):
|
|
- policy_events / v7_decision_events
|
|
- position_state
|
|
- account_events
|
|
- status_snapshots
|
|
- trade_events
|
|
- trade_reconstruction
|
|
- trade_exit_legs
|
|
- anomaly_events
|
|
|
|
Capital/peak_capital/trade_seq are read from the kernel's AccountProjection
|
|
(single authority). No duplicate tracking in this module.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import math
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timezone
|
|
from enum import Enum
|
|
from typing import Any, Callable, Mapping, Optional
|
|
|
|
from prod.clean_arch.dita import AccountProjection, Decision, DecisionAction, Intent, TradeSide, TradeStage
|
|
from prod.clean_arch.dita_v2.contracts import KernelDiagnosticCode, KernelOutcome
|
|
|
|
Writer = Callable[[str, dict[str, Any]], None]
|
|
|
|
|
|
def _json_safe(value: Any) -> Any:
|
|
if isinstance(value, Enum):
|
|
return value.value
|
|
if isinstance(value, dict):
|
|
return {str(key): _json_safe(val) for key, val in value.items()}
|
|
if isinstance(value, (list, tuple)):
|
|
return [_json_safe(item) for item in value]
|
|
if hasattr(value, "isoformat"):
|
|
try:
|
|
return value.isoformat()
|
|
except Exception:
|
|
pass
|
|
if hasattr(value, "__dict__"):
|
|
try:
|
|
return _json_safe(dict(vars(value)))
|
|
except Exception:
|
|
pass
|
|
return value
|
|
|
|
|
|
def _json_text(value: Any) -> str:
|
|
return json.dumps(_json_safe(value), separators=(",", ":"), ensure_ascii=False, default=str)
|
|
|
|
|
|
def _direction(side: TradeSide) -> int:
|
|
return -1 if side == TradeSide.SHORT else 1
|
|
|
|
|
|
def _direction_from_str(side: str) -> int:
|
|
return -1 if side.upper() in ("SHORT", "SELL") else 1
|
|
|
|
|
|
def _notional(size: float, price: float) -> float:
|
|
if not math.isfinite(size) or not math.isfinite(price):
|
|
return 0.0
|
|
return abs(size) * abs(price)
|
|
|
|
|
|
def _safe_float(value: Any, default: float = 0.0) -> float:
|
|
try:
|
|
out = float(value)
|
|
except Exception:
|
|
return default
|
|
if not math.isfinite(out):
|
|
return default
|
|
return out
|
|
|
|
|
|
def _decision_summary(decision: Decision | None) -> dict[str, Any]:
|
|
if decision is None:
|
|
return {}
|
|
return {
|
|
"timestamp": decision.timestamp.isoformat() if hasattr(decision.timestamp, "isoformat") else str(decision.timestamp),
|
|
"decision_id": decision.decision_id,
|
|
"asset": decision.asset,
|
|
"action": decision.action.value,
|
|
"side": decision.side.value,
|
|
"reason": decision.reason,
|
|
"confidence": float(decision.confidence or 0.0),
|
|
"velocity_divergence": float(decision.velocity_divergence or 0.0),
|
|
"irp_alignment": float(decision.irp_alignment or 0.0),
|
|
"reference_price": float(decision.reference_price or 0.0),
|
|
"target_size": float(decision.target_size or 0.0),
|
|
"leverage": float(decision.leverage or 0.0),
|
|
"bars_held": int(decision.bars_held or 0),
|
|
"stage": decision.stage.value,
|
|
"metadata": _json_safe(decision.metadata),
|
|
}
|
|
|
|
|
|
def _intent_summary(intent: Intent | None) -> dict[str, Any]:
|
|
if intent is None:
|
|
return {}
|
|
return {
|
|
"timestamp": intent.timestamp.isoformat() if hasattr(intent.timestamp, "isoformat") else str(intent.timestamp),
|
|
"trade_id": intent.trade_id,
|
|
"decision_id": intent.decision_id,
|
|
"asset": intent.asset,
|
|
"action": intent.action.value,
|
|
"side": intent.side.value,
|
|
"reason": intent.reason,
|
|
"target_size": float(intent.target_size or 0.0),
|
|
"leverage": float(intent.leverage or 0.0),
|
|
"reference_price": float(intent.reference_price or 0.0),
|
|
"confidence": float(intent.confidence or 0.0),
|
|
"bars_held": int(intent.bars_held or 0),
|
|
"stage": intent.stage.value,
|
|
"exit_leg_ratios": [float(r) for r in intent.exit_leg_ratios],
|
|
"metadata": _json_safe(intent.metadata),
|
|
}
|
|
|
|
|
|
def _outcome_summary(outcome: KernelOutcome | None) -> dict[str, Any]:
|
|
if outcome is None:
|
|
return {}
|
|
return {
|
|
"accepted": bool(outcome.accepted),
|
|
"slot_id": int(outcome.slot_id),
|
|
"trade_id": outcome.trade_id,
|
|
"state": outcome.state.value,
|
|
"diagnostic_code": outcome.diagnostic_code.value,
|
|
"severity": outcome.severity.value,
|
|
"details": _json_safe(outcome.details),
|
|
}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PinkClickHousePersistenceConfig:
|
|
"""Row-shape knobs for the PINK ClickHouse mirror."""
|
|
|
|
strategy: str = "pink"
|
|
runtime_namespace: str = "pink"
|
|
strategy_namespace: str = "pink"
|
|
event_namespace: str = "pink"
|
|
actor_name: str = "PinkDirectRuntime"
|
|
exec_venue: str = "bingx"
|
|
data_venue: str = "binance"
|
|
ledger_authority: str = "exchange"
|
|
initial_capital: float = 25_000.0
|
|
max_account_leverage: float = 3.0
|
|
exchange_leverage_mode: str = ""
|
|
leverage_mapping_rule: str = "round_half_even_linear_0.5_to_9.0_to_1_to_exchange_cap"
|
|
|
|
|
|
class PinkClickHousePersistence:
|
|
"""Durable PINK ClickHouse sink — capital reads from kernel AccountProjection."""
|
|
|
|
def __init__(
|
|
self,
|
|
account: AccountProjection,
|
|
*,
|
|
config: PinkClickHousePersistenceConfig | None = None,
|
|
sink: Writer | None = None,
|
|
v7_sink: Writer | None = None,
|
|
) -> None:
|
|
self.account = account
|
|
self.config = config or PinkClickHousePersistenceConfig(
|
|
runtime_namespace=account.runtime_namespace,
|
|
strategy_namespace=account.strategy_namespace,
|
|
event_namespace=account.event_namespace,
|
|
actor_name=account.actor_name,
|
|
exec_venue=account.exec_venue,
|
|
data_venue=account.data_venue,
|
|
ledger_authority=account.ledger_authority,
|
|
initial_capital=float(account.snapshot.capital or 25_000.0),
|
|
)
|
|
self._sink = sink or self._resolve_sink("pink")
|
|
self._v7_sink = v7_sink or self._resolve_v7_sink("pink")
|
|
|
|
@staticmethod
|
|
def _resolve_sink(strategy: str) -> Writer:
|
|
from prod.ch_writer import ch_put_pink
|
|
|
|
return ch_put_pink
|
|
|
|
@staticmethod
|
|
def _resolve_v7_sink(strategy: str) -> Writer:
|
|
from prod.ch_writer import ch_put_pink_v7
|
|
|
|
return ch_put_pink_v7
|
|
|
|
def _capital(self) -> float:
|
|
return float(self.account.snapshot.capital or 0.0)
|
|
|
|
def _peak_capital(self) -> float:
|
|
return float(getattr(self.account.snapshot, "peak_capital", self._capital()) or self._capital())
|
|
|
|
def _trade_seq(self) -> int:
|
|
return int(getattr(self.account.snapshot, "trade_seq", 0) or 0)
|
|
|
|
def _equity(self) -> float:
|
|
return float(self.account.snapshot.equity or self._capital())
|
|
# ------------------------------------------------------------------
|
|
# Public API
|
|
# ------------------------------------------------------------------
|
|
|
|
def persist_step(
|
|
self,
|
|
*,
|
|
snapshot: Any,
|
|
decision: Decision,
|
|
intent: Intent,
|
|
outcome: KernelOutcome | None = None,
|
|
slot_dict: dict[str, Any] | None = None,
|
|
acc_dict: dict[str, Any] | None = None,
|
|
phase: str = "step",
|
|
market_state: Mapping[str, Any] | None = None,
|
|
) -> None:
|
|
slot = slot_dict or {}
|
|
stage = (
|
|
TradeStage(decision.stage.value)
|
|
if hasattr(decision.stage, "value")
|
|
else TradeStage(decision.stage) if isinstance(decision.stage, str)
|
|
else TradeStage.ORDER_REQUESTED
|
|
)
|
|
status = self._state_label(slot, phase)
|
|
|
|
self._write_policy_event(snapshot, decision, intent, phase=phase)
|
|
self._write_account_event(snapshot, decision, intent, stage=stage, slot_dict=slot)
|
|
self._write_position_state(snapshot, decision, intent, slot_dict=slot, stage=stage, status=status, market_state=market_state)
|
|
self._write_status_snapshot(snapshot, decision, intent, slot_dict=slot, phase=phase)
|
|
|
|
# Emit anomaly for diagnostic codes (except OK).
|
|
if outcome is not None and outcome.diagnostic_code != KernelDiagnosticCode.OK:
|
|
self._write_anomaly(
|
|
snapshot, decision, intent,
|
|
anomaly=outcome.diagnostic_code.value,
|
|
origin="ditav2_kernel",
|
|
detail=outcome.details,
|
|
)
|
|
|
|
if outcome is None:
|
|
# Decision-only step (HOLD, no execution).
|
|
return
|
|
|
|
if decision.action == DecisionAction.ENTER:
|
|
self._write_trade_reconstruction(
|
|
snapshot, intent.trade_id,
|
|
event_type="ENTRY_FILLED",
|
|
event_id=f"{intent.trade_id}:entry",
|
|
payload={
|
|
"decision": _decision_summary(decision),
|
|
"intent": _intent_summary(intent),
|
|
"outcome": _outcome_summary(outcome),
|
|
"slot": slot,
|
|
"market_state": _json_safe(market_state or {}),
|
|
},
|
|
market_state=market_state,
|
|
)
|
|
return
|
|
|
|
if decision.action != DecisionAction.EXIT:
|
|
return
|
|
|
|
partial = slot.get("closed", False) is False and slot.get("size", 0) > 0
|
|
self._write_trade_reconstruction(
|
|
snapshot, intent.trade_id,
|
|
event_type="PARTIAL_EXIT" if partial else "EXIT",
|
|
event_id=f"{intent.trade_id}:{'partial' if partial else 'close'}",
|
|
payload={
|
|
"decision": _decision_summary(decision),
|
|
"intent": _intent_summary(intent),
|
|
"outcome": _outcome_summary(outcome),
|
|
"slot": slot,
|
|
"market_state": _json_safe(market_state or {}),
|
|
},
|
|
market_state=market_state,
|
|
)
|
|
# Terminal trade event.
|
|
if slot.get("closed", False):
|
|
self._write_trade_event(snapshot, decision, intent, slot, outcome, market_state=market_state)
|
|
|
|
def persist_recovery_state(
|
|
self,
|
|
*,
|
|
snapshot: Any,
|
|
acc_dict: dict[str, Any] | None = None,
|
|
phase: str = "recovery",
|
|
event_type: str = "RECOVERY",
|
|
market_state: Mapping[str, Any] | None = None,
|
|
) -> None:
|
|
"""Persist recovery-only state after kernel reconcile."""
|
|
slot_dict = acc_dict or {}
|
|
self._write_status_snapshot(
|
|
snapshot, decision=None, intent=None, slot_dict={}, phase=phase,
|
|
)
|
|
self._write_account_event(
|
|
snapshot, decision=None, intent=None,
|
|
stage=TradeStage.TRADE_TERMINAL_WRITTEN,
|
|
slot_dict={}, event_type=event_type,
|
|
)
|
|
self._write_position_state(
|
|
snapshot, decision=None, intent=None,
|
|
slot_dict={}, stage=TradeStage.TRADE_TERMINAL_WRITTEN,
|
|
status=self._state_label({}, phase), market_state=market_state,
|
|
)
|
|
self._write_trade_reconstruction(
|
|
snapshot,
|
|
trade_id=acc_dict.get("trade_id", "") if acc_dict else "",
|
|
event_type=event_type,
|
|
event_id=f"recovery:{phase}",
|
|
payload={"acc_dict": _json_safe(acc_dict or {}), "phase": phase, "market_state": _json_safe(market_state or {})},
|
|
market_state=market_state,
|
|
)
|
|
|
|
def record_anomaly(
|
|
self,
|
|
*,
|
|
snapshot: Any,
|
|
decision: Any,
|
|
intent: Any,
|
|
anomaly: str,
|
|
origin: str = "emergent",
|
|
sensor: str = "",
|
|
detail: Any = "",
|
|
rm_meta: float = 0.0,
|
|
) -> None:
|
|
"""Persist a DITA anomaly row with legacy-compatible shape."""
|
|
self._sink(
|
|
"anomaly_events",
|
|
{
|
|
"ts": snapshot.timestamp.isoformat(),
|
|
"decision_id": decision.decision_id,
|
|
"trade_id": intent.trade_id,
|
|
"symbol": intent.asset,
|
|
"anomaly": anomaly,
|
|
"origin": origin,
|
|
"sensor": sensor,
|
|
"detail": _json_text(detail) if not isinstance(detail, str) else detail,
|
|
"rm_meta": float(rm_meta),
|
|
},
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Internal helpers
|
|
# ------------------------------------------------------------------
|
|
|
|
@staticmethod
|
|
def _state_label(slot_dict: dict[str, Any], phase: str) -> str:
|
|
if slot_dict.get("closed", False):
|
|
return "CLOSED"
|
|
if slot_dict.get("size", 0) > 0:
|
|
if phase.lower().startswith("recovery"):
|
|
return "RECOVERED_OPEN"
|
|
return "OPEN"
|
|
return "FLAT"
|
|
|
|
def _posture(self, slot_dict: dict[str, Any]) -> str:
|
|
if slot_dict.get("closed", False) or not slot_dict.get("size", 0):
|
|
return "FLAT"
|
|
return str(slot_dict.get("side", "FLAT"))
|
|
|
|
def _slot_entry_price(self, slot_dict: dict[str, Any]) -> float:
|
|
return _safe_float(slot_dict.get("entry_price", 0.0), 0.0)
|
|
|
|
def _slot_size(self, slot_dict: dict[str, Any]) -> float:
|
|
return _safe_float(slot_dict.get("size", 0.0), 0.0)
|
|
|
|
def _slot_side(self, slot_dict: dict[str, Any]) -> TradeSide:
|
|
raw = str(slot_dict.get("side", "FLAT")).upper()
|
|
if raw == "SHORT":
|
|
return TradeSide.SHORT
|
|
if raw == "LONG":
|
|
return TradeSide.LONG
|
|
return TradeSide.FLAT
|
|
|
|
def _slot_trade_id(self, slot_dict: dict[str, Any]) -> str:
|
|
return str(slot_dict.get("trade_id", ""))
|
|
|
|
def _slot_asset(self, slot_dict: dict[str, Any]) -> str:
|
|
return str(slot_dict.get("asset", ""))
|
|
|
|
# ------------------------------------------------------------------
|
|
# Row writers
|
|
# ------------------------------------------------------------------
|
|
|
|
def _write_anomaly(
|
|
self, snapshot: Any, decision: Decision, intent: Intent,
|
|
*, anomaly: str, origin: str = "ditav2_kernel", detail: Any = "",
|
|
) -> None:
|
|
self._sink("anomaly_events", {
|
|
"ts": snapshot.timestamp.isoformat(),
|
|
"decision_id": decision.decision_id,
|
|
"trade_id": intent.trade_id,
|
|
"symbol": intent.asset,
|
|
"anomaly": anomaly,
|
|
"origin": origin,
|
|
"sensor": "",
|
|
"detail": _json_text(detail) if not isinstance(detail, str) else detail,
|
|
"rm_meta": 0.0,
|
|
})
|
|
|
|
def _write_policy_event(
|
|
self, snapshot: Any, decision: Decision, intent: Intent, *, phase: str,
|
|
) -> None:
|
|
price = _safe_float(decision.reference_price, 0.0)
|
|
quantity = _safe_float(intent.target_size, 0.0)
|
|
row = {
|
|
"ts": snapshot.timestamp.isoformat(),
|
|
"strategy": self.config.strategy,
|
|
"runtime_namespace": self.config.runtime_namespace,
|
|
"strategy_namespace": self.config.strategy_namespace,
|
|
"event_namespace": self.config.event_namespace,
|
|
"actor_name": self.config.actor_name,
|
|
"exec_venue": self.config.exec_venue,
|
|
"data_venue": self.config.data_venue,
|
|
"source": "ditav2",
|
|
"trade_id": intent.trade_id,
|
|
"asset": decision.asset,
|
|
"side": decision.side.value,
|
|
"entry_price": price,
|
|
"current_price": price,
|
|
"quantity": quantity,
|
|
"notional": _notional(quantity, price),
|
|
"leverage": _safe_float(intent.leverage, 1.0),
|
|
"bar_idx": 0,
|
|
"decision_seq": self._trade_seq(),
|
|
"bars_held": int(intent.bars_held or 0),
|
|
"action": decision.action.value,
|
|
"reason": decision.reason,
|
|
"pnl_pct": 0.0,
|
|
"mfe": 0.0,
|
|
"mae": 0.0,
|
|
"mfe_risk": 0.0,
|
|
"mae_risk": 0.0,
|
|
"exit_pressure": 0.0,
|
|
"rv_comp": 0.0,
|
|
"mae_thresh1": 0.0,
|
|
"bounce_score": 0.0,
|
|
"bounce_risk": 0.0,
|
|
"ob_imbalance": 0.0,
|
|
"vel_div_entry": float(decision.velocity_divergence or 0.0),
|
|
"vel_div_now": float(decision.velocity_divergence or 0.0),
|
|
"v50_vel": 0.0,
|
|
"v750_vel": 0.0,
|
|
"exf_funding": 0.0,
|
|
"exf_dvol": 0.0,
|
|
"exf_fear_greed": 0.0,
|
|
"exf_taker": 0.0,
|
|
"posture": decision.side.value,
|
|
}
|
|
self._sink("policy_events", row)
|
|
self._v7_sink("v7_decision_events", row)
|
|
|
|
def _write_account_event(
|
|
self, snapshot: Any, decision: Decision | None, intent: Intent | None,
|
|
*, stage: TradeStage, slot_dict: dict[str, Any], event_type: str | None = None,
|
|
) -> None:
|
|
capital = self._capital()
|
|
peak_cap = self._peak_capital()
|
|
is_open = not slot_dict.get("closed", False) and slot_dict.get("size", 0) > 0
|
|
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(),
|
|
"event_type": event_type or stage.value,
|
|
"strategy": self.config.strategy,
|
|
"posture": self._posture(slot_dict),
|
|
"capital": capital,
|
|
"peak_capital": peak_cap,
|
|
"drawdown_pct": drawdown_pct,
|
|
"pnl_today": float(self.account.snapshot.realized_pnl or 0.0),
|
|
"trades_today": self._trade_seq(),
|
|
"open_positions": 1 if is_open else 0,
|
|
"boost": 1.0,
|
|
"beta": 0.0,
|
|
"current_open_notional": open_notional,
|
|
"current_account_leverage": 0.0 if capital <= 0 else open_notional / capital,
|
|
"exchange_leverage": int(round(_safe_float(slot_dict.get("leverage", 0.0), 0.0))),
|
|
"exchange_leverage_mode": self.config.exchange_leverage_mode,
|
|
"leverage_mapping_rule": self.config.leverage_mapping_rule,
|
|
"runtime_namespace": self.config.runtime_namespace,
|
|
"strategy_namespace": self.config.strategy_namespace,
|
|
"event_namespace": self.config.event_namespace,
|
|
"actor_name": self.config.actor_name,
|
|
"exec_venue": self.config.exec_venue,
|
|
"data_venue": self.config.data_venue,
|
|
"notes": _json_text({
|
|
"decision_id": None if decision is None else decision.decision_id,
|
|
"trade_id": None if intent is None else intent.trade_id,
|
|
"reason": None if intent is None else intent.reason,
|
|
"stage": stage.value,
|
|
}),
|
|
}
|
|
self._sink("account_events", row)
|
|
|
|
def _write_position_state(
|
|
self, snapshot: Any, decision: Decision | None, intent: Intent | None,
|
|
*, slot_dict: dict[str, Any], stage: TradeStage, status: str,
|
|
market_state: Mapping[str, Any] | None = None,
|
|
) -> None:
|
|
side = self._slot_side(slot_dict)
|
|
trade_id = self._slot_trade_id(slot_dict)
|
|
asset = self._slot_asset(slot_dict)
|
|
if not trade_id and intent is not None:
|
|
trade_id = intent.trade_id
|
|
asset = intent.asset
|
|
side = intent.side
|
|
row = {
|
|
"ts": snapshot.timestamp.isoformat(),
|
|
"trade_id": trade_id,
|
|
"asset": asset,
|
|
"direction": _direction(side),
|
|
"entry_price": self._slot_entry_price(slot_dict),
|
|
"quantity": self._slot_size(slot_dict),
|
|
"notional": _notional(self._slot_size(slot_dict), self._slot_entry_price(slot_dict)),
|
|
"leverage": _safe_float(slot_dict.get("leverage", 0.0), 0.0),
|
|
"bucket_id": -1,
|
|
"entry_bar": int(slot_dict.get("active_leg_index", 0) or 0),
|
|
"status": status,
|
|
"exit_reason": slot_dict.get("close_reason", ""),
|
|
"pnl": _safe_float(slot_dict.get("realized_pnl", 0.0), 0.0),
|
|
"bars_held": 0,
|
|
"market_state_bundle_json": _json_text(market_state or {}),
|
|
"tp_base_pct": 0.0,
|
|
"tp_effective_pct": 0.0,
|
|
"our_leverage": _safe_float(slot_dict.get("leverage", 0.0), 0.0),
|
|
}
|
|
self._sink("position_state", row)
|
|
|
|
def _write_status_snapshot(
|
|
self, snapshot: Any, decision: Decision | None, intent: Intent | None,
|
|
*, slot_dict: dict[str, Any], phase: str,
|
|
) -> None:
|
|
capital = self._capital()
|
|
peak_cap = self._peak_capital()
|
|
is_open = not slot_dict.get("closed", False) and slot_dict.get("size", 0) > 0
|
|
open_notional = _notional(self._slot_size(slot_dict), self._slot_entry_price(slot_dict)) if is_open else 0.0
|
|
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"),
|
|
"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,
|
|
"trades_executed": self._trade_seq(),
|
|
"posture": self._posture(slot_dict),
|
|
"rm": 1.0 if decision is None else max(0.0, min(1.0, decision.confidence)),
|
|
"vel_div": 0.0 if decision is None else float(decision.velocity_divergence),
|
|
"vol_ok": 1,
|
|
"phase": phase,
|
|
"mhs_status": "GREEN",
|
|
"boost": 1.0,
|
|
"cat5": 0.0,
|
|
"conviction_multiplier": 0.0 if intent is None else float(intent.confidence or 0.0),
|
|
"exchange_leverage": int(round(_safe_float(slot_dict.get("leverage", 0.0), 0.0))),
|
|
"exchange_leverage_mode": self.config.exchange_leverage_mode,
|
|
"leverage_mapping_rule": self.config.leverage_mapping_rule,
|
|
"account_capital": capital,
|
|
"portfolio_capital": capital,
|
|
"current_open_notional": open_notional,
|
|
"current_account_leverage": leverage,
|
|
"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,
|
|
}
|
|
self._sink("status_snapshots", row)
|
|
|
|
def _write_trade_event(
|
|
self, snapshot: Any, decision: Decision, intent: Intent,
|
|
slot_dict: dict[str, Any], outcome: KernelOutcome | None,
|
|
*, market_state: Mapping[str, Any] | None = None,
|
|
) -> None:
|
|
entry_price = _safe_float(slot_dict.get("entry_price", 0.0), 0.0) or _safe_float(intent.reference_price, 0.0)
|
|
quantity = _safe_float(slot_dict.get("initial_size", slot_dict.get("size", 0.0)), 0.0) or _safe_float(intent.target_size, 0.0)
|
|
exit_price = _safe_float(slot_dict.get("entry_price", 0.0), 0.0)
|
|
pnl = _safe_float(slot_dict.get("realized_pnl", 0.0), 0.0)
|
|
pnl_pct = 0.0
|
|
leverage_val = _safe_float(slot_dict.get("leverage", intent.leverage), 1.0)
|
|
denom = abs(quantity * entry_price * max(leverage_val, 1e-9))
|
|
if denom > 0:
|
|
pnl_pct = pnl / denom
|
|
capital_after = self._capital()
|
|
capital_before = capital_after - pnl
|
|
open_notional = _notional(quantity, exit_price or entry_price)
|
|
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(),
|
|
"date": snapshot.timestamp.date().isoformat(),
|
|
"strategy": self.config.strategy,
|
|
"trade_id": intent.trade_id,
|
|
"asset": intent.asset,
|
|
"side": intent.side.value,
|
|
"entry_price": entry_price,
|
|
"exit_price": exit_price,
|
|
"quantity": quantity,
|
|
"pnl": pnl,
|
|
"pnl_pct": pnl_pct,
|
|
"exit_reason": intent.reason,
|
|
"vel_div_entry": float(decision.velocity_divergence or 0.0),
|
|
"boost_at_entry": 1.0,
|
|
"beta_at_entry": 0.0,
|
|
"posture": intent.side.value,
|
|
"leverage": leverage_val,
|
|
"conviction_multiplier": conviction,
|
|
"exchange_leverage": int(round(leverage_val)),
|
|
"exchange_leverage_mode": self.config.exchange_leverage_mode,
|
|
"leverage_mapping_rule": self.config.leverage_mapping_rule,
|
|
"runtime_namespace": self.config.runtime_namespace,
|
|
"strategy_namespace": self.config.strategy_namespace,
|
|
"event_namespace": self.config.event_namespace,
|
|
"actor_name": self.config.actor_name,
|
|
"exec_venue": self.config.exec_venue,
|
|
"data_venue": self.config.data_venue,
|
|
"account_capital": capital_after,
|
|
"portfolio_capital": capital_after,
|
|
"current_open_notional": open_notional,
|
|
"remaining_notional_capacity": max(0.0, self.config.max_account_leverage * capital_after - open_notional),
|
|
"max_account_leverage": self.config.max_account_leverage,
|
|
"margin_required": 0.0 if leverage_val <= 0 else open_notional / leverage_val,
|
|
"ledger_authority": self.config.ledger_authority,
|
|
"regime_signal": 0,
|
|
"capital_before": capital_before,
|
|
"capital_after": capital_after,
|
|
"peak_capital": self._peak_capital(),
|
|
"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),
|
|
"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)}),
|
|
"friction_payload_json": _json_text({"fees": 0.0}),
|
|
"event_payload_json": _json_text({"phase": "terminal_close", "trade_id": intent.trade_id}),
|
|
"market_state_bundle_json": _json_text(market_state or {}),
|
|
"tp_base_pct": _safe_float(metadata.get("tp_base_pct", 0.0), 0.0),
|
|
"tp_effective_pct": _safe_float(metadata.get("tp_effective_pct", 0.0), 0.0),
|
|
"our_leverage": _safe_float(metadata.get("our_leverage", 0.0), 0.0),
|
|
}
|
|
self._sink("trade_events", row)
|
|
|
|
def _write_trade_reconstruction(
|
|
self, snapshot: Any, trade_id: str, *,
|
|
event_type: str, event_id: str, payload: Any,
|
|
market_state: Mapping[str, Any] | None = None,
|
|
) -> None:
|
|
self._sink("trade_reconstruction", {
|
|
"ts": snapshot.timestamp.isoformat(),
|
|
"trade_id": trade_id,
|
|
"event_type": event_type,
|
|
"event_id": event_id,
|
|
"payload_json": _json_text(payload),
|
|
"market_state_bundle_json": _json_text(market_state or {}),
|
|
})
|