import json from pathlib import Path import httpx import pytest from app.bars.models import Timeframe from app.market.yahoo import MAX_1M_WINDOW_SECONDS, YahooSource, parse_chart FIXTURE = Path(__file__).parent / "fixtures" / "yahoo_es_1h.json" def test_parser_filters_null_ohlc_rows(): payload = json.loads(FIXTURE.read_text()) raw_count = len(payload["chart"]["result"][0]["timestamp"]) bars = parse_chart(payload, Timeframe.H1) assert len(bars) == raw_count - 1 assert all(None not in (bar.o, bar.h, bar.l, bar.c) for bar in bars) assert all(bar.source == "yahoo" and bar.symbol == "ES=F" for bar in bars) @pytest.mark.asyncio async def test_one_minute_history_is_windowed_and_deduplicated(): payload = json.loads(FIXTURE.read_text()) calls = [] async def handler(request: httpx.Request): calls.append(request) return httpx.Response(200, json=payload) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: source = YahooSource(client=client) bars = await source.history( "ES=F", Timeframe.M1, 0, MAX_1M_WINDOW_SECONDS * 2 + 1 ) assert len(calls) == 3 assert len(bars) == len({bar.t for bar in parse_chart(payload, Timeframe.M1)}) @pytest.mark.asyncio async def test_thirty_minute_history_uses_yahoos_native_interval(): payload = json.loads(FIXTURE.read_text()) calls = [] async def handler(request: httpx.Request): calls.append(request) return httpx.Response(200, json=payload) async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as client: bars = await YahooSource(client=client).history( "ES=F", Timeframe.M30, range_="60d" ) assert len(calls) == 1 assert calls[0].url.params["interval"] == "30m" assert calls[0].url.params["range"] == "60d" assert bars and all(bar.tf is Timeframe.M30 for bar in bars) def test_the_forming_candle_is_bucketed_not_stamped_at_request_time(): # Yahoo stamps the in-progress candle with the moment of the request. Taken # verbatim, every poll appended a new "1m" bar seconds after the last — # 04:38:11, 04:38:50, 04:39:15 — instead of revising the current minute. from app.bars.models import Timeframe from app.market.yahoo import parse_chart payload = { "chart": {"result": [{ "timestamp": [1786500000, 1786500060, 1786500071], # last is mid-minute "indicators": {"quote": [{ "open": [1, 2, 3], "high": [1, 2, 3], "low": [1, 2, 3], "close": [1, 2, 3], "volume": [1, 1, 1], }]}, "meta": {"symbol": "ES=F"}, }]} } times = [bar.t for bar in parse_chart(payload, Timeframe.M1)] assert all(t % 60 == 0 for t in times), f"unaligned bar times: {times}" assert times[-1] == 1786500060, "the forming candle did not fold onto its own minute"