Exemplo n.º 1
0
        public void ValidateArbitrationTradeOrderExecution(ArbitrationOpportunity arbitrationTrade)
        {
            bool          tradeFailed  = false;
            StringBuilder errorMessage = new StringBuilder("Arbitration trade " + CommonFunctions.StringManipulation.NullIntString(arbitrationTrade.Id) + " did not execute. ");

            //Note, btce is special. If orders are fulfilled immediately, it just returns zero, not the actual order id. So if the exchange is btce,
            //and the order id is 0, no need to do a check.

            //Check to see if both the buy and sell orders were executed; check for btce order fulfillment first, to keep an error getting thrown on the order fulfillment check because of a non-existent '0' order id.
            if (!(arbitrationTrade.SellExchange.GetType() == typeof(Btce) && arbitrationTrade.SellOrderId == "0") && !arbitrationTrade.SellExchange.IsOrderFulfilled(arbitrationTrade.SellOrderId))
            {
                errorMessage.Append("Sell order did not get filled. ");
                tradeFailed = true;
            }

            if (!(arbitrationTrade.BuyExchange.GetType() == typeof(Btce) && arbitrationTrade.BuyOrderId == "0") && !arbitrationTrade.BuyExchange.IsOrderFulfilled(arbitrationTrade.BuyOrderId))
            {
                errorMessage.Append("Buy order did not get filled. ");
                tradeFailed = true;
            }

            if (tradeFailed)
            {
                ExchangeBalanceInfo postArbitrationTradebalanceInfo = CalculateFiatAndBitcoinTotals();
                string balanceString = "";
                balanceString += "Balances after trade:" + Environment.NewLine + postArbitrationTradebalanceInfo.ToString() + Environment.NewLine;
                balanceString += "Differences: " + Environment.NewLine + BuildDifferenceString(_exchangeBalanceInfoBeforeArbitrationTrade, postArbitrationTradebalanceInfo);

                throw new ArbitrationTradeValidationException(errorMessage.ToString(), balanceString);
            }
        }
Exemplo n.º 2
0
        //private ArbitrationOpportunity FindArbitration(List<ArbitrationOpportunity> opportunityList)
        //{
        //    ArbitrationOpportunity returnOpportunity = null;

        //    if (opportunityList != null && opportunityList.Count > 0)
        //    {
        //        //Validate the list of opportunities
        //        opportunityList = _opportunityValidator.ValidateOpportunities(opportunityList);

        //        //Decide which opportunity to execute:
        //        returnOpportunity = DetermineOpportunityToExecute(opportunityList);
        //    }

        //    return returnOpportunity;
        //}

        private void ExecuteArbitrationTrade(ArbitrationOpportunity opportunityToExecute)
        {
            if (opportunityToExecute != null)
            {
                opportunityToExecute.ExecutionDateTime = DateTime.Now;
                opportunityToExecute.ArbitrationRunId  = _currentRun.Id;

                try
                {
                    Parallel.Invoke(() => { Sell(opportunityToExecute); }, () => { Buy(opportunityToExecute); });
                }
                catch (AggregateException ae)
                {
                    //Save the opportunity to the db to generate an id; will be useful for the error message
                    opportunityToExecute.PersistToDb();

                    StringBuilder errorMesasge = new StringBuilder("There was a problem executing arbitration trade " + opportunityToExecute.Id + ": ");

                    foreach (var e in ae.InnerExceptions)
                    {
                        errorMesasge.Append(Environment.NewLine + "\t" + e.Message);
                    }

                    log.Error(errorMesasge);
                }

                //If there is not already an id (which would be the case if there was an error), save the opportunity to the db.
                //Do this after executing the trade, to save a couple precious milliseconds between the orderbook update and order execution
                if (opportunityToExecute.Id == null)
                {
                    opportunityToExecute.PersistToDb();
                }

                LogAndDisplayString("Just executed arbitration opportunity " + opportunityToExecute.Id + ".", LogLevel.Info);

                //Print the balance info before the trade is excecuted.
                log.Info("Balances before trade:" + Environment.NewLine + _opportunityValidator.BalanceInfoBeforeTradeString());

                //If the transfer mode is 'OnTime,' move transfer bicoins from the buy exchange to the sell exchange.
                if (_currentRun.TransferMode == TransferMode.OnTime)
                {
                    TransferOnTime(opportunityToExecute);
                }

                //If in live mode, save the arbitration trade again since it now contains ids for the buy and sell orders
                if (CurrentRun.ArbitrationMode == ArbitrationMode.Live)
                {
                    opportunityToExecute.PersistToDb();
                }

                DisplayArbitrationTrade(opportunityToExecute);
            }
        }
