Example #1
0
        /// <summary>
        /// Queue a trade based on a proposal, the callback must return the trade execution
        /// which will be used to update the allocation.
        /// </summary>
        /// <param name="p">TradeProposal to be verified.</param>
        /// <param name="tradeCallback">Trade callback to be executed if verification was successful.</param>
        /// <returns>Boolean indicating successful execution of the callback.</returns>
        public virtual ResponseObject <OrderUpdate> QueueTrade(TradeProposal p, Func <OrderUpdate> tradeCallback)
        {
            Guard.Argument(_allocation).NotNull("Initialise allocations first");
            Guard.Argument(p).NotNull();

            var alloc = GetAvailableFunds(p.From.Symbol);

            if (alloc.Free < p.From.Free || alloc.Locked < p.From.Locked)
            {
                _logger.LogCritical($"Got trade proposal for ({p.From}, but allocation " +
                                    $"showed only ({alloc}) was available\n" +
                                    "Trade will not be executed.");
                return(ResponseObject.OrderRefused);
            }

            // Let the provider execute the trade and save the execution report
            var order = tradeCallback();

            // TradingProvider can give a null execution report when it decides not to trade.
            // if this happens the portfolio will be checked against the remote using an 'empty' or 'monoid' trade execution.
            if (order is null)
            {
                _logger.LogWarning("TradingProvider implementation returned a null OrderUpdate");
                return(ResponseObject.OrderPlacementFailed("Implementation returned a null OrderUpdate"));
            }

            TradeExecution exec = TradeExecution.FromOrder(order);

            // Update the local information
            UpdateAllocation(exec);

            return(new ResponseObject <OrderUpdate>(order));
        }
Example #2
0
        public void ConstructorHappyFlow()
        {
            var pair     = TradingPairTests.GetTradingPair("BNB", "ETH", 20);
            var proposal = new TradeProposal(pair, new Balance(new Currency("BNB"), 1, 0.00000001M));

            Assert.Equal(1, proposal.From.Free);
            Assert.Equal(0.00000001M, proposal.From.Locked);
        }
Example #3
0
        public void ConstructorPerformsRounding()
        {
            var pair     = TradingPairTests.GetTradingPair("BNB", "ETH", 2);
            var balance  = new Balance(new Currency("BNB"), 42.1234569M, 0.000000000099M);
            var proposal = new TradeProposal(pair, balance);

            Assert.Equal(42.12M, proposal.From.Free);
            Assert.Equal(0, proposal.From.Locked);
        }
Example #4
0
        /// <inheritdoc />
        public ResponseObject <OrderUpdate> QueueTrade(TradeProposal p, Func <OrderUpdate> tradeCallback)
        {
            if (p.From.Symbol == new Currency(RefuseCoin))
            {
                return(ResponseObject.OrderRefused);
            }

            return(new ResponseObject <OrderUpdate>(tradeCallback()));
        }
Example #5
0
        /// <summary>
        /// Place a buy market order given a non base quantity.
        /// </summary>
        /// <param name="pair">TradingPair to consider.</param>
        /// <param name="quantity">Quantity of non base currency.</param>
        /// <returns>ResponseObject containing an OrderUpdate.</returns>
        public OrderUpdate ExecuteMarketOrderBuy(TradingPair pair, decimal quantity)
        {
            _logger.LogDebug($"Executing buy market order with pair {pair}, quantity {quantity}");
            Guard.Argument(pair).NotNull(nameof(pair));
            Guard.Argument(quantity).NotZero(nameof(quantity)).NotNegative();
            var currency      = pair.Right;
            var priceEstimate = _dataProvider.GetCurrentPriceTopAsk(pair);
            var proposal      = new TradeProposal(pair, new Balance(currency, quantity * priceEstimate, 0));

            var result = _allocationManager.QueueTrade(proposal, () =>
            {
                var orderQuery = HelperMethods.RetryMethod(
                    () =>
                {
                    var attempt = Implementation.ExecuteMarketOrder(pair, OrderSide.Buy, quantity, TradeId);
                    if (attempt.Success)
                    {
                        return(attempt);
                    }

                    _logger.LogInformation($"Retrying with slightly lower quantity");
                    quantity *= 0.999M;
                    return(new ResponseObject <OrderUpdate>(ResponseCode.Error, attempt.Message));
                }, _logger);

                if (orderQuery.Success)
                {
                    _openOrders.Add(orderQuery.Data.OrderId, orderQuery.Data);
                    return(WaitForOrderStatus(orderQuery.Data.OrderId, OrderUpdate.OrderStatus.Filled));
                }

                throw new OrderFailedException(orderQuery.Message);
            });

            if (!result.Success)
            {
                throw new OrderRefusedException(result.Message);
            }

            if (Program.CommandLineArgs.Trading)
            {
                _logger.LogInformation($"Executed Market Buy: {JsonConvert.SerializeObject(result)}");
            }

            return(result.Data
                   .IsBuy()
                   .IsMarket()
                   .IsFilled());
        }
