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,682 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analytical pricing engine for American vanilla options.
|
|
3
|
+
|
|
4
|
+
This module implements three approximation methods:
|
|
5
|
+
- BS93: Bjerksund-Stensland (1993) single-barrier approximation
|
|
6
|
+
- BS02: Bjerksund-Stensland (2002) two-barrier approximation
|
|
7
|
+
- BAW: Barone-Adesi & Whaley (1987) quadratic approximation
|
|
8
|
+
|
|
9
|
+
References:
|
|
10
|
+
[1] Bjerksund, P., and Stensland, G., 1993. Closed-form approximation of American options.
|
|
11
|
+
[2] Bjerksund, P., and Stensland, G., 2002. Closed-form approximation of American options.
|
|
12
|
+
Scandinavian Journal of Management, 18(4), 487-507.
|
|
13
|
+
[3] Barone-Adesi, G., and Whaley, R. E., 1987. Efficient analytic approximation of American
|
|
14
|
+
option values. Journal of Finance, 42(2), 301-320.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from typing import Optional, Union
|
|
19
|
+
|
|
20
|
+
from scipy.stats import norm, multivariate_normal
|
|
21
|
+
from quantark.asset.equity.engine.base_engine import BaseEngine
|
|
22
|
+
from quantark.asset.equity.product.option import AmericanOption
|
|
23
|
+
from quantark.asset.equity.product.base_equity_product import BaseEquityProduct
|
|
24
|
+
from quantark.asset.equity.param import EngineParams
|
|
25
|
+
from quantark.priceenv import PricingEnvironment
|
|
26
|
+
from quantark.util.exceptions import ValidationError, NumericalError, PricingError
|
|
27
|
+
from quantark.util.enum.engine_enums import AmericanAnalyticalMethod, EngineType
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AmericanOptionAnalyticalEngine(BaseEngine):
|
|
31
|
+
"""
|
|
32
|
+
Analytical pricing engine for American vanilla options.
|
|
33
|
+
|
|
34
|
+
Supports three approximation methods:
|
|
35
|
+
- BS93 (default): Fast, single-barrier approximation
|
|
36
|
+
- BS02: More accurate two-barrier approximation
|
|
37
|
+
- BAW: Quadratic approximation with iterative critical price search
|
|
38
|
+
|
|
39
|
+
For American puts, BS93/BS02 use put-call transformation while BAW uses direct put pricing.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
engine_type = EngineType.ANALYTICAL
|
|
43
|
+
|
|
44
|
+
DEFAULT_METHOD = AmericanAnalyticalMethod.BS93
|
|
45
|
+
|
|
46
|
+
MIN_VOL = 0.001
|
|
47
|
+
MAX_VOL = 5.0
|
|
48
|
+
MIN_MATURITY = 1e-6
|
|
49
|
+
MAX_MATURITY = 30.0
|
|
50
|
+
|
|
51
|
+
def __init__(self, params: Optional[EngineParams] = None, method: Union[str, AmericanAnalyticalMethod, tuple] = None):
|
|
52
|
+
"""
|
|
53
|
+
Initialize American option analytical engine.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
params: Engine configuration parameters
|
|
57
|
+
method: Pricing method, can be:
|
|
58
|
+
- AmericanAnalyticalMethod enum (e.g., AmericanAnalyticalMethod.BS93)
|
|
59
|
+
- String "BS93"/"BS02"/"BAW" (backward compatibility)
|
|
60
|
+
- Tuple from EngineType.ANALYTICAL(AmericanAnalyticalMethod.BS93)
|
|
61
|
+
- None (defaults to BS93)
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ValidationError: If invalid method is specified
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(params)
|
|
67
|
+
|
|
68
|
+
if method is None:
|
|
69
|
+
self.method = self.DEFAULT_METHOD
|
|
70
|
+
elif isinstance(method, tuple):
|
|
71
|
+
engine_type, analytical_method = method
|
|
72
|
+
if engine_type != EngineType.ANALYTICAL:
|
|
73
|
+
raise ValidationError(
|
|
74
|
+
f"Expected EngineType.ANALYTICAL, got {engine_type}"
|
|
75
|
+
)
|
|
76
|
+
if not isinstance(analytical_method, AmericanAnalyticalMethod):
|
|
77
|
+
raise ValidationError(
|
|
78
|
+
f"Expected AmericanAnalyticalMethod, got {type(analytical_method).__name__}"
|
|
79
|
+
)
|
|
80
|
+
self.method = analytical_method
|
|
81
|
+
elif isinstance(method, AmericanAnalyticalMethod):
|
|
82
|
+
self.method = method
|
|
83
|
+
elif isinstance(method, str):
|
|
84
|
+
try:
|
|
85
|
+
self.method = AmericanAnalyticalMethod[method.upper()]
|
|
86
|
+
except KeyError:
|
|
87
|
+
valid_methods = ', '.join([m.name for m in AmericanAnalyticalMethod])
|
|
88
|
+
raise ValidationError(
|
|
89
|
+
f"Invalid method '{method}'. "
|
|
90
|
+
f"Valid methods are: {valid_methods}"
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
raise ValidationError(
|
|
94
|
+
f"Method must be AmericanAnalyticalMethod enum, string, or EngineType tuple, got {type(method).__name__}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def price(
|
|
98
|
+
self, product: BaseEquityProduct, pricing_env: PricingEnvironment
|
|
99
|
+
) -> float:
|
|
100
|
+
"""
|
|
101
|
+
Price an American vanilla option using the selected approximation method.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
product: American vanilla option
|
|
105
|
+
pricing_env: Pricing environment with market data
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
Option price
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
PricingError: If product is not an American option
|
|
112
|
+
ValidationError: If input parameters are invalid
|
|
113
|
+
NumericalError: If numerical computation fails
|
|
114
|
+
"""
|
|
115
|
+
if not isinstance(product, AmericanOption):
|
|
116
|
+
raise PricingError(
|
|
117
|
+
f"AmericanOptionAnalyticalEngine only supports AmericanOption, "
|
|
118
|
+
f"got {type(product).__name__}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
S = pricing_env.spot
|
|
122
|
+
K = product.strike
|
|
123
|
+
T = product.get_maturity(pricing_env)
|
|
124
|
+
r = pricing_env.get_rate(T)
|
|
125
|
+
q = pricing_env.get_div_yield(T)
|
|
126
|
+
sigma = pricing_env.get_vol(K, T)
|
|
127
|
+
multiplier = product.contract_multiplier
|
|
128
|
+
|
|
129
|
+
self._validate_inputs(S, K, T, r, q, sigma)
|
|
130
|
+
|
|
131
|
+
if T < self.MIN_MATURITY:
|
|
132
|
+
return product.get_payoff(S)
|
|
133
|
+
|
|
134
|
+
sigma = np.clip(sigma, self.MIN_VOL, self.MAX_VOL)
|
|
135
|
+
T = np.clip(T, self.MIN_MATURITY, self.MAX_MATURITY)
|
|
136
|
+
|
|
137
|
+
b = r - q
|
|
138
|
+
|
|
139
|
+
is_call = product.is_call()
|
|
140
|
+
|
|
141
|
+
if is_call and b >= r and r >= 0:
|
|
142
|
+
return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
|
|
143
|
+
|
|
144
|
+
if is_call and b >= r and r < 0 and q <= r:
|
|
145
|
+
return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
if self.method == AmericanAnalyticalMethod.BS93:
|
|
149
|
+
price = self._price_bs93(S, K, T, r, b, sigma, is_call)
|
|
150
|
+
elif self.method == AmericanAnalyticalMethod.BS02:
|
|
151
|
+
price = self._price_bs02(S, K, T, r, b, sigma, is_call)
|
|
152
|
+
else:
|
|
153
|
+
price = self._price_baw(S, K, T, r, b, sigma, is_call)
|
|
154
|
+
|
|
155
|
+
if np.isnan(price) or np.isinf(price):
|
|
156
|
+
raise NumericalError("NaN or Inf result detected")
|
|
157
|
+
|
|
158
|
+
price *= multiplier
|
|
159
|
+
intrinsic = product.intrinsic_value(S)
|
|
160
|
+
if price < intrinsic - 1e-6:
|
|
161
|
+
raise NumericalError(
|
|
162
|
+
f"Price ({price:.6f}) below intrinsic value ({intrinsic:.6f})"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return max(price, intrinsic)
|
|
166
|
+
|
|
167
|
+
except Exception as e:
|
|
168
|
+
import warnings
|
|
169
|
+
|
|
170
|
+
warnings.warn(
|
|
171
|
+
f"American option pricing failed ({e}), using European fallback"
|
|
172
|
+
)
|
|
173
|
+
if is_call:
|
|
174
|
+
return self._european_call_bsm(S, K, T, r, b, sigma) * multiplier
|
|
175
|
+
else:
|
|
176
|
+
return self._european_put_bsm(S, K, T, r, b, sigma) * multiplier
|
|
177
|
+
|
|
178
|
+
def _validate_inputs(
|
|
179
|
+
self, S: float, K: float, T: float, r: float, q: float, sigma: float
|
|
180
|
+
):
|
|
181
|
+
"""Validate input parameters."""
|
|
182
|
+
if S <= 0:
|
|
183
|
+
raise ValidationError(f"Spot price must be positive, got {S}")
|
|
184
|
+
if K <= 0:
|
|
185
|
+
raise ValidationError(f"Strike price must be positive, got {K}")
|
|
186
|
+
if T < 0:
|
|
187
|
+
raise ValidationError(f"Time to maturity must be non-negative, got {T}")
|
|
188
|
+
if sigma <= 0:
|
|
189
|
+
raise ValidationError(f"Volatility must be positive, got {sigma}")
|
|
190
|
+
|
|
191
|
+
def _price_bs93(
|
|
192
|
+
self,
|
|
193
|
+
S: float,
|
|
194
|
+
K: float,
|
|
195
|
+
T: float,
|
|
196
|
+
r: float,
|
|
197
|
+
b: float,
|
|
198
|
+
sigma: float,
|
|
199
|
+
is_call: bool,
|
|
200
|
+
) -> float:
|
|
201
|
+
"""Price using Bjerksund-Stensland 1993 approximation."""
|
|
202
|
+
if is_call:
|
|
203
|
+
return self._price_american_call_bs93(S, K, T, r, b, sigma)
|
|
204
|
+
else:
|
|
205
|
+
if r <= 0 and r <= b:
|
|
206
|
+
return self._european_put_bsm(S, K, T, r, b, sigma)
|
|
207
|
+
return self._price_american_put_bs93(S, K, T, r, b, sigma)
|
|
208
|
+
|
|
209
|
+
def _price_bs02(
|
|
210
|
+
self,
|
|
211
|
+
S: float,
|
|
212
|
+
K: float,
|
|
213
|
+
T: float,
|
|
214
|
+
r: float,
|
|
215
|
+
b: float,
|
|
216
|
+
sigma: float,
|
|
217
|
+
is_call: bool,
|
|
218
|
+
) -> float:
|
|
219
|
+
"""Price using Bjerksund-Stensland 2002 approximation."""
|
|
220
|
+
if is_call:
|
|
221
|
+
return self._price_american_call_bs02(S, K, T, r, b, sigma)
|
|
222
|
+
else:
|
|
223
|
+
if r <= 0 and r <= b:
|
|
224
|
+
return self._european_put_bsm(S, K, T, r, b, sigma)
|
|
225
|
+
return self._price_american_put_bs02(S, K, T, r, b, sigma)
|
|
226
|
+
|
|
227
|
+
def _price_baw(
|
|
228
|
+
self,
|
|
229
|
+
S: float,
|
|
230
|
+
K: float,
|
|
231
|
+
T: float,
|
|
232
|
+
r: float,
|
|
233
|
+
b: float,
|
|
234
|
+
sigma: float,
|
|
235
|
+
is_call: bool,
|
|
236
|
+
) -> float:
|
|
237
|
+
"""Price using Barone-Adesi-Whaley approximation."""
|
|
238
|
+
if is_call:
|
|
239
|
+
return self._price_american_call_baw(S, K, T, r, b, sigma)
|
|
240
|
+
else:
|
|
241
|
+
return self._price_american_put_baw(S, K, T, r, b, sigma)
|
|
242
|
+
|
|
243
|
+
def _price_american_call_bs93(
|
|
244
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
245
|
+
) -> float:
|
|
246
|
+
"""
|
|
247
|
+
Bjerksund-Stensland 1993 approximation for American call.
|
|
248
|
+
|
|
249
|
+
Single-barrier approximation using optimal exercise boundary.
|
|
250
|
+
"""
|
|
251
|
+
beta = (0.5 - b / sigma**2) + np.sqrt(
|
|
252
|
+
(0.5 - b / sigma**2) ** 2 + 2 * r / sigma**2
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
B_infinity = beta * K / (beta - 1.0)
|
|
256
|
+
B_0 = max(K, r * K / (r - b))
|
|
257
|
+
|
|
258
|
+
h_T = -1.0 * (b * T + 2.0 * sigma * np.sqrt(T)) * B_0 / (B_infinity - B_0)
|
|
259
|
+
|
|
260
|
+
I = B_0 + (B_infinity - B_0) * (1.0 - self._safe_exp(h_T))
|
|
261
|
+
|
|
262
|
+
if S >= I:
|
|
263
|
+
return S - K
|
|
264
|
+
|
|
265
|
+
alpha = (I - K) * I ** (-beta)
|
|
266
|
+
|
|
267
|
+
value = (
|
|
268
|
+
alpha * S**beta
|
|
269
|
+
- alpha * self._phi_bs93(S, T, beta, I, I, r, b, sigma)
|
|
270
|
+
+ self._phi_bs93(S, T, 1, I, I, r, b, sigma)
|
|
271
|
+
- self._phi_bs93(S, T, 1, K, I, r, b, sigma)
|
|
272
|
+
- K * self._phi_bs93(S, T, 0, I, I, r, b, sigma)
|
|
273
|
+
+ K * self._phi_bs93(S, T, 0, K, I, r, b, sigma)
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return value
|
|
277
|
+
|
|
278
|
+
def _price_american_put_bs93(
|
|
279
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
280
|
+
) -> float:
|
|
281
|
+
"""American put via put-call transformation for BS93."""
|
|
282
|
+
# Transform parameters for put-call analogue
|
|
283
|
+
# Put(S,K,r,q) = Call(K,S,q,r) with transformed b
|
|
284
|
+
S_new = K
|
|
285
|
+
K_new = S
|
|
286
|
+
r_new = r - b # This is q (dividend yield)
|
|
287
|
+
b_new = -b
|
|
288
|
+
|
|
289
|
+
call_value = self._price_american_call_bs93(
|
|
290
|
+
S_new, K_new, T, r_new, b_new, sigma
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
return call_value
|
|
294
|
+
|
|
295
|
+
def _phi_bs93(
|
|
296
|
+
self,
|
|
297
|
+
S: float,
|
|
298
|
+
T: float,
|
|
299
|
+
gamma: float,
|
|
300
|
+
H: float,
|
|
301
|
+
I: float,
|
|
302
|
+
r: float,
|
|
303
|
+
b: float,
|
|
304
|
+
sigma: float,
|
|
305
|
+
) -> float:
|
|
306
|
+
"""
|
|
307
|
+
Auxiliary function φ for BS93.
|
|
308
|
+
|
|
309
|
+
φ(S, T, γ, H, I) = e^(λT) S^γ [N(d) - (I/S)^κ N(d₂)]
|
|
310
|
+
|
|
311
|
+
Note: This follows the original BS93 formula structure.
|
|
312
|
+
"""
|
|
313
|
+
lamda = T * (-r + b * gamma + 0.5 * gamma * (gamma - 1) * sigma**2)
|
|
314
|
+
|
|
315
|
+
d = (
|
|
316
|
+
-1.0
|
|
317
|
+
* (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T)
|
|
318
|
+
/ (sigma * np.sqrt(T))
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
kappa = 2 * b / sigma**2 + 2 * gamma - 1
|
|
322
|
+
|
|
323
|
+
d2 = d - 2 * self._safe_log(I / S) / (sigma * np.sqrt(T))
|
|
324
|
+
|
|
325
|
+
return (
|
|
326
|
+
self._safe_exp(lamda)
|
|
327
|
+
* S**gamma
|
|
328
|
+
* (norm.cdf(d) - (I / S) ** kappa * norm.cdf(d2))
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def _price_american_call_bs02(
|
|
332
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
333
|
+
) -> float:
|
|
334
|
+
"""
|
|
335
|
+
Bjerksund-Stensland 2002 approximation for American call.
|
|
336
|
+
|
|
337
|
+
More accurate two-barrier approximation.
|
|
338
|
+
"""
|
|
339
|
+
beta = (0.5 - b / sigma**2) + np.sqrt(
|
|
340
|
+
(0.5 - b / sigma**2) ** 2 + 2 * r / sigma**2
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
B_infinity = beta * K / (beta - 1.0)
|
|
344
|
+
B_0 = max(K, r * K / (r - b))
|
|
345
|
+
|
|
346
|
+
t1 = 0.5 * (np.sqrt(5) - 1) * T
|
|
347
|
+
|
|
348
|
+
h1 = -(b * t1 + 2 * sigma * np.sqrt(t1)) * K**2 / ((B_infinity - B_0) * B_0)
|
|
349
|
+
h2 = -(b * T + 2 * sigma * np.sqrt(T)) * K**2 / ((B_infinity - B_0) * B_0)
|
|
350
|
+
|
|
351
|
+
I1 = B_0 + (B_infinity - B_0) * (1 - self._safe_exp(h1))
|
|
352
|
+
I2 = B_0 + (B_infinity - B_0) * (1 - self._safe_exp(h2))
|
|
353
|
+
|
|
354
|
+
if I1 > K:
|
|
355
|
+
log_alpha1 = self._safe_log(I1 - K) - beta * self._safe_log(I1)
|
|
356
|
+
alpha1 = self._safe_exp(log_alpha1) if log_alpha1 > -700 else 0.0
|
|
357
|
+
else:
|
|
358
|
+
alpha1 = 0.0
|
|
359
|
+
|
|
360
|
+
if I2 > K:
|
|
361
|
+
log_alpha2 = self._safe_log(I2 - K) - beta * self._safe_log(I2)
|
|
362
|
+
alpha2 = self._safe_exp(log_alpha2) if log_alpha2 > -700 else 0.0
|
|
363
|
+
else:
|
|
364
|
+
alpha2 = 0.0
|
|
365
|
+
|
|
366
|
+
if S >= I2:
|
|
367
|
+
return S - K
|
|
368
|
+
|
|
369
|
+
value = (
|
|
370
|
+
alpha2 * S**beta
|
|
371
|
+
- alpha2 * self._phi_bs02(S, t1, beta, I2, I2, r, b, sigma)
|
|
372
|
+
+ self._phi_bs02(S, t1, 1, I2, I2, r, b, sigma)
|
|
373
|
+
- self._phi_bs02(S, t1, 1, I1, I2, r, b, sigma)
|
|
374
|
+
- K * self._phi_bs02(S, t1, 0, I2, I2, r, b, sigma)
|
|
375
|
+
+ K * self._phi_bs02(S, t1, 0, I1, I2, r, b, sigma)
|
|
376
|
+
+ alpha1 * self._phi_bs02(S, t1, beta, I1, I2, r, b, sigma)
|
|
377
|
+
- alpha1 * self._psi_bs02(S, T, beta, I1, I2, I1, t1, r, b, sigma)
|
|
378
|
+
+ self._psi_bs02(S, T, 1, I1, I2, I1, t1, r, b, sigma)
|
|
379
|
+
- self._psi_bs02(S, T, 1, K, I2, I1, t1, r, b, sigma)
|
|
380
|
+
- K * self._psi_bs02(S, T, 0, I1, I2, I1, t1, r, b, sigma)
|
|
381
|
+
+ K * self._psi_bs02(S, T, 0, K, I2, I1, t1, r, b, sigma)
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
return value
|
|
385
|
+
|
|
386
|
+
def _price_american_put_bs02(
|
|
387
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
388
|
+
) -> float:
|
|
389
|
+
"""American put via put-call transformation for BS02."""
|
|
390
|
+
S_orig, K_orig, r_orig, b_orig = S, K, r, b
|
|
391
|
+
|
|
392
|
+
S = K_orig
|
|
393
|
+
K = S_orig
|
|
394
|
+
r = r_orig - b_orig
|
|
395
|
+
b = -b_orig
|
|
396
|
+
|
|
397
|
+
call_value = self._price_american_call_bs02(S, K, T, r, b, sigma)
|
|
398
|
+
|
|
399
|
+
return call_value
|
|
400
|
+
|
|
401
|
+
def _phi_bs02(
|
|
402
|
+
self,
|
|
403
|
+
S: float,
|
|
404
|
+
T: float,
|
|
405
|
+
gamma: float,
|
|
406
|
+
H: float,
|
|
407
|
+
I: float,
|
|
408
|
+
r: float,
|
|
409
|
+
b: float,
|
|
410
|
+
sigma: float,
|
|
411
|
+
) -> float:
|
|
412
|
+
"""
|
|
413
|
+
Auxiliary function φ for BS02.
|
|
414
|
+
|
|
415
|
+
φ(S, T, γ, H, I) = e^(λT) S^γ [N(-d) - (I/S)^κ N(-d₂)]
|
|
416
|
+
"""
|
|
417
|
+
if S <= 0 or T <= 0 or H <= 0 or I <= 0:
|
|
418
|
+
return 0.0
|
|
419
|
+
|
|
420
|
+
lambda_val = -r + gamma * b + 0.5 * gamma * (gamma - 1) * sigma**2
|
|
421
|
+
kappa = 2 * b / sigma**2 + 2 * gamma - 1
|
|
422
|
+
|
|
423
|
+
d = (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T) / (
|
|
424
|
+
sigma * np.sqrt(T)
|
|
425
|
+
)
|
|
426
|
+
d2 = (
|
|
427
|
+
self._safe_log(I**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
|
|
428
|
+
) / (sigma * np.sqrt(T))
|
|
429
|
+
|
|
430
|
+
term1 = norm.cdf(-d)
|
|
431
|
+
term2 = (I / S) ** kappa * norm.cdf(-d2)
|
|
432
|
+
|
|
433
|
+
return self._safe_exp(lambda_val * T) * S**gamma * (term1 - term2)
|
|
434
|
+
|
|
435
|
+
def _psi_bs02(
|
|
436
|
+
self,
|
|
437
|
+
S: float,
|
|
438
|
+
T: float,
|
|
439
|
+
gamma: float,
|
|
440
|
+
H: float,
|
|
441
|
+
I2: float,
|
|
442
|
+
I1: float,
|
|
443
|
+
t1: float,
|
|
444
|
+
r: float,
|
|
445
|
+
b: float,
|
|
446
|
+
sigma: float,
|
|
447
|
+
) -> float:
|
|
448
|
+
"""
|
|
449
|
+
Auxiliary function Ψ for BS02.
|
|
450
|
+
|
|
451
|
+
Uses bivariate normal CDF for improved accuracy.
|
|
452
|
+
"""
|
|
453
|
+
if S <= 0 or T <= 0 or t1 <= 0 or H <= 0 or I1 <= 0 or I2 <= 0:
|
|
454
|
+
return 0.0
|
|
455
|
+
|
|
456
|
+
lambda_val = -r + gamma * b + 0.5 * gamma * (gamma - 1) * sigma**2
|
|
457
|
+
kappa = 2 * b / sigma**2 + 2 * gamma - 1
|
|
458
|
+
|
|
459
|
+
rho = np.sqrt(t1 / T)
|
|
460
|
+
|
|
461
|
+
# Calculate e parameters (for t1 dimension)
|
|
462
|
+
e1 = (self._safe_log(S / I1) + (b + (gamma - 0.5) * sigma**2) * t1) / (
|
|
463
|
+
sigma * np.sqrt(t1)
|
|
464
|
+
)
|
|
465
|
+
e2 = (
|
|
466
|
+
self._safe_log(I2**2 / (S * I1)) + (b + (gamma - 0.5) * sigma**2) * t1
|
|
467
|
+
) / (sigma * np.sqrt(t1))
|
|
468
|
+
e3 = (self._safe_log(S / I1) - (b + (gamma - 0.5) * sigma**2) * t1) / (
|
|
469
|
+
sigma * np.sqrt(t1)
|
|
470
|
+
)
|
|
471
|
+
e4 = (
|
|
472
|
+
self._safe_log(I2**2 / (S * I1)) - (b + (gamma - 0.5) * sigma**2) * t1
|
|
473
|
+
) / (sigma * np.sqrt(t1))
|
|
474
|
+
|
|
475
|
+
# Calculate f parameters (for T dimension)
|
|
476
|
+
f1 = (self._safe_log(S / H) + (b + (gamma - 0.5) * sigma**2) * T) / (
|
|
477
|
+
sigma * np.sqrt(T)
|
|
478
|
+
)
|
|
479
|
+
f2 = (
|
|
480
|
+
self._safe_log(I2**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
|
|
481
|
+
) / (sigma * np.sqrt(T))
|
|
482
|
+
f3 = (
|
|
483
|
+
self._safe_log(I2**2 / (S * H)) + (b + (gamma - 0.5) * sigma**2) * T
|
|
484
|
+
) / (sigma * np.sqrt(T))
|
|
485
|
+
f4 = (
|
|
486
|
+
self._safe_log(S * I1**2 / (H * I2**2)) + (b + (gamma - 0.5) * sigma**2) * T
|
|
487
|
+
) / (sigma * np.sqrt(T))
|
|
488
|
+
|
|
489
|
+
M1 = self._bivariate_normal_cdf(-e1, -f1, rho)
|
|
490
|
+
M2 = self._bivariate_normal_cdf(-e2, -f2, rho)
|
|
491
|
+
M3 = self._bivariate_normal_cdf(-e3, -f3, -rho)
|
|
492
|
+
M4 = self._bivariate_normal_cdf(-e4, -f4, -rho)
|
|
493
|
+
|
|
494
|
+
term1 = M1
|
|
495
|
+
term2 = (I2 / S) ** kappa * M2
|
|
496
|
+
term3 = (I1 / S) ** kappa * M3
|
|
497
|
+
term4 = (I1 / I2) ** kappa * M4
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
self._safe_exp(lambda_val * T) * S**gamma * (term1 - term2 - term3 + term4)
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
def _bivariate_normal_cdf(self, x: float, y: float, rho: float) -> float:
|
|
504
|
+
"""
|
|
505
|
+
Bivariate normal CDF M(x, y, ρ).
|
|
506
|
+
|
|
507
|
+
Uses scipy's multivariate_normal for accurate computation.
|
|
508
|
+
"""
|
|
509
|
+
if abs(rho) < 1e-10:
|
|
510
|
+
return norm.cdf(x) * norm.cdf(y)
|
|
511
|
+
|
|
512
|
+
if abs(rho) >= 1.0:
|
|
513
|
+
if rho > 0:
|
|
514
|
+
return min(norm.cdf(x), norm.cdf(y))
|
|
515
|
+
else:
|
|
516
|
+
return max(norm.cdf(x) + norm.cdf(y) - 1, 0.0)
|
|
517
|
+
|
|
518
|
+
mean = [0, 0]
|
|
519
|
+
cov = [[1, rho], [rho, 1]]
|
|
520
|
+
return multivariate_normal.cdf([x, y], mean, cov)
|
|
521
|
+
|
|
522
|
+
def _price_american_call_baw(
|
|
523
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
524
|
+
) -> float:
|
|
525
|
+
"""
|
|
526
|
+
Barone-Adesi-Whaley approximation for American call.
|
|
527
|
+
|
|
528
|
+
Quadratic approximation with iterative critical price search.
|
|
529
|
+
"""
|
|
530
|
+
if b >= r:
|
|
531
|
+
return self._european_call_bsm(S, K, T, r, b, sigma)
|
|
532
|
+
|
|
533
|
+
M = 2 * r / sigma**2
|
|
534
|
+
N = 2 * b / sigma**2
|
|
535
|
+
K_param = 1 - self._safe_exp(-r * T)
|
|
536
|
+
|
|
537
|
+
discriminant = (N - 1) ** 2 + 4 * M / K_param
|
|
538
|
+
if discriminant < 0:
|
|
539
|
+
return self._european_call_bsm(S, K, T, r, b, sigma)
|
|
540
|
+
|
|
541
|
+
q2 = (-(N - 1) + np.sqrt(discriminant)) / 2
|
|
542
|
+
|
|
543
|
+
S_star = self._find_critical_call_price(K, T, r, b, sigma, q2)
|
|
544
|
+
|
|
545
|
+
if S >= S_star:
|
|
546
|
+
return S - K
|
|
547
|
+
|
|
548
|
+
d1_s_star = self._d1_baw(S_star, K, T, b, sigma)
|
|
549
|
+
A2 = (S_star / q2) * (1 - self._safe_exp((b - r) * T) * norm.cdf(d1_s_star))
|
|
550
|
+
|
|
551
|
+
c_bsm = self._european_call_bsm(S, K, T, r, b, sigma)
|
|
552
|
+
|
|
553
|
+
return c_bsm + A2 * (S / S_star) ** q2
|
|
554
|
+
|
|
555
|
+
def _price_american_put_baw(
|
|
556
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
557
|
+
) -> float:
|
|
558
|
+
"""
|
|
559
|
+
Barone-Adesi-Whaley approximation for American put.
|
|
560
|
+
|
|
561
|
+
Direct put pricing (not transformation).
|
|
562
|
+
"""
|
|
563
|
+
if r <= 0 and r <= b:
|
|
564
|
+
return self._european_put_bsm(S, K, T, r, b, sigma)
|
|
565
|
+
|
|
566
|
+
M = 2 * r / sigma**2
|
|
567
|
+
N = 2 * b / sigma**2
|
|
568
|
+
K_param = 1 - self._safe_exp(-r * T)
|
|
569
|
+
|
|
570
|
+
discriminant = (N - 1) ** 2 + 4 * M / K_param
|
|
571
|
+
if discriminant < 0:
|
|
572
|
+
return self._european_put_bsm(S, K, T, r, b, sigma)
|
|
573
|
+
|
|
574
|
+
q1 = (-(N - 1) - np.sqrt(discriminant)) / 2
|
|
575
|
+
|
|
576
|
+
S_star_star = self._find_critical_put_price(K, T, r, b, sigma, q1)
|
|
577
|
+
|
|
578
|
+
if S <= S_star_star:
|
|
579
|
+
return K - S
|
|
580
|
+
|
|
581
|
+
d1_s_star_star = self._d1_baw(S_star_star, K, T, b, sigma)
|
|
582
|
+
A1 = -(S_star_star / q1) * (
|
|
583
|
+
1 - self._safe_exp((b - r) * T) * norm.cdf(-d1_s_star_star)
|
|
584
|
+
)
|
|
585
|
+
|
|
586
|
+
p_bsm = self._european_put_bsm(S, K, T, r, b, sigma)
|
|
587
|
+
|
|
588
|
+
return p_bsm + A1 * (S / S_star_star) ** q1
|
|
589
|
+
|
|
590
|
+
def _find_critical_call_price(
|
|
591
|
+
self, K: float, T: float, r: float, b: float, sigma: float, q2: float
|
|
592
|
+
) -> float:
|
|
593
|
+
"""Find critical stock price S* for American call using optimization."""
|
|
594
|
+
from scipy.optimize import fmin
|
|
595
|
+
|
|
596
|
+
def objective(S_star):
|
|
597
|
+
if S_star <= 0:
|
|
598
|
+
return float("inf")
|
|
599
|
+
d1_s = self._d1_baw(S_star, K, T, b, sigma)
|
|
600
|
+
c_bsm = self._european_call_bsm(S_star, K, T, r, b, sigma)
|
|
601
|
+
lhs = S_star - K
|
|
602
|
+
rhs = (
|
|
603
|
+
c_bsm + (1 - self._safe_exp((b - r) * T) * norm.cdf(d1_s)) * S_star / q2
|
|
604
|
+
)
|
|
605
|
+
return abs(lhs - rhs)
|
|
606
|
+
|
|
607
|
+
S0 = max(K, K * 1.1)
|
|
608
|
+
try:
|
|
609
|
+
result = fmin(objective, S0, disp=False, full_output=True)
|
|
610
|
+
return result[0][0] if result[4] == 0 else K * 1.5
|
|
611
|
+
except:
|
|
612
|
+
return K * 1.5
|
|
613
|
+
|
|
614
|
+
def _find_critical_put_price(
|
|
615
|
+
self, K: float, T: float, r: float, b: float, sigma: float, q1: float
|
|
616
|
+
) -> float:
|
|
617
|
+
"""Find critical stock price S** for American put using optimization."""
|
|
618
|
+
from scipy.optimize import fmin
|
|
619
|
+
|
|
620
|
+
def objective(S_star_star):
|
|
621
|
+
if S_star_star <= 0:
|
|
622
|
+
return float("inf")
|
|
623
|
+
d1_s = self._d1_baw(S_star_star, K, T, b, sigma)
|
|
624
|
+
p_bsm = self._european_put_bsm(S_star_star, K, T, r, b, sigma)
|
|
625
|
+
lhs = K - S_star_star
|
|
626
|
+
rhs = (
|
|
627
|
+
p_bsm
|
|
628
|
+
- (1 - self._safe_exp((b - r) * T) * norm.cdf(-d1_s)) * S_star_star / q1
|
|
629
|
+
)
|
|
630
|
+
return abs(lhs - rhs)
|
|
631
|
+
|
|
632
|
+
S0 = min(K, K * 0.9)
|
|
633
|
+
try:
|
|
634
|
+
result = fmin(objective, S0, disp=False, full_output=True)
|
|
635
|
+
return result[0][0] if result[4] == 0 else K * 0.5
|
|
636
|
+
except:
|
|
637
|
+
return K * 0.5
|
|
638
|
+
|
|
639
|
+
def _d1_baw(self, S: float, K: float, T: float, b: float, sigma: float) -> float:
|
|
640
|
+
"""Calculate d1 for BAW method."""
|
|
641
|
+
return (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
|
|
642
|
+
|
|
643
|
+
def _european_call_bsm(
|
|
644
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
645
|
+
) -> float:
|
|
646
|
+
"""European call price using Black-Scholes-Merton formula."""
|
|
647
|
+
d1 = (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
|
|
648
|
+
d2 = d1 - sigma * np.sqrt(T)
|
|
649
|
+
|
|
650
|
+
return S * self._safe_exp((b - r) * T) * norm.cdf(d1) - K * self._safe_exp(
|
|
651
|
+
-r * T
|
|
652
|
+
) * norm.cdf(d2)
|
|
653
|
+
|
|
654
|
+
def _european_put_bsm(
|
|
655
|
+
self, S: float, K: float, T: float, r: float, b: float, sigma: float
|
|
656
|
+
) -> float:
|
|
657
|
+
"""European put price using Black-Scholes-Merton formula."""
|
|
658
|
+
d1 = (self._safe_log(S / K) + (b + 0.5 * sigma**2) * T) / (sigma * np.sqrt(T))
|
|
659
|
+
d2 = d1 - sigma * np.sqrt(T)
|
|
660
|
+
|
|
661
|
+
return K * self._safe_exp(-r * T) * norm.cdf(-d2) - S * self._safe_exp(
|
|
662
|
+
(b - r) * T
|
|
663
|
+
) * norm.cdf(-d1)
|
|
664
|
+
|
|
665
|
+
def _safe_log(self, x: float) -> float:
|
|
666
|
+
"""Safe logarithm to avoid log(0) or log(negative)."""
|
|
667
|
+
return np.log(max(x, 1e-16))
|
|
668
|
+
|
|
669
|
+
def _safe_sqrt(self, x: float) -> float:
|
|
670
|
+
"""Safe square root to avoid sqrt(negative)."""
|
|
671
|
+
return np.sqrt(max(x, 0.0))
|
|
672
|
+
|
|
673
|
+
def _safe_exp(self, x: float) -> float:
|
|
674
|
+
"""Safe exponential to avoid overflow/underflow."""
|
|
675
|
+
if x > 700:
|
|
676
|
+
return np.exp(700)
|
|
677
|
+
if x < -700:
|
|
678
|
+
return 0.0
|
|
679
|
+
return np.exp(x)
|
|
680
|
+
|
|
681
|
+
def __repr__(self):
|
|
682
|
+
return f"AmericanOptionAnalyticalEngine(method='{self.method}')"
|