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.
- quantark/__init__.py +3 -0
- quantark/_compat.py +150 -0
- quantark/asset/__init__.py +8 -0
- quantark/asset/bond/__init__.py +2 -0
- quantark/asset/bond/engine/__init__.py +44 -0
- quantark/asset/bond/engine/analytical/__init__.py +12 -0
- quantark/asset/bond/engine/analytical/black_engine.py +583 -0
- quantark/asset/bond/engine/analytical/bond_forward_engine.py +390 -0
- quantark/asset/bond/engine/analytical/bond_futures_engine.py +569 -0
- quantark/asset/bond/engine/convertible/__init__.py +12 -0
- quantark/asset/bond/engine/convertible/convertible_bond_engine.py +800 -0
- quantark/asset/bond/engine/discount/__init__.py +10 -0
- quantark/asset/bond/engine/discount/bond_discount_engine.py +517 -0
- quantark/asset/bond/engine/discount/frn_engine.py +913 -0
- quantark/asset/bond/engine/pde/__init__.py +14 -0
- quantark/asset/bond/engine/pde/convertible/__init__.py +21 -0
- quantark/asset/bond/engine/pde/convertible/jump_diffusion_engine.py +603 -0
- quantark/asset/bond/engine/pde/convertible/pde_params.py +59 -0
- quantark/asset/bond/engine/pde/convertible/tf_engine.py +546 -0
- quantark/asset/bond/engine/tree/__init__.py +14 -0
- quantark/asset/bond/engine/tree/convertible/__init__.py +21 -0
- quantark/asset/bond/engine/tree/convertible/binomial_engine.py +488 -0
- quantark/asset/bond/engine/tree/convertible/tree_params.py +72 -0
- quantark/asset/bond/engine/tree/convertible/trinomial_engine.py +1341 -0
- quantark/asset/bond/product/__init__.py +37 -0
- quantark/asset/bond/product/base_bond_product.py +114 -0
- quantark/asset/bond/product/convertible/__init__.py +16 -0
- quantark/asset/bond/product/convertible/convertible_bond.py +595 -0
- quantark/asset/bond/product/couponbond/__init__.py +12 -0
- quantark/asset/bond/product/couponbond/fixed_bond.py +285 -0
- quantark/asset/bond/product/couponbond/frn.py +538 -0
- quantark/asset/bond/product/forward/__init__.py +9 -0
- quantark/asset/bond/product/forward/base_bond_forward.py +92 -0
- quantark/asset/bond/product/forward/bond_forward.py +335 -0
- quantark/asset/bond/product/futures/__init__.py +8 -0
- quantark/asset/bond/product/futures/bond_futures.py +532 -0
- quantark/asset/bond/product/option/__init__.py +9 -0
- quantark/asset/bond/product/option/euro_short_term_bond_option.py +231 -0
- quantark/asset/bond/riskmeasures/__init__.py +13 -0
- quantark/asset/bond/riskmeasures/bond_greeks_calculator.py +484 -0
- quantark/asset/bond/schedule/__init__.py +21 -0
- quantark/asset/bond/schedule/cashflow.py +595 -0
- quantark/asset/equity/__init__.py +11 -0
- quantark/asset/equity/analysis/__init__.py +4 -0
- quantark/asset/equity/analysis/autocallable_path_analyzer.py +257 -0
- quantark/asset/equity/engine/__init__.py +84 -0
- quantark/asset/equity/engine/analytical/__init__.py +37 -0
- quantark/asset/equity/engine/analytical/american_option_engine.py +682 -0
- quantark/asset/equity/engine/analytical/asian_option_analytical_engine.py +1102 -0
- quantark/asset/equity/engine/analytical/barrier_analytical_engine.py +455 -0
- quantark/asset/equity/engine/analytical/black_scholes_engine.py +322 -0
- quantark/asset/equity/engine/analytical/deltaone_engine.py +340 -0
- quantark/asset/equity/engine/analytical/digital_option_engine.py +168 -0
- quantark/asset/equity/engine/analytical/double_barrier_option_engine.py +481 -0
- quantark/asset/equity/engine/analytical/double_sharkfin_option_analytical_engine.py +508 -0
- quantark/asset/equity/engine/analytical/one_touch_analytical_engine.py +302 -0
- quantark/asset/equity/engine/analytical/range_accrual_analytical_engine.py +396 -0
- quantark/asset/equity/engine/analytical/single_sharkfin_option_analytical_engine.py +229 -0
- quantark/asset/equity/engine/base_engine.py +137 -0
- quantark/asset/equity/engine/event_stats.py +85 -0
- quantark/asset/equity/engine/mc/__init__.py +31 -0
- quantark/asset/equity/engine/mc/american_option_mc_engine.py +485 -0
- quantark/asset/equity/engine/mc/asian_option_mc_engine.py +678 -0
- quantark/asset/equity/engine/mc/barrier_option_mc_engine.py +726 -0
- quantark/asset/equity/engine/mc/digital_option_mc_engine.py +419 -0
- quantark/asset/equity/engine/mc/double_sharkfin_option_mc_engine.py +676 -0
- quantark/asset/equity/engine/mc/euro_mc_engine.py +423 -0
- quantark/asset/equity/engine/mc/phoenix_mc_engine.py +1206 -0
- quantark/asset/equity/engine/mc/range_accrual_mc_engine.py +738 -0
- quantark/asset/equity/engine/mc/single_sharkfin_option_mc_engine.py +549 -0
- quantark/asset/equity/engine/mc/snowball_mc_engine.py +2250 -0
- quantark/asset/equity/engine/pde/__init__.py +36 -0
- quantark/asset/equity/engine/pde/american_pde_solver.py +211 -0
- quantark/asset/equity/engine/pde/barrier_pde_solver.py +692 -0
- quantark/asset/equity/engine/pde/base_pde_solver.py +994 -0
- quantark/asset/equity/engine/pde/double_barrier_pde_solver.py +510 -0
- quantark/asset/equity/engine/pde/double_one_touch_pde_solver.py +435 -0
- quantark/asset/equity/engine/pde/european_pde_solver.py +170 -0
- quantark/asset/equity/engine/pde/ko_reset_snowball_pde_solver.py +477 -0
- quantark/asset/equity/engine/pde/one_touch_pde_solver.py +439 -0
- quantark/asset/equity/engine/pde/phoenix_pde_solver.py +613 -0
- quantark/asset/equity/engine/pde/snowball_pde_solver.py +1810 -0
- quantark/asset/equity/engine/pde/spatial_grid.py +750 -0
- quantark/asset/equity/engine/pde/time_grid.py +308 -0
- quantark/asset/equity/engine/pde_engine.py +238 -0
- quantark/asset/equity/engine/quad/__init__.py +23 -0
- quantark/asset/equity/engine/quad/discrete_quad_engine.py +106 -0
- quantark/asset/equity/engine/quad/european_quad_engine.py +325 -0
- quantark/asset/equity/engine/quad/ko_reset_snowball_quad_engine.py +362 -0
- quantark/asset/equity/engine/quad/phoenix_quad_engine.py +614 -0
- quantark/asset/equity/engine/quad/quad_adapters.py +1260 -0
- quantark/asset/equity/engine/quad/quad_core.py +513 -0
- quantark/asset/equity/engine/quad/quad_math.py +219 -0
- quantark/asset/equity/engine/quad/snowball_quad_engine.py +1137 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_american_analytical.py +117 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_american_pde.py +114 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_asian_analytical.py +440 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_barrier_analytical.py +269 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_barrier_pde_solver.py +636 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_digital_option.py +256 -0
- quantark/asset/equity/engine/validation/script/benchmark_check_snowball_pde_solver.py +807 -0
- quantark/asset/equity/engine/validation/script/boundary_check_american_analytical.py +290 -0
- quantark/asset/equity/engine/validation/script/boundary_check_american_pde.py +242 -0
- quantark/asset/equity/engine/validation/script/boundary_check_asian_analytical.py +612 -0
- quantark/asset/equity/engine/validation/script/boundary_check_barrier_analytical.py +434 -0
- quantark/asset/equity/engine/validation/script/boundary_check_barrier_pde_solver.py +748 -0
- quantark/asset/equity/engine/validation/script/boundary_check_digital_option.py +575 -0
- quantark/asset/equity/engine/validation/script/boundary_check_snowball_pde_solver.py +1101 -0
- quantark/asset/equity/engine/validation/script/greeks_check_digital_option.py +349 -0
- quantark/asset/equity/engine/validation/script/mc_comparison_barrier_pde.py +270 -0
- quantark/asset/equity/engine/validation/script/quick_mc_compare.py +51 -0
- quantark/asset/equity/engine/validation/script/validation_stepdown_improved.py +97 -0
- quantark/asset/equity/param/__init__.py +24 -0
- quantark/asset/equity/param/engine_param_profiles.py +325 -0
- quantark/asset/equity/param/engine_params.py +728 -0
- quantark/asset/equity/process/__init__.py +7 -0
- quantark/asset/equity/process/bsm/__init__.py +7 -0
- quantark/asset/equity/process/bsm/bsm_process.py +108 -0
- quantark/asset/equity/process/bsm/qmc_brownian_bridge.py +401 -0
- quantark/asset/equity/process/bsm/qmc_path_generator.py +694 -0
- quantark/asset/equity/process/bsm/qmc_rqmc_driver.py +163 -0
- quantark/asset/equity/process/bsm/qmc_sobol.py +195 -0
- quantark/asset/equity/process/bsm/qmc_variance_reduction.py +292 -0
- quantark/asset/equity/product/__init__.py +8 -0
- quantark/asset/equity/product/base_equity_product.py +72 -0
- quantark/asset/equity/product/deltaone/__init__.py +22 -0
- quantark/asset/equity/product/deltaone/base_deltaone_product.py +147 -0
- quantark/asset/equity/product/deltaone/futures.py +485 -0
- quantark/asset/equity/product/deltaone/spot_instrument.py +118 -0
- quantark/asset/equity/product/option/__init__.py +104 -0
- quantark/asset/equity/product/option/american_option.py +114 -0
- quantark/asset/equity/product/option/asian_option.py +531 -0
- quantark/asset/equity/product/option/barrier_option.py +289 -0
- quantark/asset/equity/product/option/base_equity_option.py +659 -0
- quantark/asset/equity/product/option/digital_option.py +102 -0
- quantark/asset/equity/product/option/double_barrier_option.py +286 -0
- quantark/asset/equity/product/option/double_one_touch_option.py +310 -0
- quantark/asset/equity/product/option/double_sharkfin_option.py +466 -0
- quantark/asset/equity/product/option/european_vanilla_option.py +103 -0
- quantark/asset/equity/product/option/ko_reset_snowball_option.py +563 -0
- quantark/asset/equity/product/option/observation_schedule.py +530 -0
- quantark/asset/equity/product/option/one_touch_option.py +287 -0
- quantark/asset/equity/product/option/phoenix_config.py +116 -0
- quantark/asset/equity/product/option/phoenix_helpers.py +576 -0
- quantark/asset/equity/product/option/phoenix_option.py +1167 -0
- quantark/asset/equity/product/option/range_accrual_config.py +288 -0
- quantark/asset/equity/product/option/range_accrual_helpers.py +608 -0
- quantark/asset/equity/product/option/range_accrual_option.py +526 -0
- quantark/asset/equity/product/option/single_sharkfin_option.py +420 -0
- quantark/asset/equity/product/option/snowball_config.py +261 -0
- quantark/asset/equity/product/option/snowball_helpers.py +977 -0
- quantark/asset/equity/product/option/snowball_option.py +1242 -0
- quantark/asset/equity/report/__init__.py +15 -0
- quantark/asset/equity/report/autocallable_risk_report.py +2118 -0
- quantark/asset/equity/report/plotting.py +87 -0
- quantark/asset/equity/report/snowball_risk_comparison_report.py +2230 -0
- quantark/asset/equity/report/surfaces.py +123 -0
- quantark/asset/equity/report/term_structure.py +126 -0
- quantark/asset/equity/riskmeasures/__init__.py +7 -0
- quantark/asset/equity/riskmeasures/greeks_calculator.py +1204 -0
- quantark/asset/rate/__init__.py +58 -0
- quantark/asset/rate/engine/__init__.py +25 -0
- quantark/asset/rate/engine/cap_floor_engine.py +514 -0
- quantark/asset/rate/engine/fra_engine.py +286 -0
- quantark/asset/rate/engine/irs_discount_engine.py +891 -0
- quantark/asset/rate/engine/swaption_engine.py +587 -0
- quantark/asset/rate/product/__init__.py +67 -0
- quantark/asset/rate/product/cap_floor.py +550 -0
- quantark/asset/rate/product/fra.py +219 -0
- quantark/asset/rate/product/irs.py +1223 -0
- quantark/asset/rate/product/swaption.py +372 -0
- quantark/backtest/__init__.py +153 -0
- quantark/backtest/base.py +263 -0
- quantark/backtest/dashboard.py +874 -0
- quantark/backtest/equity/__init__.py +35 -0
- quantark/backtest/equity/config.py +118 -0
- quantark/backtest/equity/engine.py +408 -0
- quantark/backtest/equity/hedge_executor.py +374 -0
- quantark/backtest/equity/metrics.py +396 -0
- quantark/backtest/equity/results.py +232 -0
- quantark/backtest/equity/state.py +252 -0
- quantark/backtest/examples/__init__.py +4 -0
- quantark/backtest/examples/advanced_backtest.py +345 -0
- quantark/backtest/examples/basic_delta_hedge.py +246 -0
- quantark/backtest/examples/fi_dv01_hedge.py +267 -0
- quantark/backtest/fi/__init__.py +30 -0
- quantark/backtest/fi/config.py +114 -0
- quantark/backtest/fi/engine.py +378 -0
- quantark/backtest/fi/hedge_executor.py +254 -0
- quantark/backtest/fi/metrics.py +308 -0
- quantark/backtest/fi/results.py +193 -0
- quantark/backtest/fi/state.py +212 -0
- quantark/backtest/logger.py +393 -0
- quantark/backtest/otc/__init__.py +74 -0
- quantark/backtest/otc/_replay.py +637 -0
- quantark/backtest/otc/book_engine.py +587 -0
- quantark/backtest/otc/config.py +175 -0
- quantark/backtest/otc/dashboard.py +1006 -0
- quantark/backtest/otc/engine.py +420 -0
- quantark/backtest/otc/engine_factory.py +138 -0
- quantark/backtest/otc/market.py +216 -0
- quantark/backtest/otc/results.py +107 -0
- quantark/backtest/otc/state.py +166 -0
- quantark/backtest/report_generator.py +608 -0
- quantark/backtest/strategy/__init__.py +28 -0
- quantark/backtest/strategy/base_strategy.py +235 -0
- quantark/backtest/strategy/convexity_neutral_strategy.py +247 -0
- quantark/backtest/strategy/delta_neutral_strategy.py +283 -0
- quantark/backtest/strategy/dv01_neutral_strategy.py +283 -0
- quantark/backtest/transaction_costs.py +485 -0
- quantark/backtest/visualizer.py +1019 -0
- quantark/cashleg/__init__.py +31 -0
- quantark/cashleg/accrual_leg.py +120 -0
- quantark/cashleg/base.py +48 -0
- quantark/cashleg/base_amount.py +60 -0
- quantark/cashleg/deterministic_leg.py +39 -0
- quantark/cashleg/event_distribution.py +262 -0
- quantark/cashleg/fixed_payoff_leg.py +92 -0
- quantark/cashleg/leg_schedule.py +95 -0
- quantark/cashleg/leg_valuator.py +40 -0
- quantark/dynamicscenario/__init__.py +97 -0
- quantark/dynamicscenario/base.py +297 -0
- quantark/dynamicscenario/config.py +122 -0
- quantark/dynamicscenario/engine.py +703 -0
- quantark/dynamicscenario/equity/__init__.py +14 -0
- quantark/dynamicscenario/fi/__init__.py +24 -0
- quantark/dynamicscenario/fi/config.py +149 -0
- quantark/dynamicscenario/fi/engine.py +500 -0
- quantark/dynamicscenario/fi/results.py +503 -0
- quantark/dynamicscenario/path/__init__.py +17 -0
- quantark/dynamicscenario/path/day_path.py +397 -0
- quantark/dynamicscenario/path/fi_path_library.py +488 -0
- quantark/dynamicscenario/path/path_builder.py +726 -0
- quantark/dynamicscenario/path/path_library.py +620 -0
- quantark/dynamicscenario/report/__init__.py +12 -0
- quantark/dynamicscenario/report/dynamic_report.py +1175 -0
- quantark/dynamicscenario/report/visualizer.py +1586 -0
- quantark/dynamicscenario/results/__init__.py +19 -0
- quantark/dynamicscenario/results/dynamic_results.py +579 -0
- quantark/dynamicscenario/results/result_exporter.py +438 -0
- quantark/param/__init__.py +75 -0
- quantark/param/basis/__init__.py +19 -0
- quantark/param/basis/basis_yield.py +301 -0
- quantark/param/div/__init__.py +16 -0
- quantark/param/div/dividend_yield.py +123 -0
- quantark/param/index/__init__.py +52 -0
- quantark/param/index/rate_index.py +568 -0
- quantark/param/quote/__init__.py +7 -0
- quantark/param/quote/spot_quote.py +35 -0
- quantark/param/rrf/__init__.py +22 -0
- quantark/param/rrf/rate_curve.py +436 -0
- quantark/param/vol/__init__.py +6 -0
- quantark/param/vol/vol_surface.py +118 -0
- quantark/portfolio/__init__.py +61 -0
- quantark/portfolio/base.py +203 -0
- quantark/portfolio/equity/__init__.py +17 -0
- quantark/portfolio/equity/portfolio.py +391 -0
- quantark/portfolio/equity/position.py +368 -0
- quantark/portfolio/fi/__init__.py +14 -0
- quantark/portfolio/fi/portfolio.py +424 -0
- quantark/portfolio/fi/position.py +272 -0
- quantark/portfolio/portfolio_snapshot.py +221 -0
- quantark/portfolio/portfolio_storage.py +414 -0
- quantark/priceenv/__init__.py +7 -0
- quantark/priceenv/pricing_environment.py +196 -0
- quantark/rfq/__init__.py +32 -0
- quantark/rfq/builders.py +102 -0
- quantark/rfq/models.py +214 -0
- quantark/rfq/registry.py +611 -0
- quantark/rfq/service.py +237 -0
- quantark/simm/__init__.py +155 -0
- quantark/simm/calibration/__init__.py +206 -0
- quantark/simm/calibration/accessors.py +439 -0
- quantark/simm/calibration/commodity.py +156 -0
- quantark/simm/calibration/credit_non_qualifying.py +79 -0
- quantark/simm/calibration/credit_qualifying.py +130 -0
- quantark/simm/calibration/cross_risk.py +39 -0
- quantark/simm/calibration/equity.py +125 -0
- quantark/simm/calibration/fx.py +92 -0
- quantark/simm/calibration/ir.py +152 -0
- quantark/simm/calibration/version.py +33 -0
- quantark/simm/config.py +186 -0
- quantark/simm/crif/__init__.py +35 -0
- quantark/simm/crif/models.py +230 -0
- quantark/simm/crif/parser.py +585 -0
- quantark/simm/engines/__init__.py +62 -0
- quantark/simm/engines/aggregation/__init__.py +67 -0
- quantark/simm/engines/aggregation/addon.py +141 -0
- quantark/simm/engines/aggregation/bucket_aggregator.py +298 -0
- quantark/simm/engines/aggregation/concentration.py +349 -0
- quantark/simm/engines/aggregation/product_class_aggregator.py +183 -0
- quantark/simm/engines/aggregation/risk_class_aggregator.py +403 -0
- quantark/simm/engines/aggregation/simm_calculator.py +430 -0
- quantark/simm/engines/aggregation/weighted_sensitivity.py +272 -0
- quantark/simm/engines/base.py +231 -0
- quantark/simm/engines/classification/__init__.py +10 -0
- quantark/simm/engines/classification/bucket_mapper.py +347 -0
- quantark/simm/engines/factory.py +137 -0
- quantark/simm/engines/portfolio_adapter.py +336 -0
- quantark/simm/engines/result.py +176 -0
- quantark/simm/engines/risk_class/__init__.py +18 -0
- quantark/simm/engines/risk_class/equity_engine.py +263 -0
- quantark/simm/engines/risk_class/ir_engine.py +264 -0
- quantark/simm/report/__init__.py +17 -0
- quantark/simm/report/crif_export.py +284 -0
- quantark/simm/report/excel_generator.py +401 -0
- quantark/simm/report/html_generator.py +840 -0
- quantark/simm/results/__init__.py +38 -0
- quantark/simm/results/attribution.py +313 -0
- quantark/simm/results/simm_result.py +339 -0
- quantark/simm/results/whatif.py +268 -0
- quantark/simm/sensitivity.py +533 -0
- quantark/simm/taxonomy.py +416 -0
- quantark/stresstest/__init__.py +67 -0
- quantark/stresstest/base.py +116 -0
- quantark/stresstest/config.py +5 -0
- quantark/stresstest/engine.py +5 -0
- quantark/stresstest/equity/__init__.py +17 -0
- quantark/stresstest/equity/config.py +69 -0
- quantark/stresstest/equity/engine.py +272 -0
- quantark/stresstest/equity/report/__init__.py +7 -0
- quantark/stresstest/equity/report/report_generator.py +423 -0
- quantark/stresstest/equity/report/visualizer.py +328 -0
- quantark/stresstest/equity/results.py +145 -0
- quantark/stresstest/fi/__init__.py +15 -0
- quantark/stresstest/fi/config.py +59 -0
- quantark/stresstest/fi/engine.py +213 -0
- quantark/stresstest/fi/metrics.py +60 -0
- quantark/stresstest/fi/results.py +64 -0
- quantark/stresstest/report/__init__.py +12 -0
- quantark/stresstest/report/report_generator.py +5 -0
- quantark/stresstest/report/visualizer.py +5 -0
- quantark/stresstest/results/__init__.py +16 -0
- quantark/stresstest/results/result_aggregator.py +325 -0
- quantark/stresstest/results/result_exporter.py +286 -0
- quantark/stresstest/results/stress_results.py +5 -0
- quantark/stresstest/scenario/__init__.py +13 -0
- quantark/stresstest/scenario/scenario.py +242 -0
- quantark/stresstest/scenario/scenario_builder.py +376 -0
- quantark/stresstest/scenario/scenario_library.py +435 -0
- quantark/stresstest/scenario/scenario_storage.py +224 -0
- quantark/stresstest/stress/__init__.py +13 -0
- quantark/stresstest/stress/stress_applicator.py +590 -0
- quantark/stresstest/stress/stress_types.py +142 -0
- quantark/util/__init__.py +23 -0
- quantark/util/barrier_shift.py +44 -0
- quantark/util/calendar/__init__.py +27 -0
- quantark/util/calendar/business_calendar.py +584 -0
- quantark/util/calendar/day_counter.py +517 -0
- quantark/util/calendar/holidayfile/china.csv +1920 -0
- quantark/util/calendar/holidayfile/china_sse.csv +1462 -0
- quantark/util/enum/__init__.py +81 -0
- quantark/util/enum/bond_enums.py +112 -0
- quantark/util/enum/deltaone_enums.py +16 -0
- quantark/util/enum/engine_enums.py +137 -0
- quantark/util/enum/greeks_enums.py +29 -0
- quantark/util/enum/option_enums.py +221 -0
- quantark/util/exceptions.py +66 -0
- quantark/util/marketdata/__init__.py +39 -0
- quantark/util/marketdata/adapter/base_adapter.py +203 -0
- quantark/util/marketdata/adapter/mock_adapter.py +265 -0
- quantark/util/marketdata/converter.py +289 -0
- quantark/util/marketdata/example_usage.py +314 -0
- quantark/util/marketdata/generator/__init__.py +7 -0
- quantark/util/marketdata/generator/mock_generator.py +466 -0
- quantark/util/marketdata/models.py +358 -0
- quantark/util/marketdata/storage/__init__.py +7 -0
- quantark/util/marketdata/storage/parquet_storage.py +340 -0
- quantark/util/numerical/__init__.py +98 -0
- quantark/util/numerical/comparison.py +219 -0
- quantark/util/numerical/constants.py +98 -0
- quantark/util/numerical/formatting.py +380 -0
- quantark/util/numerical/pnl.py +17 -0
- quantark/util/numerical/safe_math.py +238 -0
- quantark/util/numerical/validation.py +315 -0
- quantark/var/__init__.py +39 -0
- quantark/var/attribution.py +398 -0
- quantark/var/backtest/__init__.py +7 -0
- quantark/var/backtest/var_backtester.py +309 -0
- quantark/var/base.py +63 -0
- quantark/var/config.py +219 -0
- quantark/var/engines/__init__.py +13 -0
- quantark/var/engines/historical.py +925 -0
- quantark/var/engines/monte_carlo.py +870 -0
- quantark/var/engines/parametric.py +1199 -0
- quantark/var/results/__init__.py +16 -0
- quantark/var/results/incremental_var_result.py +131 -0
- quantark/var/results/var_report.py +346 -0
- quantark/var/results/var_result.py +134 -0
- quantark/var/risk_factors/__init__.py +22 -0
- quantark/var/risk_factors/base.py +41 -0
- quantark/var/risk_factors/equity_factors.py +158 -0
- quantark/var/risk_factors/fi_factors.py +99 -0
- quantark-0.1.0.dist-info/METADATA +351 -0
- quantark-0.1.0.dist-info/RECORD +399 -0
- quantark-0.1.0.dist-info/WHEEL +4 -0
- quantark-0.1.0.dist-info/licenses/LICENSE +202 -0
- quantark-0.1.0.dist-info/licenses/NOTICE +2 -0
- 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
|
+
)
|