currency-rate 1.2.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +2 -1
  3. data/README.md +8 -0
  4. data/Rakefile +29 -0
  5. data/VERSION +1 -1
  6. data/api_keys.yml.sample +3 -0
  7. data/bin/rake +29 -0
  8. data/bin/rspec +29 -0
  9. data/currency-rate.gemspec +46 -35
  10. data/lib/adapter.rb +4 -3
  11. data/lib/adapters/crypto/binance_adapter.rb +42 -0
  12. data/lib/adapters/crypto/bitfinex_adapter.rb +29 -6
  13. data/lib/adapters/crypto/bitpay_adapter.rb +17 -2
  14. data/lib/adapters/crypto/bitstamp_adapter.rb +9 -8
  15. data/lib/adapters/crypto/coinbase_adapter.rb +20 -4
  16. data/lib/adapters/crypto/exmo_adapter.rb +27 -0
  17. data/lib/adapters/crypto/kraken_adapter.rb +22 -11
  18. data/lib/adapters/crypto/localbitcoins_adapter.rb +13 -2
  19. data/lib/adapters/fiat/currency_layer_adapter.rb +14 -12
  20. data/lib/adapters/fiat/fixer_adapter.rb +4 -2
  21. data/lib/adapters/fiat/forge_adapter.rb +5 -3
  22. data/lib/adapters/fiat/free_forex_adapter.rb +32 -0
  23. data/lib/configuration.rb +12 -8
  24. data/lib/currency_rate.rb +16 -7
  25. data/lib/fetcher.rb +28 -41
  26. data/lib/storage/file_storage.rb +2 -2
  27. data/lib/synchronizer.rb +31 -17
  28. data/spec/fixtures/adapters/binance_rates.yml +993 -0
  29. data/spec/fixtures/adapters/bitfinex_rates.yml +4050 -0
  30. data/spec/fixtures/adapters/{bitpay_adapter.yml → bitpay_rates.yml} +179 -175
  31. data/spec/fixtures/adapters/bitstamp_rates.yml +61 -0
  32. data/spec/fixtures/adapters/{btc_china_adapter.yml → btc_china_rates.yml} +0 -0
  33. data/spec/fixtures/adapters/{btce_adapter.yml → btce_rates.yml} +0 -0
  34. data/spec/fixtures/adapters/coinbase_rates.yml +181 -0
  35. data/spec/fixtures/adapters/{currency_layer_adapter.yml → currency_layer_rates.yml} +0 -0
  36. data/spec/fixtures/adapters/exmo_rates.yml +1251 -0
  37. data/spec/fixtures/adapters/fixer_rates.yml +174 -0
  38. data/spec/fixtures/adapters/{forge_adapter.yml → forge_rates.yml} +0 -0
  39. data/spec/fixtures/adapters/free_forex_rates.yml +59 -0
  40. data/spec/fixtures/adapters/{huobi_adapter.yml → huobi_rates.yml} +0 -0
  41. data/spec/fixtures/adapters/kraken_rates.yml +507 -0
  42. data/spec/fixtures/adapters/localbitcoins_rates.yml +493 -0
  43. data/spec/fixtures/adapters/normalized/binance_rates.yml +134 -0
  44. data/spec/fixtures/adapters/normalized/bitfinex_rates.yml +95 -0
  45. data/spec/fixtures/adapters/normalized/bitpay_rates.yml +164 -0
  46. data/spec/fixtures/adapters/normalized/bitstamp_rates.yml +9 -0
  47. data/spec/fixtures/adapters/normalized/{btc_china_adapter.yml → btc_china_rates.yml} +0 -0
  48. data/spec/fixtures/adapters/normalized/{btce_adapter.yml → btce_rates.yml} +0 -0
  49. data/spec/fixtures/adapters/normalized/coinbase_rates.yml +179 -0
  50. data/spec/fixtures/adapters/normalized/{currency_layer_adapter.yml → currency_layer_rates.yml} +0 -0
  51. data/spec/fixtures/adapters/normalized/exmo_rates.yml +42 -0
  52. data/spec/fixtures/adapters/normalized/fixer_rates.yml +170 -0
  53. data/spec/fixtures/adapters/normalized/{forge_adapter.yml → forge_rates.yml} +0 -0
  54. data/spec/fixtures/adapters/normalized/free_forex_rates.yml +16 -0
  55. data/spec/fixtures/adapters/normalized/{huobi_adapter.yml → huobi_rates.yml} +0 -0
  56. data/spec/fixtures/adapters/normalized/kraken_rates.yml +21 -0
  57. data/spec/fixtures/adapters/normalized/localbitcoins_rates.yml +73 -0
  58. data/spec/fixtures/adapters/normalized/{okcoin_adapter.yml → okcoin_rates.yml} +0 -0
  59. data/spec/fixtures/adapters/normalized/{yahoo_adapter.yml → yahoo_rates.yml} +0 -0
  60. data/spec/fixtures/adapters/{okcoin_adapter.yml → okcoin_rates.yml} +0 -0
  61. data/spec/fixtures/adapters/{yahoo_adapter.yml → yahoo_rates.yml} +0 -0
  62. data/spec/lib/adapters/crypto/binance_adapter_spec.rb +13 -0
  63. data/spec/lib/adapters/crypto/exmo_adapter_spec.rb +13 -0
  64. data/spec/lib/adapters/fiat/free_forex_adapter_spec.rb +13 -0
  65. data/spec/lib/fetcher_spec.rb +17 -49
  66. data/spec/lib/synchronizer_spec.rb +36 -2
  67. data/spec/spec_helper.rb +2 -2
  68. metadata +66 -35
  69. data/spec/fixtures/adapters/bitfinex_adapter.yml +0 -18
  70. data/spec/fixtures/adapters/bitstamp_adapter.yml +0 -30
  71. data/spec/fixtures/adapters/coinbase_adapter.yml +0 -680
  72. data/spec/fixtures/adapters/fixer_adapter.yml +0 -34
  73. data/spec/fixtures/adapters/kraken_adapter.yml +0 -124
  74. data/spec/fixtures/adapters/localbitcoins_adapter.yml +0 -381
  75. data/spec/fixtures/adapters/normalized/bitfinex_adapter.yml +0 -2
  76. data/spec/fixtures/adapters/normalized/bitpay_adapter.yml +0 -161
  77. data/spec/fixtures/adapters/normalized/bitstamp_adapter.yml +0 -3
  78. data/spec/fixtures/adapters/normalized/coinbase_adapter.yml +0 -680
  79. data/spec/fixtures/adapters/normalized/fixer_adapter.yml +0 -32
  80. data/spec/fixtures/adapters/normalized/kraken_adapter.yml +0 -4
  81. data/spec/fixtures/adapters/normalized/localbitcoins_adapter.yml +0 -57
