class BitexBot::OpeningFlow

Any arbitrage workflow has 2 stages, opening positions and then closing them. The OpeningFlow stage places an order on bitex, detecting and storing all transactions spawn from that order as Open positions.

Public Class Methods

active() click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 18
def self.active
  where.not(status: :finalised)
end
active_transaction?(transaction, threshold) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 157
def self.active_transaction?(transaction, threshold)
  threshold.present? && transaction.timestamp < (threshold - 30.minutes).to_i
end
calc_remote_value(maker_fee, taker_fee, taker_orders, taker_transactions) click to toggle source

create_for_market helpers

# File lib/bitex_bot/models/opening_flow.rb, line 80
def self.calc_remote_value(maker_fee, taker_fee, taker_orders, taker_transactions)
  value_to_use_needed = (value_to_use + maker_plus(maker_fee)) / (1 - taker_fee / 100)
  safest_price = safest_price(taker_transactions, taker_orders, value_to_use_needed)
  remote_value = remote_value_to_use(value_to_use_needed, safest_price)

  [remote_value, safest_price]
end
create_for_market(taker_balance, taker_orders, taker_transactions, maker_fee, taker_fee, store) click to toggle source

This use hooks methods, these must be defined in the subclass:

#maker_price
#order_class
#remote_value_to_use
#safest_price
#value_to_use

rubocop:disable Metrics/AbcSize

# File lib/bitex_bot/models/opening_flow.rb, line 34
def self.create_for_market(taker_balance, taker_orders, taker_transactions, maker_fee, taker_fee, store)
  self.store = store

  remote_value, safest_price = calc_remote_value(maker_fee, taker_fee, taker_orders, taker_transactions)
  Robot.log(
    :info,
    "Opening: Need #{taker_specie_to_spend} #{remote_value.truncate(8)} on #{Robot.taker.name} taker,"\
    " has #{taker_balance.truncate(8)}."
  )

  unless enough_remote_funds?(taker_balance, remote_value)
    raise CannotCreateFlow,
          "Needed #{remote_value} but you only have #{taker_specie_to_spend} #{taker_balance} on your taker market."
  end

  price = maker_price(remote_value)

  order = create_order!(price)
  unless enough_funds?(order)
    raise CannotCreateFlow,
          "Needed #{maker_specie_to_spend} #{value_per_order} on #{Robot.maker.name} maker to place this #{order_class}"\
          " but you only have #{maker_specie_to_spend} #{available_maker_balance}."
  end

  flow = create!(
    price: price,
    value_to_use: value_to_use,
    suggested_closing_price: safest_price,
    status: 'executing',
    order_id: order.id
  )

  Robot.log(
    :info,
    "Opening: Placed #{order_class} ##{order.id} #{maker_specie_to_spend} #{value_per_order} @ #{price.truncate(2)}."\
    " (#{maker_specie_to_obtain} #{remote_value})."\
    " #{name.demodulize}##{flow.id} suggests closing price #{Robot.taker.quote.upcase}"\
    " #{flow.suggested_closing_price}."
  )
  flow
rescue StandardError => e
  raise CannotCreateFlow, e.message
end
create_open_position!(transaction, flow) click to toggle source

sync_open_positions helpers rubocop:disable Metrics/AbcSize

# File lib/bitex_bot/models/opening_flow.rb, line 125
def self.create_open_position!(transaction, flow)
  Robot.log(
    :info,
    "Opening: #{self} ##{flow.id} was hit for #{Robot.maker.base.upcase} #{transaction.raw.quantity}"\
    " @ #{Robot.maker.quote.upcase} #{transaction.price}. Creating #{open_position_class}..."
  )

  open_position_class.create!(
    transaction_id: transaction.id,
    price: transaction.price,
    amount: transaction.amount,
    quantity: transaction.raw.quantity,
    opening_flow: flow
  )
end
create_order!(maker_price) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 88
def self.create_order!(maker_price)
  Robot.maker.send_order(order_type, maker_price, value_per_order, true)
rescue StandardError => e
  raise CannotCreateFlow, e.message
end
enough_funds?(order) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 94
def self.enough_funds?(order)
  !order.reason.to_s.inquiry.not_enough_funds?
end
enough_remote_funds?(taker_balance, remote_value) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 98
def self.enough_remote_funds?(taker_balance, remote_value)
  taker_balance >= remote_value
end
expected_kind_transaction?(transaction) click to toggle source

sought_transaction helpers

# File lib/bitex_bot/models/opening_flow.rb, line 153
def self.expected_kind_transaction?(transaction)
  transaction.raw.is_a?(transaction_class)
end
expected_order_book?(maker_transaction) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 165
def self.expected_order_book?(maker_transaction)
  maker_transaction.raw.order_book.to_s == Robot.maker.base_quote
end
maker_plus(fee) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 102
def self.maker_plus(fee)
  value_to_use * fee / 100
end
old_active() click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 22
def self.old_active
  active.where('created_at < ?', Settings.time_to_live.seconds.ago)
end
open_position?(transaction) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 161
def self.open_position?(transaction)
  open_position_class.find_by_transaction_id(transaction.id)
end
sought_transaction?(threshold, transaction) click to toggle source

This use hooks methods, these must be defined in the subclass:

#transaction_class
# File lib/bitex_bot/models/opening_flow.rb, line 144
def self.sought_transaction?(threshold, transaction)
  expected_kind_transaction?(transaction) &&
    !active_transaction?(transaction, threshold) &&
    !open_position?(transaction) &&
    expected_order_book?(transaction)
end
sync_open_positions() click to toggle source

Buys on bitex represent open positions, we mirror them locally so that we can plan on how to close them. This use hooks methods, these must be defined in the subclass:

#transaction_order_id(transaction)
#open_position_class
# File lib/bitex_bot/models/opening_flow.rb, line 111
def self.sync_open_positions
  threshold = open_position_class.order('created_at DESC').first.try(:created_at)
  Robot.maker.transactions.map do |transaction|
    next unless sought_transaction?(threshold, transaction)

    flow = find_by_order_id(transaction_order_id(transaction))
    next unless flow.present?

    create_open_position!(transaction, flow)
  end.compact
end

Public Instance Methods

finalise!() click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 183
def finalise!
  order = order_class.find(order_id)
  canceled_or_completed?(order) ? do_finalize : do_cancel(order)
end

Private Instance Methods

canceled_or_completed?(order) click to toggle source

finalise! helpers

# File lib/bitex_bot/models/opening_flow.rb, line 191
def canceled_or_completed?(order)
  %i[cancelled completed].any? { |status| order.status == status }
end
do_cancel(order) click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 200
def do_cancel(order)
  Robot.log(:info, "Opening: #{order_class} ##{order_id} canceled.")
  order.cancel!
  settling! unless settling?
end
do_finalize() click to toggle source
# File lib/bitex_bot/models/opening_flow.rb, line 195
def do_finalize
  Robot.log(:info, "Opening: #{order_class} ##{order_id} finalised.")
  finalised!
end