Core mechanism
One recorded valuation obtains per-asset deltas. Repeated directional changes of the same tape reveal cross-gamma terms: how one asset's move changes another asset's delta.
The Java source prices an equal-weight basket of three correlated assets. Spot deltas come from one adjoint sweep; the same recorded Java product is replayed with common random numbers for the cross-gamma matrix.
Cross-gamma uses 15 bump replays plus the base valuation, with common random numbers.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import com.nablatensor.engine.Nabla;
import com.nablatensor.quant.BasketMarket;
import com.nablatensor.quant.BasketOption;
import com.nablatensor.quant.OptionTypeEnum;
public final class BasketOptionsRiskStudio {
private BasketOptionsRiskStudio() {}
public static void main(String[] args) {
BasketMarket m = BasketMarket.of()
.s1(100)
.s2(100)
.s3(100)
.v1(22 / 100.0)
.v2(18 / 100.0)
.v3(25 / 100.0)
.rate(3 / 100.0)
.build();
double strike = 100, maturity = 1;
double[] weights = {
1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0
};
double[][] corr = {
{
1.0, 0.4, 0.2
}, {
0.4, 1.0, 0.3
}, {
0.2, 0.3, 1.0
}
};
int steps = 12;
long paths = 15000L, seed = 42L;
try (Nabla.TypedPricer<BasketMarket> p = Nabla.model(m, BasketOption.option(OptionTypeEnum.CALL,
weights, strike, corr, maturity, steps))
.fp64()
.greeks()
.on("cpu")
.build()) {
Nabla.TypedValuation<BasketMarket> base = p.value()
.with(m)
.scenarios(paths)
.seed(seed)
.run();
BasketMarket g = base.greeks();
System.out.println("RESULT|" + base.price() + "|" + g.s1() + "|" + g.s2() + "|" + g.s3());
double[] s = {
m.s1(), m.s2(), m.s3()
}, h = {
s[0] * 0.01, s[1] * 0.01, s[2] * 0.01
};
double[][] gamma = new double[3][3];
for (int a = 0; a < 3; a++) {
double[] up = s.clone(), dn = s.clone();
up[a] += h[a];
dn[a] -= h[a];
gamma[a][a] = (price(p, m, up[0], up[1], up[2], paths, seed) - 2.0 * base.price() + price(p,
m, dn[0], dn[1], dn[2], paths, seed)) / (h[a] * h[a]);
}
for (int a = 0; a < 3; a++) for (int b = a + 1; b < 3; b++) {
double[] pp = s.clone(), pm = s.clone(), mp = s.clone(), mm = s.clone();
pp[a] += h[a];
pp[b] += h[b];
pm[a] += h[a];
pm[b] -= h[b];
mp[a] -= h[a];
mp[b] += h[b];
mm[a] -= h[a];
mm[b] -= h[b];
gamma[a][b] = (price(p, m, pp[0], pp[1], pp[2], paths, seed) - price(p, m, pm[0], pm[1],
pm[2], paths, seed) - price(p, m, mp[0], mp[1], mp[2], paths, seed) + price(p, m, mm[0],
mm[1], mm[2], paths, seed)) / (4 * h[a] * h[b]);
gamma[b][a] = gamma[a][b];
}
for (int a = 0; a < 3; a++) System.out.println("GAMMA|" + a + "|" + gamma[a][0] + "|" + gamma[a][1]
+ "|" + gamma[a][2]);
}
}
private static double price(Nabla.TypedPricer<BasketMarket> p, BasketMarket original, double s1,
double s2, double s3, long paths, long seed) {
BasketMarket bumped = BasketMarket.of()
.s1(s1)
.s2(s2)
.s3(s3)
.v1(original.v1())
.v2(original.v2())
.v3(original.v3())
.rate(original.rate())
.build();
return p.value()
.with(bumped)
.scenarios(paths)
.seed(seed)
.run()
.price();
}
}
A basket payoff depends jointly on several underlyings, so dependence assumptions and cross-sensitivities are part of the economic result.
One recorded valuation obtains per-asset deltas. Repeated directional changes of the same tape reveal cross-gamma terms: how one asset's move changes another asset's delta.
Calibrate marginal volatilities and correlations consistently, label all assets, examine matrix symmetry and stability, and separate model correlation risk from market spot risk.
*Keywords: heston monte carlo java, sabr monte carlo, hull-white one factor java, libor market model java, adjoint model parameter risk*
Phase 1 adds five model step blocks in nablatensor-quant. Each is a small class that reads its parameters from a typed market record, exposes a step(...) method, and provides a ready european(...) / product builder. Because the parameters are ADouble inputs, one adjoint sweep returns the full model parameter gradient — the sensitivities a calibration or risk-attribution loop consumes — next to the usual spot/rate Greeks.
ModelsTest checks, for every model, that the full adjoint parameter gradient agrees with a central bump-and-revalue on the same seed, plus a degenerate identity per model:
Heston's variance-parameter adjoints (v0, kappa, theta, xi) match a bump only to a few percent — full-truncation Euler's max(v, 0) floor is non-smooth on a measure-zero set; the spot / rate / strike / rho adjoints are exact to Monte-Carlo noise. This is a property of the scheme, documented on HestonModel.
The browser matrix is a small illustrative portfolio. High-dimensional correlation modelling, calibration and hedging need additional controls.