# 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.

```bash
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

```bash
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`):

```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:

```bash
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](docs/models.md) and [docs/analytics.md](docs/analytics.md).

## Tests

```bash
.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.
