← Back to Blog
How-To

A derivative that jumps at a branch or a kink

At an if-statement or a max(), AAD returns the derivative of the side the code took. That is exact for the code that ran and silent about the jump next door. How to see which decisions a price depends on, how close each one was to flipping, and what to do about it.

Natalija Karpichina
Natalija Karpichina
· 4 min read
AAD Greeks digital options barriers smoothing model validation

Pricing code is full of decisions: a payoff that pays if the spot ends above the strike, a barrier check, a floor on volatility, a solver that stops when it is close enough. AAD differentiates the code that ran. At a decision, that means the side that was taken.

This is exact, and it is also the source of most surprises with AAD on real pricers. The derivative of a step is zero on both sides of it. The derivative of a knock-out is the derivative of whichever branch you were on. The jump itself, which is what a risk manager usually cares about near a barrier, is not in the number.

What AAD returns at a decision

A step. A digital call pays 1 if the final spot is above the strike. Priced by Monte Carlo with the comparison kept on the record (aadc.iif), the pathwise AAD delta is 0.0: on every path the payoff is flat in the spot. The closed-form delta is 0.018996903871755226. Bumping does not rescue it: with the same random numbers, the central bump gave 0.018681076520808804 at h = 1, 0.023048081421777056 at h = 0.1 and 0.0 at h = 0.001, where no path crossed the strike inside the bump.

A kink. For max(S − K, 0), AAD returned a derivative of 0.0 just below the strike, 1.0 just above it, and 1.0 exactly at it. At the kink the derivative does not exist; the engine reports the slope of the side the comparison chose.

A branch. Here is a contract that knocks out to a rebate of 1 if the spot is at or above 102 today, written with an ordinary Python if:

def knock_out(S):
    if S >= B:                                          # collapses to a Python bool while recording
        return aadc.idouble(rebate)
    return bs_call(S)

f = aadc.Functions(); f.start_recording(trace=True, trace_inputs_as_random=False)
s = aadc.idouble(101.7); a = s.mark_as_input()
o = knock_out(s).mark_as_output()
f.stop_recording()

At spot 101.7 the price is 8.010806439387522 and the AAD delta 0.5359894024953883: the delta of the call. Nothing in that number says the price falls to 1 a short way up.

How far was the input from flipping it?

The useful question at a decision is not only “which way did it go” but “how close was it”. The engine keeps every decision it froze during recording, with its signed margin: the left side minus the right side of the comparison at record time. Asked for the decisions closest to flipping, it answers:

branches order=margin
  entry   count  kind      op   margin(first)  where
  #2          1  FROZEN    >=   -0.29999999999999716  p2_branch.py:45 in knock_out   ibool->bool x1

The spot was 0.3 below the barrier, at a named line. The same record can be replayed at new inputs, and it re-checks every recorded decision as it goes. At spot 101.9 the decision survives and the replay returns 8.118394621766484, the same digits as pricing again. At 102.5 it does not:

FLIPPED  #2     frozen     >=   margin -0.29999999999999716 -> 0.5   p2_branch.py:45 in knock_out

The value is withheld rather than returned: past the flipped decision, the recorded arithmetic is the call’s, not the price at that spot.

Writing the comparison with aadc.iif (or aadc.where_arr for arrays, aadc.branching_function for whole functions) keeps both sides on the record, so a replay across the threshold follows the right branch. It does not change the derivative at the input point: that is still the slope of the side you are on.

What to do about it

Report the margin. For a production Greek, the derivative of the branch taken is often the right answer, as long as you also know how far the input is from the nearest decision. A delta reported with “0.3 from the barrier at line 45” is more useful than a delta alone, and the margin is free: it is on the record.

Smooth the decision. Replace the step with a smooth transition over a band, and AAD sees the jump as a steep slope. With a logistic band of width 0.5 around the strike, the digital’s AAD delta was 0.018939830301448957; with width 2.0, 0.01892119508062362, against the closed form 0.018996903871755226. The price moves too, from 0.4185046363427941 unsmoothed to 0.4190136026007339 and 0.4202531408453784: smoothing is a model change, and its width is a choice with a cost in price bias that should be stated alongside the Greek.

Re-record across the decision. When a scenario crosses a threshold, record again at the new point. A replay that names the flipped decision tells you when this is necessary.

Your agent can check this directly

aadc-mcp is a local MCP server that lets a coding agent ask the AADC engine instead of reading the code. pip install aadc-mcp, register it with your MCP client, and ask the agent to trace your pricer. inspect_tape(tape_id, "branches order=margin") lists the decisions closest to flipping, each with its margin and file:line; replay re-evaluates at new inputs and either returns the exact value or withholds it, naming the decision that flipped or could not be re-checked. For a script that records with marked inputs, open_session, ask_session and replay_session answer the same questions from the live recording.

AADC runs here as Community Edition: free for non-commercial and academic use; commercial use, including evaluation inside a company, needs a licence from MatLogica.


Implemented using AADC, a commercial adjoint AD compiler (matlogica.com).

Want to see these results on your own portfolio?

Get in Touch

Interested in these opportunities?

Let's arrange a free demo for you and your team.

Book a Demo