public Task <AggregatedOrderBookModel> GetAggregatedAsync(string assetPair) { AggregatedOrderBook orderBook = _aggregatedOrderBookService.GetByAssetPair(assetPair); if (orderBook == null) { throw new ValidationApiException(HttpStatusCode.NotFound, "Order book does not exist"); } return(Task.FromResult(Mapper.Map <AggregatedOrderBookModel>(orderBook))); }
private static bool AreEqual(AggregatedOrderBook a, AggregatedOrderBook b) { if (a == null && b == null) { return(true); } if (a == null || b == null) { return(false); } if (a.AssetPair != b.AssetPair) { return(false); } return(AreEqual(a.SellLevels, b.SellLevels) && AreEqual(a.BuyLevels, b.BuyLevels)); }
public async Task Create_Order_Book_From_Several_Exchanges() { // arrange _assetPairs.Add(new AssetPairModel { Name = "BTCUSD", PriceAccuracy = 3 }); _externalOrderBooks.AddRange(new[] { new ExternalOrderBook { Exchange = "Exchange1", AssetPair = "BTCUSD", Timestamp = DateTime.UtcNow, SellLevels = new List <ExternalOrderBookLevel> { new ExternalOrderBookLevel { Price = 1.8762345m, Volume = 1, OriginalPrice = 1.8762345m }, new ExternalOrderBookLevel { Price = 2.1341931m, Volume = 2, OriginalPrice = 2.1341931m }, new ExternalOrderBookLevel { Price = 3.9626635m, Volume = 3, OriginalPrice = 3.9626635m } }, BuyLevels = new List <ExternalOrderBookLevel> { new ExternalOrderBookLevel { Price = 1.5761689m, Volume = 1, OriginalPrice = 1.5761689m }, new ExternalOrderBookLevel { Price = 1.0341931m, Volume = 2, OriginalPrice = 1.0341931m }, new ExternalOrderBookLevel { Price = 0.5626635m, Volume = 3, OriginalPrice = 0.5626635m } } }, new ExternalOrderBook { Exchange = "Exchange2", AssetPair = "BTCUSD", Timestamp = DateTime.UtcNow, SellLevels = new List <ExternalOrderBookLevel> { new ExternalOrderBookLevel { Price = 1.877m, Volume = 1, OriginalPrice = 1.877m }, new ExternalOrderBookLevel { Price = 2.136m, Volume = 2, OriginalPrice = 2.136m }, new ExternalOrderBookLevel { Price = 3.963m, Volume = 3, OriginalPrice = 3.963m } }, BuyLevels = new List <ExternalOrderBookLevel> { new ExternalOrderBookLevel { Price = 1.576m, Volume = 1, OriginalPrice = 1.576m }, new ExternalOrderBookLevel { Price = 1.035m, Volume = 2, OriginalPrice = 1.035m }, new ExternalOrderBookLevel { Price = 0.562m, Volume = 3, OriginalPrice = 0.562m } } } }); var expectedAggregatedOrderBook = new AggregatedOrderBook { AssetPair = "BTCUSD", Timestamp = DateTime.UtcNow, SellLevels = new List <AggregatedOrderBookLevel> { new AggregatedOrderBookLevel { Price = 1.877m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 1.8762345m, Volume = 1 }, new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 1.877m, Volume = 1 } } }, new AggregatedOrderBookLevel { Price = 2.135m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 2.1341931m, Volume = 2 } } }, new AggregatedOrderBookLevel { Price = 2.136m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 2.136m, Volume = 2 } } }, new AggregatedOrderBookLevel { Price = 3.963m, Volume = 6, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 3.9626635m, Volume = 3 }, new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 3.963m, Volume = 3 } } } }, BuyLevels = new List <AggregatedOrderBookLevel> { new AggregatedOrderBookLevel { Price = 1.576m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 1.5761689m, Volume = 1 }, new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 1.576m, Volume = 1 } } }, new AggregatedOrderBookLevel { Price = 1.034m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 1.0341931m, Volume = 2 } } }, new AggregatedOrderBookLevel { Price = 1.035m, Volume = 2, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 1.035m, Volume = 2 } } }, new AggregatedOrderBookLevel { Price = 0.562m, Volume = 6, ExchangeVolumes = new List <AggregatedOrderBookVolume> { new AggregatedOrderBookVolume { Exchange = "Exchange1", Price = 0.5626635m, Volume = 3 }, new AggregatedOrderBookVolume { Exchange = "Exchange2", Price = 0.562m, Volume = 3 } } } } }; // act await _service.UpdateAsync("BTCUSD"); var actualAggregatedOrderBook = _service.GetByAssetPair("BTCUSD"); // assert Assert.IsTrue(AreEqual(expectedAggregatedOrderBook, actualAggregatedOrderBook)); }
public Task UpdateAsync(string assetPair) { string exchangeName = _settingsService.GetExchangeName(); AssetPairModel assetPairSettings = _marketInstrumentService.GetAssetPair(assetPair, exchangeName); if (assetPairSettings == null) { throw new FailedOperationException("Asset pair not found"); } IReadOnlyList <ExternalOrderBook> externalOrderBooks = _externalOrderBookService.GetByAssetPair(assetPair); var sellLevels = new Dictionary <decimal, Dictionary <string, ExternalOrderBookLevel> >(); foreach (ExternalOrderBook externalOrderBook in externalOrderBooks) { foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.SellLevels) { decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy, true); if (!sellLevels.ContainsKey(price)) { sellLevels[price] = new Dictionary <string, ExternalOrderBookLevel>(); } sellLevels[price][externalOrderBook.Exchange] = priceLevel; } } var buyLevels = new Dictionary <decimal, Dictionary <string, ExternalOrderBookLevel> >(); foreach (ExternalOrderBook externalOrderBook in externalOrderBooks) { foreach (ExternalOrderBookLevel priceLevel in externalOrderBook.BuyLevels) { decimal price = priceLevel.Price.TruncateDecimalPlaces(assetPairSettings.PriceAccuracy); if (!buyLevels.ContainsKey(price)) { buyLevels[price] = new Dictionary <string, ExternalOrderBookLevel>(); } buyLevels[price][externalOrderBook.Exchange] = priceLevel; } } var aggregatedOrderBook = new AggregatedOrderBook { AssetPair = assetPair, Timestamp = DateTime.UtcNow, SellLevels = sellLevels .Select(priceLevel => new AggregatedOrderBookLevel { Price = priceLevel.Key, Volume = priceLevel.Value.Sum(exchangeVolume => exchangeVolume.Value.Volume), ExchangeVolumes = priceLevel.Value .Select(o => new AggregatedOrderBookVolume(o.Key, o.Value.OriginalPrice, o.Value.Volume)) .ToList() }) .OrderBy(o => o.Price) .ToList(), BuyLevels = buyLevels .Select(priceLevel => new AggregatedOrderBookLevel { Price = priceLevel.Key, Volume = priceLevel.Value.Sum(exchangeVolume => exchangeVolume.Value.Volume), ExchangeVolumes = priceLevel.Value .Select(o => new AggregatedOrderBookVolume(o.Key, o.Value.OriginalPrice, o.Value.Volume)) .ToList() }) .OrderByDescending(o => o.Price) .ToList() }; _orderBooks.AddOrUpdate(assetPair, aggregatedOrderBook, (key, value) => aggregatedOrderBook); return(Task.CompletedTask); }
private async Task ExecuteMarketOrderAsync(MarketOrder marketOrder) { AggregatedOrderBook aggregatedOrderBook = _aggregatedOrderBookService.GetByAssetPair(marketOrder.AssetPair); if (aggregatedOrderBook == null) { _log.WarningWithDetails("Can not execute market order the aggregated order book does not exist", marketOrder); return; } IReadOnlyList <ExternalLimitOrder> externalLimitOrders = await _externalLimitOrderService.GetByParentIdAsync(marketOrder.Id); List <ExternalLimitOrder> activeLimitOrders = externalLimitOrders .Where(o => o.Status == ExternalLimitOrderStatus.Active) .ToList(); decimal executedVolume = externalLimitOrders .Where(o => o.Status == ExternalLimitOrderStatus.Filled || o.Status == ExternalLimitOrderStatus.PartiallyFilled) .Sum(o => o.Volume); decimal remainingVolume = marketOrder.Volume - executedVolume; bool rerouteFailedLimitOrders; var excludedExchanges = new List <string>(); do { rerouteFailedLimitOrders = false; IReadOnlyList <ExchangeVolume> volumes; if (marketOrder.Type == OrderType.Sell) { volumes = aggregatedOrderBook.GetBuyVolumes(remainingVolume, activeLimitOrders, excludedExchanges); } else { volumes = aggregatedOrderBook.GetSellVolumes(remainingVolume, activeLimitOrders, excludedExchanges); } if (volumes.Count == 0) { _log.WarningWithDetails("Can not execute market order no liquidity in aggregated order book", new { MarketOrder = marketOrder, RemainingVolume = remainingVolume, ActiveVolume = activeLimitOrders.Sum(o => o.Volume) }); break; } foreach (ExchangeVolume exchangeVolume in volumes) { ExchangeSettings exchangeSettings = await _exchangeSettingsService.GetByNameAsync(exchangeVolume.Exchange); decimal price; if (marketOrder.Type == OrderType.Sell) { price = exchangeVolume.Price * (1 - exchangeSettings.SlippageMarkup); } else { price = exchangeVolume.Price * (1 + exchangeSettings.SlippageMarkup); } try { ExternalLimitOrder externalLimitOrder = await _exchangeService.CreateLimitOrderAsync(exchangeVolume.Exchange, marketOrder.AssetPair, price, exchangeVolume.Volume, marketOrder.Type); _log.InfoWithDetails("External limit order created", externalLimitOrder); await _externalLimitOrderService.AddAsync(marketOrder.Id, externalLimitOrder); activeLimitOrders.Add(externalLimitOrder); } catch (Exception exception) { rerouteFailedLimitOrders = true; excludedExchanges.Add(exchangeSettings.Name); _log.ErrorWithDetails(exception, "An error occurred while creating external limit order", new { MarketOrderId = marketOrder.Id, exchangeVolume.Exchange, marketOrder.AssetPair, Price = price, exchangeVolume.Volume, OrderType = marketOrder.Type.ToString() }); } } } while (rerouteFailedLimitOrders); }
public void TestAggregatedOrderBook() { #region Check Union of sorted sets is still sorted var o11 = new ExchangeOrder(1, 1, OrderSide.Buy, OrderType.Limit, 10, 5); var o12 = new ExchangeOrder(2, 1, OrderSide.Buy, OrderType.Limit, 9, 4); var o13 = new ExchangeOrder(3, 1, OrderSide.Sell, OrderType.Limit, 12, 6); var o14 = new ExchangeOrder(4, 1, OrderSide.Sell, OrderType.Limit, 11, 7); var orderBook1 = new MockOrderBook(1).PlaceOrder(o11) .PlaceOrder(o12) .PlaceOrder(o13) .PlaceOrder(o14); var o21 = new ExchangeOrder(5, 2, OrderSide.Buy, OrderType.Limit, 10.5m, 5.5m); var o22 = new ExchangeOrder(6, 2, OrderSide.Buy, OrderType.Limit, 9.5m, 4.5m); var o23 = new ExchangeOrder(7, 2, OrderSide.Sell, OrderType.Limit, 12.5m, 5.5m); var o24 = new ExchangeOrder(8, 2, OrderSide.Sell, OrderType.Limit, 11.5m, 6.5m); var orderBook2 = new MockOrderBook(2).PlaceOrder(o21) .PlaceOrder(o22) .PlaceOrder(o23) .PlaceOrder(o24); var aggregatedOrderBook = new AggregatedOrderBook().InsertBook(orderBook1) .InsertBook(orderBook2); Assert.AreEqual(5, aggregatedOrderBook.Bids[0].ID); Assert.AreEqual(1, aggregatedOrderBook.Bids[1].ID); Assert.AreEqual(6, aggregatedOrderBook.Bids[2].ID); Assert.AreEqual(2, aggregatedOrderBook.Bids[3].ID); Assert.AreEqual(4, aggregatedOrderBook.Asks[0].ID); Assert.AreEqual(8, aggregatedOrderBook.Asks[1].ID); Assert.AreEqual(3, aggregatedOrderBook.Asks[2].ID); Assert.AreEqual(7, aggregatedOrderBook.Asks[3].ID); #endregion #region Check replacing an order book var o222 = o22.WithUpdatedRemainingVolume(1); var o232 = o23.WithUpdatedRemainingVolume(0.5m); var o242 = o24.WithUpdatedRemainingVolume(0.2m); var o25 = new ExchangeOrder(9, 2, OrderSide.Sell, OrderType.Limit, 13, 10); var orderBook22 = new MockOrderBook(2).PlaceOrder(o222) .PlaceOrder(o232) .PlaceOrder(o242) .PlaceOrder(o25); var aggregatedOrderBook2 = aggregatedOrderBook.InsertBook(orderBook22); Assert.AreEqual(1, aggregatedOrderBook2.Bids[0].ID); Assert.AreEqual(6, aggregatedOrderBook2.Bids[1].ID); Assert.AreEqual(3.5m, aggregatedOrderBook2.Bids[1].RemainingVolume); Assert.AreEqual(2, aggregatedOrderBook2.Bids[2].ID); Assert.AreEqual(4, aggregatedOrderBook2.Asks[0].ID); Assert.AreEqual(8, aggregatedOrderBook2.Asks[1].ID); Assert.AreEqual(6.3m, aggregatedOrderBook2.Asks[1].RemainingVolume); Assert.AreEqual(3, aggregatedOrderBook2.Asks[2].ID); Assert.AreEqual(7, aggregatedOrderBook2.Asks[3].ID); Assert.AreEqual(5, aggregatedOrderBook2.Asks[3].RemainingVolume); Assert.AreEqual(9, aggregatedOrderBook2.Asks[4].ID); #endregion #region Check arbitrage scanning var o31 = new ExchangeOrder(1, 3, OrderSide.Buy, OrderType.Limit, 12, 5); var o32 = new ExchangeOrder(2, 3, OrderSide.Buy, OrderType.Limit, 9, 7); var o33 = new ExchangeOrder(3, 3, OrderSide.Sell, OrderType.Limit, 13, 4); var o34 = new ExchangeOrder(4, 3, OrderSide.Sell, OrderType.Limit, 14, 6); var orderBook3 = new MockOrderBook(3).PlaceOrder(o31) .PlaceOrder(o32) .PlaceOrder(o33) .PlaceOrder(o34); var o41 = new ExchangeOrder(5, 4, OrderSide.Buy, OrderType.Limit, 10.5m, 2); var o42 = new ExchangeOrder(6, 4, OrderSide.Buy, OrderType.Limit, 8, 6); var o43 = new ExchangeOrder(7, 4, OrderSide.Sell, OrderType.Limit, 11, 8); var o44 = new ExchangeOrder(8, 4, OrderSide.Sell, OrderType.Limit, 13.5m, 2); var orderBook4 = new MockOrderBook(4).PlaceOrder(o41) .PlaceOrder(o42) .PlaceOrder(o43) .PlaceOrder(o44); var aggregatedOrderBook3 = new AggregatedOrderBook().InsertBook(orderBook3) .InsertBook(orderBook4); var arbitrage = aggregatedOrderBook3.LookForArbitrage(); Assert.AreEqual(5, arbitrage.BuyDico[4]); Assert.AreEqual(5, arbitrage.SellDico[3]); #endregion #region Check arbitrage scanning 2 var o51 = new ExchangeOrder(1, 5, OrderSide.Buy, OrderType.Limit, 13, 5); var o52 = new ExchangeOrder(2, 5, OrderSide.Buy, OrderType.Limit, 12.8m, 2); var o53 = new ExchangeOrder(3, 5, OrderSide.Buy, OrderType.Limit, 12.7m, 6); var o54 = new ExchangeOrder(4, 5, OrderSide.Buy, OrderType.Limit, 12, 4); var orderBook5 = new MockOrderBook(5).PlaceOrder(o51) .PlaceOrder(o52) .PlaceOrder(o53) .PlaceOrder(o54); var o61 = new ExchangeOrder(1, 6, OrderSide.Sell, OrderType.Limit, 12.5m, 10); var o62 = new ExchangeOrder(2, 6, OrderSide.Sell, OrderType.Limit, 13, 4); var orderBook6 = new MockOrderBook(6).PlaceOrder(o61) .PlaceOrder(o62); var o71 = new ExchangeOrder(1, 7, OrderSide.Sell, OrderType.Limit, 12, 7); var o72 = new ExchangeOrder(2, 7, OrderSide.Sell, OrderType.Limit, 12.7m, 5); var orderBook7 = new MockOrderBook(7).PlaceOrder(o71) .PlaceOrder(o72); var aggregatedOrderBook4 = new AggregatedOrderBook().InsertBook(orderBook5) .InsertBook(orderBook6) .InsertBook(orderBook7); var arbitrage2 = aggregatedOrderBook4.LookForArbitrage(); Assert.AreEqual(7, arbitrage2.BuyDico[7]); Assert.AreEqual(6, arbitrage2.BuyDico[6]); Assert.AreEqual(13, arbitrage2.SellDico[5]); #endregion }