class Cryptoexchange::Client

Public Class Methods

new(ticker_ttl: 3, cache_size: 200) click to toggle source
# File lib/cryptoexchange/client.rb, line 3
def initialize(ticker_ttl: 3, cache_size: 200)
  LruTtlCache.ticker_cache(ticker_ttl, cache_size)
end

Public Instance Methods

available_exchanges() click to toggle source
# File lib/cryptoexchange/client.rb, line 37
def available_exchanges
  folder_names = Dir[File.join(File.dirname(__dir__), 'cryptoexchange', 'exchanges', '**')]
  folder_names.map do |folder_name|
    folder_name.split('/').last
  end.sort
end
exchange_for(currency) click to toggle source
# File lib/cryptoexchange/client.rb, line 44
def exchange_for(currency)
  exchanges = []
  available_exchanges.each do |exchange|
    pairs = pairs(exchange)
    next if pairs.is_a?(Hash) && !pairs[:error].empty?
    pairs.each do |pair|
      if [pair.base, pair.target].include?(currency.upcase)
        exchanges << exchange
        break
      end
    end
  end
  exchanges.uniq.sort
end
order_book(market_pair) click to toggle source
# File lib/cryptoexchange/client.rb, line 59
def order_book(market_pair)
  exchange = market_pair.market
  market_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Services::OrderBook"
  market_class = Object.const_get(market_classname)
  order_book = market_class.new

  if market_class.supports_individual_ticker_query?
    order_book.fetch(market_pair)
  else
    order_books = order_book.fetch
    order_books.find do |o|
      o.base.casecmp(market_pair.base) == 0 &&
        o.target.casecmp(market_pair.target) == 0
    end
  end
end
order_book_stream(market_pair:, onopen: nil, onmessage:, onclose: nil) click to toggle source
# File lib/cryptoexchange/client.rb, line 104
def order_book_stream(market_pair:, onopen: nil, onmessage:, onclose: nil)
  fetch_stream(
    market_pair: market_pair,
    onopen: onopen,
    onmessage: onmessage,
    onclose: onclose,
    stream_type: 'order_book'
  )
end
pairs(exchange) click to toggle source
# File lib/cryptoexchange/client.rb, line 13
def pairs(exchange)
  pairs_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Services::Pairs"
  pairs_class = Object.const_get(pairs_classname)
  pairs_object = pairs_class.new
  pairs_object.fetch
end
ticker(market_pair) click to toggle source
# File lib/cryptoexchange/client.rb, line 20
def ticker(market_pair)
  exchange = market_pair.market
  market_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Services::Market"
  market_class = Object.const_get(market_classname)
  market = market_class.new

  if market_class.supports_individual_ticker_query?
    market.fetch(market_pair)
  else
    tickers = market.fetch
    tickers.find do |t|
      t.base.casecmp(market_pair.base) == 0 &&
        t.target.casecmp(market_pair.target) == 0
    end
  end
end
ticker_stream(market_pair:, onopen: nil, onmessage:, onclose: nil) click to toggle source
# File lib/cryptoexchange/client.rb, line 84
def ticker_stream(market_pair:, onopen: nil, onmessage:, onclose: nil)
  fetch_stream(
    market_pair: market_pair,
    onopen: onopen,
    onmessage: onmessage,
    onclose: onclose,
    stream_type: 'market'
  )
end
trade_page_url(exchange, args={}) click to toggle source
# File lib/cryptoexchange/client.rb, line 7
def trade_page_url(exchange, args={})
  pairs_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Market"
  pairs_class = Object.const_get(pairs_classname)
  pairs_class.trade_page_url(base: args[:base], target: args[:target])
end
trade_stream(market_pair:, onopen: nil, onmessage:, onclose: nil) click to toggle source
# File lib/cryptoexchange/client.rb, line 94
def trade_stream(market_pair:, onopen: nil, onmessage:, onclose: nil)
  fetch_stream(
    market_pair: market_pair,
    onopen: onopen,
    onmessage: onmessage,
    onclose: onclose,
    stream_type: 'trade'
  )
end
trades(market_pair) click to toggle source
# File lib/cryptoexchange/client.rb, line 76
def trades(market_pair)
  exchange = market_pair.market
  market_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Services::Trades"
  market_class = Object.const_get(market_classname)
  trades = market_class.new
  trades.fetch(market_pair)
end

Private Instance Methods

fetch_stream(market_pair:, onopen: nil, onmessage:, onclose: nil, stream_type:) click to toggle source

NOTE: # ruby-doc.org/core-2.5.0/Thread.html#method-c-abort_on_exception When set to true, if any thread is aborted by an exception, the raised exception will be re-raised in the main thread.

# File lib/cryptoexchange/client.rb, line 118
def fetch_stream(market_pair:, onopen: nil, onmessage:, onclose: nil, stream_type:)
  fail 'stream_type must be one of "market", "trade", "order_book"' unless %w(market trade order_book).include?(stream_type)

  exchange         = market_pair.market
  stream_classname = "Cryptoexchange::Exchanges::#{StringHelper.camelize(exchange)}::Services::#{StringHelper.camelize(stream_type)}Stream"
  stream_class     = Object.const_get(stream_classname)
  stream           = stream_class.new

  Thread.new do
    EM.run do
      ws = WebSocket::EventMachine::Client.connect(uri: stream.url)

      ws.onopen do
        onopen.call if onopen
        ws.send(stream.subscribe_event(market_pair))
      end

      ws.onmessage do |message, _|
        message = JSON.parse(message)

        if stream.valid_message?(message)
          onmessage.call(stream.parse_message(message, market_pair))
        end
      end

      ws.onclose do
        onclose.call if onclose
      end
    end
  end
end