You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This document summarizes the formulas implemented by TheGreekLab.
Notation:
$S$ - spot price
$F$ - futures price
$K$ - strike price
$T$ - time to expiry in years
$r$ - risk-free rate
$q$ - continuous dividend yield
$D_i$ - deterministic cash dividend paid at time $t_i$
$r_f$ - foreign risk-free rate
$b$ - cost of carry
$\sigma$ - annualized volatility
$N(x)$ - standard normal CDF
$n(x)$ - standard normal PDF
$\Delta t = T / N$ - binomial time step
Time To Expiry
Every model requires an explicit day-count convention. If $t_v$ is the
market-data valuation timestamp, $t_e$ is the contract expiration timestamp
and $D$ is the selected fixed year denominator, then:
The difference is measured in actual elapsed seconds, including the time of
day. DayCountConvention.ACT_365F uses $D=365$ and
DayCountConvention.ACT_360 uses $D=360$; neither denominator changes for a
leap year. OptionContract stores only the zoned expiration date, while the
caller selects the convention for each valuation.
Cost Of Carry
The generalized Black-Scholes engine uses a cost-of-carry parameter $b$:
The curve-aware API separates the forward used in the option distribution from
the funding discount factor applied to the payoff. Let $D_r(T)$ denote the
funding discount factor and $F(T)$ the forward price for delivery at $T$.
FundingCurve and DividendYieldCurve are distinct nominal API types so that
their economic roles cannot be exchanged at a call site.
For an equity with spot $S$ and continuous-dividend discount factor $D_q(T)$,
EquityForwardCurve constructs:
$$F(T)=S\frac{D_q(T)}{D_r(T)}$$
With flat continuously compounded rates, $D_r(T)=e^{-rT}$ and
$D_q(T)=e^{-qT}$, which reduces to:
$$F(T)=S e^{(r-q)T}$$
ForwardBlack76 uses the forward form of the Black formula:
At $T=0$, the pricer returns intrinsic value and requires $D_r(0)=1$ within
numerical tolerance.
Curve Interpolation and Scope
FlatDiscountCurve evaluates $e^{-rT}$. InterpolatedDiscountCurve has an
implicit valuation node $D_r(0)=1$ and interpolates the logarithm of positive
discount factors between supplied nodes. InterpolatedForwardCurve requires
an explicit valuation node $F(0)$ and uses the same interpolation for positive
forward prices. For either value $V$, interpolation between adjacent nodes is:
The implementations preserve node values, forbid timestamps before valuation
and do not extrapolate past the final node. They do not bootstrap curves from
market instruments, apply calendar conventions, clean arbitrage, or provide
curve Greeks. Those policies are deliberately outside the current API.
Volatility Surface Coordinates
VolatilitySurface quotes implied volatility by expiry and forward-relative
log moneyness:
$$k(T)=\ln\left(\frac{K}{F(T)}\right)$$
ForwardBlack76 obtains $sigma(T,k(T))$ after it has obtained $F(T)$ from
the forward curve. FlatVolatilitySurface returns a single validated
volatility for every finite $k$ at or after its valuation timestamp, preserving
the scalar-volatility behavior of earlier API paths. Interpolated volatility
surfaces, smile calibration and static-arbitrage checks are not implemented.
Generalized European Option Price
Call:
$$C = S D_b N(d_1) - K D_r N(d_2)$$
Put:
$$P = C - S D_b + K D_r$$
Equivalently:
$$P = K D_r N(-d_2) - S D_b N(-d_1)$$
At expiry, the model returns intrinsic value:
$$C_T = \max(S-K, 0)$$$$P_T = \max(K-S, 0)$$
Black-Scholes-Merton
For dividend-paying equity options:
$$b = r - q$$
Therefore:
$$D_b = e^{-qT}$$
Call:
$$C = S e^{-qT} N(d_1) - K e^{-rT} N(d_2)$$
Put:
$$P = K e^{-rT}N(-d_2) - S e^{-qT}N(-d_1)$$
Discrete Cash Dividend Approximations
Let the applicable deterministic dividend schedule be
$(D_i,t_i)_{i=1}^{n}$, ordered so that:
$$0 < t_1 < \dots < t_n < T$$
The implementation includes only ex-dividend timestamps strictly between the
valuation timestamp and expiration. Dividend amounts must be positive and
finite. These models require an EquityFrame with $q=0$ because the discrete
schedule is the complete dividend input.
The time-zero present value of the applicable schedule is:
$$PV_D = \sum_{i=1}^{n} D_i e^{-rt_i}$$
Except for Bos-Vandermark, the adjusted or escrowed spot is:
$$S^* = S-PV_D$$
The adjusted inputs are passed to the no-continuous-yield
Black-Scholes-Merton formula:
The Simple model retains the original strike and scales volatility by the
ratio of original to escrowed spot:
$$K^*=K, \qquad
\sigma^*=\sigma\frac{S}{S-PV_D}$$
The method is $O(n)$. Discounting reflects dividend dates, but the volatility
scaling does not otherwise distinguish an early dividend from a late one.
Haug-Haug adjustment (Vol2)
Define $t_0=0$ and the remaining dividend PV immediately before interval $j$:
$$R_j=\sum_{k=j}^{n}D_k e^{-rt_k}$$
For the interval $(t_{j-1},t_j)$, Haug-Haug uses:
$$\sigma_j=\sigma\frac{S}{S-R_j}$$
After the final dividend the interval volatility returns to the original
$\sigma$. The time-weighted annualized variance is:
The adjusted spot is $S-PV_D$, the strike is unchanged, and the double sum
makes this implementation $O(n^2)$.
Bos-Vandermark spot-strike adjustment
Bos-Vandermark splits each discounted dividend linearly according to its
position within the option lifetime. The near and far present-value components
are:
An early dividend is assigned mostly to the spot adjustment, whereas a late
dividend is assigned mostly to the strike adjustment. The split is evaluated
in one pass, so the method is $O(n)$.
Numerical Greeks for discrete-dividend models
The library bumps the original, unadjusted market inputs and then recomputes
the complete dividend adjustment. This is different from applying analytical
Black-Scholes Greeks only to frozen adjusted inputs.
Vega uses the valid local volatility interval, including its actual width when
the lower endpoint is clipped at PricingValidation.MIN_VOLATILITY. Theta is
a forward timestamp difference:
at most one calendar day and at most half the remaining time
Only dividends applicable after the bumped timestamp are retained. Theta can
therefore be non-smooth when the bump crosses an ex-dividend timestamp.
Complexity and limitations
Model
Time
Additional space
Simple
$O(n)$
$O(n)$ shared schedule preparation
Haug-Haug
$O(n)$
$O(n)$ shared schedule preparation
Bos-Gairat-Shepeleva
$O(n^2)$
$O(n)$ shared schedule preparation
Bos-Vandermark
$O(n)$
$O(n)$ shared schedule preparation
All four methods are analytical approximations, not exact discrete-jump
solutions. Their accuracy can deteriorate for large dividends, long maturities
or dense schedules. The reference suite includes published Haug-Haug and
Bos-Vandermark values from Haug, Haug and Lewis (2003). The original methods
are described in:
Bos and Vandermark, “Finessing Fixed Dividends,” Risk 15(9), 2002.
Bos, Gairat and Shepeleva, “Dealing with Discrete Dividends,” Risk 16(1),
2003.
Haug, Haug and Lewis, “Back to Basics: A New Approach to the Discrete
Dividend Problem,” Wilmott, 2003.
Full bibliographic details and the mapping from each source to the
implementation and reference tests are maintained in
REFERENCES.md.
Black-76
For futures options, TheGreekLab treats the futures price as the model input:
$$S = F$$$$b = 0$$
Therefore:
$$D_b = e^{-rT}$$
The flat-rate Black76 model is the FuturesFrame specialization of the
forward Black formula above: $D_r(T)=e^{-rT}$ and the supplied futures price is
$F(T)$. ForwardBlack76 generalizes the same price formula to curve inputs.
Black-76 rho is specialized because $d_1$ and $d_2$ do not depend on $r$ when
$F$ is supplied directly:
$$\rho = -T \cdot V$$
where $V$ is the option price.
Garman-Kohlhagen
For FX options:
$$b = r - r_f$$
Therefore:
$$D_b = e^{-r_f T}$$
Call:
$$C = S e^{-r_f T}N(d_1) - K e^{-rT}N(d_2)$$
Put:
$$P = K e^{-rT}N(-d_2) - S e^{-r_f T}N(-d_1)$$
European Greeks
The following formulas are implemented for the generalized Black-Scholes engine.
Delta
Call:
$$\Delta_C = D_b N(d_1)$$
Put:
$$\Delta_P = D_b(N(d_1)-1)$$
Gamma
Same for calls and puts:
$$\Gamma = \frac{D_b n(d_1)}{S\sigma\sqrt{T}}$$
Vega
Vega is returned per 1.00 volatility unit:
$$\nu = S D_b \sqrt{T} n(d_1)$$
Per percentage point:
$$\nu_{1\%} = 0.01 \nu$$
Rho
Call:
$$\rho_C = K T e^{-rT} N(d_2)$$
Put:
$$\rho_P = -K T e^{-rT} N(-d_2)$$
For Black-76:
$$\rho = -T V$$
Theta
The implementation returns annualized theta by default.
The Bjerksund-Stensland 2002 model is a closed-form approximation for vanilla
American options. The implementation follows the generalized cost-of-carry
notation used by Haug.
The approximation is evaluated directly for calls. Puts use put-call symmetry:
BivariateNormal.cdf() evaluates $N_2$ using the native Fortran pbivnorm
routine through the Java Foreign Function and Memory API. The returned native
probability is required to be finite and is clamped to $[0,1]$ to remove small
floating-point excursions.
Call value
If $S\ge I_2$, immediate exercise gives $S-K$. Otherwise the approximation is:
Because this is an approximation, the implementation enforces the
no-arbitrage lower bound:
$$V_A \ge \max(V_E,V_{\mathrm{intrinsic}})$$
The same lower bound is returned if $\beta$, an exercise boundary, a boundary
coefficient or the final approximation becomes non-finite. At expiry the model
returns intrinsic value directly.
The reference tests reproduce all 36 Bjerksund-Stensland 2002 values from Haug
table 3-2 for calls and puts on futures.
Numerical Greeks
The Bjerksund-Stensland implementation exposes delta, gamma, vega, theta and
rho through bump-and-revalue differentiation of the complete bounded price
function. For a model value $V$ and bump $h$:
The volatility interval is shortened at
PricingValidation.MIN_VOLATILITY, and the actual interval width is used in
the denominator. Theta advances the market-data timestamp:
at most one calendar day and at most half the remaining time
These derivatives include the intrinsic-value and European lower bounds.
Consequently, values close to an exercise boundary, expiry or fallback regime
may depend more strongly on the selected bump because the bounded
approximation is only piecewise smooth.
Leisen-Reimer uses the same backward-induction structure but transforms
Black-Scholes $d_1$ and $d_2$ into tree probabilities using Peizer-Pratt
method 2. The implementation requires an odd number of steps.
It also validates that all three probabilities are finite, belong to
$[0,1]$, and sum to one within numerical tolerance. The implementation limits
$N$ to $10,000$ so that array sizing cannot overflow and the $O(N^2)$
rollback remains bounded.
At level $i$, node $j\in{0,\ldots,2i}$ has spot price:
$$S_{i,j}=S u^{j-i}$$
For $z=1$ for a call and $z=-1$ for a put, the terminal payoff is:
European contracts use $V_{i,j}=C_{i,j}$. American contracts compare the
continuation value with immediate exercise:
$$V_{i,j}=\max\left(z(S_{i,j}-K),C_{i,j}\right)$$
Trinomial Greeks
Price, delta, gamma and theta are obtained during one backward induction. Let
$V_d$, $V_m$ and $V_u$ denote the down, middle and up values at the first tree
level, with corresponding spots $S/u$, $S$ and $Su$.
If the lower volatility is outside the supported domain, the implementation
uses the forward difference $(V(\sigma+h_\sigma)-V(\sigma))/h_\sigma$.
Rho uses a central one-basis-point bump, $h_r=10^{-4}$:
$$\rho\approx\frac{V(r+h_r)-V(r-h_r)}{2h_r}$$
Vega and rho are reported per unit change in volatility and rate. Immutable
model copies exposed through BumpableOptionModel provide the same spot,
volatility, rate and timestamp scenario repricing used by these calculations.
For each central difference, both bumped valuations use the same depth. The
implementation raises that common depth when either bumped parameter set would
otherwise violate the trinomial probability-positivity condition.
Binomial Greeks
Binomial model Greeks are finite-difference based. The implementation uses:
The common solver accepts any VolatilityPricer, brackets a sign change and
uses Brent's method to solve for $\sigma$. This keeps root finding independent
of the analytical, lattice or approximation model used to produce
$V_{\text{model}}$.
The European convenience overload additionally:
validates European exercise style,
checks model-free no-arbitrage bounds,
creates a Brenner-Subrahmanyan-style initial guess.
Invalid trial points at the edge of a lattice model's numerical domain are
skipped while the common solver locates a valid bracket.
The diagnostic API reports the terminal status, converged or last valid
volatility, reproduced model price, residual pricing error and total number of
bracket/root iterations. Compatibility overloads expose only a converged
volatility as OptionalDouble.
The volatility search interval is:
$$\sigma \in [10^{-6}, 10]$$
The initial guess uses a Brenner-Subrahmanyan-style approximation: