mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-11 01:46:12 +00:00
commit
45a15c4d29
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, false);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using CryptoExchange.Net.Authentication;
|
using CryptoExchange.Net.Authentication;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
@ -38,14 +39,14 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
|
public override Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization)
|
||||||
{
|
{
|
||||||
return base.AddAuthenticationToHeaders(uri, method, parameters, signed);
|
return base.AddAuthenticationToHeaders(uri, method, parameters, signed, postParameters, arraySerialization);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
|
public override Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed, PostParameters postParameters, ArrayParametersSerialization arraySerialization)
|
||||||
{
|
{
|
||||||
return base.AddAuthenticationToParameters(uri, method, parameters, signed);
|
return base.AddAuthenticationToParameters(uri, method, parameters, signed, postParameters, arraySerialization);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string Sign(string toSign)
|
public override string Sign(string toSign)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using CryptoExchange.Net.Objects;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Authentication
|
namespace CryptoExchange.Net.Authentication
|
||||||
@ -29,8 +30,11 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// <param name="method"></param>
|
/// <param name="method"></param>
|
||||||
/// <param name="parameters"></param>
|
/// <param name="parameters"></param>
|
||||||
/// <param name="signed"></param>
|
/// <param name="signed"></param>
|
||||||
|
/// <param name="postParameterPosition"></param>
|
||||||
|
/// <param name="arraySerialization"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
|
public virtual Dictionary<string, object> AddAuthenticationToParameters(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
|
||||||
|
PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization)
|
||||||
{
|
{
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
@ -42,8 +46,11 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
/// <param name="method"></param>
|
/// <param name="method"></param>
|
||||||
/// <param name="parameters"></param>
|
/// <param name="parameters"></param>
|
||||||
/// <param name="signed"></param>
|
/// <param name="signed"></param>
|
||||||
|
/// <param name="postParameterPosition"></param>
|
||||||
|
/// <param name="arraySerialization"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed)
|
public virtual Dictionary<string, string> AddAuthenticationToHeaders(string uri, HttpMethod method, Dictionary<string, object> parameters, bool signed,
|
||||||
|
PostParameters postParameterPosition, ArrayParametersSerialization arraySerialization)
|
||||||
{
|
{
|
||||||
return new Dictionary<string, string>();
|
return new Dictionary<string, string>();
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>A base package for implementing cryptocurrency exchange API's</Description>
|
<Description>A base package for implementing cryptocurrency exchange API's</Description>
|
||||||
<PackageVersion>3.0.5</PackageVersion>
|
<PackageVersion>3.0.11</PackageVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||||
<NeutralLanguage>en</NeutralLanguage>
|
<NeutralLanguage>en</NeutralLanguage>
|
||||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
<PackageReleaseNotes>3.0.5 - Added PausedActivity events on socket subscriptions</PackageReleaseNotes>
|
<PackageReleaseNotes>3.0.11 - Added support for checksum in SymbolOrderBook</PackageReleaseNotes>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<LangVersion>8.0</LangVersion>
|
<LangVersion>8.0</LangVersion>
|
||||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||||
|
@ -97,7 +97,7 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<param name="credentials"></param>
|
<param name="credentials"></param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
|
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToParameters(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
|
||||||
<summary>
|
<summary>
|
||||||
Add authentication to the parameter list
|
Add authentication to the parameter list
|
||||||
</summary>
|
</summary>
|
||||||
@ -105,9 +105,11 @@
|
|||||||
<param name="method"></param>
|
<param name="method"></param>
|
||||||
<param name="parameters"></param>
|
<param name="parameters"></param>
|
||||||
<param name="signed"></param>
|
<param name="signed"></param>
|
||||||
|
<param name="postParameterPosition"></param>
|
||||||
|
<param name="arraySerialization"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
|
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.AddAuthenticationToHeaders(System.String,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
|
||||||
<summary>
|
<summary>
|
||||||
Add authentication to the header dictionary
|
Add authentication to the header dictionary
|
||||||
</summary>
|
</summary>
|
||||||
@ -115,6 +117,8 @@
|
|||||||
<param name="method"></param>
|
<param name="method"></param>
|
||||||
<param name="parameters"></param>
|
<param name="parameters"></param>
|
||||||
<param name="signed"></param>
|
<param name="signed"></param>
|
||||||
|
<param name="postParameterPosition"></param>
|
||||||
|
<param name="arraySerialization"></param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.Sign(System.String)">
|
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.Sign(System.String)">
|
||||||
@ -837,6 +841,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
|
||||||
@ -1144,7 +1153,6 @@
|
|||||||
<param name="port">The proxy port</param>
|
<param name="port">The proxy port</param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.String)">
|
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.String)">
|
||||||
<inheritdoc />
|
|
||||||
<summary>
|
<summary>
|
||||||
Create new settings for a proxy
|
Create new settings for a proxy
|
||||||
</summary>
|
</summary>
|
||||||
@ -1154,7 +1162,6 @@
|
|||||||
<param name="password">The proxy password</param>
|
<param name="password">The proxy password</param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.Security.SecureString)">
|
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.Security.SecureString)">
|
||||||
<inheritdoc />
|
|
||||||
<summary>
|
<summary>
|
||||||
Create new settings for a proxy
|
Create new settings for a proxy
|
||||||
</summary>
|
</summary>
|
||||||
@ -1326,6 +1333,11 @@
|
|||||||
Connecting
|
Connecting
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:CryptoExchange.Net.Objects.OrderBookStatus.Reconnecting">
|
||||||
|
<summary>
|
||||||
|
Reconnecting
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="F:CryptoExchange.Net.Objects.OrderBookStatus.Syncing">
|
<member name="F:CryptoExchange.Net.Objects.OrderBookStatus.Syncing">
|
||||||
<summary>
|
<summary>
|
||||||
Syncing data
|
Syncing data
|
||||||
@ -1538,11 +1550,20 @@
|
|||||||
Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.
|
Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.#ctor(System.String,System.Boolean)">
|
<member name="P:CryptoExchange.Net.Objects.OrderBookOptions.StrictLevels">
|
||||||
|
<summary>
|
||||||
|
Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
|
||||||
|
when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
|
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.#ctor(System.String,System.Boolean,System.Boolean)">
|
||||||
<summary>
|
<summary>
|
||||||
</summary>
|
</summary>
|
||||||
<param name="name">The name of the order book implementation</param>
|
<param name="name">The name of the order book implementation</param>
|
||||||
<param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
|
<param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
|
||||||
|
<param name="strictLevels">Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
|
||||||
|
when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed</param>
|
||||||
|
<param name="levels">Amount of levels for this order book</param>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.ToString">
|
<member name="M:CryptoExchange.Net.Objects.OrderBookOptions.ToString">
|
||||||
<inheritdoc />
|
<inheritdoc />
|
||||||
@ -1659,41 +1680,6 @@
|
|||||||
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
|
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
|
||||||
<inheritdoc />
|
<inheritdoc />
|
||||||
</member>
|
</member>
|
||||||
<member name="T:CryptoExchange.Net.OrderBook.ProcessBufferEntry">
|
|
||||||
<summary>
|
|
||||||
Buffer entry for order book
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.ProcessBufferEntry.Asks">
|
|
||||||
<summary>
|
|
||||||
List of asks
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.ProcessBufferEntry.Bids">
|
|
||||||
<summary>
|
|
||||||
List of bids
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:CryptoExchange.Net.OrderBook.ProcessBufferSingleSequenceEntry">
|
|
||||||
<summary>
|
|
||||||
Buffer entry with a single update id per update
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.ProcessBufferSingleSequenceEntry.UpdateId">
|
|
||||||
<summary>
|
|
||||||
First update id
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.ProcessBufferSingleSequenceEntry.Asks">
|
|
||||||
<summary>
|
|
||||||
List of asks
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.ProcessBufferSingleSequenceEntry.Bids">
|
|
||||||
<summary>
|
|
||||||
List of bids
|
|
||||||
</summary>
|
|
||||||
</member>
|
|
||||||
<member name="T:CryptoExchange.Net.OrderBook.ProcessBufferRangeSequenceEntry">
|
<member name="T:CryptoExchange.Net.OrderBook.ProcessBufferRangeSequenceEntry">
|
||||||
<summary>
|
<summary>
|
||||||
Buffer entry with a first and last update id
|
Buffer entry with a first and last update id
|
||||||
@ -1754,6 +1740,11 @@
|
|||||||
If order book is set
|
If order book is set
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="P:CryptoExchange.Net.OrderBook.SymbolOrderBook.Levels">
|
||||||
|
<summary>
|
||||||
|
The amount of levels for this book
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:CryptoExchange.Net.OrderBook.SymbolOrderBook.Status">
|
<member name="P:CryptoExchange.Net.OrderBook.SymbolOrderBook.Status">
|
||||||
<summary>
|
<summary>
|
||||||
The status of the order book. Order book is up to date when the status is `Synced`
|
The status of the order book. Order book is up to date when the status is `Synced`
|
||||||
@ -1819,6 +1810,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
|
||||||
@ -1867,6 +1863,13 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.DoChecksum(System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Validate a checksum with the current order book
|
||||||
|
</summary>
|
||||||
|
<param name="checksum"></param>
|
||||||
|
<returns></returns>
|
||||||
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.SetInitialOrderBook(System.Int64,System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry},System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry})">
|
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.SetInitialOrderBook(System.Int64,System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry},System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry})">
|
||||||
<summary>
|
<summary>
|
||||||
Set the initial data for the order book
|
Set the initial data for the order book
|
||||||
@ -1883,6 +1886,12 @@
|
|||||||
<param name="bids"></param>
|
<param name="bids"></param>
|
||||||
<param name="asks"></param>
|
<param name="asks"></param>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.AddChecksum(System.Int32)">
|
||||||
|
<summary>
|
||||||
|
Add a checksum to the process queue
|
||||||
|
</summary>
|
||||||
|
<param name="checksum"></param>
|
||||||
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.UpdateOrderBook(System.Int64,System.Int64,System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry},System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry})">
|
<member name="M:CryptoExchange.Net.OrderBook.SymbolOrderBook.UpdateOrderBook(System.Int64,System.Int64,System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry},System.Collections.Generic.IEnumerable{CryptoExchange.Net.Interfaces.ISymbolOrderBookEntry})">
|
||||||
<summary>
|
<summary>
|
||||||
Update the order book using a first/last update id
|
Update the order book using a first/last update id
|
||||||
@ -2104,11 +2113,21 @@
|
|||||||
Request body content type
|
Request body content type
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:CryptoExchange.Net.RestClient.manualParseError">
|
||||||
|
<summary>
|
||||||
|
Whether or not we need to manually parse an error instead of relying on the http status code
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="F:CryptoExchange.Net.RestClient.arraySerialization">
|
<member name="F:CryptoExchange.Net.RestClient.arraySerialization">
|
||||||
<summary>
|
<summary>
|
||||||
How to serialize array parameters
|
How to serialize array parameters
|
||||||
</summary>
|
</summary>
|
||||||
</member>
|
</member>
|
||||||
|
<member name="F:CryptoExchange.Net.RestClient.requestBodyEmptyContent">
|
||||||
|
<summary>
|
||||||
|
What request body should be when no data is send
|
||||||
|
</summary>
|
||||||
|
</member>
|
||||||
<member name="P:CryptoExchange.Net.RestClient.RequestTimeout">
|
<member name="P:CryptoExchange.Net.RestClient.RequestTimeout">
|
||||||
<summary>
|
<summary>
|
||||||
Timeout for requests
|
Timeout for requests
|
||||||
@ -2159,7 +2178,7 @@
|
|||||||
</summary>
|
</summary>
|
||||||
<returns>The roundtrip time of the ping request</returns>
|
<returns>The roundtrip time of the ping request</returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.RestClient.SendRequest``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean)">
|
<member name="M:CryptoExchange.Net.RestClient.SendRequest``1(System.Uri,System.Net.Http.HttpMethod,System.Threading.CancellationToken,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,System.Boolean,System.Nullable{CryptoExchange.Net.Objects.PostParameters},System.Nullable{CryptoExchange.Net.Objects.ArrayParametersSerialization})">
|
||||||
<summary>
|
<summary>
|
||||||
Execute a request
|
Execute a request
|
||||||
</summary>
|
</summary>
|
||||||
@ -2169,7 +2188,9 @@
|
|||||||
<param name="cancellationToken">Cancellation token</param>
|
<param name="cancellationToken">Cancellation token</param>
|
||||||
<param name="parameters">The parameters of the request</param>
|
<param name="parameters">The parameters of the request</param>
|
||||||
<param name="signed">Whether or not the request should be authenticated</param>
|
<param name="signed">Whether or not the request should be authenticated</param>
|
||||||
<param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
|
<param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
|
||||||
|
<param name="postPosition">Where the post parameters should be placed</param>
|
||||||
|
<param name="arraySerialization">How array paramters should be serialized</param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.RestClient.GetResponse``1(CryptoExchange.Net.Interfaces.IRequest,System.Threading.CancellationToken)">
|
<member name="M:CryptoExchange.Net.RestClient.GetResponse``1(CryptoExchange.Net.Interfaces.IRequest,System.Threading.CancellationToken)">
|
||||||
@ -2180,7 +2201,15 @@
|
|||||||
<param name="cancellationToken">Cancellation token</param>
|
<param name="cancellationToken">Cancellation token</param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean)">
|
<member name="M:CryptoExchange.Net.RestClient.TryParseError(Newtonsoft.Json.Linq.JToken)">
|
||||||
|
<summary>
|
||||||
|
Can be used to parse an error even though response status indicates success. Some apis always return 200 OK, even though there is an error.
|
||||||
|
This can be used together with ManualParseError to check if it is an error before deserializing to an object
|
||||||
|
</summary>
|
||||||
|
<param name="data">Received data</param>
|
||||||
|
<returns>Null if not an error, Error otherwise</returns>
|
||||||
|
</member>
|
||||||
|
<member name="M:CryptoExchange.Net.RestClient.ConstructRequest(System.Uri,System.Net.Http.HttpMethod,System.Collections.Generic.Dictionary{System.String,System.Object},System.Boolean,CryptoExchange.Net.Objects.PostParameters,CryptoExchange.Net.Objects.ArrayParametersSerialization)">
|
||||||
<summary>
|
<summary>
|
||||||
Creates a request object
|
Creates a request object
|
||||||
</summary>
|
</summary>
|
||||||
@ -2188,6 +2217,8 @@
|
|||||||
<param name="method">The method of the request</param>
|
<param name="method">The method of the request</param>
|
||||||
<param name="parameters">The parameters of the request</param>
|
<param name="parameters">The parameters of the request</param>
|
||||||
<param name="signed">Whether or not the request should be authenticated</param>
|
<param name="signed">Whether or not the request should be authenticated</param>
|
||||||
|
<param name="postPosition">Where the post parameters should be placed</param>
|
||||||
|
<param name="arraySerialization">How array paramters should be serialized</param>
|
||||||
<returns></returns>
|
<returns></returns>
|
||||||
</member>
|
</member>
|
||||||
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object},System.String)">
|
<member name="M:CryptoExchange.Net.RestClient.WriteParamBody(CryptoExchange.Net.Interfaces.IRequest,System.Collections.Generic.Dictionary{System.String,System.Object},System.String)">
|
||||||
@ -2918,5 +2949,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>
|
||||||
|
@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Security;
|
using System.Security;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
@ -79,16 +80,16 @@ namespace CryptoExchange.Net
|
|||||||
foreach (var arrayEntry in arraysParameters)
|
foreach (var arrayEntry in arraysParameters)
|
||||||
{
|
{
|
||||||
if(serializationType == ArrayParametersSerialization.Array)
|
if(serializationType == ArrayParametersSerialization.Array)
|
||||||
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? WebUtility.UrlEncode(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={v}"))}&";
|
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={v}"))}&";
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var array = (Array)arrayEntry.Value;
|
var array = (Array)arrayEntry.Value;
|
||||||
uriString += string.Join("&", array.OfType<object>().Select(a => $"{arrayEntry.Key}={WebUtility.UrlEncode(a.ToString())}"));
|
uriString += string.Join("&", array.OfType<object>().Select(a => $"{arrayEntry.Key}={Uri.EscapeDataString(a.ToString())}"));
|
||||||
uriString += "&";
|
uriString += "&";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? WebUtility.UrlEncode(s.Value.ToString()) : s.Value)}"))}";
|
uriString += $"{string.Join("&", parameters.Where(p => !p.Value.GetType().IsArray).Select(s => $"{s.Key}={(urlEncodeValues ? Uri.EscapeDataString(s.Value.ToString()) : s.Value)}"))}";
|
||||||
uriString = uriString.TrimEnd('&');
|
uriString = uriString.TrimEnd('&');
|
||||||
return uriString;
|
return uriString;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Security;
|
||||||
using System.Security;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Objects
|
namespace CryptoExchange.Net.Objects
|
||||||
{
|
{
|
||||||
@ -36,7 +35,6 @@ namespace CryptoExchange.Net.Objects
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create new settings for a proxy
|
/// Create new settings for a proxy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -48,7 +46,6 @@ namespace CryptoExchange.Net.Objects
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create new settings for a proxy
|
/// Create new settings for a proxy
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -58,9 +55,6 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <param name="password">The proxy password</param>
|
/// <param name="password">The proxy password</param>
|
||||||
public ApiProxy(string host, int port, string? login, SecureString? password)
|
public ApiProxy(string host, int port, string? login, SecureString? password)
|
||||||
{
|
{
|
||||||
if (!host.StartsWith("http"))
|
|
||||||
throw new ArgumentException("Proxy host should start with either http:// or https://");
|
|
||||||
|
|
||||||
Host = host;
|
Host = host;
|
||||||
Port = port;
|
Port = port;
|
||||||
Login = login;
|
Login = login;
|
||||||
|
@ -59,6 +59,10 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Connecting,
|
Connecting,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Reconnecting
|
||||||
|
/// </summary>
|
||||||
|
Reconnecting,
|
||||||
|
/// <summary>
|
||||||
/// Syncing data
|
/// Syncing data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Syncing,
|
Syncing,
|
||||||
|
@ -44,20 +44,30 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SequenceNumbersAreConsecutive { get; }
|
public bool SequenceNumbersAreConsecutive { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
|
||||||
|
/// when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed
|
||||||
|
/// </summary>
|
||||||
|
public bool StrictLevels { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="name">The name of the order book implementation</param>
|
/// <param name="name">The name of the order book implementation</param>
|
||||||
/// <param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
|
/// <param name="sequencesAreConsecutive">Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.</param>
|
||||||
public OrderBookOptions(string name, bool sequencesAreConsecutive)
|
/// <param name="strictLevels">Whether or not a level should be removed from the book when it's pushed out of scope of the limit. For example with a book of limit 10,
|
||||||
|
/// when a new bid is added which makes the total amount of bids 11, should the last bid entry be removed</param>
|
||||||
|
/// <param name="levels">Amount of levels for this order book</param>
|
||||||
|
public OrderBookOptions(string name, bool sequencesAreConsecutive, bool strictLevels)
|
||||||
{
|
{
|
||||||
OrderBookName = name;
|
OrderBookName = name;
|
||||||
SequenceNumbersAreConsecutive = sequencesAreConsecutive;
|
SequenceNumbersAreConsecutive = sequencesAreConsecutive;
|
||||||
|
StrictLevels = strictLevels;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{base.ToString()}, OrderBookName: {OrderBookName}, SequenceNumbersAreConsequtive: {SequenceNumbersAreConsecutive}";
|
return $"{base.ToString()}, OrderBookName: {OrderBookName}, SequenceNumbersAreConsequtive: {SequenceNumbersAreConsecutive}, StrictLevels: {StrictLevels}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,40 +3,6 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace CryptoExchange.Net.OrderBook
|
namespace CryptoExchange.Net.OrderBook
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Buffer entry for order book
|
|
||||||
/// </summary>
|
|
||||||
public class ProcessBufferEntry
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// List of asks
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderSequencedBookEntry> Asks { get; set; } = new List<ISymbolOrderSequencedBookEntry>();
|
|
||||||
/// <summary>
|
|
||||||
/// List of bids
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderSequencedBookEntry> Bids { get; set; } = new List<ISymbolOrderSequencedBookEntry>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Buffer entry with a single update id per update
|
|
||||||
/// </summary>
|
|
||||||
public class ProcessBufferSingleSequenceEntry
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// First update id
|
|
||||||
/// </summary>
|
|
||||||
public long UpdateId { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// List of asks
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = new List<ISymbolOrderBookEntry>();
|
|
||||||
/// <summary>
|
|
||||||
/// List of bids
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = new List<ISymbolOrderBookEntry>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Buffer entry with a first and last update id
|
/// Buffer entry with a first and last update id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
26
CryptoExchange.Net/OrderBook/ProcessQueueItem.cs
Normal file
26
CryptoExchange.Net/OrderBook/ProcessQueueItem.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using CryptoExchange.Net.Interfaces;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.OrderBook
|
||||||
|
{
|
||||||
|
internal class ProcessQueueItem
|
||||||
|
{
|
||||||
|
public long StartUpdateId { get; set; }
|
||||||
|
public long EndUpdateId { get; set; }
|
||||||
|
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = new List<ISymbolOrderBookEntry>();
|
||||||
|
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = new List<ISymbolOrderBookEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class InitialOrderBookItem
|
||||||
|
{
|
||||||
|
public long StartUpdateId { get; set; }
|
||||||
|
public long EndUpdateId { get; set; }
|
||||||
|
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = new List<ISymbolOrderBookEntry>();
|
||||||
|
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = new List<ISymbolOrderBookEntry>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ChecksumItem
|
||||||
|
{
|
||||||
|
public int Checksum { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CryptoExchange.Net.Interfaces;
|
using CryptoExchange.Net.Interfaces;
|
||||||
using CryptoExchange.Net.Logging;
|
using CryptoExchange.Net.Logging;
|
||||||
@ -19,8 +21,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The process buffer, used while syncing
|
/// The process buffer, used while syncing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly List<object> processBuffer;
|
protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
|
||||||
private readonly object bookLock = new object();
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The ask list
|
/// The ask list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -28,11 +29,18 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bid list
|
/// The bid list
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
||||||
protected SortedList<decimal, ISymbolOrderBookEntry> bids;
|
protected SortedList<decimal, ISymbolOrderBookEntry> bids;
|
||||||
|
|
||||||
|
private readonly object bookLock = new object();
|
||||||
|
|
||||||
private OrderBookStatus status;
|
private OrderBookStatus status;
|
||||||
private UpdateSubscription? subscription;
|
private UpdateSubscription? subscription;
|
||||||
private readonly bool sequencesAreConsecutive;
|
private readonly bool sequencesAreConsecutive;
|
||||||
|
private readonly bool strictLevels;
|
||||||
|
|
||||||
|
private Task _processTask;
|
||||||
|
private AutoResetEvent _queueEvent;
|
||||||
|
private ConcurrentQueue<object> _processQueue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Order book implementation id
|
/// Order book implementation id
|
||||||
@ -48,6 +56,11 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool bookSet;
|
protected bool bookSet;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The amount of levels for this book
|
||||||
|
/// </summary>
|
||||||
|
protected int? Levels { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The status of the order book. Order book is up to date when the status is `Synced`
|
/// The status of the order book. Order book is up to date when the status is `Synced`
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -127,6 +140,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 +156,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
lock (bookLock)
|
||||||
return bids.FirstOrDefault().Value;
|
return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,7 +168,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,8 +196,12 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
throw new ArgumentNullException(nameof(options));
|
throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
Id = options.OrderBookName;
|
Id = options.OrderBookName;
|
||||||
processBuffer = new List<object>();
|
processBuffer = new List<ProcessBufferRangeSequenceEntry>();
|
||||||
|
_processQueue = new ConcurrentQueue<object>();
|
||||||
|
_queueEvent = new AutoResetEvent(false);
|
||||||
|
|
||||||
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
|
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
|
||||||
|
strictLevels = options.StrictLevels;
|
||||||
Symbol = symbol;
|
Symbol = symbol;
|
||||||
Status = OrderBookStatus.Disconnected;
|
Status = OrderBookStatus.Disconnected;
|
||||||
|
|
||||||
@ -190,7 +225,10 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<CallResult<bool>> StartAsync()
|
public async Task<CallResult<bool>> StartAsync()
|
||||||
{
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} starting");
|
||||||
Status = OrderBookStatus.Connecting;
|
Status = OrderBookStatus.Connecting;
|
||||||
|
_processTask = Task.Run(ProcessQueue);
|
||||||
|
|
||||||
var startResult = await DoStart().ConfigureAwait(false);
|
var startResult = await DoStart().ConfigureAwait(false);
|
||||||
if (!startResult)
|
if (!startResult)
|
||||||
return new CallResult<bool>(false, startResult.Error);
|
return new CallResult<bool>(false, startResult.Error);
|
||||||
@ -205,7 +243,10 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
private void Reset()
|
private void Reset()
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} connection lost");
|
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} connection lost");
|
||||||
Status = OrderBookStatus.Connecting;
|
Status = OrderBookStatus.Reconnecting;
|
||||||
|
_queueEvent.Set();
|
||||||
|
// Clear queue
|
||||||
|
while(_processQueue.TryDequeue(out _))
|
||||||
processBuffer.Clear();
|
processBuffer.Clear();
|
||||||
bookSet = false;
|
bookSet = false;
|
||||||
DoReset();
|
DoReset();
|
||||||
@ -240,7 +281,10 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
|
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} stopping");
|
||||||
Status = OrderBookStatus.Disconnected;
|
Status = OrderBookStatus.Disconnected;
|
||||||
|
_queueEvent.Set();
|
||||||
|
_processTask.Wait();
|
||||||
if(subscription != null)
|
if(subscription != null)
|
||||||
await subscription.Close().ConfigureAwait(false);
|
await subscription.Close().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
@ -263,12 +307,34 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
protected abstract Task<CallResult<bool>> DoResync();
|
protected abstract Task<CallResult<bool>> DoResync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the initial data for the order book
|
/// Validate a checksum with the current order book
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="orderBookSequenceNumber">The last update sequence number</param>
|
/// <param name="checksum"></param>
|
||||||
/// <param name="askList">List of asks</param>
|
/// <returns></returns>
|
||||||
/// <param name="bidList">List of bids</param>
|
protected virtual bool DoChecksum(int checksum) => true;
|
||||||
protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> bidList, IEnumerable<ISymbolOrderBookEntry> askList)
|
|
||||||
|
private void ProcessQueue()
|
||||||
|
{
|
||||||
|
while(Status != OrderBookStatus.Disconnected)
|
||||||
|
{
|
||||||
|
_queueEvent.WaitOne();
|
||||||
|
|
||||||
|
while (_processQueue.TryDequeue(out var item))
|
||||||
|
{
|
||||||
|
if (Status == OrderBookStatus.Disconnected)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (item is InitialOrderBookItem iobi)
|
||||||
|
ProcessInitialOrderBookItem(iobi);
|
||||||
|
if (item is ProcessQueueItem pqi)
|
||||||
|
ProcessQueueItem(pqi);
|
||||||
|
else if (item is ChecksumItem ci)
|
||||||
|
ProcessChecksum(ci);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessInitialOrderBookItem(InitialOrderBookItem item)
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
lock (bookLock)
|
||||||
{
|
{
|
||||||
@ -276,31 +342,88 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
asks.Clear();
|
asks.Clear();
|
||||||
foreach (var ask in askList)
|
foreach (var ask in item.Asks)
|
||||||
asks.Add(ask.Price, ask);
|
asks.Add(ask.Price, ask);
|
||||||
bids.Clear();
|
bids.Clear();
|
||||||
foreach (var bid in bidList)
|
foreach (var bid in item.Bids)
|
||||||
bids.Add(bid.Price, bid);
|
bids.Add(bid.Price, bid);
|
||||||
|
|
||||||
LastSequenceNumber = orderBookSequenceNumber;
|
LastSequenceNumber = item.EndUpdateId;
|
||||||
|
|
||||||
AskCount = asks.Count;
|
AskCount = asks.Count;
|
||||||
BidCount = asks.Count;
|
BidCount = bids.Count;
|
||||||
|
|
||||||
bookSet = true;
|
|
||||||
LastOrderBookUpdate = DateTime.UtcNow;
|
LastOrderBookUpdate = DateTime.UtcNow;
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{orderBookSequenceNumber}");
|
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} data set: {BidCount} bids, {AskCount} asks. #{item.EndUpdateId}");
|
||||||
CheckProcessBuffer();
|
CheckProcessBuffer();
|
||||||
OnOrderBookUpdate?.Invoke(bidList, askList);
|
OnOrderBookUpdate?.Invoke(item.Asks, item.Bids);
|
||||||
OnBestOffersChanged?.Invoke(BestBid, BestAsk);
|
OnBestOffersChanged?.Invoke(BestBid, BestAsk);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk)
|
private void ProcessQueueItem(ProcessQueueItem item)
|
||||||
{
|
{
|
||||||
if (BestBid.Price != prevBestBid.Price || BestBid.Quantity != prevBestBid.Quantity ||
|
lock (bookLock)
|
||||||
BestAsk.Price != prevBestAsk.Price || BestAsk.Quantity != prevBestAsk.Quantity)
|
{
|
||||||
OnBestOffersChanged?.Invoke(BestBid, BestAsk);
|
if (Status == OrderBookStatus.Connecting || Status == OrderBookStatus.Disconnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!bookSet)
|
||||||
|
{
|
||||||
|
processBuffer.Add(new ProcessBufferRangeSequenceEntry()
|
||||||
|
{
|
||||||
|
Asks = item.Asks,
|
||||||
|
Bids = item.Bids,
|
||||||
|
FirstUpdateId = item.StartUpdateId,
|
||||||
|
LastUpdateId = item.EndUpdateId,
|
||||||
|
});
|
||||||
|
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{item.StartUpdateId}-#{item.EndUpdateId} [{Asks.Count()} asks, {Bids.Count()} bids]");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CheckProcessBuffer();
|
||||||
|
var (prevBestBid, prevBestAsk) = BestOffers;
|
||||||
|
ProcessRangeUpdates(item.StartUpdateId, item.EndUpdateId, item.Bids, item.Asks);
|
||||||
|
|
||||||
|
if (asks.First().Key < bids.First().Key)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} detected out of sync order book. Resyncing");
|
||||||
|
_ = subscription?.Reconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnOrderBookUpdate?.Invoke(item.Bids, item.Asks);
|
||||||
|
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessChecksum(ChecksumItem ci)
|
||||||
|
{
|
||||||
|
lock (bookLock)
|
||||||
|
{
|
||||||
|
var checksumResult = DoChecksum(ci.Checksum);
|
||||||
|
if(!checksumResult)
|
||||||
|
{
|
||||||
|
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} out of sync. Resyncing");
|
||||||
|
_ = subscription?.Reconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial data for the order book
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="orderBookSequenceNumber">The last update sequence number</param>
|
||||||
|
/// <param name="askList">List of asks</param>
|
||||||
|
/// <param name="bidList">List of bids</param>
|
||||||
|
protected void SetInitialOrderBook(long orderBookSequenceNumber, IEnumerable<ISymbolOrderBookEntry> bidList, IEnumerable<ISymbolOrderBookEntry> askList)
|
||||||
|
{
|
||||||
|
bookSet = true;
|
||||||
|
|
||||||
|
_processQueue.Enqueue(new InitialOrderBookItem { StartUpdateId = orderBookSequenceNumber, EndUpdateId = orderBookSequenceNumber, Asks = askList, Bids = bidList });
|
||||||
|
_queueEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -311,31 +434,18 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <param name="asks"></param>
|
/// <param name="asks"></param>
|
||||||
protected void UpdateOrderBook(long rangeUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
protected void UpdateOrderBook(long rangeUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = rangeUpdateId, EndUpdateId = rangeUpdateId, Asks = asks, Bids = bids });
|
||||||
{
|
_queueEvent.Set();
|
||||||
if (Status == OrderBookStatus.Connecting || Status == OrderBookStatus.Disconnected)
|
}
|
||||||
return;
|
|
||||||
|
|
||||||
if (!bookSet)
|
/// <summary>
|
||||||
{
|
/// Add a checksum to the process queue
|
||||||
processBuffer.Add(new ProcessBufferSingleSequenceEntry()
|
/// </summary>
|
||||||
{
|
/// <param name="checksum"></param>
|
||||||
UpdateId = rangeUpdateId,
|
protected void AddChecksum(int checksum)
|
||||||
Asks = asks,
|
{
|
||||||
Bids = bids
|
_processQueue.Enqueue(new ChecksumItem() { Checksum = checksum });
|
||||||
});
|
_queueEvent.Set();
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{rangeUpdateId}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CheckProcessBuffer();
|
|
||||||
var prevBestBid = BestBid;
|
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessSingleSequenceUpdates(rangeUpdateId, bids, asks);
|
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -347,32 +457,8 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <param name="asks"></param>
|
/// <param name="asks"></param>
|
||||||
protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
|
||||||
{
|
_queueEvent.Set();
|
||||||
if (Status == OrderBookStatus.Connecting || Status == OrderBookStatus.Disconnected)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!bookSet)
|
|
||||||
{
|
|
||||||
processBuffer.Add(new ProcessBufferRangeSequenceEntry()
|
|
||||||
{
|
|
||||||
Asks = asks,
|
|
||||||
Bids = bids,
|
|
||||||
FirstUpdateId = firstUpdateId,
|
|
||||||
LastUpdateId = lastUpdateId
|
|
||||||
});
|
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{firstUpdateId}-{lastUpdateId}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CheckProcessBuffer();
|
|
||||||
var prevBestBid = BestBid;
|
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessRangeUpdates(firstUpdateId, lastUpdateId, bids, asks);
|
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -382,43 +468,11 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <param name="asks">List of asks</param>
|
/// <param name="asks">List of asks</param>
|
||||||
protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
||||||
{
|
{
|
||||||
lock (bookLock)
|
var highest = Math.Max(bids.Any() ? bids.Max(b => b.Sequence) : 0, asks.Any() ? asks.Max(a => a.Sequence) : 0);
|
||||||
{
|
var lowest = Math.Min(bids.Any() ? bids.Min(b => b.Sequence) : long.MaxValue, asks.Any() ? asks.Min(a => a.Sequence) : long.MaxValue);
|
||||||
if (!bookSet)
|
|
||||||
{
|
|
||||||
processBuffer.Add(new ProcessBufferEntry
|
|
||||||
{
|
|
||||||
Asks = asks,
|
|
||||||
Bids = bids
|
|
||||||
});
|
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update buffered #{Math.Min(bids.Min(b => b.Sequence), asks.Min(a => a.Sequence))}-{Math.Max(bids.Max(b => b.Sequence), asks.Max(a => a.Sequence))}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CheckProcessBuffer();
|
|
||||||
var prevBestBid = BestBid;
|
|
||||||
var prevBestAsk = BestAsk;
|
|
||||||
ProcessUpdates(bids, asks);
|
|
||||||
OnOrderBookUpdate?.Invoke(bids, asks);
|
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessUpdates(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = lowest, EndUpdateId = highest , Asks = asks, Bids = bids });
|
||||||
{
|
_queueEvent.Set();
|
||||||
var entries = new Dictionary<ISymbolOrderSequencedBookEntry, OrderBookEntryType>();
|
|
||||||
foreach (var entry in asks.OrderBy(a => a.Sequence))
|
|
||||||
entries.Add(entry, OrderBookEntryType.Ask);
|
|
||||||
foreach (var entry in bids.OrderBy(a => a.Sequence))
|
|
||||||
entries.Add(entry, OrderBookEntryType.Bid);
|
|
||||||
|
|
||||||
foreach (var entry in entries.OrderBy(e => e.Key.Sequence))
|
|
||||||
{
|
|
||||||
if(ProcessUpdate(entry.Key.Sequence, entry.Value, entry.Key))
|
|
||||||
LastSequenceNumber = entry.Key.Sequence;
|
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update #{LastSequenceNumber}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessRangeUpdates(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
private void ProcessRangeUpdates(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
@ -435,28 +489,25 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
foreach (var entry in asks)
|
foreach (var entry in asks)
|
||||||
ProcessUpdate(LastSequenceNumber + 1, OrderBookEntryType.Ask, entry);
|
ProcessUpdate(LastSequenceNumber + 1, OrderBookEntryType.Ask, entry);
|
||||||
|
|
||||||
|
if (Levels.HasValue && strictLevels)
|
||||||
|
{
|
||||||
|
while (this.bids.Count() > Levels.Value)
|
||||||
|
{
|
||||||
|
BidCount--;
|
||||||
|
this.bids.Remove(this.bids.Last().Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this.asks.Count() > Levels.Value)
|
||||||
|
{
|
||||||
|
AskCount--;
|
||||||
|
this.asks.Remove(this.asks.Last().Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LastSequenceNumber = lastUpdateId;
|
LastSequenceNumber = lastUpdateId;
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update processed #{firstUpdateId}-{lastUpdateId}");
|
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update processed #{firstUpdateId}-{lastUpdateId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessSingleSequenceUpdates(long updateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
|
||||||
{
|
|
||||||
foreach (var entry in bids)
|
|
||||||
{
|
|
||||||
if (!ProcessUpdate(updateId, OrderBookEntryType.Bid, entry))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var entry in asks)
|
|
||||||
{
|
|
||||||
if (!ProcessUpdate(updateId, OrderBookEntryType.Ask, entry))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LastSequenceNumber = updateId;
|
|
||||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} update processed #{LastSequenceNumber}");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check and empty the process buffer; see what entries to update the book with
|
/// Check and empty the process buffer; see what entries to update the book with
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -468,13 +519,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
|
|
||||||
foreach (var bufferEntry in pbList)
|
foreach (var bufferEntry in pbList)
|
||||||
{
|
{
|
||||||
if (bufferEntry is ProcessBufferEntry pbe)
|
ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
|
||||||
ProcessUpdates(pbe.Bids, pbe.Asks);
|
|
||||||
else if(bufferEntry is ProcessBufferRangeSequenceEntry pbrse)
|
|
||||||
ProcessRangeUpdates(pbrse.FirstUpdateId, pbrse.LastUpdateId, pbrse.Bids, pbrse.Asks);
|
|
||||||
else if (bufferEntry is ProcessBufferSingleSequenceEntry pbsse)
|
|
||||||
ProcessSingleSequenceUpdates(pbsse.UpdateId, pbsse.Bids, pbsse.Asks);
|
|
||||||
|
|
||||||
processBuffer.Remove(bufferEntry);
|
processBuffer.Remove(bufferEntry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -500,7 +545,6 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
{
|
{
|
||||||
// Out of sync
|
// Out of sync
|
||||||
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
|
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
|
||||||
Status = OrderBookStatus.Connecting;
|
|
||||||
subscription?.Reconnect();
|
subscription?.Reconnect();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -526,7 +570,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
listToChange[entry.Price].Quantity = entry.Quantity;
|
listToChange[entry.Price] = entry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,6 +596,14 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
return new CallResult<bool>(true, null);
|
return new CallResult<bool>(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CheckBestOffersChanged(ISymbolOrderBookEntry prevBestBid, ISymbolOrderBookEntry prevBestAsk)
|
||||||
|
{
|
||||||
|
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>
|
/// <summary>
|
||||||
/// Dispose the order book
|
/// Dispose the order book
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Web;
|
using System.Web;
|
||||||
@ -39,11 +40,21 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected RequestBodyFormat requestBodyFormat = RequestBodyFormat.Json;
|
protected RequestBodyFormat requestBodyFormat = RequestBodyFormat.Json;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not we need to manually parse an error instead of relying on the http status code
|
||||||
|
/// </summary>
|
||||||
|
protected bool manualParseError = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How to serialize array parameters
|
/// How to serialize array parameters
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected ArrayParametersSerialization arraySerialization = ArrayParametersSerialization.Array;
|
protected ArrayParametersSerialization arraySerialization = ArrayParametersSerialization.Array;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// What request body should be when no data is send
|
||||||
|
/// </summary>
|
||||||
|
protected string requestBodyEmptyContent = "{}";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timeout for requests
|
/// Timeout for requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -153,11 +164,13 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="cancellationToken">Cancellation token</param>
|
/// <param name="cancellationToken">Cancellation token</param>
|
||||||
/// <param name="parameters">The parameters of the request</param>
|
/// <param name="parameters">The parameters of the request</param>
|
||||||
/// <param name="signed">Whether or not the request should be authenticated</param>
|
/// <param name="signed">Whether or not the request should be authenticated</param>
|
||||||
/// <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
|
/// <param name="checkResult">Whether or not the resulting object should be checked for missing properties in the mapping (only outputs if log verbosity is Debug)</param>
|
||||||
|
/// <param name="postPosition">Where the post parameters should be placed</param>
|
||||||
|
/// <param name="arraySerialization">How array paramters should be serialized</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[return: NotNull]
|
[return: NotNull]
|
||||||
protected virtual async Task<WebCallResult<T>> SendRequest<T>(Uri uri, HttpMethod method, CancellationToken cancellationToken,
|
protected virtual async Task<WebCallResult<T>> SendRequest<T>(Uri uri, HttpMethod method, CancellationToken cancellationToken,
|
||||||
Dictionary<string, object>? parameters = null, bool signed = false, bool checkResult = true) where T : class
|
Dictionary<string, object>? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null) where T : class
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Debug, "Creating request for " + uri);
|
log.Write(LogVerbosity.Debug, "Creating request for " + uri);
|
||||||
if (signed && authProvider == null)
|
if (signed && authProvider == null)
|
||||||
@ -166,7 +179,7 @@ namespace CryptoExchange.Net
|
|||||||
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError());
|
return new WebCallResult<T>(null, null, null, new NoApiCredentialsError());
|
||||||
}
|
}
|
||||||
|
|
||||||
var request = ConstructRequest(uri, method, parameters, signed);
|
var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization);
|
||||||
foreach (var limiter in RateLimiters)
|
foreach (var limiter in RateLimiters)
|
||||||
{
|
{
|
||||||
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour);
|
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour);
|
||||||
@ -205,16 +218,38 @@ namespace CryptoExchange.Net
|
|||||||
var responseStream = await response.GetResponseStream().ConfigureAwait(false);
|
var responseStream = await response.GetResponseStream().ConfigureAwait(false);
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var desResult = await Deserialize<T>(responseStream).ConfigureAwait(false);
|
if (manualParseError)
|
||||||
responseStream.Close();
|
{
|
||||||
response.Close();
|
using var reader = new StreamReader(responseStream);
|
||||||
|
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
|
responseStream.Close();
|
||||||
|
response.Close();
|
||||||
|
log.Write(LogVerbosity.Debug, $"Data received: {data}");
|
||||||
|
|
||||||
return new WebCallResult<T>(statusCode, headers, desResult.Data, desResult.Error);
|
var parseResult = ValidateJson(data);
|
||||||
|
if (!parseResult.Success)
|
||||||
|
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, new ServerError(data));
|
||||||
|
var error = await TryParseError(parseResult.Data);
|
||||||
|
if(error != null)
|
||||||
|
return WebCallResult<T>.CreateErrorResult(response.StatusCode, response.ResponseHeaders, error);
|
||||||
|
|
||||||
|
var deserializeResult = Deserialize<T>(parseResult.Data);
|
||||||
|
return new WebCallResult<T>(response.StatusCode, response.ResponseHeaders, deserializeResult.Data, deserializeResult.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var desResult = await Deserialize<T>(responseStream).ConfigureAwait(false);
|
||||||
|
responseStream.Close();
|
||||||
|
response.Close();
|
||||||
|
|
||||||
|
return new WebCallResult<T>(statusCode, headers, desResult.Data, desResult.Error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
using var reader = new StreamReader(responseStream);
|
using var reader = new StreamReader(responseStream);
|
||||||
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
|
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||||
|
log.Write(LogVerbosity.Debug, $"Error received: {data}");
|
||||||
responseStream.Close();
|
responseStream.Close();
|
||||||
response.Close();
|
response.Close();
|
||||||
var parseResult = ValidateJson(data);
|
var parseResult = ValidateJson(data);
|
||||||
@ -243,6 +278,17 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be used to parse an error even though response status indicates success. Some apis always return 200 OK, even though there is an error.
|
||||||
|
/// This can be used together with ManualParseError to check if it is an error before deserializing to an object
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Received data</param>
|
||||||
|
/// <returns>Null if not an error, Error otherwise</returns>
|
||||||
|
protected virtual Task<ServerError?> TryParseError(JToken data)
|
||||||
|
{
|
||||||
|
return Task.FromResult<ServerError?>(null);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a request object
|
/// Creates a request object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -250,17 +296,19 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="method">The method of the request</param>
|
/// <param name="method">The method of the request</param>
|
||||||
/// <param name="parameters">The parameters of the request</param>
|
/// <param name="parameters">The parameters of the request</param>
|
||||||
/// <param name="signed">Whether or not the request should be authenticated</param>
|
/// <param name="signed">Whether or not the request should be authenticated</param>
|
||||||
|
/// <param name="postPosition">Where the post parameters should be placed</param>
|
||||||
|
/// <param name="arraySerialization">How array paramters should be serialized</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary<string, object>? parameters, bool signed)
|
protected virtual IRequest ConstructRequest(Uri uri, HttpMethod method, Dictionary<string, object>? parameters, bool signed, PostParameters postPosition, ArrayParametersSerialization arraySerialization)
|
||||||
{
|
{
|
||||||
if (parameters == null)
|
if (parameters == null)
|
||||||
parameters = new Dictionary<string, object>();
|
parameters = new Dictionary<string, object>();
|
||||||
|
|
||||||
var uriString = uri.ToString();
|
var uriString = uri.ToString();
|
||||||
if(authProvider != null)
|
if(authProvider != null)
|
||||||
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed);
|
parameters = authProvider.AddAuthenticationToParameters(uriString, method, parameters, signed, postPosition, arraySerialization);
|
||||||
|
|
||||||
if((method == HttpMethod.Get || method == HttpMethod.Delete || postParametersPosition == PostParameters.InUri) && parameters?.Any() == true)
|
if((method == HttpMethod.Get || method == HttpMethod.Delete || postPosition == PostParameters.InUri) && parameters?.Any() == true)
|
||||||
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
uriString += "?" + parameters.CreateParamString(true, arraySerialization);
|
||||||
|
|
||||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||||
@ -269,17 +317,17 @@ namespace CryptoExchange.Net
|
|||||||
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
if (authProvider != null)
|
if (authProvider != null)
|
||||||
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed);
|
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, postPosition, arraySerialization);
|
||||||
|
|
||||||
foreach (var header in headers)
|
foreach (var header in headers)
|
||||||
request.AddHeader(header.Key, header.Value);
|
request.AddHeader(header.Key, header.Value);
|
||||||
|
|
||||||
if ((method == HttpMethod.Post || method == HttpMethod.Put) && postParametersPosition != PostParameters.InUri)
|
if ((method == HttpMethod.Post || method == HttpMethod.Put) && postPosition != PostParameters.InUri)
|
||||||
{
|
{
|
||||||
if(parameters?.Any() == true)
|
if(parameters?.Any() == true)
|
||||||
WriteParamBody(request, parameters, contentType);
|
WriteParamBody(request, parameters, contentType);
|
||||||
else
|
else
|
||||||
request.SetContent("{}", contentType);
|
request.SetContent(requestBodyEmptyContent, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
return request;
|
return request;
|
||||||
@ -302,7 +350,16 @@ namespace CryptoExchange.Net
|
|||||||
{
|
{
|
||||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||||
foreach (var kvp in parameters.OrderBy(p => p.Key))
|
foreach (var kvp in parameters.OrderBy(p => p.Key))
|
||||||
formData.Add(kvp.Key, kvp.Value.ToString());
|
{
|
||||||
|
if (kvp.Value.GetType().IsArray)
|
||||||
|
{
|
||||||
|
var array = (Array)kvp.Value;
|
||||||
|
foreach(var value in array)
|
||||||
|
formData.Add(kvp.Key, value.ToString());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
formData.Add(kvp.Key, kvp.Value.ToString());
|
||||||
|
}
|
||||||
var stringData = formData.ToString();
|
var stringData = formData.ToString();
|
||||||
request.SetContent(stringData, contentType);
|
request.SetContent(stringData, contentType);
|
||||||
}
|
}
|
||||||
|
@ -579,7 +579,7 @@ namespace CryptoExchange.Net
|
|||||||
periodicEvent?.Set();
|
periodicEvent?.Set();
|
||||||
periodicEvent?.Dispose();
|
periodicEvent?.Dispose();
|
||||||
log.Write(LogVerbosity.Debug, "Disposing socket client, closing all subscriptions");
|
log.Write(LogVerbosity.Debug, "Disposing socket client, closing all subscriptions");
|
||||||
UnsubscribeAll().Wait();
|
Task.Run(UnsubscribeAll).Wait();
|
||||||
semaphoreSlim?.Dispose();
|
semaphoreSlim?.Dispose();
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
@ -136,9 +136,16 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private void ProcessMessage(string data)
|
private void ProcessMessage(string data)
|
||||||
{
|
{
|
||||||
log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data);
|
log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data);
|
||||||
|
if (string.IsNullOrEmpty(data)) return;
|
||||||
|
|
||||||
var tokenData = data.ToJToken(log);
|
var tokenData = data.ToJToken(log);
|
||||||
if (tokenData == null)
|
if (tokenData == null)
|
||||||
return;
|
{
|
||||||
|
data = $"\"{data}\"";
|
||||||
|
tokenData = data.ToJToken(log);
|
||||||
|
if (tokenData == null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var handledResponse = false;
|
var handledResponse = false;
|
||||||
foreach (var pendingRequest in pendingRequests.ToList())
|
foreach (var pendingRequest in pendingRequests.ToList())
|
||||||
|
21
README.md
21
README.md
@ -194,6 +194,27 @@ The order book will automatically reconnect when the connection is lost and resy
|
|||||||
To stop synchronizing an order book use the `Stop` method.
|
To stop synchronizing an order book use the `Stop` method.
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 3.0.11 - 20 Jun 2020
|
||||||
|
* Added support for checksum in SymbolOrderBook
|
||||||
|
|
||||||
|
* Version 3.0.10 - 16 Jun 2020
|
||||||
|
* Fix for order book synchronization
|
||||||
|
|
||||||
|
* Version 3.0.9 - 07 Jun 2020
|
||||||
|
* Added arraySerialization and postParameterPosition to AuthenticationProvider interface
|
||||||
|
* Fixed array serialization in request body
|
||||||
|
|
||||||
|
* Version 3.0.8 - 02 Jun 2020
|
||||||
|
* Added requestBodyEmptyContent setting for rest client
|
||||||
|
* Added TryParseError for rest implementations to check for error with success status code
|
||||||
|
|
||||||
|
* Version 3.0.7 - 20 May 2020
|
||||||
|
* Added error debug output
|
||||||
|
* Fix for unsubscribe causing possible deadlock
|
||||||
|
|
||||||
|
* Version 3.0.6 - 03 Mar 2020
|
||||||
|
* Added BestOffer to SymbolOrderBook, removed invalid check on proxy
|
||||||
|
|
||||||
* Version 3.0.5 - 05 Feb 2020
|
* Version 3.0.5 - 05 Feb 2020
|
||||||
* Added PausedActivity events on socket subscriptions
|
* Added PausedActivity events on socket subscriptions
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user