public void TestBinanceOrderbookDeserializer()
        {
            //Arrange:
            //sample websocket message from Binance for an orderbook refresh:
            string sampleWebsocketMessage = "{\"u\":494630706,\"s\":\"XRPETH\",\"b\":\"0.00034050\",\"B\":\"131.00000000\",\"a\":\"0.00034350\",\"A\":\"1358.00000000\"}";
            //expected deserialized orderbook:
            var expectedOrderbook = new BinanceOrderbook();

            expectedOrderbook.Sequence = 494630706;
            expectedOrderbook.Symbol   = "XRPETH";

            expectedOrderbook.OfficialBids = new ConcurrentDictionary <decimal, decimal>();
            expectedOrderbook.OfficialBids.TryAdd(0.00034050m, 131m);

            expectedOrderbook.OfficialAsks = new ConcurrentDictionary <decimal, decimal>();
            expectedOrderbook.OfficialAsks.TryAdd(0.00034350m, 1358m);


            //Act: run the sample websocket message through the Huobi OrderbookConverter
            IOrderbook testOrderbook = (IOrderbook)JsonSerializer.Deserialize(sampleWebsocketMessage, typeof(BinanceOrderbook));

            //Assert: test that the actual outcome matches the expected outcome
            Assert.AreEqual(expectedOrderbook.Symbol, testOrderbook.Symbol);
            Assert.AreEqual(expectedOrderbook.Sequence, testOrderbook.Sequence);
            Assert.IsTrue(expectedOrderbook.OfficialAsks.Count == testOrderbook.OfficialAsks.Count &&
                          expectedOrderbook.OfficialBids.Count == testOrderbook.OfficialBids.Count);

            Assert.IsTrue(CheckLayers(expectedOrderbook, testOrderbook));
        }
Esempio n. 2
0
        public bool Merge(IOrderbook update)
        {
            if (this.Sequence < update.Sequence)
            {
                this.Sequence  = update.Sequence;
                this.Timestamp = update.Timestamp;

                if (OfficialAsks.Count() != 0 && OfficialBids.Count() != 0)
                {
                    PreviousLowestAsk  = OfficialAsks.Keys.Min();
                    PreviousHighestBid = OfficialBids.Keys.Max();
                }
                else
                {
                    PreviousLowestAsk  = 0;
                    PreviousHighestBid = decimal.MaxValue;
                }

                OfficialAsks = update.OfficialAsks;
                OfficialBids = update.OfficialBids;

                var significantChange = SignificantChange(update);
                Exchange.OrderbookUpdateQueue.Enqueue(significantChange);
                return(significantChange.Item1);
            }

            return(false);
        }
 public OrderManager()
 {
     this.Orderbook = new FullOrderbook();
     this.Orderbook.OnTradeUpdated      += WhenTradeUpdated;
     this.Orderbook.OnOrderbookUpdated  += WhenOrderbookUpdated;
     this.Orderbook.OnLimitOrderUpdated += WhenLimitOrderUpdated;
 }
Esempio n. 4
0
        public bool Merge(IOrderbook update)
        {
            if (this.Sequence < update.Sequence)
            {
                this.Sequence  = update.Sequence;
                this.Timestamp = update.Timestamp;

                if (OfficialAsks.Count() != 0 && OfficialBids.Count() != 0)
                {
                    PreviousLowestAsk  = OfficialAsks.Keys.Min();
                    PreviousHighestBid = OfficialBids.Keys.Max();
                }
                else
                {
                    PreviousLowestAsk  = 0;
                    PreviousHighestBid = decimal.MaxValue;
                }


                //Loop through update.asks and update.bids in parallel and either add them to this.asks and this.bids or update the value thats currently there.


                update.OfficialAsks.AsParallel().ForAll(UpdateAskLayer);
                update.OfficialBids.AsParallel().ForAll(UpdateBidLayer);

                var significantChange = SignificantChange(update);
                Exchange.OrderbookUpdateQueue.Enqueue(significantChange);
                return(significantChange.Item1);
            }

            return(false);
        }
Esempio n. 5
0
        public void Add_offer_queue()
        {
            var orderbook = new MarketData("btc", 8);

            {
                IOrderbook ob = orderbook as IOrderbook;
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "bitstamp", BidOfferEnum = BidOfferEnum.Offer, Price = 100, Quantity = 1, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "binance", BidOfferEnum = BidOfferEnum.Offer, Price = 99, Quantity = 2, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "gemini", BidOfferEnum = BidOfferEnum.Offer, Price = 100, Quantity = 90, Symbol = "btc"
                });
            }

            {
                ITestFunctions ob     = orderbook as ITestFunctions;
                var            offers = ob.GetOffers();
                Assert.AreEqual(true, offers.ContainsKey(99));
                Assert.AreEqual(true, offers.ContainsKey(100));
                Assert.AreEqual(true, offers[99].ContainsKey("binance"));
                Assert.AreEqual(2, offers[100].Values.Count);
                Assert.AreEqual(true, offers[100].ContainsKey("gemini"));
                Assert.AreEqual(true, offers[100].ContainsKey("bitstamp"));

                var bids = ob.GetBids();
                Assert.AreEqual(0, bids.Count);
            }
        }
Esempio n. 6
0
        public IOrderbook DeepCopy()
        {
            IOrderbook deepCopy = (IOrderbook)this.MemberwiseClone();

            deepCopy.OfficialAsks = new ConcurrentDictionary <decimal, decimal>(OfficialAsks);
            deepCopy.OfficialBids = new ConcurrentDictionary <decimal, decimal>(OfficialBids);
            return(deepCopy);
        }
