mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-18 20:59:39 +02:00
120 lines
3 KiB
Ruby
120 lines
3 KiB
Ruby
class Provider::Marketstack
|
|
include Retryable
|
|
|
|
def initialize(api_key)
|
|
@api_key = api_key
|
|
end
|
|
|
|
def fetch_security_prices(ticker:, start_date:, end_date:)
|
|
prices = paginate("#{base_url}/eod", {
|
|
symbols: ticker,
|
|
date_from: start_date.to_s,
|
|
date_to: end_date.to_s
|
|
}) do |body|
|
|
body.dig("data").map do |price|
|
|
{
|
|
date: price["date"],
|
|
price: price["close"]&.to_f,
|
|
currency: "USD"
|
|
}
|
|
end
|
|
end
|
|
|
|
SecurityPriceResponse.new(
|
|
prices: prices,
|
|
success?: true,
|
|
raw_response: prices.to_json
|
|
)
|
|
rescue StandardError => error
|
|
SecurityPriceResponse.new(
|
|
success?: false,
|
|
error: error,
|
|
raw_response: error
|
|
)
|
|
end
|
|
|
|
def fetch_tickers(exchange_mic: nil)
|
|
url = exchange_mic ? "#{base_url}/tickers?exchange=#{exchange_mic}" : "#{base_url}/tickers"
|
|
tickers = paginate(url) do |body|
|
|
body.dig("data").map do |ticker|
|
|
{
|
|
name: ticker["name"],
|
|
symbol: ticker["symbol"],
|
|
exchange_mic: exchange_mic || ticker.dig("stock_exchange", "mic"),
|
|
exchange_acronym: ticker.dig("stock_exchange", "acronym"),
|
|
country_code: ticker.dig("stock_exchange", "country_code")
|
|
}
|
|
end
|
|
end
|
|
|
|
TickerResponse.new(
|
|
tickers: tickers,
|
|
success?: true,
|
|
raw_response: tickers.to_json
|
|
)
|
|
rescue StandardError => error
|
|
TickerResponse.new(
|
|
success?: false,
|
|
error: error,
|
|
raw_response: error
|
|
)
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :api_key
|
|
|
|
SecurityPriceResponse = Struct.new(:prices, :success?, :error, :raw_response, keyword_init: true)
|
|
TickerResponse = Struct.new(:tickers, :success?, :error, :raw_response, keyword_init: true)
|
|
|
|
def base_url
|
|
"https://api.marketstack.com/v1"
|
|
end
|
|
|
|
def client
|
|
@client ||= Faraday.new(url: base_url) do |faraday|
|
|
faraday.params["access_key"] = api_key
|
|
end
|
|
end
|
|
|
|
def build_error(response)
|
|
Provider::Base::ProviderError.new(<<~ERROR)
|
|
Failed to fetch data from #{self.class}
|
|
Status: #{response.status}
|
|
Body: #{response.body.inspect}
|
|
ERROR
|
|
end
|
|
|
|
def fetch_page(url, page, params = {})
|
|
client.get(url) do |req|
|
|
params.each { |k, v| req.params[k.to_s] = v.to_s }
|
|
req.params["offset"] = (page - 1) * 100 # Marketstack uses offset-based pagination
|
|
req.params["limit"] = 10000 # Maximum allowed by Marketstack
|
|
end
|
|
end
|
|
|
|
def paginate(url, params = {})
|
|
results = []
|
|
page = 1
|
|
total_results = Float::INFINITY
|
|
|
|
while results.length < total_results
|
|
response = fetch_page(url, page, params)
|
|
|
|
if response.success?
|
|
body = JSON.parse(response.body)
|
|
page_results = yield(body)
|
|
results.concat(page_results)
|
|
|
|
total_results = body.dig("pagination", "total")
|
|
page += 1
|
|
else
|
|
raise build_error(response)
|
|
end
|
|
|
|
break if results.length >= total_results
|
|
end
|
|
|
|
results
|
|
end
|
|
end
|