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,114 @@
|
|
|
1
|
+
"""
|
|
2
|
+
American option implementation.
|
|
3
|
+
|
|
4
|
+
American options can be exercised at any time before maturity.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from .base_equity_option import BaseEquityOption
|
|
11
|
+
from quantark.util.enum import OptionType, ExerciseType
|
|
12
|
+
from quantark.util.exceptions import ValidationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AmericanOption(BaseEquityOption):
|
|
17
|
+
"""
|
|
18
|
+
American call or put option.
|
|
19
|
+
|
|
20
|
+
An American option can be exercised at any time up to and including
|
|
21
|
+
the expiration date. This early exercise feature makes American options
|
|
22
|
+
more valuable than their European counterparts (or at least as valuable).
|
|
23
|
+
|
|
24
|
+
For calls on non-dividend-paying stocks, early exercise is never optimal,
|
|
25
|
+
so American calls equal European calls. However, for puts or when there
|
|
26
|
+
are dividends, early exercise may be optimal.
|
|
27
|
+
|
|
28
|
+
Attributes:
|
|
29
|
+
strike: Strike price
|
|
30
|
+
maturity: Time to maturity in years
|
|
31
|
+
option_type: CALL or PUT
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
strike: float,
|
|
37
|
+
option_type: OptionType,
|
|
38
|
+
maturity: Optional[float] = None,
|
|
39
|
+
exercise_date: Optional[datetime] = None,
|
|
40
|
+
settlement_date: Optional[datetime] = None,
|
|
41
|
+
contract_multiplier: float = 1.0,
|
|
42
|
+
):
|
|
43
|
+
"""
|
|
44
|
+
Initialize American option.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
strike: Strike price
|
|
48
|
+
option_type: CALL or PUT
|
|
49
|
+
maturity: Time to maturity in years (optional if exercise_date provided)
|
|
50
|
+
exercise_date: Date when option expires (optional if maturity provided)
|
|
51
|
+
settlement_date: Date when settlement occurs (optional, defaults to exercise_date)
|
|
52
|
+
|
|
53
|
+
Note:
|
|
54
|
+
Either maturity OR exercise_date must be provided (not both).
|
|
55
|
+
"""
|
|
56
|
+
# Default maturity to 0 if not provided (will be validated)
|
|
57
|
+
if maturity is None and exercise_date is None:
|
|
58
|
+
maturity = 0.0 # Will trigger validation error
|
|
59
|
+
elif maturity is None:
|
|
60
|
+
maturity = 0.0 # Placeholder when using dates
|
|
61
|
+
|
|
62
|
+
super().__init__(
|
|
63
|
+
strike=strike,
|
|
64
|
+
maturity=maturity,
|
|
65
|
+
option_type=option_type,
|
|
66
|
+
exercise_type=ExerciseType.AMERICAN,
|
|
67
|
+
exercise_date=exercise_date,
|
|
68
|
+
settlement_date=settlement_date,
|
|
69
|
+
contract_multiplier=contract_multiplier,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def get_payoff(self, spot: float) -> float:
|
|
73
|
+
"""
|
|
74
|
+
Calculate the option payoff at exercise.
|
|
75
|
+
|
|
76
|
+
For a call: max(S - K, 0)
|
|
77
|
+
For a put: max(K - S, 0)
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
spot: Spot price at exercise
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Option payoff
|
|
84
|
+
"""
|
|
85
|
+
if spot < 0:
|
|
86
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
87
|
+
|
|
88
|
+
if self.is_call():
|
|
89
|
+
intrinsic = max(spot - self.strike, 0.0)
|
|
90
|
+
else: # put
|
|
91
|
+
intrinsic = max(self.strike - spot, 0.0)
|
|
92
|
+
|
|
93
|
+
return intrinsic * self.contract_multiplier
|
|
94
|
+
|
|
95
|
+
def intrinsic_value(self, spot: float) -> float:
|
|
96
|
+
"""
|
|
97
|
+
Calculate the intrinsic value of the option.
|
|
98
|
+
|
|
99
|
+
For American options, this is the value that could be obtained
|
|
100
|
+
by exercising immediately.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
spot: Current spot price
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Intrinsic value
|
|
107
|
+
"""
|
|
108
|
+
return self.get_payoff(spot)
|
|
109
|
+
|
|
110
|
+
def __repr__(self):
|
|
111
|
+
return (
|
|
112
|
+
f"AmericanOption("
|
|
113
|
+
f"{self.option_type}, K={self.strike:.2f}, T={self.maturity:.4f})"
|
|
114
|
+
)
|
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Asian option product definition.
|
|
3
|
+
|
|
4
|
+
Asian options are path-dependent options where the payoff depends on the average
|
|
5
|
+
price of the underlying asset over a specified observation period.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import List, Optional, Tuple, Union, TYPE_CHECKING
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from quantark.util.enum import (
|
|
14
|
+
OptionType,
|
|
15
|
+
ExerciseType,
|
|
16
|
+
AveragingType,
|
|
17
|
+
AsianStrikeType,
|
|
18
|
+
)
|
|
19
|
+
from quantark.util.exceptions import ValidationError
|
|
20
|
+
from quantark.util.numerical import is_zero, safe_log, safe_exp
|
|
21
|
+
|
|
22
|
+
from .base_equity_option import BaseEquityOption
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from quantark.priceenv import PricingEnvironment
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
from quantark.util.calendar import calculate_year_fraction
|
|
29
|
+
except ImportError:
|
|
30
|
+
calculate_year_fraction = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class AsianObservationRecord:
|
|
35
|
+
"""
|
|
36
|
+
Single observation entry for Asian option averaging.
|
|
37
|
+
|
|
38
|
+
For past observations (before valuation_date), observed_price should be set.
|
|
39
|
+
For future observations, observed_price should be None.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
observation_time: Observation time as year fraction from initial date
|
|
43
|
+
observation_date: Observation date (alternative to observation_time)
|
|
44
|
+
observed_price: Actual observed price (for past observations only)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
observation_time: Optional[float] = None
|
|
48
|
+
observation_date: Optional[datetime] = None
|
|
49
|
+
observed_price: Optional[float] = None
|
|
50
|
+
|
|
51
|
+
def resolve_time(self, pricing_env: "PricingEnvironment") -> float:
|
|
52
|
+
"""
|
|
53
|
+
Resolve observation time relative to valuation date.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
pricing_env: Pricing environment with valuation date
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Year fraction from valuation date to observation date
|
|
60
|
+
(negative if observation is in the past)
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValidationError: If observation time/date cannot be resolved
|
|
64
|
+
"""
|
|
65
|
+
if self.observation_time is not None:
|
|
66
|
+
return self.observation_time
|
|
67
|
+
if self.observation_date is not None:
|
|
68
|
+
if pricing_env is None or calculate_year_fraction is None:
|
|
69
|
+
raise ValidationError(
|
|
70
|
+
"PricingEnvironment is required to resolve observation_date."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Handle past, current and future dates correctly
|
|
74
|
+
if self.observation_date == pricing_env.valuation_date:
|
|
75
|
+
return 0.0
|
|
76
|
+
elif self.observation_date > pricing_env.valuation_date:
|
|
77
|
+
# Future date: positive year fraction
|
|
78
|
+
return calculate_year_fraction(
|
|
79
|
+
pricing_env.valuation_date,
|
|
80
|
+
self.observation_date,
|
|
81
|
+
pricing_env.day_count_convention,
|
|
82
|
+
pricing_env.bus_days_in_year,
|
|
83
|
+
calendar=getattr(pricing_env, "calendar", None),
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
# Past date: negative year fraction
|
|
87
|
+
# We reverse dates for calculate_year_fraction and negate result
|
|
88
|
+
return -calculate_year_fraction(
|
|
89
|
+
self.observation_date,
|
|
90
|
+
pricing_env.valuation_date,
|
|
91
|
+
pricing_env.day_count_convention,
|
|
92
|
+
pricing_env.bus_days_in_year,
|
|
93
|
+
calendar=getattr(pricing_env, "calendar", None),
|
|
94
|
+
)
|
|
95
|
+
raise ValidationError(
|
|
96
|
+
"AsianObservationRecord requires observation_time or observation_date."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def is_observed(self) -> bool:
|
|
100
|
+
"""Check if this observation has an observed price."""
|
|
101
|
+
return self.observed_price is not None
|
|
102
|
+
|
|
103
|
+
def validate(self) -> None:
|
|
104
|
+
"""Validate the observation record."""
|
|
105
|
+
if self.observation_time is None and self.observation_date is None:
|
|
106
|
+
raise ValidationError(
|
|
107
|
+
"AsianObservationRecord must provide observation_time or observation_date."
|
|
108
|
+
)
|
|
109
|
+
if self.observed_price is not None and self.observed_price <= 0:
|
|
110
|
+
raise ValidationError(
|
|
111
|
+
f"observed_price must be positive, got {self.observed_price}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass
|
|
116
|
+
class AsianOption(BaseEquityOption):
|
|
117
|
+
"""
|
|
118
|
+
Asian option with arithmetic or geometric averaging.
|
|
119
|
+
|
|
120
|
+
Asian options are path-dependent options where the payoff depends on the
|
|
121
|
+
average price of the underlying asset over a specified observation period.
|
|
122
|
+
|
|
123
|
+
Two main variants:
|
|
124
|
+
- Fixed strike (average price option): payoff based on average vs strike
|
|
125
|
+
- Floating strike (average strike option): payoff based on final spot vs average
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
strike: Strike price (required for fixed strike, optional for floating)
|
|
129
|
+
option_type: CALL or PUT
|
|
130
|
+
asian_strike_type: FIXED or FLOATING
|
|
131
|
+
averaging_type: ARITHMETIC or GEOMETRIC
|
|
132
|
+
observation_times: List of observation times as year fractions (legacy)
|
|
133
|
+
observation_dates: List of observation dates (legacy, alternative to times)
|
|
134
|
+
observation_records: List of AsianObservationRecord (preferred, supports historical prices)
|
|
135
|
+
num_observations: Number of observations for uniform schedule (default: 12).
|
|
136
|
+
Set to None for continuous averaging.
|
|
137
|
+
maturity: Time to maturity in years
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
# Asian-specific parameters
|
|
141
|
+
asian_strike_type: AsianStrikeType = AsianStrikeType.FIXED
|
|
142
|
+
averaging_type: AveragingType = AveragingType.ARITHMETIC
|
|
143
|
+
observation_times: Optional[List[float]] = None
|
|
144
|
+
observation_dates: Optional[List[datetime]] = None
|
|
145
|
+
observation_records: Optional[List[AsianObservationRecord]] = None
|
|
146
|
+
num_observations: Optional[int] = 12
|
|
147
|
+
|
|
148
|
+
def __init__(
|
|
149
|
+
self,
|
|
150
|
+
strike: float = 0.0,
|
|
151
|
+
option_type: OptionType = OptionType.CALL,
|
|
152
|
+
asian_strike_type: AsianStrikeType = AsianStrikeType.FIXED,
|
|
153
|
+
averaging_type: AveragingType = AveragingType.ARITHMETIC,
|
|
154
|
+
maturity: Optional[float] = None,
|
|
155
|
+
exercise_date: Optional[datetime] = None,
|
|
156
|
+
observation_times: Optional[List[float]] = None,
|
|
157
|
+
observation_dates: Optional[List[datetime]] = None,
|
|
158
|
+
observation_records: Optional[List[AsianObservationRecord]] = None,
|
|
159
|
+
num_observations: Optional[int] = 12,
|
|
160
|
+
initial_price: float = 0.0,
|
|
161
|
+
contract_multiplier: float = 1.0,
|
|
162
|
+
):
|
|
163
|
+
"""
|
|
164
|
+
Initialize Asian option.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
strike: Strike price (required for fixed strike options)
|
|
168
|
+
option_type: CALL or PUT
|
|
169
|
+
asian_strike_type: FIXED (average price) or FLOATING (average strike)
|
|
170
|
+
averaging_type: ARITHMETIC or GEOMETRIC
|
|
171
|
+
maturity: Time to maturity in years (optional if exercise_date provided)
|
|
172
|
+
exercise_date: Date when option expires (optional if maturity provided)
|
|
173
|
+
observation_times: List of observation times as year fractions (legacy)
|
|
174
|
+
observation_dates: List of observation dates (legacy, alternative to times)
|
|
175
|
+
observation_records: List of AsianObservationRecord with optional observed prices
|
|
176
|
+
num_observations: Number of observations for uniform schedule (default: 12)
|
|
177
|
+
initial_price: Reference/initial underlying price
|
|
178
|
+
contract_multiplier: Underlying units represented by one contract
|
|
179
|
+
"""
|
|
180
|
+
self.asian_strike_type = asian_strike_type
|
|
181
|
+
self.averaging_type = averaging_type
|
|
182
|
+
self.observation_times = observation_times
|
|
183
|
+
self.observation_dates = observation_dates
|
|
184
|
+
self.observation_records = observation_records
|
|
185
|
+
self.num_observations = num_observations
|
|
186
|
+
|
|
187
|
+
# For floating strike, strike can be zero (strike is the average)
|
|
188
|
+
if asian_strike_type == AsianStrikeType.FLOATING and strike == 0.0:
|
|
189
|
+
strike = 1.0 # Placeholder, won't be used in payoff
|
|
190
|
+
|
|
191
|
+
super().__init__(
|
|
192
|
+
strike=strike,
|
|
193
|
+
option_type=option_type,
|
|
194
|
+
exercise_type=ExerciseType.EUROPEAN, # Asian options are European-style
|
|
195
|
+
maturity=maturity,
|
|
196
|
+
exercise_date=exercise_date,
|
|
197
|
+
initial_price=initial_price,
|
|
198
|
+
contract_multiplier=contract_multiplier,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
def validate(self) -> None:
|
|
202
|
+
"""
|
|
203
|
+
Validate Asian option parameters.
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
ValidationError: If any parameter is invalid
|
|
207
|
+
"""
|
|
208
|
+
# Skip strike validation for floating strike options
|
|
209
|
+
if self.asian_strike_type == AsianStrikeType.FIXED:
|
|
210
|
+
self._validate_strike()
|
|
211
|
+
|
|
212
|
+
self._validate_maturity_dates()
|
|
213
|
+
self._validate_date_ordering()
|
|
214
|
+
self._validate_types()
|
|
215
|
+
self._validate_tenor_end()
|
|
216
|
+
self._validate_contract_multiplier()
|
|
217
|
+
self._validate_asian_parameters()
|
|
218
|
+
|
|
219
|
+
def _validate_asian_parameters(self) -> None:
|
|
220
|
+
"""Validate Asian-specific parameters."""
|
|
221
|
+
# Validate asian_strike_type
|
|
222
|
+
if not isinstance(self.asian_strike_type, AsianStrikeType):
|
|
223
|
+
raise ValidationError(f"Invalid asian_strike_type: {self.asian_strike_type}")
|
|
224
|
+
|
|
225
|
+
# Validate averaging_type
|
|
226
|
+
if not isinstance(self.averaging_type, AveragingType):
|
|
227
|
+
raise ValidationError(f"Invalid averaging_type: {self.averaging_type}")
|
|
228
|
+
|
|
229
|
+
# Validate observation times if provided
|
|
230
|
+
if self.observation_times is not None:
|
|
231
|
+
if len(self.observation_times) == 0:
|
|
232
|
+
raise ValidationError("observation_times cannot be empty")
|
|
233
|
+
if any(t < 0 for t in self.observation_times):
|
|
234
|
+
raise ValidationError("observation_times must be non-negative")
|
|
235
|
+
if self.observation_times != sorted(self.observation_times):
|
|
236
|
+
raise ValidationError("observation_times must be sorted in ascending order")
|
|
237
|
+
|
|
238
|
+
# Validate observation dates if provided
|
|
239
|
+
if self.observation_dates is not None:
|
|
240
|
+
if len(self.observation_dates) == 0:
|
|
241
|
+
raise ValidationError("observation_dates cannot be empty")
|
|
242
|
+
if self.observation_dates != sorted(self.observation_dates):
|
|
243
|
+
raise ValidationError("observation_dates must be sorted in ascending order")
|
|
244
|
+
|
|
245
|
+
# Validate observation records if provided
|
|
246
|
+
if self.observation_records is not None:
|
|
247
|
+
if len(self.observation_records) == 0:
|
|
248
|
+
raise ValidationError("observation_records cannot be empty")
|
|
249
|
+
for rec in self.observation_records:
|
|
250
|
+
rec.validate()
|
|
251
|
+
|
|
252
|
+
# Validate num_observations
|
|
253
|
+
if self.num_observations is not None and self.num_observations < 1:
|
|
254
|
+
raise ValidationError(f"num_observations must be >= 1, got {self.num_observations}")
|
|
255
|
+
|
|
256
|
+
def get_observation_times(self, maturity: Optional[float] = None) -> List[float]:
|
|
257
|
+
"""
|
|
258
|
+
Get observation times as year fractions (legacy method).
|
|
259
|
+
|
|
260
|
+
If observation_times is specified, returns it directly.
|
|
261
|
+
Otherwise, generates uniform observations from 0 to maturity.
|
|
262
|
+
|
|
263
|
+
Note: For options with historical observations, use resolve_observations() instead.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
maturity: Time to maturity (used if generating uniform schedule)
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
List of observation times as year fractions
|
|
270
|
+
"""
|
|
271
|
+
if self.observation_times is not None:
|
|
272
|
+
return self.observation_times
|
|
273
|
+
|
|
274
|
+
# Generate uniform schedule
|
|
275
|
+
T = maturity if maturity is not None else self.maturity
|
|
276
|
+
if T is None or T <= 0:
|
|
277
|
+
raise ValidationError("Cannot generate observation schedule: maturity not set")
|
|
278
|
+
|
|
279
|
+
# If num_observations is None, signify continuous schedule
|
|
280
|
+
if self.num_observations is None:
|
|
281
|
+
return []
|
|
282
|
+
|
|
283
|
+
# Generate n equally-spaced observations from 0 to T (inclusive of T)
|
|
284
|
+
return list(np.linspace(0, T, self.num_observations + 1)[1:])
|
|
285
|
+
|
|
286
|
+
def resolve_observations(
|
|
287
|
+
self,
|
|
288
|
+
pricing_env: "PricingEnvironment",
|
|
289
|
+
) -> Tuple[List[float], List[float], int]:
|
|
290
|
+
"""
|
|
291
|
+
Resolve observation records into past and future observations.
|
|
292
|
+
|
|
293
|
+
This method separates already-observed prices from future observation times,
|
|
294
|
+
accounting for the valuation date in pricing_env.
|
|
295
|
+
|
|
296
|
+
For observations with observation_time (year fractions from initial date):
|
|
297
|
+
- Times <= 0 are considered past (already observed)
|
|
298
|
+
- Times > 0 are considered future (to be simulated)
|
|
299
|
+
|
|
300
|
+
Args:
|
|
301
|
+
pricing_env: Pricing environment with valuation date
|
|
302
|
+
|
|
303
|
+
Returns:
|
|
304
|
+
Tuple of:
|
|
305
|
+
- past_prices: List of already-observed prices
|
|
306
|
+
- future_times: List of future observation times (relative to valuation date)
|
|
307
|
+
- total_observations: Total number of observations for averaging
|
|
308
|
+
"""
|
|
309
|
+
if self.observation_records is not None:
|
|
310
|
+
return self._resolve_from_records(pricing_env)
|
|
311
|
+
else:
|
|
312
|
+
return self._resolve_from_legacy(pricing_env)
|
|
313
|
+
|
|
314
|
+
def _resolve_from_records(
|
|
315
|
+
self,
|
|
316
|
+
pricing_env: "PricingEnvironment",
|
|
317
|
+
) -> Tuple[List[float], List[float], int]:
|
|
318
|
+
"""Resolve observations from observation_records."""
|
|
319
|
+
past_prices: List[float] = []
|
|
320
|
+
future_times: List[float] = []
|
|
321
|
+
|
|
322
|
+
for rec in self.observation_records:
|
|
323
|
+
t = rec.resolve_time(pricing_env)
|
|
324
|
+
|
|
325
|
+
if t <= 0:
|
|
326
|
+
# Past observation - must have observed price
|
|
327
|
+
if rec.observed_price is None:
|
|
328
|
+
raise ValidationError(
|
|
329
|
+
f"Past observation (t={t}) must have observed_price set"
|
|
330
|
+
)
|
|
331
|
+
past_prices.append(rec.observed_price)
|
|
332
|
+
else:
|
|
333
|
+
# Future observation
|
|
334
|
+
if rec.observed_price is not None:
|
|
335
|
+
raise ValidationError(
|
|
336
|
+
f"Future observation (t={t}) should not have observed_price set"
|
|
337
|
+
)
|
|
338
|
+
future_times.append(t)
|
|
339
|
+
|
|
340
|
+
total_observations = len(past_prices) + len(future_times)
|
|
341
|
+
return past_prices, future_times, total_observations
|
|
342
|
+
|
|
343
|
+
def _resolve_from_legacy(
|
|
344
|
+
self,
|
|
345
|
+
pricing_env: "PricingEnvironment",
|
|
346
|
+
) -> Tuple[List[float], List[float], int]:
|
|
347
|
+
"""Resolve observations from legacy observation_times/observation_dates."""
|
|
348
|
+
T = self.get_maturity(pricing_env)
|
|
349
|
+
obs_times = self.get_observation_times(T)
|
|
350
|
+
|
|
351
|
+
# All observations are future (no historical prices in legacy mode)
|
|
352
|
+
past_prices: List[float] = []
|
|
353
|
+
|
|
354
|
+
if self.num_observations is None:
|
|
355
|
+
# Continuous mode
|
|
356
|
+
future_times = []
|
|
357
|
+
total_observations = 0 # Signifies continuous
|
|
358
|
+
else:
|
|
359
|
+
# Legacy mode has no historical prices, so times at valuation (t=0)
|
|
360
|
+
# are treated as known-at-start observations (deterministic under spot).
|
|
361
|
+
future_times = [t for t in obs_times if t >= 0]
|
|
362
|
+
total_observations = len(obs_times)
|
|
363
|
+
|
|
364
|
+
return past_prices, future_times, total_observations
|
|
365
|
+
|
|
366
|
+
def has_past_observations(self, pricing_env: "PricingEnvironment") -> bool:
|
|
367
|
+
"""
|
|
368
|
+
Check if there are any past observations with recorded prices.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
pricing_env: Pricing environment with valuation date
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
True if there are past observations, False otherwise
|
|
375
|
+
"""
|
|
376
|
+
past_prices, _, _ = self.resolve_observations(pricing_env)
|
|
377
|
+
return len(past_prices) > 0
|
|
378
|
+
|
|
379
|
+
def get_past_average(self, pricing_env: "PricingEnvironment") -> Optional[float]:
|
|
380
|
+
"""
|
|
381
|
+
Get the average of past observations if any exist.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
pricing_env: Pricing environment with valuation date
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
Average of past observations, or None if no past observations
|
|
388
|
+
"""
|
|
389
|
+
past_prices, _, _ = self.resolve_observations(pricing_env)
|
|
390
|
+
if len(past_prices) == 0:
|
|
391
|
+
return None
|
|
392
|
+
return self.get_average(past_prices)
|
|
393
|
+
|
|
394
|
+
def get_average(
|
|
395
|
+
self,
|
|
396
|
+
prices: Union[List[float], np.ndarray],
|
|
397
|
+
) -> float:
|
|
398
|
+
"""
|
|
399
|
+
Compute the average of observed prices.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
prices: List or array of observed prices
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Average price (arithmetic or geometric based on averaging_type)
|
|
406
|
+
|
|
407
|
+
Raises:
|
|
408
|
+
ValidationError: If prices is empty or contains invalid values
|
|
409
|
+
"""
|
|
410
|
+
if len(prices) == 0:
|
|
411
|
+
raise ValidationError("prices cannot be empty")
|
|
412
|
+
|
|
413
|
+
prices_arr = np.asarray(prices)
|
|
414
|
+
|
|
415
|
+
if self.averaging_type == AveragingType.ARITHMETIC:
|
|
416
|
+
return float(np.mean(prices_arr))
|
|
417
|
+
else: # GEOMETRIC
|
|
418
|
+
if np.any(prices_arr <= 0):
|
|
419
|
+
raise ValidationError("All prices must be positive for geometric averaging")
|
|
420
|
+
# Use log-sum-exp for numerical stability
|
|
421
|
+
log_prices = np.log(prices_arr)
|
|
422
|
+
return float(safe_exp(np.mean(log_prices)))
|
|
423
|
+
|
|
424
|
+
def get_payoff(
|
|
425
|
+
self,
|
|
426
|
+
spot: float,
|
|
427
|
+
observed_prices: Optional[Union[List[float], np.ndarray]] = None,
|
|
428
|
+
average: Optional[float] = None,
|
|
429
|
+
) -> float:
|
|
430
|
+
"""
|
|
431
|
+
Calculate the option payoff at maturity.
|
|
432
|
+
|
|
433
|
+
For fixed strike (average price option):
|
|
434
|
+
Call: max(average - K, 0)
|
|
435
|
+
Put: max(K - average, 0)
|
|
436
|
+
|
|
437
|
+
For floating strike (average strike option):
|
|
438
|
+
Call: max(spot - average, 0)
|
|
439
|
+
Put: max(average - spot, 0)
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
spot: Spot price at maturity
|
|
443
|
+
observed_prices: List of observed prices (used to compute average)
|
|
444
|
+
average: Pre-computed average (alternative to observed_prices)
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Option payoff
|
|
448
|
+
|
|
449
|
+
Raises:
|
|
450
|
+
ValidationError: If neither observed_prices nor average is provided
|
|
451
|
+
"""
|
|
452
|
+
if spot < 0:
|
|
453
|
+
raise ValidationError(f"Spot price must be non-negative, got {spot}")
|
|
454
|
+
|
|
455
|
+
# Get average price
|
|
456
|
+
if average is not None:
|
|
457
|
+
avg = average
|
|
458
|
+
elif observed_prices is not None:
|
|
459
|
+
avg = self.get_average(observed_prices)
|
|
460
|
+
else:
|
|
461
|
+
raise ValidationError("Either observed_prices or average must be provided")
|
|
462
|
+
|
|
463
|
+
# Calculate payoff based on strike type
|
|
464
|
+
if self.asian_strike_type == AsianStrikeType.FIXED:
|
|
465
|
+
# Fixed strike (average price option)
|
|
466
|
+
if self.is_call():
|
|
467
|
+
payoff = max(avg - self.strike, 0.0)
|
|
468
|
+
else:
|
|
469
|
+
payoff = max(self.strike - avg, 0.0)
|
|
470
|
+
else:
|
|
471
|
+
# Floating strike (average strike option)
|
|
472
|
+
if self.is_call():
|
|
473
|
+
payoff = max(spot - avg, 0.0)
|
|
474
|
+
else:
|
|
475
|
+
payoff = max(avg - spot, 0.0)
|
|
476
|
+
|
|
477
|
+
return payoff * self.contract_multiplier
|
|
478
|
+
|
|
479
|
+
def intrinsic_value(
|
|
480
|
+
self,
|
|
481
|
+
spot: float,
|
|
482
|
+
observed_prices: Optional[Union[List[float], np.ndarray]] = None,
|
|
483
|
+
average: Optional[float] = None,
|
|
484
|
+
) -> float:
|
|
485
|
+
"""
|
|
486
|
+
Calculate the intrinsic value of the option.
|
|
487
|
+
|
|
488
|
+
For Asian options, intrinsic value equals payoff since they're European-style.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
spot: Current spot price
|
|
492
|
+
observed_prices: List of observed prices
|
|
493
|
+
average: Pre-computed average
|
|
494
|
+
|
|
495
|
+
Returns:
|
|
496
|
+
Intrinsic value
|
|
497
|
+
"""
|
|
498
|
+
return self.get_payoff(spot, observed_prices, average)
|
|
499
|
+
|
|
500
|
+
def is_fixed_strike(self) -> bool:
|
|
501
|
+
"""Check if this is a fixed strike (average price) option."""
|
|
502
|
+
return self.asian_strike_type == AsianStrikeType.FIXED
|
|
503
|
+
|
|
504
|
+
def is_floating_strike(self) -> bool:
|
|
505
|
+
"""Check if this is a floating strike (average strike) option."""
|
|
506
|
+
return self.asian_strike_type == AsianStrikeType.FLOATING
|
|
507
|
+
|
|
508
|
+
def is_arithmetic(self) -> bool:
|
|
509
|
+
"""Check if this uses arithmetic averaging."""
|
|
510
|
+
return self.averaging_type == AveragingType.ARITHMETIC
|
|
511
|
+
|
|
512
|
+
def is_geometric(self) -> bool:
|
|
513
|
+
"""Check if this uses geometric averaging."""
|
|
514
|
+
return self.averaging_type == AveragingType.GEOMETRIC
|
|
515
|
+
|
|
516
|
+
def is_continuous(self) -> bool:
|
|
517
|
+
"""Check if this uses continuous averaging."""
|
|
518
|
+
return self.num_observations is None and self.observation_times is None and self.observation_dates is None and self.observation_records is None
|
|
519
|
+
|
|
520
|
+
def __repr__(self) -> str:
|
|
521
|
+
"""Return string representation of the Asian option."""
|
|
522
|
+
strike_str = f"K={self.strike:.2f}" if self.is_fixed_strike() else "floating"
|
|
523
|
+
maturity_str = (
|
|
524
|
+
f"T={self.maturity:.4f}"
|
|
525
|
+
if self.maturity is not None
|
|
526
|
+
else f"exp={self.exercise_date}"
|
|
527
|
+
)
|
|
528
|
+
return (
|
|
529
|
+
f"AsianOption({self.option_type}, {strike_str}, {maturity_str}, "
|
|
530
|
+
f"{self.asian_strike_type}, {self.averaging_type})"
|
|
531
|
+
)
|