70 lines
1.9 KiB
Ruby
70 lines
1.9 KiB
Ruby
# 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
|