currency-rate 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -4
  3. data/README.md +41 -53
  4. data/VERSION +1 -1
  5. data/currency-rate.gemspec +92 -78
  6. data/lib/adapter.rb +37 -83
  7. data/lib/adapters/crypto/bitfinex_adapter.rb +17 -0
  8. data/lib/adapters/crypto/bitpay_adapter.rb +14 -0
  9. data/lib/adapters/crypto/bitstamp_adapter.rb +19 -0
  10. data/lib/adapters/crypto/btc_china_adapter.rb +11 -0
  11. data/lib/adapters/crypto/btc_e_adapter.rb +18 -0
  12. data/lib/adapters/crypto/coinbase_adapter.rb +14 -0
  13. data/lib/adapters/crypto/huobi_adapter.rb +17 -0
  14. data/lib/adapters/crypto/kraken_adapter.rb +29 -0
  15. data/lib/adapters/crypto/localbitcoins_adapter.rb +14 -0
  16. data/lib/adapters/crypto/okcoin_adapter.rb +19 -0
  17. data/lib/adapters/fiat/fixer_adapter.rb +16 -0
  18. data/lib/adapters/fiat/forge_adapter.rb +23 -0
  19. data/lib/adapters/fiat/yahoo_adapter.rb +35 -0
  20. data/lib/configuration.rb +21 -0
  21. data/lib/currency_rate.rb +52 -48
  22. data/lib/fetcher.rb +71 -0
  23. data/lib/storage/file_storage.rb +29 -0
  24. data/lib/storage/serializers/yaml_serializer.rb +15 -0
  25. data/lib/synchronizer.rb +31 -0
  26. data/spec/fixtures/adapters/bitfinex_adapter.yml +18 -0
  27. data/spec/fixtures/adapters/bitpay_adapter.yml +486 -0
  28. data/spec/fixtures/adapters/bitstamp_adapter.yml +30 -0
  29. data/spec/fixtures/adapters/btc_china_adapter.yml +11 -0
  30. data/spec/fixtures/adapters/btce_adapter.yml +60 -0
  31. data/spec/fixtures/adapters/coinbase_adapter.yml +680 -0
  32. data/spec/fixtures/adapters/fixer_adapter.yml +34 -0
  33. data/spec/fixtures/adapters/forge_adapter.yml +75 -0
  34. data/spec/fixtures/adapters/huobi_adapter.yml +22 -0
  35. data/spec/fixtures/adapters/kraken_adapter.yml +124 -0
  36. data/spec/fixtures/adapters/localbitcoins_adapter.yml +381 -0
  37. data/spec/fixtures/adapters/normalized/bitfinex_adapter.yml +2 -0
  38. data/spec/fixtures/adapters/normalized/bitpay_adapter.yml +161 -0
  39. data/spec/fixtures/adapters/normalized/bitstamp_adapter.yml +3 -0
  40. data/spec/fixtures/adapters/normalized/btc_china_adapter.yml +1 -0
  41. data/spec/fixtures/adapters/normalized/btce_adapter.yml +5 -0
  42. data/spec/fixtures/adapters/normalized/coinbase_adapter.yml +680 -0
  43. data/spec/fixtures/adapters/normalized/fixer_adapter.yml +32 -0
  44. data/spec/fixtures/adapters/normalized/forge_adapter.yml +16 -0
  45. data/spec/fixtures/adapters/normalized/huobi_adapter.yml +2 -0
  46. data/spec/fixtures/adapters/normalized/kraken_adapter.yml +4 -0
  47. data/spec/fixtures/adapters/normalized/localbitcoins_adapter.yml +57 -0
  48. data/spec/fixtures/adapters/normalized/okcoin_adapter.yml +4 -0
  49. data/spec/fixtures/adapters/normalized/yahoo_adapter.yml +160 -0
  50. data/spec/fixtures/adapters/okcoin_adapter.yml +40 -0
  51. data/spec/fixtures/adapters/yahoo_adapter.yml +1119 -0
  52. data/spec/lib/adapter_spec.rb +54 -0
  53. data/spec/lib/adapters/crypto/bitfinex_adapter_spec.rb +13 -0
  54. data/spec/lib/adapters/crypto/bitpay_adapter_spec.rb +13 -0
  55. data/spec/lib/adapters/crypto/bitstamp_adapter_spec.rb +13 -0
  56. data/spec/lib/adapters/crypto/btc_china_adapter_spec.rb +13 -0
  57. data/spec/lib/adapters/crypto/btc_e_adapter_spec.rb +13 -0
  58. data/spec/lib/adapters/crypto/coinbase_adapter_spec.rb +13 -0
  59. data/spec/lib/adapters/crypto/huobi_adapter_spec.rb +13 -0
  60. data/spec/lib/adapters/crypto/kraken_adapter_spec.rb +13 -0
  61. data/spec/lib/adapters/crypto/localbitcoins_adapter_spec.rb +13 -0
  62. data/spec/lib/adapters/crypto/okcoin_adapter_spec.rb +13 -0
  63. data/spec/lib/adapters/fiat/fixer_adapter_spec.rb +13 -0
  64. data/spec/lib/adapters/fiat/forge_adapter_spec.rb +23 -0
  65. data/spec/lib/adapters/fiat/yahoo_adapter_spec.rb +13 -0
  66. data/spec/lib/currency_rate_spec.rb +9 -0
  67. data/spec/lib/fetcher_spec.rb +156 -0
  68. data/spec/lib/storage/file_storage_spec.rb +38 -0
  69. data/spec/lib/synchronizer_spec.rb +65 -0
  70. data/spec/spec_helper.rb +23 -8
  71. data/spec/support/matchers/eq_any_of.rb +3 -0
  72. metadata +67 -64
  73. data/Gemfile.lock +0 -93
  74. data/lib/core_ext/deep_get.rb +0 -11
  75. data/lib/crypto_adapter.rb +0 -19
  76. data/lib/crypto_adapters/average_rate_adapter.rb +0 -54
  77. data/lib/crypto_adapters/bitfinex_adapter.rb +0 -40
  78. data/lib/crypto_adapters/bitpay_adapter.rb +0 -33
  79. data/lib/crypto_adapters/bitstamp_adapter.rb +0 -44
  80. data/lib/crypto_adapters/btcchina_adapter.rb +0 -19
  81. data/lib/crypto_adapters/btce_adapter.rb +0 -44
  82. data/lib/crypto_adapters/coinbase_adapter.rb +0 -23
  83. data/lib/crypto_adapters/huobi_adapter.rb +0 -40
  84. data/lib/crypto_adapters/kraken_adapter.rb +0 -42
  85. data/lib/crypto_adapters/localbitcoins_adapter.rb +0 -30
  86. data/lib/crypto_adapters/okcoin_adapter.rb +0 -43
  87. data/lib/fiat_adapter.rb +0 -17
  88. data/lib/fiat_adapters/fixer_adapter.rb +0 -38
  89. data/lib/fiat_adapters/yahoo_adapter.rb +0 -51
  90. data/lib/storage.rb +0 -20
  91. data/spec/currency_rate_spec.rb +0 -52
  92. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/average_rate_adapter.yml +0 -567
  93. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/bitfinex_adapter.yml +0 -121
  94. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/bitpay_adapter.yml +0 -272
  95. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/btcchina_adapter.yml +0 -55
  96. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/btce_adapter.yml +0 -223
  97. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/coinbase_adapter.yml +0 -96
  98. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/huobi_adapter.yml +0 -83
  99. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/kraken_adapter.yml +0 -179
  100. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/localbitcoins_adapter.yml +0 -133
  101. data/spec/fixtures/vcr/exchange_rate_adapters/btc_adapters/okcoin_adapter.yml +0 -223
  102. data/spec/fixtures/vcr/exchange_rate_adapters/fiat_adapters/fixer_adapter.yml +0 -85
  103. data/spec/fixtures/vcr/exchange_rate_adapters/fiat_adapters/yahoo_adapter.yml +0 -48
  104. data/spec/lib/crypto_adapter_spec.rb +0 -56
  105. data/spec/lib/crypto_adapters/average_rate_adapter_spec.rb +0 -46
  106. data/spec/lib/crypto_adapters/bitfinex_adapter_spec.rb +0 -38
  107. data/spec/lib/crypto_adapters/bitpay_adapter_spec.rb +0 -40
  108. data/spec/lib/crypto_adapters/bitstamp_adapter_spec.rb +0 -44
  109. data/spec/lib/crypto_adapters/btcchina_adapter_spec.rb +0 -36
  110. data/spec/lib/crypto_adapters/btce_adapter_spec.rb +0 -42
  111. data/spec/lib/crypto_adapters/coinbase_adapter_spec.rb +0 -39
  112. data/spec/lib/crypto_adapters/huobi_adapter_spec.rb +0 -38
  113. data/spec/lib/crypto_adapters/kraken_adapter_spec.rb +0 -38
  114. data/spec/lib/crypto_adapters/localbitcoins_adapter_spec.rb +0 -36
  115. data/spec/lib/crypto_adapters/okcoin_adapter_spec.rb +0 -43
  116. data/spec/lib/fiat_adapters/fixer_adapter_spec.rb +0 -25
  117. data/spec/lib/fiat_adapters/yahoo_adapter_spec.rb +0 -25
  118. data/spec/lib/storage_spec.rb +0 -32
