|
Examples / BONDSPLINE.RPF |
BONDSPLINE.RPF estimates a yield curve using a cubic spline approximation to the discount function, as described in McCulloch(1971). This compares with BONDS.RPF, which estimates the yield curve (on the same data set) using a non-linear function.
This models the discount function \(P(m)\) as a cubic spline, which is (in this case), a base cubic in \(m\) combined with terms of the form \(\beta _i \left( {\max (m - m_i ,0)} \right)^3 \) for a chosen set of "knots" \(m_i\). This has the property that \(P(m)\) is continuous with continuous 1st and 2nd derivatives as well. The only thing that changes at the knots is the 3rd derivative.
The data are described in BONDS.RPF. This can be estimated as a linear regression on a generated set of regressors, where those are created by decomposing the value of the bond into its components.
The regressors are divided into the BASEREGS (the 1st, 2nd and 3rd order terms of the base cubic polynomial—the constant, basically by definition, has to be 1 since it's the discount value for m=0) and KNOTREGS, which are the cubes of the excess over the corresponding knots. The knots are chosen to be at maturities 2, 4, 6 and 8 (in half years).
dec vect[series] baseregs(3)
*
ompute nknots=4
dec vect knots(nknots)
compute knots=||2.0,4.0,6.0,8.0||
dec vect[series] knotregs(nknots)
clear(zeros) knotregs baseregs
The series VALUEADJ will be the value of the bond adjusted for the constant term in \(P(m)\) and for the partial iniitial coupon. It starts off with the full value, but is reduced for the two adjustments. Note that the "entries" here are bonds, not time.
set valueadj = value
This loops over the bonds to generate the required regressors. We walk backwards through the coupon dates. The final one (first when working backwards) includes both the face value and the final coupon.
do i=1,nbonds
compute mdate=maturity(i)
*
compute cdate=mdate
while (cdate > 0.0) {
if cdate==mdate
compute receipt=coupon(i)+100.0
else
compute receipt=coupon(i)
compute valueadj(i)=valueadj(i)-receipt
do j=1,3
compute baseregs(j)(i)=baseregs(j)(i)+receipt*cdate^j
end do j
do k=1,nknots
compute knotregs(k)(i)=knotregs(k)(i)+receipt*%plus(cdate-knots(k))^3
end do k
compute cdate=cdate-1
}
*
* Adjust for simple interest payable by the purchaser for the initial
* coupon. cdate will be -(fraction of period).
*
compute valueadj(i)=valueadj(i)-coupon(i)*cdate
end do i
This defines a FUNCTION which computes, for a given maturity \(m\), the value of the cubic spline \(P(m)\).
function cspline m
type real cspline m
*
compute cspline=1.0+%beta(1)*m+%beta(2)*m^2+%beta(3)*m^3
do k=1,nknots
compute cspline=cspline+%beta(3+k)*%plus(m-knots(k))^3
end do k
end spline
and this computes the value of that over test values for the maturity on (0,10] periods (half years), converts to (annualized) yields and graphs.
set testm 1 50 = .20*t
set pvalue 1 50 = cspline(testm)
*
* Convert the discount factor into an annualized yield curve
*
set testm 1 50 = testm/2
set yield 1 50 = -100.0*log(pvalue)/testm
scatter(style=line,header="Yield Curve",hlabel="Years",vlabel="Yield")
# testm yield
Full Program
open data bonds.xls
data(format=xls,org=cols) / coupon value maturity
compute nbonds=%allocend()
*
* The data set consists of US Treasury bonds, which have semi-annual
* coupons, but with the coupon stated in annual terms. The maturity
* series also provides annual information. The coupon is divided by two
* and the maturity multiplied by 2 to give values for the actual coupon
* period.
*
set coupon = coupon / 2
set maturity = maturity * 2
*
* This models P(m)=discount factor for maturity m as a cubic spline.
*
* These are for the unrestricted linear, quadratic and cubic terms. (The
* constant has to be 1).
*
dec vect[series] baseregs(3)
*
* These are for the knots
*
compute nknots=4
dec vect knots(nknots)
compute knots=||2.0,4.0,6.0,8.0||
dec vect[series] knotregs(nknots)
clear(zeros) knotregs baseregs
*
* This is for the value of the bond adjusted for the constant terms in
* P(n) and for the partial initial coupon.
*
set valueadj = value
*
* Loop over the bonds to generate the required set of regressors.
*
do i=1,nbonds
compute mdate=maturity(i)
*
* Walk backwards through the payments. The last one will include the
* face value as well as the final coupon.
*
compute cdate=mdate
while (cdate > 0.0) {
if cdate==mdate
compute receipt=coupon(i)+100.0
else
compute receipt=coupon(i)
*
* Adjust the value for the forced "1" in the base cubic
*
compute valueadj(i)=valueadj(i)-receipt
do j=1,3
compute baseregs(j)(i)=baseregs(j)(i)+receipt*cdate^j
end do j
do k=1,nknots
compute knotregs(k)(i)=knotregs(k)(i)+receipt*%plus(cdate-knots(k))^3
end do k
compute cdate=cdate-1
}
*
* Adjust for simple interest payable by the purchaser for the initial
* coupon. cdate will be -(fraction of period).
*
compute valueadj(i)=valueadj(i)-coupon(i)*cdate
end do i
*
linreg valueadj
# baseregs knotregs
*
* Graph the estimated yield curve from maturities of 0->10 periods (5
* years). After the present value function is evaluated, the maturity is
* switched to years and the annual yield computed.
*
function cspline m
type real cspline m
*
compute cspline=1.0+%beta(1)*m+%beta(2)*m^2+%beta(3)*m^3
do k=1,nknots
compute cspline=cspline+%beta(3+k)*%plus(m-knots(k))^3
end do k
end spline
*
set testm 1 50 = .20*t
set pvalue 1 50 = cspline(testm)
*
* Convert the discount factor into an annualized yield curve
*
set testm 1 50 = testm/2
set yield 1 50 = -100.0*log(pvalue)/testm
scatter(style=line,header="Yield Curve",hlabel="Years",vlabel="Yield")
# testm yield
Output
Linear Regression - Estimation by Least Squares
Dependent Variable VALUEADJ
Usable Observations 20
Degrees of Freedom 13
Centered R^2 0.9999138
R-Bar^2 0.9998741
Uncentered R^2 0.9999783
Mean of Dependent Variable -17.00996528
Std Error of Dependent Variable 10.11615070
Standard Error of Estimate 0.11351627
Sum of Squared Residuals 0.1675172733
Log Likelihood 19.4452
Durbin-Watson Statistic 3.0522
Variable Coeff Std Error T-Stat Signif
************************************************************************************
1. BASEREGS(1) -0.027617599 0.002329879 -11.85366 0.00000002
2. BASEREGS(2) -0.003125137 0.002225416 -1.40429 0.18367020
3. BASEREGS(3) 0.000786045 0.000508590 1.54554 0.14620552
4. KNOTREGS(1) -0.001083921 0.000734908 -1.47491 0.16404094
5. KNOTREGS(2) 0.000566519 0.000443154 1.27838 0.22346887
6. KNOTREGS(3) -0.000492874 0.000463276 -1.06389 0.30674222
7. KNOTREGS(4) 0.000032001 0.000990301 0.03231 0.97471224
Graph
.png)
Copyright © 2026 Thomas A. Doan