options-greeks
v1.0.0
Published
Black-Scholes-Merton (with dividend yield) and Black-76 option prices, greeks (delta, gamma, vega, theta, rho, vanna, charm, vomma) and a robust implied-volatility solver. Double-precision normal CDF, zero dependencies.
Maintainers
Readme
options-greeks
European option prices, greeks and implied volatility: Black-Scholes-Merton with a continuous dividend yield (stocks, indices, FX) and Black-76 (futures and forwards), first-order greeks (delta, gamma, vega, theta, rho), the second-order vanna, charm and vomma, and a safeguarded implied-volatility solver that converges from 1-hour to 10-year expiries and from deep in- to deep out-of-the-money. Built on a double-precision normal CDF. Typed, zero dependencies.
npm install options-greeksWhy
The Black-Scholes packages most people still install date from 2014. black-scholes, greeks and implied-volatility take no dividend yield, so they can't price index or FX options. black-scholes computes the normal CDF from a 100-term Taylor series, which loses precision in the tail: Φ(−7) is off by 5e-4 relative, Φ(−7.95) comes out negative, and an out-of-the-money call (S=100, K=150, T=0.1, σ=20%) is off by 8e-4. implied-volatility runs 100 bisection steps on that pricer and never signals failure. Give it a price below intrinsic and it returns 7.9e-32. Give it a price above the no-arbitrage bound and it returns 1.3e29. The newer option-pricing (2022) has a dividend yield but prices only, with no greeks or implied vol, and its CDF is accurate to only ~1e-7. This package does all of it, to double precision, with documented units and explicit failure values.
import { price, greeks, impliedVol } from "options-greeks";
// Hull's textbook example
price({ type: "call", spot: 42, strike: 40, rate: 0.1, vol: 0.2, time: 0.5 }); // 4.7594
price({ type: "put", spot: 42, strike: 40, rate: 0.1, vol: 0.2, time: 0.5 }); // 0.8086
// An index put with a dividend yield: everything in one pass
const g = greeks({ type: "put", spot: 5000, strike: 4800, rate: 0.045, dividendYield: 0.015, vol: 0.18, time: 30 / 365 });
g.price; // 28.288
g.delta; // -0.1934
g.gamma; // 0.001063
g.vega; // 393.02 per 1.00 of vol (g.vegaPerPoint 3.930 per vol point)
g.theta; // -400.08 per year (g.thetaPerDay -1.0961 per calendar day)
g.rho; // -81.80 per 1.00 of rate (g.rhoPerPoint -0.8180 per 1%)
g.vanna; // -1.2384
g.charm; // 1.1938 delta change per year
g.vomma; // 1534.94
// Black-76: pass `forward` instead of `spot`
price({ type: "call", forward: 72.5, strike: 75, rate: 0.04, vol: 0.35, time: 0.25 }); // 3.9485
// Implied volatility
impliedVol({ type: "call", spot: 42, strike: 40, rate: 0.1, time: 0.5, price: 4.76 }); // 0.20007
impliedVol({ type: "put", spot: 100, strike: 60, rate: 0.05, time: 7 / 365, price: 1e-12 }); // 0.5280
impliedVol({ type: "call", spot: 100, strike: 50, rate: 0.05, time: 1, price: 40 }); // NaN: below intrinsicAPI
Every function takes one object. Provide either spot (Black-Scholes-Merton) or forward (Black-76):
{ type: "call" | "put", spot: number, strike: number, rate: number, dividendYield?: number, vol: number, time: number }
{ type: "call" | "put", forward: number, strike: number, rate: number, vol: number, time: number }rate and dividendYield are continuously compounded decimals (0.05 = 5%; negative values are fine). For FX, pass the foreign rate as dividendYield. vol is a decimal (0.20 = 20%). time is in years; use calendar days / 365 to match the per-day theta.
Pricing and greeks
price(o)→ the option premium.greeks(o)→{ price, delta, gamma, vega, vegaPerPoint, theta, thetaPerDay, rho, rhoPerPoint, vanna, charm, vomma }, all in one pass.delta/gamma/vega/theta/rho/vanna/charm/vomma(aliasvolga) → each greek on its own.
| Greek | Definition | Units |
| --- | --- | --- |
| delta | ∂V/∂S (Black-76: ∂V/∂F) | per 1.00 of underlying |
| gamma | ∂²V/∂S² | per 1.00 of underlying, squared |
| vega | ∂V/∂σ | per 1.00 of vol (100 vol points); vegaPerPoint = vega / 100 |
| theta | −∂V/∂T, value lost as time passes | per year; thetaPerDay = theta / 365 (divide by 252 yourself for trading days) |
| rho | ∂V/∂r. Spot: dividend yield held fixed. Black-76: forward held fixed, so rho = −T·V | per 1.00 of rate; rhoPerPoint = rho / 100 |
| vanna | ∂²V/∂S∂σ = ∂delta/∂σ | per 1.00 of vol |
| charm | ∂delta/∂t = −∂²V/∂S∂T | delta change per year |
| vomma | ∂²V/∂σ² = ∂vega/∂σ | per 1.00 of vol, squared |
Degenerate inputs. At time = 0 or vol = 0 the price is the (discounted forward) intrinsic value. Delta is the payoff slope (±e^{−qT}, 0, or exactly ½·e^{−qT} at the money), and gamma, vanna and vomma are 0. At the money these are really Dirac spikes, which a number can't represent. Theta keeps its carry term. Vega at vol = 0 exactly at the forward keeps its σ → 0 limit, S·e^{−qT}·√T·φ(0).
Implied volatility
impliedVol(o)takes the same object withpricein place ofvoland returns σ.- Returns
0when the price equals the intrinsic value. This includes far out-of-the-money options whose value has underflowed to 0, and deep in-the-money options whose time value is below the rounding of the price. - Returns
NaNwhen the price is outside the no-arbitrage bounds: below intrinsic, or at or above S·e^{−qT} for a call (F·e^{−rT} for Black-76) or K·e^{−rT} for a put. It returnsNaNrather than throwing so that a stale quote doesn't abort a loop over a chain.
The solver reduces every input to the out-of-the-money option via put-call parity and normalises it, b(x, s) with x = ln(F/K) and s = σ√T. From an asymptotic initial guess it runs a safeguarded Newton iteration on ln b below the inflection point s = √(2|x|) and on ln(b_max − b) above it, inside a bracket that tightens every step. Any step that leaves the bracket or fails to reduce the residual becomes a bisection. That takes typically 5–10 iterations, about 1–2 µs.
- Returns
Utilities
normCdf(x),normPdf(x): the standard normal CDF and density.normCdfuses W. J. Cody's rational Chebyshev erfc (1969) and is accurate to ~1e-16 relative in both tails, not the 7-digit Abramowitz–Stegun 26.2.17 approximation.
Malformed input throws RangeError. That covers a non-finite or non-positive spot, forward or strike, a negative vol or time, an unknown type, both or neither of spot/forward, a dividendYield on a forward, a negative price, and time = 0 passed to impliedVol.
Correctness
Fixtures are generated by test/generate-fixtures.py:
- Published examples:
- From Hull's Options, Futures, and Other Derivatives: the S=42 / K=40 call and put (4.76 / 0.81), the S=49 / K=50 greek-letters example (delta 0.522, gamma 0.066, theta −4.31/yr, vega 12.1, rho 8.91), the dividend-yield index option (51.83) and the Black-76 crude-oil put (1.12).
- From Haug's The Complete Guide to Option Pricing Formulas: the Black-Scholes (2.1334), Merton (2.4648), Black-76 (1.7011) and Garman-Kohlhagen (0.0291) prices, and the delta (0.5946 / −0.3566), gamma (0.0278), vega (18.9358), theta (−31.1924) and rho (38.7325) examples.
- 50-digit reference: 1,400 BSM and Black-76 cases cover strikes from 0.5× to 2× spot, expiries from 1 day to 5 years, vols from 5% to 150%, and negative rates. They are checked against
mpmathat 50 digits, with every greek obtained by numerically differentiating the exact price rather than from a second copy of the formulas. All nine outputs agree to 1e-11 relative. - QuantLib: 672 cases agree with
AnalyticEuropeanEngineon price, delta, gamma, vega, theta and rho. In the far wings, where QuantLib returns prices such as −5.6e-15, this package matches the 50-digit reference instead. - Properties: put-call parity holds in prices and greeks. All eight greeks match central finite differences of the price, for spot and Black-76, calls and puts. Black-76 agrees with BSM at q = r.
- Normal CDF: matches 50-digit values from x = −37 to 8 to 2e-14 relative (2e-13 below −20).
- Implied vol: the round trip covers 12,960 cases (moneyness 0.5–2, T from 1 day to 5 years, σ from 1% to 300%, calls and puts, with rates and dividend yields), plus a 20,000-case randomised sweep (K/F 0.14–7.4, T from 2.4 hours to 10 years, σ from 0.5% to 500%). Wherever the price determines σ, it is recovered to 1e-12 relative. Where it doesn't, the error stays within the few-ulp-of-price limit, and the result always reprices exactly. Out-of-the-money prices as small as 1e-279 invert to 1e-11.
Out-of-the-money prices keep about 1e-9 relative accuracy even when they are tiny. The closed form subtracts two nearly equal tail probabilities there, so very short-dated, near-the-money wings lose a few digits, still far below any quoting precision.
Related
Pairs with options-expiration (listed expiry dates and time to expiry in years, ready for time), hagan-sabr (SABR smiles) and svi-vol-surface (arbitrage-free surfaces). Part of a markets toolkit: us-equity-market-calendar.
Author
Built by Moshe Malka — engineering leader in New York City. Studio work at Quentin.Code.
MIT © Moshe Malka