Exemplo n.º 3
0
        private void DisplayArbitrationTrade(ArbitrationOpportunity tradeToDisplay)
        {
            if (tradeToDisplay == null)
            {
                return;
            }

            if (tradeToDisplay.Id == null)
            {
                throw new Exception("Cannot display given trade; it does not have an Id.");
            }

            _tradeDataGridView.Invoke(new Action(delegate() { _tradeDataGridView.Rows.Add(tradeToDisplay.Id, tradeToDisplay.ExecutionDateTime, tradeToDisplay.BuyExchange.Name, tradeToDisplay.SellExchange.Name, Math.Round(tradeToDisplay.Profit, 10), Math.Round(tradeToDisplay.BuyAmount, 10), Math.Round(tradeToDisplay.BuyPrice, 4), Math.Round(tradeToDisplay.TotalBuyCost, 10), Math.Round(tradeToDisplay.SellPrice, 4), Math.Round(tradeToDisplay.TotalSellCost, 10)); }));
        }
Exemplo n.º 4
0
        /// <summary>
        /// Calculates profit with the exchange fees taken into account. If arbitration is still possible, ReturnOpportunity and MaxBTC are updated as needed.
        /// Returns a bool indicating if profit is possible between the two exchanges.
        /// </summary>
        /// <param name="tradeAmount">That amount of BTC to be traded between the two exchanges.</param>
        /// <param name="buyExchange">The exchange to buy from; where the ask order is being fulfilled.</param>
        /// <param name="sellExchange">The exchange to sell at; where the bid order is being fulfilled.</param>
        /// <param name="returnOpportunity">ArbitrationOpportunity that is being used to keep track of the total arbitration between the buy and sell exchanges. Note,
        ///     this variable is passed in by reference, so the object that is passed to this method is directly updated.</param>
        /// <param name="ask">Ask order that is being used from the buy exchange.</param>
        /// <param name="bid">Bid order that is being used from the sell exchange.</param>
        /// <param name="availableBtc">The amount of BTC available for an aribtration trade. Note, this variable is passed in to by reference,
        ///     so the object that is passed to this method is directly updated.</param>
        /// <param name="availableFiat">The amount of fiat available for an arbitration trade. Note, this variable is passed in to by reference,
        ///     so the object that is passed to this method is directly updated</param>
        /// <returns>Returns a boolean. True if the profit can be made from the given ask and bid. False if no arbitration can occur.</returns>
        private static bool CalculateArbitration(decimal tradeAmount, BaseExchange buyExchange, BaseExchange sellExchange, ref ArbitrationOpportunity returnOpportunity, Order ask, Order bid, ref decimal availableBtc, ref decimal availableFiat)
        {
            decimal sellAmount;
            decimal buyCost;

            if (buyExchange.CurrencyTypeBuyFeeIsAppliedTo == CurrencyType.Bitcoin)
            {
                //The buy exchange applies their fee to btc, lower the sell amount to compensate.
                sellAmount = Decimal.Multiply(tradeAmount, decimal.Subtract(1m, buyExchange.TradeFeeAsDecimal));

                buyCost = Decimal.Multiply(tradeAmount, ask.Price);
            }
            else
            {
                //Buy fee is applied to fiat, so no need to decrease sell amount.
                sellAmount = tradeAmount;

                buyCost = Decimal.Multiply(Decimal.Add(1.0m, buyExchange.TradeFeeAsDecimal), Decimal.Multiply(tradeAmount, ask.Price));
            }

            decimal sellCost = Decimal.Multiply(Decimal.Subtract(1.0m, sellExchange.TradeFeeAsDecimal), Decimal.Multiply(sellAmount, bid.Price));
            decimal profit   = Decimal.Subtract(sellCost, buyCost);

            //Just to weed out arbitration opportunities whose profit is so low that they aren't practical. Note, this isn't the Minimum profit requirement,
            //to return an arbitration opportunity, this limit just keeps the algorithm from adding to an arbitration opportunity trade amounts that don't add
            //at least 0.01 profit to the overall opportunity.
            if (profit > 0.001m)
            {
                if (returnOpportunity == null)
                {
                    returnOpportunity = new ArbitrationOpportunity(buyExchange, sellExchange);
                }

                //Increase the amount of the arbitration opportunity; add the new buy and sell orders to this opportunity
                returnOpportunity.BuyAmount  = Decimal.Add(returnOpportunity.BuyAmount, tradeAmount);
                returnOpportunity.SellAmount = Decimal.Add(returnOpportunity.SellAmount, sellAmount);

                returnOpportunity.AddBuyOrder(new Order(tradeAmount, ask.Price));
                returnOpportunity.AddSellOrder(new Order(sellAmount, bid.Price));

                //Decrement MaxBTC and MaxFiat; since some was used on this iteration, less will be available for use on subsequent iterations
                availableBtc  = Decimal.Subtract(availableBtc, tradeAmount);
                availableFiat = Decimal.Subtract(availableFiat, buyCost);

                return(true);
            }

            return(false);
        }
