mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 00:43:03 +00:00
Compare commits
37 Commits
8.6.0
...
9.0.0-beta3
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d6267da93 | |||
| 8def7f32af | |||
| ac295de9f6 | |||
| d412e0895e | |||
| 1f9e2b4fcb | |||
| b13cff5a95 | |||
| 4c050744ad | |||
| 3b15c35a02 | |||
| cd78dbf575 | |||
| a258532d6a | |||
| d2a87a1069 | |||
| e07f24ea0a | |||
| 024e8dcfe2 | |||
| 4bb5aae40a | |||
| dec94678ec | |||
| 1a49fc8251 | |||
| 29b0875960 | |||
| 976ccab1da | |||
| 02bbd37bb6 | |||
| 1bbbec7f2b | |||
| 0262f04913 | |||
| fd1ec17d72 | |||
| 4bdad7fe0c | |||
| 74f73dc790 | |||
| 0527a8a76e | |||
| c693eb8c02 | |||
| 3eb28c7fed | |||
| 618c4922b9 | |||
| c81b15861d | |||
| 4a5832cccd | |||
| 4e47c4cbdf | |||
| 2af1520ecc | |||
| cf397af3ab | |||
| a1479705e2 | |||
| 175e23f110 | |||
| 9b7019ded2 | |||
| 7904aa9ba7 |
@@ -106,6 +106,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
for(var i = 1; i <= 10; i++)
|
for(var i = 1; i <= 10; i++)
|
||||||
{
|
{
|
||||||
evnt.Set();
|
evnt.Set();
|
||||||
|
await Task.Delay(1); // Wait for the continuation.
|
||||||
Assert.That(10 - i == waiters.Count(w => w.Status != TaskStatus.RanToCompletion));
|
Assert.That(10 - i == waiters.Count(w => w.Status != TaskStatus.RanToCompletion));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,12 +176,12 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
for (var i = 0; i < requests + 1; i++)
|
for (var i = 0; i < requests + 1; i++)
|
||||||
{
|
{
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(i == requests? triggered : !triggered);
|
Assert.That(i == requests? triggered : !triggered);
|
||||||
}
|
}
|
||||||
triggered = false;
|
triggered = false;
|
||||||
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
|
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(!triggered);
|
Assert.That(!triggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
bool expected = i == 1 ? (expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
bool expected = i == 1 ? (expectLimiting ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
||||||
Assert.That(expected);
|
Assert.That(expected);
|
||||||
}
|
}
|
||||||
@@ -222,9 +222,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitEvent evnt = null;
|
RateLimitEvent evnt = null;
|
||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(evnt == null);
|
Assert.That(evnt == null);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(expectLimiting ? evnt != null : evnt == null);
|
Assert.That(expectLimiting ? evnt != null : evnt == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,12 +243,12 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
|
|
||||||
for (var i = 0; i < requests + 1; i++)
|
for (var i = 0; i < requests + 1; i++)
|
||||||
{
|
{
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(i == requests ? triggered : !triggered);
|
Assert.That(i == requests ? triggered : !triggered);
|
||||||
}
|
}
|
||||||
triggered = false;
|
triggered = false;
|
||||||
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
|
await Task.Delay((int)Math.Round(perSeconds * 1000) + 10);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(!triggered);
|
Assert.That(!triggered);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
||||||
Assert.That(expected);
|
Assert.That(expected);
|
||||||
}
|
}
|
||||||
@@ -286,7 +286,7 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
for (var i = 0; i < 2; i++)
|
for (var i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
bool expected = i == 1 ? (expectLimited ? evnt.DelayTime > TimeSpan.Zero : evnt == null) : evnt == null;
|
||||||
Assert.That(expected);
|
Assert.That(expected);
|
||||||
}
|
}
|
||||||
@@ -309,9 +309,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitEvent evnt = null;
|
RateLimitEvent evnt = null;
|
||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", key1, 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(evnt == null);
|
Assert.That(evnt == null);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", key2, 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(expectLimited ? evnt != null : evnt == null);
|
Assert.That(expectLimited ? evnt != null : evnt == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,9 +328,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitEvent evnt = null;
|
RateLimitEvent evnt = null;
|
||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(evnt == null);
|
Assert.That(evnt == null);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition2, "https://test.com", null, 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(expectLimited ? evnt != null : evnt == null);
|
Assert.That(expectLimited ? evnt != null : evnt == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,9 +348,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitEvent evnt = null;
|
RateLimitEvent evnt = null;
|
||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host1, "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(evnt == null);
|
Assert.That(evnt == null);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Request, requestDefinition1, host2, "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(expectLimited ? evnt != null : evnt == null);
|
Assert.That(expectLimited ? evnt != null : evnt == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,9 +365,9 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitEvent evnt = null;
|
RateLimitEvent evnt = null;
|
||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, default);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host1, "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(evnt == null);
|
Assert.That(evnt == null);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, default);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), host2, "123", 1, RateLimitingBehaviour.Wait, null, default);
|
||||||
Assert.That(expectLimited ? evnt != null : evnt == null);
|
Assert.That(expectLimited ? evnt != null : evnt == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,8 +381,8 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
rateLimiter.RateLimitTriggered += (x) => { evnt = x; };
|
||||||
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2));
|
var ct = new CancellationTokenSource(TimeSpan.FromSeconds(0.2));
|
||||||
|
|
||||||
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, ct.Token);
|
var result1 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
|
||||||
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, ct.Token);
|
var result2 = await rateLimiter.ProcessAsync(new TraceLogger(), 1, RateLimitItemType.Connection, new RequestDefinition("1", HttpMethod.Get), "https://test.com", "123", 1, RateLimitingBehaviour.Wait, null, ct.Token);
|
||||||
Assert.That(result2.Error, Is.TypeOf<CancellationRequestedError>());
|
Assert.That(result2.Error, Is.TypeOf<CancellationRequestedError>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace CryptoExchange.Net.Attributes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Map a enum entry to string values
|
/// Map a enum entry to string values
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Field)]
|
||||||
public class MapAttribute : Attribute
|
public class MapAttribute : Attribute
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -443,16 +443,26 @@ namespace CryptoExchange.Net.Authentication
|
|||||||
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
|
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get millisecond timestamp as a long including the time sync offset from the api client
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="apiClient"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected long GetMillisecondTimestampLong(RestApiClient apiClient)
|
||||||
|
{
|
||||||
|
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return the serialized request body
|
/// Return the serialized request body
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="serializer"></param>
|
/// <param name="serializer"></param>
|
||||||
/// <param name="parameters"></param>
|
/// <param name="parameters"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters)
|
protected static string GetSerializedBody(IMessageSerializer serializer, IDictionary<string, object> parameters)
|
||||||
{
|
{
|
||||||
if (parameters.Count == 1 && parameters.ContainsKey(Constants.BodyPlaceHolderKey))
|
if (parameters.Count == 1 && parameters.TryGetValue(Constants.BodyPlaceHolderKey, out object? value))
|
||||||
return serializer.Serialize(parameters[Constants.BodyPlaceHolderKey]);
|
return serializer.Serialize(value);
|
||||||
else
|
else
|
||||||
return serializer.Serialize(parameters);
|
return serializer.Serialize(parameters);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,9 +38,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool OutputOriginalData { get; }
|
public bool OutputOriginalData { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc />
|
||||||
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
|
|
||||||
/// </summary>
|
|
||||||
public bool Authenticated => ApiOptions.ApiCredentials != null || ClientOptions.ApiCredentials != null;
|
public bool Authenticated => ApiOptions.ApiCredentials != null || ClientOptions.ApiCredentials != null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -57,7 +55,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Logger</param>
|
/// <param name="logger">Logger</param>
|
||||||
/// <param name="outputOriginalData">Should data from this client include the orginal data in the call result</param>
|
/// <param name="outputOriginalData">Should data from this client include the original data in the call result</param>
|
||||||
/// <param name="baseAddress">Base address for this API client</param>
|
/// <param name="baseAddress">Base address for this API client</param>
|
||||||
/// <param name="apiCredentials">Api credentials</param>
|
/// <param name="apiCredentials">Api credentials</param>
|
||||||
/// <param name="clientOptions">Client options</param>
|
/// <param name="clientOptions">Client options</param>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal ILogger _logger;
|
protected internal ILogger _logger;
|
||||||
|
|
||||||
private object _versionLock = new object();
|
private readonly object _versionLock = new object();
|
||||||
private Version _exchangeVersion;
|
private Version _exchangeVersion;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
tasks.Add(client.ReconnectAsync());
|
tasks.Add(client.ReconnectAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
|
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +107,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
result.AppendLine(client.GetSubscriptionsState());
|
result.AppendLine(client.GetSubscriptionsState());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToString();
|
return result.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +122,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
result.Add(client.GetState());
|
result.Add(client.GetState());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class CryptoBaseClient : IDisposable
|
public class CryptoBaseClient : IDisposable
|
||||||
{
|
{
|
||||||
private Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
|
private readonly Dictionary<Type, object> _serviceCache = new Dictionary<Type, object>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Service provider
|
/// Service provider
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Memory cache
|
/// Memory cache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static MemoryCache _cache = new MemoryCache();
|
private readonly static MemoryCache _cache = new MemoryCache();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
@@ -155,6 +155,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||||
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
||||||
|
/// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
protected virtual Task<WebCallResult<T>> SendAsync<T>(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
@@ -163,7 +164,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null,
|
int? weight = null,
|
||||||
int? weightSingleLimiter = null) where T : class
|
int? weightSingleLimiter = null,
|
||||||
|
string? rateLimitKeySuffix = null)
|
||||||
{
|
{
|
||||||
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
|
||||||
return SendAsync<T>(
|
return SendAsync<T>(
|
||||||
@@ -174,7 +176,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
cancellationToken,
|
cancellationToken,
|
||||||
additionalHeaders,
|
additionalHeaders,
|
||||||
weight,
|
weight,
|
||||||
weightSingleLimiter);
|
weightSingleLimiter,
|
||||||
|
rateLimitKeySuffix);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -189,6 +192,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
/// <param name="weight">Override the request weight for this request definition, for example when the weight depends on the parameters</param>
|
||||||
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
||||||
|
/// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
protected virtual async Task<WebCallResult<T>> SendAsync<T>(
|
||||||
string baseAddress,
|
string baseAddress,
|
||||||
@@ -198,7 +202,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null,
|
int? weight = null,
|
||||||
int? weightSingleLimiter = null) where T : class
|
int? weightSingleLimiter = null,
|
||||||
|
string? rateLimitKeySuffix = null)
|
||||||
{
|
{
|
||||||
string? cacheKey = null;
|
string? cacheKey = null;
|
||||||
if (ShouldCache(definition))
|
if (ShouldCache(definition))
|
||||||
@@ -222,7 +227,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
currentTry++;
|
currentTry++;
|
||||||
var requestId = ExchangeHelpers.NextId();
|
var requestId = ExchangeHelpers.NextId();
|
||||||
|
|
||||||
var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter).ConfigureAwait(false);
|
var prepareResult = await PrepareAsync(requestId, baseAddress, definition, cancellationToken, additionalHeaders, weight, weightSingleLimiter, rateLimitKeySuffix).ConfigureAwait(false);
|
||||||
if (!prepareResult)
|
if (!prepareResult)
|
||||||
return new WebCallResult<T>(prepareResult.Error!);
|
return new WebCallResult<T>(prepareResult.Error!);
|
||||||
|
|
||||||
@@ -236,10 +241,17 @@ namespace CryptoExchange.Net.Clients
|
|||||||
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
_logger.RestApiSendRequest(request.RequestId, definition, request.Content, string.IsNullOrEmpty(request.Uri.Query) ? "-" : request.Uri.Query, string.Join(", ", request.GetHeaders().Select(h => h.Key + $"=[{string.Join(",", h.Value)}]")));
|
||||||
TotalRequestsMade++;
|
TotalRequestsMade++;
|
||||||
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
var result = await GetResponseAsync<T>(request, definition.RateLimitGate, cancellationToken).ConfigureAwait(false);
|
||||||
if (!result)
|
if (result.Error is not CancellationRequestedError)
|
||||||
_logger.RestApiErrorReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), result.Error?.ToString());
|
{
|
||||||
|
if (!result)
|
||||||
|
_logger.RestApiErrorReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), result.Error?.ToString());
|
||||||
|
else
|
||||||
|
_logger.RestApiResponseReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
_logger.RestApiResponseReceived(result.RequestId, result.ResponseStatusCode, (long)Math.Floor(result.ResponseTime!.Value.TotalMilliseconds), OutputOriginalData ? result.OriginalData : "[Data only available when OutputOriginal = true]");
|
{
|
||||||
|
_logger.RestApiCancellationRequested(result.RequestId);
|
||||||
|
}
|
||||||
|
|
||||||
if (await ShouldRetryRequestAsync(definition.RateLimitGate, result, currentTry).ConfigureAwait(false))
|
if (await ShouldRetryRequestAsync(definition.RateLimitGate, result, currentTry).ConfigureAwait(false))
|
||||||
continue;
|
continue;
|
||||||
@@ -264,6 +276,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="additionalHeaders">Additional headers for this request</param>
|
/// <param name="additionalHeaders">Additional headers for this request</param>
|
||||||
/// <param name="weight">Override the request weight for this request</param>
|
/// <param name="weight">Override the request weight for this request</param>
|
||||||
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
/// <param name="weightSingleLimiter">Specify the weight to apply to the individual rate limit guard for this request</param>
|
||||||
|
/// <param name="rateLimitKeySuffix">An additional optional suffix for the key selector</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
/// <exception cref="Exception"></exception>
|
/// <exception cref="Exception"></exception>
|
||||||
protected virtual async Task<CallResult> PrepareAsync(
|
protected virtual async Task<CallResult> PrepareAsync(
|
||||||
@@ -273,7 +286,8 @@ namespace CryptoExchange.Net.Clients
|
|||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
Dictionary<string, string>? additionalHeaders = null,
|
Dictionary<string, string>? additionalHeaders = null,
|
||||||
int? weight = null,
|
int? weight = null,
|
||||||
int? weightSingleLimiter = null)
|
int? weightSingleLimiter = null,
|
||||||
|
string? rateLimitKeySuffix = null)
|
||||||
{
|
{
|
||||||
// Time sync
|
// Time sync
|
||||||
if (definition.Authenticated)
|
if (definition.Authenticated)
|
||||||
@@ -308,7 +322,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
if (ClientOptions.RateLimiterEnabled)
|
if (ClientOptions.RateLimiterEnabled)
|
||||||
{
|
{
|
||||||
var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false);
|
var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false);
|
||||||
if (!limitResult)
|
if (!limitResult)
|
||||||
return new CallResult(limitResult.Error!);
|
return new CallResult(limitResult.Error!);
|
||||||
}
|
}
|
||||||
@@ -323,7 +337,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (ClientOptions.RateLimiterEnabled)
|
if (ClientOptions.RateLimiterEnabled)
|
||||||
{
|
{
|
||||||
var singleRequestWeight = weightSingleLimiter ?? 1;
|
var singleRequestWeight = weightSingleLimiter ?? 1;
|
||||||
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false);
|
var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, definition.LimitGuard, RateLimitItemType.Request, definition, baseAddress, AuthenticationProvider?._credentials.Key, singleRequestWeight, ClientOptions.RateLimitingBehaviour, rateLimitKeySuffix, cancellationToken).ConfigureAwait(false);
|
||||||
if (!limitResult)
|
if (!limitResult)
|
||||||
return new CallResult(limitResult.Error!);
|
return new CallResult(limitResult.Error!);
|
||||||
}
|
}
|
||||||
@@ -609,7 +623,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
if (ClientOptions.RateLimiterEnabled)
|
if (ClientOptions.RateLimiterEnabled)
|
||||||
{
|
{
|
||||||
var limitResult = await gate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, new RequestDefinition(uri.AbsolutePath.TrimStart('/'), method) { Authenticated = signed }, uri.Host, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false);
|
var limitResult = await gate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, new RequestDefinition(uri.AbsolutePath.TrimStart('/'), method) { Authenticated = signed }, uri.Host, AuthenticationProvider?._credentials.Key, requestWeight, ClientOptions.RateLimitingBehaviour, null, cancellationToken).ConfigureAwait(false);
|
||||||
if (!limitResult)
|
if (!limitResult)
|
||||||
return new CallResult<IRequest>(limitResult.Error!);
|
return new CallResult<IRequest>(limitResult.Error!);
|
||||||
}
|
}
|
||||||
@@ -826,7 +840,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
if (parameterPosition == HttpMethodParameterPosition.InUri)
|
||||||
{
|
{
|
||||||
foreach (var parameter in parameters)
|
foreach (var parameter in parameters)
|
||||||
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString()!);
|
uri = uri.AddQueryParameter(parameter.Key, parameter.Value.ToString()!);
|
||||||
}
|
}
|
||||||
|
|
||||||
var headers = new Dictionary<string, string>();
|
var headers = new Dictionary<string, string>();
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!socketConnections.Any())
|
if (socketConnections.IsEmpty)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return socketConnections.Sum(s => s.Value.IncomingKbps);
|
return socketConnections.Sum(s => s.Value.IncomingKbps);
|
||||||
@@ -97,7 +97,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!socketConnections.Any())
|
if (socketConnections.IsEmpty)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return socketConnections.Sum(s => s.Value.UserSubscriptionCount);
|
return socketConnections.Sum(s => s.Value.UserSubscriptionCount);
|
||||||
@@ -510,7 +510,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
|
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
{
|
{
|
||||||
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget))
|
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
|
||||||
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
|
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
|
||||||
return new CallResult<SocketConnection>(connection);
|
return new CallResult<SocketConnection>(connection);
|
||||||
}
|
}
|
||||||
@@ -598,9 +598,10 @@ namespace CryptoExchange.Net.Clients
|
|||||||
KeepAliveInterval = KeepAliveInterval,
|
KeepAliveInterval = KeepAliveInterval,
|
||||||
ReconnectInterval = ClientOptions.ReconnectInterval,
|
ReconnectInterval = ClientOptions.ReconnectInterval,
|
||||||
RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null,
|
RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null,
|
||||||
RateLimitingBehaviour = ClientOptions.RateLimitingBehaviour,
|
RateLimitingBehavior = ClientOptions.RateLimitingBehaviour,
|
||||||
Proxy = ClientOptions.Proxy,
|
Proxy = ClientOptions.Proxy,
|
||||||
Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout
|
Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout,
|
||||||
|
ReceiveBufferSize = ClientOptions.ReceiveBufferSize,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -670,8 +671,11 @@ namespace CryptoExchange.Net.Clients
|
|||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
{
|
{
|
||||||
var socketList = socketConnections.Values;
|
var socketList = socketConnections.Values;
|
||||||
foreach (var connection in socketList.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection))
|
foreach (var connection in socketList)
|
||||||
tasks.Add(connection.CloseAsync());
|
{
|
||||||
|
foreach(var subscription in connection.Subscriptions.Where(x => x.UserSubscription))
|
||||||
|
tasks.Add(connection.CloseAsync(subscription));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
|
await Task.WhenAll(tasks.ToArray()).ConfigureAwait(false);
|
||||||
@@ -718,7 +722,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
base.SetOptions(options);
|
base.SetOptions(options);
|
||||||
|
|
||||||
if ((!previousProxyIsSet && options.Proxy == null)
|
if ((!previousProxyIsSet && options.Proxy == null)
|
||||||
|| !socketConnections.Any())
|
|| socketConnections.IsEmpty)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Node accessor
|
/// Node accessor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct NodeAccessor
|
public readonly struct NodeAccessor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index
|
/// Index
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace CryptoExchange.Net.Converters.MessageParsing
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Message access definition
|
/// Message access definition
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct MessagePath : IEnumerable<NodeAccessor>
|
public readonly struct MessagePath : IEnumerable<NodeAccessor>
|
||||||
{
|
{
|
||||||
private List<NodeAccessor> _path;
|
private readonly List<NodeAccessor> _path;
|
||||||
|
|
||||||
internal void Add(NodeAccessor node)
|
internal void Add(NodeAccessor node)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,8 +87,12 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
|
|
||||||
if (prop.JsonConverterType == null && IsSimple(prop.PropertyInfo.PropertyType))
|
if (prop.JsonConverterType == null && IsSimple(prop.PropertyInfo.PropertyType))
|
||||||
{
|
{
|
||||||
if (prop.PropertyInfo.PropertyType == typeof(string))
|
if (prop.TargetType == typeof(string))
|
||||||
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
|
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
|
||||||
|
else if(prop.TargetType.IsEnum)
|
||||||
|
writer.WriteStringValue(EnumConverter.GetString(objValue));
|
||||||
|
else if (prop.TargetType == typeof(bool))
|
||||||
|
writer.WriteBooleanValue((bool)objValue);
|
||||||
else
|
else
|
||||||
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
|
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
|
||||||
}
|
}
|
||||||
@@ -187,12 +191,12 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
_converterOptionsCache.TryAdd(attribute.JsonConverterType, newOptions);
|
_converterOptionsCache.TryAdd(attribute.JsonConverterType, newOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, newOptions);
|
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, newOptions);
|
||||||
}
|
}
|
||||||
else if (attribute.DefaultDeserialization)
|
else if (attribute.DefaultDeserialization)
|
||||||
{
|
{
|
||||||
// Use default deserialization
|
// Use default deserialization
|
||||||
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
|
value = JsonDocument.ParseValue(ref reader).Deserialize(attribute.PropertyInfo.PropertyType, SerializerOptions.WithConverters);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
if (reader.TokenType is JsonTokenType.Number)
|
if (reader.TokenType is JsonTokenType.Number)
|
||||||
{
|
{
|
||||||
var longValue = reader.GetDouble();
|
var longValue = reader.GetDouble();
|
||||||
if (longValue == 0 || longValue == -1)
|
if (longValue == 0 || longValue < 0)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
return ParseFromDouble(longValue);
|
return ParseFromDouble(longValue);
|
||||||
|
|||||||
@@ -172,6 +172,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (objectType.IsDefined(typeof(FlagsAttribute)))
|
||||||
|
{
|
||||||
|
var intValue = int.Parse(value);
|
||||||
|
result = Enum.ToObject(objectType, intValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// If no explicit mapping is found try to parse string
|
// If no explicit mapping is found try to parse string
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected JsonDocument? _document;
|
protected JsonDocument? _document;
|
||||||
|
|
||||||
private static JsonSerializerOptions _serializerOptions = SerializerOptions.WithConverters;
|
private static readonly JsonSerializerOptions _serializerOptions = SerializerOptions.WithConverters;
|
||||||
private JsonSerializerOptions? _customSerializerOptions;
|
private readonly JsonSerializerOptions? _customSerializerOptions;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsJson { get; set; }
|
public bool IsJson { get; set; }
|
||||||
@@ -148,6 +148,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return value.Value.Deserialize<T>(_customSerializerOptions ?? _serializerOptions);
|
return value.Value.Deserialize<T>(_customSerializerOptions ?? _serializerOptions);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +360,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string GetOriginalString() =>
|
public override string GetOriginalString() =>
|
||||||
// Netstandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
|
// NetStandard 2.0 doesn't support GetString from a ReadonlySpan<byte>, so use ToArray there instead
|
||||||
#if NETSTANDARD2_0
|
#if NETSTANDARD2_0
|
||||||
Encoding.UTF8.GetString(_bytes.ToArray());
|
Encoding.UTF8.GetString(_bytes.ToArray());
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<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>
|
<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.6.0</PackageVersion>
|
<PackageVersion>8.8.0</PackageVersion>
|
||||||
<AssemblyVersion>8.6.0</AssemblyVersion>
|
<AssemblyVersion>8.8.0</AssemblyVersion>
|
||||||
<FileVersion>8.6.0</FileVersion>
|
<FileVersion>8.8.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<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>
|
<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>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ namespace CryptoExchange.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique
|
/// Generate a new unique id. The id is statically stored so it is guaranteed to be unique
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static int NextId() => Interlocked.Increment(ref _lastId);
|
public static int NextId() => Interlocked.Increment(ref _lastId);
|
||||||
@@ -261,6 +261,7 @@ namespace CryptoExchange.Net
|
|||||||
if (price != null)
|
if (price != null)
|
||||||
{
|
{
|
||||||
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
|
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
|
||||||
|
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
|
||||||
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
|
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
|
||||||
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
|
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ namespace CryptoExchange.Net
|
|||||||
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
|
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formData.ToString()!;
|
return formData.ToString()!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,6 +287,7 @@ namespace CryptoExchange.Net
|
|||||||
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uriBuilder.Query = httpValueCollection.ToString();
|
uriBuilder.Query = httpValueCollection.ToString();
|
||||||
return uriBuilder.Uri;
|
return uriBuilder.Uri;
|
||||||
}
|
}
|
||||||
@@ -333,6 +335,7 @@ namespace CryptoExchange.Net
|
|||||||
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
httpValueCollection.Add(parameter.Key, parameter.Value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uriBuilder.Query = httpValueCollection.ToString();
|
uriBuilder.Query = httpValueCollection.ToString();
|
||||||
return uriBuilder.Uri;
|
return uriBuilder.Uri;
|
||||||
}
|
}
|
||||||
@@ -344,7 +347,7 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="name"></param>
|
/// <param name="name"></param>
|
||||||
/// <param name="value"></param>
|
/// <param name="value"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static Uri AddQueryParmeter(this Uri uri, string name, string value)
|
public static Uri AddQueryParameter(this Uri uri, string name, string value)
|
||||||
{
|
{
|
||||||
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
|
var httpValueCollection = HttpUtility.ParseQueryString(uri.Query);
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,11 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
string BaseAddress { get; }
|
string BaseAddress { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not API credentials have been configured for this client. Does not check the credentials are actually valid.
|
||||||
|
/// </summary>
|
||||||
|
bool Authenticated { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Format a base and quote asset to an exchange accepted symbol
|
/// Format a base and quote asset to an exchange accepted symbol
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -36,7 +41,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
|
/// Set new options. Note that when using a proxy this should be provided in the options even when already set before or it will be reset.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Api crentials type</typeparam>
|
/// <typeparam name="T">Api credentials type</typeparam>
|
||||||
/// <param name="options">Options to set</param>
|
/// <param name="options">Options to set</param>
|
||||||
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
|
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
int CurrentSubscriptions { get; }
|
int CurrentSubscriptions { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Incoming data kpbs
|
/// Incoming data Kbps
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double IncomingKbps { get; }
|
double IncomingKbps { get; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
Task StopAsync();
|
Task StopAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <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
|
/// Get the average price that a market order would fill at at the current order book state. This is no guarantee that an order of that quantity would actually be filled
|
||||||
/// at that price since between this calculation and the order placement the book might have changed.
|
/// at that price since between this calculation and the order placement the book might have changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="quantity">The quantity in base asset to fill</param>
|
/// <param name="quantity">The quantity in base asset to fill</param>
|
||||||
@@ -115,7 +115,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the amount of base asset which can be traded with the quote quantity when placing a market order at at the current order book state.
|
/// Get the amount of base asset which can be traded with the quote quantity when placing a market order at at the current order book state.
|
||||||
/// This is no guarentee that an order of that quantity would actually be fill the quantity returned by this since between this calculation and the order placement the book might have changed.
|
/// This is no guarantee that an order of that quantity would actually be fill the quantity returned by this since between this calculation and the order placement the book might have changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
|
/// <param name="quoteQuantity">The quantity in quote asset looking to trade</param>
|
||||||
/// <param name="type">The type</param>
|
/// <param name="type">The type</param>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
event Func<Task> OnReconnected;
|
event Func<Task> OnReconnected;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get reconntion url
|
/// Get reconnection url
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
|
Func<Task<Uri?>>? GetReconnectionUrl { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ namespace CryptoExchange.Net
|
|||||||
public static class LibraryHelpers
|
public static class LibraryHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client order id seperator
|
/// Client order id separator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ClientOrderIdSeperator = "JK";
|
public const string ClientOrderIdSeparator = "JK";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply broker id to a client order id
|
/// Apply broker id to a client order id
|
||||||
@@ -20,25 +20,25 @@ namespace CryptoExchange.Net
|
|||||||
/// <param name="clientOrderId"></param>
|
/// <param name="clientOrderId"></param>
|
||||||
/// <param name="brokerId"></param>
|
/// <param name="brokerId"></param>
|
||||||
/// <param name="maxLength"></param>
|
/// <param name="maxLength"></param>
|
||||||
/// <param name="allowValueAdjustement"></param>
|
/// <param name="allowValueAdjustment"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustement)
|
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustment)
|
||||||
{
|
{
|
||||||
var reservedLength = brokerId.Length + ClientOrderIdSeperator.Length;
|
var reservedLength = brokerId.Length + ClientOrderIdSeparator.Length;
|
||||||
|
|
||||||
if ((clientOrderId?.Length + reservedLength) > maxLength)
|
if ((clientOrderId?.Length + reservedLength) > maxLength)
|
||||||
return clientOrderId!;
|
return clientOrderId!;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(clientOrderId))
|
if (!string.IsNullOrEmpty(clientOrderId))
|
||||||
{
|
{
|
||||||
if (allowValueAdjustement)
|
if (allowValueAdjustment)
|
||||||
clientOrderId = brokerId + ClientOrderIdSeperator + clientOrderId;
|
clientOrderId = brokerId + ClientOrderIdSeparator + clientOrderId;
|
||||||
|
|
||||||
return clientOrderId!;
|
return clientOrderId!;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeperator, maxLength);
|
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeparator, maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
return clientOrderId;
|
return clientOrderId;
|
||||||
|
|||||||
+16
-4
@@ -33,8 +33,9 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
private static readonly Action<ILogger, int, Exception?> _receiveLoopStoppedWithException;
|
private static readonly Action<ILogger, int, Exception?> _receiveLoopStoppedWithException;
|
||||||
private static readonly Action<ILogger, int, Exception?> _receiveLoopFinished;
|
private static readonly Action<ILogger, int, Exception?> _receiveLoopFinished;
|
||||||
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
|
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
|
||||||
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimoutReconnect;
|
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimeoutReconnect;
|
||||||
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
|
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
|
||||||
|
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
|
||||||
|
|
||||||
static CryptoExchangeWebSocketClientLoggingExtension()
|
static CryptoExchangeWebSocketClientLoggingExtension()
|
||||||
{
|
{
|
||||||
@@ -168,7 +169,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
|
new EventId(1026, "StartingTaskForNoDataReceivedCheck"),
|
||||||
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
"[Sckt {SocketId}] starting task checking for no data received for {Timeout}");
|
||||||
|
|
||||||
_noDataReceiveTimoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
_noDataReceiveTimeoutReconnect = LoggerMessage.Define<int, TimeSpan?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
new EventId(1027, "NoDataReceiveTimeoutReconnect"),
|
||||||
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
"[Sckt {SocketId}] no data received for {Timeout}, reconnecting socket");
|
||||||
@@ -180,9 +181,14 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
|
|
||||||
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(1028, "SocketProcessingStateChanged"),
|
new EventId(1029, "SocketProcessingStateChanged"),
|
||||||
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
|
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
|
||||||
|
|
||||||
|
_socketPingTimeout = LoggerMessage.Define<int>(
|
||||||
|
LogLevel.Warning,
|
||||||
|
new EventId(1030, "SocketPingTimeout"),
|
||||||
|
"[Sckt {Id}] ping frame timeout; reconnecting socket");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SocketConnecting(
|
public static void SocketConnecting(
|
||||||
@@ -350,7 +356,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
public static void SocketNoDataReceiveTimoutReconnect(
|
public static void SocketNoDataReceiveTimoutReconnect(
|
||||||
this ILogger logger, int socketId, TimeSpan? timeSpan)
|
this ILogger logger, int socketId, TimeSpan? timeSpan)
|
||||||
{
|
{
|
||||||
_noDataReceiveTimoutReconnect(logger, socketId, timeSpan, null);
|
_noDataReceiveTimeoutReconnect(logger, socketId, timeSpan, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SocketProcessingStateChanged(
|
public static void SocketProcessingStateChanged(
|
||||||
@@ -358,5 +364,11 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
{
|
{
|
||||||
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
|
_socketProcessingStateChanged(logger, socketId, prevState, newState, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SocketPingTimeout(
|
||||||
|
this ILogger logger, int socketId)
|
||||||
|
{
|
||||||
|
_socketPingTimeout(logger, socketId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
private static readonly Action<ILogger, string, Exception?> _restApiCheckingCache;
|
private static readonly Action<ILogger, string, Exception?> _restApiCheckingCache;
|
||||||
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
|
private static readonly Action<ILogger, string, Exception?> _restApiCacheHit;
|
||||||
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
|
private static readonly Action<ILogger, string, Exception?> _restApiCacheNotHit;
|
||||||
|
private static readonly Action<ILogger, int?, Exception?> _restApiCancellationRequested;
|
||||||
|
|
||||||
static RestApiClientLoggingExtensions()
|
static RestApiClientLoggingExtensions()
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
|
|
||||||
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
|
_restApiFailedToSyncTime = LoggerMessage.Define<int, string>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(4002, "RestApifailedToSyncTime"),
|
new EventId(4002, "RestApiFailedToSyncTime"),
|
||||||
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
|
"[Req {RequestId}] Failed to sync time, aborting request: {ErrorMessage}");
|
||||||
|
|
||||||
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
|
_restApiNoApiCredentials = LoggerMessage.Define<int, string>(
|
||||||
@@ -84,6 +84,12 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(4011, "RestApiCacheNotHit"),
|
new EventId(4011, "RestApiCacheNotHit"),
|
||||||
"Cache not hit for key {Key}");
|
"Cache not hit for key {Key}");
|
||||||
|
|
||||||
|
_restApiCancellationRequested = LoggerMessage.Define<int?>(
|
||||||
|
LogLevel.Debug,
|
||||||
|
new EventId(4012, "RestApiCancellationRequested"),
|
||||||
|
"[Req {RequestId}] Request cancelled by user");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error)
|
public static void RestApiErrorReceived(this ILogger logger, int? requestId, HttpStatusCode? responseStatusCode, long responseTime, string? error)
|
||||||
@@ -145,5 +151,9 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
{
|
{
|
||||||
_restApiCacheNotHit(logger, key, null);
|
_restApiCacheNotHit(logger, key, null);
|
||||||
}
|
}
|
||||||
|
public static void RestApiCancellationRequested(this ILogger logger, int? requestId)
|
||||||
|
{
|
||||||
|
_restApiCancellationRequested(logger, requestId, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
|
private static readonly Action<ILogger, int, bool, Exception?> _activityPaused;
|
||||||
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
|
private static readonly Action<ILogger, int, Sockets.SocketConnection.SocketStatus, Sockets.SocketConnection.SocketStatus, Exception?> _socketStatusChanged;
|
||||||
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
|
private static readonly Action<ILogger, int, string?, Exception?> _failedReconnectProcessing;
|
||||||
private static readonly Action<ILogger, int, Exception?> _unkownExceptionWhileProcessingReconnection;
|
private static readonly Action<ILogger, int, Exception?> _unknownExceptionWhileProcessingReconnection;
|
||||||
private static readonly Action<ILogger, int, WebSocketError, string?, Exception?> _webSocketErrorCodeAndDetails;
|
private static readonly Action<ILogger, int, WebSocketError, string?, Exception?> _webSocketErrorCodeAndDetails;
|
||||||
private static readonly Action<ILogger, int, string?, Exception?> _webSocketError;
|
private static readonly Action<ILogger, int, string?, Exception?> _webSocketError;
|
||||||
private static readonly Action<ILogger, int, int, Exception?> _messageSentNotPending;
|
private static readonly Action<ILogger, int, int, Exception?> _messageSentNotPending;
|
||||||
@@ -28,7 +28,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
private static readonly Action<ILogger, int, Exception?> _closingNoMoreSubscriptions;
|
private static readonly Action<ILogger, int, Exception?> _closingNoMoreSubscriptions;
|
||||||
private static readonly Action<ILogger, int, int, int, Exception?> _addingNewSubscription;
|
private static readonly Action<ILogger, int, int, int, Exception?> _addingNewSubscription;
|
||||||
private static readonly Action<ILogger, int, Exception?> _nothingToResubscribeCloseConnection;
|
private static readonly Action<ILogger, int, Exception?> _nothingToResubscribeCloseConnection;
|
||||||
private static readonly Action<ILogger, int, Exception?> _failedAuthenticationDisconnectAndRecoonect;
|
private static readonly Action<ILogger, int, Exception?> _failedAuthenticationDisconnectAndReconnect;
|
||||||
private static readonly Action<ILogger, int, Exception?> _authenticationSucceeded;
|
private static readonly Action<ILogger, int, Exception?> _authenticationSucceeded;
|
||||||
private static readonly Action<ILogger, int, string?, Exception?> _failedRequestRevitalization;
|
private static readonly Action<ILogger, int, string?, Exception?> _failedRequestRevitalization;
|
||||||
private static readonly Action<ILogger, int, Exception?> _allSubscriptionResubscribed;
|
private static readonly Action<ILogger, int, Exception?> _allSubscriptionResubscribed;
|
||||||
@@ -55,15 +55,15 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
new EventId(2002, "FailedReconnectProcessing"),
|
new EventId(2002, "FailedReconnectProcessing"),
|
||||||
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
|
"[Sckt {SocketId}] failed reconnect processing: {ErrorMessage}, reconnecting again");
|
||||||
|
|
||||||
_unkownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
|
_unknownExceptionWhileProcessingReconnection = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2003, "UnkownExceptionWhileProcessingReconnection"),
|
new EventId(2003, "UnknownExceptionWhileProcessingReconnection"),
|
||||||
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
|
"[Sckt {SocketId}] Unknown exception while processing reconnection, reconnecting again");
|
||||||
|
|
||||||
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
|
_webSocketErrorCodeAndDetails = LoggerMessage.Define<int, WebSocketError, string?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2004, "WebSocketErrorCode"),
|
new EventId(2004, "WebSocketErrorCode"),
|
||||||
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCdoe}, details: {Details}");
|
"[Sckt {SocketId}] error: Websocket error code {WebSocketErrorCode}, details: {Details}");
|
||||||
|
|
||||||
_webSocketError = LoggerMessage.Define<int, string?>(
|
_webSocketError = LoggerMessage.Define<int, string?>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
@@ -145,7 +145,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
new EventId(2020, "NothingToResubscribe"),
|
new EventId(2020, "NothingToResubscribe"),
|
||||||
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
|
"[Sckt {SocketId}] nothing to resubscribe, closing connection");
|
||||||
|
|
||||||
_failedAuthenticationDisconnectAndRecoonect = LoggerMessage.Define<int>(
|
_failedAuthenticationDisconnectAndReconnect = LoggerMessage.Define<int>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
new EventId(2021, "FailedAuthentication"),
|
new EventId(2021, "FailedAuthentication"),
|
||||||
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
|
"[Sckt {SocketId}] authentication failed on reconnected socket. Disconnecting and reconnecting");
|
||||||
@@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
_sendingData = LoggerMessage.Define<int, int, string>(
|
_sendingData = LoggerMessage.Define<int, int, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(2028, "SendingData"),
|
new EventId(2028, "SendingData"),
|
||||||
"[Sckt {SocketId}] [Req {RequestId}] sending messsage: {Data}");
|
"[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
|
||||||
|
|
||||||
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
|
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
|
||||||
LogLevel.Warning,
|
LogLevel.Warning,
|
||||||
@@ -206,9 +206,9 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
_failedReconnectProcessing(logger, socketId, error, null);
|
_failedReconnectProcessing(logger, socketId, error, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UnkownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
|
public static void UnknownExceptionWhileProcessingReconnection(this ILogger logger, int socketId, Exception e)
|
||||||
{
|
{
|
||||||
_unkownExceptionWhileProcessingReconnection(logger, socketId, e);
|
_unknownExceptionWhileProcessingReconnection(logger, socketId, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
|
public static void WebSocketErrorCodeAndDetails(this ILogger logger, int socketId, WebSocketError error, string? details, Exception e)
|
||||||
@@ -285,7 +285,7 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
}
|
}
|
||||||
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
|
public static void FailedAuthenticationDisconnectAndRecoonect(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
_failedAuthenticationDisconnectAndRecoonect(logger, socketId, null);
|
_failedAuthenticationDisconnectAndReconnect(logger, socketId, null);
|
||||||
}
|
}
|
||||||
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
|
public static void AuthenticationSucceeded(this ILogger logger, int socketId)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,7 +62,6 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
new EventId(5005, "OrderBookStopping"),
|
new EventId(5005, "OrderBookStopping"),
|
||||||
"{Api} order book {Symbol} stopping");
|
"{Api} order book {Symbol} stopping");
|
||||||
|
|
||||||
|
|
||||||
_orderBookStopped = LoggerMessage.Define<string, string>(
|
_orderBookStopped = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Trace,
|
LogLevel.Trace,
|
||||||
new EventId(5006, "OrderBookStopped"),
|
new EventId(5006, "OrderBookStopped"),
|
||||||
|
|||||||
@@ -97,7 +97,6 @@ namespace CryptoExchange.Net.Logging.Extensions
|
|||||||
new EventId(6012, "KlineTrackerConnectionRestored"),
|
new EventId(6012, "KlineTrackerConnectionRestored"),
|
||||||
"Kline tracker for {Symbol} successfully resynchronized");
|
"Kline tracker for {Symbol} successfully resynchronized");
|
||||||
|
|
||||||
|
|
||||||
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
_tradeTrackerStatusChanged = LoggerMessage.Define<string, SyncStatus, SyncStatus>(
|
||||||
LogLevel.Debug,
|
LogLevel.Debug,
|
||||||
new EventId(6013, "KlineTrackerStatusChanged"),
|
new EventId(6013, "KlineTrackerStatusChanged"),
|
||||||
|
|||||||
@@ -32,44 +32,51 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// Wait for the AutoResetEvent to be set
|
/// Wait for the AutoResetEvent to be set
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
|
public async Task<bool> WaitAsync(TimeSpan? timeout = null, CancellationToken ct = default)
|
||||||
{
|
{
|
||||||
lock (_waits)
|
CancellationTokenRegistration registration = default;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (_signaled)
|
Task<bool> waiter = _completed;
|
||||||
|
lock (_waits)
|
||||||
{
|
{
|
||||||
if(_reset)
|
if (_signaled)
|
||||||
_signaled = false;
|
|
||||||
return _completed;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (ct.IsCancellationRequested)
|
|
||||||
return _completed;
|
|
||||||
|
|
||||||
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
||||||
if (timeout.HasValue)
|
|
||||||
{
|
{
|
||||||
var timeoutSource = new CancellationTokenSource(timeout.Value);
|
if (_reset)
|
||||||
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, ct);
|
_signaled = false;
|
||||||
ct = cancellationSource.Token;
|
|
||||||
}
|
}
|
||||||
|
else if (!ct.IsCancellationRequested)
|
||||||
var registration = ct.Register(() =>
|
|
||||||
{
|
{
|
||||||
lock (_waits)
|
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
if (timeout.HasValue)
|
||||||
{
|
{
|
||||||
tcs.TrySetResult(false);
|
var timeoutSource = new CancellationTokenSource(timeout.Value);
|
||||||
|
var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(timeoutSource.Token, ct);
|
||||||
// Not the cleanest but it works
|
ct = cancellationSource.Token;
|
||||||
_waits = new Queue<TaskCompletionSource<bool>>(_waits.Where(i => i != tcs));
|
|
||||||
}
|
}
|
||||||
}, useSynchronizationContext: false);
|
|
||||||
|
|
||||||
|
|
||||||
_waits.Enqueue(tcs);
|
registration = ct.Register(() =>
|
||||||
return tcs.Task;
|
{
|
||||||
|
lock (_waits)
|
||||||
|
{
|
||||||
|
tcs.TrySetResult(false);
|
||||||
|
|
||||||
|
// Not the cleanest but it works
|
||||||
|
_waits = new Queue<TaskCompletionSource<bool>>(_waits.Where(i => i != tcs));
|
||||||
|
}
|
||||||
|
}, useSynchronizationContext: false);
|
||||||
|
|
||||||
|
|
||||||
|
_waits.Enqueue(tcs);
|
||||||
|
waiter = tcs.Task;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return await waiter.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
registration.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new error result
|
/// Create a new error result
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="error">The erro rto return</param>
|
/// <param name="error">The error to return</param>
|
||||||
public CallResult(Error error) : this(default, null, error) { }
|
public CallResult(Error error) : this(default, null, error) { }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
namespace CryptoExchange.Net.Objects
|
using CryptoExchange.Net.Attributes;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.Objects
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// What to do when a request would exceed the rate limit
|
/// What to do when a request would exceed the rate limit
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposed
|
/// Disposed
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Diposed
|
Disposed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -215,7 +217,7 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FixedDelay,
|
FixedDelay,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Backof policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
|
/// Backoff policy of 2^`reconnectAttempt`, where `reconnectAttempt` has a max value of 5
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ExponentialBackoff
|
ExponentialBackoff
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ namespace CryptoExchange.Net.Objects.Options
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
|
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
|
||||||
/// the exhange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
|
/// the exchange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
|
||||||
/// </summary>
|
/// </summary>
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||||
public TEnvironment Environment { get; set; }
|
public TEnvironment Environment { get; set; }
|
||||||
|
|||||||
@@ -52,6 +52,15 @@ namespace CryptoExchange.Net.Objects.Options
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan? ConnectDelayAfterRateLimited { get; set; }
|
public TimeSpan? ConnectDelayAfterRateLimited { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer size to use for receiving data. Leave unset to use the default buffer size.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Only specify this if you are creating a significant amount of connections and understand the typical message length we receive from the exchange.
|
||||||
|
/// Setting this too low can increase memory consumption and allocations.
|
||||||
|
/// </remarks>
|
||||||
|
public int? ReceiveBufferSize { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a copy of this options
|
/// Create a copy of this options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -72,6 +81,7 @@ namespace CryptoExchange.Net.Objects.Options
|
|||||||
item.RequestTimeout = RequestTimeout;
|
item.RequestTimeout = RequestTimeout;
|
||||||
item.RateLimitingBehaviour = RateLimitingBehaviour;
|
item.RateLimitingBehaviour = RateLimitingBehaviour;
|
||||||
item.RateLimiterEnabled = RateLimiterEnabled;
|
item.RateLimiterEnabled = RateLimiterEnabled;
|
||||||
|
item.ReceiveBufferSize = ReceiveBufferSize;
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Authenticated { get; set; }
|
public bool Authenticated { get; set; }
|
||||||
|
|
||||||
|
// Formatting
|
||||||
// Formating
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The body format for this request
|
/// The body format for this request
|
||||||
|
|||||||
@@ -12,7 +12,12 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timestamp the data was received
|
/// The timestamp the data was received
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DateTime Timestamp { get; set; }
|
public DateTime ReceiveTime { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The timestamp of the data as specified by the server. Note that the server time and client time might not be 100% in sync so this value might not be fully comparable to local time.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? DataTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The stream producing the update
|
/// The stream producing the update
|
||||||
@@ -42,29 +47,32 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public DataEvent(T data, string? streamId, string? symbol, string? originalData, DateTime timestamp, SocketUpdateType? updateType)
|
public DataEvent(T data, string? streamId, string? symbol, string? originalData, DateTime receiveTimestamp, SocketUpdateType? updateType)
|
||||||
{
|
{
|
||||||
Data = data;
|
Data = data;
|
||||||
StreamId = streamId;
|
StreamId = streamId;
|
||||||
Symbol = symbol;
|
Symbol = symbol;
|
||||||
OriginalData = originalData;
|
OriginalData = originalData;
|
||||||
Timestamp = timestamp;
|
ReceiveTime = receiveTimestamp;
|
||||||
UpdateType = updateType;
|
UpdateType = updateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. Topic, OriginalData and Timestamp will be copied over
|
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. Topic, OriginalData and ReceivedTimestamp will be copied over
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="K">The type of the new data</typeparam>
|
/// <typeparam name="K">The type of the new data</typeparam>
|
||||||
/// <param name="data">The new data</param>
|
/// <param name="data">The new data</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data)
|
public DataEvent<K> As<K>(K data)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, StreamId, Symbol, OriginalData, Timestamp, UpdateType);
|
return new DataEvent<K>(data, StreamId, Symbol, OriginalData, ReceiveTime, UpdateType)
|
||||||
|
{
|
||||||
|
DataTime = DataTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. OriginalData and Timestamp will be copied over
|
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. OriginalData and ReceivedTimestamp will be copied over
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="K">The type of the new data</typeparam>
|
/// <typeparam name="K">The type of the new data</typeparam>
|
||||||
/// <param name="data">The new data</param>
|
/// <param name="data">The new data</param>
|
||||||
@@ -72,11 +80,14 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data, string? symbol)
|
public DataEvent<K> As<K>(K data, string? symbol)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, StreamId, symbol, OriginalData, Timestamp, UpdateType);
|
return new DataEvent<K>(data, StreamId, symbol, OriginalData, ReceiveTime, UpdateType)
|
||||||
|
{
|
||||||
|
DataTime = DataTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. OriginalData and Timestamp will be copied over
|
/// Create a new DataEvent with data in the from of type K based on the current DataEvent. OriginalData and ReceivedTimestamp will be copied over
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="K">The type of the new data</typeparam>
|
/// <typeparam name="K">The type of the new data</typeparam>
|
||||||
/// <param name="data">The new data</param>
|
/// <param name="data">The new data</param>
|
||||||
@@ -86,7 +97,10 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public DataEvent<K> As<K>(K data, string streamId, string? symbol, SocketUpdateType updateType)
|
public DataEvent<K> As<K>(K data, string streamId, string? symbol, SocketUpdateType updateType)
|
||||||
{
|
{
|
||||||
return new DataEvent<K>(data, streamId, symbol, OriginalData, Timestamp, updateType);
|
return new DataEvent<K>(data, streamId, symbol, OriginalData, ReceiveTime, updateType)
|
||||||
|
{
|
||||||
|
DataTime = DataTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -98,10 +112,12 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public ExchangeEvent<K> AsExchangeEvent<K>(string exchange, K data)
|
public ExchangeEvent<K> AsExchangeEvent<K>(string exchange, K data)
|
||||||
{
|
{
|
||||||
return new ExchangeEvent<K>(exchange, this.As<K>(data));
|
return new ExchangeEvent<K>(exchange, this.As<K>(data))
|
||||||
|
{
|
||||||
|
DataTime = DataTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Specify the symbol
|
/// Specify the symbol
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -135,6 +151,15 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specify the data timestamp
|
||||||
|
/// </summary>
|
||||||
|
public DataEvent<T> WithDataTimestamp(DateTime? timestamp)
|
||||||
|
{
|
||||||
|
DataTime = timestamp;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a CallResult from this DataEvent
|
/// Create a CallResult from this DataEvent
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting.
|
/// Event when the connection is restored. Timespan parameter indicates the time the socket has been offline for before reconnecting.
|
||||||
/// Note that when the executing code is suspended and resumed at a later period (for example, a laptop going to sleep) the disconnect time will be incorrect as the diconnect
|
/// Note that when the executing code is suspended and resumed at a later period (for example, a laptop going to sleep) the disconnect time will be incorrect as the disconnect
|
||||||
/// will only be detected after resuming the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
|
/// will only be detected after resuming the code, so the initial disconnect time is lost. Use the timespan only for informational purposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<TimeSpan> ConnectionRestored
|
public event Action<TimeSpan> ConnectionRestored
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
public ApiProxy? Proxy { get; set; }
|
public ApiProxy? Proxy { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The maximum time of no data received before considering the connection lost and closting/reconnecting the socket
|
/// The maximum time of no data received before considering the connection lost and closing/reconnecting the socket
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan? Timeout { get; set; }
|
public TimeSpan? Timeout { get; set; }
|
||||||
|
|
||||||
@@ -57,13 +57,18 @@ namespace CryptoExchange.Net.Objects.Sockets
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// What to do when rate limit is reached
|
/// What to do when rate limit is reached
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RateLimitingBehaviour RateLimitingBehaviour { get; set; }
|
public RateLimitingBehaviour RateLimitingBehavior { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Encoding for sending/receiving data
|
/// Encoding for sending/receiving data
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Encoding Encoding { get; set; } = Encoding.UTF8;
|
public Encoding Encoding { get; set; } = Encoding.UTF8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The buffer size to use for receiving data
|
||||||
|
/// </summary>
|
||||||
|
public int? ReceiveBufferSize { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
|
/// Trade environment. Contains info about URL's to use to connect to the API. To swap environment select another environment for
|
||||||
/// the echange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
|
/// the exchange's environment list or create a custom environment using either `[Exchange]Environment.CreateCustom()` or `[Exchange]Environment.[Environment]`, for example `KucoinEnvironment.TestNet` or `BinanceEnvironment.Live`
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TradeEnvironment
|
public class TradeEnvironment
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -74,8 +74,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether levels should be strictly enforced. For example, when an order book has 25 levels and a new update comes in which pushes
|
/// 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
|
/// the current level 25 ask out of the top 25, should the level 26 entry be removed from the book or does the server handle this
|
||||||
/// server handle this
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool _strictLevels;
|
protected bool _strictLevels;
|
||||||
|
|
||||||
@@ -250,6 +249,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
|
|
||||||
// Clear any previous messages
|
// Clear any previous messages
|
||||||
while (_processQueue.TryDequeue(out _)) { }
|
while (_processQueue.TryDequeue(out _)) { }
|
||||||
|
|
||||||
_processBuffer.Clear();
|
_processBuffer.Clear();
|
||||||
_bookSet = false;
|
_bookSet = false;
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set the initial data for the order book. Typically the snapshot which was requested from the Rest API, or the first snapshot
|
/// 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
|
/// received from a socket subscription
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="orderBookSequenceNumber">The last update sequence number until which the snapshot is in sync</param>
|
/// <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="askList">List of asks</param>
|
||||||
@@ -618,6 +618,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
var bid = book.bids.Count() > i ? book.bids.ElementAt(i): null;
|
var bid = book.bids.Count() > i ? book.bids.ElementAt(i): null;
|
||||||
stringBuilder.AppendLine($"[{ask?.Quantity.ToString(CultureInfo.InvariantCulture),14}] {ask?.Price.ToString(CultureInfo.InvariantCulture),14} | {bid?.Price.ToString(CultureInfo.InvariantCulture),-14} [{bid?.Quantity.ToString(CultureInfo.InvariantCulture),-14}]");
|
stringBuilder.AppendLine($"[{ask?.Quantity.ToString(CultureInfo.InvariantCulture),14}] {ask?.Price.ToString(CultureInfo.InvariantCulture),14} | {bid?.Price.ToString(CultureInfo.InvariantCulture),-14} [{bid?.Quantity.ToString(CultureInfo.InvariantCulture),-14}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return stringBuilder.ToString();
|
return stringBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,6 +637,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
_queueEvent.Set();
|
_queueEvent.Set();
|
||||||
// Clear queue
|
// Clear queue
|
||||||
while (_processQueue.TryDequeue(out _)) { }
|
while (_processQueue.TryDequeue(out _)) { }
|
||||||
|
|
||||||
_processBuffer.Clear();
|
_processBuffer.Clear();
|
||||||
_bookSet = false;
|
_bookSet = false;
|
||||||
DoReset();
|
DoReset();
|
||||||
@@ -732,7 +734,7 @@ namespace CryptoExchange.Net.OrderBook
|
|||||||
var (prevBestBid, prevBestAsk) = BestOffers;
|
var (prevBestBid, prevBestAsk) = BestOffers;
|
||||||
ProcessRangeUpdates(item.StartUpdateId, item.EndUpdateId, item.Bids, item.Asks);
|
ProcessRangeUpdates(item.StartUpdateId, item.EndUpdateId, item.Bids, item.Asks);
|
||||||
|
|
||||||
if (!_asks.Any() || !_bids.Any())
|
if (_asks.Count == 0 || _bids.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_asks.First().Key < _bids.First().Key)
|
if (_asks.First().Key < _bids.First().Key)
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
|
|
||||||
private readonly IEnumerable<IGuardFilter> _filters;
|
private readonly IEnumerable<IGuardFilter> _filters;
|
||||||
private readonly Dictionary<string, IWindowTracker> _trackers;
|
private readonly Dictionary<string, IWindowTracker> _trackers;
|
||||||
private RateLimitWindowType _windowType;
|
private readonly RateLimitWindowType _windowType;
|
||||||
private double? _decayRate;
|
private readonly double? _decayRate;
|
||||||
private int? _connectionWeight;
|
private readonly int? _connectionWeight;
|
||||||
private readonly Func<RequestDefinition, string, string?, string> _keySelector;
|
private readonly Func<RequestDefinition, string, string?, string> _keySelector;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -90,7 +90,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
foreach(var filter in _filters)
|
foreach(var filter in _filters)
|
||||||
{
|
{
|
||||||
@@ -101,7 +101,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
if (type == RateLimitItemType.Connection)
|
if (type == RateLimitItemType.Connection)
|
||||||
requestWeight = _connectionWeight ?? requestWeight;
|
requestWeight = _connectionWeight ?? requestWeight;
|
||||||
|
|
||||||
var key = _keySelector(definition, host, apiKey);
|
var key = _keySelector(definition, host, apiKey) + keySuffix;
|
||||||
if (!_trackers.TryGetValue(key, out var tracker))
|
if (!_trackers.TryGetValue(key, out var tracker))
|
||||||
{
|
{
|
||||||
tracker = CreateTracker();
|
tracker = CreateTracker();
|
||||||
@@ -116,7 +116,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
foreach (var filter in _filters)
|
foreach (var filter in _filters)
|
||||||
{
|
{
|
||||||
@@ -127,7 +127,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
if (type == RateLimitItemType.Connection)
|
if (type == RateLimitItemType.Connection)
|
||||||
requestWeight = _connectionWeight ?? requestWeight;
|
requestWeight = _connectionWeight ?? requestWeight;
|
||||||
|
|
||||||
var key = _keySelector(definition, host, apiKey);
|
var key = _keySelector(definition, host, apiKey) + keySuffix;
|
||||||
var tracker = _trackers[key];
|
var tracker = _trackers[key];
|
||||||
tracker.ApplyWeight(requestWeight);
|
tracker.ApplyWeight(requestWeight);
|
||||||
return RateLimitState.Applied(Limit, TimeSpan, tracker.Current);
|
return RateLimitState.Applied(Limit, TimeSpan, tracker.Current);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
if (type != Type)
|
if (type != Type)
|
||||||
return LimitCheck.NotApplicable;
|
return LimitCheck.NotApplicable;
|
||||||
@@ -55,7 +55,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
return RateLimitState.NotApplied;
|
return RateLimitState.NotApplied;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Endpoint limit per API key
|
/// Endpoint limit per API key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method);
|
public static Func<RequestDefinition, string, string?, string> PerApiKey { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.Path + def.Method + key);
|
||||||
|
|
||||||
private readonly Dictionary<string, IWindowTracker> _trackers;
|
private readonly Dictionary<string, IWindowTracker> _trackers;
|
||||||
private readonly RateLimitWindowType _windowType;
|
private readonly RateLimitWindowType _windowType;
|
||||||
@@ -53,9 +53,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
var key = _keySelector(definition, host, apiKey);
|
var key = _keySelector(definition, host, apiKey) + keySuffix;
|
||||||
if (!_trackers.TryGetValue(key, out var tracker))
|
if (!_trackers.TryGetValue(key, out var tracker))
|
||||||
{
|
{
|
||||||
tracker = CreateTracker();
|
tracker = CreateTracker();
|
||||||
@@ -70,9 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight)
|
public RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix)
|
||||||
{
|
{
|
||||||
var key = _keySelector(definition, host, apiKey);
|
var key = _keySelector(definition, host, apiKey) + keySuffix;
|
||||||
var tracker = _trackers[key];
|
var tracker = _trackers[key];
|
||||||
tracker.ApplyWeight(requestWeight);
|
tracker.ApplyWeight(requestWeight);
|
||||||
return RateLimitState.Applied(_limit, _period, tracker.Current);
|
return RateLimitState.Applied(_limit, _period, tracker.Current);
|
||||||
|
|||||||
@@ -53,9 +53,10 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
|
|||||||
/// <param name="apiKey">The API key</param>
|
/// <param name="apiKey">The API key</param>
|
||||||
/// <param name="requestWeight">Request weight</param>
|
/// <param name="requestWeight">Request weight</param>
|
||||||
/// <param name="behaviour">Behaviour when rate limit is hit</param>
|
/// <param name="behaviour">Behaviour when rate limit is hit</param>
|
||||||
|
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <param name="ct">Cancelation token</param>
|
/// <param name="ct">Cancelation token</param>
|
||||||
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
|
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
|
||||||
Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct);
|
Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail
|
/// Enforces the rate limit as defined in the request definition. When a rate limit is hit will wait for the rate limit to pass if RateLimitingBehaviour is Wait, or return an error if it is set to Fail
|
||||||
@@ -69,8 +70,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
|
|||||||
/// <param name="apiKey">The API key</param>
|
/// <param name="apiKey">The API key</param>
|
||||||
/// <param name="behaviour">Behaviour when rate limit is hit</param>
|
/// <param name="behaviour">Behaviour when rate limit is hit</param>
|
||||||
/// <param name="requestWeight">The weight to apply to the limit guard</param>
|
/// <param name="requestWeight">The weight to apply to the limit guard</param>
|
||||||
|
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <param name="ct">Cancelation token</param>
|
/// <param name="ct">Cancelation token</param>
|
||||||
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
|
/// <returns>Error if RateLimitingBehaviour is Fail and rate limit is hit</returns>
|
||||||
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, CancellationToken ct);
|
Task<CallResult> ProcessSingleAsync(ILogger logger, int itemId, IRateLimitGuard guard, RateLimitItemType type, RequestDefinition definition, string baseAddress, string? apiKey, int requestWeight, RateLimitingBehaviour behaviour, string? keySuffix, CancellationToken ct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
|
|||||||
/// <param name="host">The host address</param>
|
/// <param name="host">The host address</param>
|
||||||
/// <param name="apiKey">The API key</param>
|
/// <param name="apiKey">The API key</param>
|
||||||
/// <param name="requestWeight">The request weight</param>
|
/// <param name="requestWeight">The request weight</param>
|
||||||
|
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight);
|
LimitCheck Check(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply the request to this guard with the specified weight
|
/// Apply the request to this guard with the specified weight
|
||||||
@@ -36,7 +37,8 @@ namespace CryptoExchange.Net.RateLimiting.Interfaces
|
|||||||
/// <param name="host">The host address</param>
|
/// <param name="host">The host address</param>
|
||||||
/// <param name="apiKey">The API key</param>
|
/// <param name="apiKey">The API key</param>
|
||||||
/// <param name="requestWeight">The request weight</param>
|
/// <param name="requestWeight">The request weight</param>
|
||||||
|
/// <param name="keySuffix">An additional optional suffix for the key selector. Can be used to make rate limiting work based on parameters.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight);
|
RateLimitState ApplyWeight(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, string? keySuffix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct)
|
public async Task<CallResult> ProcessAsync(ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct)
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
||||||
bool release = true;
|
bool release = true;
|
||||||
_waitingCount++;
|
_waitingCount++;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
|
return await CheckGuardsAsync(_guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
@@ -71,6 +71,7 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
string? apiKey,
|
string? apiKey,
|
||||||
int requestWeight,
|
int requestWeight,
|
||||||
RateLimitingBehaviour rateLimitingBehaviour,
|
RateLimitingBehaviour rateLimitingBehaviour,
|
||||||
|
string? keySuffix,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
||||||
@@ -78,7 +79,7 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
_waitingCount++;
|
_waitingCount++;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
|
return await CheckGuardsAsync(new IRateLimitGuard[] { guard }, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
@@ -94,12 +95,12 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, CancellationToken ct)
|
private async Task<CallResult> CheckGuardsAsync(IEnumerable<IRateLimitGuard> guards, ILogger logger, int itemId, RateLimitItemType type, RequestDefinition definition, string host, string? apiKey, int requestWeight, RateLimitingBehaviour rateLimitingBehaviour, string? keySuffix, CancellationToken ct)
|
||||||
{
|
{
|
||||||
foreach (var guard in guards)
|
foreach (var guard in guards)
|
||||||
{
|
{
|
||||||
// Check if a wait is needed for this guard
|
// Check if a wait is needed for this guard
|
||||||
var result = guard.Check(type, definition, host, apiKey, requestWeight);
|
var result = guard.Check(type, definition, host, apiKey, requestWeight, keySuffix);
|
||||||
if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail)
|
if (result.Delay != TimeSpan.Zero && rateLimitingBehaviour == RateLimitingBehaviour.Fail)
|
||||||
{
|
{
|
||||||
// Delay is needed and limit behaviour is to fail the request
|
// Delay is needed and limit behaviour is to fail the request
|
||||||
@@ -126,14 +127,14 @@ namespace CryptoExchange.Net.RateLimiting
|
|||||||
RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, host, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour));
|
RateLimitTriggered?.Invoke(new RateLimitEvent(itemId, _name, guard.Description, definition, host, result.Current, requestWeight, result.Limit, result.Period, result.Delay, rateLimitingBehaviour));
|
||||||
await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false);
|
await Task.Delay((int)result.Delay.TotalMilliseconds + 1, ct).ConfigureAwait(false);
|
||||||
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
await _semaphore.WaitAsync(ct).ConfigureAwait(false);
|
||||||
return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, ct).ConfigureAwait(false);
|
return await CheckGuardsAsync(guards, logger, itemId, type, definition, host, apiKey, requestWeight, rateLimitingBehaviour, keySuffix, ct).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the weight on each guard
|
// Apply the weight on each guard
|
||||||
foreach (var guard in guards)
|
foreach (var guard in guards)
|
||||||
{
|
{
|
||||||
var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight);
|
var result = guard.ApplyWeight(type, definition, host, apiKey, requestWeight, keySuffix);
|
||||||
if (result.IsApplied)
|
if (result.IsApplied)
|
||||||
{
|
{
|
||||||
RateLimitUpdated?.Invoke(new RateLimitUpdateEvent(itemId, _name, guard.Description, result.Current, result.Limit, result.Period));
|
RateLimitUpdated?.Invoke(new RateLimitUpdateEvent(itemId, _name, guard.Description, result.Current, result.Limit, result.Period));
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
if (Current == 0)
|
if (Current == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Request limit reached without any prior request. " +
|
throw new Exception("Request limit reached without any prior request. " +
|
||||||
$"This request can never execute with the current rate limiter. Request weight: {weight}, Ratelimit: {Limit}");
|
$"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the time to wait before this weight can be applied without going over the rate limit
|
// Determine the time to wait before this weight can be applied without going over the rate limit
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
if (Current == 0)
|
if (Current == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Request limit reached without any prior request. " +
|
throw new Exception("Request limit reached without any prior request. " +
|
||||||
$"This request can never execute with the current rate limiter. Request weight: {weight}, Ratelimit: {Limit}");
|
$"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the time to wait before this weight can be applied without going over the rate limit
|
// Determine the time to wait before this weight can be applied without going over the rate limit
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
if (Current == 0)
|
if (Current == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Request limit reached without any prior request. " +
|
throw new Exception("Request limit reached without any prior request. " +
|
||||||
$"This request can never execute with the current rate limiter. Request weight: {weight}, Ratelimit: {Limit}");
|
$"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the time to wait before this weight can be applied without going over the rate limit
|
// Determine the time to wait before this weight can be applied without going over the rate limit
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
if (Current == 0)
|
if (Current == 0)
|
||||||
{
|
{
|
||||||
throw new Exception("Request limit reached without any prior request. " +
|
throw new Exception("Request limit reached without any prior request. " +
|
||||||
$"This request can never execute with the current rate limiter. Request weight: {weight}, Ratelimit: {Limit}");
|
$"This request can never execute with the current rate limiter. Request weight: {weight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the time to wait before this weight can be applied without going over the rate limit
|
// Determine the time to wait before this weight can be applied without going over the rate limit
|
||||||
@@ -102,7 +102,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception("Request not possible to execute with current rate limit guard. " +
|
throw new Exception("Request not possible to execute with current rate limit guard. " +
|
||||||
$" Request weight: {requestWeight}, Ratelimit: {Limit}");
|
$" Request weight: {requestWeight}, RateLimit: {Limit}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace CryptoExchange.Net.Requests
|
|||||||
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
|
||||||
}
|
}
|
||||||
catch (PlatformNotSupportedException) { }
|
catch (PlatformNotSupportedException) { }
|
||||||
|
catch (NotImplementedException) { } // Mono runtime throws NotImplementedException
|
||||||
|
|
||||||
if (proxy != null)
|
if (proxy != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,6 +5,14 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public enum SharedKlineInterval
|
public enum SharedKlineInterval
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 1 min
|
||||||
|
/// </summary>
|
||||||
|
OneMinute = 60,
|
||||||
|
/// <summary>
|
||||||
|
/// 3 min
|
||||||
|
/// </summary>
|
||||||
|
ThreeMinutes = 60 * 3,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 5 min
|
/// 5 min
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -14,10 +22,34 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
FifteenMinutes = 60 * 15,
|
FifteenMinutes = 60 * 15,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Thirty minutes
|
||||||
|
/// </summary>
|
||||||
|
ThirtyMinutes = 60 * 30,
|
||||||
|
/// <summary>
|
||||||
/// 1 hour
|
/// 1 hour
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OneHour = 60 * 60,
|
OneHour = 60 * 60,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// 2 hours
|
||||||
|
/// </summary>
|
||||||
|
TwoHours = 60 * 60 * 2,
|
||||||
|
/// <summary>
|
||||||
|
/// 4 hours
|
||||||
|
/// </summary>
|
||||||
|
FourHours = 60 * 60 * 4,
|
||||||
|
/// <summary>
|
||||||
|
/// 6 hours
|
||||||
|
/// </summary>
|
||||||
|
SixHours = 60 * 60 * 6,
|
||||||
|
/// <summary>
|
||||||
|
/// 8 hours
|
||||||
|
/// </summary>
|
||||||
|
EightHours = 60 * 60 * 8,
|
||||||
|
/// <summary>
|
||||||
|
/// 12 hours
|
||||||
|
/// </summary>
|
||||||
|
TwelveHours = 60 * 60 * 12,
|
||||||
|
/// <summary>
|
||||||
/// 1 day
|
/// 1 day
|
||||||
/// </summary>
|
/// </summary>
|
||||||
OneDay = 60 * 60 * 24,
|
OneDay = 60 * 60 * 24,
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
evnt.StreamId,
|
evnt.StreamId,
|
||||||
evnt.Symbol,
|
evnt.Symbol,
|
||||||
evnt.OriginalData,
|
evnt.OriginalData,
|
||||||
evnt.Timestamp,
|
evnt.ReceiveTime,
|
||||||
evnt.UpdateType)
|
evnt.UpdateType)
|
||||||
{
|
{
|
||||||
|
DataTime = evnt.DataTime;
|
||||||
Exchange = exchange;
|
Exchange = exchange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
public class ExchangeParameters
|
public class ExchangeParameters
|
||||||
{
|
{
|
||||||
private readonly List<ExchangeParameter> _parameters;
|
private readonly List<ExchangeParameter> _parameters;
|
||||||
private static List<ExchangeParameter> _staticParameters = new List<ExchangeParameter>();
|
private readonly static List<ExchangeParameter> _staticParameters = new List<ExchangeParameter>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
NextPageToken = nextPageToken;
|
NextPageToken = nextPageToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy the ExchangeWebResult to a new data type
|
/// Copy the ExchangeWebResult to a new data type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -31,9 +31,17 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
{
|
{
|
||||||
SupportIntervals = new[]
|
SupportIntervals = new[]
|
||||||
{
|
{
|
||||||
|
SharedKlineInterval.OneMinute,
|
||||||
|
SharedKlineInterval.ThreeMinutes,
|
||||||
SharedKlineInterval.FiveMinutes,
|
SharedKlineInterval.FiveMinutes,
|
||||||
SharedKlineInterval.FifteenMinutes,
|
SharedKlineInterval.FifteenMinutes,
|
||||||
|
SharedKlineInterval.ThirtyMinutes,
|
||||||
SharedKlineInterval.OneHour,
|
SharedKlineInterval.OneHour,
|
||||||
|
SharedKlineInterval.TwoHours,
|
||||||
|
SharedKlineInterval.FourHours,
|
||||||
|
SharedKlineInterval.SixHours,
|
||||||
|
SharedKlineInterval.EightHours,
|
||||||
|
SharedKlineInterval.TwelveHours,
|
||||||
SharedKlineInterval.OneDay,
|
SharedKlineInterval.OneDay,
|
||||||
SharedKlineInterval.OneWeek,
|
SharedKlineInterval.OneWeek,
|
||||||
SharedKlineInterval.OneMonth
|
SharedKlineInterval.OneMonth
|
||||||
|
|||||||
@@ -22,10 +22,17 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
{
|
{
|
||||||
SupportIntervals = new[]
|
SupportIntervals = new[]
|
||||||
{
|
{
|
||||||
|
SharedKlineInterval.OneMinute,
|
||||||
|
SharedKlineInterval.ThreeMinutes,
|
||||||
SharedKlineInterval.FiveMinutes,
|
SharedKlineInterval.FiveMinutes,
|
||||||
SharedKlineInterval.FifteenMinutes,
|
SharedKlineInterval.FifteenMinutes,
|
||||||
|
SharedKlineInterval.ThirtyMinutes,
|
||||||
SharedKlineInterval.OneHour,
|
SharedKlineInterval.OneHour,
|
||||||
SharedKlineInterval.FifteenMinutes,
|
SharedKlineInterval.TwoHours,
|
||||||
|
SharedKlineInterval.FourHours,
|
||||||
|
SharedKlineInterval.SixHours,
|
||||||
|
SharedKlineInterval.EightHours,
|
||||||
|
SharedKlineInterval.TwelveHours,
|
||||||
SharedKlineInterval.OneDay,
|
SharedKlineInterval.OneDay,
|
||||||
SharedKlineInterval.OneWeek,
|
SharedKlineInterval.OneWeek,
|
||||||
SharedKlineInterval.OneMonth
|
SharedKlineInterval.OneMonth
|
||||||
|
|||||||
@@ -46,6 +46,10 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int? PriceDecimals { get; set; }
|
public int? PriceDecimals { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// The max amount of significant figures to use for price. For example with value of 5 these values are valid: 0.00001, 0.12300, 123.53, 12345, but this is not: 12345.1
|
||||||
|
/// </summary>
|
||||||
|
public int? PriceSignificantFigures { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// Whether the symbol is currently available for trading
|
/// Whether the symbol is currently available for trading
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Trading { get; set; }
|
public bool Trading { get; set; }
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
using CryptoExchange.Net.Objects;
|
using CryptoExchange.Net.Objects;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace CryptoExchange.Net.SharedApis
|
namespace CryptoExchange.Net.SharedApis
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A symbol representation based on a base and quote asset
|
/// A symbol representation based on a base and quote asset
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SharedSymbol
|
public record SharedSymbol
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The base asset of the symbol
|
/// The base asset of the symbol
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using CryptoExchange.Net.Objects.Sockets;
|
|||||||
using CryptoExchange.Net.RateLimiting;
|
using CryptoExchange.Net.RateLimiting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -32,6 +33,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
internal static int _lastStreamId;
|
internal static int _lastStreamId;
|
||||||
private static readonly object _streamIdLock = new();
|
private static readonly object _streamIdLock = new();
|
||||||
|
private static readonly ArrayPool<byte> _receiveBufferPool = ArrayPool<byte>.Shared;
|
||||||
|
|
||||||
private readonly AsyncResetEvent _sendEvent;
|
private readonly AsyncResetEvent _sendEvent;
|
||||||
private readonly ConcurrentQueue<SendItem> _sendBuffer;
|
private readonly ConcurrentQueue<SendItem> _sendBuffer;
|
||||||
@@ -46,14 +48,15 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private ProcessState _processState;
|
private ProcessState _processState;
|
||||||
private DateTime _lastReconnectTime;
|
private DateTime _lastReconnectTime;
|
||||||
private string _baseAddress;
|
private readonly string _baseAddress;
|
||||||
private int _reconnectAttempt;
|
private int _reconnectAttempt;
|
||||||
|
private readonly int _receiveBufferSize;
|
||||||
|
|
||||||
private const int _receiveBufferSize = 1048576;
|
private const int _defaultReceiveBufferSize = 1048576;
|
||||||
private const int _sendBufferSize = 4096;
|
private const int _sendBufferSize = 4096;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Received messages, the size and the timstamp
|
/// Received messages, the size and the timestamp
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected readonly List<ReceiveItem> _receivedMessages;
|
protected readonly List<ReceiveItem> _receivedMessages;
|
||||||
|
|
||||||
@@ -96,7 +99,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
UpdateReceivedMessages();
|
UpdateReceivedMessages();
|
||||||
|
|
||||||
if (!_receivedMessages.Any())
|
if (_receivedMessages.Count == 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return Math.Round(_receivedMessages.Sum(v => v.Bytes) / 1000d / 3d);
|
return Math.Round(_receivedMessages.Sum(v => v.Bytes) / 1000d / 3d);
|
||||||
@@ -149,6 +152,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_sendBuffer = new ConcurrentQueue<SendItem>();
|
_sendBuffer = new ConcurrentQueue<SendItem>();
|
||||||
_ctsSource = new CancellationTokenSource();
|
_ctsSource = new CancellationTokenSource();
|
||||||
_receivedMessagesLock = new object();
|
_receivedMessagesLock = new object();
|
||||||
|
_receiveBufferSize = websocketParameters.ReceiveBufferSize ?? _defaultReceiveBufferSize;
|
||||||
|
|
||||||
_closeSem = new SemaphoreSlim(1, 1);
|
_closeSem = new SemaphoreSlim(1, 1);
|
||||||
_socket = CreateSocket();
|
_socket = CreateSocket();
|
||||||
@@ -219,7 +223,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
if (Parameters.RateLimiter != null)
|
if (Parameters.RateLimiter != null)
|
||||||
{
|
{
|
||||||
var definition = new RequestDefinition(Uri.AbsolutePath, HttpMethod.Get) { ConnectionId = Id };
|
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);
|
var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, Id, RateLimitItemType.Connection, definition, _baseAddress, null, 1, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false);
|
||||||
if (!limitResult)
|
if (!limitResult)
|
||||||
return new CallResult(new ClientRateLimitError("Connection limit reached"));
|
return new CallResult(new ClientRateLimitError("Connection limit reached"));
|
||||||
}
|
}
|
||||||
@@ -296,7 +300,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
await (OnReconnecting?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
await (OnReconnecting?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay here to prevent very repid looping when a connection to the server is accepted and immediately disconnected
|
// Delay here to prevent very rapid looping when a connection to the server is accepted and immediately disconnected
|
||||||
var initialDelay = GetReconnectDelay();
|
var initialDelay = GetReconnectDelay();
|
||||||
await Task.Delay(initialDelay).ConfigureAwait(false);
|
await Task.Delay(initialDelay).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -491,7 +495,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!_sendBuffer.Any())
|
if (_sendBuffer.IsEmpty)
|
||||||
await _sendEvent.WaitAsync(ct: _ctsSource.Token).ConfigureAwait(false);
|
await _sendEvent.WaitAsync(ct: _ctsSource.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
@@ -508,7 +512,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, data.Id, RateLimitItemType.Request, requestDefinition, _baseAddress, null, data.Weight, Parameters.RateLimitingBehaviour, _ctsSource.Token).ConfigureAwait(false);
|
var limitResult = await Parameters.RateLimiter.ProcessAsync(_logger, data.Id, RateLimitItemType.Request, requestDefinition, _baseAddress, null, data.Weight, Parameters.RateLimitingBehavior, null, _ctsSource.Token).ConfigureAwait(false);
|
||||||
if (!limitResult)
|
if (!limitResult)
|
||||||
{
|
{
|
||||||
await (OnRequestRateLimited?.Invoke(data.Id) ?? Task.CompletedTask).ConfigureAwait(false);
|
await (OnRequestRateLimited?.Invoke(data.Id) ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
@@ -566,8 +570,8 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private async Task ReceiveLoopAsync()
|
private async Task ReceiveLoopAsync()
|
||||||
{
|
{
|
||||||
var buffer = new ArraySegment<byte>(new byte[_receiveBufferSize]);
|
byte[] rentedBuffer = _receiveBufferPool.Rent(_receiveBufferSize);
|
||||||
var received = 0;
|
var buffer = new ArraySegment<byte>(rentedBuffer);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
@@ -583,19 +587,30 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
receiveResult = await _socket.ReceiveAsync(buffer, _ctsSource.Token).ConfigureAwait(false);
|
receiveResult = await _socket.ReceiveAsync(buffer, _ctsSource.Token).ConfigureAwait(false);
|
||||||
received += receiveResult.Count;
|
|
||||||
lock (_receivedMessagesLock)
|
lock (_receivedMessagesLock)
|
||||||
_receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count));
|
_receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count));
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
|
if (ex.InnerException?.InnerException?.Message.Contains("KeepAliveTimeout") == true)
|
||||||
|
{
|
||||||
|
// Specific case that the websocket connection got closed because of a ping frame timeout
|
||||||
|
// Unfortunately doesn't seem to be a nicer way to catch
|
||||||
|
_logger.SocketPingTimeout(Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_closeTask?.IsCompleted != false)
|
||||||
|
_closeTask = CloseInternalAsync();
|
||||||
|
|
||||||
// canceled
|
// canceled
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception wse)
|
catch (Exception wse)
|
||||||
{
|
{
|
||||||
// Connection closed unexpectedly
|
if (!_ctsSource.Token.IsCancellationRequested && !_stopRequested)
|
||||||
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
|
// Connection closed unexpectedly
|
||||||
|
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
|
||||||
|
|
||||||
if (_closeTask?.IsCompleted != false)
|
if (_closeTask?.IsCompleted != false)
|
||||||
_closeTask = CloseInternalAsync();
|
_closeTask = CloseInternalAsync();
|
||||||
break;
|
break;
|
||||||
@@ -637,7 +652,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
// Received a complete message and it's not multi part
|
// Received a complete message and it's not multi part
|
||||||
_logger.SocketReceivedSingleMessage(Id, receiveResult.Count);
|
_logger.SocketReceivedSingleMessage(Id, receiveResult.Count);
|
||||||
await ProcessData(receiveResult.MessageType, new ReadOnlyMemory<byte>(buffer.Array, buffer.Offset, receiveResult.Count)).ConfigureAwait(false);
|
await ProcessData(receiveResult.MessageType, new ReadOnlyMemory<byte>(buffer.Array!, buffer.Offset, receiveResult.Count)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -667,11 +682,11 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
if (multiPartMessage)
|
if (multiPartMessage)
|
||||||
{
|
{
|
||||||
// When the connection gets interupted we might not have received a full message
|
// When the connection gets interrupted we might not have received a full message
|
||||||
if (receiveResult?.EndOfMessage == true)
|
if (receiveResult?.EndOfMessage == true)
|
||||||
{
|
{
|
||||||
_logger.SocketReassembledMessage(Id, multipartStream!.Length);
|
_logger.SocketReassembledMessage(Id, multipartStream!.Length);
|
||||||
// Get the underlying buffer of the memorystream holding the written data and delimit it (GetBuffer return the full array, not only the written part)
|
// Get the underlying buffer of the memory stream holding the written data and delimit it (GetBuffer return the full array, not only the written part)
|
||||||
await ProcessData(receiveResult.MessageType, new ReadOnlyMemory<byte>(multipartStream.GetBuffer(), 0, (int)multipartStream.Length)).ConfigureAwait(false);
|
await ProcessData(receiveResult.MessageType, new ReadOnlyMemory<byte>(multipartStream.GetBuffer(), 0, (int)multipartStream.Length)).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -693,12 +708,13 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_receiveBufferPool.Return(rentedBuffer, true);
|
||||||
_logger.SocketReceiveLoopFinished(Id);
|
_logger.SocketReceiveLoopFinished(Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Proccess a stream message
|
/// Process a stream message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="type"></param>
|
/// <param name="type"></param>
|
||||||
/// <param name="data"></param>
|
/// <param name="data"></param>
|
||||||
@@ -730,6 +746,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
_ = ReconnectAsync().ConfigureAwait(false);
|
_ = ReconnectAsync().ConfigureAwait(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(500, _ctsSource.Token).ConfigureAwait(false);
|
await Task.Delay(500, _ctsSource.Token).ConfigureAwait(false);
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
public DateTime? DisconnectTime { get; set; }
|
public DateTime? DisconnectTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tag for identificaion
|
/// Tag for identification
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Tag { get; set; }
|
public string Tag { get; set; }
|
||||||
|
|
||||||
@@ -214,7 +214,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
private readonly IByteMessageAccessor _accessor;
|
private readonly IByteMessageAccessor _accessor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The task that is sending periodic data on the websocket. Can be used for sending Ping messages every x seconds or similair. Not necesarry.
|
/// The task that is sending periodic data on the websocket. Can be used for sending Ping messages every x seconds or similar. Not necessary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Task? periodicTask;
|
protected Task? periodicTask;
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
foreach (var query in _listeners.OfType<Query>().ToList())
|
foreach (var query in _listeners.OfType<Query>().ToList())
|
||||||
{
|
{
|
||||||
query.Fail(new WebError("Connection interupted"));
|
query.Fail(new WebError("Connection interrupted"));
|
||||||
_listeners.Remove(query);
|
_listeners.Remove(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,7 +311,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
foreach (var query in _listeners.OfType<Query>().ToList())
|
foreach (var query in _listeners.OfType<Query>().ToList())
|
||||||
{
|
{
|
||||||
query.Fail(new WebError("Connection interupted"));
|
query.Fail(new WebError("Connection interrupted"));
|
||||||
_listeners.Remove(query);
|
_listeners.Remove(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,7 +340,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
{
|
{
|
||||||
foreach (var query in _listeners.OfType<Query>().ToList())
|
foreach (var query in _listeners.OfType<Query>().ToList())
|
||||||
{
|
{
|
||||||
query.Fail(new WebError("Connection interupted"));
|
query.Fail(new WebError("Connection interrupted"));
|
||||||
_listeners.Remove(query);
|
_listeners.Remove(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -369,7 +369,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch(Exception ex)
|
||||||
{
|
{
|
||||||
_logger.UnkownExceptionWhileProcessingReconnection(SocketId, ex);
|
_logger.UnknownExceptionWhileProcessingReconnection(SocketId, ex);
|
||||||
_ = _socket.ReconnectAsync().ConfigureAwait(false);
|
_ = _socket.ReconnectAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -392,7 +392,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handler for whenever a request is rate limited and rate limit behaviour is set to fail
|
/// Handler for whenever a request is rate limited and rate limit behavior is set to fail
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="requestId"></param>
|
/// <param name="requestId"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}");
|
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString())) || propValue.ToString() == "0")
|
if ((propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString()))) || propValue.ToString() == "0")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary)))
|
if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary)))
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
}
|
}
|
||||||
else if (jsonObject!.Type == JTokenType.Array)
|
else if (jsonObject!.Type == JTokenType.Array)
|
||||||
{
|
{
|
||||||
var jObjs = (JArray)jsonObject;
|
var jArray = (JArray)jsonObject;
|
||||||
if (resultData is IEnumerable list)
|
if (resultData is IEnumerable list)
|
||||||
{
|
{
|
||||||
var enumerator = list.GetEnumerator();
|
var enumerator = list.GetEnumerator();
|
||||||
foreach (var jObj in jObjs)
|
foreach (var jObj in jArray)
|
||||||
{
|
{
|
||||||
if (!enumerator.MoveNext())
|
if (!enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
@@ -105,7 +105,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var item in jObj.Children())
|
foreach (var item in jObj.Children())
|
||||||
{
|
{
|
||||||
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
|
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
|
||||||
if (arrayProp != null)
|
if (arrayProp != null)
|
||||||
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
|
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
|
||||||
i++;
|
i++;
|
||||||
@@ -123,7 +123,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
{
|
{
|
||||||
var resultProps = resultData.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
var resultProps = resultData.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var item in jObjs.Children())
|
foreach (var item in jArray.Children())
|
||||||
{
|
{
|
||||||
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
|
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
|
||||||
if (arrayProp != null)
|
if (arrayProp != null)
|
||||||
@@ -196,7 +196,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}");
|
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {propValue}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString())) || propValue.ToString() == "0")
|
if ((propertyValue == default && (propValue.Type == JTokenType.Null || string.IsNullOrEmpty(propValue.ToString()))) || propValue.ToString() == "0")
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary)))
|
if (propertyValue!.GetType().GetInterfaces().Contains(typeof(IDictionary)))
|
||||||
@@ -227,10 +227,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
if (propValue.Type != JTokenType.Array)
|
if (propValue.Type != JTokenType.Array)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var jObjs = (JArray)propValue;
|
var jArray = (JArray)propValue;
|
||||||
var list = (IEnumerable)propertyValue;
|
var list = (IEnumerable)propertyValue;
|
||||||
var enumerator = list.GetEnumerator();
|
var enumerator = list.GetEnumerator();
|
||||||
foreach (JToken jtoken in jObjs)
|
foreach (JToken jToken in jArray)
|
||||||
{
|
{
|
||||||
var moved = enumerator.MoveNext();
|
var moved = enumerator.MoveNext();
|
||||||
if (!moved)
|
if (!moved)
|
||||||
@@ -241,9 +241,9 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
// Custom converter for the type, skip
|
// Custom converter for the type, skip
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (jtoken.Type == JTokenType.Object)
|
if (jToken.Type == JTokenType.Object)
|
||||||
{
|
{
|
||||||
foreach (var subProp in ((JObject)jtoken).Properties())
|
foreach (var subProp in ((JObject)jToken).Properties())
|
||||||
{
|
{
|
||||||
if (ignoreProperties?.Contains(subProp.Name) == true)
|
if (ignoreProperties?.Contains(subProp.Name) == true)
|
||||||
continue;
|
continue;
|
||||||
@@ -251,7 +251,7 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
CheckObject(method, subProp, enumerator.Current, ignoreProperties);
|
CheckObject(method, subProp, enumerator.Current, ignoreProperties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (jtoken.Type == JTokenType.Array)
|
else if (jToken.Type == JTokenType.Array)
|
||||||
{
|
{
|
||||||
var resultObj = enumerator.Current;
|
var resultObj = enumerator.Current;
|
||||||
var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
var resultProps = resultObj.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
||||||
@@ -262,11 +262,11 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var item in jtoken.Children())
|
foreach (var item in jToken.Children())
|
||||||
{
|
{
|
||||||
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
|
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
|
||||||
if (arrayProp != null)
|
if (arrayProp != null)
|
||||||
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), propertyType, arrayProp.Name, "Array index " + i, ignoreProperties);
|
CheckPropertyValue(method, item, arrayProp.GetValue(resultObj), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties);
|
||||||
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
@@ -274,10 +274,10 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var value = enumerator.Current;
|
var value = enumerator.Current;
|
||||||
if (value == default && ((JValue)jtoken).Type != JTokenType.Null)
|
if (value == default && ((JValue)jToken).Type != JTokenType.Null)
|
||||||
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {jtoken}");
|
throw new Exception($"{method}: Property `{propertyName}` has no value while input json `{propName}` has value {jToken}");
|
||||||
|
|
||||||
CheckValues(method, propertyName!, propertyType, (JValue)jtoken, value!);
|
CheckValues(method, propertyName!, propertyType, (JValue)jToken, value!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -298,11 +298,11 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
}
|
}
|
||||||
else if (propValue.Type == JTokenType.Array)
|
else if (propValue.Type == JTokenType.Array)
|
||||||
{
|
{
|
||||||
var jObjs = (JArray)propValue;
|
var jArray = (JArray)propValue;
|
||||||
if (propertyValue is IEnumerable list)
|
if (propertyValue is IEnumerable list)
|
||||||
{
|
{
|
||||||
var enumerator = list.GetEnumerator();
|
var enumerator = list.GetEnumerator();
|
||||||
foreach (var jObj in jObjs)
|
foreach (var jObj in jArray)
|
||||||
{
|
{
|
||||||
if (!enumerator.MoveNext())
|
if (!enumerator.MoveNext())
|
||||||
{
|
{
|
||||||
@@ -348,9 +348,9 @@ namespace CryptoExchange.Net.Testing.Comparers
|
|||||||
{
|
{
|
||||||
var resultProps = propertyValue.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
var resultProps = propertyValue.GetType().GetProperties().Select(p => (p, p.GetCustomAttributes(typeof(ArrayPropertyAttribute), true).Cast<ArrayPropertyAttribute>().SingleOrDefault()));
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (var item in jObjs.Children())
|
foreach (var item in jArray.Children())
|
||||||
{
|
{
|
||||||
var arrayProp = resultProps.Where(p => p.Item2 != null).SingleOrDefault(p => p.Item2!.Index == i).p;
|
var arrayProp = resultProps.Where(p => p.Item2 != null).FirstOrDefault(p => p.Item2!.Index == i).p;
|
||||||
if (arrayProp != null)
|
if (arrayProp != null)
|
||||||
CheckPropertyValue(method, item, arrayProp.GetValue(propertyValue), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
|
CheckPropertyValue(method, item, arrayProp.GetValue(propertyValue), arrayProp.PropertyType, arrayProp.Name, "Array index " + i, ignoreProperties!);
|
||||||
i++;
|
i++;
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ namespace CryptoExchange.Net.Testing
|
|||||||
/// Base class for executing REST API integration tests
|
/// Base class for executing REST API integration tests
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TClient">Client type</typeparam>
|
/// <typeparam name="TClient">Client type</typeparam>
|
||||||
public abstract class RestIntergrationTest<TClient>
|
public abstract class RestIntegrationTest<TClient>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a client instance
|
/// Get a client instance
|
||||||
@@ -113,7 +113,6 @@ namespace CryptoExchange.Net.Testing
|
|||||||
if (lastMessage == null)
|
if (lastMessage == null)
|
||||||
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
|
throw new Exception($"{name} expected to {line} to be send to server but did not receive anything");
|
||||||
|
|
||||||
|
|
||||||
var lastMessageJson = JToken.Parse(lastMessage);
|
var lastMessageJson = JToken.Parse(lastMessage);
|
||||||
var expectedJson = JToken.Parse(line.Substring(2));
|
var expectedJson = JToken.Parse(line.Substring(2));
|
||||||
foreach(var item in expectedJson)
|
foreach(var item in expectedJson)
|
||||||
@@ -133,7 +132,9 @@ namespace CryptoExchange.Net.Testing
|
|||||||
overrideValue = lastMessageJson[prop.Name]?.Value<decimal>().ToString();
|
overrideValue = lastMessageJson[prop.Name]?.Value<decimal>().ToString();
|
||||||
}
|
}
|
||||||
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
|
else if (lastMessageJson[prop.Name]?.Value<string>() != val.ToString() && ignoreProperties?.Contains(prop.Name) != true)
|
||||||
|
{
|
||||||
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
|
throw new Exception($"{name} Expected {prop.Name} to be {val}, but was {lastMessageJson[prop.Name]?.Value<string>()}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO check objects and arrays
|
// TODO check objects and arrays
|
||||||
|
|||||||
@@ -150,9 +150,9 @@ namespace CryptoExchange.Net.Testing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TClient"></typeparam>
|
/// <typeparam name="TClient"></typeparam>
|
||||||
/// <exception cref="Exception"></exception>
|
/// <exception cref="Exception"></exception>
|
||||||
public static void CheckForMissingRestInterfaces<TClient>()
|
public static void CheckForMissingRestInterfaces<TClient>(string[]? excludeInterfaces = null)
|
||||||
{
|
{
|
||||||
CheckForMissingInterfaces(typeof(TClient), typeof(Task));
|
CheckForMissingInterfaces(typeof(TClient), typeof(Task), excludeInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -160,26 +160,32 @@ namespace CryptoExchange.Net.Testing
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TClient"></typeparam>
|
/// <typeparam name="TClient"></typeparam>
|
||||||
/// <exception cref="Exception"></exception>
|
/// <exception cref="Exception"></exception>
|
||||||
public static void CheckForMissingSocketInterfaces<TClient>()
|
public static void CheckForMissingSocketInterfaces<TClient>(string[]? excludeInterfaces = null)
|
||||||
{
|
{
|
||||||
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>));
|
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>), excludeInterfaces);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes)
|
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes, string[]? excludeInterfaces = null)
|
||||||
{
|
{
|
||||||
var assembly = Assembly.GetAssembly(clientType);
|
var assembly = Assembly.GetAssembly(clientType);
|
||||||
var interfaceType = clientType.GetInterface("I" + clientType.Name);
|
var interfaceType = clientType.GetInterface("I" + clientType.Name);
|
||||||
var clientInterfaces = assembly!.GetTypes().Where(t => t.Name.StartsWith("I" + clientType.Name) && !t.Name.EndsWith("Shared"));
|
var clientInterfaces = assembly!.GetTypes()
|
||||||
|
.Where(t => t.Name.StartsWith("I" + clientType.Name)
|
||||||
|
&& !t.Name.EndsWith("Shared")
|
||||||
|
&& (excludeInterfaces?.Contains(t.Name) != true));
|
||||||
|
|
||||||
foreach (var clientInterface in clientInterfaces)
|
foreach (var clientInterface in clientInterfaces)
|
||||||
{
|
{
|
||||||
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
|
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && !t.IsInterface && t != clientInterface);
|
||||||
foreach (var implementation in implementations)
|
foreach (var implementation in implementations)
|
||||||
{
|
{
|
||||||
int methods = 0;
|
int methods = 0;
|
||||||
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
|
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
|
||||||
{
|
{
|
||||||
var interfaceMethod = clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray()) ?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
|
var interfaceMethod =
|
||||||
|
clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())
|
||||||
|
?? clientInterface.GetInterfaces().Select(x => x.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())).FirstOrDefault()
|
||||||
|
?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
|
||||||
methods++;
|
methods++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace CryptoExchange.Net.Trackers.Klines
|
|||||||
IEnumerable<SharedKline> GetData(DateTime? fromTimestamp = null, DateTime? toTimestamp = null);
|
IEnumerable<SharedKline> GetData(DateTime? fromTimestamp = null, DateTime? toTimestamp = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get statitistics on the klines
|
/// Get statistics on the klines
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fromTimestamp">Start timestamp to get the data from, defaults to tracked data start time</param>
|
/// <param name="fromTimestamp">Start timestamp to get the data from, defaults to tracked data start time</param>
|
||||||
/// <param name="toTimestamp">End timestamp to get the data until, defaults to current time</param>
|
/// <param name="toTimestamp">End timestamp to get the data until, defaults to current time</param>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ namespace CryptoExchange.Net.Trackers.Trades
|
|||||||
IEnumerable<SharedTrade> GetData(DateTime? fromTimestamp = null, DateTime? toTimestamp = null);
|
IEnumerable<SharedTrade> GetData(DateTime? fromTimestamp = null, DateTime? toTimestamp = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get statitistics on the trades
|
/// Get statistics on the trades
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="fromTimestamp">Start timestamp to get the data from, defaults to tracked data start time</param>
|
/// <param name="fromTimestamp">Start timestamp to get the data from, defaults to tracked data start time</param>
|
||||||
/// <param name="toTimestamp">End timestamp to get the data until, defaults to current time</param>
|
/// <param name="toTimestamp">End timestamp to get the data until, defaults to current time</param>
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace CryptoExchange.Net.Trackers.Trades
|
|||||||
Period = period;
|
Period = period;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TradesStats GetStats(IEnumerable<SharedTrade> trades)
|
private static TradesStats GetStats(IEnumerable<SharedTrade> trades)
|
||||||
{
|
{
|
||||||
if (!trades.Any())
|
if (!trades.Any())
|
||||||
return new TradesStats();
|
return new TradesStats();
|
||||||
@@ -350,7 +350,7 @@ namespace CryptoExchange.Net.Trackers.Trades
|
|||||||
_data.Add(item);
|
_data.Add(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_data.Any())
|
if (_data.Count != 0)
|
||||||
_firstTimestamp = _data.Min(v => v.Timestamp);
|
_firstTimestamp = _data.Min(v => v.Timestamp);
|
||||||
|
|
||||||
ApplyWindow(false);
|
ApplyWindow(false);
|
||||||
@@ -431,7 +431,6 @@ namespace CryptoExchange.Net.Trackers.Trades
|
|||||||
SetSyncStatus();
|
SetSyncStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void HandleConnectionLost()
|
private void HandleConnectionLost()
|
||||||
{
|
{
|
||||||
_logger.TradeTrackerConnectionLost(SymbolName);
|
_logger.TradeTrackerConnectionLost(SymbolName);
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ namespace CryptoExchange.Net.Trackers.Trades
|
|||||||
public bool Complete { get; set; }
|
public bool Complete { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Compare 2 stat snapshots to eachother
|
/// Compare 2 stat snapshots to each other
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TradesCompare CompareTo(TradesStats otherStats)
|
public TradesCompare CompareTo(TradesStats otherStats)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,23 +5,26 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Binance.Net" Version="10.9.0" />
|
<PackageReference Include="Binance.Net" Version="10.18.0" />
|
||||||
<PackageReference Include="Bitfinex.Net" Version="7.10.0" />
|
<PackageReference Include="Bitfinex.Net" Version="8.1.1" />
|
||||||
<PackageReference Include="BitMart.Net" Version="1.7.0" />
|
<PackageReference Include="BitMart.Net" Version="1.14.0" />
|
||||||
<PackageReference Include="Bybit.Net" Version="3.16.0" />
|
<PackageReference Include="Bybit.Net" Version="4.3.2" />
|
||||||
<PackageReference Include="CoinEx.Net" Version="7.9.0" />
|
<PackageReference Include="CoinEx.Net" Version="8.0.1" />
|
||||||
<PackageReference Include="CryptoCom.Net" Version="1.2.0" />
|
<PackageReference Include="CryptoCom.Net" Version="1.6.0" />
|
||||||
<PackageReference Include="GateIo.Net" Version="1.12.0" />
|
<PackageReference Include="DeepCoin.Net" Version="1.0.0" />
|
||||||
<PackageReference Include="JK.BingX.Net" Version="1.14.0" />
|
<PackageReference Include="GateIo.Net" Version="1.20.1" />
|
||||||
<PackageReference Include="JK.Bitget.Net" Version="1.13.0" />
|
<PackageReference Include="HyperLiquid.Net" Version="1.1.0" />
|
||||||
<PackageReference Include="JK.Mexc.Net" Version="1.11.0" />
|
<PackageReference Include="JK.BingX.Net" Version="1.21.0" />
|
||||||
<PackageReference Include="JK.OKX.Net" Version="2.8.0" />
|
<PackageReference Include="JK.Bitget.Net" Version="1.21.0" />
|
||||||
<PackageReference Include="JKorf.Coinbase.Net" Version="1.4.0" />
|
<PackageReference Include="JK.Mexc.Net" Version="2.1.0" />
|
||||||
<PackageReference Include="JKorf.HTX.Net" Version="6.4.0" />
|
<PackageReference Include="JK.OKX.Net" Version="2.15.0" />
|
||||||
<PackageReference Include="KrakenExchange.Net" Version="5.2.0" />
|
<PackageReference Include="JKorf.BitMEX.Net" Version="1.1.0" />
|
||||||
<PackageReference Include="Kucoin.Net" Version="5.18.0" />
|
<PackageReference Include="JKorf.Coinbase.Net" Version="1.8.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
<PackageReference Include="JKorf.HTX.Net" Version="6.9.0" />
|
||||||
<PackageReference Include="WhiteBit.Net" Version="1.0.0" />
|
<PackageReference Include="KrakenExchange.Net" Version="5.7.1" />
|
||||||
|
<PackageReference Include="Kucoin.Net" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||||
|
<PackageReference Include="WhiteBit.Net" Version="1.4.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
@inject IBinanceRestClient binanceClient
|
@inject IBinanceRestClient binanceClient
|
||||||
@inject IBingXRestClient bingXClient
|
@inject IBingXRestClient bingXClient
|
||||||
@inject IBitfinexRestClient bitfinexClient
|
@inject IBitfinexRestClient bitfinexClient
|
||||||
@inject IBitMartRestClient bitmartClient
|
|
||||||
@inject IBitgetRestClient bitgetClient
|
@inject IBitgetRestClient bitgetClient
|
||||||
|
@inject IBitMartRestClient bitmartClient
|
||||||
|
@inject IBitMEXRestClient bitmexClient
|
||||||
@inject IBybitRestClient bybitClient
|
@inject IBybitRestClient bybitClient
|
||||||
@inject ICoinbaseRestClient coinbaseClient
|
@inject ICoinbaseRestClient coinbaseClient
|
||||||
@inject ICoinExRestClient coinexClient
|
@inject ICoinExRestClient coinexClient
|
||||||
@inject ICryptoComRestClient cryptocomClient
|
@inject ICryptoComRestClient cryptocomClient
|
||||||
|
@inject IDeepCoinRestClient deepCoinClient
|
||||||
@inject IGateIoRestClient gateioClient
|
@inject IGateIoRestClient gateioClient
|
||||||
@inject IHTXRestClient huobiClient
|
@inject IHTXRestClient htxClient
|
||||||
|
@inject IHyperLiquidRestClient hyperLiquidClient
|
||||||
@inject IKrakenRestClient krakenClient
|
@inject IKrakenRestClient krakenClient
|
||||||
@inject IKucoinRestClient kucoinClient
|
@inject IKucoinRestClient kucoinClient
|
||||||
@inject IMexcRestClient mexcClient
|
@inject IMexcRestClient mexcClient
|
||||||
@@ -32,19 +35,22 @@
|
|||||||
var bitfinexTask = bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUSD");
|
var bitfinexTask = bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUSD");
|
||||||
var bitgetTask = bitgetClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT_SPBL");
|
var bitgetTask = bitgetClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT_SPBL");
|
||||||
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
|
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
|
||||||
|
var bitmexTask = bitmexClient.ExchangeApi.ExchangeData.GetSymbolsAsync("XBT_USDT");
|
||||||
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
|
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
|
||||||
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
|
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
|
||||||
var coinexTask = coinexClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
var coinexTask = coinexClient.SpotApiV2.ExchangeData.GetTickersAsync(["BTCUSDT"]);
|
||||||
var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
|
var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
|
||||||
|
var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot);
|
||||||
var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
|
var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT");
|
||||||
var htxTask = huobiClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
|
var htxTask = htxClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt");
|
||||||
|
var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync(); // HyperLiquid does not have BTC spot trading
|
||||||
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
|
var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD");
|
||||||
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
|
var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT");
|
||||||
var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
||||||
var okxTask = okxClient.UnifiedApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
var okxTask = okxClient.UnifiedApi.ExchangeData.GetTickerAsync("BTCUSDT");
|
||||||
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
|
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
|
||||||
|
|
||||||
await Task.WhenAll(binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bybitTask, coinexTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask);
|
await Task.WhenAll(binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bybitTask, coinexTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask);
|
||||||
|
|
||||||
if (binanceTask.Result.Success)
|
if (binanceTask.Result.Success)
|
||||||
_prices.Add("Binance", binanceTask.Result.Data.LastPrice);
|
_prices.Add("Binance", binanceTask.Result.Data.LastPrice);
|
||||||
@@ -61,6 +67,9 @@
|
|||||||
if (bitmartTask.Result.Success)
|
if (bitmartTask.Result.Success)
|
||||||
_prices.Add("BitMart", bitgetTask.Result.Data.ClosePrice);
|
_prices.Add("BitMart", bitgetTask.Result.Data.ClosePrice);
|
||||||
|
|
||||||
|
if (bitmexTask.Result.Success)
|
||||||
|
_prices.Add("BitMEX", bitmexTask.Result.Data.First().LastPrice);
|
||||||
|
|
||||||
if (bybitTask.Result.Success)
|
if (bybitTask.Result.Success)
|
||||||
_prices.Add("Bybit", bybitTask.Result.Data.List.First().LastPrice);
|
_prices.Add("Bybit", bybitTask.Result.Data.List.First().LastPrice);
|
||||||
|
|
||||||
@@ -68,17 +77,31 @@
|
|||||||
_prices.Add("Coinbase", coinbaseTask.Result.Data.LastPrice ?? 0);
|
_prices.Add("Coinbase", coinbaseTask.Result.Data.LastPrice ?? 0);
|
||||||
|
|
||||||
if (coinexTask.Result.Success)
|
if (coinexTask.Result.Success)
|
||||||
_prices.Add("CoinEx", coinexTask.Result.Data.Ticker.LastPrice);
|
_prices.Add("CoinEx", coinexTask.Result.Data.Single().LastPrice);
|
||||||
|
|
||||||
if (cryptocomTask.Result.Success)
|
if (cryptocomTask.Result.Success)
|
||||||
_prices.Add("CryptoCom", cryptocomTask.Result.Data.First().LastPrice ?? 0);
|
_prices.Add("CryptoCom", cryptocomTask.Result.Data.First().LastPrice ?? 0);
|
||||||
|
|
||||||
|
if (deepCoinTask.Result.Success)
|
||||||
|
{
|
||||||
|
// DeepCoin API doesn't offer an endpoint to filter for a specific ticker, so we have to filter client side
|
||||||
|
var tickers = deepCoinTask.Result.Data;
|
||||||
|
_prices.Add("DeepCoin", tickers.Single(x => x.Symbol == "BTC-USDT").LastPrice ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
if (gateioTask.Result.Success)
|
if (gateioTask.Result.Success)
|
||||||
_prices.Add("GateIo", gateioTask.Result.Data.First().LastPrice);
|
_prices.Add("GateIo", gateioTask.Result.Data.First().LastPrice);
|
||||||
|
|
||||||
if (htxTask.Result.Success)
|
if (htxTask.Result.Success)
|
||||||
_prices.Add("HTX", htxTask.Result.Data.ClosePrice ?? 0);
|
_prices.Add("HTX", htxTask.Result.Data.ClosePrice ?? 0);
|
||||||
|
|
||||||
|
if (hyperLiquidTask.Result.Success)
|
||||||
|
{
|
||||||
|
// HyperLiquid API doesn't offer an endpoint to filter for a specific ticker, so we have to filter client side
|
||||||
|
var tickers = hyperLiquidTask.Result.Data.Tickers;
|
||||||
|
_prices.Add("HyperLiquid", tickers.Single(x => x.Symbol == "BTC").MidPrice ?? 9);
|
||||||
|
}
|
||||||
|
|
||||||
if (krakenTask.Result.Success)
|
if (krakenTask.Result.Success)
|
||||||
_prices.Add("Kraken", krakenTask.Result.Data.First().Value.LastTrade.Price);
|
_prices.Add("Kraken", krakenTask.Result.Data.First().Value.LastTrade.Price);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,15 @@
|
|||||||
@inject IBitfinexSocketClient bitfinexSocketClient
|
@inject IBitfinexSocketClient bitfinexSocketClient
|
||||||
@inject IBitgetSocketClient bitgetSocketClient
|
@inject IBitgetSocketClient bitgetSocketClient
|
||||||
@inject IBitMartSocketClient bitmartSocketClient
|
@inject IBitMartSocketClient bitmartSocketClient
|
||||||
|
@inject IBitMEXSocketClient bitmexSocketClient
|
||||||
@inject IBybitSocketClient bybitSocketClient
|
@inject IBybitSocketClient bybitSocketClient
|
||||||
@inject ICoinbaseSocketClient coinbaseSocketClient
|
@inject ICoinbaseSocketClient coinbaseSocketClient
|
||||||
@inject ICoinExSocketClient coinExSocketClient
|
@inject ICoinExSocketClient coinExSocketClient
|
||||||
@inject ICryptoComSocketClient cryptocomSocketClient
|
@inject ICryptoComSocketClient cryptocomSocketClient
|
||||||
|
@inject IDeepCoinSocketClient deepCoinSocketClient
|
||||||
@inject IGateIoSocketClient gateioSocketClient
|
@inject IGateIoSocketClient gateioSocketClient
|
||||||
@inject IHTXSocketClient htxSocketClient
|
@inject IHTXSocketClient htxSocketClient
|
||||||
|
@inject IHyperLiquidSocketClient hyperLiquidSocketClient
|
||||||
@inject IKrakenSocketClient krakenSocketClient
|
@inject IKrakenSocketClient krakenSocketClient
|
||||||
@inject IKucoinSocketClient kucoinSocketClient
|
@inject IKucoinSocketClient kucoinSocketClient
|
||||||
@inject IMexcSocketClient mexcSocketClient
|
@inject IMexcSocketClient mexcSocketClient
|
||||||
@@ -40,13 +43,17 @@
|
|||||||
bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
|
bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
|
||||||
bitgetSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.LastPrice)),
|
bitgetSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.LastPrice)),
|
||||||
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
|
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
|
||||||
|
bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)),
|
||||||
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
||||||
coinExSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("CoinEx", data.Data.LastPrice)),
|
coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(["ETHBTC"], data => UpdateData("CoinEx", data.Data.First().LastPrice)),
|
||||||
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice)),
|
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice ?? 0)),
|
||||||
cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)),
|
cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)),
|
||||||
|
deepCoinSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH-BTC", data => UpdateData("DeepCoin", data.Data.LastPrice ?? 0)),
|
||||||
gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("GateIo", data.Data.LastPrice)),
|
gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("GateIo", data.Data.LastPrice)),
|
||||||
htxSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("HTX", data.Data.ClosePrice ?? 0)),
|
htxSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ethbtc", data => UpdateData("HTX", data.Data.ClosePrice ?? 0)),
|
||||||
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/XBT", data => UpdateData("Kraken", data.Data.LastPrice)),
|
// HyperLiquid doesn't support the ETH/BTC pair
|
||||||
|
//hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("ETH", data => UpdateData("HyperLiquid", data.Data.MidPrice ?? 0)),
|
||||||
|
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/BTC", data => UpdateData("Kraken", data.Data.LastPrice)),
|
||||||
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
|
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
|
||||||
mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
|
mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
|
||||||
okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("OKX", data.Data.LastPrice ?? 0)),
|
okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("OKX", data.Data.LastPrice ?? 0)),
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
@using Bitfinex.Net.Interfaces
|
@using Bitfinex.Net.Interfaces
|
||||||
@using Bitget.Net.Interfaces;
|
@using Bitget.Net.Interfaces;
|
||||||
@using BitMart.Net.Interfaces;
|
@using BitMart.Net.Interfaces;
|
||||||
|
@using BitMEX.Net.Interfaces;
|
||||||
@using Bybit.Net.Interfaces
|
@using Bybit.Net.Interfaces
|
||||||
@using CoinEx.Net.Interfaces
|
@using CoinEx.Net.Interfaces
|
||||||
@using Coinbase.Net.Interfaces
|
@using Coinbase.Net.Interfaces
|
||||||
@using CryptoExchange.Net.Interfaces
|
@using CryptoExchange.Net.Interfaces
|
||||||
@using CryptoCom.Net.Interfaces
|
@using CryptoCom.Net.Interfaces
|
||||||
|
@using DeepCoin.Net.Interfaces
|
||||||
@using GateIo.Net.Interfaces
|
@using GateIo.Net.Interfaces
|
||||||
@using HTX.Net.Interfaces
|
@using HTX.Net.Interfaces
|
||||||
|
@using HyperLiquid.Net.Interfaces
|
||||||
@using Kraken.Net.Interfaces
|
@using Kraken.Net.Interfaces
|
||||||
@using Kucoin.Net.Clients
|
@using Kucoin.Net.Clients
|
||||||
@using Kucoin.Net.Interfaces
|
@using Kucoin.Net.Interfaces
|
||||||
@@ -24,12 +27,15 @@
|
|||||||
@inject IBitfinexOrderBookFactory bitfinexFactory
|
@inject IBitfinexOrderBookFactory bitfinexFactory
|
||||||
@inject IBitgetOrderBookFactory bitgetFactory
|
@inject IBitgetOrderBookFactory bitgetFactory
|
||||||
@inject IBitMartOrderBookFactory bitmartFactory
|
@inject IBitMartOrderBookFactory bitmartFactory
|
||||||
|
@inject IBitMEXOrderBookFactory bitmexFactory
|
||||||
@inject IBybitOrderBookFactory bybitFactory
|
@inject IBybitOrderBookFactory bybitFactory
|
||||||
@inject ICoinbaseOrderBookFactory coinbaseFactory
|
@inject ICoinbaseOrderBookFactory coinbaseFactory
|
||||||
@inject ICoinExOrderBookFactory coinExFactory
|
@inject ICoinExOrderBookFactory coinExFactory
|
||||||
@inject ICryptoComOrderBookFactory cryptocomFactory
|
@inject ICryptoComOrderBookFactory cryptocomFactory
|
||||||
|
@inject IDeepCoinOrderBookFactory deepCoinFactory
|
||||||
@inject IGateIoOrderBookFactory gateioFactory
|
@inject IGateIoOrderBookFactory gateioFactory
|
||||||
@inject IHTXOrderBookFactory htxFactory
|
@inject IHTXOrderBookFactory htxFactory
|
||||||
|
@inject IHyperLiquidOrderBookFactory hyperLiquidFactory
|
||||||
@inject IKrakenOrderBookFactory krakenFactory
|
@inject IKrakenOrderBookFactory krakenFactory
|
||||||
@inject IKucoinOrderBookFactory kucoinFactory
|
@inject IKucoinOrderBookFactory kucoinFactory
|
||||||
@inject IMexcOrderBookFactory mexcFactory
|
@inject IMexcOrderBookFactory mexcFactory
|
||||||
@@ -73,12 +79,16 @@
|
|||||||
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
|
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
|
||||||
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
|
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
|
||||||
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
|
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
|
||||||
|
{ "BitMEX", bitmexFactory.Create("ETH_XBT") },
|
||||||
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
||||||
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
||||||
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
||||||
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
|
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
|
||||||
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
|
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
|
||||||
|
{ "DeepCoin", deepCoinFactory.Create("ETH-BTC") },
|
||||||
{ "HTX", htxFactory.CreateSpot("ethbtc") },
|
{ "HTX", htxFactory.CreateSpot("ethbtc") },
|
||||||
|
// HyperLiquid does not support the ETH/BTC pair
|
||||||
|
//{ "HyperLiquid", hyperLiquidFactory.Create("ETH/BTC") },
|
||||||
{ "Kraken", krakenFactory.CreateSpot("ETH/BTC") },
|
{ "Kraken", krakenFactory.CreateSpot("ETH/BTC") },
|
||||||
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
|
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
|
||||||
{ "Mexc", mexcFactory.CreateSpot("ETHBTC") },
|
{ "Mexc", mexcFactory.CreateSpot("ETHBTC") },
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/SpotClient"
|
@page "/SpotClient"
|
||||||
@using CryptoExchange.Net.SharedApis
|
@using CryptoExchange.Net.SharedApis
|
||||||
|
@using System.Diagnostics
|
||||||
@inject IEnumerable<ISpotTickerRestClient> restClients
|
@inject IEnumerable<ISpotTickerRestClient> restClients
|
||||||
|
|
||||||
<h3>ETH-BTC prices:</h3>
|
<h3>ETH-BTC prices:</h3>
|
||||||
@@ -20,6 +21,8 @@
|
|||||||
{
|
{
|
||||||
if (ticker.Success)
|
if (ticker.Success)
|
||||||
_prices.Add(ticker.Exchange, ticker.Data.LastPrice);
|
_prices.Add(ticker.Exchange, ticker.Data.LastPrice);
|
||||||
|
else
|
||||||
|
Debug.WriteLine($"{ticker.Exchange} failed: {ticker.Error}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
@using BingX.Net.Interfaces
|
@using BingX.Net.Interfaces
|
||||||
@using Bitfinex.Net.Interfaces
|
@using Bitfinex.Net.Interfaces
|
||||||
@using Bitget.Net.Interfaces;
|
@using Bitget.Net.Interfaces;
|
||||||
|
@using BitMEX.Net.Interfaces;
|
||||||
@using BitMart.Net.Interfaces;
|
@using BitMart.Net.Interfaces;
|
||||||
@using Bybit.Net.Interfaces
|
@using Bybit.Net.Interfaces
|
||||||
@using CoinEx.Net.Interfaces
|
@using CoinEx.Net.Interfaces
|
||||||
@@ -13,8 +14,10 @@
|
|||||||
@using CryptoCom.Net.Interfaces
|
@using CryptoCom.Net.Interfaces
|
||||||
@using CryptoExchange.Net.SharedApis
|
@using CryptoExchange.Net.SharedApis
|
||||||
@using CryptoExchange.Net.Trackers.Trades
|
@using CryptoExchange.Net.Trackers.Trades
|
||||||
|
@using DeepCoin.Net.Interfaces
|
||||||
@using GateIo.Net.Interfaces
|
@using GateIo.Net.Interfaces
|
||||||
@using HTX.Net.Interfaces
|
@using HTX.Net.Interfaces
|
||||||
|
@using HyperLiquid.Net.Interfaces
|
||||||
@using Kraken.Net.Interfaces
|
@using Kraken.Net.Interfaces
|
||||||
@using Kucoin.Net.Clients
|
@using Kucoin.Net.Clients
|
||||||
@using Kucoin.Net.Interfaces
|
@using Kucoin.Net.Interfaces
|
||||||
@@ -26,12 +29,15 @@
|
|||||||
@inject IBitfinexTrackerFactory bitfinexFactory
|
@inject IBitfinexTrackerFactory bitfinexFactory
|
||||||
@inject IBitgetTrackerFactory bitgetFactory
|
@inject IBitgetTrackerFactory bitgetFactory
|
||||||
@inject IBitMartTrackerFactory bitmartFactory
|
@inject IBitMartTrackerFactory bitmartFactory
|
||||||
|
@inject IBitMEXTrackerFactory bitmexFactory
|
||||||
@inject IBybitTrackerFactory bybitFactory
|
@inject IBybitTrackerFactory bybitFactory
|
||||||
@inject ICoinbaseTrackerFactory coinbaseFactory
|
@inject ICoinbaseTrackerFactory coinbaseFactory
|
||||||
@inject ICoinExTrackerFactory coinExFactory
|
@inject ICoinExTrackerFactory coinExFactory
|
||||||
@inject ICryptoComTrackerFactory cryptocomFactory
|
@inject ICryptoComTrackerFactory cryptocomFactory
|
||||||
|
@inject IDeepCoinTrackerFactory deepCoinFactory
|
||||||
@inject IGateIoTrackerFactory gateioFactory
|
@inject IGateIoTrackerFactory gateioFactory
|
||||||
@inject IHTXTrackerFactory htxFactory
|
@inject IHTXTrackerFactory htxFactory
|
||||||
|
@inject IHyperLiquidTrackerFactory hyperLiquidFactory
|
||||||
@inject IKrakenTrackerFactory krakenFactory
|
@inject IKrakenTrackerFactory krakenFactory
|
||||||
@inject IKucoinTrackerFactory kucoinFactory
|
@inject IKucoinTrackerFactory kucoinFactory
|
||||||
@inject IMexcTrackerFactory mexcFactory
|
@inject IMexcTrackerFactory mexcFactory
|
||||||
@@ -59,26 +65,30 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
var usdtSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||||
|
|
||||||
_trackers = new List<ITradeTracker>
|
_trackers = new List<ITradeTracker>
|
||||||
{
|
{
|
||||||
{ binanceFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ binanceFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ bingXFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bingXFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bitfinexFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bitgetFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bitmartFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bitmexFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ bybitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ coinExFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ coinbaseFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ cryptocomFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ coinExFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ gateioFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ cryptocomFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ htxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ deepCoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ krakenFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ gateioFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ kucoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ htxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
{ mexcFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
// HyperLiquid doesn't support spot pair, but does have a futures BTC/USDC pair
|
||||||
{ okxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ hyperLiquidFactory.CreateTradeTracker(new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDC"), period: TimeSpan.FromMinutes(5)) },
|
||||||
{ whitebitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
{ krakenFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ kucoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ mexcFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ okxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ whitebitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
};
|
};
|
||||||
|
|
||||||
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
|
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
|
||||||
|
|||||||
@@ -40,11 +40,14 @@ namespace BlazorClient
|
|||||||
services.AddBitfinex();
|
services.AddBitfinex();
|
||||||
services.AddBitget();
|
services.AddBitget();
|
||||||
services.AddBitMart();
|
services.AddBitMart();
|
||||||
|
services.AddBitMEX();
|
||||||
services.AddBybit();
|
services.AddBybit();
|
||||||
services.AddCoinbase();
|
services.AddCoinbase();
|
||||||
services.AddCoinEx();
|
services.AddCoinEx();
|
||||||
services.AddCryptoCom();
|
services.AddCryptoCom();
|
||||||
|
services.AddDeepCoin();
|
||||||
services.AddGateIo();
|
services.AddGateIo();
|
||||||
|
services.AddHyperLiquid();
|
||||||
services.AddHTX();
|
services.AddHTX();
|
||||||
services.AddKraken();
|
services.AddKraken();
|
||||||
services.AddKucoin();
|
services.AddKucoin();
|
||||||
|
|||||||
@@ -13,12 +13,15 @@
|
|||||||
@using Bitfinex.Net.Interfaces.Clients;
|
@using Bitfinex.Net.Interfaces.Clients;
|
||||||
@using Bitget.Net.Interfaces.Clients;
|
@using Bitget.Net.Interfaces.Clients;
|
||||||
@using BitMart.Net.Interfaces.Clients;
|
@using BitMart.Net.Interfaces.Clients;
|
||||||
|
@using BitMEX.Net.Interfaces.Clients;
|
||||||
@using Bybit.Net.Interfaces.Clients;
|
@using Bybit.Net.Interfaces.Clients;
|
||||||
@using Coinbase.Net.Interfaces.Clients;
|
@using Coinbase.Net.Interfaces.Clients;
|
||||||
@using CoinEx.Net.Interfaces.Clients;
|
@using CoinEx.Net.Interfaces.Clients;
|
||||||
@using CryptoCom.Net.Interfaces.Clients;
|
@using CryptoCom.Net.Interfaces.Clients;
|
||||||
|
@using DeepCoin.Net.Interfaces.Clients;
|
||||||
@using GateIo.Net.Interfaces.Clients;
|
@using GateIo.Net.Interfaces.Clients;
|
||||||
@using HTX.Net.Interfaces.Clients;
|
@using HTX.Net.Interfaces.Clients;
|
||||||
|
@using HyperLiquid.Net.Interfaces.Clients;
|
||||||
@using Kraken.Net.Interfaces.Clients;
|
@using Kraken.Net.Interfaces.Clients;
|
||||||
@using Kucoin.Net.Interfaces.Clients;
|
@using Kucoin.Net.Interfaces.Clients;
|
||||||
@using Mexc.Net.Interfaces.Clients;
|
@using Mexc.Net.Interfaces.Clients;
|
||||||
|
|||||||
@@ -17,13 +17,16 @@ The following API's are directly supported. Note that there are 3rd party implem
|
|||||||
|Bitfinex|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[](https://www.nuget.org/packages/Bitfinex.Net)|
|
|Bitfinex|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[](https://www.nuget.org/packages/Bitfinex.Net)|
|
||||||
|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[](https://www.nuget.org/packages/JK.Bitget.Net)|
|
|Bitget|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[](https://www.nuget.org/packages/JK.Bitget.Net)|
|
||||||
|BitMart|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[](https://www.nuget.org/packages/BitMart.Net)|
|
|BitMart|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[](https://www.nuget.org/packages/BitMart.Net)|
|
||||||
|
|BitMEX|[JKorf/BitMEX.Net](https://github.com/JKorf/BitMEX.Net)|[](https://www.nuget.org/packages/JKorf.BitMEX.Net)|
|
||||||
|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[](https://www.nuget.org/packages/Bybit.Net)|
|
|Bybit|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[](https://www.nuget.org/packages/Bybit.Net)|
|
||||||
|Coinbase|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[](https://www.nuget.org/packages/JKorf.Coinbase.Net)|
|
|Coinbase|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[](https://www.nuget.org/packages/JKorf.Coinbase.Net)|
|
||||||
|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[](https://www.nuget.org/packages/CoinEx.Net)|
|
|CoinEx|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[](https://www.nuget.org/packages/CoinEx.Net)|
|
||||||
|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[](https://www.nuget.org/packages/CoinGecko.Net)|
|
|CoinGecko|[JKorf/CoinGecko.Net](https://github.com/JKorf/CoinGecko.Net)|[](https://www.nuget.org/packages/CoinGecko.Net)|
|
||||||
|Crypto.com|[JKorf/CryptoCom.Net](https://github.com/JKorf/CryptoCom.Net)|[](https://www.nuget.org/packages/CryptoCom.Net)|
|
|Crypto.com|[JKorf/CryptoCom.Net](https://github.com/JKorf/CryptoCom.Net)|[](https://www.nuget.org/packages/CryptoCom.Net)|
|
||||||
|
|DeepCoin|[JKorf/DeepCoin.Net](https://github.com/JKorf/DeepCoin.Net)|[](https://www.nuget.org/packages/DeepCoin.Net)|
|
||||||
|Gate.io|[JKorf/GateIo.Net](https://github.com/JKorf/GateIo.Net)|[](https://www.nuget.org/packages/GateIo.Net)|
|
|Gate.io|[JKorf/GateIo.Net](https://github.com/JKorf/GateIo.Net)|[](https://www.nuget.org/packages/GateIo.Net)|
|
||||||
|HTX|[JKorf/HTX.Net](https://github.com/JKorf/HTX.Net)|[](https://www.nuget.org/packages/JKorf.HTX.Net)|
|
|HTX|[JKorf/HTX.Net](https://github.com/JKorf/HTX.Net)|[](https://www.nuget.org/packages/JKorf.HTX.Net)|
|
||||||
|
|HyperLiquid|[JKorf/HyperLiquid.Net](https://github.com/JKorf/HyperLiquid.Net)|[](https://www.nuget.org/packages/HyperLiquid.Net)|
|
||||||
|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[](https://www.nuget.org/packages/KrakenExchange.Net)|
|
|Kraken|[JKorf/Kraken.Net](https://github.com/JKorf/Kraken.Net)|[](https://www.nuget.org/packages/KrakenExchange.Net)|
|
||||||
|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[](https://www.nuget.org/packages/Kucoin.Net)|
|
|Kucoin|[JKorf/Kucoin.Net](https://github.com/JKorf/Kucoin.Net)|[](https://www.nuget.org/packages/Kucoin.Net)|
|
||||||
|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[](https://www.nuget.org/packages/JK.Mexc.Net)|
|
|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[](https://www.nuget.org/packages/JK.Mexc.Net)|
|
||||||
@@ -49,7 +52,9 @@ When creating an account on new exchanges please consider using a referral link
|
|||||||
|Coinbase|[https://advanced.coinbase.com/join/T6H54H8](https://advanced.coinbase.com/join/T6H54H8)|
|
|Coinbase|[https://advanced.coinbase.com/join/T6H54H8](https://advanced.coinbase.com/join/T6H54H8)|
|
||||||
|CoinEx|[https://www.coinex.com/register?refer_code=hd6gn](https://www.coinex.com/register?refer_code=hd6gn)|
|
|CoinEx|[https://www.coinex.com/register?refer_code=hd6gn](https://www.coinex.com/register?refer_code=hd6gn)|
|
||||||
|Crypto.com|[https://crypto.com/exch/26ge92xbkn](https://crypto.com/exch/26ge92xbkn)|
|
|Crypto.com|[https://crypto.com/exch/26ge92xbkn](https://crypto.com/exch/26ge92xbkn)|
|
||||||
|
|DeepCoin|[https://s.deepcoin.com/jddhfca)|
|
||||||
|HTX|[https://www.htx.com/invite/en-us/1f?invite_code=fxp9](https://www.htx.com/invite/en-us/1f?invite_code=fxp9)|
|
|HTX|[https://www.htx.com/invite/en-us/1f?invite_code=fxp9](https://www.htx.com/invite/en-us/1f?invite_code=fxp9)|
|
||||||
|
|HyperLiquid|[https://app.hyperliquid.xyz/join/JKORF](https://app.hyperliquid.xyz/join/JKORF)|
|
||||||
|Kucoin|[https://www.kucoin.com/r/rf/QBS4FPED](https://www.kucoin.com/r/rf/QBS4FPED)|
|
|Kucoin|[https://www.kucoin.com/r/rf/QBS4FPED](https://www.kucoin.com/r/rf/QBS4FPED)|
|
||||||
|OKX|[https://okx.com/join/48046699](https://okx.com/join/48046699)|
|
|OKX|[https://okx.com/join/48046699](https://okx.com/join/48046699)|
|
||||||
|WhiteBit|[https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf](https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf)|
|
|WhiteBit|[https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf](https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf)|
|
||||||
@@ -66,6 +71,36 @@ 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).
|
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 8.8.0 - 10 Feb 2025
|
||||||
|
* Split DataEvent.Timestamp in DataEvent.ReceivedTime and DataEvent.DataTime
|
||||||
|
* Added SharedKlineInterval enum values
|
||||||
|
* Fixed some typos
|
||||||
|
|
||||||
|
* Version 8.7.4 - 08 Feb 2025
|
||||||
|
* Fixed exception when creating rest client for mono runtime
|
||||||
|
|
||||||
|
* Version 8.7.3 - 05 Feb 2025
|
||||||
|
* Added handling of negative number DateTime deserialization to default
|
||||||
|
* Updated SharedSymbol from class to record
|
||||||
|
* Fixed issue with serialization of nullable types in System.Text.Json ArrayConverter
|
||||||
|
* Fix for unnecessary error message in logging when closing websocket connection
|
||||||
|
|
||||||
|
* Version 8.7.2 - 27 Jan 2025
|
||||||
|
* Some small fixes in the System.Text.Json ArrayConverter
|
||||||
|
* Added support for Flags enum deserialization in System.Text.Json EnumConverter
|
||||||
|
|
||||||
|
* Version 8.7.1 - 24 Jan 2025
|
||||||
|
* Added Authenticated property to IBaseApiClient interface to check if a client was provided API credentials
|
||||||
|
|
||||||
|
* Version 8.7.0 - 21 Jan 2025
|
||||||
|
* Added GetMillisecondTimestampLong helper method to AuthenticationProvider
|
||||||
|
* Added PriceSignificationFigures to SharedSpotSymbol model
|
||||||
|
|
||||||
|
* Version 8.6.1 - 09 Jan 2025
|
||||||
|
* Fixed websocket connection getting stuck after a ping frame timeout
|
||||||
|
* Removed websocket Error callback when exception is expected
|
||||||
|
* Removed unnecessary type restraints on RestApiClient.SendAsync methods
|
||||||
|
|
||||||
* Version 8.6.0 - 07 Jan 2025
|
* Version 8.6.0 - 07 Jan 2025
|
||||||
* Added support for passing weight to apply to an individual ratelimit guard
|
* Added support for passing weight to apply to an individual ratelimit guard
|
||||||
* Added IFeeRestClient to service registration
|
* Added IFeeRestClient to service registration
|
||||||
|
|||||||
+234
-3
@@ -151,13 +151,16 @@
|
|||||||
<tr><td>Bitfinex</td><td><a href="https://github.com/JKorf/Bitfinex.Net">JKorf/Bitfinex.Net</a></td><td><a href="https://www.nuget.org/packages/Bitfinex.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Bitfinex.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Bitfinex</td><td><a href="https://github.com/JKorf/Bitfinex.Net">JKorf/Bitfinex.Net</a></td><td><a href="https://www.nuget.org/packages/Bitfinex.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Bitfinex.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Bitget</td><td><a href="https://github.com/JKorf/Bitget.Net">JKorf/Bitget.Net</a></td><td><a href="https://www.nuget.org/packages/JK.Bitget.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.Bitget.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Bitget</td><td><a href="https://github.com/JKorf/Bitget.Net">JKorf/Bitget.Net</a></td><td><a href="https://www.nuget.org/packages/JK.Bitget.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.Bitget.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>BitMart</td><td><a href="https://github.com/JKorf/BitMart.Net">JKorf/BitMart.Net</a></td><td><a href="https://www.nuget.org/packages/BitMart.Net" target="_blank"><img src="https://img.shields.io/nuget/v/BitMart.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>BitMart</td><td><a href="https://github.com/JKorf/BitMart.Net">JKorf/BitMart.Net</a></td><td><a href="https://www.nuget.org/packages/BitMart.Net" target="_blank"><img src="https://img.shields.io/nuget/v/BitMart.net.svg?style=flat-square" /></a></td></tr>
|
||||||
|
<tr><td>BitMEX</td><td><a href="https://github.com/JKorf/BitMEX.Net">JKorf/BitMEX.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.BitMEX.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.BitMEX.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Bybit</td><td><a href="https://github.com/JKorf/Bybit.Net">JKorf/Bybit.Net</a></td><td><a href="https://www.nuget.org/packages/Bybit.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Bybit</td><td><a href="https://github.com/JKorf/Bybit.Net">JKorf/Bybit.Net</a></td><td><a href="https://www.nuget.org/packages/Bybit.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Coinbase</td><td><a href="https://github.com/JKorf/Coinbase.Net">JKorf/Coinbase.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.Coinbase.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.Coinbase.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Coinbase</td><td><a href="https://github.com/JKorf/Coinbase.Net">JKorf/Coinbase.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.Coinbase.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.Coinbase.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>CoinEx</td><td><a href="https://github.com/JKorf/CoinEx.Net">JKorf/CoinEx.Net</a></td><td><a href="https://www.nuget.org/packages/CoinEx.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CoinEx.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>CoinEx</td><td><a href="https://github.com/JKorf/CoinEx.Net">JKorf/CoinEx.Net</a></td><td><a href="https://www.nuget.org/packages/CoinEx.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CoinEx.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>CoinGecko</td><td><a href="https://github.com/JKorf/CoinGecko.Net">JKorf/CoinGecko.Net</a></td><td><a href="https://www.nuget.org/packages/CoinGecko.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CoinGecko.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>CoinGecko</td><td><a href="https://github.com/JKorf/CoinGecko.Net">JKorf/CoinGecko.Net</a></td><td><a href="https://www.nuget.org/packages/CoinGecko.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CoinGecko.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Crypto.com</td><td><a href="https://github.com/JKorf/CryptoCom.Net">JKorf/CryptoCom.Net</a></td><td><a href="https://www.nuget.org/packages/CryptoCom.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Crypto.com</td><td><a href="https://github.com/JKorf/CryptoCom.Net">JKorf/CryptoCom.Net</a></td><td><a href="https://www.nuget.org/packages/CryptoCom.Net" target="_blank"><img src="https://img.shields.io/nuget/v/CryptoCom.net.svg?style=flat-square" /></a></td></tr>
|
||||||
|
<tr><td>DeepCoin</td><td><a href="https://github.com/JKorf/DeepCoin.Net">JKorf/DeepCoin.Net</a></td><td><a href="https://www.nuget.org/packages/DeepCoin.Net" target="_blank"><img src="https://img.shields.io/nuget/v/DeepCoin.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Gate.io</td><td><a href="https://github.com/JKorf/GateIo.Net">JKorf/GateIo.Net</a></td><td><a href="https://www.nuget.org/packages/GateIo.Net" target="_blank"><img src="https://img.shields.io/nuget/v/GateIo.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Gate.io</td><td><a href="https://github.com/JKorf/GateIo.Net">JKorf/GateIo.Net</a></td><td><a href="https://www.nuget.org/packages/GateIo.Net" target="_blank"><img src="https://img.shields.io/nuget/v/GateIo.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>HTX</td><td><a href="https://github.com/JKorf/HTX.Net">JKorf/HTX.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.HTX.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.HTX.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>HTX</td><td><a href="https://github.com/JKorf/HTX.Net">JKorf/HTX.Net</a></td><td><a href="https://www.nuget.org/packages/JKorf.HTX.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JKorf.HTX.net.svg?style=flat-square" /></a></td></tr>
|
||||||
|
<tr><td>HyperLiquid</td><td><a href="https://github.com/JKorf/HyperLiquid.Net">JKorf/HyperLiquid.Net</a></td><td><a href="https://www.nuget.org/packages/HyperLiquid.Net" target="_blank"><img src="https://img.shields.io/nuget/v/HyperLiquid.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Kraken</td><td><a href="https://github.com/JKorf/Kraken.Net">JKorf/Kraken.Net</a></td><td><a href="https://www.nuget.org/packages/KrakenExchange.Net" target="_blank"><img src="https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Kraken</td><td><a href="https://github.com/JKorf/Kraken.Net">JKorf/Kraken.Net</a></td><td><a href="https://www.nuget.org/packages/KrakenExchange.Net" target="_blank"><img src="https://img.shields.io/nuget/v/KrakenExchange.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Kucoin</td><td><a href="https://github.com/JKorf/Kucoin.Net">JKorf/Kucoin.Net</a></td><td><a href="https://www.nuget.org/packages/Kucoin.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Kucoin.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Kucoin</td><td><a href="https://github.com/JKorf/Kucoin.Net">JKorf/Kucoin.Net</a></td><td><a href="https://www.nuget.org/packages/Kucoin.Net" target="_blank"><img src="https://img.shields.io/nuget/v/Kucoin.net.svg?style=flat-square" /></a></td></tr>
|
||||||
<tr><td>Mexc</td><td><a href="https://github.com/JKorf/Mexc.Net">JKorf/Mexc.Net</a></td><td><a href="https://www.nuget.org/packages/JK.Mexc.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square" /></a></td></tr>
|
<tr><td>Mexc</td><td><a href="https://github.com/JKorf/Mexc.Net">JKorf/Mexc.Net</a></td><td><a href="https://www.nuget.org/packages/JK.Mexc.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square" /></a></td></tr>
|
||||||
@@ -197,13 +200,15 @@
|
|||||||
|
|
||||||
<b>Referral</b>
|
<b>Referral</b>
|
||||||
<p>When creating an account on new exchanges please consider using a referral link from below to support development</p>
|
<p>When creating an account on new exchanges please consider using a referral link from below to support development</p>
|
||||||
<table>
|
<table class="table table-bordered">
|
||||||
<tr><td>Exchange</td><td>Link</td></tr>
|
<tr><th>Exchange</th><th>Link</th></tr>
|
||||||
<tr><td>Bybit</td><td>https://partner.bybit.com/b/jkorf</td></tr>
|
<tr><td>Bybit</td><td>https://partner.bybit.com/b/jkorf</td></tr>
|
||||||
<tr><td>Coinbase</td><td>https://advanced.coinbase.com/join/T6H54H8</td></tr>
|
<tr><td>Coinbase</td><td>https://advanced.coinbase.com/join/T6H54H8</td></tr>
|
||||||
<tr><td>CoinEx</td><td>https://www.coinex.com/register?refer_code=hd6gn</td></tr>
|
<tr><td>CoinEx</td><td>https://www.coinex.com/register?refer_code=hd6gn</td></tr>
|
||||||
<tr><td>Crypto.com</td><td>https://crypto.com/exch/26ge92xbkn</td></tr>
|
<tr><td>Crypto.com</td><td>https://crypto.com/exch/26ge92xbkn</td></tr>
|
||||||
|
<tr><td>DeepCoin</td><td>https://s.deepcoin.com/jddhfca</td></tr>
|
||||||
<tr><td>HTX</td><td>https://www.htx.com/invite/en-us/1f?invite_code=fxp9</td></tr>
|
<tr><td>HTX</td><td>https://www.htx.com/invite/en-us/1f?invite_code=fxp9</td></tr>
|
||||||
|
<tr><td>HyperLiquid</td><td>https://app.hyperliquid.xyz/join/JKORF</td></tr>
|
||||||
<tr><td>Kucoin</td><td>https://www.kucoin.com/r/rf/QBS4FPED</td></tr>
|
<tr><td>Kucoin</td><td>https://www.kucoin.com/r/rf/QBS4FPED</td></tr>
|
||||||
<tr><td>OKX</td><td>https://okx.com/join/48046699</td></tr>
|
<tr><td>OKX</td><td>https://okx.com/join/48046699</td></tr>
|
||||||
<tr><td>WhiteBit</td><td>https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf</td></tr>
|
<tr><td>WhiteBit</td><td>https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf</td></tr>
|
||||||
@@ -280,6 +285,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="install-htx-tab" data-toggle="tab" href="#install-htx" role="tab" aria-controls="install-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="install-htx-tab" data-toggle="tab" href="#install-htx" role="tab" aria-controls="install-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="install-hyperliquid-tab" data-toggle="tab" href="#install-hyperliquid" role="tab" aria-controls="install-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="install-kraken-tab" data-toggle="tab" href="#install-kraken" role="tab" aria-controls="install-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="install-kraken-tab" data-toggle="tab" href="#install-kraken" role="tab" aria-controls="install-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -345,6 +353,9 @@
|
|||||||
<div class="tab-pane fade" id="install-htx" role="tabpanel" aria-labelledby="install-htx-tab">
|
<div class="tab-pane fade" id="install-htx" role="tabpanel" aria-labelledby="install-htx-tab">
|
||||||
<pre><code>dotnet add package JKorf.HTX.Net</code></pre>
|
<pre><code>dotnet add package JKorf.HTX.Net</code></pre>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade" id="install-hyperliquid" role="tabpanel" aria-labelledby="install-hyperliquid-tab">
|
||||||
|
<pre><code>dotnet add package HyperLiquid.Net</code></pre>
|
||||||
|
</div>
|
||||||
<div class="tab-pane fade" id="install-kraken" role="tabpanel" aria-labelledby="install-kraken-tab">
|
<div class="tab-pane fade" id="install-kraken" role="tabpanel" aria-labelledby="install-kraken-tab">
|
||||||
<pre><code>dotnet add package KrakenExchange.Net</code></pre>
|
<pre><code>dotnet add package KrakenExchange.Net</code></pre>
|
||||||
<img src="assets/images/KrakenInstall.png" />
|
<img src="assets/images/KrakenInstall.png" />
|
||||||
@@ -420,6 +431,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="di-htx-tab" data-toggle="tab" href="#di-htx" role="tab" aria-controls="di-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="di-htx-tab" data-toggle="tab" href="#di-htx" role="tab" aria-controls="di-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="di-hyperliquid-tab" data-toggle="tab" href="#di-hyperliquid" role="tab" aria-controls="di-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="di-kraken-tab" data-toggle="tab" href="#di-kraken" role="tab" aria-controls="di-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="di-kraken-tab" data-toggle="tab" href="#di-kraken" role="tab" aria-controls="di-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -478,6 +492,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="di-htx" role="tabpanel" aria-labelledby="di-htx-tab">
|
<div class="tab-pane fade" id="di-htx" role="tabpanel" aria-labelledby="di-htx-tab">
|
||||||
<pre><code>builder.Services.AddHTX();</code></pre>
|
<pre><code>builder.Services.AddHTX();</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="di-hyperliquid" role="tabpanel" aria-labelledby="di-hyperliquid-tab">
|
||||||
|
<pre><code>builder.Services.AddHyperLiquid();</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="di-kraken" role="tabpanel" aria-labelledby="di-kraken-tab">
|
<div class="tab-pane fade" id="di-kraken" role="tabpanel" aria-labelledby="di-kraken-tab">
|
||||||
<pre><code>builder.Services.AddKraken();</code></pre>
|
<pre><code>builder.Services.AddKraken();</code></pre>
|
||||||
@@ -542,6 +559,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="interfaces-htx-tab" data-toggle="tab" href="#interfaces-htx" role="tab" aria-controls="interfaces-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="interfaces-htx-tab" data-toggle="tab" href="#interfaces-htx" role="tab" aria-controls="interfaces-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="interfaces-hyperliquid-tab" data-toggle="tab" href="#interfaces-hyperliquid" role="tab" aria-controls="interfaces-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="interfaces-kraken-tab" data-toggle="tab" href="#interfaces-kraken" role="tab" aria-controls="interfaces-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="interfaces-kraken-tab" data-toggle="tab" href="#interfaces-kraken" role="tab" aria-controls="interfaces-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -975,6 +995,39 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade" id="interfaces-hyperliquid" role="tabpanel" aria-labelledby="interfaces-hyperliquid-tab">
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr><th>Interface</th><th>Description</th></tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>IHyperLiquidRestClient</code></td>
|
||||||
|
<td>The client for accessing the HyperLiquid REST API</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>IHyperLiquidSocketClient</code></td>
|
||||||
|
<td>The client for accessing the HyperLiquid Websocket API</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>IHyperLiquidOrderBookFactory</code></td>
|
||||||
|
<td>A factory for creating SymbolOrderBook instances for the HyperLiquid API</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>IHyperLiquidTrackerFactory</code></td>
|
||||||
|
<td>A factory for creating kline and trade Tracker instances for the HyperLiquid API</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>ICryptoRestClient</code></td>
|
||||||
|
<td>An aggregating client from which multiple different library REST clients can be accessed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>ICryptoSocketClient</code></td>
|
||||||
|
<td>An aggregating client from which multiple different library Websocket clients can be accessed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>ISharedClient</code></td>
|
||||||
|
<td>Various interfaces deriving from ISharedClient which can be used for common functionality</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
<div class="tab-pane fade" id="interfaces-kraken" role="tabpanel" aria-labelledby="interfaces-kraken-tab">
|
<div class="tab-pane fade" id="interfaces-kraken" role="tabpanel" aria-labelledby="interfaces-kraken-tab">
|
||||||
<table class="table table-bordered">
|
<table class="table table-bordered">
|
||||||
<tr><th>Interface</th><th>Description</th></tr>
|
<tr><th>Interface</th><th>Description</th></tr>
|
||||||
@@ -1237,6 +1290,9 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="rest-htx-tab" data-toggle="tab" href="#rest-htx" role="tab" aria-controls="rest-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="rest-htx-tab" data-toggle="tab" href="#rest-htx" role="tab" aria-controls="rest-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="rest-hyperliquid-tab" data-toggle="tab" href="#rest-hyperliquid" role="tab" aria-controls="rest-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="rest-kraken-tab" data-toggle="tab" href="#rest-kraken" role="tab" aria-controls="rest-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="rest-kraken-tab" data-toggle="tab" href="#rest-kraken" role="tab" aria-controls="rest-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -1409,6 +1465,18 @@ if (!tickersResult.Success)
|
|||||||
// Handle error, tickersResult.Error contains more information
|
// Handle error, tickersResult.Error contains more information
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
// Handle data, tickersResult.Data will contain the actual data
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="rest-hyperliquid" role="tabpanel" aria-labelledby="rest-hyperliquid-tab">
|
||||||
|
<pre><code>var client = new HyperLiquidRestClient();
|
||||||
|
var tickersResult = await client.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
|
||||||
|
if (!tickersResult.Success)
|
||||||
|
{
|
||||||
|
// Handle error, tickersResult.Error contains more information
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
// Handle data, tickersResult.Data will contain the actual data
|
// Handle data, tickersResult.Data will contain the actual data
|
||||||
}</code></pre>
|
}</code></pre>
|
||||||
@@ -1595,6 +1663,9 @@ else
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="socket-htx-tab" data-toggle="tab" href="#socket-htx" role="tab" aria-controls="socket-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="socket-htx-tab" data-toggle="tab" href="#socket-htx" role="tab" aria-controls="socket-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="socket-hyperliquid-tab" data-toggle="tab" href="#socket-hyperliquid" role="tab" aria-controls="socket-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="socket-kraken-tab" data-toggle="tab" href="#socket-kraken" role="tab" aria-controls="socket-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="socket-kraken-tab" data-toggle="tab" href="#socket-kraken" role="tab" aria-controls="socket-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -1745,6 +1816,17 @@ if (!subscribeResult.Success)
|
|||||||
{
|
{
|
||||||
// Handle error, subscribeResult.Error contains more information on why the subscription failed
|
// Handle error, subscribeResult.Error contains more information on why the subscription failed
|
||||||
}
|
}
|
||||||
|
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="socket-hyperliquid" role="tabpanel" aria-labelledby="socket-hyperliquid-tab">
|
||||||
|
<pre><code>var client = new HyperLiquidSocketClient();
|
||||||
|
var subscribeResult = await client.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", update => {
|
||||||
|
// Handle the data update, update.Data will contain the actual data
|
||||||
|
});
|
||||||
|
if (!subscribeResult.Success)
|
||||||
|
{
|
||||||
|
// Handle error, subscribeResult.Error contains more information on why the subscription failed
|
||||||
|
}
|
||||||
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
|
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="socket-kraken" role="tabpanel" aria-labelledby="socket-kraken-tab">
|
<div class="tab-pane fade" id="socket-kraken" role="tabpanel" aria-labelledby="socket-kraken-tab">
|
||||||
@@ -1999,6 +2081,9 @@ var binanceTriggered = CheckForTrigger(lastBinanceTicker);</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="shared-htx-tab" data-toggle="tab" href="#shared-htx" role="tab" aria-controls="shared-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="shared-htx-tab" data-toggle="tab" href="#shared-htx" role="tab" aria-controls="shared-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="shared-hyperliquid-tab" data-toggle="tab" href="#shared-hyperliquid" role="tab" aria-controls="shared-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="shared-kraken-tab" data-toggle="tab" href="#shared-kraken" role="tab" aria-controls="shared-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="shared-kraken-tab" data-toggle="tab" href="#shared-kraken" role="tab" aria-controls="shared-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -2152,6 +2237,19 @@ var usdFuturesSharedRestClient = htxRestClient.UsdtFuturesApi.SharedClient;
|
|||||||
|
|
||||||
// USDT Futures API common functionality socket client
|
// USDT Futures API common functionality socket client
|
||||||
var usdFuturesSharedSocketClient = htxSocketClient.UsdtFuturesApi.SharedClient;</code></pre>
|
var usdFuturesSharedSocketClient = htxSocketClient.UsdtFuturesApi.SharedClient;</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="shared-hyperliquid" role="tabpanel" aria-labelledby="shared-hyperliquid-tab">
|
||||||
|
<pre><code>// Spot API common functionality rest client
|
||||||
|
var spotSharedRestClients = hyperliquidRestClient.SpotApi.SharedClient;
|
||||||
|
|
||||||
|
// Spot API common functionality socket client
|
||||||
|
var spotSharedSocketClient = hyperliquidSocketClient.SpotApi.SharedClient;
|
||||||
|
|
||||||
|
// Perpetual Futures API common functionality rest client
|
||||||
|
var futuresSharedRestClient = hyperliquidRestClient.FuturesApi.SharedClient;
|
||||||
|
|
||||||
|
// Perpetual Futures API common functionality socket client
|
||||||
|
var futuresSharedSocketClient = hyperliquidSocketClient.FuturesApi.SharedClient;</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="shared-kraken" role="tabpanel" aria-labelledby="shared-kraken-tab">
|
<div class="tab-pane fade" id="shared-kraken" role="tabpanel" aria-labelledby="shared-kraken-tab">
|
||||||
<pre><code>// Spot API common functionality rest client
|
<pre><code>// Spot API common functionality rest client
|
||||||
@@ -2485,6 +2583,9 @@ options.ApiCredentials = new ApiCredentials("YOUR PUBLIC KEY", "YOUR PRIVATE KEY
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-htx" role="tab" aria-controls="options-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-htx" role="tab" aria-controls="options-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-hyperliquid" role="tab" aria-controls="options-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-kraken" role="tab" aria-controls="options-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-kraken" role="tab" aria-controls="options-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -2656,6 +2757,18 @@ builder.Services.AddGateIo(builder.Configuration.GetSection("GateIo"));</code></
|
|||||||
|
|
||||||
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
|
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
|
||||||
builder.Services.AddHTX(builder.Configuration.GetSection("HTX"));</code></pre>
|
builder.Services.AddHTX(builder.Configuration.GetSection("HTX"));</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="options-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
|
||||||
|
<pre><code>builder.Services.AddHyperLiquid(
|
||||||
|
options => {
|
||||||
|
options.Rest.RequestTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
options.Socket.RequestTimeout = TimeSpan.FromSeconds(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
// OR
|
||||||
|
|
||||||
|
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
|
||||||
|
builder.Services.AddHyperLiquid(builder.Configuration.GetSection("HyperLiquid"));</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="options-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
|
<div class="tab-pane fade" id="options-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
|
||||||
<pre><code>builder.Services.AddKraken(
|
<pre><code>builder.Services.AddKraken(
|
||||||
@@ -2775,6 +2888,9 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-constr-htx" role="tab" aria-controls="options-constr-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-constr-htx" role="tab" aria-controls="options-constr-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-constr-hyperliquid" role="tab" aria-controls="options-constr-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-constr-kraken" role="tab" aria-controls="options-constr-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-constr-kraken" role="tab" aria-controls="options-constr-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -2869,6 +2985,12 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="options-constr-htx" role="tabpanel" aria-labelledby="options-htx-tab">
|
<div class="tab-pane fade" id="options-constr-htx" role="tabpanel" aria-labelledby="options-htx-tab">
|
||||||
<pre><code>var client = new HTXRestClient(opts =>
|
<pre><code>var client = new HTXRestClient(opts =>
|
||||||
|
{
|
||||||
|
opts.RequestTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
});</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="options-constr-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
|
||||||
|
<pre><code>var client = new HyperLiquidRestClient(opts =>
|
||||||
{
|
{
|
||||||
opts.RequestTimeout = TimeSpan.FromSeconds(30);
|
opts.RequestTimeout = TimeSpan.FromSeconds(30);
|
||||||
});</code></pre>
|
});</code></pre>
|
||||||
@@ -2952,6 +3074,9 @@ builder.Services.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-default-htx" role="tab" aria-controls="options-default-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="options-htx-tab" data-toggle="tab" href="#options-default-htx" role="tab" aria-controls="options-default-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="options-hyperliquid-tab" data-toggle="tab" href="#options-default-hyperliquid" role="tab" aria-controls="options-default-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-default-kraken" role="tab" aria-controls="options-default-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="options-kraken-tab" data-toggle="tab" href="#options-default-kraken" role="tab" aria-controls="options-default-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -3055,6 +3180,13 @@ var client = new GateIoRestClient();</code></pre>
|
|||||||
options.RequestTimeout = TimeSpan.FromSeconds(30);
|
options.RequestTimeout = TimeSpan.FromSeconds(30);
|
||||||
});
|
});
|
||||||
var client = new HTXRestClient();</code></pre>
|
var client = new HTXRestClient();</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="options-default-hyperliquid" role="tabpanel" aria-labelledby="options-hyperliquid-tab">
|
||||||
|
<pre><code>HyperLiquidRestClient.SetDefaultOptions(options =>
|
||||||
|
{
|
||||||
|
options.RequestTimeout = TimeSpan.FromSeconds(30);
|
||||||
|
});
|
||||||
|
var client = new HyperLiquidRestClient();</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="options-default-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
|
<div class="tab-pane fade" id="options-default-kraken" role="tabpanel" aria-labelledby="options-kraken-tab">
|
||||||
<pre><code>KrakenRestClient.SetDefaultOptions(options =>
|
<pre><code>KrakenRestClient.SetDefaultOptions(options =>
|
||||||
@@ -3317,6 +3449,9 @@ var client = new XTRestClient();</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="book-htx-tab" data-toggle="tab" href="#book-htx" role="tab" aria-controls="book-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="book-htx-tab" data-toggle="tab" href="#book-htx" role="tab" aria-controls="book-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="book-hyperliquid-tab" data-toggle="tab" href="#book-hyperliquid" role="tab" aria-controls="book-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="book-kraken-tab" data-toggle="tab" href="#book-kraken" role="tab" aria-controls="book-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="book-kraken-tab" data-toggle="tab" href="#book-kraken" role="tab" aria-controls="book-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -3490,6 +3625,19 @@ if (!startResult.Success)
|
|||||||
}
|
}
|
||||||
// Book has successfully started and synchronized
|
// Book has successfully started and synchronized
|
||||||
|
|
||||||
|
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
|
||||||
|
await book.StopAsync();
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="book-hyperliquid" role="tabpanel" aria-labelledby="book-hyperliquid-tab">
|
||||||
|
<pre><code>var book = new HyperLiquidSymbolOrderBook("HYPE/USDC");
|
||||||
|
var startResult = await book.StartAsync();
|
||||||
|
if (!startResult.Success)
|
||||||
|
{
|
||||||
|
// Handle error, error info available in startResult.Error
|
||||||
|
}
|
||||||
|
// Book has successfully started and synchronized
|
||||||
|
|
||||||
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
|
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
|
||||||
await book.StopAsync();
|
await book.StopAsync();
|
||||||
</code></pre>
|
</code></pre>
|
||||||
@@ -3726,6 +3874,9 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
|
|||||||
<li class="nav-item" role="presentation">
|
<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>
|
<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>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="tracker-hyperliquid-tab" data-toggle="tab" href="#tracker-hyperliquid" role="tab" aria-controls="tracker-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<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>
|
<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>
|
||||||
@@ -3983,6 +4134,26 @@ if (!startResult.Success)
|
|||||||
// Tracker has successfully started
|
// Tracker has successfully started
|
||||||
// Note that it might not be fully synced yet, check tracker.Status for this.
|
// 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-hyperliquid" role="tabpanel" aria-labelledby="tracker-hyperliquid-tab">
|
||||||
|
<pre><code>// Either create a new factory or inject the IHyperLiquidTrackerFactory interface
|
||||||
|
var factory = new HyperLiquidTrackerFactory();
|
||||||
|
|
||||||
|
var symbol = new SharedSymbol(TradingMode.Spot, "HYPE", "USDC");
|
||||||
|
|
||||||
|
// Create a tracker for HYPE/USDC 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()
|
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
|
||||||
await tracker.StopAsync();
|
await tracker.StopAsync();
|
||||||
</code></pre>
|
</code></pre>
|
||||||
@@ -4441,6 +4612,9 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="limit-htx-tab" data-toggle="tab" href="#limit-htx" role="tab" aria-controls="limit-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="limit-htx-tab" data-toggle="tab" href="#limit-htx" role="tab" aria-controls="limit-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="limit-hyperliquid-tab" data-toggle="tab" href="#limit-hyperliquid" role="tab" aria-controls="limit-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="limit-kraken-tab" data-toggle="tab" href="#limit-kraken" role="tab" aria-controls="limit-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="limit-kraken-tab" data-toggle="tab" href="#limit-kraken" role="tab" aria-controls="limit-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -4583,6 +4757,20 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
|
|||||||
<p>To be notified of when a rate limit is hit the static <code>HTXExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
|
<p>To be notified of when a rate limit is hit the static <code>HTXExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
|
||||||
<pre><code>HTXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
|
<pre><code>HTXExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
|
||||||
|
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="limit-hyperliquid" role="tabpanel" aria-labelledby="limit-hyperliquid-tab">
|
||||||
|
<pre><code>services.AddHyperLiquid(x =>
|
||||||
|
x.RatelimiterEnabled = true;
|
||||||
|
x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
|
||||||
|
}, x =>
|
||||||
|
{
|
||||||
|
x.RatelimiterEnabled = true;
|
||||||
|
x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
|
||||||
|
});</code></pre>
|
||||||
|
<p>To be notified of when a rate limit is hit the static <code>HyperLiquidExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
|
||||||
|
<pre><code>HyperLiquidExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
|
||||||
|
|
||||||
</code></pre>
|
</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="limit-kraken" role="tabpanel" aria-labelledby="limit-kraken-tab">
|
<div class="tab-pane fade" id="limit-kraken" role="tabpanel" aria-labelledby="limit-kraken-tab">
|
||||||
@@ -4760,6 +4948,9 @@ var responseSource = result.DataSource;</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-symbols-htx-tab" data-toggle="tab" href="#example-symbols-htx" role="tab" aria-controls="example-symbols-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-symbols-htx-tab" data-toggle="tab" href="#example-symbols-htx" role="tab" aria-controls="example-symbols-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-symbols-hyperliquid-tab" data-toggle="tab" href="#example-symbols-hyperliquid" role="tab" aria-controls="example-symbols-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-symbols-kraken-tab" data-toggle="tab" href="#example-symbols-kraken" role="tab" aria-controls="example-symbols-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-symbols-kraken-tab" data-toggle="tab" href="#example-symbols-kraken" role="tab" aria-controls="example-symbols-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -4823,6 +5014,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-symbols-htx" role="tabpanel" aria-labelledby="example-symbols-htx-tab">
|
<div class="tab-pane fade" id="example-symbols-htx" role="tabpanel" aria-labelledby="example-symbols-htx-tab">
|
||||||
<pre><code>await htxClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
|
<pre><code>await htxClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-symbols-hyperliquid" role="tabpanel" aria-labelledby="example-symbols-hyperliquid-tab">
|
||||||
|
<pre><code>await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAsync();</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-symbols-kraken" role="tabpanel" aria-labelledby="example-symbols-kraken-tab">
|
<div class="tab-pane fade" id="example-symbols-kraken" role="tabpanel" aria-labelledby="example-symbols-kraken-tab">
|
||||||
<pre><code>await krakenClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
|
<pre><code>await krakenClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
|
||||||
@@ -4896,6 +5090,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-ticker-htx-tab" data-toggle="tab" href="#example-ticker-htx" role="tab" aria-controls="example-ticker-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-ticker-htx-tab" data-toggle="tab" href="#example-ticker-htx" role="tab" aria-controls="example-ticker-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-ticker-hyperliquid-tab" data-toggle="tab" href="#example-ticker-hyperliquid" role="tab" aria-controls="example-ticker-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-ticker-kraken-tab" data-toggle="tab" href="#example-ticker-kraken" role="tab" aria-controls="example-ticker-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-ticker-kraken-tab" data-toggle="tab" href="#example-ticker-kraken" role="tab" aria-controls="example-ticker-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -4961,6 +5158,11 @@ await coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");</
|
|||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-ticker-htx" role="tabpanel" aria-labelledby="example-ticker-htx-tab">
|
<div class="tab-pane fade" id="example-ticker-htx" role="tabpanel" aria-labelledby="example-ticker-htx-tab">
|
||||||
<pre><code>await htxClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
|
<pre><code>await htxClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-ticker-hyperliquid" role="tabpanel" aria-labelledby="example-ticker-hyperliquid-tab">
|
||||||
|
<pre><code>// HyperLiquid API doesn't offer a symbol filter, so we have to filter client side
|
||||||
|
var tickersResult = await hyperLiquidClient.SpotApi.ExchangeData.GetExchangeInfoAndTickersAsync();
|
||||||
|
var ticker = tickersResult.Data.Tickers.Single(x => x.Symbol == "HYPE/USDC");</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-ticker-kraken" role="tabpanel" aria-labelledby="example-ticker-kraken-tab">
|
<div class="tab-pane fade" id="example-ticker-kraken" role="tabpanel" aria-labelledby="example-ticker-kraken-tab">
|
||||||
<pre><code>await krakenClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
|
<pre><code>await krakenClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");</code></pre>
|
||||||
@@ -5036,6 +5238,9 @@ var ticker = tickersResult.Data.Single(x => x.Symbol == "BTC_USDT");</code></pre
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-balances-htx-tab" data-toggle="tab" href="#example-balances-htx" role="tab" aria-controls="example-balances-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-balances-htx-tab" data-toggle="tab" href="#example-balances-htx" role="tab" aria-controls="example-balances-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-balances-hyperliquid-tab" data-toggle="tab" href="#example-balances-hyperliquid" role="tab" aria-controls="example-balances-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-balances-kraken-tab" data-toggle="tab" href="#example-balances-kraken" role="tab" aria-controls="example-balances-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-balances-kraken-tab" data-toggle="tab" href="#example-balances-kraken" role="tab" aria-controls="example-balances-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -5103,6 +5308,9 @@ var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
|
|||||||
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
|
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
|
||||||
|
|
||||||
var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-balances-hyperliquid" role="tabpanel" aria-labelledby="example-balances-hyperliquid-tab">
|
||||||
|
<pre><code>await hyperLiquidClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-balances-kraken" role="tabpanel" aria-labelledby="example-balances-kraken-tab">
|
<div class="tab-pane fade" id="example-balances-kraken" role="tabpanel" aria-labelledby="example-balances-kraken-tab">
|
||||||
<pre><code>await krakenClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
<pre><code>await krakenClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
||||||
@@ -5176,6 +5384,9 @@ var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-place-htx-tab" data-toggle="tab" href="#example-place-htx" role="tab" aria-controls="example-place-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-place-htx-tab" data-toggle="tab" href="#example-place-htx" role="tab" aria-controls="example-place-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-place-hyperliquid-tab" data-toggle="tab" href="#example-place-hyperliquid" role="tab" aria-controls="example-place-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-place-kraken-tab" data-toggle="tab" href="#example-place-kraken" role="tab" aria-controls="example-place-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-place-kraken-tab" data-toggle="tab" href="#example-place-kraken" role="tab" aria-controls="example-place-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -5241,6 +5452,10 @@ var accounts = await htxClient.SpotApi.Account.GetAccountsAsync();
|
|||||||
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
|
var account = accounts.Data.Single(a => a.Type == AccountType.Spot);
|
||||||
|
|
||||||
var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);</code></pre>
|
var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSDT", OrderSide.Buy, OrderType.Limit, 0.1m, price: 50000);</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-place-hyperliquid" role="tabpanel" aria-labelledby="example-place-hyperliquid-tab">
|
||||||
|
<pre><code>// BTC not support on HyperLiquid Spot trading, example uses HYPE/USDC Pair
|
||||||
|
await hyperLiquidClient.SpotApi.Trading.PlaceOrderAsync("HYPE/USDC",OrderSide.Buy, OrderType.Limit, 1m, 20);</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-place-kraken" role="tabpanel" aria-labelledby="example-place-kraken-tab">
|
<div class="tab-pane fade" id="example-place-kraken" role="tabpanel" aria-labelledby="example-place-kraken-tab">
|
||||||
<pre><code>await krakenClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT",OrderSide.Buy, OrderType.Limit, 0.1m, 50000);</code></pre>
|
<pre><code>await krakenClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT",OrderSide.Buy, OrderType.Limit, 0.1m, 50000);</code></pre>
|
||||||
@@ -5314,6 +5529,9 @@ var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSD
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-stream-ticker-htx-tab" data-toggle="tab" href="#example-stream-ticker-htx" role="tab" aria-controls="example-stream-ticker-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-stream-ticker-htx-tab" data-toggle="tab" href="#example-stream-ticker-htx" role="tab" aria-controls="example-stream-ticker-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-stream-ticker-hyperliquid-tab" data-toggle="tab" href="#example-stream-ticker-hyperliquid" role="tab" aria-controls="example-stream-ticker-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-stream-ticker-kraken-tab" data-toggle="tab" href="#example-stream-ticker-kraken" role="tab" aria-controls="example-stream-ticker-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-stream-ticker-kraken-tab" data-toggle="tab" href="#example-stream-ticker-kraken" role="tab" aria-controls="example-stream-ticker-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -5400,6 +5618,11 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
|
|||||||
<div class="tab-pane fade" id="example-stream-ticker-gateio" role="tabpanel" aria-labelledby="example-stream-ticker-gateio-tab">
|
<div class="tab-pane fade" id="example-stream-ticker-gateio" role="tabpanel" aria-labelledby="example-stream-ticker-gateio-tab">
|
||||||
<pre><code>await gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
|
<pre><code>await gateioSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
|
||||||
// Handle update
|
// Handle update
|
||||||
|
});</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-stream-ticker-hyperliquid" role="tabpanel" aria-labelledby="example-stream-ticker-hyperliquid-tab">
|
||||||
|
<pre><code>await hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("HYPE/USDC", data => {
|
||||||
|
// Handle update
|
||||||
});</code></pre>
|
});</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-stream-ticker-htx" role="tabpanel" aria-labelledby="example-stream-ticker-htx-tab">
|
<div class="tab-pane fade" id="example-stream-ticker-htx" role="tabpanel" aria-labelledby="example-stream-ticker-htx-tab">
|
||||||
@@ -5494,6 +5717,9 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-stream-order-htx-tab" data-toggle="tab" href="#example-stream-order-htx" role="tab" aria-controls="example-stream-order-htx" aria-selected="false">HTX</a>
|
<a class="nav-link" id="example-stream-order-htx-tab" data-toggle="tab" href="#example-stream-order-htx" role="tab" aria-controls="example-stream-order-htx" aria-selected="false">HTX</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<a class="nav-link" id="example-stream-order-hyperliquid-tab" data-toggle="tab" href="#example-stream-order-hyperliquid" role="tab" aria-controls="example-stream-order-hyperliquid" aria-selected="false">HyperLiquid</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<a class="nav-link" id="example-stream-order-kraken-tab" data-toggle="tab" href="#example-stream-order-kraken" role="tab" aria-controls="example-stream-order-kraken" aria-selected="false">Kraken</a>
|
<a class="nav-link" id="example-stream-order-kraken-tab" data-toggle="tab" href="#example-stream-order-kraken" role="tab" aria-controls="example-stream-order-kraken" aria-selected="false">Kraken</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -5621,6 +5847,11 @@ _ = Task.Run(async () => {
|
|||||||
<div class="tab-pane fade" id="example-stream-order-htx" role="tabpanel" aria-labelledby="example-stream-order-htx-tab">
|
<div class="tab-pane fade" id="example-stream-order-htx" role="tabpanel" aria-labelledby="example-stream-order-htx-tab">
|
||||||
<pre><code>await htxSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(onOrderMatched: data => {
|
<pre><code>await htxSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(onOrderMatched: data => {
|
||||||
// Handle update
|
// Handle update
|
||||||
|
});</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="example-stream-order-hyperliquid" role="tabpanel" aria-labelledby="example-stream-order-hyperliquid-tab">
|
||||||
|
<pre><code>await hyperLiquidSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(null, data => {
|
||||||
|
// Handle update
|
||||||
});</code></pre>
|
});</code></pre>
|
||||||
</div>
|
</div>
|
||||||
<div class="tab-pane fade" id="example-stream-order-kraken" role="tabpanel" aria-labelledby="example-stream-order-kraken-tab">
|
<div class="tab-pane fade" id="example-stream-order-kraken" role="tabpanel" aria-labelledby="example-stream-order-kraken-tab">
|
||||||
|
|||||||
Reference in New Issue
Block a user