""" Advanced usage example for the EdgeX Python SDK. This example demonstrates more advanced features of the SDK, including: - Order management - WebSocket integration - Error handling - Pagination """ import asyncio import os import logging from decimal import Decimal from typing import Dict, Any, List from edgex_sdk import ( Client, OrderSide, OrderType, TimeInForce, CreateOrderParams, CancelOrderParams, GetActiveOrderParams, OrderFillTransactionParams, GetKLineParams, GetOrderBookDepthParams, WebSocketManager ) # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) class EdgeXTrader: """Example trader using the EdgeX Python SDK.""" def __init__(self, base_url: str, ws_url: str, account_id: int, stark_private_key: str): """ Initialize the trader. Args: base_url: Base URL for API endpoints ws_url: Base URL for WebSocket endpoints account_id: Account ID for authentication stark_private_key: Stark private key for signing """ self.client = Client( base_url=base_url, account_id=account_id, stark_private_key=stark_private_key ) self.ws_manager = WebSocketManager( base_url=ws_url, account_id=account_id, stark_pri_key=stark_private_key ) self.metadata = None self.contracts = {} self.market_data = {} self.active_orders = {} self.positions = {} self.assets = {} async def initialize(self): """Initialize the trader by fetching metadata and account information.""" logger.info("Initializing trader...") try: # Get metadata self.metadata = await self.client.get_metadata() logger.info("Metadata retrieved") # Extract contracts contract_list = self.metadata.get("data", {}).get("contractList", []) for contract in contract_list: contract_id = contract.get("contractId") if contract_id: self.contracts[contract_id] = contract logger.info(f"Found {len(self.contracts)} contracts") # Get account assets assets_response = await self.client.get_account_asset() self.assets = assets_response.get("data", {}) logger.info("Account assets retrieved") # Get account positions positions_response = await self.client.get_account_positions() positions_data = positions_response.get("data", {}) position_list = positions_data.get("positionList", []) for position in position_list: contract_id = position.get("contractId") if contract_id: self.positions[contract_id] = position logger.info(f"Found {len(self.positions)} positions") # Get active orders await self.update_active_orders() # Initialize WebSocket await self.initialize_websocket() logger.info("Trader initialized successfully") return True except Exception as e: logger.error(f"Failed to initialize trader: {str(e)}") return False async def update_active_orders(self): """Update the list of active orders.""" try: params = GetActiveOrderParams() active_orders_response = await self.client.get_active_orders(params) order_list = active_orders_response.get("data", {}).get("list", []) self.active_orders = {} for order in order_list: order_id = order.get("orderId") if order_id: self.active_orders[order_id] = order logger.info(f"Found {len(self.active_orders)} active orders") return True except Exception as e: logger.error(f"Failed to update active orders: {str(e)}") return False async def initialize_websocket(self): """Initialize WebSocket connections and subscriptions.""" try: # Connect to public WebSocket self.ws_manager.connect_public() logger.info("Connected to public WebSocket") # Connect to private WebSocket self.ws_manager.connect_private() logger.info("Connected to private WebSocket") # Subscribe to account updates self.ws_manager.subscribe_account_update(self.handle_account_update) logger.info("Subscribed to account updates") # Subscribe to order updates self.ws_manager.subscribe_order_update(self.handle_order_update) logger.info("Subscribed to order updates") # Subscribe to position updates self.ws_manager.subscribe_position_update(self.handle_position_update) logger.info("Subscribed to position updates") # Subscribe to market data for BTCUSDT (contract ID: 10000001) self.ws_manager.subscribe_ticker("10000001", self.handle_ticker_update) self.ws_manager.subscribe_kline("10000001", "1m", self.handle_kline_update) self.ws_manager.subscribe_depth("10000001", self.handle_depth_update) logger.info("Subscribed to market data for BTCUSDT (10000001)") return True except Exception as e: logger.error(f"Failed to initialize WebSocket: {str(e)}") return False def handle_account_update(self, message: str): """ Handle account update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) logger.info(f"Account update: {data}") # Update assets account_data = data.get("content", {}).get("data", {}) if account_data: self.assets = account_data except Exception as e: logger.error(f"Failed to handle account update: {str(e)}") def handle_order_update(self, message: str): """ Handle order update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) logger.info(f"Order update: {data}") # Update active orders asyncio.create_task(self.update_active_orders()) except Exception as e: logger.error(f"Failed to handle order update: {str(e)}") def handle_position_update(self, message: str): """ Handle position update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) logger.info(f"Position update: {data}") # Update positions position_data = data.get("content", {}).get("data", {}) contract_id = position_data.get("contractId") if contract_id: self.positions[contract_id] = position_data except Exception as e: logger.error(f"Failed to handle position update: {str(e)}") def handle_ticker_update(self, message: str): """ Handle ticker update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) # Extract ticker data content = data.get("content", {}) ticker_data_list = content.get("data", []) # Handle both single ticker and list of tickers if isinstance(ticker_data_list, list) and ticker_data_list: ticker_data = ticker_data_list[0] # Take the first ticker else: ticker_data = ticker_data_list contract_id = ticker_data.get("contractId") if isinstance(ticker_data, dict) else None if contract_id: if "ticker" not in self.market_data: self.market_data["ticker"] = {} self.market_data["ticker"][contract_id] = ticker_data logger.info(f"Ticker update for {contract_id}: {ticker_data.get('lastPrice')}") except Exception as e: logger.error(f"Failed to handle ticker update: {str(e)}") def handle_kline_update(self, message: str): """ Handle K-line update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) # Extract K-line data kline_data = data.get("content", {}).get("data", {}) contract_id = kline_data.get("contractId") interval = kline_data.get("interval") if contract_id and interval: if "kline" not in self.market_data: self.market_data["kline"] = {} if contract_id not in self.market_data["kline"]: self.market_data["kline"][contract_id] = {} self.market_data["kline"][contract_id][interval] = kline_data logger.info(f"K-line update for {contract_id} {interval}: {kline_data.get('close')}") except Exception as e: logger.error(f"Failed to handle K-line update: {str(e)}") def handle_depth_update(self, message: str): """ Handle depth update messages from WebSocket. Args: message: The WebSocket message """ try: import json data = json.loads(message) # Extract depth data depth_data = data.get("content", {}).get("data", {}) contract_id = depth_data.get("contractId") if contract_id: if "depth" not in self.market_data: self.market_data["depth"] = {} self.market_data["depth"][contract_id] = depth_data logger.info(f"Depth update for {contract_id}") except Exception as e: logger.error(f"Failed to handle depth update: {str(e)}") async def create_limit_order( self, contract_id: str, size: str, price: str, side: str, time_in_force: str = TimeInForce.GOOD_TIL_CANCEL, reduce_only: bool = False ) -> Dict[str, Any]: """ Create a limit order. Args: contract_id: The contract ID size: The order size price: The order price side: The order side (BUY or SELL) time_in_force: The time in force reduce_only: Whether the order is reduce-only Returns: Dict[str, Any]: The created order Raises: ValueError: If the order creation fails """ try: # Create order parameters params = CreateOrderParams( contract_id=contract_id, size=size, price=price, type=OrderType.LIMIT, side=side, time_in_force=time_in_force, reduce_only=reduce_only ) # Create the order result = await self.client.create_order(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to create order: {error_param}") raise ValueError(f"Failed to create order: {result.get('code')}") # Update active orders await self.update_active_orders() logger.info(f"Created limit order: {result.get('data', {}).get('orderId')}") return result except Exception as e: logger.error(f"Failed to create limit order: {str(e)}") raise async def cancel_order(self, order_id: str) -> Dict[str, Any]: """ Cancel an order. Args: order_id: The order ID Returns: Dict[str, Any]: The cancellation result Raises: ValueError: If the order cancellation fails """ try: # Create cancel order parameters params = CancelOrderParams(order_id=order_id) # Cancel the order result = await self.client.cancel_order(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to cancel order: {error_param}") raise ValueError(f"Failed to cancel order: {result.get('code')}") # Update active orders await self.update_active_orders() logger.info(f"Cancelled order: {order_id}") return result except Exception as e: logger.error(f"Failed to cancel order: {str(e)}") raise async def cancel_all_orders(self, contract_id: str = None) -> Dict[str, Any]: """ Cancel all orders for a contract. Args: contract_id: The contract ID (optional) Returns: Dict[str, Any]: The cancellation result Raises: ValueError: If the order cancellation fails """ try: # Create cancel order parameters params = CancelOrderParams(contract_id=contract_id or "") # Cancel the orders result = await self.client.cancel_order(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to cancel orders: {error_param}") raise ValueError(f"Failed to cancel orders: {result.get('code')}") # Update active orders await self.update_active_orders() logger.info(f"Cancelled all orders for contract: {contract_id or 'all'}") return result except Exception as e: logger.error(f"Failed to cancel all orders: {str(e)}") raise async def get_order_fill_transactions( self, contract_id: str = None, size: str = "10", offset_data: str = "" ) -> Dict[str, Any]: """ Get order fill transactions. Args: contract_id: The contract ID (optional) size: The page size offset_data: The offset data for pagination Returns: Dict[str, Any]: The order fill transactions Raises: ValueError: If the request fails """ try: # Create parameters params = OrderFillTransactionParams( size=size, offset_data=offset_data ) if contract_id: params.filter_contract_id_list = [contract_id] # Get order fill transactions result = await self.client.get_order_fill_transactions(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to get order fill transactions: {error_param}") raise ValueError(f"Failed to get order fill transactions: {result.get('code')}") logger.info(f"Got order fill transactions: {len(result.get('data', {}).get('list', []))}") return result except Exception as e: logger.error(f"Failed to get order fill transactions: {str(e)}") raise async def get_k_line( self, contract_id: str, interval: str, size: str = "100", offset_data: str = "" ) -> Dict[str, Any]: """ Get K-line data. Args: contract_id: The contract ID interval: The K-line interval size: The page size offset_data: The offset data for pagination Returns: Dict[str, Any]: The K-line data Raises: ValueError: If the request fails """ try: # Create parameters params = GetKLineParams( contract_id=contract_id, interval=interval, size=size, offset_data=offset_data ) # Get K-line data result = await self.client.quote.get_k_line(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to get K-line data: {error_param}") raise ValueError(f"Failed to get K-line data: {result.get('code')}") logger.info(f"Got K-line data: {len(result.get('data', {}).get('list', []))}") return result except Exception as e: logger.error(f"Failed to get K-line data: {str(e)}") raise async def get_order_book_depth( self, contract_id: str, limit: int = 15 ) -> Dict[str, Any]: """ Get order book depth. Args: contract_id: The contract ID limit: The depth limit (valid values are 15 or 200) Returns: Dict[str, Any]: The order book depth Raises: ValueError: If the request fails """ try: # Create parameters params = GetOrderBookDepthParams( contract_id=contract_id, limit=limit ) # Get order book depth result = await self.client.quote.get_order_book_depth(params) # Check for success if result.get("code") != "SUCCESS": error_param = result.get("errorParam") if error_param: raise ValueError(f"Failed to get order book depth: {error_param}") raise ValueError(f"Failed to get order book depth: {result.get('code')}") logger.info(f"Got order book depth for {contract_id}") return result except Exception as e: logger.error(f"Failed to get order book depth: {str(e)}") raise async def close(self): """Close all connections.""" try: # Disconnect WebSocket self.ws_manager.disconnect_all() logger.info("Disconnected from WebSocket") return True except Exception as e: logger.error(f"Failed to close connections: {str(e)}") return False async def main(): """Main function.""" # Load configuration from environment variables base_url = os.getenv("EDGEX_BASE_URL", "https://testnet.edgex.exchange") ws_url = os.getenv("EDGEX_WS_URL", "wss://quote-testnet.edgex.exchange") account_id = int(os.getenv("EDGEX_ACCOUNT_ID", "12345")) stark_private_key = os.getenv("EDGEX_STARK_PRIVATE_KEY", "your-stark-private-key") # Create trader trader = EdgeXTrader( base_url=base_url, ws_url=ws_url, account_id=account_id, stark_private_key=stark_private_key ) # Initialize trader if not await trader.initialize(): logger.error("Failed to initialize trader") return try: # Get K-line data for BTCUSDT (contract ID: 10000001) klines = await trader.get_k_line("10000001", "1m") logger.info(f"Retrieved K-line data: {len(klines.get('data', {}).get('list', []))} entries") # Get order book depth for BTCUSDT (contract ID: 10000001) await trader.get_order_book_depth("10000001") logger.info(f"Retrieved order book depth") # Create a limit order (commented out to avoid actual order creation) # order = await trader.create_limit_order( # contract_id="10000001", # BTCUSDT # size="0.001", # price="30000", # side=OrderSide.BUY # ) # # # Cancel the order # if order and order.get("data", {}).get("orderId"): # await trader.cancel_order(order.get("data", {}).get("orderId")) # Wait for some WebSocket updates logger.info("Waiting for WebSocket updates...") await asyncio.sleep(60) finally: # Close connections await trader.close() if __name__ == "__main__": asyncio.run(main())