bitex_bot 0.6.1 → 0.9.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 (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,11 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe BitstampApiWrapper do
4
- let(:api_wrapper) { described_class }
5
4
  let(:taker_settings) do
6
5
  BitexBot::SettingsClass.new(
7
6
  bitstamp: {
8
- api_key: 'YOUR_API_KEY', secret: 'YOUR_API_SECRET', client_id: 'YOUR_BITSTAMP_USERNAME'
7
+ api_key: 'BITSTAMP_API_KEY',
8
+ secret: 'BITSTAMP_API_SECRET',
9
+ client_id: 'BITSTAMP_USERNAME',
10
+ order_book: 'btcusd'
9
11
  }
10
12
  )
11
13
  end
@@ -15,186 +17,178 @@ describe BitstampApiWrapper do
15
17
  BitexBot::Robot.setup
16
18
  end
17
19
 
18
- it 'Sends User-Agent header' do
19
- url = 'https://www.bitstamp.net/api/v2/balance/btcusd/'
20
- stub_stuff = stub_request(:post, url).with(headers: { 'User-Agent': BitexBot.user_agent })
20
+ let(:api_wrapper) { BitexBot::Robot.taker }
21
21
 
22
- # we don't care about the response
23
- api_wrapper.balance rescue nil
22
+ describe 'Sends User-Agent header' do
23
+ let(:url) { 'https://www.bitstamp.net/api/v2/balance/btcusd/' }
24
24
 
25
- expect(stub_stuff).to have_been_requested
26
- end
25
+ it do
26
+ stub_stuff = stub_request(:post, url).with(headers: { 'User-Agent': BitexBot.user_agent })
27
27
 
28
- def stub_balance(balance: '0.5', reserved: '1.5', available: '2.0', fee: '0.2')
29
- Bitstamp.stub(:balance) do
30
- {
31
- 'btc_balance' => balance,
32
- 'btc_reserved' => reserved,
33
- 'btc_available' => available,
34
- 'usd_balance' => balance,
35
- 'usd_reserved' => reserved,
36
- 'usd_available' => balance,
37
- 'fee' => fee
38
- }
28
+ # we don't care about the response
29
+ api_wrapper.balance rescue nil
30
+
31
+ expect(stub_stuff).to have_been_requested
39
32
  end
40
33
  end
41
34
 
42
- it '#balance' do
43
- stub_balance
35
+ describe '#currency_pair' do
36
+ it { expect(api_wrapper.currency_pair).to eq({ name: 'btcusd', base: 'btc', quote: 'usd' }) }
37
+ end
38
+
39
+ describe '#base' do
40
+ it { expect(api_wrapper.base).to eq('btc') }
41
+ end
44
42
 
45
- balance = api_wrapper.balance
46
- balance.should be_a(ApiWrapper::BalanceSummary)
47
- balance.crypto.should be_a(ApiWrapper::Balance)
48
- balance.fiat.should be_a(ApiWrapper::Balance)
43
+ describe '#quote' do
44
+ it { expect(api_wrapper.quote).to eq('usd') }
45
+ end
49
46
 
50
- crypto = balance.crypto
51
- crypto.total.should be_a(BigDecimal)
52
- crypto.reserved.should be_a(BigDecimal)
53
- crypto.available.should be_a(BigDecimal)
47
+ describe '#balance', vcr: { cassette_name: 'bitstamp/balance' } do
48
+ subject { api_wrapper.balance }
54
49
 
55
- fiat = balance.fiat
56
- fiat.total.should be_a(BigDecimal)
57
- fiat.reserved.should be_a(BigDecimal)
58
- fiat.available.should be_a(BigDecimal)
50
+ it { is_expected.to be_a(ApiWrapper::BalanceSummary) }
59
51
 
60
- balance.fee.should be_a(BigDecimal)
61
- end
52
+ its(:members) { is_expected.to eq(%i[crypto fiat fee]) }
62
53
 
63
- it '#cancel' do
64
- stub_orders
54
+ shared_examples_for 'currency balance' do |currency_type|
55
+ subject { api_wrapper.balance.send(currency_type) }
65
56
 
66
- expect(api_wrapper.orders.sample).to respond_to(:cancel!)
67
- end
57
+ it { is_expected.to be_a(ApiWrapper::Balance) }
68
58
 
69
- def stub_order_book(count: 3, price: 1.5, amount: 2.5)
70
- Bitstamp.stub(:order_book) do
71
- {
72
- 'timestamp' => Time.now.to_i.to_s,
73
- 'bids' => count.times.map { |i| [(price + i).to_s, (amount + i).to_s] },
74
- 'asks' => count.times.map { |i| [(price + i).to_s, (amount + i).to_s] }
75
- }
59
+ its(:members) { is_expected.to eq(%i[total reserved available]) }
60
+
61
+ its(:total) { is_expected.to be_a(BigDecimal) }
62
+ its(:reserved) { is_expected.to be_a(BigDecimal) }
63
+ its(:available) { is_expected.to be_a(BigDecimal) }
64
+ end
65
+
66
+ it_behaves_like 'currency balance', :crypto
67
+ it_behaves_like 'currency balance', :fiat
68
+
69
+ it 'fee' do
70
+ expect(api_wrapper.balance.fee).to be_a(BigDecimal)
76
71
  end
77
72
  end
78
73
 
79
- it '#order_book' do
80
- stub_order_book
74
+ describe '.order_book', vcr: { cassette_name: 'bitstamp/order_book' } do
75
+ subject { api_wrapper.order_book }
81
76
 
82
- order_book = api_wrapper.order_book
83
- order_book.should be_a(ApiWrapper::OrderBook)
84
- order_book.bids.all? { |bid| bid.should be_a(ApiWrapper::OrderSummary) }
85
- order_book.asks.all? { |ask| ask.should be_a(ApiWrapper::OrderSummary) }
86
- order_book.timestamp.should be_a(Integer)
77
+ it { is_expected.to be_a(ApiWrapper::OrderBook) }
87
78
 
88
- bid = order_book.bids.sample
89
- bid.price.should be_a(BigDecimal)
90
- bid.quantity.should be_a(BigDecimal)
79
+ its(:members) { is_expected.to eq(%i[timestamp bids asks]) }
91
80
 
92
- ask = order_book.asks.sample
93
- ask.price.should be_a(BigDecimal)
94
- ask.quantity.should be_a(BigDecimal)
95
- end
81
+ its(:timestamp) { is_expected.to be_a(Integer) }
82
+ its(:bids) { is_expected.to be_a(Array) }
83
+ its(:asks) { is_expected.to be_a(Array) }
96
84
 
97
- # [<Bitstamp::Order @id=76, @type=0, @price='1.1', @amount='1.0', @datetime='2013-09-26 23:15:04'>]
98
- def stub_orders(count: 1, price: 1.5, amount: 2.5)
99
- Bitstamp.orders.stub(:all) do
100
- count.times.map do |i|
101
- Bitstamp::Order.new(
102
- id: i,
103
- type: (i % 2),
104
- price: (price + 1).to_s,
105
- amount: (amount + i).to_s,
106
- datetime: 1.seconds.ago.strftime('%Y-%m-%d %H:%m:%S')
107
- )
108
- end
85
+ shared_examples_for :orders do |order_type|
86
+ subject { api_wrapper.order_book.send(order_type).sample }
87
+
88
+ it { is_expected.to be_a(ApiWrapper::OrderSummary) }
89
+
90
+ its(:price) { is_expected.to be_a(BigDecimal) }
91
+ its(:quantity) { is_expected.to be_a(BigDecimal) }
109
92
  end
93
+
94
+ it_behaves_like :orders, :bids
95
+ it_behaves_like :orders, :asks
110
96
  end
111
97
 
112
- it '#orders' do
113
- stub_orders
98
+ describe '.send_order' do
99
+ context 'successful buy', vcr: { cassette_name: 'bitstamp/orders/successful_buy' } do
100
+ subject { api_wrapper.send_order(:buy, 1.01, 1) }
114
101
 
115
- api_wrapper.orders.all? { |o| o.should be_a(ApiWrapper::Order) }
102
+ it { is_expected.to be_a(ApiWrapper::Order) }
116
103
 
117
- order = api_wrapper.orders.sample
118
- order.id.should be_a(String)
119
- order.type.should be_a(Symbol)
120
- order.price.should be_a(BigDecimal)
121
- order.amount.should be_a(BigDecimal)
122
- order.timestamp.should be_a(Integer)
104
+ its(:members) { is_expected.to eq(%i[id type price amount timestamp raw]) }
123
105
 
124
- expect(order).to respond_to(:cancel!)
125
- end
106
+ its(:id) { is_expected.to be_a(String) }
107
+ its(:type) { is_expected.to be_a(Symbol) }
108
+ its(:price) { is_expected.to be_a(BigDecimal) }
109
+ its(:amount) { is_expected.to be_a(BigDecimal) }
110
+ its(:timestamp) { is_expected.to be_a(Integer) }
111
+ its(:raw) { is_expected.to be_a(Bitstamp::Order) }
126
112
 
127
- context '#place_order' do
128
- it 'raises OrderNotFound error on bitstamp errors' do
129
- Bitstamp.orders.stub(:buy) do
130
- raise OrderNotFound
131
- end
113
+ context 'raw order' do
114
+ subject { api_wrapper.send_order(:buy, 1.01, 1).raw }
132
115
 
133
- expect { api_wrapper.place_order(:buy, 10, 100) }.to raise_exception(OrderNotFound)
116
+ its(:id) { is_expected.to be_a(Integer) }
117
+ its(:type) { is_expected.to be_a(Integer) }
118
+ its(:price) { is_expected.to be_a(String) }
119
+ its(:amount) { is_expected.to be_a(String) }
120
+ its(:datetime) { is_expected.to be_a(String) }
121
+ end
134
122
  end
135
- end
136
123
 
137
- # [<Bitstamp::Transactions @tid=14, @price='1.9', @amount='1.1', @date='1380648951'>]
138
- def stub_transactions(count: 1, price: 1.5, amount: 2.5)
139
- Bitstamp.stub(:transactions) do
140
- count.times.map do |i|
141
- double(
142
- tid: i,
143
- date: 1.seconds.ago.to_i,
144
- price: (price + i).to_s,
145
- amount: (amount + i).to_s
146
- )
147
- end
124
+ context 'failure sell', vcr: { cassette_name: 'bitstamp/orders/failure_sell' } do
125
+ subject { api_wrapper.send_order(:sell, 1_000, 1) }
126
+
127
+ it { is_expected.to be_nil }
148
128
  end
149
129
  end
150
130
 
151
- it '#transactions' do
152
- stub_transactions
131
+ describe '.transactions', vcr: { cassette_name: 'bitstamp/transactions' } do
132
+ subject { api_wrapper.transactions.sample }
133
+
134
+ it { is_expected.to be_a(ApiWrapper::Transaction) }
153
135
 
154
- api_wrapper.transactions.all? { |o| o.should be_a(ApiWrapper::Transaction) }
136
+ its(:members) { is_expected.to eq(%i[id price amount timestamp raw]) }
155
137
 
156
- transaction = api_wrapper.transactions.sample
157
- transaction.id.should be_a(Integer)
158
- transaction.price.should be_a(BigDecimal)
159
- transaction.amount.should be_a(BigDecimal)
160
- transaction.timestamp.should be_a(Integer)
138
+ its(:id) { is_expected.to be_a(Integer) }
139
+ its(:price) { is_expected.to be_a(BigDecimal) }
140
+ its(:amount) { is_expected.to be_a(BigDecimal) }
141
+ its(:timestamp) { is_expected.to be_a(Integer) }
142
+ its(:raw) { is_expected.to be_a(Bitstamp::Transactions) }
161
143
  end
162
144
 
163
- # [<Bitstamp::UserTransaction @id=76, @order_id=14, @type=1, @usd='0.00', @btc='-3.078', @btc_usd='0.00', @fee='0.00', @datetime='2013-09-26 13:46:59'>]
164
- def stub_user_transactions(count: 1, usd: 1.5, btc: 2.5, btc_usd: 3.5, fee: 0.05)
165
- Bitstamp.user_transactions.stub(:all) do
166
- count.times.map do |i|
167
- double(
168
- id: i,
169
- order_id: i,
170
- type: (i % 2),
171
- usd: (usd + i).to_s,
172
- btc: (btc + i).to_s,
173
- btc_usd: (btc_usd + i).to_s,
174
- fee: fee.to_s,
175
- datetime: 1.seconds.ago.strftime('%Y-%m-%d %H:%m:%S')
176
- )
177
- end
145
+ describe '.user_transaction', vcr: { cassette_name: 'bitstamp/user_transactions' } do
146
+ subject { api_wrapper.user_transactions.sample }
147
+
148
+ it { is_expected.to be_a(ApiWrapper::UserTransaction) }
149
+
150
+ its(:members) { is_expected.to eq(%i[order_id fiat crypto crypto_fiat fee type timestamp]) }
151
+
152
+ # same user transactions haven't order_id
153
+ its(:order_id) do
154
+ is_expected.to be_a(Integer) if subject.order_id.present?
155
+ is_expected.to be_nil unless subject.order_id.present?
178
156
  end
157
+
158
+ its(:fiat) { is_expected.to be_a(BigDecimal) }
159
+ its(:crypto) { is_expected.to be_a(BigDecimal) }
160
+ its(:crypto_fiat) { is_expected.to be_a(BigDecimal) }
161
+ its(:fee) { is_expected.to be_a(BigDecimal) }
162
+ its(:type) { is_expected.to be_a(Integer) }
163
+ its(:timestamp) { is_expected.to be_a(Integer) }
179
164
  end
180
165
 
181
- it '#user_transaction' do
182
- stub_user_transactions
183
- BitstampApiWrapper.user_transactions.all? { |ut| ut.should be_a(ApiWrapper::UserTransaction) }
184
-
185
- user_transaction = BitstampApiWrapper.user_transactions.sample
186
- user_transaction.usd.should be_a(BigDecimal)
187
- user_transaction.btc.should be_a(BigDecimal)
188
- user_transaction.btc_usd.should be_a(BigDecimal)
189
- user_transaction.order_id.should be_a(Integer)
190
- user_transaction.fee.should be_a(BigDecimal)
191
- user_transaction.type.should be_a(Integer)
192
- user_transaction.timestamp.should be_a(Integer)
166
+ describe '.orders', vcr: { cassette_name: 'bitstamp/orders/all' } do
167
+ subject { api_wrapper.orders.sample }
168
+
169
+ it { is_expected.to be_a(ApiWrapper::Order) }
170
+
171
+ its(:id) { is_expected.to be_a(String) }
172
+ its(:type) { is_expected.to be_a(Symbol) }
173
+ its(:price) { is_expected.to be_a(BigDecimal) }
174
+ its(:amount) { is_expected.to be_a(BigDecimal) }
175
+ its(:amount) { is_expected.to be_a(BigDecimal) }
176
+ its(:timestamp) { is_expected.to be_a(Integer) }
177
+ end
178
+
179
+ describe '.find_lost', vcr: { cassette_name: 'bitstamp/orders/all', allow_playback_repeats: true } do
180
+ before(:each) { Timecop.freeze(Time.strptime(order.timestamp.to_s, '%s') - 3.minutes.ago) }
181
+
182
+ let(:order) { api_wrapper.orders.sample }
183
+
184
+ subject { api_wrapper.find_lost(order.type, order.price, order.amount) }
185
+
186
+ it { is_expected.to be_present }
193
187
  end
194
188
 
195
- it '#find_lost' do
196
- stub_orders
189
+ describe '.cancel', vcr: { cassette_name: 'bitstamp/orders/all' } do
190
+ subject { api_wrapper.orders.sample }
197
191
 
198
- api_wrapper.orders.all? { |o| api_wrapper.find_lost(o.type, o.price, o.amount).present? }
192
+ it { is_expected.to respond_to(:cancel!) }
199
193
  end
200
194
  end
@@ -1,11 +1,15 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe ItbitApiWrapper do
4
- let(:api_wrapper) { described_class }
5
4
  let(:taker_settings) do
6
5
  BitexBot::SettingsClass.new(
7
6
  itbit: {
8
- client_key: 'client-key', secret: 'secret', user_id: 'user-id', default_wallet_id: 'wallet-000', sandbox: false
7
+ client_key: 'client-key',
8
+ secret: 'secret',
9
+ user_id: 'user-id',
10
+ default_wallet_id: 'wallet-000',
11
+ sandbox: false,
12
+ order_book: 'xbtusd'
9
13
  }
10
14
  )
11
15
  end
@@ -15,8 +19,10 @@ describe ItbitApiWrapper do
15
19
  BitexBot::Robot.setup
16
20
  end
17
21
 
22
+ let(:api_wrapper) { BitexBot::Robot.taker }
23
+
18
24
  it 'Sends User-Agent header' do
19
- url = 'https://api.itbit.com/v1/markets/XBTUSD/order_book'
25
+ url = "https://api.itbit.com/v1/markets/#{api_wrapper.currency_pair[:name].upcase}/order_book"
20
26
  stub_stuff = stub_request(:get, url).with(headers: { 'User-Agent': BitexBot.user_agent })
21
27
 
22
28
  # We don't care about the response