Examples / BONDS.RPF |
BONDS.RPF is an illustration of the use of a FUNCTION to evaluate a complicated objective function. In this case, it evaluates the present value of a bond with multiple coupon payments as part of the estimation of a yield curve using a portfolio of bonds. See also the BONDSPLINE.RPF example which does a similar analysis, but with a cubic spline.
Suppose that we want to estimate a yield-curve using data from many bonds. For each bond, we have the following information:
• Periods to maturity (series MATURITY).
• The coupon (series COUPON).
• The current market value (series VALUE).
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.
open data bonds.xls
data(format=xls,org=cols) / coupon value maturity
compute nbonds=%allocend()
*
set coupon = coupon / 2
set maturity = maturity * 2
We will model the function \(\delta \left( T \right)\), which gives the present value of $1 at \(T\), by
\begin{equation} \delta \left( T \right) = \exp \left( { - T\left( {a_0 + a_1 T + a_2 \max \left( {0,T - C} \right)} \right)} \right) \label{eq:BondsDiscount} \end{equation}
The term multiplying \(–T\) is a piecewise linear function with a join point at \(C\) (which will be called CUSP in the program).
We need to minimize over the parameters of \eqref{eq:BondsDiscount} the gaps between the present values predicted by the model and the actual market values. However, the present value for a bond (which is a single entry in the data set) requires a sum over all the coupons due until maturity. We can’t use annuity formulas for this because the interest rate isn't fixed. So we create a FUNCTION that takes as its argument the number of the bond and uses the data in the three series described above to compute the present value of the bond given the parameter settings being evaluated. The start of is:
function BondPV bond
type real BondPV
type integer bond
local real mdate cdate
In practice, you would start with those first three lines and add the LOCAL after you've written the rest, once you know what other local variables you will need. The FUNCTION code needs to give a value to BondPV before it returns. In this case, we will keep setting and resetting the value of this as we add terms to the present value. To start, we take the discounted value of the redemption at maturity:
compute mdate=maturity(bond)
compute BondPV=100.0 * $
exp(-mdate*(a0+mdate*a1+%plus(mdate-cusp)*a2))
The next part of this walks backwards through the coupon payments as long as there are full coupon periods, adding the discounted value to what's in BondPV.
compute cdate=mdate
while (cdate > 0.0) {
compute BondPV=BondPV + coupon(bond) * $
exp(-cdate*(a0+cdate*a1+%plus(cdate-cusp)*a2))
compute cdate=cdate-1
}
And finally we adjust for the simple interest payable by the purchaser for the initial coupon. CDATE will be –(fraction of period). Note that you don’t need a RETURN, just END to indicate that the function is finished.
compute BondPV=BondPV+coupon(bond)*cdate
end
With that done, we still need a FRML which gives the value for each entry. Here, all this FRML needs to do is return the value of the function for entry T. The first three lines are best placed before the FUNCTION since it needs A0, A1, A2 and CUSP:
Before the FUNCTION definition:
nonlin a0 a1 a2
compute a0=.030,a1=a2=0.0
compute cusp=2.0
After the FUNCTION definition:
frml BondPrice value = BondPV(t)
*
nlls(robusterrors,frml=BondPrice) value
This graphs the estimated yield curve from maturities of 0->10 periods (5 years). (TESTM is the "test" maturity, which has values running over (0,10]. After the present value function is evaluated, the maturity is switched to years and the annual yield computed.
set testm 1 50 = .20*t
set pvalue 1 50 = $
exp(-testm*(a0+testm*a1+%plus(testm-cusp)*a2))
set testm 1 50 = testm/2
set yield 1 50 = -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
*
nonlin a0 a1 a2
compute a0=.030,a1=a2=0.0
compute cusp=2.0
*
function BondPV bond
type real BondPV
type integer bond
*
local real mdate cdate
*
compute mdate=maturity(bond)
compute BondPV=100.0 * $
exp(-mdate*(a0+mdate*a1+%plus(mdate-cusp)*a2))
*
* Walk backwards through the coupons.
*
compute cdate=mdate
while (cdate > 0.0) {
compute BondPV=BondPV + coupon(bond) * $
exp(-cdate*(a0+cdate*a1+%plus(cdate-cusp)*a2))
compute cdate=cdate-1
}
*
* Adjust for simple interest payable by the purchaser for the initial
* coupon. cdate will be -(fraction of period).
*
compute BondPV=BondPV+coupon(bond)*cdate
end
*
frml BondPrice value = BondPV(t)
*
nlls(robusterrors,frml=BondPrice) value
*
* 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.
*
set testm 1 50 = .20*t
set pvalue 1 50 = $
exp(-testm*(a0+testm*a1+%plus(testm-cusp)*a2))
set testm 1 50 = testm/2
set yield 1 50 = -log(pvalue)/testm
scatter(style=line,header="Yield Curve",hlabel="Years",vlabel="Yield")
# testm yield
Nonlinear Least Squares - Estimation by Gauss-Newton
Convergence in 3 Iterations. Final criterion was 0.0000000 <= 0.0000100
With Heteroscedasticity-Consistent (Eicker-White) Standard Errors
Dependent Variable VALUE
Usable Observations 20
Degrees of Freedom 17
Centered R^2 0.9997669
R-Bar^2 0.9997394
Uncentered R^2 0.9999990
Mean of Dependent Variable 106.99531250
Std Error of Dependent Variable 7.35723688
Standard Error of Estimate 0.11876009
Sum of Squared Residuals 0.2397673034
Log Likelihood 15.8594
Durbin-Watson Statistic 2.3282
Variable Coeff Std Error T-Stat Signif
************************************************************************************
1. A0 0.028560456 0.000620441 46.03253 0.00000000
2. A1 0.001772348 0.000338740 5.23217 0.00000017
3. A2 -0.001926927 0.000349390 -5.51511 0.00000003
Copyright © 2025 Thomas A. Doan