bitex_bot 0.0.1

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 (41) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +1 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +50 -0
  6. data/Rakefile +1 -0
  7. data/bin/bitex_bot +5 -0
  8. data/bitex_bot.gemspec +41 -0
  9. data/lib/bitex_bot/database.rb +93 -0
  10. data/lib/bitex_bot/models/buy_closing_flow.rb +34 -0
  11. data/lib/bitex_bot/models/buy_opening_flow.rb +86 -0
  12. data/lib/bitex_bot/models/close_buy.rb +7 -0
  13. data/lib/bitex_bot/models/close_sell.rb +4 -0
  14. data/lib/bitex_bot/models/closing_flow.rb +80 -0
  15. data/lib/bitex_bot/models/open_buy.rb +11 -0
  16. data/lib/bitex_bot/models/open_sell.rb +11 -0
  17. data/lib/bitex_bot/models/opening_flow.rb +114 -0
  18. data/lib/bitex_bot/models/order_book_simulator.rb +75 -0
  19. data/lib/bitex_bot/models/sell_closing_flow.rb +36 -0
  20. data/lib/bitex_bot/models/sell_opening_flow.rb +82 -0
  21. data/lib/bitex_bot/robot.rb +173 -0
  22. data/lib/bitex_bot/settings.rb +13 -0
  23. data/lib/bitex_bot/version.rb +3 -0
  24. data/lib/bitex_bot.rb +18 -0
  25. data/settings.yml.sample +84 -0
  26. data/spec/factories/bitex_buy.rb +12 -0
  27. data/spec/factories/bitex_sell.rb +12 -0
  28. data/spec/factories/buy_opening_flow.rb +17 -0
  29. data/spec/factories/open_buy.rb +17 -0
  30. data/spec/factories/open_sell.rb +17 -0
  31. data/spec/factories/sell_opening_flow.rb +17 -0
  32. data/spec/models/buy_closing_flow_spec.rb +150 -0
  33. data/spec/models/buy_opening_flow_spec.rb +154 -0
  34. data/spec/models/order_book_simulator_spec.rb +57 -0
  35. data/spec/models/robot_spec.rb +103 -0
  36. data/spec/models/sell_closing_flow_spec.rb +160 -0
  37. data/spec/models/sell_opening_flow_spec.rb +156 -0
  38. data/spec/spec_helper.rb +43 -0
  39. data/spec/support/bitex_stubs.rb +66 -0
  40. data/spec/support/bitstamp_stubs.rb +110 -0
  41. metadata +363 -0
