This commit is contained in:
2026-09-09 15:07:58 +08:00
parent d656c05b3d
commit 71a0f6e404
31 changed files with 3657 additions and 9 deletions

View File

@@ -0,0 +1,60 @@
"""事件回测纯函数:入场/出场索引语义、汇总统计(含空样本与分年)。"""
from __future__ import annotations
from datetime import datetime
import pytest
from app.backtest.events import _entry_exit_indices, _stats_block
from app.schemas import EventBacktestSpec
def _spec(holding_days: int = 5) -> EventBacktestSpec:
return EventBacktestSpec.model_validate({
"holding_days": holding_days,
"entry": {"indicator": []},
})
def test_entry_exit_next_day_and_hold():
spec = _spec(holding_days=5)
assert _entry_exit_indices(10, spec, n=100) == (11, 16) # 次日入,持有 5 日出
def test_entry_exit_out_of_range_none():
spec = _spec(holding_days=5)
# 出场索引越界exit_i >= n
assert _entry_exit_indices(94, spec, n=100) is None
assert _entry_exit_indices(99, spec, n=100) is None
def test_entry_exit_boundary_exact_fit():
spec = _spec(holding_days=5)
# exit_i == n-1 恰好可用
assert _entry_exit_indices(93, spec, n=100) == (94, 99)
def test_stats_block_empty():
s = _stats_block([])
assert s["samples"] == 0 and s["stocks"] == 0
assert s["by_year"] == []
assert s["win_rate"] == 0.0
def test_stats_block_aggregates():
trades = [
{"ts_code": "000001.SZ", "ret_pct": 10.0,
"entry_date": datetime(2024, 1, 5), "entry_price": 10, "exit_price": 11},
{"ts_code": "000001.SZ", "ret_pct": -4.0,
"entry_date": datetime(2024, 3, 6), "entry_price": 10, "exit_price": 9.6},
{"ts_code": "600519.SH", "ret_pct": 2.0,
"entry_date": datetime(2023, 5, 10), "entry_price": 10, "exit_price": 10.2},
]
s = _stats_block(trades)
assert s["samples"] == 3
assert s["stocks"] == 2
assert s["mean_pct"] == pytest.approx((10.0 - 4.0 + 2.0) / 3, abs=1e-3) # 统计块保留 3 位小数
assert s["win_rate"] == round(2 / 3 * 100, 2)
assert [y["year"] for y in s["by_year"]] == [2023, 2024] # 分年升序
assert s["by_year"][1]["samples"] == 2
assert s["max_pct"] == 10.0 and s["min_pct"] == -4.0