Files
signal-platform/tests/unit/test_fundamentals_research_runner.py
T

103 lines
2.8 KiB
Python

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_split_safe_matrix_is_smaller_and_trial_corrected():
assert runner.SPLIT_SAFE_N_TRIALS == 10
assert runner.SPLIT_SAFE_ARMS[0]["id"] == "control_w00"
assert {arm["weight"] for arm in runner.SPLIT_SAFE_ARMS[1:]} == {
0.05,
0.10,
0.15,
}
assert {arm["composite"] for arm in runner.SPLIT_SAFE_ARMS[1:]} == {
"quality",
"growth",
"balanced",
}
def test_split_safe_output_has_a_distinct_name():
assert runner._default_out(runner.SPLIT_SAFE_PROTOCOL).name.startswith(
"fundamentals-splitsafe-"
)
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