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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +63 -0
- data/.rubocop.yml +33 -0
- data/Gemfile +1 -1
- data/Rakefile +1 -1
- data/bin/bitex_bot +1 -1
- data/bitex_bot.gemspec +34 -34
- data/lib/bitex_bot/database.rb +67 -67
- data/lib/bitex_bot/models/api_wrappers/api_wrapper.rb +142 -0
- data/lib/bitex_bot/models/api_wrappers/bitstamp/bitstamp_api_wrapper.rb +137 -0
- data/lib/bitex_bot/models/api_wrappers/itbit/itbit_api_wrapper.rb +116 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_api_wrapper.rb +111 -0
- data/lib/bitex_bot/models/api_wrappers/kraken/kraken_order.rb +117 -0
- data/lib/bitex_bot/models/buy_closing_flow.rb +23 -16
- data/lib/bitex_bot/models/buy_opening_flow.rb +48 -54
- data/lib/bitex_bot/models/close_buy.rb +2 -2
- data/lib/bitex_bot/models/closing_flow.rb +98 -79
- data/lib/bitex_bot/models/open_buy.rb +11 -10
- data/lib/bitex_bot/models/open_sell.rb +11 -10
- data/lib/bitex_bot/models/opening_flow.rb +157 -99
- data/lib/bitex_bot/models/order_book_simulator.rb +62 -67
- data/lib/bitex_bot/models/sell_closing_flow.rb +25 -20
- data/lib/bitex_bot/models/sell_opening_flow.rb +47 -54
- data/lib/bitex_bot/models/store.rb +3 -1
- data/lib/bitex_bot/robot.rb +203 -176
- data/lib/bitex_bot/settings.rb +71 -12
- data/lib/bitex_bot/version.rb +1 -1
- data/lib/bitex_bot.rb +40 -16
- data/settings.rb.sample +43 -66
- data/spec/bitex_bot/settings_spec.rb +87 -15
- data/spec/factories/bitex_buy.rb +3 -3
- data/spec/factories/bitex_sell.rb +3 -3
- data/spec/factories/buy_opening_flow.rb +1 -1
- data/spec/factories/open_buy.rb +12 -10
- data/spec/factories/open_sell.rb +12 -10
- data/spec/factories/sell_opening_flow.rb +1 -1
- data/spec/models/api_wrappers/bitstamp_api_wrapper_spec.rb +200 -0
- data/spec/models/api_wrappers/itbit_api_wrapper_spec.rb +176 -0
- data/spec/models/api_wrappers/kraken_api_wrapper_spec.rb +209 -0
- data/spec/models/bitex_api_spec.rb +1 -1
- data/spec/models/buy_closing_flow_spec.rb +140 -71
- data/spec/models/buy_opening_flow_spec.rb +126 -56
- data/spec/models/order_book_simulator_spec.rb +10 -10
- data/spec/models/robot_spec.rb +61 -47
- data/spec/models/sell_closing_flow_spec.rb +130 -62
- data/spec/models/sell_opening_flow_spec.rb +129 -60
- data/spec/spec_helper.rb +19 -16
- data/spec/support/bitex_stubs.rb +13 -14
- data/spec/support/bitstamp/bitstamp_api_wrapper_stubs.rb +35 -0
- data/spec/support/bitstamp/bitstamp_stubs.rb +91 -0
- metadata +60 -42
- data/lib/bitex_bot/models/bitfinex_api_wrapper.rb +0 -118
- data/lib/bitex_bot/models/bitstamp_api_wrapper.rb +0 -82
- data/lib/bitex_bot/models/itbit_api_wrapper.rb +0 -68
- data/lib/bitex_bot/models/kraken_api_wrapper.rb +0 -188
- data/spec/models/bitfinex_api_wrapper_spec.rb +0 -17
- data/spec/models/bitstamp_api_wrapper_spec.rb +0 -15
- data/spec/models/itbit_api_wrapper_spec.rb +0 -15
- data/spec/support/bitstamp_stubs.rb +0 -110
| @@ -1,102 +1,128 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe BitexBot::BuyClosingFlow do
         | 
| 4 | 
            -
               | 
| 4 | 
            +
              let(:taker_settings) do
         | 
| 5 | 
            +
                BitexBot::SettingsClass.new(
         | 
| 6 | 
            +
                  bitstamp: {
         | 
| 7 | 
            +
                    api_key: 'YOUR_API_KEY', secret: 'YOUR_API_SECRET', client_id: 'YOUR_BITSTAMP_USERNAME'
         | 
| 8 | 
            +
                  }
         | 
| 9 | 
            +
                )
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              before(:each) do
         | 
| 13 | 
            +
                BitexBot::Settings.stub(taker: taker_settings)
         | 
| 14 | 
            +
                BitexBot::Robot.setup
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              it 'closes a single open position completely' do
         | 
| 5 18 | 
             
                stub_bitstamp_sell
         | 
| 6 19 | 
             
                open = create :open_buy
         | 
| 7 | 
            -
                 | 
| 20 | 
            +
                BitexBot::BuyClosingFlow.close_open_positions
         | 
| 21 | 
            +
                flow = BitexBot::BuyClosingFlow.last
         | 
| 22 | 
            +
             | 
| 8 23 | 
             
                open.reload.closing_flow.should == flow
         | 
| 24 | 
            +
             | 
| 9 25 | 
             
                flow.open_positions.should == [open]
         | 
| 10 26 | 
             
                flow.desired_price.should == 310
         | 
| 11 27 | 
             
                flow.quantity.should == 2
         | 
