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,608 @@
1
+ """
2
+ Factory functions for creating common Range Accrual option structures.
3
+
4
+ This module provides helper functions that simplify the creation of Range Accrual
5
+ options by providing sensible defaults for common market structures.
6
+
7
+ Available helpers:
8
+ - create_standard_range_accrual(): Basic range accrual with flat barriers
9
+ - create_reverse_range_accrual(): Pays when spot is OUTSIDE range
10
+ - create_stepdown_range_accrual(): Barriers decrease over time (easier to hit)
11
+ - generate_range_observation_records(): Create observation schedule with weights
12
+ - assign_calendar_day_weights(): Assign Friday=3 weights for calendar day convention
13
+
14
+ Example:
15
+ >>> from asset.equity.product.option import create_standard_range_accrual
16
+ >>> option = create_standard_range_accrual(
17
+ ... initial_price=100.0,
18
+ ... upper_barrier=110.0,
19
+ ... lower_barrier=90.0,
20
+ ... maturity=1.0,
21
+ ... accrual_rate=0.05,
22
+ ... )
23
+ """
24
+
25
+ from datetime import datetime, timedelta
26
+ from typing import List, Optional
27
+
28
+ from quantark.util.calendar.day_counter import DayCountConvention
29
+ from quantark.util.enum import CouponPayType, ObservationFrequency
30
+ from quantark.util.exceptions import ValidationError
31
+
32
+ from .range_accrual_config import RangeAccrualConfig, RangeAccrualObservationRecord
33
+ from .range_accrual_option import RangeAccrualOption
34
+
35
+
36
+ # =============================================================================
37
+ # Internal Helper Functions
38
+ # =============================================================================
39
+
40
+
41
+ def _validate_core_params(
42
+ initial_price: float,
43
+ upper_barrier: float,
44
+ lower_barrier: float,
45
+ maturity: float,
46
+ func_name: str,
47
+ ) -> None:
48
+ """Validate core parameters common to all helpers."""
49
+ if initial_price <= 0:
50
+ raise ValidationError(
51
+ f"{func_name}: initial_price must be positive, got {initial_price}"
52
+ )
53
+ if upper_barrier <= 0:
54
+ raise ValidationError(
55
+ f"{func_name}: upper_barrier must be positive, got {upper_barrier}"
56
+ )
57
+ if lower_barrier <= 0:
58
+ raise ValidationError(
59
+ f"{func_name}: lower_barrier must be positive, got {lower_barrier}"
60
+ )
61
+ if lower_barrier >= upper_barrier:
62
+ raise ValidationError(
63
+ f"{func_name}: lower_barrier ({lower_barrier}) must be less than "
64
+ f"upper_barrier ({upper_barrier})"
65
+ )
66
+ if maturity <= 0:
67
+ raise ValidationError(
68
+ f"{func_name}: maturity must be positive, got {maturity}"
69
+ )
70
+
71
+
72
+ # =============================================================================
73
+ # Calendar Day Weight Functions
74
+ # =============================================================================
75
+
76
+
77
+ def assign_calendar_day_weights(
78
+ observation_dates: List[datetime],
79
+ ) -> List[float]:
80
+ """
81
+ Assign weights based on calendar day convention.
82
+
83
+ Friday observations get weight=3.0 (covers Saturday and Sunday).
84
+ Other weekdays get weight=1.0.
85
+
86
+ Args:
87
+ observation_dates: List of observation dates (business days only)
88
+
89
+ Returns:
90
+ List of weights where Friday=3, other weekdays=1
91
+
92
+ Example:
93
+ >>> from datetime import datetime
94
+ >>> dates = [
95
+ ... datetime(2025, 1, 6), # Monday
96
+ ... datetime(2025, 1, 7), # Tuesday
97
+ ... datetime(2025, 1, 10), # Friday
98
+ ... ]
99
+ >>> assign_calendar_day_weights(dates)
100
+ [1.0, 1.0, 3.0]
101
+ """
102
+ weights = []
103
+ for date in observation_dates:
104
+ # weekday(): 0=Monday, 4=Friday, 5=Saturday, 6=Sunday
105
+ if date.weekday() == 4: # Friday
106
+ weights.append(3.0)
107
+ else:
108
+ weights.append(1.0)
109
+ return weights
110
+
111
+
112
+ def generate_range_observation_records(
113
+ start_date: datetime,
114
+ end_date: datetime,
115
+ frequency: ObservationFrequency = ObservationFrequency.DAILY,
116
+ use_calendar_day_weights: bool = False,
117
+ upper_barrier: Optional[float] = None,
118
+ lower_barrier: Optional[float] = None,
119
+ ) -> List[RangeAccrualObservationRecord]:
120
+ """
121
+ Generate observation records with optional calendar day weighting.
122
+
123
+ If use_calendar_day_weights=True:
124
+ - Friday observations get weight=3.0 (covers Sat+Sun)
125
+ - Other weekdays get weight=1.0
126
+ - Weekends are skipped (business days only)
127
+
128
+ This matches market convention for calendar day accrual.
129
+
130
+ Args:
131
+ start_date: First observation date (inclusive)
132
+ end_date: Last observation date (inclusive)
133
+ frequency: Observation frequency (DAILY, WEEKLY, MONTHLY, etc.)
134
+ use_calendar_day_weights: If True, auto-assign Friday=3 weights
135
+ upper_barrier: Optional per-observation upper barrier
136
+ lower_barrier: Optional per-observation lower barrier
137
+
138
+ Returns:
139
+ List of RangeAccrualObservationRecord
140
+
141
+ Example:
142
+ >>> from datetime import datetime
143
+ >>> records = generate_range_observation_records(
144
+ ... start_date=datetime(2025, 1, 6),
145
+ ... end_date=datetime(2025, 1, 10),
146
+ ... use_calendar_day_weights=True,
147
+ ... )
148
+ >>> [r.weight for r in records]
149
+ [1.0, 1.0, 1.0, 1.0, 3.0] # Mon, Tue, Wed, Thu, Fri
150
+ """
151
+ if start_date > end_date:
152
+ raise ValidationError(
153
+ f"start_date ({start_date}) must be before or equal to end_date ({end_date})"
154
+ )
155
+
156
+ dates = _generate_observation_dates(start_date, end_date, frequency)
157
+
158
+ if use_calendar_day_weights:
159
+ weights = assign_calendar_day_weights(dates)
160
+ else:
161
+ weights = [1.0] * len(dates)
162
+
163
+ records = []
164
+ for date, weight in zip(dates, weights):
165
+ records.append(
166
+ RangeAccrualObservationRecord(
167
+ observation_date=date,
168
+ weight=weight,
169
+ upper_barrier=upper_barrier,
170
+ lower_barrier=lower_barrier,
171
+ )
172
+ )
173
+
174
+ return records
175
+
176
+
177
+ def _generate_observation_dates(
178
+ start_date: datetime,
179
+ end_date: datetime,
180
+ frequency: ObservationFrequency,
181
+ ) -> List[datetime]:
182
+ """Generate observation dates based on frequency, skipping weekends."""
183
+ dates = []
184
+ current = start_date
185
+
186
+ if frequency == ObservationFrequency.DAILY:
187
+ while current <= end_date:
188
+ # Skip weekends (Saturday=5, Sunday=6)
189
+ if current.weekday() < 5:
190
+ dates.append(current)
191
+ current += timedelta(days=1)
192
+
193
+ elif frequency == ObservationFrequency.WEEKLY:
194
+ while current <= end_date:
195
+ # Skip weekends
196
+ if current.weekday() < 5:
197
+ dates.append(current)
198
+ current += timedelta(weeks=1)
199
+
200
+ elif frequency == ObservationFrequency.MONTHLY:
201
+ while current <= end_date:
202
+ # Skip weekends
203
+ if current.weekday() < 5:
204
+ dates.append(current)
205
+ # Move to same day next month
206
+ month = current.month + 1
207
+ year = current.year
208
+ if month > 12:
209
+ month = 1
210
+ year += 1
211
+ # Handle end-of-month edge cases
212
+ day = min(current.day, _days_in_month(year, month))
213
+ current = datetime(year, month, day)
214
+
215
+ elif frequency == ObservationFrequency.QUARTERLY:
216
+ while current <= end_date:
217
+ if current.weekday() < 5:
218
+ dates.append(current)
219
+ # Move 3 months forward
220
+ month = current.month + 3
221
+ year = current.year
222
+ while month > 12:
223
+ month -= 12
224
+ year += 1
225
+ day = min(current.day, _days_in_month(year, month))
226
+ current = datetime(year, month, day)
227
+
228
+ elif frequency == ObservationFrequency.SEMI_ANNUALLY:
229
+ while current <= end_date:
230
+ if current.weekday() < 5:
231
+ dates.append(current)
232
+ # Move 6 months forward
233
+ month = current.month + 6
234
+ year = current.year
235
+ while month > 12:
236
+ month -= 12
237
+ year += 1
238
+ day = min(current.day, _days_in_month(year, month))
239
+ current = datetime(year, month, day)
240
+
241
+ elif frequency == ObservationFrequency.ANNUALLY:
242
+ while current <= end_date:
243
+ if current.weekday() < 5:
244
+ dates.append(current)
245
+ current = datetime(current.year + 1, current.month, current.day)
246
+
247
+ else:
248
+ raise ValidationError(
249
+ f"Unsupported observation frequency: {frequency}. "
250
+ "Use CUSTOM frequency with explicit observation_records instead."
251
+ )
252
+
253
+ return dates
254
+
255
+
256
+ def _days_in_month(year: int, month: int) -> int:
257
+ """Get number of days in a month."""
258
+ if month in (1, 3, 5, 7, 8, 10, 12):
259
+ return 31
260
+ elif month in (4, 6, 9, 11):
261
+ return 30
262
+ elif month == 2:
263
+ if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
264
+ return 29
265
+ return 28
266
+ raise ValidationError(f"Invalid month: {month}")
267
+
268
+
269
+ # =============================================================================
270
+ # Factory Functions
271
+ # =============================================================================
272
+
273
+
274
+ def create_standard_range_accrual(
275
+ initial_price: float,
276
+ upper_barrier: float,
277
+ lower_barrier: float,
278
+ maturity: float,
279
+ contract_multiplier: float = 1.0,
280
+ accrual_rate: float = 0.01,
281
+ num_observations: int = 252,
282
+ is_rate_annualized: bool = True,
283
+ use_calendar_day_weights: bool = False,
284
+ day_count_convention: DayCountConvention = DayCountConvention.ACT_365,
285
+ accrual_pay_type: CouponPayType = CouponPayType.EXPIRY,
286
+ observation_records: Optional[List[RangeAccrualObservationRecord]] = None,
287
+ ) -> RangeAccrualOption:
288
+ """
289
+ Create a standard range accrual option with flat barriers.
290
+
291
+ The most common range accrual structure with:
292
+ - Flat (constant) upper and lower barriers
293
+ - Daily observations by default (252 per year)
294
+ - Pay at expiry
295
+ - Optional calendar day weighting (Friday=3)
296
+
297
+ Args:
298
+ initial_price: Reference price for payoff calculations
299
+ upper_barrier: Upper barrier level
300
+ lower_barrier: Lower barrier level
301
+ maturity: Time to maturity in years
302
+ contract_multiplier: Underlying units represented by one contract
303
+ accrual_rate: Per-period or annualized accrual rate (default: 1%)
304
+ num_observations: Number of observations (default: 252 for daily)
305
+ is_rate_annualized: Whether accrual_rate is annualized (default: True)
306
+ use_calendar_day_weights: If True, auto-assign Friday=3 weights
307
+ day_count_convention: Day count for year fraction (default: ACT/365)
308
+ accrual_pay_type: INSTANT or EXPIRY (default: EXPIRY)
309
+ observation_records: Custom observation records (overrides num_observations)
310
+
311
+ Returns:
312
+ Configured RangeAccrualOption instance
313
+
314
+ Example:
315
+ >>> option = create_standard_range_accrual(
316
+ ... initial_price=100.0,
317
+ ... upper_barrier=110.0,
318
+ ... lower_barrier=90.0,
319
+ ... maturity=1.0,
320
+ ... accrual_rate=0.05,
321
+ ... )
322
+ """
323
+ _validate_core_params(
324
+ initial_price,
325
+ upper_barrier,
326
+ lower_barrier,
327
+ maturity,
328
+ "create_standard_range_accrual",
329
+ )
330
+
331
+ range_config = RangeAccrualConfig(
332
+ upper_barrier=upper_barrier,
333
+ lower_barrier=lower_barrier,
334
+ accrual_rate=accrual_rate,
335
+ is_rate_annualized=is_rate_annualized,
336
+ day_count_convention=day_count_convention,
337
+ accrual_pay_type=accrual_pay_type,
338
+ is_reverse=False,
339
+ use_calendar_day_weights=use_calendar_day_weights,
340
+ )
341
+
342
+ # Build observation records if using calendar day weights
343
+ obs_records = observation_records
344
+ if obs_records is None and use_calendar_day_weights:
345
+ # Generate uniform schedule with Friday=3 weights
346
+ obs_times = [
347
+ (i + 1) / num_observations * maturity for i in range(num_observations)
348
+ ]
349
+ # Estimate weekday pattern based on observation index
350
+ # Simplified: assume starting Monday, every 5th observation is Friday
351
+ weights = []
352
+ for i in range(num_observations):
353
+ day_of_week = i % 5 # 0=Mon, 1=Tue, ..., 4=Fri
354
+ if day_of_week == 4: # Friday
355
+ weights.append(3.0)
356
+ else:
357
+ weights.append(1.0)
358
+ obs_records = [
359
+ RangeAccrualObservationRecord(observation_time=t, weight=w)
360
+ for t, w in zip(obs_times, weights)
361
+ ]
362
+
363
+ return RangeAccrualOption(
364
+ initial_price=initial_price,
365
+ range_config=range_config,
366
+ maturity=maturity,
367
+ observation_records=obs_records,
368
+ num_observations=num_observations if obs_records is None else None,
369
+ contract_multiplier=contract_multiplier,
370
+ )
371
+
372
+
373
+ def create_reverse_range_accrual(
374
+ initial_price: float,
375
+ upper_barrier: float,
376
+ lower_barrier: float,
377
+ maturity: float,
378
+ contract_multiplier: float = 1.0,
379
+ accrual_rate: float = 0.01,
380
+ num_observations: int = 252,
381
+ is_rate_annualized: bool = True,
382
+ use_calendar_day_weights: bool = False,
383
+ day_count_convention: DayCountConvention = DayCountConvention.ACT_365,
384
+ accrual_pay_type: CouponPayType = CouponPayType.EXPIRY,
385
+ observation_records: Optional[List[RangeAccrualObservationRecord]] = None,
386
+ ) -> RangeAccrualOption:
387
+ """
388
+ Create a reverse range accrual option that pays when OUTSIDE the range.
389
+
390
+ Same as standard range accrual, but accrual occurs when spot is
391
+ BELOW lower_barrier OR ABOVE upper_barrier.
392
+
393
+ Args:
394
+ initial_price: Reference price for payoff calculations
395
+ upper_barrier: Upper barrier level
396
+ lower_barrier: Lower barrier level
397
+ maturity: Time to maturity in years
398
+ contract_multiplier: Underlying units represented by one contract
399
+ accrual_rate: Per-period or annualized accrual rate (default: 1%)
400
+ num_observations: Number of observations (default: 252 for daily)
401
+ is_rate_annualized: Whether accrual_rate is annualized (default: True)
402
+ use_calendar_day_weights: If True, auto-assign Friday=3 weights
403
+ day_count_convention: Day count for year fraction (default: ACT/365)
404
+ accrual_pay_type: INSTANT or EXPIRY (default: EXPIRY)
405
+ observation_records: Custom observation records (overrides num_observations)
406
+
407
+ Returns:
408
+ Configured RangeAccrualOption instance with is_reverse=True
409
+
410
+ Example:
411
+ >>> option = create_reverse_range_accrual(
412
+ ... initial_price=100.0,
413
+ ... upper_barrier=110.0,
414
+ ... lower_barrier=90.0,
415
+ ... maturity=1.0,
416
+ ... accrual_rate=0.05,
417
+ ... )
418
+ >>> option.range_config.is_reverse
419
+ True
420
+ """
421
+ _validate_core_params(
422
+ initial_price,
423
+ upper_barrier,
424
+ lower_barrier,
425
+ maturity,
426
+ "create_reverse_range_accrual",
427
+ )
428
+
429
+ range_config = RangeAccrualConfig(
430
+ upper_barrier=upper_barrier,
431
+ lower_barrier=lower_barrier,
432
+ accrual_rate=accrual_rate,
433
+ is_rate_annualized=is_rate_annualized,
434
+ day_count_convention=day_count_convention,
435
+ accrual_pay_type=accrual_pay_type,
436
+ is_reverse=True, # Key difference: pay when OUTSIDE range
437
+ use_calendar_day_weights=use_calendar_day_weights,
438
+ )
439
+
440
+ # Build observation records if using calendar day weights
441
+ obs_records = observation_records
442
+ if obs_records is None and use_calendar_day_weights:
443
+ obs_times = [
444
+ (i + 1) / num_observations * maturity for i in range(num_observations)
445
+ ]
446
+ weights = []
447
+ for i in range(num_observations):
448
+ day_of_week = i % 5
449
+ if day_of_week == 4:
450
+ weights.append(3.0)
451
+ else:
452
+ weights.append(1.0)
453
+ obs_records = [
454
+ RangeAccrualObservationRecord(observation_time=t, weight=w)
455
+ for t, w in zip(obs_times, weights)
456
+ ]
457
+
458
+ return RangeAccrualOption(
459
+ initial_price=initial_price,
460
+ range_config=range_config,
461
+ maturity=maturity,
462
+ observation_records=obs_records,
463
+ num_observations=num_observations if obs_records is None else None,
464
+ contract_multiplier=contract_multiplier,
465
+ )
466
+
467
+
468
+ def create_stepdown_range_accrual(
469
+ initial_price: float,
470
+ initial_upper_barrier: float,
471
+ initial_lower_barrier: float,
472
+ maturity: float,
473
+ upper_stepdown_rate: float = 0.005,
474
+ lower_stepdown_rate: float = 0.005,
475
+ contract_multiplier: float = 1.0,
476
+ accrual_rate: float = 0.01,
477
+ num_observations: int = 12,
478
+ is_rate_annualized: bool = True,
479
+ use_calendar_day_weights: bool = False,
480
+ day_count_convention: DayCountConvention = DayCountConvention.ACT_365,
481
+ accrual_pay_type: CouponPayType = CouponPayType.EXPIRY,
482
+ is_reverse: bool = False,
483
+ ) -> RangeAccrualOption:
484
+ """
485
+ Create a step-down range accrual where barriers narrow over time.
486
+
487
+ Both upper and lower barriers move toward initial_price at each observation,
488
+ making it progressively harder to stay in-range. This structure provides
489
+ higher accrual rates to compensate for the narrowing corridor.
490
+
491
+ Upper barrier: decreases by upper_stepdown_rate * initial_price each period
492
+ Lower barrier: increases by lower_stepdown_rate * initial_price each period
493
+
494
+ Args:
495
+ initial_price: Reference price for payoff calculations
496
+ initial_upper_barrier: Starting upper barrier
497
+ initial_lower_barrier: Starting lower barrier
498
+ maturity: Time to maturity in years
499
+ upper_stepdown_rate: Upper barrier step-down as % of initial_price per period
500
+ lower_stepdown_rate: Lower barrier step-up as % of initial_price per period
501
+ contract_multiplier: Underlying units represented by one contract
502
+ accrual_rate: Per-period or annualized accrual rate (default: 1%)
503
+ num_observations: Number of observations (default: 12 for monthly)
504
+ is_rate_annualized: Whether accrual_rate is annualized (default: True)
505
+ use_calendar_day_weights: If True, auto-assign Friday=3 weights
506
+ day_count_convention: Day count for year fraction (default: ACT/365)
507
+ accrual_pay_type: INSTANT or EXPIRY (default: EXPIRY)
508
+ is_reverse: If True, pay when OUTSIDE range (default: False)
509
+
510
+ Returns:
511
+ Configured RangeAccrualOption with time-varying barriers
512
+
513
+ Example:
514
+ >>> option = create_stepdown_range_accrual(
515
+ ... initial_price=100.0,
516
+ ... initial_upper_barrier=115.0,
517
+ ... initial_lower_barrier=85.0,
518
+ ... maturity=1.0,
519
+ ... upper_stepdown_rate=0.01, # -1% per period
520
+ ... lower_stepdown_rate=0.01, # +1% per period
521
+ ... num_observations=12,
522
+ ... )
523
+ >>> option.range_config.upper_barrier[0] # First: 115
524
+ 115.0
525
+ >>> option.range_config.upper_barrier[-1] # Last: ~103
526
+ 104.0
527
+ """
528
+ _validate_core_params(
529
+ initial_price,
530
+ initial_upper_barrier,
531
+ initial_lower_barrier,
532
+ maturity,
533
+ "create_stepdown_range_accrual",
534
+ )
535
+
536
+ if upper_stepdown_rate < 0:
537
+ raise ValidationError(
538
+ f"upper_stepdown_rate must be non-negative, got {upper_stepdown_rate}"
539
+ )
540
+ if lower_stepdown_rate < 0:
541
+ raise ValidationError(
542
+ f"lower_stepdown_rate must be non-negative, got {lower_stepdown_rate}"
543
+ )
544
+
545
+ # Generate step-down barriers
546
+ upper_barriers = []
547
+ lower_barriers = []
548
+ upper_step = upper_stepdown_rate * initial_price
549
+ lower_step = lower_stepdown_rate * initial_price
550
+
551
+ for i in range(num_observations):
552
+ upper = initial_upper_barrier - i * upper_step
553
+ lower = initial_lower_barrier + i * lower_step
554
+
555
+ # Ensure barriers don't cross
556
+ if lower >= upper:
557
+ raise ValidationError(
558
+ f"Barriers cross at observation {i}: lower ({lower}) >= upper ({upper}). "
559
+ "Reduce stepdown rates or use fewer observations."
560
+ )
561
+
562
+ upper_barriers.append(upper)
563
+ lower_barriers.append(lower)
564
+
565
+ range_config = RangeAccrualConfig(
566
+ upper_barrier=upper_barriers,
567
+ lower_barrier=lower_barriers,
568
+ accrual_rate=accrual_rate,
569
+ is_rate_annualized=is_rate_annualized,
570
+ day_count_convention=day_count_convention,
571
+ accrual_pay_type=accrual_pay_type,
572
+ is_reverse=is_reverse,
573
+ use_calendar_day_weights=use_calendar_day_weights,
574
+ )
575
+
576
+ # Build observation records
577
+ obs_times = [
578
+ (i + 1) / num_observations * maturity for i in range(num_observations)
579
+ ]
580
+
581
+ if use_calendar_day_weights:
582
+ weights = []
583
+ for i in range(num_observations):
584
+ day_of_week = i % 5
585
+ if day_of_week == 4:
586
+ weights.append(3.0)
587
+ else:
588
+ weights.append(1.0)
589
+ else:
590
+ weights = [1.0] * num_observations
591
+
592
+ obs_records = [
593
+ RangeAccrualObservationRecord(
594
+ observation_time=t,
595
+ weight=w,
596
+ upper_barrier=upper_barriers[i],
597
+ lower_barrier=lower_barriers[i],
598
+ )
599
+ for i, (t, w) in enumerate(zip(obs_times, weights))
600
+ ]
601
+
602
+ return RangeAccrualOption(
603
+ initial_price=initial_price,
604
+ range_config=range_config,
605
+ maturity=maturity,
606
+ observation_records=obs_records,
607
+ contract_multiplier=contract_multiplier,
608
+ )