mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-08 00:16:27 +00:00
Merge pull request #28 from BenDavison/null-during-orderbook-sync
Fix for null reference on SymbolOrderBook when no data is available yet, added BestOffers
This commit is contained in:
commit
ef4ab4bd8b
69
CryptoExchange.Net.UnitTests/SymbolOrderBookTests.cs
Normal file
69
CryptoExchange.Net.UnitTests/SymbolOrderBookTests.cs
Normal file
@ -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<CallResult<bool>> DoResync()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected override Task<CallResult<UpdateSubscription>> 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -837,6 +837,11 @@
|
||||
The best ask currently in the order book
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:CryptoExchange.Net.Interfaces.ISymbolOrderBook.BestOffers">
|
||||
<summary>
|
||||
BestBid/BesAsk returned as a pair
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:CryptoExchange.Net.Interfaces.ISymbolOrderBook.Start">
|
||||
<summary>
|
||||
Start connecting and synchronizing the order book
|
||||
@ -1819,6 +1824,11 @@
|
||||
The best ask currently in the order book
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:CryptoExchange.Net.OrderBook.SymbolOrderBook.BestOffers">
|
||||
<summary>
|
||||
BestBid/BesAsk returned as a pair
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.#ctor(System.String,CryptoExchange.Net.Objects.OrderBookOptions)">
|
||||
<summary>
|
||||
ctor
|
||||
@ -2918,5 +2928,148 @@
|
||||
<member name="M:CryptoExchange.Net.Sockets.WebsocketFactory.CreateWebsocket(CryptoExchange.Net.Logging.Log,System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.String})">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute">
|
||||
<summary>
|
||||
Specifies that <see langword="null"/> is allowed as an input even if the
|
||||
corresponding type disallows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.AllowNullAttribute.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.AllowNullAttribute"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute">
|
||||
<summary>
|
||||
Specifies that <see langword="null"/> is disallowed as an input even if the
|
||||
corresponding type allows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.DisallowNullAttribute.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DisallowNullAttribute"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute">
|
||||
<summary>
|
||||
Specifies that a method that will never return under any circumstance.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnAttribute"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute">
|
||||
<summary>
|
||||
Specifies that the method will not return if the associated <see cref="T:System.Boolean"/>
|
||||
parameter is passed the specified value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.ParameterValue">
|
||||
<summary>
|
||||
Gets the condition parameter value.
|
||||
Code after the method is considered unreachable by diagnostics if the argument
|
||||
to the associated parameter matches this value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute.#ctor(System.Boolean)">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.DoesNotReturnIfAttribute"/>
|
||||
class with the specified parameter value.
|
||||
</summary>
|
||||
<param name="parameterValue">
|
||||
The condition parameter value.
|
||||
Code after the method is considered unreachable by diagnostics if the argument
|
||||
to the associated parameter matches this value.
|
||||
</param>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute">
|
||||
<summary>
|
||||
Specifies that an output may be <see langword="null"/> even if the
|
||||
corresponding type disallows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullAttribute.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.MaybeNullAttribute"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute">
|
||||
<summary>
|
||||
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue"/>,
|
||||
the parameter may be <see langword="null"/> even if the corresponding type disallows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.ReturnValue">
|
||||
<summary>
|
||||
Gets the return value condition.
|
||||
If the method returns this value, the associated parameter may be <see langword="null"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute.#ctor(System.Boolean)">
|
||||
<summary>
|
||||
Initializes the attribute with the specified return value condition.
|
||||
</summary>
|
||||
<param name="returnValue">
|
||||
The return value condition.
|
||||
If the method returns this value, the associated parameter may be <see langword="null"/>.
|
||||
</param>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.NotNullAttribute">
|
||||
<summary>
|
||||
Specifies that an output is not <see langword="null"/> even if the
|
||||
corresponding type allows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.NotNullAttribute.#ctor">
|
||||
<summary>
|
||||
Initializes a new instance of the <see cref="T:System.Diagnostics.CodeAnalysis.NotNullAttribute"/> class.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute">
|
||||
<summary>
|
||||
Specifies that the output will be non-<see langword="null"/> if the
|
||||
named parameter is non-<see langword="null"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.ParameterName">
|
||||
<summary>
|
||||
Gets the associated parameter name.
|
||||
The output will be non-<see langword="null"/> if the argument to the
|
||||
parameter specified is non-<see langword="null"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute.#ctor(System.String)">
|
||||
<summary>
|
||||
Initializes the attribute with the associated parameter name.
|
||||
</summary>
|
||||
<param name="parameterName">
|
||||
The associated parameter name.
|
||||
The output will be non-<see langword="null"/> if the argument to the
|
||||
parameter specified is non-<see langword="null"/>.
|
||||
</param>
|
||||
</member>
|
||||
<member name="T:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute">
|
||||
<summary>
|
||||
Specifies that when a method returns <see cref="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue"/>,
|
||||
the parameter will not be <see langword="null"/> even if the corresponding type allows it.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.ReturnValue">
|
||||
<summary>
|
||||
Gets the return value condition.
|
||||
If the method returns this value, the associated parameter will not be <see langword="null"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:System.Diagnostics.CodeAnalysis.NotNullWhenAttribute.#ctor(System.Boolean)">
|
||||
<summary>
|
||||
Initializes the attribute with the specified return value condition.
|
||||
</summary>
|
||||
<param name="returnValue">
|
||||
The return value condition.
|
||||
If the method returns this value, the associated parameter will not be <see langword="null"/>.
|
||||
</param>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
|
@ -70,6 +70,11 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
ISymbolOrderBookEntry BestAsk { get; }
|
||||
|
||||
/// <summary>
|
||||
/// BestBid/BesAsk returned as a pair
|
||||
/// </summary>
|
||||
(ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start connecting and synchronizing the order book
|
||||
/// </summary>
|
||||
|
@ -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();
|
||||
|
||||
/// <summary>
|
||||
/// The best bid currently in the order book
|
||||
/// </summary>
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BestBid/BesAsk returned as a pair
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user