@@ -1,11 +1,27 @@
1
1
  module CurrencyRate
2
2
  class CoinbaseAdapter < Adapter
3
- FETCH_URL = 'https://coinbase.com/api/v1/currencies/exchange_rates'
3
+ # No need to use it for fetching, just additional information about supported currencies
4
+ SUPPORTED_CURRENCIES = %w(
5
+ AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BAT BBD BCH BDT BGN BHD BIF BMD
6
+ BND BOB BRL BSD BTC BTN BWP BYN BYR BZD CAD CDF CHF CLF CLP CNH CNY COP CRC
7
+ CUC CVE CZK DJF DKK DOP DZD EEK EGP ERN ETB ETC ETH EUR FJD FKP GBP GEL GGP
8
+ GHS GIP GMD GNF GTQ GYD HKD HNL HRK HTG HUF IDR ILS IMP INR IQD ISK JEP JMD
9
+ JOD JPY KES KGS KHR KMF KRW KWD KYD KZT LAK LBP LKR LRD LSL LTC LTL LVL LYD
10
+ MAD MDL MGA MKD MMK MNT MOP MRO MTL MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK
11
+ NPR NZD OMR PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD SCR SEK
12
+ SGD SHP SLL SOS SRD SSP STD SVC SZL THB TJS TMT TND TOP TRY TTD TWD TZS UAH
13
+ UGX USD SDC UYU UZS VEF VND VUV WST XAF XAG XAU XCD XDR XOF XPD XPF XPT YER
14
+ ZAR ZEC ZMK ZMW ZRX ZWL
15
+ )
4
16
 
