Esempio n. 1
0
        public void TestCreditCardInstallmentPlan()
        {
            SimpleLoanCalculationRequest request = new SimpleLoanCalculationRequest();

            request.Amount          = (decimal)100000;
            request.Currency        = "RSD";
            request.StardDate       = new DateTime(2019, 10, 3);
            request.RegularInterest = new List <InterestRateEntry>();
            request.RegularInterest.Add(new InterestRateEntry()
            {
                Date           = request.StardDate,
                RatePercentage = 12,
                IsCompound     = false,
                CalendarBasis  = CalendarBasisKind.Calendar30E360,
                RateUnitOfTime = SimpleUnitOfTime.Y,
                Name           = "Kamata"
            });
            request.InstallmentSchedule            = new SimpleSchedule();
            request.InstallmentSchedule.DayOfMonth = 0;
            request.MinimumDaysForFirstInstallment = 0;
            request.NumberOfInstallments           = 1;
            request.RepaymentType = RepaymentType.Bullet;
            request.InstallmentSchedule.FrequencyPeriod     = 1;
            request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.Y;
            //request.IntercalarInterestRepaymentType = IntercalarInterestRepaymentType.WithFirstInstallment;

            request.InterestSchedule                     = new SimpleSchedule();
            request.InterestSchedule.DayOfMonth          = 31;
            request.InterestSchedule.FrequencyPeriod     = 1;
            request.InterestSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M;
            request.ForceInterestWithInstallment         = true;

            request.Fees = new List <FeeEntry>();
            // Ne znam detalje pojedinacnih troskova, pa sam spojio u jedan fiksni iznos koji se vidi na amortplanu
            request.Fees.Add(new FeeEntry()
            {
                FixedAmount          = (decimal)500,
                Currency             = "RSD",
                Date                 = request.StardDate,
                Kind                 = FeeConditionKind.OriginationFee,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.ContractAmount,
                Name                 = "Naknada za obradu kreditnog zahteva, Kreditni biro, Menice"
            });

            var result       = InstallmentPlanCalculation.CalculateInstallmentPlan(request);
            var someInr      = result.Rows.Where(it => it.Date == new DateTime(2020, 3, 31)).First().InterestRepayment;
            var sumRepayment = result.Rows.Sum(it => it.PrincipalRepayment);

            Assert.True(sumRepayment == request.Amount);
            Assert.True(result.Rows.Count == 14);
            //Assert.True(Math.Round(result.APR, 2) == (decimal)18.39);
            Assert.True(Math.Round(result.Annuity, 2) == (decimal)100000);
            Assert.True(result.Rows[1].PrincipalRepayment == (decimal)0);
            Assert.True(result.Rows[1].InterestRepayment == (decimal)900);
            Assert.True(result.Rows[2].InterestRepayment == (decimal)1000);
            Assert.True(result.Rows.Last().PrincipalRepayment == (decimal)100000);
            Assert.True(Math.Round(someInr, 2) == (decimal)1033.33);
        }
Esempio n. 2
0
        public void TestSimpleInstallmentPlanCalculationCase4()
        {
            SimpleLoanCalculationRequest request = new SimpleLoanCalculationRequest();

            request.Amount          = (decimal)95500;
            request.Currency        = "RSD";
            request.StardDate       = new DateTime(2019, 10, 3);
            request.RegularInterest = new List <InterestRateEntry>();
            request.RegularInterest.Add(new InterestRateEntry()
            {
                Date           = request.StardDate,
                RatePercentage = 15.950,
                IsCompound     = false,
                CalendarBasis  = CalendarBasisKind.Calendar30E360,
                RateUnitOfTime = SimpleUnitOfTime.Y,
                Name           = "Kamata"
            });

            request.InstallmentSchedule            = new SimpleSchedule();
            request.InstallmentSchedule.DayOfMonth = 5;
            request.MinimumDaysForFirstInstallment = 20;
            request.NumberOfInstallments           = 60;
            request.RepaymentType = RepaymentType.FixedAnnuity;
            request.InstallmentSchedule.FrequencyPeriod     = 1;
            request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M;
            request.IntercalarInterestRepaymentType         = IntercalarInterestRepaymentType.WithFirstInstallment;

            request.Fees = new List <FeeEntry>();
            // Ne znam detalje pojedinacnih troskova, pa sam spojio u jedan fiksni iznos koji se vidi na amortplanu
            request.Fees.Add(new FeeEntry()
            {
                FixedAmount          = (decimal)2158.25,
                Currency             = "RSD",
                Date                 = request.StardDate,
                Kind                 = FeeConditionKind.OriginationFee,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.ContractAmount,
                Name                 = "Naknada za obradu kreditnog zahteva, Kreditni biro, Menice"
            });

            request.AdjustFirstInstallment = true; // strange logic, but ok...

            var result       = InstallmentPlanCalculation.CalculateInstallmentPlan(request);
            var someInr      = result.Rows.Where(it => it.Date == new DateTime(2020, 3, 5)).First().InterestRepayment;
            var sumRepayment = result.Rows.Sum(it => it.PrincipalRepayment);

            Assert.True(sumRepayment == request.Amount);
            Assert.True(result.Rows.Count == 61);
            Assert.True(Math.Round(result.APR, 2) == (decimal)18.39);
            Assert.True(Math.Round(result.Annuity, 2) == (decimal)2319.84);
            Assert.True(result.Rows[1].PrincipalRepayment == (decimal)1050.49);
            // Imamo razliku u zaokruzivanju
            // Assert.True(result.Rows[1].InterestRepayment == (decimal)1353.98);
            Assert.True(result.Rows[1].InterestRepayment == (decimal)1353.97);
            Assert.True(result.Rows.Last().PrincipalRepayment == (decimal)2289.23);
            Assert.True(Math.Round(someInr, 2) == (decimal)1212.38);
        }
