bitex_bot 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +63 -0
  3. data/.rubocop.yml +33 -0
  4. data/Gemfile +1 -1
  5. data/Rakefile +1 -1
  6. data/bin/bitex_bot +1 -1
  7. data/bitex_bot.gemspec +34 -34
  8. data/lib/bitex_bot/database.rb +67 -67
  9. data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
  10. data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
  11. data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
  12. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
  13. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
  14. data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
  15. data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
  16. data/lib/bitex_bot/models/close_buy.rb +2 -2
  17. data/lib/bitex_bot/models/closing_flow.rb +98 -79
  18. data/lib/bitex_bot/models/open_buy.rb +11 -10
  19. data/lib/bitex_bot/models/open_sell.rb +11 -10
  20. data/lib/bitex_bot/models/opening_flow.rb +157 -99
  21. data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
  22. data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
  23. data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
  24. data/lib/bitex_bot/models/store.rb +3 -1
  25. data/lib/bitex_bot/robot.rb +203 -176
  26. data/lib/bitex_bot/settings.rb +71 -12
  27. data/lib/bitex_bot/version.rb +1 -1
  28. data/lib/bitex_bot.rb +40 -16
  29. data/settings.rb.sample +43 -66
  30. data/spec/bitex_bot/settings_spec.rb +87 -15
  31. data/spec/factories/bitex_buy.rb +3 -3
  32. data/spec/factories/bitex_sell.rb +3 -3
  33. data/spec/factories/buy_opening_flow.rb +1 -1
  34. data/spec/factories/open_buy.rb +12 -10
  35. data/spec/factories/open_sell.rb +12 -10
  36. data/spec/factories/sell_opening_flow.rb +1 -1
  37. data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
  38. data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
  39. data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
  40. data/spec/models/bitex_api_spec.rb +1 -1
  41. data/spec/models/buy_closing_flow_spec.rb +140 -71
  42. data/spec/models/buy_opening_flow_spec.rb +126 -56
  43. data/spec/models/order_book_simulator_spec.rb +10 -10
  44. data/spec/models/robot_spec.rb +61 -47
  45. data/spec/models/sell_closing_flow_spec.rb +130 -62
  46. data/spec/models/sell_opening_flow_spec.rb +129 -60
  47. data/spec/spec_helper.rb +19 -16
  48. data/spec/support/bitex_stubs.rb +13 -14
  49. data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
  50. data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
  51. metadata +60 -42
  52. data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
  53. data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
  54. data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
  55. data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
  56. data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
  57. data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
  58. data/spec/models/itbit_api_wrapper_spec.rb +0 -15
  59. data/spec/support/bitstamp_stubs.rb +0 -110
@@ -1,77 +1,72 @@
1
- # Simulates hitting an order-book to find a price at which an order can be
2
- # assumed to get executed completely.
3
- # It essentially drops the start of the order book, to account for price
4
- # volatility (assuming those orders may be taken by someone else), and then
5
- # digs until the given USD amount or BTC quantity are reached, finally returning
6
- # the last price seen, which is the 'safest' price at which we can expect this
7
- # order to get executed quickly.
8
- class BitexBot::OrderBookSimulator
9
-
10
- # @param volatility [Integer] How many seconds of recent volume we need to
11
- # skip from the start of the order book to be more certain that our order
12
- # will get executed.
13
- # @param transactions [Hash] a list of hashes representing
14
- # all transactions in the other exchange. Each hash contains 'date', 'tid',
15
- # 'price' and 'amount', where 'amount' is the BTC transacted.
16
- # @param order_book [[price, quantity]] a list of lists representing the
17
- # order book to dig in.
18
- # @param amount_target [BigDecimal] stop when this amount has been reached,
19
- # leave as nil if looking for a quantity_target.
20
- # @param quantity_target [BigDecimal] stop when this quantity has been
21
- # reached, leave as nil if looking for an amount_target.
22
- # @return [Decimal] Returns the price that we're more likely to get when
23
- # executing an order for the given amount or quantity.
24
- def self.run(volatility, transactions, order_book,
25
- amount_target, quantity_target)
1
+ module BitexBot
2
+ # Simulates hitting an order-book to find a price at which an order can be assumed to get executed completely.
3
+ # It essentially drops the start of the order book, to account for price volatility (assuming those orders may be taken by
4
+ # someone else), and then digs until the given USD amount or BTC quantity are reached, finally returning the last price seen,
5
+ # which is the 'safest' price at which we can expect this order to get executed quickly.
6
+ #
7
+ class OrderBookSimulator
8
+ # @param volatility [Integer] How many seconds of recent volume we need to skip from the start of the order book to be more
9
+ # certain that our order will get executed.
10
+ # @param transactions [Hash] a list of hashes representing all transactions in the other exchange:
11
+ # Each hash contains 'date', 'tid', 'price' and 'amount', where 'amount' is the BTC transacted.
12
+ # @param order_book [[price, quantity]] a list of lists representing the order book to dig in.
13
+ # @param amount_target [BigDecimal] stop when this amount has been reached, leave as nil if looking for a quantity_target.
14
+ # @param quantity_target [BigDecimal] stop when this quantity has been reached, leave as nil if looking for an
15
+ # amount_target.
16
+ # @return [Decimal] Returns the price that we're more likely to get when executing an order for the given amount or
17
+ # quantity.
18
+ #
19
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
20
+ def self.run(volatility, transactions, order_book, amount_target, quantity_target)
21
+ to_skip = estimate_quantity_to_skip(volatility, transactions)
22
+ Robot.log(:debug, "Skipping #{to_skip} BTC")
23
+ seen = 0
26
24
 
