Summary of Generation 4 Interest Rate Model Library
IR Library 4 includes four term structure models:
· Three single-factor short-rate models: the Black-Karasinski (BK) model (lognormal), the Squared Gaussian (SqG) model (normal process squared), and the Hull-White (HW) model (normal). One can fit any of the models to the observed at-the-money (ATM) volatility term structure for swaptions rather accurately and very quickly. Single-factor models operate on a penthanomial lattice.
· A two-factor, steady-correlation, short-rate Gaussian (TFG) model (normal). Much like in all single-factor cases, this model can be quickly calibrated to a set of ATM swaptions, but it also accepts two inter-rate correlation inputs. Once they are made, the model will keep inter-rate correlation structure steady in forward time. For example, a user or developer can define correlation between the 2-yr swap rate and the short rate as 80% and correlation between the 10-yr swap rate and the short rate as 50%. Unlike single-factor models, the two-factor model operates uses very accurate analytical approximations, without a lattice.
Each of the models can operate with time-dependent volatility as well as with a constant one. Calibration to the ATM swaption surface is done “on average” unless the number of swaptions matches the number of internal parameters of the model, in which case it is meant to be exact.
Quick calibration to swaps (or option-free bonds) and swaptions and (for the two-factor case) correlation structure was made possible due to various exact or very accurate analytical approximations. They facilitate no-arbitrage in rates and an accurate prediction of swaption values. Examples include a special analytic for convexity adjustment, assessment of “Black-Scholes volatility compression” effect that deflates options in non-linear short-rate models, approximations for the long rates and their volatility, etc. No actual instruments, swaps or swaptions, are priced on a lattice during calibration and lattice construction.
The relevant set of long rates requested by the user is produced on the lattice and agrees with the underlying short rate process. For the two-factor case, they are stored in the form of analytical relationships.
Generation 4 IR Library enforces several solid forward sampling techniques over the rate lattice (single-factor cases) or using analytical relationships (the two-factor case). Users are recommended to employ our enhanced sampling method (a "Quasi Monte-Carlo"), which pre-processes the "random" shocks before they are actually used to generate paths. This pre-processing, called ortho-normalization, ensures the shocks are scaled to the proper volatility and independent of each other. The other useful and recommended technique is short-rate “fudging”, i.e. adding an artificial, time-variant, adjustment to the short-rate paths that ensure exact valuation of option-free instruments, even with a limited path sample.
Before taking full the advantage of the new IR Library 4 consider the following facts:
· Normal models are proven to better reflect both historical data and the implied volatility skew (i.e. the market perception). The SqG model can also be viewed as a reasonable choice that precludes negative rates.
· The time-dependent volatility version, for either model, better replicates the observed swaption market.
· Negative rates are not precluded if one employs the HW model or the TFG model. Although it will not result in a sizable mispricing, the user must ensure negative rates are formally legitimate (cause no computational troubles) for any non-AD&Co application or function called.
· Enhanced sampling technique is 2 - 2.5 times more accurate than the regular sampling, for the same number of paths, but starts with pre-processing.
· The fudging option has virtually no drawbacks.
· The durations of most mortgage instruments are going to be shorter under HW than under BK and may better match the trading practice (the SqG case will be in between.) Mortgage derivatives, IOs, POs, and MSRs may have stronger or weaker rate sensitivity - depending on their Vega.
· The two-factor model carries a minimal valuation effect for pass-throughs and ARMs. CMO tranches that are much shorter or much longer than the collateral will be affected when switching to the two-factor model.
· Generation 4 Library has a somewhat different API. The changes were motivated by the need to have a multi-threaded system that can be used on Internet/Intranet servers. Some of the arguments passed to functions in generation 3 are now embedded in the main data structure IRIOStructure (see the next section). Most of the typical calls accept only one argument – a pointer to this structure.
· The library is normally compiled for a “sparse” rate grid that is fast and fair for MBS applications, but can be recompiled for a monthly or any other user-desired grid. A monthly lattice is necessary for derivative pricing.
The table below summarizes changes and enhancements.
Generation 3
Generation 4
Models covered
3 single-factor models: BK, SqG, and HW
Same + Two-factor Gaussian model (TFG)
Rate inputs
14 points used or skipped
Same
Arbitrage freeness on the lattice
Exact on the benchmark points; perfectly accurate for HW, very accurate for SqG, fairly accurate for BK between them
Same + perfectly accurate for TFG
Interpolation
Spline interpolation of the input curve
Same
Volatility parameter
Constant or time-dependent
Constant or time-dependent
Calibration to swaption volatilities
Fast calibration to an ATM swaption vol matrix - up to 3*) user-defined swap maturities and many expirations. Automatically finds the best volatility in the model, either constant or time-dependent, and mean reversion parameter
Same; calibration of the TFG model involves more parameters and, hence, somewhat more accurate
Calibration to correlation(s)
N/A
Accepts 2 correlations between the short-rate and two user-defined long rates
Long rates generated
Three at a time*)
Same
Lattice technology
Enhanced to fit theoretical standards for arbitrage-freeness and the future rate statistics
Same for the single-factor models; lattice is not used for the two-factor model
Forward sampling technique
An enhanced sampling method over lattice with ortho-normalized shocks. Option to “fudge” short-rate paths
Same applies except the sampling is continuous, lattice-free, for the TFG.
Multi-threaded
No
Yes
*) at a special user's request, AD&CO can expand the long-rate maturity set
*) at a special user's request, AD&CO can expand the long-rate maturity set
Most commonly, the library file is named adco_ir, but it can be named with modifications reflecting compiler options (adco_ir_mo for monthly lattice, for example). A companion library, bondwrap, can be used for integration with a client’s derivative systems.
Main Data Structures, Header Files, Inputs, Outputs
ADIRdefs.h header file
Here is the full text of the ADIRdefs.h header file that contains most of the data types, globals, and exposed functions needed:
extern ADCO_IR_IMPEXP const int realtreas[YCPOINTS]; /* maturities in months potential benchmarks */
extern ADCO_IR_IMPEXP const int intpdtreas[intpdYCPOINTS];
#else /* MAKE_ADIR defined */
/* months covered by lattice (TIMES=45) */
ADCO_IR_IMPEXP const int tnodes[TIMES+1]= {0,2,5,11,17,23,29,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,
239,251,263,275,287,299,311,323,335,347,359,371,383,395,407,419,431,443,455,467,479,491};
/* months covered by lattice (TIMES=45). Each node precedes
potential bond's or swap's maturity or coupon anniversary by 1 month */
ADCO_IR_IMPEXP const int realtreas[YCPOINTS]={1,3,6,12,24,36,48,60,84,120,180,240,300,360}; /* maturities in months potential benchmarks */
ADCO_IR_IMPEXP const int intpdtreas[intpdYCPOINTS]= {1,3,6,12,18,24,30,36,48,60,72,84,96,108,120,144,180,240,300,360,420,480};
/* maturities in months for potential benchmarks to select from */
static int realtreas[YCPOINTS]={1,3,6,12,24,36,48,60,84,120,180,240,300,360};
/* maturities for spline interpolation */
A user must provide data via filling needed elements ("fields") of the IRIOStructure structure. Do not leave them unassigned. One may call adco_setIOdefault() function first to initialize the IRIOStructure with default values.
1. double YC[YCPOINTS]; /* input yield curve supplied by the user
User supplies up to 14 (generally, YCPOINTS) yield-curve rates that are aligned to and numbered in realtreas[] array. The system does not know whether the Treasuries, agencies, corporates, swaps, or LIBOR accrual and compounding convention applies; all the rates are assumed to be given in the BEY 30/360, percentage form. It is therefore up the user to make all needed conversions before the library is used. If some points are unavailable, set them to MISSING, not zero. Only actual rate inputs will be used by the library to construct the forward curve and calibrate the lattice. Negative rates are accepted for the HW or the TFG models, but not for SqG model or BK model. Example:
IRIOStructure IO;
IO.YC[0] = MISSING;
/* 1-mo skipped */
IO.YC[1] = 1.9;
/* 3-mo */
IO.YC[2] = 2.06;
/* 6-mo */
IO.YC[3] = 2.5675;
/* 12-mo */
IO.YC[4] = 3.671;
/* 24-mo */
IO.YC[5] = 4.295;
/* 36-mo */
IO.YC[6] = MISSING;
/* 48-mo skipped */
IO.YC[7] = 4.998;
/* 60-mo */
IO.YC[8] = 5.4129;
/* 84-mo */
IO.YC[9] = 5.7745;
/* 120-mo */
IO.YC[10] = 6.078;
/* 180-mo */
IO.YC[11] = 6.1979;
/* 240-mo */
IO.YC[12] = MISSING;
/* 300-mo skipped */
IO.YC[13] = 6.2430;
/* 360-mo */
The library will build forward curve using (A) input coupon or zero rates, and (B) spline-interpolated additional rates. In addition, the skipped points are reconstructed and filled. Thus, points IO.YC[0], IO.YC[6] and IO.YC[12] will be filled when library is used, in the example above.
2. enum enumYC flagYC; /* supplied by user: zero or coupon */
This flag interprets the input yield curve as a zero-coupon curve (if flagYC is set to zeroYC) or a coupon curve (if flagYC is set to couponYC).
3. int flagVTS; /* flag whether to have a constant volatility (value
0) or VTS (value 1); supplied by user */
Setting flagVTS to 0 tells the library to use a constant-volatility model, setting it to 1 enables a time-dependent volatility term structure (VTS).
4. int fitMarketVol; /* flag whether to calibrate volatility (value 1) or use
the user-provided input (value 0); supplied by user */
int fitMarketmrev; /* flag whether to calibrate mean reversion (value 1) or the user-provided input (value 0); supplied by user */
These two fields tell the library to employ the fast volatility calibrator (constant or time-dependent) to approximate given swaption matrix. If the value of fitMarketVol is set to 1, volatility will be calibrated; the mean reversion can be either calibrated as well (fitMarketmrev set to 1) or left unchanged (fitMarketmrev set to 0). If the value of fitMarketVol is set to 0, then calibration will not be performed, and the value of fitMarketmrev is going to be disregarded. In other words, the system does not calibrate mean reversion without calibrating volatility.
5. int matr[LONGRATES+1]; /* user-defined long-rate maturities to use; numbered
1 to LONGRATES */
int constructrate[LONGRATES+1]; /* added to 4.5; use 0 if don't want long rates to
be constructed */
The matr[] array determines long rate maturities (up to 3) that the library will work with. The forward curve, the random paths will be constructed for these long rates (if the matching element of constructrate[] array is set to 1), assuming the BEY 30/360 form. The same set of rates is used to assign the swaption volatility matrix (see below). Example:
IRIOStructure IO;
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* undefined; will be disregarded */
The system regards only positive maturity inputs. Element IO.matr[0] is always disregarded since the zero-index is reserved for the short (one-month) rate.
The constructrate[] array tells the library to actually compute lattice of long rates (value of 1) or void this operation (value of 0). In any case, swaption volatility matrix (see the next item) will be associated with tenors defined by matr[] array. The use of constructrate[] array becomes apparent when we work with derivatives typically valued on the monthly lattice with many nodes. Only the short-rate lattice is needed to price swaptions, for example, regardless of the exercise style. Hence, computation of a long rate lattice is redundant, but can be time-consuming.
6. PATHS SwaptionVol[YCPOINTS]; /* swaption volatility matrix in the Black quotation;
supplied by user */
This is an ATM swaption volatility matrix that can be used by the calibration mechanism. This matrix is a YCPOINTS by LONGRATES table with possible expiration set taking from realtreas[] array and possible swap maturities defined by the matr[] array. Regardless of the rate model, volatilities are always quoted in the Black'76, i.e. relative percentage, form. If any of matr[] elements is set to a zero or a negative number, corresponding swaptions will be ignored. Example:
IRIOStructure IO;
int iRate;
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* undefined swap will be disregarded */
for (iRate=1;iRate<=LONRATES;iRate++)
for (j=0;j<YCPOINTS;j++)
IO.SwaptionVol[j].r[iRate]=0; /* initializing to zero */
IO.SwaptionVol[3].r[3]=20.0; /* 1-yr expiry on an undefined swap */
IO.SwaptionVol[9].r[3]=15.0; /* 10-yr expiry on an undefined swap */
In this example, volatility calibrator can use 12 options: 6 options on the 2-year swap and 6 options on the 10-year swap. The third maturity in matr[]array has not been set to a positive number. Therefore, options on this undefined swap will be ignored - even if the volatility inputs were made.
Note that the above input does not start until the 1-year and does not extend beyond the 10-year expiry. The program will extrapolate the "local" volatility for the skipped expirations and remains fully functional - subject to the limited volatility input.
Since the element r[0] of the PATHS structure is reserved for the short rate, elements SwaptionVol[].r[0] are ignored by the calibrator.
7. int numpaths; /* user-supplied number of random or quasi-random
paths */
This is a self-explanatory input that can be omitted only if the user does not plan to generate interest rate paths.
8. long seed; /* use -1 for the first use; will change as random
numbers get generated */
The seed is used to initialize the ADCO's random number generator. A negative 1 should be always used for the first time. After each random call, seed changes and can be used for the next call. Can be omitted only if the user does not plan to generate interest rate paths.
9. int quasi_random_nodes; /* user-supplied number of nodes when quasi-random
sampling applies */
If the user plans to take advantage of the enhanced forward sampling technique ("Quasi Monte-Carlo"), he should input the number of lattice steps, for which this method applies. Using the following assignment,
quasi_random_nodes = TIMES;
guarantees that the quasi Monte-Carlo will apply for the entire possible length of the lattice (492 months). Since the library will never generate random rate paths for more than 372 months (MPL macro defined), setting quasi_random_nodes = 35 yields the same goal. Assigning a smaller number such as quasi_random_nodes = 14 will let the system to use this method for the first 14 time steps (120 months), then switch to a regular sampling. Finally, if quasi_random_nodes = 0, the regular Monte-Carlo sampling will be used (antithetic reflection is provided in either case).
10. enum irprocess process; /* Term structure model selector; user-supplied */
This field tells the library which interest rate model to use.
11. char *datafile_path; /* Where to look for the license key; user-defined */
The full path to the license key file, adco_lic.key.
12. IO.ir2f->corr; /* correlation matrix for the two-factor model */
This is the only element of the ir2f structure that is used as input. The structure is of the PATHS type meaning its elements
are ordered by long rates. For example, element IO.ir2f->corr[1] denotes correlation between the short rate and the long rate defined in matr[1] and so on. Element [0] is not used. See a common code below:
if (irproc == TwoFactorGaussian){
IO->ir2f->corr.r[1] = 0.7;
IO->ir2f->corr.r[2] = 0.4;
IO->ir2f->corr.r[3] = MISSING;
}
Only two correlations matter. If all three are assigned, only elements [1] and [2] will be used. If the total number of assigned correlations is less than 2, an error will be returned as the two-factor model cannot be built.
Dual-use fields
The following fields can be used for both inputs and outputs; some of them become mandatory inputs if logical flags suggest so.
13. double svol; /* short-rate volatility; user-supplied or calibrated
double mrev; mean reversion; user-supplied or calibrated */
These are the short-rate volatility and the mean reversion constants (for singe-factor models). The svol constant is quoted as relative percentage, for the BK model, and as absolute percentage, for the HW model. For the SqG model, the short-rate volatility constant is derived as one-half geometric average of the absolute volatility and the relative volatility. Examples:
svol = 0.25; /* 25% of short-rate volatility */
or
svol = 1.0; /* 1.0% (100 basis points) of absolute volatility */
or
svol = 0.274; /* for SqG model */
The mean reversion constant, mrev, is determined as a decimal, i.e. 0.03 stands for 3%. Both constants, svol and mrev, can be calibrated to a swaption volatility matrix or used as inputs. The short-rate volatility constant svol is not utilized unless flagVTS is set to 0.
14. double svolVTS[TIMES+1]; /* short-rate volatility term structure; used only if
flagVTS=1 will be filled in the library if
fitMarketVol=1 or used as input */
This is a short-rate volatility array, each element of which applies to certain forward time span. If flagVTS = 1, the library will employ a variable-volatility term structure model. Further, if fitMarketVol = 0, then array svolVTS[] defines the short-rate volatility term structure, i.e. it becomes an input array. In this case, the user is responsible for providing the entire array in the model-relevant form; the program will not perform "hole filling" nor quote conversion. If fitMarketVol = 1, this array will be best tuned to match the swaption volatility matrix, SwaptionVol[]. In the latter case, it becomes an output; it is not necessary to initialize it before using the library for it is not used to start the calibration process. Even if the SwaptionVol[] matrix is not completely known, the calibrator will extrapolate or interpolate values.
Note that svolVTS[] array is aligned to the lattice nodes, not to the yield curve points. Element svolVTS[0] denotes volatility between now (month 0) and forward month 2, svolVTS[1] is volatility between forward months 3 and 5, etc.
Library outputs
15. int YCnodes[YCPOINTS]; /* lattice nodes numbers matching the benchmark maturities;
will be filled in the library to represent node numbers
of YC points */
This array "maps" the yield curve points into the lattice. For example, YCnodes[9] is equal to 14 meaning that the 120-month point of the yield curve is aligned to the time node 14 of the lattice. This is a "utility" array that helps compare output volatility arrays (see next) with the given swaption matrix.
16. PATHS FittedVTS[TIMES+1]; /* calibrated Black-equivalent swaption volatility
matrix; will be filled in the library if
fitMarketVol=1 */
This is the calibrated volatility matrix; it is computed by the library once volatility calibration option is selected (i.e. fitMarketVol is set to 1). For example, element FittedVTS[6].r[2] denotes the swaption's implied volatility for the expiration node 6 (looking up the tnodes[] array we find it is the 2-year point) for the underlying swap defined by matr[2]. No calibrated volatility array is computed for an undefined maturity.
Volatility quotations used for this vector are model-dependent: relative for the BK model, absolute for the HW and the TFG models, derived for the SqG model (see quotations for svol above). Note that the expiration nodes are now aligned to the lattice, not to the yield curve points. Use the YCnodes[] utility array to compare the calibrated volatility matrix with the input points, the SwaptionVol[] array.
17. PATHS forward[MAXMONTHS]; /* the interpolated forward curve;
will be filled in the library */
This is the set of forward rates between today and month 372 computed by the program. The short (1-month) forward rates are bound to the spline-interpolated yield curve points. Between them, the forward rates are piece-wise linearly interpolated. From this description, one can conclude that (A) the forward curve is continuous, and (B) it can be made very smooth if one employs many spline nodes.
The short forward rates are all in the MEY 30/360 form. The PATHS component r[0] is reserved for the short rate. For example, forward[19].r[0] is the 1-month rate, 19 month forward; forward[0].r[0] is today's 1-month rate.
Long coupon-bond (or swap) forward rates are computed exactly for forward points listed in the tnodes[] vector and interpolated between them. For instance, forward[19].r[2] is the long rate 19 months forward, for a coupon-bond or a swap maturity defined by matr[2]. All the long rates are in the BEY, 30/360 form. No forward curve is computed for an undefined maturity.
17. double VolMSE; /* accuracy of volatility matrix calibration;
returned by the system will be filled in the library
if fitMarketVol=1 */
This is the accuracy (as a root-mean-squared error measure across swaption volatility matrix) of the volatility calibration, stated in the model-relevant form (relative percentage for BK, absolute percentage for HW and TFG, derived for SqG).
18. PATHS **paths; /* container of random paths; filled by
adco_getpaths() or adco_getpaths2() */
The user is responsible to allocate a two-dimensional array (see Call samples, comments, and instructions later in this document) large enough to store interest rate paths. For example, element paths[125][25].r[0] contains the simulated 1-month rate (MEY 30/360), for run 125, future month 25. All the long rates are for coupon bonds' or swaps' maturities defined in the matr[] array (BEY 30/360). Any element paths[][0].r[] is a rate today.
19. IR2Faddin *ir2f;
This pointer points to the calibration results structure for the two-factor model that contains optimized volatility constants or functions, mean reversion constants, long-rate relationships functions to the model’s factors and some other analytical results. For example, array afforward[MAXMONTHS] contains the mean rates in forward time, double dimensional arrays BT1[LONGRATES+1][YCPOINTS+1] and BT2[LONGRATES+1][YCPOINTS+1] represent linearized relationships to model’s two state variable, x1 and x2. In most practical cases, the user does not need to access this information directly; it is utilized internally by the system.
Builds a lattice calibrated to the yield curve and swaptions (if requested by setting IO->fitMarketVol to 1). Aside from filling out IO fields such as IO->svolVTS[], IO->FittedVTS[], IO->forward[], the function builds the lattice (pointed by IO->latticeX and IO->latticeR)and its transitional probabilities (pointed by IO->TProb). Note that the forward curve construction via a call to adco_getforward() is already embedded in adco_getlattice().
Pointer IO->latticeR reserves a [LEVELS+1] by [TIMES+1] array of PATHS structures. For example, and IO->latticeR[3][23].r[2] is the long rate with maturity given by matr[2], on the 3-rd time step (it is 11 month forward - according to the tnodes[] array), and 23-rd rate level. Today's rates are stored in IO->latticeR[0][].r[]. Pointer IO->latticeX points to a similar lattice of a normal deviate (as doubles) that is mapped to the short rate.
Pointer IO->TProb reserves a 3-dimensional, [LEVELS+1] by [TIMES+1] by [BRANCHES+1], array of doubles. For example, IO->TProb[3][23][2] is the probability to move from the node located on the 3-rd time step of the lattice (11 month forward) and 23-rd rate level along the 2-nd branch (out of 5) evolving from this node. The nodes and branches are counted from the top to the bottom.
Note to users of the TFG model: a call to adco_getklattice is still necessary for rate, volatility and correlation calibration work although the model does not build a lattice.
This function computes the 14-point coupon curve based on the information contained in the library. It should be called at any time after the forward curve is constructed. If IO.flagYC is set to couponYC, it makes no sense to separately call this function because the contents of CouponCurve[] array will be identical to that of IO.YC[] input array with all holes filled. It is only when IO.flagYC is set to zeroYC (zero-coupon rates are used as input), function adco_getCouponCurve() does a non-trivial job.
Builds a lattice calibrated to the yield curve and swaptions (if requested by setting IO->fitMarketVol to 1), generates IO->numpaths random paths pointed by the IO->paths pointer. This function implements an "old-style", version 2, call and is retained for compatibility.
Generates IO->numpaths random paths pointed by the IO->paths pointer assuming the lattice has already been built by the adco_getbklattice() function (see function 2 above). Presents some advantage over adco_getpaths() when multiple calls to the path generator need to be made (perhaps, with different seeds), without any change to the lattice. Other than that, adco_getpaths() and adco_getpaths2() return the same paths.
Adjusts previously generated short-rate paths to ensure exact valuation of option-free instruments – even for a limited path sample. This function should be called right after adco_getpaths or adco_getpaths2.
7. double WINEXP adco_random(long *seed);
This is a utility function generating random numbers. Always start with *seed filled with -1, then keep passing the currently updated seed number. The safest way to obtain a brand new sequence is to make a fair number of calls to adco_random(). The results of these calls can be used or trashed. Simply changing the seed without calling the function is not a good idea as the function actually contains some static variables that get updated on each call.
This function returns a set of deterministic interest rate paths that visit every node of the single-factor lattice. Call to this function is typically required if the instrument is path-independent and can be valued operating backwards on the lattice. As in the case of random sampling, this function stores all paths in the memory pointed by the IO->paths pointer. Not used for the TFG model.
It is important to understand that the default values are not the "best" or "recommended" ones; the sole purpose of this function is to ensure no garbage is contained in IO and memory is allocated. For example, the input rates are all set to MISSING, swaption volatilities and long-rate maturities are all set to zeros. The user, therefore, must provide actual input values, but, once adco_constructIO() is called, it is not necessary to worry about skipped points or memory. Note that IO->paths is set to NULL meaning the caller himself should allocate the memory large enough to hold paths.
Since the function allocates a memory, it should not be called in succession, without calling adco_destroyIO() first (see below). In most case, adco_constructIO() needs to be called once, at the start of user’s application.
This function frees up the memory allocated by the previous function. Should be called at the end of user’s application. A block of memory pointed by IO->paths should be freed directly by the user himself.
Getting the library’s version
11. void WINEXP adco_version(char * VS);
This function works with versinfo.exe - can return the version and license expiration date.
After the lattice is built, these functions can compute additional long rates of maturity matr (not listed in the IO->matr[] array) on all grid points from time 0 till ntsteps; the results are stored in the longrate[][] array. Note that ntsteps is measured in the time nodes, not in months or years. For example, setting ntsteps to 34 fills the longrate[][] array up to the 30-yr horizon. Setting matr to a value already found in the IO->matr[] array is redundant because it will result in a long rate matrix already computed and stored in IO->latticeR[][].r[].
After the forward curve is built, these functions can compute additional forward long rates of maturity matr (not listed in the IO->matr[] array) for ntsteps forward step; the results are stored in the longrate[] array.
14. Forward annuity valuation
WINEXP double STDCALL adco_forward_annuity_static(int matr, IRIOStructure *IO, int ntsteps, int freq);
Computes and returns the present value for a forward annuity that pays $1 per annum at frequency of freq. The annuity starts ntsteps node forward.
Valuation of a cashflow vector
15. Yield-based static valuation
WINEXP IR_Error STDCALL getMEY2Px(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, double yield, double *Price, int NumMonths,
These functions compute yield (MEY form) given forward price or present value, or compute forward price or present value given yield. The cashflow is pointed by pCF and is assumed to run monthly and be NumMonths long. The cashflow-generating instrument can settle forward in Days2Settle days. The cashflow vector timing is Market2PayDays away from valuation day. Argument Yield0 is initial approximation of the yield. Argument NetDelay is not used. Accrued, an accrued interest for the settlement date, is assumed known and passed to all the functions.
16. Rate path based static valuation of a single path
WINEXP IR_Error STDCALL getCFPrice(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, PATHS *path, double *sfrwd,
int NumMonths, double OAS, double Accrued,
double *PV, double *FP);
WINEXP IR_Error STDCALL getCFPrice_OASCorrection(int Market2PayDays, int Days2Settle,
These functions perform somewhat similar role to the previous group except the short rate curve (plus a spread, OAS), not a yield, is used for discounting. The short-rates and OAS are in percents. getCFPrice() function computes the forward price (pointed by FP) and the present value (pointed by PV) using a vector of short rates pointed by sfrwd. This vector starts at the value date, so that sfrwd[0] denotes today’s 1-mo rate, in the MEY 30/360 form.
The two other functions are intended to compute a spread (OAS). Given a vector of cashflows (pCF) and "equivalent yields" (rate_eq) computed with some spread OAS0, function getCFPrice_OASCorrection() recomputes PV and FP for another OAS. Using iterative calls to this “corrective function”, function getCFOAS() find the discount spread and stores it into *pOAS. Note that in both functions the rates sfrwd pointer is used only for the forward settlement calculations.
Backward inductor
17. Backward inductors for a rate-contingent instrument given its cashflow grid (single-factor models only)
The following two functions have fundamental importance for valuation of callable, putable, amortizable, and coupon-changing bonds.
WINEXP IR_Error STDCALL getLatticeBondPrices(int Months2Settle, int Days2Settle,
double *PutPrice, int NumMonths, double basePrice,
double *pOAS, double PVvec[LEVELS+1]);
First, the caller should prepare a two-dimensional array (matrix) of balances (pointed by Bal) and coupon rates (pointed by Coupon) computed without consideration of any embedded calls and puts. The embedded call prices are stored in an array pointed by the CallPrice pointer. For example, if CallPrice[125] is equal to 102, it means that a call struck 102 is available in month 125. If the same element is equal to a large number, say 10000, then the call is not-existing. The PutPrice pointer serves a similar purpose. Array PVvec[] is the present value array found on the lattice at time zero. This array allows measuring the Greeks directly, without resetting the curve.
Parameters Months2Settle and Days2Settle have the same meanings as before whereas NetDelay represents the distance (in calendar days) between the value day and the cashflow day. Argument accrued points to a two-dimensional array of accrued interests pre-computed at all grid’s nodes. The only role of this pointer is to make the backward aware of accrued interests when a call or a put exercise is considered.
The actual use of these two functions requires proper generation and positioning of bond’s cashflows first. This work is done within a specialized library, called bondwrap that simplifies the interface (see next topic).
Day functions
18. Day calendar functions
The interest rate library is equipped with a number of day calendar functions prototyped in ADDate.h. Below is the entire content of that header file:
#ifdef _WIN32
#define WINEXP _declspec(dllexport)
#define STDCALL __stdcall
#else
#define WINEXP
#define STDCALL
#endif
#define SUNDAY 1
#define MONDAY 2
#define TUESDAY 3
#define WEDNESDAY 4
#define THURSDAY 5
#define FRIDAY 6
#define SATURDAY 7
#define JANUARY 1
#define FEBRUARY 2
#define MARCH 3
#define APRIL 4
#define MAY 5
#define JUNE 6
#define JULY 7
#define AUGUST 8
#define SEPTEMBER 9
#define OCTOBER 10
#define NOVEMBER 11
#define DECEMBER 12
typedef struct _adcoDATE
{
int Year;
int Month;
int Day;
} adcoDATE;
typedef enum _DCBTYPES
{
DCB_30_360,
DCB_ACT_360,
DCB_ACT_365,
DCB_ACT_ACT
} DCBTYPES;
typedef enum _BusinessDayConvention
{
BDC_NONE,
BDC_FOLLOWING,
BDC_MODIFIED_FOLLOWING,
BDC_PREVIOUS,
BDC_MODIFIED_PREVIOUS
} BDCTYPES;
WINEXP adcoDATE STDCALL DateAdd(char MDY, adcoDATE myDate, int Step);
WINEXP adcoDATE STDCALL DateSubtract(char MDY, adcoDATE myDate, int Step);
WINEXP int STDCALL DateComp(adcoDATE Date1, adcoDATE Date2);
WINEXP int STDCALL IsLeapYear(int Year);
WINEXP int STDCALL funcLDofM(int Month, int Year);
WINEXP adcoDATE STDCALL MakeDate(int Month, int Day, int Year);
WINEXP adcoDATE STDCALL FindBusinessDate(adcoDATE myDate, int TargetCouponDay, BDCTYPES Bdc);
WINEXP int STDCALL WeekDay(adcoDATE myDate);
WINEXP int STDCALL IsBusinessDate(adcoDATE myDate);
WINEXP int STDCALL IsHoliday(adcoDATE Date1);
int IsXthWeekDayInMonth(adcoDATE Date1,int x, int dayoftheweek, int month);
int IsLastWeekDayInMonth(adcoDATE Date1, int dayoftheweek, int month);
These functions allow computing the day difference using various day counts, building a business day sequence, checking whether a date is a good business date or not, compare dates, add and subtract them, etc. The system 4.5 supports every day count basis enumerated in DCBTYPES above, but only two business day conventions: BDC_NONE and BDC_MODIFIED_FOLLOWING (the latter is used most commonly in U.S. whereas the former allows not using the business day convention).
These functions convert Black-Scholes volatility quotes from one form (say, lognormal) to another (normal or squared Gaussian).
Derivatives Suite
The actual use of the backward inductors described in the previous section requires proper generation and positioning of bond’s cashflows first. This work is done within a specialized library, called bondwrap, that simplifies the programming interface. This “wrapper” library is equipped with simple “set” and “get” functions that operate with base types only and allow passing information to and from the system. Finally, analyzeBond() and analyzeSwap() functions do the entire analytical work. Internally, bondwrap calls many of the functions located in the interest rate library (“main” library). The actual valuation work for bonds and swaps with or without embedded options is done by the backward inductors (section 17 above). This covers European, American and Bermudan exercise, cancelable or extendible swaps or vanilla swaptions.
Caps are priced using different functions. The Black-Scholes analytics is fully supported too, for three distribution types (normal, lognormal, squared Gaussian). This group of functions is prototyped in IRderivatives.h and resides in the main library, not in the wrapper.
Call Samples and Comments
Please refer to file irTest.c that contains numerous useful call examples. They demonstrate how to pass market information, build forward curves and the lattice, generate random or deterministic paths, collect rate statistic, or price bond and derivatives (swaptions, caps). We give below just some of examples.
Constructing the forward curve
This example demonstrates a forward curve construction. Note that we do not allocate memory for IO.paths pointer because we do not plan to generate paths for this application. Nor do we supply volatility information or rate model selection – they don’t matter. We show how to form and pass a swap curve.
void main()
{
callForwardCurve);
}
void callForwardCurve()
{
int i;
IRIOStructure IO;
FILE *outfile;
long numpaths = 10000;
clock_t clockstart;
double timepassed;
for(i = 0; i < MPL; i ++)
{
fprintf(outfile, "%u\t%.4f\t%.4f\t%.4f\t%.4f\n",
i,IO.forward[i].r[0],IO.forward[i].r[1],IO.forward[i].r[2],IO.forward[i].r[3]);
}
endlabel:
fclose(outfile);
adco_destroyIO(&IO); /* third key call */
}
This example is straight-forward: IO initializing, rate and maturity definitions (stored in the IO structure), call to the adco_getforward() function, IO destruction. The output file should contain information for the forward rates, 1-mo, 2-yr, 10-yr, and 5-yr - as defined in the matr[] array. If the forward curve cannot be calibrated to the given market rates an error message is posted to the file. Skipped values IO.YC[0], IO.YC[6] and IO.YC[12] are reconstructed as IO.YC[0] =1.9000, IO.YC[6]=4.6512 and IO.YC[12]=6.2529.
Constructing the lattice
This example demonstrates lattice calibration and construction. Note that we do not allocate memory for IO.paths pointer because we do not plan to generate paths for this application. We show how to form a swap curve and a swaption volatility matrix and set up calibration flags.
void main()
{
callLattice(HullWhite);
}
void callLattice(enum irprocess irproc)
/* Sample calling program for adco_getbklattice */
{
struct IRIOStruct IO;
int rtnval,i,j,k;
int iRate;
FILE *outfile;
clock_t clockstart;
double timepassed;
adco_constructIO(&IO); /* first key call */
/* Set up the rate environment */
IO.svol = 0.25;
IO.mrev = 0.03;
IO.numpaths = 1;
IO.process = irproc; /* assign rate model – not necessary for the forward curve computation */
/* defining the long-rate maturities */
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* ignore this maturity */
IO.flagVTS = 1; /* let use time-dependent short-rate volatility */
IO.datafile_path = malloc(20*sizeof(char));
IO.datafile_path[0] = '\0';
strcat(IO.datafile_path, DATAFILEPATH); /* DATAFILEPATH can refer to
"c:\\adco\\ppmodel" or another
directory where the license key file
is located */
if(rtnval != 0)
{
fprintf(outfile, "Uh-oh, rtnval = %d\n", rtnval);
goto endlabel; /* to free pointers and close outfile */
}
/* the rest is just formatting the output to test it */
fprintf(outfile, "TIMES = %d LEVELS = %d \n", TIMES, LEVELS);
fprintf(outfile, "%f sec\n", timepassed);
/* printing out the short-rate lattice */
fprintf(outfile, "\nShort Lattice... \n");
fprintf(outfile," <-- MONTH -->\n");
/* printing out lattice for the 1-st long maturity */
if (matr[1]>0){
fprintf(outfile, "\n%u-mo Lattice... \n",IO.matr[1]);
fprintf(outfile," <-- MONTH -->\n");
In this program, we request constructing a Hull-White lattice calibrated to the given swaption volatility matrix (12 options). The model is going to build a time dependent volatility. Note that this calibration work is stored in the IO.svolVTS[] short-rate volatility array, the mean reversion constant, IO.mrev, and the "equivalent" volatility constant, IO.svol (which, for this time-dependent volatility set-up, will serve for information purposes only). Each element of IO.svolVTS[] denotes the absolute "local" volatility (recall we use the Hull-White model) applied for the lattices' time successive time steps.
We could also check how the input and output absolute volatilities compare to each other. In order to do so, we could match the input relative volatility, IO.SwaptionVol[i].r[j], multiplied by the corresponding swap forward rate, IO.forward[tnodes[IO.YCnodes[i]]+1].r[j], with the output (calibrated) number found in IO.FittedVol[IO.YCnodes[i]].r[j]. Recall that the time index is measured in yield curve nodes for SwaptionVol[] array, and in lattice nodes for FittedVol[] and tnodes[] arrays. Given the yield curve point i we could recover the matching lattice node as IO.YCnodes[i].
The lattices built for the short rate, IO.latticeR[i][j].r[0], and two long rates, IO.latticeR[i][j].r[1] and IO.latticeR[i][j].r[2], are printed to the output file. So is the 3-dimensional array of probabilities, IO.TProb[i][k][j]. Because we have not defined a positive value for maturity matr[3], lattice IO.latticeR[i][j].r[3] contains garbage and is not printed out.
Running random paths and testing simple bonds values
This example combines the path generation and a very simple test for bond pricing. In order to measure random pricing errors, we stratify paths using different seeds. Each stratum consists of 100 quasi-random paths; standard error is calculated using 100 strata. Since one does not need random sampling to value option-free bonds, this example is given solely to demonstrate programming methods, the call sequence, and properties of generated paths.
/* defining three long maturities */
IO.matr[1]=300;
IO.matr[2]=330;
IO.matr[3]=360;
/* initializing market vols */
for (iRate=1;iRate<=3;iRate++)
for (j=0;j<YCPOINTS;j++)
IO.SwaptionVol[j].r[iRate]=0;
IO.fitMarketVol=0; /* do not calibrate volatility */
IO.fitMarketmrev=0; /* do not calibrate mean reversion */
IO.flagVTS=0; /* use constant-volatility model */
for (iRate=1; iRate<=LONGRATES; iRate++){
avgBond[iRate]=0.0;
varBond[iRate]=0.0;
}
for (itest=1; itest<=STRATA; itest++){
for (iRate=1; iRate<=LONGRATES; iRate++){
SumBond[iRate]=0.0;
}
/* 100 paths will be generated with the current seed, IO.seed (changes after each call) */
adco_getpaths2(&IO); /* third key call */
/* if paths are fudged, all bonds should be valued exactly with any number of paths */ adco_fudgepaths(&IO); /* fourth key call */
/* coupon bond pricing test */
for (iRate=1; iRate<=LONGRATES; iRate++)
if (IO.matr[iRate]>0) {
SemiCoupon=IO.paths[0][0].r[iRate]/2.0; /* take the coupon rate from the lattice root */
for (j=0; j<numpaths; j++) { /* simple static bond pricing */
Bond=100.0;
for (i=IO.matr[iRate]-1; i>=0; i--) {
if ((i+1)%6==0) Bond+=SemiCoupon;
Bond=Bond/(1+IO.paths[j][i].r[0]/1200);
}
SumBond[iRate]+=Bond/numpaths;/* price for one stratum */
}
/* pricing average and variance */
avgBond[iRate]+=SumBond[iRate]/STRATA; /* pricing average */
varBond[iRate]+=SumBond[iRate]*SumBond[iRate]/STRATA;
} /* if a valid rate */
} /* for itest */
for (iRate=1; iRate<=LONGRATES; iRate++){
/* computing pricing RMSE */
Because we elected to fudge short rate paths, all bonds should be valued exactly. Have we commented the “fudging call” line, we would have seen the RMSE error, larger for longer bonds. The accuracy improves with square-root of the total number of paths, which is STRATA times numpaths. We stratified paths for two reasons. First we want to assess valuation accuracy and that is done by measuring standard deviation of prices obtained for different strata (this measure is then divided by square-root of STRATA). Second, we generated only numpaths of random rate paths per stratum (100 in this example) and allocated memory only for this small set of paths. The running averages and variances of bond’s values are updated and we discard the rate paths. For the next stratum, we generate the same number of different paths and re-use the same block of memory. Hence, we can run virtually unlimited number of total paths, without clogging the memory.
If we decided to use the TFG model instead of a single-factor model, we would simply assign values to correlation structure IO->ir2f->corr – see the exposed function group 12 above.
API INTEGRATION CODING FOR DEVELOPERS
Interest Rate Process version 4.5
Summary of Generation 4 Interest Rate Model Library
Main Data Structures, Header Files, Inputs, Outputs
Exposed Most Commonly Used Functions
Exposed Utility Functions
Derivatives Suite
Call Samples and Comments
Summary of Generation 4 Interest Rate Model Library
IR Library 4 includes four term structure models:
· Three single-factor short-rate models: the Black-Karasinski (BK) model (lognormal), the Squared Gaussian (SqG) model (normal process squared), and the Hull-White (HW) model (normal). One can fit any of the models to the observed at-the-money (ATM) volatility term structure for swaptions rather accurately and very quickly. Single-factor models operate on a penthanomial lattice.
· A two-factor, steady-correlation, short-rate Gaussian (TFG) model (normal). Much like in all single-factor cases, this model can be quickly calibrated to a set of ATM swaptions, but it also accepts two inter-rate correlation inputs. Once they are made, the model will keep inter-rate correlation structure steady in forward time. For example, a user or developer can define correlation between the 2-yr swap rate and the short rate as 80% and correlation between the 10-yr swap rate and the short rate as 50%. Unlike single-factor models, the two-factor model operates uses very accurate analytical approximations, without a lattice.
Each of the models can operate with time-dependent volatility as well as with a constant one. Calibration to the ATM swaption surface is done “on average” unless the number of swaptions matches the number of internal parameters of the model, in which case it is meant to be exact.
Quick calibration to swaps (or option-free bonds) and swaptions and (for the two-factor case) correlation structure was made possible due to various exact or very accurate analytical approximations. They facilitate no-arbitrage in rates and an accurate prediction of swaption values. Examples include a special analytic for convexity adjustment, assessment of “Black-Scholes volatility compression” effect that deflates options in non-linear short-rate models, approximations for the long rates and their volatility, etc. No actual instruments, swaps or swaptions, are priced on a lattice during calibration and lattice construction.
The relevant set of long rates requested by the user is produced on the lattice and agrees with the underlying short rate process. For the two-factor case, they are stored in the form of analytical relationships.
Generation 4 IR Library enforces several solid forward sampling techniques over the rate lattice (single-factor cases) or using analytical relationships (the two-factor case). Users are recommended to employ our enhanced sampling method (a "Quasi Monte-Carlo"), which pre-processes the "random" shocks before they are actually used to generate paths. This pre-processing, called ortho-normalization, ensures the shocks are scaled to the proper volatility and independent of each other. The other useful and recommended technique is short-rate “fudging”, i.e. adding an artificial, time-variant, adjustment to the short-rate paths that ensure exact valuation of option-free instruments, even with a limited path sample.
Before taking full the advantage of the new IR Library 4 consider the following facts:
· Normal models are proven to better reflect both historical data and the implied volatility skew (i.e. the market perception). The SqG model can also be viewed as a reasonable choice that precludes negative rates.
· The time-dependent volatility version, for either model, better replicates the observed swaption market.
· Negative rates are not precluded if one employs the HW model or the TFG model. Although it will not result in a sizable mispricing, the user must ensure negative rates are formally legitimate (cause no computational troubles) for any non-AD&Co application or function called.
· Enhanced sampling technique is 2 - 2.5 times more accurate than the regular sampling, for the same number of paths, but starts with pre-processing.
· The fudging option has virtually no drawbacks.
· The durations of most mortgage instruments are going to be shorter under HW than under BK and may better match the trading practice (the SqG case will be in between.) Mortgage derivatives, IOs, POs, and MSRs may have stronger or weaker rate sensitivity - depending on their Vega.
· The two-factor model carries a minimal valuation effect for pass-throughs and ARMs. CMO tranches that are much shorter or much longer than the collateral will be affected when switching to the two-factor model.
· Generation 4 Library has a somewhat different API. The changes were motivated by the need to have a multi-threaded system that can be used on Internet/Intranet servers. Some of the arguments passed to functions in generation 3 are now embedded in the main data structure IRIOStructure (see the next section). Most of the typical calls accept only one argument – a pointer to this structure.
· The library is normally compiled for a “sparse” rate grid that is fast and fair for MBS applications, but can be recompiled for a monthly or any other user-desired grid. A monthly lattice is necessary for derivative pricing.
The table below summarizes changes and enhancements.
Generation 3
Generation 4
Models covered
3 single-factor models: BK, SqG, and HW
Same + Two-factor Gaussian model (TFG)
Rate inputs
14 points used or skipped
Same
Arbitrage freeness on the lattice
Exact on the benchmark points; perfectly accurate for HW, very accurate for SqG, fairly accurate for BK between them
Same + perfectly accurate for TFG
Interpolation
Spline interpolation of the input curve
Same
Volatility parameter
Constant or time-dependent
Constant or time-dependent
Calibration to swaption volatilities
Fast calibration to an ATM swaption vol matrix - up to 3*) user-defined swap maturities and many expirations. Automatically finds the best volatility in the model, either constant or time-dependent, and mean reversion parameter
Same; calibration of the TFG model involves more parameters and, hence, somewhat more accurate
Calibration to correlation(s)
N/A
Accepts 2 correlations between the short-rate and two user-defined long rates
Long rates generated
Three at a time*)
Same
Lattice technology
Enhanced to fit theoretical standards for arbitrage-freeness and the future rate statistics
Same for the single-factor models; lattice is not used for the two-factor model
Forward sampling technique
An enhanced sampling method over lattice with ortho-normalized shocks. Option to “fudge” short-rate paths
Same applies except the sampling is continuous, lattice-free, for the TFG.
Multi-threaded
No
Yes
*) at a special user's request, AD&CO can expand the long-rate maturity set
*) at a special user's request, AD&CO can expand the long-rate maturity set
Most commonly, the library file is named adco_ir, but it can be named with modifications reflecting compiler options (adco_ir_mo for monthly lattice, for example). A companion library, bondwrap, can be used for integration with a client’s derivative systems.
Main Data Structures, Header Files, Inputs, Outputs
ADIRdefs.h header file
Here is the full text of the ADIRdefs.h header file that contains most of the data types, globals, and exposed functions needed:
#if defined(WIN32)
#define WINEXP __stdcall
#else
#define WINEXP
#endif
#define
MPL
372
/* max path length in months */
#define
MAXMONTHS
372
/* same as MPL */
#define
YCPOINTS
14
/* potential benchmarks
1,3,6,12,24,36,48,60,84,120,180,240,300,360 */
#define
LONGRATES
3
/* total number of long rates constructed */
#define
LEVELS
41
/* number of "space" points on lattice */
#define
TIMES
45
/* number of lattice time steps */
#define
BRANCHES
5
/* per lattice point */
#define
CENTER
21
/* index for the root node */
#define
MAXBRIDGELEG
12
/* max month distance between time nodes */
#define
MISSING
-98.0
/* for missing rates in the benchmark set */
#define
RANDOM
adco_random
/* adco_random or C_random */
#define
intpdYCPOINTS
22
/* number of interpolated yield-curve points */
#ifndef MAKE_ADIR_ARRAYS
extern ADCO_IR_IMPEXP const int tnodes[TIMES+1];
extern ADCO_IR_IMPEXP const int realtreas[YCPOINTS]; /* maturities in months potential benchmarks */
extern ADCO_IR_IMPEXP const int intpdtreas[intpdYCPOINTS];
#else /* MAKE_ADIR defined */
/* months covered by lattice (TIMES=45) */
ADCO_IR_IMPEXP const int tnodes[TIMES+1]= {0,2,5,11,17,23,29,35,47,59,71,83,95,107,119,131,143,155,167,179,191,203,215,227,
239,251,263,275,287,299,311,323,335,347,359,371,383,395,407,419,431,443,455,467,479,491};
/* months covered by lattice (TIMES=45). Each node precedes
potential bond's or swap's maturity or coupon anniversary by 1 month */
ADCO_IR_IMPEXP const int realtreas[YCPOINTS]={1,3,6,12,24,36,48,60,84,120,180,240,300,360}; /* maturities in months potential benchmarks */
ADCO_IR_IMPEXP const int intpdtreas[intpdYCPOINTS]= {1,3,6,12,18,24,30,36,48,60,72,84,96,108,120,144,180,240,300,360,420,480};
/* maturities in months for potential benchmarks to select from */
static int realtreas[YCPOINTS]={1,3,6,12,24,36,48,60,84,120,180,240,300,360};
/* maturities for spline interpolation */
enum irprocess {
BlackKarasinski,
HullWhite,
SquaredGaussian,
TwoFactorGaussian
};
enum enumYC {
couponYC,
zeroYC
};
typedef struct _PATHS {
double r[LONGRATES+1]; /* contains all the rates, r[0] = 1-mo rate */
} PATHS;
/* the two-factor data structure */
typedef struct IR2Faddin {
PATHS corr;
double mrev[3];
double beta;
double ro;
double sigma[2];
double sigmaR;
double sigmaV;
double sigmaVnew[3];
double b1[3];
double b2[3];
double bR[3];
double bV[3];
double bSum[3];
double BT1[LONGRATES+1][YCPOINTS+1];
double BT2[LONGRATES+1][YCPOINTS+1];
PATHS afforward[MAXMONTHS];
double Scaler[YCPOINTS+1];
} IR2Faddin;
/* the main data structure */
typedef struct IRIOStruct {
double YC[YCPOINTS]; /* input yield curve */
enum enumYC flagYC; /* supplied by user: zero or coupon */
double svolVTS[TIMES+1];/* short-rate vol term structure; */
/* will be filled in IRCode if fitMarketVol==1 */
int YCnodes[YCPOINTS]; /* lattice nodes numbers matching the benchmark maturities; */
/* will be filled in IRCode to represent node numbers matching YC points */
PATHS FittedVTS[TIMES+1];/* calibrated Black-equivalent swaption volatility matrix; */
/* will be filled in IRCode if fitMarketVol==1 */
PATHS forward[MAXMONTHS];/* the interpolated forward curve; */
/* will be filled in IRCode */
int flagVTS; /* flag whether to have a constant vol (0) or VTS (1); */
/* supplied by user */
int fitMarketVol; /* flag whether to calibrate vol (1) or use the user-provided input (0); */
/* supplied by user */
int fitMarketmrev; /* flag whether to calibrate mean reversion (1) or
use the user-provided input (0); */
/* supplied by user */
double VolMSE; /* accuracy of volatility matrix calibration; */
/* will be filled in IRCode if fitMarketVol==1 */
PATHS SwaptionVol[YCPOINTS];/*swaption vol matrix in the Black quotation; */
/* supplied by user */
double svol; /* short-rate volatility; user-supplied or calibrated */
double mrev; /* mean reversion; user-supplied or calibrated */
int numpaths; /* user-supplied number of random or quasi-random paths */
long seed; /* use -1 for the first use */
int quasi_random_nodes; /* user-supplied number of nodes when quasi-random sampling applies */
int matr[LONGRATES+1]; /* user-defined long-rate maturities to use */
int constructrate[LONGRATES+1]; /* added to 4.5; use 0 if don't want long rates to be constructed */
PATHS **paths; /* container of random paths; filled by adco_getpaths or adco_getpaths2 */
/* added for version 4 */
enum irprocess process; /* Term structure model selector; user-supplied */
char *datafile_path; /* Where to look for the license key; user-defined */
PATHS **latticeR; /* Rate lattice with up to 4 rates; computed */
double **latticeX; /* Gaussian deviate on lattice; computed */
double ***TProb; /* Transitional probability matrix; computed */
int **Offset; /* Upper ascending level for each node; calculated */
double rvec[TIMES+1]; /* Lattice calibrating vector; computed, retained as a handler */
double rvec_static[TIMES+1];/* Static rate calibrating vector; computed, retained as a handler */
double CvxAdjustment[TIMES+1]; /* Convexity adjustment vector measured at nodes;
Computed for internal calibration */
double FDstep; /* for finite-difference grid; currently not used */
double sfrwd[492]; /* short forward rate extrapolated to the end of grid */
IR2Faddin *ir2f; /* added to 4.4 for two-factor specifics */
} IRIOStructure;
typedef enum IRPerror
{
ADCO_IR_OK,
MATH_ERROR,
IRP_LICENSE_EXPIRED,
LICENSE_KEY_NOT_FOUND,
LICENSE_KEY_ERROR,
INVALID_IRP_INPUT,
CCY_DATAFILE_ERROR,
CCY_ILLEGAL_DATE,
CANT_FIT_IR_CURVE,
CANT_FIT_VOL_CURVE,
MISSING_VOL_CURVE,
SPREAD_DONT_CONVERGE,
CANT_FIT_2FACTOR_MODEL,
INVALID_ENTRY_2FACTOR_MODEL
} IR_Error;
/* exposed functions
WINEXP IR_Error STDCALL adco_getpaths(IRIOStructure *IO);
WINEXP IR_Error STDCALL adco_getpaths2(IRIOStructure *IO);
WINEXP IR_Error STDCALL adco_fudgepaths(IRIOStructure *IO);
WINEXP IR_Error STDCALL adco_getforward(IRIOStructure *IO);
WINEXP IR_Error STDCALL adco_getshockedforward(IRIOStructure *IO,
int minLevel, int maxLevel,
PATHS **shockedforward);
WINEXP IR_Error STDCALL adco_getbklattice(IRIOStructure *IO);
WINEXP void STDCALL adco_constructIO(IRIOStructure *IO);
WINEXP void STDCALL adco_destroyIO(IRIOStructure *IO);
WINEXP double STDCALL adco_random(long * idum);
WINEXP double STDCALL C_random(long * idum);
WINEXP void STDCALL adco_version(char * VS);
WINEXP void STDCALL adco_getCouponCurve(double CouponCurve[YCPOINTS], IRIOStructure *IO);
WINEXP void STDCALL adco_get_cumProbability(IRIOStructure *IO, double CProb[LEVELS+1][TIMES+1]);
WINEXP void STDCALL adco_getlevelpathset(IRIOStructure *IO);
WINEXP void STDCALL adco_forward_coupon(int n, int ntsteps, double longrate[LEVELS+1][TIMES+1],
IRIOStructure *IO);
WINEXP void STDCALL adco_forward_zero(int n, int ntsteps, double longrate[LEVELS+1][TIMES+1],
IRIOStructure *IO);
WINEXP void STDCALL adco_forward_coupon_static(int matr, IRIOStructure *IO, double longrate[TIMES+1],
int ntsteps);
WINEXP void STDCALL adco_forward_zero_static(int matr, IRIOStructure *IO, double longrate[TIMES+1],
int ntsteps);
WINEXP double STDCALL adco_forward_annuity_static(int matr, IRIOStructure *IO, int ntsteps, int freq);
enum CurveType
{
treasury,
swap
};
typedef enum _MtgCFType
{
CF_PI,
CF_PO,
CF_IO,
CF_SVC,
CF_MSR
} MtgCFType;
WINEXP IR_Error STDCALL getLatticeBondPrices(int Months2Settle, int Days2Settle, int NetDelay,
double **Coupon, double **Bal,IRIOStructure *IO,double **accrued,
double *CallPrice, double *PutPrice, int NumMonths, double OAS,
double PVvec[LEVELS+1]);
WINEXP IR_Error STDCALL getLatticeBondOAS(int Months2Settle, int Days2Settle, int NetDelay,
double **Coupon, double **Bal,IRIOStructure *IO,double **accrued,
double *CallPrice,double *PutPrice, int NumMonths,double basePrice,
double *pOAS,double PVvec[LEVELS+1]);
WINEXP IR_Error STDCALL getCFPrice(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, PATHS *path, double *sfrwd,
int NumMonths, double OAS, double Accrued,
double *PV, double *FP);
WINEXP IR_Error STDCALL getCFPrice_OASCorrection(int Market2PayDays, int Days2Settle, int NetDelay,
double *pPVofCF,int NumMonths,
double OAS0, double OAS, double Accrued, double *sfrwd,
double *pPV, double *pFP, double *pMDur, double *rate_eq);
WINEXP IR_Error STDCALL getCFOAS(int Market2PayDays, int Days2Settle, int NetDelay,
double *pPVofCF, int NumMonths, double basePrice,
double OAS0, double *pOAS, double Accrued, double *sfrwd,
double *pPV, double *pFP, double *rate_eq);
WINEXP IR_Error STDCALL getMEY2Px(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, double yield, double *Price, int NumMonths, double Accrued);
WINEXP IR_Error STDCALL getMEY2Pv(int Market2PayDays,
double *pCF, double yield, double *Pv, int NumMonths);
WINEXP IR_Error STDCALL getPx2MEY(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, int NumMonths, double basePrice,
double *pYield, double Yield0, double Accrued);
WINEXP IR_Error STDCALL getPv2MEY(int Market2PayDays,
double *pCF, int NumMonths, double basePV,
double *pYield, double Yield0);
WINEXP void STDCALL adco_forecast_index(PATHS *ref_path, PATHS *ref_forward,
enum CurveType ref_curve_type, double *index_forward,
enum CurveType index_curve_type, int index_maturity,
enum irprocess process, double *index_vec, int Numperiods);
WINEXP double STDCALL ConvertLN2SqGVol(double F, double BSVol, double Expiry);
WINEXP double STDCALL ConvertLN2NVol(double F, double BSVol, double Expiry);
WINEXP double STDCALL ConvertN2SqGVol(double F, double NVol, double Expiry);
WINEXP double STDCALL ConvertN2LNVol(double F, double NVol, double Expiry);
WINEXP double STDCALL ConvertSqG2NVol(double F, double SqGVol, double Expiry);
WINEXP double STDCALL ConvertSqG2LNVol(double F, double SqGVol, double Expiry);
Mandatory user-supplied input fields
A user must provide data via filling needed elements ("fields") of the IRIOStructure structure. Do not leave them unassigned. One may call adco_setIOdefault() function first to initialize the IRIOStructure with default values.
1. double YC[YCPOINTS]; /* input yield curve supplied by the user
User supplies up to 14 (generally, YCPOINTS) yield-curve rates that are aligned to and numbered in realtreas[] array. The system does not know whether the Treasuries, agencies, corporates, swaps, or LIBOR accrual and compounding convention applies; all the rates are assumed to be given in the BEY 30/360, percentage form. It is therefore up the user to make all needed conversions before the library is used. If some points are unavailable, set them to MISSING, not zero. Only actual rate inputs will be used by the library to construct the forward curve and calibrate the lattice. Negative rates are accepted for the HW or the TFG models, but not for SqG model or BK model. Example:
IRIOStructure IO;
IO.YC[0] = MISSING;
/* 1-mo skipped */
IO.YC[1] = 1.9;
/* 3-mo */
IO.YC[2] = 2.06;
/* 6-mo */
IO.YC[3] = 2.5675;
/* 12-mo */
IO.YC[4] = 3.671;
/* 24-mo */
IO.YC[5] = 4.295;
/* 36-mo */
IO.YC[6] = MISSING;
/* 48-mo skipped */
IO.YC[7] = 4.998;
/* 60-mo */
IO.YC[8] = 5.4129;
/* 84-mo */
IO.YC[9] = 5.7745;
/* 120-mo */
IO.YC[10] = 6.078;
/* 180-mo */
IO.YC[11] = 6.1979;
/* 240-mo */
IO.YC[12] = MISSING;
/* 300-mo skipped */
IO.YC[13] = 6.2430;
/* 360-mo */
The library will build forward curve using (A) input coupon or zero rates, and (B) spline-interpolated additional rates. In addition, the skipped points are reconstructed and filled. Thus, points IO.YC[0], IO.YC[6] and IO.YC[12] will be filled when library is used, in the example above.
2. enum enumYC flagYC; /* supplied by user: zero or coupon */
This flag interprets the input yield curve as a zero-coupon curve (if flagYC is set to zeroYC) or a coupon curve (if flagYC is set to couponYC).
3. int flagVTS; /* flag whether to have a constant volatility (value
0) or VTS (value 1); supplied by user */
Setting flagVTS to 0 tells the library to use a constant-volatility model, setting it to 1 enables a time-dependent volatility term structure (VTS).
4. int fitMarketVol; /* flag whether to calibrate volatility (value 1) or use
the user-provided input (value 0); supplied by user */
int fitMarketmrev; /* flag whether to calibrate mean reversion (value 1) or the user-provided input (value 0); supplied by user */
These two fields tell the library to employ the fast volatility calibrator (constant or time-dependent) to approximate given swaption matrix. If the value of fitMarketVol is set to 1, volatility will be calibrated; the mean reversion can be either calibrated as well (fitMarketmrev set to 1) or left unchanged (fitMarketmrev set to 0). If the value of fitMarketVol is set to 0, then calibration will not be performed, and the value of fitMarketmrev is going to be disregarded. In other words, the system does not calibrate mean reversion without calibrating volatility.
5. int matr[LONGRATES+1]; /* user-defined long-rate maturities to use; numbered
1 to LONGRATES */
int constructrate[LONGRATES+1]; /* added to 4.5; use 0 if don't want long rates to
be constructed */
The matr[] array determines long rate maturities (up to 3) that the library will work with. The forward curve, the random paths will be constructed for these long rates (if the matching element of constructrate[] array is set to 1), assuming the BEY 30/360 form. The same set of rates is used to assign the swaption volatility matrix (see below). Example:
IRIOStructure IO;
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* undefined; will be disregarded */
The system regards only positive maturity inputs. Element IO.matr[0] is always disregarded since the zero-index is reserved for the short (one-month) rate.
The constructrate[] array tells the library to actually compute lattice of long rates (value of 1) or void this operation (value of 0). In any case, swaption volatility matrix (see the next item) will be associated with tenors defined by matr[] array. The use of constructrate[] array becomes apparent when we work with derivatives typically valued on the monthly lattice with many nodes. Only the short-rate lattice is needed to price swaptions, for example, regardless of the exercise style. Hence, computation of a long rate lattice is redundant, but can be time-consuming.
6. PATHS SwaptionVol[YCPOINTS]; /* swaption volatility matrix in the Black quotation;
supplied by user */
This is an ATM swaption volatility matrix that can be used by the calibration mechanism. This matrix is a YCPOINTS by LONGRATES table with possible expiration set taking from realtreas[] array and possible swap maturities defined by the matr[] array. Regardless of the rate model, volatilities are always quoted in the Black'76, i.e. relative percentage, form. If any of matr[] elements is set to a zero or a negative number, corresponding swaptions will be ignored. Example:
IRIOStructure IO;
int iRate;
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* undefined swap will be disregarded */
for (iRate=1;iRate<=LONRATES;iRate++)
for (j=0;j<YCPOINTS;j++)
IO.SwaptionVol[j].r[iRate]=0; /* initializing to zero */
IO.SwaptionVol[3].r[1]=25.8; /* 1-yr expiry on 2-yr swap */
IO.SwaptionVol[4].r[1]=22.4; /* 2-yr expiry */
IO.SwaptionVol[5].r[1]=20.8; /* 3-yr expiry */
IO.SwaptionVol[7].r[1]=18.6; /* 5-yr expiry */
IO.SwaptionVol[8].r[1]=17.1; /* 7-yr expiry */
IO.SwaptionVol[9].r[1]=14.7; /* 10-yr expiry */
IO.SwaptionVol[3].r[2]=20.3; /* 1-yr expiry on 10-yr swap */
IO.SwaptionVol[4].r[2]=19.2; /* 2-yr expiry */
IO.SwaptionVol[5].r[2]=18.4; /* 3-yr expiry */
IO.SwaptionVol[7].r[2]=16.4; /* 5-yr expiry */
IO.SwaptionVol[8].r[2]=15.0; /* 7-yr expiry */
IO.SwaptionVol[9].r[2]=13.0; /* 10-yr expiry */
IO.SwaptionVol[3].r[3]=20.0; /* 1-yr expiry on an undefined swap */
IO.SwaptionVol[9].r[3]=15.0; /* 10-yr expiry on an undefined swap */
In this example, volatility calibrator can use 12 options: 6 options on the 2-year swap and 6 options on the 10-year swap. The third maturity in matr[]array has not been set to a positive number. Therefore, options on this undefined swap will be ignored - even if the volatility inputs were made.
Note that the above input does not start until the 1-year and does not extend beyond the 10-year expiry. The program will extrapolate the "local" volatility for the skipped expirations and remains fully functional - subject to the limited volatility input.
Since the element r[0] of the PATHS structure is reserved for the short rate, elements SwaptionVol[].r[0] are ignored by the calibrator.
7. int numpaths; /* user-supplied number of random or quasi-random
paths */
This is a self-explanatory input that can be omitted only if the user does not plan to generate interest rate paths.
8. long seed; /* use -1 for the first use; will change as random
numbers get generated */
The seed is used to initialize the ADCO's random number generator. A negative 1 should be always used for the first time. After each random call, seed changes and can be used for the next call. Can be omitted only if the user does not plan to generate interest rate paths.
9. int quasi_random_nodes; /* user-supplied number of nodes when quasi-random
sampling applies */
If the user plans to take advantage of the enhanced forward sampling technique ("Quasi Monte-Carlo"), he should input the number of lattice steps, for which this method applies. Using the following assignment,
quasi_random_nodes = TIMES;
guarantees that the quasi Monte-Carlo will apply for the entire possible length of the lattice (492 months). Since the library will never generate random rate paths for more than 372 months (MPL macro defined), setting quasi_random_nodes = 35 yields the same goal. Assigning a smaller number such as quasi_random_nodes = 14 will let the system to use this method for the first 14 time steps (120 months), then switch to a regular sampling. Finally, if quasi_random_nodes = 0, the regular Monte-Carlo sampling will be used (antithetic reflection is provided in either case).
10. enum irprocess process; /* Term structure model selector; user-supplied */
This field tells the library which interest rate model to use.
11. char *datafile_path; /* Where to look for the license key; user-defined */
The full path to the license key file, adco_lic.key.
12. IO.ir2f->corr; /* correlation matrix for the two-factor model */
This is the only element of the ir2f structure that is used as input. The structure is of the PATHS type meaning its elements
are ordered by long rates. For example, element IO.ir2f->corr[1] denotes correlation between the short rate and the long rate defined in matr[1] and so on. Element [0] is not used. See a common code below:
if (irproc == TwoFactorGaussian){
IO->ir2f->corr.r[1] = 0.7;
IO->ir2f->corr.r[2] = 0.4;
IO->ir2f->corr.r[3] = MISSING;
}
Only two correlations matter. If all three are assigned, only elements [1] and [2] will be used. If the total number of assigned correlations is less than 2, an error will be returned as the two-factor model cannot be built.
Dual-use fields
The following fields can be used for both inputs and outputs; some of them become mandatory inputs if logical flags suggest so.
13. double svol; /* short-rate volatility; user-supplied or calibrated
double mrev; mean reversion; user-supplied or calibrated */
These are the short-rate volatility and the mean reversion constants (for singe-factor models). The svol constant is quoted as relative percentage, for the BK model, and as absolute percentage, for the HW model. For the SqG model, the short-rate volatility constant is derived as one-half geometric average of the absolute volatility and the relative volatility. Examples:
svol = 0.25; /* 25% of short-rate volatility */
or
svol = 1.0; /* 1.0% (100 basis points) of absolute volatility */
or
svol = 0.274; /* for SqG model */
The mean reversion constant, mrev, is determined as a decimal, i.e. 0.03 stands for 3%. Both constants, svol and mrev, can be calibrated to a swaption volatility matrix or used as inputs. The short-rate volatility constant svol is not utilized unless flagVTS is set to 0.
14. double svolVTS[TIMES+1]; /* short-rate volatility term structure; used only if
flagVTS=1 will be filled in the library if
fitMarketVol=1 or used as input */
This is a short-rate volatility array, each element of which applies to certain forward time span. If flagVTS = 1, the library will employ a variable-volatility term structure model. Further, if fitMarketVol = 0, then array svolVTS[] defines the short-rate volatility term structure, i.e. it becomes an input array. In this case, the user is responsible for providing the entire array in the model-relevant form; the program will not perform "hole filling" nor quote conversion. If fitMarketVol = 1, this array will be best tuned to match the swaption volatility matrix, SwaptionVol[]. In the latter case, it becomes an output; it is not necessary to initialize it before using the library for it is not used to start the calibration process. Even if the SwaptionVol[] matrix is not completely known, the calibrator will extrapolate or interpolate values.
Note that svolVTS[] array is aligned to the lattice nodes, not to the yield curve points. Element svolVTS[0] denotes volatility between now (month 0) and forward month 2, svolVTS[1] is volatility between forward months 3 and 5, etc.
Library outputs
15. int YCnodes[YCPOINTS]; /* lattice nodes numbers matching the benchmark maturities;
will be filled in the library to represent node numbers
of YC points */
This array "maps" the yield curve points into the lattice. For example, YCnodes[9] is equal to 14 meaning that the 120-month point of the yield curve is aligned to the time node 14 of the lattice. This is a "utility" array that helps compare output volatility arrays (see next) with the given swaption matrix.
16. PATHS FittedVTS[TIMES+1]; /* calibrated Black-equivalent swaption volatility
matrix; will be filled in the library if
fitMarketVol=1 */
This is the calibrated volatility matrix; it is computed by the library once volatility calibration option is selected (i.e. fitMarketVol is set to 1). For example, element FittedVTS[6].r[2] denotes the swaption's implied volatility for the expiration node 6 (looking up the tnodes[] array we find it is the 2-year point) for the underlying swap defined by matr[2]. No calibrated volatility array is computed for an undefined maturity.
Volatility quotations used for this vector are model-dependent: relative for the BK model, absolute for the HW and the TFG models, derived for the SqG model (see quotations for svol above). Note that the expiration nodes are now aligned to the lattice, not to the yield curve points. Use the YCnodes[] utility array to compare the calibrated volatility matrix with the input points, the SwaptionVol[] array.
17. PATHS forward[MAXMONTHS]; /* the interpolated forward curve;
will be filled in the library */
This is the set of forward rates between today and month 372 computed by the program. The short (1-month) forward rates are bound to the spline-interpolated yield curve points. Between them, the forward rates are piece-wise linearly interpolated. From this description, one can conclude that (A) the forward curve is continuous, and (B) it can be made very smooth if one employs many spline nodes.
The short forward rates are all in the MEY 30/360 form. The PATHS component r[0] is reserved for the short rate. For example, forward[19].r[0] is the 1-month rate, 19 month forward; forward[0].r[0] is today's 1-month rate.
Long coupon-bond (or swap) forward rates are computed exactly for forward points listed in the tnodes[] vector and interpolated between them. For instance, forward[19].r[2] is the long rate 19 months forward, for a coupon-bond or a swap maturity defined by matr[2]. All the long rates are in the BEY, 30/360 form. No forward curve is computed for an undefined maturity.
17. double VolMSE; /* accuracy of volatility matrix calibration;
returned by the system will be filled in the library
if fitMarketVol=1 */
This is the accuracy (as a root-mean-squared error measure across swaption volatility matrix) of the volatility calibration, stated in the model-relevant form (relative percentage for BK, absolute percentage for HW and TFG, derived for SqG).
18. PATHS **paths; /* container of random paths; filled by
adco_getpaths() or adco_getpaths2() */
The user is responsible to allocate a two-dimensional array (see Call samples, comments, and instructions later in this document) large enough to store interest rate paths. For example, element paths[125][25].r[0] contains the simulated 1-month rate (MEY 30/360), for run 125, future month 25. All the long rates are for coupon bonds' or swaps' maturities defined in the matr[] array (BEY 30/360). Any element paths[][0].r[] is a rate today.
19. IR2Faddin *ir2f;
This pointer points to the calibration results structure for the two-factor model that contains optimized volatility constants or functions, mean reversion constants, long-rate relationships functions to the model’s factors and some other analytical results. For example, array afforward[MAXMONTHS] contains the mean rates in forward time, double dimensional arrays BT1[LONGRATES+1][YCPOINTS+1] and BT2[LONGRATES+1][YCPOINTS+1] represent linearized relationships to model’s two state variable, x1 and x2. In most practical cases, the user does not need to access this information directly; it is utilized internally by the system.
Exposed Most Commonly Used Functions
Forward curve and lattice construction
1. IR_Error WINEXP adco_getforward(IRIOStructure *IO);
Computes the forward curve and places it into IO->forward[].
2. IR_Error WINEXP adco_getbklattice(IRIOStructure * IO);
Builds a lattice calibrated to the yield curve and swaptions (if requested by setting IO->fitMarketVol to 1). Aside from filling out IO fields such as IO->svolVTS[], IO->FittedVTS[], IO->forward[], the function builds the lattice (pointed by IO->latticeX and IO->latticeR)and its transitional probabilities (pointed by IO->TProb). Note that the forward curve construction via a call to adco_getforward() is already embedded in adco_getlattice().
Pointer IO->latticeR reserves a [LEVELS+1] by [TIMES+1] array of PATHS structures. For example, and IO->latticeR[3][23].r[2] is the long rate with maturity given by matr[2], on the 3-rd time step (it is 11 month forward - according to the tnodes[] array), and 23-rd rate level. Today's rates are stored in IO->latticeR[0][].r[]. Pointer IO->latticeX points to a similar lattice of a normal deviate (as doubles) that is mapped to the short rate.
Pointer IO->TProb reserves a 3-dimensional, [LEVELS+1] by [TIMES+1] by [BRANCHES+1], array of doubles. For example, IO->TProb[3][23][2] is the probability to move from the node located on the 3-rd time step of the lattice (11 month forward) and 23-rd rate level along the 2-nd branch (out of 5) evolving from this node. The nodes and branches are counted from the top to the bottom.
Note to users of the TFG model: a call to adco_getklattice is still necessary for rate, volatility and correlation calibration work although the model does not build a lattice.
3. void WINEXP adco_getCouponCurve(double CouponCurve[YCPOINTS]);
This function computes the 14-point coupon curve based on the information contained in the library. It should be called at any time after the forward curve is constructed. If IO.flagYC is set to couponYC, it makes no sense to separately call this function because the contents of CouponCurve[] array will be identical to that of IO.YC[] input array with all holes filled. It is only when IO.flagYC is set to zeroYC (zero-coupon rates are used as input), function adco_getCouponCurve() does a non-trivial job.
Rate paths generation
4. WINEXP IR_Error adco_getpaths(IRIOStructure *IO);
Builds a lattice calibrated to the yield curve and swaptions (if requested by setting IO->fitMarketVol to 1), generates IO->numpaths random paths pointed by the IO->paths pointer. This function implements an "old-style", version 2, call and is retained for compatibility.
5. WINEXP IR_Error adco_getpaths2(IRIOStructure *IO);
Generates IO->numpaths random paths pointed by the IO->paths pointer assuming the lattice has already been built by the adco_getbklattice() function (see function 2 above). Presents some advantage over adco_getpaths() when multiple calls to the path generator need to be made (perhaps, with different seeds), without any change to the lattice. Other than that, adco_getpaths() and adco_getpaths2() return the same paths.
6. WINEXP IR_Error STDCALL adco_fudgepaths(IRIOStructure *IO);
Adjusts previously generated short-rate paths to ensure exact valuation of option-free instruments – even for a limited path sample. This function should be called right after adco_getpaths or adco_getpaths2.
7. double WINEXP adco_random(long *seed);
This is a utility function generating random numbers. Always start with *seed filled with -1, then keep passing the currently updated seed number. The safest way to obtain a brand new sequence is to make a fair number of calls to adco_random(). The results of these calls can be used or trashed. Simply changing the seed without calling the function is not a good idea as the function actually contains some static variables that get updated on each call.
8. WINEXP void STDCALL adco_getlevelpathset(IRIOStructure *IO);
This function returns a set of deterministic interest rate paths that visit every node of the single-factor lattice. Call to this function is typically required if the instrument is path-independent and can be valued operating backwards on the lattice. As in the case of random sampling, this function stores all paths in the memory pointed by the IO->paths pointer. Not used for the TFG model.
Initializing, memory allocation
9. void WINEXP adco_constructIO(IRIOStructure *IO);
This function initializes the IO structure, i.e. serves as a C++ constructor. The exact contents is as follows:
WINEXP void STDCALL adco_constructIO(IRIOStructure *IO)
{
int i,j,iRate;
PATHS *latticeRData = (PATHS *) malloc(sizeof(PATHS)*(LEVELS+1)*(TIMES+1));
double *latticeXData = (double *) malloc(sizeof(double)*(LEVELS+1)*(TIMES+1));
int *OffsetData = (int *) malloc(sizeof(int)*(LEVELS+1)*(TIMES+1));
double *TProbData = (double *)
malloc(sizeof(double)*(LEVELS+1)*(TIMES+1)*(BRANCHES+1));
double **TProbData2 = (double **) malloc(sizeof(double *)*(LEVELS+1)*(TIMES+1));
for (i=0; i<YCPOINTS; i++){
IO->YC[i]=MISSING;
for (iRate=0; iRate<=LONGRATES; iRate++)
IO->SwaptionVol[i].r[iRate]=0.0;
}
for (iRate=0; iRate<=LONGRATES; iRate++) {
IO->matr[iRate]=0;
IO->constructrate[iRate]=1;
}
IO->matr[0]=1;
IO->datafile_path = DefaultDataPath;/*"c:\\adco\\ppmodel"; */
IO->fitMarketmrev = 0;
IO->fitMarketVol = 0;
IO->flagVTS = 0;
IO->flagYC = couponYC;
IO->mrev = 0;
IO->svol = 0;
IO->numpaths = 100;
IO->paths = NULL;
IO->quasi_random_nodes = TIMES;
IO->seed = -1;
for (i=0; i<=TIMES; i++)
IO->svolVTS[i]=0.0;
/* memory allocation for LatticeR and TProb pointers */
IO->latticeR = (PATHS **)malloc(sizeof(PATHS *)*(LEVELS+1));
IO->latticeX = (double **)malloc(sizeof(double *)*(LEVELS+1));
IO->Offset = (int **)malloc(sizeof(int *)*(LEVELS+1));
IO->TProb = (double ***)malloc(sizeof(double **)*(LEVELS+1));
for (i=0; i<(TIMES+1)*(LEVELS+1); i++)
TProbData2[i] = &TProbData[i*(BRANCHES+1)];
for (i=0; i<=LEVELS; i++)
IO->TProb[i] = &TProbData2[i*(TIMES+1)];
for (j=0; j<=LEVELS; j++){
IO->latticeR[j] = &latticeRData[j*(TIMES+1)];
IO->latticeX[j] = &latticeXData[j*(TIMES+1)];
IO->Offset[j] = &OffsetData[j*(TIMES+1)];
}
IO->ir2f = (IR2Faddin *)malloc(sizeof(IR2Faddin));
}
It is important to understand that the default values are not the "best" or "recommended" ones; the sole purpose of this function is to ensure no garbage is contained in IO and memory is allocated. For example, the input rates are all set to MISSING, swaption volatilities and long-rate maturities are all set to zeros. The user, therefore, must provide actual input values, but, once adco_constructIO() is called, it is not necessary to worry about skipped points or memory. Note that IO->paths is set to NULL meaning the caller himself should allocate the memory large enough to hold paths.
Since the function allocates a memory, it should not be called in succession, without calling adco_destroyIO() first (see below). In most case, adco_constructIO() needs to be called once, at the start of user’s application.
10. WINEXP void STDCALL adco_destroyIO(IRIOStructure *IO);
This function frees up the memory allocated by the previous function. Should be called at the end of user’s application. A block of memory pointed by IO->paths should be freed directly by the user himself.
Getting the library’s version
11. void WINEXP adco_version(char * VS);
This function works with versinfo.exe - can return the version and license expiration date.
Exposed Utility Functions
Getting additional rate information
12. Computing additional rates in the grid
WINEXP void STDCALL adco_forward_coupon (int matr, int ntsteps, double longrate[LEVELS+1][TIMES+1], IRIOStructure *IO);
WINEXP void STDCALL adco_forward_zero (int matr, int ntsteps, double longrate[LEVELS+1][TIMES+1], IRIOStructure *IO);
After the lattice is built, these functions can compute additional long rates of maturity matr (not listed in the IO->matr[] array) on all grid points from time 0 till ntsteps; the results are stored in the longrate[][] array. Note that ntsteps is measured in the time nodes, not in months or years. For example, setting ntsteps to 34 fills the longrate[][] array up to the 30-yr horizon. Setting matr to a value already found in the IO->matr[] array is redundant because it will result in a long rate matrix already computed and stored in IO->latticeR[][].r[].
13. Computing additional forward rates
WINEXP void STDCALL adco_forward_coupon_static(int matr, IRIOStructure *IO, double longrate[TIMES+1], int ntsteps);
WINEXP void STDCALL adco_forward_zero_static(int matr, IRIOStructure *IO, double longrate[TIMES+1], int ntsteps);
After the forward curve is built, these functions can compute additional forward long rates of maturity matr (not listed in the IO->matr[] array) for ntsteps forward step; the results are stored in the longrate[] array.
14. Forward annuity valuation
WINEXP double STDCALL adco_forward_annuity_static(int matr, IRIOStructure *IO, int ntsteps, int freq);
Computes and returns the present value for a forward annuity that pays $1 per annum at frequency of freq. The annuity starts ntsteps node forward.
Valuation of a cashflow vector
15. Yield-based static valuation
WINEXP IR_Error STDCALL getMEY2Px(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, double yield, double *Price, int NumMonths,
double Accrued);
WINEXP IR_Error STDCALL getMEY2Pv(int Market2PayDays,
double *pCF, double yield, double *Pv, int NumMonths);
WINEXP IR_Error STDCALL getPx2MEY(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, int NumMonths, double basePrice,
double *pYield, double Yield0, double Accrued);
WINEXP IR_Error STDCALL getPv2MEY(int Market2PayDays,
double *pCF, int NumMonths, double basePV,
double *pYield, double Yield0);
These functions compute yield (MEY form) given forward price or present value, or compute forward price or present value given yield. The cashflow is pointed by pCF and is assumed to run monthly and be NumMonths long. The cashflow-generating instrument can settle forward in Days2Settle days. The cashflow vector timing is Market2PayDays away from valuation day. Argument Yield0 is initial approximation of the yield. Argument NetDelay is not used. Accrued, an accrued interest for the settlement date, is assumed known and passed to all the functions.
16. Rate path based static valuation of a single path
WINEXP IR_Error STDCALL getCFPrice(int Market2PayDays, int Days2Settle, int NetDelay,
double *pCF, PATHS *path, double *sfrwd,
int NumMonths, double OAS, double Accrued,
double *PV, double *FP);
WINEXP IR_Error STDCALL getCFPrice_OASCorrection(int Market2PayDays, int Days2Settle,
int NetDelay, double *pPVofCF, int NumMonths,
double OAS0, double OAS, double Accrued, double *sfrwd,
double *pPV, double *pFP, double *pMDur, double
*rate_eq);
WINEXP IR_Error STDCALL getCFOAS(int Market2PayDays, int Days2Settle, int NetDelay,
double *pPVofCF, int NumMonths, double basePrice,
double OAS0, double *pOAS, double Accrued, double*sfrwd,
double *pPV, double *pFP, double *rate_eq);
These functions perform somewhat similar role to the previous group except the short rate curve (plus a spread, OAS), not a yield, is used for discounting. The short-rates and OAS are in percents. getCFPrice() function computes the forward price (pointed by FP) and the present value (pointed by PV) using a vector of short rates pointed by sfrwd. This vector starts at the value date, so that sfrwd[0] denotes today’s 1-mo rate, in the MEY 30/360 form.
The two other functions are intended to compute a spread (OAS). Given a vector of cashflows (pCF) and "equivalent yields" (rate_eq) computed with some spread OAS0, function getCFPrice_OASCorrection() recomputes PV and FP for another OAS. Using iterative calls to this “corrective function”, function getCFOAS() find the discount spread and stores it into *pOAS. Note that in both functions the rates sfrwd pointer is used only for the forward settlement calculations.
Backward inductor
17. Backward inductors for a rate-contingent instrument given its cashflow grid (single-factor models only)
The following two functions have fundamental importance for valuation of callable, putable, amortizable, and coupon-changing bonds.
WINEXP IR_Error STDCALL getLatticeBondPrices(int Months2Settle, int Days2Settle,
int NetDelay, double **Coupon, double **Bal,
IRIOStructure *IO, double **accrued, double *CallPrice,
double *PutPrice, int NumMonths, double OAS,
double PVvec[LEVELS+1]);
WINEXP IR_Error STDCALL getLatticeBondOAS(int Months2Settle, int Days2Settle,
int NetDelay, double **Coupon, double **Bal,
IRIOStructure *IO, double **accrued, double *CallPrice,
double *PutPrice, int NumMonths, double basePrice,
double *pOAS, double PVvec[LEVELS+1]);
First, the caller should prepare a two-dimensional array (matrix) of balances (pointed by Bal) and coupon rates (pointed by Coupon) computed without consideration of any embedded calls and puts. The embedded call prices are stored in an array pointed by the CallPrice pointer. For example, if CallPrice[125] is equal to 102, it means that a call struck 102 is available in month 125. If the same element is equal to a large number, say 10000, then the call is not-existing. The PutPrice pointer serves a similar purpose. Array PVvec[] is the present value array found on the lattice at time zero. This array allows measuring the Greeks directly, without resetting the curve.
Parameters Months2Settle and Days2Settle have the same meanings as before whereas NetDelay represents the distance (in calendar days) between the value day and the cashflow day. Argument accrued points to a two-dimensional array of accrued interests pre-computed at all grid’s nodes. The only role of this pointer is to make the backward aware of accrued interests when a call or a put exercise is considered.
The actual use of these two functions requires proper generation and positioning of bond’s cashflows first. This work is done within a specialized library, called bondwrap that simplifies the interface (see next topic).
Day functions
18. Day calendar functions
The interest rate library is equipped with a number of day calendar functions prototyped in ADDate.h. Below is the entire content of that header file:
#ifdef _WIN32
#define WINEXP _declspec(dllexport)
#define STDCALL __stdcall
#else
#define WINEXP
#define STDCALL
#endif
#define SUNDAY 1
#define MONDAY 2
#define TUESDAY 3
#define WEDNESDAY 4
#define THURSDAY 5
#define FRIDAY 6
#define SATURDAY 7
#define JANUARY 1
#define FEBRUARY 2
#define MARCH 3
#define APRIL 4
#define MAY 5
#define JUNE 6
#define JULY 7
#define AUGUST 8
#define SEPTEMBER 9
#define OCTOBER 10
#define NOVEMBER 11
#define DECEMBER 12
typedef struct _adcoDATE
{
int Year;
int Month;
int Day;
} adcoDATE;
typedef enum _DCBTYPES
{
DCB_30_360,
DCB_ACT_360,
DCB_ACT_365,
DCB_ACT_ACT
} DCBTYPES;
typedef enum _BusinessDayConvention
{
BDC_NONE,
BDC_FOLLOWING,
BDC_MODIFIED_FOLLOWING,
BDC_PREVIOUS,
BDC_MODIFIED_PREVIOUS
} BDCTYPES;
WINEXP adcoDATE STDCALL DateAdd(char MDY, adcoDATE myDate, int Step);
WINEXP adcoDATE STDCALL DateSubtract(char MDY, adcoDATE myDate, int Step);
WINEXP double STDCALL DateDiff(adcoDATE Date1, adcoDATE Date2, DCBTYPES Dcb);
WINEXP double STDCALL YearBasis(adcoDATE Date1, DCBTYPES Dcb);
WINEXP int STDCALL DateComp(adcoDATE Date1, adcoDATE Date2);
WINEXP int STDCALL IsLeapYear(int Year);
WINEXP int STDCALL funcLDofM(int Month, int Year);
WINEXP adcoDATE STDCALL MakeDate(int Month, int Day, int Year);
WINEXP adcoDATE STDCALL FindBusinessDate(adcoDATE myDate, int TargetCouponDay, BDCTYPES Bdc);
WINEXP int STDCALL WeekDay(adcoDATE myDate);
WINEXP int STDCALL IsBusinessDate(adcoDATE myDate);
WINEXP int STDCALL IsHoliday(adcoDATE Date1);
int IsXthWeekDayInMonth(adcoDATE Date1,int x, int dayoftheweek, int month);
int IsLastWeekDayInMonth(adcoDATE Date1, int dayoftheweek, int month);
These functions allow computing the day difference using various day counts, building a business day sequence, checking whether a date is a good business date or not, compare dates, add and subtract them, etc. The system 4.5 supports every day count basis enumerated in DCBTYPES above, but only two business day conventions: BDC_NONE and BDC_MODIFIED_FOLLOWING (the latter is used most commonly in U.S. whereas the former allows not using the business day convention).
Black-Scholes utilities
19. Black-Scholes volatility converters
WINEXP double STDCALL ConvertLN2SqGVol(double F,double BSVol,double Expiry);
WINEXP double STDCALL ConvertLN2NVol(double F,double BSVol,double Expiry);
WINEXP double STDCALL ConvertN2SqGVol(double F,double NVol,double Expiry);
WINEXP double STDCALL ConvertN2LNVol(double F,double NVol,double Expiry);
WINEXP double STDCALL ConvertSqG2NVol(double F,double SqGVol,double Expiry);
WINEXP double STDCALL ConvertSqG2LNVol(double F,double SqGVol,double Expiry);
These functions convert Black-Scholes volatility quotes from one form (say, lognormal) to another (normal or squared Gaussian).
Derivatives Suite
The actual use of the backward inductors described in the previous section requires proper generation and positioning of bond’s cashflows first. This work is done within a specialized library, called bondwrap, that simplifies the programming interface. This “wrapper” library is equipped with simple “set” and “get” functions that operate with base types only and allow passing information to and from the system. Finally, analyzeBond() and analyzeSwap() functions do the entire analytical work. Internally, bondwrap calls many of the functions located in the interest rate library (“main” library). The actual valuation work for bonds and swaps with or without embedded options is done by the backward inductors (section 17 above). This covers European, American and Bermudan exercise, cancelable or extendible swaps or vanilla swaptions.
Caps are priced using different functions. The Black-Scholes analytics is fully supported too, for three distribution types (normal, lognormal, squared Gaussian). This group of functions is prototyped in IRderivatives.h and resides in the main library, not in the wrapper.
Call Samples and Comments
Please refer to file irTest.c that contains numerous useful call examples. They demonstrate how to pass market information, build forward curves and the lattice, generate random or deterministic paths, collect rate statistic, or price bond and derivatives (swaptions, caps). We give below just some of examples.
Constructing the forward curve
This example demonstrates a forward curve construction. Note that we do not allocate memory for IO.paths pointer because we do not plan to generate paths for this application. Nor do we supply volatility information or rate model selection – they don’t matter. We show how to form and pass a swap curve.
void main()
{
callForwardCurve);
}
void callForwardCurve()
{
int i;
IRIOStructure IO;
FILE *outfile;
long numpaths = 10000;
clock_t clockstart;
double timepassed;
int rtnval;
adco_constructIO(&IO); /* first key call */
IO.flagYC = couponYC;
/* curve as of 5/13/02
IO.YC[0] = MISSING; /* 1-mo skipped */
IO.YC[1] = 1.9; /* 3-mo */
IO.YC[2] = 2.06; /* 6-mo */
IO.YC[3] = 2.5675; /* 12-mo */
IO.YC[4] = 3.671; /* 24-mo */
IO.YC[5] = 4.295; /* 36-mo */
IO.YC[6] = MISSING; /* 48-mo skipped */
IO.YC[7] = 4.998; /* 60-mo */
IO.YC[8] = 5.4129; /* 84-mo */
IO.YC[9] = 5.7745; /* 120-mo */
IO.YC[10] = 6.078; /* 180-mo */
IO.YC[11] = 6.1979; /* 240-mo */
IO.YC[12] = MISSING;/* 300-mo skipped */
IO.YC[13] = 6.2430; /* 360-mo */
/* defining the long rates */
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 60;
IO.datafile_path = malloc(20*sizeof(char));
IO.datafile_path[0] = '\0'
outfile = fopen("ForwardCurve.txt","w");
strcat(IO.datafile_path, DATAFILEPATH);
/* DATAFILEPATH can refer to
"c:\\adco\\ppmodel" or another
directory where the license key file
is located */
rtnval = adco_getforward(&IO); /* second key call */
if(rtnval != 0)
{
fprintf(outfile, "Uh-oh, rtnval = %d\n", rtnval);
goto endlabel; /* to free pointers and close the outfile */
}
fprintf(outfile, "TIMES = %d \n", TIMES);
fprintf(outfile, "frwd\tshort\t%u\t%u\t%u\n",IO.matr[1],IO.matr[2],IO.matr[3]);
for(i = 0; i < MPL; i ++)
{
fprintf(outfile, "%u\t%.4f\t%.4f\t%.4f\t%.4f\n",
i,IO.forward[i].r[0],IO.forward[i].r[1],IO.forward[i].r[2],IO.forward[i].r[3]);
}
endlabel:
fclose(outfile);
adco_destroyIO(&IO); /* third key call */
}
This example is straight-forward: IO initializing, rate and maturity definitions (stored in the IO structure), call to the adco_getforward() function, IO destruction. The output file should contain information for the forward rates, 1-mo, 2-yr, 10-yr, and 5-yr - as defined in the matr[] array. If the forward curve cannot be calibrated to the given market rates an error message is posted to the file. Skipped values IO.YC[0], IO.YC[6] and IO.YC[12] are reconstructed as IO.YC[0] =1.9000, IO.YC[6]=4.6512 and IO.YC[12]=6.2529.
Constructing the lattice
This example demonstrates lattice calibration and construction. Note that we do not allocate memory for IO.paths pointer because we do not plan to generate paths for this application. We show how to form a swap curve and a swaption volatility matrix and set up calibration flags.
void main()
{
callLattice(HullWhite);
}
void callLattice(enum irprocess irproc)
/* Sample calling program for adco_getbklattice */
{
struct IRIOStruct IO;
int rtnval,i,j,k;
int iRate;
FILE *outfile;
clock_t clockstart;
double timepassed;
adco_constructIO(&IO); /* first key call */
/* Set up the rate environment */
IO.svol = 0.25;
IO.mrev = 0.03;
IO.numpaths = 1;
IO.process = irproc; /* assign rate model – not necessary for the forward curve computation */
/* defining the long-rate maturities */
IO.matr[1] = 24;
IO.matr[2] = 120;
IO.matr[3] = 0; /* ignore this maturity */
IO.flagVTS = 1; /* let use time-dependent short-rate volatility */
IO.flagYC = couponYC;
/* curve as of 5/13/02 */
IO.YC[0] = MISSING; /* 1-mo skipped */
IO.YC[1] = 1.9; /* 3-mo */
IO.YC[2] = 2.06; /* 6-mo */
IO.YC[3] = 2.5675; /* 12-mo */
IO.YC[4] = 3.671; /* 24-mo */
IO.YC[5] = 4.295; /* 36-mo */
IO.YC[6] = MISSING; /* 48-mo skipped */
IO.YC[7] = 4.998; /* 60-mo */
IO.YC[8] = 5.4129; /* 84-mo */
IO.YC[9] = 5.7745; /* 120-mo */
IO.YC[10] = 6.078; /* 180-mo */
IO.YC[11] = 6.1979; /* 240-mo */
IO.YC[12] = MISSING;/* 300-mo skipped */
IO.YC[13] = 6.2430; /* 360-mo */
for (i=0;i<=TIMES;i++)
IO.svolVTS[i]=0.2*(1-0.5*sin(tnodes[i]/12.0*6.28/30));
/* initializing market vols */
for (iRate=1;iRate<=3;iRate++)
for (j=0;j<YCPOINTS;j++)
IO.SwaptionVol[j].r[iRate]=0;
IO.fitMarketmrev=1; /*let us calibrate volatility */
IO.fitMarketVol=1; /*let us calibrate mean reversion */
/* market vols as of 5/13/02 */
IO.SwaptionVol[3].r[1]=25.8; /* 1-yr expiry on 2-yr swap */
IO.SwaptionVol[4].r[1]=22.4; /* 2-yr expiry */
IO.SwaptionVol[5].r[1]=20.8; /* 3-yr expiry */
IO.SwaptionVol[7].r[1]=18.6; /* 5-yr expiry */
IO.SwaptionVol[8].r[1]=17.1; /* 7-yr expiry */
IO.SwaptionVol[9].r[1]=14.7; /* 10-yr expiry */
IO.SwaptionVol[3].r[2]=20.3; /* 1-yr expiry on 10-yr swap */
IO.SwaptionVol[4].r[2]=19.2; /* 2-yr expiry */
IO.SwaptionVol[5].r[2]=18.4; /* 3-yr expiry */
IO.SwaptionVol[7].r[2]=16.4; /* 5-yr expiry */
IO.SwaptionVol[8].r[2]=15.0; /* 7-yr expiry */
IO.SwaptionVol[9].r[2]=13.0; /* 10-yr expiry */
IO.datafile_path = malloc(20*sizeof(char));
IO.datafile_path[0] = '\0';
strcat(IO.datafile_path, DATAFILEPATH); /* DATAFILEPATH can refer to
"c:\\adco\\ppmodel" or another
directory where the license key file
is located */
clockstart = clock();
rtnval = adco_getbklattice(&IO); /* second key call */
timepassed = (double)(clock() - clockstart)/CLOCKS_PER_SEC;
free(IO.datafile_path);
outfile = fopen("latticeTest.txt", "w");
if(rtnval != 0)
{
fprintf(outfile, "Uh-oh, rtnval = %d\n", rtnval);
goto endlabel; /* to free pointers and close outfile */
}
/* the rest is just formatting the output to test it */
fprintf(outfile, "TIMES = %d LEVELS = %d \n", TIMES, LEVELS);
fprintf(outfile, "%f sec\n", timepassed);
/* printing out the short-rate lattice */
fprintf(outfile, "\nShort Lattice... \n");
fprintf(outfile," <-- MONTH -->\n");
for(j = 0; j < TIMES; j++)
fprintf(outfile,"%-6i ",tnodes[j]);
fprintf(outfile,"\n");
}
/* printing out lattice for the 1-st long maturity */
if (matr[1]>0){
fprintf(outfile, "\n%u-mo Lattice... \n",IO.matr[1]);
fprintf(outfile," <-- MONTH -->\n");
for(j = 0; j < TIMES; j++)
fprintf(outfile,"%-6i ",tnodes[j]);
for(i = 1; i <= LEVELS; i++)
{
for(j = 0; j < TIMES; j++)
fprintf(outfile, "%-6.3f ", IO.latticeR[i][j].r[1]);
}
/* printing out lattice for the 2-nd long maturity */
if (matr[2]>0){
fprintf(outfile, "\n%u-mo Lattice... \n",IO.matr[2]);
fprintf(outfile," <-- MONTH -->\n");
for(j = 0; j < TIMES; j++)
fprintf(outfile,"%-6i ",tnodes[j]);
fprintf(outfile,"\n");
}
}
/* printing out lattice for the 3-rd long maturity */
if (matr[3]>0){
fprintf(outfile, "\n%u-mo Lattice... \n",IO.matr[3]);
fprintf(outfile," <-- MONTH -->\n");
for(j = 0; j < TIMES; j++)
fprintf(outfile,"%-6i ",tnodes[j]);
for(i = 1; i <= LEVELS; i++)
{
for(j = 0 ; j < TIMES; j++)
fprintf(outfile, "%-6.3f ", IO.latticeR[i][j].r[3]);
fprintf(outfile,"\n");
}
}
/* printing out transitional probability */
fprintf(outfile, "Probabilities... \n\n");
fprintf(outfile," <-- MONTH -->\n");
for(j = 0; j < TIMES; j++)
fprintf(outfile,"%-6i ",tnodes[j]);
for(i=1; i <= LEVELS; i++)
{
fprintf(outfile," Rate Level[%d]
\n",i);
for(j = 1; j <= BRANCHES; j++)
{
for(k = 0; k < TIMES; k++)
fprint)f(outfile, "%-6.4f ", IO.TProb[i][j][k]);
fprintf(outfile,"\n");
}
fprintf(outfile,"\n");
}
endlabel:
fclose(outfile);
adco_destroyIO(&IO); /* third key call */
}
In this program, we request constructing a Hull-White lattice calibrated to the given swaption volatility matrix (12 options). The model is going to build a time dependent volatility. Note that this calibration work is stored in the IO.svolVTS[] short-rate volatility array, the mean reversion constant, IO.mrev, and the "equivalent" volatility constant, IO.svol (which, for this time-dependent volatility set-up, will serve for information purposes only). Each element of IO.svolVTS[] denotes the absolute "local" volatility (recall we use the Hull-White model) applied for the lattices' time successive time steps.
We could also check how the input and output absolute volatilities compare to each other. In order to do so, we could match the input relative volatility, IO.SwaptionVol[i].r[j], multiplied by the corresponding swap forward rate, IO.forward[tnodes[IO.YCnodes[i]]+1].r[j], with the output (calibrated) number found in IO.FittedVol[IO.YCnodes[i]].r[j]. Recall that the time index is measured in yield curve nodes for SwaptionVol[] array, and in lattice nodes for FittedVol[] and tnodes[] arrays. Given the yield curve point i we could recover the matching lattice node as IO.YCnodes[i].
The lattices built for the short rate, IO.latticeR[i][j].r[0], and two long rates, IO.latticeR[i][j].r[1] and IO.latticeR[i][j].r[2], are printed to the output file. So is the 3-dimensional array of probabilities, IO.TProb[i][k][j]. Because we have not defined a positive value for maturity matr[3], lattice IO.latticeR[i][j].r[3] contains garbage and is not printed out.
Running random paths and testing simple bonds values
This example combines the path generation and a very simple test for bond pricing. In order to measure random pricing errors, we stratify paths using different seeds. Each stratum consists of 100 quasi-random paths; standard error is calculated using 100 strata. Since one does not need random sampling to value option-free bonds, this example is given solely to demonstrate programming methods, the call sequence, and properties of generated paths.
void main()
{
callBondPricing(BlackKarasinski);
}
void callBondPricing(enum irprocess irproc)
{
#define STRATA 100
int i,j,iRate,itest;
IRIOStructure IO;
FILE *outfile;
long numpaths = 100;
clock_t clockstart;
double timepassed;
double SemiCoupon,Bond,SumBond[LONGRATES];
double avgBond[LONGRATES],varBond[LONGRATES],stdBond[LONGRATES];
int rtnval;
adco_constructIO(&IO); /* first key call */
IO.mrev = .03; /* 3% mean reversion */
IO.svol = 0.25; /* 25% short-rate vol */
IO.numpaths = numpaths; /* 1000 paths in each stratum */
IO.quasi_random_nodes=TIMES; /* use Quasi Monte-Carlo method for the entire lattice */
IO.seed=-1; /* to start */
IO.process = irproc; /* assign rate model */
IO.flagYC = couponYC;
/* curve as of 5/13/02 */
IO.YC[0] = MISSING; /* 1-mo skipped */
IO.YC[1] = 1.9; /* 3-mo */
IO.YC[2] = 2.06; /* 6-mo */
IO.YC[3] = 2.5675; /* 12-mo */
IO.YC[4] = 3.671; /* 24-mo */
IO.YC[5] = 4.295; /* 36-mo */
IO.YC[6] = MISSING; /* 48-mo skipped */
IO.YC[7] = 4.998; /* 60-mo */
IO.YC[8] = 5.4129; /* 84-mo */
IO.YC[9] = 5.7745; /* 120-mo */
IO.YC[10] = 6.078; /* 180-mo */
IO.YC[11] = 6.1979; /* 240-mo */
IO.YC[12] = MISSING;/* 300-mo skipped */
IO.YC[13] = 6.2430; /* 360-mo */
/* defining three long maturities */
IO.matr[1]=300;
IO.matr[2]=330;
IO.matr[3]=360;
/* initializing market vols */
for (iRate=1;iRate<=3;iRate++)
for (j=0;j<YCPOINTS;j++)
IO.SwaptionVol[j].r[iRate]=0;
IO.fitMarketVol=0; /* do not calibrate volatility */
IO.fitMarketmrev=0; /* do not calibrate mean reversion */
IO.flagVTS=0; /* use constant-volatility model */
IO.paths = (PATHS **) malloc(numpaths * sizeof(PATHS *));
IO.datafile_path = malloc(20*sizeof(char));
IO.datafile_path[0] = '\0';
strcat(IO.datafile_path, DATAFILEPATH);
for (i=0; i < numpaths; i++)
IO.paths[i] = (PATHS *) calloc(MAXMONTHS, sizeof(PATHS));
outfile = fopen("BondPricingTest.txt","w");
/* lattice will be constructed without volatility calibration */
rtnval = adco_getbklattice(&IO); /* second key call; lattice is calibrated */
if (rtnval != 0) { /*otherwise a garbage will be computed */
fprintf(outfile, "Uh-oh, rtnval = %d\n", rtnval);
goto endlabel; /* to free pointers
fprintf(outfile, "TIMES = %d LEVELS = %d \n", TIMES, LEVELS);
for (iRate=1; iRate<=LONGRATES; iRate++){
avgBond[iRate]=0.0;
varBond[iRate]=0.0;
}
for (itest=1; itest<=STRATA; itest++){
for (iRate=1; iRate<=LONGRATES; iRate++){
SumBond[iRate]=0.0;
}
/* 100 paths will be generated with the current seed, IO.seed (changes after each call) */
adco_getpaths2(&IO); /* third key call */
/* if paths are fudged, all bonds should be valued exactly with any number of paths */
adco_fudgepaths(&IO); /* fourth key call */
/* coupon bond pricing test */
for (iRate=1; iRate<=LONGRATES; iRate++)
if (IO.matr[iRate]>0) {
SemiCoupon=IO.paths[0][0].r[iRate]/2.0; /* take the coupon rate from the lattice root */
for (j=0; j<numpaths; j++) { /* simple static bond pricing */
Bond=100.0;
for (i=IO.matr[iRate]-1; i>=0; i--) {
if ((i+1)%6==0) Bond+=SemiCoupon;
Bond=Bond/(1+IO.paths[j][i].r[0]/1200);
}
SumBond[iRate]+=Bond/numpaths;/* price for one stratum */
}
/* pricing average and variance */
avgBond[iRate]+=SumBond[iRate]/STRATA; /* pricing average */
varBond[iRate]+=SumBond[iRate]*SumBond[iRate]/STRATA;
} /* if a valid rate */
} /* for itest */
for (iRate=1; iRate<=LONGRATES; iRate++){
/* computing pricing RMSE */
stdBond[iRate]=sqrt(varBond[iRate]-avgBond[iRate]*avgBond[iRate]);
fprintf(outfile,"%d-mo avgBond = %10f\tstdBond = %10f\tpaths =
%d\tSTRATA=%d\n",IO.matr[iRate],avgBond[iRate],stdBond[iRate],numpaths,STRATA);
}
endlabel:
fclose(outfile);
for(i=0; i < numpaths; i++)
free (IO.paths[i]);
free(IO.paths);
free(IO.datafile_path);
destroyIO(&IO); /* last key call */
}
Because we elected to fudge short rate paths, all bonds should be valued exactly. Have we commented the “fudging call” line, we would have seen the RMSE error, larger for longer bonds. The accuracy improves with square-root of the total number of paths, which is STRATA times numpaths. We stratified paths for two reasons. First we want to assess valuation accuracy and that is done by measuring standard deviation of prices obtained for different strata (this measure is then divided by square-root of STRATA). Second, we generated only numpaths of random rate paths per stratum (100 in this example) and allocated memory only for this small set of paths. The running averages and variances of bond’s values are updated and we discard the rate paths. For the next stratum, we generate the same number of different paths and re-use the same block of memory. Hence, we can run virtually unlimited number of total paths, without clogging the memory.
If we decided to use the TFG model instead of a single-factor model, we would simply assign values to correlation structure IO->ir2f->corr – see the exposed function group 12 above.