Replicating Lavender's Greeks¶
This guide builds option pricing from first principles — the same math that powers Lavender — and shows you how to independently verify every Greek we publish. Each section introduces one concept with the underlying mathematics, then the corresponding code, then a live verification against the Lavender API.
By the end you'll have a working implementation that reproduces our numbers exactly.
Want a quick visual before reading the math?
On the public Lavender Discord, run /bscomp ticker:AAPL (or any OPRA-listed ticker). The bot plots the difference between Lavender's American Greek and the Black-Scholes equivalent on the same ticker -- near zero where the models agree, spiking where the modeling differences this page covers actually matter (ex-dividend dates, deep-ITM contracts, hard-to-borrow names). Free, no credit card, 100 commands per day.
1. Why Log-Normal?¶
Option pricing starts with a simple observation: stock prices can't go below zero, but they can double, triple, or grow without bound. The distribution of future prices is skewed — there's a floor at zero but no ceiling.
It turns out that if you take the logarithm of daily returns, they look approximately normal:
This means the price itself follows a log-normal distribution: the log of the price is normally distributed. This is the foundational assumption behind Black-Scholes.
Why this matters
If the stock price is log-normal, then we can compute the expected value of any payoff — like \(\max(S_T - K, 0)\) for a call — using the properties of the normal distribution. That's exactly what the Black-Scholes formula does.
2. The Normal Distribution¶
Every formula below uses two functions from the standard normal distribution:
Quick reference: \(N(0) = 0.5\), \(N(1.96) \approx 0.975\), \(N(-\infty) = 0\), \(N(\infty) = 1\).
3. From Price Distribution to Option Value¶
A European call option pays \(\max(S_T - K, 0)\) at expiry. Under risk-neutral pricing, the option's value today is the discounted expected payoff:
Because \(S_T\) is log-normal, this integral has a closed-form solution. The result is the Black-76 formula when expressed in terms of the forward price.
The key insight: \(N(d_2)\) is the (risk-neutral) probability that the option finishes in the money, and \(N(d_1)\) is a delta-weighted version of that probability.
4. The Core: \(d_1\) and \(d_2\)¶
Try it now
Fetch the at-the-forward call and put around 90 days out for SPX and its weekly variant SPXW (four rows: one ATF call/put pair per root). The chain group provides forward, t, t_disc, and rate. The core group provides the Greeks you'll verify against. Any single row is enough -- the formulas below work the same against all of them.
The two roots resolve to different expiries (each independently picks the nearest expiry to 90 days out from its own listing), and to different ATF strikes since each has its own calibrated forward. That's expected -- it's exactly why the chain fields are per-row.
These two numbers appear in every option pricing formula. They measure how far in-the-money the forward is, normalized by volatility:
where:
| Symbol | Lavender L1 field | Meaning |
|---|---|---|
| \(F\) | forward |
Calibrated forward price |
| \(K\) | strike |
Strike price |
| \(\sigma\) | vol |
Implied volatility (decimal) |
| \(T\) | t |
Variance-weighted time to expiry (years) |
Interpretation:
- \(d_2 > 0\) means the forward is above the strike — the option is in the money
- The magnitude tells you how many "vol-adjusted standard deviations" ITM you are
- \(N(d_2)\) is the risk-neutral probability of finishing ITM
Two time fields
Lavender publishes two time-to-expiry fields per expiry, t and t_disc. Use the published values exactly as given in the formulas below — they already carry Lavender's time conventions, so plugging them straight in reproduces our numbers with no further adjustment.
Example — SPX 6600 call, Dec 18 2026 expiry:
F = 6711.04 K = 6600 vol = 0.20805 T = 0.7094
d1 = (ln(6711.04/6600) + 0.5 × 0.20805² × 0.7094) / (0.20805 × √0.7094)
= (0.01668 + 0.01534) / 0.17521
= 0.1828
d2 = 0.1828 - 0.17521 = 0.0075
Both positive — the forward (\(6711) is above the strike (\)6600), so this call is slightly in the money.
5. Option Price (Black-76)¶
Try it now
The theo field is Lavender's model price — the number your formula should reproduce:
With \(d_1\) and \(d_2\) in hand, the Black-76 price formula is:
Or compactly, with \(\phi = +1\) for calls and \(\phi = -1\) for puts:
where \(DF = e^{-r \cdot t_\text{disc}}\) is the discount factor, computed from the L1 fields rate and t_disc.
Example (continued):
rate = 0.03699 t_disc = 0.71184
DF = exp(-0.03699 × 0.71184) = 0.97403
price = 0.97403 × (6711.04 × N(0.1828) - 6600 × N(0.0075))
= 0.97403 × (6711.04 × 0.5725 - 6600 × 0.5030)
= 0.97403 × (3841.6 - 3319.8)
= 508.3
Lavender theo = 508.7 (worked here to 4 decimal places — the §11 code
matches Lavender to < 0.001%.)
5b. Where the Model Breaks: Dividends¶
The Black-76 formula above assumes the forward price \(F\) is known and fixed. In practice, Lavender calibrates \(F\) from live option markets, accounting for discrete dividend payments. But the classic BSM approach uses a continuous dividend yield \(q\) — a smooth approximation of lumpy cash payments.
This matters. Real dividends are discrete events: on the ex-date, the stock drops by approximately the dividend amount, and the forward drops with it. A continuous yield smears this effect across all tenors.
To see this concretely, here's what happens inside a binomial tree when a dividend falls mid-life:
The impact is largest for:
- Short-dated options spanning an ex-date — a $0.75 quarterly dividend on a $200 stock is a 0.4% price drop. For a 2-week ATM call, that matters.
- Deep ITM options — the forward-spot spread depends on cumulative dividends, and continuous yield gets the compounding wrong.
- Calendar spreads — the near-term and far-term legs see different numbers of discrete dividends.
How Lavender handles this
Lavender's forward field is calibrated from live option markets and reflects the market's dividend expectations — not a theoretical \(S \cdot e^{(r-q)T}\) calculation. This is why Black-76 with our forward matches, while BSM with a continuous-yield approximation introduces error.
6. Delta — Sensitivity to the Underlying¶
Delta measures how much the option price changes per $1 move in the stock:
The \(F/S\) factor converts from "per dollar of forward" to "per dollar of spot" — this is Lavender's spot-adjusted delta convention.
Example:
S = 6583.72 F/S = 6711.04 / 6583.72 = 1.01934
delta = 0.97403 × N(0.1828) × 1.01934
= 0.97403 × 0.5725 × 1.01934
= 0.5681
Lavender delta = 0.5684 ✓
6b. Where the Model Breaks: Early Exercise¶
Black-76 (and BSM) assume the option holder waits until expiry to exercise. This is true for European options (SPX, NDX, RUT), but most US equity options are American — they can be exercised at any time.
Why would you exercise early? Consider a deep in-the-money put: if you own a $100 put on a $20 stock, the put is worth $80 of intrinsic value. But Black-76 says the option is worth less than $80 because it discounts the payoff: \(DF \times 80 < 80\). The rational move is to exercise now, collect $80 in cash, and earn interest on it.
Here's what this looks like inside a binomial tree. At each node, the model asks: "Is exercising now worth more than holding?" Red squares are nodes where the answer is yes:
This has real consequences for Greeks:
- Delta for a deep ITM American put is exactly \(-1\) (you will exercise), while Black-76 gives a delta slightly above \(-1\)
- Gamma vanishes below the exercise boundary (delta is flat at \(-1\))
- Theta can flip sign near the boundary — the option is gaining value as the interest advantage of early exercise grows
Why this needs more than Black-76
Lavender's American Greeks reflect the early-exercise boundary, so they can't be reproduced with Black-76 alone — you need a model that checks, at each step, whether exercising beats holding (a binomial tree is the textbook way to do it). The BSM approximation is typically within 5% for delta, gamma, and vega; theta on American options with upcoming dividends can differ 10–15% because the early-exercise boundary reshapes the time-decay curve. Deep ITM puts and near-expiry options diverge most.
7. Gamma — Curvature of Delta¶
Gamma measures how fast delta changes — it's the second derivative of price with respect to spot:
Gamma is always positive (for both calls and puts) and highest for at-the-money options near expiry.
Example:
n(0.1828) = 0.3924
gamma = 0.97403 × 0.3924 / (6711.04 × 0.20805 × 0.8422) × 1.01934²
= 0.000338
Lavender gamma = 0.000338 ✓
8. Vega — Sensitivity to Volatility¶
Vega measures price sensitivity to a 1% change in implied volatility:
The division by 100 converts from "per unit vol" to "per 1% vol" — Lavender's convention.
Example:
9. Theta and Decay — Two Views of Time¶
Time erodes option value. Lavender provides two measures of this, both in the core field group:
- Theta (\(\theta\)) — the analytical partial derivative \(\partial V / \partial t\), per calendar day. This is the standard Greek that every textbook defines and every risk system expects.
- Decay — the expected dollar change from now to the same time on the next trading day. This accounts for weekends, holidays, and the discrete forward roll. Decay is what your P&L actually shows overnight.
For most options theta is negative — time value erodes as expiry approaches — but deep in-the-money options can show positive theta when the interest earned on the option's value outweighs that erosion.
Theta vs Decay
Lavender publishes two time-decay measures. Theta is the analytical partial derivative — what standard models produce. Decay is the expected price change from now to the same time on the next trading day, accounting for weekends and the discrete forward roll.
Concrete scenarios for an ATM call losing ~$0.15 per calendar day:
| Scenario | Theta (per calendar day) | Decay (to next trading day) |
|---|---|---|
| Tuesday, no upcoming holiday | -$0.15 | -$0.15 (1 trading day = 1 calendar day) |
| Friday before a normal Monday | -$0.15 | -$0.45 (spans Sat + Sun) |
| Wednesday before Thanksgiving | -$0.15 | -$0.60 (spans Thu holiday + weekend) |
| Deep ITM American put near exercise boundary | < 0 (interest cost dominates) | can flip to > 0 (early exercise strictly beats holding) |
Use theta for risk reports and anywhere you want the standard partial derivative. Use decay for P&L attribution — it tells you what your position will actually gain or lose by the next time the market opens.
9b. Theta in the Real World¶
The analytical theta is clean and elegant — but it misses several effects that matter in practice.
Theta acceleration near expiry. An ATM option with 30 days left loses about $0.15/day. With 5 days left, it loses $0.40/day. With 1 day left, it can lose several dollars. This non-linearity is fully captured by the formula (the \(1/\sqrt{T}\) term blows up), but it surprises traders who think of theta as roughly constant.
Weekends and holidays. The analytical formula treats time as continuous — every day is the same. But markets are closed on weekends. An option expiring Monday has two extra days of theta built into Friday's close. This is why Lavender provides decay alongside theta: decay accounts for the actual calendar, including weekends, holidays, and the discrete forward roll from one business day to the next.
Dividends and theta. An option spanning an ex-date has a theta "bump" — the forward drops by the dividend, changing the option's moneyness. Continuous-yield BSM spreads this smoothly, but in reality the effect is concentrated on a single day.
American exercise and theta. Near the exercise boundary, an American put's theta can turn positive — the option is gaining value as the interest advantage of exercise grows. Black-76's analytical theta can't capture this; only a tree model with an explicit exercise check produces correct American theta.
Which number should I use?
Use theta for risk reports, margin calculations, and any context where you want the standard partial derivative. Use decay for P&L attribution — it tells you what your position will actually gain or lose overnight, accounting for weekends and holidays.
10. Rho — Interest Rate Sensitivity¶
Rho measures price sensitivity to a 1% change in the risk-free rate:
Example:
rho = 6600 × 0.71184 × 0.97403 × N(0.0075) / 100
= 6600 × 0.71184 × 0.97403 × 0.5030 / 100
= 23.01
Lavender rho = 23.02 ✓
11. Complete Implementation¶
Everything above in one function — paste it, point it at your Lavender instance, and verify.
Try it with any option
Swap the root and tenor to verify any European option:
The chain fields (forward, t, t_disc, rate) vary by expiry — each tenor has its own calibrated forward and time parameters. Longer tenors (dte=180, dte=365) give larger absolute Greeks which can make hand-calculation easier.
import numpy as np
from scipy.stats import norm
import pandas as pd
def black76(F, K, T, t_disc, r, vol, is_call, S):
"""Replicate Lavender's European Greeks from L1 pricing inputs."""
DF = np.exp(-r * t_disc)
sqrtT = np.sqrt(T)
d1 = (np.log(F / K) + 0.5 * vol**2 * T) / (vol * sqrtT)
d2 = d1 - vol * sqrtT
cp = 1 if is_call else -1
FoS = F / S
price = cp * DF * (F * norm.cdf(cp * d1) - K * norm.cdf(cp * d2))
delta = cp * DF * norm.cdf(cp * d1) * FoS
gamma = DF * norm.pdf(d1) / (F * vol * sqrtT) * FoS**2
vega = DF * F * norm.pdf(d1) * sqrtT / 100
theta = -(DF * F * norm.pdf(d1) * vol / (2 * sqrtT) - r * price) / 365
rho = cp * K * t_disc * DF * norm.cdf(cp * d2) / 100
return dict(price=price, delta=delta, gamma=gamma,
vega=vega, theta=theta, rho=rho)
# Fetch and verify -- pull the ATF call at the nearest 90-day expiry
url = "http://localhost:2112/l1/greeks"
df = pd.read_csv(f"{url}?underlying=SPX&dte=90¢er=atf&right=call&greeks=core,chain&format=csv")
row = df.iloc[0]
ref = black76(row.forward, row.strike, row.t, row.t_disc,
row.rate, row.vol, True, row.und_price)
print("Greek Lavender Reference Diff")
print("─" * 50)
for g in ["theo", "delta", "gamma", "vega", "theta", "rho"]:
lav, b76 = row[g], ref[g]
pct = abs(lav - b76) / max(abs(lav), 1e-12) * 100
print(f"{g:6s} {lav:+10.6f} {b76:+10.6f} {pct:.3f}%")
black76 <- function(F, K, T, t_disc, r, vol, is_call, S) {
DF <- exp(-r * t_disc)
sqrtT <- sqrt(T)
d1 <- (log(F / K) + 0.5 * vol^2 * T) / (vol * sqrtT)
d2 <- d1 - vol * sqrtT
cp <- ifelse(is_call, 1, -1)
FoS <- F / S
price <- cp * DF * (F * pnorm(cp * d1) - K * pnorm(cp * d2))
delta <- cp * DF * pnorm(cp * d1) * FoS
gamma <- DF * dnorm(d1) / (F * vol * sqrtT) * FoS^2
vega <- DF * F * dnorm(d1) * sqrtT / 100
theta <- -(DF * F * dnorm(d1) * vol / (2 * sqrtT) - r * price) / 365
rho <- cp * K * t_disc * DF * pnorm(cp * d2) / 100
list(price=price, delta=delta, gamma=gamma,
vega=vega, theta=theta, rho=rho)
}
# Fetch and verify -- pull the ATF call at the nearest 90-day expiry
df <- read.csv("http://localhost:2112/l1/greeks?underlying=SPX&dte=90¢er=atf&right=call&greeks=core,chain&format=csv")
row <- df[1, ]
ref <- black76(row$forward, row$strike, row$t, row$t_disc,
row$rate, row$vol, TRUE, row$und_price)
for(g in c("theo", "delta", "gamma", "vega", "theta", "rho")) {
lav <- row[[g]]
b76 <- ref[[g]]
pct <- abs(lav - b76) / max(abs(lav), 1e-12) * 100
cat(sprintf(" %6s lav=%+.6f ref=%+.6f diff=%.3f%%\n", g, lav, b76, pct))
}
Set up cells:
F = 6711.04 (from L1 "forward")
K = 6600 (strike)
T = 0.7094 (from L1 "t")
T_disc = 0.7118 (from L1 "t_disc")
r = 0.03699 (from L1 "rate")
vol = 0.20805 (from L1 "vol")
S = 6583.72 (from L1 "und_price")
cp = 1 (1 for call, -1 for put)
Compute:
DF = EXP(-r * T_disc)
d1 = (LN(F/K) + 0.5*vol^2*T) / (vol*SQRT(T))
d2 = d1 - vol*SQRT(T)
Price = cp * DF * (F*NORM.S.DIST(cp*d1,TRUE) - K*NORM.S.DIST(cp*d2,TRUE))
Delta = cp * DF * NORM.S.DIST(cp*d1,TRUE) * F/S
Gamma = DF * NORM.S.DIST(d1,FALSE) / (F*vol*SQRT(T)) * (F/S)^2
Vega = DF * F * NORM.S.DIST(d1,FALSE) * SQRT(T) / 100
Theta = -(DF*F*NORM.S.DIST(d1,FALSE)*vol/(2*SQRT(T)) - r*Price) / 365
Rho = cp * K * T_disc * DF * NORM.S.DIST(cp*d2,TRUE) / 100
Expected output:
12. What About BSM?¶
BSM inputs from L1
You need und_price (spot), rate, and borrow — plus the derived dividend yield \(q\):
BSM works for both European and American options (as an approximation for American).
The classic Black-Scholes-Merton formula uses the spot price \(S\) and a continuous dividend yield \(q\) instead of the forward \(F\). The two are related:
You can derive \(q\) from Lavender's forward:
Using BSM with this \(q\) will give you a close approximation — typically within 5% for delta, gamma, vega, and theta. Rho and epsilon may differ by 10–20% because the parameterizations handle interest rate sensitivity differently.
For exact replication of European Greeks, use Black-76 with the forward field directly.
13. American Options¶
Black-76 and BSM assume the option can only be exercised at expiry (European exercise). American options — most US equity options — can be exercised any time before expiry. This early exercise premium means:
- American call prices ≥ European call prices (equality for non-dividend stocks)
- American put prices > European put prices (always, because you can exercise a deep ITM put early to earn interest on the strike)
Pricing American options correctly means accounting for early exercise — checking, as time runs backward from expiry, whether exercising now beats holding. A binomial tree is the standard textbook way to do this, and it captures the early-exercise premium that Black-76 misses.
A BSM approximation for American Greeks is typically within 5% for delta, gamma, and vega. Theta on American calls or puts with upcoming dividends can differ 10–15% because the early-exercise boundary reshapes time decay in ways BSM's closed form can't capture. For a more accurate independent check, implement a tree yourself — Hull chapters 13 and 21 provide a clear treatment of the math.
Worked example: AAPL American call¶
To make the point concrete, here is a live comparison. Pull an ATF AAPL call at the nearest 90-day expiry, then run our Black-76 reference implementation from §11 against it:
AAPL is American-style and has an upcoming dividend, so Black-76 is expected to approximate rather than reproduce Lavender's Greeks.
| Greek | Lavender (tree) | Black-76 (reference) | Diff |
|---|---|---|---|
theo |
13.564 | 13.566 | 0.013% |
delta |
0.506 | 0.506 | 0.017% |
gamma |
0.011 | 0.011 | 0.082% |
vega |
0.534 | 0.534 | 0.000% |
theta |
-0.0876 | -0.0764 | 12.77% |
rho |
0.308 | 0.308 | 0.182% |
Price, delta, gamma, vega, and rho all agree to within 0.2% — for this particular strike and tenor, the early-exercise premium is small. But theta is 12.77% off, exactly as expected: the upcoming dividend shifts the optimal exercise point for part of the option's life, reshaping the time-decay curve in a way that Black-76's analytical theta cannot capture.
Flip this over: when you run the same comparison on SPX (European, no dividends) in §11, every value matches to < 0.001%. That is the distinction in practice.
Reference binomial implementation
Want to reproduce the American numbers yourself? A short, self-contained binomial tree with an early-exercise check — the same code that draws the exercise-boundary chart in §6b — is available as early_exercise.py. It's ~150 lines end-to-end, covering the tree setup, backward induction, and boundary extraction: one way a quant can independently confirm Lavender's American Greeks.
Summary: Inputs and Expected Match¶
Every option on the Lavender L1 API includes the pricing inputs you need:
| L1 Field | Symbol | Used for |
|---|---|---|
forward |
\(F\) | Forward price (Black-76 input) |
vol |
\(\sigma\) | Implied volatility |
t |
\(T\) | Variance-weighted time (for \(\sigma\sqrt{T}\)) |
t_disc |
\(t_\text{disc}\) | Calendar time (for discounting) |
rate |
\(r\) | Risk-free rate |
und_price |
\(S\) | Spot price (for delta/gamma adjustment) |
strike |
\(K\) | Strike price |
right |
— | call or put |
Expected agreement for European options (Black-76):
| Greek | Typical difference |
|---|---|
| Delta | < 0.01% |
| Gamma | < 0.01% |
| Vega | < 0.01% |
| Theta | < 0.01% |
| Rho | < 0.01% |
These are not approximations — with the same inputs, Black-76 reproduces Lavender's European Greeks exactly.