Playbook › Playbook
yfinance fast_info.previous_close is unreliable — it corrupts every computed day change
Claim
.mcp/fin.py --px computed its day-change column as last_price / previous_close - 1 using
yf.Ticker(t).fast_info. For AKAM on 2026-09-26 this printed −14.47%. The truth, from
get_stock_info on the same ticker the same minute:
| field | value |
|---|---|
previousClose |
110.41 |
currentPrice |
113.94 |
regularMarketChangePercent |
+3.20 |
And directly from fast_info on the same call:
| accessor | value |
|---|---|
previous_close |
133.21 ← wrong |
regular_market_previous_close |
110.41 ← correct |
last_price |
113.94 |
fast_info exposes both fields, and only the longer-named one is the prior regular-session
close. previous_close appears to be sourced from chart metadata and can drift to an
unrelated session, most visibly on a name that gapped.
Why it is worth a note
The failure is silent and directional. It does not raise, does not return None, and
produces a plausible-looking percentage, so nothing downstream can detect it. On 2026-09-26 it
did two distinct kinds of damage in one scan:
- It invented an event. AKAM appeared as the worst name in the universe at −14.5%, which read as a CDN/security rout. AKAM had in fact just signed a 7-year, $11.6B agreement with Anthropic and closed up 3.2%.
- It hid a real one. The same sweep reported ZS at −6.4% when the true figure was −10.1% — the largest single-name move on the board, and the trigger of a genuine sector de-rate.
So the error is not noise that averages out. It manufactures false signal and suppresses true signal at the same time, which is the worst possible shape for a number that feeds a movers list.
Blast radius
Anything consuming fin.py --px: /watchlist-scan, /daily-brief, .mcp/alerts.py,
.mcp/news.py. Prices themselves were always correct — last_price is fine. Only the
percent column was affected, so no zone check, fair-value comparison or trim level was ever
wrong because of this. The damage was confined to "what moved today," which is exactly what a
morning brief is for.
The rule
Never compute a day change from fast_info.previous_close. Use
fast_info.regular_market_previous_close, or take regularMarketChangePercent from
get_stock_info when a single name has to be right. Fixed in .mcp/fin.py on 2026-09-26 with
the fallback fi.regular_market_previous_close or fi.previous_close.
The generalisation is the standing rule, not the specific field: a vendor accessor whose name sounds like the number wanted is not the same as the number wanted, and a computed percentage is the easiest place for that substitution to hide. Cross-check one instance against a second endpoint before trusting a whole column. ⧉ [[principle-primary-source-beats-vendor]]
Related
[[pitfall-stale-entry-zone-suppresses-a-name]] — same week, same scan, same shape: a mechanism that quietly removes a name from view rather than reporting something false about it.