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,614 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Quadrature pricing engine for Phoenix (autocallable) options.
|
|
3
|
+
|
|
4
|
+
Implements a two-state (knocked-in / not-knocked-in) quadrature recursion
|
|
5
|
+
with coupon jumps at observation times.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
from typing import Optional, Sequence
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from quantark.asset.equity.engine.quad.quad_math import QuadratureMath
|
|
16
|
+
from quantark.asset.equity.engine.quad.snowball_quad_engine import SnowballQuadEngine
|
|
17
|
+
from quantark.asset.equity.param import MCParams, QuadParams
|
|
18
|
+
from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
|
|
19
|
+
from quantark.asset.equity.product.option.phoenix_option import PhoenixOption
|
|
20
|
+
from quantark.priceenv import PricingEnvironment
|
|
21
|
+
from quantark.util.enum import CouponPayType, ObservationType
|
|
22
|
+
from quantark.util.exceptions import PricingError, ValidationError
|
|
23
|
+
from quantark.util.numerical import Tolerance, is_close, is_zero, safe_log
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PhoenixQuadEngine(SnowballQuadEngine):
|
|
27
|
+
"""
|
|
28
|
+
Quadrature pricing engine for Phoenix options with coupon jumps.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, params: Optional[QuadParams] = None):
|
|
32
|
+
super().__init__(params=params)
|
|
33
|
+
|
|
34
|
+
def _validate_product(self, product: PhoenixOption) -> None:
|
|
35
|
+
if product.barrier_config.ko_observation_type != ObservationType.DISCRETE:
|
|
36
|
+
raise PricingError("PhoenixQuadEngine requires discrete KO monitoring.")
|
|
37
|
+
|
|
38
|
+
def price(
|
|
39
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
40
|
+
) -> float:
|
|
41
|
+
if not isinstance(product, PhoenixOption):
|
|
42
|
+
raise PricingError(
|
|
43
|
+
f"PhoenixQuadEngine only supports PhoenixOption, got {type(product).__name__}"
|
|
44
|
+
)
|
|
45
|
+
if pricing_env is None:
|
|
46
|
+
raise PricingError("PricingEnvironment is required for PhoenixQuadEngine.")
|
|
47
|
+
|
|
48
|
+
self._validate_product(product)
|
|
49
|
+
|
|
50
|
+
spot = pricing_env.spot
|
|
51
|
+
maturity = product.get_maturity(pricing_env)
|
|
52
|
+
if is_zero(maturity, tol=Tolerance.ZERO):
|
|
53
|
+
return product.get_payoff(spot, pricing_env=pricing_env)
|
|
54
|
+
|
|
55
|
+
rate = pricing_env.get_rate(maturity)
|
|
56
|
+
div = pricing_env.get_div_yield(maturity)
|
|
57
|
+
vol = pricing_env.get_vol(product.strike, maturity)
|
|
58
|
+
if vol <= 0:
|
|
59
|
+
raise ValidationError(f"Volatility must be positive, got {vol}")
|
|
60
|
+
if not np.isfinite(div):
|
|
61
|
+
raise ValidationError(f"Dividend yield must be finite, got {div}")
|
|
62
|
+
if vol > 5.0:
|
|
63
|
+
raise ValidationError(f"Volatility too high for quadrature stability: {vol}")
|
|
64
|
+
|
|
65
|
+
ko_records = product.resolve_ko_observations(pricing_env)
|
|
66
|
+
if not ko_records:
|
|
67
|
+
raise PricingError("KO observation schedule is empty for PhoenixQuadEngine.")
|
|
68
|
+
|
|
69
|
+
ki_continuous = product.has_ki_barrier and (
|
|
70
|
+
product.barrier_config.ki_continuous
|
|
71
|
+
or product.barrier_config.ki_observation_type == ObservationType.CONTINUOUS
|
|
72
|
+
)
|
|
73
|
+
ki_records: Sequence = []
|
|
74
|
+
if product.has_ki_barrier and not ki_continuous:
|
|
75
|
+
ki_records = product.resolve_ki_observations(pricing_env)
|
|
76
|
+
if not ki_records:
|
|
77
|
+
raise PricingError("KI observation schedule is empty for PhoenixQuadEngine.")
|
|
78
|
+
|
|
79
|
+
times = self._merge_times(
|
|
80
|
+
[rec.observation_time for rec in ko_records],
|
|
81
|
+
[rec.observation_time for rec in ki_records],
|
|
82
|
+
maturity,
|
|
83
|
+
)
|
|
84
|
+
if not times:
|
|
85
|
+
raise PricingError("Observation time grid is empty for PhoenixQuadEngine.")
|
|
86
|
+
|
|
87
|
+
coupon_barrier = product.coupon_config.coupon_barrier
|
|
88
|
+
if isinstance(coupon_barrier, list):
|
|
89
|
+
if len(coupon_barrier) != len(ko_records):
|
|
90
|
+
raise ValidationError(
|
|
91
|
+
"Coupon barrier schedule length does not match KO observations."
|
|
92
|
+
)
|
|
93
|
+
coupon_barriers = np.array(coupon_barrier, dtype=float)
|
|
94
|
+
else:
|
|
95
|
+
coupon_barriers = np.full(len(ko_records), float(coupon_barrier))
|
|
96
|
+
|
|
97
|
+
align_log = self._select_alignment_log(
|
|
98
|
+
spot,
|
|
99
|
+
ko_records,
|
|
100
|
+
coupon_barriers=coupon_barriers,
|
|
101
|
+
ki_records=ki_records,
|
|
102
|
+
product=product,
|
|
103
|
+
)
|
|
104
|
+
fft_padding_factor = self._resolve_fft_padding_factor()
|
|
105
|
+
fft_filter_alpha, fft_filter_power = self._resolve_fft_filter()
|
|
106
|
+
math_utils = QuadratureMath(
|
|
107
|
+
grid_x=self.params.grid_points,
|
|
108
|
+
spot=spot,
|
|
109
|
+
maturity=maturity,
|
|
110
|
+
vol_max=vol,
|
|
111
|
+
num_std_devs=self.params.num_std_devs,
|
|
112
|
+
align_log=align_log,
|
|
113
|
+
fft_padding_factor=fft_padding_factor,
|
|
114
|
+
fft_filter_alpha=fft_filter_alpha,
|
|
115
|
+
fft_filter_power=fft_filter_power,
|
|
116
|
+
)
|
|
117
|
+
grid = math_utils.grid
|
|
118
|
+
spot_grid = spot * np.exp(grid)
|
|
119
|
+
dt = self._build_dt(times)
|
|
120
|
+
tau = 0.5 * vol * vol * dt
|
|
121
|
+
if np.any(tau[1:] <= 0.0):
|
|
122
|
+
raise ValidationError("time step too small for quadrature solver.")
|
|
123
|
+
|
|
124
|
+
alpha = (rate - div - 0.5 * vol * vol) / (vol * vol)
|
|
125
|
+
beta = (rate - div - 0.5 * vol * vol) ** 2 / (vol**4) + 2.0 * rate / (
|
|
126
|
+
vol * vol
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
v_in = np.array(
|
|
130
|
+
[
|
|
131
|
+
product.get_maturity_payoff_v1(spot_value, pricing_env=pricing_env)
|
|
132
|
+
for spot_value in spot_grid
|
|
133
|
+
],
|
|
134
|
+
dtype=float,
|
|
135
|
+
)
|
|
136
|
+
v_out = np.array(
|
|
137
|
+
[
|
|
138
|
+
product.get_maturity_payoff_v0(spot_value, pricing_env=pricing_env)
|
|
139
|
+
for spot_value in spot_grid
|
|
140
|
+
],
|
|
141
|
+
dtype=float,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
log_ki_barrier = None
|
|
145
|
+
if product.has_ki_barrier and ki_continuous:
|
|
146
|
+
if product.barrier_config.ki_barrier is None:
|
|
147
|
+
raise PricingError("KI barrier configuration is missing.")
|
|
148
|
+
if isinstance(product.barrier_config.ki_barrier, list):
|
|
149
|
+
raise PricingError("Continuous KI requires scalar ki_barrier.")
|
|
150
|
+
log_ki_barrier = safe_log(product.barrier_config.ki_barrier / spot)
|
|
151
|
+
|
|
152
|
+
ko_times = np.array([rec.observation_time for rec in ko_records], dtype=float)
|
|
153
|
+
period_year_fractions = np.array(
|
|
154
|
+
product.get_coupon_period_year_fractions(ko_times.tolist()),
|
|
155
|
+
dtype=float,
|
|
156
|
+
)
|
|
157
|
+
coupon_amounts = np.array(
|
|
158
|
+
[
|
|
159
|
+
product.get_coupon_payoff(i, year_fraction=period_year_fractions[i])
|
|
160
|
+
for i in range(len(ko_records))
|
|
161
|
+
],
|
|
162
|
+
dtype=float,
|
|
163
|
+
)
|
|
164
|
+
coupon_cumulative = np.concatenate(([0.0], np.cumsum(coupon_amounts)))
|
|
165
|
+
|
|
166
|
+
use_memory = product.has_memory_coupon
|
|
167
|
+
if use_memory and len(ko_records) > 50:
|
|
168
|
+
raise ValidationError(
|
|
169
|
+
f"Too many observations ({len(ko_records)}) for Memory Phoenix Quad engine. "
|
|
170
|
+
"Limit is 50 to prevent performance degradation. Use MC engine instead."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Initialize value function vectors.
|
|
174
|
+
# v_in_list[k] = Value(S, t, memory=k)
|
|
175
|
+
# At maturity, we initialize for the maximum possible accumulated coupons.
|
|
176
|
+
# Max accumulation at maturity is N (if we missed all N-1 previous and miss the last one?
|
|
177
|
+
# Actually, at maturity, we just pay what is due.
|
|
178
|
+
# Let's track surfaces based on "number of missed coupons entering the next period".
|
|
179
|
+
# At T (step N), we construct N+1 surfaces for having missed 0, 1, ..., N coupons.
|
|
180
|
+
num_obs = len(ko_records)
|
|
181
|
+
v_in_list = []
|
|
182
|
+
v_out_list = []
|
|
183
|
+
|
|
184
|
+
def accumulated_before(obs_idx: int, missed: int) -> float:
|
|
185
|
+
if missed <= 0 or obs_idx <= 0:
|
|
186
|
+
return 0.0
|
|
187
|
+
start = max(obs_idx - missed, 0)
|
|
188
|
+
return float(coupon_cumulative[obs_idx] - coupon_cumulative[start])
|
|
189
|
+
|
|
190
|
+
# Terminal condition for each memory state (base payoff; coupons added via jumps)
|
|
191
|
+
for k in range(num_obs + 1):
|
|
192
|
+
v_in_k = np.array(
|
|
193
|
+
[
|
|
194
|
+
product.get_maturity_payoff_v1(spot_value, pricing_env=pricing_env)
|
|
195
|
+
for spot_value in spot_grid
|
|
196
|
+
],
|
|
197
|
+
dtype=float,
|
|
198
|
+
)
|
|
199
|
+
# V1 usually doesn't pay coupons? Checked logic: get_maturity_payoff_v1 doesn't take accumulated.
|
|
200
|
+
# So accumulated is ignored for V1.
|
|
201
|
+
|
|
202
|
+
v_out_k = np.array(
|
|
203
|
+
[
|
|
204
|
+
product.get_maturity_payoff_v0(
|
|
205
|
+
spot_value,
|
|
206
|
+
accumulated_coupons=0.0,
|
|
207
|
+
pricing_env=pricing_env,
|
|
208
|
+
)
|
|
209
|
+
for spot_value in spot_grid
|
|
210
|
+
],
|
|
211
|
+
dtype=float,
|
|
212
|
+
)
|
|
213
|
+
v_in_list.append(v_in_k)
|
|
214
|
+
v_out_list.append(v_out_k)
|
|
215
|
+
|
|
216
|
+
full_p_lr, full_p_ur, full_p0 = 0, len(grid) - 1, (len(grid) - 1) % 2
|
|
217
|
+
omega_grid = math_utils.z_grid
|
|
218
|
+
disable_ko_after_ki = product.barrier_config.disable_ko_after_ki
|
|
219
|
+
smoothing_width = self._resolve_event_smoothing_width(math_utils, product)
|
|
220
|
+
|
|
221
|
+
for step_index in range(len(times), 0, -1):
|
|
222
|
+
obs_time = times[step_index - 1]
|
|
223
|
+
|
|
224
|
+
# Identify if this time step is a KO/Coupon observation
|
|
225
|
+
ko_weight = None
|
|
226
|
+
ko_index = None
|
|
227
|
+
ko_record = None
|
|
228
|
+
for idx, rec in enumerate(ko_records):
|
|
229
|
+
if is_close(obs_time, rec.observation_time, abs_tol=Tolerance.PRECISION):
|
|
230
|
+
ko_index = idx
|
|
231
|
+
ko_record = rec
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
# If this is an observation $i$ (0-based index `ko_index`),
|
|
235
|
+
# we are moving from $t_{i+1}$ (where we have valid surfaces) to $t_i$.
|
|
236
|
+
# The surfaces coming from diffusion are $U[k]$ for $k \in \{0, \dots, i+1\}$ (missed 0 to i+1 coupons).
|
|
237
|
+
# We need to construct surfaces $W[k]$ for $k \in \{0, \dots, i\}$ (missed 0 to i coupons).
|
|
238
|
+
|
|
239
|
+
if ko_record is not None and ko_index is not None:
|
|
240
|
+
barrier_val = coupon_barriers[ko_index]
|
|
241
|
+
coupon_amt = float(coupon_amounts[ko_index])
|
|
242
|
+
|
|
243
|
+
# Determine Pay/Miss weights (smoothed if enabled)
|
|
244
|
+
pay_weight = self._smooth_step_weight(
|
|
245
|
+
grid,
|
|
246
|
+
barrier_val,
|
|
247
|
+
spot,
|
|
248
|
+
smoothing_width,
|
|
249
|
+
trigger_is_down=product.is_reverse,
|
|
250
|
+
)
|
|
251
|
+
if pay_weight is None:
|
|
252
|
+
if product.is_reverse:
|
|
253
|
+
pay_mask = spot_grid <= barrier_val
|
|
254
|
+
else:
|
|
255
|
+
pay_mask = spot_grid >= barrier_val
|
|
256
|
+
pay_weight = pay_mask.astype(float)
|
|
257
|
+
|
|
258
|
+
ko_weight = self._smooth_step_weight(
|
|
259
|
+
grid,
|
|
260
|
+
ko_record.barrier,
|
|
261
|
+
spot,
|
|
262
|
+
smoothing_width,
|
|
263
|
+
trigger_is_down=product.is_reverse,
|
|
264
|
+
)
|
|
265
|
+
if ko_weight is None:
|
|
266
|
+
ko_mask = (
|
|
267
|
+
spot_grid <= ko_record.barrier
|
|
268
|
+
if product.is_reverse
|
|
269
|
+
else spot_grid >= ko_record.barrier
|
|
270
|
+
)
|
|
271
|
+
ko_weight = ko_mask.astype(float)
|
|
272
|
+
|
|
273
|
+
ko_discount = self._ko_discount(
|
|
274
|
+
rate, obs_time, ko_record.settlement_time
|
|
275
|
+
)
|
|
276
|
+
base_ko_payoff = float(ko_record.payoff or 0.0)
|
|
277
|
+
|
|
278
|
+
coupon_discount = 1.0
|
|
279
|
+
if product.coupon_config.coupon_pay_type == CouponPayType.EXPIRY:
|
|
280
|
+
coupon_discount = self._ko_discount(rate, obs_time, maturity)
|
|
281
|
+
|
|
282
|
+
new_v_in_list = []
|
|
283
|
+
new_v_out_list = []
|
|
284
|
+
|
|
285
|
+
# Max possible missed coupons entering this step is `ko_index`.
|
|
286
|
+
# If use_memory is False, we only have state 0.
|
|
287
|
+
max_k = ko_index if use_memory else 0
|
|
288
|
+
|
|
289
|
+
for k in range(max_k + 1):
|
|
290
|
+
# We are in state "missed k coupons" just before observation.
|
|
291
|
+
|
|
292
|
+
# Branch 1: Coupon Condition Met (Pay)
|
|
293
|
+
# Payoff = Current + (k * accumulated)
|
|
294
|
+
# Transition -> State 0 (reset memory)
|
|
295
|
+
accumulated_pay = (
|
|
296
|
+
accumulated_before(ko_index, k) if use_memory else 0.0
|
|
297
|
+
)
|
|
298
|
+
total_pay = (coupon_amt + accumulated_pay) * coupon_discount
|
|
299
|
+
|
|
300
|
+
# Continuation value if paid: comes from state 0 of the next step
|
|
301
|
+
# Wait, diffused surfaces `v_out_list` are indexed by *future* memory state.
|
|
302
|
+
# If we pay now, the future starts with 0 memory.
|
|
303
|
+
# So we use `v_out_list[0]`.
|
|
304
|
+
val_pay_out = v_out_list[0] + total_pay
|
|
305
|
+
val_pay_in = v_in_list[0] + total_pay
|
|
306
|
+
|
|
307
|
+
# Branch 2: Coupon Condition Missed
|
|
308
|
+
# Payoff = 0
|
|
309
|
+
# Transition -> State k+1 (accumulate)
|
|
310
|
+
# Use `v_out_list[k+1]`
|
|
311
|
+
# If memory is off, we stay in state 0 (or technically state 0 again for next step).
|
|
312
|
+
next_k_miss = k + 1 if use_memory else 0
|
|
313
|
+
val_miss_out = v_out_list[next_k_miss]
|
|
314
|
+
val_miss_in = v_in_list[next_k_miss]
|
|
315
|
+
|
|
316
|
+
# Combine
|
|
317
|
+
# W[k] = PayMask * (Payoff + U[0]) + MissMask * U[k+1]
|
|
318
|
+
combined_out = pay_weight * val_pay_out + (1.0 - pay_weight) * val_miss_out
|
|
319
|
+
combined_in = pay_weight * val_pay_in + (1.0 - pay_weight) * val_miss_in
|
|
320
|
+
|
|
321
|
+
# Apply KO logic (KO overrides everything if hit)
|
|
322
|
+
# KO Payoff = BaseKO + (Current + k*Acc)
|
|
323
|
+
# Note: KO usually implies coupon payment if coupon condition met?
|
|
324
|
+
# Product logic `get_ko_payoff` adds `current_coupon` if triggered.
|
|
325
|
+
# It also adds `accumulated_coupons`.
|
|
326
|
+
# So:
|
|
327
|
+
ko_pay_val = (base_ko_payoff + total_pay) * ko_discount
|
|
328
|
+
# If miss coupon barrier but hit KO barrier?
|
|
329
|
+
# Usually KO barrier >= Coupon barrier (for standard).
|
|
330
|
+
# If Spot > KO, then Spot > Coupon. So PayMask is True.
|
|
331
|
+
# If reverse: Spot < KO <= Coupon. So PayMask is True.
|
|
332
|
+
# So if KO hit, we typically pay the coupon too.
|
|
333
|
+
# But we should be careful if KO barrier is tighter than coupon barrier (rare).
|
|
334
|
+
# `get_ko_payoff` handles this check.
|
|
335
|
+
# Here we assume if KO hit, we pay.
|
|
336
|
+
# Ideally we check masks again.
|
|
337
|
+
|
|
338
|
+
# Detailed KO payoff logic:
|
|
339
|
+
# If KO hit:
|
|
340
|
+
# If Coupon hit: Pay Base + Current + Acc.
|
|
341
|
+
# If Coupon miss: Pay Base + Acc? Or Base only?
|
|
342
|
+
# Phoenix `get_ko_payoff` adds `accumulated_coupons` unconditionally?
|
|
343
|
+
# Let's check `get_ko_payoff`.
|
|
344
|
+
# It adds `accumulated_coupons` unconditionally.
|
|
345
|
+
# It adds `current_coupon` IF `is_coupon_triggered`.
|
|
346
|
+
|
|
347
|
+
ko_val_grid = np.full_like(combined_out, base_ko_payoff * ko_discount)
|
|
348
|
+
# Add accumulated unconditionally
|
|
349
|
+
ko_val_grid += (accumulated_pay * ko_discount)
|
|
350
|
+
# Add current coupon proportionally to pay-weight
|
|
351
|
+
ko_val_grid += pay_weight * (coupon_amt * ko_discount)
|
|
352
|
+
|
|
353
|
+
combined_out = ko_weight * ko_val_grid + (1.0 - ko_weight) * combined_out
|
|
354
|
+
if not disable_ko_after_ki:
|
|
355
|
+
combined_in = ko_weight * ko_val_grid + (1.0 - ko_weight) * combined_in
|
|
356
|
+
|
|
357
|
+
new_v_in_list.append(combined_in)
|
|
358
|
+
new_v_out_list.append(combined_out)
|
|
359
|
+
|
|
360
|
+
v_in_list = new_v_in_list
|
|
361
|
+
v_out_list = new_v_out_list
|
|
362
|
+
|
|
363
|
+
# KI Logic (Discrete/Continuous) applied to ALL surfaces
|
|
364
|
+
if ki_continuous and log_ki_barrier is not None:
|
|
365
|
+
ki_mask = (
|
|
366
|
+
spot_grid >= product.barrier_config.ki_barrier
|
|
367
|
+
if product.is_reverse
|
|
368
|
+
else spot_grid <= product.barrier_config.ki_barrier
|
|
369
|
+
)
|
|
370
|
+
for i in range(len(v_out_list)):
|
|
371
|
+
v_out_list[i][ki_mask] = v_in_list[i][ki_mask]
|
|
372
|
+
elif ki_records:
|
|
373
|
+
ki_record = self._match_record(obs_time, ki_records)
|
|
374
|
+
if ki_record is not None:
|
|
375
|
+
ki_weight = self._smooth_step_weight(
|
|
376
|
+
grid,
|
|
377
|
+
ki_record.barrier,
|
|
378
|
+
spot,
|
|
379
|
+
smoothing_width,
|
|
380
|
+
trigger_is_down=not product.is_reverse,
|
|
381
|
+
)
|
|
382
|
+
if ki_weight is None:
|
|
383
|
+
ki_mask = (
|
|
384
|
+
spot_grid >= ki_record.barrier
|
|
385
|
+
if product.is_reverse
|
|
386
|
+
else spot_grid <= ki_record.barrier
|
|
387
|
+
)
|
|
388
|
+
ki_weight = ki_mask.astype(float)
|
|
389
|
+
if ko_weight is not None and not disable_ko_after_ki:
|
|
390
|
+
ki_weight = ki_weight * (1.0 - ko_weight)
|
|
391
|
+
for i in range(len(v_out_list)):
|
|
392
|
+
v_out_list[i] = (1.0 - ki_weight) * v_out_list[i] + ki_weight * v_in_list[i]
|
|
393
|
+
|
|
394
|
+
# Diffusion Step
|
|
395
|
+
tau_step = float(tau[step_index])
|
|
396
|
+
prefactor = math.exp(-beta * tau_step) / math.sqrt(math.pi * tau_step) / 2.0
|
|
397
|
+
omega_array = np.exp(-(omega_grid**2) / (4.0 * tau_step) - alpha * omega_grid)
|
|
398
|
+
|
|
399
|
+
# Diffuse all surfaces
|
|
400
|
+
for i in range(len(v_in_list)):
|
|
401
|
+
v_in_list[i] = self._diffuse_fft(
|
|
402
|
+
v_in_list[i],
|
|
403
|
+
math_utils,
|
|
404
|
+
omega_array,
|
|
405
|
+
prefactor,
|
|
406
|
+
full_p_lr,
|
|
407
|
+
full_p_ur,
|
|
408
|
+
full_p0,
|
|
409
|
+
alpha,
|
|
410
|
+
beta,
|
|
411
|
+
tau_step,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if ki_continuous:
|
|
415
|
+
v_out_list[i] = self._diffuse_with_bridge(
|
|
416
|
+
v_out_list[i],
|
|
417
|
+
v_in_list[i],
|
|
418
|
+
math_utils,
|
|
419
|
+
omega_array,
|
|
420
|
+
prefactor,
|
|
421
|
+
full_p_lr,
|
|
422
|
+
full_p_ur,
|
|
423
|
+
full_p0,
|
|
424
|
+
log_ki_barrier,
|
|
425
|
+
alpha,
|
|
426
|
+
beta,
|
|
427
|
+
vol,
|
|
428
|
+
dt[step_index],
|
|
429
|
+
tau_step,
|
|
430
|
+
product.is_reverse,
|
|
431
|
+
)
|
|
432
|
+
else:
|
|
433
|
+
v_out_list[i] = self._diffuse_fft(
|
|
434
|
+
v_out_list[i],
|
|
435
|
+
math_utils,
|
|
436
|
+
omega_array,
|
|
437
|
+
prefactor,
|
|
438
|
+
full_p_lr,
|
|
439
|
+
full_p_ur,
|
|
440
|
+
full_p0,
|
|
441
|
+
alpha,
|
|
442
|
+
beta,
|
|
443
|
+
tau_step,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Final result is value at t=0 with 0 accumulated coupons.
|
|
447
|
+
value_surface = (
|
|
448
|
+
v_in_list[0]
|
|
449
|
+
if getattr(product, "_otc_lifecycle_knocked_in", False)
|
|
450
|
+
else v_out_list[0]
|
|
451
|
+
)
|
|
452
|
+
return math_utils.interpolate(value_surface, x=0.0)
|
|
453
|
+
|
|
454
|
+
def calculate_event_stats(
|
|
455
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
456
|
+
):
|
|
457
|
+
if not isinstance(product, PhoenixOption):
|
|
458
|
+
return None
|
|
459
|
+
|
|
460
|
+
from quantark.asset.equity.engine.mc.phoenix_mc_engine import PhoenixMCEngine
|
|
461
|
+
|
|
462
|
+
mc_engine = PhoenixMCEngine(params=MCParams())
|
|
463
|
+
return mc_engine.calculate_event_stats(product, pricing_env)
|
|
464
|
+
|
|
465
|
+
def __repr__(self):
|
|
466
|
+
return "PhoenixQuadEngine()"
|
|
467
|
+
|
|
468
|
+
def _select_alignment_log(
|
|
469
|
+
self,
|
|
470
|
+
spot: float,
|
|
471
|
+
ko_records: Sequence,
|
|
472
|
+
coupon_barriers: Optional[np.ndarray],
|
|
473
|
+
ki_records: Sequence,
|
|
474
|
+
product: PhoenixOption,
|
|
475
|
+
) -> Optional[float]:
|
|
476
|
+
ko_candidates: list[float] = []
|
|
477
|
+
for rec in ko_records:
|
|
478
|
+
if rec.barrier is not None and rec.barrier > 0:
|
|
479
|
+
ko_candidates.append(float(rec.barrier))
|
|
480
|
+
|
|
481
|
+
coupon_candidates: list[float] = []
|
|
482
|
+
if coupon_barriers is not None:
|
|
483
|
+
coupon_candidates.extend([float(b) for b in coupon_barriers if b > 0])
|
|
484
|
+
|
|
485
|
+
ki_candidates: list[float] = []
|
|
486
|
+
if product.has_ki_barrier:
|
|
487
|
+
ki_barrier = product.barrier_config.ki_barrier
|
|
488
|
+
if isinstance(ki_barrier, list):
|
|
489
|
+
ki_candidates.extend([float(b) for b in ki_barrier if b > 0])
|
|
490
|
+
elif ki_barrier is not None and ki_barrier > 0:
|
|
491
|
+
ki_candidates.append(float(ki_barrier))
|
|
492
|
+
|
|
493
|
+
def to_logs(candidates: list[float]) -> list[float]:
|
|
494
|
+
logs = []
|
|
495
|
+
for b in candidates:
|
|
496
|
+
try:
|
|
497
|
+
logs.append(safe_log(b / spot))
|
|
498
|
+
except Exception:
|
|
499
|
+
continue
|
|
500
|
+
return logs
|
|
501
|
+
|
|
502
|
+
ko_logs = to_logs(ko_candidates)
|
|
503
|
+
coupon_logs = to_logs(coupon_candidates)
|
|
504
|
+
ki_logs = to_logs(ki_candidates)
|
|
505
|
+
|
|
506
|
+
if not ko_logs and not coupon_logs and not ki_logs:
|
|
507
|
+
return None
|
|
508
|
+
|
|
509
|
+
def closest(logs: list[float]) -> Optional[float]:
|
|
510
|
+
if not logs:
|
|
511
|
+
return None
|
|
512
|
+
idx = int(np.argmin(np.abs(np.asarray(logs))))
|
|
513
|
+
return float(logs[idx])
|
|
514
|
+
|
|
515
|
+
priority = self._resolve_align_priority()
|
|
516
|
+
if priority == "ko":
|
|
517
|
+
return closest(ko_logs) or closest(coupon_logs) or closest(ki_logs)
|
|
518
|
+
if priority == "coupon":
|
|
519
|
+
return closest(coupon_logs) or closest(ko_logs) or closest(ki_logs)
|
|
520
|
+
if priority == "ki":
|
|
521
|
+
return closest(ki_logs) or closest(ko_logs) or closest(coupon_logs)
|
|
522
|
+
|
|
523
|
+
# auto: reverse prefers KO near spot, else coupon, else KI
|
|
524
|
+
if product.is_reverse:
|
|
525
|
+
ko_near = closest(ko_logs)
|
|
526
|
+
if ko_near is not None and abs(ko_near) <= 0.05:
|
|
527
|
+
return ko_near
|
|
528
|
+
return closest(coupon_logs) or closest(ki_logs) or closest(ko_logs)
|
|
529
|
+
|
|
530
|
+
return closest(ko_logs + coupon_logs + ki_logs)
|
|
531
|
+
|
|
532
|
+
def _resolve_align_priority(self) -> str:
|
|
533
|
+
priority = getattr(self.params, "align_priority", None)
|
|
534
|
+
if priority is None:
|
|
535
|
+
return "auto"
|
|
536
|
+
return str(priority).lower()
|
|
537
|
+
|
|
538
|
+
def _resolve_fft_padding_factor(self) -> int:
|
|
539
|
+
factor = getattr(self.params, "fft_padding_factor", None)
|
|
540
|
+
if factor is None or int(factor) <= 0:
|
|
541
|
+
return 2
|
|
542
|
+
return int(factor)
|
|
543
|
+
|
|
544
|
+
def _resolve_fft_filter(self) -> tuple[float, int]:
|
|
545
|
+
alpha = getattr(self.params, "fft_filter_alpha", None)
|
|
546
|
+
power = getattr(self.params, "fft_filter_power", None)
|
|
547
|
+
|
|
548
|
+
if alpha is None:
|
|
549
|
+
alpha = 12.0
|
|
550
|
+
if power is None:
|
|
551
|
+
power = 8
|
|
552
|
+
|
|
553
|
+
return float(alpha), int(power)
|
|
554
|
+
|
|
555
|
+
def _resolve_event_smoothing_width(
|
|
556
|
+
self, math_utils: QuadratureMath, product: PhoenixOption
|
|
557
|
+
) -> float:
|
|
558
|
+
mode = getattr(self.params, "event_smoothing_mode", "fixed")
|
|
559
|
+
cells = getattr(self.params, "event_smoothing_cells", 0)
|
|
560
|
+
kernel_width = getattr(self.params, "event_smoothing_log_width", 0.002)
|
|
561
|
+
|
|
562
|
+
try:
|
|
563
|
+
cells = int(cells)
|
|
564
|
+
except (TypeError, ValueError):
|
|
565
|
+
cells = 0
|
|
566
|
+
|
|
567
|
+
if str(mode).lower() == "reverse_aware" and product.is_reverse:
|
|
568
|
+
cells = 0
|
|
569
|
+
elif str(mode).lower() == "auto":
|
|
570
|
+
h = float(math_utils.h)
|
|
571
|
+
cells = max(1, int(0.5 + float(kernel_width) / h))
|
|
572
|
+
|
|
573
|
+
if cells <= 0:
|
|
574
|
+
return 0.0
|
|
575
|
+
return float(cells) * float(math_utils.h)
|
|
576
|
+
|
|
577
|
+
def _smooth_step_weight(
|
|
578
|
+
self,
|
|
579
|
+
grid: np.ndarray,
|
|
580
|
+
barrier: float,
|
|
581
|
+
spot: float,
|
|
582
|
+
width: float,
|
|
583
|
+
*,
|
|
584
|
+
trigger_is_down: bool,
|
|
585
|
+
) -> Optional[np.ndarray]:
|
|
586
|
+
if width <= 0.0:
|
|
587
|
+
return None
|
|
588
|
+
if barrier is None or barrier <= 0.0 or spot <= 0.0:
|
|
589
|
+
return None
|
|
590
|
+
barrier_log = safe_log(barrier / spot)
|
|
591
|
+
return self._smooth_step_weight_log(
|
|
592
|
+
grid, barrier_log, width, trigger_is_down=trigger_is_down
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def _smooth_step_weight_log(
|
|
596
|
+
self,
|
|
597
|
+
grid: np.ndarray,
|
|
598
|
+
barrier_log: float,
|
|
599
|
+
width: float,
|
|
600
|
+
*,
|
|
601
|
+
trigger_is_down: bool,
|
|
602
|
+
) -> Optional[np.ndarray]:
|
|
603
|
+
if width <= 0.0:
|
|
604
|
+
return None
|
|
605
|
+
x = grid - float(barrier_log)
|
|
606
|
+
kernel = str(getattr(self.params, "event_smoothing_kernel", "cosine")).lower()
|
|
607
|
+
if kernel == "tanh":
|
|
608
|
+
base = 0.5 * (1.0 + np.tanh(x / width))
|
|
609
|
+
else:
|
|
610
|
+
t = np.clip((x + width) / (2.0 * width), 0.0, 1.0)
|
|
611
|
+
base = 0.5 - 0.5 * np.cos(np.pi * t)
|
|
612
|
+
if trigger_is_down:
|
|
613
|
+
return 1.0 - base
|
|
614
|
+
return base
|