Login     Register

        Contact Us     Search

New SQL Server loan functions in XLeratorDB/financial 1.13

Sep 25

Written by: Charles Flock
9/25/2014 5:34 PM  RssIcon

We added 7 new table-valued functions to XLeratorDB/financial and XLeratorDB/financial 2008 that generate principal and interest payment schedules for different types of loans. This article provides insight into some of the calculations used in these new functions.
On September 19, 2014 we added 7 new table-valued functions to the XLeratorDB financial library that generate cash flow projections for a wide variety of loan types. Using these functions is as easy as using SQL and doesn't require any advanced knowledge of financial calculations. The functions insulate the user from the complexity of the calculations for these type of loans. The purpose of this article is to explore some of those complexities in more detail, especially the calculations of the principal payment, interest payment and grace interest amounts. We will do that by using SQL and other XLeratorDB functions. Knowing the how of the calculations, of course, is not necessary to use the functions and all of the examples provided in this article are really the hard way of doing things. Nothing could be easier than just using the new functions.
There are any number of ways to group the functions together. Let's start by splitting the functions into loans with fixed maturity dates and loans with no fixed maturity date.

Loans with no Fixed Maturity Dates
·         Bullet - Single payment at maturity
·         Balloon – periodic interest-only payments with the entire principal balance paid at maturity.
·         ConstantCashFlow – periodic annuity-style payments of principal and interest.
·         ConstantPrincipal – the principal amount is amortized on a straight-line basis over the life of the loan.

Loans with no Fixed Maturity Date
·         ConstantPaymentAmount – a fixed periodic payment is made until the loan is paid off. Unlike ConstantCashFlow where the periodic payment amount is calculated by the function, the periodic payment amount is an input to the function.
·         ConstantPrincipalAmount – fixed amount of principal is made periodically until the loan is paid off.
·         ConstantPrincipalRate – a fixed percentage of principal is paid off periodically until the loan is paid off.

Date Calculations
One of the key aspects of understanding the loan calculations is understanding how the dates are calculated. We created the table-valued function PaymentPeriods function precisely for that reason. PaymentPeriods exposes the results of the date calculations used in the loan functions. You should clink on the link to the documentation to read more about it, but I will summarize the important points here:
·         All dates are moved to the end of the month and all calculations use these end-of-month dates.
·         The first payment date is calculated by the functions.
·         All subsequent payments are calculated from the first payment date.
·         If a calculated payment date lands in the grace period, then the payment date is moved to the end of the grace period and all remaining payment dates are calculated from the end of the grace period.
·         Payment frequencies are always specified as the number of months between payments. For example, monthly payments would have a payment frequency of 1; quarterly payments would have a payment frequency of 3, semi-annual payments would have a payment frequency of 6, annual payments would have a payment frequency of 12.

Interest Calculations
In these new functions the interest portion of each cash flow is calculated using the following equation:

Where:

I
=
Interest Payment
P
=
Principal Amount
r
=
Interest Rate
F
=
Frequency
T
=
Time, in years, from the previous interest payment date to the current interest payment date


For example, let's say we had a 100,000 loan at an interest rate of 6.0% that pays interest semi-annually starting on 2014-06-30. The day-count convention is Actual/360. In SQL terms here's what the interest calculation would look like.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2014-12-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
 
SELECT
@P*(POWER(POWER((1+@R*@F/12),12/@F),wct.YEARFRAC(@date_start,@date_end,@basis))-1) as I
This produces the following result.
I
----------------------
3067.6790394899

This is all pretty conventional and straightforward stuff for the simpler loan types, which I would define as
Bullet, Balloon, ConstantCashFlow, and ConstantPaymentAmount. The thing that these loans have in common is that there is a single repayment structure. In other words, I don't have to account for the principal balance changing between interest payment dates. We could replicate the interest (and principal) calculations for a loan with a periodic interest payment and a single payment of interest at maturity with the following SQL.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2019-12-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
 
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal,
       0e+00 as InterestPayment,
       CAST(0 as money) as PrincipalPayment
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, @F) as date),
       CASE
          WHEN CAST(wct.EOMONTH(PaymentDate, @F) as date) = @date_end THEN 0
          ELSE Principal
       END,
Principal *(POWER(POWER(1+@R*@F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,PaymentDate) OVER (ORDER BY PERIOD),CAST(wct.EOMONTH(PaymentDate, @F) as date),@basis)) - 1),
       CASE
          WHEN CAST(wct.EOMONTH(PaymentDate, @F) as date) = @date_end THEN Principal
          ELSE 0
       END
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   *
FROM
   mycte

Or, you could just enter this SQL:

SELECT
       Period,
       PaymentDate,
       CapitalAmountInDebt as Principal,
       InterestPayment,
       PrincipalPayment,
       CashFlow