Esempio n. 3
0
        public void TestSimpleInstallmentPlanCalculationCase2()
        {
            SimpleLoanCalculationRequest request = new SimpleLoanCalculationRequest();

            request.Amount          = (decimal)4060.92;
            request.Currency        = "EUR";
            request.StardDate       = new DateTime(2019, 10, 30);
            request.RegularInterest = new List <InterestRateEntry>();
            request.RegularInterest.Add(new InterestRateEntry()
            {
                Date           = request.StardDate,
                RatePercentage = 3.990,
                IsCompound     = false,
                CalendarBasis  = CalendarBasisKind.Calendar30E360,
                RateUnitOfTime = SimpleUnitOfTime.Y,
                Name           = "Kamata"
            });
            request.InstallmentSchedule            = new SimpleSchedule();
            request.InstallmentSchedule.DayOfMonth = 20;
            request.MinimumDaysForFirstInstallment = 20;
            request.NumberOfInstallments           = 36;
            request.RepaymentType = RepaymentType.FixedAnnuity;
            request.InstallmentSchedule.FrequencyPeriod     = 1;
            request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M;
            request.IntercalarInterestRepaymentType         = IntercalarInterestRepaymentType.WithFirstInstallment;

            request.Fees = new List <FeeEntry>();
            request.Fees.Add(new FeeEntry()
            {
                Percentage           = (decimal)1.5,
                Date                 = request.StardDate,
                Kind                 = FeeConditionKind.OriginationFee,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.ContractAmount,
                Name                 = "Naknada za obradu kreditnog zahteva"
            });

            request.AdjustFirstInstallment = true; // strange logic, but ok...

            var result       = InstallmentPlanCalculation.CalculateInstallmentPlan(request);
            var someInr      = result.Rows.Where(it => it.Date == new DateTime(2020, 3, 20)).First().InterestRepayment;
            var sumRepayment = result.Rows.Sum(it => it.PrincipalRepayment);

            Assert.True(sumRepayment == request.Amount);
            Assert.True(result.Rows.Count == 37);
            Assert.True(Math.Round(result.APR, 2) == (decimal)5.13);
            Assert.True(Math.Round(result.Annuity, 2) == (decimal)119.88);
            Assert.True(result.Rows[1].PrincipalRepayment == (decimal)106.38);
            Assert.True(result.Rows[1].InterestRepayment == (decimal)9.00);
            Assert.True(result.Rows.Last().PrincipalRepayment == (decimal)119.32);
            Assert.True(Math.Round(someInr, 2) == (decimal)12.08);
        }
        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);
        }
