Files
sanguo_vnpy_v2/sanguo_live/engine.py
T
claude_dev 96b1924fd5 feat: 实盘模拟(live) + 组合回测MVP(portfolio)
[live] 实盘模拟 vnpy+miniQMT 直连(supervisor 轮询, 前后端):
- sanguo_live: LiveTradingEngine + AShareCtaTemplate(定寸/禁做空) + runner_supervisor(DB驱动) + persistence(4表WAL)
- sanguo_api/routes_live: 9路由(create/start/stop/positions/trades/account/status)
- frontend live: New/List/Monitor + api/live.ts; config/live.yaml

[portfolio] 组合回测 MVP(BulletTrade, 链路代码完成待验证):
- runner_backtest 加 JSON 入口(--json, BacktestEngine 顶层 import)
- sanguo_api/routes_portfolio: POST /portfolio/backtest SSH 触发 VPS 跑
- frontend PortfolioBacktest.vue + api/portfolio.ts: 表单+结果+净值曲线
- 路由/菜单注册(/backtest/portfolio 组合回测)
- 已知: MVP 链路未端到端验证, agent 改至中途被停; 待 Mac 起服务联调
2026-07-18 20:04:16 +08:00

155 lines
5.5 KiB
Python

"""LiveTradingEngine:vnpy 原生实盘链路封装。
参考 ``vnpy_v4.4.0/examples/no_ui/run.py`` 和 ``examples/no_ui/run.py``,
按 A 股 + QMT 场景精简:
- EventEngine + MainEngine
- add_gateway(QmtGateway) 连 miniQMT
- add_app(CtaStrategyApp) 挂 CTA 引擎
- 透出 connect / add_strategy / subscribe / init_all / start_all / stop_all / 查询
本机未装 vnpy_ctastrategy / vnpy_qmt 时 import 容错,实例化才报错。
"""
from __future__ import annotations
import logging
from typing import Any
logger = logging.getLogger(__name__)
try:
from vnpy.event import EventEngine # type: ignore
from vnpy.trader.engine import MainEngine # type: ignore
from vnpy_qmt import QmtGateway # type: ignore
from vnpy_ctastrategy import CtaStrategyApp, CtaEngine # type: ignore
_DEPS_OK = True
_IMPORT_ERROR: Exception | None = None
except ImportError as _e:
EventEngine = None # type: ignore
MainEngine = None # type: ignore
QmtGateway = None # type: ignore
CtaStrategyApp = None # type: ignore
CtaEngine = None # type: ignore
_DEPS_OK = False
_IMPORT_ERROR = _e
class LiveTradingEngine:
"""vnpy 原生实盘引擎封装。单实例持有 MainEngine 生命周期。
使用:
eng = LiveTradingEngine()
eng.connect({"交易账号": "66639661", "mini路径": "C:\\\\..."})
eng.add_strategy(AShareDoubleMaStrategy, "dm1", "600000.SSE", {...})
eng.subscribe(["600000.SSE"])
eng.init_all(); eng.start_all()
# ... 常驻 ...
eng.stop_all(); eng.close()
"""
def __init__(self) -> None:
if not _DEPS_OK:
raise RuntimeError(
f"vnpy 依赖缺失(vnpy/vnpy_qmt/vnpy_ctastrategy),"
f"无法初始化 LiveTradingEngine: {_IMPORT_ERROR}"
)
self.event_engine: EventEngine = EventEngine()
self.main_engine: MainEngine = MainEngine(self.event_engine)
self.main_engine.add_gateway(QmtGateway)
self.cta_engine: CtaEngine = self.main_engine.add_app(CtaStrategyApp)
logger.info("LiveTradingEngine 初始化完成(MainEngine + QMT + CTA)")
# ---------------------- 连接 / 策略 / 行情 ----------------------
def connect(self, setting: dict[str, str]) -> None:
"""连接 miniQMT。``setting`` = ``{"交易账号":..., "mini路径":...}``。"""
self.main_engine.connect(setting, "QMT")
logger.info("已请求连接 QMT: 账号=%s", setting.get("交易账号"))
def add_strategy(
self,
strategy_class: type,
strategy_name: str,
vt_symbol: str,
setting: dict[str, Any],
) -> None:
"""注册策略实例到 CTA 引擎。"""
self.cta_engine.add_strategy(
strategy_class, strategy_name, vt_symbol, setting
)
logger.info("已添加策略 %s @ %s (class=%s)", strategy_name, vt_symbol,
strategy_class.__name__)
def subscribe(self, vt_symbols: list[str]) -> None:
"""订阅 ``vt_symbol`` 列表(格式 ``SYMBOL.EXCHANGE``,如 ``600000.SSE``)。
订阅是行情驱动策略的前提:QmtGateway.on_tick → EVENT_TICK →
CtaEngine → strategy.on_tick → BarGenerator 合成 15min bar。
"""
from vnpy.trader.object import SubscribeRequest # type: ignore
from vnpy.trader.constant import Exchange # type: ignore
ok = 0
for vt_symbol in vt_symbols:
try:
symbol, exch_code = vt_symbol.split(".", 1)
exchange = Exchange(exch_code)
except (ValueError, KeyError):
logger.warning("vt_symbol 无法解析,跳过: %r", vt_symbol)
continue
req = SubscribeRequest(symbol=symbol, exchange=exchange)
self.main_engine.subscribe(req, "QMT")
ok += 1
logger.info("已请求订阅 %d / %d 个标的", ok, len(vt_symbols))
# ---------------------- 生命周期 ----------------------
def init_all(self) -> None:
self.cta_engine.init_all_strategies()
logger.info("所有策略初始化完成")
def start_all(self) -> None:
self.cta_engine.start_all_strategies()
logger.info("所有策略已启动")
def stop_all(self) -> None:
try:
self.cta_engine.stop_all_strategies()
logger.info("所有策略已停止")
except Exception as e: # noqa: BLE001
logger.warning("stop_all_strategies 异常: %s", e)
def close(self) -> None:
try:
self.main_engine.close()
finally:
logger.info("LiveTradingEngine 已关闭")
# ---------------------- 查询(OMS 缓存) ----------------------
def _oms(self) -> Any:
return self.main_engine.get_engine("oms")
def get_all_accounts(self) -> list:
"""返回 QMT 网关所有账户(AccountData 列表)。连接前可能为空。"""
oms = self._oms()
if oms is None:
return []
return [a for a in oms.get_all_accounts() if a.gateway_name == "QMT"]
def get_positions(self) -> list:
"""返回 QMT 持仓(PositionData 列表)。"""
oms = self._oms()
if oms is None:
return []
return [p for p in oms.get_all_positions() if p.gateway_name == "QMT"]
def get_orders(self) -> list:
"""返回 QMT 当日委托(OrderData 列表)。"""
oms = self._oms()
if oms is None:
return []
return [o for o in oms.get_all_orders() if o.gateway_name == "QMT"]
__all__ = ["LiveTradingEngine"]