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:
Codex
2026-06-12 15:09:32 +02:00
parent c3a18f693a
commit 84e4a50e3f
67 changed files with 15090 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
"""DITA boundary for clean-arch trading experiments.
Decision -> Intent -> Trade -> Account
This package is infrastructure-free. It provides the canonical contracts
and pure engines used by the simulator and by any future adapters that need
BLUE/PINK comparable semantics.
"""
from .account import AccountProjection, AccountSnapshot
from .contracts import (
AccountEvent,
Decision,
DecisionAction,
DecisionConfig,
DecisionContext,
Intent,
IntentContext,
TradeEvent,
TradePosition,
TradeSide,
TradeStage,
)
from .decision import DecisionEngine
from .intent import IntentEngine
from .observability import DitaObservabilityNamespace, LEGACY_ANOMALY_SENSOR_KEY
from .trade import TradeExecutionResult, TradeExecutor
__all__ = [
"AccountEvent",
"AccountProjection",
"AccountSnapshot",
"Decision",
"DecisionAction",
"DecisionConfig",
"DecisionContext",
"DecisionEngine",
"DitaObservabilityNamespace",
"Intent",
"IntentContext",
"IntentEngine",
"LEGACY_ANOMALY_SENSOR_KEY",
"TradeEvent",
"TradeExecutionResult",
"TradeExecutor",
"TradePosition",
"TradeSide",
"TradeStage",
]

View 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 {},
)

View File

