Skip to content
All library documents

Improving Raw SVI Calibration with a Better Initial Guess

Article Quant Q&A · Author: Hasek

Summary

The document presents a failed calibration of the raw SVI total-variance parametrization for a single options maturity. The example converts market implied volatilities to total variance, computes forward log-moneyness, and minimizes squared fitting error across strikes. With the supplied initial parameter vector and bounds, the optimizer returns a nearly straight-line fit instead of the observed volatility smile, and changing optimization algorithms does not resolve the problem.

The accepted answer identifies the initial guess as the main issue and recommends starting from half the minimum observed total variance, with modest positive values for slope and curvature, a negative correlation parameter, and a small horizontal shift. The author reports that this revised starting point gives a strong fit using BFGS. This is a practical calibration tip, not a general guarantee: the post does not discuss arbitrage constraints, quote weighting, parameter identifiability, or performance across maturities and datasets.

Key ideas

  • Raw SVI models total implied variance as a function of forward log-moneyness.
  • The example’s original starting parameter values lead to a poor, nearly linear fit to the volatility smile.
  • A more suitable initial guess uses half the minimum market total variance and modest values for the other parameters.
  • The revised initialization produced a good fit with BFGS in the reported example.
  • An effective starting point does not by itself ensure arbitrage-free or robust calibration in other datasets.

Tags

Full text
# The raw SVI parametrization numerical optimization


# The raw SVI parametrization numerical optimization












Recall that a raw SVI parametrization of a total variance for a fixed maturity looks like

$$w(k, \xi) = a + b\cdot(\rho\cdot(k-m)+\sqrt{(k-m)^2+\sigma^2})$$

where $\xi=\{a,b,m,\rho,\sigma\}$ is the set of model parameters and $k=\ln\left(\frac{S}{F_T}\right)$ is defined as a forward log-moneyness.

I took the code from this question as a quick start to model implementation however wasn't able to achieve a good fit to market quotes. Here is how the full model calibration setup looks like

```
from scipy import optimize
import datetime
import numpy as np
from dateutil.relativedelta import relativedelta
import math

def eqFwd(spot, time, rate):
    forward = spot * math.exp(time * rate)
    return forward

def forwardMoneyness(strike, spot, time, rate):
    forward = eqFwd(spot, time, rate)
    moneyness = strike / forward
    return moneyness

def logForwardMoneyness(strike, spot, time, rate):
    logMoneyness = math.log(forwardMoneyness(strike, spot, time, rate))
    return logMoneyness

def SVI(logMoneyness, params):
    a = params[0]
    b = params[1]
    m = params[2]
    rho = params[3]
    sigma = params[4]
    totalVariance = a + b * (rho * (logMoneyness - m) + np.sqrt((logMoneyness - m)** 2 + sigma**2))
    return totalVariance

def targetFunction(x, moneyness, total_implied_variance):
    value = 0
    for i in range(len(moneyness)):
        model_total_implied_variance = SVI(np.log(moneyness[i]), x);
        value = value + (total_implied_variance[i] - model_total_implied_variance) ** 2;
    return np.sqrt(value)

def calibrateSVI(spot, rate, time, strikes, market_vols):
    total_implied_variance = time * market_vols ** 2
    moneyness = [forwardMoneyness(k, spot, time, rate) for k in strikes]
    bound = [(1e-5, max(total_implied_variance)), (1e-3, 0.99), (min(strikes), max(strikes)), (-0.99, 0.99), (1e-3, 0.99)]
    x0 = [0, max(total_implied_variance), 0, 1, min(strikes)]
    result = optimize.minimize(fun=targetFunction, x0=x0, bounds=bound, args=(moneyness, total_implied_variance), tol=1e-8, method='BFGS')
    parametrization = result.x
    return parametrization
```

The calibration routine results in SVI producing a straight line.

```
valuationDay = datetime.date(2025, 4, 23)
spot = 145.9

expirationDay = datetime.date(2025, 12, 17)
timeToMaturity = (expirationDay - valuationDay).days / 365.
rateToMaturity = 0.07
strikes = [100., 120., 140., 150., 160., 180., 200]
market = np.array([0.5528, 0.4503, 0.3974, 0.3846, 0.3762, 0.3859, 0.4185])

params = calibrateSVI(spot, rateToMaturity, timeToMaturity, strikes, market)

y = [logForwardMoneyness(k, spot, timeToMaturity, rateToMaturity) for k in strikes]
totalVar = [SVI(ly, params) for ly in y]
model = [np.sqrt(tVar / timeToMaturity) for tVar in totalVar]
print(model)

from matplotlib import pyplot as plt

plt.plot(strikes, market, label='market')
plt.plot(strikes, model, label='SVI')
plt.legend()
plt.show()
```

I played around a bit with different available optimization methods (BFGS, SLSQP, Nelder-Mead etc.) however the only difference in obtained results was the slope of the line which changed from diagonal to horizontal.

What has gone wrong in my SVI calibration?

## Answer by Hasek (score 4, accepted)

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

The problem (as it frequently happens in optimization) was in the very unfortunate initial guess. I found the article Robust Calibration For SVI Model Arbitrage Free which suggests to use $$\begin{cases} a = \frac{1}{2}\min(\mbox{market total variance})\\ b = 0.1\\ \rho = -0.5\\ m = 0.1\\ \sigma = 0.1 \end{cases}$$ as a viable initial guess. I recommend future readers to look up section 3.2.1. The Initial Guess there as it helps to build some intuition behind these values.

Thus I changed the `x0` line to

```
x0 = [0.5 * min(total_implied_variance), 0.1, 0.1, -0.5, 0.1]
```

and got an excellent fit with the BFGS method.

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.