{"record":{"id":"61191739eb2ab8c7","repo":"we-promise/sure","slug":"could-not-determine-currency-for-symbol-from-ti","errorCode":null,"errorMessage":"Could not determine currency for #{symbol} from Tiingo search","messagePattern":"Could not determine currency for #(.+?) from Tiingo search","errorType":"exception","errorClass":"Provider::Tiingo::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/tiingo.rb","lineNumber":300,"sourceCode":"\n      response = client.get(\"#{base_url}/tiingo/utilities/search\") do |req|\n        req.params[\"query\"] = symbol\n      end\n\n      parsed = JSON.parse(response.body)\n      check_api_error!(parsed)\n\n      if parsed.is_a?(Array)\n        match = best_match_for_ticker(parsed, symbol)\n        currency = currency_for_country(match&.dig(\"countryCode\"))\n\n        if currency.present?\n          Rails.cache.write(\"tiingo:currency:#{symbol.upcase}\", currency, expires_in: 24.hours)\n          return currency\n        end\n      end\n\n      raise Error, \"Could not determine currency for #{symbol} from Tiingo search\"\n    end\n\n    def map_exchange_to_mic(exchange_name)\n      return nil if exchange_name.blank?\n      TIINGO_EXCHANGE_TO_MIC[exchange_name.strip] || exchange_name.strip\n    end\n\n    # Tiingo's search/utilities response never includes a priceCurrency field\n    # (confirmed against the live API), only countryCode. Resolve the currency\n    # via the countries gem's ISO 4217 data (already used for country\n    # resolution in Provider::TwelveData) instead of hand-maintaining a\n    # per-provider allowlist.\n    def currency_for_country(country_code)\n      return nil if country_code.blank?\n      ISO3166::Country.new(country_code.strip)&.currency_code\n    end\n\n    # Tiingo's search endpoint can return multiple entries sharing the exact","sourceCodeStart":282,"sourceCodeEnd":318,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/tiingo.rb#L282-L318","documentation":"fetch_currency_for_symbol is the fallback when the 'tiingo:currency:<SYMBOL>' cache is cold: it re-queries the search endpoint and resolves a currency via best_match_for_ticker -> currency_for_country(countryCode) using ISO 4217 data. It deliberately raises (rather than defaulting) when it cannot determine a currency, to avoid silently mislabeling prices. The raise means either search returned no usable array/match, or the match's countryCode is missing/mapped to no currency.","triggerScenarios":"fetch_security_prices for a symbol whose currency cache entry expired (24h TTL) and whose search results have no countryCode, an unrecognized country code, or zero matches. Non-country instruments (crypto tickers like BTC-USD on the daily endpoint) commonly have no countryCode and hit this path.","commonSituations":"Crypto/derivative symbols routed through the equities daily endpoint; Tiingo search dropping countryCode for small exchanges; first-of-day fetch after TTL expiry racing an upstream data gap.","solutions":["Identify the symbol class: if it is crypto/forex, price it via an endpoint/provider that returns the currency directly instead of inferring from country","Retry later -- a missing countryCode is often a transient upstream gap; the 24h cache means it re-attempts next day","If a legitimate countryCode is unmapped, extend currency_for_country's ISO 4217 mapping"],"exampleFix":"# before\ncurrency = Rails.cache.read(cache_key) || fetch_currency_for_symbol(symbol)\n\n# after\ncurrency = Rails.cache.read(cache_key)\ncurrency ||= begin\n  fetch_currency_for_symbol(symbol)\nrescue Provider::Tiingo::Error\n  raise if crypto_or_fx?(symbol)\n  'USD' # explicit, logged fallback for equities with missing country data\nend","handlingStrategy":"fallback","validationCode":"# Warm the currency cache via search before bulk pricing\nprovider.search_securities(symbol) unless Rails.cache.read(\"tiingo:currency:#{symbol.upcase}\")","typeGuard":null,"tryCatchPattern":"begin\n  currency = fetch_currency_for_symbol(symbol)\nrescue Provider::Tiingo::Error\n  currency = nil # skip symbol this round; do not guess\nend","preventionTips":["Call search_securities first: it pre-populates tiingo:currency:<SYMBOL> for 24h and avoids the fallback entirely","Route crypto/forex symbols to providers that report currency directly"],"tags":["tiingo","currency","search","provider-data","iso-4217"],"backgroundTag":"currency-resolution-failed","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}