FROM wct.Balloon(
  100000            --@OutstandingAmount
 ,'Actual/360'      --@InterestBasis
 ,.06               --@InterestRate
 ,6                 --@PaymentFrequency
 ,'2019-12-31'      --@MaturityDate
 ,'2014-06-30'      --@ReferenceDate
 ,NULL              --@PrevPayDate
 ,NULL              --@StartDate
 ,NULL              --@FirstPayDate
 ,NULL              --@GracePeriodStartDate
 ,NULL              --@GracePeriodEndDate
)

Either way, you get the following result.

Period
PaymentDate
Principal
InterestPayment
PrincipalPayment
CashFlow
0
2014-06-30
100000
0.0000
0
0.0000
1
2014-12-31
100000
3067.6790
0
3067.6790
2
2015-06-30
100000
3016.9156
0
3016.9156
3
2015-12-31
100000
3067.6790
0
3067.6790
4
2016-06-30
100000
3033.8340
0
3033.8340
5
2016-12-31
100000
3067.6790
0
3067.6790
6
2017-06-30
100000
3016.9156
0
3016.9156
7
2017-12-31
100000
3067.6790
0
3067.6790
8
2018-06-30
100000
3016.9156
0
3016.9156
9
2018-12-31
100000
3067.6790
0
3067.6790
10
2019-06-30
100000
3016.9156
0
3016.9156
11
2019-12-31
0
3067.6790
100000
103067.6790


However for
ConstantPrincipal, ConstantPrincipalAmount and ConstantPrincipalRate this formula needs to be adjusted for the cases where the principal balance changes between interest payment dates. Let's look at how that calculation works using the example above.
The first thing that we are going to do is to break up the calculation into monthly pieces. This SQL gets us started by generating a date and a principal amount for each month in the interest period.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2014-12-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
 
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   *
FROM
   mycte
This produces the following result.

Period
PaymentDate
Principal
0
2014-06-30
100000
1
2014-07-31
100000
2
2014-08-31
100000
3
2014-09-30
100000
4
2014-10-31
100000
5
2014-11-30
100000
6
2014-12-31
100000


Now, we can modify the SQL in the CTE to calculate the interest for each period using our interest formula from above.
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI
FROM
   mycte
This produces the following result.

Period
PaymentDate
Principal
AI
0
2014-06-30
100000
0
1
2014-07-31
100000
510.3662
2
2014-08-31
100000
510.3662
3
2014-09-30
100000
493.8622
4
2014-10-31
100000
510.3662
5
2014-11-30
100000
493.8622
6
2014-12-31
100000
510.3662


However, if we add up all the AI values, it will not equal the interest value that we originally calculated.
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   SUM(AI) as I
FROM (
   SELECT
       *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI
   FROM
       mycte
   )n
This produces the following result.
I
----------------------
3029.18926565336

The following SQL tells us why this is wrong.
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   SUM(AI) as I,
   SUM(InterestFactor) as [Sum of Interest Factors],
   wct.PRODUCT(1 + InterestFactor) - 1 as [Product of Interest Factors],
(POWER(POWER((1+@R*@F/12),12/@F),wct.YEARFRAC(@date_start,@date_end,@basis))-1) as [Period Interest Factor]
FROM (
   SELECT
       *,
(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,PaymentDate) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) as InterestFactor,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI
   FROM
       mycte
   )n

As you can see, the interest factor for the (six-month) period is not the sum of the monthly interest factors but is the product of (1 + monthly interest factors) - 1

I
Sum of Interest Factors
Product of Interest Factors
Period Interest Factor
3029.1893
0.030291893
0.030676790
0.030676790

Formulaically:
Where:

I
=
Interest Payment
P
=
Principal Amount
F
=
Frequency
n
=
Period
r
=
Interest Rate
R
=
(1 + (r*f/12)^(12/F)
tn
=
Time in years from period 0 to period n


We need to adjust our SQL so that it does more than just simply sum up the interest for each month. Essentially the monthly accrued interest that we calculated also needs to accrue interest until the next interest payment date. The following SQL shows one way to do that calculation.
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   *,
   AI * CIF as [Compounded],
   SUM(AI*CIF) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as Cumulative
FROM (
   SELECT
       *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI,
POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(PaymentDate,@date_end,2))as CIF
   FROM
       mycte
   )n

This produces the following result (which has been reformatted for ease of viewing).

Period
Payment
Date
Principal
AI
CIF
Compounded
Cumulative
0
2014-06-30
100000
0.000000
1.030677
0.000000
0.000000
1
2014-07-31
100000
510.366215
1.025443
523.351602
523.351602
2
2014-08-31
100000
510.366215
1.020236
520.694155
1044.045758
3
2014-09-30
100000
493.862203
1.015223
501.380037
1545.425794
4
2014-10-31
100000
510.366215
1.010067
515.504321
2060.930116
5
2014-11-30
100000
493.862203
1.005104
496.382709
2557.312825
6
2014-12-31
100000
510.366215
1.000000
510.366215
3067.679039


