Metadata-Version: 2.4
Name: noarb
Version: 1.0.0
Summary: Find the arbitrage violations in an option chain, and say which quote to check first
Author: Bhavya Dhoot
License: MIT
Project-URL: Homepage, https://github.com/Bhavya-Dhoot/noarb
Project-URL: Issues, https://github.com/Bhavya-Dhoot/noarb/issues
Keywords: options,arbitrage,black-scholes,implied-volatility,greeks,derivatives,quant
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Dynamic: license-file

# noarb

[![tests](https://github.com/Bhavya-Dhoot/noarb/actions/workflows/tests.yml/badge.svg)](https://github.com/Bhavya-Dhoot/noarb/actions/workflows/tests.yml)
[![PyPI](https://img.shields.io/pypi/v/noarb.svg)](https://pypi.org/project/noarb/)
[![Python](https://img.shields.io/pypi/pyversions/noarb.svg)](https://pypi.org/project/noarb/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

Point it at an option chain. It tells you which quotes cannot all be true at
once, and which one to check first.

```
$ noarb --chain chain.csv --only-violations

SEVERITY   RULE                 EXPIRY      DETAIL                                                        REASON
violation  put-call-parity      2027-03-09  strike 110: call minus put is -4.80, parity requires -7.80    Parity is an identity for European options. Check for a dividend the feed omitted, then whether these are American.
violation  put-call-parity      2026-12-09  strike 100: call minus put is 3.49, parity requires 0.99      Parity is an identity for European options. Check for a dividend the feed omitted, then whether these are American.
violation  put-call-parity      2026-12-09  strike 120: call minus put is -16.51, parity requires -18.76  Parity is an identity for European options. Check for a dividend the feed omitted, then whether these are American.
...

36 quotes, 10 violations, 0 warnings, largest slack 3.0000
```

Exit code 1, so a pipeline can refuse to trade on a chain that disagrees with
itself.

## Install

```bash
pip install noarb
```

No runtime dependencies. Python 3.9 and up. It never touches the network, and
there is a CI job that fails the build if anyone adds an import that could.

## Why

A chain that violates one of these conditions is not a trading opportunity. It
is a data problem, and it is a data problem you want to find before it reaches
a model rather than after.

The conditions checked here are **model-free**. None of them assumes
Black-Scholes or any other distribution for the underlying — they follow from
the payoffs alone. If one is violated, a portfolio exists that costs nothing
today and cannot lose money later. That is a much stronger statement than "the
implied volatility looks a bit odd", and it is why a violation is worth
stopping a pipeline for.

What actually causes them, in order of how often: a stale quote, the wrong
spot, a dividend the feed did not mention, or the wrong expiry calendar. So
every finding names the cheapest explanation to check first.

## What it checks

| Rule | Condition |
|---|---|
| `lower-bound` | An option is never worth less than its discounted intrinsic value |
| `upper-bound` | A call is never worth more than the forward; a put never more than the discounted strike |
| `strike-monotonicity` | Calls get cheaper as the strike rises; puts get dearer |
| `vertical-spread` | A spread never costs more than the most it can pay |
| `butterfly-convexity` | Value is convex in the strike, with strike-distance weights so uneven ladders are handled |
| `put-call-parity` | `C − P = S·e^(−qT) − K·e^(−rT)`, an identity for European options |
| `calendar` | More time is never worth less — checked only where there is no dividend, because with one it genuinely can be |

Plus warnings, which do not fail the build: crossed markets, quotes whose
spread is more than half the offer, and contracts that have already expired.

## Input

A CSV. Headers are matched case-insensitively and tolerate spaces and
underscores.

```csv
right,strike,expiry,price,spot,rate,dividend
call,95,2026-12-09,7.53,100,0.05,0
put,95,2026-12-09,1.60,100,0.05,0
```

`expiry` takes an ISO date or a bare year fraction. Give `bid` and `ask`
instead of `price` and the mid is used, with both retained so the quote-quality
checks still work. Pass `--percent-rates` if your rate column says `5` rather
than `0.05`.

## Usage

```bash
noarb --chain chain.csv                      # table, exit 1 if anything is violated
noarb --chain chain.csv --only-violations    # drop the warnings
noarb --chain chain.csv --format json        # for a downstream tool
noarb --chain chain.csv --format csv         # for a spreadsheet
noarb --chain chain.csv --tolerance 0.05     # in currency, default 0.01
noarb --chain chain.csv --as-of 2026-09-09   # valuation date for ISO expiries
noarb --chain chain.csv --quiet              # exit code only
```

Exit codes: `0` clean, `1` at least one violation, `2` the input could not be
read. Warnings alone still exit `0`.

## As a library

```python
from noarb import bs

bs.price("call", spot=42, strike=40, years=0.5, rate=0.10, vol=0.20)  # 4.7594
bs.greeks("call", 100, 100, 1.0, 0.05, 0.25).vega  # per vol point
bs.implied_vol("call", 8.5917, 100, 100, 1.0, 0.05)  # 0.15
```

`implied_vol` returns `None` rather than a number when the quote does not
identify one. That is not an error path — a deep in-the-money put quoted at
parity prices identically at 5% and at 80% volatility, so any number returned
would be invented. See [the note below](#the-implied-volatility-of-a-quote-that-implies-nothing).

Units, fixed at the point of calculation so two callers cannot disagree:

| | |
|---|---|
| delta, gamma | per 1.00 of spot |
| vega, rho | per 1 percentage point |
| theta | per calendar day, 365-day year |

## How it is checked

The model is not tested against itself. Every assertion is a published value,
an identity that holds for any arbitrage-free model, or a numerical derivative
of the code's own price function:

- Hull's textbook values reproduce exactly
- put–call parity holds to `3e-14` across the grid
- all five Greeks agree with central differences to `1e-6`
- implied volatility round-trips to `2.5e-08`
- every arbitrage check is tested with a chain that breaks it *and* a chain
  that does not, because a check that always fires is worse than one that never does

CI runs the suite on Python 3.9 through 3.13 across Linux, macOS and Windows,
asserts the exit codes from the command line, fails if a runtime dependency or
a network import appears, and builds and installs the wheel into a clean
virtualenv before running it.

### The implied volatility of a quote that implies nothing

Far enough from the money, a quote sits on its intrinsic value to the last bit
a float can hold. Every volatility from zero up to some large number reprices
it equally well, and a root finder will happily hand back the smallest one.

A deep in-the-money put quoted at parity would then report `0.00%` implied
volatility — which a reader takes for a market view rather than for the absence
of one. So a volatility only counts as implied here when the price is
measurably sensitive to it. Otherwise the answer is `n/a`, and that is the
honest one.

This was found by a test, not by inspection: the solver returned `0.0` for 231
of 648 grid points before the guard existed.

## Scope

European options, Black-Scholes-Merton, continuous dividend yield. American
early exercise is not modelled — the parity check will report violations on
American chains, and it will be right that the identity does not hold, not
right that there is free money. Nothing here is investment advice.

## Related

The checks tell you which quotes disagree. Sizing the resulting position is a
separate job, and there is a workbook for it:
**[The Options Greeks and Position Sizing Workbook](https://bhavyadhoot.gumroad.com/l/options-greeks-workbook)**
— live Excel formulas for pricing, all five Greeks, a strike ladder that proves
its own put–call parity, risk-budgeted position sizing that reprices at the
stop rather than approximating with delta, and a multi-leg payoff builder. Its
numbers are verified against this package.

## Licence

MIT. See [LICENSE](LICENSE).