Esempio n. 7
0
 public async Task UnSubscribe(IOrderbook market, IClientWebSocket client)
 {
     if (client.State == WebSocketState.Open)
     {
         await Client.SendAsync(
             new ArraySegment <byte>(Encoding.ASCII.GetBytes($"{{\"method\": \"unsubscribeOrderbook\",\"params\": {{ \"symbol\": \"{market.Symbol}\" }} }}")),
             WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
     }
 }
Esempio n. 8
0
 public async Task UnSubscribe(IOrderbook market, IClientWebSocket client)
 {
     if (client.State == WebSocketState.Open)
     {
         await Client.SendAsync(new ArraySegment <byte>(
                                    Encoding.ASCII.GetBytes($"{{\"sub\": \"market.{market.Symbol.ToLower()}.mbp.150\",\n  \"id\": \"id{ID}\"\n }}")
                                    ), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);
     }
 }
Esempio n. 9
0
        public void Change_bid_and_offer_queue()
        {
            var orderbook = new MarketData("btc", 8);

            {
                IOrderbook ob = orderbook as IOrderbook;
                //add:
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "bitstamp", BidOfferEnum = BidOfferEnum.Bid, Price = 100, Quantity = 1, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "binance", BidOfferEnum = BidOfferEnum.Bid, Price = 99, Quantity = 2, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "gemini", BidOfferEnum = BidOfferEnum.Bid, Price = 100, Quantity = 90, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "bitstamp", BidOfferEnum = BidOfferEnum.Offer, Price = 100, Quantity = 1, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "binance", BidOfferEnum = BidOfferEnum.Offer, Price = 99, Quantity = 2, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "gemini", BidOfferEnum = BidOfferEnum.Offer, Price = 100, Quantity = 90, Symbol = "btc"
                });

                //change qty from 90 to 3
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "gemini", BidOfferEnum = BidOfferEnum.Bid, Price = 100, Quantity = 3, Symbol = "btc"
                });
                ob.AddOrUpdateOrder(new Order {
                    Exchange = "gemini", BidOfferEnum = BidOfferEnum.Offer, Price = 100, Quantity = 3, Symbol = "btc"
                });
            }

            {
                ITestFunctions ob   = orderbook as ITestFunctions;
                var            bids = ob.GetBids();
                Assert.AreEqual(true, bids.ContainsKey(99));
                Assert.AreEqual(true, bids.ContainsKey(100));
                Assert.AreEqual(true, bids[99].ContainsKey("binance"));
                Assert.AreEqual(2, bids[100].Values.Count);
                Assert.AreEqual(true, bids[100].ContainsKey("gemini"));
                Assert.AreEqual(true, bids[100].ContainsKey("bitstamp"));
                Assert.AreEqual(4, bids[100].Values.Sum(x => x.Quantity)); //should be 4, but not 90.

                var offers = ob.GetOffers();
                Assert.AreEqual(true, offers.ContainsKey(99));
                Assert.AreEqual(true, offers.ContainsKey(100));
                Assert.AreEqual(true, offers[99].ContainsKey("binance"));
                Assert.AreEqual(2, offers[100].Values.Count);
                Assert.AreEqual(true, offers[100].ContainsKey("gemini"));
                Assert.AreEqual(true, offers[100].ContainsKey("bitstamp"));
                Assert.AreEqual(4, offers[100].Values.Sum(x => x.Quantity)); //should be 4, but not 90.
            }
        }
Esempio n. 10
0
 public Triangle(IOrderbook firstSymbolOrderbook, IOrderbook secondSymbolOrderbook, IOrderbook thirdSymbolOrderbook, Directions direction, IExchange exch)
 {
     FirstSymbolOrderbook  = firstSymbolOrderbook;
     SecondSymbolOrderbook = secondSymbolOrderbook;
     ThirdSymbolOrderbook  = thirdSymbolOrderbook;
     FirstSymbol           = firstSymbolOrderbook.Symbol;
     SecondSymbol          = secondSymbolOrderbook.Symbol;
     ThirdSymbol           = thirdSymbolOrderbook.Symbol;
     Direction             = direction;
     _logger  = new LoggerFactory().CreateLogger <Triangle>();
     Exchange = exch;
 }
Esempio n. 11
0
        public async Task SubscribeViaQueue(IOrderbook market)
        {
            market.OfficialAsks.Clear();
            market.OfficialBids.Clear();
            if (Client.State == WebSocketState.Open) //given the amount of time it takes to complete this for loop, a client could be aborted in process.
            {
                await Client.SendAsync(
                    new ArraySegment <byte>(Encoding.ASCII.GetBytes($"{{\"method\": \"subscribeOrderbook\",\"params\": {{ \"symbol\": \"{market.Symbol}\" }} }}")),
                    WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);

                Client.Markets.Add(market);
            }
        }
Esempio n. 12
0
        public async Task SubscribeViaQueue(IOrderbook market)
        {
            if (Client.State == WebSocketState.Open)
            {
                await Client.SendAsync(new ArraySegment <byte>(
                                           Encoding.ASCII.GetBytes($"{{\"method\": \"SUBSCRIBE\",\"params\": [\"{market.Symbol.ToLower()}@bookTicker\"], \"id\": {ID} }}")
                                           ), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);

                ID++;
                Client.Markets.Add(market);
            }
            await Task.Delay(250);
        }
