we-promise/sure · error · Provider::MoexPublic::InvalidSecurityPriceError

No price found for #{symbol} on #{date}

Error message

No price found for #{symbol} on #{date}

What it means

Raised by Provider::MoexPublic#fetch_security_price (an InvalidSecurityPriceError) when a same-day history query (start_date == end_date == date) for a resolved SECID returns zero candles. MOEX ISS /history/... only returns rows for trading sessions, so the single-day window is empty whenever `date` is not a session day or the board had no trades. Resolution already succeeded (resolve_instrument ran), so this is purely 'no rows in the candle history for that exact day'.

Source

Thrown at app/models/provider/moex_public.rb:138

        logo_url: nil,
        description: nil,
        kind: instrument[:kind],
        exchange_operating_mic: MOEX_MIC
      )
    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 price found for #{symbol} on #{date}" if historical.data.blank?

      # Exact date if present, else the nearest available close on or before it.
      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
      secid = normalize_secid(symbol)
      instrument = resolve_instrument(secid)
      bond = instrument[:market].to_s.downcase == "bonds"

      prices = history_prices(secid, instrument, start_date, end_date, bond)

      # The history endpoint does not carry the live/most-recent session, so for
      # a range reaching today append the current marketdata price.

View on GitHub (pinned to e69894adb9)

Solutions

  1. Widen the window — call fetch_security_prices(symbol:, start_date: date - 7.days, end_date: date) and pick select { |p| p.date <= date }.max_by(&:date), exactly like the MFAPI provider does.
  2. Skip non-trading days: check the date against the MOEX trading calendar (or fall back to 'most recent session' via the provider's current-price path) before requesting.
  3. Validate the date is within the instrument's listing period (from /securities/{secid}.json metadata) and not in the future.
  4. Catch InvalidSecurityPriceError per-date and carry forward the last known price instead of failing the valuation batch.

Example fix

# before
historical = fetch_security_prices(symbol: symbol, exchange_operating_mic: exchange_operating_mic, start_date: date, end_date: date)

# after
historical = fetch_security_prices(symbol: symbol, exchange_operating_mic: exchange_operating_mic, start_date: date - 7.days, end_date: date)
# raise only if the whole 7-day window is empty, then reuse the existing closest-previous fallback below
Defensive patterns

Strategy: validation

Validate before calling

window_start = date - 7.days # ask for a window, not one day, so non-trading dates still resolve
prices = provider.fetch_security_prices(symbol: secid, exchange_operating_mic: mic, start_date: window_start, end_date: date)

Try / catch

begin
  price = provider.fetch_security_price(symbol: secid, exchange_operating_mic: mic, date: date)
rescue Provider::MoexPublic::InvalidSecurityPriceError
  price = nil # carry forward previous close
end

Prevention

When it happens

Trigger: Calling fetch_security_price with a Saturday, Sunday, or Russian public holiday as date; a thinly-traded bond board with no trades that session; a date after delisting; a date before the instrument listed on the resolved board; requesting a future date.

Common situations: Daily portfolio valuation jobs that run on weekends and naively ask for today's price; syncing US-style date assumptions against the MOEX calendar (Russia has its own holiday set); illiquid corporate bonds on non-primary boards.

Related errors


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