Now that we have figured out how to break in the interest payment calculation into sub-periods, accounting for the reduction in the principal balance is pretty simple.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2014-12-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
DECLARE @ppmt as money = 1000     --Principal Payment Amount
 
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       --Principal
       Principal - @ppmt
   FROM
       mycte
   WHERE
       PaymentDate < @date_end
   )
SELECT
   *,
   AI * CIF as [Compounded],
   SUM(AI*CIF) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as Cumulative
FROM (
   SELECT
       *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI,
POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(PaymentDate,@date_end,2))as CIF
   FROM
       mycte
   )n

Of course, our CTE is only attempting to mimic the calculation of the first interest payments. It does not contain any logic for calculating the interest payment dates nor for clearing out the cumulative interest amounts on the interest payment dates. But, it's quite useful for understanding how a single interest payment account was calculated. It produces the following result.

Period
Payment
Date
Principal
AI
CIF
Compounded
Cumulative
0
2014-06-30
100000
0.000000
1.030677
0.000000
0.000000
1
2014-07-31
99000
510.366215
1.025443
523.351602
523.351602
2
2014-08-31
98000
505.262553
1.020236
515.487214
1038.838816
3
2014-09-30
97000
483.984959
1.015223
491.352436
1530.191252
4
2014-10-31
96000
495.055228
1.010067
500.039192
2030.230444
5
2014-11-30
95000
474.107715
1.005104
476.527401
2506.757844
6
2014-12-31
94000
484.847904
1.000000
484.847904
2991.605748


This is easily compared to the calculation in
ConstantPrincipalAmount
SELECT TOP 7
   Period,
   PaymentDate,
   CapitalAmountInDebt as Principal,
   PrincipalPayment,
   InterestPayment
FROM wct.ConstantPrincipalAmount(
  100000            --@OutstandingAmount
 ,'Actual/360'      --@InterestBasis
 ,.06               --@InterestRate
 ,1                 --@FreqPayPrincipal
 ,6                 --@FreqPayInterest
 ,1000              --@PrinPayAmount
 ,'2014-06-30'      --@ReferenceDate
 ,NULL              --@PrevPrincipalPayDate
 ,NULL              --@PrevInterestPayDate
 ,NULL              --@StartDate
 ,NULL              --@FirstPrincipalPayDate
 ,NULL              --@FirstInterestPayDate
 ,NULL              --@PrincipalGracePeriodStartDate
 ,NULL              --@PrincipalGracePeriodEndDate
 ,NULL              --@InterestGracePeriodStartDate
 ,NULL              --@InterestGracePeriodEndDate
)
which produces the following result.

Period
PaymentDate
Principal
PrincipalPayment
InterestPayment
0
6/30/2014
100000
0
0.000000
1
7/31/2014
99000
1000
0.000000
2
8/31/2014
98000
1000
0.000000
3
9/30/2014
97000
1000
0.000000
4
10/31/2014
96000
1000
0.000000
5
11/30/2014
95000
1000
0.000000
6
12/31/2014
94000
1000
2991.605748

 
Grace Interest
Grace interest is the extra interest that is accrued during a grace period. A grace period can occur at the very start of the loan when, for example, the first interest payment isn't due for a year but all the subsequent interest payments are to be made quarterly. Grace interest should not be confused with interest forgiveness as interest is still accrued during the grace period and the full amount of accrued interest is due at the first interest payment date or the grace period end date.
Grace Interest can also occur at some point during the life of the loan as specified by a grace period start date and a grace period end date. Again, the grace interest amount is the difference between the total interest accrued during the grace period and the amount of interest that would have been accrued if the period were of normal length.
In both cases, the grace interest is really an allocation of the interest that is accrued during the grace period. Thus, the calculation of the grace interest amount is a three-step process. First, calculate the total interest for the grace period. Second, calculate the interest for the last n months of the grace period, where n is the interest payment frequency. Third, subtract that amount from the total interest for the period.
In SQL terms we could look at the grace interest calculation as something like this.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_firstpay as date = '2015-03-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 3
 
SELECT
   *,
   I-R as [Grace Interest]
FROM (
SELECT @P*(POWER(POWER((1+@R*@F/12),12/@F),wct.YEARFRAC(@date_start,@date_FirstPay,@basis))-1) as I,
@P*(POWER(POWER((1+@R*@F/12),12/@F),wct.YEARFRAC(wct.EOMONTH(@date_FirstPay, -@F),@date_FirstPay,@basis))-1) as R
   )n
This produces the following result.

I
R
Grace Interest
4637.05462126520
1499.99999999999
3137.05462126521


