fix(tests): Mac本地9个既有测试失败根治——test_cta_engine的sys.modules mock注入从import期挪进module-scoped fixture(pytest全量collection先于执行,import期注入污染后收集的metrics/datareader/factor)+venv310按lock补装empyrical-reloaded==0.5.12;影子柜台设计文档头更新(P1已上线/P3前半已实现) [nas]
CI/CD / test (push) Successful in 15s
CI/CD / nas-deploy (push) Successful in 47s
CI/CD / nas-verify (push) Successful in 23s

This commit is contained in:
2026-08-15 00:02:12 +08:00
parent f9322a7c48
commit b9315ef7e7
2 changed files with 45 additions and 40 deletions
+6 -5
View File
@@ -1,7 +1,8 @@
# 模拟盘影子交易柜台设计 v2(验收第四轮 #4)
> 状态:设计稿 v2(2026-08-14,按用户反馈重写驱动方式)。**P0 对账已通过(2026-08-14
> P1 未动工**
> 状态:设计稿 v2(2026-08-14,按用户反馈重写驱动方式)。**P0 对账已通过(2026-08-14
> P1 影子柜台已上线(2026-08-14 晚,shadow#39 实跑)**;P3 前半(双轨对账报表)已实现(534a06a)
> v1 的「schtask 每 15 分钟定时拉数据」已废弃——用户明确:数据要与实盘完全一样
> v1 的「schtask 每 15 分钟定时拉数据」已废弃——用户明确:数据要与实盘完全一样
> 由 miniQMT 实时提供,步长由策略自己的周期决定(tick 到月线),15 分钟只是举例。
@@ -97,9 +98,9 @@ VPSminiQMT 客户端保持登录,唯一有实时行情的机器)
| 期 | 内容 | 验收 |
|----|------|------|
| P0 | 对账 spikecheckpoint 续跑 vs 全量重放,末持仓/净值一致(含跨除权用例) | ✅ **已通过(2026-08-14VPS 真数据)**all_weather 2024 全年(242 交易日)全量重放 vs 3 段续跑(5月初/9月初分段,跨多次除权),期末 9 只持仓逐只一致、现金 2,256.29/总值 1,335,875.29 相对偏差 <1e-6、净值曲线 <1e-6。脚本 `scripts/shadow_desk/spike_p0_checkpoint_replay.py`runner 通道 `--initial-positions`+`final_portfolio`1e7d696 |
| P1 | 影子柜台常驻进程:bar 级订阅(分钟/日线)+ 实时价撮合 + 虚拟账户 + 落库 | 盘中下单成交可见,前端实时刷新 |
| P2 | tick 推送接入(tick 级策略)+ 月线/周线合成 | tick 策略可跑 |
| P3 | 日终对账自动化 + NAS 交叉对照;影子单同步 QMT(真实现盘前置) | |
| P1 | 影子柜台常驻进程:bar 级订阅(分钟/日线)+ 实时价撮合 + 虚拟账户 + 落库 | **已上线(2026-08-14 晚)**supervisor schtask 拉起 shadow 进程,shadow#39all_weather 影子)实跑;ShadowBroker 5 协议方法补齐、bullet_trade 两层实例锁(b27c3b4/e1cafac),四实例齐跑 live3/4/5+shadow39。涨跌停/停牌拒单 61cc01e |
| P2 | tick 推送接入(tick 级策略)+ 月线/周线合成 | tick 策略可跑(挂起:等双轨验证稳) |
| P3 | 日终对账自动化 + NAS 交叉对照;影子单同步 QMT(真实现盘前置) | 前半 ✅ **双轨对账报表(534a06a2026-08-15**reconcile 四指标+自动配对 live5↔shadow39+API/CLI/supervisor 日终兜底;后半(影子单同步 QMT)挂起 |
工程分期不影响使用语义:策略侧从第一天就按「声明周期」配置,
tick 级在 P2 放开。
+39 -35
View File
@@ -1,14 +1,23 @@
"""Tests for sanguo_backtest.cta_engine module."""
# Mock vnpy and tzlocal modules before importing anything that depends on them
# Mock vnpy and tzlocal modules before importing anything that depends on them
# ⚠️注入必须在 fixture 内(测试期)而非模块 import 期:pytest 全量 collection 先于任何
# 测试执行,import 期注入的 sys.modules mock 会污染后收集的 datareader/metrics/factor
# 模块(它们 import 到的是 MagicMock)——2026-08-15 修,见 tests 全量 9 failed 根因。
import sys
import importlib
import importlib.util
from unittest.mock import MagicMock
import pytest
import json
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime
from pathlib import Path
import pandas as pd
# Only mock these when the real module is NOT importable (e.g. local env missing empyrical).
# In the container (authoritative test env) everything imports fine, so NO mock is installed
# → zero sys.modules pollution leaking into other test modules at collection time.
# (Previous unconditional sys.modules[...]=MagicMock() here broke datareader/spike/factor/metrics
# tests because it ran at collection time and never restored.)
# → zero sys.modules pollution leaking into other test modules.
mock_tzlocal = MagicMock()
mock_tzlocal.get_localzone_name = MagicMock(return_value="UTC")
_MOCK_FACTORIES = {
@@ -18,41 +27,36 @@ _MOCK_FACTORIES = {
"vnpy.trader.object": MagicMock,
"vnpy.trader.database": MagicMock,
"vnpy_ctastrategy.backtesting": MagicMock,
"empyrical": MagicMock,
}
_SAVED_MODULES = {}
for _name, _factory in _MOCK_FACTORIES.items():
try:
_found = importlib.util.find_spec(_name)
except ModuleNotFoundError:
_found = None
if _found is None:
_SAVED_MODULES[_name] = sys.modules.get(_name)
sys.modules[_name] = _factory()
import pytest
import json
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime
from pathlib import Path
import pandas as pd
from sanguo_backtest.cta_engine import run_cta_backtest
@pytest.fixture(scope="module", autouse=True)
def _restore_modules_after():
"""Restore sys.modules after this module's tests, so downstream tests import the
REAL vnpy/empyrical instead of the mocks we installed above. Also drop cta_engine/
metrics from the module cache (they were imported while mocks were active) so they
re-import fresh with real dependencies."""
yield
for cached in ("sanguo_backtest.cta_engine", "sanguo_backtest.metrics", "sanguo_backtest.ashare_engine"):
sys.modules.pop(cached, None)
for k, orig in _SAVED_MODULES.items():
if orig is None:
sys.modules.pop(k, None)
else:
sys.modules[k] = orig
def _mock_deps_and_import_cta_engine():
"""module 级唯一 mock 窗口:装 mock(仅当真模块不可导入)→ import cta_engine →
测试 → teardown 恢复 sys.modules 并清 sanguo_backtest 模块缓存(它们是在 mock
生效期间 import 的,须弹出让后续测试用真依赖重新 import)。"""
saved = {}
for name, factory in _MOCK_FACTORIES.items():
try:
found = importlib.util.find_spec(name)
except ModuleNotFoundError:
found = None
if found is None:
saved[name] = sys.modules.get(name)
sys.modules[name] = factory()
try:
cta_engine = importlib.import_module("sanguo_backtest.cta_engine")
globals()["run_cta_backtest"] = cta_engine.run_cta_backtest
yield
finally:
for cached in ("sanguo_backtest.cta_engine", "sanguo_backtest.metrics", "sanguo_backtest.ashare_engine"):
sys.modules.pop(cached, None)
for k, orig in saved.items():
if orig is None:
sys.modules.pop(k, None)
else:
sys.modules[k] = orig
globals().pop("run_cta_backtest", None)
class TestRunCtaBacktest: