Files
sanguo_vnpy_v2/sanguo_web/services/main_service.py
T
claude_dev 918bbed0fc fix: 修复登录500错误和移除明文密码提示
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service)
- 移除登录页面上的明文密码提示
- 改进前端错误处理,避免数据加载失败导致登录显示错误
2026-07-02 12:23:55 +08:00

716 lines
25 KiB
Python

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