bitex_bot 0.6.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -3
  3. data/Gemfile +3 -1
  4. data/bitex_bot.gemspec +5 -2
  5. data/lib/bitex_bot/database.rb +2 -2
  6. data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +47 -35
  7. data/lib/bitex_bot/models/api_wrappers/bitex/bitex_api_wrapper.rb +178 -0
  8. data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +62 -45
  9. data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +52 -28
  10. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +61 -28
  11. data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +12 -6
  12. data/lib/bitex_bot/models/buy_closing_flow.rb +3 -2
  13. data/lib/bitex_bot/models/buy_opening_flow.rb +31 -6
  14. data/lib/bitex_bot/models/closing_flow.rb +37 -22
  15. data/lib/bitex_bot/models/open_buy.rb +1 -3
  16. data/lib/bitex_bot/models/open_sell.rb +1 -3
  17. data/lib/bitex_bot/models/opening_flow.rb +42 -28
  18. data/lib/bitex_bot/models/order_book_simulator.rb +14 -13
  19. data/lib/bitex_bot/models/sell_closing_flow.rb +3 -2
  20. data/lib/bitex_bot/models/sell_opening_flow.rb +29 -4
  21. data/lib/bitex_bot/robot.rb +28 -43
  22. data/lib/bitex_bot/settings.rb +2 -0
  23. data/lib/bitex_bot/version.rb +1 -1
  24. data/settings.rb.sample +23 -5
  25. data/spec/bitex_bot/settings_spec.rb +13 -6
  26. data/spec/factories/bitex_ask.rb +14 -0
  27. data/spec/factories/bitex_bid.rb +14 -0
  28. data/spec/factories/bitex_buy.rb +7 -7
  29. data/spec/factories/bitex_sell.rb +7 -7
  30. data/spec/factories/buy_opening_flow.rb +10 -10
  31. data/spec/factories/open_buy.rb +8 -8
  32. data/spec/factories/open_sell.rb +8 -8
  33. data/spec/factories/sell_opening_flow.rb +10 -10
  34. data/spec/fixtures/bitstamp/balance.yml +63 -0
  35. data/spec/fixtures/bitstamp/order_book.yml +60 -0
  36. data/spec/fixtures/bitstamp/orders/all.yml +62 -0
  37. data/spec/fixtures/bitstamp/orders/failure_sell.yml +60 -0
  38. data/spec/fixtures/bitstamp/orders/successful_buy.yml +62 -0
  39. data/spec/fixtures/bitstamp/transactions.yml +244 -0
  40. data/spec/fixtures/bitstamp/user_transactions.yml +223 -0
  41. data/spec/models/api_wrappers/bitex_api_wrapper_spec.rb +147 -0
  42. data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +134 -140
  43. data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +9 -3
  44. data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +142 -73
  45. data/spec/models/bitex_api_spec.rb +4 -4
  46. data/spec/models/buy_closing_flow_spec.rb +19 -24
  47. data/spec/models/buy_opening_flow_spec.rb +102 -83
  48. data/spec/models/order_book_simulator_spec.rb +5 -0
  49. data/spec/models/robot_spec.rb +7 -4
  50. data/spec/models/sell_closing_flow_spec.rb +21 -25
  51. data/spec/models/sell_opening_flow_spec.rb +100 -80
  52. data/spec/spec_helper.rb +3 -1
  53. data/spec/support/bitex_stubs.rb +80 -40
  54. data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +2 -2
  55. data/spec/support/bitstamp/bitstamp_stubs.rb +3 -3
  56. data/spec/support/vcr.rb +8 -0
  57. data/spec/support/webmock.rb +8 -0
  58. metadata +77 -10
@@ -1,17 +1,29 @@
1
1
  # Wrapper implementation for Itbit API.
2
2
  # https://api.itbit.com/docs
3
3
  class ItbitApiWrapper < ApiWrapper
4
- def self.setup(settings)
4
+ attr_accessor :client_key, :secret, :user_id, :default_wallet_id, :sandbox
5
+
6
+ def initialize(settings)
7
+ self.client_key = settings.client_key
8
+ self.secret = settings.secret
9
+ self.user_id = settings.user_id
10
+ self.default_wallet_id = settings.default_wallet_id
11
+ self.sandbox = settings.sandbox
12
+ currency_pair(settings.order_book)
13
+ setup
14
+ end
15
+
16
+ def setup
5
17
  Itbit.tap do |conf|
