mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 16:44:15 +08:00
- #201 warmup 测试改为与全历史计算基准对比数值 (ewm 指标从首值播种, 仅断言非 NaN 无法区分新旧代码) - #224 注入测试改用合法同构 parquet 做目标 (读 /etc/passwd 在未加固 连接下同样报错, 区分不了攻击面是否真实闭合) 两个测试均已在修复前代码 (41205b1/e89ea9b) 上验证会失败。
75 lines
2.7 KiB
Python
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
|