from datetime import datetime, timezone from app.services.macro.fetchers import ( _drop_in_progress_daily_klines, _latest_closed_daily_point, _parse_farside_latest_total, ) from app.services.bottom_indicators import ahr999 as scanner_ahr999 from app.services.macro import fetchers def test_drop_in_progress_daily_klines_removes_today_open_bar(): now = datetime(2026, 5, 25, 8, 0, tzinfo=timezone.utc) rows = [ [1779494400000, 0, 0, 0, "76715.20"], [1779580800000, 0, 0, 0, "77030.30"], [1779667200000, 0, 0, 0, "77404.80"], # 2026-05-25 00:00 UTC, still open ] filtered = _drop_in_progress_daily_klines(rows, now=now) assert [row[0] for row in filtered] == [1779494400000, 1779580800000] def test_latest_closed_daily_point_skips_today_point(): now = datetime(2026, 5, 25, 8, 0, tzinfo=timezone.utc) rows = [ {"timestamp": 1779494400000, "sumOpenInterestValue": "1"}, {"timestamp": 1779580800000, "sumOpenInterestValue": "2"}, {"timestamp": 1779667200000, "sumOpenInterestValue": "3"}, ] latest = _latest_closed_daily_point(rows, now=now) assert latest == rows[1] def test_parse_farside_latest_total_uses_newest_date_not_first_row(): html = """
11 Jan 20240.0655.3
24 May 20260.0(12.5)
25 May 20260.0321.0
""" parsed = _parse_farside_latest_total(html) assert parsed["value"] == 321_000_000.0 assert parsed["raw"]["date"] == "25 May 2026" def test_macro_ahr999_uses_same_formula_as_scanner(monkeypatch): # Build 300 daily bars with realistic OPEN timestamps so the fetcher's # in-progress-day drop (_drop_in_progress_daily_klines) actually fires on # the last bar (today's 00:00 UTC). With fake integer timestamps the drop # is a no-op, so the fetcher would compute AHR999 over one MORE bar than # the scanner below — producing a spurious 4th-decimal mismatch (the # fetcher and scanner share the exact same ahr999() function, so identical # inputs must yield identical output). now = datetime.now(timezone.utc) midnight_ms = int( now.replace(hour=0, minute=0, second=0, microsecond=0).timestamp() * 1000 ) day_ms = 86_400_000 # Row i opens at (299 - i) days before today's midnight; the last row # (i=299) opens at today's midnight → still in progress → dropped. closes = [ [midnight_ms - (299 - i) * day_ms, 0, 0, 0, str(50_000 + i)] for i in range(300) ] class _Resp: def raise_for_status(self): return None def json(self): return closes class _Client: async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): return None async def get(self, *_args, **_kwargs): return _Resp() monkeypatch.setattr(fetchers.httpx, "AsyncClient", lambda *a, **kw: _Client()) result = __import__("asyncio").run(fetchers.fetch_ahr999()) expected = scanner_ahr999([float(r[4]) for r in closes[:-1]]) assert result["value"] == round(expected or 0, 4)