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,694 @@
1
+ """
2
+ Created on Mon Nov 17 2025
3
+
4
+ @author: yaofuxin
5
+ @description: MC/QMC GBM/BSM path generators built on top of generic random
6
+ streams, Brownian bridge utilities and variance reduction
7
+ helpers. The generators are NumPy-first and do not depend on
8
+ Dask, making them suitable for use in production pricing
9
+ engines.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from dataclasses import dataclass
15
+ from typing import Dict, Optional, Tuple
16
+
17
+ import numpy as np
18
+
19
+ from .qmc_brownian_bridge import apply_brownian_bridge
20
+ from .qmc_sobol import PseudoRandomNormalGenerator, RandomStream, SobolNormalGenerator
21
+ from .qmc_variance_reduction import (
22
+ VarianceReductionConfig,
23
+ apply_variance_reduction_to_normals,
24
+ )
25
+
26
+
27
+ def _build_time_grid(
28
+ maturity: float,
29
+ time_steps: int,
30
+ dt_array: Optional[np.ndarray] = None,
31
+ ) -> Tuple[np.ndarray, np.ndarray]:
32
+ """
33
+ Build a time grid and associated dt array.
34
+
35
+ Parameters
36
+ ----------
37
+ maturity : float
38
+ Option maturity.
39
+ time_steps : int
40
+ Number of time steps.
41
+ dt_array : np.ndarray, optional
42
+ Custom time step sizes. If provided, it must have length equal to
43
+ time_steps and sum to maturity.
44
+
45
+ Returns
46
+ -------
47
+ times : np.ndarray
48
+ Time grid of shape (time_steps,) with strictly increasing values.
49
+ dt : np.ndarray
50
+ Time increments of shape (time_steps,).
51
+ """
52
+ if time_steps <= 0:
53
+ raise ValueError("time_steps must be positive")
54
+ if maturity <= 0.0:
55
+ raise ValueError("maturity must be positive")
56
+
57
+ if dt_array is not None:
58
+ dt = np.asarray(dt_array, dtype=float)
59
+ if dt.ndim != 1 or dt.shape[0] != time_steps:
60
+ raise ValueError(
61
+ f"dt_array must be one-dimensional with length {time_steps}, "
62
+ f"got shape {dt.shape}."
63
+ )
64
+ if np.any(dt <= 0.0):
65
+ raise ValueError("dt_array must have strictly positive entries")
66
+ if not np.isclose(dt.sum(), maturity):
67
+ raise ValueError(
68
+ f"Sum of dt_array ({dt.sum()}) must equal maturity ({maturity})."
69
+ )
70
+ times = np.cumsum(dt)
71
+ else:
72
+ dt_value = maturity / float(time_steps)
73
+ dt = np.full(time_steps, dt_value, dtype=float)
74
+ times = np.cumsum(dt)
75
+
76
+ return times, dt
77
+
78
+
79
+ @dataclass
80
+ class GBMPathGenerator:
81
+ """
82
+ General-purpose GBM/BSM path generator driven by a RandomStream.
83
+
84
+ This class supports both classical MC (with PseudoRandomNormalGenerator)
85
+ and QMC (with SobolNormalGenerator), with optional Brownian bridge and
86
+ variance reduction techniques.
87
+ """
88
+
89
+ initial_value: float = 100.0
90
+ vol: float = 0.2
91
+ rrf: float = 0.0
92
+ div: float = 0.0
93
+ maturity: float = 1.0
94
+ time_steps: int = 244
95
+ num_paths: int = 10000
96
+ model: str = "bsm"
97
+ random_stream: Optional[RandomStream] = None
98
+ use_brownian_bridge: bool = False
99
+ vr_config: Optional[VarianceReductionConfig] = None
100
+ is_qmc: bool = False
101
+ dt_array: Optional[np.ndarray] = None
102
+
103
+ def __post_init__(self) -> None:
104
+ if self.initial_value <= 0.0:
105
+ raise ValueError("initial_value must be positive")
106
+ if self.vol < 0.0:
107
+ raise ValueError("vol must be non-negative")
108
+ if self.num_paths <= 0:
109
+ raise ValueError("num_paths must be positive")
110
+
111
+ valid_models = ["bsm", "black", "gbm"]
112
+ if self.model not in valid_models:
113
+ raise ValueError(
114
+ f"Invalid model '{self.model}', choose from {valid_models}"
115
+ )
116
+
117
+ # Default to pseudorandom generator if none is provided
118
+ if self.random_stream is None:
119
+ self.random_stream = PseudoRandomNormalGenerator()
120
+ self.is_qmc = False
121
+
122
+ # Precompute drift based on model type
123
+ self._set_drift()
124
+
125
+ # Build time grid
126
+ self.times, self.dt_vector = _build_time_grid(
127
+ maturity=self.maturity,
128
+ time_steps=self.time_steps,
129
+ dt_array=self.dt_array,
130
+ )
131
+
132
+ def _set_drift(self) -> None:
133
+ if self.model == "black":
134
+ self.drift = self.rrf
135
+ elif self.model == "bsm":
136
+ self.drift = self.rrf - self.div
137
+ elif self.model == "gbm":
138
+ self.drift = 0.0
139
+ else:
140
+ raise ValueError(f"Invalid model '{self.model}'")
141
+
142
+ def _generate_base_normals(self, batch_id: Optional[int]) -> np.ndarray:
143
+ """
144
+ Generate base standard normals before variance reduction is applied.
145
+
146
+ For antithetic variates in MC mode, we generate ceil(num_paths / 2)
147
+ base paths so that the expanded matrix after pairing reaches at least
148
+ num_paths paths.
149
+ """
150
+ dim = self.time_steps
151
+
152
+ if self.vr_config is not None and self.vr_config.antithetic and not self.is_qmc:
153
+ n_base = (self.num_paths + 1) // 2
154
+ else:
155
+ n_base = self.num_paths
156
+
157
+ if self.random_stream is None:
158
+ raise RuntimeError("Random stream is not configured")
159
+
160
+ return self.random_stream.normal(n_paths=n_base, dim=dim, batch_id=batch_id)
161
+
162
+ def _build_brownian_increments(self, z: np.ndarray) -> np.ndarray:
163
+ """
164
+ Convert standard normals to Brownian increments dW_t.
165
+
166
+ If use_brownian_bridge is True, a Brownian bridge is used to map the
167
+ normals into Brownian motion and then to increments. Otherwise, dW_t
168
+ = sqrt(dt_t) * Z_t is used directly.
169
+ """
170
+ if self.use_brownian_bridge:
171
+ return apply_brownian_bridge(z, self.times)
172
+
173
+ # Direct construction without Brownian bridge
174
+ sqrt_dt = np.sqrt(self.dt_vector).reshape(1, -1)
175
+ return sqrt_dt * z
176
+
177
+ def generate_paths(
178
+ self,
179
+ seed: Optional[int] = None,
180
+ batch_id: Optional[int] = None,
181
+ return_aux: bool = False,
182
+ ) -> Tuple[np.ndarray, Optional[Dict[str, np.ndarray]]]:
183
+ """
184
+ Generate GBM/BSM paths.
185
+
186
+ Parameters
187
+ ----------
188
+ seed : int, optional
189
+ Seed used for pseudorandom streams. If the underlying random
190
+ stream supports seeding via constructor only (e.g., Sobol),
191
+ this argument is ignored.
192
+ batch_id : int, optional
193
+ Batch identifier for RQMC usage. Different batch_ids produce
194
+ independent randomized Sobol batches.
195
+ return_aux : bool
196
+ If True, also return auxiliary data such as importance sampling
197
+ weights and control variates.
198
+
199
+ Returns
200
+ -------
201
+ paths : np.ndarray
202
+ Simulated paths of shape (num_paths, time_steps + 1).
203
+ aux : dict or None
204
+ Dictionary containing auxiliary arrays if return_aux is True.
205
+ Keys:
206
+ - "weights": importance sampling weights (if enabled)
207
+ - "control_variate": GBM control variate (if enabled)
208
+ """
209
+ # Optional reseeding for pseudorandom generator
210
+ if seed is not None and isinstance(
211
+ self.random_stream, PseudoRandomNormalGenerator
212
+ ):
213
+ self.random_stream = PseudoRandomNormalGenerator(seed=seed)
214
+
215
+ base_normals = self._generate_base_normals(batch_id=batch_id)
216
+
217
+ # Variance reduction applied at the level of standard normals
218
+ z_processed, weights, control_variate = apply_variance_reduction_to_normals(
219
+ n_paths=self.num_paths,
220
+ dim=self.time_steps,
221
+ base_normals=base_normals,
222
+ vr_config=self.vr_config,
223
+ is_qmc=self.is_qmc,
224
+ )
225
+
226
+ # Convert to Brownian increments
227
+ dW = self._build_brownian_increments(z_processed)
228
+
229
+ # Build GBM/BSM paths
230
+ paths = np.zeros((self.num_paths, self.time_steps + 1), dtype=float)
231
+ paths[:, 0] = self.initial_value
232
+
233
+ drift_term = (self.drift - 0.5 * self.vol * self.vol) * self.dt_vector
234
+ drift_term = drift_term.reshape(1, -1)
235
+
236
+ diffusion_term = self.vol * dW
237
+ exp_term = np.exp(drift_term + diffusion_term)
238
+
239
+ paths[:, 1:] = self.initial_value * np.cumprod(exp_term, axis=1)
240
+
241
+ aux: Optional[Dict[str, np.ndarray]] = None
242
+ if return_aux:
243
+ aux = {}
244
+ # Include batch identifier for downstream engines (e.g., barrier corrections)
245
+ aux["batch_id"] = np.array(batch_id if batch_id is not None else 0)
246
+ if weights is not None:
247
+ aux["weights"] = weights
248
+ if control_variate is not None:
249
+ aux["control_variate"] = control_variate
250
+
251
+ return paths, aux
252
+
253
+ def generate_terminal_values_qmc(
254
+ self,
255
+ batch_id: Optional[int] = None,
256
+ seed: Optional[int] = None,
257
+ dim: int = 1,
258
+ ) -> np.ndarray:
259
+ """
260
+ Generate terminal asset values S_T using low-dimensional QMC.
261
+
262
+ This method is intended for path-independent payoffs, where using a
263
+ low-dimensional Sobol sequence (e.g., 1D) provides excellent
264
+ effectiveness and avoids the high-dimensional degradation that occurs
265
+ when full paths are simulated.
266
+
267
+ Parameters
268
+ ----------
269
+ batch_id : int, optional
270
+ Batch identifier for RQMC usage.
271
+ seed : int, optional
272
+ Seed for pseudorandom streams; ignored for Sobol-based streams.
273
+ dim : int
274
+ Sobol dimension to use (typically 1 or 2). Only the first column
275
+ is used to construct the terminal value.
276
+
277
+ Returns
278
+ -------
279
+ np.ndarray
280
+ Array of terminal values S_T of shape (num_paths,).
281
+ """
282
+ if seed is not None and isinstance(
283
+ self.random_stream, PseudoRandomNormalGenerator
284
+ ):
285
+ self.random_stream = PseudoRandomNormalGenerator(seed=seed)
286
+
287
+ # Use low-dimensional normals for the terminal value
288
+ z = self.random_stream.normal(self.num_paths, dim, batch_id=batch_id)
289
+ z_1d = z[:, 0]
290
+
291
+ T = self.maturity
292
+ drift_term = (self.drift - 0.5 * self.vol * self.vol) * T
293
+ diffusion_term = self.vol * np.sqrt(T) * z_1d
294
+
295
+ return self.initial_value * np.exp(drift_term + diffusion_term)
296
+
297
+
298
+ @dataclass
299
+ class GBMPathGeneratorQMC(GBMPathGenerator):
300
+ """
301
+ Convenience subclass for QMC-based GBM path generation.
302
+
303
+ This simply defaults to using SobolNormalGenerator and sets is_qmc=True.
304
+ """
305
+
306
+ def __post_init__(self) -> None:
307
+ if self.random_stream is None:
308
+ self.random_stream = SobolNormalGenerator()
309
+ self.is_qmc = True
310
+ super().__post_init__()
311
+
312
+
313
+ @dataclass
314
+ class MultiAssetGBMPathGenerator:
315
+ """
316
+ Multi-asset GBM/BSM path generator with correlation support.
317
+
318
+ This class generates correlated paths for multiple assets using Cholesky
319
+ decomposition to transform independent standard normals into correlated
320
+ normals. It supports both classical MC (with PseudoRandomNormalGenerator)
321
+ and QMC (with SobolNormalGenerator), with optional Brownian bridge per
322
+ asset for improved QMC effectiveness.
323
+
324
+ The dimension layout for QMC is: dimensions 0 to T-1 for asset 0,
325
+ dimensions T to 2T-1 for asset 1, etc. This grouping allows per-asset
326
+ Brownian bridge construction.
327
+
328
+ Parameters
329
+ ----------
330
+ initial_values : np.ndarray
331
+ Initial spot prices for each asset, shape (n_assets,).
332
+ vols : np.ndarray
333
+ Volatilities for each asset, shape (n_assets,).
334
+ rrfs : np.ndarray
335
+ Risk-free rates for each asset, shape (n_assets,).
336
+ divs : np.ndarray
337
+ Dividend yields for each asset, shape (n_assets,).
338
+ correlation_matrix : np.ndarray
339
+ Correlation matrix of shape (n_assets, n_assets). Must be symmetric
340
+ and positive semi-definite.
341
+ maturity : float
342
+ Time to maturity in years.
343
+ time_steps : int
344
+ Number of time steps for path discretization.
345
+ num_paths : int
346
+ Number of Monte Carlo paths to generate.
347
+ model : str
348
+ Model type: "bsm" (Black-Scholes-Merton), "black", or "gbm".
349
+ random_stream : RandomStream, optional
350
+ Random stream for generating normals. Defaults to pseudorandom.
351
+ use_brownian_bridge : bool
352
+ Whether to use Brownian bridge construction for each asset.
353
+ vr_config : VarianceReductionConfig, optional
354
+ Variance reduction configuration (antithetic not supported for QMC).
355
+ is_qmc : bool
356
+ Whether this is a QMC-based generator.
357
+ dt_array : np.ndarray, optional
358
+ Custom time step sizes.
359
+ """
360
+
361
+ initial_values: np.ndarray = None
362
+ vols: np.ndarray = None
363
+ rrfs: np.ndarray = None
364
+ divs: np.ndarray = None
365
+ correlation_matrix: np.ndarray = None
366
+ maturity: float = 1.0
367
+ time_steps: int = 244
368
+ num_paths: int = 10000
369
+ model: str = "bsm"
370
+ random_stream: Optional[RandomStream] = None
371
+ use_brownian_bridge: bool = False
372
+ vr_config: Optional[VarianceReductionConfig] = None
373
+ is_qmc: bool = False
374
+ dt_array: Optional[np.ndarray] = None
375
+
376
+ def __post_init__(self) -> None:
377
+ # Convert inputs to numpy arrays
378
+ self.initial_values = np.asarray(self.initial_values, dtype=float)
379
+ self.vols = np.asarray(self.vols, dtype=float)
380
+ self.rrfs = np.asarray(self.rrfs, dtype=float)
381
+ self.divs = np.asarray(self.divs, dtype=float)
382
+ self.correlation_matrix = np.asarray(self.correlation_matrix, dtype=float)
383
+
384
+ # Determine number of assets
385
+ self.n_assets = self.initial_values.shape[0]
386
+
387
+ # Validate shapes
388
+ if self.vols.shape != (self.n_assets,):
389
+ raise ValueError(
390
+ f"vols must have shape ({self.n_assets},), got {self.vols.shape}"
391
+ )
392
+ if self.rrfs.shape != (self.n_assets,):
393
+ raise ValueError(
394
+ f"rrfs must have shape ({self.n_assets},), got {self.rrfs.shape}"
395
+ )
396
+ if self.divs.shape != (self.n_assets,):
397
+ raise ValueError(
398
+ f"divs must have shape ({self.n_assets},), got {self.divs.shape}"
399
+ )
400
+ if self.correlation_matrix.shape != (self.n_assets, self.n_assets):
401
+ raise ValueError(
402
+ f"correlation_matrix must have shape ({self.n_assets}, {self.n_assets}), "
403
+ f"got {self.correlation_matrix.shape}"
404
+ )
405
+
406
+ # Validate values
407
+ if np.any(self.initial_values <= 0.0):
408
+ raise ValueError("All initial_values must be positive")
409
+ if np.any(self.vols < 0.0):
410
+ raise ValueError("All vols must be non-negative")
411
+ if self.num_paths <= 0:
412
+ raise ValueError("num_paths must be positive")
413
+
414
+ # Validate correlation matrix
415
+ if not np.allclose(self.correlation_matrix, self.correlation_matrix.T):
416
+ raise ValueError("correlation_matrix must be symmetric")
417
+ if not np.allclose(np.diag(self.correlation_matrix), 1.0):
418
+ raise ValueError("correlation_matrix diagonal must be all ones")
419
+
420
+ valid_models = ["bsm", "black", "gbm"]
421
+ if self.model not in valid_models:
422
+ raise ValueError(
423
+ f"Invalid model '{self.model}', choose from {valid_models}"
424
+ )
425
+
426
+ # Default to pseudorandom generator if none is provided
427
+ if self.random_stream is None:
428
+ self.random_stream = PseudoRandomNormalGenerator()
429
+ self.is_qmc = False
430
+
431
+ # Compute Cholesky decomposition for correlation
432
+ try:
433
+ self.cholesky_L = np.linalg.cholesky(self.correlation_matrix)
434
+ except np.linalg.LinAlgError:
435
+ raise ValueError(
436
+ "correlation_matrix must be positive semi-definite. "
437
+ "Cholesky decomposition failed."
438
+ )
439
+
440
+ # Precompute drifts based on model type
441
+ self._set_drifts()
442
+
443
+ # Build time grid
444
+ self.times, self.dt_vector = _build_time_grid(
445
+ maturity=self.maturity,
446
+ time_steps=self.time_steps,
447
+ dt_array=self.dt_array,
448
+ )
449
+
450
+ def _set_drifts(self) -> None:
451
+ """Set drift for each asset based on model type."""
452
+ if self.model == "black":
453
+ self.drifts = self.rrfs.copy()
454
+ elif self.model == "bsm":
455
+ self.drifts = self.rrfs - self.divs
456
+ elif self.model == "gbm":
457
+ self.drifts = np.zeros(self.n_assets, dtype=float)
458
+ else:
459
+ raise ValueError(f"Invalid model '{self.model}'")
460
+
461
+ def _generate_base_normals(self, batch_id: Optional[int]) -> np.ndarray:
462
+ """
463
+ Generate base independent standard normals for all assets and time steps.
464
+
465
+ The dimension is n_assets * time_steps, laid out so that dimensions
466
+ 0 to time_steps-1 correspond to asset 0, etc.
467
+
468
+ Returns
469
+ -------
470
+ np.ndarray
471
+ Array of shape (n_paths, n_assets * time_steps).
472
+ """
473
+ total_dim = self.n_assets * self.time_steps
474
+
475
+ if self.vr_config is not None and self.vr_config.antithetic and not self.is_qmc:
476
+ n_base = (self.num_paths + 1) // 2
477
+ else:
478
+ n_base = self.num_paths
479
+
480
+ if self.random_stream is None:
481
+ raise RuntimeError("Random stream is not configured")
482
+
483
+ return self.random_stream.normal(
484
+ n_paths=n_base, dim=total_dim, batch_id=batch_id
485
+ )
486
+
487
+ def _apply_correlation(self, z_independent: np.ndarray) -> np.ndarray:
488
+ """
489
+ Apply Cholesky correlation to independent normals.
490
+
491
+ Parameters
492
+ ----------
493
+ z_independent : np.ndarray
494
+ Independent standard normals of shape (n_paths, n_assets, time_steps).
495
+
496
+ Returns
497
+ -------
498
+ np.ndarray
499
+ Correlated standard normals of shape (n_paths, n_assets, time_steps).
500
+ """
501
+ n_paths, n_assets, time_steps = z_independent.shape
502
+
503
+ # Apply Cholesky: for each time step, correlate across assets
504
+ # z_correlated[i, :, t] = L @ z_independent[i, :, t]
505
+ # Using einsum for efficient batch matrix multiplication
506
+ z_correlated = np.einsum("ij,kjt->kit", self.cholesky_L, z_independent)
507
+
508
+ return z_correlated
509
+
510
+ def _build_brownian_increments_per_asset(
511
+ self, z: np.ndarray, asset_idx: int
512
+ ) -> np.ndarray:
513
+ """
514
+ Convert standard normals to Brownian increments for a single asset.
515
+
516
+ Parameters
517
+ ----------
518
+ z : np.ndarray
519
+ Standard normals for one asset, shape (n_paths, time_steps).
520
+ asset_idx : int
521
+ Asset index (for debugging/logging purposes).
522
+
523
+ Returns
524
+ -------
525
+ np.ndarray
526
+ Brownian increments dW of shape (n_paths, time_steps).
527
+ """
528
+ if self.use_brownian_bridge:
529
+ return apply_brownian_bridge(z, self.times)
530
+
531
+ # Direct construction without Brownian bridge
532
+ sqrt_dt = np.sqrt(self.dt_vector).reshape(1, -1)
533
+ return sqrt_dt * z
534
+
535
+ def generate_paths(
536
+ self,
537
+ seed: Optional[int] = None,
538
+ batch_id: Optional[int] = None,
539
+ return_aux: bool = False,
540
+ ) -> Tuple[np.ndarray, Optional[Dict[str, np.ndarray]]]:
541
+ """
542
+ Generate correlated GBM/BSM paths for all assets.
543
+
544
+ Parameters
545
+ ----------
546
+ seed : int, optional
547
+ Seed used for pseudorandom streams. Ignored for Sobol-based streams.
548
+ batch_id : int, optional
549
+ Batch identifier for RQMC usage. Different batch_ids produce
550
+ independent randomized Sobol batches.
551
+ return_aux : bool
552
+ If True, also return auxiliary data such as importance sampling
553
+ weights.
554
+
555
+ Returns
556
+ -------
557
+ paths : np.ndarray
558
+ Simulated paths of shape (n_assets, num_paths, time_steps + 1).
559
+ aux : dict or None
560
+ Dictionary containing auxiliary arrays if return_aux is True.
561
+ Keys:
562
+ - "weights": importance sampling weights (if enabled)
563
+ """
564
+ # Optional reseeding for pseudorandom generator
565
+ if seed is not None and isinstance(
566
+ self.random_stream, PseudoRandomNormalGenerator
567
+ ):
568
+ self.random_stream = PseudoRandomNormalGenerator(seed=seed)
569
+
570
+ # Generate base normals: shape (n_paths or n_base, n_assets * time_steps)
571
+ base_normals = self._generate_base_normals(batch_id=batch_id)
572
+
573
+ # Apply variance reduction if configured
574
+ total_dim = self.n_assets * self.time_steps
575
+ z_processed, weights, _ = apply_variance_reduction_to_normals(
576
+ n_paths=self.num_paths,
577
+ dim=total_dim,
578
+ base_normals=base_normals,
579
+ vr_config=self.vr_config,
580
+ is_qmc=self.is_qmc,
581
+ )
582
+
583
+ # Reshape to (n_paths, n_assets, time_steps)
584
+ # Layout: first time_steps columns are asset 0, next are asset 1, etc.
585
+ z_reshaped = z_processed.reshape(self.num_paths, self.n_assets, self.time_steps)
586
+
587
+ # Build Brownian increments per asset independently (with optional Brownian bridge)
588
+ # This preserves QMC effectiveness for each asset's marginal distribution
589
+ dW_independent = np.zeros((self.n_assets, self.num_paths, self.time_steps))
590
+ for i in range(self.n_assets):
591
+ dW_independent[i] = self._build_brownian_increments_per_asset(
592
+ z_reshaped[:, i, :], asset_idx=i
593
+ )
594
+
595
+ # Apply correlation to the Brownian increments
596
+ # Reshape for correlation: (n_paths, n_assets, time_steps)
597
+ dW_independent_transposed = np.transpose(dW_independent, (1, 0, 2))
598
+ dW_correlated = self._apply_correlation(dW_independent_transposed)
599
+ # Transpose back to (n_assets, n_paths, time_steps)
600
+ dW = np.transpose(dW_correlated, (1, 0, 2))
601
+
602
+ # Build GBM/BSM paths for each asset
603
+ paths = np.zeros(
604
+ (self.n_assets, self.num_paths, self.time_steps + 1), dtype=float
605
+ )
606
+
607
+ for i in range(self.n_assets):
608
+ paths[i, :, 0] = self.initial_values[i]
609
+
610
+ drift_term = (self.drifts[i] - 0.5 * self.vols[i] ** 2) * self.dt_vector
611
+ drift_term = drift_term.reshape(1, -1)
612
+
613
+ diffusion_term = self.vols[i] * dW[i]
614
+ exp_term = np.exp(drift_term + diffusion_term)
615
+
616
+ paths[i, :, 1:] = self.initial_values[i] * np.cumprod(exp_term, axis=1)
617
+
618
+ aux: Optional[Dict[str, np.ndarray]] = None
619
+ if return_aux:
620
+ aux = {}
621
+ if weights is not None:
622
+ aux["weights"] = weights
623
+
624
+ return paths, aux
625
+
626
+ def generate_terminal_values(
627
+ self,
628
+ batch_id: Optional[int] = None,
629
+ seed: Optional[int] = None,
630
+ ) -> np.ndarray:
631
+ """
632
+ Generate terminal asset values S_T for all assets using low-dimensional QMC.
633
+
634
+ This method is intended for path-independent payoffs on multiple assets,
635
+ where using a low-dimensional Sobol sequence (n_assets dimensions) provides
636
+ excellent effectiveness.
637
+
638
+ Parameters
639
+ ----------
640
+ batch_id : int, optional
641
+ Batch identifier for RQMC usage.
642
+ seed : int, optional
643
+ Seed for pseudorandom streams; ignored for Sobol-based streams.
644
+
645
+ Returns
646
+ -------
647
+ np.ndarray
648
+ Array of terminal values of shape (n_assets, num_paths).
649
+ """
650
+ if seed is not None and isinstance(
651
+ self.random_stream, PseudoRandomNormalGenerator
652
+ ):
653
+ self.random_stream = PseudoRandomNormalGenerator(seed=seed)
654
+
655
+ # Generate low-dimensional normals: one dimension per asset
656
+ z = self.random_stream.normal(self.num_paths, self.n_assets, batch_id=batch_id)
657
+
658
+ # Apply correlation
659
+ z_correlated = (self.cholesky_L @ z.T).T # shape (num_paths, n_assets)
660
+
661
+ T = self.maturity
662
+ terminal_values = np.zeros((self.n_assets, self.num_paths), dtype=float)
663
+
664
+ for i in range(self.n_assets):
665
+ drift_term = (self.drifts[i] - 0.5 * self.vols[i] ** 2) * T
666
+ diffusion_term = self.vols[i] * np.sqrt(T) * z_correlated[:, i]
667
+ terminal_values[i] = self.initial_values[i] * np.exp(
668
+ drift_term + diffusion_term
669
+ )
670
+
671
+ return terminal_values
672
+
673
+
674
+ @dataclass
675
+ class MultiAssetGBMPathGeneratorQMC(MultiAssetGBMPathGenerator):
676
+ """
677
+ Convenience subclass for QMC-based multi-asset GBM path generation.
678
+
679
+ This simply defaults to using SobolNormalGenerator and sets is_qmc=True.
680
+ """
681
+
682
+ def __post_init__(self) -> None:
683
+ if self.random_stream is None:
684
+ self.random_stream = SobolNormalGenerator()
685
+ self.is_qmc = True
686
+ super().__post_init__()
687
+
688
+
689
+ __all__ = [
690
+ "GBMPathGenerator",
691
+ "GBMPathGeneratorQMC",
692
+ "MultiAssetGBMPathGenerator",
693
+ "MultiAssetGBMPathGeneratorQMC",
694
+ ]