~singpolyma/sgx-jmp

829d69d81f60d0a222bf5d1a38f78e07ae69dfeb — Stephen Paul Weber 2 years ago 963e24d
Add helper to fetch current BTC sell prices

Scrapes the sell price for Bitcoin from canadianbitcoins.com
USD price is done by converting this CAD sell price to USD via openexchangerates
3 files changed, 90 insertions(+), 0 deletions(-)

M Gemfile
A lib/btc_sell_prices.rb
A test/test_btc_sell_prices.rb
M Gemfile => Gemfile +2 -0
@@ 6,9 6,11 @@ gem "blather", git: "https://github.com/singpolyma/blather.git", branch: "ergono
gem "braintree"
gem "dhall"
gem "em-hiredis"
gem "em-http-request"
gem "em-pg-client", git: "https://github.com/royaltm/ruby-em-pg-client"
gem "em_promise.rb"
gem "eventmachine"
gem "money-open-exchange-rates"

group(:development) do
	gem "pry-reload"

A lib/btc_sell_prices.rb => lib/btc_sell_prices.rb +57 -0
@@ 0,0 1,57 @@
# frozen_string_literal: true

require "em-http"
require "money/bank/open_exchange_rates_bank"
require "nokogiri"

require_relative "em"

class BTCSellPrices
	def initialize(redis, oxr_app_id)
		@redis = redis
		@oxr = Money::Bank::OpenExchangeRatesBank.new(
			Money::RatesStore::Memory.new
		)
		@oxr.app_id = oxr_app_id
	end

	def cad
		fetch_canadianbitcoins.then do |http|
			canadianbitcoins = Nokogiri::HTML.parse(http.response)

			bitcoin_row = canadianbitcoins.at("#ticker > table > tbody > tr")
			raise "Bitcoin row has moved" unless bitcoin_row.at("td").text == "Bitcoin"

			BigDecimal.new(
				bitcoin_row.at("td:nth-of-type(3)").text.match(/^\$(\d+\.\d+)/)[1]
			)
		end
	end

	def usd
		EMPromise.all([cad, cad_to_usd]).then { |(a, b)| a * b }
	end

protected

	def fetch_canadianbitcoins
		EM::HttpRequest.new(
			"https://www.canadianbitcoins.com",
			tls: { verify_peer: true }
		).get
	end

	def cad_to_usd
		@redis.get("cad_to_usd").then do |rate|
			next rate.to_f if rate

			EM.promise_defer {
				# OXR gem is not async, so defer to threadpool
				oxr.update_rates
				oxr.get_rate("CAD", "USD")
			}.then do |orate|
				@redis.set("cad_to_usd", orate, ex: 60 * 60).then { orate }
			end
		end
	end
end

A test/test_btc_sell_prices.rb => test/test_btc_sell_prices.rb +31 -0
@@ 0,0 1,31 @@
# frozen_string_literal: true

require "em-hiredis"
require "test_helper"
require "btc_sell_prices"

class BTCSellPricesTest < Minitest::Test
	def setup
		@redis = Minitest::Mock.new
		@subject = BTCSellPrices.new(@redis, "")
	end

	def test_cad
		stub_request(:get, "https://www.canadianbitcoins.com").to_return(
			body: "<div id='ticker'><table><tbody><tr>" \
			      "<td>Bitcoin</td><td></td><td>$123.00</td>"
		)
		assert_equal BigDecimal.new(123), @subject.cad.sync
	end
	em :test_cad

	def test_usd
		stub_request(:get, "https://www.canadianbitcoins.com").to_return(
			body: "<div id='ticker'><table><tbody><tr>" \
			      "<td>Bitcoin<td></td><td>$123.00</td>"
		)
		@redis.expect(:get, EMPromise.resolve("0.5"), ["cad_to_usd"])
		assert_equal BigDecimal.new(123) / 2, @subject.usd.sync
	end
	em :test_usd
end