import abc import logging from typing import Dict, Any, List, Tuple, Optional logger = logging.getLogger(__name__) class ExchangeInterface(abc.ABC): """Base class for all exchange interfaces. This abstract class defines the required methods that all exchange implementations must provide to ensure compatibility with the trading system. """ def __init__(self, api_key: str = None, api_secret: str = None, test_mode: bool = True): """Initialize the exchange interface. Args: api_key: API key for the exchange api_secret: API secret for the exchange test_mode: If True, use test/sandbox environment """ self.api_key = api_key self.api_secret = api_secret self.test_mode = test_mode self.client = None self.last_price_cache = {} @abc.abstractmethod def connect(self) -> bool: """Connect to the exchange API. Returns: bool: True if connection successful, False otherwise """ pass @abc.abstractmethod def get_balance(self, asset: str) -> float: """Get balance of a specific asset. Args: asset: Asset symbol (e.g., 'BTC', 'USDT') Returns: float: Available balance of the asset """ pass @abc.abstractmethod def get_ticker(self, symbol: str) -> Dict[str, Any]: """Get current ticker data for a symbol. Args: symbol: Trading symbol (e.g., 'BTC/USDT') Returns: dict: Ticker data including price information """ pass @abc.abstractmethod def place_order(self, symbol: str, side: str, order_type: str, quantity: float, price: float = None) -> Dict[str, Any]: """Place an order on the exchange. Args: symbol: Trading symbol (e.g., 'BTC/USDT') side: Order side ('buy' or 'sell') order_type: Order type ('market', 'limit', etc.) quantity: Order quantity price: Order price (for limit orders) Returns: dict: Order information including order ID """ pass @abc.abstractmethod def cancel_order(self, symbol: str, order_id: str) -> bool: """Cancel an existing order. Args: symbol: Trading symbol (e.g., 'BTC/USDT') order_id: ID of the order to cancel Returns: bool: True if cancellation successful, False otherwise """ pass @abc.abstractmethod def get_order_status(self, symbol: str, order_id: str) -> Dict[str, Any]: """Get status of an existing order. Args: symbol: Trading symbol (e.g., 'BTC/USDT') order_id: ID of the order Returns: dict: Order status information """ pass @abc.abstractmethod def get_open_orders(self, symbol: str = None) -> List[Dict[str, Any]]: """Get all open orders, optionally filtered by symbol. Args: symbol: Trading symbol (e.g., 'BTC/USDT'), or None for all symbols Returns: list: List of open orders """ pass def get_last_price(self, symbol: str) -> float: """Get last known price for a symbol, may use cached value. Args: symbol: Trading symbol (e.g., 'BTC/USDT') Returns: float: Last price """ try: ticker = self.get_ticker(symbol) price = float(ticker['last']) self.last_price_cache[symbol] = price return price except Exception as e: logger.error(f"Error getting price for {symbol}: {str(e)}") # Return cached price if available return self.last_price_cache.get(symbol, 0.0) def execute_trade(self, symbol: str, action: str, quantity: float = None, percent_of_balance: float = None) -> Optional[Dict[str, Any]]: """Execute a trade based on a signal. Args: symbol: Trading symbol (e.g., 'BTC/USDT') action: Trade action ('BUY', 'SELL') quantity: Specific quantity to trade percent_of_balance: Alternative to quantity - percentage of available balance to use Returns: dict: Order information or None if order failed """ if action not in ['BUY', 'SELL']: logger.error(f"Invalid action: {action}. Must be 'BUY' or 'SELL'") return None side = action.lower() try: # Determine base and quote assets from symbol (e.g., BTC/USDT -> BTC, USDT) base_asset, quote_asset = symbol.split('/') # Calculate quantity if percent_of_balance is provided if quantity is None and percent_of_balance is not None: if percent_of_balance <= 0 or percent_of_balance > 1: logger.error(f"Invalid percent_of_balance: {percent_of_balance}. Must be between 0 and 1") return None if side == 'buy': # For buy, use quote asset (e.g., USDT) balance = self.get_balance(quote_asset) price = self.get_last_price(symbol) quantity = (balance * percent_of_balance) / price else: # For sell, use base asset (e.g., BTC) balance = self.get_balance(base_asset) quantity = balance * percent_of_balance if not quantity or quantity <= 0: logger.error(f"Invalid quantity: {quantity}") return None # Place market order order = self.place_order( symbol=symbol, side=side, order_type='market', quantity=quantity ) logger.info(f"Executed {side.upper()} order for {quantity} {base_asset} at market price") return order except Exception as e: logger.error(f"Error executing {action} trade for {symbol}: {str(e)}") return None