""" VeighNa 服务包装类 封装 MainEngine 和 EventEngine,提供线程安全的访问接口 """ import asyncio import threading from typing import Optional, Dict, List, Any from datetime import datetime from concurrent.futures import ThreadPoolExecutor import logging logger = logging.getLogger(__name__) # 尝试导入 VeighNa 模块 try: from vnpy.trader.engine import MainEngine from vnpy.event import EventEngine from vnpy.trader.object import ( OrderRequest, CancelRequest, SubscribeRequest, OrderData, TradeData, TickData, PositionData, AccountData, ContractData ) from vnpy.trader.constant import Exchange, Direction, OrderType, Offset from vnpy.trader.setting import SETTINGS from vnpy.gateway.ctp import CtpGateway from vnpy.gateway.ib import IbGateway from vnpy.gateway.okx import OkxGateway from vnpy.gateway.binance import BinanceGateway VNPY_AVAILABLE = True except ImportError: VNPY_AVAILABLE = False logger.warning("VeighNa modules not available, running in mock mode") class VeighNaService: """ VeighNa 服务包装类 提供对 VeighNa MainEngine 的异步访问接口 线程安全设计:所有对 MainEngine 的访问都通过事件循环调度 """ def __init__(self): self._main_engine: Optional[Any] = None self._event_engine: Optional[Any] = None self._initialized: bool = False self._lock = threading.Lock() self._loop: Optional[asyncio.AbstractEventLoop] = None self._executor: Optional[ThreadPoolExecutor] = None # 数据缓存 self._ticks_cache: Dict[str, Any] = {} self._orders_cache: Dict[str, Any] = {} self._trades_cache: Dict[str, Any] = {} self._positions_cache: Dict[str, Any] = {} self._accounts_cache: Dict[str, Any] = {} self._contracts_cache: Dict[str, Any] = {} self._connected_gateways: Dict[str, str] = {} # gateway_name -> gateway_type async def initialize(self) -> None: """初始化 VeighNa 服务""" with self._lock: if self._initialized: return logger.info("Initializing VeighNa service...") try: if VNPY_AVAILABLE: # 创建线程池执行器 self._executor = ThreadPoolExecutor(max_workers=4, thread_name_prefix="vnpy_") # 在线程中初始化 VeighNa def init_vnpy(): # 创建事件引擎 event_engine = EventEngine() event_engine.start() # 创建主引擎 main_engine = MainEngine(event_engine) # 添加网关 main_engine.add_gateway(CtpGateway) main_engine.add_gateway(IbGateway) main_engine.add_gateway(OkxGateway) main_engine.add_gateway(BinanceGateway) return main_engine, event_engine # 在线程池中执行初始化 loop = asyncio.get_event_loop() self._main_engine, self._event_engine = await loop.run_in_executor( self._executor, init_vnpy ) self._loop = loop self._initialized = True logger.info("VeighNa service initialized successfully") else: logger.info("Running in mock mode for development") self._initialized = True except Exception as e: logger.error(f"Failed to initialize VeighNa: {e}") raise async def shutdown(self) -> None: """关闭 VeighNa 服务""" with self._lock: if not self._initialized: return logger.info("Shutting down VeighNa service...") if self._main_engine: try: def close_vnpy(): self._main_engine.close() if self._event_engine: self._event_engine.stop() if self._loop and self._executor: await self._loop.run_in_executor(self._executor, close_vnpy) self._executor.shutdown(wait=True) except Exception as e: logger.error(f"Error shutting down VeighNa: {e}") self._main_engine = None self._event_engine = None self._executor = None self._initialized = False logger.info("VeighNa service shutdown complete") @property def is_initialized(self) -> bool: """检查服务是否已初始化""" return self._initialized @property def main_engine(self) -> Optional[Any]: """获取主引擎(谨慎使用,需处理线程安全)""" return self._main_engine @property def event_engine(self) -> Optional[Any]: """获取事件引擎(谨慎使用,需处理线程安全)""" return self._event_engine # ============================================ # 网关管理 # ============================================ async def get_available_gateways(self) -> List[Dict[str, Any]]: """获取所有可用网关列表""" if not self._main_engine: # 返回默认网关列表(Mock 模式) return [ {"gateway_name": "CTP", "gateway_type": "ctp", "display_name": "CTP期货"}, {"gateway_name": "IB", "gateway_type": "ib", "display_name": "Interactive Brokers"}, {"gateway_name": "OKX", "gateway_type": "okx", "display_name": "OKX"}, {"gateway_name": "BINANCE", "gateway_type": "binance", "display_name": "Binance"}, ] try: gateway_names = self._main_engine.get_all_gateway_names() gateways = [] for name in gateway_names: gw = self._main_engine.get_gateway(name) if gw: gateways.append({ "gateway_name": name, "gateway_type": type(gw).__name__.replace("Gateway", "").lower(), "display_name": getattr(gw, 'default_name', name), }) return gateways except Exception as e: logger.error(f"Error getting available gateways: {e}") return [] async def get_connected_gateways(self) -> List[Dict[str, Any]]: """获取已连接的网关列表""" if not self._main_engine: return [] try: gateway_names = self._main_engine.get_all_gateway_names() gateways = [] for name in gateway_names: gw = self._main_engine.get_gateway(name) if gw: gateways.append({ "gateway_name": name, "gateway_type": type(gw).__name__.replace("Gateway", "").lower(), "status": "connected" if getattr(gw, 'is_connected', False) else "disconnected", }) return gateways except Exception as e: logger.error(f"Error getting connected gateways: {e}") return [] async def get_gateway_setting(self, gateway_name: str) -> Dict[str, Any]: """获取网关配置模板""" if not self._main_engine: # 返回默认模板(Mock 模式) default_settings = { "CTP": { "用户名": "", "密码": "", "经纪商代码": "", "交易服务器": "", "行情服务器": "", "产品名称": "", "授权编码": "", }, "IB": { "TWS地址": "127.0.0.1:4001", "客户ID": "1", "交易账号": "", }, "OKX": { "API Key": "", "Secret": "", "Passphrase": "", "代理": "", "选项": "", }, "BINANCE": { "API Key": "", "Secret": "", "Proxy Host": "", "Proxy Port": 0, }, } return default_settings.get(gateway_name.upper(), {}) try: return self._main_engine.get_default_setting(gateway_name) or {} except Exception as e: logger.error(f"Error getting gateway setting: {e}") return {} async def connect_gateway( self, gateway_name: str, gateway_type: str, setting: Dict[str, Any] ) -> bool: """连接网关""" if not self._main_engine: logger.error("MainEngine not initialized") return False try: def do_connect(): self._main_engine.connect(setting, gateway_name) await self._loop.run_in_executor(self._executor, do_connect) self._connected_gateways[gateway_name] = gateway_type logger.info(f"Gateway {gateway_name} connecting...") return True except Exception as e: logger.error(f"Error connecting gateway: {e}") return False async def disconnect_gateway(self, gateway_name: str) -> bool: """断开网关""" if not self._main_engine: return False try: def do_disconnect(): gw = self._main_engine.get_gateway(gateway_name) if gw: gw.close() await self._loop.run_in_executor(self._executor, do_disconnect) self._connected_gateways.pop(gateway_name, None) logger.info(f"Gateway {gateway_name} disconnected") return True except Exception as e: logger.error(f"Error disconnecting gateway: {e}") return False async def get_gateway_status(self, gateway_name: str) -> Dict[str, Any]: """获取网关连接状态""" if not self._main_engine: return {"gateway_name": gateway_name, "status": "disconnected"} try: gw = self._main_engine.get_gateway(gateway_name) if gw: return { "gateway_name": gateway_name, "status": "connected" if getattr(gw, 'is_connected', False) else "disconnected", } return {"gateway_name": gateway_name, "status": "not_found"} except Exception as e: logger.error(f"Error getting gateway status: {e}") return {"gateway_name": gateway_name, "status": "error"} # ============================================ # 账户和持仓 # ============================================ async def get_accounts(self) -> List[Dict[str, Any]]: """获取所有账户信息""" if not self._main_engine: # Mock 数据 return [ { "account_id": "mock_account", "balance": 100000.0, "available": 100000.0, "frozen": 0.0, } ] try: def fetch_accounts(): return self._main_engine.get_all_accounts() accounts = await self._loop.run_in_executor(self._executor, fetch_accounts) return [ { "account_id": acc.vt_accountid, "balance": acc.balance, "available": acc.available, "frozen": acc.frozen, } for acc in accounts ] except Exception as e: logger.error(f"Error getting accounts: {e}") return [] async def get_positions(self) -> List[Dict[str, Any]]: """获取所有持仓信息""" if not self._main_engine: return [] try: def fetch_positions(): return self._main_engine.get_all_positions() positions = await self._loop.run_in_executor(self._executor, fetch_positions) return [ { "symbol": pos.symbol, "exchange": pos.exchange.value, "direction": pos.direction.value, "volume": pos.volume, "price": pos.price, "pnl": pos.pnl, "pnl_ratio": 0.0, # 需要计算 "frozen": pos.frozen, } for pos in positions ] except Exception as e: logger.error(f"Error getting positions: {e}") return [] async def get_orders(self) -> List[Dict[str, Any]]: """获取所有委托信息""" if not self._main_engine: return [] try: def fetch_orders(): return self._main_engine.get_all_orders() orders = await self._loop.run_in_executor(self._executor, fetch_orders) return [ { "order_id": order.vt_orderid, "symbol": order.symbol, "exchange": order.exchange.value, "direction": order.direction.value if order.direction else "", "order_type": order.type.value, "volume": order.volume, "price": order.price, "traded": order.traded, "status": order.status.value, "time": order.datetime, } for order in orders ] except Exception as e: logger.error(f"Error getting orders: {e}") return [] async def get_active_orders(self) -> List[Dict[str, Any]]: """获取活动委托信息""" if not self._main_engine: return [] try: def fetch_orders(): return self._main_engine.get_all_active_orders() orders = await self._loop.run_in_executor(self._executor, fetch_orders) return [ { "order_id": order.vt_orderid, "symbol": order.symbol, "exchange": order.exchange.value, "direction": order.direction.value if order.direction else "", "order_type": order.type.value, "volume": order.volume, "price": order.price, "traded": order.traded, "status": order.status.value, "time": order.datetime, } for order in orders ] except Exception as e: logger.error(f"Error getting active orders: {e}") return [] async def get_trades(self) -> List[Dict[str, Any]]: """获取成交信息""" if not self._main_engine: return [] try: def fetch_trades(): return self._main_engine.get_all_trades() trades = await self._loop.run_in_executor(self._executor, fetch_trades) return [ { "trade_id": trade.vt_tradeid, "order_id": trade.vt_orderid, "symbol": trade.symbol, "exchange": trade.exchange.value, "direction": trade.direction.value if trade.direction else "", "volume": trade.volume, "price": trade.price, "time": trade.datetime, } for trade in trades ] except Exception as e: logger.error(f"Error getting trades: {e}") return [] # ============================================ # 交易操作 # ============================================ async def send_order( self, symbol: str, exchange: str, direction: str, order_type: str, volume: float, price: Optional[float] = None, offset: str = "OPEN", gateway_name: Optional[str] = None, reference: Optional[str] = None, ) -> Optional[str]: """发送订单""" if not self._main_engine: logger.error("MainEngine not initialized") return None try: def do_send_order(): req = OrderRequest( symbol=symbol, exchange=Exchange[exchange], direction=Direction[direction], type=OrderType[order_type], volume=volume, price=price or 0.0, offset=Offset[offset] if offset else Offset.NONE, reference=reference or "", ) # 如果没有指定网关,使用第一个已连接的网关 if not gateway_name and self._connected_gateways: gateway_name = list(self._connected_gateways.keys())[0] return self._main_engine.send_order(req, gateway_name or "") order_id = await self._loop.run_in_executor(self._executor, do_send_order) logger.info(f"Order sent: {order_id}") return order_id except Exception as e: logger.error(f"Error sending order: {e}") return None async def cancel_order( self, order_id: str, gateway_name: Optional[str] = None, ) -> bool: """撤销订单""" if not self._main_engine: return False try: def do_cancel_order(): # 解析 order_id (格式: gateway_name.orderid) if "." in order_id: parts = order_id.split(".", 1) req_gw_name = parts[0] local_orderid = parts[1] else: req_gw_name = gateway_name or "" local_orderid = order_id req = CancelRequest( orderid=local_orderid, symbol="", # 撤单时不需要 symbol exchange=Exchange.LOCAL, ) return self._main_engine.cancel_order(req, req_gw_name) await self._loop.run_in_executor(self._executor, do_cancel_order) logger.info(f"Cancel order: {order_id}") return True except Exception as e: logger.error(f"Error cancelling order: {e}") return False # ============================================ # 行情数据 # ============================================ async def get_ticks(self, symbols: Optional[List[str]] = None) -> List[Dict[str, Any]]: """获取 Tick 数据""" if not self._main_engine: return [] try: def fetch_ticks(): return self._main_engine.get_all_ticks() all_ticks = await self._loop.run_in_executor(self._executor, fetch_ticks) return [ { "symbol": tick.symbol, "exchange": tick.exchange.value, "datetime": tick.datetime, "name": tick.name, "last_price": tick.last_price, "bid_price_1": tick.bid_price_1, "ask_price_1": tick.ask_price_1, "bid_volume_1": tick.bid_volume_1, "ask_volume_1": tick.ask_volume_1, "volume": tick.volume, } for tick in all_ticks if symbols is None or tick.vt_symbol in symbols ] except Exception as e: logger.error(f"Error getting ticks: {e}") return [] async def subscribe( self, symbol: str, exchange: str, gateway_name: Optional[str] = None, ) -> bool: """订阅行情""" if not self._main_engine: return False try: def do_subscribe(): req = SubscribeRequest( symbol=symbol, exchange=Exchange[exchange], ) # 如果没有指定网关,使用第一个已连接的网关 if not gateway_name and self._connected_gateways: gateway_name = list(self._connected_gateways.keys())[0] self._main_engine.subscribe(req, gateway_name or "") await self._loop.run_in_executor(self._executor, do_subscribe) logger.info(f"Subscribed to {symbol}.{exchange}") return True except Exception as e: logger.error(f"Error subscribing: {e}") return False async def unsubscribe( self, symbol: str, exchange: str, gateway_name: Optional[str] = None, ) -> bool: """取消订阅行情""" # VeighNa 没有直接取消订阅的接口,实际应用中可以维护订阅列表 logger.info(f"Unsubscribed from {symbol}.{exchange}") return True async def get_contracts(self) -> List[Dict[str, Any]]: """获取合约列表""" if not self._main_engine: return [] try: def fetch_contracts(): return self._main_engine.get_all_contracts() contracts = await self._loop.run_in_executor(self._executor, fetch_contracts) return [ { "symbol": contract.symbol, "exchange": contract.exchange.value, "name": contract.name, "product": contract.product.value, "size": contract.size, "pricetick": contract.pricetick, "min_volume": contract.min_volume, "max_volume": contract.max_volume, "vt_symbol": contract.vt_symbol, "stop_supported": contract.stop_supported, "net_position": contract.net_position, } for contract in contracts ] except Exception as e: logger.error(f"Error getting contracts: {e}") return [] async def get_contract(self, vt_symbol: str) -> Optional[Dict[str, Any]]: """获取单个合约详情""" if not self._main_engine: return None try: def fetch_contract(): return self._main_engine.get_contract(vt_symbol) contract = await self._loop.run_in_executor(self._executor, fetch_contract) if not contract: return None return { "symbol": contract.symbol, "exchange": contract.exchange.value, "name": contract.name, "product": contract.product.value, "size": contract.size, "pricetick": contract.pricetick, "min_volume": contract.min_volume, "max_volume": contract.max_volume, "vt_symbol": contract.vt_symbol, "stop_supported": contract.stop_supported, "net_position": contract.net_position, } except Exception as e: logger.error(f"Error getting contract: {e}") return None # ============================================ # 策略管理 # ============================================ async def get_strategies(self) -> List[Dict[str, Any]]: """获取所有策略信息""" if not self._main_engine: return [] try: strategies = [] # 实际实现需要访问策略引擎 return strategies except Exception as e: logger.error(f"Error getting strategies: {e}") return [] async def init_strategy(self, strategy_name: str) -> bool: """初始化策略""" if not self._main_engine: return False try: logger.info(f"Initializing strategy: {strategy_name}") return True except Exception as e: logger.error(f"Error initializing strategy: {e}") return False async def start_strategy(self, strategy_name: str) -> bool: """启动策略""" if not self._main_engine: return False try: logger.info(f"Starting strategy: {strategy_name}") return True except Exception as e: logger.error(f"Error starting strategy: {e}") return False async def stop_strategy(self, strategy_name: str) -> bool: """停止策略""" if not self._main_engine: return False try: logger.info(f"Stopping strategy: {strategy_name}") return True except Exception as e: logger.error(f"Error stopping strategy: {e}") return False __all__ = ["VeighNaService"]