mirror of
				https://github.com/JKorf/CryptoExchange.Net
				synced 2025-10-31 10:27:48 +00:00 
			
		
		
		
	Compare commits
	
		
			22 Commits
		
	
	
		
			CryptoExch
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 995cd3d84c | ||
|  | 919cdf0075 | ||
|  | d181c9cfc1 | ||
|  | 5943142c44 | ||
|  | dbc430e838 | ||
|  | 7413d03d31 | ||
|  | dd60067684 | ||
|  | 04e4ddf525 | ||
|  | 99bf6d7c75 | ||
|  | 99a203933c | ||
|  | b43d2a2040 | ||
|  | ba9c406def | ||
|  | f5f4d50cc9 | ||
|  | f87506b490 | ||
|  | f6f9a53ce5 | ||
|  | 61130ef54e | ||
|  | e8bcbd59be | ||
|  | d433ff7475 | ||
|  | 71957037d0 | ||
|  | bcdcdbbd4e | ||
|  | 1ece13f5bc | ||
|  | da70ba6ec7 | 
| @ -6,9 +6,9 @@ | ||||
|     <PackageId>CryptoExchange.Net.Protobuf</PackageId> | ||||
|     <Authors>JKorf</Authors> | ||||
|     <Description>Protobuf support for CryptoExchange.Net</Description> | ||||
|     <PackageVersion>9.8.0</PackageVersion> | ||||
|     <AssemblyVersion>9.8.0</AssemblyVersion> | ||||
|     <FileVersion>9.8.0</FileVersion> | ||||
|     <PackageVersion>9.11.1</PackageVersion> | ||||
|     <AssemblyVersion>9.11.1</AssemblyVersion> | ||||
|     <FileVersion>9.11.1</FileVersion> | ||||
|     <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> | ||||
|     <PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags> | ||||
|     <RepositoryType>git</RepositoryType> | ||||
| @ -41,7 +41,7 @@ | ||||
|     <DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="CryptoExchange.Net" Version="9.8.0" /> | ||||
|     <PackageReference Include="CryptoExchange.Net" Version="9.11.0" /> | ||||
|     <PackageReference Include="protobuf-net" Version="3.2.56" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @ -5,6 +5,18 @@ | ||||
| Protobuf support for CryptoExchange.Net. | ||||
| 
 | ||||
| ## Release notes | ||||
| * Version 9.11.1 - 30 Oct 2025 | ||||
|     * Updated CryptoExchange.Net version to 9.11.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ | ||||
| 
 | ||||
| * Version 9.11.0 - 30 Oct 2025 | ||||
|     * Updated CryptoExchange.Net version to 9.11.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ | ||||
| 
 | ||||
| * Version 9.10.0 - 15 Oct 2025 | ||||
|     * Updated CryptoExchange.Net version to 9.10.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ | ||||
| 
 | ||||
| * Version 9.9.0 - 06 Oct 2025 | ||||
|     * Updated CryptoExchange.Net version to 9.9.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ | ||||
| 
 | ||||
| * Version 9.8.0 - 30 Sep 2025 | ||||
|     * Updated CryptoExchange.Net version to 9.8.0, see https://github.com/JKorf/CryptoExchange.Net/releases/ | ||||
| 
 | ||||
|  | ||||
| @ -6,10 +6,10 @@ | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1"></PackageReference> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0"></PackageReference> | ||||
|     <PackageReference Include="Moq" Version="4.20.72" /> | ||||
|     <PackageReference Include="NUnit" Version="4.3.2"></PackageReference> | ||||
|     <PackageReference Include="NUnit3TestAdapter" Version="5.0.0"></PackageReference> | ||||
|     <PackageReference Include="NUnit" Version="4.4.0"></PackageReference> | ||||
|     <PackageReference Include="NUnit3TestAdapter" Version="5.2.0"></PackageReference> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|  | ||||
| @ -202,7 +202,7 @@ namespace CryptoExchange.Net.UnitTests | ||||
|             await sub; | ||||
| 
 | ||||
|             // assert | ||||
|             ClassicAssert.IsFalse(client.SubClient.TestSubscription.Confirmed); | ||||
|             ClassicAssert.IsTrue(client.SubClient.TestSubscription.Status != SubscriptionStatus.Subscribed); | ||||
|         } | ||||
| 
 | ||||
|         [TestCase()] | ||||
| @ -225,7 +225,7 @@ namespace CryptoExchange.Net.UnitTests | ||||
|             await sub; | ||||
| 
 | ||||
|             // assert | ||||
|             Assert.That(client.SubClient.TestSubscription.Confirmed); | ||||
|             Assert.That(client.SubClient.TestSubscription.Status == SubscriptionStatus.Subscribed); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -21,6 +21,8 @@ namespace CryptoExchange.Net.Clients | ||||
|         protected BaseRestClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name) | ||||
|         { | ||||
|             _logger = loggerFactory?.CreateLogger(name + ".RestClient") ?? NullLoggerFactory.Instance.CreateLogger(name); | ||||
| 
 | ||||
|             LibraryHelpers.StaticLogger = loggerFactory?.CreateLogger(name); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -40,6 +40,8 @@ namespace CryptoExchange.Net.Clients | ||||
|         protected BaseSocketClient(ILoggerFactory? loggerFactory, string name) : base(loggerFactory, name) | ||||
|         { | ||||
|             _logger = loggerFactory?.CreateLogger(name + ".SocketClient") ?? NullLoggerFactory.Instance.CreateLogger(name); | ||||
| 
 | ||||
|             LibraryHelpers.StaticLogger = loggerFactory?.CreateLogger(name); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|  | ||||
| @ -269,6 +269,7 @@ namespace CryptoExchange.Net.Clients | ||||
|                 return new CallResult<UpdateSubscription>(new ServerError(new ErrorInfo(ErrorType.WebsocketPaused, "Socket is paused"))); | ||||
|             } | ||||
| 
 | ||||
|             subscription.Status = SubscriptionStatus.Subscribing; | ||||
|             var waitEvent = new AsyncResetEvent(false); | ||||
|             var subQuery = subscription.CreateSubscriptionQuery(socketConnection); | ||||
|             if (subQuery != null) | ||||
| @ -279,7 +280,7 @@ namespace CryptoExchange.Net.Clients | ||||
|                 { | ||||
|                     waitEvent?.Set(); | ||||
|                     var isTimeout = subResult.Error is CancellationRequestedError; | ||||
|                     if (isTimeout && subscription.Confirmed) | ||||
|                     if (isTimeout && subscription.Status == SubscriptionStatus.Subscribed) | ||||
|                     { | ||||
|                         // No response received, but the subscription did receive updates. We'll assume success | ||||
|                     } | ||||
| @ -287,6 +288,7 @@ namespace CryptoExchange.Net.Clients | ||||
|                     { | ||||
|                         _logger.FailedToSubscribe(socketConnection.SocketId, subResult.Error?.ToString()); | ||||
|                         // If this was a timeout we still need to send an unsubscribe to prevent messages coming in later | ||||
|                         subscription.Status = SubscriptionStatus.Pending; | ||||
|                         await socketConnection.CloseAsync(subscription).ConfigureAwait(false); | ||||
|                         return new CallResult<UpdateSubscription>(subResult.Error!); | ||||
|                     } | ||||
| @ -295,7 +297,7 @@ namespace CryptoExchange.Net.Clients | ||||
|                 subscription.HandleSubQueryResponse(subQuery.Response!); | ||||
|             } | ||||
| 
 | ||||
|             subscription.Confirmed = true; | ||||
|             subscription.Status = SubscriptionStatus.Subscribed; | ||||
|             if (ct != default) | ||||
|             { | ||||
|                 subscription.CancellationTokenRegistration = ct.Register(async () => | ||||
| @ -847,7 +849,7 @@ namespace CryptoExchange.Net.Clients | ||||
|                         cs.SubscriptionStates.ForEach(subState => | ||||
|                         { | ||||
|                             sb.AppendLine($"\t\t\tId: {subState.Id}"); | ||||
|                             sb.AppendLine($"\t\t\tConfirmed: {subState.Confirmed}"); | ||||
|                             sb.AppendLine($"\t\t\tStatus: {subState.Status}"); | ||||
|                             sb.AppendLine($"\t\t\tInvocations: {subState.Invocations}"); | ||||
|                             sb.AppendLine($"\t\t\tIdentifiers: [{subState.ListenMatcher.ToString()}]"); | ||||
|                         }); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Runtime.Serialization; | ||||
| using System.Text.Json; | ||||
| @ -47,7 +48,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                 if (string.IsNullOrEmpty(value)) | ||||
|                 { | ||||
|                     if (typeToConvert == typeof(bool)) | ||||
|                         Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null bool value, but property type is not a nullable bool"); | ||||
|                         LibraryHelpers.StaticLogger?.LogWarning("Received null bool value, but property type is not a nullable bool"); | ||||
|                     return default; | ||||
|                 } | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using System.Globalization; | ||||
| @ -14,8 +15,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|     { | ||||
|         private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); | ||||
|         private const long _ticksPerSecond = TimeSpan.TicksPerMillisecond * 1000; | ||||
|         private const double _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000d; | ||||
|         private const double _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000d / 1000; | ||||
|         private const decimal _ticksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000m; | ||||
|         private const decimal _ticksPerNanosecond = TimeSpan.TicksPerMillisecond / 1000m / 1000; | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|         public override bool CanConvert(Type typeToConvert) | ||||
| @ -39,17 +40,17 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                 if (reader.TokenType == JsonTokenType.Null) | ||||
|                 { | ||||
|                     if (typeToConvert == typeof(DateTime)) | ||||
|                         Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | DateTime value of null, but property is not nullable"); | ||||
|                         LibraryHelpers.StaticLogger?.LogWarning("DateTime value of null, but property is not nullable"); | ||||
|                     return default; | ||||
|                 } | ||||
| 
 | ||||
|                 if (reader.TokenType is JsonTokenType.Number) | ||||
|                 { | ||||
|                     var longValue = reader.GetDouble(); | ||||
|                     if (longValue == 0 || longValue < 0) | ||||
|                     var decValue = reader.GetDecimal(); | ||||
|                     if (decValue == 0 || decValue < 0) | ||||
|                         return default; | ||||
| 
 | ||||
|                     return ParseFromDouble(longValue); | ||||
|                     return ParseFromDecimal(decValue); | ||||
|                 } | ||||
|                 else if (reader.TokenType is JsonTokenType.String) | ||||
|                 { | ||||
| @ -57,7 +58,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     if (string.IsNullOrWhiteSpace(stringValue) | ||||
|                         || stringValue == "-1" | ||||
|                         || stringValue == "0001-01-01T00:00:00Z" | ||||
|                         || double.TryParse(stringValue, out var doubleVal) && doubleVal == 0) | ||||
|                         || decimal.TryParse(stringValue, out var decVal) && decVal == 0) | ||||
|                     { | ||||
|                         return default; | ||||
|                     } | ||||
| @ -88,20 +89,24 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Parse a long value to datetime | ||||
|         /// Parse a double value to datetime | ||||
|         /// </summary> | ||||
|         /// <param name="longValue"></param> | ||||
|         /// <returns></returns> | ||||
|         public static DateTime ParseFromDouble(double longValue) | ||||
|         { | ||||
|             if (longValue < 19999999999) | ||||
|                 return ConvertFromSeconds(longValue); | ||||
|             if (longValue < 19999999999999) | ||||
|                 return ConvertFromMilliseconds(longValue); | ||||
|             if (longValue < 19999999999999999) | ||||
|                 return ConvertFromMicroseconds(longValue); | ||||
|         public static DateTime ParseFromDouble(double value) | ||||
|             => ParseFromDecimal((decimal)value); | ||||
| 
 | ||||
|             return ConvertFromNanoseconds(longValue); | ||||
|         /// <summary> | ||||
|         /// Parse a decimal value to datetime | ||||
|         /// </summary> | ||||
|         public static DateTime ParseFromDecimal(decimal value) | ||||
|         { | ||||
|             if (value < 19999999999) | ||||
|                 return ConvertFromSeconds(value); | ||||
|             if (value < 19999999999999) | ||||
|                 return ConvertFromMilliseconds(value); | ||||
|             if (value < 19999999999999999) | ||||
|                 return ConvertFromMicroseconds(value); | ||||
| 
 | ||||
|             return ConvertFromNanoseconds(value); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
| @ -120,7 +125,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     || !int.TryParse(stringValue.Substring(8, 2), out var hour) | ||||
|                     || !int.TryParse(stringValue.Substring(10, 2), out var minute)) | ||||
|                 { | ||||
|                     Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); | ||||
|                     LibraryHelpers.StaticLogger?.LogWarning("Unknown DateTime format: " + stringValue); | ||||
|                     return default; | ||||
|                 } | ||||
|                 return new DateTime(year, month, day, hour, minute, 0, DateTimeKind.Utc); | ||||
| @ -133,7 +138,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     || !int.TryParse(stringValue.Substring(4, 2), out var month) | ||||
|                     || !int.TryParse(stringValue.Substring(6, 2), out var day)) | ||||
|                 { | ||||
|                     Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); | ||||
|                     LibraryHelpers.StaticLogger?.LogWarning("Unknown DateTime format: " + stringValue); | ||||
|                     return default; | ||||
|                 } | ||||
|                 return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc); | ||||
| @ -146,25 +151,25 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     || !int.TryParse(stringValue.Substring(2, 2), out var month) | ||||
|                     || !int.TryParse(stringValue.Substring(4, 2), out var day)) | ||||
|                 { | ||||
|                     Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); | ||||
|                     LibraryHelpers.StaticLogger?.LogWarning("Unknown DateTime format: " + stringValue); | ||||
|                     return default; | ||||
|                 } | ||||
|                 return new DateTime(year + 2000, month, day, 0, 0, 0, DateTimeKind.Utc); | ||||
|             } | ||||
| 
 | ||||
