78 lines
2.3 KiB
Ruby
78 lines
2.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
require "net/http"
|
|
|
|
require "provider/adapters/adapter"
|
|
|
|
class Provider
|
|
module Adapters
|
|
# Bank Negara Malaysia daily exchange rates. Data available from 2006-01-03.
|
|
# Historical rates are fetched per-currency per-month.
|
|
class BNM < Adapter
|
|
BASE_URL = "https://api.bnm.gov.my/public/exchange-rate"
|
|
SESSION = "0900"
|
|
HEADERS = { "Accept" => "application/vnd.BNM.API.v1+json" }.freeze
|
|
|
|
class << self
|
|
def backfill_range = 30
|
|
end
|
|
|
|
def fetch(after: nil, upto: nil)
|
|
start_date = Date.parse(after.to_s)
|
|
end_date = upto || Date.today
|
|
currencies = fetch_currencies
|
|
currencies.flat_map { |code| fetch_currency(code, start_date, end_date) }
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_currencies
|
|
uri = URI("#{BASE_URL}?session=#{SESSION}")
|
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
http.get("#{uri.path}?#{uri.query}", HEADERS)
|
|
end
|
|
data = JSON.parse(response.body)
|
|
data["data"].map { |item| item["currency_code"] }
|
|
end
|
|
|
|
def fetch_currency(code, start_date, end_date)
|
|
records = []
|
|
each_month(start_date, end_date) do |year, month|
|
|
uri = URI("#{BASE_URL}/#{code}/year/#{year}/month/#{month}?session=#{SESSION}")
|
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
|
http.get("#{uri.path}?#{uri.query}", HEADERS)
|
|
end
|
|
data = JSON.parse(response.body)
|
|
currency_data = data["data"]
|
|
next if currency_data.nil? || currency_data.is_a?(Array)
|
|
|
|
unit = currency_data["unit"] || 1
|
|
rates = currency_data["rate"]
|
|
rates = [rates] unless rates.is_a?(Array)
|
|
|
|
rates.each do |rate|
|
|
mid = rate["middle_rate"]
|
|
next unless mid
|
|
|
|
date = Date.parse(rate["date"])
|
|
next if date > end_date
|
|
|
|
records << { date:, base: code, quote: "MYR", rate: mid / unit }
|
|
end
|
|
|
|
sleep(1)
|
|
end
|
|
records
|
|
end
|
|
|
|
def each_month(start_date, end_date)
|
|
date = Date.new(start_date.year, start_date.month, 1)
|
|
while date <= end_date
|
|
yield date.year, date.month
|
|
date = date.next_month
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|