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,583 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Black '76 pricing engine for European bond options.
|
|
3
|
+
|
|
4
|
+
The Black model is the standard approach for pricing European options on
|
|
5
|
+
bonds and other fixed income instruments.
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
from scipy import stats
|
|
13
|
+
|
|
14
|
+
from quantark.asset.bond.product.option.euro_short_term_bond_option import EuroShortTermBondOption
|
|
15
|
+
from quantark.asset.bond.engine.discount.bond_discount_engine import BondDiscountEngine
|
|
16
|
+
from quantark.priceenv import PricingEnvironment
|
|
17
|
+
from quantark.util.exceptions import ValidationError, NumericalError, PricingError
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class BlackBondOptionResults:
|
|
22
|
+
"""
|
|
23
|
+
Results from Black model bond option pricing.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
price: Option price
|
|
27
|
+
forward_bond_price: Forward price of the underlying bond
|
|
28
|
+
d1: Black model d1 parameter
|
|
29
|
+
d2: Black model d2 parameter
|
|
30
|
+
discount_factor: Discount factor to option expiry
|
|
31
|
+
time_to_expiry: Time to option expiry in years
|
|
32
|
+
volatility: Volatility used in pricing
|
|
33
|
+
"""
|
|
34
|
+
price: float
|
|
35
|
+
forward_bond_price: float
|
|
36
|
+
d1: float
|
|
37
|
+
d2: float
|
|
38
|
+
discount_factor: float
|
|
39
|
+
time_to_expiry: float
|
|
40
|
+
volatility: float
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class BlackBondOptionEngine:
|
|
44
|
+
"""
|
|
45
|
+
Black '76 pricing engine for European bond options.
|
|
46
|
+
|
|
47
|
+
Uses the Black '76 model to price European options on bonds:
|
|
48
|
+
|
|
49
|
+
Call = D(T) * [F * N(d1) - K * N(d2)]
|
|
50
|
+
Put = D(T) * [K * N(-d2) - F * N(-d1)]
|
|
51
|
+
|
|
52
|
+
where:
|
|
53
|
+
D(T) = discount factor to option expiry
|
|
54
|
+
F = forward bond price
|
|
55
|
+
K = strike price
|
|
56
|
+
d1 = [ln(F/K) + σ²T/2] / (σ√T)
|
|
57
|
+
d2 = d1 - σ√T
|
|
58
|
+
σ = volatility of bond price
|
|
59
|
+
|
|
60
|
+
The forward bond price is calculated by:
|
|
61
|
+
F = (Dirty Price - PV of coupons before expiry) * exp(r*T)
|
|
62
|
+
|
|
63
|
+
Attributes:
|
|
64
|
+
pricing_env: Pricing environment with market data
|
|
65
|
+
bond_engine: Engine for pricing the underlying bond
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, pricing_env: PricingEnvironment):
|
|
69
|
+
"""
|
|
70
|
+
Initialize Black bond option engine.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
pricing_env: Pricing environment with rate curve and volatility
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValidationError: If pricing environment is invalid
|
|
77
|
+
"""
|
|
78
|
+
if pricing_env is None:
|
|
79
|
+
raise ValidationError("Pricing environment is required")
|
|
80
|
+
|
|
81
|
+
self.pricing_env = pricing_env
|
|
82
|
+
self.bond_engine = BondDiscountEngine(pricing_env)
|
|
83
|
+
|
|
84
|
+
def price(
|
|
85
|
+
self,
|
|
86
|
+
option: EuroShortTermBondOption,
|
|
87
|
+
volatility: Optional[float] = None,
|
|
88
|
+
valuation_date: Optional[datetime] = None
|
|
89
|
+
) -> float:
|
|
90
|
+
"""
|
|
91
|
+
Price a European bond option using Black '76 model.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
option: Bond option to price
|
|
95
|
+
volatility: Price volatility (if None, uses vol surface from pricing_env)
|
|
96
|
+
valuation_date: Valuation date (default: pricing env date)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Option price
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
PricingError: If option has expired or pricing fails
|
|
103
|
+
NumericalError: If numerical issues occur
|
|
104
|
+
"""
|
|
105
|
+
results = self.price_with_details(option, volatility, valuation_date)
|
|
106
|
+
return results.price
|
|
107
|
+
|
|
108
|
+
def price_with_details(
|
|
109
|
+
self,
|
|
110
|
+
option: EuroShortTermBondOption,
|
|
111
|
+
volatility: Optional[float] = None,
|
|
112
|
+
valuation_date: Optional[datetime] = None
|
|
113
|
+
) -> BlackBondOptionResults:
|
|
114
|
+
"""
|
|
115
|
+
Price a European bond option with detailed results.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
option: Bond option to price
|
|
119
|
+
volatility: Price volatility (if None, uses vol surface from pricing_env)
|
|
120
|
+
valuation_date: Valuation date (default: pricing env date)
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
BlackBondOptionResults with pricing details
|
|
124
|
+
|
|
125
|
+
Raises:
|
|
126
|
+
PricingError: If option has expired or pricing fails
|
|
127
|
+
NumericalError: If numerical issues occur
|
|
128
|
+
"""
|
|
129
|
+
if valuation_date is None:
|
|
130
|
+
valuation_date = self.pricing_env.valuation_date
|
|
131
|
+
|
|
132
|
+
# Check if option has expired
|
|
133
|
+
if option.is_expired(valuation_date):
|
|
134
|
+
raise PricingError("Option has expired")
|
|
135
|
+
|
|
136
|
+
# Calculate time to expiry
|
|
137
|
+
T = option.get_time_to_expiry(valuation_date)
|
|
138
|
+
|
|
139
|
+
# Handle near-expiry case
|
|
140
|
+
if T < 1e-10:
|
|
141
|
+
bond_price = self._get_bond_price(option, valuation_date)
|
|
142
|
+
payoff = option.get_payoff(bond_price)
|
|
143
|
+
return BlackBondOptionResults(
|
|
144
|
+
price=payoff,
|
|
145
|
+
forward_bond_price=bond_price,
|
|
146
|
+
d1=0.0,
|
|
147
|
+
d2=0.0,
|
|
148
|
+
discount_factor=1.0,
|
|
149
|
+
time_to_expiry=T,
|
|
150
|
+
volatility=0.0
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Get forward bond price
|
|
154
|
+
F = self._calculate_forward_bond_price(option, valuation_date)
|
|
155
|
+
|
|
156
|
+
# Get strike
|
|
157
|
+
K = option.strike
|
|
158
|
+
|
|
159
|
+
# Get discount factor to option expiry
|
|
160
|
+
discount_factor = self.pricing_env.get_discount_factor(T)
|
|
161
|
+
|
|
162
|
+
# Get volatility
|
|
163
|
+
if volatility is None:
|
|
164
|
+
volatility = self._get_volatility(option, valuation_date, F, T)
|
|
165
|
+
|
|
166
|
+
# Validate inputs
|
|
167
|
+
self._validate_inputs(F, K, T, volatility)
|
|
168
|
+
|
|
169
|
+
# Calculate d1 and d2
|
|
170
|
+
d1, d2 = self._calculate_d1_d2(F, K, T, volatility)
|
|
171
|
+
|
|
172
|
+
# Calculate option price
|
|
173
|
+
if option.is_call():
|
|
174
|
+
price = self._price_call(F, K, discount_factor, d1, d2)
|
|
175
|
+
else:
|
|
176
|
+
price = self._price_put(F, K, discount_factor, d1, d2)
|
|
177
|
+
|
|
178
|
+
# Apply notional
|
|
179
|
+
price *= option.notional
|
|
180
|
+
|
|
181
|
+
# Sanity check
|
|
182
|
+
if price < 0:
|
|
183
|
+
raise NumericalError(f"Negative price computed: {price}")
|
|
184
|
+
|
|
185
|
+
return BlackBondOptionResults(
|
|
186
|
+
price=price,
|
|
187
|
+
forward_bond_price=F,
|
|
188
|
+
d1=d1,
|
|
189
|
+
d2=d2,
|
|
190
|
+
discount_factor=discount_factor,
|
|
191
|
+
time_to_expiry=T,
|
|
192
|
+
volatility=volatility
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _get_bond_price(
|
|
196
|
+
self,
|
|
197
|
+
option: EuroShortTermBondOption,
|
|
198
|
+
valuation_date: datetime
|
|
199
|
+
) -> float:
|
|
200
|
+
"""
|
|
201
|
+
Get current bond price (clean or dirty based on option specs).
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
option: Bond option
|
|
205
|
+
valuation_date: Valuation date
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Bond price
|
|
209
|
+
"""
|
|
210
|
+
if option.strike_is_clean:
|
|
211
|
+
return self.bond_engine.clean_price(
|
|
212
|
+
option.underlying,
|
|
213
|
+
valuation_date,
|
|
214
|
+
valuation_date
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
return self.bond_engine.dirty_price(
|
|
218
|
+
option.underlying,
|
|
219
|
+
valuation_date,
|
|
220
|
+
valuation_date
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _calculate_forward_bond_price(
|
|
224
|
+
self,
|
|
225
|
+
option: EuroShortTermBondOption,
|
|
226
|
+
valuation_date: datetime
|
|
227
|
+
) -> float:
|
|
228
|
+
"""
|
|
229
|
+
Calculate the forward price of the bond to option expiry.
|
|
230
|
+
|
|
231
|
+
Forward Price = (Spot Dirty Price - PV of coupons before expiry) * exp(r*T)
|
|
232
|
+
|
|
233
|
+
For clean price options, we adjust by subtracting accrued interest at expiry.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
option: Bond option
|
|
237
|
+
valuation_date: Valuation date
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Forward bond price
|
|
241
|
+
"""
|
|
242
|
+
underlying = option.underlying
|
|
243
|
+
expiry_date = option.expiry_date
|
|
244
|
+
|
|
245
|
+
# Get time to expiry
|
|
246
|
+
T = option.get_time_to_expiry(valuation_date)
|
|
247
|
+
|
|
248
|
+
# Get current dirty price
|
|
249
|
+
spot_dirty = self.bond_engine.dirty_price(underlying, valuation_date, valuation_date)
|
|
250
|
+
|
|
251
|
+
# Get cashflows between valuation and expiry
|
|
252
|
+
all_cashflows = underlying.get_cashflows(valuation_date)
|
|
253
|
+
|
|
254
|
+
# Calculate PV of coupons paid before expiry
|
|
255
|
+
coupon_pv = 0.0
|
|
256
|
+
for cf in all_cashflows:
|
|
257
|
+
if cf.payment_date <= expiry_date:
|
|
258
|
+
# Time from valuation to coupon payment
|
|
259
|
+
time_to_coupon = (cf.payment_date - valuation_date).days / 365.0
|
|
260
|
+
if time_to_coupon >= 0:
|
|
261
|
+
df = self.pricing_env.get_discount_factor(time_to_coupon)
|
|
262
|
+
coupon_pv += cf.amount * df
|
|
263
|
+
|
|
264
|
+
# Forward dirty price
|
|
265
|
+
r = self.pricing_env.get_rate(T)
|
|
266
|
+
forward_dirty = (spot_dirty - coupon_pv) * math.exp(r * T)
|
|
267
|
+
|
|
268
|
+
# Adjust for clean vs dirty
|
|
269
|
+
if option.strike_is_clean:
|
|
270
|
+
# Calculate accrued interest at expiry
|
|
271
|
+
accrued_at_expiry = underlying.calculate_accrued_interest(expiry_date)
|
|
272
|
+
forward_clean = forward_dirty - accrued_at_expiry
|
|
273
|
+
return forward_clean
|
|
274
|
+
else:
|
|
275
|
+
return forward_dirty
|
|
276
|
+
|
|
277
|
+
def _get_volatility(
|
|
278
|
+
self,
|
|
279
|
+
option: EuroShortTermBondOption,
|
|
280
|
+
valuation_date: datetime,
|
|
281
|
+
forward_price: float,
|
|
282
|
+
time_to_expiry: float
|
|
283
|
+
) -> float:
|
|
284
|
+
"""
|
|
285
|
+
Get volatility for pricing.
|
|
286
|
+
|
|
287
|
+
First tries to use the vol surface from pricing environment.
|
|
288
|
+
If not available, uses a default volatility.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
option: Bond option
|
|
292
|
+
valuation_date: Valuation date
|
|
293
|
+
forward_price: Forward bond price
|
|
294
|
+
time_to_expiry: Time to option expiry
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Volatility for pricing
|
|
298
|
+
"""
|
|
299
|
+
try:
|
|
300
|
+
# Try to use vol surface if available
|
|
301
|
+
if self.pricing_env.vol_surface is not None:
|
|
302
|
+
return self.pricing_env.get_vol(option.strike, time_to_expiry)
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
# Default volatility for bonds (typically lower than equities)
|
|
307
|
+
# Around 5-15% for government bonds
|
|
308
|
+
return 0.10 # 10% default
|
|
309
|
+
|
|
310
|
+
def _validate_inputs(
|
|
311
|
+
self,
|
|
312
|
+
F: float,
|
|
313
|
+
K: float,
|
|
314
|
+
T: float,
|
|
315
|
+
sigma: float
|
|
316
|
+
) -> None:
|
|
317
|
+
"""
|
|
318
|
+
Validate inputs for numerical stability.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
F: Forward price
|
|
322
|
+
K: Strike price
|
|
323
|
+
T: Time to expiry
|
|
324
|
+
sigma: Volatility
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
ValidationError: If inputs are invalid
|
|
328
|
+
"""
|
|
329
|
+
if F <= 0:
|
|
330
|
+
raise ValidationError(f"Forward price must be positive, got {F}")
|
|
331
|
+
if K <= 0:
|
|
332
|
+
raise ValidationError(f"Strike must be positive, got {K}")
|
|
333
|
+
if T < 0:
|
|
334
|
+
raise ValidationError(f"Time to expiry must be non-negative, got {T}")
|
|
335
|
+
if sigma <= 0:
|
|
336
|
+
raise ValidationError(f"Volatility must be positive, got {sigma}")
|
|
337
|
+
if sigma > 5.0:
|
|
338
|
+
raise ValidationError(f"Volatility too high: {sigma}")
|
|
339
|
+
|
|
340
|
+
def _calculate_d1_d2(
|
|
341
|
+
self,
|
|
342
|
+
F: float,
|
|
343
|
+
K: float,
|
|
344
|
+
T: float,
|
|
345
|
+
sigma: float
|
|
346
|
+
) -> tuple:
|
|
347
|
+
"""
|
|
348
|
+
Calculate Black model d1 and d2 parameters.
|
|
349
|
+
|
|
350
|
+
d1 = [ln(F/K) + σ²T/2] / (σ√T)
|
|
351
|
+
d2 = d1 - σ√T
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
F: Forward price
|
|
355
|
+
K: Strike price
|
|
356
|
+
T: Time to expiry
|
|
357
|
+
sigma: Volatility
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Tuple of (d1, d2)
|
|
361
|
+
"""
|
|
362
|
+
try:
|
|
363
|
+
sqrt_T = math.sqrt(T)
|
|
364
|
+
log_moneyness = math.log(F / K)
|
|
365
|
+
|
|
366
|
+
# Check for extreme values
|
|
367
|
+
if abs(log_moneyness) > 100:
|
|
368
|
+
raise NumericalError(f"Extreme moneyness: ln(F/K) = {log_moneyness}")
|
|
369
|
+
|
|
370
|
+
numerator = log_moneyness + 0.5 * sigma * sigma * T
|
|
371
|
+
denominator = sigma * sqrt_T
|
|
372
|
+
|
|
373
|
+
if denominator <= 1e-10:
|
|
374
|
+
raise NumericalError(f"Denominator too small: σ√T = {denominator}")
|
|
375
|
+
|
|
376
|
+
d1 = numerator / denominator
|
|
377
|
+
d2 = d1 - sigma * sqrt_T
|
|
378
|
+
|
|
379
|
+
return d1, d2
|
|
380
|
+
|
|
381
|
+
except (OverflowError, ValueError) as e:
|
|
382
|
+
raise NumericalError(f"Numerical error in d1/d2 calculation: {e}")
|
|
383
|
+
|
|
384
|
+
def _price_call(
|
|
385
|
+
self,
|
|
386
|
+
F: float,
|
|
387
|
+
K: float,
|
|
388
|
+
discount_factor: float,
|
|
389
|
+
d1: float,
|
|
390
|
+
d2: float
|
|
391
|
+
) -> float:
|
|
392
|
+
"""
|
|
393
|
+
Calculate call option price using Black formula.
|
|
394
|
+
|
|
395
|
+
Call = D(T) * [F * N(d1) - K * N(d2)]
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
F: Forward price
|
|
399
|
+
K: Strike price
|
|
400
|
+
discount_factor: Discount factor to expiry
|
|
401
|
+
d1: d1 parameter
|
|
402
|
+
d2: d2 parameter
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Call option price
|
|
406
|
+
"""
|
|
407
|
+
N_d1 = stats.norm.cdf(d1)
|
|
408
|
+
N_d2 = stats.norm.cdf(d2)
|
|
409
|
+
|
|
410
|
+
return discount_factor * (F * N_d1 - K * N_d2)
|
|
411
|
+
|
|
412
|
+
def _price_put(
|
|
413
|
+
self,
|
|
414
|
+
F: float,
|
|
415
|
+
K: float,
|
|
416
|
+
discount_factor: float,
|
|
417
|
+
d1: float,
|
|
418
|
+
d2: float
|
|
419
|
+
) -> float:
|
|
420
|
+
"""
|
|
421
|
+
Calculate put option price using Black formula.
|
|
422
|
+
|
|
423
|
+
Put = D(T) * [K * N(-d2) - F * N(-d1)]
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
F: Forward price
|
|
427
|
+
K: Strike price
|
|
428
|
+
discount_factor: Discount factor to expiry
|
|
429
|
+
d1: d1 parameter
|
|
430
|
+
d2: d2 parameter
|
|
431
|
+
|
|
432
|
+
Returns:
|
|
433
|
+
Put option price
|
|
434
|
+
"""
|
|
435
|
+
N_minus_d1 = stats.norm.cdf(-d1)
|
|
436
|
+
N_minus_d2 = stats.norm.cdf(-d2)
|
|
437
|
+
|
|
438
|
+
return discount_factor * (K * N_minus_d2 - F * N_minus_d1)
|
|
439
|
+
|
|
440
|
+
def implied_volatility(
|
|
441
|
+
self,
|
|
442
|
+
option: EuroShortTermBondOption,
|
|
443
|
+
market_price: float,
|
|
444
|
+
valuation_date: Optional[datetime] = None,
|
|
445
|
+
initial_guess: float = 0.10,
|
|
446
|
+
max_iterations: int = 100,
|
|
447
|
+
tolerance: float = 1e-6
|
|
448
|
+
) -> float:
|
|
449
|
+
"""
|
|
450
|
+
Calculate implied volatility from market price using Newton-Raphson.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
option: Bond option
|
|
454
|
+
market_price: Observed market price
|
|
455
|
+
valuation_date: Valuation date
|
|
456
|
+
initial_guess: Initial volatility guess (default: 10%)
|
|
457
|
+
max_iterations: Maximum iterations
|
|
458
|
+
tolerance: Convergence tolerance
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Implied volatility
|
|
462
|
+
|
|
463
|
+
Raises:
|
|
464
|
+
NumericalError: If convergence fails
|
|
465
|
+
"""
|
|
466
|
+
if valuation_date is None:
|
|
467
|
+
valuation_date = self.pricing_env.valuation_date
|
|
468
|
+
|
|
469
|
+
if market_price <= 0:
|
|
470
|
+
raise ValidationError(f"Market price must be positive, got {market_price}")
|
|
471
|
+
|
|
472
|
+
# Adjust for notional
|
|
473
|
+
target_price = market_price / option.notional
|
|
474
|
+
|
|
475
|
+
sigma = initial_guess
|
|
476
|
+
|
|
477
|
+
for iteration in range(max_iterations):
|
|
478
|
+
# Calculate price and vega at current volatility
|
|
479
|
+
results = self.price_with_details(option, sigma, valuation_date)
|
|
480
|
+
price = results.price / option.notional
|
|
481
|
+
|
|
482
|
+
# Calculate raw vega (not per 1% change) for Newton-Raphson
|
|
483
|
+
vega = self._calculate_raw_vega(
|
|
484
|
+
results.forward_bond_price,
|
|
485
|
+
option.strike,
|
|
486
|
+
results.time_to_expiry,
|
|
487
|
+
sigma,
|
|
488
|
+
results.discount_factor,
|
|
489
|
+
results.d1
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
# Check convergence
|
|
493
|
+
price_diff = price - target_price
|
|
494
|
+
|
|
495
|
+
if abs(price_diff) < tolerance:
|
|
496
|
+
return sigma
|
|
497
|
+
|
|
498
|
+
# Newton-Raphson update with fallback to bisection if vega too small
|
|
499
|
+
if abs(vega) < 1e-10:
|
|
500
|
+
# Fallback: try bisection step
|
|
501
|
+
if price < target_price:
|
|
502
|
+
sigma = sigma * 1.5 # increase vol
|
|
503
|
+
else:
|
|
504
|
+
sigma = sigma * 0.5 # decrease vol
|
|
505
|
+
else:
|
|
506
|
+
sigma = sigma - price_diff / vega
|
|
507
|
+
|
|
508
|
+
# Bounds check
|
|
509
|
+
sigma = max(0.001, min(5.0, sigma))
|
|
510
|
+
|
|
511
|
+
raise NumericalError(
|
|
512
|
+
f"Implied volatility did not converge after {max_iterations} iterations"
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
def _calculate_raw_vega(
|
|
516
|
+
self,
|
|
517
|
+
F: float,
|
|
518
|
+
K: float,
|
|
519
|
+
T: float,
|
|
520
|
+
sigma: float,
|
|
521
|
+
discount_factor: float,
|
|
522
|
+
d1: float
|
|
523
|
+
) -> float:
|
|
524
|
+
"""
|
|
525
|
+
Calculate raw vega (sensitivity to volatility) for Newton-Raphson.
|
|
526
|
+
|
|
527
|
+
Vega = D(T) * F * √T * n(d1)
|
|
528
|
+
|
|
529
|
+
where n(d1) is the standard normal PDF.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
F: Forward price
|
|
533
|
+
K: Strike price
|
|
534
|
+
T: Time to expiry
|
|
535
|
+
sigma: Volatility
|
|
536
|
+
discount_factor: Discount factor
|
|
537
|
+
d1: d1 parameter
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
Raw vega (∂V/∂σ)
|
|
541
|
+
"""
|
|
542
|
+
sqrt_T = math.sqrt(T)
|
|
543
|
+
n_d1 = stats.norm.pdf(d1)
|
|
544
|
+
|
|
545
|
+
# Raw vega without scaling
|
|
546
|
+
return discount_factor * F * sqrt_T * n_d1
|
|
547
|
+
|
|
548
|
+
def _calculate_vega(
|
|
549
|
+
self,
|
|
550
|
+
F: float,
|
|
551
|
+
K: float,
|
|
552
|
+
T: float,
|
|
553
|
+
sigma: float,
|
|
554
|
+
discount_factor: float,
|
|
555
|
+
d1: float
|
|
556
|
+
) -> float:
|
|
557
|
+
"""
|
|
558
|
+
Calculate vega (sensitivity to volatility).
|
|
559
|
+
|
|
560
|
+
Vega = D(T) * F * √T * n(d1)
|
|
561
|
+
|
|
562
|
+
where n(d1) is the standard normal PDF.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
F: Forward price
|
|
566
|
+
K: Strike price
|
|
567
|
+
T: Time to expiry
|
|
568
|
+
sigma: Volatility
|
|
569
|
+
discount_factor: Discount factor
|
|
570
|
+
d1: d1 parameter
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
Vega (per 1% vol change)
|
|
574
|
+
"""
|
|
575
|
+
sqrt_T = math.sqrt(T)
|
|
576
|
+
n_d1 = stats.norm.pdf(d1)
|
|
577
|
+
|
|
578
|
+
# Vega per 1% change (divide by 100)
|
|
579
|
+
return discount_factor * F * sqrt_T * n_d1 / 100
|
|
580
|
+
|
|
581
|
+
def __repr__(self):
|
|
582
|
+
return f"BlackBondOptionEngine(valuation_date={self.pricing_env.valuation_date.date()})"
|
|
583
|
+
|