private decimal CalculateExtraPayments(MortgageInformation mortgage, int i)
        {
            var extraPayment = 0m;

            if (mortgage.ExtraPayments != null && mortgage.ExtraPayments.ExtraPaymentInterval > 0 && mortgage.ExtraPayments.ExtraPaymentAmount > 0)
            {
                if (i % mortgage.ExtraPayments.ExtraPaymentInterval == 0)
                {
                    extraPayment = mortgage.ExtraPayments.ExtraPaymentAmount;
                }
            }
            return extraPayment;
        }
        private decimal CalculateInterestDue(MortgageInformation mortgageInformation, decimal intrestRate, decimal currentBalance)
        {
            var cp = (decimal)mortgageInformation.CompoundPeriod;

            var value = 1 + intrestRate / cp;

            var pf = (decimal)mortgageInformation.PaymentFrequency;

            var power = cp / pf;

            var result = (Math.Pow((double)value, (double)power) - 1) * (double)currentBalance;

            return (decimal)((Math.Round(result, 2)));
        }
        public IList<PaymentScheduleItem> GeneratePaymentSchedule(MortgageInformation mortgageInformation)
        {
            var result = new List<PaymentScheduleItem>();

            if (mortgageInformation == null || mortgageInformation.Term < 1 || mortgageInformation.LoanAmount <= 0)
            {
                return result;
            }

            var currentPaymentDate = mortgageInformation.FirstPaymentDate;
            var numOfPayments = NumberOfMonthlyPayments(mortgageInformation.PaymentFrequency, mortgageInformation.Term);
            var calculatedCurrentBalance = mortgageInformation.LoanAmount;
            var calculatedPreviousRate = mortgageInformation.AnnualInterestRate;
            var calculatedPreviousPaymentDue = 0m;
            var cmltvTaxReturned = 0m;

            for (int i = 1; i <= numOfPayments && calculatedCurrentBalance > 0; i++)
            {
                var item = new PaymentScheduleItem();

                item.PaymentNumber = i;
                item.IntrestRate = CalculateInterestRate(mortgageInformation, i, calculatedPreviousRate);
                item.PaymentDate = CalculatePaymentDate(mortgageInformation, i, currentPaymentDate);
                item.IntrestDue = CalculateInterestDue(mortgageInformation, item.IntrestRate, calculatedCurrentBalance);
                item.PaymentDue = CalculatePaymentDue(mortgageInformation, i, item.IntrestRate, item.IntrestDue, calculatedCurrentBalance, calculatedPreviousRate, calculatedPreviousPaymentDue);
                item.ExtraPayments = CalculateExtraPayments(mortgageInformation, i);
                item.PrincipalPaid = CalculatePrincipalPaid(item.PaymentDue, item.IntrestDue, item.ExtraPayments);
                item.Balance = CalculateCurrentBalance(calculatedCurrentBalance, item.PrincipalPaid);
                item.TaxReturned = CalculateTaxReturned(mortgageInformation.TaxBracket, item.IntrestDue);
                result.Add(item);

                currentPaymentDate = item.PaymentDate;
                cmltvTaxReturned += item.TaxReturned;
                item.CmltvTaxReturned = cmltvTaxReturned;
                calculatedPreviousRate = item.IntrestRate;
                calculatedPreviousPaymentDue = item.PaymentDue;
                calculatedCurrentBalance = item.Balance;
            }

            return result;
        }
        private decimal CalculateInterestRate(MortgageInformation mortgageInformation, int i, decimal calculatedPreviousRate)
        {
            if (mortgageInformation.RateType == MortgageEnums.RateType.Variable && i > 1)
            {
                var rate = calculatedPreviousRate;

                if (mortgageInformation.PeriodsBetweenAdjustments != 0 && i >= (int)mortgageInformation.PaymentFrequency * mortgageInformation.YearsRateRemainsFixed)
                {

                    if (mortgageInformation.PeriodsBetweenAdjustments == 0 || (i - 1) % mortgageInformation.PeriodsBetweenAdjustments == 0)
                    {
                        rate = Math.Max(mortgageInformation.IntrestRateMin,
                                                 calculatedPreviousRate +
                                                 mortgageInformation.EstimatedAdjustment);
                    }

                    return Math.Min(mortgageInformation.IntrestRateCap, rate);
                }
            }

            return mortgageInformation.AnnualInterestRate;
        }
        private decimal CalculatePaymentDue(MortgageInformation mortgageInformation, int i, decimal intrestRate, decimal intrestDue, decimal currentBalance, decimal calculatedPreviousRate, decimal calculatedPreviousPaymentDue)
        {
            decimal paymentDue;

            var cp = (decimal)mortgageInformation.CompoundPeriod;
            var pf = (decimal)mortgageInformation.PaymentFrequency;

            var fullBalancePlusIntrest = currentBalance + intrestDue;
            var nper = mortgageInformation.Term * pf;

            if (i == nper)
            {
                paymentDue = currentBalance + intrestDue;
            }
            else
            {
                if (intrestRate == calculatedPreviousRate && i > 1)
                {
                    paymentDue = calculatedPreviousPaymentDue;
                }
                //TODO handle Acc Bi-Weekly
                //TODO handle Acc Weekly
                else
                {
                    var value = 1 + intrestRate / cp;

                    var power = cp / pf;

                    var result = (Math.Pow((double)value, (double)power) - 1);

                    var pmt = -Financial.Pmt(result, (double)nper - i + 1, (double)currentBalance);

                    paymentDue = (decimal)Math.Round(pmt, 2);
                }
            }

            return Math.Min(fullBalancePlusIntrest, paymentDue);
        }
 private DateTime CalculatePaymentDate(MortgageInformation mortgageInformation, int i, DateTime currentPaymentDate)
 {
     switch (mortgageInformation.PaymentFrequency)
     {
         case MortgageEnums.PaymentFrequency.Monthly:
             return i == 1 ? currentPaymentDate : currentPaymentDate.AddMonths(1);
         case MortgageEnums.PaymentFrequency.SemiMonthly://TODO
         case MortgageEnums.PaymentFrequency.BiWeekly://TODO
         case MortgageEnums.PaymentFrequency.Weekly://TODO
         default:
             return currentPaymentDate.AddMonths(i);
     }
 }
        private MortgageInformation BuildMortgageInformation(MortgageRequest mortgageRequest)
        {
            RateInformation rateInfo = BuildRateInfo(mortgageRequest);
            PaymentInformation payInfo = BuildPaymentInfo(mortgageRequest);
            ExtraPayments extraPayments = BuildExtraPayments(mortgageRequest);

            var mortgageInfo = new MortgageInformation(rateInfo, payInfo, extraPayments)
                                   {
                                       LoanAmount = mortgageRequest.LoanAmount,
                                       AnnualInterestRate = mortgageRequest.AnnualInterestRate,
                                       Term = mortgageRequest.Term,
                                       CompoundPeriod = mortgageRequest.CompoundPeriod,
                                       PaymentFrequency = mortgageRequest.PaymentFrequency,
                                       TaxBracket = mortgageRequest.TaxBracket
                                   };

            return mortgageInfo;
        }