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,750 @@
1
+ """
2
+ Spatial grid utilities for PDE solvers.
3
+
4
+ Provides methods for generating spatial discretizations in log-price space,
5
+ including the Tavella-Randall transformation for concentrating grid points
6
+ near critical prices (strikes, barriers, etc.).
7
+
8
+ The beta parameter in Tavella-Randall controls grid concentration:
9
+ - Smaller beta = more concentration near critical point
10
+ - Larger beta = more uniform grid
11
+
12
+ Beta is automatically calculated to achieve a target local spacing (epsilon)
13
+ near the critical point using bisection. Default epsilon values:
14
+ - Pricing only: 0.5% (eps_crit=0.005)
15
+ - Pricing + Greeks: 0.2-0.3% (eps_crit=0.002-0.003)
16
+ """
17
+
18
+ import numpy as np
19
+ from typing import List, Optional, Tuple
20
+
21
+
22
+ class SpatialGrid:
23
+ """
24
+ Static utility class for generating spatial grids.
25
+
26
+ All methods work in log-price space (x = ln(S)) for numerical stability.
27
+ The Tavella-Randall transformation allows concentration of grid points
28
+ near critical prices like strikes and barriers.
29
+
30
+ The transformation is:
31
+ x(w) = x_crit + beta * sinh(c1*(1-w) + c2*w), w in [0, 1]
32
+
33
+ where:
34
+ c1 = arcsinh((x_min - x_crit) / beta)
35
+ c2 = arcsinh((x_max - x_crit) / beta)
36
+
37
+ The local spacing near the critical point is approximately:
38
+ dx_crit ≈ beta * (c2 - c1) / N
39
+
40
+ Beta is chosen to achieve a target relative spacing epsilon:
41
+ dx_crit ≈ ln(1 + epsilon) ≈ epsilon
42
+ """
43
+
44
+ # Default target relative spacing near critical point
45
+ # 0.3% gives good pricing + Greeks accuracy
46
+ DEFAULT_EPSILON_CRIT = 0.003
47
+
48
+ # Maximum allowed ratio of max/min grid spacing (tail coarseness guard)
49
+ DEFAULT_MAX_DX_RATIO = 100.0
50
+
51
+ # Bisection parameters for beta calculation
52
+ _BISECTION_ITERS = 80
53
+ _BETA_LO = 1e-12
54
+
55
+ @staticmethod
56
+ def _beta_for_target_dx(
57
+ x_min: float,
58
+ x_max: float,
59
+ x_crit: float,
60
+ num_points: int,
61
+ dx_target: float,
62
+ beta_lo: float = _BETA_LO,
63
+ beta_hi: Optional[float] = None,
64
+ iters: int = _BISECTION_ITERS,
65
+ ) -> float:
66
+ """
67
+ Solve for beta to achieve a target local spacing near the critical point.
68
+
69
+ Uses bisection to find beta such that:
70
+ dx_crit = beta * (c2 - c1) / N ≈ dx_target
71
+
72
+ where N = num_points - 1, and c1, c2 are the Tavella-Randall parameters.
73
+
74
+ Args:
75
+ x_min: Minimum log-price
76
+ x_max: Maximum log-price
77
+ x_crit: Critical log-price (concentration point)
78
+ num_points: Number of grid points
79
+ dx_target: Target local spacing near critical point (in log-space)
80
+ beta_lo: Lower bound for bisection (default: 1e-12)
81
+ beta_hi: Upper bound for bisection (default: auto-calculated)
82
+ iters: Maximum bisection iterations (default: 80)
83
+
84
+ Returns:
85
+ Beta value that achieves (approximately) the target spacing
86
+ """
87
+ N = num_points - 1
88
+ if beta_hi is None:
89
+ # Large enough that grid is near-uniform
90
+ beta_hi = 1e3 * (x_max - x_min)
91
+
92
+ def dxcrit(beta: float) -> float:
93
+ """Compute local spacing at critical point for given beta."""
94
+ c1 = np.arcsinh((x_min - x_crit) / beta)
95
+ c2 = np.arcsinh((x_max - x_crit) / beta)
96
+ return beta * (c2 - c1) / N
97
+
98
+ # Check bracket validity
99
+ f_lo = dxcrit(beta_lo) - dx_target
100
+ f_hi = dxcrit(beta_hi) - dx_target
101
+
102
+ if f_lo > 0:
103
+ # dx_target is smaller than achievable with maximum concentration
104
+ return beta_lo
105
+ if f_hi < 0:
106
+ # dx_target is larger than uniform spacing; use near-uniform
107
+ return beta_hi
108
+
109
+ # Bisection search
110
+ lo, hi = beta_lo, beta_hi
111
+ for _ in range(iters):
112
+ mid = 0.5 * (lo + hi)
113
+ f_mid = dxcrit(mid) - dx_target
114
+ if f_mid >= 0:
115
+ hi = mid
116
+ else:
117
+ lo = mid
118
+
119
+ return 0.5 * (lo + hi)
120
+
121
+ @staticmethod
122
+ def _compute_dx_at_crit(
123
+ x_min: float, x_max: float, x_crit: float, num_points: int, beta: float
124
+ ) -> float:
125
+ """
126
+ Compute the local grid spacing at the critical point.
127
+
128
+ This is useful for verifying that the grid achieves the target spacing.
129
+
130
+ Args:
131
+ x_min: Minimum log-price
132
+ x_max: Maximum log-price
133
+ x_crit: Critical log-price
134
+ num_points: Number of grid points
135
+ beta: Concentration parameter
136
+
137
+ Returns:
138
+ Local spacing dx at the critical point
139
+ """
140
+ N = num_points - 1
141
+ c1 = np.arcsinh((x_min - x_crit) / beta)
142
+ c2 = np.arcsinh((x_max - x_crit) / beta)
143
+ return beta * (c2 - c1) / N
144
+
145
+ @staticmethod
146
+ def check_grid_quality(
147
+ dx_vec: np.ndarray, max_ratio: float = DEFAULT_MAX_DX_RATIO
148
+ ) -> Tuple[bool, float]:
149
+ """
150
+ Check grid quality by examining the tail coarseness ratio.
151
+
152
+ A very high ratio of max/min spacing indicates extreme concentration
153
+ that may cause numerical issues in the tails.
154
+
155
+ Args:
156
+ dx_vec: Array of grid spacings
157
+ max_ratio: Maximum acceptable ratio of max/min spacing
158
+
159
+ Returns:
160
+ Tuple of (is_acceptable, actual_ratio)
161
+ """
162
+ if len(dx_vec) == 0:
163
+ return True, 1.0
164
+
165
+ dx_min = np.min(dx_vec)
166
+ dx_max = np.max(dx_vec)
167
+
168
+ if dx_min <= 0:
169
+ return False, np.inf
170
+
171
+ ratio = dx_max / dx_min
172
+ return ratio <= max_ratio, ratio
173
+
174
+ @staticmethod
175
+ def build_uniform_log(
176
+ s_min: float, s_max: float, num_points: int
177
+ ) -> Tuple[np.ndarray, np.ndarray, float]:
178
+ """
179
+ Build a uniform grid in log-price space.
180
+
181
+ Args:
182
+ s_min: Minimum spot price (S space)
183
+ s_max: Maximum spot price (S space)
184
+ num_points: Number of grid points
185
+
186
+ Returns:
187
+ Tuple of (x_vec, s_vec, dx):
188
+ - x_vec: Log-price grid points, shape (num_points,)
189
+ - s_vec: Price grid points (exp(x_vec)), shape (num_points,)
190
+ - dx: Uniform grid spacing in log-space
191
+ """
192
+ if s_min <= 0:
193
+ raise ValueError(f"s_min must be positive, got {s_min}")
194
+ if s_max <= s_min:
195
+ raise ValueError(f"s_max ({s_max}) must be greater than s_min ({s_min})")
196
+ if num_points < 3:
197
+ raise ValueError(f"num_points must be at least 3, got {num_points}")
198
+
199
+ x_min = np.log(s_min)
200
+ x_max = np.log(s_max)
201
+
202
+ x_vec = np.linspace(x_min, x_max, num_points)
203
+ s_vec = np.exp(x_vec)
204
+ dx = (x_max - x_min) / (num_points - 1)
205
+
206
+ return x_vec, s_vec, dx
207
+
208
+ @staticmethod
209
+ def build_tavella_randall(
210
+ s_min: float,
211
+ s_max: float,
212
+ num_points: int,
213
+ critical_point: float,
214
+ beta: Optional[float] = None,
215
+ eps_crit: Optional[float] = None,
216
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
217
+ """
218
+ Build a non-uniform grid using Tavella-Randall transformation.
219
+
220
+ The transformation concentrates grid points near a critical price
221
+ (e.g., strike or barrier) while maintaining smooth transitions.
222
+
223
+ The transformation is:
224
+ x(w) = x_crit + beta * sinh(c1*(1-w) + c2*w), w = i/N
225
+
226
+ where:
227
+ c1 = arcsinh((x_min - x_crit) / beta)
228
+ c2 = arcsinh((x_max - x_crit) / beta)
229
+
230
+ Beta controls concentration strength (smaller = more concentrated).
231
+ When beta is None, it is automatically calculated to achieve a target
232
+ relative spacing eps_crit near the critical point.
233
+
234
+ Args:
235
+ s_min: Minimum spot price (S space)
236
+ s_max: Maximum spot price (S space)
237
+ num_points: Number of grid points
238
+ critical_point: Price to concentrate around (e.g., strike)
239
+ beta: Concentration parameter. If None, auto-calculated from eps_crit.
240
+ eps_crit: Target relative spacing near critical point (e.g., 0.003 for 0.3%).
241
+ Only used when beta is None. Default: 0.003 (0.3%).
242
+
243
+ Returns:
244
+ Tuple of (x_vec, s_vec, dx_vec):
245
+ - x_vec: Log-price grid points
246
+ - s_vec: Price grid points
247
+ - dx_vec: Variable grid spacings
248
+
249
+ Notes:
250
+ The local spacing near the critical point is approximately:
251
+ dx_crit ≈ beta * (c2 - c1) / N
252
+
253
+ In spot space, this corresponds to:
254
+ dS/S ≈ dx_crit ≈ eps_crit
255
+
256
+ Typical values for eps_crit:
257
+ - Pricing only: 0.005 (0.5%)
258
+ - Pricing + Greeks: 0.002-0.003 (0.2-0.3%)
259
+ """
260
+ if s_min <= 0:
261
+ raise ValueError(f"s_min must be positive, got {s_min}")
262
+ if s_max <= s_min:
263
+ raise ValueError(f"s_max ({s_max}) must be greater than s_min ({s_min})")
264
+ if num_points < 3:
265
+ raise ValueError(f"num_points must be at least 3, got {num_points}")
266
+ if critical_point < s_min or critical_point > s_max:
267
+ raise ValueError(
268
+ f"critical_point ({critical_point}) must be in [{s_min}, {s_max}]"
269
+ )
270
+
271
+ # Convert to log space
272
+ x_min = np.log(s_min)
273
+ x_max = np.log(s_max)
274
+ x_crit = np.log(critical_point)
275
+
276
+ # Auto-calculate beta if not provided
277
+ # Beta is solved via bisection to achieve target local spacing
278
+ if beta is None:
279
+ if eps_crit is None:
280
+ eps_crit = SpatialGrid.DEFAULT_EPSILON_CRIT
281
+
282
+ # Target spacing in log-space: ln(1 + eps) ≈ eps for small eps
283
+ dx_target = np.log1p(eps_crit)
284
+
285
+ beta = SpatialGrid._beta_for_target_dx(
286
+ x_min, x_max, x_crit, num_points, dx_target
287
+ )
288
+
289
+ # Calculate Tavella-Randall transformation parameters
290
+ c1 = np.arcsinh((x_min - x_crit) / beta)
291
+ c2 = np.arcsinh((x_max - x_crit) / beta)
292
+
293
+ # Generate grid
294
+ N = num_points - 1
295
+ i_vec = np.arange(num_points)
296
+
297
+ # Transformation: x_i = x_crit + beta * sinh(c1 * (1 - i/N) + c2 * (i/N))
298
+ weight = i_vec / N
299
+ x_vec = x_crit + beta * np.sinh(c1 * (1.0 - weight) + c2 * weight)
300
+
301
+ # Convert back to price space
302
+ s_vec = np.exp(x_vec)
303
+
304
+ # Calculate variable grid spacings
305
+ dx_vec = np.diff(x_vec)
306
+
307
+ return x_vec, s_vec, dx_vec
308
+
309
+ # ============================================================
310
+ # ODE-based Tavella-Randall helper methods for multiple critical points
311
+ # ============================================================
312
+
313
+ @staticmethod
314
+ def _ode_f(y: float, A: float, beta: float, crits: np.ndarray) -> float:
315
+ """
316
+ Right-hand side of the ODE for multi-critical-point grid generation.
317
+
318
+ ODE: dY/de = A * (sum_k J_k^-2)^(-0.5)
319
+ where J_k = sqrt(beta^2 + (Y - B_k)^2)
320
+
321
+ Args:
322
+ y: Current position in log-space
323
+ A: Scaling constant (found via shooting)
324
+ beta: Concentration parameter
325
+ crits: Array of critical points in log-space
326
+
327
+ Returns:
328
+ dY/de value at current position
329
+ """
330
+ j_sq = beta * beta + (y - crits) ** 2
331
+ s = np.sum(1.0 / j_sq)
332
+ return A / np.sqrt(s)
333
+
334
+ @staticmethod
335
+ def _ode_rk4_step(
336
+ y: float, h: float, A: float, beta: float, crits: np.ndarray
337
+ ) -> float:
338
+ """
339
+ Single RK4 (Runge-Kutta 4th order) integration step.
340
+
341
+ Args:
342
+ y: Current position
343
+ h: Step size in parameter space e
344
+ A: Scaling constant
345
+ beta: Concentration parameter
346
+ crits: Critical points array
347
+
348
+ Returns:
349
+ New position after one RK4 step
350
+ """
351
+ f = SpatialGrid._ode_f
352
+ k1 = f(y, A, beta, crits)
353
+ k2 = f(y + 0.5 * h * k1, A, beta, crits)
354
+ k3 = f(y + 0.5 * h * k2, A, beta, crits)
355
+ k4 = f(y + h * k3, A, beta, crits)
356
+ return y + (h / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
357
+
358
+ @staticmethod
359
+ def _ode_integrate(
360
+ y_min: float, N: int, A: float, beta: float, crits: np.ndarray
361
+ ) -> np.ndarray:
362
+ """
363
+ Integrate ODE from e=0 to e=1 with N steps.
364
+
365
+ Args:
366
+ y_min: Starting position (Y(0) = y_min)
367
+ N: Number of steps (generates N+1 points)
368
+ A: Scaling constant
369
+ beta: Concentration parameter
370
+ crits: Critical points array
371
+
372
+ Returns:
373
+ Array of N+1 grid points
374
+ """
375
+ h = 1.0 / N
376
+ mesh = np.empty(N + 1, dtype=float)
377
+ mesh[0] = y_min
378
+ y = y_min
379
+ for i in range(1, N + 1):
380
+ y = SpatialGrid._ode_rk4_step(y, h, A, beta, crits)
381
+ mesh[i] = y
382
+ return mesh
383
+
384
+ @staticmethod
385
+ def _ode_find_A(
386
+ y_min: float, y_max: float, N: int, beta: float, crits: np.ndarray
387
+ ) -> float:
388
+ """
389
+ Find scaling constant A via shooting method (bisection).
390
+
391
+ Solves for A such that Y(1) = y_max given Y(0) = y_min.
392
+
393
+ Args:
394
+ y_min: Lower boundary
395
+ y_max: Upper boundary
396
+ N: Number of grid intervals
397
+ beta: Concentration parameter
398
+ crits: Critical points array
399
+
400
+ Returns:
401
+ Scaling constant A
402
+ """
403
+ a_lo = 0.0
404
+ a_hi = max(4.0 * abs(y_max), abs(y_max - y_min))
405
+
406
+ def residual(A: float) -> float:
407
+ mesh = SpatialGrid._ode_integrate(y_min, N, A, beta, crits)
408
+ return mesh[-1] - y_max
409
+
410
+ f_lo = residual(a_lo)
411
+ f_hi = residual(a_hi)
412
+
413
+ # Expand bracket if needed
414
+ for _ in range(20):
415
+ if f_lo * f_hi <= 0:
416
+ break
417
+ a_hi *= 2.0
418
+ f_hi = residual(a_hi)
419
+
420
+ # Bisection
421
+ tol = 1e-10
422
+ for _ in range(100):
423
+ if abs(a_hi - a_lo) <= tol:
424
+ break
425
+ a_mid = 0.5 * (a_lo + a_hi)
426
+ f_mid = residual(a_mid)
427
+ if f_mid == 0.0:
428
+ return a_mid
429
+ if f_lo * f_mid < 0:
430
+ a_hi, f_hi = a_mid, f_mid
431
+ else:
432
+ a_lo, f_lo = a_mid, f_mid
433
+
434
+ return 0.5 * (a_lo + a_hi)
435
+
436
+ @staticmethod
437
+ def _calculate_beta_for_multi_crit(
438
+ x_min: float,
439
+ x_max: float,
440
+ x_crits: np.ndarray,
441
+ num_points: int,
442
+ eps_crit: float,
443
+ use_heuristic_beta: bool = False,
444
+ ) -> float:
445
+ """
446
+ Find beta that achieves target spacing at critical points.
447
+
448
+ Uses bisection to find beta such that the minimum spacing
449
+ near any critical point is approximately eps_crit.
450
+
451
+ Optimization: Uses a coarse grid for the expensive search,
452
+ scaling the target spacing accordingly.
453
+ """
454
+ if use_heuristic_beta:
455
+ M = len(x_crits)
456
+ # Heuristic: beta ~ ln(1+eps) * sqrt(M/12)
457
+ # Adjusts concentration based on number of observation dates
458
+ beta = np.log1p(eps_crit) * np.sqrt(max(M, 1) / 12.0)
459
+
460
+ # Check grid quality and adjust if necessary
461
+ # Ensure spacing ratio is acceptable (avoid tail coarseness)
462
+ N_check = num_points - 1
463
+ max_check_iters = 5
464
+
465
+ for _ in range(max_check_iters):
466
+ # Generate grid to check spacing ratio
467
+ A = SpatialGrid._ode_find_A(x_min, x_max, N_check, beta, x_crits)
468
+ mesh = SpatialGrid._ode_integrate(x_min, N_check, A, beta, x_crits)
469
+ dx = np.diff(mesh)
470
+
471
+ is_acceptable, _ = SpatialGrid.check_grid_quality(dx, max_ratio=100.0)
472
+ if is_acceptable:
473
+ break
474
+
475
+ # If ratio too high, increase beta (smoother grid)
476
+ beta *= 1.2
477
+
478
+ return beta
479
+
480
+ dx_target = np.log1p(eps_crit)
481
+ N = num_points - 1
482
+
483
+ # Use coarse grid for beta search to improve performance
484
+ # Beta is a shape parameter, so we can estimate it on a coarser mesh
485
+ N_coarse = min(N, 64)
486
+ dx_target_adj = dx_target * (N / N_coarse)
487
+
488
+ # Bracket for beta (log-scale search)
489
+ beta_lo = 1e-6 * (x_max - x_min)
490
+ beta_hi = 10.0 * (x_max - x_min)
491
+
492
+ def min_spacing_near_crits(beta: float) -> float:
493
+ A = SpatialGrid._ode_find_A(x_min, x_max, N_coarse, beta, x_crits)
494
+ mesh = SpatialGrid._ode_integrate(x_min, N_coarse, A, beta, x_crits)
495
+ dx = np.diff(mesh)
496
+
497
+ # Find minimum spacing near any critical point
498
+ min_dx = float("inf")
499
+ for xc in x_crits:
500
+ idx = np.searchsorted(mesh, xc)
501
+ idx = max(0, min(idx, len(dx) - 1))
502
+ if idx > 0:
503
+ min_dx = min(min_dx, dx[idx - 1])
504
+ if idx < len(dx):
505
+ min_dx = min(min_dx, dx[idx])
506
+ return min_dx
507
+
508
+ # Bisection with geometric mean (log-scale)
509
+ # Reduced iterations (20) as precise beta is not critical for grid quality
510
+ for _ in range(20):
511
+ beta_mid = np.sqrt(beta_lo * beta_hi)
512
+ current_dx = min_spacing_near_crits(beta_mid)
513
+ if current_dx > dx_target_adj:
514
+ beta_hi = beta_mid # Need tighter concentration
515
+ else:
516
+ beta_lo = beta_mid # Loosen concentration
517
+
518
+ return np.sqrt(beta_lo * beta_hi)
519
+
520
+ @staticmethod
521
+ def _snap_critical_points(x_vec: np.ndarray, x_crits: np.ndarray) -> np.ndarray:
522
+ """
523
+ Snap nearest grid points to exact critical values.
524
+
525
+ Ensures critical points are exactly included in the grid,
526
+ which is important for barrier boundary conditions.
527
+
528
+ Args:
529
+ x_vec: Grid points array
530
+ x_crits: Critical points to snap to
531
+
532
+ Returns:
533
+ Grid with critical points exactly included
534
+ """
535
+ x_vec = x_vec.copy()
536
+ for xc in x_crits:
537
+ # Find nearest grid index
538
+ idx = np.argmin(np.abs(x_vec - xc))
539
+ # Snap to exact critical value
540
+ x_vec[idx] = xc
541
+ # Re-sort to maintain monotonicity (shouldn't change much)
542
+ x_vec = np.sort(x_vec)
543
+ return x_vec
544
+
545
+ @staticmethod
546
+ def build_tavella_randall_multi(
547
+ s_min: float,
548
+ s_max: float,
549
+ num_points: int,
550
+ critical_points: List[float],
551
+ beta: Optional[float] = None,
552
+ eps_crit: Optional[float] = None,
553
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
554
+ """
555
+ Build a non-uniform grid with concentration near multiple critical points.
556
+
557
+ Uses the ODE-based Tavella-Randall method:
558
+ dY/de = A * (sum_k J_k^-2)^(-0.5)
559
+ where J_k = sqrt(beta^2 + (Y - B_k)^2), solved via RK4 with shooting.
560
+
561
+ This method provides C-infinity smooth grids that naturally concentrate
562
+ around all critical points simultaneously.
563
+
564
+ Args:
565
+ s_min: Minimum spot price (S space)
566
+ s_max: Maximum spot price (S space)
567
+ num_points: Total number of grid points
568
+ critical_points: List of prices to concentrate around
569
+ beta: Concentration parameter. If None, auto-calculated from eps_crit.
570
+ eps_crit: Target relative spacing near critical points (e.g., 0.003 for 0.3%).
571
+ Only used when beta is None. Default: 0.003 (0.3%).
572
+
573
+ Returns:
574
+ Tuple of (x_vec, s_vec, dx_vec):
575
+ - x_vec: Log-price grid points
576
+ - s_vec: Price grid points
577
+ - dx_vec: Variable grid spacings
578
+
579
+ Notes:
580
+ Critical points are exactly included in the final grid via post-processing,
581
+ which is important for barrier boundary conditions.
582
+ """
583
+ # Validation
584
+ if s_min <= 0:
585
+ raise ValueError(f"s_min must be positive, got {s_min}")
586
+ if s_max <= s_min:
587
+ raise ValueError(f"s_max ({s_max}) must be greater than s_min ({s_min})")
588
+ if num_points < 3:
589
+ raise ValueError(f"num_points must be at least 3, got {num_points}")
590
+
591
+ # Handle empty critical points -> uniform grid
592
+ if not critical_points:
593
+ x_vec, s_vec, dx = SpatialGrid.build_uniform_log(s_min, s_max, num_points)
594
+ dx_vec = np.full(num_points - 1, dx)
595
+ return x_vec, s_vec, dx_vec
596
+
597
+ # Filter and sort critical points within bounds
598
+ crits = np.array([c for c in critical_points if s_min < c < s_max])
599
+ if len(crits) == 0:
600
+ # No valid critical points, use uniform grid
601
+ x_vec, s_vec, dx = SpatialGrid.build_uniform_log(s_min, s_max, num_points)
602
+ dx_vec = np.full(num_points - 1, dx)
603
+ return x_vec, s_vec, dx_vec
604
+
605
+ crits = np.sort(np.unique(crits))
606
+
607
+ # If only one critical point, use single Tavella-Randall (optimization)
608
+ if len(crits) == 1:
609
+ return SpatialGrid.build_tavella_randall(
610
+ s_min, s_max, num_points, crits[0], beta, eps_crit
611
+ )
612
+
613
+ # Convert to log-space
614
+ x_min = np.log(s_min)
615
+ x_max = np.log(s_max)
616
+ x_crits = np.log(crits)
617
+ N = num_points - 1
618
+
619
+ # Auto-calculate beta if not provided
620
+ if beta is None:
621
+ if eps_crit is None:
622
+ eps_crit = SpatialGrid.DEFAULT_EPSILON_CRIT
623
+ beta = SpatialGrid._calculate_beta_for_multi_crit(
624
+ x_min, x_max, x_crits, num_points, eps_crit
625
+ )
626
+
627
+ # Safeguard against extreme concentration
628
+ beta = max(beta, 1e-10 * (x_max - x_min))
629
+
630
+ # Find scaling constant A via shooting method
631
+ A = SpatialGrid._ode_find_A(x_min, x_max, N, beta, x_crits)
632
+
633
+ # Generate mesh via RK4 integration
634
+ x_vec = SpatialGrid._ode_integrate(x_min, N, A, beta, x_crits)
635
+
636
+ # Snap critical points to grid (ensure exact inclusion)
637
+ x_vec = SpatialGrid._snap_critical_points(x_vec, x_crits)
638
+
639
+ # Convert to price space and compute spacings
640
+ s_vec = np.exp(x_vec)
641
+ dx_vec = np.diff(x_vec)
642
+
643
+ return x_vec, s_vec, dx_vec
644
+
645
+ @staticmethod
646
+ def calculate_auto_bounds(
647
+ spot: float,
648
+ sigma: float,
649
+ tau: float,
650
+ r: float = 0.0,
651
+ q: float = 0.0,
652
+ strike: Optional[float] = None,
653
+ barriers: Optional[List[float]] = None,
654
+ num_std: float = 4.0,
655
+ ) -> Tuple[float, float]:
656
+ """
657
+ Automatically calculate appropriate grid bounds.
658
+
659
+ Uses volatility-based expansion to ensure the grid captures
660
+ the relevant price range for the option.
661
+
662
+ Args:
663
+ spot: Current spot price
664
+ sigma: Volatility
665
+ tau: Time to maturity in years
666
+ r: Risk-free rate (default: 0)
667
+ q: Dividend yield (default: 0)
668
+ strike: Strike price (optional, ensures it's in bounds)
669
+ barriers: Barrier prices (optional, ensures they're in bounds)
670
+ num_std: Number of standard deviations for bounds (default: 4)
671
+
672
+ Returns:
673
+ Tuple of (s_min, s_max)
674
+ """
675
+ if spot <= 0:
676
+ raise ValueError(f"spot must be positive, got {spot}")
677
+ if sigma <= 0:
678
+ raise ValueError(f"sigma must be positive, got {sigma}")
679
+ if tau <= 0:
680
+ raise ValueError(f"tau must be positive, got {tau}")
681
+
682
+ # Calculate expected drift and volatility range
683
+ drift = (r - q) * tau
684
+ vol_range = num_std * sigma * np.sqrt(tau)
685
+
686
+ # Calculate bounds
687
+ s_min = spot * np.exp(drift - vol_range)
688
+ s_max = spot * np.exp(drift + vol_range)
689
+
690
+ # Ensure strike is within bounds
691
+ if strike is not None and strike > 0:
692
+ s_min = min(s_min, strike * 0.5)
693
+ s_max = max(s_max, strike * 2.0)
694
+
695
+ # Ensure barriers are within bounds
696
+ if barriers is not None:
697
+ for b in barriers:
698
+ if b > 0:
699
+ s_min = min(s_min, b * 0.8)
700
+ s_max = max(s_max, b * 1.2)
701
+
702
+ # Sanity bounds
703
+ s_min = max(s_min, spot * 0.01) # At least 1% of spot
704
+ s_max = min(s_max, spot * 100.0) # At most 100x spot
705
+
706
+ return s_min, s_max
707
+
708
+ @staticmethod
709
+ def build(
710
+ s_min: float,
711
+ s_max: float,
712
+ num_points: int,
713
+ critical_points: Optional[List[float]] = None,
714
+ use_adaptive: bool = False,
715
+ beta: Optional[float] = None,
716
+ eps_crit: Optional[float] = None,
717
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
718
+ """
719
+ Build a spatial grid using the appropriate method.
720
+
721
+ This is a convenience method that chooses between uniform and
722
+ Tavella-Randall based on the parameters.
723
+
724
+ Args:
725
+ s_min: Minimum spot price
726
+ s_max: Maximum spot price
727
+ num_points: Number of grid points
728
+ critical_points: Points to concentrate around (optional)
729
+ use_adaptive: Whether to use Tavella-Randall transformation
730
+ beta: Concentration parameter for Tavella-Randall. If None, auto-calculated.
731
+ eps_crit: Target relative spacing near critical points (e.g., 0.003 for 0.3%).
732
+ Only used when beta is None and use_adaptive is True.
733
+
734
+ Returns:
735
+ Tuple of (x_vec, s_vec, dx_vec)
736
+ """
737
+ if use_adaptive and critical_points:
738
+ return SpatialGrid.build_tavella_randall_multi(
739
+ s_min, s_max, num_points, critical_points, beta, eps_crit
740
+ )
741
+ elif use_adaptive and not critical_points:
742
+ # Use midpoint as critical point
743
+ mid = np.sqrt(s_min * s_max) # Geometric mean
744
+ return SpatialGrid.build_tavella_randall(
745
+ s_min, s_max, num_points, mid, beta, eps_crit
746
+ )
747
+ else:
748
+ x_vec, s_vec, dx = SpatialGrid.build_uniform_log(s_min, s_max, num_points)
749
+ dx_vec = np.full(num_points - 1, dx)
750
+ return x_vec, s_vec, dx_vec