public static decimal CalculateIRR(this IReadOnlyPortfolio portfolio, DateRange dateRange)
        {
            // Get the initial portfolio value
            var initialHoldings      = portfolio.Holdings.All(dateRange.FromDate);
            var initialHoldingsValue = initialHoldings.Sum(x => x.Value(dateRange.FromDate));
            var initialCashBalance   = portfolio.CashAccount.Balance(dateRange.FromDate);
            var initialValue         = initialHoldingsValue + initialCashBalance;

            // Get the final portfolio value
            var finalHoldings      = portfolio.Holdings.All(dateRange.ToDate);
            var finalHoldingsValue = finalHoldings.Sum(x => x.Value(dateRange.ToDate));
            var finalCashBalance   = portfolio.CashAccount.Balance(dateRange.ToDate);
            var finalValue         = finalHoldingsValue + finalCashBalance;

            // Generate list of cashFlows
            var cashFlows        = new CashFlows();
            var transactionRange = new DateRange(dateRange.FromDate.AddDays(1), dateRange.ToDate);
            var transactions     = portfolio.CashAccount.Transactions.InDateRange(transactionRange)
                                   .Where(x => (x.Type == BankAccountTransactionType.Deposit) || ((x.Type == BankAccountTransactionType.Withdrawl)));

            foreach (var transaction in transactions)
            {
                cashFlows.Add(transaction.Date, -transaction.Amount);
            }


            var irr = IrrCalculator.CalculateIrr(dateRange.FromDate, initialValue, dateRange.ToDate, finalValue, cashFlows);

            return((decimal)Math.Round(irr, 5));
        }
        public void NoCashflows()
        {
            var cashFlows = new CashFlows();

            var result = IrrCalculator.CalculateIrr(new Date(2000, 01, 01), 1000.00m, new Date(2005, 12, 31), 1500.00m, cashFlows);

            result.Should().BeApproximately(0.0698802d, 0.000001d);
        }
        public void SingleCashFlow()
        {
            var cashFlows = new CashFlows();

            cashFlows.Add(new Date(2001, 01, 01), -1000.00m);

            var result = IrrCalculator.CalculateIrr(new Date(2000, 01, 01), 1000.00m, new Date(2005, 12, 31), 2500.00m, cashFlows);

            result.Should().BeApproximately(0.0413562d, 0.000001d);
        }
        public void TwoCashFlow()
        {
            var cashFlows = new CashFlows();

            cashFlows.Add(new Date(2001, 01, 01), -1000.00m);
            cashFlows.Add(new Date(2002, 01, 01), 500.00m);

            var result = IrrCalculator.CalculateIrr(new Date(2000, 01, 01), 1000.00m, new Date(2005, 12, 31), 5000.00m, cashFlows);

            result.Should().BeApproximately(0.2244317d, 0.000001d);
        }
        public void MoreThanOneYear()
        {
            var cashFlows = new CashFlows();

            cashFlows.Add(new Date(2001, 01, 01), -1500.00m);
            cashFlows.Add(new Date(2002, 01, 01), 500.00m);
            cashFlows.Add(new Date(2003, 01, 01), 2000.00m);
            cashFlows.Add(new Date(2004, 01, 01), -5000.00m);
            cashFlows.Add(new Date(2005, 01, 01), 100.00m);

            var result = IrrCalculator.CalculateIrr(new Date(2000, 01, 01), 1000.00m, new Date(2005, 12, 31), 10000.00m, cashFlows);

            result.Should().BeApproximately(0.2220352d, 0.000001d);
        }
        public void LessThanOneYear()
        {
            var cashFlows = new CashFlows();

            cashFlows.Add(new Date(2000, 02, 01), -1000.00m);
            cashFlows.Add(new Date(2000, 03, 01), 500.00m);
            cashFlows.Add(new Date(2000, 04, 01), -1500.00m);
            cashFlows.Add(new Date(2000, 05, 01), -1500.00m);
            cashFlows.Add(new Date(2000, 06, 01), 500.00m);
            cashFlows.Add(new Date(2000, 07, 01), -2000.00m);

            var result = IrrCalculator.CalculateIrr(new Date(2000, 01, 01), 1000.00m, new Date(2000, 08, 30), 10000.00m, cashFlows);

            result.Should().BeApproximately(2.5071595d, 0.000001d);
        }
        public ServiceResult <PortfolioPerformanceResponse> GetPerformance(DateRange dateRange)
        {
            if (_Portfolio == null)
            {
                return(ServiceResult <PortfolioPerformanceResponse> .NotFound());
            }

            var response = new PortfolioPerformanceResponse();

            var dateRangeExcludingFirstDay = new DateRange(dateRange.FromDate.AddDays(1), dateRange.ToDate);

            var openingHoldings = _Portfolio.Holdings.All(dateRange.FromDate);
            var closingHoldings = _Portfolio.Holdings.All(dateRange.ToDate);


            var workingList = new List <HoldingPerformanceWorkItem>();

            HoldingPerformanceWorkItem workItem;

            // Add opening holdings
            foreach (var holding in openingHoldings)
            {
                workItem = new HoldingPerformanceWorkItem(holding.Stock.ToSummaryResponse(dateRange.FromDate));

                var value = holding.Value(dateRange.FromDate);

                workItem.HoldingPerformance.OpeningBalance = value;
                workItem.StartDate    = dateRange.FromDate;
                workItem.InitialValue = value;

                workingList.Add(workItem);
            }

            // Process transactions during the period
            var transactions = _Portfolio.Transactions.InDateRange(dateRangeExcludingFirstDay);

            foreach (var transaction in transactions)
            {
                if ((transaction is Aquisition) ||
                    (transaction is OpeningBalance) ||
                    (transaction is Disposal) ||
                    (transaction is IncomeReceived))
                {
                    var newItem = false;

                    workItem = workingList.FirstOrDefault(x => x.HoldingPerformance.Stock.Id == transaction.Stock.Id);
                    if (workItem == null)
                    {
                        newItem  = true;
                        workItem = new HoldingPerformanceWorkItem(transaction.Stock.ToSummaryResponse(dateRange.FromDate));
                        workItem.HoldingPerformance.OpeningBalance = 0.00m;
                        workingList.Add(workItem);
                    }

                    if (transaction is Aquisition aquisition)
                    {
                        var value = aquisition.Units * aquisition.AveragePrice;

                        workItem.HoldingPerformance.Purchases += value;
                        if (newItem)
                        {
                            workItem.StartDate    = aquisition.Date;
                            workItem.InitialValue = value;
                        }
                        else
                        {
                            workItem.CashFlows.Add(aquisition.Date, -value);
                        }
                    }
                    else if (transaction is OpeningBalance openingBalance)
                    {
                        workItem.HoldingPerformance.Purchases += openingBalance.CostBase;

                        if (newItem)
                        {
                            workItem.StartDate    = openingBalance.Date;
                            workItem.InitialValue = openingBalance.CostBase;
                        }
                        else
                        {
                            workItem.CashFlows.Add(openingBalance.Date, -openingBalance.CostBase);
                        }
                    }
                    else if (transaction is Disposal disposal)
                    {
                        var value = disposal.Units * disposal.AveragePrice;

                        workItem.HoldingPerformance.Sales += value;
                        workItem.CashFlows.Add(disposal.Date, value);
                    }
                    else if (transaction is IncomeReceived income)
                    {
                        workItem.HoldingPerformance.Dividends += income.CashIncome;
                        workItem.CashFlows.Add(income.Date, income.CashIncome);
                    }
                }
            }

            // Populate HoldingPerformance from work list
            foreach (var item in workingList)
            {
                //    var holding = closingHoldings.FirstOrDefault(x => x.Stock.Id == item.HoldingPerformance.Stock.Id);
                var holding = _Portfolio.Holdings[item.HoldingPerformance.Stock.Id];

                if (holding.EffectivePeriod.ToDate < dateRange.ToDate)
                {
                    // Holding sold before period ended
                    item.HoldingPerformance.ClosingBalance = 0.00m;
                    item.EndDate    = holding.EffectivePeriod.ToDate;
                    item.FinalValue = 0.00m;
                    item.HoldingPerformance.DrpCashBalance = 0.00m;
                }
                else
                {
                    // Holding still held at period end
                    var value = holding.Value(dateRange.ToDate);
                    item.HoldingPerformance.ClosingBalance = value;

                    item.EndDate    = dateRange.ToDate;
                    item.FinalValue = value;

                    item.HoldingPerformance.DrpCashBalance = holding.DrpAccount.Balance(dateRange.ToDate);
                }

                item.HoldingPerformance.CapitalGain = item.HoldingPerformance.ClosingBalance - (item.HoldingPerformance.OpeningBalance + item.HoldingPerformance.Purchases - item.HoldingPerformance.Sales);
                item.HoldingPerformance.TotalReturn = item.HoldingPerformance.CapitalGain + item.HoldingPerformance.Dividends;

                var irr = IrrCalculator.CalculateIrr(item.StartDate, item.InitialValue, item.EndDate, item.FinalValue, item.CashFlows);
                if (double.IsNaN(irr) || double.IsInfinity(irr))
                {
                    item.HoldingPerformance.Irr = 0.00M;
                }
                else
                {
                    item.HoldingPerformance.Irr = (decimal)Math.Round(irr, 5);
                }

                response.HoldingPerformance.Add(item.HoldingPerformance);
            }

            var cashTransactions = _Portfolio.CashAccount.Transactions.InDateRange(dateRangeExcludingFirstDay);

            response.OpeningCashBalance = _Portfolio.CashAccount.Balance(dateRange.FromDate);
            response.Deposits           = cashTransactions.Where(x => x.Type == BankAccountTransactionType.Deposit).Sum(x => x.Amount);
            response.Withdrawls         = cashTransactions.Where(x => x.Type == BankAccountTransactionType.Withdrawl).Sum(x => x.Amount);
            response.Interest           = cashTransactions.Where(x => x.Type == BankAccountTransactionType.Interest).Sum(x => x.Amount);
            response.Fees = cashTransactions.Where(x => x.Type == BankAccountTransactionType.Fee).Sum(x => x.Amount);
            response.ClosingCashBalance = _Portfolio.CashAccount.Balance(dateRange.ToDate);

            response.OpeningBalance       = openingHoldings.Sum(x => x.Value(dateRange.FromDate));
            response.Dividends            = response.HoldingPerformance.Sum(x => x.Dividends);
            response.ChangeInMarketValue  = response.HoldingPerformance.Sum(x => x.CapitalGain);
            response.OutstandingDRPAmount = -response.HoldingPerformance.Sum(x => x.DrpCashBalance);
            response.ClosingBalance       = closingHoldings.Sum(x => x.Value(dateRange.ToDate));


            return(ServiceResult <PortfolioPerformanceResponse> .Ok(response));
        }