103 lines
2.9 KiB
Ruby
103 lines
2.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "net/http"
|
|
require "ox"
|
|
|
|
require "provider/adapters/adapter"
|
|
|
|
class Provider
|
|
module Adapters
|
|
# National Bank of Moldova (Banca Națională a Moldovei). Publishes daily rates for
|
|
# 30+ currencies against MDL, plus daily reference prices for gold and silver.
|
|
# Date-parameterized XML endpoints, one request per day. Metals come from
|
|
# official_metal_rates in MDL per gram; values are normalized to per troy ounce here.
|
|
class NBM < Adapter
|
|
FX_URL = "https://www.bnm.md/en/official_exchange_rates"
|
|
METAL_URL = "https://www.bnm.md/en/official_metal_rates"
|
|
|
|
METAL_CODES = ["XAU", "XAG"].freeze
|
|
|
|
class << self
|
|
def backfill_range = 30
|
|
end
|
|
|
|
def fetch(after: nil, upto: nil)
|
|
end_date = upto || Date.today
|
|
dataset = []
|
|
|
|
first = true
|
|
(after..end_date).each do |date|
|
|
next if date.saturday? || date.sunday?
|
|
|
|
sleep(0.5) unless first
|
|
first = false
|
|
dataset.concat(fetch_date(date))
|
|
|
|
sleep(0.5)
|
|
dataset.concat(fetch_metals_date(date))
|
|
end
|
|
|
|
dataset
|
|
end
|
|
|
|
def parse(xml)
|
|
doc = Ox.load(xml)
|
|
val_curs = doc.locate("ValCurs").first
|
|
return [] unless val_curs
|
|
|
|
date_str = val_curs[:Date]
|
|
return [] unless date_str
|
|
|
|
date = Date.strptime(date_str, "%d.%m.%Y")
|
|
|
|
val_curs.locate("Valute").filter_map do |v|
|
|
code = v.locate("CharCode").first&.text
|
|
next unless code&.match?(/\A[A-Z]{3}\z/)
|
|
|
|
nominal = v.locate("Nominal").first&.text.to_f
|
|
value = v.locate("Value").first&.text.to_f
|
|
next if value.zero? || nominal.zero?
|
|
|
|
{ date:, base: code, quote: "MDL", rate: value / nominal }
|
|
end
|
|
end
|
|
|
|
def parse_metals(xml)
|
|
doc = Ox.load(xml)
|
|
metal_price = doc.locate("MetalPrice").first
|
|
return [] unless metal_price
|
|
|
|
date_str = metal_price[:Date]
|
|
return [] unless date_str
|
|
|
|
date = Date.strptime(date_str, "%d.%m.%Y")
|
|
return [] if date.saturday? || date.sunday?
|
|
|
|
metal_price.locate("Metal").filter_map do |m|
|
|
code = m.locate("CharCode").first&.text
|
|
next unless METAL_CODES.include?(code)
|
|
|
|
nominal = m.locate("Nominal").first&.text.to_f
|
|
value = m.locate("Value").first&.text.to_f
|
|
next if value.zero? || nominal.zero?
|
|
|
|
{ date:, base: code, quote: "MDL", rate: (value / nominal) * GRAMS_PER_TROY_OUNCE }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_date(date)
|
|
url = URI(FX_URL)
|
|
url.query = URI.encode_www_form(get_xml: 1, date: date.strftime("%d.%m.%Y"))
|
|
parse(Net::HTTP.get(url))
|
|
end
|
|
|
|
def fetch_metals_date(date)
|
|
url = URI(METAL_URL)
|
|
url.query = URI.encode_www_form(get_xml: 1, date: date.strftime("%d.%m.%Y"))
|
|
parse_metals(Net::HTTP.get(url))
|
|
end
|
|
end
|
|
end
|
|
end
|