| 28 | 
            +
                flow.amount.should == 600
         | 
| 12 29 | 
             
                flow.btc_profit.should be_nil
         | 
| 13 | 
            -
                flow. | 
| 30 | 
            +
                flow.fiat_profit.should be_nil
         | 
| 31 | 
            +
             | 
| 14 32 | 
             
                close = flow.close_positions.first
         | 
| 15 33 | 
             
                close.order_id.should == '1'
         | 
| 16 34 | 
             
                close.amount.should be_nil
         | 
| 17 35 | 
             
                close.quantity.should be_nil
         | 
| 18 36 | 
             
              end
         | 
| 19 | 
            -
             | 
| 20 | 
            -
              it  | 
| 37 | 
            +
             | 
| 38 | 
            +
              it 'closes an aggregate of several open positions' do
         | 
| 21 39 | 
             
                stub_bitstamp_sell
         | 
| 22 40 | 
             
                open_one = create :tiny_open_buy
         | 
| 23 41 | 
             
                open_two = create :open_buy
         | 
| 24 | 
            -
                 | 
| 42 | 
            +
                BitexBot::BuyClosingFlow.close_open_positions
         | 
| 43 | 
            +
                flow = BitexBot::BuyClosingFlow.last
         | 
| 44 | 
            +
             | 
| 25 45 | 
             
                close = flow.close_positions.first
         | 
| 46 | 
            +
             | 
| 26 47 | 
             
                open_one.reload.closing_flow.should == flow
         | 
| 27 48 | 
             
                open_two.reload.closing_flow.should == flow
         | 
| 49 | 
            +
             | 
| 28 50 | 
             
                flow.open_positions.should == [open_one, open_two]
         | 
