434d7cb357
根因三层(全案=audit/20261005_qmt_identity_env_plan.md): schtask 无 per-task env+机器级 env 未设→wrapper 被迫字面量;「部署身份」无单一权威源(env 名分裂三个);模拟号零安全压力致 32 文件 88 处 copy-paste 扩散。 - qmt_gate_common.load_identity 正典装载器(:14-59): env SANGUO_QMT_ACCOUNT/BIGQMT_ACCOUNT_ID(兼容别名)>json>RuntimeError fail-loud;SANGUO_IDENTITY_JSON 显式指路不存在=立即报错;查找=repo 相对>C:\sanguo_vnpy_v2\config\(生产副本回退位) - 灾备腿同源: relogin/qmt_bridge_probe/setclip 改 from qmt_gate_common import ACCOUNT - 桥腿镜像同链: xt_gateway._qmt_account/qmt_gateway_client._identity_default_account/is_price_source_vps/bridge_switch_ops(3 处 env 注入+1 处内嵌探针串)/check_xtquant——env 带默认的硬编码默认值全数退役 - xt_eod_wrapper.ps1 改 Get-Content identity json(保持 ASCII);码内 docstring 字面量→占位符(engine/runner/runner_live/live.yaml 注释/README) - .gitignore 白名单 !config/qmt_identity.json(全局 *.json 曾吞之,git check-ignore 实证) - tests/trader/test_qmt_identity.py 五钉: 装载链语义(env 双名覆盖/显式指路 fail)+单源一致性(identity≡live.yaml≡watch_accounts≡gate_common≡各腿镜像,休市表 CI 同步测先例)+码面字面量 grep-pin(生产码面零 66639661,只 config 三件套可含) - 监控 spec: §3.2 账号来源 bullet/§9.A-18 坑条/§12 行数+行号勘正(插入块致 bridge_ping/calendar_probe/relogin_running 引用行号推移);runbook: 非代码资产两行(副本回退路径+生产 wrapper 待改清单=10-08 窗口后 §五③) 生产 wrapper(C:\sanguo_bigqmt 库外)字面量收尾=10-08 复市验证窗口后非代码资产流程;VPS 班车同窗口(护 10-08 07:50 relogin 首班旧码纯净)。trader+api 620 绿。 Co-Authored-By: Claude Code <noreply@anthropic.com>
155 lines
5.5 KiB
Python
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({"交易账号": "<QMT账号>", "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"]
|