We can use the
Balloon function to verify this calculation.
SELECT TOP 2
       Period,
       PaymentDate,
       CapitalAmountInDebt,
       InterestPayment,
       GraceInterest
FROM wct.Balloon(
  100000            --@OutstandingAmount
 ,'Actual/360'      --@InterestBasis
 ,.06               --@InterestRate
 ,3                 --@PaymentFrequency
 ,'2019-06-30'      --@MaturityDate
 ,'2014-06-30'      --@ReferenceDate
 ,NULL              --@PrevPayDate
 ,NULL              --@StartDate
 ,'2015-03-31'      --@FirstPayDate
 ,NULL              --@GracePeriodStartDate
 ,NULL              --@GracePeriodEndDate
)
This produces the following result.

Period
PaymentDate
CapitalAmountInDebt
InterestPayment
GraceInterest
0
6/30/2014
100000
0
0
1
3/31/2015
100000
1500
3137.05462


We can modify the SQL that we used to calculate the accrued interest for each month in the grace period (see above) to calculate the interest payment amount and the grace interest amount. We will need to use the
PaymentPeriods function to determine the first payment date.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_firstpay as date = '2015-03-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
DECLARE @period_firstpay as int =(
   SELECT
       MonthsUntilFirstPayment
   FROM
       wct.PAYMENTPERIODS(
          @date_start,
          @F,
          NULL,
          NULL,
          @date_firstpay,
          NULL,
          NULL,
          NULL
          )
       )
 
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal
   FROM
       mycte
   WHERE
       PaymentDate < @date_firstpay
   )
SELECT
   *,
SUM([Grace Interest]) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as [Cum Grace Interest],
SUM([Interest Payment]) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as [Cum Interest Payment]
FROM (
       SELECT
       Period,
       PaymentDate,
       Principal,
       CASE
          WHEN PERIOD <= @period_firstpay - @F THEN AI
          ELSE 0
       END * CIF as [Grace Interest],
       CASE
          WHEN PERIOD <= @period_firstpay - @F THEN 0
          ELSE AI
       END * CIF as [Interest Payment]
   FROM (
       SELECT
          *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI,
POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(PaymentDate,@date_firstpay,2))as CIF
       FROM
          mycte
       )n
   )p
This produces the following result.

Period
Payment Date
Principal
Grace Interest
Interest Payment
Cum Grace Interest
Cum Interest Payment
0
2014-06-30
100000
0.000000
0.000000
0.000000
0.000000
1
2014-07-31
100000
531.143866
0.000000
531.143866
0.000000
2
2014-08-31
100000
528.446852
0.000000
1059.590719
0.000000
3
2014-09-30
100000
508.845162
0.000000
1568.435881
0.000000
4
2014-10-31
100000
0.000000
523.179746
1568.435881
523.179746
5
2014-11-30
100000
0.000000
503.773429
1568.435881
1026.953175
6
2014-12-31
100000
0.000000
517.965137
1568.435881
1544.918312
7
2015-01-31
100000
0.000000
515.335041
1568.435881
2060.253353
8
2015-02-28
100000
0.000000
463.214395
1568.435881
2523.467748
9
2015-03-31
100000
0.000000
510.366215
1568.435881
3033.833963


And now we adapt it for principal reductions during the grace period.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_firstpay as date = '2015-03-31'
DECLARE @R as float = 0.06
DECLARE @basis as int =--Actual/360
DECLARE @F as int = 6
DECLARE @ppmt as money = 1000
DECLARE @period_firstpay as int =(
   SELECT
       MonthsUntilFirstPayment
   FROM
       wct.PAYMENTPERIODS(
          @date_start,
          @F,
          NULL,
          NULL,
          @date_firstpay,
          NULL,
          NULL,
          NULL
          )
       )
 
;WITH mycte as (
   SELECT
       0 as Period,
       @date_start as PaymentDate,
       @P as Principal
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.EOMONTH(PaymentDate, 1) as date),
       Principal - @ppmt
   FROM
       mycte
   WHERE
       PaymentDate < @date_firstpay
   )
SELECT
   *,
SUM([Grace Interest]) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as [Cum Grace Interest],
SUM([Interest Payment]) OVER (ORDER BY PERIOD ROWS UNBOUNDED PRECEDING) as [Cum Interest Payment]
FROM (
       SELECT
       Period,
       PaymentDate,
       Principal,
       CASE
          WHEN PERIOD <= @period_firstpay - @F THEN AI
          ELSE 0
       END * CIF as [Grace Interest],
       CASE
          WHEN PERIOD <= @period_firstpay - @F THEN 0
          ELSE AI
       END * CIF as [Interest Payment]
   FROM (
       SELECT
          *,
LAG(Principal,1,0) OVER (ORDER BY PERIOD) *(POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(LAG(PaymentDate,1,NULL) OVER (ORDER BY PERIOD),PaymentDate,2)) - 1) As AI,
POWER(POWER(1+@R * @F/12e+00, 12e+00/@F), wct.YEARFRAC(PaymentDate,@date_firstpay,2))as CIF
       FROM
          mycte
       )n
   )p