| 29 | 
            -
                flow.desired_price.round( | 
| 51 | 
            +
                flow.desired_price.round(10).should == '310.4_975_124_378'.to_d
         | 
| 30 52 | 
             
                flow.quantity.should == 2.01
         | 
| 53 | 
            +
                flow.amount.should == 604
         | 
| 31 54 | 
             
                flow.btc_profit.should be_nil
         | 
| 32 | 
            -
                flow. | 
| 33 | 
            -
                close.order_id.should == '1'
         | 
| 34 | 
            -
                close.amount.should be_nil
         | 
| 35 | 
            -
                close.quantity.should be_nil
         | 
| 36 | 
            -
              end
         | 
| 37 | 
            -
              
         | 
| 38 | 
            -
              it 'keeps trying to place a closed position on bitstamp errors' do
         | 
| 39 | 
            -
                stub_bitstamp_sell
         | 
| 40 | 
            -
                Bitstamp.orders.stub(:sell) do 
         | 
| 41 | 
            -
                  double(id: nil, error: {price: ['Ensure it is good']})
         | 
| 42 | 
            -
                end
         | 
| 43 | 
            -
                open = create :open_buy
         | 
| 44 | 
            -
                flow = BitexBot::BuyClosingFlow.close_open_positions
         | 
| 45 | 
            -
                open.reload.closing_flow.should == flow
         | 
| 46 | 
            -
                flow.open_positions.should == [open]
         | 
| 47 | 
            -
                flow.desired_price.should == 310
         | 
| 48 | 
            -
                flow.quantity.should == 2
         | 
| 49 | 
            -
                flow.btc_profit.should be_nil
         | 
| 50 | 
            -
                flow.usd_profit.should be_nil
         | 
| 51 | 
            -
                flow.close_positions.should be_empty
         | 
| 55 | 
            +
                flow.fiat_profit.should be_nil
         | 
| 52 56 |  | 
| 53 | 
            -
                stub_bitstamp_user_transactions
         | 
| 54 | 
            -
                stub_bitstamp_sell
         | 
| 55 | 
            -
                flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 56 | 
            -
                close = flow.close_positions.first
         | 
| 57 57 | 
             
                close.order_id.should == '1'
         | 
| 58 58 | 
             
                close.amount.should be_nil
         | 
| 59 59 | 
             
                close.quantity.should be_nil
         | 
| 60 60 | 
             
              end
         | 
| 61 | 
            -
             | 
| 62 | 
            -
              it  | 
| 61 | 
            +
             | 
| 62 | 
            +
              it 'does not try to close if the amount is too low' do
         | 
| 63 63 | 
             
                open = create :tiny_open_buy
         | 
| 64 64 | 
             
                expect do
         | 
| 65 65 | 
             
                  BitexBot::BuyClosingFlow.close_open_positions.should be_nil
         | 
| 66 | 
            -
                end.not_to change{ BitexBot::BuyClosingFlow.count }
         | 
| 66 | 
            +
                end.not_to change { BitexBot::BuyClosingFlow.count }
         | 
| 67 67 | 
             
              end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
              it  | 
| 68 | 
            +
             | 
| 69 | 
            +
              it 'does not try to close if there are no open positions' do
         | 
| 70 70 | 
             
                expect do
         | 
| 71 71 | 
             
                  BitexBot::BuyClosingFlow.close_open_positions.should be_nil
         | 
| 72 | 
            -
                end.not_to change{ BitexBot::BuyClosingFlow.count }
         | 
| 72 | 
            +
                end.not_to change { BitexBot::BuyClosingFlow.count }
         | 
| 73 73 | 
             
              end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
              describe  | 
| 74 | 
            +
             | 
| 75 | 
            +
              describe 'when syncinc executed orders' do
         | 
| 76 76 | 
             
                before(:each) do
         | 
| 77 77 | 
             
                  stub_bitstamp_sell
         | 
| 78 | 
            -
                   | 
| 78 | 
            +
                  stub_bitstamp_empty_user_transactions
         | 
| 79 79 | 
             
                  create :tiny_open_buy
         | 
| 80 80 | 
             
                  create :open_buy
         | 
| 81 81 | 
             
                end
         | 
| 82 | 
            -
             | 
| 83 | 
            -
                it  | 
| 84 | 
            -
                   | 
| 82 | 
            +
             | 
| 83 | 
            +
                it 'syncs the executed orders, calculates profit' do
         | 
| 84 | 
            +
                  BitexBot::BuyClosingFlow.close_open_positions
         | 
| 85 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 85 86 | 
             
                  stub_bitstamp_orders_into_transactions
         | 
| 87 | 
            +
             | 
| 86 88 | 
             
                  flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 89 | 
            +
             | 
| 87 90 | 
             
                  close = flow.close_positions.last
         | 
| 88 | 
            -
                  close.amount.should ==  | 
| 91 | 
            +
                  close.amount.should == 624.105
         | 
| 89 92 | 
             
                  close.quantity.should == 2.01
         | 
| 93 | 
            +
             | 
| 90 94 | 
             
                  flow.should be_done
         | 
| 91 | 
            -
                  flow.btc_profit.should  | 
| 92 | 
            -
                  flow. | 
| 95 | 
            +
                  flow.btc_profit.should be_zero
         | 
| 96 | 
            +
                  flow.fiat_profit.should == 20.105
         | 
| 93 97 | 
             
                end
         | 
| 94 | 
            -
             | 
| 95 | 
            -
                 | 
| 96 | 
            -
                   | 
| 98 | 
            +
             | 
| 99 | 
            +
                context 'with other fx rate and closed open positions' do
         | 
| 100 | 
            +
                  let(:fx_rate) { 10.to_d }
         | 
| 101 | 
            +
                  let(:flow) { subject.class.last }
         | 
| 102 | 
            +
                  let(:positions_balance_amount) { flow.positions_balance_amount - flow.open_positions.sum(:amount) }
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  before(:each) do
         | 
| 105 | 
            +
                    BitexBot::Settings.stub(fx_rate: fx_rate)
         | 
| 106 | 
            +
                    subject.class.close_open_positions
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    stub_bitstamp_orders_into_transactions
         | 
| 109 | 
            +
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 110 | 
            +
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  it 'syncs the executed orders, calculates profit with other fx rate' do
         | 
| 113 | 
            +
                    flow.should be_done
         | 
| 114 | 
            +
                    flow.btc_profit.should be_zero
         | 
| 115 | 
            +
                    flow.fiat_profit.should eq positions_balance_amount
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                it 'retries closing at a lower price every minute' do
         | 
| 120 | 
            +
                  BitexBot::BuyClosingFlow.close_open_positions
         | 
| 121 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 122 | 
            +
             | 
| 97 123 | 
             
                  expect do
         | 
| 98 124 | 
             
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 99 | 
            -
                  end.not_to change{ BitexBot::CloseBuy.count }
         | 
| 125 | 
            +
                  end.not_to change { BitexBot::CloseBuy.count }
         | 
| 100 126 | 
             
                  flow.should_not be_done
         | 
| 101 127 |  | 
| 102 128 | 
             
                  # Immediately calling sync again does not try to cancel the ask.
         | 
| @@ -110,18 +136,19 @@ describe BitexBot::BuyClosingFlow do | |
| 110 136 | 
             
                  Bitstamp.orders.all.size.should == 1
         | 
| 111 137 | 
             
                  expect do
         | 
| 112 138 | 
             
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 113 | 
            -
                  end.not_to change{ BitexBot::CloseBuy.count }
         | 
| 114 | 
            -
                  Bitstamp.orders.all.size.should  | 
| 139 | 
            +
                  end.not_to change { BitexBot::CloseBuy.count }
         | 
| 140 | 
            +
                  Bitstamp.orders.all.size.should be_zero
         | 
| 115 141 | 
             
                  flow.should_not be_done
         | 
| 116 | 
            -
             | 
| 142 | 
            +
             | 
| 117 143 | 
             
                  # Next time we try to sync_closed_positions the flow
         | 
| 118 144 | 
             
                  # detects the previous close_buy was cancelled correctly so
         | 
| 119 145 | 
             
                  # it syncs it's total amounts and tries to place a new one.
         | 
| 120 146 | 
             
                  expect do
         | 
| 121 147 | 
             
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 122 | 
            -
                  end.to change{ BitexBot::CloseBuy.count }.by(1)
         | 
| 148 | 
            +
                  end.to change { BitexBot::CloseBuy.count }.by(1)
         | 
| 149 | 
            +
             | 
| 123 150 | 
             
                  flow.close_positions.first.tap do |close|
         | 
| 124 | 
            -
                    close.amount.should ==  | 
| 151 | 
            +
                    close.amount.should == 312.0_525
         | 
| 125 152 | 
             
                    close.quantity.should == 1.005
         | 
| 126 153 | 
             
                  end
         | 
| 127 154 |  | 
| @@ -130,34 +157,36 @@ describe BitexBot::BuyClosingFlow do | |
| 130 157 | 
             
                  stub_bitstamp_orders_into_transactions
         | 
| 131 158 | 
             
                  flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 132 159 | 
             
                  flow.close_positions.last.tap do |close|
         | 
| 133 | 
            -
                    close.amount.should ==  | 
| 160 | 
            +
                    close.amount.should == 312.02_235
         | 
| 134 161 | 
             
                    close.quantity.should == 1.005
         | 
| 135 162 | 
             
                  end
         | 
| 136 163 | 
             
                  flow.should be_done
         | 
| 137 | 
            -
                  flow.btc_profit.should  | 
| 138 | 
            -
                  flow. | 
| 164 | 
            +
                  flow.btc_profit.should be_zero
         | 
| 165 | 
            +
                  flow.fiat_profit.should == 20.07_485
         | 
| 139 166 | 
             
                end
         | 
| 140 | 
            -
                
         | 
| 141 | 
            -
                it "does not retry for an amount less than minimum_for_closing" do
         | 
| 142 | 
            -
                  flow = BitexBot::BuyClosingFlow.close_open_positions
         | 
| 143 167 |  | 
| 168 | 
            +
                it 'does not retry for an amount less than minimum_for_closing' do
         | 
| 169 | 
            +
                  BitexBot::BuyClosingFlow.close_open_positions
         | 
| 170 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 144 171 | 
             
                  stub_bitstamp_orders_into_transactions(ratio: 0.999)
         | 
| 145 172 | 
             
                  Bitstamp.orders.all.first.cancel!
         | 
| 146 173 |  | 
| 147 174 | 
             
                  expect do
         | 
| 148 175 | 
             
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 149 | 
            -
                  end.not_to change{ BitexBot::CloseBuy.count }
         | 
| 176 | 
            +
                  end.not_to change { BitexBot::CloseBuy.count }
         | 
| 150 177 |  | 
| 151 178 | 
             
                  flow.should be_done
         | 
| 152 | 
            -
                  flow.btc_profit.should == 0. | 
| 153 | 
            -
                  flow. | 
| 179 | 
            +
                  flow.btc_profit.should == 0.00_201
         | 
| 180 | 
            +
                  flow.fiat_profit.should == 19.480_895
         | 
| 154 181 | 
             
                end
         | 
| 155 | 
            -
             | 
| 156 | 
            -
                it  | 
| 182 | 
            +
             | 
| 183 | 
            +
                it 'can lose USD if price had to be dropped dramatically' do
         | 
| 157 184 | 
             
                  # This flow is forced to sell the original BTC quantity for less, thus regaining
         | 
| 158 185 | 
             
                  # less USD than what was spent on bitex.
         | 
| 159 | 
            -
                   | 
| 160 | 
            -
                   | 
| 186 | 
            +
                  BitexBot::BuyClosingFlow.close_open_positions
         | 
| 187 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  60.times do
         | 
| 161 190 | 
             
                    Timecop.travel 60.seconds.from_now
         | 
| 162 191 | 
             
                    flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 163 192 | 
             
                  end
         | 
| @@ -165,10 +194,50 @@ describe BitexBot::BuyClosingFlow do | |
| 165 194 | 
             
                  stub_bitstamp_orders_into_transactions
         | 
| 166 195 |  | 
| 167 196 | 
             
                  flow.sync_closed_positions(Bitstamp.orders.all, Bitstamp.user_transactions.all)
         | 
| 168 | 
            -
             | 
| 169 197 | 
             
                  flow.reload.should be_done
         | 
| 170 | 
            -
                  flow.btc_profit.should  | 
| 171 | 
            -
                  flow. | 
| 198 | 
            +
                  flow.btc_profit.should be_zero
         | 
| 199 | 
            +
                  flow.fiat_profit.should == -34.165
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
              end
         | 
| 202 | 
            +
             | 
| 203 | 
            +
              describe 'when there are errors placing the closing order' do
         | 
| 204 | 
            +
                it 'keeps trying to place a closed position on bitstamp errors' do
         | 
| 205 | 
            +
                  BitstampApiWrapper.stub(send_order: nil)
         | 
| 206 | 
            +
                  BitstampApiWrapper.stub(find_lost: nil)
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                  open = create :open_buy
         | 
| 209 | 
            +
                  expect do
         | 
| 210 | 
            +
                    BitexBot::BuyClosingFlow.close_open_positions
         | 
| 211 | 
            +
                  end.to raise_exception(OrderNotFound)
         | 
| 212 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  open.reload.closing_flow.should == flow
         | 
| 215 | 
            +
             | 
| 216 | 
            +
                  flow.open_positions.should == [open]
         | 
| 217 | 
            +
                  flow.desired_price.should == 310
         | 
| 218 | 
            +
                  flow.quantity.should == 2
         | 
| 219 | 
            +
                  flow.btc_profit.should be_nil
         | 
| 220 | 
            +
                  flow.fiat_profit.should be_nil
         | 
| 221 | 
            +
                  flow.close_positions.should be_empty
         | 
| 222 | 
            +
                end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                it 'retries until it finds the lost order' do
         | 
| 225 | 
            +
                  BitstampApiWrapper.stub(send_order: nil)
         | 
| 226 | 
            +
                  BitstampApiWrapper.stub(:orders) do
         | 
| 227 | 
            +
                    [BitstampApiWrapper::Order.new(1, :sell, 310, 2.5, 1.minute.ago.to_i)]
         | 
| 228 | 
            +
                  end
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                  open = create(:open_buy)
         | 
| 231 | 
            +
                  BitexBot::BuyClosingFlow.close_open_positions
         | 
| 232 | 
            +
                  flow = BitexBot::BuyClosingFlow.last
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  flow.close_positions.should_not be_empty
         | 
| 235 | 
            +
                  flow.close_positions.first do |position|
         | 
| 236 | 
            +
                    position.id.should eq 1234
         | 
| 237 | 
            +
                    position.type.should eq 1
         | 
| 238 | 
            +
                    position.amount.should eq 1000
         | 
| 239 | 
            +
                    position.price.should eq 2000
         | 
| 240 | 
            +
                  end
         | 
| 172 241 | 
             
                end
         | 
| 173 242 | 
             
              end
         | 
| 174 243 | 
             
            end
         | 
| @@ -1,91 +1,141 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe BitexBot::BuyOpeningFlow do
         | 
| 4 | 
            -
              before(:each)  | 
| 5 | 
            -
             | 
| 6 | 
            -
               | 
| 7 | 
            -
              let(:store){ BitexBot::Store.create }
         | 
| 4 | 
            +
              before(:each) { Bitex.api_key = 'valid_key' }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
              let(:store) { BitexBot::Store.create }
         | 
| 8 7 |  | 
| 9 8 | 
             
              it { should validate_presence_of :status }
         | 
| 10 9 | 
             
              it { should validate_presence_of :price }
         | 
| 11 10 | 
             
              it { should validate_presence_of :value_to_use }
         | 
| 12 11 | 
             
              it { should validate_presence_of :order_id }
         | 
| 13 | 
            -
              it { should(validate_inclusion_of(:status)
         | 
| 14 | 
            -
                .in_array(BitexBot::BuyOpeningFlow.statuses)) }
         | 
| 12 | 
            +
              it { should(validate_inclusion_of(:status).in_array(BitexBot::BuyOpeningFlow.statuses)) }
         | 
| 15 13 |  | 
| 16 | 
            -
              describe  | 
| 17 | 
            -
                it  | 
| 14 | 
            +
              describe 'when creating a buying flow' do
         | 
| 15 | 
            +
                it 'spends 50 usd' do
         | 
| 18 16 | 
             
                  stub_bitex_orders
         | 
| 19 17 | 
             
                  BitexBot::Settings.stub(time_to_live: 3,
         | 
| 20 18 | 
             
                    buying: double(amount_to_spend_per_order: 50, profit: 0))
         | 
| 21 19 |  | 
| 22 20 | 
             
                  flow = BitexBot::BuyOpeningFlow.create_for_market(100,
         | 
| 23 | 
            -
                     | 
| 24 | 
            -
             | 
| 21 | 
            +
                    bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 22 | 
            +
                    store)
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  flow.order_id.should == 12345
         | 
| 25 25 | 
             
                  flow.value_to_use.should == 50
         | 
| 26 26 | 
             
                  flow.price.should <= flow.suggested_closing_price
         | 
| 27 | 
            -
                  flow.price.round(14).should ==  | 
| 27 | 
            +
                  flow.price.round(14).should == '19.85074626865672'.to_d
         | 
| 28 28 | 
             
                  flow.suggested_closing_price.should == 20
         | 
| 29 | 
            -
                  flow.order_id.should == 12345
         | 
| 30 29 | 
             
                end
         | 
| 31 30 |  | 
| 32 | 
            -
                 | 
| 31 | 
            +
                let(:order_id) { 12_345 }
         | 
| 32 | 
            +
                let(:usd_price) { '14.888_059_701_492'.to_d }
         | 
| 33 | 
            +
                let(:suggested_closing_price) { 15.to_d }
         | 
| 34 | 
            +
                let(:amount_to_spend) { 100.to_d }
         | 
| 35 | 
            +
                let(:btc_balance) { 100_000.to_d }
         | 
| 36 | 
            +
                let(:maker_fee) { 0.5.to_d }
         | 
| 37 | 
            +
                let(:taker_fee) { 0.25.to_d }
         | 
| 38 | 
            +
                let(:order_book) { bitstamp_api_wrapper_order_book.bids }
         | 
| 39 | 
            +
                let(:transactions) { bitstamp_api_wrapper_transactions_stub }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                it 'spends 100 usd' do
         | 
| 42 | 
            +
                  BitexBot::Settings.stub(
         | 
| 43 | 
            +
                    time_to_live: 3,
         | 
| 44 | 
            +
                    buying: double(amount_to_spend_per_order: amount_to_spend, profit: 0)
         | 
| 45 | 
            +
                  )
         | 
| 33 46 | 
             
                  stub_bitex_orders
         | 
| 34 | 
            -
                  BitexBot::Settings.stub(time_to_live: 3,
         | 
| 35 | 
            -
                    buying: double(amount_to_spend_per_order: 100, profit: 0))
         | 
| 36 47 |  | 
| 37 | 
            -
                  flow = | 
| 38 | 
            -
                     | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 48 | 
            +
                  flow =
         | 
| 49 | 
            +
                    BitexBot::BuyOpeningFlow.create_for_market(
         | 
| 50 | 
            +
                      btc_balance,
         | 
| 51 | 
            +
                      order_book,
         | 
| 52 | 
            +
                      transactions,
         | 
| 53 | 
            +
                      maker_fee,
         | 
| 54 | 
            +
                      taker_fee,
         | 
| 55 | 
            +
                      store
         | 
| 56 | 
            +
                    )
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  flow.order_id.should eq order_id
         | 
| 59 | 
            +
                  flow.value_to_use.should eq amount_to_spend
         | 
| 60 | 
            +
                  flow.price.should.should <= flow.suggested_closing_price
         | 
| 61 | 
            +
                  flow.price.truncate(12).should eq usd_price
         | 
| 62 | 
            +
                  flow.suggested_closing_price.should eq suggested_closing_price
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                let(:other_fx_rate) { 10.to_d }
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                it 'spends 100 usd with other fx_rate' do
         | 
| 68 | 
            +
                  BitexBot::Settings.stub(
         | 
| 69 | 
            +
                    fx_rate: other_fx_rate,
         | 
| 70 | 
            +
                    time_to_live: 3,
         | 
| 71 | 
            +
                    buying: double(amount_to_spend_per_order: amount_to_spend, profit: 0)
         | 
| 72 | 
            +
                  )
         | 
| 73 | 
            +
                  stub_bitex_orders
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  flow =
         | 
| 76 | 
            +
                    BitexBot::BuyOpeningFlow.create_for_market(
         | 
| 77 | 
            +
                      btc_balance,
         | 
| 78 | 
            +
                      order_book,
         | 
| 79 | 
            +
                      transactions,
         | 
| 80 | 
            +
                      maker_fee,
         | 
| 81 | 
            +
                      taker_fee,
         | 
| 82 | 
            +
                      store
         | 
| 83 | 
            +
                    )
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  flow.order_id.should eq order_id
         | 
| 86 | 
            +
                  flow.value_to_use.should eq amount_to_spend
         | 
| 87 | 
            +
                  flow.price.should <= flow.suggested_closing_price * other_fx_rate
         | 
| 88 | 
            +
                  flow.price.truncate(11).should eq usd_price * other_fx_rate
         | 
| 89 | 
            +
                  flow.suggested_closing_price.should eq suggested_closing_price
         | 
| 44 90 | 
             
                end
         | 
| 45 | 
            -
             | 
| 46 | 
            -
                it  | 
| 91 | 
            +
             | 
| 92 | 
            +
                it 'lowers the price to pay on bitex to take a profit' do
         | 
| 47 93 | 
             
                  stub_bitex_orders
         | 
| 48 94 | 
             
                  BitexBot::Settings.stub(time_to_live: 3,
         | 
| 49 | 
            -
                    buying: double(amount_to_spend_per_order: 100, profit: 50))
         | 
| 95 | 
            +
                    buying: double(amount_to_spend_per_order: 100, profit: 50.to_d))
         | 
