Options¶
See the Options guide for a narrative walkthrough.
Pricing¶
pricing
¶
Option pricing in pure JAX.
The Black-Scholes-Merton price is a plain differentiable function, so the Greeks
module gets every sensitivity from jax.grad without duplicating formulas. A
jitted Newton solver recovers implied volatility, and a binomial (Cox-Ross-
Rubinstein) lattice prices American-style options.
Convention: +1 for calls, -1 for puts (is_call boolean helpers are
provided for readability at call sites).
black_scholes_price
¶
black_scholes_price(
spot: Array,
strike: Array,
ttm: Array,
vol: Array,
rate: Array = 0.0,
div: Array = 0.0,
is_call: bool = True,
) -> Array
Black-Scholes-Merton price of a European option.
All array arguments broadcast, so this prices whole option chains at once.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
spot
|
Array
|
Underlying price and strike. |
required |
strike
|
Array
|
Underlying price and strike. |
required |
ttm
|
Array
|
Time to maturity in years. |
required |
vol
|
Array
|
Annualized volatility. |
required |
rate
|
Array
|
Continuous risk-free rate and dividend yield. |
0.0
|
div
|
Array
|
Continuous risk-free rate and dividend yield. |
0.0
|
is_call
|
bool
|
|
True
|
Source code in src/jaxfolio/options/pricing.py
price_call_chain
¶
price_call_chain(
spot: float,
strikes: Array,
ttms: Array,
vols: Array,
rate: float = 0.0,
div: float = 0.0,
) -> Array
Vectorized call prices across a chain of (strike, ttm, vol) triples.
ttms and vols may be scalars (broadcast to every strike) or arrays
aligned with strikes.
Source code in src/jaxfolio/options/pricing.py
implied_volatility
¶
implied_volatility(
price: float,
spot: float,
strike: float,
ttm: float,
rate: float = 0.0,
div: float = 0.0,
is_call: bool = True,
*,
initial_vol: float = 0.2,
max_iter: int = 50,
tol: float = 1e-08,
) -> Array
Recover implied volatility via jitted Newton iterations.
Vega (the derivative of price w.r.t. vol) comes from jax.grad on the
pricer, so the Newton step needs no hand-coded derivative. Returns NaN
when no volatility reproduces price to within tol (e.g. a price below
intrinsic value or a deep-OTM option with vanishing vega), so callers can
distinguish "no solution" from a genuinely tiny implied vol.
Source code in src/jaxfolio/options/pricing.py
binomial_american
¶
binomial_american(
spot: float,
strike: float,
ttm: float,
vol: float,
rate: float = 0.0,
div: float = 0.0,
is_call: bool = True,
*,
steps: int = 256,
dividends: list[tuple[float, float]] | None = None,
) -> Array
Cox-Ross-Rubinstein binomial price for an American-style option.
Backward induction with early-exercise checks at every node. steps
controls the lattice resolution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
div
|
float
|
Continuous dividend yield (always supported). |
0.0
|
dividends
|
list[tuple[float, float]] | None
|
Optional schedule of discrete cash dividends as |
None
|
Source code in src/jaxfolio/options/pricing.py
Greeks¶
greeks
¶
Option Greeks via JAX automatic differentiation.
Rather than re-deriving closed-form Greek formulas (and risking them drifting out
of sync with the pricer), we differentiate :func:black_scholes_price directly:
- delta = d price / d spot
- gamma = d^2 price / d spot^2
- vega = d price / d vol
- theta = -d price / d ttm (per-year; divide by 365 for per-calendar-day)
- rho = d price / d rate
All are vmap-friendly, so :func:chain_greeks returns every Greek across a
whole option chain in one call.
delta
¶
Delta: sensitivity of price to the underlying spot.
gamma
¶
Gamma: second derivative of price w.r.t. spot (same for calls and puts).
Source code in src/jaxfolio/options/greeks.py
vega
¶
Vega: sensitivity of price to volatility (per 1.00 change in vol).
Source code in src/jaxfolio/options/greeks.py
theta
¶
Theta: time decay (per year). Negative for most long options.
Source code in src/jaxfolio/options/greeks.py
rho
¶
Rho: sensitivity of price to the risk-free rate.
all_greeks
¶
Return every first/second-order Greek for a single option as a dict.
Source code in src/jaxfolio/options/greeks.py
chain_greeks
¶
chain_greeks(
spot: float,
strikes: Array,
ttms: Array,
vols: Array,
rate: float = 0.0,
div: float = 0.0,
is_call: bool = True,
) -> dict[str, Array]
Vectorized Greeks across a chain of (strike, ttm, vol) triples.
Returns a dict of arrays (one entry per Greek), each aligned with the input
chain. Built from vmap over the single-option autodiff Greeks.
Source code in src/jaxfolio/options/greeks.py
Strategies¶
strategies
¶
Multi-leg options strategies: payoffs, P&L, and Greeks.
An :class:OptionLeg describes a single position (call/put, long/short, strike,
expiry, quantity). An :class:OptionStrategy is a collection of legs (optionally
plus a stock leg) with helpers to evaluate payoff at expiry, mark-to-model P&L at
a horizon, net Greeks, and the break-even points. A library of preset
constructors builds the common structures (covered call, collar, spreads,
straddle, iron condor, butterfly, calendar).
OptionLeg
dataclass
¶
OptionLeg(
kind: str,
strike: float,
quantity: float = 1.0,
expiry: float = 0.25,
premium: float | None = None,
)
A single option position.
Attributes:
| Name | Type | Description |
|---|---|---|
kind |
str
|
|
strike |
float
|
Strike price. |
quantity |
float
|
Signed contract count — positive = long, negative = short. |
expiry |
float
|
Time to expiry in years (used for mark-to-model and Greeks). |
premium |
float | None
|
Premium paid (long) or received (short) per contract. If |
StockLeg
dataclass
¶
A linear underlying position held alongside options (e.g. covered call).
OptionStrategy
dataclass
¶
A named collection of option (and optional stock) legs.
payoff_at_expiry
¶
Total strategy P&L at expiry across a grid of terminal spot prices.
Source code in src/jaxfolio/options/strategies.py
net_premium
¶
Net premium: negative = net debit (paid), positive = net credit.
value_at
¶
value_at(
spot: float,
vol: float,
rate: float = 0.0,
div: float = 0.0,
ttm_shift: float = 0.0,
) -> float
Mark-to-model value of all option legs at a horizon.
ttm_shift is subtracted from each leg's expiry to advance time (e.g.
ttm_shift=1/12 marks the book one month forward).
Source code in src/jaxfolio/options/strategies.py
pnl_at
¶
pnl_at(
spot: float,
vol: float,
rate: float = 0.0,
div: float = 0.0,
ttm_shift: float = 0.0,
) -> float
Mark-to-model P&L relative to the entry cost of the option legs.
Source code in src/jaxfolio/options/strategies.py
greeks
¶
Net position Greeks (sum of per-leg Greeks weighted by quantity).
Source code in src/jaxfolio/options/strategies.py
break_evens
¶
Approximate break-even spot prices (payoff sign changes).
Source code in src/jaxfolio/options/strategies.py
covered_call
¶
covered_call(
spot: float,
strike: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long stock + short call: income overlay that caps upside.
Source code in src/jaxfolio/options/strategies.py
protective_put
¶
protective_put(
spot: float,
strike: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long stock + long put: downside insurance.
Source code in src/jaxfolio/options/strategies.py
collar
¶
collar(
spot: float,
put_strike: float,
call_strike: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long stock + long put + short call: bounded payoff, often near-zero cost.
Source code in src/jaxfolio/options/strategies.py
bull_call_spread
¶
bull_call_spread(
spot: float,
lower: float,
upper: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long lower-strike call + short upper-strike call: capped bullish debit.
Source code in src/jaxfolio/options/strategies.py
bear_put_spread
¶
bear_put_spread(
spot: float,
upper: float,
lower: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long upper-strike put + short lower-strike put: capped bearish debit.
Source code in src/jaxfolio/options/strategies.py
straddle
¶
straddle(
spot: float,
strike: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long call + long put at the same strike: a bet on large moves either way.
Source code in src/jaxfolio/options/strategies.py
strangle
¶
strangle(
spot: float,
put_strike: float,
call_strike: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long OTM put + long OTM call: cheaper volatility bet than a straddle.
Source code in src/jaxfolio/options/strategies.py
iron_condor
¶
iron_condor(
spot: float,
put_long: float,
put_short: float,
call_short: float,
call_long: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Short strangle wrapped in long protective wings: a range-bound credit trade.
Strikes must satisfy put_long < put_short < call_short < call_long.
Source code in src/jaxfolio/options/strategies.py
butterfly
¶
butterfly(
spot: float,
lower: float,
middle: float,
upper: float,
*,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Long butterfly with calls: peak payoff pinned at the middle strike.
Source code in src/jaxfolio/options/strategies.py
calendar_spread
¶
calendar_spread(
spot: float,
strike: float,
*,
near_expiry: float = 0.08,
far_expiry: float = 0.33,
vol: float = 0.25,
rate: float = 0.0,
) -> OptionStrategy
Short near-dated + long far-dated call at one strike: a theta/vega play.
Because the legs have different expiries, use :meth:OptionStrategy.pnl_at
(mark-to-model) rather than expiry payoff to analyze this structure.
Source code in src/jaxfolio/options/strategies.py
Overlays¶
overlay
¶
Options overlays on top of an optimized equity portfolio.
Bridges the optimizer output (:class:PortfolioResult) and the options layer:
apply a covered-call or collar overlay to the largest holdings, aggregate the net
Greeks of the resulting book, and produce a combined at-expiry payoff for the
overlaid position. This is what makes the two halves of the library compose.
OverlayBook
dataclass
¶
OverlayBook(
strategies: dict[str, OptionStrategy],
weights: dict[str, float],
spots: dict[str, float],
)
A basket of per-asset option strategies scaled by portfolio weights.
Attributes:
| Name | Type | Description |
|---|---|---|
strategies |
dict[str, OptionStrategy]
|
Mapping |
weights |
dict[str, float]
|
Portfolio weight of each overlaid asset (used to scale payoffs/Greeks). |
spots |
dict[str, float]
|
Reference spot price per asset. |
net_greeks
¶
Weighted net Greeks across the whole overlay book.
Source code in src/jaxfolio/options/overlay.py
payoff_curve
¶
Weighted portfolio P&L across a grid of relative spot shocks.
shock_grid is a multiplier applied to every asset's spot (e.g.
np.linspace(0.7, 1.3, 200) sweeps -30% to +30%). Returns the
weight-scaled aggregate P&L of the overlaid holdings.
Source code in src/jaxfolio/options/overlay.py
covered_call_overlay
¶
covered_call_overlay(
result: PortfolioResult,
spots: dict[str, float],
*,
top_n: int = 5,
moneyness: float = 1.05,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OverlayBook
Write covered calls on the top_n largest holdings.
moneyness sets the call strike relative to spot (1.05 = 5% OTM).
Assets without a provided spot price are skipped.
Source code in src/jaxfolio/options/overlay.py
collar_overlay
¶
collar_overlay(
result: PortfolioResult,
spots: dict[str, float],
*,
top_n: int = 5,
put_moneyness: float = 0.95,
call_moneyness: float = 1.05,
expiry: float = 0.25,
vol: float = 0.25,
rate: float = 0.0,
) -> OverlayBook
Wrap the top_n largest holdings in protective collars.
Bounds each holding's outcome between put_moneyness and
call_moneyness times spot — capping both tail loss and upside.
Source code in src/jaxfolio/options/overlay.py
Volatility surface¶
surface
¶
Implied-volatility surfaces.
A :class:VolSurface turns a discrete grid of implied vols (strikes x expiries)
into a callable iv(strike, ttm) by interpolating in total variance
(w = iv^2 * ttm) across time and across strike — the representation in which
a well-behaved surface is smooth and arbitrage checks are natural. Build one from
market option prices (:meth:VolSurface.from_chain, which inverts prices with the
existing Newton :func:~jaxfolio.options.pricing.implied_volatility) or fit a
parametric raw-SVI slice per expiry (:meth:VolSurface.fit_svi).
The surface reuses the package pricer/Greeks: :meth:VolSurface.price and
:meth:VolSurface.greeks read the vol off the surface and call
:func:~jaxfolio.options.pricing.black_scholes_price /
:func:~jaxfolio.options.greeks.all_greeks, so nothing is re-derived.
Arbitrage diagnostics (:meth:VolSurface.arbitrage_report) flag butterfly
violations (call price must be convex in strike) and calendar violations
(total variance must not decrease with maturity).
SVIParams
dataclass
¶
Raw-SVI parameters for a single expiry slice (Gatheral, 2004).
Total variance as a function of log-moneyness k = log(strike / spot)::
w(k) = a + b * (rho * (k - m) + sqrt((k - m)^2 + sigma^2))
Attributes:
| Name | Type | Description |
|---|---|---|
a |
float
|
Vertical level of the variance smile (>= 0 region after fit). |
b |
float
|
Slope / wing steepness ( |
rho |
float
|
Skew, |
m |
float
|
Horizontal shift of the smile minimum. |
sigma |
float
|
Curvature at the minimum ( |
total_variance
¶
Total implied variance w(k) at log-moneyness k.
Source code in src/jaxfolio/options/surface.py
VolSurface
dataclass
¶
VolSurface(
strikes: ndarray,
ttms: ndarray,
iv_grid: ndarray,
spot: float,
rate: float = 0.0,
div: float = 0.0,
svi: list[SVIParams] | None = None,
)
An interpolated implied-volatility surface.
Attributes:
| Name | Type | Description |
|---|---|---|
strikes |
ndarray
|
Strictly increasing strike grid, shape |
ttms |
ndarray
|
Strictly increasing time-to-maturity grid (years), shape |
iv_grid |
ndarray
|
Implied vols, shape |
spot |
float
|
Reference spot used for log-moneyness. |
rate, div |
Continuous risk-free rate and dividend yield used when pricing off the surface. |
|
svi |
list[SVIParams] | None
|
Optional per-expiry SVI slices (populated by :meth: |
from_iv_grid
classmethod
¶
from_iv_grid(
strikes, ttms, iv_grid, spot, *, rate=0.0, div=0.0
) -> VolSurface
Build directly from a known implied-vol grid.
from_chain
classmethod
¶
from_chain(
spot: float,
strikes,
ttms,
prices: ndarray,
*,
rate: float = 0.0,
div: float = 0.0,
is_call: bool = True,
) -> VolSurface
Invert a grid of market option prices into an implied-vol surface.
prices has shape (len(ttms), len(strikes)). Each entry is inverted
with the Newton solver; non-invertible quotes (below intrinsic) become
NaN and are then filled by nearest-valid interpolation along the smile.
Source code in src/jaxfolio/options/surface.py
fit_svi
¶
fit_svi() -> VolSurface
Return a copy whose smiles are replaced by fitted raw-SVI slices.
Each expiry's (log-moneyness, total-variance) points are fit with
:func:calibrate_svi; the grid is re-evaluated from the fits so iv
becomes smooth and extrapolates sensibly beyond the quoted strikes.
Source code in src/jaxfolio/options/surface.py
iv
¶
Interpolated implied vol at (strike, ttm).
Interpolates in total variance across maturity (flat-forward beyond the quoted range) and along the smile at each bracketing expiry.
Source code in src/jaxfolio/options/surface.py
price
¶
Black-Scholes price of an option, using the surface's implied vol.
Source code in src/jaxfolio/options/surface.py
greeks
¶
Full Greeks of an option, using the surface's implied vol.
Source code in src/jaxfolio/options/surface.py
arbitrage_report
¶
Check static no-arbitrage conditions on the surface.
Returns a dict with butterfly_ok (call price convex in strike at each
expiry), calendar_ok (total variance non-decreasing in maturity at
each strike), an overall arbitrage_free flag, and the lists of
offending (ttm, strike) / (strike, ttm_pair) locations.
Source code in src/jaxfolio/options/surface.py
is_arbitrage_free
¶
Convenience boolean: arbitrage_report(...)['arbitrage_free'].
calibrate_svi
¶
calibrate_svi(k: ndarray, w: ndarray) -> SVIParams
Least-squares-fit raw-SVI parameters to observed total variances.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
k
|
ndarray
|
Log-moneyness of each observation. |
required |
w
|
ndarray
|
Observed total implied variance ( |
required |
Returns:
| Type | Description |
|---|---|
SVIParams
|
The fitted slice. Bounds keep the fit in the no-vertical-arbitrage region
( |
Source code in src/jaxfolio/options/surface.py
Execution framework¶
Research simulation, not live trading
The execution framework simulates fills against a modeled price with transaction costs. It is a backtesting / research tool — not a live broker, order-management system, or exchange connection. See the disclaimer.
costs
¶
Transaction-cost models for the options execution simulator.
A :class:CostModel turns a mid price into a realistic fill price and a
commission. Costs are intentionally simple and transparent (per-contract
commission + a proportional half-spread / slippage in basis points) — enough to
make simulated P&L reflect frictions without pretending to model a real venue's
microstructure.
CostModel
dataclass
¶
CostModel(
commission_per_contract: float = 0.65,
slippage_bps: float = 5.0,
min_commission: float = 0.0,
)
A commission + slippage model.
Attributes:
| Name | Type | Description |
|---|---|---|
commission_per_contract |
float
|
Flat cash commission charged per contract traded (per unit of absolute quantity), applied on both entry and exit. |
slippage_bps |
float
|
Proportional slippage in basis points of the mid price, modeling the
half-spread / market impact. A buy fills at |
min_commission |
float
|
Floor applied to the per-trade commission. |
fill_price
¶
Fill price for a side trade (+1 buy, -1 sell) at mid.
Slippage always works against the trader: buys fill above mid, sells below. A negative mid is floored at 0 (options cannot be worth < 0).
Source code in src/jaxfolio/options/execution/costs.py
commission
¶
Commission for trading quantity contracts (sign-insensitive).
book
¶
Order/fill accounting and a mark-to-market option book.
This is the core of the execution layer: an :class:ExecutionSimulator prices an
option at Black-Scholes mid, applies a :class:~jaxfolio.options.execution.costs.CostModel
to get a fill, updates an :class:OptionBook of :class:Position objects and a
cash balance, and can mark the whole book to market at any spot/vol/horizon.
Scope: this simulates fills against a modeled price. It is a research /
backtesting tool — not a live broker, order-management system, or connection
to any exchange. See DISCLAIMER.md.
Instrument
dataclass
¶
A vanilla option contract identity (what is traded).
Order
dataclass
¶
Order(instrument: Instrument, quantity: float)
An instruction to trade quantity contracts of instrument.
quantity is signed: positive = buy (long), negative = sell (short).
Fill
dataclass
¶
Fill(
instrument: Instrument,
quantity: float,
mid: float,
fill_price: float,
commission: float,
)
The realized result of executing an :class:Order.
cash_flow
property
¶
Signed cash impact: buying pays (negative), selling receives (positive).
Position
dataclass
¶
Position(
instrument: Instrument,
quantity: float = 0.0,
avg_price: float = 0.0,
)
A net position in one instrument, tracking quantity and average cost.
apply
¶
apply(fill: Fill) -> None
Fold a fill into the position, updating the average entry price.
Source code in src/jaxfolio/options/execution/book.py
market_value
¶
Mark-to-model value of the position at a horizon.
Source code in src/jaxfolio/options/execution/book.py
OptionBook
dataclass
¶
OptionBook(positions: dict[tuple, Position] = dict())
A collection of option positions keyed by instrument identity.
market_value
¶
Total mark-to-model value of all open positions.
net_greeks
¶
Net position Greeks across the book.
Source code in src/jaxfolio/options/execution/book.py
ExecutionSimulator
dataclass
¶
ExecutionSimulator(
cost_model: CostModel = CostModel(),
rate: float = 0.0,
div: float = 0.0,
cash: float = 0.0,
book: OptionBook = OptionBook(),
fills: list[Fill] = list(),
)
Executes orders against a modeled mid price and books the results.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cost_model
|
CostModel
|
The commission/slippage model applied to every fill. |
CostModel()
|
rate
|
float
|
Rate and dividend yield used both to price the option mid and to mark the book. |
0.0
|
div
|
float
|
Rate and dividend yield used both to price the option mid and to mark the book. |
0.0
|
cash
|
float
|
Starting cash balance (updated by every fill's cash flow). |
0.0
|
mid_price
¶
mid_price(
instrument: Instrument,
spot: float,
vol: float,
ttm_shift: float = 0.0,
) -> float
Black-Scholes mid price of instrument at the given market state.
Source code in src/jaxfolio/options/execution/book.py
execute
¶
Execute order at the current market state, updating book and cash.
Source code in src/jaxfolio/options/execution/book.py
equity
¶
Total equity = cash + mark-to-market value of the option book.
rolling
¶
Walk-forward simulation of a rolled option overlay.
Ties the execution layer to a price path: hold the underlying and repeatedly
write / roll a short call (a covered-call overlay), marking the combined book to
market every step and charging transaction costs at each roll. This is the
options analogue of the walk-forward equity backtester in
:mod:jaxfolio.backtest — a research simulation, not live trading.
OverlaySimResult
dataclass
¶
OverlaySimResult(
equity: ndarray,
stock_only: ndarray,
times: ndarray,
total_costs: float,
n_rolls: int,
)
Result of a rolled-overlay simulation.
Attributes:
| Name | Type | Description |
|---|---|---|
equity |
ndarray
|
Total equity (stock + option book + cash) at each step. |
stock_only |
ndarray
|
Buy-and-hold stock equity at each step, for comparison. |
times |
ndarray
|
Elapsed time in years at each step. |
total_costs |
float
|
Total commissions paid across all rolls. |
n_rolls |
int
|
Number of times the call was written/rolled. |
simulate_covered_call_roll
¶
simulate_covered_call_roll(
prices,
*,
moneyness: float = 1.05,
tenor: float = 0.25,
roll_every: int = 21,
vol: float = 0.25,
shares: float = 1.0,
cost_model: CostModel | None = None,
rate: float = 0.0,
div: float = 0.0,
periods_per_year: int = 252,
) -> OverlaySimResult
Simulate a rolling covered-call overlay over a price path.
Holds shares of the underlying throughout and writes one short call per
share at moneyness * spot with time-to-expiry tenor. Every
roll_every steps the existing call is bought back and a fresh one written
at the current spot, incurring costs from cost_model.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prices
|
1-D array (or Polars Series) of underlying prices along the path. |
required | |
moneyness
|
float
|
Call strike relative to spot ( |
1.05
|
tenor
|
float
|
Time-to-expiry (years) of each written call. |
0.25
|
roll_every
|
int
|
Number of steps between rolls. Should satisfy
|
21
|
vol
|
float
|
Flat implied vol used to price and mark the options. |
0.25
|
shares
|
float
|
Underlying shares held (and calls written) — the overlay is fully covered. |
1.0
|
Returns:
| Type | Description |
|---|---|
OverlaySimResult
|
Equity path of the overlaid book, the stock-only benchmark, costs, and the number of rolls. |