@@ -0,0 +1,17 @@
1
+ module CurrencyRate
2
+ class BitfinexAdapter < Adapter
3
+ FETCH_URL = {
4
+ "BTC_USD" => "https://api.bitfinex.com/v1/pubticker/btcusd",
5
+ "LTC_USD" => "https://api.bitfinex.com/v1/pubticker/ltcusd",
6
+ }
7
+
8
+ def normalize(data)
9
+ return nil unless super
10
+ data.reduce({}) do |result, (pair, value)|
11
+ result[pair] = BigDecimal.new(value["last_price"].to_s)
12
+ result
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module CurrencyRate
2
+ class BitpayAdapter < Adapter
3
+ FETCH_URL = "https://bitpay.com/api/rates"
4
+
5
+ def normalize(data)
6
+ return nil unless super
7
+ data.reject { |rate| rate["code"] == "BTC" }.reduce({}) do |result, rate|
8
+ result["BTC_#{rate['code'].upcase}"] = BigDecimal.new(rate["rate"].to_s)
9
+ result
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module CurrencyRate
2
+ class BitstampAdapter < Adapter
3
+ FETCH_URL = {
4
+ 'BTC_USD' => 'https://www.bitstamp.net/api/v2/ticker/btcusd/',
5
+ 'BTC_EUR' => 'https://www.bitstamp.net/api/v2/ticker/btceur/',
6
+ 'LTC_USD' => 'https://www.bitstamp.net/api/v2/ticker/ltcusd/',
7
+ 'LTC_EUR' => 'https://www.bitstamp.net/api/v2/ticker/ltceur/',
8
+ }
9
+
10
+ def normalize(data)
11
+ return nil unless super
12
+ data.reduce({}) do |result, (pair, value)|
13
+ result[pair] = BigDecimal.new(value["last"].to_s)
14
+ result
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ module CurrencyRate
2
+ class BtcChinaAdapter < Adapter
3
+ FETCH_URL = 'https://data.btcchina.com/data/ticker'
4
+
5
+ def normalize(data)
6
+ return nil unless super
7
+ { "BTC_CNY" => BigDecimal.new(data["ticker"]["last"].to_s) }
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module CurrencyRate
2
+ class BtcEAdapter < Adapter
3
+ FETCH_URL = {
4
+ 'BTC_USD' => 'https://wex.nz/api/2/btc_usd/ticker',
5
+ 'BTC_EUR' => 'https://wex.nz/api/2/btc_eur/ticker',
6
+ 'BTC_RUB' => 'https://wex.nz/api/2/btc_rur/ticker',
7
+ }
8
+
9
+ def normalize(data)
10
+ return nil unless super
11
+ data.reduce({}) do |result, (pair, value)|
12
+ result[pair] = BigDecimal.new(value["ticker"]["last"].to_s)
13
+ result
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module CurrencyRate
2
+ class CoinbaseAdapter < Adapter
3
+ FETCH_URL = 'https://coinbase.com/api/v1/currencies/exchange_rates'
4
+
5
+ def normalize(data)
6
+ return nil unless super
7
+ data.reduce({}) do |result, (pair, value)|
8
+ result[pair.gsub("_to_", "_").upcase] = BigDecimal.new(value.to_s)
9
+ result
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module CurrencyRate
2
+ class HuobiAdapter < Adapter
3
+ FETCH_URL = {
4
+ "BTC_CNY" => "http://api.huobi.com/staticmarket/ticker_btc_json.js",
5
+ "LTC_CNY" => "http://api.huobi.com/staticmarket/ticker_ltc_json.js"
6
+ }
7
+
8
+ def normalize(data)
9
+ return nil unless super
10
+ data.reduce({}) do |result, (pair, value)|
11
+ result[pair] = BigDecimal.new(value["ticker"]["last"].to_s)
12
+ result
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ module CurrencyRate
2
+ class KrakenAdapter < Adapter
3
+ FETCH_URL = {
4
+ "BTC_USD" => "https://api.kraken.com/0/public/Ticker?pair=xbtusd",
5
+ "BTC_EUR" => "https://api.kraken.com/0/public/Ticker?pair=xbteur",
6
+ "LTC_USD" => "https://api.kraken.com/0/public/Ticker?pair=ltcusd",
7
+ "LTC_EUR" => "https://api.kraken.com/0/public/Ticker?pair=ltceur",
8
+ }
9
+
10
+ ASSET_MAP = {
11
+ "BTC" => "XBT",
12
+ }
13
+
14
+ def normalize(data)
15
+ return nil unless super
16
+ data.reduce({}) do |result, (pair, value)|
17
+ crypto, fiat = pair.split("_")
18
+ result[pair] = BigDecimal.new(value["result"]["X#{ta(crypto)}Z#{ta(fiat)}"]["c"].first.to_s)
19
+ result
20
+ end
21
+ end
22
+
23
+ def translate_asset(asset)
24
+ ASSET_MAP[asset] || asset
25
+ end
26
+ alias_method :ta, :translate_asset
27
+
28
+ end
29
+ end
@@ -0,0 +1,14 @@
1
+ module CurrencyRate
2
+ class LocalbitcoinsAdapter < Adapter
3
+ FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
4
+
5
+ def normalize(data)
6
+ return nil unless super
7
+ data.reduce({}) do |result, (fiat, value)|
8
+ result["BTC_#{fiat.upcase}"] = BigDecimal.new(value["rates"]["last"].to_s)
9
+ result
10
+ end
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ module CurrencyRate
2
+ class OkcoinAdapter < Adapter
3
+ FETCH_URL = {
4
+ 'LTC_USD' => 'https://www.okcoin.com/api/v1/ticker.do?symbol=ltc_usd',
5
+ 'BTC_USD' => 'https://www.okcoin.com/api/v1/ticker.do?symbol=btc_usd',
6
+ 'LTC_CNY' => 'https://www.okcoin.cn/api/ticker.do?symbol=ltc_cny',
7
+ 'BTC_CNY' => 'https://www.okcoin.cn/api/ticker.do?symbol=btc_cny'
8
+ }
9
+
10
+ def normalize(data)
11
+ return nil unless super
12
+ data.reduce({}) do |result, (pair, value)|
13
+ result[pair] = BigDecimal.new(value["ticker"]["last"].to_s)
14
+ result
15
+ end
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module CurrencyRate
2
+ class FixerAdapter < Adapter
3
+ ANCHOR_CURRENCY = "USD"
4
+ FETCH_URL = "http://api.fixer.io/latest?base=usd"
5
+
6
+ def normalize(data)
7
+ return nil unless super
8
+ rates = { "anchor" => ANCHOR_CURRENCY }
9
+ data["rates"].each do |k, v|
10
+ rates[k] = BigDecimal.new(v.to_s)
11
+ end
12
+ rates
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module CurrencyRate
2
+ class ForgeAdapter < Adapter
3
+ SUPPORTED_CURRENCIES = %w(JPY CHF CAD SEK NOK MXN ZAR TRY CNH EUR GBP AUD NZD XAU XAG)
4
+ ANCHOR_CURRENCY = "USD"
5
+ FETCH_URL = "https://forex.1forge.com/1.0.2/quotes?pairs=" +
6
+ SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}#{c}" }.join(",")
7
+ API_KEY_PARAM = "api_key"
8
+
9
+ def normalize(data)
10
+ return nil unless super
11
+ rates = { "anchor" => self.class::ANCHOR_CURRENCY }
12
+ data.each do |rate|
13
+ if rate["error"]
14
+ CurrencyRate.logger.error("Forge exchange returned error")
15
+ return nil
16
+ end
17
+ rates[rate["symbol"].sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal.new(rate["price"].to_s)
18
+ end
19
+ rates
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module CurrencyRate
2
+ class YahooAdapter < Adapter
3
+ SUPPORTED_CURRENCIES = %w(
4
+ AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN BHD BIF BMD BND
5
+ BRL BSD BTN BWP BYR BZD CAD CHF CLF CLP CNY COP CRC CUC CUP CVE CZK DEM
6
+ DJF DKK DOP DZD ECS EGP ERN ETB EUR FJD FKP FRF GBP GEL GHS GIP GMD GNF
7
+ GTQ GYD HKD HNL HRK HTG HUF IDR IEP ILS INR IQD ISK ITL JMD JOD JPY KES
8
+ KGS KHR KMF KWD KYD KZT LAK LBP LKR LRD LSL LTL LVL LYD MAD MGA MMK MNT
9
+ MOP MRO MUR MVR MWK MXN MXV MYR MZN NAD NGN NIO NOK NPR NZD OMR PAB PEN
10
+ PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SDG SEK SGD SLL SOS
11
+ SRD STD SVC SYP SZL THB TJS TMT TND TOP TRY TTD UAH UGX USD UYU UZS VND
12
+ VUV WST XAF XAG XAU XCD XDR XOF XPD XPF XPT YER ZAR ZMW ZWL
13
+ )
14
+
15
+ ANCHOR_CURRENCY = "USD"
16
+
17
+ FETCH_URL = "http://query.yahooapis.com/v1/public/yql?" + URI.encode_www_form(
18
+ format: 'json',
19
+ env: "store://datatables.org/alltableswithkeys",
20
+ q: "SELECT * FROM yahoo.finance.xchange WHERE pair IN" +
21
+ # The following line is building array string in SQL: '("USDJPY", "USDRUB", ...)'
22
+ "(#{SUPPORTED_CURRENCIES.map{|x| '"' + ANCHOR_CURRENCY + x.upcase + '"'}.join(',')})"
23
+ )
24
+
25
+ def normalize(data)
26
+ return nil unless super
27
+ rates = { "anchor" => self.class::ANCHOR_CURRENCY }
28
+ data["query"]["results"]["rate"].each do |rate|
29
+ rates[rate["Name"].split("/")[1]] = BigDecimal.new(rate["Rate"].to_s)
30
+ end
31
+ rates
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module CurrencyRate
2
+ class Configuration
3
+ attr_accessor :api_keys
4
+ attr_accessor :file_storage
5
+ attr_accessor :logger
6
+ attr_accessor :crypto_adapters
7
+ attr_accessor :fiat_adapters
8
+
9
+ def initialize
10
+ @api_keys = { }
11
+ @file_storage = { path: "" }
12
+ @logger = {
13
+ device: $stdout,
14
+ level: :info,
15
+ formatter: nil,
16
+ }
17
+ @crypto_adapters = CurrencyRate.adapters :crypto
18
+ @fiat_adapters = CurrencyRate.adapters :fiat
19
+ end
20
+ end
21
+ end
data/lib/currency_rate.rb CHANGED
@@ -1,69 +1,73 @@
1
- require 'singleton'
2
- require 'bigdecimal'
3
- require 'satoshi-unit'
4
- require 'open-uri'
5
- require 'json'
1
+ require "bigdecimal"
2
+ require "logger"
3
+ require "singleton"
4
+ require "json"
5
+ require "http"
6
6
 
7
+ require "configuration"
7
8
  require "adapter"
8
- require "crypto_adapter"
9
- require "fiat_adapter"
9
+ require "fetcher"
10
+ require "synchronizer"
10
11
 
11
12
  Dir["#{File.expand_path File.dirname(__FILE__)}/**/*.rb"].each { |f| require f }
12
13
 
13
14
  module CurrencyRate
15
+ class << self
16
+ attr_writer :configuration
17
+ end
14
18
 
15
- def self.get(adapter_name, from, to, anchor_currency: nil, try_storage_on_fetching_failed: false)
16
-
17
- a = adapter_instance(adapter_name)
18
- a.try_storage_on_fetching_failed = try_storage_on_fetching_failed
19
-
20
- # Setting default values for anchor currency depending on
21
- # which adapter type is un use.
22
- anchor_currency ||= if a.kind_of?(CryptoAdapter)
23
- a.class::SUPPORTED_CRYPTO_CURRENCIES.include?("BTC") ? "BTC" : a.class::SUPPORTED_CRYPTO_CURRENCIES.first
19
+ def self.method_missing(m, *args, &block)
20
+ if m.to_s.end_with? "_adapters"
21
+ self.send(:adapters, m[0..-10])
24
22
  else
25
- a.class::SUPPORTED_CURRENCIES.include?("USD") ? "USD" : a.class::SUPPORTED_CURRENCIES.first
26
- end if anchor_currency.nil?
23
+ super
24
+ end
25
+ end
27
26
 
28
- # None of the currencies is anchor currency?
29
- # No problem, convert the amount given into the anchor currency first.
30
- if [to, from].include?(anchor_currency)
31
- a.rate_for(from, to)
32
- else
33
- rate_from = get(adapter_name, anchor_currency, from, try_storage_on_fetching_failed: try_storage_on_fetching_failed)
34
- rate_to = get(adapter_name, anchor_currency, to , try_storage_on_fetching_failed: try_storage_on_fetching_failed)
35
- BigDecimal.new(rate_to.to_s)/BigDecimal.new(rate_from.to_s)
27
+ def self.adapters(type)
28
+ Dir[File.join self.root, "lib/adapters/#{type}/*"].map do |file|
29
+ File.basename(file, ".rb").split('_').map {|w| w.capitalize}.join
36
30
  end
37
31
  end
38
32
 
39
- def self.convert(adapter_name, amount:, from:, to:, anchor_currency: nil, try_storage_on_fetching_failed: false)
40
- a = adapter_instance(adapter_name)
41
- result = BigDecimal.new(amount.to_s)*BigDecimal.new(get(a, from, to, anchor_currency: anchor_currency, try_storage_on_fetching_failed: try_storage_on_fetching_failed).to_s)
42
- result.round(a.kind_of?(CryptoAdapter) && a.class::SUPPORTED_CRYPTO_CURRENCIES.include?(to) ? a.class::DECIMAL_PRECISION : 2)
33
+ def self.configuration
34
+ @configuration ||= Configuration.new
43
35
  end
44
36
 
45
- def self.default_currencies_for(adapter_name)
46
- adapter_class(adapter_name)::DEFAULT_CURRENCIES
37
+ def self.configure
38
+ yield(configuration)
47
39
  end
48
40
 
49
- private
41
+ def self.fetcher
42
+ @fetcher ||= Fetcher.new
43
+ end
50
44
 
51
- def self.adapter_instance(s)
52
- return s unless s.kind_of?(String) # if we pass class, no need to convert
53
- adapter_class(s).instance
54
- end
45
+ def self.fetch_crypto(exchange, from, to)
46
+ fetcher.fetch_crypto(exchange, from, to)
47
+ end
55
48
 
56
- def self.adapter_class(s)
57
- classify_string("#{s}_adapter", CurrencyRate)
58
- end
49
+ def self.fetch_fiat(from, to)
50
+ fetcher.fetch_fiat(from, to)
51
+ end
59
52
 
60
- def self.classify_string(s, prefix="")
61
- s = if s !~ /[[:upper:]]/ # contains uppercase characters? Must be a class name already!
62
- s.split('_').collect(&:capitalize).join
63
- else
64
- s.sub("_adapter", "Adapter")
65
- end
66
- Kernel.const_get(prefix.to_s + "::" + s)
67
- end
53
+ def self.logger
54
+ return @logger if @logger
55
+ @logger = Logger.new(configuration.logger[:device])
56
+ @logger.progname = "CurrencyRate"
57
+ @logger.level = configuration.logger[:level]
58
+ @logger.formatter = configuration.logger[:formatter] if configuration.logger[:formatter]
59
+ @logger
60
+ end
68
61
 
62
+ def self.synchronizer
63
+ @synchronizer ||= Synchronizer.new
64
+ end
65
+
66
+ def self.sync!
67
+ synchronizer.sync!
68
+ end
69
+
70
+ def self.root
71
+ File.expand_path("../", File.dirname(__FILE__))
72
+ end
69
73
  end
data/lib/fetcher.rb ADDED
@@ -0,0 +1,71 @@
1
+ module CurrencyRate
2
+ class Fetcher
3
+ attr_accessor :storage
4
+ attr_accessor :fiat_exchanges
5
+
6
+ def initialize(storage: nil)
7
+ @storage = storage || FileStorage.new
8
+ @fiat_exchanges = ["Yahoo", "Fixer", "Forge"]
9
+ end
10
+
11
+ def fetch_crypto(exchange, from, to)
12
+ crypto = from.upcase
13
+ fiat = to.upcase
14
+ pair = "#{from}_#{to}"
15
+ rates = @storage.read(exchange)
16
+
17
+ if rates.nil?
18
+ CurrencyRate.logger.warn("Fetcher#fetch_crypto: rates for #{exchange} not found in storage <#{@storage.class.name}>")
19
+ return nil
20
+ end
21
+ return BigDecimal.new(rates[pair]) if rates[pair]
22
+
23
+ supported_crypto, supported_fiat = rates.keys.reduce([[], []]) do |result, rate|
24
+ c, f = rate.split("_")
25
+ result[0] << c
26
+ result[1] << f
27
+ result
28
+ end.map { |x| x.uniq }
29
+
30
+ unless supported_crypto.include?(crypto)
31
+ CurrencyRate.logger.warn("Fetcher#fetch_crypto: #{exchange} doesn't support #{crypto}")
32
+ return nil
33
+ end
34
+
35
+ # If requested pair not found and exchange supports requested cryptocurrency
36
+ # then we can convert using another supported fiat currency as an anchor
37
+ # USD has first priority since it's the most popular world currency
38
+ if fiat != "USD" && supported_fiat.delete("USD")
39
+ usd_fiat = fetch_fiat("USD", fiat)
40
+ return BigDecimal.new(rates["#{crypto}_USD"]) * BigDecimal.new(usd_fiat) if usd_fiat
41
+ end
42
+
43
+ # We don't have usd_fiat pair so try other supported currencies as convertation anchor
44
+ supported_fiat.each do |anchor|
45
+ anchor_fiat = fetch_fiat(anchor, fiat)
46
+ return BigDecimal.new(rates["#{crypto}_#{anchor}"]) * BigDecimal.new(anchor_fiat) if anchor_fiat
47
+ end
48
+
49
+ # We didn't find a way to fetch rate for requested pair
50
+ CurrencyRate.logger.warn("Fetcher#fetch_crypto: cannot fetch #{from}_#{to} from #{exchange}")
51
+ nil
52
+ end
53
+
54
+ def fetch_fiat(from, to)
55
+ @fiat_exchanges.each do |exchange|
56
+ left = from.upcase
57
+ right = to.upcase
58
+ rates = @storage.read(exchange)
59
+ next if rates.nil?
60
+ anchor = rates.delete("anchor")
61
+ return BigDecimal.new(rates[right]) if anchor == left && rates[right]
62
+ return BigDecimal.new(1) / BigDecimal.new(rates[left]) if anchor == right && rates[left]
63
+ return BigDecimal.new(rates[left]) / BigDecimal.new(rates[right]) if rates[left] && rates[right]
64
+ end
65
+ CurrencyRate.logger.warn("Fetcher#fetch_fiat: rate for #{from}_#{to} not found")
66
+ nil
67
+ end
68
+
69
+ end
70
+ end
71
+