Price Swaption After Calibrating a Hull-White Model to Swaption Volatility Surface
This example shows how a Hull-White model can be calibrated to a swaption volatility surface and using functionality in the Financial Instruments Toolbox™ and lsqnonlin. After calibration, you can use the calibrated Hull-White model parameters to price a Swaption instrument..
Calibrating a Hull-White model to a swaption volatility surface involves adjusting the parameters of the Hull-White interest rate model so that its output (specifically, the implied volatilities of swaptions) matches the observed market data represented by the volatility surface.
Calibration is an important step in this workflow to price a swaption for the following reasons:
Accurate pricing — Proper calibration ensures that the Hull-White model can effectively price swaptions and other interest-rate derivatives, reflecting current market conditions.
Risk management — A calibrated model helps in assessing and managing interest-rate risk, allowing for better hedging strategies.
Market consistency — Calibration to the swaption volatility surface ensures that the model is consistent with market expectations and dynamics.
Load Market Data
Load the market data. For the purpose of illustration in this example, the market data is hard-coded.
Settle = datetime(2025,2,28); SettleEndofMonthDate = dateshift(Settle,'end','month'); SettleEndofMonthFlag = Settle == SettleEndofMonthDate;
Create Zero Curve
Create the zero curve from ZeroRates and ZeroTimes using ratecurve.
ZeroRates = [5.4 5.4 5.4 5.4 5.4 4.8 4.4 4 3.8 3.7 3.7 3.6 3.4]'/100; ZeroTimes = [calmonths([1 2 3 6]) calyears([1 2 3 5 7 10 15 20 30])]'; ZeroDates = Settle + ZeroTimes; myRC = ratecurve('zero', Settle, ZeroDates, ZeroRates,Compounding=-1,Basis=3, ... InterpMethod="pchip");
ATM Swaption Normal Vol Data
Define the swapTenors, the period from the start date to the end date of the swap contract, and load the SwaptionVolATM_Normal.mat data.
swapTenors = [1:5 7 10 15 20 30]; optionExpiries = [calmonths([1 3 6]) calyears([1:5 7 10 20])]'; ExerciseDates = Settle + optionExpiries; if SettleEndofMonthFlag ExerciseDates = dateshift(ExerciseDates,'end','month') end
ExerciseDates = 11×1 datetime array
"31-Mar-2025"
"31-May-2025"
"31-Aug-2025"
"28-Feb-2026"
"28-Feb-2027"
"29-Feb-2028"
"28-Feb-2029"
"28-Feb-2030"
"29-Feb-2032"
"28-Feb-2035"
variables = load("SwaptionVolATM_Normal.mat");
SwaptionVolATM_Normal = variables.SwaptionVolATM_NormalSwaptionVolATM_Normal = 11×10
0.0507 0.0668 0.0571 0.0542 0.0522 0.0460 0.0375 0.0356 0.0315 0.0275
0.0533 0.0640 0.0551 0.0525 0.0507 0.0450 0.0405 0.0362 0.0326 0.0289
0.0595 0.0596 0.0557 0.0499 0.0484 0.0434 0.0394 0.0358 0.0327 0.0292
0.0581 0.0510 0.0491 0.0449 0.0410 0.0373 0.0374 0.0340 0.0315 0.0283
0.0436 0.0405 0.0398 0.0370 0.0367 0.0343 0.0323 0.0310 0.0287 0.0265
0.0373 0.0348 0.0344 0.0322 0.0323 0.0304 0.0307 0.0288 0.0269 0.0250
0.0325 0.0305 0.0305 0.0307 0.0289 0.0294 0.0289 0.0271 0.0253 0.0236
0.0289 0.0291 0.0278 0.0279 0.0281 0.0280 0.0274 0.0256 0.0239 0.0223
0.0262 0.0260 0.0260 0.0261 0.0260 0.0258 0.0249 0.0229 0.0215 0.0204
0.0244 0.0243 0.0238 0.0237 0.0233 0.0226 0.0213 0.0197 0.0184 0.0181
0.0154 0.0152 0.0150 0.0148 0.0146 0.0142 0.0136 0.0136 0.0135 0.0138
⋮
Compute Swaption Prices Using Normal Model
To calibrate the Normal model, first you must compute the prices for each swaption.
nExpiries = numel(optionExpiries); nTenors = numel(swapTenors); SwaptionPrices = zeros(nExpiries,nTenors); SwaptionStrike = zeros(nExpiries,nTenors); SwapMaturity = repmat(NaT,[nExpiries nTenors]); SwaptionInstruments(nExpiries,nTenors) = fininstrument.FinInstrument; for iExpiry=1:length(ExerciseDates) for iTenor=1:length(swapTenors) SwapMaturity(iExpiry,iTenor) = ExerciseDates(iExpiry) + calyears(swapTenors(iTenor)); ExerciseDateEndofMonthDate = dateshift(ExerciseDates(iExpiry),'end','month'); ExerciseDateEndofMonthFlag = ExerciseDates(iExpiry) == ExerciseDateEndofMonthDate; if ExerciseDateEndofMonthFlag SwapMaturity(iExpiry,iTenor) = dateshift(SwapMaturity(iExpiry,iTenor),'end','month'); end tmpSwap = fininstrument("Swap","Maturity",SwapMaturity(iExpiry,iTenor),"LegRate",[0 0],... "StartDate",ExerciseDates(iExpiry)); SwaptionStrike(iExpiry,iTenor) = parswaprate(tmpSwap,myRC); tmpModel = finmodel("Normal","Volatility",SwaptionVolATM_Normal(iExpiry,iTenor)); SwaptionInstruments(iExpiry,iTenor) = fininstrument("Swaption","Strike",SwaptionStrike(iExpiry,iTenor),... "ExerciseDate",ExerciseDates(iExpiry),"Swap",tmpSwap); tmpPricer = finpricer("Analytic","Model",tmpModel,"DiscountCurve",myRC); SwaptionPrices(iExpiry,iTenor) = price(tmpPricer,SwaptionInstruments(iExpiry,iTenor)); end end ExerciseDatesFull = repmat(ExerciseDates,1,numel(swapTenors))
ExerciseDatesFull = 11×10 datetime array
"31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025" "31-Mar-2025"
"31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025" "31-May-2025"
"31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025" "31-Aug-2025"
"28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026" "28-Feb-2026"
"28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027" "28-Feb-2027"
"29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028" "29-Feb-2028"
"28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029" "28-Feb-2029"
"28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030" "28-Feb-2030"
"29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032" "29-Feb-2032"
"28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035" "28-Feb-2035"
Calibrate Hull-White Model Using lsqnonlin
You can now calibrate the Hull-White model using lsqnonlin. Note that you use the function swaptionbylg2f to compute swaption prices. This uses a 2-factor Hull-White model but you can also can be use a 1-factor Hull-White model by setting the b, eta and rho parameters to be 0.
options = optimoptions('lsqnonlin'); options.Display = 'iter'; options.FunctionTolerance = 1e-3; options.OptimalityTolerance = 1e-3; options.UseParallel = true; zeroVal = 1e-12; HW1Fobjfun = @(x) SwaptionPrices(:) - ... swaptionbylg2f(myRC,x(1),zeroVal,x(2),zeroVal,zeroVal,... SwaptionStrike(:),ExerciseDatesFull(:),SwapMaturity(:)); x0 = [.2 .1]; lb = [.0001 .0001]; ub = [1 1]; ModelParams = lsqnonlin(HW1Fobjfun,x0,lb,ub,options);
Norm of First-order
Iteration Func-count Resnorm step optimality
0 3 1500.21 887
1 6 661.471 0.273129 2.35e+04
2 9 415.405 0.0589536 159
3 12 333.853 0.0895385 2.9e+03
4 15 328.73 0.0198526 15.7
5 18 328.547 0.00506845 41.2
Local minimum possible.
lsqnonlin stopped because the final change in the sum of squares relative to
its initial value is less than the value of the function tolerance.
<stopping criteria details>
a = ModelParams(1)
a = 0.0511
sigma = ModelParams(2)
sigma = 0.0401
Create Swap and Swaption Instruments
Use fininstrument to create the underlying Swap instrument object.
Swap = fininstrument("Swap",'Maturity',datetime(2028,2,29),'LegRate',[0 0],'LegType', ... ["float","fixed"],'Notional',100,'StartDate',datetime(2026,2,28),'Name',"swap_instrument")
Swap =
Swap with properties:
LegRate: [0 0]
LegType: ["float" "fixed"]
Reset: [2 2]
Basis: [0 0]
Notional: 100
LatestFloatingRate: [NaN NaN]
ResetOffset: [0 0]
DaycountAdjustedCashFlow: [0 0]
ProjectionCurve: [0×0 ratecurve]
BusinessDayConvention: ["actual" "actual"]
Holidays: NaT
EndMonthRule: [1 1]
StartDate: 28-Feb-2026
Maturity: 29-Feb-2028
Name: "swap_instrument"
Use fininstrument to create the Swaption instrument object.
Swaption = fininstrument("Swaption",'Strike',0.02,'ExerciseDate',datetime(2026,2,28),'Swap',Swap,'Name',"swaption_instrument")
Swaption =
Swaption with properties:
OptionType: "call"
ExerciseStyle: "european"
ExerciseDate: 28-Feb-2026
Strike: 0.0200
Swap: [1×1 fininstrument.Swap]
Name: "swaption_instrument"
Create HullWhite Model Using Calibrated Parameters
Use finmodel to create a HullWhite model object.
HullWhiteModel = finmodel("HullWhite",'Alpha',0.0511,'Sigma',0.0401)
HullWhiteModel =
HullWhite with properties:
Alpha: 0.0511
Sigma: 0.0401
Create analytic Pricer
Use finpricer to create an outPricer object using an analytic pricing method and use the ratecurve object for the 'DiscountCurve' name-value pair argument.
outPricer = finpricer("analytic",'Model',HullWhiteModel,'DiscountCurve',myRC)
outPricer =
HullWhite with properties:
DiscountCurve: [1×1 ratecurve]
Model: [1×1 finmodel.HullWhite]
Price Swaption Using Calibrated Market Data
Use price to compute the price for the Swaption instrument.
[Price, outPR] = price(outPricer,Swaption,"all")Price = 4.8225
outPR =
priceresult with properties:
Results: [1×1 table]
PricerData: []