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
@@ -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