# frozen_string_literal: true require "json" require "net/http" require "provider/adapters/adapter" class Provider module Adapters # Federal Reserve Economic Data (FRED). Publishes daily H.10 exchange rates. # Most series are quoted as foreign currency per USD. A few (AUD, EUR, GBP, NZD) # are quoted as USD per foreign currency and stored with the foreign currency as base. # The H.10 is a curated policy publication — these are all the series available. class FRED < Adapter API_URL = "https://api.stlouisfed.org/fred/series/observations" # series_id => [quote, base] SERIES = { "DEXBZUS" => ["BRL", "USD"], "DEXCAUS" => ["CAD", "USD"], "DEXCHUS" => ["CNY", "USD"], "DEXDNUS" => ["DKK", "USD"], "DEXHKUS" => ["HKD", "USD"], "DEXINUS" => ["INR", "USD"], "DEXJPUS" => ["JPY", "USD"], "DEXKOUS" => ["KRW", "USD"], "DEXMAUS" => ["MYR", "USD"], "DEXMXUS" => ["MXN", "USD"], "DEXNOUS" => ["NOK", "USD"], "DEXSDUS" => ["SEK", "USD"], "DEXSFUS" => ["ZAR", "USD"], "DEXSIUS" => ["SGD", "USD"], "DEXSLUS" => ["LKR", "USD"], "DEXSZUS" => ["CHF", "USD"], "DEXTAUS" => ["TWD", "USD"], "DEXTHUS" => ["THB", "USD"], "DEXUSAL" => ["USD", "AUD"], "DEXUSEU" => ["USD", "EUR"], "DEXUSNZ" => ["USD", "NZD"], "DEXUSUK" => ["USD", "GBP"], }.freeze class << self def api_key = ENV["FRED_API_KEY"] || raise(Adapter::Unavailable, "no API key") end def fetch(after: nil, upto: nil) dataset = [] params = {} params[:observation_start] = after.to_s if after SERIES.each do |series_id, (quote, series_base)| sleep(0.2) dataset.concat(fetch_series(series_id, quote, series_base, **params)) end dataset end private def fetch_series(series_id, quote, series_base, **params) url = URI(API_URL) url.query = URI.encode_www_form( series_id: series_id, api_key: self.class.api_key, file_type: "json", **params, ) response = Net::HTTP.get(url) data = JSON.parse(response) (data["observations"] || []).filter_map do |obs| value = obs["value"] next if value == "." rate = Float(value) next if rate.zero? date = Date.parse(obs["date"]) { date:, base: series_base, quote:, rate: } end end end end end