27
- to_skip = estimate_quantity_to_skip(volatility, transactions)
28
- BitexBot::Robot.logger.debug("Skipping #{to_skip} BTC")
29
- seen = 0
30
- safest_price = 0
31
-
32
- order_book.each do |price, quantity|
33
- price = price.to_d
34
- quantity = quantity.to_d
35
-
36
- # An order may be partially or completely skipped due to volatility.
37
- if to_skip > 0
38
- dropped = [quantity, to_skip].min
39
- to_skip -= dropped
40
- quantity -= dropped
41
- BitexBot::Robot.logger.debug("Skipped #{dropped} BTC @ $#{price}")
42
- next if quantity == 0
43
- end
44
-
45
- if quantity_target
46
- if quantity >= (quantity_target - seen)
47
- BitexBot::Robot.logger.debug("Best price to get "\
48
- "#{quantity_target} BTC is $#{price}")
49
- return price
50
- else
51
- seen += quantity
25
+ order_book.each do |order_summary|
26
+ price = order_summary.price
27
+ quantity = order_summary.quantity
28
+
29
+ # An order may be partially or completely skipped due to volatility.
30
+ if to_skip.positive?
31
+ dropped = [quantity, to_skip].min
32
+ to_skip -= dropped
33
+ quantity -= dropped
34
+ Robot.log(:debug, "Skipped #{dropped} BTC @ $#{price}")
35
+ next if quantity.zero?
52
36
  end
53
- elsif amount_target
54
- amount = price * quantity
55
- if amount >= (amount_target - seen)
56
- BitexBot::Robot.logger.debug("Best price to get "\
57
- "$#{amount_target} is $#{price}")
58
- return price
59
- else
37
+
38
+ if quantity_target.present?
39
+ return best_price('BTC', quantity_target, price) if best_price?(quantity, quantity_target, seen)
40
+ seen += quantity
41
+ elsif amount_target.present?
42
+ amount = price * quantity
43
+ return best_price('$', amount_target, price) if best_price?(amount, amount_target, seen)
60
44
  seen += amount
61
45
  end
62
46
  end
47
+ order_book.last.price
63
48
  end
64
-
65
- return order_book.last.first.to_d
66
- end
49
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
67
50
 
68
- private
51
+ # private class methods
52
+
53
+ def self.estimate_quantity_to_skip(volatility, transactions)
54
+ threshold = transactions.first.timestamp - volatility
55
+ transactions
56
+ .select { |t| t.timestamp > threshold }
57
+ .map { |t| t.amount.to_d }
58
+ .sum
59
+ end
60
+
61
+ def self.best_price?(volume, target, seen)
62
+ volume >= (target - seen)
63
+ end
64
+
65
+ def self.best_price(currency, target, price)
66
+ Robot.log(:debug, "Best price to get #{currency} #{target} is $#{price}")
67
+ price
68
+ end
69
69
 
70
- def self.estimate_quantity_to_skip(volatility, transactions)
71
- threshold = transactions.first.date.to_i - volatility
72
- transactions
73
- .select{|t| t.date.to_i > threshold}
74
- .collect{|t| t.amount.to_d }
75
- .sum
70
+ # end: private class methods
76
71
  end
77
72
  end
@@ -1,36 +1,41 @@
1
1
  module BitexBot
2
+ # It bought at Bitex and needs to close (sell) in the other market.
2
3
  class SellClosingFlow < ClosingFlow