| 50 96 |  | 
| 51 97 | 
             
                  flow = BitexBot::BuyOpeningFlow.create_for_market(100000,
         | 
| 52 | 
            -
                     | 
| 98 | 
            +
                    bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 99 | 
            +
                    store)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                  flow.order_id.should == 12345
         | 
| 53 102 | 
             
                  flow.value_to_use.should == 100
         | 
| 54 103 | 
             
                  flow.price.should <= flow.suggested_closing_price
         | 
| 55 | 
            -
                  flow.price. | 
| 104 | 
            +
                  flow.price.should == '7.44402985074627'.to_d
         | 
| 56 105 | 
             
                  flow.suggested_closing_price.should == 15
         | 
| 57 | 
            -
                  flow.order_id.should == 12345
         | 
| 58 106 | 
             
                end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
                it  | 
| 61 | 
            -
                  Bitex::Bid.stub(:create!)  | 
| 62 | 
            -
                    raise StandardError.new("Cannot Create")
         | 
| 63 | 
            -
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                it 'fails when there is a problem placing the bid on bitex' do
         | 
| 109 | 
            +
                  Bitex::Bid.stub(:create!) { raise StandardError.new('Cannot Create') }
         | 
| 64 110 |  | 
| 65 111 | 
             
                  BitexBot::Settings.stub(time_to_live: 3,
         | 
| 66 112 | 
             
                    buying: double(amount_to_spend_per_order: 100, profit: 0))
         | 
