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,613 @@
1
+ """
2
+ PDE solver for Phoenix options using the Two-Surface method.
3
+
4
+ Adds coupon jumps at observation times on top of the Snowball PDE framework.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Dict, List
10
+
11
+ import numpy as np
12
+ import scipy.sparse as sp
13
+ from scipy.linalg import solve_banded
14
+ from time import perf_counter
15
+
16
+ from quantark.asset.equity.engine.pde.base_pde_solver import PDESolutionResult
17
+ from quantark.asset.equity.engine.pde.snowball_pde_solver import SnowballPDESolver
18
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
19
+ from quantark.asset.equity.product.option.phoenix_option import PhoenixOption
20
+ from quantark.asset.equity.product.option.observation_schedule import ResolvedObservationRecord
21
+ from quantark.priceenv import PricingEnvironment
22
+ from quantark.util.enum import CouponPayType, ObservationType
23
+ from quantark.util.exceptions import PricingError, ValidationError
24
+ from quantark.util.numerical import is_close, is_zero
25
+
26
+
27
+ class PhoenixPDESolver(SnowballPDESolver):
28
+ """
29
+ Two-Surface PDE solver for Phoenix options with coupon jumps.
30
+
31
+ KO and KI behavior follows SnowballPDESolver. Coupon payoffs are added at
32
+ observation times based on the coupon barrier.
33
+ """
34
+
35
+ # Override class attributes for product type checking
36
+ _supported_product_type: type = PhoenixOption
37
+ _solver_name: str = "PhoenixPDESolver"
38
+
39
+ def __init__(self, params=None):
40
+ super().__init__(params=params)
41
+ self._coupon_observation_indices: Dict[int, int] = {}
42
+ self._coupon_barriers: np.ndarray = np.array([])
43
+ self._coupon_amounts: np.ndarray = np.array([])
44
+ self._coupon_cumulative: np.ndarray = np.array([])
45
+
46
+ # price() and calculate_greeks() are inherited from SnowballPDESolver
47
+ # The _check_product_type() method uses _supported_product_type to validate
48
+
49
+ # _validate_product is identical to parent, so we inherit it
50
+
51
+ def get_critical_points(
52
+ self, product: PhoenixOption, pricing_env: PricingEnvironment
53
+ ) -> List[float]:
54
+ points = super().get_critical_points(product, pricing_env)
55
+
56
+ coupon_barrier = product.coupon_config.coupon_barrier
57
+ if isinstance(coupon_barrier, list):
58
+ points.extend([b for b in coupon_barrier if b > 0])
59
+ elif coupon_barrier > 0:
60
+ points.append(coupon_barrier)
61
+
62
+ return sorted(set([p for p in points if p > 0]))
63
+
64
+ def _get_barriers(self, product: BaseEquityProduct) -> List[float]:
65
+ barriers = super()._get_barriers(product)
66
+ if not isinstance(product, PhoenixOption):
67
+ return barriers
68
+
69
+ coupon_barrier = product.coupon_config.coupon_barrier
70
+ if isinstance(coupon_barrier, list):
71
+ barriers.extend([b for b in coupon_barrier if b > 0])
72
+ elif coupon_barrier is not None and coupon_barrier > 0:
73
+ barriers.append(coupon_barrier)
74
+
75
+ return barriers
76
+
77
+ def _get_immediate_ko_payoff(
78
+ self, product: PhoenixOption, pricing_env: PricingEnvironment
79
+ ) -> float:
80
+ ko_records = product.resolve_ko_observations(pricing_env)
81
+ ko_record_0 = self._find_record_at_time(ko_records, 0.0)
82
+ if ko_record_0 is None:
83
+ raise ValidationError(
84
+ "Immediate KO payoff requested but no KO observation exists at valuation date."
85
+ )
86
+
87
+ spot = pricing_env.spot
88
+ coupon_payoff = 0.0
89
+ if product.is_coupon_triggered(spot, 0):
90
+ coupon_payoff = product.get_coupon_payoff(0)
91
+
92
+ payoff = float(ko_record_0.payoff or 0.0) + float(coupon_payoff)
93
+ settlement_time = ko_record_0.settlement_time
94
+ if settlement_time is not None and settlement_time > 0.0:
95
+ df = pricing_env.get_discount_factor(settlement_time)
96
+ return float(payoff) * float(df)
97
+ return float(payoff)
98
+
99
+ def _calculate_terminal_value(
100
+ self, product: PhoenixOption, spot: float, pricing_env: PricingEnvironment
101
+ ) -> float:
102
+ """Calculate terminal payoff when already expired."""
103
+ knocked_in = self._is_already_knocked_in(product, spot)
104
+ return product.get_payoff(
105
+ spot,
106
+ knocked_in=knocked_in,
107
+ accumulated_coupons=0.0,
108
+ pricing_env=pricing_env,
109
+ )
110
+
111
+ def _solve(
112
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
113
+ ) -> PDESolutionResult:
114
+ """
115
+ Core Two-Surface PDE solving logic for Phoenix options.
116
+ Overrides Snowball logic to handle vector states for memory coupons.
117
+ """
118
+ spot = pricing_env.spot
119
+ tau = product.get_maturity(pricing_env)
120
+
121
+ # Determine knocked-in state at valuation
122
+ ki_continuous = (
123
+ product.barrier_config.ki_continuous
124
+ or product.barrier_config.ki_observation_type == ObservationType.CONTINUOUS
125
+ )
126
+ knocked_in_at_valuation = self._is_knocked_in_at_valuation(
127
+ product, spot, pricing_env, ki_continuous=ki_continuous
128
+ )
129
+ self._knocked_in_at_valuation = knocked_in_at_valuation
130
+
131
+ # Extract market data
132
+ strike = product.strike
133
+ r = pricing_env.get_rate(tau)
134
+ q = pricing_env.get_div_yield(tau)
135
+ sigma = pricing_env.get_vol(strike, tau)
136
+
137
+ # Store product properties
138
+ self._is_reverse = product.is_reverse
139
+ self._ki_continuous = ki_continuous
140
+ if product.has_ki_barrier:
141
+ ki_barrier = product.barrier_config.ki_barrier
142
+ if isinstance(ki_barrier, list):
143
+ self._ki_barrier = ki_barrier[0]
144
+ else:
145
+ self._ki_barrier = ki_barrier
146
+
147
+ if self._profile_enabled:
148
+ self._reset_profile_stats()
149
+
150
+ # Build grids
151
+ if self._profile_enabled:
152
+ t0 = perf_counter()
153
+ x_vec, s_vec, dx_vec, t_vec, dt_vec = self._build_grids(
154
+ product, pricing_env, spot, sigma, tau, r, q
155
+ )
156
+ if self._profile_enabled:
157
+ self._profile_stats["grid_build"] += perf_counter() - t0
158
+
159
+ # Memory coupon setup
160
+ use_memory = product.has_memory_coupon
161
+ num_obs = len(self._coupon_barriers)
162
+ if use_memory and num_obs > 50:
163
+ raise ValidationError(
164
+ f"Too many observations ({num_obs}) for Memory Phoenix PDE engine. "
165
+ "Limit is 50 to prevent performance degradation. Use MC engine instead."
166
+ )
167
+
168
+ # Determine number of memory states to track at maturity
169
+ max_k = num_obs if use_memory else 0
170
+
171
+ # Initialize lists of grids
172
+ num_x, num_t = len(x_vec), len(t_vec)
173
+
174
+ # grid_v0_list[k] is the V0 surface for k missed coupons
175
+ grid_v0_list = [np.zeros((num_x, num_t)) for _ in range(max_k + 1)]
176
+ grid_v1_list = [np.zeros((num_x, num_t)) for _ in range(max_k + 1)]
177
+
178
+ # Set terminal conditions for all memory states
179
+ self._set_terminal_condition_vector(
180
+ grid_v0_list, grid_v1_list, x_vec, s_vec, product, pricing_env
181
+ )
182
+
183
+ # Apply terminal coupon/KO/KI if maturity is an observation time.
184
+ terminal_tidx = len(t_vec) - 1
185
+ coupon_obs_idx = self._coupon_observation_indices.get(terminal_tidx)
186
+ if coupon_obs_idx is not None:
187
+ self._apply_coupon_jump_vector(
188
+ grid_v0_list,
189
+ grid_v1_list,
190
+ s_vec,
191
+ terminal_tidx,
192
+ current_time=tau,
193
+ product=product,
194
+ pricing_env=pricing_env,
195
+ obs_idx=coupon_obs_idx,
196
+ )
197
+
198
+ if product.has_ki_barrier:
199
+ should_apply_ki = self._ki_continuous or terminal_tidx in self._ki_observation_indices
200
+ if should_apply_ki:
201
+ for k in range(len(grid_v0_list)):
202
+ self._apply_ki_jump(grid_v0_list[k], grid_v1_list[k], s_vec, terminal_tidx, product)
203
+
204
+ ko_record = self._ko_observation_indices.get(terminal_tidx)
205
+ if ko_record is not None:
206
+ self._apply_ko_jump_vector(
207
+ grid_v0_list,
208
+ grid_v1_list,
209
+ s_vec,
210
+ terminal_tidx,
211
+ current_time=tau,
212
+ product=product,
213
+ pricing_env=pricing_env,
214
+ ko_record=ko_record,
215
+ )
216
+
217
+ # Build operator matrices
218
+ l, c, u = self._calculate_coefficients(r, q, sigma, dx_vec, num_x)
219
+ A = self._build_operator_matrix(l, c, u, num_x)
220
+
221
+ # Time stepping with vector state
222
+ self._time_stepping_vector_surface(
223
+ grid_v0_list,
224
+ grid_v1_list,
225
+ A,
226
+ l,
227
+ c,
228
+ u,
229
+ x_vec,
230
+ s_vec,
231
+ t_vec,
232
+ dt_vec,
233
+ product,
234
+ pricing_env,
235
+ r,
236
+ q,
237
+ sigma,
238
+ tau,
239
+ )
240
+
241
+ # Result is from state 0 (no accumulated memory at valuation)
242
+ spot_log = np.log(spot)
243
+ if knocked_in_at_valuation:
244
+ solution_vec = grid_v1_list[0][:, 0]
245
+ else:
246
+ solution_vec = grid_v0_list[0][:, 0]
247
+
248
+ return PDESolutionResult(
249
+ solution_vec=solution_vec,
250
+ x_vec=x_vec,
251
+ s_vec=s_vec,
252
+ spot_log=spot_log,
253
+ )
254
+
255
+ def _set_terminal_condition_vector(
256
+ self,
257
+ grid_v0_list: List[np.ndarray],
258
+ grid_v1_list: List[np.ndarray],
259
+ x_vec: np.ndarray,
260
+ s_vec: np.ndarray,
261
+ product: PhoenixOption,
262
+ pricing_env: PricingEnvironment,
263
+ ) -> None:
264
+ """Set terminal conditions for all memory states."""
265
+ # V1 (Knocked-In): Payoff usually doesn't depend on memory (coupon lost?)
266
+ payoff_v1 = np.array(
267
+ [product.get_maturity_payoff_v1(s, pricing_env) for s in s_vec]
268
+ )
269
+ for grid in grid_v1_list:
270
+ grid[:, -1] = payoff_v1
271
+
272
+ # V0 (Not Knocked-In): Base payoff (coupons added via jumps)
273
+ for k, grid in enumerate(grid_v0_list):
274
+ payoff_v0 = np.array(
275
+ [
276
+ product.get_maturity_payoff_v0(
277
+ s, accumulated_coupons=0.0, pricing_env=pricing_env
278
+ )
279
+ for s in s_vec
280
+ ]
281
+ )
282
+ grid[:, -1] = payoff_v0
283
+
284
+ def _build_grids(
285
+ self,
286
+ product: PhoenixOption,
287
+ pricing_env: PricingEnvironment,
288
+ spot: float,
289
+ sigma: float,
290
+ tau: float,
291
+ r: float,
292
+ q: float,
293
+ ):
294
+ result = super()._build_grids(product, pricing_env, spot, sigma, tau, r, q)
295
+ _, _, _, t_vec, _ = result
296
+
297
+ self._coupon_observation_indices.clear()
298
+ ko_records = self._get_cached_ko_records(pricing_env, product)
299
+ if not ko_records:
300
+ return result
301
+
302
+ ko_times = [rec.observation_time for rec in ko_records]
303
+ num_obs = len(ko_times)
304
+
305
+ coupon_barrier = product.coupon_config.coupon_barrier
306
+ if isinstance(coupon_barrier, list):
307
+ if len(coupon_barrier) != num_obs:
308
+ raise ValidationError(
309
+ "Coupon barrier schedule length does not match KO observations."
310
+ )
311
+ self._coupon_barriers = np.array(coupon_barrier, dtype=float)
312
+ else:
313
+ self._coupon_barriers = np.full(num_obs, float(coupon_barrier))
314
+
315
+ period_year_fractions = np.array(
316
+ product.get_coupon_period_year_fractions(ko_times),
317
+ dtype=float,
318
+ )
319
+ self._coupon_amounts = np.array(
320
+ [
321
+ product.get_coupon_payoff(i, year_fraction=period_year_fractions[i])
322
+ for i in range(num_obs)
323
+ ],
324
+ dtype=float,
325
+ )
326
+ self._coupon_cumulative = np.concatenate(
327
+ ([0.0], np.cumsum(self._coupon_amounts))
328
+ )
329
+ for obs_idx, obs_time in enumerate(ko_times):
330
+ if is_close(obs_time, 0.0):
331
+ self._coupon_observation_indices[0] = obs_idx
332
+ elif is_close(obs_time, tau):
333
+ self._coupon_observation_indices[len(t_vec) - 1] = obs_idx
334
+ elif 0.0 < obs_time < tau:
335
+ idx = self._aligned_time_index(t_vec, obs_time, "Coupon observation")
336
+ self._coupon_observation_indices[idx] = obs_idx
337
+
338
+ return result
339
+
340
+ def _accumulated_coupon_amount(self, obs_idx: int, missed_count: int) -> float:
341
+ if missed_count <= 0 or obs_idx <= 0:
342
+ return 0.0
343
+ start = max(obs_idx - missed_count, 0)
344
+ return float(self._coupon_cumulative[obs_idx] - self._coupon_cumulative[start])
345
+
346
+ def _time_stepping_vector_surface(
347
+ self,
348
+ grid_v0_list: List[np.ndarray],
349
+ grid_v1_list: List[np.ndarray],
350
+ A: sp.csc_matrix,
351
+ l: np.ndarray,
352
+ c: np.ndarray,
353
+ u: np.ndarray,
354
+ x_vec: np.ndarray,
355
+ s_vec: np.ndarray,
356
+ t_vec: np.ndarray,
357
+ dt_vec: np.ndarray,
358
+ product: PhoenixOption,
359
+ pricing_env: PricingEnvironment,
360
+ r: float,
361
+ q: float,
362
+ sigma: float,
363
+ tau: float,
364
+ ) -> None:
365
+ """Backward time stepping for vector surfaces."""
366
+ params = self.params
367
+ num_t, num_x = len(t_vec), len(x_vec)
368
+ I_int = sp.eye(num_x - 2, format="csc")
369
+ use_banded = params.use_banded_solver
370
+ n_int = num_x - 2
371
+
372
+ # Reuse caches
373
+ self._matrix_cache.clear()
374
+ self._banded_cache.clear()
375
+
376
+ # Temporary buffers for RHS/Sol
377
+ rhs = None
378
+ if use_banded and n_int > 2:
379
+ rhs = np.empty(n_int, dtype=float)
380
+
381
+ smooth_js = set()
382
+ event_theta = params.event_theta
383
+ event_steps = params.event_rannacher_steps
384
+ if params.use_rannacher and params.auto_grid and params.rannacher_at_events:
385
+ event_times = self._get_event_times(product, tau)
386
+ if event_times and event_steps > 0:
387
+ for et in event_times:
388
+ idx = int(np.argmin(np.abs(t_vec - et)))
389
+ if 0 < idx < num_t - 1 and is_close(float(t_vec[idx]), float(et)):
390
+ for k in range(event_steps):
391
+ smooth_idx = idx - 1 - k
392
+ if smooth_idx >= 0:
393
+ smooth_js.add(smooth_idx)
394
+
395
+ for j in range(num_t - 2, -1, -1):
396
+ dt = dt_vec[j]
397
+ theta = params.theta
398
+
399
+ steps_from_end = num_t - 1 - j
400
+ if params.use_rannacher and steps_from_end < params.rannacher_steps:
401
+ theta = 1.0
402
+ elif j in smooth_js:
403
+ theta = event_theta
404
+
405
+ banded, lower1, main1, upper1 = (None, None, None, None)
406
+ M1, M2_lu = (None, None)
407
+
408
+ if use_banded and n_int > 2:
409
+ banded, lower1, main1, upper1 = self._get_banded_system(l, c, u, dt, theta)
410
+ else:
411
+ M1, M2_lu = self._get_matrices(I_int, A, dt, theta)
412
+
413
+ tau_remaining = tau - t_vec[j]
414
+
415
+ # Step V0 grids
416
+ self._step_grids(grid_v0_list, j, dt, theta, x_vec, s_vec, tau_remaining, product, pricing_env,
417
+ use_banded, banded, lower1, main1, upper1, M1, M2_lu, rhs, l, u, is_v1=False)
418
+
419
+ # Step V1 grids
420
+ self._step_grids(grid_v1_list, j, dt, theta, x_vec, s_vec, tau_remaining, product, pricing_env,
421
+ use_banded, banded, lower1, main1, upper1, M1, M2_lu, rhs, l, u, is_v1=True)
422
+
423
+ # Apply modifications (Coupons, KO, KI)
424
+ self._apply_step_modifications_vector_surface(
425
+ grid_v0_list, grid_v1_list, x_vec, s_vec, j, tau_remaining, product, pricing_env
426
+ )
427
+
428
+ def _step_grids(self, grid_list, j, dt, theta, x_vec, s_vec, tau_remaining, product, pricing_env,
429
+ use_banded, banded, lower1, main1, upper1, M1, M2_lu, rhs, l, u, is_v1):
430
+ """Helper to diffuse a list of grids."""
431
+ for grid in grid_list:
432
+ # Set boundary
433
+ if is_v1:
434
+ self._set_boundary_conditions_v1(grid, x_vec, s_vec, j, tau_remaining, product, pricing_env)
435
+ else:
436
+ self._set_boundary_conditions_v0(grid, x_vec, s_vec, j, tau_remaining, product, pricing_env)
437
+
438
+ v_next = grid[1:-1, j + 1]
439
+
440
+ if use_banded and banded is not None:
441
+ np.multiply(main1, v_next, out=rhs)
442
+ rhs[1:] += lower1 * v_next[:-1]
443
+ rhs[:-1] += upper1 * v_next[1:]
444
+
445
+ if len(grid) > 2:
446
+ rhs[0] += dt * ((1.0 - theta) * l[1] * grid[0, j + 1] + theta * l[1] * grid[0, j])
447
+ rhs[-1] += dt * ((1.0 - theta) * u[-2] * grid[-1, j + 1] + theta * u[-2] * grid[-1, j])
448
+
449
+ sol = solve_banded((1, 1), banded, rhs, overwrite_b=True, check_finite=False)
450
+ grid[1:-1, j] = sol
451
+ else:
452
+ rhs_val = M1 @ v_next
453
+ if len(grid) > 2:
454
+ rhs_val[0] += dt * ((1.0 - theta) * l[1] * grid[0, j + 1] + theta * l[1] * grid[0, j])
455
+ rhs_val[-1] += dt * ((1.0 - theta) * u[-2] * grid[-1, j + 1] + theta * u[-2] * grid[-1, j])
456
+
457
+ grid[1:-1, j] = M2_lu.solve(rhs_val)
458
+
459
+ def _apply_step_modifications_vector_surface(
460
+ self,
461
+ grid_v0_list: List[np.ndarray],
462
+ grid_v1_list: List[np.ndarray],
463
+ x_vec: np.ndarray,
464
+ s_vec: np.ndarray,
465
+ t_idx: int,
466
+ tau: float,
467
+ product: PhoenixOption,
468
+ pricing_env: PricingEnvironment,
469
+ ) -> None:
470
+ current_time = self._total_tau - tau
471
+
472
+ # 1. Coupon Jump (Fan-In)
473
+ coupon_obs_idx = self._coupon_observation_indices.get(t_idx)
474
+ if coupon_obs_idx is not None:
475
+ self._apply_coupon_jump_vector(
476
+ grid_v0_list,
477
+ grid_v1_list,
478
+ s_vec,
479
+ t_idx,
480
+ current_time,
481
+ product,
482
+ pricing_env,
483
+ coupon_obs_idx,
484
+ )
485
+
486
+ # 2. KI Jump
487
+ if product.has_ki_barrier:
488
+ should_apply_ki = self._ki_continuous or t_idx in self._ki_observation_indices
489
+ if should_apply_ki:
490
+ # Apply to all states
491
+ for k in range(len(grid_v0_list)):
492
+ self._apply_ki_jump(grid_v0_list[k], grid_v1_list[k], s_vec, t_idx, product)
493
+
494
+ # 3. KO Jump
495
+ ko_record = self._ko_observation_indices.get(t_idx)
496
+ if ko_record is not None:
497
+ self._apply_ko_jump_vector(
498
+ grid_v0_list,
499
+ grid_v1_list,
500
+ s_vec,
501
+ t_idx,
502
+ current_time,
503
+ product,
504
+ pricing_env,
505
+ ko_record,
506
+ )
507
+
508
+ def _apply_coupon_jump_vector(
509
+ self,
510
+ grid_v0_list: List[np.ndarray],
511
+ grid_v1_list: List[np.ndarray],
512
+ s_vec: np.ndarray,
513
+ t_idx: int,
514
+ current_time: float,
515
+ product: PhoenixOption,
516
+ pricing_env: PricingEnvironment,
517
+ obs_idx: int,
518
+ ) -> None:
519
+ if obs_idx < 0 or obs_idx >= self._coupon_barriers.shape[0]:
520
+ return
521
+
522
+ barrier = float(self._coupon_barriers[obs_idx])
523
+ coupon_amt = float(self._coupon_amounts[obs_idx])
524
+ use_memory = product.has_memory_coupon
525
+
526
+ settlement_time = (
527
+ self._total_tau
528
+ if product.coupon_config.coupon_pay_type == CouponPayType.EXPIRY
529
+ else current_time
530
+ )
531
+ coupon_discount = self._df_between_times(pricing_env, current_time, settlement_time)
532
+
533
+ # Coupon barrier behaves like KO (UP barrier) - pay when above
534
+ pay_mask = self._get_barrier_mask(s_vec, barrier, product.is_reverse, is_up_barrier=True)
535
+
536
+ max_k = obs_idx if use_memory else 0
537
+ diffused_v0_0 = grid_v0_list[0][:, t_idx].copy()
538
+ diffused_v1_0 = grid_v1_list[0][:, t_idx].copy()
539
+
540
+ for k in range(max_k + 1):
541
+ accumulated_pay = (
542
+ self._accumulated_coupon_amount(obs_idx, k) if use_memory else 0.0
543
+ )
544
+ total_pay = (coupon_amt + accumulated_pay) * coupon_discount
545
+
546
+ # V0
547
+ val_pay_0 = diffused_v0_0 + total_pay
548
+ next_k = k + 1 if use_memory else 0
549
+ val_miss_0 = grid_v0_list[next_k][:, t_idx]
550
+
551
+ grid_v0_list[k][pay_mask, t_idx] = val_pay_0[pay_mask]
552
+ grid_v0_list[k][~pay_mask, t_idx] = val_miss_0[~pay_mask]
553
+
554
+ # V1
555
+ val_pay_1 = diffused_v1_0 + total_pay
556
+ val_miss_1 = grid_v1_list[next_k][:, t_idx]
557
+
558
+ grid_v1_list[k][pay_mask, t_idx] = val_pay_1[pay_mask]
559
+ grid_v1_list[k][~pay_mask, t_idx] = val_miss_1[~pay_mask]
560
+
561
+ def _apply_ko_jump_vector(
562
+ self,
563
+ grid_v0_list: List[np.ndarray],
564
+ grid_v1_list: List[np.ndarray],
565
+ s_vec: np.ndarray,
566
+ t_idx: int,
567
+ current_time: float,
568
+ product: PhoenixOption,
569
+ pricing_env: PricingEnvironment,
570
+ ko_record: ResolvedObservationRecord,
571
+ ) -> None:
572
+ barrier = ko_record.barrier
573
+ base_payoff = float(ko_record.payoff or 0.0)
574
+
575
+ ko_mask = self._get_barrier_mask(s_vec, barrier, product.is_reverse, is_up_barrier=True)
576
+
577
+ coupon_amt = 0.0
578
+ obs_idx = self._coupon_observation_indices.get(t_idx)
579
+ if obs_idx is not None:
580
+ coupon_amt = float(self._coupon_amounts[obs_idx])
581
+
582
+ use_memory = product.has_memory_coupon
583
+ max_k = obs_idx if (obs_idx is not None and use_memory) else 0
584
+
585
+ if obs_idx is not None:
586
+ coupon_barrier = float(self._coupon_barriers[obs_idx])
587
+ pay_mask = self._get_barrier_mask(s_vec, coupon_barrier, product.is_reverse, is_up_barrier=True)
588
+ else:
589
+ pay_mask = np.zeros_like(s_vec, dtype=bool)
590
+
591
+ df = 1.0
592
+ if ko_record.settlement_time is not None and ko_record.settlement_time > current_time:
593
+ df = self._df_between_times(pricing_env, current_time, ko_record.settlement_time)
594
+
595
+ for k in range(len(grid_v0_list)):
596
+ effective_k = k if k <= max_k else max_k
597
+ accumulated_pay = (
598
+ self._accumulated_coupon_amount(obs_idx, effective_k)
599
+ if (use_memory and obs_idx is not None)
600
+ else 0.0
601
+ )
602
+
603
+ total_payoff = np.full_like(
604
+ s_vec, (base_payoff + accumulated_pay) * df, dtype=float
605
+ )
606
+ if coupon_amt > 0.0:
607
+ total_payoff = np.where(
608
+ pay_mask, total_payoff + coupon_amt * df, total_payoff
609
+ )
610
+
611
+ if ko_mask.any():
612
+ grid_v0_list[k][ko_mask, t_idx] = total_payoff[ko_mask]
613
+ grid_v1_list[k][ko_mask, t_idx] = total_payoff[ko_mask]