Esempio n. 13
0
        public void TestHuobiOrderbookDeserializer()
        {
            //Arrange:
            //sample websocket message from Huobi for an orderbook refresh:
            string sampleWebsocketMessage = "{\"ch\":\"market.btcusdt.mbp.refresh.10\",\"ts\":1611504245560,\"tick\":{\"seqNum\":119437040567,\"bids\":[[32115.28,0.02],[32115.13,0.15],[32114.05,0.072382],[32113.94,0.066716],[32113.61,0.02],[32113.52,2.521393],[32113.48,0.117404],[32109.42,0.139009],[32108.76,0.123],[32108.25,0.11175]],\"asks\":[[32115.29,0.005],[32115.69,0.03],[32116.2,0.01759],[32118.17,0.03],[32118.86,0.03],[32119.61,0.12],[32120.41,0.08],[32121.51,0.125775],[32121.64,0.03563],[32124.4,0.001]]}}";
            //expected deserialized orderbook:
            var expectedOrderbook = new HuobiOrderbook();

            expectedOrderbook.Sequence = 119437040567;
            expectedOrderbook.Symbol   = "BTCUSDT";

            expectedOrderbook.OfficialBids = new ConcurrentDictionary <decimal, decimal>();
            expectedOrderbook.OfficialBids.TryAdd(32115.28m, 0.02m);
            expectedOrderbook.OfficialBids.TryAdd(32115.13m, 0.15m);
            expectedOrderbook.OfficialBids.TryAdd(32114.05m, 0.072382m);
            expectedOrderbook.OfficialBids.TryAdd(32113.94m, 0.066716m);
            expectedOrderbook.OfficialBids.TryAdd(32113.61m, 0.02m);
            expectedOrderbook.OfficialBids.TryAdd(32113.52m, 2.521393m);
            expectedOrderbook.OfficialBids.TryAdd(32113.48m, 0.117404m);
            expectedOrderbook.OfficialBids.TryAdd(32109.42m, 0.139009m);
            expectedOrderbook.OfficialBids.TryAdd(32108.76m, 0.123m);
            expectedOrderbook.OfficialBids.TryAdd(32108.25m, 0.11175m);

            expectedOrderbook.OfficialAsks = new ConcurrentDictionary <decimal, decimal>();
            expectedOrderbook.OfficialAsks.TryAdd(32115.29m, 0.005m);
            expectedOrderbook.OfficialAsks.TryAdd(32115.69m, 0.03m);
            expectedOrderbook.OfficialAsks.TryAdd(32116.2m, 0.01759m);
            expectedOrderbook.OfficialAsks.TryAdd(32118.17m, 0.03m);
            expectedOrderbook.OfficialAsks.TryAdd(32118.86m, 0.03m);
            expectedOrderbook.OfficialAsks.TryAdd(32119.61m, 0.12m);
            expectedOrderbook.OfficialAsks.TryAdd(32120.41m, 0.08m);
            expectedOrderbook.OfficialAsks.TryAdd(32121.51m, 0.125775m);
            expectedOrderbook.OfficialAsks.TryAdd(32121.64m, 0.03563m);
            expectedOrderbook.OfficialAsks.TryAdd(32124.4m, 0.001m);

            //Act: run the sample websocket message through the Huobi OrderbookConverter
            IOrderbook testOrderbook = (IOrderbook)JsonSerializer.Deserialize(sampleWebsocketMessage, typeof(HuobiOrderbook));

            //Assert: test that the actual outcome matches the expected outcome
            Assert.AreEqual(expectedOrderbook.Symbol, testOrderbook.Symbol);
            Assert.AreEqual(expectedOrderbook.Sequence, testOrderbook.Sequence);
            Assert.IsTrue(expectedOrderbook.OfficialAsks.Count == testOrderbook.OfficialAsks.Count &&
                          expectedOrderbook.OfficialBids.Count == testOrderbook.OfficialBids.Count);

            Assert.IsTrue(CheckLayers(expectedOrderbook, testOrderbook));
        }
Esempio n. 14
0
        public async Task SendPong(IOrderbook orderbook) //sends a 'pong' message back to the server if required to maintain connection. Only Huobi (so far) uses this methodology
        {
            if (Client.State == WebSocketState.Open)
            {
                try
                {
                    var cts = new CancellationToken();
                    await Client.SendAsync(new ArraySegment <byte>(Encoding.ASCII.GetBytes($"{{\"pong\": {orderbook.PongValue}}}")), WebSocketMessageType.Text, true, cts);

                    //_logger.LogDebug($"took {(DateTime.UtcNow - orderbook.Timestamp).TotalMilliseconds}ms to send pong");
                    orderbook.Pong = false; //set the flag back to false
                }
                catch (Exception ex)
                {
                    _logger.LogError($"problem sending pong: {ex.Message}");
                }
            }
        }