Exemplo n.º 5
0
        private void Sell(ArbitrationOpportunity opportunityToExecute)
        {
            switch (_currentRun.ArbitrationMode)
            {
            case ArbitrationMode.Live:

                //In live move, save the order id string
                opportunityToExecute.SellOrderId = opportunityToExecute.SellExchange.Sell(opportunityToExecute.SellAmount, opportunityToExecute.SellPrice);
                break;

            case ArbitrationMode.Simulation:
                opportunityToExecute.SellExchange.SimulatedSell(opportunityToExecute.SellAmount, opportunityToExecute.TotalSellCost);
                break;
            }
        }
Exemplo n.º 6
0
        /// <summary>
        /// Looks through all the possible combinations of the exchanges in _exchangeList to find possible arbitration opportunities. Returns a list of all the
        /// possible arbitration opportunities ordered by profit.
        /// </summary>
        /// <param name="maxBtc">The maximum amount of BTC that can be used to find arbitration</param>
        /// <param name="maxFiat">The maximum amount of fiat that can be used in an arbitration trade.</param>
        /// <param name="minimumProfit">The minimum profit required for an arbitration opportunity to be considered valid. That is, the minimum profit required
        ///     for an arbitration opportunity to be returned in the return list.</param>
        /// <returns>If there are arbitration opportunities available, a list of all the opportunities order by descending profit. If there aren't any opportunities,
        /// null is returned.</returns>
        public List <ArbitrationOpportunity> FindArbitration(decimal maxBtc, decimal maxFiat, decimal minimumProfit, bool accountForBtcTransferFee)
        {
            List <ArbitrationOpportunity> opportunityList = new List <ArbitrationOpportunity>();

            UpdateExchangeOrderBooks();

            foreach (BaseExchange buyExchange in _exchangeList)
            {
                foreach (BaseExchange sellExchange in _exchangeList)
                {
                    //No point in trying to find arbitration with the same exchange; move on to the next one.
                    if (buyExchange == sellExchange)
                    {
                        continue;
                    }

                    //In addition to the hard BTC limit passed into this method, arbitration opportunities will be limited by the number
                    //of bitcoins in the sell exchange. Recalculate MaxBtc with this in mind.
                    decimal availableBtc = Math.Min(maxBtc, sellExchange.AvailableBtc);

                    //Same with fiat; in addition to the hard fiat limit passed into this method, arbitration opportunities will be limited
                    //by the amount of money in the buy exchange. Recalculate MaxFiat with this in mind.
                    //Same with fiat; in addition to the hard fiat limit passed into this method, arbitration opportunities will be limited
                    //by the amount of money in the buy exchange. Recalculate MaxFiat with this in mind.
                    //**NOTE** Because of how the exchange to rounding (like Bitstamp), it is possible calculate a value from the available fiat, but then
                    //have the actual cost be higher. Thus, subtract $0.01 from the availabe fiat in the buy exchange; this will ensure that scenario doesn't ever happen
                    decimal availableFiat = Math.Min(maxFiat, (buyExchange.AvailableFiat - 0.01m));

                    ArbitrationOpportunity opportunity = CalculateArbitration(buyExchange, sellExchange, availableBtc, availableFiat, minimumProfit, accountForBtcTransferFee);

                    if (opportunity != null)
                    {
                        opportunityList.Add(opportunity);
                    }
                }
            }

            if (opportunityList.Count > 0)
            {
                opportunityList = opportunityList.OrderByDescending(opportunity => opportunity.Profit).ToList();

                return(opportunityList);
            }

            return(null);
        }
