quantark 0.1.0__py3-none-any.whl

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 (399) hide show
  1. quantark/__init__.py +3 -0
  2. quantark/_compat.py +150 -0
  3. quantark/asset/__init__.py +8 -0
  4. quantark/asset/bond/__init__.py +2 -0
  5. quantark/asset/bond/engine/__init__.py +44 -0
  6. quantark/asset/bond/engine/analytical/__init__.py +12 -0
  7. quantark/asset/bond/engine/analytical/black_engine.py +583 -0
  8. quantark/asset/bond/engine/analytical/bond_forward_engine.py +390 -0
  9. quantark/asset/bond/engine/analytical/bond_futures_engine.py +569 -0
  10. quantark/asset/bond/engine/convertible/__init__.py +12 -0
  11. quantark/asset/bond/engine/convertible/convertible_bond_engine.py +800 -0
  12. quantark/asset/bond/engine/discount/__init__.py +10 -0
  13. quantark/asset/bond/engine/discount/bond_discount_engine.py +517 -0
  14. quantark/asset/bond/engine/discount/frn_engine.py +913 -0
  15. quantark/asset/bond/engine/pde/__init__.py +14 -0
  16. quantark/asset/bond/engine/pde/convertible/__init__.py +21 -0
  17. quantark/asset/bond/engine/pde/convertible/jump_diffusion_engine.py +603 -0
  18. quantark/asset/bond/engine/pde/convertible/pde_params.py +59 -0
  19. quantark/asset/bond/engine/pde/convertible/tf_engine.py +546 -0
  20. quantark/asset/bond/engine/tree/__init__.py +14 -0
  21. quantark/asset/bond/engine/tree/convertible/__init__.py +21 -0
  22. quantark/asset/bond/engine/tree/convertible/binomial_engine.py +488 -0
  23. quantark/asset/bond/engine/tree/convertible/tree_params.py +72 -0
  24. quantark/asset/bond/engine/tree/convertible/trinomial_engine.py +1341 -0
  25. quantark/asset/bond/product/__init__.py +37 -0
  26. quantark/asset/bond/product/base_bond_product.py +114 -0
  27. quantark/asset/bond/product/convertible/__init__.py +16 -0
  28. quantark/asset/bond/product/convertible/convertible_bond.py +595 -0
  29. quantark/asset/bond/product/couponbond/__init__.py +12 -0
  30. quantark/asset/bond/product/couponbond/fixed_bond.py +285 -0
  31. quantark/asset/bond/product/couponbond/frn.py +538 -0
  32. quantark/asset/bond/product/forward/__init__.py +9 -0
  33. quantark/asset/bond/product/forward/base_bond_forward.py +92 -0
  34. quantark/asset/bond/product/forward/bond_forward.py +335 -0
  35. quantark/asset/bond/product/futures/__init__.py +8 -0
  36. quantark/asset/bond/product/futures/bond_futures.py +532 -0
  37. quantark/asset/bond/product/option/__init__.py +9 -0
  38. quantark/asset/bond/product/option/euro_short_term_bond_option.py +231 -0
  39. quantark/asset/bond/riskmeasures/__init__.py +13 -0
  40. quantark/asset/bond/riskmeasures/bond_greeks_calculator.py +484 -0
  41. quantark/asset/bond/schedule/__init__.py +21 -0
  42. quantark/asset/bond/schedule/cashflow.py +595 -0
  43. quantark/asset/equity/__init__.py +11 -0
  44. quantark/asset/equity/analysis/__init__.py +4 -0
  45. quantark/asset/equity/analysis/autocallable_path_analyzer.py +257 -0
  46. quantark/asset/equity/engine/__init__.py +84 -0
  47. quantark/asset/equity/engine/analytical/__init__.py +37 -0
  48. quantark/asset/equity/engine/analytical/american_option_engine.py +682 -0
  49. quantark/asset/equity/engine/analytical/asian_option_analytical_engine.py +1102 -0
  50. quantark/asset/equity/engine/analytical/barrier_analytical_engine.py +455 -0
  51. quantark/asset/equity/engine/analytical/black_scholes_engine.py +322 -0
  52. quantark/asset/equity/engine/analytical/deltaone_engine.py +340 -0
  53. quantark/asset/equity/engine/analytical/digital_option_engine.py +168 -0
  54. quantark/asset/equity/engine/analytical/double_barrier_option_engine.py +481 -0
  55. quantark/asset/equity/engine/analytical/double_sharkfin_option_analytical_engine.py +508 -0
  56. quantark/asset/equity/engine/analytical/one_touch_analytical_engine.py +302 -0
  57. quantark/asset/equity/engine/analytical/range_accrual_analytical_engine.py +396 -0
  58. quantark/asset/equity/engine/analytical/single_sharkfin_option_analytical_engine.py +229 -0
  59. quantark/asset/equity/engine/base_engine.py +137 -0
  60. quantark/asset/equity/engine/event_stats.py +85 -0
  61. quantark/asset/equity/engine/mc/__init__.py +31 -0
  62. quantark/asset/equity/engine/mc/american_option_mc_engine.py +485 -0
  63. quantark/asset/equity/engine/mc/asian_option_mc_engine.py +678 -0
  64. quantark/asset/equity/engine/mc/barrier_option_mc_engine.py +726 -0
  65. quantark/asset/equity/engine/mc/digital_option_mc_engine.py +419 -0
  66. quantark/asset/equity/engine/mc/double_sharkfin_option_mc_engine.py +676 -0
  67. quantark/asset/equity/engine/mc/euro_mc_engine.py +423 -0
  68. quantark/asset/equity/engine/mc/phoenix_mc_engine.py +1206 -0
  69. quantark/asset/equity/engine/mc/range_accrual_mc_engine.py +738 -0
  70. quantark/asset/equity/engine/mc/single_sharkfin_option_mc_engine.py +549 -0
  71. quantark/asset/equity/engine/mc/snowball_mc_engine.py +2250 -0
  72. quantark/asset/equity/engine/pde/__init__.py +36 -0
  73. quantark/asset/equity/engine/pde/american_pde_solver.py +211 -0
  74. quantark/asset/equity/engine/pde/barrier_pde_solver.py +692 -0
  75. quantark/asset/equity/engine/pde/base_pde_solver.py +994 -0
  76. quantark/asset/equity/engine/pde/double_barrier_pde_solver.py +510 -0
  77. quantark/asset/equity/engine/pde/double_one_touch_pde_solver.py +435 -0
  78. quantark/asset/equity/engine/pde/european_pde_solver.py +170 -0
  79. quantark/asset/equity/engine/pde/ko_reset_snowball_pde_solver.py +477 -0
  80. quantark/asset/equity/engine/pde/one_touch_pde_solver.py +439 -0
  81. quantark/asset/equity/engine/pde/phoenix_pde_solver.py +613 -0
  82. quantark/asset/equity/engine/pde/snowball_pde_solver.py +1810 -0
  83. quantark/asset/equity/engine/pde/spatial_grid.py +750 -0
  84. quantark/asset/equity/engine/pde/time_grid.py +308 -0
  85. quantark/asset/equity/engine/pde_engine.py +238 -0
  86. quantark/asset/equity/engine/quad/__init__.py +23 -0
  87. quantark/asset/equity/engine/quad/discrete_quad_engine.py +106 -0
  88. quantark/asset/equity/engine/quad/european_quad_engine.py +325 -0
  89. quantark/asset/equity/engine/quad/ko_reset_snowball_quad_engine.py +362 -0
  90. quantark/asset/equity/engine/quad/phoenix_quad_engine.py +614 -0
  91. quantark/asset/equity/engine/quad/quad_adapters.py +1260 -0
  92. quantark/asset/equity/engine/quad/quad_core.py +513 -0
  93. quantark/asset/equity/engine/quad/quad_math.py +219 -0
  94. quantark/asset/equity/engine/quad/snowball_quad_engine.py +1137 -0
  95. quantark/asset/equity/engine/validation/script/benchmark_check_american_analytical.py +117 -0
  96. quantark/asset/equity/engine/validation/script/benchmark_check_american_pde.py +114 -0
  97. quantark/asset/equity/engine/validation/script/benchmark_check_asian_analytical.py +440 -0
  98. quantark/asset/equity/engine/validation/script/benchmark_check_barrier_analytical.py +269 -0
  99. quantark/asset/equity/engine/validation/script/benchmark_check_barrier_pde_solver.py +636 -0
  100. quantark/asset/equity/engine/validation/script/benchmark_check_digital_option.py +256 -0
  101. quantark/asset/equity/engine/validation/script/benchmark_check_snowball_pde_solver.py +807 -0
  102. quantark/asset/equity/engine/validation/script/boundary_check_american_analytical.py +290 -0
  103. quantark/asset/equity/engine/validation/script/boundary_check_american_pde.py +242 -0
  104. quantark/asset/equity/engine/validation/script/boundary_check_asian_analytical.py +612 -0
  105. quantark/asset/equity/engine/validation/script/boundary_check_barrier_analytical.py +434 -0
  106. quantark/asset/equity/engine/validation/script/boundary_check_barrier_pde_solver.py +748 -0
  107. quantark/asset/equity/engine/validation/script/boundary_check_digital_option.py +575 -0
  108. quantark/asset/equity/engine/validation/script/boundary_check_snowball_pde_solver.py +1101 -0
  109. quantark/asset/equity/engine/validation/script/greeks_check_digital_option.py +349 -0
  110. quantark/asset/equity/engine/validation/script/mc_comparison_barrier_pde.py +270 -0
  111. quantark/asset/equity/engine/validation/script/quick_mc_compare.py +51 -0
  112. quantark/asset/equity/engine/validation/script/validation_stepdown_improved.py +97 -0
  113. quantark/asset/equity/param/__init__.py +24 -0
  114. quantark/asset/equity/param/engine_param_profiles.py +325 -0
  115. quantark/asset/equity/param/engine_params.py +728 -0
  116. quantark/asset/equity/process/__init__.py +7 -0
  117. quantark/asset/equity/process/bsm/__init__.py +7 -0
  118. quantark/asset/equity/process/bsm/bsm_process.py +108 -0
  119. quantark/asset/equity/process/bsm/qmc_brownian_bridge.py +401 -0
  120. quantark/asset/equity/process/bsm/qmc_path_generator.py +694 -0
  121. quantark/asset/equity/process/bsm/qmc_rqmc_driver.py +163 -0
  122. quantark/asset/equity/process/bsm/qmc_sobol.py +195 -0
  123. quantark/asset/equity/process/bsm/qmc_variance_reduction.py +292 -0
  124. quantark/asset/equity/product/__init__.py +8 -0
  125. quantark/asset/equity/product/base_equity_product.py +72 -0
  126. quantark/asset/equity/product/deltaone/__init__.py +22 -0
  127. quantark/asset/equity/product/deltaone/base_deltaone_product.py +147 -0
  128. quantark/asset/equity/product/deltaone/futures.py +485 -0
  129. quantark/asset/equity/product/deltaone/spot_instrument.py +118 -0
  130. quantark/asset/equity/product/option/__init__.py +104 -0
  131. quantark/asset/equity/product/option/american_option.py +114 -0
  132. quantark/asset/equity/product/option/asian_option.py +531 -0
  133. quantark/asset/equity/product/option/barrier_option.py +289 -0
  134. quantark/asset/equity/product/option/base_equity_option.py +659 -0
  135. quantark/asset/equity/product/option/digital_option.py +102 -0
  136. quantark/asset/equity/product/option/double_barrier_option.py +286 -0
  137. quantark/asset/equity/product/option/double_one_touch_option.py +310 -0
  138. quantark/asset/equity/product/option/double_sharkfin_option.py +466 -0
  139. quantark/asset/equity/product/option/european_vanilla_option.py +103 -0
  140. quantark/asset/equity/product/option/ko_reset_snowball_option.py +563 -0
  141. quantark/asset/equity/product/option/observation_schedule.py +530 -0
  142. quantark/asset/equity/product/option/one_touch_option.py +287 -0
  143. quantark/asset/equity/product/option/phoenix_config.py +116 -0
  144. quantark/asset/equity/product/option/phoenix_helpers.py +576 -0
  145. quantark/asset/equity/product/option/phoenix_option.py +1167 -0
  146. quantark/asset/equity/product/option/range_accrual_config.py +288 -0
  147. quantark/asset/equity/product/option/range_accrual_helpers.py +608 -0
  148. quantark/asset/equity/product/option/range_accrual_option.py +526 -0
  149. quantark/asset/equity/product/option/single_sharkfin_option.py +420 -0
  150. quantark/asset/equity/product/option/snowball_config.py +261 -0
  151. quantark/asset/equity/product/option/snowball_helpers.py +977 -0
  152. quantark/asset/equity/product/option/snowball_option.py +1242 -0
  153. quantark/asset/equity/report/__init__.py +15 -0
  154. quantark/asset/equity/report/autocallable_risk_report.py +2118 -0
  155. quantark/asset/equity/report/plotting.py +87 -0
  156. quantark/asset/equity/report/snowball_risk_comparison_report.py +2230 -0
  157. quantark/asset/equity/report/surfaces.py +123 -0
  158. quantark/asset/equity/report/term_structure.py +126 -0
  159. quantark/asset/equity/riskmeasures/__init__.py +7 -0
  160. quantark/asset/equity/riskmeasures/greeks_calculator.py +1204 -0
  161. quantark/asset/rate/__init__.py +58 -0
  162. quantark/asset/rate/engine/__init__.py +25 -0
  163. quantark/asset/rate/engine/cap_floor_engine.py +514 -0
  164. quantark/asset/rate/engine/fra_engine.py +286 -0
  165. quantark/asset/rate/engine/irs_discount_engine.py +891 -0
  166. quantark/asset/rate/engine/swaption_engine.py +587 -0
  167. quantark/asset/rate/product/__init__.py +67 -0
  168. quantark/asset/rate/product/cap_floor.py +550 -0
  169. quantark/asset/rate/product/fra.py +219 -0
  170. quantark/asset/rate/product/irs.py +1223 -0
  171. quantark/asset/rate/product/swaption.py +372 -0
  172. quantark/backtest/__init__.py +153 -0
  173. quantark/backtest/base.py +263 -0
  174. quantark/backtest/dashboard.py +874 -0
  175. quantark/backtest/equity/__init__.py +35 -0
  176. quantark/backtest/equity/config.py +118 -0
  177. quantark/backtest/equity/engine.py +408 -0
  178. quantark/backtest/equity/hedge_executor.py +374 -0
  179. quantark/backtest/equity/metrics.py +396 -0
  180. quantark/backtest/equity/results.py +232 -0
  181. quantark/backtest/equity/state.py +252 -0
  182. quantark/backtest/examples/__init__.py +4 -0
  183. quantark/backtest/examples/advanced_backtest.py +345 -0
  184. quantark/backtest/examples/basic_delta_hedge.py +246 -0
  185. quantark/backtest/examples/fi_dv01_hedge.py +267 -0
  186. quantark/backtest/fi/__init__.py +30 -0
  187. quantark/backtest/fi/config.py +114 -0
  188. quantark/backtest/fi/engine.py +378 -0
  189. quantark/backtest/fi/hedge_executor.py +254 -0
  190. quantark/backtest/fi/metrics.py +308 -0
  191. quantark/backtest/fi/results.py +193 -0
  192. quantark/backtest/fi/state.py +212 -0
  193. quantark/backtest/logger.py +393 -0
  194. quantark/backtest/otc/__init__.py +74 -0
  195. quantark/backtest/otc/_replay.py +637 -0
  196. quantark/backtest/otc/book_engine.py +587 -0
  197. quantark/backtest/otc/config.py +175 -0
  198. quantark/backtest/otc/dashboard.py +1006 -0
  199. quantark/backtest/otc/engine.py +420 -0
  200. quantark/backtest/otc/engine_factory.py +138 -0
  201. quantark/backtest/otc/market.py +216 -0
  202. quantark/backtest/otc/results.py +107 -0
  203. quantark/backtest/otc/state.py +166 -0
  204. quantark/backtest/report_generator.py +608 -0
  205. quantark/backtest/strategy/__init__.py +28 -0
  206. quantark/backtest/strategy/base_strategy.py +235 -0
  207. quantark/backtest/strategy/convexity_neutral_strategy.py +247 -0
  208. quantark/backtest/strategy/delta_neutral_strategy.py +283 -0
  209. quantark/backtest/strategy/dv01_neutral_strategy.py +283 -0
  210. quantark/backtest/transaction_costs.py +485 -0
  211. quantark/backtest/visualizer.py +1019 -0
  212. quantark/cashleg/__init__.py +31 -0
  213. quantark/cashleg/accrual_leg.py +120 -0
  214. quantark/cashleg/base.py +48 -0
  215. quantark/cashleg/base_amount.py +60 -0
  216. quantark/cashleg/deterministic_leg.py +39 -0
  217. quantark/cashleg/event_distribution.py +262 -0
  218. quantark/cashleg/fixed_payoff_leg.py +92 -0
  219. quantark/cashleg/leg_schedule.py +95 -0
  220. quantark/cashleg/leg_valuator.py +40 -0
  221. quantark/dynamicscenario/__init__.py +97 -0
  222. quantark/dynamicscenario/base.py +297 -0
  223. quantark/dynamicscenario/config.py +122 -0
  224. quantark/dynamicscenario/engine.py +703 -0
  225. quantark/dynamicscenario/equity/__init__.py +14 -0
  226. quantark/dynamicscenario/fi/__init__.py +24 -0
  227. quantark/dynamicscenario/fi/config.py +149 -0
  228. quantark/dynamicscenario/fi/engine.py +500 -0
  229. quantark/dynamicscenario/fi/results.py +503 -0
  230. quantark/dynamicscenario/path/__init__.py +17 -0
  231. quantark/dynamicscenario/path/day_path.py +397 -0
  232. quantark/dynamicscenario/path/fi_path_library.py +488 -0
  233. quantark/dynamicscenario/path/path_builder.py +726 -0
  234. quantark/dynamicscenario/path/path_library.py +620 -0
  235. quantark/dynamicscenario/report/__init__.py +12 -0
  236. quantark/dynamicscenario/report/dynamic_report.py +1175 -0
  237. quantark/dynamicscenario/report/visualizer.py +1586 -0
  238. quantark/dynamicscenario/results/__init__.py +19 -0
  239. quantark/dynamicscenario/results/dynamic_results.py +579 -0
  240. quantark/dynamicscenario/results/result_exporter.py +438 -0
  241. quantark/param/__init__.py +75 -0
  242. quantark/param/basis/__init__.py +19 -0
  243. quantark/param/basis/basis_yield.py +301 -0
  244. quantark/param/div/__init__.py +16 -0
  245. quantark/param/div/dividend_yield.py +123 -0
  246. quantark/param/index/__init__.py +52 -0
  247. quantark/param/index/rate_index.py +568 -0
  248. quantark/param/quote/__init__.py +7 -0
  249. quantark/param/quote/spot_quote.py +35 -0
  250. quantark/param/rrf/__init__.py +22 -0
  251. quantark/param/rrf/rate_curve.py +436 -0
  252. quantark/param/vol/__init__.py +6 -0
  253. quantark/param/vol/vol_surface.py +118 -0
  254. quantark/portfolio/__init__.py +61 -0
  255. quantark/portfolio/base.py +203 -0
  256. quantark/portfolio/equity/__init__.py +17 -0
  257. quantark/portfolio/equity/portfolio.py +391 -0
  258. quantark/portfolio/equity/position.py +368 -0
  259. quantark/portfolio/fi/__init__.py +14 -0
  260. quantark/portfolio/fi/portfolio.py +424 -0
  261. quantark/portfolio/fi/position.py +272 -0
  262. quantark/portfolio/portfolio_snapshot.py +221 -0
  263. quantark/portfolio/portfolio_storage.py +414 -0
  264. quantark/priceenv/__init__.py +7 -0
  265. quantark/priceenv/pricing_environment.py +196 -0
  266. quantark/rfq/__init__.py +32 -0
  267. quantark/rfq/builders.py +102 -0
  268. quantark/rfq/models.py +214 -0
  269. quantark/rfq/registry.py +611 -0
  270. quantark/rfq/service.py +237 -0
  271. quantark/simm/__init__.py +155 -0
  272. quantark/simm/calibration/__init__.py +206 -0
  273. quantark/simm/calibration/accessors.py +439 -0
  274. quantark/simm/calibration/commodity.py +156 -0
  275. quantark/simm/calibration/credit_non_qualifying.py +79 -0
  276. quantark/simm/calibration/credit_qualifying.py +130 -0
  277. quantark/simm/calibration/cross_risk.py +39 -0
  278. quantark/simm/calibration/equity.py +125 -0
  279. quantark/simm/calibration/fx.py +92 -0
  280. quantark/simm/calibration/ir.py +152 -0
  281. quantark/simm/calibration/version.py +33 -0
  282. quantark/simm/config.py +186 -0
  283. quantark/simm/crif/__init__.py +35 -0
  284. quantark/simm/crif/models.py +230 -0
  285. quantark/simm/crif/parser.py +585 -0
  286. quantark/simm/engines/__init__.py +62 -0
  287. quantark/simm/engines/aggregation/__init__.py +67 -0
  288. quantark/simm/engines/aggregation/addon.py +141 -0
  289. quantark/simm/engines/aggregation/bucket_aggregator.py +298 -0
  290. quantark/simm/engines/aggregation/concentration.py +349 -0
  291. quantark/simm/engines/aggregation/product_class_aggregator.py +183 -0
  292. quantark/simm/engines/aggregation/risk_class_aggregator.py +403 -0
  293. quantark/simm/engines/aggregation/simm_calculator.py +430 -0
  294. quantark/simm/engines/aggregation/weighted_sensitivity.py +272 -0
  295. quantark/simm/engines/base.py +231 -0
  296. quantark/simm/engines/classification/__init__.py +10 -0
  297. quantark/simm/engines/classification/bucket_mapper.py +347 -0
  298. quantark/simm/engines/factory.py +137 -0
  299. quantark/simm/engines/portfolio_adapter.py +336 -0
  300. quantark/simm/engines/result.py +176 -0
  301. quantark/simm/engines/risk_class/__init__.py +18 -0
  302. quantark/simm/engines/risk_class/equity_engine.py +263 -0
  303. quantark/simm/engines/risk_class/ir_engine.py +264 -0
  304. quantark/simm/report/__init__.py +17 -0
  305. quantark/simm/report/crif_export.py +284 -0
  306. quantark/simm/report/excel_generator.py +401 -0
  307. quantark/simm/report/html_generator.py +840 -0
  308. quantark/simm/results/__init__.py +38 -0
  309. quantark/simm/results/attribution.py +313 -0
  310. quantark/simm/results/simm_result.py +339 -0
  311. quantark/simm/results/whatif.py +268 -0
  312. quantark/simm/sensitivity.py +533 -0
  313. quantark/simm/taxonomy.py +416 -0
  314. quantark/stresstest/__init__.py +67 -0
  315. quantark/stresstest/base.py +116 -0
  316. quantark/stresstest/config.py +5 -0
  317. quantark/stresstest/engine.py +5 -0
  318. quantark/stresstest/equity/__init__.py +17 -0
  319. quantark/stresstest/equity/config.py +69 -0
  320. quantark/stresstest/equity/engine.py +272 -0
  321. quantark/stresstest/equity/report/__init__.py +7 -0
  322. quantark/stresstest/equity/report/report_generator.py +423 -0
  323. quantark/stresstest/equity/report/visualizer.py +328 -0
  324. quantark/stresstest/equity/results.py +145 -0
  325. quantark/stresstest/fi/__init__.py +15 -0
  326. quantark/stresstest/fi/config.py +59 -0
  327. quantark/stresstest/fi/engine.py +213 -0
  328. quantark/stresstest/fi/metrics.py +60 -0
  329. quantark/stresstest/fi/results.py +64 -0
  330. quantark/stresstest/report/__init__.py +12 -0
  331. quantark/stresstest/report/report_generator.py +5 -0
  332. quantark/stresstest/report/visualizer.py +5 -0
  333. quantark/stresstest/results/__init__.py +16 -0
  334. quantark/stresstest/results/result_aggregator.py +325 -0
  335. quantark/stresstest/results/result_exporter.py +286 -0
  336. quantark/stresstest/results/stress_results.py +5 -0
  337. quantark/stresstest/scenario/__init__.py +13 -0
  338. quantark/stresstest/scenario/scenario.py +242 -0
  339. quantark/stresstest/scenario/scenario_builder.py +376 -0
  340. quantark/stresstest/scenario/scenario_library.py +435 -0
  341. quantark/stresstest/scenario/scenario_storage.py +224 -0
  342. quantark/stresstest/stress/__init__.py +13 -0
  343. quantark/stresstest/stress/stress_applicator.py +590 -0
  344. quantark/stresstest/stress/stress_types.py +142 -0
  345. quantark/util/__init__.py +23 -0
  346. quantark/util/barrier_shift.py +44 -0
  347. quantark/util/calendar/__init__.py +27 -0
  348. quantark/util/calendar/business_calendar.py +584 -0
  349. quantark/util/calendar/day_counter.py +517 -0
  350. quantark/util/calendar/holidayfile/china.csv +1920 -0
  351. quantark/util/calendar/holidayfile/china_sse.csv +1462 -0
  352. quantark/util/enum/__init__.py +81 -0
  353. quantark/util/enum/bond_enums.py +112 -0
  354. quantark/util/enum/deltaone_enums.py +16 -0
  355. quantark/util/enum/engine_enums.py +137 -0
  356. quantark/util/enum/greeks_enums.py +29 -0
  357. quantark/util/enum/option_enums.py +221 -0
  358. quantark/util/exceptions.py +66 -0
  359. quantark/util/marketdata/__init__.py +39 -0
  360. quantark/util/marketdata/adapter/base_adapter.py +203 -0
  361. quantark/util/marketdata/adapter/mock_adapter.py +265 -0
  362. quantark/util/marketdata/converter.py +289 -0
  363. quantark/util/marketdata/example_usage.py +314 -0
  364. quantark/util/marketdata/generator/__init__.py +7 -0
  365. quantark/util/marketdata/generator/mock_generator.py +466 -0
  366. quantark/util/marketdata/models.py +358 -0
  367. quantark/util/marketdata/storage/__init__.py +7 -0
  368. quantark/util/marketdata/storage/parquet_storage.py +340 -0
  369. quantark/util/numerical/__init__.py +98 -0
  370. quantark/util/numerical/comparison.py +219 -0
  371. quantark/util/numerical/constants.py +98 -0
  372. quantark/util/numerical/formatting.py +380 -0
  373. quantark/util/numerical/pnl.py +17 -0
  374. quantark/util/numerical/safe_math.py +238 -0
  375. quantark/util/numerical/validation.py +315 -0
  376. quantark/var/__init__.py +39 -0
  377. quantark/var/attribution.py +398 -0
  378. quantark/var/backtest/__init__.py +7 -0
  379. quantark/var/backtest/var_backtester.py +309 -0
  380. quantark/var/base.py +63 -0
  381. quantark/var/config.py +219 -0
  382. quantark/var/engines/__init__.py +13 -0
  383. quantark/var/engines/historical.py +925 -0
  384. quantark/var/engines/monte_carlo.py +870 -0
  385. quantark/var/engines/parametric.py +1199 -0
  386. quantark/var/results/__init__.py +16 -0
  387. quantark/var/results/incremental_var_result.py +131 -0
  388. quantark/var/results/var_report.py +346 -0
  389. quantark/var/results/var_result.py +134 -0
  390. quantark/var/risk_factors/__init__.py +22 -0
  391. quantark/var/risk_factors/base.py +41 -0
  392. quantark/var/risk_factors/equity_factors.py +158 -0
  393. quantark/var/risk_factors/fi_factors.py +99 -0
  394. quantark-0.1.0.dist-info/METADATA +351 -0
  395. quantark-0.1.0.dist-info/RECORD +399 -0
  396. quantark-0.1.0.dist-info/WHEEL +4 -0
  397. quantark-0.1.0.dist-info/licenses/LICENSE +202 -0
  398. quantark-0.1.0.dist-info/licenses/NOTICE +2 -0
  399. quantark_compat.pth +1 -0
