190 lines
5.6 KiB
Ruby
190 lines
5.6 KiB
Ruby
|
|
# frozen_string_literal: true
|
||
|
|
|
||
|
|
require "net/http"
|
||
|
|
|
||
|
|
require "provider/adapters/adapter"
|
||
|
|
|
||
|
|
class Provider
|
||
|
|
module Adapters
|
||
|
|
# International Monetary Fund representative exchange rates.
|
||
|
|
# Daily rates for 50+ currencies. Most rates are foreign currency per USD;
|
||
|
|
# currencies marked with (1) are USD per foreign unit.
|
||
|
|
class IMF < Adapter
|
||
|
|
BASE_URL = "https://www.imf.org/external/np/fin/data/rms_mth.aspx"
|
||
|
|
|
||
|
|
# Currency name to ISO code mapping
|
||
|
|
CURRENCY_MAP = {
|
||
|
|
"afghan afghani" => "AFN",
|
||
|
|
"algerian dinar" => "DZD",
|
||
|
|
"angolan kwanza" => "AOA",
|
||
|
|
"australian dollar" => "AUD",
|
||
|
|
"bahamian dollar" => "BSD",
|
||
|
|
"bahrain dinar" => "BHD",
|
||
|
|
"bangladeshi taka" => "BDT",
|
||
|
|
"barbados dollar" => "BBD",
|
||
|
|
"botswana pula" => "BWP",
|
||
|
|
"brazilian real" => "BRL",
|
||
|
|
"brunei dollar" => "BND",
|
||
|
|
"canadian dollar" => "CAD",
|
||
|
|
"chilean peso" => "CLP",
|
||
|
|
"chinese yuan" => "CNY",
|
||
|
|
"colombian peso" => "COP",
|
||
|
|
"costa rican colon" => "CRC",
|
||
|
|
"czech koruna" => "CZK",
|
||
|
|
"danish krone" => "DKK",
|
||
|
|
"djibouti franc" => "DJF",
|
||
|
|
"egyptian pound" => "EGP",
|
||
|
|
"euro" => "EUR",
|
||
|
|
"fiji dollar" => "FJD",
|
||
|
|
"hungarian forint" => "HUF",
|
||
|
|
"icelandic krona" => "ISK",
|
||
|
|
"indian rupee" => "INR",
|
||
|
|
"iranian rial" => "IRR",
|
||
|
|
"israeli new shekel" => "ILS",
|
||
|
|
"japanese yen" => "JPY",
|
||
|
|
"jordanian dinar" => "JOD",
|
||
|
|
"kazakhstani tenge" => "KZT",
|
||
|
|
"kuwaiti dinar" => "KWD",
|
||
|
|
"libyan dinar" => "LYD",
|
||
|
|
"malaysian ringgit" => "MYR",
|
||
|
|
"mauritian rupee" => "MUR",
|
||
|
|
"mexican peso" => "MXN",
|
||
|
|
"moldovan leu" => "MDL",
|
||
|
|
"mongolian tugrik" => "MNT",
|
||
|
|
"moroccan dirham" => "MAD",
|
||
|
|
"mozambican metical" => "MZN",
|
||
|
|
"myanmar kyat" => "MMK",
|
||
|
|
"namibian dollar" => "NAD",
|
||
|
|
"nepalese rupee" => "NPR",
|
||
|
|
"new zealand dollar" => "NZD",
|
||
|
|
"nigerian naira" => "NGN",
|
||
|
|
"norwegian krone" => "NOK",
|
||
|
|
"omani rial" => "OMR",
|
||
|
|
"pakistani rupee" => "PKR",
|
||
|
|
"peruvian sol" => "PEN",
|
||
|
|
"philippine peso" => "PHP",
|
||
|
|
"polish zloty" => "PLN",
|
||
|
|
"qatari riyal" => "QAR",
|
||
|
|
"romanian leu" => "RON",
|
||
|
|
"russian ruble" => "RUB",
|
||
|
|
"rwandan franc" => "RWF",
|
||
|
|
"samoan tala" => "WST",
|
||
|
|
"saudi arabian riyal" => "SAR",
|
||
|
|
"singapore dollar" => "SGD",
|
||
|
|
"south african rand" => "ZAR",
|
||
|
|
"korean won" => "KRW",
|
||
|
|
"sri lankan rupee" => "LKR",
|
||
|
|
"swedish krona" => "SEK",
|
||
|
|
"swiss franc" => "CHF",
|
||
|
|
"thai baht" => "THB",
|
||
|
|
"trinidadian dollar" => "TTD",
|
||
|
|
"tunisian dinar" => "TND",
|
||
|
|
"ugandan shilling" => "UGX",
|
||
|
|
"u.a.e. dirham" => "AED",
|
||
|
|
"u.k. pound" => "GBP",
|
||
|
|
"u.s. dollar" => "USD",
|
||
|
|
"uruguayan peso" => "UYU",
|
||
|
|
"venezuelan bolivar" => "VES",
|
||
|
|
"yemeni rial" => "YER",
|
||
|
|
"zambian kwacha" => "ZMW",
|
||
|
|
}.freeze
|
||
|
|
|
||
|
|
def fetch(after: nil, upto: nil)
|
||
|
|
end_date = upto || Date.today
|
||
|
|
|
||
|
|
dataset = []
|
||
|
|
cursor = Date.new(after.year, after.month, 1)
|
||
|
|
|
||
|
|
while cursor <= end_date
|
||
|
|
last_day = Date.new(cursor.year, cursor.month, -1)
|
||
|
|
dataset.concat(parse(fetch_month(last_day, "REP")))
|
||
|
|
dataset.concat(parse_sdrcv(fetch_month(last_day, "SDRCV")))
|
||
|
|
cursor >>= 1
|
||
|
|
end
|
||
|
|
|
||
|
|
dataset = dataset.select { |r| r[:date] >= after } if after
|
||
|
|
dataset
|
||
|
|
end
|
||
|
|
|
||
|
|
def parse(tsv)
|
||
|
|
parse_rows(tsv) do |currency_name, rate, date|
|
||
|
|
indirect = currency_name.end_with?("(1)")
|
||
|
|
clean_name = currency_name.delete_suffix("(1)").strip.downcase
|
||
|
|
iso = CURRENCY_MAP[clean_name]
|
||
|
|
next unless iso
|
||
|
|
next if iso == "USD"
|
||
|
|
|
||
|
|
if indirect
|
||
|
|
{ date:, base: iso, quote: "USD", rate: }
|
||
|
|
else
|
||
|
|
{ date:, base: "USD", quote: iso, rate: }
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
# SDR cross rates: "SDRs per Currency unit" — only emit USD/XDR. Other pairs
|
||
|
|
# in this report are derived by IMF from the rep series and would create
|
||
|
|
# ambiguous bridges in BaseConversion if retained.
|
||
|
|
def parse_sdrcv(tsv)
|
||
|
|
parse_rows(tsv) do |currency_name, rate, date|
|
||
|
|
iso = CURRENCY_MAP[currency_name.downcase]
|
||
|
|
next unless iso == "USD"
|
||
|
|
|
||
|
|
{ date:, base: iso, quote: "XDR", rate: }
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
private
|
||
|
|
|
||
|
|
def parse_rows(tsv)
|
||
|
|
return [] if tsv.nil? || tsv.strip.empty?
|
||
|
|
|
||
|
|
records = []
|
||
|
|
dates = nil
|
||
|
|
|
||
|
|
tsv.each_line(chomp: true) do |line|
|
||
|
|
next if line.strip.empty?
|
||
|
|
|
||
|
|
if line.start_with?("Currency\t")
|
||
|
|
headers = line.split("\t")
|
||
|
|
dates = headers[1..].map { |h| Date.parse(h.strip) }
|
||
|
|
next
|
||
|
|
end
|
||
|
|
|
||
|
|
next unless dates
|
||
|
|
|
||
|
|
cols = line.split("\t")
|
||
|
|
currency_name = cols[0]&.strip
|
||
|
|
next unless currency_name
|
||
|
|
|
||
|
|
cols[1..].zip(dates).each do |value, date|
|
||
|
|
next unless date
|
||
|
|
|
||
|
|
cleaned = value&.tr(",", "")&.gsub(/[^0-9.\-]/, "")
|
||
|
|
next if cleaned.nil? || cleaned.empty?
|
||
|
|
|
||
|
|
rate = Float(cleaned)
|
||
|
|
next if rate.zero?
|
||
|
|
|
||
|
|
record = yield(currency_name, rate, date)
|
||
|
|
records << record if record
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
records
|
||
|
|
end
|
||
|
|
|
||
|
|
def fetch_month(last_day, report_type)
|
||
|
|
url = URI(BASE_URL)
|
||
|
|
url.query = URI.encode_www_form(
|
||
|
|
SelectDate: last_day.to_s,
|
||
|
|
reportType: report_type,
|
||
|
|
tsvflag: "Y",
|
||
|
|
)
|
||
|
|
|
||
|
|
Net::HTTP.get(url)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|