module BitexBot

Get version and bitex-bot as user-agent

Constants

Settings
VERSION

Public Class Methods

start_robot() click to toggle source
# File lib/bitex_bot/robot.rb, line 72
def self.start_robot
  setup
  log(:info, "Loading trading robot, ctrl+c *once* to exit gracefully.\n")
  new
end
user_agent() click to toggle source
# File lib/bitex_bot.rb, line 29
def self.user_agent
  "Bitexbot/#{VERSION} (https://github.com/bitex-la/bitex-bot)"
end

Public Instance Methods

active_closing_flows?() click to toggle source

rubocop:enable Metrics/AbcSize

# File lib/bitex_bot/robot.rb, line 104
def active_closing_flows?
  [BuyClosingFlow, SellClosingFlow].map(&:active).any?(&:exists?)
end
active_opening_flows?() click to toggle source
# File lib/bitex_bot/robot.rb, line 108
def active_opening_flows?
  [BuyOpeningFlow, SellOpeningFlow].map(&:active).any?(&:exists?)
end
store() click to toggle source

The trader has a Store

# File lib/bitex_bot/robot.rb, line 113
def store
  @store ||= Store.first || Store.create
end
trade!() click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/bitex_bot/robot.rb, line 79
def trade!
  sync_opening_flows if active_opening_flows?
  finalise_some_opening_flows
  shutdown! if shutdable?
  start_closing_flows if open_positions?
  sync_closing_flows if active_closing_flows?
  start_opening_flows_if_needed
rescue CannotCreateFlow => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
  sleep_for(60 * 3)
rescue Curl::Err::TimeoutError => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
  sleep_for(15)
rescue OrderNotFound => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
rescue ApiWrapperError => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
rescue OrderArgumentError => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
rescue StandardError => e
  notify("#{e.class} - #{e.message}\n\n#{e.backtrace.join("\n")}")
  sleep_for(60 * 2)
end

Private Instance Methods

active_flows(opening_flow_class) click to toggle source
# File lib/bitex_bot/robot.rb, line 144
def active_flows(opening_flow_class)
  turn_off? ? opening_flow_class.active : opening_flow_class.old_active
end
active_flows?() click to toggle source
# File lib/bitex_bot/robot.rb, line 132
def active_flows?
  active_opening_flows? || active_closing_flows?
end
alert?(currency, flag) click to toggle source
# File lib/bitex_bot/robot.rb, line 229
def alert?(currency, flag)
  return unless store.send("#{currency}_#{flag}").present?

  balance(currency) <= store.send("#{currency}_#{flag}")
end
balance(currency) click to toggle source
# File lib/bitex_bot/robot.rb, line 235
def balance(currency)
  fx_rate = currency == :fiat ? Settings.buying_fx_rate : 1
  store.send("maker_#{currency}") / fx_rate + store.send("taker_#{currency}")
end
check_balance_warning() click to toggle source
# File lib/bitex_bot/robot.rb, line 224
def check_balance_warning
  notify_balance_warning(maker.base, balance(:crypto), store.crypto_warning) if alert?(:crypto, :warning)
  notify_balance_warning(maker.quote, balance(:fiat), store.fiat_warning) if alert?(:fiat, :warning)
end
expired_last_warning?() click to toggle source

rubocop:enable Metrics/AbcSize

# File lib/bitex_bot/robot.rb, line 215
def expired_last_warning?
  store.last_warning.nil? || store.last_warning < 30.minutes.ago
end
finalise_some_opening_flows() click to toggle source
# File lib/bitex_bot/robot.rb, line 140
def finalise_some_opening_flows
  [BuyOpeningFlow, SellOpeningFlow].each { |kind| active_flows(kind).each(&:finalise!) }
end
log_balances(header) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/bitex_bot/robot.rb, line 205
def log_balances(header)
  log(
    :info,
    "#{header}\n"\
    "Store: #{maker.name} maker - #{maker.base.upcase}: #{store.maker_crypto}, #{maker.quote.upcase}: #{store.maker_fiat}.\n"\
    "Store: #{taker.name} taker - #{taker.base.upcase}: #{store.taker_crypto}, #{taker.quote.upcase}: #{store.taker_fiat}.\n"
  )
end
new_mail(subj, message) click to toggle source
# File lib/bitex_bot/robot.rb, line 254
def new_mail(subj, message)
  Mail.new do
    from Settings.mailer.from
    to Settings.mailer.to
    subject subj
    body message
  end
