we-promise/sure · error · Provider::Tiingo::Error

Could not determine currency for #{symbol} from Tiingo searc

Error message

Could not determine currency for #{symbol} from Tiingo search

What it means

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.

Source

Thrown at app/models/provider/tiingo.rb:300

      response = client.get("#{base_url}/tiingo/utilities/search") do |req|
        req.params["query"] = symbol
      end

      parsed = JSON.parse(response.body)
      check_api_error!(parsed)

      if parsed.is_a?(Array)
        match = best_match_for_ticker(parsed, symbol)
        currency = currency_for_country(match&.dig("countryCode"))

        if currency.present?
          Rails.cache.write("tiingo:currency:#{symbol.upcase}", currency, expires_in: 24.hours)
          return currency
        end
      end

      raise Error, "Could not determine currency for #{symbol} from Tiingo search"
    end

    def map_exchange_to_mic(exchange_name)
      return nil if exchange_name.blank?
      TIINGO_EXCHANGE_TO_MIC[exchange_name.strip] || exchange_name.strip
    end

    # Tiingo's search/utilities response never includes a priceCurrency field
    # (confirmed against the live API), only countryCode. Resolve the currency
    # via the countries gem's ISO 4217 data (already used for country
    # resolution in Provider::TwelveData) instead of hand-maintaining a
    # per-provider allowlist.
    def currency_for_country(country_code)
      return nil if country_code.blank?
      ISO3166::Country.new(country_code.strip)&.currency_code
    end

    # Tiingo's search endpoint can return multiple entries sharing the exact

View on GitHub (pinned to e69894adb9)

Solutions

  1. 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
  2. Retry later -- a missing countryCode is often a transient upstream gap; the 24h cache means it re-attempts next day
  3. If a legitimate countryCode is unmapped, extend currency_for_country's ISO 4217 mapping

Example fix

# before
currency = Rails.cache.read(cache_key) || fetch_currency_for_symbol(symbol)

# after
currency = Rails.cache.read(cache_key)
currency ||= begin
  fetch_currency_for_symbol(symbol)
rescue Provider::Tiingo::Error
  raise if crypto_or_fx?(symbol)
  'USD' # explicit, logged fallback for equities with missing country data
end
Defensive patterns

Strategy: fallback

Validate before calling

# Warm the currency cache via search before bulk pricing
provider.search_securities(symbol) unless Rails.cache.read("tiingo:currency:#{symbol.upcase}")

Try / catch

begin
  currency = fetch_currency_for_symbol(symbol)
rescue Provider::Tiingo::Error
  currency = nil # skip symbol this round; do not guess
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/61191739eb2ab8c7. Report an issue: GitHub.