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,692 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PDE solver for single barrier options.
|
|
3
|
+
|
|
4
|
+
Implements the finite difference method for knock-in and knock-out
|
|
5
|
+
barrier options with continuous or discrete monitoring.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Optional, Set
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
|
|
13
|
+
from quantark.asset.equity.product.option.barrier_option import BarrierOption
|
|
14
|
+
from quantark.asset.equity.product.option.observation_schedule import ResolvedObservationRecord
|
|
15
|
+
from quantark.asset.equity.param import PDEParams
|
|
16
|
+
from quantark.priceenv import PricingEnvironment
|
|
17
|
+
from quantark.util.enum import ObservationType, ObservationAggregation
|
|
18
|
+
from quantark.util.exceptions import PricingError
|
|
19
|
+
from quantark.util.numerical import is_close, safe_divide
|
|
20
|
+
|
|
21
|
+
from .base_pde_solver import BasePDESolver
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BarrierPDESolver(BasePDESolver):
|
|
25
|
+
"""
|
|
26
|
+
PDE solver for single barrier options.
|
|
27
|
+
|
|
28
|
+
Handles all four barrier types:
|
|
29
|
+
- DOWN_OUT: Knock-out when price goes below barrier
|
|
30
|
+
- DOWN_IN: Knock-in when price goes below barrier
|
|
31
|
+
- UP_OUT: Knock-out when price goes above barrier
|
|
32
|
+
- UP_IN: Knock-in when price goes above barrier
|
|
33
|
+
|
|
34
|
+
For knock-out options, the grid boundary at the barrier is set to
|
|
35
|
+
the rebate value (or 0 if no rebate).
|
|
36
|
+
|
|
37
|
+
For knock-in options, we use the identity:
|
|
38
|
+
Knock-in = Vanilla - Knock-out
|
|
39
|
+
|
|
40
|
+
This solver also handles discrete observation by only checking
|
|
41
|
+
the barrier at specified observation times.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, params: Optional[PDEParams] = None):
|
|
45
|
+
"""
|
|
46
|
+
Initialize barrier option PDE solver.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
params: PDE engine configuration parameters
|
|
50
|
+
"""
|
|
51
|
+
super().__init__(params)
|
|
52
|
+
self._observation_indices: Set[int] = set()
|
|
53
|
+
self._schedule_records: Dict[int, List[ResolvedObservationRecord]] = {}
|
|
54
|
+
self._schedule_aggregation: ObservationAggregation = (
|
|
55
|
+
ObservationAggregation.STOP_FIRST_HIT
|
|
56
|
+
)
|
|
57
|
+
self._total_tau: float = 0.0
|
|
58
|
+
self._terminal_schedule_records: List[ResolvedObservationRecord] = []
|
|
59
|
+
self._has_terminal_observation: bool = False
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def _current_time(total_tau: float, tau_remaining: float) -> float:
|
|
63
|
+
return max(total_tau - tau_remaining, 0.0)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def _df_between_times(
|
|
67
|
+
pricing_env: PricingEnvironment, start_time: float, end_time: float
|
|
68
|
+
) -> float:
|
|
69
|
+
if end_time <= start_time:
|
|
70
|
+
return 1.0
|
|
71
|
+
df_end = pricing_env.get_discount_factor(end_time)
|
|
72
|
+
df_start = pricing_env.get_discount_factor(start_time)
|
|
73
|
+
return float(safe_divide(df_end, df_start, fallback=0.0))
|
|
74
|
+
|
|
75
|
+
def _cashflow_value_at_time(
|
|
76
|
+
self,
|
|
77
|
+
pricing_env: PricingEnvironment,
|
|
78
|
+
cashflow: float,
|
|
79
|
+
current_time: float,
|
|
80
|
+
settlement_time: Optional[float],
|
|
81
|
+
) -> float:
|
|
82
|
+
if settlement_time is None or settlement_time <= current_time:
|
|
83
|
+
return float(cashflow)
|
|
84
|
+
df = self._df_between_times(pricing_env, current_time, settlement_time)
|
|
85
|
+
return float(cashflow) * df
|
|
86
|
+
|
|
87
|
+
def price(
|
|
88
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
89
|
+
) -> float:
|
|
90
|
+
"""
|
|
91
|
+
Price a barrier option using PDE method.
|
|
92
|
+
|
|
93
|
+
For knock-in options, uses: Knock-in = Vanilla - Knock-out
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
product: Barrier option
|
|
97
|
+
pricing_env: Pricing environment
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Option price
|
|
101
|
+
|
|
102
|
+
Raises:
|
|
103
|
+
PricingError: If product is not a barrier option
|
|
104
|
+
"""
|
|
105
|
+
if not isinstance(product, BarrierOption):
|
|
106
|
+
raise PricingError(
|
|
107
|
+
f"BarrierPDESolver only supports BarrierOption, "
|
|
108
|
+
f"got {type(product).__name__}"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
if getattr(product, "observation_type", None) == ObservationType.EXPIRY:
|
|
112
|
+
raise PricingError(
|
|
113
|
+
"BarrierPDESolver does not support EXPIRY observation_type. "
|
|
114
|
+
"Use BarrierAnalyticalEngine for expiry-only monitoring."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Check if barrier is already hit
|
|
118
|
+
spot = pricing_env.spot
|
|
119
|
+
if product.is_barrier_hit(spot):
|
|
120
|
+
if product.is_knock_out:
|
|
121
|
+
maturity = product.get_maturity(pricing_env)
|
|
122
|
+
settlement_time = 0.0 if product.pay_at_hit else maturity
|
|
123
|
+
return self._cashflow_value_at_time(
|
|
124
|
+
pricing_env=pricing_env,
|
|
125
|
+
cashflow=product.rebate,
|
|
126
|
+
current_time=0.0,
|
|
127
|
+
settlement_time=settlement_time,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
# Knocked in, price as vanilla
|
|
131
|
+
return self._price_vanilla(product, pricing_env)
|
|
132
|
+
|
|
133
|
+
if product.is_knock_in:
|
|
134
|
+
# Knock-in = Vanilla - Knock-out (with no rebate)
|
|
135
|
+
vanilla_price = self._price_vanilla(product, pricing_env)
|
|
136
|
+
|
|
137
|
+
# Create a temporary knock-out version with no rebate
|
|
138
|
+
ko_price = self._price_knock_out(product, pricing_env)
|
|
139
|
+
|
|
140
|
+
return vanilla_price - ko_price
|
|
141
|
+
else:
|
|
142
|
+
# Direct knock-out pricing
|
|
143
|
+
return super().price(product, pricing_env)
|
|
144
|
+
|
|
145
|
+
def _price_vanilla(
|
|
146
|
+
self, product: BarrierOption, pricing_env: PricingEnvironment
|
|
147
|
+
) -> float:
|
|
148
|
+
"""
|
|
149
|
+
Price the underlying vanilla option.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
product: Barrier option (used for strike, type, maturity)
|
|
153
|
+
pricing_env: Pricing environment
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Vanilla option price
|
|
157
|
+
"""
|
|
158
|
+
from quantark.asset.equity.product.option import EuropeanVanillaOption
|
|
159
|
+
from .european_pde_solver import EuropeanPDESolver
|
|
160
|
+
|
|
161
|
+
vanilla = EuropeanVanillaOption(
|
|
162
|
+
strike=product.strike,
|
|
163
|
+
option_type=product.option_type,
|
|
164
|
+
maturity=product.maturity,
|
|
165
|
+
exercise_date=product.exercise_date,
|
|
166
|
+
settlement_date=product.settlement_date,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
solver = EuropeanPDESolver(self.params)
|
|
170
|
+
return product.participation_rate * solver.price(vanilla, pricing_env)
|
|
171
|
+
|
|
172
|
+
def _price_knock_out(
|
|
173
|
+
self, product: BarrierOption, pricing_env: PricingEnvironment
|
|
174
|
+
) -> float:
|
|
175
|
+
"""
|
|
176
|
+
Price as a knock-out option (for knock-in decomposition).
|
|
177
|
+
|
|
178
|
+
Creates a temporary knock-out version and prices it.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
product: Original barrier option
|
|
182
|
+
pricing_env: Pricing environment
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Knock-out option price
|
|
186
|
+
"""
|
|
187
|
+
# For knock-in decomposition, we need knock-out with zero rebate
|
|
188
|
+
from quantark.util.enum import BarrierType
|
|
189
|
+
|
|
190
|
+
# Convert knock-in type to knock-out
|
|
191
|
+
if product.barrier_type == BarrierType.UP_IN:
|
|
192
|
+
ko_type = BarrierType.UP_OUT
|
|
193
|
+
elif product.barrier_type == BarrierType.DOWN_IN:
|
|
194
|
+
ko_type = BarrierType.DOWN_OUT
|
|
195
|
+
else:
|
|
196
|
+
ko_type = product.barrier_type
|
|
197
|
+
|
|
198
|
+
ko_product = BarrierOption(
|
|
199
|
+
strike=product.strike,
|
|
200
|
+
option_type=product.option_type,
|
|
201
|
+
barrier=product.barrier,
|
|
202
|
+
barrier_type=ko_type,
|
|
203
|
+
maturity=product.maturity,
|
|
204
|
+
exercise_date=product.exercise_date,
|
|
205
|
+
settlement_date=product.settlement_date,
|
|
206
|
+
rebate=0.0, # Zero rebate for decomposition
|
|
207
|
+
participation_rate=product.participation_rate,
|
|
208
|
+
pay_at_hit=product.pay_at_hit,
|
|
209
|
+
observation_type=product.observation_type,
|
|
210
|
+
observation_dates=product.observation_dates,
|
|
211
|
+
observation_schedule=product.observation_schedule,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return super().price(ko_product, pricing_env)
|
|
215
|
+
|
|
216
|
+
def calculate_greeks(
|
|
217
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
218
|
+
) -> Dict[str, float]:
|
|
219
|
+
"""
|
|
220
|
+
Calculate Greeks for a barrier option using PDE method.
|
|
221
|
+
|
|
222
|
+
For knock-in options, uses: Greeks_KI = Greeks_Vanilla - Greeks_KO
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
product: Barrier option
|
|
226
|
+
pricing_env: Pricing environment
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Dictionary with price, delta, gamma
|
|
230
|
+
|
|
231
|
+
Raises:
|
|
232
|
+
PricingError: If product is not a barrier option
|
|
233
|
+
"""
|
|
234
|
+
if not isinstance(product, BarrierOption):
|
|
235
|
+
raise PricingError(
|
|
236
|
+
f"BarrierPDESolver only supports BarrierOption, "
|
|
237
|
+
f"got {type(product).__name__}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if getattr(product, "observation_type", None) == ObservationType.EXPIRY:
|
|
241
|
+
raise PricingError(
|
|
242
|
+
"BarrierPDESolver does not support EXPIRY observation_type. "
|
|
243
|
+
"Use BarrierAnalyticalEngine for expiry-only monitoring."
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
spot = pricing_env.spot
|
|
247
|
+
tau = product.get_maturity(pricing_env)
|
|
248
|
+
|
|
249
|
+
# Handle expired case
|
|
250
|
+
if tau <= 0:
|
|
251
|
+
return {
|
|
252
|
+
"price": self._calculate_intrinsic(product, spot),
|
|
253
|
+
"delta": self._intrinsic_delta(product, spot),
|
|
254
|
+
"gamma": 0.0,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
# Check if barrier is already hit
|
|
258
|
+
if product.is_barrier_hit(spot):
|
|
259
|
+
if product.is_knock_out:
|
|
260
|
+
# Knocked out: return rebate Greeks (rebate is constant, so delta=gamma=0)
|
|
261
|
+
maturity = product.get_maturity(pricing_env)
|
|
262
|
+
settlement_time = 0.0 if product.pay_at_hit else maturity
|
|
263
|
+
rebate_value = self._cashflow_value_at_time(
|
|
264
|
+
pricing_env=pricing_env,
|
|
265
|
+
cashflow=product.rebate,
|
|
266
|
+
current_time=0.0,
|
|
267
|
+
settlement_time=settlement_time,
|
|
268
|
+
)
|
|
269
|
+
return {"price": rebate_value, "delta": 0.0, "gamma": 0.0}
|
|
270
|
+
else:
|
|
271
|
+
# Knocked in: return vanilla Greeks
|
|
272
|
+
return self._calculate_greeks_vanilla(product, pricing_env)
|
|
273
|
+
|
|
274
|
+
if product.is_knock_in:
|
|
275
|
+
# Knock-in = Vanilla - Knock-out decomposition
|
|
276
|
+
vanilla_greeks = self._calculate_greeks_vanilla(product, pricing_env)
|
|
277
|
+
ko_greeks = self._calculate_greeks_knock_out(product, pricing_env)
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
"price": vanilla_greeks["price"] - ko_greeks["price"],
|
|
281
|
+
"delta": vanilla_greeks["delta"] - ko_greeks["delta"],
|
|
282
|
+
"gamma": vanilla_greeks["gamma"] - ko_greeks["gamma"],
|
|
283
|
+
}
|
|
284
|
+
else:
|
|
285
|
+
# Direct knock-out pricing
|
|
286
|
+
return super().calculate_greeks(product, pricing_env)
|
|
287
|
+
|
|
288
|
+
def _calculate_greeks_vanilla(
|
|
289
|
+
self, product: BarrierOption, pricing_env: PricingEnvironment
|
|
290
|
+
) -> Dict[str, float]:
|
|
291
|
+
"""
|
|
292
|
+
Calculate Greeks for the underlying vanilla option.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
product: Barrier option (used for strike, type, maturity)
|
|
296
|
+
pricing_env: Pricing environment
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Dictionary with price, delta, gamma for vanilla option
|
|
300
|
+
"""
|
|
301
|
+
from quantark.asset.equity.product.option import EuropeanVanillaOption
|
|
302
|
+
from .european_pde_solver import EuropeanPDESolver
|
|
303
|
+
|
|
304
|
+
vanilla = EuropeanVanillaOption(
|
|
305
|
+
strike=product.strike,
|
|
306
|
+
option_type=product.option_type,
|
|
307
|
+
maturity=product.maturity,
|
|
308
|
+
exercise_date=product.exercise_date,
|
|
309
|
+
settlement_date=product.settlement_date,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
solver = EuropeanPDESolver(self.params)
|
|
313
|
+
greeks = solver.calculate_greeks(vanilla, pricing_env)
|
|
314
|
+
|
|
315
|
+
# Apply participation rate
|
|
316
|
+
pr = product.participation_rate
|
|
317
|
+
return {
|
|
318
|
+
"price": pr * greeks["price"],
|
|
319
|
+
"delta": pr * greeks["delta"],
|
|
320
|
+
"gamma": pr * greeks["gamma"],
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
def _calculate_greeks_knock_out(
|
|
324
|
+
self, product: BarrierOption, pricing_env: PricingEnvironment
|
|
325
|
+
) -> Dict[str, float]:
|
|
326
|
+
"""
|
|
327
|
+
Calculate Greeks as a knock-out option (for knock-in decomposition).
|
|
328
|
+
|
|
329
|
+
Creates a temporary knock-out version and calculates Greeks.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
product: Original barrier option
|
|
333
|
+
pricing_env: Pricing environment
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Dictionary with price, delta, gamma for knock-out option
|
|
337
|
+
"""
|
|
338
|
+
from quantark.util.enum import BarrierType
|
|
339
|
+
|
|
340
|
+
# Convert knock-in type to knock-out
|
|
341
|
+
if product.barrier_type == BarrierType.UP_IN:
|
|
342
|
+
ko_type = BarrierType.UP_OUT
|
|
343
|
+
elif product.barrier_type == BarrierType.DOWN_IN:
|
|
344
|
+
ko_type = BarrierType.DOWN_OUT
|
|
345
|
+
else:
|
|
346
|
+
ko_type = product.barrier_type
|
|
347
|
+
|
|
348
|
+
ko_product = BarrierOption(
|
|
349
|
+
strike=product.strike,
|
|
350
|
+
option_type=product.option_type,
|
|
351
|
+
barrier=product.barrier,
|
|
352
|
+
barrier_type=ko_type,
|
|
353
|
+
maturity=product.maturity,
|
|
354
|
+
exercise_date=product.exercise_date,
|
|
355
|
+
settlement_date=product.settlement_date,
|
|
356
|
+
rebate=0.0, # Zero rebate for decomposition
|
|
357
|
+
participation_rate=product.participation_rate,
|
|
358
|
+
pay_at_hit=product.pay_at_hit,
|
|
359
|
+
observation_type=product.observation_type,
|
|
360
|
+
observation_dates=product.observation_dates,
|
|
361
|
+
observation_schedule=product.observation_schedule,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
return super().calculate_greeks(ko_product, pricing_env)
|
|
365
|
+
|
|
366
|
+
def set_terminal_condition(
|
|
367
|
+
self,
|
|
368
|
+
grid: np.ndarray,
|
|
369
|
+
x_vec: np.ndarray,
|
|
370
|
+
s_vec: np.ndarray,
|
|
371
|
+
product: BaseEquityProduct,
|
|
372
|
+
pricing_env: PricingEnvironment,
|
|
373
|
+
) -> None:
|
|
374
|
+
"""
|
|
375
|
+
Set the terminal condition (payoff at maturity).
|
|
376
|
+
|
|
377
|
+
For knock-out options:
|
|
378
|
+
- Zero payoff if barrier already hit
|
|
379
|
+
- Standard payoff otherwise
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
grid: Solution grid [num_x, num_t]
|
|
383
|
+
x_vec: Log-price grid points
|
|
384
|
+
s_vec: Price grid points
|
|
385
|
+
product: Barrier option
|
|
386
|
+
pricing_env: Pricing environment
|
|
387
|
+
"""
|
|
388
|
+
K = product.strike
|
|
389
|
+
barrier = product.barrier
|
|
390
|
+
participation = product.participation_rate
|
|
391
|
+
|
|
392
|
+
# Calculate base payoff
|
|
393
|
+
if product.is_call():
|
|
394
|
+
payoff = np.maximum(s_vec - K, 0.0)
|
|
395
|
+
else:
|
|
396
|
+
payoff = np.maximum(K - s_vec, 0.0)
|
|
397
|
+
payoff = payoff * participation
|
|
398
|
+
|
|
399
|
+
apply_terminal_barrier = product.observation_type != ObservationType.DISCRETE
|
|
400
|
+
apply_terminal_barrier = (
|
|
401
|
+
apply_terminal_barrier or self._has_terminal_observation
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
if apply_terminal_barrier:
|
|
405
|
+
if (
|
|
406
|
+
product.observation_type == ObservationType.DISCRETE
|
|
407
|
+
and self._terminal_schedule_records
|
|
408
|
+
):
|
|
409
|
+
current_time = self._total_tau
|
|
410
|
+
for rec in self._terminal_schedule_records:
|
|
411
|
+
rec_barrier = rec.barrier if rec.barrier is not None else barrier
|
|
412
|
+
cashflow_value = self._cashflow_value_at_time(
|
|
413
|
+
pricing_env=pricing_env,
|
|
414
|
+
cashflow=rec.payoff,
|
|
415
|
+
current_time=current_time,
|
|
416
|
+
settlement_time=rec.settlement_time,
|
|
417
|
+
)
|
|
418
|
+
if self._schedule_aggregation == ObservationAggregation.ACCUMULATE:
|
|
419
|
+
if product.is_up_barrier:
|
|
420
|
+
payoff[s_vec >= rec_barrier] += cashflow_value
|
|
421
|
+
else:
|
|
422
|
+
payoff[s_vec <= rec_barrier] += cashflow_value
|
|
423
|
+
else:
|
|
424
|
+
if product.is_up_barrier:
|
|
425
|
+
payoff[s_vec >= rec_barrier] = cashflow_value
|
|
426
|
+
else:
|
|
427
|
+
payoff[s_vec <= rec_barrier] = cashflow_value
|
|
428
|
+
break
|
|
429
|
+
else:
|
|
430
|
+
if product.is_up_barrier:
|
|
431
|
+
payoff[s_vec >= barrier] = product.rebate
|
|
432
|
+
else:
|
|
433
|
+
payoff[s_vec <= barrier] = product.rebate
|
|
434
|
+
|
|
435
|
+
grid[:, -1] = payoff
|
|
436
|
+
|
|
437
|
+
def set_boundary_conditions(
|
|
438
|
+
self,
|
|
439
|
+
grid: np.ndarray,
|
|
440
|
+
x_vec: np.ndarray,
|
|
441
|
+
s_vec: np.ndarray,
|
|
442
|
+
t_idx: int,
|
|
443
|
+
tau: float,
|
|
444
|
+
product: BaseEquityProduct,
|
|
445
|
+
pricing_env: PricingEnvironment,
|
|
446
|
+
) -> None:
|
|
447
|
+
"""
|
|
448
|
+
Set boundary conditions at spatial edges.
|
|
449
|
+
|
|
450
|
+
For barrier options, the boundary at the barrier level
|
|
451
|
+
is set to the rebate (discounted if paid at expiry).
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
grid: Solution grid [num_x, num_t]
|
|
455
|
+
x_vec: Log-price grid points
|
|
456
|
+
s_vec: Price grid points
|
|
457
|
+
t_idx: Current time index
|
|
458
|
+
tau: Time remaining to maturity
|
|
459
|
+
product: Barrier option
|
|
460
|
+
pricing_env: Pricing environment
|
|
461
|
+
"""
|
|
462
|
+
K = product.strike
|
|
463
|
+
total_tau = (
|
|
464
|
+
self._total_tau
|
|
465
|
+
if self._total_tau > 0
|
|
466
|
+
else product.get_maturity(pricing_env)
|
|
467
|
+
)
|
|
468
|
+
current_time = self._current_time(total_tau, tau)
|
|
469
|
+
df_to_maturity = self._df_between_times(pricing_env, current_time, total_tau)
|
|
470
|
+
|
|
471
|
+
q = pricing_env.get_div_yield(tau) if tau > 0 else 0.0
|
|
472
|
+
df_div = np.exp(-q * tau) if tau > 0 else 1.0
|
|
473
|
+
participation = product.participation_rate
|
|
474
|
+
|
|
475
|
+
if product.observation_type == ObservationType.DISCRETE:
|
|
476
|
+
if product.is_call():
|
|
477
|
+
grid[0, t_idx] = 0.0
|
|
478
|
+
grid[-1, t_idx] = (
|
|
479
|
+
max(s_vec[-1] * df_div - K * df_to_maturity, 0.0) * participation
|
|
480
|
+
)
|
|
481
|
+
else:
|
|
482
|
+
grid[0, t_idx] = K * df_to_maturity * participation
|
|
483
|
+
grid[-1, t_idx] = 0.0
|
|
484
|
+
return
|
|
485
|
+
|
|
486
|
+
settlement_time = current_time if product.pay_at_hit else total_tau
|
|
487
|
+
rebate_value = self._cashflow_value_at_time(
|
|
488
|
+
pricing_env=pricing_env,
|
|
489
|
+
cashflow=product.rebate,
|
|
490
|
+
current_time=current_time,
|
|
491
|
+
settlement_time=settlement_time,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if product.is_up_barrier:
|
|
495
|
+
if product.is_call():
|
|
496
|
+
grid[0, t_idx] = 0.0
|
|
497
|
+
else:
|
|
498
|
+
grid[0, t_idx] = K * df_to_maturity * participation
|
|
499
|
+
grid[-1, t_idx] = rebate_value
|
|
500
|
+
else:
|
|
501
|
+
grid[0, t_idx] = rebate_value
|
|
502
|
+
if product.is_call():
|
|
503
|
+
grid[-1, t_idx] = (
|
|
504
|
+
max(s_vec[-1] * df_div - K * df_to_maturity, 0.0) * participation
|
|
505
|
+
)
|
|
506
|
+
else:
|
|
507
|
+
grid[-1, t_idx] = 0.0
|
|
508
|
+
|
|
509
|
+
def _apply_step_modifications(
|
|
510
|
+
self,
|
|
511
|
+
grid: np.ndarray,
|
|
512
|
+
x_vec: np.ndarray,
|
|
513
|
+
s_vec: np.ndarray,
|
|
514
|
+
t_idx: int,
|
|
515
|
+
tau: float,
|
|
516
|
+
product: BaseEquityProduct,
|
|
517
|
+
pricing_env: PricingEnvironment,
|
|
518
|
+
) -> None:
|
|
519
|
+
"""
|
|
520
|
+
Apply barrier checks at each time step.
|
|
521
|
+
|
|
522
|
+
For discrete monitoring, only check at observation times.
|
|
523
|
+
For continuous monitoring, check at every time step.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
grid: Solution grid
|
|
527
|
+
x_vec: Log-price grid points
|
|
528
|
+
s_vec: Price grid points
|
|
529
|
+
t_idx: Current time index
|
|
530
|
+
tau: Time remaining to maturity
|
|
531
|
+
product: Barrier option
|
|
532
|
+
pricing_env: Pricing environment
|
|
533
|
+
"""
|
|
534
|
+
# For continuous monitoring, always apply barrier
|
|
535
|
+
# For discrete, only at observation times
|
|
536
|
+
if product.observation_type == ObservationType.DISCRETE:
|
|
537
|
+
if t_idx not in self._observation_indices:
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
total_tau = (
|
|
541
|
+
self._total_tau
|
|
542
|
+
if self._total_tau > 0
|
|
543
|
+
else product.get_maturity(pricing_env)
|
|
544
|
+
)
|
|
545
|
+
current_time = self._current_time(total_tau, tau)
|
|
546
|
+
schedule_records = self._schedule_records.get(t_idx)
|
|
547
|
+
|
|
548
|
+
if schedule_records:
|
|
549
|
+
for rec in schedule_records:
|
|
550
|
+
barrier = rec.barrier if rec.barrier is not None else product.barrier
|
|
551
|
+
cashflow_value = self._cashflow_value_at_time(
|
|
552
|
+
pricing_env=pricing_env,
|
|
553
|
+
cashflow=rec.payoff,
|
|
554
|
+
current_time=current_time,
|
|
555
|
+
settlement_time=rec.settlement_time,
|
|
556
|
+
)
|
|
557
|
+
if self._schedule_aggregation == ObservationAggregation.ACCUMULATE:
|
|
558
|
+
if product.is_up_barrier:
|
|
559
|
+
grid[s_vec >= barrier, t_idx] += cashflow_value
|
|
560
|
+
else:
|
|
561
|
+
grid[s_vec <= barrier, t_idx] += cashflow_value
|
|
562
|
+
else:
|
|
563
|
+
if product.is_up_barrier:
|
|
564
|
+
grid[s_vec >= barrier, t_idx] = cashflow_value
|
|
565
|
+
else:
|
|
566
|
+
grid[s_vec <= barrier, t_idx] = cashflow_value
|
|
567
|
+
# Stop-first-hit semantics: once applied, exit early
|
|
568
|
+
return
|
|
569
|
+
return
|
|
570
|
+
|
|
571
|
+
settlement_time = current_time if product.pay_at_hit else total_tau
|
|
572
|
+
rebate_value = self._cashflow_value_at_time(
|
|
573
|
+
pricing_env=pricing_env,
|
|
574
|
+
cashflow=product.rebate,
|
|
575
|
+
current_time=current_time,
|
|
576
|
+
settlement_time=settlement_time,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# Apply barrier knockout
|
|
580
|
+
if product.is_up_barrier:
|
|
581
|
+
# Up barrier: knockout at high prices
|
|
582
|
+
grid[s_vec >= product.barrier, t_idx] = rebate_value
|
|
583
|
+
else:
|
|
584
|
+
# Down barrier: knockout at low prices
|
|
585
|
+
grid[s_vec <= product.barrier, t_idx] = rebate_value
|
|
586
|
+
|
|
587
|
+
def _get_barriers(self, product: BaseEquityProduct) -> List[float]:
|
|
588
|
+
"""Include schedule-specific barriers when building spatial bounds."""
|
|
589
|
+
barriers = super()._get_barriers(product)
|
|
590
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
591
|
+
if schedule is not None:
|
|
592
|
+
for rec in schedule.records:
|
|
593
|
+
if rec.barrier is not None:
|
|
594
|
+
barriers.append(rec.barrier)
|
|
595
|
+
return barriers
|
|
596
|
+
|
|
597
|
+
def _build_grids(
|
|
598
|
+
self,
|
|
599
|
+
product: BaseEquityProduct,
|
|
600
|
+
pricing_env: PricingEnvironment,
|
|
601
|
+
spot: float,
|
|
602
|
+
sigma: float,
|
|
603
|
+
tau: float,
|
|
604
|
+
r: float,
|
|
605
|
+
q: float,
|
|
606
|
+
):
|
|
607
|
+
"""
|
|
608
|
+
Build grids and setup observation indices for discrete monitoring.
|
|
609
|
+
"""
|
|
610
|
+
result = super()._build_grids(product, pricing_env, spot, sigma, tau, r, q)
|
|
611
|
+
x_vec, s_vec, dx_vec, t_vec, dt_vec = result
|
|
612
|
+
|
|
613
|
+
# Setup observation time indices for discrete monitoring
|
|
614
|
+
self._total_tau = tau
|
|
615
|
+
self._observation_indices.clear()
|
|
616
|
+
self._schedule_records.clear()
|
|
617
|
+
self._schedule_aggregation = ObservationAggregation.STOP_FIRST_HIT
|
|
618
|
+
self._terminal_schedule_records = []
|
|
619
|
+
self._has_terminal_observation = False
|
|
620
|
+
|
|
621
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
622
|
+
if schedule is not None:
|
|
623
|
+
resolved_records = schedule.resolve(
|
|
624
|
+
pricing_env=pricing_env,
|
|
625
|
+
default_barrier=product.barrier,
|
|
626
|
+
default_payoff=product.rebate,
|
|
627
|
+
require_single=True,
|
|
628
|
+
)
|
|
629
|
+
self._schedule_aggregation = schedule.aggregation_mode
|
|
630
|
+
if self._schedule_aggregation in (
|
|
631
|
+
ObservationAggregation.BEST,
|
|
632
|
+
ObservationAggregation.WORST,
|
|
633
|
+
):
|
|
634
|
+
raise PricingError(
|
|
635
|
+
f"PDE solver does not support aggregation mode {self._schedule_aggregation.value}"
|
|
636
|
+
)
|
|
637
|
+
for rec in resolved_records:
|
|
638
|
+
if is_close(rec.observation_time, 0.0):
|
|
639
|
+
idx = 0
|
|
640
|
+
self._observation_indices.add(idx)
|
|
641
|
+
self._schedule_records.setdefault(idx, []).append(rec)
|
|
642
|
+
elif is_close(rec.observation_time, tau):
|
|
643
|
+
self._terminal_schedule_records.append(rec)
|
|
644
|
+
self._has_terminal_observation = True
|
|
645
|
+
elif 0.0 < rec.observation_time < tau:
|
|
646
|
+
idx = int(np.argmin(np.abs(t_vec - rec.observation_time)))
|
|
647
|
+
self._observation_indices.add(idx)
|
|
648
|
+
self._schedule_records.setdefault(idx, []).append(rec)
|
|
649
|
+
elif (
|
|
650
|
+
hasattr(product, "observation_type")
|
|
651
|
+
and product.observation_type == ObservationType.DISCRETE
|
|
652
|
+
and hasattr(product, "observation_dates")
|
|
653
|
+
and product.observation_dates is not None
|
|
654
|
+
):
|
|
655
|
+
for obs_time in product.observation_dates:
|
|
656
|
+
if is_close(obs_time, 0.0):
|
|
657
|
+
self._observation_indices.add(0)
|
|
658
|
+
elif is_close(obs_time, tau):
|
|
659
|
+
self._has_terminal_observation = True
|
|
660
|
+
elif 0.0 < obs_time < tau:
|
|
661
|
+
idx = int(np.argmin(np.abs(t_vec - obs_time)))
|
|
662
|
+
self._observation_indices.add(idx)
|
|
663
|
+
|
|
664
|
+
return result
|
|
665
|
+
|
|
666
|
+
def get_critical_points(
|
|
667
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
668
|
+
) -> List[float]:
|
|
669
|
+
"""
|
|
670
|
+
Get critical prices for grid concentration.
|
|
671
|
+
|
|
672
|
+
For barrier options, both strike and barrier are critical.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
product: Barrier option
|
|
676
|
+
pricing_env: Pricing environment
|
|
677
|
+
|
|
678
|
+
Returns:
|
|
679
|
+
List containing strike and barrier
|
|
680
|
+
"""
|
|
681
|
+
points = [product.strike, product.barrier]
|
|
682
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
683
|
+
if schedule is not None:
|
|
684
|
+
for rec in schedule.records:
|
|
685
|
+
if rec.barrier is not None:
|
|
686
|
+
points.append(rec.barrier)
|
|
687
|
+
# sort and make unique before return
|
|
688
|
+
points = sorted(set(points))
|
|
689
|
+
return points
|
|
690
|
+
|
|
691
|
+
def __repr__(self):
|
|
692
|
+
return "BarrierPDESolver()"
|