Calculates the option-adjusted spread (OAS) of a callable bond: the spread over the risk-free curve at which the bond’s model price, with the issuer calling the bond whenever that is in its interest, equals its market price. A callable bond pays a higher yield because the issuer can redeem it early when rates fall, which caps its price. The Z-spread lumps that option together with the credit and liquidity premium; the OAS takes the value of the option out, so callable and straight bonds can be compared on their credit spread alone.

The interest rate is modeled with a Ho-Lee binomial tree of twenty steps per coupon period, calibrated to the zero-coupon curve (with the short rate’s volatility in absolute terms, 0.01 being 100 basis points a year). The bond is valued backwards through the tree, capped at the call price on every coupon date from the first call date, and the spread added to every rate in the tree is solved for. The bond price is the clean price: between coupon dates the interest accrued since the last coupon is added to it, as a buyer pays it on top. At that spread the method also returns:

  • Straight Bond Price: the price of the same bond without the call.
  • Option Value: the straight bond price minus the market price, the value of the call to the issuer.
  • Effective Duration and Effective Convexity: the price change for a 100 basis point shift of the curve up and down at an unchanged OAS, which account for the call (a callable bond’s convexity turns negative when the call is near).

For more information, see the following sources:

  • Ho, T.S.Y. and Lee, S.-B. (1986). “Term Structure Movements and Pricing Interest Rate Contingent Claims.” Journal of Finance, 41(5), 1011-1029.
  • Fabozzi, F.J. (2016). “Bond Markets, Analysis, and Strategies.” Pearson, 9th edition, chapter 18.

Also known as: OAS, option-adjusted spread of a callable bond, effective duration, negative convexity.

Option-Adjusted Spread in Python or via the MCP

Install the Finance Toolkit with:

pip install financetoolkit -U

Then call get_option_adjusted_spread as shown below.

from financetoolkit import FixedIncome

fixedincome = FixedIncome()

fixedincome.get_option_adjusted_spread(
    bond_price=[98, 100, 102],
    coupon_rate=0.05,
    years_to_maturity=10,
    years_to_first_call=2,
    spot_rates={1: 0.036, 2: 0.035, 5: 0.037, 10: 0.041, 20: 0.047, 30: 0.047},
)

Or, after setting up the Finance Toolkit MCP, ask:

What is the option-adjusted spread of a 10-year bond with a 5% coupon, callable at par after two years, at prices of 98, 100 and 102 on a spot curve from 3.6% at 1 year to 4.7% at 30 years?

Which returns:

  98 100 102
Option-Adjusted Spread 0.0057 0.0012 -0.0039
Straight Bond Price 103.338 107.146 111.637
Option Value 5.3382 7.1458 9.6374
Effective Duration 4.743 4.1973 3.6058
Effective Convexity -102.679 -106.054 -100.365

Parameters

get_option_adjusted_spread accepts the following parameters.

ParameterTypeDescriptionExample
bond_pricefloat | list[float]The market (clean) price of the bond, or several prices. Defaults to 100.950.0
coupon_ratefloatThe annual coupon rate. Defaults to 0.05.0.04
years_to_maturityfloatThe years to maturity. Defaults to 10.10.0
call_pricefloat | NoneThe price at which the issuer can call the bond, None for a bond without a call. Defaults to 100.100
years_to_first_callfloatThe years until the bond can first be called. Defaults to 2.2
volatilityfloatThe volatility of the short rate in absolute terms. Defaults to 0.01.0.008
frequencyintThe coupon payments per year. Defaults to 2.2
par_valuefloatThe par value of the bond. Defaults to 100.1000.0
spot_ratespd.Series | dict | NoneThe zero-coupon curve (annually compounded) by maturity in years. Defaults to None, which uses the US Treasury zero-coupon curve of the end date (see get_zero_coupon_curve).None, which uses the US Treasury zero-coupon curve of the end date (see get_zero_coupon_curve)
roundingint | NoneThe number of decimals to round the results to. Defaults to None.2
Share