mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-10 09:26:22 +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 CryptoExchange.Net.Authentication;
|
||||
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)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace CryptoExchange.Net.Authentication
|
||||
@ -29,8 +30,11 @@ namespace CryptoExchange.Net.Authentication
|
||||
/// <param name="method"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="signed"></param>
|
||||
/// <param name="postParameterPosition"></param>
|
||||
/// <param name="arraySerialization"></param>
|
||||
/// <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;
|
||||
}
|
||||
@ -42,8 +46,11 @@ namespace CryptoExchange.Net.Authentication
|
||||
/// <param name="method"></param>
|
||||
/// <param name="parameters"></param>
|
||||
/// <param name="signed"></param>
|
||||
/// <param name="postParameterPosition"></param>
|
||||
/// <param name="arraySerialization"></param>
|
||||
/// <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>();
|
||||
}
|
||||
|
@ -6,12 +6,12 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>A base package for implementing cryptocurrency exchange API's</Description>
|
||||
<PackageVersion>3.0.5</PackageVersion>
|
||||
<PackageVersion>3.0.11</PackageVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
|
||||
<NeutralLanguage>en</NeutralLanguage>
|
||||
<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>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
@ -97,7 +97,7 @@
|
||||
</summary>
|
||||
<param name="credentials"></param>
|
||||
</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>
|
||||
Add authentication to the parameter list
|
||||
</summary>
|
||||
@ -105,9 +105,11 @@
|
||||
<param name="method"></param>
|
||||
<param name="parameters"></param>
|
||||
<param name="signed"></param>
|
||||
<param name="postParameterPosition"></param>
|
||||
<param name="arraySerialization"></param>
|
||||
<returns></returns>
|
||||
</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>
|
||||
Add authentication to the header dictionary
|
||||
</summary>
|
||||
@ -115,6 +117,8 @@
|
||||
<param name="method"></param>
|
||||
<param name="parameters"></param>
|
||||
<param name="signed"></param>
|
||||
<param name="postParameterPosition"></param>
|
||||
<param name="arraySerialization"></param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<member name="M:CryptoExchange.Net.Authentication.AuthenticationProvider.Sign(System.String)">
|
||||
@ -837,6 +841,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
|
||||
@ -1144,7 +1153,6 @@
|
||||
<param name="port">The proxy port</param>
|
||||
</member>
|
||||
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.String)">
|
||||
<inheritdoc />
|
||||
<summary>
|
||||
Create new settings for a proxy
|
||||
</summary>
|
||||
@ -1154,7 +1162,6 @@
|
||||
<param name="password">The proxy password</param>
|
||||
</member>
|
||||
<member name="M:CryptoExchange.Net.Objects.ApiProxy.#ctor(System.String,System.Int32,System.String,System.Security.SecureString)">
|
||||
<inheritdoc />
|
||||
<summary>
|
||||
Create new settings for a proxy
|
||||
</summary>
|
||||
@ -1326,6 +1333,11 @@
|
||||
Connecting
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:CryptoExchange.Net.Objects.OrderBookStatus.Reconnecting">
|
||||
<summary>
|
||||
Reconnecting
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:CryptoExchange.Net.Objects.OrderBookStatus.Syncing">
|
||||
<summary>
|
||||
Syncing data
|
||||
@ -1538,11 +1550,20 @@
|
||||
Whether each update should have a consecutive id number. Used to identify and reconnect when numbers are skipped.
|
||||
</summary>
|
||||
</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>
|
||||
<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="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 name="M:CryptoExchange.Net.Objects.OrderBookOptions.ToString">
|
||||
<inheritdoc />
|
||||
@ -1659,41 +1680,6 @@
|
||||
<member name="M:CryptoExchange.Net.Objects.SocketClientOptions.ToString">
|
||||
<inheritdoc />
|
||||
</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">
|
||||
<summary>
|
||||
Buffer entry with a first and last update id
|
||||
@ -1754,6 +1740,11 @@
|
||||
If order book is set
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
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
|
||||
</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
|
||||
@ -1867,6 +1863,13 @@
|
||||
</summary>
|
||||
<returns></returns>
|
||||
</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})">
|
||||
<summary>
|
||||
Set the initial data for the order book
|
||||
@ -1883,6 +1886,12 @@
|
||||
<param name="bids"></param>
|
||||
<param name="asks"></param>
|
||||
</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})">
|
||||
<summary>
|
||||
Update the order book using a first/last update id
|
||||
@ -2104,11 +2113,21 @@
|
||||
Request body content type
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
How to serialize array parameters
|
||||
</summary>
|
||||
</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">
|
||||
<summary>
|
||||
Timeout for requests
|
||||
@ -2159,7 +2178,7 @@
|
||||
</summary>
|
||||
<returns>The roundtrip time of the ping request</returns>
|
||||
</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>
|
||||
Execute a request
|
||||
</summary>
|
||||
@ -2169,7 +2188,9 @@
|
||||
<param name="cancellationToken">Cancellation token</param>
|
||||
<param name="parameters">The parameters of the request</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>
|
||||
</member>
|
||||
<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>
|
||||
<returns></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)">
|
||||
<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>
|
||||
Creates a request object
|
||||
</summary>
|
||||
@ -2188,6 +2217,8 @@
|
||||
<param name="method">The method 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="postPosition">Where the post parameters should be placed</param>
|
||||
<param name="arraySerialization">How array paramters should be serialized</param>
|
||||
<returns></returns>
|
||||
</member>
|
||||
<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})">
|
||||
<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>
|
||||
|
@ -7,6 +7,7 @@ using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using CryptoExchange.Net.Logging;
|
||||
using CryptoExchange.Net.Objects;
|
||||
using Newtonsoft.Json;
|
||||
@ -79,16 +80,16 @@ namespace CryptoExchange.Net
|
||||
foreach (var arrayEntry in arraysParameters)
|
||||
{
|
||||
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
|
||||
{
|
||||
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 += $"{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('&');
|
||||
return uriString;
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Security;
|
||||
using System.Security;
|
||||
|
||||
namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
@ -36,7 +35,6 @@ namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create new settings for a proxy
|
||||
/// </summary>
|
||||
@ -48,7 +46,6 @@ namespace CryptoExchange.Net.Objects
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create new settings for a proxy
|
||||
/// </summary>
|
||||
@ -58,9 +55,6 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <param name="password">The proxy password</param>
|
||||
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;
|
||||
Port = port;
|
||||
Login = login;
|
||||
|
@ -59,6 +59,10 @@
|
||||
/// </summary>
|
||||
Connecting,
|
||||
/// <summary>
|
||||
/// Reconnecting
|
||||
/// </summary>
|
||||
Reconnecting,
|
||||
/// <summary>
|
||||
/// Syncing data
|
||||
/// </summary>
|
||||
Syncing,
|
||||
|
@ -44,20 +44,30 @@ namespace CryptoExchange.Net.Objects
|
||||
/// </summary>
|
||||
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>
|
||||
/// <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>
|
||||
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;
|
||||
SequenceNumbersAreConsecutive = sequencesAreConsecutive;
|
||||
StrictLevels = strictLevels;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
{
|
||||
/// <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>
|
||||
/// Buffer entry with a first and last update id
|
||||
/// </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.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CryptoExchange.Net.Interfaces;
|
||||
using CryptoExchange.Net.Logging;
|
||||
@ -19,8 +21,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <summary>
|
||||
/// The process buffer, used while syncing
|
||||
/// </summary>
|
||||
protected readonly List<object> processBuffer;
|
||||
private readonly object bookLock = new object();
|
||||
protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
|
||||
/// <summary>
|
||||
/// The ask list
|
||||
/// </summary>
|
||||
@ -28,11 +29,18 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <summary>
|
||||
/// The bid list
|
||||
/// </summary>
|
||||
|
||||
protected SortedList<decimal, ISymbolOrderBookEntry> bids;
|
||||
|
||||
private readonly object bookLock = new object();
|
||||
|
||||
private OrderBookStatus status;
|
||||
private UpdateSubscription? subscription;
|
||||
private readonly bool sequencesAreConsecutive;
|
||||
private readonly bool strictLevels;
|
||||
|
||||
private Task _processTask;
|
||||
private AutoResetEvent _queueEvent;
|
||||
private ConcurrentQueue<object> _processQueue;
|
||||
|
||||
/// <summary>
|
||||
/// Order book implementation id
|
||||
@ -48,6 +56,11 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// </summary>
|
||||
protected bool bookSet;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of levels for this book
|
||||
/// </summary>
|
||||
protected int? Levels { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// The status of the order book. Order book is up to date when the status is `Synced`
|
||||
/// </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>
|
||||
/// The best bid currently in the order book
|
||||
/// </summary>
|
||||
@ -135,7 +156,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
get
|
||||
{
|
||||
lock (bookLock)
|
||||
return bids.FirstOrDefault().Value;
|
||||
return bids.FirstOrDefault().Value ?? emptySymbolOrderBookEntry;
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +168,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,8 +196,12 @@ namespace CryptoExchange.Net.OrderBook
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
|
||||
Id = options.OrderBookName;
|
||||
processBuffer = new List<object>();
|
||||
processBuffer = new List<ProcessBufferRangeSequenceEntry>();
|
||||
_processQueue = new ConcurrentQueue<object>();
|
||||
_queueEvent = new AutoResetEvent(false);
|
||||
|
||||
sequencesAreConsecutive = options.SequenceNumbersAreConsecutive;
|
||||
strictLevels = options.StrictLevels;
|
||||
Symbol = symbol;
|
||||
Status = OrderBookStatus.Disconnected;
|
||||
|
||||
@ -190,7 +225,10 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <returns></returns>
|
||||
public async Task<CallResult<bool>> StartAsync()
|
||||
{
|
||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} starting");
|
||||
Status = OrderBookStatus.Connecting;
|
||||
_processTask = Task.Run(ProcessQueue);
|
||||
|
||||
var startResult = await DoStart().ConfigureAwait(false);
|
||||
if (!startResult)
|
||||
return new CallResult<bool>(false, startResult.Error);
|
||||
@ -205,7 +243,10 @@ namespace CryptoExchange.Net.OrderBook
|
||||
private void Reset()
|
||||
{
|
||||
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();
|
||||
bookSet = false;
|
||||
DoReset();
|
||||
@ -240,7 +281,10 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <returns></returns>
|
||||
public async Task StopAsync()
|
||||
{
|
||||
log.Write(LogVerbosity.Debug, $"{Id} order book {Symbol} stopping");
|
||||
Status = OrderBookStatus.Disconnected;
|
||||
_queueEvent.Set();
|
||||
_processTask.Wait();
|
||||
if(subscription != null)
|
||||
await subscription.Close().ConfigureAwait(false);
|
||||
}
|
||||
@ -263,12 +307,34 @@ namespace CryptoExchange.Net.OrderBook
|
||||
protected abstract Task<CallResult<bool>> DoResync();
|
||||
|
||||
/// <summary>
|
||||
/// Set the initial data for the order book
|
||||
/// Validate a checksum with the current 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)
|
||||
/// <param name="checksum"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual bool DoChecksum(int checksum) => true;
|
||||
|
||||
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)
|
||||
{
|
||||
@ -276,31 +342,88 @@ namespace CryptoExchange.Net.OrderBook
|
||||
return;
|
||||
|
||||
asks.Clear();
|
||||
foreach (var ask in askList)
|
||||
foreach (var ask in item.Asks)
|
||||
asks.Add(ask.Price, ask);
|
||||
bids.Clear();
|
||||
foreach (var bid in bidList)
|
||||
foreach (var bid in item.Bids)
|
||||
bids.Add(bid.Price, bid);
|
||||
|
||||
LastSequenceNumber = orderBookSequenceNumber;
|
||||
LastSequenceNumber = item.EndUpdateId;
|
||||
|
||||
AskCount = asks.Count;
|
||||
BidCount = asks.Count;
|
||||
BidCount = bids.Count;
|
||||
|
||||
bookSet = true;
|
||||
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();
|
||||
OnOrderBookUpdate?.Invoke(bidList, askList);
|
||||
OnOrderBookUpdate?.Invoke(item.Asks, item.Bids);
|
||||
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 ||
|
||||
BestAsk.Price != prevBestAsk.Price || BestAsk.Quantity != prevBestAsk.Quantity)
|
||||
OnBestOffersChanged?.Invoke(BestBid, BestAsk);
|
||||
lock (bookLock)
|
||||
{
|
||||
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>
|
||||
@ -311,31 +434,18 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <param name="asks"></param>
|
||||
protected void UpdateOrderBook(long rangeUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||
{
|
||||
lock (bookLock)
|
||||
{
|
||||
if (Status == OrderBookStatus.Connecting || Status == OrderBookStatus.Disconnected)
|
||||
return;
|
||||
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = rangeUpdateId, EndUpdateId = rangeUpdateId, Asks = asks, Bids = bids });
|
||||
_queueEvent.Set();
|
||||
}
|
||||
|
||||
if (!bookSet)
|
||||
{
|
||||
processBuffer.Add(new ProcessBufferSingleSequenceEntry()
|
||||
{
|
||||
UpdateId = rangeUpdateId,
|
||||
Asks = asks,
|
||||
Bids = bids
|
||||
});
|
||||
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>
|
||||
/// Add a checksum to the process queue
|
||||
/// </summary>
|
||||
/// <param name="checksum"></param>
|
||||
protected void AddChecksum(int checksum)
|
||||
{
|
||||
_processQueue.Enqueue(new ChecksumItem() { Checksum = checksum });
|
||||
_queueEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -347,32 +457,8 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <param name="asks"></param>
|
||||
protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||
{
|
||||
lock (bookLock)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
|
||||
_queueEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -382,43 +468,11 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <param name="asks">List of asks</param>
|
||||
protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
||||
{
|
||||
lock (bookLock)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
private void ProcessUpdates(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
||||
{
|
||||
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}");
|
||||
}
|
||||
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = lowest, EndUpdateId = highest , Asks = asks, Bids = bids });
|
||||
_queueEvent.Set();
|
||||
}
|
||||
|
||||
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)
|
||||
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;
|
||||
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>
|
||||
/// Check and empty the process buffer; see what entries to update the book with
|
||||
/// </summary>
|
||||
@ -468,13 +519,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
|
||||
foreach (var bufferEntry in pbList)
|
||||
{
|
||||
if (bufferEntry is ProcessBufferEntry pbe)
|
||||
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);
|
||||
|
||||
ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
|
||||
processBuffer.Remove(bufferEntry);
|
||||
}
|
||||
}
|
||||
@ -500,7 +545,6 @@ namespace CryptoExchange.Net.OrderBook
|
||||
{
|
||||
// Out of sync
|
||||
log.Write(LogVerbosity.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
|
||||
Status = OrderBookStatus.Connecting;
|
||||
subscription?.Reconnect();
|
||||
return false;
|
||||
}
|
||||
@ -526,7 +570,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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>
|
||||
/// Dispose the order book
|
||||
/// </summary>
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
@ -39,11 +40,21 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
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>
|
||||
/// How to serialize array parameters
|
||||
/// </summary>
|
||||
protected ArrayParametersSerialization arraySerialization = ArrayParametersSerialization.Array;
|
||||
|
||||
/// <summary>
|
||||
/// What request body should be when no data is send
|
||||
/// </summary>
|
||||
protected string requestBodyEmptyContent = "{}";
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for requests
|
||||
/// </summary>
|
||||
@ -153,11 +164,13 @@ namespace CryptoExchange.Net
|
||||
/// <param name="cancellationToken">Cancellation token</param>
|
||||
/// <param name="parameters">The parameters of the request</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>
|
||||
[return: NotNull]
|
||||
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);
|
||||
if (signed && authProvider == null)
|
||||
@ -166,7 +179,7 @@ namespace CryptoExchange.Net
|
||||
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)
|
||||
{
|
||||
var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour);
|
||||
@ -205,16 +218,38 @@ namespace CryptoExchange.Net
|
||||
var responseStream = await response.GetResponseStream().ConfigureAwait(false);
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var desResult = await Deserialize<T>(responseStream).ConfigureAwait(false);
|
||||
responseStream.Close();
|
||||
response.Close();
|
||||
if (manualParseError)
|
||||
{
|
||||
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
|
||||
{
|
||||
using var reader = new StreamReader(responseStream);
|
||||
var data = await reader.ReadToEndAsync().ConfigureAwait(false);
|
||||
log.Write(LogVerbosity.Debug, $"Error received: {data}");
|
||||
responseStream.Close();
|
||||
response.Close();
|
||||
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>
|
||||
/// Creates a request object
|
||||
/// </summary>
|
||||
@ -250,17 +296,19 @@ namespace CryptoExchange.Net
|
||||
/// <param name="method">The method 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="postPosition">Where the post parameters should be placed</param>
|
||||
/// <param name="arraySerialization">How array paramters should be serialized</param>
|
||||
/// <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)
|
||||
parameters = new Dictionary<string, object>();
|
||||
|
||||
var uriString = uri.ToString();
|
||||
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);
|
||||
|
||||
var contentType = requestBodyFormat == RequestBodyFormat.Json ? Constants.JsonContentHeader : Constants.FormContentHeader;
|
||||
@ -269,17 +317,17 @@ namespace CryptoExchange.Net
|
||||
|
||||
var headers = new Dictionary<string, string>();
|
||||
if (authProvider != null)
|
||||
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed);
|
||||
headers = authProvider.AddAuthenticationToHeaders(uriString, method, parameters!, signed, postPosition, arraySerialization);
|
||||
|
||||
foreach (var header in headers)
|
||||
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)
|
||||
WriteParamBody(request, parameters, contentType);
|
||||
else
|
||||
request.SetContent("{}", contentType);
|
||||
request.SetContent(requestBodyEmptyContent, contentType);
|
||||
}
|
||||
|
||||
return request;
|
||||
@ -302,7 +350,16 @@ namespace CryptoExchange.Net
|
||||
{
|
||||
var formData = HttpUtility.ParseQueryString(string.Empty);
|
||||
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();
|
||||
request.SetContent(stringData, contentType);
|
||||
}
|
||||
|
@ -579,7 +579,7 @@ namespace CryptoExchange.Net
|
||||
periodicEvent?.Set();
|
||||
periodicEvent?.Dispose();
|
||||
log.Write(LogVerbosity.Debug, "Disposing socket client, closing all subscriptions");
|
||||
UnsubscribeAll().Wait();
|
||||
Task.Run(UnsubscribeAll).Wait();
|
||||
semaphoreSlim?.Dispose();
|
||||
base.Dispose();
|
||||
}
|
||||
|
@ -136,9 +136,16 @@ namespace CryptoExchange.Net.Sockets
|
||||
private void ProcessMessage(string data)
|
||||
{
|
||||
log.Write(LogVerbosity.Debug, $"Socket {Socket.Id} received data: " + data);
|
||||
if (string.IsNullOrEmpty(data)) return;
|
||||
|
||||
var tokenData = data.ToJToken(log);
|
||||
if (tokenData == null)
|
||||
return;
|
||||
{
|
||||
data = $"\"{data}\"";
|
||||
tokenData = data.ToJToken(log);
|
||||
if (tokenData == null)
|
||||
return;
|
||||
}
|
||||
|
||||
var handledResponse = false;
|
||||
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.
|
||||
|
||||
## 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
|
||||
* Added PausedActivity events on socket subscriptions
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user