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,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cash-or-nothing European digital option implementation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from .base_equity_option import BaseEquityOption
|
|
9
|
+
from quantark.util.enum import OptionType, ExerciseType
|
|
10
|
+
from quantark.util.exceptions import ValidationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class CashOrNothingDigitalOption(BaseEquityOption):
|
|
15
|
+
"""
|
|
16
|
+
European cash-or-nothing digital call or put.
|
|
17
|
+
|
|
18
|
+
Pays a fixed cash amount if the terminal spot is on the paying side of
|
|
19
|
+
the strike (S > K for calls, S < K for puts), otherwise pays zero.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
payout: float = 0.0
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
strike: float,
|
|
27
|
+
payout: float,
|
|
28
|
+
option_type: OptionType,
|
|
29
|
+
maturity: Optional[float] = None,
|
|
30
|
+
exercise_date: Optional[datetime] = None,
|
|
31
|
+
settlement_date: Optional[datetime] = None,
|
|
32
|
+
contract_multiplier: float = 1.0,
|
|
33
|
+
):
|
|
34
|
+
"""
|
|
35
|
+
Initialize cash-or-nothing digital option.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
strike: Strike price
|
|
39
|
+
payout: Fixed cash payout when in the money at maturity
|
|
40
|
+
option_type: CALL or PUT
|
|
41
|
+
maturity: Time to maturity in years (optional if exercise_date provided)
|
|
42
|
+
exercise_date: Date when option can be exercised (optional if maturity provided)
|
|
43
|
+
settlement_date: Date when settlement occurs (optional, defaults to exercise_date)
|
|
44
|
+
|
|
45
|
+
Note:
|
|
46
|
+
Either maturity OR exercise_date must be provided (not both).
|
|
47
|
+
"""
|
|
48
|
+
if maturity is None and exercise_date is None:
|
|
49
|
+
maturity = 0.0 # Will trigger validation error
|
|
50
|
+
elif maturity is None:
|
|
51
|
+
maturity = 0.0 # Placeholder when using dates
|
|
52
|
+
|
|
53
|
+
self.payout = payout
|
|
54
|
+
|
|
55
|
+
super().__init__(
|
|
56
|
+
strike=strike,
|
|
57
|
+
maturity=maturity,
|
|
58
|
+
option_type=option_type,
|
|
59
|
+
exercise_type=ExerciseType.EUROPEAN,
|
|
60
|
+
exercise_date=exercise_date,
|
|
61
|
+
settlement_date=settlement_date,
|
|
62
|
+
contract_multiplier=contract_multiplier,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def validate(self) -> None:
|
|
66
|
+
"""Validate option parameters."""
|
|
67
|
+
super().validate()
|
|
68
|
+
|
|
69
|
+
if self.payout <= 0:
|
|
70
|
+
raise ValidationError(f"Payout must be positive, got {self.payout}")
|
|
71
|
+
|
|
72
|
+
if self.exercise_type != ExerciseType.EUROPEAN:
|
|
73
|
+
raise ValidationError(
|
|
74
|
+
f"CashOrNothingDigitalOption only supports European exercise, got {self.exercise_type}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def get_payoff(self, spot: float) -> float:
|
|
78
|
+
"""
|
|
79
|
+
Calculate the digital option payoff at maturity.
|
|
80
|
+
|
|
81
|
+
For a call: payout if S > K else 0
|
|
82
|
+
For a put: payout if S < K else 0
|
|
83
|
+
"""
|
|
84
|
+
if spot < 0:
|
|
85
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
86
|
+
|
|
87
|
+
if self.is_call():
|
|
88
|
+
payoff = self.payout if spot > self.strike else 0.0
|
|
89
|
+
else:
|
|
90
|
+
payoff = self.payout if spot < self.strike else 0.0
|
|
91
|
+
|
|
92
|
+
return payoff * self.contract_multiplier
|
|
93
|
+
|
|
94
|
+
def intrinsic_value(self, spot: float) -> float:
|
|
95
|
+
"""Intrinsic value equals digital payoff."""
|
|
96
|
+
return self.get_payoff(spot)
|
|
97
|
+
|
|
98
|
+
def __repr__(self):
|
|
99
|
+
return (
|
|
100
|
+
f"CashOrNothingDigitalOption("
|
|
101
|
+
f"{self.option_type}, K={self.strike:.4f}, P={self.payout:.4f}, T={self.maturity:.4f})"
|
|
102
|
+
)
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Double barrier option implementation.
|
|
3
|
+
|
|
4
|
+
Double barrier options have both an upper and lower barrier.
|
|
5
|
+
The option knocks in or out if either barrier is breached.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from .base_equity_option import BaseEquityOption
|
|
12
|
+
from .observation_schedule import ObservationRecord, ObservationSchedule
|
|
13
|
+
from quantark.util.enum import (
|
|
14
|
+
OptionType,
|
|
15
|
+
ExerciseType,
|
|
16
|
+
DoubleBarrierType,
|
|
17
|
+
ObservationType,
|
|
18
|
+
ObservationAggregation,
|
|
19
|
+
)
|
|
20
|
+
from quantark.util.exceptions import ValidationError
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class DoubleBarrierOption(BaseEquityOption):
|
|
25
|
+
"""
|
|
26
|
+
Double barrier option (knock-in or knock-out with two barriers).
|
|
27
|
+
|
|
28
|
+
Double barrier options have both an upper barrier (above spot) and
|
|
29
|
+
a lower barrier (below spot). The option:
|
|
30
|
+
- KNOCK_OUT: Deactivates if either barrier is hit
|
|
31
|
+
- KNOCK_IN: Activates if either barrier is hit
|
|
32
|
+
|
|
33
|
+
These are sometimes called "corridor options" or "range options".
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
strike: Strike price
|
|
37
|
+
maturity: Time to maturity in years
|
|
38
|
+
option_type: CALL or PUT
|
|
39
|
+
upper_barrier: Upper barrier price (above current spot)
|
|
40
|
+
lower_barrier: Lower barrier price (below current spot)
|
|
41
|
+
barrier_type: KNOCK_IN or KNOCK_OUT
|
|
42
|
+
rebate: Amount paid if knocked out (or not knocked in)
|
|
43
|
+
observation_type: CONTINUOUS or DISCRETE monitoring
|
|
44
|
+
observation_dates: For discrete, list of observation times (year fractions)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
upper_barrier: float = 0.0
|
|
48
|
+
lower_barrier: float = 0.0
|
|
49
|
+
barrier_type: DoubleBarrierType = DoubleBarrierType.KNOCK_OUT
|
|
50
|
+
rebate: float = 0.0
|
|
51
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS
|
|
52
|
+
observation_dates: Optional[List[float]] = None
|
|
53
|
+
observation_schedule: Optional[ObservationSchedule] = None
|
|
54
|
+
|
|
55
|
+
def __init__(
|
|
56
|
+
self,
|
|
57
|
+
strike: float,
|
|
58
|
+
option_type: OptionType,
|
|
59
|
+
upper_barrier: float,
|
|
60
|
+
lower_barrier: float,
|
|
61
|
+
barrier_type: DoubleBarrierType,
|
|
62
|
+
maturity: Optional[float] = None,
|
|
63
|
+
exercise_date: Optional[datetime] = None,
|
|
64
|
+
settlement_date: Optional[datetime] = None,
|
|
65
|
+
rebate: float = 0.0,
|
|
66
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS,
|
|
67
|
+
observation_dates: Optional[List[float]] = None,
|
|
68
|
+
observation_schedule: Optional[ObservationSchedule] = None,
|
|
69
|
+
contract_multiplier: float = 1.0,
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Initialize double barrier option.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
strike: Strike price
|
|
76
|
+
option_type: CALL or PUT
|
|
77
|
+
upper_barrier: Upper barrier price
|
|
78
|
+
lower_barrier: Lower barrier price
|
|
79
|
+
barrier_type: KNOCK_IN or KNOCK_OUT
|
|
80
|
+
maturity: Time to maturity in years (optional if exercise_date provided)
|
|
81
|
+
exercise_date: Expiration date (optional if maturity provided)
|
|
82
|
+
settlement_date: Settlement date (optional)
|
|
83
|
+
rebate: Rebate amount (default: 0)
|
|
84
|
+
observation_type: CONTINUOUS or DISCRETE (default: CONTINUOUS)
|
|
85
|
+
observation_dates: For discrete, list of observation times
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValidationError: If parameters are invalid
|
|
89
|
+
"""
|
|
90
|
+
# Default maturity handling
|
|
91
|
+
if maturity is None and exercise_date is None:
|
|
92
|
+
maturity = 0.0
|
|
93
|
+
elif maturity is None:
|
|
94
|
+
maturity = 0.0
|
|
95
|
+
|
|
96
|
+
# Store barrier parameters before calling super().__init__
|
|
97
|
+
self.upper_barrier = upper_barrier
|
|
98
|
+
self.lower_barrier = lower_barrier
|
|
99
|
+
self.barrier_type = barrier_type
|
|
100
|
+
self.rebate = rebate
|
|
101
|
+
self.observation_type = observation_type
|
|
102
|
+
self.observation_dates = observation_dates
|
|
103
|
+
self.observation_schedule = observation_schedule
|
|
104
|
+
|
|
105
|
+
super().__init__(
|
|
106
|
+
strike=strike,
|
|
107
|
+
maturity=maturity,
|
|
108
|
+
option_type=option_type,
|
|
109
|
+
exercise_type=ExerciseType.EUROPEAN,
|
|
110
|
+
exercise_date=exercise_date,
|
|
111
|
+
settlement_date=settlement_date,
|
|
112
|
+
contract_multiplier=contract_multiplier,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def validate(self) -> None:
|
|
116
|
+
"""
|
|
117
|
+
Validate double barrier option parameters.
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
ValidationError: If parameters are invalid
|
|
121
|
+
"""
|
|
122
|
+
super().validate()
|
|
123
|
+
|
|
124
|
+
if self.upper_barrier <= 0:
|
|
125
|
+
raise ValidationError(
|
|
126
|
+
f"Upper barrier must be positive, got {self.upper_barrier}"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if self.lower_barrier <= 0:
|
|
130
|
+
raise ValidationError(
|
|
131
|
+
f"Lower barrier must be positive, got {self.lower_barrier}"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
if self.lower_barrier >= self.upper_barrier:
|
|
135
|
+
raise ValidationError(
|
|
136
|
+
f"Lower barrier ({self.lower_barrier}) must be less than "
|
|
137
|
+
f"upper barrier ({self.upper_barrier})"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if self.rebate < 0:
|
|
141
|
+
raise ValidationError(f"Rebate must be non-negative, got {self.rebate}")
|
|
142
|
+
|
|
143
|
+
if not isinstance(self.barrier_type, DoubleBarrierType):
|
|
144
|
+
raise ValidationError(f"Invalid barrier type: {self.barrier_type}")
|
|
145
|
+
|
|
146
|
+
if not isinstance(self.observation_type, ObservationType):
|
|
147
|
+
raise ValidationError(f"Invalid observation type: {self.observation_type}")
|
|
148
|
+
|
|
149
|
+
# Strike should be between barriers for knock-out to make sense
|
|
150
|
+
if self.barrier_type == DoubleBarrierType.KNOCK_OUT:
|
|
151
|
+
if not (self.lower_barrier < self.strike < self.upper_barrier):
|
|
152
|
+
# This is a warning, not an error - could be intentional
|
|
153
|
+
pass
|
|
154
|
+
|
|
155
|
+
# For discrete observation, must have observation dates unless schedule supplied
|
|
156
|
+
if (
|
|
157
|
+
self.observation_type == ObservationType.DISCRETE
|
|
158
|
+
and self.observation_schedule is None
|
|
159
|
+
and (self.observation_dates is None or len(self.observation_dates) == 0)
|
|
160
|
+
):
|
|
161
|
+
raise ValidationError(
|
|
162
|
+
"Observation dates required for discrete barrier monitoring"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Normalize observation schedule (preferred) or legacy dates for discrete monitoring
|
|
166
|
+
if self.observation_schedule is not None:
|
|
167
|
+
if self.observation_type == ObservationType.CONTINUOUS:
|
|
168
|
+
raise ValidationError("ObservationSchedule requires DISCRETE observation_type.")
|
|
169
|
+
normalized_schedule = ObservationSchedule(
|
|
170
|
+
records=[
|
|
171
|
+
ObservationRecord(
|
|
172
|
+
observation_time=rec.observation_time,
|
|
173
|
+
observation_date=rec.observation_date,
|
|
174
|
+
upper_barrier=rec.upper_barrier if rec.upper_barrier is not None else self.upper_barrier,
|
|
175
|
+
lower_barrier=rec.lower_barrier if rec.lower_barrier is not None else self.lower_barrier,
|
|
176
|
+
payoff=rec.payoff if rec.payoff is not None else self.rebate,
|
|
177
|
+
return_rate=rec.return_rate,
|
|
178
|
+
)
|
|
179
|
+
for rec in self.observation_schedule.records
|
|
180
|
+
],
|
|
181
|
+
aggregation_mode=self.observation_schedule.aggregation_mode,
|
|
182
|
+
frequency=self.observation_schedule.frequency,
|
|
183
|
+
)
|
|
184
|
+
normalized_schedule.validate(require_double=True)
|
|
185
|
+
self.observation_schedule = normalized_schedule
|
|
186
|
+
if self.observation_schedule.times:
|
|
187
|
+
self.observation_dates = self.observation_schedule.times
|
|
188
|
+
self.observation_type = ObservationType.DISCRETE
|
|
189
|
+
elif self.observation_type == ObservationType.DISCRETE:
|
|
190
|
+
self.observation_schedule = ObservationSchedule.from_legacy(
|
|
191
|
+
observation_dates=self.observation_dates or [],
|
|
192
|
+
default_barrier=None,
|
|
193
|
+
default_payoff=self.rebate,
|
|
194
|
+
aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
|
|
195
|
+
upper_barrier=self.upper_barrier,
|
|
196
|
+
lower_barrier=self.lower_barrier,
|
|
197
|
+
)
|
|
198
|
+
self.observation_dates = self.observation_schedule.times
|
|
199
|
+
|
|
200
|
+
def get_payoff(self, spot: float) -> float:
|
|
201
|
+
"""
|
|
202
|
+
Calculate the option payoff at maturity.
|
|
203
|
+
|
|
204
|
+
Note: This returns the vanilla payoff. The actual payoff depends on
|
|
205
|
+
whether either barrier was hit during the option's life.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
spot: Spot price at maturity
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Option payoff (call or put style)
|
|
212
|
+
"""
|
|
213
|
+
if spot < 0:
|
|
214
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
215
|
+
|
|
216
|
+
if self.is_call():
|
|
217
|
+
intrinsic = max(spot - self.strike, 0.0)
|
|
218
|
+
else:
|
|
219
|
+
intrinsic = max(self.strike - spot, 0.0)
|
|
220
|
+
|
|
221
|
+
return intrinsic * self.contract_multiplier
|
|
222
|
+
|
|
223
|
+
def intrinsic_value(self, spot: float) -> float:
|
|
224
|
+
"""
|
|
225
|
+
Calculate intrinsic value.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
spot: Current spot price
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
Intrinsic value
|
|
232
|
+
"""
|
|
233
|
+
return self.get_payoff(spot)
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def is_knock_in(self) -> bool:
|
|
237
|
+
"""Check if this is a knock-in barrier."""
|
|
238
|
+
return self.barrier_type == DoubleBarrierType.KNOCK_IN
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def is_knock_out(self) -> bool:
|
|
242
|
+
"""Check if this is a knock-out barrier."""
|
|
243
|
+
return self.barrier_type == DoubleBarrierType.KNOCK_OUT
|
|
244
|
+
|
|
245
|
+
def is_barrier_hit(self, spot: float) -> bool:
|
|
246
|
+
"""
|
|
247
|
+
Check if either barrier would be hit at a given spot price.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
spot: Spot price to check
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
True if either barrier is hit
|
|
254
|
+
"""
|
|
255
|
+
return spot >= self.upper_barrier or spot <= self.lower_barrier
|
|
256
|
+
|
|
257
|
+
def is_in_corridor(self, spot: float) -> bool:
|
|
258
|
+
"""
|
|
259
|
+
Check if spot is within the corridor (between barriers).
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
spot: Spot price to check
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
True if spot is between lower and upper barriers
|
|
266
|
+
"""
|
|
267
|
+
return self.lower_barrier < spot < self.upper_barrier
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def corridor_width(self) -> float:
|
|
271
|
+
"""Get the width of the corridor in price terms."""
|
|
272
|
+
return self.upper_barrier - self.lower_barrier
|
|
273
|
+
|
|
274
|
+
@property
|
|
275
|
+
def corridor_width_log(self) -> float:
|
|
276
|
+
"""Get the width of the corridor in log-price terms."""
|
|
277
|
+
import math
|
|
278
|
+
return math.log(self.upper_barrier / self.lower_barrier)
|
|
279
|
+
|
|
280
|
+
def __repr__(self):
|
|
281
|
+
return (
|
|
282
|
+
f"DoubleBarrierOption("
|
|
283
|
+
f"{self.option_type}, K={self.strike:.2f}, "
|
|
284
|
+
f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
|
|
285
|
+
f"{self.barrier_type}, T={self.maturity:.4f})"
|
|
286
|
+
)
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Double one-touch option implementation.
|
|
3
|
+
|
|
4
|
+
Double one-touch options have two barriers (upper and lower) and pay
|
|
5
|
+
a fixed rebate if either barrier is touched (or neither, for double no-touch).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Optional, List
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from ..base_equity_product import BaseEquityProduct
|
|
12
|
+
from .observation_schedule import ObservationRecord, ObservationSchedule
|
|
13
|
+
from quantark.util.enum import (
|
|
14
|
+
ObservationType,
|
|
15
|
+
ObservationAggregation,
|
|
16
|
+
TouchType,
|
|
17
|
+
)
|
|
18
|
+
from quantark.util.exceptions import ValidationError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class DoubleOneTouchOption(BaseEquityProduct):
|
|
23
|
+
"""
|
|
24
|
+
Double one-touch or double no-touch option.
|
|
25
|
+
|
|
26
|
+
Double one-touch options pay a fixed rebate if EITHER barrier is touched.
|
|
27
|
+
Double no-touch options pay a fixed rebate if NEITHER barrier is touched.
|
|
28
|
+
|
|
29
|
+
These are also known as:
|
|
30
|
+
- Double one-touch: "range" or "boundary" options
|
|
31
|
+
- Double no-touch: "range binary" or "range accrual" options
|
|
32
|
+
|
|
33
|
+
Attributes:
|
|
34
|
+
upper_barrier: Upper barrier price
|
|
35
|
+
lower_barrier: Lower barrier price
|
|
36
|
+
rebate: Amount paid when condition is satisfied (default: 1.0)
|
|
37
|
+
maturity: Time to maturity in years
|
|
38
|
+
payment_at_hit: For one-touch, True = pay on hit, False = pay at expiry
|
|
39
|
+
touch_type: DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH
|
|
40
|
+
observation_type: CONTINUOUS or DISCRETE monitoring
|
|
41
|
+
observation_dates: For discrete, list of observation times
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
upper_barrier: float = 0.0
|
|
45
|
+
lower_barrier: float = 0.0
|
|
46
|
+
rebate: float = 1.0
|
|
47
|
+
maturity: float = 0.0
|
|
48
|
+
payment_at_hit: bool = True
|
|
49
|
+
touch_type: TouchType = TouchType.DOUBLE_ONE_TOUCH
|
|
50
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS
|
|
51
|
+
observation_dates: Optional[List[float]] = None
|
|
52
|
+
observation_schedule: Optional[ObservationSchedule] = None
|
|
53
|
+
exercise_date: Optional[datetime] = None
|
|
54
|
+
settlement_date: Optional[datetime] = None
|
|
55
|
+
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
upper_barrier: float,
|
|
59
|
+
lower_barrier: float,
|
|
60
|
+
maturity: Optional[float] = None,
|
|
61
|
+
exercise_date: Optional[datetime] = None,
|
|
62
|
+
settlement_date: Optional[datetime] = None,
|
|
63
|
+
rebate: float = 1.0,
|
|
64
|
+
payment_at_hit: bool = True,
|
|
65
|
+
touch_type: TouchType = TouchType.DOUBLE_ONE_TOUCH,
|
|
66
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS,
|
|
67
|
+
observation_dates: Optional[List[float]] = None,
|
|
68
|
+
observation_schedule: Optional[ObservationSchedule] = None,
|
|
69
|
+
):
|
|
70
|
+
"""
|
|
71
|
+
Initialize double one-touch option.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
upper_barrier: Upper barrier price
|
|
75
|
+
lower_barrier: Lower barrier price
|
|
76
|
+
maturity: Time to maturity in years (optional if exercise_date provided)
|
|
77
|
+
exercise_date: Expiration date (optional if maturity provided)
|
|
78
|
+
settlement_date: Settlement date (optional)
|
|
79
|
+
rebate: Payment amount (default: 1.0)
|
|
80
|
+
payment_at_hit: For one-touch, True = pay on hit (default: True)
|
|
81
|
+
touch_type: DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH
|
|
82
|
+
observation_type: CONTINUOUS or DISCRETE
|
|
83
|
+
observation_dates: For discrete, list of observation times
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
ValidationError: If parameters are invalid
|
|
87
|
+
"""
|
|
88
|
+
# Handle maturity vs exercise_date
|
|
89
|
+
if maturity is None and exercise_date is None:
|
|
90
|
+
maturity = 0.0
|
|
91
|
+
elif maturity is None:
|
|
92
|
+
maturity = 0.0
|
|
93
|
+
|
|
94
|
+
self.upper_barrier = upper_barrier
|
|
95
|
+
self.lower_barrier = lower_barrier
|
|
96
|
+
self.rebate = rebate
|
|
97
|
+
self.maturity = maturity
|
|
98
|
+
self.payment_at_hit = payment_at_hit
|
|
99
|
+
self.touch_type = touch_type
|
|
100
|
+
self.observation_type = observation_type
|
|
101
|
+
self.observation_dates = observation_dates
|
|
102
|
+
self.observation_schedule = observation_schedule
|
|
103
|
+
self.exercise_date = exercise_date
|
|
104
|
+
self.settlement_date = settlement_date
|
|
105
|
+
|
|
106
|
+
self.validate()
|
|
107
|
+
|
|
108
|
+
def validate(self) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Validate double one-touch option parameters.
|
|
111
|
+
|
|
112
|
+
Raises:
|
|
113
|
+
ValidationError: If parameters are invalid
|
|
114
|
+
"""
|
|
115
|
+
if self.upper_barrier <= 0:
|
|
116
|
+
raise ValidationError(
|
|
117
|
+
f"Upper barrier must be positive, got {self.upper_barrier}"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
if self.lower_barrier <= 0:
|
|
121
|
+
raise ValidationError(
|
|
122
|
+
f"Lower barrier must be positive, got {self.lower_barrier}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if self.lower_barrier >= self.upper_barrier:
|
|
126
|
+
raise ValidationError(
|
|
127
|
+
f"Lower barrier ({self.lower_barrier}) must be less than "
|
|
128
|
+
f"upper barrier ({self.upper_barrier})"
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
if self.rebate < 0:
|
|
132
|
+
raise ValidationError(f"Rebate must be non-negative, got {self.rebate}")
|
|
133
|
+
|
|
134
|
+
if self.touch_type not in (TouchType.DOUBLE_ONE_TOUCH, TouchType.DOUBLE_NO_TOUCH):
|
|
135
|
+
raise ValidationError(
|
|
136
|
+
f"Touch type must be DOUBLE_ONE_TOUCH or DOUBLE_NO_TOUCH, "
|
|
137
|
+
f"got {self.touch_type}"
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Validate maturity
|
|
141
|
+
has_dates = self.exercise_date is not None
|
|
142
|
+
has_maturity = self.maturity is not None and self.maturity > 0
|
|
143
|
+
|
|
144
|
+
if not has_dates and not has_maturity:
|
|
145
|
+
raise ValidationError("Either maturity or exercise_date must be provided")
|
|
146
|
+
|
|
147
|
+
# For discrete observation, must have observation dates
|
|
148
|
+
if (
|
|
149
|
+
self.observation_type == ObservationType.DISCRETE
|
|
150
|
+
and self.observation_schedule is None
|
|
151
|
+
and (self.observation_dates is None or len(self.observation_dates) == 0)
|
|
152
|
+
):
|
|
153
|
+
raise ValidationError(
|
|
154
|
+
"Observation dates required for discrete barrier monitoring"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Normalize observation schedule (preferred) or legacy dates for discrete monitoring
|
|
158
|
+
if self.observation_schedule is not None:
|
|
159
|
+
if self.observation_type == ObservationType.CONTINUOUS:
|
|
160
|
+
raise ValidationError("ObservationSchedule requires DISCRETE observation_type.")
|
|
161
|
+
normalized_schedule = ObservationSchedule(
|
|
162
|
+
records=[
|
|
163
|
+
ObservationRecord(
|
|
164
|
+
observation_time=rec.observation_time,
|
|
165
|
+
observation_date=rec.observation_date,
|
|
166
|
+
upper_barrier=rec.upper_barrier if rec.upper_barrier is not None else self.upper_barrier,
|
|
167
|
+
lower_barrier=rec.lower_barrier if rec.lower_barrier is not None else self.lower_barrier,
|
|
168
|
+
payoff=rec.payoff if rec.payoff is not None else self.rebate,
|
|
169
|
+
return_rate=rec.return_rate,
|
|
170
|
+
)
|
|
171
|
+
for rec in self.observation_schedule.records
|
|
172
|
+
],
|
|
173
|
+
aggregation_mode=self.observation_schedule.aggregation_mode,
|
|
174
|
+
frequency=self.observation_schedule.frequency,
|
|
175
|
+
)
|
|
176
|
+
normalized_schedule.validate(require_double=True)
|
|
177
|
+
self.observation_schedule = normalized_schedule
|
|
178
|
+
if self.observation_schedule.times:
|
|
179
|
+
self.observation_dates = self.observation_schedule.times
|
|
180
|
+
self.observation_type = ObservationType.DISCRETE
|
|
181
|
+
elif self.observation_type == ObservationType.DISCRETE:
|
|
182
|
+
self.observation_schedule = ObservationSchedule.from_legacy(
|
|
183
|
+
observation_dates=self.observation_dates or [],
|
|
184
|
+
default_barrier=None,
|
|
185
|
+
default_payoff=self.rebate,
|
|
186
|
+
aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
|
|
187
|
+
upper_barrier=self.upper_barrier,
|
|
188
|
+
lower_barrier=self.lower_barrier,
|
|
189
|
+
)
|
|
190
|
+
self.observation_dates = self.observation_schedule.times
|
|
191
|
+
|
|
192
|
+
def get_maturity(self, pricing_env=None) -> float:
|
|
193
|
+
"""
|
|
194
|
+
Get time to maturity in years.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
pricing_env: Pricing environment (required if using dates)
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
Time to maturity in years
|
|
201
|
+
"""
|
|
202
|
+
if self.exercise_date is not None:
|
|
203
|
+
if pricing_env is None:
|
|
204
|
+
raise ValidationError(
|
|
205
|
+
"PricingEnvironment required for date-based maturity calculation"
|
|
206
|
+
)
|
|
207
|
+
from quantark.util.calendar import calculate_year_fraction
|
|
208
|
+
|
|
209
|
+
return calculate_year_fraction(
|
|
210
|
+
pricing_env.valuation_date,
|
|
211
|
+
self.exercise_date,
|
|
212
|
+
pricing_env.day_count_convention,
|
|
213
|
+
pricing_env.bus_days_in_year,
|
|
214
|
+
calendar=getattr(pricing_env, "calendar", None),
|
|
215
|
+
)
|
|
216
|
+
else:
|
|
217
|
+
return self.maturity
|
|
218
|
+
|
|
219
|
+
def time_shift(self, time_bump: float, bumped_date: datetime, pricing_env) -> bool:
|
|
220
|
+
"""Shift observation schedule and maturity for theta bumping."""
|
|
221
|
+
schedule = getattr(self, "observation_schedule", None)
|
|
222
|
+
if schedule is not None:
|
|
223
|
+
if schedule.uses_dates():
|
|
224
|
+
pricing_env.valuation_date = bumped_date
|
|
225
|
+
bumped_schedule = schedule.time_shift(time_bump, bumped_date)
|
|
226
|
+
if bumped_schedule is None:
|
|
227
|
+
return True
|
|
228
|
+
self.observation_schedule = bumped_schedule
|
|
229
|
+
if hasattr(self, "observation_dates") and bumped_schedule.uses_times():
|
|
230
|
+
self.observation_dates = bumped_schedule.times
|
|
231
|
+
|
|
232
|
+
if getattr(self, "exercise_date", None) is None:
|
|
233
|
+
if getattr(self, "maturity", None) is not None:
|
|
234
|
+
self.maturity -= time_bump
|
|
235
|
+
else:
|
|
236
|
+
pricing_env.valuation_date = bumped_date
|
|
237
|
+
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
def get_payoff(self, spot: float, touched: bool = False) -> float:
|
|
241
|
+
"""
|
|
242
|
+
Calculate the option payoff.
|
|
243
|
+
|
|
244
|
+
For double one-touch: pays rebate if either barrier touched
|
|
245
|
+
For double no-touch: pays rebate if neither barrier touched
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
spot: Spot price (not directly used)
|
|
249
|
+
touched: Whether any barrier was touched
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Option payoff
|
|
253
|
+
"""
|
|
254
|
+
if self.is_double_one_touch:
|
|
255
|
+
return self.rebate if touched else 0.0
|
|
256
|
+
else: # Double no-touch
|
|
257
|
+
return self.rebate if not touched else 0.0
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def is_double_one_touch(self) -> bool:
|
|
261
|
+
"""Check if this is a double one-touch option."""
|
|
262
|
+
return self.touch_type == TouchType.DOUBLE_ONE_TOUCH
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def is_double_no_touch(self) -> bool:
|
|
266
|
+
"""Check if this is a double no-touch option."""
|
|
267
|
+
return self.touch_type == TouchType.DOUBLE_NO_TOUCH
|
|
268
|
+
|
|
269
|
+
def is_barrier_hit(self, spot: float) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
Check if either barrier would be hit at a given spot price.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
spot: Spot price to check
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
True if either barrier is hit
|
|
278
|
+
"""
|
|
279
|
+
return spot >= self.upper_barrier or spot <= self.lower_barrier
|
|
280
|
+
|
|
281
|
+
def is_in_corridor(self, spot: float) -> bool:
|
|
282
|
+
"""
|
|
283
|
+
Check if spot is within the corridor.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
spot: Spot price to check
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
True if spot is between lower and upper barriers
|
|
290
|
+
"""
|
|
291
|
+
return self.lower_barrier < spot < self.upper_barrier
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def corridor_width(self) -> float:
|
|
295
|
+
"""Get the width of the corridor in price terms."""
|
|
296
|
+
return self.upper_barrier - self.lower_barrier
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def corridor_width_log(self) -> float:
|
|
300
|
+
"""Get the width of the corridor in log-price terms."""
|
|
301
|
+
import math
|
|
302
|
+
return math.log(self.upper_barrier / self.lower_barrier)
|
|
303
|
+
|
|
304
|
+
def __repr__(self):
|
|
305
|
+
touch_str = "DoubleOneTouch" if self.is_double_one_touch else "DoubleNoTouch"
|
|
306
|
+
return (
|
|
307
|
+
f"{touch_str}Option("
|
|
308
|
+
f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
|
|
309
|
+
f"rebate={self.rebate:.2f}, T={self.maturity:.4f})"
|
|
310
|
+
)
|