/// <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(); }
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) )); }
/// <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(); }
/// <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); } }
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)); }