A C++ Encoded Hull-White Interest Rate Tree-BuilderA C++ Encoded Hull-White Interest Rate...
Transcript of A C++ Encoded Hull-White Interest Rate Tree-BuilderA C++ Encoded Hull-White Interest Rate...
A C++ Encoded Hull-White Interest Rate Tree-Builder
John H. Li1
Duke UniversityDurham, NC
April 15, 2002
1 John Li graduated from Trinity College, Duke University Class of 2002 with and BS degree and High Distinctionhonors in Economics. He also holds a minor in Computer Science. He is employed by Deutsche Bank and will beworking in the Corporate Derivatives group of the Global Markets Division. He will reside in London and New YorkCity.
2
Acknowledgement
I would like to gratefully thank my independent study advisor, Dr. Bjorn Eraker, for
recommending this research topic and providing me excellent guidance during the study. With his
enthusiasm and great efforts to explain things clearly and simply, he helped to make the esoteric parts of
finance understandable for me during my financial engineering independent study. Throughout my
thesis-writing period, he provided encouragement, sound advice, good teaching and many ideas.
I wish to acknowledge Deutsche Bank’s Corporate Derivatives desk for providing me with up-
to-date term structures and market prices for specific interest rate derivatives at my request. I
particularly wish to express appreciation to my future manager and head of the desk, Raj Bhattacharrya,
for his enthusiastic supervision during this project.
I am indebted to my uncle and Johnson Graduate School of Management professor, Dr.
Charles Lee, for his continued support and assistance in the preparation of this manuscript. Gratitude is
also extended to Brian Bevan and John Mayer for their support.
Lastly and most importantly, I wish to thank my parents, Dr. Pei-Luen Li and Ling Li. I am
forever indebted to them for their understanding, endless patience and encouragement when it was most
required. To them I dedicate this thesis.
3
Abstract
The Hull-White model is a single-factor, no arbitrage approach to modeling the term structure of
interest rates. It models the term structure by describing the evolution of the short rate, or the
instantaneous rate of interest. Implementing this model results in a trinomial pricing tree that can be used
to price complex interest rate derivatives such as options on swaps and bonds. The difficulty of this
model lies in its relative complexity and multi-stage implementation. The model's advantage over similar
models is its calculation speed.
This paper does not develop a new method but rather explains the author’s original
implementation of the algorithm behind the Hull-White interest rate model using C++ programming
code. The paper will first explain the generalized Hull-White model. It will then explain the construction
of the Hull-White tree by correlating each step in the model's two-stage tree-building procedure with the
C++ program architecture. The paper will then run the Hull-White model using current market data to
price a one-year bond option on a ten-year zero-coupon bond and briefly explain the discrepancies in
the instrument's market price and calculated price. The paper will also describe some of the limitations
of the program and discuss possible future improvements.
4
Introduction
Interest rate derivatives are financial instruments whose payoffs are a function of the term
structure of interest rates. Like foreign exchange and equity derivatives, interest rate derivatives are
extremely important in today's economy for both risk management and speculative purposes. As a
result, many new financial products have been created to meet the needs of end users such as
corporations, banks, money managers and insurance companies. The challenge lies in finding an
accurate procedure for pricing and hedging these products.
Pricing interest rate products is more complicated than pricing foreign exchange or equity
derivative for several reasons. A primary reason is because for the valuation of many interest rate
products, it is necessary to develop a model describing the behavior of today's entire term structure of
interest rates2. As a result, a no-arbitrage term structure model must be created. The no-arbitrage
property ensures that the value generated by the term structure model is exactly consistent with the
market's bond prices implied by the zero-coupon yield curve. There are generally two different
approaches to building no-arbitrage yield curve models: describing the evolution of the forward rate and
describing the evolution of the short-term interest rate3. Each approach has its strengths and
weaknesses.
The first approach describes the evolution of the instantaneous forward rate and was first
developed by Heath, Jarrow, and Morton. The Heath-Jarrow-Morton (HJM) model considers the
current term structure with a user specification of the volatility of forward rates to build a tree to model
the behavior of the instantaneous forward rate. "The HJM tree of forward rates is the fundamental unit
representing the evolution of interest rates in a given period of time4." Brace, Gatarek and Musiella
(1997) extended this model into the LIBOR Market Model (LMM) that allows one to apply the model
to observable non-instantaneous forward rates, such as the 3-month LIBOR. The approach behind
both models results in an easily understandable tree implementation that permits as complex of a
volatility structure as desired. "The HJM-LMM models provide approaches that give the user complete
2 Hull, J., Options, Futures and Other Derivatives, Prentice Hall, 2000. pg 530.2 Hull, J. and A. White, "The General Hull-White Model and Super Calibration," Financial Analysts Journal, Vol. 57,No. 6 (Nov/Dec 2001), pg 37.
5
freedom in choosing the volatility term structure5." The weakness of the approach is that the trees
created cannot be represented as recombining trees; calculations are based on a statistical process.
HJM-LMM models are thus difficult to implement by any other means other than Monte-Carlo
simulation6. This makes the accurate pricing of interest rate derivatives both time and computationally
inefficient.
The second approach to modeling the yield curve is to take the initial term structure as given and
describe how the short-term interest rate, the rate that applies over the next short interval of time, can
evolve7. Models of the short rate are implemented in the form of a recombining tree similar to the stock
price tree first developed by Cox, Ross and Rubinstein (1979) and do not need to be statistically
calculated8. As a result, interest rate trees implemented using this approach are both robust and
computationally fast; most models used for routine interest rate derivatives pricing are based on this
approach. The method's weakness is its relative complexity and lack of flexibility in the user
specification of the volatility environment. Example models of the short-term interest rate include the Ho
and Lee model as well as the Hull and White model. This study examines the implementation of the
single-factor Hull-White model.
The Hull-White Model
The single-factor, no-arbitrage Hull-White model is a model where the function of the
instantaneous interest rate (short rate), r, follows the following stochastic differential equation:
dy = (?(t)-ay)dt + sdz (1)
where y = f(r) is some function of the short rate, ?(t) is a function of time chosen so that the model
provides an exact fit to today's zero-coupon yield curve, a is the mean reversion rate, dt is a small
change in time, s is the annual standard deviation of the short rate and dz is a Wiener process9. A
4 "Heath-Jarrow-Morton Model," http://www.mathworks.com/access/helpdesk/ help/toolbox/finderiv/using8.shtml,The MathWorks Inc., 2001.5 Hull Options, pg 618.6 Hull “The General Hull-White” pg 38.7 Hull, Options, pg. 596.8 Hull, “The General Hull-White,” pg. 39.9 “CurveTrader Online Help,” http://www.powerfinance.com/help. Leap of Faith Research, Inc., 1998.
6
trinomial tree is used to construct a discrete time and space Markov approximation of the state variable
y.
The parameters a and s make up the volatility parameter (state factor) that is chosen by the user
to calibrate the model to the market prices of a set of actively traded interest-rate derivatives10. The
model assumes that the short rate is normally distributed and subject to mean reversion, the well
documented phenomenon where interest rates appear to drift to a long-run average level over time. The
model also assumes there are no market frictions, taxes nor transaction costs. It is assumed that assets
are perfectly divisible and trading takes place at discrete time steps11.
For this study the author identifies the short rate as the state variable:
y = r
A downside to setting the state variable equal to the short rate is the possibility of negative interest rates.
However, in practice this probability is small. Furthermore, when y = r and a ? 0, the model reduces
to the analytically tractable model:
dr = (?(t)-ar)dt + sdz (2)
as shown and proven in Hull and White (1990). The model is analytically tractable because it allows for
the pricing at time t of a zero-coupon bond maturing at time T in terms of the initial term structure and r
at time t12. Specifically, Hull and White proved:)(),(),(),( trTtBeTtATtP −= (3)
where:
ae
TtBtTa )(1
),(−−−
= (4)
( ) ( )( )
( ) ( ) ( ) ( )141,0ln
,,0,0
ln,ln 2223
−−−∂
∂−= −− atataT eee
attP
TtBtPTP
TtA σ (5)
10 Hull, “The General Hull-White,” pg 39.11 Leippold, M. and Z. Wiener, "The Term Structure of Interest Rates II: The Hull-White Trinomial Tree of InterestRates, 1999, pg 1.12 Hull, J. and A. White, "Using Hull-White Interest Rate Trees," Journal of Derivatives, Vol. 3, No. 3, (Spring 1996),pp. 28, 30.
7
In practice, bond prices are usually computed in terms of R(t), the discrete ?t-period at time t rather
than r. Hull and White convert equation (3) to:
( ) ( ) )(),(ˆTt,ˆ, tRTtBeATtP −= (6)
where:
( ) ( )( )
( )( )
( )( )
−∆+
∆+−=
tPttP
tttBTtB
tPTP
TtA,0
,0ln
,,
,0,0
ln,ˆln (7)
( ) ( ) ( ) ( )[ ]tttBTtBTtBea
at ∆+−− − ,,,14
22σ
( )( )
ttttB
TtBTtB ∆
∆+=
,,
),(ˆ (8)
An alternative would be to set:
y = ln(r)
This prevents the chance of negative interest rates, but has no analytic tractability13.
The Hull-White model can be viewed as an extension of the Ho and Lee model with mean
reversion rate of a; when a = 0, the model reduces to the Ho-Lee model. Furthermore, when written
as:
dzdtyat
ady σθ
+
−=
)(
the Hull-White model can be characterized as an extension of the Vasicek model with a time-dependent
reversion level of at)(θ
at rate a14.
A trinomial interest rate tree is a discrete representation of the stochastic process for the short
rate15. The C++ implementation of the Hull-White model roughly follows the two-stage procedure for
constructing trinomial trees. Each stage will be outlined.
13 Hull, Options, pg. 587.14 Hull, Options, pg. 574.15 Hull, Options, pg. 578.
8
Theoretical Implementation- First Stage
The Hull-White interest rate tree is a discrete-time representation of the stochastic process for
the short rate. Each step on the tree represents a point in time, ti. The time step on the tree is ?t=ti+1-
ti. Each node on the interest rate tree at time ti with a relative tree position j is denoted as node (i,j).
t0 t1 t2 t3
j=3
j=2
j=1
j=0
j=-1
j=-2
j=-3
node(1,1)
node(3,-3)
Figure 1
It is assumed that the ?t-period interest rate at time t, R(t), follows the same process as the short rate, r:
dR = (?(t)-aR)dt + sdz (9)
The goal of the first stage is to construct a tree such that the central node at each time step,
node (i,0), has a value of zero16. This is achievable by defining a new variable R* obtained from R by
setting both setting ?(t) and the initial value of R equal to zero17. R* follows the process:
dR* = -aR*dt + sdz (10)
where the expected value of ( ) ( ) ( )dttaRtRttR *** −=−∆+ ; its variance is equal to s 2?t18.
Setting ?(t) to zero and the initial value of R* is zero results in a process that is mean reverting to
zero. If R* starts at zero the unconditional expected value of R* at all future times is zero (Hull 4).
Define ?R as the interest rate spacing between the nodes on the tree, fit to represent the volatility of R,
computed to the Hull-White recommended specifications for error minimization:
tR ∆=∆ 3σ
16 Hull, “Using Hull-White,” pg. 28.17 Hull, “Using Hull-White,” pg. 28.18 Hull, Options, pg. 581.
9
The upward, middle and downward branching probabilities for each node are then set to match the
expected value and the standard deviation of the change in R* for the process in equation (10)19.
Solving this system of equations leads to the following probabilities.
? u = 26
1 222 tajtja ∆−∆+
? m = 222
32
tja ∆−
? d = 26
1 222 tajtja ∆+∆+
The model incorporates mean reversion by setting a limit variables jmax equal to the smallest integer
greater than ta∆
84.1 and jmin = 0 - jmax. If x at any node is greater than jmax or less than jmin, the branching
will switch to a downward or upward branching pattern, respectively.
Standard Downward Upward
pu pu pu
pm pm pm
pd pd pd
Figure 2
The probabilities for an upward-branching method are:
? u =26
1 222 tajtja ∆+∆+
? m = tajtja ∆−∆−− 231 222
19 Hull, “Using Hull-White,” pg. 27.
10
? d = 2
367 222 tajtja ∆+∆
+
The probabilities for a downward-branching method are:
? u = 2
367 222 tajtja ∆−∆
+
? m = tajtja ∆+∆−− 231 222
? d = 26
1 222 tajtja ∆−∆+
Hull-White (1990) proved this equation prevents any of the probabilities of any node from being
negative. The end product of the first stage is a recombining tree with a time step of ?t and vertical
spacing of ?R that is symmetrical around 0.
Implementation- 1st Stage
This first stage is created by iterating through the tree twice- once to construct and connect
nodes and once to add each node's respective probabilities. The end result is a tree represented as a
vector of nodes.
0 1
j=2
j=1
j=0
j=-1
j=-2
2 Depth
0
1
2
3
4
5
6 Vector Position 7
8
Relative Position
Figure 3
Vector Position 0 1 2 3 4 5 6 7 8Depth 0 1 1 1 2 2 2 2 2Relative Position 0 1 0 -1 2 1 0 -1 -2
Table 1
11
Each node is represented in C++ as a struct containing the following data:
struct myNode{ int nodeNumber; // node's numerical position on the vector int depth; // equals the depth of the node int relativePosition; // equals the relative position of the node float rate; // equals the delta(t) rate for the node float presentValue; // equals the node's Arrow-Debreu price float alpha; // equals g(t) float pu; // probability of branching up float pm; // probability of branching middle float pd; // probability of branching down
myNode * up; // the node connected via the highest branch myNode * middle; // the node connected via the middle branch myNode * down; // the node connected via the lowest branch....
The program’s first iteration uses the user-inputted term structure. The program will build a tree whose
depth, D, matches the depth of the term structure inputted and assumes that each interest rate given is
for time D*?t. The time step, ?t, is a user input. For simplicity, the root node is hard coded:
void HullTree::connectNodes(tvector<float> structure){ // initializing the root node maxdepth = 0; myTree[0]->depth = 0; myTree[0]->relativePosition = 0; myTree[0]->presentValue = 1.00000;
if (structure.size() > 1) { myTree[0]->up = myTree[1]; myTree[0]->middle = myTree[2]; myTree[0]->down = myTree[3]; myTree[1]->depth = 1; myTree[2]->depth = 1; myTree[3]->depth = 1; myTree[1]->relativePosition = 1; myTree[2]->relativePosition = 0; myTree[3]->relativePosition = -1; }...
For robustness, the calling one of two functions creates all other depths: expand, which expands the
number of nodes vertically, and maintain, which maintains the number of nodes during the next time
step. A portion of maintain is shown below:
int HullTree::maintain(int lastNodeNumber, int nodesInDepth, int tempDepth)
12
{ int beginningNode = lastNodeNumber-nodesInDepth+1;… // prevent the top node from expanding myTree[beginningNode]->up = myTree[beginningNode + nodesInDepth]; myTree[beginningNode]->middle = myTree[beginningNode + nodesInDepth + 1]; myTree[beginningNode]->down = myTree[beginningNode + nodesInDepth + 2];
// expand the middle nodes accordingly for (int i = 1; i <= nodesInDepth - 2; i++) { myTree[beginningNode + i]->up = myTree[beginningNode + i +nodesInDepth - 1]; myTree[beginningNode + i]->middle = myTree[beginningNode + i +nodesInDepth]; myTree[beginningNode + i]->down = myTree[beginningNode + i +nodesInDepth + 1]; }
myTree[lastNodeNumber]->up = myTree[lastNodeNumber + nodesInDepth -2]; myTree[lastNodeNumber]->middle = myTree[lastNodeNumber + nodesInDepth -1]; myTree[lastNodeNumber]->down = myTree[lastNodeNumber + nodesInDepth];
// adding relativePosition int divider = (nodesInDepth - 1) / 2; for (int a = 0; a < nodesInDepth; a++) { myTree[beginningNode + nodesInDepth + a]->relativePosition = divider - a; } return nodesInDepth;}
The function, connectNodes, chooses to either expand or maintain the width of the tree by comparing
the interest rate of node(i,j) to jmin and jmax limits.
// tempNode is the bottom-most node of the nodes in depth dif (100 * tempNode->relativePosition * deltaR > jMax || 100 * tempNode->relativePosition * deltaR < jMin) { maintain(lastNodeNumber, nodesInDepth, count); } else expand(lastNodeNumber, nodesInDepth, count);...
Adding branching probabilities requires a second iteration through the vector of nodes, this time
adding the up, middle and down probabilities to each node based on the above formulas:
void HullTree::udm(myNode * node){ if (node->relativePosition * deltaR > jMax)
13
{ node->pu = (7.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(3 * meanReversion * node->relativePosition*deltaT))/2); node->pm = (0.00000-(1.00000/3.00000)) - (meanReversion *meanReversion * node->relativePosition * node->relativePosition * deltaT *deltaT)+(2 * meanReversion * node->relativePosition*deltaT); node->pd = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(meanReversion * node->relativePosition*deltaT))/2); }
else if (node->relativePosition * deltaR < jMin) {... } else { node->pu = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(meanReversion * node->relativePosition*deltaT))/2); node->pm = (2.00000/3.00000) - (meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT); node->pd = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)+(meanReversion * node->relativePosition*deltaT))/2); }}
The end product is a tree represented as a vector of nodes, with each node containing pointers to other
nodes within the vector and probabilities attached to the pointers.
Vector Position 0 1 2 3 4 5 6 7 8R* 0.000% 1.732% 0.000% -1.732% 3.464% 1.732% 0.000% -1.732% -3.464%? u 0.16667 0.121667 0.166667 0.221667 0.086667 0.121667 0.166667 0.221667 0.286667? m 0.66667 0.656667 0.666667 0.56667 0.626667 0.656667 0.666667 .0656667 0.62666? d 0.16667 0.221667 0.166667 0.121667 0.286667 0.221667 0.166667 0.121667 0.08666Up (Vector Position) 1 4 5 6Middle 2 5 6 7Down 3 6 7 8Depth 0 1 1 1 2 2 2 2 2Relative Position 0 1 0 -1 2 1 0 -1 -2
Table 2
Theoretical Implementation- 2nd Stage
The end product of the first stage tree is a tree that represents the process (10):
dR* = -aR*dt + sdz
14
The second stage in the tree construction is to convert the generic level tree into a tree whose interest
rates are calibrated to the initial term structure. "Within the calibrated tree the prices of the zero bonds
that mature at each tree time-period coincide with those implied by the yield curve currently observed in
the market20." This is accomplished by defining g:
g(t) = R(t) – R*(t) (11)
where:
[ ]dttagtdg )()( −= θ
Since g(t) is a function ?(t) and the function ?(t) is selected so that the model fits the term structure, the
de facto process is to adjust the nodes in the tree so that it correctly prices discount bonds of all
maturities21.
In order for the interest rate tree to be exactly consistent to the initial term structure, g for each
time-step must be calculated iteratively. Given a tree of node(i,j) where ( ni ≤≤0 ; ii mim ≤≤− ),
define the following:
R*(i,j): value of R* at node(i,j)
R(i,j): value of R at node(i,j)
g(ti) = gi = R(i,j) – R*(i,j)
R(0,0) is equal to the term structure rate for 1*?t. As a result, if ?t = 1, R(0,0) is the one year rate on
the yield curve and R(i,j) is the one year rate for year i+1 on the interest rate tree.
Hull and White define Q(i,j|h,k) as an Arrow-Debreu (AD) price, the present value at
node(h,k) of a security that pays off $1 at node(i,j) and zero at any other node22. Q(i,j|h,k) is equal to
the probability of reaching i,j from h,k, discounted at R(h,k). If h = i-1:
Q(i,j|i-1,k) = ))(( 11,1),1|,( −−− −+−− iiiki ttgxekijip
Q(i,j|0,0) can be denoted as Qij as denotes as the root AD price for node(i,j):
20 Leippold, "The Term Structure of,” pg. 10.21 Hull, “The General Hull-White,” pg. 42.22 Hull, “The General Hull-White,” pg. 42.
15
kittgx
k
kik
ij
Qekijip
QkijiQQ
iiiki
,1))((
,1
11,1),1|,(
),1|,(
−−+−
−
−−−−=
−=
∑
∑(12)
Qij is determined for every node j at step i23. Only after computing the Arrow-Debreu prices for
node(i,j) one can begin to calibrate the interest rate tree to the term structure.
Calibration of the interest rate tree is accomplished by matching the price of a discount bond of
length ti+1, P(i+1), to the summation of all of the Arrow-Debreu prices of nodes at time ti, each Arrow-
Debreu price discounted at each node's R(i,j) rate. For example, if ?t =0.5, the summation of the
values for Q(1,1), Q(1,0) and Q(1,-1) discounted at (R*1,1 + g1), (R*1,0 + g1) and (R*1,-1 + g1),
respectively, would be calibrated to the price of a (2*.5) = 1 year discount bond.
Denote P(i+1) as the price at node(0,0) of a discount bond that pays $1 at time ti+1. P(i+1) is
computed using the term structure:))((
111 ++−
+ = ii tyi eP
where yi+1 is the interest rate on the current term structure for ti+1. P(i+1) is matched to the summation
of the present value of Q(i,j) prices. Denote V(i,j) as the present value of Q(i,j), each value discounted
to each node's R = R*(t) + g(t) rate:))(( 1 iiiij ttgx
ij eV −+− +=
Hull and White calculate the present value as
))((
1
1 iiiij ttgx
jij
ijj
iji
eQ
VQP
−+−
+
+∑
∑
=
=
(13)
The formula can be rearranged to solve for gi:
t
PeQ
ii
tRjj ij
g∆
+−∆∆−∑=
)1(lnln(14)
Denote the Arrow Debreu price at node(0,0) = 1. Based on (14), R(0,0) can be calculated. The next
iteration uses R(0,0) to price Q(1,1), Q(1,0), and Q(1,-1). Only after Q(1,1), Q(1,0) and Q(1,-1) are
23 Hull ,“The General Hull-White,” pg. 42-43
16
determined can R(1,1), R(1,0) and R(1,-1) be calculated through calibration techniques. This iterative
technique is used until i = n total steps and the interest rate tree is complete.
Implementation- 2nd Stage
The completion of the first stage is an interest rate tree centered around zero. The second stage
is implemented by iterating through the vector of nodes one depth at a time. The function that
orchestrates the iterative process is function addRates. During the iteration of each depth within
addRates, a temporary vector, depthVector, containing all nodes at time t is created.
void HullTree::addRates(tvector<float> structure){ ... int tempDepth = 1;
for (int a = 1; a < myTree.size(); a++) { // we're put all nodes of the same depth on a vector if (myTree[a]->depth == tempDepth) { depthVector.push_back(myTree[a]); } else { tempDepth++; ...
Each node within depthVector has its Arrow-Debreu prices calculated using each predecessor node's
R rates as an input. Because C++ pointers are one-directional—nodes that node(a,b) points to as its
highest, middle and lowest branch nodes do not have pointers back to node (a,b)—a search algorithm
is created to find the predecessors of each node in depthVector and return these predecessor nodes
through a temporary vector, tempVector.
tvector<myNode*> HullTree::findConnectors(myNode * node){ tvector<myNode *> tempVector; for (int count = 0; count < myTree.size(); count++) { if (myTree[count]->up == node || myTree[count]->middle == node||myTree[count]->down == node) { myNode * tempNode = myTree[count]; tempVector.push_back(tempNode); }
17
} return tempVector;}
Given tempVector, the Arrow-Debreu price for the current node[a] in depthVector is calculated.
float HullTree::addPresentValue(myNode * node, tvector<myNode *> depthVector,tvector<float> structure){ float alpha= 0.00000; // going through those connecting nodes, finding Q for depthVector[a] for (int b = 0; b < tempVector.size(); b++) { if (tempVector[b]->up == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pu *exp(0.00000 - (tempVector[b]->rate)*deltaT) * tempVector[b]->presentValue +depthVector[a]->presentValue; } else if (tempVector[b]->middle == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pm *exp(0.00000 - (tempVector[b]->rate)*deltaT) * tempVector[b]->presentValue +depthVector[a]->presentValue; } else if (tempVector[b]->down == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pd *exp(0.00000 - (tempVector[b]->rate)*deltaT) * tempVector[b]->presentValue +depthVector[a]->presentValue; } ...
This process is run for each node in depthVector. Using depthVector's Arrow-Debreu prices, gi is
determined.
... g = 0; for (int c = 0; c < depthVector.size(); c++) { g = g + depthVector[c]->presentValue * exp(0.00000 - (deltaR *deltaT * depthVector[c]->relativePosition)); } g = log(g); g = g - log(bondPrices[depthVector[0]->depth]); g = g / deltaT; return g;}
18
This value is returned to the central node of time ti, node(i,0) in addRates. The central node's R*(i,0)
value is zero; its new calibrated interest rate, R(i,0), is set to gi. The remaining nodes at ti have their
R(i,j) values calculated using function addRemainingRates.
void HullTree::addRemainingRates(myNode * tempNode, tvector<myNode *>depthVector){ for (int a = 0; a < depthVector.size(); a++) { depthVector[a]->rate = depthVector[a]->relativePosition * deltaR +tempNode->rate; }}
depthVector gets erased after each node in the vector has its R(i,j) values calculated. The vector gets
recreated for the next time period, beginning the iteration for time i+1. This iterative process continues
until the tree is calibrated. The C++ implementation of both stages is illustrated in Appendix A.
Pricing Analysis
The Hull-White tree generated allows for the pricing of numerous interest rate derivatives.
Many of such instruments have their values derived from the U.S. Treasury term structure, shown below
for April 10, 2002:
Term StructureMaturity Yield (%)3 month 1.76 month 1.972 year 3.445 year 4.610 year 5.2530 year 5.71
Table 3
19
Figure 4
One such derivative is a European put option on a zero-coupon bond. A common instrument using a
portfolio of such options is an interest rate cap, an option that provides a payoff whenever a specified
interest rate exceeds a certain level24. The specific derivative priced is a one-year put option on a zero-
coupon bond that will expire in ten years. The notional price is $1000. The strike price is at the money;
the current price of the bond is $592.74. It is assumed that a = 1 and s = 0.01.
Consider the construction of an eight-step, one-year tree. The time step is ?t = 1 / 8 = 0.125
years and the rate step, ?r = t∆3σ = 0.0061237. jmin and jmax values are calculated as the smallest
integer greater than 0.184 / a?t = 15. Inputting a, s , ?t and the term rates for 1/8, 2/8, 3/8...8/8 year
into the C++ tree generator results in a completed interest rate tree with ?t-period rate, R, at each
node. Rates for maturities between given interest rates in Table 3 are calculated using linear
interpolation. Table 4 presents the completed tree and corresponding g = R – R* rates; appendix B
displays the program’s true output in full detail.
Branching Probabilities ?t rate, Rj ? up ? middle ? down i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 77 0.1267 0.6590 0.2142 0.07616 0.1320 0.6610 0.2070 0.0675 0.07005 0.1374 0.6628 0.1999 0.0589 0.0614 0.06384 0.1429 0.6642 0.1929 0.0503 0.0528 0.0552 0.05773 0.1486 0.6653 0.1861 0.0416 0.0442 0.0467 0.0491 0.05162 0.1545 0.6660 0.1795 0.0331 0.0355 0.0381 0.0405 0.0430 0.0455
24 Hull, Options, pg. 665.
20
1 0.1605 0.6665 0.1730 0.0247 0.0270 0.0294 0.0320 0.0344 0.0369 0.03930 0.1667 0.6667 0.1667 0.0162 0.0185 0.0209 0.0232 0.0258 0.0283 0.0308 0.0332
-1 0.1730 0.6665 0.1605 0.0124 0.0148 0.0171 0.0197 0.0222 0.0246 0.0271-2 0.1795 0.6660 0.1545 0.0086 0.0110 0.0136 0.0160 0.0185 0.0210-3 0.1861 0.6653 0.1486 0.0049 0.0075 0.0099 0.0124 0.0148-4 0.1929 0.6642 0.1429 0.0013 0.0038 0.0063 0.0087-5 0.1999 0.6628 0.1374 0.0000 0.0001 0.0026-6 0.2070 0.6610 0.1320 0.0000 0.0000-7 0.2142 0.6590 0.1267 0.0000
g 0.0162 0.0185 0.0209 0.0232 0.0258 0.0283 0.0308 0.0332Table 4
Figure 5 is a graphical representation of the tree without branching lines between nodes.
Nodes
0
0.01
0.02
0.03
0.04
0.05
0.06
0.07
0.08
0 2 4 6 8
Time Steps
Rat
e
Nodes
Figure 5
Next, the bond payoff, P(t,T), at each terminal node (ti = 8) must be calculated. This is done
analytically through equations (3) through (8). Table 5 presents a spreadsheet which calculates the
P(t,T) values analytically.
User Inputs Leaf Node Calculationst 1 Leaf Node Rates P(t,T)
T 10 0.0761 0.4764deltaT 0.125 0.0700 0.4941
a 0.1 0.0638 0.5125sigma 0.01 0.0577 0.5316
0.0516 0.5514
21
Term Structure Calculations 0.0455 0.5720P(0,t) 0.9757 0.0393 0.5933
P(0,T) 0.5927 0.0332 0.6154P(0,t+deltaT) 0.9714 0.0271 0.6383
deltaR 0.0061 0.0210 0.6621
0.0148 0.6867Calculations 0.0087 0.7123
B(t,T) 5.9343 0.0026 0.7388Bhat(t,T) 5.9715 0.0000 0.7503
B(t,t+deltat) 0.1242 0.0000 0.7503Ahat(t,T) 0.7503
Table 5
User-inputted values are italicized. Appendix C shows the calculation of each formula using Microsoft
Excel cell references.
Calculating the value of the put option whose strike price is the current bond price, $592.74, is
accomplished by calculating the option payoff, 1000*MAX(0.59274 - P(t,T), 0), where MAX takes
the higher value of 0.59274-P(t,T) or zero. Table 6 provides these payoffs.
Leaf Node CalculationsStrike Price (x1000) Leaf Node Rates P(t,T) Put Option Price
0.592739659 0.0760759 0.476392195 116.3474638 0.0699522 0.494135092 98.60456681 0.0638285 0.512538811 80.20084794 0.0577048 0.531627964 61.11169533 0.0515811 0.551428079 41.31158047 0.0454573 0.571965976 20.77368251 0.0393336 0.593268453 0 0.0332099 0.615364325 0 0.0270862 0.638283142 0 0.0209624 0.662055951 0 0.0148387 0.686713766 0 0.00871498 0.712290029 0 0.00259126 0.738818865 0 0 0.750339983 0 0 0.750339983 0
Table 6
22
Each option price must then be discounted back through the tree. The process works in
reverse: from end nodes(i,j), the value at node(i-1,k) is computed as the discounted sum of all of its
branching nodes’ values. Specifically,
Standard Branching: ( ) ))((1,,1,,1
1,1 −− −−−+− Π+Π+Π= iiki ttR
kidownkimiddekiupki evvvv
Downward Branching: ( ) ))((2,1,,,1
1,1 −− −−−−− Π+Π+Π= iiki ttR
kidownkimiddekiupki evvvv
Upward Branching: ( ) ))((2,1,,,1
1,1 −− −−++− Π+Π+Π= iiki ttR
kidownkimiddekiupki evvvv
Table 7 iterates from i = 7 backwards to i = 0 and discounts at each time step using the rates and
transition probabilities in Table 4.
Put Option Pricej i = 0 i = 1 i = 2 i = 3 i = 4 i = 5 i = 6 i = 77 116.34756 96.3210 98.60465 76.4811 78.3106 80.20084 56.7367 58.1482 59.6071 61.11173 37.4190 38.1860 39.1058 40.1843 41.31162 20.8372 20.5673 20.3003 20.0950 20.1095 20.77371 9.7706 8.9816 8.0483 6.9027 5.4161 3.3187 0.00000 4.0198 3.3902 2.7176 2.0045 1.2660 0.5512 0.0000 0.0000
-1 0.8362 0.5363 0.2818 0.0951 0.0000 0.0000 0.0000-2 0.0619 0.0170 0.0000 0.0000 0.0000 0.0000-3 0.0000 0.0000 0.0000 0.0000 0.0000-4 0.0000 0.0000 0.0000 0.0000-5 0.0000 0.0000 0.0000-6 0.0000 0.0000-7 0.0000
Table 7
The price of the put option is calculated to be $4.0198, or 0.402% of the bond’s notional value.
Comparison
The market’s ask price for the bond option is $4.39, or 0.439% of the bond's notional. This is
a $0.37 increase over the tree-calculated option price. There are several explanations for this
difference, listed in decreasing hypothesized significance:
23
• Illiquidity premium- While puts and calls on bonds are quite common, many are bundled into
portfolios to create easily hedged interest rate caps and floors. One-year options for zero
coupon bonds are seldom traded alone in the over-the-counter market and as a result, there
may be an extra illiquidity premium added to the price of the option. When asked how often
options off the Treasury curve are traded, the analyst covering the desk replied that she prices
bond options of duration of this length once a year.
• Pricing inaccuracy- The interest rate derivatives analyst covering short-term options did not
have a model immediately available that was capable of pricing this particular option. As a
result, she entered the yield curve into a European swaption model to generate the price of the
bond option. It is unclear what possible differences in assumptions or calculations the analyst
used to generate the market price.
• Differences in volatility assumptions- The Hull-White model contains a single factor, volatility,
that can be modified to fit the market price of the derivative. Determining the volatility
parameters a and s is known as model calibration and is out of the scope of the article. In this
example, a and s are assumed to be .1 and .01, respectively. These numbers were chosen
because they match the volatility parameters of examples priced by Hull, Leippold and Wiener.
It is unknown if the volatility parameters chosen result in a minimal or near minimal goodness-of-
fit measure25. Likewise, the market model priced was without calibration; it is unknown what
volatility assumptions should be used.
• Time step inaccuracy- The interest rate tree generated in the exercise has only eight steps and is
considered a rough approximation of the option price because each time step is equivalent to
over 45 days. When calculated using 20 time steps (?t = 18.25 days) the price of the bond
option increases to $4.16 (appendix D). It is shown that continuing to increase the resolution of
the tree will continue narrow the difference between the market price and calculated price26.
• Linear interpolation of interest rates- For this exercise linear interpolation of the term structure is
used. This violates the no-arbitrage assumption described in the beginning of the article as the
term structure used in the tree is no longer exactly consistent with the market yield curve.
25 Hull, Options, pg. 593.26 Hull, Options, pg. 589.
24
• Limitations of the model- The single-factor Hull-White model is limited in its flexibility in
managing volatility. "The model can be made to provide a fit to volatilities observed in the
market, but the user has no control over the volatilities at subsequent times27." A solution is to
generate a model whose volatility is dependent on time. Hull and White (1996) create such a
model:
d(f(r)) = (?(t)+u-af(r))dt +s 1dz1
where u has an initial value of zero and follows the process
du=-budt+s 2d2
The C++ tree-constructor is flexible enough to be easily modified to accommodate this feature
and create three-dimensional trees. However, it was felt that a model with time-dependent
volatility was not suitable for this article because its pricing would require intense calibration.
Furthermore, making volatility time-dependent may lead many to believe that volatility as a
function of time, implying that there can be assumptions on what the factor is in the future. This
is an incorrect assumption.
It can only be concluded that the calculated price serves as an approximation of the true price of the
bond option.
The C++ tree-builder itself can be improved. Firstly, the code should be able to handle and
automatically interpolate inputted two, three, five, 10, and 30-year treasury rates. This function is
currently done by hand. Later developments include having a built in bond-pricer and a function that
automatically calculates user-inputted payoff functions. These functions are currently either hand-
calculated or run on a fairly inflexible spreadsheet. These developments could be done by converting
the source file into a Microsoft Excel-linkable dynamic link library or add-on. These functions would
make this program comparable to the highly marketed Pricingtools.com-created Hull-White tree
constructor. Long-run developments include a calibrating function that connects to a listing of prices of
similar derivatives on the Internet.
27 Hull, Options, pg. 601.
25
Summary
In this article the single factor Hull-White term structure model is explained. No new theoretical
aspects are added to the model; rather, an advanced C++-encoded algorithm implementing its tree-
building procedure is described. The program's vital functions are shown to roughly match the two-
stage construction process first developed by Hull. When executed, the program is shown to be a
flexible tree-generator capable of modeling the short-term interest rate and pricing interest rate
derivatives. An example bond option is then priced twice using the current term structure, once using
eight time-steps and once at twenty time-steps. Finally, time is devoted to discussing the possible
reasons for the discrepancies between the market price and the model-generated price of the option.
26
References
“CurveTrader Online Help,” http://www.powerfinance.com/help. Leap of Faith Research, Inc., 1998.
"Heath-Jarrow-Morton Model," http://www.mathworks.com/access/helpdesk/
help/toolbox/finderiv/using8.shtml, The MathWorks Inc., 2001.
Hull, J., Options, Futures and Other Derivatives, Prentice Hall, 2000.
Hull, J. and A. White, "The General Hull-White Model and Super Calibration," Financial Analysts
Journal, Vol. 57, No. 6 (Nov/Dec 2001), pp. 34-43.
Hull, J. and A. White, "Using Hull-White Interest Rate Trees," Journal of Derivatives, Vol. 3, No. 3,
(Spring 1996), pp. 26-36.
Leippold, M. and Z. Wiener, "The Term Structure of Interest Rates II: The Hull-White Trinomial Tree
of Interest Rates, 1999, 1-17.
27
Appendix A- Hullwhite.Cpp
// ------------------------------------------------// Written by John Li// 3/18/2002// ------------------------------------------------
#include <iostream>#include <string>#include "tvector.h"#include "math.h"#include "fstream.h"#include "hulltree.h"
float meanReversion;float deltaT;float deltaR;float jMax;float jMin;tvector<float> termStructure;
void getInputs(){ float sigma; float tempFloat; string tempfile;
ifstream inputFile; cout << "Enter File: "; cin >> tempfile; // what's the file inputFile.open(tempfile.c_str()); // open it
inputFile >> meanReversion; inputFile >> sigma; inputFile >> deltaT;
// defining term structure while (inputFile >> tempFloat) { termStructure.push_back(tempFloat); } // figuring out deltaR deltaR = sigma * sqrt(3 * deltaT); // figuring out jMin and jMax jMax = ceil( 0.184 / (meanReversion * deltaT) ); jMin = 0 - jMax;}
int main(){
28
HullTree myHullTree; getInputs(); myHullTree.buildTree(termStructure, meanReversion, deltaT, deltaR,jMin, jMax);}
29
Appendix A- Hulltree.H
#ifndef _HULLTREE_H#define _HULLTREE_H// The hull tree// John Li
#include "tvector.h"#include "math.h"
struct myNode{
int nodeNumber; int depth; // equals depth of the tree int relativePosition; // equals j (-2,-1,-0, 1, 2) for the node float rate; // equals R for the node float presentValue; // equals Q for the node float alpha; // equals value of center node= term struct
float pu; // probability of going upfloat pm; // probability of going in the middlefloat pd; // probability of going down
myNode * up; myNode * middle; myNode * down;
myNode(int & i, int & a, int & b, float & c, float & d, float & e,float & z, float & y, float & x, myNode * f = NULL, myNode * g = NULL, myNode* h = NULL) : nodeNumber(i),
depth(a), relativePosition(b), rate(c), presentValue(d), alpha(e),
pu(z), pm(y), pd(x),
up(f), middle(g), down(h) { }};
class HullTree{
public:
HullTree(); // constructor ~HullTree(); // destructor
void udm(myNode * node);
30
void addBondPrices(tvector<float> structure);tvector<myNode *> findConnectors(myNode * node);float addPresentValue(myNode * node, tvector<myNode *>
depthVector, tvector<float> structure); void addRemainingRates(myNode * tempNode, tvector<myNode *>depthVector);
void addRates(tvector<float> structure); int expand(int lastNodeNumber, int nodesInDepth, inttempDepth); int maintain(int lastNodeNumber, int nodesInDepth, inttempDepth);
void connectNodes(tvector<float> structure);void outputTree();void buildTree(tvector<float> structure, float a, float dT,
float dR, float min, float max);
private:
tvector<myNode *> myTree;
// same size as structure, saves alpha values per termtvector<float> alphaStructure;
// same size as structure, saves bond prices per term tvector<float> bondPrices;
// same size as structure, saves width per termtvector<int> width;
myNode * rootNode;float meanReversion;float deltaT;float deltaR;float jMin;float jMax;
};
#endif
31
Appendix A- Hulltree.Cpp
// ------------------------------------------------// Written by John Li// 3/19/2002// ------------------------------------------------
#include "hulltree.h"#include "tvector.h"#include "math.h"#include <string>#include <fstream>
// default constructorHullTree::HullTree(){
}
// destructorHullTree::~HullTree(){
}
//this figures out the Pu, Pm, and Pd for each nodevoid HullTree::udm(myNode * node){ if (node->relativePosition * deltaR * 100 > jMax) { node->pu = (7.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(3 * meanReversion * node->relativePosition*deltaT))/2); node->pm = (0.00000-(1.00000/3.00000)) - (meanReversion *meanReversion * node->relativePosition * node->relativePosition * deltaT *deltaT)+(2 * meanReversion * node->relativePosition*deltaT); node->pd = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(meanReversion * node->relativePosition*deltaT))/2); }
else if (node->relativePosition * deltaR * 100 < jMin) { node->pu = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)+(meanReversion * node->relativePosition*deltaT))/2); node->pm = (0.00000-(1.00000/3.00000)) - (meanReversion *meanReversion * node->relativePosition * node->relativePosition * deltaT *deltaT) - (2 * meanReversion * node->relativePosition*deltaT); node->pd = (7.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)+(3 * meanReversion * node->relativePosition*deltaT))/2);
32
} else { node->pu = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)-(meanReversion * node->relativePosition*deltaT))/2); node->pm = (2.00000/3.00000) - (meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT); node->pd = (1.00000/6.00000) + (((meanReversion * meanReversion *node->relativePosition * node->relativePosition * deltaT *deltaT)+(meanReversion * node->relativePosition*deltaT))/2); }
}
// calculates bond prices based on the term structurevoid HullTree::addBondPrices(tvector<float> structure){ for (int a = 0; a < structure.size(); a++) { float temp = exp(0.00000 - (structure[a] * (a+1)*deltaT));
bondPrices.push_back(temp); }
}
// finds and returns connecting nodes for the calculation of Qtvector<myNode*> HullTree::findConnectors(myNode * node){ tvector<myNode *> tempVector; for (int count = 0; count < myTree.size(); count++) { if (myTree[count]->up == node || myTree[count]->middle == node||myTree[count]->down == node) { myNode * tempNode = myTree[count]; tempVector.push_back(tempNode); } } return tempVector;}
// adds Qfloat HullTree::addPresentValue(myNode * node, tvector<myNode *> depthVector,tvector<float> structure){
float alpha= 0.00000;
for (int a = 0; a < depthVector.size(); a++)
33
{ depthVector[a]->presentValue = 0;
// find connecting nodes to each node of the same depth tvector<myNode *> tempVector = findConnectors(depthVector[a]);
// going through those connecting nodes, finding Q for depthVector[a] for (int b = 0; b < tempVector.size(); b++) { if (tempVector[b]->up == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pu * exp(0.00000 -((tempVector[b]->rate)*deltaT)) * tempVector[b]->presentValue +depthVector[a]->presentValue; } else if (tempVector[b]->middle == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pm * exp(0.00000 -((tempVector[b]->rate)*deltaT)) * tempVector[b]->presentValue +depthVector[a]->presentValue; } else if (tempVector[b]->down == depthVector[a]) { depthVector[a]->presentValue = tempVector[b]->pd * exp(0.00000 -((tempVector[b]->rate)*deltaT)) * tempVector[b]->presentValue +depthVector[a]->presentValue; } } }
for (int c = 0; c < depthVector.size(); c++) { alpha = alpha + (depthVector[c]->presentValue * exp(0.00000 - (deltaR* deltaT * depthVector[c]->relativePosition))); }
alpha = log(alpha);
alpha = alpha - log(bondPrices[depthVector[0]->depth]);
alpha = alpha / deltaT;
return alpha;
}
// adds the remaining ratesvoid HullTree::addRemainingRates(myNode * tempNode, tvector<myNode *>depthVector){ for (int a = 0; a < depthVector.size(); a++) {
depthVector[a]->rate = depthVector[a]->relativePosition * deltaR +
34
tempNode->rate;
}
}
// adds the term structure onto the center nodesvoid HullTree::addRates(tvector<float> structure){ myTree[0]->rate = structure[0]; myTree[0]->presentValue = 1;
myNode * tempNode = myTree[0];
tvector<myNode *> depthVector; int tempDepth = 1;
for (int a = 1; a < myTree.size(); a++) { // we're put all nodes of the same depth on a vector if (myTree[a]->depth == tempDepth) { depthVector.push_back(myTree[a]); } else { // getting the center node tempNode = tempNode->middle;
// calling present value with the center node and vector of nodes withthe same depth tempNode->rate = addPresentValue(tempNode, depthVector, structure);
// add remaining rates to the nodes in the same depth as tempNode addRemainingRates(tempNode, depthVector);
tempDepth++;
// clear and add the first node of the next depth depthVector.clear(); a--; } }
// getting the center node tempNode = tempNode->middle;
// calling present value with the center node and vector of nodes with thesame depth tempNode->rate = addPresentValue(tempNode, depthVector, structure);
addRemainingRates(tempNode, depthVector);
35
}
int HullTree::expand(int lastNodeNumber, int nodesInDepth, int tempDepth){ int beginningNode = lastNodeNumber-nodesInDepth+1; int t = 0;
// temp variables, then adding on new blank nodes int aa = 0; float bb = 0;
for (int c = 0; c < nodesInDepth + 2; c++) { myNode * tempNode = newmyNode(aa,tempDepth,aa,bb,bb,bb,bb,bb,bb,NULL,NULL,NULL); myTree.push_back(tempNode); }
while (t < nodesInDepth) { myTree[beginningNode+t]->up = myTree[beginningNode + t +nodesInDepth]; myTree[beginningNode+t]->middle = myTree[beginningNode + t +nodesInDepth + 1]; myTree[beginningNode+t]->down = myTree[beginningNode + t +nodesInDepth + 2]; t++; }
// adding relativePosition int divider = (nodesInDepth + 1) / 2; for (int a = 0; a < nodesInDepth+2; a++) { myTree[beginningNode + nodesInDepth + a]->relativePosition = divider - a; }
return nodesInDepth + 2;}
int HullTree::maintain(int lastNodeNumber, int nodesInDepth, int tempDepth){ int beginningNode = lastNodeNumber-nodesInDepth+1;
// temp variables, then adding on new blank nodes int aa = 0; float bb = 0;
for (int c = 0; c < nodesInDepth; c++) { myNode * tempNode = newmyNode(aa,tempDepth,aa,bb,bb,bb,bb,bb,bb,NULL,NULL,NULL); myTree.push_back(tempNode); }
36
// prevent the top node from expanding myTree[beginningNode]->up = myTree[beginningNode + nodesInDepth]; myTree[beginningNode]->middle = myTree[beginningNode + nodesInDepth + 1]; myTree[beginningNode]->down = myTree[beginningNode + nodesInDepth + 2];
// expand the middle nodes accordingly for (int i = 1; i <= nodesInDepth - 2; i++) { myTree[beginningNode + i]->up = myTree[beginningNode + i +nodesInDepth - 1]; myTree[beginningNode + i]->middle = myTree[beginningNode + i +nodesInDepth]; myTree[beginningNode + i]->down = myTree[beginningNode + i +nodesInDepth + 1]; }
myTree[lastNodeNumber]->up = myTree[lastNodeNumber + nodesInDepth -2]; myTree[lastNodeNumber]->middle = myTree[lastNodeNumber + nodesInDepth -1]; myTree[lastNodeNumber]->down = myTree[lastNodeNumber + nodesInDepth];
// adding relativePosition int divider = (nodesInDepth - 1) / 2; for (int a = 0; a < nodesInDepth; a++) { myTree[beginningNode + nodesInDepth + a]->relativePosition = divider - a; }
return nodesInDepth;}
// goes down the vector of myNodes and connects the nodes to each othervoid HullTree::connectNodes(tvector<float> structure){ // temporary variables myNode * tempNode;
// temp variables, originally making a max of 9 nodes int aa = 0; float bb = 0;
for (int c = 0; c < 9; c++) { myNode * tempNode = new myNode(aa,aa,aa,bb,bb,bb,bb,bb,bb,NULL,NULL,NULL); myTree.push_back(tempNode); }
// initializing the root node myTree[0]->depth = 0; myTree[0]->relativePosition = 0; myTree[0]->presentValue = 1.00000;
37
width.push_back(1);
if (structure.size() > 1) { myTree[0]->up = myTree[1]; myTree[0]->middle = myTree[2]; myTree[0]->down = myTree[3]; myTree[1]->depth = 1; myTree[2]->depth = 1; myTree[3]->depth = 1; myTree[1]->relativePosition = 1; myTree[2]->relativePosition = 0; myTree[3]->relativePosition = -1; width.push_back(3); }
if (structure.size() > 2) { myTree[1]->up = myTree[4]; myTree[1]->middle = myTree[5]; myTree[1]->down = myTree[6]; myTree[2]->up = myTree[5]; myTree[2]->middle = myTree[6]; myTree[2]->down = myTree[7]; myTree[3]->up = myTree[6]; myTree[3]->middle = myTree[7]; myTree[3]->down = myTree[8]; myTree[4]->depth = 2; myTree[5]->depth = 2; myTree[6]->depth = 2; myTree[7]->depth = 2; myTree[8]->depth = 2; myTree[4]->relativePosition = 2; myTree[5]->relativePosition = 1; myTree[6]->relativePosition = 0; myTree[7]->relativePosition = -1; myTree[8]->relativePosition = -2; width.push_back(5); }
if (structure.size() > 3) { for (int count = 3; count < structure.size(); count++) { tempNode=myTree[myTree.size()-1];
// see if tempNode->relativePosition * deltaR is greater than jMax orless than jMin if (100 * tempNode->relativePosition * deltaR > jMax || 100 * tempNode->relativePosition * deltaR < jMin) { width.push_back(maintain(myTree.size()-1, width[count-1], count)); }
38
// if not, then make it even bigger else width.push_back(expand(myTree.size()-1, width[count-1], count)); } }}
// used for debugging purposesvoid HullTree::outputTree(){ string filename = "OUTPUT"; ofstream output(filename.c_str());
for (int count = 0; count < myTree.size(); count++) { myTree[count]->nodeNumber = count; }
for (int count = 0; count < myTree.size(); count++) { output << count << " "; output << myTree[count]->depth << " "; output << myTree[count]->relativePosition << " "; output << myTree[count]->rate << " "; output << myTree[count]->pu << " "; output << myTree[count]->pm << " "; output << myTree[count]->pd << endl; }}
// builds the treevoid HullTree::buildTree(tvector<float> structure, float a, float dT,float dR, float min, float max){ // variables which equal referenced values meanReversion = a; deltaT = dT; deltaR = dR; jMin = min; jMax = max;
connectNodes(structure); // connect the nodes
for (int count = 0; count < myTree.size(); count++) { udm(myTree[count]); }
addBondPrices(structure); addRates(structure); outputTree();}
39
40
Appendix B
Node # i j R ?up ?middle ?down0 0 0 0.016175 0.166667 0.666667 0.1666671 1 1 0.02465 0.160495 0.66651 0.1729952 1 0 0.018527 0.166667 0.666667 0.1666673 1 -1 0.012403 0.172995 0.66651 0.1604954 2 2 0.033125 0.154479 0.666042 0.1794795 2 1 0.027001 0.160495 0.66651 0.1729956 2 0 0.020878 0.166667 0.666667 0.1666677 2 -1 0.014754 0.172995 0.66651 0.1604958 2 -2 0.00863 0.179479 0.666042 0.1544799 3 3 0.041602 0.14862 0.66526 0.18612
10 3 2 0.035479 0.154479 0.666042 0.17947911 3 1 0.029355 0.160495 0.66651 0.17299512 3 0 0.023231 0.166667 0.666667 0.16666713 3 -1 0.017108 0.172995 0.66651 0.16049514 3 -2 0.010984 0.179479 0.666042 0.15447915 3 -3 0.00486 0.18612 0.66526 0.1486216 4 4 0.050333 0.142917 0.664167 0.19291717 4 3 0.044209 0.14862 0.66526 0.1861218 4 2 0.038085 0.154479 0.666042 0.17947919 4 1 0.031961 0.160495 0.66651 0.17299520 4 0 0.025838 0.166667 0.666667 0.16666721 4 -1 0.019714 0.172995 0.66651 0.16049522 4 -2 0.01359 0.179479 0.666042 0.15447923 4 -3 0.007467 0.18612 0.66526 0.1486224 4 -4 0.001343 0.192917 0.664167 0.14291725 5 5 0.058912 0.13737 0.66276 0.1998726 5 4 0.052789 0.142917 0.664167 0.19291727 5 3 0.046665 0.14862 0.66526 0.1861228 5 2 0.040541 0.154479 0.666042 0.17947929 5 1 0.034418 0.160495 0.66651 0.17299530 5 0 0.028294 0.166667 0.666667 0.16666731 5 -1 0.02217 0.172995 0.66651 0.16049532 5 -2 0.016046 0.179479 0.666042 0.15447933 5 -3 0.009923 0.18612 0.66526 0.1486234 5 -4 0.003799 0.192917 0.664167 0.14291735 5 -5 -0.00232 0.19987 0.66276 0.1373736 6 6 0.067494 0.131979 0.661042 0.20697937 6 5 0.06137 0.13737 0.66276 0.1998738 6 4 0.055246 0.142917 0.664167 0.19291739 6 3 0.049122 0.14862 0.66526 0.1861240 6 2 0.042999 0.154479 0.666042 0.17947941 6 1 0.036875 0.160495 0.66651 0.17299542 6 0 0.030751 0.166667 0.666667 0.16666743 6 -1 0.024628 0.172995 0.66651 0.16049544 6 -2 0.018504 0.179479 0.666042 0.15447945 6 -3 0.01238 0.18612 0.66526 0.1486246 6 -4 0.006256 0.192917 0.664167 0.142917
41
47 6 -5 0.000133 0.19987 0.66276 0.1373748 6 -6 -0.00599 0.206979 0.661042 0.13197949 7 7 0.076076 0.126745 0.65901 0.21424550 7 6 0.069952 0.131979 0.661042 0.20697951 7 5 0.063829 0.13737 0.66276 0.1998752 7 4 0.057705 0.142917 0.664167 0.19291753 7 3 0.051581 0.14862 0.66526 0.1861254 7 2 0.045457 0.154479 0.666042 0.17947955 7 1 0.039334 0.160495 0.66651 0.17299556 7 0 0.03321 0.166667 0.666667 0.16666757 7 -1 0.027086 0.172995 0.66651 0.16049558 7 -2 0.020962 0.179479 0.666042 0.15447959 7 -3 0.014839 0.18612 0.66526 0.1486260 7 -4 0.008715 0.192917 0.664167 0.14291761 7 -5 0.002591 0.19987 0.66276 0.1373762 7 -6 -0.00353 0.206979 0.661042 0.13197963 7 -7 -0.00966 0.214245 0.65901 0.126745
42
Appendix C
B10 C D E F G
11 User Inputs Leaf Node Calculations
12 t 1 Leaf Node
Rates P(t,T)13 T 10 0.0761 $D$27*EXP(0-$D$25*F13)14 deltaT 0.125 0.0700 $D$27*EXP(0-$D$25*F14)15 a 0.1 0.0638 $D$27*EXP(0-$D$25*F15)16 sigma 0.01 0.0577 $D$27*EXP(0-$D$25*F16)
17 0.0516 $D$27*EXP(0-$D$25*F17)
18 Term Structure Calculations 0.0455 $D$27*EXP(0-$D$25*F18)19 P(0,t) Term Structure-DONE'!E5 0.0393 $D$27*EXP(0-$D$25*F19)20 P(0,T) Term Structure-DONE'!E13 0.0332 $D$27*EXP(0-$D$25*F20)21 P(0,t+deltaT) Term Structure-DONE'!E6 0.0271 $D$27*EXP(0-$D$25*F21)22 deltaR D16*SQRT(3*D14) 0.0210 $D$27*EXP(0-$D$25*F22)
23 Calculations 0.0148 $D$27*EXP(0-$D$25*F23)24 B(t,T) (1-EXP(0-(D15*(D13-D12))))/D15 0.0087 $D$27*EXP(0-$D$25*F24)25 Bhat(t,T) D24*D14/D26 0.0026 $D$27*EXP(0-$D$25*F25)26 B(t,t+deltat) ((1-EXP(0-(D15*D14)))/D15) -0.0035 $D$27*EXP(0-$D$25*F26)
27 Ahat(t,T)
EXP(LN(D20/D19)-((D24/D26)*LN(D21/D19))-
(((D16*D16)/(4*D15))*(1-EXP(0-(2*D15*D12)))*D24*(D24-D26))) -0.0097 $D$27*EXP(0-$D$25*F27)
43
Appendix D
Leaf Node CalculationsStrike Price (x1000) Leaf Node Rates P(t,T) Put Option Price
0.592739659 0.107542 0.394178702 198.5609571 0.103669 0.40336644 189.3732193 0.0997957 0.412769067 179.9705919 0.0959228 0.422389868 170.3497908 0.0920498 0.432235168 160.5044913 0.0881768 0.442309947 150.4297121 0.0843038 0.452619555 140.1201043 0.0804308 0.463169465 129.5701944 0.0765578 0.473965278 118.7743813 0.0726849 0.485012437 107.7272219 0.0688119 0.496317378 96.42228055 0.0649389 0.507885822 84.85383732 0.0610659 0.519723909 73.01575033 0.0571929 0.531837924 60.90173459 0.0533199 0.5442343 48.50535859 0.049447 0.556919287 35.82037224 0.045574 0.569900273 22.83938581 0.041701 0.583183828 9.555831284 0.037828 0.596777003 0 0.033955 0.610687015 0 0.030082 0.62492125 0 0.0262091 0.639486884 0 0.0223361 0.654392402 0 0.0184631 0.669645346 0
t 1 Leaf Node Rates P(t,T) Leaf Node Rates P(t,T) Leaf Node Rates P(t,T)T 10 0.10754 0.39418 0.04945 0.55692 0.00000 0.74739
deltaT 0.05 0.10367 0.40337 0.04557 0.56990 0.00000 0.74739a 0.1 0.09980 0.41277 0.04170 0.58318 0.00000 0.74739
sigma 0.01 0.09592 0.42239 0.03783 0.59678 0.00000 0.747390.09205 0.43224 0.03396 0.61069 0.00000 0.747390.08818 0.44231 0.03008 0.62492 0.00000 0.74739
P(0,t) 0.9757 0.08430 0.45262 0.02621 0.63949 0.00000 0.74739P(0,T) 0.5927 0.08043 0.46317 0.02234 0.65439 0.00000 0.74739
P(0,t+deltaT) 0.9740 0.07656 0.47397 0.01846 0.66965 0.00000 0.74739deltaR 0.0039 0.07268 0.48501 0.01459 0.68525
0.06881 0.49632 0.01072 0.70123B(t,T) 5.9343 0.06494 0.50789 0.00684 0.71757
Bhat(t,T) 5.9492 0.06107 0.51972 0.00297 0.73430B(t,t+deltat) 0.0499 0.05719 0.53184 0.00000 0.74739
Ahat(t,T) 0.7474 0.05332 0.54423 0.00000 0.74739
User Inputs
Term Structure Calculations
Calculations
Leaf Node Calculations
44
0.0145901 0.685253815 0 0.0107171 0.701226094 0 0.00684414 0.717570494 0 0.00297115 0.734295985 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0 0 0.747390658 0