| 67 113 |  | 
| 68 114 | 
             
                  expect do
         | 
| 69 115 | 
             
                    flow = BitexBot::BuyOpeningFlow.create_for_market(100000,
         | 
| 70 | 
            -
                       | 
| 116 | 
            +
                      bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 117 | 
            +
                      store)
         | 
| 118 | 
            +
             | 
| 71 119 | 
             
                    flow.should be_nil
         | 
| 72 120 | 
             
                    BitexBot::BuyOpeningFlow.count.should == 0
         | 
| 73 121 | 
             
                  end.to raise_exception(BitexBot::CannotCreateFlow)
         | 
| 74 122 | 
             
                end
         | 
| 75 | 
            -
             | 
| 76 | 
            -
                it  | 
| 123 | 
            +
             | 
| 124 | 
            +
                it 'fails when there are not enough bitcoin to sell in the other exchange' do
         | 
| 77 125 | 
             
                  stub_bitex_orders
         | 
| 78 126 | 
             
                  BitexBot::Settings.stub(time_to_live: 3,
         | 
| 79 127 | 
             
                    buying: double(amount_to_spend_per_order: 100, profit: 0))
         | 
| 80 128 |  | 
| 81 129 | 
             
                  expect do
         | 
| 82 130 | 
             
                    flow = BitexBot::BuyOpeningFlow.create_for_market(1,
         | 
| 83 | 
            -
                       | 
| 131 | 
            +
                      bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 132 | 
            +
                      store)
         | 
