Yahoo Finance Integration#
Overview#
Provider::YahooFinance is a keyless provider that serves two concepts in Sure:
- Securities — price history and security info via Yahoo's
/v8/finance/chartand/v10/finance/quoteSummaryendpoints. - Exchange Rates — FX pair data via the same chart endpoint using
{FROM}{TO}=Xsymbols.
No API key is required. Authentication is handled by a cookie/crumb mechanism (see below). The provider is registered as yahoo_finance in Provider::Registry and the base URL defaults to https://query1.finance.yahoo.com, overridable via YAHOO_FINANCE_URL .
Cookie/Crumb Authentication#
Yahoo Finance's chart and quoteSummary endpoints require a CSRF-style crumb token alongside a session cookie. The full flow is in fetch_cookie_and_crumb and request_cookie_and_crumb:
- GET
https://fc.yahoo.com— sets a session cookie in the response (even on HTTP 404). The cookie value is extracted before the first;inset-cookie. - GET
/v1/test/getcrumbwith the cookie — returns a plain-text crumb string. - Cookie and crumb are cached under
yahoo_finance_auth_crumb, capped atMAX_CRUMB_CACHE_DURATION(1 hour) or the cookie's ownMax-Age, whichever is shorter .
Stale-crumb retry: Both fetch_authenticated_chart and fetch_security_info inspect the response body for {"code": "Unauthorized"} even when the HTTP status is 200. On detection, they call clear_crumb_cache and retry exactly once with fresh credentials before raising AuthenticationError.
Rate-limit vs. auth disambiguation: If the crumb endpoint returns the string "too many requests", the provider raises RateLimitError rather than AuthenticationError — distinguishing transient throttling from a genuine auth failure .
Price Fetching#
fetch_security_prices converts a date range to Unix timestamps (UTC) and calls fetch_authenticated_chart at /v8/finance/chart/{symbol} with interval=1d and includeAdjustedClose=true. The timestamp and indicators.quote[0].close arrays are zipped; nil close values (weekends, holidays) are skipped .
fetch_security_price (single-date variant) fetches a 10-day trailing window and returns the closest prior date's price.
Currency normalization: Yahoo returns some prices in minor units — normalize_currency_and_price converts GBp → GBP (×0.01) and ZAc → ZAR (×0.01) .
Symbol normalization: normalize_symbol appends exchange suffixes driven by EXCHANGE_CONFIG — e.g., XNSE → .NS, XBOM → .BO, XBOG → .CL.
Results are cached for 5 minutes (CACHE_DURATION) .
Rate Limiting & Request Throttling#
- Client-side throttle:
throttle_requestenforces a minimum gap between consecutive requests (default 0.5 s, viaYAHOO_FINANCE_MIN_REQUEST_INTERVAL). - HTTP 429 retry: Faraday retries up to
YAHOO_FINANCE_MAX_RETRIES(default 5) times with exponential backoff and 50% jitter on 429 responses . Interval starts atYAHOO_FINANCE_RETRY_INTERVAL(default 1.0 s). - User-agent rotation: Requests cycle through five modern browser UA strings (Chrome 145, Firefox 148, Safari 26, Edge 145) to reduce bot detection , following the approach from yfinance PR #2277.
Health Check System#
health_status returns :healthy, :rate_limited, :unavailable, or :unknown :
- Async: When the cached status is stale,
health_statusenqueuesYahooFinanceHealthCheckJobrather than blocking the caller. The job callsrefresh_health_status. - Distributed lock: A 15-second lock prevents thundering-herd health re-checks .
- Probe:
perform_health_checkfetches a cookie/crumb, then GETs/v8/finance/chart/AAPLwithrange=1d. HTTP 429 →:rate_limited;Unauthorizedbody or non-2xx →:unavailable; valid chart result →:healthy.
Status transitions are written to DebugLogEntry (category provider_health) . Freshness thresholds: healthy = 15 min, rate_limited = 30 min, unavailable = 5 min; all assessments are retained for 1 hour.
Error Types#
All errors are subclasses of Provider::YahooFinance::Error < Provider::Error :
| Exception | When raised |
|---|---|
AuthenticationError | Cookie/crumb fetch failure, or Unauthorized persists after crumb refresh |
RateLimitError | HTTP 429 or crumb body equals "too many requests" |
InvalidSymbolError | Symbol not recognized by Yahoo |
MarketClosedError | Market closed at the requested time |
InvalidSecurityPriceError | Price data is invalid or unparseable |
When AuthenticationError or RateLimitError propagates through the health check, affected securities go offline: true with offline_reason: "health_check_failed" . To debug manually, use:
Provider::Registry.get_provider(:yahoo_finance).fetch_security_price(
symbol: "NVDA", exchange_operating_mic: "XNAS", date: Date.current
)
Key Source Files#
| File | Purpose |
|---|---|
app/models/provider/yahoo_finance.rb | Main provider implementation |
app/jobs/yahoo_finance_health_check_job.rb | Async health check job |
app/models/provider/registry.rb | Provider registration |
test/models/provider/yahoo_finance_test.rb | Unit tests (951 lines) |
Docker networking: web and worker containers use 8.8.8.8/1.1.1.1 DNS to avoid IPv6-first resolution failures against Yahoo Finance endpoints. If TCP errors persist, add extra_hosts entries for specific Yahoo IP addresses. See the Docker Self-Hosting guide for details .