3
4
  has_many :open_positions, class_name: 'OpenSell', foreign_key: :closing_flow_id
4
5
  has_many :close_positions, class_name: 'CloseSell', foreign_key: :closing_flow_id
5
- scope :active, lambda { where(done: false) }
6
-
6
+
7
+ scope :active, -> { where(done: false) }
8
+
7
9
  def self.open_position_class
8
10
  OpenSell
9
11
  end
10
12
 
11
- def order_method
12
- :buy
13
- end
14
-
15
- # The amount received when selling initially, minus
16
- # the amount spent re-buying the sold coins.
17
- def get_usd_profit
18
- open_positions.sum(:amount) - close_positions.sum(:amount)
13
+ private
14
+
15
+ # create_or_cancel! helpers
16
+ # The amount received when selling initially, minus the amount spent re-buying the sold coins.
17
+ def estimate_fiat_profit
18
+ open_positions.sum(:amount) - positions_balance_amount
19
19
  end
20
-
21
- # The coins we actually bought minus the coins we were supposed
22
- # to re-buy
23
- def get_btc_profit
20
+
21
+ # The coins we actually bought minus the coins we were supposed to re-buy.
22
+ def estimate_btc_profit
24
23
  close_positions.sum(:quantity) - quantity
25
24
  end
26
-
27
- def get_next_price_and_quantity
25
+
26
+ def next_price_and_quantity
28
27
  closes = close_positions
29
- next_price =
30
- desired_price + ((closes.count * (closes.count * 3)) / 100.0)
31
- next_quantity =
32
- ((quantity * desired_price) - closes.sum(:amount)) / next_price
28
+ next_price = desired_price + price_variation(closes.count)
29
+ next_quantity = ((quantity * desired_price) - closes.sum(:amount)) / next_price
30
+
33
31
  [next_price, next_quantity]
34
32
  end
33
+ # end: create_or_cancel! helpers
34
+
35
+ # create_order_and_close_position helpers
36
+ def order_method
37
+ :buy
38
+ end
39
+ # end: create_order_and_close_position helpers
35
40
  end
36
41
  end
@@ -1,83 +1,76 @@
1
1
  module BitexBot
2
- # A workflow for selling bitcoin in Bitex and buying on another exchange. The
3
- # SellOpeningFlow factory function estimates how much you could buy on the other
4
- # exchange and calculates a reasonable price taking into account the remote
5
- # orderbook and the recent operated volume.
2
+ # A workflow for selling bitcoin in Bitex and buying on another exchange. The SellOpeningFlow factory function estimates how
3
+ # much you could buy on the other exchange and calculates a reasonable price taking into account the remote order book and the
4
+ # recent operated volume.
6
5
  #
7
- # When created, a SellOpeningFlow places an Ask on Bitex for the calculated
8
- # quantity and price, when the Ask is matched on Bitex an OpenSell is
9
- # created to buy the same quantity for a lower price on the other exchange.
6
+ # When created, a SellOpeningFlow places an Ask on Bitex for the calculated quantity and price, when the Ask is matched on
7
+ # Bitex an OpenSell is created to buy the same quantity for a lower price on the other exchange.
10
8
  #
11
- # A SellOpeningFlow can be cancelled at any point, which will cancel the Bitex
12
- # order and any orders on the remote exchange created from its OpenSell's
13
- #
14
- # @attr order_id The first thing a SellOpeningFlow does is placing an Ask on Bitex,
15
- # this is its unique id.
9
+ # A SellOpeningFlow can be cancelled at any point, which will cancel the Bitex order and any orders on the remote exchange
10
+ # created from its OpenSell's
11
+ #
12
+ # @attr order_id The first thing a SellOpeningFlow does is placing an Ask on Bitex, this is its unique id.
16
13
  class SellOpeningFlow < OpeningFlow