| 133 | 
            +
             | 
| 84 134 | 
             
                    flow.should be_nil
         | 
| 85 135 | 
             
                    BitexBot::BuyOpeningFlow.count.should == 0
         | 
| 86 136 | 
             
                  end.to raise_exception(BitexBot::CannotCreateFlow)
         | 
| 87 137 | 
             
                end
         | 
| 88 | 
            -
             | 
| 138 | 
            +
             | 
| 89 139 | 
             
                it 'prioritizes profit from store' do
         | 
| 90 140 | 
             
                  store = BitexBot::Store.new(buying_profit: 0.5)
         | 
| 91 141 | 
             
                  stub_bitex_orders
         | 
| @@ -93,20 +143,23 @@ describe BitexBot::BuyOpeningFlow do | |
| 93 143 | 
             
                    buying: double(amount_to_spend_per_order: 50, profit: 0))
         | 
| 94 144 |  | 
| 95 145 | 
             
                  flow = BitexBot::BuyOpeningFlow.create_for_market(100,
         | 
| 96 | 
            -
                     | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 146 | 
            +
                    bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 147 | 
            +
                    store)
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  flow.price.round(13).should == '19.7514925373134'.to_d
         | 
| 99 150 | 
             
                end
         | 
| 100 151 | 
             
              end
         | 
