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:
0
prod/clean_arch/ports/__init__.py
Normal file
0
prod/clean_arch/ports/__init__.py
Normal file
118
prod/clean_arch/ports/data_feed.py
Normal file
118
prod/clean_arch/ports/data_feed.py
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PORT: DataFeed
|
||||
==============
|
||||
Abstract interface for market data sources.
|
||||
|
||||
Clean Architecture Principle:
|
||||
- Core business logic depends on this PORT (interface)
|
||||
- Adapters implement this port
|
||||
- Easy to swap: Hazelcast → Binance → In-Kernel Rust
|
||||
|
||||
Future Evolution:
|
||||
- Current: HazelcastAdapter (DolphinNG6 feed)
|
||||
- Next: BinanceWebsocketAdapter (direct)
|
||||
- Future: RustKernelAdapter (in-kernel, zero-copy)
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional, Callable, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class MarketSnapshot:
|
||||
"""
|
||||
Immutable market snapshot - single source of truth.
|
||||
|
||||
Contains BOTH price and computed features (eigenvalues, etc.)
|
||||
Guaranteed to be synchronized - same timestamp for all fields.
|
||||
"""
|
||||
timestamp: datetime
|
||||
symbol: str
|
||||
|
||||
# Price data
|
||||
price: float
|
||||
bid: Optional[float] = None
|
||||
ask: Optional[float] = None
|
||||
|
||||
# Computed features (from DolphinNG6)
|
||||
eigenvalues: Optional[List[float]] = None
|
||||
eigenvectors: Optional[Any] = None # Matrix
|
||||
velocity_divergence: Optional[float] = None
|
||||
irp_alignment: Optional[float] = None
|
||||
|
||||
# Metadata
|
||||
scan_number: Optional[int] = None
|
||||
source: str = "unknown" # "hazelcast", "binance", "kernel"
|
||||
scan_payload: Optional[Dict[str, Any]] = None
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Check if snapshot has required fields."""
|
||||
return self.price > 0 and self.eigenvalues is not None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ACBUpdate:
|
||||
"""Adaptive Circuit Breaker update."""
|
||||
timestamp: datetime
|
||||
boost: float
|
||||
beta: float
|
||||
cut: float
|
||||
posture: str
|
||||
|
||||
|
||||
class DataFeedPort(ABC):
|
||||
"""
|
||||
PORT: Abstract data feed interface.
|
||||
|
||||
Implementations:
|
||||
- HazelcastDataFeed: Current (DolphinNG6 integration)
|
||||
- BinanceDataFeed: Direct WebSocket
|
||||
- RustKernelDataFeed: Future in-kernel implementation
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self) -> bool:
|
||||
"""Connect to data source."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def disconnect(self):
|
||||
"""Clean disconnect."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_latest_snapshot(self, symbol: str) -> Optional[MarketSnapshot]:
|
||||
"""
|
||||
Get latest synchronized snapshot (price + features).
|
||||
|
||||
This is the KEY method - returns ATOMIC data.
|
||||
No sync issues possible.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def subscribe_snapshots(self, callback: Callable[[MarketSnapshot], None]):
|
||||
"""
|
||||
Subscribe to snapshot stream.
|
||||
|
||||
callback receives MarketSnapshot whenever new data arrives.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_acb_update(self) -> Optional[ACBUpdate]:
|
||||
"""Get latest ACB (Adaptive Circuit Breaker) update."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_latency_ms(self) -> float:
|
||||
"""Report current data latency (for monitoring)."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def health_check(self) -> bool:
|
||||
"""Check if feed is healthy."""
|
||||
pass
|
||||
74
prod/clean_arch/ports/execution.py
Normal file
74
prod/clean_arch/ports/execution.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Execution port for DITA-driven live trading.
|
||||
|
||||
This port is intentionally free of Nautilus Trader node concepts.
|
||||
It provides a direct exchange boundary for PINK/BLUE-compatible DITA runtimes.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExchangeStateSnapshot:
|
||||
"""Exchange-led account/position/order snapshot."""
|
||||
|
||||
timestamp: datetime
|
||||
capital: float
|
||||
equity: float
|
||||
open_positions: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||||
open_orders: list[Dict[str, Any]] = field(default_factory=list)
|
||||
all_orders: list[Dict[str, Any]] = field(default_factory=list)
|
||||
all_fills: list[Dict[str, Any]] = field(default_factory=list)
|
||||
account: Dict[str, Any] = field(default_factory=dict)
|
||||
open_notional: float = 0.0
|
||||
source: str = "exchange"
|
||||
recovered: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ExecutionReceipt:
|
||||
"""Canonical receipt returned by a direct execution adapter."""
|
||||
|
||||
timestamp: datetime
|
||||
status: str
|
||||
symbol: str
|
||||
side: str
|
||||
action: str
|
||||
quantity: float
|
||||
price: float
|
||||
client_order_id: str
|
||||
order_id: str = ""
|
||||
realized_pnl: float = 0.0
|
||||
fees: float = 0.0
|
||||
raw_ack: Dict[str, Any] = field(default_factory=dict)
|
||||
raw_state: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
class ExecutionPort(ABC):
|
||||
"""Direct exchange execution boundary."""
|
||||
|
||||
@abstractmethod
|
||||
async def connect(self) -> bool:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def disconnect(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def refresh_state(self, symbol: str | None = None, *, include_history: bool = False) -> ExchangeStateSnapshot:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def submit_intent(self, intent: Any) -> ExecutionReceipt:
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
async def reconcile(self, symbol: str | None = None) -> ExchangeStateSnapshot:
|
||||
"""Recovery-only path for catastrophic restart/hibernate scenarios."""
|
||||
raise NotImplementedError
|
||||
|
||||
Reference in New Issue
Block a user