diff --git a/CryptoExchange.Net.UnitTests/SymbolOrderBookTests.cs b/CryptoExchange.Net.UnitTests/SymbolOrderBookTests.cs new file mode 100644 index 0000000..59917c6 --- /dev/null +++ b/CryptoExchange.Net.UnitTests/SymbolOrderBookTests.cs @@ -0,0 +1,69 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.OrderBook; +using CryptoExchange.Net.Sockets; +using CryptoExchange.Net.UnitTests.TestImplementations; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace CryptoExchange.Net.UnitTests +{ + [TestFixture] + public class SymbolOrderBookTests + { + private static OrderBookOptions defaultOrderBookOptions = new OrderBookOptions("Test", true); + + private class TestableSymbolOrderBook : SymbolOrderBook + { + public TestableSymbolOrderBook() : base("BTC/USD", defaultOrderBookOptions) + { + } + + public override void Dispose() {} + + protected override Task> DoResync() + { + throw new NotImplementedException(); + } + + protected override Task> DoStart() + { + throw new NotImplementedException(); + } + } + + [TestCase] + public void GivenEmptyBidList_WhenBestBid_ThenEmptySymbolOrderBookEntry() + { + var symbolOrderBook = new TestableSymbolOrderBook(); + Assert.IsNotNull(symbolOrderBook.BestBid); + Assert.AreEqual(0m, symbolOrderBook.BestBid.Price); + Assert.AreEqual(0m, symbolOrderBook.BestAsk.Quantity); + } + + [TestCase] + public void GivenEmptyAskList_WhenBestAsk_ThenEmptySymbolOrderBookEntry() + { + var symbolOrderBook = new TestableSymbolOrderBook(); + Assert.IsNotNull(symbolOrderBook.BestBid); + Assert.AreEqual(0m, symbolOrderBook.BestBid.Price); + Assert.AreEqual(0m, symbolOrderBook.BestAsk.Quantity); + } + + [TestCase] + public void GivenEmptyBidAndAskList_WhenBestOffers_ThenEmptySymbolOrderBookEntries() + { + var symbolOrderBook = new TestableSymbolOrderBook(); + Assert.IsNotNull(symbolOrderBook.BestOffers); + Assert.IsNotNull(symbolOrderBook.BestOffers.Bid); + Assert.IsNotNull(symbolOrderBook.BestOffers.Ask); + Assert.AreEqual(0m, symbolOrderBook.BestOffers.Bid.Price); + Assert.AreEqual(0m, symbolOrderBook.BestOffers.Bid.Quantity); + Assert.AreEqual(0m, symbolOrderBook.BestOffers.Ask.Price); + Assert.AreEqual(0m, symbolOrderBook.BestOffers.Ask.Quantity); + } + } +} diff --git a/CryptoExchange.Net/CryptoExchange.Net.xml b/CryptoExchange.Net/CryptoExchange.Net.xml index 97639e7..afa751c 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.xml +++ b/CryptoExchange.Net/CryptoExchange.Net.xml @@ -837,6 +837,11 @@ The best ask currently in the order book + + + BestBid/BesAsk returned as a pair + + Start connecting and synchronizing the order book @@ -1819,6 +1824,11 @@ The best ask currently in the order book + + + BestBid/BesAsk returned as a pair + + ctor @@ -2918,5 +2928,148 @@ + + + Specifies that is allowed as an input even if the + corresponding type disallows it. + + + + + Initializes a new instance of the class. + + + + + Specifies that is disallowed as an input even if the + corresponding type allows it. + + + + + Initializes a new instance of the class. + + + + + Specifies that a method that will never return under any circumstance. + + + + + Initializes a new instance of the class. + + + + + Specifies that the method will not return if the associated + parameter is passed the specified value. + + + + + Gets the condition parameter value. + Code after the method is considered unreachable by diagnostics if the argument + to the associated parameter matches this value. + + + + + Initializes a new instance of the + class with the specified parameter value. + + + The condition parameter value. + Code after the method is considered unreachable by diagnostics if the argument + to the associated parameter matches this value. + + + + + Specifies that an output may be even if the + corresponding type disallows it. + + + + + Initializes a new instance of the class. + + + + + Specifies that when a method returns , + the parameter may be even if the corresponding type disallows it. + + + + + Gets the return value condition. + If the method returns this value, the associated parameter may be . + + + + + Initializes the attribute with the specified return value condition. + + + The return value condition. + If the method returns this value, the associated parameter may be . + + + + + Specifies that an output is not even if the + corresponding type allows it. + + + + + Initializes a new instance of the class. + + + + + Specifies that the output will be non- if the + named parameter is non-. + + + + + Gets the associated parameter name. + The output will be non- if the argument to the + parameter specified is non-. + + + + + Initializes the attribute with the associated parameter name. + + + The associated parameter name. + The output will be non- if the argument to the + parameter specified is non-. + + + + + Specifies that when a method returns , + the parameter will not be even if the corresponding type allows it. + + + + + Gets the return value condition. + If the method returns this value, the associated parameter will not be . + + + + + Initializes the attribute with the specified return value condition. + + + The return value condition. + If the method returns this value, the associated parameter will not be . + + diff --git a/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs b/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs index 9d0b502..ee24057 100644 --- a/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs +++ b/CryptoExchange.Net/Interfaces/ISymbolOrderBook.cs @@ -70,6 +70,11 @@ namespace CryptoExchange.Net.Interfaces /// ISymbolOrderBookEntry BestAsk { get; } + /// + /// BestBid/BesAsk returned as a pair + /// + (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; } + /// /// Start connecting and synchronizing the order book /// diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs index 36e94b8..ea35512 100644 --- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs +++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs @@ -127,6 +127,14 @@ namespace CryptoExchange.Net.OrderBook } } + private class EmptySymbolOrderBookEntry : ISymbolOrderBookEntry + { + public decimal Quantity { get { return 0m; } set {; } } + public decimal Price { get { return 0m; } set {; } } + } + + private static ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry(); + /// /// The best bid currently in the order book /// @@ -135,7 +143,7 @@ namespace CryptoExchange.Net.OrderBook get { lock (bookLock) - return bids.FirstOrDefault().Value; + return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; } } @@ -147,7 +155,17 @@ namespace CryptoExchange.Net.OrderBook get { lock (bookLock) - return asks.FirstOrDefault().Value; + return asks.FirstOrDefault().Value ?? emptySymbolOrderBookEntry; + } + } + + /// + /// BestBid/BesAsk returned as a pair + /// + public (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { + get { + lock (bookLock) + return (BestBid,BestAsk); } } @@ -298,9 +316,10 @@ namespace CryptoExchange.Net.OrderBook private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk) { - if (BestBid.Price != prevBestBid.Price || BestBid.Quantity != prevBestBid.Quantity || - BestAsk.Price != prevBestAsk.Price || BestAsk.Quantity != prevBestAsk.Quantity) - OnBestOffersChanged?.Invoke(BestBid, BestAsk); + var (bestBid, bestAsk) = BestOffers; + if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity || + bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity) + OnBestOffersChanged?.Invoke(bestBid, bestAsk); } /// @@ -329,8 +348,7 @@ namespace CryptoExchange.Net.OrderBook else { CheckProcessBuffer(); - var prevBestBid = BestBid; - var prevBestAsk = BestAsk; + var (prevBestBid, prevBestAsk) = BestOffers; ProcessSingleSequenceUpdates(rangeUpdateId, bids, asks); OnOrderBookUpdate?.Invoke(bids, asks); CheckBestOffersChanged(prevBestBid, prevBestAsk); @@ -366,8 +384,7 @@ namespace CryptoExchange.Net.OrderBook else { CheckProcessBuffer(); - var prevBestBid = BestBid; - var prevBestAsk = BestAsk; + var (prevBestBid, prevBestAsk) = BestOffers; ProcessRangeUpdates(firstUpdateId, lastUpdateId, bids, asks); OnOrderBookUpdate?.Invoke(bids, asks); CheckBestOffersChanged(prevBestBid, prevBestAsk); @@ -396,8 +413,7 @@ namespace CryptoExchange.Net.OrderBook else { CheckProcessBuffer(); - var prevBestBid = BestBid; - var prevBestAsk = BestAsk; + var (prevBestBid, prevBestAsk) = BestOffers; ProcessUpdates(bids, asks); OnOrderBookUpdate?.Invoke(bids, asks); CheckBestOffersChanged(prevBestBid, prevBestAsk);