end
notify(message, subj = 'Notice from your robot trader') click to toggle source
# File lib/bitex_bot/robot.rb, line 245
def notify(message, subj = 'Notice from your robot trader')
  log(:info, "Sending mail with subject: #{subj}\n\n#{message}")
  return unless Settings.mailer.present?

  new_mail(subj, message).tap do |mail|
    mail.delivery_method(Settings.mailer.delivery_method.to_sym, Settings.mailer.options.to_hash)
  end.deliver!
end
notify_balance_warning(currency, amount, warning_amount) click to toggle source
# File lib/bitex_bot/robot.rb, line 240
def notify_balance_warning(currency, amount, warning_amount)
  notify("#{currency.upcase} balance is too low, it's #{amount}, make it #{warning_amount} to stop this warning.")
  store.update(last_warning: Time.now)
end
open_positions?() click to toggle source
# File lib/bitex_bot/robot.rb, line 152
def open_positions?
  [OpenBuy, OpenSell].map(&:open).any?(&:exists?)
end
recent_operations() click to toggle source

rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/bitex_bot/robot.rb, line 187
def recent_operations
  threshold = (Settings.time_to_live / 2).seconds.ago
  [BuyOpeningFlow, SellOpeningFlow].map { |kind| kind.active.where('created_at > ?', threshold).first }
end
shutdable?() click to toggle source
# File lib/bitex_bot/robot.rb, line 123
def shutdable?
  !(active_flows? || open_positions?) && turn_off?
end
shutdown!() click to toggle source
# File lib/bitex_bot/robot.rb, line 127
def shutdown!
  log(:info, 'Shutdown completed')
  exit
end
start_closing_flows() click to toggle source
# File lib/bitex_bot/robot.rb, line 148
def start_closing_flows
  [BuyClosingFlow, SellClosingFlow].each(&:close_open_positions)
end
start_opening_flows_if_needed() click to toggle source

rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

# File lib/bitex_bot/robot.rb, line 161
def start_opening_flows_if_needed
  return log(:debug, 'Not placing new orders, because Store is held') if store.reload.hold?
  return log(:debug, 'Not placing new orders, has active closing flows.') if active_closing_flows?
  return log(:debug, 'Not placing new orders, shutting down.') if turn_off?

  recent_buying, recent_selling = recent_operations
  return log(:debug, 'Not placing new orders, recent ones exist.') if recent_buying && recent_selling

  maker_balance = with_cooldown { maker.balance }
  taker_balance = with_cooldown { taker.balance }

  sync_log_and_store(maker_balance, taker_balance)
  log_balances('Store: Current balances.')

  check_balance_warning if expired_last_warning?
  return if stop_opening_flows?

  order_book = with_cooldown { taker.order_book }
  transactions = with_cooldown { taker.transactions }

  args = [transactions, maker_balance.fee, taker_balance.fee, store]
  BuyOpeningFlow.create_for_market(*[taker_balance.crypto.available, order_book.bids] + args) unless recent_buying
  SellOpeningFlow.create_for_market(*[taker_balance.fiat.available, order_book.asks] + args) unless recent_selling
end
stop_opening_flows?() click to toggle source
# File lib/bitex_bot/robot.rb, line 219
def stop_opening_flows?
  (log(:info, "Opening: Not placing new orders, #{maker.quote.upcase} target not met") if alert?(:fiat, :stop)) ||
    (log(:info, "Opening: Not placing new orders, #{maker.base.upcase} target not met") if alert?(:crypto, :stop))
end
sync_closing_flows() click to toggle source
# File lib/bitex_bot/robot.rb, line 156
def sync_closing_flows
  [BuyClosingFlow, SellClosingFlow].each { |kind| kind.active.each(&:sync_closed_positions) }
end
sync_log_and_store(maker_balance, taker_balance) click to toggle source
# File lib/bitex_bot/robot.rb, line 192
def sync_log_and_store(maker_balance, taker_balance)
  log_balances('Store: Updating log, maker and taker balances...')
  file = Settings.log.try(:file)
  last_log = `tail -c 61440 #{file}` if file.present?

  store.update(
    maker_fiat: maker_balance.fiat.total, maker_crypto: maker_balance.crypto.total,
    taker_fiat: taker_balance.fiat.total, taker_crypto: taker_balance.crypto.total,
    log: last_log
  )
end
sync_opening_flows() click to toggle source
# File lib/bitex_bot/robot.rb, line 119
def sync_opening_flows
  [SellOpeningFlow, BuyOpeningFlow].each(&:sync_open_positions)
end
turn_off?() click to toggle source
# File lib/bitex_bot/robot.rb, line 136
def turn_off?
  self.class.graceful_shutdown
end