Core mechanism
An optimiser changes alpha, rho and nu to minimise residual error between model and market volatilities. Adjoint gradients provide the direction of change without separately finite-differencing each parameter.
Fit alpha, rho and nu to a synthetic volatility smile using Hagan’s SABR Black-volatility approximation. Compare the fitted curve with six target quotes while beta stays fixed at 0.5.
A volatility smile plots implied volatility against strike for one expiry. SABR (Stochastic Alpha, Beta, Rho) describes its shape with a volatility level alpha, elasticity beta, correlation rho and volatility of volatility nu.
Set the forward and maturity, then choose the “True” parameters that generate six synthetic target quotes. Change the initial alpha, rho and nu to explore different solver starting points, set the iteration limit, and choose “Calibrate”. The output shows recovered parameters, the sum of squared volatility errors (SSE), iteration count and the fitted smile.
Forward and strikes use decimal values: 0.05 means 5%. Maturity is in years and Black volatilities are decimals: 1.50 means 150%, not 1.50%. Beta, strikes and tolerance are read-only in the form. This demo does not import observed quotes or offer normal-volatility or shifted-SABR controls.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import com.nablatensor.engine.ADouble;
import com.nablatensor.quant.Calibrator;
import com.nablatensor.quant.SabrHagan;
public final class SabrCalibrationRiskStudio {
private static final double BETA = 0.5, F = 0.0500000000000, T = 1.00000000000, A_TRUE = 0.284000000000,
R_TRUE = -0.310000000000, N_TRUE = 0.570000000000;
private static final double[] K = {
0.030, 0.038, 0.045, 0.055, 0.062, 0.070
};
public static void main(String[] args) {
double[] target = new double[K.length];
for (int i = 0; i < K.length; i++) target[i] = SabrHagan.blackVol(A_TRUE, BETA, R_TRUE, N_TRUE,
F, K[i], T);
Calibrator.Result r = Calibrator.of(rec -> {
ADouble alpha = rec.input("alpha", 0.200000000000), rho = rec.input("rho", 0.00000000000),
nu = rec.input("nu", 0.300000000000), beta = rec.constant(BETA), sse = rec.constant(0.0); for (int i = 0; i < K.length; i++) {
ADouble d = SabrHagan.blackVol(rec, alpha, beta, rho, nu, F, K[i], T)
.sub(target[i]); sse = sse.add(d.mul(d));
}
rec.output(sse);
})
.parameter("alpha", 0.200000000000, 1e-4, 2.0)
.parameter("rho", 0.00000000000, -0.999, 0.999)
.parameter("nu", 0.300000000000, 1e-4, 5.0)
.maxIterations(80)
.tolerance(1.00000000000e-12)
.on("cpu")
.solve();
double alpha = r.parameters()
.get("alpha"), rho = r.parameters()
.get("rho"), nu = r.parameters()
.get("nu");
System.out.println("RESULT|" + alpha + "|" + rho + "|" + nu + "|" + r.objective() + "|" + r.iterations()
+ "|" + r.converged());
for (int j = 0; j < K.length; j++) System.out.println("ROW|" + K[j] + "|" + target[j] + "|" + SabrHagan.blackVol(alpha,
BETA, rho, nu, F, K[j], T));
}
}
Keep the default forward 0.05, maturity 1 year and beta 0.5. Generate targets with alpha 0.284, rho −0.31 and nu 0.57. Start the solver at alpha 0.20, rho 0 and nu 0.30, with at most 80 iterations and tolerance 1e−12. These are constructed quotes from the same formula being fitted, not observations from a market feed.
| Strike (decimal) | Target volatility (decimal) |
|---|---|
| 0.030 | 1.511224 |
| 0.038 | 1.403076 |
| 0.045 | 1.329400 |
| 0.055 | 1.246528 |
| 0.062 | 1.199711 |
| 0.070 | 1.154536 |
With these defaults and the unedited Java source, the solver recovers alpha 0.2840, rho −0.3100 and nu 0.5700 to the displayed precision. Choose “Calibrate” to inspect the residual SSE and convergence status for your run. The objective gives each of the six volatility residuals equal weight.
A close fit here demonstrates recovery of known parameters. It does not establish a good fit to noisy market quotes, stable hedges or safe extrapolation beyond the six strikes. The unshifted Black formula needs positive forwards and strikes. This implementation’s differentiated formula also requires the forward to differ from every strike; setting it to a listed strike stops calibration.
Read the SABR calibration lesson with adjoint gradients for the recorded objective and solver mechanics. For the formula and volatility conventions, see the SABR Black-volatility reference.
This SABR calculator fits a synthetic Black volatility smile to recover alpha, rho and nu with beta held fixed.
An optimiser changes alpha, rho and nu to minimise residual error between model and market volatilities. Adjoint gradients provide the direction of change without separately finite-differencing each parameter.
Choose quote conventions and weights, constrain parameters, inspect residuals by strike and expiry, and test parameter stability between calibration dates.
The Java source first generates six target Black implied volatilities using the plain-number Hagan formula. The objective then evaluates the differentiable version at the same strikes and adds the squared residuals. It calibrates a single expiry with equal quote weights, not a full volatility surface.
L-BFGS uses an adjoint gradient of the recorded objective. Alpha and nu are constrained to positive values, while rho stays between −0.999 and 0.999. Beta remains fixed at 0.5. Solver tolerance is a stopping control; it is not a guarantee that the final SSE will be below that value.
The form changes the generated Java literals. Editing Java allows different objectives or target quotes, but the displayed smile parser expects the six configured strike rows. Keep the output record format intact if you want manually edited results to populate the chart.
The synthetic target parameters are known in advance, which makes this a reproducible calibration exercise. Real quote conventions, weighting, multiple starting points, stability and arbitrage checks require additional work. Browser runtime varies by device; timings from separate JVM examples do not describe this calculator.
A low objective value does not guarantee robust risk. Extrapolation, arbitrage checks, data quality and calibration stability need separate controls.