bitex_bot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+