Charges to parameters of SABR model for a swaption
Charges to parameters of SABR model for a swaption
Loading saved threads...
Jonita D'souza · External communityPost link
External question — Quantitative Finance Stack Exchange
Author: Jonita D'souza
Original post: https://quant.stackexchange.com/questions/85534
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
I have forward premium for a Swaption. How will I allocate the charges to parameters of SABR model for risk based Liquidity Reserve Calculation?
So, I just want to understand how forward bid-offer spreads quoted by brokers are allocated to SABR parameter, to understand the liquidity cost.
Quote
Report
Dimitri Vulis · External communityPost link
External answer — Quantitative Finance Stack Exchange
Author: Dimitri Vulis
Original post: https://quant.stackexchange.com/a/85536
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
SABR model uses 3 model parameters: alpha is the at the money vol, rho is the skew, nu is the vol of vol. But this discussion can be generalized to other pricing models.
The brokers quote bid-offer spreads for different strikes - out of the money, at the money, in the money.
The model fair value of a swaption depends on these 3 model parameters and on the forward rate.
Calculate the sensitivities of the model fair value to each model parameter. These are sometimes called "model greeks". You can calculate these numerically by bumping the model parameters up and down a little and re-running the pricing model. The sensitivity of the fair value to the alpha is like vega.
Having the sensitivities, you have (at least) two methodologies to set the liquidity reserve. The result shouldn't be very different between them.
bottom up - invert the Jacobian matrix that relates the bid-offer spreads quoted by brokers and the sensitivities to the model parameters.
top down - from broker quotes, calibrate the model parameters from all bid side quotes and all offer side quotes. The liquidity reserve for each model parameter is the sensitivity to this parameter times the difference between "offer" and "bid" values for this parameter.
Quote
Report
pandashark · External communityPost link
External answer — Quantitative Finance Stack Exchange
Author: pandashark
Original post: https://quant.stackexchange.com/a/85569
License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/
Adaptation: HTML converted to plain text; contact email addresses removed.
In short: calibrate SABR to bid and offer quotes separately, compare the fitted parameters, and multiply each parameter difference by the position's sensitivity to that parameter. The worked example below shows how.
Building on Dimitri Vulis's answer, here is a worked example using
QuantLib
.
Setup
In SABR,
$\beta$
is typically fixed by convention (commonly 0 or 0.5 for rates), leaving three free parameters:
$\alpha$
— ATM vol level
$\nu$
— vol-of-vol, controls smile curvature (butterfly)
$\rho$
— correlation, controls skew (risk reversal)
Given broker-quoted bid-offer spreads across strikes, how much of the liquidity cost is attributable to each parameter?
Approach 1: Top-Down (Separate Bid/Offer Calibration)
Calibrate SABR to bid vols and offer vols independently. The parameter differences give per-parameter uncertainty bands. Using QuantLib's
SABRInterpolation
on a stylized 5Y10Y swaption smile (
$\beta=0.5$
fixed):
alpha nu rho
Mid: 0.0284 0.4473 -0.0478
Bid: 0.0288 0.3920 -0.0888
Offer: 0.0281 0.4996 -0.0130
─────────────────────────────────────
Δ(param): -0.0007 0.1076 0.0757
The liquidity charge per parameter is
$\left|\frac{\partial V}{\partial \theta_j}\right| \times |\Delta\theta_j|$
, where the model greeks are computed by bumping each SABR parameter and repricing via
sabrVolatility()
+
blackFormula()
:
ATM liquidity reserve decomposition (per unit notional):
alpha charge = 0.0115 bp (38%)
nu charge = 0.0170 bp (56%)
rho charge = 0.0019 bp ( 6%)
TOTAL = 0.0304 bp
Caveat:
This approach can be fragile. SABR calibration is nonlinear, and small perturbations in input vols can produce disproportionate parameter shifts, especially for
$\nu$
and
$\rho$
which are sensitive to wing data. Some desks regularize by constraining certain parameters across bid/offer.
Approach 2: Bottom-Up (Jacobian Decomposition)
Calibrate SABR to
mid
vols once, then compute the Jacobian
$J_{ij} = \frac{\partial \sigma_i}{\partial \theta_j}$
at each strike. Convert to price space via vega:
Strike BidOffer$ Alpha$ Nu$ Rho$ Total$
──────────────────────────────────────────────────────────
1.0% 0.000160 0.000026 0.000239 0.000040 0.000305
2.0% 0.000151 0.000068 0.000296 0.000069 0.000433
2.5% 0.000131 0.000098 0.000229 0.000048 0.000374
3.0% 0.000105 0.000115 0.000170 0.000019 0.000304 ← ATM
3.5% 0.000157 0.000108 0.000220 0.000088 0.000416
4.0% 0.000233 0.000088 0.000305 0.000118 0.000511
5.0% 0.000508 0.000056 0.000374 0.000111 0.000542
Interpretation
The table shows the SABR structure at work:
ATM
:
$\alpha$
charge dominates —
$\alpha$
sets the ATM vol level, so ATM liquidity cost is mostly vol uncertainty
Wings
(deep OTM):
$\nu$
charge dominates — vol-of-vol controls smile curvature
Skew
(asymmetry between low/high strikes):
$\rho$
contributes — it controls the tilt
This decomposition is a
first-order approximation
. The sum of per-parameter charges won't exactly equal the total bid-offer spread due to: (1) cross-terms in the Taylor expansion, (2) the overdetermined system (more strikes than parameters) leaves a least-squares residual, and (3) the market smile is not exactly SABR-shaped. Many desks add an "unexplained residual" bucket. A practical alternative is bump-and-reprice, which captures nonlinearities automatically.
Regulatory Context
This decomposition applies to
Prudent Valuation (PVA)
under CRR2 Article 105, where banks compute close-out cost AVAs from bid-offer spreads. It also informs
FRTB IMA
liquidity horizon assignments.
QuantLib Code
#include <ql/quantlib.hpp>
using namespace QuantLib;
// Market data: 5Y10Y swaption smile
Real forward = 0.03;
Time expiry = 5.0;
std::vector<Real> strikes = {0.01, 0.02, 0.025, 0.03, 0.035, 0.04, 0.05};
std::vector<Real> midVols = {0.35, 0.22, 0.195, 0.18, 0.175, 0.178, 0.195};
// Calibrate SABR (beta=0.5 fixed)
SABRInterpolation sabr(
strikes.begin(), strikes.end(), midVols.begin(),
expiry, forward,
Null<Real>(), 0.5, Null<Real>(), Null<Real>(),
false, true, false, false, // alpha,nu,rho free; beta fixed
true, // vega-weighted
ext::make_shared<EndCriteria>(100000, 100, 1e-8, 1e-8, 1e-8));
sabr.update();
// sabr.alpha(), sabr.nu(), sabr.rho() now hold calibrated params
// Model greeks by finite difference
Real bump = 1e-4;
Real volUp = sabrVolatility(K, forward, expiry, alpha+bump, beta, nu, rho);
Real volDn = sabrVolatility(K, forward, expiry, alpha-bump, beta, nu, rho);
Real dVoldAlpha = (volUp - volDn) / (2.0 * bump);
// Convert to price: dPrice/dParam = vega * dVol/dParam
Real vega = blackFormulaStdDevDerivative(K, forward, midVol*sqrt(expiry))
* sqrt(expiry);
Real chargeAlpha = fabs(vega * dVoldAlpha * deltaAlpha);
Full working example (~200 lines, both approaches)
/* Demonstrates how to decompose swaption bid-offer spreads into
SABR parameter charges for liquidity reserve calculation.
Two approaches:
1. Top-down: calibrate SABR to bid vols and offer vols separately,
then compute per-parameter reserves from the parameter differences.
2. Bottom-up (Jacobian): compute sensitivities of option prices to
SABR parameters, invert to map bid-offer price spreads back to
parameter space.
*/
#include <ql/quantlib.hpp>
#include <iostream>
#include <iomanip>
#include <vector>
#include <cmath>
using namespace QuantLib;
int main() {
std::cout << std::fixed << std::setprecision(6);
// ---------------------------------------------------------------
// Market data: 5Y10Y swaption smile (lognormal vols)
// ---------------------------------------------------------------
Real forward = 0.03; // 3% forward swap rate
Time expiry = 5.0; // 5Y option expiry
// Strikes: -200bp to +200bp around forward
std::vector<Real> strikes = {0.01, 0.02, 0.025, 0.03, 0.035, 0.04, 0.05};
// Mid-market vols (lognormal)
std::vector<Real> midVols = {0.35, 0.22, 0.195, 0.18, 0.175, 0.178, 0.195};
// Bid-offer half-spreads in vol points (wider for wings)
std::vector<Real> halfSpreadVol = {0.015, 0.005, 0.003, 0.002, 0.003, 0.005, 0.015};
// Construct bid and offer vol vectors
std::vector<Real> bidVols(strikes.size()), offerVols(strikes.size());
for (Size i = 0; i < strikes.size(); ++i) {
bidVols[i] = midVols[i] - halfSpreadVol[i];
offerVols[i] = midVols[i] + halfSpreadVol[i];
}
// ---------------------------------------------------------------
// SABR calibration: mid, bid, offer
// ---------------------------------------------------------------
// Fix beta = 0.5 (common convention for rates)
Real betaFixed = 0.5;
auto calibrate = [&](const std::vector<Real>& vols, const std::string& label) {
SABRInterpolation sabr(
strikes.begin(), strikes.end(), vols.begin(),
expiry, forward,
Null<Real>(), betaFixed, Null<Real>(), Null<Real>(),
false, true, false, false, // alpha,nu,rho free; beta fixed
true, // vega weighted
ext::shared_ptr<EndCriteria>(new EndCriteria(100000, 100, 1e-8, 1e-8, 1e-8)),
ext::shared_ptr<OptimizationMethod>(),
0.0020, false, 50, 0.0,
VolatilityType::ShiftedLognormal);
sabr.update();
std::cout << label << ":\n"
<< " alpha = " << sabr.alpha()
<< " beta = " << sabr.beta()
<< " nu = " << sabr.nu()
<< " rho = " << sabr.rho()
<< " (rms err = " << sabr.rmsError() << ")\n";
return std::make_tuple(sabr.alpha(), sabr.beta(), sabr.nu(), sabr.rho());
};
std::cout << "=== SABR Calibration Results ===\n\n";
auto [alphaMid, betaMid, nuMid, rhoMid] = calibrate(midVols, "Mid");
auto [alphaBid, betaBid, nuBid, rhoBid] = calibrate(bidVols, "Bid");
auto [alphaOffer, betaOffer, nuOffer, rhoOffer] = calibrate(offerVols, "Offer");
// ---------------------------------------------------------------
// Approach 1: Top-down parameter charge decomposition
// ---------------------------------------------------------------
std::cout << "\n=== Approach 1: Top-Down (Calibrate Bid & Offer Separately) ===\n\n";
Real dAlpha = alphaOffer - alphaBid;
Real dNu = nuOffer - nuBid;
Real dRho = rhoOffer - rhoBid;
std::cout << "Parameter bid-offer ranges:\n"
<< " delta(alpha) = " << dAlpha << "\n"
<< " delta(nu) = " << dNu << "\n"
<< " delta(rho) = " << dRho << "\n";
// Compute model greeks (sensitivities) by finite difference on ATM price
Real bump = 1e-4;
Real atmMidVol = sabrVolatility(forward, forward, expiry,
alphaMid, betaMid, nuMid, rhoMid);
Real atmPrice = blackFormula(Option::Call, forward, forward,
atmMidVol * std::sqrt(expiry));
// d(price)/d(alpha)
Real volUp = sabrVolatility(forward, forward, expiry,
alphaMid + bump, betaMid, nuMid, rhoMid);
Real volDn = sabrVolatility(forward, forward, expiry,
alphaMid - bump, betaMid, nuMid, rhoMid);
Real dPdAlpha = (blackFormula(Option::Call, forward, forward, volUp * std::sqrt(expiry))
- blackFormula(Option::Call, forward, forward, volDn * std::sqrt(expiry)))
/ (2.0 * bump);
// d(price)/d(nu)
volUp = sabrVolatility(forward, forward, expiry,
alphaMid, betaMid, nuMid + bump, rhoMid);
volDn = sabrVolatility(forward, forward, expiry,
alphaMid, betaMid, nuMid - bump, rhoMid);
Real dPdNu = (blackFormula(Option::Call, forward, forward, volUp * std::sqrt(expiry))
- blackFormula(Option::Call, forward, forward, volDn * std::sqrt(expiry)))
/ (2.0 * bump);
// d(price)/d(rho)
volUp = sabrVolatility(forward, forward, expiry,
alphaMid, betaMid, nuMid, std::min(rhoMid + bump, 0.9999));
volDn = sabrVolatility(forward, forward, expiry,
alphaMid, betaMid, nuMid, std::max(rhoMid - bump, -0.9999));
Real dPdRho = (blackFormula(Option::Call, forward, forward, volUp * std::sqrt(expiry))
- blackFormula(Option::Call, forward, forward, volDn * std::sqrt(expiry)))
/ (2.0 * bump);
std::cout << "\nATM model greeks (sensitivities to SABR params):\n"
<< " dPrice/dAlpha = " << dPdAlpha << "\n"
<< " dPrice/dNu = " << dPdNu << "\n"
<< " dPrice/dRho = " << dPdRho << "\n";
Real chargeAlpha = std::fabs(dPdAlpha * dAlpha);
Real chargeNu = std::fabs(dPdNu * dNu);
Real chargeRho = std::fabs(dPdRho * dRho);
Real totalCharge = chargeAlpha + chargeNu + chargeRho;
std::cout << "\nLiquidity reserve decomposition (ATM, per unit notional):\n"
<< " alpha charge = " << chargeAlpha
<< " (" << 100.0 * chargeAlpha / totalCharge << "%)\n"
<< " nu charge = " << chargeNu
<< " (" << 100.0 * chargeNu / totalCharge << "%)\n"
<< " rho charge = " << chargeRho
<< " (" << 100.0 * chargeRho / totalCharge << "%)\n"
<< " TOTAL = " << totalCharge << "\n";
// ---------------------------------------------------------------
// Approach 2: Bottom-up Jacobian at multiple strikes
// ---------------------------------------------------------------
std::cout << "\n=== Approach 2: Bottom-Up (Jacobian at Each Strike) ===\n\n";
std::cout << std::setw(10) << "Strike"
<< std::setw(12) << "BidOffer"
<< std::setw(12) << "Alpha$"
<< std::setw(12) << "Nu$"
<< std::setw(12) << "Rho$"
<< std::setw(12) << "Total$" << "\n";
std::cout << std::string(70, '-') << "\n";
for (Size i = 0; i < strikes.size(); ++i) {
Real K = strikes[i];
Real bidOfferPrice = blackFormula(Option::Call, K, forward,
offerVols[i] * std::sqrt(expiry))
- blackFormula(Option::Call, K, forward,
bidVols[i] * std::sqrt(expiry));
// Jacobian row: d(vol)/d(param) at this strike, using mid params
auto sabrVol = [&](Real a, Real n, Real r) {
return sabrVolatility(K, forward, expiry, a, betaMid, n, r);
};
Real dvdA = (sabrVol(alphaMid + bump, nuMid, rhoMid)
- sabrVol(alphaMid - bump, nuMid, rhoMid)) / (2.0 * bump);
Real dvdN = (sabrVol(alphaMid, nuMid + bump, rhoMid)
- sabrVol(alphaMid, nuMid - bump, rhoMid)) / (2.0 * bump);
Real dvdR = (sabrVol(alphaMid, nuMid, std::min(rhoMid + bump, 0.9999))
- sabrVol(alphaMid, nuMid, std::max(rhoMid - bump, -0.9999))) / (2.0 * bump);
// Convert vol sensitivity to price sensitivity via vega
Real midVol_i = sabrVol(alphaMid, nuMid, rhoMid);
Real vega = blackFormulaStdDevDerivative(K, forward,
midVol_i * std::sqrt(expiry))
* std::sqrt(expiry);
Real chA = std::fabs(vega * dvdA * dAlpha);
Real chN = std::fabs(vega * dvdN * dNu);
Real chR = std::fabs(vega * dvdR * dRho);
std::cout << std::setw(10) << K
<< std::setw(12) << bidOfferPrice
<< std::setw(12) << chA
<< std::setw(12) << chN
<< std::setw(12) << chR
<< std::setw(12) << chA + chN + chR << "\n";
}
std::cout << "\nDone.\n";
return 0;
}
Compile against QuantLib:
clang++ -std=c++17 -O2 -I/path/to/quantlib -lQuantLib -o sabr_reserve sabr_liquidity_reserve.cpp
Quote
Report
Post Reply
Quoted from Forex.com.bd-Editorial External answer — Quantitative Finance Stack Exchange Author: Dimitri Vulis Source score (net votes, not local likes): 1 Original post: https://quant.stackexchange.com/a/85536 License: CC BY-SA 4.0 — https://creativecommons.org/licenses/by-sa/4.0/ Adaptation: HTML converted to plain text; contact email addresses removed. SABR model uses 3 model parameters: alpha is the at the money vol, rho is the skew, nu is the vol of vol. But this discussion can be generalized to other pricing models. The brokers quote bid-offer spreads for different strikes - out of the money, at the money, in the money. The model fair value of a swaption depends on these 3 model parameters and on the forward rate. Calculate the sensitivities of the model fair value to each model parameter. These are sometimes called "model greeks". You can calculate these numerically by bumping the model parameters up and down a little and re-running the pricing model. The sensitivity of the fair value to the alpha is like vega. Having the sensitivities, you have (at least) two methodologies to set the liquidity reserve. The result shouldn't be very different between them. bottom up - invert the Jacobian matrix that relates the bid-offer spreads quoted by brokers and the sensitivities to the model parameters. top down - from broker quotes, calibrate the model parameters from all bid side quotes and all offer side quotes. The liquidity reserve for each model parameter is the sensitivity to this parameter times the difference between "offer" and "bid" values for this parameter.
Checking account access…