Skip to content
All library documents

Calibrating SABR Volatility with Shifted Strikes

Article Quant Q&A · Author: rosietaylor11

Summary

The note addresses why a set of SABR-calibrated volatilities may appear to show only a downward skew rather than a volatility smile. It explains that the referenced paper shifts all observed strikes so they become positive before fitting the SABR model, and reproduces that approach on the supplied market data. The reply reports omitting the point with zero volatility because it disrupts the calculation.

The example fits SABR parameters by minimizing the root mean squared difference between model and observed volatilities, subject to bounds on the parameters. It applies the shift to both strikes and the forward when evaluating the model, then compares fitted values with market quotes. The described fit produces a smile resembling the paper's results. This is a calibration illustration rather than evidence of robust performance: the shift is chosen to make the lowest strike positive, one data point is excluded, and no broader validation or sensitivity analysis is given.

Key ideas

  • The described SABR fitting procedure shifts strikes to make them positive.
  • The shift is also applied to the forward in the model volatility calculation.
  • Parameters are fitted by minimizing the root mean squared error against market volatilities.
  • The example excludes a quote with zero volatility because it causes calculation problems.
  • A fitted smile is shown, but the example does not establish robustness across datasets or shift choices.

Tags

Full text
# Interpreting SABR calibration model output


# Interpreting SABR calibration model output












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

## Answer by StackG (score 2)

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

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:

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([
    ql.sabrVolatility(strike*1e-4 + shift, fwd + shift, expiry, alpha, beta, nu, rho)
    for strike in strikes
])

results = pd.DataFrame([vols, calc_vols], columns=strikes, index=['market', 'SABR'])
```

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.