Core mechanism
Mean reversion and volatility are adjusted to reproduce swaption targets. A co-terminal diagonal may allow several parameter combinations to price almost equally well, revealing a flat calibration direction.
A closed-form Jamshidian swaption price and bounded Nelder–Mead fit the (a, sigma) pair to a co-terminal diagonal. This numerical route does not use an AAD tape.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import com.nablatensor.quant.HullWhiteAnalytic;
import com.nablatensor.quant.YieldCurve;
import java.util.List;
/**
* nablatensor.com/learn, chapter 7.5: the real {@code
* HullWhiteCalibrationShowcase}, unchanged. Jamshidian's swaption price needs
* a bisection root-find for the critical short rate plus a cumulative normal
* on the result -- a data-dependent loop and a special function, neither one
* expressible as a fixed sequence of tape nodes -- so this whole page is
* plain {@code double} code with no {@code ADouble}, no {@code
* AadRecorder}, and no engine string anywhere.
*/
public final class HullWhiteCalibrationRiskStudio {
private HullWhiteCalibrationRiskStudio() {}
public static void main(String[] args) {
int n = 12;
double[] pillars = new double[n];
double[] zeros = new double[n];
for (int i = 0; i < n; i++) {
pillars[i] = i + 1.0;
zeros[i] = 0.026000000000000002 + (0.035 - 0.026000000000000002) * (i / (n - 1.0));
}
YieldCurve curve = YieldCurve.of()
.pillars(pillars)
.zeroRates(zeros)
.build();
double[] expiries = {
1, 2, 3, 4, 5, 7
};
int[] tenors = {
9, 8, 7, 6, 5, 3
};
double accrual = 1.0;
HullWhiteAnalytic reference = HullWhiteAnalytic.of(curve, 0.1, 0.0095);
double[] bump = {
1.03, 0.99, 1.01, 1.00, 0.98, 1.02
};
double[] normalVols = new double[expiries.length];
for (int i = 0; i < expiries.length; i++) {
int periods = tenors[i];
double ann = 0.0;
for (int j = 1; j <= periods; j++) {
ann += accrual * curve.discountFactor(expiries[i] + j * accrual);
}
double fwd = (curve.discountFactor(expiries[i]) - curve.discountFactor(expiries[i] + periods * accrual)) / ann;
double px = reference.payerSwaption(expiries[i], accrual, periods, fwd);
normalVols[i] = bump[i] * px / (ann * Math.sqrt(expiries[i] / (2.0 * Math.PI)));
}
List<com.nablatensor.quant.HullWhiteCalibration.SwaptionQuote> quotes = com.nablatensor.quant.HullWhiteCalibration.grid(expiries,
tenors, accrual, normalVols);
long t0 = System.nanoTime();
com.nablatensor.quant.HullWhiteCalibration.Result r = com.nablatensor.quant.HullWhiteCalibration.calibrate(curve,
quotes, 0.03, 0.02);
double ms = (System.nanoTime() - t0) / 1e6;
HullWhiteAnalytic hw = r.model(curve);
double cap = hw.cap(2.0, 1.0, 5, 0.033);
double modelP10 = hw.bondReconstitution(0.0, 10.0, hw.r0()), curveP10 = curve.discountFactor(10.0);
System.out.println("RESULT|" + r.a() + "|" + r.sigma() + "|" + r.rmsePrice() + "|" + r.iterations()
+ "|" + r.converged() + "|" + ms + "|" + cap + "|" + modelP10 + "|" + curveP10);
for (int k = 0; k < quotes.size(); k++) System.out.println("ROW|" + expiries[k] + "|" + tenors[k]
+ "|" + r.targetPrices()[k] + "|" + r.modelPrices()[k]);
}
}
Hull–White calibration shows that a solver can sit inside a valuation workflow, with parameter identifiability often more important than raw fit error.
Mean reversion and volatility are adjusted to reproduce swaption targets. A co-terminal diagonal may allow several parameter combinations to price almost equally well, revealing a flat calibration direction.
Use sufficiently rich instruments, inspect the objective landscape and parameter confidence, impose economically justified constraints, and validate out-of-sample prices and Greeks.
*Keywords: hull white one factor java, jamshidian swaption java, hull white calibration java, swaption pricing java, caplet analytic java, ho lee model java*
Feature F6. The existing HullWhite1F Monte-Carlo step block assumes a flat initial forward. This adds the term-structure-consistent analytic model: given today's discount curve and (a, sigma) it reprices the curve exactly and prices bond options, caps/floors and European swaptions in closed form, then calibrates (a, sigma) to a swaption grid.
Each quote's ATM normal vol becomes a target price via Bachelier (feature F2); the model price is the Jamshidian swaption; the sum of squared price residuals is minimised by a bounded Nelder-Mead. The analytic swaption is not recordable (a root-find and N(x)), so this is the numerical rather than the adjoint calibration route — the adjoint route runs against the HullWhite1F Monte-Carlo swaption instead.
A tiny residual on one calibration set does not establish a unique or stable model. The page intentionally highlights that limitation.