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

No MOEX FX rate for #{from}/#{to} on #{date}

Error message

No MOEX FX rate for #{from}/#{to} on #{date}

What it means

Raised by Provider::MoexPublic#fetch_exchange_rate when the MOEX ISS FX endpoint returns zero rows for the currency pair over an FX_RATE_LOOKBACK_DAYS window ending at `date`. The method already tolerates weekends by scanning backwards for the closest prior close, so an empty series means MOEX genuinely does not quote that pair (or did not within the lookback). MOEX quotes FX against RUB (e.g. USD/RUB, CNY/RUB), so cross pairs like USD/EUR are not directly available.

Source

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

      prices.sort_by(&:date)
    end
  end

  def max_history_days
    nil # ISS serves full history.
  end

  # ================================
  #          Exchange Rates
  # ================================

  def fetch_exchange_rate(from:, to:, date:)
    with_provider_response do
      # Fetch a short lookback window, not just the exact day, so a weekend or
      # holiday request still resolves to the previous trading day's close.
      rates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)
      raise Error, "No MOEX FX rate for #{from}/#{to} on #{date}" if rates.blank?

      rates.find { |r| r.date == date } ||
        rates.select { |r| r.date <= date }.max_by(&:date) ||
        rates.first
    end
  end

  def fetch_exchange_rates(from:, to:, start_date:, end_date:)
    with_provider_response do
      exchange_rates(from, to, start_date, end_date)
    end
  end

  private

    # ================================
    #          HTTP / parsing
    # ================================

View on GitHub (pinned to e69894adb9)

Solutions

  1. Check whether the pair is RUB-based; for cross rates derive via RUB legs (EUR->RUB and USD->RUB) or route non-RUB pairs to another provider in the registry.
  2. Increase FX_RATE_LOOKBACK_DAYS (e.g. to 14) to span long holiday stretches.
  3. Verify the pair exists on MOEX (ISS /engines/currency/markets/svt/sessions/today or the CETS market) before wiring it into sync config.
  4. Fall back to another rate provider for unsupported pairs rather than failing the valuation.

Example fix

# before
rates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)

# after
if to == "RUB" || from == "RUB"
  rates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)
else
  rates = cross_rate_via_rub(from, to, date) # EUR->RUB / USD->RUB, or delegate to another provider
end
Defensive patterns

Strategy: fallback

Validate before calling

return nil unless [from, to].include?("RUB") # MOEX only quotes FX vs RUB; route cross pairs elsewhere

Try / catch

begin
  rate = provider.fetch_exchange_rate(from: from, to: to, date: date)
rescue Provider::MoexPublic::Error
  rate = alternate_provider.fetch_exchange_rate(from: from, to: to, date: date)
end

Prevention

When it happens

Trigger: fetch_exchange_rate(from: "EUR", to: "USD", date: ...) — no direct MOEX market for non-RUB legs; exotic currency not traded on MOEX (e.g. BRL, ZAR); a lookback window entirely inside a long Russian market holiday; requesting a date before the pair started trading on MOEX (sanctions-era listings moved).

Common situations: Valuing a foreign-currency account against a non-RUB pair; assuming MOEX is a universal FX source like ECB/Frankfurter; window too short around New Year holidays (Jan 1-8 in Russia).

Related errors


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