5
- def normalize(data)
17
+ ANCHOR_CURRENCY = "BTC"
18
+
19
+ FETCH_URL = "https://api.coinbase.com/v2/exchange-rates?currency=#{ANCHOR_CURRENCY}"
20
+
21
+ def normalize(response)
6
22
  return nil unless super
7
- data.reduce({}) do |result, (pair, value)|
8
- result[pair.gsub("_to_", "_").upcase] = BigDecimal.new(value.to_s)
23
+ response["data"]["rates"].reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (currency, rate)|
24
+ result[currency] = BigDecimal.new(rate.to_s)
9
25
  result
10
26
  end
11
27
  end
@@ -0,0 +1,27 @@
1
+ module CurrencyRate
2
+ class ExmoAdapter < Adapter
3
+ # No need to use it for fetching, just additional information about supported currencies
4
+ SUPPORTED_CURRENCIES = %w(
5
+ ADA BCH BTC BTCZ BTG DASH DOGE DXT EOS ETC ETH EUR GAS GNT GUSD HB
6
+ HBZ INK KICK LSK LTC MNX NEO OMG PLN QTUM RUB SMART STQ TRX TRY UAH
7
+ USD USDT WAVES XEM XLM XMR XRP ZEC ZRX
8
+ )
9
+
10
+ ANCHOR_CURRENCY = "BTC"
11
+
12
+ FETCH_URL = "https://api.exmo.com/v1/ticker/"
13
+
14
+ def normalize(data)
15
+ return nil unless super
16
+ data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (key, value)|
17
+ if key.split("_")[0] == ANCHOR_CURRENCY
18
+ result[key.sub("#{self.class::ANCHOR_CURRENCY}_", "")] = BigDecimal.new(value["avg"].to_s)
19
+ elsif key.split("_")[1] == ANCHOR_CURRENCY
20
+ result[key.sub("_#{self.class::ANCHOR_CURRENCY}", "")] = 1 / BigDecimal.new(value["avg"].to_s)
21
+ end
22
+ result
23
+ end
24
+ end
25
+
26
+ end
27
+ end
@@ -1,21 +1,32 @@
1
1
  module CurrencyRate
2
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
- }
3
+ SUPPORTED_CURRENCIES = %w(
4
+ ADA BCH BSV BTC DASH EOS ETC ETH GNO LTC MLN
5
+ NMC QTUM REP USDT XDG XLM XMR XRP XTZ ZEC
6
+ )
9
7
 
10
8
  ASSET_MAP = {
11
- "BTC" => "XBT",
12
- }
9
+ "XXBTZ" => "BTC",
10
+ "XETCZ" => "ETC",
11
+ "XETHZ" => "ETH",
12
+ "XLTCZ" => "LTC",
13
+ "XREPZ" => "REP",
14
+ "XXLMZ" => "XLM",
15
+ "XXMRZ" => "XMR",
16
+ "XXRPZ" => "XRP",
17
+ "XZECZ" => "ZEC",
18
+ "USDTZ" => "USDT",
19
+ "TZUSD" => "USDT",
20
+ }
21
+
22
+ ANCHOR_CURRENCY = "USD"
23
+
24
+ FETCH_URL = "https://api.kraken.com/0/public/Ticker?pair=#{ %w(ADAUSD BCHUSD BSVUSD DASHUSD EOSUSD GNOUSD QTUMUSD XTZUSD USDTZUSD XETCZUSD XETHZUSD XLTCZUSD XREPZUSD XXLMZUSD XXMRZUSD XXRPZUSD XZECZUSD XXBTZUSD).join(",") }"
13
25
 
14
26
  def normalize(data)
15
27
  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)
28
+ data["result"].reduce({ "anchor" => ANCHOR_CURRENCY, ANCHOR_CURRENCY => BigDecimal.new("1") }) do |result, (pair, value)|
29
+ result[ta(pair.sub(ANCHOR_CURRENCY, ""))] = 1 / BigDecimal.new(value["c"].first.to_s)
19
30
  result
20
31
  end
21
32
  end
@@ -1,11 +1,22 @@
1
1
  module CurrencyRate
2
2
  class LocalbitcoinsAdapter < Adapter
