bitex_bot 0.3.7 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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