{"record":{"id":"00c1b0363cf18141","repo":"we-promise/sure","slug":"failed-to-fetch-security-prices-prices-response","errorCode":null,"errorMessage":"Failed to fetch security prices: #{prices_response.error.message}","messagePattern":"Failed to fetch security prices: #(.+?)","errorType":"exception","errorClass":"Provider::YahooFinance::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/yahoo_finance.rb","lineNumber":305,"sourceCode":"  def fetch_security_price(symbol:, exchange_operating_mic: nil, date:)\n    with_provider_response do\n      symbol = normalize_symbol(symbol, exchange_operating_mic)\n      cache_key = \"security_price_#{symbol}_#{exchange_operating_mic}_#{date}\"\n      if cached_result = get_cached_result(cache_key)\n        cached_result\n      else\n        # For a single date, we'll fetch a range and find the closest match\n        end_date = date\n        start_date = date - 10.days # Extended range for better coverage\n\n        prices_response = fetch_security_prices(\n          symbol: symbol,\n          exchange_operating_mic: exchange_operating_mic,\n          start_date: start_date,\n          end_date: end_date\n        )\n\n        raise Error, \"Failed to fetch security prices: #{prices_response.error.message}\" unless prices_response.success?\n\n        prices = prices_response.data\n        if prices.length == 1\n          target_price = prices.first\n        else\n          # Find the exact date or the closest previous date\n          target_price = prices.find { |p| p.date == date } ||\n                        prices.select { |p| p.date <= date }.max_by(&:date)\n\n          raise Error, \"No price found for #{symbol} on or before #{date}\" unless target_price\n        end\n\n        cache_result(cache_key, target_price)\n        target_price\n      end\n    end\n  end\n","sourceCodeStart":287,"sourceCodeEnd":323,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/yahoo_finance.rb#L287-L323","documentation":"Provider::YahooFinance::Error raised inside fetch_security_price when the delegated fetch_security_prices call (a 10-day window ending at the target date) returned a failed ProviderResponse. The message wraps the inner error — the real cause is whatever broke the range fetch (rate limit, missing chart data, invalid JSON from Yahoo).","triggerScenarios":"fetch_security_price(symbol:, date:) on a cache miss delegates to fetch_security_prices for a 10-day chart window; that inner call hit Yahoo rate limiting, returned no chart data for the symbol, or returned non-JSON — and this wrapper propagates its message.","commonSituations":"Bulk price backfills tripping Yahoo's unofficial rate limits; obscure tickers Yahoo doesn't chart; Yahoo endpoint changes breaking parsing; datacenter IP blocks.","solutions":["Read the wrapped message to find the root cause and fix that ('No chart data', 'Invalid response format', rate-limit text)","For rate-limit causes, back off (health_status tracks rate_limited for 30 min) and retry the batch later with pacing","For missing-chart causes, verify the symbol on Yahoo and re-fetch via search_securities to get the canonical form","Cache single-date prices (already 5-min cached) and reduce redundant calls during syncs"],"exampleFix":"// before\nprice = provider.fetch_security_price(symbol: \"ASML.AS\", exchange_operating_mic: \"XAMS\", date: d)\n\n// after\nbegin\n  price = provider.fetch_security_price(symbol: \"ASML.AS\", exchange_operating_mic: \"XAMS\", date: d)\nrescue Provider::YahooFinance::Error => e\n  if provider.health_status == :rate_limited\n    RetryablePriceSyncJob.perform_later(wait: 10.minutes)\n  else\n    price = fallback_provider.fetch_security_price(symbol: \"ASML.AS\", exchange_operating_mic: \"XAMS\", date: d)\n  end\nend","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"begin\n  provider.fetch_security_price(symbol:, exchange_operating_mic:, date:)\nrescue Provider::YahooFinance::Error => e\n  if provider.health_status == :rate_limited\n    RetryablePriceJob.perform_later(wait: 10.minutes)\n  else\n    fallback_provider.fetch_security_price(symbol:, exchange_operating_mic:, date:)\n  end\nend","preventionTips":["Batch price backfills with pacing instead of tight loops","Consult health_status before large backfills; skip while rate_limited","Cache per-symbol-per-date prices so retries and re-syncs don't multiply calls","Keep a second price provider for symbols Yahoo doesn't chart"],"tags":["ruby","yahoo-finance","price-history","wrapped-error","api"],"backgroundTag":"provider-request-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}