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,435 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PDE solver for double one-touch (and double no-touch) options.
|
|
3
|
+
|
|
4
|
+
Implements the finite difference method for digital barrier options
|
|
5
|
+
with two barriers (upper and lower).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Dict, Optional, List, Set
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
|
|
12
|
+
from quantark.asset.equity.product.option.double_one_touch_option import DoubleOneTouchOption
|
|
13
|
+
from quantark.asset.equity.param import PDEParams
|
|
14
|
+
from quantark.priceenv import PricingEnvironment
|
|
15
|
+
from quantark.util.enum import ObservationType, ObservationAggregation, TouchType
|
|
16
|
+
from quantark.util.exceptions import PricingError
|
|
17
|
+
|
|
18
|
+
from .base_pde_solver import BasePDESolver
|
|
19
|
+
from .spatial_grid import SpatialGrid
|
|
20
|
+
from .time_grid import TimeGrid
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DoubleOneTouchPDESolver(BasePDESolver):
|
|
24
|
+
"""
|
|
25
|
+
PDE solver for double one-touch and double no-touch options.
|
|
26
|
+
|
|
27
|
+
Double one-touch options pay a fixed rebate if EITHER barrier is touched.
|
|
28
|
+
Double no-touch options pay a fixed rebate if NEITHER barrier is touched.
|
|
29
|
+
|
|
30
|
+
For double one-touch:
|
|
31
|
+
- Boundary conditions: rebate at both barriers
|
|
32
|
+
- Terminal condition: 0 (didn't touch either barrier yet)
|
|
33
|
+
|
|
34
|
+
For double no-touch:
|
|
35
|
+
- Boundary conditions: 0 at both barriers (touched = failed)
|
|
36
|
+
- Terminal condition: rebate inside corridor
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, params: Optional[PDEParams] = None):
|
|
40
|
+
"""
|
|
41
|
+
Initialize double one-touch option PDE solver.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
params: PDE engine configuration parameters
|
|
45
|
+
"""
|
|
46
|
+
super().__init__(params)
|
|
47
|
+
self._observation_indices: Set[int] = set()
|
|
48
|
+
self._schedule_records: Dict[int, List] = {}
|
|
49
|
+
self._schedule_aggregation: ObservationAggregation = (
|
|
50
|
+
ObservationAggregation.STOP_FIRST_HIT
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def price(
|
|
54
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
55
|
+
) -> float:
|
|
56
|
+
"""
|
|
57
|
+
Price a double one-touch or double no-touch option.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
product: Double one-touch option
|
|
61
|
+
pricing_env: Pricing environment
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Option price
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
PricingError: If product is not a double one-touch option
|
|
68
|
+
"""
|
|
69
|
+
if not isinstance(product, DoubleOneTouchOption):
|
|
70
|
+
raise PricingError(
|
|
71
|
+
f"DoubleOneTouchPDESolver only supports DoubleOneTouchOption, "
|
|
72
|
+
f"got {type(product).__name__}"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Check if barrier already hit (outside corridor)
|
|
76
|
+
spot = pricing_env.spot
|
|
77
|
+
if product.is_barrier_hit(spot):
|
|
78
|
+
if product.is_double_one_touch:
|
|
79
|
+
# Already touched, immediate rebate
|
|
80
|
+
return product.rebate
|
|
81
|
+
else:
|
|
82
|
+
# No-touch already failed
|
|
83
|
+
return 0.0
|
|
84
|
+
|
|
85
|
+
return super().price(product, pricing_env)
|
|
86
|
+
|
|
87
|
+
def calculate_greeks(
|
|
88
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
89
|
+
) -> Dict[str, float]:
|
|
90
|
+
"""
|
|
91
|
+
Calculate Greeks for a double one-touch or double no-touch option.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
product: Double one-touch option
|
|
95
|
+
pricing_env: Pricing environment
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Dictionary with price, delta, gamma
|
|
99
|
+
|
|
100
|
+
Raises:
|
|
101
|
+
PricingError: If product is not a double one-touch option
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(product, DoubleOneTouchOption):
|
|
104
|
+
raise PricingError(
|
|
105
|
+
f"DoubleOneTouchPDESolver only supports DoubleOneTouchOption, "
|
|
106
|
+
f"got {type(product).__name__}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
spot = pricing_env.spot
|
|
110
|
+
tau = product.get_maturity(pricing_env)
|
|
111
|
+
|
|
112
|
+
# Handle expired case
|
|
113
|
+
if tau <= 0:
|
|
114
|
+
return {
|
|
115
|
+
"price": self._calculate_intrinsic(product, spot),
|
|
116
|
+
"delta": self._intrinsic_delta(product, spot),
|
|
117
|
+
"gamma": 0.0,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Check if barrier already hit (outside corridor)
|
|
121
|
+
if product.is_barrier_hit(spot):
|
|
122
|
+
if product.is_double_one_touch:
|
|
123
|
+
# Already touched, fixed rebate (delta=gamma=0)
|
|
124
|
+
return {"price": product.rebate, "delta": 0.0, "gamma": 0.0}
|
|
125
|
+
else:
|
|
126
|
+
# No-touch already failed
|
|
127
|
+
return {"price": 0.0, "delta": 0.0, "gamma": 0.0}
|
|
128
|
+
|
|
129
|
+
return super().calculate_greeks(product, pricing_env)
|
|
130
|
+
|
|
131
|
+
def set_terminal_condition(
|
|
132
|
+
self,
|
|
133
|
+
grid: np.ndarray,
|
|
134
|
+
x_vec: np.ndarray,
|
|
135
|
+
s_vec: np.ndarray,
|
|
136
|
+
product: BaseEquityProduct,
|
|
137
|
+
pricing_env: PricingEnvironment,
|
|
138
|
+
) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Set the terminal condition at maturity.
|
|
141
|
+
|
|
142
|
+
For double one-touch:
|
|
143
|
+
- At maturity, if neither barrier touched, value = 0
|
|
144
|
+
- At barriers and beyond, value = rebate
|
|
145
|
+
|
|
146
|
+
For double no-touch:
|
|
147
|
+
- At maturity, if neither barrier touched, value = rebate
|
|
148
|
+
- At barriers and beyond, value = 0
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
grid: Solution grid [num_x, num_t]
|
|
152
|
+
x_vec: Log-price grid points
|
|
153
|
+
s_vec: Price grid points
|
|
154
|
+
product: Double one-touch option
|
|
155
|
+
pricing_env: Pricing environment
|
|
156
|
+
"""
|
|
157
|
+
upper = product.upper_barrier
|
|
158
|
+
lower = product.lower_barrier
|
|
159
|
+
rebate = product.rebate
|
|
160
|
+
|
|
161
|
+
# Use tolerance for floating-point comparisons
|
|
162
|
+
tol = 1e-10
|
|
163
|
+
at_or_above_upper = s_vec >= upper - tol * upper
|
|
164
|
+
at_or_below_lower = s_vec <= lower + tol * lower
|
|
165
|
+
|
|
166
|
+
if product.is_double_one_touch:
|
|
167
|
+
# One-touch: at maturity, inside corridor = 0, at/beyond barriers = rebate
|
|
168
|
+
grid[:, -1] = 0.0
|
|
169
|
+
grid[at_or_above_upper, -1] = rebate
|
|
170
|
+
grid[at_or_below_lower, -1] = rebate
|
|
171
|
+
else:
|
|
172
|
+
# No-touch: at maturity, inside corridor = rebate, at/beyond barriers = 0
|
|
173
|
+
grid[:, -1] = rebate
|
|
174
|
+
grid[at_or_above_upper, -1] = 0.0
|
|
175
|
+
grid[at_or_below_lower, -1] = 0.0
|
|
176
|
+
|
|
177
|
+
def set_boundary_conditions(
|
|
178
|
+
self,
|
|
179
|
+
grid: np.ndarray,
|
|
180
|
+
x_vec: np.ndarray,
|
|
181
|
+
s_vec: np.ndarray,
|
|
182
|
+
t_idx: int,
|
|
183
|
+
tau: float,
|
|
184
|
+
product: BaseEquityProduct,
|
|
185
|
+
pricing_env: PricingEnvironment,
|
|
186
|
+
) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Set boundary conditions at spatial edges.
|
|
189
|
+
|
|
190
|
+
For double one-touch:
|
|
191
|
+
- Both barriers: rebate (possibly discounted)
|
|
192
|
+
|
|
193
|
+
For double no-touch:
|
|
194
|
+
- Both barriers: 0
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
grid: Solution grid [num_x, num_t]
|
|
198
|
+
x_vec: Log-price grid points
|
|
199
|
+
s_vec: Price grid points
|
|
200
|
+
t_idx: Current time index
|
|
201
|
+
tau: Time remaining to maturity
|
|
202
|
+
product: Double one-touch option
|
|
203
|
+
pricing_env: Pricing environment
|
|
204
|
+
"""
|
|
205
|
+
rebate = product.rebate
|
|
206
|
+
r = pricing_env.get_rate(tau) if tau > 0 else 0.0
|
|
207
|
+
df = np.exp(-r * tau) if tau > 0 else 1.0
|
|
208
|
+
|
|
209
|
+
if product.is_double_one_touch:
|
|
210
|
+
if product.payment_at_hit:
|
|
211
|
+
barrier_value = rebate
|
|
212
|
+
else:
|
|
213
|
+
barrier_value = rebate * df
|
|
214
|
+
|
|
215
|
+
# Both boundaries are barriers
|
|
216
|
+
grid[0, t_idx] = barrier_value # Lower barrier
|
|
217
|
+
grid[-1, t_idx] = barrier_value # Upper barrier
|
|
218
|
+
else:
|
|
219
|
+
# No-touch: barriers are absorbing at zero
|
|
220
|
+
grid[0, t_idx] = 0.0
|
|
221
|
+
grid[-1, t_idx] = 0.0
|
|
222
|
+
|
|
223
|
+
def _apply_step_modifications(
|
|
224
|
+
self,
|
|
225
|
+
grid: np.ndarray,
|
|
226
|
+
x_vec: np.ndarray,
|
|
227
|
+
s_vec: np.ndarray,
|
|
228
|
+
t_idx: int,
|
|
229
|
+
tau: float,
|
|
230
|
+
product: BaseEquityProduct,
|
|
231
|
+
pricing_env: PricingEnvironment,
|
|
232
|
+
) -> None:
|
|
233
|
+
"""
|
|
234
|
+
Apply barrier checks at each time step.
|
|
235
|
+
|
|
236
|
+
For discrete monitoring, only check at observation times.
|
|
237
|
+
For continuous monitoring, check at every step.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
grid: Solution grid
|
|
241
|
+
x_vec: Log-price grid points
|
|
242
|
+
s_vec: Price grid points
|
|
243
|
+
t_idx: Current time index
|
|
244
|
+
tau: Time remaining to maturity
|
|
245
|
+
product: Double one-touch option
|
|
246
|
+
pricing_env: Pricing environment
|
|
247
|
+
"""
|
|
248
|
+
# For discrete monitoring, only check at observation times
|
|
249
|
+
if product.observation_type == ObservationType.DISCRETE:
|
|
250
|
+
if t_idx not in self._observation_indices:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
schedule_records = self._schedule_records.get(t_idx)
|
|
254
|
+
upper = product.upper_barrier
|
|
255
|
+
lower = product.lower_barrier
|
|
256
|
+
rebate = product.rebate
|
|
257
|
+
r = pricing_env.get_rate(tau) if tau > 0 else 0.0
|
|
258
|
+
df = np.exp(-r * tau) if tau > 0 else 1.0
|
|
259
|
+
|
|
260
|
+
if schedule_records:
|
|
261
|
+
for rec in schedule_records:
|
|
262
|
+
upper = (
|
|
263
|
+
rec.upper_barrier
|
|
264
|
+
if rec.upper_barrier is not None
|
|
265
|
+
else product.upper_barrier
|
|
266
|
+
)
|
|
267
|
+
lower = (
|
|
268
|
+
rec.lower_barrier
|
|
269
|
+
if rec.lower_barrier is not None
|
|
270
|
+
else product.lower_barrier
|
|
271
|
+
)
|
|
272
|
+
payoff = rec.payoff
|
|
273
|
+
if product.is_double_one_touch:
|
|
274
|
+
barrier_value = payoff if product.payment_at_hit else payoff * df
|
|
275
|
+
else:
|
|
276
|
+
barrier_value = 0.0
|
|
277
|
+
at_or_above_upper = s_vec >= upper
|
|
278
|
+
at_or_below_lower = s_vec <= lower
|
|
279
|
+
outside_corridor = at_or_above_upper | at_or_below_lower
|
|
280
|
+
if self._schedule_aggregation == ObservationAggregation.ACCUMULATE:
|
|
281
|
+
grid[outside_corridor, t_idx] += barrier_value
|
|
282
|
+
else:
|
|
283
|
+
grid[outside_corridor, t_idx] = barrier_value
|
|
284
|
+
return
|
|
285
|
+
return
|
|
286
|
+
|
|
287
|
+
if product.is_double_one_touch:
|
|
288
|
+
if product.payment_at_hit:
|
|
289
|
+
barrier_value = rebate
|
|
290
|
+
else:
|
|
291
|
+
barrier_value = rebate * df
|
|
292
|
+
else:
|
|
293
|
+
barrier_value = 0.0
|
|
294
|
+
|
|
295
|
+
# Apply barrier values at/beyond barriers
|
|
296
|
+
at_or_above_upper = s_vec >= upper
|
|
297
|
+
at_or_below_lower = s_vec <= lower
|
|
298
|
+
outside_corridor = at_or_above_upper | at_or_below_lower
|
|
299
|
+
grid[outside_corridor, t_idx] = barrier_value
|
|
300
|
+
|
|
301
|
+
def _get_barriers(self, product: BaseEquityProduct) -> List[float]:
|
|
302
|
+
"""Include schedule-specific barriers when building spatial bounds."""
|
|
303
|
+
barriers = super()._get_barriers(product)
|
|
304
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
305
|
+
if schedule is not None:
|
|
306
|
+
for rec in schedule.records:
|
|
307
|
+
if rec.lower_barrier is not None:
|
|
308
|
+
barriers.append(rec.lower_barrier)
|
|
309
|
+
if rec.upper_barrier is not None:
|
|
310
|
+
barriers.append(rec.upper_barrier)
|
|
311
|
+
return barriers
|
|
312
|
+
|
|
313
|
+
def _build_grids(
|
|
314
|
+
self,
|
|
315
|
+
product: BaseEquityProduct,
|
|
316
|
+
pricing_env: PricingEnvironment,
|
|
317
|
+
spot: float,
|
|
318
|
+
sigma: float,
|
|
319
|
+
tau: float,
|
|
320
|
+
r: float,
|
|
321
|
+
q: float,
|
|
322
|
+
):
|
|
323
|
+
"""Build grids for double barrier options.
|
|
324
|
+
|
|
325
|
+
Uses a small buffer beyond the barriers to ensure proper PDE
|
|
326
|
+
diffusion from barrier values into the interior. The barriers
|
|
327
|
+
are included as interior points so their values propagate.
|
|
328
|
+
"""
|
|
329
|
+
params: PDEParams = self.params
|
|
330
|
+
|
|
331
|
+
lower = product.lower_barrier
|
|
332
|
+
upper = product.upper_barrier
|
|
333
|
+
|
|
334
|
+
s_min, s_max = SpatialGrid.calculate_auto_bounds(
|
|
335
|
+
spot,
|
|
336
|
+
sigma,
|
|
337
|
+
tau,
|
|
338
|
+
r,
|
|
339
|
+
q,
|
|
340
|
+
barriers=[lower, upper],
|
|
341
|
+
num_std=5.0, # Wider range for barrier options
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
# Critical points: both barriers and spot for grid concentration
|
|
345
|
+
critical_points = [lower, upper]
|
|
346
|
+
if s_min < spot < s_max:
|
|
347
|
+
critical_points.append(spot)
|
|
348
|
+
|
|
349
|
+
# Build spatial grid with concentration at barriers
|
|
350
|
+
x_vec, s_vec, dx_vec = SpatialGrid.build(
|
|
351
|
+
s_min,
|
|
352
|
+
s_max,
|
|
353
|
+
params.grid_size,
|
|
354
|
+
critical_points=critical_points,
|
|
355
|
+
use_adaptive=params.adaptive_grid,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
# Get event times
|
|
359
|
+
event_times = self._get_event_times(product, tau)
|
|
360
|
+
|
|
361
|
+
# Build time grid
|
|
362
|
+
t_vec, dt_vec = TimeGrid.build(
|
|
363
|
+
tau,
|
|
364
|
+
params.time_steps,
|
|
365
|
+
method=params.time_grid_type,
|
|
366
|
+
event_times=event_times,
|
|
367
|
+
grade_exponent=params.grade_exponent,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Setup observation indices for discrete monitoring
|
|
371
|
+
self._observation_indices.clear()
|
|
372
|
+
self._schedule_records.clear()
|
|
373
|
+
self._schedule_aggregation = ObservationAggregation.STOP_FIRST_HIT
|
|
374
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
375
|
+
if schedule is not None:
|
|
376
|
+
resolved_records = schedule.resolve(
|
|
377
|
+
pricing_env=pricing_env,
|
|
378
|
+
default_upper=product.upper_barrier,
|
|
379
|
+
default_lower=product.lower_barrier,
|
|
380
|
+
default_payoff=product.rebate,
|
|
381
|
+
require_double=True,
|
|
382
|
+
)
|
|
383
|
+
self._schedule_aggregation = schedule.aggregation_mode
|
|
384
|
+
if self._schedule_aggregation in (
|
|
385
|
+
ObservationAggregation.BEST,
|
|
386
|
+
ObservationAggregation.WORST,
|
|
387
|
+
):
|
|
388
|
+
raise PricingError(
|
|
389
|
+
f"PDE solver does not support aggregation mode {self._schedule_aggregation.value}"
|
|
390
|
+
)
|
|
391
|
+
for rec in resolved_records:
|
|
392
|
+
if 0 < rec.observation_time < tau:
|
|
393
|
+
idx = np.argmin(np.abs(t_vec - rec.observation_time))
|
|
394
|
+
self._observation_indices.add(idx)
|
|
395
|
+
self._schedule_records.setdefault(idx, []).append(rec)
|
|
396
|
+
elif (
|
|
397
|
+
product.observation_type == ObservationType.DISCRETE
|
|
398
|
+
and product.observation_dates is not None
|
|
399
|
+
):
|
|
400
|
+
for obs_time in product.observation_dates:
|
|
401
|
+
if 0 < obs_time < tau:
|
|
402
|
+
idx = np.argmin(np.abs(t_vec - obs_time))
|
|
403
|
+
self._observation_indices.add(idx)
|
|
404
|
+
|
|
405
|
+
return x_vec, s_vec, dx_vec, t_vec, dt_vec
|
|
406
|
+
|
|
407
|
+
def get_critical_points(
|
|
408
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
409
|
+
) -> List[float]:
|
|
410
|
+
"""
|
|
411
|
+
Get critical prices for grid concentration.
|
|
412
|
+
|
|
413
|
+
For double one-touch, both barriers are critical.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
product: Double one-touch option
|
|
417
|
+
pricing_env: Pricing environment
|
|
418
|
+
|
|
419
|
+
Returns:
|
|
420
|
+
List containing both barriers
|
|
421
|
+
"""
|
|
422
|
+
points = [product.lower_barrier, product.upper_barrier]
|
|
423
|
+
schedule = getattr(product, "observation_schedule", None)
|
|
424
|
+
if schedule is not None:
|
|
425
|
+
for rec in schedule.records:
|
|
426
|
+
if rec.lower_barrier is not None:
|
|
427
|
+
points.append(rec.lower_barrier)
|
|
428
|
+
if rec.upper_barrier is not None:
|
|
429
|
+
points.append(rec.upper_barrier)
|
|
430
|
+
# sort and make unique before return
|
|
431
|
+
points = sorted(set(points))
|
|
432
|
+
return points
|
|
433
|
+
|
|
434
|
+
def __repr__(self):
|
|
435
|
+
return "DoubleOneTouchPDESolver()"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PDE solver for European vanilla options.
|
|
3
|
+
|
|
4
|
+
Implements the simplest case of PDE pricing: European calls and puts
|
|
5
|
+
with standard Black-Scholes boundary conditions.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, List
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
|
|
12
|
+
from quantark.asset.equity.product.option import EuropeanVanillaOption
|
|
13
|
+
from quantark.asset.equity.param import PDEParams
|
|
14
|
+
from quantark.priceenv import PricingEnvironment
|
|
15
|
+
from quantark.util.exceptions import PricingError
|
|
16
|
+
|
|
17
|
+
from .base_pde_solver import BasePDESolver
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EuropeanPDESolver(BasePDESolver):
|
|
21
|
+
"""
|
|
22
|
+
PDE solver for European vanilla options.
|
|
23
|
+
|
|
24
|
+
Uses finite difference method to solve the Black-Scholes PDE
|
|
25
|
+
for European call and put options.
|
|
26
|
+
|
|
27
|
+
Terminal condition:
|
|
28
|
+
Call: max(S - K, 0)
|
|
29
|
+
Put: max(K - S, 0)
|
|
30
|
+
|
|
31
|
+
Boundary conditions:
|
|
32
|
+
Call: V(0) = 0, V(Smax) ≈ Smax - K*exp(-r*tau)
|
|
33
|
+
Put: V(0) ≈ K*exp(-r*tau), V(Smax) = 0
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, params: Optional[PDEParams] = None):
|
|
37
|
+
"""
|
|
38
|
+
Initialize European option PDE solver.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
params: PDE engine configuration parameters
|
|
42
|
+
"""
|
|
43
|
+
super().__init__(params)
|
|
44
|
+
|
|
45
|
+
def price(
|
|
46
|
+
self,
|
|
47
|
+
product: BaseEquityProduct,
|
|
48
|
+
pricing_env: PricingEnvironment
|
|
49
|
+
) -> float:
|
|
50
|
+
"""
|
|
51
|
+
Price a European vanilla option using PDE method.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
product: European vanilla option
|
|
55
|
+
pricing_env: Pricing environment
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Option price
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
PricingError: If product is not a European vanilla option
|
|
62
|
+
"""
|
|
63
|
+
if not isinstance(product, EuropeanVanillaOption):
|
|
64
|
+
raise PricingError(
|
|
65
|
+
f"EuropeanPDESolver only supports EuropeanVanillaOption, "
|
|
66
|
+
f"got {type(product).__name__}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return super().price(product, pricing_env)
|
|
70
|
+
|
|
71
|
+
def set_terminal_condition(
|
|
72
|
+
self,
|
|
73
|
+
grid: np.ndarray,
|
|
74
|
+
x_vec: np.ndarray,
|
|
75
|
+
s_vec: np.ndarray,
|
|
76
|
+
product: BaseEquityProduct,
|
|
77
|
+
pricing_env: PricingEnvironment
|
|
78
|
+
) -> None:
|
|
79
|
+
"""
|
|
80
|
+
Set the terminal condition (payoff at maturity).
|
|
81
|
+
|
|
82
|
+
For European options:
|
|
83
|
+
Call: max(S - K, 0)
|
|
84
|
+
Put: max(K - S, 0)
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
grid: Solution grid [num_x, num_t]
|
|
88
|
+
x_vec: Log-price grid points
|
|
89
|
+
s_vec: Price grid points
|
|
90
|
+
product: European vanilla option
|
|
91
|
+
pricing_env: Pricing environment
|
|
92
|
+
"""
|
|
93
|
+
K = product.strike
|
|
94
|
+
|
|
95
|
+
if product.is_call():
|
|
96
|
+
grid[:, -1] = np.maximum(s_vec - K, 0.0)
|
|
97
|
+
else: # put
|
|
98
|
+
grid[:, -1] = np.maximum(K - s_vec, 0.0)
|
|
99
|
+
|
|
100
|
+
def set_boundary_conditions(
|
|
101
|
+
self,
|
|
102
|
+
grid: np.ndarray,
|
|
103
|
+
x_vec: np.ndarray,
|
|
104
|
+
s_vec: np.ndarray,
|
|
105
|
+
t_idx: int,
|
|
106
|
+
tau: float,
|
|
107
|
+
product: BaseEquityProduct,
|
|
108
|
+
pricing_env: PricingEnvironment
|
|
109
|
+
) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Set boundary conditions at spatial edges.
|
|
112
|
+
|
|
113
|
+
For European options:
|
|
114
|
+
Lower boundary (S → 0):
|
|
115
|
+
Call: V → 0
|
|
116
|
+
Put: V → K * exp(-r*tau)
|
|
117
|
+
|
|
118
|
+
Upper boundary (S → ∞):
|
|
119
|
+
Call: V → S - K * exp(-r*tau)
|
|
120
|
+
Put: V → 0
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
grid: Solution grid [num_x, num_t]
|
|
124
|
+
x_vec: Log-price grid points
|
|
125
|
+
s_vec: Price grid points
|
|
126
|
+
t_idx: Current time index
|
|
127
|
+
tau: Time remaining to maturity
|
|
128
|
+
product: European vanilla option
|
|
129
|
+
pricing_env: Pricing environment
|
|
130
|
+
"""
|
|
131
|
+
K = product.strike
|
|
132
|
+
r = pricing_env.get_rate(tau) if tau > 0 else 0.0
|
|
133
|
+
q = pricing_env.get_div_yield(tau) if tau > 0 else 0.0
|
|
134
|
+
|
|
135
|
+
df = np.exp(-r * tau) if tau > 0 else 1.0
|
|
136
|
+
df_div = np.exp(-q * tau) if tau > 0 else 1.0
|
|
137
|
+
|
|
138
|
+
if product.is_call():
|
|
139
|
+
# Lower boundary: call worth 0 when S = 0
|
|
140
|
+
grid[0, t_idx] = 0.0
|
|
141
|
+
# Upper boundary: call worth approximately S*exp(-q*tau) - K*exp(-r*tau)
|
|
142
|
+
grid[-1, t_idx] = max(s_vec[-1] * df_div - K * df, 0.0)
|
|
143
|
+
else: # put
|
|
144
|
+
# Lower boundary: put worth K*exp(-r*tau) when S = 0
|
|
145
|
+
grid[0, t_idx] = K * df
|
|
146
|
+
# Upper boundary: put worth 0 when S is very large
|
|
147
|
+
grid[-1, t_idx] = 0.0
|
|
148
|
+
|
|
149
|
+
def get_critical_points(
|
|
150
|
+
self,
|
|
151
|
+
product: BaseEquityProduct,
|
|
152
|
+
pricing_env: PricingEnvironment
|
|
153
|
+
) -> List[float]:
|
|
154
|
+
"""
|
|
155
|
+
Get critical prices for grid concentration.
|
|
156
|
+
|
|
157
|
+
For European options, the strike is the critical point.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
product: European vanilla option
|
|
161
|
+
pricing_env: Pricing environment
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
List containing the strike price
|
|
165
|
+
"""
|
|
166
|
+
return [product.strike]
|
|
167
|
+
|
|
168
|
+
def __repr__(self):
|
|
169
|
+
return "EuropeanPDESolver()"
|
|
170
|
+
|