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]
This commit is contained in:
@@ -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 @@ VPS(miniQMT 客户端保持登录,唯一有实时行情的机器)
|
||||
| 期 | 内容 | 验收 |
|
||||
|----|------|------|
|
||||
| P0 | 对账 spike:checkpoint 续跑 vs 全量重放,末持仓/净值一致(含跨除权用例) | ✅ **已通过(2026-08-14,VPS 真数据)**: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#39(all_weather 影子)实跑;ShadowBroker 5 协议方法补齐、bullet_trade 两层实例锁(b27c3b4/e1cafac),四实例齐跑 live3/4/5+shadow39。涨跌停/停牌拒单 61cc01e |
|
||||
| P2 | tick 推送接入(tick 级策略)+ 月线/周线合成 | tick 策略可跑(挂起:等双轨验证稳) |
|
||||
| P3 | 日终对账自动化 + NAS 交叉对照;影子单同步 QMT(真实现盘前置) | 前半 ✅ **双轨对账报表(534a06a,2026-08-15)**:reconcile 四指标+自动配对 live5↔shadow39+API/CLI/supervisor 日终兜底;后半(影子单同步 QMT)挂起 |
|
||||
|
||||
工程分期不影响使用语义:策略侧从第一天就按「声明周期」配置,
|
||||
tick 级在 P2 放开。
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user