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 2024 | 0.0 | 655.3 |
| 24 May 2026 | 0.0 | (12.5) |
| 25 May 2026 | 0.0 | 321.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)