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,703 @@
1
+ """
2
+ Core dynamic scenario analysis engine.
3
+
4
+ This module contains the main engine for running dynamic scenario simulations
5
+ with time evolution and optional hedging strategies.
6
+ """
7
+
8
+ from typing import Optional, Dict, Any, List
9
+ from datetime import datetime, timedelta
10
+ import time
11
+ from copy import deepcopy
12
+
13
+ from quantark.portfolio import Portfolio
14
+ from quantark.priceenv import PricingEnvironment
15
+ from quantark.param import (
16
+ SpotQuote,
17
+ FlatVolSurface,
18
+ FlatRateCurve,
19
+ ContinuousDividendYield,
20
+ FlatBasisYield,
21
+ TermStructureDividendYield,
22
+ TermStructureBasisYield,
23
+ )
24
+ from quantark.param.basis.basis_yield import (
25
+ calculate_basis_from_rate_dividend,
26
+ calculate_dividend_from_rate_basis,
27
+ )
28
+ from quantark.asset.equity.riskmeasures import GreeksCalculator
29
+ from quantark.asset.equity.product.deltaone import SpotInstrument, Futures
30
+ from quantark.asset.equity.engine.analytical import DeltaOneEngine
31
+
32
+ from quantark.backtest.strategy.base_strategy import BaseStrategy
33
+ from quantark.backtest.transaction_costs import TransactionCostModel, ZeroCostModel
34
+
35
+ from quantark.dynamicscenario.config import DynamicScenarioConfig
36
+ from quantark.dynamicscenario.path.day_path import DayPath, DayStep, ParameterChange
37
+ from quantark.dynamicscenario.results.dynamic_results import (
38
+ DynamicScenarioResults, DayResult, PositionSnapshot,
39
+ TradeSnapshot, MarketState
40
+ )
41
+ from quantark.stresstest.stress.stress_types import (
42
+ StressType,
43
+ StressLevel,
44
+ BasisDividendRelationshipMode,
45
+ )
46
+ from quantark.util.exceptions import ValidationError
47
+ from quantark.util.numerical import pnl_pct_of_abs_baseline
48
+
49
+
50
+ class DynamicScenarioEngine:
51
+ """
52
+ Engine for executing dynamic scenario analysis.
53
+
54
+ This is the main entry point for running multi-day scenario simulations.
55
+ It handles:
56
+ - Applying day-by-day market changes to pricing environments
57
+ - Calculating portfolio value and Greeks at each step
58
+ - Optionally executing hedging strategies
59
+ - Recording day-by-day state evolution
60
+
61
+ Example:
62
+ >>> config = DynamicScenarioConfig(calculate_greeks=True)
63
+ >>> engine = DynamicScenarioEngine(config)
64
+ >>>
65
+ >>> # Create a 5-day rally path
66
+ >>> path = PathLibrary.consecutive_rally(days=5, daily_pct=0.02)
67
+ >>>
68
+ >>> # Run simulation
69
+ >>> results = engine.run(portfolio, path)
70
+ >>> print(results.get_summary())
71
+
72
+ >>> # Run with hedging
73
+ >>> strategy = DeltaNeutralStrategy(delta_threshold=50)
74
+ >>> results = engine.run(portfolio, path, hedge_strategy=strategy)
75
+ """
76
+
77
+ def __init__(self, config: Optional[DynamicScenarioConfig] = None):
78
+ """
79
+ Initialize dynamic scenario engine.
80
+
81
+ Args:
82
+ config: Configuration for execution
83
+ """
84
+ self.config = config or DynamicScenarioConfig()
85
+ self.greeks_calculator = GreeksCalculator() if self.config.calculate_greeks else None
86
+ self._deltaone_engine = DeltaOneEngine()
87
+
88
+ def run(
89
+ self,
90
+ portfolio: Portfolio,
91
+ day_path: DayPath,
92
+ hedge_strategy: Optional[BaseStrategy] = None,
93
+ transaction_cost_model: Optional[TransactionCostModel] = None
94
+ ) -> DynamicScenarioResults:
95
+ """
96
+ Run dynamic scenario simulation.
97
+
98
+ Simulates portfolio evolution through the day path, optionally
99
+ applying hedging strategies at each step.
100
+
101
+ Args:
102
+ portfolio: Portfolio to simulate
103
+ day_path: Day path defining market evolution
104
+ hedge_strategy: Optional hedging strategy (from backtest module)
105
+ transaction_cost_model: Optional transaction cost model
106
+
107
+ Returns:
108
+ DynamicScenarioResults with day-by-day evolution
109
+
110
+ Raises:
111
+ ValidationError: If portfolio or path is invalid
112
+ """
113
+ # Validate inputs
114
+ if not portfolio or len(portfolio) == 0:
115
+ raise ValidationError("Portfolio must contain at least one position")
116
+
117
+ if not day_path or day_path.num_days == 0:
118
+ raise ValidationError("Day path must have at least one day")
119
+
120
+ start_time = time.time()
121
+ print(f"Starting dynamic scenario: {day_path.name}")
122
+ print(f" Days: {day_path.num_days}")
123
+ print(f" Hedging: {'Yes' if hedge_strategy else 'No'}")
124
+
125
+ # Use zero cost model if none provided
126
+ if transaction_cost_model is None:
127
+ transaction_cost_model = ZeroCostModel()
128
+
129
+ # Create working copy of portfolio
130
+ working_portfolio = self._clone_portfolio(portfolio)
131
+
132
+ # Track state
133
+ baseline_value = working_portfolio.get_portfolio_value()
134
+ cumulative_transaction_costs = 0.0
135
+ total_hedges = 0
136
+ day_results: List[DayResult] = []
137
+ previous_value = baseline_value
138
+
139
+ # Reset strategy if provided
140
+ if hedge_strategy:
141
+ hedge_strategy.reset()
142
+
143
+ # Track hedge positions
144
+ hedge_positions: Dict[str, str] = {} # underlying -> position_id
145
+
146
+ # Run each day
147
+ for day_step in day_path:
148
+ print(f" Processing Day {day_step.day_index}...")
149
+
150
+ # Get date for this day
151
+ day_date = day_path.get_date_for_day(day_step.day_index)
152
+
153
+ # Apply day's market changes
154
+ self._apply_day_changes(working_portfolio, day_step)
155
+
156
+ # Update valuation date if we have dates
157
+ if day_date:
158
+ for env in working_portfolio.pricing_environments.values():
159
+ env.valuation_date = day_date
160
+
161
+ # Calculate portfolio value and Greeks
162
+ portfolio_value = working_portfolio.get_portfolio_value()
163
+ daily_pnl = portfolio_value - previous_value
164
+ cumulative_pnl = portfolio_value - baseline_value
165
+
166
+ portfolio_greeks = {}
167
+ if self.config.calculate_greeks and self.greeks_calculator:
168
+ use_analytical = (self.config.greeks_method == 'analytical')
169
+ portfolio_greeks = working_portfolio.get_portfolio_greeks(
170
+ self.greeks_calculator,
171
+ use_analytical=use_analytical
172
+ )
173
+
174
+ # Execute hedging if strategy provided
175
+ trades_today: List[TradeSnapshot] = []
176
+ transaction_costs_today = 0.0
177
+
178
+ if hedge_strategy:
179
+ # Get market data for strategy
180
+ market_data = self._get_market_data(working_portfolio)
181
+
182
+ # Call strategy on_step
183
+ current_time = day_date or datetime.now()
184
+ hedge_strategy.on_step(
185
+ current_time=current_time,
186
+ portfolio_greeks=portfolio_greeks,
187
+ market_data=market_data
188
+ )
189
+
190
+ # Check if hedging needed
191
+ should_hedge = hedge_strategy.should_hedge(
192
+ current_time=current_time,
193
+ portfolio_greeks=portfolio_greeks,
194
+ market_data=market_data
195
+ )
196
+
197
+ if should_hedge:
198
+ # Calculate hedge size
199
+ hedge_size = hedge_strategy.calculate_hedge_size(
200
+ current_time=current_time,
201
+ portfolio_greeks=portfolio_greeks,
202
+ market_data=market_data
203
+ )
204
+
205
+ if abs(hedge_size) > 1e-10:
206
+ # Execute hedge for each underlying
207
+ for underlying in working_portfolio.pricing_environments.keys():
208
+ trade, cost = self._execute_hedge(
209
+ portfolio=working_portfolio,
210
+ underlying=underlying,
211
+ hedge_size=hedge_size,
212
+ transaction_cost_model=transaction_cost_model,
213
+ hedge_positions=hedge_positions,
214
+ current_time=current_time
215
+ )
216
+
217
+ if trade:
218
+ trades_today.append(trade)
219
+ transaction_costs_today += cost
220
+ total_hedges += 1
221
+
222
+ # Update strategy
223
+ hedge_strategy.on_hedge_executed(
224
+ current_time=current_time,
225
+ hedge_size=hedge_size,
226
+ hedge_price=market_data.get('spot', 0.0)
227
+ )
228
+
229
+ # Recalculate Greeks after hedge
230
+ if self.config.calculate_greeks and self.greeks_calculator:
231
+ portfolio_greeks = working_portfolio.get_portfolio_greeks(
232
+ self.greeks_calculator,
233
+ use_analytical=use_analytical
234
+ )
235
+
236
+ cumulative_transaction_costs += transaction_costs_today
237
+ net_pnl = cumulative_pnl - cumulative_transaction_costs
238
+
239
+ # Capture position snapshots
240
+ position_snapshots = self._capture_position_snapshots(working_portfolio)
241
+
242
+ # Capture market state
243
+ market_state = self._capture_market_state(working_portfolio)
244
+
245
+ # Create day result
246
+ day_result = DayResult(
247
+ day_index=day_step.day_index,
248
+ date=day_date,
249
+ label=day_step.label,
250
+ portfolio_value=portfolio_value,
251
+ daily_pnl=daily_pnl,
252
+ cumulative_pnl=cumulative_pnl,
253
+ transaction_costs_today=transaction_costs_today,
254
+ cumulative_transaction_costs=cumulative_transaction_costs,
255
+ net_pnl=net_pnl,
256
+ greeks=portfolio_greeks,
257
+ positions=position_snapshots,
258
+ trades=trades_today,
259
+ market_state=market_state,
260
+ )
261
+ day_results.append(day_result)
262
+
263
+ # Update previous value for next iteration
264
+ previous_value = portfolio_value
265
+
266
+ total_time = time.time() - start_time
267
+
268
+ # Build final results
269
+ final_value = working_portfolio.get_portfolio_value()
270
+
271
+ results = DynamicScenarioResults(
272
+ path_name=day_path.name,
273
+ baseline_value=baseline_value,
274
+ final_value=final_value,
275
+ day_results=day_results,
276
+ total_pnl=final_value - baseline_value,
277
+ total_pnl_pct=pnl_pct_of_abs_baseline(final_value - baseline_value, baseline_value),
278
+ total_transaction_costs=cumulative_transaction_costs,
279
+ net_pnl=final_value - baseline_value - cumulative_transaction_costs,
280
+ total_hedges=total_hedges,
281
+ total_execution_time=total_time,
282
+ config_summary=self.config.get_summary(),
283
+ metadata={
284
+ 'path_description': day_path.description,
285
+ 'hedge_strategy': hedge_strategy.name if hedge_strategy else None,
286
+ }
287
+ )
288
+
289
+ print(f"\nDynamic scenario completed in {total_time:.2f} seconds")
290
+ print(f" Final P&L: ${results.total_pnl:,.2f} ({results.total_pnl_pct:+.2f}%)")
291
+
292
+ return results
293
+
294
+ def _clone_portfolio(self, portfolio: Portfolio) -> Portfolio:
295
+ """Create a deep copy of portfolio with cloned pricing environments."""
296
+ # Clone pricing environments
297
+ cloned_envs = {}
298
+ for underlying, env in portfolio.pricing_environments.items():
299
+ cloned_envs[underlying] = PricingEnvironment(
300
+ rate_curve=deepcopy(env.rate_curve),
301
+ valuation_date=env.valuation_date,
302
+ spot_quote=deepcopy(env.spot_quote) if env.spot_quote else None,
303
+ vol_surface=deepcopy(env.vol_surface) if env.vol_surface else None,
304
+ div_yield=deepcopy(env.div_yield) if env.div_yield else None,
305
+ basis_yield=deepcopy(env.basis_yield) if env.basis_yield else None,
306
+ day_count_convention=env.day_count_convention,
307
+ bus_days_in_year=env.bus_days_in_year,
308
+ )
309
+
310
+ # Create new portfolio with cloned environments
311
+ cloned_portfolio = Portfolio(
312
+ portfolio_name=portfolio.portfolio_name + "_simulation",
313
+ pricing_environments=cloned_envs,
314
+ creation_date=portfolio.creation_date,
315
+ )
316
+
317
+ # Deep copy positions
318
+ cloned_portfolio.positions = deepcopy(portfolio.positions)
319
+
320
+ return cloned_portfolio
321
+
322
+ def _apply_day_changes(self, portfolio: Portfolio, day_step: DayStep) -> None:
323
+ """Apply a day's market changes to portfolio pricing environments."""
324
+ for change in day_step.changes:
325
+ if change.level == StressLevel.PORTFOLIO:
326
+ # Apply to all underlyings
327
+ for underlying in portfolio.pricing_environments.keys():
328
+ self._apply_parameter_change(
329
+ portfolio.pricing_environments[underlying],
330
+ change,
331
+ portfolio,
332
+ underlying,
333
+ )
334
+ elif change.level == StressLevel.UNDERLYING:
335
+ # Apply to specific underlying
336
+ if change.target in portfolio.pricing_environments:
337
+ self._apply_parameter_change(
338
+ portfolio.pricing_environments[change.target],
339
+ change,
340
+ portfolio,
341
+ change.target,
342
+ )
343
+ # POSITION level would need position-specific environments
344
+
345
+ def _apply_parameter_change(
346
+ self,
347
+ env: PricingEnvironment,
348
+ change: ParameterChange,
349
+ portfolio: Portfolio,
350
+ underlying: str,
351
+ ) -> None:
352
+ """Apply a single parameter change to a pricing environment."""
353
+ param = change.parameter.lower()
354
+
355
+ if param == "spot":
356
+ if env.spot_quote:
357
+ current = env.spot_quote.spot
358
+ new_value = change.apply(current)
359
+ if new_value <= 0:
360
+ raise ValidationError(f"Spot cannot be <= 0, got {new_value}")
361
+ env.spot_quote = SpotQuote(
362
+ spot=new_value,
363
+ timestamp=env.spot_quote.timestamp,
364
+ asset_name=env.spot_quote.asset_name,
365
+ )
366
+
367
+ elif param in ["volatility", "vol"]:
368
+ if env.vol_surface and isinstance(env.vol_surface, FlatVolSurface):
369
+ current = env.vol_surface.volatility
370
+ new_value = change.apply(current)
371
+ if new_value <= 0:
372
+ raise ValidationError(f"Volatility cannot be <= 0, got {new_value}")
373
+ env.vol_surface = FlatVolSurface(volatility=new_value)
374
+
375
+ elif param == "rate":
376
+ if env.rate_curve and isinstance(env.rate_curve, FlatRateCurve):
377
+ current = env.rate_curve.get_rate(1.0)
378
+ new_value = change.apply(current)
379
+ env.rate_curve = FlatRateCurve(rate=new_value)
380
+
381
+ elif param in ["dividend_yield", "div_yield", "dividend"]:
382
+ if env.div_yield and isinstance(env.div_yield, ContinuousDividendYield):
383
+ current = env.div_yield.div_yield
384
+ new_value = change.apply(current)
385
+ if new_value < 0:
386
+ new_value = 0.0
387
+ env.div_yield = ContinuousDividendYield(div_yield=new_value)
388
+ elif env.div_yield is None:
389
+ # Create dividend yield if it doesn't exist
390
+ new_value = change.apply(0.0)
391
+ if new_value < 0:
392
+ new_value = 0.0
393
+ env.div_yield = ContinuousDividendYield(div_yield=new_value)
394
+ time_to_maturity = self._infer_time_to_maturity(
395
+ change, portfolio, underlying, env
396
+ )
397
+ self._apply_basis_dividend_relationship(
398
+ env,
399
+ change,
400
+ source_param="dividend",
401
+ time_to_maturity=time_to_maturity,
402
+ )
403
+
404
+ elif param == "basis":
405
+ time_to_maturity = self._infer_time_to_maturity(
406
+ change, portfolio, underlying, env
407
+ )
408
+ current = (
409
+ env.basis_yield.get_basis_yield(time_to_maturity)
410
+ if env.basis_yield
411
+ else 0.0
412
+ )
413
+ new_value = change.apply(current)
414
+ env.basis_yield = FlatBasisYield(basis_yield=new_value)
415
+ self._apply_basis_dividend_relationship(
416
+ env,
417
+ change,
418
+ source_param="basis",
419
+ time_to_maturity=time_to_maturity,
420
+ )
421
+
422
+ def _get_market_data(self, portfolio: Portfolio) -> Dict[str, float]:
423
+ """Get current market data from portfolio for strategy."""
424
+ # Use first underlying's environment
425
+ first_underlying = list(portfolio.pricing_environments.keys())[0]
426
+ env = portfolio.pricing_environments[first_underlying]
427
+
428
+ return {
429
+ 'spot': env.spot if env.spot_quote else 0.0,
430
+ 'volatility': env.vol_surface.volatility if isinstance(env.vol_surface, FlatVolSurface) else 0.0,
431
+ 'rate': env.rate_curve.get_rate(1.0) if env.rate_curve else 0.0,
432
+ 'div_yield': env.div_yield.div_yield if isinstance(env.div_yield, ContinuousDividendYield) else 0.0,
433
+ 'basis_yield': env.basis_yield.get_basis_yield(1.0) if env.basis_yield else 0.0,
434
+ }
435
+
436
+ def _execute_hedge(
437
+ self,
438
+ portfolio: Portfolio,
439
+ underlying: str,
440
+ hedge_size: float,
441
+ transaction_cost_model: TransactionCostModel,
442
+ hedge_positions: Dict[str, str],
443
+ current_time: datetime
444
+ ) -> tuple:
445
+ """
446
+ Execute a hedge trade.
447
+
448
+ Returns:
449
+ Tuple of (TradeSnapshot or None, transaction_cost)
450
+ """
451
+ env = portfolio.pricing_environments[underlying]
452
+ hedge_price = env.spot
453
+ notional = abs(hedge_size * hedge_price)
454
+
455
+ # Calculate transaction cost
456
+ transaction_cost = transaction_cost_model.calculate_cost(
457
+ quantity=hedge_size,
458
+ price=hedge_price,
459
+ notional=notional,
460
+ instrument_type='spot',
461
+ trade_type='hedge'
462
+ )
463
+
464
+ # Check if hedge position exists
465
+ existing_position_id = hedge_positions.get(underlying)
466
+
467
+ if existing_position_id and existing_position_id in portfolio.positions:
468
+ # Update existing position
469
+ position = portfolio.positions[existing_position_id]
470
+ old_quantity = position.quantity
471
+ new_quantity = old_quantity + hedge_size
472
+
473
+ if abs(new_quantity) < 1e-10:
474
+ # Close position
475
+ portfolio.remove_position(existing_position_id)
476
+ del hedge_positions[underlying]
477
+ trade_type = 'close'
478
+ else:
479
+ position.quantity = new_quantity
480
+ trade_type = 'adjust'
481
+
482
+ trade = TradeSnapshot(
483
+ trade_type=trade_type,
484
+ underlying=underlying,
485
+ instrument_type='spot',
486
+ quantity=hedge_size,
487
+ price=hedge_price,
488
+ notional=notional,
489
+ transaction_cost=transaction_cost,
490
+ reason='delta_hedge'
491
+ )
492
+ else:
493
+ # Create new hedge position
494
+ from quantark.util.enum.deltaone_enums import DeltaOneType
495
+ hedge_product = SpotInstrument(
496
+ underlying=underlying,
497
+ deltaone_type=DeltaOneType.STOCK
498
+ )
499
+
500
+ position = portfolio.add_position(
501
+ product=hedge_product,
502
+ quantity=hedge_size,
503
+ entry_price=hedge_price,
504
+ underlying=underlying,
505
+ engine=self._deltaone_engine,
506
+ entry_timestamp=current_time
507
+ )
508
+
509
+ hedge_positions[underlying] = position.position_id
510
+
511
+ trade = TradeSnapshot(
512
+ trade_type='open',
513
+ underlying=underlying,
514
+ instrument_type='spot',
515
+ quantity=hedge_size,
516
+ price=hedge_price,
517
+ notional=notional,
518
+ transaction_cost=transaction_cost,
519
+ reason='delta_hedge'
520
+ )
521
+
522
+ return trade, transaction_cost
523
+
524
+ def _capture_position_snapshots(self, portfolio: Portfolio) -> List[PositionSnapshot]:
525
+ """Capture snapshots of all positions."""
526
+ snapshots = []
527
+
528
+ for position_id, position in portfolio.positions.items():
529
+ env = portfolio.pricing_environments[position.underlying]
530
+ market_value = position.get_market_value(env)
531
+ pnl = position.get_pnl(env)
532
+
533
+ # Get position Greeks if calculating
534
+ greeks = None
535
+ if self.config.calculate_greeks and self.greeks_calculator:
536
+ use_analytical = (self.config.greeks_method == 'analytical')
537
+ greeks = position.get_greeks(
538
+ env,
539
+ self.greeks_calculator,
540
+ use_analytical=use_analytical
541
+ )
542
+
543
+ snapshot = PositionSnapshot(
544
+ position_id=position_id,
545
+ underlying=position.underlying,
546
+ product_type=position.product.__class__.__name__,
547
+ quantity=position.quantity,
548
+ market_value=market_value,
549
+ entry_price=position.entry_price,
550
+ pnl=pnl,
551
+ greeks=greeks,
552
+ )
553
+ snapshots.append(snapshot)
554
+
555
+ return snapshots
556
+
557
+ def _capture_market_state(self, portfolio: Portfolio) -> MarketState:
558
+ """Capture current market state from portfolio."""
559
+ spot = {}
560
+ volatility = {}
561
+ div_yield = {}
562
+ basis_yield = {}
563
+ rate = 0.0
564
+
565
+ for underlying, env in portfolio.pricing_environments.items():
566
+ if env.spot_quote:
567
+ spot[underlying] = env.spot_quote.spot
568
+
569
+ if isinstance(env.vol_surface, FlatVolSurface):
570
+ volatility[underlying] = env.vol_surface.volatility
571
+
572
+ if isinstance(env.div_yield, ContinuousDividendYield):
573
+ div_yield[underlying] = env.div_yield.div_yield
574
+
575
+ if env.basis_yield is not None:
576
+ basis_yield[underlying] = env.basis_yield.get_basis_yield(1.0)
577
+
578
+ # Use rate from first environment
579
+ if env.rate_curve and rate == 0.0:
580
+ rate = env.rate_curve.get_rate(1.0)
581
+
582
+ return MarketState(
583
+ spot=spot,
584
+ volatility=volatility,
585
+ rate=rate,
586
+ div_yield=div_yield,
587
+ basis_yield=basis_yield,
588
+ )
589
+
590
+ def _apply_basis_dividend_relationship(
591
+ self,
592
+ env: PricingEnvironment,
593
+ change: ParameterChange,
594
+ source_param: str,
595
+ time_to_maturity: float,
596
+ ) -> None:
597
+ """Apply basis-dividend relationship adjustments based on config."""
598
+ relationship_mode = self.config.basis_dividend_relationship_mode
599
+ if time_to_maturity <= 0:
600
+ raise ValidationError(
601
+ f"time_to_maturity must be positive, got {time_to_maturity}"
602
+ )
603
+
604
+ rate = env.rate_curve.get_rate(time_to_maturity)
605
+
606
+ if source_param == "basis":
607
+ if relationship_mode == BasisDividendRelationshipMode.AUTO_ADJUST_DIVIDEND:
608
+ basis_val = env.basis_yield.get_basis_yield(time_to_maturity)
609
+ new_div = calculate_dividend_from_rate_basis(rate, basis_val)
610
+ if isinstance(env.div_yield, TermStructureDividendYield):
611
+ current_div = env.div_yield.get_yield(time_to_maturity)
612
+ shift = new_div - current_div
613
+ new_yields = [max(0.0, float(y) + shift) for y in env.div_yield.yields]
614
+ env.div_yield = TermStructureDividendYield(
615
+ times=list(env.div_yield.times), yields=new_yields
616
+ )
617
+ else:
618
+ if new_div < 0:
619
+ new_div = 0.0
620
+ env.div_yield = ContinuousDividendYield(div_yield=new_div)
621
+ elif relationship_mode == BasisDividendRelationshipMode.SYNCHRONIZED:
622
+ current_div = env.div_yield.get_yield(time_to_maturity) if env.div_yield else 0.0
623
+ new_div = change.apply(current_div)
624
+ if isinstance(env.div_yield, TermStructureDividendYield):
625
+ shift = new_div - current_div
626
+ new_yields = [max(0.0, float(y) + shift) for y in env.div_yield.yields]
627
+ env.div_yield = TermStructureDividendYield(
628
+ times=list(env.div_yield.times), yields=new_yields
629
+ )
630
+ else:
631
+ if new_div < 0:
632
+ new_div = 0.0
633
+ env.div_yield = ContinuousDividendYield(div_yield=new_div)
634
+ elif source_param == "dividend":
635
+ if relationship_mode == BasisDividendRelationshipMode.AUTO_ADJUST_BASIS:
636
+ div_val = env.div_yield.get_yield(time_to_maturity) if env.div_yield else 0.0
637
+ new_basis = calculate_basis_from_rate_dividend(rate, div_val)
638
+ if isinstance(env.basis_yield, TermStructureBasisYield):
639
+ current_basis = env.basis_yield.get_basis_yield(time_to_maturity)
640
+ shift = new_basis - current_basis
641
+ new_yields = [float(y) + shift for y in env.basis_yield.yields]
642
+ if any(abs(y) > 0.50 for y in new_yields):
643
+ raise ValidationError(
644
+ "Stressed term-structure basis yields must be within +/-50%."
645
+ )
646
+ env.basis_yield = TermStructureBasisYield(
647
+ times=list(env.basis_yield.times), yields=new_yields
648
+ )
649
+ else:
650
+ env.basis_yield = FlatBasisYield(basis_yield=new_basis)
651
+ elif relationship_mode == BasisDividendRelationshipMode.SYNCHRONIZED:
652
+ current_basis = env.basis_yield.get_basis_yield(time_to_maturity) if env.basis_yield else 0.0
653
+ new_basis = change.apply(current_basis)
654
+ if isinstance(env.basis_yield, TermStructureBasisYield):
655
+ shift = new_basis - current_basis
656
+ new_yields = [float(y) + shift for y in env.basis_yield.yields]
657
+ if any(abs(y) > 0.50 for y in new_yields):
658
+ raise ValidationError(
659
+ "Stressed term-structure basis yields must be within +/-50%."
660
+ )
661
+ env.basis_yield = TermStructureBasisYield(
662
+ times=list(env.basis_yield.times), yields=new_yields
663
+ )
664
+ else:
665
+ env.basis_yield = FlatBasisYield(basis_yield=new_basis)
666
+
667
+ def _infer_time_to_maturity(
668
+ self,
669
+ change: ParameterChange,
670
+ portfolio: Portfolio,
671
+ underlying: str,
672
+ env: PricingEnvironment,
673
+ ) -> float:
674
+ """Infer time to maturity from metadata or futures positions."""
675
+ if change.metadata and "time_to_maturity" in change.metadata:
676
+ time_to_maturity = float(change.metadata.get("time_to_maturity", 1.0))
677
+ if time_to_maturity <= 0:
678
+ raise ValidationError(
679
+ f"time_to_maturity must be positive, got {time_to_maturity}"
680
+ )
681
+ return time_to_maturity
682
+
683
+ if hasattr(portfolio, "get_positions_by_underlying"):
684
+ positions = portfolio.get_positions_by_underlying(underlying)
685
+ else:
686
+ positions = [
687
+ pos for pos in portfolio.positions.values()
688
+ if pos.underlying == underlying
689
+ ]
690
+ for position in positions:
691
+ product = position.product
692
+ if isinstance(product, Futures):
693
+ try:
694
+ time_to_maturity = product.get_maturity(env)
695
+ except Exception:
696
+ continue
697
+ if time_to_maturity is not None and time_to_maturity > 0:
698
+ return float(time_to_maturity)
699
+
700
+ return 1.0
701
+
702
+ def __repr__(self) -> str:
703
+ return f"DynamicScenarioEngine(config={self.config})"