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,800 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unified facade engine for convertible bond pricing.
|
|
3
|
+
|
|
4
|
+
This module provides ConvertibleBondEngine that dispatches to appropriate
|
|
5
|
+
underlying engines based on method selection.
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Dict, Optional, Union
|
|
11
|
+
|
|
12
|
+
from quantark.asset.bond.product.convertible.convertible_bond import ConvertibleBond
|
|
13
|
+
from quantark.asset.bond.engine.tree.convertible import (
|
|
14
|
+
ConvertibleBondTreeParams,
|
|
15
|
+
ConvertibleBondBinomialEngine,
|
|
16
|
+
ConvertibleBondTrinomialEngine,
|
|
17
|
+
)
|
|
18
|
+
from quantark.asset.bond.engine.pde.convertible import (
|
|
19
|
+
ConvertibleBondPDEParams,
|
|
20
|
+
ConvertibleBondJumpDiffusionEngine,
|
|
21
|
+
ConvertibleBondTFEngine,
|
|
22
|
+
)
|
|
23
|
+
from quantark.priceenv import PricingEnvironment
|
|
24
|
+
from quantark.param.rrf import ParallelShiftRateCurve
|
|
25
|
+
from quantark.util.enum.engine_enums import EngineType, ConvertibleBondMethod, PDEMethod
|
|
26
|
+
from quantark.util.exceptions import ValidationError
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ConvertibleBondResult:
|
|
31
|
+
"""
|
|
32
|
+
Comprehensive result container for convertible bond pricing.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
price: Clean price of the convertible bond
|
|
36
|
+
dirty_price: Dirty price including accrued interest
|
|
37
|
+
delta: Price sensitivity to stock price
|
|
38
|
+
gamma: Second derivative of price with respect to stock
|
|
39
|
+
conversion_probability: Probability of eventual conversion
|
|
40
|
+
equity_component: Equity-like component of value
|
|
41
|
+
bond_component: Bond-like component of value (COCB for TF model)
|
|
42
|
+
default_probability: Probability of default (trinomial model only)
|
|
43
|
+
method: Method used for pricing
|
|
44
|
+
floor_bond_price: Straight bond price without conversion/options
|
|
45
|
+
floor_bond_dv01: Floor bond DV01 (price change per bp rate move)
|
|
46
|
+
floor_bond_cs01: Floor bond CS01 (price change per bp spread move)
|
|
47
|
+
floor_bond_duration: Floor bond modified duration
|
|
48
|
+
floor_bond_convexity: Floor bond convexity
|
|
49
|
+
dv01: Convertible DV01 (price change per bp rate move)
|
|
50
|
+
cs01: Convertible CS01 (price change per bp spread move)
|
|
51
|
+
modified_duration: Convertible modified duration
|
|
52
|
+
convexity: Convertible convexity
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
price: float
|
|
56
|
+
dirty_price: float
|
|
57
|
+
delta: float = 0.0
|
|
58
|
+
gamma: float = 0.0
|
|
59
|
+
conversion_probability: float = 0.0
|
|
60
|
+
equity_component: float = 0.0
|
|
61
|
+
bond_component: float = 0.0
|
|
62
|
+
default_probability: float = 0.0
|
|
63
|
+
method: str = ""
|
|
64
|
+
# Floor bond metrics
|
|
65
|
+
floor_bond_price: float = 0.0
|
|
66
|
+
floor_bond_dv01: float = 0.0
|
|
67
|
+
floor_bond_cs01: float = 0.0
|
|
68
|
+
floor_bond_duration: float = 0.0
|
|
69
|
+
floor_bond_convexity: float = 0.0
|
|
70
|
+
# Convertible risk metrics
|
|
71
|
+
dv01: float = 0.0
|
|
72
|
+
cs01: float = 0.0
|
|
73
|
+
modified_duration: float = 0.0
|
|
74
|
+
convexity: float = 0.0
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ConvertibleBondEngine:
|
|
78
|
+
"""
|
|
79
|
+
Unified facade engine for convertible bond pricing.
|
|
80
|
+
|
|
81
|
+
This engine dispatches pricing requests to specialized engines based on
|
|
82
|
+
the selected method. It supports the two-level enum pattern consistent
|
|
83
|
+
with other engines in the library.
|
|
84
|
+
|
|
85
|
+
Supported Methods:
|
|
86
|
+
Tree-based:
|
|
87
|
+
- BINOMIAL_GS: Goldman Sachs credit-adjusted binomial model
|
|
88
|
+
- TRINOMIAL_HW: Hull-White trinomial with default
|
|
89
|
+
|
|
90
|
+
PDE-based:
|
|
91
|
+
- JUMP_DIFFUSION: Bloomberg OVCV jump-diffusion model
|
|
92
|
+
- TF: Tsiveriotis-Fernandes decomposition
|
|
93
|
+
|
|
94
|
+
Usage:
|
|
95
|
+
# Using two-level enum pattern
|
|
96
|
+
engine = ConvertibleBondEngine(
|
|
97
|
+
pricing_env,
|
|
98
|
+
method=EngineType.TREE(ConvertibleBondMethod.BINOMIAL_GS)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Using single method enum
|
|
102
|
+
engine = ConvertibleBondEngine(
|
|
103
|
+
pricing_env,
|
|
104
|
+
method=ConvertibleBondMethod.JUMP_DIFFUSION
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Using string
|
|
108
|
+
engine = ConvertibleBondEngine(pricing_env, method="binomial_gs")
|
|
109
|
+
|
|
110
|
+
# Pricing
|
|
111
|
+
price = engine.price(convertible_bond)
|
|
112
|
+
result = engine.price_with_details(convertible_bond)
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
# Map methods to engine types
|
|
116
|
+
TREE_METHODS = {
|
|
117
|
+
ConvertibleBondMethod.BINOMIAL_GS,
|
|
118
|
+
ConvertibleBondMethod.TRINOMIAL_HW,
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
PDE_METHODS = {
|
|
122
|
+
ConvertibleBondMethod.JUMP_DIFFUSION,
|
|
123
|
+
ConvertibleBondMethod.TF,
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
DEFAULT_METHOD = ConvertibleBondMethod.BINOMIAL_GS
|
|
127
|
+
|
|
128
|
+
def __init__(
|
|
129
|
+
self,
|
|
130
|
+
pricing_env: PricingEnvironment,
|
|
131
|
+
method: Optional[
|
|
132
|
+
Union[str, ConvertibleBondMethod, tuple]
|
|
133
|
+
] = None,
|
|
134
|
+
tree_params: Optional[ConvertibleBondTreeParams] = None,
|
|
135
|
+
pde_params: Optional[ConvertibleBondPDEParams] = None,
|
|
136
|
+
scheme: Optional[Union[str, PDEMethod]] = None,
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
Initialize the facade engine.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
pricing_env: Pricing environment with market data
|
|
143
|
+
method: Pricing method selection, can be:
|
|
144
|
+
- ConvertibleBondMethod enum
|
|
145
|
+
- String (e.g., "binomial_gs", "jump_diffusion")
|
|
146
|
+
- Tuple from EngineType.TREE(ConvertibleBondMethod.BINOMIAL_GS)
|
|
147
|
+
- None (defaults to BINOMIAL_GS)
|
|
148
|
+
tree_params: Configuration for tree-based engines (optional)
|
|
149
|
+
pde_params: Configuration for PDE-based engines (optional)
|
|
150
|
+
scheme: PDE numerical scheme (optional, for PDE methods only)
|
|
151
|
+
|
|
152
|
+
Raises:
|
|
153
|
+
ValidationError: If invalid method or configuration
|
|
154
|
+
"""
|
|
155
|
+
if pricing_env is None:
|
|
156
|
+
raise ValidationError("Pricing environment is required")
|
|
157
|
+
|
|
158
|
+
self.pricing_env = pricing_env
|
|
159
|
+
self.tree_params = tree_params
|
|
160
|
+
self.pde_params = pde_params
|
|
161
|
+
self.scheme = scheme
|
|
162
|
+
|
|
163
|
+
# Parse method
|
|
164
|
+
self.method = self._parse_method(method)
|
|
165
|
+
|
|
166
|
+
# Create appropriate underlying engine
|
|
167
|
+
self._engine = self._create_engine()
|
|
168
|
+
|
|
169
|
+
def _parse_method(
|
|
170
|
+
self, method: Optional[Union[str, ConvertibleBondMethod, tuple]]
|
|
171
|
+
) -> ConvertibleBondMethod:
|
|
172
|
+
"""
|
|
173
|
+
Parse the method argument into a ConvertibleBondMethod enum.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
method: Method specification
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
ConvertibleBondMethod enum value
|
|
180
|
+
|
|
181
|
+
Raises:
|
|
182
|
+
ValidationError: If invalid method
|
|
183
|
+
"""
|
|
184
|
+
if method is None:
|
|
185
|
+
return self.DEFAULT_METHOD
|
|
186
|
+
|
|
187
|
+
if isinstance(method, ConvertibleBondMethod):
|
|
188
|
+
return method
|
|
189
|
+
|
|
190
|
+
if isinstance(method, str):
|
|
191
|
+
try:
|
|
192
|
+
return ConvertibleBondMethod(method.lower())
|
|
193
|
+
except ValueError:
|
|
194
|
+
valid_methods = [m.value for m in ConvertibleBondMethod]
|
|
195
|
+
raise ValidationError(
|
|
196
|
+
f"Invalid method: {method}. Valid methods: {valid_methods}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if isinstance(method, tuple):
|
|
200
|
+
if len(method) != 2:
|
|
201
|
+
raise ValidationError(
|
|
202
|
+
f"Invalid method tuple: expected (EngineType, Method), got {method}"
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
engine_type, cb_method = method
|
|
206
|
+
|
|
207
|
+
# Validate engine type matches method
|
|
208
|
+
if engine_type == EngineType.TREE:
|
|
209
|
+
if cb_method not in self.TREE_METHODS:
|
|
210
|
+
raise ValidationError(
|
|
211
|
+
f"Method {cb_method} is not a tree method"
|
|
212
|
+
)
|
|
213
|
+
elif engine_type == EngineType.PDE:
|
|
214
|
+
if cb_method not in self.PDE_METHODS:
|
|
215
|
+
raise ValidationError(
|
|
216
|
+
f"Method {cb_method} is not a PDE method"
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
raise ValidationError(
|
|
220
|
+
f"Unsupported engine type: {engine_type}. "
|
|
221
|
+
f"Expected TREE or PDE."
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
if not isinstance(cb_method, ConvertibleBondMethod):
|
|
225
|
+
raise ValidationError(
|
|
226
|
+
f"Expected ConvertibleBondMethod, got {type(cb_method)}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return cb_method
|
|
230
|
+
|
|
231
|
+
raise ValidationError(
|
|
232
|
+
f"Invalid method type: {type(method)}. "
|
|
233
|
+
f"Expected ConvertibleBondMethod, str, or tuple."
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _create_engine(self):
|
|
237
|
+
"""
|
|
238
|
+
Create the appropriate underlying engine based on method.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Specialized pricing engine instance
|
|
242
|
+
"""
|
|
243
|
+
if self.method == ConvertibleBondMethod.BINOMIAL_GS:
|
|
244
|
+
return ConvertibleBondBinomialEngine(
|
|
245
|
+
self.pricing_env, self.tree_params
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
elif self.method == ConvertibleBondMethod.TRINOMIAL_HW:
|
|
249
|
+
return ConvertibleBondTrinomialEngine(
|
|
250
|
+
self.pricing_env, self.tree_params
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
elif self.method == ConvertibleBondMethod.JUMP_DIFFUSION:
|
|
254
|
+
params = self.pde_params or ConvertibleBondPDEParams()
|
|
255
|
+
if self.scheme:
|
|
256
|
+
if isinstance(self.scheme, PDEMethod):
|
|
257
|
+
params.scheme = self.scheme.value
|
|
258
|
+
else:
|
|
259
|
+
params.scheme = str(self.scheme)
|
|
260
|
+
return ConvertibleBondJumpDiffusionEngine(self.pricing_env, params)
|
|
261
|
+
|
|
262
|
+
elif self.method == ConvertibleBondMethod.TF:
|
|
263
|
+
params = self.pde_params or ConvertibleBondPDEParams()
|
|
264
|
+
if self.scheme:
|
|
265
|
+
if isinstance(self.scheme, PDEMethod):
|
|
266
|
+
params.scheme = self.scheme.value
|
|
267
|
+
else:
|
|
268
|
+
params.scheme = str(self.scheme)
|
|
269
|
+
return ConvertibleBondTFEngine(self.pricing_env, params)
|
|
270
|
+
|
|
271
|
+
else:
|
|
272
|
+
raise ValidationError(f"Unsupported method: {self.method}")
|
|
273
|
+
|
|
274
|
+
def price(self, bond: ConvertibleBond) -> float:
|
|
275
|
+
"""
|
|
276
|
+
Calculate the clean price of the convertible bond.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
bond: Convertible bond to price
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
Clean price
|
|
283
|
+
"""
|
|
284
|
+
return self._engine.price(bond)
|
|
285
|
+
|
|
286
|
+
def price_with_details(
|
|
287
|
+
self, bond: ConvertibleBond, include_risk_metrics: bool = True
|
|
288
|
+
) -> ConvertibleBondResult:
|
|
289
|
+
"""
|
|
290
|
+
Calculate price with detailed results.
|
|
291
|
+
|
|
292
|
+
Dispatches to the appropriate underlying engine and converts
|
|
293
|
+
the result to a unified ConvertibleBondResult.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
bond: Convertible bond to price
|
|
297
|
+
include_risk_metrics: Whether to compute risk metrics (DV01, CS01,
|
|
298
|
+
duration, convexity). Set to False to skip for performance.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
ConvertibleBondResult with full pricing details
|
|
302
|
+
"""
|
|
303
|
+
raw_result = self._engine.price_with_details(bond)
|
|
304
|
+
|
|
305
|
+
# Convert to unified result format
|
|
306
|
+
result = ConvertibleBondResult(
|
|
307
|
+
price=raw_result.price,
|
|
308
|
+
dirty_price=raw_result.dirty_price,
|
|
309
|
+
delta=getattr(raw_result, "delta", 0.0),
|
|
310
|
+
gamma=getattr(raw_result, "gamma", 0.0),
|
|
311
|
+
method=self.method.value,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Extract method-specific fields
|
|
315
|
+
if hasattr(raw_result, "conversion_probability"):
|
|
316
|
+
result.conversion_probability = raw_result.conversion_probability
|
|
317
|
+
|
|
318
|
+
if hasattr(raw_result, "equity_component"):
|
|
319
|
+
result.equity_component = raw_result.equity_component
|
|
320
|
+
|
|
321
|
+
if hasattr(raw_result, "bond_component"):
|
|
322
|
+
result.bond_component = raw_result.bond_component
|
|
323
|
+
|
|
324
|
+
if hasattr(raw_result, "default_probability"):
|
|
325
|
+
result.default_probability = raw_result.default_probability
|
|
326
|
+
|
|
327
|
+
# Compute risk metrics if requested
|
|
328
|
+
if include_risk_metrics:
|
|
329
|
+
# Floor bond metrics (analytical, fast)
|
|
330
|
+
result.floor_bond_price = self.floor_bond_price(bond)
|
|
331
|
+
result.floor_bond_duration = self.floor_bond_duration(bond)
|
|
332
|
+
result.floor_bond_convexity = self.floor_bond_convexity(bond)
|
|
333
|
+
result.floor_bond_dv01 = self.floor_bond_dv01(bond)
|
|
334
|
+
result.floor_bond_cs01 = self.floor_bond_cs01(bond)
|
|
335
|
+
|
|
336
|
+
# Convertible risk metrics (numerical, slower)
|
|
337
|
+
result.dv01 = self.dv01(bond)
|
|
338
|
+
result.cs01 = self.cs01(bond)
|
|
339
|
+
result.modified_duration = self.modified_duration(bond)
|
|
340
|
+
result.convexity = self.convexity(bond)
|
|
341
|
+
|
|
342
|
+
return result
|
|
343
|
+
|
|
344
|
+
def calculate_delta(self, bond: ConvertibleBond) -> float:
|
|
345
|
+
"""
|
|
346
|
+
Calculate delta (price sensitivity to stock price).
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
bond: Convertible bond
|
|
350
|
+
|
|
351
|
+
Returns:
|
|
352
|
+
Delta
|
|
353
|
+
"""
|
|
354
|
+
if hasattr(self._engine, "calculate_delta"):
|
|
355
|
+
return self._engine.calculate_delta(bond)
|
|
356
|
+
result = self.price_with_details(bond)
|
|
357
|
+
return result.delta
|
|
358
|
+
|
|
359
|
+
def calculate_gamma(self, bond: ConvertibleBond) -> float:
|
|
360
|
+
"""
|
|
361
|
+
Calculate gamma (second derivative with respect to stock).
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
bond: Convertible bond
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Gamma
|
|
368
|
+
"""
|
|
369
|
+
if hasattr(self._engine, "calculate_gamma"):
|
|
370
|
+
return self._engine.calculate_gamma(bond)
|
|
371
|
+
result = self.price_with_details(bond)
|
|
372
|
+
return result.gamma
|
|
373
|
+
|
|
374
|
+
def get_cocb(self, bond: ConvertibleBond) -> float:
|
|
375
|
+
"""
|
|
376
|
+
Get Cash-Only Component of Bond (TF model only).
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
bond: Convertible bond
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
COCB value
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
ValidationError: If not using TF model
|
|
386
|
+
"""
|
|
387
|
+
if self.method != ConvertibleBondMethod.TF:
|
|
388
|
+
raise ValidationError(
|
|
389
|
+
"COCB is only available with the TF model"
|
|
390
|
+
)
|
|
391
|
+
return self._engine.get_cocb(bond)
|
|
392
|
+
|
|
393
|
+
# =========================================================================
|
|
394
|
+
# Floor Bond Methods
|
|
395
|
+
# =========================================================================
|
|
396
|
+
|
|
397
|
+
def _floor_bond_price_with_env(
|
|
398
|
+
self, bond: ConvertibleBond, pricing_env: PricingEnvironment
|
|
399
|
+
) -> float:
|
|
400
|
+
"""
|
|
401
|
+
Calculate floor bond (straight bond) price in a given environment.
|
|
402
|
+
|
|
403
|
+
The floor bond is the value of the convertible assuming no conversion
|
|
404
|
+
and no exercise of call/put options. It represents the investment
|
|
405
|
+
value floor of the convertible bond.
|
|
406
|
+
|
|
407
|
+
Cashflows are discounted at the risky rate (risk-free + credit spread).
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
bond: Convertible bond product
|
|
411
|
+
pricing_env: Pricing environment to use
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
Floor bond dirty price
|
|
415
|
+
"""
|
|
416
|
+
valuation_date = pricing_env.valuation_date
|
|
417
|
+
|
|
418
|
+
# Check if bond has matured
|
|
419
|
+
if bond.is_expired(valuation_date):
|
|
420
|
+
return 0.0
|
|
421
|
+
|
|
422
|
+
# Get future cashflows
|
|
423
|
+
cashflows = bond.get_cashflows(valuation_date)
|
|
424
|
+
|
|
425
|
+
if not cashflows:
|
|
426
|
+
return 0.0
|
|
427
|
+
|
|
428
|
+
# Get credit spread from bond
|
|
429
|
+
credit_spread = bond.credit_spread if bond.credit_spread else 0.0
|
|
430
|
+
|
|
431
|
+
# Discount each cashflow at risky rate
|
|
432
|
+
pv = 0.0
|
|
433
|
+
for cf in cashflows:
|
|
434
|
+
time_to_payment = (cf.payment_date - valuation_date).days / 365.0
|
|
435
|
+
|
|
436
|
+
if time_to_payment < 0:
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
# Risk-free discount factor from curve; apply spread as parallel shift
|
|
440
|
+
df = pricing_env.get_discount_factor(time_to_payment)
|
|
441
|
+
risky_df = df * math.exp(-credit_spread * time_to_payment)
|
|
442
|
+
pv += cf.amount * risky_df
|
|
443
|
+
|
|
444
|
+
return pv
|
|
445
|
+
|
|
446
|
+
def floor_bond_price(self, bond: ConvertibleBond) -> float:
|
|
447
|
+
"""
|
|
448
|
+
Calculate the floor bond (straight bond) price.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
bond: Convertible bond product
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
Floor bond dirty price
|
|
455
|
+
"""
|
|
456
|
+
return self._floor_bond_price_with_env(bond, self.pricing_env)
|
|
457
|
+
|
|
458
|
+
def floor_bond_dv01(self, bond: ConvertibleBond) -> float:
|
|
459
|
+
"""
|
|
460
|
+
Calculate DV01 of the floor bond.
|
|
461
|
+
|
|
462
|
+
DV01 is the price change for a 1 basis point parallel increase in rates.
|
|
463
|
+
Uses numerical bumping to remain consistent under non-flat curves.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
bond: Convertible bond product
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
Floor bond DV01 (positive value, price decreases when rates rise)
|
|
470
|
+
"""
|
|
471
|
+
rate_bump = 0.0001
|
|
472
|
+
base_price = self.floor_bond_price(bond)
|
|
473
|
+
if base_price == 0.0:
|
|
474
|
+
return 0.0
|
|
475
|
+
|
|
476
|
+
env_up = deepcopy(self.pricing_env)
|
|
477
|
+
env_up.rate_curve = ParallelShiftRateCurve(
|
|
478
|
+
self.pricing_env.rate_curve, shift=rate_bump
|
|
479
|
+
)
|
|
480
|
+
price_up = self._floor_bond_price_with_env(bond, env_up)
|
|
481
|
+
|
|
482
|
+
env_down = deepcopy(self.pricing_env)
|
|
483
|
+
env_down.rate_curve = ParallelShiftRateCurve(
|
|
484
|
+
self.pricing_env.rate_curve, shift=-rate_bump
|
|
485
|
+
)
|
|
486
|
+
price_down = self._floor_bond_price_with_env(bond, env_down)
|
|
487
|
+
|
|
488
|
+
return (price_down - price_up) / 2.0
|
|
489
|
+
|
|
490
|
+
def floor_bond_cs01(self, bond: ConvertibleBond) -> float:
|
|
491
|
+
"""
|
|
492
|
+
Calculate CS01 of the floor bond.
|
|
493
|
+
|
|
494
|
+
CS01 is the price change for a 1 basis point increase in credit spread.
|
|
495
|
+
For the floor bond, CS01 equals DV01 since both rate and spread
|
|
496
|
+
affect discounting identically (discount at r + s).
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
bond: Convertible bond product
|
|
500
|
+
|
|
501
|
+
Returns:
|
|
502
|
+
Floor bond CS01 (equals DV01)
|
|
503
|
+
"""
|
|
504
|
+
# For floor bond, CS01 = DV01 since both enter the discount factor
|
|
505
|
+
return self.floor_bond_dv01(bond)
|
|
506
|
+
|
|
507
|
+
def floor_bond_duration(self, bond: ConvertibleBond) -> float:
|
|
508
|
+
"""
|
|
509
|
+
Calculate modified duration of the floor bond.
|
|
510
|
+
|
|
511
|
+
Derived from DV01: Duration = DV01 / (Price * 0.0001).
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
bond: Convertible bond product
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Floor bond modified duration
|
|
518
|
+
"""
|
|
519
|
+
price = self.floor_bond_price(bond)
|
|
520
|
+
if price == 0.0:
|
|
521
|
+
return 0.0
|
|
522
|
+
dv01_value = self.floor_bond_dv01(bond)
|
|
523
|
+
return dv01_value / (price * 0.0001)
|
|
524
|
+
|
|
525
|
+
def floor_bond_convexity(self, bond: ConvertibleBond) -> float:
|
|
526
|
+
"""
|
|
527
|
+
Calculate convexity of the floor bond.
|
|
528
|
+
|
|
529
|
+
Convexity measures the curvature of the price-yield relationship.
|
|
530
|
+
|
|
531
|
+
Args:
|
|
532
|
+
bond: Convertible bond product
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
Floor bond convexity
|
|
536
|
+
"""
|
|
537
|
+
rate_bump = 0.0001
|
|
538
|
+
base_price = self.floor_bond_price(bond)
|
|
539
|
+
if base_price == 0.0:
|
|
540
|
+
return 0.0
|
|
541
|
+
|
|
542
|
+
env_up = deepcopy(self.pricing_env)
|
|
543
|
+
env_up.rate_curve = ParallelShiftRateCurve(
|
|
544
|
+
self.pricing_env.rate_curve, shift=rate_bump
|
|
545
|
+
)
|
|
546
|
+
price_up = self._floor_bond_price_with_env(bond, env_up)
|
|
547
|
+
|
|
548
|
+
env_down = deepcopy(self.pricing_env)
|
|
549
|
+
env_down.rate_curve = ParallelShiftRateCurve(
|
|
550
|
+
self.pricing_env.rate_curve, shift=-rate_bump
|
|
551
|
+
)
|
|
552
|
+
price_down = self._floor_bond_price_with_env(bond, env_down)
|
|
553
|
+
|
|
554
|
+
return (price_up + price_down - 2.0 * base_price) / (
|
|
555
|
+
base_price * rate_bump * rate_bump
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
# =========================================================================
|
|
559
|
+
# Convertible Bond Risk Metrics (Numerical)
|
|
560
|
+
# =========================================================================
|
|
561
|
+
|
|
562
|
+
def dv01(self, bond: ConvertibleBond) -> float:
|
|
563
|
+
"""
|
|
564
|
+
Calculate DV01 of the convertible bond.
|
|
565
|
+
|
|
566
|
+
Uses numerical rate bumping since the convertible has embedded options.
|
|
567
|
+
Only the risk-free rate is bumped, isolating interest rate risk.
|
|
568
|
+
|
|
569
|
+
Args:
|
|
570
|
+
bond: Convertible bond product
|
|
571
|
+
|
|
572
|
+
Returns:
|
|
573
|
+
Convertible DV01 (positive value, price decreases when rates rise)
|
|
574
|
+
"""
|
|
575
|
+
rate_bump = 0.0001 # 1 basis point
|
|
576
|
+
|
|
577
|
+
# Price with rate bumped up
|
|
578
|
+
env_up = deepcopy(self.pricing_env)
|
|
579
|
+
env_up.rate_curve = ParallelShiftRateCurve(
|
|
580
|
+
self.pricing_env.rate_curve, shift=rate_bump
|
|
581
|
+
)
|
|
582
|
+
engine_up = self._create_bumped_engine(env_up)
|
|
583
|
+
price_up = engine_up.price(bond)
|
|
584
|
+
|
|
585
|
+
# Price with rate bumped down
|
|
586
|
+
env_down = deepcopy(self.pricing_env)
|
|
587
|
+
env_down.rate_curve = ParallelShiftRateCurve(
|
|
588
|
+
self.pricing_env.rate_curve, shift=-rate_bump
|
|
589
|
+
)
|
|
590
|
+
engine_down = self._create_bumped_engine(env_down)
|
|
591
|
+
price_down = engine_down.price(bond)
|
|
592
|
+
|
|
593
|
+
# DV01 = (price_down - price_up) / 2 (central difference)
|
|
594
|
+
# Note: price falls when rate rises, so DV01 is positive
|
|
595
|
+
return (price_down - price_up) / 2
|
|
596
|
+
|
|
597
|
+
def cs01(self, bond: ConvertibleBond) -> float:
|
|
598
|
+
"""
|
|
599
|
+
Calculate CS01 of the convertible bond.
|
|
600
|
+
|
|
601
|
+
Uses numerical credit bumping.
|
|
602
|
+
|
|
603
|
+
- For BINOMIAL_GS: bumps credit_spread directly (GS credit-adjusted discounting).
|
|
604
|
+
- For TRINOMIAL_HW / JUMP_DIFFUSION / TF: bumps hazard_rate using an
|
|
605
|
+
approximate mapping from spread shift to intensity shift:
|
|
606
|
+
d(lambda) ~= d(spread) / (1 - recovery)
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
bond: Convertible bond product
|
|
610
|
+
|
|
611
|
+
Returns:
|
|
612
|
+
Convertible CS01 (positive value, price decreases when spread rises)
|
|
613
|
+
"""
|
|
614
|
+
spread_bump = 0.0001 # 1 basis point
|
|
615
|
+
|
|
616
|
+
if self.method == ConvertibleBondMethod.BINOMIAL_GS:
|
|
617
|
+
# Create bond with bumped spread up
|
|
618
|
+
base_spread = bond.credit_spread if bond.credit_spread else 0.0
|
|
619
|
+
bond_up = self._create_spread_bumped_bond(
|
|
620
|
+
bond, base_spread + spread_bump
|
|
621
|
+
)
|
|
622
|
+
price_up = self.price(bond_up)
|
|
623
|
+
|
|
624
|
+
# Create bond with bumped spread down
|
|
625
|
+
bond_down = self._create_spread_bumped_bond(
|
|
626
|
+
bond, max(0.0, base_spread - spread_bump)
|
|
627
|
+
)
|
|
628
|
+
price_down = self.price(bond_down)
|
|
629
|
+
|
|
630
|
+
# CS01 = (price_down - price_up) / 2 (central difference)
|
|
631
|
+
return (price_down - price_up) / 2
|
|
632
|
+
|
|
633
|
+
# Hazard-based models: bump hazard_rate
|
|
634
|
+
recovery = bond.recovery_rate if bond.recovery_rate is not None else 0.0
|
|
635
|
+
denom = max(1e-8, 1.0 - recovery)
|
|
636
|
+
hazard_bump = spread_bump / denom
|
|
637
|
+
|
|
638
|
+
base_hazard = bond.hazard_rate if bond.hazard_rate else 0.0
|
|
639
|
+
bond_up = self._create_hazard_bumped_bond(
|
|
640
|
+
bond, base_hazard + hazard_bump
|
|
641
|
+
)
|
|
642
|
+
price_up = self.price(bond_up)
|
|
643
|
+
|
|
644
|
+
bond_down = self._create_hazard_bumped_bond(
|
|
645
|
+
bond, max(0.0, base_hazard - hazard_bump)
|
|
646
|
+
)
|
|
647
|
+
price_down = self.price(bond_down)
|
|
648
|
+
|
|
649
|
+
# CS01 = (price_down - price_up) / 2 (central difference)
|
|
650
|
+
return (price_down - price_up) / 2
|
|
651
|
+
|
|
652
|
+
def modified_duration(self, bond: ConvertibleBond) -> float:
|
|
653
|
+
"""
|
|
654
|
+
Calculate modified duration of the convertible bond.
|
|
655
|
+
|
|
656
|
+
Derived from DV01: Duration = DV01 / (Price * 0.0001)
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
bond: Convertible bond product
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
Convertible modified duration
|
|
663
|
+
"""
|
|
664
|
+
price = self.price(bond)
|
|
665
|
+
if price == 0:
|
|
666
|
+
return 0.0
|
|
667
|
+
|
|
668
|
+
dv01_value = self.dv01(bond)
|
|
669
|
+
return dv01_value / (price * 0.0001)
|
|
670
|
+
|
|
671
|
+
def convexity(self, bond: ConvertibleBond) -> float:
|
|
672
|
+
"""
|
|
673
|
+
Calculate convexity of the convertible bond.
|
|
674
|
+
|
|
675
|
+
Uses central difference with rate bumps.
|
|
676
|
+
|
|
677
|
+
Args:
|
|
678
|
+
bond: Convertible bond product
|
|
679
|
+
|
|
680
|
+
Returns:
|
|
681
|
+
Convertible convexity
|
|
682
|
+
"""
|
|
683
|
+
rate_bump = 0.0001 # 1 basis point
|
|
684
|
+
base_price = self.price(bond)
|
|
685
|
+
|
|
686
|
+
if base_price == 0:
|
|
687
|
+
return 0.0
|
|
688
|
+
|
|
689
|
+
# Price with rate bumped up
|
|
690
|
+
env_up = deepcopy(self.pricing_env)
|
|
691
|
+
env_up.rate_curve = ParallelShiftRateCurve(
|
|
692
|
+
self.pricing_env.rate_curve, shift=rate_bump
|
|
693
|
+
)
|
|
694
|
+
engine_up = self._create_bumped_engine(env_up)
|
|
695
|
+
price_up = engine_up.price(bond)
|
|
696
|
+
|
|
697
|
+
# Price with rate bumped down
|
|
698
|
+
env_down = deepcopy(self.pricing_env)
|
|
699
|
+
env_down.rate_curve = ParallelShiftRateCurve(
|
|
700
|
+
self.pricing_env.rate_curve, shift=-rate_bump
|
|
701
|
+
)
|
|
702
|
+
engine_down = self._create_bumped_engine(env_down)
|
|
703
|
+
price_down = engine_down.price(bond)
|
|
704
|
+
|
|
705
|
+
# Convexity = (P_up + P_down - 2*P_base) / (P_base * bump^2)
|
|
706
|
+
return (price_up + price_down - 2 * base_price) / (base_price * rate_bump * rate_bump)
|
|
707
|
+
|
|
708
|
+
def _create_bumped_engine(self, bumped_env: PricingEnvironment):
|
|
709
|
+
"""
|
|
710
|
+
Create a new engine instance with a bumped pricing environment.
|
|
711
|
+
|
|
712
|
+
Args:
|
|
713
|
+
bumped_env: Pricing environment with bumped parameters
|
|
714
|
+
|
|
715
|
+
Returns:
|
|
716
|
+
New engine instance
|
|
717
|
+
"""
|
|
718
|
+
if self.method == ConvertibleBondMethod.BINOMIAL_GS:
|
|
719
|
+
return ConvertibleBondBinomialEngine(bumped_env, self.tree_params)
|
|
720
|
+
elif self.method == ConvertibleBondMethod.TRINOMIAL_HW:
|
|
721
|
+
return ConvertibleBondTrinomialEngine(bumped_env, self.tree_params)
|
|
722
|
+
elif self.method == ConvertibleBondMethod.JUMP_DIFFUSION:
|
|
723
|
+
params = self.pde_params or ConvertibleBondPDEParams()
|
|
724
|
+
return ConvertibleBondJumpDiffusionEngine(bumped_env, params)
|
|
725
|
+
elif self.method == ConvertibleBondMethod.TF:
|
|
726
|
+
params = self.pde_params or ConvertibleBondPDEParams()
|
|
727
|
+
return ConvertibleBondTFEngine(bumped_env, params)
|
|
728
|
+
else:
|
|
729
|
+
raise ValidationError(f"Unsupported method: {self.method}")
|
|
730
|
+
|
|
731
|
+
def _create_spread_bumped_bond(
|
|
732
|
+
self, bond: ConvertibleBond, new_spread: float
|
|
733
|
+
) -> ConvertibleBond:
|
|
734
|
+
"""
|
|
735
|
+
Create a copy of the bond with a bumped credit spread.
|
|
736
|
+
|
|
737
|
+
Args:
|
|
738
|
+
bond: Original convertible bond
|
|
739
|
+
new_spread: New credit spread value
|
|
740
|
+
|
|
741
|
+
Returns:
|
|
742
|
+
New bond with modified credit spread
|
|
743
|
+
"""
|
|
744
|
+
return ConvertibleBond(
|
|
745
|
+
issue_date=bond.issue_date,
|
|
746
|
+
maturity_date=bond.maturity_date,
|
|
747
|
+
face_value=bond.face_value,
|
|
748
|
+
coupon_rate=bond.coupon_rate,
|
|
749
|
+
conversion_ratio=bond.conversion_ratio,
|
|
750
|
+
conversion_price=bond.conversion_price,
|
|
751
|
+
payment_frequency=bond.payment_frequency,
|
|
752
|
+
day_count_convention=bond.day_count_convention,
|
|
753
|
+
conversion_start_date=bond.conversion_start_date,
|
|
754
|
+
conversion_end_date=bond.conversion_end_date,
|
|
755
|
+
call_schedule=bond.call_schedule,
|
|
756
|
+
put_schedule=bond.put_schedule,
|
|
757
|
+
credit_spread=new_spread,
|
|
758
|
+
hazard_rate=bond.hazard_rate,
|
|
759
|
+
recovery_rate=bond.recovery_rate,
|
|
760
|
+
stock_jump_on_default=bond.stock_jump_on_default,
|
|
761
|
+
continuous_dividend_yield=bond.continuous_dividend_yield,
|
|
762
|
+
discrete_dividends=bond.discrete_dividends,
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
def _create_hazard_bumped_bond(
|
|
766
|
+
self, bond: ConvertibleBond, new_hazard_rate: float
|
|
767
|
+
) -> ConvertibleBond:
|
|
768
|
+
"""
|
|
769
|
+
Create a copy of the bond with a bumped hazard rate.
|
|
770
|
+
|
|
771
|
+
Args:
|
|
772
|
+
bond: Original convertible bond
|
|
773
|
+
new_hazard_rate: New hazard rate value
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
New bond with modified hazard rate
|
|
777
|
+
"""
|
|
778
|
+
return ConvertibleBond(
|
|
779
|
+
issue_date=bond.issue_date,
|
|
780
|
+
maturity_date=bond.maturity_date,
|
|
781
|
+
face_value=bond.face_value,
|
|
782
|
+
coupon_rate=bond.coupon_rate,
|
|
783
|
+
conversion_ratio=bond.conversion_ratio,
|
|
784
|
+
conversion_price=bond.conversion_price,
|
|
785
|
+
payment_frequency=bond.payment_frequency,
|
|
786
|
+
day_count_convention=bond.day_count_convention,
|
|
787
|
+
conversion_start_date=bond.conversion_start_date,
|
|
788
|
+
conversion_end_date=bond.conversion_end_date,
|
|
789
|
+
call_schedule=bond.call_schedule,
|
|
790
|
+
put_schedule=bond.put_schedule,
|
|
791
|
+
credit_spread=bond.credit_spread,
|
|
792
|
+
hazard_rate=new_hazard_rate,
|
|
793
|
+
recovery_rate=bond.recovery_rate,
|
|
794
|
+
stock_jump_on_default=bond.stock_jump_on_default,
|
|
795
|
+
continuous_dividend_yield=bond.continuous_dividend_yield,
|
|
796
|
+
discrete_dividends=bond.discrete_dividends,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
def __repr__(self):
|
|
800
|
+
return f"ConvertibleBondEngine(method={self.method.value})"
|