@@ -0,0 +1,132 @@
"""Intent planning layer."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from .contracts import Decision, DecisionAction, DecisionConfig, DecisionContext, Intent, IntentContext, TradePosition, TradeSide, TradeStage
@dataclass(frozen=True)
class IntentPlanResult:
intent: Intent
trade_id_created: bool
class IntentEngine:
"""Converts a pure decision into an executable intent.
This is where sizing and trade identity are attached.
"""
def __init__(self, config: Optional[DecisionConfig] = None):
self.config = config or DecisionConfig()
def plan(
self,
decision: Decision,
context: IntentContext,
position: Optional[TradePosition] = None,
) -> IntentPlanResult:
if decision.action == DecisionAction.ENTER:
return self._plan_entry(decision, context)
if decision.action == DecisionAction.EXIT and position is not None:
return self._plan_exit(decision, context, position)
return IntentPlanResult(
intent=Intent(
timestamp=decision.timestamp,
trade_id=decision.decision_id.replace("-D-", "-T-"),
decision_id=decision.decision_id,
asset=decision.asset,
action=decision.action,
side=decision.side,
reason=decision.reason,
target_size=0.0,
leverage=1.0,
reference_price=decision.reference_price,
confidence=decision.confidence,
bars_held=0,
stage=TradeStage.INTENT_CREATED,
exit_leg_ratios=self.config.exit_leg_ratios,
metadata={"policy_version": self.config.policy_version, **decision.metadata},
),
trade_id_created=False,
)
def _plan_entry(self, decision: Decision, context: IntentContext) -> IntentPlanResult:
price = decision.reference_price
confidence = max(0.05, min(1.0, decision.confidence))
# Honor the decision's sizing when present (BLUE-parity cubic sizer
# attaches leverage + target_size in DecisionEngine._decide_entry).
# For legacy decisions the recompute below yields the identical values,
# so preferring the decision's numbers is behavior-preserving.
if decision.leverage and decision.leverage > 0 and decision.target_size and decision.target_size > 0:
leverage = float(decision.leverage)
target_size = float(decision.target_size)
target_exposure = target_size * price if price > 0 else 0.0
else:
leverage = min(self.config.max_leverage, max(1.0, 1.0 + confidence * (self.config.max_leverage - 1.0)))
target_exposure = context.capital * self.config.capital_fraction * leverage
target_size = target_exposure / price if price > 0 else 0.0
trade_id = self._trade_id(decision.asset, context.trade_seq + 1)
return IntentPlanResult(
intent=Intent(
timestamp=decision.timestamp,
trade_id=trade_id,
decision_id=decision.decision_id,
asset=decision.asset,
action=decision.action,
side=decision.side,
reason=decision.reason,
target_size=target_size,
leverage=leverage,
reference_price=price,
confidence=confidence,
bars_held=0,
stage=TradeStage.INTENT_CREATED,
exit_leg_ratios=self.config.exit_leg_ratios,
metadata={
"policy_version": self.config.policy_version,
"target_exposure": target_exposure,
"entry_velocity_divergence": decision.velocity_divergence,
"entry_irp_alignment": decision.irp_alignment,
**decision.metadata,
},
),
trade_id_created=True,
)
def _plan_exit(self, decision: Decision, context: IntentContext, position: TradePosition) -> IntentPlanResult:
exit_ratio = position.next_exit_ratio()
target_size = position.size * exit_ratio if exit_ratio > 0 else position.size
return IntentPlanResult(
intent=Intent(
timestamp=decision.timestamp,
trade_id=position.trade_id,
decision_id=decision.decision_id,
asset=position.asset,
action=decision.action,
side=position.side,
reason=decision.reason,
target_size=target_size,
leverage=position.leverage,
reference_price=decision.reference_price,
confidence=decision.confidence,
bars_held=position.bars_held,
stage=TradeStage.INTENT_CREATED,
exit_leg_ratios=position.exit_leg_ratios,
metadata={
"policy_version": self.config.policy_version,
"exit_ratio": exit_ratio,
"remaining_size_before": position.size,
**decision.metadata,
},
),
trade_id_created=False,
)
@staticmethod
def _trade_id(symbol: str, seq: int) -> str:
return f"{symbol}-T-{seq:012d}"

View File

@@ -0,0 +1,32 @@
"""DITA observability namespace helpers.
These helpers keep DITA diagnostics isolated by runtime namespace while still
allowing optional legacy key mirroring when explicitly requested.
"""
from __future__ import annotations
from dataclasses import dataclass
LEGACY_ANOMALY_SENSOR_KEY = "dita_anomaly_sensors"
@dataclass(frozen=True)
class DitaObservabilityNamespace:
"""Namespace contract for DITA observability payloads."""
runtime_namespace: str = "pink"
feature_map: str = "DOLPHIN_FEATURES"
meta_health_map: str = "DOLPHIN_META_HEALTH"
state_map: str = "DOLPHIN_STATE_PINK"
anomaly_sensor_key: str | None = None
mirror_legacy_key: bool = False
def resolved_sensor_key(self) -> str:
value = str(self.anomaly_sensor_key or "").strip()
if value:
return value
ns = str(self.runtime_namespace or "pink").strip().lower()
return f"{LEGACY_ANOMALY_SENSOR_KEY}_{ns}"

View File

@@ -0,0 +1,139 @@
"""Trade execution and single-slot FSM."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, List, Optional, Sequence
from .contracts import DecisionAction, Intent, TradeEvent, TradePosition, TradeSide, TradeStage
@dataclass(frozen=True)
class TradeExecutionResult:
"""Result of applying an intent to a trade slot."""
intent: Intent
receipt: Optional[Any]
stages: Sequence[TradeStage]
position_before: Optional[TradePosition]
position_after: Optional[TradePosition]
partial_close: bool = False
class TradeExecutor:
"""Single-slot trade FSM.
Owns the live position and translates executable intents into exchange
requests and canonical lifecycle stages.
"""
def __init__(self) -> None:
self.position: Optional[TradePosition] = None
self.trade_history: List[TradeEvent] = []
def execute(self, intent: Intent, exchange: Any, capital_before: float) -> TradeExecutionResult:
position_before = self._clone_position(self.position)
if intent.action == DecisionAction.ENTER:
return self._execute_enter(intent, exchange, capital_before, position_before)
if intent.action == DecisionAction.EXIT:
return self._execute_exit(intent, exchange, capital_before, position_before)
return TradeExecutionResult(
intent=intent,
receipt=None,
stages=(TradeStage.INTENT_CREATED,),
position_before=position_before,
position_after=self._clone_position(self.position),
)
def apply_fill(self, receipt: Any, intent: Intent) -> None:
if receipt is None:
return
if intent.action == DecisionAction.ENTER and receipt.status == "FILLED":
self.position = TradePosition(
trade_id=intent.trade_id,
asset=intent.asset,
side=intent.side,
entry_price=receipt.fill_price,
entry_time=intent.timestamp,
size=receipt.fill_size,
leverage=intent.leverage,
entry_velocity_divergence=float(
intent.metadata.get("entry_velocity_divergence", intent.metadata.get("velocity_divergence", 0.0))
),
entry_irp_alignment=float(intent.metadata.get("entry_irp_alignment", intent.confidence)),
current_price=receipt.fill_price,
initial_size=receipt.fill_size,
exit_leg_ratios=tuple(intent.exit_leg_ratios),
)
return
if intent.action == DecisionAction.EXIT and self.position is not None and receipt.status == "FILLED":
self.position.size = max(0.0, float(receipt.remaining_size))
self.position.exit_price = receipt.fill_price
self.position.realized_pnl += receipt.realized_pnl
self.position.mark_price(receipt.fill_price)
self.position.closed = self.position.size <= 1e-12
self.position.close_reason = intent.reason if self.position.closed else "PARTIAL_" + intent.reason
if self.position.closed:
self.position = None
def _execute_enter(self, intent: Intent, exchange: Any, capital_before: float, position_before: Optional[TradePosition]) -> TradeExecutionResult:
if self.position is not None and not self.position.closed:
return TradeExecutionResult(intent=intent, receipt=exchange.reject(intent, "POSITION_ALREADY_OPEN"), stages=(TradeStage.ORDER_REQUESTED,), position_before=position_before, position_after=self._clone_position(self.position))
receipt = exchange.submit(intent)
stages = (TradeStage.ORDER_REQUESTED, TradeStage.ORDER_SENT)
if receipt and receipt.status == "FILLED":
self.apply_fill(receipt, intent)
stages = stages + (TradeStage.ORDER_ACKED, TradeStage.POSITION_OPENED)
return TradeExecutionResult(intent=intent, receipt=receipt, stages=stages, position_before=position_before, position_after=self._clone_position(self.position))
def _execute_exit(self, intent: Intent, exchange: Any, capital_before: float, position_before: Optional[TradePosition]) -> TradeExecutionResult:
if self.position is None or self.position.closed:
return TradeExecutionResult(intent=intent, receipt=exchange.reject(intent, "NO_OPEN_POSITION"), stages=(TradeStage.EXIT_REQUESTED,), position_before=position_before, position_after=None)
receipt = exchange.submit(intent)
stages = [TradeStage.EXIT_REQUESTED, TradeStage.EXIT_SENT]
if receipt and receipt.status == "FILLED":
self.apply_fill(receipt, intent)
stages.append(TradeStage.EXIT_ACKED)
if self.position is None:
stages.extend([TradeStage.POSITION_CLOSED, TradeStage.TRADE_TERMINAL_WRITTEN])
else:
stages.extend([TradeStage.POSITION_PARTIALLY_CLOSED, TradeStage.POSITION_UPDATED])
return TradeExecutionResult(
intent=intent,
receipt=receipt,
stages=tuple(stages),
position_before=position_before,
position_after=self._clone_position(self.position),
partial_close=self.position is not None,
)
@staticmethod
def _clone_position(position: Optional[TradePosition]) -> Optional[TradePosition]:
if position is None:
return None
return TradePosition(
trade_id=position.trade_id,
asset=position.asset,
side=position.side,
entry_price=position.entry_price,
entry_time=position.entry_time,
size=position.size,
leverage=position.leverage,
entry_velocity_divergence=position.entry_velocity_divergence,
entry_irp_alignment=position.entry_irp_alignment,
bars_held=position.bars_held,
current_price=position.current_price,
realized_pnl=position.realized_pnl,
unrealized_pnl=position.unrealized_pnl,
exit_price=position.exit_price,
closed=position.closed,
close_reason=position.close_reason,
initial_size=position.initial_size,
exit_leg_ratios=position.exit_leg_ratios,
exit_leg_index=position.exit_leg_index,
)
def decision_confidence_from_intent(intent: Intent) -> float:
return max(0.0, min(1.0, float(intent.confidence)))