| 101 | 
            -
             | 
| 102 | 
            -
              describe  | 
| 103 | 
            -
                let(:flow){ create(:buy_opening_flow) }
         | 
| 152 | 
            +
             | 
| 153 | 
            +
              describe 'when fetching open positions' do
         | 
| 154 | 
            +
                let(:flow) { create(:buy_opening_flow) }
         | 
| 104 155 |  | 
| 105 156 | 
             
                it 'only gets buys' do
         | 
| 106 157 | 
             
                  flow.order_id.should == 12345
         | 
| 107 158 | 
             
                  stub_bitex_transactions
         | 
| 159 | 
            +
             | 
| 108 160 | 
             
                  expect do
         | 
| 109 161 | 
             
                    all = BitexBot::BuyOpeningFlow.sync_open_positions
         | 
| 162 | 
            +
             | 
| 110 163 | 
             
                    all.size.should == 1
         | 
| 111 164 | 
             
                    all.first.tap do |o|
         | 
| 112 165 | 
             
                      o.price == 300.0
         | 
| @@ -115,9 +168,9 @@ describe BitexBot::BuyOpeningFlow do | |
| 115 168 | 
             
                      o.transaction_id.should == 12345678
         | 
| 116 169 | 
             
                      o.opening_flow.should == flow
         | 
| 117 170 | 
             
                    end
         | 
| 118 | 
            -
                  end.to change{ BitexBot::OpenBuy.count }.by(1)
         | 
| 171 | 
            +
                  end.to change { BitexBot::OpenBuy.count }.by(1)
         | 
| 119 172 | 
             
                end
         | 
| 120 | 
            -
             | 
| 173 | 
            +
             | 
| 121 174 | 
             
                it 'does not register the same buy twice' do
         | 
| 122 175 | 
             
                  flow.order_id.should == 12345
         | 
| 123 176 | 
             
                  stub_bitex_transactions
         | 
| @@ -125,40 +178,57 @@ describe BitexBot::BuyOpeningFlow do | |
| 125 178 | 
             
                  BitexBot::OpenBuy.count.should == 1
         | 
| 126 179 | 
             
                  Timecop.travel 1.second.from_now
         | 
| 127 180 | 
             
                  stub_bitex_transactions(build(:bitex_buy, id: 23456))
         | 
| 181 | 
            +
             | 
| 128 182 | 
             
                  expect do
         | 
| 129 183 | 
             
                    news = BitexBot::BuyOpeningFlow.sync_open_positions
         | 
| 130 184 | 
             
                    news.first.transaction_id.should == 23456
         | 
| 131 | 
            -
                  end.to change{ BitexBot::OpenBuy.count }.by(1)
         | 
| 185 | 
            +
                  end.to change { BitexBot::OpenBuy.count }.by(1)
         | 
| 132 186 | 
             
                end
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                it 'does not register  | 
| 187 | 
            +
             | 
| 188 | 
            +
                it 'does not register buys from another order book' do
         | 
| 135 189 | 
             
                  flow.order_id.should == 12345
         | 
