diff --git a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
index b3438b1..5180641 100644
--- a/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
+++ b/CryptoExchange.Net/Converters/SystemTextJson/DateTimeConverter.cs
@@ -88,13 +88,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
}
///
- /// Parse a long value to datetime
+ /// Parse a double value to datetime
///
public static DateTime ParseFromDouble(double value)
=> ParseFromDecimal((decimal)value);
///
- /// Parse a long value to datetime
+ /// Parse a decimal value to datetime
///
public static DateTime ParseFromDecimal(decimal value)
{
diff --git a/CryptoExchange.Net/ExtensionMethods.cs b/CryptoExchange.Net/ExtensionMethods.cs
index dbe6cf9..702b3f5 100644
--- a/CryptoExchange.Net/ExtensionMethods.cs
+++ b/CryptoExchange.Net/ExtensionMethods.cs
@@ -399,7 +399,7 @@ namespace CryptoExchange.Net
///
/// Whether the trading mode is linear
///
- 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;
///
/// Whether the trading mode is inverse
@@ -416,6 +416,36 @@ namespace CryptoExchange.Net
///
public static bool IsDelivery(this TradingMode type) => type == TradingMode.DeliveryInverse || type == TradingMode.DeliveryLinear;
+ ///
+ /// Whether the account type is a futures account
+ ///
+ public static bool IsFuturesAccount(this SharedAccountType type) =>
+ type == SharedAccountType.PerpetualLinearFutures
+ || type == SharedAccountType.DeliveryLinearFutures
+ || type == SharedAccountType.PerpetualInverseFutures
+ || type == SharedAccountType.DeliveryInverseFutures;
+
+ ///
+ /// Whether the account type is a margin account
+ ///
+ public static bool IsMarginAccount(this SharedAccountType type) =>
+ type == SharedAccountType.CrossMargin
+ || type == SharedAccountType.IsolatedMargin;
+
+ ///
+ /// Map a TradingMode value to a SharedAccountType enum value
+ ///
+ 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");
+ }
+
///
/// Register rest client interfaces
///
@@ -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)!);
diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs b/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs
new file mode 100644
index 0000000..e3bb862
--- /dev/null
+++ b/CryptoExchange.Net/SharedApis/Enums/SharedAccountType.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace CryptoExchange.Net.SharedApis
+{
+ ///
+ /// Account type
+ ///
+ public enum SharedAccountType
+ {
+ ///
+ /// Unified account, combined account for multiple different types of trading
+ ///
+ Unified,
+
+ ///
+ /// Funding account, where withdrawals and deposits are made from and to
+ ///
+ Funding,
+
+ ///
+ /// Spot trading account
+ ///
+ Spot,
+
+ ///
+ /// Cross margin account
+ ///
+ CrossMargin,
+ ///
+ /// Isolated margin account
+ ///
+ IsolatedMargin,
+
+ ///
+ /// Perpetual linear futures account
+ ///
+ PerpetualLinearFutures,
+ ///
+ /// Delivery linear futures account
+ ///
+ DeliveryLinearFutures,
+ ///
+ /// Perpetual inverse futures account
+ ///
+ PerpetualInverseFutures,
+ ///
+ /// Delivery inverse futures account
+ ///
+ DeliveryInverseFutures,
+
+ ///
+ /// Option account
+ ///
+ Option,
+
+ ///
+ /// Other
+ ///
+ Other
+ }
+}
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs
index 8983c61..8190fc4 100644
--- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs
+++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IBalanceRestClient.cs
@@ -12,7 +12,7 @@ namespace CryptoExchange.Net.SharedApis
///
/// Balances request options
///
- EndpointOptions GetBalancesOptions { get; }
+ GetBalancesOptions GetBalancesOptions { get; }
///
/// Get balances for the user
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs
new file mode 100644
index 0000000..23eabab
--- /dev/null
+++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITransferRestClient.cs
@@ -0,0 +1,25 @@
+using CryptoExchange.Net.Objects;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace CryptoExchange.Net.SharedApis
+{
+ ///
+ /// Client for transferring funds between account types
+ ///
+ public interface ITransferRestClient : ISharedClient
+ {
+ ///
+ /// Transfer request options
+ ///
+ TransferOptions TransferOptions { get; }
+
+ ///
+ /// Transfer funds between account types
+ ///
+ /// Request info
+ /// Cancellation token
+ Task> TransferAsync(TransferRequest request, CancellationToken ct = default);
+ }
+}
diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs
new file mode 100644
index 0000000..1355cbb
--- /dev/null
+++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetBalancesOptions.cs
@@ -0,0 +1,96 @@
+using CryptoExchange.Net.Objects;
+using System.Linq;
+
+namespace CryptoExchange.Net.SharedApis
+{
+ ///
+ /// Options for requesting a transfer
+ ///
+ public class GetBalancesOptions : EndpointOptions
+ {
+ ///
+ /// Supported account types
+ ///
+ public AccountTypeFilter[] SupportedAccountTypes { get; set; }
+
+ ///
+ /// ctor
+ ///
+ public GetBalancesOptions(params AccountTypeFilter[] accountTypes) : base(true)
+ {
+ SupportedAccountTypes = accountTypes;
+ }
+
+ ///
+ /// Validate a request
+ ///
+ 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);
+ }
+
+ ///
+ /// Is the account type valid for this client
+ ///
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// Account type filter
+ ///
+ public enum AccountTypeFilter
+ {
+ ///
+ /// Funding account
+ ///
+ Funding,
+ ///
+ /// Spot account
+ ///
+ Spot,
+ ///
+ /// Futures account
+ ///
+ Futures,
+ ///
+ /// Margin account
+ ///
+ Margin,
+ ///
+ /// Option account
+ ///
+ Option
+ }
+}
diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs
new file mode 100644
index 0000000..c0abf9e
--- /dev/null
+++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/TransferOptions.cs
@@ -0,0 +1,42 @@
+using CryptoExchange.Net.Objects;
+using System.Linq;
+
+namespace CryptoExchange.Net.SharedApis
+{
+ ///
+ /// Options for requesting a transfer
+ ///
+ public class TransferOptions : EndpointOptions
+ {
+ ///
+ /// Supported account types
+ ///
+ public SharedAccountType[] SupportedAccountTypes { get; set; }
+
+ ///
+ /// ctor
+ ///
+ public TransferOptions(SharedAccountType[] accountTypes) : base(true)
+ {
+ SupportedAccountTypes = accountTypes;
+ }
+
+ ///
+ /// Validate a request
+ ///
+ 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);
+ }
+ }
+}
diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs
index 2cab40b..50a4852 100644
--- a/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs
+++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetBalancesRequest.cs
@@ -8,18 +8,28 @@ namespace CryptoExchange.Net.SharedApis
public record GetBalancesRequest : SharedRequest
{
///
- /// Trading mode
+ /// Account type
///
- public TradingMode? TradingMode { get; set; }
+ public SharedAccountType? AccountType { get; set; }
///
/// ctor
///
/// Trading mode
/// Exchange specific parameters
- public GetBalancesRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters)
+ public GetBalancesRequest(TradingMode tradingMode, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters)
{
- TradingMode = tradingMode;
+ AccountType = tradingMode.ToAccountType();
+ }
+
+ ///
+ /// ctor
+ ///
+ /// Account type
+ /// Exchange specific parameters
+ public GetBalancesRequest(SharedAccountType? accountType = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters)
+ {
+ AccountType = accountType;
}
}
}
diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs
new file mode 100644
index 0000000..2b5908b
--- /dev/null
+++ b/CryptoExchange.Net/SharedApis/Models/Rest/TransferRequest.cs
@@ -0,0 +1,61 @@
+namespace CryptoExchange.Net.SharedApis
+{
+ ///
+ /// Request to transfer funds between account types
+ ///
+ public record TransferRequest : SharedRequest
+ {
+ ///
+ /// Asset
+ ///
+ public string Asset { get; set; }
+ ///
+ /// Quantity
+ ///
+ public decimal Quantity { get; set; }
+ ///
+ /// From symbol
+ ///
+ public string? FromSymbol { get; set; }
+ ///
+ /// To symbol
+ ///
+ public string? ToSymbol { get; set; }
+
+ ///
+ /// From account type
+ ///
+ public SharedAccountType FromAccountType { get; set; }
+ ///
+ /// To account type
+ ///
+ public SharedAccountType ToAccountType { get; set; }
+
+ ///
+ /// ctor
+ ///
+ /// The asset to transfer
+ /// Quantity to transfer
+ /// From account type
+ /// To account type
+ /// From symbol
+ /// To symbol
+ /// Exchange specific parameters
+ 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;
+ }
+ }
+}
diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs b/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs
index ee622d1..23ea5dd 100644
--- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs
+++ b/CryptoExchange.Net/SharedApis/ResponseModels/SharedBalance.cs
@@ -17,6 +17,7 @@
/// Total quantity
///
public decimal Total { get; set; }
+
///
/// Isolated margin symbol, only applicable for isolated margin futures
///
diff --git a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs
index eaa61d6..b851882 100644
--- a/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs
+++ b/CryptoExchange.Net/Testing/Comparers/SystemTextJsonComparer.cs
@@ -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)