mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2025-06-09 17:06:19 +00:00
Documentation
This commit is contained in:
parent
f7445543f2
commit
cb1826da7a
@ -116,7 +116,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
// assert
|
// assert
|
||||||
Assert.IsTrue(client.ClientOptions.BaseAddress == "http://test.address.com/");
|
Assert.IsTrue(client.ClientOptions.BaseAddress == "http://test.address.com/");
|
||||||
Assert.IsTrue(client.ClientOptions.RateLimiters.Count() == 1);
|
Assert.IsTrue(client.ClientOptions.RateLimiters.Count == 1);
|
||||||
Assert.IsTrue(client.ClientOptions.RateLimitingBehaviour == RateLimitingBehaviour.Fail);
|
Assert.IsTrue(client.ClientOptions.RateLimitingBehaviour == RateLimitingBehaviour.Fail);
|
||||||
Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1));
|
Assert.IsTrue(client.ClientOptions.RequestTimeout == TimeSpan.FromMinutes(1));
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ namespace CryptoExchange.Net.Attributes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used for conversion in ArrayConverter
|
/// Used for conversion in ArrayConverter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public class JsonConversionAttribute: Attribute
|
public class JsonConversionAttribute: Attribute
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace CryptoExchange.Net.Attributes
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Marks property as optional
|
|
||||||
/// </summary>
|
|
||||||
public class JsonOptionalPropertyAttribute : Attribute
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ using Newtonsoft.Json.Linq;
|
|||||||
namespace CryptoExchange.Net.Authentication
|
namespace CryptoExchange.Net.Authentication
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Api credentials info
|
/// Api credentials, used to sign requests accessing private endpoints
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ApiCredentials: IDisposable
|
public class ApiCredentials: IDisposable
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,7 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal Log log;
|
protected internal Log log;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The authentication provider
|
/// The authentication provider when api credentials have been provided
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal AuthenticationProvider? authProvider;
|
protected internal AuthenticationProvider? authProvider;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -72,7 +72,6 @@ namespace CryptoExchange.Net
|
|||||||
ClientOptions = options;
|
ClientOptions = options;
|
||||||
|
|
||||||
ExchangeName = exchangeName;
|
ExchangeName = exchangeName;
|
||||||
//BaseAddress = options.BaseAddress;
|
|
||||||
|
|
||||||
log.Write(LogLevel.Debug, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}");
|
log.Write(LogLevel.Debug, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}");
|
||||||
}
|
}
|
||||||
@ -88,7 +87,7 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to parse the json data and returns a JToken, validating the input not being empty and being valid json
|
/// Tries to parse the json data and return a JToken, validating the input not being empty and being valid json
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data">The data to parse</param>
|
/// <param name="data">The data to parse</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
@ -181,6 +181,7 @@ namespace CryptoExchange.Net.Converters
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mark property as an index in the array
|
/// Mark property as an index in the array
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
public class ArrayPropertyAttribute: Attribute
|
public class ArrayPropertyAttribute: Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -75,7 +75,7 @@ namespace CryptoExchange.Net.Converters
|
|||||||
|
|
||||||
private bool GetValue(string value, out T result)
|
private bool GetValue(string value, out T result)
|
||||||
{
|
{
|
||||||
//check for exact match first, then if not found fallback to a case insensitive match
|
// Check for exact match first, then if not found fallback to a case insensitive match
|
||||||
var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
|
var mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCulture));
|
||||||
if(mapping.Equals(default(KeyValuePair<T, string>)))
|
if(mapping.Equals(default(KeyValuePair<T, string>)))
|
||||||
mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
|||||||
namespace CryptoExchange.Net.Converters
|
namespace CryptoExchange.Net.Converters
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// converter for milliseconds to datetime
|
/// Converter for milliseconds to datetime
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimestampConverter : JsonConverter
|
public class TimestampConverter : JsonConverter
|
||||||
{
|
{
|
||||||
|
@ -18,7 +18,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
IRequestFactory RequestFactory { get; set; }
|
IRequestFactory RequestFactory { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total amount of requests made
|
/// The total amount of requests made with this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int TotalRequestsMade { get; }
|
int TotalRequestsMade { get; }
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
void RemoveRateLimiters();
|
void RemoveRateLimiters();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client options
|
/// The options provided for this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
RestClientOptions ClientOptions { get; }
|
RestClientOptions ClientOptions { get; }
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
public interface ISocketClient: IDisposable
|
public interface ISocketClient: IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client options
|
/// The options provided for this client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SocketClientOptions ClientOptions { get; }
|
SocketClientOptions ClientOptions { get; }
|
||||||
|
|
||||||
@ -20,6 +20,13 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double IncomingKbps { get; }
|
public double IncomingKbps { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Unsubscribe from a stream using the subscription id received when starting the subscription
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="subscriptionId">The id of the subscription to unsubscribe</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task UnsubscribeAsync(int subscriptionId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unsubscribe from a stream
|
/// Unsubscribe from a stream
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -7,41 +7,41 @@ using System.Threading.Tasks;
|
|||||||
namespace CryptoExchange.Net.Interfaces
|
namespace CryptoExchange.Net.Interfaces
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for websocket interaction
|
/// Webscoket connection interface
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IWebsocket: IDisposable
|
public interface IWebsocket: IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Websocket closed
|
/// Websocket closed event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action OnClose;
|
event Action OnClose;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Websocket message received
|
/// Websocket message received event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<string> OnMessage;
|
event Action<string> OnMessage;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Websocket error
|
/// Websocket error event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<Exception> OnError;
|
event Action<Exception> OnError;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Websocket opened
|
/// Websocket opened event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action OnOpen;
|
event Action OnOpen;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Id
|
/// Unique id for this socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int Id { get; }
|
int Id { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Origin
|
/// Origin header
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string? Origin { get; set; }
|
string? Origin { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encoding to use
|
/// Encoding to use for sending/receiving string data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Encoding? Encoding { get; set; }
|
Encoding? Encoding { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reconnecting
|
/// Whether socket is in the process of reconnecting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool Reconnecting { get; set; }
|
bool Reconnecting { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -61,15 +61,15 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Func<string, string>? DataInterpreterString { get; set; }
|
Func<string, string>? DataInterpreterString { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Socket url
|
/// The url the socket connects to
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Url { get; }
|
string Url { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is closed
|
/// Whether the socket connection is closed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsClosed { get; }
|
bool IsClosed { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is open
|
/// Whether the socket connection is open
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsOpen { get; }
|
bool IsOpen { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -77,10 +77,15 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
SslProtocols SSLProtocols { get; set; }
|
SslProtocols SSLProtocols { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Timeout
|
/// The max time for no data being received before the connection is considered lost
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TimeSpan Timeout { get; set; }
|
TimeSpan Timeout { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Set a proxy to use when connecting
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="proxy"></param>
|
||||||
|
void SetProxy(ApiProxy proxy);
|
||||||
|
/// <summary>
|
||||||
/// Connect the socket
|
/// Connect the socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
@ -91,18 +96,13 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
void Send(string data);
|
void Send(string data);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reset socket
|
/// Reset socket when a connection is lost to prepare for a new connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
void Reset();
|
void Reset();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Close the connecting
|
/// Close the connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task CloseAsync();
|
Task CloseAsync();
|
||||||
/// <summary>
|
|
||||||
/// Set proxy
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="proxy"></param>
|
|
||||||
void SetProxy(ApiProxy proxy);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,17 +11,17 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a websocket for an url
|
/// Create a websocket for an url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="log"></param>
|
/// <param name="log">The logger</param>
|
||||||
/// <param name="url"></param>
|
/// <param name="url">The url the socket is fo</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IWebsocket CreateWebsocket(Log log, string url);
|
IWebsocket CreateWebsocket(Log log, string url);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a websocket for an url
|
/// Create a websocket for an url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="log"></param>
|
/// <param name="log">The logger</param>
|
||||||
/// <param name="url"></param>
|
/// <param name="url">The url the socket is fo</param>
|
||||||
/// <param name="cookies"></param>
|
/// <param name="cookies">Cookies to be send in the initial request</param>
|
||||||
/// <param name="headers"></param>
|
/// <param name="headers">Headers to be send in the initial request</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers);
|
IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ using System;
|
|||||||
namespace CryptoExchange.Net.Logging
|
namespace CryptoExchange.Net.Logging
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Log to console
|
/// ILogger implementation for logging to the console
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ConsoleLogger : ILogger
|
public class ConsoleLogger : ILogger
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@ using System.Diagnostics;
|
|||||||
namespace CryptoExchange.Net.Logging
|
namespace CryptoExchange.Net.Logging
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default log writer, writes to debug
|
/// Default log writer, uses Trace.WriteLine
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DebugLogger: ILogger
|
public class DebugLogger: ILogger
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
public class BaseOptions
|
public class BaseOptions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimum log level to output. Setting it to null will send all messages to the registered ILoggers.
|
/// The minimum log level to output
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public LogLevel LogLevel { get; set; } = LogLevel.Information;
|
public LogLevel LogLevel { get; set; } = LogLevel.Information;
|
||||||
|
|
||||||
@ -86,12 +86,12 @@ namespace CryptoExchange.Net.Objects
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The api credentials
|
/// The api credentials used for signing requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiCredentials? ApiCredentials { get; set; }
|
public ApiCredentials? ApiCredentials { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proxy to use
|
/// Proxy to use when connecting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ApiProxy? Proxy { get; set; }
|
public ApiProxy? Proxy { get; set; }
|
||||||
|
|
||||||
@ -161,7 +161,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{base.ToString()}, RateLimiters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, RequestTimeout: {RequestTimeout:c}";
|
return $"{base.ToString()}, RateLimiters: {RateLimiters.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, RequestTimeout: {RequestTimeout:c}, HttpClient: {(HttpClient == null ? "-": "set")}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
public TimeSpan ReconnectInterval { get; set; } = TimeSpan.FromSeconds(5);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum number of times to try to reconnect
|
/// The maximum number of times to try to reconnect, default null will retry indefinitely
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? MaxReconnectTries { get; set; }
|
public int? MaxReconnectTries { get; set; }
|
||||||
|
|
||||||
@ -196,11 +196,13 @@ namespace CryptoExchange.Net.Objects
|
|||||||
public int MaxConcurrentResubscriptionsPerSocket { get; set; } = 5;
|
public int MaxConcurrentResubscriptionsPerSocket { get; set; } = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time to wait for a socket response before giving a timeout
|
/// The max time to wait for a response after sending a request on the socket before giving a timeout
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan SocketResponseTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
public TimeSpan SocketResponseTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time after which the connection is assumed to be dropped. This can only be used for socket connections where a steady flow of data is expected.
|
/// The max time of not receiving any data after which the connection is assumed to be dropped. This can only be used for socket connections where a steady flow of data is expected,
|
||||||
|
/// for example when the server sends intermittent ping requests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan SocketNoDataTimeout { get; set; }
|
public TimeSpan SocketNoDataTimeout { get; set; }
|
||||||
|
|
||||||
@ -234,7 +236,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
|
return $"{base.ToString()}, AutoReconnect: {AutoReconnect}, ReconnectInterval: {ReconnectInterval}, MaxReconnectTries: {MaxReconnectTries}, MaxResubscribeTries: {MaxResubscribeTries}, MaxConcurrentResubscriptionsPerSocket: {MaxConcurrentResubscriptionsPerSocket}, SocketResponseTimeout: {SocketResponseTimeout:c}, SocketNoDataTimeout: {SocketNoDataTimeout}, SocketSubscriptionsCombineTarget: {SocketSubscriptionsCombineTarget}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,19 +10,19 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
public class ProcessBufferRangeSequenceEntry
|
public class ProcessBufferRangeSequenceEntry
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// First update id
|
/// First sequence number in this update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long FirstUpdateId { get; set; }
|
public long FirstUpdateId { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Last update id
|
/// Last sequence number in this update
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public long LastUpdateId { get; set; }
|
public long LastUpdateId { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of asks
|
/// List of changed/new asks
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of bids
|
/// List of changed/new bids
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
||||||
}
|
}
|
||||||
|
@ -18,52 +18,64 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
|
public abstract class SymbolOrderBook : ISymbolOrderBook, IDisposable
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The process buffer, used while syncing
|
|
||||||
/// </summary>
|
|
||||||
protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
|
|
||||||
/// <summary>
|
|
||||||
/// The ask list
|
|
||||||
/// </summary>
|
|
||||||
protected SortedList<decimal, ISymbolOrderBookEntry> asks;
|
|
||||||
/// <summary>
|
|
||||||
/// The bid list
|
|
||||||
/// </summary>
|
|
||||||
protected SortedList<decimal, ISymbolOrderBookEntry> bids;
|
|
||||||
|
|
||||||
private readonly object bookLock = new object();
|
private readonly object bookLock = new object();
|
||||||
|
|
||||||
private OrderBookStatus status;
|
private OrderBookStatus status;
|
||||||
private UpdateSubscription? subscription;
|
private UpdateSubscription? subscription;
|
||||||
|
|
||||||
private readonly bool validateChecksum;
|
|
||||||
|
|
||||||
private bool _stopProcessing;
|
private bool _stopProcessing;
|
||||||
private Task? _processTask;
|
private Task? _processTask;
|
||||||
|
|
||||||
private readonly AutoResetEvent _queueEvent;
|
private readonly AutoResetEvent _queueEvent;
|
||||||
private readonly ConcurrentQueue<object> _processQueue;
|
private readonly ConcurrentQueue<object> _processQueue;
|
||||||
|
private readonly bool validateChecksum;
|
||||||
|
|
||||||
|
private class EmptySymbolOrderBookEntry : ISymbolOrderBookEntry
|
||||||
|
{
|
||||||
|
public decimal Quantity { get { return 0m; } set {; } }
|
||||||
|
public decimal Price { get { return 0m; } set {; } }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry();
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Order book implementation id
|
/// A buffer to store messages received before the initial book snapshot is processed. These messages
|
||||||
|
/// will be processed after the book snapshot is set. Any messages in this buffer with sequence numbers lower
|
||||||
|
/// than the snapshot sequence number will be discarded
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Id { get; }
|
protected readonly List<ProcessBufferRangeSequenceEntry> processBuffer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ask list, should only be accessed using the bookLock
|
||||||
|
/// </summary>
|
||||||
|
protected SortedList<decimal, ISymbolOrderBookEntry> asks;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The bid list, should only be accessed using the bookLock
|
||||||
|
/// </summary>
|
||||||
|
protected SortedList<decimal, ISymbolOrderBookEntry> bids;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The log
|
/// The log
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Log log;
|
protected Log log;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether update numbers are consecutive
|
/// Whether update numbers are consecutive. If set to true and an update comes in which isn't the previous sequences number + 1
|
||||||
|
/// the book will resynchronize as it is deemed out of sync
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool sequencesAreConsecutive;
|
protected bool sequencesAreConsecutive;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether levels should be strictly enforced
|
/// Whether levels should be strictly enforced. For example, when an order book has 25 levels and a new update comes in which pushes
|
||||||
|
/// the current level 25 ask out of the top 25, should the curent the level 26 entry be removed from the book or does the
|
||||||
|
/// server handle this
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool strictLevels;
|
protected bool strictLevels;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If order book is set
|
/// If the initial snapshot of the book has been set
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool bookSet;
|
protected bool bookSet;
|
||||||
|
|
||||||
@ -72,9 +84,10 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected int? Levels { get; set; } = null;
|
protected int? Levels { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The status of the order book. Order book is up to date when the status is `Synced`
|
public string Id { get; }
|
||||||
/// </summary>
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public OrderBookStatus Status
|
public OrderBookStatus Status
|
||||||
{
|
{
|
||||||
get => status;
|
get => status;
|
||||||
@ -90,46 +103,31 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Last update identifier
|
|
||||||
/// </summary>
|
|
||||||
public long LastSequenceNumber { get; private set; }
|
public long LastSequenceNumber { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// The symbol of the order book
|
/// <inheritdoc/>
|
||||||
/// </summary>
|
|
||||||
public string Symbol { get; }
|
public string Symbol { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Event when the state changes
|
|
||||||
/// </summary>
|
|
||||||
public event Action<OrderBookStatus, OrderBookStatus>? OnStatusChange;
|
public event Action<OrderBookStatus, OrderBookStatus>? OnStatusChange;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Event when the BestBid or BestAsk changes ie a Pricing Tick
|
|
||||||
/// </summary>
|
|
||||||
public event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)>? OnBestOffersChanged;
|
public event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)>? OnBestOffersChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Event when order book was updated, containing the changed bids and asks. Be careful! It can generate a lot of events at high-liquidity markets
|
|
||||||
/// </summary>
|
|
||||||
public event Action<(IEnumerable<ISymbolOrderBookEntry> Bids, IEnumerable<ISymbolOrderBookEntry> Asks)>? OnOrderBookUpdate;
|
public event Action<(IEnumerable<ISymbolOrderBookEntry> Bids, IEnumerable<ISymbolOrderBookEntry> Asks)>? OnOrderBookUpdate;
|
||||||
/// <summary>
|
|
||||||
/// Timestamp of the last update
|
/// <inheritdoc/>
|
||||||
/// </summary>
|
|
||||||
public DateTime UpdateTime { get; private set; }
|
public DateTime UpdateTime { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The number of asks in the book
|
|
||||||
/// </summary>
|
|
||||||
public int AskCount { get; private set; }
|
public int AskCount { get; private set; }
|
||||||
/// <summary>
|
|
||||||
/// The number of bids in the book
|
/// <inheritdoc/>
|
||||||
/// </summary>
|
|
||||||
public int BidCount { get; private set; }
|
public int BidCount { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The list of asks
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Asks
|
public IEnumerable<ISymbolOrderBookEntry> Asks
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -139,9 +137,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The list of bids
|
|
||||||
/// </summary>
|
|
||||||
public IEnumerable<ISymbolOrderBookEntry> Bids
|
public IEnumerable<ISymbolOrderBookEntry> Bids
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -151,9 +147,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Get a snapshot of the book at this moment
|
|
||||||
/// </summary>
|
|
||||||
public (IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks) Book
|
public (IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks) Book
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -163,17 +157,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class EmptySymbolOrderBookEntry : ISymbolOrderBookEntry
|
/// <inheritdoc/>
|
||||||
{
|
|
||||||
public decimal Quantity { get { return 0m; } set {; } }
|
|
||||||
public decimal Price { get { return 0m; } set {; } }
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly ISymbolOrderBookEntry emptySymbolOrderBookEntry = new EmptySymbolOrderBookEntry();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The best bid currently in the order book
|
|
||||||
/// </summary>
|
|
||||||
public ISymbolOrderBookEntry BestBid
|
public ISymbolOrderBookEntry BestBid
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -183,9 +167,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// The best ask currently in the order book
|
|
||||||
/// </summary>
|
|
||||||
public ISymbolOrderBookEntry BestAsk
|
public ISymbolOrderBookEntry BestAsk
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -195,9 +177,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// BestBid/BesAsk returned as a pair
|
|
||||||
/// </summary>
|
|
||||||
public (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers {
|
public (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers {
|
||||||
get {
|
get {
|
||||||
lock (bookLock)
|
lock (bookLock)
|
||||||
@ -208,9 +188,9 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id">The id of the order book. Should be set to {Exchange}[{type}], for example: Kucoin[Spot]</param>
|
||||||
/// <param name="symbol"></param>
|
/// <param name="symbol">The symbol the order book is for</param>
|
||||||
/// <param name="options"></param>
|
/// <param name="options">The options for the order book</param>
|
||||||
protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
|
protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
|
||||||
{
|
{
|
||||||
if (symbol == null)
|
if (symbol == null)
|
||||||
@ -236,10 +216,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
log.UpdateWriters(writers.ToList());
|
log.UpdateWriters(writers.ToList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Start connecting and synchronizing the order book
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task<CallResult<bool>> StartAsync()
|
public async Task<CallResult<bool>> StartAsync()
|
||||||
{
|
{
|
||||||
if (Status != OrderBookStatus.Disconnected)
|
if (Status != OrderBookStatus.Disconnected)
|
||||||
@ -275,13 +252,21 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
return new CallResult<bool>(true, null);
|
return new CallResult<bool>(true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Get the average price that a market order would fill at at the current order book state. This is no guarentee that an order of that quantity would actually be filled
|
public async Task StopAsync()
|
||||||
/// at that price since between this calculation and the order placement the book can have changed.
|
{
|
||||||
/// </summary>
|
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopping");
|
||||||
/// <param name="quantity">The quantity in base asset to fill</param>
|
Status = OrderBookStatus.Disconnected;
|
||||||
/// <param name="type">The type</param>
|
_queueEvent.Set();
|
||||||
/// <returns>Average fill price</returns>
|
if (_processTask != null)
|
||||||
|
await _processTask.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (subscription != null)
|
||||||
|
await subscription.CloseAsync().ConfigureAwait(false);
|
||||||
|
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
|
public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
|
||||||
{
|
{
|
||||||
if (Status != OrderBookStatus.Synced)
|
if (Status != OrderBookStatus.Synced)
|
||||||
@ -312,6 +297,219 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8), null);
|
return new CallResult<decimal>(Math.Round(totalCost / totalAmount, 8), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation for starting the order book. Should typically have logic for subscribing to the update stream and retrieving
|
||||||
|
/// and setting the initial order book
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract Task<CallResult<UpdateSubscription>> DoStartAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reset the order book
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void DoReset() { }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resync the order book
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected abstract Task<CallResult<bool>> DoResyncAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implementation for validating a checksum value with the current order book. If checksum validation fails (returns false)
|
||||||
|
/// the order book will be resynchronized
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="checksum"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected virtual bool DoChecksum(int checksum) => true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the initial data for the order book. Typically the snapshot which was requested from the Rest API, or the first snapshot
|
||||||
|
/// received from a socket subcription
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="orderBookSequenceNumber">The last update sequence number until which the snapshot is in sync</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)
|
||||||
|
{
|
||||||
|
_processQueue.Enqueue(new InitialOrderBookItem { StartUpdateId = orderBookSequenceNumber, EndUpdateId = orderBookSequenceNumber, Asks = askList, Bids = bidList });
|
||||||
|
_queueEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an update to the process queue. Updates the book by providing changed bids and asks, along with an update number which should be higher than the previous update numbers
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="updateId">The sequence number</param>
|
||||||
|
/// <param name="bids">List of updated/new bids</param>
|
||||||
|
/// <param name="asks">List of updated/new asks</param>
|
||||||
|
protected void UpdateOrderBook(long updateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
|
{
|
||||||
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = updateId, EndUpdateId = updateId, Asks = asks, Bids = bids });
|
||||||
|
_queueEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an update to the process queue. Updates the book by providing changed bids and asks, along with the first and last sequence number in the update
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="firstUpdateId">The sequence number of the first update</param>
|
||||||
|
/// <param name="lastUpdateId">The sequence number of the last update</param>
|
||||||
|
/// <param name="bids">List of updated/new bids</param>
|
||||||
|
/// <param name="asks">List of updated/new asks</param>
|
||||||
|
protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
|
{
|
||||||
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
|
||||||
|
_queueEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add an update to the process queue. Updates the book by providing changed bids and asks, each with its own sequence number
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bids">List of updated/new bids</param>
|
||||||
|
/// <param name="asks">List of updated/new asks</param>
|
||||||
|
protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = lowest, EndUpdateId = highest, Asks = asks, Bids = bids });
|
||||||
|
_queueEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add a checksum value to the process queue
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="checksum">The checksum value</param>
|
||||||
|
protected void AddChecksum(int checksum)
|
||||||
|
{
|
||||||
|
_processQueue.Enqueue(new ChecksumItem() { Checksum = checksum });
|
||||||
|
_queueEvent.Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check and empty the process buffer; see what entries to update the book with
|
||||||
|
/// </summary>
|
||||||
|
protected void CheckProcessBuffer()
|
||||||
|
{
|
||||||
|
var pbList = processBuffer.ToList();
|
||||||
|
if (pbList.Count > 0)
|
||||||
|
log.Write(LogLevel.Debug, "Processing buffered updates");
|
||||||
|
|
||||||
|
foreach (var bufferEntry in pbList)
|
||||||
|
{
|
||||||
|
ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
|
||||||
|
processBuffer.Remove(bufferEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update order book with an entry
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sequence">Sequence number of the update</param>
|
||||||
|
/// <param name="type">Type of entry</param>
|
||||||
|
/// <param name="entry">The entry</param>
|
||||||
|
protected virtual bool ProcessUpdate(long sequence, OrderBookEntryType type, ISymbolOrderBookEntry entry)
|
||||||
|
{
|
||||||
|
if (sequence <= LastSequenceNumber)
|
||||||
|
{
|
||||||
|
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update skipped #{sequence}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequencesAreConsecutive && sequence > LastSequenceNumber + 1)
|
||||||
|
{
|
||||||
|
// Out of sync
|
||||||
|
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
|
||||||
|
_stopProcessing = true;
|
||||||
|
Resubscribe();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTime = DateTime.UtcNow;
|
||||||
|
var listToChange = type == OrderBookEntryType.Ask ? asks : bids;
|
||||||
|
if (entry.Quantity == 0)
|
||||||
|
{
|
||||||
|
if (!listToChange.ContainsKey(entry.Price))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
listToChange.Remove(entry.Price);
|
||||||
|
if (type == OrderBookEntryType.Ask) AskCount--;
|
||||||
|
else BidCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!listToChange.ContainsKey(entry.Price))
|
||||||
|
{
|
||||||
|
listToChange.Add(entry.Price, entry);
|
||||||
|
if (type == OrderBookEntryType.Ask) AskCount++;
|
||||||
|
else BidCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
listToChange[entry.Price] = entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wait until the order book snapshot has been set
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="timeout">Max wait time</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected async Task<CallResult<bool>> WaitForSetOrderBookAsync(int timeout)
|
||||||
|
{
|
||||||
|
var startWait = DateTime.UtcNow;
|
||||||
|
while (!bookSet && Status == OrderBookStatus.Syncing)
|
||||||
|
{
|
||||||
|
if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
|
||||||
|
return new CallResult<bool>(false, new ServerError("Timeout while waiting for data"));
|
||||||
|
|
||||||
|
await Task.Delay(10).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CallResult<bool>(true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dispose the order book
|
||||||
|
/// </summary>
|
||||||
|
public abstract void Dispose();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String representation of the top 3 entries
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String representation of the top x entries
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string ToString(int numberOfEntries)
|
||||||
|
{
|
||||||
|
var result = string.Empty;
|
||||||
|
result += $"Asks ({AskCount}): {Environment.NewLine}";
|
||||||
|
foreach (var entry in Asks.Take(numberOfEntries).Reverse())
|
||||||
|
result += $" {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
|
||||||
|
|
||||||
|
result += $"Bids ({BidCount}): {Environment.NewLine}";
|
||||||
|
foreach (var entry in Bids.Take(numberOfEntries))
|
||||||
|
result += $" {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
private void Reset()
|
private void Reset()
|
||||||
{
|
{
|
||||||
_queueEvent.Set();
|
_queueEvent.Set();
|
||||||
@ -339,50 +537,9 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
Status = OrderBookStatus.Synced;
|
Status = OrderBookStatus.Synced;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stop syncing the order book
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public async Task StopAsync()
|
|
||||||
{
|
|
||||||
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopping");
|
|
||||||
Status = OrderBookStatus.Disconnected;
|
|
||||||
_queueEvent.Set();
|
|
||||||
if(_processTask != null)
|
|
||||||
await _processTask.ConfigureAwait(false);
|
|
||||||
|
|
||||||
if(subscription != null)
|
|
||||||
await subscription.CloseAsync().ConfigureAwait(false);
|
|
||||||
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Start the order book
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected abstract Task<CallResult<UpdateSubscription>> DoStartAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reset the order book
|
|
||||||
/// </summary>
|
|
||||||
protected virtual void DoReset() { }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Resync the order book
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected abstract Task<CallResult<bool>> DoResyncAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate a checksum with the current order book
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="checksum"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected virtual bool DoChecksum(int checksum) => true;
|
|
||||||
|
|
||||||
private void ProcessQueue()
|
private void ProcessQueue()
|
||||||
{
|
{
|
||||||
while(Status != OrderBookStatus.Disconnected)
|
while (Status != OrderBookStatus.Disconnected)
|
||||||
{
|
{
|
||||||
_queueEvent.WaitOne();
|
_queueEvent.WaitOne();
|
||||||
|
|
||||||
@ -462,7 +619,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
_stopProcessing = true;
|
_stopProcessing = true;
|
||||||
Resubscribe();
|
Resubscribe();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnOrderBookUpdate?.Invoke((item.Bids, item.Asks));
|
OnOrderBookUpdate?.Invoke((item.Bids, item.Asks));
|
||||||
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
CheckBestOffersChanged(prevBestBid, prevBestAsk);
|
||||||
@ -476,7 +633,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
{
|
{
|
||||||
if (!validateChecksum)
|
if (!validateChecksum)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool checksumResult = false;
|
bool checksumResult = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -490,7 +647,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!checksumResult)
|
if (!checksumResult)
|
||||||
{
|
{
|
||||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync. Resyncing");
|
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync. Resyncing");
|
||||||
_stopProcessing = true;
|
_stopProcessing = true;
|
||||||
@ -520,67 +677,6 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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)
|
|
||||||
{
|
|
||||||
_processQueue.Enqueue(new InitialOrderBookItem { StartUpdateId = orderBookSequenceNumber, EndUpdateId = orderBookSequenceNumber, Asks = askList, Bids = bidList });
|
|
||||||
_queueEvent.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update the order book using a single id for an update
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="rangeUpdateId"></param>
|
|
||||||
/// <param name="bids"></param>
|
|
||||||
/// <param name="asks"></param>
|
|
||||||
protected void UpdateOrderBook(long rangeUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
|
||||||
{
|
|
||||||
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = rangeUpdateId, EndUpdateId = rangeUpdateId, Asks = asks, Bids = bids });
|
|
||||||
_queueEvent.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// Update the order book using a first/last update id
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="firstUpdateId"></param>
|
|
||||||
/// <param name="lastUpdateId"></param>
|
|
||||||
/// <param name="bids"></param>
|
|
||||||
/// <param name="asks"></param>
|
|
||||||
protected void UpdateOrderBook(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
|
||||||
{
|
|
||||||
_processQueue.Enqueue(new ProcessQueueItem { StartUpdateId = firstUpdateId, EndUpdateId = lastUpdateId, Asks = asks, Bids = bids });
|
|
||||||
_queueEvent.Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update the order book using sequenced entries
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="bids">List of bids</param>
|
|
||||||
/// <param name="asks">List of asks</param>
|
|
||||||
protected void UpdateOrderBook(IEnumerable<ISymbolOrderSequencedBookEntry> bids, IEnumerable<ISymbolOrderSequencedBookEntry> asks)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
|
|
||||||
_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)
|
private void ProcessRangeUpdates(long firstUpdateId, long lastUpdateId, IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks)
|
||||||
{
|
{
|
||||||
if (lastUpdateId <= LastSequenceNumber)
|
if (lastUpdateId <= LastSequenceNumber)
|
||||||
@ -612,132 +708,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
|
|
||||||
LastSequenceNumber = lastUpdateId;
|
LastSequenceNumber = lastUpdateId;
|
||||||
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update processed #{firstUpdateId}-{lastUpdateId}");
|
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update processed #{firstUpdateId}-{lastUpdateId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check and empty the process buffer; see what entries to update the book with
|
|
||||||
/// </summary>
|
|
||||||
protected void CheckProcessBuffer()
|
|
||||||
{
|
|
||||||
var pbList = processBuffer.ToList();
|
|
||||||
if(pbList.Count > 0)
|
|
||||||
log.Write(LogLevel.Debug, "Processing buffered updates");
|
|
||||||
|
|
||||||
foreach (var bufferEntry in pbList)
|
|
||||||
{
|
|
||||||
ProcessRangeUpdates(bufferEntry.FirstUpdateId, bufferEntry.LastUpdateId, bufferEntry.Bids, bufferEntry.Asks);
|
|
||||||
processBuffer.Remove(bufferEntry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Update order book with an entry
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sequence">Sequence number of the update</param>
|
|
||||||
/// <param name="type">Type of entry</param>
|
|
||||||
/// <param name="entry">The entry</param>
|
|
||||||
protected virtual bool ProcessUpdate(long sequence, OrderBookEntryType type, ISymbolOrderBookEntry entry)
|
|
||||||
{
|
|
||||||
if (sequence <= LastSequenceNumber)
|
|
||||||
{
|
|
||||||
log.Write(LogLevel.Debug, $"{Id} order book {Symbol} update skipped #{sequence}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sequencesAreConsecutive && sequence > LastSequenceNumber + 1)
|
|
||||||
{
|
|
||||||
// Out of sync
|
|
||||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync (expected { LastSequenceNumber + 1}, was {sequence}), reconnecting");
|
|
||||||
_stopProcessing = true;
|
|
||||||
Resubscribe();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateTime = DateTime.UtcNow;
|
|
||||||
var listToChange = type == OrderBookEntryType.Ask ? asks : bids;
|
|
||||||
if (entry.Quantity == 0)
|
|
||||||
{
|
|
||||||
if (!listToChange.ContainsKey(entry.Price))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
listToChange.Remove(entry.Price);
|
|
||||||
if (type == OrderBookEntryType.Ask) AskCount--;
|
|
||||||
else BidCount--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!listToChange.ContainsKey(entry.Price))
|
|
||||||
{
|
|
||||||
listToChange.Add(entry.Price, entry);
|
|
||||||
if (type == OrderBookEntryType.Ask) AskCount++;
|
|
||||||
else BidCount++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
listToChange[entry.Price] = entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Wait until the order book has been set
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="timeout">Max wait time</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
protected async Task<CallResult<bool>> WaitForSetOrderBookAsync(int timeout)
|
|
||||||
{
|
|
||||||
var startWait = DateTime.UtcNow;
|
|
||||||
while (!bookSet && Status == OrderBookStatus.Syncing)
|
|
||||||
{
|
|
||||||
if ((DateTime.UtcNow - startWait).TotalMilliseconds > timeout)
|
|
||||||
return new CallResult<bool>(false, new ServerError("Timeout while waiting for data"));
|
|
||||||
|
|
||||||
await Task.Delay(10).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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>
|
|
||||||
public abstract void Dispose();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String representation of the top 3 entries
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return ToString(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// String representation of the top x entries
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public string ToString(int numberOfEntries)
|
|
||||||
{
|
|
||||||
var result = string.Empty;
|
|
||||||
result += $"Asks ({AskCount}): {Environment.NewLine}";
|
|
||||||
foreach (var entry in Asks.Take(numberOfEntries).Reverse())
|
|
||||||
result += $" {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
|
|
||||||
|
|
||||||
result += $"Bids ({BidCount}): {Environment.NewLine}";
|
|
||||||
foreach (var entry in Bids.Take(numberOfEntries))
|
|
||||||
result += $" {entry.Price.ToString(CultureInfo.InvariantCulture).PadLeft(8)} | {entry.Quantity.ToString(CultureInfo.InvariantCulture).PadRight(8)}{Environment.NewLine}";
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DescComparer<T> : IComparer<T>
|
internal class DescComparer<T> : IComparer<T>
|
||||||
|
@ -11,7 +11,7 @@ using CryptoExchange.Net.Interfaces;
|
|||||||
namespace CryptoExchange.Net.Requests
|
namespace CryptoExchange.Net.Requests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Request object
|
/// Request object, wrapper for HttpRequestMessage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Request : IRequest
|
public class Request : IRequest
|
||||||
{
|
{
|
||||||
@ -49,6 +49,7 @@ namespace CryptoExchange.Net.Requests
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Uri Uri => request.RequestUri;
|
public Uri Uri => request.RequestUri;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public int RequestId { get; }
|
public int RequestId { get; }
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ using CryptoExchange.Net.Objects;
|
|||||||
namespace CryptoExchange.Net.Requests
|
namespace CryptoExchange.Net.Requests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WebRequest factory
|
/// Request factory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RequestFactory : IRequestFactory
|
public class RequestFactory : IRequestFactory
|
||||||
{
|
{
|
||||||
|
@ -8,7 +8,7 @@ using CryptoExchange.Net.Interfaces;
|
|||||||
namespace CryptoExchange.Net.Requests
|
namespace CryptoExchange.Net.Requests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// HttpWebResponse response object
|
/// Response object, wrapper for HttpResponseMessage
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class Response : IResponse
|
internal class Response : IResponse
|
||||||
{
|
{
|
||||||
|
@ -67,9 +67,7 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
|
protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Total requests made by this client
|
|
||||||
/// </summary>
|
|
||||||
public int TotalRequestsMade { get; private set; }
|
public int TotalRequestsMade { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -133,7 +131,6 @@ 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="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
|
/// <param name="parameterPosition">Where the parameters should be placed, overwrites the value set in the client</param>
|
||||||
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
|
/// <param name="arraySerialization">How array parameters should be serialized, overwrites the value set in the client</param>
|
||||||
/// <param name="credits">Credits used for the request</param>
|
/// <param name="credits">Credits used for the request</param>
|
||||||
@ -147,7 +144,6 @@ namespace CryptoExchange.Net
|
|||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, object>? parameters = null,
|
Dictionary<string, object>? parameters = null,
|
||||||
bool signed = false,
|
bool signed = false,
|
||||||
bool checkResult = true,
|
|
||||||
HttpMethodParameterPosition? parameterPosition = null,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
int credits = 1,
|
int credits = 1,
|
||||||
|
@ -80,9 +80,7 @@ namespace CryptoExchange.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal int? RateLimitPerSocketPerSecond { get; set; }
|
protected internal int? RateLimitPerSocketPerSecond { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
|
|
||||||
/// </summary>
|
|
||||||
public double IncomingKbps
|
public double IncomingKbps
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -42,7 +42,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private DateTime _lastReceivedMessagesUpdate;
|
private DateTime _lastReceivedMessagesUpdate;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received messages time -> size
|
/// Received messages, the size and the timstamp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly List<ReceiveItem> _receivedMessages;
|
protected readonly List<ReceiveItem> _receivedMessages;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -72,17 +72,15 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly List<Action<string>> messageHandlers = new List<Action<string>>();
|
protected readonly List<Action<string>> messageHandlers = new List<Action<string>>();
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The id of this socket
|
|
||||||
/// </summary>
|
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string? Origin { get; set; }
|
public string? Origin { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// Whether this socket is currently reconnecting
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public bool Reconnecting { get; set; }
|
public bool Reconnecting { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timestamp this socket has been active for the last time
|
/// The timestamp this socket has been active for the last time
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -92,22 +90,19 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// Delegate used for processing byte data received from socket connections before it is processed by handlers
|
/// Delegate used for processing byte data received from socket connections before it is processed by handlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<byte[], string>? DataInterpreterBytes { get; set; }
|
public Func<byte[], string>? DataInterpreterBytes { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate used for processing string data received from socket connections before it is processed by handlers
|
/// Delegate used for processing string data received from socket connections before it is processed by handlers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<string, string>? DataInterpreterString { get; set; }
|
public Func<string, string>? DataInterpreterString { get; set; }
|
||||||
/// <summary>
|
|
||||||
/// Url this socket connects to
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public string Url { get; }
|
public string Url { get; }
|
||||||
/// <summary>
|
|
||||||
/// If the connection is closed
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public bool IsClosed => _socket.State == WebSocketState.Closed;
|
public bool IsClosed => _socket.State == WebSocketState.Closed;
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// If the connection is open
|
|
||||||
/// </summary>
|
|
||||||
public bool IsOpen => _socket.State == WebSocketState.Open && !_closing;
|
public bool IsOpen => _socket.State == WebSocketState.Open && !_closing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -116,9 +111,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
public SslProtocols SSLProtocols { get; set; }
|
public SslProtocols SSLProtocols { get; set; }
|
||||||
|
|
||||||
private Encoding _encoding = Encoding.UTF8;
|
private Encoding _encoding = Encoding.UTF8;
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Encoding used for decoding the received bytes into a string
|
|
||||||
/// </summary>
|
|
||||||
public Encoding? Encoding
|
public Encoding? Encoding
|
||||||
{
|
{
|
||||||
get => _encoding;
|
get => _encoding;
|
||||||
@ -128,19 +121,16 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_encoding = value;
|
_encoding = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The max amount of outgoing messages per second
|
/// The max amount of outgoing messages per second
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? RatelimitPerSecond { get; set; }
|
public int? RatelimitPerSecond { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The timespan no data is received on the socket. If no data is received within this time an error is generated
|
|
||||||
/// </summary>
|
|
||||||
public TimeSpan Timeout { get; set; }
|
public TimeSpan Timeout { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
|
|
||||||
/// </summary>
|
|
||||||
public double IncomingKbps
|
public double IncomingKbps
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@ -157,33 +147,28 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Socket closed event
|
|
||||||
/// </summary>
|
|
||||||
public event Action OnClose
|
public event Action OnClose
|
||||||
{
|
{
|
||||||
add => closeHandlers.Add(value);
|
add => closeHandlers.Add(value);
|
||||||
remove => closeHandlers.Remove(value);
|
remove => closeHandlers.Remove(value);
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Socket message received event
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public event Action<string> OnMessage
|
public event Action<string> OnMessage
|
||||||
{
|
{
|
||||||
add => messageHandlers.Add(value);
|
add => messageHandlers.Add(value);
|
||||||
remove => messageHandlers.Remove(value);
|
remove => messageHandlers.Remove(value);
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Socket error event
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public event Action<Exception> OnError
|
public event Action<Exception> OnError
|
||||||
{
|
{
|
||||||
add => errorHandlers.Add(value);
|
add => errorHandlers.Add(value);
|
||||||
remove => errorHandlers.Remove(value);
|
remove => errorHandlers.Remove(value);
|
||||||
}
|
}
|
||||||
/// <summary>
|
|
||||||
/// Socket opened event
|
/// <inheritdoc />
|
||||||
/// </summary>
|
|
||||||
public event Action OnOpen
|
public event Action OnOpen
|
||||||
{
|
{
|
||||||
add => openHandlers.Add(value);
|
add => openHandlers.Add(value);
|
||||||
@ -224,10 +209,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_socket = CreateSocket();
|
_socket = CreateSocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Set a proxy to use. Should be set before connecting
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="proxy"></param>
|
|
||||||
public virtual void SetProxy(ApiProxy proxy)
|
public virtual void SetProxy(ApiProxy proxy)
|
||||||
{
|
{
|
||||||
_socket.Options.Proxy = new WebProxy(proxy.Host, proxy.Port);
|
_socket.Options.Proxy = new WebProxy(proxy.Host, proxy.Port);
|
||||||
@ -235,10 +217,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_socket.Options.Proxy.Credentials = new NetworkCredential(proxy.Login, proxy.Password);
|
_socket.Options.Proxy.Credentials = new NetworkCredential(proxy.Login, proxy.Password);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Connect the websocket
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>True if successfull</returns>
|
|
||||||
public virtual async Task<bool> ConnectAsync()
|
public virtual async Task<bool> ConnectAsync()
|
||||||
{
|
{
|
||||||
log.Write(LogLevel.Debug, $"Socket {Id} connecting");
|
log.Write(LogLevel.Debug, $"Socket {Id} connecting");
|
||||||
@ -270,10 +249,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Send data over the websocket
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">Data to send</param>
|
|
||||||
public virtual void Send(string data)
|
public virtual void Send(string data)
|
||||||
{
|
{
|
||||||
if (_closing)
|
if (_closing)
|
||||||
@ -285,10 +261,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_sendEvent.Set();
|
_sendEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Close the websocket
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public virtual async Task CloseAsync()
|
public virtual async Task CloseAsync()
|
||||||
{
|
{
|
||||||
log.Write(LogLevel.Debug, $"Socket {Id} closing");
|
log.Write(LogLevel.Debug, $"Socket {Id} closing");
|
||||||
@ -344,9 +317,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
log.Write(LogLevel.Trace, $"Socket {Id} disposed");
|
log.Write(LogLevel.Trace, $"Socket {Id} disposed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Reset the socket so a new connection can be attempted after it has been connected before
|
|
||||||
/// </summary>
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
log.Write(LogLevel.Debug, $"Socket {Id} resetting");
|
log.Write(LogLevel.Debug, $"Socket {Id} resetting");
|
||||||
|
@ -26,7 +26,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
public DateTime ReceivedTimestamp { get; set; }
|
public DateTime ReceivedTimestamp { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
///
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="connection"></param>
|
/// <param name="connection"></param>
|
||||||
/// <param name="jsonData"></param>
|
/// <param name="jsonData"></param>
|
||||||
|
@ -14,7 +14,7 @@ using CryptoExchange.Net.Objects;
|
|||||||
namespace CryptoExchange.Net.Sockets
|
namespace CryptoExchange.Net.Sockets
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Socket connecting
|
/// A single socket connection to the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SocketConnection
|
public class SocketConnection
|
||||||
{
|
{
|
||||||
@ -22,26 +22,32 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// Connection lost event
|
/// Connection lost event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action? ConnectionLost;
|
public event Action? ConnectionLost;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connection closed and no reconnect is happening
|
/// Connection closed and no reconnect is happening
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action? ConnectionClosed;
|
public event Action? ConnectionClosed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connecting restored event
|
/// Connecting restored event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TimeSpan>? ConnectionRestored;
|
public event Action<TimeSpan>? ConnectionRestored;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The connection is paused event
|
/// The connection is paused event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action? ActivityPaused;
|
public event Action? ActivityPaused;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The connection is unpaused event
|
/// The connection is unpaused event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action? ActivityUnpaused;
|
public event Action? ActivityUnpaused;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Connecting closed event
|
/// Connecting closed event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action? Closed;
|
public event Action? Closed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unhandled message event
|
/// Unhandled message event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,30 +63,35 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If connection is authenticated
|
/// If the connection has been authenticated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Authenticated { get; set; }
|
public bool Authenticated { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If connection is made
|
/// If connection is made
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Connected { get; private set; }
|
public bool Connected { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The underlying socket
|
/// The underlying websocket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IWebsocket Socket { get; set; }
|
public IWebsocket Socket { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the socket should be reconnected upon closing
|
/// If the socket should be reconnected upon closing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShouldReconnect { get; set; }
|
public bool ShouldReconnect { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current reconnect try
|
/// Current reconnect try, reset when a successful connection is made
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ReconnectTry { get; set; }
|
public int ReconnectTry { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current resubscribe try
|
/// Current resubscribe try, reset when a successful connection is made
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int ResubscribeTry { get; set; }
|
public int ResubscribeTry { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time of disconnecting
|
/// Time of disconnecting
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -138,7 +149,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process a message received by the socket
|
/// Process a message received by the socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
/// <param name="data">The received data</param>
|
||||||
private void ProcessMessage(string data)
|
private void ProcessMessage(string data)
|
||||||
{
|
{
|
||||||
var timestamp = DateTime.UtcNow;
|
var timestamp = DateTime.UtcNow;
|
||||||
@ -193,7 +204,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add subscription to this connection
|
/// Add a subscription to this connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="subscription"></param>
|
/// <param name="subscription"></param>
|
||||||
public void AddSubscription(SocketSubscription subscription)
|
public void AddSubscription(SocketSubscription subscription)
|
||||||
@ -203,15 +214,20 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a subscription on this connection
|
/// Get a subscription on this connection by id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
public SocketSubscription GetSubscription(int id)
|
public SocketSubscription? GetSubscription(int id)
|
||||||
{
|
{
|
||||||
lock (subscriptionLock)
|
lock (subscriptionLock)
|
||||||
return subscriptions.SingleOrDefault(s => s.Id == id);
|
return subscriptions.SingleOrDefault(s => s.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Process data
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="messageEvent"></param>
|
||||||
|
/// <returns>True if the data was successfully handled</returns>
|
||||||
private bool HandleData(MessageEvent messageEvent)
|
private bool HandleData(MessageEvent messageEvent)
|
||||||
{
|
{
|
||||||
SocketSubscription? currentSubscription = null;
|
SocketSubscription? currentSubscription = null;
|
||||||
@ -249,7 +265,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
if (sw.ElapsedMilliseconds > 500)
|
if (sw.ElapsedMilliseconds > 500)
|
||||||
log.Write(LogLevel.Warning, $"Socket {Socket.Id} message processing slow ({sw.ElapsedMilliseconds}ms), consider offloading data handling to another thread. " +
|
log.Write(LogLevel.Debug, $"Socket {Socket.Id} message processing slow ({sw.ElapsedMilliseconds}ms), consider offloading data handling to another thread. " +
|
||||||
"Data from this socket may arrive late or not at all if message processing is continuously slow.");
|
"Data from this socket may arrive late or not at all if message processing is continuously slow.");
|
||||||
else
|
else
|
||||||
log.Write(LogLevel.Trace, $"Socket {Socket.Id} message processed in {sw.ElapsedMilliseconds}ms");
|
log.Write(LogLevel.Trace, $"Socket {Socket.Id} message processed in {sw.ElapsedMilliseconds}ms");
|
||||||
@ -269,7 +285,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <typeparam name="T">The data type expected in response</typeparam>
|
/// <typeparam name="T">The data type expected in response</typeparam>
|
||||||
/// <param name="obj">The object to send</param>
|
/// <param name="obj">The object to send</param>
|
||||||
/// <param name="timeout">The timeout for response</param>
|
/// <param name="timeout">The timeout for response</param>
|
||||||
/// <param name="handler">The response handler</param>
|
/// <param name="handler">The response handler, should return true if the received JToken was the response to the request</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
|
public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
|
||||||
{
|
{
|
||||||
@ -391,6 +407,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
if (!reconnectResult)
|
if (!reconnectResult)
|
||||||
{
|
{
|
||||||
ResubscribeTry++;
|
ResubscribeTry++;
|
||||||
|
DisconnectTime = time;
|
||||||
|
|
||||||
if (socketClient.ClientOptions.MaxResubscribeTries != null &&
|
if (socketClient.ClientOptions.MaxResubscribeTries != null &&
|
||||||
ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
|
ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
|
||||||
@ -419,7 +436,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
if (lostTriggered)
|
if (lostTriggered)
|
||||||
{
|
{
|
||||||
lostTriggered = false;
|
lostTriggered = false;
|
||||||
InvokeConnectionRestored(time);
|
_ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -443,11 +460,6 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void InvokeConnectionRestored(DateTime? disconnectTime)
|
|
||||||
{
|
|
||||||
await Task.Run(() => ConnectionRestored?.Invoke(disconnectTime.HasValue ? DateTime.UtcNow - disconnectTime.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ProcessReconnectAsync()
|
private async Task<bool> ProcessReconnectAsync()
|
||||||
{
|
{
|
||||||
if (Authenticated)
|
if (Authenticated)
|
||||||
|
@ -9,9 +9,10 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
public class SocketSubscription
|
public class SocketSubscription
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscription id
|
/// Unique subscription id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Id { get; }
|
public int Id { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exception event
|
/// Exception event
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -23,25 +24,28 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
public Action<MessageEvent> MessageHandler { get; set; }
|
public Action<MessageEvent> MessageHandler { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Request object
|
/// The request object send when subscribing on the server. Either this or the `Identifier` property should be set
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public object? Request { get; set; }
|
public object? Request { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Subscription identifier
|
/// The subscription identifier, used instead of a `Request` object to identify the subscription
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? Identifier { get; set; }
|
public string? Identifier { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is user subscription or generic
|
/// Whether this is a user subscription or an internal listener
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UserSubscription { get; set; }
|
public bool UserSubscription { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the subscription has been confirmed
|
/// If the subscription has been confirmed to be subscribed by the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Confirmed { get; set; }
|
public bool Confirmed { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancellation token registration, should be disposed when subscription is closed
|
/// Cancellation token registration, should be disposed when subscription is closed. Used for closing the subscription with
|
||||||
|
/// a provided cancelation token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
|
public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
|
||||||
|
|
||||||
@ -55,7 +59,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create SocketSubscription for a request
|
/// Create SocketSubscription for a subscribe request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <param name="request"></param>
|
/// <param name="request"></param>
|
||||||
|
@ -23,7 +23,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event when the connection is closed. This event happens when reconnecting/resubscribing has failed too often based on the <see cref="SocketClientOptions.MaxReconnectTries"/> and <see cref="SocketClientOptions.MaxResubscribeTries"/> options,
|
/// Event when the connection is closed. This event happens when reconnecting/resubscribing has failed too often based on the <see cref="SocketClientOptions.MaxReconnectTries"/> and <see cref="SocketClientOptions.MaxResubscribeTries"/> options,
|
||||||
/// or <see cref="SocketClientOptions.AutoReconnect"/> is false
|
/// or <see cref="SocketClientOptions.AutoReconnect"/> is false. The socket will not be reconnected
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action ConnectionClosed
|
public event Action ConnectionClosed
|
||||||
{
|
{
|
||||||
@ -33,8 +33,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting.
|
/// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting.
|
||||||
/// Note that when the executing code is suspended and resumed at a later period (for example laptop going to sleep) the disconnect time will be incorrect as the diconnect
|
/// Note that when the executing code is suspended and resumed at a later period (for example, a laptop going to sleep) the disconnect time will be incorrect as the diconnect
|
||||||
/// will only be detected after resuming. This will lead to an incorrect disconnected timespan.
|
/// will only be detected after resuming the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TimeSpan> ConnectionRestored
|
public event Action<TimeSpan> ConnectionRestored
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user