96 lines
2.9 KiB
Ruby
96 lines
2.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json"
|
|
require "net/http"
|
|
|
|
require "provider/adapters/adapter"
|
|
|
|
class Provider
|
|
module Adapters
|
|
# National Bank of Poland. Publishes daily mid-market rates (Table A) for ~32 currencies
|
|
# and weekly mid-market rates (Table B) for ~150 additional currencies against PLN,
|
|
# plus a daily gold reference price. Gold values come in PLN per gram and are
|
|
# normalized here to per troy ounce.
|
|
class NBP < Adapter
|
|
TABLE_A_URL = "https://api.nbp.pl/api/exchangerates/tables/A"
|
|
TABLE_B_URL = "https://api.nbp.pl/api/exchangerates/tables/B"
|
|
GOLD_URL = "https://api.nbp.pl/api/cenyzlota"
|
|
def fetch(after: nil, upto: nil)
|
|
end_date = upto || Date.today
|
|
dataset = []
|
|
|
|
each_chunk(after, end_date) do |chunk_start, chunk_end|
|
|
dataset.concat(fetch_rates(TABLE_A_URL, chunk_start, chunk_end))
|
|
dataset.concat(fetch_rates(TABLE_B_URL, chunk_start, chunk_end))
|
|
dataset.concat(fetch_gold(chunk_start, chunk_end))
|
|
end
|
|
|
|
dataset
|
|
end
|
|
|
|
def parse(json)
|
|
data = json.is_a?(String) ? JSON.parse(json) : json
|
|
|
|
data.flat_map do |table|
|
|
date = Date.parse(table["effectiveDate"])
|
|
table["rates"].filter_map do |rate|
|
|
iso = rate["code"]
|
|
mid = rate["mid"]
|
|
next unless iso.match?(/\A[A-Z]{3}\z/)
|
|
next if mid.nil? || mid.zero?
|
|
|
|
{ date:, base: iso, quote: "PLN", rate: mid }
|
|
end
|
|
end
|
|
end
|
|
|
|
def parse_gold(json)
|
|
data = json.is_a?(String) ? JSON.parse(json) : json
|
|
|
|
data.filter_map do |row|
|
|
price = row["cena"]
|
|
next if price.nil? || price.zero?
|
|
|
|
{ date: Date.parse(row["data"]), base: "XAU", quote: "PLN", rate: price * GRAMS_PER_TROY_OUNCE }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_rates(table_url, start_date, end_date)
|
|
url = URI("#{table_url}/#{start_date}/#{end_date}/?format=json")
|
|
response = Net::HTTP.get_response(url)
|
|
|
|
# This happens if the date range includes no working days
|
|
return [] if response.is_a?(Net::HTTPNotFound)
|
|
|
|
response.value
|
|
parse(response.body)
|
|
end
|
|
|
|
def fetch_gold(start_date, end_date)
|
|
url = URI("#{GOLD_URL}/#{start_date}/#{end_date}/?format=json")
|
|
response = Net::HTTP.get_response(url)
|
|
|
|
# This happens if the date range includes no working days
|
|
return [] if response.is_a?(Net::HTTPNotFound)
|
|
|
|
response.value
|
|
parse_gold(response.body)
|
|
end
|
|
|
|
# NBP API limits queries to 93 days per request
|
|
def each_chunk(start_date, end_date)
|
|
current = start_date
|
|
first = true
|
|
while current <= end_date
|
|
sleep(0.5) unless first
|
|
first = false
|
|
chunk_end = [current + 92, end_date].min
|
|
yield current, chunk_end
|
|
current = chunk_end + 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|