Quick answer
Ask Yahoo Finance's chart endpoint for 1-minute candles on a real, actively-traded stock and a chunk of them come back with open, high, low, close, and volume all null — not zero, not missing from the array, genuinely null, sitting at the correct timestamp. We pulled live 1-minute bars for GSAT (Globalstar, NASDAQ) today and got 170 null closes out of 391 candles — 43.5% — scattered across 168 separate null/non-null transitions through the trading session, not one gap at the open or close. SIRI showed 18 nulls (4.6%) the same day; AAPL showed zero. The daily interval never does this. The 1-minute interval does, and only for names that don't trade every single minute.
The Yahoo Finance Quote Scraper preserves those nulls exactly as the source emits them, rather than zero-filling them into a silently wrong chart.
What actually happens when a minute has no trade 👻
Yahoo's /v8/finance/chart/{symbol} endpoint returns one flat array per OHLCV field, index-aligned to a shared timestamp array. The assumption most people bring to that shape is "every timestamp has a price." It doesn't. If nobody traded the ticker during that specific minute, Yahoo still emits the timestamp — but every field in indicators.quote[0] at that index comes back null.
Here's the live probe, run today against the real endpoint with the same curl-cffi client this Actor ships:
from curl_cffi.requests import AsyncSession
import asyncio, datetime
async def main():
async with AsyncSession() as s:
r = await s.get(
"https://query1.finance.yahoo.com/v8/finance/chart/GSAT",
params={"range": "1d", "interval": "1m"},
impersonate="chrome131", timeout=30,
)
result = r.json()["chart"]["result"][0]
ts, q = result["timestamp"], result["indicators"]["quote"][0]
nulls = [i for i, c in enumerate(q["close"]) if c is None]
print(f"{len(nulls)}/{len(ts)} null closes")
i = nulls[0]
print(datetime.datetime.fromtimestamp(ts[i], tz=datetime.UTC),
"open=", q["open"][i], "volume=", q["volume"][i])
asyncio.run(main())
170/391 null closes
2026-09-11 13:37:00+00:00 open= None volume= None
That's minute 7 of the session — well into normal trading hours, not a pre-market artifact. regularMarketVolume for the day was 347,041 shares, so GSAT absolutely traded — just not in every 60-second bucket Yahoo reports on. Widen the window and the picture holds: 5 days of 1-minute bars on GSAT came back 38.6% null (754 of 1,951 candles). Zoom out to the 1d/interval=1d combination — this Actor's own default — and the nulls disappear entirely: 22 daily candles, zero nulls, because a full trading day almost never passes with zero prints.
The failure mode this causes downstream is boring and immediate: sum(c["close"] for c in candles) / len(candles) on a null-bearing list doesn't give you a wrong average — it raises TypeError: unsupported operand type(s) for +: 'float' and 'NoneType' and kills your script the first time you point it at anything less liquid than a mega-cap.
Why the default input hides this from you
The reason most people never hit this: the Actor's own range/interval defaults are 1mo / 1d, and daily bars don't have the problem — a session either happened or it's absent from the array entirely, it doesn't get partially reported. The null-candle behavior is opt-in risk: it only shows up once you ask for interval: "1m" (or 5m/15m) on a symbol that isn't AAPL-tier liquid. That's exactly the combination a lot of backtesting and screening use cases reach for — sub-daily bars across a watchlist that includes names outside the S&P mega-caps — so it's worth knowing before your pipeline is the one that finds out.
This Actor's own model layer was built anticipating exactly this: Candle.close, .open, .high, .low, and .volume are all float | None / int | None on purpose, and the test suite ships fixtures with "close": [226.01, null] and asserts the null survives the parse untouched — no silent coercion to 0.0, no dropped row. candle_count on each output row counts every index-aligned candle Yahoo returned, nulls included, so a customer scanning for a suspiciously low candle_count on an interval="1m" run should check for nulls-within-candles, not just missing candles.
What we handle for you 🛡️
Every fetch goes out through curl-cffi with real Chrome/Firefox/Safari TLS impersonation, rotated per retry attempt, and we back off exponentially (2s → 30s cap, 5 attempts) on 408/429/5xx, honouring Retry-After when Yahoo sends it. A genuinely delisted symbol comes back as a clean 404 with chart.error populated — we treat that as terminal and don't burn retries on it, but one bad ticker in a batch of a thousand never takes down the other 999: fault isolation is per-symbol, and the run only fails if every requested symbol comes back empty. Apify Proxy routes every request, with RESIDENTIAL selectable if a run needs it. And as shown above, we never invent a 0.0 where Yahoo said null — your dataset carries the same gaps the source carries, typed and index-aligned, so you can decide how to handle them instead of us deciding for you.
Output
{
"symbol": "GSAT",
"currency": "USD",
"exchange_name": "NMS",
"instrument_type": "EQUITY",
"regular_market_price": 82.6,
"requested_range": "1d",
"requested_interval": "1m",
"candle_count": 391,
"candles": [
{ "timestamp": "2026-09-11T13:30:00Z", "open": 82.6, "high": 82.6, "low": 82.6, "close": 82.6, "volume": 11190 },
{ "timestamp": "2026-09-11T13:37:00Z", "open": null, "high": null, "low": null, "close": null, "volume": null }
]
}
from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("DevilScrapes/yahoo-finance-quote-scraper").call(
run_input={"symbols": ["GSAT", "AAPL"], "range": "1d", "interval": "1m"}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
real = [c for c in item["candles"] if c["close"] is not None]
print(item["symbol"], f"{len(real)}/{item['candle_count']} candles with a real print")
Pricing: $0.20 flat per run plus $0.006 per symbol returned — $6.20 per 1,000 symbols. Every new Apify account gets $5 of free credit, no card required to try it.
→ Yahoo Finance Quote Scraper on Apify
FAQ
Does this happen on daily candles too?
Not in what we measured: range=1mo, interval=1d on the same GSAT ticker returned 22 candles, zero nulls. The null-candle pattern only showed up at 1-minute granularity.
Is this a bug in the Actor?
No — it's Yahoo's own data shape. The Actor's Candle model makes every OHLCV field nullable on purpose and its test suite has fixtures encoding exactly this case; we pass the source's nulls through rather than silently zero-filling them.
Which symbols are affected?
Anything that doesn't print a trade every single minute of the session. Mega-caps like AAPL showed zero nulls in our probe; a mid-liquidity name like SIRI showed 4.6%; a lower-liquidity name like GSAT showed 43.5%.
Built by Devil Scrapes. We rotate real browser TLS fingerprints, retry with backoff, and pass through the source's own nulls instead of quietly inventing zeros for you.
Top comments (0)