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:
@@ -26,6 +26,10 @@ class AccountSnapshot:
|
||||
fees_paid: float = 0.0
|
||||
trade_seq: int = 0
|
||||
peak_capital: float = 0.0
|
||||
# E-anchored provenance (Phase 1): "seed" | "e_anchored" | "k_bridged"
|
||||
capital_source: str = "seed"
|
||||
e_wallet_balance: float = 0.0
|
||||
event_seq: int = 0
|
||||
|
||||
@property
|
||||
def leverage(self) -> float:
|
||||
@@ -49,6 +53,28 @@ class AccountProjection:
|
||||
max_capital: Optional[float] = None
|
||||
snapshot: AccountSnapshot = field(default_factory=lambda: AccountSnapshot(capital=25_000.0, equity=25_000.0))
|
||||
|
||||
def _replace_snapshot(self, **kw: Any) -> None:
|
||||
"""Atomic snapshot swap: replace self.snapshot with a new frozen AccountSnapshot.
|
||||
|
||||
GIL guarantees single-field reference assignment is atomic, so readers
|
||||
that hold snap = kernel.account.snapshot before use see a consistent view.
|
||||
"""
|
||||
cur = self.snapshot
|
||||
self.snapshot = AccountSnapshot(
|
||||
capital=kw.get("capital", cur.capital),
|
||||
equity=kw.get("equity", cur.equity),
|
||||
realized_pnl=kw.get("realized_pnl", cur.realized_pnl),
|
||||
unrealized_pnl=kw.get("unrealized_pnl", cur.unrealized_pnl),
|
||||
open_positions=kw.get("open_positions", cur.open_positions),
|
||||
open_notional=kw.get("open_notional", cur.open_notional),
|
||||
fees_paid=kw.get("fees_paid", cur.fees_paid),
|
||||
trade_seq=kw.get("trade_seq", cur.trade_seq),
|
||||
peak_capital=kw.get("peak_capital", cur.peak_capital),
|
||||
capital_source=kw.get("capital_source", cur.capital_source),
|
||||
e_wallet_balance=kw.get("e_wallet_balance", cur.e_wallet_balance),
|
||||
event_seq=kw.get("event_seq", cur.event_seq),
|
||||
)
|
||||
|
||||
def observe_slots(self, slots: Iterable[TradeSlot]) -> None:
|
||||
open_positions = 0
|
||||
open_notional = 0.0
|
||||
@@ -62,27 +88,57 @@ class AccountProjection:
|
||||
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
|
||||
self._replace_snapshot(
|
||||
open_positions=open_positions,
|
||||
open_notional=open_notional,
|
||||
unrealized_pnl=unrealized_pnl,
|
||||
equity=self.snapshot.capital + unrealized_pnl if math.isfinite(self.snapshot.capital + unrealized_pnl) else self.snapshot.capital,
|
||||
peak_capital=max(self.snapshot.peak_capital, self.snapshot.capital) if open_notional > 0 and self.snapshot.capital > 0 else self.snapshot.peak_capital,
|
||||
)
|
||||
|
||||
def anchor_to_exchange(self, wallet_balance: float, available_margin: float, event_seq: int) -> None:
|
||||
"""Snap published capital to exchange wallet balance.
|
||||
|
||||
The exchange is the ledger of record (E-anchored). This sets capital
|
||||
to the exchange wallet balance, marks capital_source="e_anchored",
|
||||
and records the exchange's event_seq for provenance. Between anchors
|
||||
settle() bridges using capital_source="k_bridged".
|
||||
Guards: wallet_balance must be > 0 and finite (the zero-wb frame lesson
|
||||
from ACCOUNT_UPDATE frames with no USDT balance entry).
|
||||
"""
|
||||
wb = safe_float(wallet_balance, 0.0)
|
||||
if wb <= 0.0 or not math.isfinite(wb):
|
||||
return
|
||||
self.snapshot.capital = wb
|
||||
self.snapshot.e_wallet_balance = wb
|
||||
self.snapshot.capital_source = "e_anchored"
|
||||
self.snapshot.event_seq = int(event_seq)
|
||||
self.snapshot.equity = wb + self.snapshot.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)
|
||||
self.snapshot.equity = wb
|
||||
self.snapshot.peak_capital = max(self.snapshot.peak_capital, wb)
|
||||
|
||||
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)
|
||||
rp = safe_float(realized_pnl, 0.0)
|
||||
# Include fees in capital delta (today fees only accumulate in
|
||||
# fees_paid while published capital ignores them between reseeds).
|
||||
net = rp - safe_float(fees, 0.0)
|
||||
new_capital = safe_float(self.snapshot.capital + net, 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
|
||||
new_source = self.snapshot.capital_source
|
||||
if new_source == "e_anchored" and abs(net) > 1e-12:
|
||||
new_source = "k_bridged"
|
||||
new_fees = self.snapshot.fees_paid + safe_float(fees, 0.0)
|
||||
new_equity = new_capital + self.snapshot.unrealized_pnl
|
||||
if not math.isfinite(new_equity):
|
||||
new_equity = new_capital
|
||||
self._replace_snapshot(
|
||||
capital=new_capital, capital_source=new_source,
|
||||
realized_pnl=self.snapshot.realized_pnl + rp,
|
||||
fees_paid=new_fees, equity=new_equity,
|
||||
)
|
||||
|
||||
def to_account_event(
|
||||
self,
|
||||
|
||||
Reference in New Issue
Block a user