Files
shy3130 91fef6d793 test: 加强 #201/#224 回归测试至可复现级别
- #201 warmup 测试改为与全历史计算基准对比数值 (ewm 指标从首值播种,
  仅断言非 NaN 无法区分新旧代码)
- #224 注入测试改用合法同构 parquet 做目标 (读 /etc/passwd 在未加固
  连接下同样报错, 区分不了攻击面是否真实闭合)

两个测试均已在修复前代码 (41205b1/e89ea9b) 上验证会失败。
2026-09-03 13:26:42 +08:00

75 lines
2.7 KiB
Python

"""#201 回归: 旧信号回测的 _load_panel 必须带指标 warmup 窗口。
直接按 [start,end] 过滤后 compute_all, 区间头部的 MA/MACD/RSI 会因缺
历史窗口而失真 (回测起始段信号不可信)。
"""
from __future__ import annotations
from datetime import date, timedelta
from unittest.mock import MagicMock
import polars as pl
from app.services.backtest import BacktestService
def _synthetic_enriched(n_days: int) -> pl.DataFrame:
base = date(2026, 1, 1)
days = [base + timedelta(days=i) for i in range(n_days)]
n = len(days)
closes = [10.0 + (i % 7) * 0.3 + i * 0.01 for i in range(n)]
return pl.DataFrame(
{
"symbol": ["600000.SH"] * n,
"date": days,
"open": [c - 0.05 for c in closes],
"high": [c + 0.1 for c in closes],
"low": [c - 0.1 for c in closes],
"close": closes,
"volume": [10000.0] * n,
"amount": [c * 10000.0 for c in closes],
"raw_close": closes,
"raw_high": [c + 0.1 for c in closes],
"raw_low": [c - 0.1 for c in closes],
}
)
def test_load_panel_warms_up_indicators(monkeypatch) -> None:
df = _synthetic_enriched(250)
monkeypatch.setattr(
"app.services.backtest.scan_enriched_parquet", lambda glob: df.lazy()
)
svc = BacktestService(repo=MagicMock())
start = df["date"][-30]
end = df["date"][-1]
panel = svc._load_panel(["600000.SH"], start, end)
# warmup 行不进入结果面板 (pandas datetime64 与 date 直接比较会类型不符)
assert str(panel["date"].min())[:10] == start.isoformat()
assert str(panel["date"].max())[:10] == end.isoformat()
# 数值复现: 区间头部的指标必须与「全历史计算后裁剪」的基准一致。
# 只断言非 NaN 区分不了新旧代码 —— ewm 类指标 (RSI/MACD) 从首个值
# 播种, 无 warmup 时首行也有值, 只是数值失真 (#201 的实际危害)
from app.indicators.pipeline import compute_all
reference = compute_all(df).filter(pl.col("date") >= start)
ref_rsi = float(reference["rsi_14"][0])
first = panel.iloc[0]
assert abs(first["rsi_14"] - ref_rsi) < 1.0, (
f"rsi_14 without warmup: {first['rsi_14']} vs reference {ref_rsi}"
)
def test_load_panel_insufficient_history_degrades_gracefully(monkeypatch) -> None:
# 数据起点晚于 warmup 起点时自然退化: 有多少算多少, 不抛异常
df = _synthetic_enriched(20)
monkeypatch.setattr(
"app.services.backtest.scan_enriched_parquet", lambda glob: df.lazy()
)
svc = BacktestService(repo=MagicMock())
panel = svc._load_panel(["600000.SH"], df["date"][0], df["date"][-1])
assert len(panel) == 20