main
py 67 lines 3.23 KB
Raw
1 from datetime import datetime, timedelta, timezone
2 from decimal import Decimal
3 from types import SimpleNamespace
4
5 import pytest
6
7 from app.sector_performance import belongs_to_region, performance_window, rank_performers, rank_top_gainers
8
9
10 def observation(days_ago, price):
11 return SimpleNamespace(observed_at=datetime(2026, 9, 7, tzinfo=timezone.utc) - timedelta(days=days_ago), price=Decimal(str(price)))
12
13
14 @pytest.mark.parametrize("period,reference_days", [("DAY", 1), ("WEEK", 7), ("MONTH", 30), ("YEAR", 365)])
15 def test_performance_uses_nearest_valid_prior_trading_observation(period, reference_days):
16 latest, reference, pct = performance_window([observation(reference_days + 1, 100), observation(reference_days, 110), observation(0, 121)], period)
17 assert latest.price == Decimal("121")
18 assert reference.price == Decimal("110")
19 assert pct == Decimal("10")
20
21
22 def test_missing_or_non_positive_reference_is_ineligible():
23 assert performance_window([observation(0, 120)], "WEEK") is None
24 assert performance_window([observation(8, 0), observation(0, 120)], "WEEK") is None
25
26
27 def test_day_uses_previous_observed_trading_close_not_calendar_subtraction():
28 # Monday close must compare with Friday's observation; no Sunday quote is invented.
29 monday = datetime(2026, 9, 7, tzinfo=timezone.utc)
30 friday = monday - timedelta(days=3)
31 latest, reference, pct = performance_window([
32 SimpleNamespace(observed_at=friday, price=Decimal("100")),
33 SimpleNamespace(observed_at=monday, price=Decimal("105")),
34 ], "DAY")
35 assert reference.observed_at == friday
36 assert pct == Decimal("5")
37
38
39 def test_top_gainers_are_deduplicated_sorted_and_backend_limited():
40 rows = [
41 {"globalInstrumentId": f"id-{index}", "ticker": f"T{index}", "performancePct": Decimal(10 - index)}
42 for index in range(7)
43 ] + [{"globalInstrumentId": "id-0", "ticker": "ZZZ", "performancePct": Decimal("1")}]
44 result = rank_top_gainers(rows, 99)
45 assert [row["globalInstrumentId"] for row in result] == ["id-0", "id-1", "id-2", "id-3", "id-4"]
46
47
48 def test_equal_performance_has_stable_ticker_then_identity_order():
49 result = rank_top_gainers([
50 {"globalInstrumentId": "b", "ticker": "BBB", "performancePct": Decimal("4")},
51 {"globalInstrumentId": "a", "ticker": "AAA", "performancePct": Decimal("4")},
52 ])
53 assert [row["globalInstrumentId"] for row in result] == ["a", "b"]
54
55
56 def test_best_and_worst_are_independently_limited_and_deterministic():
57 rows = [{"globalInstrumentId": f"id-{index}", "ticker": f"T{index}", "performancePct": Decimal(index - 3)} for index in range(7)]
58 best, worst = rank_performers(rows, 5)
59 assert [row["performancePct"] for row in best] == [Decimal("3"), Decimal("2"), Decimal("1"), Decimal("0"), Decimal("-1")]
60 assert [row["performancePct"] for row in worst] == [Decimal("-3"), Decimal("-2"), Decimal("-1"), Decimal("0"), Decimal("1")]
61
62
63 def test_region_membership_is_provider_neutral():
64 assert belongs_to_region({"country": "IN", "exchange": "XNSE"}, "INDIA")
65 assert belongs_to_region({"country": "US", "exchange": "XNAS"}, "USA")
66 assert belongs_to_region({"country": "DE", "exchange": "XETR"}, "EUROPE")
67 assert not belongs_to_region({"country": "US", "exchange": "XNAS"}, "EUROPE")