1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-12 08:53:01 +00:00

Compare commits

...

7 Commits

16 changed files with 234 additions and 63 deletions
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
@@ -6,10 +6,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"></PackageReference>
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="NUnit" Version="4.1.0"></PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"></PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"></PackageReference>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.2.2"></PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"></PackageReference>
</ItemGroup>
<ItemGroup>
+14 -4
View File
@@ -490,11 +490,14 @@ namespace CryptoExchange.Net.Clients
SocketConnection connection;
if (!dedicatedRequestConnection)
{
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
}
else
{
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection).FirstOrDefault().Value;
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault().Value;
if (connection != null && !connection.DedicatedRequestConnection.Authenticated)
// Mark dedicated request connection as authenticated if the request is authenticated
connection.DedicatedRequestConnection.Authenticated = authenticated;
}
if (connection != null)
@@ -519,7 +522,14 @@ namespace CryptoExchange.Net.Clients
var socketConnection = new SocketConnection(_logger, this, socket, address);
socketConnection.UnhandledMessage += HandleUnhandledMessage;
socketConnection.ConnectRateLimitedAsync += HandleConnectRateLimitedAsync;
socketConnection.DedicatedRequestConnection = dedicatedRequestConnection;
if (dedicatedRequestConnection)
{
socketConnection.DedicatedRequestConnection = new DedicatedConnectionState
{
IsDedicatedRequestConnection = dedicatedRequestConnection,
Authenticated = authenticated
};
}
foreach (var ptg in PeriodicTaskRegistrations)
socketConnection.QueryPeriodic(ptg.Identifier, ptg.Interval, ptg.QueryDelegate, ptg.Callback);
@@ -652,7 +662,7 @@ namespace CryptoExchange.Net.Clients
var tasks = new List<Task>();
{
var socketList = socketConnections.Values;
foreach (var connection in socketList.Where(s => !s.DedicatedRequestConnection))
foreach (var connection in socketList.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection))
tasks.Add(connection.CloseAsync());
}
@@ -38,7 +38,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
private class ArrayConverterInner<T> : JsonConverter<T>
{
private static readonly ConcurrentDictionary<Type, List<ArrayPropertyInfo>> _typeAttributesCache = new ConcurrentDictionary<Type, List<ArrayPropertyInfo>>();
private static readonly ConcurrentDictionary<Type, JsonSerializerOptions> _converterOptionsCache = new ConcurrentDictionary<Type, JsonSerializerOptions>();
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
@@ -108,7 +108,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
return default;
var result = Activator.CreateInstance(typeToConvert);
return (T)ParseObject(ref reader, result, typeToConvert);
return (T)ParseObject(ref reader, result, typeToConvert, options);
}
private static bool IsSimple(Type type)
@@ -148,7 +148,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
return attributes;
}
private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType)
private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType, JsonSerializerOptions options)
{
if (reader.TokenType != JsonTokenType.StartArray)
throw new Exception("Not an array");
@@ -175,15 +175,24 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
object? value = null;
if (attribute.JsonConverterType != null)
{
// Has JsonConverter attribute
var options = new JsonSerializerOptions();
options.Converters.Add((JsonConverter)Activator.CreateInstance(attribute.JsonConverterType));
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
if (!_converterOptionsCache.TryGetValue(attribute.JsonConverterType, out var newOptions))
{
var converter = (JsonConverter)Activator.CreateInstance(attribute.JsonConverterType);
newOptions = new JsonSerializerOptions
{
NumberHandling = SerializerOptions.WithConverters.NumberHandling,
PropertyNameCaseInsensitive = SerializerOptions.WithConverters.PropertyNameCaseInsensitive,
Converters = { converter },
};
_converterOptionsCache.TryAdd(attribute.JsonConverterType, newOptions);
}
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, newOptions);
}
else if (attribute.DefaultDeserialization)
{
// Use default deserialization
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType);
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
}
else
{
@@ -194,12 +203,15 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
JsonTokenType.True => true,
JsonTokenType.String => reader.GetString(),
JsonTokenType.Number => reader.GetDecimal(),
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType),
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType, options),
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
};
}
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
if (targetType.IsAssignableFrom(value?.GetType()))
attribute.PropertyInfo.SetValue(result, value == null ? null : value);
else
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
}
index++;
@@ -50,6 +50,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
catch (Exception ex)
{
var info = $"Deserialize unknown Exception: {ex.Message}";
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
}
}
/// <inheritdoc />
+3 -3
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>8.1.0</PackageVersion>
<AssemblyVersion>8.1.0</AssemblyVersion>
<FileVersion>8.1.0</FileVersion>
<PackageVersion>8.1.1</PackageVersion>
<AssemblyVersion>8.1.1</AssemblyVersion>
<FileVersion>8.1.1</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags>
<RepositoryType>git</RepositoryType>
@@ -19,7 +19,12 @@ namespace CryptoExchange.Net.Requests
if (client == null)
{
var handler = new HttpClientHandler();
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
catch (PlatformNotSupportedException) { }
if (proxy != null)
{
handler.Proxy = new WebProxy
@@ -14,4 +14,19 @@
/// </summary>
public bool Authenticated { get; set; }
}
/// <summary>
/// Dedicated connection state
/// </summary>
public class DedicatedConnectionState
{
/// <summary>
/// Whether the connection is a dedicated request connection
/// </summary>
public bool IsDedicatedRequestConnection { get; set; }
/// <summary>
/// Whether the dedication request connection should be authenticated
/// </summary>
public bool Authenticated { get; set; }
}
}
@@ -186,9 +186,9 @@ namespace CryptoExchange.Net.Sockets
}
/// <summary>
/// Whether this connection should be kept alive even when there is no subscription
/// Info on whether this connection is a dedicated request connection
/// </summary>
public bool DedicatedRequestConnection { get; internal set; }
public DedicatedConnectionState DedicatedRequestConnection { get; internal set; } = new DedicatedConnectionState();
private bool _pausedActivity;
private readonly object _listenersLock;
@@ -618,7 +618,7 @@ namespace CryptoExchange.Net.Sockets
bool shouldCloseConnection;
lock (_listenersLock)
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection;
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
if (!anyDuplicateSubscription)
{
@@ -841,7 +841,7 @@ namespace CryptoExchange.Net.Sockets
if (!_socket.IsOpen)
return new CallResult(new WebError("Socket not connected"));
if (!DedicatedRequestConnection)
if (!DedicatedRequestConnection.IsDedicatedRequestConnection)
{
bool anySubscriptions;
lock (_listenersLock)
@@ -859,7 +859,7 @@ namespace CryptoExchange.Net.Sockets
lock (_listenersLock)
{
anyAuthenticated = _listeners.OfType<Subscription>().Any(s => s.Authenticated)
|| (DedicatedRequestConnection && ApiClient.AuthenticationProvider != null);
|| (DedicatedRequestConnection.IsDedicatedRequestConnection && DedicatedRequestConnection.Authenticated);
}
if (anyAuthenticated)
+16 -16
View File
@@ -5,22 +5,22 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="10.7.0" />
<PackageReference Include="Bitfinex.Net" Version="7.8.2" />
<PackageReference Include="BitMart.Net" Version="1.4.0" />
<PackageReference Include="Bybit.Net" Version="3.14.3" />
<PackageReference Include="CoinEx.Net" Version="7.7.2" />
<PackageReference Include="CryptoCom.Net" Version="1.0.1" />
<PackageReference Include="GateIo.Net" Version="1.9.0" />
<PackageReference Include="JK.BingX.Net" Version="1.11.2" />
<PackageReference Include="JK.Bitget.Net" Version="1.10.4" />
<PackageReference Include="JK.Mexc.Net" Version="1.9.0" />
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.1.2" />
<PackageReference Include="JKorf.HTX.Net" Version="6.2.0" />
<PackageReference Include="KrakenExchange.Net" Version="5.0.2" />
<PackageReference Include="Kucoin.Net" Version="5.16.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
<PackageReference Include="Binance.Net" Version="10.8.0" />
<PackageReference Include="Bitfinex.Net" Version="7.9.0" />
<PackageReference Include="BitMart.Net" Version="1.5.0" />
<PackageReference Include="Bybit.Net" Version="3.15.0" />
<PackageReference Include="CoinEx.Net" Version="7.8.0" />
<PackageReference Include="CryptoCom.Net" Version="1.1.0" />
<PackageReference Include="GateIo.Net" Version="1.10.0" />
<PackageReference Include="JK.BingX.Net" Version="1.12.0" />
<PackageReference Include="JK.Bitget.Net" Version="1.11.0" />
<PackageReference Include="JK.Mexc.Net" Version="1.10.0" />
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.2.0" />
<PackageReference Include="JKorf.HTX.Net" Version="6.3.0" />
<PackageReference Include="KrakenExchange.Net" Version="5.1.0" />
<PackageReference Include="Kucoin.Net" Version="5.17.0" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
</ItemGroup>
</Project>
+1 -1
View File
@@ -74,7 +74,7 @@
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
{ "CryptoCom", cryptocomFactory.CreateExchange("ETH_BTC") },
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
{ "HTX", htxFactory.CreateSpot("ethbtc") },
{ "Kraken", krakenFactory.CreateSpot("ETH/XBT") },
+111
View File
@@ -0,0 +1,111 @@
@page "/Trackers"
@using System.Collections.Concurrent
@using System.Timers
@using Binance.Net.Interfaces
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces
@using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Interfaces
@using CryptoCom.Net.Interfaces
@using CryptoExchange.Net.SharedApis
@using CryptoExchange.Net.Trackers.Trades
@using GateIo.Net.Interfaces
@using HTX.Net.Interfaces
@using Kraken.Net.Interfaces
@using Kucoin.Net.Clients
@using Kucoin.Net.Interfaces
@using Mexc.Net.Interfaces
@using OKX.Net.Interfaces;
@inject IBinanceTrackerFactory binanceFactory
@inject IBingXTrackerFactory bingXFactory
@inject IBitfinexTrackerFactory bitfinexFactory
@inject IBitgetTrackerFactory bitgetFactory
@inject IBitMartTrackerFactory bitmartFactory
@inject IBybitTrackerFactory bybitFactory
@inject ICoinbaseTrackerFactory coinbaseFactory
@inject ICoinExTrackerFactory coinExFactory
@inject ICryptoComTrackerFactory cryptocomFactory
@inject IGateIoTrackerFactory gateioFactory
@inject IHTXTrackerFactory htxFactory
@inject IKrakenTrackerFactory krakenFactory
@inject IKucoinTrackerFactory kucoinFactory
@inject IMexcTrackerFactory mexcFactory
@inject IOKXTrackerFactory okxFactory
@implements IDisposable
<h3>ETH-BTC trade Trackers, live updates:</h3>
<div style="display:flex; flex-wrap: wrap;">
@foreach (var tracker in _trackers.OrderBy(p => p.Exchange))
{
<div style="margin-bottom: 20px; flex: 1; min-width: 700px;">
<h4>@tracker.Exchange</h4>
@foreach(var line in GetInfo(tracker))
{
<div>@line</div>
}
</div>
}
</div>
@code{
private List<ITradeTracker> _trackers = new List<ITradeTracker>();
private Timer _timer;
protected override async Task OnInitializedAsync()
{
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
_trackers = new List<ITradeTracker>
{
{ binanceFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bingXFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ coinExFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ cryptocomFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ gateioFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ htxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ krakenFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ kucoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ mexcFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ okxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
};
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
// Use a manual update timer so the page isn't refreshed too often
_timer = new Timer(500);
_timer.Start();
_timer.Elapsed += (o, e) => InvokeAsync(StateHasChanged);
}
private string[] GetInfo(ITradeTracker tracker)
{
var secondLastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));
var lastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-1));
var compare = lastMinute.CompareTo(secondLastMinute);
return [
$"{tracker.SymbolName} | {tracker.Status} - Synced from {tracker.SyncedFrom}",
$"Total trades: {tracker.Count}",
$"Trades last minute: {lastMinute.TradeCount}, minute before: {secondLastMinute.TradeCount}",
$"Average weighted price: {lastMinute.VolumeWeightedAveragePrice}, minute before: {secondLastMinute.VolumeWeightedAveragePrice}, dif: {compare.VolumeWeightedAveragePriceDif.PercentageDifference}%"
];
}
public void Dispose()
{
_timer.Stop();
_timer.Dispose();
foreach (var tracker in _trackers.Where(b => b.Status != CryptoExchange.Net.Objects.SyncStatus.Disconnected))
// It's not necessary to wait for this
_ = tracker.StopAsync();
}
}
@@ -27,6 +27,11 @@
Order books
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="Trackers">
Trackers
</NavLink>
</li>
</ul>
</div>
+14 -14
View File
@@ -6,20 +6,20 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="10.7.0" />
<PackageReference Include="Bitfinex.Net" Version="7.8.2" />
<PackageReference Include="BitMart.Net" Version="1.4.0" />
<PackageReference Include="Bybit.Net" Version="3.14.3" />
<PackageReference Include="CoinEx.Net" Version="7.7.2" />
<PackageReference Include="CryptoCom.Net" Version="1.0.1" />
<PackageReference Include="GateIo.Net" Version="1.9.0" />
<PackageReference Include="JK.Bitget.Net" Version="1.10.4" />
<PackageReference Include="JK.Mexc.Net" Version="1.9.0" />
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.1.2" />
<PackageReference Include="JKorf.HTX.Net" Version="6.2.0" />
<PackageReference Include="KrakenExchange.Net" Version="5.0.2" />
<PackageReference Include="Kucoin.Net" Version="5.16.0" />
<PackageReference Include="Binance.Net" Version="10.8.0" />
<PackageReference Include="Bitfinex.Net" Version="7.9.0" />
<PackageReference Include="BitMart.Net" Version="1.5.0" />
<PackageReference Include="Bybit.Net" Version="3.15.0" />
<PackageReference Include="CoinEx.Net" Version="7.8.0" />
<PackageReference Include="CryptoCom.Net" Version="1.1.0" />
<PackageReference Include="GateIo.Net" Version="1.10.0" />
<PackageReference Include="JK.Bitget.Net" Version="1.11.0" />
<PackageReference Include="JK.Mexc.Net" Version="1.10.0" />
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="1.2.0" />
<PackageReference Include="JKorf.HTX.Net" Version="6.3.0" />
<PackageReference Include="KrakenExchange.Net" Version="5.1.0" />
<PackageReference Include="Kucoin.Net" Version="5.17.0" />
</ItemGroup>
</Project>
+3 -3
View File
@@ -8,9 +8,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="10.7.0" />
<PackageReference Include="BitMart.Net" Version="1.4.0" />
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
<PackageReference Include="Binance.Net" Version="10.8.0" />
<PackageReference Include="BitMart.Net" Version="1.5.0" />
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
</ItemGroup>
</Project>
+5
View File
@@ -49,6 +49,11 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes
* Version 8.1.1 - 01 Nov 2024
* Fixed socket connections trying to authenticated connection when it's marked as dedicated request connection even when no authentication is needed
* Fixed System.Text.Json ArrayConverter not passing serializer options to nested deserialization
* Fixed System.Text.Json ArrayConverter creating new serializer options each time a JsonConverter attribute is encountered
* Version 8.1.0 - 28 Oct 2024
* Added KlineTracker and TradeTracker implementation
* Added Side to SharedTrade model
+4 -1
View File
@@ -3265,6 +3265,9 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
<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>
The basic workings of the trackers are simple, an initial request is made for a snapshot of the history (or a partial snapshot depending on what the API supports). At the same time a websocket subscription is set up to provide the tracker with new data.
</p>
<p>
Currently there are 2 different trackers available, the <code>TradeTracker</code> and the <code>KlineTracker</code>.
@@ -3653,7 +3656,7 @@ await tracker.StopAsync();
<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:
Using the <code>tracker.GetData(fromTime, toTime)</code> method the trackers exposes the data, or a subset of the data, can be retrieved:
<pre><code>// Get all the data currently tracked:
var data = tracker.GetData();