""" State management for the Exchange Kernel. Implements the storage and reconciliation logic for desired vs actual state. This is the "Kubernetes for orders" concept - maintaining intent and continuously reconciling reality to match intent. """ from abc import ABC, abstractmethod from typing import Any from collections import defaultdict from .models import OrderIntent, OrderState, ReconciliationStatus from ..schema.order_spec import Uint64 # --------------------------------------------------------------------------- # Intent State Store - Desired State # --------------------------------------------------------------------------- class IntentStateStore(ABC): """ Storage for order intents (desired state). This represents what the strategy kernel wants. Intents are durable and persist across restarts. The reconciliation loop continuously works to make actual state match these intents. """ @abstractmethod async def create_intent(self, intent: OrderIntent) -> None: """ Store a new order intent. Args: intent: The order intent to store Raises: AlreadyExistsError: If intent_id already exists """ pass @abstractmethod async def get_intent(self, intent_id: str) -> OrderIntent: """ Retrieve an order intent. Args: intent_id: Intent ID to retrieve Returns: The order intent Raises: NotFoundError: If intent_id doesn't exist """ pass @abstractmethod async def update_intent(self, intent: OrderIntent) -> None: """ Update an existing order intent. Args: intent: Updated intent (intent_id must match existing) Raises: NotFoundError: If intent_id doesn't exist """ pass @abstractmethod async def delete_intent(self, intent_id: str) -> None: """ Delete an order intent. Args: intent_id: Intent ID to delete Raises: NotFoundError: If intent_id doesn't exist """ pass @abstractmethod async def list_intents( self, symbol_id: str | None = None, group_id: str | None = None, ) -> list[OrderIntent]: """ List all order intents, optionally filtered. Args: symbol_id: Filter by symbol group_id: Filter by OCO group Returns: List of matching intents """ pass @abstractmethod async def get_intents_by_group(self, group_id: str) -> list[OrderIntent]: """ Get all intents in an OCO group. Args: group_id: Group ID to query Returns: List of intents in the group """ pass # --------------------------------------------------------------------------- # Actual State Store - Current Reality # --------------------------------------------------------------------------- class ActualStateStore(ABC): """ Storage for actual order state (reality on exchange). This represents what actually exists on the exchange right now. Updated frequently from exchange feeds and order status queries. """ @abstractmethod async def create_order_state(self, state: OrderState) -> None: """ Store a new order state. Args: state: The order state to store Raises: AlreadyExistsError: If order state for this intent_id already exists """ pass @abstractmethod async def get_order_state(self, intent_id: str) -> OrderState: """ Retrieve order state for an intent. Args: intent_id: Intent ID to query Returns: The current order state Raises: NotFoundError: If no state exists for this intent """ pass @abstractmethod async def get_order_state_by_exchange_id(self, exchange_order_id: str) -> OrderState: """ Retrieve order state by exchange order ID. Useful for processing exchange callbacks that only provide exchange_order_id. Args: exchange_order_id: Exchange's order ID Returns: The order state Raises: NotFoundError: If no state exists for this exchange order ID """ pass @abstractmethod async def update_order_state(self, state: OrderState) -> None: """ Update an existing order state. Args: state: Updated state (intent_id must match existing) Raises: NotFoundError: If state doesn't exist """ pass @abstractmethod async def delete_order_state(self, intent_id: str) -> None: """ Delete an order state. Args: intent_id: Intent ID whose state to delete Raises: NotFoundError: If state doesn't exist """ pass @abstractmethod async def list_order_states( self, symbol_id: str | None = None, reconciliation_status: ReconciliationStatus | None = None, ) -> list[OrderState]: """ List all order states, optionally filtered. Args: symbol_id: Filter by symbol reconciliation_status: Filter by reconciliation status Returns: List of matching order states """ pass @abstractmethod async def get_stale_orders(self, max_age_seconds: int) -> list[OrderState]: """ Find orders that haven't been synced recently. Used to identify orders that need status updates from exchange. Args: max_age_seconds: Maximum age since last sync Returns: List of order states that need refresh """ pass # --------------------------------------------------------------------------- # In-Memory Implementations (for testing/prototyping) # --------------------------------------------------------------------------- class InMemoryIntentStore(IntentStateStore): """Simple in-memory implementation of IntentStateStore""" def __init__(self): self._intents: dict[str, OrderIntent] = {} self._by_symbol: dict[str, set[str]] = defaultdict(set) self._by_group: dict[str, set[str]] = defaultdict(set) async def create_intent(self, intent: OrderIntent) -> None: if intent.intent_id in self._intents: raise ValueError(f"Intent {intent.intent_id} already exists") self._intents[intent.intent_id] = intent self._by_symbol[intent.order.symbol_id].add(intent.intent_id) if intent.group_id: self._by_group[intent.group_id].add(intent.intent_id) async def get_intent(self, intent_id: str) -> OrderIntent: if intent_id not in self._intents: raise KeyError(f"Intent {intent_id} not found") return self._intents[intent_id] async def update_intent(self, intent: OrderIntent) -> None: if intent.intent_id not in self._intents: raise KeyError(f"Intent {intent.intent_id} not found") old_intent = self._intents[intent.intent_id] # Update indices if symbol or group changed if old_intent.order.symbol_id != intent.order.symbol_id: self._by_symbol[old_intent.order.symbol_id].discard(intent.intent_id) self._by_symbol[intent.order.symbol_id].add(intent.intent_id) if old_intent.group_id != intent.group_id: if old_intent.group_id: self._by_group[old_intent.group_id].discard(intent.intent_id) if intent.group_id: self._by_group[intent.group_id].add(intent.intent_id) self._intents[intent.intent_id] = intent async def delete_intent(self, intent_id: str) -> None: if intent_id not in self._intents: raise KeyError(f"Intent {intent_id} not found") intent = self._intents[intent_id] self._by_symbol[intent.order.symbol_id].discard(intent_id) if intent.group_id: self._by_group[intent.group_id].discard(intent_id) del self._intents[intent_id] async def list_intents( self, symbol_id: str | None = None, group_id: str | None = None, ) -> list[OrderIntent]: if symbol_id and group_id: # Intersection of both filters symbol_ids = self._by_symbol.get(symbol_id, set()) group_ids = self._by_group.get(group_id, set()) intent_ids = symbol_ids & group_ids elif symbol_id: intent_ids = self._by_symbol.get(symbol_id, set()) elif group_id: intent_ids = self._by_group.get(group_id, set()) else: intent_ids = self._intents.keys() return [self._intents[iid] for iid in intent_ids] async def get_intents_by_group(self, group_id: str) -> list[OrderIntent]: intent_ids = self._by_group.get(group_id, set()) return [self._intents[iid] for iid in intent_ids] class InMemoryActualStateStore(ActualStateStore): """Simple in-memory implementation of ActualStateStore""" def __init__(self): self._states: dict[str, OrderState] = {} self._by_exchange_id: dict[str, str] = {} # exchange_order_id -> intent_id self._by_symbol: dict[str, set[str]] = defaultdict(set) async def create_order_state(self, state: OrderState) -> None: if state.intent_id in self._states: raise ValueError(f"Order state for intent {state.intent_id} already exists") self._states[state.intent_id] = state self._by_exchange_id[state.exchange_order_id] = state.intent_id self._by_symbol[state.status.order.symbol_id].add(state.intent_id) async def get_order_state(self, intent_id: str) -> OrderState: if intent_id not in self._states: raise KeyError(f"Order state for intent {intent_id} not found") return self._states[intent_id] async def get_order_state_by_exchange_id(self, exchange_order_id: str) -> OrderState: if exchange_order_id not in self._by_exchange_id: raise KeyError(f"Order state for exchange order {exchange_order_id} not found") intent_id = self._by_exchange_id[exchange_order_id] return self._states[intent_id] async def update_order_state(self, state: OrderState) -> None: if state.intent_id not in self._states: raise KeyError(f"Order state for intent {state.intent_id} not found") old_state = self._states[state.intent_id] # Update exchange_id index if it changed if old_state.exchange_order_id != state.exchange_order_id: del self._by_exchange_id[old_state.exchange_order_id] self._by_exchange_id[state.exchange_order_id] = state.intent_id # Update symbol index if it changed old_symbol = old_state.status.order.symbol_id new_symbol = state.status.order.symbol_id if old_symbol != new_symbol: self._by_symbol[old_symbol].discard(state.intent_id) self._by_symbol[new_symbol].add(state.intent_id) self._states[state.intent_id] = state async def delete_order_state(self, intent_id: str) -> None: if intent_id not in self._states: raise KeyError(f"Order state for intent {intent_id} not found") state = self._states[intent_id] del self._by_exchange_id[state.exchange_order_id] self._by_symbol[state.status.order.symbol_id].discard(intent_id) del self._states[intent_id] async def list_order_states( self, symbol_id: str | None = None, reconciliation_status: ReconciliationStatus | None = None, ) -> list[OrderState]: if symbol_id: intent_ids = self._by_symbol.get(symbol_id, set()) states = [self._states[iid] for iid in intent_ids] else: states = list(self._states.values()) if reconciliation_status: states = [s for s in states if s.reconciliation_status == reconciliation_status] return states async def get_stale_orders(self, max_age_seconds: int) -> list[OrderState]: import time current_time = int(time.time()) threshold = current_time - max_age_seconds return [ state for state in self._states.values() if state.last_sync_at < threshold ] # --------------------------------------------------------------------------- # Reconciliation Engine (framework only, no implementation) # --------------------------------------------------------------------------- class ReconciliationEngine: """ Reconciliation engine that continuously works to make actual state match intent. This is the heart of the "Kubernetes for orders" concept. It: 1. Compares desired state (intents) with actual state (exchange orders) 2. Computes necessary actions (place, modify, cancel) 3. Executes those actions via the exchange API 4. Handles retries, errors, and edge cases This is a framework class - concrete implementations will be exchange-specific. """ def __init__( self, intent_store: IntentStateStore, actual_store: ActualStateStore, ): """ Initialize the reconciliation engine. Args: intent_store: Store for desired state actual_store: Store for actual state """ self.intent_store = intent_store self.actual_store = actual_store self._running = False async def start(self) -> None: """Start the reconciliation loop""" self._running = True # Implementation would start async reconciliation loop here pass async def stop(self) -> None: """Stop the reconciliation loop""" self._running = False # Implementation would stop reconciliation loop here pass async def reconcile_intent(self, intent_id: str) -> None: """ Reconcile a specific intent. Compares the intent with actual state and takes necessary actions. Args: intent_id: Intent to reconcile """ # Framework only - concrete implementation needed pass async def reconcile_all(self) -> None: """ Reconcile all intents. Full reconciliation pass over all orders. """ # Framework only - concrete implementation needed pass def get_metrics(self) -> dict[str, Any]: """ Get reconciliation metrics. Returns: Metrics about reconciliation performance, errors, etc. """ return { "running": self._running, "reconciliation_lag_ms": 0, # Framework only "pending_reconciliations": 0, # Framework only "error_count": 0, # Framework only "retry_count": 0, # Framework only }