Calibrate a SABR model?

Following on from this question, I have used the same market data they attached but am unsure on interpreting the output.

When I plot the SABR probabilities against strike for data below, no smile is displayed, only a skew/downward slope as all the output volatilities are decreasing.

How do I use this data to produce the volatility smile graphs? or does this data just not display smile?

The first line of market data Travaglini uses is;

and the first line of the output SABR volatilities is;

market data and code from https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2725485

  • $\begingroup$ Can you reproduce the code here in case the link breaks? $\endgroup$
    – StackG
    Oct 10, 2020 at 3:59
  • $\begingroup$ I have now updated the question, with examples of the data, thank you StackG! $\endgroup$ Oct 10, 2020 at 9:29

1 Answer 1


The paper takes each observed smile, bumps all of the strikes by a shift term to make them positive, and the fits a SABR smile to them.

When I do the same thing with the dataset you've attached above (I remove the -150 point because it's vol of 0.0 breaks things) I get the following 'smiley' fit, which looks similar to the results presented above:

SABR fit

This was generated like this:

import numpy as np
import pandas as pd
import QuantLib as ql
from matplotlib import pyplot as plt
from scipy import optimize

# Parameters from the post
strikes = [-100, -50, -25, 0, 25, 50, 100, 150]
vols = [1.047, 0.4812, 0.4327, 0.4268, 0.4148, 0.4253, 0.4322, 0.4495]

fwd = 0.01076
expiry = 0.25
shift = 0.0110 # Shift chosen to make first strike positive

# params are sigma_0, beta, vol_vol, rho
params = [0.4, 0.6, 0.1, -0.4]

# Optimise SABR least squares using python's minimize function
def f(params):
    alpha, beta, nu, rho = params[0], params[1], params[2], params[3]

    alpha = max(alpha, 1e-8) # Avoid alpha going negative
    beta = max(beta, 1e-8) # Avoid beta going negative
    nu = max(nu, 1e-8) # Avoid nu going negative
    rho = max(rho, -0.999) # Avoid rhp going < -1.0
    rho = min(rho, 0.999) # Avoid rho going > 1.0

    calc_vols = np.array([
        ql.sabrVolatility(strike*1e-4 + shift, fwd + shift, expiry, alpha, beta, nu, rho)
        for strike in strikes

    error = ((calc_vols - np.array(vols))**2 ).mean() **.5
    return error

cons = (
    {'type': 'ineq', 'fun': lambda x: x[0]},
    {'type': 'ineq', 'fun': lambda x: 0.99 - x[1]},
    {'type': 'ineq', 'fun': lambda x: x[1]},
    {'type': 'ineq', 'fun': lambda x: x[2]},
    {'type': 'ineq', 'fun': lambda x: 1. - x[3]**2}

result = optimize.minimize(f, params, constraints=cons, options={'eps': 1e-5})
alpha, beta, nu, rho = result['x'][0], result['x'][1], result['x'][2], result['x'][3]

calc_vols = np.array([
    for strike in strikes

results = pd.DataFrame([vols, calc_vols], columns=strikes, index=['market', 'SABR'])
  • $\begingroup$ That's right - SABR is trying to fit the market as closely as possible, so if the market has no 'smile' then SABR probably will not as well. The smilyness is controlled by the SABR vol-of-vol parameter (which is small in the fit above) and the slope is determined by a combination of rho and beta - small rho and beta near to 0 leads to the downward slope we see here $\endgroup$
    – StackG
    Oct 10, 2020 at 14:30

Your Answer

