public InterestCalculator(decimal sumMoney, decimal interest, byte years, InterestCalculation interestCalc)
        {
            this.SumMoney = sumMoney;
            this.Interest = interest;
            this.Years    = years;

            this.InterestCalc = interestCalc;
        }
Example #2
0
        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);
        }
Example #4
0
        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());
        }
Example #5
0
        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);
        }