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,703 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core dynamic scenario analysis engine.
|
|
3
|
+
|
|
4
|
+
This module contains the main engine for running dynamic scenario simulations
|
|
5
|
+
with time evolution and optional hedging strategies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional, Dict, Any, List
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
import time
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
|
|
13
|
+
from quantark.portfolio import Portfolio
|
|
14
|
+
from quantark.priceenv import PricingEnvironment
|
|
15
|
+
from quantark.param import (
|
|
16
|
+
SpotQuote,
|
|
17
|
+
FlatVolSurface,
|
|
18
|
+
FlatRateCurve,
|
|
19
|
+
ContinuousDividendYield,
|
|
20
|
+
FlatBasisYield,
|
|
21
|
+
TermStructureDividendYield,
|
|
22
|
+
TermStructureBasisYield,
|
|
23
|
+
)
|
|
24
|
+
from quantark.param.basis.basis_yield import (
|
|
25
|
+
calculate_basis_from_rate_dividend,
|
|
26
|
+
calculate_dividend_from_rate_basis,
|
|
27
|
+
)
|
|
28
|
+
from quantark.asset.equity.riskmeasures import GreeksCalculator
|
|
29
|
+
from quantark.asset.equity.product.deltaone import SpotInstrument, Futures
|
|
30
|
+
from quantark.asset.equity.engine.analytical import DeltaOneEngine
|
|
31
|
+
|
|
32
|
+
from quantark.backtest.strategy.base_strategy import BaseStrategy
|
|
33
|
+
from quantark.backtest.transaction_costs import TransactionCostModel, ZeroCostModel
|
|
34
|
+
|
|
35
|
+
from quantark.dynamicscenario.config import DynamicScenarioConfig
|
|
36
|
+
from quantark.dynamicscenario.path.day_path import DayPath, DayStep, ParameterChange
|
|
37
|
+
from quantark.dynamicscenario.results.dynamic_results import (
|
|
38
|
+
DynamicScenarioResults, DayResult, PositionSnapshot,
|
|
39
|
+
TradeSnapshot, MarketState
|
|
40
|
+
)
|
|
41
|
+
from quantark.stresstest.stress.stress_types import (
|
|
42
|
+
StressType,
|
|
43
|
+
StressLevel,
|
|
44
|
+
BasisDividendRelationshipMode,
|
|
45
|
+
)
|
|
46
|
+
from quantark.util.exceptions import ValidationError
|
|
47
|
+
from quantark.util.numerical import pnl_pct_of_abs_baseline
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DynamicScenarioEngine:
|
|
51
|
+
"""
|
|
52
|
+
Engine for executing dynamic scenario analysis.
|
|
53
|
+
|
|
54
|
+
This is the main entry point for running multi-day scenario simulations.
|
|
55
|
+
It handles:
|
|
56
|
+
- Applying day-by-day market changes to pricing environments
|
|
57
|
+
- Calculating portfolio value and Greeks at each step
|
|
58
|
+
- Optionally executing hedging strategies
|
|
59
|
+
- Recording day-by-day state evolution
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> config = DynamicScenarioConfig(calculate_greeks=True)
|
|
63
|
+
>>> engine = DynamicScenarioEngine(config)
|
|
64
|
+
>>>
|
|
65
|
+
>>> # Create a 5-day rally path
|
|
66
|
+
>>> path = PathLibrary.consecutive_rally(days=5, daily_pct=0.02)
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Run simulation
|
|
69
|
+
>>> results = engine.run(portfolio, path)
|
|
70
|
+
>>> print(results.get_summary())
|
|
71
|
+
|
|
72
|
+
>>> # Run with hedging
|
|
73
|
+
>>> strategy = DeltaNeutralStrategy(delta_threshold=50)
|
|
74
|
+
>>> results = engine.run(portfolio, path, hedge_strategy=strategy)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, config: Optional[DynamicScenarioConfig] = None):
|
|
78
|
+
"""
|
|
79
|
+
Initialize dynamic scenario engine.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
config: Configuration for execution
|
|
83
|
+
"""
|
|
84
|
+
self.config = config or DynamicScenarioConfig()
|
|
85
|
+
self.greeks_calculator = GreeksCalculator() if self.config.calculate_greeks else None
|
|
86
|
+
self._deltaone_engine = DeltaOneEngine()
|
|
87
|
+
|
|
88
|
+
def run(
|
|
89
|
+
self,
|
|
90
|
+
portfolio: Portfolio,
|
|
91
|
+
day_path: DayPath,
|
|
92
|
+
hedge_strategy: Optional[BaseStrategy] = None,
|
|
93
|
+
transaction_cost_model: Optional[TransactionCostModel] = None
|
|
94
|
+
) -> DynamicScenarioResults:
|
|
95
|
+
"""
|
|
96
|
+
Run dynamic scenario simulation.
|
|
97
|
+
|
|
98
|
+
Simulates portfolio evolution through the day path, optionally
|
|
99
|
+
applying hedging strategies at each step.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
portfolio: Portfolio to simulate
|
|
103
|
+
day_path: Day path defining market evolution
|
|
104
|
+
hedge_strategy: Optional hedging strategy (from backtest module)
|
|
105
|
+
transaction_cost_model: Optional transaction cost model
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
DynamicScenarioResults with day-by-day evolution
|
|
109
|
+
|
|
110
|
+
Raises:
|
|
111
|
+
ValidationError: If portfolio or path is invalid
|
|
112
|
+
"""
|
|
113
|
+
# Validate inputs
|
|
114
|
+
if not portfolio or len(portfolio) == 0:
|
|
115
|
+
raise ValidationError("Portfolio must contain at least one position")
|
|
116
|
+
|
|
117
|
+
if not day_path or day_path.num_days == 0:
|
|
118
|
+
raise ValidationError("Day path must have at least one day")
|
|
119
|
+
|
|
120
|
+
start_time = time.time()
|
|
121
|
+
print(f"Starting dynamic scenario: {day_path.name}")
|
|
122
|
+
print(f" Days: {day_path.num_days}")
|
|
123
|
+
print(f" Hedging: {'Yes' if hedge_strategy else 'No'}")
|
|
124
|
+
|
|
125
|
+
# Use zero cost model if none provided
|
|
126
|
+
if transaction_cost_model is None:
|
|
127
|
+
transaction_cost_model = ZeroCostModel()
|
|
128
|
+
|
|
129
|
+
# Create working copy of portfolio
|
|
130
|
+
working_portfolio = self._clone_portfolio(portfolio)
|
|
131
|
+
|
|
132
|
+
# Track state
|
|
133
|
+
baseline_value = working_portfolio.get_portfolio_value()
|
|
134
|
+
cumulative_transaction_costs = 0.0
|
|
135
|
+
total_hedges = 0
|
|
136
|
+
day_results: List[DayResult] = []
|
|
137
|
+
previous_value = baseline_value
|
|
138
|
+
|
|
139
|
+
# Reset strategy if provided
|
|
140
|
+
if hedge_strategy:
|
|
141
|
+
hedge_strategy.reset()
|
|
142
|
+
|
|
143
|
+
# Track hedge positions
|
|
144
|
+
hedge_positions: Dict[str, str] = {} # underlying -> position_id
|
|
145
|
+
|
|
146
|
+
# Run each day
|
|
147
|
+
for day_step in day_path:
|
|
148
|
+
print(f" Processing Day {day_step.day_index}...")
|
|
149
|
+
|
|
150
|
+
# Get date for this day
|
|
151
|
+
day_date = day_path.get_date_for_day(day_step.day_index)
|
|
152
|
+
|
|
153
|
+
# Apply day's market changes
|
|
154
|
+
self._apply_day_changes(working_portfolio, day_step)
|
|
155
|
+
|
|
156
|
+
# Update valuation date if we have dates
|
|
157
|
+
if day_date:
|
|
158
|
+
for env in working_portfolio.pricing_environments.values():
|
|
159
|
+
env.valuation_date = day_date
|
|
160
|
+
|
|
161
|
+
# Calculate portfolio value and Greeks
|
|
162
|
+
portfolio_value = working_portfolio.get_portfolio_value()
|
|
163
|
+
daily_pnl = portfolio_value - previous_value
|
|
164
|
+
cumulative_pnl = portfolio_value - baseline_value
|
|
165
|
+
|
|
166
|
+
portfolio_greeks = {}
|
|
167
|
+
if self.config.calculate_greeks and self.greeks_calculator:
|
|
168
|
+
use_analytical = (self.config.greeks_method == 'analytical')
|
|
169
|
+
portfolio_greeks = working_portfolio.get_portfolio_greeks(
|
|
170
|
+
self.greeks_calculator,
|
|
171
|
+
use_analytical=use_analytical
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Execute hedging if strategy provided
|
|
175
|
+
trades_today: List[TradeSnapshot] = []
|
|
176
|
+
transaction_costs_today = 0.0
|
|
177
|
+
|
|
178
|
+
if hedge_strategy:
|
|
179
|
+
# Get market data for strategy
|
|
180
|
+
market_data = self._get_market_data(working_portfolio)
|
|
181
|
+
|
|
182
|
+
# Call strategy on_step
|
|
183
|
+
current_time = day_date or datetime.now()
|
|
184
|
+
hedge_strategy.on_step(
|
|
185
|
+
current_time=current_time,
|
|
186
|
+
portfolio_greeks=portfolio_greeks,
|
|
187
|
+
market_data=market_data
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Check if hedging needed
|
|
191
|
+
should_hedge = hedge_strategy.should_hedge(
|
|
192
|
+
current_time=current_time,
|
|
193
|
+
portfolio_greeks=portfolio_greeks,
|
|
194
|
+
market_data=market_data
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if should_hedge:
|
|
198
|
+
# Calculate hedge size
|
|
199
|
+
hedge_size = hedge_strategy.calculate_hedge_size(
|
|
200
|
+
current_time=current_time,
|
|
201
|
+
portfolio_greeks=portfolio_greeks,
|
|
202
|
+
market_data=market_data
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
if abs(hedge_size) > 1e-10:
|
|
206
|
+
# Execute hedge for each underlying
|
|
207
|
+
for underlying in working_portfolio.pricing_environments.keys():
|
|
208
|
+
trade, cost = self._execute_hedge(
|
|
209
|
+
portfolio=working_portfolio,
|
|
210
|
+
underlying=underlying,
|
|
211
|
+
hedge_size=hedge_size,
|
|
212
|
+
transaction_cost_model=transaction_cost_model,
|
|
213
|
+
hedge_positions=hedge_positions,
|
|
214
|
+
current_time=current_time
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
if trade:
|
|
218
|
+
trades_today.append(trade)
|
|
219
|
+
transaction_costs_today += cost
|
|
220
|
+
total_hedges += 1
|
|
221
|
+
|
|
222
|
+
# Update strategy
|
|
223
|
+
hedge_strategy.on_hedge_executed(
|
|
224
|
+
current_time=current_time,
|
|
225
|
+
hedge_size=hedge_size,
|
|
226
|
+
hedge_price=market_data.get('spot', 0.0)
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
# Recalculate Greeks after hedge
|
|
230
|
+
if self.config.calculate_greeks and self.greeks_calculator:
|
|
231
|
+
portfolio_greeks = working_portfolio.get_portfolio_greeks(
|
|
232
|
+
self.greeks_calculator,
|
|
233
|
+
use_analytical=use_analytical
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
cumulative_transaction_costs += transaction_costs_today
|
|
237
|
+
net_pnl = cumulative_pnl - cumulative_transaction_costs
|
|
238
|
+
|
|
239
|
+
# Capture position snapshots
|
|
240
|
+
position_snapshots = self._capture_position_snapshots(working_portfolio)
|
|
241
|
+
|
|
242
|
+
# Capture market state
|
|
243
|
+
market_state = self._capture_market_state(working_portfolio)
|
|
244
|
+
|
|
245
|
+
# Create day result
|
|
246
|
+
day_result = DayResult(
|
|
247
|
+
day_index=day_step.day_index,
|
|
248
|
+
date=day_date,
|
|
249
|
+
label=day_step.label,
|
|
250
|
+
portfolio_value=portfolio_value,
|
|
251
|
+
daily_pnl=daily_pnl,
|
|
252
|
+
cumulative_pnl=cumulative_pnl,
|
|
253
|
+
transaction_costs_today=transaction_costs_today,
|
|
254
|
+
cumulative_transaction_costs=cumulative_transaction_costs,
|
|
255
|
+
net_pnl=net_pnl,
|
|
256
|
+
greeks=portfolio_greeks,
|
|
257
|
+
positions=position_snapshots,
|
|
258
|
+
trades=trades_today,
|
|
259
|
+
market_state=market_state,
|
|
260
|
+
)
|
|
261
|
+
day_results.append(day_result)
|
|
262
|
+
|
|
263
|
+
# Update previous value for next iteration
|
|
264
|
+
previous_value = portfolio_value
|
|
265
|
+
|
|
266
|
+
total_time = time.time() - start_time
|
|
267
|
+
|
|
268
|
+
# Build final results
|
|
269
|
+
final_value = working_portfolio.get_portfolio_value()
|
|
270
|
+
|
|
271
|
+
results = DynamicScenarioResults(
|
|
272
|
+
path_name=day_path.name,
|
|
273
|
+
baseline_value=baseline_value,
|
|
274
|
+
final_value=final_value,
|
|
275
|
+
day_results=day_results,
|
|
276
|
+
total_pnl=final_value - baseline_value,
|
|
277
|
+
total_pnl_pct=pnl_pct_of_abs_baseline(final_value - baseline_value, baseline_value),
|
|
278
|
+
total_transaction_costs=cumulative_transaction_costs,
|
|
279
|
+
net_pnl=final_value - baseline_value - cumulative_transaction_costs,
|
|
280
|
+
total_hedges=total_hedges,
|
|
281
|
+
total_execution_time=total_time,
|
|
282
|
+
config_summary=self.config.get_summary(),
|
|
283
|
+
metadata={
|
|
284
|
+
'path_description': day_path.description,
|
|
285
|
+
'hedge_strategy': hedge_strategy.name if hedge_strategy else None,
|
|
286
|
+
}
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
print(f"\nDynamic scenario completed in {total_time:.2f} seconds")
|
|
290
|
+
print(f" Final P&L: ${results.total_pnl:,.2f} ({results.total_pnl_pct:+.2f}%)")
|
|
291
|
+
|
|
292
|
+
return results
|
|
293
|
+
|
|
294
|
+
def _clone_portfolio(self, portfolio: Portfolio) -> Portfolio:
|
|
295
|
+
"""Create a deep copy of portfolio with cloned pricing environments."""
|
|
296
|
+
# Clone pricing environments
|
|
297
|
+
cloned_envs = {}
|
|
298
|
+
for underlying, env in portfolio.pricing_environments.items():
|
|
299
|
+
cloned_envs[underlying] = PricingEnvironment(
|
|
300
|
+
rate_curve=deepcopy(env.rate_curve),
|
|
301
|
+
valuation_date=env.valuation_date,
|
|
302
|
+
spot_quote=deepcopy(env.spot_quote) if env.spot_quote else None,
|
|
303
|
+
vol_surface=deepcopy(env.vol_surface) if env.vol_surface else None,
|
|
304
|
+
div_yield=deepcopy(env.div_yield) if env.div_yield else None,
|
|
305
|
+
basis_yield=deepcopy(env.basis_yield) if env.basis_yield else None,
|
|
306
|
+
day_count_convention=env.day_count_convention,
|
|
307
|
+
bus_days_in_year=env.bus_days_in_year,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
# Create new portfolio with cloned environments
|
|
311
|
+
cloned_portfolio = Portfolio(
|
|
312
|
+
portfolio_name=portfolio.portfolio_name + "_simulation",
|
|
313
|
+
pricing_environments=cloned_envs,
|
|
314
|
+
creation_date=portfolio.creation_date,
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
# Deep copy positions
|
|
318
|
+
cloned_portfolio.positions = deepcopy(portfolio.positions)
|
|
319
|
+
|
|
320
|
+
return cloned_portfolio
|
|
321
|
+
|
|
322
|
+
def _apply_day_changes(self, portfolio: Portfolio, day_step: DayStep) -> None:
|
|
323
|
+
"""Apply a day's market changes to portfolio pricing environments."""
|
|
324
|
+
for change in day_step.changes:
|
|
325
|
+
if change.level == StressLevel.PORTFOLIO:
|
|
326
|
+
# Apply to all underlyings
|
|
327
|
+
for underlying in portfolio.pricing_environments.keys():
|
|
328
|
+
self._apply_parameter_change(
|
|
329
|
+
portfolio.pricing_environments[underlying],
|
|
330
|
+
change,
|
|
331
|
+
portfolio,
|
|
332
|
+
underlying,
|
|
333
|
+
)
|
|
334
|
+
elif change.level == StressLevel.UNDERLYING:
|
|
335
|
+
# Apply to specific underlying
|
|
336
|
+
if change.target in portfolio.pricing_environments:
|
|
337
|
+
self._apply_parameter_change(
|
|
338
|
+
portfolio.pricing_environments[change.target],
|
|
339
|
+
change,
|
|
340
|
+
portfolio,
|
|
341
|
+
change.target,
|
|
342
|
+
)
|
|
343
|
+
# POSITION level would need position-specific environments
|
|
344
|
+
|
|
345
|
+
def _apply_parameter_change(
|
|
346
|
+
self,
|
|
347
|
+
env: PricingEnvironment,
|
|
348
|
+
change: ParameterChange,
|
|
349
|
+
portfolio: Portfolio,
|
|
350
|
+
underlying: str,
|
|
351
|
+
) -> None:
|
|
352
|
+
"""Apply a single parameter change to a pricing environment."""
|
|
353
|
+
param = change.parameter.lower()
|
|
354
|
+
|
|
355
|
+
if param == "spot":
|
|
356
|
+
if env.spot_quote:
|
|
357
|
+
current = env.spot_quote.spot
|
|
358
|
+
new_value = change.apply(current)
|
|
359
|
+
if new_value <= 0:
|
|
360
|
+
raise ValidationError(f"Spot cannot be <= 0, got {new_value}")
|
|
361
|
+
env.spot_quote = SpotQuote(
|
|
362
|
+
spot=new_value,
|
|
363
|
+
timestamp=env.spot_quote.timestamp,
|
|
364
|
+
asset_name=env.spot_quote.asset_name,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
elif param in ["volatility", "vol"]:
|
|
368
|
+
if env.vol_surface and isinstance(env.vol_surface, FlatVolSurface):
|
|
369
|
+
current = env.vol_surface.volatility
|
|
370
|
+
new_value = change.apply(current)
|
|
371
|
+
if new_value <= 0:
|
|
372
|
+
raise ValidationError(f"Volatility cannot be <= 0, got {new_value}")
|
|
373
|
+
env.vol_surface = FlatVolSurface(volatility=new_value)
|
|
374
|
+
|
|
375
|
+
elif param == "rate":
|
|
376
|
+
if env.rate_curve and isinstance(env.rate_curve, FlatRateCurve):
|
|
377
|
+
current = env.rate_curve.get_rate(1.0)
|
|
378
|
+
new_value = change.apply(current)
|
|
379
|
+
env.rate_curve = FlatRateCurve(rate=new_value)
|
|
380
|
+
|
|
381
|
+
elif param in ["dividend_yield", "div_yield", "dividend"]:
|
|
382
|
+
if env.div_yield and isinstance(env.div_yield, ContinuousDividendYield):
|
|
383
|
+
current = env.div_yield.div_yield
|
|
384
|
+
new_value = change.apply(current)
|
|
385
|
+
if new_value < 0:
|
|
386
|
+
new_value = 0.0
|
|
387
|
+
env.div_yield = ContinuousDividendYield(div_yield=new_value)
|
|
388
|
+
elif env.div_yield is None:
|
|
389
|
+
# Create dividend yield if it doesn't exist
|
|
390
|
+
new_value = change.apply(0.0)
|
|
391
|
+
if new_value < 0:
|
|
392
|
+
new_value = 0.0
|
|
393
|
+
env.div_yield = ContinuousDividendYield(div_yield=new_value)
|
|
394
|
+
time_to_maturity = self._infer_time_to_maturity(
|
|
395
|
+
change, portfolio, underlying, env
|
|
396
|
+
)
|
|
397
|
+
self._apply_basis_dividend_relationship(
|
|
398
|
+
env,
|
|
399
|
+
change,
|
|
400
|
+
source_param="dividend",
|
|
401
|
+
time_to_maturity=time_to_maturity,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
elif param == "basis":
|
|
405
|
+
time_to_maturity = self._infer_time_to_maturity(
|
|
406
|
+
change, portfolio, underlying, env
|
|
407
|
+
)
|
|
408
|
+
current = (
|
|
409
|
+
env.basis_yield.get_basis_yield(time_to_maturity)
|
|
410
|
+
if env.basis_yield
|
|
411
|
+
else 0.0
|
|
412
|
+
)
|
|
413
|
+
new_value = change.apply(current)
|
|
414
|
+
env.basis_yield = FlatBasisYield(basis_yield=new_value)
|
|
415
|
+
self._apply_basis_dividend_relationship(
|
|
416
|
+
env,
|
|
417
|
+
change,
|
|
418
|
+
source_param="basis",
|
|
419
|
+
time_to_maturity=time_to_maturity,
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
def _get_market_data(self, portfolio: Portfolio) -> Dict[str, float]:
|
|
423
|
+
"""Get current market data from portfolio for strategy."""
|
|
424
|
+
# Use first underlying's environment
|
|
425
|
+
first_underlying = list(portfolio.pricing_environments.keys())[0]
|
|
426
|
+
env = portfolio.pricing_environments[first_underlying]
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
'spot': env.spot if env.spot_quote else 0.0,
|
|
430
|
+
'volatility': env.vol_surface.volatility if isinstance(env.vol_surface, FlatVolSurface) else 0.0,
|
|
431
|
+
'rate': env.rate_curve.get_rate(1.0) if env.rate_curve else 0.0,
|
|
432
|
+
'div_yield': env.div_yield.div_yield if isinstance(env.div_yield, ContinuousDividendYield) else 0.0,
|
|
433
|
+
'basis_yield': env.basis_yield.get_basis_yield(1.0) if env.basis_yield else 0.0,
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
def _execute_hedge(
|
|
437
|
+
self,
|
|
438
|
+
portfolio: Portfolio,
|
|
439
|
+
underlying: str,
|
|
440
|
+
hedge_size: float,
|
|
441
|
+
transaction_cost_model: TransactionCostModel,
|
|
442
|
+
hedge_positions: Dict[str, str],
|
|
443
|
+
current_time: datetime
|
|
444
|
+
) -> tuple:
|
|
445
|
+
"""
|
|
446
|
+
Execute a hedge trade.
|
|
447
|
+
|
|
448
|
+
Returns:
|
|
449
|
+
Tuple of (TradeSnapshot or None, transaction_cost)
|
|
450
|
+
"""
|
|
451
|
+
env = portfolio.pricing_environments[underlying]
|
|
452
|
+
hedge_price = env.spot
|
|
453
|
+
notional = abs(hedge_size * hedge_price)
|
|
454
|
+
|
|
455
|
+
# Calculate transaction cost
|
|
456
|
+
transaction_cost = transaction_cost_model.calculate_cost(
|
|
457
|
+
quantity=hedge_size,
|
|
458
|
+
price=hedge_price,
|
|
459
|
+
notional=notional,
|
|
460
|
+
instrument_type='spot',
|
|
461
|
+
trade_type='hedge'
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
# Check if hedge position exists
|
|
465
|
+
existing_position_id = hedge_positions.get(underlying)
|
|
466
|
+
|
|
467
|
+
if existing_position_id and existing_position_id in portfolio.positions:
|
|
468
|
+
# Update existing position
|
|
469
|
+
position = portfolio.positions[existing_position_id]
|
|
470
|
+
old_quantity = position.quantity
|
|
471
|
+
new_quantity = old_quantity + hedge_size
|
|
472
|
+
|
|
473
|
+
if abs(new_quantity) < 1e-10:
|
|
474
|
+
# Close position
|
|
475
|
+
portfolio.remove_position(existing_position_id)
|
|
476
|
+
del hedge_positions[underlying]
|
|
477
|
+
trade_type = 'close'
|
|
478
|
+
else:
|
|
479
|
+
position.quantity = new_quantity
|
|
480
|
+
trade_type = 'adjust'
|
|
481
|
+
|
|
482
|
+
trade = TradeSnapshot(
|
|
483
|
+
trade_type=trade_type,
|
|
484
|
+
underlying=underlying,
|
|
485
|
+
instrument_type='spot',
|
|
486
|
+
quantity=hedge_size,
|
|
487
|
+
price=hedge_price,
|
|
488
|
+
notional=notional,
|
|
489
|
+
transaction_cost=transaction_cost,
|
|
490
|
+
reason='delta_hedge'
|
|
491
|
+
)
|
|
492
|
+
else:
|
|
493
|
+
# Create new hedge position
|
|
494
|
+
from quantark.util.enum.deltaone_enums import DeltaOneType
|
|
495
|
+
hedge_product = SpotInstrument(
|
|
496
|
+
underlying=underlying,
|
|
497
|
+
deltaone_type=DeltaOneType.STOCK
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
position = portfolio.add_position(
|
|
501
|
+
product=hedge_product,
|
|
502
|
+
quantity=hedge_size,
|
|
503
|
+
entry_price=hedge_price,
|
|
504
|
+
underlying=underlying,
|
|
505
|
+
engine=self._deltaone_engine,
|
|
506
|
+
entry_timestamp=current_time
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
hedge_positions[underlying] = position.position_id
|
|
510
|
+
|
|
511
|
+
trade = TradeSnapshot(
|
|
512
|
+
trade_type='open',
|
|
513
|
+
underlying=underlying,
|
|
514
|
+
instrument_type='spot',
|
|
515
|
+
quantity=hedge_size,
|
|
516
|
+
price=hedge_price,
|
|
517
|
+
notional=notional,
|
|
518
|
+
transaction_cost=transaction_cost,
|
|
519
|
+
reason='delta_hedge'
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
return trade, transaction_cost
|
|
523
|
+
|
|
524
|
+
def _capture_position_snapshots(self, portfolio: Portfolio) -> List[PositionSnapshot]:
|
|
525
|
+
"""Capture snapshots of all positions."""
|
|
526
|
+
snapshots = []
|
|
527
|
+
|
|
528
|
+
for position_id, position in portfolio.positions.items():
|
|
529
|
+
env = portfolio.pricing_environments[position.underlying]
|
|
530
|
+
market_value = position.get_market_value(env)
|
|
531
|
+
pnl = position.get_pnl(env)
|
|
532
|
+
|
|
533
|
+
# Get position Greeks if calculating
|
|
534
|
+
greeks = None
|
|
535
|
+
if self.config.calculate_greeks and self.greeks_calculator:
|
|
536
|
+
use_analytical = (self.config.greeks_method == 'analytical')
|
|
537
|
+
greeks = position.get_greeks(
|
|
538
|
+
env,
|
|
539
|
+
self.greeks_calculator,
|
|
540
|
+
use_analytical=use_analytical
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
snapshot = PositionSnapshot(
|
|
544
|
+
position_id=position_id,
|
|
545
|
+
underlying=position.underlying,
|
|
546
|
+
product_type=position.product.__class__.__name__,
|
|
547
|
+
quantity=position.quantity,
|
|
548
|
+
market_value=market_value,
|
|
549
|
+
entry_price=position.entry_price,
|
|
550
|
+
pnl=pnl,
|
|
551
|
+
greeks=greeks,
|
|
552
|
+
)
|
|
553
|
+
snapshots.append(snapshot)
|
|
554
|
+
|
|
555
|
+
return snapshots
|
|
556
|
+
|
|
557
|
+
def _capture_market_state(self, portfolio: Portfolio) -> MarketState:
|
|
558
|
+
"""Capture current market state from portfolio."""
|
|
559
|
+
spot = {}
|
|
560
|
+
volatility = {}
|
|
561
|
+
div_yield = {}
|
|
562
|
+
basis_yield = {}
|
|
563
|
+
rate = 0.0
|
|
564
|
+
|
|
565
|
+
for underlying, env in portfolio.pricing_environments.items():
|
|
566
|
+
if env.spot_quote:
|
|
567
|
+
spot[underlying] = env.spot_quote.spot
|
|
568
|
+
|
|
569
|
+
if isinstance(env.vol_surface, FlatVolSurface):
|
|
570
|
+
volatility[underlying] = env.vol_surface.volatility
|
|
571
|
+
|
|
572
|
+
if isinstance(env.div_yield, ContinuousDividendYield):
|
|
573
|
+
div_yield[underlying] = env.div_yield.div_yield
|
|
574
|
+
|
|
575
|
+
if env.basis_yield is not None:
|
|
576
|
+
basis_yield[underlying] = env.basis_yield.get_basis_yield(1.0)
|
|
577
|
+
|
|
578
|
+
# Use rate from first environment
|
|
579
|
+
if env.rate_curve and rate == 0.0:
|
|
580
|
+
rate = env.rate_curve.get_rate(1.0)
|
|
581
|
+
|
|
582
|
+
return MarketState(
|
|
583
|
+
spot=spot,
|
|
584
|
+
volatility=volatility,
|
|
585
|
+
rate=rate,
|
|
586
|
+
div_yield=div_yield,
|
|
587
|
+
basis_yield=basis_yield,
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
def _apply_basis_dividend_relationship(
|
|
591
|
+
self,
|
|
592
|
+
env: PricingEnvironment,
|
|
593
|
+
change: ParameterChange,
|
|
594
|
+
source_param: str,
|
|
595
|
+
time_to_maturity: float,
|
|
596
|
+
) -> None:
|
|
597
|
+
"""Apply basis-dividend relationship adjustments based on config."""
|
|
598
|
+
relationship_mode = self.config.basis_dividend_relationship_mode
|
|
599
|
+
if time_to_maturity <= 0:
|
|
600
|
+
raise ValidationError(
|
|
601
|
+
f"time_to_maturity must be positive, got {time_to_maturity}"
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
rate = env.rate_curve.get_rate(time_to_maturity)
|
|
605
|
+
|
|
606
|
+
if source_param == "basis":
|
|
607
|
+
if relationship_mode == BasisDividendRelationshipMode.AUTO_ADJUST_DIVIDEND:
|
|
608
|
+
basis_val = env.basis_yield.get_basis_yield(time_to_maturity)
|
|
609
|
+
new_div = calculate_dividend_from_rate_basis(rate, basis_val)
|
|
610
|
+
if isinstance(env.div_yield, TermStructureDividendYield):
|
|
611
|
+
current_div = env.div_yield.get_yield(time_to_maturity)
|
|
612
|
+
shift = new_div - current_div
|
|
613
|
+
new_yields = [max(0.0, float(y) + shift) for y in env.div_yield.yields]
|
|
614
|
+
env.div_yield = TermStructureDividendYield(
|
|
615
|
+
times=list(env.div_yield.times), yields=new_yields
|
|
616
|
+
)
|
|
617
|
+
else:
|
|
618
|
+
if new_div < 0:
|
|
619
|
+
new_div = 0.0
|
|
620
|
+
env.div_yield = ContinuousDividendYield(div_yield=new_div)
|
|
621
|
+
elif relationship_mode == BasisDividendRelationshipMode.SYNCHRONIZED:
|
|
622
|
+
current_div = env.div_yield.get_yield(time_to_maturity) if env.div_yield else 0.0
|
|
623
|
+
new_div = change.apply(current_div)
|
|
624
|
+
if isinstance(env.div_yield, TermStructureDividendYield):
|
|
625
|
+
shift = new_div - current_div
|
|
626
|
+
new_yields = [max(0.0, float(y) + shift) for y in env.div_yield.yields]
|
|
627
|
+
env.div_yield = TermStructureDividendYield(
|
|
628
|
+
times=list(env.div_yield.times), yields=new_yields
|
|
629
|
+
)
|
|
630
|
+
else:
|
|
631
|
+
if new_div < 0:
|
|
632
|
+
new_div = 0.0
|
|
633
|
+
env.div_yield = ContinuousDividendYield(div_yield=new_div)
|
|
634
|
+
elif source_param == "dividend":
|
|
635
|
+
if relationship_mode == BasisDividendRelationshipMode.AUTO_ADJUST_BASIS:
|
|
636
|
+
div_val = env.div_yield.get_yield(time_to_maturity) if env.div_yield else 0.0
|
|
637
|
+
new_basis = calculate_basis_from_rate_dividend(rate, div_val)
|
|
638
|
+
if isinstance(env.basis_yield, TermStructureBasisYield):
|
|
639
|
+
current_basis = env.basis_yield.get_basis_yield(time_to_maturity)
|
|
640
|
+
shift = new_basis - current_basis
|
|
641
|
+
new_yields = [float(y) + shift for y in env.basis_yield.yields]
|
|
642
|
+
if any(abs(y) > 0.50 for y in new_yields):
|
|
643
|
+
raise ValidationError(
|
|
644
|
+
"Stressed term-structure basis yields must be within +/-50%."
|
|
645
|
+
)
|
|
646
|
+
env.basis_yield = TermStructureBasisYield(
|
|
647
|
+
times=list(env.basis_yield.times), yields=new_yields
|
|
648
|
+
)
|
|
649
|
+
else:
|
|
650
|
+
env.basis_yield = FlatBasisYield(basis_yield=new_basis)
|
|
651
|
+
elif relationship_mode == BasisDividendRelationshipMode.SYNCHRONIZED:
|
|
652
|
+
current_basis = env.basis_yield.get_basis_yield(time_to_maturity) if env.basis_yield else 0.0
|
|
653
|
+
new_basis = change.apply(current_basis)
|
|
654
|
+
if isinstance(env.basis_yield, TermStructureBasisYield):
|
|
655
|
+
shift = new_basis - current_basis
|
|
656
|
+
new_yields = [float(y) + shift for y in env.basis_yield.yields]
|
|
657
|
+
if any(abs(y) > 0.50 for y in new_yields):
|
|
658
|
+
raise ValidationError(
|
|
659
|
+
"Stressed term-structure basis yields must be within +/-50%."
|
|
660
|
+
)
|
|
661
|
+
env.basis_yield = TermStructureBasisYield(
|
|
662
|
+
times=list(env.basis_yield.times), yields=new_yields
|
|
663
|
+
)
|
|
664
|
+
else:
|
|
665
|
+
env.basis_yield = FlatBasisYield(basis_yield=new_basis)
|
|
666
|
+
|
|
667
|
+
def _infer_time_to_maturity(
|
|
668
|
+
self,
|
|
669
|
+
change: ParameterChange,
|
|
670
|
+
portfolio: Portfolio,
|
|
671
|
+
underlying: str,
|
|
672
|
+
env: PricingEnvironment,
|
|
673
|
+
) -> float:
|
|
674
|
+
"""Infer time to maturity from metadata or futures positions."""
|
|
675
|
+
if change.metadata and "time_to_maturity" in change.metadata:
|
|
676
|
+
time_to_maturity = float(change.metadata.get("time_to_maturity", 1.0))
|
|
677
|
+
if time_to_maturity <= 0:
|
|
678
|
+
raise ValidationError(
|
|
679
|
+
f"time_to_maturity must be positive, got {time_to_maturity}"
|
|
680
|
+
)
|
|
681
|
+
return time_to_maturity
|
|
682
|
+
|
|
683
|
+
if hasattr(portfolio, "get_positions_by_underlying"):
|
|
684
|
+
positions = portfolio.get_positions_by_underlying(underlying)
|
|
685
|
+
else:
|
|
686
|
+
positions = [
|
|
687
|
+
pos for pos in portfolio.positions.values()
|
|
688
|
+
if pos.underlying == underlying
|
|
689
|
+
]
|
|
690
|
+
for position in positions:
|
|
691
|
+
product = position.product
|
|
692
|
+
if isinstance(product, Futures):
|
|
693
|
+
try:
|
|
694
|
+
time_to_maturity = product.get_maturity(env)
|
|
695
|
+
except Exception:
|
|
696
|
+
continue
|
|
697
|
+
if time_to_maturity is not None and time_to_maturity > 0:
|
|
698
|
+
return float(time_to_maturity)
|
|
699
|
+
|
|
700
|
+
return 1.0
|
|
701
|
+
|
|
702
|
+
def __repr__(self) -> str:
|
|
703
|
+
return f"DynamicScenarioEngine(config={self.config})"
|