repo hygiene: track the PINK launcher import closure
67 production .py modules that the running PINK service imports but which were never committed: prod/bingx/ (HTTP client, market/user streams, journal, config), prod/clean_arch/ adapters/persistence/runtime/dita/dita_v2 production modules and their co-located tests. Rule going forward: every module imported by launch_dolphin_pink.py / pink_direct.py must appear in git ls-files. Excludes _backup dirs, __pycache__, and non-code files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
118
prod/clean_arch/dita/account.py
Normal file
118
prod/clean_arch/dita/account.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""Account projection and CH/HZ-shaped rows."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
import math
|
||||
|
||||
from .contracts import AccountEvent, Decision, Intent, TradePosition, TradeSide, TradeStage
|
||||
|
||||
|
||||
@dataclass
|
||||
class AccountSnapshot:
|
||||
"""Derived account state used for projections and row emission."""
|
||||
|
||||
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
|
||||
|
||||
@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:
|
||||
"""Thin account projection.
|
||||
|
||||
This is not policy. It only projects confirmed execution facts into the
|
||||
live account view and the durable row shape used by CH/HZ/TUI consumers.
|
||||
"""
|
||||
|
||||
runtime_namespace: str = "pink"
|
||||
strategy_namespace: str = "pink"
|
||||
event_namespace: str = "pink"
|
||||
actor_name: str = "clean_arch"
|
||||
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_position(self, position: Optional[TradePosition]) -> None:
|
||||
if position is None:
|
||||
self.snapshot.open_positions = 0
|
||||
self.snapshot.open_notional = 0.0
|
||||
self.snapshot.unrealized_pnl = 0.0
|
||||
self.snapshot.equity = self.snapshot.capital
|
||||
return
|
||||
self.snapshot.open_positions = 1
|
||||
mark = position.current_price
|
||||
if not math.isfinite(mark) or mark <= 0:
|
||||
mark = position.entry_price if math.isfinite(position.entry_price) and position.entry_price > 0 else 0.0
|
||||
self.snapshot.open_notional = mark * position.size
|
||||
self.snapshot.unrealized_pnl = position.unrealized_pnl
|
||||
self.snapshot.equity = self.snapshot.capital + position.unrealized_pnl
|
||||
|
||||
def settle(self, realized_pnl: float, fees: float = 0.0) -> None:
|
||||
if not math.isfinite(realized_pnl):
|
||||
realized_pnl = 0.0
|
||||
new_capital = self.snapshot.capital + realized_pnl
|
||||
if not math.isfinite(new_capital):
|
||||
new_capital = 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 += fees
|
||||
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_event(
|
||||
self,
|
||||
*,
|
||||
timestamp: datetime,
|
||||
decision: Decision,
|
||||
intent: Intent,
|
||||
position: Optional[TradePosition],
|
||||
stage: TradeStage,
|
||||
extra: Optional[Dict[str, Any]] = None,
|
||||
) -> AccountEvent:
|
||||
self.observe_position(position)
|
||||
return AccountEvent(
|
||||
timestamp=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=self.snapshot.capital,
|
||||
equity=self.snapshot.equity,
|
||||
open_positions=self.snapshot.open_positions,
|
||||
current_open_notional=self.snapshot.open_notional,
|
||||
current_account_leverage=self.snapshot.leverage,
|
||||
decision_id=decision.decision_id,
|
||||
trade_id=intent.trade_id,
|
||||
asset=decision.asset,
|
||||
side=intent.side,
|
||||
reason=intent.reason,
|
||||
stage=stage,
|
||||
pnl=self.snapshot.realized_pnl,
|
||||
pnl_pct=0.0 if self.snapshot.capital <= 0 else (self.snapshot.realized_pnl / self.snapshot.capital),
|
||||
bars_held=intent.bars_held,
|
||||
metadata=extra or {},
|
||||
)
|
||||
Reference in New Issue
Block a user