Exemplo n.º 7
0
        private void LogArbitrationOpportunity(ArbitrationOpportunity opportunity)
        {
            if (opportunity != null)
            {
                StringBuilder logStringBuilder = new StringBuilder();
                logStringBuilder.Append("\"" + DateTime.Now.ToString() + "\",\"" + opportunity.BuyExchange.Name + "\",\"" + opportunity.SellExchange.Name + "\",\"" + opportunity.BuyPrice + "\",\"" + opportunity.SellPrice + "\",\"" + opportunity.BuyAmount + "\",\"" + opportunity.Profit + "\",\"" + opportunity.BuyExchangeOrderBook.AsksToString() + "\",\"" + opportunity.SellExchangeOrderBook.BidsToString() + "\"");

                try
                {
                    FileWriting.WriteToFile(_currentRun.LogFileName, logStringBuilder.ToString(), FileMode.Append);
                }
                catch (IOException)
                {
                    //Do nothing; just need to catch the exception. If the file is not available its no worries,
                    //just don't log.
                }
            }
        }
Exemplo n.º 8
0
        private void Buy(ArbitrationOpportunity opportunityToExecute)
        {
            //*NOTE* doing a floor round here to avoid a round up situation (where Math.Round rounds up so the total buy cost is slightly more
            //than the amount of base currency at the buy exchange). This actually will leave some base currency in the exchange which is wrong,
            //but since it is rounded to 9 decimal places that's ok (we're are talking trillionths of a penny here)
            switch (_currentRun.ArbitrationMode)
            {
            case ArbitrationMode.Live:

                //When in live mode, save the order id string
                opportunityToExecute.BuyOrderId = opportunityToExecute.BuyExchange.Buy(opportunityToExecute.BuyAmount, opportunityToExecute.BuyPrice);
                break;

            case ArbitrationMode.Simulation:
                opportunityToExecute.BuyExchange.SimulatedBuy(opportunityToExecute.BuyAmount, MathHelpers.FloorRound(opportunityToExecute.TotalBuyCost, 9));
                break;
            }
        }
Exemplo n.º 9
0
        /// <summary>
        /// Returns the ArbitrationOpportunity with the largest profit in the given list. If the given list is null or
        /// empty, this method returns null.
        /// </summary>
        /// <param name="opportunityList">List of arbitration opportunities to compare.</param>
        /// <returns>ArbitrationOpportunity with the greatest profit. Null if the given list is null or empty.</returns>
        public static ArbitrationOpportunity MostProfitableTrade(List <ArbitrationOpportunity> opportunityList)
        {
            ArbitrationOpportunity highestProfitOpportunity = null;

            if (opportunityList != null && opportunityList.Count > 0)
            {
                highestProfitOpportunity = opportunityList[0];

                //Since highestProfit was initialized to the profit of the first opportunity in the list, start looping
                //at the second index.
                for (int counter = 1; counter < opportunityList.Count; counter++)
                {
                    if (opportunityList[counter].Profit > highestProfitOpportunity.Profit)
                    {
                        highestProfitOpportunity = opportunityList[counter];
                    }
                }
            }

            return(highestProfitOpportunity);
        }
