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,546 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tsiveriotis-Fernandes decomposition PDE engine for convertible bond pricing.
|
|
3
|
+
|
|
4
|
+
Implements the TF model which decomposes the convertible bond value into
|
|
5
|
+
equity-like and debt-like components, each discounted at appropriate rates.
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Dict, Optional, Tuple
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from scipy import sparse
|
|
14
|
+
from scipy.sparse.linalg import spsolve
|
|
15
|
+
|
|
16
|
+
from quantark.asset.bond.product.convertible.convertible_bond import ConvertibleBond
|
|
17
|
+
from quantark.asset.bond.engine.pde.convertible.pde_params import ConvertibleBondPDEParams
|
|
18
|
+
from quantark.priceenv import PricingEnvironment
|
|
19
|
+
from quantark.util.exceptions import ValidationError, PricingError
|
|
20
|
+
from quantark.util.numerical import Tolerance, safe_exp, safe_sqrt, safe_log, is_zero
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ConvertibleBondTFResult:
|
|
25
|
+
"""
|
|
26
|
+
Result container for Tsiveriotis-Fernandes PDE engine pricing.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
price: Clean price of the convertible bond
|
|
30
|
+
dirty_price: Dirty price including accrued interest
|
|
31
|
+
equity_component: Equity-like component (u) - value conditional on conversion
|
|
32
|
+
bond_component: Bond-like component (v) - value conditional on redemption
|
|
33
|
+
delta: Price sensitivity to stock price
|
|
34
|
+
gamma: Second derivative of price with respect to stock
|
|
35
|
+
theta: Time decay (daily)
|
|
36
|
+
conversion_probability: Risk-neutral probability of eventual conversion
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
price: float
|
|
40
|
+
dirty_price: float
|
|
41
|
+
equity_component: float
|
|
42
|
+
bond_component: float
|
|
43
|
+
delta: float = 0.0
|
|
44
|
+
gamma: float = 0.0
|
|
45
|
+
theta: float = 0.0
|
|
46
|
+
conversion_probability: float = 0.0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ConvertibleBondTFEngine:
|
|
50
|
+
"""
|
|
51
|
+
Tsiveriotis-Fernandes decomposition model for convertible bonds.
|
|
52
|
+
|
|
53
|
+
This engine implements the TF model which splits the convertible bond
|
|
54
|
+
value V into two components:
|
|
55
|
+
V = u + v
|
|
56
|
+
|
|
57
|
+
where:
|
|
58
|
+
u = equity component (discounted at risk-free rate r)
|
|
59
|
+
v = bond/debt component (discounted at risky rate r + credit_spread)
|
|
60
|
+
|
|
61
|
+
The coupled PDE system is:
|
|
62
|
+
u_t + 0.5*sigma^2*S^2*u_SS + (r-q)*S*u_S - r*u = 0
|
|
63
|
+
v_t + 0.5*sigma^2*S^2*v_SS + (r-q)*S*v_S - (r+credit_spread)*v = 0
|
|
64
|
+
|
|
65
|
+
with boundary conditions:
|
|
66
|
+
- At conversion: u = conversion_value, v = 0
|
|
67
|
+
- At redemption: u = 0, v = face_value
|
|
68
|
+
- At maturity: u = max(0, conversion_value - face_value), v = min(face_value, conversion_value)
|
|
69
|
+
|
|
70
|
+
This decomposition is particularly useful for analyzing the COCB
|
|
71
|
+
(cash-only component of the bond) which is just v.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
pricing_env: PricingEnvironment,
|
|
77
|
+
params: Optional[ConvertibleBondPDEParams] = None,
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Initialize the TF PDE engine.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
pricing_env: Pricing environment with market data
|
|
84
|
+
params: PDE configuration parameters (optional)
|
|
85
|
+
"""
|
|
86
|
+
if pricing_env is None:
|
|
87
|
+
raise ValidationError("Pricing environment is required")
|
|
88
|
+
|
|
89
|
+
self.pricing_env = pricing_env
|
|
90
|
+
self.params = params if params is not None else ConvertibleBondPDEParams()
|
|
91
|
+
|
|
92
|
+
def price(self, bond: ConvertibleBond) -> float:
|
|
93
|
+
"""
|
|
94
|
+
Calculate the clean price of the convertible bond.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
bond: Convertible bond to price
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Clean price of the bond
|
|
101
|
+
"""
|
|
102
|
+
result = self.price_with_details(bond)
|
|
103
|
+
return result.price
|
|
104
|
+
|
|
105
|
+
def price_with_details(
|
|
106
|
+
self, bond: ConvertibleBond
|
|
107
|
+
) -> ConvertibleBondTFResult:
|
|
108
|
+
"""
|
|
109
|
+
Calculate price with detailed results including component decomposition.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
bond: Convertible bond to price
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
ConvertibleBondTFResult with full pricing details and decomposition
|
|
116
|
+
"""
|
|
117
|
+
valuation_date = self.pricing_env.valuation_date
|
|
118
|
+
|
|
119
|
+
# Validate inputs
|
|
120
|
+
if bond.is_expired(valuation_date):
|
|
121
|
+
raise PricingError("Cannot price expired bond")
|
|
122
|
+
|
|
123
|
+
# Get market data
|
|
124
|
+
spot = self.pricing_env.spot
|
|
125
|
+
T = bond.time_to_maturity(valuation_date)
|
|
126
|
+
|
|
127
|
+
# Credit parameters
|
|
128
|
+
credit_spread = bond.credit_spread
|
|
129
|
+
|
|
130
|
+
# Dividend yield
|
|
131
|
+
q = bond.continuous_dividend_yield
|
|
132
|
+
|
|
133
|
+
# Build grid
|
|
134
|
+
S_min = spot * self.params.min_stock_multiple
|
|
135
|
+
S_max = spot * self.params.max_stock_multiple
|
|
136
|
+
N_s = self.params.num_space_steps
|
|
137
|
+
N_t = self.params.num_time_steps
|
|
138
|
+
dt = T / N_t
|
|
139
|
+
|
|
140
|
+
# Use log-space grid
|
|
141
|
+
log_S_min = safe_log(S_min)
|
|
142
|
+
log_S_max = safe_log(S_max)
|
|
143
|
+
log_S = np.linspace(log_S_min, log_S_max, N_s + 1)
|
|
144
|
+
S = np.exp(log_S)
|
|
145
|
+
|
|
146
|
+
# Initialize solutions for both components
|
|
147
|
+
u, v = self._terminal_condition(bond, S)
|
|
148
|
+
P = self._terminal_conversion_probability(bond, S)
|
|
149
|
+
|
|
150
|
+
# Get coupon schedule
|
|
151
|
+
coupon_schedule = self._build_coupon_schedule(bond, valuation_date)
|
|
152
|
+
|
|
153
|
+
# Time stepping - backward from T to 0
|
|
154
|
+
# All times are measured in years from valuation date
|
|
155
|
+
for n in range(N_t - 1, -1, -1):
|
|
156
|
+
t = n * dt # Current time (years from valuation)
|
|
157
|
+
t_next = t + dt # End of this step (closer to maturity)
|
|
158
|
+
node_date = valuation_date + timedelta(days=int(t * 365))
|
|
159
|
+
|
|
160
|
+
# Use Rannacher smoothing for first few steps
|
|
161
|
+
use_implicit = n >= N_t - self.params.rannacher_steps
|
|
162
|
+
|
|
163
|
+
# Query time-local forward rate for this step
|
|
164
|
+
r_local = self.pricing_env.rate_curve.get_forward_rate(t, t_next)
|
|
165
|
+
|
|
166
|
+
# Query time-local effective volatility for this step
|
|
167
|
+
vol_local = self.pricing_env.get_step_volatility(spot, t, t_next)
|
|
168
|
+
|
|
169
|
+
# Risky discount rate for bond component
|
|
170
|
+
r_risky_local = r_local + credit_spread
|
|
171
|
+
|
|
172
|
+
# Solve for equity component u (discounted at r)
|
|
173
|
+
A_u, b_u = self._build_matrices(
|
|
174
|
+
S, u, r_local, q, vol_local, r_local, dt, use_implicit
|
|
175
|
+
)
|
|
176
|
+
u = spsolve(A_u.tocsr(), b_u)
|
|
177
|
+
|
|
178
|
+
# Apply coupon payments (jump condition) to the bond component's
|
|
179
|
+
# known later-time state (coupons paid only if not converted).
|
|
180
|
+
coupon_amount = 0.0
|
|
181
|
+
for ct, ca in coupon_schedule:
|
|
182
|
+
if t < ct <= t_next:
|
|
183
|
+
coupon_amount += ca
|
|
184
|
+
if coupon_amount > 0.0:
|
|
185
|
+
v = v + coupon_amount
|
|
186
|
+
|
|
187
|
+
# Solve for bond component v (discounted at r + credit_spread)
|
|
188
|
+
A_v, b_v = self._build_matrices(
|
|
189
|
+
S, v, r_local, q, vol_local, r_risky_local, dt, use_implicit
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
v = spsolve(A_v.tocsr(), b_v)
|
|
193
|
+
|
|
194
|
+
conversion_possible = bond.conversion_end_date >= node_date
|
|
195
|
+
P[0] = 0.0
|
|
196
|
+
P[-1] = 1.0 if conversion_possible else 0.0
|
|
197
|
+
A_p, b_p = self._build_matrices(
|
|
198
|
+
S, P, r_local, q, vol_local, 0.0, dt, use_implicit
|
|
199
|
+
)
|
|
200
|
+
P = spsolve(A_p.tocsr(), b_p)
|
|
201
|
+
P = np.clip(P, 0.0, 1.0)
|
|
202
|
+
|
|
203
|
+
# Apply constraints and update components and probability
|
|
204
|
+
u, v, P = self._apply_constraints(bond, S, u, v, node_date, P)
|
|
205
|
+
P = np.clip(P, 0.0, 1.0)
|
|
206
|
+
|
|
207
|
+
# Total value
|
|
208
|
+
V = u + v
|
|
209
|
+
|
|
210
|
+
# Interpolate to get values at spot
|
|
211
|
+
spot_idx = np.searchsorted(S, spot)
|
|
212
|
+
if spot_idx == 0:
|
|
213
|
+
u_spot = u[0]
|
|
214
|
+
v_spot = v[0]
|
|
215
|
+
V_spot = V[0]
|
|
216
|
+
conv_prob = P[0]
|
|
217
|
+
elif spot_idx >= len(S):
|
|
218
|
+
u_spot = u[-1]
|
|
219
|
+
v_spot = v[-1]
|
|
220
|
+
V_spot = V[-1]
|
|
221
|
+
conv_prob = P[-1]
|
|
222
|
+
else:
|
|
223
|
+
w = (spot - S[spot_idx - 1]) / (S[spot_idx] - S[spot_idx - 1])
|
|
224
|
+
u_spot = (1 - w) * u[spot_idx - 1] + w * u[spot_idx]
|
|
225
|
+
v_spot = (1 - w) * v[spot_idx - 1] + w * v[spot_idx]
|
|
226
|
+
V_spot = (1 - w) * V[spot_idx - 1] + w * V[spot_idx]
|
|
227
|
+
conv_prob = (1 - w) * P[spot_idx - 1] + w * P[spot_idx]
|
|
228
|
+
|
|
229
|
+
dirty_price = V_spot
|
|
230
|
+
conv_prob = float(np.clip(conv_prob, 0.0, 1.0))
|
|
231
|
+
|
|
232
|
+
# Calculate Greeks
|
|
233
|
+
delta, gamma = self._calculate_greeks(S, V, spot, spot_idx)
|
|
234
|
+
|
|
235
|
+
# Calculate accrued interest
|
|
236
|
+
accrued = bond.calculate_accrued_interest(valuation_date)
|
|
237
|
+
clean_price = dirty_price - accrued
|
|
238
|
+
|
|
239
|
+
return ConvertibleBondTFResult(
|
|
240
|
+
price=clean_price,
|
|
241
|
+
dirty_price=dirty_price,
|
|
242
|
+
equity_component=u_spot,
|
|
243
|
+
bond_component=v_spot,
|
|
244
|
+
delta=delta,
|
|
245
|
+
gamma=gamma,
|
|
246
|
+
theta=0.0,
|
|
247
|
+
conversion_probability=conv_prob,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def _terminal_condition(
|
|
251
|
+
self, bond: ConvertibleBond, S: np.ndarray
|
|
252
|
+
) -> Tuple[np.ndarray, np.ndarray]:
|
|
253
|
+
"""
|
|
254
|
+
Compute terminal conditions for u and v at maturity.
|
|
255
|
+
|
|
256
|
+
At maturity:
|
|
257
|
+
- If conversion: u = conversion_value, v = 0
|
|
258
|
+
- If redemption: u = 0, v = face_value
|
|
259
|
+
- Decision: max(face_value, conversion_value)
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
bond: Convertible bond
|
|
263
|
+
S: Stock price grid
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
Tuple of (u, v) arrays
|
|
267
|
+
"""
|
|
268
|
+
face_value = bond.face_value
|
|
269
|
+
conversion_value = bond.conversion_ratio * S
|
|
270
|
+
|
|
271
|
+
# u = equity component (value from conversion)
|
|
272
|
+
# v = bond component (value from redemption)
|
|
273
|
+
u = np.zeros_like(S)
|
|
274
|
+
v = np.zeros_like(S)
|
|
275
|
+
|
|
276
|
+
if not bond.is_convertible_at(bond.maturity_date):
|
|
277
|
+
v[:] = face_value
|
|
278
|
+
return u, v
|
|
279
|
+
|
|
280
|
+
for i in range(len(S)):
|
|
281
|
+
if conversion_value[i] > face_value:
|
|
282
|
+
u[i] = conversion_value[i]
|
|
283
|
+
v[i] = 0.0
|
|
284
|
+
else:
|
|
285
|
+
u[i] = 0.0
|
|
286
|
+
v[i] = face_value
|
|
287
|
+
|
|
288
|
+
return u, v
|
|
289
|
+
|
|
290
|
+
def _terminal_conversion_probability(
|
|
291
|
+
self, bond: ConvertibleBond, S: np.ndarray
|
|
292
|
+
) -> np.ndarray:
|
|
293
|
+
"""
|
|
294
|
+
Terminal condition for the eventual conversion probability.
|
|
295
|
+
|
|
296
|
+
At maturity: P = 1 if conversion is optimal and allowed, else 0.
|
|
297
|
+
"""
|
|
298
|
+
if not bond.is_convertible_at(bond.maturity_date):
|
|
299
|
+
return np.zeros_like(S)
|
|
300
|
+
conversion_value = bond.conversion_ratio * S
|
|
301
|
+
return (conversion_value > bond.face_value).astype(float)
|
|
302
|
+
|
|
303
|
+
def _build_matrices(
|
|
304
|
+
self,
|
|
305
|
+
S: np.ndarray,
|
|
306
|
+
V: np.ndarray,
|
|
307
|
+
r: float,
|
|
308
|
+
q: float,
|
|
309
|
+
vol: float,
|
|
310
|
+
discount_rate: float,
|
|
311
|
+
dt: float,
|
|
312
|
+
use_implicit: bool,
|
|
313
|
+
) -> Tuple[sparse.csr_matrix, np.ndarray]:
|
|
314
|
+
"""
|
|
315
|
+
Build finite difference matrices for the PDE.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
S: Stock price grid
|
|
319
|
+
V: Current solution
|
|
320
|
+
r: Risk-free rate (for drift)
|
|
321
|
+
q: Dividend yield
|
|
322
|
+
vol: Volatility
|
|
323
|
+
discount_rate: Rate for discounting (r for u, r+spread for v)
|
|
324
|
+
dt: Time step
|
|
325
|
+
use_implicit: Whether to use fully implicit scheme
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Tuple of (A matrix, b vector) for A*V_new = b
|
|
329
|
+
"""
|
|
330
|
+
N = len(S)
|
|
331
|
+
drift = r - q
|
|
332
|
+
|
|
333
|
+
# Build tridiagonal matrix coefficients
|
|
334
|
+
diag = np.zeros(N)
|
|
335
|
+
lower = np.zeros(N - 1)
|
|
336
|
+
upper = np.zeros(N - 1)
|
|
337
|
+
|
|
338
|
+
for i in range(1, N - 1):
|
|
339
|
+
h_minus = S[i] - S[i - 1]
|
|
340
|
+
h_plus = S[i + 1] - S[i]
|
|
341
|
+
h = 0.5 * (h_minus + h_plus)
|
|
342
|
+
|
|
343
|
+
# Diffusion: 0.5 * sigma^2 * S^2
|
|
344
|
+
D = 0.5 * vol * vol * S[i] * S[i]
|
|
345
|
+
|
|
346
|
+
# Convection: drift * S
|
|
347
|
+
C = drift * S[i]
|
|
348
|
+
|
|
349
|
+
# Reaction: -discount_rate
|
|
350
|
+
R = -discount_rate
|
|
351
|
+
|
|
352
|
+
lower[i - 1] = D / (h_minus * h) - C / (2 * h)
|
|
353
|
+
diag[i] = -2 * D / (h_minus * h_plus) + R
|
|
354
|
+
upper[i] = D / (h_plus * h) + C / (2 * h)
|
|
355
|
+
|
|
356
|
+
# Boundary conditions
|
|
357
|
+
diag[0] = 1.0
|
|
358
|
+
upper[0] = 0.0
|
|
359
|
+
diag[-1] = 1.0
|
|
360
|
+
lower[-1] = 0.0
|
|
361
|
+
|
|
362
|
+
# Scheme selection
|
|
363
|
+
if use_implicit:
|
|
364
|
+
theta_scheme = 1.0
|
|
365
|
+
elif self.params.scheme == "explicit_euler":
|
|
366
|
+
theta_scheme = 0.0
|
|
367
|
+
else: # crank_nicolson
|
|
368
|
+
theta_scheme = 0.5
|
|
369
|
+
|
|
370
|
+
L = sparse.diags(
|
|
371
|
+
[lower, diag, upper], [-1, 0, 1], shape=(N, N), format="csr"
|
|
372
|
+
)
|
|
373
|
+
I = sparse.eye(N, format="csr")
|
|
374
|
+
|
|
375
|
+
A = I - theta_scheme * dt * L
|
|
376
|
+
|
|
377
|
+
if theta_scheme < 1.0:
|
|
378
|
+
A_old = I + (1 - theta_scheme) * dt * L
|
|
379
|
+
b = A_old @ V
|
|
380
|
+
else:
|
|
381
|
+
b = V.copy()
|
|
382
|
+
|
|
383
|
+
# Boundary values will be set by constraints
|
|
384
|
+
b[0] = V[0]
|
|
385
|
+
b[-1] = V[-1]
|
|
386
|
+
|
|
387
|
+
return A, b
|
|
388
|
+
|
|
389
|
+
def _apply_constraints(
|
|
390
|
+
self,
|
|
391
|
+
bond: ConvertibleBond,
|
|
392
|
+
S: np.ndarray,
|
|
393
|
+
u: np.ndarray,
|
|
394
|
+
v: np.ndarray,
|
|
395
|
+
node_date: datetime,
|
|
396
|
+
P: np.ndarray,
|
|
397
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
398
|
+
"""
|
|
399
|
+
Apply early exercise constraints and update component decomposition.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
bond: Convertible bond
|
|
403
|
+
S: Stock price grid
|
|
404
|
+
u: Equity component
|
|
405
|
+
v: Bond component
|
|
406
|
+
node_date: Current date
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Tuple of updated (u, v, conversion_probability)
|
|
410
|
+
"""
|
|
411
|
+
u_new = u.copy()
|
|
412
|
+
v_new = v.copy()
|
|
413
|
+
P_new = P.copy()
|
|
414
|
+
V = u + v # Total value
|
|
415
|
+
|
|
416
|
+
face_value = bond.face_value
|
|
417
|
+
|
|
418
|
+
for i in range(len(S)):
|
|
419
|
+
stock = S[i]
|
|
420
|
+
conversion_value = bond.conversion_ratio * stock
|
|
421
|
+
total_value = V[i]
|
|
422
|
+
|
|
423
|
+
# Track the optimal action
|
|
424
|
+
action = "hold"
|
|
425
|
+
optimal_value = total_value
|
|
426
|
+
|
|
427
|
+
# Check conversion (holder's right)
|
|
428
|
+
if bond.is_convertible_at(node_date):
|
|
429
|
+
if conversion_value >= (optimal_value - Tolerance.PRECISION):
|
|
430
|
+
action = "convert"
|
|
431
|
+
optimal_value = conversion_value
|
|
432
|
+
|
|
433
|
+
# Check call (issuer's right)
|
|
434
|
+
call_price = bond.get_call_price_at(node_date)
|
|
435
|
+
if call_price is not None and bond.is_callable_at(node_date, stock):
|
|
436
|
+
if optimal_value > call_price:
|
|
437
|
+
# Issuer calls; holder chooses max(conversion, call)
|
|
438
|
+
if bond.is_convertible_at(node_date) and conversion_value > call_price:
|
|
439
|
+
action = "convert"
|
|
440
|
+
optimal_value = conversion_value
|
|
441
|
+
else:
|
|
442
|
+
action = "call"
|
|
443
|
+
optimal_value = call_price
|
|
444
|
+
|
|
445
|
+
# Check put (holder's right)
|
|
446
|
+
put_price = bond.get_put_price_at(node_date)
|
|
447
|
+
if put_price is not None:
|
|
448
|
+
if put_price > optimal_value:
|
|
449
|
+
action = "put"
|
|
450
|
+
optimal_value = put_price
|
|
451
|
+
|
|
452
|
+
# Update components based on action
|
|
453
|
+
if action == "convert":
|
|
454
|
+
u_new[i] = conversion_value
|
|
455
|
+
v_new[i] = 0.0
|
|
456
|
+
P_new[i] = 1.0
|
|
457
|
+
elif action == "call":
|
|
458
|
+
u_new[i] = 0.0
|
|
459
|
+
v_new[i] = call_price
|
|
460
|
+
P_new[i] = 0.0
|
|
461
|
+
elif action == "put":
|
|
462
|
+
u_new[i] = 0.0
|
|
463
|
+
v_new[i] = put_price
|
|
464
|
+
P_new[i] = 0.0
|
|
465
|
+
# else: hold - keep current decomposition
|
|
466
|
+
|
|
467
|
+
# Boundary conditions
|
|
468
|
+
# At S=0: pure bond component
|
|
469
|
+
u_new[0] = 0.0
|
|
470
|
+
v_new[0] = bond.recovery_rate * face_value
|
|
471
|
+
P_new[0] = 0.0
|
|
472
|
+
|
|
473
|
+
# At S_max: pure equity component
|
|
474
|
+
u_new[-1] = bond.conversion_ratio * S[-1]
|
|
475
|
+
v_new[-1] = 0.0
|
|
476
|
+
conversion_possible = bond.conversion_end_date >= node_date
|
|
477
|
+
P_new[-1] = 1.0 if conversion_possible else 0.0
|
|
478
|
+
|
|
479
|
+
return u_new, v_new, P_new
|
|
480
|
+
|
|
481
|
+
def _build_coupon_schedule(
|
|
482
|
+
self, bond: ConvertibleBond, valuation_date: datetime
|
|
483
|
+
) -> list:
|
|
484
|
+
"""
|
|
485
|
+
Build list of (time, amount) tuples for coupon payments.
|
|
486
|
+
"""
|
|
487
|
+
T = bond.time_to_maturity(valuation_date)
|
|
488
|
+
schedule = []
|
|
489
|
+
|
|
490
|
+
for cf in bond.get_all_cashflows():
|
|
491
|
+
cf_time = (cf.payment_date - valuation_date).days / 365.0
|
|
492
|
+
if 0 < cf_time <= T:
|
|
493
|
+
coupon_amount = cf.amount
|
|
494
|
+
if cf.payment_date >= bond.maturity_date:
|
|
495
|
+
coupon_amount -= bond.face_value
|
|
496
|
+
if coupon_amount > 0:
|
|
497
|
+
schedule.append((cf_time, coupon_amount))
|
|
498
|
+
|
|
499
|
+
return schedule
|
|
500
|
+
|
|
501
|
+
def _calculate_greeks(
|
|
502
|
+
self,
|
|
503
|
+
S: np.ndarray,
|
|
504
|
+
V: np.ndarray,
|
|
505
|
+
spot: float,
|
|
506
|
+
spot_idx: int,
|
|
507
|
+
) -> Tuple[float, float]:
|
|
508
|
+
"""
|
|
509
|
+
Calculate delta and gamma from the PDE grid.
|
|
510
|
+
"""
|
|
511
|
+
if spot_idx <= 1 or spot_idx >= len(S) - 1:
|
|
512
|
+
return 0.0, 0.0
|
|
513
|
+
|
|
514
|
+
i = spot_idx
|
|
515
|
+
h_minus = S[i] - S[i - 1]
|
|
516
|
+
h_plus = S[i + 1] - S[i]
|
|
517
|
+
|
|
518
|
+
delta = (V[i + 1] - V[i - 1]) / (h_plus + h_minus)
|
|
519
|
+
|
|
520
|
+
gamma = 2.0 * (
|
|
521
|
+
V[i + 1] / (h_plus * (h_plus + h_minus))
|
|
522
|
+
- V[i] / (h_plus * h_minus)
|
|
523
|
+
+ V[i - 1] / (h_minus * (h_plus + h_minus))
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
return delta, gamma
|
|
527
|
+
|
|
528
|
+
def get_cocb(self, bond: ConvertibleBond) -> float:
|
|
529
|
+
"""
|
|
530
|
+
Get the Cash-Only Component of Bond (COCB).
|
|
531
|
+
|
|
532
|
+
The COCB is the bond component v in the TF decomposition,
|
|
533
|
+
which represents the present value of cash flows assuming
|
|
534
|
+
no conversion ever occurs.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
bond: Convertible bond
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
COCB value
|
|
541
|
+
"""
|
|
542
|
+
result = self.price_with_details(bond)
|
|
543
|
+
return result.bond_component
|
|
544
|
+
|
|
545
|
+
def __repr__(self):
|
|
546
|
+
return f"ConvertibleBondTFEngine(scheme={self.params.scheme})"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tree-based pricing engines for bonds.
|
|
3
|
+
"""
|
|
4
|
+
from quantark.asset.bond.engine.tree.convertible import (
|
|
5
|
+
ConvertibleBondTreeParams,
|
|
6
|
+
ConvertibleBondBinomialEngine,
|
|
7
|
+
ConvertibleBondTrinomialEngine,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"ConvertibleBondTreeParams",
|
|
12
|
+
"ConvertibleBondBinomialEngine",
|
|
13
|
+
"ConvertibleBondTrinomialEngine",
|
|
14
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tree-based pricing engines for convertible bonds.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- ConvertibleBondTreeParams: Configuration for tree-based pricing
|
|
6
|
+
- ConvertibleBondBinomialEngine: Goldman Sachs credit-adjusted binomial model
|
|
7
|
+
- ConvertibleBondTrinomialEngine: Hull-White trinomial model with default
|
|
8
|
+
"""
|
|
9
|
+
from quantark.asset.bond.engine.tree.convertible.tree_params import ConvertibleBondTreeParams
|
|
10
|
+
from quantark.asset.bond.engine.tree.convertible.binomial_engine import (
|
|
11
|
+
ConvertibleBondBinomialEngine,
|
|
12
|
+
)
|
|
13
|
+
from quantark.asset.bond.engine.tree.convertible.trinomial_engine import (
|
|
14
|
+
ConvertibleBondTrinomialEngine,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"ConvertibleBondTreeParams",
|
|
19
|
+
"ConvertibleBondBinomialEngine",
|
|
20
|
+
"ConvertibleBondTrinomialEngine",
|
|
21
|
+
]
|