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
- 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.
- Increase FX_RATE_LOOKBACK_DAYS (e.g. to 14) to span long holiday stretches.
- Verify the pair exists on MOEX (ISS /engines/currency/markets/svt/sessions/today or the CETS market) before wiring it into sync config.
- 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
- Keep a per-provider supported-pair list and dispatch rate lookups by pair, not by first provider.
- Size the lookback window to the target market's longest holiday closure.
- Cache resolved rates per (pair, date) to avoid re-hitting empty series.
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
- Frankfurter currencies endpoint returned no data
- Unexpected Frankfurter response shape
- Invalid date in Frankfurter response: #{e.message}
- Invalid Frankfurter response: #{e.message}
- Unexpected Frankfurter response shape (expected an array)
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/669d056635f10fd7.
Report an issue: GitHub.