currency-rate 1.5.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|