we-promise/sure · warning · Provider::TinkoffInvest::InvalidSecurityPriceError

No T-Invest price for #{symbol} on #{date}

Error message

No T-Invest price for #{symbol} on #{date}

What it means

fetch_security_price delegates to fetch_security_prices for the single day and raises InvalidSecurityPriceError when the result set is empty -- no candles and no live price existed for that instrument on that date. For T-Invest this typically means a non-trading day (MOEX weekend/holiday), a date before the instrument listed, or a suspended board. The rescue-free raise distinguishes 'we got data but not for this date' from instrument resolution failures.

Source

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

        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(
        symbol: symbol,
        exchange_operating_mic: exchange_operating_mic,
        start_date: date,
        end_date: date
      )

      raise historical.error if historical.error.present?
      raise InvalidSecurityPriceError, "No T-Invest price for #{symbol} on #{date}" if historical.data.blank?

      historical.data.find { |p| p.date == date } ||
        historical.data.select { |p| p.date <= date }.max_by(&:date) ||
        historical.data.first
    end
  end

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

      uid = short["uid"]
      bond = short["instrumentType"].to_s == "bond"
      currency = short["currency"].to_s.upcase
      mic = mic_for(short["classCode"], short["exchange"])

      # Bonds quote in % of par; multiply by nominal to get a money price. A

View on GitHub (pinned to e69894adb9)

Solutions

  1. Validate the date against MOEX trading calendar / walk back to the previous trading day before calling
  2. For 'today' prices, tolerate lag: retry shortly after session open or use the previous close explicitly
  3. Confirm the instrument has any history at all via fetch_security_prices over a wide range

Example fix

# before
price = provider.fetch_security_price(symbol: sym, exchange_operating_mic: mic, date: date)

# after
begin
  price = provider.fetch_security_price(symbol: sym, exchange_operating_mic: mic, date: date)
rescue Provider::TinkoffInvest::InvalidSecurityPriceError
  date = MoexTradingCalendar.previous_trading_day(date)
  retry
end
Defensive patterns

Strategy: fallback

Validate before calling

# MOEX trades Mon-Fri; verify holidays before exact-date pricing
date = MoexTradingCalendar.previous_trading_day(date) unless MoexTradingCalendar.trading_day?(date)

Try / catch

begin
  price = provider.fetch_security_price(symbol:, exchange_operating_mic:, date:)
rescue Provider::TinkoffInvest::InvalidSecurityPriceError
  price = security.last_known_price # degrade to previous valuation
end

Prevention

When it happens

Trigger: fetch_security_price(symbol: 'SBER', exchange_operating_mic: 'XMOS', date: <Saturday or MOEX holiday>) returns an empty historical.data; also early-morning calls where the candle feed lags and the live-price append hasn't produced a row for today yet; dates before listing.

Common situations: Valuation jobs running on Moscow-exchange holidays not in the app's holiday calendar; 'price as of today' queries fired before session data lands; newly listed bonds with no candle history yet.

Related errors


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