/// <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)));
        }