public void Create_Dynamically_Distributed_Limit_Orders_By_Levels_With_Buy_Smart_Markup() { // arrange int priceAccuracy = 3; int volumeAccuracy = 8; var quote1 = new Quote("BTCUSD", DateTime.UtcNow, 6000, 5950, "none"); var quote2 = new Quote("BTCUSD", DateTime.UtcNow, 6050, 5900, "none"); decimal baseAssetBalance = 1; decimal quoteAssetBalance = -46.5m; int timeSinceLastTrade = 1; int halfLifePeriod = 30; var levels = new[] { new InstrumentLevel { Number = 1, Volume = 0.01m, Markup = 0.01m }, new InstrumentLevel { Number = 2, Volume = 0.02m, Markup = 0.02m }, new InstrumentLevel { Number = 3, Volume = 0.03m, Markup = 0.03m }, new InstrumentLevel { Number = 4, Volume = 0.04m, Markup = 0.04m }, new InstrumentLevel { Number = 5, Volume = 0.05m, Markup = 0.05m } }; var expectedLimitOrders = new List <LimitOrder> { LimitOrder.CreateSell(6390.000m, 0.05m), LimitOrder.CreateSell(6295.715m, 0.04m), LimitOrder.CreateSell(6209.429m, 0.03m), LimitOrder.CreateSell(6130.929m, 0.02m), LimitOrder.CreateSell(6060.000m, 0.01m), LimitOrder.CreateBuy(5866.741m, 0.01m), LimitOrder.CreateBuy(5820.499m, 0.02m), LimitOrder.CreateBuy(5743.785m, 0.03m), LimitOrder.CreateBuy(5660.571m, 0.04m), LimitOrder.CreateBuy(5571.071m, 0.05m) }; // act IReadOnlyCollection <LimitOrder> actualLimitOrders = Calculator.CalculateLimitOrders(quote1, quote2, levels, baseAssetBalance, quoteAssetBalance, timeSinceLastTrade, halfLifePeriod, true, 0, 0, 0, 0, priceAccuracy, volumeAccuracy); // assert Assert.IsTrue(AreEqual(expectedLimitOrders, actualLimitOrders)); }
public void Create_Dynamically_Distributed_Limit_Orders_By_Levels_With_Sell_Smart_Markup_With_One_Quote() { // arrange int priceAccuracy = 3; int volumeAccuracy = 8; var quote1 = new Quote("BTCUSD", DateTime.UtcNow, 6000, 5950, "none"); var quote2 = new Quote("BTCUSD", DateTime.UtcNow, 6000, 5950, "none"); decimal baseAssetBalance = -10; decimal quoteAssetBalance = -10; int timeSinceLastTrade = 1; int halfLifePeriod = 30; var levels = new[] { new InstrumentLevel { Number = 1, Volume = 0.01m, Markup = 0.01m }, new InstrumentLevel { Number = 2, Volume = 0.02m, Markup = 0.02m }, new InstrumentLevel { Number = 3, Volume = 0.03m, Markup = 0.03m }, new InstrumentLevel { Number = 4, Volume = 0.04m, Markup = 0.04m }, new InstrumentLevel { Number = 5, Volume = 0.05m, Markup = 0.05m } }; var expectedLimitOrders = new List <LimitOrder> { LimitOrder.CreateSell(6300.000m, 0.05m), LimitOrder.CreateSell(6240.000m, 0.04m), LimitOrder.CreateSell(6180.000m, 0.03m), LimitOrder.CreateSell(6120.000m, 0.02m), LimitOrder.CreateSell(6083.958m, 0.01m), LimitOrder.CreateBuy(5890.500m, 0.01m), LimitOrder.CreateBuy(5831.000m, 0.02m), LimitOrder.CreateBuy(5771.500m, 0.03m), LimitOrder.CreateBuy(5712.000m, 0.04m), LimitOrder.CreateBuy(5652.500m, 0.05m) }; // act IReadOnlyCollection <LimitOrder> actualLimitOrders = Calculator.CalculateLimitOrders(quote1, quote2, levels, baseAssetBalance, quoteAssetBalance, timeSinceLastTrade, halfLifePeriod, true, 0, 0, 0, 0, priceAccuracy, volumeAccuracy, new OrderBookUpdateReport(DateTime.UtcNow)); // assert Assert.IsTrue(AreEqual(expectedLimitOrders, actualLimitOrders)); }
/// <summary> /// Calculates cross limit orders by original limit orders using cross price. /// </summary> /// <param name="quote">Best prices of cross instrument.</param> /// <param name="limitOrders">The collection of original limit orders.</param> /// <param name="inverse">Indicates that the quote is inverse.</param> /// <param name="priceAccuracy">The accuracy of price.</param> /// <param name="volumeAccuracy">The accuracy of volume.</param> /// <returns>A collection of limit orders.</returns> public static IReadOnlyCollection <LimitOrder> CalculateCrossLimitOrders(Quote quote, IReadOnlyCollection <LimitOrder> limitOrders, bool inverse, int priceAccuracy, int volumeAccuracy) { return(limitOrders .Select(o => o.Type == LimitOrderType.Sell ? LimitOrder.CreateSell(CalculateCrossSellPrice(quote, o.Price, inverse) .TruncateDecimalPlaces(priceAccuracy, true), Math.Round(o.Volume, volumeAccuracy)) : LimitOrder.CreateBuy(CalculateCrossBuyPrice(quote, o.Price, inverse) .TruncateDecimalPlaces(priceAccuracy), Math.Round(o.Volume, volumeAccuracy))) .ToArray()); }
public void Create_Dynamically_Distributed_Limit_Orders_By_Levels() { // arrange int priceAccuracy = 3; int volumeAccuracy = 8; var quote1 = new Quote("BTCUSD", DateTime.UtcNow, 6000, 5950, "none"); var quote2 = new Quote("BTCUSD", DateTime.UtcNow, 6050, 5900, "none"); var levels = new[] { new InstrumentLevel { Number = 1, Volume = 0.01m, Markup = 0.01m }, new InstrumentLevel { Number = 2, Volume = 0.02m, Markup = 0.02m }, new InstrumentLevel { Number = 3, Volume = 0.03m, Markup = 0.03m }, new InstrumentLevel { Number = 4, Volume = 0.04m, Markup = 0.04m }, new InstrumentLevel { Number = 5, Volume = 0.05m, Markup = 0.05m } }; var expectedLimitOrders = new List <LimitOrder> { LimitOrder.CreateSell(6390.000m, 0.05m), LimitOrder.CreateSell(6295.715m, 0.04m), LimitOrder.CreateSell(6209.429m, 0.03m), LimitOrder.CreateSell(6130.929m, 0.02m), LimitOrder.CreateSell(6060.000m, 0.01m), LimitOrder.CreateBuy(5890.500m, 0.01m), LimitOrder.CreateBuy(5820.499m, 0.02m), LimitOrder.CreateBuy(5743.785m, 0.03m), LimitOrder.CreateBuy(5660.571m, 0.04m), LimitOrder.CreateBuy(5571.071m, 0.05m) }; // act IReadOnlyCollection <LimitOrder> actualLimitOrders = Calculator.CalculateLimitOrders(quote1, quote2, levels, 0, 0, 0, 0, false, 0, 0, 0, 0, priceAccuracy, volumeAccuracy, new OrderBookUpdateReport(DateTime.UtcNow)); // assert Assert.IsTrue(AreEqual(expectedLimitOrders, actualLimitOrders)); }
//[TestMethod] public async Task Calculate_Limit_Orders() { // arrange IReadOnlyCollection <LimitOrder> actualLimitOrders = null; var expectedLimitOrders = new[] { LimitOrder.CreateSell(6600.35m, 10), LimitOrder.CreateSell(6522.917m, 6), LimitOrder.CreateSell(6464, 1), LimitOrder.CreateBuy(6237, 1), LimitOrder.CreateBuy(6179.25m, 6), LimitOrder.CreateBuy(5935.05m, 10) }; _instruments.Add(new Instrument { AssetPairId = "BTCUSD", Mode = InstrumentMode.Active, Levels = new[] { new InstrumentLevel { Number = 1, Volume = 1, Markup = .01m }, new InstrumentLevel { Number = 2, Volume = 6, Markup = .03m }, new InstrumentLevel { Number = 3, Volume = 10, Markup = .05m } } }); _lykkeExchangeServiceMock .Setup(o => o.ApplyAsync(It.IsAny <string>(), It.IsAny <IReadOnlyCollection <LimitOrder> >())) .Returns((string assetPairId, IReadOnlyCollection <LimitOrder> limitOrders) => Task.CompletedTask) .Callback((string assetPairId, IReadOnlyCollection <LimitOrder> limitOrders) => actualLimitOrders = limitOrders); _orderBookServiceMock.Setup(o => o.UpdateAsync(It.IsAny <OrderBook>())) .Returns((OrderBook orderBook) => Task.CompletedTask); // act await _service.UpdateOrderBooksAsync(); // assert Assert.IsTrue(AreEqual(expectedLimitOrders, actualLimitOrders)); }
/// <summary> /// Creates limit orders using price levels of an instrument. /// </summary> /// <param name="quote">Best prices of instrument.</param> /// <param name="levels">A collection of price levels.</param> /// <param name="priceAccuracy">The accuracy of price.</param> /// <param name="volumeAccuracy">The accuracy of volume.</param> /// <returns>A collection of limit orders.</returns> public static IReadOnlyCollection <LimitOrder> CalculateLimitOrders(Quote quote, IReadOnlyCollection <InstrumentLevel> levels, int priceAccuracy, int volumeAccuracy) { var limitOrders = new List <LimitOrder>(); foreach (InstrumentLevel instrumentLevel in levels.OrderBy(o => o.Number)) { decimal sellPrice = (quote.Ask * (1 + instrumentLevel.Markup)) .TruncateDecimalPlaces(priceAccuracy, true); decimal buyPrice = (quote.Bid * (1 - instrumentLevel.Markup)) .TruncateDecimalPlaces(priceAccuracy); decimal volume = Math.Round(instrumentLevel.Volume, volumeAccuracy); limitOrders.Add(LimitOrder.CreateSell(sellPrice, volume)); limitOrders.Add(LimitOrder.CreateBuy(buyPrice, volume)); } return(limitOrders); }
private IReadOnlyCollection <LimitOrder> CreateLimitOrders(IndexSettings indexSettings, AssetPairSettings assetPairSettings, decimal sellPrice, decimal buyPrice) { var limitOrders = new List <LimitOrder>(); string walletId = _settingsService.GetWalletId(); decimal sellVolume = Math.Round(indexSettings.SellVolume / indexSettings.SellLimitOrdersCount, assetPairSettings.VolumeAccuracy); if (sellVolume >= assetPairSettings.MinVolume) { for (int i = 0; i < indexSettings.SellLimitOrdersCount; i++) { limitOrders.Add(LimitOrder.CreateSell(walletId, sellPrice, sellVolume)); } } else { limitOrders.Add(LimitOrder.CreateSell(walletId, sellPrice, Math.Round(indexSettings.SellVolume, assetPairSettings.VolumeAccuracy))); } decimal buyVolume = Math.Round(indexSettings.BuyVolume / indexSettings.BuyLimitOrdersCount, assetPairSettings.VolumeAccuracy); if (buyVolume >= assetPairSettings.MinVolume) { for (int i = 0; i < indexSettings.BuyLimitOrdersCount; i++) { limitOrders.Add(LimitOrder.CreateBuy(walletId, buyPrice, buyVolume)); } } else { limitOrders.Add(LimitOrder.CreateBuy(walletId, buyPrice, Math.Round(indexSettings.BuyVolume, assetPairSettings.VolumeAccuracy))); } return(limitOrders); }
/// <summary> /// Creates dynamically distributed limit orders using price levels of an instrument. /// </summary> /// <param name="quote1">Best prices of instrument on first level.</param> /// <param name="quote2">Best prices of instrument on second level.</param> /// <param name="levels">A collection of price levels.</param> /// <param name="baseAssetBalance">The amount of the base asset on a hedge exchange.</param> /// <param name="quoteAssetBalance">The amount of the quote asset on a hedge exchange.</param> /// <param name="timeSinceLastTrade">The total seconds from last trade in order book.</param> /// <param name="halfLifePeriod">The half life period in seconds.</param> /// <param name="allowSmartMarkup">If <c>true</c> then smart markup for first level will be applied; otherwise default markup.</param> /// <param name="globalMarkup">Global markup.</param> /// <param name="pnLStopLossMarkup">Total pnl stop loss markup.</param> /// <param name="fiatEquityStopLossMarkup">Fiat equity stop loss markup.</param> /// <param name="noFreshQuotesStopLossMarkup">No fresh quotes stop loss markup.</param> /// <param name="priceAccuracy">The accuracy of price.</param> /// <param name="volumeAccuracy">The accuracy of volume.</param> /// <returns>A collection of limit orders.</returns> public static IReadOnlyCollection <LimitOrder> CalculateLimitOrders( Quote quote1, Quote quote2, InstrumentLevel[] levels, decimal baseAssetBalance, decimal quoteAssetBalance, int timeSinceLastTrade, int halfLifePeriod, bool allowSmartMarkup, decimal globalMarkup, decimal pnLStopLossMarkup, decimal fiatEquityStopLossMarkup, decimal noFreshQuotesStopLossMarkup, int priceAccuracy, int volumeAccuracy) { var limitOrders = new List <LimitOrder>(); if (levels.Length == 0) { return(limitOrders); } decimal sumSideVolume = levels.Sum(o => o.Volume); decimal deltaVolume = sumSideVolume - levels[0].Volume; decimal sellDeltaPrice = (quote2.Ask - quote1.Ask) / quote1.Ask; decimal buyDeltaPrice = (quote1.Bid - quote2.Bid) / quote1.Bid; decimal sellMarketPrice = quote1.Ask; decimal sellRawPrice = quote1.Ask; decimal buyMarketPrice = quote1.Bid; decimal buyRawPrice = quote1.Bid; decimal sumVolume = levels[0].Volume; decimal sellFirstLevelMarkup = levels[0].Markup; decimal buyFirstLevelMarkup = levels[0].Markup; if (allowSmartMarkup) { double alpha = Math.Log(2) / halfLifePeriod; decimal relativeSpread = (quote1.Ask - quote1.Bid) / quote1.Mid; decimal smartMarkup = levels[0].Markup - relativeSpread / 2m + relativeSpread * (decimal)Math.Exp(-alpha * timeSinceLastTrade); if (-baseAssetBalance * quote1.Mid >= quoteAssetBalance) { sellFirstLevelMarkup = smartMarkup; } else { buyFirstLevelMarkup = smartMarkup; } } decimal commonMarkup = globalMarkup + pnLStopLossMarkup + noFreshQuotesStopLossMarkup; decimal sellFirstLevelPrice = (sellRawPrice * (1 + sellFirstLevelMarkup + commonMarkup + fiatEquityStopLossMarkup)) .TruncateDecimalPlaces(priceAccuracy, true); if (fiatEquityStopLossMarkup != decimal.MinusOne) // don't create sell orders if 'FiatEquityThresholdTo' exceeded { limitOrders.Add(LimitOrder.CreateSell(sellFirstLevelPrice, Math.Round(levels[0].Volume, volumeAccuracy))); } decimal buyFirstLevelPrice = (buyRawPrice * (1 - (buyFirstLevelMarkup + commonMarkup))) .TruncateDecimalPlaces(priceAccuracy); limitOrders.Add(LimitOrder.CreateBuy(buyFirstLevelPrice, Math.Round(levels[0].Volume, volumeAccuracy))); for (int i = 1; i < levels.Length; i++) { decimal prevSellMarketPrice = sellMarketPrice; decimal prevBuyMarketPrice = buyMarketPrice; decimal prevSumVolume = sumVolume; sumVolume += levels[i].Volume; sellMarketPrice = (1 + (sumVolume - levels[0].Volume) / deltaVolume * sellDeltaPrice) * quote1.Ask; buyMarketPrice = (1 - (sumVolume - levels[0].Volume) / deltaVolume * buyDeltaPrice) * quote1.Bid; sellRawPrice = (sellMarketPrice * sumVolume - prevSellMarketPrice * prevSumVolume) / levels[i].Volume; buyRawPrice = (buyMarketPrice * sumVolume - prevBuyMarketPrice * prevSumVolume) / levels[i].Volume; decimal buyPrice = (buyRawPrice * (1 - (levels[i].Markup + commonMarkup))) .TruncateDecimalPlaces(priceAccuracy); limitOrders.Add(LimitOrder.CreateBuy(Math.Min(buyFirstLevelPrice, buyPrice), Math.Round(levels[i].Volume, volumeAccuracy))); if (fiatEquityStopLossMarkup == decimal.MinusOne) // don't create sell orders if 'FiatEquityThresholdTo' exceeded { continue; } decimal sellPrice = (sellRawPrice * (1 + levels[i].Markup + commonMarkup + fiatEquityStopLossMarkup)) .TruncateDecimalPlaces(priceAccuracy, true); limitOrders.Add(LimitOrder.CreateSell(Math.Max(sellFirstLevelPrice, sellPrice), Math.Round(levels[i].Volume, volumeAccuracy))); } return(limitOrders); }