mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-08 08:26:20 +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
|
The best ask currently in the order book
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</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">
|
<member name="M:CryptoExchange.Net.Interfaces.ISymbolOrderBook.Start">
|
||||||
<summary>
|
<summary>
|
||||||
Start connecting and synchronizing the order book
|
Start connecting and synchronizing the order book
|
||||||
@ -1819,6 +1824,11 @@
|
|||||||
The best ask currently in the order book
|
The best ask currently in the order book
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</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)">
|
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.#ctor(System.String,CryptoExchange.Net.Objects.OrderBookOptions)">
|
||||||
<summary>
|
<summary>
|
||||||
ctor
|
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})">
|
<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 />
|
<inheritdoc />
|
||||||
</member>
|
</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>
|
</members>
|
||||||
</doc>
|
</doc>
|
||||||
|
@ -70,6 +70,11 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
ISymbolOrderBookEntry BestAsk { get; }
|
ISymbolOrderBookEntry BestAsk { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// BestBid/BesAsk returned as a pair
|
||||||
|
/// </summary>
|
||||||
|
(ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start connecting and synchronizing the order book
|
/// Start connecting and synchronizing the order book
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// The best bid currently in the order book
|
/// The best bid currently in the order book
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -135,7 +143,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
lock (bookLock)
|
||||||
return bids.FirstOrDefault().Value;
|
return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +155,17 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
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)
|
private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk)
|
||||||
{
|
{
|
||||||
if (BestBid.Price != prevBestBid.Price || BestBid.Quantity != prevBestBid.Quantity ||
|
var (bestBid, bestAsk) = BestOffers;
|
||||||
BestAsk.Price != prevBestAsk.Price || BestAsk.Quantity != prevBestAsk.Quantity)
|
if (bestBid.Price != prevBestBid.Price || bestBid.Quantity != prevBestBid.Quantity ||
|
||||||
OnBestOffersChanged?.Invoke(BestBid, BestAsk);
|
bestAsk.Price != prevBestAsk.Price || bestAsk.Quantity != prevBestAsk.Quantity)
|
||||||
|
OnBestOffersChanged?.Invoke(bestBid, bestAsk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -329,8 +348,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
CheckProcessBuffer();
|
CheckProcessBuffer();
|
||||||
var prevBestBid = BestBid;
|
var (prevBestBid, prevBestAsk) = BestOffers;
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessSingleSequenceUpdates(rangeUpdateId, bids, asks);
|
ProcessSingleSequenceUpdates(rangeUpdateId, bids, asks);
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
OnOrderBookUpdate?.Invoke(bids, asks);
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
||||||
@ -366,8 +384,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
CheckProcessBuffer();
|
CheckProcessBuffer();
|
||||||
var prevBestBid = BestBid;
|
var (prevBestBid, prevBestAsk) = BestOffers;
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessRangeUpdates(firstUpdateId, lastUpdateId, bids, asks);
|
ProcessRangeUpdates(firstUpdateId, lastUpdateId, bids, asks);
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
OnOrderBookUpdate?.Invoke(bids, asks);
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
||||||
@ -396,8 +413,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
CheckProcessBuffer();
|
CheckProcessBuffer();
|
||||||
var prevBestBid = BestBid;
|
var (prevBestBid, prevBestAsk) = BestOffers;
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessUpdates(bids, asks);
|
ProcessUpdates(bids, asks);
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
OnOrderBookUpdate?.Invoke(bids, asks);
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user