This produces the following result.

Period
Payment Date
Principal
Grace Interest
Interest Payment
Cum Grace Interest
Cum Interest Payment
0
2014-06-30
100000
0.000000
0.000000
0.000000
0.000000
1
2014-07-31
99000
531.143866
0.000000
531.143866
0.000000
2
2014-08-31
98000
523.162384
0.000000
1054.306250
0.000000
3
2014-09-30
97000
498.668259
0.000000
1552.974509
0.000000
4
2014-10-31
96000
0.000000
507.484353
1552.974509
507.484353
5
2014-11-30
95000
0.000000
483.622492
1552.974509
991.106845
6
2014-12-31
94000
0.000000
492.066880
1552.974509
1483.173725
7
2015-01-31
93000
0.000000
484.414939
1552.974509
1967.588664
8
2015-02-28
92000
0.000000
430.789387
1552.974509
2398.378051
9
2015-03-31
91000
0.000000
469.536918
1552.974509
2867.914969


Which we can compare to the results returned by
ConstantPrincipalRate.
SELECT TOP 10
   Period,
   PaymentDate,
   CapitalAmountInDebt as Principal,
   PrincipalPayment,
   InterestPayment,
   GraceInterest,
   InterestRate
FROM wct.ConstantPrincipalRate(
  100000            --@OutstandingAmount
 ,'Actual/360'      --@InterestBasis
 ,.06               --@InterestRate
 ,1                 --@FreqPayPrincipal
 ,6                 --@FreqPayInterest
 ,.01               --@AmortizationRate
 ,1000              --@MinimumPaymnet
 ,'2014-06-30'      --@ReferenceDate
 ,NULL              --@PrevPrincipalPayDate
 ,NULL              --@PrevInterestPayDate
 ,NULL              --@StartDate
 ,NULL              --@FirstPrincipalPayDate
 ,'2015-03-31'      --@FirstInterestPayDate
 ,NULL              --@PrincipalGracePeriodStartDate
 ,NULL              --@PrincipalGracePeriodEndDate
 ,NULL              --@InterestGracePeriodStartDate
 ,NULL              --@InterestGracePeriodEndDate
)
This produces the following result.

Period
Payment Date
Principal
Principal
Payment
Interest
Payment
Grace
Interest
Interest
Rate
0
6/30/2014
100000
0
0.000000
0.000000
0.00000000
1
7/31/2014
99000
1000
0.000000
0.000000
0.00510366
2
8/31/2014
98000
1000
0.000000
0.000000
0.01023337
3
9/30/2014
97000
1000
0.000000
0.000000
0.01522253
4
10/31/2014
96000
1000
0.000000
0.000000
0.02040389
5
11/30/2014
95000
1000
0.000000
0.000000
0.02544327
6
12/31/2014
94000
1000
0.000000
0.000000
0.03067679
7
1/31/2015
93000
1000
0.000000
0.000000
0.03593702
8
2/28/2015
92000
1000
0.000000
0.000000
0.04071126
9
3/31/2015
91000
1000
2867.914969
1552.974509
0.04602270

 
Principal Payments
For Bullet and Balloon there is a single principal payment at the maturity of the loan.
For ConstantCashFlow the principal payment is the same as what would be calculated using the PPMT function. This SQL shows how the principal payment amount changes for each period.
DECLARE @P as float = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_maturity as date = '2019-06-30'
DECLARE @R as float = 0.06
DECLARE @F as int = 3
DECLARE @num_pmts as int =(
   SELECT
       NumberOfPayments
   FROM
       wct.PaymentPeriods(
          @date_start,
          @F,
          NULL,
          @date_start,
          NULL,
          NULL,
          NULL,
          @date_maturity
          )
   )
 
;with mycte as (
   SELECT
       0 as Period,
       @P as Principal,
       0e+00 as PrincipalPayment
   UNION ALL
   SELECT
       Period + 1,
       Principal - wct.PPMT(@R*@F/12,Period + 1,@num_pmts,-@P,0,0),
       wct.PPMT(@R*@F/12,Period + 1,@num_pmts,-@P,0,0)
   FROM
       mycte
   WHERE
       Period < @num_pmts
   )
SELECT
   *
FROM
   mycte
This produces the following result.

