edgekit

edgekit.options

Black-Scholes — European option pricing, the full first-order Greeks, and an implied-volatility solver, with a continuous dividend / carry yield q throughout. Enough to price, hedge, and back out vol without pulling in a derivatives library.

What's inside. The two normal-distribution primitives (norm_cdf, norm_pdf) the formulas are built on, the price itself (bs_price), the Greek bundle (bs_greeks), and a Newton/bisection implied-vol root-finder (implied_vol). Calls and puts are selected with kind="call" / "put"; the dividend yield q also serves as the foreign rate for FX or the convenience-adjusted carry for a future.

!Know your Greek units before you size a hedge

The Greeks are reported in raw Black-Scholes units, not the rescaled forms desks often quote:

  • delta — change in option value per $1 move in spot.
  • gamma — change in delta per $1 move in spot (i.e. per $1²).
  • vega — per 1.00 (100 vol-points) change in sigma; divide by 100 for the per-1%-vol figure traders usually quote.
  • theta — per year; divide by 365 for per-calendar-day decay.
  • rho — per 1.00 (100 bp × 100) change in the rate.

Distribution primitives#

norm_cdf#

Standard-normal cumulative distribution function — the N(·) in the Black-Scholes price. Vectorised over array input.

norm_cdf(x) -> float | np.ndarray
  • x — a scalar or array of standardised values.

Returns: the cumulative probability P(Z ≤ x), same shape as the input.

norm_pdf#

Standard-normal probability density function — the n(·) that appears in gamma and vega.

norm_pdf(x) -> float | np.ndarray
  • x — a scalar or array of standardised values.

Returns: the density at x, same shape as the input.

Pricing#

bs_price#

The Black-Scholes-Merton price of a European call or put with continuous yield q. The core valuation everything else in the module supports.

bs_price(S, K, t, r, sigma, kind="call", q=0.0) -> float
ParamTypeDefaultMeaning
SfloatSpot price of the underlying.
KfloatStrike price.
tfloatTime to expiry in years.
rfloatContinuously-compounded risk-free rate.
sigmafloatVolatility (annualised, e.g. 0.20 for 20%).
kindstr"call""call" or "put".
qfloat0.0Continuous dividend / carry yield.

Returns: a float option premium in the underlying's currency.

import edgekit as ek
ek.options.bs_price(S=100, K=105, t=0.5, r=0.03, sigma=0.20, kind="call")

Greeks & implied vol#

bs_greeks#

All five first-order Greeks in one call — delta, gamma, vega, theta, rho — for the same option as bs_price. Read the units callout above before you size a hedge off these.

bs_greeks(S, K, t, r, sigma, kind="call", q=0.0) -> dict
ParamTypeDefaultMeaning
SfloatSpot price.
KfloatStrike.
tfloatTime to expiry (years).
rfloatRisk-free rate.
sigmafloatVolatility.
kindstr"call""call" or "put".
qfloat0.0Dividend / carry yield.

Returns: a dict with keys "delta" (per $1 spot), "gamma" (per $1² spot), "vega" (per 1.00 vol — ÷100 for per-1%), "theta" (per year — ÷365 for per-day), and "rho" (per 1.00 rate).

g = ek.options.bs_greeks(S=100, K=100, t=0.25, r=0.03, sigma=0.20)
g["delta"]
g["vega"] / 100     # per 1% vol move
g["theta"] / 365    # per calendar day

implied_vol#

Back out the volatility that reprices a European option to an observed price — a Newton root-find with a bisection fallback. The inverse of bs_price: feed it a market premium, get the market's vol view.

implied_vol(price, S, K, t, r, kind="call", q=0.0, tol=1e-6, max_iter=100) -> float
ParamTypeDefaultMeaning
pricefloatObserved option premium to match.
SfloatSpot price.
KfloatStrike.
tfloatTime to expiry (years).
rfloatRisk-free rate.
kindstr"call""call" or "put".
qfloat0.0Dividend / carry yield.
tolfloat1e-6Price convergence tolerance.
max_iterint100Maximum solver iterations.

Returns: a float implied volatility. Returns nan when price is below intrinsic value (no real vol reproduces it).

iv = ek.options.implied_vol(price=6.20, S=100, K=100, t=0.25, r=0.03, kind="call")
# nan if price < max(S - K, 0) discounted (below intrinsic)

See also#