currency-rate 1.7.0 → 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 -1
 - data/lib/adapter.rb +53 -24
 - data/lib/adapters/crypto/binance_adapter.rb +1 -1
 - data/lib/adapters/crypto/bitfinex_adapter.rb +1 -1
 - 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 +1 -1
 - data/lib/adapters/crypto/kraken_adapter.rb +1 -1
 - 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 +1 -1
 - data/lib/adapters/crypto/poloniex_adapter.rb +1 -1
 - data/lib/adapters/crypto/yadio_adapter.rb +1 -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 +19 -63
 - data/lib/currency_rate/version.rb +1 -1
 - data/lib/storage/file_storage.rb +14 -9
 - data/lib/utils/string_extensions.rb +19 -0
 - metadata +6 -11
 - data/api_keys.yml.sample +0 -4
 - data/lib/adapters/crypto/btc_china_adapter.rb +0 -11
 - data/lib/adapters/crypto/btc_e_adapter.rb +0 -18
 - data/lib/adapters/crypto/hit_btc_adapter.rb +0 -61
 - data/lib/adapters/fiat/yahoo_adapter.rb +0 -35
 - data/lib/configuration.rb +0 -28
 - data/lib/fetcher.rb +0 -80
 - data/lib/synchronizer.rb +0 -54
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: e33cf57e2389c7faea732bc6a2c1005d1373daa26622d77d07058a157cb408b4
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ecdcdf02e7478deb612bebe5fed48c3d46d99916f9f25b4e0666bcf88ed4c1f9
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: d6d71b129e93052a97d6e27749531c16818e19eeb1f7b44b6889d67683543094eea7c9cf59f7dc6dae01e1e6e0f570c0709c06d3c786b7c7d275f1b4e60c6e65
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 21cd15e77e63eb8df45f4b52904b7c9ac3fb0e0a93838a3373c31165af5a7d115c6982b2e8cdf869898ed24dbe07008bcc817b9464ca0510717cf5a92e359844
         
     | 
    
        data/.gitignore
    CHANGED
    
    
    
        data/lib/adapter.rb
    CHANGED
    
    | 
         @@ -4,28 +4,41 @@ module CurrencyRate 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                SUPPORTED_CURRENCIES = []
         
     | 
| 
       6 
6 
     | 
    
         
             
                FETCH_URL = nil
         
     | 
| 
       7 
     | 
    
         
            -
                API_KEY_PARAM = nil
         
     | 
| 
       8 
7 
     | 
    
         | 
| 
       9 
     | 
    
         
            -
                 
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader   :rates
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_accessor :container
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_accessor :api_key
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def name(format=:snake_case)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @camel_case_name ||= self.class.name.gsub(/^.*::/, "").sub("Adapter", "")
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @snake_case_name ||= @camel_case_name.to_snake_case
         
     | 
| 
      
 15 
     | 
    
         
            +
                  format == :camel_case ? @camel_case_name : @snake_case_name
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def get_rate(from, to)
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @rates = @container.storage.read(self.name) if @rates.nil? && @container.storage.exists?(self.name)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  return BigDecimal(rates[to])                if ANCHOR_CURRENCY == from && rates[to]
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return BigDecimal(1 / rates[from])          if ANCHOR_CURRENCY == to && rates[from]
         
     | 
| 
      
 22 
     | 
    
         
            +
                  return BigDecimal(rates[to] / rates[from])  if rates[from] && rates[to]
         
     | 
| 
      
 23 
     | 
    
         
            +
                  nil
         
     | 
| 
       11 
24 
     | 
    
         
             
                end
         
     | 
| 
       12 
25 
     | 
    
         | 
| 
       13 
26 
     | 
    
         
             
                def fetch_rates
         
     | 
| 
       14 
27 
     | 
    
         
             
                  begin
         
     | 
| 
       15 
     | 
    
         
            -
                    normalize 
     | 
| 
      
 28 
     | 
    
         
            +
                    @rates = normalize(exchange_data)
         
     | 
| 
       16 
29 
     | 
    
         
             
                  rescue StandardError => e
         
     | 
| 
       17 
     | 
    
         
            -
                     
     | 
| 
       18 
     | 
    
         
            -
                    CurrencyRate.logger.error(e)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @container.log(:error, e)
         
     | 
| 
       19 
31 
     | 
    
         
             
                    nil
         
     | 
| 
       20 
32 
     | 
    
         
             
                  end
         
     | 
| 
       21 
33 
     | 
    
         
             
                end
         
     | 
| 
       22 
34 
     | 
    
         | 
| 
       23 
35 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       24 
     | 
    
         
            -
                  if data.nil?
         
     | 
| 
       25 
     | 
    
         
            -
                     
     | 
| 
      
 36 
     | 
    
         
            +
                  @rates = if data.nil?
         
     | 
| 
      
 37 
     | 
    
         
            +
                    @container.log(:warn, "#{self.name}#normalize: data is nil")
         
     | 
| 
       26 
38 
     | 
    
         
             
                    return nil
         
     | 
| 
      
 39 
     | 
    
         
            +
                  else
         
     | 
| 
      
 40 
     | 
    
         
            +
                    parse_raw_data(data)
         
     | 
| 
       27 
41 
     | 
    
         
             
                  end
         
     | 
| 
       28 
     | 
    
         
            -
                  true
         
     | 
| 
       29 
42 
     | 
    
         
             
                end
         
     | 
| 
       30 
43 
     | 
    
         | 
| 
       31 
44 
     | 
    
         
             
                def exchange_data
         
     | 
| 
         @@ -37,34 +50,50 @@ module CurrencyRate 
     | 
|
| 
       37 
50 
     | 
    
         
             
                        result[name] = request url
         
     | 
| 
       38 
51 
     | 
    
         
             
                      end
         
     | 
| 
       39 
52 
     | 
    
         
             
                    else
         
     | 
| 
       40 
     | 
    
         
            -
                      request self.class::FETCH_URL
         
     | 
| 
      
 53 
     | 
    
         
            +
                      result = request self.class::FETCH_URL
         
     | 
| 
       41 
54 
     | 
    
         
             
                    end
         
     | 
| 
       42 
55 
     | 
    
         
             
                  rescue StandardError => e
         
     | 
| 
       43 
     | 
    
         
            -
                     
     | 
| 
       44 
     | 
    
         
            -
                    CurrencyRate.logger.error(e)
         
     | 
| 
      
 56 
     | 
    
         
            +
                    @container.log(:error, e)
         
     | 
| 
       45 
57 
     | 
    
         
             
                    nil
         
     | 
| 
       46 
58 
     | 
    
         
             
                  end
         
     | 
| 
       47 
59 
     | 
    
         
             
                end
         
     | 
| 
       48 
60 
     | 
    
         | 
| 
       49 
     | 
    
         
            -
                def  
     | 
| 
       50 
     | 
    
         
            -
                   
     | 
| 
       51 
     | 
    
         
            -
                  if self.class::API_KEY_PARAM
         
     | 
| 
       52 
     | 
    
         
            -
                    api_key = CurrencyRate.configuration.api_keys[self.name]
         
     | 
| 
      
 61 
     | 
    
         
            +
                def url_with_api_key(url)
         
     | 
| 
      
 62 
     | 
    
         
            +
                  if url.include?("__API_KEY__")
         
     | 
