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

98 lines
3.1 KiB
Python

from __future__ import annotations
import math
import pytest
from app.services import fundamentals_research as research
def test_favorable_percentiles_are_tie_aware():
ranks = research.favorable_percentiles(
{"a": 3, "b": 3, "c": 3, "d": 3, "e": 3},
higher_is_better=True,
)
assert set(ranks.values()) == {50.0}
def test_lower_is_better_flips_the_rank():
ranks = research.favorable_percentiles(
{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5},
higher_is_better=False,
)
assert ranks["a"] == 100.0
assert ranks["e"] == 0.0
def test_invalid_and_thin_cross_sections_stay_null():
ranks = research.favorable_percentiles(
{"a": 1, "b": 2, "c": math.nan, "d": None, "e": 5},
higher_is_better=True,
)
assert all(value is None for value in ranks.values())
def test_composites_use_equal_subgroup_weighting():
features = {
str(index): {
"operating_margin": index,
"fcf_margin": index,
"net_debt_to_ebitda": 6 - index,
"share_count_change_yoy": 6 - index,
"revenue_growth_yoy": index,
"eps_growth_yoy": index,
}
for index in range(1, 6)
}
scores = research.cross_section_scores(features)
assert scores["5"]["quality"] == 100.0
assert scores["5"]["growth"] == 100.0
assert scores["5"]["balanced"] == 100.0
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
with pytest.raises(ValueError, match="weight"):
research.overlay_rank(90, 50, 1.1)