Exemple #1
0
        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)));
        }
Exemple #2
0
        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));
        }
Exemple #3
0
        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));
        }
Exemple #4
0
        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
        }