public InterestCalculator(decimal sumMoney, decimal interest, byte years, InterestCalculation interestCalc) { this.SumMoney = sumMoney; this.Interest = interest; this.Years = years; this.InterestCalc = interestCalc; }
public InterestCalculator(decimal sumMoney, decimal interest, byte years, InterestCalculation interestCalc) { this.SumMoney = sumMoney; this.Interest = interest; this.Years = years; this.InterestCalc = interestCalc; }
public static double CalculateAnnuity( DateTime repaymentPeriodStartDate, DateTime firstInstallmentDate, int numberOfInstallments, int installmentFrequencyPeriod, SimpleUnitOfTime installmentFrequencyUnitOfTime, double ratePercentage, SimpleUnitOfTime rateUnitOfTime, bool isCompound, CalendarBasisKind calendarBasis, double principalAmount, bool adjustFirstInstallment = false) { DateTime d2 = firstInstallmentDate.AddPeriod(installmentFrequencyPeriod * (numberOfInstallments - 1), installmentFrequencyUnitOfTime); double dummyAnnuity = 100; double outstanding = 0; double basis = 0; double interest = 0; DateTime d1; while (d2 > firstInstallmentDate) { d1 = d2.AddPeriod(-installmentFrequencyPeriod, installmentFrequencyUnitOfTime); basis = outstanding + dummyAnnuity; interest = InterestCalculation.CalculateInterest(d2, d1, d1, ratePercentage, rateUnitOfTime.CharLiteral(), isCompound, calendarBasis, basis).Sum(it => it.Interest); outstanding = basis + interest; d2 = d1; } d1 = repaymentPeriodStartDate; if (adjustFirstInstallment) { var prevDate = firstInstallmentDate.AddPeriod(-installmentFrequencyPeriod, installmentFrequencyUnitOfTime); if (prevDate < repaymentPeriodStartDate) { d1 = prevDate; } } basis = outstanding + dummyAnnuity; interest = InterestCalculation.CalculateInterest(d2, d1, d1, ratePercentage, rateUnitOfTime.CharLiteral(), isCompound, calendarBasis, basis).Sum(it => it.Interest); outstanding = basis + interest; return(principalAmount / outstanding * dummyAnnuity); }
static void Main() { //InterestCalculation overallSimpleInterest = (sumMoney, interest, years) // => // { // return Math.Round(sumMoney *(1 + years * interest / 100), 4); // }; InterestCalculation overallSimpleInterest = GetSimpleInterest; var simpleInterest = new InterestCalculator(2500, 7.2m, 15, overallSimpleInterest); Console.WriteLine("Simple interest sum: " + simpleInterest.CalcInterest()); InterestCalculation overallCompoundInterest = GetCompoundInterest; var compoundInterest = new InterestCalculator(500, 5.6m, 10, overallCompoundInterest); Console.WriteLine("Compound interest sum: " + compoundInterest.CalcInterest()); }
public override void CalculateOffer(PriceCalculationParameters calculationParameters, OfferPriceCalculation priceCalculator, string feeCurrencyConversionMethod) { var ignore = priceCalculator.CalculatePrice(this, calculationParameters).Result; // isCompound i calendarBasis izvuci tokom CalculatePrice da dodatno opisu Napr bool isCompound = false; CalendarBasisKind calendarBasis = CalendarBasisKind.CalendarActActISDA; double yearlyRate = Convert.ToDouble(Napr) / 100; var ir = Conditions.InterestRates.FirstOrDefault(it => it.Kind == InterestRateKinds.RegularInterest); if (ir != null) { isCompound = ir.IsCompound; calendarBasis = ir.CalendarBasis; yearlyRate = Convert.ToDouble(ir.CalculatedRate); Napr = Convert.ToDecimal(yearlyRate); } var startDate = calculationParameters.RequestDate ?? DateTime.Today; List <InstallmentPlanRow> rows = new List <InstallmentPlanRow>(); List <FeeCondition> rowFeeList = new List <FeeCondition>(); List <FeeCondition> fees = Conditions.Fees; fees = FeeCalculation.PrepareFees(fees, Currency, feeCurrencyConversionMethod); decimal runningAmount = Math.Round(Amount, 2); DateTime runningDate = startDate; if (CalculationDate > DateTime.Today) { CalculationDate = runningDate; } else { runningDate = CalculationDate ?? DateTime.Today; } // SLOBA: Fee scheduling, and scheduling in general has to be improved in advanced calculator. decimal totalFeeAmount = 0; if (fees != null) { List <FeeCondition> feeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.EventTriggered && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); totalFeeAmount = Math.Round(FeeCalculation.CalculateFee(Amount, feeList), 4); } NumberOfInstallments = 1; /* Disbursment */ InstallmentPlanRow rowDisbursement = new InstallmentPlanRow(); rowDisbursement.Date = runningDate; rowDisbursement.Ordinal = 0; rowDisbursement.ActivityKind = ActivityKind.Disbursement; rowDisbursement.Description = "Loan disbursement"; rowDisbursement.Disbursement = Amount; rowDisbursement.StartingBalance = 0; rowDisbursement.OutstandingBalance = Amount; rowDisbursement.PrincipalRepayment = 0; rowDisbursement.InterestRepayment = 0; rowDisbursement.NetCashFlow = Amount - totalFeeAmount; rowDisbursement.Fee = totalFeeAmount; rowDisbursement.YearFrac = 0; rows.Add(rowDisbursement); int i = -1; InstallmentPlanRow rowInterest; var intrStartDate = startDate; runningDate = runningDate.AddMonths(1); while (runningDate <= MaturityDate.Value) { i++; var interest = Math.Round(InterestCalculation.CalculateInterest(intrStartDate, runningDate, runningDate, Convert.ToDouble(yearlyRate), 'Y', isCompound, calendarBasis, Convert.ToDouble(runningAmount)) .Sum(it => it.Interest), 2); rowInterest = new InstallmentPlanRow(); rowInterest.Ordinal = i + 1; rowInterest.Date = runningDate; rowInterest.ActivityKind = ActivityKind.Repayment; rowInterest.Description = "Interest and fee repayment"; rowInterest.InterestRepayment = Convert.ToDecimal(interest); rowInterest.PrincipalRepayment = 0; rowInterest.Annuity = rowInterest.InterestRepayment; rowInterest.StartingBalance = Math.Round(runningAmount, 2); rowInterest.OutstandingBalance = Math.Round(runningAmount, 2); #region Fee calculation if (fees != null) { decimal totalRowFee = 0; rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Monthly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } if ((i + 1) % 4 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Quarterly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 6 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Semiyearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } if ((i + 1) % 12 == 0) { rowFeeList = fees.FindAll(f => f.Frequency == FeeConditionFrequency.Yearly && f.EffectiveDate <= DateTime.Today && f.Currencies.Contains(Currency)); if (rowFeeList.Count > 0) { totalRowFee += FeeCalculation.CalculateFee(runningAmount, rowFeeList); } } rowInterest.Fee = Math.Round(totalRowFee, 4); } #endregion rowInterest.NetCashFlow = rowInterest.Disbursement - rowInterest.PrincipalRepayment - rowInterest.InterestRepayment - rowInterest.Fee - rowInterest.OtherExpenses; rowInterest.YearFrac = Convert.ToDecimal(InterestCalculation.YearFrac(startDate, runningDate)); //, calendarBasis); rows.Add(rowInterest); intrStartDate = runningDate; runningDate = runningDate.AddMonths(1); } i++; rowInterest = new InstallmentPlanRow(); rowInterest.Ordinal = i + 1; rowInterest.Date = runningDate; rowInterest.ActivityKind = ActivityKind.Repayment; rowInterest.Description = "Principal repayment"; rowInterest.InterestRepayment = 0; rowInterest.PrincipalRepayment = Amount; rowInterest.Annuity = Amount; rowInterest.StartingBalance = Amount; rowInterest.OutstandingBalance = 0; #region Fee calculation rowInterest.Fee = 0; #endregion rowInterest.NetCashFlow = rowInterest.Disbursement - rowInterest.PrincipalRepayment - rowInterest.InterestRepayment - rowInterest.Fee - rowInterest.OtherExpenses; rowInterest.YearFrac = Convert.ToDecimal(InterestCalculation.YearFrac(startDate, runningDate)); //, calendarBasis); rows.Add(rowInterest); NumberOfInstallments = 1; // arrangementRequest.InstallmentPlan = rows; CalculateAPR(rows); }
public static InstallmentPlanCalculationResult CalculateInstallmentPlan(SimpleLoanCalculationRequest request) { if (request.NumberOfInstallments == 0) { request.NumberOfInstallments = 1; } if (request.CalculationTarget == CalculationTarget.Term) { request.NumberOfInstallments = 12 * 200; // this will be cut off } //request.StardDate = request.StardDate.Date; // default installment schedule if (request.InstallmentSchedule == null) { request.InstallmentSchedule = new SimpleSchedule(); request.InstallmentSchedule.DayOfMonth = request.StardDate.Day; request.InstallmentSchedule.FrequencyPeriod = 1; request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M; } if (request.InstallmentSchedule.DayOfMonth == 0) { request.InstallmentSchedule.DayOfMonth = request.StardDate.Day; } // default interest schedule if (request.InterestSchedule == null) { request.InterestSchedule = new SimpleSchedule(); request.InterestSchedule.DayOfMonth = request.InstallmentSchedule.DayOfMonth; request.InterestSchedule.FrequencyPeriod = request.InstallmentSchedule.FrequencyPeriod; request.InterestSchedule.FrequencyUnitOfTime = request.InstallmentSchedule.FrequencyUnitOfTime; } int dayOfMonth = request.InstallmentSchedule.DayOfMonth; DateTime startDate = request.StardDate; DateTime repaymentPeriodStartDate = request.StardDate; DateTime firstInstallmentDate = new DateTime(startDate.Year, startDate.Month, dayOfMonth); DateTime firstInterestDate = new DateTime(startDate.Year, startDate.Month, request.InterestSchedule.DayOfMonth); if (firstInterestDate <= startDate) { firstInterestDate = firstInterestDate.AddMonths(1); } if (firstInstallmentDate < startDate) { firstInstallmentDate = firstInstallmentDate.AddMonths(1); } Decimal intercalarInterest = 0; if (request.RegularInterest.Count != 1) { throw new NotImplementedException("Currently support only single interest rate entry"); } var inr = request.RegularInterest.FirstOrDefault(); if (firstInstallmentDate == startDate) { firstInstallmentDate = firstInstallmentDate.AddPeriod(request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime); } if (firstInterestDate == startDate) { firstInterestDate = firstInterestDate.AddPeriod(request.InterestSchedule.FrequencyPeriod, request.InterestSchedule.FrequencyUnitOfTime); } // for days 29..31 firstInstallmentDate = firstInstallmentDate.MoveTo(request.InstallmentSchedule.DayOfMonth); firstInterestDate = firstInterestDate.MoveTo(request.InterestSchedule.DayOfMonth); bool hasIntercalar = false; if (startDate.AddDays(request.MinimumDaysForFirstInstallment) > firstInstallmentDate) { // This means that date of first interest calculation and first installment will differ // Intercalar period must be forced repaymentPeriodStartDate = firstInterestDate; firstInstallmentDate = repaymentPeriodStartDate.AddPeriod(request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime); hasIntercalar = true; } decimal annuity = request.Amount / request.NumberOfInstallments; if (request.RepaymentType == RepaymentType.FixedAnnuity) { if (request.CalculationTarget == CalculationTarget.Amount) { decimal dummyAmount = 100000; annuity = (decimal)CalculateAnnuity(repaymentPeriodStartDate, firstInstallmentDate, request.NumberOfInstallments, request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime, inr.RatePercentage, inr.RateUnitOfTime, inr.IsCompound, inr.CalendarBasis, (double)dummyAmount, request.AdjustFirstInstallment); request.Amount = Math.Round(dummyAmount / annuity * request.Annuity, 2, MidpointRounding.ToEven); annuity = request.Annuity; } } if (request.CalculationTarget == CalculationTarget.Annuity && request.RepaymentType == RepaymentType.FixedAnnuity) { annuity = (decimal)CalculateAnnuity(repaymentPeriodStartDate, firstInstallmentDate, request.NumberOfInstallments, request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime, inr.RatePercentage, inr.RateUnitOfTime, inr.IsCompound, inr.CalendarBasis, (double)request.Amount, request.AdjustFirstInstallment); } else if (request.CalculationTarget == CalculationTarget.Annuity && request.RepaymentType == RepaymentType.FixedPrincipalRepayment) { annuity = request.Amount / request.NumberOfInstallments; } else if (request.CalculationTarget == CalculationTarget.Annuity && request.RepaymentType == RepaymentType.Bullet) { annuity = request.Amount; request.NumberOfInstallments = 1; } else { annuity = request.Annuity; } annuity = Math.Round(annuity, 2, MidpointRounding.ToEven); if (hasIntercalar && inr != null) { intercalarInterest = InterestCalculation.CalculateInterest(startDate, firstInterestDate, firstInterestDate, inr.RatePercentage, inr.RateUnitOfTime.CharLiteral(), inr.IsCompound, inr.CalendarBasis, (double)request.Amount).TotalInterest; intercalarInterest = Math.Round(intercalarInterest, 2, MidpointRounding.ToEven); } InstallmentPlanCalculationResult result = new InstallmentPlanCalculationResult { NumberOfInstallments = request.NumberOfInstallments, Annuity = annuity, Amount = request.Amount, Rows = new List <InstallmentPlanRow>() }; // Disbursement row InstallmentPlanRow row = new InstallmentPlanRow { Ordinal = 0, Date = startDate, ActivityKind = ActivityKind.Disbursement, Description = "Disbursement", Disbursement = request.Amount, NetCashFlow = -request.Amount }; row.DiscountedNetCashFlow = row.NetCashFlow; row.OutstandingBalance = request.Amount; result.Rows.Add(row); int ordinal = 1; bool addIntercalarToFirstInstallment = false; // Intercalary interest if (firstInterestDate != firstInstallmentDate && intercalarInterest != 0) { if (request.IntercalarInterestRepaymentType == IntercalarInterestRepaymentType.AfterIntercalarPeriod) { row = new InstallmentPlanRow { Ordinal = ordinal, Date = firstInterestDate, Description = "Intercalar interest", ActivityKind = ActivityKind.InterestPayment, InterestRepayment = intercalarInterest, OutstandingBalance = request.Amount, StartingBalance = request.Amount, Annuity = intercalarInterest, NetCashFlow = intercalarInterest, DiscountedNetCashFlow = intercalarInterest }; result.Rows.Add(row); ordinal++; } else if (request.IntercalarInterestRepaymentType == IntercalarInterestRepaymentType.WithDisbursement) { row.AddInterest(intercalarInterest, "Intercalar interest"); } else { // As this row is still not created, we leave this for later addIntercalarToFirstInstallment = true; } } DateTime d1 = repaymentPeriodStartDate; DateTime d2 = firstInstallmentDate; decimal outstanding = request.Amount; #region new way DateTime maturityDate = firstInstallmentDate.AddPeriod(request.InstallmentSchedule.FrequencyPeriod * (request.NumberOfInstallments - 1), request.InstallmentSchedule.FrequencyUnitOfTime); maturityDate = maturityDate.MoveTo(request.InstallmentSchedule.DayOfMonth); // correction for day of month 29..31 SortedDictionary <DateTime, InstlCalcRow> calcDates = new SortedDictionary <DateTime, InstlCalcRow>(); DateTime d = firstInterestDate; if (intercalarInterest != 0) // if there was intercalar interest calculated, then skip this date { d1 = firstInterestDate; d = d.AddPeriod(request.InterestSchedule.FrequencyPeriod, request.InterestSchedule.FrequencyUnitOfTime); d = d.MoveTo(request.InterestSchedule.DayOfMonth); // correction for day of month 29..31 } while (d < maturityDate) { calcDates.Add(d, new InstlCalcRow() { HasInterestRepayment = true }); d = d.AddPeriod(request.InterestSchedule.FrequencyPeriod, request.InterestSchedule.FrequencyUnitOfTime); d = d.MoveTo(request.InterestSchedule.DayOfMonth); // correction for day of month 29..31 } d = firstInstallmentDate; while (d < maturityDate) { if (calcDates.ContainsKey(d)) { calcDates[d].HasPrincipalRepayment = true; } else { calcDates.Add(d, new InstlCalcRow() { HasInterestRepayment = request.ForceInterestWithInstallment, HasPrincipalRepayment = true }); } d = d.AddPeriod(request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime); d = d.MoveTo(request.InstallmentSchedule.DayOfMonth); // correction for day of month 29..31 } // on maturity date we have to add interest and principal calcDates.Add(maturityDate, new InstlCalcRow() { HasInterestRepayment = true, HasPrincipalRepayment = true }); decimal inrCalc = 0; decimal inrBase = 0; bool installmentAdjusted = false; bool suddenEnd = false; decimal minimalInstallment = 1; // TODO: read from configuration foreach (var calcDate in calcDates) { d2 = calcDate.Key; inrBase = outstanding; if (inr.IsCompound) { inrBase = outstanding + inrCalc; } inrCalc = inrCalc + Math.Round(InterestCalculation.CalculateInterest(d1, d2, d2, inr.RatePercentage, inr.RateUnitOfTime.CharLiteral(), inr.IsCompound, inr.CalendarBasis, (double)inrBase).TotalInterest, 2, MidpointRounding.ToEven); decimal inrRepayment = 0; if (calcDate.Value.HasInterestRepayment) { inrRepayment = inrCalc; inrCalc = 0; } row = new InstallmentPlanRow { Ordinal = ordinal, Date = d2, ActivityKind = ActivityKind.Repayment, Description = "Repayment", StartingBalance = outstanding, InterestRepayment = inrRepayment }; if (calcDate.Value.HasPrincipalRepayment) { if (d2 == maturityDate) // if last installment { row.PrincipalRepayment = outstanding; } else if (request.AdjustFirstInstallment && !installmentAdjusted && d2 < maturityDate && request.RepaymentType == RepaymentType.FixedAnnuity) { // We calculate PrincipalRepayment using dummy interest from simplified installment plan where all periods are equal var dummyDate = d2.AddPeriod(-request.InstallmentSchedule.FrequencyPeriod, request.InstallmentSchedule.FrequencyUnitOfTime); var dummyInterest = InterestCalculation.CalculateInterest(dummyDate, d2, d2, inr.RatePercentage, inr.RateUnitOfTime.CharLiteral(), inr.IsCompound, inr.CalendarBasis, (double)outstanding).TotalInterest; dummyInterest = Math.Round(dummyInterest, 2, MidpointRounding.ToEven); row.PrincipalRepayment = annuity - dummyInterest; installmentAdjusted = true; } else if (request.RepaymentType == RepaymentType.FixedAnnuity) { row.PrincipalRepayment = annuity - row.InterestRepayment; } else // fixed principal repayment { row.PrincipalRepayment = annuity; } if (row.PrincipalRepayment > outstanding - minimalInstallment) { // break before reaching maturityDate (for example when calculating term) // minimalInstallment is used to avoid insignificant amounts in last installment row.PrincipalRepayment = outstanding; result.NumberOfInstallments = calcDates.Count(it => it.Key < d2 && it.Value.HasPrincipalRepayment) + 1; maturityDate = d2; suddenEnd = true; // force interest repayment if there wasn't one... row.InterestRepayment += inrCalc; inrCalc = 0; } } else { row.PrincipalRepayment = 0; } row.Annuity = row.PrincipalRepayment + row.InterestRepayment + row.Fee; row.OutstandingBalance = outstanding -= row.PrincipalRepayment; row.NetCashFlow = row.PrincipalRepayment + row.InterestRepayment + row.Fee; row.DiscountedNetCashFlow = row.PrincipalRepayment + row.InterestRepayment + row.Fee; result.Rows.Add(row); if (suddenEnd) { break; } ordinal++; d1 = d2; } #endregion if (addIntercalarToFirstInstallment) { var firstInstallment = result.Rows.FirstOrDefault(it => it.Date == firstInstallmentDate); if (firstInstallment != null) { firstInstallment.AddInterest(intercalarInterest, "Intercalar interest"); } } // Now fees if (request.Fees != null) { if (!request.FeeCurrencyConversionDone && !string.IsNullOrEmpty(request.Currency)) { AssecoCurrencyConvertion.CurrencyConverter currencyConverter = new AssecoCurrencyConvertion.CurrencyConverter(); foreach (var feeEntry in request.Fees) { if (!string.IsNullOrWhiteSpace(feeEntry.Currency) && feeEntry.Currency != request.Currency) { feeEntry.FixedAmountInDealCurrency = currencyConverter.CurrencyConvert(feeEntry.FixedAmount, feeEntry.Currency, request.Currency, request.StardDate.ToString("o", System.Globalization.CultureInfo.InvariantCulture), request.FeeCurrencyConversionMethod); feeEntry.LowerLimitInDealCurrency = currencyConverter.CurrencyConvert(feeEntry.LowerLimit, feeEntry.Currency, request.Currency, request.StardDate.ToString("o", System.Globalization.CultureInfo.InvariantCulture), request.FeeCurrencyConversionMethod); feeEntry.UpperLimitInDealCurrency = currencyConverter.CurrencyConvert(feeEntry.UpperLimit, feeEntry.Currency, request.Currency, request.StardDate.ToString("o", System.Globalization.CultureInfo.InvariantCulture), request.FeeCurrencyConversionMethod); } else { feeEntry.FixedAmountInDealCurrency = feeEntry.FixedAmount; feeEntry.LowerLimitInDealCurrency = feeEntry.LowerLimit; feeEntry.UpperLimitInDealCurrency = feeEntry.UpperLimit; } } } List <FeeEntry> newFeeDates = new List <FeeEntry>(); foreach (var feeEntry in request.Fees.Where(it => it.Frequency != FeeConditionFrequency.EventTriggered)) { d = feeEntry.Date; while (d < maturityDate) { switch (feeEntry.Frequency) { case FeeConditionFrequency.Monthly: d = d.AddMonths(1); break; case FeeConditionFrequency.Quarterly: d = d.AddMonths(3); break; case FeeConditionFrequency.Semiyearly: d = d.AddMonths(6); break; case FeeConditionFrequency.Yearly: d = d.AddYears(1); break; default: break; } // TODO: check if d is last date and if it should be applied bool applyFeeOnLastDate = false; if ((d <= maturityDate) && (applyFeeOnLastDate || d < d1)) { FeeEntry f = new FeeEntry { CalculationBasisType = feeEntry.CalculationBasisType, Currency = feeEntry.Currency, Date = d, FixedAmount = feeEntry.FixedAmount, Frequency = feeEntry.Frequency, Kind = feeEntry.Kind, LowerLimit = feeEntry.LowerLimit, Name = feeEntry.Name, Percentage = feeEntry.Percentage, ServiceCode = feeEntry.ServiceCode, TariffCode = feeEntry.TariffCode, UpperLimit = feeEntry.UpperLimit, FixedAmountInDealCurrency = feeEntry.FixedAmountInDealCurrency, LowerLimitInDealCurrency = feeEntry.LowerLimitInDealCurrency, UpperLimitInDealCurrency = feeEntry.UpperLimitInDealCurrency }; newFeeDates.Add(f); } } } foreach (var feeEntry in request.Fees.Union(newFeeDates)) { row = result.Rows.Where(it => it.Date == feeEntry.Date).LastOrDefault(); if (row == null) { row = new InstallmentPlanRow(); var prevDate = result.Rows.Where(it => it.Date < feeEntry.Date).Max(it => it.Date); int index = 0; if (prevDate != null) { var prevRow = result.Rows.Where(it => it.Date == prevDate).LastOrDefault(); index = result.Rows.IndexOf(prevRow) + 1; row.StartingBalance = prevRow.StartingBalance; row.OutstandingBalance = prevRow.OutstandingBalance; } result.Rows.Insert(index, row); row.Ordinal = 9999999; // we will have to renumerate this. row.Description = feeEntry.Name; } else { row.Description = row.Description + ", " + feeEntry.Name; } decimal feeBasis = request.Amount; if (feeEntry.CalculationBasisType == CalculationBasisType.AccountBalance) { feeBasis = row.OutstandingBalance; // as we calculate for future period } decimal fee = feeEntry.FixedAmountInDealCurrency + feeEntry.Percentage / 100 * feeBasis; if (feeEntry.UpperLimitInDealCurrency > 0 && fee > feeEntry.UpperLimitInDealCurrency) { fee = feeEntry.UpperLimitInDealCurrency; } if (fee < feeEntry.LowerLimitInDealCurrency) { fee = feeEntry.LowerLimitInDealCurrency; } fee = Math.Round(fee, 2, MidpointRounding.ToEven); row.Fee += fee; row.NetCashFlow += fee; row.DiscountedNetCashFlow = row.NetCashFlow; // TODO: Add installment plan column OtherPayments (Druge isplate) to support this /* * if (row.Disbursement > 0 && request.payFeeFromDisbursement) * { * row.Disbursement -= fee; * row.OtherPayments += fee; * * } */ } } // TODO: Check if there are rows with row.Ordinal = 9999999 and renumerate // At the end, APR calculation List <NetCashFlowItem> cashFlow = result.Rows.Select(it => new NetCashFlowItem() { Date = it.Date, NetCashFlow = -it.NetCashFlow, DiscountedNetCashFlow = -it.DiscountedNetCashFlow }).ToList(); result.APR = InterestCalculation.CalculateEffectiveInterestRate(cashFlow); // Update DiscountedNetCashFlow foreach (var item in result.Rows) { item.DiscountedNetCashFlow = InterestCalculation.CalculateDiscountedNetCashFlow((double)item.NetCashFlow, (double)result.APR, item.Date, startDate); } return(result); }