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

Unknown T-Invest instrument: #{symbol}

Error message

Unknown T-Invest instrument: #{symbol}

What it means

fetch_security_info first resolves the symbol via resolve_short (InstrumentsService/FindInstrument, cached with skip_nil so misses aren't cached). resolve_short returns nil when FindInstrument yields no rows, or no rows whose instrumentType maps to a surfaced kind. fetch_security_info then raises Error 'Unknown T-Invest instrument: <symbol>'. Exchange suffixes like .ME/.MOEX/.MISX/.MCX are stripped automatically before the lookup, so the remaining causes are genuinely unknown tickers or unsupported instrument types.

Source

Thrown at app/models/provider/tinkoff_invest.rb:98

        next nil unless row["apiTradeAvailableFlag"] # only surface instruments the API can actually price
        next nil unless surfaced_kind(row["instrumentType"])

        Provider::SecurityConcept::Security.new(
          symbol: row["ticker"].to_s,
          name: (row["name"].presence || row["ticker"]).to_s,
          logo_url: nil, # FindInstrument carries no brand; logos come from #fetch_security_info
          exchange_operating_mic: mic_for(row["classCode"], row["exchange"]),
          country_code: row["countryOfRisk"].presence,
          currency: row["currency"].to_s.upcase.presence
        )
      end.uniq { |s| [ s.symbol, s.exchange_operating_mic ] }
    end
  end

  def fetch_security_info(symbol:, exchange_operating_mic:)
    with_provider_response do
      short = resolve_short(symbol, exchange_operating_mic)
      raise Error, "Unknown T-Invest instrument: #{symbol}" if short.nil?

      detail = instrument_detail(short["uid"])

      SecurityInfo.new(
        symbol: short["ticker"].to_s,
        name: (detail["name"].presence || short["name"]).to_s,
        links: nil, # T-Invest exposes no issuer website
        logo_url: logo_url(detail.dig("brand", "logoName")),
        description: nil,
        kind: surfaced_kind(short["instrumentType"]),
        exchange_operating_mic: mic_for(short["classCode"], detail["exchange"])
      )
    end
  end

  def fetch_security_price(symbol:, exchange_operating_mic:, date:)
    with_provider_response do
      historical = fetch_security_prices(

View on GitHub (pinned to e69894adb9)

Solutions

  1. Verify the instrument exists in T-Invest (invest.tinkoff.ru or the API's FindInstrument) using the bare SECID without exchange suffix
  2. If it exists but raises, check its instrumentType -- only kinds surfaced_kind maps are supported; unsupported types must use another provider
  3. Retry once for a transient empty catalog response; skip_nil caching means a later successful lookup will fill the cache

Example fix

# before
info = provider.fetch_security_info(symbol: symbol, exchange_operating_mic: mic)

# after
begin
  info = provider.fetch_security_info(symbol: symbol, exchange_operating_mic: mic)
rescue Provider::TinkoffInvest::Error
  fallback_info = other_provider.fetch_security_info(symbol: symbol, exchange_operating_mic: mic) if other_provider
  raise unless fallback_info
  fallback_info
end
Defensive patterns

Strategy: validation

Validate before calling

# Only ask T-Invest for instruments it actually lists
provider.find_instruments(security.symbol).any? { |r| r['ticker'].to_s.casecmp?(security.symbol.sub(/\.(ME|MOEX|MISX|MCX)\z/i, '')) }

Type guard

def t_invest_instrument?(find_instruments_rows, symbol)
  bare = symbol.sub(/\.(ME|MOEX|MISX|MCX)\z/i, '')
  find_instruments_rows.any? { |r| r['ticker'].to_s.casecmp?(bare) || r['isin'].to_s.casecmp?(bare) }
end

Try / catch

begin
  info = provider.fetch_security_info(symbol:, exchange_operating_mic:)
rescue Provider::TinkoffInvest::Error => e
  raise unless e.message.include?('Unknown T-Invest instrument')
  security.update!(info_provider: 'moex_public')
end

Prevention

When it happens

Trigger: Calling fetch_security_info(symbol: 'SBER.MOEX', exchange_operating_mic: 'XMOS') works, but a foreign ticker like 'AAPL', a typo, or an instrument whose only listings have unsupported instrumentType raises. Also possible: FindInstrument returned a transient empty response (not cached thanks to skip_nil).

Common situations: Securities synced from other providers (MOEX resolver, manual entry) that T-Invest doesn't list; delisted instruments; tickers valid on SPB but not tradable via this API account; brand-new listings not yet in T-Invest's instrument catalog.

Related errors


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