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,682 @@
1
+ """
2
+ Analytical pricing engine for American vanilla options.
3
+
4
+ This module implements three approximation methods:
5
+ - BS93: Bjerksund-Stensland (1993) single-barrier approximation
6
+ - BS02: Bjerksund-Stensland (2002) two-barrier approximation
7
+ - BAW: Barone-Adesi & Whaley (1987) quadratic approximation
8
+
9
+ References:
10
+ [1] Bjerksund, P., and Stensland, G., 1993. Closed-form approximation of American options.
11
+ [2] Bjerksund, P., and Stensland, G., 2002. Closed-form approximation of American options.
12
+ Scandinavian Journal of Management, 18(4), 487-507.
13
+ [3] Barone-Adesi, G., and Whaley, R. E., 1987. Efficient analytic approximation of American
14
+ option values. Journal of Finance, 42(2), 301-320.
15
+ """
16
+
17
+ import numpy as np
18
+ from typing import Optional, Union
19
+
20
+ from scipy.stats import norm, multivariate_normal
21
+ from quantark.asset.equity.engine.base_engine import BaseEngine
22
+ from quantark.asset.equity.product.option import AmericanOption
23
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
24
+ from quantark.asset.equity.param import EngineParams
25
+ from quantark.priceenv import PricingEnvironment
26
+ from quantark.util.exceptions import ValidationError, NumericalError, PricingError
27
+ from quantark.util.enum.engine_enums import AmericanAnalyticalMethod, EngineType
28
+
29
+
30
+ class AmericanOptionAnalyticalEngine(BaseEngine):
31
+ """
32
+ Analytical pricing engine for American vanilla options.
33
+
34
+ Supports three approximation methods:
35
+ - BS93 (default): Fast, single-barrier approximation
36
+ - BS02: More accurate two-barrier approximation
37
+ - BAW: Quadratic approximation with iterative critical price search
38
+
39
+ For American puts, BS93/BS02 use put-call transformation while BAW uses direct put pricing.
40
+ """
41
+
42
+ engine_type = EngineType.ANALYTICAL
43
+
44
+ DEFAULT_METHOD = AmericanAnalyticalMethod.BS93
45
+
46
+ MIN_VOL = 0.001
47
+ MAX_VOL = 5.0
48
+ MIN_MATURITY = 1e-6
49
+ MAX_MATURITY = 30.0
50
+
51
+ def __init__(self, params: Optional[EngineParams] = None, method: Union[str, AmericanAnalyticalMethod, tuple] = None):
52
+ """
53
+ Initialize American option analytical engine.
54
+
55
+ Args:
56
+ params: Engine configuration parameters
57
+ method: Pricing method, can be:
58
+ - AmericanAnalyticalMethod enum (e.g., AmericanAnalyticalMethod.BS93)
59
+ - String "BS93"/"BS02"/"BAW" (backward compatibility)
60
+ - Tuple from EngineType.ANALYTICAL(AmericanAnalyticalMethod.BS93)
61
+ - None (defaults to BS93)
62
+
63
+ Raises:
64
+ ValidationError: If invalid method is specified
65
+ """
66
+ super().__init__(params)
67
+
68
+ if method is None:
69
+ self.method = self.DEFAULT_METHOD
70
+ elif isinstance(method, tuple):
71
+ engine_type, analytical_method = method
72
+ if engine_type != EngineType.ANALYTICAL:
73
+ raise ValidationError(
74
+ f"Expected EngineType.ANALYTICAL, got {engine_type}"
75
+ )
76
+ if not isinstance(analytical_method, AmericanAnalyticalMethod):
77
+ raise ValidationError(
78
+ f"Expected AmericanAnalyticalMethod, got {type(analytical_method).__name__}"
79
+ )
80
+ self.method = analytical_method
81
+ elif isinstance(method, AmericanAnalyticalMethod):
82
+ self.method = method
83
+ elif isinstance(method, str):
84
+ try:
85
+ self.method = AmericanAnalyticalMethod[method.upper()]
86
+ except KeyError:
87
+ valid_methods = ', '.join([m.name for m in AmericanAnalyticalMethod])
88
+ raise ValidationError(
89
+ f"Invalid method '{method}'. "
90
+ f"Valid methods are: {valid_methods}"
91
+ )
92
+ else:
93
+ raise ValidationError(
94
+ f"Method must be AmericanAnalyticalMethod enum, string, or EngineType tuple, got {type(method).__name__}"
95
+ )
96
+
97
+ def price(
98
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
99
+ ) -> float:
100
+ """
101
+ Price an American vanilla option using the selected approximation method.
102
+
103
+ Args:
104
+ product: American vanilla option
105
+ pricing_env: Pricing environment with market data
106
+
107
+ Returns:
108
+ Option price
109
+
110
+ Raises:
111
+ PricingError: If product is not an American option
112
+ ValidationError: If input parameters are invalid
113
+ NumericalError: If numerical computation fails
114
+ """
115
+ if not isinstance(product, AmericanOption):
116
+ raise PricingError(
117
+ f"AmericanOptionAnalyticalEngine only supports AmericanOption, "
118
+ f"got {type(product).__name__}"
119
+ )
120
+
121
+ S = pricing_env.spot
122
+ K = product.strike
123
+ T = product.get_maturity(pricing_env)
124
+ r = pricing_env.get_rate(T)
125
+ q = pricing_env.get_div_yield(T)
126
+ sigma = pricing_env.get_vol(K, T)
127
+ multiplier = product.contract_multiplier
128
+
129
+ self._validate_inputs(S, K, T, r, q, sigma)
130
+
131
+ if T < self.MIN_MATURITY:
132
+ return product.get_payoff(S)
133
+
134
+ sigma = np.clip(sigma, self.MIN_VOL, self.MAX_VOL)
135
+ T = np.clip(T, self.MIN_MATURITY, self.MAX_MATURITY)
136
+
137
+ b = r - q
138
+
139
+ is_call = product.is_call()
140
+
141
+ if is_call and b >= r and r >= 0:
142
+ return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
143
+
144
+ if is_call and b >= r and r < 0 and q <= r:
145
+ return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
146
+
147
+ try:
148
+ if self.method == AmericanAnalyticalMethod.BS93:
149
+ price = self._price_bs93(S, K, T, r, b, sigma, is_call)
150
+ elif self.method == AmericanAnalyticalMethod.BS02:
151
+ price = self._price_bs02(S, K, T, r, b, sigma, is_call)
152
+ else:
153
+ price = self._price_baw(S, K, T, r, b, sigma, is_call)
154
+
155
+ if np.isnan(price) or np.isinf(price):
156
+ raise NumericalError("NaN or Inf result detected")
157
+
158
+ price *= multiplier
159
+ intrinsic = product.intrinsic_value(S)
160
+ if price < intrinsic - 1e-6:
161
+ raise NumericalError(
162
+ f"Price ({price:.6f}) below intrinsic value ({intrinsic:.6f})"
163
+ )
164
+
165
+ return max(price, intrinsic)
166
+
167
+ except Exception as e:
168
+ import warnings
169
+
170
+ warnings.warn(
171
+ f"American option pricing failed ({e}), using European fallback"
172
+ )
173
+ if is_call:
174
+ return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
175
+ else:
176
+ return self._european_put_bsm(S, K, T, r, b, sigma) * multiplier
177
+
178
+ def _validate_inputs(
179
+ self, S: float, K: float, T: float, r: float, q: float, sigma: float
180
+ ):
181
+ """Validate input parameters."""
182
+ if S <= 0:
183
+ raise ValidationError(f"Spot price must be positive, got {S}")
184
+ if K <= 0:
185
+ raise ValidationError(f"Strike price must be positive, got {K}")
186
+ if T < 0:
187
+ raise ValidationError(f"Time to maturity must be non-negative, got {T}")
188
+ if sigma <= 0:
189
+ raise ValidationError(f"Volatility must be positive, got {sigma}")
190
+
191
+ def _price_bs93(
192
+ self,
193
+ S: float,
194
+ K: float,
195
+ T: float,
196
+ r: float,
197
+ b: float,
198
+ sigma: float,
199
+ is_call: bool,
200
+ ) -> float:
201
+ """Price using Bjerksund-Stensland 1993 approximation."""
202
+ if is_call:
203
+ return self._price_american_call_bs93(S, K, T, r, b, sigma)
204
+ else:
205
+ if r <= 0 and r <= b:
206
+ return self._european_put_bsm(S, K, T, r, b, sigma)
207
+ return self._price_american_put_bs93(S, K, T, r, b, sigma)
208
+
209
+ def _price_bs02(
210
+ self,
211
+ S: float,
212
+ K: float,
213
+ T: float,
214
+ r: float,
215
+ b: float,
216
+ sigma: float,
217
+ is_call: bool,
218
+ ) -> float:
219
+ """Price using Bjerksund-Stensland 2002 approximation."""
220
+ if is_call:
221
+ return self._price_american_call_bs02(S, K, T, r, b, sigma)
222
+ else:
223
+ if r <= 0 and r <= b:
224
+ return self._european_put_bsm(S, K, T, r, b, sigma)
225
+ return self._price_american_put_bs02(S, K, T, r, b, sigma)
226
+
227
+ def _price_baw(
228
+ self,
229
+ S: float,
230
+ K: float,
231
+ T: float,
232
+ r: float,
233
+ b: float,
234
+ sigma: float,
235
+ is_call: bool,
236
+ ) -> float:
237
+ """Price using Barone-Adesi-Whaley approximation."""
238
+ if is_call:
239
+ return self._price_american_call_baw(S, K, T, r, b, sigma)
240
+ else:
241
+ return self._price_american_put_baw(S, K, T, r, b, sigma)
242
+
243
+ def _price_american_call_bs93(
244
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
245
+ ) -> float:
246
+ """
247
+ Bjerksund-Stensland 1993 approximation for American call.
248
+
249
+ Single-barrier approximation using optimal exercise boundary.
250
+ """
251
+ beta = (0.5 - b / sigma**2) + np.sqrt(
252
+ (0.5 - b / sigma**2) ** 2 + 2 * r / sigma**2
253
+ )
254
+
255
+ B_infinity = beta * K / (beta - 1.0)
256
+ B_0 = max(K, r * K / (r - b))
257
+
258
+ h_T = -1.0 * (b * T + 2.0 * sigma * np.sqrt(T)) * B_0 / (B_infinity - B_0)
259
+
260
+ I = B_0 + (B_infinity - B_0) * (1.0 - self._safe_exp(h_T))
261
+
262
+ if S >= I:
263
+ return S - K
264
+
265
+ alpha = (I - K) * I ** (-beta)
266
+
267
+ value = (
268
+ alpha * S**beta
269
+ - alpha * self._phi_bs93(S, T, beta, I, I, r, b, sigma)
270
+ + self._phi_bs93(S, T, 1, I, I, r, b, sigma)
271
+ - self._phi_bs93(S, T, 1, K, I, r, b, sigma)
272
+ - K * self._phi_bs93(S, T, 0, I, I, r, b, sigma)
273
+ + K * self._phi_bs93(S, T, 0, K, I, r, b, sigma)
274
+ )
275
+
276
+ return value
277
+
278
+ def _price_american_put_bs93(
279
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
280
+ ) -> float:
281
+ """American put via put-call transformation for BS93."""
282
+ # Transform parameters for put-call analogue
283
+ # Put(S,K,r,q) = Call(K,S,q,r) with transformed b
284
+ S_new = K
285
+ K_new = S
286
+ r_new = r - b # This is q (dividend yield)
287
+ b_new = -b
288
+
289
+ call_value = self._price_american_call_bs93(
290
+ S_new, K_new, T, r_new, b_new, sigma
291
+ )
292
+
293
+ return call_value
294
+
295
+ def _phi_bs93(
296
+ self,
297
+ S: float,
298
+ T: float,
299
+ gamma: float,
300
+ H: float,
301
+ I: float,
302
+ r: float,
303
+ b: float,
304
+ sigma: float,
305
+ ) -> float:
306
+ """
307
+ Auxiliary function φ for BS93.
308
+
309
+ φ(S, T, γ, H, I) = e^(λT) S^γ [N(d) - (I/S)^κ N(d₂)]
310
+
311
+ Note: This follows the original BS93 formula structure.
312
+ """
313
+ lamda = T * (-r + b * gamma + 0.5 * gamma * (gamma - 1) * sigma**2)
314
+
315
+ d = (
316
+ -1.0
317
+ * (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T)
318
+ / (sigma * np.sqrt(T))
319
+ )
320
+
321
+ kappa = 2 * b / sigma**2 + 2 * gamma - 1
322
+
323
+ d2 = d - 2 * self._safe_log(I / S) / (sigma * np.sqrt(T))
324
+
325
+ return (
326
+ self._safe_exp(lamda)
327
+ * S**gamma
328
+ * (norm.cdf(d) - (I / S) ** kappa * norm.cdf(d2))
329
+ )
330
+
331
+ def _price_american_call_bs02(
332
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
333
+ ) -> float:
334
+ """
335
+ Bjerksund-Stensland 2002 approximation for American call.
336
+
337
+ More accurate two-barrier approximation.
338
+ """
339
+ beta = (0.5 - b / sigma**2) + np.sqrt(
340
+ (0.5 - b / sigma**2) ** 2 + 2 * r / sigma**2
341
+ )
342
+
343
+ B_infinity = beta * K / (beta - 1.0)
344
+ B_0 = max(K, r * K / (r - b))
345
+
346
+ t1 = 0.5 * (np.sqrt(5) - 1) * T
347
+
348
+ h1 = -(b * t1 + 2 * sigma * np.sqrt(t1)) * K**2 / ((B_infinity - B_0) * B_0)
349
+ h2 = -(b * T + 2 * sigma * np.sqrt(T)) * K**2 / ((B_infinity - B_0) * B_0)
350
+
351
+ I1 = B_0 + (B_infinity - B_0) * (1 - self._safe_exp(h1))
352
+ I2 = B_0 + (B_infinity - B_0) * (1 - self._safe_exp(h2))
353
+
354
+ if I1 > K:
355
+ log_alpha1 = self._safe_log(I1 - K) - beta * self._safe_log(I1)
356
+ alpha1 = self._safe_exp(log_alpha1) if log_alpha1 > -700 else 0.0
357
+ else:
358
+ alpha1 = 0.0
359
+
360
+ if I2 > K:
361
+ log_alpha2 = self._safe_log(I2 - K) - beta * self._safe_log(I2)
362
+ alpha2 = self._safe_exp(log_alpha2) if log_alpha2 > -700 else 0.0
363
+ else:
364
+ alpha2 = 0.0
365
+
366
+ if S >= I2:
367
+ return S - K
368
+
369
+ value = (
370
+ alpha2 * S**beta
371
+ - alpha2 * self._phi_bs02(S, t1, beta, I2, I2, r, b, sigma)
372
+ + self._phi_bs02(S, t1, 1, I2, I2, r, b, sigma)
373
+ - self._phi_bs02(S, t1, 1, I1, I2, r, b, sigma)
374
+ - K * self._phi_bs02(S, t1, 0, I2, I2, r, b, sigma)
375
+ + K * self._phi_bs02(S, t1, 0, I1, I2, r, b, sigma)
376
+ + alpha1 * self._phi_bs02(S, t1, beta, I1, I2, r, b, sigma)
377
+ - alpha1 * self._psi_bs02(S, T, beta, I1, I2, I1, t1, r, b, sigma)
378
+ + self._psi_bs02(S, T, 1, I1, I2, I1, t1, r, b, sigma)
379
+ - self._psi_bs02(S, T, 1, K, I2, I1, t1, r, b, sigma)
380
+ - K * self._psi_bs02(S, T, 0, I1, I2, I1, t1, r, b, sigma)
381
+ + K * self._psi_bs02(S, T, 0, K, I2, I1, t1, r, b, sigma)
382
+ )
383
+
384
+ return value
385
+
386
+ def _price_american_put_bs02(
387
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
388
+ ) -> float:
389
+ """American put via put-call transformation for BS02."""
390
+ S_orig, K_orig, r_orig, b_orig = S, K, r, b
391
+
392
+ S = K_orig
393
+ K = S_orig
394
+ r = r_orig - b_orig
395
+ b = -b_orig
396
+
397
+ call_value = self._price_american_call_bs02(S, K, T, r, b, sigma)
398
+
399
+ return call_value
400
+
401
+ def _phi_bs02(
402
+ self,
403
+ S: float,
404
+ T: float,
405
+ gamma: float,
406
+ H: float,
407
+ I: float,
408
+ r: float,
409
+ b: float,
410
+ sigma: float,
411
+ ) -> float:
412
+ """
413
+ Auxiliary function φ for BS02.
414
+
415
+ φ(S, T, γ, H, I) = e^(λT) S^γ [N(-d) - (I/S)^κ N(-d₂)]
416
+ """
417
+ if S <= 0 or T <= 0 or H <= 0 or I <= 0:
418
+ return 0.0
419
+
420
+ lambda_val = -r + gamma * b + 0.5 * gamma * (gamma - 1) * sigma**2
421
+ kappa = 2 * b / sigma**2 + 2 * gamma - 1
422
+
423
+ d = (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T) / (
424
+ sigma * np.sqrt(T)
425
+ )
426
+ d2 = (
427
+ self._safe_log(I**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
428
+ ) / (sigma * np.sqrt(T))
429
+
430
+ term1 = norm.cdf(-d)
431
+ term2 = (I / S) ** kappa * norm.cdf(-d2)
432
+
433
+ return self._safe_exp(lambda_val * T) * S**gamma * (term1 - term2)
434
+
435
+ def _psi_bs02(
436
+ self,
437
+ S: float,
438
+ T: float,
439
+ gamma: float,
440
+ H: float,
441
+ I2: float,
442
+ I1: float,
443
+ t1: float,
444
+ r: float,
445
+ b: float,
446
+ sigma: float,
447
+ ) -> float:
448
+ """
449
+ Auxiliary function Ψ for BS02.
450
+
451
+ Uses bivariate normal CDF for improved accuracy.
452
+ """
453
+ if S <= 0 or T <= 0 or t1 <= 0 or H <= 0 or I1 <= 0 or I2 <= 0:
454
+ return 0.0
455
+
456
+ lambda_val = -r + gamma * b + 0.5 * gamma * (gamma - 1) * sigma**2
457
+ kappa = 2 * b / sigma**2 + 2 * gamma - 1
458
+
459
+ rho = np.sqrt(t1 / T)
460
+
461
+ # Calculate e parameters (for t1 dimension)
462
+ e1 = (self._safe_log(S / I1) + (b + (gamma - 0.5) * sigma**2) * t1) / (
463
+ sigma * np.sqrt(t1)
464
+ )
465
+ e2 = (
466
+ self._safe_log(I2**2 / (S * I1)) + (b + (gamma - 0.5) * sigma**2) * t1
467
+ ) / (sigma * np.sqrt(t1))
468
+ e3 = (self._safe_log(S / I1) - (b + (gamma - 0.5) * sigma**2) * t1) / (
469
+ sigma * np.sqrt(t1)
470
+ )
471
+ e4 = (
472
+ self._safe_log(I2**2 / (S * I1)) - (b + (gamma - 0.5) * sigma**2) * t1
473
+ ) / (sigma * np.sqrt(t1))
474
+
475
+ # Calculate f parameters (for T dimension)
476
+ f1 = (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T) / (
477
+ sigma * np.sqrt(T)
478
+ )
479
+ f2 = (
480
+ self._safe_log(I2**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
481
+ ) / (sigma * np.sqrt(T))
482
+ f3 = (
483
+ self._safe_log(I2**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
484
+ ) / (sigma * np.sqrt(T))
485
+ f4 = (
486
+ self._safe_log(S * I1**2 / (H * I2**2)) + (b + (gamma - 0.5) * sigma**2) * T
487
+ ) / (sigma * np.sqrt(T))
488
+
489
+ M1 = self._bivariate_normal_cdf(-e1, -f1, rho)
490
+ M2 = self._bivariate_normal_cdf(-e2, -f2, rho)
491
+ M3 = self._bivariate_normal_cdf(-e3, -f3, -rho)
492
+ M4 = self._bivariate_normal_cdf(-e4, -f4, -rho)
493
+
494
+ term1 = M1
495
+ term2 = (I2 / S) ** kappa * M2
496
+ term3 = (I1 / S) ** kappa * M3
497
+ term4 = (I1 / I2) ** kappa * M4
498
+
499
+ return (
500
+ self._safe_exp(lambda_val * T) * S**gamma * (term1 - term2 - term3 + term4)
501
+ )
502
+
503
+ def _bivariate_normal_cdf(self, x: float, y: float, rho: float) -> float:
504
+ """
505
+ Bivariate normal CDF M(x, y, ρ).
506
+
507
+ Uses scipy's multivariate_normal for accurate computation.
508
+ """
509
+ if abs(rho) < 1e-10:
510
+ return norm.cdf(x) * norm.cdf(y)
511
+
512
+ if abs(rho) >= 1.0:
513
+ if rho > 0:
514
+ return min(norm.cdf(x), norm.cdf(y))
515
+ else:
516
+ return max(norm.cdf(x) + norm.cdf(y) - 1, 0.0)
517
+
518
+ mean = [0, 0]
519
+ cov = [[1, rho], [rho, 1]]
520
+ return multivariate_normal.cdf([x, y], mean, cov)
521
+
522
+ def _price_american_call_baw(
523
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
524
+ ) -> float:
525
+ """
526
+ Barone-Adesi-Whaley approximation for American call.
527
+
528
+ Quadratic approximation with iterative critical price search.
529
+ """
530
+ if b >= r:
531
+ return self._european_call_bsm(S, K, T, r, b, sigma)
532
+
533
+ M = 2 * r / sigma**2
534
+ N = 2 * b / sigma**2
535
+ K_param = 1 - self._safe_exp(-r * T)
536
+
537
+ discriminant = (N - 1) ** 2 + 4 * M / K_param
538
+ if discriminant < 0:
539
+ return self._european_call_bsm(S, K, T, r, b, sigma)
540
+
541
+ q2 = (-(N - 1) + np.sqrt(discriminant)) / 2
542
+
543
+ S_star = self._find_critical_call_price(K, T, r, b, sigma, q2)
544
+
545
+ if S >= S_star:
546
+ return S - K
547
+
548
+ d1_s_star = self._d1_baw(S_star, K, T, b, sigma)
549
+ A2 = (S_star / q2) * (1 - self._safe_exp((b - r) * T) * norm.cdf(d1_s_star))
550
+
551
+ c_bsm = self._european_call_bsm(S, K, T, r, b, sigma)
552
+
553
+ return c_bsm + A2 * (S / S_star) ** q2
554
+
555
+ def _price_american_put_baw(
556
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
557
+ ) -> float:
558
+ """
559
+ Barone-Adesi-Whaley approximation for American put.
560
+
561
+ Direct put pricing (not transformation).
562
+ """
563
+ if r <= 0 and r <= b:
564
+ return self._european_put_bsm(S, K, T, r, b, sigma)
565
+
566
+ M = 2 * r / sigma**2
567
+ N = 2 * b / sigma**2
568
+ K_param = 1 - self._safe_exp(-r * T)
569
+
570
+ discriminant = (N - 1) ** 2 + 4 * M / K_param
571
+ if discriminant < 0:
572
+ return self._european_put_bsm(S, K, T, r, b, sigma)
573
+
574
+ q1 = (-(N - 1) - np.sqrt(discriminant)) / 2
575
+
576
+ S_star_star = self._find_critical_put_price(K, T, r, b, sigma, q1)
577
+
578
+ if S <= S_star_star:
579
+ return K - S
580
+
581
+ d1_s_star_star = self._d1_baw(S_star_star, K, T, b, sigma)
582
+ A1 = -(S_star_star / q1) * (
583
+ 1 - self._safe_exp((b - r) * T) * norm.cdf(-d1_s_star_star)
584
+ )
585
+
586
+ p_bsm = self._european_put_bsm(S, K, T, r, b, sigma)
587
+
588
+ return p_bsm + A1 * (S / S_star_star) ** q1
589
+
590
+ def _find_critical_call_price(
591
+ self, K: float, T: float, r: float, b: float, sigma: float, q2: float
592
+ ) -> float:
593
+ """Find critical stock price S* for American call using optimization."""
594
+ from scipy.optimize import fmin
595
+
596
+ def objective(S_star):
597
+ if S_star <= 0:
598
+ return float("inf")
599
+ d1_s = self._d1_baw(S_star, K, T, b, sigma)
600
+ c_bsm = self._european_call_bsm(S_star, K, T, r, b, sigma)
601
+ lhs = S_star - K
602
+ rhs = (
603
+ c_bsm + (1 - self._safe_exp((b - r) * T) * norm.cdf(d1_s)) * S_star / q2
604
+ )
605
+ return abs(lhs - rhs)
606
+
607
+ S0 = max(K, K * 1.1)
608
+ try:
609
+ result = fmin(objective, S0, disp=False, full_output=True)
610
+ return result[0][0] if result[4] == 0 else K * 1.5
611
+ except:
612
+ return K * 1.5
613
+
614
+ def _find_critical_put_price(
615
+ self, K: float, T: float, r: float, b: float, sigma: float, q1: float
616
+ ) -> float:
617
+ """Find critical stock price S** for American put using optimization."""
618
+ from scipy.optimize import fmin
619
+
620
+ def objective(S_star_star):
621
+ if S_star_star <= 0:
622
+ return float("inf")
623
+ d1_s = self._d1_baw(S_star_star, K, T, b, sigma)
624
+ p_bsm = self._european_put_bsm(S_star_star, K, T, r, b, sigma)
625
+ lhs = K - S_star_star
626
+ rhs = (
627
+ p_bsm
628
+ - (1 - self._safe_exp((b - r) * T) * norm.cdf(-d1_s)) * S_star_star / q1
629
+ )
630
+ return abs(lhs - rhs)
631
+
632
+ S0 = min(K, K * 0.9)
633
+ try:
634
+ result = fmin(objective, S0, disp=False, full_output=True)
635
+ return result[0][0] if result[4] == 0 else K * 0.5
636
+ except:
637
+ return K * 0.5
638
+
639
+ def _d1_baw(self, S: float, K: float, T: float, b: float, sigma: float) -> float:
640
+ """Calculate d1 for BAW method."""
641
+ return (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
642
+
643
+ def _european_call_bsm(
644
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
645
+ ) -> float:
646
+ """European call price using Black-Scholes-Merton formula."""
647
+ d1 = (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
648
+ d2 = d1 - sigma * np.sqrt(T)
649
+
650
+ return S * self._safe_exp((b - r) * T) * norm.cdf(d1) - K * self._safe_exp(
651
+ -r * T
652
+ ) * norm.cdf(d2)
653
+
654
+ def _european_put_bsm(
655
+ self, S: float, K: float, T: float, r: float, b: float, sigma: float
656
+ ) -> float:
657
+ """European put price using Black-Scholes-Merton formula."""
658
+ d1 = (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
659
+ d2 = d1 - sigma * np.sqrt(T)
660
+
661
+ return K * self._safe_exp(-r * T) * norm.cdf(-d2) - S * self._safe_exp(
662
+ (b - r) * T
663
+ ) * norm.cdf(-d1)
664
+
665
+ def _safe_log(self, x: float) -> float:
666
+ """Safe logarithm to avoid log(0) or log(negative)."""
667
+ return np.log(max(x, 1e-16))
668
+
669
+ def _safe_sqrt(self, x: float) -> float:
670
+ """Safe square root to avoid sqrt(negative)."""
671
+ return np.sqrt(max(x, 0.0))
672
+
673
+ def _safe_exp(self, x: float) -> float:
674
+ """Safe exponential to avoid overflow/underflow."""
675
+ if x > 700:
676
+ return np.exp(700)
677
+ if x < -700:
678
+ return 0.0
679
+ return np.exp(x)
680
+
681
+ def __repr__(self):
682
+ return f"AmericanOptionAnalyticalEngine(method='{self.method}')"