diff --git a/BitMEX.Net/BitMEX.Net.csproj b/BitMEX.Net/BitMEX.Net.csproj index 799b32b..aafd51a 100644 --- a/BitMEX.Net/BitMEX.Net.csproj +++ b/BitMEX.Net/BitMEX.Net.csproj @@ -52,7 +52,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/BitMEX.Net/BitMEX.Net.xml b/BitMEX.Net/BitMEX.Net.xml index a36e84c..2586c4d 100644 --- a/BitMEX.Net/BitMEX.Net.xml +++ b/BitMEX.Net/BitMEX.Net.xml @@ -150,6 +150,37 @@ + + + + + + + + + + + + + + + + + + ctor + + + + + + + + + + + ctor + + Utility methods for BitMEX @@ -3440,6 +3471,36 @@ Tracker factory + + + Create a new Spot user data tracker + + User identifier + Configuration + Credentials + Environment + + + + Create a new spot user data tracker + + Configuration + + + + Create a new futures user data tracker + + User identifier + Configuration + Credentials + Environment + + + + Create a new futures user data tracker + + Configuration + Api addresses diff --git a/BitMEX.Net/BitMEXTrackerFactory.cs b/BitMEX.Net/BitMEXTrackerFactory.cs index 0e9702a..8045b32 100644 --- a/BitMEX.Net/BitMEXTrackerFactory.cs +++ b/BitMEX.Net/BitMEXTrackerFactory.cs @@ -1,12 +1,17 @@ +using BitMEX.Net.Clients; +using BitMEX.Net.Interfaces; +using BitMEX.Net.Interfaces.Clients; +using CryptoExchange.Net.Authentication; using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.Klines; using CryptoExchange.Net.Trackers.Trades; -using BitMEX.Net.Interfaces; -using BitMEX.Net.Interfaces.Clients; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; -using BitMEX.Net.Clients; namespace BitMEX.Net { @@ -59,5 +64,63 @@ public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, period ); } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new BitMEXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BitMEXSocketClient(); + return new BitMEXUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, BitMEXEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new BitMEXUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new BitMEXUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new BitMEXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BitMEXSocketClient(); + return new BitMEXUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, BitMEXEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new BitMEXUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new BitMEXUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } } } diff --git a/BitMEX.Net/BitMEXUserDataTracker.cs b/BitMEX.Net/BitMEXUserDataTracker.cs new file mode 100644 index 0000000..527bba8 --- /dev/null +++ b/BitMEX.Net/BitMEXUserDataTracker.cs @@ -0,0 +1,63 @@ +using BitMEX.Net.Interfaces.Clients; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Objects; +using Microsoft.Extensions.Logging; + +namespace BitMEX.Net +{ + /// + public class BitMEXUserSpotDataTracker : UserSpotDataTracker + { + /// + /// ctor + /// + public BitMEXUserSpotDataTracker( + ILogger logger, + IBitMEXRestClient restClient, + IBitMEXSocketClient socketClient, + string? userIdentifier, + SpotUserDataTrackerConfig? config) : base( + logger, + restClient.ExchangeApi.SharedClient, + null, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + userIdentifier, + config ?? new SpotUserDataTrackerConfig()) + { + } + } + + /// + public class BitMEXUserFuturesDataTracker : UserFuturesDataTracker + { + /// + protected override bool WebsocketPositionUpdatesAreFullSnapshots => false; + + /// + /// ctor + /// + public BitMEXUserFuturesDataTracker( + ILogger logger, + IBitMEXRestClient restClient, + IBitMEXSocketClient socketClient, + string? userIdentifier, + FuturesUserDataTrackerConfig? config) : base(logger, + restClient.ExchangeApi.SharedClient, + null, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + userIdentifier, + config ?? new FuturesUserDataTrackerConfig()) + { + } + } +} diff --git a/BitMEX.Net/Clients/BitMEXUserClientProvider.cs b/BitMEX.Net/Clients/BitMEXUserClientProvider.cs index 781a23f..a53383c 100644 --- a/BitMEX.Net/Clients/BitMEXUserClientProvider.cs +++ b/BitMEX.Net/Clients/BitMEXUserClientProvider.cs @@ -64,7 +64,7 @@ public void ClearUserClients(string userIdentifier) /// public IBitMEXRestClient GetRestClient(string userIdentifier, ApiCredentials? credentials = null, BitMEXEnvironment? environment = null) { - if (!_restClients.TryGetValue(userIdentifier, out var client)) + if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed) client = CreateRestClient(userIdentifier, credentials, environment); return client; @@ -73,7 +73,7 @@ public IBitMEXRestClient GetRestClient(string userIdentifier, ApiCredentials? cr /// public IBitMEXSocketClient GetSocketClient(string userIdentifier, ApiCredentials? credentials = null, BitMEXEnvironment? environment = null) { - if (!_socketClients.TryGetValue(userIdentifier, out var client)) + if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed) client = CreateSocketClient(userIdentifier, credentials, environment); return client; diff --git a/BitMEX.Net/Clients/ExchangeApi/BitMEXRestClientExchangeApiShared.cs b/BitMEX.Net/Clients/ExchangeApi/BitMEXRestClientExchangeApiShared.cs index fda846a..5626012 100644 --- a/BitMEX.Net/Clients/ExchangeApi/BitMEXRestClientExchangeApiShared.cs +++ b/BitMEX.Net/Clients/ExchangeApi/BitMEXRestClientExchangeApiShared.cs @@ -181,7 +181,13 @@ async Task> IDepositRestClient.GetDepositsAsy nextToken = new OffsetToken((offset ?? 0) + deposits.Data.Count()); return deposits.AsExchangeResult(Exchange, TradingMode.Spot, - deposits.Data.Where(x => x.TransactionType == TransactionType.Deposit).Select(x => new SharedDeposit(BitMEXExchange.AssetAliases.ExchangeToCommonName(BitMEXUtils.GetAssetFromCurrency(x.Currency)), x.Quantity.ToSharedAssetQuantity(x.Currency), x.TransactionStatus == TransactionStatus.Completed, x.TransactionTime) + deposits.Data.Where(x => x.TransactionType == TransactionType.Deposit).Select(x => + new SharedDeposit( + BitMEXExchange.AssetAliases.ExchangeToCommonName(BitMEXUtils.GetAssetFromCurrency(x.Currency)), + x.Quantity.ToSharedAssetQuantity(x.Currency), + x.TransactionStatus == TransactionStatus.Completed, + x.TransactionTime, + x.TransactionStatus == TransactionStatus.Completed ? SharedTransferStatus.Completed : SharedTransferStatus.Failed) { Network = x.Network, TransactionId = x.Transaction, @@ -522,6 +528,44 @@ async Task> ISpotSymbolRestClient.GetSpotS return response; } + async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset)); + } + + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode != TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol)); + } + + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName)); + } #endregion #region Spot Ticker client @@ -1064,6 +1108,44 @@ async Task> IFuturesSymbolRestClient.Ge return response; } + async Task> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset)); + } + + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode == TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Spot symbols not allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol)); + } + + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName)); + } #endregion #region Futures Ticker client @@ -1146,7 +1228,7 @@ async Task> ILeverageRestClient.GetLeverageAsy return result.AsExchangeError(Exchange, new ServerError(new ErrorInfo(ErrorType.NoPosition, "Position not found"))); var position = result.Data.First(); - return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(position.Leverage) + return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(position.Leverage ?? 0) { MarginMode = position.CrossMargin ? SharedMarginMode.Cross : SharedMarginMode.Isolated }); @@ -1171,7 +1253,7 @@ async Task> ILeverageRestClient.SetLeverageAsy if (!result) return result.AsExchangeResult(Exchange, null, default); - return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(result.Data.Leverage) { MarginMode = request.MarginMode }); + return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(result.Data.Leverage ?? 0) { MarginMode = request.MarginMode }); } else { @@ -1179,7 +1261,7 @@ async Task> ILeverageRestClient.SetLeverageAsy if (!result) return result.AsExchangeResult(Exchange, null, default); - return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(result.Data.Leverage) { MarginMode = request.MarginMode }); + return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(result.Data.Leverage ?? 0) { MarginMode = request.MarginMode }); } } @@ -1524,6 +1606,7 @@ async Task> IFuturesOrderRestClient.GetPosit LiquidationPrice = x.LiquidationPrice == 0 ? null : x.LiquidationPrice, Leverage = x.Leverage, AverageOpenPrice = x.AverageEntryPrice, + PositionMode = SharedPositionMode.OneWay, PositionSide = x.CurrentQuantity < 0 ? SharedPositionSide.Short : SharedPositionSide.Long }).ToArray()); } diff --git a/BitMEX.Net/Clients/ExchangeApi/BitMEXSocketClientExchangeApiShared.cs b/BitMEX.Net/Clients/ExchangeApi/BitMEXSocketClientExchangeApiShared.cs index 48af957..80b5406 100644 --- a/BitMEX.Net/Clients/ExchangeApi/BitMEXSocketClientExchangeApiShared.cs +++ b/BitMEX.Net/Clients/ExchangeApi/BitMEXSocketClientExchangeApiShared.cs @@ -397,6 +397,7 @@ async Task> IPositionSocketClient.SubscribeTo update => handler(update.ToType(update.Data.Where(x => x.Currency != null).Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, Math.Abs(x.CurrentQuantity ?? 0), x.Timestamp) { AverageOpenPrice = x.AverageEntryPrice, + PositionMode = SharedPositionMode.OneWay, PositionSide = x.CurrentQuantity < 0 ? SharedPositionSide.Short : SharedPositionSide.Long, UnrealizedPnl = x.UnrealizedPnl.ToSharedAssetQuantity(x.Currency!), Leverage = x.Leverage, diff --git a/BitMEX.Net/Interfaces/IBitMEXTrackerFactory.cs b/BitMEX.Net/Interfaces/IBitMEXTrackerFactory.cs index 23891af..0a15911 100644 --- a/BitMEX.Net/Interfaces/IBitMEXTrackerFactory.cs +++ b/BitMEX.Net/Interfaces/IBitMEXTrackerFactory.cs @@ -1,4 +1,8 @@ +using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; namespace BitMEX.Net.Interfaces { @@ -7,5 +11,32 @@ namespace BitMEX.Net.Interfaces /// public interface IBitMEXTrackerFactory : ITrackerFactory { + /// + /// Create a new Spot user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, BitMEXEnvironment? environment = null); + /// + /// Create a new spot user data tracker + /// + /// Configuration + IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null); + + /// + /// Create a new futures user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, BitMEXEnvironment? environment = null); + /// + /// Create a new futures user data tracker + /// + /// Configuration + IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null); } } diff --git a/BitMEX.Net/Objects/Models/BitMEXPosition.cs b/BitMEX.Net/Objects/Models/BitMEXPosition.cs index 5325943..cbad60c 100644 --- a/BitMEX.Net/Objects/Models/BitMEXPosition.cs +++ b/BitMEX.Net/Objects/Models/BitMEXPosition.cs @@ -39,27 +39,27 @@ public record BitMEXPosition /// The maximum of the maker, taker, and settlement fee. /// [JsonPropertyName("commission")] - public decimal FeeRate { get; set; } + public decimal? FeeRate { get; set; } /// /// Initial margin requirement /// [JsonPropertyName("initMarginReq")] - public decimal InitialMarginRequirement { get; set; } + public decimal? InitialMarginRequirement { get; set; } /// /// Maintenance margin requirement /// [JsonPropertyName("maintMarginReq")] - public decimal MaintenanceMarginRequirement { get; set; } + public decimal? MaintenanceMarginRequirement { get; set; } /// /// Risk limit /// [JsonPropertyName("riskLimit")] - public decimal RiskLimit { get; set; } + public decimal? RiskLimit { get; set; } /// /// Leverage /// [JsonPropertyName("leverage")] - public decimal Leverage { get; set; } + public decimal? Leverage { get; set; } /// /// Cross margin /// @@ -74,22 +74,22 @@ public record BitMEXPosition /// Rebalanced profit and loss /// [JsonPropertyName("rebalancedPnl")] - public long RebalancedPnl { get; set; } + public long? RebalancedPnl { get; set; } /// /// Previous realised profit and loss /// [JsonPropertyName("prevRealisedPnl")] - public long PreviousRealisedPnl { get; set; } + public long? PreviousRealisedPnl { get; set; } /// /// Previous unrealised profit and loss /// [JsonPropertyName("prevUnrealisedPnl")] - public long PreviousUnrealisedPnl { get; set; } + public long? PreviousUnrealisedPnl { get; set; } /// /// Opening quantity /// [JsonPropertyName("openingQty")] - public long OpeningQuantity { get; set; } + public long? OpeningQuantity { get; set; } /// /// Open order buy quantity /// @@ -104,7 +104,7 @@ public record BitMEXPosition /// Open order buy premium /// [JsonPropertyName("openOrderBuyPremium")] - public decimal OpenOrderBuyPremium { get; set; } + public decimal? OpenOrderBuyPremium { get; set; } /// /// Open order sell quantity /// @@ -119,7 +119,7 @@ public record BitMEXPosition /// Open order sell premium /// [JsonPropertyName("openOrderSellPremium")] - public decimal OpenOrderSellPremium { get; set; } + public decimal? OpenOrderSellPremium { get; set; } /// /// Current quantity /// @@ -129,27 +129,27 @@ public record BitMEXPosition /// Current cost /// [JsonPropertyName("currentCost")] - public decimal CurrentCost { get; set; } + public decimal? CurrentCost { get; set; } /// /// Current commissions /// [JsonPropertyName("currentComm")] - public long CurrentCommissions { get; set; } + public long? CurrentCommissions { get; set; } /// /// Realised cost /// [JsonPropertyName("realisedCost")] - public decimal RealisedCost { get; set; } + public decimal? RealisedCost { get; set; } /// /// Unrealised cost /// [JsonPropertyName("unrealisedCost")] - public decimal UnrealisedCost { get; set; } + public decimal? UnrealisedCost { get; set; } /// /// Gross open premium /// [JsonPropertyName("grossOpenPremium")] - public decimal GrossOpenPremium { get; set; } + public decimal? GrossOpenPremium { get; set; } /// /// Is open /// @@ -164,22 +164,22 @@ public record BitMEXPosition /// Mark value /// [JsonPropertyName("markValue")] - public decimal MarkValue { get; set; } + public decimal? MarkValue { get; set; } /// /// Risk value /// [JsonPropertyName("riskValue")] - public decimal RiskValue { get; set; } + public decimal? RiskValue { get; set; } /// /// Home notional /// [JsonPropertyName("homeNotional")] - public decimal HomeNotional { get; set; } + public decimal? HomeNotional { get; set; } /// /// Foreign notional /// [JsonPropertyName("foreignNotional")] - public decimal ForeignNotional { get; set; } + public decimal? ForeignNotional { get; set; } /// /// Position status /// @@ -189,62 +189,62 @@ public record BitMEXPosition /// Position cost /// [JsonPropertyName("posCost")] - public decimal PositionCost { get; set; } + public decimal? PositionCost { get; set; } /// /// Position cross /// [JsonPropertyName("posCross")] - public decimal PositionCross { get; set; } + public decimal? PositionCross { get; set; } /// /// Position commissions /// [JsonPropertyName("posComm")] - public long PositionCommissions { get; set; } + public long? PositionCommissions { get; set; } /// /// Position loss /// [JsonPropertyName("posLoss")] - public decimal PositionLoss { get; set; } + public decimal? PositionLoss { get; set; } /// /// Position margin /// [JsonPropertyName("posMargin")] - public long PositionMargin { get; set; } + public long? PositionMargin { get; set; } /// /// Position maintenance /// [JsonPropertyName("posMaint")] - public long PositionMaintenance { get; set; } + public long? PositionMaintenance { get; set; } /// /// Initial margin /// [JsonPropertyName("initMargin")] - public decimal InitialMargin { get; set; } + public decimal? InitialMargin { get; set; } /// /// Maintenance margin /// [JsonPropertyName("maintMargin")] - public decimal MaintenanceMargin { get; set; } + public decimal? MaintenanceMargin { get; set; } /// /// Realised profit and loss /// [JsonPropertyName("realisedPnl")] - public long RealisedPnl { get; set; } + public long? RealisedPnl { get; set; } /// /// Unrealised profit and loss /// [JsonPropertyName("unrealisedPnl")] - public long UnrealizedPnl { get; set; } + public long? UnrealizedPnl { get; set; } /// /// Unrealised profit and loss percentage /// [JsonPropertyName("unrealisedPnlPcnt")] - public decimal UnrealisedPnlPcnt { get; set; } + public decimal? UnrealisedPnlPcnt { get; set; } /// /// Unrealised roe percentage /// [JsonPropertyName("unrealisedRoePcnt")] - public decimal UnrealisedRoePercentage { get; set; } + public decimal? UnrealisedRoePercentage { get; set; } /// /// Average cost price ///