| 
       53 
63 
     | 
    
         
             
                    if api_key.nil?
         
     | 
| 
       54 
     | 
    
         
            -
                       
     | 
| 
      
 64 
     | 
    
         
            +
                      @container.log(:error, "API key for #{self.name} is not set")
         
     | 
| 
       55 
65 
     | 
    
         
             
                      return nil
         
     | 
| 
      
 66 
     | 
    
         
            +
                    else
         
     | 
| 
      
 67 
     | 
    
         
            +
                      url.sub("__API_KEY__", api_key.strip)
         
     | 
| 
       56 
68 
     | 
    
         
             
                    end
         
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
                     
     | 
| 
      
 69 
     | 
    
         
            +
                  else
         
     | 
| 
      
 70 
     | 
    
         
            +
                    url
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
                def request(url)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  unless url = url_with_api_key(url)
         
     | 
| 
      
 76 
     | 
    
         
            +
                    return nil
         
     | 
| 
       59 
77 
     | 
    
         
             
                  end
         
     | 
| 
       60 
     | 
    
         
            -
                  http_client = HTTP.timeout(connect:  
     | 
| 
       61 
     | 
    
         
            -
                   
     | 
| 
       62 
     | 
    
         
            -
                    http_client
         
     | 
| 
      
 78 
     | 
    
         
            +
                  http_client = HTTP.timeout(connect: @container.connect_timeout, read: @container.read_timeout)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  http_client
         
     | 
| 
       63 
80 
     | 
    
         
             
                    .headers("Accept" => "application/json; version=1")
         
     | 
| 
       64 
81 
     | 
    
         
             
                    .headers("Content-Type" => "text/plain")
         
     | 
| 
       65 
     | 
    
         
            -
                    .get( 
     | 
| 
      
 82 
     | 
    
         
            +
                    .get(url)
         
     | 
| 
       66 
83 
     | 
    
         
             
                    .to_s
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
                def parse_raw_data(data)
         
     | 
| 
      
 87 
     | 
    
         
            +
                  if data.kind_of?(Hash)
         
     | 
| 
      
 88 
     | 
    
         
            +
                    data.each { |k,v| data[k] = parse_raw_data(v) }
         
     | 
| 
      
 89 
     | 
    
         
            +
                    data
         
     | 
| 
      
 90 
     | 
    
         
            +
                  else
         
     | 
| 
      
 91 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 92 
     | 
    
         
            +
                      return JSON.parse(data)
         
     | 
| 
      
 93 
     | 
    
         
            +
                    rescue JSON::ParserError
         
     | 
| 
      
 94 
     | 
    
         
            +
                      return data
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                  end
         
     | 
| 
       68 
97 
     | 
    
         
             
                end
         
     | 
| 
       69 
98 
     | 
    
         | 
| 
       70 
99 
     | 
    
         
             
              end
         
     | 
| 
         @@ -20,7 +20,7 @@ module CurrencyRate 
     | 
|
| 
       20 
20 
     | 
    
         
             
                            }
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       23 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 23 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       24 
24 
     | 
    
         
             
                  binance_result = data["Binance"].reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, hash|
         
     | 
| 
       25 
25 
     | 
    
         
             
                    if hash["symbol"].index(ANCHOR_CURRENCY) == 0
         
     | 
| 
       26 
26 
     | 
    
         
             
                      result[hash["symbol"].sub(ANCHOR_CURRENCY, "")] = BigDecimal(hash["price"].to_s)
         
     | 
| 
         @@ -21,7 +21,7 @@ module CurrencyRate 
     | 
|
| 
       21 
21 
     | 
    
         
             
                FETCH_URL = "https://api.bitfinex.com/v2/tickers?symbols=ALL"
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       24 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 24 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       25 
25 
     | 
    
         
             
                  data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, pair_info|
         
     | 
| 
       26 
26 
     | 
    
         
             
                    pair_name = pair_info[0].sub("t", "")
         
     | 
| 
       27 
27 
     | 
    
         
             
                    key = pair_name.sub(ANCHOR_CURRENCY, "")
         
     | 
| 
         @@ -18,7 +18,7 @@ module CurrencyRate 
     | 
|
| 
       18 
18 
     | 
    
         
             
                FETCH_URL = "https://bitpay.com/api/rates"
         
     | 
| 
       19 
19 
     | 
    
         | 
| 
       20 
20 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       21 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       22 
22 
     | 
    
         
             
                  data.reject { |rate| rate["code"] == ANCHOR_CURRENCY }.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, rate|
         
     | 
| 
       23 
23 
     | 
    
         
             
                    result["#{rate['code'].upcase}"] = BigDecimal(rate["rate"].to_s)
         
     | 
| 
       24 
24 
     | 
    
         
             
                    result
         
     | 
| 
         @@ -10,7 +10,7 @@ module CurrencyRate 
     | 
|
| 
       10 
10 
     | 
    
         
             
                FETCH_URL["USD"] = "https://www.bitstamp.net/api/ticker/"
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       13 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 13 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       14 
14 
     | 
    
         
             
                  data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (key, value)|
         
     | 
| 
       15 
15 
     | 
    
         
             
                    if key == "USD"
         
     | 
| 
       16 
16 
     | 
    
         
             
                      result[key] =  BigDecimal(value["last"].to_s)
         
     | 
| 
         @@ -12,12 +12,10 @@ module CurrencyRate 
     | 
|
| 
       12 
12 
     | 
    
         
             
                )
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                ANCHOR_CURRENCY = "BTC"
         
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                FETCH_URL = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest"
         
     | 
| 
       17 
     | 
    
         
            -
                API_KEY_PARAM = "CMC_PRO_API_KEY"
         
     | 
| 
      
 15 
     | 
    
         
            +
                FETCH_URL = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/listings/latest?CMC_PRO_API_KEY=__API_KEY__"
         
     | 
| 
       18 
16 
     | 
    
         | 
| 
       19 
17 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       20 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 18 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       21 
19 
     | 
    
         
             
                  data["data"].each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |payload, result|
         
     | 
| 
       22 
20 
     | 
    
         
             
                    if payload["symbol"] == ANCHOR_CURRENCY
         
     | 
| 
       23 
21 
     | 
    
         
             
                      result["USD"] = BigDecimal(payload["quote"]["USD"]["price"].to_s)
         
     | 
| 
         @@ -18,9 +18,9 @@ module CurrencyRate 
     | 
