Пример #1
0
        /// <summary>
        /// Cryptocurrency order book.
        /// Process order book data from one source per one target pair.
        /// </summary>
        /// <param name="targetPair">Select target pair</param>
        /// <param name="source">Provide order book data source</param>
        /// <param name="targetType">Select target precision (default: All - accepts every type of data)</param>
        public CryptoOrderBook(string targetPair, IOrderBookSource source, CryptoOrderBookType targetType = CryptoOrderBookType.All)
        {
            CryptoValidations.ValidateInput(targetPair, nameof(targetPair));
            CryptoValidations.ValidateInput(source, nameof(source));

            TargetPairOriginal = targetPair;
            TargetPair         = CryptoPairsHelper.Clean(targetPair);
            _source            = source;
            TargetType         = targetType;

            if (targetType == CryptoOrderBookType.L1 || targetType == CryptoOrderBookType.L2)
            {
                // save memory, only one order at price level
                _priceLevelInitialCapacity = 1;
            }
            else if (targetType == CryptoOrderBookType.L3)
            {
                // better performance, could be multiple orders at price level
                _priceLevelInitialCapacity = 5;
            }

            Subscribe();
            RestartAutoSnapshotReloading();
            RestartValidityChecking();
        }
Пример #2
0
        public void TargetPair_ShouldFilterAsExpected(string pair, int expectedCount)
        {
            var           source = GetSourceMock();
            ICryptoOrders orders = new CryptoOrders(source, null, pair);

            var updatedAll  = new List <CryptoOrder>();
            var updatedOurs = new List <CryptoOrder>();

            orders.OrderChangedStream.Subscribe(x => updatedAll.Add(x));
            orders.OurOrderChangedStream.Subscribe(x => updatedOurs.Add(x));
            var currentActive = orders.GetActiveOrders();

            for (int i = 0; i < 10; i++)
            {
                var clientId1 = orders.GenerateClientId().ToString();
                var clientId2 = orders.GenerateClientId().ToString();
                var side      = i % 2 == 0 ? CryptoOrderSide.Bid : CryptoOrderSide.Ask;

                source.StreamOrder(GetOrderMock(clientId1, "btcusd", side));
                source.StreamOrder(GetOrderMock(clientId2, "neobtc", side));
            }

            var currentAll  = orders.GetAllOrders();
            var currentOurs = orders.GetOrders();

            Assert.Equal(expectedCount, updatedOurs.Count);
            Assert.Equal(expectedCount, currentOurs.Count);

            Assert.Equal(expectedCount, updatedAll.Count);
            Assert.Equal(expectedCount, currentAll.Count);
            Assert.Empty(currentActive);

            Assert.Equal(orders.TargetPairOriginal, pair);
            Assert.Equal(orders.TargetPair, CryptoPairsHelper.Clean(pair));
        }
        public void StreamingSnapshot_DifferentPairs_ShouldHandleCorrectly()
        {
            var pair1    = "BTC/USD";
            var pair2    = "ETH/BTC";
            var data1    = GetOrderBookSnapshotMockData(pair1, 500);
            var data2    = GetOrderBookSnapshotMockData(pair2, 200);
            var data     = data2.Concat(data1).ToArray();
            var now      = CryptoDateUtils.ConvertFromUnixSeconds(1577575307.123451);
            var snapshot = new OrderBookLevelBulk(OrderBookAction.Insert, data, CryptoOrderBookType.L2)
            {
                ExchangeName    = "test",
                ServerSequence  = 3,
                ServerTimestamp = now
            };
            var source = new OrderBookSourceMock(snapshot);

            ICryptoOrderBook orderBook1 = new CryptoOrderBook(pair1, source);
            ICryptoOrderBook orderBook2 = new CryptoOrderBook(pair2, source);

            orderBook1.OrderBookUpdatedStream.Subscribe(x =>
            {
                Assert.Equal("test", x.ExchangeName);
                Assert.Equal(3, x.ServerSequence);
                Assert.Equal(now, x.ServerTimestamp);
            });

            orderBook2.OrderBookUpdatedStream.Subscribe(x =>
            {
                Assert.Equal("test", x.ExchangeName);
                Assert.Equal(3, x.ServerSequence);
                Assert.Equal(now, x.ServerTimestamp);
            });

            source.StreamSnapshot();

            Assert.Equal(500, orderBook1.BidLevels.Length);
            Assert.Equal(500, orderBook1.AskLevels.Length);

            Assert.Equal(200, orderBook2.BidLevels.Length);
            Assert.Equal(200, orderBook2.AskLevels.Length);

            Assert.Equal(499, orderBook1.BidLevels.First().Price);
            Assert.Equal(1499, orderBook1.BidLevels.First().Amount);

            Assert.Equal(199, orderBook2.BidLevels.First().Price);
            Assert.Equal(599, orderBook2.BidLevels.First().Amount);

            Assert.Equal(501, orderBook1.AskLevels.First().Price);
            Assert.Equal(2501, orderBook1.AskLevels.First().Amount);

            Assert.Equal(201, orderBook2.AskLevels.First().Price);
            Assert.Equal(1001, orderBook2.AskLevels.First().Amount);

            var levels = orderBook1.Levels;

            foreach (var level in levels)
            {
                Assert.Equal(CryptoPairsHelper.Clean(pair1), level.Pair);
            }
        }
        public void StreamingSnapshot_ShouldHandleCorrectly()
        {
            var pair     = "BTC/USD";
            var data     = GetOrderBookSnapshotMockData(pair, 500);
            var snapshot = new OrderBookLevelBulk(OrderBookAction.Insert, data, CryptoOrderBookType.L2);
            var source   = new OrderBookSourceMock(snapshot);

            var orderBook = new CryptoOrderBook(pair, source);

            source.StreamSnapshot();

            Assert.Equal(500, orderBook.BidLevels.Length);
            Assert.Equal(500, orderBook.AskLevels.Length);

            Assert.Equal(499, orderBook.BidPrice);
            Assert.Equal(1499, orderBook.BidLevels.First().Amount);

            Assert.Equal(501, orderBook.AskPrice);
            Assert.Equal(2501, orderBook.AskLevels.First().Amount);

            var levels = orderBook.Levels;

            foreach (var level in levels)
            {
                Assert.Equal(CryptoPairsHelper.Clean(pair), level.Pair);
            }
        }
 public static OrderBookLevel CreateLevel(string pair, double? price, CryptoOrderSide side, string key = null)
 {
     return new OrderBookLevel(
         key ?? CreateKey(price,side),
         side,
         null,
         null,
         null,
         pair == null ? null : CryptoPairsHelper.Clean(pair)
     );
 }
 public static OrderBookLevel CreateLevelById(string pair, double?price, double?amount, CryptoOrderSide side, int?count = 3, string key = null)
 {
     return(new OrderBookLevel(
                key ?? CreateKey(price, side),
                side,
                null,
                amount,
                count,
                pair == null ? null : CryptoPairsHelper.Clean(pair)
                ));
 }
