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,726 @@
1
+ """
2
+ Monte Carlo pricing engine for barrier options.
3
+ """
4
+
5
+ import math
6
+ from typing import Optional, Union, Tuple
7
+
8
+ import numpy as np
9
+
10
+ from quantark.asset.equity.engine.base_engine import BaseEngine
11
+ from quantark.asset.equity.product.option import BarrierOption
12
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
13
+ from quantark.asset.equity.param import MCParams
14
+ from quantark.priceenv import PricingEnvironment
15
+ from quantark.util.enum import ObservationType, ObservationAggregation
16
+ from quantark.util.enum.engine_enums import MonteCarloMethod, EngineType
17
+ from quantark.util.exceptions import ValidationError, PricingError
18
+ from quantark.util.numerical import (
19
+ is_zero,
20
+ is_close,
21
+ validate_positive,
22
+ validate_non_negative,
23
+ safe_exp,
24
+ safe_log,
25
+ )
26
+
27
+ from quantark.asset.equity.process.bsm.qmc_brownian_bridge import compute_step_crossing_probabilities
28
+ from quantark.asset.equity.process.bsm.qmc_path_generator import GBMPathGenerator
29
+ from quantark.asset.equity.process.bsm.qmc_sobol import (
30
+ PseudoRandomNormalGenerator,
31
+ SobolNormalGenerator,
32
+ )
33
+ from quantark.asset.equity.process.bsm.qmc_rqmc_driver import run_rqmc
34
+ from quantark.asset.equity.process.bsm.qmc_variance_reduction import VarianceReductionConfig
35
+
36
+
37
+ class BarrierOptionMCEngine(BaseEngine):
38
+ """
39
+ Monte Carlo pricing engine for barrier options.
40
+
41
+ Supports three Monte Carlo methods:
42
+ - PSEUDO: Standard Monte Carlo with pseudorandom numbers
43
+ - QUASI: Quasi-Monte Carlo with Sobol sequences
44
+ - RANDOMIZED_QUASI: Randomized QMC with adaptive batching
45
+
46
+ Optional Brownian-bridge handling can be enabled for continuous
47
+ monitoring to approximate barrier crossings between time steps.
48
+ """
49
+
50
+ engine_type = EngineType.MONTE_CARLO
51
+
52
+ DEFAULT_METHOD = MonteCarloMethod.PSEUDO
53
+
54
+ def __init__(
55
+ self,
56
+ params: Optional[MCParams] = None,
57
+ method: Union[str, MonteCarloMethod, tuple, None] = None,
58
+ use_brownian_bridge: bool = False,
59
+ ):
60
+ """
61
+ Initialize Monte Carlo engine for barrier options.
62
+
63
+ Args:
64
+ params: Monte Carlo configuration parameters (MCParams)
65
+ method: Monte Carlo method selection, one of:
66
+ - EngineType.MONTE_CARLO(MonteCarloMethod.XXX) (preferred)
67
+ - MonteCarloMethod.XXX
68
+ - String: "pseudo", "quasi", "randomized_quasi"
69
+ - None: defaults to MonteCarloMethod.PSEUDO
70
+ use_brownian_bridge: Enable Brownian-bridge handling for continuous monitoring
71
+
72
+ Raises:
73
+ ValidationError: If method is invalid or params are invalid
74
+ """
75
+ if params is None:
76
+ params = MCParams()
77
+
78
+ if not isinstance(params, MCParams):
79
+ raise ValidationError(
80
+ f"params must be MCParams instance, got {type(params).__name__}"
81
+ )
82
+
83
+ super().__init__(params)
84
+
85
+ if method is None:
86
+ self.method = self.DEFAULT_METHOD
87
+ elif isinstance(method, tuple):
88
+ engine_type, mc_method = method
89
+ if engine_type != EngineType.MONTE_CARLO:
90
+ raise ValidationError(
91
+ f"Expected EngineType.MONTE_CARLO, got {engine_type}"
92
+ )
93
+ if not isinstance(mc_method, MonteCarloMethod):
94
+ raise ValidationError(
95
+ f"Expected MonteCarloMethod, got {type(mc_method).__name__}"
96
+ )
97
+ self.method = mc_method
98
+ elif isinstance(method, MonteCarloMethod):
99
+ self.method = method
100
+ elif isinstance(method, str):
101
+ try:
102
+ self.method = MonteCarloMethod[method.upper()]
103
+ except KeyError:
104
+ valid_methods = [m.name for m in MonteCarloMethod]
105
+ raise ValidationError(
106
+ f"Invalid method string '{method}'. Valid methods: {valid_methods}"
107
+ )
108
+ else:
109
+ raise ValidationError(
110
+ f"Invalid method type {type(method).__name__}. "
111
+ "Expected MonteCarloMethod, tuple, str, or None"
112
+ )
113
+
114
+ if not isinstance(use_brownian_bridge, bool):
115
+ raise ValidationError(
116
+ f"use_brownian_bridge must be bool, got {type(use_brownian_bridge).__name__}"
117
+ )
118
+ self.use_brownian_bridge = use_brownian_bridge
119
+
120
+ def price(
121
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
122
+ ) -> float:
123
+ """
124
+ Price a barrier option using Monte Carlo simulation.
125
+
126
+ Args:
127
+ product: Barrier option to price
128
+ pricing_env: Pricing environment with market data
129
+
130
+ Returns:
131
+ Option price
132
+
133
+ Raises:
134
+ PricingError: If product is not a barrier option
135
+ ValidationError: If pricing parameters are invalid
136
+ """
137
+ if not isinstance(product, BarrierOption):
138
+ raise PricingError(
139
+ f"BarrierOptionMCEngine only supports BarrierOption, "
140
+ f"got {type(product).__name__}"
141
+ )
142
+
143
+ S = pricing_env.spot
144
+ K = product.strike
145
+ T = product.get_maturity(pricing_env)
146
+ r = pricing_env.get_rate(T)
147
+ q = pricing_env.get_div_yield(T)
148
+ sigma = pricing_env.get_vol(K, T)
149
+
150
+ self._validate_inputs(S, K, T, r, q, sigma, product)
151
+
152
+ if is_zero(T):
153
+ return self._price_expiry_payoff(product, S, r, T)
154
+
155
+ if product.observation_type != ObservationType.EXPIRY and product.is_barrier_hit(S):
156
+ if product.is_knock_out:
157
+ return self._price_immediate_knock_out(product, r, T)
158
+ return self._price_vanilla_mc(product, pricing_env)
159
+
160
+ if product.observation_type == ObservationType.EXPIRY:
161
+ if self.method == MonteCarloMethod.RANDOMIZED_QUASI:
162
+ price, std_error = self._price_rqmc(
163
+ product, pricing_env, S, K, T, r, q, sigma
164
+ )
165
+ else:
166
+ price, std_error = self._price_expiry_mc(
167
+ product, S, K, T, r, q, sigma
168
+ )
169
+ elif self.method == MonteCarloMethod.RANDOMIZED_QUASI:
170
+ price, std_error = self._price_rqmc(
171
+ product, pricing_env, S, K, T, r, q, sigma
172
+ )
173
+ else:
174
+ price, std_error = self._price_mc_or_qmc(
175
+ product, pricing_env, S, K, T, r, q, sigma
176
+ )
177
+
178
+ self._last_std_error = std_error
179
+
180
+ if price < 0.0:
181
+ raise PricingError(f"Negative price computed: {price}")
182
+
183
+ return price
184
+
185
+ def _validate_inputs(
186
+ self,
187
+ S: float,
188
+ K: float,
189
+ T: float,
190
+ r: float,
191
+ q: float,
192
+ sigma: float,
193
+ product: BarrierOption,
194
+ ) -> None:
195
+ """Validate pricing inputs."""
196
+ validate_positive(S, "spot")
197
+ validate_positive(K, "strike")
198
+ validate_non_negative(T, "maturity")
199
+ validate_positive(sigma, "volatility")
200
+ validate_non_negative(q, "dividend_yield")
201
+ validate_positive(product.barrier, "barrier")
202
+ validate_non_negative(product.rebate, "rebate")
203
+ validate_positive(product.participation_rate, "participation_rate")
204
+
205
+ def _price_immediate_knock_out(
206
+ self, product: BarrierOption, r: float, T: float
207
+ ) -> float:
208
+ if product.pay_at_hit:
209
+ return product.rebate
210
+ return product.rebate * safe_exp(-r * T)
211
+
212
+ def _price_vanilla_mc(
213
+ self, product: BarrierOption, pricing_env: PricingEnvironment
214
+ ) -> float:
215
+ from quantark.asset.equity.engine.mc.euro_mc_engine import EuropeanMCEngine
216
+ from quantark.asset.equity.product.option import EuropeanVanillaOption
217
+
218
+ vanilla = EuropeanVanillaOption(
219
+ strike=product.strike,
220
+ option_type=product.option_type,
221
+ maturity=product.maturity,
222
+ exercise_date=product.exercise_date,
223
+ settlement_date=product.settlement_date,
224
+ )
225
+
226
+ engine = EuropeanMCEngine(params=self.params, method=self.method)
227
+ price = engine.price(vanilla, pricing_env)
228
+ self._last_std_error = engine.get_last_std_error()
229
+ self._last_rqmc_result = engine.get_last_rqmc_result()
230
+ return price * product.participation_rate
231
+
232
+ def _create_path_generator(
233
+ self,
234
+ S: float,
235
+ r: float,
236
+ q: float,
237
+ sigma: float,
238
+ T: float,
239
+ dt_array: np.ndarray,
240
+ batch_id: Optional[int] = None,
241
+ num_paths: Optional[int] = None,
242
+ ) -> GBMPathGenerator:
243
+ """
244
+ Create a GBMPathGenerator configured for the observation grid.
245
+ """
246
+ params = self.params
247
+ effective_num_paths = params.num_paths if num_paths is None else int(num_paths)
248
+ if effective_num_paths <= 0:
249
+ raise ValidationError(
250
+ f"num_paths must be positive, got {effective_num_paths}"
251
+ )
252
+
253
+ if self.method == MonteCarloMethod.PSEUDO:
254
+ seed = params.seed + (batch_id or 0) * 1000
255
+ random_stream = PseudoRandomNormalGenerator(seed=seed)
256
+ is_qmc = False
257
+ elif self.method in (MonteCarloMethod.QUASI, MonteCarloMethod.RANDOMIZED_QUASI):
258
+ random_stream = SobolNormalGenerator(base_seed=params.seed)
259
+ is_qmc = True
260
+ else:
261
+ raise ValidationError(f"Unknown Monte Carlo method: {self.method}")
262
+
263
+ vr_config = None
264
+ if params.use_antithetic and not is_qmc:
265
+ vr_config = VarianceReductionConfig(antithetic=True)
266
+
267
+ generator = GBMPathGenerator(
268
+ initial_value=S,
269
+ vol=sigma,
270
+ rrf=r,
271
+ div=q,
272
+ maturity=T,
273
+ time_steps=len(dt_array),
274
+ num_paths=effective_num_paths,
275
+ model="bsm",
276
+ random_stream=random_stream,
277
+ use_brownian_bridge=self.use_brownian_bridge,
278
+ vr_config=vr_config,
279
+ is_qmc=is_qmc,
280
+ dt_array=dt_array,
281
+ )
282
+
283
+ return generator
284
+
285
+ def _build_continuous_grid(self, T: float) -> Tuple[np.ndarray, np.ndarray]:
286
+ time_steps = int(self.params.time_steps)
287
+ if time_steps <= 0:
288
+ raise ValidationError(f"time_steps must be positive, got {time_steps}")
289
+ dt_array = np.full(time_steps, T / float(time_steps), dtype=float)
290
+ times = np.cumsum(dt_array)
291
+ return times, dt_array
292
+
293
+ def _build_discrete_grid(
294
+ self, product: BarrierOption, pricing_env: PricingEnvironment, T: float
295
+ ) -> Tuple[
296
+ np.ndarray,
297
+ np.ndarray,
298
+ np.ndarray,
299
+ np.ndarray,
300
+ np.ndarray,
301
+ ObservationAggregation,
302
+ ]:
303
+ schedule = product.observation_schedule
304
+ if schedule is None or not schedule.records:
305
+ raise ValidationError("Discrete monitoring requires ObservationSchedule.")
306
+
307
+ resolved = schedule.resolve(
308
+ pricing_env,
309
+ default_barrier=product.barrier,
310
+ default_payoff=product.rebate,
311
+ require_single=True,
312
+ )
313
+
314
+ obs_times = np.array([rec.observation_time for rec in resolved], dtype=float)
315
+ if np.any(obs_times < 0.0) or np.any(obs_times > T):
316
+ raise ValidationError("Observation times must be within [0, maturity].")
317
+ sorted_obs = np.sort(obs_times)
318
+ if np.any(np.diff(sorted_obs) <= 0.0):
319
+ raise ValidationError("Observation times must be strictly increasing.")
320
+
321
+ if is_close(sorted_obs[-1], T):
322
+ all_times = sorted_obs
323
+ else:
324
+ all_times = np.concatenate([sorted_obs, [T]])
325
+
326
+ dt_array = np.diff(np.concatenate([[0.0], all_times]))
327
+ obs_indices = np.searchsorted(all_times, obs_times)
328
+ barriers = np.array([rec.barrier for rec in resolved], dtype=float)
329
+ payoffs = np.array([rec.payoff for rec in resolved], dtype=float)
330
+ settlement_times = np.array(
331
+ [
332
+ rec.settlement_time if rec.settlement_time is not None else rec.observation_time
333
+ for rec in resolved
334
+ ],
335
+ dtype=float,
336
+ )
337
+
338
+ return (
339
+ all_times,
340
+ dt_array,
341
+ obs_indices,
342
+ barriers,
343
+ payoffs,
344
+ settlement_times,
345
+ schedule.aggregation_mode,
346
+ )
347
+
348
+ def _calculate_vanilla_payoff(
349
+ self, product: BarrierOption, terminal_prices: np.ndarray
350
+ ) -> np.ndarray:
351
+ K = product.strike
352
+ if product.is_call():
353
+ payoffs = np.maximum(terminal_prices - K, 0.0)
354
+ else:
355
+ payoffs = np.maximum(K - terminal_prices, 0.0)
356
+ return payoffs * product.participation_rate
357
+
358
+ def _price_expiry_payoff(
359
+ self, product: BarrierOption, spot: float, r: float, T: float
360
+ ) -> float:
361
+ hit = product.is_barrier_hit(spot)
362
+ payoff = product.get_payoff(spot) * product.participation_rate
363
+ if product.is_knock_out:
364
+ value = product.rebate if hit else payoff
365
+ else:
366
+ value = payoff if hit else product.rebate
367
+ return value * safe_exp(-r * T)
368
+
369
+ def _expiry_payoffs(
370
+ self, product: BarrierOption, terminal_prices: np.ndarray
371
+ ) -> np.ndarray:
372
+ hit = terminal_prices >= product.barrier if product.is_up_barrier else terminal_prices <= product.barrier
373
+ vanilla = self._calculate_vanilla_payoff(product, terminal_prices)
374
+ if product.is_knock_out:
375
+ return np.where(hit, product.rebate, vanilla)
376
+ return np.where(hit, vanilla, product.rebate)
377
+
378
+ def _compute_bridge_step_hit_probabilities(
379
+ self,
380
+ paths: np.ndarray,
381
+ barrier: float,
382
+ sigma: float,
383
+ times: np.ndarray,
384
+ ) -> np.ndarray:
385
+ return compute_step_crossing_probabilities(paths, barrier, sigma, times)
386
+
387
+ def _expected_rebate_at_hit(
388
+ self,
389
+ step_hit_prob: np.ndarray,
390
+ times: np.ndarray,
391
+ r: float,
392
+ rebate: float,
393
+ ) -> np.ndarray:
394
+ survival_before = np.cumprod(1.0 - step_hit_prob, axis=1)
395
+ survival_before = np.concatenate(
396
+ [np.ones((step_hit_prob.shape[0], 1)), survival_before[:, :-1]], axis=1
397
+ )
398
+ first_hit_prob = survival_before * step_hit_prob
399
+ discount = safe_exp(-r * times).reshape(1, -1)
400
+ return rebate * np.sum(first_hit_prob * discount, axis=1)
401
+
402
+ def _aggregate_discrete_hit_payoffs(
403
+ self,
404
+ hit_matrix: np.ndarray,
405
+ payoffs: np.ndarray,
406
+ settlement_times: np.ndarray,
407
+ aggregation: ObservationAggregation,
408
+ r: float,
409
+ pay_at_hit: bool,
410
+ T: float,
411
+ ) -> Tuple[np.ndarray, np.ndarray]:
412
+ hit_any = hit_matrix.any(axis=1)
413
+ if hit_matrix.size == 0:
414
+ return np.zeros(hit_matrix.shape[0], dtype=float), hit_any
415
+
416
+ if aggregation == ObservationAggregation.STOP_FIRST_HIT:
417
+ first_idx = np.argmax(hit_matrix, axis=1)
418
+ payoff = payoffs[first_idx]
419
+ if pay_at_hit:
420
+ discount = safe_exp(-r * settlement_times[first_idx])
421
+ else:
422
+ discount = safe_exp(-r * T)
423
+ discounted = payoff * discount
424
+ discounted[~hit_any] = 0.0
425
+ return discounted, hit_any
426
+
427
+ if aggregation == ObservationAggregation.ACCUMULATE:
428
+ if pay_at_hit:
429
+ discount = safe_exp(-r * settlement_times)
430
+ discounted = np.sum(hit_matrix * (payoffs * discount), axis=1)
431
+ else:
432
+ total = np.sum(hit_matrix * payoffs, axis=1)
433
+ discounted = total * safe_exp(-r * T)
434
+ return discounted, hit_any
435
+
436
+ if aggregation in (ObservationAggregation.BEST, ObservationAggregation.WORST):
437
+ if pay_at_hit:
438
+ discount = safe_exp(-r * settlement_times)
439
+ value_matrix = hit_matrix * (payoffs * discount)
440
+ if aggregation == ObservationAggregation.BEST:
441
+ discounted = value_matrix.max(axis=1)
442
+ else:
443
+ value_matrix = np.where(hit_matrix, value_matrix, np.inf)
444
+ discounted = value_matrix.min(axis=1)
445
+ else:
446
+ value_matrix = hit_matrix * payoffs
447
+ if aggregation == ObservationAggregation.BEST:
448
+ best = value_matrix.max(axis=1)
449
+ discounted = best * safe_exp(-r * T)
450
+ else:
451
+ value_matrix = np.where(hit_matrix, value_matrix, np.inf)
452
+ worst = value_matrix.min(axis=1)
453
+ discounted = worst * safe_exp(-r * T)
454
+ discounted[~hit_any] = 0.0
455
+ return discounted, hit_any
456
+
457
+ raise ValidationError(f"Unknown aggregation mode: {aggregation}")
458
+
459
+ def _discounted_payoffs_continuous(
460
+ self,
461
+ product: BarrierOption,
462
+ paths: np.ndarray,
463
+ times: np.ndarray,
464
+ r: float,
465
+ T: float,
466
+ sigma: float,
467
+ ) -> np.ndarray:
468
+ terminal_prices = paths[:, -1]
469
+ vanilla_payoffs = self._calculate_vanilla_payoff(product, terminal_prices)
470
+ df_T = safe_exp(-r * T)
471
+
472
+ if self.use_brownian_bridge:
473
+ step_hit_prob = self._compute_bridge_step_hit_probabilities(
474
+ paths, product.barrier, sigma, times
475
+ )
476
+ survival_prob = np.prod(1.0 - step_hit_prob, axis=1)
477
+
478
+ if product.is_knock_out:
479
+ if product.pay_at_hit:
480
+ rebate_leg = self._expected_rebate_at_hit(
481
+ step_hit_prob, times, r, product.rebate
482
+ )
483
+ return rebate_leg + survival_prob * vanilla_payoffs * df_T
484
+ return (
485
+ survival_prob * vanilla_payoffs
486
+ + (1.0 - survival_prob) * product.rebate
487
+ ) * df_T
488
+
489
+ return (
490
+ (1.0 - survival_prob) * vanilla_payoffs
491
+ + survival_prob * product.rebate
492
+ ) * df_T
493
+
494
+ hit_matrix = (
495
+ paths[:, 1:] >= product.barrier
496
+ if product.is_up_barrier
497
+ else paths[:, 1:] <= product.barrier
498
+ )
499
+ hit_any = hit_matrix.any(axis=1)
500
+
501
+ if product.is_knock_out:
502
+ if product.pay_at_hit:
503
+ first_idx = np.argmax(hit_matrix, axis=1)
504
+ hit_time = times[first_idx]
505
+ rebate_payoff = product.rebate * safe_exp(-r * hit_time)
506
+ rebate_payoff[~hit_any] = 0.0
507
+ return np.where(hit_any, rebate_payoff, vanilla_payoffs * df_T)
508
+ return np.where(hit_any, product.rebate, vanilla_payoffs) * df_T
509
+
510
+ return np.where(hit_any, vanilla_payoffs, product.rebate) * df_T
511
+
512
+ def _discounted_payoffs_discrete(
513
+ self,
514
+ product: BarrierOption,
515
+ paths: np.ndarray,
516
+ obs_indices: np.ndarray,
517
+ barriers: np.ndarray,
518
+ payoffs: np.ndarray,
519
+ settlement_times: np.ndarray,
520
+ aggregation: ObservationAggregation,
521
+ r: float,
522
+ T: float,
523
+ ) -> np.ndarray:
524
+ terminal_prices = paths[:, -1]
525
+ vanilla_payoffs = self._calculate_vanilla_payoff(product, terminal_prices)
526
+ df_T = safe_exp(-r * T)
527
+
528
+ obs_prices = paths[:, obs_indices + 1]
529
+ hit_matrix = obs_prices >= barriers if product.is_up_barrier else obs_prices <= barriers
530
+
531
+ hit_payoffs, hit_any = self._aggregate_discrete_hit_payoffs(
532
+ hit_matrix,
533
+ payoffs,
534
+ settlement_times,
535
+ aggregation,
536
+ r,
537
+ product.pay_at_hit,
538
+ T,
539
+ )
540
+
541
+ if product.is_knock_out:
542
+ return np.where(hit_any, hit_payoffs, vanilla_payoffs * df_T)
543
+
544
+ return np.where(hit_any, vanilla_payoffs * df_T, product.rebate * df_T)
545
+
546
+ def _price_mc_or_qmc(
547
+ self,
548
+ product: BarrierOption,
549
+ pricing_env: PricingEnvironment,
550
+ S: float,
551
+ K: float,
552
+ T: float,
553
+ r: float,
554
+ q: float,
555
+ sigma: float,
556
+ ) -> Tuple[float, float]:
557
+ if product.observation_type == ObservationType.DISCRETE:
558
+ (
559
+ times,
560
+ dt_array,
561
+ obs_indices,
562
+ barriers,
563
+ payoffs,
564
+ settlement_times,
565
+ aggregation,
566
+ ) = self._build_discrete_grid(product, pricing_env, T)
567
+ generator = self._create_path_generator(S, r, q, sigma, T, dt_array)
568
+ paths, _ = generator.generate_paths(return_aux=True)
569
+ discounted = self._discounted_payoffs_discrete(
570
+ product,
571
+ paths,
572
+ obs_indices,
573
+ barriers,
574
+ payoffs,
575
+ settlement_times,
576
+ aggregation,
577
+ r,
578
+ T,
579
+ )
580
+ else:
581
+ times, dt_array = self._build_continuous_grid(T)
582
+ generator = self._create_path_generator(S, r, q, sigma, T, dt_array)
583
+ paths, _ = generator.generate_paths(return_aux=True)
584
+ discounted = self._discounted_payoffs_continuous(
585
+ product, paths, times, r, T, sigma
586
+ )
587
+
588
+ mean_payoff = float(discounted.mean())
589
+ std_payoff = float(discounted.std(ddof=1))
590
+ std_error = std_payoff / math.sqrt(len(discounted))
591
+
592
+ return mean_payoff, std_error
593
+
594
+ def _price_rqmc(
595
+ self,
596
+ product: BarrierOption,
597
+ pricing_env: PricingEnvironment,
598
+ S: float,
599
+ K: float,
600
+ T: float,
601
+ r: float,
602
+ q: float,
603
+ sigma: float,
604
+ ) -> Tuple[float, float]:
605
+ if product.observation_type == ObservationType.EXPIRY:
606
+ dt_array = np.array([T], dtype=float)
607
+
608
+ def pricer_fn(paths, aux):
609
+ terminal_prices = paths[:, -1]
610
+ payoffs = self._expiry_payoffs(product, terminal_prices)
611
+ return payoffs * safe_exp(-r * T)
612
+
613
+ elif product.observation_type == ObservationType.DISCRETE:
614
+ (
615
+ times,
616
+ dt_array,
617
+ obs_indices,
618
+ barriers,
619
+ payoffs,
620
+ settlement_times,
621
+ aggregation,
622
+ ) = self._build_discrete_grid(product, pricing_env, T)
623
+
624
+ def pricer_fn(paths, aux):
625
+ return self._discounted_payoffs_discrete(
626
+ product,
627
+ paths,
628
+ obs_indices,
629
+ barriers,
630
+ payoffs,
631
+ settlement_times,
632
+ aggregation,
633
+ r,
634
+ T,
635
+ )
636
+ else:
637
+ times, dt_array = self._build_continuous_grid(T)
638
+
639
+ def pricer_fn(paths, aux):
640
+ return self._discounted_payoffs_continuous(
641
+ product, paths, times, r, T, sigma
642
+ )
643
+
644
+ params = self.params
645
+ max_batches = getattr(
646
+ params, "rqmc_max_batches", getattr(params, "max_batches", 32)
647
+ )
648
+ min_batches = getattr(
649
+ params, "rqmc_min_batches", getattr(params, "min_batches", 4)
650
+ )
651
+ if hasattr(params, "resolve_rqmc_target_std"):
652
+ target_std = params.resolve_rqmc_target_std(
653
+ product=product, pricing_env=pricing_env
654
+ )
655
+ else:
656
+ target_std = getattr(params, "target_std", 1e-4)
657
+ if hasattr(params, "resolve_rqmc_paths_per_batch"):
658
+ per_batch_paths = params.resolve_rqmc_paths_per_batch(
659
+ max_batches=max_batches
660
+ )
661
+ else:
662
+ per_batch_paths = params.num_paths
663
+
664
+ generator = self._create_path_generator(
665
+ S, r, q, sigma, T, dt_array, num_paths=per_batch_paths
666
+ )
667
+
668
+ result = run_rqmc(
669
+ pricer_fn=pricer_fn,
670
+ path_generator=generator,
671
+ max_batches=max_batches,
672
+ target_std=target_std,
673
+ min_batches=min_batches,
674
+ )
675
+
676
+ self._last_rqmc_result = result
677
+
678
+ return result.price, result.std_error
679
+
680
+ def _price_expiry_mc(
681
+ self,
682
+ product: BarrierOption,
683
+ S: float,
684
+ K: float,
685
+ T: float,
686
+ r: float,
687
+ q: float,
688
+ sigma: float,
689
+ ) -> Tuple[float, float]:
690
+ dt_array = np.array([T], dtype=float)
691
+ generator = self._create_path_generator(S, r, q, sigma, T, dt_array)
692
+ paths, _ = generator.generate_paths(return_aux=True)
693
+ terminal_prices = paths[:, -1]
694
+
695
+ payoffs = self._expiry_payoffs(product, terminal_prices)
696
+ discounted = payoffs * safe_exp(-r * T)
697
+
698
+ mean_payoff = float(discounted.mean())
699
+ std_payoff = float(discounted.std(ddof=1))
700
+ std_error = std_payoff / math.sqrt(len(discounted))
701
+
702
+ return mean_payoff, std_error
703
+
704
+ def get_last_std_error(self) -> Optional[float]:
705
+ """
706
+ Get the standard error from the last pricing run.
707
+
708
+ Returns:
709
+ Standard error, or None if no pricing has been performed yet
710
+ """
711
+ return getattr(self, "_last_std_error", None)
712
+
713
+ def get_last_rqmc_result(self):
714
+ """
715
+ Get the full RQMC result from the last RQMC pricing run.
716
+
717
+ Returns:
718
+ RQMCResult object, or None if last pricing was not RQMC
719
+ """
720
+ return getattr(self, "_last_rqmc_result", None)
721
+
722
+ def __repr__(self) -> str:
723
+ return (
724
+ f"BarrierOptionMCEngine(method={self.method.name}, "
725
+ f"brownian_bridge={self.use_brownian_bridge})"
726
+ )