|             if (double.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var doubleValue)) | ||||
|             if (decimal.TryParse(stringValue, NumberStyles.Float, CultureInfo.InvariantCulture, out var decimalValue)) | ||||
|             { | ||||
|                 // Parse 1637745563.000 format | ||||
|                 if (doubleValue <= 0) | ||||
|                 if (decimalValue <= 0) | ||||
|                     return default; | ||||
|                 if (doubleValue < 19999999999) | ||||
|                     return ConvertFromSeconds(doubleValue); | ||||
|                 if (doubleValue < 19999999999999) | ||||
|                     return ConvertFromMilliseconds((long)doubleValue); | ||||
|                 if (doubleValue < 19999999999999999) | ||||
|                     return ConvertFromMicroseconds((long)doubleValue); | ||||
|                 if (decimalValue < 19999999999) | ||||
|                     return ConvertFromSeconds(decimalValue); | ||||
|                 if (decimalValue < 19999999999999) | ||||
|                     return ConvertFromMilliseconds(decimalValue); | ||||
|                 if (decimalValue < 19999999999999999) | ||||
|                     return ConvertFromMicroseconds(decimalValue); | ||||
| 
 | ||||
|                 return ConvertFromNanoseconds((long)doubleValue); | ||||
|                 return ConvertFromNanoseconds(decimalValue); | ||||
|             } | ||||
| 
 | ||||
|             if (stringValue.Length == 10) | ||||
| @ -175,7 +180,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     || !int.TryParse(values[1], out var month) | ||||
|                     || !int.TryParse(values[2], out var day)) | ||||
|                 { | ||||
|                     Trace.WriteLine("{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Unknown DateTime format: " + stringValue); | ||||
|                     LibraryHelpers.StaticLogger?.LogWarning("Unknown DateTime format: " + stringValue); | ||||
|                     return default; | ||||
|                 } | ||||
| 
 | ||||
| @ -188,54 +193,70 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|         /// <summary> | ||||
|         /// Convert a seconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         /// <param name="seconds"></param> | ||||
|         /// <returns></returns> | ||||
|         public static DateTime ConvertFromSeconds(double seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a milliseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         /// <param name="milliseconds"></param> | ||||
|         /// <returns></returns> | ||||
|         public static DateTime ConvertFromMilliseconds(double milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a microseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         /// <param name="microseconds"></param> | ||||
|         /// <returns></returns> | ||||
|         public static DateTime ConvertFromMicroseconds(double microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond)); | ||||
|         public static DateTime ConvertFromSeconds(decimal seconds) => _epoch.AddTicks((long)Math.Round(seconds * _ticksPerSecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         /// <param name="nanoseconds"></param> | ||||
|         /// <returns></returns> | ||||
|         public static DateTime ConvertFromNanoseconds(double nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond)); | ||||
|         public static DateTime ConvertFromSeconds(double seconds) => ConvertFromSeconds((decimal)seconds); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromSeconds(long seconds) => ConvertFromSeconds((decimal)seconds); | ||||
|         /// <summary> | ||||
|         /// Convert a milliseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMilliseconds(decimal milliseconds) => _epoch.AddTicks((long)Math.Round(milliseconds * TimeSpan.TicksPerMillisecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMilliseconds(double milliseconds) => ConvertFromMilliseconds((decimal)milliseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMilliseconds(long milliseconds) => ConvertFromMilliseconds((decimal)milliseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a microseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMicroseconds(decimal microseconds) => _epoch.AddTicks((long)Math.Round(microseconds * _ticksPerMicrosecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMicroseconds(double microseconds) => ConvertFromMicroseconds((decimal)microseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromMicroseconds(long microseconds) => ConvertFromMicroseconds((decimal)microseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromNanoseconds(decimal nanoseconds) => _epoch.AddTicks((long)Math.Round(nanoseconds * _ticksPerNanosecond)); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromNanoseconds(double nanoseconds) => ConvertFromNanoseconds((decimal)nanoseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a nanoseconds since epoch (01-01-1970) value to DateTime | ||||
|         /// </summary> | ||||
|         public static DateTime ConvertFromNanoseconds(long nanoseconds) => ConvertFromNanoseconds((decimal)nanoseconds); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Convert a DateTime value to seconds since epoch (01-01-1970) value | ||||
|         /// </summary> | ||||
|         /// <param name="time"></param> | ||||
|         /// <returns></returns> | ||||
|         [return: NotNullIfNotNull("time")] | ||||
|         public static long? ConvertToSeconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalSeconds); | ||||
|         /// <summary> | ||||
|         /// Convert a DateTime value to milliseconds since epoch (01-01-1970) value | ||||
|         /// </summary> | ||||
|         /// <param name="time"></param> | ||||
|         /// <returns></returns> | ||||
|         [return: NotNullIfNotNull("time")] | ||||
|         public static long? ConvertToMilliseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).TotalMilliseconds); | ||||
|         /// <summary> | ||||
|         /// Convert a DateTime value to microseconds since epoch (01-01-1970) value | ||||
|         /// </summary> | ||||
|         /// <param name="time"></param> | ||||
|         /// <returns></returns> | ||||
|         [return: NotNullIfNotNull("time")] | ||||
|         public static long? ConvertToMicroseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerMicrosecond); | ||||
|         /// <summary> | ||||
|         /// Convert a DateTime value to nanoseconds since epoch (01-01-1970) value | ||||
|         /// </summary> | ||||
|         /// <param name="time"></param> | ||||
|         /// <returns></returns> | ||||
|         [return: NotNullIfNotNull("time")] | ||||
|         public static long? ConvertToNanoseconds(DateTime? time) => time == null ? null : (long)Math.Round((time.Value - _epoch).Ticks / _ticksPerNanosecond); | ||||
|     } | ||||
|  | ||||
| @ -1,4 +1,6 @@ | ||||
| using CryptoExchange.Net.Attributes; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Microsoft.Extensions.Logging.Abstractions; | ||||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| @ -79,7 +81,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|             } | ||||
|             public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|             { | ||||
|                 return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn); | ||||
|                 return _enumConverter.ReadNullable(ref reader, typeToConvert, options, out var isEmptyString); | ||||
|             } | ||||
| 
 | ||||
|             public override void Write(Utf8JsonWriter writer, T? value, JsonSerializerOptions options) | ||||
| @ -98,20 +100,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|         /// <inheritdoc /> | ||||
|         public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) | ||||
|         { | ||||
|             var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString, out var warn); | ||||
|             var t = ReadNullable(ref reader, typeToConvert, options, out var isEmptyString); | ||||
|             if (t == null) | ||||
|             { | ||||
|                 if (warn) | ||||
|                 if (isEmptyString && !_unknownValuesWarned.Contains(null)) | ||||
|                 { | ||||
|                     if (isEmptyString) | ||||
|                     { | ||||
|                         // We received an empty string and have no mapping for it, and the property isn't nullable | ||||
|                         Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo"); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received null enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo"); | ||||
|                     } | ||||
|                     // We received an empty string and have no mapping for it, and the property isn't nullable | ||||
|                     LibraryHelpers.StaticLogger?.LogWarning($"Received null or empty enum value, but property type is not a nullable enum. EnumType: {typeof(T).Name}. If you think {typeof(T).Name} should be nullable please open an issue on the Github repo"); | ||||
|                 } | ||||
| 
 | ||||