Period
Principal
Principal Payment
0
100000.000000
0.000000
1
95675.426413
4324.573587
2
91285.984221
4389.442191
3
86830.700397
4455.283824
4
82308.587316
4522.113081
5
77718.642538
4589.944778
6
73059.848589
4658.793949
7
68331.172730
4728.675859
8
63531.566733
4799.605996
9
58659.966647
4871.600086
10
53715.292559
4944.674088
11
48696.448360
5018.844199
12
43602.321498
5094.126862
13
38431.782733
5170.538765
14
33183.685887
5248.096846
15
27856.867588
5326.818299
16
22450.147014
5406.720574
17
16962.325632
5487.821382
18
11392.186929
5570.138703
19
5738.496145
5653.690784
20
0.000000
5738.496145


You can use this SQL to obtain the same result from
ConstantCashFlow.
SELECT
   Period,
   CapitalAmountInDebt as Principal,
   PrincipalPayment
FROM wct.ConstantCashFlow(
   100000           --@OutstandingAmount
   ,0               --@LastPrinPayAmount
   ,.06             --@InterestRate
   ,3               --@PaymentFrequency
   ,'2019-06-30'    --@MaturityDate
   ,'2014-06-30'    --@ReferenceDate
   ,NULL            --@PrevPayDate
   ,NULL            --@StartDate
   ,NULL            --@FirstPayDate
   ,NULL            --@GracePeriodStartDate
   ,NULL            --@InterestGracePeriodEndDate
   )

In this SQL we can see how entering a final payment affects the principal payment calculation.
DECLARE @P as float = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_maturity as date = '2019-06-30'
DECLARE @R as float = 0.06
DECLARE @F as int = 3
DECLARE @FV as float = 50000             --Final Payment
DECLARE @num_pmts as int =(
   SELECT
       NumberOfPayments
   FROM
       wct.PaymentPeriods(
          @date_start,
          @F,
          NULL,
          @date_start,
          NULL,
          NULL,
          NULL,
          @date_maturity
          )
   )
 
;with mycte as (
   SELECT
       0 as Period,
       @P as Principal,
       0e+00 as PrincipalPayment
   UNION ALL
   SELECT
       Period + 1,
       CASE Period + 1
          WHEN @num_pmts THEN 0
          ELSE Principal - wct.PPMT(@R*@F/12,Period + 1,@num_pmts-1,-@P,@FV,0)
       END,
       CASE PERIOD + 1
          WHEN @num_pmts THEN @FV
          ELSE wct.PPMT(@R*@F/12,Period + 1,@num_pmts-1,-@P,@FV,0)
       END
   FROM
       mycte
   WHERE
       Period < @num_pmts
   )
SELECT
   *
FROM
   mycte
This produces the following result.

Period
Principal
Principal Payment
0
100000.000000
0.000000
1
97706.076494
2293.923506
2
95377.744136
2328.332358
3
93014.486792
2363.257344
4
90615.780589
2398.706204
5
88181.093792
2434.686797
6
85709.886693
2471.207099
7
83201.611488
2508.275205
8
80655.712154
2545.899333
9
78071.624331
2584.087823
10
75448.775190
2622.849141
11
72786.583313
2662.191878
12
70084.458557
2702.124756
13
67341.801929
2742.656627
14
64558.005452
2783.796477
15
61732.452029
2825.553424
16
58864.515303
2867.936725
17
55953.559527
2910.955776
18
52998.939414
2954.620113
19
50000.000000
2998.939414
20
0.000000
50000.000000


Here is how that looks in the
ConstantCashFlow function.
SELECT
       Period,
          CapitalAmountInDebt as Principal,
          PrincipalPayment
FROM wct.ConstantCashFlow(
       100000 --@OutstandingAmount
       ,50000 --@LastPrinPayAmount
       ,.06   --@InterestRate
       ,3     --@PaymentFrequency
       ,'2019-06-30' --@MaturityDate
       ,'2014-06-30' --@ReferenceDate
       ,NULL --@PrevPayDate
       ,NULL --@StartDate
       ,NULL --@FirstPayDate
       ,NULL --@GracePeriodStartDate
       ,NULL --@InterestGracePeriodEndDate
       )

Though it might not seem obvious, the existence of grace periods in
ConstantCashFlow will not affect the calculation of the principal payment amounts.
ConstantPrincipal amortizes the loan principal on a straight-line basis over the life of the loan based on the number of principal payments.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2019-06-30'
DECLARE @F as int = 3
DECLARE @num_pmts as int =(
   SELECT
       NumberOfPayments
   FROM
       wct.PaymentPeriods(
          @date_start,
          @F,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL,
          @date_end
          )
       )
 
SELECT
   @P / @num_pmts as PrincipalPayment
This produces the following result.
PrincipalPayment
---------------------
5000.00

If a final payment is entered then the calculation needs to be modified.
DECLARE @P as money = 100000
DECLARE @date_start as date = '2014-06-30'
DECLARE @date_end as date = '2019-06-30'
DECLARE @F as int = 3
DECLARE @amt_last_pmt as money = 25000
DECLARE @num_pmts as int =(
   SELECT
       NumberOfPayments
   FROM
       wct.PaymentPeriods(
          @date_start,
          @F,
          NULL,
          NULL,
          NULL,
          NULL,
          NULL,
          @date_end
          )
       )
 
