diff --git a/CryptoExchange.Net/ExchangeSymbolCache.cs b/CryptoExchange.Net/ExchangeSymbolCache.cs index e3b0fc8b..59c75251 100644 --- a/CryptoExchange.Net/ExchangeSymbolCache.cs +++ b/CryptoExchange.Net/ExchangeSymbolCache.cs @@ -33,7 +33,7 @@ namespace CryptoExchange.Net if (keyedCache != null && DateTime.UtcNow - keyedCache.UpdateTime < TimeSpan.FromMinutes(60)) return; - exchangeInfo.Set(key, new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x.SharedSymbol))); + exchangeInfo.Set(key, new ExchangeInfo(DateTime.UtcNow, updateData.ToDictionary(x => x.Name, x => x))); } /// @@ -118,6 +118,22 @@ namespace CryptoExchange.Net return exchangeInfo.ParseSymbol(key, symbolName); } + /// + /// Get a symbol catalog for a specific exchange(topic) and environment. Only available if has been called previously. + /// + /// Exchange name + /// Id for the provided data + /// Trade environment + /// Additional data set identification key + public static SharedSymbolCatalog? GetSymbolCatalog(string exchange, string topicId, string environmentName, string? key) + { + var id = topicId + environmentName; + if (!_symbolInfos.TryGetValue(id, out var exchangeInfo)) + return null; + + return exchangeInfo.GetSymbolCatalog(exchange, key); + } + class ExchangeKeyedCache { private ExchangeInfo? _noKeyCache; @@ -163,7 +179,7 @@ namespace CryptoExchange.Net public SharedSymbol? ParseSymbol(string? key, string symbolName) { - SharedSymbol? symbolInfo = null; + SharedSpotSymbol? symbolInfo = null; if (key == null) { if (_noKeyCache != null) @@ -173,7 +189,7 @@ namespace CryptoExchange.Net return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName) { - DeliverTime = symbolInfo.DeliverTime + DeliverTime = (symbolInfo as SharedFuturesSymbol)?.DeliveryTime }; } @@ -183,7 +199,7 @@ namespace CryptoExchange.Net { return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName) { - DeliverTime = symbolInfo.DeliverTime + DeliverTime = (symbolInfo as SharedFuturesSymbol)?.DeliveryTime }; } } @@ -199,7 +215,7 @@ namespace CryptoExchange.Net { return new SharedSymbol(symbolInfo.TradingMode, symbolInfo.BaseAsset, symbolInfo.QuoteAsset, symbolName) { - DeliverTime = symbolInfo.DeliverTime + DeliverTime = (symbolInfo as SharedFuturesSymbol)?.DeliveryTime }; } @@ -265,7 +281,7 @@ namespace CryptoExchange.Net { return _noKeyCache.Symbols .Where(x => x.Value.BaseAsset.Equals(baseAsset, StringComparison.InvariantCultureIgnoreCase)) - .Select(x => x.Value) + .Select(x => x.Value.SharedSymbol) .ToArray(); } @@ -274,7 +290,7 @@ namespace CryptoExchange.Net { result.AddRange(cache.Symbols .Where(x => x.Value.BaseAsset.Equals(baseAsset, StringComparison.InvariantCultureIgnoreCase)) - .Select(x => x.Value)); + .Select(x => x.Value.SharedSymbol)); } return result.ToArray(); @@ -286,18 +302,63 @@ namespace CryptoExchange.Net return exchangeInfo.Symbols .Where(x => x.Value.BaseAsset.Equals(baseAsset, StringComparison.InvariantCultureIgnoreCase)) - .Select(x => x.Value) + .Select(x => x.Value.SharedSymbol) .ToArray(); } + + internal SharedSymbolCatalog? GetSymbolCatalog(string exchange, string? key) + { + IEnumerable cachedSymbols; + if (key == null) + { + if (_noKeyCache != null) + cachedSymbols = _noKeyCache.Symbols.Values; + else + cachedSymbols = _keyedCache.Values.SelectMany(x => x.Symbols.Values); + } + else + { + if (!_keyedCache.TryGetValue(key, out var exchangeInfo) || exchangeInfo == null) + return null; + + cachedSymbols = exchangeInfo.Symbols.Values; + } + + var assets = new Dictionary(); + var symbols = new Dictionary(); + foreach (var symbol in cachedSymbols) + { + if (!assets.TryGetValue(symbol.BaseAsset, out var baseAssetInfo)) + { + baseAssetInfo = new SharedAssetInfo(symbol.BaseAsset, symbol.BaseAssetType, symbol.BaseAssetSubType); + assets.Add(symbol.BaseAsset, baseAssetInfo); + } + + if (!assets.TryGetValue(symbol.QuoteAsset, out var quoteAssetInfo)) + { + quoteAssetInfo = new SharedAssetInfo(symbol.QuoteAsset, symbol.QuoteAssetType, symbol.QuoteAssetSubType); + assets.Add(symbol.QuoteAsset, quoteAssetInfo); + } + + symbols.Add(symbol.Name, symbol); + } + + return new SharedSymbolCatalog + { + Exchange = exchange, + Assets = assets, + Symbols = symbols + }; + } } class ExchangeInfo { public DateTime UpdateTime { get; set; } - public Dictionary Symbols { get; set; } + public Dictionary Symbols { get; set; } - public ExchangeInfo(DateTime updateTime, Dictionary symbols) + public ExchangeInfo(DateTime updateTime, Dictionary symbols) { UpdateTime = updateTime; Symbols = symbols; diff --git a/CryptoExchange.Net/LibraryHelpers.cs b/CryptoExchange.Net/LibraryHelpers.cs index 63e6e8e8..4c0f66f4 100644 --- a/CryptoExchange.Net/LibraryHelpers.cs +++ b/CryptoExchange.Net/LibraryHelpers.cs @@ -4,6 +4,8 @@ using CryptoExchange.Net.Objects.Options; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.IO.Pipelines; +using System.Linq; using System.Net; using System.Net.Http; @@ -14,6 +16,61 @@ namespace CryptoExchange.Net /// public static class LibraryHelpers { + private static readonly HashSet _stableCoins = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // USD + "USDT", "USDC", "DAI", "FDUSD", "USDE", "TUSD", "USDP", "PYUSD", "GUSD", + "USDD", "LUSD", "USDJ", "SUSD", "ZUSD", "BUSD", "USTC", "USDX", "USDK", + "CUSD", "USD1", "USD0", "XUSD", "BFUSD", "USDS", "RLUSD", "OUSD", "USDH", + "APXUSD", "USDQ", "USDPT", "FIDD", "AUSD", + // EUR + "EURS", "EURC", "EURI", "EURT", "AGEUR", "CEUR", "AEUR", "EURQ", "EUROP", + // Other + "CNYT", // CNY + "CREAL", "BRL1", // BRL + "XSGD", // SGD + "GYEN", // JPY + "KGST", // KGS + "QCAD", // CAD + "TGBP", // GBP + "AUDX", // AUD + "MXNB", // MXN + }; + + private static readonly HashSet _commodities = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // Metals + "XAU", "XAUT", "XAG", "XPT", "XPD", "COPPER", "PAXG", "XNI", "XCU", "XAL", "GOLD", "SILVER", + // Energy + "BZ", "NATGAS", "NGAS", "CL", "XTI", "UKOIL", "USOIL", "BRENTOIL" + }; + + private static readonly HashSet _stocks = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // Top stocks, will need to update periodically + "AAAU", "AADR", "AAPL", "ACWI", "ACWX", "AGG", "AMD", "AMLP", "AMZN", "ARKF", + "ARKG", "ARKK", "ARKQ", "ARKW", "AVGO", "BA", "BABA", "BND", "BNDX", "BOTZ", + "CIBR", "COIN", "DIA", "DIVB", "DVY", "EEM", "EFA", "EFAV", "ESGU", "EWG", + "EWJ", "EWT", "EWU", "EWW", "EWY", "EWZ", "FDN", "FEZ", "GLDM", "GOOGL", + "HDV", "HOOD", "HYG", "IAU", "IBB", "ICLN", "IEFA", "IEMG", "IGSB", "IJH", + "IJR", "INTC", "ITOT", "IUSB", "IUSG", "IUSV", "IWM", "IWO", "IWR", "IYR", + "JETS", "JPM", "LIT", "MCHI", "META", "MGK", "MSTR", "MTUM", "MU", "NET", + "NFLX", "NOBL", "NVDA", "OIH", "ORCL", "PAVE", "PBW", "PLTR", "QQQ", "QQQM", + "SCHB", "SCHD", "SCHF", "SCHG", "SCHH", "SCHV", "SCHX", "SKHY", "SPCX", "SPCXD", + "SPLG", "SPY", "SPYG", "SPYV", "SQQQ", "TSLA", "TSM", "TQQQ", "USMV", "VBR", + "VCIT", "VCSH", "VEA", "VEU", "VGIT", "VGK", "VGT", "VHT", "VIG", "VNQ", + "VOO", "VOT", "VTI", "VTV", "VUG", "VXUS", "XBI", "XLC", "XLE", "XLF", + "XLI", "XLK", "XLP", "XLU", "XLV", "XLY", "CSCO", "UBER", "MRVL", "RKLB", + "COHR", "SOXL", "HD", "DIS", "CBRS", "V", "BRKB", "FLNC", "LLY", "COST", + "ARM", "BMNR", "NBIS", "ASML", "AAOI", "GLW", "SHLD", "BE", "QNTX", "IBM", + "AMAT", "NOK", "ASTS", "BBX", "SLX", "SKHYNIX", "SAMSUNG", "HYUNDAI", "NVO", + "IREN", "ONDS", "CRM" , "VRT", "ZEST", "BTW", "HPE", "AXTI", "BX", "CRWD", + "CRDO", "NOW", "ZM", "DKNG", "RIVN", "URNM", "EBAY", "ADBE", "UVXY", "RDW", + "CIEN","PANW", "WIN", "PAYP", "HIMS", "CRWV", "QCOM", "LITE", "DRAM", "ANTHROPIC", + "OPENAI", "USAR", "BILL", "SNDK", "NASDAQ100", "SPX500", "BSB", "CRCL", "STRC", + "MSFT", "WDC" + }; + private static ILogger? _staticLogger; /// /// Static logger @@ -105,6 +162,67 @@ namespace CryptoExchange.Net return _defaultClientReferences.TryGetValue(key, out var id) ? id : throw new KeyNotFoundException($"{exchange} not found in configuration"); } + /// + /// Check whether an asset is a known stablecoin. Note that this is not definitive, only large known stocks are checked + /// + /// Asset name + /// Additional stablecoin names for the specific exchange + public static bool IsStableCoin(string asset, params HashSet additionalStableCoins) + { + if (string.IsNullOrEmpty(asset)) + return false; + + return _stableCoins.Contains(asset) || (additionalStableCoins != null && additionalStableCoins.Contains(asset, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// Check whether an asset is a known commodity. Note that this is not definitive, only large known stocks are checked + /// + /// Asset name + /// Additional commodity names for the specific exchange + public static bool IsCommodity(string asset, params HashSet additionalCommodities) + { + if (string.IsNullOrEmpty(asset)) + return false; + + return _commodities.Contains(asset) || (additionalCommodities != null && additionalCommodities.Contains(asset, StringComparer.OrdinalIgnoreCase)); + } + + /// + /// Check whether an asset is a known stock. Note that this is not definitive, only large known stocks are checked + /// + /// Asset name + /// Additional stock names for the specific exchange + public static bool IsEquity(string asset, params HashSet additionalStocks) + => IsEquity(asset, [], additionalStocks); + + /// + /// Check whether an asset is a known stock. + /// + /// Asset name + /// Suffixes to check, for example when `X` is a potential suffix both `TSLA` and `TSLAX` will be checked + /// Additional stock names for the specific exchange + public static bool IsEquity(string asset, string[] potentialSuffixes, params HashSet additionalStocks) + { + if (string.IsNullOrEmpty(asset)) + return false; + + if (_stocks.Contains(asset) || (additionalStocks != null && additionalStocks.Contains(asset, StringComparer.OrdinalIgnoreCase))) + return true; + + foreach (var suffix in potentialSuffixes) + { + if (!asset.EndsWith(suffix)) + continue; + + var suffixAsset = asset.Substring(0, asset.Length - suffix.Length); + if (_stocks.Contains(suffixAsset) || (additionalStocks != null && additionalStocks.Contains(suffixAsset, StringComparer.OrdinalIgnoreCase))) + return true; + } + + return false; + } + /// /// Create a new HttpMessageHandler instance /// diff --git a/CryptoExchange.Net/SharedApis/Enums/SharedAssetType.cs b/CryptoExchange.Net/SharedApis/Enums/SharedAssetType.cs new file mode 100644 index 00000000..ce07bd5d --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Enums/SharedAssetType.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Asset type + /// + public enum SharedAssetType + { + /// + /// Unknown or unspecified asset type + /// + Unspecified, + /// + /// Cryptocurrency asset type + /// + Crypto, + /// + /// Fiat currency asset type + /// + Fiat, + /// + /// Traditional finance asset type + /// + TradFi + } + + /// + /// Asset sub type + /// + public enum SharedAssetSubType + { + // --- Crypto sub types --- + /// + /// Stable coin, can be for different fiat currencies + /// + StableCoin, + + // --- TradFi sub types --- + /// + /// Equity, can be stocks, ETFs, or indices + /// + Equity, + /// + /// Commodity, can be oil, gas, metals, etc. + /// + Commodity + } +} diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs index 761c2dcb..7329596d 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesSymbolRestClient.cs @@ -9,6 +9,11 @@ namespace CryptoExchange.Net.SharedApis /// public interface IFuturesSymbolRestClient : ISharedClient { + /// + /// Get the futures symbol catalog. Only available if has been called previously. + /// + SharedSymbolCatalog? FuturesSymbolCatalog { get; } + /// /// Futures symbol request options.
/// Use and to check for required and optional parameters for the request.
diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs index 30d00092..64e16d02 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotSymbolRestClient.cs @@ -9,6 +9,11 @@ namespace CryptoExchange.Net.SharedApis ///
public interface ISpotSymbolRestClient : ISharedClient { + /// + /// Get the spot symbol catalog. Only available if has been called previously. + /// + SharedSymbolCatalog? SpotSymbolCatalog { get; } + /// /// Spot symbols request options.
/// Use and to check for required and optional parameters for the request.
diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs index 8663b466..ab2b92a3 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFuturesSymbolsOptions.cs @@ -1,4 +1,5 @@ -using System; +using CryptoExchange.Net.Objects; +using System; using System.Collections.Generic; using System.Text; @@ -15,5 +16,43 @@ namespace CryptoExchange.Net.SharedApis public GetFuturesSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(IFuturesSymbolRestClient.GetFuturesSymbolsAsync)) { } + + /// + public override Error? ValidateRequest(GetSymbolsRequest request, IFuturesSymbolRestClient client) + { + if (request.BaseAssetType != null && request.BaseAssetSubType != null) + { + var error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); + if (error != null) + return error; + } + + if (request.QuoteAssetType != null && request.QuoteAssetSubType != null) + { + var error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); + if (error != null) + return error; + } + + return base.ValidateRequest(request, client); + } + + private Error? ValidateAssetTypeCombination(SharedAssetType type, SharedAssetSubType subType) + { + if (type == SharedAssetType.Crypto + && (subType == SharedAssetSubType.Commodity + || (subType == SharedAssetSubType.Equity))) + { + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + } + + if (type == SharedAssetType.TradFi && subType == SharedAssetSubType.StableCoin) + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + + if (type == SharedAssetType.Fiat) + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + + return null; + } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs index ac25a78c..db7f463b 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetSpotSymbolsOptions.cs @@ -1,4 +1,5 @@ -using System; +using CryptoExchange.Net.Objects; +using System; using System.Collections.Generic; using System.Text; @@ -15,5 +16,44 @@ namespace CryptoExchange.Net.SharedApis public GetSpotSymbolsOptions(string exchange, bool authenticated) : base(exchange, authenticated, nameof(ISpotSymbolRestClient.GetSpotSymbolsAsync)) { } + + + /// + public override Error? ValidateRequest(GetSymbolsRequest request, ISpotSymbolRestClient client) + { + if (request.BaseAssetType != null && request.BaseAssetSubType != null) + { + var error = ValidateAssetTypeCombination(request.BaseAssetType.Value, request.BaseAssetSubType.Value); + if (error != null) + return error; + } + + if (request.QuoteAssetType != null && request.QuoteAssetSubType != null) + { + var error = ValidateAssetTypeCombination(request.QuoteAssetType.Value, request.QuoteAssetSubType.Value); + if (error != null) + return error; + } + + return base.ValidateRequest(request, client); + } + + private Error? ValidateAssetTypeCombination(SharedAssetType type, SharedAssetSubType subType) + { + if (type == SharedAssetType.Crypto + && (subType == SharedAssetSubType.Commodity + || (subType == SharedAssetSubType.Equity))) + { + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + } + + if (type == SharedAssetType.TradFi && subType == SharedAssetSubType.StableCoin) + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + + if (type == SharedAssetType.Fiat) + return ArgumentError.Invalid(nameof(GetSymbolsRequest.BaseAssetSubType), $"Invalid combination of asset type filters: {type} and {subType}"); + + return null; + } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs index ce71f088..6aa66445 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetSymbolsRequest.cs @@ -5,13 +5,44 @@ ///
public record GetSymbolsRequest : SharedRequest { + /// + /// Base asset type filter + /// + public SharedAssetType? BaseAssetType { get; } + /// + /// Base asset subtype filter + /// + public SharedAssetSubType? BaseAssetSubType { get; } + /// + /// Quote asset type filter + /// + public SharedAssetType? QuoteAssetType { get; } + /// + /// Quote asset subtype filter + /// + public SharedAssetSubType? QuoteAssetSubType { get; } + /// /// ctor /// /// Trading mode filter + /// Filter by base asset type + /// Filter by base asset subtype + /// Filter by quote asset type + /// Filter by quote asset subtype /// Exchange specific parameters - public GetSymbolsRequest(TradingMode? tradingMode = null, ExchangeParameters? exchangeParameters = null) : base(tradingMode, exchangeParameters) + public GetSymbolsRequest( + TradingMode? tradingMode = null, + SharedAssetType? baseAssetType = null, + SharedAssetSubType? baseAssetSubType = null, + SharedAssetType? quoteAssetType = null, + SharedAssetSubType? quoteAssetSubType = null, + ExchangeParameters? exchangeParameters = null) : base(tradingMode, exchangeParameters) { + BaseAssetType = baseAssetType; + BaseAssetSubType = baseAssetSubType; + QuoteAssetType = quoteAssetType; + QuoteAssetSubType = quoteAssetSubType; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/SharedSymbolCatalog.cs b/CryptoExchange.Net/SharedApis/Models/SharedSymbolCatalog.cs new file mode 100644 index 00000000..abd8c422 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/SharedSymbolCatalog.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Symbol and asset catalog for a shared client + /// + public class SharedSymbolCatalog + { + /// + /// Exchange name + /// + public string Exchange { get; set; } = string.Empty; + /// + /// Assets supported + /// + public IReadOnlyDictionary Assets { get; set; } = new Dictionary(); + /// + /// Symbols supported + /// + public IReadOnlyDictionary Symbols { get; set; } = new Dictionary(); + } + + /// + /// Asset info + /// + [DebuggerDisplay("{DebugView,nq}")] + public class SharedAssetInfo + { + private string DebugView => $"{Name} - {Type}{(SubType == null ? "": $" {SubType}")}"; + + /// + /// Asset name + /// + public string Name { get; set; } + /// + /// Asset type + /// + public SharedAssetType Type { get; set; } + /// + /// Asset sub type + /// + public SharedAssetSubType? SubType { get; set; } + + /// + /// ctor + /// + public SharedAssetInfo(string name, SharedAssetType type, SharedAssetSubType? subType) + { + Name = name; + Type = type; + SubType = subType; + } + } +} diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs b/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs index a8718cf2..8db5fb81 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs +++ b/CryptoExchange.Net/SharedApis/ResponseModels/SharedFuturesSymbol.cs @@ -10,7 +10,7 @@ namespace CryptoExchange.Net.SharedApis [DebuggerDisplay("{DebugView,nq}")] public record SharedFuturesSymbol : SharedSpotSymbol { - private string DebugView => $"{TradingMode} {Name}{(DeliveryTime != null ? $" Delivery: {DeliveryTime:yyyy-MM-dd}": "")}"; + private string DebugView => $"{TradingMode} {(DisplayName ?? Name)} - {BaseAssetType}{(BaseAssetSubType == null ? "" : " " + BaseAssetSubType)}{(DeliveryTime != null ? $" Delivery: {DeliveryTime:yyyy-MM-dd}": "")}"; /// /// The size of a single contract diff --git a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs b/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs index 2233f7b7..1eb70235 100644 --- a/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs +++ b/CryptoExchange.Net/SharedApis/ResponseModels/SharedSpotSymbol.cs @@ -1,13 +1,17 @@ -using System.Diagnostics; +using System; +using System.Data; +using System.Diagnostics; namespace CryptoExchange.Net.SharedApis { /// /// Symbol info /// - [DebuggerDisplay("{TradingMode} {Name,nq}")] + [DebuggerDisplay("{DebugView,nq}")] public record SharedSpotSymbol { + private string DebugView => $"{TradingMode} {(DisplayName ?? Name)} - {BaseAssetType} {BaseAssetSubType}"; + /// /// The trading mode of the symbol /// @@ -25,6 +29,10 @@ namespace CryptoExchange.Net.SharedApis /// public string Name { get; set; } /// + /// The display name of the symbol + /// + public string? DisplayName { get; set; } + /// /// Minimal quantity of an order in the base asset /// public decimal? MinTradeQuantity { get; set; } @@ -60,6 +68,22 @@ namespace CryptoExchange.Net.SharedApis /// Whether the symbol is currently available for trading /// public bool Trading { get; set; } + /// + /// Base asset type + /// + public SharedAssetType BaseAssetType { get; set; } + /// + /// Base asset sub type + /// + public SharedAssetSubType? BaseAssetSubType { get; set; } + /// + /// Quote asset type + /// + public SharedAssetType QuoteAssetType { get; set; } + /// + /// Quote asset sub type + /// + public SharedAssetSubType? QuoteAssetSubType { get; set; } /// /// ctor diff --git a/CryptoExchange.Net/SharedApis/SharedUtils.cs b/CryptoExchange.Net/SharedApis/SharedUtils.cs index 426c7f09..b4c098b5 100644 --- a/CryptoExchange.Net/SharedApis/SharedUtils.cs +++ b/CryptoExchange.Net/SharedApis/SharedUtils.cs @@ -1,5 +1,6 @@ using CryptoExchange.Net.Objects; using System.Collections.Generic; +using System.Linq; namespace CryptoExchange.Net.SharedApis { @@ -176,5 +177,21 @@ namespace CryptoExchange.Net.SharedApis return result.ToArray(); } + + public static T[] ApplySymbolFilter(T[] symbols, GetSymbolsRequest request) where T : SharedSpotSymbol + { + IEnumerable resultData = symbols; + if (request.TradingMode != null) + resultData = resultData.Where(x => x.TradingMode == request.TradingMode); + if (request.BaseAssetType != null) + resultData = resultData.Where(x => x.BaseAssetType == request.BaseAssetType); + if (request.QuoteAssetType != null) + resultData = resultData.Where(x => x.QuoteAssetType == request.QuoteAssetType); + if (request.BaseAssetSubType != null) + resultData = resultData.Where(x => x.BaseAssetSubType == request.BaseAssetSubType); + if (request.QuoteAssetSubType != null) + resultData = resultData.Where(x => x.QuoteAssetSubType == request.QuoteAssetSubType); + return resultData.ToArray(); + } } }