Example #6
0
        /// <summary>
        /// Place a buy stoploss order.
        /// </summary>
        /// <param name="pair">TradingPair.</param>
        /// <param name="quantity">Quantity of none base currency to trade with.</param>
        /// <param name="price">Price to set the order at.</param>
        /// <returns>ResponseObject containing an OrderUpdate.</returns>
        public OrderUpdate PlaceStoplossBuy(TradingPair pair, decimal quantity, decimal price)
        {
            _logger.LogDebug($"Placing stoploss buy order for pair {pair}, quantity {quantity}, price {price}");
            Guard.Argument(pair).NotNull(nameof(pair));
            Guard.Argument(quantity).NotZero(nameof(quantity)).NotNegative();
            Guard.Argument(price).NotZero(nameof(price)).NotNegative();
            var currency = pair.Right;
            var proposal = new TradeProposal(pair, new Balance(currency, quantity * price, 0));

            var result = _allocationManager.QueueTrade(proposal, () =>
            {
                return(HelperMethods.RetryMethod(
                           context =>
                {
                    var query = Implementation.PlaceStoplossOrder(pair, OrderSide.Buy, quantity, price, TradeId);
                    if (query.Code == ResponseCode.ImmediateOrderTrigger)
                    {
                        _logger.LogWarning($"Increase stop price and retry.");
                        price += pair.MinPriceTick * context.Iteration; // Linear price backoff
                        context.DisableBackoff();                       // No need for delays
                    }

                    return query;
                },
                           _logger).Data);
            });

            if (result.Success)
            {
                _openOrders[result.Data.OrderId] = result.Data;
                var order = WaitForOrderStatus(result.Data.OrderId, OrderUpdate.OrderStatus.New);
                order.IsStopLoss()
                .IsBuy()
                .IsNew();

                if (Program.CommandLineArgs.Trading)
                {
                    _logger.LogInformation($"Placed Stoploss Buy: {JsonConvert.SerializeObject(result)}");
                }

                return(result.Data);
            }

            throw new OrderRefusedException(result.Message);
        }
Example #7
0
        public void QueueTradeInvalid()
        {
            var     alloc    = MakeDefaultAllocation();
            Balance balance  = alloc.GetAvailableFunds(new Currency("ETH"));
            var     proposal = new TradeProposal(TradingPair.Parse("EOSETH"), new Balance(
                                                     balance.Symbol,
                                                     balance.Free + 1,
                                                     balance.Locked));

            var result = alloc.QueueTrade(proposal, () =>
            {
                Logger.LogCritical("Invalid trade is being executed");
                Assert.True(false, "Trade callback of invalid trade is being executed");
                return(null);
            });

            Assert.False(result.Success, "Invalid proposal was reported as executed");
        }
Example #8
0
        public void QueueTradeHappyFlow()
        {
            var alloc = MakeDefaultAllocation();

            // Get most valuable asset from backtesting settings.
            decimal total   = alloc.GetAvailableFunds(new Currency("ETH")).Free;
            decimal quote   = alloc.GetAvailableFunds(new Currency("EOS")).Free;
            Balance balance = new Balance(new Currency("ETH"), total, 0);

            var proposal = new TradeProposal(TradingPair.Parse("EOSETH"), balance);

            var result = alloc.QueueTrade(proposal, () =>
            {
                Logger.LogInformation($"Trading all of the {proposal.From.Free}{proposal.From.Symbol}");

                var order = new OrderUpdate(
                    orderId: 0,
                    tradeId: 0,
                    orderStatus: OrderUpdate.OrderStatus.New,
                    orderType: OrderUpdate.OrderTypes.Market,
                    createdTimestamp: 0,
                    setPrice: 1,
                    side: OrderSide.Buy,
                    pair: TradingPair.Parse("EOSETH"),
                    setQuantity: proposal.From.Free)
                {
                    AverageFilledPrice = 1,
                    FilledQuantity     = proposal.From.Free,
                    LastFillPrice      = 1,
                    LastFillIncrement  = proposal.From.Free,
                };

                return(order);
            });

            Assert.True(result.Success, "Valid trade was declared invalid");
            Assert.Equal(total - proposal.From.Free, alloc.GetAvailableFunds(balance.Symbol).Free);
            Assert.Equal(quote + proposal.From.Free, alloc.GetAvailableFunds(new Currency("EOS")).Free);
        }
