918bbed0fc
- 修复 deps.py 中 get_vn_service 的引用错误 (vn_service.vn_service -> vn_service) - 移除登录页面上的明文密码提示 - 改进前端错误处理,避免数据加载失败导致登录显示错误
716 lines
25 KiB
Python
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"]
|