@@ -0,0 +1,1204 @@
1
+ """
2
+ Greeks calculation for equity derivatives.
3
+ """
4
+
5
+ import math
6
+ from copy import deepcopy
7
+ from datetime import timedelta
8
+ from typing import Dict, Optional, Sequence, Tuple
9
+
10
+ from scipy import stats
11
+
12
+ from quantark.asset.equity.engine.base_engine import BaseEngine
13
+ from quantark.asset.equity.param import EngineParams
14
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
15
+ from quantark.asset.equity.product.option import EuropeanVanillaOption
16
+ from quantark.priceenv import PricingEnvironment
17
+ from quantark.util.calendar import calculate_year_fraction
18
+ from quantark.util.enum import CommonGreek, EquityGreek
19
+ from quantark.util.enum.engine_enums import EngineType, GreeksCalculationMode
20
+ from quantark.util.exceptions import ValidationError
21
+ from quantark.util.numerical import is_zero
22
+
23
+
24
+ class GreeksCalculator:
25
+ """
26
+ Calculator for option Greeks using both analytical and numerical methods.
27
+
28
+ Supports:
29
+ - Analytical Greeks: Using closed-form Black-Scholes formulas
30
+ - Numerical Greeks: Using finite difference method (FDM)
31
+
32
+ The greeks_mode parameter controls delta/gamma calculation for engines that
33
+ implement their own calculate_greeks() method (e.g., PDE engines):
34
+ - GreeksCalculationMode.BUMP: Always use finite difference bump method
35
+ - GreeksCalculationMode.ENGINE: Use engine.calculate_greeks() when overridden
36
+ - GreeksCalculationMode.AUTO: Use engine method for PDE engines, bump otherwise
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ params: Optional[EngineParams] = None,
42
+ greeks_mode: GreeksCalculationMode = GreeksCalculationMode.BUMP,
43
+ ):
44
+ """
45
+ Initialize Greeks calculator.
46
+
47
+ Args:
48
+ params: Engine parameters (for bump sizes in FDM)
49
+ greeks_mode: Mode for delta/gamma calculation when engine has
50
+ its own calculate_greeks() method (e.g., PDE engines)
51
+ """
52
+ self.params = params if params is not None else EngineParams()
53
+ self._bump_config = self.params.get_effective_bump_config()
54
+ self.greeks_mode = greeks_mode
55
+
56
+ def calculate(
57
+ self,
58
+ product: BaseEquityProduct,
59
+ pricing_env: PricingEnvironment,
60
+ engine: BaseEngine,
61
+ method: str = "auto",
62
+ greeks: Optional[Sequence[object]] = None,
63
+ ) -> Dict[str, float]:
64
+ """Unified entry point for Greeks calculation."""
65
+ method = method.lower()
66
+ if method not in ("auto", "analytical", "numerical"):
67
+ raise ValidationError(f"Unknown greeks method: {method}")
68
+
69
+ requested = self._normalize_greeks(greeks)
70
+ analytical_supported = {
71
+ "price",
72
+ "delta",
73
+ "gamma",
74
+ "vega",
75
+ "theta",
76
+ "rho",
77
+ }
78
+
79
+ if method in ("auto", "analytical") and isinstance(
80
+ product, EuropeanVanillaOption
81
+ ):
82
+ if requested is None or requested.issubset(analytical_supported):
83
+ greeks_out = self.calculate_analytical_greeks(product, pricing_env)
84
+ if requested is None:
85
+ return greeks_out
86
+ return {key: greeks_out[key] for key in greeks_out if key in requested}
87
+ if method == "analytical":
88
+ raise ValidationError(
89
+ "Analytical greeks do not support requested greeks: "
90
+ f"{sorted(requested - analytical_supported)}"
91
+ )
92
+
93
+ return self.calculate_numerical_greeks(
94
+ product, pricing_env, engine, greeks=greeks
95
+ )
96
+
97
+ def _normalize_greeks(
98
+ self, greeks: Optional[Sequence[object]]
99
+ ) -> Optional[set[str]]:
100
+ if greeks is None:
101
+ return None
102
+ if len(greeks) == 0:
103
+ return set()
104
+ aliases = {
105
+ "deltaq": "delta_q",
106
+ "deltadq": "delta_q",
107
+ "d_delta_d_q": "delta_q",
108
+ "d_delta_dq": "delta_q",
109
+ "rhoq": "dividend_rho",
110
+ "div_rho": "dividend_rho",
111
+ "dividendrho": "dividend_rho",
112
+ }
113
+ allowed = {
114
+ "price",
115
+ "delta",
116
+ "gamma",
117
+ "vega",
118
+ "theta",
119
+ "rho",
120
+ "dividend_rho",
121
+ "vanna",
122
+ "volga",
123
+ "delta_q",
124
+ "charm",
125
+ "color",
126
+ "convexity_theta",
127
+ "r_theta",
128
+ "q_theta",
129
+ }
130
+ normalized: set[str] = set()
131
+ for greek in greeks:
132
+ if isinstance(greek, (CommonGreek, EquityGreek)):
133
+ name = greek.value
134
+ elif isinstance(greek, str):
135
+ name = greek.strip().lower()
136
+ else:
137
+ raise ValidationError(
138
+ f"Unsupported greek identifier type: {type(greek).__name__}"
139
+ )
140
+ name = aliases.get(name, name)
141
+ if name not in allowed:
142
+ raise ValidationError(f"Unknown greek name: {name}")
143
+ normalized.add(name)
144
+ return normalized
145
+
146
+ def _has_custom_greeks(self, engine: BaseEngine) -> bool:
147
+ """Return True if engine overrides calculate_greeks()."""
148
+ engine_calculate_greeks = getattr(engine.__class__, "calculate_greeks", None)
149
+ return (
150
+ engine_calculate_greeks is not None
151
+ and engine_calculate_greeks is not BaseEngine.calculate_greeks
152
+ )
153
+
154
+ def _should_use_engine_greeks(self, engine: BaseEngine) -> bool:
155
+ """
156
+ Check if engine's calculate_greeks() should be used for delta/gamma.
157
+
158
+ Args:
159
+ engine: The pricing engine
160
+
161
+ Returns:
162
+ True if engine.calculate_greeks() should be used
163
+ """
164
+ if not self._has_custom_greeks(engine):
165
+ return False
166
+
167
+ if self.greeks_mode == GreeksCalculationMode.BUMP:
168
+ return False
169
+ if self.greeks_mode == GreeksCalculationMode.ENGINE:
170
+ return True
171
+ # AUTO mode: use for PDE engines
172
+ return getattr(engine, "engine_type", None) == EngineType.PDE
173
+
174
+ def _ensure_base_price(
175
+ self,
176
+ product: BaseEquityProduct,
177
+ pricing_env: PricingEnvironment,
178
+ engine: BaseEngine,
179
+ base_price: Optional[float],
180
+ ) -> float:
181
+ """Return base price, computing it if needed."""
182
+ return (
183
+ base_price if base_price is not None else engine.price(product, pricing_env)
184
+ )
185
+
186
+ def _calculate_sensitivity(
187
+ self,
188
+ base_price: float,
189
+ price_up: float,
190
+ price_down: Optional[float] = None,
191
+ bump: float = 1.0,
192
+ scale: float = 1.0,
193
+ mode: str = "central",
194
+ ) -> float:
195
+ """Generic finite-difference sensitivity helper."""
196
+ if mode == "central":
197
+ if price_down is None:
198
+ raise ValidationError("central mode requires price_down")
199
+ return (price_up - price_down) / (2.0 * scale * bump)
200
+ if mode == "second_order":
201
+ if price_down is None:
202
+ raise ValidationError("second_order mode requires price_down")
203
+ return (price_up - 2.0 * base_price + price_down) / (
204
+ scale * bump
205
+ ) ** 2
206
+ if mode == "one_sided":
207
+ return price_up - base_price
208
+ raise ValidationError(f"Unknown sensitivity mode: {mode}")
209
+
210
+ def _get_delta_gamma(
211
+ self,
212
+ product: BaseEquityProduct,
213
+ pricing_env: PricingEnvironment,
214
+ engine: BaseEngine,
215
+ base_price: Optional[float],
216
+ ) -> Tuple[float, float, float]:
217
+ """Get base price, delta, and gamma via engine or bump method."""
218
+ if self._should_use_engine_greeks(engine):
219
+ engine_greeks = engine.calculate_greeks(product, pricing_env)
220
+ if base_price is None:
221
+ base_price = engine_greeks["price"]
222
+ return base_price, engine_greeks["delta"], engine_greeks["gamma"]
223
+
224
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
225
+ spot_prices = self._spot_bumped_prices(
226
+ product, pricing_env, engine, self._bump_config.spot_bump, base_price=base_price
227
+ )[1:]
228
+
229
+ delta = self.calculate_numerical_delta(
230
+ product,
231
+ pricing_env,
232
+ engine,
233
+ base_price=base_price,
234
+ spot_prices=spot_prices,
235
+ bump=self._bump_config.spot_bump,
236
+ )
237
+ gamma = self.calculate_numerical_gamma(
238
+ product,
239
+ pricing_env,
240
+ engine,
241
+ base_price=base_price,
242
+ spot_prices=spot_prices,
243
+ bump=self._bump_config.spot_bump,
244
+ )
245
+
246
+ return base_price, delta, gamma
247
+
248
+ def calculate_analytical_greeks(
249
+ self,
250
+ product: BaseEquityProduct,
251
+ pricing_env: PricingEnvironment,
252
+ price: Optional[float] = None,
253
+ ) -> Dict[str, float]:
254
+ """
255
+ Calculate Greeks using analytical Black-Scholes formulas.
256
+
257
+ Only works for European vanilla options under Black-Scholes model.
258
+
259
+ Args:
260
+ product: European vanilla option
261
+ pricing_env: Pricing environment
262
+ price: Pre-calculated price (optional, will calculate if not provided)
263
+
264
+ Returns:
265
+ Dictionary of Greeks: delta, gamma, vega, theta, rho
266
+
267
+ Raises:
268
+ ValidationError: If product is not a European vanilla option
269
+ """
270
+ if not isinstance(product, EuropeanVanillaOption):
271
+ raise ValidationError(
272
+ f"Analytical Greeks only support EuropeanVanillaOption, "
273
+ f"got {type(product).__name__}"
274
+ )
275
+
276
+ # Extract parameters
277
+ S = pricing_env.spot
278
+ K = product.strike
279
+ T = product.get_maturity(pricing_env)
280
+ r = pricing_env.get_rate(T)
281
+ q = pricing_env.get_div_yield(T)
282
+ sigma = pricing_env.get_vol(K, T)
283
+
284
+ # Handle edge case: option at expiry
285
+ if is_zero(T):
286
+ return self._greeks_at_expiry(product, S)
287
+
288
+ # Calculate d1 and d2
289
+ sqrt_T = math.sqrt(T)
290
+ d1 = (math.log(S / K) + (r - q + 0.5 * sigma**2) * T) / (sigma * sqrt_T)
291
+ d2 = d1 - sigma * sqrt_T
292
+
293
+ # Calculate discount factors
294
+ discount_div = math.exp(-q * T)
295
+ discount_rf = math.exp(-r * T)
296
+
297
+ # Standard normal PDF and CDF
298
+ n_d1 = stats.norm.pdf(d1) # phi(d1)
299
+ N_d1 = stats.norm.cdf(d1) # Phi(d1)
300
+ N_d2 = stats.norm.cdf(d2) # Phi(d2)
301
+
302
+ greeks = {}
303
+
304
+ multiplier = product.contract_multiplier
305
+
306
+ # Calculate price if not provided (per-unit)
307
+ if price is None:
308
+ if product.is_call():
309
+ price = S * discount_div * N_d1 - K * discount_rf * N_d2
310
+ else:
311
+ price = K * discount_rf * stats.norm.cdf(
312
+ -d2
313
+ ) - S * discount_div * stats.norm.cdf(-d1)
314
+ else:
315
+ price = price / multiplier
316
+ greeks["price"] = price
317
+
318
+ # Delta: ∂V/∂S
319
+ if product.is_call():
320
+ delta = discount_div * N_d1
321
+ else:
322
+ delta = -discount_div * stats.norm.cdf(-d1)
323
+ greeks["delta"] = delta
324
+
325
+ # Gamma: ∂²V/∂S²
326
+ gamma = discount_div * n_d1 / (S * sigma * sqrt_T)
327
+ greeks["gamma"] = gamma
328
+
329
+ # Vega: ∂V/∂σ (divided by 100 for 1% change)
330
+ vega = S * discount_div * n_d1 * sqrt_T / 100
331
+ greeks["vega"] = vega
332
+
333
+ # Theta: ∂V/∂t (per day, divided by 365)
334
+ # Decomposed into three components:
335
+ # convexity_theta: time decay from gamma/convexity erosion (always negative)
336
+ # r_theta: time decay from interest rate cost of carry
337
+ # q_theta: time decay from dividend yield
338
+ term1 = -S * discount_div * n_d1 * sigma / (2 * sqrt_T)
339
+ if product.is_call():
340
+ term2 = -r * K * discount_rf * N_d2
341
+ term3 = q * S * discount_div * N_d1
342
+ else:
343
+ term2 = r * K * discount_rf * stats.norm.cdf(-d2)
344
+ term3 = -q * S * discount_div * stats.norm.cdf(-d1)
345
+
346
+ # Store decomposed components (per day)
347
+ convexity_theta = term1 / 365
348
+ r_theta = term2 / 365
349
+ q_theta = term3 / 365
350
+ theta = convexity_theta + r_theta + q_theta
351
+
352
+ greeks["theta"] = theta
353
+ greeks["convexity_theta"] = convexity_theta
354
+ greeks["r_theta"] = r_theta
355
+ greeks["q_theta"] = q_theta
356
+
357
+ # Rho: ∂V/∂r (divided by 100 for 1% change)
358
+ if product.is_call():
359
+ rho = K * T * discount_rf * N_d2 / 100
360
+ else:
361
+ rho = -K * T * discount_rf * stats.norm.cdf(-d2) / 100
362
+ greeks["rho"] = rho
363
+
364
+ # Dividend Rho: ∂V/∂q (divided by 100 for 1% change)
365
+ if product.is_call():
366
+ dividend_rho = -S * T * discount_div * N_d1 / 100
367
+ else:
368
+ dividend_rho = S * T * discount_div * stats.norm.cdf(-d1) / 100
369
+ greeks["dividend_rho"] = dividend_rho
370
+
371
+ for key, value in greeks.items():
372
+ greeks[key] = value * multiplier
373
+
374
+ return greeks
375
+
376
+ def calculate_numerical_greeks(
377
+ self,
378
+ product: BaseEquityProduct,
379
+ pricing_env: PricingEnvironment,
380
+ engine: BaseEngine,
381
+ base_price: Optional[float] = None,
382
+ greeks: Optional[Sequence[object]] = None,
383
+ ) -> Dict[str, float]:
384
+ """
385
+ Calculate Greeks using finite difference method (FDM).
386
+
387
+ Uses central differences for better accuracy.
388
+ Works for any product and engine combination.
389
+
390
+ Bump sizes are configured via BumpConfig in EngineParams:
391
+ - Delta/Gamma: Relative spot bump (default: 1%)
392
+ - Vega: Absolute vol bump (default: 1 vol point)
393
+ - Theta: Time bump in days (default: 1 day)
394
+ - Rho: Absolute rate bump (default: 1bp), scaled to per 1% change
395
+ - Dividend Rho: Absolute div yield bump (default: 1bp), scaled to per 1% change
396
+
397
+ For delta and gamma, if greeks_mode is ENGINE or AUTO (with PDE engine),
398
+ the engine's own calculate_greeks() method is used instead of bumping.
399
+
400
+ Args:
401
+ product: The derivative product
402
+ pricing_env: Pricing environment
403
+ engine: Pricing engine to use
404
+ base_price: Pre-calculated base price (optional)
405
+
406
+ Returns:
407
+ Dictionary of Greeks for the requested set (or defaults if None).
408
+ """
409
+ requested = self._normalize_greeks(greeks)
410
+ if requested is None:
411
+ requested = {
412
+ "price",
413
+ "delta",
414
+ "gamma",
415
+ "vega",
416
+ "theta",
417
+ "rho",
418
+ "dividend_rho",
419
+ "convexity_theta",
420
+ "r_theta",
421
+ "q_theta",
422
+ }
423
+
424
+ if product.is_linear:
425
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
426
+ greeks_out = self._greeks_for_linear(product, base_price)
427
+ for extra in requested:
428
+ greeks_out.setdefault(extra, 0.0)
429
+ return {key: greeks_out[key] for key in greeks_out if key in requested}
430
+
431
+ greeks_out: Dict[str, float] = {}
432
+ if "price" in requested and base_price is not None:
433
+ greeks_out["price"] = base_price
434
+
435
+ delta = None
436
+ gamma = None
437
+ if {"delta", "gamma", "delta_q", "vanna"} & requested:
438
+ base_price, delta, gamma = self._get_delta_gamma(
439
+ product, pricing_env, engine, base_price
440
+ )
441
+ if delta is not None and "delta" in requested:
442
+ greeks_out["delta"] = delta
443
+ if gamma is not None and "gamma" in requested:
444
+ greeks_out["gamma"] = gamma
445
+
446
+ if base_price is None:
447
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
448
+ if "price" in requested and "price" not in greeks_out:
449
+ greeks_out["price"] = base_price
450
+
451
+ # Other Greeks always use bump method
452
+ if "vega" in requested:
453
+ greeks_out["vega"] = self.calculate_numerical_vega(
454
+ product,
455
+ pricing_env,
456
+ engine,
457
+ base_price=base_price,
458
+ vol_bump=self._bump_config.vol_bump,
459
+ )
460
+ if "volga" in requested:
461
+ greeks_out["volga"] = self.calculate_numerical_volga(
462
+ product,
463
+ pricing_env,
464
+ engine,
465
+ base_price=base_price,
466
+ vol_bump=self._bump_config.vol_bump,
467
+ )
468
+ if "vanna" in requested:
469
+ greeks_out["vanna"] = self.calculate_numerical_vanna(
470
+ product,
471
+ pricing_env,
472
+ engine,
473
+ base_price=base_price,
474
+ vol_bump=self._bump_config.vol_bump,
475
+ )
476
+ if "delta_q" in requested:
477
+ greeks_out["delta_q"] = self.calculate_numerical_delta_q(
478
+ product,
479
+ pricing_env,
480
+ engine,
481
+ base_price=base_price,
482
+ div_bump=self._bump_config.div_bump,
483
+ base_delta=delta,
484
+ )
485
+ if "theta" in requested:
486
+ greeks_out["theta"] = self.calculate_numerical_theta(
487
+ product,
488
+ pricing_env,
489
+ engine,
490
+ base_price=base_price,
491
+ time_bump_days=self._bump_config.time_bump_days,
492
+ )
493
+ if "rho" in requested:
494
+ greeks_out["rho"] = self.calculate_numerical_rho(
495
+ product,
496
+ pricing_env,
497
+ engine,
498
+ base_price=base_price,
499
+ rate_bump=self._bump_config.rate_bump,
500
+ )
501
+ if "dividend_rho" in requested:
502
+ greeks_out["dividend_rho"] = self.calculate_numerical_dividend_rho(
503
+ product,
504
+ pricing_env,
505
+ engine,
506
+ base_price=base_price,
507
+ div_bump=self._bump_config.div_bump,
508
+ )
509
+
510
+ # Estimate theta components using fast approximation from existing Greeks
511
+ if {"convexity_theta", "r_theta", "q_theta"} & requested:
512
+ if "theta" not in greeks_out:
513
+ greeks_out["theta"] = self.calculate_numerical_theta(
514
+ product,
515
+ pricing_env,
516
+ engine,
517
+ base_price=base_price,
518
+ time_bump_days=self._bump_config.time_bump_days,
519
+ )
520
+ if "rho" not in greeks_out:
521
+ greeks_out["rho"] = self.calculate_numerical_rho(
522
+ product,
523
+ pricing_env,
524
+ engine,
525
+ base_price=base_price,
526
+ rate_bump=self._bump_config.rate_bump,
527
+ )
528
+ if "dividend_rho" not in greeks_out:
529
+ greeks_out["dividend_rho"] = self.calculate_numerical_dividend_rho(
530
+ product,
531
+ pricing_env,
532
+ engine,
533
+ base_price=base_price,
534
+ div_bump=self._bump_config.div_bump,
535
+ )
536
+ T = product.get_maturity(pricing_env)
537
+ r = pricing_env.get_rate(T)
538
+ q = pricing_env.get_div_yield(T)
539
+ theta_components = self.estimate_theta_components(
540
+ theta=greeks_out["theta"],
541
+ rho=greeks_out["rho"],
542
+ dividend_rho=greeks_out["dividend_rho"],
543
+ r=r,
544
+ q=q,
545
+ T=T,
546
+ )
547
+ for key, value in theta_components.items():
548
+ if key in requested:
549
+ greeks_out[key] = value
550
+
551
+ return {key: greeks_out[key] for key in greeks_out if key in requested}
552
+
553
+ def calculate_numerical_delta(
554
+ self,
555
+ product: BaseEquityProduct,
556
+ pricing_env: PricingEnvironment,
557
+ engine: BaseEngine,
558
+ base_price: Optional[float] = None,
559
+ spot_prices: Optional[Tuple[float, float]] = None,
560
+ bump: Optional[float] = None,
561
+ ) -> float:
562
+ """Numerical delta using central spot bump."""
563
+ bump = bump if bump is not None else self.params.bump_size
564
+ base_price, price_up_spot, price_down_spot = self._spot_bumped_prices(
565
+ product,
566
+ pricing_env,
567
+ engine,
568
+ bump,
569
+ base_price=base_price,
570
+ reuse=spot_prices,
571
+ )
572
+ return self._calculate_sensitivity(
573
+ base_price,
574
+ price_up_spot,
575
+ price_down_spot,
576
+ bump=bump,
577
+ scale=pricing_env.spot,
578
+ mode="central",
579
+ )
580
+
581
+ def calculate_numerical_gamma(
582
+ self,
583
+ product: BaseEquityProduct,
584
+ pricing_env: PricingEnvironment,
585
+ engine: BaseEngine,
586
+ base_price: Optional[float] = None,
587
+ spot_prices: Optional[Tuple[float, float]] = None,
588
+ bump: Optional[float] = None,
589
+ ) -> float:
590
+ """Numerical gamma using central spot bump."""
591
+ bump = bump if bump is not None else self.params.bump_size
592
+ base_price, price_up_spot, price_down_spot = self._spot_bumped_prices(
593
+ product,
594
+ pricing_env,
595
+ engine,
596
+ bump,
597
+ base_price=base_price,
598
+ reuse=spot_prices,
599
+ )
600
+ return self._calculate_sensitivity(
601
+ base_price,
602
+ price_up_spot,
603
+ price_down_spot,
604
+ bump=bump,
605
+ scale=pricing_env.spot,
606
+ mode="second_order",
607
+ )
608
+
609
+ def calculate_numerical_vega(
610
+ self,
611
+ product: BaseEquityProduct,
612
+ pricing_env: PricingEnvironment,
613
+ engine: BaseEngine,
614
+ base_price: Optional[float] = None,
615
+ vol_bump: Optional[float] = None,
616
+ ) -> float:
617
+ """Numerical vega from a vol bump."""
618
+ vol_bump = vol_bump if vol_bump is not None else self._bump_config.vol_bump
619
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
620
+ T = product.get_maturity(pricing_env)
621
+ strike = getattr(product, "strike", pricing_env.spot)
622
+ current_vol = pricing_env.get_vol(strike, T)
623
+ env_up_vol = self._build_vol_bumped_env(
624
+ pricing_env, product, current_vol, vol_bump, direction=1.0
625
+ )
626
+ price_up_vol = engine.price(product, env_up_vol)
627
+ return self._calculate_sensitivity(
628
+ base_price, price_up_vol, bump=vol_bump, mode="one_sided"
629
+ )
630
+
631
+ def calculate_numerical_volga(
632
+ self,
633
+ product: BaseEquityProduct,
634
+ pricing_env: PricingEnvironment,
635
+ engine: BaseEngine,
636
+ base_price: Optional[float] = None,
637
+ vol_bump: Optional[float] = None,
638
+ ) -> float:
639
+ """Numerical volga (second derivative wrt vol) using vol bumps."""
640
+ vol_bump = vol_bump if vol_bump is not None else self._bump_config.vol_bump
641
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
642
+ T = product.get_maturity(pricing_env)
643
+ strike = getattr(product, "strike", pricing_env.spot)
644
+ current_vol = pricing_env.get_vol(strike, T)
645
+
646
+ if current_vol - vol_bump <= 0:
647
+ env_up = self._build_vol_bumped_env(
648
+ pricing_env, product, current_vol, vol_bump, direction=1.0
649
+ )
650
+ vega_base = self.calculate_numerical_vega(
651
+ product, pricing_env, engine, base_price=base_price, vol_bump=vol_bump
652
+ )
653
+ vega_up = self.calculate_numerical_vega(
654
+ product, env_up, engine, base_price=None, vol_bump=vol_bump
655
+ )
656
+ return (vega_up - vega_base) / vol_bump
657
+
658
+ env_up = self._build_vol_bumped_env(
659
+ pricing_env, product, current_vol, vol_bump, direction=1.0
660
+ )
661
+ env_down = self._build_vol_bumped_env(
662
+ pricing_env, product, current_vol, vol_bump, direction=-1.0
663
+ )
664
+ price_up = engine.price(product, env_up)
665
+ price_down = engine.price(product, env_down)
666
+ return self._calculate_sensitivity(
667
+ base_price, price_up, price_down, bump=vol_bump, mode="second_order"
668
+ )
669
+
670
+ def calculate_numerical_vanna(
671
+ self,
672
+ product: BaseEquityProduct,
673
+ pricing_env: PricingEnvironment,
674
+ engine: BaseEngine,
675
+ base_price: Optional[float] = None,
676
+ vol_bump: Optional[float] = None,
677
+ ) -> float:
678
+ """Numerical vanna (cross derivative wrt spot and vol)."""
679
+ vol_bump = vol_bump if vol_bump is not None else self._bump_config.vol_bump
680
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
681
+ T = product.get_maturity(pricing_env)
682
+ strike = getattr(product, "strike", pricing_env.spot)
683
+ current_vol = pricing_env.get_vol(strike, T)
684
+
685
+ env_up = self._build_vol_bumped_env(
686
+ pricing_env, product, current_vol, vol_bump, direction=1.0
687
+ )
688
+ env_down = self._build_vol_bumped_env(
689
+ pricing_env, product, current_vol, vol_bump, direction=-1.0
690
+ )
691
+
692
+ if current_vol - vol_bump <= 0:
693
+ base_delta = self.calculate_numerical_delta(
694
+ product,
695
+ pricing_env,
696
+ engine,
697
+ base_price=base_price,
698
+ bump=self._bump_config.spot_bump,
699
+ )
700
+ delta_up = self.calculate_numerical_delta(
701
+ product,
702
+ env_up,
703
+ engine,
704
+ base_price=base_price,
705
+ bump=self._bump_config.spot_bump,
706
+ )
707
+ return (delta_up - base_delta) / vol_bump
708
+
709
+ delta_up = self.calculate_numerical_delta(
710
+ product,
711
+ env_up,
712
+ engine,
713
+ base_price=base_price,
714
+ bump=self._bump_config.spot_bump,
715
+ )
716
+ delta_down = self.calculate_numerical_delta(
717
+ product,
718
+ env_down,
719
+ engine,
720
+ base_price=base_price,
721
+ bump=self._bump_config.spot_bump,
722
+ )
723
+ return (delta_up - delta_down) / (2.0 * vol_bump)
724
+
725
+ def calculate_numerical_theta(
726
+ self,
727
+ product: BaseEquityProduct,
728
+ pricing_env: PricingEnvironment,
729
+ engine: BaseEngine,
730
+ base_price: Optional[float] = None,
731
+ time_bump_days: Optional[int] = None,
732
+ ) -> float:
733
+ """Numerical theta via time bump with observation schedule handling."""
734
+ time_bump_days = (
735
+ time_bump_days
736
+ if time_bump_days is not None
737
+ else self._bump_config.time_bump_days
738
+ )
739
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
740
+ product_theta = deepcopy(product)
741
+ env_theta = deepcopy(pricing_env)
742
+ current_maturity = product.get_maturity(pricing_env)
743
+
744
+ bumped_date = pricing_env.valuation_date + timedelta(days=time_bump_days)
745
+ time_bump = calculate_year_fraction(
746
+ pricing_env.valuation_date,
747
+ bumped_date,
748
+ pricing_env.day_count_convention,
749
+ pricing_env.bus_days_in_year,
750
+ calendar=getattr(pricing_env, "calendar", None),
751
+ )
752
+
753
+ if current_maturity <= time_bump:
754
+ return 0.0
755
+
756
+ dropped_all_observations = product_theta.time_shift(
757
+ time_bump, bumped_date, env_theta
758
+ )
759
+
760
+ if dropped_all_observations:
761
+ return 0.0
762
+
763
+ price_theta = engine.price(product_theta, env_theta)
764
+ return price_theta - base_price
765
+
766
+ def calculate_numerical_rho(
767
+ self,
768
+ product: BaseEquityProduct,
769
+ pricing_env: PricingEnvironment,
770
+ engine: BaseEngine,
771
+ base_price: Optional[float] = None,
772
+ rate_bump: Optional[float] = None,
773
+ ) -> float:
774
+ """Numerical rho from a rate bump (per 1% rate change)."""
775
+ rate_bump = rate_bump if rate_bump is not None else self._bump_config.rate_bump
776
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
777
+ env_up_rate = deepcopy(pricing_env)
778
+ from quantark.param.rrf import FlatRateCurve
779
+
780
+ T = product.get_maturity(pricing_env)
781
+ current_rate = pricing_env.get_rate(T)
782
+ env_up_rate.rate_curve = FlatRateCurve(current_rate + rate_bump)
783
+ price_up_rate = engine.price(product, env_up_rate)
784
+ raw = self._calculate_sensitivity(
785
+ base_price, price_up_rate, bump=rate_bump, mode="one_sided"
786
+ )
787
+ return raw * (0.01 / rate_bump)
788
+
789
+ def calculate_numerical_dividend_rho(
790
+ self,
791
+ product: BaseEquityProduct,
792
+ pricing_env: PricingEnvironment,
793
+ engine: BaseEngine,
794
+ base_price: Optional[float] = None,
795
+ div_bump: Optional[float] = None,
796
+ ) -> float:
797
+ """
798
+ Numerical dividend_rho (psi) from dividend yield bump.
799
+
800
+ Measures price sensitivity to dividend yield changes:
801
+ dividend_rho = dV/dq
802
+
803
+ Args:
804
+ product: The derivative product
805
+ pricing_env: Pricing environment
806
+ engine: Pricing engine
807
+ base_price: Pre-calculated base price
808
+ div_bump: Absolute dividend yield bump (uses config if None)
809
+
810
+ Returns:
811
+ Dividend rho value (price change per 1% div_yield change).
812
+ Negative for call options (higher div = lower call price).
813
+ Positive for put options (higher div = higher put price).
814
+ """
815
+ div_bump = div_bump if div_bump is not None else self._bump_config.div_bump
816
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
817
+ T = product.get_maturity(pricing_env)
818
+ current_div = pricing_env.get_div_yield(T)
819
+ env_up_div = self._build_div_bumped_env(
820
+ pricing_env, product, current_div, div_bump, direction=1.0
821
+ )
822
+ price_up_div = engine.price(product, env_up_div)
823
+ raw = self._calculate_sensitivity(
824
+ base_price, price_up_div, bump=div_bump, mode="one_sided"
825
+ )
826
+ return raw * (0.01 / div_bump)
827
+
828
+ def calculate_numerical_delta_q(
829
+ self,
830
+ product: BaseEquityProduct,
831
+ pricing_env: PricingEnvironment,
832
+ engine: BaseEngine,
833
+ base_price: Optional[float] = None,
834
+ div_bump: Optional[float] = None,
835
+ base_delta: Optional[float] = None,
836
+ ) -> float:
837
+ """Numerical dDelta/dq via dividend yield bumps."""
838
+ div_bump = div_bump if div_bump is not None else self._bump_config.div_bump
839
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
840
+ T = product.get_maturity(pricing_env)
841
+ current_div = pricing_env.get_div_yield(T)
842
+
843
+ if base_delta is None:
844
+ base_delta = self.calculate_numerical_delta(
845
+ product,
846
+ pricing_env,
847
+ engine,
848
+ base_price=base_price,
849
+ bump=self._bump_config.spot_bump,
850
+ )
851
+
852
+ if current_div - div_bump < 0:
853
+ env_up = self._build_div_bumped_env(
854
+ pricing_env, product, current_div, div_bump, direction=1.0
855
+ )
856
+ delta_up = self.calculate_numerical_delta(
857
+ product,
858
+ env_up,
859
+ engine,
860
+ base_price=base_price,
861
+ bump=self._bump_config.spot_bump,
862
+ )
863
+ return (delta_up - base_delta) / div_bump
864
+
865
+ env_up = self._build_div_bumped_env(
866
+ pricing_env, product, current_div, div_bump, direction=1.0
867
+ )
868
+ env_down = self._build_div_bumped_env(
869
+ pricing_env, product, current_div, div_bump, direction=-1.0
870
+ )
871
+ delta_up = self.calculate_numerical_delta(
872
+ product,
873
+ env_up,
874
+ engine,
875
+ base_price=base_price,
876
+ bump=self._bump_config.spot_bump,
877
+ )
878
+ delta_down = self.calculate_numerical_delta(
879
+ product,
880
+ env_down,
881
+ engine,
882
+ base_price=base_price,
883
+ bump=self._bump_config.spot_bump,
884
+ )
885
+ return (delta_up - delta_down) / (2.0 * div_bump)
886
+
887
+ def estimate_theta_components(
888
+ self,
889
+ theta: float,
890
+ rho: float,
891
+ dividend_rho: float,
892
+ r: float,
893
+ q: float,
894
+ T: float,
895
+ rate_bump: float = 0.01,
896
+ dividend_bump: float = 0.01,
897
+ ) -> Dict[str, float]:
898
+ """
899
+ Fast estimation of theta components from existing Greeks.
900
+
901
+ Uses the relationships between theta components and other Greeks:
902
+ r_theta ≈ -r/T * rho / rate_bump (corrected for scale and daily conversion)
903
+ q_theta ≈ -q/T * dividend_rho / dividend_bump (corrected for scale and daily conversion)
904
+ convexity_theta ≈ theta - r_theta - q_theta
905
+
906
+ This is an approximation that avoids repricing. For exact decomposition,
907
+ use _calculate_numerical_theta_components() instead.
908
+
909
+ Args:
910
+ theta: Total theta (per day)
911
+ rho: Rho (sensitivity to rate, per 1% change)
912
+ dividend_rho: Dividend rho (sensitivity to dividend yield, per 1% change)
913
+ r: Interest rate (annual)
914
+ q: Dividend yield (annual)
915
+ T: Time to maturity in years
916
+ rate_bump: Rate scale of the rho input (default: 1% = 0.01)
917
+ dividend_bump: Dividend scale of the dividend_rho input (default: 1% = 0.01)
918
+
919
+ Returns:
920
+ Dictionary with convexity_theta, r_theta, q_theta (all per day)
921
+ """
922
+ if is_zero(T):
923
+ return {
924
+ "convexity_theta": 0.0,
925
+ "r_theta": 0.0,
926
+ "q_theta": 0.0,
927
+ }
928
+
929
+ # Rho/Dividend Rho are per rate_bump/dividend_bump size, so divide by scale.
930
+ # Divide by 365 to convert annual rate decay to daily theta equivalent.
931
+ # Divide by T to cancel out the T term in Rho (Rho = dV/dr = T * dV/d(rT) approx).
932
+ r_theta = -r / T * (rho / rate_bump) / 365
933
+ q_theta = -q / T * (dividend_rho / dividend_bump) / 365
934
+ convexity_theta = theta - r_theta - q_theta
935
+
936
+ return {
937
+ "convexity_theta": convexity_theta,
938
+ "r_theta": r_theta,
939
+ "q_theta": q_theta,
940
+ }
941
+
942
+ def _calculate_numerical_theta_components(
943
+ self,
944
+ product: BaseEquityProduct,
945
+ pricing_env: PricingEnvironment,
946
+ engine: BaseEngine,
947
+ base_price: Optional[float] = None,
948
+ time_bump_days: Optional[int] = None,
949
+ ) -> Dict[str, float]:
950
+ """
951
+ Exact numerical theta decomposition via repricing with zeroed r/q.
952
+
953
+ This method computes exact theta components by repricing with different
954
+ rate and dividend yield combinations:
955
+ 1. theta_no_rq = theta with r=0, q=0 → convexity_theta
956
+ 2. theta_no_q = theta with q=0 → r_theta = theta_no_q - convexity_theta
957
+ 3. theta_no_r = theta with r=0 → q_theta = theta_no_r - convexity_theta
958
+
959
+ Note: This is computationally expensive (3 extra pricings) and should
960
+ be treated as a slow path. For fast estimation, use
961
+ estimate_theta_components() instead.
962
+
963
+ Args:
964
+ product: The derivative product
965
+ pricing_env: Pricing environment
966
+ engine: Pricing engine
967
+ base_price: Pre-calculated base price
968
+ time_bump_days: Time bump in days
969
+
970
+ Returns:
971
+ Dictionary with convexity_theta, r_theta, q_theta (all per day)
972
+ """
973
+ from quantark.param.div import ContinuousDividendYield
974
+ from quantark.param.rrf import FlatRateCurve
975
+
976
+ time_bump_days = (
977
+ time_bump_days
978
+ if time_bump_days is not None
979
+ else self._bump_config.time_bump_days
980
+ )
981
+
982
+ T = product.get_maturity(pricing_env)
983
+ if is_zero(T):
984
+ return {
985
+ "convexity_theta": 0.0,
986
+ "r_theta": 0.0,
987
+ "q_theta": 0.0,
988
+ }
989
+
990
+ # Create environments with zeroed r and/or q
991
+ env_no_r = deepcopy(pricing_env)
992
+ env_no_r.rate_curve = FlatRateCurve(0.0)
993
+
994
+ env_no_q = deepcopy(pricing_env)
995
+ env_no_q.div_yield = ContinuousDividendYield(0.0)
996
+
997
+ env_no_rq = deepcopy(pricing_env)
998
+ env_no_rq.rate_curve = FlatRateCurve(0.0)
999
+ env_no_rq.div_yield = ContinuousDividendYield(0.0)
1000
+
1001
+ # Calculate theta in each environment
1002
+ theta_no_rq = self.calculate_numerical_theta(
1003
+ product, env_no_rq, engine, time_bump_days=time_bump_days
1004
+ )
1005
+ theta_no_q = self.calculate_numerical_theta(
1006
+ product, env_no_q, engine, time_bump_days=time_bump_days
1007
+ )
1008
+ theta_no_r = self.calculate_numerical_theta(
1009
+ product, env_no_r, engine, time_bump_days=time_bump_days
1010
+ )
1011
+
1012
+ # Decompose
1013
+ convexity_theta = theta_no_rq
1014
+ r_theta = theta_no_q - convexity_theta
1015
+ q_theta = theta_no_r - convexity_theta
1016
+
1017
+ return {
1018
+ "convexity_theta": convexity_theta,
1019
+ "r_theta": r_theta,
1020
+ "q_theta": q_theta,
1021
+ }
1022
+
1023
+ def _spot_bumped_prices(
1024
+ self,
1025
+ product: BaseEquityProduct,
1026
+ pricing_env: PricingEnvironment,
1027
+ engine: BaseEngine,
1028
+ bump: float,
1029
+ base_price: Optional[float] = None,
1030
+ reuse: Optional[Tuple[float, float]] = None,
1031
+ ) -> Tuple[float, float, float]:
1032
+ """
1033
+ Compute base, up, and down spot bump prices, optionally reusing bumps.
1034
+ """
1035
+ base_price = self._ensure_base_price(product, pricing_env, engine, base_price)
1036
+ if reuse is not None:
1037
+ price_up_spot, price_down_spot = reuse
1038
+ else:
1039
+ env_up = deepcopy(pricing_env)
1040
+ env_up.spot_quote.spot *= 1 + bump
1041
+ price_up_spot = engine.price(product, env_up)
1042
+
1043
+ env_down = deepcopy(pricing_env)
1044
+ env_down.spot_quote.spot *= 1 - bump
1045
+ price_down_spot = engine.price(product, env_down)
1046
+
1047
+ return base_price, price_up_spot, price_down_spot
1048
+
1049
+ def _build_vol_bumped_env(
1050
+ self,
1051
+ pricing_env: PricingEnvironment,
1052
+ product: BaseEquityProduct,
1053
+ current_vol: float,
1054
+ vol_bump: float,
1055
+ *,
1056
+ direction: float,
1057
+ ) -> PricingEnvironment:
1058
+ from quantark.param.vol import FlatVolSurface, TermStructureVolSurface
1059
+
1060
+ new_vol = current_vol + direction * vol_bump
1061
+ if new_vol <= 0:
1062
+ raise ValidationError(
1063
+ f"Stressed volatility must be positive, got {new_vol}"
1064
+ )
1065
+
1066
+ env = deepcopy(pricing_env)
1067
+ if isinstance(pricing_env.vol_surface, TermStructureVolSurface):
1068
+ new_vols = [float(v) + direction * vol_bump for v in pricing_env.vol_surface.vols]
1069
+ if any(v <= 0 for v in new_vols):
1070
+ raise ValidationError("Stressed term-structure vol must be positive.")
1071
+ env.vol_surface = TermStructureVolSurface(
1072
+ times=list(pricing_env.vol_surface.times), vols=new_vols
1073
+ )
1074
+ else:
1075
+ env.vol_surface = FlatVolSurface(new_vol)
1076
+ return env
1077
+
1078
+ def _build_div_bumped_env(
1079
+ self,
1080
+ pricing_env: PricingEnvironment,
1081
+ product: BaseEquityProduct,
1082
+ current_div: float,
1083
+ div_bump: float,
1084
+ *,
1085
+ direction: float,
1086
+ ) -> PricingEnvironment:
1087
+ from quantark.param.div import ContinuousDividendYield, TermStructureDividendYield
1088
+
1089
+ new_div = current_div + direction * div_bump
1090
+ if new_div < 0:
1091
+ raise ValidationError(
1092
+ f"Stressed dividend yield cannot be negative, got {new_div}"
1093
+ )
1094
+
1095
+ env = deepcopy(pricing_env)
1096
+ if isinstance(pricing_env.div_yield, TermStructureDividendYield):
1097
+ new_yields = [
1098
+ float(y) + direction * div_bump for y in pricing_env.div_yield.yields
1099
+ ]
1100
+ if any(y < 0 for y in new_yields):
1101
+ raise ValidationError(
1102
+ "Stressed term-structure dividend yield cannot be negative."
1103
+ )
1104
+ env.div_yield = TermStructureDividendYield(
1105
+ times=list(pricing_env.div_yield.times), yields=new_yields
1106
+ )
1107
+ else:
1108
+ env.div_yield = ContinuousDividendYield(new_div)
1109
+ return env
1110
+
1111
+ def _greeks_for_linear(
1112
+ self, product: BaseEquityProduct, price: float
1113
+ ) -> Dict[str, float]:
1114
+ """
1115
+ Calculate Greeks for linear (delta-one) products.
1116
+
1117
+ Delta-one products have trivial Greeks:
1118
+ - Delta = 1.0 (always)
1119
+ - Gamma, Vega, Theta, Rho, Dividend Rho = 0.0 (no optionality)
1120
+
1121
+ Args:
1122
+ product: Delta-one product
1123
+ price: Current price
1124
+
1125
+ Returns:
1126
+ Dictionary of Greeks
1127
+ """
1128
+ return {
1129
+ "price": price,
1130
+ "delta": 1.0,
1131
+ "gamma": 0.0,
1132
+ "vega": 0.0,
1133
+ "theta": 0.0,
1134
+ "convexity_theta": 0.0,
1135
+ "r_theta": 0.0,
1136
+ "q_theta": 0.0,
1137
+ "rho": 0.0,
1138
+ "dividend_rho": 0.0,
1139
+ }
1140
+
1141
+ def _greeks_at_expiry(
1142
+ self, product: EuropeanVanillaOption, spot: float
1143
+ ) -> Dict[str, float]:
1144
+ """
1145
+ Calculate Greeks at expiry.
1146
+
1147
+ At expiry:
1148
+ - Price = intrinsic value
1149
+ - Delta = 1 (ITM call), -1 (ITM put), 0 (OTM)
1150
+ - Gamma, Vega, Theta, Rho = 0
1151
+
1152
+ Args:
1153
+ product: European vanilla option
1154
+ spot: Current spot price
1155
+
1156
+ Returns:
1157
+ Dictionary of Greeks
1158
+ """
1159
+ multiplier = product.contract_multiplier
1160
+ price = product.get_payoff(spot) / multiplier
1161
+
1162
+ # Delta at expiry
1163
+ if product.is_call():
1164
+ delta = 1.0 if spot > product.strike else 0.0
1165
+ else:
1166
+ delta = -1.0 if spot < product.strike else 0.0
1167
+
1168
+ return {
1169
+ "price": price * multiplier,
1170
+ "delta": delta * multiplier,
1171
+ "gamma": 0.0,
1172
+ "vega": 0.0,
1173
+ "theta": 0.0,
1174
+ "convexity_theta": 0.0,
1175
+ "r_theta": 0.0,
1176
+ "q_theta": 0.0,
1177
+ "rho": 0.0,
1178
+ }
1179
+
1180
+ def compare_greeks(
1181
+ self, analytical: Dict[str, float], numerical: Dict[str, float]
1182
+ ) -> Dict[str, Dict[str, float]]:
1183
+ """
1184
+ Compare analytical and numerical Greeks.
1185
+
1186
+ Args:
1187
+ analytical: Analytical Greeks
1188
+ numerical: Numerical Greeks
1189
+
1190
+ Returns:
1191
+ Dictionary with 'analytical', 'numerical', and 'difference' sub-dictionaries
1192
+ """
1193
+ difference = {}
1194
+ for key in analytical:
1195
+ if key in numerical:
1196
+ diff = analytical[key] - numerical[key]
1197
+ rel_diff = diff / analytical[key] if abs(analytical[key]) > 1e-10 else 0
1198
+ difference[key] = {"absolute": diff, "relative": rel_diff}
1199
+
1200
+ return {
1201
+ "analytical": analytical,
1202
+ "numerical": numerical,
1203
+ "difference": difference,
1204
+ }