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.
Files changed (301) hide show
  1. {quantflow-0.8.0 → quantflow-0.9.0}/.github/copilot-instructions.md +0 -12
  2. quantflow-0.9.0/.github/instructions/release.instructions.md +48 -0
  3. {quantflow-0.8.0 → quantflow-0.9.0}/CLAUDE.md +1 -0
  4. {quantflow-0.8.0 → quantflow-0.9.0}/PKG-INFO +1 -1
  5. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/black.md +4 -5
  6. quantflow-0.9.0/docs/assets/logos/png/quantflow-github-social-1280x640.png +0 -0
  7. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/pricing_method_comparison.py +5 -5
  8. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_bns2_calibration.py +7 -4
  9. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_bns_calibration.py +2 -1
  10. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_heston_calibration.py +1 -0
  11. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_hestonj_calibration.py +5 -2
  12. quantflow-0.9.0/docs/release-notes.md +165 -0
  13. {quantflow-0.8.0 → quantflow-0.9.0}/pyproject.toml +1 -1
  14. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/base.py +89 -26
  15. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/pricer.py +48 -33
  16. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/pricer.py +60 -117
  17. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/marginal.py +127 -38
  18. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_divfm.py +13 -10
  19. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_options.py +3 -2
  20. {quantflow-0.8.0 → quantflow-0.9.0}/uv.lock +18 -18
  21. quantflow-0.8.0/docs/release-notes.md +0 -68
  22. quantflow-0.8.0/notebooks/applications/calibration.md +0 -204
  23. quantflow-0.8.0/notebooks/applications/calibration.py +0 -189
  24. quantflow-0.8.0/notebooks/applications/volatility_surface.md +0 -144
  25. quantflow-0.8.0/notebooks/data/fed.md +0 -40
  26. quantflow-0.8.0/notebooks/data/fiscal_data.md +0 -31
  27. quantflow-0.8.0/notebooks/data/fmp.md +0 -80
  28. quantflow-0.8.0/notebooks/data/timeseries.md +0 -39
  29. quantflow-0.8.0/notebooks/models/bns.md +0 -111
  30. quantflow-0.8.0/notebooks/models/cir.md +0 -186
  31. quantflow-0.8.0/notebooks/models/gousv.md +0 -48
  32. quantflow-0.8.0/notebooks/models/heston.md +0 -133
  33. quantflow-0.8.0/notebooks/models/heston_jumps.md +0 -78
  34. quantflow-0.8.0/notebooks/models/jump_diffusion.md +0 -101
  35. quantflow-0.8.0/notebooks/models/ou.md +0 -203
  36. quantflow-0.8.0/notebooks/models/overview.md +0 -19
  37. quantflow-0.8.0/notebooks/models/poisson.md +0 -269
  38. quantflow-0.8.0/notebooks/models/wiener.md +0 -73
  39. {quantflow-0.8.0 → quantflow-0.9.0}/.coveragerc +0 -0
  40. {quantflow-0.8.0 → quantflow-0.9.0}/.dockerignore +0 -0
  41. {quantflow-0.8.0 → quantflow-0.9.0}/.github/instructions/makefile.instructions.md +0 -0
  42. {quantflow-0.8.0 → quantflow-0.9.0}/.github/instructions/tutorial.instructions.md +0 -0
  43. {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/build.yml +0 -0
  44. {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/deploy.yml +0 -0
  45. {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/docker-multiarch.yml +0 -0
  46. {quantflow-0.8.0 → quantflow-0.9.0}/.github/workflows/release.yml +0 -0
  47. {quantflow-0.8.0 → quantflow-0.9.0}/.gitignore +0 -0
  48. {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/launch.json +0 -0
  49. {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/settings.json +0 -0
  50. {quantflow-0.8.0 → quantflow-0.9.0}/.vscode/tasks.json +0 -0
  51. {quantflow-0.8.0 → quantflow-0.9.0}/CITATION.cff +0 -0
  52. {quantflow-0.8.0 → quantflow-0.9.0}/LICENSE +0 -0
  53. {quantflow-0.8.0 → quantflow-0.9.0}/Makefile +0 -0
  54. {quantflow-0.8.0 → quantflow-0.9.0}/app/__main__.py +0 -0
  55. {quantflow-0.8.0 → quantflow-0.9.0}/app/cointegration.py +0 -0
  56. {quantflow-0.8.0 → quantflow-0.9.0}/app/double_exponential_sampling.py +0 -0
  57. {quantflow-0.8.0 → quantflow-0.9.0}/app/gaussian_sampling.py +0 -0
  58. {quantflow-0.8.0 → quantflow-0.9.0}/app/heston_divfm_fit.py +0 -0
  59. {quantflow-0.8.0 → quantflow-0.9.0}/app/heston_vol_surface.py +0 -0
  60. {quantflow-0.8.0 → quantflow-0.9.0}/app/hurst.py +0 -0
  61. {quantflow-0.8.0 → quantflow-0.9.0}/app/poisson_sampling.py +0 -0
  62. {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/__init__.py +0 -0
  63. {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/heston_divfm_fit.py +0 -0
  64. {quantflow-0.8.0 → quantflow-0.9.0}/app/scripts/test_comparison.py +0 -0
  65. {quantflow-0.8.0 → quantflow-0.9.0}/app/supersmoother.py +0 -0
  66. {quantflow-0.8.0 → quantflow-0.9.0}/app/utils/__init__.py +0 -0
  67. {quantflow-0.8.0 → quantflow-0.9.0}/app/utils/paths.py +0 -0
  68. {quantflow-0.8.0 → quantflow-0.9.0}/app/volatility_surface.py +0 -0
  69. {quantflow-0.8.0 → quantflow-0.9.0}/dev/blocks/quantflow.yaml +0 -0
  70. {quantflow-0.8.0 → quantflow-0.9.0}/dev/build-examples +0 -0
  71. {quantflow-0.8.0 → quantflow-0.9.0}/dev/charts.yaml +0 -0
  72. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/.helmignore +0 -0
  73. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/Chart.yaml +0 -0
  74. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/_helpers.tpl +0 -0
  75. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/_service.tpl +0 -0
  76. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/app.yaml +0 -0
  77. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/configmap.yaml +0 -0
  78. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/templates/secret.yaml +0 -0
  79. {quantflow-0.8.0 → quantflow-0.9.0}/dev/helm/values.yaml +0 -0
  80. {quantflow-0.8.0 → quantflow-0.9.0}/dev/install +0 -0
  81. {quantflow-0.8.0 → quantflow-0.9.0}/dev/lint +0 -0
  82. {quantflow-0.8.0 → quantflow-0.9.0}/dev/marimo +0 -0
  83. {quantflow-0.8.0 → quantflow-0.9.0}/dev/quantflow.dockerfile +0 -0
  84. {quantflow-0.8.0 → quantflow-0.9.0}/dev/test +0 -0
  85. {quantflow-0.8.0 → quantflow-0.9.0}/docs/__init__.py +0 -0
  86. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/deribit.md +0 -0
  87. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fed.md +0 -0
  88. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fmp.md +0 -0
  89. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/fred.md +0 -0
  90. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/data/index.md +0 -0
  91. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/index.md +0 -0
  92. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/calibration.md +0 -0
  93. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/divfm.md +0 -0
  94. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/index.md +0 -0
  95. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/pricer.md +0 -0
  96. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/svi.md +0 -0
  97. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/options/vol_surface.md +0 -0
  98. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/index.md +0 -0
  99. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/interest_rate.md +0 -0
  100. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/rates/yield_curve.md +0 -0
  101. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/bns.md +0 -0
  102. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/cir.md +0 -0
  103. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/compound_poisson.md +0 -0
  104. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/copula.md +0 -0
  105. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/dsp.md +0 -0
  106. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/heston.md +0 -0
  107. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/index.md +0 -0
  108. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/jump_diffusion.md +0 -0
  109. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/ou.md +0 -0
  110. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/poisson.md +0 -0
  111. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/sp/wiener.md +0 -0
  112. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/ewma.md +0 -0
  113. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/index.md +0 -0
  114. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/kalman.md +0 -0
  115. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/ohlc.md +0 -0
  116. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/paths.md +0 -0
  117. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/ta/supersmoother.md +0 -0
  118. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/bins.md +0 -0
  119. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/distributions.md +0 -0
  120. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/index.md +0 -0
  121. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/marginal1d.md +0 -0
  122. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/numbers.md +0 -0
  123. {quantflow-0.8.0 → quantflow-0.9.0}/docs/api/utils/types.md +0 -0
  124. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/heston.gif +0 -0
  125. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/README.md +0 -0
  126. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/favicon.ico +0 -0
  127. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/head-snippet.html +0 -0
  128. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/manifest.json +0 -0
  129. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-1024.png +0 -0
  130. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-128.png +0 -0
  131. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-256.png +0 -0
  132. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-app-icon-512.png +0 -0
  133. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-linkedin-banner.png +0 -0
  134. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-1200.png +0 -0
  135. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-dark-1200.png +0 -0
  136. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-dark-2400.png +0 -0
  137. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-tagline-dark-1600.png +0 -0
  138. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-lockup-tagline-light-1600.png +0 -0
  139. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-1024.png +0 -0
  140. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-128.png +0 -0
  141. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-16.png +0 -0
  142. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-256.png +0 -0
  143. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-32.png +0 -0
  144. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-48.png +0 -0
  145. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-512.png +0 -0
  146. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-64.png +0 -0
  147. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-1024.png +0 -0
  148. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-128.png +0 -0
  149. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-16.png +0 -0
  150. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-256.png +0 -0
  151. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-32.png +0 -0
  152. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-48.png +0 -0
  153. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-512.png +0 -0
  154. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/png/quantflow-mark-dark-64.png +0 -0
  155. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-app-icon.svg +0 -0
  156. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-favicon.svg +0 -0
  157. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-linkedin-banner.svg +0 -0
  158. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-dark.svg +0 -0
  159. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-tagline-dark.svg +0 -0
  160. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup-tagline-light.svg +0 -0
  161. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-lockup.svg +0 -0
  162. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-mark-dark.svg +0 -0
  163. {quantflow-0.8.0 → quantflow-0.9.0}/docs/assets/logos/quantflow-mark.svg +0 -0
  164. {quantflow-0.8.0 → quantflow-0.9.0}/docs/bib2md.py +0 -0
  165. {quantflow-0.8.0 → quantflow-0.9.0}/docs/bibliography.md +0 -0
  166. {quantflow-0.8.0 → quantflow-0.9.0}/docs/contributing.md +0 -0
  167. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/__init__.py +0 -0
  168. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/_utils.py +0 -0
  169. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/cir_pdf_comparison.py +0 -0
  170. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/fft.py +0 -0
  171. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/heston_volatility_pricer.py +0 -0
  172. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/vol_surface_inputs.py +0 -0
  173. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/volsurface.json +0 -0
  174. {quantflow-0.8.0 → quantflow-0.9.0}/docs/examples/wiener_volatility_pricer.py +0 -0
  175. {quantflow-0.8.0 → quantflow-0.9.0}/docs/favicon.ico +0 -0
  176. {quantflow-0.8.0 → quantflow-0.9.0}/docs/glossary.md +0 -0
  177. {quantflow-0.8.0 → quantflow-0.9.0}/docs/index.md +0 -0
  178. {quantflow-0.8.0 → quantflow-0.9.0}/docs/javascripts/mathjax.js +0 -0
  179. {quantflow-0.8.0 → quantflow-0.9.0}/docs/mcp.md +0 -0
  180. {quantflow-0.8.0 → quantflow-0.9.0}/docs/references.bib +0 -0
  181. {quantflow-0.8.0 → quantflow-0.9.0}/docs/robots.txt +0 -0
  182. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/characteristic.md +0 -0
  183. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/convexity_correction.md +0 -0
  184. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/index.md +0 -0
  185. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/inversion.md +0 -0
  186. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/levy.md +0 -0
  187. {quantflow-0.8.0 → quantflow-0.9.0}/docs/theory/option_pricing.md +0 -0
  188. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/bns_calibration.md +0 -0
  189. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/cir.md +0 -0
  190. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/index.md +0 -0
  191. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/option_pricing.md +0 -0
  192. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/pricing_method_comparison.md +0 -0
  193. {quantflow-0.8.0 → quantflow-0.9.0}/docs/tutorials/volatility_surface.md +0 -0
  194. {quantflow-0.8.0 → quantflow-0.9.0}/mkdocs.yml +0 -0
  195. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/__init__.py +0 -0
  196. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/__init__.py +0 -0
  197. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/server.py +0 -0
  198. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/__init__.py +0 -0
  199. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/base.py +0 -0
  200. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/charts.py +0 -0
  201. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/crypto.py +0 -0
  202. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/fred.py +0 -0
  203. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/stocks.py +0 -0
  204. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ai/tools/vault.py +0 -0
  205. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/__init__.py +0 -0
  206. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/deribit.py +0 -0
  207. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fed.py +0 -0
  208. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fiscal_data.py +0 -0
  209. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fmp.py +0 -0
  210. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/fred.py +0 -0
  211. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/data/vault.py +0 -0
  212. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/__init__.py +0 -0
  213. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/bs.py +0 -0
  214. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/__init__.py +0 -0
  215. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/bns.py +0 -0
  216. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/calibration/heston.py +0 -0
  217. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/__init__.py +0 -0
  218. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/network.py +0 -0
  219. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/trainer.py +0 -0
  220. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/divfm/weights.py +0 -0
  221. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/butterfly.md +0 -0
  222. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/calendar_spread.md +0 -0
  223. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/spread.md +0 -0
  224. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/straddle.md +0 -0
  225. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/strangle.md +0 -0
  226. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/docs/terminology.md +0 -0
  227. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/inputs.py +0 -0
  228. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/__init__.py +0 -0
  229. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/base.py +0 -0
  230. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/butterfly.py +0 -0
  231. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/calendar_spread.py +0 -0
  232. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/spread.py +0 -0
  233. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/straddle.py +0 -0
  234. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/strategies/strangle.py +0 -0
  235. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/surface.py +0 -0
  236. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/options/svi.py +0 -0
  237. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/py.typed +0 -0
  238. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/__init__.py +0 -0
  239. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/interest_rate.py +0 -0
  240. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/nelson_siegel.py +0 -0
  241. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/rates/yield_curve.py +0 -0
  242. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/__init__.py +0 -0
  243. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/base.py +0 -0
  244. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/bns.py +0 -0
  245. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/cir.py +0 -0
  246. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/copula.py +0 -0
  247. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/dsp.py +0 -0
  248. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/heston.py +0 -0
  249. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/jump_diffusion.py +0 -0
  250. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/ou.py +0 -0
  251. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/poisson.py +0 -0
  252. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/sp/wiener.py +0 -0
  253. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/__init__.py +0 -0
  254. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/base.py +0 -0
  255. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/ewma.py +0 -0
  256. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/kalman.py +0 -0
  257. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/ohlc.py +0 -0
  258. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/paths.py +0 -0
  259. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/ta/supersmoother.py +0 -0
  260. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/__init__.py +0 -0
  261. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/bins.py +0 -0
  262. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/dates.py +0 -0
  263. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/distributions.py +0 -0
  264. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/functions.py +0 -0
  265. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/numbers.py +0 -0
  266. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/plot.py +0 -0
  267. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/transforms.py +0 -0
  268. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow/utils/types.py +0 -0
  269. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/__init__.py +0 -0
  270. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/conftest.py +0 -0
  271. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_futures.json +0 -0
  272. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_instruments.json +0 -0
  273. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/deribit_options.json +0 -0
  274. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/fixtures/volsurface.json +0 -0
  275. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ai.py +0 -0
  276. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_bns.py +0 -0
  277. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_cir.py +0 -0
  278. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_copula.py +0 -0
  279. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_data.py +0 -0
  280. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_data_deribit.py +0 -0
  281. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_disable_outliers.py +0 -0
  282. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_distributions.py +0 -0
  283. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_frft.py +0 -0
  284. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_heston.py +0 -0
  285. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_implied_fwd.py +0 -0
  286. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_jump_diffusion.py +0 -0
  287. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ohlc.py +0 -0
  288. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_options_pricer.py +0 -0
  289. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_ou.py +0 -0
  290. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_poisson.py +0 -0
  291. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_rates.py +0 -0
  292. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_strategies.py +0 -0
  293. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_surface_methods.py +0 -0
  294. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_svi.py +0 -0
  295. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_utils.py +0 -0
  296. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_vault.py +0 -0
  297. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/test_wiener.py +0 -0
  298. {quantflow-0.8.0 → quantflow-0.9.0}/quantflow_tests/utils.py +0 -0
  299. {quantflow-0.8.0 → quantflow-0.9.0}/readme.md +0 -0
  300. {quantflow-0.8.0 → quantflow-0.9.0}/rops.toml +0 -0
  301. {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,4 +1,5 @@
1
1
  @readme.md
2
2
  @.github/copilot-instructions.md
3
3
  @.github/instructions/makefile.instructions.md
4
+ @.github/instructions/release.instructions.md
4
5
  @.github/instructions/tutorial.instructions.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantflow
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: quantitative analysis
5
5
  Project-URL: Homepage, https://github.com/quantmind/quantflow
6
6
  Project-URL: Repository, https://github.com/quantmind/quantflow
@@ -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.call_at(log_strikes))
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.call_at(log_strikes)), ttm, 0.5, 1.0
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.call_at(log_strikes)), ttm, 0.5, 1.0
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, max_log_strike=max_log_strike)
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
- max_log_strike=max_log_strike,
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.pricer import OptionPricer
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.3,
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.2,
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(
@@ -23,6 +23,7 @@ pricer = OptionPricer(
23
23
  calibration: HestonCalibration[Heston] = HestonCalibration(
24
24
  pricer=pricer,
25
25
  vol_surface=surface,
26
+ moneyness_weight=0.3,
26
27
  )
27
28
 
28
29
  result = calibration.fit()
@@ -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
- moneyness_weight=0.5,
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,6 +1,6 @@
1
1
  [project]
2
2
  name = "quantflow"
3
- version = "0.8.0"
3
+ version = "0.9.0"
4
4
  description = "quantitative analysis"
5
5
  authors = [ { name = "Luca Sbardella", email = "luca@quantmind.com" } ]
6
6
  license = "BSD-3-Clause"
@@ -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
- if self._mid_price is None:
49
- prices = tuple(float(option.call_price) for option in self.options)
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 price residuals: `weight * (model_price - mid_price)` per option"""
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
- for entry in self.options.values():
206
- model_price = self.pricer.call_price(entry.ttm, entry.log_strike)
207
- weight = self.cost_weight(entry.ttm, entry.log_strike)
208
- r = weight * (model_price - entry.mid_price())
209
- res.append(r if np.isfinite(r) else 1e6)
210
- return np.asarray(res)
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 | None = 1.0,
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=model.df,
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 | None = 1.0,
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=model.df,
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,