{"record":{"id":"669d056635f10fd7","repo":"we-promise/sure","slug":"no-moex-fx-rate-for-from-to-on-date","errorCode":null,"errorMessage":"No MOEX FX rate for #{from}/#{to} on #{date}","messagePattern":"No MOEX FX rate for #(.+?)/#(.+?) on #(.+?)","errorType":"exception","errorClass":"Provider::MoexPublic::Error","httpStatus":null,"severity":"error","filePath":"app/models/provider/moex_public.rb","lineNumber":189,"sourceCode":"\n      prices.sort_by(&:date)\n    end\n  end\n\n  def max_history_days\n    nil # ISS serves full history.\n  end\n\n  # ================================\n  #          Exchange Rates\n  # ================================\n\n  def fetch_exchange_rate(from:, to:, date:)\n    with_provider_response do\n      # Fetch a short lookback window, not just the exact day, so a weekend or\n      # holiday request still resolves to the previous trading day's close.\n      rates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)\n      raise Error, \"No MOEX FX rate for #{from}/#{to} on #{date}\" if rates.blank?\n\n      rates.find { |r| r.date == date } ||\n        rates.select { |r| r.date <= date }.max_by(&:date) ||\n        rates.first\n    end\n  end\n\n  def fetch_exchange_rates(from:, to:, start_date:, end_date:)\n    with_provider_response do\n      exchange_rates(from, to, start_date, end_date)\n    end\n  end\n\n  private\n\n    # ================================\n    #          HTTP / parsing\n    # ================================","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/provider/moex_public.rb#L171-L207","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","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."],"exampleFix":"# before\nrates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)\n\n# after\nif to == \"RUB\" || from == \"RUB\"\n  rates = exchange_rates(from, to, date - FX_RATE_LOOKBACK_DAYS, date)\nelse\n  rates = cross_rate_via_rub(from, to, date) # EUR->RUB / USD->RUB, or delegate to another provider\nend","handlingStrategy":"fallback","validationCode":"return nil unless [from, to].include?(\"RUB\") # MOEX only quotes FX vs RUB; route cross pairs elsewhere","typeGuard":null,"tryCatchPattern":"begin\n  rate = provider.fetch_exchange_rate(from: from, to: to, date: date)\nrescue Provider::MoexPublic::Error\n  rate = alternate_provider.fetch_exchange_rate(from: from, to: to, date: date)\nend","preventionTips":["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."],"tags":["moex","fx-rates","currency-pair","exchange-rates"],"backgroundTag":"exchange-rate-unavailable","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}