Exemplo n.º 10
0
        public void Start(ArbitrationRun givenRun)
        {
            //Set property because some special logic needs to happen on the set
            CurrentRun = givenRun;

            //Now that the run is officially starting, set the start time if it has not already been set
            //(continued runs keep old start time
            if (_currentRun.StartDateTime == null)
            {
                _currentRun.StartDateTime = DateTime.Now;
            }

            //Set the validator
            _opportunityValidator = new OpportunityValidator(givenRun.RoundsRequiredForValidation, CurrentRun.ExchangeList);

            //Need to reset previousOpportunity since this is a new run; don't validate opportunities from previous runs
            _previousOpportunity = null;

            try
            {
                _currentRun.PersistToDb();

                log.Info("Arbitration hunting started. Arbitration run ID = " + givenRun.Id + ".");

                SetExchangeTradeFee();
                SetInitialAccountBalances();
                DisplayExchangeBalances();

                _timer.Interval = _currentRun.SeachIntervalMilliseconds;
                _timer.Enabled  = true;
                _stopped        = false;
            }

            //Catch any kind of exception here that could occur from updating the exchange balances. A problem can manifest itself several ways depending on the exchange, so this needs to be a catch all.
            catch (Exception exception)
            {
                OnError("There was a problem updating one of the exchange balances.", exception, "There was a problem getting the account balance for one of the exchanges: ");
            }
        }
Exemplo n.º 11
0
        public string ValidateExchangeBalancesAfterTrade(ArbitrationOpportunity arbitrationTrade)
        {
            ExchangeBalanceInfo postArbitrationTradebalanceInfo = CalculateFiatAndBitcoinTotals();
            decimal             realizedProfit = postArbitrationTradebalanceInfo.TotalFiatBalance - _exchangeBalanceInfoBeforeArbitrationTrade.TotalFiatBalance;
            string errorMessage  = "";
            string balanceString = "";

            balanceString += "\tBalances after trade:" + Environment.NewLine + postArbitrationTradebalanceInfo.ToString() + Environment.NewLine;
            balanceString += "\tDifferences: " + Environment.NewLine + BuildDifferenceString(_exchangeBalanceInfoBeforeArbitrationTrade, postArbitrationTradebalanceInfo);

            if (realizedProfit <= arbitrationTrade.Profit * 0.98m)
            {
                errorMessage = "Realized profit for arbitration trade " + arbitrationTrade.Id + " was more than 2% less than the expected profit. Expected profit = " + arbitrationTrade.Profit + ", realized profit = " + realizedProfit + ".";
            }

            else if (realizedProfit >= arbitrationTrade.Profit * 1.02m)
            {
                errorMessage = "Realized profit for arbitration trade " + arbitrationTrade.Id + " was more than 2% greater than the expected profit. Expected profit = " + arbitrationTrade.Profit + ", realized profit = " + realizedProfit + ".";
            }

            if (postArbitrationTradebalanceInfo.TotalBitcoinBalance <= _exchangeBalanceInfoBeforeArbitrationTrade.TotalBitcoinBalance * 0.98m)
            {
                errorMessage += "Bitcoin balance after arbitration trade " + arbitrationTrade.Id + " decreased by more than 2%. Bitcoin balance before trade = " + _exchangeBalanceInfoBeforeArbitrationTrade.TotalBitcoinBalance + ", bitcoin balance after trade = " + postArbitrationTradebalanceInfo.TotalBitcoinBalance + ".";
            }

            else if (postArbitrationTradebalanceInfo.TotalBitcoinBalance >= _exchangeBalanceInfoBeforeArbitrationTrade.TotalBitcoinBalance * 1.02m)
            {
                errorMessage += "Bitcoin balance after arbitration trade " + arbitrationTrade.Id + " increased by more than 2%. Bitcoin balance before trade = " + _exchangeBalanceInfoBeforeArbitrationTrade.TotalBitcoinBalance + ", bitcoin balance after trade = " + postArbitrationTradebalanceInfo.TotalBitcoinBalance + ".";
            }

            //If there is text in erroMessage, the trade did not validate
            if (!String.IsNullOrWhiteSpace(errorMessage))
            {
                throw new ArbitrationTradeValidationException(errorMessage, balanceString);
            }

            return(balanceString);
        }
