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,102 @@
1
+ """
2
+ Cash-or-nothing European digital option implementation.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+ from datetime import datetime
8
+ from .base_equity_option import BaseEquityOption
9
+ from quantark.util.enum import OptionType, ExerciseType
10
+ from quantark.util.exceptions import ValidationError
11
+
12
+
13
+ @dataclass
14
+ class CashOrNothingDigitalOption(BaseEquityOption):
15
+ """
16
+ European cash-or-nothing digital call or put.
17
+
18
+ Pays a fixed cash amount if the terminal spot is on the paying side of
19
+ the strike (S > K for calls, S < K for puts), otherwise pays zero.
20
+ """
21
+
22
+ payout: float = 0.0
23
+
24
+ def __init__(
25
+ self,
26
+ strike: float,
27
+ payout: float,
28
+ option_type: OptionType,
29
+ maturity: Optional[float] = None,
30
+ exercise_date: Optional[datetime] = None,
31
+ settlement_date: Optional[datetime] = None,
32
+ contract_multiplier: float = 1.0,
33
+ ):
34
+ """
35
+ Initialize cash-or-nothing digital option.
36
+
37
+ Args:
38
+ strike: Strike price
39
+ payout: Fixed cash payout when in the money at maturity
40
+ option_type: CALL or PUT
41
+ maturity: Time to maturity in years (optional if exercise_date provided)
42
+ exercise_date: Date when option can be exercised (optional if maturity provided)
43
+ settlement_date: Date when settlement occurs (optional, defaults to exercise_date)
44
+
45
+ Note:
46
+ Either maturity OR exercise_date must be provided (not both).
47
+ """
48
+ if maturity is None and exercise_date is None:
49
+ maturity = 0.0 # Will trigger validation error
50
+ elif maturity is None:
51
+ maturity = 0.0 # Placeholder when using dates
52
+
53
+ self.payout = payout
54
+
55
+ super().__init__(
56
+ strike=strike,
57
+ maturity=maturity,
58
+ option_type=option_type,
59
+ exercise_type=ExerciseType.EUROPEAN,
60
+ exercise_date=exercise_date,
61
+ settlement_date=settlement_date,
62
+ contract_multiplier=contract_multiplier,
63
+ )
64
+
65
+ def validate(self) -> None:
66
+ """Validate option parameters."""
67
+ super().validate()
68
+
69
+ if self.payout <= 0:
70
+ raise ValidationError(f"Payout must be positive, got {self.payout}")
71
+
72
+ if self.exercise_type != ExerciseType.EUROPEAN:
73
+ raise ValidationError(
74
+ f"CashOrNothingDigitalOption only supports European exercise, got {self.exercise_type}"
75
+ )
76
+
77
+ def get_payoff(self, spot: float) -> float:
78
+ """
79
+ Calculate the digital option payoff at maturity.
80
+
81
+ For a call: payout if S > K else 0
82
+ For a put: payout if S < K else 0
83
+ """
84
+ if spot < 0:
85
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
86
+
87
+ if self.is_call():
88
+ payoff = self.payout if spot > self.strike else 0.0
89
+ else:
90
+ payoff = self.payout if spot < self.strike else 0.0
91
+
92
+ return payoff * self.contract_multiplier
93
+
94
+ def intrinsic_value(self, spot: float) -> float:
95
+ """Intrinsic value equals digital payoff."""
96
+ return self.get_payoff(spot)
97
+
98
+ def __repr__(self):
99
+ return (
100
+ f"CashOrNothingDigitalOption("
101
+ f"{self.option_type}, K={self.strike:.4f}, P={self.payout:.4f}, T={self.maturity:.4f})"
102
+ )
@@ -0,0 +1,286 @@
1
+ """
2
+ Double barrier option implementation.
3
+
4
+ Double barrier options have both an upper and lower barrier.
5
+ The option knocks in or out if either barrier is breached.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Optional, List
10
+ from datetime import datetime
11
+ from .base_equity_option import BaseEquityOption
12
+ from .observation_schedule import ObservationRecord, ObservationSchedule
13
+ from quantark.util.enum import (
14
+ OptionType,
15
+ ExerciseType,
16
+ DoubleBarrierType,
17
+ ObservationType,
18
+ ObservationAggregation,
19
+ )
20
+ from quantark.util.exceptions import ValidationError
21
+
22
+
23
+ @dataclass
24
+ class DoubleBarrierOption(BaseEquityOption):
25
+ """
26
+ Double barrier option (knock-in or knock-out with two barriers).
27
+
28
+ Double barrier options have both an upper barrier (above spot) and
29
+ a lower barrier (below spot). The option:
30
+ - KNOCK_OUT: Deactivates if either barrier is hit
31
+ - KNOCK_IN: Activates if either barrier is hit
32
+
33
+ These are sometimes called "corridor options" or "range options".
34
+
35
+ Attributes:
36
+ strike: Strike price
37
+ maturity: Time to maturity in years
38
+ option_type: CALL or PUT
39
+ upper_barrier: Upper barrier price (above current spot)
40
+ lower_barrier: Lower barrier price (below current spot)
41
+ barrier_type: KNOCK_IN or KNOCK_OUT
42
+ rebate: Amount paid if knocked out (or not knocked in)
43
+ observation_type: CONTINUOUS or DISCRETE monitoring
44
+ observation_dates: For discrete, list of observation times (year fractions)
45
+ """
46
+
47
+ upper_barrier: float = 0.0
48
+ lower_barrier: float = 0.0
49
+ barrier_type: DoubleBarrierType = DoubleBarrierType.KNOCK_OUT
50
+ rebate: float = 0.0
51
+ observation_type: ObservationType = ObservationType.CONTINUOUS
52
+ observation_dates: Optional[List[float]] = None
53
+ observation_schedule: Optional[ObservationSchedule] = None
54
+
55
+ def __init__(
56
+ self,
57
+ strike: float,
58
+ option_type: OptionType,
59
+ upper_barrier: float,
60
+ lower_barrier: float,
61
+ barrier_type: DoubleBarrierType,
62
+ maturity: Optional[float] = None,
63
+ exercise_date: Optional[datetime] = None,
64
+ settlement_date: Optional[datetime] = None,
65
+ rebate: float = 0.0,
66
+ observation_type: ObservationType = ObservationType.CONTINUOUS,
67
+ observation_dates: Optional[List[float]] = None,
68
+ observation_schedule: Optional[ObservationSchedule] = None,
69
+ contract_multiplier: float = 1.0,
70
+ ):
71
+ """
72
+ Initialize double barrier option.
73
+
74
+ Args:
75
+ strike: Strike price
76
+ option_type: CALL or PUT
77
+ upper_barrier: Upper barrier price
78
+ lower_barrier: Lower barrier price
79
+ barrier_type: KNOCK_IN or KNOCK_OUT
80
+ maturity: Time to maturity in years (optional if exercise_date provided)
81
+ exercise_date: Expiration date (optional if maturity provided)
82
+ settlement_date: Settlement date (optional)
83
+ rebate: Rebate amount (default: 0)
84
+ observation_type: CONTINUOUS or DISCRETE (default: CONTINUOUS)
85
+ observation_dates: For discrete, list of observation times
86
+
87
+ Raises:
88
+ ValidationError: If parameters are invalid
89
+ """
90
+ # Default maturity handling
91
+ if maturity is None and exercise_date is None:
92
+ maturity = 0.0
93
+ elif maturity is None:
94
+ maturity = 0.0
95
+
96
+ # Store barrier parameters before calling super().__init__
97
+ self.upper_barrier = upper_barrier
98
+ self.lower_barrier = lower_barrier
99
+ self.barrier_type = barrier_type
100
+ self.rebate = rebate
101
+ self.observation_type = observation_type
102
+ self.observation_dates = observation_dates
103
+ self.observation_schedule = observation_schedule
104
+
105
+ super().__init__(
106
+ strike=strike,
107
+ maturity=maturity,
108
+ option_type=option_type,
109
+ exercise_type=ExerciseType.EUROPEAN,
110
+ exercise_date=exercise_date,
111
+ settlement_date=settlement_date,
112
+ contract_multiplier=contract_multiplier,
113
+ )
114
+
115
+ def validate(self) -> None:
116
+ """
117
+ Validate double barrier option parameters.
118
+
119
+ Raises:
120
+ ValidationError: If parameters are invalid
121
+ """
122
+ super().validate()
123
+
124
+ if self.upper_barrier <= 0:
125
+ raise ValidationError(
126
+ f"Upper barrier must be positive, got {self.upper_barrier}"
127
+ )
128
+
129
+ if self.lower_barrier <= 0:
130
+ raise ValidationError(
131
+ f"Lower barrier must be positive, got {self.lower_barrier}"
132
+ )
133
+
134
+ if self.lower_barrier >= self.upper_barrier:
135
+ raise ValidationError(
136
+ f"Lower barrier ({self.lower_barrier}) must be less than "
137
+ f"upper barrier ({self.upper_barrier})"
138
+ )
139
+
140
+ if self.rebate < 0:
141
+ raise ValidationError(f"Rebate must be non-negative, got {self.rebate}")
142
+
143
+ if not isinstance(self.barrier_type, DoubleBarrierType):
144
+ raise ValidationError(f"Invalid barrier type: {self.barrier_type}")
145
+
146
+ if not isinstance(self.observation_type, ObservationType):
147
+ raise ValidationError(f"Invalid observation type: {self.observation_type}")
148
+
149
+ # Strike should be between barriers for knock-out to make sense
150
+ if self.barrier_type == DoubleBarrierType.KNOCK_OUT:
151
+ if not (self.lower_barrier < self.strike < self.upper_barrier):
152
+ # This is a warning, not an error - could be intentional
153
+ pass
154
+
155
+ # For discrete observation, must have observation dates unless schedule supplied
156
+ if (
157
+ self.observation_type == ObservationType.DISCRETE
158
+ and self.observation_schedule is None
159
+ and (self.observation_dates is None or len(self.observation_dates) == 0)
160
+ ):
161
+ raise ValidationError(
162
+ "Observation dates required for discrete barrier monitoring"
163
+ )
164
+
165
+ # Normalize observation schedule (preferred) or legacy dates for discrete monitoring
166
+ if self.observation_schedule is not None:
167
+ if self.observation_type == ObservationType.CONTINUOUS:
168
+ raise ValidationError("ObservationSchedule requires DISCRETE observation_type.")
169
+ normalized_schedule = ObservationSchedule(
170
+ records=[
171
+ ObservationRecord(
172
+ observation_time=rec.observation_time,
173
+ observation_date=rec.observation_date,
174
+ upper_barrier=rec.upper_barrier if rec.upper_barrier is not None else self.upper_barrier,
175
+ lower_barrier=rec.lower_barrier if rec.lower_barrier is not None else self.lower_barrier,
176
+ payoff=rec.payoff if rec.payoff is not None else self.rebate,
177
+ return_rate=rec.return_rate,
178
+ )
179
+ for rec in self.observation_schedule.records
180
+ ],
181
+ aggregation_mode=self.observation_schedule.aggregation_mode,
182
+ frequency=self.observation_schedule.frequency,
183
+ )
184
+ normalized_schedule.validate(require_double=True)
185
+ self.observation_schedule = normalized_schedule
186
+ if self.observation_schedule.times:
187
+ self.observation_dates = self.observation_schedule.times
188
+ self.observation_type = ObservationType.DISCRETE
189
+ elif self.observation_type == ObservationType.DISCRETE:
190
+ self.observation_schedule = ObservationSchedule.from_legacy(
191
+ observation_dates=self.observation_dates or [],
192
+ default_barrier=None,
193
+ default_payoff=self.rebate,
194
+ aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
195
+ upper_barrier=self.upper_barrier,
196
+ lower_barrier=self.lower_barrier,
197
+ )
198
+ self.observation_dates = self.observation_schedule.times
199
+
200
+ def get_payoff(self, spot: float) -> float:
201
+ """
202
+ Calculate the option payoff at maturity.
203
+
204
+ Note: This returns the vanilla payoff. The actual payoff depends on
205
+ whether either barrier was hit during the option's life.
206
+
207
+ Args:
208
+ spot: Spot price at maturity
209
+
210
+ Returns:
211
+ Option payoff (call or put style)
212
+ """
213
+ if spot < 0:
214
+ raise ValidationError(f"Spot price must be non-negative, got {spot}")
215
+
216
+ if self.is_call():
217
+ intrinsic = max(spot - self.strike, 0.0)
218
+ else:
219
+ intrinsic = max(self.strike - spot, 0.0)
220
+
221
+ return intrinsic * self.contract_multiplier
222
+
223
+ def intrinsic_value(self, spot: float) -> float:
224
+ """
225
+ Calculate intrinsic value.
226
+
227
+ Args:
228
+ spot: Current spot price
229
+
230
+ Returns:
231
+ Intrinsic value
232
+ """
233
+ return self.get_payoff(spot)
234
+
235
+ @property
236
+ def is_knock_in(self) -> bool:
237
+ """Check if this is a knock-in barrier."""
238
+ return self.barrier_type == DoubleBarrierType.KNOCK_IN
239
+
240
+ @property
241
+ def is_knock_out(self) -> bool:
242
+ """Check if this is a knock-out barrier."""
243
+ return self.barrier_type == DoubleBarrierType.KNOCK_OUT
244
+
245
+ def is_barrier_hit(self, spot: float) -> bool:
246
+ """
247
+ Check if either barrier would be hit at a given spot price.
248
+
249
+ Args:
250
+ spot: Spot price to check
251
+
252
+ Returns:
253
+ True if either barrier is hit
254
+ """
255
+ return spot >= self.upper_barrier or spot <= self.lower_barrier
256
+
257
+ def is_in_corridor(self, spot: float) -> bool:
258
+ """
259
+ Check if spot is within the corridor (between barriers).
260
+
261
+ Args:
262
+ spot: Spot price to check
263
+
264
+ Returns:
265
+ True if spot is between lower and upper barriers
266
+ """
267
+ return self.lower_barrier < spot < self.upper_barrier
268
+
269
+ @property
270
+ def corridor_width(self) -> float:
271
+ """Get the width of the corridor in price terms."""
272
+ return self.upper_barrier - self.lower_barrier
273
+
274
+ @property
275
+ def corridor_width_log(self) -> float:
276
+ """Get the width of the corridor in log-price terms."""
277
+ import math
278
+ return math.log(self.upper_barrier / self.lower_barrier)
279
+
280
+ def __repr__(self):
281
+ return (
282
+ f"DoubleBarrierOption("
283
+ f"{self.option_type}, K={self.strike:.2f}, "
284
+ f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
285
+ f"{self.barrier_type}, T={self.maturity:.4f})"
286
+ )
@@ -0,0 +1,310 @@
1
+ """
2
+ Double one-touch option implementation.
3
+
4
+ Double one-touch options have two barriers (upper and lower) and pay
5
+ a fixed rebate if either barrier is touched (or neither, for double no-touch).
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from typing import Optional, List
10
+ from datetime import datetime
11
+ from ..base_equity_product import BaseEquityProduct
12
+ from .observation_schedule import ObservationRecord, ObservationSchedule
13
+ from quantark.util.enum import (
14
+ ObservationType,
15
+ ObservationAggregation,
16
+ TouchType,
17
+ )
18
+ from quantark.util.exceptions import ValidationError
19
+
20
+
21
+ @dataclass
22
+ class DoubleOneTouchOption(BaseEquityProduct):
23
+ """
24
+ Double one-touch or double no-touch option.
25
+
26
+ Double one-touch options pay a fixed rebate if EITHER barrier is touched.
27
+ Double no-touch options pay a fixed rebate if NEITHER barrier is touched.
28
+
29
+ These are also known as:
30
+ - Double one-touch: "range" or "boundary" options
31
+ - Double no-touch: "range binary" or "range accrual" options
32
+
33
+ Attributes:
34
+ upper_barrier: Upper barrier price
35
+ lower_barrier: Lower barrier price
36
+ rebate: Amount paid when condition is satisfied (default: 1.0)
37
+ maturity: Time to maturity in years
38
+ payment_at_hit: For one-touch, True = pay on hit, False = pay at expiry
39
+ touch_type: DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH
40
+ observation_type: CONTINUOUS or DISCRETE monitoring
41
+ observation_dates: For discrete, list of observation times
42
+ """
43
+
44
+ upper_barrier: float = 0.0
45
+ lower_barrier: float = 0.0
46
+ rebate: float = 1.0
47
+ maturity: float = 0.0
48
+ payment_at_hit: bool = True
49
+ touch_type: TouchType = TouchType.DOUBLE_ONE_TOUCH
50
+ observation_type: ObservationType = ObservationType.CONTINUOUS
51
+ observation_dates: Optional[List[float]] = None
52
+ observation_schedule: Optional[ObservationSchedule] = None
53
+ exercise_date: Optional[datetime] = None
54
+ settlement_date: Optional[datetime] = None
55
+
56
+ def __init__(
57
+ self,
58
+ upper_barrier: float,
59
+ lower_barrier: float,
60
+ maturity: Optional[float] = None,
61
+ exercise_date: Optional[datetime] = None,
62
+ settlement_date: Optional[datetime] = None,
63
+ rebate: float = 1.0,
64
+ payment_at_hit: bool = True,
65
+ touch_type: TouchType = TouchType.DOUBLE_ONE_TOUCH,
66
+ observation_type: ObservationType = ObservationType.CONTINUOUS,
67
+ observation_dates: Optional[List[float]] = None,
68
+ observation_schedule: Optional[ObservationSchedule] = None,
69
+ ):
70
+ """
71
+ Initialize double one-touch option.
72
+
73
+ Args:
74
+ upper_barrier: Upper barrier price
75
+ lower_barrier: Lower barrier price
76
+ maturity: Time to maturity in years (optional if exercise_date provided)
77
+ exercise_date: Expiration date (optional if maturity provided)
78
+ settlement_date: Settlement date (optional)
79
+ rebate: Payment amount (default: 1.0)
80
+ payment_at_hit: For one-touch, True = pay on hit (default: True)
81
+ touch_type: DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH
82
+ observation_type: CONTINUOUS or DISCRETE
83
+ observation_dates: For discrete, list of observation times
84
+
85
+ Raises:
86
+ ValidationError: If parameters are invalid
87
+ """
88
+ # Handle maturity vs exercise_date
89
+ if maturity is None and exercise_date is None:
90
+ maturity = 0.0
91
+ elif maturity is None:
92
+ maturity = 0.0
93
+
94
+ self.upper_barrier = upper_barrier
95
+ self.lower_barrier = lower_barrier
96
+ self.rebate = rebate
97
+ self.maturity = maturity
98
+ self.payment_at_hit = payment_at_hit
99
+ self.touch_type = touch_type
100
+ self.observation_type = observation_type
101
+ self.observation_dates = observation_dates
102
+ self.observation_schedule = observation_schedule
103
+ self.exercise_date = exercise_date
104
+ self.settlement_date = settlement_date
105
+
106
+ self.validate()
107
+
108
+ def validate(self) -> None:
109
+ """
110
+ Validate double one-touch option parameters.
111
+
112
+ Raises:
113
+ ValidationError: If parameters are invalid
114
+ """
115
+ if self.upper_barrier <= 0:
116
+ raise ValidationError(
117
+ f"Upper barrier must be positive, got {self.upper_barrier}"
118
+ )
119
+
120
+ if self.lower_barrier <= 0:
121
+ raise ValidationError(
122
+ f"Lower barrier must be positive, got {self.lower_barrier}"
123
+ )
124
+
125
+ if self.lower_barrier >= self.upper_barrier:
126
+ raise ValidationError(
127
+ f"Lower barrier ({self.lower_barrier}) must be less than "
128
+ f"upper barrier ({self.upper_barrier})"
129
+ )
130
+
131
+ if self.rebate < 0:
132
+ raise ValidationError(f"Rebate must be non-negative, got {self.rebate}")
133
+
134
+ if self.touch_type not in (TouchType.DOUBLE_ONE_TOUCH, TouchType.DOUBLE_NO_TOUCH):
135
+ raise ValidationError(
136
+ f"Touch type must be DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH, "
137
+ f"got {self.touch_type}"
138
+ )
139
+
140
+ # Validate maturity
141
+ has_dates = self.exercise_date is not None
142
+ has_maturity = self.maturity is not None and self.maturity > 0
143
+
144
+ if not has_dates and not has_maturity:
145
+ raise ValidationError("Either maturity or exercise_date must be provided")
146
+
147
+ # For discrete observation, must have observation dates
148
+ if (
149
+ self.observation_type == ObservationType.DISCRETE
150
+ and self.observation_schedule is None
151
+ and (self.observation_dates is None or len(self.observation_dates) == 0)
152
+ ):
153
+ raise ValidationError(
154
+ "Observation dates required for discrete barrier monitoring"
155
+ )
156
+
157
+ # Normalize observation schedule (preferred) or legacy dates for discrete monitoring
158
+ if self.observation_schedule is not None:
159
+ if self.observation_type == ObservationType.CONTINUOUS:
160
+ raise ValidationError("ObservationSchedule requires DISCRETE observation_type.")
161
+ normalized_schedule = ObservationSchedule(
162
+ records=[
163
+ ObservationRecord(
164
+ observation_time=rec.observation_time,
165
+ observation_date=rec.observation_date,
166
+ upper_barrier=rec.upper_barrier if rec.upper_barrier is not None else self.upper_barrier,
167
+ lower_barrier=rec.lower_barrier if rec.lower_barrier is not None else self.lower_barrier,
168
+ payoff=rec.payoff if rec.payoff is not None else self.rebate,
169
+ return_rate=rec.return_rate,
170
+ )
171
+ for rec in self.observation_schedule.records
172
+ ],
173
+ aggregation_mode=self.observation_schedule.aggregation_mode,
174
+ frequency=self.observation_schedule.frequency,
175
+ )
176
+ normalized_schedule.validate(require_double=True)
177
+ self.observation_schedule = normalized_schedule
178
+ if self.observation_schedule.times:
179
+ self.observation_dates = self.observation_schedule.times
180
+ self.observation_type = ObservationType.DISCRETE
181
+ elif self.observation_type == ObservationType.DISCRETE:
182
+ self.observation_schedule = ObservationSchedule.from_legacy(
183
+ observation_dates=self.observation_dates or [],
184
+ default_barrier=None,
185
+ default_payoff=self.rebate,
186
+ aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
187
+ upper_barrier=self.upper_barrier,
188
+ lower_barrier=self.lower_barrier,
189
+ )
190
+ self.observation_dates = self.observation_schedule.times
191
+
192
+ def get_maturity(self, pricing_env=None) -> float:
193
+ """
194
+ Get time to maturity in years.
195
+
196
+ Args:
197
+ pricing_env: Pricing environment (required if using dates)
198
+
199
+ Returns:
200
+ Time to maturity in years
201
+ """
202
+ if self.exercise_date is not None:
203
+ if pricing_env is None:
204
+ raise ValidationError(
205
+ "PricingEnvironment required for date-based maturity calculation"
206
+ )
207
+ from quantark.util.calendar import calculate_year_fraction
208
+
209
+ return calculate_year_fraction(
210
+ pricing_env.valuation_date,
211
+ self.exercise_date,
212
+ pricing_env.day_count_convention,
213
+ pricing_env.bus_days_in_year,
214
+ calendar=getattr(pricing_env, "calendar", None),
215
+ )
216
+ else:
217
+ return self.maturity
218
+
219
+ def time_shift(self, time_bump: float, bumped_date: datetime, pricing_env) -> bool:
220
+ """Shift observation schedule and maturity for theta bumping."""
221
+ schedule = getattr(self, "observation_schedule", None)
222
+ if schedule is not None:
223
+ if schedule.uses_dates():
224
+ pricing_env.valuation_date = bumped_date
225
+ bumped_schedule = schedule.time_shift(time_bump, bumped_date)
226
+ if bumped_schedule is None:
227
+ return True
228
+ self.observation_schedule = bumped_schedule
229
+ if hasattr(self, "observation_dates") and bumped_schedule.uses_times():
230
+ self.observation_dates = bumped_schedule.times
231
+
232
+ if getattr(self, "exercise_date", None) is None:
233
+ if getattr(self, "maturity", None) is not None:
234
+ self.maturity -= time_bump
235
+ else:
236
+ pricing_env.valuation_date = bumped_date
237
+
238
+ return False
239
+
240
+ def get_payoff(self, spot: float, touched: bool = False) -> float:
241
+ """
242
+ Calculate the option payoff.
243
+
244
+ For double one-touch: pays rebate if either barrier touched
245
+ For double no-touch: pays rebate if neither barrier touched
246
+
247
+ Args:
248
+ spot: Spot price (not directly used)
249
+ touched: Whether any barrier was touched
250
+
251
+ Returns:
252
+ Option payoff
253
+ """
254
+ if self.is_double_one_touch:
255
+ return self.rebate if touched else 0.0
256
+ else: # Double no-touch
257
+ return self.rebate if not touched else 0.0
258
+
259
+ @property
260
+ def is_double_one_touch(self) -> bool:
261
+ """Check if this is a double one-touch option."""
262
+ return self.touch_type == TouchType.DOUBLE_ONE_TOUCH
263
+
264
+ @property
265
+ def is_double_no_touch(self) -> bool:
266
+ """Check if this is a double no-touch option."""
267
+ return self.touch_type == TouchType.DOUBLE_NO_TOUCH
268
+
269
+ def is_barrier_hit(self, spot: float) -> bool:
270
+ """
271
+ Check if either barrier would be hit at a given spot price.
272
+
273
+ Args:
274
+ spot: Spot price to check
275
+
276
+ Returns:
277
+ True if either barrier is hit
278
+ """
279
+ return spot >= self.upper_barrier or spot <= self.lower_barrier
280
+
281
+ def is_in_corridor(self, spot: float) -> bool:
282
+ """
283
+ Check if spot is within the corridor.
284
+
285
+ Args:
286
+ spot: Spot price to check
287
+
288
+ Returns:
289
+ True if spot is between lower and upper barriers
290
+ """
291
+ return self.lower_barrier < spot < self.upper_barrier
292
+
293
+ @property
294
+ def corridor_width(self) -> float:
295
+ """Get the width of the corridor in price terms."""
296
+ return self.upper_barrier - self.lower_barrier
297
+
298
+ @property
299
+ def corridor_width_log(self) -> float:
300
+ """Get the width of the corridor in log-price terms."""
301
+ import math
302
+ return math.log(self.upper_barrier / self.lower_barrier)
303
+
304
+ def __repr__(self):
305
+ touch_str = "DoubleOneTouch" if self.is_double_one_touch else "DoubleNoTouch"
306
+ return (
307
+ f"{touch_str}Option("
308
+ f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
309
+ f"rebate={self.rebate:.2f}, T={self.maturity:.4f})"
310
+ )