currency-rate 1.5.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -3
- data/Gemfile.lock +68 -0
- data/README.md +2 -2
- data/Rakefile +12 -3
- data/currency-rate.gemspec +1 -1
- data/lib/adapter.rb +56 -22
- data/lib/adapters/crypto/binance_adapter.rb +1 -1
- data/lib/adapters/crypto/bitfinex_adapter.rb +3 -2
- data/lib/adapters/crypto/bitpay_adapter.rb +1 -1
- data/lib/adapters/crypto/bitstamp_adapter.rb +1 -1
- data/lib/adapters/crypto/coin_market_cap_adapter.rb +2 -4
- data/lib/adapters/crypto/coinbase_adapter.rb +3 -3
- data/lib/adapters/crypto/exmo_adapter.rb +1 -1
- data/lib/adapters/crypto/hit_BTC_adapter.rb +64 -0
- data/lib/adapters/crypto/huobi_adapter.rb +17 -9
- data/lib/adapters/crypto/kraken_adapter.rb +5 -5
- data/lib/adapters/crypto/localbitcoins_adapter.rb +1 -1
- data/lib/adapters/crypto/okcoin_adapter.rb +4 -6
- data/lib/adapters/crypto/paxful_adapter.rb +18 -0
- data/lib/adapters/crypto/poloniex_adapter.rb +33 -0
- data/lib/adapters/crypto/yadio_adapter.rb +2 -1
- data/lib/adapters/fiat/bonbast_adapter.rb +3 -5
- data/lib/adapters/fiat/coinmonitor_adapter.rb +4 -5
- data/lib/adapters/fiat/currency_layer_adapter.rb +2 -3
- data/lib/adapters/fiat/fixer_adapter.rb +3 -4
- data/lib/adapters/fiat/forge_adapter.rb +15 -6
- data/lib/adapters/fiat/free_forex_adapter.rb +7 -3
- data/lib/container.rb +203 -0
- data/lib/currency_rate.rb +20 -63
- data/lib/currency_rate/version.rb +1 -1
- data/lib/exceptions.rb +9 -0
- data/lib/storage/file_storage.rb +15 -10
- data/lib/utils/string_extensions.rb +19 -0
- metadata +12 -14
- data/api_keys.yml.sample +0 -4
- data/bin/rake +0 -29
- data/bin/rspec +0 -29
- data/lib/adapters/crypto/btc_china_adapter.rb +0 -11
- data/lib/adapters/crypto/btc_e_adapter.rb +0 -18
- data/lib/adapters/fiat/yahoo_adapter.rb +0 -35
- data/lib/configuration.rb +0 -29
- data/lib/fetcher.rb +0 -78
- data/lib/synchronizer.rb +0 -51
@@ -1,8 +1,8 @@
|
|
1
1
|
module CurrencyRate
|
2
2
|
class KrakenAdapter < Adapter
|
3
3
|
SUPPORTED_CURRENCIES = %w(
|
4
|
-
ADA BCH BSV BTC DASH EOS ETC ETH GNO LTC MLN
|
5
|
-
NMC QTUM REP XDG XLM XMR XRP XTZ ZEC
|
4
|
+
ADA BCH BSV BTC DAI DASH EOS ETC ETH GNO LTC MLN
|
5
|
+
NMC QTUM REP USDC XDG XLM XMR XRP XTZ ZEC
|
6
6
|
)
|
7
7
|
|
8
8
|
ASSET_MAP = {
|
@@ -21,14 +21,14 @@ module CurrencyRate
|
|
21
21
|
|
22
22
|
ANCHOR_CURRENCY = "BTC"
|
23
23
|
|
24
|
-
FETCH_URL = "https://api.kraken.com/0/public/Ticker?pair=#{ %w(ADAXBT BCHXBT BSVXBT DASHXBT EOSXBT GNOXBT QTUMXBT XTZXBT XETCXXBT XETHXXBT XLTCXXBT XREPXXBT XXLMXXBT XXMRXXBT XXRPXXBT XZECXXBT XXBTZUSD).join(",") }"
|
24
|
+
FETCH_URL = "https://api.kraken.com/0/public/Ticker?pair=#{ %w(ADAXBT BCHXBT BSVXBT DASHXBT EOSXBT GNOXBT QTUMXBT XTZXBT XETCXXBT XETHXXBT XLTCXXBT XREPXXBT XXLMXXBT XXMRXXBT XXRPXXBT XZECXXBT XXBTZUSD XBTDAI XBTUSDC).join(",") }"
|
25
25
|
|
26
26
|
def normalize(data)
|
27
|
-
return nil unless super
|
27
|
+
return nil unless data = super
|
28
28
|
data["result"].reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (pair, value)|
|
29
29
|
key = ta(pair.sub(ta(ANCHOR_CURRENCY), ""))
|
30
30
|
|
31
|
-
if
|
31
|
+
if %w(USD DAI USDC).include?(key)
|
32
32
|
result[key] = BigDecimal(value["c"].first.to_s)
|
33
33
|
else
|
34
34
|
result[key] = 1 / BigDecimal(value["c"].first.to_s)
|
@@ -14,7 +14,7 @@ module CurrencyRate
|
|
14
14
|
FETCH_URL = 'https://localbitcoins.com/bitcoinaverage/ticker-all-currencies/'
|
15
15
|
|
16
16
|
def normalize(data)
|
17
|
-
return nil unless super
|
17
|
+
return nil unless data = super
|
18
18
|
data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (fiat, value)|
|
19
19
|
result["#{fiat.upcase}"] = BigDecimal(value["rates"]["last"].to_s)
|
20
20
|
result
|
@@ -1,16 +1,14 @@
|
|
1
1
|
module CurrencyRate
|
2
2
|
class OkcoinAdapter < Adapter
|
3
3
|
FETCH_URL = {
|
4
|
-
'LTC_USD' => 'https://www.okcoin.com/api/
|
5
|
-
'BTC_USD' => 'https://www.okcoin.com/api/
|
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'
|
4
|
+
'LTC_USD' => 'https://www.okcoin.com/api/spot/v3/instruments/LTC-USD/ticker',
|
5
|
+
'BTC_USD' => 'https://www.okcoin.com/api/spot/v3/instruments/BTC-USD/ticker',
|
8
6
|
}
|
9
7
|
|
10
8
|
def normalize(data)
|
11
|
-
return nil unless super
|
9
|
+
return nil unless data = super
|
12
10
|
data.reduce({}) do |result, (pair, value)|
|
13
|
-
result[pair] = BigDecimal(value["
|
11
|
+
result[pair] = BigDecimal(value["last"].to_s)
|
14
12
|
result
|
15
13
|
end
|
16
14
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module CurrencyRate
|
2
|
+
class PaxfulAdapter < Adapter
|
3
|
+
SUPPORTED_CURRENCIES = %w(USD)
|
4
|
+
|
5
|
+
ANCHOR_CURRENCY = "BTC"
|
6
|
+
|
7
|
+
FETCH_URL = "https://paxful.com/api/currency/btc"
|
8
|
+
|
9
|
+
def normalize(data)
|
10
|
+
return nil unless data = super
|
11
|
+
|
12
|
+
{
|
13
|
+
"anchor" => ANCHOR_CURRENCY,
|
14
|
+
"USD" => BigDecimal(data["price"].to_s)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module CurrencyRate
|
2
|
+
class PoloniexAdapter < Adapter
|
3
|
+
SUPPORTED_CURRENCIES = %w(BTS DASH DOGE LTC NXT STR XEM XMR XRP USDT
|
4
|
+
ETH SC DCR LSK STEEM ETC REP ARDR ZEC STRAT
|
5
|
+
GNT ZRX CVC OMG GAS STORJ EOS SNT KNC BAT
|
6
|
+
LOOM QTUM USDC MANA BNT BCHABC BCHSV FOAM
|
7
|
+
NMR POLY LPT ATOM TRX ETHBNT LINK XTZ PAX
|
8
|
+
USDJ SNX MATIC MKR DAI NEO SWFTC FXC AVA
|
9
|
+
CHR BNB BUSD MDT XFIL LEND REN LRC WRX SXP
|
10
|
+
STPT SWAP EXE).freeze
|
11
|
+
|
12
|
+
ANCHOR_CURRENCY = "BTC".freeze
|
13
|
+
|
14
|
+
FETCH_URL = "https://poloniex.com/public?command=returnTicker".freeze
|
15
|
+
|
16
|
+
def normalize(data)
|
17
|
+
return nil unless data = super
|
18
|
+
|
19
|
+
data.each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |(pair_name, pair_info), result|
|
20
|
+
next unless pair_name.include?(ANCHOR_CURRENCY)
|
21
|
+
|
22
|
+
key = pair_name.sub(ANCHOR_CURRENCY, "").sub("_", "")
|
23
|
+
|
24
|
+
result[key] =
|
25
|
+
if pair_name.index(ANCHOR_CURRENCY) == 0
|
26
|
+
1 / BigDecimal(pair_info["last"])
|
27
|
+
else
|
28
|
+
BigDecimal(pair_info["last"])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -7,10 +7,11 @@ module CurrencyRate
|
|
7
7
|
FETCH_URL = "https://api.yadio.io/rate/BTC"
|
8
8
|
|
9
9
|
def normalize(data)
|
10
|
-
return nil unless super
|
10
|
+
return nil unless data = super
|
11
11
|
|
12
12
|
{
|
13
13
|
"anchor" => ANCHOR_CURRENCY,
|
14
|
+
"USD" => BigDecimal(data["usd"].to_s),
|
14
15
|
"VES" => BigDecimal(data["rate"].to_s),
|
15
16
|
}
|
16
17
|
end
|
@@ -12,13 +12,11 @@ module CurrencyRate
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def request(url)
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
CurrencyRate.configuration.read_timeout
|
15
|
+
http_client = HTTP.timeout(
|
16
|
+
connect: @container.connect_timeout,
|
17
|
+
read: @container.read_timeout
|
19
18
|
)
|
20
19
|
http_client.get(url).to_s
|
21
|
-
|
22
20
|
end
|
23
21
|
|
24
22
|
end
|
@@ -3,12 +3,11 @@ module CurrencyRate
|
|
3
3
|
# No need to use it for fetching, just additional information about supported currencies
|
4
4
|
SUPPORTED_CURRENCIES = %w(ARS)
|
5
5
|
ANCHOR_CURRENCY = "USD"
|
6
|
-
FETCH_URL = "https://ar.coinmonitor.info/
|
6
|
+
FETCH_URL = "https://ar.coinmonitor.info/data_ar.json"
|
7
7
|
|
8
8
|
def normalize(data)
|
9
|
-
return nil unless super
|
10
|
-
|
11
|
-
{ "anchor" => ANCHOR_CURRENCY }.merge({ SUPPORTED_CURRENCIES.first => BigDecimal(data["DOL_blue"].to_s) })
|
9
|
+
return nil unless data = super
|
10
|
+
{ "anchor" => ANCHOR_CURRENCY }.merge({ SUPPORTED_CURRENCIES.first => BigDecimal(data["BTC_avr_ars"].to_s) })
|
12
11
|
end
|
13
12
|
end
|
14
|
-
end
|
13
|
+
end
|
@@ -17,11 +17,10 @@ module CurrencyRate
|
|
17
17
|
)
|
18
18
|
|
19
19
|
ANCHOR_CURRENCY = "USD"
|
20
|
-
FETCH_URL = "http://
|
21
|
-
API_KEY_PARAM = "access_key"
|
20
|
+
FETCH_URL = "http://apilayer.net/api/live?access_key=__API_KEY__"
|
22
21
|
|
23
22
|
def normalize(data)
|
24
|
-
return nil unless super
|
23
|
+
return nil unless data = super
|
25
24
|
rates = { "anchor" => self.class::ANCHOR_CURRENCY }
|
26
25
|
data["quotes"].each do |key, value|
|
27
26
|
rates[key.sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal(value.to_s)
|
@@ -2,13 +2,12 @@ module CurrencyRate
|
|
2
2
|
class FixerAdapter < Adapter
|
3
3
|
# EUR is the only currency available as a base on free plan
|
4
4
|
ANCHOR_CURRENCY = "EUR"
|
5
|
-
FETCH_URL = "http://data.fixer.io/latest"
|
6
|
-
API_KEY_PARAM = "access_key"
|
5
|
+
FETCH_URL = "http://data.fixer.io/api/latest?access_key=__API_KEY__&base=#{ANCHOR_CURRENCY}"
|
7
6
|
|
8
7
|
def normalize(data)
|
9
|
-
return nil unless super
|
8
|
+
return nil unless data = super
|
10
9
|
rates = { "anchor" => ANCHOR_CURRENCY }
|
11
|
-
data["rates"].each do |k,
|
10
|
+
data["rates"].each do |k,v|
|
12
11
|
rates[k] = BigDecimal(v.to_s)
|
13
12
|
end
|
14
13
|
rates
|
@@ -1,22 +1,31 @@
|
|
1
1
|
module CurrencyRate
|
2
2
|
class ForgeAdapter < Adapter
|
3
3
|
SUPPORTED_CURRENCIES = %w(
|
4
|
-
|
4
|
+
AED AFN ALL AMD ANG AOA ARE ARS AUD AUN AWG BAM BBD BDT BGN BHD BIF BMD
|
5
|
+
BND BOB BRI BRL BSD BTN BWP BYN BZD CAD CDF CHF CLF CLP CLY CNH CNY COP
|
6
|
+
CRC CUP CVE CYP CZK DJF DKK DOE DOP DZD EGP ETB EUR FJD FRN GBP GEL GHS
|
7
|
+
GMD GNF GTQ GYD HKD HNL HRK HTG HUF IDR ILS INR IQD IRR ISK JMD JOD JPY
|
8
|
+
KES KHR KMF KRU KRW KWD KYD KZT LAK LBP LFX LKR LRD LSL LTL LYD M5P MAD
|
9
|
+
MAL MDL MGA MKD MMK MOP MRU MTL MUR MVR MWK MXN MYR MZN NAD NBL NGN NIO
|
10
|
+
NOK NPR NSO NZD OMR OSO PAB PEN PGK PHP PKR PLN PYG QAR RON RSD RUB RWF
|
11
|
+
SAR SBD SCR SDG SEK SGD SHP SLL SOS SRD STN SVC SZL THB TJS TMT TND TOP
|
12
|
+
TRY TTD TWD TZS UAH UGX USD UYU UZS VES VND VRL VRN XAG XAGK XAU XAUK XCD
|
13
|
+
XDR XOF XPD XPDK XPF XPT XPTK YER ZAR ZMW ZWD
|
5
14
|
)
|
6
15
|
|
7
16
|
ANCHOR_CURRENCY = "USD"
|
8
|
-
|
9
|
-
|
17
|
+
currency_pairs_request = SUPPORTED_CURRENCIES.map { |c| "#{ANCHOR_CURRENCY}/#{c}" }.join(",")
|
18
|
+
FETCH_URL = "https://api.1forge.com/quotes?pairs=#{currency_pairs_request}&api_key=__API_KEY__"
|
10
19
|
|
11
20
|
def normalize(data)
|
12
|
-
return nil unless super
|
21
|
+
return nil unless data = super
|
13
22
|
rates = { "anchor" => self.class::ANCHOR_CURRENCY }
|
14
23
|
data.each do |rate|
|
15
24
|
if rate["error"]
|
16
|
-
|
25
|
+
@container.logger.error("Forge exchange returned error")
|
17
26
|
return nil
|
18
27
|
end
|
19
|
-
rates[rate["
|
28
|
+
rates[rate["s"].sub("#{self.class::ANCHOR_CURRENCY}/", "")] = BigDecimal(rate["p"].to_s) if rate["p"]
|
20
29
|
end
|
21
30
|
rates
|
22
31
|
end
|
@@ -1,4 +1,8 @@
|
|
1
1
|
module CurrencyRate
|
2
|
+
|
3
|
+
# Beware, freeforexapi.com doesn't normally work and you need to place
|
4
|
+
# a small banner on your website so they'd list your websites IP address.
|
5
|
+
# More info here: http://freeforexapi.com/Home/Api
|
2
6
|
class FreeForexAdapter < Adapter
|
3
7
|
SUPPORTED_CURRENCIES = %w(
|
4
8
|
AED AFN ALL AMD ANG AOA ARS ATS AUD AWG AZM AZN BAM BBD BDT
|
@@ -18,10 +22,10 @@ module CurrencyRate
|
|
18
22
|
)
|
19
23
|
|
20
24
|
ANCHOR_CURRENCY = "USD"
|
21
|
-
FETCH_URL = "https://www.freeforexapi.com/api/live?pairs=" + SUPPORTED_CURRENCIES.map{|
|
25
|
+
FETCH_URL = "https://www.freeforexapi.com/api/live?pairs=" + SUPPORTED_CURRENCIES.map { |c| "USD#{c}"}.join(",")
|
22
26
|
|
23
27
|
def normalize(data)
|
24
|
-
return nil unless super
|
28
|
+
return nil unless data = super
|
25
29
|
rates = { "anchor" => self.class::ANCHOR_CURRENCY }
|
26
30
|
data["rates"].each do |pair, payload|
|
27
31
|
rates[pair.sub(self.class::ANCHOR_CURRENCY, "")] = BigDecimal(payload["rate"].to_s)
|
@@ -29,4 +33,4 @@ module CurrencyRate
|
|
29
33
|
rates
|
30
34
|
end
|
31
35
|
end
|
32
|
-
end
|
36
|
+
end
|
data/lib/container.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
class CurrencyRate::Container
|
2
|
+
|
3
|
+
attr_accessor :api_keys
|
4
|
+
attr_accessor :logger
|
5
|
+
attr_accessor :adapters
|
6
|
+
attr_accessor :connect_timeout
|
7
|
+
attr_accessor :read_timeout
|
8
|
+
attr_accessor :storage
|
9
|
+
attr_accessor :logger_callbacks
|
10
|
+
attr_accessor :limit_adapters_for_pairs
|
11
|
+
|
12
|
+
def initialize(
|
13
|
+
api_keys: {},
|
14
|
+
adapter_type: nil,
|
15
|
+
adapter_names: nil,
|
16
|
+
limit_adapters_for_pairs: {},
|
17
|
+
storage_settings: CurrencyRate.default_config[:storage_settings],
|
18
|
+
connect_timeout: CurrencyRate.default_config[:connect_timeout],
|
19
|
+
read_timeout: CurrencyRate.default_config[:read_timeout],
|
20
|
+
logger_settings: CurrencyRate.default_config[:logger_settings],
|
21
|
+
logger_callbacks: CurrencyRate.default_config[:logger_callbacks]
|
22
|
+
)
|
23
|
+
|
24
|
+
logger_settigns = CurrencyRate.default_config[:logger_settings].merge(logger_settings)
|
25
|
+
storage_settigns = CurrencyRate.default_config[:storage_settings].merge(storage_settings)
|
26
|
+
|
27
|
+
method(__method__).parameters.map do |_, name|
|
28
|
+
value = binding.local_variable_get(name)
|
29
|
+
instance_variable_set("@#{name}", value)
|
30
|
+
end
|
31
|
+
|
32
|
+
_logger_callbacks = {}
|
33
|
+
@logger_callbacks.each do |k,v|
|
34
|
+
_logger_callbacks[Logger::Severity.const_get(k.to_s.upcase)] = v
|
35
|
+
end
|
36
|
+
@logger_callbacks = _logger_callbacks
|
37
|
+
|
38
|
+
_limit_adapters_for_pairs = {}
|
39
|
+
@limit_adapters_for_pairs.each do |k,v|
|
40
|
+
_limit_adapters_for_pairs[k] = CurrencyRate.const_get(v.to_s.to_camel_case + "Adapter").instance
|
41
|
+
end
|
42
|
+
@limit_adapters_for_pairs = _limit_adapters_for_pairs
|
43
|
+
|
44
|
+
@storage = CurrencyRate.const_get(storage_settigns[:type].to_s.to_camel_case + "Storage").new(
|
45
|
+
path: storage_settings[:path],
|
46
|
+
container: self,
|
47
|
+
serializer: storage_settings[:serializer]
|
48
|
+
)
|
49
|
+
|
50
|
+
load_adapters!(names: adapter_names, type: adapter_type)
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing(m, *args, &block)
|
54
|
+
if m.to_s.end_with? "_adapters"
|
55
|
+
self.send(:adapters, m[0..-10])
|
56
|
+
else
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def logger
|
62
|
+
return @logger if @logger
|
63
|
+
@logger = Logger.new(@logger_settings[:device])
|
64
|
+
@logger.progname = "CurrencyRate"
|
65
|
+
@logger.level = @logger_settings[:level]
|
66
|
+
@logger.formatter = @logger_settings[:formatter] if @logger_settings[:formatter]
|
67
|
+
@logger
|
68
|
+
end
|
69
|
+
|
70
|
+
# This method doesn't make any requests. It merely reads normalized data
|
71
|
+
# from the selected storage. Contrary to what one might assume,
|
72
|
+
# it doesn't call CurrencyRate::Container.sync! if storage for a particular
|
73
|
+
# adapter doesn't exist - that's because those are two separate tasks
|
74
|
+
# and you might not want to make that external http(s) request even if
|
75
|
+
# the rates in storage are not found.
|
76
|
+
#
|
77
|
+
# It also uses a 3 different strategies to calculate rates before they are returned:
|
78
|
+
#
|
79
|
+
# 1. If only one adapter is specified and/or available, it will return the rate for the
|
80
|
+
# pair using the data from the storage for this particular adapter.
|
81
|
+
#
|
82
|
+
# 2. If two or more adapters are specified and available, it will return the average rate
|
83
|
+
# based on the the rates from all of those adapters.
|
84
|
+
#
|
85
|
+
# 3. If `strategy: :majority` flag is set and an odd number of adapters (say, 3)
|
86
|
+
# is passed and/or available it will look at the rates from all, separate them
|
87
|
+
# into two groups based on how close their rates are and return the average of the rates
|
88
|
+
# that are closest in the group that is the largest.
|
89
|
+
#
|
90
|
+
# It will also check @limit_adapters_for_pairs hash for the request pair
|
91
|
+
# and if the key exists it will exclude adapters that are not listed in the array
|
92
|
+
# under that key.
|
93
|
+
def get_rate(from, to, use_adapters=@adapter_names, strategy: :average)
|
94
|
+
adapters_for_pair = @limit_adapters_for_pairs["#{from}/#{to}"]
|
95
|
+
_adapters = adapters.select { |a| use_adapters.include?(a.name(:camel_case)) }
|
96
|
+
_adapters = _adapters.select { |a| adapters_for_pair.include?(a.name(:camel_case)) } if adapters_for_pair
|
97
|
+
|
98
|
+
warning_loggers = []
|
99
|
+
rates = _adapters.map do |a|
|
100
|
+
rate = [a.name(:camel_case), a.get_rate(from, to)]
|
101
|
+
warning_loggers.push -> { self.log(:warn, "No rate for pair #{from}/#{to} found in #{a.name(:camel_case)} adapter") if rate[1].nil? }
|
102
|
+
rate
|
103
|
+
end.select { |r| !r[1].nil? }
|
104
|
+
|
105
|
+
if rates.empty?
|
106
|
+
self.log(:error,
|
107
|
+
"No rate for pair #{from}/#{to} is found in any of the available adapters " +
|
108
|
+
"(#{_adapters.map { |a| a.name(:camel_case) }.join(", ")})"
|
109
|
+
)
|
110
|
+
else
|
111
|
+
warning_loggers.each { |wl| wl.call }
|
112
|
+
end
|
113
|
+
|
114
|
+
if rates.size == 1
|
115
|
+
rates[0][1]
|
116
|
+
else
|
117
|
+
if strategy == :majority && rates.size.odd?
|
118
|
+
rates.sort! { |r1, r2| r1[1] <=> r2[1] }
|
119
|
+
|
120
|
+
largest_discrepancy_rate_index = 0
|
121
|
+
last_discrepancy = 0
|
122
|
+
|
123
|
+
rates.each_with_index do |r,i|
|
124
|
+
if i > 0
|
125
|
+
discrepancy = r[1] - rates[i-1][1]
|
126
|
+
if discrepancy > last_discrepancy
|
127
|
+
last_discrepancy = discrepancy
|
128
|
+
largest_discrepancy_rate_index = i
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
rates_group_1 = rates[0...largest_discrepancy_rate_index]
|
134
|
+
rates_group_2 = rates[largest_discrepancy_rate_index..rates.size-1]
|
135
|
+
rates = [rates_group_1, rates_group_2].max { |g1,g2| g1.size <=> g2.size }
|
136
|
+
end
|
137
|
+
rates.inject(BigDecimal(0)) { |sum, i| sum + i[1] } / BigDecimal(rates.size)
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
def sync!
|
143
|
+
successfull = []
|
144
|
+
failed = []
|
145
|
+
@adapters.each do |adapter|
|
146
|
+
adapter_name = adapter.class.to_s.sub("CurrencyRate::", "")
|
147
|
+
begin
|
148
|
+
rates = adapter.fetch_rates
|
149
|
+
unless rates
|
150
|
+
self.log(:warn, "Trying to sync rates for #{adapter_name}, rates not found, but http(s) request did not return any error.")
|
151
|
+
failed << adapter_name
|
152
|
+
next
|
153
|
+
end
|
154
|
+
@storage.write(adapter_name.to_snake_case.sub("_adapter", ""), rates)
|
155
|
+
successfull << adapter_name
|
156
|
+
rescue StandardError => e
|
157
|
+
failed << adapter_name
|
158
|
+
self.log(:error, e)
|
159
|
+
next
|
160
|
+
end
|
161
|
+
end
|
162
|
+
[successfull, failed]
|
163
|
+
end
|
164
|
+
|
165
|
+
def log(level, message)
|
166
|
+
severity = Logger::Severity.const_get(level.to_s.upcase)
|
167
|
+
logger.send(level, message)
|
168
|
+
if @logger_callbacks[severity]
|
169
|
+
@logger_callbacks[severity].call(level, message)
|
170
|
+
else
|
171
|
+
@logger_callbacks.keys.sort.each do |k|
|
172
|
+
@logger_callbacks[k].call(level, message) && break if k < severity
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def load_adapters!(names: nil, type: :all)
|
180
|
+
if names
|
181
|
+
names = [names] if names.kind_of?(String)
|
182
|
+
@adapters = names.map do |name|
|
183
|
+
CurrencyRate.const_get(name + "Adapter").instance
|
184
|
+
end
|
185
|
+
else
|
186
|
+
crypto_adapter_files = Dir[File.join CurrencyRate.root, "lib/adapters/crypto/*"]
|
187
|
+
fiat_adapter_files = Dir[File.join CurrencyRate.root, "lib/adapters/fiat/*"]
|
188
|
+
adapter_files = case type
|
189
|
+
when :crypto then crypto_adapter_files
|
190
|
+
when :fiat then fiat_adapter_files
|
191
|
+
else crypto_adapter_files + fiat_adapter_files
|
192
|
+
end
|
193
|
+
@adapters = adapter_files.map do |file|
|
194
|
+
CurrencyRate.const_get(File.basename(file, ".rb").to_camel_case).instance
|
195
|
+
end
|
196
|
+
end
|
197
|
+
@adapters.each { |a| a.container = self; a.api_key = @api_keys[a.name] }
|
198
|
+
@adapter_names = @adapters.map { |a| a.name(:camel_case) }
|
199
|
+
@adapters
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|