|
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                FETCH_URL = "https://api.coinbase.com/v2/exchange-rates?currency=#{ANCHOR_CURRENCY}"
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                def normalize( 
     | 
| 
       22 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
       23 
     | 
    
         
            -
                   
     | 
| 
      
 21 
     | 
    
         
            +
                def normalize(data)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
      
 23 
     | 
    
         
            +
                  data["data"]["rates"].reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (currency, rate)|
         
     | 
| 
       24 
24 
     | 
    
         
             
                    result[currency] = BigDecimal(rate.to_s)
         
     | 
| 
       25 
25 
     | 
    
         
             
                    result
         
     | 
| 
       26 
26 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -12,7 +12,7 @@ module CurrencyRate 
     | 
|
| 
       12 
12 
     | 
    
         
             
                FETCH_URL = "https://api.exmo.com/v1/ticker/"
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       15 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 15 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       16 
16 
     | 
    
         
             
                  data.reduce({ "anchor" => ANCHOR_CURRENCY }) do |result, (key, value)|
         
     | 
| 
       17 
17 
     | 
    
         
             
                    if key.split("_")[0] == ANCHOR_CURRENCY
         
     | 
| 
       18 
18 
     | 
    
         
             
                      result[key.sub("#{self.class::ANCHOR_CURRENCY}_", "")] = BigDecimal(value["avg"].to_s)
         
     | 
| 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module CurrencyRate
         
     | 
| 
      
 2 
     | 
    
         
            +
              class HitBTCAdapter < Adapter
         
     | 
| 
      
 3 
     | 
    
         
            +
                SUPPORTED_CURRENCIES = %w(
         
     | 
| 
      
 4 
     | 
    
         
            +
                  ZRC EDG IHT AEON SOC HBAR ZRX OPT APPC DRGN PTOY
         
     | 
| 
      
 5 
     | 
    
         
            +
                  XDN OKB CHSB NCT GUSD GET FUN EXP EMRX REV GHOST
         
     | 
| 
      
 6 
     | 
    
         
            +
                  BMH SNC DTR ERD SCL HMQ ACT ETC QTUM MTX SBTC
         
     | 
| 
      
 7 
     | 
    
         
            +
                  KIND SMT BTB SWFTC 1ST UTT AXPR NMR EVX IOTA XPRM
         
     | 
| 
      
 8 
     | 
    
         
            +
                  STMX SALT DGB NTK AMM ALGO ORMEUS BDG BQX EKO FYP
         
     | 
| 
      
 9 
     | 
    
         
            +
                  IPX HOT MG BTS ADX CRPT WAXP POA PLBT SHIP HTML
         
     | 
| 
      
 10 
     | 
    
         
            +
                  BOX GNO UBT BTT ZEN VEO POA20 BCPT SRN XPR ETHBNT
         
     | 
| 
      
 11 
     | 
    
         
            +
                  MANA QKC MLN FLP SOLO TRUE VSYS JST GNT BOS PHB
         
     | 
| 
      
 12 
     | 
    
         
            +
                  ZEC ESH SWM NANO VIBE HVN SOLVE ELEC LRC AGI LNC
         
     | 
| 
      
 13 
     | 
    
         
            +
                  WAVES WTC ONT STRAT GNX NEU BCN XPNT ECA ARDR KIN
         
     | 
| 
      
 14 
     | 
    
         
            +
                  LSK USE IOTX CRO IDH LINK OAX CPT NGC XNS KEY TKY
         
     | 
| 
      
 15 
     | 
    
         
            +
                  HSR TNT SMART TRST DCR WINGS GT MKR ONE DOGE ARN
         
     | 
| 
      
 16 
     | 
    
         
            +
                  ACAT BMC RAISE EXM TIME REX FDZ HT MTH SCC BET
         
     | 
| 
      
 17 
     | 
    
         
            +
                  DENT IDRT IPL ZAP CMCT TDP XAUR MTL NEBL SUSDT BAT
         
     | 
| 
      
 18 
     | 
    
         
            +
                  STEEM CUR BYTZ PRO LOOM USD DRG DICE ADK COMP DRT
         
     | 
| 
      
 19 
     | 
    
         
            +
                  XTZ WETH EURS CHZ NEO NPLC XCON LEVL PAX AIM PART
         
     | 
| 
      
 20 
     | 
    
         
            +
                  PRE ERK HEDG FET PAXG DAG AVA CUTE NEXO DAY PITCH
         
     | 
| 
      
 21 
     | 
    
         
            +
                  MITX NXT POWR PLR CVCOIN TUSD MYST DLT REM RLC DNA
         
     | 
| 
      
 22 
     | 
    
         
            +
                  FOTA SBD ELF TEL C20 PNT CND UTK ASI CVC ETP ETH
         
     | 
| 
      
 23 
     | 
    
         
            +
                  ZIL ARPA INK NPXS LEO MESH NIM DATX FXT PBT GST
         
     | 
| 
      
 24 
     | 
    
         
            +
                  BSV GAS CBC MCO SENT GBX XRC POE SUR LOC WIKI PPT
         
     | 
| 
      
 25 
     | 
    
         
            +
                  CVT APM LEND NUT DOV KMD AYA LUN XEM RVN BCD XMR
         
     | 
| 
      
 26 
     | 
    
         
            +
                  NWC USG CLO NLC2 BBTC BERRY ART GRIN VITAE XBP OMG
         
     | 
| 
      
 27 
     | 
    
         
            +
                  MDA KRL BCH POLY PLA BANCA ENJ TRIGX UUU PASS ANT
         
     | 
| 
      
 28 
     | 
    
         
            +
                  LAMB BIZZ RFR AMB ROOBEE BST LCC RCN MIN BUSD DIT
         
     | 
| 
      
 29 
     | 
    
         
            +
                  PPC AE IQ BNK CENNZ SUB OCN DGD VRA STX AERGO HGT
         
     | 
| 
      
 30 
     | 
    
         
            +
                  TRAD IGNIS REP DAPP DNT CDT YCC SNGLS ICX PKT COV
         
     | 
| 
      
 31 
     | 
    
         
            +
                  PAY ABYSS BLZ DAV TKN ERT SPC SEELE XZC MAID AUTO
         
     | 
| 
      
 32 
     | 
    
         
            +
                  REN DATA AUC TV LAVA KAVA DAI DAPS ADA COCOS MITH
         
     | 
| 
      
 33 
     | 
    
         
            +
                  SETH NRG PHX DASH VLX CHAT VET EOSDT ZSC KNC DGTX
         
     | 
| 
      
 34 
     | 
    
         
            +
                  CEL SHORTUSD IOST BNB PBTT XMC EMC VIB BNT STORJ
         
     | 
| 
      
 35 
     | 
    
         
            +
                  ATOM LCX SC FTT BTM XLM TRX CELR BRD DBIX ETN SNT
         
     | 
| 
      
 36 
     | 
    
         
            +
                  MOF HEX XUC PLU FACE TNC SIG PXG BDP BTX TAU DCT
         
     | 
| 
      
 37 
     | 
    
         
            +
                  YOYOW SYBC SWT MAN GLEEC EOS XVG NAV CURE XRP KICK
         
     | 
| 
      
 38 
     | 
    
         
            +
                  BRDG LTC USDC MATIC FTX BTG PMA
         
     | 
| 
      
 39 
     | 
    
         
            +
                ).freeze
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                ANCHOR_CURRENCY = "BTC".freeze
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                FETCH_URL = "https://api.hitbtc.com/api/2/public/ticker".freeze
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def normalize(data)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  data.each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |pair_info, result|
         
     | 
| 
      
 49 
     | 
    
         
            +
                    pair_name = pair_info["symbol"]
         
     | 
| 
      
 50 
     | 
    
         
            +
                    next unless pair_name.include?(ANCHOR_CURRENCY)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    next unless pair_info["last"]
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                    key = pair_name.sub(ANCHOR_CURRENCY, "")
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                    result[key] =
         
     | 
| 
      
 56 
     | 
    
         
            +
                      if pair_name.index(ANCHOR_CURRENCY) == 0
         
     | 
| 
      
 57 
     | 
    
         
            +
                        BigDecimal(pair_info["last"])
         
     | 
| 
      
 58 
     | 
    
         
            +
                      else
         
     | 
| 
      
 59 
     | 
    
         
            +
                        1 / BigDecimal(pair_info["last"])
         
     | 
| 
      
 60 
     | 
    
         
            +
                      end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  end
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -24,7 +24,7 @@ module CurrencyRate 
     | 
|
| 
       24 
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 
     | 
    
         | 
| 
         @@ -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
         
     | 
| 
         @@ -14,7 +14,7 @@ module CurrencyRate 
     | 
|
| 
       14 
14 
     | 
    
         
             
                FETCH_URL = "https://poloniex.com/public?command=returnTicker".freeze
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
                def normalize(data)
         
     | 
| 
       17 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
      
 17 
     | 
    
         
            +
                  return nil unless data = super
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                  data.each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |(pair_name, pair_info), result|
         
     | 
| 
       20 
20 
     | 
    
         
             
                    next unless pair_name.include?(ANCHOR_CURRENCY)
         
     | 
| 
         @@ -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
         
     | 
    
        data/lib/currency_rate.rb
    CHANGED
    
    | 
         @@ -5,81 +5,37 @@ require "json" 
     | 
|
| 
       5 
5 
     | 
    
         
             
            require "http"
         
     | 
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            require_relative "exceptions"
         
     | 
| 
       8 
     | 
    
         
            -
            require_relative "configuration"
         
     | 
| 
       9 
8 
     | 
    
         
             
            require_relative "adapter"
         
     | 
| 
       10 
     | 
    
         
            -
            require_relative " 
     | 
| 
       11 
     | 
    
         
            -
            require_relative " 
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative "container"
         
     | 
| 
      
 10 
     | 
    
         
            +
            require_relative "utils/string_extensions"
         
     | 
| 
       12 
11 
     | 
    
         | 
| 
       13 
12 
     | 
    
         
             
            Dir["#{File.expand_path __dir__}/adapters/**/*.rb"].each { |f| require f }
         
     | 
| 
       14 
     | 
    
         
            -
            Dir["#{File.expand_path __dir__}/storage/**/*.rb"].each 
     | 
| 
      
 13 
     | 
    
         
            +
            Dir["#{File.expand_path __dir__}/storage/**/*.rb"].each  { |f| require f }
         
     | 
| 
       15 
14 
     | 
    
         | 
| 
       16 
15 
     | 
    
         
             
            module CurrencyRate
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              @@default_config = {
         
     | 
| 
      
 18 
     | 
    
         
            +
                connect_timeout: 10,
         
     | 
| 
      
 19 
     | 
    
         
            +
                read_timeout: 10,
         
     | 
| 
      
 20 
     | 
    
         
            +
                logger_callbacks: {},
         
     | 
| 
      
 21 
     | 
    
         
            +
                logger_settings:  { device: $stdout, level: :info, formatter: nil },
         
     | 
| 
      
 22 
     | 
    
         
            +
                storage_settings: { path: __dir__, serializer: CurrencyRate::Storage::YAMLSerializer }
         
     | 
| 
      
 23 
     | 
    
         
            +
              }
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
       17 
25 
     | 
    
         
             
              class << self
         
     | 
| 
       18 
     | 
    
         
            -
                attr_writer :configuration
         
     | 
| 
       19 
     | 
    
         
            -
              end
         
     | 
| 
       20 
26 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
                  self.send(:adapters, m[0..-10])
         
     | 
| 
       24 
     | 
    
         
            -
                else
         
     | 
| 
       25 
     | 
    
         
            -
                  super
         
     | 
| 
      
 27 
     | 
    
         
            +
                def default_config
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @@default_config
         
     | 
| 
       26 
29 
     | 
    
         
             
                end
         
     | 
| 
       27 
     | 
    
         
            -
              end
         
     | 
| 
       28 
30 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                  File.basename(file, ".rb").split('_')[0...-1].map {|w| w.capitalize}.join
         
     | 
| 
      
 31 
     | 
    
         
            +
                def update_default_config(config_hash)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  @@default_config.merge!(config_hash)
         
     | 
| 
       32 
33 
     | 
    
         
             
                end
         
     | 
| 
       33 
     | 
    
         
            -
              end
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
              def self.configuration
         
     | 
| 
       36 
     | 
    
         
            -
                @configuration ||= Configuration.new
         
     | 
| 
       37 
     | 
    
         
            -
              end
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
              def self.configure
         
     | 
| 
       40 
     | 
    
         
            -
                yield(configuration)
         
     | 
| 
       41 
     | 
    
         
            -
              end
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
              def self.fetcher
         
     | 
| 
       44 
     | 
    
         
            -
                @fetcher ||= Fetcher.new(fiat_exchanges: configuration.fiat_adapters,
         
     | 
| 
       45 
     | 
    
         
            -
                                         crypto_exchanges: configuration.crypto_adapters,
         
     | 
| 
       46 
     | 
    
         
            -
                                         limit_sources_for_fiat_currencies: configuration.limit_sources_for_fiat_currencies)
         
     | 
| 
       47 
     | 
    
         
            -
              end
         
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
              def self.fetch_crypto(exchange, from, to)
         
     | 
| 
       50 
     | 
    
         
            -
                fetcher.fetch_crypto(exchange, from, to)
         
     | 
| 
       51 
     | 
    
         
            -
              end
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
              def self.fetch_fiat(from, to)
         
     | 
| 
       54 
     | 
    
         
            -
                fetcher.fetch_fiat(from, to)
         
     | 
| 
       55 
     | 
    
         
            -
              end
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
              def self.logger
         
     | 
| 
       58 
     | 
    
         
            -
                return @logger if @logger
         
     | 
| 
       59 
     | 
    
         
            -
                @logger = Logger.new(configuration.logger[:device])
         
     | 
| 
       60 
     | 
    
         
            -
                @logger.progname = "CurrencyRate"
         
     | 
| 
       61 
     | 
    
         
            -
                @logger.level = configuration.logger[:level]
         
     | 
| 
       62 
     | 
    
         
            -
                @logger.formatter = configuration.logger[:formatter] if configuration.logger[:formatter]
         
     | 
| 
       63 
     | 
    
         
            -
                @logger
         
     | 
| 
       64 
     | 
    
         
            -
              end
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
              def self.synchronizer
         
     | 
| 
       67 
     | 
    
         
            -
                @synchronizer ||= Synchronizer.new
         
     | 
| 
       68 
     | 
    
         
            -
              end
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
              def self.sync_crypto!
         
     | 
| 
       71 
     | 
    
         
            -
                synchronizer.sync_crypto!
         
     | 
| 
       72 
     | 
    
         
            -
              end
         
     | 
| 
       73 
34 
     | 
    
         | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 35 
     | 
    
         
            +
                def root
         
     | 
| 
      
 36 
     | 
    
         
            +
                  File.expand_path("../", File.dirname(__FILE__))
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
       77 
38 
     | 
    
         | 
| 
       78 
     | 
    
         
            -
              def self.sync!
         
     | 
| 
       79 
     | 
    
         
            -
                synchronizer.sync!
         
     | 
| 
       80 
39 
     | 
    
         
             
              end
         
     | 
| 
       81 
40 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
              def self.root
         
     | 
| 
       83 
     | 
    
         
            -
                File.expand_path("../", File.dirname(__FILE__))
         
     | 
| 
       84 
     | 
    
         
            -
              end
         
     | 
| 
       85 
41 
     | 
    
         
             
            end
         
     | 
    
        data/lib/storage/file_storage.rb
    CHANGED
    
    | 
         @@ -3,27 +3,32 @@ module CurrencyRate 
     | 
|
| 
       3 
3 
     | 
    
         
             
                attr_reader   :path
         
     | 
| 
       4 
4 
     | 
    
         
             
                attr_accessor :serializer
         
     | 
| 
       5 
5 
     | 
    
         | 
| 
       6 
     | 
    
         
            -
                def initialize(path:, serializer: nil)
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(path:, container:, serializer: nil)
         
     | 
| 
       7 
7 
     | 
    
         
             
                  @path       = path
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @container  = container
         
     | 
| 
       8 
9 
     | 
    
         
             
                  @serializer = serializer || Storage::YAMLSerializer.new
         
     | 
| 
       9 
10 
     | 
    
         
             
                end
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                def  
     | 
| 
       12 
     | 
    
         
            -
                   
     | 
| 
      
 12 
     | 
    
         
            +
                def exists?(adapter_name)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  File.exists? path_for(adapter_name)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def read(adapter_name)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  path = path_for adapter_name
         
     | 
| 
       13 
18 
     | 
    
         
             
                  @serializer.deserialize File.read(path)
         
     | 
| 
       14 
19 
     | 
    
         
             
                rescue StandardError => e
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
      
 20 
     | 
    
         
            +
                  @container.log(:error, e)
         
     | 
| 
       16 
21 
     | 
    
         
             
                  nil
         
     | 
| 
       17 
22 
     | 
    
         
             
                end
         
     | 
| 
       18 
23 
     | 
    
         | 
| 
       19 
     | 
    
         
            -
                def write( 
     | 
| 
       20 
     | 
    
         
            -
                  File.write path_for( 
     | 
| 
      
 24 
     | 
    
         
            +
                def write(adapter_name, data = "")
         
     | 
| 
      
 25 
     | 
    
         
            +
                  File.write path_for(adapter_name), @serializer.serialize(data)
         
     | 
| 
       21 
26 
     | 
    
         
             
                rescue StandardError => e
         
     | 
| 
       22 
     | 
    
         
            -
                   
     | 
| 
      
 27 
     | 
    
         
            +
                  @container.log(:error, e)
         
     | 
| 
       23 
28 
     | 
    
         
             
                end
         
     | 
| 
       24 
29 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                def path_for( 
     | 
| 
       26 
     | 
    
         
            -
                  File.join @path, "#{ 
     | 
| 
      
 30 
     | 
    
         
            +
                def path_for(adapter_name)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  File.join @path, "#{adapter_name}.yml"
         
     | 
| 
       27 
32 
     | 
    
         
             
                end
         
     | 
| 
       28 
33 
     | 
    
         
             
              end
         
     | 
| 
       29 
34 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module StringExtensions
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def to_snake_case
         
     | 
| 
      
 4 
     | 
    
         
            +
                self.to_s.gsub(/::/, '/').
         
     | 
| 
      
 5 
     | 
    
         
            +
                gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
         
     | 
| 
      
 6 
     | 
    
         
            +
                gsub(/([a-z\d])([A-Z])/,'\1_\2').
         
     | 
| 
      
 7 
     | 
    
         
            +
                tr("-", "_").
         
     | 
| 
      
 8 
     | 
    
         
            +
                downcase
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def to_camel_case
         
     | 
| 
      
 12 
     | 
    
         
            +
                words = self.to_s.split('_')
         
     | 
| 
      
 13 
     | 
    
         
            +
                words.map { |w| w[0].capitalize + w[1..-1] }.join
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            class String; include StringExtensions; end
         
     | 
| 
      
 19 
     | 
    
         
            +
            class Symbol; include StringExtensions; end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: currency-rate
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version:  
     | 
| 
      
 4 
     | 
    
         
            +
              version: 2.0.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Roman Snitko
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2020- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2020-12-01 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: http
         
     | 
| 
         @@ -94,19 +94,16 @@ files: 
     | 
|
| 
       94 
94 
     | 
    
         
             
            - LICENSE.txt
         
     | 
| 
       95 
95 
     | 
    
         
             
            - README.md
         
     | 
| 
       96 
96 
     | 
    
         
             
            - Rakefile
         
     | 
| 
       97 
     | 
    
         
            -
            - api_keys.yml.sample
         
     | 
| 
       98 
97 
     | 
    
         
             
            - currency-rate.gemspec
         
     | 
| 
       99 
98 
     | 
    
         
             
            - lib/adapter.rb
         
     | 
| 
       100 
99 
     | 
    
         
             
            - lib/adapters/crypto/binance_adapter.rb
         
     | 
| 
       101 
100 
     | 
    
         
             
            - lib/adapters/crypto/bitfinex_adapter.rb
         
     | 
| 
       102 
101 
     | 
    
         
             
            - lib/adapters/crypto/bitpay_adapter.rb
         
     | 
| 
       103 
102 
     | 
    
         
             
            - lib/adapters/crypto/bitstamp_adapter.rb
         
     | 
| 
       104 
     | 
    
         
            -
            - lib/adapters/crypto/btc_china_adapter.rb
         
     | 
| 
       105 
     | 
    
         
            -
            - lib/adapters/crypto/btc_e_adapter.rb
         
     | 
| 
       106 
103 
     | 
    
         
             
            - lib/adapters/crypto/coin_market_cap_adapter.rb
         
     | 
| 
       107 
104 
     | 
    
         
             
            - lib/adapters/crypto/coinbase_adapter.rb
         
     | 
| 
       108 
105 
     | 
    
         
             
            - lib/adapters/crypto/exmo_adapter.rb
         
     | 
| 
       109 
     | 
    
         
            -
            - lib/adapters/crypto/ 
     | 
| 
      
 106 
     | 
    
         
            +
            - lib/adapters/crypto/hit_BTC_adapter.rb
         
     | 
| 
       110 
107 
     | 
    
         
             
            - lib/adapters/crypto/huobi_adapter.rb
         
     | 
| 
       111 
108 
     | 
    
         
             
            - lib/adapters/crypto/kraken_adapter.rb
         
     | 
| 
       112 
109 
     | 
    
         
             
            - lib/adapters/crypto/localbitcoins_adapter.rb
         
     | 
| 
         @@ -120,15 +117,13 @@ files: 
     | 
|
| 
       120 
117 
     | 
    
         
             
            - lib/adapters/fiat/fixer_adapter.rb
         
     | 
| 
       121 
118 
     | 
    
         
             
            - lib/adapters/fiat/forge_adapter.rb
         
     | 
| 
       122 
119 
     | 
    
         
             
            - lib/adapters/fiat/free_forex_adapter.rb
         
     | 
| 
       123 
     | 
    
         
            -
            - lib/ 
     | 
| 
       124 
     | 
    
         
            -
            - lib/configuration.rb
         
     | 
| 
      
 120 
     | 
    
         
            +
            - lib/container.rb
         
     | 
| 
       125 
121 
     | 
    
         
             
            - lib/currency_rate.rb
         
     | 
| 
       126 
122 
     | 
    
         
             
            - lib/currency_rate/version.rb
         
     | 
| 
       127 
123 
     | 
    
         
             
            - lib/exceptions.rb
         
     | 
| 
       128 
     | 
    
         
            -
            - lib/fetcher.rb
         
     | 
| 
       129 
124 
     | 
    
         
             
            - lib/storage/file_storage.rb
         
     | 
| 
       130 
125 
     | 
    
         
             
            - lib/storage/serializers/yaml_serializer.rb
         
     | 
| 
       131 
     | 
    
         
            -
            - lib/ 
     | 
| 
      
 126 
     | 
    
         
            +
            - lib/utils/string_extensions.rb
         
     | 
| 
       132 
127 
     | 
    
         
             
            homepage: https://gitlab.com/hodlhodl-public/currency-rate
         
     | 
| 
       133 
128 
     | 
    
         
             
            licenses: []
         
     | 
| 
       134 
129 
     | 
    
         
             
            metadata:
         
     | 
| 
         @@ -150,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       150 
145 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       151 
146 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       152 
147 
     | 
    
         
             
            requirements: []
         
     | 
| 
       153 
     | 
    
         
            -
            rubygems_version: 3.1. 
     | 
| 
      
 148 
     | 
    
         
            +
            rubygems_version: 3.1.2
         
     | 
| 
       154 
149 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       155 
150 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       156 
151 
     | 
    
         
             
            summary: Converter for fiat and crypto currencies
         
     | 
    
        data/api_keys.yml.sample
    DELETED
    
    
| 
         @@ -1,18 +0,0 @@ 
     | 
|
| 
       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(value["ticker"]["last"].to_s)
         
     | 
| 
       13 
     | 
    
         
            -
                    result
         
     | 
| 
       14 
     | 
    
         
            -
                  end
         
     | 
| 
       15 
     | 
    
         
            -
                end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
              end
         
     | 
| 
       18 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,61 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module CurrencyRate
         
     | 
| 
       2 
     | 
    
         
            -
              class HitBTCAdapter < Adapter
         
     | 
| 
       3 
     | 
    
         
            -
                SUPPORTED_CURRENCIES = %w(ZRC EDG IHT AEON SOC HBAR ZRX OPT APPC DRGN PTOY
         
     | 
| 
       4 
     | 
    
         
            -
                                          XDN OKB CHSB NCT GUSD GET FUN EXP EMRX REV GHOST
         
     | 
| 
       5 
     | 
    
         
            -
                                          BMH SNC DTR ERD SCL HMQ ACT ETC QTUM MTX SBTC
         
     | 
| 
       6 
     | 
    
         
            -
                                          KIND SMT BTB SWFTC 1ST UTT AXPR NMR EVX IOTA XPRM
         
     | 
| 
       7 
     | 
    
         
            -
                                          STMX SALT DGB NTK AMM ALGO ORMEUS BDG BQX EKO FYP
         
     | 
| 
       8 
     | 
    
         
            -
                                          IPX HOT MG BTS ADX CRPT WAXP POA PLBT SHIP HTML
         
     | 
| 
       9 
     | 
    
         
            -
                                          BOX GNO UBT BTT ZEN VEO POA20 BCPT SRN XPR ETHBNT
         
     | 
| 
       10 
     | 
    
         
            -
                                          MANA QKC MLN FLP SOLO TRUE VSYS JST GNT BOS PHB
         
     | 
| 
       11 
     | 
    
         
            -
                                          ZEC ESH SWM NANO VIBE HVN SOLVE ELEC LRC AGI LNC
         
     | 
| 
       12 
     | 
    
         
            -
                                          WAVES WTC ONT STRAT GNX NEU BCN XPNT ECA ARDR KIN
         
     | 
| 
       13 
     | 
    
         
            -
                                          LSK USE IOTX CRO IDH LINK OAX CPT NGC XNS KEY TKY
         
     | 
| 
       14 
     | 
    
         
            -
                                          HSR TNT SMART TRST DCR WINGS GT MKR ONE DOGE ARN
         
     | 
| 
       15 
     | 
    
         
            -
                                          ACAT BMC RAISE EXM TIME REX FDZ HT MTH SCC BET
         
     | 
| 
       16 
     | 
    
         
            -
                                          DENT IDRT IPL ZAP CMCT TDP XAUR MTL NEBL SUSDT BAT
         
     | 
| 
       17 
     | 
    
         
            -
                                          STEEM CUR BYTZ PRO LOOM USD DRG DICE ADK COMP DRT
         
     | 
| 
       18 
     | 
    
         
            -
                                          XTZ WETH EURS CHZ NEO NPLC XCON LEVL PAX AIM PART
         
     | 
| 
       19 
     | 
    
         
            -
                                          PRE ERK HEDG FET PAXG DAG AVA CUTE NEXO DAY PITCH
         
     | 
| 
       20 
     | 
    
         
            -
                                          MITX NXT POWR PLR CVCOIN TUSD MYST DLT REM RLC DNA
         
     | 
| 
       21 
     | 
    
         
            -
                                          FOTA SBD ELF TEL C20 PNT CND UTK ASI CVC ETP ETH
         
     | 
| 
       22 
     | 
    
         
            -
                                          ZIL ARPA INK NPXS LEO MESH NIM DATX FXT PBT GST
         
     | 
| 
       23 
     | 
    
         
            -
                                          BSV GAS CBC MCO SENT GBX XRC POE SUR LOC WIKI PPT
         
     | 
| 
       24 
     | 
    
         
            -
                                          CVT APM LEND NUT DOV KMD AYA LUN XEM RVN BCD XMR
         
     | 
| 
       25 
     | 
    
         
            -
                                          NWC USG CLO NLC2 BBTC BERRY ART GRIN VITAE XBP OMG
         
     | 
| 
       26 
     | 
    
         
            -
                                          MDA KRL BCH POLY PLA BANCA ENJ TRIGX UUU PASS ANT
         
     | 
| 
       27 
     | 
    
         
            -
                                          LAMB BIZZ RFR AMB ROOBEE BST LCC RCN MIN BUSD DIT
         
     | 
| 
       28 
     | 
    
         
            -
                                          PPC AE IQ BNK CENNZ SUB OCN DGD VRA STX AERGO HGT
         
     | 
| 
       29 
     | 
    
         
            -
                                          TRAD IGNIS REP DAPP DNT CDT YCC SNGLS ICX PKT COV
         
     | 
| 
       30 
     | 
    
         
            -
                                          PAY ABYSS BLZ DAV TKN ERT SPC SEELE XZC MAID AUTO
         
     | 
| 
       31 
     | 
    
         
            -
                                          REN DATA AUC TV LAVA KAVA DAI DAPS ADA COCOS MITH
         
     | 
| 
       32 
     | 
    
         
            -
                                          SETH NRG PHX DASH VLX CHAT VET EOSDT ZSC KNC DGTX
         
     | 
| 
       33 
     | 
    
         
            -
                                          CEL SHORTUSD IOST BNB PBTT XMC EMC VIB BNT STORJ
         
     | 
| 
       34 
     | 
    
         
            -
                                          ATOM LCX SC FTT BTM XLM TRX CELR BRD DBIX ETN SNT
         
     | 
| 
       35 
     | 
    
         
            -
                                          MOF HEX XUC PLU FACE TNC SIG PXG BDP BTX TAU DCT
         
     | 
| 
       36 
     | 
    
         
            -
                                          YOYOW SYBC SWT MAN GLEEC EOS XVG NAV CURE XRP KICK
         
     | 
| 
       37 
     | 
    
         
            -
                                          BRDG LTC USDC MATIC FTX BTG PMA).freeze
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                ANCHOR_CURRENCY = "BTC".freeze
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                FETCH_URL = "https://api.hitbtc.com/api/2/public/ticker".freeze
         
     | 
| 
       42 
     | 
    
         
            -
             
     | 
| 
       43 
     | 
    
         
            -
                def normalize(data)
         
     | 
| 
       44 
     | 
    
         
            -
                  return nil unless super
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                  data.each_with_object({ "anchor" => ANCHOR_CURRENCY }) do |pair_info, result|
         
     | 
| 
       47 
     | 
    
         
            -
                    pair_name = pair_info["symbol"]
         
     | 
| 
       48 
     | 
    
         
            -
                    next unless pair_name.include?(ANCHOR_CURRENCY)
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                    key = pair_name.sub(ANCHOR_CURRENCY, "")
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                    result[key] =
         
     | 
| 
       53 
     | 
    
         
            -
                      if pair_name.index(ANCHOR_CURRENCY) == 0
         
     | 
| 
       54 
     | 
    
         
            -
                        BigDecimal(pair_info["last"])
         
     | 
| 
       55 
     | 
    
         
            -
                      else
         
     | 
| 
       56 
     | 
    
         
            -
                        1 / BigDecimal(pair_info["last"])
         
     | 
| 
       57 
     | 
    
         
            -
                      end
         
     | 
| 
       58 
     | 
    
         
            -
                  end
         
     | 
| 
       59 
     | 
    
         
            -
                end
         
     | 
| 
       60 
     | 
    
         
            -
              end
         
     | 
| 
       61 
     | 
    
         
            -
            end
         
     | 
| 
         @@ -1,35 +0,0 @@ 
     | 
|
| 
       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(rate["Rate"].to_s)
         
     | 
| 
       30 
     | 
    
         
            -
                  end
         
     | 
| 
       31 
     | 
    
         
            -
                  rates
         
     | 
| 
       32 
     | 
    
         
            -
                end
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
              end
         
     | 
| 
       35 
     | 
    
         
            -
            end
         
     | 
    
        data/lib/configuration.rb
    DELETED
    
    | 
         @@ -1,28 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module CurrencyRate
         
     | 
| 
       2 
     | 
    
         
            -
              class Configuration
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :api_keys
         
     | 
| 
       4 
     | 
    
         
            -
                attr_accessor :logger
         
     | 
| 
       5 
     | 
    
         
            -
                attr_accessor :crypto_adapters
         
     | 
| 
       6 
     | 
    
         
            -
                attr_accessor :fiat_adapters
         
     | 
| 
       7 
     | 
    
         
            -
                attr_accessor :connect_timeout
         
     | 
| 
       8 
     | 
    
         
            -
                attr_accessor :read_timeout
         
     | 
| 
       9 
     | 
    
         
            -
                attr_accessor :storage
         
     | 
| 
       10 
     | 
    
         
            -
                attr_accessor :limit_sources_for_fiat_currencies
         
     | 
| 
       11 
     | 
    
         
            -
                attr_accessor :crypto_currencies
         
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
                def initialize
         
     | 
| 
       14 
     | 
    
         
            -
                  @api_keys         = { }
         
     | 
| 
       15 
     | 
    
         
            -
                  @logger           = {
         
     | 
| 
       16 
     | 
    
         
            -
                                        device: $stdout,
         
     | 
| 
       17 
     | 
    
         
            -
                                        level:  :info,
         
     | 
| 
       18 
     | 
    
         
            -
                                        formatter: nil,
         
     | 
| 
       19 
     | 
    
         
            -
                                      }
         
     | 
| 
       20 
     | 
    
         
            -
                   @crypto_adapters = CurrencyRate.adapters :crypto
         
     | 
| 
       21 
     | 
    
         
            -
                   @fiat_adapters   = CurrencyRate.adapters :fiat
         
     | 
| 
       22 
     | 
    
         
            -
                   @connect_timeout = 4
         
     | 
| 
       23 
     | 
    
         
            -
                   @read_timeout    = 4
         
     | 
| 
       24 
     | 
    
         
            -
                   @limit_sources_for_fiat_currencies = {}
         
     | 
| 
       25 
     | 
    
         
            -
                   @crypto_currencies = []
         
     | 
| 
       26 
     | 
    
         
            -
                end
         
     | 
| 
       27 
     | 
    
         
            -
              end
         
     | 
| 
       28 
     | 
    
         
            -
            end
         
     | 
    
        data/lib/fetcher.rb
    DELETED
    
    | 
         @@ -1,80 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module CurrencyRate
         
     | 
| 
       2 
     | 
    
         
            -
              class Fetcher
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :storage
         
     | 
| 
       4 
     | 
    
         
            -
                attr_accessor :fiat_exchanges
         
     | 
| 
       5 
     | 
    
         
            -
                attr_accessor :crypto_exchanges
         
     | 
| 
       6 
     | 
    
         
            -
                attr_accessor :limit_sources_for_fiat_currencies
         
     | 
| 
       7 
     | 
    
         
            -
             
     | 
| 
       8 
     | 
    
         
            -
                def initialize(fiat_exchanges: nil, crypto_exchanges: nil, storage: nil, limit_sources_for_fiat_currencies: {})
         
     | 
| 
       9 
     | 
    
         
            -
                  @storage = storage || CurrencyRate.configuration.storage
         
     | 
| 
       10 
     | 
    
         
            -
                  raise CurrencyRate::StorageNotDefinedError unless @storage
         
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
                  @fiat_exchanges = fiat_exchanges || ["Yahoo", "Fixer", "Forge"]
         
     | 
| 
       13 
     | 
    
         
            -
                  @crypto_exchanges = crypto_exchanges || ["Bitstamp", "Binance"]
         
     | 
| 
       14 
     | 
    
         
            -
                  @limit_sources_for_fiat_currencies = limit_sources_for_fiat_currencies
         
     | 
| 
       15 
     | 
    
         
            -
                end
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
                def fetch_crypto(exchange, from, to)
         
     | 
| 
       18 
     | 
    
         
            -
                  from = from.strip.upcase
         
     | 
| 
       19 
     | 
    
         
            -
                  to = to.strip.upcase
         
     | 
| 
       20 
     | 
    
         
            -
                  rates = @storage.read(exchange)
         
     | 
| 
       21 
     | 
    
         
            -
             
     | 
| 
       22 
     | 
    
         
            -
                  if rates.nil?
         
     | 
| 
       23 
     | 
    
         
            -
                    CurrencyRate.logger.warn("Fetcher#fetch_crypto: rates for #{exchange} not found in storage <#{@storage.class.name}>")
         
     | 
| 
       24 
     | 
    
         
            -
                    return nil
         
     | 
| 
       25 
     | 
    
         
            -
                  end
         
     | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
                  rate = calculate_rate(rates, from, to)
         
     | 
| 
       28 
     | 
    
         
            -
                  return rate unless rate.nil?
         
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                  if to != "USD"
         
     | 
| 
       31 
     | 
    
         
            -
                    usd_fiat = fetch_fiat("USD", to)
         
     | 
| 
       32 
     | 
    
         
            -
                    return BigDecimal(rates["USD"] * usd_fiat) if usd_fiat && rates["USD"]
         
     | 
| 
       33 
     | 
    
         
            -
                  end
         
     | 
| 
       34 
     | 
    
         
            -
                  nil
         
     | 
| 
       35 
     | 
    
         
            -
                end
         
     | 
| 
       36 
     | 
    
         
            -
             
     | 
| 
       37 
     | 
    
         
            -
                def fetch_fiat(from, to)
         
     | 
| 
       38 
     | 
    
         
            -
                  from = from.strip.upcase
         
     | 
| 
       39 
     | 
    
         
            -
                  to = to.strip.upcase
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                  exchanges = @fiat_exchanges.dup
         
     | 
| 
       42 
     | 
    
         
            -
                  exchanges += @crypto_exchanges if is_crypto_currency?(from) || is_crypto_currency?(to)
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                  if(@limit_sources_for_fiat_currencies[from])
         
     | 
| 
       45 
     | 
    
         
            -
                    exchanges.select! { |ex| @limit_sources_for_fiat_currencies[from].include?(ex) }
         
     | 
| 
       46 
     | 
    
         
            -
                  end
         
     | 
| 
       47 
     | 
    
         
            -
                  if(@limit_sources_for_fiat_currencies[to])
         
     | 
| 
       48 
     | 
    
         
            -
                    exchanges.select! { |ex| @limit_sources_for_fiat_currencies[to].include?(ex) }
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  exchanges.each do |exchange|
         
     | 
| 
       52 
     | 
    
         
            -
                    rates = @storage.read(exchange)
         
     | 
| 
       53 
     | 
    
         
            -
                    next if rates.nil?
         
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
                    rate = calculate_rate(rates, from, to)
         
     | 
| 
       56 
     | 
    
         
            -
                    return rate unless rate.nil?
         
     | 
| 
       57 
     | 
    
         
            -
                  end
         
     | 
| 
       58 
     | 
    
         
            -
                  nil
         
     | 
| 
       59 
     | 
    
         
            -
                end
         
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
                private
         
     | 
| 
       62 
     | 
    
         
            -
             
     | 
| 
       63 
     | 
    
         
            -
                  def calculate_rate(rates, from, to)
         
     | 
| 
       64 
     | 
    
         
            -
                    anchor = rates.delete("anchor")
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
                    return BigDecimal(rates[to])               if anchor == from && rates[to]
         
     | 
| 
       67 
     | 
    
         
            -
                    return BigDecimal(1 / rates[from])         if anchor == to && rates[from]
         
     | 
| 
       68 
     | 
    
         
            -
                    return BigDecimal(rates[to] / rates[from]) if rates[from] && rates[to]
         
     | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
                    CurrencyRate.logger.warn("Fetcher: rate for #{from}_#{to} not found.")
         
     | 
| 
       71 
     | 
    
         
            -
                    nil
         
     | 
| 
       72 
     | 
    
         
            -
                  end
         
     | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
                  def is_crypto_currency?(currency)
         
     | 
| 
       75 
     | 
    
         
            -
                    CurrencyRate.configuration.crypto_currencies.include?(currency)
         
     | 
| 
       76 
     | 
    
         
            -
                  end
         
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
              end
         
     | 
| 
       79 
     | 
    
         
            -
            end
         
     | 
| 
       80 
     | 
    
         
            -
             
     | 
    
        data/lib/synchronizer.rb
    DELETED
    
    | 
         @@ -1,54 +0,0 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            module CurrencyRate
         
     | 
| 
       2 
     | 
    
         
            -
              class Synchronizer
         
     | 
| 
       3 
     | 
    
         
            -
                attr_accessor :storage
         
     | 
| 
       4 
     | 
    
         
            -
             
     | 
| 
       5 
     | 
    
         
            -
                def initialize(storage: nil)
         
     | 
| 
       6 
     | 
    
         
            -
                  @storage = storage || CurrencyRate.configuration.storage
         
     | 
| 
       7 
     | 
    
         
            -
                  raise CurrencyRate::StorageNotDefinedError unless @storage
         
     | 
| 
       8 
     | 
    
         
            -
                end
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
                def sync_fiat!
         
     | 
| 
       11 
     | 
    
         
            -
                  _sync CurrencyRate.configuration.fiat_adapters
         
     | 
| 
       12 
     | 
    
         
            -
                end
         
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
                def sync_crypto!
         
     | 
| 
       15 
     | 
    
         
            -
                  _sync CurrencyRate.configuration.crypto_adapters
         
     | 
| 
       16 
     | 
    
         
            -
                end
         
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                def sync!
         
     | 
| 
       19 
     | 
    
         
            -
                  fiat = sync_fiat!
         
     | 
| 
       20 
     | 
    
         
            -
                  crypto = sync_crypto!
         
     | 
| 
       21 
     | 
    
         
            -
                  [fiat[0] | crypto[0], fiat[1] | crypto[1]]
         
     | 
| 
       22 
     | 
    
         
            -
                end
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                private
         
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                def _sync(adapters)
         
     | 
| 
       27 
     | 
    
         
            -
                  successfull = []
         
     | 
| 
       28 
     | 
    
         
            -
                  failed = []
         
     | 
| 
       29 
     | 
    
         
            -
                  adapters.each do |provider|
         
     | 
| 
       30 
     | 
    
         
            -
                    adapter_name = "#{provider}Adapter"
         
     | 
| 
       31 
     | 
    
         
            -
                    begin
         
     | 
| 
       32 
     | 
    
         
            -
                      adapter = CurrencyRate.const_get(adapter_name).instance
         
     | 
| 
       33 
     | 
    
         
            -
                      rates = adapter.fetch_rates
         
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
                      unless rates
         
     | 
| 
       36 
     | 
    
         
            -
                        CurrencyRate.logger.warn("Synchronizer#sync!: rates for #{provider} not found")
         
     | 
| 
       37 
     | 
    
         
            -
                        failed.push(provider)
         
     | 
| 
       38 
     | 
    
         
            -
                        next
         
     | 
| 
       39 
     | 
    
         
            -
                      end
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                      exchange_name = provider
         
     | 
| 
       42 
     | 
    
         
            -
                      @storage.write(exchange_name, rates)
         
     | 
| 
       43 
     | 
    
         
            -
                      successfull.push(provider)
         
     | 
| 
       44 
     | 
    
         
            -
                    rescue StandardError => e
         
     | 
| 
       45 
     | 
    
         
            -
                      failed.push({ provider: e })
         
     | 
| 
       46 
     | 
    
         
            -
                      CurrencyRate.logger.error(e)
         
     | 
| 
       47 
     | 
    
         
            -
                      next
         
     | 
| 
       48 
     | 
    
         
            -
                    end
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
                  [successfull, failed]
         
     | 
| 
       51 
     | 
    
         
            -
                end
         
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
              end
         
     | 
| 
       54 
     | 
    
         
            -
            end
         
     |