Esempio n. 15
0
        public async Task SubscribeViaQueue(IOrderbook market)
        {
            if (Client.State == WebSocketState.Open)
            {
                try
                {
                    await Client.SendAsync(new ArraySegment <byte>(
                                               Encoding.ASCII.GetBytes($"{{\"sub\": \"market.{market.Symbol.ToLower()}.mbp.refresh.10\",\n  \"id\": \"id{ID}\"\n }}")
                                               ), WebSocketMessageType.Text, true, CancellationToken.None).ConfigureAwait(false);

                    ID++;
                    Client.Markets.Add(market);
                } catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
            await Task.Delay(100);
        }
Esempio n. 16
0
        public bool CheckLayers(IOrderbook expected, IOrderbook actual)
        {
            bool layersMatch = true;

            foreach (var layer in expected.OfficialBids)
            {
                if (actual.OfficialBids.TryGetValue(layer.Key, out var testValue))
                {
                    if (layer.Value != testValue)
                    {
                        layersMatch = false;
                        Assert.Fail($"Expected value {layer.Value} was actually {testValue}");
                        return(layersMatch);
                    }
                }
                else
                {
                    layersMatch = false;
                    Assert.Fail($"Expected key {layer.Key} not present");
                    return(layersMatch);
                }
            }
            foreach (var layer in expected.OfficialAsks)
            {
                if (actual.OfficialAsks.TryGetValue(layer.Key, out var testValue))
                {
                    if (layer.Value != testValue)
                    {
                        layersMatch = false;
                        Assert.Fail($"Expected value {layer.Value} was actually {testValue}");
                        return(layersMatch);
                    }
                }
                else
                {
                    layersMatch = false;
                    Assert.Fail($"Expected key {layer.Key} not present");
                    return(layersMatch);
                }
            }
            return(layersMatch);
        }
Esempio n. 17
0
        public (bool, string) SignificantChange(IOrderbook updatedOrderbook)
        {
            if (Exchange.ProfitableSymbolMapping.TryGetValue(Symbol, out var lastProfitable))
            {
                if (DateTime.UtcNow - lastProfitable < TimeSpan.FromMinutes(1)) //if the symbol had a profitable triangle in the last minute
                {
                    return(true, "Symbol had a profitable triangle within the last minute");
                }
                else
                {
                    if (OfficialAsks.Keys.Min() < PreviousLowestAsk || OfficialBids.Keys.Max() > PreviousHighestBid)
                    {
                        return(true, "Price improved");
                    }
                    else //price got worse or did not change
                    {
                        return(false, "Price worsened");
                    }
                }
            }
            else //symbol is not mapped as profitable - update is only significant if the bottom bid/ask layers changed, and the price improved
            {
                if (OfficialAsks.Count < 1 || OfficialBids.Count < 1)
                {
                    return(false, "No OfficialAsks/Bids");
                }

                if (OfficialAsks.Keys.Min() < PreviousLowestAsk || OfficialBids.Keys.Max() > PreviousHighestBid) //if the lowest ask price got lower, or the highest bid got higher, this is a universally better price that will always improve profitability
                {
                    return(true, "Price improved");
                }
                else //price got worse or did not change
                {
                    return(false, "Price worsened");
                }
            }
        }
        public void Place_Single_New_Sell_Order()
        {
            orderbook = new FullOrderbook();

            var orderId   = "order1";
            var isBid     = false;
            var quantity  = 10;
            var price     = 100;
            var timestamp = DateTimeOffset.Now;
            var order     = new LimitOrder(
                orderId,
                isBid,
                quantity,
                price,
                timestamp);

            orderbook.PlaceOrder(order);

            Assert.IsNotNull(orderbook.AskLevels);
            Assert.IsNotNull(orderbook.BestAskLevel);
            Assert.AreEqual(quantity, orderbook.BestAskLevel.TotalLevelQuantity);
            Assert.AreEqual(price, orderbook.BestAskLevel.LimitPrice);
            Assert.AreEqual(isBid, orderbook.BestAskLevel.IsBid);
            Assert.AreEqual(1, orderbook.BestAskLevel.NumberOfOrders);
            Assert.IsNotNull(orderbook.BestAskLevel.Orders);
            Assert.IsNotNull(orderbook.BestAskLevel.Orders.List);
            Assert.AreEqual(1, orderbook.BestAskLevel.Orders.List.Count);
            Assert.AreEqual(orderId, orderbook.BestAskLevel.Orders.List.First.Value.OrderId);
            Assert.AreEqual(isBid, orderbook.BestAskLevel.Orders.List.First.Value.IsBid);
            Assert.AreEqual(quantity, orderbook.BestAskLevel.Orders.List.First.Value.Quantity);
            Assert.AreEqual(price, orderbook.BestAskLevel.Orders.List.First.Value.LimitPrice);
            Assert.AreEqual(timestamp, orderbook.BestAskLevel.Orders.List.First.Value.ReceivedTimestamp);
            Assert.IsNotNull(orderbook.BestAskLevel.Orders.Table);
            Assert.AreEqual(1, orderbook.BestAskLevel.Orders.Table.Count);
            Assert.AreSame(orderbook.BestAskLevel.Orders.Table[orderId],
                           orderbook.BestAskLevel.Orders.List.First);
        }
 public OrderbookEventArgs(IOrderbook orderbook)
 {
     Orderbook = orderbook;
 }
        public void Place_New_Sell_Order_And_Match_Exist_Buy_Orders()
        {
            orderbook = new FullOrderbook();

            var bid_orderId_1   = "bid_order_1";
            var bid_isBid_1     = true;
            var bid_quantity_1  = 5;
            var bid_price_1     = 23;
            var bid_timestamp_1 = DateTimeOffset.Now;
            var bid_order_1     = new LimitOrder(
                bid_orderId_1,
                bid_isBid_1,
                bid_quantity_1,
                bid_price_1,
                bid_timestamp_1);

            var bid_orderId_2   = "bid_order_2";
            var bid_isBid_2     = true;
            var bid_quantity_2  = 5;
            var bid_price_2     = 22;
            var bid_timestamp_2 = DateTimeOffset.Now;
            var bid_order_2     = new LimitOrder(
                bid_orderId_2,
                bid_isBid_2,
                bid_quantity_2,
                bid_price_2,
                bid_timestamp_2);

            var bid_orderId_3   = "bid_order_3";
            var bid_isBid_3     = true;
            var bid_quantity_3  = 5;
            var bid_price_3     = 21;
            var bid_timestamp_3 = DateTimeOffset.Now;
            var bid_order_3     = new LimitOrder(
                bid_orderId_3,
                bid_isBid_3,
                bid_quantity_3,
                bid_price_3,
                bid_timestamp_3);

            var bid_orderId_4   = "bid_order_4";
            var bid_isBid_4     = true;
            var bid_quantity_4  = 5;
            var bid_price_4     = 21;
            var bid_timestamp_4 = DateTimeOffset.Now;
            var bid_order_4     = new LimitOrder(
                bid_orderId_4,
                bid_isBid_4,
                bid_quantity_4,
                bid_price_4,
                bid_timestamp_4);

            var bid_orderId_5   = "bid_order_5";
            var bid_isBid_5     = true;
            var bid_quantity_5  = 5;
            var bid_price_5     = 21;
            var bid_timestamp_5 = DateTimeOffset.Now;
            var bid_order_5     = new LimitOrder(
                bid_orderId_5,
                bid_isBid_5,
                bid_quantity_5,
                bid_price_5,
                bid_timestamp_5);

            var ask_orderId   = "ask_order";
            var ask_isBid     = false;
            var ask_quantity  = 16;
            var ask_price     = 21;
            var ask_timestamp = DateTimeOffset.Now;
            var ask_order     = new LimitOrder(
                ask_orderId,
                ask_isBid,
                ask_quantity,
                ask_price,
                ask_timestamp);

            orderbook.PlaceOrder(bid_order_1);
            orderbook.PlaceOrder(bid_order_2);
            orderbook.PlaceOrder(bid_order_3);
            orderbook.PlaceOrder(bid_order_4);
            orderbook.PlaceOrder(bid_order_5);
            orderbook.PlaceOrder(ask_order);


            //<1>. Should have only one bid level at $21 left.
            //<2>. Two orders at this level, with Quantity 4 and 5.
            //<3>. TotalLevelQuantity at this level should be 9.
            Assert.IsNull(orderbook.BestAskLevel);

            //Assert <1>
            Assert.IsNotNull(orderbook.BestBidLevel);
            var bestBidLevels = orderbook.GetBestBidLevels(3);

            Assert.AreEqual(1, bestBidLevels.Count);
            Assert.AreEqual(21, orderbook.BestBidLevel.LimitPrice);

            //Assert <2>
            Assert.AreEqual(2, orderbook.BestBidLevel.NumberOfOrders);
            var firstBidOrderNode = orderbook.BestBidLevel.Orders.List.First;

            Assert.IsNotNull(firstBidOrderNode);
            Assert.AreEqual(4, firstBidOrderNode.Value.Quantity);
            var secondBidOrderNode = firstBidOrderNode.Next;

            Assert.IsNotNull(secondBidOrderNode);
            Assert.AreEqual(5, secondBidOrderNode.Value.Quantity);

            //Assert <3>
            Assert.AreEqual(9, orderbook.BestBidLevel.TotalLevelQuantity);
        }
        public void Place_Mutiple_Buy_Orders_With_Same_Price()
        {
            orderbook = new FullOrderbook();

            var limitPrice = 100;
            var isBid      = true;

            var orderId_1   = "order1";
            var isBid_1     = isBid;
            var quantity_1  = 10;
            var price_1     = limitPrice;
            var timestamp_1 = DateTimeOffset.Now;
            var order_1     = new LimitOrder(
                orderId_1,
                isBid_1,
                quantity_1,
                price_1,
                timestamp_1);

            var orderId_2   = "order2";
            var isBid_2     = isBid;
            var quantity_2  = 20;
            var price_2     = limitPrice;
            var timestamp_2 = DateTimeOffset.Now;
            var order_2     = new LimitOrder(
                orderId_2,
                isBid_2,
                quantity_2,
                price_2,
                timestamp_2);

            var orderId_3   = "order3";
            var isBid_3     = isBid;
            var quantity_3  = 30;
            var price_3     = limitPrice;
            var timestamp_3 = DateTimeOffset.Now;
            var order_3     = new LimitOrder(
                orderId_3,
                isBid_3,
                quantity_3,
                price_3,
                timestamp_3);

            orderbook.PlaceOrder(order_1);
            orderbook.PlaceOrder(order_2);
            orderbook.PlaceOrder(order_3);

            Assert.IsNotNull(orderbook.BidLevels);
            Assert.IsNotNull(orderbook.BestBidLevel);
            Assert.AreEqual(quantity_1 + quantity_2 + quantity_3, orderbook.BestBidLevel.TotalLevelQuantity);
            Assert.AreEqual(limitPrice, orderbook.BestBidLevel.LimitPrice);
            Assert.AreEqual(isBid, orderbook.BestBidLevel.IsBid);
            Assert.AreEqual(3, orderbook.BestBidLevel.NumberOfOrders);
            Assert.IsNotNull(orderbook.BestBidLevel.Orders);
            Assert.IsNotNull(orderbook.BestBidLevel.Orders.List);
            Assert.AreEqual(3, orderbook.BestBidLevel.Orders.List.Count);
            Assert.IsTrue(orderbook.BestBidLevel.Orders.List.Contains(order_1));
            Assert.IsTrue(orderbook.BestBidLevel.Orders.List.Contains(order_2));
            Assert.IsTrue(orderbook.BestBidLevel.Orders.List.Contains(order_3));
            Assert.AreEqual(3, orderbook.BestBidLevel.Orders.Table.Count);
        }
        public void Place_Mutiple_Sell_Orders_With_Different_Prices()
        {
            orderbook = new FullOrderbook();

            var isBid = false;

            var orderId_1   = "order1";
            var isBid_1     = isBid;
            var quantity_1  = 10;
            var price_1     = 100;
            var timestamp_1 = DateTimeOffset.Now;
            var order_1     = new LimitOrder(
                orderId_1,
                isBid_1,
                quantity_1,
                price_1,
                timestamp_1);

            var orderId_2   = "order2";
            var isBid_2     = isBid;
            var quantity_2  = 20;
            var price_2     = 200;
            var timestamp_2 = DateTimeOffset.Now;
            var order_2     = new LimitOrder(
                orderId_2,
                isBid_2,
                quantity_2,
                price_2,
                timestamp_2);

            var orderId_3   = "order3";
            var isBid_3     = isBid;
            var quantity_3  = 30;
            var price_3     = 300;
            var timestamp_3 = DateTimeOffset.Now;
            var order_3     = new LimitOrder(
                orderId_3,
                isBid_3,
                quantity_3,
                price_3,
                timestamp_3);

            List <LimitOrder> testOrders = new List <LimitOrder>()
            {
                order_1, order_2, order_3
            };

            orderbook.PlaceOrder(order_1);
            orderbook.PlaceOrder(order_2);
            orderbook.PlaceOrder(order_3);

            Assert.IsNotNull(orderbook.AskLevels);
            Assert.IsNotNull(orderbook.BestAskLevel);
            var bestAsksFromGetBestAskLevels = orderbook.GetBestAskLevels(3);

            Assert.IsNotNull(bestAsksFromGetBestAskLevels);
            Assert.AreEqual(3, bestAsksFromGetBestAskLevels.Count);
            var askLevelEnumerator = orderbook.AskLevels.GetEnumerator();

            for (int i = 0; i < 3; i++)
            {
                Assert.IsTrue(askLevelEnumerator.MoveNext());
                var askLevel  = askLevelEnumerator.Current;
                var testOrder = testOrders[i];

                Assert.AreEqual(testOrder.Quantity, askLevel.TotalLevelQuantity);
                Assert.AreEqual(testOrder.LimitPrice, askLevel.LimitPrice);
                Assert.AreEqual(1, askLevel.NumberOfOrders);
                Assert.IsNotNull(askLevel.Orders);
                Assert.IsNotNull(askLevel.Orders.List);
                Assert.AreEqual(1, askLevel.Orders.List.Count);
                Assert.AreEqual(testOrder.OrderId, askLevel.Orders.List.First.Value.OrderId);
                Assert.AreEqual(isBid, askLevel.Orders.List.First.Value.IsBid);
                Assert.AreEqual(testOrder.Quantity, askLevel.Orders.List.First.Value.Quantity);
                Assert.AreEqual(testOrder.LimitPrice, askLevel.Orders.List.First.Value.LimitPrice);
                Assert.AreEqual(testOrder.ReceivedTimestamp, askLevel.Orders.List.First.Value.ReceivedTimestamp);
                Assert.IsNotNull(askLevel.Orders.Table);
                Assert.AreEqual(1, askLevel.Orders.Table.Count);
                Assert.AreSame(askLevel.Orders.Table[testOrder.OrderId],
                               askLevel.Orders.List.First);
                Assert.AreSame(askLevel, bestAsksFromGetBestAskLevels[i]);
            }
        }
        public void Remove_Order_By_OrderId()
        {
            orderbook = new FullOrderbook();

            var bid_orderId_1   = "bid_order_1";
            var bid_isBid_1     = true;
            var bid_quantity_1  = 5;
            var bid_price_1     = 23;
            var bid_timestamp_1 = DateTimeOffset.Now;
            var bid_order_1     = new LimitOrder(
                bid_orderId_1,
                bid_isBid_1,
                bid_quantity_1,
                bid_price_1,
                bid_timestamp_1);

            var bid_orderId_2   = "bid_order_2";
            var bid_isBid_2     = true;
            var bid_quantity_2  = 5;
            var bid_price_2     = 22;
            var bid_timestamp_2 = DateTimeOffset.Now;
            var bid_order_2     = new LimitOrder(
                bid_orderId_2,
                bid_isBid_2,
                bid_quantity_2,
                bid_price_2,
                bid_timestamp_2);

            var bid_orderId_3   = "bid_order_3";
            var bid_isBid_3     = true;
            var bid_quantity_3  = 5;
            var bid_price_3     = 21;
            var bid_timestamp_3 = DateTimeOffset.Now;
            var bid_order_3     = new LimitOrder(
                bid_orderId_3,
                bid_isBid_3,
                bid_quantity_3,
                bid_price_3,
                bid_timestamp_3);

            var bid_orderId_4   = "bid_order_4";
            var bid_isBid_4     = true;
            var bid_quantity_4  = 5;
            var bid_price_4     = 21;
            var bid_timestamp_4 = DateTimeOffset.Now;
            var bid_order_4     = new LimitOrder(
                bid_orderId_4,
                bid_isBid_4,
                bid_quantity_4,
                bid_price_4,
                bid_timestamp_4);

            var bid_orderId_5   = "bid_order_5";
            var bid_isBid_5     = true;
            var bid_quantity_5  = 5;
            var bid_price_5     = 21;
            var bid_timestamp_5 = DateTimeOffset.Now;
            var bid_order_5     = new LimitOrder(
                bid_orderId_5,
                bid_isBid_5,
                bid_quantity_5,
                bid_price_5,
                bid_timestamp_5);

            orderbook.PlaceOrder(bid_order_1);
            orderbook.PlaceOrder(bid_order_2);
            orderbook.PlaceOrder(bid_order_3);
            orderbook.PlaceOrder(bid_order_4);
            orderbook.PlaceOrder(bid_order_5);
            var orderToRemove = new LimitOrder();

            orderToRemove.SetOrderId(bid_order_3.OrderId);
            orderbook.RemoveOrderByOrderId(orderToRemove);

            //<1>. Should have 3 level of Bids, $23, $22, $21
            //<2>. At level $21, there are two orders left, with Quantity 5 and 5.
            //<3>. TotalLevelQuantity at this level should be 10.

            //Assert <1>
            Assert.IsNotNull(orderbook.BestBidLevel);
            var bestBidLevels = orderbook.GetBestBidLevels(3);

            Assert.AreEqual(3, bestBidLevels.Count);
            Assert.AreEqual(23, bestBidLevels[0].LimitPrice);
            Assert.AreEqual(22, bestBidLevels[1].LimitPrice);
            Assert.AreEqual(21, bestBidLevels[2].LimitPrice);

            //Assert <2>
            var targetLevel = bestBidLevels[2];

            Assert.AreEqual(2, targetLevel.NumberOfOrders);
            var firstBidOrderNode = targetLevel.Orders.List.First;

            Assert.IsNotNull(firstBidOrderNode);
            Assert.AreEqual(5, firstBidOrderNode.Value.Quantity);
            var secondBidOrderNode = firstBidOrderNode.Next;

            Assert.IsNotNull(secondBidOrderNode);
            Assert.AreEqual(5, secondBidOrderNode.Value.Quantity);

            //Assert <3>
            Assert.AreEqual(10, targetLevel.TotalLevelQuantity);
        }
Esempio n. 24
0
        public static void MapHelper(string firstDirection, string secondDirection, string thirdDirection, IOrderbook firstMarket, IOrderbook secondMarket, IOrderbook thirdMarket, IExchange Exchange)
        {
            var direction   = (Triangle.Directions)Enum.Parse(typeof(Triangle.Directions), $"{firstDirection}{secondDirection}{thirdDirection}");
            var newTriangle = new Triangle(firstMarket, secondMarket, thirdMarket, direction, Exchange);

            //Console.WriteLine($"{exchangeName}: {firstDirection} {firstMarket.symbol}, {secondDirection} {secondMarket.symbol}, {thirdDirection} {thirdMarket.symbol}");
            UniqueTriangleCount++;
            TriarbEligibleMarkets.Add(firstMarket.Symbol);
            TriarbEligibleMarkets.Add(secondMarket.Symbol);
            TriarbEligibleMarkets.Add(thirdMarket.Symbol);
            Exchange.OfficialOrderbooks.TryAdd(firstMarket.Symbol, firstMarket);
            Exchange.OfficialOrderbooks.TryAdd(secondMarket.Symbol, secondMarket);
            Exchange.OfficialOrderbooks.TryAdd(thirdMarket.Symbol, thirdMarket);
            foreach (var marketName in new List <string> {
                firstMarket.Symbol, secondMarket.Symbol, thirdMarket.Symbol
            })
            {
                if (!Exchange.TriarbMarketMapping.Keys.Contains(marketName))
                {
                    Exchange.TriarbMarketMapping.AddOrUpdate(marketName, new List <Triangle>()
                    {
                        newTriangle
                    }, (key, triangleList) =>
                    {
                        if (key == marketName && !triangleList.Contains(newTriangle))
                        {
                            triangleList.Add(newTriangle);
                        }
                        return(triangleList);
                    });
                }
                else
                {
                    Exchange.TriarbMarketMapping.TryGetValue(marketName, out var triangles);
                    if (!triangles.Contains(newTriangle))
                    {
                        Exchange.TriarbMarketMapping.AddOrUpdate(marketName, new List <Triangle>()
                        {
                            newTriangle
                        }, (key, triangleList) =>
                        {
                            if (key == marketName && !triangleList.Contains(newTriangle))
                            {
                                triangleList.Add(newTriangle);
                            }
                            return(triangleList);
                        });
                    }
                }
            }
        }
        public void Update_Order_Price()
        {
            orderbook = new FullOrderbook();
            decimal newPrice = 22;

            var bid_orderId_1   = "bid_order_1";
            var bid_isBid_1     = true;
            var bid_quantity_1  = 5;
            var bid_price_1     = 23;
            var bid_timestamp_1 = DateTimeOffset.Now;
            var bid_order_1     = new LimitOrder(
                bid_orderId_1,
                bid_isBid_1,
                bid_quantity_1,
                bid_price_1,
                bid_timestamp_1);

            var bid_orderId_2   = "bid_order_2";
            var bid_isBid_2     = true;
            var bid_quantity_2  = 5;
            var bid_price_2     = 22;
            var bid_timestamp_2 = DateTimeOffset.Now;
            var bid_order_2     = new LimitOrder(
                bid_orderId_2,
                bid_isBid_2,
                bid_quantity_2,
                bid_price_2,
                bid_timestamp_2);

            var bid_orderId_3   = "bid_order_3";
            var bid_isBid_3     = true;
            var bid_quantity_3  = 4;
            var bid_price_3     = 21;
            var bid_timestamp_3 = DateTimeOffset.Now;
            var bid_order_3     = new LimitOrder(
                bid_orderId_3,
                bid_isBid_3,
                bid_quantity_3,
                bid_price_3,
                bid_timestamp_3);

            var bid_orderId_4   = "bid_order_4";
            var bid_isBid_4     = true;
            var bid_quantity_4  = 5;
            var bid_price_4     = 21;
            var bid_timestamp_4 = DateTimeOffset.Now;
            var bid_order_4     = new LimitOrder(
                bid_orderId_4,
                bid_isBid_4,
                bid_quantity_4,
                bid_price_4,
                bid_timestamp_4);

            var bid_orderId_5   = "bid_order_5";
            var bid_isBid_5     = true;
            var bid_quantity_5  = 3;
            var bid_price_5     = 21;
            var bid_timestamp_5 = DateTimeOffset.Now;
            var bid_order_5     = new LimitOrder(
                bid_orderId_5,
                bid_isBid_5,
                bid_quantity_5,
                bid_price_5,
                bid_timestamp_5);

            orderbook.PlaceOrder(bid_order_1);
            orderbook.PlaceOrder(bid_order_2);
            orderbook.PlaceOrder(bid_order_3);
            orderbook.PlaceOrder(bid_order_4);
            orderbook.PlaceOrder(bid_order_5);

            var updated_bid_order_3 = new LimitOrder(
                bid_order_3.OrderId,
                bid_order_3.IsBid,
                bid_order_3.Quantity,
                newPrice,
                DateTimeOffset.Now
                );

            orderbook.PlaceOrder(updated_bid_order_3);

            //<1>. Should have 3 level of Bids, $23, $22, $21
            //<2>. At level $22, there are two orders left, with Quantity 5 and 4.
            //<3>. At level $21, there is two order left, with Quantity 5 and 3.

            //Assert <1>
            Assert.IsNotNull(orderbook.BestBidLevel);
            var bestBidLevels = orderbook.GetBestBidLevels(3);

            Assert.AreEqual(3, bestBidLevels.Count);
            Assert.AreEqual(23, bestBidLevels[0].LimitPrice);
            Assert.AreEqual(22, bestBidLevels[1].LimitPrice);
            Assert.AreEqual(21, bestBidLevels[2].LimitPrice);

            //Assert <2>
            var targetLevel_22 = bestBidLevels[1];

            Assert.AreEqual(2, targetLevel_22.NumberOfOrders);
            var firstBidOrderNode_22 = targetLevel_22.Orders.List.First;

            Assert.IsNotNull(firstBidOrderNode_22);
            Assert.AreEqual(5, firstBidOrderNode_22.Value.Quantity);
            var secondBidOrderNode_22 = firstBidOrderNode_22.Next;

            Assert.IsNotNull(secondBidOrderNode_22);
            Assert.AreEqual(4, secondBidOrderNode_22.Value.Quantity);
            Assert.AreEqual(9, targetLevel_22.TotalLevelQuantity);

            //Assert <3>
            var targetLevel_21 = bestBidLevels[2];

            Assert.AreEqual(2, targetLevel_21.NumberOfOrders);
            var firstBidOrderNode_21 = targetLevel_21.Orders.List.First;

            Assert.IsNotNull(firstBidOrderNode_21);
            Assert.AreEqual(5, firstBidOrderNode_21.Value.Quantity);
            var secondBidOrderNode_21 = firstBidOrderNode_21.Next;

            Assert.IsNotNull(secondBidOrderNode_21);
            Assert.AreEqual(3, secondBidOrderNode_21.Value.Quantity);
            Assert.AreEqual(8, targetLevel_21.TotalLevelQuantity);
        }
        public void Place_New_Buy_Order_And_Match_Exist_Sell_Orders()
        {
            orderbook = new FullOrderbook();

            var ask_orderId_1   = "ask_order_1";
            var ask_isBid_1     = false;
            var ask_quantity_1  = 5;
            var ask_price_1     = 10;
            var ask_timestamp_1 = DateTimeOffset.Now;
            var ask_order_1     = new LimitOrder(
                ask_orderId_1,
                ask_isBid_1,
                ask_quantity_1,
                ask_price_1,
                ask_timestamp_1);

            var ask_orderId_2   = "ask_order_2";
            var ask_isBid_2     = false;
            var ask_quantity_2  = 5;
            var ask_price_2     = 11;
            var ask_timestamp_2 = DateTimeOffset.Now;
            var ask_order_2     = new LimitOrder(
                ask_orderId_2,
                ask_isBid_2,
                ask_quantity_2,
                ask_price_2,
                ask_timestamp_2);

            var ask_orderId_3   = "ask_order_3";
            var ask_isBid_3     = false;
            var ask_quantity_3  = 5;
            var ask_price_3     = 11;
            var ask_timestamp_3 = DateTimeOffset.Now;
            var ask_order_3     = new LimitOrder(
                ask_orderId_3,
                ask_isBid_3,
                ask_quantity_3,
                ask_price_3,
                ask_timestamp_3);

            var ask_orderId_4   = "ask_order_4";
            var ask_isBid_4     = false;
            var ask_quantity_4  = 5;
            var ask_price_4     = 12;
            var ask_timestamp_4 = DateTimeOffset.Now;
            var ask_order_4     = new LimitOrder(
                ask_orderId_4,
                ask_isBid_4,
                ask_quantity_4,
                ask_price_4,
                ask_timestamp_4);

            var bid_orderId   = "bid_order";
            var bid_isBid     = true;
            var bid_quantity  = 17;
            var bid_price     = 12;
            var bid_timestamp = DateTimeOffset.Now;
            var bid_order     = new LimitOrder(
                bid_orderId,
                bid_isBid,
                bid_quantity,
                bid_price,
                bid_timestamp);

            orderbook.PlaceOrder(ask_order_1);
            orderbook.PlaceOrder(ask_order_2);
            orderbook.PlaceOrder(ask_order_3);
            orderbook.PlaceOrder(ask_order_4);
            orderbook.PlaceOrder(bid_order);


            //<1>. Should have only one ask level at $12 left.
            //<2>. Only one order at this level.
            //<3>. TotalLevelQuantity at this level should be 3.
            Assert.IsNull(orderbook.BestBidLevel);

            //Assert <1>
            Assert.IsNotNull(orderbook.BestAskLevel);
            var bestAskLevels = orderbook.GetBestAskLevels(3);

            Assert.AreEqual(1, bestAskLevels.Count);
            Assert.AreEqual(12, orderbook.BestAskLevel.LimitPrice);

            //Assert <2>
            Assert.AreEqual(1, orderbook.BestAskLevel.NumberOfOrders);

            //Assert <3>
            Assert.AreEqual(3, orderbook.BestAskLevel.TotalLevelQuantity);
        }
Esempio n. 27
0
        private async Task BackgroundProcessing(CancellationToken stoppingToken)
        {
            while (Client.State == WebSocketState.Open && !stoppingToken.IsCancellationRequested)
            {
                var buffer = WebSocket.CreateClientBuffer(1024 * 64, 1024);

                using (var ms = new MemoryStream())
                {
                    string payload = string.Empty;
                    WebSocketReceiveResult result = null;

                    var receiptTask = Task.Run(() =>
                    {
                        result = Client.ReceiveAsync(ms, buffer, CancellationToken.None).Result;
                    });
                    bool successfulReceiveAsync = receiptTask.Wait(TimeSpan.FromMilliseconds(10000));
                    if (!successfulReceiveAsync)
                    {
                        _logger.LogError($"ReceiveAsync Timed Out");
                        continue;
                    }

                    if (result.MessageType == WebSocketMessageType.Text) //hitbtc, binance
                    {
                        var reader = new StreamReader(ms, Encoding.UTF8);
                        payload = await reader.ReadToEndAsync();
                    }
                    else if (result.MessageType == WebSocketMessageType.Binary) //huobi global sends all data in a compressed GZIP format
                    {
                        payload = ReadBinarySocketMessage(ms);
                    }
                    else if (result.MessageType == WebSocketMessageType.Close)
                    {
                        _logger.LogError($"{Exchange.ExchangeName}: Socket connection closed because {result.CloseStatusDescription}");
                        await HandleWebsocketClose();

                        continue;
                    }

                    IOrderbook orderbook = (IOrderbook)JsonSerializer.Deserialize(payload, OrderbookType);

                    if (orderbook.Pong == true)
                    {
                        SendPong(orderbook); //do not await this task; the listener should move on in reading other websocket messages
                        continue;
                    }

                    if (Exchange.OfficialOrderbooks.TryGetValue(orderbook.Symbol, out IOrderbook OfficialOrderbook))
                    {
                        bool shouldRecalculate = false;
                        lock (OfficialOrderbook.OrderbookLock) //lock the orderbook when the orderbook is being modified
                        {
                            shouldRecalculate = OfficialOrderbook.Merge(orderbook);
                        }

                        if (shouldRecalculate)
                        {
                            if (Exchange.TriarbMarketMapping.TryGetValue(orderbook.Symbol, out List <Triangle> impactedTriangles)) //get all of the impacted triangles
                            {
                                foreach (var impactedTriangle in impactedTriangles)
                                {
                                    var triangleSnapshot = CreateTriangleSnapshot(impactedTriangle);
                                    Exchange.TrianglesToRecalculate.Enqueue(triangleSnapshot);
                                }
                            }
                        }
                    }
                }
            }
            await HandleWebsocketClose();
        }
        public void Update_Order_Quantity()
        {
            orderbook = new FullOrderbook();
            decimal newQty = 10;

            var bid_orderId_1   = "bid_order_1";
            var bid_isBid_1     = true;
            var bid_quantity_1  = 5;
            var bid_price_1     = 23;
            var bid_timestamp_1 = DateTimeOffset.Now;
            var bid_order_1     = new LimitOrder(
                bid_orderId_1,
                bid_isBid_1,
                bid_quantity_1,
                bid_price_1,
                bid_timestamp_1);

            var bid_orderId_2   = "bid_order_2";
            var bid_isBid_2     = true;
            var bid_quantity_2  = 5;
            var bid_price_2     = 23;
            var bid_timestamp_2 = DateTimeOffset.Now;
            var bid_order_2     = new LimitOrder(
                bid_orderId_2,
                bid_isBid_2,
                bid_quantity_2,
                bid_price_2,
                bid_timestamp_2);

            var bid_orderId_3   = "bid_order_3";
            var bid_isBid_3     = true;
            var bid_quantity_3  = 5;
            var bid_price_3     = 23;
            var bid_timestamp_3 = DateTimeOffset.Now;
            var bid_order_3     = new LimitOrder(
                bid_orderId_3,
                bid_isBid_3,
                bid_quantity_3,
                bid_price_3,
                bid_timestamp_3);

            orderbook.PlaceOrder(bid_order_1);
            orderbook.PlaceOrder(bid_order_2);
            orderbook.PlaceOrder(bid_order_3);

            var updated_bid_order_2 = bid_order_2.DeepCopyWithQtyChanged(newQty);

            orderbook.PlaceOrder(updated_bid_order_2);

            //<1>. Should have only one bid level.
            //<2>. At this level, the Quantities are 5, 10, 5.

            //Assert <1>
            Assert.IsNotNull(orderbook.BestBidLevel);
            var bestBidLevels = orderbook.GetBestBidLevels(3);

            Assert.AreSame(orderbook.BestBidLevel, bestBidLevels[0]);
            Assert.AreEqual(1, bestBidLevels.Count);
            Assert.AreEqual(23, bestBidLevels[0].LimitPrice);

            //Assert <2>
            Assert.AreEqual(3, orderbook.BestBidLevel.NumberOfOrders);
            var firstBidOrderNode = orderbook.BestBidLevel.Orders.List.First;

            Assert.IsNotNull(firstBidOrderNode);
            Assert.AreEqual(5, firstBidOrderNode.Value.Quantity);
            var secondBidOrderNode = firstBidOrderNode.Next;

            Assert.IsNotNull(secondBidOrderNode);
            Assert.AreEqual(newQty, secondBidOrderNode.Value.Quantity);
            var thirdBidOrderNode = secondBidOrderNode.Next;

            Assert.IsNotNull(thirdBidOrderNode);
            Assert.AreEqual(5, thirdBidOrderNode.Value.Quantity);
            Assert.AreEqual(20, orderbook.BestBidLevel.TotalLevelQuantity);
        }