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,977 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Factory functions for creating common Snowball option structures.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions that simplify the creation of Snowball options
|
|
5
|
+
by providing sensible defaults for common market structures. Each helper accepts
|
|
6
|
+
minimal required parameters and allows full customization via **kwargs.
|
|
7
|
+
|
|
8
|
+
Available helpers:
|
|
9
|
+
- create_standard_snowball(): Basic snowball with flat KO barrier, continuous KI
|
|
10
|
+
- create_stepdown_snowball(): KO barrier decreases each observation period
|
|
11
|
+
- create_european_ki_snowball(): KI only observed at maturity
|
|
12
|
+
- create_parachute_snowball(): Last KO barrier equals KI barrier
|
|
13
|
+
- create_phoenix_snowball(): Periodic coupons above coupon barrier
|
|
14
|
+
- create_airbag_snowball(): Reduced participation below airbag barrier
|
|
15
|
+
|
|
16
|
+
Utilities:
|
|
17
|
+
- generate_ko_observation_dates(): Create evenly spaced observation dates
|
|
18
|
+
- generate_stepdown_barriers(): Generate decreasing barrier levels
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> from asset.equity.product.option import create_standard_snowball
|
|
22
|
+
>>> snowball = create_standard_snowball(
|
|
23
|
+
... initial_price=100.0,
|
|
24
|
+
... strike=100.0,
|
|
25
|
+
... maturity=1.0,
|
|
26
|
+
... contract_multiplier=1.0,
|
|
27
|
+
... )
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from dataclasses import fields
|
|
31
|
+
from typing import List, Optional
|
|
32
|
+
|
|
33
|
+
from quantark.util.exceptions import ValidationError
|
|
34
|
+
from quantark.util.enum import ObservationType, CouponPayType, PostKOScheduleMode
|
|
35
|
+
|
|
36
|
+
from .snowball_option import SnowballOption
|
|
37
|
+
from .ko_reset_snowball_option import KnockOutResetSnowballOption
|
|
38
|
+
from .snowball_config import BarrierConfig, PayoffConfig, AccrualConfig, AirbagConfig
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# =============================================================================
|
|
42
|
+
# Utility Functions
|
|
43
|
+
# =============================================================================
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def generate_ko_observation_dates(
|
|
47
|
+
maturity: float,
|
|
48
|
+
frequency: str = "monthly",
|
|
49
|
+
skip_first: int = 0,
|
|
50
|
+
) -> List[float]:
|
|
51
|
+
"""
|
|
52
|
+
Generate evenly spaced observation dates as year fractions.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
maturity: Time to maturity in years
|
|
56
|
+
frequency: Observation frequency - "monthly", "quarterly", "weekly", or "daily"
|
|
57
|
+
skip_first: Number of initial observations to skip (lock-out period)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
List of observation times as year fractions
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValidationError: If frequency is invalid or maturity is non-positive
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> generate_ko_observation_dates(1.0, "quarterly")
|
|
67
|
+
[0.25, 0.5, 0.75, 1.0]
|
|
68
|
+
>>> generate_ko_observation_dates(1.0, "monthly", skip_first=2)
|
|
69
|
+
[0.25, 0.333..., 0.416..., ..., 1.0]
|
|
70
|
+
"""
|
|
71
|
+
if maturity <= 0:
|
|
72
|
+
raise ValidationError(f"maturity must be positive, got {maturity}")
|
|
73
|
+
|
|
74
|
+
frequency_map = {
|
|
75
|
+
"monthly": 12,
|
|
76
|
+
"quarterly": 4,
|
|
77
|
+
"weekly": 52,
|
|
78
|
+
"daily": 252,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if frequency not in frequency_map:
|
|
82
|
+
raise ValidationError(
|
|
83
|
+
f"frequency must be one of {list(frequency_map.keys())}, got '{frequency}'"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
num_periods = frequency_map[frequency]
|
|
87
|
+
num_observations = int(maturity * num_periods)
|
|
88
|
+
|
|
89
|
+
if num_observations < 1:
|
|
90
|
+
raise ValidationError(
|
|
91
|
+
f"maturity {maturity} with frequency '{frequency}' yields no observations"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Generate dates evenly spaced up to maturity
|
|
95
|
+
all_dates = [(i + 1) / num_observations * maturity for i in range(num_observations)]
|
|
96
|
+
|
|
97
|
+
if skip_first >= len(all_dates):
|
|
98
|
+
raise ValidationError(
|
|
99
|
+
f"skip_first={skip_first} would skip all {len(all_dates)} observations"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return all_dates[skip_first:]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def generate_stepdown_barriers(
|
|
106
|
+
initial_barrier: float,
|
|
107
|
+
stepdown_amount: float,
|
|
108
|
+
num_observations: int,
|
|
109
|
+
min_barrier: Optional[float] = None,
|
|
110
|
+
) -> List[float]:
|
|
111
|
+
"""
|
|
112
|
+
Generate decreasing barrier levels for step-down structures.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
initial_barrier: Starting barrier level
|
|
116
|
+
stepdown_amount: Amount to decrease each period (absolute value)
|
|
117
|
+
num_observations: Number of observation dates
|
|
118
|
+
min_barrier: Optional floor for barrier levels
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
List of barrier levels, decreasing by stepdown_amount each period
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
ValidationError: If parameters are invalid
|
|
125
|
+
|
|
126
|
+
Example:
|
|
127
|
+
>>> generate_stepdown_barriers(103.0, 0.5, 4)
|
|
128
|
+
[103.0, 102.5, 102.0, 101.5]
|
|
129
|
+
>>> generate_stepdown_barriers(103.0, 2.0, 4, min_barrier=100.0)
|
|
130
|
+
[103.0, 101.0, 100.0, 100.0]
|
|
131
|
+
"""
|
|
132
|
+
if initial_barrier <= 0:
|
|
133
|
+
raise ValidationError(
|
|
134
|
+
f"initial_barrier must be positive, got {initial_barrier}"
|
|
135
|
+
)
|
|
136
|
+
if stepdown_amount < 0:
|
|
137
|
+
raise ValidationError(
|
|
138
|
+
f"stepdown_amount must be non-negative, got {stepdown_amount}"
|
|
139
|
+
)
|
|
140
|
+
if num_observations < 1:
|
|
141
|
+
raise ValidationError(
|
|
142
|
+
f"num_observations must be at least 1, got {num_observations}"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
barriers = []
|
|
146
|
+
current = initial_barrier
|
|
147
|
+
for _ in range(num_observations):
|
|
148
|
+
if min_barrier is not None:
|
|
149
|
+
current = max(current, min_barrier)
|
|
150
|
+
barriers.append(current)
|
|
151
|
+
current -= stepdown_amount
|
|
152
|
+
|
|
153
|
+
return barriers
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# =============================================================================
|
|
157
|
+
# Helper Functions
|
|
158
|
+
# =============================================================================
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _validate_core_params(
|
|
162
|
+
initial_price: float,
|
|
163
|
+
strike: float,
|
|
164
|
+
maturity: float,
|
|
165
|
+
contract_multiplier: float,
|
|
166
|
+
func_name: str,
|
|
167
|
+
) -> None:
|
|
168
|
+
"""Validate core parameters common to all helpers."""
|
|
169
|
+
if initial_price <= 0:
|
|
170
|
+
raise ValidationError(
|
|
171
|
+
f"{func_name}: initial_price must be positive, got {initial_price}"
|
|
172
|
+
)
|
|
173
|
+
if strike <= 0:
|
|
174
|
+
raise ValidationError(f"{func_name}: strike must be positive, got {strike}")
|
|
175
|
+
if maturity <= 0:
|
|
176
|
+
raise ValidationError(f"{func_name}: maturity must be positive, got {maturity}")
|
|
177
|
+
if contract_multiplier <= 0:
|
|
178
|
+
raise ValidationError(
|
|
179
|
+
f"{func_name}: contract_multiplier must be positive, got {contract_multiplier}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _extract_config_kwargs(kwargs: dict) -> tuple:
|
|
184
|
+
"""
|
|
185
|
+
Extract kwargs for each config class using introspection.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
ValidationError: If kwargs contains unknown parameters.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
(barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs)
|
|
192
|
+
"""
|
|
193
|
+
barrier_fields = {f.name for f in fields(BarrierConfig)}
|
|
194
|
+
payoff_fields = {f.name for f in fields(PayoffConfig)}
|
|
195
|
+
accrual_fields = {f.name for f in fields(AccrualConfig)}
|
|
196
|
+
airbag_fields = {f.name for f in fields(AirbagConfig)}
|
|
197
|
+
|
|
198
|
+
barrier_kwargs = {}
|
|
199
|
+
payoff_kwargs = {}
|
|
200
|
+
accrual_kwargs = {}
|
|
201
|
+
airbag_kwargs = {}
|
|
202
|
+
unknown_kwargs = []
|
|
203
|
+
|
|
204
|
+
for key, value in kwargs.items():
|
|
205
|
+
if key in barrier_fields:
|
|
206
|
+
barrier_kwargs[key] = value
|
|
207
|
+
elif key in payoff_fields:
|
|
208
|
+
payoff_kwargs[key] = value
|
|
209
|
+
elif key in accrual_fields:
|
|
210
|
+
accrual_kwargs[key] = value
|
|
211
|
+
elif key in airbag_fields:
|
|
212
|
+
airbag_kwargs[key] = value
|
|
213
|
+
else:
|
|
214
|
+
unknown_kwargs.append(key)
|
|
215
|
+
|
|
216
|
+
if unknown_kwargs:
|
|
217
|
+
raise ValidationError(
|
|
218
|
+
f"Unknown parameters provided: {', '.join(unknown_kwargs)}. "
|
|
219
|
+
"Please check spelling or valid configuration fields."
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def create_standard_snowball(
|
|
226
|
+
initial_price: float,
|
|
227
|
+
strike: float,
|
|
228
|
+
maturity: float,
|
|
229
|
+
contract_multiplier: float = 1.0,
|
|
230
|
+
ko_barrier: Optional[float] = None,
|
|
231
|
+
ko_rate: float = 0.15,
|
|
232
|
+
ki_barrier: Optional[float] = None,
|
|
233
|
+
num_observations: int = 12,
|
|
234
|
+
is_reverse: bool = False,
|
|
235
|
+
**kwargs,
|
|
236
|
+
) -> SnowballOption:
|
|
237
|
+
"""
|
|
238
|
+
Create a standard snowball with flat KO barrier and continuous KI monitoring.
|
|
239
|
+
|
|
240
|
+
This is the most common snowball structure with:
|
|
241
|
+
- Discrete KO observations (monthly by default)
|
|
242
|
+
- Continuous KI monitoring
|
|
243
|
+
- Flat (constant) KO barrier
|
|
244
|
+
- Annualized coupon rate
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
initial_price: Reference price for payoff calculations
|
|
248
|
+
strike: Strike price for embedded option
|
|
249
|
+
maturity: Time to maturity in years
|
|
250
|
+
contract_multiplier: Underlying units represented by one contract
|
|
251
|
+
ko_barrier: Knock-out barrier (default: 103% of initial_price)
|
|
252
|
+
ko_rate: Annualized knock-out rate (default: 15%)
|
|
253
|
+
ki_barrier: Knock-in barrier (default: 75% of initial_price)
|
|
254
|
+
num_observations: Number of KO observations (default: 12 for monthly)
|
|
255
|
+
is_reverse: If True, create reverse snowball (default: False)
|
|
256
|
+
**kwargs: Additional parameters passed to config objects or SnowballOption
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Configured SnowballOption instance
|
|
260
|
+
|
|
261
|
+
Example:
|
|
262
|
+
>>> snowball = create_standard_snowball(
|
|
263
|
+
... initial_price=100.0,
|
|
264
|
+
... strike=100.0,
|
|
265
|
+
... maturity=1.0,
|
|
266
|
+
... )
|
|
267
|
+
>>> snowball.barrier_config.ko_barrier
|
|
268
|
+
103.0
|
|
269
|
+
"""
|
|
270
|
+
_validate_core_params(
|
|
271
|
+
initial_price, strike, maturity, contract_multiplier, "create_standard_snowball"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# Apply defaults
|
|
275
|
+
if ko_barrier is None:
|
|
276
|
+
ko_barrier = 1.03 * initial_price
|
|
277
|
+
if ki_barrier is None:
|
|
278
|
+
ki_barrier = 0.75 * initial_price
|
|
279
|
+
|
|
280
|
+
# Generate observation dates
|
|
281
|
+
ko_observation_dates = [
|
|
282
|
+
(i + 1) / num_observations * maturity for i in range(num_observations)
|
|
283
|
+
]
|
|
284
|
+
|
|
285
|
+
# Extract config-specific kwargs
|
|
286
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
|
|
287
|
+
|
|
288
|
+
# Build configs
|
|
289
|
+
barrier_config = BarrierConfig(
|
|
290
|
+
ko_barrier=ko_barrier,
|
|
291
|
+
ko_rate=ko_rate,
|
|
292
|
+
ko_observation_type=barrier_kwargs.pop(
|
|
293
|
+
"ko_observation_type", ObservationType.DISCRETE
|
|
294
|
+
),
|
|
295
|
+
ko_observation_dates=barrier_kwargs.pop(
|
|
296
|
+
"ko_observation_dates", ko_observation_dates
|
|
297
|
+
),
|
|
298
|
+
ki_barrier=ki_barrier,
|
|
299
|
+
ki_observation_type=barrier_kwargs.pop(
|
|
300
|
+
"ki_observation_type", ObservationType.CONTINUOUS
|
|
301
|
+
),
|
|
302
|
+
ki_continuous=barrier_kwargs.pop("ki_continuous", True),
|
|
303
|
+
**barrier_kwargs,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
payoff_config = PayoffConfig(
|
|
307
|
+
rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
|
|
308
|
+
include_principal=payoff_kwargs.pop("include_principal", False),
|
|
309
|
+
**payoff_kwargs,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
accrual_config = AccrualConfig(
|
|
313
|
+
coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
|
|
314
|
+
is_annualized=accrual_kwargs.pop("is_annualized", True),
|
|
315
|
+
**accrual_kwargs,
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
airbag_config = AirbagConfig(**airbag_kwargs)
|
|
319
|
+
|
|
320
|
+
return SnowballOption(
|
|
321
|
+
initial_price=initial_price,
|
|
322
|
+
strike=strike,
|
|
323
|
+
maturity=maturity,
|
|
324
|
+
contract_multiplier=contract_multiplier,
|
|
325
|
+
barrier_config=barrier_config,
|
|
326
|
+
payoff_config=payoff_config,
|
|
327
|
+
accrual_config=accrual_config,
|
|
328
|
+
airbag_config=airbag_config,
|
|
329
|
+
is_reverse=is_reverse,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def create_stepdown_snowball(
|
|
334
|
+
initial_price: float,
|
|
335
|
+
strike: float,
|
|
336
|
+
maturity: float,
|
|
337
|
+
contract_multiplier: float = 1.0,
|
|
338
|
+
initial_ko_barrier: Optional[float] = None,
|
|
339
|
+
stepdown_rate: float = 0.005,
|
|
340
|
+
ko_rate: float = 0.15,
|
|
341
|
+
ki_barrier: Optional[float] = None,
|
|
342
|
+
num_observations: int = 12,
|
|
343
|
+
is_reverse: bool = False,
|
|
344
|
+
**kwargs,
|
|
345
|
+
) -> SnowballOption:
|
|
346
|
+
"""
|
|
347
|
+
Create a step-down snowball where KO barrier decreases each observation.
|
|
348
|
+
|
|
349
|
+
Common in China structured products market (递减雪球). The KO barrier
|
|
350
|
+
starts high and decreases each observation period, making knock-out
|
|
351
|
+
progressively easier to achieve.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
initial_price: Reference price for payoff calculations
|
|
355
|
+
strike: Strike price for embedded option
|
|
356
|
+
maturity: Time to maturity in years
|
|
357
|
+
contract_multiplier: Underlying units represented by one contract
|
|
358
|
+
initial_ko_barrier: Starting KO barrier (default: 103% of initial_price)
|
|
359
|
+
stepdown_rate: Rate of barrier decrease per period as fraction of initial_price
|
|
360
|
+
(default: 0.5% per period)
|
|
361
|
+
ko_rate: Annualized knock-out rate (default: 15%)
|
|
362
|
+
ki_barrier: Knock-in barrier (default: 75% of initial_price)
|
|
363
|
+
num_observations: Number of KO observations (default: 12 for monthly)
|
|
364
|
+
is_reverse: If True, create reverse snowball (default: False)
|
|
365
|
+
**kwargs: Additional parameters passed to config objects or SnowballOption
|
|
366
|
+
|
|
367
|
+
Returns:
|
|
368
|
+
Configured SnowballOption instance with decreasing KO barriers
|
|
369
|
+
|
|
370
|
+
Example:
|
|
371
|
+
>>> snowball = create_stepdown_snowball(
|
|
372
|
+
... initial_price=100.0,
|
|
373
|
+
... strike=100.0,
|
|
374
|
+
... maturity=1.0,
|
|
375
|
+
... stepdown_rate=0.005,
|
|
376
|
+
... )
|
|
377
|
+
>>> snowball.barrier_config.ko_barrier[:4]
|
|
378
|
+
[103.0, 102.5, 102.0, 101.5]
|
|
379
|
+
"""
|
|
380
|
+
_validate_core_params(
|
|
381
|
+
initial_price, strike, maturity, contract_multiplier, "create_stepdown_snowball"
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Apply defaults
|
|
385
|
+
if initial_ko_barrier is None:
|
|
386
|
+
initial_ko_barrier = 1.03 * initial_price
|
|
387
|
+
if ki_barrier is None:
|
|
388
|
+
ki_barrier = 0.75 * initial_price
|
|
389
|
+
|
|
390
|
+
# Generate step-down barriers
|
|
391
|
+
stepdown_amount = stepdown_rate * initial_price
|
|
392
|
+
ko_barriers = generate_stepdown_barriers(
|
|
393
|
+
initial_barrier=initial_ko_barrier,
|
|
394
|
+
stepdown_amount=stepdown_amount,
|
|
395
|
+
num_observations=num_observations,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
# Generate observation dates
|
|
399
|
+
ko_observation_dates = [
|
|
400
|
+
(i + 1) / num_observations * maturity for i in range(num_observations)
|
|
401
|
+
]
|
|
402
|
+
|
|
403
|
+
# Extract config-specific kwargs
|
|
404
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
|
|
405
|
+
|
|
406
|
+
# Build configs
|
|
407
|
+
barrier_config = BarrierConfig(
|
|
408
|
+
ko_barrier=ko_barriers,
|
|
409
|
+
ko_rate=ko_rate,
|
|
410
|
+
ko_observation_type=barrier_kwargs.pop(
|
|
411
|
+
"ko_observation_type", ObservationType.DISCRETE
|
|
412
|
+
),
|
|
413
|
+
ko_observation_dates=barrier_kwargs.pop(
|
|
414
|
+
"ko_observation_dates", ko_observation_dates
|
|
415
|
+
),
|
|
416
|
+
ki_barrier=ki_barrier,
|
|
417
|
+
ki_observation_type=barrier_kwargs.pop(
|
|
418
|
+
"ki_observation_type", ObservationType.CONTINUOUS
|
|
419
|
+
),
|
|
420
|
+
ki_continuous=barrier_kwargs.pop("ki_continuous", True),
|
|
421
|
+
**barrier_kwargs,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
payoff_config = PayoffConfig(
|
|
425
|
+
rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
|
|
426
|
+
include_principal=payoff_kwargs.pop("include_principal", False),
|
|
427
|
+
**payoff_kwargs,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
accrual_config = AccrualConfig(
|
|
431
|
+
coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
|
|
432
|
+
is_annualized=accrual_kwargs.pop("is_annualized", True),
|
|
433
|
+
**accrual_kwargs,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
airbag_config = AirbagConfig(**airbag_kwargs)
|
|
437
|
+
|
|
438
|
+
return SnowballOption(
|
|
439
|
+
initial_price=initial_price,
|
|
440
|
+
strike=strike,
|
|
441
|
+
maturity=maturity,
|
|
442
|
+
contract_multiplier=contract_multiplier,
|
|
443
|
+
barrier_config=barrier_config,
|
|
444
|
+
payoff_config=payoff_config,
|
|
445
|
+
accrual_config=accrual_config,
|
|
446
|
+
airbag_config=airbag_config,
|
|
447
|
+
is_reverse=is_reverse,
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def create_european_ki_snowball(
|
|
452
|
+
initial_price: float,
|
|
453
|
+
strike: float,
|
|
454
|
+
maturity: float,
|
|
455
|
+
contract_multiplier: float = 1.0,
|
|
456
|
+
ko_barrier: Optional[float] = None,
|
|
457
|
+
ko_rate: float = 0.15,
|
|
458
|
+
ki_barrier: Optional[float] = None,
|
|
459
|
+
num_ko_observations: int = 12,
|
|
460
|
+
is_reverse: bool = False,
|
|
461
|
+
**kwargs,
|
|
462
|
+
) -> SnowballOption:
|
|
463
|
+
"""
|
|
464
|
+
Create a snowball with European-style KI (only observed at maturity).
|
|
465
|
+
|
|
466
|
+
Unlike standard snowballs with continuous KI monitoring, this structure
|
|
467
|
+
only checks the KI barrier at maturity. This increases the probability
|
|
468
|
+
of the V0 outcome (no KO, no KI).
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
initial_price: Reference price for payoff calculations
|
|
472
|
+
strike: Strike price for embedded option
|
|
473
|
+
maturity: Time to maturity in years
|
|
474
|
+
contract_multiplier: Underlying units represented by one contract
|
|
475
|
+
ko_barrier: Knock-out barrier (default: 103% of initial_price)
|
|
476
|
+
ko_rate: Annualized knock-out rate (default: 15%)
|
|
477
|
+
ki_barrier: Knock-in barrier (default: 75% of initial_price)
|
|
478
|
+
num_ko_observations: Number of KO observations (default: 12 for monthly)
|
|
479
|
+
is_reverse: If True, create reverse snowball (default: False)
|
|
480
|
+
**kwargs: Additional parameters passed to config objects or SnowballOption
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Configured SnowballOption instance with European-style KI
|
|
484
|
+
|
|
485
|
+
Example:
|
|
486
|
+
>>> snowball = create_european_ki_snowball(
|
|
487
|
+
... initial_price=100.0,
|
|
488
|
+
... strike=100.0,
|
|
489
|
+
... maturity=1.0,
|
|
490
|
+
... )
|
|
491
|
+
>>> snowball.barrier_config.ki_continuous
|
|
492
|
+
False
|
|
493
|
+
>>> snowball.barrier_config.ki_observation_dates
|
|
494
|
+
[1.0]
|
|
495
|
+
"""
|
|
496
|
+
_validate_core_params(
|
|
497
|
+
initial_price,
|
|
498
|
+
strike,
|
|
499
|
+
maturity,
|
|
500
|
+
contract_multiplier,
|
|
501
|
+
"create_european_ki_snowball",
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Apply defaults
|
|
505
|
+
if ko_barrier is None:
|
|
506
|
+
ko_barrier = 1.03 * initial_price
|
|
507
|
+
if ki_barrier is None:
|
|
508
|
+
ki_barrier = 0.75 * initial_price
|
|
509
|
+
|
|
510
|
+
# Generate KO observation dates
|
|
511
|
+
ko_observation_dates = [
|
|
512
|
+
(i + 1) / num_ko_observations * maturity for i in range(num_ko_observations)
|
|
513
|
+
]
|
|
514
|
+
|
|
515
|
+
# KI only at maturity (European-style)
|
|
516
|
+
ki_observation_dates = [maturity]
|
|
517
|
+
|
|
518
|
+
# Extract config-specific kwargs
|
|
519
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
|
|
520
|
+
|
|
521
|
+
# Build configs - force discrete KI with single observation at maturity
|
|
522
|
+
barrier_config = BarrierConfig(
|
|
523
|
+
ko_barrier=ko_barrier,
|
|
524
|
+
ko_rate=ko_rate,
|
|
525
|
+
ko_observation_type=barrier_kwargs.pop(
|
|
526
|
+
"ko_observation_type", ObservationType.DISCRETE
|
|
527
|
+
),
|
|
528
|
+
ko_observation_dates=barrier_kwargs.pop(
|
|
529
|
+
"ko_observation_dates", ko_observation_dates
|
|
530
|
+
),
|
|
531
|
+
ki_barrier=ki_barrier,
|
|
532
|
+
ki_observation_type=ObservationType.DISCRETE, # Force discrete
|
|
533
|
+
ki_observation_dates=ki_observation_dates,
|
|
534
|
+
ki_continuous=False, # Force non-continuous
|
|
535
|
+
**barrier_kwargs,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
payoff_config = PayoffConfig(
|
|
539
|
+
rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
|
|
540
|
+
include_principal=payoff_kwargs.pop("include_principal", False),
|
|
541
|
+
**payoff_kwargs,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
accrual_config = AccrualConfig(
|
|
545
|
+
coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
|
|
546
|
+
is_annualized=accrual_kwargs.pop("is_annualized", True),
|
|
547
|
+
**accrual_kwargs,
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
airbag_config = AirbagConfig(**airbag_kwargs)
|
|
551
|
+
|
|
552
|
+
return SnowballOption(
|
|
553
|
+
initial_price=initial_price,
|
|
554
|
+
strike=strike,
|
|
555
|
+
maturity=maturity,
|
|
556
|
+
contract_multiplier=contract_multiplier,
|
|
557
|
+
barrier_config=barrier_config,
|
|
558
|
+
payoff_config=payoff_config,
|
|
559
|
+
accrual_config=accrual_config,
|
|
560
|
+
airbag_config=airbag_config,
|
|
561
|
+
is_reverse=is_reverse,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
def create_parachute_snowball(
|
|
566
|
+
initial_price: float,
|
|
567
|
+
strike: float,
|
|
568
|
+
maturity: float,
|
|
569
|
+
contract_multiplier: float = 1.0,
|
|
570
|
+
ko_barrier: Optional[float] = None,
|
|
571
|
+
ko_rate: float = 0.15,
|
|
572
|
+
ki_barrier: Optional[float] = None,
|
|
573
|
+
num_observations: int = 12,
|
|
574
|
+
is_reverse: bool = False,
|
|
575
|
+
**kwargs,
|
|
576
|
+
) -> SnowballOption:
|
|
577
|
+
"""
|
|
578
|
+
Create a parachute snowball where last KO barrier equals KI barrier.
|
|
579
|
+
|
|
580
|
+
Also known as 降落伞雪球 in China market. At the final observation,
|
|
581
|
+
the KO barrier drops to the KI level, guaranteeing an exit (knock-out)
|
|
582
|
+
if the product hasn't been knocked in. This provides a "parachute"
|
|
583
|
+
safety net for investors.
|
|
584
|
+
|
|
585
|
+
Args:
|
|
586
|
+
initial_price: Reference price for payoff calculations
|
|
587
|
+
strike: Strike price for embedded option
|
|
588
|
+
maturity: Time to maturity in years
|
|
589
|
+
contract_multiplier: Underlying units represented by one contract
|
|
590
|
+
ko_barrier: Knock-out barrier for early observations (default: 103% of initial_price)
|
|
591
|
+
ko_rate: Annualized knock-out rate (default: 15%)
|
|
592
|
+
ki_barrier: Knock-in barrier, also final KO barrier (default: 75% of initial_price)
|
|
593
|
+
num_observations: Number of KO observations (default: 12 for monthly)
|
|
594
|
+
is_reverse: If True, create reverse snowball (default: False)
|
|
595
|
+
**kwargs: Additional parameters passed to config objects or SnowballOption
|
|
596
|
+
|
|
597
|
+
Returns:
|
|
598
|
+
Configured SnowballOption instance with parachute structure
|
|
599
|
+
|
|
600
|
+
Example:
|
|
601
|
+
>>> snowball = create_parachute_snowball(
|
|
602
|
+
... initial_price=100.0,
|
|
603
|
+
... strike=100.0,
|
|
604
|
+
... maturity=1.0,
|
|
605
|
+
... )
|
|
606
|
+
>>> barriers = snowball.barrier_config.ko_barrier
|
|
607
|
+
>>> barriers[-1] == snowball.barrier_config.ki_barrier
|
|
608
|
+
True
|
|
609
|
+
"""
|
|
610
|
+
_validate_core_params(
|
|
611
|
+
initial_price,
|
|
612
|
+
strike,
|
|
613
|
+
maturity,
|
|
614
|
+
contract_multiplier,
|
|
615
|
+
"create_parachute_snowball",
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
# Apply defaults
|
|
619
|
+
if ko_barrier is None:
|
|
620
|
+
ko_barrier = 1.03 * initial_price
|
|
621
|
+
if ki_barrier is None:
|
|
622
|
+
ki_barrier = 0.75 * initial_price
|
|
623
|
+
|
|
624
|
+
# Build parachute KO barriers: flat until last, then drop to KI level
|
|
625
|
+
ko_barriers = [ko_barrier] * (num_observations - 1) + [ki_barrier]
|
|
626
|
+
|
|
627
|
+
# Generate observation dates
|
|
628
|
+
ko_observation_dates = [
|
|
629
|
+
(i + 1) / num_observations * maturity for i in range(num_observations)
|
|
630
|
+
]
|
|
631
|
+
|
|
632
|
+
# Extract config-specific kwargs
|
|
633
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(kwargs)
|
|
634
|
+
|
|
635
|
+
# Build configs
|
|
636
|
+
barrier_config = BarrierConfig(
|
|
637
|
+
ko_barrier=ko_barriers,
|
|
638
|
+
ko_rate=ko_rate,
|
|
639
|
+
ko_observation_type=barrier_kwargs.pop(
|
|
640
|
+
"ko_observation_type", ObservationType.DISCRETE
|
|
641
|
+
),
|
|
642
|
+
ko_observation_dates=barrier_kwargs.pop(
|
|
643
|
+
"ko_observation_dates", ko_observation_dates
|
|
644
|
+
),
|
|
645
|
+
ki_barrier=ki_barrier,
|
|
646
|
+
ki_observation_type=barrier_kwargs.pop(
|
|
647
|
+
"ki_observation_type", ObservationType.CONTINUOUS
|
|
648
|
+
),
|
|
649
|
+
ki_continuous=barrier_kwargs.pop("ki_continuous", True),
|
|
650
|
+
**barrier_kwargs,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
payoff_config = PayoffConfig(
|
|
654
|
+
rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
|
|
655
|
+
include_principal=payoff_kwargs.pop("include_principal", False),
|
|
656
|
+
**payoff_kwargs,
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
accrual_config = AccrualConfig(
|
|
660
|
+
coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
|
|
661
|
+
is_annualized=accrual_kwargs.pop("is_annualized", True),
|
|
662
|
+
**accrual_kwargs,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
airbag_config = AirbagConfig(**airbag_kwargs)
|
|
666
|
+
|
|
667
|
+
return SnowballOption(
|
|
668
|
+
initial_price=initial_price,
|
|
669
|
+
strike=strike,
|
|
670
|
+
maturity=maturity,
|
|
671
|
+
contract_multiplier=contract_multiplier,
|
|
672
|
+
barrier_config=barrier_config,
|
|
673
|
+
payoff_config=payoff_config,
|
|
674
|
+
accrual_config=accrual_config,
|
|
675
|
+
airbag_config=airbag_config,
|
|
676
|
+
is_reverse=is_reverse,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def create_airbag_snowball(
|
|
684
|
+
initial_price: float,
|
|
685
|
+
strike: float,
|
|
686
|
+
maturity: float,
|
|
687
|
+
contract_multiplier: float = 1.0,
|
|
688
|
+
ko_barrier: Optional[float] = None,
|
|
689
|
+
ko_rate: float = 0.15,
|
|
690
|
+
ki_barrier: Optional[float] = None,
|
|
691
|
+
airbag_barrier: Optional[float] = None,
|
|
692
|
+
participation_rate: float = 1.0,
|
|
693
|
+
airbag_participation_rate: float = 0.5,
|
|
694
|
+
airbag_strike: Optional[float] = None,
|
|
695
|
+
num_observations: int = 12,
|
|
696
|
+
is_reverse: bool = False,
|
|
697
|
+
**kwargs,
|
|
698
|
+
) -> SnowballOption:
|
|
699
|
+
"""
|
|
700
|
+
Create an airbag snowball with reduced participation below airbag barrier.
|
|
701
|
+
|
|
702
|
+
Airbag snowballs provide additional protection for extreme downside
|
|
703
|
+
by reducing the participation rate when spot falls below the airbag
|
|
704
|
+
barrier. This limits losses in severe market downturns.
|
|
705
|
+
|
|
706
|
+
Args:
|
|
707
|
+
initial_price: Reference price for payoff calculations
|
|
708
|
+
strike: Strike price for embedded option
|
|
709
|
+
maturity: Time to maturity in years
|
|
710
|
+
contract_multiplier: Underlying units represented by one contract
|
|
711
|
+
ko_barrier: Knock-out barrier (default: 103% of initial_price)
|
|
712
|
+
ko_rate: Annualized knock-out rate (default: 15%)
|
|
713
|
+
ki_barrier: Knock-in barrier (default: 75% of initial_price)
|
|
714
|
+
airbag_barrier: Barrier below which participation is reduced
|
|
715
|
+
(default: 60% of initial_price)
|
|
716
|
+
participation_rate: Participation rate for normal KI scenario (default: 100%)
|
|
717
|
+
airbag_participation_rate: Participation rate when below airbag barrier (default: 50%)
|
|
718
|
+
airbag_strike: Strike price for airbag payoff (optional, defaults to strike)
|
|
719
|
+
num_observations: Number of observations (default: 12 for monthly)
|
|
720
|
+
is_reverse: If True, create reverse snowball (default: False)
|
|
721
|
+
**kwargs: Additional parameters passed to config objects or SnowballOption
|
|
722
|
+
|
|
723
|
+
Returns:
|
|
724
|
+
Configured SnowballOption instance with airbag structure
|
|
725
|
+
|
|
726
|
+
Example:
|
|
727
|
+
>>> snowball = create_airbag_snowball(
|
|
728
|
+
... initial_price=100.0,
|
|
729
|
+
... strike=100.0,
|
|
730
|
+
... maturity=1.0,
|
|
731
|
+
... participation_rate=1.0,
|
|
732
|
+
... airbag_participation_rate=0.5,
|
|
733
|
+
... )
|
|
734
|
+
>>> snowball.airbag_config.airbag_participation_rate
|
|
735
|
+
0.5
|
|
736
|
+
"""
|
|
737
|
+
_validate_core_params(
|
|
738
|
+
initial_price,
|
|
739
|
+
strike,
|
|
740
|
+
maturity,
|
|
741
|
+
contract_multiplier,
|
|
742
|
+
"create_airbag_snowball",
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
# Apply defaults
|
|
746
|
+
if is_reverse:
|
|
747
|
+
if ko_barrier is None:
|
|
748
|
+
ko_barrier = 0.97 * initial_price
|
|
749
|
+
if ki_barrier is None:
|
|
750
|
+
ki_barrier = 1.25 * initial_price
|
|
751
|
+
if airbag_barrier is None:
|
|
752
|
+
airbag_barrier = 1.40 * initial_price
|
|
753
|
+
else:
|
|
754
|
+
if ko_barrier is None:
|
|
755
|
+
ko_barrier = 1.03 * initial_price
|
|
756
|
+
if ki_barrier is None:
|
|
757
|
+
ki_barrier = 0.75 * initial_price
|
|
758
|
+
if airbag_barrier is None:
|
|
759
|
+
airbag_barrier = 0.60 * initial_price
|
|
760
|
+
|
|
761
|
+
# Validate airbag barrier
|
|
762
|
+
# For standard snowball: airbag_barrier < ki_barrier (both below initial)
|
|
763
|
+
# For reverse snowball: airbag_barrier > ki_barrier (both above initial)
|
|
764
|
+
if is_reverse:
|
|
765
|
+
if airbag_barrier <= ki_barrier:
|
|
766
|
+
raise ValidationError(
|
|
767
|
+
f"create_airbag_snowball: for reverse snowball, airbag_barrier "
|
|
768
|
+
f"({airbag_barrier}) must be greater than ki_barrier ({ki_barrier})"
|
|
769
|
+
)
|
|
770
|
+
else:
|
|
771
|
+
if airbag_barrier >= ki_barrier:
|
|
772
|
+
raise ValidationError(
|
|
773
|
+
f"create_airbag_snowball: airbag_barrier ({airbag_barrier}) "
|
|
774
|
+
f"must be less than ki_barrier ({ki_barrier})"
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
# Generate observation dates
|
|
778
|
+
ko_observation_dates = [
|
|
779
|
+
(i + 1) / num_observations * maturity for i in range(num_observations)
|
|
780
|
+
]
|
|
781
|
+
|
|
782
|
+
# Extract config-specific kwargs
|
|
783
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = (
|
|
784
|
+
_extract_config_kwargs(kwargs)
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
# Use airbag params in config
|
|
788
|
+
airbag_kwargs["airbag_barrier"] = airbag_barrier
|
|
789
|
+
airbag_kwargs["airbag_participation_rate"] = airbag_participation_rate
|
|
790
|
+
airbag_kwargs["airbag_strike"] = airbag_strike
|
|
791
|
+
|
|
792
|
+
# Build configs
|
|
793
|
+
barrier_config = BarrierConfig(
|
|
794
|
+
ko_barrier=ko_barrier,
|
|
795
|
+
ko_rate=ko_rate,
|
|
796
|
+
ko_observation_type=barrier_kwargs.pop(
|
|
797
|
+
"ko_observation_type", ObservationType.DISCRETE
|
|
798
|
+
),
|
|
799
|
+
ko_observation_dates=barrier_kwargs.pop(
|
|
800
|
+
"ko_observation_dates", ko_observation_dates
|
|
801
|
+
),
|
|
802
|
+
ki_barrier=ki_barrier,
|
|
803
|
+
ki_observation_type=barrier_kwargs.pop(
|
|
804
|
+
"ki_observation_type", ObservationType.CONTINUOUS
|
|
805
|
+
),
|
|
806
|
+
ki_continuous=barrier_kwargs.pop("ki_continuous", True),
|
|
807
|
+
**barrier_kwargs,
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
payoff_config = PayoffConfig(
|
|
811
|
+
rebate_rate=payoff_kwargs.pop("rebate_rate", ko_rate),
|
|
812
|
+
include_principal=payoff_kwargs.pop("include_principal", False),
|
|
813
|
+
participation_rate=participation_rate,
|
|
814
|
+
**payoff_kwargs,
|
|
815
|
+
)
|
|
816
|
+
|
|
817
|
+
accrual_config = AccrualConfig(
|
|
818
|
+
coupon_pay_type=accrual_kwargs.pop("coupon_pay_type", CouponPayType.INSTANT),
|
|
819
|
+
is_annualized=accrual_kwargs.pop("is_annualized", True),
|
|
820
|
+
**accrual_kwargs,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
airbag_config = AirbagConfig(**airbag_kwargs)
|
|
824
|
+
|
|
825
|
+
return SnowballOption(
|
|
826
|
+
initial_price=initial_price,
|
|
827
|
+
strike=strike,
|
|
828
|
+
maturity=maturity,
|
|
829
|
+
contract_multiplier=contract_multiplier,
|
|
830
|
+
barrier_config=barrier_config,
|
|
831
|
+
payoff_config=payoff_config,
|
|
832
|
+
accrual_config=accrual_config,
|
|
833
|
+
airbag_config=airbag_config,
|
|
834
|
+
is_reverse=is_reverse,
|
|
835
|
+
)
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
def create_ko_reset_snowball(
|
|
839
|
+
initial_price: float,
|
|
840
|
+
strike: float,
|
|
841
|
+
maturity_pre: float,
|
|
842
|
+
maturity_post: float,
|
|
843
|
+
contract_multiplier: float = 1.0,
|
|
844
|
+
pre_ko_barrier: Optional[float] = None,
|
|
845
|
+
pre_ko_rate: float = 0.15,
|
|
846
|
+
post_ko_barrier: Optional[float] = None,
|
|
847
|
+
post_ko_rate: float = 0.03,
|
|
848
|
+
ki_barrier: Optional[float] = None,
|
|
849
|
+
pre_frequency: str = "monthly",
|
|
850
|
+
post_frequency: str = "monthly",
|
|
851
|
+
ki_frequency: str = "daily",
|
|
852
|
+
pre_lockout: int = 0,
|
|
853
|
+
post_lockout: int = 0,
|
|
854
|
+
ki_continuous: bool = False,
|
|
855
|
+
post_ko_mode: PostKOScheduleMode = PostKOScheduleMode.ABSOLUTE,
|
|
856
|
+
**kwargs,
|
|
857
|
+
) -> KnockOutResetSnowballOption:
|
|
858
|
+
"""
|
|
859
|
+
Create a KO-reset snowball with pre/post KO schedules and KI monitoring.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
initial_price: Reference price for payoff calculations
|
|
863
|
+
strike: Strike price for embedded option
|
|
864
|
+
maturity_pre: Pre-KI schedule horizon in years
|
|
865
|
+
maturity_post: Post-KI schedule horizon in years (absolute or offset)
|
|
866
|
+
contract_multiplier: Underlying units represented by one contract
|
|
867
|
+
pre_ko_barrier: Pre-KI KO barrier (default: 103% of initial_price)
|
|
868
|
+
pre_ko_rate: Pre-KI KO rate (annualized)
|
|
869
|
+
post_ko_barrier: Post-KI KO barrier (default: 95% of initial_price)
|
|
870
|
+
post_ko_rate: Post-KI KO rate (annualized)
|
|
871
|
+
ki_barrier: KI barrier (default: 80% of initial_price)
|
|
872
|
+
pre_frequency: Observation frequency for pre-KI KO schedule
|
|
873
|
+
post_frequency: Observation frequency for post-KI KO schedule
|
|
874
|
+
ki_frequency: Observation frequency for discrete KI monitoring
|
|
875
|
+
pre_lockout: Number of initial pre-KI KO observations to skip
|
|
876
|
+
post_lockout: Number of initial post-KI KO observations to skip
|
|
877
|
+
ki_continuous: If True, use continuous KI monitoring (ABSOLUTE mode only)
|
|
878
|
+
post_ko_mode: ABSOLUTE or REBASED post-KI schedule mode
|
|
879
|
+
**kwargs: Additional parameters passed to config objects or option
|
|
880
|
+
"""
|
|
881
|
+
_validate_core_params(
|
|
882
|
+
initial_price=initial_price,
|
|
883
|
+
strike=strike,
|
|
884
|
+
maturity=maturity_pre,
|
|
885
|
+
contract_multiplier=contract_multiplier,
|
|
886
|
+
func_name="create_ko_reset_snowball",
|
|
887
|
+
)
|
|
888
|
+
if maturity_post <= 0:
|
|
889
|
+
raise ValidationError(
|
|
890
|
+
f"create_ko_reset_snowball: maturity_post must be positive, got {maturity_post}"
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
pre_ko_barrier = (
|
|
894
|
+
pre_ko_barrier if pre_ko_barrier is not None else 1.03 * initial_price
|
|
895
|
+
)
|
|
896
|
+
post_ko_barrier = (
|
|
897
|
+
post_ko_barrier if post_ko_barrier is not None else 0.95 * initial_price
|
|
898
|
+
)
|
|
899
|
+
ki_barrier = ki_barrier if ki_barrier is not None else 0.80 * initial_price
|
|
900
|
+
|
|
901
|
+
if post_ko_mode == PostKOScheduleMode.REBASED and ki_continuous:
|
|
902
|
+
raise ValidationError(
|
|
903
|
+
"Rebased post-KO schedule requires discrete KI monitoring."
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
pre_obs = generate_ko_observation_dates(
|
|
907
|
+
maturity=maturity_pre, frequency=pre_frequency, skip_first=pre_lockout
|
|
908
|
+
)
|
|
909
|
+
post_obs = generate_ko_observation_dates(
|
|
910
|
+
maturity=maturity_post, frequency=post_frequency, skip_first=post_lockout
|
|
911
|
+
)
|
|
912
|
+
|
|
913
|
+
ki_observation_dates = None
|
|
914
|
+
ki_observation_type = (
|
|
915
|
+
ObservationType.CONTINUOUS if ki_continuous else ObservationType.DISCRETE
|
|
916
|
+
)
|
|
917
|
+
if not ki_continuous:
|
|
918
|
+
ki_observation_dates = generate_ko_observation_dates(
|
|
919
|
+
maturity=maturity_pre, frequency=ki_frequency, skip_first=0
|
|
920
|
+
)
|
|
921
|
+
|
|
922
|
+
barrier_kwargs, payoff_kwargs, accrual_kwargs, airbag_kwargs = _extract_config_kwargs(
|
|
923
|
+
kwargs
|
|
924
|
+
)
|
|
925
|
+
|
|
926
|
+
pre_barrier_config = BarrierConfig(
|
|
927
|
+
ko_barrier=pre_ko_barrier,
|
|
928
|
+
ko_rate=pre_ko_rate,
|
|
929
|
+
ko_observation_type=ObservationType.DISCRETE,
|
|
930
|
+
ko_observation_dates=pre_obs,
|
|
931
|
+
ki_barrier=ki_barrier,
|
|
932
|
+
ki_observation_type=ki_observation_type,
|
|
933
|
+
ki_observation_dates=ki_observation_dates,
|
|
934
|
+
ki_continuous=ki_continuous,
|
|
935
|
+
**barrier_kwargs,
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
post_barrier_config = BarrierConfig(
|
|
939
|
+
ko_barrier=post_ko_barrier,
|
|
940
|
+
ko_rate=post_ko_rate,
|
|
941
|
+
ko_observation_type=ObservationType.DISCRETE,
|
|
942
|
+
ko_observation_dates=post_obs,
|
|
943
|
+
)
|
|
944
|
+
|
|
945
|
+
payoff_config = PayoffConfig(**payoff_kwargs) if payoff_kwargs else None
|
|
946
|
+
accrual_config = AccrualConfig(**accrual_kwargs) if accrual_kwargs else None
|
|
947
|
+
airbag_config = AirbagConfig(**airbag_kwargs) if airbag_kwargs else None
|
|
948
|
+
|
|
949
|
+
return KnockOutResetSnowballOption(
|
|
950
|
+
initial_price=initial_price,
|
|
951
|
+
strike=strike,
|
|
952
|
+
barrier_config=pre_barrier_config,
|
|
953
|
+
post_barrier_config=post_barrier_config,
|
|
954
|
+
payoff_config=payoff_config,
|
|
955
|
+
accrual_config=accrual_config,
|
|
956
|
+
airbag_config=airbag_config,
|
|
957
|
+
contract_multiplier=contract_multiplier,
|
|
958
|
+
maturity=max(maturity_pre, maturity_post),
|
|
959
|
+
post_ko_mode=post_ko_mode,
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
# =============================================================================
|
|
964
|
+
# Module Exports
|
|
965
|
+
# =============================================================================
|
|
966
|
+
|
|
967
|
+
__all__ = [
|
|
968
|
+
# Helper functions
|
|
969
|
+
"create_standard_snowball",
|
|
970
|
+
"create_stepdown_snowball",
|
|
971
|
+
"create_european_ki_snowball",
|
|
972
|
+
"create_parachute_snowball",
|
|
973
|
+
"create_airbag_snowball",
|
|
974
|
+
# Utility functions
|
|
975
|
+
"generate_ko_observation_dates",
|
|
976
|
+
"generate_stepdown_barriers",
|
|
977
|
+
]
|