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,530 @@
1
+ """
2
+ Observation schedule structures for barrier-like products.
3
+ """
4
+
5
+ from dataclasses import dataclass, field
6
+ from datetime import datetime
7
+ from math import isclose
8
+ from typing import List, Optional, TYPE_CHECKING
9
+ from copy import deepcopy
10
+
11
+ from quantark.util.enum import ObservationAggregation, TenorEnd, ObservationFrequency
12
+ from quantark.util.calendar import DayCountConvention
13
+ from quantark.util.exceptions import ValidationError
14
+
15
+ if TYPE_CHECKING:
16
+ from quantark.priceenv import PricingEnvironment
17
+
18
+ # Type alias for cleaner signatures
19
+ PricingEnv = Optional["PricingEnvironment"]
20
+
21
+
22
+ try:
23
+ from quantark.util.calendar import calculate_year_fraction, calculate_day_count_fraction
24
+ except ImportError:
25
+ # Fallback for environments where calendar utilities are unavailable at import time.
26
+ calculate_year_fraction = None
27
+ calculate_day_count_fraction = None
28
+
29
+
30
+ @dataclass
31
+ class ObservationRecord:
32
+ """
33
+ Single observation entry with optional per-date barrier and payoff data.
34
+
35
+ Fields for annualized rate handling:
36
+ is_rate_annualized: Whether return_rate is annualized (default: False)
37
+ initial_date: Start date for accrual period calculation
38
+ settlement_date: Settlement date (used if tenor_end = SETTLEMENT)
39
+ maturity_date: Maturity date (used if tenor_end = MATURITY)
40
+ day_count_convention: Per-observation day count convention override
41
+ tenor_end: Defines end date for accrual (SETTLEMENT/MATURITY/default to observation)
42
+ day_count_fraction: Pre-calculated day count fraction (optional, computed if not provided)
43
+ """
44
+
45
+ observation_time: Optional[float] = None
46
+ observation_date: Optional[datetime] = None
47
+ barrier: Optional[float] = None
48
+ upper_barrier: Optional[float] = None
49
+ lower_barrier: Optional[float] = None
50
+ payoff: Optional[float] = None
51
+ return_rate: Optional[float] = None
52
+ is_rate_annualized: Optional[bool] = False
53
+
54
+ # Date-based accrual fields
55
+ initial_date: Optional[datetime] = None
56
+ settlement_date: Optional[datetime] = None
57
+ maturity_date: Optional[datetime] = None
58
+ day_count_convention: Optional[DayCountConvention] = None
59
+ tenor_end: Optional[TenorEnd] = None
60
+ day_count_fraction: Optional[float] = None
61
+
62
+ def resolve_time(self, pricing_env: PricingEnv) -> float:
63
+ """Resolve observation time to a year fraction.
64
+
65
+ Args:
66
+ pricing_env: PricingEnvironment containing valuation date and calendar conventions
67
+
68
+ Returns:
69
+ Year fraction from valuation date to observation date
70
+
71
+ Raises:
72
+ ValidationError: If observation time/date cannot be resolved
73
+ """
74
+ if self.observation_time is not None:
75
+ return self.observation_time
76
+ if self.observation_date is not None:
77
+ if pricing_env is None or calculate_year_fraction is None:
78
+ raise ValidationError(
79
+ "PricingEnvironment is required to resolve observation_date."
80
+ )
81
+ return calculate_year_fraction(
82
+ pricing_env.valuation_date,
83
+ self.observation_date,
84
+ pricing_env.day_count_convention,
85
+ pricing_env.bus_days_in_year,
86
+ calendar=getattr(pricing_env, "calendar", None),
87
+ )
88
+ raise ValidationError("ObservationRecord requires observation_time or observation_date.")
89
+
90
+ def get_payoff(self, default_payoff: float, pricing_env: PricingEnv = None) -> float:
91
+ """
92
+ Return the payoff for this record, following the calculation flow:
93
+ 1. If payoff is explicitly set, return it
94
+ 2. If return_rate is not annualized, return return_rate
95
+ 3. If day_count_fraction is pre-calculated, return day_count_fraction * return_rate
96
+ 4. Calculate day_count_fraction based on tenor_end and dates, then return day_count_fraction * return_rate
97
+
98
+ Args:
99
+ default_payoff: Default payoff value if not specified
100
+ pricing_env: Optional pricing environment for date resolution
101
+
102
+ Returns:
103
+ Resolved payoff value for this observation
104
+ """
105
+ # If explicit payoff is set, use it
106
+ if self.payoff is not None:
107
+ return self.payoff
108
+
109
+ # If no return_rate, use default
110
+ if self.return_rate is None:
111
+ return default_payoff
112
+
113
+ # If return_rate is not annualized, return it directly
114
+ if not self.is_rate_annualized:
115
+ return self.return_rate
116
+
117
+ # If day_count_fraction is pre-calculated, use it
118
+ if self.day_count_fraction is not None:
119
+ return self.day_count_fraction * self.return_rate
120
+
121
+ # Calculate day_count_fraction based on tenor_end
122
+ if self.initial_date is None:
123
+ # Cannot calculate day_count_fraction without initial_date
124
+ return self.return_rate
125
+
126
+ # Determine end_date based on tenor_end
127
+ end_date = self._determine_end_date()
128
+ if end_date is None:
129
+ # Cannot calculate day_count_fraction without end_date
130
+ return self.return_rate
131
+
132
+ # Calculate day_count_fraction
133
+ return self._calculate_annualized_payoff(end_date)
134
+
135
+ def _determine_end_date(self) -> Optional[datetime]:
136
+ """Determine the end date for accrual period based on tenor_end setting."""
137
+ if self.tenor_end == TenorEnd.SETTLEMENT and self.settlement_date is not None:
138
+ return self.settlement_date
139
+ if self.tenor_end == TenorEnd.MATURITY and self.maturity_date is not None:
140
+ return self.maturity_date
141
+ if self.observation_date is not None:
142
+ return self.observation_date
143
+ return None
144
+
145
+ def _calculate_annualized_payoff(self, end_date: datetime) -> float:
146
+ """Calculate annualized payoff using day count fraction."""
147
+ if self.day_count_convention is not None and calculate_day_count_fraction is not None:
148
+ try:
149
+ dcf = calculate_day_count_fraction(
150
+ self.initial_date, end_date, self.day_count_convention
151
+ )
152
+ return dcf * self.return_rate
153
+ except ImportError:
154
+ # If calculation fails due to import issues, return raw return_rate
155
+ return self.return_rate
156
+
157
+ # Fallback to raw return_rate if no day count convention
158
+ return self.return_rate
159
+
160
+ def validate(self, require_single: bool = False, require_double: bool = False) -> None:
161
+ """Validate record fields."""
162
+ if self.observation_time is None and self.observation_date is None:
163
+ raise ValidationError("ObservationRecord must provide observation_time or observation_date.")
164
+ if require_single:
165
+ if self.barrier is None:
166
+ raise ValidationError("Single-barrier observation requires barrier level.")
167
+ if self.upper_barrier is not None or self.lower_barrier is not None:
168
+ raise ValidationError("Single-barrier observation must not include upper/lower barriers.")
169
+ if require_double:
170
+ if self.upper_barrier is None or self.lower_barrier is None:
171
+ raise ValidationError("Double-barrier observation requires both upper_barrier and lower_barrier.")
172
+ if self.upper_barrier <= 0 or self.lower_barrier <= 0:
173
+ raise ValidationError("Barrier levels must be positive.")
174
+ if self.lower_barrier >= self.upper_barrier:
175
+ raise ValidationError("lower_barrier must be less than upper_barrier.")
176
+
177
+ # Validate date consistency for annualized rates
178
+ if self.is_rate_annualized and self.return_rate is not None:
179
+ if self.day_count_fraction is None and self.initial_date is not None:
180
+ # Check that we have sufficient info to calculate day_count_fraction
181
+ has_end_date = (
182
+ (self.tenor_end == TenorEnd.SETTLEMENT and self.settlement_date is not None)
183
+ or (self.tenor_end == TenorEnd.MATURITY and self.maturity_date is not None)
184
+ or self.observation_date is not None
185
+ )
186
+ if not has_end_date:
187
+ raise ValidationError(
188
+ "Annualized return_rate requires either day_count_fraction, "
189
+ "or initial_date with appropriate tenor_end and corresponding date."
190
+ )
191
+
192
+
193
+ @dataclass
194
+ class ResolvedObservationRecord:
195
+ """
196
+ ObservationRecord with resolved time and concrete barrier/payoff values.
197
+
198
+ This is the final state used by pricing engines. It contains only:
199
+ - observation_time: When to check for barrier breach
200
+ - barrier levels: What levels to check against
201
+ - payoff: The final payoff amount if barrier is hit
202
+ - settlement_time: When the payoff is settled (for discounting)
203
+
204
+ All intermediate calculation values (return_rate, day_count_fraction)
205
+ have been consumed during resolution to produce the final payoff.
206
+ """
207
+
208
+ observation_time: float
209
+ barrier: Optional[float] = None
210
+ upper_barrier: Optional[float] = None
211
+ lower_barrier: Optional[float] = None
212
+ payoff: float = 0.0
213
+ settlement_time: Optional[float] = None
214
+
215
+
216
+ @dataclass
217
+ class ObservationSchedule:
218
+ """Ordered schedule of observation records with aggregation semantics."""
219
+
220
+ records: List[ObservationRecord] = field(default_factory=list)
221
+ aggregation_mode: ObservationAggregation = ObservationAggregation.STOP_FIRST_HIT
222
+ frequency: ObservationFrequency = ObservationFrequency.CUSTOM
223
+
224
+ def validate(self, require_single: bool = False, require_double: bool = False) -> None:
225
+ """Validate schedule ordering and record completeness."""
226
+ if len(self.records) == 0:
227
+ raise ValidationError("ObservationSchedule must contain at least one record for discrete monitoring.")
228
+ if not isinstance(self.aggregation_mode, ObservationAggregation):
229
+ raise ValidationError(f"Invalid aggregation mode: {self.aggregation_mode}")
230
+ times: List[float] = []
231
+ for rec in self.records:
232
+ rec.validate(require_single=require_single, require_double=require_double)
233
+ if rec.observation_time is not None:
234
+ times.append(rec.observation_time)
235
+ if times and not self._is_sorted(times):
236
+ raise ValidationError("Observation records must be ordered by observation_time.")
237
+
238
+ def uses_dates(self) -> bool:
239
+ """Return True if any record uses observation_date."""
240
+ return any(
241
+ getattr(rec, "observation_date", None) is not None for rec in self.records
242
+ )
243
+
244
+ def uses_times(self) -> bool:
245
+ """Return True if any record uses observation_time."""
246
+ return any(
247
+ getattr(rec, "observation_time", None) is not None for rec in self.records
248
+ )
249
+
250
+ def time_shift(
251
+ self, time_bump: float, bumped_date: Optional[datetime] = None
252
+ ) -> Optional["ObservationSchedule"]:
253
+ """
254
+ Shift observation schedule by time_bump and drop past records.
255
+
256
+ - observation_time is reduced by time_bump
257
+ - observation_date records are dropped if <= bumped_date
258
+ """
259
+ if not self.records:
260
+ return self
261
+
262
+ updated_records = []
263
+ for rec in self.records:
264
+ rec_copy = deepcopy(rec)
265
+
266
+ if getattr(rec_copy, "observation_date", None) is not None:
267
+ if bumped_date is not None and rec_copy.observation_date <= bumped_date:
268
+ continue
269
+
270
+ if getattr(rec_copy, "observation_time", None) is not None:
271
+ adjusted_time = rec_copy.observation_time - time_bump
272
+ if adjusted_time <= 0:
273
+ continue
274
+ rec_copy.observation_time = adjusted_time
275
+
276
+ updated_records.append(rec_copy)
277
+
278
+ if not updated_records:
279
+ return None
280
+
281
+ return ObservationSchedule(
282
+ records=updated_records,
283
+ aggregation_mode=self.aggregation_mode,
284
+ frequency=self.frequency,
285
+ )
286
+
287
+ def resolve(
288
+ self,
289
+ pricing_env,
290
+ default_barrier: Optional[float] = None,
291
+ default_upper: Optional[float] = None,
292
+ default_lower: Optional[float] = None,
293
+ default_payoff: float = 0.0,
294
+ require_single: bool = False,
295
+ require_double: bool = False,
296
+ ) -> List[ResolvedObservationRecord]:
297
+ """
298
+ Resolve records to concrete numeric times and defaults.
299
+ Payoff calculation is delegated to ObservationRecord.get_payoff().
300
+ """
301
+ self.validate(require_single=require_single, require_double=require_double)
302
+
303
+ resolved: List[ResolvedObservationRecord] = []
304
+ times: List[float] = []
305
+
306
+ for rec in self.records:
307
+ # Resolve observation time
308
+ t = rec.resolve_time(pricing_env)
309
+ times.append(t)
310
+
311
+ # Get payoff using the record's get_payoff method
312
+ payoff_value = rec.get_payoff(default_payoff, pricing_env)
313
+
314
+ # Resolve settlement time
315
+ settlement_t: Optional[float] = None
316
+ if rec.settlement_date is not None and calculate_year_fraction is not None:
317
+ try:
318
+ settlement_t = calculate_year_fraction(
319
+ pricing_env.valuation_date,
320
+ rec.settlement_date,
321
+ pricing_env.day_count_convention,
322
+ pricing_env.bus_days_in_year,
323
+ calendar=getattr(pricing_env, "calendar", None),
324
+ )
325
+ except Exception:
326
+ # If calculation fails, default to observation time
327
+ settlement_t = t
328
+ else:
329
+ # Default: settlement at observation time
330
+ settlement_t = t
331
+
332
+ resolved.append(
333
+ ResolvedObservationRecord(
334
+ observation_time=t,
335
+ barrier=rec.barrier if rec.barrier is not None else default_barrier,
336
+ upper_barrier=rec.upper_barrier if rec.upper_barrier is not None else default_upper,
337
+ lower_barrier=rec.lower_barrier if rec.lower_barrier is not None else default_lower,
338
+ payoff=payoff_value,
339
+ settlement_time=settlement_t,
340
+ )
341
+ )
342
+
343
+ if times and not self._is_sorted(times):
344
+ raise ValidationError("Observation records must be ordered by resolved observation time.")
345
+ return resolved
346
+
347
+ def ensure_regular_frequency(
348
+ self,
349
+ times: List[float],
350
+ tolerance: float = 1e-8,
351
+ business_days_in_year: float = 252.0,
352
+ ) -> float:
353
+ """Ensure schedule has a regular observation frequency; return the interval.
354
+
355
+ This method supports three usage modes:
356
+ 1) `frequency` is `ObservationFrequency.CUSTOM` or None: infer dt from `times`.
357
+ 2) `frequency` is a numeric dt (legacy): validate against that dt.
358
+ 3) `frequency` is an `ObservationFrequency`: validate against calendar or business-day dt.
359
+
360
+ Args:
361
+ times: List of observation times in years
362
+ tolerance: Tolerance for comparing floating-point intervals (default: 1e-8)
363
+ business_days_in_year: Business-day year length used when validating
364
+ business-day frequency conventions (default: 252.0).
365
+
366
+ Returns:
367
+ The regular frequency interval (float)
368
+
369
+ Raises:
370
+ ValidationError: If spacing is irregular or does not match declared frequency.
371
+ """
372
+ if len(times) < 2:
373
+ raise ValidationError("Need at least 2 observation times to determine interval.")
374
+
375
+ inferred_dt = times[1] - times[0]
376
+ if inferred_dt <= 0.0:
377
+ raise ValidationError("Observation times must be strictly increasing.")
378
+
379
+ for i in range(1, len(times) - 1):
380
+ dt_i = times[i + 1] - times[i]
381
+ if dt_i <= 0.0:
382
+ raise ValidationError("Observation times must be strictly increasing.")
383
+ if not isclose(dt_i, inferred_dt, rel_tol=tolerance, abs_tol=tolerance):
384
+ raise ValidationError(
385
+ "ObservationSchedule requires a regular frequency for analytical usage, "
386
+ "but observation_times are not equally spaced."
387
+ )
388
+
389
+ frequency = self.frequency
390
+
391
+ if frequency is None or frequency == ObservationFrequency.CUSTOM:
392
+ return inferred_dt
393
+
394
+ if isinstance(frequency, (int, float)):
395
+ expected_dt = float(frequency)
396
+ if not isclose(
397
+ inferred_dt, expected_dt, rel_tol=tolerance, abs_tol=tolerance
398
+ ):
399
+ raise ValidationError(
400
+ f"ObservationSchedule declared frequency '{frequency}' "
401
+ f"does not match inferred interval {inferred_dt:.6f} from observation_times."
402
+ )
403
+ return expected_dt
404
+
405
+ if not isinstance(frequency, ObservationFrequency):
406
+ raise ValidationError(
407
+ f"Invalid schedule frequency type: {type(frequency).__name__}"
408
+ )
409
+
410
+ expected_dt_calendar = frequency.to_year_fraction(use_business_days=False)
411
+ expected_dt_business = frequency.to_year_fraction(
412
+ use_business_days=True, days_in_year=business_days_in_year
413
+ )
414
+
415
+ matches_calendar = isclose(
416
+ inferred_dt, expected_dt_calendar, rel_tol=tolerance, abs_tol=tolerance
417
+ )
418
+ matches_business = isclose(
419
+ inferred_dt, expected_dt_business, rel_tol=tolerance, abs_tol=tolerance
420
+ )
421
+
422
+ if not (matches_calendar or matches_business):
423
+ raise ValidationError(
424
+ f"ObservationSchedule declared frequency '{frequency.name}' "
425
+ f"(expected dt={expected_dt_calendar:.6f} or {expected_dt_business:.6f}) "
426
+ f"does not match inferred interval {inferred_dt:.6f} from observation_times."
427
+ )
428
+
429
+ return inferred_dt
430
+
431
+ def has_fixed_payoff(self, default_payoff: float = 0.0, tolerance: float = 1e-8) -> bool:
432
+ """Check if all records share the same payoff within tolerance.
433
+
434
+ Args:
435
+ default_payoff: Default payoff value to use for records without explicit payoffs
436
+ tolerance: Tolerance for comparing payoff values (default: 1e-8 for floating-point precision)
437
+
438
+ Returns:
439
+ True if all payoffs are equal within tolerance, False otherwise
440
+
441
+ Note:
442
+ Payoffs are computed once per record and cached for efficiency.
443
+ """
444
+ if not self.records:
445
+ return True
446
+
447
+ # Compute payoffs once and cache in list
448
+ payoffs = [rec.get_payoff(default_payoff) for rec in self.records]
449
+ base = payoffs[0]
450
+
451
+ for payoff in payoffs[1:]:
452
+ if not isclose(payoff, base, rel_tol=tolerance, abs_tol=tolerance):
453
+ return False
454
+ return True
455
+
456
+ def assert_analytical_ready(
457
+ self,
458
+ default_payoff: float = 0.0,
459
+ tolerance: float = 1e-8,
460
+ business_days_in_year: float = 252.0,
461
+ ) -> None:
462
+ """
463
+ Validate preconditions for analytical engines that rely on barrier shift:
464
+ regular observation frequency and fixed payoff across observations.
465
+
466
+ Args:
467
+ default_payoff: Default payoff value to use for records without explicit payoffs
468
+ tolerance: Tolerance for validation checks (default: 1e-8 for numerical stability)
469
+ business_days_in_year: Number of business days in a year used for validating
470
+ business-day frequency conventions (default: 252.0).
471
+
472
+ Raises:
473
+ ValidationError: If schedule is not suitable for analytical pricing
474
+ """
475
+ times = [
476
+ rec.observation_time
477
+ for rec in self.records
478
+ if rec.observation_time is not None
479
+ ]
480
+ self.ensure_regular_frequency(
481
+ times, tolerance=tolerance, business_days_in_year=business_days_in_year
482
+ )
483
+ if not self.has_fixed_payoff(
484
+ default_payoff=default_payoff, tolerance=tolerance
485
+ ):
486
+ raise ValidationError(
487
+ "Analytical barrier shift requires a fixed payoff across observation records."
488
+ )
489
+
490
+ @property
491
+ def times(self) -> List[float]:
492
+ """Return observation_time values when present."""
493
+ return [rec.observation_time for rec in self.records if rec.observation_time is not None]
494
+
495
+ @classmethod
496
+ def from_legacy(
497
+ cls,
498
+ observation_dates: List[float],
499
+ default_barrier: Optional[float],
500
+ default_payoff: float,
501
+ aggregation_mode: ObservationAggregation = ObservationAggregation.STOP_FIRST_HIT,
502
+ frequency: ObservationFrequency = ObservationFrequency.CUSTOM,
503
+ upper_barrier: Optional[float] = None,
504
+ lower_barrier: Optional[float] = None,
505
+ ) -> "ObservationSchedule":
506
+ """Create a schedule from legacy observation_dates and uniform barrier/payoff."""
507
+ if observation_dates is None or len(observation_dates) == 0:
508
+ raise ValidationError("observation_dates required to build ObservationSchedule from legacy fields.")
509
+ records = [
510
+ ObservationRecord(
511
+ observation_time=t,
512
+ barrier=default_barrier,
513
+ upper_barrier=upper_barrier,
514
+ lower_barrier=lower_barrier,
515
+ payoff=default_payoff,
516
+ )
517
+ for t in observation_dates
518
+ ]
519
+ schedule = cls(
520
+ records=records,
521
+ aggregation_mode=aggregation_mode,
522
+ frequency=frequency,
523
+ )
524
+ schedule.validate(require_single=upper_barrier is None and lower_barrier is None, require_double=upper_barrier is not None or lower_barrier is not None)
525
+ return schedule
526
+
527
+ @staticmethod
528
+ def _is_sorted(values: List[float]) -> bool:
529
+ """Check if list is non-decreasing."""
530
+ return all(values[i] <= values[i + 1] for i in range(len(values) - 1))