6
- conf.client_key = settings.client_key
7
- conf.secret = settings.secret
8
- conf.user_id = settings.user_id
9
- conf.default_wallet_id = settings.default_wallet_id
10
- conf.sandbox = settings.sandbox
18
+ conf.client_key = client_key
19
+ conf.secret = secret
20
+ conf.user_id = user_id
21
+ conf.default_wallet_id = default_wallet_id
22
+ conf.sandbox = sandbox
11
23
  end
12
24
  end
13
25
 
14
- def self.amount_and_quantity(order_id, _transactions)
26
+ def amount_and_quantity(order_id)
15
27
  order = Itbit::Order.find(order_id)
16
28
  amount = order.volume_weighted_average_price * order.amount_filled
17
29
  quantity = order.amount_filled
@@ -19,24 +31,24 @@ class ItbitApiWrapper < ApiWrapper
19
31
  [amount, quantity]
20
32
  end
21
33
 
22
- def self.balance
34
+ def balance
23
35
  balance_summary_parser(wallet[:balances])
24
36
  end
25
37
 
26
- def self.find_lost(type, price, _quantity)
38
+ def find_lost(type, price, _quantity)
27
39
  orders.find { |o| o.type == type && o.price == price && o.timestamp >= 5.minutes.ago.to_i }
28
40
  end
29
41
 
30
- def self.order_book
31
- order_book_parser(Itbit::XBTUSDMarketData.orders)
42
+ def order_book
43
+ order_book_parser(market.orders)
32
44
  end
33
45
 
34
- def self.orders
35
- Itbit::Order.all(status: :open).map { |o| order_parser(o) }
46
+ def orders
47
+ Itbit::Order.all(instrument: currency_pair, status: :open).map { |o| order_parser(o) }
36
48
  end
37
49
 
38
- def self.place_order(type, price, quantity)
39
- Itbit::Order.create!(type, :xbtusd, quantity.round(4), price.round(2), wait: true)
50
+ def place_order(type, price, quantity)
51
+ Itbit::Order.create!(type, currency_pair, quantity.round(4), price.round(2), wait: true, currency: currency_base)
40
52
  rescue RestClient::RequestTimeout => e
41
53
  # On timeout errors, we still look for the latest active closing order that may be available.
42
54
  # We have a magic threshold of 5 minutes and also use the price to recognize an order as the current one.
@@ -49,12 +61,12 @@ class ItbitApiWrapper < ApiWrapper
49
61
  raise e
50
62
  end
51
63
 
52
- def self.transactions
53
- Itbit::XBTUSDMarketData.trades.map { |t| transaction_parser(t.symbolize_keys) }
64
+ def transactions
65
+ market.trades.map { |t| transaction_parser(t.symbolize_keys) }
54
66
  end
55
67
 
56
68
  # We don't need to fetch the list of transaction for itbit since we wont actually use them later.
57
- def self.user_transactions
69
+ def user_transactions
58
70
  []
59
71
  end
60
72
 
@@ -64,15 +76,15 @@ class ItbitApiWrapper < ApiWrapper
64
76
  # { total_balance: 0.0, currency: :eur, available_balance: 0.0 },
65
77
  # { total_balance: 0.0, currency: :sgd, available_balance: 0.0 }
66
78
  # ]
67
- def self.balance_summary_parser(balances)
68
- BalanceSummary.new(balance_parser(balances, :xbt), balance_parser(balances, :usd), 0.5.to_d)
79
+ def balance_summary_parser(balances)
80
+ BalanceSummary.new(balance_parser(balances, base.to_sym), balance_parser(balances, quote.to_sym), 0.5.to_d)
69
81
  end
70
82
 
71
- def self.wallet
83
+ def wallet
72
84
  Itbit::Wallet.all.find { |w| w[:id] == Itbit.default_wallet_id }
73
85
  end
74
86
 
75
- def self.balance_parser(balances, currency)
87
+ def balance_parser(balances, currency)
76
88
  currency_balance = balances.find { |balance| balance[:currency] == currency }
