93 lines
3.1 KiB
Python
93 lines
3.1 KiB
Python
"""runner_backtest 基准对齐/回撤/扩展指标 纯函数单测(B1)。"""
|
|
import pytest
|
|
|
|
from sanguo_portfolio.runner_backtest import (
|
|
_align_benchmark,
|
|
_compute_extended_metrics,
|
|
_extract_drawdown,
|
|
)
|
|
|
|
|
|
EQ = [
|
|
{"date": "2024-01-01", "equity": 100.0},
|
|
{"date": "2024-01-02", "equity": 110.0},
|
|
{"date": "2024-01-03", "equity": 99.0},
|
|
{"date": "2024-01-04", "equity": 120.0},
|
|
{"date": "2024-01-05", "equity": 90.0},
|
|
]
|
|
BD = [
|
|
{"date": "2024-01-01", "close": 200.0},
|
|
{"date": "2024-01-02", "close": 220.0},
|
|
{"date": "2024-01-03", "close": 210.0},
|
|
{"date": "2024-01-04", "close": 260.0},
|
|
{"date": "2024-01-05", "close": 208.0},
|
|
]
|
|
|
|
|
|
def test_drawdown_series():
|
|
dd = _extract_drawdown(EQ)
|
|
vals = [p["drawdown"] for p in dd]
|
|
assert vals[0] == 0.0 and vals[1] == 0.0 and vals[3] == 0.0
|
|
assert vals[2] == pytest.approx(-10.0)
|
|
assert vals[4] == pytest.approx(-25.0)
|
|
|
|
|
|
def test_align_benchmark_normalizes_and_matches_length():
|
|
bench = _align_benchmark(BD, EQ)
|
|
assert len(bench) == len(EQ)
|
|
assert bench[0]["benchmark"] == pytest.approx(1.0)
|
|
assert bench[4]["benchmark"] == pytest.approx(1.04)
|
|
|
|
|
|
def test_align_benchmark_ffill_missing_dates():
|
|
bench = _align_benchmark(BD[:2], EQ)
|
|
# 后 3 天无基准数据 → 前向填充 1.1
|
|
assert [p["benchmark"] for p in bench] == [1.0, 1.1, 1.1, 1.1, 1.1]
|
|
|
|
|
|
def test_align_benchmark_empty_inputs():
|
|
assert _align_benchmark([], EQ) == []
|
|
assert _align_benchmark(BD, []) == []
|
|
|
|
|
|
def test_extended_metrics_values():
|
|
bench = _align_benchmark(BD, EQ)
|
|
m = _compute_extended_metrics(EQ, bench)
|
|
assert m["benchmark_return"] == pytest.approx(4.0)
|
|
assert m["excess_return"] == pytest.approx(-14.0) # -10% 策略 - +4% 基准
|
|
assert m["beta"] == pytest.approx(1.0874, abs=1e-3)
|
|
assert "annual_volatility" in m
|
|
assert "sortino" in m
|
|
assert "calmar" in m
|
|
|
|
|
|
def test_extended_metrics_short_series():
|
|
assert _compute_extended_metrics([{"date": "d", "equity": 1.0}], []) == {}
|
|
assert _compute_extended_metrics([], []) == {}
|
|
|
|
|
|
def test_holdings_curve_aggregates_daily():
|
|
"""每日持仓聚合:count=非零标的数,value=市值合计;零持仓行剔除。"""
|
|
import pandas as pd
|
|
from sanguo_portfolio.runner_backtest import _extract_holdings_curve
|
|
|
|
df = pd.DataFrame([
|
|
{"date": "2024-01-01", "code": "600000", "amount": 100, "value": 1000.0},
|
|
{"date": "2024-01-01", "code": "000001", "amount": 200, "value": 2000.0},
|
|
{"date": "2024-01-01", "code": "510300", "amount": 0, "value": 0.0}, # 已清仓剔除
|
|
{"date": "2024-01-02", "code": "600000", "amount": 100, "value": 1100.0},
|
|
])
|
|
curve = _extract_holdings_curve(df)
|
|
assert curve == [
|
|
{"date": "2024-01-01", "count": 2, "value": 3000.0},
|
|
{"date": "2024-01-02", "count": 1, "value": 1100.0},
|
|
]
|
|
|
|
|
|
def test_holdings_curve_empty_inputs():
|
|
from sanguo_portfolio.runner_backtest import _extract_holdings_curve
|
|
assert _extract_holdings_curve(None) == []
|
|
import pandas as pd
|
|
assert _extract_holdings_curve(pd.DataFrame()) == []
|
|
assert _extract_holdings_curve(pd.DataFrame({"code": ["600000"]})) == [] # 无 date 列
|