Example #9
0
        /// <summary>
        /// Place a buy limit order given a non base quantity and a target price.
        /// </summary>
        /// <param name="pair">TradingPair to consider.</param>
        /// <param name="quantity">Quantity of non base currency to trade with.</param>
        /// <param name="price">Price to set order at.</param>
        /// <returns>ResponseObject containing an OrderUpdate.</returns>
        public OrderUpdate PlaceLimitOrderSell(TradingPair pair, decimal quantity, decimal price)
        {
            _logger.LogDebug($"Placing limit sell order with pair {pair}, quantity {quantity}, price {price}");
            Guard.Argument(pair).NotNull(nameof(pair));
            Guard.Argument(quantity).NotZero(nameof(quantity)).NotNegative();
            Guard.Argument(price).NotZero(nameof(price)).NotNegative();
            var currency = pair.Left;
            var proposal = new TradeProposal(pair, new Balance(currency, quantity, 0));

            var result = _allocationManager.QueueTrade(proposal, () =>
            {
                return(HelperMethods.RetryMethod(
                           () => Implementation.PlaceLimitOrder(
                               pair,
                               OrderSide.Sell,
                               quantity,
                               price,
                               TradeId),
                           _logger).Data);
            });

            if (result.Success)
            {
                _openOrders[result.Data.OrderId] = result.Data;
                var order = WaitForOrderStatus(result.Data.OrderId, OrderUpdate.OrderStatus.New);
                order.IsLimit()
                .IsSell()
                .IsNew();

                if (Program.CommandLineArgs.Trading)
                {
                    _logger.LogInformation($"Placed Limit Sell: {JsonConvert.SerializeObject(result)}");
                }

                return(order);
            }

            throw new OrderRefusedException(result.Message);
        }
Example #10
0
        /// <summary>
        /// Place a sell market order given a non base quantity.
        /// </summary>
        /// <param name="pair">TradingPair to consider.</param>
        /// <param name="quantity">Quantity of non base currency.</param>
        /// <returns>ResponseObject containing an OrderUpdate.</returns>
        public OrderUpdate ExecuteMarketOrderSell(TradingPair pair, decimal quantity)
        {
            _logger.LogDebug($"Executing sell market order with pair {pair}, quantity {quantity}");
            Guard.Argument(pair).NotNull(nameof(pair));
            Guard.Argument(quantity).NotZero(nameof(quantity)).NotNegative();
            var currency = pair.Left;
            var proposal = new TradeProposal(pair, new Balance(currency, quantity, 0));

            var result = _allocationManager.QueueTrade(proposal, () =>
            {
                var orderQuery = HelperMethods.RetryMethod(
                    () => Implementation.ExecuteMarketOrder(pair, OrderSide.Sell, quantity, TradeId), _logger);

                if (orderQuery.Success)
                {
                    _openOrders.Add(orderQuery.Data.OrderId, orderQuery.Data);
                    return(WaitForOrderStatus(orderQuery.Data.OrderId, OrderUpdate.OrderStatus.Filled));
                }

                throw new OrderFailedException(orderQuery.Message);
            });

            if (!result.Success)
            {
                throw new OrderRefusedException(result.Message);
            }

            if (Program.CommandLineArgs.Trading)
            {
                _logger.LogInformation($"Executed Market Sell: {JsonConvert.SerializeObject(result)}");
            }

            return(result.Data
                   .IsSell()
                   .IsMarket()
                   .IsFilled());
        }
Example #11
0
        public void QueueTradeReportZero()
        {
            var     alloc    = MakeDefaultAllocation();
            Balance balance  = alloc.GetAvailableFunds(new Currency("ETH"));
            var     proposal = new TradeProposal(TradingPair.Parse("EOSETH"), balance);

            var result = alloc.QueueTrade(proposal, () =>
                                          new OrderUpdate(
                                              0,
                                              0,
                                              OrderUpdate.OrderStatus.Filled,
                                              OrderUpdate.OrderTypes.Limit,
                                              0,
                                              0,
                                              OrderSide.Buy,
                                              TradingPair.Parse("EOSETH"),
                                              0));

            Assert.True(result.Success, "Valid proposal was not executed");

            // Assert that the allocation was not mutated
            Assert.Equal(proposal.From.Free, alloc.GetAvailableFunds(proposal.From.Symbol).Free);
            Assert.Equal(proposal.From.Locked, alloc.GetAvailableFunds(proposal.From.Symbol).Locked);
        }