frankfurter/lib/provider/adapters/banxico.rb

71 lines
1.9 KiB
Ruby
Raw Normal View History

2026-05-21 01:38:14 -06:00
# frozen_string_literal: true
require "json"
require "net/http"
require "provider/adapters/adapter"
class Provider
module Adapters
# Banco de México. Fetches daily FIX and reference exchange rates for 5
# currencies against the Mexican peso (MXN) via the SIE REST API.
# Supports batched series queries and date range filtering.
class BANXICO < Adapter
BASE_URL = "https://www.banxico.org.mx/SieAPIRest/service/v1/series"
# series_id => base currency (all quoted against MXN)
SERIES = {
"SF43718" => "USD",
"SF46410" => "EUR",
"SF46407" => "GBP",
"SF46406" => "JPY",
"SF60632" => "CAD",
}.freeze
class << self
def api_key = ENV["BANXICO_API_KEY"] || raise(Adapter::Unavailable, "no API key")
end
def fetch(after: nil, upto: nil)
ids = SERIES.keys.join(",")
url = if after
end_date = upto || Date.today
URI("#{BASE_URL}/#{ids}/datos/#{after}/#{end_date}")
else
URI("#{BASE_URL}/#{ids}/datos")
end
response = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http|
request = Net::HTTP::Get.new(url)
request["Bmx-Token"] = self.class.api_key
http.request(request)
end
parse(response.body)
end
def parse(json)
data = JSON.parse(json)
series = data.dig("bmx", "series") || []
series.flat_map do |s|
base = SERIES[s["idSerie"]]
next [] unless base
(s["datos"] || []).filter_map do |obs|
value = obs["dato"]&.tr(",", "")
next unless value
# Skip "N/E" (not available) values
rate = Float(value, exception: false)
next unless rate&.positive?
date = Date.strptime(obs["fecha"], "%d/%m/%Y")
{ date:, base:, quote: "MXN", rate: }
end
end
end
end
end
end