|                 return new T(); // return default value | ||||
| @ -122,10 +117,9 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString, out bool warn) | ||||
|         private T? ReadNullable(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options, out bool isEmptyString) | ||||
|         { | ||||
|             isEmptyString = false; | ||||
|             warn = false; | ||||
|             var enumType = typeof(T); | ||||
|             if (_mapping == null) | ||||
|                 _mapping = AddMapping(); | ||||
| @ -154,9 +148,8 @@ namespace CryptoExchange.Net.Converters.SystemTextJson | ||||
|                     // We received an enum value but weren't able to parse it. | ||||
|                     if (!_unknownValuesWarned.Contains(stringValue)) | ||||
|                     { | ||||
|                         warn = true; | ||||
|                         _unknownValuesWarned.Add(stringValue!); | ||||
|                         Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo"); | ||||
|                         LibraryHelpers.StaticLogger?.LogWarning($"Cannot map enum value. EnumType: {enumType.Name}, Value: {stringValue}, Known values: {string.Join(", ", _mapping.Select(m => m.Value))}. If you think {stringValue} should added please open an issue on the Github repo"); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|  | ||||
| @ -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>9.8.0</PackageVersion> | ||||
|     <AssemblyVersion>9.8.0</AssemblyVersion> | ||||
|     <FileVersion>9.8.0</FileVersion> | ||||
|     <PackageVersion>9.11.0</PackageVersion> | ||||
|     <AssemblyVersion>9.11.0</AssemblyVersion> | ||||
|     <FileVersion>9.11.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;CryptoExchange.Net</PackageTags> | ||||
|     <RepositoryType>git</RepositoryType> | ||||
| @ -51,11 +51,11 @@ | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" /> | ||||
|     <PackageReference Include="System.Text.Json" Version="9.0.6" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.10" /> | ||||
|     <PackageReference Include="System.Text.Json" Version="9.0.10" /> | ||||
|   </ItemGroup> | ||||
|   <ItemGroup Label="Transitive Client Packages"> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.6" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.6" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="9.0.10" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.10" /> | ||||
|   </ItemGroup> | ||||
| </Project> | ||||
| @ -399,7 +399,7 @@ namespace CryptoExchange.Net | ||||
|         /// <summary> | ||||
|         /// Whether the trading mode is linear | ||||
|         /// </summary> | ||||
|         public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear; | ||||
|         public static bool IsLinear(this TradingMode type) => type == TradingMode.PerpetualLinear || type == TradingMode.DeliveryLinear;         | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Whether the trading mode is inverse | ||||
| @ -416,6 +416,36 @@ namespace CryptoExchange.Net | ||||
|         /// </summary> | ||||
|         public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Whether the account type is a futures account | ||||
|         /// </summary> | ||||
|         public static bool IsFuturesAccount(this SharedAccountType type) => | ||||
|             type == SharedAccountType.PerpetualLinearFutures | ||||
|             || type == SharedAccountType.DeliveryLinearFutures | ||||
|             || type == SharedAccountType.PerpetualInverseFutures | ||||
|             || type == SharedAccountType.DeliveryInverseFutures; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Whether the account type is a margin account | ||||
|         /// </summary> | ||||
|         public static bool IsMarginAccount(this SharedAccountType type) => | ||||
|             type == SharedAccountType.CrossMargin | ||||
|             || type == SharedAccountType.IsolatedMargin; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Map a TradingMode value to a SharedAccountType enum value | ||||
|         /// </summary> | ||||
|         public static SharedAccountType ToAccountType(this TradingMode mode) | ||||
|         { | ||||
|             if (mode == TradingMode.Spot) return SharedAccountType.Spot; | ||||
|             if (mode == TradingMode.PerpetualLinear) return SharedAccountType.PerpetualLinearFutures; | ||||
|             if (mode == TradingMode.PerpetualInverse) return SharedAccountType.PerpetualInverseFutures; | ||||
|             if (mode == TradingMode.DeliveryInverse) return SharedAccountType.DeliveryInverseFutures; | ||||
|             if (mode == TradingMode.DeliveryLinear) return SharedAccountType.DeliveryLinearFutures; | ||||
| 
 | ||||
|             throw new ArgumentException(nameof(mode), "Unmapped trading mode"); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Register rest client interfaces | ||||
|         /// </summary> | ||||
| @ -445,6 +475,8 @@ namespace CryptoExchange.Net | ||||
|                 services.AddTransient(x => (IFeeRestClient)client(x)!); | ||||
|             if (typeof(IBookTickerRestClient).IsAssignableFrom(typeof(T))) | ||||
|                 services.AddTransient(x => (IBookTickerRestClient)client(x)!); | ||||
|             if (typeof(ITransferRestClient).IsAssignableFrom(typeof(T))) | ||||
|                 services.AddTransient(x => (ITransferRestClient)client(x)!); | ||||
| 
 | ||||
|             if (typeof(ISpotOrderRestClient).IsAssignableFrom(typeof(T))) | ||||
|                 services.AddTransient(x => (ISpotOrderRestClient)client(x)!); | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| using CryptoExchange.Net.Objects; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Net; | ||||
| @ -12,11 +13,50 @@ namespace CryptoExchange.Net | ||||
|     /// </summary> | ||||
|     public static class LibraryHelpers | ||||
|     { | ||||
|         private static ILogger? _staticLogger; | ||||
|         /// <summary> | ||||
|         /// Static logger | ||||
|         /// </summary> | ||||
|         public static ILogger? StaticLogger | ||||
|         { | ||||
|             get => _staticLogger; | ||||
|             internal set   | ||||
|             { | ||||
|                 if (_staticLogger != null) | ||||
|                     return; | ||||
| 
 | ||||
|                 _staticLogger = value; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Client order id separator | ||||
|         /// </summary> | ||||
|         public const string ClientOrderIdSeparator = "JK"; | ||||
| 
 | ||||
|         private static Dictionary<string, string> _defaultClientReferences = new Dictionary<string, string>() | ||||
|         { | ||||
|             { "Binance.Spot", "x-VICEW9VV" }, | ||||
|             { "Binance.Futures", "x-d63tKbx3" }, | ||||
|             { "BingX", "easytrading" }, | ||||
|             { "Bitfinex", "kCCe-CNBO" }, | ||||
|             { "Bitget", "6x21p" }, | ||||
|             { "BitMart", "EASYTRADING0001" }, | ||||
|             { "BitMEX", "Sent from JKorf" }, | ||||
|             { "BloFin", "5c07cf695885c282" }, | ||||
|             { "Bybit", "Zx000356" }, | ||||
|             { "CoinEx", "x-147866029-" }, | ||||
|             { "GateIo", "copytraderpw" }, | ||||
|             { "HTX", "AA1ef14811" }, | ||||
|             { "Kucoin.FuturesName", "Easytradingfutures" }, | ||||
|             { "Kucoin.FuturesKey", "9e08c05f-454d-4580-82af-2f4c7027fd00" }, | ||||
|             { "Kucoin.SpotName", "Easytrading" }, | ||||
|             { "Kucoin.SpotKey", "f8ae62cb-2b3d-420c-8c98-e1c17dd4e30a" }, | ||||
|             { "Mexc", "EASYT" }, | ||||
|             { "OKX", "1425d83a94fbBCDE" }, | ||||
|             { "XT", "4XWeqN10M1fcoI5L" }, | ||||
|         }; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Apply broker id to a client order id | ||||
|         /// </summary> | ||||
| @ -47,6 +87,22 @@ namespace CryptoExchange.Net | ||||
|             return clientOrderId; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get the client reference for an exchange if available | ||||
|         /// </summary> | ||||
|         public static string GetClientReference(Func<string?> optionsReference, string exchange, string? topic = null) | ||||
|         { | ||||
|             var optionsValue = optionsReference(); | ||||
|             if (!string.IsNullOrEmpty(optionsValue)) | ||||
|                 return optionsValue!; | ||||
| 
 | ||||
|             var key = exchange; | ||||
|             if (topic != null) | ||||
|                 key += "." + topic; | ||||
| 
 | ||||
|             return _defaultClientReferences.TryGetValue(key, out var id) ? id : throw new KeyNotFoundException($"{exchange} not found in configuration"); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Create a new HttpMessageHandler instance | ||||
|         /// </summary>   | ||||
|  | ||||
| @ -267,4 +267,31 @@ namespace CryptoExchange.Net.Objects | ||||
|         Succeed | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Subscription status | ||||
|     /// </summary> | ||||
|     public enum SubscriptionStatus | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Pending, waiting before (re)subscription can be started | ||||
|         /// </summary> | ||||
|         Pending, | ||||
|         /// <summary> | ||||
|         /// Currently (re)subscribing, will start producing updates soon if subscription is successful | ||||
|         /// </summary> | ||||
|         Subscribing, | ||||
|         /// <summary> | ||||
|         /// Subscribed and listening to updates | ||||
|         /// </summary> | ||||
|         Subscribed, | ||||
|         /// <summary> | ||||
|         /// Subscription is being closed and will stop producing updates | ||||
|         /// </summary> | ||||
|         Closing, | ||||
|         /// <summary> | ||||
|         /// Subscription is closed and will no long produce updates | ||||
|         /// </summary> | ||||
|         Closed | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -15,6 +15,7 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
|         private readonly Subscription _listener; | ||||
| 
 | ||||
|         private object _eventLock = new object(); | ||||
|         private bool _connectionEventsSubscribed = true; | ||||
|         private List<Action> _connectionClosedEventHandlers = new List<Action>(); | ||||
|         private List<Action> _connectionLostEventHandlers = new List<Action>(); | ||||
|         private List<Action<Error>> _resubscribeFailedEventHandlers = new List<Action<Error>>(); | ||||
| @ -22,6 +23,11 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
|         private List<Action> _activityPausedEventHandlers = new List<Action>(); | ||||
|         private List<Action> _activityUnpausedEventHandlers = new List<Action>(); | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Event when the status of the subscription changes | ||||
|         /// </summary> | ||||
|         public event Action<SubscriptionStatus>? SubscriptionStatusChanged; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Event when the connection is lost. The socket will automatically reconnect when possible. | ||||
|         /// </summary> | ||||
| @ -113,21 +119,34 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
|             _connection.ActivityUnpaused += HandleUnpausedEvent; | ||||
| 
 | ||||
|             _listener = subscription; | ||||
|             _listener.Unsubscribed += HandleUnsubscribed; | ||||
|             _listener.StatusChanged += (x) => SubscriptionStatusChanged?.Invoke(x); | ||||
|         } | ||||
| 
 | ||||
|         private void HandleUnsubscribed() | ||||
|         private void UnsubscribeConnectionEvents() | ||||
|         { | ||||
|             _connection.ConnectionClosed -= HandleConnectionClosedEvent; | ||||
|             _connection.ConnectionLost -= HandleConnectionLostEvent; | ||||
|             _connection.ConnectionRestored -= HandleConnectionRestoredEvent; | ||||
|             _connection.ResubscribingFailed -= HandleResubscribeFailedEvent; | ||||
|             _connection.ActivityPaused -= HandlePausedEvent; | ||||
|             _connection.ActivityUnpaused -= HandleUnpausedEvent; | ||||
|             lock (_eventLock) | ||||
|             { | ||||
|                 if (!_connectionEventsSubscribed) | ||||
|                     return; | ||||
| 
 | ||||
|                 _connection.ConnectionClosed -= HandleConnectionClosedEvent; | ||||
|                 _connection.ConnectionLost -= HandleConnectionLostEvent; | ||||
|                 _connection.ConnectionRestored -= HandleConnectionRestoredEvent; | ||||
|                 _connection.ResubscribingFailed -= HandleResubscribeFailedEvent; | ||||
|                 _connection.ActivityPaused -= HandlePausedEvent; | ||||
|                 _connection.ActivityUnpaused -= HandleUnpausedEvent; | ||||
|                 _connectionEventsSubscribed = false; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private void HandleConnectionClosedEvent() | ||||
|         { | ||||
|             UnsubscribeConnectionEvents(); | ||||
| 
 | ||||
|             // If we're not the subscription closing this connection don't bother emitting | ||||
|             if (!_listener.IsClosingConnection) | ||||
|                 return; | ||||
| 
 | ||||
|             List<Action> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _connectionClosedEventHandlers.ToList(); | ||||
| @ -138,6 +157,12 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
| 
 | ||||
|         private void HandleConnectionLostEvent() | ||||
|         { | ||||
|             if (!_listener.Active) | ||||
|             { | ||||
|                 UnsubscribeConnectionEvents(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             List<Action> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _connectionLostEventHandlers.ToList(); | ||||
| @ -148,6 +173,12 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
| 
 | ||||
|         private void HandleConnectionRestoredEvent(TimeSpan period) | ||||
|         { | ||||
|             if (!_listener.Active) | ||||
|             { | ||||
|                 UnsubscribeConnectionEvents(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             List<Action<TimeSpan>> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _connectionRestoredEventHandlers.ToList(); | ||||
| @ -158,6 +189,12 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
| 
 | ||||
|         private void HandleResubscribeFailedEvent(Error error) | ||||
|         { | ||||
|             if (!_listener.Active) | ||||
|             { | ||||
|                 UnsubscribeConnectionEvents(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             List<Action<Error>> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _resubscribeFailedEventHandlers.ToList(); | ||||
| @ -168,6 +205,12 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
| 
 | ||||
|         private void HandlePausedEvent() | ||||
|         { | ||||
|             if (!_listener.Active) | ||||
|             { | ||||
|                 UnsubscribeConnectionEvents(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             List<Action> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _activityPausedEventHandlers.ToList(); | ||||
| @ -178,6 +221,12 @@ namespace CryptoExchange.Net.Objects.Sockets | ||||
| 
 | ||||
|         private void HandleUnpausedEvent() | ||||
|         { | ||||
|             if (!_listener.Active) | ||||
|             { | ||||
|                 UnsubscribeConnectionEvents(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             List<Action> handlers; | ||||
|             lock (_eventLock) | ||||
|                 handlers = _activityUnpausedEventHandlers.ToList(); | ||||
|  | ||||
							
								
								
									
										63
									
								
								CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| 
 | ||||
| namespace CryptoExchange.Net.SharedApis | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Account type | ||||
|     /// </summary> | ||||
|     public enum SharedAccountType | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Unified account, combined account for multiple different types of trading | ||||
|         /// </summary> | ||||
|         Unified, | ||||
|          | ||||
|         /// <summary> | ||||
|         /// Funding account, where withdrawals and deposits are made from and to | ||||
|         /// </summary> | ||||
|         Funding, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Spot trading account | ||||
|         /// </summary> | ||||
|         Spot, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Cross margin account | ||||
|         /// </summary> | ||||
|         CrossMargin, | ||||
|         /// <summary> | ||||
|         /// Isolated margin account | ||||
|         /// </summary> | ||||
|         IsolatedMargin, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Perpetual linear futures account | ||||
|         /// </summary> | ||||
|         PerpetualLinearFutures, | ||||
|         /// <summary> | ||||
|         /// Delivery linear futures account | ||||
|         /// </summary> | ||||
|         DeliveryLinearFutures, | ||||
|         /// <summary> | ||||
|         /// Perpetual inverse futures account | ||||
|         /// </summary> | ||||
|         PerpetualInverseFutures, | ||||
|         /// <summary> | ||||
|         /// Delivery inverse futures account | ||||
|         /// </summary> | ||||
|         DeliveryInverseFutures, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Option account | ||||
|         /// </summary> | ||||
|         Option, | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Other | ||||
|         /// </summary> | ||||
|         Other | ||||
|     } | ||||
| } | ||||
| @ -12,7 +12,7 @@ namespace CryptoExchange.Net.SharedApis | ||||
|         /// <summary> | ||||
|         /// Balances request options | ||||
|         /// </summary> | ||||
|         EndpointOptions<GetBalancesRequest> GetBalancesOptions { get; } | ||||
|         GetBalancesOptions GetBalancesOptions { get; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Get balances for the user | ||||
|  | ||||
| @ -0,0 +1,25 @@ | ||||
| using CryptoExchange.Net.Objects; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| 
 | ||||
| namespace CryptoExchange.Net.SharedApis | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Client for transferring funds between account types | ||||
|     /// </summary> | ||||
|     public interface ITransferRestClient : ISharedClient | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Transfer request options | ||||
|         /// </summary> | ||||
|         TransferOptions TransferOptions { get; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Transfer funds between account types | ||||
|         /// </summary> | ||||
|         /// <param name="request">Request info</param> | ||||
|         /// <param name="ct">Cancellation token</param> | ||||
|         Task<ExchangeWebResult<SharedId>> TransferAsync(TransferRequest request, CancellationToken ct = default); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,96 @@ | ||||
| using CryptoExchange.Net.Objects; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace CryptoExchange.Net.SharedApis | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Options for requesting a transfer | ||||
|     /// </summary> | ||||
|     public class GetBalancesOptions : EndpointOptions<GetBalancesRequest> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Supported account types | ||||
|         /// </summary> | ||||
|         public AccountTypeFilter[] SupportedAccountTypes { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// ctor | ||||
|         /// </summary> | ||||
|         public GetBalancesOptions(params AccountTypeFilter[] accountTypes) : base(true) | ||||
|         { | ||||
|             SupportedAccountTypes = accountTypes; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Validate a request | ||||
|         /// </summary> | ||||
|         public Error? ValidateRequest( | ||||
|             string exchange, | ||||
|             GetBalancesRequest request, | ||||
|             TradingMode[] supportedApiTypes) | ||||
|         { | ||||
|             if (request.AccountType != null && !IsValid(request.AccountType.Value)) | ||||
|                 return ArgumentError.Invalid(nameof(request.AccountType), "Invalid AccountType"); | ||||
| 
 | ||||
|             return base.ValidateRequest(exchange, request, null, supportedApiTypes); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Is the account type valid for this client | ||||
|         /// </summary> | ||||
|         /// <param name="accountType"></param> | ||||
|         /// <returns></returns> | ||||
|         public bool IsValid(SharedAccountType accountType) | ||||
|         { | ||||
|             if (accountType == SharedAccountType.Funding) | ||||
|                 return SupportedAccountTypes.Contains(AccountTypeFilter.Funding); | ||||
| 
 | ||||
|             if (accountType == SharedAccountType.Spot)  | ||||
|                 return SupportedAccountTypes.Contains(AccountTypeFilter.Spot); | ||||
| 
 | ||||
|             if (accountType == SharedAccountType.PerpetualLinearFutures | ||||
|                 || accountType == SharedAccountType.PerpetualInverseFutures | ||||
|                 || accountType == SharedAccountType.DeliveryLinearFutures | ||||
|                 || accountType == SharedAccountType.DeliveryInverseFutures) | ||||
|             { | ||||
|                 return SupportedAccountTypes.Contains(AccountTypeFilter.Futures); | ||||
|             } | ||||
| 
 | ||||
| 
 | ||||
|             if (accountType == SharedAccountType.CrossMargin | ||||
|                 || accountType == SharedAccountType.IsolatedMargin) | ||||
|             { | ||||
|                 return SupportedAccountTypes.Contains(AccountTypeFilter.Margin); | ||||
|             } | ||||
| 
 | ||||
|             return SupportedAccountTypes.Contains(AccountTypeFilter.Option); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// <summary> | ||||
|     /// Account type filter | ||||
|     /// </summary> | ||||
|     public enum AccountTypeFilter | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Funding account | ||||
|         /// </summary> | ||||
|         Funding, | ||||
|         /// <summary> | ||||
|         /// Spot account | ||||
|         /// </summary> | ||||
|         Spot, | ||||
|         /// <summary> | ||||
|         /// Futures account | ||||
|         /// </summary> | ||||
|         Futures, | ||||
|         /// <summary> | ||||
|         /// Margin account | ||||
|         /// </summary> | ||||
|         Margin, | ||||
|         /// <summary> | ||||
|         /// Option account | ||||
|         /// </summary> | ||||
|         Option | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,42 @@ | ||||
| using CryptoExchange.Net.Objects; | ||||
| using System.Linq; | ||||
| 
 | ||||
| namespace CryptoExchange.Net.SharedApis | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Options for requesting a transfer | ||||
|     /// </summary> | ||||
|     public class TransferOptions : EndpointOptions<TransferRequest> | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Supported account types | ||||
|         /// </summary> | ||||
|         public SharedAccountType[] SupportedAccountTypes { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// ctor | ||||
|         /// </summary> | ||||
|         public TransferOptions(SharedAccountType[] accountTypes) : base(true) | ||||
|         { | ||||
|             SupportedAccountTypes = accountTypes; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Validate a request | ||||
|         /// </summary> | ||||
|         public new Error? ValidateRequest( | ||||
|             string exchange, | ||||
|             TransferRequest request, | ||||
|             TradingMode? tradingMode, | ||||
|             TradingMode[] supportedApiTypes) | ||||
|         { | ||||
|             if (!SupportedAccountTypes.Contains(request.FromAccountType)) | ||||
|                 return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid FromAccountType"); | ||||
| 
 | ||||
|             if (!SupportedAccountTypes.Contains(request.ToAccountType)) | ||||
|                 return ArgumentError.Invalid(nameof(request.FromAccountType), "Invalid ToAccountType"); | ||||
| 
 | ||||
|             return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -8,18 +8,28 @@ namespace CryptoExchange.Net.SharedApis | ||||
|     public record GetBalancesRequest : SharedRequest | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Trading mode | ||||
|         /// Account type | ||||
|         /// </summary> | ||||
|         public TradingMode? TradingMode { get; set; } | ||||
|         public SharedAccountType? AccountType { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// ctor | ||||
|         /// </summary> | ||||
|         /// <param name="tradingMode">Trading mode</param> | ||||
|         /// <param name="exchangeParameters">Exchange specific parameters</param> | ||||
|         public GetBalancesRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) | ||||
|         public GetBalancesRequest(TradingMode tradingMode, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) | ||||
|         { | ||||
|             TradingMode = tradingMode; | ||||
|             AccountType = tradingMode.ToAccountType(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// ctor | ||||
|         /// </summary> | ||||
|         /// <param name="accountType">Account type</param> | ||||
|         /// <param name="exchangeParameters">Exchange specific parameters</param> | ||||
|         public GetBalancesRequest(SharedAccountType? accountType = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) | ||||
|         { | ||||
|             AccountType = accountType; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										61
									
								
								CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,61 @@ | ||||
| namespace CryptoExchange.Net.SharedApis | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Request to transfer funds between account types | ||||
|     /// </summary> | ||||
|     public record TransferRequest : SharedRequest | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Asset | ||||
|         /// </summary> | ||||
|         public string Asset { get; set; } | ||||
|         /// <summary> | ||||
|         /// Quantity | ||||
|         /// </summary> | ||||
|         public decimal Quantity { get; set; } | ||||
|         /// <summary> | ||||
|         /// From symbol | ||||
|         /// </summary> | ||||
|         public string? FromSymbol { get; set; } | ||||
|         /// <summary> | ||||
|         /// To symbol | ||||
|         /// </summary> | ||||
|         public string? ToSymbol { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// From account type | ||||
|         /// </summary> | ||||
|         public SharedAccountType FromAccountType { get; set; } | ||||
|         /// <summary> | ||||
|         /// To account type | ||||
|         /// </summary> | ||||
|         public SharedAccountType ToAccountType { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// ctor | ||||
|         /// </summary> | ||||
|         /// <param name="asset">The asset to transfer</param> | ||||
|         /// <param name="quantity">Quantity to transfer</param> | ||||
|         /// <param name="fromAccount">From account type</param> | ||||
|         /// <param name="toAccount">To account type</param> | ||||
|         /// <param name="fromSymbol">From symbol</param> | ||||
|         /// <param name="toSymbol">To symbol</param> | ||||
|         /// <param name="exchangeParameters">Exchange specific parameters</param> | ||||
|         public TransferRequest( | ||||
|             string asset, | ||||
|             decimal quantity, | ||||
|             SharedAccountType fromAccount, | ||||
|             SharedAccountType toAccount, | ||||
|             string? fromSymbol = null, | ||||
|             string? toSymbol = null, | ||||
|             ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) | ||||
|         { | ||||
|             Asset = asset; | ||||
|             Quantity = quantity; | ||||
|             FromAccountType = fromAccount; | ||||
|             ToAccountType = toAccount; | ||||
|             FromSymbol = fromSymbol; | ||||
|             ToSymbol = toSymbol; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -17,6 +17,7 @@ | ||||
|         /// Total quantity | ||||
|         /// </summary> | ||||
|         public decimal Total { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Isolated margin symbol, only applicable for isolated margin futures | ||||
|         /// </summary> | ||||
|  | ||||
| @ -28,6 +28,10 @@ namespace CryptoExchange.Net.SharedApis | ||||
|         /// </summary> | ||||
|         public string OrderId { get; set; } | ||||
|         /// <summary> | ||||
|         /// The client order id | ||||
|         /// </summary> | ||||
|         public string? ClientOrderId { get; set; } | ||||
|         /// <summary> | ||||
|         /// Side of the trade | ||||
|         /// </summary> | ||||
|         public SharedOrderSide? Side { get; set; } | ||||
|  | ||||
| @ -296,7 +296,7 @@ namespace CryptoExchange.Net.Sockets | ||||
| 
 | ||||
|             lock (_listenersLock) | ||||
|             { | ||||
|                 foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription)) | ||||
|                 foreach (var subscription in _listeners.OfType<Subscription>().Where(l => l.UserSubscription && !l.IsClosingConnection)) | ||||
|                     subscription.Reset(); | ||||
| 
 | ||||
|                 foreach (var query in _listeners.OfType<Query>().ToList()) | ||||
| @ -527,10 +527,10 @@ namespace CryptoExchange.Net.Sockets | ||||
|                             continue; | ||||
|                         } | ||||
| 
 | ||||
|                         if (processor is Subscription subscriptionProcessor && !subscriptionProcessor.Confirmed) | ||||
|                         if (processor is Subscription subscriptionProcessor && subscriptionProcessor.Status == SubscriptionStatus.Subscribing) | ||||
|                         { | ||||
|                             // If this message is for this listener then it is automatically confirmed, even if the subscription is not (yet) confirmed | ||||
|                             subscriptionProcessor.Confirmed = true; | ||||
|                             subscriptionProcessor.Status = SubscriptionStatus.Subscribed; | ||||
|                             if (subscriptionProcessor.SubscriptionQuery?.TimeoutBehavior == TimeoutBehavior.Succeed) | ||||
|                                 // If this subscription has a query waiting for a timeout (success if there is no error response) | ||||
|                                 // then time it out now as the data is being received, so we assume it's successful | ||||
| @ -657,13 +657,16 @@ namespace CryptoExchange.Net.Sockets | ||||
|         public async Task CloseAsync(Subscription subscription) | ||||
|         { | ||||
|             // If we are resubscribing this subscription at this moment we'll want to wait for a bit until it is finished to avoid concurrency issues | ||||
|             while (subscription.IsResubscribing) | ||||
|             while (subscription.Status == SubscriptionStatus.Subscribing) | ||||
|                 await Task.Delay(50).ConfigureAwait(false); | ||||
| 
 | ||||
|             subscription.Closed = true; | ||||
|             subscription.Status = SubscriptionStatus.Closing; | ||||
| 
 | ||||
|             if (Status == SocketStatus.Closing || Status == SocketStatus.Closed || Status == SocketStatus.Disposed) | ||||
|             { | ||||
|                 subscription.Status = SubscriptionStatus.Closed; | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             _logger.ClosingSubscription(SocketId, subscription.Id); | ||||
|             if (subscription.CancellationTokenRegistration.HasValue) | ||||
| @ -675,7 +678,7 @@ namespace CryptoExchange.Net.Sockets | ||||
| 
 | ||||
|             bool shouldCloseConnection; | ||||
|             lock (_listenersLock) | ||||
|                 shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection; | ||||
|                 shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Status == SubscriptionStatus.Closing || r.Status == SubscriptionStatus.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection; | ||||
|              | ||||
|             if (!anyDuplicateSubscription) | ||||
|             { | ||||
| @ -693,6 +696,7 @@ namespace CryptoExchange.Net.Sockets | ||||
| 
 | ||||
|             if (Status == SocketStatus.Closing) | ||||
|             { | ||||
|                 subscription.Status = SubscriptionStatus.Closed; | ||||
|                 _logger.AlreadyClosing(SocketId); | ||||
|                 return; | ||||
|             } | ||||
| @ -700,6 +704,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|             if (shouldCloseConnection) | ||||
|             { | ||||
|                 Status = SocketStatus.Closing; | ||||
|                 subscription.IsClosingConnection = true; | ||||
|                 _logger.ClosingNoMoreSubscriptions(SocketId); | ||||
|                 await CloseAsync().ConfigureAwait(false); | ||||
|             } | ||||
| @ -707,7 +712,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|             lock (_listenersLock) | ||||
|                 _listeners.Remove(subscription); | ||||
| 
 | ||||
|             subscription.InvokeUnsubscribedHandler(); | ||||
|             subscription.Status = SubscriptionStatus.Closed; | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
| @ -991,7 +996,7 @@ namespace CryptoExchange.Net.Sockets | ||||
| 
 | ||||
|                 List<Subscription> subList; | ||||
|                 lock (_listenersLock) | ||||
|                     subList = _listeners.OfType<Subscription>().Where(x => !x.Closed).Skip(batch * batchSize).Take(batchSize).ToList(); | ||||
|                     subList = _listeners.OfType<Subscription>().Where(x => x.Active).Skip(batch * batchSize).Take(batchSize).ToList(); | ||||
| 
 | ||||
|                 if (subList.Count == 0) | ||||
|                     break; | ||||
| @ -1000,34 +1005,32 @@ namespace CryptoExchange.Net.Sockets | ||||
|                 foreach (var subscription in subList) | ||||
|                 { | ||||
|                     subscription.ConnectionInvocations = 0; | ||||
|                     if (subscription.Closed) | ||||
|                     if (!subscription.Active) | ||||
|                         // Can be closed during resubscribing | ||||
|                         continue; | ||||
| 
 | ||||
|                     subscription.IsResubscribing = true; | ||||
|                     subscription.Status = SubscriptionStatus.Subscribing; | ||||
|                     var result = await ApiClient.RevitalizeRequestAsync(subscription).ConfigureAwait(false); | ||||
|                     if (!result) | ||||
|                     { | ||||
|                         _logger.FailedRequestRevitalization(SocketId, result.Error?.ToString()); | ||||
|                         subscription.IsResubscribing = false; | ||||
|                         subscription.Status = SubscriptionStatus.Pending; | ||||
|                         return result; | ||||
|                     } | ||||
| 
 | ||||
|                     var subQuery = subscription.CreateSubscriptionQuery(this); | ||||
|                     if (subQuery == null) | ||||
|                     { | ||||
|                         subscription.IsResubscribing = false; | ||||
|                         subscription.Status = SubscriptionStatus.Subscribed; | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     var waitEvent = new AsyncResetEvent(false); | ||||
|                     taskList.Add(SendAndWaitQueryAsync(subQuery, waitEvent).ContinueWith((r) =>  | ||||
|                     {  | ||||
|                         subscription.IsResubscribing = false; | ||||
|                         subscription.Status = r.Result.Success ? SubscriptionStatus.Subscribed: SubscriptionStatus.Pending; | ||||
|                         subscription.HandleSubQueryResponse(subQuery.Response!); | ||||
|                         waitEvent.Set(); | ||||
|                         if (r.Result.Success) | ||||
|                             subscription.Confirmed = true; | ||||
|                         return r.Result; | ||||
|                     })); | ||||
|                 } | ||||
|  | ||||
| @ -34,21 +34,33 @@ namespace CryptoExchange.Net.Sockets | ||||
|         /// Is it a user subscription | ||||
|         /// </summary> | ||||
|         public bool UserSubscription { get; set; } | ||||
|          | ||||
| 
 | ||||
|         private SubscriptionStatus _status; | ||||
|         /// <summary> | ||||
|         /// Has the subscription been confirmed | ||||
|         /// Current subscription status | ||||
|         /// </summary> | ||||
|         public bool Confirmed { get; set; } | ||||
|         public SubscriptionStatus Status | ||||
|         { | ||||
|             get => _status; | ||||
|             set  | ||||
|             { | ||||
|                 if (_status == value) | ||||
|                     return; | ||||
| 
 | ||||
|                 _status = value; | ||||
|                 Task.Run(() => StatusChanged?.Invoke(value)); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Is the subscription closed | ||||
|         /// Whether the subscription is active | ||||
|         /// </summary> | ||||
|         public bool Closed { get; set; } | ||||
|         public bool Active => Status != SubscriptionStatus.Closing && Status != SubscriptionStatus.Closed; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Is the subscription currently resubscribing | ||||
|         /// Whether the unsubscribing of this subscription lead to the closing of the connection | ||||
|         /// </summary> | ||||
|         public bool IsResubscribing { get; set; } | ||||
|         public bool IsClosingConnection { get; set; } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Logger | ||||
| @ -77,7 +89,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|         /// <summary> | ||||
|         /// Listener unsubscribed event | ||||
|         /// </summary> | ||||
|         public event Action? Unsubscribed; | ||||
|         public event Action<SubscriptionStatus>? StatusChanged; | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Subscription topic | ||||
| @ -167,7 +179,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|         /// </summary> | ||||
|         public void Reset() | ||||
|         { | ||||
|             Confirmed = false; | ||||
|             Status = SubscriptionStatus.Pending; | ||||
|             DoHandleReset(); | ||||
|         } | ||||
| 
 | ||||
| @ -185,24 +197,16 @@ namespace CryptoExchange.Net.Sockets | ||||
|             Exception?.Invoke(e); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// Invoke the unsubscribed event | ||||
|         /// </summary> | ||||
|         public void InvokeUnsubscribedHandler() | ||||
|         { | ||||
|             Unsubscribed?.Invoke(); | ||||
|         } | ||||
| 
 | ||||
|         /// <summary> | ||||
|         /// State of this subscription | ||||
|         /// </summary> | ||||
|         /// <param name="Id">The id of the subscription</param> | ||||
|         /// <param name="Confirmed">True when the subscription query is handled (either accepted or rejected)</param> | ||||
|         /// <param name="Status">Subscription status</param> | ||||
|         /// <param name="Invocations">Number of times this subscription got a message</param> | ||||
|         /// <param name="ListenMatcher">Matcher for this subscription</param> | ||||
|         public record SubscriptionState( | ||||
|             int Id, | ||||
|             bool Confirmed, | ||||
|             SubscriptionStatus Status, | ||||
|             int Invocations, | ||||
|             MessageMatcher ListenMatcher | ||||
|         ); | ||||
| @ -213,7 +217,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|         /// <returns></returns> | ||||
|         public SubscriptionState GetState() | ||||
|         { | ||||
|             return new SubscriptionState(Id, Confirmed, TotalInvocations, MessageMatcher); | ||||
|             return new SubscriptionState(Id, Status, TotalInvocations, MessageMatcher); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -18,7 +18,7 @@ namespace CryptoExchange.Net.Sockets | ||||
|         /// <param name="authenticated"></param> | ||||
|         public SystemSubscription(ILogger logger, bool authenticated = false) : base(logger, authenticated, false) | ||||
|         { | ||||
|             Confirmed = true; | ||||
|             Status = SubscriptionStatus.Subscribed; | ||||
|         } | ||||
| 
 | ||||
|         /// <inheritdoc /> | ||||
|  | ||||
| @ -415,7 +415,7 @@ namespace CryptoExchange.Net.Testing.Comparers | ||||
|                 var value = jsonValue.GetDecimal(); | ||||
|                 if (objectValue is DateTime time) | ||||
|                 { | ||||
|                     if (time != DateTimeConverter.ParseFromDouble((double)value)) | ||||
|                     if (time != DateTimeConverter.ParseFromDecimal(value)) | ||||
|                         throw new Exception($"{method}: {property} not equal: {DateTimeConverter.ParseFromDouble((double)value!)} vs {time}"); | ||||
|                 } | ||||
|                 else if (propertyType.IsEnum || Nullable.GetUnderlyingType(propertyType)?.IsEnum == true) | ||||
|  | ||||
| @ -325,9 +325,12 @@ namespace CryptoExchange.Net.Trackers.Trades | ||||
|                 if (Period != null) | ||||
|                     items = items.Where(e => e.Timestamp >= DateTime.UtcNow.Add(-Period.Value)); | ||||
| 
 | ||||
|                 _snapshotId = data.Max(d => d.Timestamp.Ticks); | ||||
|                 foreach (var item in items.OrderBy(d => d.Timestamp)) | ||||
|                     _data.Add(item); | ||||
|                 if (items.Any()) | ||||
|                 { | ||||
|                     _snapshotId = data.Max(d => d.Timestamp.Ticks); | ||||
|                     foreach (var item in items.OrderBy(d => d.Timestamp)) | ||||
|                         _data.Add(item); | ||||
|                 } | ||||
| 
 | ||||
|                 _snapshotSet = true; | ||||
|                 _changed = true; | ||||
|  | ||||
| @ -5,30 +5,32 @@ | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Binance.Net" Version="11.7.1" /> | ||||
|     <PackageReference Include="Bitfinex.Net" Version="9.7.0" /> | ||||
|     <PackageReference Include="BitMart.Net" Version="2.8.0" /> | ||||
|     <PackageReference Include="BloFin.Net" Version="1.0.0" /> | ||||
|     <PackageReference Include="Bybit.Net" Version="5.8.0" /> | ||||
|     <PackageReference Include="CoinEx.Net" Version="9.7.0" /> | ||||
|     <PackageReference Include="CoinW.Net" Version="1.4.0" /> | ||||
|     <PackageReference Include="CryptoCom.Net" Version="2.8.0" /> | ||||
|     <PackageReference Include="DeepCoin.Net" Version="2.7.0" /> | ||||
|     <PackageReference Include="GateIo.Net" Version="2.8.1" /> | ||||
|     <PackageReference Include="HyperLiquid.Net" Version="2.12.0" /> | ||||
|     <PackageReference Include="JK.BingX.Net" Version="2.7.0" /> | ||||
|     <PackageReference Include="JK.Bitget.Net" Version="2.7.1" /> | ||||
|     <PackageReference Include="JK.Mexc.Net" Version="3.8.0" /> | ||||
|     <PackageReference Include="JK.OKX.Net" Version="3.7.1" /> | ||||
|     <PackageReference Include="JKorf.BitMEX.Net" Version="2.7.0" /> | ||||
|     <PackageReference Include="JKorf.Coinbase.Net" Version="2.7.0" /> | ||||
|     <PackageReference Include="JKorf.HTX.Net" Version="7.7.0" /> | ||||
|     <PackageReference Include="KrakenExchange.Net" Version="6.7.0" /> | ||||
|     <PackageReference Include="Kucoin.Net" Version="7.7.1" /> | ||||
|     <PackageReference Include="Binance.Net" Version="11.9.0" /> | ||||
|     <PackageReference Include="Bitfinex.Net" Version="9.9.0" /> | ||||
|     <PackageReference Include="BitMart.Net" Version="2.10.0" /> | ||||
|     <PackageReference Include="BloFin.Net" Version="1.2.0" /> | ||||
|     <PackageReference Include="Bybit.Net" Version="5.10.1" /> | ||||
|     <PackageReference Include="CoinEx.Net" Version="9.9.0" /> | ||||
|     <PackageReference Include="CoinW.Net" Version="1.6.0" /> | ||||
|     <PackageReference Include="CryptoCom.Net" Version="2.10.0" /> | ||||
|     <PackageReference Include="DeepCoin.Net" Version="2.9.0" /> | ||||
|     <PackageReference Include="GateIo.Net" Version="2.11.0" /> | ||||
|     <PackageReference Include="HyperLiquid.Net" Version="2.14.0" /> | ||||
|     <PackageReference Include="JK.BingX.Net" Version="2.9.0" /> | ||||
|     <PackageReference Include="JK.Bitget.Net" Version="2.9.0" /> | ||||
|     <PackageReference Include="JK.Mexc.Net" Version="3.10.0" /> | ||||
|     <PackageReference Include="JK.OKX.Net" Version="3.9.0" /> | ||||
|     <PackageReference Include="Jkorf.Aster.Net" Version="1.1.0" /> | ||||
|     <PackageReference Include="JKorf.BitMEX.Net" Version="2.9.0" /> | ||||
|     <PackageReference Include="JKorf.Coinbase.Net" Version="2.9.0" /> | ||||
|     <PackageReference Include="JKorf.HTX.Net" Version="7.9.0" /> | ||||
|     <PackageReference Include="JKorf.Upbit.Net" Version="1.0.0" /> | ||||
|     <PackageReference Include="KrakenExchange.Net" Version="6.9.0" /> | ||||
|     <PackageReference Include="Kucoin.Net" Version="7.9.0" /> | ||||
|     <PackageReference Include="Serilog.AspNetCore" Version="9.0.0" /> | ||||
|     <PackageReference Include="Toobit.Net" Version="1.6.0" /> | ||||
|     <PackageReference Include="WhiteBit.Net" Version="2.8.0" /> | ||||
|     <PackageReference Include="XT.Net" Version="2.7.0" /> | ||||
|     <PackageReference Include="Toobit.Net" Version="1.8.0" /> | ||||
|     <PackageReference Include="WhiteBit.Net" Version="2.10.0" /> | ||||
|     <PackageReference Include="XT.Net" Version="2.9.0" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| @page "/" | ||||
| @inject IAsterRestClient asterClient | ||||
| @inject IBinanceRestClient binanceClient | ||||
| @inject IBingXRestClient bingXClient | ||||
| @inject IBitfinexRestClient bitfinexClient | ||||
| @ -20,6 +21,7 @@ | ||||
| @inject IMexcRestClient mexcClient | ||||
| @inject IOKXRestClient okxClient | ||||
| @inject IToobitRestClient toobitClient | ||||
| @inject IUpbitRestClient upbitClient | ||||
| @inject IWhiteBitRestClient whitebitClient | ||||
| @inject IXTRestClient xtClient | ||||
| 
 | ||||
| @ -34,6 +36,7 @@ | ||||
| 
 | ||||
|     protected override async Task OnInitializedAsync() | ||||
|     { | ||||
|         var asterTask = asterClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT"); | ||||
|         var binanceTask = binanceClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT"); | ||||
|         var bingXTask = bingXClient.SpotApi.ExchangeData.GetTickersAsync("BTC-USDT"); | ||||
|         var bitfinexTask = bitfinexClient.SpotApi.ExchangeData.GetTickerAsync("tBTCUSD"); | ||||
| @ -49,16 +52,20 @@ | ||||
|         var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot); | ||||
|         var gateioTask = gateioClient.SpotApi.ExchangeData.GetTickersAsync("BTC_USDT"); | ||||
|         var htxTask = htxClient.SpotApi.ExchangeData.GetTickerAsync("btcusdt"); | ||||
|         var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync(); // HyperLiquid does not have BTC spot trading | ||||
|         var hyperLiquidTask = hyperLiquidClient.FuturesApi.ExchangeData.GetExchangeInfoAndTickersAsync(); | ||||
|         var krakenTask = krakenClient.SpotApi.ExchangeData.GetTickerAsync("XBTUSD"); | ||||
|         var kucoinTask = kucoinClient.SpotApi.ExchangeData.GetTickerAsync("BTC-USDT"); | ||||
|         var mexcTask = mexcClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT"); | ||||
|         var okxTask = okxClient.UnifiedApi.ExchangeData.GetTickerAsync("BTC-USDT"); | ||||
|         var toobitTask = toobitClient.SpotApi.ExchangeData.GetTickersAsync("BTCUSDT"); | ||||
|         var upbitTask = upbitClient.SpotApi.ExchangeData.GetTickerAsync("USDT-BTC"); | ||||
|         var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync(); | ||||
|         var xtTask = xtClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt"); | ||||
| 
 | ||||
|         await Task.WhenAll(binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bybitTask, coinexTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask); | ||||
|         await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bitmexTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask, htxTask, krakenTask, kucoinTask, mexcTask, okxTask); | ||||
| 
 | ||||
|         if (asterTask.Result.Success) | ||||
|             _prices.Add("Aster", asterTask.Result.Data.LastPrice); | ||||
| 
 | ||||
|         if (binanceTask.Result.Success) | ||||
|             _prices.Add("Binance", binanceTask.Result.Data.LastPrice); | ||||
| @ -131,6 +138,9 @@ | ||||
|         if (toobitTask.Result.Success) | ||||
|             _prices.Add("Toobit", toobitTask.Result.Data.Single().LastPrice ?? 0); | ||||
| 
 | ||||
|         if (upbitTask.Result.Success) | ||||
|             _prices.Add("Upbit", upbitTask.Result.Data.LastPrice); | ||||
| 
 | ||||
|         if (whitebitTask.Result.Success){ | ||||
|             // WhiteBit API doesn't offer an endpoint to filter for a specific ticker, so we have to filter client side | ||||
|             var tickers = whitebitTask.Result.Data; | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| @page "/LiveData" | ||||
| @inject IAsterSocketClient asterSocketClient | ||||
| @inject IBinanceSocketClient binanceSocketClient | ||||
| @inject IBingXSocketClient bingXSocketClient | ||||
| @inject IBitfinexSocketClient bitfinexSocketClient | ||||
| @ -20,6 +21,7 @@ | ||||
| @inject IMexcSocketClient mexcSocketClient | ||||
| @inject IOKXSocketClient okxSocketClient | ||||
| @inject IToobitSocketClient toobitSocketClient | ||||
| @inject IUpbitSocketClient upbitSocketClient | ||||
| @inject IWhiteBitSocketClient whitebitSocketClient | ||||
| @inject IXTSocketClient xtSocketClient | ||||
| @using System.Collections.Concurrent | ||||
| @ -43,6 +45,8 @@ | ||||
|     { | ||||
|         var tasks = new Task<CallResult<UpdateSubscription>>[] | ||||
|         { | ||||
|             // Aster doesn't support the ETH/BTC pair | ||||
|             //asterSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Aster", data.Data.LastPrice)), | ||||
|             binanceSocketClient.SpotApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Binance", data.Data.LastPrice)), | ||||
|             bingXSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BingX", data.Data.LastPrice)), | ||||
|             bitfinexSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)), | ||||
| @ -69,6 +73,7 @@ | ||||
|             okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("OKX", data.Data.LastPrice ?? 0)), | ||||
|             // Toobit doesn't support the ETH/BTC pair | ||||
|             //toobitSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Toobit", data.Data.LastPrice ?? 0)), | ||||
|             upbitSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("BTC-ETH", data => UpdateData("Upbit", data.Data.LastPrice)), | ||||
|             whitebitSocketClient.V4Api.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("WhiteBit", data.Data.Ticker.LastPrice)), | ||||
|         }; | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| @page "/OrderBooks" | ||||
| @using System.Collections.Concurrent | ||||
| @using System.Timers | ||||
| @using Aster.Net.Interfaces | ||||
| @using Binance.Net.Interfaces | ||||
| @using BingX.Net.Interfaces | ||||
| @using Bitfinex.Net.Interfaces | ||||
| @ -24,9 +25,11 @@ | ||||
| @using Kucoin.Net.Interfaces | ||||
| @using Mexc.Net.Interfaces | ||||
| @using OKX.Net.Interfaces; | ||||
| @using Upbit.Net.Interfaces; | ||||
| @using Toobit.Net.Interfaces; | ||||
| @using WhiteBit.Net.Interfaces | ||||
| @using XT.Net.Interfaces | ||||
| @inject IAsterOrderBookFactory asterFactory | ||||
| @inject IBinanceOrderBookFactory binanceFactory | ||||
| @inject IBingXOrderBookFactory bingXFactory | ||||
| @inject IBitfinexOrderBookFactory bitfinexFactory | ||||
| @ -48,6 +51,7 @@ | ||||
| @inject IMexcOrderBookFactory mexcFactory | ||||
| @inject IOKXOrderBookFactory okxFactory | ||||
| @inject IToobitOrderBookFactory toobitFactory | ||||
| @inject IUpbitOrderBookFactory upbitFactory | ||||
| @inject IWhiteBitOrderBookFactory whitebitFactory | ||||
| @inject IXTOrderBookFactory xtFactory | ||||
| @implements IDisposable | ||||
| @ -83,6 +87,7 @@ | ||||
| 
 | ||||
|         _books = new Dictionary<string, ISymbolOrderBook> | ||||
|             { | ||||
|                 { "Aster", binanceFactory.CreateSpot("ETHUSDT") }, | ||||
|                 { "Binance", binanceFactory.CreateSpot("ETHBTC") }, | ||||
|                 { "BingX", bingXFactory.CreateSpot("ETH-BTC") }, | ||||
|                 { "Bitfinex", bitfinexFactory.Create("tETHBTC") }, | ||||
| @ -104,6 +109,7 @@ | ||||
|                 { "Mexc", mexcFactory.CreateSpot("ETHBTC") }, | ||||
|                 { "OKX", okxFactory.Create("ETH-BTC") }, | ||||
|                 { "Toobit", toobitFactory.CreateSpot("ETHUSDT") }, | ||||
|                 { "Upbit", upbitFactory.CreateSpot("BTC-ETH") }, | ||||
|                 { "WhiteBit", whitebitFactory.CreateV4("ETH_BTC") }, | ||||
|                 { "XT", xtFactory.CreateSpot("eth_btc") }, | ||||
|             }; | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| @page "/Trackers" | ||||
| @using System.Collections.Concurrent | ||||
| @using System.Timers | ||||
| @using Aster.Net.Interfaces | ||||
| @using Binance.Net.Interfaces | ||||
| @using BingX.Net.Interfaces | ||||
| @using Bitfinex.Net.Interfaces | ||||
| @ -25,9 +26,11 @@ | ||||
| @using Kucoin.Net.Interfaces | ||||
| @using Mexc.Net.Interfaces | ||||
| @using OKX.Net.Interfaces; | ||||
| @using Upbit.Net.Interfaces; | ||||
| @using Toobit.Net.Interfaces; | ||||
| @using WhiteBit.Net.Interfaces | ||||
| @using XT.Net.Interfaces | ||||
| @inject IAsterTrackerFactory asterFactory | ||||
| @inject IBinanceTrackerFactory binanceFactory | ||||
| @inject IBingXTrackerFactory bingXFactory | ||||
| @inject IBitfinexTrackerFactory bitfinexFactory | ||||
| @ -49,11 +52,12 @@ | ||||
| @inject IMexcTrackerFactory mexcFactory | ||||
| @inject IOKXTrackerFactory okxFactory | ||||
| @inject IToobitTrackerFactory toobitFactory | ||||
| @inject IUpbitTrackerFactory upbitFactory | ||||
| @inject IWhiteBitTrackerFactory whitebitFactory | ||||
| @inject IXTTrackerFactory xtFactory | ||||
| @implements IDisposable | ||||
| 
 | ||||
| <h3>ETH-BTC trade Trackers, live updates:</h3> | ||||
| <h3>Trade Trackers, live updates:</h3> | ||||
| <div style="display:flex; flex-wrap: wrap;"> | ||||
|     @foreach (var tracker in _trackers.OrderBy(p => p.Exchange)) | ||||
|     { | ||||
| @ -79,6 +83,7 @@ | ||||
| 
 | ||||
|         _trackers = new List<ITradeTracker> | ||||
|             { | ||||
|                 { asterFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { binanceFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { bingXFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { bitfinexFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
| @ -100,11 +105,12 @@ | ||||
|                 { mexcFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { okxFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { toobitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { upbitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { whitebitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|                 { xtFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) }, | ||||
|             }; | ||||
| 
 | ||||
|         await Task.WhenAll(_trackers.Select(b => b.StartAsync())); | ||||
|         await Task.WhenAll(_trackers.Select(b => b.StartAsync(false))); | ||||
| 
 | ||||
|         // Use a manual update timer so the page isn't refreshed too often | ||||
|         _timer = new Timer(500); | ||||
|  | ||||
| @ -33,6 +33,7 @@ namespace BlazorClient | ||||
|                 restOptions.ApiCredentials = new ApiCredentials("KEY", "SECRET"); | ||||
|             }); | ||||
| 
 | ||||
|             services.AddAster(); | ||||
|             services.AddBingX(); | ||||
|             services.AddBitfinex(); | ||||
|             services.AddBitget(); | ||||
| @ -53,6 +54,7 @@ namespace BlazorClient | ||||
|             services.AddMexc(); | ||||
|             services.AddOKX(); | ||||
|             services.AddToobit(); | ||||
|             services.AddUpbit(); | ||||
|             services.AddWhiteBit(); | ||||
|             services.AddXT(); | ||||
|         } | ||||
|  | ||||
| @ -8,6 +8,7 @@ | ||||
| @using Microsoft.JSInterop | ||||
| @using BlazorClient | ||||
| @using BlazorClient.Shared | ||||
| @using Aster.Net.Interfaces.Clients; | ||||
| @using Binance.Net.Interfaces.Clients; | ||||
| @using BingX.Net.Interfaces.Clients; | ||||
| @using Bitfinex.Net.Interfaces.Clients; | ||||
| @ -28,6 +29,7 @@ | ||||
| @using Kucoin.Net.Interfaces.Clients; | ||||
| @using Mexc.Net.Interfaces.Clients; | ||||
| @using OKX.Net.Interfaces.Clients; | ||||
| @using Upbit.Net.Interfaces.Clients; | ||||
| @using Toobit.Net.Interfaces.Clients; | ||||
| @using WhiteBit.Net.Interfaces.Clients | ||||
| @using XT.Net.Interfaces.Clients | ||||
|  | ||||
| @ -6,20 +6,20 @@ | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
| 	<ItemGroup> | ||||
| 		<PackageReference Include="Binance.Net" Version="11.1.0" /> | ||||
| 	<PackageReference Include="Bitfinex.Net" Version="9.1.0" /> | ||||
| 	<PackageReference Include="BitMart.Net" Version="2.1.0" /> | ||||
| 	<PackageReference Include="Bybit.Net" Version="5.1.0" /> | ||||
| 	<PackageReference Include="CoinEx.Net" Version="9.1.0" /> | ||||
| 	<PackageReference Include="CryptoCom.Net" Version="2.1.0" /> | ||||
| 	<PackageReference Include="GateIo.Net" Version="2.1.0" /> | ||||
| 	<PackageReference Include="JK.Bitget.Net" Version="2.1.0" /> | ||||
| 	<PackageReference Include="JK.Mexc.Net" Version="3.1.0" /> | ||||
| 	<PackageReference Include="JK.OKX.Net" Version="3.1.0" /> | ||||
| 	<PackageReference Include="JKorf.Coinbase.Net" Version="2.1.0" /> | ||||
| 	<PackageReference Include="JKorf.HTX.Net" Version="7.1.0" /> | ||||
| 	<PackageReference Include="KrakenExchange.Net" Version="6.1.0" /> | ||||
| 		<PackageReference Include="Kucoin.Net" Version="7.1.0" /> | ||||
| 		<PackageReference Include="Binance.Net" Version="11.9.0" /> | ||||
| 	<PackageReference Include="Bitfinex.Net" Version="9.9.0" /> | ||||
| 	<PackageReference Include="BitMart.Net" Version="2.10.0" /> | ||||
| 	<PackageReference Include="Bybit.Net" Version="5.10.1" /> | ||||
| 	<PackageReference Include="CoinEx.Net" Version="9.9.0" /> | ||||
| 	<PackageReference Include="CryptoCom.Net" Version="2.10.0" /> | ||||
| 	<PackageReference Include="GateIo.Net" Version="2.11.0" /> | ||||
| 	<PackageReference Include="JK.Bitget.Net" Version="2.9.0" /> | ||||
| 	<PackageReference Include="JK.Mexc.Net" Version="3.10.0" /> | ||||
| 	<PackageReference Include="JK.OKX.Net" Version="3.9.0" /> | ||||
| 	<PackageReference Include="JKorf.Coinbase.Net" Version="2.9.0" /> | ||||
| 	<PackageReference Include="JKorf.HTX.Net" Version="7.9.0" /> | ||||
| 	<PackageReference Include="KrakenExchange.Net" Version="6.9.0" /> | ||||
| 		<PackageReference Include="Kucoin.Net" Version="7.9.0" /> | ||||
| 	</ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | ||||
| @ -8,9 +8,9 @@ | ||||
|   </PropertyGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Binance.Net" Version="11.1.0" /> | ||||
|     <PackageReference Include="BitMart.Net" Version="2.1.0" /> | ||||
|     <PackageReference Include="JK.OKX.Net" Version="3.1.0" /> | ||||
|     <PackageReference Include="Binance.Net" Version="11.9.0" /> | ||||
|     <PackageReference Include="BitMart.Net" Version="2.10.0" /> | ||||
|     <PackageReference Include="JK.OKX.Net" Version="3.9.0" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | ||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
								
							| @ -12,6 +12,7 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us | ||||
| 
 | ||||
| ||Exchange|Type|Repository|Nuget|Referral Link|Referral Fee Discount| | ||||
| |--|--|--|--|--|--|--| | ||||
| ||Aster|DEX|[JKorf/Aster.Net](https://github.com/JKorf/Aster.Net)|[](https://www.nuget.org/packages/JKorf.Aster.Net)|[Link](https://www.asterdex.com/en/referral/FD2E11)|4%| | ||||
| ||Binance|CEX|[JKorf/Binance.Net](https://github.com/JKorf/Binance.Net)|[](https://www.nuget.org/packages/Binance.Net)|[Link](https://accounts.binance.com/register?ref=X5K3F2ZG)|20%| | ||||
| ||BingX|CEX|[JKorf/BingX.Net](https://github.com/JKorf/BingX.Net)|[](https://www.nuget.org/packages/JK.BingX.Net)|[Link](https://bingx.com/invite/FFHRJKWG/)|20%| | ||||
| ||Bitfinex|CEX|[JKorf/Bitfinex.Net](https://github.com/JKorf/Bitfinex.Net)|[](https://www.nuget.org/packages/Bitfinex.Net)|-|-| | ||||
| @ -34,6 +35,7 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us | ||||
| ||Mexc|CEX|[JKorf/Mexc.Net](https://github.com/JKorf/Mexc.Net)|[](https://www.nuget.org/packages/JK.Mexc.Net)|-|-| | ||||
| ||OKX|CEX|[JKorf/OKX.Net](https://github.com/JKorf/OKX.Net)|[](https://www.nuget.org/packages/JK.OKX.Net)|[Link](https://www.okx.com/join/14592495)|20%| | ||||
| ||Toobit|CEX|[JKorf/Toobit.Net](https://github.com/JKorf/Toobit.Net)|[](https://www.nuget.org/packages/Toobit.Net)|[Link](https://www.toobit.com/en-US/register?invite_code=zsV19h)|-| | ||||
| ||Upbit|CEX|[JKorf/Upbit.Net](https://github.com/JKorf/Upbit.Net)|[](https://www.nuget.org/packages/JKorf.Upbit.Net)|-|-| | ||||
| ||WhiteBit|CEX|[JKorf/WhiteBit.Net](https://github.com/JKorf/WhiteBit.Net)|[](https://www.nuget.org/packages/WhiteBit.Net)|[Link](https://whitebit.com/referral/a8e59b59-186c-4662-824c-3095248e0edf)|-| | ||||
| ||XT|CEX|[JKorf/XT.Net](https://github.com/JKorf/XT.Net)|[](https://www.nuget.org/packages/XT.Net)|[Link](https://www.xt.com/ru/accounts/register?ref=CZG39C)|25%| | ||||
| 
 | ||||
| @ -64,6 +66,23 @@ 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 9.11.0 - 30 Oct 2025 | ||||
|     * Added StaticLogger to LibraryHelpers, updated warning logging for converters to use StaticLogger | ||||
|     * Added client reference helper to LibraryHelpers | ||||
|     * Fixed exception when initial trade snapshot has no items in TradeTracker | ||||
| 
 | ||||
| * Version 9.10.0 - 15 Oct 2025 | ||||
|     * Added ITransferRestClient Shared interface	 | ||||
|     * Added ClientOrderId property to SharedUserTrade model | ||||
|     * Updated IBalanceRestClient, GetBalancesRequest now mainly works with SharedAccountType type, allowing more options | ||||
|     * Updated IBalanceRestClient, GetBalanceOptions now specifies supported account types | ||||
|     * Updated DateTimeConverter to work primarily with decimal instead of double to prevent some floating point issues | ||||
| 
 | ||||
| * Version 9.9.0 - 06 Oct 2025 | ||||
|     * Updated socket Subscription status handling | ||||
|     * Added SubscriptionStatusChanged event to UpdateSubscription (SubscribeAsync methods reponse) | ||||
|     * Fixed timing issue for connection events in UpdateSubscription | ||||
| 
 | ||||
| * Version 9.8.0 - 30 Sep 2025 | ||||
|     * Added ContractAddress to SharedAsset model | ||||
|     * Added ITrackerFactory interface | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user