Exemplo n.º 12
0
        private void TransferOnTime(ArbitrationOpportunity opportunityToExecute)
        {
            Transfer executedTransfer = null;

            switch (_currentRun.ArbitrationMode)
            {
            case ArbitrationMode.Live:

                //No need to keep track of transfers in live mode since they are completed automatically.
                executedTransfer = TransferManager.OnTimeTransfer_Live(opportunityToExecute.BuyExchange, opportunityToExecute.SellExchange, opportunityToExecute.BuyAmount, log);
                break;

            case ArbitrationMode.Simulation:
                executedTransfer = TransferManager.OnTimeTransfer_Simulate(opportunityToExecute.BuyExchange, opportunityToExecute.SellExchange, opportunityToExecute.BuyAmount, log);
                //In simulation mode, keep track of transfers so that they can be completed manually later.
                _transfersInProcess.Add(executedTransfer);
                break;
            }

            //if (executedTransfer != null)
            {
                PrintToOutputTextBox((String.Format(LOG_MESSAGE, executedTransfer.Id.Value, executedTransfer.Amount, executedTransfer.OriginExchange.Name, executedTransfer.DestinationExchange.Name)));
            }
        }
Exemplo n.º 13
0
        /// <summary>
        /// Determines if the arbitration is possible with the given buy and sell exchanges. The order book for both exchanges must be ordered (that is,
        /// asks with ascending order price and bids with descending order price) for this method to work properly.
        /// </summary>
        /// <param name="buyExchange">The exchange to buy from.</param>
        /// <param name="sellExchange">The exchange to sell at.</param>
        /// <param name="availableBtc">The amount of BTC available for an aribtration trade.</param>
        /// <param name="availableFiat">The amount of fiat available for an arbitration trade.</param>
        /// <param name="minimumProfit">The minimum profit required for an arbitration opportunity to be considered valid. That is, the minimum profit required
        ///     for an arbitration opportunity to be returned by this method.</param>
        /// <returns>An ArbirationOpportunity object is arbitration between the two given exchanges can occur. Otherwise, returns null.</returns>
        public ArbitrationOpportunity CalculateArbitration(BaseExchange buyExchange, BaseExchange sellExchange, decimal availableBtc, decimal availableFiat, decimal minimumProfit, bool accountForBtcTransferFee)
        {
            ArbitrationOpportunity returnOpportunity = null;

            //These two lists keep track of the asks and bids lists as they are used
            List <Order> runningAskList;
            List <Order> runningBidList;

            //Only need to clone the Asks of the buy exchange (since we aren't selling there don't need the bids), and only need to clone the Bids of the sell
            //exchange (since we aren't buying there don't need the asks)
            if (buyExchange.OrderBook != null && buyExchange.OrderBook.Asks != null && sellExchange.OrderBook != null && sellExchange.OrderBook.Bids != null)
            {
                runningAskList = buyExchange.OrderBook.Asks.Clone();
                runningBidList = sellExchange.OrderBook.Bids.Clone();
            }
            else
            {
                //Defensive code: at this point, both orderbooks and their order lists shouldn't be null. But just in case, return null.
                return(null);
            }

            //Continue to look for aribtration while:
            //  - The next ask has a higher price than the next bid (meaning there is a potential for arbitration)
            //  - There is btc left to use up
            //  - There is fiat left to use. Note, this is limited at 1.00 to prevent any fringe situation where this method finds arbitration with a very small
            //    amount of btc. If there is less than 1.00, just don't bother looking for arbitration as that isn't practical. This would probably be assuaged
            //    by the min profit limit anyways, but just to be safe.
            while (runningBidList.Count > 0 && runningAskList.Count > 0 && runningBidList[0].Price > runningAskList[0].Price && availableBtc > 0 && availableFiat > 1.00m)
            {
                decimal buyAmount;
                Order   bid = runningBidList[0];
                Order   ask = runningAskList[0];

                bool calculateProfitResult;

                //Part of the ask (buy) order has been fulfilled
                if ((buyExchange.CurrencyTypeBuyFeeIsAppliedTo == CurrencyType.Fiat && ask.Amount > bid.Amount) || (buyExchange.CurrencyTypeBuyFeeIsAppliedTo == CurrencyType.Bitcoin && ask.Amount > Decimal.Multiply(bid.Amount, Decimal.Add(1, buyExchange.TradeFeeAsDecimal))))
                {
                    buyAmount             = DetermineBuyTradeAmount(availableBtc, ask, bid, availableFiat, buyExchange.TradeFeeAsDecimal, buyExchange.CurrencyTypeBuyFeeIsAppliedTo, OrderType.Bid);
                    calculateProfitResult = CalculateArbitration(buyAmount, buyExchange, sellExchange, ref returnOpportunity, ask, bid, ref availableBtc, ref availableFiat);

                    if (calculateProfitResult)
                    {
                        ask.Amount = decimal.Subtract(ask.Amount, buyAmount);

                        //If the entire bid order was filled, remove it
                        //*NOTE* Greater than or equal too is needed, because in the case of the buy fee being applied to btc, the buy amount may actually be greater than amount of the sell order
                        if (buyAmount >= bid.Amount)
                        {
                            runningBidList.RemoveAt(0);
                        }
                    }
                }

                //All of the ask (buy) order has been filled
                else if ((buyExchange.CurrencyTypeBuyFeeIsAppliedTo == CurrencyType.Fiat && ask.Amount < bid.Amount) || (buyExchange.CurrencyTypeBuyFeeIsAppliedTo == CurrencyType.Bitcoin && ask.Amount < Decimal.Multiply(bid.Amount, Decimal.Add(1, buyExchange.TradeFeeAsDecimal))))
                {
                    buyAmount             = DetermineBuyTradeAmount(availableBtc, ask, bid, availableFiat, buyExchange.TradeFeeAsDecimal, buyExchange.CurrencyTypeBuyFeeIsAppliedTo, OrderType.Ask);
                    calculateProfitResult = CalculateArbitration(buyAmount, buyExchange, sellExchange, ref returnOpportunity, ask, bid, ref availableBtc, ref availableFiat);

                    if (calculateProfitResult)
                    {
                        bid.Amount = Decimal.Subtract(bid.Amount, buyAmount);

                        //If the entire ask order was filled, remove it
                        if (buyAmount >= ask.Amount)
                        {
                            runningAskList.RemoveAt(0);
                        }
                    }
                }

                //The ask and buy orders are the same amount so the both get filled
                //WARNING!!!! This block technically isn't right, it very slightly error when buy fee is applied to btc. But, the error is very small, and this is a very, very unlikely case so
                //I'm not doing to do anything about it. To see the error, take test case 18 and add another tier of arbitration opportunity.
                else
                {
                    buyAmount             = DetermineBuyTradeAmount(availableBtc, ask, bid, availableFiat, buyExchange.TradeFeeAsDecimal, buyExchange.CurrencyTypeBuyFeeIsAppliedTo, OrderType.Ask);
                    calculateProfitResult = CalculateArbitration(buyAmount, buyExchange, sellExchange, ref returnOpportunity, ask, bid, ref availableBtc, ref availableFiat);

                    if (calculateProfitResult)
                    {
                        //If both orders were wholly filled, delete them. Note, since this else block is for asks and bids whose amount are the same,
                        //tradeAmount only needs to be checked against one of the orders
                        if (buyAmount >= ask.Amount)
                        {
                            runningBidList.RemoveAt(0);
                            runningAskList.RemoveAt(0);
                        }
                    }
                }

                //If no profit could be found, exit the while loop
                if (!calculateProfitResult)
                {
                    break;
                }
            }

            //An oppportunity was found, now see if it meets the minimum profit requirements
            if (returnOpportunity != null)
            {
                if (returnOpportunity.BuyExchangeOrderBook == null || returnOpportunity.SellExchangeOrderBook == null)
                {
                    //If arbitration can occur, add the order books to returnOpportunity for recordkeeping and trouble shooting
                    returnOpportunity.BuyExchangeOrderBook  = buyExchange.OrderBook;
                    returnOpportunity.SellExchangeOrderBook = sellExchange.OrderBook;
                }

                returnOpportunity.CalculateArbitrationOpportunityCosts();

                //Todo: just increase the buy amount by the transfer fee, don't use this method
                //Arbitration calculation finished, now take into account the transfer trade fee if specified:
                if (accountForBtcTransferFee)
                {
                    //Calculate the average btc cost in fiat
                    decimal averageCost = (returnOpportunity.BuyPrice + returnOpportunity.SellPrice) / 2;

                    //Using the average buy/sell cost, determine if the opportunity is stil profitable after losing that amount of btc in dollars.
                    returnOpportunity.Profit -= returnOpportunity.BuyExchange.BtcTransferFee * averageCost;
                }

                //Now that arbitration has been fully calculated between the two exchange, and the opportunity has the required minimum profit, see if the buy and sell orders are valid:
                if (returnOpportunity.Profit >= minimumProfit && returnOpportunity.BuyExchange.IsOrderCostValidForExchange(returnOpportunity.TotalBuyCost, returnOpportunity.BuyAmount) && returnOpportunity.SellExchange.IsOrderCostValidForExchange(returnOpportunity.TotalSellCost, returnOpportunity.SellAmount))
                {
                    return(returnOpportunity);
                }
            }

            return(null);
        }
