/// <summary> /// Take realised profits and then for any remaining amount use virtual profits /// </summary> public async Task <RevenueMoney> CalculateRevenueOfPosition( IList <Order> activeFulfilledTradeOrders, DateTime universeDateTime, ISystemProcessOperationRunRuleContext ctx, IMarketDataCacheStrategy cacheStrategy) { if (activeFulfilledTradeOrders == null || !activeFulfilledTradeOrders.Any()) { Logger.LogInformation($"RevenueCurrencyConvertingCalculator CalculateRevenueOfPosition had a null or empty active fulfilled trade orders. Returning."); return(null); } var realisedRevenue = await CalculateRealisedRevenue(activeFulfilledTradeOrders, _targetCurrency, universeDateTime, ctx); var totalPurchaseVolume = CalculateTotalPurchaseVolume(activeFulfilledTradeOrders); var totalSaleVolume = CalculateTotalSalesVolume(activeFulfilledTradeOrders); var sizeOfVirtualPosition = totalPurchaseVolume - totalSaleVolume; if (sizeOfVirtualPosition <= 0) { Logger.LogInformation($"RevenueCurrencyConvertingCalculator CalculateRevenueOfPosition had no virtual position. Total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Returning the realised revenue only."); // fully traded out position; return its value return(new RevenueMoney(false, realisedRevenue, HighProfitComponents.Realised)); } // has a virtual position; calculate its value var security = activeFulfilledTradeOrders.FirstOrDefault()?.Instrument; if (security == null) { Logger.LogInformation($"RevenueCurrencyConvertingCalculator CalculateRevenueOfPosition had no virtual position. Total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Could not find a security so just returning the realised revenue."); return(new RevenueMoney(false, realisedRevenue, HighProfitComponents.Realised)); } var marketDataRequest = MarketDataRequest( activeFulfilledTradeOrders.FirstOrDefault()?.Market?.MarketIdentifierCode, security.Identifiers, universeDateTime, ctx, cacheStrategy.DataSource); if (marketDataRequest == null) { Logger.LogInformation($"RevenueCurrencyConvertingCalculator CalculateRevenueOfPosition had no virtual position. Total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Returning the realised revenue only."); return(new RevenueMoney(false, null, HighProfitComponents.Realised)); } var marketResponse = cacheStrategy.Query(marketDataRequest); if (marketResponse.HadMissingData()) { Logger.LogInformation($"Revenue currency converting calculator calculating for inferred virtual profits due to missing market data"); return(new RevenueMoney(true, null, HighProfitComponents.Virtual)); } var virtualRevenue = (marketResponse.PriceOrClose()?.Value ?? 0) * sizeOfVirtualPosition; var money = new Money(virtualRevenue, marketResponse.PriceOrClose()?.Currency.Code ?? string.Empty); var convertedVirtualRevenues = await _currencyConverterService.Convert(new[] { money }, _targetCurrency, universeDateTime, ctx); if (realisedRevenue == null && convertedVirtualRevenues == null) { Logger.LogInformation($"Revenue currency converting calculator could not calculate converted virtual revenues, returning null."); return(null); } if (realisedRevenue == null) { Logger.LogInformation($"Revenue currency converting calculator could not calculate realised revenues, returning unrealised revenues."); return(new RevenueMoney(false, convertedVirtualRevenues, HighProfitComponents.Virtual)); } if (convertedVirtualRevenues == null) { Logger.LogInformation($"Revenue currency converting calculator could not calculate virtual revenues. Returning realised revenues."); return(new RevenueMoney(false, realisedRevenue, HighProfitComponents.Realised)); } if (!realisedRevenue.Value.DenominatedInCommonCurrency(convertedVirtualRevenues.Value)) { var convertedMoney = await this._currencyConverterService.Convert( new[] { convertedVirtualRevenues.Value }, realisedRevenue.Value.Currency, universeDateTime, ctx); if (convertedMoney == null) { this.Logger.LogError($"CalculateRevenueOfPosition at {universeDateTime} was unable to convert ({convertedVirtualRevenues.Value.Currency}) {convertedVirtualRevenues.Value.Value} to the realised revenue currency of {realisedRevenue.Value.Currency} due to missing currency conversion data."); return(new RevenueMoney(true, realisedRevenue, HighProfitComponents.Realised)); } convertedVirtualRevenues = convertedMoney.Value; } var totalMoneys = realisedRevenue + convertedVirtualRevenues; var component = realisedRevenue.HasValue && realisedRevenue.Value.Value > 0 ? HighProfitComponents.Hybrid : HighProfitComponents.Virtual; return(new RevenueMoney(false, totalMoneys, component)); }
/// <summary> /// The calculate revenue of position. /// </summary> /// <param name="activeFulfilledTradeOrders"> /// The active fulfilled trade orders. /// </param> /// <param name="universeDateTime"> /// The universe date time. /// </param> /// <param name="ruleRunContext"> /// The rule run context. /// </param> /// <param name="marketCacheStrategy"> /// The market cache strategy. /// </param> /// <returns> /// The <see cref="Task"/>. /// </returns> public async Task <RevenueMoney> CalculateRevenueOfPosition( IList <Order> activeFulfilledTradeOrders, DateTime universeDateTime, ISystemProcessOperationRunRuleContext ruleRunContext, IMarketDataCacheStrategy marketCacheStrategy) { if (activeFulfilledTradeOrders == null || !activeFulfilledTradeOrders.Any()) { this.Logger.LogInformation( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had null or empty active fulfilled trade orders."); return(null); } var realisedRevenue = this.CalculateRealisedRevenue(activeFulfilledTradeOrders); var totalPurchaseVolume = this.CalculateTotalPurchaseVolume(activeFulfilledTradeOrders); var totalSaleVolume = this.CalculateTotalSalesVolume(activeFulfilledTradeOrders); var sizeOfVirtualPosition = totalPurchaseVolume - totalSaleVolume; if (sizeOfVirtualPosition <= 0) { this.Logger.LogInformation( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had a fully traded out position with a total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Returning realised profits only."); // fully traded out position; return its value return(new RevenueMoney(false, realisedRevenue, HighProfitComponents.Realised)); } // has a virtual position; calculate its value var security = activeFulfilledTradeOrders.FirstOrDefault()?.Instrument; if (security == null) { this.Logger.LogWarning( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had a fully traded out position with a total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Was going to calculate a virtual position but could not find security information from the active fulfilled trade orders."); return(new RevenueMoney(false, realisedRevenue, HighProfitComponents.Realised)); } var marketDataRequest = this.MarketDataRequest( activeFulfilledTradeOrders.First().Market.MarketIdentifierCode, security.Identifiers, universeDateTime, ruleRunContext, marketCacheStrategy.DataSource); if (marketDataRequest == null) { this.Logger.LogWarning( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had a fully traded out position with a total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Had a null market data request. Returning null."); return(new RevenueMoney(false, null, HighProfitComponents.Realised)); } var marketDataResult = marketCacheStrategy.Query(marketDataRequest); if (marketDataResult.HadMissingData()) { this.Logger.LogWarning( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had a fully traded out position with a total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Had missing market data so will be calculating the inferred virtual profits instead."); return(new RevenueMoney(true, null, HighProfitComponents.Realised)); } var virtualRevenue = (marketDataResult.PriceOrClose()?.Value ?? 0) * sizeOfVirtualPosition; var money = new Money(virtualRevenue, marketDataResult.PriceOrClose()?.Currency.Code ?? string.Empty); if (realisedRevenue == null) { this.Logger.LogWarning( $"RevenueCalculator CalculateRevenueOfPosition at {universeDateTime} had a fully traded out position with a total purchase volume of {totalPurchaseVolume} and total sale volume of {totalSaleVolume}. Had a null value for realised revenue so returning virtual revenue only of ({money.Currency}) {money.Value}."); return(new RevenueMoney(false, money, HighProfitComponents.Virtual)); } if (!realisedRevenue.Value.DenominatedInCommonCurrency(money)) { var convertedMoney = await this.CurrencyConverterService.Convert( new[] { money }, realisedRevenue.Value.Currency, universeDateTime, ruleRunContext); if (convertedMoney == null) { this.Logger.LogError($"CalculateRevenueOfPosition at {universeDateTime} was unable to convert ({money.Currency}) {money.Value} to the realised revenue currency of {realisedRevenue.Value.Currency} due to missing currency conversion data."); return(new RevenueMoney(true, realisedRevenue, HighProfitComponents.Realised)); } money = convertedMoney.Value; } var totalMoneys = realisedRevenue + money; var component = (realisedRevenue.HasValue && realisedRevenue.Value.Value > 0) ? HighProfitComponents.Hybrid : HighProfitComponents.Virtual; return(await Task.FromResult(new RevenueMoney(false, totalMoneys, component))); }