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,466 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Double sharkfin option product definition.
|
|
3
|
+
|
|
4
|
+
Double sharkfin options are capped double-barrier structures. The product
|
|
5
|
+
knocks out if either the upper or lower barrier is hit. If no barrier is hit,
|
|
6
|
+
it pays a vanilla-style call or put payoff capped by the relevant barrier.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from math import ceil
|
|
12
|
+
from typing import List, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
from quantark.util.enum import (
|
|
15
|
+
ExerciseType,
|
|
16
|
+
ObservationAggregation,
|
|
17
|
+
ObservationFrequency,
|
|
18
|
+
ObservationType,
|
|
19
|
+
OptionType,
|
|
20
|
+
)
|
|
21
|
+
from quantark.util.exceptions import ValidationError
|
|
22
|
+
from quantark.util.numerical import is_close
|
|
23
|
+
|
|
24
|
+
from .base_equity_option import BaseEquityOption
|
|
25
|
+
from .observation_schedule import ObservationRecord, ObservationSchedule
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class DoubleSharkfinOption(BaseEquityOption):
|
|
30
|
+
"""
|
|
31
|
+
Double sharkfin option with upper and lower knock-out barriers.
|
|
32
|
+
|
|
33
|
+
Payoff states:
|
|
34
|
+
1. Either barrier hit: knock_out_rebate
|
|
35
|
+
2. No barrier hit:
|
|
36
|
+
- CALL: no_hit_rebate + participation_rate * max(min(S, U) - K, 0)
|
|
37
|
+
- PUT: no_hit_rebate + participation_rate * max(K - max(S, L), 0)
|
|
38
|
+
|
|
39
|
+
Monitoring styles:
|
|
40
|
+
- EXPIRY: barriers checked only at terminal spot
|
|
41
|
+
- DISCRETE: barriers checked on observation dates, including daily schedules
|
|
42
|
+
- CONTINUOUS: barriers checked continuously by the pricing engine/path logic
|
|
43
|
+
|
|
44
|
+
Attributes:
|
|
45
|
+
strike: Strike price.
|
|
46
|
+
option_type: CALL or PUT payoff direction.
|
|
47
|
+
upper_barrier: Upper knock-out barrier level.
|
|
48
|
+
lower_barrier: Lower knock-out barrier level.
|
|
49
|
+
maturity: Time to maturity in years.
|
|
50
|
+
participation_rate: Participation in the capped no-hit payoff.
|
|
51
|
+
knock_out_rebate: Fixed payoff if either barrier is hit.
|
|
52
|
+
no_hit_rebate: Fixed payoff added when no barrier is hit.
|
|
53
|
+
pay_at_hit: If True, pay knock-out rebate immediately on hit;
|
|
54
|
+
otherwise pay it at expiry.
|
|
55
|
+
observation_type: EXPIRY, DISCRETE, or CONTINUOUS monitoring.
|
|
56
|
+
observation_dates: Legacy discrete observation times in years.
|
|
57
|
+
observation_schedule: Preferred discrete observation schedule.
|
|
58
|
+
observation_frequency: Frequency used when generating a regular schedule.
|
|
59
|
+
contract_multiplier: Underlying units represented by one contract.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
upper_barrier: float = 0.0
|
|
63
|
+
lower_barrier: float = 0.0
|
|
64
|
+
participation_rate: float = 1.0
|
|
65
|
+
knock_out_rebate: float = 0.0
|
|
66
|
+
no_hit_rebate: float = 0.0
|
|
67
|
+
pay_at_hit: bool = False
|
|
68
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS
|
|
69
|
+
observation_dates: Optional[List[float]] = None
|
|
70
|
+
observation_schedule: Optional[ObservationSchedule] = None
|
|
71
|
+
observation_frequency: ObservationFrequency = ObservationFrequency.CUSTOM
|
|
72
|
+
use_business_days_for_frequency: bool = True
|
|
73
|
+
business_days_in_year: float = 252.0
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
strike: float,
|
|
78
|
+
option_type: OptionType,
|
|
79
|
+
upper_barrier: float,
|
|
80
|
+
lower_barrier: float,
|
|
81
|
+
maturity: Optional[float] = None,
|
|
82
|
+
exercise_date: Optional[datetime] = None,
|
|
83
|
+
settlement_date: Optional[datetime] = None,
|
|
84
|
+
participation_rate: float = 1.0,
|
|
85
|
+
knock_out_rebate: float = 0.0,
|
|
86
|
+
no_hit_rebate: float = 0.0,
|
|
87
|
+
pay_at_hit: bool = False,
|
|
88
|
+
observation_type: ObservationType = ObservationType.CONTINUOUS,
|
|
89
|
+
observation_dates: Optional[List[float]] = None,
|
|
90
|
+
observation_schedule: Optional[ObservationSchedule] = None,
|
|
91
|
+
observation_frequency: ObservationFrequency = ObservationFrequency.CUSTOM,
|
|
92
|
+
use_business_days_for_frequency: bool = True,
|
|
93
|
+
business_days_in_year: float = 252.0,
|
|
94
|
+
contract_multiplier: float = 1.0,
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Initialize a double sharkfin option.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
strike: Strike price.
|
|
101
|
+
option_type: CALL or PUT payoff direction.
|
|
102
|
+
upper_barrier: Upper knock-out barrier level.
|
|
103
|
+
lower_barrier: Lower knock-out barrier level.
|
|
104
|
+
maturity: Time to maturity in years (optional if exercise_date provided).
|
|
105
|
+
exercise_date: Expiration date (optional if maturity provided).
|
|
106
|
+
settlement_date: Settlement date.
|
|
107
|
+
participation_rate: Participation in the capped no-hit payoff.
|
|
108
|
+
knock_out_rebate: Fixed payoff if either barrier is hit.
|
|
109
|
+
no_hit_rebate: Fixed payoff added if no barrier is hit.
|
|
110
|
+
pay_at_hit: If True, knock-out rebate is paid immediately on hit.
|
|
111
|
+
If False, it is paid at expiry.
|
|
112
|
+
observation_type: EXPIRY, DISCRETE, or CONTINUOUS monitoring.
|
|
113
|
+
observation_dates: Discrete observation times in year fractions.
|
|
114
|
+
observation_schedule: Preferred discrete observation schedule.
|
|
115
|
+
observation_frequency: Frequency for generated discrete schedules.
|
|
116
|
+
use_business_days_for_frequency: Use business-day spacing for frequency.
|
|
117
|
+
business_days_in_year: Business-day count for generated schedules.
|
|
118
|
+
contract_multiplier: Underlying units represented by one contract.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
ValidationError: If parameters are invalid.
|
|
122
|
+
"""
|
|
123
|
+
if maturity is None and exercise_date is None:
|
|
124
|
+
maturity = 0.0
|
|
125
|
+
elif maturity is None:
|
|
126
|
+
maturity = 0.0
|
|
127
|
+
|
|
128
|
+
self.upper_barrier = upper_barrier
|
|
129
|
+
self.lower_barrier = lower_barrier
|
|
130
|
+
self.participation_rate = participation_rate
|
|
131
|
+
self.knock_out_rebate = knock_out_rebate
|
|
132
|
+
self.no_hit_rebate = no_hit_rebate
|
|
133
|
+
self.pay_at_hit = pay_at_hit
|
|
134
|
+
self.observation_type = observation_type
|
|
135
|
+
self.observation_dates = observation_dates
|
|
136
|
+
self.observation_schedule = observation_schedule
|
|
137
|
+
self.observation_frequency = observation_frequency
|
|
138
|
+
self.use_business_days_for_frequency = use_business_days_for_frequency
|
|
139
|
+
self.business_days_in_year = business_days_in_year
|
|
140
|
+
|
|
141
|
+
super().__init__(
|
|
142
|
+
strike=strike,
|
|
143
|
+
option_type=option_type,
|
|
144
|
+
exercise_type=ExerciseType.EUROPEAN,
|
|
145
|
+
maturity=maturity,
|
|
146
|
+
exercise_date=exercise_date,
|
|
147
|
+
settlement_date=settlement_date,
|
|
148
|
+
contract_multiplier=contract_multiplier,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
def validate(self) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Validate double sharkfin option parameters.
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
ValidationError: If any parameter is invalid.
|
|
157
|
+
"""
|
|
158
|
+
super().validate()
|
|
159
|
+
|
|
160
|
+
if self.upper_barrier <= 0:
|
|
161
|
+
raise ValidationError(
|
|
162
|
+
f"Upper barrier must be positive, got {self.upper_barrier}"
|
|
163
|
+
)
|
|
164
|
+
if self.lower_barrier <= 0:
|
|
165
|
+
raise ValidationError(
|
|
166
|
+
f"Lower barrier must be positive, got {self.lower_barrier}"
|
|
167
|
+
)
|
|
168
|
+
if self.lower_barrier >= self.upper_barrier:
|
|
169
|
+
raise ValidationError(
|
|
170
|
+
f"Lower barrier ({self.lower_barrier}) must be less than "
|
|
171
|
+
f"upper barrier ({self.upper_barrier})"
|
|
172
|
+
)
|
|
173
|
+
if self.participation_rate < 0:
|
|
174
|
+
raise ValidationError(
|
|
175
|
+
f"Participation rate must be non-negative, got {self.participation_rate}"
|
|
176
|
+
)
|
|
177
|
+
if self.knock_out_rebate < 0:
|
|
178
|
+
raise ValidationError(
|
|
179
|
+
f"Knock-out rebate must be non-negative, got {self.knock_out_rebate}"
|
|
180
|
+
)
|
|
181
|
+
if self.no_hit_rebate < 0:
|
|
182
|
+
raise ValidationError(
|
|
183
|
+
f"No-hit rebate must be non-negative, got {self.no_hit_rebate}"
|
|
184
|
+
)
|
|
185
|
+
if not isinstance(self.pay_at_hit, bool):
|
|
186
|
+
raise ValidationError(f"pay_at_hit must be boolean, got {self.pay_at_hit}")
|
|
187
|
+
if not isinstance(self.observation_type, ObservationType):
|
|
188
|
+
raise ValidationError(f"Invalid observation type: {self.observation_type}")
|
|
189
|
+
if not isinstance(self.observation_frequency, ObservationFrequency):
|
|
190
|
+
raise ValidationError(
|
|
191
|
+
f"Invalid observation frequency: {self.observation_frequency}"
|
|
192
|
+
)
|
|
193
|
+
if self.business_days_in_year <= 0:
|
|
194
|
+
raise ValidationError(
|
|
195
|
+
f"business_days_in_year must be positive, got {self.business_days_in_year}"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
self._validate_strike_in_corridor()
|
|
199
|
+
self._normalize_observation_schedule()
|
|
200
|
+
|
|
201
|
+
def _validate_strike_in_corridor(self) -> None:
|
|
202
|
+
"""Validate that the strike is inside the double sharkfin corridor."""
|
|
203
|
+
if not (self.lower_barrier < self.strike < self.upper_barrier):
|
|
204
|
+
raise ValidationError(
|
|
205
|
+
"Double sharkfin requires lower_barrier < strike < upper_barrier, "
|
|
206
|
+
f"got lower_barrier={self.lower_barrier}, strike={self.strike}, "
|
|
207
|
+
f"upper_barrier={self.upper_barrier}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def _normalize_observation_schedule(self) -> None:
|
|
211
|
+
"""Normalize discrete monitoring inputs into ObservationSchedule."""
|
|
212
|
+
if self.observation_type in (ObservationType.CONTINUOUS, ObservationType.EXPIRY):
|
|
213
|
+
if self.observation_schedule is not None:
|
|
214
|
+
raise ValidationError(
|
|
215
|
+
"ObservationSchedule requires DISCRETE observation_type."
|
|
216
|
+
)
|
|
217
|
+
self.observation_dates = self.observation_dates or []
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
if self.observation_schedule is not None:
|
|
221
|
+
normalized_schedule = ObservationSchedule(
|
|
222
|
+
records=[
|
|
223
|
+
ObservationRecord(
|
|
224
|
+
observation_time=rec.observation_time,
|
|
225
|
+
observation_date=rec.observation_date,
|
|
226
|
+
upper_barrier=(
|
|
227
|
+
rec.upper_barrier
|
|
228
|
+
if rec.upper_barrier is not None
|
|
229
|
+
else self.upper_barrier
|
|
230
|
+
),
|
|
231
|
+
lower_barrier=(
|
|
232
|
+
rec.lower_barrier
|
|
233
|
+
if rec.lower_barrier is not None
|
|
234
|
+
else self.lower_barrier
|
|
235
|
+
),
|
|
236
|
+
payoff=(
|
|
237
|
+
rec.payoff
|
|
238
|
+
if rec.payoff is not None
|
|
239
|
+
else self.knock_out_rebate
|
|
240
|
+
),
|
|
241
|
+
return_rate=rec.return_rate,
|
|
242
|
+
is_rate_annualized=rec.is_rate_annualized,
|
|
243
|
+
initial_date=rec.initial_date,
|
|
244
|
+
settlement_date=rec.settlement_date,
|
|
245
|
+
maturity_date=rec.maturity_date,
|
|
246
|
+
day_count_convention=rec.day_count_convention,
|
|
247
|
+
tenor_end=rec.tenor_end,
|
|
248
|
+
day_count_fraction=rec.day_count_fraction,
|
|
249
|
+
)
|
|
250
|
+
for rec in self.observation_schedule.records
|
|
251
|
+
],
|
|
252
|
+
aggregation_mode=self.observation_schedule.aggregation_mode,
|
|
253
|
+
frequency=self.observation_schedule.frequency,
|
|
254
|
+
)
|
|
255
|
+
self._validate_observation_times(normalized_schedule.times)
|
|
256
|
+
normalized_schedule.validate(require_double=True)
|
|
257
|
+
self.observation_schedule = normalized_schedule
|
|
258
|
+
if self.observation_schedule.times:
|
|
259
|
+
self.observation_dates = self.observation_schedule.times
|
|
260
|
+
return
|
|
261
|
+
|
|
262
|
+
if self.observation_dates is None or len(self.observation_dates) == 0:
|
|
263
|
+
self.observation_dates = self._generate_observation_dates()
|
|
264
|
+
self._validate_observation_times(self.observation_dates)
|
|
265
|
+
|
|
266
|
+
self.observation_schedule = ObservationSchedule.from_legacy(
|
|
267
|
+
observation_dates=self.observation_dates,
|
|
268
|
+
default_barrier=None,
|
|
269
|
+
default_payoff=self.knock_out_rebate,
|
|
270
|
+
aggregation_mode=ObservationAggregation.STOP_FIRST_HIT,
|
|
271
|
+
frequency=self.observation_frequency,
|
|
272
|
+
upper_barrier=self.upper_barrier,
|
|
273
|
+
lower_barrier=self.lower_barrier,
|
|
274
|
+
)
|
|
275
|
+
self.observation_dates = self.observation_schedule.times
|
|
276
|
+
|
|
277
|
+
def _validate_observation_times(self, times: List[float]) -> None:
|
|
278
|
+
"""Validate numeric observation times when present."""
|
|
279
|
+
if any(t < 0 for t in times):
|
|
280
|
+
raise ValidationError("Observation dates must be non-negative.")
|
|
281
|
+
if times != sorted(times):
|
|
282
|
+
raise ValidationError("Observation dates must be sorted in ascending order.")
|
|
283
|
+
|
|
284
|
+
def _generate_observation_dates(self) -> List[float]:
|
|
285
|
+
"""
|
|
286
|
+
Generate regular discrete observation times from observation_frequency.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
Observation times in years.
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
ValidationError: If a schedule cannot be generated.
|
|
293
|
+
"""
|
|
294
|
+
if self.observation_frequency == ObservationFrequency.CUSTOM:
|
|
295
|
+
raise ValidationError(
|
|
296
|
+
"Observation dates required for discrete double sharkfin monitoring "
|
|
297
|
+
"when observation_frequency is CUSTOM."
|
|
298
|
+
)
|
|
299
|
+
if self.maturity is None or self.maturity <= 0:
|
|
300
|
+
raise ValidationError(
|
|
301
|
+
"Positive maturity is required to generate discrete observations."
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
days_in_year = (
|
|
305
|
+
self.business_days_in_year
|
|
306
|
+
if self.use_business_days_for_frequency
|
|
307
|
+
else 365.0
|
|
308
|
+
)
|
|
309
|
+
dt = self.observation_frequency.to_year_fraction(
|
|
310
|
+
use_business_days=self.use_business_days_for_frequency,
|
|
311
|
+
days_in_year=days_in_year,
|
|
312
|
+
)
|
|
313
|
+
if dt <= 0:
|
|
314
|
+
raise ValidationError(f"Observation frequency produced invalid dt={dt}")
|
|
315
|
+
|
|
316
|
+
count = max(1, int(ceil(self.maturity / dt)))
|
|
317
|
+
times = [min((idx + 1) * dt, self.maturity) for idx in range(count)]
|
|
318
|
+
if not is_close(times[-1], self.maturity):
|
|
319
|
+
times.append(self.maturity)
|
|
320
|
+
return times
|
|
321
|
+
|
|
322
|
+
def is_barrier_hit(self, spot: float) -> bool:
|
|
323
|
+
"""
|
|
324
|
+
Check whether a single observed spot hits either sharkfin barrier.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
spot: Observed underlying price.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
True if either barrier is hit.
|
|
331
|
+
"""
|
|
332
|
+
if spot < 0:
|
|
333
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
334
|
+
return spot >= self.upper_barrier or spot <= self.lower_barrier
|
|
335
|
+
|
|
336
|
+
def has_barrier_hit(self, path: Sequence[float]) -> bool:
|
|
337
|
+
"""
|
|
338
|
+
Check whether any observed spot in a path hits either barrier.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
path: Sequence of observed underlying prices.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
True if any observation hits either barrier.
|
|
345
|
+
"""
|
|
346
|
+
if len(path) == 0:
|
|
347
|
+
raise ValidationError("Path must contain at least one observed spot.")
|
|
348
|
+
return any(self.is_barrier_hit(spot) for spot in path)
|
|
349
|
+
|
|
350
|
+
def is_in_corridor(self, spot: float) -> bool:
|
|
351
|
+
"""
|
|
352
|
+
Check whether a spot is strictly inside the sharkfin corridor.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
spot: Observed underlying price.
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
True if spot is between lower and upper barriers.
|
|
359
|
+
"""
|
|
360
|
+
if spot < 0:
|
|
361
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
362
|
+
return self.lower_barrier < spot < self.upper_barrier
|
|
363
|
+
|
|
364
|
+
def get_no_hit_payoff(self, spot: float) -> float:
|
|
365
|
+
"""
|
|
366
|
+
Calculate the capped no-hit payoff.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
spot: Terminal underlying price.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Payoff assuming neither barrier was hit.
|
|
373
|
+
"""
|
|
374
|
+
if spot < 0:
|
|
375
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
376
|
+
|
|
377
|
+
if self.is_call():
|
|
378
|
+
capped_spot = min(spot, self.upper_barrier)
|
|
379
|
+
intrinsic = max(capped_spot - self.strike, 0.0)
|
|
380
|
+
else:
|
|
381
|
+
capped_spot = max(spot, self.lower_barrier)
|
|
382
|
+
intrinsic = max(self.strike - capped_spot, 0.0)
|
|
383
|
+
|
|
384
|
+
payoff = self.no_hit_rebate + self.participation_rate * intrinsic
|
|
385
|
+
return payoff * self.contract_multiplier
|
|
386
|
+
|
|
387
|
+
def get_barrier_payoff(self) -> float:
|
|
388
|
+
"""
|
|
389
|
+
Calculate payoff when either sharkfin barrier has been hit.
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Knock-out rebate scaled by contract multiplier.
|
|
393
|
+
"""
|
|
394
|
+
return self.knock_out_rebate * self.contract_multiplier
|
|
395
|
+
|
|
396
|
+
def get_payoff(self, spot: float, barrier_hit: Optional[bool] = None) -> float:
|
|
397
|
+
"""
|
|
398
|
+
Calculate the terminal double sharkfin payoff.
|
|
399
|
+
|
|
400
|
+
Args:
|
|
401
|
+
spot: Terminal underlying price.
|
|
402
|
+
barrier_hit: Optional observed barrier state. When omitted, terminal
|
|
403
|
+
spot is used to infer a hit. Pricing engines for DISCRETE or
|
|
404
|
+
CONTINUOUS monitoring should pass the path-derived barrier state.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
Sharkfin payoff scaled by contract multiplier.
|
|
408
|
+
"""
|
|
409
|
+
if spot < 0:
|
|
410
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
411
|
+
|
|
412
|
+
if barrier_hit is None:
|
|
413
|
+
barrier_hit = self.is_barrier_hit(spot)
|
|
414
|
+
|
|
415
|
+
if barrier_hit:
|
|
416
|
+
return self.get_barrier_payoff()
|
|
417
|
+
return self.get_no_hit_payoff(spot)
|
|
418
|
+
|
|
419
|
+
def get_observation_times(self) -> List[float]:
|
|
420
|
+
"""
|
|
421
|
+
Get discrete observation times.
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
Observation times in years. Empty for EXPIRY or CONTINUOUS monitoring.
|
|
425
|
+
"""
|
|
426
|
+
if self.observation_schedule is not None:
|
|
427
|
+
return self.observation_schedule.times
|
|
428
|
+
return self.observation_dates or []
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def corridor_width(self) -> float:
|
|
432
|
+
"""Get the width of the corridor in price terms."""
|
|
433
|
+
return self.upper_barrier - self.lower_barrier
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def corridor_width_log(self) -> float:
|
|
437
|
+
"""Get the width of the corridor in log-price terms."""
|
|
438
|
+
from quantark.util.numerical import safe_log
|
|
439
|
+
|
|
440
|
+
return safe_log(self.upper_barrier / self.lower_barrier)
|
|
441
|
+
|
|
442
|
+
def time_shift(self, time_bump: float, bumped_date: datetime, pricing_env) -> bool:
|
|
443
|
+
"""Shift observation schedule and maturity for theta bumping."""
|
|
444
|
+
schedule = getattr(self, "observation_schedule", None)
|
|
445
|
+
if schedule is not None:
|
|
446
|
+
if schedule.uses_dates():
|
|
447
|
+
pricing_env.valuation_date = bumped_date
|
|
448
|
+
bumped_schedule = schedule.time_shift(time_bump, bumped_date)
|
|
449
|
+
if bumped_schedule is None:
|
|
450
|
+
return True
|
|
451
|
+
self.observation_schedule = bumped_schedule
|
|
452
|
+
if bumped_schedule.uses_times():
|
|
453
|
+
self.observation_dates = bumped_schedule.times
|
|
454
|
+
|
|
455
|
+
if getattr(self, "exercise_date", None) is None and self.maturity is not None:
|
|
456
|
+
self.maturity -= time_bump
|
|
457
|
+
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
def __repr__(self) -> str:
|
|
461
|
+
return (
|
|
462
|
+
"DoubleSharkfinOption("
|
|
463
|
+
f"{self.option_type}, K={self.strike:.2f}, "
|
|
464
|
+
f"L={self.lower_barrier:.2f}, U={self.upper_barrier:.2f}, "
|
|
465
|
+
f"T={self.maturity:.4f})"
|
|
466
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
European vanilla 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 EuropeanVanillaOption(BaseEquityOption):
|
|
15
|
+
"""
|
|
16
|
+
European vanilla call or put option.
|
|
17
|
+
|
|
18
|
+
A European option can only be exercised at maturity.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
strike: Strike price
|
|
22
|
+
maturity: Time to maturity in years
|
|
23
|
+
option_type: CALL or PUT
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
strike: float,
|
|
29
|
+
option_type: OptionType,
|
|
30
|
+
maturity: Optional[float] = None,
|
|
31
|
+
exercise_date: Optional[datetime] = None,
|
|
32
|
+
settlement_date: Optional[datetime] = None,
|
|
33
|
+
contract_multiplier: float = 1.0,
|
|
34
|
+
):
|
|
35
|
+
"""
|
|
36
|
+
Initialize European vanilla option.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
strike: Strike price
|
|
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
|
+
# Default maturity to 0 if not provided (will be validated)
|
|
49
|
+
if maturity is None and exercise_date is None:
|
|
50
|
+
maturity = 0.0 # Will trigger validation error
|
|
51
|
+
elif maturity is None:
|
|
52
|
+
maturity = 0.0 # Placeholder when using dates
|
|
53
|
+
|
|
54
|
+
super().__init__(
|
|
55
|
+
strike=strike,
|
|
56
|
+
maturity=maturity,
|
|
57
|
+
option_type=option_type,
|
|
58
|
+
exercise_type=ExerciseType.EUROPEAN,
|
|
59
|
+
exercise_date=exercise_date,
|
|
60
|
+
settlement_date=settlement_date,
|
|
61
|
+
contract_multiplier=contract_multiplier,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def get_payoff(self, spot: float) -> float:
|
|
65
|
+
"""
|
|
66
|
+
Calculate the option payoff at maturity.
|
|
67
|
+
|
|
68
|
+
For a call: max(S - K, 0)
|
|
69
|
+
For a put: max(K - S, 0)
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
spot: Spot price at maturity
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Option payoff
|
|
76
|
+
"""
|
|
77
|
+
if spot < 0:
|
|
78
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
79
|
+
|
|
80
|
+
if self.is_call():
|
|
81
|
+
intrinsic = max(spot - self.strike, 0.0)
|
|
82
|
+
else: # put
|
|
83
|
+
intrinsic = max(self.strike - spot, 0.0)
|
|
84
|
+
|
|
85
|
+
return intrinsic * self.contract_multiplier
|
|
86
|
+
|
|
87
|
+
def intrinsic_value(self, spot: float) -> float:
|
|
88
|
+
"""
|
|
89
|
+
Calculate the intrinsic value of the option.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
spot: Current spot price
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Intrinsic value
|
|
96
|
+
"""
|
|
97
|
+
return self.get_payoff(spot)
|
|
98
|
+
|
|
99
|
+
def __repr__(self):
|
|
100
|
+
return (
|
|
101
|
+
f"EuropeanVanillaOption("
|
|
102
|
+
f"{self.option_type}, K={self.strike:.2f}, T={self.maturity:.4f})"
|
|
103
|
+
)
|