SELECT
   (@P - @amt_last_pmt) /(@num_pmts - 1) as PrincipalPayment
This produces the following result.
PrincipalPayment
---------------------
3947.3684

We could also adjust our SQL to be a little more flexible and work in both cases.
SELECT
   CASE @amt_last_pmt
       WHEN 0 THEN @P / @num_pmts
       ELSE (@P - @amt_last_pmt) /(@num_pmts - 1)
   END as PrincipalPayment

The calculation of the principal payment for
ConstantPrincipalAmount is simple since the principal payment amount is an input to the function. The principal payment amount is applied to the outstanding balance until the balance is zero. The final payment is the lesser of the supplied principal payment amount and the outstanding balance of the loan.
DECLARE @P as money = 100000
DECLARE @ppmt as money = 4833.33
 
;WITH mycte as (
   SELECT
       0 as Period,
       @P as Principal,
       CAST(0 as money) as PrincipalPayment
   UNION ALL
   SELECT
       Period + 1,
       CAST(wct.GREATEROF(0, Principal - @ppmt) as money),
       CAST(wct.LESSEROF(Principal, @ppmt) as money)
   FROM
       mycte
   WHERE
       Principal > 0
   )
SELECT
   *
FROM
   mycte
This produces the following result.

Period
Principal
Principal Payment
0
100000
0
1
95166.67
4833.33
2
90333.34
4833.33
3
85500.01
4833.33
4
80666.68
4833.33
5
75833.35
4833.33
6
71000.02
4833.33
7
66166.69
4833.33
8
61333.36
4833.33
9
56500.03
4833.33
10
51666.70
4833.33
11
46833.37
4833.33
12
42000.04
4833.33
13
37166.71
4833.33
14
32333.38
4833.33
15
27500.05
4833.33
16
22666.72
4833.33
17
17833.39
4833.33
18
13000.06
4833.33
19
8166.73
4833.33
20
3333.40
4833.33
21
0
3333.40


You can run the following SQL to get the same results out of
ConstantPrincipalAmount.
SELECT
   Period,
   CapitalAmountInDebt as Principal,
   PrincipalPayment
FROM wct.ConstantPrincipalAmount(
   100000           --@OutstandingAmount
   ,NULL            --@InterestBasis
   ,.06             --@InterestRate
   ,3               --@FreqPayPrincipal
   ,3               --@FreqPayInterest
   ,4833.33         --@PrinPayAmount
   ,'2014-10-01'    --@ReferenceDate
   ,NULL            --@PrevPrincipalPayDate
   ,NULL            --@PrevInterestPayDate
   ,NULL            --@StartDate
   ,NULL            --@FirstPrincipalPayDate
   ,NULL            --@FirstInterestPayDate
   ,NULL            --@PrincipalGracePeriodStartDate
   ,NULL            --@PrincipalGracePeriodEndDate
   ,NULL            --@InterestGracePeriodStartDate
   ,NULL            --@InterestGracePeriodEndDate
   )

ConstantPrincipalRate
allows you to specify an amortization rate which is applied to the outstanding principal balance in order to calculate the principal payment amount. It also requires a minimum payment amount which is used in figuring out when to terminate the cash flow projection. The following SQL demonstrates the calculation of the principal payments.
DECLARE @P as float = 100000
DECLARE @rate_amort as float = 0.2
DECLARE @min_pmt as float = 2500
 
;WITH mycte as (
   SELECT
       0 as Period,
       @P as Principal,
       CAST(0 as float) as PrincipalPayment
   UNION ALL
   SELECT
       Period + 1,
CAST(wct.GREATEROF(0, Principal - cast(wct.GREATEROF(@min_pmt, @rate_amort * Principal) as float)) as float),
CAST(wct.LESSEROF(Principal, wct.GREATEROF(@min_pmt, @rate_amort * Principal)) as float)
   FROM
       mycte
   WHERE
       Principal > 0
   )
SELECT
   *
FROM
   mycte
This produces the following result.

Period
Principal
Principal Payment
0
100000.000000
0.000000
1
80000.000000
20000.000000
2
64000.000000
16000.000000
3
51200.000000
12800.000000
4
40960.000000
10240.000000
5
32768.000000
8192.000000
6
26214.400000
6553.600000
7
20971.520000
5242.880000
8
16777.216000
4194.304000
9
13421.772800
3355.443200
10
10737.418240
2684.354560
11
8237.418240
2500.000000
12
5737.418240
2500.000000
13
3237.418240
2500.000000
14
737.418240
2500.000000
15
0.000000
737.418240