Exemplo n.º 14
0
        private void IntervalElapsed(Object source, ElapsedEventArgs e)
        {
            ArbitrationOpportunity        opportunityToExecute = null;
            List <ArbitrationOpportunity> opportunityList      = null;

            //Timer needs to chill while arbitration is looked for.
            _timer.Enabled = false;

            //Update the balances first, to give time for orders that may have been initiated on the previous round to execute.
            //Only need to update account balances in live mode.
            if (_currentRun.ArbitrationMode == ArbitrationMode.Live)
            {
                UpdateExchangeAccountInfo();
            }

            DisplayExchangeBalances();

            try
            {
                //Before looking for another arbitration opportunity, see if the previous one was executed correctly.
                if (_previousOpportunity != null)
                {
                    //Verify the arbitration trade executed as expected.
                    try
                    {
                        //See if the orders filled (only necesary in live mode)
                        if (CurrentRun.ArbitrationMode == ArbitrationMode.Live)
                        {
                            _opportunityValidator.ValidateArbitrationTradeOrderExecution(_previousOpportunity);
                        }

                        //Compare the balances - this will detect if realized profit is different than computed profit, which would indicate there is a serious problem somewhere
                        log.Info(_opportunityValidator.ValidateExchangeBalancesAfterTrade(_previousOpportunity));
                    }
                    catch (ArbitrationTradeValidationException error)
                    {
                        LogAndDisplayString(error.Message, LogLevel.Warning);
                        log.Info(error.ExchangeBalanceDetailMessage);
                    }
                }

                //Reset _previousOpportunity, so it is not verified again
                _previousOpportunity = null;

                //Prepare the opportunity validator so it can compare balances before and after a trade
                _opportunityValidator.SetFiatAndBitcoinBalanceBeforeArbitrationTrade();

                try
                {
                    opportunityList = FindArbitrationOpportunities();

                    if (opportunityList != null && opportunityList.Count > 0)
                    {
                        opportunityToExecute = DetermineOpportunityToExecute(opportunityList);
                    }
                }
                catch (Exception exception)
                {
                    log.Warn("There was a problem looking for arbitration: " + Environment.NewLine + exception.Message);
                    log.Debug(exception.StackTrace);
                }

                //If an opportunity was found, execute it
                if (opportunityToExecute != null)
                {
                    ExecuteArbitrationTrade(opportunityToExecute);

                    //Save the arbitration opportunity so it can be verified on the next round.
                    _previousOpportunity = opportunityToExecute;
                }

                //Do the logging and display after the fact, to reduce lage between order book get and order execution
                DisplayAndLogArbitrationOpportunityList(opportunityList);

                //Only need to keep track of transfers in simulation mode.
                if (_currentRun.ArbitrationMode == ArbitrationMode.Simulation)
                {
                    //Complete any transfers that are done processing.
                    TransferManager.CompleteTransfers(_transfersInProcess);
                }

                //Based on the transfer mode of the current run, process any transfers that are due.
                try
                {
                    if (_currentRun.TransferMode == TransferMode.RollupOnTrades)
                    {
                        TransferRollupOnTrades();
                    }

                    else if (_currentRun.TransferMode == TransferMode.RollupByHour)
                    {
                        TransferRollupByHours();
                    }

                    //Note, for transfer mode 'None,' don't need to do anything.
                }
                catch (Exception exception)
                {
                    OnError("There was a problem processing the transfers.", exception, "There was a problem processing the transfers: ");
                }

                //If this manager hasn't been stopped since the timer fired (which would be the case if there were a fatal error or the stop button was clicked), turn the timer back on.
                if (!_stopped)
                {
                    _timer.Enabled = true;
                }
            }
            catch (Exception exception)
            {
                OnError("There was an error in this arbitration run, stopping the run.", exception, "Unexpected error with the arbitration run: ");
            }
        }