Core mechanism
The payoff consumes active model outputs and returns an active value. The recorder and backend handle graph construction, replay and adjoints without product code implementing differentiation itself.
The editable Java source prices three catalogue payoffs and a capped call lambda through the same NablaTensor engine. Edit the inline payoff and run that exact source.
Form changes regenerate Java and reset manual edits. Manual source edits are compiled as written when you run.
This exact source runs in TeaVM. Form changes update its Java literals and reset manual edits.
import com.nablatensor.engine.ADouble;
import com.nablatensor.engine.AadRecorder;
import com.nablatensor.engine.Nabla;
import com.nablatensor.quant.EquityMarket;
import com.nablatensor.quant.GbmPath;
import com.nablatensor.quant.MonteCarlo;
import com.nablatensor.quant.Product;
import com.nablatensor.quant.Products;
import com.nablatensor.quant.TimeGrid;
public final class WriteYourOwnPayoffRiskStudio {
private WriteYourOwnPayoffRiskStudio() {}
public static void main(String[] args) {
EquityMarket market = EquityMarket.of()
.spot(100)
.strike(100)
.vol(20 / 100.0)
.rate(3 / 100.0)
.maturity(1)
.build();
int steps = 64;
long scenarios = 20000L, seed = 42L;
double cap = market.spot() * (15 / 100.0);
row("European call", Products.europeanCall(), market, steps, scenarios, seed);
row("Asian call", Products.asianCall(), market, steps, scenarios, seed);
row("Lookback call", Products.lookbackCall(), market, steps, scenarios, seed);
row("Capped call (inline)", capped(cap), market, steps, scenarios, seed);
}
private static void row(String name, Product<EquityMarket> product, EquityMarket market, int steps,
long scenarios, long seed) {
try (MonteCarlo<EquityMarket> mc = MonteCarlo.of(product)
.market(market)
.steps(steps)
.fp64()
.greeks()
.on("cpu")
.build()) {
Nabla.TypedValuation<EquityMarket> v = mc.run(scenarios, seed);
System.out.println("ROW|" + name + "|" + mc.nodes() + "|" + v.price() + "|" + v.greek(EquityMarket::spot)
+ "|" + v.greek(EquityMarket::vol));
}
}
private static Product<EquityMarket> capped(double cap) {
return(AadRecorder rec, Nabla.Inputs<EquityMarket> in, TimeGrid grid) -> {
ADouble spot = in.of(EquityMarket::spot), strike = in.of(EquityMarket::strike), rate = in.of(EquityMarket::rate);
ADouble vol = in.of(EquityMarket::vol), maturity = in.of(EquityMarket::maturity);
GbmPath model = GbmPath.of(rec, rate, vol, grid, maturity);
ADouble terminal = spot;
for (int t = 0; t < grid.steps(); t++) terminal = model.step(terminal, rec.randn(), t);
ADouble payoff = terminal.sub(strike)
.max(0.0)
.min(rec.constant(cap));
rec.output(payoff.mul(rate.neg()
.mul(maturity)
.exp()));
};
}
}
A payoff is often the only product-specific part of a valuation; keeping it small and composable makes a new trade a controlled extension instead of an engine fork.
The payoff consumes active model outputs and returns an active value. The recorder and backend handle graph construction, replay and adjoints without product code implementing differentiation itself.
Express the payoff with supported primitives, write examples and independent price checks, expose a deliberate input set, and test discontinuities or path-dependent branches explicitly.
NablaTensor's first design rule: anything a quant would reasonably want to tweak lives in a seam. The payoff is Seam 1 — a Product is a functional interface, you write the valuation in plain Java over ADouble, and swapping it re-records the tape in microseconds. The engine, the MonteCarlo driver and the Greek machinery never change.
Four different tapes, four build()s, no code outside the payoff lambda. The capped call's Greeks fall out of the same adjoint sweep as everything else — the cap (a min node) is just another op the reverse pass knows how to walk.
A useful custom-payoff test has two independent parts: verify the contractual payoff in ordinary numeric code, then verify its active implementation and Greeks. Keeping the payoff expressed in composable primitives allows the same source to run in the scalar reference and differentiated replays.
Payoff branches, maxima, barriers and exercise decisions deserve special attention because they introduce kinks or discontinuities. A production extension should document how those features are treated for risk, and compare selected outputs with controlled bump-and-revalue tests.
A short implementation diff does not eliminate product validation. Contract interpretation, conventions, testing and model approval remain product responsibilities.