77
89
  Balance.new(
78
90
  currency_balance[:total_balance].to_d,
@@ -81,7 +93,7 @@ class ItbitApiWrapper < ApiWrapper
81
93
  )
82
94
  end
83
95
 
84
- def self.last_order_by(price)
96
+ def last_order_by(price)
85
97
  Itbit::Order.all.select { |o| o.price == price && (o.created_time - Time.now.to_i).abs < 500 }.first
86
98
  end
87
99
 
@@ -89,11 +101,11 @@ class ItbitApiWrapper < ApiWrapper
89
101
  # bids: [[0.63921e3, 0.195e1], [0.637e3, 0.47e0], [0.63e3, 0.158e1]],
90
102
  # asks: [[0.6424e3, 0.4e0], [0.6433e3, 0.95e0], [0.6443e3, 0.25e0]]
91
103
  # }
92
- def self.order_book_parser(book)
104
+ def order_book_parser(book)
93
105
  OrderBook.new(Time.now.to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
94
106
  end
95
107
 
96
- def self.order_summary_parser(orders)
108
+ def order_summary_parser(orders)
97
109
  orders.map { |order| OrderSummary.new(order[0], order[1]) }
98
110
  end
99
111
 
@@ -103,12 +115,24 @@ class ItbitApiWrapper < ApiWrapper
103
115
  # @volume_weighted_average_price=0.0, @amount_filled=0.0, @created_time=1415290187, @status=:open,
104
116
  # @metadata={foo: 'bar'}, @client_order_identifier='o'
105
117
  # >
106
- def self.order_parser(order)
118
+ def order_parser(order)
107
119
  Order.new(order.id, order.side, order.price, order.amount, order.created_time, order)
108
120
  end
109
121
 
110
122
  # { tid: 601855, price: 0.41814e3, amount: 0.19e-1, date: 1460161126 }
111
- def self.transaction_parser(transaction)
112
- Transaction.new(transaction[:tid], transaction[:price], transaction[:amount], transaction[:date])
123
+ def transaction_parser(transaction)
124
+ Transaction.new(transaction[:tid], transaction[:price], transaction[:amount], transaction[:date], transaction)
125
+ end
126
+
127
+ def market
128
+ "Itbit::#{currency_pair[:name].upcase}MarketData".constantize
129
+ end
130
+
131
+ def currency_pair(order_book = '')
132
+ @currency_pair ||= {
133
+ name: order_book,
134
+ base: order_book.slice(0..2),
135
+ quote: order_book.slice(3..6)
136
+ }
113
137
  end
114
138
  end
@@ -1,83 +1,90 @@
1
1
  # Wrapper implementation for Kraken API.
2
2
  # https://www.kraken.com/en-us/help/api
3
3
  class KrakenApiWrapper < ApiWrapper
4
+ attr_accessor :api_key, :api_secret, :client
5
+
4
6
  MIN_AMOUNT = 0.002
5
7
 
6
- def self.setup(settings)
7
- HTTParty::Basement.headers('User-Agent' => BitexBot.user_agent)
8
- @settings = settings
8
+ def initialize(settings)
9
+ self.api_key = settings.api_key
10
+ self.api_secret = settings.api_secret
11
+ setup
9
12
  end
10
13
 
11
- def self.client
12
- @client ||= KrakenClient.load(@settings)
14
+ def setup
15
+ KrakenOrder.api_wrapper = self
16
+ self.client ||= KrakenClient.load(api_key: api_key, api_secret: api_secret)
17
+ HTTParty::Basement.headers('User-Agent' => BitexBot.user_agent)
13
18
  end
14
19
 
15
- def self.amount_and_quantity(order_id, _transactions)
20
+ def amount_and_quantity(order_id)
16
21
  KrakenOrder.amount_and_quantity(order_id)
17
22
  end
18
23
 
19
- def self.balance
24
+ def balance
20
25
  balance_summary_parser(client.private.balance)
21
26
  rescue KrakenClient::ErrorResponse, Net::ReadTimeout
22
27
  retry
23
28
  end
24
29
 
25
- def self.enough_order_size?(quantity, _price)
30
+ def enough_order_size?(quantity, _price)
26
31
  quantity >= MIN_AMOUNT
27
32
  end
28
33
 
29
- def self.find_lost(type, price, quantity)
34
+ def find_lost(type, price, quantity)
30
35
  KrakenOrder.find_lost(type, price, quantity)
31
36
  end
32
37
 
33
- def self.order_book
34
- order_book_parser(client.public.order_book('XBTUSD')[:XXBTZUSD])
38
+ def order_book
39
+ order_book_parser(client.public.order_book(currency_pair[:altname])[currency_pair[:name]])
35
40
  rescue NoMethodError
36
41
  retry
37
42
  end
38
43
 
39
- def self.orders
44
+ def orders
40
45
  KrakenOrder.open.map { |ko| order_parser(ko) }
41
46
  end
42
47
 
43
- def self.send_order(type, price, quantity)
48
+ def send_order(type, price, quantity)
44
49
  KrakenOrder.create!(type, price, quantity)
45
50
  end
46
51
 
47
- def self.transactions
48
- client.public.trades('XBTUSD')[:XXBTZUSD].reverse.map { |t| transaction_parser(t) }
52
+ def transactions
53
+ client.public.trades(currency_pair[:altname])[currency_pair[:name]].reverse.map { |t| transaction_parser(t) }
49
54
  rescue NoMethodError
50
55
  retry
51
56
  end
52
57
 
53
58
  # We don't need to fetch the list of transactions for Kraken
54
- def self.user_transactions
59
+ def user_transactions
55
60
  []
56
61
  end
57
62
 
58
63
  # { ZEUR: '1433.0939', XXBT: '0.0000000000', 'XETH': '99.7497224800' }
59
- def self.balance_summary_parser(balances)
64
+ # rubocop:disable Metrics/AbcSize
65
+ def balance_summary_parser(balances)
60
66
  open_orders = KrakenOrder.open
61
67
  BalanceSummary.new(
62
- balance_parser(balances, :XXBT, btc_reserved(open_orders)),
63
- balance_parser(balances, :ZUSD, usd_reserved(open_orders)),
64
- client.private.trade_volume(pair: 'XBTUSD')[:fees][:XXBTZUSD][:fee].to_d
68
+ balance_parser(balances, currency_pair[:base], btc_reserved(open_orders)),
69
+ balance_parser(balances, currency_pair[:quote], usd_reserved(open_orders)),
70
+ client.private.trade_volume(pair: currency_pair[:altname])[:fees][currency_pair[:name]][:fee].to_d
65
71
  )
66
72
  end
73
+ # rubocop:enable Metrics/AbcSize
67
74
 
68
- def self.balance_parser(balances, currency, reserved)
75
+ def balance_parser(balances, currency, reserved)
69
76
  Balance.new(balances[currency].to_d, reserved, balances[currency].to_d - reserved)
70
77
  end
71
78
 
72
- def self.btc_reserved(open_orders)
79
+ def btc_reserved(open_orders)
73
80
  orders_by(open_orders, :sell).map { |o| (o.amount - o.executed_amount).to_d }.sum
74
81
  end
75
82
 
76
- def self.usd_reserved(open_orders)
83
+ def usd_reserved(open_orders)
77
84
  orders_by(open_orders, :buy).map { |o| (o.amount - o.executed_amount) * o.price.to_d }.sum
78
85
  end
79
86
 
80
- def self.orders_by(open_orders, order_type)
87
+ def orders_by(open_orders, order_type)
81
88
  open_orders.select { |o| o.type == order_type }
82
89
  end
83
90
 
@@ -85,16 +92,16 @@ class KrakenApiWrapper < ApiWrapper
85
92
  # 'asks': [['204.52893', '0.010', 1440291148], ['204.78790', '0.312', 1440291132]],
86
93
  # 'bids': [['204.24000', '0.100', 1440291016], ['204.23010', '0.312', 1440290699]]
87
94
  # }
88
- def self.order_book_parser(book)
95
+ def order_book_parser(book)
89
96
  OrderBook.new(Time.now.to_i, order_summary_parser(book[:bids]), order_summary_parser(book[:asks]))
90
97
  end
91
98
 
92
- def self.order_summary_parser(stock_market)
99
+ def order_summary_parser(stock_market)
93
100
  stock_market.map { |stock| OrderSummary.new(stock[0].to_d, stock[1].to_d) }
94
101
  end
95
102
 
96
103
  # <KrakenOrder: @id='O5TDV2-WDYB2-6OGJRD', @type=:buy, @price='1.01', @amount='1.00000000', @datetime='2013-09-26 23:15:04'>
97
- def self.order_parser(order)
104
+ def order_parser(order)
98
105
  Order.new(order.id.to_s, order.type, order.price, order.amount, order.datetime, order)
99
106
  end
100
107
 
@@ -103,7 +110,33 @@ class KrakenApiWrapper < ApiWrapper
103
110
  # ['202.51626', '0.01440000', 1440277319.1922, 'b', 'l', ''],
104
111
  # ['202.54000', '0.10000000', 1440277322.8993, 'b', 'l', '']
105
112
  # ]
106
- def self.transaction_parser(transaction)
113
+ def transaction_parser(transaction)
107
114
  Transaction.new(transaction[2].to_i, transaction[0].to_d, transaction[1].to_d, transaction[2].to_i)
108
115
  end
116
+
117
+ # {
118
+ # 'XBTUSD' => {
119
+ # 'altname' => 'XBTUSD',
120
+ # 'aclass_base' => 'currency',
121
+ # 'base' => 'XXBT',
122
+ # 'aclass_quote' => 'currency',
123
+ # 'quote' => 'ZUSD',
124
+ # 'lot' => 'unit',
125
+ # 'pair_decimals' => 1,
126
+ # 'lot_decimals' => 8,
127
+ # 'lot_multiplier' => 1,
128
+ # 'leverage_buy' => [2, 3, 4, 5],
129
+ # 'leverage_sell' => [2, 3, 4, 5],
130
+ # 'fees' => [[0, 0.26], .., [250_000, 0.2]],
131
+ # 'fees_maker' => [[0, 0.16], .., [250_000, 0.1]],
132
+ # 'fee_volume_currency' => 'ZUSD',
133
+ # 'margin_call' => 80,
134
+ # 'margin_stop' => 40
135
+ # }
136
+ # }
137
+ def currency_pair
138
+ @currency_pair ||= client.public.asset_pairs.map do |currency_pair, data|
139
+ [data['altname'], data.merge(name: currency_pair).with_indifferent_access]
140
+ end.to_h[BitexBot::Settings.taker_settings.order_book.upcase]
141
+ end
109
142
  end
@@ -2,7 +2,7 @@ require 'kraken_client'
2
2
 
3
3
  # Wrapper for Kraken orders.
4
4
  class KrakenOrder
5
- cattr_accessor :last_closed_order
5
+ cattr_accessor :last_closed_order, :api_wrapper
6
6
  attr_accessor :id, :amount, :executed_amount, :price, :avg_price, :type, :datetime
7
7
 
8
8
  # rubocop:disable Metrics/AbcSize
@@ -24,11 +24,17 @@ class KrakenOrder
24
24
  # rubocop:enable Metrics/AbcSize
25
25
 
26
26
  def self.order_info_by(type, price, quantity)
27
- KrakenApiWrapper.client.private.add_order(pair: 'XBTUSD', type: type, ordertype: 'limit', price: price, volume: quantity)
27
+ api_wrapper.client.private.add_order(
28
+ pair: KrakenApiWrapper.currency_pair[:altname],
29
+ type: type,
30
+ ordertype: 'limit',
31
+ price: price,
32
+ volume: quantity
33
+ )
28
34
  end
29
35
 
30
36
  def self.find(id)
31
- new(*KrakenApiWrapper.client.private.query_orders(txid: id).first)
37
+ new(*api_wrapper.client.private.query_orders(txid: id).first)
32
38
  rescue KrakenClient::ErrorResponse
33
39
  retry
34
40
  end
@@ -42,13 +48,13 @@ class KrakenOrder
42
48
  end
43
49
 
44
50
  def self.open
45
- KrakenApiWrapper.client.private.open_orders['open'].map { |o| new(*o) }
51
+ api_wrapper.client.private.open_orders['open'].map { |o| new(*o) }
46
52
  rescue KrakenClient::ErrorResponse
47
53
  retry
48
54
  end
49
55
 
50
56
  def self.closed(start: 1.hour.ago.to_i)
51
- KrakenApiWrapper.client.private.closed_orders(start: start)[:closed].map { |o| new(*o) }
57
+ api_wrapper.client.private.closed_orders(start: start)[:closed].map { |o| new(*o) }
52
58
  rescue KrakenClient::ErrorResponse
53
59
  retry
54
60
  end
@@ -100,7 +106,7 @@ class KrakenOrder
100
106
  end
101
107
 
102
108
  def cancel!
103
- KrakenApiWrapper.client.private.cancel_order(txid: id)
109
+ api_wrapper.client.private.cancel_order(txid: id)
104
110
  rescue KrakenClient::ErrorResponse => e
105
111
  e.message == 'EService:Unavailable' ? retry : raise
106
112
  end
@@ -10,9 +10,10 @@ module BitexBot
10
10
  OpenBuy
11
11
  end
12
12
 
13
- def fx_rate
13
+ def self.fx_rate
14
14
  Settings.buying_fx_rate
15
15
  end
16
+ def_delegator self, :fx_rate
16
17
 
17
18
  private
18
19
 
@@ -37,7 +38,7 @@ module BitexBot
37
38
  # end: create_or_cancel! hookers
38
39
 
39
40
  # create_order_and_close_position hookers
40
- def order_method
41
+ def order_type
41
42
  :sell
42
43
  end
43
44
  # end: create_order_and_close_position hookers
@@ -28,13 +28,13 @@ module BitexBot
28
28
  # @return [BuyOpeningFlow] The newly created flow.
29
29
  # @raise [CannotCreateFlow] If there's any problem creating this flow, for example when you run out of USD on bitex or out
30
30
  # of BTC on the other exchange.
31
- def self.create_for_market(btc_balance, order_book, transactions, maker_fee, taker_fee, store)
31
+ def self.create_for_market(taker_crypto_balance, taker_bids, taker_transactions, maker_fee, taker_fee, store)
32
32
  super
33
33
  end
34
34
 
35
35
  # sync_open_positions helpers
36
36
  def self.transaction_order_id(transaction)
37
- transaction.bid_id
37
+ transaction.raw.bid_id
38
38
  end
39
39
 
40
40
  def self.open_position_class
@@ -50,23 +50,28 @@ module BitexBot
50
50
 
51
51
  # create_for_market helpers
52
52
  def self.maker_price(crypto_to_resell)
53
- value_to_use / crypto_to_resell * (1 - profit / 100)
53
+ value_to_use * fx_rate / crypto_to_resell * (1 - profit / 100)
54
54
  end
55
55
 
56
56
  def self.order_class
57
57
  Bitex::Bid
58
58
  end
59
+ def_delegator self, :order_class
60
+
61
+ def self.order_type
62
+ :buy
63
+ end
59
64
 
60
65
  def self.profit
61
66
  store.buying_profit || Settings.buying.profit
62
67
  end
63
68
 
64
69
  def self.remote_value_to_use(value_to_use_needed, safest_price)
65
- (value_to_use_needed / fx_rate) / safest_price
70
+ value_to_use_needed / safest_price
66
71
  end
67
72
 
68
- def self.safest_price(transactions, order_book, amount_to_use)
69
- OrderBookSimulator.run(Settings.time_to_live, transactions, order_book, amount_to_use / fx_rate, nil)
73
+ def self.safest_price(transactions, taker_bids, amount_to_use)
74
+ OrderBookSimulator.run(Settings.time_to_live, transactions, taker_bids, amount_to_use, nil, fx_rate)
70
75
  end
71
76
 
72
77
  def self.value_to_use
@@ -77,5 +82,25 @@ module BitexBot
77
82
  def self.fx_rate
78
83
  Settings.buying_fx_rate
79
84
  end
85
+
86
+ def self.value_per_order
87
+ value_to_use * fx_rate
88
+ end
89
+
90
+ def self.maker_specie_to_spend
91
+ Robot.maker.quote.upcase
92
+ end
93
+
94
+ def self.maker_specie_to_obtain
95
+ Robot.maker.base.upcase
96
+ end
97
+
98
+ def self.taker_specie_to_spend
99
+ Robot.taker.base.upcase
100
+ end
101
+
102
+ def self.taker_specie_to_obtain
103
+ Robot.taker.quote.upcase
104
+ end
80
105
  end
81
106
  end
@@ -1,22 +1,28 @@
1
1
  module BitexBot
2
2
  # Close buy/sell positions.
3
3
  class ClosingFlow < ActiveRecord::Base
4
+ extend Forwardable
5
+
4
6
  self.abstract_class = true
5
7
 
6
8
  cattr_reader(:close_time_to_live) { 30 }
7
9
 
8
- # Start a new CloseBuy that closes exising OpenBuy's by selling on another exchange what was just bought on bitex.
10
+ # Start a new CloseBuy that closes existing OpenBuy's by selling on another exchange what was just bought on bitex.
9
11
  def self.close_open_positions
10
- open_positions = open_position_class.open
11
- return if open_positions.empty?
12
+ return unless open_positions.any?
12
13
 
13
- quantity = open_positions.map(&:quantity).sum
14
- amount = open_positions.map(&:amount).sum
15
- price = suggested_amount(open_positions) / quantity
14
+ positions = open_positions
15
+ quantity = positions.sum(&:quantity)
16
+ amount = positions.sum(&:amount) / fx_rate
17
+ price = suggested_amount(positions) / quantity
16
18
 
17
- # Don't even bother trying to close a position that's too small.
18
19
  return unless Robot.taker.enough_order_size?(quantity, price)
19
- create_closing_flow!(price, quantity, amount, open_positions)
20
+
21
+ create_closing_flow!(price, quantity, amount, positions)
22
+ end
23
+
24
+ def self.open_positions
25
+ open_position_class.open
20
26
  end
21
27
 
22
28
  # close_open_positions helpers
@@ -36,9 +42,9 @@ module BitexBot
36
42
  end
37
43
 
38
44
  # TODO: should receive a order_ids and user_transaccions array, then each Wrapper should know how to search for them.
39
- def sync_closed_positions(orders, transactions)
45
+ def sync_closed_positions
40
46
  # Maybe we couldn't create the bitstamp order when this flow was created, so we try again when syncing.
41
- latest_close.nil? ? create_initial_order_and_close_position! : create_or_cancel!(orders, transactions)
47
+ latest_close.nil? ? create_initial_order_and_close_position! : create_or_cancel!
42
48
  end
43
49
 
44
50
  def estimate_fiat_profit
@@ -53,15 +59,14 @@ module BitexBot
53
59
 
54
60
  # sync_closed_positions helpers
55
61
  # rubocop:disable Metrics/AbcSize
56
- # Metrics/AbcSize: Assignment Branch Condition size for create_or_cancel! is too high. [17.23/16]
57
- def create_or_cancel!(orders, transactions)
62
+ def create_or_cancel!
58
63
  order_id = latest_close.order_id.to_s
59
- order = orders.find { |o| o.id.to_s == order_id }
64
+ order = Robot.with_cooldown { Robot.taker.orders.find { |o| o.id.to_s == order_id } }
60
65
 
61
66
  # When order is nil it means the other exchange is done executing it so we can now have a look of all the sales that were
62
67
  # spawned from it.
63
68
  if order.nil?
64
- sync_position(order_id, transactions)
69
+ sync_position(order_id)
65
70
  create_next_position!
66
71
  elsif latest_close.created_at < close_time_to_live.seconds.ago
67
72
  cancel!(order)
@@ -77,9 +82,9 @@ module BitexBot
77
82
  # create_or_cancel! helpers
78
83
  def cancel!(order)
79
84
  Robot.with_cooldown do
80
- Robot.log(:debug, "Finalising #{order.class}##{order.id}")
85
+ Robot.log(:debug, "Finalising #{order.raw.class}##{order.id}")
81
86
  order.cancel!
82
- Robot.log(:debug, "Finalised #{order.class}##{order.id}")
87
+ Robot.log(:debug, "Finalised #{order.raw.class}##{order.id}")
83
88
  end
84
89
  rescue StandardError => error
85
90
  Robot.log(:debug, error)
@@ -90,19 +95,25 @@ module BitexBot
90
95
  # estimate_crypto_profit
91
96
  # amount_positions_balance
92
97
  # next_price_and_quantity
98
+ # rubocop:disable Metrics/AbcSize
93
99
  def create_next_position!
94
100
  next_price, next_quantity = next_price_and_quantity
95
101
  if Robot.taker.enough_order_size?(next_quantity, next_price)
96
102
  create_order_and_close_position(next_quantity, next_price)
97
103
  else
98
104
  update!(crypto_profit: estimate_crypto_profit, fiat_profit: estimate_fiat_profit, fx_rate: fx_rate, done: true)
99
- Robot.logger.info("Closing: Finished #{self.class.name} ##{id} earned $#{fiat_profit} and #{crypto_profit} BTC.")
105
+ Robot.log(
106
+ :info,
107
+ "Closing: Finished #{self.class} ##{id} earned"\
108
+ "#{Robot.maker.quote.upcase} #{fiat_profit} and #{Robot.maker.base.upcase} #{crypto_profit}."
109
+ )
100
110
  end
101
111
  end
112
+ # rubocop:enable Metrics/AbcSize
102
113
 
103
- def sync_position(order_id, transactions)
114
+ def sync_position(order_id)
104
115
  latest = latest_close
105
- latest.amount, latest.quantity = Robot.taker.amount_and_quantity(order_id, transactions)
116
+ latest.amount, latest.quantity = Robot.taker.amount_and_quantity(order_id)
106
117
  latest.save!
107
118
  end
108
119
  # end: create_or_cancel! helpers
@@ -114,11 +125,15 @@ module BitexBot
114
125
  # end: next_price_and_quantity helpers
115
126
 
116
127
  # This use hooks methods, these must be defined in the subclass:
117
- # order_method
128
+ # order_type
118
129
  def create_order_and_close_position(quantity, price)
119
130
  # TODO: investigate how to generate an ID to insert in the fields of goals where possible.
120
- Robot.log(:info, "Closing: Going to place #{order_method} order for #{self.class.name} ##{id} #{quantity} BTC @ $#{price}")
121
- order = Robot.taker.place_order(order_method, price, quantity)
131
+ Robot.log(
132
+ :info,
133
+ "Closing: Going to place #{order_type} order for #{self.class} ##{id}"\
134
+ " #{Robot.taker.base.upcase} #{quantity} @ #{Robot.taker.quote.upcase} #{price}"
135
+ )
136
+ order = Robot.taker.place_order(order_type, price, quantity)
122
137
  close_positions.create!(order_id: order.id)
123
138
  end
124
139
  end
@@ -1,12 +1,10 @@
1
1
  module BitexBot
2
2
  # An OpenBuy represents a Buy transaction on Bitex.
3
3
  # OpenBuys are open buy positions that are closed by one or several CloseBuys.
4
- # TODO: document attributes.
5
- #
6
4
  class OpenBuy < ActiveRecord::Base
7
5
  belongs_to :opening_flow, class_name: 'BuyOpeningFlow', foreign_key: :opening_flow_id
8
6
  belongs_to :closing_flow, class_name: 'BuyClosingFlow', foreign_key: :closing_flow_id
9
7
 
10
- scope :open, -> { where('closing_flow_id IS NULL') }
8
+ scope :open, -> { where(closing_flow: nil) }
11
9
  end
12
10
  end
@@ -1,12 +1,10 @@
1
1
  module BitexBot
2
2
  # An OpenSell represents a Sell transaction on Bitex.
3
3
  # OpenSells are open sell positions that are closed by one SellClosingFlow.
4
- # TODO: document attributes.
5
- #
6
4
  class OpenSell < ActiveRecord::Base
7
5
  belongs_to :opening_flow, class_name: 'SellOpeningFlow', foreign_key: :opening_flow_id
8
6
  belongs_to :closing_flow, class_name: 'SellClosingFlow', foreign_key: :closing_flow_id
9
7
 
10
- scope :open, -> { where('closing_flow_id IS NULL') }
8
+ scope :open, -> { where(closing_flow: nil) }
11
9
  end
12
10
  end