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,977 @@
1
+ """
2
+ Factory functions for creating common Snowball option structures.
3
+
4
+ This module provides helper functions that simplify the creation of Snowball options
5
+ by providing sensible defaults for common market structures. Each helper accepts
6
+ minimal required parameters and allows full customization via **kwargs.
7
+
8
+ Available helpers:
9
+ - create_standard_snowball(): Basic snowball with flat KO barrier, continuous KI
10
+ - create_stepdown_snowball(): KO barrier decreases each observation period
11
+ - create_european_ki_snowball(): KI only observed at maturity
12
+ - create_parachute_snowball(): Last KO barrier equals KI barrier
13
+ - create_phoenix_snowball(): Periodic coupons above coupon barrier
14
+ - create_airbag_snowball(): Reduced participation below airbag barrier
15
+
16
+ Utilities:
17
+ - generate_ko_observation_dates(): Create evenly spaced observation dates
18
+ - generate_stepdown_barriers(): Generate decreasing barrier levels
19
+
20
+ Example:
21
+ >>> from asset.equity.product.option import create_standard_snowball
22
+ >>> snowball = create_standard_snowball(
23
+ ... initial_price=100.0,
24
+ ... strike=100.0,
25
+ ... maturity=1.0,
26
+ ... contract_multiplier=1.0,
27
+ ... )
28
+ """
29
+
30
+ from dataclasses import fields
31
+ from typing import List, Optional
32
+
33
+ from quantark.util.exceptions import ValidationError
34
+ from quantark.util.enum import ObservationType, CouponPayType, PostKOScheduleMode
35
+
36
+ from .snowball_option import SnowballOption
37
+ from .ko_reset_snowball_option import KnockOutResetSnowballOption
38
+ from .snowball_config import BarrierConfig, PayoffConfig, AccrualConfig, AirbagConfig
39
+
40
+
41
+ # =============================================================================
42
+ # Utility Functions
43
+ # =============================================================================
44
+
45
+
46
+ def generate_ko_observation_dates(
47
+ maturity: float,
48
+ frequency: str = "monthly",
49
+ skip_first: int = 0,
50
+ ) -> List[float]:
51
+ """
52
+ Generate evenly spaced observation dates as year fractions.
53
+
54
+ Args:
55
+ maturity: Time to maturity in years
56
+ frequency: Observation frequency - "monthly", "quarterly", "weekly", or "daily"
57
+ skip_first: Number of initial observations to skip (lock-out period)
58
+
59
+ Returns:
60
+ List of observation times as year fractions
61
+
62
+ Raises:
63
+ ValidationError: If frequency is invalid or maturity is non-positive
64
+
65
+ Example:
66
+ >>> generate_ko_observation_dates(1.0, "quarterly")
67
+ [0.25, 0.5, 0.75, 1.0]
68
+ >>> generate_ko_observation_dates(1.0, "monthly", skip_first=2)
69
+ [0.25, 0.333..., 0.416..., ..., 1.0]
70
+ """
71
+ if maturity <= 0:
72
+ raise ValidationError(f"maturity must be positive, got {maturity}")
73
+
74
+ frequency_map = {
75
+ "monthly": 12,
76
+ "quarterly": 4,
77
+ "weekly": 52,
78
+ "daily": 252,
79
+ }
80
+
81
+ if frequency not in frequency_map:
82
+ raise ValidationError(
83
+ f"frequency must be one of {list(frequency_map.keys())}, got '{frequency}'"
84
+ )
85
+
86
+ num_periods = frequency_map[frequency]
87
+ num_observations = int(maturity * num_periods)
88
+
89
+ if num_observations < 1:
90
+ raise ValidationError(
91
+ f"maturity {maturity} with frequency '{frequency}' yields no observations"
92
+ )
93
+
94
+ # Generate dates evenly spaced up to maturity
95
+ all_dates = [(i + 1) / num_observations * maturity for i in range(num_observations)]
96
+
97
+ if skip_first >= len(all_dates):
98
+ raise ValidationError(
99
+ f"skip_first={skip_first} would skip all {len(all_dates)} observations"
100
+ )
101
+
102
+ return all_dates[skip_first:]
103
+
104
+
105
+ def generate_stepdown_barriers(
106
+ initial_barrier: float,
107
+ stepdown_amount: float,
108
+ num_observations: int,
109
+ min_barrier: Optional[float] = None,
110
+ ) -> List[float]:
111
+ """
112
+ Generate decreasing barrier levels for step-down structures.
113
+
114
+ Args:
115
+ initial_barrier: Starting barrier level
116
+ stepdown_amount: Amount to decrease each period (absolute value)
117
+ num_observations: Number of observation dates
118
+ min_barrier: Optional floor for barrier levels
119
+
120
+ Returns:
121
+ List of barrier levels, decreasing by stepdown_amount each period
122
+
123
+ Raises:
124
+ ValidationError: If parameters are invalid
125
+
126
+ Example:
127
+ >>> generate_stepdown_barriers(103.0, 0.5, 4)
128
+ [103.0, 102.5, 102.0, 101.5]
129
+ >>> generate_stepdown_barriers(103.0, 2.0, 4, min_barrier=100.0)
130
+ [103.0, 101.0, 100.0, 100.0]
131
+ """
132
+ if initial_barrier <= 0:
133
+ raise ValidationError(
134
+ f"initial_barrier must be positive, got {initial_barrier}"
135
+ )
136
+ if stepdown_amount < 0:
137
+ raise ValidationError(
138
+ f"stepdown_amount must be non-negative, got {stepdown_amount}"
139
+ )
140
+ if num_observations < 1:
141
+ raise ValidationError(
142
+ f"num_observations must be at least 1, got {num_observations}"
143
+ )
144
+
145
+ barriers = []
146
+ current = initial_barrier
147
+ for _ in range(num_observations):
148
+ if min_barrier is not None:
149
+ current = max(current, min_barrier)
150
+ barriers.append(current)
151
+ current -= stepdown_amount
152
+
153
+ return barriers
154
+
155
+
156
+ # =============================================================================
157
+ # Helper Functions
158
+ # =============================================================================
159
+
160
+
161
+ def _validate_core_params(
162
+ initial_price: float,
163
+ strike: float,
164
+ maturity: float,
165
+ contract_multiplier: float,
166
+ func_name: str,
167
+ ) -> None:
168
+ """Validate core parameters common to all helpers."""
169
+ if initial_price <= 0:
170
+ raise ValidationError(
171
+ f"{func_name}: initial_price must be positive, got {initial_price}"
172
+ )
173
+ if strike <= 0:
174
+ raise ValidationError(f"{func_name}: strike must be positive, got {strike}")
175
+ if maturity <= 0:
176
+ raise ValidationError(f"{func_name}: maturity must be positive, got {maturity}")
177
+ if contract_multiplier <= 0:
178
+ raise ValidationError(
179
+ f"{func_name}: contract_multiplier must be positive, got {contract_multiplier}"
180
+ )
181
+
182
+
183
+ def _extract_config_kwargs(kwargs: dict) -> tuple:
184
+ """
185
+ Extract kwargs for each config class using introspection.
186
+
187
+ Raises:
188
+ ValidationError: If kwargs contains unknown parameters.
189
+
190
+ Returns:
191
+ (barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs)
192
+ """
193
+ barrier_fields = {f.name for f in fields(BarrierConfig)}
194
+ payoff_fields = {f.name for f in fields(PayoffConfig)}
195
+ accrual_fields = {f.name for f in fields(AccrualConfig)}
196
+ airbag_fields = {f.name for f in fields(AirbagConfig)}
197
+
198
+ barrier_kwargs = {}
199
+ payoff_kwargs = {}
200
+ accrual_kwargs = {}
201
+ airbag_kwargs = {}
202
+ unknown_kwargs = []
203
+
204
+ for key, value in kwargs.items():
205
+ if key in barrier_fields:
206
+ barrier_kwargs[key] = value
207
+ elif key in payoff_fields:
208
+ payoff_kwargs[key] = value
209
+ elif key in accrual_fields:
210
+ accrual_kwargs[key] = value
211
+ elif key in airbag_fields:
212
+ airbag_kwargs[key] = value
213
+ else:
214
+ unknown_kwargs.append(key)
215
+
216
+ if unknown_kwargs:
217
+ raise ValidationError(
218
+ f"Unknown parameters provided: {', '.join(unknown_kwargs)}. "
219
+ "Please check spelling or valid configuration fields."
220
+ )
221
+
222
+ return barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs
223
+
224
+
225
+ def create_standard_snowball(
226
+ initial_price: float,
227
+ strike: float,
228
+ maturity: float,
229
+ contract_multiplier: float = 1.0,
230
+ ko_barrier: Optional[float] = None,
231
+ ko_rate: float = 0.15,
232
+ ki_barrier: Optional[float] = None,
233
+ num_observations: int = 12,
234
+ is_reverse: bool = False,
235
+ **kwargs,
236
+ ) -> SnowballOption:
237
+ """
238
+ Create a standard snowball with flat KO barrier and continuous KI monitoring.
239
+
240
+ This is the most common snowball structure with:
241
+ - Discrete KO observations (monthly by default)
242
+ - Continuous KI monitoring
243
+ - Flat (constant) KO barrier
244
+ - Annualized coupon rate
245
+
246
+ Args:
247
+ initial_price: Reference price for payoff calculations
248
+ strike: Strike price for embedded option
249
+ maturity: Time to maturity in years
250
+ contract_multiplier: Underlying units represented by one contract
251
+ ko_barrier: Knock-out barrier (default: 103% of initial_price)
252
+ ko_rate: Annualized knock-out rate (default: 15%)
253
+ ki_barrier: Knock-in barrier (default: 75% of initial_price)
254
+ num_observations: Number of KO observations (default: 12 for monthly)
255
+ is_reverse: If True, create reverse snowball (default: False)
256
+ **kwargs: Additional parameters passed to config objects or SnowballOption
257
+
258
+ Returns:
259
+ Configured SnowballOption instance
260
+
261
+ Example:
262
+ >>> snowball = create_standard_snowball(
263
+ ... initial_price=100.0,
264
+ ... strike=100.0,
265
+ ... maturity=1.0,
266
+ ... )
267
+ >>> snowball.barrier_config.ko_barrier
268
+ 103.0
269
+ """
270
+ _validate_core_params(
271
+ initial_price, strike, maturity, contract_multiplier, "create_standard_snowball"
272
+ )
273
+
274
+ # Apply defaults
275
+ if ko_barrier is None:
276
+ ko_barrier = 1.03 * initial_price
277
+ if ki_barrier is None:
278
+ ki_barrier = 0.75 * initial_price
279
+
280
+ # Generate observation dates
281
+ ko_observation_dates = [
282
+ (i + 1) / num_observations * maturity for i in range(num_observations)
283
+ ]
284
+
285
+ # Extract config-specific kwargs
286
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
287
+
288
+ # Build configs
289
+ barrier_config = BarrierConfig(
290
+ ko_barrier=ko_barrier,
291
+ ko_rate=ko_rate,
292
+ ko_observation_type=barrier_kwargs.pop(
293
+ "ko_observation_type", ObservationType.DISCRETE
294
+ ),
295
+ ko_observation_dates=barrier_kwargs.pop(
296
+ "ko_observation_dates", ko_observation_dates
297
+ ),
298
+ ki_barrier=ki_barrier,
299
+ ki_observation_type=barrier_kwargs.pop(
300
+ "ki_observation_type", ObservationType.CONTINUOUS
301
+ ),
302
+ ki_continuous=barrier_kwargs.pop("ki_continuous", True),
303
+ **barrier_kwargs,
304
+ )
305
+
306
+ payoff_config = PayoffConfig(
307
+ rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
308
+ include_principal=payoff_kwargs.pop("include_principal", False),
309
+ **payoff_kwargs,
310
+ )
311
+
312
+ accrual_config = AccrualConfig(
313
+ coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
314
+ is_annualized=accrual_kwargs.pop("is_annualized", True),
315
+ **accrual_kwargs,
316
+ )
317
+
318
+ airbag_config = AirbagConfig(**airbag_kwargs)
319
+
320
+ return SnowballOption(
321
+ initial_price=initial_price,
322
+ strike=strike,
323
+ maturity=maturity,
324
+ contract_multiplier=contract_multiplier,
325
+ barrier_config=barrier_config,
326
+ payoff_config=payoff_config,
327
+ accrual_config=accrual_config,
328
+ airbag_config=airbag_config,
329
+ is_reverse=is_reverse,
330
+ )
331
+
332
+
333
+ def create_stepdown_snowball(
334
+ initial_price: float,
335
+ strike: float,
336
+ maturity: float,
337
+ contract_multiplier: float = 1.0,
338
+ initial_ko_barrier: Optional[float] = None,
339
+ stepdown_rate: float = 0.005,
340
+ ko_rate: float = 0.15,
341
+ ki_barrier: Optional[float] = None,
342
+ num_observations: int = 12,
343
+ is_reverse: bool = False,
344
+ **kwargs,
345
+ ) -> SnowballOption:
346
+ """
347
+ Create a step-down snowball where KO barrier decreases each observation.
348
+
349
+ Common in China structured products market (递减雪球). The KO barrier
350
+ starts high and decreases each observation period, making knock-out
351
+ progressively easier to achieve.
352
+
353
+ Args:
354
+ initial_price: Reference price for payoff calculations
355
+ strike: Strike price for embedded option
356
+ maturity: Time to maturity in years
357
+ contract_multiplier: Underlying units represented by one contract
358
+ initial_ko_barrier: Starting KO barrier (default: 103% of initial_price)
359
+ stepdown_rate: Rate of barrier decrease per period as fraction of initial_price
360
+ (default: 0.5% per period)
361
+ ko_rate: Annualized knock-out rate (default: 15%)
362
+ ki_barrier: Knock-in barrier (default: 75% of initial_price)
363
+ num_observations: Number of KO observations (default: 12 for monthly)
364
+ is_reverse: If True, create reverse snowball (default: False)
365
+ **kwargs: Additional parameters passed to config objects or SnowballOption
366
+
367
+ Returns:
368
+ Configured SnowballOption instance with decreasing KO barriers
369
+
370
+ Example:
371
+ >>> snowball = create_stepdown_snowball(
372
+ ... initial_price=100.0,
373
+ ... strike=100.0,
374
+ ... maturity=1.0,
375
+ ... stepdown_rate=0.005,
376
+ ... )
377
+ >>> snowball.barrier_config.ko_barrier[:4]
378
+ [103.0, 102.5, 102.0, 101.5]
379
+ """
380
+ _validate_core_params(
381
+ initial_price, strike, maturity, contract_multiplier, "create_stepdown_snowball"
382
+ )
383
+
384
+ # Apply defaults
385
+ if initial_ko_barrier is None:
386
+ initial_ko_barrier = 1.03 * initial_price
387
+ if ki_barrier is None:
388
+ ki_barrier = 0.75 * initial_price
389
+
390
+ # Generate step-down barriers
391
+ stepdown_amount = stepdown_rate * initial_price
392
+ ko_barriers = generate_stepdown_barriers(
393
+ initial_barrier=initial_ko_barrier,
394
+ stepdown_amount=stepdown_amount,
395
+ num_observations=num_observations,
396
+ )
397
+
398
+ # Generate observation dates
399
+ ko_observation_dates = [
400
+ (i + 1) / num_observations * maturity for i in range(num_observations)
401
+ ]
402
+
403
+ # Extract config-specific kwargs
404
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
405
+
406
+ # Build configs
407
+ barrier_config = BarrierConfig(
408
+ ko_barrier=ko_barriers,
409
+ ko_rate=ko_rate,
410
+ ko_observation_type=barrier_kwargs.pop(
411
+ "ko_observation_type", ObservationType.DISCRETE
412
+ ),
413
+ ko_observation_dates=barrier_kwargs.pop(
414
+ "ko_observation_dates", ko_observation_dates
415
+ ),
416
+ ki_barrier=ki_barrier,
417
+ ki_observation_type=barrier_kwargs.pop(
418
+ "ki_observation_type", ObservationType.CONTINUOUS
419
+ ),
420
+ ki_continuous=barrier_kwargs.pop("ki_continuous", True),
421
+ **barrier_kwargs,
422
+ )
423
+
424
+ payoff_config = PayoffConfig(
425
+ rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
426
+ include_principal=payoff_kwargs.pop("include_principal", False),
427
+ **payoff_kwargs,
428
+ )
429
+
430
+ accrual_config = AccrualConfig(
431
+ coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
432
+ is_annualized=accrual_kwargs.pop("is_annualized", True),
433
+ **accrual_kwargs,
434
+ )
435
+
436
+ airbag_config = AirbagConfig(**airbag_kwargs)
437
+
438
+ return SnowballOption(
439
+ initial_price=initial_price,
440
+ strike=strike,
441
+ maturity=maturity,
442
+ contract_multiplier=contract_multiplier,
443
+ barrier_config=barrier_config,
444
+ payoff_config=payoff_config,
445
+ accrual_config=accrual_config,
446
+ airbag_config=airbag_config,
447
+ is_reverse=is_reverse,
448
+ )
449
+
450
+
451
+ def create_european_ki_snowball(
452
+ initial_price: float,
453
+ strike: float,
454
+ maturity: float,
455
+ contract_multiplier: float = 1.0,
456
+ ko_barrier: Optional[float] = None,
457
+ ko_rate: float = 0.15,
458
+ ki_barrier: Optional[float] = None,
459
+ num_ko_observations: int = 12,
460
+ is_reverse: bool = False,
461
+ **kwargs,
462
+ ) -> SnowballOption:
463
+ """
464
+ Create a snowball with European-style KI (only observed at maturity).
465
+
466
+ Unlike standard snowballs with continuous KI monitoring, this structure
467
+ only checks the KI barrier at maturity. This increases the probability
468
+ of the V0 outcome (no KO, no KI).
469
+
470
+ Args:
471
+ initial_price: Reference price for payoff calculations
472
+ strike: Strike price for embedded option
473
+ maturity: Time to maturity in years
474
+ contract_multiplier: Underlying units represented by one contract
475
+ ko_barrier: Knock-out barrier (default: 103% of initial_price)
476
+ ko_rate: Annualized knock-out rate (default: 15%)
477
+ ki_barrier: Knock-in barrier (default: 75% of initial_price)
478
+ num_ko_observations: Number of KO observations (default: 12 for monthly)
479
+ is_reverse: If True, create reverse snowball (default: False)
480
+ **kwargs: Additional parameters passed to config objects or SnowballOption
481
+
482
+ Returns:
483
+ Configured SnowballOption instance with European-style KI
484
+
485
+ Example:
486
+ >>> snowball = create_european_ki_snowball(
487
+ ... initial_price=100.0,
488
+ ... strike=100.0,
489
+ ... maturity=1.0,
490
+ ... )
491
+ >>> snowball.barrier_config.ki_continuous
492
+ False
493
+ >>> snowball.barrier_config.ki_observation_dates
494
+ [1.0]
495
+ """
496
+ _validate_core_params(
497
+ initial_price,
498
+ strike,
499
+ maturity,
500
+ contract_multiplier,
501
+ "create_european_ki_snowball",
502
+ )
503
+
504
+ # Apply defaults
505
+ if ko_barrier is None:
506
+ ko_barrier = 1.03 * initial_price
507
+ if ki_barrier is None:
508
+ ki_barrier = 0.75 * initial_price
509
+
510
+ # Generate KO observation dates
511
+ ko_observation_dates = [
512
+ (i + 1) / num_ko_observations * maturity for i in range(num_ko_observations)
513
+ ]
514
+
515
+ # KI only at maturity (European-style)
516
+ ki_observation_dates = [maturity]
517
+
518
+ # Extract config-specific kwargs
519
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
520
+
521
+ # Build configs - force discrete KI with single observation at maturity
522
+ barrier_config = BarrierConfig(
523
+ ko_barrier=ko_barrier,
524
+ ko_rate=ko_rate,
525
+ ko_observation_type=barrier_kwargs.pop(
526
+ "ko_observation_type", ObservationType.DISCRETE
527
+ ),
528
+ ko_observation_dates=barrier_kwargs.pop(
529
+ "ko_observation_dates", ko_observation_dates
530
+ ),
531
+ ki_barrier=ki_barrier,
532
+ ki_observation_type=ObservationType.DISCRETE, # Force discrete
533
+ ki_observation_dates=ki_observation_dates,
534
+ ki_continuous=False, # Force non-continuous
535
+ **barrier_kwargs,
536
+ )
537
+
538
+ payoff_config = PayoffConfig(
539
+ rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
540
+ include_principal=payoff_kwargs.pop("include_principal", False),
541
+ **payoff_kwargs,
542
+ )
543
+
544
+ accrual_config = AccrualConfig(
545
+ coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
546
+ is_annualized=accrual_kwargs.pop("is_annualized", True),
547
+ **accrual_kwargs,
548
+ )
549
+
550
+ airbag_config = AirbagConfig(**airbag_kwargs)
551
+
552
+ return SnowballOption(
553
+ initial_price=initial_price,
554
+ strike=strike,
555
+ maturity=maturity,
556
+ contract_multiplier=contract_multiplier,
557
+ barrier_config=barrier_config,
558
+ payoff_config=payoff_config,
559
+ accrual_config=accrual_config,
560
+ airbag_config=airbag_config,
561
+ is_reverse=is_reverse,
562
+ )
563
+
564
+
565
+ def create_parachute_snowball(
566
+ initial_price: float,
567
+ strike: float,
568
+ maturity: float,
569
+ contract_multiplier: float = 1.0,
570
+ ko_barrier: Optional[float] = None,
571
+ ko_rate: float = 0.15,
572
+ ki_barrier: Optional[float] = None,
573
+ num_observations: int = 12,
574
+ is_reverse: bool = False,
575
+ **kwargs,
576
+ ) -> SnowballOption:
577
+ """
578
+ Create a parachute snowball where last KO barrier equals KI barrier.
579
+
580
+ Also known as 降落伞雪球 in China market. At the final observation,
581
+ the KO barrier drops to the KI level, guaranteeing an exit (knock-out)
582
+ if the product hasn't been knocked in. This provides a "parachute"
583
+ safety net for investors.
584
+
585
+ Args:
586
+ initial_price: Reference price for payoff calculations
587
+ strike: Strike price for embedded option
588
+ maturity: Time to maturity in years
589
+ contract_multiplier: Underlying units represented by one contract
590
+ ko_barrier: Knock-out barrier for early observations (default: 103% of initial_price)
591
+ ko_rate: Annualized knock-out rate (default: 15%)
592
+ ki_barrier: Knock-in barrier, also final KO barrier (default: 75% of initial_price)
593
+ num_observations: Number of KO observations (default: 12 for monthly)
594
+ is_reverse: If True, create reverse snowball (default: False)
595
+ **kwargs: Additional parameters passed to config objects or SnowballOption
596
+
597
+ Returns:
598
+ Configured SnowballOption instance with parachute structure
599
+
600
+ Example:
601
+ >>> snowball = create_parachute_snowball(
602
+ ... initial_price=100.0,
603
+ ... strike=100.0,
604
+ ... maturity=1.0,
605
+ ... )
606
+ >>> barriers = snowball.barrier_config.ko_barrier
607
+ >>> barriers[-1] == snowball.barrier_config.ki_barrier
608
+ True
609
+ """
610
+ _validate_core_params(
611
+ initial_price,
612
+ strike,
613
+ maturity,
614
+ contract_multiplier,
615
+ "create_parachute_snowball",
616
+ )
617
+
618
+ # Apply defaults
619
+ if ko_barrier is None:
620
+ ko_barrier = 1.03 * initial_price
621
+ if ki_barrier is None:
622
+ ki_barrier = 0.75 * initial_price
623
+
624
+ # Build parachute KO barriers: flat until last, then drop to KI level
625
+ ko_barriers = [ko_barrier] * (num_observations - 1) + [ki_barrier]
626
+
627
+ # Generate observation dates
628
+ ko_observation_dates = [
629
+ (i + 1) / num_observations * maturity for i in range(num_observations)
630
+ ]
631
+
632
+ # Extract config-specific kwargs
633
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
634
+
635
+ # Build configs
636
+ barrier_config = BarrierConfig(
637
+ ko_barrier=ko_barriers,
638
+ ko_rate=ko_rate,
639
+ ko_observation_type=barrier_kwargs.pop(
640
+ "ko_observation_type", ObservationType.DISCRETE
641
+ ),
642
+ ko_observation_dates=barrier_kwargs.pop(
643
+ "ko_observation_dates", ko_observation_dates
644
+ ),
645
+ ki_barrier=ki_barrier,
646
+ ki_observation_type=barrier_kwargs.pop(
647
+ "ki_observation_type", ObservationType.CONTINUOUS
648
+ ),
649
+ ki_continuous=barrier_kwargs.pop("ki_continuous", True),
650
+ **barrier_kwargs,
651
+ )
652
+
653
+ payoff_config = PayoffConfig(
654
+ rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
655
+ include_principal=payoff_kwargs.pop("include_principal", False),
656
+ **payoff_kwargs,
657
+ )
658
+
659
+ accrual_config = AccrualConfig(
660
+ coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
661
+ is_annualized=accrual_kwargs.pop("is_annualized", True),
662
+ **accrual_kwargs,
663
+ )
664
+
665
+ airbag_config = AirbagConfig(**airbag_kwargs)
666
+
667
+ return SnowballOption(
668
+ initial_price=initial_price,
669
+ strike=strike,
670
+ maturity=maturity,
671
+ contract_multiplier=contract_multiplier,
672
+ barrier_config=barrier_config,
673
+ payoff_config=payoff_config,
674
+ accrual_config=accrual_config,
675
+ airbag_config=airbag_config,
676
+ is_reverse=is_reverse,
677
+ )
678
+
679
+
680
+
681
+
682
+
683
+ def create_airbag_snowball(
684
+ initial_price: float,
685
+ strike: float,
686
+ maturity: float,
687
+ contract_multiplier: float = 1.0,
688
+ ko_barrier: Optional[float] = None,
689
+ ko_rate: float = 0.15,
690
+ ki_barrier: Optional[float] = None,
691
+ airbag_barrier: Optional[float] = None,
692
+ participation_rate: float = 1.0,
693
+ airbag_participation_rate: float = 0.5,
694
+ airbag_strike: Optional[float] = None,
695
+ num_observations: int = 12,
696
+ is_reverse: bool = False,
697
+ **kwargs,
698
+ ) -> SnowballOption:
699
+ """
700
+ Create an airbag snowball with reduced participation below airbag barrier.
701
+
702
+ Airbag snowballs provide additional protection for extreme downside
703
+ by reducing the participation rate when spot falls below the airbag
704
+ barrier. This limits losses in severe market downturns.
705
+
706
+ Args:
707
+ initial_price: Reference price for payoff calculations
708
+ strike: Strike price for embedded option
709
+ maturity: Time to maturity in years
710
+ contract_multiplier: Underlying units represented by one contract
711
+ ko_barrier: Knock-out barrier (default: 103% of initial_price)
712
+ ko_rate: Annualized knock-out rate (default: 15%)
713
+ ki_barrier: Knock-in barrier (default: 75% of initial_price)
714
+ airbag_barrier: Barrier below which participation is reduced
715
+ (default: 60% of initial_price)
716
+ participation_rate: Participation rate for normal KI scenario (default: 100%)
717
+ airbag_participation_rate: Participation rate when below airbag barrier (default: 50%)
718
+ airbag_strike: Strike price for airbag payoff (optional, defaults to strike)
719
+ num_observations: Number of observations (default: 12 for monthly)
720
+ is_reverse: If True, create reverse snowball (default: False)
721
+ **kwargs: Additional parameters passed to config objects or SnowballOption
722
+
723
+ Returns:
724
+ Configured SnowballOption instance with airbag structure
725
+
726
+ Example:
727
+ >>> snowball = create_airbag_snowball(
728
+ ... initial_price=100.0,
729
+ ... strike=100.0,
730
+ ... maturity=1.0,
731
+ ... participation_rate=1.0,
732
+ ... airbag_participation_rate=0.5,
733
+ ... )
734
+ >>> snowball.airbag_config.airbag_participation_rate
735
+ 0.5
736
+ """
737
+ _validate_core_params(
738
+ initial_price,
739
+ strike,
740
+ maturity,
741
+ contract_multiplier,
742
+ "create_airbag_snowball",
743
+ )
744
+
745
+ # Apply defaults
746
+ if is_reverse:
747
+ if ko_barrier is None:
748
+ ko_barrier = 0.97 * initial_price
749
+ if ki_barrier is None:
750
+ ki_barrier = 1.25 * initial_price
751
+ if airbag_barrier is None:
752
+ airbag_barrier = 1.40 * initial_price
753
+ else:
754
+ if ko_barrier is None:
755
+ ko_barrier = 1.03 * initial_price
756
+ if ki_barrier is None:
757
+ ki_barrier = 0.75 * initial_price
758
+ if airbag_barrier is None:
759
+ airbag_barrier = 0.60 * initial_price
760
+
761
+ # Validate airbag barrier
762
+ # For standard snowball: airbag_barrier < ki_barrier (both below initial)
763
+ # For reverse snowball: airbag_barrier > ki_barrier (both above initial)
764
+ if is_reverse:
765
+ if airbag_barrier <= ki_barrier:
766
+ raise ValidationError(
767
+ f"create_airbag_snowball: for reverse snowball, airbag_barrier "
768
+ f"({airbag_barrier}) must be greater than ki_barrier ({ki_barrier})"
769
+ )
770
+ else:
771
+ if airbag_barrier >= ki_barrier:
772
+ raise ValidationError(
773
+ f"create_airbag_snowball: airbag_barrier ({airbag_barrier}) "
774
+ f"must be less than ki_barrier ({ki_barrier})"
775
+ )
776
+
777
+ # Generate observation dates
778
+ ko_observation_dates = [
779
+ (i + 1) / num_observations * maturity for i in range(num_observations)
780
+ ]
781
+
782
+ # Extract config-specific kwargs
783
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = (
784
+ _extract_config_kwargs(kwargs)
785
+ )
786
+
787
+ # Use airbag params in config
788
+ airbag_kwargs["airbag_barrier"] = airbag_barrier
789
+ airbag_kwargs["airbag_participation_rate"] = airbag_participation_rate
790
+ airbag_kwargs["airbag_strike"] = airbag_strike
791
+
792
+ # Build configs
793
+ barrier_config = BarrierConfig(
794
+ ko_barrier=ko_barrier,
795
+ ko_rate=ko_rate,
796
+ ko_observation_type=barrier_kwargs.pop(
797
+ "ko_observation_type", ObservationType.DISCRETE
798
+ ),
799
+ ko_observation_dates=barrier_kwargs.pop(
800
+ "ko_observation_dates", ko_observation_dates
801
+ ),
802
+ ki_barrier=ki_barrier,
803
+ ki_observation_type=barrier_kwargs.pop(
804
+ "ki_observation_type", ObservationType.CONTINUOUS
805
+ ),
806
+ ki_continuous=barrier_kwargs.pop("ki_continuous", True),
807
+ **barrier_kwargs,
808
+ )
809
+
810
+ payoff_config = PayoffConfig(
811
+ rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
812
+ include_principal=payoff_kwargs.pop("include_principal", False),
813
+ participation_rate=participation_rate,
814
+ **payoff_kwargs,
815
+ )
816
+
817
+ accrual_config = AccrualConfig(
818
+ coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
819
+ is_annualized=accrual_kwargs.pop("is_annualized", True),
820
+ **accrual_kwargs,
821
+ )
822
+
823
+ airbag_config = AirbagConfig(**airbag_kwargs)
824
+
825
+ return SnowballOption(
826
+ initial_price=initial_price,
827
+ strike=strike,
828
+ maturity=maturity,
829
+ contract_multiplier=contract_multiplier,
830
+ barrier_config=barrier_config,
831
+ payoff_config=payoff_config,
832
+ accrual_config=accrual_config,
833
+ airbag_config=airbag_config,
834
+ is_reverse=is_reverse,
835
+ )
836
+
837
+
838
+ def create_ko_reset_snowball(
839
+ initial_price: float,
840
+ strike: float,
841
+ maturity_pre: float,
842
+ maturity_post: float,
843
+ contract_multiplier: float = 1.0,
844
+ pre_ko_barrier: Optional[float] = None,
845
+ pre_ko_rate: float = 0.15,
846
+ post_ko_barrier: Optional[float] = None,
847
+ post_ko_rate: float = 0.03,
848
+ ki_barrier: Optional[float] = None,
849
+ pre_frequency: str = "monthly",
850
+ post_frequency: str = "monthly",
851
+ ki_frequency: str = "daily",
852
+ pre_lockout: int = 0,
853
+ post_lockout: int = 0,
854
+ ki_continuous: bool = False,
855
+ post_ko_mode: PostKOScheduleMode = PostKOScheduleMode.ABSOLUTE,
856
+ **kwargs,
857
+ ) -> KnockOutResetSnowballOption:
858
+ """
859
+ Create a KO-reset snowball with pre/post KO schedules and KI monitoring.
860
+
861
+ Args:
862
+ initial_price: Reference price for payoff calculations
863
+ strike: Strike price for embedded option
864
+ maturity_pre: Pre-KI schedule horizon in years
865
+ maturity_post: Post-KI schedule horizon in years (absolute or offset)
866
+ contract_multiplier: Underlying units represented by one contract
867
+ pre_ko_barrier: Pre-KI KO barrier (default: 103% of initial_price)
868
+ pre_ko_rate: Pre-KI KO rate (annualized)
869
+ post_ko_barrier: Post-KI KO barrier (default: 95% of initial_price)
870
+ post_ko_rate: Post-KI KO rate (annualized)
871
+ ki_barrier: KI barrier (default: 80% of initial_price)
872
+ pre_frequency: Observation frequency for pre-KI KO schedule
873
+ post_frequency: Observation frequency for post-KI KO schedule
874
+ ki_frequency: Observation frequency for discrete KI monitoring
875
+ pre_lockout: Number of initial pre-KI KO observations to skip
876
+ post_lockout: Number of initial post-KI KO observations to skip
877
+ ki_continuous: If True, use continuous KI monitoring (ABSOLUTE mode only)
878
+ post_ko_mode: ABSOLUTE or REBASED post-KI schedule mode
879
+ **kwargs: Additional parameters passed to config objects or option
880
+ """
881
+ _validate_core_params(
882
+ initial_price=initial_price,
883
+ strike=strike,
884
+ maturity=maturity_pre,
885
+ contract_multiplier=contract_multiplier,
886
+ func_name="create_ko_reset_snowball",
887
+ )
888
+ if maturity_post <= 0:
889
+ raise ValidationError(
890
+ f"create_ko_reset_snowball: maturity_post must be positive, got {maturity_post}"
891
+ )
892
+
893
+ pre_ko_barrier = (
894
+ pre_ko_barrier if pre_ko_barrier is not None else 1.03 * initial_price
895
+ )
896
+ post_ko_barrier = (
897
+ post_ko_barrier if post_ko_barrier is not None else 0.95 * initial_price
898
+ )
899
+ ki_barrier = ki_barrier if ki_barrier is not None else 0.80 * initial_price
900
+
901
+ if post_ko_mode == PostKOScheduleMode.REBASED and ki_continuous:
902
+ raise ValidationError(
903
+ "Rebased post-KO schedule requires discrete KI monitoring."
904
+ )
905
+
906
+ pre_obs = generate_ko_observation_dates(
907
+ maturity=maturity_pre, frequency=pre_frequency, skip_first=pre_lockout
908
+ )
909
+ post_obs = generate_ko_observation_dates(
910
+ maturity=maturity_post, frequency=post_frequency, skip_first=post_lockout
911
+ )
912
+
913
+ ki_observation_dates = None
914
+ ki_observation_type = (
915
+ ObservationType.CONTINUOUS if ki_continuous else ObservationType.DISCRETE
916
+ )
917
+ if not ki_continuous:
918
+ ki_observation_dates = generate_ko_observation_dates(
919
+ maturity=maturity_pre, frequency=ki_frequency, skip_first=0
920
+ )
921
+
922
+ barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(
923
+ kwargs
924
+ )
925
+
926
+ pre_barrier_config = BarrierConfig(
927
+ ko_barrier=pre_ko_barrier,
928
+ ko_rate=pre_ko_rate,
929
+ ko_observation_type=ObservationType.DISCRETE,
930
+ ko_observation_dates=pre_obs,
931
+ ki_barrier=ki_barrier,
932
+ ki_observation_type=ki_observation_type,
933
+ ki_observation_dates=ki_observation_dates,
934
+ ki_continuous=ki_continuous,
935
+ **barrier_kwargs,
936
+ )
937
+
938
+ post_barrier_config = BarrierConfig(
939
+ ko_barrier=post_ko_barrier,
940
+ ko_rate=post_ko_rate,
941
+ ko_observation_type=ObservationType.DISCRETE,
942
+ ko_observation_dates=post_obs,
943
+ )
944
+
945
+ payoff_config = PayoffConfig(**payoff_kwargs) if payoff_kwargs else None
946
+ accrual_config = AccrualConfig(**accrual_kwargs) if accrual_kwargs else None
947
+ airbag_config = AirbagConfig(**airbag_kwargs) if airbag_kwargs else None
948
+
949
+ return KnockOutResetSnowballOption(
950
+ initial_price=initial_price,
951
+ strike=strike,
952
+ barrier_config=pre_barrier_config,
953
+ post_barrier_config=post_barrier_config,
954
+ payoff_config=payoff_config,
955
+ accrual_config=accrual_config,
956
+ airbag_config=airbag_config,
957
+ contract_multiplier=contract_multiplier,
958
+ maturity=max(maturity_pre, maturity_post),
959
+ post_ko_mode=post_ko_mode,
960
+ )
961
+
962
+
963
+ # =============================================================================
964
+ # Module Exports
965
+ # =============================================================================
966
+
967
+ __all__ = [
968
+ # Helper functions
969
+ "create_standard_snowball",
970
+ "create_stepdown_snowball",
971
+ "create_european_ki_snowball",
972
+ "create_parachute_snowball",
973
+ "create_airbag_snowball",
974
+ # Utility functions
975
+ "generate_ko_observation_dates",
976
+ "generate_stepdown_barriers",
977
+ ]