3
+ # No need to use it for fetching, just additional information about supported currencies
4
+ SUPPORTED_CURRENCIES = %w(
5
+ AED AOA ARS AUD BDT BRL BYN CAD CHF CLP CNY COP CRC CZK DKK DOP EGP ETH
6
+ EUR GBP GEL GHS HKD HUF IDR ILS INR IRR JOD JPY KES KRW KWD KZT LKR LTC
7
+ MAD MWK MXN MYR NGN NOK NZD OMR PAB PEN PHP PKR PLN QAR RON RSD RUB RWF
8
+ SAR SEK SGD SZL THB TRY TWD TZS UAH UGX USD UYU VES VND XAF XMR XOF XRP
9
+ ZAR ZMW
10
+ )
11
+
12
+ ANCHOR_CURRENCY = "BTC"
13
+
3
14
  FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
4
15
 
5
16
  def normalize(data)
6
17
  return nil unless super
7
- data.reduce({}) do |result, (fiat, value)|
8
- result["BTC_#{fiat.upcase}"] = BigDecimal.new(value["rates"]["last"].to_s)
18
+ data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (fiat, value)|
19
+ result["#{fiat.upcase}"] = BigDecimal.new(value["rates"]["last"].to_s)
9
20
  result
10
21
  end
11
22
  end
@@ -1,18 +1,20 @@
1
1
  module CurrencyRate
2
2
  class CurrencyLayerAdapter < Adapter
3
3
  # No need to use it for fetching, just additional information about supported currencies
4
- SUPPORTED_CURRENCIES = %w(AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN
5
- BHD BIF BMD BND BOB BRL BSD BTC BTN BWP BYN BYR BZD CAD
6
- CDF CHF CLF CLP CNY COP CRC CUC CUP CVE CZK DJF DKK DOP
7
- DZD EGP ERN ETB EUR FJD FKP GBP GEL GGP GHS GIP GMD GNF
8
- GTQ GYD HKD HNL HRK HTG HUF IDR ILS IMP INR IQD IRR ISK
9
- JEP JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK
10
- LBP LKR LRD LSL LTL LVL LYD MAD MDL MGA MKD MMK MNT MOP
11
- MRO MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK NPR NZD OMR
12
- PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD
13
- SCR SDG SEK SGD SHP SLL SOS SRD STD SVC SYP SZL THB TJS
14
- TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VEF VND
15
- VUV WST XAF XAG XAU XCD XDR XOF XPF YER ZAR ZMK ZMW ZWL)
4
+ SUPPORTED_CURRENCIES = %w(
5
+ AED AFN ALL AMD ANG AOA ARS AUD AWG AZN BAM BBD BDT BGN
6
+ BHD BIF BMD BND BOB BRL BSD BTC BTN BWP BYN BYR BZD CAD
7
+ CDF CHF CLF CLP CNY COP CRC CUC CUP CVE CZK DJF DKK DOP
8
+ DZD EGP ERN ETB EUR FJD FKP GBP GEL GGP GHS GIP GMD GNF
9
+ GTQ GYD HKD HNL HRK HTG HUF IDR ILS IMP INR IQD IRR ISK
10
+ JEP JMD JOD JPY KES KGS KHR KMF KPW KRW KWD KYD KZT LAK
11
+ LBP LKR LRD LSL LTL LVL LYD MAD MDL MGA MKD MMK MNT MOP
12
+ MRO MUR MVR MWK MXN MYR MZN NAD NGN NIO NOK NPR NZD OMR
13
+ PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF SAR SBD
14
+ SCR SDG SEK SGD SHP SLL SOS SRD STD SVC SYP SZL THB TJS
15
+ TMT TND TOP TRY TTD TWD TZS UAH UGX USD UYU UZS VEF VND
16
+ VUV WST XAF XAG XAU XCD XDR XOF XPF YER ZAR ZMK ZMW ZWL
17
+ )
16
18
 
17
19
  ANCHOR_CURRENCY = "USD"
18
20
  FETCH_URL = "http://www.apilayer.net/api/live"
@@ -1,7 +1,9 @@
1
1
  module CurrencyRate
2
2
  class FixerAdapter < Adapter
