59 lines
1.7 KiB
Python
59 lines
1.7 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_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)
|