optionsdesk
Options strategy analyzer for the terminal. Price options with BSM, Black-76, a CRR binomial tree and Monte Carlo, solve implied volatility, fit an SVI volatility surface to an option chain, build multi-leg strategies (YAML, one-line shorthand or templates) and get breakevens, max profit/loss, net Greeks, probability of profit, a SPAN-like margin estimate and a P&L risk matrix, with a Unicode payoff chart in the terminal and SVG/HTML/JSON export. Fully offline, built for NSE (NIFTY, BANKNIFTY) and US equity traders.
Features
- Pricing: Black-Scholes-Merton with continuous dividend yield and Greeks (delta, gamma, vega, theta/day, rho, vanna, charm, vomma); Black-76 for options on futures; CRR binomial for American options with early exercise (n and n+1 step averaging); Monte Carlo with antithetic variates and a 95% confidence interval.
- Implied volatility: safeguarded Newton-Raphson using vega, Brenner-Subrahmanyam start, bisection fallback, parity transform for deep ITM quotes, clear errors for prices that violate no-arbitrage bounds.
- Volatility surface: chain CSV in, IV per quote (mid), raw SVI fit per expiry (scipy least squares, multi-start), total-variance interpolation across expiries, butterfly (density), calendar and quote-convexity arbitrage checks.
- Strategies: legs from YAML, CLI shorthand (
"-1 C 24500 2026-11-27, +1 C 24700 2026-11-27") or 15 templates (spreads, iron condor, straddle, strangle, butterfly, calendar, ratio spreads, covered call, collar). - Analytics: payoff at expiry and T+n, breakevens via root finding, max profit/loss with unbounded detection, net Greeks, probability of profit (risk-neutral or real-world drift), expected value, margin scan, risk matrix.
- Output: rich tables, terminal chart, SVG and HTML export, JSON on every command.
- Contract specs: lot sizes and exercise style from a TOML file you can override.
Install
Requires Python 3.12.
cd optionsdesk
uv venv .venv
uv pip install -p .venv/bin/python -e '.[dev]'
source .venv/bin/activate # or call .venv/bin/optionsdesk directly
Quickstart
optionsdesk price -t call -S 42 -K 40 --days 182.5 -r 0.10 --vol 0.20 --greeks
optionsdesk smile examples/nifty_chain.csv -S 24650 -r 0.065 -q 0.013 --asof 2026-10-16
optionsdesk analyze examples/iron_condor.yaml --days 7,21
All numeric rates and volatilities are decimals (0.065 is 6.5%). Time to expiry is calendar days over 365. Valuation date defaults to today; pass --asof YYYY-MM-DD for reproducible runs. The examples below use --asof 2026-10-16.
Usage
Price an option
Hull's textbook example (S=42, K=40, r=10%, sigma=20%, six months): call 4.76.
$ optionsdesk price -t call -S 42 -K 40 --days 182.5 -r 0.10 --vol 0.20 --greeks
Model bsm
Price 4.7594
Greeks
Greek Value Unit
─────────────────────────────────────────
delta 0.7791 per 1 unit move
gamma 0.049963 per 1 unit move
vega 0.0881 per vol point
theta -0.0125 per calendar day
rho 0.1398 per 1% rate
vanna -0.009316 delta per vol point
charm -0.000064 delta per day
vomma 0.002128 vega per vol point
Other models: --model american (CRR tree, --steps, default 500), --model black76 (pass the futures price as -S), --model mc (--paths, --seed). Add --json for machine-readable output.
$ optionsdesk price -t put -S 50 -K 50 --days 152 -r 0.10 --vol 0.40 --model american
Model american
Price 4.2838
The European put on the same inputs is 4.0760; the early exercise premium is about 0.21.
$ optionsdesk price -t call -S 42 -K 40 --days 182.5 -r 0.10 --vol 0.20 --model mc --seed 1
Model mc
Price 4.7504
Std error 0.0055
95% CI [4.7397, 4.7611]
Implied volatility
$ optionsdesk iv --price 4.76 -t call -S 42 -K 40 --days 182.5 -r 0.10
Implied volatility (bsm): 20.0066%
An option price that no volatility can produce (below intrinsic or above the upper bound) exits with status 1 and a message such as price 1 is below the no-arbitrage lower bound 50.
$ optionsdesk iv --price 520 -t call -S 24650 -K 24700 --expiry 2026-11-24 --asof 2026-10-16 -r 0.065 -q 0.013
Implied volatility (bsm): 14.8366%
Fit a smile and surface
examples/nifty_chain.csv is synthetic: quotes priced from a known raw SVI surface with 0.3% IV noise, tick-rounded bid/ask spreads, stale last prices in the wings. Regenerate with python examples/generate_chain.py > examples/nifty_chain.csv. The generating ATM vols are 13.8%, 14.5%, 15.2% and 16.0%, which the fit recovers.
$ optionsdesk smile examples/nifty_chain.csv -S 24650 -r 0.065 -q 0.013 --asof 2026-10-16
Raw SVI fit per expiry, total variance w(k)
Expiry T (yr) Pts a b rho m sigma ATM IV RMSE (pts)
────────────────────────────────────────────────────────────────────────────────────────────────────
2026-10-27 0.0301 86 0.00009 0.01497 -0.5482 +0.00417 0.02989 13.81% 0.059
2026-11-24 0.1068 143 0.00096 0.02773 -0.5020 +0.00312 0.04454 14.50% 0.049
2026-12-29 0.2027 190 0.00236 0.03803 -0.4490 +0.00219 0.06010 15.20% 0.052
2027-03-30 0.4521 201 0.00660 0.05660 -0.4108 -0.00138 0.08814 15.99% 0.050
--quotes lists every quote's market IV against the fit. --json emits the parameters.
$ optionsdesk arb examples/nifty_chain.csv -S 24650 -r 0.065 -q 0.013 --asof 2026-10-16
PASS Butterfly (SVI density g(k) >= 0)
PASS Calendar (total variance non-decreasing)
FAIL Quote convexity (call prices from market IVs): 18 violation(s)
Expiry Where Magnitude Detail
───────────────────────────────────────────────────────────────────────────
2026-10-27 22,900.0000 -1.625 call price is not convex in strike
2026-11-24 21,700.0000 -2.5 call price is not convex in strike
2026-11-24 22,150.0000 -2.875 call price is not convex in strike
2026-11-24 27,000.0000 -1.75 call price is not convex in strike
2026-11-24 27,100.0000 -1.575 call price is not convex in strike
2026-11-24 27,600.0000 -0.75 call price is not convex in strike
2026-11-24 27,800.0000 -0.575 call price is not convex in strike
2026-11-24 28,000.0000 -0.35 call price is not convex in strike
... 10 more
The SVI slices are free of butterfly and calendar arbitrage. The quote-level check flags wing quotes whose implied call prices are not convex beyond what their bid/ask spreads explain; the command exits 1 when anything fails so it can gate a pipeline.
Analyze a strategy
YAML (examples/iron_condor.yaml):
name: NIFTY iron condor (24 Nov)
underlying: NIFTY # lot size 75 from the contract specs
spot: 24650
rate: 0.065
dividend_yield: 0.013
asof: 2026-10-16
chain: nifty_chain.csv # volatility from the fitted SVI surface (or `vol: 0.145` for flat)
legs:
- {qty: 1, type: P, strike: 24100, expiry: 2026-11-24}
- {qty: -1, type: P, strike: 24300, expiry: 2026-11-24}
- {qty: -1, type: C, strike: 25000, expiry: 2026-11-24}
- {qty: 1, type: C, strike: 25200, expiry: 2026-11-24}
$ optionsdesk analyze examples/iron_condor.yaml --days 7,21
╭────────────────────────────╮
│ NIFTY iron condor (24 Nov) │
╰────────────────────────────╯
Legs
Leg Entry Size
───────────────────────────────────────
+1 P 24100 2026-11-24 250.30 75
-1 P 24300 2026-11-24 297.43 -75
-1 C 25000 2026-11-24 357.68 -75
+1 C 25200 2026-11-24 274.72 75
P&L at expiry
9756 │ ███████████████████
│ █████████████████████
│ █████████████████████
│ █████████████████████
│ ███████████████████████
│ ███████████████████████
│ ███████████████████████
│ █████████████████████████
│ ◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦██████████
│ ◦◦◦◦◦◦ ███████████████◦◦◦◦◦◦◦███
0.0 │────────◦◦◦◦◦••••••••••••••••••••••••••••••──◦◦◦◦◦──────────────────────
│•••◦◦◦◦◦██████████████ ┊ •••••••◦◦◦◦██████████████████
│◦◦◦███████████████████ ┊ ███••◦◦◦◦◦•████████████
│██████████████████████ ┊ ██████████◦◦◦◦◦◦•••████
│█████████████████████ ┊ ███████████████◦◦◦◦◦◦◦
│█████████████████████ ┊ ██████████████████████
-5244 │█████████████████████ ┊ █████████████████████
└────────────────────────────────────────────────────────────────────────
▲ ▲ breakeven
23.4k 24.6k 26.0k
█ expiry P&L • T+7 ◦ T+21 ┊ spot
Summary (spot 24,650.00, to 2026-11-24)
Metric Value
───────────────────────────────────────────────────────────────────────────────────────────
Net credit 9,756
Max profit 9,756 (at 24,650)
Max loss -5,244
Breakevens 24,169.92, 25,130.08
Probability of profit (drift 5.20%, vol 14.81%) 31.1%
Expected P&L -858
Margin estimate (SPAN-like scan) 4,874 (worst: price +8.0%, vol -4 pts)
Net Greeks (position)
Greek Value Unit
─────────────────────────────────────────
delta -1.4711 per 1 unit move
gamma -0.002959 per 1 unit move
vega -246.3476 per vol point
theta 39.5115 per calendar day
rho -28.3228 per 1% rate
vanna 0.199122 delta per vol point
charm -0.013633 delta per day
vomma 29.294011 vega per vol point
Risk matrix: P&L by spot and date
Spot T+7 T+21 expiry
───────────────────────────────────
23,171 -1,595 -2,493 -5,244
23,418 -791 -1,284 -5,244
23,664 -77 23 -5,244
23,910 459 1,222 -5,244
24,157 746 2,077 -969
24,404 746 2,389 9,756
24,650 463 2,079 9,756
24,896 -61 1,215 9,756
25,143 -752 -10 -969
25,390 -1,526 -1,347 -5,244
25,636 -2,299 -2,574 -5,244
25,882 -3,008 -3,556 -5,244
26,129 -3,612 -4,254 -5,244
Legs without premium are priced by the model at the valuation date (using the surface vol for each strike), so T+0 P&L at today's spot is zero. Pass premium: 120.5 (or @120.5 in the shorthand) to use your actual fill. Quantities are signed lots; P&L and Greeks are scaled by the lot size.
Shorthand and templates need no file:
optionsdesk analyze --legs "+1 C 24500 2026-11-24, -1 C 24800 2026-11-24" \
--underlying NIFTY --vol 0.145 -S 24650 -r 0.065 -q 0.013 --asof 2026-10-16
optionsdesk analyze --template iron-condor --strikes 24100,24300,25000,25200 \
--expiry 2026-11-24 --underlying NIFTY --chain examples/nifty_chain.csv \
-S 24650 -r 0.065 -q 0.013 --asof 2026-10-16
$ optionsdesk analyze --legs "+1 C 24500 2026-11-24, -1 C 24800 2026-11-24" --underlying NIFTY --vol 0.145 -S 24650 -r 0.065 -q 0.013 --asof 2026-10-16 --no-chart --days 14
╭─────────────────╮
│ Custom strategy │
╰─────────────────╯
Legs
Leg Entry Size
───────────────────────────────────────
+1 C 24500 2026-11-24 619.32 75
-1 C 24800 2026-11-24 459.27 -75
Summary (spot 24,650.00, to 2026-11-24)
Metric Value
────────────────────────────────────────────────────────────────────────────────────────────
Net debit 12,004
Max profit 10,496
Max loss -12,004 (at 24,500)
Breakevens 24,660.05
Probability of profit (drift 5.20%, vol 14.50%) 53.4%
Expected P&L 83.60
Margin estimate (SPAN-like scan) 11,767 (worst: price -8.0%, vol -4 pts)
Net Greeks (position)
Greek Value Unit
─────────────────────────────────────────
delta 7.5756 per 1 unit move
gamma -0.000911 per 1 unit move
vega -85.7870 per vol point
theta -8.5188 per calendar day
rho 186.7043 per 1% rate
vanna -0.512755 delta per vol point
charm 0.098790 delta per day
vomma 9.673371 vega per vol point
Risk matrix: P&L by spot and
date
Spot T+14 expiry
────────────────────────────
23,171 -10,644 -12,004
23,418 -9,722 -12,004
23,664 -8,430 -12,004
23,910 -6,753 -12,004
24,157 -4,737 -12,004
24,404 -2,482 -12,004
24,650 -136 -754
24,896 2,142 10,496
25,143 4,211 10,496
25,390 5,971 10,496
25,636 7,376 10,496
25,882 8,432 10,496
26,129 9,178 10,496
A calendar spread prices the surviving far leg with surface vol at the near expiry:
$ optionsdesk analyze examples/calendar.yaml --no-chart --days 14
╭──────────────────────────────╮
│ NIFTY call calendar at 24700 │
╰──────────────────────────────╯
Legs
Leg Entry Size
───────────────────────────────────────
-1 C 24700 2026-11-24 515.36 -75
+1 C 24700 2026-12-29 790.18 75
Summary (spot 24,650.00, to 2026-11-24)
Metric Value
────────────────────────────────────────────────────────────────────────────────────────────
Net debit 20,612
Max profit 17,564 (at 24,700)
Max loss -20,641
Breakevens 24,192.85, 25,406.46
Probability of profit (drift 5.20%, vol 14.81%) 38.7%
Expected P&L -2,637
Margin estimate (SPAN-like scan) 17,934 (worst: price -8.0%, vol -4 pts)
Net Greeks (position)
Greek Value Unit
─────────────────────────────────────────
delta 1.7102 per 1 unit move
gamma -0.007972 per 1 unit move
vega 875.4994 per vol point
theta 106.6384 per calendar day
rho 960.5321 per 1% rate
vanna -0.065920 delta per vol point
charm 0.026569 delta per day
vomma 2.107162 vega per vol point
Risk matrix: P&L by spot and
date
Spot T+14 expiry
────────────────────────────
23,171 -10,995 -16,921
23,418 -8,366 -14,640
23,664 -5,643 -11,367
23,910 -3,047 -6,880
24,157 -820 -984
24,404 829 6,461
24,650 1,760 15,526
24,896 1,933 11,467
25,143 1,408 5,186
25,390 326 291
25,636 -1,128 -3,407
25,882 -2,761 -6,112
26,129 -4,406 -8,030
Export: --svg out.svg, --html report.html (self-contained page with chart, summary and matrix), --json. Other examples: examples/bull_call_spread.yaml (explicit premiums, side: sell), calendar.yaml and ratio_spread.yaml (template keys), covered_call.yaml.
Templates and contract specs
$ optionsdesk templates
Template Strikes Description
bull-call-spread 2 Buy low call, sell high call (strikes: low, high)
bear-put-spread 2 Buy high put, sell low put (strikes: low, high)
bull-put-spread 2 Sell high put, buy low put for a credit (strikes: low, high)
bear-call-spread 2 Sell low call, buy high call for a credit (strikes: low, high)
long-straddle 1 Buy call and put at one strike (strike)
short-straddle 1 Sell call and put at one strike (strike)
long-strangle 2 Buy OTM put and OTM call (strikes: put, call)
short-strangle 2 Sell OTM put and OTM call (strikes: put, call)
iron-condor 4 Short strangle with long wings (strikes: put wing, short put, short
call, call wing)
butterfly 3 Long call butterfly: +1 low, -2 mid, +1 high (strikes: low, mid, high)
calendar 1 Sell near call, buy far call at one strike (strike, needs far expiry)
call-ratio-spread 2 Buy 1 low call, sell N high calls (strikes: low, high)
put-ratio-spread 2 Sell N low puts, buy 1 high put (strikes: low, high)
covered-call 1 Long underlying plus short call (strike)
collar 2 Long underlying, long put, short call (strikes: put, call)
$ optionsdesk specs
Symbol Lot size Tick Exchange Currency Style
AAPL 100 0.01 NASDAQ USD american
BANKNIFTY 35 0.05 NSE INR european
FINNIFTY 65 0.05 NSE INR european
MIDCPNIFTY 140 0.05 NSE INR european
NIFTY 75 0.05 NSE INR european
RELIANCE 500 0.05 NSE INR european
SENSEX 20 0.05 BSE INR european
SPX 100 0.05 CBOE USD european
SPY 100 0.01 NYSE Arca USD american
Lot sizes change. Override with --contracts my.toml or OPTIONSDESK_CONTRACTS=my.toml; entries replace the bundled ones with the same symbol, and --lot-size overrides a single run. Contracts with style = "american" (US equities) are priced with the binomial tree under the default --model auto.
Configuration
| Option | Meaning |
|---|---|
-r/--rate, -q/--div |
Risk-free rate (default 0.065) and dividend yield (default 0), continuous, decimal |
--asof |
Valuation date; defaults to today |
--vol / --chain |
Flat vol, or a chain CSV for surface vol (strike-based lookup) |
--model |
auto (BSM for european contracts, tree for american), bsm, black76, american |
--days 7,14 |
Extra T+n curves and risk-matrix columns |
--drift |
Real-world annual drift for probability of profit and expected value (default risk-neutral r - q) |
--margin-range, --margin-vol |
Margin scan: price move (default 8%) and vol shift (default 4 vol points) |
OPTIONSDESK_CONTRACTS |
Path to a contracts TOML |
Chain CSV columns: strike, expiry (YYYY-MM-DD), bid, ask, last, oi, type (C/P). The mid price is used when a two-sided quote exists, otherwise last.
Greeks units: vega per vol point, rho per 1% rate, theta and charm per calendar day, vanna per vol point, vomma per vol point squared (all per unit of underlying; strategy Greeks are multiplied by lots and lot size).
Architecture
optionsdesk/
models/bsm.py BSM price and Greeks (vectorised over spot)
models/black76.py Black-76 as BSM with q = r, own rho
models/binomial.py CRR tree vectorised over spots, bump Greeks
models/montecarlo.py antithetic MC with CI
models/iv.py Newton/bisection IV (BSM, Black-76, American)
market.py spot, rate, yield, as-of date
surface.py chain loader, smile points, SVI fit, VolSurface, arbitrage checks
strategy.py Leg, shorthand parser, YAML loader, templates
contracts.py lot sizes from TOML (data/contracts.toml bundled)
analytics.py Position valuation, breakevens, max P&L, PoP, EV, margin, risk matrix
charts.py Unicode chart, SVG, HTML
report.py rich tables
cli.py click commands: price iv smile arb analyze templates specs
tests/ pytest suite
examples/ synthetic NIFTY chain, generator, strategy YAMLs
docs/ models.md, analytics.md
Algorithms, formulas and design decisions: docs/models.md and docs/analytics.md.
Tests
.venv/bin/pytest -q
Covers Hull textbook values, put-call parity, Greeks against finite differences, binomial convergence and the known American put, Monte Carlo inside its CI, IV recovery across moneyness and expiry, SVI parameter recovery, arbitrage detection, hand-calculated breakevens and max P&L for the templates, probability of profit against Monte Carlo, and CLI end-to-end runs on the examples.
Limitations
- Margin is an approximation of an exchange SPAN risk array (single price x vol grid, one-day horizon, no short option minimum, no spread or calendar credits, no exposure margin). Use your broker's calculator for real requirements.
- Surface vol is looked up by strike (sticky strike) and the curves hold it fixed as spot moves. Black-76 mode treats the spot as one futures price shared by all legs.
- American Greeks come from bumped trees and omit vanna, charm and vomma. Dividends are a continuous yield; discrete dividends are not modelled.
- Futures and stock legs are linear in spot with no carry; legs that expire before the evaluation date are not rolled.
- Probability of profit assumes a single lognormal volatility (the model vol at spot for the first expiry), so it does not capture skew in the terminal distribution.
- Lot sizes and expiry conventions change; the bundled TOML is a starting point.
- Not investment advice.