3
- ANCHOR_CURRENCY = "USD"
4
- FETCH_URL = "http://api.fixer.io/latest?base=usd"
3
+ # EUR is the only currency available as a base on free plan
4
+ ANCHOR_CURRENCY = "EUR"
5
+ FETCH_URL = "http://data.fixer.io/latest"
6
+ API_KEY_PARAM = "access_key"
5
7
 
6
8
  def normalize(data)
7
9
  return nil unless super
@@ -1,9 +1,11 @@
1
1
  module CurrencyRate
2
2
  class ForgeAdapter < Adapter
3
- SUPPORTED_CURRENCIES = %w(JPY CHF CAD SEK NOK MXN ZAR TRY CNH EUR GBP AUD NZD XAU XAG)
3
+ SUPPORTED_CURRENCIES = %w(
4
+ JPY CHF CAD SEK NOK MXN ZAR TRY CNH EUR GBP AUD NZD XAU XAG
5
+ )
6
+
4
7
  ANCHOR_CURRENCY = "USD"
5
- FETCH_URL = "https://forex.1forge.com/1.0.2/quotes?pairs=" +
6
- SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}#{c}" }.join(",")
8
+ FETCH_URL = "https://forex.1forge.com/1.0.2/quotes?pairs=" + SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}#{c}" }.join(",")
7
9
  API_KEY_PARAM = "api_key"
8
10
 
9
11
  def normalize(data)
