In this overview we discuss the mechanics of the calculations of option-adjusted spread for corporate bonds. This overview is not a replacement for finance texts which give a far more detailed explanation of the theory behind the calculations. It is also not a substitute for reading the documentation about the individual functions, but is designed to give a somewhat higher and more integrated view of the how the functions work together.
Traditional bond-pricing measures like the PRICE function found in XLeratorDB and most spreadsheet applications are essentially time-value-of-money calculations where a single value, the yield, is used to discount all the cash flows associated with a particular bond. In this type of calculation, evaluating the impact of any embedded options in the bond requires recalculating the either the price or yield of the bond using the option date and the associated strike price. This is the basis of yield-to-call, yield-to-put, and yield-to-worst calculations.
Many times, however, it is important to understand the relative value of a bond. In other words, how does the price of a particular bond compare to the price of a benchmark bond? Generally, this type of spread calculation takes the price of the bond as well as the benchmark yield curve as input and then returns a value, known as the spread, which is the amount added to the benchmark curve to resolve to the entered price. This spread value is assumed to be added to every point on the benchmark curve. You can use the XLeratorDB ZSPREAD function to calculate this kind of spread.
The Z-spread calculation, however, does not consider any options embedded in the bond. Inclusion of embedded options in the calculation results in what is known as the option-adjusted spread associated with the bond and is the focus of this article.
Traditionally, when discussing option-adjusted spread, the mechanics of the calculation are explained in terms of a binomial tree or lattice, which usually looks something like this.
In this article, however, we will use a lower triangular matrix as it more closely reflects the physical manifestation of the calculations in database terms. It would also make sense to use an upper triangular matrix.
This example is taken from  Chapter 40 pp 875 – 876. We start with the following par curve.
The following SQL uses the XLeratorDB CMTCurve function to put the time (T), par rate, spot rate, discount factor, and continuously compounded zero coupon rate into a temp table, #z.
We will use the XLeratorDB table-value function LogNormalIRLattice to demonstrate how the calculation of OAS works. For more information on LogNormalIRLattice refer to the documentation. For more information on the math behind the calculation refer to  and .
Using the bond from  (Exhibit 40-19) we are going to construct the interest rate lattice for a bond that matures in 4 years that is redeemable at par starting with the next coupon date. The bond has a 6.5% coupon which is paid annually and the option-adjusted spread is 35 basis points. The volatility is 10%. For the moment, we will simply put the all the information into a temp table, #Lattice, and then extract the information as needed to explain different aspects of the calculation.
The #Lattice table contains all the information that we need to understand the option-adjusted spread calculation. You should note that each rows is uniquely identified by the step number (num_step) and the node number (num_node).
We can extract and PIVOT the calibrated rates from #Lattice table using the following SQL.
This is what the calibrated rates should look like.
This is a table of the forward rates used to construct the interest rate Lattice. The way to read this table is to start with the lower left hand corner and read from left to right. In this document we default to using 0-based arrays, but if you feel more comfortable with 1-based arrays, you can simply add 1 to num_step and num_node in the SELECT statement.
The forward rates at each node are converted to discount factors (df) using the following formula.
The discount factors associated with the calibrated rates are stored in the df_calibrated column. The following SQL selects and pivots that column for ease of viewing.
This is what the calibrated discount factors should look like.
The one thing that we haven't addressed so far is how the calibrated rates for each step at node 0 are calculated. The calibration process requires finding values for rs,0 such that the interest rate lattice returns the discount factors from the yield curve supplied to the function. Thus, the calibration requires knowing the discount factors at that step. In this example, we actually know the discount factors because the coupon dates line up exactly with the yield curve. These are the discount factors which are stored in #z.
As you can see the df0,0 equals the discount factor for the 1-year rate.
We know that the calibrated discount factors are calculated directly from the calibrated forward rates. In other words, df1,0 is calculated directly from r1,0. While there is a closed-form solution for r1,0, there is no closed-form solution for rs,0 where s > 1 and a solution is found using a root-finding algorithm with the tolerance set to 0.0000000001.This process is repeated for each subsequent step until the entire tree is filled out.
Even though we provide the calibrated discount factors, it is actually quite straightforward to check the calibration simply by using the XLeratorDB PriceFromIRLattice function.
The following SQL will return the (cumulative) discount factors by pricing a zero-coupon bond with a redemption value of 1 and a zero option-adjusted spread. This shows that the interest rate Lattice is correctly calibrated to the supplied curve.
Having calculated the calibrated rates, the option-adjusted spread is calculated by adding a single value (the spread) to each of the calibrated forward rates and calculating the discounted cash flow values through every node through to (0, 0) and comparing that to the supplied (clean) price. This spread is adjusted (through another root-finding algorithm) until the calculated price is approximately equal to the supplied price. The tolerance is set 0.000001.
The following SQL returns the pivoted forward rates from the #lattice table.
This is what the forward rates should look like.
As you can see, they are all 35 basis points greater than the calibrated rates.
The following SQL returns the pivoted discount factors calculated from the forward rates.
This what the discount factors should look like.
All that remains to be done is to calculate the discount cash flow values. These values are contained in the PVCF column.
This is what the discounted cash flows should look like.
Notice, that there is now one more step (4), which we will get to, but there are a few other pieces of data we want to get from the table.
This should produce the following result.
Time (T) 0 is the settlement date of the bond. We can see from this table that we are receiving coupons of 6.5 at T 1, 2, 3, 4 and the par value of 100 at T 4. Additionally, the bond is callable (at par) starting at T 1 (1 year from now). Delta is change in time from one step to the next. The continuously compounded zero coupon rate is provided for information purposes.
For the most part, you will not need to use the LogNormalIRLattice function, but it is useful to have to research any questions that might arise about the calculation of the option-adjusted spread.
Let's look at another example, which addresses some other factors in the calculation of the option-adjusted spread. The first is the treatment of the accrued interest on the bond. The second is the interpolation of the spot rates or the continuously compounded zeroes when the first supplied rate is greater than the settlement date.
Let's say we have the following CMT curve that is going to be used in the calculation.
We can use the XLeratorDB CMTCURVE function to put calculate the continuously compounded zero coupon rates and put them into the temp table #z.
Note that we are not using any of the bootstrapped rates from the yield curve in our curve. If you think that you will get better results by including the bootstrapped rates in the option-adjusted spread calculation, then you should include them.
We will use the following call schedule and bond information to generate the interest rate lattice which is stored in the temp table #lattice.
We can use the following SQL to get the pivoted results for the data that varies by step (not by node) with the exception of the dates (because you cannot mix data type in a column in SQL Server). There is separate SQL for the dates.
This produces the following results, which has been reformatted.
The first thing to notice is that coupon for step 0 is -1.523958 which represents the accrued interest as of the settlement date of the bond. As we traverse the interest rate lattice from the upper right corner to the lower left corner, the last thing that will happen is that this accrued interest will be added to the accumulated values from the lattice, bringing that value to the clean price of the bond.
The second thing is that, unlike in the previous example, the delta for all the periods is not the same. The first period (0) has a delta less than all the other periods, because the bond is settling after the previous coupon date.
Notice that the table includes the continuously compounded zero coupon rate. These are the interpolated values from #z. We used the default interpolation method, which is linear, and when seeking to find an interpolated value less than first x-value in the ordered set, it uses the first value. In other words, the CC zero for T = .25 is used for all values of T <= 0.25. We can get the interpolated zeroes and their associated discount factors (which are the basis for the calibration) with the following SQL.
These results, then, become the targets for the calibration process.
Thus, if the calibration is done correctly the discount factor returned by traversing the lattice should be equal to the discount factor, within the tolerance. In the previous example, we used the PriceFromIRLattice function to validate the calibration. Here is somewhat more involved SQL which does not rely on that function.
We can use the following SQL to get all the node information from lattice, which shows the lattice resolving to the clean price of 101.03125 within the stated tolerance.
We have created the following new functions for the calculation of the option-adjusted spread.
We have also created the following function for the calculation of the zero-volatility spread. The Z-spread and the option adjusted spread should be the same for the bonds which do not have calls or puts.