Main Content

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_Normal
SwaptionVolATM_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: []

See Also

Functions