You could run the following SQL to get the same results out of
ConstantPrincipalRate.
SELECT
   Period,
   CapitalAmountInDebt as Principal,
   PrincipalPayment
FROM wct.ConstantPrincipalRate(
  100000        --@OutstandingAmount
 ,NULL          --@InterestBasis
 ,NULL          --@InterestRate
 ,NULL          --@FreqPayPrincipal
 ,NULL          --@FreqPayInterest
 ,0.20          --@AmortizationRate
 ,2500.00       --@MinimumPayment
 ,'2012-10-01'  --@ReferenceDate
 ,NULL          --@PrevPrincipalPayDate
 ,NULL          --@PrevInterestPayDate
 ,NULL          --@StartDate
 ,NULL          --@FirstPrincipalPayDate
 ,NULL          --@FirstInterestPayDate
 ,NULL          --@PrincipalGracePeriodStartDate
 ,NULL          --@PrincipalGracePeriodEndDate
 ,NULL          --@InterestGracePeriodStartDate
 ,NULL          --@InterestGracePeriodEndDate
)

The
ConstantPaymentAmount table-valued function has the payment amount as an input parameter. This payment amount is applied to the interest payment first and the remaining balance is then applied to the principal balance until the principal is reduced to zero. Even though the payment amount is entered, it is adjusted when there is an initial grace period or interim grace period to reflect the additional interest that arises as a result of the grace period.
DECLARE @P as float = 100000
DECLARE @R as float = 0.06
DECLARE @F as int = 3
DECLARE @amt_pmt as float = 6666.00
 
;WITH mycte as (
   SELECT
       0 as Period,
       @P as Principal,
       CAST(0 as float) as PrincipalPayment,
       CAST(0 as float) as InterestPayment
   UNION ALL
   SELECT
       Period + 1,
Principal - CAST(wct.LESSEROF(Principal, @amt_pmt - Principal * @R * @F/12) as float) as Principal,
CAST(wct.LESSEROF(Principal, @amt_pmt - Principal * @R * @F/12) as float) as PrincipalPayment,
       Principal * @R * @F/12 as InterestPayment
   FROM
       mycte
   WHERE
       Principal > 0
   )
SELECT
   *,
   PrincipalPayment + InterestPayment as CashFlow
FROM
   mycte
This produces the following result.

Period
Principal
Principal Payment
Interest Payment
Cash Flow
0
100000.000000
0.000000
0.000000
0.000000
1
94834.000000
5166.000000
1500.000000
6666.000000
2
89590.510000
5243.490000
1422.510000
6666.000000
3
84268.367650
5322.142350
1343.857650
6666.000000
4
78866.393165
5401.974485
1264.025515
6666.000000
5
73383.389062
5483.004103
1182.995897
6666.000000
6
67818.139898
5565.249164
1100.750836
6666.000000
7
62169.411997
5648.727902
1017.272098
6666.000000
8
56435.953177
5733.458820
932.541180
6666.000000
9
50616.492474
5819.460702
846.539298
6666.000000
10
44709.739861
5906.752613
759.247387
6666.000000
11
38714.385959
5995.353902
670.646098
6666.000000
12
32629.101749
6085.284211
580.715789
6666.000000
13
26452.538275
6176.563474
489.436526
6666.000000
14
20183.326349
6269.211926
396.788074
6666.000000
15
13820.076244
6363.250105
302.749895
6666.000000
16
7361.377388
6458.698856
207.301144
6666.000000
17
805.798049
6555.579339
110.420661
6666.000000
18
0.000000
805.798049
12.086971
817.885019


You could run the following SQL to get the same results out of
ConstantPaymentAmount.
SELECT
   Period,
   CapitalAmountInDebt as Principal,
   PrincipalPayment,
   InterestPayment,
   CashFlow
FROM wct.ConstantPaymentAmount(
  100000        --@OutstandingAmount
 ,NULL          --@InterestBasis
 ,.06           --@InterestRate
 ,3             --@PaymentFrequency
 ,6666.00       --@PaymentAmount
 ,'2014-10-01'  --@ReferenceDate
 ,NULL          --@PrevPayDate
 ,NULL          --@StartDate
 ,NULL          --@FirstPayDate
 ,NULL          --@GracePeriodStartDate
 ,NULL          --@InterestGracePeriodEndDate
)

We think that these new table-valued functions are a great addition to the XLeratorDB/financial library. We hope that you found this article insightful in explaining the intricacies of the interest and principal calculations contained in these functions. This was certainly not an exhaustive treatment of all the things that you can do with these new functions.

To find out more, you should download the free 15-day trial today and try them out. You can find all the documentation and lots of examples right here at westclintech.com and you should be able to just paste the examples right into SSMS to find out how you can use these functions.

Tags:
Categories:

Search Blogs

Copyright 2008-2024 Westclintech LLC         Privacy Policy        Terms of Service