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,738 @@
1
+ """
2
+ Monte Carlo pricing engine for Range Accrual options.
3
+
4
+ This engine prices Range Accrual options using Monte Carlo simulation with support for:
5
+ - Weighted observations (e.g., Friday = 3 for weekend carry)
6
+ - Historical (past) observations with recorded in-range outcomes
7
+ - Time-varying upper and lower barriers
8
+ - Reverse mode (pay when outside range instead of inside)
9
+ - Annualized or non-annualized accrual rates
10
+ - Three Monte Carlo methods (PSEUDO, QUASI, RANDOMIZED_QUASI)
11
+ - Vectorized NumPy operations for efficiency
12
+
13
+ For options where valuation_date > initial_date, past observations with recorded
14
+ outcomes are combined with simulated future observations for payoff calculations.
15
+ """
16
+
17
+ import math
18
+ from dataclasses import dataclass
19
+ from typing import Dict, List, Optional, Tuple, Union
20
+
21
+ import numpy as np
22
+
23
+ from quantark.asset.equity.engine.base_engine import BaseEngine
24
+ from quantark.asset.equity.param import MCParams
25
+ from quantark.asset.equity.process.bsm.qmc_path_generator import GBMPathGenerator
26
+ from quantark.asset.equity.process.bsm.qmc_rqmc_driver import run_rqmc
27
+ from quantark.asset.equity.process.bsm.qmc_sobol import (
28
+ PseudoRandomNormalGenerator,
29
+ SobolNormalGenerator,
30
+ )
31
+ from quantark.asset.equity.process.bsm.qmc_variance_reduction import VarianceReductionConfig
32
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
33
+ from quantark.asset.equity.product.option.range_accrual_option import RangeAccrualOption
34
+ from quantark.priceenv import PricingEnvironment
35
+ from quantark.util.enum.engine_enums import EngineType, MonteCarloMethod
36
+ from quantark.util.exceptions import PricingError, ValidationError
37
+ from quantark.util.numerical import is_zero
38
+
39
+
40
+ @dataclass
41
+ class RangeAccrualMCResult:
42
+ """Result container for Range Accrual option MC pricing."""
43
+
44
+ price: float
45
+ std_error: float
46
+ num_paths: int
47
+ in_range_ratio_mean: float # Mean of in-range weight ratios across paths
48
+ in_range_ratio_std: float # Std of in-range weight ratios across paths
49
+ num_past_observations: int = 0 # Number of already-observed outcomes
50
+ num_future_observations: int = 0 # Number of simulated observations
51
+ past_in_range_weights: float = 0.0 # Accumulated in-range weights from past
52
+ total_weights: float = 0.0 # Total weight sum
53
+ batches_used: Optional[int] = None
54
+
55
+
56
+ class RangeAccrualMCEngine(BaseEngine):
57
+ """
58
+ Monte Carlo pricing engine for Range Accrual options.
59
+
60
+ Supports three Monte Carlo methods:
61
+ - PSEUDO: Standard Monte Carlo with pseudorandom numbers
62
+ - QUASI: Quasi-Monte Carlo with Sobol sequences
63
+ - RANDOMIZED_QUASI: Randomized QMC with adaptive batching
64
+
65
+ Range Accrual options pay based on the proportion of observations where the
66
+ underlying stays within a defined price range. The payoff formula is:
67
+
68
+ Payoff = initial_price * contract_multiplier * accrual_rate
69
+ * (sum_in_range_weights / sum_total_weights) * year_fraction
70
+
71
+ Usage:
72
+ # Preferred: Two-level enum pattern
73
+ engine = RangeAccrualMCEngine(
74
+ params=MCParams(num_paths=100000),
75
+ method=EngineType.MONTE_CARLO(MonteCarloMethod.QUASI)
76
+ )
77
+
78
+ # Alternative: Direct method enum
79
+ engine = RangeAccrualMCEngine(
80
+ params=MCParams(num_paths=100000),
81
+ method=MonteCarloMethod.QUASI
82
+ )
83
+
84
+ # Backward compatibility: String
85
+ engine = RangeAccrualMCEngine(method="quasi")
86
+
87
+ The engine creates a GBMPathGenerator internally based on the pricing
88
+ environment and product observation schedule.
89
+ """
90
+
91
+ engine_type = EngineType.MONTE_CARLO
92
+
93
+ DEFAULT_METHOD = MonteCarloMethod.PSEUDO
94
+
95
+ def __init__(
96
+ self,
97
+ params: Optional[MCParams] = None,
98
+ method: Union[str, MonteCarloMethod, tuple, None] = None,
99
+ ):
100
+ """
101
+ Initialize Range Accrual Monte Carlo engine.
102
+
103
+ Args:
104
+ params: Monte Carlo configuration parameters (MCParams)
105
+ method: Monte Carlo method selection, one of:
106
+ - EngineType.MONTE_CARLO(MonteCarloMethod.XXX) (preferred)
107
+ - MonteCarloMethod.XXX
108
+ - String: "pseudo", "quasi", "randomized_quasi"
109
+ - None: defaults to MonteCarloMethod.PSEUDO
110
+
111
+ Raises:
112
+ ValidationError: If method is invalid or params are invalid
113
+ """
114
+ if params is None:
115
+ params = MCParams()
116
+
117
+ if not isinstance(params, MCParams):
118
+ raise ValidationError(
119
+ f"params must be MCParams instance, got {type(params).__name__}"
120
+ )
121
+
122
+ super().__init__(params)
123
+
124
+ if method is None:
125
+ self.method = self.DEFAULT_METHOD
126
+ elif isinstance(method, tuple):
127
+ engine_type, mc_method = method
128
+ if engine_type != EngineType.MONTE_CARLO:
129
+ raise ValidationError(
130
+ f"Expected EngineType.MONTE_CARLO, got {engine_type}"
131
+ )
132
+ if not isinstance(mc_method, MonteCarloMethod):
133
+ raise ValidationError(
134
+ f"Expected MonteCarloMethod, got {type(mc_method).__name__}"
135
+ )
136
+ self.method = mc_method
137
+ elif isinstance(method, MonteCarloMethod):
138
+ self.method = method
139
+ elif isinstance(method, str):
140
+ try:
141
+ self.method = MonteCarloMethod[method.upper()]
142
+ except KeyError:
143
+ valid_methods = [m.name for m in MonteCarloMethod]
144
+ raise ValidationError(
145
+ f"Invalid method string '{method}'. Valid methods: {valid_methods}"
146
+ )
147
+ else:
148
+ raise ValidationError(
149
+ f"Invalid method type {type(method).__name__}. "
150
+ "Expected MonteCarloMethod, tuple, str, or None"
151
+ )
152
+
153
+ # Result storage
154
+ self._last_result: Optional[RangeAccrualMCResult] = None
155
+
156
+ def price(
157
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
158
+ ) -> float:
159
+ """
160
+ Price a Range Accrual option using Monte Carlo simulation.
161
+
162
+ Args:
163
+ product: Range Accrual option to price
164
+ pricing_env: Pricing environment with market data
165
+
166
+ Returns:
167
+ Option price
168
+
169
+ Raises:
170
+ PricingError: If product is not a RangeAccrualOption
171
+ ValidationError: If pricing parameters are invalid
172
+ """
173
+ if not isinstance(product, RangeAccrualOption):
174
+ raise PricingError(
175
+ f"RangeAccrualMCEngine only supports RangeAccrualOption, "
176
+ f"got {type(product).__name__}"
177
+ )
178
+
179
+ # Extract market data
180
+ S = pricing_env.spot
181
+ T = product.get_maturity(pricing_env)
182
+ r = pricing_env.get_rate(T)
183
+ q = pricing_env.get_div_yield(T)
184
+ sigma = pricing_env.get_vol(product.initial_price, T)
185
+
186
+ self._validate_inputs(S, T, r, q, sigma, product)
187
+
188
+ # Handle near-expiry case
189
+ if is_zero(T):
190
+ # At expiry, use product's payoff calculation with known weights
191
+ past_in_range, past_total = product.get_past_accrual(pricing_env)
192
+ total_weights = product.get_total_weights()
193
+ return product.get_payoff(
194
+ S,
195
+ in_range_weights=past_in_range,
196
+ total_weights=total_weights,
197
+ pricing_env=pricing_env,
198
+ )
199
+
200
+ # Price using appropriate method
201
+ if self.method == MonteCarloMethod.RANDOMIZED_QUASI:
202
+ result = self._price_rqmc(product, pricing_env, S, T, r, q, sigma)
203
+ else:
204
+ result = self._price_mc_or_qmc(product, pricing_env, S, T, r, q, sigma)
205
+
206
+ self._last_result = result
207
+
208
+ # Range accrual payoffs are non-negative by construction
209
+ if result.price < 0:
210
+ raise PricingError(f"Negative price computed: {result.price}")
211
+
212
+ return result.price
213
+
214
+ def _validate_inputs(
215
+ self,
216
+ S: float,
217
+ T: float,
218
+ r: float,
219
+ q: float,
220
+ sigma: float,
221
+ product: RangeAccrualOption,
222
+ ) -> None:
223
+ """Validate pricing inputs."""
224
+ if S <= 0:
225
+ raise ValidationError(f"Spot price must be positive, got {S}")
226
+ if T < 0:
227
+ raise ValidationError(f"Time to maturity must be non-negative, got {T}")
228
+ if sigma <= 0:
229
+ raise ValidationError(f"Volatility must be positive, got {sigma}")
230
+ if q < 0:
231
+ raise ValidationError(f"Dividend yield must be non-negative, got {q}")
232
+
233
+ if product.range_config is None:
234
+ raise ValidationError("range_config is required for Range Accrual option")
235
+
236
+ def _build_observation_grid(
237
+ self,
238
+ product: RangeAccrualOption,
239
+ pricing_env: PricingEnvironment,
240
+ T: float,
241
+ ) -> Tuple[
242
+ np.ndarray, # all_times
243
+ np.ndarray, # dt_array
244
+ np.ndarray, # future_obs_indices
245
+ List[Tuple[float, bool]], # past_observations: (weight, in_range)
246
+ List[Tuple[float, float, int]], # future_observations: (weight, time, obs_idx)
247
+ float, # total_weights
248
+ ]:
249
+ """
250
+ Build time grid aligned with future observation times.
251
+
252
+ Uses resolve_observations() to separate past (already observed) from
253
+ future (to be simulated) observations.
254
+
255
+ Args:
256
+ product: Range Accrual option product
257
+ pricing_env: Pricing environment with valuation date
258
+ T: Time to maturity
259
+
260
+ Returns:
261
+ Tuple of:
262
+ - all_times: Sorted unique times including future observations and maturity
263
+ - dt_array: Time increments between times
264
+ - future_obs_indices: Indices into paths for future observations
265
+ - past_observations: List of (weight, in_range) for past observations
266
+ - future_observations: List of (weight, time, obs_idx) for future observations
267
+ - total_weights: Sum of all observation weights
268
+ """
269
+ past_obs, future_obs, total_weights = product.resolve_observations(pricing_env)
270
+
271
+ # If no future observations, we only need to compute payoff from past data
272
+ if len(future_obs) == 0:
273
+ return (
274
+ np.array([T]),
275
+ np.array([T]),
276
+ np.array([], dtype=int),
277
+ past_obs,
278
+ future_obs,
279
+ total_weights,
280
+ )
281
+
282
+ # Extract future observation times
283
+ future_times = np.array([t for _, t, _ in future_obs])
284
+
285
+ # Ensure maturity is included in the time grid
286
+ all_times_set = set(future_times.tolist()) | {T}
287
+ all_times = np.array(sorted(all_times_set))
288
+
289
+ # Build dt_array
290
+ times_with_zero = np.concatenate([[0.0], all_times])
291
+ dt_array = np.diff(times_with_zero)
292
+
293
+ # Find indices for future observation times in the path array
294
+ # Paths have shape (num_paths, num_times + 1) where index 0 is t=0
295
+ future_obs_indices = np.searchsorted(all_times, future_times) + 1
296
+
297
+ return (
298
+ all_times,
299
+ dt_array,
300
+ future_obs_indices,
301
+ past_obs,
302
+ future_obs,
303
+ total_weights,
304
+ )
305
+
306
+ def _create_path_generator(
307
+ self,
308
+ S: float,
309
+ r: float,
310
+ q: float,
311
+ sigma: float,
312
+ T: float,
313
+ dt_array: np.ndarray,
314
+ batch_id: Optional[int] = None,
315
+ num_paths: Optional[int] = None,
316
+ ) -> GBMPathGenerator:
317
+ """
318
+ Create a GBMPathGenerator configured for the observation grid.
319
+
320
+ Args:
321
+ S: Spot price
322
+ r: Risk-free rate
323
+ q: Dividend yield
324
+ sigma: Volatility
325
+ T: Time to maturity
326
+ dt_array: Non-uniform time increments
327
+ batch_id: Batch identifier for RQMC
328
+ num_paths: Override for number of paths
329
+
330
+ Returns:
331
+ Configured GBMPathGenerator
332
+ """
333
+ params = self.params
334
+ effective_num_paths = params.num_paths if num_paths is None else int(num_paths)
335
+ if effective_num_paths <= 0:
336
+ raise ValidationError(
337
+ f"num_paths must be positive, got {effective_num_paths}"
338
+ )
339
+
340
+ if self.method == MonteCarloMethod.PSEUDO:
341
+ seed = params.seed + (batch_id or 0) * 1000
342
+ random_stream = PseudoRandomNormalGenerator(seed=seed)
343
+ is_qmc = False
344
+ elif self.method in (MonteCarloMethod.QUASI, MonteCarloMethod.RANDOMIZED_QUASI):
345
+ random_stream = SobolNormalGenerator(base_seed=params.seed)
346
+ is_qmc = True
347
+ else:
348
+ raise ValidationError(f"Unknown Monte Carlo method: {self.method}")
349
+
350
+ # Use antithetic variates for non-QMC (no barriers to worry about)
351
+ vr_config = None
352
+ if params.use_antithetic and not is_qmc:
353
+ vr_config = VarianceReductionConfig(antithetic=True)
354
+
355
+ generator = GBMPathGenerator(
356
+ initial_value=S,
357
+ vol=sigma,
358
+ rrf=r,
359
+ div=q,
360
+ maturity=T,
361
+ time_steps=len(dt_array),
362
+ num_paths=effective_num_paths,
363
+ model="bsm",
364
+ random_stream=random_stream,
365
+ use_brownian_bridge=False,
366
+ vr_config=vr_config,
367
+ is_qmc=is_qmc,
368
+ dt_array=dt_array,
369
+ )
370
+
371
+ return generator
372
+
373
+ def _check_in_range(
374
+ self,
375
+ product: RangeAccrualOption,
376
+ spots: np.ndarray,
377
+ future_obs: List[Tuple[float, float, int]],
378
+ ) -> np.ndarray:
379
+ """
380
+ Check which observations are in range for all paths.
381
+
382
+ Args:
383
+ product: Range Accrual option product
384
+ spots: Spot prices at observation times, shape (num_paths, num_future_obs)
385
+ future_obs: List of (weight, time, obs_idx) for future observations
386
+
387
+ Returns:
388
+ Boolean array of shape (num_paths, num_future_obs) indicating in-range
389
+ """
390
+ num_paths, num_future = spots.shape
391
+ in_range = np.zeros((num_paths, num_future), dtype=bool)
392
+
393
+ for i, (_, _, obs_idx) in enumerate(future_obs):
394
+ # Get barriers for this observation
395
+ upper = product.range_config.get_upper_barrier(obs_idx)
396
+ lower = product.range_config.get_lower_barrier(obs_idx)
397
+
398
+ # Check if in range
399
+ spot_col = spots[:, i]
400
+ in_range_col = (spot_col >= lower) & (spot_col <= upper)
401
+
402
+ # Apply reverse mode if configured
403
+ if product.range_config.is_reverse:
404
+ in_range_col = ~in_range_col
405
+
406
+ in_range[:, i] = in_range_col
407
+
408
+ return in_range
409
+
410
+ def _compute_payoffs(
411
+ self,
412
+ product: RangeAccrualOption,
413
+ pricing_env: PricingEnvironment,
414
+ paths: np.ndarray,
415
+ future_obs_indices: np.ndarray,
416
+ past_obs: List[Tuple[float, bool]],
417
+ future_obs: List[Tuple[float, float, int]],
418
+ total_weights: float,
419
+ ) -> Tuple[np.ndarray, np.ndarray]:
420
+ """
421
+ Compute payoffs for all paths.
422
+
423
+ Args:
424
+ product: Range Accrual option product
425
+ pricing_env: Pricing environment
426
+ paths: Simulated paths, shape (num_paths, num_times + 1)
427
+ future_obs_indices: Indices into paths for future observations
428
+ past_obs: List of (weight, in_range) for past observations
429
+ future_obs: List of (weight, time, obs_idx) for future observations
430
+ total_weights: Sum of all observation weights
431
+
432
+ Returns:
433
+ Tuple of:
434
+ - payoffs: Undiscounted payoffs, shape (num_paths,)
435
+ - in_range_ratios: In-range weight ratios, shape (num_paths,)
436
+ """
437
+ num_paths = paths.shape[0]
438
+ num_future = len(future_obs)
439
+
440
+ # Calculate past contribution (same for all paths)
441
+ past_in_range_weights = sum(w for w, in_range in past_obs if in_range)
442
+
443
+ # Handle case with no future observations
444
+ if num_future == 0:
445
+ in_range_ratio = past_in_range_weights / total_weights if total_weights > 0 else 0.0
446
+ in_range_ratios = np.full(num_paths, in_range_ratio)
447
+ else:
448
+ # Extract simulated prices at future observation times
449
+ future_spots = paths[:, future_obs_indices] # (num_paths, num_future)
450
+
451
+ # Check in-range status for all future observations
452
+ in_range = self._check_in_range(product, future_spots, future_obs)
453
+
454
+ # Get future weights as array
455
+ future_weights = np.array([w for w, _, _ in future_obs])
456
+
457
+ # Calculate in-range weights for each path
458
+ # Weight is added if in_range is True
459
+ future_in_range_weights = (in_range * future_weights).sum(axis=1)
460
+
461
+ # Total in-range weights = past + future
462
+ total_in_range_weights = past_in_range_weights + future_in_range_weights
463
+
464
+ # Compute in-range ratio
465
+ in_range_ratios = total_in_range_weights / total_weights if total_weights > 0 else np.zeros(num_paths)
466
+
467
+ # Compute payoffs using product's formula
468
+ # Payoff = initial_price * contract_multiplier * accrual_rate * ratio * year_fraction
469
+ year_fraction = product.get_year_fraction(pricing_env)
470
+ accrual_rate = product.range_config.accrual_rate
471
+
472
+ payoffs = (
473
+ product.initial_price
474
+ * product.contract_multiplier
475
+ * accrual_rate
476
+ * in_range_ratios
477
+ * year_fraction
478
+ )
479
+
480
+ return payoffs, in_range_ratios
481
+
482
+ def _price_mc_or_qmc(
483
+ self,
484
+ product: RangeAccrualOption,
485
+ pricing_env: PricingEnvironment,
486
+ S: float,
487
+ T: float,
488
+ r: float,
489
+ q: float,
490
+ sigma: float,
491
+ ) -> RangeAccrualMCResult:
492
+ """
493
+ Price using normal MC or QMC (non-randomized).
494
+ """
495
+ # Build observation grid
496
+ (
497
+ all_times,
498
+ dt_array,
499
+ future_obs_indices,
500
+ past_obs,
501
+ future_obs,
502
+ total_weights,
503
+ ) = self._build_observation_grid(product, pricing_env, T)
504
+
505
+ num_past = len(past_obs)
506
+ num_future = len(future_obs)
507
+ past_in_range_weights = sum(w for w, in_range in past_obs if in_range)
508
+
509
+ # Handle special case: all observations are in the past
510
+ if num_future == 0:
511
+ # Compute payoff from past observations only
512
+ in_range_ratio = past_in_range_weights / total_weights if total_weights > 0 else 0.0
513
+
514
+ year_fraction = product.get_year_fraction(pricing_env)
515
+ accrual_rate = product.range_config.accrual_rate
516
+ payoff = (
517
+ product.initial_price
518
+ * product.contract_multiplier
519
+ * accrual_rate
520
+ * in_range_ratio
521
+ * year_fraction
522
+ )
523
+
524
+ # Discount payoff
525
+ discount_factor = math.exp(-r * T)
526
+ price = discount_factor * payoff
527
+
528
+ return RangeAccrualMCResult(
529
+ price=price,
530
+ std_error=0.0, # No simulation uncertainty
531
+ num_paths=0,
532
+ in_range_ratio_mean=in_range_ratio,
533
+ in_range_ratio_std=0.0,
534
+ num_past_observations=num_past,
535
+ num_future_observations=0,
536
+ past_in_range_weights=past_in_range_weights,
537
+ total_weights=total_weights,
538
+ )
539
+
540
+ # Create path generator for future observations
541
+ maturity_for_sim = all_times[-1]
542
+ generator = self._create_path_generator(
543
+ S, r, q, sigma, maturity_for_sim, dt_array
544
+ )
545
+
546
+ # Generate paths
547
+ paths, _ = generator.generate_paths(return_aux=False)
548
+
549
+ # Compute payoffs
550
+ payoffs, in_range_ratios = self._compute_payoffs(
551
+ product,
552
+ pricing_env,
553
+ paths,
554
+ future_obs_indices,
555
+ past_obs,
556
+ future_obs,
557
+ total_weights,
558
+ )
559
+
560
+ # Discount payoffs
561
+ discount_factor = math.exp(-r * T)
562
+ discounted_payoffs = discount_factor * payoffs
563
+
564
+ # Compute price and standard error
565
+ price = float(discounted_payoffs.mean())
566
+ std_payoff = float(discounted_payoffs.std(ddof=1))
567
+ std_error = std_payoff / math.sqrt(len(payoffs))
568
+
569
+ return RangeAccrualMCResult(
570
+ price=price,
571
+ std_error=std_error,
572
+ num_paths=len(paths),
573
+ in_range_ratio_mean=float(in_range_ratios.mean()),
574
+ in_range_ratio_std=float(in_range_ratios.std(ddof=1)),
575
+ num_past_observations=num_past,
576
+ num_future_observations=num_future,
577
+ past_in_range_weights=past_in_range_weights,
578
+ total_weights=total_weights,
579
+ )
580
+
581
+ def _price_rqmc(
582
+ self,
583
+ product: RangeAccrualOption,
584
+ pricing_env: PricingEnvironment,
585
+ S: float,
586
+ T: float,
587
+ r: float,
588
+ q: float,
589
+ sigma: float,
590
+ ) -> RangeAccrualMCResult:
591
+ """
592
+ Price using Randomized QMC with adaptive batching.
593
+ """
594
+ # Build observation grid
595
+ (
596
+ all_times,
597
+ dt_array,
598
+ future_obs_indices,
599
+ past_obs,
600
+ future_obs,
601
+ total_weights,
602
+ ) = self._build_observation_grid(product, pricing_env, T)
603
+
604
+ num_past = len(past_obs)
605
+ num_future = len(future_obs)
606
+ past_in_range_weights = sum(w for w, in_range in past_obs if in_range)
607
+
608
+ # Handle special case: all observations are in the past
609
+ if num_future == 0:
610
+ in_range_ratio = past_in_range_weights / total_weights if total_weights > 0 else 0.0
611
+
612
+ year_fraction = product.get_year_fraction(pricing_env)
613
+ accrual_rate = product.range_config.accrual_rate
614
+ payoff = (
615
+ product.initial_price
616
+ * product.contract_multiplier
617
+ * accrual_rate
618
+ * in_range_ratio
619
+ * year_fraction
620
+ )
621
+
622
+ discount_factor = math.exp(-r * T)
623
+ price = discount_factor * payoff
624
+
625
+ return RangeAccrualMCResult(
626
+ price=price,
627
+ std_error=0.0,
628
+ num_paths=0,
629
+ in_range_ratio_mean=in_range_ratio,
630
+ in_range_ratio_std=0.0,
631
+ num_past_observations=num_past,
632
+ num_future_observations=0,
633
+ past_in_range_weights=past_in_range_weights,
634
+ total_weights=total_weights,
635
+ )
636
+
637
+ params = self.params
638
+ max_batches = getattr(
639
+ params, "rqmc_max_batches", getattr(params, "max_batches", 32)
640
+ )
641
+ min_batches = getattr(
642
+ params, "rqmc_min_batches", getattr(params, "min_batches", 4)
643
+ )
644
+ if hasattr(params, "resolve_rqmc_target_std"):
645
+ target_std = params.resolve_rqmc_target_std(
646
+ product=product, pricing_env=pricing_env
647
+ )
648
+ else:
649
+ target_std = getattr(params, "target_std", 1e-4)
650
+ if hasattr(params, "resolve_rqmc_paths_per_batch"):
651
+ per_batch_paths = params.resolve_rqmc_paths_per_batch(
652
+ max_batches=max_batches
653
+ )
654
+ else:
655
+ per_batch_paths = params.num_paths
656
+
657
+ # Create path generator
658
+ maturity_for_sim = all_times[-1]
659
+ generator = self._create_path_generator(
660
+ S,
661
+ r,
662
+ q,
663
+ sigma,
664
+ maturity_for_sim,
665
+ dt_array,
666
+ num_paths=per_batch_paths,
667
+ )
668
+
669
+ discount_factor = math.exp(-r * T)
670
+
671
+ def pricer_fn(paths, aux):
672
+ """Pricer function for RQMC driver."""
673
+ payoffs, _ = self._compute_payoffs(
674
+ product,
675
+ pricing_env,
676
+ paths,
677
+ future_obs_indices,
678
+ past_obs,
679
+ future_obs,
680
+ total_weights,
681
+ )
682
+ return discount_factor * payoffs
683
+
684
+ result = run_rqmc(
685
+ pricer_fn=pricer_fn,
686
+ path_generator=generator,
687
+ max_batches=max_batches,
688
+ target_std=target_std,
689
+ min_batches=min_batches,
690
+ )
691
+
692
+ # Run one more batch to get ratio statistics
693
+ paths, _ = generator.generate_paths(return_aux=False, batch_id=0)
694
+ _, in_range_ratios = self._compute_payoffs(
695
+ product,
696
+ pricing_env,
697
+ paths,
698
+ future_obs_indices,
699
+ past_obs,
700
+ future_obs,
701
+ total_weights,
702
+ )
703
+
704
+ return RangeAccrualMCResult(
705
+ price=result.price,
706
+ std_error=result.std_error,
707
+ num_paths=result.total_paths,
708
+ in_range_ratio_mean=float(in_range_ratios.mean()),
709
+ in_range_ratio_std=float(in_range_ratios.std(ddof=1)),
710
+ num_past_observations=num_past,
711
+ num_future_observations=num_future,
712
+ past_in_range_weights=past_in_range_weights,
713
+ total_weights=total_weights,
714
+ batches_used=result.batches_used,
715
+ )
716
+
717
+ def get_last_result(self) -> Optional[RangeAccrualMCResult]:
718
+ """
719
+ Get the full result from the last pricing run.
720
+
721
+ Returns:
722
+ RangeAccrualMCResult object, or None if no pricing has been performed
723
+ """
724
+ return self._last_result
725
+
726
+ def get_last_std_error(self) -> Optional[float]:
727
+ """
728
+ Get the standard error from the last pricing run.
729
+
730
+ Returns:
731
+ Standard error, or None if no pricing has been performed
732
+ """
733
+ if self._last_result is None:
734
+ return None
735
+ return self._last_result.std_error
736
+
737
+ def __repr__(self):
738
+ return f"RangeAccrualMCEngine(method={self.method.name})"