class Devilicious::Arbitrer

Public Instance Methods

markets() click to toggle source
# File lib/devilicious/arbitrer.rb, line 3
def markets
  @markets ||= [

    Market::Kraken,
    Market::BitcoinDe,
    Market::BtcE,
    Market::HitBtc,
    Market::Bitcurex,
    Market::Bitstamp,
    Market::BitNz,
    Market::AnxBtc,

  ].map { |market| market.new }
end
run!() click to toggle source
# File lib/devilicious/arbitrer.rb, line 18
def run!
  @market_queue = []
  spawn_observers!

  loop do
    sleep 1 while @market_queue.empty?

    market_1 = @market_queue.shift

    markets.each do |market_2|
      next if market_1 == market_2

      if market_1.order_book.nil? || market_2.order_book.nil?
        Log.debug "Order book(s) not available, skipping"
        next
      end

      # dup everything to avoid race conditions while calculating opportunities
      order_book_1 = market_1.order_book.dup
      order_book_2 = market_2.order_book.dup
      order_book_1.market = market_1.dup
      order_book_2.market = market_2.dup

      check_for_opportunity(order_book_1, order_book_2)
      check_for_opportunity(order_book_2, order_book_1)
    end
  end
end

Private Instance Methods

check_for_opportunity(order_book_1, order_book_2) click to toggle source
# File lib/devilicious/arbitrer.rb, line 79
def check_for_opportunity(order_book_1, order_book_2)
  Log.debug "Checking opportunity buying from #{order_book_1.market} and selling at #{order_book_2.market}... "
  if opportunity = find_best_opportunity(order_book_1, order_book_2)
    threshold = Money.new(Devilicious.config.beep_profit_threshold, Devilicious.config.default_fiat_currency)
    if threshold > 0 && opportunity.profit >= threshold
      Thread.new do
        3.times { system "/usr/bin/aplay", "-q", "#{__dir__}/sounds/boom.wav" }
      end
    end

    formatter = Formatter.list[Devilicious.config.formatter]
    formatter.output(opportunity)
  end
end
find_best_opportunity(order_book_1, order_book_2) click to toggle source
# File lib/devilicious/arbitrer.rb, line 94
def find_best_opportunity(order_book_1, order_book_2)
  return unless order_book_1.lowest_ask.price < order_book_2.highest_bid.price

  initial_ask_offer = order_book_1.weighted_asks_up_to(order_book_2.highest_bid.price)
  initial_bid_offer = order_book_2.weighted_bids_down_to(order_book_1.lowest_ask.price)

  _dummy = initial_ask_offer.price + initial_bid_offer.price # NOTE: will raise if currency mismatch

  max_volume = [initial_ask_offer.volume, initial_bid_offer.volume].min

  best_offer_limited_volume = find_best_volume(
    order_book_1, order_book_2,
    [max_volume, Devilicious.config.max_volume].min
  )

  best_offer_unlimited_volume = find_best_volume(
    order_book_1, order_book_2,
    [max_volume, BigDecimal.new("22E6")].min
  )

  best_volume = if best_offer_limited_volume.profit > best_offer_unlimited_volume.profit
    best_offer_limited_volume
  else
    best_offer_unlimited_volume
  end.volume

  return if best_offer_limited_volume.profit < Devilicious.config.min_profit

  best_offer_limited_volume.best_volume = best_volume

  best_offer_limited_volume
end
find_best_volume(order_book_1, order_book_2, max_volume) click to toggle source
# File lib/devilicious/arbitrer.rb, line 127
def find_best_volume(order_book_1, order_book_2, max_volume)
  volume = best_volume = BigDecimal.new(Devilicious.config.min_volume)
  best_profit = 0

  while volume <= max_volume
    ask_offer = order_book_1.min_ask_price_for_volume(order_book_2.highest_bid.price, volume)
    bid_offer = order_book_2.max_bid_price_for_volume(order_book_1.lowest_ask.price, volume)

    fee = (
      ask_offer.price * order_book_1.market.trade_fee +
      bid_offer.price * order_book_2.market.trade_fee
    ) * volume

    profit = (bid_offer.price - ask_offer.price) * volume - fee

    if profit > best_profit
      best_profit, best_volume, best_profit_fee = profit, volume, fee
    end

    volume += BigDecimal.new("0.1")
  end

  OpenStruct.new(
    order_book_1: order_book_1,
    order_book_2: order_book_2,
    ask_offer: order_book_1.min_ask_price_for_volume(order_book_2.highest_bid.price, best_volume),
    bid_offer: order_book_2.max_bid_price_for_volume(order_book_1.lowest_ask.price, best_volume),
    volume: best_volume,
    profit: best_profit,
    fee: best_profit_fee,
  )
end
spawn_observers!() click to toggle source
# File lib/devilicious/arbitrer.rb, line 49
def spawn_observers!
  Thread.abort_on_exception = true
  trap("EXIT", -> { Thread.list.each(&:kill) } )

  Thread.new do
    timeout = Devilicious.config.market_refresh_rate

    loop do
      threads = markets.map do |market|
        Log.debug "Refreshing order book for #{market}..."
        Thread.new { market.refresh_order_book!; @market_queue << market }
      end

      sleep timeout

      alive_threads = threads.select(&:alive?)
      unless alive_threads.empty?
        Log.warn "Timeout after #{timeout} seconds for #{alive_threads.size} observer threads"
      end

      threads.each do |thread|
        thread.abort_on_exception = false
        thread.kill
      end

      threads.each(&:join)
    end
  end
end