main
py 60 lines 3.45 KB
Raw
1 from datetime import date, datetime, time, timezone
2
3 from app.market_sessions import MarketCalendarException, MarketTradingSchedule, class_due, market_session_status, price_sync_eligible
4 from app.persistence import SqliteResearchPersistence
5
6
7 NSE = [MarketTradingSchedule("NSE", "XNSE", "IN", "Asia/Kolkata", day, time(9, 15), time(15, 30)) for day in range(5)]
8
9
10 def test_nse_session_holiday_and_exception_rules_are_data_driven():
11 assert market_session_status("NSE", NSE, [], datetime(2026, 9, 7, 3, 30, tzinfo=timezone.utc)) == "CLOSED"
12 assert market_session_status("NSE", NSE, [], datetime(2026, 9, 7, 5, 0, tzinfo=timezone.utc)) == "OPEN"
13 assert market_session_status("NSE", NSE, [], datetime(2026, 9, 7, 11, 0, tzinfo=timezone.utc)) == "CLOSED"
14 assert market_session_status("NSE", NSE, [], datetime(2026, 9, 6, 5, 0, tzinfo=timezone.utc)) == "CLOSED"
15 holiday = [MarketCalendarException("NSE", date(2026, 9, 7), "CLOSED")]
16 assert market_session_status("NSE", NSE, holiday, datetime(2026, 9, 7, 5, 0, tzinfo=timezone.utc)) == "HOLIDAY"
17 early = [MarketCalendarException("NSE", date(2026, 9, 7), "EARLY_CLOSE", close_time=time(12, 0))]
18 assert market_session_status("NSE", NSE, early, datetime(2026, 9, 7, 6, 0, tzinfo=timezone.utc)) == "OPEN"
19 assert market_session_status("NSE", NSE, early, datetime(2026, 9, 7, 7, 0, tzinfo=timezone.utc)) == "CLOSED"
20
21
22 def test_unknown_fails_closed_and_dst_iana_zone_is_used():
23 assert market_session_status("MISSING", NSE, [], datetime.now(timezone.utc)) == "UNKNOWN"
24 nyse = [MarketTradingSchedule("NYSE", "XNYS", "US", "America/New_York", day, time(9, 30), time(16)) for day in range(5)]
25 assert market_session_status("NYSE", nyse, [], datetime(2026, 7, 6, 14, 0, tzinfo=timezone.utc)) == "OPEN"
26 assert market_session_status("NYSE", nyse, [], datetime(2026, 1, 6, 14, 0, tzinfo=timezone.utc)) == "CLOSED"
27
28
29 def test_price_eligibility_is_open_only_but_other_class_due_is_independent():
30 now = datetime(2026, 9, 7, 5, tzinfo=timezone.utc)
31 assert price_sync_eligible("OPEN", None, 300, now)
32 assert not price_sync_eligible("OPEN", now, 300, now)
33 assert not price_sync_eligible("CLOSED", now.replace(hour=0), 300, now)
34 assert class_due(now.replace(day=6), 3600, now)
35
36
37 def test_market_schedule_and_exception_time_deserialization_accepts_postgres_time_and_sqlite_strings():
38 class Result:
39 def __init__(self, rows): self.rows = rows
40 def fetchall(self): return self.rows
41 class Connection:
42 def execute(self, sql):
43 if "market_trading_schedules" in sql:
44 return Result([{
45 "market_code": "NSE", "mic": "XNSE", "country_code": "IN", "timezone": "Asia/Kolkata", "trading_day": 0,
46 "regular_open_time": time(9, 15), "regular_close_time": time(15, 30), "enabled": True,
47 }])
48 return Result([{
49 "market_code": "NSE", "trading_date": "2026-09-07", "exception_type": "EARLY_CLOSE",
50 "open_time": "09:15:00", "close_time": time(12, 0), "reason": "fixture",
51 }])
52
53 persistence = SqliteResearchPersistence()
54 persistence._connection = Connection()
55 schedule = persistence.load_market_schedules({"NSE"})[0]
56 exception = persistence.load_market_calendar_exceptions({"NSE"})[0]
57 assert schedule.regular_open_time == time(9, 15)
58 assert schedule.regular_close_time == time(15, 30)
59 assert exception.open_time == time(9, 15)
60 assert exception.close_time == time(12, 0)