Files
siloqy/prod/clean_arch/dita_v2/account.py
Codex 3d7b00e28d Snapshot PINK DITAv2 system + Sprint 0 flaw-fix verification
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>
2026-05-30 18:26:43 +02:00

124 lines
4.7 KiB
Python

"""Account projection for DITAv2."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Iterable, Optional
import math
from .contracts import TradeSide, TradeSlot, TradeStage
from .utils import safe_float
@dataclass
class AccountSnapshot:
"""Derived account state."""
capital: float
equity: float
realized_pnl: float = 0.0
unrealized_pnl: float = 0.0
open_positions: int = 0
open_notional: float = 0.0
fees_paid: float = 0.0
trade_seq: int = 0
peak_capital: float = 0.0
@property
def leverage(self) -> float:
if self.capital <= 0 or self.open_notional <= 0:
return 0.0
return self.open_notional / self.capital
@dataclass
class AccountProjection:
"""Aggregate account view over all active slots."""
runtime_namespace: str = "dita_v2"
strategy_namespace: str = "dita_v2"
event_namespace: str = "dita_v2"
actor_name: str = "ExecutionKernel"
exec_venue: str = "bingx"
data_venue: str = "binance"
ledger_authority: str = "exchange"
min_capital: float = 0.0
max_capital: Optional[float] = None
snapshot: AccountSnapshot = field(default_factory=lambda: AccountSnapshot(capital=25_000.0, equity=25_000.0))
def observe_slots(self, slots: Iterable[TradeSlot]) -> None:
open_positions = 0
open_notional = 0.0
unrealized_pnl = 0.0
for slot in slots:
if slot.closed or slot.size <= 0:
continue
if slot.fsm_state in {TradeStage.POSITION_OPEN, TradeStage.POSITION_OPENED, TradeStage.ENTRY_WORKING, TradeStage.EXIT_WORKING}:
open_positions += 1
mark = safe_float(slot.entry_price, 0.0)
mark = safe_float(slot.metadata.get("mark_price"), mark)
open_notional += abs(slot.size) * abs(mark)
unrealized_pnl += float(slot.unrealized_pnl or 0.0)
self.snapshot.open_positions = open_positions
self.snapshot.open_notional = open_notional
self.snapshot.unrealized_pnl = unrealized_pnl
self.snapshot.equity = self.snapshot.capital + unrealized_pnl
if not math.isfinite(self.snapshot.equity):
self.snapshot.equity = self.snapshot.capital
if open_notional > 0 and self.snapshot.capital > 0:
self.snapshot.peak_capital = max(self.snapshot.peak_capital, self.snapshot.capital)
def settle(self, realized_pnl: float, fees: float = 0.0) -> None:
realized_pnl = safe_float(realized_pnl, 0.0)
new_capital = safe_float(self.snapshot.capital + realized_pnl, self.snapshot.capital)
if self.max_capital is not None:
new_capital = min(new_capital, self.max_capital)
new_capital = max(self.min_capital, new_capital)
self.snapshot.capital = new_capital
self.snapshot.realized_pnl += realized_pnl
self.snapshot.fees_paid += safe_float(fees, 0.0)
self.snapshot.equity = self.snapshot.capital + self.snapshot.unrealized_pnl
if not math.isfinite(self.snapshot.equity):
self.snapshot.equity = self.snapshot.capital
def to_account_event(
self,
*,
timestamp: datetime,
trade_id: str,
asset: str,
side: TradeSide,
stage: TradeStage,
reason: str,
pnl: float = 0.0,
pnl_pct: float = 0.0,
bars_held: int = 0,
metadata: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
self.snapshot.equity = self.snapshot.capital + self.snapshot.unrealized_pnl
return {
"timestamp": timestamp.isoformat() if hasattr(timestamp, "isoformat") else str(timestamp),
"runtime_namespace": self.runtime_namespace,
"strategy_namespace": self.strategy_namespace,
"event_namespace": self.event_namespace,
"actor_name": self.actor_name,
"exec_venue": self.exec_venue,
"data_venue": self.data_venue,
"ledger_authority": self.ledger_authority,
"capital": float(self.snapshot.capital),
"equity": float(self.snapshot.equity),
"open_positions": int(self.snapshot.open_positions),
"current_open_notional": float(self.snapshot.open_notional),
"current_account_leverage": float(self.snapshot.leverage),
"trade_id": trade_id,
"asset": asset,
"side": side.value,
"reason": reason,
"stage": stage.value,
"pnl": float(pnl),
"pnl_pct": float(pnl_pct),
"bars_held": int(bars_held),
"metadata": dict(metadata or {}),
}