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,692 @@
1
+ """
2
+ PDE solver for single barrier options.
3
+
4
+ Implements the finite difference method for knock-in and knock-out
5
+ barrier options with continuous or discrete monitoring.
6
+ """
7
+
8
+ from typing import Dict, List, Optional, Set
9
+
10
+ import numpy as np
11
+
12
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
13
+ from quantark.asset.equity.product.option.barrier_option import BarrierOption
14
+ from quantark.asset.equity.product.option.observation_schedule import ResolvedObservationRecord
15
+ from quantark.asset.equity.param import PDEParams
16
+ from quantark.priceenv import PricingEnvironment
17
+ from quantark.util.enum import ObservationType, ObservationAggregation
18
+ from quantark.util.exceptions import PricingError
19
+ from quantark.util.numerical import is_close, safe_divide
20
+
21
+ from .base_pde_solver import BasePDESolver
22
+
23
+
24
+ class BarrierPDESolver(BasePDESolver):
25
+ """
26
+ PDE solver for single barrier options.
27
+
28
+ Handles all four barrier types:
29
+ - DOWN_OUT: Knock-out when price goes below barrier
30
+ - DOWN_IN: Knock-in when price goes below barrier
31
+ - UP_OUT: Knock-out when price goes above barrier
32
+ - UP_IN: Knock-in when price goes above barrier
33
+
34
+ For knock-out options, the grid boundary at the barrier is set to
35
+ the rebate value (or 0 if no rebate).
36
+
37
+ For knock-in options, we use the identity:
38
+ Knock-in = Vanilla - Knock-out
39
+
40
+ This solver also handles discrete observation by only checking
41
+ the barrier at specified observation times.
42
+ """
43
+
44
+ def __init__(self, params: Optional[PDEParams] = None):
45
+ """
46
+ Initialize barrier option PDE solver.
47
+
48
+ Args:
49
+ params: PDE engine configuration parameters
50
+ """
51
+ super().__init__(params)
52
+ self._observation_indices: Set[int] = set()
53
+ self._schedule_records: Dict[int, List[ResolvedObservationRecord]] = {}
54
+ self._schedule_aggregation: ObservationAggregation = (
55
+ ObservationAggregation.STOP_FIRST_HIT
56
+ )
57
+ self._total_tau: float = 0.0
58
+ self._terminal_schedule_records: List[ResolvedObservationRecord] = []
59
+ self._has_terminal_observation: bool = False
60
+
61
+ @staticmethod
62
+ def _current_time(total_tau: float, tau_remaining: float) -> float:
63
+ return max(total_tau - tau_remaining, 0.0)
64
+
65
+ @staticmethod
66
+ def _df_between_times(
67
+ pricing_env: PricingEnvironment, start_time: float, end_time: float
68
+ ) -> float:
69
+ if end_time <= start_time:
70
+ return 1.0
71
+ df_end = pricing_env.get_discount_factor(end_time)
72
+ df_start = pricing_env.get_discount_factor(start_time)
73
+ return float(safe_divide(df_end, df_start, fallback=0.0))
74
+
75
+ def _cashflow_value_at_time(
76
+ self,
77
+ pricing_env: PricingEnvironment,
78
+ cashflow: float,
79
+ current_time: float,
80
+ settlement_time: Optional[float],
81
+ ) -> float:
82
+ if settlement_time is None or settlement_time <= current_time:
83
+ return float(cashflow)
84
+ df = self._df_between_times(pricing_env, current_time, settlement_time)
85
+ return float(cashflow) * df
86
+
87
+ def price(
88
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
89
+ ) -> float:
90
+ """
91
+ Price a barrier option using PDE method.
92
+
93
+ For knock-in options, uses: Knock-in = Vanilla - Knock-out
94
+
95
+ Args:
96
+ product: Barrier option
97
+ pricing_env: Pricing environment
98
+
99
+ Returns:
100
+ Option price
101
+
102
+ Raises:
103
+ PricingError: If product is not a barrier option
104
+ """
105
+ if not isinstance(product, BarrierOption):
106
+ raise PricingError(
107
+ f"BarrierPDESolver only supports BarrierOption, "
108
+ f"got {type(product).__name__}"
109
+ )
110
+
111
+ if getattr(product, "observation_type", None) == ObservationType.EXPIRY:
112
+ raise PricingError(
113
+ "BarrierPDESolver does not support EXPIRY observation_type. "
114
+ "Use BarrierAnalyticalEngine for expiry-only monitoring."
115
+ )
116
+
117
+ # Check if barrier is already hit
118
+ spot = pricing_env.spot
119
+ if product.is_barrier_hit(spot):
120
+ if product.is_knock_out:
121
+ maturity = product.get_maturity(pricing_env)
122
+ settlement_time = 0.0 if product.pay_at_hit else maturity
123
+ return self._cashflow_value_at_time(
124
+ pricing_env=pricing_env,
125
+ cashflow=product.rebate,
126
+ current_time=0.0,
127
+ settlement_time=settlement_time,
128
+ )
129
+ else:
130
+ # Knocked in, price as vanilla
131
+ return self._price_vanilla(product, pricing_env)
132
+
133
+ if product.is_knock_in:
134
+ # Knock-in = Vanilla - Knock-out (with no rebate)
135
+ vanilla_price = self._price_vanilla(product, pricing_env)
136
+
137
+ # Create a temporary knock-out version with no rebate
138
+ ko_price = self._price_knock_out(product, pricing_env)
139
+
140
+ return vanilla_price - ko_price
141
+ else:
142
+ # Direct knock-out pricing
143
+ return super().price(product, pricing_env)
144
+
145
+ def _price_vanilla(
146
+ self, product: BarrierOption, pricing_env: PricingEnvironment
147
+ ) -> float:
148
+ """
149
+ Price the underlying vanilla option.
150
+
151
+ Args:
152
+ product: Barrier option (used for strike, type, maturity)
153
+ pricing_env: Pricing environment
154
+
155
+ Returns:
156
+ Vanilla option price
157
+ """
158
+ from quantark.asset.equity.product.option import EuropeanVanillaOption
159
+ from .european_pde_solver import EuropeanPDESolver
160
+
161
+ vanilla = EuropeanVanillaOption(
162
+ strike=product.strike,
163
+ option_type=product.option_type,
164
+ maturity=product.maturity,
165
+ exercise_date=product.exercise_date,
166
+ settlement_date=product.settlement_date,
167
+ )
168
+
169
+ solver = EuropeanPDESolver(self.params)
170
+ return product.participation_rate * solver.price(vanilla, pricing_env)
171
+
172
+ def _price_knock_out(
173
+ self, product: BarrierOption, pricing_env: PricingEnvironment
174
+ ) -> float:
175
+ """
176
+ Price as a knock-out option (for knock-in decomposition).
177
+
178
+ Creates a temporary knock-out version and prices it.
179
+
180
+ Args:
181
+ product: Original barrier option
182
+ pricing_env: Pricing environment
183
+
184
+ Returns:
185
+ Knock-out option price
186
+ """
187
+ # For knock-in decomposition, we need knock-out with zero rebate
188
+ from quantark.util.enum import BarrierType
189
+
190
+ # Convert knock-in type to knock-out
191
+ if product.barrier_type == BarrierType.UP_IN:
192
+ ko_type = BarrierType.UP_OUT
193
+ elif product.barrier_type == BarrierType.DOWN_IN:
194
+ ko_type = BarrierType.DOWN_OUT
195
+ else:
196
+ ko_type = product.barrier_type
197
+
198
+ ko_product = BarrierOption(
199
+ strike=product.strike,
200
+ option_type=product.option_type,
201
+ barrier=product.barrier,
202
+ barrier_type=ko_type,
203
+ maturity=product.maturity,
204
+ exercise_date=product.exercise_date,
205
+ settlement_date=product.settlement_date,
206
+ rebate=0.0, # Zero rebate for decomposition
207
+ participation_rate=product.participation_rate,
208
+ pay_at_hit=product.pay_at_hit,
209
+ observation_type=product.observation_type,
210
+ observation_dates=product.observation_dates,
211
+ observation_schedule=product.observation_schedule,
212
+ )
213
+
214
+ return super().price(ko_product, pricing_env)
215
+
216
+ def calculate_greeks(
217
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
218
+ ) -> Dict[str, float]:
219
+ """
220
+ Calculate Greeks for a barrier option using PDE method.
221
+
222
+ For knock-in options, uses: Greeks_KI = Greeks_Vanilla - Greeks_KO
223
+
224
+ Args:
225
+ product: Barrier option
226
+ pricing_env: Pricing environment
227
+
228
+ Returns:
229
+ Dictionary with price, delta, gamma
230
+
231
+ Raises:
232
+ PricingError: If product is not a barrier option
233
+ """
234
+ if not isinstance(product, BarrierOption):
235
+ raise PricingError(
236
+ f"BarrierPDESolver only supports BarrierOption, "
237
+ f"got {type(product).__name__}"
238
+ )
239
+
240
+ if getattr(product, "observation_type", None) == ObservationType.EXPIRY:
241
+ raise PricingError(
242
+ "BarrierPDESolver does not support EXPIRY observation_type. "
243
+ "Use BarrierAnalyticalEngine for expiry-only monitoring."
244
+ )
245
+
246
+ spot = pricing_env.spot
247
+ tau = product.get_maturity(pricing_env)
248
+
249
+ # Handle expired case
250
+ if tau <= 0:
251
+ return {
252
+ "price": self._calculate_intrinsic(product, spot),
253
+ "delta": self._intrinsic_delta(product, spot),
254
+ "gamma": 0.0,
255
+ }
256
+
257
+ # Check if barrier is already hit
258
+ if product.is_barrier_hit(spot):
259
+ if product.is_knock_out:
260
+ # Knocked out: return rebate Greeks (rebate is constant, so delta=gamma=0)
261
+ maturity = product.get_maturity(pricing_env)
262
+ settlement_time = 0.0 if product.pay_at_hit else maturity
263
+ rebate_value = self._cashflow_value_at_time(
264
+ pricing_env=pricing_env,
265
+ cashflow=product.rebate,
266
+ current_time=0.0,
267
+ settlement_time=settlement_time,
268
+ )
269
+ return {"price": rebate_value, "delta": 0.0, "gamma": 0.0}
270
+ else:
271
+ # Knocked in: return vanilla Greeks
272
+ return self._calculate_greeks_vanilla(product, pricing_env)
273
+
274
+ if product.is_knock_in:
275
+ # Knock-in = Vanilla - Knock-out decomposition
276
+ vanilla_greeks = self._calculate_greeks_vanilla(product, pricing_env)
277
+ ko_greeks = self._calculate_greeks_knock_out(product, pricing_env)
278
+
279
+ return {
280
+ "price": vanilla_greeks["price"] - ko_greeks["price"],
281
+ "delta": vanilla_greeks["delta"] - ko_greeks["delta"],
282
+ "gamma": vanilla_greeks["gamma"] - ko_greeks["gamma"],
283
+ }
284
+ else:
285
+ # Direct knock-out pricing
286
+ return super().calculate_greeks(product, pricing_env)
287
+
288
+ def _calculate_greeks_vanilla(
289
+ self, product: BarrierOption, pricing_env: PricingEnvironment
290
+ ) -> Dict[str, float]:
291
+ """
292
+ Calculate Greeks for the underlying vanilla option.
293
+
294
+ Args:
295
+ product: Barrier option (used for strike, type, maturity)
296
+ pricing_env: Pricing environment
297
+
298
+ Returns:
299
+ Dictionary with price, delta, gamma for vanilla option
300
+ """
301
+ from quantark.asset.equity.product.option import EuropeanVanillaOption
302
+ from .european_pde_solver import EuropeanPDESolver
303
+
304
+ vanilla = EuropeanVanillaOption(
305
+ strike=product.strike,
306
+ option_type=product.option_type,
307
+ maturity=product.maturity,
308
+ exercise_date=product.exercise_date,
309
+ settlement_date=product.settlement_date,
310
+ )
311
+
312
+ solver = EuropeanPDESolver(self.params)
313
+ greeks = solver.calculate_greeks(vanilla, pricing_env)
314
+
315
+ # Apply participation rate
316
+ pr = product.participation_rate
317
+ return {
318
+ "price": pr * greeks["price"],
319
+ "delta": pr * greeks["delta"],
320
+ "gamma": pr * greeks["gamma"],
321
+ }
322
+
323
+ def _calculate_greeks_knock_out(
324
+ self, product: BarrierOption, pricing_env: PricingEnvironment
325
+ ) -> Dict[str, float]:
326
+ """
327
+ Calculate Greeks as a knock-out option (for knock-in decomposition).
328
+
329
+ Creates a temporary knock-out version and calculates Greeks.
330
+
331
+ Args:
332
+ product: Original barrier option
333
+ pricing_env: Pricing environment
334
+
335
+ Returns:
336
+ Dictionary with price, delta, gamma for knock-out option
337
+ """
338
+ from quantark.util.enum import BarrierType
339
+
340
+ # Convert knock-in type to knock-out
341
+ if product.barrier_type == BarrierType.UP_IN:
342
+ ko_type = BarrierType.UP_OUT
343
+ elif product.barrier_type == BarrierType.DOWN_IN:
344
+ ko_type = BarrierType.DOWN_OUT
345
+ else:
346
+ ko_type = product.barrier_type
347
+
348
+ ko_product = BarrierOption(
349
+ strike=product.strike,
350
+ option_type=product.option_type,
351
+ barrier=product.barrier,
352
+ barrier_type=ko_type,
353
+ maturity=product.maturity,
354
+ exercise_date=product.exercise_date,
355
+ settlement_date=product.settlement_date,
356
+ rebate=0.0, # Zero rebate for decomposition
357
+ participation_rate=product.participation_rate,
358
+ pay_at_hit=product.pay_at_hit,
359
+ observation_type=product.observation_type,
360
+ observation_dates=product.observation_dates,
361
+ observation_schedule=product.observation_schedule,
362
+ )
363
+
364
+ return super().calculate_greeks(ko_product, pricing_env)
365
+
366
+ def set_terminal_condition(
367
+ self,
368
+ grid: np.ndarray,
369
+ x_vec: np.ndarray,
370
+ s_vec: np.ndarray,
371
+ product: BaseEquityProduct,
372
+ pricing_env: PricingEnvironment,
373
+ ) -> None:
374
+ """
375
+ Set the terminal condition (payoff at maturity).
376
+
377
+ For knock-out options:
378
+ - Zero payoff if barrier already hit
379
+ - Standard payoff otherwise
380
+
381
+ Args:
382
+ grid: Solution grid [num_x, num_t]
383
+ x_vec: Log-price grid points
384
+ s_vec: Price grid points
385
+ product: Barrier option
386
+ pricing_env: Pricing environment
387
+ """
388
+ K = product.strike
389
+ barrier = product.barrier
390
+ participation = product.participation_rate
391
+
392
+ # Calculate base payoff
393
+ if product.is_call():
394
+ payoff = np.maximum(s_vec - K, 0.0)
395
+ else:
396
+ payoff = np.maximum(K - s_vec, 0.0)
397
+ payoff = payoff * participation
398
+
399
+ apply_terminal_barrier = product.observation_type != ObservationType.DISCRETE
400
+ apply_terminal_barrier = (
401
+ apply_terminal_barrier or self._has_terminal_observation
402
+ )
403
+
404
+ if apply_terminal_barrier:
405
+ if (
406
+ product.observation_type == ObservationType.DISCRETE
407
+ and self._terminal_schedule_records
408
+ ):
409
+ current_time = self._total_tau
410
+ for rec in self._terminal_schedule_records:
411
+ rec_barrier = rec.barrier if rec.barrier is not None else barrier
412
+ cashflow_value = self._cashflow_value_at_time(
413
+ pricing_env=pricing_env,
414
+ cashflow=rec.payoff,
415
+ current_time=current_time,
416
+ settlement_time=rec.settlement_time,
417
+ )
418
+ if self._schedule_aggregation == ObservationAggregation.ACCUMULATE:
419
+ if product.is_up_barrier:
420
+ payoff[s_vec >= rec_barrier] += cashflow_value
421
+ else:
422
+ payoff[s_vec <= rec_barrier] += cashflow_value
423
+ else:
424
+ if product.is_up_barrier:
425
+ payoff[s_vec >= rec_barrier] = cashflow_value
426
+ else:
427
+ payoff[s_vec <= rec_barrier] = cashflow_value
428
+ break
429
+ else:
430
+ if product.is_up_barrier:
431
+ payoff[s_vec >= barrier] = product.rebate
432
+ else:
433
+ payoff[s_vec <= barrier] = product.rebate
434
+
435
+ grid[:, -1] = payoff
436
+
437
+ def set_boundary_conditions(
438
+ self,
439
+ grid: np.ndarray,
440
+ x_vec: np.ndarray,
441
+ s_vec: np.ndarray,
442
+ t_idx: int,
443
+ tau: float,
444
+ product: BaseEquityProduct,
445
+ pricing_env: PricingEnvironment,
446
+ ) -> None:
447
+ """
448
+ Set boundary conditions at spatial edges.
449
+
450
+ For barrier options, the boundary at the barrier level
451
+ is set to the rebate (discounted if paid at expiry).
452
+
453
+ Args:
454
+ grid: Solution grid [num_x, num_t]
455
+ x_vec: Log-price grid points
456
+ s_vec: Price grid points
457
+ t_idx: Current time index
458
+ tau: Time remaining to maturity
459
+ product: Barrier option
460
+ pricing_env: Pricing environment
461
+ """
462
+ K = product.strike
463
+ total_tau = (
464
+ self._total_tau
465
+ if self._total_tau > 0
466
+ else product.get_maturity(pricing_env)
467
+ )
468
+ current_time = self._current_time(total_tau, tau)
469
+ df_to_maturity = self._df_between_times(pricing_env, current_time, total_tau)
470
+
471
+ q = pricing_env.get_div_yield(tau) if tau > 0 else 0.0
472
+ df_div = np.exp(-q * tau) if tau > 0 else 1.0
473
+ participation = product.participation_rate
474
+
475
+ if product.observation_type == ObservationType.DISCRETE:
476
+ if product.is_call():
477
+ grid[0, t_idx] = 0.0
478
+ grid[-1, t_idx] = (
479
+ max(s_vec[-1] * df_div - K * df_to_maturity, 0.0) * participation
480
+ )
481
+ else:
482
+ grid[0, t_idx] = K * df_to_maturity * participation
483
+ grid[-1, t_idx] = 0.0
484
+ return
485
+
486
+ settlement_time = current_time if product.pay_at_hit else total_tau
487
+ rebate_value = self._cashflow_value_at_time(
488
+ pricing_env=pricing_env,
489
+ cashflow=product.rebate,
490
+ current_time=current_time,
491
+ settlement_time=settlement_time,
492
+ )
493
+
494
+ if product.is_up_barrier:
495
+ if product.is_call():
496
+ grid[0, t_idx] = 0.0
497
+ else:
498
+ grid[0, t_idx] = K * df_to_maturity * participation
499
+ grid[-1, t_idx] = rebate_value
500
+ else:
501
+ grid[0, t_idx] = rebate_value
502
+ if product.is_call():
503
+ grid[-1, t_idx] = (
504
+ max(s_vec[-1] * df_div - K * df_to_maturity, 0.0) * participation
505
+ )
506
+ else:
507
+ grid[-1, t_idx] = 0.0
508
+
509
+ def _apply_step_modifications(
510
+ self,
511
+ grid: np.ndarray,
512
+ x_vec: np.ndarray,
513
+ s_vec: np.ndarray,
514
+ t_idx: int,
515
+ tau: float,
516
+ product: BaseEquityProduct,
517
+ pricing_env: PricingEnvironment,
518
+ ) -> None:
519
+ """
520
+ Apply barrier checks at each time step.
521
+
522
+ For discrete monitoring, only check at observation times.
523
+ For continuous monitoring, check at every time step.
524
+
525
+ Args:
526
+ grid: Solution grid
527
+ x_vec: Log-price grid points
528
+ s_vec: Price grid points
529
+ t_idx: Current time index
530
+ tau: Time remaining to maturity
531
+ product: Barrier option
532
+ pricing_env: Pricing environment
533
+ """
534
+ # For continuous monitoring, always apply barrier
535
+ # For discrete, only at observation times
536
+ if product.observation_type == ObservationType.DISCRETE:
537
+ if t_idx not in self._observation_indices:
538
+ return
539
+
540
+ total_tau = (
541
+ self._total_tau
542
+ if self._total_tau > 0
543
+ else product.get_maturity(pricing_env)
544
+ )
545
+ current_time = self._current_time(total_tau, tau)
546
+ schedule_records = self._schedule_records.get(t_idx)
547
+
548
+ if schedule_records:
549
+ for rec in schedule_records:
550
+ barrier = rec.barrier if rec.barrier is not None else product.barrier
551
+ cashflow_value = self._cashflow_value_at_time(
552
+ pricing_env=pricing_env,
553
+ cashflow=rec.payoff,
554
+ current_time=current_time,
555
+ settlement_time=rec.settlement_time,
556
+ )
557
+ if self._schedule_aggregation == ObservationAggregation.ACCUMULATE:
558
+ if product.is_up_barrier:
559
+ grid[s_vec >= barrier, t_idx] += cashflow_value
560
+ else:
561
+ grid[s_vec <= barrier, t_idx] += cashflow_value
562
+ else:
563
+ if product.is_up_barrier:
564
+ grid[s_vec >= barrier, t_idx] = cashflow_value
565
+ else:
566
+ grid[s_vec <= barrier, t_idx] = cashflow_value
567
+ # Stop-first-hit semantics: once applied, exit early
568
+ return
569
+ return
570
+
571
+ settlement_time = current_time if product.pay_at_hit else total_tau
572
+ rebate_value = self._cashflow_value_at_time(
573
+ pricing_env=pricing_env,
574
+ cashflow=product.rebate,
575
+ current_time=current_time,
576
+ settlement_time=settlement_time,
577
+ )
578
+
579
+ # Apply barrier knockout
580
+ if product.is_up_barrier:
581
+ # Up barrier: knockout at high prices
582
+ grid[s_vec >= product.barrier, t_idx] = rebate_value
583
+ else:
584
+ # Down barrier: knockout at low prices
585
+ grid[s_vec <= product.barrier, t_idx] = rebate_value
586
+
587
+ def _get_barriers(self, product: BaseEquityProduct) -> List[float]:
588
+ """Include schedule-specific barriers when building spatial bounds."""
589
+ barriers = super()._get_barriers(product)
590
+ schedule = getattr(product, "observation_schedule", None)
591
+ if schedule is not None:
592
+ for rec in schedule.records:
593
+ if rec.barrier is not None:
594
+ barriers.append(rec.barrier)
595
+ return barriers
596
+
597
+ def _build_grids(
598
+ self,
599
+ product: BaseEquityProduct,
600
+ pricing_env: PricingEnvironment,
601
+ spot: float,
602
+ sigma: float,
603
+ tau: float,
604
+ r: float,
605
+ q: float,
606
+ ):
607
+ """
608
+ Build grids and setup observation indices for discrete monitoring.
609
+ """
610
+ result = super()._build_grids(product, pricing_env, spot, sigma, tau, r, q)
611
+ x_vec, s_vec, dx_vec, t_vec, dt_vec = result
612
+
613
+ # Setup observation time indices for discrete monitoring
614
+ self._total_tau = tau
615
+ self._observation_indices.clear()
616
+ self._schedule_records.clear()
617
+ self._schedule_aggregation = ObservationAggregation.STOP_FIRST_HIT
618
+ self._terminal_schedule_records = []
619
+ self._has_terminal_observation = False
620
+
621
+ schedule = getattr(product, "observation_schedule", None)
622
+ if schedule is not None:
623
+ resolved_records = schedule.resolve(
624
+ pricing_env=pricing_env,
625
+ default_barrier=product.barrier,
626
+ default_payoff=product.rebate,
627
+ require_single=True,
628
+ )
629
+ self._schedule_aggregation = schedule.aggregation_mode
630
+ if self._schedule_aggregation in (
631
+ ObservationAggregation.BEST,
632
+ ObservationAggregation.WORST,
633
+ ):
634
+ raise PricingError(
635
+ f"PDE solver does not support aggregation mode {self._schedule_aggregation.value}"
636
+ )
637
+ for rec in resolved_records:
638
+ if is_close(rec.observation_time, 0.0):
639
+ idx = 0
640
+ self._observation_indices.add(idx)
641
+ self._schedule_records.setdefault(idx, []).append(rec)
642
+ elif is_close(rec.observation_time, tau):
643
+ self._terminal_schedule_records.append(rec)
644
+ self._has_terminal_observation = True
645
+ elif 0.0 < rec.observation_time < tau:
646
+ idx = int(np.argmin(np.abs(t_vec - rec.observation_time)))
647
+ self._observation_indices.add(idx)
648
+ self._schedule_records.setdefault(idx, []).append(rec)
649
+ elif (
650
+ hasattr(product, "observation_type")
651
+ and product.observation_type == ObservationType.DISCRETE
652
+ and hasattr(product, "observation_dates")
653
+ and product.observation_dates is not None
654
+ ):
655
+ for obs_time in product.observation_dates:
656
+ if is_close(obs_time, 0.0):
657
+ self._observation_indices.add(0)
658
+ elif is_close(obs_time, tau):
659
+ self._has_terminal_observation = True
660
+ elif 0.0 < obs_time < tau:
661
+ idx = int(np.argmin(np.abs(t_vec - obs_time)))
662
+ self._observation_indices.add(idx)
663
+
664
+ return result
665
+
666
+ def get_critical_points(
667
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
668
+ ) -> List[float]:
669
+ """
670
+ Get critical prices for grid concentration.
671
+
672
+ For barrier options, both strike and barrier are critical.
673
+
674
+ Args:
675
+ product: Barrier option
676
+ pricing_env: Pricing environment
677
+
678
+ Returns:
679
+ List containing strike and barrier
680
+ """
681
+ points = [product.strike, product.barrier]
682
+ schedule = getattr(product, "observation_schedule", None)
683
+ if schedule is not None:
684
+ for rec in schedule.records:
685
+ if rec.barrier is not None:
686
+ points.append(rec.barrier)
687
+ # sort and make unique before return
688
+ points = sorted(set(points))
689
+ return points
690
+
691
+ def __repr__(self):
692
+ return "BarrierPDESolver()"