17
-
18
- # Start a workflow for selling bitcoin on bitex and buying on the other
19
- # exchange. The quantity to be sold on bitex is retrieved from Settings, if
20
- # there is not enough BTC on bitex or USD on the other exchange then no
21
- # order will be placed and an exception will be raised instead.
22
- # The amount a SellOpeningFlow will try to sell and the price it will try to
23
- # charge are derived from these parameters:
24
- #
25
- # @param usd_balance [BigDecimal] amount of usd available in the other
26
- # exchange that can be spent to balance this sale.
27
- # @param order_book [[price, quantity]] a list of lists representing an ask
28
- # order book in the other exchange.
29
- # @param transactions [Hash] a list of hashes representing
30
- # all transactions in the other exchange. Each hash contains 'date', 'tid',
31
- # 'price' and 'amount', where 'amount' is the BTC transacted.
32
- # @param bitex_fee [BigDecimal] the transaction fee to pay on bitex.
33
- # @param other_fee [BigDecimal] the transaction fee to pay on the other
34
- # exchange.
14
+ # Start a workflow for selling bitcoin on bitex and buying on the other exchange. The quantity to be sold on bitex is
15
+ # retrieved from Settings, if there is not enough BTC on bitex or USD on the other exchange then no order will be placed and
16
+ # an exception will be raised instead.
17
+ # The amount a SellOpeningFlow will try to sell and the price it will try to charge are derived from these parameters:
18
+ #
19
+ # @param usd_balance [BigDecimal] amount of usd available in the other exchange that can be spent to balance this sale.
20
+ # @param order_book [[price, quantity]] a list of lists representing an ask order book in the other exchange.
21
+ # @param transactions [Hash] a list of hashes representing all transactions in the other exchange:
22
+ # Each hash contains 'date', 'tid', 'price' and 'amount', where 'amount' is the BTC transacted.
23
+ # @param maker_fee [BigDecimal] the transaction fee to pay on our maker exchange.
24
+ # @param taker_fee [BigDecimal] the transaction fee to pay on the taker exchange.
35
25
  # @param store [Store] An updated config for this robot, mainly to use for profit.
36
26
  #
37
27
  # @return [SellOpeningFlow] The newly created flow.
38
- # @raise [CannotCreateFlow] If there's any problem creating this flow, for
39
- # example when you run out of BTC on bitex or out of USD on the other
40
- # exchange.
41
- def self.create_for_market(usd_balance, order_book, transactions,
42
- bitex_fee, other_fee, store)
28
+ # @raise [CannotCreateFlow] If there's any problem creating this flow, for example when you run out of BTC on bitex or out
29
+ # of USD on the other exchange.
30
+ def self.create_for_market(usd_balance, order_book, transactions, maker_fee, taker_fee, store)
43
31
  super
44
32
  end
45
-
33
+
34
+ # sync_open_positions helpers
35
+ def self.transaction_order_id(transaction)
36
+ transaction.ask_id
37
+ end
38
+
46
39
  def self.open_position_class
47
40
  OpenSell
48
41
  end
49
-
42
+ # end: sync_open_positions helpers
43
+
44
+ # sought_transaction helpers
50
45
  def self.transaction_class
51
46
  Bitex::Sell
52
47
  end
53
-
54
- def self.transaction_order_id(transaction)
55
- transaction.ask_id
48
+ # end: sought_transaction helpers
49
+
50
+ # create_for_market helpers
51
+ def self.maker_price(usd_to_spend_re_buying)
52
+ usd_to_spend_re_buying / value_to_use * (1 + profit / 100)
56
53
  end
57
54
 
58
55
  def self.order_class
59
56
  Bitex::Ask
60
57
  end
61
58
 
62
- def self.value_to_use
63
- store.selling_quantity_to_sell_per_order || Settings.selling.quantity_to_sell_per_order
64
- end
65
-
66
- def self.get_safest_price(transactions, order_book, bitcoins_to_use)
67
- OrderBookSimulator.run(Settings.time_to_live, transactions,
68
- order_book, nil, bitcoins_to_use)
59
+ def self.profit
60
+ store.selling_profit || Settings.selling.profit
69
61
  end
70
-
71
- def self.get_remote_value_to_use(value_to_use_needed, safest_price)
62
+
63
+ def self.remote_value_to_use(value_to_use_needed, safest_price)
72
64
  value_to_use_needed * safest_price
73
65
  end
74
66
 
75
- def self.profit
76
- store.selling_profit || Settings.selling.profit
67
+ def self.safest_price(transactions, order_book, bitcoins_to_use)
68
+ OrderBookSimulator.run(Settings.time_to_live, transactions, order_book, nil, bitcoins_to_use)
77
69
  end
78
-
79
- def self.get_bitex_price(btc_to_sell, usd_to_spend_re_buying)
80
- (usd_to_spend_re_buying / btc_to_sell) * (1 + profit / 100.0)
70
+
71
+ def self.value_to_use
72
+ store.selling_quantity_to_sell_per_order || Settings.selling.quantity_to_sell_per_order
81
73
  end
74
+ # end: create_for_market helpers
82
75
  end
83
76
  end
@@ -1,2 +1,4 @@
1
- class BitexBot::Store < ActiveRecord::Base
1
+ module BitexBot
2
+ class Store < ActiveRecord::Base
3
+ end
2
4
  end