Files
ai/backend.old/src/exchange_kernel/base.py
2026-03-11 18:47:11 -04:00

362 lines
9.7 KiB
Python

"""
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