feat: add fundamentals weighting backtest research
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import zipfile
|
||||
|
||||
from scripts import run_fundamentals_research as runner
|
||||
|
||||
|
||||
def _window(name, sharpe, drawdown):
|
||||
return {
|
||||
"window": name,
|
||||
"sharpe": sharpe,
|
||||
"sharpe_se": 0.2,
|
||||
"dsr": 0.8,
|
||||
"cagr_pct": 10.0,
|
||||
"max_drawdown_pct": drawdown,
|
||||
"calmar": 1.0,
|
||||
"trades": 20,
|
||||
}
|
||||
|
||||
|
||||
def test_arm_matrix_is_bounded_and_pre_registered():
|
||||
assert runner.N_TRIALS == 13
|
||||
assert runner.ARMS[0]["id"] == "control_w00"
|
||||
assert {arm["weight"] for arm in runner.ARMS[1:]} == {0.1, 0.2, 0.3, 0.4}
|
||||
assert {arm["composite"] for arm in runner.ARMS[1:]} == {
|
||||
"quality",
|
||||
"growth",
|
||||
"balanced",
|
||||
}
|
||||
|
||||
|
||||
def test_development_grade_does_not_read_test_window():
|
||||
control = {
|
||||
"windows": [
|
||||
_window("train", 1.0, 10.0),
|
||||
_window("validation", 1.0, 10.0),
|
||||
_window("test", 9.0, 1.0),
|
||||
]
|
||||
}
|
||||
arm = {
|
||||
"windows": [
|
||||
_window("train", 1.1, 10.0),
|
||||
_window("validation", 1.2, 11.0),
|
||||
_window("test", -9.0, 90.0),
|
||||
]
|
||||
}
|
||||
assert runner._development_grade(control, arm)["pass"] is True
|
||||
|
||||
|
||||
def test_output_bundle_is_self_contained(tmp_path):
|
||||
report = {
|
||||
"generated_at": "2026-07-23T00:00:00Z",
|
||||
"splits": {"train_end": "2024-01-01", "test_start": "2025-01-01"},
|
||||
"n_trials": 13,
|
||||
"warnings": ["survivorship bias"],
|
||||
"factor_ic": {"full": []},
|
||||
"arms": [],
|
||||
"development_selection": None,
|
||||
"final_check": None,
|
||||
}
|
||||
output = tmp_path / "result.json"
|
||||
runner._write_outputs(report, output, bundle=True)
|
||||
with zipfile.ZipFile(output.with_suffix(".zip")) as archive:
|
||||
names = set(archive.namelist())
|
||||
assert {
|
||||
"result.json",
|
||||
"result.md",
|
||||
"result-arms.csv",
|
||||
"result-factor-ic.csv",
|
||||
"result-trades.csv",
|
||||
} <= names
|
||||
|
||||
|
||||
def test_winner_concentration_exposes_top_five_dependence():
|
||||
details = [
|
||||
{"pnl": value, "r": value / 10} for value in (100, 90, 80, 70, 60, -10, -20)
|
||||
]
|
||||
result = runner._winner_concentration(details)
|
||||
assert result["top5_pnl"] == 400
|
||||
assert result["net_pnl_ex_top5"] == -30
|
||||
assert result["avg_r_ex_top5"] == -1.5
|
||||
Reference in New Issue
Block a user