Пример #7
0
        /// <summary>
        /// Orders manager
        /// </summary>
        /// <param name="source">Orders source</param>
        /// <param name="orderPrefix">Select prefix if you want to distinguish orders</param>
        /// <param name="targetPair">Select target pair, if you want to filter monitored orders</param>
        public CryptoOrders(IOrderSource source, long?orderPrefix = null, string targetPair = null)
        {
            CryptoValidations.ValidateInput(source, nameof(source));

            _source            = source;
            TargetPair         = CryptoPairsHelper.Clean(targetPair);
            TargetPairOriginal = targetPair;
            _source.SetExistingOrders(_idToOrder);
            _orderPrefix = orderPrefix;

            Subscribe();
        }
Пример #8
0
        /// <summary>
        /// Level constructor
        /// </summary>
        public OrderBookLevel(string id, CryptoOrderSide side, double?price, double?amount, int?count, string pair)
        {
            Id     = id;
            Side   = side;
            Price  = price;
            Count  = count;
            Amount = amount;
            Pair   = pair == null ? null : CryptoPairsHelper.Clean(pair);

            Price  = Price;
            Amount = Abs(Amount);
            Count  = Abs(Count);
        }
        /// <summary>
        /// Cryptocurrency order book.
        /// Process order book data from one source per one target pair.
        /// </summary>
        /// <param name="targetPair">Select target pair</param>
        /// <param name="source">Provide level 2 source data</param>
        public CryptoOrderBookL2(string targetPair, IOrderBookSource source)
        {
            CryptoValidations.ValidateInput(targetPair, nameof(targetPair));
            CryptoValidations.ValidateInput(source, nameof(source));

            TargetPairOriginal = targetPair;
            TargetPair         = CryptoPairsHelper.Clean(targetPair);
            _source            = source;

            Subscribe();
            RestartAutoSnapshotReloading();
            RestartValidityChecking();
        }
        private CryptoWallet ConvertWallet(WalletResponse response)
        {
            var id = response.Currency;

            var wallet = new CryptoWallet
            {
                Type             = "Exchange",
                Currency         = CryptoPairsHelper.Clean(response.Currency),
                Balance          = Math.Abs(response.Balance),
                BalanceAvailable = response.Available
            };

            _lastWallet = wallet;
            return(wallet);
        }
        private CryptoWallet ConvertWallet(Wallet response)
        {
            //var currency = response.Currency ?? "XBt";

            var wallet = new CryptoWallet
            {
                Currency = CryptoPairsHelper.Clean(response.Currency),
                Balance  = response.Balance,
                //?? _lastWallet?.Balance ?? 0,
                BalanceAvailable = response.BalanceAvailable ?? _lastWallet?.BalanceAvailable ?? 0,
                Type             = response.Type.ToString()
            };

            _lastWallet = wallet;
            return(wallet);
        }
        private CryptoOrder ConvertOrder(OrderResponse order)
        {
            var id              = order?.Id.ToString() ?? "00000";
            var clientId        = order?.ProfileId;
            var existingCurrent = ExistingOrders.ContainsKey(id) ? ExistingOrders[id] : null;
            var existingPartial = _partiallyFilledOrders.ContainsKey(id) ? _partiallyFilledOrders[id] : null;
            var existing        = existingPartial ?? existingCurrent;

            var price = Math.Abs(FirstNonZero(order?.Price, existing?.Price) ?? 0);

            var amount = Math.Abs(FirstNonZero(order?.Amount, existing?.AmountOrig) ?? 0);

            var amountOrig = Math.Abs(order?.Size ?? 0);

            var currentStatus = existing != null &&
                                existing.OrderStatus != CryptoOrderStatus.Undefined &&
                                existing.OrderStatus != CryptoOrderStatus.New &&
                                order?.OrderStatus == OrderStatus.Undefined
                ? existing.OrderStatus
                : ConvertOrderStatus(order);

            var newOrder = new CryptoOrder

            {
                Id    = id,
                Pair  = CryptoPairsHelper.Clean(order?.Pair),
                Price = price,
                //Amount = amount,
                AmountOrig  = amountOrig,
                Side        = ConvertSide(order.Side),
                OrderStatus = ConvertOrderStatus(order),
                Type        = (CryptoOrderType)order.OrderType,
                Created     = ConvertToDatetime(order.MtsCreate),
                ClientId    = clientId
                              //Created = Convert.ToDateTime(order.MtsCreate)
            };

            if (currentStatus == CryptoOrderStatus.PartiallyFilled)
            {
                // save partially filled orders
                _partiallyFilledOrders[newOrder.Id] = newOrder;
            }

            return(newOrder);
        }
        private CryptoOrder ConvertOrder(OrderResponse order)
        {
            var id              = order.Data.Id.ToString();
            var clientId        = order.Data.Id;
            var existingCurrent = ExistingOrders.ContainsKey(id) ? ExistingOrders[id] : null;
            var existingPartial = _partiallyFilledOrders.ContainsKey(id) ? _partiallyFilledOrders[id] : null;
            var existing        = existingPartial ?? existingCurrent;

            var price = Math.Abs(FirstNonZero(order.Data.Price, existing?.Price) ?? 0);

            var amount = Math.Abs(FirstNonZero(order.Data.Amount, existing?.AmountOrig) ?? 0);

            var amountOrig = Math.Abs(order.Data.Amount);

            var currentStatus = existing != null &&
                                existing.OrderStatus != CryptoOrderStatus.Undefined &&
                                existing.OrderStatus != CryptoOrderStatus.New
                                //&&
                                //order.OrderStatus == OrderStatus.Undefined
                ? existing.OrderStatus
                : ConvertOrderStatus(order);

            var newOrder = new CryptoOrder

            {
                Id    = id,
                Pair  = CryptoPairsHelper.Clean(order.Symbol),
                Price = price,
                //Amount = amount,
                AmountOrig  = amountOrig,
                Side        = ConvertSide(order.Data.Amount),
                OrderStatus = ConvertOrderStatus(order),
                Type        = (CryptoOrderType)order.Data.OrderType,
                //TODO
                //Created = ConvertToDatetime(DateTimeOffset.Parse(order.Data.MicroTimeStamp, out dt))
            };

            if (currentStatus == CryptoOrderStatus.PartiallyFilled)
            {
                // save partially filled orders
                _partiallyFilledOrders[newOrder.Id] = newOrder;
            }

            return(newOrder);
        }
        public async Task StreamingFromFile_ShouldHandleCorrectly()
        {
            var pair         = "XBTUSD";
            var communicator = new RawFileCommunicator();

            communicator.FileNames = _rawFiles;

            var client = new BitmexWebsocketClient(communicator);
            var source = new BitmexOrderBookSource(client);

            source.LoadSnapshotEnabled = false;
            source.BufferEnabled       = false;

            var orderBook = new CryptoOrderBook(pair, source);

            orderBook.SnapshotReloadEnabled = false;
            orderBook.ValidityCheckEnabled  = false;

            var receivedUpdate = 0;
            IOrderBookChangeInfo lastReceivedUpdate = null;

            var receivedTopLevel = 0;
            IOrderBookChangeInfo lastReceivedTopLevel = null;

            var receivedBidAskUpdate = 0;
            IOrderBookChangeInfo lastReceivedBidAskUpdate = null;


            orderBook.OrderBookUpdatedStream.Subscribe(x =>
            {
                receivedUpdate++;
                lastReceivedUpdate = x;
            });
            orderBook.TopLevelUpdatedStream.Subscribe(x =>
            {
                receivedTopLevel++;
                lastReceivedTopLevel = x;
            });
            orderBook.BidAskUpdatedStream.Subscribe(x =>
            {
                receivedBidAskUpdate++;
                lastReceivedBidAskUpdate = x;
            });


            await communicator.Start();

            Assert.Equal(3284, orderBook.BidLevels.Length);
            Assert.Equal(4035, orderBook.AskLevels.Length);

            Assert.Equal(6249, orderBook.BidPrice);
            Assert.Equal(163556, orderBook.BidLevels.First().Amount);

            Assert.Equal(6249.5, orderBook.AskPrice);
            Assert.Equal(270526, orderBook.AskLevels.First().Amount);

            Assert.Equal(322703, receivedUpdate);
            Assert.Equal(6249, lastReceivedUpdate?.Quotes?.Bid);
            Assert.Equal(6249.5, lastReceivedUpdate?.Quotes?.Ask);
            Assert.Equal("8799374800", lastReceivedUpdate.Sources[0].Levels[0].Id);
            Assert.Equal(6252, lastReceivedUpdate.Sources[0].Levels[0].Price);

            Assert.Equal(79020, receivedTopLevel);
            Assert.Equal(6249, lastReceivedTopLevel?.Quotes?.Bid);
            Assert.Equal(6249.5, lastReceivedTopLevel?.Quotes?.Ask);
            Assert.Equal("8799375100", lastReceivedTopLevel.Sources[0].Levels[0].Id);
            Assert.Equal(6249, lastReceivedTopLevel.Sources[0].Levels[0].Price);

            Assert.Equal(584, receivedBidAskUpdate);
            Assert.Equal(6249, lastReceivedBidAskUpdate?.Quotes?.Bid);
            Assert.Equal(6249.5, lastReceivedBidAskUpdate?.Quotes?.Ask);
            Assert.Equal("8799375050", lastReceivedBidAskUpdate.Sources[0].Levels[0].Id);
            Assert.Equal(6249.5, lastReceivedBidAskUpdate.Sources[0].Levels[0].Price);

            var levels = orderBook.Levels;

            foreach (var level in levels)
            {
                Assert.Equal(CryptoPairsHelper.Clean(pair), level.Pair);
            }
        }
