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.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.RequestTimeout == TimeSpan.FromMinutes(1));
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ namespace CryptoExchange.Net.Attributes
|
||||
/// <summary>
|
||||
/// Used for conversion in ArrayConverter
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Api credentials info
|
||||
/// Api credentials, used to sign requests accessing private endpoints
|
||||
/// </summary>
|
||||
public class ApiCredentials: IDisposable
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
protected internal Log log;
|
||||
/// <summary>
|
||||
/// The authentication provider
|
||||
/// The authentication provider when api credentials have been provided
|
||||
/// </summary>
|
||||
protected internal AuthenticationProvider? authProvider;
|
||||
/// <summary>
|
||||
@ -72,7 +72,6 @@ namespace CryptoExchange.Net
|
||||
ClientOptions = options;
|
||||
|
||||
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}");
|
||||
}
|
||||
@ -88,7 +87,7 @@ namespace CryptoExchange.Net
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <param name="data">The data to parse</param>
|
||||
/// <returns></returns>
|
||||
|
@ -181,6 +181,7 @@ namespace CryptoExchange.Net.Converters
|
||||
/// <summary>
|
||||
/// Mark property as an index in the array
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ArrayPropertyAttribute: Attribute
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -75,7 +75,7 @@ namespace CryptoExchange.Net.Converters
|
||||
|
||||
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));
|
||||
if(mapping.Equals(default(KeyValuePair<T, string>)))
|
||||
mapping = Mapping.FirstOrDefault(kv => kv.Value.Equals(value, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
@ -4,7 +4,7 @@ using Newtonsoft.Json;
|
||||
namespace CryptoExchange.Net.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// converter for milliseconds to datetime
|
||||
/// Converter for milliseconds to datetime
|
||||
/// </summary>
|
||||
public class TimestampConverter : JsonConverter
|
||||
{
|
||||
|
@ -18,7 +18,7 @@ namespace CryptoExchange.Net.Interfaces
|
||||
IRequestFactory RequestFactory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total amount of requests made
|
||||
/// The total amount of requests made with this client
|
||||
/// </summary>
|
||||
int TotalRequestsMade { get; }
|
||||
|
||||
@ -34,7 +34,7 @@ namespace CryptoExchange.Net.Interfaces
|
||||
void RemoveRateLimiters();
|
||||
|
||||
/// <summary>
|
||||
/// Client options
|
||||
/// The options provided for this client
|
||||
/// </summary>
|
||||
RestClientOptions ClientOptions { get; }
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Interfaces
|
||||
public interface ISocketClient: IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Client options
|
||||
/// The options provided for this client
|
||||
/// </summary>
|
||||
SocketClientOptions ClientOptions { get; }
|
||||
|
||||
@ -20,6 +20,13 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
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>
|
||||
/// Unsubscribe from a stream
|
||||
/// </summary>
|
||||
|
@ -7,41 +7,41 @@ using System.Threading.Tasks;
|
||||
namespace CryptoExchange.Net.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for websocket interaction
|
||||
/// Webscoket connection interface
|
||||
/// </summary>
|
||||
public interface IWebsocket: IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Websocket closed
|
||||
/// Websocket closed event
|
||||
/// </summary>
|
||||
event Action OnClose;
|
||||
/// <summary>
|
||||
/// Websocket message received
|
||||
/// Websocket message received event
|
||||
/// </summary>
|
||||
event Action<string> OnMessage;
|
||||
/// <summary>
|
||||
/// Websocket error
|
||||
/// Websocket error event
|
||||
/// </summary>
|
||||
event Action<Exception> OnError;
|
||||
/// <summary>
|
||||
/// Websocket opened
|
||||
/// Websocket opened event
|
||||
/// </summary>
|
||||
event Action OnOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// Unique id for this socket
|
||||
/// </summary>
|
||||
int Id { get; }
|
||||
/// <summary>
|
||||
/// Origin
|
||||
/// Origin header
|
||||
/// </summary>
|
||||
string? Origin { get; set; }
|
||||
/// <summary>
|
||||
/// Encoding to use
|
||||
/// Encoding to use for sending/receiving string data
|
||||
/// </summary>
|
||||
Encoding? Encoding { get; set; }
|
||||
/// <summary>
|
||||
/// Reconnecting
|
||||
/// Whether socket is in the process of reconnecting
|
||||
/// </summary>
|
||||
bool Reconnecting { get; set; }
|
||||
/// <summary>
|
||||
@ -61,15 +61,15 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
Func<string, string>? DataInterpreterString { get; set; }
|
||||
/// <summary>
|
||||
/// Socket url
|
||||
/// The url the socket connects to
|
||||
/// </summary>
|
||||
string Url { get; }
|
||||
/// <summary>
|
||||
/// Is closed
|
||||
/// Whether the socket connection is closed
|
||||
/// </summary>
|
||||
bool IsClosed { get; }
|
||||
/// <summary>
|
||||
/// Is open
|
||||
/// Whether the socket connection is open
|
||||
/// </summary>
|
||||
bool IsOpen { get; }
|
||||
/// <summary>
|
||||
@ -77,10 +77,15 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// </summary>
|
||||
SslProtocols SSLProtocols { get; set; }
|
||||
/// <summary>
|
||||
/// Timeout
|
||||
/// The max time for no data being received before the connection is considered lost
|
||||
/// </summary>
|
||||
TimeSpan Timeout { get; set; }
|
||||
/// <summary>
|
||||
/// Set a proxy to use when connecting
|
||||
/// </summary>
|
||||
/// <param name="proxy"></param>
|
||||
void SetProxy(ApiProxy proxy);
|
||||
/// <summary>
|
||||
/// Connect the socket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
@ -91,18 +96,13 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// <param name="data"></param>
|
||||
void Send(string data);
|
||||
/// <summary>
|
||||
/// Reset socket
|
||||
/// Reset socket when a connection is lost to prepare for a new connection
|
||||
/// </summary>
|
||||
void Reset();
|
||||
/// <summary>
|
||||
/// Close the connecting
|
||||
/// Close the connection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task CloseAsync();
|
||||
/// <summary>
|
||||
/// Set proxy
|
||||
/// </summary>
|
||||
/// <param name="proxy"></param>
|
||||
void SetProxy(ApiProxy proxy);
|
||||
}
|
||||
}
|
||||
|
@ -11,17 +11,17 @@ namespace CryptoExchange.Net.Interfaces
|
||||
/// <summary>
|
||||
/// Create a websocket for an url
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="log">The logger</param>
|
||||
/// <param name="url">The url the socket is fo</param>
|
||||
/// <returns></returns>
|
||||
IWebsocket CreateWebsocket(Log log, string url);
|
||||
/// <summary>
|
||||
/// Create a websocket for an url
|
||||
/// </summary>
|
||||
/// <param name="log"></param>
|
||||
/// <param name="url"></param>
|
||||
/// <param name="cookies"></param>
|
||||
/// <param name="headers"></param>
|
||||
/// <param name="log">The logger</param>
|
||||
/// <param name="url">The url the socket is fo</param>
|
||||
/// <param name="cookies">Cookies to be send in the initial request</param>
|
||||
/// <param name="headers">Headers to be send in the initial request</param>
|
||||
/// <returns></returns>
|
||||
IWebsocket CreateWebsocket(Log log, string url, IDictionary<string, string> cookies, IDictionary<string, string> headers);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ using System;
|
||||
namespace CryptoExchange.Net.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Log to console
|
||||
/// ILogger implementation for logging to the console
|
||||
/// </summary>
|
||||
public class ConsoleLogger : ILogger
|
||||
{
|
||||
|
@ -5,7 +5,7 @@ using System.Diagnostics;
|
||||
namespace CryptoExchange.Net.Logging
|
||||
{
|
||||
/// <summary>
|
||||
/// Default log writer, writes to debug
|
||||
/// Default log writer, uses Trace.WriteLine
|
||||
/// </summary>
|
||||
public class DebugLogger: ILogger
|
||||
{
|
||||
|
@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Objects
|
||||
public class BaseOptions
|
||||
{
|
||||
/// <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>
|
||||
public LogLevel LogLevel { get; set; } = LogLevel.Information;
|
||||
|
||||
@ -86,12 +86,12 @@ namespace CryptoExchange.Net.Objects
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The api credentials
|
||||
/// The api credentials used for signing requests
|
||||
/// </summary>
|
||||
public ApiCredentials? ApiCredentials { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Proxy to use
|
||||
/// Proxy to use when connecting
|
||||
/// </summary>
|
||||
public ApiProxy? Proxy { get; set; }
|
||||
|
||||
@ -161,7 +161,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <inheritdoc />
|
||||
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);
|
||||
|
||||
/// <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>
|
||||
public int? MaxReconnectTries { get; set; }
|
||||
|
||||
@ -196,11 +196,13 @@ namespace CryptoExchange.Net.Objects
|
||||
public int MaxConcurrentResubscriptionsPerSocket { get; set; } = 5;
|
||||
|
||||
/// <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>
|
||||
public TimeSpan SocketResponseTimeout { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
/// <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>
|
||||
public TimeSpan SocketNoDataTimeout { get; set; }
|
||||
|
||||
@ -234,7 +236,7 @@ namespace CryptoExchange.Net.Objects
|
||||
/// <inheritdoc />
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// First update id
|
||||
/// First sequence number in this update
|
||||
/// </summary>
|
||||
public long FirstUpdateId { get; set; }
|
||||
/// <summary>
|
||||
/// Last update id
|
||||
/// Last sequence number in this update
|
||||
/// </summary>
|
||||
public long LastUpdateId { get; set; }
|
||||
/// <summary>
|
||||
/// List of asks
|
||||
/// List of changed/new asks
|
||||
/// </summary>
|
||||
public IEnumerable<ISymbolOrderBookEntry> Asks { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
||||
/// <summary>
|
||||
/// List of bids
|
||||
/// List of changed/new bids
|
||||
/// </summary>
|
||||
public IEnumerable<ISymbolOrderBookEntry> Bids { get; set; } = Array.Empty<ISymbolOrderBookEntry>();
|
||||
}
|
||||
|
@ -18,52 +18,64 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// </summary>
|
||||
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 OrderBookStatus status;
|
||||
private UpdateSubscription? subscription;
|
||||
|
||||
private readonly bool validateChecksum;
|
||||
|
||||
private bool _stopProcessing;
|
||||
private Task? _processTask;
|
||||
|
||||
private readonly AutoResetEvent _queueEvent;
|
||||
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>
|
||||
/// 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>
|
||||
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>
|
||||
/// The log
|
||||
/// </summary>
|
||||
protected Log log;
|
||||
|
||||
/// <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>
|
||||
protected bool sequencesAreConsecutive;
|
||||
|
||||
/// <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>
|
||||
protected bool strictLevels;
|
||||
|
||||
/// <summary>
|
||||
/// If order book is set
|
||||
/// If the initial snapshot of the book has been set
|
||||
/// </summary>
|
||||
protected bool bookSet;
|
||||
|
||||
@ -72,9 +84,10 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// </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>
|
||||
/// <inheritdoc/>
|
||||
public string Id { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public OrderBookStatus Status
|
||||
{
|
||||
get => status;
|
||||
@ -90,46 +103,31 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Last update identifier
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public long LastSequenceNumber { get; private set; }
|
||||
/// <summary>
|
||||
/// The symbol of the order book
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Symbol { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event when the state changes
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public event Action<OrderBookStatus, OrderBookStatus>? OnStatusChange;
|
||||
|
||||
/// <summary>
|
||||
/// Event when the BestBid or BestAsk changes ie a Pricing Tick
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public event Action<(ISymbolOrderBookEntry BestBid, ISymbolOrderBookEntry BestAsk)>? OnBestOffersChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
/// <inheritdoc/>
|
||||
public event Action<(IEnumerable<ISymbolOrderBookEntry> Bids, IEnumerable<ISymbolOrderBookEntry> Asks)>? OnOrderBookUpdate;
|
||||
/// <summary>
|
||||
/// Timestamp of the last update
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc/>
|
||||
public DateTime UpdateTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of asks in the book
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public int AskCount { get; private set; }
|
||||
/// <summary>
|
||||
/// The number of bids in the book
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int BidCount { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of asks
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ISymbolOrderBookEntry> Asks
|
||||
{
|
||||
get
|
||||
@ -139,9 +137,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of bids
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public IEnumerable<ISymbolOrderBookEntry> Bids
|
||||
{
|
||||
get
|
||||
@ -151,9 +147,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a snapshot of the book at this moment
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public (IEnumerable<ISymbolOrderBookEntry> bids, IEnumerable<ISymbolOrderBookEntry> asks) Book
|
||||
{
|
||||
get
|
||||
@ -163,17 +157,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
/// The best bid currently in the order book
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public ISymbolOrderBookEntry BestBid
|
||||
{
|
||||
get
|
||||
@ -183,9 +167,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The best ask currently in the order book
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public ISymbolOrderBookEntry BestAsk
|
||||
{
|
||||
get
|
||||
@ -195,9 +177,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BestBid/BesAsk returned as a pair
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public (ISymbolOrderBookEntry Bid, ISymbolOrderBookEntry Ask) BestOffers {
|
||||
get {
|
||||
lock (bookLock)
|
||||
@ -208,9 +188,9 @@ namespace CryptoExchange.Net.OrderBook
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="symbol"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="id">The id of the order book. Should be set to {Exchange}[{type}], for example: Kucoin[Spot]</param>
|
||||
/// <param name="symbol">The symbol the order book is for</param>
|
||||
/// <param name="options">The options for the order book</param>
|
||||
protected SymbolOrderBook(string id, string symbol, OrderBookOptions options)
|
||||
{
|
||||
if (symbol == null)
|
||||
@ -236,10 +216,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
log.UpdateWriters(writers.ToList());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start connecting and synchronizing the order book
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <inheritdoc/>
|
||||
public async Task<CallResult<bool>> StartAsync()
|
||||
{
|
||||
if (Status != OrderBookStatus.Disconnected)
|
||||
@ -275,13 +252,21 @@ namespace CryptoExchange.Net.OrderBook
|
||||
return new CallResult<bool>(true, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// at that price since between this calculation and the order placement the book can have changed.
|
||||
/// </summary>
|
||||
/// <param name="quantity">The quantity in base asset to fill</param>
|
||||
/// <param name="type">The type</param>
|
||||
/// <returns>Average fill price</returns>
|
||||
/// <inheritdoc/>
|
||||
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");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CallResult<decimal> CalculateAverageFillPrice(decimal quantity, OrderBookEntryType type)
|
||||
{
|
||||
if (Status != OrderBookStatus.Synced)
|
||||
@ -312,6 +297,219 @@ namespace CryptoExchange.Net.OrderBook
|
||||
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()
|
||||
{
|
||||
_queueEvent.Set();
|
||||
@ -339,50 +537,9 @@ namespace CryptoExchange.Net.OrderBook
|
||||
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()
|
||||
{
|
||||
while(Status != OrderBookStatus.Disconnected)
|
||||
while (Status != OrderBookStatus.Disconnected)
|
||||
{
|
||||
_queueEvent.WaitOne();
|
||||
|
||||
@ -490,7 +647,7 @@ namespace CryptoExchange.Net.OrderBook
|
||||
throw;
|
||||
}
|
||||
|
||||
if(!checksumResult)
|
||||
if (!checksumResult)
|
||||
{
|
||||
log.Write(LogLevel.Warning, $"{Id} order book {Symbol} out of sync. Resyncing");
|
||||
_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)
|
||||
{
|
||||
if (lastUpdateId <= LastSequenceNumber)
|
||||
@ -613,131 +709,6 @@ namespace CryptoExchange.Net.OrderBook
|
||||
LastSequenceNumber = 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>
|
||||
|
@ -11,7 +11,7 @@ using CryptoExchange.Net.Interfaces;
|
||||
namespace CryptoExchange.Net.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// Request object
|
||||
/// Request object, wrapper for HttpRequestMessage
|
||||
/// </summary>
|
||||
public class Request : IRequest
|
||||
{
|
||||
@ -49,6 +49,7 @@ namespace CryptoExchange.Net.Requests
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Uri => request.RequestUri;
|
||||
|
||||
/// <inheritdoc />
|
||||
public int RequestId { get; }
|
||||
|
||||
|
@ -7,7 +7,7 @@ using CryptoExchange.Net.Objects;
|
||||
namespace CryptoExchange.Net.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// WebRequest factory
|
||||
/// Request factory
|
||||
/// </summary>
|
||||
public class RequestFactory : IRequestFactory
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ using CryptoExchange.Net.Interfaces;
|
||||
namespace CryptoExchange.Net.Requests
|
||||
{
|
||||
/// <summary>
|
||||
/// HttpWebResponse response object
|
||||
/// Response object, wrapper for HttpResponseMessage
|
||||
/// </summary>
|
||||
internal class Response : IResponse
|
||||
{
|
||||
|
@ -67,9 +67,7 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
protected IEnumerable<IRateLimiter> RateLimiters { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Total requests made by this client
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public int TotalRequestsMade { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
@ -133,7 +131,6 @@ 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="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="credits">Credits used for the request</param>
|
||||
@ -147,7 +144,6 @@ namespace CryptoExchange.Net
|
||||
CancellationToken cancellationToken,
|
||||
Dictionary<string, object>? parameters = null,
|
||||
bool signed = false,
|
||||
bool checkResult = true,
|
||||
HttpMethodParameterPosition? parameterPosition = null,
|
||||
ArrayParametersSerialization? arraySerialization = null,
|
||||
int credits = 1,
|
||||
|
@ -80,9 +80,7 @@ namespace CryptoExchange.Net
|
||||
/// </summary>
|
||||
protected internal int? RateLimitPerSocketPerSecond { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current kilobytes per second of data being received by all connection from this client, averaged over the last 3 seconds
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public double IncomingKbps
|
||||
{
|
||||
get
|
||||
|
@ -42,7 +42,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
private DateTime _lastReceivedMessagesUpdate;
|
||||
|
||||
/// <summary>
|
||||
/// Received messages time -> size
|
||||
/// Received messages, the size and the timstamp
|
||||
/// </summary>
|
||||
protected readonly List<ReceiveItem> _receivedMessages;
|
||||
/// <summary>
|
||||
@ -72,17 +72,15 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// </summary>
|
||||
protected readonly List<Action<string>> messageHandlers = new List<Action<string>>();
|
||||
|
||||
/// <summary>
|
||||
/// The id of this socket
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public int Id { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? Origin { get; set; }
|
||||
/// <summary>
|
||||
/// Whether this socket is currently reconnecting
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Reconnecting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timestamp this socket has been active for the last time
|
||||
/// </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
|
||||
/// </summary>
|
||||
public Func<byte[], string>? DataInterpreterBytes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate used for processing string data received from socket connections before it is processed by handlers
|
||||
/// </summary>
|
||||
public Func<string, string>? DataInterpreterString { get; set; }
|
||||
/// <summary>
|
||||
/// Url this socket connects to
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Url { get; }
|
||||
/// <summary>
|
||||
/// If the connection is closed
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsClosed => _socket.State == WebSocketState.Closed;
|
||||
|
||||
/// <summary>
|
||||
/// If the connection is open
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool IsOpen => _socket.State == WebSocketState.Open && !_closing;
|
||||
|
||||
/// <summary>
|
||||
@ -116,9 +111,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
public SslProtocols SSLProtocols { get; set; }
|
||||
|
||||
private Encoding _encoding = Encoding.UTF8;
|
||||
/// <summary>
|
||||
/// Encoding used for decoding the received bytes into a string
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public Encoding? Encoding
|
||||
{
|
||||
get => _encoding;
|
||||
@ -128,19 +121,16 @@ namespace CryptoExchange.Net.Sockets
|
||||
_encoding = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The max amount of outgoing messages per second
|
||||
/// </summary>
|
||||
public int? RatelimitPerSecond { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timespan no data is received on the socket. If no data is received within this time an error is generated
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current kilobytes per second of data being received, averaged over the last 3 seconds
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public double IncomingKbps
|
||||
{
|
||||
get
|
||||
@ -157,33 +147,28 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Socket closed event
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public event Action OnClose
|
||||
{
|
||||
add => closeHandlers.Add(value);
|
||||
remove => closeHandlers.Remove(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Socket message received event
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<string> OnMessage
|
||||
{
|
||||
add => messageHandlers.Add(value);
|
||||
remove => messageHandlers.Remove(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Socket error event
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action<Exception> OnError
|
||||
{
|
||||
add => errorHandlers.Add(value);
|
||||
remove => errorHandlers.Remove(value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Socket opened event
|
||||
/// </summary>
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Action OnOpen
|
||||
{
|
||||
add => openHandlers.Add(value);
|
||||
@ -224,10 +209,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
_socket = CreateSocket();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a proxy to use. Should be set before connecting
|
||||
/// </summary>
|
||||
/// <param name="proxy"></param>
|
||||
/// <inheritdoc />
|
||||
public virtual void SetProxy(ApiProxy proxy)
|
||||
{
|
||||
_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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect the websocket
|
||||
/// </summary>
|
||||
/// <returns>True if successfull</returns>
|
||||
/// <inheritdoc />
|
||||
public virtual async Task<bool> ConnectAsync()
|
||||
{
|
||||
log.Write(LogLevel.Debug, $"Socket {Id} connecting");
|
||||
@ -270,10 +249,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data over the websocket
|
||||
/// </summary>
|
||||
/// <param name="data">Data to send</param>
|
||||
/// <inheritdoc />
|
||||
public virtual void Send(string data)
|
||||
{
|
||||
if (_closing)
|
||||
@ -285,10 +261,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
_sendEvent.Set();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close the websocket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <inheritdoc />
|
||||
public virtual async Task CloseAsync()
|
||||
{
|
||||
log.Write(LogLevel.Debug, $"Socket {Id} closing");
|
||||
@ -344,9 +317,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
log.Write(LogLevel.Trace, $"Socket {Id} disposed");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the socket so a new connection can be attempted after it has been connected before
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
log.Write(LogLevel.Debug, $"Socket {Id} resetting");
|
||||
|
@ -26,7 +26,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
public DateTime ReceivedTimestamp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <param name="jsonData"></param>
|
||||
|
@ -14,7 +14,7 @@ using CryptoExchange.Net.Objects;
|
||||
namespace CryptoExchange.Net.Sockets
|
||||
{
|
||||
/// <summary>
|
||||
/// Socket connecting
|
||||
/// A single socket connection to the server
|
||||
/// </summary>
|
||||
public class SocketConnection
|
||||
{
|
||||
@ -22,26 +22,32 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// Connection lost event
|
||||
/// </summary>
|
||||
public event Action? ConnectionLost;
|
||||
|
||||
/// <summary>
|
||||
/// Connection closed and no reconnect is happening
|
||||
/// </summary>
|
||||
public event Action? ConnectionClosed;
|
||||
|
||||
/// <summary>
|
||||
/// Connecting restored event
|
||||
/// </summary>
|
||||
public event Action<TimeSpan>? ConnectionRestored;
|
||||
|
||||
/// <summary>
|
||||
/// The connection is paused event
|
||||
/// </summary>
|
||||
public event Action? ActivityPaused;
|
||||
|
||||
/// <summary>
|
||||
/// The connection is unpaused event
|
||||
/// </summary>
|
||||
public event Action? ActivityUnpaused;
|
||||
|
||||
/// <summary>
|
||||
/// Connecting closed event
|
||||
/// </summary>
|
||||
public event Action? Closed;
|
||||
|
||||
/// <summary>
|
||||
/// Unhandled message event
|
||||
/// </summary>
|
||||
@ -57,30 +63,35 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If connection is authenticated
|
||||
/// If the connection has been authenticated
|
||||
/// </summary>
|
||||
public bool Authenticated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If connection is made
|
||||
/// </summary>
|
||||
public bool Connected { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The underlying socket
|
||||
/// The underlying websocket
|
||||
/// </summary>
|
||||
public IWebsocket Socket { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the socket should be reconnected upon closing
|
||||
/// </summary>
|
||||
public bool ShouldReconnect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current reconnect try
|
||||
/// Current reconnect try, reset when a successful connection is made
|
||||
/// </summary>
|
||||
public int ReconnectTry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current resubscribe try
|
||||
/// Current resubscribe try, reset when a successful connection is made
|
||||
/// </summary>
|
||||
public int ResubscribeTry { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Time of disconnecting
|
||||
/// </summary>
|
||||
@ -138,7 +149,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// <summary>
|
||||
/// Process a message received by the socket
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <param name="data">The received data</param>
|
||||
private void ProcessMessage(string data)
|
||||
{
|
||||
var timestamp = DateTime.UtcNow;
|
||||
@ -193,7 +204,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add subscription to this connection
|
||||
/// Add a subscription to this connection
|
||||
/// </summary>
|
||||
/// <param name="subscription"></param>
|
||||
public void AddSubscription(SocketSubscription subscription)
|
||||
@ -203,15 +214,20 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a subscription on this connection
|
||||
/// Get a subscription on this connection by id
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
public SocketSubscription GetSubscription(int id)
|
||||
public SocketSubscription? GetSubscription(int id)
|
||||
{
|
||||
lock (subscriptionLock)
|
||||
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)
|
||||
{
|
||||
SocketSubscription? currentSubscription = null;
|
||||
@ -249,7 +265,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
sw.Stop();
|
||||
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.");
|
||||
else
|
||||
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>
|
||||
/// <param name="obj">The object to send</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>
|
||||
public virtual Task SendAndWaitAsync<T>(T obj, TimeSpan timeout, Func<JToken, bool> handler)
|
||||
{
|
||||
@ -391,6 +407,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
if (!reconnectResult)
|
||||
{
|
||||
ResubscribeTry++;
|
||||
DisconnectTime = time;
|
||||
|
||||
if (socketClient.ClientOptions.MaxResubscribeTries != null &&
|
||||
ResubscribeTry >= socketClient.ClientOptions.MaxResubscribeTries)
|
||||
@ -419,7 +436,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
if (lostTriggered)
|
||||
{
|
||||
lostTriggered = false;
|
||||
InvokeConnectionRestored(time);
|
||||
_ = Task.Run(() => ConnectionRestored?.Invoke(time.HasValue ? DateTime.UtcNow - time.Value : TimeSpan.FromSeconds(0))).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
if (Authenticated)
|
||||
|
@ -9,9 +9,10 @@ namespace CryptoExchange.Net.Sockets
|
||||
public class SocketSubscription
|
||||
{
|
||||
/// <summary>
|
||||
/// Subscription id
|
||||
/// Unique subscription id
|
||||
/// </summary>
|
||||
public int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception event
|
||||
/// </summary>
|
||||
@ -23,25 +24,28 @@ namespace CryptoExchange.Net.Sockets
|
||||
public Action<MessageEvent> MessageHandler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request object
|
||||
/// The request object send when subscribing on the server. Either this or the `Identifier` property should be set
|
||||
/// </summary>
|
||||
public object? Request { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Subscription identifier
|
||||
/// The subscription identifier, used instead of a `Request` object to identify the subscription
|
||||
/// </summary>
|
||||
public string? Identifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is user subscription or generic
|
||||
/// Whether this is a user subscription or an internal listener
|
||||
/// </summary>
|
||||
public bool UserSubscription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the subscription has been confirmed
|
||||
/// If the subscription has been confirmed to be subscribed by the server
|
||||
/// </summary>
|
||||
public bool Confirmed { get; set; }
|
||||
|
||||
/// <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>
|
||||
public CancellationTokenRegistration? CancellationTokenRegistration { get; set; }
|
||||
|
||||
@ -55,7 +59,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create SocketSubscription for a request
|
||||
/// Create SocketSubscription for a subscribe request
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="request"></param>
|
||||
|
@ -23,7 +23,7 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
/// <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,
|
||||
/// or <see cref="SocketClientOptions.AutoReconnect"/> is false
|
||||
/// or <see cref="SocketClientOptions.AutoReconnect"/> is false. The socket will not be reconnected
|
||||
/// </summary>
|
||||
public event Action ConnectionClosed
|
||||
{
|
||||
@ -33,8 +33,8 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// will only be detected after resuming. This will lead to an incorrect disconnected timespan.
|
||||
/// 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 the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
|
||||
/// </summary>
|
||||
public event Action<TimeSpan> ConnectionRestored
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user