quantflow 0.8.0__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/copilot-instructions.md +0 -12
- quantflow-0.9.0/.github/instructions/release.instructions.md +48 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/CLAUDE.md +1 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/PKG-INFO +1 -1
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/black.md +4 -5
- quantflow-0.9.0/docs/assets/logos/png/quantflow-github-social-1280x640.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/pricing_method_comparison.py +5 -5
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_bns2_calibration.py +7 -4
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_bns_calibration.py +2 -1
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_heston_calibration.py +1 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_hestonj_calibration.py +5 -2
- quantflow-0.9.0/docs/release-notes.md +165 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/pyproject.toml +1 -1
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/base.py +89 -26
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/pricer.py +48 -33
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/pricer.py +60 -117
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/marginal.py +127 -38
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_divfm.py +13 -10
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_options.py +3 -2
- {quantflow-0.8.0 → quantflow-0.9.0}/uv.lock +18 -18
- quantflow-0.8.0/docs/release-notes.md +0 -68
- quantflow-0.8.0/notebooks/applications/calibration.md +0 -204
- quantflow-0.8.0/notebooks/applications/calibration.py +0 -189
- quantflow-0.8.0/notebooks/applications/volatility_surface.md +0 -144
- quantflow-0.8.0/notebooks/data/fed.md +0 -40
- quantflow-0.8.0/notebooks/data/fiscal_data.md +0 -31
- quantflow-0.8.0/notebooks/data/fmp.md +0 -80
- quantflow-0.8.0/notebooks/data/timeseries.md +0 -39
- quantflow-0.8.0/notebooks/models/bns.md +0 -111
- quantflow-0.8.0/notebooks/models/cir.md +0 -186
- quantflow-0.8.0/notebooks/models/gousv.md +0 -48
- quantflow-0.8.0/notebooks/models/heston.md +0 -133
- quantflow-0.8.0/notebooks/models/heston_jumps.md +0 -78
- quantflow-0.8.0/notebooks/models/jump_diffusion.md +0 -101
- quantflow-0.8.0/notebooks/models/ou.md +0 -203
- quantflow-0.8.0/notebooks/models/overview.md +0 -19
- quantflow-0.8.0/notebooks/models/poisson.md +0 -269
- quantflow-0.8.0/notebooks/models/wiener.md +0 -73
- {quantflow-0.8.0 → quantflow-0.9.0}/.coveragerc +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.dockerignore +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/instructions/makefile.instructions.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/instructions/tutorial.instructions.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/build.yml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/deploy.yml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/docker-multiarch.yml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/release.yml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.gitignore +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/launch.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/settings.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/tasks.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/CITATION.cff +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/LICENSE +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/Makefile +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/__main__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/cointegration.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/double_exponential_sampling.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/gaussian_sampling.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/heston_divfm_fit.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/heston_vol_surface.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/hurst.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/poisson_sampling.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/heston_divfm_fit.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/test_comparison.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/supersmoother.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/utils/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/utils/paths.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/app/volatility_surface.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/blocks/quantflow.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/build-examples +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/charts.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/.helmignore +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/Chart.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/_helpers.tpl +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/_service.tpl +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/app.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/configmap.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/secret.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/values.yaml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/install +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/lint +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/marimo +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/quantflow.dockerfile +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/dev/test +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/deribit.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fed.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fmp.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fred.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/calibration.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/divfm.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/pricer.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/svi.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/vol_surface.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/interest_rate.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/yield_curve.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/bns.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/cir.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/compound_poisson.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/copula.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/dsp.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/heston.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/jump_diffusion.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/ou.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/poisson.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/wiener.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/ewma.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/kalman.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/ohlc.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/paths.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/supersmoother.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/bins.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/distributions.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/marginal1d.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/numbers.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/types.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/heston.gif +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/README.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/favicon.ico +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/head-snippet.html +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/manifest.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-1024.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-128.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-256.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-512.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-linkedin-banner.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-1200.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-dark-1200.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-dark-2400.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-tagline-dark-1600.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-tagline-light-1600.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-1024.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-128.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-16.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-256.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-32.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-48.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-512.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-64.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-1024.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-128.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-16.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-256.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-32.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-48.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-512.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-64.png +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-app-icon.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-favicon.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-linkedin-banner.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-dark.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-tagline-dark.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-tagline-light.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-mark-dark.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-mark.svg +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/bib2md.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/bibliography.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/contributing.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/_utils.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/cir_pdf_comparison.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/fft.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/heston_volatility_pricer.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_inputs.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/volsurface.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/wiener_volatility_pricer.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/favicon.ico +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/glossary.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/javascripts/mathjax.js +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/mcp.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/references.bib +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/robots.txt +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/characteristic.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/convexity_correction.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/inversion.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/levy.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/option_pricing.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/bns_calibration.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/cir.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/index.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/option_pricing.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/pricing_method_comparison.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/volatility_surface.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/mkdocs.yml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/server.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/base.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/charts.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/crypto.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/fred.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/stocks.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/vault.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/deribit.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fed.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fiscal_data.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fmp.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fred.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/vault.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/bs.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/bns.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/heston.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/network.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/trainer.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/weights.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/butterfly.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/calendar_spread.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/spread.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/straddle.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/strangle.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/terminology.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/inputs.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/base.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/butterfly.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/calendar_spread.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/spread.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/straddle.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/strangle.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/surface.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/svi.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/py.typed +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/interest_rate.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/nelson_siegel.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/yield_curve.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/base.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/bns.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/cir.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/copula.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/dsp.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/heston.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/jump_diffusion.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/ou.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/poisson.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/wiener.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/base.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/ewma.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/kalman.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/ohlc.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/paths.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/supersmoother.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/bins.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/dates.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/distributions.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/functions.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/numbers.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/plot.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/transforms.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/types.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/__init__.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/conftest.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_futures.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_instruments.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_options.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/volsurface.json +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ai.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_bns.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_cir.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_copula.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_data.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_data_deribit.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_disable_outliers.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_distributions.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_frft.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_heston.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_implied_fwd.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_jump_diffusion.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ohlc.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_options_pricer.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ou.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_poisson.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_rates.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_strategies.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_surface_methods.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_svi.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_utils.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_vault.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_wiener.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/utils.py +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/readme.md +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/rops.toml +0 -0
- {quantflow-0.8.0 → quantflow-0.9.0}/taplo.toml +0 -0
|
@@ -45,15 +45,3 @@ applyTo: '/**'
|
|
|
45
45
|
|
|
46
46
|
* Strategy runtime markdown descriptions (read by `load_description()` at runtime) live inside the package at `quantflow/options/strategies/docs/` — they must be inside the package to be accessible when the library is installed
|
|
47
47
|
* mkdocs documentation pages live in `docs/api/options/` — do not mix these two locations
|
|
48
|
-
|
|
49
|
-
## Releasing
|
|
50
|
-
|
|
51
|
-
The release procedure is fully driven by `make release` and the `release.yml` workflow:
|
|
52
|
-
|
|
53
|
-
1. Bump `version` in `pyproject.toml` to the new release version.
|
|
54
|
-
2. Add a `## vX.Y.Z` section to `docs/release-notes.md` with the notes for the release. The header text is matched verbatim by the workflow's `awk` extractor, so it must be `## vX.Y.Z` exactly (no trailing dash, no title after the version).
|
|
55
|
-
3. Commit and merge to `main`.
|
|
56
|
-
4. From `main`, run `make release` — it reads the version from `pyproject.toml`, prompts for confirmation, then creates an annotated `vX.Y.Z` tag and pushes it.
|
|
57
|
-
5. The tag push triggers `.github/workflows/release.yml`, which runs lint and tests, publishes the package to PyPI (`make publish`), and posts the extracted `## vX.Y.Z` section as the GitHub Release body.
|
|
58
|
-
|
|
59
|
-
Do not publish to PyPI manually or via the old `head_commit.message == 'release'` flow — the tag-triggered workflow is the only supported path.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Release Instructions
|
|
2
|
+
|
|
3
|
+
Releases are driven by `v*` git tags. Pushing a tag triggers
|
|
4
|
+
`.github/workflows/release.yml`, which runs lint and the test suite, publishes
|
|
5
|
+
the package to PyPI (`make publish`), then extracts the matching `## vX.Y.Z`
|
|
6
|
+
section from `docs/release-notes.md` and publishes it as the GitHub Release
|
|
7
|
+
body.
|
|
8
|
+
|
|
9
|
+
## Cutting a release
|
|
10
|
+
|
|
11
|
+
1. Bump `version` in `pyproject.toml` to the new release version.
|
|
12
|
+
2. Add a `## vX.Y.Z` section at the top of `docs/release-notes.md` with the
|
|
13
|
+
notes for the release. The header text is matched verbatim by the
|
|
14
|
+
workflow's `awk` extractor, so it must be `## vX.Y.Z` exactly (no trailing
|
|
15
|
+
dash, no title after the version). The release workflow fails if this
|
|
16
|
+
section is missing.
|
|
17
|
+
3. Commit and merge to `main`; let the `build` workflow pass.
|
|
18
|
+
4. From `main`, run `make release` — it reads the version from
|
|
19
|
+
`pyproject.toml`, asks for confirmation, then creates an annotated `vX.Y.Z`
|
|
20
|
+
tag and pushes it. The `release` workflow takes it from there.
|
|
21
|
+
|
|
22
|
+
Do not publish to PyPI manually, and do not revive the old
|
|
23
|
+
`head_commit.message == 'release'` flow — the tag-triggered workflow is the
|
|
24
|
+
only supported path.
|
|
25
|
+
|
|
26
|
+
## Release-notes conventions
|
|
27
|
+
|
|
28
|
+
The `## vX.Y.Z` section in `docs/release-notes.md` is rendered both on the
|
|
29
|
+
docs site and as the GitHub Release body, so it must follow these conventions:
|
|
30
|
+
|
|
31
|
+
- Open with a one-paragraph summary describing the theme of the release. If
|
|
32
|
+
the release contains breaking changes, point readers to the **Breaking
|
|
33
|
+
changes** section in that paragraph.
|
|
34
|
+
- Group entries under H3 subsections in this order: `### Breaking changes`,
|
|
35
|
+
`### New features`, `### Improvements and fixes`,
|
|
36
|
+
`### Documentation and assets`. Omit any subsection that has no entries.
|
|
37
|
+
- Every PR reference must be a markdown link of the form
|
|
38
|
+
`[#NN](https://github.com/quantmind/quantflow/pull/NN)` — never a bare
|
|
39
|
+
`(#NN)`. GitHub's auto-linking only works in some contexts, and the explicit
|
|
40
|
+
URL works everywhere. When one entry references multiple PRs, list them
|
|
41
|
+
comma-separated inside one set of parentheses, each as its own link.
|
|
42
|
+
- Build the PR list by running `git log vPREV..HEAD --oneline` against the
|
|
43
|
+
previous release tag and following each squashed-merge commit back to its
|
|
44
|
+
PR. Cross-check with `gh pr list --state merged --base main` for any PRs
|
|
45
|
+
merged since the previous tag.
|
|
46
|
+
- End the section with a
|
|
47
|
+
`[Full changelog](https://github.com/quantmind/quantflow/compare/vPREV...vX.Y.Z)`
|
|
48
|
+
link comparing the new tag against the previous one.
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# Black Pricing
|
|
2
2
|
|
|
3
|
-
Here we define the log strike `k` as
|
|
4
|
-
|
|
3
|
+
Here we define the [log strike](../../glossary.md#log-strike) `k` as
|
|
4
|
+
|
|
5
|
+
\begin{equation}
|
|
5
6
|
k = \log{\frac{K}{F}}
|
|
6
|
-
|
|
7
|
+
\end{equation}
|
|
7
8
|
|
|
8
9
|
where $K$ is the strike price and $F$ is the forward price of the underlying asset.
|
|
9
|
-
We also refers to this log-strike as `moneyness`, since it is zero for at-the-money (ATM) options,
|
|
10
|
-
negative for in-the-money (ITM) call options, and positive for out-of-the-money (OTM) call options.
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
::: quantflow.options.bs.black_price
|
|
@@ -62,7 +62,7 @@ class PricingMethodComparison(BaseModel):
|
|
|
62
62
|
def _implied_vols(
|
|
63
63
|
self, r: OptionPricingResult, log_strikes: np.ndarray, ttm: float
|
|
64
64
|
) -> np.ndarray:
|
|
65
|
-
call = np.asarray(r.
|
|
65
|
+
call = np.asarray(r.call_price(log_strikes))
|
|
66
66
|
intrinsic = np.maximum(0.0, 1.0 - np.exp(log_strikes))
|
|
67
67
|
call = np.clip(call, intrinsic, 1.0)
|
|
68
68
|
return implied_black_volatility(log_strikes, call, ttm, 0.5, 1.0).values
|
|
@@ -75,10 +75,10 @@ class PricingMethodComparison(BaseModel):
|
|
|
75
75
|
ttm: float,
|
|
76
76
|
) -> float:
|
|
77
77
|
iv = implied_black_volatility(
|
|
78
|
-
log_strikes, np.asarray(r.
|
|
78
|
+
log_strikes, np.asarray(r.call_price(log_strikes)), ttm, 0.5, 1.0
|
|
79
79
|
).values
|
|
80
80
|
iv_ref = implied_black_volatility(
|
|
81
|
-
log_strikes, np.asarray(ref.
|
|
81
|
+
log_strikes, np.asarray(ref.call_price(log_strikes)), ttm, 0.5, 1.0
|
|
82
82
|
).values
|
|
83
83
|
finite = np.isfinite(iv) & np.isfinite(iv_ref)
|
|
84
84
|
return float(np.max(np.abs(iv[finite] - iv_ref[finite])))
|
|
@@ -90,7 +90,7 @@ class PricingMethodComparison(BaseModel):
|
|
|
90
90
|
log_strikes = ms.option_support(
|
|
91
91
|
self.ref_n + 1, max_log_strike=max_log_strike
|
|
92
92
|
)
|
|
93
|
-
ref = ms.call_option(self.ref_n,
|
|
93
|
+
ref = ms.call_option(self.ref_n, max_moneyness=self.max_moneyness)
|
|
94
94
|
iv_ref = self._implied_vols(ref, log_strikes, ttm)
|
|
95
95
|
moneyness_ref = log_strikes / np.sqrt(ttm)
|
|
96
96
|
ttm_label = f"TTM={ttm}"
|
|
@@ -121,7 +121,7 @@ class PricingMethodComparison(BaseModel):
|
|
|
121
121
|
for n in self.ns:
|
|
122
122
|
r = ms.call_option(
|
|
123
123
|
n,
|
|
124
|
-
|
|
124
|
+
max_moneyness=self.max_moneyness,
|
|
125
125
|
pricing_method=method,
|
|
126
126
|
)
|
|
127
127
|
ks = (
|
|
@@ -2,7 +2,8 @@ import json
|
|
|
2
2
|
|
|
3
3
|
from docs.examples._utils import assets_path, print_model
|
|
4
4
|
from quantflow.options.calibration import BNS2Calibration
|
|
5
|
-
from quantflow.options.
|
|
5
|
+
from quantflow.options.calibration.base import ResidualKind
|
|
6
|
+
from quantflow.options.pricer import OptionPricer, OptionPricingMethod
|
|
6
7
|
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
|
|
7
8
|
from quantflow.sp.bns import BNS, BNS2
|
|
8
9
|
|
|
@@ -23,14 +24,16 @@ pricer = OptionPricer(
|
|
|
23
24
|
model=BNS2(
|
|
24
25
|
bns1=BNS.create(vol=0.45, kappa=20.0, decay=10.0, rho=-0.6),
|
|
25
26
|
bns2=BNS.create(vol=0.45, kappa=0.3, decay=10.0, rho=0.3),
|
|
26
|
-
weight=0.
|
|
27
|
-
)
|
|
27
|
+
weight=0.5,
|
|
28
|
+
),
|
|
29
|
+
method=OptionPricingMethod.COS,
|
|
28
30
|
)
|
|
29
31
|
|
|
30
32
|
calibration: BNS2Calibration[BNS2] = BNS2Calibration(
|
|
31
33
|
pricer=pricer,
|
|
32
34
|
vol_surface=surface,
|
|
33
|
-
moneyness_weight=0.
|
|
35
|
+
moneyness_weight=0.3,
|
|
36
|
+
residual_kind=ResidualKind.IV,
|
|
34
37
|
)
|
|
35
38
|
|
|
36
39
|
result = calibration.fit()
|
|
@@ -2,7 +2,7 @@ import json
|
|
|
2
2
|
|
|
3
3
|
from docs.examples._utils import assets_path, print_model
|
|
4
4
|
from quantflow.options.calibration import BNSCalibration
|
|
5
|
-
from quantflow.options.pricer import OptionPricer
|
|
5
|
+
from quantflow.options.pricer import OptionPricer, OptionPricingMethod
|
|
6
6
|
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
|
|
7
7
|
from quantflow.sp.bns import BNS
|
|
8
8
|
|
|
@@ -16,6 +16,7 @@ surface.disable_outliers()
|
|
|
16
16
|
# Create a BNS pricer with initial parameters
|
|
17
17
|
pricer = OptionPricer(
|
|
18
18
|
model=BNS.create(vol=0.5, kappa=1.0, decay=10.0, rho=-0.2),
|
|
19
|
+
method=OptionPricingMethod.COS,
|
|
19
20
|
)
|
|
20
21
|
|
|
21
22
|
calibration: BNSCalibration[BNS] = BNSCalibration(
|
|
@@ -2,10 +2,12 @@ import json
|
|
|
2
2
|
|
|
3
3
|
from docs.examples._utils import assets_path, print_model
|
|
4
4
|
from quantflow.options.calibration import HestonJCalibration
|
|
5
|
+
from quantflow.options.calibration.base import ResidualKind
|
|
5
6
|
from quantflow.options.pricer import OptionPricer
|
|
6
7
|
from quantflow.options.surface import VolSurface, VolSurfaceInputs, surface_from_inputs
|
|
7
8
|
from quantflow.sp.heston import HestonJ
|
|
8
9
|
from quantflow.utils.distributions import DoubleExponential
|
|
10
|
+
from quantflow.utils.marginal import OptionPricingMethod
|
|
9
11
|
|
|
10
12
|
# Load a saved volatility surface snapshot and build the surface
|
|
11
13
|
with open("docs/examples/volsurface.json") as fp:
|
|
@@ -24,14 +26,15 @@ pricer = OptionPricer(
|
|
|
24
26
|
sigma=0.8,
|
|
25
27
|
jump_fraction=0.3,
|
|
26
28
|
jump_asymmetry=0.2,
|
|
27
|
-
)
|
|
29
|
+
),
|
|
30
|
+
method=OptionPricingMethod.COS,
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
# Set up the calibration, dropping the first (very short) maturity
|
|
31
34
|
calibration: HestonJCalibration[DoubleExponential] = HestonJCalibration(
|
|
32
35
|
pricer=pricer,
|
|
33
36
|
vol_surface=surface,
|
|
34
|
-
|
|
37
|
+
residual_kind=ResidualKind.IV,
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
result = calibration.fit()
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Release Notes
|
|
2
|
+
|
|
3
|
+
This page is the source of truth for quantflow release notes. Each section
|
|
4
|
+
below maps to a tagged release on
|
|
5
|
+
[GitHub](https://github.com/quantmind/quantflow/releases). When a new tag is
|
|
6
|
+
pushed, the matching section is extracted by
|
|
7
|
+
`.github/workflows/release.yml` and published as the GitHub Release body.
|
|
8
|
+
|
|
9
|
+
## v0.9.0
|
|
10
|
+
|
|
11
|
+
Pricing-engine and calibration overhaul. `MaturityPricer` now evaluates call
|
|
12
|
+
prices and Greeks lazily at arbitrary log-strikes instead of carrying a
|
|
13
|
+
precomputed grid, Fourier pricers take a moneyness-based truncation parameter,
|
|
14
|
+
and the volatility-surface calibration can fit Black implied vols directly.
|
|
15
|
+
This release contains several API changes: see **Breaking changes** below.
|
|
16
|
+
|
|
17
|
+
### Breaking changes
|
|
18
|
+
|
|
19
|
+
**`MaturityPricer` reworked.** ([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
20
|
+
|
|
21
|
+
- The precomputed `std`, `log_strike` and `call` arrays are gone. A
|
|
22
|
+
`MaturityPricer` now holds a single `pricing` field (an
|
|
23
|
+
`OptionPricingResult`) that evaluates call prices and Greeks on demand at
|
|
24
|
+
any log-strike.
|
|
25
|
+
- `moneyness` is now a method, `moneyness(log_strikes)`, not a cached array
|
|
26
|
+
property. The `time_value` and `intrinsic_value` array properties and the
|
|
27
|
+
`interp(...)` helper were removed; use `prices(log_strikes)` to get a
|
|
28
|
+
DataFrame of prices and implied vols on a chosen log-strike grid.
|
|
29
|
+
|
|
30
|
+
**Fourier pricing truncation: `max_log_strike` → moneyness parameters.**
|
|
31
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
32
|
+
|
|
33
|
+
- `Marginal1D.call_option`, `call_option_carr_madan` and `call_option_lewis`
|
|
34
|
+
take `max_moneyness` (a multiple of the marginal standard deviation)
|
|
35
|
+
instead of `max_log_strike`. The COS path takes
|
|
36
|
+
`cos_moneyness_std_precision` instead.
|
|
37
|
+
- `OptionPricingResult.call_at(...)` is renamed `call_price(...)`, the
|
|
38
|
+
`method` field is removed, and a new abstract `call_greeks(log_strike)`
|
|
39
|
+
returns a `Greeks` namedtuple `(price, delta, gamma)`.
|
|
40
|
+
|
|
41
|
+
**`OptionPricerBase.call_price` → `call_prices`.**
|
|
42
|
+
([#59](https://github.com/quantmind/quantflow/pull/59)) The method is now
|
|
43
|
+
vectorised: it takes arrays of times-to-maturity and log-strikes and prices
|
|
44
|
+
them in a single maturity-grouped call.
|
|
45
|
+
|
|
46
|
+
**`DIVFMPricer` no longer builds a fixed moneyness grid.**
|
|
47
|
+
([#59](https://github.com/quantmind/quantflow/pull/59)) The
|
|
48
|
+
`max_moneyness_ttm` and `n` fields are removed; the fitted IV surface is
|
|
49
|
+
evaluated on demand through `OptionPricingResultDIVFM`.
|
|
50
|
+
|
|
51
|
+
### New features
|
|
52
|
+
|
|
53
|
+
- **Implied-vol calibration residuals.** New `ResidualKind` enum and a
|
|
54
|
+
`residual_kind` field on `VolModelCalibration`: set it to `ResidualKind.IV`
|
|
55
|
+
to fit the model to Black implied vols (recovered by inverting the model
|
|
56
|
+
price) rather than to forward-space prices. The IV residual is naturally
|
|
57
|
+
well-scaled across moneyness, so `moneyness_weight` is not applied in that
|
|
58
|
+
mode. ([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
59
|
+
- **Greeks from the pricing result.** `OptionPricingCosResult.call_greeks`
|
|
60
|
+
returns closed-form price, delta and gamma from the COS expansion; the
|
|
61
|
+
transform-based result derives delta and gamma by differentiating the call
|
|
62
|
+
grid; DIVFM uses finite differences on the fitted surface.
|
|
63
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
64
|
+
- **COS truncation control on `OptionPricer`.** New
|
|
65
|
+
`cos_moneyness_std_precision` field (default 12) sets the width of the COS
|
|
66
|
+
integration interval in standard deviations.
|
|
67
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
68
|
+
|
|
69
|
+
### Improvements and fixes
|
|
70
|
+
|
|
71
|
+
- Calibration residuals are now computed in a single vectorised pricing call.
|
|
72
|
+
Deep-wing strikes where the model price falls outside the no-arbitrage band
|
|
73
|
+
(so Newton fails to invert it) are masked out instead of poisoning the fit,
|
|
74
|
+
and a parameter set that fails to invert on more than half the options is
|
|
75
|
+
rejected with a large penalty.
|
|
76
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
77
|
+
- Calibration plots now evaluate the model on a fresh moneyness grid;
|
|
78
|
+
`plot(max_moneyness=...)` no longer accepts `None`.
|
|
79
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
80
|
+
- `OptionEntry.mid_price()` no longer caches through a private attribute.
|
|
81
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
82
|
+
- Stale Jupytext notebook mirrors under `notebooks/` removed.
|
|
83
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
84
|
+
|
|
85
|
+
### Documentation and assets
|
|
86
|
+
|
|
87
|
+
- New GitHub social-preview banner under `docs/assets/logos/png/`.
|
|
88
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
89
|
+
- `docs/api/options/black.md` and the volatility-surface calibration examples
|
|
90
|
+
updated for the new pricer API.
|
|
91
|
+
([#59](https://github.com/quantmind/quantflow/pull/59))
|
|
92
|
+
- The release procedure moved out of `.github/copilot-instructions.md` into
|
|
93
|
+
its own `.github/instructions/release.instructions.md`.
|
|
94
|
+
|
|
95
|
+
[Full changelog](https://github.com/quantmind/quantflow/compare/v0.8.0...v0.9.0)
|
|
96
|
+
|
|
97
|
+
## v0.8.0
|
|
98
|
+
|
|
99
|
+
Volatility-surface calibration overhaul. This release adds a two-factor BNS
|
|
100
|
+
model, a double-Heston model (with optional jumps), Lewis and COS pricing
|
|
101
|
+
methods, and reworks the calibration package layout. Several module renames
|
|
102
|
+
and signature changes were made along the way: see **Breaking changes** below.
|
|
103
|
+
|
|
104
|
+
### Breaking changes
|
|
105
|
+
|
|
106
|
+
**Module renames.**
|
|
107
|
+
|
|
108
|
+
- `quantflow.sp.weiner` is now `quantflow.sp.wiener` (typo fix). Update
|
|
109
|
+
imports.
|
|
110
|
+
- `quantflow.options.calibration` is now a package, not a single module.
|
|
111
|
+
Top-level imports keep working through the package `__init__.py`
|
|
112
|
+
re-exports. Code reaching into the old `quantflow.options.heston_calibration`
|
|
113
|
+
must switch to `quantflow.options.calibration.heston`.
|
|
114
|
+
|
|
115
|
+
**`ModelOptionPrice` field rename.** ([#47](https://github.com/quantmind/quantflow/pull/47))
|
|
116
|
+
|
|
117
|
+
- `ModelOptionPrice.moneyness` previously meant `log(K/F)`. It now means
|
|
118
|
+
standardised moneyness `log(K/F) / sqrt(ttm)`, and the raw log-strike is
|
|
119
|
+
exposed as a new field `log_strike`. Code reading `option.moneyness` and
|
|
120
|
+
expecting a log-strike must switch to `option.log_strike`.
|
|
121
|
+
- `get_intrinsic_value(moneyness=...)` argument renamed to `log_strike=...`.
|
|
122
|
+
|
|
123
|
+
### New features
|
|
124
|
+
|
|
125
|
+
- **`BNS2`**: two-factor Barndorff-Nielsen & Shephard stochastic-volatility
|
|
126
|
+
model with a single Brownian motion driving a convex combination of
|
|
127
|
+
independent Gamma-OU variances and per-factor leverage. New section in the
|
|
128
|
+
BNS calibration tutorial. ([#54](https://github.com/quantmind/quantflow/pull/54))
|
|
129
|
+
- **`DoubleHeston` and `DoubleHestonJ`**: two-factor Heston (with optional
|
|
130
|
+
log-price jumps) and matching `DoubleHestonCalibration` /
|
|
131
|
+
`DoubleHestonJCalibration`. ([#46](https://github.com/quantmind/quantflow/pull/46))
|
|
132
|
+
- **Lewis and COS option-pricing methods**: selectable via
|
|
133
|
+
`OptionPricingMethod`, alongside the existing Carr-Madan / FFT path.
|
|
134
|
+
([#47](https://github.com/quantmind/quantflow/pull/47))
|
|
135
|
+
- **CIR tutorial** with PDF comparison example.
|
|
136
|
+
([#49](https://github.com/quantmind/quantflow/pull/49))
|
|
137
|
+
|
|
138
|
+
### Improvements and fixes
|
|
139
|
+
|
|
140
|
+
- Heston calibration convergence fixes.
|
|
141
|
+
([#45](https://github.com/quantmind/quantflow/pull/45),
|
|
142
|
+
[#49](https://github.com/quantmind/quantflow/pull/49))
|
|
143
|
+
- BNS calibration: dedicated `BNSCalibration` class extracted, characteristic
|
|
144
|
+
exponent derivation cleaned up, broader test coverage.
|
|
145
|
+
([#50](https://github.com/quantmind/quantflow/pull/50),
|
|
146
|
+
[#51](https://github.com/quantmind/quantflow/pull/51))
|
|
147
|
+
- OU module reworked: clearer Gamma-OU API, stronger tests for moments and
|
|
148
|
+
the integrated Laplace transform.
|
|
149
|
+
([#51](https://github.com/quantmind/quantflow/pull/51))
|
|
150
|
+
- `pricing_method_comparison` example simplified; redundant time-comparison
|
|
151
|
+
code removed. ([#48](https://github.com/quantmind/quantflow/pull/48))
|
|
152
|
+
|
|
153
|
+
### Documentation and assets
|
|
154
|
+
|
|
155
|
+
- New logo set (favicon, lockup, marks, social banners) under
|
|
156
|
+
`docs/assets/logos/`. ([#53](https://github.com/quantmind/quantflow/pull/53))
|
|
157
|
+
- New `docs/mcp.md` page covering the MCP server.
|
|
158
|
+
- Bibliography rebuilt from BibTeX via `docs/bib2md.py`; glossary expanded;
|
|
159
|
+
mathjax tweaks for inline rendering.
|
|
160
|
+
([#47](https://github.com/quantmind/quantflow/pull/47),
|
|
161
|
+
[#49](https://github.com/quantmind/quantflow/pull/49))
|
|
162
|
+
- Tutorial-writing instructions added at
|
|
163
|
+
`.github/instructions/tutorial.instructions.md`.
|
|
164
|
+
|
|
165
|
+
[Full changelog](https://github.com/quantmind/quantflow/compare/v0.7.0...v0.8.0)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import enum
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from decimal import Decimal
|
|
@@ -13,12 +14,26 @@ from scipy.optimize import Bounds, OptimizeResult, least_squares, minimize
|
|
|
13
14
|
from quantflow.sp.base import StochasticProcess1D
|
|
14
15
|
from quantflow.utils import plot
|
|
15
16
|
|
|
17
|
+
from ..bs import implied_black_volatility
|
|
16
18
|
from ..pricer import OptionPricerBase
|
|
17
19
|
from ..surface import OptionPrice, VolSurface
|
|
18
20
|
|
|
19
21
|
M = TypeVar("M", bound=StochasticProcess1D)
|
|
20
22
|
|
|
21
23
|
|
|
24
|
+
class ResidualKind(enum.StrEnum):
|
|
25
|
+
"""How calibration residuals are measured against the market"""
|
|
26
|
+
|
|
27
|
+
PRICE = enum.auto()
|
|
28
|
+
"""Residual on forward-space option prices: `weight * (model_price - mid_price)`"""
|
|
29
|
+
|
|
30
|
+
IV = enum.auto()
|
|
31
|
+
"""Residual on Black implied volatilities: `weight * (model_iv - mid_iv)`,
|
|
32
|
+
where `model_iv` is recovered from the model price by Black-Scholes
|
|
33
|
+
inversion. Naturally well-scaled across moneyness, so `moneyness_weight`
|
|
34
|
+
is usually unnecessary."""
|
|
35
|
+
|
|
36
|
+
|
|
22
37
|
class ModelCalibrationEntryKey(NamedTuple):
|
|
23
38
|
maturity: datetime
|
|
24
39
|
strike: Decimal
|
|
@@ -36,7 +51,6 @@ class OptionEntry(BaseModel):
|
|
|
36
51
|
log_strike: float = Field(description="Log-strike as log(K/F)")
|
|
37
52
|
options: list[OptionPrice] = Field(default_factory=list)
|
|
38
53
|
"""Bid and ask option prices for this entry"""
|
|
39
|
-
_mid_price: float | None = PrivateAttr(default=None)
|
|
40
54
|
|
|
41
55
|
def implied_vol_range(self) -> Bounds:
|
|
42
56
|
"""Get the range of implied volatilities across bid and ask"""
|
|
@@ -45,10 +59,8 @@ class OptionEntry(BaseModel):
|
|
|
45
59
|
|
|
46
60
|
def mid_price(self) -> float:
|
|
47
61
|
"""Mid price as the average of bid and ask call prices"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self._mid_price = sum(prices) / len(prices)
|
|
51
|
-
return self._mid_price
|
|
62
|
+
prices = tuple(float(option.call_price) for option in self.options)
|
|
63
|
+
return sum(prices) / len(prices)
|
|
52
64
|
|
|
53
65
|
def mid_iv(self) -> float:
|
|
54
66
|
"""Mid implied volatility as the average of bid and ask"""
|
|
@@ -81,6 +93,18 @@ class VolModelCalibration(BaseModel, ABC, Generic[M]):
|
|
|
81
93
|
" to calibrate the model with"
|
|
82
94
|
),
|
|
83
95
|
)
|
|
96
|
+
residual_kind: ResidualKind = Field(
|
|
97
|
+
default=ResidualKind.PRICE,
|
|
98
|
+
description=(
|
|
99
|
+
"Kind of residual used by the calibration cost function."
|
|
100
|
+
" `price` (default) measures the residual on forward-space"
|
|
101
|
+
" option prices and applies the `moneyness_weight` cost weights;"
|
|
102
|
+
" `iv` measures it on Black implied volatilities by inverting"
|
|
103
|
+
" the model price. The `iv` residual is already in vol units"
|
|
104
|
+
" and is naturally well-scaled across moneyness, so the"
|
|
105
|
+
" `moneyness_weight` cost weights are not applied in that mode."
|
|
106
|
+
),
|
|
107
|
+
)
|
|
84
108
|
moneyness_weight: float = Field(
|
|
85
109
|
default=0.0,
|
|
86
110
|
ge=0.0,
|
|
@@ -108,6 +132,11 @@ class VolModelCalibration(BaseModel, ABC, Generic[M]):
|
|
|
108
132
|
repr=False,
|
|
109
133
|
description="The options to calibrate",
|
|
110
134
|
)
|
|
135
|
+
_log_strikes: np.ndarray = PrivateAttr(default_factory=lambda: np.empty(0))
|
|
136
|
+
_ttms: np.ndarray = PrivateAttr(default_factory=lambda: np.empty(0))
|
|
137
|
+
_moneyness: np.ndarray = PrivateAttr(default_factory=lambda: np.empty(0))
|
|
138
|
+
_mid_prices: np.ndarray = PrivateAttr(default_factory=lambda: np.empty(0))
|
|
139
|
+
_mid_ivs: np.ndarray = PrivateAttr(default_factory=lambda: np.empty(0))
|
|
111
140
|
|
|
112
141
|
def model_post_init(self, _ctx: Any) -> None:
|
|
113
142
|
if not self.options:
|
|
@@ -118,6 +147,12 @@ class VolModelCalibration(BaseModel, ABC, Generic[M]):
|
|
|
118
147
|
entry = OptionEntry(ttm=option.ttm, log_strike=option.log_strike)
|
|
119
148
|
self.options[key] = entry
|
|
120
149
|
entry.options.append(option)
|
|
150
|
+
entries = tuple(self.options.values())
|
|
151
|
+
self._log_strikes = np.asarray([e.log_strike for e in entries])
|
|
152
|
+
self._ttms = np.asarray([e.ttm for e in entries])
|
|
153
|
+
self._moneyness = self._log_strikes / np.sqrt(self._ttms)
|
|
154
|
+
self._mid_prices = np.asarray([e.mid_price() for e in entries])
|
|
155
|
+
self._mid_ivs = np.asarray([e.mid_iv() for e in entries])
|
|
121
156
|
|
|
122
157
|
@abstractmethod
|
|
123
158
|
def get_params(self) -> np.ndarray:
|
|
@@ -192,52 +227,84 @@ class VolModelCalibration(BaseModel, ABC, Generic[M]):
|
|
|
192
227
|
weight = np.exp(self.moneyness_weight * moneyness * moneyness)
|
|
193
228
|
return float(min(weight, self.max_cost_weight))
|
|
194
229
|
|
|
230
|
+
def cost_weights(self) -> np.ndarray:
|
|
231
|
+
"""Vector of cost weights for all calibration options"""
|
|
232
|
+
weights = np.exp(self.moneyness_weight * self._moneyness * self._moneyness)
|
|
233
|
+
return np.minimum(weights, self.max_cost_weight)
|
|
234
|
+
|
|
195
235
|
def penalize(self) -> float:
|
|
196
236
|
"""Additional scalar penalty added to the cost function (default: 0)"""
|
|
197
237
|
return 0.0
|
|
198
238
|
|
|
199
239
|
def residuals(self, params: np.ndarray) -> np.ndarray:
|
|
200
|
-
"""Weighted
|
|
240
|
+
"""Weighted residuals per option, in price or implied-vol space.
|
|
241
|
+
|
|
242
|
+
Controlled by `residual_kind`:
|
|
243
|
+
|
|
244
|
+
- `price`: `weight * (model_price - mid_price)`
|
|
245
|
+
- `iv`: `weight * (model_iv - mid_iv)`, where `model_iv` is the
|
|
246
|
+
Black implied volatility of the model price.
|
|
247
|
+
"""
|
|
201
248
|
self.set_params(params)
|
|
202
249
|
self.pricer.reset()
|
|
203
|
-
res = []
|
|
204
250
|
with np.errstate(all="ignore"):
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
251
|
+
try:
|
|
252
|
+
model_prices = self.pricer.call_prices(self._ttms, self._log_strikes)
|
|
253
|
+
except ValueError:
|
|
254
|
+
return np.full(self._log_strikes.shape, 1e6)
|
|
255
|
+
if self.residual_kind is ResidualKind.IV:
|
|
256
|
+
implied = implied_black_volatility(
|
|
257
|
+
self._log_strikes,
|
|
258
|
+
model_prices,
|
|
259
|
+
self._ttms,
|
|
260
|
+
initial_sigma=self._mid_ivs,
|
|
261
|
+
call_put=1,
|
|
262
|
+
)
|
|
263
|
+
# Fourier pricers can return prices outside the no-arb band
|
|
264
|
+
# for deep-wing strikes, where Newton fails to invert. Mask
|
|
265
|
+
# those points out (zero residual). If fewer than half of the
|
|
266
|
+
# options invert successfully the parameter set is treated as
|
|
267
|
+
# invalid and a large penalty is returned.
|
|
268
|
+
ok = implied.converged
|
|
269
|
+
if 2 * int(ok.sum()) < ok.size:
|
|
270
|
+
return np.full(self._log_strikes.shape, 1e6)
|
|
271
|
+
r = np.where(ok, implied.values - self._mid_ivs, 0.0)
|
|
272
|
+
else:
|
|
273
|
+
r = self.cost_weights() * (model_prices - self._mid_prices)
|
|
274
|
+
return np.where(np.isfinite(r), r, 1e6)
|
|
211
275
|
|
|
212
276
|
def cost_function(self, params: np.ndarray) -> float:
|
|
213
277
|
"""Scalar cost: sum of squared residuals plus any penalty"""
|
|
214
278
|
r = self.residuals(params)
|
|
215
279
|
return float(np.dot(r, r)) + self.penalize()
|
|
216
280
|
|
|
281
|
+
def _model_grid(
|
|
282
|
+
self, ttm: float, max_moneyness: float, support: int
|
|
283
|
+
) -> pd.DataFrame:
|
|
284
|
+
log_strikes = np.linspace(-max_moneyness, max_moneyness, support) * np.sqrt(ttm)
|
|
285
|
+
return self.pricer.maturity(ttm).prices(log_strikes)
|
|
286
|
+
|
|
217
287
|
def plot(
|
|
218
288
|
self,
|
|
219
289
|
index: int = 0,
|
|
220
290
|
*,
|
|
221
|
-
max_moneyness: float
|
|
291
|
+
max_moneyness: float = 1.0,
|
|
222
292
|
support: int = 51,
|
|
223
293
|
**kwargs: Any,
|
|
224
294
|
) -> Any:
|
|
225
295
|
"""Plot implied volatility for market and model prices"""
|
|
226
296
|
cross = self.vol_surface.maturities[index]
|
|
227
297
|
options = tuple(self.vol_surface.option_prices(index=index, converged=True))
|
|
228
|
-
model = self.pricer.maturity(cross.ttm(self.ref_date))
|
|
229
|
-
if max_moneyness is not None:
|
|
230
|
-
model = model.max_moneyness(max_moneyness=max_moneyness, support=support)
|
|
231
298
|
return plot.plot_vol_surface(
|
|
232
299
|
pd.DataFrame([d.info_dict() for d in options]),
|
|
233
|
-
model=
|
|
300
|
+
model=self._model_grid(cross.ttm(self.ref_date), max_moneyness, support),
|
|
234
301
|
**kwargs,
|
|
235
302
|
)
|
|
236
303
|
|
|
237
304
|
def plot_maturities(
|
|
238
305
|
self,
|
|
239
306
|
*,
|
|
240
|
-
max_moneyness: float
|
|
307
|
+
max_moneyness: float = 1.0,
|
|
241
308
|
support: int = 51,
|
|
242
309
|
cols: int = 2,
|
|
243
310
|
row_height: int = 400,
|
|
@@ -257,15 +324,11 @@ class VolModelCalibration(BaseModel, ABC, Generic[M]):
|
|
|
257
324
|
row = i // cols + 1
|
|
258
325
|
col = i % cols + 1
|
|
259
326
|
options = tuple(self.vol_surface.option_prices(index=i, converged=True))
|
|
260
|
-
model = self.pricer.maturity(cross.ttm(self.ref_date))
|
|
261
|
-
if max_moneyness is not None:
|
|
262
|
-
model = model.max_moneyness(
|
|
263
|
-
max_moneyness=max_moneyness, support=support
|
|
264
|
-
)
|
|
265
|
-
|
|
266
327
|
plot.plot_vol_surface(
|
|
267
328
|
pd.DataFrame([d.info_dict() for d in options]),
|
|
268
|
-
model=
|
|
329
|
+
model=self._model_grid(
|
|
330
|
+
cross.ttm(self.ref_date), max_moneyness, support
|
|
331
|
+
),
|
|
269
332
|
fig=fig,
|
|
270
333
|
fig_params={"row": row, "col": col},
|
|
271
334
|
**kwargs,
|