{"id":50384573,"url":"https://github.com/yipjunkai/pyvolr","last_synced_at":"2026-05-30T14:01:37.589Z","repository":{"id":359901674,"uuid":"1247942369","full_name":"yipjunkai/pyvolr","owner":"yipjunkai","description":"Modern Black-Scholes-Merton pricing, Greeks, and implied volatility for Python. Rust core. Drop-in replacement for the abandoned py_vollib.","archived":false,"fork":false,"pushed_at":"2026-05-24T02:48:32.000Z","size":93,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T03:25:10.818Z","etag":null,"topics":["black-scholes","black-scholes-merton","greeks","implied-volatility","maturin","numpy","options","options-pricing","py-vollib","pyo3","python","quant","quantitative-finance","rust"],"latest_commit_sha":null,"homepage":"https://github.com/yipjunkai/pyvolr","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yipjunkai.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":"GOVERNANCE.md","roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-24T01:36:16.000Z","updated_at":"2026-05-24T02:48:36.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yipjunkai/pyvolr","commit_stats":null,"previous_names":["yipjunkai/pyvolr"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/yipjunkai/pyvolr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yipjunkai%2Fpyvolr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yipjunkai%2Fpyvolr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yipjunkai%2Fpyvolr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yipjunkai%2Fpyvolr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yipjunkai","download_url":"https://codeload.github.com/yipjunkai/pyvolr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yipjunkai%2Fpyvolr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33694714,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["black-scholes","black-scholes-merton","greeks","implied-volatility","maturin","numpy","options","options-pricing","py-vollib","pyo3","python","quant","quantitative-finance","rust"],"created_at":"2026-05-30T14:01:36.697Z","updated_at":"2026-05-30T14:01:37.577Z","avatar_url":"https://github.com/yipjunkai.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pyvolr\n\n[![PyPI](https://img.shields.io/pypi/v/pyvolr.svg)](https://pypi.org/project/pyvolr/)\n[![Python versions](https://img.shields.io/pypi/pyversions/pyvolr.svg)](https://pypi.org/project/pyvolr/)\n[![Wheel](https://img.shields.io/pypi/wheel/pyvolr.svg)](https://pypi.org/project/pyvolr/#files)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/yipjunkai/pyvolr/badge)](https://securityscorecards.dev/viewer/?uri=github.com/yipjunkai/pyvolr)\n[![CI](https://github.com/yipjunkai/pyvolr/actions/workflows/ci.yml/badge.svg)](https://github.com/yipjunkai/pyvolr/actions/workflows/ci.yml)\n[![License](https://img.shields.io/pypi/l/pyvolr.svg)](#-license)\n\n**Modern Black-Scholes-Merton pricing, Greeks, and implied volatility for Python.** Rust core. Vectorized. Drop-in replacement for the abandoned `py_vollib`.\n\n```python\nfrom pyvolr import bs\n\nbs.price(\"c\", S=100, K=105, T=0.5, r=0.05, sigma=0.2) # 4.581680167540007\n```\n\n## ⚡ Performance\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/yipjunkai/pyvolr/main/docs/assets/perf-competitors-time-dark.svg\"\u003e\n  \u003cimg alt=\"Time per call: pyvolr vs the active BSM-pricing ecosystem, log-log by array size\" src=\"https://raw.githubusercontent.com/yipjunkai/pyvolr/main/docs/assets/perf-competitors-time-light.svg\"\u003e\n\u003c/picture\u003e\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n\u003cpicture\u003e\n  \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/yipjunkai/pyvolr/main/docs/assets/perf-competitors-thru-dark.svg\"\u003e\n  \u003cimg alt=\"Throughput: pyvolr vs the active BSM-pricing ecosystem, log-log by array size\" src=\"https://raw.githubusercontent.com/yipjunkai/pyvolr/main/docs/assets/perf-competitors-thru-light.svg\"\u003e\n\u003c/picture\u003e\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\nSix libraries on the chart: **`pyvolr`**, [`vollib`](https://pypi.org/project/vollib/) (resurrected upstream of `py_vollib`, pure Python), [`py_vollib_vectorized`](https://pypi.org/project/py_vollib_vectorized/) (numba), [`blackscholes`](https://pypi.org/project/blackscholes/) (pure Python, object-per-call), [`QuantLib`](https://pypi.org/project/QuantLib/) (C++ core, looped scalar), and [`quantforge`](https://pypi.org/project/quantforge/) (Rust + SIMD).\n\npyvolr leads at every input size up to ~1M strikes. **quantforge overtakes at very large batches** via explicit SIMD vectorisation — the same axis [fast-vollib](https://arxiv.org/abs/2604.27210) takes with Triton kernels. pyvolr's positioning is explicitly the \"Rust-cored CPU option\" — no `unsafe` SIMD intrinsics, no GPU dependency, abi3 wheel ships in one file. If you're pricing 10M+ strikes per call and CPU-only, prefer quantforge.\n\n| Scenario                       |   pyvolr |  py_vollib |  speedup |\n| ------------------------------ | -------: | ---------: | -------: |\n| `bs.price`, scalar             |   4.2 µs |     2.2 µs |     0.5× |\n| `bs.price`, 1k strikes         |  24.6 µs |    2.32 ms |      94× |\n| `bs.price`, 10k strikes        |   153 µs |   23.32 ms |     152× |\n| `bs.price`, 100k strikes       |  1.39 ms |  234.91 ms |     169× |\n| `bs.price`, 1M strikes         | 14.54 ms |   2,350 ms |     162× |\n| `bs.greeks` (all 5), 10k       |   273 µs |   89.95 ms |     330× |\n| `bs.implied_vol`, scalar       |   4.4 µs |    15.0 µs |     3.4× |\n| `bs.implied_vol`, 10k strikes  |   465 µs |   128 ms ¹ |     275× |\n| `black76.price`, scalar        |   3.7 µs |     2.2 µs |     0.6× |\n| `black76.price`, 10k strikes   |   141 µs |   23.19 ms |     164× |\n| `black76.implied_vol`, scalar  |   3.9 µs |    14.7 µs |     3.8× |\n\n¹ py_vollib's `implied_volatility` is scalar-only; the 10k figure is `N` × scalar measured via `compare_py_vollib.py`. pyvolr's vectorised path parallelises automatically above N=1024 via rayon — set `RAYON_NUM_THREADS=1` to force serial.\n\nThe table above is the headline-vs-the-abandoned-upstream comparison (py_vollib's last release is broken on Python 3.12+, see [docs/why.md](docs/why.md)). For the workload most people actually run — a smile, an option chain, an IV snapshot — pyvolr is faster than every actively-maintained alternative and installs cleanly on every modern Python.\n\n`bs.greeks` returning all five Greeks at once uses a single-pass Rust kernel that shares `d1`/`d2`, discount factors, `cdf`, and `pdf` across the five outputs — ~3× faster than the equivalent five separate calls. For batches ≥4096 rows, the work also dispatches across CPU cores in parallel.\n\n**Numerical agreement:** pyvolr matches every library above to f64 precision (~1e-13 relative) on every well-posed input across price + 5 Greeks + IV. At deep-OTM short-expiry corners pyvolr is *more* precise than the rest — `blackscholes` and `quantforge` underflow to zero where pyvolr's `erfcx`-based cdf retains the ~1e-50 price; QuantLib and the alternatives lose 1-2 digits. Run `python bench/sanity_check_competitors.py` in each venv to re-validate.\n\nReproduce the table with `python bench/compare_py_vollib.py`; reproduce the chart with `python bench/compare_competitors.py bench` then `python bench/compare_competitors.py chart` (across the Python 3.11 + 3.12 venvs documented in the script's docstring). Library versions: Apple M4 Pro / Python 3.10.20 / numpy 2.2.6 / pyvolr 0.1.2 / py_vollib 1.0.1 (table) / vollib 1.0.7 / py_vollib_vectorized 0.1.1 / blackscholes 0.2.0 / QuantLib 1.42.1 / quantforge 0.1.1 (chart).\n\n## 📦 Install\n\n```bash\npip install pyvolr\n```\n\nOr via [`uv`](https://github.com/astral-sh/uv):\n\n```bash\nuv pip install pyvolr\n```\n\nPre-built wheels are published for Linux (x86_64, aarch64), macOS (Intel, Apple Silicon), and Windows (x86_64) across Python 3.10–3.14, plus free-threaded builds for 3.13t and 3.14t.\n\n### Tested on\n\n|         | 3.10 | 3.11 | 3.12 | 3.13 | 3.14 |\n| ------- | :--: | :--: | :--: | :--: | :--: |\n| Linux   |  ✅  |  ✅  |  ✅  |  ✅  |  ✅  |\n| macOS   |  ✅  |  ✅  |  ✅  |  ✅  |  ✅  |\n| Windows |  —   |  —   |  ✅  |  ✅  |  ✅  |\n\nEvery push and PR runs the full `pytest` + `cargo test` suites across the matrix above. Windows × {3.10, 3.11} are skipped intentionally to keep CI minutes reasonable — the wheels themselves still build for those combinations and are published. Free-threaded wheels (3.13t, 3.14t) are built and exercised through `cibuildwheel`'s in-wheel test pass on every release across Linux/macOS/Windows.\n\nFrom source (requires Rust):\n\n```bash\ngit clone https://github.com/yipjunkai/pyvolr\ncd pyvolr\nuv venv --python 3.12 \u0026\u0026 source .venv/bin/activate\nuv pip install -e \".[dev,test]\"\nmaturin develop --release\n```\n\n## 🚀 Quick start\n\n```python\nimport numpy as np\nfrom pyvolr import bs\n\n# Scalar\nbs.price(\"c\", S=100, K=105, T=0.5, r=0.05, sigma=0.2)\n\n# Vectorized — broadcast over any combination of inputs\nstrikes = np.linspace(80, 120, 41)\nprices = bs.price(\"c\", S=100, K=strikes, T=0.5, r=0.05, sigma=0.2)\n\n# All five Greeks in one call\ngreeks = bs.greeks(\"c\", S=100, K=strikes, T=0.5, r=0.05, sigma=0.2)\n# {\"delta\": [...], \"gamma\": [...], \"theta\": [...], \"vega\": [...], \"rho\": [...]}\n\n# Implied volatility from a market price\nbs.implied_vol(price=5.20, flag=\"c\", S=100, K=100, T=0.25, r=0.05)\n\n# Broadcasting works in any dimension\nstrike_grid = np.linspace(80, 120, 5).reshape(-1, 1)\nvol_grid = np.linspace(0.10, 0.40, 4).reshape(1, -1)\nsurface = bs.price(\"c\", S=100, K=strike_grid, T=0.5, r=0.05, sigma=vol_grid)\n# shape (5, 4)\n\n# Black-76 for options on futures / forwards — same API, F replaces S, no q.\nfrom pyvolr import black76\nblack76.price(\"c\", F=100, K=105, T=0.5, r=0.05, sigma=0.2)\n```\n\n## ✨ Features\n\n- **Black-Scholes-Merton pricing** — calls and puts with continuous dividend yield\n- **Black-76 pricing** — European options on futures/forwards (`pyvolr.black76`), same vectorized API as `bs`\n- **Analytical Greeks** — delta, gamma, theta, vega, rho (with documented sign and unit conventions)\n- **Robust implied volatility** — Jäckel \"Let's Be Rational\" algorithm: rational-cubic initial guess plus Householder order-4 iteration converges to ~1e-13 precision in ≤2 iterations across the full no-arbitrage range\n- **Automatic parallelism on large batches** — `implied_vol` (above N≈1,000 rows) and the bundled `greeks` kernel (above N≈4,000) release the GIL and dispatch per-row work to rayon's global thread pool; set `RAYON_NUM_THREADS=1` to opt out\n- **Full numpy broadcasting** — any combination of inputs in any shape, scalar-in scalar-out\n- **`py_vollib` drop-in shim** — `pyvolr.compat.py_vollib` mirrors the upstream module tree (including `py_vollib.black`) for one-import-line migration\n- **Rust core, no compiler needed** — abi3 wheels for Python 3.10–3.14 × {Linux, macOS, Windows}\n- **Free-threaded Python ready** — dedicated wheels for 3.13t and 3.14t; the Rust core releases the GIL around the math, so pricing scales across threads without a process pool\n- **Typed end-to-end** — pyright-strict library code, full type stubs for the Rust extension\n\n## 🗺️ Coming soon\n\n- [ ] Drop-in compat shim for `py_vollib_vectorized` (`vectorized_*` API + `price_dataframe`/`get_all_greeks`, pandas as soft dep)\n- [ ] Bachelier (normal model, for negative rates)\n- [ ] Higher-order Greeks (vanna, vomma, charm, speed, zomma, color)\n- [ ] SIMD batch evaluation\n- [ ] American options (CRR binomial → finite difference)\n- [ ] Volatility surface fitting (SVI, SSVI)\n\n## 🔄 Migrating from py_vollib\n\nReplace your imports — the signatures and `'c'`/`'p'` flag convention are preserved exactly:\n\n```python\n# Before\nfrom py_vollib.black_scholes import black_scholes\nfrom py_vollib.black_scholes.greeks.analytical import delta\nfrom py_vollib.black_scholes.implied_volatility import implied_volatility\nfrom py_vollib.black import black  # futures options\n\n# After\nfrom pyvolr.compat.py_vollib.black_scholes import black_scholes\nfrom pyvolr.compat.py_vollib.black_scholes.greeks.analytical import delta\nfrom pyvolr.compat.py_vollib.black_scholes.implied_volatility import implied_volatility\nfrom pyvolr.compat.py_vollib.black import black  # futures options\n```\n\nThe compat shim also preserves py_vollib's _unit conventions_: vega is per-1% vol, theta is per-day, rho is per-1% rate, and `implied_volatility` takes `flag` as its last argument. For new code, prefer the modern `pyvolr.bs` API — it accepts numpy arrays, broadcasts naturally, uses per-unit conventions consistently, and returns all Greeks in a single call.\n\n## 🤔 Why pyvolr exists\n\n`py_vollib` has been broken on Python 3.12+ since the release — a transitive dependency imports `DBL_MIN` / `DBL_MAX` from CPython's internal `_testcapi` test module, which isn't shipped with modern Python distributions. The fix is two lines (`sys.float_info.{min,max}` are the correct sources), but `py_lets_be_rational` hasn't released since 2017, `py_vollib` since 2020, and the maintainers are gone.\n\nFull backstory: [docs/why.md](docs/why.md).\n\n## 📁 Project structure\n\n```text\npyvolr/\n├── crates/core/             # Rust numerical core\n│   ├── src/\n│   │   ├── lib.rs           # PyO3 bindings (flat-array entry points)\n│   │   ├── bsm.rs           # BSM pricing, d1/d2, forward price\n│   │   ├── black76.rs       # Black-76 (futures options) — delegates to BSM with q=r\n│   │   ├── greeks.rs        # Delta, gamma, theta, vega, rho\n│   │   ├── iv.rs            # Jäckel \"Let's Be Rational\" IV solver (Householder-4, ≤2 iters)\n│   │   └── normal.rs        # Φ / φ, erfcx (Lentz CF), inverse CDF (Wichura AS241)\n│   └── benches/             # criterion benches gating the README's perf claims\n├── bench/                   # Python-level speed/precision scripts (dev-only, not in CI)\n│   ├── compare_py_vollib.py            # reproduces the perf table\n│   ├── compare_competitors.py          # reproduces the perf chart (6 libraries)\n│   └── sanity_check_competitors.py     # cross-validates numerical agreement\n├── python/pyvolr/\n│   ├── bs.py                # BSM public API (numpy-broadcast wrappers)\n│   ├── black76.py           # Black-76 public API\n│   ├── _wrappers.py         # Shared FFI helpers (broadcast, flag normalize)\n│   ├── _core.pyi            # Type stubs for the Rust extension\n│   └── compat/py_vollib/    # Drop-in shim mirroring py_vollib's tree\n├── tests/                   # pytest + hypothesis property tests\n├── .github/workflows/       # ci, release, release-please, differential, fuzz, perf, security, scorecard, stale\n├── .github/scripts/         # CI helper scripts (perf-gate comparator)\n├── Cargo.toml               # Rust workspace\n└── pyproject.toml           # maturin build backend + project config\n```\n\n## 📚 API reference\n\n| Function                                       | Returns                    | Vectorized over        |\n| ---------------------------------------------- | -------------------------- | ---------------------- |\n| `bs.price(flag, S, K, T, r, sigma, q=0)`       | option price               | all numeric inputs     |\n| `bs.delta(flag, S, K, T, r, sigma, q=0)`       | ∂Price/∂S                  | all numeric inputs     |\n| `bs.gamma(S, K, T, r, sigma, q=0)`             | ∂²Price/∂S²                | all numeric inputs     |\n| `bs.vega(S, K, T, r, sigma, q=0)`              | ∂Price/∂σ (per unit vol)   | all numeric inputs     |\n| `bs.theta(flag, S, K, T, r, sigma, q=0)`       | −∂Price/∂T (per year)      | all numeric inputs     |\n| `bs.rho(flag, S, K, T, r, sigma, q=0)`         | ∂Price/∂r (per unit r)     | all numeric inputs     |\n| `bs.greeks(flag, S, K, T, r, sigma, q=0)`      | `dict` of all five Greeks  | all numeric inputs     |\n| `bs.implied_vol(price, flag, S, K, T, r, q=0)` | σ (NaN on bound violation) | price + numeric inputs |\n| `black76.price(flag, F, K, T, r, sigma)`       | option price on a forward  | all numeric inputs     |\n| `black76.{delta,gamma,vega,theta,rho}(...)`    | Greeks for Black-76        | all numeric inputs     |\n| `black76.greeks(flag, F, K, T, r, sigma)`      | `dict` of all five Greeks  | all numeric inputs     |\n| `black76.implied_vol(price, flag, F, K, T, r)` | σ (NaN on bound violation) | price + numeric inputs |\n| `pyvolr.compat.py_vollib.…`                    | py_vollib-shaped scalars   | n/a (scalar API)       |\n\n`flag` accepts `'c'`/`'C'` (call), `'p'`/`'P'` (put), or an array thereof.\n\n## 🛡️ Sustainability\n\n`py_vollib` died because nobody was paid to maintain it. pyvolr is engineered to outlive its maintainer:\n\n- **One-click releases** via release-please + PyPI Trusted Publishing — PyPI publication needs no stored credentials (OIDC), and release-please authenticates as a repo-scoped GitHub App rather than a user PAT, so the credential survives a maintainer handoff\n- **Nightly differential tests** against `py_vollib` on a Python 3.10 sidecar to catch numerical drift\n- **Wide CI matrix** (Python 3.10–3.14 × Linux/macOS/Windows) — the specific failure mode that killed the predecessor\n- **All GitHub Actions pinned** with weekly Dependabot bumps, hardening against supply-chain attacks\n- **Hand-off plan documented** in [GOVERNANCE.md](GOVERNANCE.md)\n\nCommercial sponsorship channels will be added if demand warrants. For now the best support is real-world use, good bug reports, and PRs.\n\n## 🤝 Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md). Particularly welcome: new pricing models (Bachelier, American), higher-order Greeks, SIMD/vectorization work, and property tests for edge cases.\n\n## 📄 License\n\nDual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at your option.\n\nAlgorithms are reimplemented from published references (Hull, Merton, Jäckel); no third-party source code is incorporated.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyipjunkai%2Fpyvolr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyipjunkai%2Fpyvolr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyipjunkai%2Fpyvolr/lists"}