@@ -0,0 +1,32 @@
1
+ module CurrencyRate
2
+ class FreeForexAdapter < Adapter
3
+ SUPPORTED_CURRENCIES = %w(
4
+ AED AFN ALL AMD ANG AOA ARS ATS AUD AWG AZM AZN BAM BBD BDT
5
+ BEF BGN BHD BIF BMD BND BOB BRL BSD BTN BWP BYN BYR BZD CAD
6
+ CDF CLP CNH CNY COP CRC CUC CUP CVE CYP CZK DEM DJF DKK DOP
7
+ DZD EEK EGP ERN ESP ETB EUR FIM FJD FKP FRF GBP GEL GGP GHC
8
+ GHS GIP GMD GNF GRD GTQ GYD HKD HNL HRK HTG HUF IDR IEP ILS
9
+ IMP INR IQD IRR ISK ITL JEP JMD JOD KES KGS KHR KMF KPW KRW
10
+ KWD KYD KZT LAK LBP LKR LRD LSL LTL LUF LVL LYD MAD MDL MGA
11
+ MGF MKD MMK MNT MOP MRO MRU MTL MUR MVR MWK MXN MYR MZM MZN
12
+ NAD NGN NIO NLG NOK NPR NZD OMR PAB PEN PGK PHP PKR PLN PTE
13
+ PYG QAR ROL RON RSD RUB RWF SAR SBD SCR SDD SDG SEK SGD SHP
14
+ SIT SKK SLL SOS SPL SRD SRG STD STN SVC SYP SZL THB TJS TMM
15
+ TMT TND TOP TRL TRY TTD TVD TWD TZS UAH UGX UYU UZS VAL VEB
16
+ VEF VES VND VUV WST XAF XAG XAU XBT XCD XDR XOF XPD XPF XPT
17
+ YER ZAR ZMK ZMW ZWD
18
+ )
19
+
20
+ ANCHOR_CURRENCY = "USD"
21
+ FETCH_URL = "https://www.freeforexapi.com/api/live?pairs=" + SUPPORTED_CURRENCIES.map{|cur| "USD#{cur}"}.join(",")
22
+
23
+ def normalize(data)
24
+ return nil unless super
25
+ rates = { "anchor" => self.class::ANCHOR_CURRENCY }
26
+ data["rates"].each do |pair, payload|
27
+ rates[pair.sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal.new(payload["rate"].to_s)
28
+ end
29
+ rates
30
+ end
31
+ end
32
+ end
data/lib/configuration.rb CHANGED
@@ -5,17 +5,21 @@ module CurrencyRate
5
5
  attr_accessor :logger
6
6
  attr_accessor :crypto_adapters
7
7
  attr_accessor :fiat_adapters
8
+ attr_accessor :connect_timeout
9
+ attr_accessor :read_timeout
8
10
 
9
11
  def initialize
10
- @api_keys = { }
11
- @file_storage = { path: "" }
12
- @logger = {
13
- device: $stdout,
14
- level: :info,
15
- formatter: nil,
16
- }
12
+ @api_keys = { }
13
+ @file_storage = { path: "" }
14
+ @logger = {
15
+ device: $stdout,
16
+ level: :info,
17
+ formatter: nil,
18
+ }
17
19
  @crypto_adapters = CurrencyRate.adapters :crypto
18
- @fiat_adapters = CurrencyRate.adapters :fiat
20
+ @fiat_adapters = CurrencyRate.adapters :fiat
21
+ @connect_timeout = 4
22
+ @read_timeout = 4
19
23
  end
20
24
  end
21
25
  end
data/lib/currency_rate.rb CHANGED
@@ -4,12 +4,13 @@ require "singleton"
4
4
  require "json"
5
5
  require "http"
6
6
 
7
- require "configuration"
8
- require "adapter"
9
- require "fetcher"
10
- require "synchronizer"
7
+ require_relative "configuration"
8
+ require_relative "adapter"
9
+ require_relative "fetcher"
10
+ require_relative "synchronizer"
11
11
 
12
- Dir["#{File.expand_path File.dirname(__FILE__)}/**/*.rb"].each { |f| require f }
12
+ Dir["#{File.expand_path __dir__}/adapters/**/*.rb"].each { |f| require f }
13
+ Dir["#{File.expand_path __dir__}/storage/**/*.rb"].each { |f| require f }
13
14
 
14
15
  module CurrencyRate
15
16
  class << self
@@ -26,7 +27,7 @@ module CurrencyRate
26
27
 
27
28
  def self.adapters(type)
28
29
  Dir[File.join self.root, "lib/adapters/#{type}/*"].map do |file|
29
- File.basename(file, ".rb").split('_').map {|w| w.capitalize}.join
30
+ File.basename(file, ".rb").split('_')[0...-1].map {|w| w.capitalize}.join
30
31
  end
31
32
  end
32
33
 
@@ -39,7 +40,7 @@ module CurrencyRate
39
40
  end
40
41
 
41
42
  def self.fetcher
42
- @fetcher ||= Fetcher.new
43
+ @fetcher ||= Fetcher.new(fiat_exchanges: configuration.fiat_adapters)
43
44
  end
44
45
 
45
46
  def self.fetch_crypto(exchange, from, to)
@@ -63,6 +64,14 @@ module CurrencyRate
63
64
  @synchronizer ||= Synchronizer.new
64
65
  end
65
66
 
67
+ def self.sync_crypto!
68
+ synchronizer.sync_crypto!
69
+ end
70
+
71
+ def self.sync_fiat!
72
+ synchronizer.sync_fiat!
73
+ end
74
+
66
75
  def self.sync!
67
76
  synchronizer.sync!
68
77
  end
data/lib/fetcher.rb CHANGED
@@ -3,69 +3,56 @@ module CurrencyRate
3
3
  attr_accessor :storage
4
4
  attr_accessor :fiat_exchanges
5
5
 
6
- def initialize(storage: nil)
6
+ def initialize(fiat_exchanges: nil, storage: nil)
7
7
  @storage = storage || FileStorage.new
8
- @fiat_exchanges = ["Yahoo", "Fixer", "Forge"]
8
+ @fiat_exchanges = fiat_exchanges || ["Yahoo", "Fixer", "Forge"]
9
9
  end
10
10
 
11
11
  def fetch_crypto(exchange, from, to)
12
- crypto = from.upcase
13
- fiat = to.upcase
14
- pair = "#{from}_#{to}"
12
+ from = from.strip.upcase
13
+ to = to.strip.upcase
15
14
  rates = @storage.read(exchange)
16
15
 
17
16
  if rates.nil?
18
17
  CurrencyRate.logger.warn("Fetcher#fetch_crypto: rates for #{exchange} not found in storage <#{@storage.class.name}>")
19
18
  return nil
20
19
  end
21
- return BigDecimal.new(rates[pair]) if rates[pair]
22
20
 
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 }
21
+ rate = calculate_rate(rates, from, to)
22
+ return rate unless rate.nil?
29
23
 
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
24
+ if to != "USD"
25
+ usd_fiat = fetch_fiat("USD", to)
26
+ return BigDecimal.new(rates["USD"] * usd_fiat) if usd_fiat && rates["USD"]
47
27
  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
