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