Skip to content
All library documents

Calibrating a SABR Volatility Smile with QuantLib

Article Quant Q&A · Author: Naim Hussain

Summary

The document presents a Python workflow for fitting a SABR volatility smile with QuantLib. The example supplies option strikes and implied volatilities, constructs a NoArbSabrInterpolatedSmileSection, sets initial values for alpha, beta, nu, and rho, and chooses which parameters to hold fixed. It also specifies a stopping criterion and a differential-evolution optimizer. The data are for options on a short-rate futures contract, and the code converts volatility percentages to decimals before calibration.

The reported failure appears when retrieving the fitted alpha: QuantLib raises an error that rho is outside its bounds, reporting a value of 0.99. The document does not provide a diagnosis or a corrected calibration, and its only response points readers to a separate example without reproducing it. As a result, it is useful for seeing the calibration interface and the kind of parameter-bound failure that can occur, but it does not establish which inputs, constraints, or data-quality checks would resolve this particular error.

Key ideas

  • The example fits a SABR smile to market implied volatilities using QuantLib's interpolated smile section.
  • The setup provides initial parameter guesses and allows beta to remain fixed while other parameters are fitted.
  • The reported calibration raises an out-of-bounds error for rho when the fitted alpha is queried.
  • The document does not explain the cause of the error or give a working correction.

Tags

Full text
# Using quantlib in python to optimise SABR parameters


# Using quantlib in python to optimise SABR parameters












I'm trying to use SABR to model volatility smile using QuantLib in python. Can someone provide an easy example of optimising SABR parameters using quantlib and returning a quantlib sabr smile?

I've added some further details and my attempted solution

Options data is as follows:

```
data = pd.DataFrame(
    {
        'date': dt.date(2024, 9, 10),
        'contract_code': 'SFR',
        'expiry_code': 'Z25',
        'strike': [94.125, 94.25, 94.375, 94.5, 94.625, 94.75, 94.875, 95.0, 95.125, 95.25, 95.375, 95.5, 95.625, 95.75, 95.875, 96.0, 96.125, 
                   96.25, 96.375, 96.5, 96.625, 96.75, 96.875, 97.0, 97.125, 97.25, 97.375, 97.5, 97.625, 97.75, 97.875, 98.0, 98.125, 98.25, 
                   98.375, 98.5, 98.625, 98.75, 98.875, 99.0, 99.125, 99.25, 99.375, 99.5, 99.625, 99.75, 99.875, 100.0, 100.125],
        'price': [0.01, 0.0125, 0.015, 0.0175, 0.02, 0.0225, 0.0275, 0.0325, 0.0375, 0.045, 0.0525, 0.0625, 0.0725, 0.085, 0.1025, 0.1225, 
                  0.145, 0.1725, 0.205, 0.24, 0.28, 0.3225, 0.37, 0.425, 0.485, 0.5, 0.4475, 0.4, 0.355, 0.315, 0.2775, 0.245, 0.2175, 0.1925, 
                  0.17, 0.15, 0.1325, 0.1175, 0.1025, 0.09, 0.0775, 0.0675, 0.0575, 0.05, 0.0425, 0.0375, 0.0325, 0.0275, 0.025],
        'expiry_date': dt.date(2025, 12, 12),
        'underlying': 97.2,
        'discount': 0.954263,
        'strike_rate': [0.05875, 0.0575, 0.05625, 0.055, 0.05375, 0.0525, 0.05125, 0.05, 0.04875, 0.0475, 0.04625, 0.045, 0.04375, 0.0425,
                        0.04125, 0.04, 0.03875, 0.0375, 0.03625, 0.035, 0.03375, 0.0325, 0.03125, 0.03, 0.02875, 0.0275, 0.02625, 0.025, 0.02375,
                        0.0225, 0.02125, 0.02, 0.01875, 0.0175, 0.01625, 0.015, 0.01375, 0.0125, 0.01125, 0.01, 0.00875, 0.0075, 0.00625, 0.005,
                        0.00375, 0.0025, 0.00125, 0.0, -0.00125],
        'underlying_rate': 0.028,
        'iv': [132.69826311096224, 132.6135998674864, 131.74391649530892, 130.2713933900565, 128.3101954353009, 125.93667550873326, 125.56317516483074, 
               124.40993955031576, 122.62840728809596, 121.96250429167836, 120.53097623805455, 119.77806107150022, 118.23800214789529, 117.10953251807356,
               117.1802345710329, 117.15922895760573, 116.9829008238809, 117.36798298135997, 118.13524431159198, 118.49245653666745, 119.09935624508483, 
               119.271343264066, 119.62504006972723, 120.71994677387485, 121.93816832073068, 122.76110065088719, 123.53032918656201, 124.49442096239669,
               125.08718288273084, 125.95250561358881, 126.50962001530628, 127.45924149981506, 128.91910012542854, 130.28270398837554, 131.6082404394893,
               132.96538266595968, 134.43958655981677, 136.13652599810882, 137.12827877498975, 138.46836961840881, 139.08198492215723, 140.2211015214183,
               140.6247832406773, 141.82579296748509, 142.33243969844253, 144.06330163737894, 145.25016082696825, 145.78344706531442, 148.339319620407],
        'atm_vol': 122.76110065088719
    }
) 

def build_sabr_smile(data: pd.DataFrame):
    
    date = pd.to_datetime(data['date']).dt.date[0]
    ql_date = ql.Date.from_date(date)
    ql.Settings.instance().evaluationDate = ql_date
    ql_dcc = ql.Actual365Fixed()
    
    expiry_time = ql.Date.from_date(pd.to_datetime(data['expiry_date']).dt.date[0])
    forward_rate = data['underlying_rate'].values[0]
    strike_prices = data['strike_rate'].values
    atm_vol = data['atm_vol'].values[0]/100
    market_vols = data['iv'].values/100
    sabr_smile = ql.NoArbSabrInterpolatedSmileSection(
        expiry_time,
        forward_rate,
        strike_prices,
        True,  # hasFloatingStrikes
        atm_vol,  # atmVolatility (using the at-the-money volatility)
        market_vols,
        0.4,  # alpha (initial guess)
        0.5,  # beta (initial guess)
        0.1,  # nu (initial guess)
        0.1,  # rho (initial guess)
        False,  # isAlphaFixed
        True,  # isBetaFixed
        False,  # isNuFixed
        False,  # isRhoFixed
        True,  # vegaWeighted
        ql.EndCriteria(1000, 100, 1e-6, 1e-6, 1e-6),
        ql.DifferentialEvolution()
    )
    return sabr_smile
    
ql_sabr_smile = build_sabr_smile(clean_smile_data)
ql_sabr_smile.alpha()
```

i run into this error

```
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
~\AppData\Local\Temp\ipykernel_29140\4249249177.py in ?()
----> 1 ql_sabr_smile.alpha()

~\AppData\Local\miniconda3\envs\analytics\Lib\site-packages\QuantLib\QuantLib.py in ?(self)
  13297     def alpha(self):
  13298         r"""alpha(NoArbSabrInterpolatedSmileSection self) -> Real"""
> 13299         return _QuantLib.NoArbSabrInterpolatedSmileSection_alpha(self)

RuntimeError: rho (0.99) out of bounds
```

any help would be appreciated

## Answer by David Duarte (score 3)

https://quant.stackexchange.com/a/80910

Here is an example: SABR Model Pricing Engine in Python QuantLib

Just a friendly reminder: readers are advised to search before asking questions

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.