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,614 @@
1
+ """
2
+ Quadrature pricing engine for Phoenix (autocallable) options.
3
+
4
+ Implements a two-state (knocked-in / not-knocked-in) quadrature recursion
5
+ with coupon jumps at observation times.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ from typing import Optional, Sequence
12
+
13
+ import numpy as np
14
+
15
+ from quantark.asset.equity.engine.quad.quad_math import QuadratureMath
16
+ from quantark.asset.equity.engine.quad.snowball_quad_engine import SnowballQuadEngine
17
+ from quantark.asset.equity.param import MCParams, QuadParams
18
+ from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
19
+ from quantark.asset.equity.product.option.phoenix_option import PhoenixOption
20
+ from quantark.priceenv import PricingEnvironment
21
+ from quantark.util.enum import CouponPayType, ObservationType
22
+ from quantark.util.exceptions import PricingError, ValidationError
23
+ from quantark.util.numerical import Tolerance, is_close, is_zero, safe_log
24
+
25
+
26
+ class PhoenixQuadEngine(SnowballQuadEngine):
27
+ """
28
+ Quadrature pricing engine for Phoenix options with coupon jumps.
29
+ """
30
+
31
+ def __init__(self, params: Optional[QuadParams] = None):
32
+ super().__init__(params=params)
33
+
34
+ def _validate_product(self, product: PhoenixOption) -> None:
35
+ if product.barrier_config.ko_observation_type != ObservationType.DISCRETE:
36
+ raise PricingError("PhoenixQuadEngine requires discrete KO monitoring.")
37
+
38
+ def price(
39
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
40
+ ) -> float:
41
+ if not isinstance(product, PhoenixOption):
42
+ raise PricingError(
43
+ f"PhoenixQuadEngine only supports PhoenixOption, got {type(product).__name__}"
44
+ )
45
+ if pricing_env is None:
46
+ raise PricingError("PricingEnvironment is required for PhoenixQuadEngine.")
47
+
48
+ self._validate_product(product)
49
+
50
+ spot = pricing_env.spot
51
+ maturity = product.get_maturity(pricing_env)
52
+ if is_zero(maturity, tol=Tolerance.ZERO):
53
+ return product.get_payoff(spot, pricing_env=pricing_env)
54
+
55
+ rate = pricing_env.get_rate(maturity)
56
+ div = pricing_env.get_div_yield(maturity)
57
+ vol = pricing_env.get_vol(product.strike, maturity)
58
+ if vol <= 0:
59
+ raise ValidationError(f"Volatility must be positive, got {vol}")
60
+ if not np.isfinite(div):
61
+ raise ValidationError(f"Dividend yield must be finite, got {div}")
62
+ if vol > 5.0:
63
+ raise ValidationError(f"Volatility too high for quadrature stability: {vol}")
64
+
65
+ ko_records = product.resolve_ko_observations(pricing_env)
66
+ if not ko_records:
67
+ raise PricingError("KO observation schedule is empty for PhoenixQuadEngine.")
68
+
69
+ ki_continuous = product.has_ki_barrier and (
70
+ product.barrier_config.ki_continuous
71
+ or product.barrier_config.ki_observation_type == ObservationType.CONTINUOUS
72
+ )
73
+ ki_records: Sequence = []
74
+ if product.has_ki_barrier and not ki_continuous:
75
+ ki_records = product.resolve_ki_observations(pricing_env)
76
+ if not ki_records:
77
+ raise PricingError("KI observation schedule is empty for PhoenixQuadEngine.")
78
+
79
+ times = self._merge_times(
80
+ [rec.observation_time for rec in ko_records],
81
+ [rec.observation_time for rec in ki_records],
82
+ maturity,
83
+ )
84
+ if not times:
85
+ raise PricingError("Observation time grid is empty for PhoenixQuadEngine.")
86
+
87
+ coupon_barrier = product.coupon_config.coupon_barrier
88
+ if isinstance(coupon_barrier, list):
89
+ if len(coupon_barrier) != len(ko_records):
90
+ raise ValidationError(
91
+ "Coupon barrier schedule length does not match KO observations."
92
+ )
93
+ coupon_barriers = np.array(coupon_barrier, dtype=float)
94
+ else:
95
+ coupon_barriers = np.full(len(ko_records), float(coupon_barrier))
96
+
97
+ align_log = self._select_alignment_log(
98
+ spot,
99
+ ko_records,
100
+ coupon_barriers=coupon_barriers,
101
+ ki_records=ki_records,
102
+ product=product,
103
+ )
104
+ fft_padding_factor = self._resolve_fft_padding_factor()
105
+ fft_filter_alpha, fft_filter_power = self._resolve_fft_filter()
106
+ math_utils = QuadratureMath(
107
+ grid_x=self.params.grid_points,
108
+ spot=spot,
109
+ maturity=maturity,
110
+ vol_max=vol,
111
+ num_std_devs=self.params.num_std_devs,
112
+ align_log=align_log,
113
+ fft_padding_factor=fft_padding_factor,
114
+ fft_filter_alpha=fft_filter_alpha,
115
+ fft_filter_power=fft_filter_power,
116
+ )
117
+ grid = math_utils.grid
118
+ spot_grid = spot * np.exp(grid)
119
+ dt = self._build_dt(times)
120
+ tau = 0.5 * vol * vol * dt
121
+ if np.any(tau[1:] <= 0.0):
122
+ raise ValidationError("time step too small for quadrature solver.")
123
+
124
+ alpha = (rate - div - 0.5 * vol * vol) / (vol * vol)
125
+ beta = (rate - div - 0.5 * vol * vol) ** 2 / (vol**4) + 2.0 * rate / (
126
+ vol * vol
127
+ )
128
+
129
+ v_in = np.array(
130
+ [
131
+ product.get_maturity_payoff_v1(spot_value, pricing_env=pricing_env)
132
+ for spot_value in spot_grid
133
+ ],
134
+ dtype=float,
135
+ )
136
+ v_out = np.array(
137
+ [
138
+ product.get_maturity_payoff_v0(spot_value, pricing_env=pricing_env)
139
+ for spot_value in spot_grid
140
+ ],
141
+ dtype=float,
142
+ )
143
+
144
+ log_ki_barrier = None
145
+ if product.has_ki_barrier and ki_continuous:
146
+ if product.barrier_config.ki_barrier is None:
147
+ raise PricingError("KI barrier configuration is missing.")
148
+ if isinstance(product.barrier_config.ki_barrier, list):
149
+ raise PricingError("Continuous KI requires scalar ki_barrier.")
150
+ log_ki_barrier = safe_log(product.barrier_config.ki_barrier / spot)
151
+
152
+ ko_times = np.array([rec.observation_time for rec in ko_records], dtype=float)
153
+ period_year_fractions = np.array(
154
+ product.get_coupon_period_year_fractions(ko_times.tolist()),
155
+ dtype=float,
156
+ )
157
+ coupon_amounts = np.array(
158
+ [
159
+ product.get_coupon_payoff(i, year_fraction=period_year_fractions[i])
160
+ for i in range(len(ko_records))
161
+ ],
162
+ dtype=float,
163
+ )
164
+ coupon_cumulative = np.concatenate(([0.0], np.cumsum(coupon_amounts)))
165
+
166
+ use_memory = product.has_memory_coupon
167
+ if use_memory and len(ko_records) > 50:
168
+ raise ValidationError(
169
+ f"Too many observations ({len(ko_records)}) for Memory Phoenix Quad engine. "
170
+ "Limit is 50 to prevent performance degradation. Use MC engine instead."
171
+ )
172
+
173
+ # Initialize value function vectors.
174
+ # v_in_list[k] = Value(S, t, memory=k)
175
+ # At maturity, we initialize for the maximum possible accumulated coupons.
176
+ # Max accumulation at maturity is N (if we missed all N-1 previous and miss the last one?
177
+ # Actually, at maturity, we just pay what is due.
178
+ # Let's track surfaces based on "number of missed coupons entering the next period".
179
+ # At T (step N), we construct N+1 surfaces for having missed 0, 1, ..., N coupons.
180
+ num_obs = len(ko_records)
181
+ v_in_list = []
182
+ v_out_list = []
183
+
184
+ def accumulated_before(obs_idx: int, missed: int) -> float:
185
+ if missed <= 0 or obs_idx <= 0:
186
+ return 0.0
187
+ start = max(obs_idx - missed, 0)
188
+ return float(coupon_cumulative[obs_idx] - coupon_cumulative[start])
189
+
190
+ # Terminal condition for each memory state (base payoff; coupons added via jumps)
191
+ for k in range(num_obs + 1):
192
+ v_in_k = np.array(
193
+ [
194
+ product.get_maturity_payoff_v1(spot_value, pricing_env=pricing_env)
195
+ for spot_value in spot_grid
196
+ ],
197
+ dtype=float,
198
+ )
199
+ # V1 usually doesn't pay coupons? Checked logic: get_maturity_payoff_v1 doesn't take accumulated.
200
+ # So accumulated is ignored for V1.
201
+
202
+ v_out_k = np.array(
203
+ [
204
+ product.get_maturity_payoff_v0(
205
+ spot_value,
206
+ accumulated_coupons=0.0,
207
+ pricing_env=pricing_env,
208
+ )
209
+ for spot_value in spot_grid
210
+ ],
211
+ dtype=float,
212
+ )
213
+ v_in_list.append(v_in_k)
214
+ v_out_list.append(v_out_k)
215
+
216
+ full_p_lr, full_p_ur, full_p0 = 0, len(grid) - 1, (len(grid) - 1) % 2
217
+ omega_grid = math_utils.z_grid
218
+ disable_ko_after_ki = product.barrier_config.disable_ko_after_ki
219
+ smoothing_width = self._resolve_event_smoothing_width(math_utils, product)
220
+
221
+ for step_index in range(len(times), 0, -1):
222
+ obs_time = times[step_index - 1]
223
+
224
+ # Identify if this time step is a KO/Coupon observation
225
+ ko_weight = None
226
+ ko_index = None
227
+ ko_record = None
228
+ for idx, rec in enumerate(ko_records):
229
+ if is_close(obs_time, rec.observation_time, abs_tol=Tolerance.PRECISION):
230
+ ko_index = idx
231
+ ko_record = rec
232
+ break
233
+
234
+ # If this is an observation $i$ (0-based index `ko_index`),
235
+ # we are moving from $t_{i+1}$ (where we have valid surfaces) to $t_i$.
236
+ # The surfaces coming from diffusion are $U[k]$ for $k \in \{0, \dots, i+1\}$ (missed 0 to i+1 coupons).
237
+ # We need to construct surfaces $W[k]$ for $k \in \{0, \dots, i\}$ (missed 0 to i coupons).
238
+
239
+ if ko_record is not None and ko_index is not None:
240
+ barrier_val = coupon_barriers[ko_index]
241
+ coupon_amt = float(coupon_amounts[ko_index])
242
+
243
+ # Determine Pay/Miss weights (smoothed if enabled)
244
+ pay_weight = self._smooth_step_weight(
245
+ grid,
246
+ barrier_val,
247
+ spot,
248
+ smoothing_width,
249
+ trigger_is_down=product.is_reverse,
250
+ )
251
+ if pay_weight is None:
252
+ if product.is_reverse:
253
+ pay_mask = spot_grid <= barrier_val
254
+ else:
255
+ pay_mask = spot_grid >= barrier_val
256
+ pay_weight = pay_mask.astype(float)
257
+
258
+ ko_weight = self._smooth_step_weight(
259
+ grid,
260
+ ko_record.barrier,
261
+ spot,
262
+ smoothing_width,
263
+ trigger_is_down=product.is_reverse,
264
+ )
265
+ if ko_weight is None:
266
+ ko_mask = (
267
+ spot_grid <= ko_record.barrier
268
+ if product.is_reverse
269
+ else spot_grid >= ko_record.barrier
270
+ )
271
+ ko_weight = ko_mask.astype(float)
272
+
273
+ ko_discount = self._ko_discount(
274
+ rate, obs_time, ko_record.settlement_time
275
+ )
276
+ base_ko_payoff = float(ko_record.payoff or 0.0)
277
+
278
+ coupon_discount = 1.0
279
+ if product.coupon_config.coupon_pay_type == CouponPayType.EXPIRY:
280
+ coupon_discount = self._ko_discount(rate, obs_time, maturity)
281
+
282
+ new_v_in_list = []
283
+ new_v_out_list = []
284
+
285
+ # Max possible missed coupons entering this step is `ko_index`.
286
+ # If use_memory is False, we only have state 0.
287
+ max_k = ko_index if use_memory else 0
288
+
289
+ for k in range(max_k + 1):
290
+ # We are in state "missed k coupons" just before observation.
291
+
292
+ # Branch 1: Coupon Condition Met (Pay)
293
+ # Payoff = Current + (k * accumulated)
294
+ # Transition -> State 0 (reset memory)
295
+ accumulated_pay = (
296
+ accumulated_before(ko_index, k) if use_memory else 0.0
297
+ )
298
+ total_pay = (coupon_amt + accumulated_pay) * coupon_discount
299
+
300
+ # Continuation value if paid: comes from state 0 of the next step
301
+ # Wait, diffused surfaces `v_out_list` are indexed by *future* memory state.
302
+ # If we pay now, the future starts with 0 memory.
303
+ # So we use `v_out_list[0]`.
304
+ val_pay_out = v_out_list[0] + total_pay
305
+ val_pay_in = v_in_list[0] + total_pay
306
+
307
+ # Branch 2: Coupon Condition Missed
308
+ # Payoff = 0
309
+ # Transition -> State k+1 (accumulate)
310
+ # Use `v_out_list[k+1]`
311
+ # If memory is off, we stay in state 0 (or technically state 0 again for next step).
312
+ next_k_miss = k + 1 if use_memory else 0
313
+ val_miss_out = v_out_list[next_k_miss]
314
+ val_miss_in = v_in_list[next_k_miss]
315
+
316
+ # Combine
317
+ # W[k] = PayMask * (Payoff + U[0]) + MissMask * U[k+1]
318
+ combined_out = pay_weight * val_pay_out + (1.0 - pay_weight) * val_miss_out
319
+ combined_in = pay_weight * val_pay_in + (1.0 - pay_weight) * val_miss_in
320
+
321
+ # Apply KO logic (KO overrides everything if hit)
322
+ # KO Payoff = BaseKO + (Current + k*Acc)
323
+ # Note: KO usually implies coupon payment if coupon condition met?
324
+ # Product logic `get_ko_payoff` adds `current_coupon` if triggered.
325
+ # It also adds `accumulated_coupons`.
326
+ # So:
327
+ ko_pay_val = (base_ko_payoff + total_pay) * ko_discount
328
+ # If miss coupon barrier but hit KO barrier?
329
+ # Usually KO barrier >= Coupon barrier (for standard).
330
+ # If Spot > KO, then Spot > Coupon. So PayMask is True.
331
+ # If reverse: Spot < KO <= Coupon. So PayMask is True.
332
+ # So if KO hit, we typically pay the coupon too.
333
+ # But we should be careful if KO barrier is tighter than coupon barrier (rare).
334
+ # `get_ko_payoff` handles this check.
335
+ # Here we assume if KO hit, we pay.
336
+ # Ideally we check masks again.
337
+
338
+ # Detailed KO payoff logic:
339
+ # If KO hit:
340
+ # If Coupon hit: Pay Base + Current + Acc.
341
+ # If Coupon miss: Pay Base + Acc? Or Base only?
342
+ # Phoenix `get_ko_payoff` adds `accumulated_coupons` unconditionally?
343
+ # Let's check `get_ko_payoff`.
344
+ # It adds `accumulated_coupons` unconditionally.
345
+ # It adds `current_coupon` IF `is_coupon_triggered`.
346
+
347
+ ko_val_grid = np.full_like(combined_out, base_ko_payoff * ko_discount)
348
+ # Add accumulated unconditionally
349
+ ko_val_grid += (accumulated_pay * ko_discount)
350
+ # Add current coupon proportionally to pay-weight
351
+ ko_val_grid += pay_weight * (coupon_amt * ko_discount)
352
+
353
+ combined_out = ko_weight * ko_val_grid + (1.0 - ko_weight) * combined_out
354
+ if not disable_ko_after_ki:
355
+ combined_in = ko_weight * ko_val_grid + (1.0 - ko_weight) * combined_in
356
+
357
+ new_v_in_list.append(combined_in)
358
+ new_v_out_list.append(combined_out)
359
+
360
+ v_in_list = new_v_in_list
361
+ v_out_list = new_v_out_list
362
+
363
+ # KI Logic (Discrete/Continuous) applied to ALL surfaces
364
+ if ki_continuous and log_ki_barrier is not None:
365
+ ki_mask = (
366
+ spot_grid >= product.barrier_config.ki_barrier
367
+ if product.is_reverse
368
+ else spot_grid <= product.barrier_config.ki_barrier
369
+ )
370
+ for i in range(len(v_out_list)):
371
+ v_out_list[i][ki_mask] = v_in_list[i][ki_mask]
372
+ elif ki_records:
373
+ ki_record = self._match_record(obs_time, ki_records)
374
+ if ki_record is not None:
375
+ ki_weight = self._smooth_step_weight(
376
+ grid,
377
+ ki_record.barrier,
378
+ spot,
379
+ smoothing_width,
380
+ trigger_is_down=not product.is_reverse,
381
+ )
382
+ if ki_weight is None:
383
+ ki_mask = (
384
+ spot_grid >= ki_record.barrier
385
+ if product.is_reverse
386
+ else spot_grid <= ki_record.barrier
387
+ )
388
+ ki_weight = ki_mask.astype(float)
389
+ if ko_weight is not None and not disable_ko_after_ki:
390
+ ki_weight = ki_weight * (1.0 - ko_weight)
391
+ for i in range(len(v_out_list)):
392
+ v_out_list[i] = (1.0 - ki_weight) * v_out_list[i] + ki_weight * v_in_list[i]
393
+
394
+ # Diffusion Step
395
+ tau_step = float(tau[step_index])
396
+ prefactor = math.exp(-beta * tau_step) / math.sqrt(math.pi * tau_step) / 2.0
397
+ omega_array = np.exp(-(omega_grid**2) / (4.0 * tau_step) - alpha * omega_grid)
398
+
399
+ # Diffuse all surfaces
400
+ for i in range(len(v_in_list)):
401
+ v_in_list[i] = self._diffuse_fft(
402
+ v_in_list[i],
403
+ math_utils,
404
+ omega_array,
405
+ prefactor,
406
+ full_p_lr,
407
+ full_p_ur,
408
+ full_p0,
409
+ alpha,
410
+ beta,
411
+ tau_step,
412
+ )
413
+
414
+ if ki_continuous:
415
+ v_out_list[i] = self._diffuse_with_bridge(
416
+ v_out_list[i],
417
+ v_in_list[i],
418
+ math_utils,
419
+ omega_array,
420
+ prefactor,
421
+ full_p_lr,
422
+ full_p_ur,
423
+ full_p0,
424
+ log_ki_barrier,
425
+ alpha,
426
+ beta,
427
+ vol,
428
+ dt[step_index],
429
+ tau_step,
430
+ product.is_reverse,
431
+ )
432
+ else:
433
+ v_out_list[i] = self._diffuse_fft(
434
+ v_out_list[i],
435
+ math_utils,
436
+ omega_array,
437
+ prefactor,
438
+ full_p_lr,
439
+ full_p_ur,
440
+ full_p0,
441
+ alpha,
442
+ beta,
443
+ tau_step,
444
+ )
445
+
446
+ # Final result is value at t=0 with 0 accumulated coupons.
447
+ value_surface = (
448
+ v_in_list[0]
449
+ if getattr(product, "_otc_lifecycle_knocked_in", False)
450
+ else v_out_list[0]
451
+ )
452
+ return math_utils.interpolate(value_surface, x=0.0)
453
+
454
+ def calculate_event_stats(
455
+ self, product: BaseEquityProduct, pricing_env: PricingEnvironment
456
+ ):
457
+ if not isinstance(product, PhoenixOption):
458
+ return None
459
+
460
+ from quantark.asset.equity.engine.mc.phoenix_mc_engine import PhoenixMCEngine
461
+
462
+ mc_engine = PhoenixMCEngine(params=MCParams())
463
+ return mc_engine.calculate_event_stats(product, pricing_env)
464
+
465
+ def __repr__(self):
466
+ return "PhoenixQuadEngine()"
467
+
468
+ def _select_alignment_log(
469
+ self,
470
+ spot: float,
471
+ ko_records: Sequence,
472
+ coupon_barriers: Optional[np.ndarray],
473
+ ki_records: Sequence,
474
+ product: PhoenixOption,
475
+ ) -> Optional[float]:
476
+ ko_candidates: list[float] = []
477
+ for rec in ko_records:
478
+ if rec.barrier is not None and rec.barrier > 0:
479
+ ko_candidates.append(float(rec.barrier))
480
+
481
+ coupon_candidates: list[float] = []
482
+ if coupon_barriers is not None:
483
+ coupon_candidates.extend([float(b) for b in coupon_barriers if b > 0])
484
+
485
+ ki_candidates: list[float] = []
486
+ if product.has_ki_barrier:
487
+ ki_barrier = product.barrier_config.ki_barrier
488
+ if isinstance(ki_barrier, list):
489
+ ki_candidates.extend([float(b) for b in ki_barrier if b > 0])
490
+ elif ki_barrier is not None and ki_barrier > 0:
491
+ ki_candidates.append(float(ki_barrier))
492
+
493
+ def to_logs(candidates: list[float]) -> list[float]:
494
+ logs = []
495
+ for b in candidates:
496
+ try:
497
+ logs.append(safe_log(b / spot))
498
+ except Exception:
499
+ continue
500
+ return logs
501
+
502
+ ko_logs = to_logs(ko_candidates)
503
+ coupon_logs = to_logs(coupon_candidates)
504
+ ki_logs = to_logs(ki_candidates)
505
+
506
+ if not ko_logs and not coupon_logs and not ki_logs:
507
+ return None
508
+
509
+ def closest(logs: list[float]) -> Optional[float]:
510
+ if not logs:
511
+ return None
512
+ idx = int(np.argmin(np.abs(np.asarray(logs))))
513
+ return float(logs[idx])
514
+
515
+ priority = self._resolve_align_priority()
516
+ if priority == "ko":
517
+ return closest(ko_logs) or closest(coupon_logs) or closest(ki_logs)
518
+ if priority == "coupon":
519
+ return closest(coupon_logs) or closest(ko_logs) or closest(ki_logs)
520
+ if priority == "ki":
521
+ return closest(ki_logs) or closest(ko_logs) or closest(coupon_logs)
522
+
523
+ # auto: reverse prefers KO near spot, else coupon, else KI
524
+ if product.is_reverse:
525
+ ko_near = closest(ko_logs)
526
+ if ko_near is not None and abs(ko_near) <= 0.05:
527
+ return ko_near
528
+ return closest(coupon_logs) or closest(ki_logs) or closest(ko_logs)
529
+
530
+ return closest(ko_logs + coupon_logs + ki_logs)
531
+
532
+ def _resolve_align_priority(self) -> str:
533
+ priority = getattr(self.params, "align_priority", None)
534
+ if priority is None:
535
+ return "auto"
536
+ return str(priority).lower()
537
+
538
+ def _resolve_fft_padding_factor(self) -> int:
539
+ factor = getattr(self.params, "fft_padding_factor", None)
540
+ if factor is None or int(factor) <= 0:
541
+ return 2
542
+ return int(factor)
543
+
544
+ def _resolve_fft_filter(self) -> tuple[float, int]:
545
+ alpha = getattr(self.params, "fft_filter_alpha", None)
546
+ power = getattr(self.params, "fft_filter_power", None)
547
+
548
+ if alpha is None:
549
+ alpha = 12.0
550
+ if power is None:
551
+ power = 8
552
+
553
+ return float(alpha), int(power)
554
+
555
+ def _resolve_event_smoothing_width(
556
+ self, math_utils: QuadratureMath, product: PhoenixOption
557
+ ) -> float:
558
+ mode = getattr(self.params, "event_smoothing_mode", "fixed")
559
+ cells = getattr(self.params, "event_smoothing_cells", 0)
560
+ kernel_width = getattr(self.params, "event_smoothing_log_width", 0.002)
561
+
562
+ try:
563
+ cells = int(cells)
564
+ except (TypeError, ValueError):
565
+ cells = 0
566
+
567
+ if str(mode).lower() == "reverse_aware" and product.is_reverse:
568
+ cells = 0
569
+ elif str(mode).lower() == "auto":
570
+ h = float(math_utils.h)
571
+ cells = max(1, int(0.5 + float(kernel_width) / h))
572
+
573
+ if cells <= 0:
574
+ return 0.0
575
+ return float(cells) * float(math_utils.h)
576
+
577
+ def _smooth_step_weight(
578
+ self,
579
+ grid: np.ndarray,
580
+ barrier: float,
581
+ spot: float,
582
+ width: float,
583
+ *,
584
+ trigger_is_down: bool,
585
+ ) -> Optional[np.ndarray]:
586
+ if width <= 0.0:
587
+ return None
588
+ if barrier is None or barrier <= 0.0 or spot <= 0.0:
589
+ return None
590
+ barrier_log = safe_log(barrier / spot)
591
+ return self._smooth_step_weight_log(
592
+ grid, barrier_log, width, trigger_is_down=trigger_is_down
593
+ )
594
+
595
+ def _smooth_step_weight_log(
596
+ self,
597
+ grid: np.ndarray,
598
+ barrier_log: float,
599
+ width: float,
600
+ *,
601
+ trigger_is_down: bool,
602
+ ) -> Optional[np.ndarray]:
603
+ if width <= 0.0:
604
+ return None
605
+ x = grid - float(barrier_log)
606
+ kernel = str(getattr(self.params, "event_smoothing_kernel", "cosine")).lower()
607
+ if kernel == "tanh":
608
+ base = 0.5 * (1.0 + np.tanh(x / width))
609
+ else:
610
+ t = np.clip((x + width) / (2.0 * width), 0.0, 1.0)
611
+ base = 0.5 - 0.5 * np.cos(np.pi * t)
612
+ if trigger_is_down:
613
+ return 1.0 - base
614
+ return base