Files
set50-system/backend/tests/test_themes.py
Kunthawat Greethong affc29a3be [verified] Add multi-theme registry + combined scoring engine (60% theme / 40% Siamchart)
- themes.py: 3-theme registry (tourism monthly, auto_credit monthly, refining_energy quarterly) with Thai labels + frequency; curated SET50 symbol->theme exposure map; z-normalized theme scoring and Siamchart fundamental score; 60/40 combined score (multi-theme mean)
- Frequency recorded per theme so consumers don't mix different-cadence factors as same-timestamp
- 8 tests; full suite pass
2026-08-25 15:19:39 +07:00

73 lines
2.8 KiB
Python

"""Tests for the multi-theme registry + scoring."""
from __future__ import annotations
import unittest
from app import themes
class ThemesTest(unittest.TestCase):
def test_list_themes_has_three(self) -> None:
t = themes.list_themes()
self.assertEqual(len(t), 3)
ids = {x.id for x in t}
self.assertEqual(ids, {"tourism", "auto_credit", "refining_energy"})
for x in t:
self.assertTrue(x.label_th) # Thai label present
def test_exposure_mapping_contains_expected(self) -> None:
self.assertIn("AOT", themes.THEME_SYMBOLS["tourism"])
self.assertIn("PTT", themes.THEME_SYMBOLS["refining_energy"])
self.assertIn("TISCO", themes.THEME_SYMBOLS["auto_credit"])
def test_frequency_recorded_per_theme(self) -> None:
self.assertEqual(themes.THEME_FREQUENCY["refining_energy"], "quarterly")
self.assertEqual(themes.THEME_FREQUENCY["tourism"], "monthly")
def test_build_theme_scores_z_normalizes(self) -> None:
signals = [
{"symbol": "A", "score": 10.0},
{"symbol": "B", "score": 5.0},
{"symbol": "C", "score": 0.0},
]
scores = themes.build_theme_scores("tourism", signals)
self.assertAlmostEqual(scores["A"], 1.224, places=2)
self.assertAlmostEqual(scores["B"], 0.0, places=2)
self.assertAlmostEqual(scores["C"], -1.224, places=2)
def test_siamchart_score_uses_growth_and_yield(self) -> None:
factors = {
"factors": [
{"symbol": "X", "eps_growth_yoy": 10.0, "dividend_yield": 2.0},
{"symbol": "Y", "eps_growth_yoy": -5.0, "dividend_yield": 0.0},
]
}
sc = themes.build_siamchart_score(factors)
# X = 10 + 2*2 = 14 ; Y = -5 -> X higher
self.assertGreater(sc["X"], sc["Y"])
def test_combine_60_40(self) -> None:
theme_scores = [{"A": 1.0, "B": -1.0}]
siamchart = {"A": 2.0, "B": 0.0}
merged = themes.combine_score(theme_scores, siamchart)
# A: 0.6*1.0 + 0.4*2.0 = 1.4 ; B: 0.6*(-1) + 0.4*0 = -0.6
self.assertAlmostEqual(merged["A"]["combined"], 1.4, places=5)
self.assertAlmostEqual(merged["B"]["combined"], -0.6, places=5)
self.assertAlmostEqual(merged["A"]["theme_score"], 1.0, places=5)
def test_multiple_themes_average(self) -> None:
# Symbol in two themes -> theme_score is the mean.
t1 = {"A": 1.0}
t2 = {"A": 3.0}
merged = themes.combine_score([t1, t2], {})
self.assertAlmostEqual(merged["A"]["theme_score"], 2.0, places=5)
def test_missing_sym_siamchart_gets_theme_only(self) -> None:
merged = themes.combine_score([{"A": 2.0}], {})
self.assertAlmostEqual(merged["A"]["combined"], 0.6 * 2.0, places=5)
if __name__ == "__main__":
unittest.main()