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

Compare commits

...

21 Commits

Author SHA1 Message Date
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
Jkorf 62c9769c72 Updated to version 8.4.0 2024-11-28 14:24:00 +01:00
Jkorf 92d7bc1e2e Added GetFeesAsync Shared REST client support, Added TimePeriodFilterSupport and MaxLimit properties to PaginatedEndpointOptions 2024-11-28 14:18:09 +01:00
Jkorf 99e4f96f63 Updated some testing code 2024-11-27 13:07:26 +01:00
Jkorf 94d8afe149 Updated package dependency versions 2024-11-27 13:07:05 +01:00
Jkorf 90ad59c63a Added comma split enum string json converter 2024-11-22 16:20:09 +01:00
Jkorf c2273edfaa Added library options class 2024-11-20 09:52:38 +01:00
Jkorf 236283f4dd Update example-config.json 2024-11-19 14:53:58 +01:00
27 changed files with 750 additions and 55 deletions
@@ -6,7 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"></PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"></PackageReference>
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.2.2"></PackageReference>
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"></PackageReference>
@@ -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));
}
}
}
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace CryptoExchange.Net.Converters.SystemTextJson
{
/// <summary>
/// Converter for comma seperated enum values
/// </summary>
public class CommaSplitEnumConverter<T> : JsonConverter<IEnumerable<T>> where T : Enum
{
/// <inheritdoc />
public override IEnumerable<T>? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return (reader.GetString()?.Split(',').Select(x => EnumConverter.ParseString<T>(x)).ToArray() ?? new T[0])!;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, IEnumerable<T> value, JsonSerializerOptions options)
{
writer.WriteStringValue(string.Join(",", value.Select(x => EnumConverter.GetString(x))));
}
}
}
@@ -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)
{
+10 -10
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>8.3.0</PackageVersion>
<AssemblyVersion>8.3.0</AssemblyVersion>
<FileVersion>8.3.0</FileVersion>
<PackageVersion>8.4.5</PackageVersion>
<AssemblyVersion>8.4.5</AssemblyVersion>
<FileVersion>8.4.5</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>
@@ -48,17 +48,17 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0">
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.0" />
<PackageReference Include="System.Text.Json" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
</ItemGroup>
</Project>
+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;
}
}
}
@@ -0,0 +1,58 @@
using CryptoExchange.Net.Authentication;
using Microsoft.Extensions.DependencyInjection;
namespace CryptoExchange.Net.Objects.Options
{
/// <summary>
/// Library options
/// </summary>
/// <typeparam name="TRestOptions"></typeparam>
/// <typeparam name="TSocketOptions"></typeparam>
/// <typeparam name="TApiCredentials"></typeparam>
/// <typeparam name="TEnvironment"></typeparam>
public class LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
where TRestOptions: RestExchangeOptions, new()
where TSocketOptions: SocketExchangeOptions, new()
where TApiCredentials: ApiCredentials
where TEnvironment: TradeEnvironment
{
/// <summary>
/// Rest client options
/// </summary>
public TRestOptions Rest { get; set; } = new TRestOptions();
/// <summary>
/// Socket client options
/// </summary>
public TSocketOptions Socket { get; set; } = new TSocketOptions();
/// <summary>
/// Trade environment. Contains info about URL's to use to connect to the API.
/// </summary>
public TEnvironment? Environment { get; set; }
/// <summary>
/// The api credentials used for signing requests.
/// </summary>
public TApiCredentials? ApiCredentials { get; set; }
/// <summary>
/// The DI service lifetime for the socket client
/// </summary>
public ServiceLifetime? SocketClientLifeTime { get; set; }
/// <summary>
/// Copy values from these options to the target options
/// </summary>
public T Set<T>(T targetOptions) where T: LibraryOptions<TRestOptions, TSocketOptions, TApiCredentials, TEnvironment>
{
targetOptions.ApiCredentials = ApiCredentials;
targetOptions.Environment = Environment;
targetOptions.SocketClientLifeTime = SocketClientLifeTime;
targetOptions.Rest = Rest.Set(targetOptions.Rest);
targetOptions.Socket = Socket.Set(targetOptions.Socket);
return targetOptions;
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Client for requesting user trading fees
/// </summary>
public interface IFeeRestClient : ISharedClient
{
/// <summary>
/// Fee request options
/// </summary>
EndpointOptions<GetFeeRequest> GetFeeOptions { get; }
/// <summary>
/// Get trading fees for a symbol
/// </summary>
/// <param name="request">Request info</param>
/// <param name="ct">Cancellation token</param>
Task<ExchangeWebResult<SharedFee>> GetFeesAsync(GetFeeRequest request, CancellationToken ct = default);
}
}
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetClosedOrdersOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
public GetClosedOrdersOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true)
{
TimeFilterSupported = timeFilterSupported;
}
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetDepositsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
public GetDepositsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true)
{
TimeFilterSupported = timeFilterSupported;
}
@@ -8,7 +8,7 @@
/// <summary>
/// ctor
/// </summary>
public GetFundingRateHistoryOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
public GetFundingRateHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication)
{
}
}
@@ -20,10 +20,6 @@ namespace CryptoExchange.Net.SharedApis
/// </summary>
public int? MaxTotalDataPoints { get; set; }
/// <summary>
/// Max number of data points which can be requested in a single request
/// </summary>
public int? MaxRequestDataPoints { get; set; }
/// <summary>
/// The max age of the data that can be requested
/// </summary>
public TimeSpan? MaxAge { get; set; }
@@ -31,14 +27,13 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetKlinesOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
public GetKlinesOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication)
{
SupportIntervals = new[]
{
SharedKlineInterval.FiveMinutes,
SharedKlineInterval.FifteenMinutes,
SharedKlineInterval.OneHour,
SharedKlineInterval.FifteenMinutes,
SharedKlineInterval.OneDay,
SharedKlineInterval.OneWeek,
SharedKlineInterval.OneMonth
@@ -48,7 +43,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetKlinesOptions(SharedPaginationSupport paginationType, bool needsAuthentication, params SharedKlineInterval[] intervals) : base(paginationType, needsAuthentication)
public GetKlinesOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication)
{
SupportIntervals = intervals;
}
@@ -69,8 +64,8 @@ namespace CryptoExchange.Net.SharedApis
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
return new ArgumentError($"Only the most recent {MaxAge} klines are available");
if (MaxRequestDataPoints.HasValue && request.Limit > MaxRequestDataPoints.Value)
return new ArgumentError($"Only {MaxRequestDataPoints} klines can be retrieved per request");
if (request.Limit > MaxLimit)
return new ArgumentError($"Only {MaxLimit} klines can be retrieved per request");
if (MaxTotalDataPoints.HasValue)
{
@@ -96,8 +91,6 @@ namespace CryptoExchange.Net.SharedApis
sb.AppendLine($"Max age of data: {MaxAge}");
if (MaxTotalDataPoints != null)
sb.AppendLine($"Max total data points available: {MaxTotalDataPoints}");
if (MaxRequestDataPoints != null)
sb.AppendLine($"Max data points per request: {MaxRequestDataPoints}");
return sb.ToString();
}
}
@@ -8,7 +8,7 @@
/// <summary>
/// ctor
/// </summary>
public GetPositionHistoryOptions(SharedPaginationSupport paginationType) : base(paginationType, true)
public GetPositionHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true)
{
}
}
@@ -17,7 +17,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetTradeHistoryOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
public GetTradeHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication)
{
}
@@ -16,7 +16,7 @@ namespace CryptoExchange.Net.SharedApis
/// <summary>
/// ctor
/// </summary>
public GetWithdrawalsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
public GetWithdrawalsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true)
{
TimeFilterSupported = timeFilterSupported;
}
@@ -1,4 +1,5 @@
using System.Text;
using CryptoExchange.Net.Objects;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
@@ -13,12 +14,24 @@ namespace CryptoExchange.Net.SharedApis
/// </summary>
public SharedPaginationSupport PaginationSupport { get; }
/// <summary>
/// Whether filtering based on start/end time is supported
/// </summary>
public bool TimePeriodFilterSupport { get; }
/// <summary>
/// Max amount of results that can be requested
/// </summary>
public int MaxLimit { get; set; }
/// <summary>
/// ctor
/// </summary>
public PaginatedEndpointOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(needsAuthentication)
public PaginatedEndpointOptions(SharedPaginationSupport paginationType, bool timePeriodSupport, int maxLimit, bool needsAuthentication) : base(needsAuthentication)
{
PaginationSupport = paginationType;
TimePeriodFilterSupport = timePeriodSupport;
MaxLimit = maxLimit;
}
/// <inheritdoc />
@@ -26,6 +39,8 @@ namespace CryptoExchange.Net.SharedApis
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Pagination type: {PaginationSupport}");
sb.AppendLine($"Time period filter support: {TimePeriodFilterSupport}");
sb.AppendLine($"Max limit: {MaxLimit}");
return sb.ToString();
}
}
@@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Request to retrieve trading fees
/// </summary>
public record GetFeeRequest : SharedSymbolRequest
{
/// <summary>
/// ctor
/// </summary>
/// <param name="symbol">Symbol to retrieve fees for</param>
/// <param name="exchangeParameters">Exchange specific parameters</param>
public GetFeeRequest(SharedSymbol symbol, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters)
{
}
}
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Trading fee info
/// </summary>
public record SharedFee
{
/// <summary>
/// Taker fee percentage
/// </summary>
public decimal TakerFee { get; set; }
/// <summary>
/// Maker fee percentage
/// </summary>
public decimal MakerFee { get; set; }
/// <summary>
/// ctor
/// </summary>
public SharedFee(decimal makerFee, decimal takerFee)
{
MakerFee = makerFee;
TakerFee = takerFee;
}
}
}
@@ -224,6 +224,9 @@ namespace CryptoExchange.Net.Testing.Comparers
else if (propertyValue.GetType().GetInterfaces().Contains(typeof(IEnumerable))
&& propertyValue.GetType() != typeof(string))
{
if (propValue.Type != JTokenType.Array)
return;
var jObjs = (JArray)propValue;
var list = (IEnumerable)propertyValue;
var enumerator = list.GetEnumerator();
@@ -372,7 +375,8 @@ namespace CryptoExchange.Net.Testing.Comparers
}
else if (objectValue is DateTime time)
{
if (time != DateTimeConverter.ParseFromString(jsonValue.Value<string>()!))
var jsonStr = jsonValue.Value<string>()!;
if (!string.IsNullOrEmpty(jsonStr) && time != DateTimeConverter.ParseFromString(jsonStr))
throw new Exception($"{method}: {property} not equal: {jsonValue.Value<string>()} vs {time}");
}
else if (objectValue is bool bl)
+12 -9
View File
@@ -173,17 +173,20 @@ namespace CryptoExchange.Net.Testing
foreach (var clientInterface in clientInterfaces)
{
var implementation = assembly.GetTypes().Single(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
int methods = 0;
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && t != clientInterface);
foreach (var implementation in implementations)
{
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}");
methods++;
}
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}");
methods++;
}
Debug.WriteLine($"{clientInterface.Name} {methods} methods validated");
Debug.WriteLine($"{clientInterface.Name} {methods} methods validated");
}
}
}
}
@@ -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>
@@ -229,7 +229,7 @@ namespace CryptoExchange.Net.Trackers.Klines
if (_restClient.GetKlinesOptions.MaxAge != null && DateTime.UtcNow.Add(-_restClient.GetKlinesOptions.MaxAge.Value) > startTime)
startTime = DateTime.UtcNow.Add(-_restClient.GetKlinesOptions.MaxAge.Value);
var limit = Math.Min(_restClient.GetKlinesOptions.MaxRequestDataPoints ?? _restClient.GetKlinesOptions.MaxTotalDataPoints ?? 100, Limit ?? 100);
var limit = Math.Min(_restClient.GetKlinesOptions.MaxLimit, Limit ?? 100);
var request = new GetKlinesRequest(Symbol, _interval, startTime, DateTime.UtcNow, limit: limit);
var data = new List<SharedKline>();
+9 -3
View File
@@ -1,13 +1,18 @@
{
// Options section, select this section during DI registration using Configuration.GetSection("ExchangeApiOptions")
"ExchangeApiOptions": {
// API credentials for both REST and Websocket client
"ApiCredentials": {
"Key": "APIKEY",
"Secret": "SECRET"
"Secret": "SECRET",
"PassPhrase": "Phrase" // Optional passphrase for exchanges which need it
},
// Set the environment by name
"Environment": {
"name": "live"
},
"Rest":{
// REST client options
"Rest": {
"RequestTimeout": "00:00:20",
"CachingEnabled": true,
"OutputOriginalData": true,
@@ -18,7 +23,8 @@
"Password": "Pass"
}
},
"Socket":{
// Socket client options
"Socket": {
"RequestTimeout": "00:00:05",
"SocketSubscriptionsCombineTarget": 15
}
+41
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,31 @@ 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.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
* Added CommaSplitEnumConverter System.Text.Json converter
* Added TimePeriodFilterSupport and MaxLimit properties to PaginatedEndpointOptions
* Updated package dependency versions
* Version 8.3.0 - 19 Nov 2024
* Added support for IOptions injection, allowing options to be read from IConfiguration
* Added handling of Infinity values in decimal converter
+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>