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
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ describe ItbitApiWrapper do
4
+ let(:api_wrapper) { described_class }
5
+ let(:taker_settings) do
6
+ BitexBot::SettingsClass.new(
7
+ itbit: {
8
+ client_key: 'client-key', secret: 'secret', user_id: 'user-id', default_wallet_id: 'wallet-000', sandbox: false
9
+ }
10
+ )
11
+ end
12
+
13
+ before(:each) do
14
+ BitexBot::Settings.stub(taker: taker_settings)
15
+ BitexBot::Robot.setup
16
+ end
17
+
18
+ it 'Sends User-Agent header' do
19
+ url = 'https://api.itbit.com/v1/markets/XBTUSD/order_book'
20
+ stub_stuff = stub_request(:get, url).with(headers: { 'User-Agent': BitexBot.user_agent })
21
+
22
+ # We don't care about the response
23
+ api_wrapper.order_book rescue nil
24
+
25
+ expect(stub_stuff).to have_been_requested
26
+ end
27
+
28
+ def stub_default_wallet_id
29
+ Itbit.stub(:default_wallet_id) { 'wallet-000' }
30
+ end
31
+
32
+ def stub_balance(count: 1, total: 1.5, available: 2.5)
33
+ stub_default_wallet_id
34
+ Itbit::Wallet.stub(:all) do
35
+ count.times.map do |i|
36
+ {
37
+ id: "wallet-#{i.to_s.rjust(3, '0')}",
38
+ name: 'primary',
39
+ user_id: '326a3369-78fc-44e7-ad52-03e97371ca72',
40
+ account_identifier: 'PRIVATEBETA55-2285-2HN',
41
+ balances: [
42
+ { total_balance: (total + i).to_d, currency: :usd, available_balance: (available + i).to_d },
43
+ { total_balance: (total + i).to_d, currency: :xbt, available_balance: (available + i).to_d },
44
+ { total_balance: (total + i).to_d, currency: :eur, available_balance: (available + i).to_d }
45
+ ]
46
+ }
47
+ end
48
+ end
49
+ end
50
+
51
+ it '#balance' do
52
+ stub_balance
53
+
54
+ balance = api_wrapper.balance
55
+ balance.should be_a(ApiWrapper::BalanceSummary)
56
+ balance.btc.should be_a(ApiWrapper::Balance)
57
+ balance.usd.should be_a(ApiWrapper::Balance)
58
+
59
+ btc = balance.btc
60
+ btc.total.should be_a(BigDecimal)
61
+ btc.reserved.should be_a(BigDecimal)
62
+ btc.available.should be_a(BigDecimal)
63
+
64
+ usd = balance.usd
65
+ usd.total.should be_a(BigDecimal)
66
+ usd.reserved.should be_a(BigDecimal)
67
+ usd.available.should be_a(BigDecimal)
68
+
69
+ balance.fee.should be_a(BigDecimal)
70
+ end
71
+
72
+ it '#cancel' do
73
+ stub_orders
74
+
75
+ expect(api_wrapper.orders.sample).to respond_to(:cancel!)
76
+ end
77
+
78
+ def stub_order_book(count: 3, price: 1.5, amount: 2.5)
79
+ Itbit::XBTUSDMarketData.stub(:orders) do
80
+ {
81
+ bids: count.times.map { |i| [(price + i).to_d, (amount + i).to_d] },
82
+ asks: count.times.map { |i| [(price + i).to_d, (amount + i).to_d] }
83
+ }
84
+ end
85
+ end
86
+
87
+ it '#order_book' do
88
+ stub_order_book
89
+
90
+ order_book = api_wrapper.order_book
91
+ order_book.should be_a(ApiWrapper::OrderBook)
92
+ order_book.bids.all? { |bid| bid.should be_a(ApiWrapper::OrderSummary) }
93
+ order_book.asks.all? { |ask| ask.should be_a(ApiWrapper::OrderSummary) }
94
+ order_book.timestamp.should be_a(Integer)
95
+
96
+ bid = order_book.bids.sample
97
+ bid.price.should be_a(BigDecimal)
98
+ bid.quantity.should be_a(BigDecimal)
99
+
100
+ ask = order_book.asks.sample
101
+ ask.price.should be_a(BigDecimal)
102
+ ask.quantity.should be_a(BigDecimal)
103
+ end
104
+
105
+ def stub_orders(count: 1, amount: 1.5, price: 2.5)
106
+ Itbit::Order.stub(:all).with(hash_including(status: :open)) do
107
+ count.times.map do |i|
108
+ Itbit::Order.new({
109
+ id: "id-#{i.to_s.rjust(3, '0')}",
110
+ wallet_id: "wallet-#{i.to_s.rjust(3, '0')}",
111
+ side: :buy,
112
+ instrument: :xbtusd,
113
+ type: :limit,
114
+ amount: (amount + i).to_d,
115
+ display_amount: (amount + i).to_d,
116
+ amount_filled: (amount + i).to_d,
117
+ price: (price + i).to_d,
118
+ volume_weighted_average_price: (price + i).to_d,
119
+ status: :open,
120
+ client_order_identifier: 'o',
121
+ metadata: { foo: 'bar' },
122
+ created_time: 1.seconds.ago.to_i
123
+ }.stringify_keys)
124
+ end
125
+ end
126
+ end
127
+
128
+ it '#orders' do
129
+ stub_orders
130
+
131
+ api_wrapper.orders.all? { |o| o.should be_a(ApiWrapper::Order) }
132
+
133
+ order = api_wrapper.orders.sample
134
+ order.id.should be_a(String)
135
+ order.type.should be_a(Symbol)
136
+ order.price.should be_a(BigDecimal)
137
+ order.amount.should be_a(BigDecimal)
138
+ order.timestamp.should be_a(Integer)
139
+ end
140
+
141
+ def stub_transactions(count: 1, price: 1.5, amount: 2.5)
142
+ Itbit::XBTUSDMarketData.stub(:trades) do
143
+ count.times.map do |i|
144
+ {
145
+ tid: i,
146
+ price: (price + i).to_d,
147
+ amount: (amount + i).to_d,
148
+ date: 1.seconds.ago.to_i
149
+ }
150
+ end
151
+ end
152
+ end
153
+
154
+ it '#transactions' do
155
+ stub_transactions
156
+
157
+ api_wrapper.transactions.all? { |o| o.should be_a(ApiWrapper::Transaction) }
158
+
159
+ transaction = api_wrapper.transactions.sample
160
+ transaction.id.should be_a(Integer)
161
+ transaction.price.should be_a(BigDecimal)
162
+ transaction.amount.should be_a(BigDecimal)
163
+ transaction.timestamp.should be_a(Integer)
164
+ end
165
+
166
+ it '#user_transaction' do
167
+ api_wrapper.user_transactions.should be_a(Array)
168
+ api_wrapper.user_transactions.empty?.should be_truthy
169
+ end
170
+
171
+ it '#find_lost' do
172
+ stub_orders
173
+
174
+ api_wrapper.orders.all? { |o| api_wrapper.find_lost(o.type, o.price, o.amount).present? }
175
+ end
176
+ end
@@ -0,0 +1,209 @@
1
+ require 'spec_helper'
2
+
3
+ describe KrakenApiWrapper do
4
+ let(:api_wrapper) { described_class }
5
+ let(:api_client) { api_wrapper.client }
6
+ let(:taker_settings) do
7
+ BitexBot::SettingsClass.new(
8
+ kraken: {
9
+ api_key: 'your_api_key', api_secret: 'your_api_secret'
10
+ }
11
+ )
12
+ end
13
+
14
+ before(:each) do
15
+ BitexBot::Settings.stub(taker: taker_settings)
16
+ BitexBot::Robot.setup
17
+ end
18
+
19
+ def stub_public_client
20
+ api_client.stub(public: double)
21
+ end
22
+
23
+ def stub_private_client
24
+ api_client.stub(private: double)
25
+ end
26
+
27
+ it 'Sends User-Agent header' do
28
+ url = 'https://api.kraken.com/0/public/Depth?pair=XBTUSD'
29
+ stub_stuff = stub_request(:get, url).with(headers: { 'User-Agent': BitexBot.user_agent })
30
+
31
+ # We don't care about the response
32
+ KrakenApiWrapper.order_book rescue nil
33
+
34
+ expect(stub_stuff).to have_been_requested
35
+ end
36
+
37
+ def stub_balance
38
+ api_client.private.stub(account_info: [{ taker_fees: '89.2' }])
39
+ api_client.private.stub(:balance) do
40
+ { 'XXBT': '1433.0939', 'ZUSD': '1230.0233', 'XETH': '99.7497224800' }.with_indifferent_access
41
+ end
42
+ end
43
+
44
+ def stub_trade_volume
45
+ api_client.private.stub(:trade_volume).with(hash_including(pair: 'XBTUSD')) do
46
+ {
47
+ 'currency' => 'ZUSD', 'volume' => '3878.8703',
48
+ 'fees' => {
49
+ 'XXBTZUSD' => {
50
+ 'fee' => '0.2600',
51
+ 'minfee' => '0.1000',
52
+ 'maxfee' => '0.2600',
53
+ 'nextfee' => '0.2400',
54
+ 'nextvolume' => '10000.0000',
55
+ 'tiervolume' => '0.0000'
56
+ }
57
+ },
58
+ 'fees_maker' => {
59
+ 'XETHZEUR' => {
60
+ 'fee' => '0.1600',
61
+ 'minfee' => '0.0000',
62
+ 'maxfee' => '0.1600',
63
+ 'nextfee' => '0.1400',
64
+ 'nextvolume' => '10000.0000',
65
+ 'tiervolume' => '0.0000'
66
+ }
67
+ }
68
+ }.with_indifferent_access
69
+ end
70
+ end
71
+
72
+ it '#balance' do
73
+ stub_private_client
74
+ stub_orders
75
+ stub_balance
76
+ stub_trade_volume
77
+
78
+ balance = api_wrapper.balance
79
+ balance.should be_a(ApiWrapper::BalanceSummary)
80
+ balance.btc.should be_a(ApiWrapper::Balance)
81
+ balance.usd.should be_a(ApiWrapper::Balance)
82
+
83
+ btc = balance.btc
84
+ btc.total.should be_a(BigDecimal)
85
+ btc.reserved.should be_a(BigDecimal)
86
+ btc.available.should be_a(BigDecimal)
87
+
88
+ usd = balance.usd
89
+ usd.total.should be_a(BigDecimal)
90
+ usd.reserved.should be_a(BigDecimal)
91
+ usd.available.should be_a(BigDecimal)
92
+
93
+ balance.fee.should be_a(BigDecimal)
94
+ end
95
+
96
+ it '#cancel' do
97
+ stub_private_client
98
+ stub_orders
99
+
100
+ expect(api_wrapper.orders.sample).to respond_to(:cancel!)
101
+ end
102
+
103
+ def stub_order_book(count: 3, price: 1.5, amount: 2.5)
104
+ api_client.public.stub(:order_book) do
105
+ {
106
+ 'XXBTZUSD' => {
107
+ 'bids' => count.times.map { |i| [(price + i).to_d, (amount + i).to_d, 1.seconds.ago.to_i.to_s] },
108
+ 'asks' => count.times.map { |i| [(price + i).to_d, (amount + i).to_d, 1.seconds.ago.to_i.to_s] }
109
+ }
110
+ }.with_indifferent_access
111
+ end
112
+ end
113
+
114
+ it '#order_book' do
115
+ stub_public_client
116
+ stub_order_book
117
+
118
+ order_book = api_wrapper.order_book
119
+ order_book.should be_a(ApiWrapper::OrderBook)
120
+ order_book.bids.all? { |bid| bid.should be_a(ApiWrapper::OrderSummary) }
121
+ order_book.asks.all? { |ask| ask.should be_a(ApiWrapper::OrderSummary) }
122
+ order_book.timestamp.should be_a(Integer)
123
+
124
+ bid = order_book.bids.sample
125
+ bid.price.should be_a(BigDecimal)
126
+ bid.quantity.should be_a(BigDecimal)
127
+
128
+ ask = order_book.asks.sample
129
+ ask.price.should be_a(BigDecimal)
130
+ ask.quantity.should be_a(BigDecimal)
131
+ end
132
+
133
+ def stub_orders
134
+ api_client.private.stub(:open_orders) do
135
+ {
136
+ 'open' => {
137
+ 'O5TDV2-WDYB2-6OGJRD' => {
138
+ 'refid' => nil, 'userref' => nil, 'status' => 'open', 'opentm' => 1_440_292_821.839, 'starttm' => 0, 'expiretm' => 0,
139
+ 'descr' => {
140
+ 'pair' => 'ETHEUR', 'type' => 'buy', 'ordertype' => 'limit', 'price' => '1.19000', 'price2' => '0',
141
+ 'leverage' => 'none', 'order' => 'buy 1204.00000000 ETHEUR @ limit 1.19000'
142
+ },
143
+ 'vol' => '1204.00000000', 'vol_exec' => '0.00000000', 'cost' => '0.00000', 'fee' => '0.00000',
144
+ 'price' => '0.00008', 'misc' => '', 'oflags' => 'fciq'
145
+ },
146
+ 'OGAEYL-LVSPL-BYGGRR' => {
147
+ 'refid' => nil, 'userref' => nil, 'status' => 'open', 'opentm' => 1_440_254_004.621, 'starttm' => 0, 'expiretm' => 0,
148
+ 'descr' => {
149
+ 'pair' => 'ETHEUR', 'type' => 'sell', 'ordertype' => 'limit', 'price' => '1.29000', 'price2' => '0',
150
+ 'leverage' => 'none', 'order' => 'sell 99.74972000 ETHEUR @ limit 1.29000'
151
+ },
152
+ 'vol' => '99.74972000', 'vol_exec' => '0.00000000', 'cost' => '0.00000', 'fee' => '0.00000',
153
+ 'price' => '0.00009', 'misc' => '', 'oflags' => 'fciq'
154
+ }
155
+ }
156
+ }.with_indifferent_access
157
+ end
158
+ end
159
+
160
+ it '#orders' do
161
+ stub_private_client
162
+ stub_orders
163
+
164
+ api_wrapper.orders.all? { |o| o.should be_a(ApiWrapper::Order) }
165
+
166
+ order = api_wrapper.orders.sample
167
+ order.id.should be_a(String)
168
+ order.type.should be_a(Symbol)
169
+ order.price.should be_a(BigDecimal)
170
+ order.amount.should be_a(BigDecimal)
171
+ order.timestamp.should be_a(Integer)
172
+ end
173
+
174
+ def stub_transactions(count: 1, price: 1.5, amount: 2.5)
175
+ api_client.public.stub(:trades).with('XBTUSD') do
176
+ {
177
+ XXBTZUSD: [
178
+ ['202.51626', '0.01440000', 1_440_277_319.1_922, 'b', 'l', ''],
179
+ ['202.54000', '0.10000000', 1_440_277_322.8_993, 'b', 'l', '']
180
+ ]
181
+ }
182
+ end
183
+ end
184
+
185
+ it '#transactions' do
186
+ stub_public_client
187
+ stub_transactions
188
+
189
+ api_wrapper.transactions.all? { |o| o.should be_a(ApiWrapper::Transaction) }
190
+
191
+ transaction = api_wrapper.transactions.sample
192
+ transaction.id.should be_a(Integer)
193
+ transaction.price.should be_a(BigDecimal)
194
+ transaction.amount.should be_a(BigDecimal)
195
+ transaction.timestamp.should be_a(Integer)
196
+ end
197
+
198
+ it '#user_transaction' do
199
+ api_wrapper.user_transactions.should be_a(Array)
200
+ api_wrapper.user_transactions.empty?.should be_truthy
201
+ end
202
+
203
+ it '#find_lost' do
204
+ stub_private_client
205
+ stub_orders
206
+
207
+ described_class.orders.all? { |o| described_class.find_lost(o.type, o.price, o.amount).present? }
208
+ end
209
+ end
@@ -6,7 +6,7 @@ describe 'BitexApi' do
6
6
  end
7
7
 
8
8
  it 'Sends User-Agent header' do
9
- stub_request(:get, "https://bitex.la/api-v1/rest/private/profile?api_key=your_bitex_api_key_which_should_be_kept_safe")
9
+ stub_request(:get, 'https://bitex.la/api-v1/rest/private/profile?api_key=your_bitex_api_key_which_should_be_kept_safe')
10
10
  .with(headers: { 'User-Agent': BitexBot.user_agent })
11
11
  Bitex::Profile.get rescue nil # we don't care about the response
12
12
  end