1
0
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:
Jan Korf 2020-03-03 09:09:50 +01:00 committed by GitHub
commit ef4ab4bd8b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 254 additions and 11 deletions

View 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);
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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);