diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs
index e06535b0..2838af41 100644
--- a/CryptoExchange.Net.UnitTests/RestClientTests.cs
+++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs
@@ -302,7 +302,7 @@ public async Task EndpointRateLimiterMultipleEndpoints(string endpoint, bool exp
public async Task ApiKeyRateLimiterBasics(string key1, string key2, string endpoint1, string endpoint2, bool expectLimited)
{
var rateLimiter = new RateLimitGate("Test");
- rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerApiKey, new AuthenticatedEndpointFilter(true), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Fixed));
+ rateLimiter.AddGuard(new RateLimitGuard(RateLimitGuard.PerApiKey, new AuthenticatedEndpointFilter(true), 1, TimeSpan.FromSeconds(0.1), RateLimitWindowType.Sliding));
var requestDefinition1 = new RequestDefinition(endpoint1, HttpMethod.Get) { Authenticated = key1 != null };
var requestDefinition2 = new RequestDefinition(endpoint2, HttpMethod.Get) { Authenticated = key2 != null };
diff --git a/CryptoExchange.Net/Logging/Extensions/TrackerLoggingExtensions.cs b/CryptoExchange.Net/Logging/Extensions/TrackerLoggingExtensions.cs
new file mode 100644
index 00000000..514db432
--- /dev/null
+++ b/CryptoExchange.Net/Logging/Extensions/TrackerLoggingExtensions.cs
@@ -0,0 +1,292 @@
+using System;
+using CryptoExchange.Net.Objects;
+using Microsoft.Extensions.Logging;
+
+namespace CryptoExchange.Net.Logging.Extensions
+{
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+
+ public static class TrackerLoggingExtensions
+ {
+ private static readonly Action _klineTrackerStatusChanged;
+ private static readonly Action _klineTrackerStarting;
+ private static readonly Action _klineTrackerStartFailed;
+ private static readonly Action _klineTrackerStarted;
+ private static readonly Action _klineTrackerStopping;
+ private static readonly Action _klineTrackerStopped;
+ private static readonly Action _klineTrackerInitialDataSet;
+ private static readonly Action _klineTrackerKlineUpdated;
+ private static readonly Action _klineTrackerKlineAdded;
+ private static readonly Action _klineTrackerConnectionLost;
+ private static readonly Action _klineTrackerConnectionClosed;
+ private static readonly Action _klineTrackerConnectionRestored;
+
+ private static readonly Action _tradeTrackerStatusChanged;
+ private static readonly Action _tradeTrackerStarting;
+ private static readonly Action _tradeTrackerStartFailed;
+ private static readonly Action _tradeTrackerStarted;
+ private static readonly Action _tradeTrackerStopping;
+ private static readonly Action _tradeTrackerStopped;
+ private static readonly Action _tradeTrackerInitialDataSet;
+ private static readonly Action _tradeTrackerPreSnapshotSkip;
+ private static readonly Action _tradeTrackerPreSnapshotApplied;
+ private static readonly Action _tradeTrackerTradeAdded;
+ private static readonly Action _tradeTrackerConnectionLost;
+ private static readonly Action _tradeTrackerConnectionClosed;
+ private static readonly Action _tradeTrackerConnectionRestored;
+
+ static TrackerLoggingExtensions()
+ {
+ _klineTrackerStatusChanged = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6001, "KlineTrackerStatusChanged"),
+ "Kline tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
+
+ _klineTrackerStarting = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6002, "KlineTrackerStarting"),
+ "Kline tracker for {Symbol} starting");
+
+ _klineTrackerStartFailed = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6003, "KlineTrackerStartFailed"),
+ "Kline tracker for {Symbol} failed to start: {Error}");
+
+ _klineTrackerStarted = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6004, "KlineTrackerStarted"),
+ "Kline tracker for {Symbol} started");
+
+ _klineTrackerStopping = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6005, "KlineTrackerStopping"),
+ "Kline tracker for {Symbol} stopping");
+
+ _klineTrackerStopped = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6006, "KlineTrackerStopped"),
+ "Kline tracker for {Symbol} stopped");
+
+ _klineTrackerInitialDataSet = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6007, "KlineTrackerInitialDataSet"),
+ "Kline tracker for {Symbol} initial data set, last timestamp: {LastTime}");
+
+ _klineTrackerKlineUpdated = LoggerMessage.Define(
+ LogLevel.Trace,
+ new EventId(6008, "KlineTrackerKlineUpdated"),
+ "Kline tracker for {Symbol} kline updated for open time: {LastTime}");
+
+ _klineTrackerKlineAdded = LoggerMessage.Define(
+ LogLevel.Trace,
+ new EventId(6009, "KlineTrackerKlineAdded"),
+ "Kline tracker for {Symbol} new kline for open time: {LastTime}");
+
+ _klineTrackerConnectionLost = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6010, "KlineTrackerConnectionLost"),
+ "Kline tracker for {Symbol} connection lost");
+
+ _klineTrackerConnectionClosed = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6011, "KlineTrackerConnectionClosed"),
+ "Kline tracker for {Symbol} disconnected");
+
+ _klineTrackerConnectionRestored = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6012, "KlineTrackerConnectionRestored"),
+ "Kline tracker for {Symbol} successfully resynchronized");
+
+
+ _tradeTrackerStatusChanged = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6013, "KlineTrackerStatusChanged"),
+ "Trade tracker for {Symbol} status changed: {OldStatus} => {NewStatus}");
+
+ _tradeTrackerStarting = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6014, "KlineTrackerStarting"),
+ "Trade tracker for {Symbol} starting");
+
+ _tradeTrackerStartFailed = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6015, "KlineTrackerStartFailed"),
+ "Trade tracker for {Symbol} failed to start: {Error}");
+
+ _tradeTrackerStarted = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6016, "KlineTrackerStarted"),
+ "Trade tracker for {Symbol} started");
+
+ _tradeTrackerStopping = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6017, "KlineTrackerStopping"),
+ "Trade tracker for {Symbol} stopping");
+
+ _tradeTrackerStopped = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6018, "KlineTrackerStopped"),
+ "Trade tracker for {Symbol} stopped");
+
+ _tradeTrackerInitialDataSet = LoggerMessage.Define(
+ LogLevel.Debug,
+ new EventId(6019, "TradeTrackerInitialDataSet"),
+ "Trade tracker for {Symbol} snapshot set, Count: {Count}, Last id: {LastId}");
+
+ _tradeTrackerPreSnapshotSkip = LoggerMessage.Define(
+ LogLevel.Trace,
+ new EventId(6020, "TradeTrackerPreSnapshotSkip"),
+ "Trade tracker for {Symbol} skipping {Id}, already in snapshot");
+
+ _tradeTrackerPreSnapshotApplied = LoggerMessage.Define(
+ LogLevel.Trace,
+ new EventId(6021, "TradeTrackerPreSnapshotApplied"),
+ "Trade tracker for {Symbol} adding {Id} from pre-snapshot");
+
+ _tradeTrackerTradeAdded = LoggerMessage.Define(
+ LogLevel.Trace,
+ new EventId(6022, "TradeTrackerTradeAdded"),
+ "Trade tracker for {Symbol} adding trade {Id}");
+
+ _tradeTrackerConnectionLost = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6023, "TradeTrackerConnectionLost"),
+ "Trade tracker for {Symbol} connection lost");
+
+ _tradeTrackerConnectionClosed = LoggerMessage.Define(
+ LogLevel.Warning,
+ new EventId(6024, "TradeTrackerConnectionClosed"),
+ "Trade tracker for {Symbol} disconnected");
+
+ _tradeTrackerConnectionRestored = LoggerMessage.Define(
+ LogLevel.Information,
+ new EventId(6025, "TradeTrackerConnectionRestored"),
+ "Trade tracker for {Symbol} successfully resynchronized");
+ }
+
+ public static void KlineTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
+ {
+ _klineTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
+ }
+
+ public static void KlineTrackerStarting(this ILogger logger, string symbol)
+ {
+ _klineTrackerStarting(logger, symbol, null);
+ }
+
+ public static void KlineTrackerStartFailed(this ILogger logger, string symbol, string error)
+ {
+ _klineTrackerStartFailed(logger, symbol, error, null);
+ }
+
+ public static void KlineTrackerStarted(this ILogger logger, string symbol)
+ {
+ _klineTrackerStarted(logger, symbol, null);
+ }
+
+ public static void KlineTrackerStopping(this ILogger logger, string symbol)
+ {
+ _klineTrackerStopping(logger, symbol, null);
+ }
+
+ public static void KlineTrackerStopped(this ILogger logger, string symbol)
+ {
+ _klineTrackerStopped(logger, symbol, null);
+ }
+
+ public static void KlineTrackerInitialDataSet(this ILogger logger, string symbol, DateTime lastTime)
+ {
+ _klineTrackerInitialDataSet(logger, symbol, lastTime, null);
+ }
+
+ public static void KlineTrackerKlineUpdated(this ILogger logger, string symbol, DateTime lastTime)
+ {
+ _klineTrackerKlineUpdated(logger, symbol, lastTime, null);
+ }
+
+ public static void KlineTrackerKlineAdded(this ILogger logger, string symbol, DateTime lastTime)
+ {
+ _klineTrackerKlineAdded(logger, symbol, lastTime, null);
+ }
+
+ public static void KlineTrackerConnectionLost(this ILogger logger, string symbol)
+ {
+ _klineTrackerConnectionLost(logger, symbol, null);
+ }
+
+ public static void KlineTrackerConnectionClosed(this ILogger logger, string symbol)
+ {
+ _klineTrackerConnectionClosed(logger, symbol, null);
+ }
+
+ public static void KlineTrackerConnectionRestored(this ILogger logger, string symbol)
+ {
+ _klineTrackerConnectionRestored(logger, symbol, null);
+ }
+
+ public static void TradeTrackerStatusChanged(this ILogger logger, string symbol, SyncStatus oldStatus, SyncStatus newStatus)
+ {
+ _tradeTrackerStatusChanged(logger, symbol, oldStatus, newStatus, null);
+ }
+
+ public static void TradeTrackerStarting(this ILogger logger, string symbol)
+ {
+ _tradeTrackerStarting(logger, symbol, null);
+ }
+
+ public static void TradeTrackerStartFailed(this ILogger logger, string symbol, string error)
+ {
+ _tradeTrackerStartFailed(logger, symbol, error, null);
+ }
+
+ public static void TradeTrackerStarted(this ILogger logger, string symbol)
+ {
+ _tradeTrackerStarted(logger, symbol, null);
+ }
+
+ public static void TradeTrackerStopping(this ILogger logger, string symbol)
+ {
+ _tradeTrackerStopping(logger, symbol, null);
+ }
+
+ public static void TradeTrackerStopped(this ILogger logger, string symbol)
+ {
+ _tradeTrackerStopped(logger, symbol, null);
+ }
+
+ public static void TradeTrackerInitialDataSet(this ILogger logger, string symbol, int count, long lastId)
+ {
+ _tradeTrackerInitialDataSet(logger, symbol, count, lastId, null);
+ }
+
+ public static void TradeTrackerPreSnapshotSkip(this ILogger logger, string symbol, long lastId)
+ {
+ _tradeTrackerPreSnapshotSkip(logger, symbol, lastId, null);
+ }
+
+ public static void TradeTrackerPreSnapshotApplied(this ILogger logger, string symbol, long lastId)
+ {
+ _tradeTrackerPreSnapshotApplied(logger, symbol, lastId, null);
+ }
+
+ public static void TradeTrackerTradeAdded(this ILogger logger, string symbol, long lastId)
+ {
+ _tradeTrackerTradeAdded(logger, symbol, lastId, null);
+ }
+
+ public static void TradeTrackerConnectionLost(this ILogger logger, string symbol)
+ {
+ _tradeTrackerConnectionLost(logger, symbol, null);
+ }
+
+ public static void TradeTrackerConnectionClosed(this ILogger logger, string symbol)
+ {
+ _tradeTrackerConnectionClosed(logger, symbol, null);
+ }
+
+ public static void TradeTrackerConnectionRestored(this ILogger logger, string symbol)
+ {
+ _tradeTrackerConnectionRestored(logger, symbol, null);
+ }
+ }
+}
diff --git a/CryptoExchange.Net/Objects/Enums.cs b/CryptoExchange.Net/Objects/Enums.cs
index 56783d80..a0f877c6 100644
--- a/CryptoExchange.Net/Objects/Enums.cs
+++ b/CryptoExchange.Net/Objects/Enums.cs
@@ -68,6 +68,33 @@ public enum RequestBodyFormat
Json
}
+ ///
+ /// Tracker sync status
+ ///
+ public enum SyncStatus
+ {
+ ///
+ /// Not connected
+ ///
+ Disconnected,
+ ///
+ /// Syncing, data connection is being made
+ ///
+ Syncing,
+ ///
+ /// The connection is active, but the full data backlog is not yet reached. For example, a tracker set to retain 10 minutes of data only has 8 minutes of data at this moment.
+ ///
+ PartiallySynced,
+ ///
+ /// Synced
+ ///
+ Synced,
+ ///
+ /// Disposed
+ ///
+ Diposed
+ }
+
///
/// Status of the order book
///
diff --git a/CryptoExchange.Net/Objects/RequestDefinition.cs b/CryptoExchange.Net/Objects/RequestDefinition.cs
index 1dd05673..e583dddd 100644
--- a/CryptoExchange.Net/Objects/RequestDefinition.cs
+++ b/CryptoExchange.Net/Objects/RequestDefinition.cs
@@ -58,12 +58,16 @@ public class RequestDefinition
///
public IRateLimitGuard? LimitGuard { get; set; }
-
///
/// Whether this request should never be cached
///
public bool PreventCaching { get; set; }
+ ///
+ /// Connection id
+ ///
+ public int? ConnectionId { get; set; }
+
///
/// ctor
///
diff --git a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs
index 9501360e..e9e51270 100644
--- a/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs
+++ b/CryptoExchange.Net/RateLimiting/Guards/RateLimitGuard.cs
@@ -18,6 +18,10 @@ public class RateLimitGuard : IRateLimitGuard
///
public static Func PerEndpoint { get; } = new Func((def, host, key) => def.Path + def.Method);
///
+ /// Apply guard per connection
+ ///
+ public static Func PerConnection { get; } = new Func((def, host, key) => def.ConnectionId.ToString());
+ ///
/// Apply guard per API key
///
public static Func PerApiKey { get; } = new Func((def, host, key) => key!);
diff --git a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
index 3c0a445e..fd3fb650 100644
--- a/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
+++ b/CryptoExchange.Net/Sockets/CryptoExchangeWebSocketClient.cs
@@ -209,7 +209,7 @@ private async Task ConnectInternalAsync()
{
if (Parameters.RateLimiter != null)
{
- var definition = new RequestDefinition(Id.ToString(), HttpMethod.Get);
+ var definition = new RequestDefinition(Uri.AbsolutePath, HttpMethod.Get) { ConnectionId = Id };
var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, definition, _baseAddress, null, 1, Parameters.RateLimitingBehaviour, _ctsSource.Token).ConfigureAwait(false);
if (!limitResult)
return new CallResult(new ClientRateLimitError("Connection limit reached"));
@@ -475,7 +475,7 @@ public void Dispose()
///
private async Task SendLoopAsync()
{
- var requestDefinition = new RequestDefinition(Id.ToString(), HttpMethod.Get);
+ var requestDefinition = new RequestDefinition(Uri.AbsolutePath, HttpMethod.Get) { ConnectionId = Id };
try
{
while (true)
diff --git a/CryptoExchange.Net/Sockets/Query.cs b/CryptoExchange.Net/Sockets/Query.cs
index d79279dc..833abf8b 100644
--- a/CryptoExchange.Net/Sockets/Query.cs
+++ b/CryptoExchange.Net/Sockets/Query.cs
@@ -177,6 +177,10 @@ protected Query(object request, bool authenticated, int weight = 1) : base(reque
///
public override async Task Handle(SocketConnection connection, DataEvent
+
+
+
+
Trackers
+
+ Trackers offer a way to keep track of live data. This data can than be aggregated into statistics and different time slices can be compared to get realtime insights.
+
+
+ Currently there are 2 different trackers available, the TradeTracker and the KlineTracker.
+
+
+
+ Creation and starting
+ The example uses the TradeTracker, but the same logic can be applied to the KlineTracker.
+
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Assuming IExchangeTrackerFactory is injected as trackerFactory
+// Create tracker dynamically by exchange name
+var tracker = trackerFactory.CreateTradeTracker("Binance", symbol);
+// OR by directly referencing the specific exchange
+tracker = trackerFactory.Binance.CreateTradeTracker(symbol);
+
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBinanceTrackerFactory interface
+var factory = new BinanceTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBingXTrackerFactory interface
+var factory = new BingXTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBitfinexTrackerFactory interface
+var factory = new BitfinexTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBitgetTrackerFactory interface
+var factory = new BitgetTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBitMartTrackerFactory interface
+var factory = new BitMartTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IBybitTrackerFactory interface
+var factory = new BybitTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the ICoinbaseTrackerFactory interface
+var factory = new CoinbaseTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the ICoinExTrackerFactory interface
+var factory = new CoinExTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IGateIoTrackerFactory interface
+var factory = new GateIoTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the ICryptoComTrackerFactory interface
+var factory = new CryptoComTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IHTXTrackerFactory interface
+var factory = new HTXTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IKrakenTrackerFactory interface
+var factory = new KrakenTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USD");
+
+// Create a tracker for ETH/USD keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IKucoinTrackerFactory interface
+var factory = new KucoinTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IMexcTrackerFactory interface
+var factory = new MexcTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
// Either create a new factory or inject the IOKXTrackerFactory interface
+var factory = new OKXTrackerFactory();
+
+var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
+
+// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
+var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
+var startResult = await tracker.StartAsync();
+if (!startResult.Success)
+{
+ // Handle error, error info available in startResult.Error
+}
+// Tracker has successfully started
+// Note that it might not be fully synced yet, check tracker.Status for this.
+
+// Once no longer needed you can stop the live sync functionality by calling StopAsync()
+await tracker.StopAsync();
+
+
+
+
+
+
+ Stats and comparing data
+
+ Using the tracker.GetData(fromTime, toTime) method the trackers expose the data, or a subset of the data, can be retrieved:
+
// Get all the data currently tracked:
+var data = tracker.GetData();
+
+// Get the data for the last minute:
+var data = tracker.GetData(DateTime.UtcNow.AddMinutes(-1));
+
+// Get the data for the second last minute:
+var data = tracker.GetData(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));
+
+
+ In a similar way statistics about the full data, or a subset of it, can be retrieved using the tracker.GetStats(fromTime, toTime) method:
+
// Get statistics on all the data:
+var stats = tracker.GetStats();
+
+// Get statistics for the last minute:
+var stats = tracker.GetStats(DateTime.UtcNow.AddMinutes(-1));
+
+// Get statistics for the second last minute:
+var stats = tracker.GetStats(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));
+
+ See below for an overview of what these stats include.
+
+
+
+
+ Stats can also be compared to eachother to see how much values have changed using the stats.CompareTo(otherStats) method:
+
+
var statsSecondLastMinute = tracker.GetStats(compareTime.AddMinutes(-2), compareTime.AddMinutes(-1));
+var statsLastMinute = tracker.GetStats(compareTime.AddMinutes(-1), compareTime);
+var comparison = statsLastMinute.CompareTo(statsSecondLastMinute);
+
+Console.WriteLine($"The volume of last minute compared to the minute before that: {comparison.VolumeDif?.Difference} ({comparison.VolumeDif?.PercentageDifference}%)");
+// Output: The volume of last minute compared to the minute before that: -1261,57350000 (-85,2572%)
+
+
+
+ The TradeTracker object
+
+
+
+ The following properties and events are exposed by the TradeTracker object:
+
+
Field
Description
+
+
Count
+
The total number of trades currently tracked
+
+
+
Exchange
+
The name of the exchange
+
+
+
SymbolName
+
The name of the symbol being tracked
+
+
+
Symbol
+
The symbol as passed in the constructor
+
+
+
Limit
+
The max number of results tracked
+
+
+
Period
+
The max age of results tracked
+
+
+
SyncedFrom
+
The timestamp from which on the trades are registered
+
+
+
Status
+
The current synchronization status. Note the PartiallySynced means that the connection is active, but the data set is not yet complete. For example if the tracker is set to track 5 minutes of trades, it could be that currently only 3 minutes of data is tracked and it will take 2 more minutes to be fully synced.
+
+
+
Last
+
The current last trade
+
+
+
OnAdded
+
Event for when a new trade is added
+
+
+
OnRemoved
+
Event for when a trade is removed from the tracker due to not being within the set tracking period/limit anymore
+
+
+
OnStatusChanged
+
Event for when the status of the tracker changes
+
+
+
+
+
+
+ The following properties are available in the TradesStats object:
+
+
Field
Description
+
+
TradeCount
+
The number trades in this data set
+
+
+
FirstTradeTime
+
The timestamp of the first trade in this data set
+
+
+
LastTradeTime
+
The timestamp of the last trade in this data set
+
+
+
AveragePrice
+
The average price of the trades in this dataset
+
+
+
VolumeWeightedAveragePrice
+
The volume weighted average price for trades in this dataset
+
+
+
Volume
+
The total volume of all trades in this set
+
+
+
QuoteVolume
+
The total volume of all trades in this set denoted in the quote asset
+
+
+
BuySellRatio
+
The buy sell ratio of all trades in this data set. The factor of how much of the trades were a buy.
+
+
+
Complete
+
Whether the data set is complete. A set is not complete when not all data is available. For example, when only 1 hour of data is available in the tracker but stats are requested for the last 2 hours.
+
+
+
+
+ The KlineTracker object
+
+
+
+ The following properties and events are exposed by the KlineTracker object:
+
+
Field
Description
+
+
Count
+
The total number of klines currently tracked
+
+
+
Exchange
+
The name of the exchange
+
+
+
SymbolName
+
The name of the symbol being tracked
+
+
+
Symbol
+
The symbol as passed in the constructor
+
+
+
Limit
+
The max number of results tracked
+
+
+
Period
+
The max age of results tracked
+
+
+
SyncedFrom
+
The timestamp from which on the klines are registered
+
+
+
Status
+
The current synchronization status. Note the PartiallySynced means that the connection is active, but the data set is not yet complete. For example if the tracker is set to track 10 hours of klines, it could be that currently only 9 hours of data is tracked and it will take another hour to be fully synced.
+
+
+
Last
+
The current last kline
+
+
+
OnAdded
+
Event for when a new kline is added
+
+
+
OnRemoved
+
Event for when a kline is removed from the tracker due to not being within the set tracking period/limit anymore
+
+
+
OnStatusChanged
+
Event for when the status of the tracker changes
+
+
+
+
+
+
+ The following properties are available on the KlinesStats object:
+
+
Field
Description
+
+
KlineCount
+
The number klines in this data set
+
+
+
FirstOpenTime
+
The open time of the first kline in this data set
+
+
+
LastOpenTime
+
The open time of the last kline in this data set
+
+
+
LowPrice
+
The lowest price for all klines in this set
+
+
+
HighPrice
+
The highest price for all kline in this set
+
+
+
Volume
+
The total volume of all klines in this set
+
+
+
AverageVolume
+
The average volume per kline for all klines in this set
+
+
+
Complete
+
Whether the data set is complete. A set is not complete when not all data is available. For example, when only 1 hour of data is available in the tracker but stats are requested for the last 2 hours.