Esempio n. 5
0
        public void TestSimpleInstallmentPlanCalculationCase1b()
        {
            SimpleLoanCalculationRequest request = new SimpleLoanCalculationRequest();

            //request.Amount = (decimal)16741.12;
            request.Annuity           = (decimal)228.75;
            request.CalculationTarget = CalculationTarget.Amount;
            request.Currency          = "EUR";
            request.StardDate         = new DateTime(2019, 10, 24);
            request.RegularInterest   = new List <InterestRateEntry>();
            request.RegularInterest.Add(new InterestRateEntry()
            {
                Date           = request.StardDate,
                RatePercentage = 3.990,
                IsCompound     = false,
                CalendarBasis  = CalendarBasisKind.Calendar30E360,
                RateUnitOfTime = SimpleUnitOfTime.Y,
                Name           = "Kamata"
            });
            request.InstallmentSchedule            = new SimpleSchedule();
            request.InstallmentSchedule.DayOfMonth = 5;
            request.MinimumDaysForFirstInstallment = 20;
            request.NumberOfInstallments           = 84;
            request.RepaymentType = RepaymentType.FixedAnnuity;
            request.InstallmentSchedule.FrequencyPeriod     = 1;
            request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M;
            request.IntercalarInterestRepaymentType         = IntercalarInterestRepaymentType.WithFirstInstallment;

            request.Fees = new List <FeeEntry>();
            request.Fees.Add(new FeeEntry()
            {
                Percentage           = (decimal)1.5,
                Date                 = request.StardDate,
                Kind                 = FeeConditionKind.OriginationFee,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.ContractAmount,
                Name                 = "Naknada za obradu kreditnog zahteva"
            });

            request.Fees.Add(new FeeEntry()
            {
                FixedAmount          = (decimal)2.47,
                Currency             = "EUR",
                Date                 = new DateTime(2019, 12, 5),
                Kind                 = FeeConditionKind.Expense,
                Frequency            = FeeConditionFrequency.Monthly,
                CalculationBasisType = CalculationBasisType.PrecalculatedAmount,
                Name                 = "Trosak otvaranja i vodjenja racuna"
            });

            request.Fees.Add(new FeeEntry()
            {
                FixedAmount          = (decimal)(673.01 - 2.47),
                Currency             = "EUR",
                Date                 = new DateTime(2019, 12, 5),
                Kind                 = FeeConditionKind.Expense,
                Frequency            = FeeConditionFrequency.Yearly,
                CalculationBasisType = CalculationBasisType.PrecalculatedAmount,
                Name                 = "Osiguranje nepokretnosti"
            });

            request.AdjustFirstInstallment = true; // strange logic, but ok...

            var result       = InstallmentPlanCalculation.CalculateInstallmentPlan(request);
            var someInr      = result.Rows.Where(it => it.Date == new DateTime(2020, 3, 5)).First().InterestRepayment;
            var sumRepayment = result.Rows.Sum(it => it.PrincipalRepayment);

            Assert.True(sumRepayment == request.Amount);
            Assert.True(result.Rows.Count == 85);
            Assert.True(result.Rows.Last().PrincipalRepayment == (decimal)227.96);
            Assert.True(Math.Round(result.APR, 2) == (decimal)12.47);
            Assert.True(Math.Round(result.Amount, 2) == (decimal)16740.82);
            Assert.True(Math.Round(someInr, 2) == (decimal)53.93);
        }
Esempio n. 6
0
        public void TestSimpleInstallmentPlanCalculationCase29th()
        {
            SimpleLoanCalculationRequest request = new SimpleLoanCalculationRequest();

            request.Amount = (decimal)200000.00;
            //request.Annuity = (decimal)228.75;
            request.CalculationTarget = CalculationTarget.Annuity;
            request.Currency          = "RSD";
            request.StardDate         = new DateTime(2020, 1, 29);
            request.RegularInterest   = new List <InterestRateEntry>();
            request.RegularInterest.Add(new InterestRateEntry()
            {
                Date           = request.StardDate,
                RatePercentage = 7.95,
                IsCompound     = false,
                CalendarBasis  = CalendarBasisKind.Calendar30E360,
                RateUnitOfTime = SimpleUnitOfTime.Y,
                Name           = "Kamata"
            });
            request.InstallmentSchedule            = new SimpleSchedule();
            request.InstallmentSchedule.DayOfMonth = 5;
            request.MinimumDaysForFirstInstallment = 20;
            request.NumberOfInstallments           = 36;
            request.RepaymentType = RepaymentType.FixedAnnuity;
            request.InstallmentSchedule.FrequencyPeriod     = 1;
            request.InstallmentSchedule.FrequencyUnitOfTime = SimpleUnitOfTime.M;
            request.IntercalarInterestRepaymentType         = IntercalarInterestRepaymentType.WithFirstInstallment;

            request.Fees = new List <FeeEntry>();
            request.Fees.Add(new FeeEntry()
            {
                Percentage           = (decimal)2,
                Date                 = request.StardDate,
                Kind                 = FeeConditionKind.OriginationFee,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.ContractAmount,
                Name                 = "Naknada za obradu kreditnog zahteva"
            });


            request.Fees.Add(new FeeEntry()
            {
                FixedAmount          = (decimal)245.00,
                Currency             = "RSD",
                Date                 = new DateTime(2020, 1, 29),
                Kind                 = FeeConditionKind.Expense,
                Frequency            = FeeConditionFrequency.EventTriggered,
                CalculationBasisType = CalculationBasisType.PrecalculatedAmount,
                Name                 = "Trošak KB"
            });

            request.AdjustFirstInstallment = true; // strange logic, but ok...
            request.InterestSchedule       = null;
            var result       = InstallmentPlanCalculation.CalculateInstallmentPlan(request);
            var someInr      = result.Rows.Where(it => it.Date == new DateTime(2020, 3, 5)).First().InterestRepayment;
            var sumRepayment = result.Rows.Sum(it => it.PrincipalRepayment);

            Assert.True(sumRepayment == request.Amount);
            Assert.True(result.Rows.Count == 37);
            Assert.True(result.Rows.Last().PrincipalRepayment == (decimal)6221.49);
            Assert.True(Math.Round(result.APR, 2) == (decimal)9.82);
            Assert.True(Math.Round(result.Annuity, 2) == (decimal)6262.66);
            Assert.True(Math.Round(someInr, 2) == (decimal)1590.00);
        }