362 lines
9.7 KiB
Python
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
|