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

Shared exchange functionality (#214)

This commit is contained in:
Jan Korf
2024-09-27 09:17:44 +02:00
committed by GitHub
parent 5d3de52da6
commit b8686d60b9
199 changed files with 7219 additions and 277 deletions
@@ -0,0 +1,146 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for an exchange endpoint
/// </summary>
public class EndpointOptions
{
/// <summary>
/// Required exchange-specific parameters
/// </summary>
public List<ParameterDescription> RequiredExchangeParameters { get; set; } = new List<ParameterDescription>();
/// <summary>
/// Endpoint name
/// </summary>
public string EndpointName { get; set; }
/// <summary>
/// Information on the specific exchange request
/// </summary>
public string? RequestNotes { get; set; }
/// <summary>
/// Whether the call requires authentication
/// </summary>
public bool NeedsAuthentication { get; set; }
/// <summary>
/// ctor
/// </summary>
public EndpointOptions(string endpointName, bool needAuthentication)
{
EndpointName = endpointName;
NeedsAuthentication = needAuthentication;
}
/// <summary>
/// Validate a request
/// </summary>
/// <param name="exchange">Exchange name</param>
/// <param name="exchangeParameters">Provided exchange parameters</param>
/// <param name="tradingMode">Request trading mode</param>
/// <param name="supportedTradingModes">Supported trading modes</param>
/// <returns></returns>
public virtual Error? ValidateRequest(string exchange, ExchangeParameters? exchangeParameters, TradingMode? tradingMode, TradingMode[] supportedTradingModes)
{
if (tradingMode != null && !supportedTradingModes.Contains(tradingMode.Value))
return new ArgumentError($"ApiType.{tradingMode} is not supported, supported types: {string.Join(", ", supportedTradingModes)}");
foreach (var param in RequiredExchangeParameters)
{
if (!string.IsNullOrEmpty(param.Name))
{
if (ExchangeParameters.HasValue(exchangeParameters, exchange, param.Name!, param.ValueType) != true)
return new ArgumentError($"Required exchange parameter `{param.Name}` for exchange `{exchange}` is missing or has incorrect type. Expected type is {param.ValueType.Name}. Example: {param.ExampleValue}");
}
else
{
if (param.Names.All(x => ExchangeParameters.HasValue(exchangeParameters, exchange, x, param.ValueType) != true))
return new ArgumentError($"One of exchange parameters `{string.Join(", ", param.Names)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
}
}
return null;
}
/// <inheritdoc />
public virtual string ToString(string exchange)
{
var sb = new StringBuilder();
sb.AppendLine($"{exchange} {EndpointName}");
if (!string.IsNullOrEmpty(RequestNotes))
sb.AppendLine(RequestNotes);
sb.AppendLine($"Needs authentication: {NeedsAuthentication}");
sb.AppendLine($"Required exchange specific parameters: {string.Join(", ", RequiredExchangeParameters.Select(x => x.ToString()))}");
return sb.ToString();
}
}
/// <summary>
/// Options for an exchange endpoint
/// </summary>
/// <typeparam name="T">Type of data</typeparam>
public class EndpointOptions<T> : EndpointOptions where T : SharedRequest
{
/// <summary>
/// Required optional parameters in the request
/// </summary>
public List<ParameterDescription> RequiredOptionalParameters { get; set; } = new List<ParameterDescription>();
/// <summary>
/// ctor
/// </summary>
public EndpointOptions(bool needsAuthentication) : base(typeof(T).Name, needsAuthentication)
{
}
/// <summary>
/// Validate a request
/// </summary>
/// <param name="exchange">Exchange name</param>
/// <param name="request">The request</param>
/// <param name="tradingMode">Request trading mode</param>
/// <param name="supportedTradingModes">Supported trading modes</param>
/// <returns></returns>
public virtual Error? ValidateRequest(string exchange, T request, TradingMode? tradingMode, TradingMode[] supportedTradingModes)
{
foreach (var param in RequiredOptionalParameters)
{
if (!string.IsNullOrEmpty(param.Name))
{
if (typeof(T).GetProperty(param.Name).GetValue(request, null) == null)
return new ArgumentError($"Required optional parameter `{param.Name}` for exchange `{exchange}` is missing. Example: {param.ExampleValue}");
}
else
{
if (param.Names.All(x => typeof(T).GetProperty(param.Name).GetValue(request, null) == null))
return new ArgumentError($"One of optional parameters `{string.Join(", ", param.Names)}` for exchange `{exchange}` should be provided. Example: {param.ExampleValue}");
}
}
return ValidateRequest(exchange, request.ExchangeParameters, tradingMode, supportedTradingModes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder();
sb.AppendLine($"{exchange} {typeof(T).Name}");
sb.AppendLine($"Needs authentication: {NeedsAuthentication}");
if (!string.IsNullOrEmpty(RequestNotes))
sb.AppendLine(RequestNotes);
if (RequiredOptionalParameters.Any())
sb.AppendLine($"Required optional parameters: {string.Join(", ", RequiredOptionalParameters.Select(x => x.ToString()))}");
if (RequiredExchangeParameters.Any())
sb.AppendLine($"Required exchange specific parameters: {string.Join(", ", RequiredExchangeParameters.Select(x => x.ToString()))}");
return sb.ToString();
}
}
}
@@ -0,0 +1,41 @@
using CryptoExchange.Net.Objects;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting closed orders
/// </summary>
public class GetClosedOrdersOptions : PaginatedEndpointOptions<GetClosedOrdersRequest>
{
/// <summary>
/// Whether the start/end time filter is supported
/// </summary>
public bool TimeFilterSupported { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetClosedOrdersOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
{
TimeFilterSupported = timeFilterSupported;
}
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetClosedOrdersRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (TimeFilterSupported && request.StartTime != null)
return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Time filter supported: {TimeFilterSupported}");
return sb.ToString();
}
}
}
@@ -0,0 +1,41 @@
using CryptoExchange.Net.Objects;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting deposits
/// </summary>
public class GetDepositsOptions : PaginatedEndpointOptions<GetDepositsRequest>
{
/// <summary>
/// Whether the start/end time filter is supported
/// </summary>
public bool TimeFilterSupported { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetDepositsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
{
TimeFilterSupported = timeFilterSupported;
}
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetDepositsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (TimeFilterSupported && request.StartTime != null)
return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Time filter supported: {TimeFilterSupported}");
return sb.ToString();
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting funding rate history
/// </summary>
public class GetFundingRateHistoryOptions : PaginatedEndpointOptions<GetFundingRateHistoryRequest>
{
/// <summary>
/// ctor
/// </summary>
public GetFundingRateHistoryOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
{
}
}
}
@@ -0,0 +1,104 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting kline/candlestick data
/// </summary>
public class GetKlinesOptions : PaginatedEndpointOptions<GetKlinesRequest>
{
/// <summary>
/// The supported kline intervals
/// </summary>
public IEnumerable<SharedKlineInterval> SupportIntervals { get; }
/// <summary>
/// Max number of data points which can be requested
/// </summary>
public int? MaxTotalDataPoints { get; set; }
/// <summary>
/// Max number of data points which can be requested in a single request
/// </summary>
public int? MaxRequestDataPoints { get; set; }
/// <summary>
/// The max age of the data that can be requested
/// </summary>
public TimeSpan? MaxAge { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetKlinesOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
{
SupportIntervals = new[]
{
SharedKlineInterval.FiveMinutes,
SharedKlineInterval.FifteenMinutes,
SharedKlineInterval.OneHour,
SharedKlineInterval.FifteenMinutes,
SharedKlineInterval.OneDay,
SharedKlineInterval.OneWeek,
SharedKlineInterval.OneMonth
};
}
/// <summary>
/// ctor
/// </summary>
public GetKlinesOptions(SharedPaginationSupport paginationType, bool needsAuthentication, params SharedKlineInterval[] intervals) : base(paginationType, needsAuthentication)
{
SupportIntervals = intervals;
}
/// <summary>
/// Check whether a specific interval is supported
/// </summary>
/// <param name="interval"></param>
/// <returns></returns>
public bool IsSupported(SharedKlineInterval interval) => SupportIntervals.Contains(interval);
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetKlinesRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (!IsSupported(request.Interval))
return new ArgumentError("Interval not supported");
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
return new ArgumentError($"Only the most recent {MaxAge} klines are available");
if (MaxRequestDataPoints.HasValue && request.Limit > MaxRequestDataPoints.Value)
return new ArgumentError($"Only {MaxRequestDataPoints} klines can be retrieved per request");
if (MaxTotalDataPoints.HasValue)
{
if (request.Limit > MaxTotalDataPoints.Value)
return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available");
if (request.StartTime.HasValue == true)
{
if (((request.EndTime ?? DateTime.UtcNow) - request.StartTime.Value).TotalSeconds / (int)request.Interval > MaxTotalDataPoints.Value)
return new ArgumentError($"Only the most recent {MaxTotalDataPoints} klines are available, time filter failed");
}
}
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Supported SharedKlineInterval values: {string.Join(", ", SupportIntervals)}");
if (MaxAge != null)
sb.AppendLine($"Max age of data: {MaxAge}");
if (MaxTotalDataPoints != null)
sb.AppendLine($"Max total data points available: {MaxTotalDataPoints}");
if (MaxRequestDataPoints != null)
sb.AppendLine($"Max data points per request: {MaxRequestDataPoints}");
return sb.ToString();
}
}
}
@@ -0,0 +1,71 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting order book
/// </summary>
public class GetOrderBookOptions : EndpointOptions<GetOrderBookRequest>
{
/// <summary>
/// Supported order book depths
/// </summary>
public IEnumerable<int>? SupportedLimits { get; set; }
/// <summary>
/// The min order book depth
/// </summary>
public int? MinLimit { get; set; }
/// <summary>
/// The max order book depth
/// </summary>
public int? MaxLimit { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetOrderBookOptions(int minLimit, int maxLimit, bool authenticated) : base(authenticated)
{
MinLimit = minLimit;
MaxLimit = maxLimit;
}
/// <summary>
/// ctor
/// </summary>
public GetOrderBookOptions(IEnumerable<int> supportedLimits, bool authenticated) : base(authenticated)
{
SupportedLimits = supportedLimits;
}
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetOrderBookRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (request.Limit == null)
return null;
if (MaxLimit.HasValue && request.Limit.Value > MaxLimit)
return new ArgumentError($"Max limit is {MaxLimit}");
if (MinLimit.HasValue && request.Limit.Value < MinLimit)
return new ArgumentError($"Min limit is {MaxLimit}");
if (SupportedLimits != null && !SupportedLimits.Contains(request.Limit.Value))
return new ArgumentError($"Limit should be one of " + string.Join(", ", SupportedLimits));
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Supported limit values: [{(SupportedLimits == null ? string.Join(", ", SupportedLimits) : $"{MinLimit}..{MaxLimit}")}]");
return sb.ToString();
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting position history
/// </summary>
public class GetPositionHistoryOptions : PaginatedEndpointOptions<GetPositionHistoryRequest>
{
/// <summary>
/// ctor
/// </summary>
public GetPositionHistoryOptions(SharedPaginationSupport paginationType) : base(paginationType, true)
{
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting current position mode
/// </summary>
public class GetPositionModeOptions : EndpointOptions<GetPositionModeRequest>
{
/// <summary>
/// ctor
/// </summary>
public GetPositionModeOptions() : base(true)
{
}
}
}
@@ -0,0 +1,41 @@
using CryptoExchange.Net.Objects;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting recent trades
/// </summary>
public class GetRecentTradesOptions : EndpointOptions<GetRecentTradesRequest>
{
/// <summary>
/// The max number of trades that can be requested
/// </summary>
public int MaxLimit { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetRecentTradesOptions(int limit, bool authenticated) : base(authenticated)
{
MaxLimit = limit;
}
/// <inheritdoc />
public Error? Validate(GetRecentTradesRequest request)
{
if (request.Limit > MaxLimit)
return new ArgumentError($"Only the most recent {MaxLimit} trades are available");
return null;
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Max data points: {MaxLimit}");
return sb.ToString();
}
}
}
@@ -0,0 +1,42 @@
using CryptoExchange.Net.Objects;
using System;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting trade history
/// </summary>
public class GetTradeHistoryOptions : PaginatedEndpointOptions<GetTradeHistoryRequest>
{
/// <summary>
/// The max age of data that can be requested
/// </summary>
public TimeSpan? MaxAge { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetTradeHistoryOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(paginationType, needsAuthentication)
{
}
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetTradeHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value))
return new ArgumentError($"Only the most recent {MaxAge} trades are available");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
if (MaxAge != null)
sb.AppendLine($"Max age of data: {MaxAge}");
return sb.ToString();
}
}
}
@@ -0,0 +1,41 @@
using CryptoExchange.Net.Objects;
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting withdrawals
/// </summary>
public class GetWithdrawalsOptions : PaginatedEndpointOptions<GetWithdrawalsRequest>
{
/// <summary>
/// Whether the start/end time filter is supported
/// </summary>
public bool TimeFilterSupported { get; set; }
/// <summary>
/// ctor
/// </summary>
public GetWithdrawalsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported) : base(paginationType, true)
{
TimeFilterSupported = timeFilterSupported;
}
/// <inheritdoc />
public override Error? ValidateRequest(string exchange, GetWithdrawalsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes)
{
if (TimeFilterSupported && request.StartTime != null)
return new ArgumentError($"Time filter is not supported");
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Time filter supported: {TimeFilterSupported}");
return sb.ToString();
}
}
}
@@ -0,0 +1,32 @@
using System.Text;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for paginated endpoints
/// </summary>
/// <typeparam name="T"></typeparam>
public class PaginatedEndpointOptions<T> : EndpointOptions<T> where T : SharedRequest
{
/// <summary>
/// Type of pagination supported
/// </summary>
public SharedPaginationSupport PaginationSupport { get; }
/// <summary>
/// ctor
/// </summary>
public PaginatedEndpointOptions(SharedPaginationSupport paginationType, bool needsAuthentication) : base(needsAuthentication)
{
PaginationSupport = paginationType;
}
/// <inheritdoc />
public override string ToString(string exchange)
{
var sb = new StringBuilder(base.ToString(exchange));
sb.AppendLine($"Pagination type: {PaginationSupport}");
return sb.ToString();
}
}
}
@@ -0,0 +1,49 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for placing a new futures order
/// </summary>
public class PlaceFuturesOrderOptions : EndpointOptions<PlaceFuturesOrderRequest>
{
/// <summary>
/// ctor
/// </summary>
public PlaceFuturesOrderOptions() : base(true)
{
}
/// <summary>
/// Validate a request
/// </summary>
public Error? ValidateRequest(
string exchange,
PlaceFuturesOrderRequest request,
TradingMode? tradingMode,
TradingMode[] supportedApiTypes,
IEnumerable<SharedOrderType> supportedOrderTypes,
IEnumerable<SharedTimeInForce> supportedTimeInForce,
SharedQuantitySupport quantitySupport)
{
if (request.OrderType == SharedOrderType.Other)
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
if (!supportedOrderTypes.Contains(request.OrderType))
return new ArgumentError("Order type not supported");
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
return new ArgumentError("Order time in force not supported");
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity, request.QuoteQuantity);
if (quantityError != null)
return quantityError;
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
}
}
@@ -0,0 +1,49 @@
using CryptoExchange.Net.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for placing a new spot order
/// </summary>
public class PlaceSpotOrderOptions : EndpointOptions<PlaceSpotOrderRequest>
{
/// <summary>
/// ctor
/// </summary>
public PlaceSpotOrderOptions() : base(true)
{
}
/// <summary>
/// Validate a request
/// </summary>
public Error? ValidateRequest(
string exchange,
PlaceSpotOrderRequest request,
TradingMode? tradingMode,
TradingMode[] supportedApiTypes,
IEnumerable<SharedOrderType> supportedOrderTypes,
IEnumerable<SharedTimeInForce> supportedTimeInForce,
SharedQuantitySupport quantitySupport)
{
if (request.OrderType == SharedOrderType.Other)
throw new ArgumentException("OrderType can't be `Other`", nameof(request.OrderType));
if (!supportedOrderTypes.Contains(request.OrderType))
return new ArgumentError("Order type not supported");
if (request.TimeInForce != null && !supportedTimeInForce.Contains(request.TimeInForce.Value))
return new ArgumentError("Order time in force not supported");
var quantityError = quantitySupport.Validate(request.Side, request.OrderType, request.Quantity, request.QuoteQuantity);
if (quantityError != null)
return quantityError;
return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes);
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for setting leverage
/// </summary>
public class SetLeverageOptions : EndpointOptions<SetLeverageRequest>
{
/// <summary>
/// ctor
/// </summary>
public SetLeverageOptions() : base(true)
{
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for setting position mode
/// </summary>
public class SetPositionModeOptions : EndpointOptions<SetPositionModeRequest>
{
/// <summary>
/// ctor
/// </summary>
public SetPositionModeOptions() : base(true)
{
}
}
}
@@ -0,0 +1,15 @@
namespace CryptoExchange.Net.SharedApis
{
/// <summary>
/// Options for requesting a withdrawal
/// </summary>
public class WithdrawOptions : EndpointOptions<WithdrawRequest>
{
/// <summary>
/// ctor
/// </summary>
public WithdrawOptions() : base(true)
{
}
}
}