You calibrated SABR to a swaption smile, it fit beautifully, and then someone asked for a price at a low strike. The number came back wrong — or the implied density went negative, or the whole thing returned NaN.
This is not a bug in your code. It’s a known limitation of the formula almost everyone uses, and it shows up precisely in the market regime we’ve been living in for a decade. Here’s what breaks, why, and what desks actually do about it.
First: SABR the model versus SABR the formula
The confusion at the root of this is that “SABR” refers to two different things.
The model is a stochastic volatility SDE:
![]()
The forward follows a CEV process whose volatility is itself stochastic and correlated with it. Four parameters:
sets the level,
the backbone,
the skew,
the smile convexity.
The formula is Hagan et al.’s 2002 asymptotic expansion, which maps those parameters directly to a Black implied volatility. It’s why SABR became the market standard — no numerical PDE, no simulation, just plug in and get a vol.
The model is fine. The formula is an approximation, and approximations have a domain of validity. Low strikes are outside it.
Break #1: the implied density goes negative
This is the serious one, because it’s silent. The formula returns a plausible-looking volatility, you convert it to a price, and the resulting price surface admits arbitrage.
Recall that the risk-neutral density is recoverable from prices via Breeden-Litzenberger:
![]()
A density must be non-negative. Butterfly arbitrage exists exactly when it isn’t. Let’s check a realistic low-rate calibration:
import numpy as np
from scipy.stats import norm
def hagan_vol(F, K, T, alpha, beta, rho, nu):
if abs(F-K) < 1e-12:
FK = F**(1-beta)
return alpha/FK*(1 + ((1-beta)**2/24*alpha**2/FK**2
+ rho*beta*nu*alpha/(4*FK)
+ (2-3*rho**2)*nu**2/24)*T)
logFK = np.log(F/K)
FKb = (F*K)**((1-beta)/2)
z = nu/alpha*FKb*logFK
x = np.log((np.sqrt(1-2*rho*z+z**2)+z-rho)/(1-rho))
A = alpha/(FKb*(1 + (1-beta)**2/24*logFK**2 + (1-beta)**4/1920*logFK**4))
B = 1 + ((1-beta)**2/24*alpha**2/FKb**2 + rho*beta*nu*alpha/(4*FKb)
+ (2-3*rho**2)*nu**2/24)*T
return A*(z/x)*B
def black_call(F, K, T, vol):
d1 = (np.log(F/K) + 0.5*vol**2*T)/(vol*np.sqrt(T))
d2 = d1 - vol*np.sqrt(T)
return F*norm.cdf(d1) - K*norm.cdf(d2)
F, T = 0.02, 5.0 # 2% forward, 5y
alpha, beta, rho, nu = 0.035, 0.5, -0.3, 0.45
Ks = np.linspace(0.0005, 0.05, 400)
ps = np.array([black_call(F, k, T, hagan_vol(F,k,T,alpha,beta,rho,nu)) for k in Ks])
den = np.gradient(np.gradient(ps, Ks), Ks) # implied density
print(f"Negative density at {(den < -1e-8).sum()} of {len(Ks)} strikes")
print(f"Minimum density: {den.min():.2f}")
Negative density at 16 of 400 strikes
Minimum density: -29.11
Sixteen strikes with a negative density, and it goes as low as
. That’s not a rounding artefact — it’s a region of the strike axis where the model says a butterfly spread has negative value. If you priced there and someone traded against you, they’d have a free lunch.
Look at what the vols themselves are doing in that region:
Strike Hagan vol
0.0010 84.76%
0.0020 71.37%
0.0050 52.92%
0.0100 38.75%
0.0200 26.12% <- ATM
0.0400 24.11%
The wing is exploding — 85% vol at a strike that’s 5% of the forward. The formula doesn’t know when to stop.
Break #2: it can’t handle zero or negative strikes at all
Look at the formula. There’s a
in it. When
that’s undefined; when
it’s a log of a negative number.
K = +0.0050 -> vol = 0.529
K = 0.0000 -> vol = nan
K = -0.0050 -> vol = nan
K = -0.0100 -> vol = nan
For most of the past decade this was not a theoretical concern. EUR and JPY rates traded below zero, negative strikes were quoted daily, and the market-standard smile formula simply could not produce a number for them. That is the practical reason shifted SABR became ubiquitous.
Break #3: the approximation degrades with maturity
Hagan’s expansion is asymptotic in
— small vol-of-vol, short maturity. The correction terms carry an explicit factor of
, so as maturity grows the “correction” stops being a correction:
T = 0.5y -> 24.89%
T = 1y -> 25.02%
T = 5y -> 26.12%
T = 10y -> 27.49%
T = 20y -> 30.22%
T = 30y -> 32.96%
Same parameters, ATM strike, and the quoted vol drifts by eight points across the maturity range. Some of that is real model behaviour; some is the expansion losing accuracy. At 30 years with high vol-of-vol, you should not assume the formula is telling you what the model says.
Why this became everyone’s problem
Worth understanding the history, because it explains why a known-imperfect formula is still the standard.
When Hagan published in 2002, rates were 4-6%. A strike 200bp below the forward was still comfortably positive, the low-strike wing was barely quoted, and the approximation was excellent everywhere anyone cared about. It became market standard for good reason: it’s a closed form, it’s fast, and it inverted the calibration problem into something trivial.
Then rates collapsed. Suddenly the interesting strikes were the ones near and below zero — exactly where the expansion falls apart. The formula didn’t get worse; the market moved into its blind spot.
What desks actually do
Shifted SABR — the standard fix, and the first thing to reach for. Add a constant shift
to both forward and strike, price on
and
, so everything stays positive:
s = 0.03 # 3% shift
for K in [0.005, 0.0, -0.005, -0.01]:
v = hagan_vol(F+s, K+s, T, alpha, beta, rho, nu)
print(f"K = {K:+.4f} -> {v:.2%}")
K = +0.0050 -> 22.14%
K = 0.0000 -> 24.90%
K = -0.0050 -> 28.20%
K = -0.0100 -> 32.25%
Negative strikes now price, and the wing behaves. The catch: the shift is a modelling choice, not a market observable. Different shifts give different smiles, so it must be fixed by convention and used consistently across the desk — and it needs to be large enough that no strike you’ll ever quote goes below
.
Normal (Bachelier) SABR — set
and work in normal vol. Handles negative rates natively, which is why normal vol became the quoting convention in EUR rates.
Antonov’s exact or near-exact solutions — for the zero-correlation case there are closed forms that avoid the expansion entirely, extendable to general
by mapping. Slower, correct in the wings.
Arbitrage-free SABR (Hagan’s own PDE) — Hagan later published a finite-difference approach that solves the model’s density directly, guaranteeing non-negativity. It’s the right answer when you need the wings to be trustworthy, at the cost of being numerical rather than closed-form.
Wing extrapolation — keep Hagan in the region where it’s reliable and splice on a controlled extrapolation (SVI-style, or a parametric wing) beyond it. Common in production because it preserves speed where it matters.
The check to run
Whatever you use, don’t assume. After calibrating, sweep the strike axis and compute the implied density numerically — the ten lines above. If it goes negative anywhere you might quote, you have arbitrage in your surface, and you’d rather find it than have a client find it.
Two other checks worth automating: call prices must be monotonically decreasing in strike, and total implied variance must be non-decreasing in maturity. Those catch butterfly and calendar arbitrage respectively, and neither depends on your model being correct.
The takeaway
SABR breaks at low strikes because Hagan’s formula is an asymptotic approximation, not the model — and the approximation’s error grows exactly where the forward is small, the strike is far, or the maturity is long. In a low-rate world that’s the region everyone trades.
The general lesson generalises beyond SABR: know which of your tools are models and which are approximations to models, and know where the approximations stop holding. That distinction is most of what separates a working implementation from a plausible one.
If you want the machinery underneath — how these models are derived, implemented and validated, and how to test a surface before you trust it — that’s what we build in the modules, from the stochastic calculus up.
Get the Quant Reference Card — four pages covering stochastic calculus, measure change, the models you’ll actually meet, discretisation, rates, credit and XVA, with the practitioner notes that don’t make it into textbooks. Free.