feat: add split-safe fundamentals research protocol

This commit is contained in:
2026-07-23 17:27:28 +02:00
parent 7f944d718f
commit 34d6dda1ab
6 changed files with 273 additions and 64 deletions
+39
View File
@@ -51,6 +51,45 @@ def test_composites_use_equal_subgroup_weighting():
assert scores["3"]["balanced"] == 50.0
def test_split_safe_composites_ignore_eps_and_share_count():
def features(unsafe_multiplier: int):
return {
str(index): {
"operating_margin": index,
"fcf_margin": index,
"net_debt_to_ebitda": 6 - index,
"revenue_growth_yoy": index,
"eps_growth_yoy": unsafe_multiplier * (6 - index),
"share_count_change_yoy": unsafe_multiplier * index,
}
for index in range(1, 6)
}
baseline = research.cross_section_scores(features(1), split_safe=True)
distorted = research.cross_section_scores(features(1_000_000), split_safe=True)
assert distorted == baseline
assert baseline["5"]["quality"] == 100.0
assert baseline["5"]["growth"] == 100.0
assert baseline["5"]["balanced"] == 100.0
assert "eps_growth_yoy" not in baseline["5"]
assert "share_count_change_yoy" not in baseline["5"]
def test_split_safe_quality_still_requires_two_comparable_inputs():
features = {
str(index): {
"operating_margin": index,
"revenue_growth_yoy": index,
}
for index in range(1, 6)
}
scores = research.cross_section_scores(features, split_safe=True)
assert all(row["quality"] is None for row in scores.values())
assert scores["5"]["growth"] == 100.0
assert scores["5"]["balanced"] is None
def test_overlay_uses_neutral_missing_score_and_validates_weight():
assert research.overlay_rank(90, None, 0.2) == 82.0
assert research.overlay_rank(90, 100, 0.2) == 92.0
@@ -29,6 +29,27 @@ def test_arm_matrix_is_bounded_and_pre_registered():
}
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": [