@@ -0,0 +1,154 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitexBot::BuyOpeningFlow do
4
+ before(:each) do
5
+ Bitex.api_key = "valid_key"
6
+ end
7
+
8
+ it { should validate_presence_of :status }
9
+ it { should validate_presence_of :price }
10
+ it { should validate_presence_of :value_to_use }
11
+ it { should validate_presence_of :order_id }
12
+ it { should(ensure_inclusion_of(:status)
13
+ .in_array(BitexBot::BuyOpeningFlow.statuses)) }
14
+
15
+ describe "when creating a buying flow" do
16
+ it "spends 50 usd" do
17
+ stub_bitex_bid_create
18
+ BitexBot::Settings.stub(time_to_live: 3,
19
+ buying: double(amount_to_spend_per_order: 50, profit: 0))
20
+
21
+ flow = BitexBot::BuyOpeningFlow.create_for_market(100,
22
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
23
+
24
+ flow.value_to_use.should == 50
25
+ flow.price.should <= flow.suggested_closing_price
26
+ flow.price.should == "19.85074626865672".to_d
27
+ flow.suggested_closing_price.should == 20
28
+ flow.order_id.should == 12345
29
+ end
30
+
31
+ it "spends 100 usd" do
32
+ stub_bitex_bid_create
33
+ BitexBot::Settings.stub(time_to_live: 3,
34
+ buying: double(amount_to_spend_per_order: 100, profit: 0))
35
+
36
+ flow = BitexBot::BuyOpeningFlow.create_for_market(100000,
37
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
38
+ flow.value_to_use.should == 100
39
+ flow.price.should <= flow.suggested_closing_price
40
+ flow.price.should == "14.88805970149254".to_d
41
+ flow.suggested_closing_price.should == 15
42
+ flow.order_id.should == 12345
43
+ end
44
+
45
+ it "lowers the price to pay on bitex to take a profit" do
46
+ stub_bitex_bid_create
47
+ BitexBot::Settings.stub(time_to_live: 3,
48
+ buying: double(amount_to_spend_per_order: 100, profit: 50))
49
+
50
+ flow = BitexBot::BuyOpeningFlow.create_for_market(100000,
51
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
52
+ flow.value_to_use.should == 100
53
+ flow.price.should <= flow.suggested_closing_price
54
+ flow.price.should == "7.444029850746269".to_d
55
+ flow.suggested_closing_price.should == 15
56
+ flow.order_id.should == 12345
57
+ end
58
+
59
+ it "fails when there is a problem placing the bid on bitex" do
60
+ Bitex::Bid.stub(:create!) do
61
+ raise StandardError.new("Cannot Create")
62
+ end
63
+
64
+ BitexBot::Settings.stub(time_to_live: 3,
65
+ buying: double(amount_to_spend_per_order: 100, profit: 0))
66
+
67
+ expect do
68
+ flow = BitexBot::BuyOpeningFlow.create_for_market(100000,
69
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
70
+ flow.should be_nil
71
+ BitexBot::BuyOpeningFlow.count.should == 0
72
+ end.to raise_exception(BitexBot::CannotCreateFlow)
73
+ end
74
+
75
+ it "fails when there are not enough bitcoin to sell in the other exchange" do
76
+ stub_bitex_bid_create
77
+ BitexBot::Settings.stub(time_to_live: 3,
78
+ buying: double(amount_to_spend_per_order: 100, profit: 0))
79
+
80
+ expect do
81
+ flow = BitexBot::BuyOpeningFlow.create_for_market(1,
82
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
83
+ flow.should be_nil
84
+ BitexBot::BuyOpeningFlow.count.should == 0
85
+ end.to raise_exception(BitexBot::CannotCreateFlow)
86
+ end
87
+ end
88
+
89
+ describe "when fetching open positions" do
90
+ let(:flow){ create(:buy_opening_flow) }
91
+
92
+ it 'only gets buys' do
93
+ flow.order_id.should == 12345
94
+ stub_bitex_transactions
95
+ expect do
96
+ all = BitexBot::BuyOpeningFlow.sync_open_positions
97
+ all.size.should == 1
98
+ all.first.tap do |o|
99
+ o.price == 300.0
100
+ o.amount == 600.0
101
+ o.quantity == 2
102
+ o.transaction_id.should == 12345678
103
+ o.opening_flow.should == flow
104
+ end
105
+ end.to change{ BitexBot::OpenBuy.count }.by(1)
106
+ end
107
+
108
+ it 'does not register the same buy twice' do
109
+ flow.order_id.should == 12345
110
+ stub_bitex_transactions
111
+ BitexBot::BuyOpeningFlow.sync_open_positions
112
+ BitexBot::OpenBuy.count.should == 1
113
+ Timecop.travel 1.second.from_now
114
+ stub_bitex_transactions(build(:bitex_buy, id: 23456))
115
+ expect do
116
+ news = BitexBot::BuyOpeningFlow.sync_open_positions
117
+ news.first.transaction_id.should == 23456
118
+ end.to change{ BitexBot::OpenBuy.count }.by(1)
119
+ end
120
+
121
+ it 'does not register litecoin buys' do
122
+ flow.order_id.should == 12345
123
+ Bitex::Transaction.stub(all: [build(:bitex_buy, id: 23456, specie: :ltc)])
124
+ expect do
125
+ BitexBot::BuyOpeningFlow.sync_open_positions.should be_empty
126
+ end.not_to change{ BitexBot::OpenBuy.count }
127
+ BitexBot::OpenBuy.count.should == 0
128
+ end
129
+
130
+ it 'does not register buys from unknown bids' do
131
+ stub_bitex_transactions
132
+ expect do
133
+ BitexBot::BuyOpeningFlow.sync_open_positions.should be_empty
134
+ end.not_to change{ BitexBot::OpenBuy.count }
135
+ end
136
+ end
137
+
138
+ it 'cancels the associated bitex bid' do
139
+ stub_bitex_bid_create
140
+ BitexBot::Settings.stub(time_to_live: 3,
141
+ buying: double(amount_to_spend_per_order: 50, profit: 0))
142
+
143
+ flow = BitexBot::BuyOpeningFlow.create_for_market(100,
144
+ bitstamp_order_book_stub['bids'], bitstamp_transactions_stub, 0.5, 0.25)
145
+
146
+ flow.finalise!
147
+ flow.should be_settling
148
+ flow.finalise!
149
+ flow.should be_settling
150
+ Bitex::Order.stub(active: [])
151
+ flow.finalise!
152
+ flow.should be_finalised
153
+ end
154
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitexBot::OrderBookSimulator do
4
+ describe 'when buying on bitex to sell somewhere else' do
5
+ def simulate(volatility, amount)
6
+ BitexBot::OrderBookSimulator.run(volatility, bitstamp_transactions_stub,
7
+ bitstamp_order_book_stub['bids'], amount, nil)
8
+ end
9
+
10
+ it 'gets the safest price' do
11
+ simulate(0, 20).should == 30
12
+ end
13
+
14
+ it 'adjusts for medium volatility' do
15
+ simulate(3, 20).should == 25
16
+ end
17
+
18
+ it 'adjusts for high volatility' do
19
+ simulate(6, 20).should == 20
20
+ end
21
+
22
+ it 'big orders dig deep' do
23
+ simulate(0, 180).should == 15
24
+ end
25
+
26
+ it 'big orders with high volatility' do
27
+ simulate(6, 100).should == 10
28
+ end
29
+ end
30
+
31
+ describe 'when selling on bitex to buy somewhere else' do
32
+ def simulate(volatility, quantity)
33
+ BitexBot::OrderBookSimulator.run(volatility, bitstamp_transactions_stub,
34
+ bitstamp_order_book_stub['asks'], nil, quantity)
35
+ end
36
+
37
+ it 'gets the safest price' do
38
+ simulate(0, 2).should == 10
39
+ end
40
+
41
+ it 'adjusts for medium volatility' do
42
+ simulate(3, 2).should == 15
43
+ end
44
+
45
+ it 'adjusts for high volatility' do
46
+ simulate(6, 2).should == 25
47
+ end
48
+
49
+ it 'big orders dig deep' do
50
+ simulate(0, 8).should == 25
51
+ end
52
+
53
+ it 'big orders with high volatility dig deep' do
54
+ simulate(6, 6).should == 30
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitexBot::Robot do
4
+ before(:each) do
5
+ BitexBot::Settings.stub(
6
+ time_to_live: 10,
7
+ buying: double(
8
+ amount_to_spend_per_order: 50,
9
+ profit: 0),
10
+ selling: double(
11
+ quantity_to_sell_per_order: 1,
12
+ profit: 0),
13
+ mailer: double(
14
+ from: 'test@test.com',
15
+ to: 'test@test.com',
16
+ method: :test,
17
+ options: {}
18
+ )
19
+ )
20
+ Bitex.api_key = "valid_key"
21
+ Bitex::Profile.stub(get: {fee: 0.5})
22
+ stub_bitex_bid_create
23
+ stub_bitex_ask_create
24
+ stub_bitstamp_sell
25
+ stub_bitstamp_buy
26
+ stub_bitstamp_balance
27
+ stub_bitstamp_order_book
28
+ stub_bitstamp_transactions
29
+ stub_bitstamp_user_transactions
30
+ end
31
+ let(:bot){ BitexBot::Robot.new }
32
+
33
+ it 'Starts out by creating opening flows that timeout' do
34
+ bot.trade!
35
+ stub_bitex_transactions
36
+ buying = BitexBot::BuyOpeningFlow.last
37
+ selling = BitexBot::SellOpeningFlow.last
38
+
39
+ Timecop.travel 10.minutes.from_now
40
+ bot.trade!
41
+
42
+ buying.reload.should be_settling
43
+ selling.reload.should be_settling
44
+
45
+ Bitex::Order.stub(active: [])
46
+ bot.trade!
47
+ buying.reload.should be_finalised
48
+ selling.reload.should be_finalised
49
+ end
50
+
51
+ it 'creates alternating opening flows' do
52
+ Bitex::Transaction.stub(all: [])
53
+ bot.trade!
54
+ BitexBot::BuyOpeningFlow.active.count.should == 1
55
+ Timecop.travel 2.seconds.from_now
56
+ bot.trade!
57
+ BitexBot::BuyOpeningFlow.active.count.should == 1
58
+ Timecop.travel 5.seconds.from_now
59
+ bot.trade!
60
+ BitexBot::BuyOpeningFlow.active.count.should == 2
61
+
62
+ stub_bitex_transactions
63
+ Bitex::Order.stub(active: [])
64
+ Timecop.travel 5.seconds.from_now
65
+ bot.trade!
66
+ BitexBot::BuyOpeningFlow.active.count.should == 1
67
+ Timecop.travel 5.seconds.from_now
68
+ bot.trade!
69
+ BitexBot::BuyOpeningFlow.active.count.should == 0
70
+ end
71
+
72
+ it 'does not place new opening flows until all closing flows are done' do
73
+ bot.trade!
74
+ stub_bitex_transactions
75
+ Bitex::Order.stub(active: [])
76
+ expect do
77
+ bot.trade!
78
+ end.to change{ BitexBot::BuyClosingFlow.count }.by(1)
79
+
80
+ Timecop.travel 15.seconds.from_now
81
+ bot.trade!
82
+ bot.should be_active_closing_flows
83
+ bot.should_not be_active_opening_flows
84
+
85
+ stub_bitstamp_orders_into_transactions
86
+ expect do
87
+ bot.trade!
88
+ bot.should_not be_active_closing_flows
89
+ end.to change{ BitexBot::BuyOpeningFlow.count }.by(1)
90
+ end
91
+
92
+ it 'notifies exceptions and sleeps' do
93
+ Bitstamp.stub(:balance) do
94
+ raise StandardError.new('oh moova')
95
+ end
96
+ bot.trade!
97
+ Mail::TestMailer.deliveries.count.should == 1
98
+ end
99
+
100
+ #it 'goes through all the motions buying and selling' do
101
+ # pending
102
+ #end
103
+ end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitexBot::SellClosingFlow do
4
+ it "closes a single open position completely" do
5
+ stub_bitstamp_buy
6
+ open = create :open_sell
7
+ flow = BitexBot::SellClosingFlow.close_open_positions
8
+ open.reload.closing_flow.should == flow
9
+ flow.open_positions.should == [open]
10
+ flow.desired_price.should == 290
11
+ flow.quantity.should == 2
12
+ flow.amount.should == 600
13
+ flow.btc_profit.should be_nil
14
+ flow.usd_profit.should be_nil
15
+ close = flow.close_positions.first
16
+ close.order_id.should == 1
17
+ close.amount.should be_nil
18
+ close.quantity.should be_nil
19
+ end
20
+
21
+ it "closes an aggregate of several open positions" do
22
+ stub_bitstamp_buy
23
+ open_one = create :tiny_open_sell
24
+ open_two = create :open_sell
25
+ flow = BitexBot::SellClosingFlow.close_open_positions
26
+ close = flow.close_positions.first
27
+ open_one.reload.closing_flow.should == flow
28
+ open_two.reload.closing_flow.should == flow
29
+ flow.open_positions.should == [open_one, open_two]
30
+ flow.desired_price.should == '290.497512437810945273631840797'.to_d
31
+ flow.quantity.should == 2.01
32
+ flow.amount.should == 604
33
+ flow.btc_profit.should be_nil
34
+ flow.usd_profit.should be_nil
35
+ close.order_id.should == 1
36
+ close.amount.should be_nil
37
+ close.quantity.should be_nil
38
+ end
39
+
40
+ it "does not try to close if the amount is too low" do
41
+ open = create :tiny_open_sell
42
+ expect do
43
+ BitexBot::SellClosingFlow.close_open_positions.should be_nil
44
+ end.not_to change{ BitexBot::SellClosingFlow.count }
45
+ end
46
+
47
+ it "does not try to close if there are no open positions" do
48
+ expect do
49
+ BitexBot::SellClosingFlow.close_open_positions.should be_nil
50
+ end.not_to change{ BitexBot::SellClosingFlow.count }
51
+ end
52
+
53
+ describe "when syncinc executed orders" do
54
+ before(:each) do
55
+ stub_bitstamp_buy
56
+ stub_bitstamp_user_transactions
57
+ create :tiny_open_sell
58
+ create :open_sell
59
+ end
60
+
61
+ it "syncs the executed orders, calculates profit" do
62
+ flow = BitexBot::SellClosingFlow.close_open_positions
63
+ stub_bitstamp_orders_into_transactions
64
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
65
+ close = flow.close_positions.last
66
+ close.amount.should == 583.9
67
+ close.quantity.should == 2.01
68
+ flow.should be_done
69
+ flow.btc_profit.should == 0
70
+ flow.usd_profit.should == 20.1
71
+ end
72
+
73
+ it "retries closing at a higher price every minute" do
74
+ flow = BitexBot::SellClosingFlow.close_open_positions
75
+ expect do
76
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
77
+ end.not_to change{ BitexBot::CloseSell.count }
78
+ flow.should_not be_done
79
+
80
+ # Immediately calling sync again does not try to cancel the ask.
81
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
82
+ Bitstamp.orders.all.size.should == 1
83
+
84
+ # Partially executes order, and 61 seconds after that
85
+ # sync_closed_positions tries to cancel it.
86
+ stub_bitstamp_orders_into_transactions(ratio: 0.5)
87
+ Timecop.travel 61.seconds.from_now
88
+ Bitstamp.orders.all.size.should == 1
89
+ expect do
90
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
91
+ end.not_to change{ BitexBot::CloseSell.count }
92
+ Bitstamp.orders.all.size.should == 0
93
+ flow.should_not be_done
94
+
95
+ # Next time we try to sync_closed_positions the flow
96
+ # detects the previous close_buy was cancelled correctly so
97
+ # it syncs it's total amounts and tries to place a new one.
98
+ expect do
99
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
100
+ end.to change{ BitexBot::CloseSell.count }.by(1)
101
+ flow.close_positions.first.tap do |close|
102
+ close.amount.should == 291.95
103
+ close.quantity.should == 1.005
104
+ end
105
+
106
+ # The second ask is executed completely so we can wrap it up and consider
107
+ # this closing flow done.
108
+ stub_bitstamp_orders_into_transactions
109
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
110
+ flow.close_positions.last.tap do |close|
111
+ close.amount.should == 291.95
112
+ close.quantity.should == '1.004930813120933'.to_d
113
+ end
114
+ flow.should be_done
115
+ flow.btc_profit.should == '-0.000069186879067'.to_d
116
+ flow.usd_profit.should == 20.1
117
+ end
118
+
119
+ it "does not retry for an amount less than minimum_for_closing" do
120
+ flow = BitexBot::SellClosingFlow.close_open_positions
121
+
122
+ 20.times do
123
+ Timecop.travel 60.seconds.from_now
124
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
125
+ end
126
+
127
+ stub_bitstamp_orders_into_transactions(ratio: 0.999)
128
+ Bitstamp.orders.all.first.cancel!
129
+
130
+ expect do
131
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
132
+ end.not_to change{ BitexBot::CloseSell.count }
133
+
134
+ flow.should be_done
135
+ flow.btc_profit.should == '-0.015739962920125'.to_d
136
+ flow.usd_profit.should == '20.6839'.to_d
137
+ end
138
+
139
+ it "can lose BTC if price had to be raised dramatically" do
140
+ # This flow is forced to spend the original USD amount paying more than
141
+ # expected, thus regaining less BTC than what was sold on bitex.
142
+ flow = BitexBot::SellClosingFlow.close_open_positions
143
+ 60.times do
144
+ Timecop.travel 60.seconds.from_now
145
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
146
+ end
147
+
148
+ stub_bitstamp_orders_into_transactions
149
+
150
+ flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
151
+
152
+ flow.reload.should be_done
153
+ flow.btc_profit.should == "-0.117278093149271".to_d
154
+ flow.usd_profit.should == "20.1".to_d
155
+ close = flow.close_positions.last
156
+ (close.amount / close.quantity)
157
+ .should == '308.497512437810935201007569945345172662'.to_d
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+
3
+ describe BitexBot::SellOpeningFlow do
4
+ before(:each) do
5
+ Bitex.api_key = "valid_key"
6
+ end
7
+
8
+ it { should validate_presence_of :status }
9
+ it { should validate_presence_of :price }
10
+ it { should validate_presence_of :value_to_use }
11
+ it { should validate_presence_of :order_id }
12
+ it { should(ensure_inclusion_of(:status)
13
+ .in_array(BitexBot::SellOpeningFlow.statuses)) }
14
+
15
+ describe "when creating a selling flow" do
16
+ it "sells 2 bitcoin" do
17
+ stub_bitex_ask_create
18
+ BitexBot::Settings.stub(time_to_live: 3,
19
+ selling: double(quantity_to_sell_per_order: 2, profit: 0))
20
+
21
+ flow = BitexBot::SellOpeningFlow.create_for_market(1000,
22
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
23
+
24
+ flow.value_to_use.should == 2
25
+ flow.price.should >= flow.suggested_closing_price
26
+ flow.price.should == "20.15037593984962".to_d
27
+ flow.suggested_closing_price.should == 20
28
+ flow.order_id.should == 12345
29
+ end
30
+
31
+ it "sells 4 bitcoin" do
32
+ stub_bitex_ask_create
33
+ BitexBot::Settings.stub(time_to_live: 3,
34
+ selling: double(quantity_to_sell_per_order: 4, profit: 0))
35
+
36
+ flow = BitexBot::SellOpeningFlow.create_for_market(1000,
37
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
38
+
39
+ flow.value_to_use.should == 4
40
+ flow.price.should >= flow.suggested_closing_price
41
+ flow.price.should == "25.18796992481203".to_d
42
+ flow.suggested_closing_price.should == 25
43
+ flow.order_id.should == 12345
44
+ end
45
+
46
+ it "raises the price to charge on bitex to take a profit" do
47
+ stub_bitex_ask_create
48
+ BitexBot::Settings.stub(time_to_live: 3,
49
+ selling: double(quantity_to_sell_per_order: 4, profit: 50))
50
+
51
+ flow = BitexBot::SellOpeningFlow.create_for_market(1000,
52
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
53
+
54
+ flow.value_to_use.should == 4
55
+ flow.price.should >= flow.suggested_closing_price
56
+ flow.price.should == "37.78195488721804".to_d
57
+ flow.suggested_closing_price.should == 25
58
+ flow.order_id.should == 12345
59
+ end
60
+
61
+ it "fails when there is a problem placing the ask on bitex" do
62
+ Bitex::Ask.stub(:create!) do
63
+ raise StandardError.new("Cannot Create")
64
+ end
65
+
66
+ BitexBot::Settings.stub(time_to_live: 3,
67
+ selling: double(quantity_to_sell_per_order: 4, profit: 50))
68
+
69
+ expect do
70
+ flow = BitexBot::SellOpeningFlow.create_for_market(100000,
71
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
72
+ flow.should be_nil
73
+ BitexBot::SellOpeningFlow.count.should == 0
74
+ end.to raise_exception(BitexBot::CannotCreateFlow)
75
+ end
76
+
77
+ it "fails when there are not enough USD to re-buy in the other exchange" do
78
+ stub_bitex_bid_create
79
+ BitexBot::Settings.stub(time_to_live: 3,
80
+ selling: double(quantity_to_sell_per_order: 4, profit: 50))
81
+
82
+ expect do
83
+ flow = BitexBot::SellOpeningFlow.create_for_market(1,
84
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
85
+ flow.should be_nil
86
+ BitexBot::SellOpeningFlow.count.should == 0
87
+ end.to raise_exception(BitexBot::CannotCreateFlow)
88
+ end
89
+ end
90
+
91
+ describe "when fetching open positions" do
92
+ let(:flow){ create(:sell_opening_flow) }
93
+
94
+ it 'only gets sells' do
95
+ flow.order_id.should == 12345
96
+ stub_bitex_transactions
97
+ expect do
98
+ all = BitexBot::SellOpeningFlow.sync_open_positions
99
+ all.size.should == 1
100
+ all.first.tap do |o|
101
+ o.price.should == 300.0
102
+ o.amount.should == 600.0
103
+ o.quantity.should == 2
104
+ o.transaction_id.should == 12345678
105
+ o.opening_flow.should == flow
106
+ end
107
+ end.to change{ BitexBot::OpenSell.count }.by(1)
108
+ end
109
+
110
+ it 'does not register the same buy twice' do
111
+ flow.order_id.should == 12345
112
+ stub_bitex_transactions
113
+ BitexBot::SellOpeningFlow.sync_open_positions
114
+ BitexBot::OpenSell.count.should == 1
115
+ Timecop.travel 1.second.from_now
116
+ stub_bitex_transactions(build(:bitex_sell, id: 23456))
117
+ expect do
118
+ news = BitexBot::SellOpeningFlow.sync_open_positions
119
+ news.first.transaction_id.should == 23456
120
+ end.to change{ BitexBot::OpenSell.count }.by(1)
121
+ end
122
+
123
+ it 'does not register litecoin buys' do
124
+ flow.order_id.should == 12345
125
+ Bitex::Transaction.stub(all: [build(:bitex_sell, id: 23456, specie: :ltc)])
126
+ expect do
127
+ BitexBot::SellOpeningFlow.sync_open_positions.should be_empty
128
+ end.not_to change{ BitexBot::OpenSell.count }
129
+ BitexBot::OpenSell.count.should == 0
130
+ end
131
+
132
+ it 'does not register buys from unknown bids' do
133
+ stub_bitex_transactions
134
+ expect do
135
+ BitexBot::SellOpeningFlow.sync_open_positions.should be_empty
136
+ end.not_to change{ BitexBot::OpenSell.count }
137
+ end
138
+ end
139
+
140
+ it 'cancels the associated bitex bid' do
141
+ stub_bitex_ask_create
142
+ BitexBot::Settings.stub(time_to_live: 3,
143
+ selling: double(quantity_to_sell_per_order: 4, profit: 50))
144
+
145
+ flow = BitexBot::SellOpeningFlow.create_for_market(1000,
146
+ bitstamp_order_book_stub['asks'], bitstamp_transactions_stub, 0.5, 0.25)
147
+
148
+ flow.finalise!
149
+ flow.should be_settling
150
+ flow.finalise!
151
+ flow.should be_settling
152
+ Bitex::Order.stub(active: [])
153
+ flow.finalise!
154
+ flow.should be_finalised
155
+ end
156
+ end
@@ -0,0 +1,43 @@
1
+ require 'bundler/setup'
2
+ Bundler.setup
3
+
4
+ require 'bitex_bot'
5
+ require 'factory_girl'
6
+ require 'database_cleaner'
7
+ require 'shoulda/matchers'
8
+ require 'timecop'
9
+ FactoryGirl.find_definitions
10
+
11
+ Dir[File.dirname(__FILE__) + '/support/*.rb'].each {|file| require file }
12
+
13
+ # Automatically do rake db:test:prepare
14
+ ActiveRecord::Migration.maintain_test_schema!
15
+
16
+ # Transactional fixtures do not work with Selenium tests, because Capybara
17
+ # uses a separate server thread, which the transactions would be hidden
18
+ # from. We hence use DatabaseCleaner to truncate our test database.
19
+ DatabaseCleaner.strategy = :truncation
20
+
21
+ RSpec.configure do |config|
22
+ config.include(FactoryGirl::Syntax::Methods)
23
+ config.mock_with :rspec do |mocks|
24
+ mocks.yield_receiver_to_any_instance_implementation_blocks = true
25
+ mocks.syntax = [:expect, :should]
26
+ end
27
+ config.expect_with :rspec do |c|
28
+ c.syntax = [:expect, :should]
29
+ end
30
+
31
+ config.before(:all) do
32
+ BitexBot::Robot.logger = Logger.new('/dev/null')
33
+ BitexBot::Robot.test_mode = true
34
+ end
35
+
36
+ config.after(:each) do
37
+ DatabaseCleaner.clean # Truncate the database
38
+ Timecop.return
39
+ end
40
+
41
+ config.order = "random"
42
+ end
43
+