""" Base interface for Exchange Kernels. Defines the abstract API that all exchange kernel implementations must support. Each exchange (or exchange type) will have its own kernel implementation. """ from abc import ABC, abstractmethod from typing import Callable, Any from .models import ( OrderIntent, OrderState, Position, AccountState, AssetMetadata, ) from .events import BaseEvent from ..schema.order_spec import ( StandardOrder, StandardOrderGroup, SymbolMetadata, ) class ExchangeKernel(ABC): """ Abstract base class for exchange kernels. An exchange kernel manages the lifecycle of orders on a specific exchange, maintaining both desired state (intents from strategy kernel) and actual state (current orders on exchange), and continuously reconciling them. Think of it as a Kubernetes-style controller for trading orders. """ def __init__(self, exchange_id: str): """ Initialize the exchange kernel. Args: exchange_id: Unique identifier for this exchange instance """ self.exchange_id = exchange_id # ------------------------------------------------------------------------- # Command API - Strategy kernel sends intents # ------------------------------------------------------------------------- @abstractmethod async def place_order(self, order: StandardOrder, metadata: dict[str, Any] | None = None) -> str: """ Place a single order on the exchange. This creates an OrderIntent and begins the reconciliation process to get the order onto the exchange. Args: order: The order specification metadata: Optional strategy-specific metadata Returns: intent_id: Unique identifier for this order intent Raises: ValidationError: If order violates market constraints ExchangeError: If exchange rejects the order """ pass @abstractmethod async def place_order_group( self, group: StandardOrderGroup, metadata: dict[str, Any] | None = None ) -> list[str]: """ Place a group of orders with OCO (One-Cancels-Other) relationship. Args: group: Group of orders with OCO mode metadata: Optional strategy-specific metadata Returns: intent_ids: List of intent IDs for each order in the group Raises: ValidationError: If any order violates market constraints ExchangeError: If exchange rejects the group """ pass @abstractmethod async def cancel_order(self, intent_id: str) -> None: """ Cancel an order by intent ID. Updates the intent to indicate cancellation is desired, and the reconciliation loop will handle the actual exchange cancellation. Args: intent_id: Intent ID of the order to cancel Raises: NotFoundError: If intent_id doesn't exist ExchangeError: If exchange rejects cancellation """ pass @abstractmethod async def modify_order( self, intent_id: str, new_order: StandardOrder, ) -> None: """ Modify an existing order. Updates the order intent, and the reconciliation loop will update the exchange order (via modify API if available, or cancel+replace). Args: intent_id: Intent ID of the order to modify new_order: New order specification Raises: NotFoundError: If intent_id doesn't exist ValidationError: If new order violates market constraints ExchangeError: If exchange rejects modification """ pass @abstractmethod async def cancel_all_orders(self, symbol_id: str | None = None) -> int: """ Cancel all orders, optionally filtered by symbol. Args: symbol_id: If provided, only cancel orders for this symbol Returns: count: Number of orders canceled """ pass # ------------------------------------------------------------------------- # Query API - Read desired and actual state # ------------------------------------------------------------------------- @abstractmethod async def get_order_intent(self, intent_id: str) -> OrderIntent: """ Get the desired order state (what strategy kernel wants). Args: intent_id: Intent ID to query Returns: The order intent Raises: NotFoundError: If intent_id doesn't exist """ pass @abstractmethod async def get_order_state(self, intent_id: str) -> OrderState: """ Get the actual order state (what's currently on exchange). Args: intent_id: Intent ID to query Returns: The current order state Raises: NotFoundError: If intent_id doesn't exist """ pass @abstractmethod async def get_all_intents(self, symbol_id: str | None = None) -> list[OrderIntent]: """ Get all order intents, optionally filtered by symbol. Args: symbol_id: If provided, only return intents for this symbol Returns: List of order intents """ pass @abstractmethod async def get_all_orders(self, symbol_id: str | None = None) -> list[OrderState]: """ Get all actual order states, optionally filtered by symbol. Args: symbol_id: If provided, only return orders for this symbol Returns: List of order states """ pass @abstractmethod async def get_positions(self, symbol_id: str | None = None) -> list[Position]: """ Get current positions, optionally filtered by symbol. Args: symbol_id: If provided, only return positions for this symbol Returns: List of positions """ pass @abstractmethod async def get_account_state(self) -> AccountState: """ Get current account state (balances, margin, etc.). Returns: Current account state """ pass @abstractmethod async def get_symbol_metadata(self, symbol_id: str) -> SymbolMetadata: """ Get metadata for a symbol (constraints, capabilities, etc.). Args: symbol_id: Symbol to query Returns: Symbol metadata Raises: NotFoundError: If symbol doesn't exist on this exchange """ pass @abstractmethod async def get_asset_metadata(self, asset_id: str) -> AssetMetadata: """ Get metadata for an asset. Args: asset_id: Asset to query Returns: Asset metadata Raises: NotFoundError: If asset doesn't exist """ pass @abstractmethod async def list_symbols(self) -> list[str]: """ List all available symbols on this exchange. Returns: List of symbol IDs """ pass # ------------------------------------------------------------------------- # Event Subscription API # ------------------------------------------------------------------------- @abstractmethod def subscribe_events( self, callback: Callable[[BaseEvent], None], event_filter: dict[str, Any] | None = None, ) -> str: """ Subscribe to events from this exchange kernel. Args: callback: Function to call when events occur event_filter: Optional filter criteria (event_type, symbol_id, etc.) Returns: subscription_id: Unique ID for this subscription (for unsubscribe) """ pass @abstractmethod def unsubscribe_events(self, subscription_id: str) -> None: """ Unsubscribe from events. Args: subscription_id: Subscription ID returned from subscribe_events """ pass # ------------------------------------------------------------------------- # Lifecycle Management # ------------------------------------------------------------------------- @abstractmethod async def start(self) -> None: """ Start the exchange kernel. Initializes connections, starts reconciliation loops, etc. """ pass @abstractmethod async def stop(self) -> None: """ Stop the exchange kernel. Closes connections, stops reconciliation loops, etc. Does NOT cancel open orders - call cancel_all_orders() first if desired. """ pass @abstractmethod async def health_check(self) -> dict[str, Any]: """ Check health status of the exchange kernel. Returns: Health status dict with connection state, latency, error counts, etc. """ pass # ------------------------------------------------------------------------- # Reconciliation Control (advanced) # ------------------------------------------------------------------------- @abstractmethod async def force_reconciliation(self, intent_id: str | None = None) -> None: """ Force immediate reconciliation. Args: intent_id: If provided, only reconcile this specific intent. If None, reconcile all intents. """ pass @abstractmethod def get_reconciliation_metrics(self) -> dict[str, Any]: """ Get metrics about the reconciliation process. Returns: Metrics dict with reconciliation lag, error rates, retry counts, etc. """ pass