| 136 | 
            -
                  Bitex::Trade.stub(all: [build(:bitex_buy, id: 23456,  | 
| 190 | 
            +
                  Bitex::Trade.stub(all: [build(:bitex_buy, id: 23456, order_book: :btc_ars)])
         | 
| 191 | 
            +
             | 
| 137 192 | 
             
                  expect do
         | 
| 138 193 | 
             
                    BitexBot::BuyOpeningFlow.sync_open_positions.should be_empty
         | 
| 139 | 
            -
                  end.not_to change{ BitexBot::OpenBuy.count }
         | 
| 194 | 
            +
                  end.not_to change { BitexBot::OpenBuy.count }
         | 
| 140 195 | 
             
                  BitexBot::OpenBuy.count.should == 0
         | 
| 141 196 | 
             
                end
         | 
| 142 | 
            -
             | 
| 197 | 
            +
             | 
| 143 198 | 
             
                it 'does not register buys from unknown bids' do
         | 
| 144 199 | 
             
                  stub_bitex_transactions
         | 
| 200 | 
            +
             | 
| 145 201 | 
             
                  expect do
         | 
| 146 202 | 
             
                    BitexBot::BuyOpeningFlow.sync_open_positions.should be_empty
         | 
| 147 | 
            -
                  end.not_to change{ BitexBot::OpenBuy.count }
         | 
| 203 | 
            +
                  end.not_to change { BitexBot::OpenBuy.count }
         | 
| 148 204 | 
             
                end
         | 
| 149 205 | 
             
              end
         | 
| 150 | 
            -
             | 
| 206 | 
            +
             | 
| 151 207 | 
             
              it 'cancels the associated bitex bid' do
         | 
| 152 208 | 
             
                stub_bitex_orders
         | 
| 153 209 | 
             
                BitexBot::Settings.stub(time_to_live: 3,
         | 
| 154 210 | 
             
                  buying: double(amount_to_spend_per_order: 50, profit: 0))
         | 
| 155 211 |  | 
| 156 212 | 
             
                flow = BitexBot::BuyOpeningFlow.create_for_market(100,
         | 
| 157 | 
            -
                   | 
| 158 | 
            -
             | 
| 213 | 
            +
                  bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 214 | 
            +
                  store)
         | 
| 215 | 
            +
             | 
| 159 216 | 
             
                flow.finalise!
         | 
| 160 217 | 
             
                flow.should be_settling
         | 
| 161 218 | 
             
                flow.finalise!
         | 
| 162 219 | 
             
                flow.should be_finalised
         | 
| 163 220 | 
             
              end
         | 
| 221 | 
            +
             | 
| 222 | 
            +
              it 'order has expected order book' do
         | 
| 223 | 
            +
                stub_bitex_orders
         | 
| 224 | 
            +
                BitexBot::Settings.stub(time_to_live: 3,
         | 
| 225 | 
            +
                  buying: double(amount_to_spend_per_order: 50, profit: 0))
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                flow = subject.class.create_for_market(100,
         | 
| 228 | 
            +
                  bitstamp_api_wrapper_order_book.bids, bitstamp_api_wrapper_transactions_stub, 0.5, 0.25,
         | 
| 229 | 
            +
                  store)
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                order = subject.class.order_class.find(flow.order_id)
         | 
| 232 | 
            +
                order.order_book.should eq BitexBot::Settings.maker_settings.order_book
         | 
| 233 | 
            +
              end
         | 
| 164 234 | 
             
            end
         | 
| @@ -3,8 +3,8 @@ require 'spec_helper' | |
| 3 3 | 
             
            describe BitexBot::OrderBookSimulator do
         | 
| 4 4 | 
             
              describe 'when buying on bitex to sell somewhere else' do
         | 
| 5 5 | 
             
                def simulate(volatility, amount)
         | 
| 6 | 
            -
                  BitexBot::OrderBookSimulator.run(volatility,  | 
| 7 | 
            -
                     | 
| 6 | 
            +
                  BitexBot::OrderBookSimulator.run(volatility, bitstamp_api_wrapper_transactions_stub,
         | 
| 7 | 
            +
                    bitstamp_api_wrapper_order_book.bids, amount, nil)
         | 
| 8 8 | 
             
                end
         | 
| 9 9 |  | 
| 10 10 | 
             
                it 'gets the safest price' do
         | 
| @@ -14,15 +14,15 @@ describe BitexBot::OrderBookSimulator do | |
| 14 14 | 
             
                it 'adjusts for medium volatility' do
         | 
| 15 15 | 
             
                  simulate(3, 20).should == 25
         | 
| 16 16 | 
             
                end
         | 
| 17 | 
            -
             | 
| 17 | 
            +
             | 
| 18 18 | 
             
                it 'adjusts for high volatility' do
         | 
| 19 19 | 
             
                  simulate(6, 20).should == 20
         | 
| 20 20 | 
             
                end
         | 
| 21 | 
            -
             | 
| 21 | 
            +
             | 
| 22 22 | 
             
                it 'big orders dig deep' do
         | 
| 23 23 | 
             
                  simulate(0, 180).should == 15
         | 
| 24 24 | 
             
                end
         | 
| 25 | 
            -
             | 
| 25 | 
            +
             | 
| 26 26 | 
             
                it 'big orders with high volatility' do
         | 
| 27 27 | 
             
                  simulate(6, 100).should == 10
         | 
| 28 28 | 
             
                end
         | 
| @@ -34,8 +34,8 @@ describe BitexBot::OrderBookSimulator do | |
| 34 34 |  | 
| 35 35 | 
             
              describe 'when selling on bitex to buy somewhere else' do
         | 
| 36 36 | 
             
                def simulate(volatility, quantity)
         | 
| 37 | 
            -
                  BitexBot::OrderBookSimulator.run(volatility,  | 
| 38 | 
            -
                     | 
| 37 | 
            +
                  BitexBot::OrderBookSimulator.run(volatility, bitstamp_api_wrapper_transactions_stub,
         | 
| 38 | 
            +
                    bitstamp_api_wrapper_order_book.asks, nil, quantity)
         | 
| 39 39 | 
             
                end
         | 
| 40 40 |  | 
| 41 41 | 
             
                it 'gets the safest price' do
         | 
| @@ -45,15 +45,15 @@ describe BitexBot::OrderBookSimulator do | |
| 45 45 | 
             
                it 'adjusts for medium volatility' do
         | 
| 46 46 | 
             
                  simulate(3, 2).should == 15
         | 
| 47 47 | 
             
                end
         | 
| 48 | 
            -
             | 
| 48 | 
            +
             | 
| 49 49 | 
             
                it 'adjusts for high volatility' do
         | 
| 50 50 | 
             
                  simulate(6, 2).should == 25
         | 
| 51 51 | 
             
                end
         | 
| 52 | 
            -
             | 
| 52 | 
            +
             | 
| 53 53 | 
             
                it 'big orders dig deep' do
         | 
| 54 54 | 
             
                  simulate(0, 8).should == 25
         | 
| 55 55 | 
             
                end
         | 
| 56 | 
            -
             | 
| 56 | 
            +
             | 
| 57 57 | 
             
                it 'big orders with high volatility dig deep' do
         | 
| 58 58 | 
             
                  simulate(6, 6).should == 30
         | 
| 59 59 | 
             
                end
         |