28
  end
53
29
 
54
30
  def fetch_fiat(from, to)
31
+ from = from.strip.upcase
32
+ to = to.strip.upcase
33
+
55
34
  @fiat_exchanges.each do |exchange|
56
- left = from.upcase
57
- right = to.upcase
58
35
  rates = @storage.read(exchange)
59
36
  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]
37
+
38
+ rate = calculate_rate(rates, from, to)
39
+ return rate unless rate.nil?
64
40
  end
65
- CurrencyRate.logger.warn("Fetcher#fetch_fiat: rate for #{from}_#{to} not found")
66
- nil
67
41
  end
68
42
 
43
+ private
44
+
45
+ def calculate_rate(rates, from, to)
46
+ anchor = rates.delete("anchor")
47
+
48
+ return BigDecimal.new(rates[to]) if anchor == from && rates[to]
49
+ return BigDecimal.new(1 / rates[from]) if anchor == to && rates[from]
50
+ return BigDecimal.new(rates[to] / rates[from]) if rates[from] && rates[to]
51
+
52
+ CurrencyRate.logger.warn("Fetcher: rate for #{from}_#{to} not found.")
53
+ nil
54
+ end
55
+
69
56
  end
70
57
  end
71
58
 
@@ -4,8 +4,8 @@ module CurrencyRate
4
4
  attr_accessor :serializer
5
5
 
6
6
  def initialize(path = nil, serializer: nil)
7
- @path ||= CurrencyRate.configuration.file_storage[:path]
8
- @serializer ||= Storage::YAMLSerializer.new
7
+ @path = path || CurrencyRate.configuration.file_storage[:path]
8
+ @serializer = serializer || Storage::YAMLSerializer.new
9
9
  end
10
10
 
11
11
  def read(exchange_name)
data/lib/synchronizer.rb CHANGED
@@ -6,28 +6,42 @@ module CurrencyRate
6
6
  @storage = storage || FileStorage.new
7
7
  end
8
8
 
9
+ def sync_fiat!
10
+ _sync CurrencyRate.configuration.fiat_adapters
11
+ end
12
+
13
+ def sync_crypto!
14
+ _sync CurrencyRate.configuration.crypto_adapters
15
+ end
16
+
9
17
  def sync!
18
+ fiat = sync_fiat!
19
+ crypto = sync_crypto!
20
+ [fiat[0] | crypto[0], fiat[1] | crypto[1]]
21
+ end
22
+
23
+ private
24
+
25
+ def _sync(adapters)
10
26
  successfull = []
11
27
  failed = []
12
- [CurrencyRate.configuration.fiat_adapters, CurrencyRate.configuration.crypto_adapters].each do |adapters|
13
- adapters.each do |adapter_name|
14
- adapter_name = "#{adapter_name}Adapter" unless adapter_name.include? "Adapter"
15
- begin
16
- adapter = CurrencyRate::const_get(adapter_name).instance
17
- rates = adapter.fetch_rates
18
- unless rates
19
- CurrencyRate.logger.warn("Synchronizer#sync!: rates for #{adapter_name} not found")
20
- failed.push(adapter_name)
21
- next
22
- end
23
- exchange_name = adapter_name[0..-8].downcase
24
- @storage.write(exchange_name, rates)
25
- successfull.push(adapter_name)
26
- rescue StandardError => e
27
- failed.push(adapter_name)
28
- CurrencyRate.logger.error(e)
28
+ adapters.each do |provider|
29
+ adapter_name = "#{provider}Adapter"
30
+ begin
31
+ adapter = CurrencyRate::const_get(adapter_name).instance
32
+ rates = adapter.fetch_rates
33
+ unless rates
34
+ CurrencyRate.logger.warn("Synchronizer#sync!: rates for #{provider} not found")
35
+ failed.push(provider)
29
36
  next
30
37
  end
38
+ exchange_name = provider.downcase
39
+ @storage.write(exchange_name, rates)
40
+ successfull.push(provider)
41
+ rescue StandardError => e
42
+ failed.push(provider)
43
+ CurrencyRate.logger.error(e)
44
+ next
31
45
  end
32
46
  end
33
47
  [successfull, failed]