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

Compare commits

..

17 Commits

Author SHA1 Message Date
Jkorf 73c42bd452 Updated to version 8.5.0 2024-12-23 14:25:39 +01:00
Jkorf 290be7f5e0 Added net9.0 build target, added KeepAliveTimeout for websocket connections 2024-12-23 14:14:47 +01:00
Jan Korf 0be1bb16e3 Feature/update settings (#225)
Added SetOptions method to update client settings
Added SocketConnection parameter to PeriodicQuery callback
Added setting of DefaultProxyCredentials on HttpClient instance when client is not provided by DI
Added support for overriding request time out per request
Changed max wait time for close handshake response from 5 seconds to 1 second
Fixed exception in trade tracker when there is no data in the initial snapshot
2024-12-23 08:49:58 +01:00
Jkorf 8605196390 Updated to version 8.4.5 2024-12-20 15:25:41 +01:00
Jkorf 460dd97537 Added EmptyArrayObjectConverter, added JsonSerializerOptions parameter to SystemTextJsonMessageAccessor ctor 2024-12-20 15:21:21 +01:00
JKorf 1ec5984fad Updated to version 8.4.4 2024-12-08 10:16:19 +01:00
JKorf 8260c2661d Changed JsonConverterCtorAttribute to use type parameter instead of generic parameter to support .net framework 2024-12-08 10:12:39 +01:00
Jkorf 591c1dd405 docs 2024-12-04 10:44:35 +01:00
Jkorf 0164cdfcc4 docs 2024-12-04 09:15:23 +01:00
Jkorf 23a6cfff87 docs 2024-12-04 09:07:59 +01:00
Jkorf fdcdb90a5f Added XT reference 2024-12-04 08:58:46 +01:00
Jkorf 0b7107401f Updated to version 8.4.3 2024-12-03 09:59:36 +01:00
JKorf 06add65354 Fixed KlineTracker update handling 2024-12-02 21:46:30 +01:00
Jkorf 773d288497 Updated to version 8.4.2 2024-12-02 14:39:27 +01:00
Jkorf fd4e8da938 Removed special characters in ClientOrderIdSeperator to adhere to field content rules 2024-12-02 14:38:22 +01:00
Jkorf 271743b669 Updated to version 8.4.1 2024-12-02 13:15:31 +01:00
Jkorf f4797caf37 Added replace converter, added library helpers class 2024-12-02 13:13:56 +01:00
52 changed files with 768 additions and 117 deletions
@@ -1,6 +1,7 @@
using CryptoExchange.Net.Objects;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using System.Diagnostics;
using System.Globalization;
namespace CryptoExchange.Net.UnitTests
@@ -70,5 +71,20 @@ namespace CryptoExchange.Net.UnitTests
var result = ExchangeHelpers.Normalize(input);
Assert.That(expected == result.ToString(CultureInfo.InvariantCulture));
}
[Test]
[TestCase("123", "BKR", 32, true, "BKRJK123")]
[TestCase("123", "BKR", 32, false, "123")]
[TestCase("123123123123123123123123123123", "BKR", 32, true, "123123123123123123123123123123")] // 30
[TestCase("12312312312312312312312312312", "BKR", 32, true, "12312312312312312312312312312")] // 27
[TestCase("123123123123123123123123123", "BKR", 32, true, "BKRJK123123123123123123123123123")] // 25
[TestCase(null, "BKR", 32, true, null)]
public void ApplyBrokerIdTests(string clientOrderId, string brokerId, int maxLength, bool allowValueAdjustement, string expected)
{
var result = LibraryHelpers.ApplyBrokerId(clientOrderId, brokerId, maxLength, allowValueAdjustement);
if (expected != null)
Assert.That(result, Is.EqualTo(expected));
}
}
}
@@ -1,4 +1,4 @@
#if !NETSTANDARD2_1
#if NETSTANDARD2_0
namespace System.Diagnostics.CodeAnalysis
{
using System;
@@ -369,7 +369,7 @@ namespace CryptoExchange.Net.Authentication
var rsa = RSA.Create();
if (_credentials.CredentialType == ApiCredentialsType.RsaPem)
{
#if NETSTANDARD2_1_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
// Read from pem private key
var key = _credentials.Secret!
.Replace("\n", "")
+1 -1
View File
@@ -26,7 +26,7 @@ namespace CryptoExchange.Net.Caching
/// <returns>Cached value if it was in cache</returns>
public object? Get(string key, TimeSpan maxAge)
{
_cache.TryGetValue(key, out CacheItem value);
_cache.TryGetValue(key, out CacheItem? value);
if (value == null)
return null;
@@ -93,6 +93,17 @@ namespace CryptoExchange.Net.Clients
AuthenticationProvider = CreateAuthenticationProvider(credentials.Copy());
}
/// <inheritdoc />
public virtual void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials
{
ClientOptions.Proxy = options.Proxy;
ClientOptions.RequestTimeout = options.RequestTimeout ?? ClientOptions.RequestTimeout;
ApiOptions.ApiCredentials = options.ApiCredentials ?? ClientOptions.ApiCredentials;
if (options.ApiCredentials != null)
AuthenticationProvider = CreateAuthenticationProvider(options.ApiCredentials.Copy());
}
/// <summary>
/// Dispose
/// </summary>
+2 -2
View File
@@ -15,7 +15,7 @@ namespace CryptoExchange.Net.Clients
/// <summary>
/// Version of the CryptoExchange.Net base library
/// </summary>
public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version;
public Version CryptoExchangeLibVersion { get; } = typeof(BaseClient).Assembly.GetName().Version!;
/// <summary>
/// Version of the client implementation
@@ -27,7 +27,7 @@ namespace CryptoExchange.Net.Clients
lock(_versionLock)
{
if (_exchangeVersion == null)
_exchangeVersion = GetType().Assembly.GetName().Version;
_exchangeVersion = GetType().Assembly.GetName().Version!;
return _exchangeVersion;
}
+9 -1
View File
@@ -807,7 +807,7 @@ namespace CryptoExchange.Net.Clients
if (parameterPosition == HttpMethodParameterPosition.InUri)
{
foreach (var parameter in parameters)
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString());
uri = uri.AddQueryParmeter(parameter.Key, parameter.Value.ToString()!);
}
var headers = new Dictionary<string, string>();
@@ -961,6 +961,14 @@ namespace CryptoExchange.Net.Clients
/// <returns>Server time</returns>
protected virtual Task<WebCallResult<DateTime>> GetServerTimestampAsync() => throw new NotImplementedException();
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
base.SetOptions(options);
RequestFactory.UpdateSettings(options.Proxy, options.RequestTimeout ?? ClientOptions.RequestTimeout);
}
internal async Task<WebCallResult<bool>> SyncTimeAsync()
{
var timeSyncParams = GetTimeSyncInfo();
+22 -2
View File
@@ -158,7 +158,7 @@ namespace CryptoExchange.Net.Clients
/// <param name="interval"></param>
/// <param name="queryDelegate"></param>
/// <param name="callback"></param>
protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<CallResult>? callback)
protected virtual void RegisterPeriodicQuery(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<SocketConnection, CallResult>? callback)
{
PeriodicTaskRegistrations.Add(new PeriodicTaskRegistration
{
@@ -422,9 +422,10 @@ namespace CryptoExchange.Net.Clients
result.Error!.Message = "Authentication failed: " + result.Error.Message;
return new CallResult(result.Error)!;
}
_logger.Authenticated(socket.SocketId);
}
_logger.Authenticated(socket.SocketId);
socket.Authenticated = true;
return new CallResult(null);
}
@@ -710,6 +711,25 @@ namespace CryptoExchange.Net.Clients
return new CallResult(null);
}
/// <inheritdoc />
public override void SetOptions<T>(UpdateOptions<T> options)
{
var previousProxyIsSet = ClientOptions.Proxy != null;
base.SetOptions(options);
if ((!previousProxyIsSet && options.Proxy == null)
|| !socketConnections.Any())
{
return;
}
_logger.LogInformation("Reconnecting websockets to apply proxy");
// Update proxy, also triggers reconnect
foreach (var connection in socketConnections)
_ = connection.Value.UpdateProxy(options.Proxy);
}
/// <summary>
/// Log the current state of connections and subscriptions
/// </summary>
@@ -36,7 +36,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
var result = Activator.CreateInstance(objectType);
var arr = JArray.Load(reader);
return ParseObject(arr, result, objectType);
return ParseObject(arr, result!, objectType);
}
private static object ParseObject(JArray arr, object result, Type objectType)
@@ -58,25 +58,25 @@ namespace CryptoExchange.Net.Converters.JsonNet
var count = 0;
if (innerArray.Count == 0)
{
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 0 });
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 0 })!;
property.SetValue(result, arrayResult);
}
else if (innerArray[0].Type == JTokenType.Array)
{
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { innerArray.Count });
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { innerArray.Count })!;
foreach (var obj in innerArray)
{
var innerObj = Activator.CreateInstance(objType!);
arrayResult[count] = ParseObject((JArray)obj, innerObj, objType!);
arrayResult[count] = ParseObject((JArray)obj, innerObj!, objType!);
count++;
}
property.SetValue(result, arrayResult);
}
else
{
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 1 });
var arrayResult = (IList)Activator.CreateInstance(property.PropertyType, new [] { 1 })!;
var innerObj = Activator.CreateInstance(objType!);
arrayResult[0] = ParseObject(innerArray, innerObj, objType!);
arrayResult[0] = ParseObject(innerArray, innerObj!, objType!);
property.SetValue(result, arrayResult);
}
continue;
@@ -88,7 +88,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
object? value;
if (converterAttribute != null)
{
value = arr[attribute.Index].ToObject(property.PropertyType, new JsonSerializer {Converters = {(JsonConverter) Activator.CreateInstance(converterAttribute.ConverterType)}});
value = arr[attribute.Index].ToObject(property.PropertyType, new JsonSerializer {Converters = {(JsonConverter) Activator.CreateInstance(converterAttribute.ConverterType)!}});
}
else if (conversionAttribute != null)
{
@@ -120,7 +120,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
}
else if ((property.PropertyType == typeof(decimal)
|| property.PropertyType == typeof(decimal?))
&& (value != null && value.ToString().IndexOf("e", StringComparison.OrdinalIgnoreCase) >= 0))
&& (value != null && value.ToString()!.IndexOf("e", StringComparison.OrdinalIgnoreCase) >= 0))
{
var v = value.ToString();
if (decimal.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
@@ -164,7 +164,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
last = arrayProp.Index;
var converterAttribute = GetCustomAttribute<JsonConverterAttribute>(prop);
if (converterAttribute != null)
writer.WriteRawValue(JsonConvert.SerializeObject(prop.GetValue(value), (JsonConverter)Activator.CreateInstance(converterAttribute.ConverterType)));
writer.WriteRawValue(JsonConvert.SerializeObject(prop.GetValue(value), (JsonConverter)Activator.CreateInstance(converterAttribute.ConverterType)!));
else if (!IsSimple(prop.PropertyType))
serializer.Serialize(writer, prop.GetValue(value));
else
@@ -187,9 +187,9 @@ namespace CryptoExchange.Net.Converters.JsonNet
}
private static T? GetCustomAttribute<T>(MemberInfo memberInfo) where T : Attribute =>
(T?)_attributeByMemberInfoAndTypeCache.GetOrAdd((memberInfo, typeof(T)), tuple => memberInfo.GetCustomAttribute(typeof(T)));
(T?)_attributeByMemberInfoAndTypeCache.GetOrAdd((memberInfo, typeof(T)), tuple => memberInfo.GetCustomAttribute(typeof(T))!);
private static T? GetCustomAttribute<T>(Type type) where T : Attribute =>
(T?)_attributeByTypeAndTypeCache.GetOrAdd((type, typeof(T)), tuple => type.GetCustomAttribute(typeof(T)));
(T?)_attributeByTypeAndTypeCache.GetOrAdd((type, typeof(T)), tuple => type.GetCustomAttribute(typeof(T))!);
}
}
@@ -27,7 +27,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
{
try
{
return decimal.Parse(reader.Value!.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture);
return decimal.Parse(reader.Value!.ToString()!, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch (OverflowException)
{
@@ -40,7 +40,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
{
try
{
var value = reader.Value!.ToString();
var value = reader.Value!.ToString()!;
return decimal.Parse(value, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch (OverflowException)
@@ -34,7 +34,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
/// </returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var value = reader.Value?.ToString().ToLower().Trim();
var value = reader.Value?.ToString()!.ToLower().Trim();
if (value == null || value == "")
{
if (Nullable.GetUnderlyingType(objectType) != null)
@@ -297,7 +297,7 @@ namespace CryptoExchange.Net.Converters.JsonNet
// Try getting the underlying byte[] instead of the ToArray to prevent creating a copy
using var stream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array, arraySegment.Offset, arraySegment.Count)
? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var reader = new StreamReader(stream, Encoding.UTF8, false, Math.Max(2, data.Length), true);
using var jsonTextReader = new JsonTextReader(reader);
@@ -23,7 +23,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type converterType = typeof(ArrayConverterInner<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(converterType);
return (JsonConverter)Activator.CreateInstance(converterType)!;
}
private class ArrayPropertyInfo
@@ -79,7 +79,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
JsonSerializerOptions? typeOptions = null;
if (prop.JsonConverterType != null)
{
var converter = (JsonConverter)Activator.CreateInstance(prop.JsonConverterType);
var converter = (JsonConverter)Activator.CreateInstance(prop.JsonConverterType)!;
typeOptions = new JsonSerializerOptions();
typeOptions.Converters.Clear();
typeOptions.Converters.Add(converter);
@@ -90,7 +90,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
if (prop.PropertyInfo.PropertyType == typeof(string))
writer.WriteStringValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
else
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture));
writer.WriteRawValue(Convert.ToString(objValue, CultureInfo.InvariantCulture)!);
}
else
{
@@ -107,7 +107,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
if (reader.TokenType == JsonTokenType.Null)
return default;
var result = Activator.CreateInstance(typeToConvert);
var result = Activator.CreateInstance(typeToConvert)!;
return (T)ParseObject(ref reader, result, typeToConvert, options);
}
@@ -177,7 +177,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{
if (!_converterOptionsCache.TryGetValue(attribute.JsonConverterType, out var newOptions))
{
var converter = (JsonConverter)Activator.CreateInstance(attribute.JsonConverterType);
var converter = (JsonConverter)Activator.CreateInstance(attribute.JsonConverterType)!;
newOptions = new JsonSerializerOptions
{
NumberHandling = SerializerOptions.WithConverters.NumberHandling,
@@ -209,7 +209,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
}
if (targetType.IsAssignableFrom(value?.GetType()))
attribute.PropertyInfo.SetValue(result, value == null ? null : value);
attribute.PropertyInfo.SetValue(result, value);
else
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
}
@@ -17,7 +17,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{
try
{
return decimal.Parse(reader.GetString(), NumberStyles.Float, CultureInfo.InvariantCulture);
return decimal.Parse(reader.GetString()!, NumberStyles.Float, CultureInfo.InvariantCulture);
}
catch(OverflowException)
{
@@ -21,7 +21,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type converterType = typeof(BoolConverterInner<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(converterType);
return (JsonConverter)Activator.CreateInstance(converterType)!;
}
private class BoolConverterInner<T> : JsonConverter<T>
@@ -27,7 +27,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{
Type converterType = typeof(DateTimeConverterInner<>).MakeGenericType(typeToConvert);
return (JsonConverter)Activator.CreateInstance(converterType);
return (JsonConverter)Activator.CreateInstance(converterType)!;
}
private class DateTimeConverterInner<T> : JsonConverter<T>
@@ -74,7 +74,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
if (value == null)
{
writer.WriteNullValue();
}
else
{
var dtValue = (DateTime)(object)value;
@@ -0,0 +1,37 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter mapping to an object but also handles when an empty array is send
/// </summary>
/// <typeparam name="T"></typeparam>
public class EmptyArrayObjectConverter<T> : JsonConverter<T>
{
private static JsonSerializerOptions _defaultConverter = SerializerOptions.WithConverters;
/// <inheritdoc />
public override T? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
switch (reader.TokenType)
{
case JsonTokenType.StartArray:
_ = JsonSerializer.Deserialize<object[]>(ref reader, options);
return default;
case JsonTokenType.StartObject:
return JsonSerializer.Deserialize<T>(ref reader, _defaultConverter);
};
return default;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
=> JsonSerializer.Serialize(writer, (object?)value, options);
}
}
@@ -0,0 +1,31 @@
using System;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Attribute for allowing specifying a JsonConverter with constructor parameters
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class JsonConverterCtorAttribute : JsonConverterAttribute
{
private readonly object[] _parameters;
private readonly Type _type;
/// <summary>
/// ctor
/// </summary>
public JsonConverterCtorAttribute(Type type, params object[] parameters)
{
_type = type;
_parameters = parameters;
}
/// <inheritdoc />
public override JsonConverter CreateConverter(Type typeToConvert)
{
return (JsonConverter)Activator.CreateInstance(_type, _parameters)!;
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Replace a value on a string property
/// </summary>
public class ReplaceConverter : JsonConverter<string>
{
private readonly (string ValueToReplace, string ValueToReplaceWith)[] _replacementSets;
/// <summary>
/// ctor
/// </summary>
public ReplaceConverter(params string[] replaceSets)
{
_replacementSets = replaceSets.Select(x =>
{
var split = x.Split(new string[] { "->" }, StringSplitOptions.None);
if (split.Length != 2)
throw new ArgumentException("Invalid replacement config");
return (split[0], split[1]);
}).ToArray();
}
/// <inheritdoc />
public override string? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
foreach (var set in _replacementSets)
value = value?.Replace(set.ValueToReplace, set.ValueToReplaceWith);
return value;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) => writer.WriteStringValue(value);
}
}
@@ -21,6 +21,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
protected JsonDocument? _document;
private static JsonSerializerOptions _serializerOptions = SerializerOptions.WithConverters;
private JsonSerializerOptions? _customSerializerOptions;
/// <inheritdoc />
public bool IsJson { get; set; }
@@ -31,6 +32,21 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
/// <inheritdoc />
public object? Underlying => throw new NotImplementedException();
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonMessageAccessor()
{
}
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonMessageAccessor(JsonSerializerOptions options)
{
_customSerializerOptions = options;
}
/// <inheritdoc />
public CallResult<object> Deserialize(Type type, MessagePath? path = null)
{
@@ -42,7 +58,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
try
{
var result = _document.Deserialize(type, _serializerOptions);
var result = _document.Deserialize(type, _customSerializerOptions ?? _serializerOptions);
return new CallResult<object>(result!);
}
catch (JsonException ex)
@@ -65,7 +81,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
try
{
var result = _document.Deserialize<T>(_serializerOptions);
var result = _document.Deserialize<T>(_customSerializerOptions ?? _serializerOptions);
return new CallResult<T>(result!);
}
catch (JsonException ex)
@@ -129,7 +145,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{
try
{
return value.Value.Deserialize<T>(_serializerOptions);
return value.Value.Deserialize<T>(_customSerializerOptions ?? _serializerOptions);
}
catch { }
return default;
@@ -223,6 +239,20 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
/// <inheritdoc />
public override bool OriginalDataAvailable => _stream?.CanSeek == true;
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonStreamMessageAccessor(): base()
{
}
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonStreamMessageAccessor(JsonSerializerOptions options): base(options)
{
}
/// <inheritdoc />
public async Task<CallResult> Read(Stream stream, bool bufferStream)
{
@@ -286,6 +316,20 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
{
private ReadOnlyMemory<byte> _bytes;
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonByteMessageAccessor() : base()
{
}
/// <summary>
/// ctor
/// </summary>
public SystemTextJsonByteMessageAccessor(JsonSerializerOptions options) : base(options)
{
}
/// <inheritdoc />
public CallResult Read(ReadOnlyMemory<byte> data)
{
+5 -5
View File
@@ -1,14 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
<TargetFrameworks>netstandard2.0;netstandard2.1;net9.0</TargetFrameworks>
</PropertyGroup>
<PropertyGroup>
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>8.4.0</PackageVersion>
<AssemblyVersion>8.4.0</AssemblyVersion>
<FileVersion>8.4.0</FileVersion>
<PackageVersion>8.5.0</PackageVersion>
<AssemblyVersion>8.5.0</AssemblyVersion>
<FileVersion>8.5.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags>
<RepositoryType>git</RepositoryType>
@@ -20,7 +20,7 @@
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>https://github.com/JKorf/CryptoExchange.Net?tab=readme-ov-file#release-notes</PackageReleaseNotes>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<LangVersion>12.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
<ItemGroup>
+1 -1
View File
@@ -180,7 +180,7 @@ namespace CryptoExchange.Net
{
var randomChars = new char[length];
#if NETSTANDARD2_1_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
for (int i = 0; i < length; i++)
randomChars[i] = _allowedRandomChars[RandomNumberGenerator.GetInt32(0, _allowedRandomChars.Length)];
#else
+3 -3
View File
@@ -67,7 +67,7 @@ namespace CryptoExchange.Net
{
if (serializationType == ArrayParametersSerialization.Array)
{
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={string.Format(CultureInfo.InvariantCulture, "{0}", v)}"))}&";
uriString += $"{string.Join("&", ((object[])(urlEncodeValues ? Uri.EscapeDataString(arrayEntry.Value.ToString()!) : arrayEntry.Value)).Select(v => $"{arrayEntry.Key}[]={string.Format(CultureInfo.InvariantCulture, "{0}", v)}"))}&";
}
else if (serializationType == ArrayParametersSerialization.MultipleValues)
{
@@ -111,7 +111,7 @@ namespace CryptoExchange.Net
formData.Add(kvp.Key, string.Format(CultureInfo.InvariantCulture, "{0}", kvp.Value));
}
}
return formData.ToString();
return formData.ToString()!;
}
/// <summary>
@@ -366,7 +366,7 @@ namespace CryptoExchange.Net
{
using var decompressedStream = new MemoryStream();
using var dataStream = MemoryMarshal.TryGetArray(data, out var arraySegment)
? new MemoryStream(arraySegment.Array, arraySegment.Offset, arraySegment.Count)
? new MemoryStream(arraySegment.Array!, arraySegment.Offset, arraySegment.Count)
: new MemoryStream(data.ToArray());
using var deflateStream = new GZipStream(new MemoryStream(data.ToArray()), CompressionMode.Decompress);
deflateStream.CopyTo(decompressedStream);
@@ -1,5 +1,6 @@
using CryptoExchange.Net.Authentication;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.SharedApis;
using System;
@@ -31,5 +32,12 @@ namespace CryptoExchange.Net.Interfaces
/// <typeparam name="T"></typeparam>
/// <param name="credentials"></param>
void SetApiCredentials<T>(T credentials) where T : ApiCredentials;
/// <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.
/// </summary>
/// <typeparam name="T">Api crentials type</typeparam>
/// <param name="options">Options to set</param>
void SetOptions<T>(UpdateOptions<T> options) where T : ApiCredentials;
}
}
@@ -24,6 +24,13 @@ namespace CryptoExchange.Net.Interfaces
/// <param name="requestTimeout">Request timeout to use</param>
/// <param name="httpClient">Optional shared http client instance</param>
/// <param name="proxy">Optional proxy to use when no http client is provided</param>
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient=null);
void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? httpClient = null);
/// <summary>
/// Update settings
/// </summary>
/// <param name="proxy">Proxy to use</param>
/// <param name="requestTimeout">Request timeout to use</param>
void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout);
}
}
@@ -93,5 +93,10 @@ namespace CryptoExchange.Net.Interfaces
/// </summary>
/// <returns></returns>
Task CloseAsync();
/// <summary>
/// Update proxy setting
/// </summary>
void UpdateProxy(ApiProxy? proxy);
}
}
+47
View File
@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net
{
/// <summary>
/// Helpers for client libraries
/// </summary>
public static class LibraryHelpers
{
/// <summary>
/// Client order id seperator
/// </summary>
public const string ClientOrderIdSeperator = "JK";
/// <summary>
/// Apply broker id to a client order id
/// </summary>
/// <param name="clientOrderId"></param>
/// <param name="brokerId"></param>
/// <param name="maxLength"></param>
/// <param name="allowValueAdjustement"></param>
/// <returns></returns>
public static string ApplyBrokerId(string? clientOrderId, string brokerId, int maxLength, bool allowValueAdjustement)
{
var reservedLength = brokerId.Length + ClientOrderIdSeperator.Length;
if ((clientOrderId?.Length + reservedLength) > maxLength)
return clientOrderId!;
if (!string.IsNullOrEmpty(clientOrderId))
{
if (allowValueAdjustement)
clientOrderId = brokerId + ClientOrderIdSeperator + clientOrderId;
return clientOrderId!;
}
else
{
clientOrderId = ExchangeHelpers.AppendRandomString(brokerId + ClientOrderIdSeperator, maxLength);
}
return clientOrderId;
}
}
}
@@ -14,7 +14,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(byte[] x, byte[] y)
public int Compare(byte[]? x, byte[]? y)
{
// Shortcuts: If both are null, they are the same.
if (x == null && y == null) return 0;
@@ -0,0 +1,29 @@
using CryptoExchange.Net.Authentication;
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.Objects.Options
{
/// <summary>
/// Options to update
/// </summary>
public class UpdateOptions<T> where T : ApiCredentials
{
/// <summary>
/// Proxy setting. Note that if this is not provided any previously set proxy will be reset
/// </summary>
public ApiProxy? Proxy { get; set; }
/// <summary>
/// Api credentials
/// </summary>
public T? ApiCredentials { get; set; }
/// <summary>
/// Request timeout
/// </summary>
public TimeSpan? RequestTimeout { get; set; }
}
/// <inheritdoc />
public class UpdateOptions : UpdateOptions<ApiCredentials> { }
}
@@ -13,7 +13,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public int Compare(string x, string y)
public int Compare(string? x, string? y)
{
// Shortcuts: If both are null, they are the same.
if (x == null && y == null) return 0;
@@ -156,7 +156,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="value"></param>
public void AddSecondsString(string key, DateTime value)
{
Add(key, DateTimeConverter.ConvertToSeconds(value).ToString());
Add(key, DateTimeConverter.ConvertToSeconds(value).ToString()!);
}
/// <summary>
@@ -167,7 +167,7 @@ namespace CryptoExchange.Net.Objects
public void AddOptionalSecondsString(string key, DateTime? value)
{
if (value != null)
Add(key, DateTimeConverter.ConvertToSeconds(value).ToString());
Add(key, DateTimeConverter.ConvertToSeconds(value).ToString()!);
}
/// <summary>
@@ -187,7 +187,7 @@ namespace CryptoExchange.Net.Objects
/// <param name="value"></param>
public void AddEnumAsInt<T>(string key, T value)
{
var stringVal = EnumConverter.GetString(value);
var stringVal = EnumConverter.GetString(value)!;
Add(key, int.Parse(stringVal)!);
}
@@ -843,9 +843,9 @@ namespace CryptoExchange.Net.OrderBook
internal class DescComparer<T> : IComparer<T>
{
public int Compare(T x, T y)
public int Compare(T? x, T? y)
{
return Comparer<T>.Default.Compare(y, x);
return Comparer<T>.Default.Compare(y!, x!);
}
}
}
@@ -20,7 +20,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
/// <summary>
/// Apply guard per connection
/// </summary>
public static Func<RequestDefinition, string, string?, string> PerConnection { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.ConnectionId.ToString());
public static Func<RequestDefinition, string, string?, string> PerConnection { get; } = new Func<RequestDefinition, string, string?, string>((def, host, key) => def.ConnectionId.ToString()!);
/// <summary>
/// Apply guard per API key
/// </summary>
+1 -1
View File
@@ -48,7 +48,7 @@ namespace CryptoExchange.Net.Requests
}
/// <inheritdoc />
public Uri Uri => _request.RequestUri;
public Uri Uri => _request.RequestUri!;
/// <inheritdoc />
public int RequestId { get; }
+33 -22
View File
@@ -17,28 +17,7 @@ namespace CryptoExchange.Net.Requests
public void Configure(ApiProxy? proxy, TimeSpan requestTimeout, HttpClient? client = null)
{
if (client == null)
{
var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
}
catch (PlatformNotSupportedException) { }
if (proxy != null)
{
handler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
client = new HttpClient(handler)
{
Timeout = requestTimeout
};
}
client = CreateClient(proxy, requestTimeout);
_httpClient = client;
}
@@ -51,5 +30,37 @@ namespace CryptoExchange.Net.Requests
return new Request(new HttpRequestMessage(method, uri), _httpClient, requestId);
}
/// <inheritdoc />
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout)
{
_httpClient = CreateClient(proxy, requestTimeout);
}
private HttpClient CreateClient(ApiProxy? proxy, TimeSpan requestTimeout)
{
var handler = new HttpClientHandler();
try
{
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
handler.DefaultProxyCredentials = CredentialCache.DefaultCredentials;
}
catch (PlatformNotSupportedException) { }
if (proxy != null)
{
handler.Proxy = new WebProxy
{
Address = new Uri($"{proxy.Host}:{proxy.Port}"),
Credentials = proxy.Password == null ? null : new NetworkCredential(proxy.Login, proxy.Password)
};
}
var client = new HttpClient(handler)
{
Timeout = requestTimeout
};
return client;
}
}
}
@@ -60,8 +60,8 @@ namespace CryptoExchange.Net.SharedApis
}
else
{
if (param.Names.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true))
return new ArgumentError($"One of exchange parameters `{string.Join(", ", param.Names)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
if (param.Names!.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true))
return new ArgumentError($"One of exchange parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
}
}
@@ -113,13 +113,13 @@ namespace CryptoExchange.Net.SharedApis
{
if (!string.IsNullOrEmpty(param.Name))
{
if (typeof(T).GetProperty(param.Name).GetValue(request, null) == null)
if (typeof(T).GetProperty(param.Name)!.GetValue(request, null) == null)
return new ArgumentError($"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}");
}
else
{
if (param.Names.All(x => typeof(T).GetProperty(param.Name).GetValue(request, null) == null))
return new ArgumentError($"One of optional parameters `{string.Join(", ", param.Names)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
if (param.Names!.All(x => typeof(T).GetProperty(param.Name!)!.GetValue(request, null) == null))
return new ArgumentError($"One of optional parameters `{string.Join(", ", param.Names!)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
}
}
@@ -64,7 +64,7 @@ namespace CryptoExchange.Net.SharedApis
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Supported limit values: [{(SupportedLimits == null ? string.Join(", ", SupportedLimits) : $"{MinLimit}..{MaxLimit}")}]");
sb.AppendLine($"Supported limit values: [{(SupportedLimits != null ? string.Join(", ", SupportedLimits) : $"{MinLimit}..{MaxLimit}")}]");
return sb.ToString();
}
}
@@ -55,7 +55,7 @@ namespace CryptoExchange.Net.SharedApis
{
if (Name != null)
return $"[{ValueType.Name}] {Name}: {Description} | example: {ExampleValue}";
return $"[{ValueType.Name}] {string.Join(" / ", Names)}: {Description} | example: {ExampleValue}";
return $"[{ValueType.Name}] {string.Join(" / ", Names!)}: {Description} | example: {ExampleValue}";
}
}
}
@@ -155,6 +155,12 @@ namespace CryptoExchange.Net.Sockets
_baseAddress = $"{Uri.Scheme}://{Uri.Host}";
}
/// <inheritdoc />
public void UpdateProxy(ApiProxy? proxy)
{
Parameters.Proxy = proxy;
}
/// <inheritdoc />
public virtual async Task<CallResult> ConnectAsync()
{
@@ -189,9 +195,12 @@ namespace CryptoExchange.Net.Sockets
socket.Options.SetBuffer(_receiveBufferSize, _sendBufferSize);
if (Parameters.Proxy != null)
SetProxy(socket, Parameters.Proxy);
#if NET6_0_OR_GREATER
#if NET6_0_OR_GREATER
socket.Options.CollectHttpResponseDetails = true;
#endif
#endif
#if NET9_0_OR_GREATER
socket.Options.KeepAliveTimeout = TimeSpan.FromSeconds(10);
#endif
}
catch (PlatformNotSupportedException)
{
@@ -229,13 +238,13 @@ namespace CryptoExchange.Net.Sockets
if (e is WebSocketException we)
{
#if (NET6_0_OR_GREATER)
#if (NET6_0_OR_GREATER)
if (_socket.HttpStatusCode == HttpStatusCode.TooManyRequests)
{
await (OnConnectRateLimited?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
return new CallResult(new ServerRateLimitError(we.Message));
}
#else
#else
// ClientWebSocket.HttpStatusCode is only available in .NET6+ https://learn.microsoft.com/en-us/dotnet/api/system.net.websockets.clientwebsocket.httpstatuscode?view=net-8.0
// Try to read 429 from the message instead
if (we.Message.Contains("429"))
@@ -243,7 +252,7 @@ namespace CryptoExchange.Net.Sockets
await (OnConnectRateLimited?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
return new CallResult(new ServerRateLimitError(we.Message));
}
#endif
#endif
}
return new CallResult(new CantConnectError());
@@ -435,8 +444,8 @@ namespace CryptoExchange.Net.Sockets
{
// Wait until we receive close confirmation
await Task.Delay(10).ConfigureAwait(false);
if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(5))
break; // Wait for max 5 seconds, then just abort the connection
if (DateTime.UtcNow - startWait > TimeSpan.FromSeconds(1))
break; // Wait for max 1 second, then just abort the connection
}
}
}
@@ -598,14 +607,14 @@ namespace CryptoExchange.Net.Sockets
if (_socket.State == WebSocketState.CloseReceived)
{
// Close received means it server initiated, we should send a confirmation and close the socket
_logger.SocketReceivedCloseMessage(Id, receiveResult.CloseStatus.ToString(), receiveResult.CloseStatusDescription);
_logger.SocketReceivedCloseMessage(Id, receiveResult.CloseStatus.ToString()!, receiveResult.CloseStatusDescription ?? string.Empty);
if (_closeTask?.IsCompleted != false)
_closeTask = CloseInternalAsync();
}
else
{
// Means the socket is now closed and we were the one initiating it
_logger.SocketReceivedCloseConfirmation(Id, receiveResult.CloseStatus.ToString(), receiveResult.CloseStatusDescription);
_logger.SocketReceivedCloseConfirmation(Id, receiveResult.CloseStatus.ToString()!, receiveResult.CloseStatusDescription ?? string.Empty);
}
break;
@@ -620,7 +629,7 @@ namespace CryptoExchange.Net.Sockets
// Write the data to a memory stream to be reassembled later
if (multipartStream == null)
multipartStream = new MemoryStream();
multipartStream.Write(buffer.Array, buffer.Offset, receiveResult.Count);
multipartStream.Write(buffer.Array!, buffer.Offset, receiveResult.Count);
}
else
{
@@ -634,7 +643,7 @@ namespace CryptoExchange.Net.Sockets
{
// Received the end of a multipart message, write to memory stream for reassembling
_logger.SocketReceivedPartialMessage(Id, receiveResult.Count);
multipartStream!.Write(buffer.Array, buffer.Offset, receiveResult.Count);
multipartStream!.Write(buffer.Array!, buffer.Offset, receiveResult.Count);
}
break;
@@ -23,6 +23,6 @@ namespace CryptoExchange.Net.Sockets
/// <summary>
/// Callback after query
/// </summary>
public Action<CallResult>? Callback { get; set; }
public Action<SocketConnection, CallResult>? Callback { get; set; }
}
}
+5
View File
@@ -23,6 +23,11 @@ namespace CryptoExchange.Net.Sockets
/// </summary>
public bool Completed { get; set; }
/// <summary>
/// Timeout for the request
/// </summary>
public TimeSpan? RequestTimeout { get; set; }
/// <summary>
/// The number of required responses. Can be more than 1 when for example subscribing multiple symbols streams in a single request,
/// and each symbol receives it's own confirmation response
+17 -5
View File
@@ -11,6 +11,8 @@ using System.Diagnostics;
using CryptoExchange.Net.Clients;
using CryptoExchange.Net.Logging.Extensions;
using System.Threading;
using CryptoExchange.Net.Objects.Options;
using CryptoExchange.Net.Authentication;
namespace CryptoExchange.Net.Sockets
{
@@ -396,7 +398,7 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns>
protected virtual Task HandleRequestRateLimitedAsync(int requestId)
{
Query query;
Query? query;
lock (_listenersLock)
{
query = _listeners.OfType<Query>().FirstOrDefault(x => x.Id == requestId);
@@ -425,7 +427,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="requestId">Id of the request sent</param>
protected virtual Task HandleRequestSentAsync(int requestId)
{
Query query;
Query? query;
lock (_listenersLock)
{
query = _listeners.OfType<Query>().FirstOrDefault(x => x.Id == requestId);
@@ -437,7 +439,7 @@ namespace CryptoExchange.Net.Sockets
return Task.CompletedTask;
}
query.IsSend(ApiClient.ClientOptions.RequestTimeout);
query.IsSend(query.RequestTimeout ?? ApiClient.ClientOptions.RequestTimeout);
return Task.CompletedTask;
}
@@ -583,6 +585,16 @@ namespace CryptoExchange.Net.Sockets
/// <returns></returns>
public async Task TriggerReconnectAsync() => await _socket.ReconnectAsync().ConfigureAwait(false);
/// <summary>
/// Update the proxy setting and reconnect
/// </summary>
/// <param name="proxy">New proxy setting</param>
public async Task UpdateProxy(ApiProxy? proxy)
{
_socket.UpdateProxy(proxy);
await TriggerReconnectAsync().ConfigureAwait(false);
}
/// <summary>
/// Close the connection
/// </summary>
@@ -988,7 +1000,7 @@ namespace CryptoExchange.Net.Sockets
/// <param name="interval">How often</param>
/// <param name="queryDelegate">Method returning the query to send</param>
/// <param name="callback">The callback for processing the response</param>
public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<CallResult>? callback)
public virtual void QueryPeriodic(string identifier, TimeSpan interval, Func<SocketConnection, Query> queryDelegate, Action<SocketConnection, CallResult>? callback)
{
if (queryDelegate == null)
throw new ArgumentNullException(nameof(queryDelegate));
@@ -1020,7 +1032,7 @@ namespace CryptoExchange.Net.Sockets
try
{
var result = await SendAndWaitQueryAsync(query).ConfigureAwait(false);
callback?.Invoke(result);
callback?.Invoke(this, result);
}
catch (Exception ex)
{
@@ -378,7 +378,7 @@ namespace CryptoExchange.Net.Testing.Comparers
if (objectValue is bool boolVal && jsonValue.Value<bool>() != boolVal)
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<bool>()} vs {(bool)objectValue}");
if (jsonValue.Value<bool>() != bool.Parse(objectValue.ToString()))
if (jsonValue.Value<bool>() != bool.Parse(objectValue.ToString()!))
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<bool>()} vs {(bool)objectValue}");
}
}
@@ -211,7 +211,7 @@ namespace CryptoExchange.Net.Testing.Comparers
if (dictProp.Value.Type == JTokenType.Object)
{
CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name].GetType(), null, null, ignoreProperties);
CheckPropertyValue(method, dictProp.Value, dict[dictProp.Name]!, dict[dictProp.Name]!.GetType(), null, null, ignoreProperties);
}
else
{
@@ -5,8 +5,11 @@ namespace CryptoExchange.Net.Testing
{
internal class EnumValueTraceListener : TraceListener
{
public override void Write(string message)
public override void Write(string? message)
{
if (message == null)
return;
if (message.Contains("Cannot map"))
throw new Exception("Enum value error: " + message);
@@ -14,8 +17,11 @@ namespace CryptoExchange.Net.Testing
throw new Exception("Enum null error: " + message);
}
public override void WriteLine(string message)
public override void WriteLine(string? message)
{
if (message == null)
return;
if (message.Contains("Cannot map"))
throw new Exception("Enum value error: " + message);
@@ -25,5 +25,7 @@ namespace CryptoExchange.Net.Testing.Implementations
_request.RequestId = requestId;
return _request;
}
public void UpdateSettings(ApiProxy? proxy, TimeSpan requestTimeout) {}
}
}
@@ -92,5 +92,7 @@ namespace CryptoExchange.Net.Testing.Implementations
public Task ReconnectAsync() => throw new NotImplementedException();
public void Dispose() { }
public void UpdateProxy(ApiProxy? proxy) => throw new NotImplementedException();
}
}
+2 -4
View File
@@ -169,7 +169,7 @@ namespace CryptoExchange.Net.Testing
{
var assembly = Assembly.GetAssembly(clientType);
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"));
foreach (var clientInterface in clientInterfaces)
{
@@ -179,9 +179,7 @@ namespace CryptoExchange.Net.Testing
int methods = 0;
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());
if (interfaceMethod == null)
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()) ?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
methods++;
}
@@ -23,7 +23,7 @@ namespace CryptoExchange.Net.Trackers.Klines
/// <summary>
/// The internal data structure
/// </summary>
protected readonly Dictionary<DateTime, SharedKline> _data = new Dictionary<DateTime, SharedKline>();
protected readonly SortedDictionary<DateTime, SharedKline> _data = new SortedDictionary<DateTime, SharedKline>();
/// <summary>
/// The pre-snapshot queue buffering updates received before the snapshot is set and which will be applied after the snapshot was set
/// </summary>
@@ -350,7 +350,8 @@ namespace CryptoExchange.Net.Trackers.Trades
_data.Add(item);
}
_firstTimestamp = _data.Min(v => v.Timestamp);
if (_data.Any())
_firstTimestamp = _data.Min(v => v.Timestamp);
ApplyWindow(false);
}
+44
View File
@@ -29,6 +29,7 @@ The following API's are directly supported. Note that there are 3rd party implem
|Mexc|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Mexc.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.Mexc.Net)|
|OKX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.OKX.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.OKX.Net)|
|WhiteBit|[JKorf/WhiteBit.Net](https://github.com/JKorf/WhiteBit.Net)|[![Nuget version](https://img.shields.io/nuget/v/WhiteBit.net.svg?style=flat-square)](https://www.nuget.org/packages/WhiteBit.Net)|
|XT|[JKorf/XT.Net](https://github.com/JKorf/XT.Net)|[![Nuget version](https://img.shields.io/nuget/v/XT.net.svg?style=flat-square)](https://www.nuget.org/packages/XT.Net)|
Any of these can be installed independently or install [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) which includes all exchange API's.
@@ -39,6 +40,21 @@ A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free t
## Support the project
Any support is greatly appreciated.
## Referral
When creating an account on new exchanges please consider using a referral link from below to support development
|Exchange|Link|
|--|--|
|Bybit|[https://partner.bybit.com/b/jkorf](https://partner.bybit.com/b/jkorf)|
|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)|
|Crypto.com|[https://crypto.com/exch/26ge92xbkn](https://crypto.com/exch/26ge92xbkn)|
|HTX|[https://www.htx.com/invite/en-us/1f?invite_code=fxp9](https://www.htx.com/invite/en-us/1f?invite_code=fxp9)|
|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)|
|WhiteBit|[https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf](https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf)|
|XT|[https://www.xt.com/en/accounts/register?ref=1HRM5J](https://www.xt.com/en/accounts/register?ref=1HRM5J)|
### Donate
Make a one time donation in a crypto currency of your choice. If you prefer to donate a currency not listed here please contact me.
@@ -50,6 +66,34 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
## Release notes
* Version 8.5.0 - 23 Dec 2024
* Added SetOptions method to update client settings
* Added SocketConnection parameter to PeriodicQuery callback
* Added setting of DefaultProxyCredentials on HttpClient instance when client is not provided by DI
* Added support for overriding request timeout per request
* Added build target for net9.0
* Added setting of KeepAliveTimeout on websocket connections to improve dropped connection detection
* Changed max wait time for close handshake response from 5 seconds to 1 second
* Fixed exception in trade tracker when there is no data in the initial snapshot
* Version 8.4.5 - 20 Dec 2024
* Added EmptyArrayObjectConverter System.Text.Json JsonConverter
* Added JsonSerializerOptions parameter to SystemTextJsonMessageAccessor constructor
* Version 8.4.4 - 08 Dec 2024
* Changed JsonConverterCtorAttribute to use constructor type parameter instead of generic type parameter to support .net framework
* Version 8.4.3 - 03 Dec 2024
* Fixed KlineTracker update handling
* Version 8.4.2 - 02 Dec 2024
* Removed special characters in ClientOrderIdSeperator to adhere to field content rules
* Version 8.4.1 - 02 Dec 2024
* Added JsonConverterCtorAttribute to allow specifying a custom JsonConverter with constructor parameters on properties
* Added ReplaceConverter System.Text.Json converter
* Added LibraryHelpers class for internal helper methods
* Version 8.4.0 - 28 Nov 2024
* Added GetFeesAsync Shared REST client support
* Added LibraryOptions base class
+262 -7
View File
@@ -163,6 +163,7 @@
<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>OKX</td><td><a href="https://github.com/JKorf/OKX.Net">JKorf/OKX.Net</a></td><td><a href="https://www.nuget.org/packages/JK.OKX.Net" target="_blank"><img src="https://img.shields.io/nuget/v/JK.OKX.net.svg?style=flat-square" /></a></td></tr>
<tr><td>WhiteBit</td><td><a href="https://github.com/JKorf/WhiteBit.Net">JKorf/WhiteBit.Net</a></td><td><a href="https://www.nuget.org/packages/WhiteBit.Net" target="_blank"><img src="https://img.shields.io/nuget/v/WhiteBit.net.svg?style=flat-square" /></a></td></tr>
<tr><td>XT</td><td><a href="https://github.com/JKorf/XT.Net">JKorf/XT.Net</a></td><td><a href="https://www.nuget.org/packages/XT.Net" target="_blank"><img src="https://img.shields.io/nuget/v/XT.net.svg?style=flat-square" /></a></td></tr>
</table>
<p>Note that there are 3rd party implementations going around, but only the listed ones here are created and supported by me.</p>
<p>When using multiple of these API's the <a href="https://github.com/jkorf/CryptoClients.Net">CryptoClients.Net</a> package can be used which combines these packages and allows easy access to all exchange API's.</p>
@@ -194,6 +195,21 @@
<h4>Support the project</h4>
<b>Referral</b>
<p>When creating an account on new exchanges please consider using a referral link from below to support development</p>
<table>
<tr><td>Exchange</td><td>Link</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>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>HTX</td><td>https://www.htx.com/invite/en-us/1f?invite_code=fxp9</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>WhiteBit</td><td>https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf</td></tr>
<tr><td>XT</td><td>https://www.xt.com/en/accounts/register?ref=1HRM5J</td></tr>
</table>
<b>Donate</b><br />
<p>Make a one time donation in a crypto currency of your choice. If you prefer to donate a currency not listed here please contact me.<p>
@@ -203,9 +219,7 @@
<b>Sponsor</b><br />
<p>Alternatively, sponsor me on Github using <a href="https://github.com/sponsors/JKorf">Github Sponsors</a>.</p>
<div class="alert alert-info">I develop and maintain these packages on my own for free in my spare time, any support is greatly appreciated.</div>
</section>
<hr class="divider">
@@ -279,7 +293,10 @@
<a class="nav-link" id="install-okx-tab" data-toggle="tab" href="#install-okx" role="tab" aria-controls="install-okx" aria-selected="false">OKX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="install-whitebit-tab" data-toggle="tab" href="#install-whitebit" role="tab" aria-controls="install-whitebit" aria-selected="false">OKX</a>
<a class="nav-link" id="install-whitebit-tab" data-toggle="tab" href="#install-whitebit" role="tab" aria-controls="install-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="install-xt-tab" data-toggle="tab" href="#install-xt" role="tab" aria-controls="install-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
@@ -347,6 +364,9 @@
<div class="tab-pane fade" id="install-whitebit" role="tabpanel" aria-labelledby="install-whitebit-tab">
<pre><code>dotnet add package WhiteBit.Net</code></pre>
</div>
<div class="tab-pane fade" id="install-xt" role="tabpanel" aria-labelledby="install-xt-tab">
<pre><code>dotnet add package XT.Net</code></pre>
</div>
</div>
</div>
</section>
@@ -415,6 +435,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="di-whitebit-tab" data-toggle="tab" href="#di-whitebit" role="tab" aria-controls="di-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="di-xt-tab" data-toggle="tab" href="#di-xt" role="tab" aria-controls="di-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="di-cc" role="tabpanel" aria-labelledby="di-cc-tab">
@@ -471,6 +494,9 @@
<div class="tab-pane fade" id="di-whitebit" role="tabpanel" aria-labelledby="di-whitebit-tab">
<pre><code>builder.Services.AddWhiteBit();</code></pre>
</div>
<div class="tab-pane fade" id="di-xt" role="tabpanel" aria-labelledby="di-xt-tab">
<pre><code>builder.Services.AddXT();</code></pre>
</div>
</div>
</div>
@@ -531,6 +557,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="interfaces-whitebit-tab" data-toggle="tab" href="#interfaces-whitebit" role="tab" aria-controls="interfaces-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="interfaces-xt-tab" data-toggle="tab" href="#interfaces-xt" role="tab" aria-controls="interfaces-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="interfaces-cc" role="tabpanel" aria-labelledby="interfaces-cc-tab">
@@ -1111,6 +1140,39 @@
</tr>
</table>
</div>
<div class="tab-pane fade" id="interfaces-xt" role="tabpanel" aria-labelledby="interfaces-xt-tab">
<table class="table table-bordered">
<tr><th>Interface</th><th>Description</th></tr>
<tr>
<td><code>IXTRestClient</code></td>
<td>The client for accessing the XT REST API</td>
</tr>
<tr>
<td><code>IXTSocketClient</code></td>
<td>The client for accessing the XT Websocket API</td>
</tr>
<tr>
<td><code>IXTOrderBookFactory</code></td>
<td>A factory for creating SymbolOrderBook instances for the XT API</td>
</tr>
<tr>
<td><code>IXTTrackerFactory</code></td>
<td>A factory for creating kline and trade Tracker instances for the XT 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>
</div>
</section>
@@ -1122,7 +1184,7 @@
<p>All clients work with the same principles:</p>
<ul>
<li>Mandatory parameters are non-nullable while optional parameters are nullable and will have a default value of null.</li>
<li>Any operation will return a form of <code>CallResult</code>. This result can and should be checked for success using the `Success` property. If `Success` is false the `Error` property will have more info.</li>
<li>Any operation will return a form of <code>CallResult</code>. This result can and should be checked for success using the <code>Success</code> property. If <code>Success</code> is false the <code>Error</code> property will have more info.</li>
<li>Clients will not throw exceptions.</li>
</ul>
</section>
@@ -1190,6 +1252,9 @@
<li class="nav-item" role="presentation">
<a class="nav-link" id="rest-whitebit-tab" data-toggle="tab" href="#rest-whitebit" role="tab" aria-controls="rest-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="rest-xt-tab" data-toggle="tab" href="#rest-xt" role="tab" aria-controls="rest-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="rest-cc" role="tabpanel" aria-labelledby="rest-cc-tab">
@@ -1404,6 +1469,18 @@ if (!tickersResult.Success)
// Handle error, tickersResult.Error contains more information
}
else
{
// Handle data, tickersResult.Data will contain the actual data
}</code></pre>
</div>
<div class="tab-pane fade" id="rest-xt" role="tabpanel" aria-labelledby="rest-xt-tab">
<pre><code>var client = new XTRestClient();
var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
if (!tickersResult.Success)
{
// Handle error, tickersResult.Error contains more information
}
else
{
// Handle data, tickersResult.Data will contain the actual data
}</code></pre>
@@ -1533,6 +1610,9 @@ else
<li class="nav-item" role="presentation">
<a class="nav-link" id="socket-whitebit-tab" data-toggle="tab" href="#socket-whitebit" role="tab" aria-controls="socket-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="socket-xt-tab" data-toggle="tab" href="#socket-xt" role="tab" aria-controls="socket-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="socket-cc" role="tabpanel" aria-labelledby="socket-cc-tab">
@@ -1711,7 +1791,7 @@ if (!subscribeResult.Success)
}
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
</div>
<div class="tab-pane fade" id="socket-okx" role="tabpanel" aria-labelledby="socket-okx-tab">
<div class="tab-pane fade" id="socket-whitebit" role="tabpanel" aria-labelledby="socket-whitebit-tab">
<pre><code>var client = new WhiteBitSocketClient();
var subscribeResult = await client.V4Api.ExchangeData.SubscribeToTickerUpdatesAsync("ETH_USDT", update => {
// Handle the data update, update.Data will contain the actual data
@@ -1722,6 +1802,18 @@ if (!subscribeResult.Success)
}
// Subscribing was successfull, the data will now be streamed into the data handler</code></pre>
</div>
<div class="tab-pane fade" id="socket-xt" role="tabpanel" aria-labelledby="socket-xt-tab">
<pre><code>var client = new XTSocketClient();
var subscribeResult = await client.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("eth_usdt", 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>
</div>
</div>
</div>
@@ -1922,6 +2014,9 @@ var binanceTriggered = CheckForTrigger(lastBinanceTicker);</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="shared-whitebit-tab" data-toggle="tab" href="#shared-whitebit" role="tab" aria-controls="shared-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="shared-xt-tab" data-toggle="tab" href="#shared-xt" role="tab" aria-controls="shared-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="shared-binance" role="tabpanel" aria-labelledby="shared-binance-tab">
@@ -2105,6 +2200,19 @@ var spotSharedRestClients = whitebitRestClient.V4Api.SharedClient;
// Futures and Spot API common functionality socket client
var spotSharedSocketClient = whitebitSocketClient.V4Api.SharedClient;</code></pre>
</div>
<div class="tab-pane fade" id="shared-xt" role="tabpanel" aria-labelledby="shared-xt-tab">
<pre><code>// Spot API common functionality rest client
var spotSharedRestClients = xtRestClient.SpotApi.SharedClient;
// Futures API common functionality rest client
var futuresSharedRestClients = xtRestClient.UsdtFuturesApi.SharedClient;
// Spot API common functionality socket client
var spotSharedSocketClient = xtSocketClient.SpotApi.SharedClient;
// Futures API common functionality socket client
var futuresSharedSocketClient = xtSocketClient.FuturesApi.SharedClient;</code></pre>
</div>
</div>
<h4 id="shared_tradingmode">TradingMode</h4>
@@ -2232,6 +2340,7 @@ var balances = await restClient.HTX.SpotApi.SharedClient.GetBalancesAsync(new Ge
<tr><td><code>ILeverageRestClient</code></td><td>For managing leverage for a Futures symbol</td></tr>
<tr><td><code>IPositionHistoryRestClient</code></td><td>For requesting the user position closing history</td></tr>
<tr><td><code>IPositionModeRestClient</code></td><td>For managing the position mode for the user</td></tr>
<tr><td><code>IFeeRestClient</code></td><td>For requesting maker and taker trading fee percentages for the user</td></tr>
</table>
<p style="font-style: italic;">Available Socket shared interfaces</p>
@@ -2391,6 +2500,9 @@ options.ApiCredentials = new ApiCredentials("YOUR PUBLIC KEY", "YOUR PRIVATE KEY
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-whitebit-tab" data-toggle="tab" href="#options-whitebit" role="tab" aria-controls="options-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-xt-tab" data-toggle="tab" href="#options-xt" role="tab" aria-controls="options-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="options-cc" role="tabpanel" aria-labelledby="options-cc-tab">
@@ -2605,6 +2717,18 @@ builder.Services.AddOKX(builder.Configuration.GetSection("OKX"));</code></pre>
// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example configuration
builder.Services.AddWhiteBit(builder.Configuration.GetSection("WhiteBit"));</code></pre>
</div>
<div class="tab-pane fade" id="options-xt" role="tabpanel" aria-labelledby="options-xt-tab">
<pre><code>builder.Services.AddXT(
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.AddXT(builder.Configuration.GetSection("XT"));</code></pre>
</div>
</div>
</div>
@@ -2666,6 +2790,9 @@ builder.Services.AddWhiteBit(builder.Configuration.GetSection("WhiteBit"));</cod
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-whitebit-tab" data-toggle="tab" href="#options-constr-whitebit" role="tab" aria-controls="options-constr-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-xt-tab" data-toggle="tab" href="#options-constr-xt" role="tab" aria-controls="options-constr-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="options-constr-cc" role="tabpanel" aria-labelledby="options-cc-tab">
@@ -2772,6 +2899,12 @@ builder.Services.AddWhiteBit(builder.Configuration.GetSection("WhiteBit"));</cod
</div>
<div class="tab-pane fade" id="options-constr-whitebit" role="tabpanel" aria-labelledby="options-whitebit-tab">
<pre><code>var client = new WhiteBitRestClient(opts =>
{
opts.RequestTimeout = TimeSpan.FromSeconds(30);
});</code></pre>
</div>
<div class="tab-pane fade" id="options-constr-xt" role="tabpanel" aria-labelledby="options-xt-tab">
<pre><code>var client = new XTRestClient(opts =>
{
opts.RequestTimeout = TimeSpan.FromSeconds(30);
});</code></pre>
@@ -2834,6 +2967,9 @@ builder.Services.AddWhiteBit(builder.Configuration.GetSection("WhiteBit"));</cod
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-whitebit-tab" data-toggle="tab" href="#options-default-whitebit" role="tab" aria-controls="options-default-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="options-xt-tab" data-toggle="tab" href="#options-default-xt" role="tab" aria-controls="options-default-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="options-default-binance" role="tabpanel" aria-labelledby="options-binance-tab">
@@ -2955,6 +3091,13 @@ var client = new OKXRestClient();</code></pre>
});
var client = new WhiteBitRestClient();</code></pre>
</div>
<div class="tab-pane fade" id="options-default-xt" role="tabpanel" aria-labelledby="options-xt-tab">
<pre><code>XTRestClient.SetDefaultOptions(options =>
{
options.RequestTimeout = TimeSpan.FromSeconds(30);
});
var client = new XTRestClient();</code></pre>
</div>
</div>
</div>
@@ -3189,6 +3332,9 @@ var client = new WhiteBitRestClient();</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="book-whitebit-tab" data-toggle="tab" href="#book-whitebit" role="tab" aria-controls="book-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="book-xt-tab" data-toggle="tab" href="#book-xt" role="tab" aria-controls="book-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="book-cryptoclients" role="tabpanel" aria-labelledby="book-cryptoclients-tab">
@@ -3409,6 +3555,19 @@ if (!startResult.Success)
}
// 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-xt" role="tabpanel" aria-labelledby="book-xt-tab">
<pre><code>var book = new XTSymbolOrderBook("eth_usdt");
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()
await book.StopAsync();
</code></pre>
@@ -3582,6 +3741,9 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-whitebit-tab" data-toggle="tab" href="#tracker-whitebit" role="tab" aria-controls="tracker-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="tracker-xt-tab" data-toggle="tab" href="#tracker-xt" role="tab" aria-controls="tracker-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="tracker-cryptoclients" role="tabpanel" aria-labelledby="tracker-cryptoclients-tab">
@@ -3921,6 +4083,26 @@ if (!startResult.Success)
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
</code></pre>
</div>
<div class="tab-pane fade" id="tracker-xt" role="tabpanel" aria-labelledby="tracker-xt-tab">
<pre><code>// Either create a new factory or inject the IXTTrackerFactory interface
var factory = new XTTrackerFactory();
var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT");
// Create a tracker for ETH/USDT keeping track of trades in the last 5 minutes
var tracker = factory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5));
var startResult = await tracker.StartAsync();
if (!startResult.Success)
{
// Handle error, error info available in startResult.Error
}
// Tracker has successfully started
// Note that it might not be fully synced yet, check tracker.Status for this.
// Once no longer needed you can stop the live sync functionality by calling StopAsync()
await tracker.StopAsync();
</code></pre>
@@ -4274,6 +4456,9 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
<li class="nav-item" role="presentation">
<a class="nav-link" id="limit-whitebit-tab" data-toggle="tab" href="#limit-whitebit" role="tab" aria-controls="limit-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="limit-xt-tab" data-toggle="tab" href="#limit-xt" role="tab" aria-controls="limit-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="limit-cc" role="tabpanel" aria-labelledby="limit-cc-tab">
@@ -4474,6 +4659,20 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options
<p>To be notified of when a rate limit is hit the static <code>WhiteBitExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
<pre><code>WhiteBitExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
</code></pre>
</div>
<div class="tab-pane fade" id="limit-xt" role="tabpanel" aria-labelledby="limit-xt-tab">
<pre><code>services.AddXT(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>XTExchange.RateLimiter</code> exposes an event which triggers when a rate limit is reached</p>
<pre><code>XTExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
</code></pre>
</div>
</div>
@@ -4576,6 +4775,9 @@ var responseSource = result.DataSource;</code></pre>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-symbols-whitebit-tab" data-toggle="tab" href="#example-symbols-whitebit" role="tab" aria-controls="example-symbols-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-symbols-xt-tab" data-toggle="tab" href="#example-symbols-xt" role="tab" aria-controls="example-symbols-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="example-symbols-general" role="tabpanel" aria-labelledby="example-symbols-general-tab">
@@ -4637,6 +4839,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
<div class="tab-pane fade" id="example-symbols-whitebit" role="tabpanel" aria-labelledby="example-symbols-whitebit-tab">
<pre><code>await whitebitClient.V4Api.ExchangeData.GetSymbolsAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-symbols-xt" role="tabpanel" aria-labelledby="example-symbols-xt-tab">
<pre><code>await xtClient.SpotApi.ExchangeData.GetSymbolsAsync();</code></pre>
</div>
</div>
</div>
</div>
@@ -4706,6 +4911,9 @@ await exchangeRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest(), ["Binance"
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-ticker-whitebit-tab" data-toggle="tab" href="#example-ticker-whitebit" role="tab" aria-controls="example-ticker-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-ticker-xt-tab" data-toggle="tab" href="#example-ticker-xt" role="tab" aria-controls="example-ticker-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="example-ticker-general" role="tabpanel" aria-labelledby="example-ticker-general-tab">
@@ -4770,6 +4978,9 @@ await coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");</
<pre><code>// WhiteBit API doesn't offer a symbol filter, so we have to filter client side
var tickersResult = await whitebitClient.V4Api.ExchangeData.GetTickersAsync();
var ticker = tickersResult.Data.Single(x => x.Symbol == "BTC_USDT");</code></pre>
</div>
<div class="tab-pane fade" id="example-ticker-xt" role="tabpanel" aria-labelledby="example-ticker-xt-tab">
<pre><code>await xtClient.SpotApi.ExchangeData.GetTickersAsync("btc-usdt");</code></pre>
</div>
</div>
</div>
@@ -4840,6 +5051,9 @@ var ticker = tickersResult.Data.Single(x => x.Symbol == "BTC_USDT");</code></pre
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-balances-whitebit-tab" data-toggle="tab" href="#example-balances-whitebit" role="tab" aria-controls="example-balances-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-balances-xt-tab" data-toggle="tab" href="#example-balances-xt" role="tab" aria-controls="example-balances-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="example-balances-general" role="tabpanel" aria-labelledby="example-balances-general-tab">
@@ -4905,6 +5119,9 @@ var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
<div class="tab-pane fade" id="example-balances-whitebit" role="tabpanel" aria-labelledby="example-balances-whitebit-tab">
<pre><code>await whitebitClient.V4Api.Account.GetSpotBalancesAsync();</code></pre>
</div>
<div class="tab-pane fade" id="example-balances-xt" role="tabpanel" aria-labelledby="example-balances-xt-tab">
<pre><code>await xtClient.SpotApi.Account.GetBalancesAsync();</code></pre>
</div>
</div>
</div>
</div>
@@ -4972,7 +5189,10 @@ var result = await htxClient.SpotApi.Account.GetBalancesAsync();</code></pre>
<a class="nav-link" id="example-place-okx-tab" data-toggle="tab" href="#example-place-okx" role="tab" aria-controls="example-place-okx" aria-selected="false">OKX</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-place-okx-tab" data-toggle="tab" href="#example-place-okx" role="tab" aria-controls="example-place-okx" aria-selected="false">WhiteBit</a>
<a class="nav-link" id="example-place-whitebit-tab" data-toggle="tab" href="#example-place-whitebit" role="tab" aria-controls="example-place-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-place-xt-tab" data-toggle="tab" href="#example-place-xt" role="tab" aria-controls="example-place-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
@@ -5037,6 +5257,9 @@ var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSD
<div class="tab-pane fade" id="example-place-whitebit" role="tabpanel" aria-labelledby="example-place-whitebit-tab">
<pre><code>await whitebitClient.V4Api.Trading.PlaceSpotOrderAsync("BTC_USDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, price: 50000);</code></pre>
</div>
<div class="tab-pane fade" id="example-place-xt" role="tabpanel" aria-labelledby="example-place-xt-tab">
<pre><code>await xtClient.SpotApi.Trading.PlaceOrderAsync("eth_usdt", OrderSide.Buy, OrderType.Limit, TimeInForce.GoodTillCanceled, BusinessType.Spot, 0.1m, price: 50000);</code></pre>
</div>
</div>
</div>
</div>
@@ -5106,6 +5329,9 @@ var result = await htxClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCUSD
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-ticker-whitebit-tab" data-toggle="tab" href="#example-stream-ticker-whitebit" role="tab" aria-controls="example-stream-ticker-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-ticker-xt-tab" data-toggle="tab" href="#example-stream-ticker-xt" role="tab" aria-controls="example-stream-ticker-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="example-stream-ticker-cc" role="tabpanel" aria-labelledby="example-stream-ticker-cc-tab">
@@ -5206,6 +5432,12 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
<pre><code>await whitebitSocketClient.V4Api.SubscribeToTickerUpdatesAsync("ETH_USDT", data => {
// Handle update
});
</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-ticker-xt" role="tabpanel" aria-labelledby="example-stream-ticker-xt-tab">
<pre><code>await xtSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("eth_usdt", data => {
// Handle update
});
</code></pre>
</div>
</div>
@@ -5277,6 +5509,9 @@ await exchangeSocketClient.SubscribeToTickerUpdatesAsync(new SubscribeTickerRequ
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-order-whitebit-tab" data-toggle="tab" href="#example-stream-order-whitebit" role="tab" aria-controls="example-stream-order-whitebit" aria-selected="false">WhiteBit</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" id="example-stream-order-xt-tab" data-toggle="tab" href="#example-stream-order-xt" role="tab" aria-controls="example-stream-order-xt" aria-selected="false">XT</a>
</li>
</ul>
<div class="tab-content my-3" id="myTabContent">
<div class="tab-pane fade show active" id="example-stream-order-cc" role="tabpanel" aria-labelledby="example-stream-order-cc-tab">
@@ -5432,6 +5667,26 @@ _ = Task.Run(async () => {
await whitebitSocketClient.V4Api.SubscribeToOpenOrderUpdatesAsync(["ETH_USDT", "BTC_USDT"], data => {
// Handle update
});
</code></pre>
</div>
<div class="tab-pane fade" id="example-stream-order-xt" role="tabpanel" aria-labelledby="example-stream-order-xt-tab">
<pre><code>// Retrieve the token
var listenKey = await xtRestClient.SpotApi.Account.GetWebsocketTokenAsync();
// Subscribe using the key
await xtSocketClient.SpotApi.SubscribeToBalanceUpdatesAsync(listenKey.Data, data => {
// Handle update
});
// The listen key will stay valid for 48 hours, after this no updates will be send anymore
// To extend the life time of the token it is recommended to call the GetWebsocketTokenAsync method at a set interval which will extend the lifetime
_ = Task.Run(async () => {
while (true)
{
await Task.Delay(Timespan.FromHours(4));
await xtRestClient.SpotApi.Account.GetWebsocketTokenAsync();
}
});
</code></pre>
</div>
</div>