1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-11 16:32:57 +00:00
Fix for intermittently failing rate limiting test
Added ConnectionId to RequestDefinition to correctly handle connection and path rate limiting configuration
Added ValidateMessage method to websocket Query object to filter messages even though it is matched to the query based on the  ListenIdentifier
Added KlineTracker and TradeTracker implementation
This commit is contained in:
Jan Korf
2024-10-28 10:36:19 +01:00
committed by GitHub
parent ed007b5272
commit 9e86a08327
17 changed files with 2386 additions and 5 deletions
+637
View File
@@ -106,6 +106,7 @@
<li class="nav-item"><a class="nav-link" href="#idocs_features">Additional Features</a>
<ul class="nav flex-column">
<li class="nav-item"><a class="nav-link" href="#idocs_orderbooks">Orderbooks</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_trackers">Trackers</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_logging">Logging</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_ratelimiting">Ratelimiting</a></li>
<li class="nav-item"><a class="nav-link" href="#idocs_caching">Caching</a></li>
@@ -164,6 +165,9 @@
</table>
<p>Note that there are 3rd party implementations going around, but only the listed ones here are created and supported by me.</p>
<p>When using multiple of these API's the <a href="https://github.com/jkorf/CryptoClients.Net">CryptoClients.Net</a> package can be used which combines these packages and allows easy access to all exchange API's.</p>
<table class="table table-bordered">
<tr><td>CryptoClients</td><td><a href="https://github.com/JKorf/CryptoClients.Net">JKorf/CryptoClients.Net</a></td><td><a href="https://www.nuget.org/packages/CryptoClients.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CryptoClients.net.svg?style=flat-square" /></a></a></td></tr>
</table>
<h4>Supported Frameworks</h4>
<p>
@@ -3255,6 +3259,639 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
</p>
</section>
<hr class="divider">
<section id="idocs_trackers">
<h2>Trackers</h2>
<p>
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.
</p>
<p>
Currently there are 2 different trackers available, the <code>TradeTracker</code> and the <code>KlineTracker</code>.
</p>
<p>
<b>Creation and starting</b><br />
The example uses the <code>TradeTracker</code>, but the same logic can be applied to the <code>KlineTracker</code>.
</p>
<div class="tab-wrap">
<ul class="nav nav-tabs" id="tracker" role="tablist" style="margin-bottom: -16px;">
<li class="nav-item" role="presentation">
<a class="nav-link active" id="tracker-cryptoclients-tab" data-toggle="tab" href="#tracker-cryptoclients" role="tab" aria-controls="tracker-cryptoclients" aria-selected="true">CryptoClients</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-binance-tab" data-toggle="tab" href="#tracker-binance" role="tab" aria-controls="tracker-binance" aria-selected="false">Binance</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-bingx-tab" data-toggle="tab" href="#tracker-bingx" role="tab" aria-controls="tracker-bingx" aria-selected="false">BingX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-bitfinex-tab" data-toggle="tab" href="#tracker-bitfinex" role="tab" aria-controls="tracker-bitfinex" aria-selected="false">Bitfinex</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-bitget-tab" data-toggle="tab" href="#tracker-bitget" role="tab" aria-controls="tracker-bitget" aria-selected="false">Bitget</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-bitmart-tab" data-toggle="tab" href="#tracker-bitmart" role="tab" aria-controls="tracker-bitmart" aria-selected="false">BitMart</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-bybit-tab" data-toggle="tab" href="#tracker-bybit" role="tab" aria-controls="tracker-bybit" aria-selected="false">Bybit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-coinbase-tab" data-toggle="tab" href="#tracker-coinbase" role="tab" aria-controls="tracker-coinbase" aria-selected="false">Coinbase</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-coinex-tab" data-toggle="tab" href="#tracker-coinex" role="tab" aria-controls="tracker-coinex" aria-selected="false">Coinex</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-cryptocom-tab" data-toggle="tab" href="#tracker-cryptocom" role="tab" aria-controls="tracker-cryptocom" aria-selected="false">Crypto.com</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-gateio-tab" data-toggle="tab" href="#tracker-gateio" role="tab" aria-controls="tracker-gateio" aria-selected="false">GateIo</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-htx-tab" data-toggle="tab" href="#tracker-htx" role="tab" aria-controls="tracker-htx" aria-selected="false">HTX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-kraken-tab" data-toggle="tab" href="#tracker-kraken" role="tab" aria-controls="tracker-kraken" aria-selected="false">Kraken</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-kucoin-tab" data-toggle="tab" href="#tracker-kucoin" role="tab" aria-controls="tracker-kucoin" aria-selected="false">Kucoin</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-mexc-tab" data-toggle="tab" href="#tracker-mexc" role="tab" aria-controls="tracker-mexc" aria-selected="false">Mexc</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-okx-tab" data-toggle="tab" href="#tracker-okx" role="tab" aria-controls="tracker-okx" aria-selected="false">OKX</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="tracker-cryptoclients" role="tabpanel" aria-labelledby="tracker-cryptoclients-tab">
<pre><code>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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-binance" role="tabpanel" aria-labelledby="tracker-binance-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-bingx" role="tabpanel" aria-labelledby="tracker-bingx-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-bitfinex" role="tabpanel" aria-labelledby="tracker-bitfinex-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-bitget" role="tabpanel" aria-labelledby="tracker-bitget-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-bitmart" role="tabpanel" aria-labelledby="tracker-bitmart-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-bybit" role="tabpanel" aria-labelledby="tracker-bybit-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-coinbase" role="tabpanel" aria-labelledby="tracker-coinbase-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-coinex" role="tabpanel" aria-labelledby="tracker-coinex-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-gateio" role="tabpanel" aria-labelledby="tracker-gateio-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-cryptocom" role="tabpanel" aria-labelledby="tracker-cryptocom-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-htx" role="tabpanel" aria-labelledby="tracker-htx-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-kraken" role="tabpanel" aria-labelledby="tracker-kraken-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-kucoin" role="tabpanel" aria-labelledby="tracker-kucoin-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-mexc" role="tabpanel" aria-labelledby="tracker-mexc-tab">
<pre><code>// 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();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-okx" role="tabpanel" aria-labelledby="tracker-okx-tab">
<pre><code>// 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();
</code></pre>
</div>
</div>
</div>
<p>
<b>Stats and comparing data</b><br />
Using the <code>tracker.GetData(fromTime, toTime)</code> method the trackers expose the data, or a subset of the data, can be retrieved:
<pre><code>// 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));</code></pre>
</p>
<p>
In a similar way statistics about the full data, or a subset of it, can be retrieved using the <code>tracker.GetStats(fromTime, toTime)</code> method:
<pre><code>// 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));</code></pre>
See below for an overview of what these stats include.
</p>
<p>
Stats can also be compared to eachother to see how much values have changed using the <code>stats.CompareTo(otherStats)</code> method:
<pre><code>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%)</code></pre>
</p>
<p>
<b>The <code>TradeTracker</code> object</b><br />
</p>
<p>
The following properties and events are exposed by the <code>TradeTracker</code> object:
<table class="table table-bordered">
<tr><th>Field</th><th>Description</th></tr>
<tr>
<td>Count</td>
<td>The total number of trades currently tracked</td>
</tr>
<tr>
<td>Exchange</td>
<td>The name of the exchange</td>
</tr>
<tr>
<td>SymbolName</td>
<td>The name of the symbol being tracked</td>
</tr>
<tr>
<td>Symbol</td>
<td>The symbol as passed in the constructor</td>
</tr>
<tr>
<td>Limit</td>
<td>The max number of results tracked</td>
</tr>
<tr>
<td>Period</td>
<td>The max age of results tracked</td>
</tr>
<tr>
<td>SyncedFrom</td>
<td>The timestamp from which on the trades are registered</td>
</tr>
<tr>
<td>Status</td>
<td>The current synchronization status. Note the <code>PartiallySynced</code> 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.</td>
</tr>
<tr>
<td>Last</td>
<td>The current last trade</td>
</tr>
<tr>
<td>OnAdded</td>
<td>Event for when a new trade is added</td>
</tr>
<tr>
<td>OnRemoved</td>
<td>Event for when a trade is removed from the tracker due to not being within the set tracking period/limit anymore</td>
</tr>
<tr>
<td>OnStatusChanged</td>
<td>Event for when the status of the tracker changes</td>
</tr>
</table>
</p>
<p>
The following properties are available in the <code>TradesStats</code> object:
<table class="table table-bordered">
<tr><th>Field</th><th>Description</th></tr>
<tr>
<td>TradeCount</td>
<td>The number trades in this data set</td>
</tr>
<tr>
<td>FirstTradeTime</td>
<td>The timestamp of the first trade in this data set</td>
</tr>
<tr>
<td>LastTradeTime</td>
<td>The timestamp of the last trade in this data set</td>
</tr>
<tr>
<td>AveragePrice</td>
<td>The average price of the trades in this dataset</td>
</tr>
<tr>
<td>VolumeWeightedAveragePrice</td>
<td>The volume weighted average price for trades in this dataset</td>
</tr>
<tr>
<td>Volume</td>
<td>The total volume of all trades in this set</td>
</tr>
<tr>
<td>QuoteVolume</td>
<td>The total volume of all trades in this set denoted in the quote asset</td>
</tr>
<tr>
<td>BuySellRatio</td>
<td>The buy sell ratio of all trades in this data set. The factor of how much of the trades were a buy.</td>
</tr>
<tr>
<td>Complete</td>
<td>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.</td>
</tr>
</table>
<p>
<b>The <code>KlineTracker</code> object</b><br />
</p>
<p>
The following properties and events are exposed by the <code>KlineTracker</code> object:
<table class="table table-bordered">
<tr><th>Field</th><th>Description</th></tr>
<tr>
<td>Count</td>
<td>The total number of klines currently tracked</td>
</tr>
<tr>
<td>Exchange</td>
<td>The name of the exchange</td>
</tr>
<tr>
<td>SymbolName</td>
<td>The name of the symbol being tracked</td>
</tr>
<tr>
<td>Symbol</td>
<td>The symbol as passed in the constructor</td>
</tr>
<tr>
<td>Limit</td>
<td>The max number of results tracked</td>
</tr>
<tr>
<td>Period</td>
<td>The max age of results tracked</td>
</tr>
<tr>
<td>SyncedFrom</td>
<td>The timestamp from which on the klines are registered</td>
</tr>
<tr>
<td>Status</td>
<td>The current synchronization status. Note the <code>PartiallySynced</code> 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.</td>
</tr>
<tr>
<td>Last</td>
<td>The current last kline</td>
</tr>
<tr>
<td>OnAdded</td>
<td>Event for when a new kline is added</td>
</tr>
<tr>
<td>OnRemoved</td>
<td>Event for when a kline is removed from the tracker due to not being within the set tracking period/limit anymore</td>
</tr>
<tr>
<td>OnStatusChanged</td>
<td>Event for when the status of the tracker changes</td>
</tr>
</table>
</p>
<p>
The following properties are available on the <code>KlinesStats</code> object:
<table class="table table-bordered">
<tr><th>Field</th><th>Description</th></tr>
<tr>
<td>KlineCount</td>
<td>The number klines in this data set</td>
</tr>
<tr>
<td>FirstOpenTime</td>
<td>The open time of the first kline in this data set</td>
</tr>
<tr>
<td>LastOpenTime</td>
<td>The open time of the last kline in this data set</td>
</tr>
<tr>
<td>LowPrice</td>
<td>The lowest price for all klines in this set</td>
</tr>
<tr>
<td>HighPrice</td>
<td>The highest price for all kline in this set</td>
</tr>
<tr>
<td>Volume</td>
<td>The total volume of all klines in this set</td>
</tr>
<tr>
<td>AverageVolume</td>
<td>The average volume per kline for all klines in this set</td>
</tr>
<tr>
<td>Complete</td>
<td>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.</td>
</tr>
</table>
</p>
</section>
<hr class="divider">
<!-- Layout
============================ -->