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,800 @@
1
+ """
2
+ Unified facade engine for convertible bond pricing.
3
+
4
+ This module provides ConvertibleBondEngine that dispatches to appropriate
5
+ underlying engines based on method selection.
6
+ """
7
+ import math
8
+ from copy import deepcopy
9
+ from dataclasses import dataclass
10
+ from typing import Dict, Optional, Union
11
+
12
+ from quantark.asset.bond.product.convertible.convertible_bond import ConvertibleBond
13
+ from quantark.asset.bond.engine.tree.convertible import (
14
+ ConvertibleBondTreeParams,
15
+ ConvertibleBondBinomialEngine,
16
+ ConvertibleBondTrinomialEngine,
17
+ )
18
+ from quantark.asset.bond.engine.pde.convertible import (
19
+ ConvertibleBondPDEParams,
20
+ ConvertibleBondJumpDiffusionEngine,
21
+ ConvertibleBondTFEngine,
22
+ )
23
+ from quantark.priceenv import PricingEnvironment
24
+ from quantark.param.rrf import ParallelShiftRateCurve
25
+ from quantark.util.enum.engine_enums import EngineType, ConvertibleBondMethod, PDEMethod
26
+ from quantark.util.exceptions import ValidationError
27
+
28
+
29
+ @dataclass
30
+ class ConvertibleBondResult:
31
+ """
32
+ Comprehensive result container for convertible bond pricing.
33
+
34
+ Attributes:
35
+ price: Clean price of the convertible bond
36
+ dirty_price: Dirty price including accrued interest
37
+ delta: Price sensitivity to stock price
38
+ gamma: Second derivative of price with respect to stock
39
+ conversion_probability: Probability of eventual conversion
40
+ equity_component: Equity-like component of value
41
+ bond_component: Bond-like component of value (COCB for TF model)
42
+ default_probability: Probability of default (trinomial model only)
43
+ method: Method used for pricing
44
+ floor_bond_price: Straight bond price without conversion/options
45
+ floor_bond_dv01: Floor bond DV01 (price change per bp rate move)
46
+ floor_bond_cs01: Floor bond CS01 (price change per bp spread move)
47
+ floor_bond_duration: Floor bond modified duration
48
+ floor_bond_convexity: Floor bond convexity
49
+ dv01: Convertible DV01 (price change per bp rate move)
50
+ cs01: Convertible CS01 (price change per bp spread move)
51
+ modified_duration: Convertible modified duration
52
+ convexity: Convertible convexity
53
+ """
54
+
55
+ price: float
56
+ dirty_price: float
57
+ delta: float = 0.0
58
+ gamma: float = 0.0
59
+ conversion_probability: float = 0.0
60
+ equity_component: float = 0.0
61
+ bond_component: float = 0.0
62
+ default_probability: float = 0.0
63
+ method: str = ""
64
+ # Floor bond metrics
65
+ floor_bond_price: float = 0.0
66
+ floor_bond_dv01: float = 0.0
67
+ floor_bond_cs01: float = 0.0
68
+ floor_bond_duration: float = 0.0
69
+ floor_bond_convexity: float = 0.0
70
+ # Convertible risk metrics
71
+ dv01: float = 0.0
72
+ cs01: float = 0.0
73
+ modified_duration: float = 0.0
74
+ convexity: float = 0.0
75
+
76
+
77
+ class ConvertibleBondEngine:
78
+ """
79
+ Unified facade engine for convertible bond pricing.
80
+
81
+ This engine dispatches pricing requests to specialized engines based on
82
+ the selected method. It supports the two-level enum pattern consistent
83
+ with other engines in the library.
84
+
85
+ Supported Methods:
86
+ Tree-based:
87
+ - BINOMIAL_GS: Goldman Sachs credit-adjusted binomial model
88
+ - TRINOMIAL_HW: Hull-White trinomial with default
89
+
90
+ PDE-based:
91
+ - JUMP_DIFFUSION: Bloomberg OVCV jump-diffusion model
92
+ - TF: Tsiveriotis-Fernandes decomposition
93
+
94
+ Usage:
95
+ # Using two-level enum pattern
96
+ engine = ConvertibleBondEngine(
97
+ pricing_env,
98
+ method=EngineType.TREE(ConvertibleBondMethod.BINOMIAL_GS)
99
+ )
100
+
101
+ # Using single method enum
102
+ engine = ConvertibleBondEngine(
103
+ pricing_env,
104
+ method=ConvertibleBondMethod.JUMP_DIFFUSION
105
+ )
106
+
107
+ # Using string
108
+ engine = ConvertibleBondEngine(pricing_env, method="binomial_gs")
109
+
110
+ # Pricing
111
+ price = engine.price(convertible_bond)
112
+ result = engine.price_with_details(convertible_bond)
113
+ """
114
+
115
+ # Map methods to engine types
116
+ TREE_METHODS = {
117
+ ConvertibleBondMethod.BINOMIAL_GS,
118
+ ConvertibleBondMethod.TRINOMIAL_HW,
119
+ }
120
+
121
+ PDE_METHODS = {
122
+ ConvertibleBondMethod.JUMP_DIFFUSION,
123
+ ConvertibleBondMethod.TF,
124
+ }
125
+
126
+ DEFAULT_METHOD = ConvertibleBondMethod.BINOMIAL_GS
127
+
128
+ def __init__(
129
+ self,
130
+ pricing_env: PricingEnvironment,
131
+ method: Optional[
132
+ Union[str, ConvertibleBondMethod, tuple]
133
+ ] = None,
134
+ tree_params: Optional[ConvertibleBondTreeParams] = None,
135
+ pde_params: Optional[ConvertibleBondPDEParams] = None,
136
+ scheme: Optional[Union[str, PDEMethod]] = None,
137
+ ):
138
+ """
139
+ Initialize the facade engine.
140
+
141
+ Args:
142
+ pricing_env: Pricing environment with market data
143
+ method: Pricing method selection, can be:
144
+ - ConvertibleBondMethod enum
145
+ - String (e.g., "binomial_gs", "jump_diffusion")
146
+ - Tuple from EngineType.TREE(ConvertibleBondMethod.BINOMIAL_GS)
147
+ - None (defaults to BINOMIAL_GS)
148
+ tree_params: Configuration for tree-based engines (optional)
149
+ pde_params: Configuration for PDE-based engines (optional)
150
+ scheme: PDE numerical scheme (optional, for PDE methods only)
151
+
152
+ Raises:
153
+ ValidationError: If invalid method or configuration
154
+ """
155
+ if pricing_env is None:
156
+ raise ValidationError("Pricing environment is required")
157
+
158
+ self.pricing_env = pricing_env
159
+ self.tree_params = tree_params
160
+ self.pde_params = pde_params
161
+ self.scheme = scheme
162
+
163
+ # Parse method
164
+ self.method = self._parse_method(method)
165
+
166
+ # Create appropriate underlying engine
167
+ self._engine = self._create_engine()
168
+
169
+ def _parse_method(
170
+ self, method: Optional[Union[str, ConvertibleBondMethod, tuple]]
171
+ ) -> ConvertibleBondMethod:
172
+ """
173
+ Parse the method argument into a ConvertibleBondMethod enum.
174
+
175
+ Args:
176
+ method: Method specification
177
+
178
+ Returns:
179
+ ConvertibleBondMethod enum value
180
+
181
+ Raises:
182
+ ValidationError: If invalid method
183
+ """
184
+ if method is None:
185
+ return self.DEFAULT_METHOD
186
+
187
+ if isinstance(method, ConvertibleBondMethod):
188
+ return method
189
+
190
+ if isinstance(method, str):
191
+ try:
192
+ return ConvertibleBondMethod(method.lower())
193
+ except ValueError:
194
+ valid_methods = [m.value for m in ConvertibleBondMethod]
195
+ raise ValidationError(
196
+ f"Invalid method: {method}. Valid methods: {valid_methods}"
197
+ )
198
+
199
+ if isinstance(method, tuple):
200
+ if len(method) != 2:
201
+ raise ValidationError(
202
+ f"Invalid method tuple: expected (EngineType, Method), got {method}"
203
+ )
204
+
205
+ engine_type, cb_method = method
206
+
207
+ # Validate engine type matches method
208
+ if engine_type == EngineType.TREE:
209
+ if cb_method not in self.TREE_METHODS:
210
+ raise ValidationError(
211
+ f"Method {cb_method} is not a tree method"
212
+ )
213
+ elif engine_type == EngineType.PDE:
214
+ if cb_method not in self.PDE_METHODS:
215
+ raise ValidationError(
216
+ f"Method {cb_method} is not a PDE method"
217
+ )
218
+ else:
219
+ raise ValidationError(
220
+ f"Unsupported engine type: {engine_type}. "
221
+ f"Expected TREE or PDE."
222
+ )
223
+
224
+ if not isinstance(cb_method, ConvertibleBondMethod):
225
+ raise ValidationError(
226
+ f"Expected ConvertibleBondMethod, got {type(cb_method)}"
227
+ )
228
+
229
+ return cb_method
230
+
231
+ raise ValidationError(
232
+ f"Invalid method type: {type(method)}. "
233
+ f"Expected ConvertibleBondMethod, str, or tuple."
234
+ )
235
+
236
+ def _create_engine(self):
237
+ """
238
+ Create the appropriate underlying engine based on method.
239
+
240
+ Returns:
241
+ Specialized pricing engine instance
242
+ """
243
+ if self.method == ConvertibleBondMethod.BINOMIAL_GS:
244
+ return ConvertibleBondBinomialEngine(
245
+ self.pricing_env, self.tree_params
246
+ )
247
+
248
+ elif self.method == ConvertibleBondMethod.TRINOMIAL_HW:
249
+ return ConvertibleBondTrinomialEngine(
250
+ self.pricing_env, self.tree_params
251
+ )
252
+
253
+ elif self.method == ConvertibleBondMethod.JUMP_DIFFUSION:
254
+ params = self.pde_params or ConvertibleBondPDEParams()
255
+ if self.scheme:
256
+ if isinstance(self.scheme, PDEMethod):
257
+ params.scheme = self.scheme.value
258
+ else:
259
+ params.scheme = str(self.scheme)
260
+ return ConvertibleBondJumpDiffusionEngine(self.pricing_env, params)
261
+
262
+ elif self.method == ConvertibleBondMethod.TF:
263
+ params = self.pde_params or ConvertibleBondPDEParams()
264
+ if self.scheme:
265
+ if isinstance(self.scheme, PDEMethod):
266
+ params.scheme = self.scheme.value
267
+ else:
268
+ params.scheme = str(self.scheme)
269
+ return ConvertibleBondTFEngine(self.pricing_env, params)
270
+
271
+ else:
272
+ raise ValidationError(f"Unsupported method: {self.method}")
273
+
274
+ def price(self, bond: ConvertibleBond) -> float:
275
+ """
276
+ Calculate the clean price of the convertible bond.
277
+
278
+ Args:
279
+ bond: Convertible bond to price
280
+
281
+ Returns:
282
+ Clean price
283
+ """
284
+ return self._engine.price(bond)
285
+
286
+ def price_with_details(
287
+ self, bond: ConvertibleBond, include_risk_metrics: bool = True
288
+ ) -> ConvertibleBondResult:
289
+ """
290
+ Calculate price with detailed results.
291
+
292
+ Dispatches to the appropriate underlying engine and converts
293
+ the result to a unified ConvertibleBondResult.
294
+
295
+ Args:
296
+ bond: Convertible bond to price
297
+ include_risk_metrics: Whether to compute risk metrics (DV01, CS01,
298
+ duration, convexity). Set to False to skip for performance.
299
+
300
+ Returns:
301
+ ConvertibleBondResult with full pricing details
302
+ """
303
+ raw_result = self._engine.price_with_details(bond)
304
+
305
+ # Convert to unified result format
306
+ result = ConvertibleBondResult(
307
+ price=raw_result.price,
308
+ dirty_price=raw_result.dirty_price,
309
+ delta=getattr(raw_result, "delta", 0.0),
310
+ gamma=getattr(raw_result, "gamma", 0.0),
311
+ method=self.method.value,
312
+ )
313
+
314
+ # Extract method-specific fields
315
+ if hasattr(raw_result, "conversion_probability"):
316
+ result.conversion_probability = raw_result.conversion_probability
317
+
318
+ if hasattr(raw_result, "equity_component"):
319
+ result.equity_component = raw_result.equity_component
320
+
321
+ if hasattr(raw_result, "bond_component"):
322
+ result.bond_component = raw_result.bond_component
323
+
324
+ if hasattr(raw_result, "default_probability"):
325
+ result.default_probability = raw_result.default_probability
326
+
327
+ # Compute risk metrics if requested
328
+ if include_risk_metrics:
329
+ # Floor bond metrics (analytical, fast)
330
+ result.floor_bond_price = self.floor_bond_price(bond)
331
+ result.floor_bond_duration = self.floor_bond_duration(bond)
332
+ result.floor_bond_convexity = self.floor_bond_convexity(bond)
333
+ result.floor_bond_dv01 = self.floor_bond_dv01(bond)
334
+ result.floor_bond_cs01 = self.floor_bond_cs01(bond)
335
+
336
+ # Convertible risk metrics (numerical, slower)
337
+ result.dv01 = self.dv01(bond)
338
+ result.cs01 = self.cs01(bond)
339
+ result.modified_duration = self.modified_duration(bond)
340
+ result.convexity = self.convexity(bond)
341
+
342
+ return result
343
+
344
+ def calculate_delta(self, bond: ConvertibleBond) -> float:
345
+ """
346
+ Calculate delta (price sensitivity to stock price).
347
+
348
+ Args:
349
+ bond: Convertible bond
350
+
351
+ Returns:
352
+ Delta
353
+ """
354
+ if hasattr(self._engine, "calculate_delta"):
355
+ return self._engine.calculate_delta(bond)
356
+ result = self.price_with_details(bond)
357
+ return result.delta
358
+
359
+ def calculate_gamma(self, bond: ConvertibleBond) -> float:
360
+ """
361
+ Calculate gamma (second derivative with respect to stock).
362
+
363
+ Args:
364
+ bond: Convertible bond
365
+
366
+ Returns:
367
+ Gamma
368
+ """
369
+ if hasattr(self._engine, "calculate_gamma"):
370
+ return self._engine.calculate_gamma(bond)
371
+ result = self.price_with_details(bond)
372
+ return result.gamma
373
+
374
+ def get_cocb(self, bond: ConvertibleBond) -> float:
375
+ """
376
+ Get Cash-Only Component of Bond (TF model only).
377
+
378
+ Args:
379
+ bond: Convertible bond
380
+
381
+ Returns:
382
+ COCB value
383
+
384
+ Raises:
385
+ ValidationError: If not using TF model
386
+ """
387
+ if self.method != ConvertibleBondMethod.TF:
388
+ raise ValidationError(
389
+ "COCB is only available with the TF model"
390
+ )
391
+ return self._engine.get_cocb(bond)
392
+
393
+ # =========================================================================
394
+ # Floor Bond Methods
395
+ # =========================================================================
396
+
397
+ def _floor_bond_price_with_env(
398
+ self, bond: ConvertibleBond, pricing_env: PricingEnvironment
399
+ ) -> float:
400
+ """
401
+ Calculate floor bond (straight bond) price in a given environment.
402
+
403
+ The floor bond is the value of the convertible assuming no conversion
404
+ and no exercise of call/put options. It represents the investment
405
+ value floor of the convertible bond.
406
+
407
+ Cashflows are discounted at the risky rate (risk-free + credit spread).
408
+
409
+ Args:
410
+ bond: Convertible bond product
411
+ pricing_env: Pricing environment to use
412
+
413
+ Returns:
414
+ Floor bond dirty price
415
+ """
416
+ valuation_date = pricing_env.valuation_date
417
+
418
+ # Check if bond has matured
419
+ if bond.is_expired(valuation_date):
420
+ return 0.0
421
+
422
+ # Get future cashflows
423
+ cashflows = bond.get_cashflows(valuation_date)
424
+
425
+ if not cashflows:
426
+ return 0.0
427
+
428
+ # Get credit spread from bond
429
+ credit_spread = bond.credit_spread if bond.credit_spread else 0.0
430
+
431
+ # Discount each cashflow at risky rate
432
+ pv = 0.0
433
+ for cf in cashflows:
434
+ time_to_payment = (cf.payment_date - valuation_date).days / 365.0
435
+
436
+ if time_to_payment < 0:
437
+ continue
438
+
439
+ # Risk-free discount factor from curve; apply spread as parallel shift
440
+ df = pricing_env.get_discount_factor(time_to_payment)
441
+ risky_df = df * math.exp(-credit_spread * time_to_payment)
442
+ pv += cf.amount * risky_df
443
+
444
+ return pv
445
+
446
+ def floor_bond_price(self, bond: ConvertibleBond) -> float:
447
+ """
448
+ Calculate the floor bond (straight bond) price.
449
+
450
+ Args:
451
+ bond: Convertible bond product
452
+
453
+ Returns:
454
+ Floor bond dirty price
455
+ """
456
+ return self._floor_bond_price_with_env(bond, self.pricing_env)
457
+
458
+ def floor_bond_dv01(self, bond: ConvertibleBond) -> float:
459
+ """
460
+ Calculate DV01 of the floor bond.
461
+
462
+ DV01 is the price change for a 1 basis point parallel increase in rates.
463
+ Uses numerical bumping to remain consistent under non-flat curves.
464
+
465
+ Args:
466
+ bond: Convertible bond product
467
+
468
+ Returns:
469
+ Floor bond DV01 (positive value, price decreases when rates rise)
470
+ """
471
+ rate_bump = 0.0001
472
+ base_price = self.floor_bond_price(bond)
473
+ if base_price == 0.0:
474
+ return 0.0
475
+
476
+ env_up = deepcopy(self.pricing_env)
477
+ env_up.rate_curve = ParallelShiftRateCurve(
478
+ self.pricing_env.rate_curve, shift=rate_bump
479
+ )
480
+ price_up = self._floor_bond_price_with_env(bond, env_up)
481
+
482
+ env_down = deepcopy(self.pricing_env)
483
+ env_down.rate_curve = ParallelShiftRateCurve(
484
+ self.pricing_env.rate_curve, shift=-rate_bump
485
+ )
486
+ price_down = self._floor_bond_price_with_env(bond, env_down)
487
+
488
+ return (price_down - price_up) / 2.0
489
+
490
+ def floor_bond_cs01(self, bond: ConvertibleBond) -> float:
491
+ """
492
+ Calculate CS01 of the floor bond.
493
+
494
+ CS01 is the price change for a 1 basis point increase in credit spread.
495
+ For the floor bond, CS01 equals DV01 since both rate and spread
496
+ affect discounting identically (discount at r + s).
497
+
498
+ Args:
499
+ bond: Convertible bond product
500
+
501
+ Returns:
502
+ Floor bond CS01 (equals DV01)
503
+ """
504
+ # For floor bond, CS01 = DV01 since both enter the discount factor
505
+ return self.floor_bond_dv01(bond)
506
+
507
+ def floor_bond_duration(self, bond: ConvertibleBond) -> float:
508
+ """
509
+ Calculate modified duration of the floor bond.
510
+
511
+ Derived from DV01: Duration = DV01 / (Price * 0.0001).
512
+
513
+ Args:
514
+ bond: Convertible bond product
515
+
516
+ Returns:
517
+ Floor bond modified duration
518
+ """
519
+ price = self.floor_bond_price(bond)
520
+ if price == 0.0:
521
+ return 0.0
522
+ dv01_value = self.floor_bond_dv01(bond)
523
+ return dv01_value / (price * 0.0001)
524
+
525
+ def floor_bond_convexity(self, bond: ConvertibleBond) -> float:
526
+ """
527
+ Calculate convexity of the floor bond.
528
+
529
+ Convexity measures the curvature of the price-yield relationship.
530
+
531
+ Args:
532
+ bond: Convertible bond product
533
+
534
+ Returns:
535
+ Floor bond convexity
536
+ """
537
+ rate_bump = 0.0001
538
+ base_price = self.floor_bond_price(bond)
539
+ if base_price == 0.0:
540
+ return 0.0
541
+
542
+ env_up = deepcopy(self.pricing_env)
543
+ env_up.rate_curve = ParallelShiftRateCurve(
544
+ self.pricing_env.rate_curve, shift=rate_bump
545
+ )
546
+ price_up = self._floor_bond_price_with_env(bond, env_up)
547
+
548
+ env_down = deepcopy(self.pricing_env)
549
+ env_down.rate_curve = ParallelShiftRateCurve(
550
+ self.pricing_env.rate_curve, shift=-rate_bump
551
+ )
552
+ price_down = self._floor_bond_price_with_env(bond, env_down)
553
+
554
+ return (price_up + price_down - 2.0 * base_price) / (
555
+ base_price * rate_bump * rate_bump
556
+ )
557
+
558
+ # =========================================================================
559
+ # Convertible Bond Risk Metrics (Numerical)
560
+ # =========================================================================
561
+
562
+ def dv01(self, bond: ConvertibleBond) -> float:
563
+ """
564
+ Calculate DV01 of the convertible bond.
565
+
566
+ Uses numerical rate bumping since the convertible has embedded options.
567
+ Only the risk-free rate is bumped, isolating interest rate risk.
568
+
569
+ Args:
570
+ bond: Convertible bond product
571
+
572
+ Returns:
573
+ Convertible DV01 (positive value, price decreases when rates rise)
574
+ """
575
+ rate_bump = 0.0001 # 1 basis point
576
+
577
+ # Price with rate bumped up
578
+ env_up = deepcopy(self.pricing_env)
579
+ env_up.rate_curve = ParallelShiftRateCurve(
580
+ self.pricing_env.rate_curve, shift=rate_bump
581
+ )
582
+ engine_up = self._create_bumped_engine(env_up)
583
+ price_up = engine_up.price(bond)
584
+
585
+ # Price with rate bumped down
586
+ env_down = deepcopy(self.pricing_env)
587
+ env_down.rate_curve = ParallelShiftRateCurve(
588
+ self.pricing_env.rate_curve, shift=-rate_bump
589
+ )
590
+ engine_down = self._create_bumped_engine(env_down)
591
+ price_down = engine_down.price(bond)
592
+
593
+ # DV01 = (price_down - price_up) / 2 (central difference)
594
+ # Note: price falls when rate rises, so DV01 is positive
595
+ return (price_down - price_up) / 2
596
+
597
+ def cs01(self, bond: ConvertibleBond) -> float:
598
+ """
599
+ Calculate CS01 of the convertible bond.
600
+
601
+ Uses numerical credit bumping.
602
+
603
+ - For BINOMIAL_GS: bumps credit_spread directly (GS credit-adjusted discounting).
604
+ - For TRINOMIAL_HW / JUMP_DIFFUSION / TF: bumps hazard_rate using an
605
+ approximate mapping from spread shift to intensity shift:
606
+ d(lambda) ~= d(spread) / (1 - recovery)
607
+
608
+ Args:
609
+ bond: Convertible bond product
610
+
611
+ Returns:
612
+ Convertible CS01 (positive value, price decreases when spread rises)
613
+ """
614
+ spread_bump = 0.0001 # 1 basis point
615
+
616
+ if self.method == ConvertibleBondMethod.BINOMIAL_GS:
617
+ # Create bond with bumped spread up
618
+ base_spread = bond.credit_spread if bond.credit_spread else 0.0
619
+ bond_up = self._create_spread_bumped_bond(
620
+ bond, base_spread + spread_bump
621
+ )
622
+ price_up = self.price(bond_up)
623
+
624
+ # Create bond with bumped spread down
625
+ bond_down = self._create_spread_bumped_bond(
626
+ bond, max(0.0, base_spread - spread_bump)
627
+ )
628
+ price_down = self.price(bond_down)
629
+
630
+ # CS01 = (price_down - price_up) / 2 (central difference)
631
+ return (price_down - price_up) / 2
632
+
633
+ # Hazard-based models: bump hazard_rate
634
+ recovery = bond.recovery_rate if bond.recovery_rate is not None else 0.0
635
+ denom = max(1e-8, 1.0 - recovery)
636
+ hazard_bump = spread_bump / denom
637
+
638
+ base_hazard = bond.hazard_rate if bond.hazard_rate else 0.0
639
+ bond_up = self._create_hazard_bumped_bond(
640
+ bond, base_hazard + hazard_bump
641
+ )
642
+ price_up = self.price(bond_up)
643
+
644
+ bond_down = self._create_hazard_bumped_bond(
645
+ bond, max(0.0, base_hazard - hazard_bump)
646
+ )
647
+ price_down = self.price(bond_down)
648
+
649
+ # CS01 = (price_down - price_up) / 2 (central difference)
650
+ return (price_down - price_up) / 2
651
+
652
+ def modified_duration(self, bond: ConvertibleBond) -> float:
653
+ """
654
+ Calculate modified duration of the convertible bond.
655
+
656
+ Derived from DV01: Duration = DV01 / (Price * 0.0001)
657
+
658
+ Args:
659
+ bond: Convertible bond product
660
+
661
+ Returns:
662
+ Convertible modified duration
663
+ """
664
+ price = self.price(bond)
665
+ if price == 0:
666
+ return 0.0
667
+
668
+ dv01_value = self.dv01(bond)
669
+ return dv01_value / (price * 0.0001)
670
+
671
+ def convexity(self, bond: ConvertibleBond) -> float:
672
+ """
673
+ Calculate convexity of the convertible bond.
674
+
675
+ Uses central difference with rate bumps.
676
+
677
+ Args:
678
+ bond: Convertible bond product
679
+
680
+ Returns:
681
+ Convertible convexity
682
+ """
683
+ rate_bump = 0.0001 # 1 basis point
684
+ base_price = self.price(bond)
685
+
686
+ if base_price == 0:
687
+ return 0.0
688
+
689
+ # Price with rate bumped up
690
+ env_up = deepcopy(self.pricing_env)
691
+ env_up.rate_curve = ParallelShiftRateCurve(
692
+ self.pricing_env.rate_curve, shift=rate_bump
693
+ )
694
+ engine_up = self._create_bumped_engine(env_up)
695
+ price_up = engine_up.price(bond)
696
+
697
+ # Price with rate bumped down
698
+ env_down = deepcopy(self.pricing_env)
699
+ env_down.rate_curve = ParallelShiftRateCurve(
700
+ self.pricing_env.rate_curve, shift=-rate_bump
701
+ )
702
+ engine_down = self._create_bumped_engine(env_down)
703
+ price_down = engine_down.price(bond)
704
+
705
+ # Convexity = (P_up + P_down - 2*P_base) / (P_base * bump^2)
706
+ return (price_up + price_down - 2 * base_price) / (base_price * rate_bump * rate_bump)
707
+
708
+ def _create_bumped_engine(self, bumped_env: PricingEnvironment):
709
+ """
710
+ Create a new engine instance with a bumped pricing environment.
711
+
712
+ Args:
713
+ bumped_env: Pricing environment with bumped parameters
714
+
715
+ Returns:
716
+ New engine instance
717
+ """
718
+ if self.method == ConvertibleBondMethod.BINOMIAL_GS:
719
+ return ConvertibleBondBinomialEngine(bumped_env, self.tree_params)
720
+ elif self.method == ConvertibleBondMethod.TRINOMIAL_HW:
721
+ return ConvertibleBondTrinomialEngine(bumped_env, self.tree_params)
722
+ elif self.method == ConvertibleBondMethod.JUMP_DIFFUSION:
723
+ params = self.pde_params or ConvertibleBondPDEParams()
724
+ return ConvertibleBondJumpDiffusionEngine(bumped_env, params)
725
+ elif self.method == ConvertibleBondMethod.TF:
726
+ params = self.pde_params or ConvertibleBondPDEParams()
727
+ return ConvertibleBondTFEngine(bumped_env, params)
728
+ else:
729
+ raise ValidationError(f"Unsupported method: {self.method}")
730
+
731
+ def _create_spread_bumped_bond(
732
+ self, bond: ConvertibleBond, new_spread: float
733
+ ) -> ConvertibleBond:
734
+ """
735
+ Create a copy of the bond with a bumped credit spread.
736
+
737
+ Args:
738
+ bond: Original convertible bond
739
+ new_spread: New credit spread value
740
+
741
+ Returns:
742
+ New bond with modified credit spread
743
+ """
744
+ return ConvertibleBond(
745
+ issue_date=bond.issue_date,
746
+ maturity_date=bond.maturity_date,
747
+ face_value=bond.face_value,
748
+ coupon_rate=bond.coupon_rate,
749
+ conversion_ratio=bond.conversion_ratio,
750
+ conversion_price=bond.conversion_price,
751
+ payment_frequency=bond.payment_frequency,
752
+ day_count_convention=bond.day_count_convention,
753
+ conversion_start_date=bond.conversion_start_date,
754
+ conversion_end_date=bond.conversion_end_date,
755
+ call_schedule=bond.call_schedule,
756
+ put_schedule=bond.put_schedule,
757
+ credit_spread=new_spread,
758
+ hazard_rate=bond.hazard_rate,
759
+ recovery_rate=bond.recovery_rate,
760
+ stock_jump_on_default=bond.stock_jump_on_default,
761
+ continuous_dividend_yield=bond.continuous_dividend_yield,
762
+ discrete_dividends=bond.discrete_dividends,
763
+ )
764
+
765
+ def _create_hazard_bumped_bond(
766
+ self, bond: ConvertibleBond, new_hazard_rate: float
767
+ ) -> ConvertibleBond:
768
+ """
769
+ Create a copy of the bond with a bumped hazard rate.
770
+
771
+ Args:
772
+ bond: Original convertible bond
773
+ new_hazard_rate: New hazard rate value
774
+
775
+ Returns:
776
+ New bond with modified hazard rate
777
+ """
778
+ return ConvertibleBond(
779
+ issue_date=bond.issue_date,
780
+ maturity_date=bond.maturity_date,
781
+ face_value=bond.face_value,
782
+ coupon_rate=bond.coupon_rate,
783
+ conversion_ratio=bond.conversion_ratio,
784
+ conversion_price=bond.conversion_price,
785
+ payment_frequency=bond.payment_frequency,
786
+ day_count_convention=bond.day_count_convention,
787
+ conversion_start_date=bond.conversion_start_date,
788
+ conversion_end_date=bond.conversion_end_date,
789
+ call_schedule=bond.call_schedule,
790
+ put_schedule=bond.put_schedule,
791
+ credit_spread=bond.credit_spread,
792
+ hazard_rate=new_hazard_rate,
793
+ recovery_rate=bond.recovery_rate,
794
+ stock_jump_on_default=bond.stock_jump_on_default,
795
+ continuous_dividend_yield=bond.continuous_dividend_yield,
796
+ discrete_dividends=bond.discrete_dividends,
797
+ )
798
+
799
+ def __repr__(self):
800
+ return f"ConvertibleBondEngine(method={self.method.value})"