Пример #15
0
        public void StreamingSnapshot_DifferentPairsSeparately_ShouldHandleCorrectly()
        {
            var pair1 = "BTC/USD";
            var pair2 = "ETH/BTC";
            var data1 = GetOrderBookSnapshotMockData(pair1, 500);
            var data2 = GetOrderBookSnapshotMockData(pair2, 200);
            var now   = CryptoDateUtils.ConvertFromUnixSeconds(1577575307.123451);

            var snapshot1 = new OrderBookLevelBulk(OrderBookAction.Insert, data1, CryptoOrderBookType.L2)
            {
                ExchangeName    = "test",
                ServerSequence  = 4,
                ServerTimestamp = now.AddMilliseconds(1)
            };
            var snapshot2 = new OrderBookLevelBulk(OrderBookAction.Insert, data2, CryptoOrderBookType.L2)
            {
                ExchangeName    = "test",
                ServerSequence  = 5,
                ServerTimestamp = now.AddMilliseconds(2)
            };

            var source = new OrderBookSourceMock();

            var orderBook1 = new CryptoOrderBookL2(pair1, source);
            var orderBook2 = new CryptoOrderBookL2(pair2, source);

            orderBook1.OrderBookUpdatedStream.Subscribe(x =>
            {
                Assert.Equal("test", x.ExchangeName);
                Assert.Equal(4, x.ServerSequence);
                Assert.Equal("1577575307.124451", x.ServerTimestamp.ToUnixSecondsString());
            });

            orderBook2.OrderBookUpdatedStream.Subscribe(x =>
            {
                Assert.Equal("test", x.ExchangeName);
                Assert.Equal(5, x.ServerSequence);
                Assert.Equal("1577575307.125451", x.ServerTimestamp.ToUnixSecondsString());
            });

            source.StreamSnapshotRaw(snapshot1);
            source.StreamSnapshotRaw(snapshot2);

            Assert.Equal(500, orderBook1.BidLevels.Length);
            Assert.Equal(500, orderBook1.AskLevels.Length);

            Assert.Equal(200, orderBook2.BidLevels.Length);
            Assert.Equal(200, orderBook2.AskLevels.Length);

            Assert.Equal(499, orderBook1.BidLevels.First().Price);
            Assert.Equal(1499, orderBook1.BidLevels.First().Amount);

            Assert.Equal(199, orderBook2.BidLevels.First().Price);
            Assert.Equal(599, orderBook2.BidLevels.First().Amount);

            Assert.Equal(501, orderBook1.AskLevels.First().Price);
            Assert.Equal(2501, orderBook1.AskLevels.First().Amount);

            Assert.Equal(201, orderBook2.AskLevels.First().Price);
            Assert.Equal(1001, orderBook2.AskLevels.First().Amount);

            var levels = orderBook1.Levels;

            foreach (var level in levels)
            {
                Assert.Equal(CryptoPairsHelper.Clean(pair1), level.Pair);
            }
        }
        private string AddSymbolToChannel()
        {
            var x = string.Join("_", GetChannelType(), CryptoPairsHelper.Clean(Pair));

            return(x);
        }
        public async Task StreamingDiff_TwoPairs_ShouldHandleCorrectly()
        {
            var pair1 = "BTC/USD";
            var pair2 = "ETH/USD";

            var data1    = GetOrderBookSnapshotMockData(pair1, 500);
            var data2    = GetOrderBookSnapshotMockData(pair2, 200);
            var data     = data2.Concat(data1).ToArray();
            var snapshot = new OrderBookLevelBulk(OrderBookAction.Insert, data, CryptoOrderBookType.L2);
            var source   = new OrderBookSourceMock(snapshot);

            ICryptoOrderBook orderBook1 = new CryptoOrderBook(pair1, source)
            {
                DebugEnabled = true
            };
            ICryptoOrderBook orderBook2 = new CryptoOrderBook(pair2, source)
            {
                DebugEnabled = true
            };

            source.StreamSnapshot();

            source.StreamBulk(GetInsertBulkL2(
                                  CreateLevel(pair2, 199.4, 50, CryptoOrderSide.Bid),
                                  CreateLevel(pair2, 198.3, 600, CryptoOrderSide.Bid),
                                  CreateLevel(pair2, 50.33, 3350, CryptoOrderSide.Bid),

                                  CreateLevel(pair1, 500.2, 400, CryptoOrderSide.Ask),
                                  CreateLevel(pair1, 503.1, 3000, CryptoOrderSide.Ask),
                                  CreateLevel(pair1, 800.123, 1234, CryptoOrderSide.Ask),

                                  CreateLevel(null, 101.1, null, CryptoOrderSide.Bid),
                                  CreateLevel(null, 901.1, null, CryptoOrderSide.Ask)
                                  ));

            source.StreamBulk(GetInsertBulkL2(
                                  CreateLevel(pair1, 499.4, 50, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 498.3, 600, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 300.33, 3350, CryptoOrderSide.Bid),

                                  CreateLevel(pair2, 200.2, 400, CryptoOrderSide.Ask),
                                  CreateLevel(pair2, 203.1, 3000, CryptoOrderSide.Ask),
                                  CreateLevel(pair2, 250.123, 1234, CryptoOrderSide.Ask)
                                  ));

            source.StreamBulk(GetUpdateBulkL2(
                                  CreateLevel(pair1, 499, 33, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 450, 33, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 501, 32, CryptoOrderSide.Ask),
                                  CreateLevel(pair1, 503.1, 32, CryptoOrderSide.Ask),

                                  CreateLevel(pair1, 100, null, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 900, null, CryptoOrderSide.Ask)
                                  ));

            source.StreamBulk(GetDeleteBulkL2(
                                  CreateLevel(pair1, 0, CryptoOrderSide.Bid),
                                  CreateLevel(pair1, 1, CryptoOrderSide.Bid),

                                  CreateLevel(pair2, 0, CryptoOrderSide.Bid),
                                  CreateLevel(pair2, 1, CryptoOrderSide.Bid)
                                  ));

            source.StreamBulk(GetDeleteBulkL2(
                                  CreateLevel(pair2, 400, CryptoOrderSide.Ask),
                                  CreateLevel(pair2, 399, CryptoOrderSide.Ask),

                                  CreateLevel(pair1, 1000, CryptoOrderSide.Ask),
                                  CreateLevel(pair1, 999, CryptoOrderSide.Ask)
                                  ));

            await Task.Delay(500);

            Assert.NotEmpty(orderBook1.BidLevels);
            Assert.NotEmpty(orderBook1.AskLevels);

            Assert.Equal(501, orderBook1.BidLevels.Length);
            Assert.Equal(501, orderBook1.AskLevels.Length);

            Assert.Equal(201, orderBook2.BidLevels.Length);
            Assert.Equal(201, orderBook2.AskLevels.Length);

            Assert.Equal(499.4, orderBook1.BidPrice);
            Assert.Equal(500.2, orderBook1.AskPrice);

            Assert.Equal(199.4, orderBook2.BidPrice);
            Assert.Equal(200.2, orderBook2.AskPrice);

            Assert.Equal(33, orderBook1.FindBidLevelByPrice(499)?.Amount);
            Assert.Equal(33, orderBook1.FindBidLevelByPrice(450)?.Amount);

            Assert.Equal(32, orderBook1.FindAskLevelByPrice(501)?.Amount);
            Assert.Equal(32, orderBook1.FindAskLevelByPrice(503.1)?.Amount);

            var notCompleteBid = orderBook1.FindBidLevelByPrice(100);

            Assert.Equal(CryptoPairsHelper.Clean(pair1), notCompleteBid.Pair);
            Assert.Equal(1100, notCompleteBid.Amount);
            Assert.Equal(3, notCompleteBid.Count);

            var notCompleteAsk = orderBook1.FindAskLevelByPrice(900);

            Assert.Equal(CryptoPairsHelper.Clean(pair1), notCompleteAsk.Pair);
            Assert.Equal(2900, notCompleteAsk.Amount);
            Assert.Equal(3, notCompleteAsk.Count);

            Assert.Null(orderBook1.FindBidLevelByPrice(0));
            Assert.Null(orderBook1.FindBidLevelByPrice(1));
            Assert.Null(orderBook1.FindAskLevelByPrice(1000));
            Assert.Null(orderBook1.FindAskLevelByPrice(999));

            Assert.Null(orderBook1.FindBidLevelByPrice(101.1));
            Assert.Null(orderBook1.FindAskLevelByPrice(901.1));

            Assert.Null(orderBook2.FindBidLevelByPrice(101.1));
            Assert.Null(orderBook2.FindAskLevelByPrice(901.1));
        }
        public async Task StreamingDiff_ShouldHandleCorrectly()
        {
            var pair     = "BTC/USD";
            var data     = GetOrderBookSnapshotMockData(pair, 500);
            var snapshot = new OrderBookLevelBulk(OrderBookAction.Insert, data, CryptoOrderBookType.L2);
            var source   = new OrderBookSourceMock(snapshot);

            ICryptoOrderBook orderBook = new CryptoOrderBook(pair, source);

            source.StreamSnapshot();

            source.StreamBulk(GetInsertBulkL2(
                                  CreateLevel(pair, 499.4, 50, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 498.3, 600, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 300.33, 3350, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 500.2, 400, CryptoOrderSide.Ask),
                                  CreateLevel(pair, 503.1, 3000, CryptoOrderSide.Ask),
                                  CreateLevel(pair, 800.123, 1234, CryptoOrderSide.Ask),

                                  CreateLevel(null, 101.1, null, CryptoOrderSide.Bid),
                                  CreateLevel(null, 901.1, null, CryptoOrderSide.Ask)
                                  ));

            source.StreamBulk(GetUpdateBulkL2(
                                  CreateLevel(pair, 499, 33, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 450, 33, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 501, 32, CryptoOrderSide.Ask),
                                  CreateLevel(pair, 503.1, 32, CryptoOrderSide.Ask),

                                  CreateLevel(pair, 100, null, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 900, null, CryptoOrderSide.Ask)
                                  ));

            source.StreamBulk(GetDeleteBulkL2(
                                  CreateLevel(pair, 0, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 1, CryptoOrderSide.Bid),
                                  CreateLevel(pair, 1000, CryptoOrderSide.Ask),
                                  CreateLevel(pair, 999, CryptoOrderSide.Ask)
                                  ));

            await Task.Delay(500);

            Assert.NotEmpty(orderBook.BidLevels);
            Assert.NotEmpty(orderBook.AskLevels);

            Assert.Equal(501, orderBook.BidLevels.Length);
            Assert.Equal(501, orderBook.AskLevels.Length);

            Assert.Equal(499.4, orderBook.BidPrice);
            Assert.Equal(500.2, orderBook.AskPrice);

            Assert.Equal(33, orderBook.FindBidLevelByPrice(499)?.Amount);
            Assert.Equal(33, orderBook.FindBidLevelByPrice(450)?.Amount);

            Assert.Equal(32, orderBook.FindAskLevelByPrice(501)?.Amount);
            Assert.Equal(32, orderBook.FindAskLevelByPrice(503.1)?.Amount);

            var notCompleteBid = orderBook.FindBidLevelByPrice(100);

            Assert.Equal(CryptoPairsHelper.Clean(pair), notCompleteBid.Pair);
            Assert.Equal(1100, notCompleteBid.Amount);
            Assert.Equal(3, notCompleteBid.Count);

            var notCompleteAsk = orderBook.FindAskLevelByPrice(900);

            Assert.Equal(CryptoPairsHelper.Clean(pair), notCompleteAsk.Pair);
            Assert.Equal(2900, notCompleteAsk.Amount);
            Assert.Equal(3, notCompleteAsk.Count);

            Assert.Null(orderBook.FindBidLevelByPrice(0));
            Assert.Null(orderBook.FindBidLevelByPrice(1));
            Assert.Null(orderBook.FindAskLevelByPrice(1000));
            Assert.Null(orderBook.FindAskLevelByPrice(999));

            Assert.Null(orderBook.FindBidLevelByPrice(101.1));
            Assert.Null(orderBook.FindAskLevelByPrice(901.1));
        }