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,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pricing environment that bundles all market data.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from quantark.param import SpotQuote, VolatilitySurface, RateCurve, DividendYield, BasisYield
|
|
9
|
+
from quantark.util.exceptions import MarketDataError
|
|
10
|
+
from quantark.util.calendar import Calendar, DayCountConvention
|
|
11
|
+
from quantark.util.numerical import safe_sqrt
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class PricingEnvironment:
|
|
16
|
+
"""
|
|
17
|
+
Pricing environment containing all market data required for pricing.
|
|
18
|
+
|
|
19
|
+
This class bundles together all market parameters needed for derivative pricing:
|
|
20
|
+
spot price, volatility surface, risk-free rate curve, and dividend yield.
|
|
21
|
+
|
|
22
|
+
For bond pricing, only rate_curve and valuation_date are required.
|
|
23
|
+
For equity derivatives, spot_quote and vol_surface are also required.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
rate_curve: Risk-free rate curve (required)
|
|
27
|
+
valuation_date: Date of valuation (required)
|
|
28
|
+
spot_quote: Current spot price of the underlying (optional, required for equity)
|
|
29
|
+
vol_surface: Volatility surface (optional, required for equity derivatives)
|
|
30
|
+
div_yield: Dividend yield (optional, defaults to zero)
|
|
31
|
+
basis_yield: Annualized basis yield for futures (optional, defaults to None)
|
|
32
|
+
day_count_convention: Convention for calculating year fractions (default: CALENDAR_DAYS)
|
|
33
|
+
bus_days_in_year: Number of business days per year for business day convention (default: 252)
|
|
34
|
+
calendar: Optional business day calendar (used when day_count_convention is BUSINESS_DAYS)
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
rate_curve: RateCurve
|
|
38
|
+
valuation_date: datetime
|
|
39
|
+
spot_quote: Optional[SpotQuote] = None
|
|
40
|
+
vol_surface: Optional[VolatilitySurface] = None
|
|
41
|
+
div_yield: Optional[DividendYield] = None
|
|
42
|
+
basis_yield: Optional[BasisYield] = None
|
|
43
|
+
day_count_convention: DayCountConvention = DayCountConvention.CALENDAR_DAYS
|
|
44
|
+
bus_days_in_year: int = 252
|
|
45
|
+
calendar: Optional[Calendar] = None
|
|
46
|
+
|
|
47
|
+
def __post_init__(self):
|
|
48
|
+
"""Validate pricing environment."""
|
|
49
|
+
if self.rate_curve is None:
|
|
50
|
+
raise MarketDataError("Rate curve is required")
|
|
51
|
+
if self.valuation_date is None:
|
|
52
|
+
raise MarketDataError("Valuation date is required")
|
|
53
|
+
if self.bus_days_in_year <= 0:
|
|
54
|
+
raise MarketDataError(
|
|
55
|
+
f"Business days per year must be positive, got {self.bus_days_in_year}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def spot(self) -> float:
|
|
60
|
+
"""
|
|
61
|
+
Get current spot price.
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
MarketDataError: If spot quote is not available
|
|
65
|
+
"""
|
|
66
|
+
if self.spot_quote is None:
|
|
67
|
+
raise MarketDataError(
|
|
68
|
+
"Spot quote not available in this pricing environment"
|
|
69
|
+
)
|
|
70
|
+
return self.spot_quote.spot
|
|
71
|
+
|
|
72
|
+
def get_vol(self, strike: float, time_to_maturity: float) -> float:
|
|
73
|
+
"""
|
|
74
|
+
Get volatility for given strike and maturity.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
strike: Strike price
|
|
78
|
+
time_to_maturity: Time to maturity in years
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
Implied volatility
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
MarketDataError: If volatility surface is not available
|
|
85
|
+
"""
|
|
86
|
+
if self.vol_surface is None:
|
|
87
|
+
raise MarketDataError(
|
|
88
|
+
"Volatility surface not available in this pricing environment"
|
|
89
|
+
)
|
|
90
|
+
if self.spot_quote is None:
|
|
91
|
+
raise MarketDataError(
|
|
92
|
+
"Spot quote not available in this pricing environment"
|
|
93
|
+
)
|
|
94
|
+
return self.vol_surface.get_vol(strike, time_to_maturity, self.spot)
|
|
95
|
+
|
|
96
|
+
def get_step_volatility(
|
|
97
|
+
self, strike: float, t_start: float, t_end: float
|
|
98
|
+
) -> float:
|
|
99
|
+
"""
|
|
100
|
+
Get effective volatility for a time step from implied vol term structure.
|
|
101
|
+
|
|
102
|
+
Uses total variance difference:
|
|
103
|
+
sigma_step = sqrt((w(t_end) - w(t_start)) / (t_end - t_start))
|
|
104
|
+
where:
|
|
105
|
+
w(t) = sigma_imp(strike, t)^2 * t
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
strike: Reference strike for volatility lookup (often spot)
|
|
109
|
+
t_start: Start time in years from valuation
|
|
110
|
+
t_end: End time in years from valuation
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Effective step volatility (annualized)
|
|
114
|
+
"""
|
|
115
|
+
dt = t_end - t_start
|
|
116
|
+
if dt <= 0:
|
|
117
|
+
return self.get_vol(strike, max(t_end, 0.001))
|
|
118
|
+
|
|
119
|
+
vol_end = self.get_vol(strike, t_end)
|
|
120
|
+
w_end = vol_end * vol_end * t_end
|
|
121
|
+
|
|
122
|
+
if t_start <= 0:
|
|
123
|
+
w_start = 0.0
|
|
124
|
+
else:
|
|
125
|
+
vol_start = self.get_vol(strike, t_start)
|
|
126
|
+
w_start = vol_start * vol_start * t_start
|
|
127
|
+
|
|
128
|
+
var_step = max(0.0, w_end - w_start)
|
|
129
|
+
return safe_sqrt(var_step / dt)
|
|
130
|
+
|
|
131
|
+
def get_rate(self, time_to_maturity: float) -> float:
|
|
132
|
+
"""
|
|
133
|
+
Get risk-free rate for given maturity.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
time_to_maturity: Time to maturity in years
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Risk-free rate
|
|
140
|
+
"""
|
|
141
|
+
return self.rate_curve.get_rate(time_to_maturity)
|
|
142
|
+
|
|
143
|
+
def get_discount_factor(self, time_to_maturity: float) -> float:
|
|
144
|
+
"""
|
|
145
|
+
Get discount factor for given maturity.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
time_to_maturity: Time to maturity in years
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Discount factor
|
|
152
|
+
"""
|
|
153
|
+
return self.rate_curve.get_discount_factor(time_to_maturity)
|
|
154
|
+
|
|
155
|
+
def get_div_yield(self, time_to_maturity: float) -> float:
|
|
156
|
+
"""
|
|
157
|
+
Get dividend yield for given maturity.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
time_to_maturity: Time to maturity in years
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Dividend yield (0 if not specified)
|
|
164
|
+
"""
|
|
165
|
+
if self.div_yield is None:
|
|
166
|
+
return 0.0
|
|
167
|
+
return self.div_yield.get_yield(time_to_maturity)
|
|
168
|
+
|
|
169
|
+
def get_basis_yield(self, time_to_maturity: float) -> float:
|
|
170
|
+
"""
|
|
171
|
+
Get basis yield for given maturity.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
time_to_maturity: Time to maturity in years
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Basis yield (0 if not specified)
|
|
178
|
+
"""
|
|
179
|
+
if self.basis_yield is None:
|
|
180
|
+
return 0.0
|
|
181
|
+
return self.basis_yield.get_basis_yield(time_to_maturity)
|
|
182
|
+
|
|
183
|
+
def __repr__(self):
|
|
184
|
+
parts = [f"valuation_date={self.valuation_date.date()}"]
|
|
185
|
+
|
|
186
|
+
if self.spot_quote is not None:
|
|
187
|
+
parts.append(f"spot={self.spot:.2f}")
|
|
188
|
+
|
|
189
|
+
parts.append(f"rate={self.rate_curve.get_rate(1.0):.2%}")
|
|
190
|
+
|
|
191
|
+
if self.vol_surface is not None and self.spot_quote is not None:
|
|
192
|
+
parts.append(
|
|
193
|
+
f"vol={self.vol_surface.get_vol(self.spot, 1.0, self.spot):.2%}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return f"PricingEnvironment({', '.join(parts)})"
|
quantark/rfq/__init__.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
RFQ module for equity OTC quote solving.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from quantark.rfq.models import (
|
|
6
|
+
RFQEngineSpec,
|
|
7
|
+
RFQInputMode,
|
|
8
|
+
RFQObjectInput,
|
|
9
|
+
RFQQuote,
|
|
10
|
+
RFQQuoteStatus,
|
|
11
|
+
RFQRequest,
|
|
12
|
+
RFQTarget,
|
|
13
|
+
RFQTargetLabel,
|
|
14
|
+
RFQTermsheetInput,
|
|
15
|
+
RFQUnknownSpec,
|
|
16
|
+
)
|
|
17
|
+
from quantark.rfq.service import RFQService, quote_rfq
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"RFQEngineSpec",
|
|
21
|
+
"RFQInputMode",
|
|
22
|
+
"RFQObjectInput",
|
|
23
|
+
"RFQQuote",
|
|
24
|
+
"RFQQuoteStatus",
|
|
25
|
+
"RFQRequest",
|
|
26
|
+
"RFQTarget",
|
|
27
|
+
"RFQTargetLabel",
|
|
28
|
+
"RFQTermsheetInput",
|
|
29
|
+
"RFQUnknownSpec",
|
|
30
|
+
"RFQService",
|
|
31
|
+
"quote_rfq",
|
|
32
|
+
]
|
quantark/rfq/builders.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Normalization helpers for term-sheet RFQ inputs.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Dict
|
|
9
|
+
|
|
10
|
+
from quantark.param import (
|
|
11
|
+
ContinuousDividendYield,
|
|
12
|
+
FlatRateCurve,
|
|
13
|
+
FlatVolSurface,
|
|
14
|
+
SpotQuote,
|
|
15
|
+
)
|
|
16
|
+
from quantark.priceenv import PricingEnvironment
|
|
17
|
+
from quantark.rfq.models import RFQTermsheetInput
|
|
18
|
+
from quantark.rfq.registry import ENGINE_BUILDERS, PRODUCT_BUILDERS
|
|
19
|
+
from quantark.util.exceptions import ValidationError
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def build_product_from_termsheet(termsheet: RFQTermsheetInput) -> Any:
|
|
23
|
+
"""Build product instance from term-sheet input."""
|
|
24
|
+
return PRODUCT_BUILDERS.build(termsheet.product_type, termsheet.product_kwargs)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def build_engine_from_termsheet(termsheet: RFQTermsheetInput) -> Any:
|
|
28
|
+
"""Build engine instance from term-sheet engine spec."""
|
|
29
|
+
return ENGINE_BUILDERS.build(termsheet.engine_spec)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_pricing_env_from_market_kwargs(market_kwargs: Dict[str, Any]) -> PricingEnvironment:
|
|
33
|
+
"""Build pricing environment from a normalized market kwargs mapping."""
|
|
34
|
+
allowed = {
|
|
35
|
+
"valuation_date",
|
|
36
|
+
"rate_curve",
|
|
37
|
+
"rate",
|
|
38
|
+
"spot_quote",
|
|
39
|
+
"spot",
|
|
40
|
+
"asset_name",
|
|
41
|
+
"vol_surface",
|
|
42
|
+
"volatility",
|
|
43
|
+
"div_yield",
|
|
44
|
+
"dividend_yield",
|
|
45
|
+
"q",
|
|
46
|
+
"basis_yield",
|
|
47
|
+
"day_count_convention",
|
|
48
|
+
"bus_days_in_year",
|
|
49
|
+
"calendar",
|
|
50
|
+
}
|
|
51
|
+
unknown = set(market_kwargs) - allowed
|
|
52
|
+
if unknown:
|
|
53
|
+
unknown_list = ", ".join(sorted(unknown))
|
|
54
|
+
raise ValidationError(f"Unsupported market_kwargs: {unknown_list}")
|
|
55
|
+
|
|
56
|
+
valuation_date = market_kwargs.get("valuation_date")
|
|
57
|
+
if valuation_date is None:
|
|
58
|
+
raise ValidationError("market_kwargs.valuation_date is required")
|
|
59
|
+
if not isinstance(valuation_date, datetime):
|
|
60
|
+
raise ValidationError("market_kwargs.valuation_date must be datetime")
|
|
61
|
+
|
|
62
|
+
rate_curve = market_kwargs.get("rate_curve")
|
|
63
|
+
if rate_curve is None:
|
|
64
|
+
rate = market_kwargs.get("rate")
|
|
65
|
+
if rate is None:
|
|
66
|
+
raise ValidationError(
|
|
67
|
+
"market_kwargs requires either rate_curve or rate"
|
|
68
|
+
)
|
|
69
|
+
rate_curve = FlatRateCurve(rate=float(rate))
|
|
70
|
+
|
|
71
|
+
spot_quote = market_kwargs.get("spot_quote")
|
|
72
|
+
if spot_quote is None and "spot" in market_kwargs:
|
|
73
|
+
spot_quote = SpotQuote(
|
|
74
|
+
spot=float(market_kwargs["spot"]),
|
|
75
|
+
timestamp=valuation_date,
|
|
76
|
+
asset_name=market_kwargs.get("asset_name"),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
vol_surface = market_kwargs.get("vol_surface")
|
|
80
|
+
if vol_surface is None and "volatility" in market_kwargs:
|
|
81
|
+
vol_surface = FlatVolSurface(volatility=float(market_kwargs["volatility"]))
|
|
82
|
+
|
|
83
|
+
div_yield = market_kwargs.get("div_yield")
|
|
84
|
+
if div_yield is None:
|
|
85
|
+
if "dividend_yield" in market_kwargs:
|
|
86
|
+
div_yield = ContinuousDividendYield(
|
|
87
|
+
div_yield=float(market_kwargs["dividend_yield"])
|
|
88
|
+
)
|
|
89
|
+
elif "q" in market_kwargs:
|
|
90
|
+
div_yield = ContinuousDividendYield(div_yield=float(market_kwargs["q"]))
|
|
91
|
+
|
|
92
|
+
return PricingEnvironment(
|
|
93
|
+
rate_curve=rate_curve,
|
|
94
|
+
valuation_date=valuation_date,
|
|
95
|
+
spot_quote=spot_quote,
|
|
96
|
+
vol_surface=vol_surface,
|
|
97
|
+
div_yield=div_yield,
|
|
98
|
+
basis_yield=market_kwargs.get("basis_yield"),
|
|
99
|
+
day_count_convention=market_kwargs.get("day_count_convention"),
|
|
100
|
+
bus_days_in_year=market_kwargs.get("bus_days_in_year", 252),
|
|
101
|
+
calendar=market_kwargs.get("calendar"),
|
|
102
|
+
)
|
quantark/rfq/models.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data models for RFQ quote solving.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import Any, Dict, Optional
|
|
11
|
+
|
|
12
|
+
from quantark.asset.equity.engine.base_engine import BaseEngine
|
|
13
|
+
from quantark.priceenv import PricingEnvironment
|
|
14
|
+
from quantark.util.exceptions import ValidationError
|
|
15
|
+
from quantark.util.numerical import is_close
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RFQInputMode(Enum):
|
|
19
|
+
"""Supported RFQ request input modes."""
|
|
20
|
+
|
|
21
|
+
OBJECT = "object"
|
|
22
|
+
TERMSHEET = "termsheet"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RFQTargetLabel(Enum):
|
|
26
|
+
"""Target labels supported by the RFQ solver."""
|
|
27
|
+
|
|
28
|
+
PRICE = "price"
|
|
29
|
+
PREMIUM = "premium"
|
|
30
|
+
REOFFER = "reoffer"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class RFQQuoteStatus(Enum):
|
|
34
|
+
"""RFQ quote lifecycle status for v1."""
|
|
35
|
+
|
|
36
|
+
SUCCESS = "success"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass(frozen=True)
|
|
40
|
+
class RFQUnknownSpec:
|
|
41
|
+
"""Describe the single unknown value to solve for."""
|
|
42
|
+
|
|
43
|
+
field_path: str
|
|
44
|
+
lower_bound: float
|
|
45
|
+
upper_bound: float
|
|
46
|
+
initial_guess: Optional[float] = None
|
|
47
|
+
display_label: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
def __post_init__(self) -> None:
|
|
50
|
+
if not self.field_path:
|
|
51
|
+
raise ValidationError("field_path is required")
|
|
52
|
+
if self.lower_bound >= self.upper_bound:
|
|
53
|
+
raise ValidationError(
|
|
54
|
+
"lower_bound must be strictly less than upper_bound"
|
|
55
|
+
)
|
|
56
|
+
if self.initial_guess is not None:
|
|
57
|
+
if not (self.lower_bound <= self.initial_guess <= self.upper_bound):
|
|
58
|
+
raise ValidationError(
|
|
59
|
+
"initial_guess must lie within [lower_bound, upper_bound]"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class RFQTarget:
|
|
65
|
+
"""Explicit target price objective."""
|
|
66
|
+
|
|
67
|
+
label: RFQTargetLabel
|
|
68
|
+
value: float
|
|
69
|
+
|
|
70
|
+
def __post_init__(self) -> None:
|
|
71
|
+
if not isinstance(self.label, RFQTargetLabel):
|
|
72
|
+
raise ValidationError(f"Invalid RFQ target label: {self.label}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True)
|
|
76
|
+
class RFQObjectInput:
|
|
77
|
+
"""Object-based RFQ input path."""
|
|
78
|
+
|
|
79
|
+
product: Any
|
|
80
|
+
pricing_env: PricingEnvironment
|
|
81
|
+
engine: BaseEngine
|
|
82
|
+
|
|
83
|
+
def __post_init__(self) -> None:
|
|
84
|
+
if self.product is None:
|
|
85
|
+
raise ValidationError("product is required for object RFQ input")
|
|
86
|
+
if self.pricing_env is None:
|
|
87
|
+
raise ValidationError("pricing_env is required for object RFQ input")
|
|
88
|
+
if self.engine is None:
|
|
89
|
+
raise ValidationError("engine is required for object RFQ input")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class RFQEngineSpec:
|
|
94
|
+
"""Term-sheet engine selection."""
|
|
95
|
+
|
|
96
|
+
engine_name: str
|
|
97
|
+
params_type: Optional[str] = None
|
|
98
|
+
params_kwargs: Dict[str, Any] = field(default_factory=dict)
|
|
99
|
+
method: Optional[Any] = None
|
|
100
|
+
engine_kwargs: Dict[str, Any] = field(default_factory=dict)
|
|
101
|
+
|
|
102
|
+
def __post_init__(self) -> None:
|
|
103
|
+
if not self.engine_name:
|
|
104
|
+
raise ValidationError("engine_name is required in engine_spec")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass(frozen=True)
|
|
108
|
+
class RFQTermsheetInput:
|
|
109
|
+
"""Term-sheet RFQ input path."""
|
|
110
|
+
|
|
111
|
+
product_type: str
|
|
112
|
+
product_kwargs: Dict[str, Any]
|
|
113
|
+
market_kwargs: Dict[str, Any]
|
|
114
|
+
engine_spec: RFQEngineSpec
|
|
115
|
+
|
|
116
|
+
def __post_init__(self) -> None:
|
|
117
|
+
if not self.product_type:
|
|
118
|
+
raise ValidationError("product_type is required for term-sheet RFQ input")
|
|
119
|
+
if self.product_kwargs is None:
|
|
120
|
+
raise ValidationError("product_kwargs is required for term-sheet RFQ input")
|
|
121
|
+
if self.market_kwargs is None:
|
|
122
|
+
raise ValidationError("market_kwargs is required for term-sheet RFQ input")
|
|
123
|
+
if self.engine_spec is None:
|
|
124
|
+
raise ValidationError("engine_spec is required for term-sheet RFQ input")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass(frozen=True)
|
|
128
|
+
class RFQRequest:
|
|
129
|
+
"""Canonical RFQ request."""
|
|
130
|
+
|
|
131
|
+
input_mode: RFQInputMode
|
|
132
|
+
unknown: RFQUnknownSpec
|
|
133
|
+
target: RFQTarget
|
|
134
|
+
object_input: Optional[RFQObjectInput] = None
|
|
135
|
+
termsheet_input: Optional[RFQTermsheetInput] = None
|
|
136
|
+
valid_until: Optional[datetime] = None
|
|
137
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
138
|
+
|
|
139
|
+
def __post_init__(self) -> None:
|
|
140
|
+
if not isinstance(self.input_mode, RFQInputMode):
|
|
141
|
+
raise ValidationError(f"Invalid RFQ input mode: {self.input_mode}")
|
|
142
|
+
if self.unknown is None:
|
|
143
|
+
raise ValidationError("unknown is required")
|
|
144
|
+
if self.target is None:
|
|
145
|
+
raise ValidationError("target is required")
|
|
146
|
+
|
|
147
|
+
if self.input_mode == RFQInputMode.OBJECT:
|
|
148
|
+
if self.object_input is None:
|
|
149
|
+
raise ValidationError("object_input is required when input_mode=OBJECT")
|
|
150
|
+
if self.termsheet_input is not None:
|
|
151
|
+
raise ValidationError(
|
|
152
|
+
"termsheet_input must not be supplied when input_mode=OBJECT"
|
|
153
|
+
)
|
|
154
|
+
elif self.input_mode == RFQInputMode.TERMSHEET:
|
|
155
|
+
if self.termsheet_input is None:
|
|
156
|
+
raise ValidationError(
|
|
157
|
+
"termsheet_input is required when input_mode=TERMSHEET"
|
|
158
|
+
)
|
|
159
|
+
if self.object_input is not None:
|
|
160
|
+
raise ValidationError(
|
|
161
|
+
"object_input must not be supplied when input_mode=TERMSHEET"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(frozen=True)
|
|
166
|
+
class RFQQuote:
|
|
167
|
+
"""Successful RFQ quote response."""
|
|
168
|
+
|
|
169
|
+
quote_id: str
|
|
170
|
+
quoted_at: datetime
|
|
171
|
+
status: RFQQuoteStatus
|
|
172
|
+
field_path: str
|
|
173
|
+
field_label: str
|
|
174
|
+
solved_value: float
|
|
175
|
+
target_label: RFQTargetLabel
|
|
176
|
+
target_value: float
|
|
177
|
+
achieved_price: float
|
|
178
|
+
residual: float
|
|
179
|
+
engine_summary: Dict[str, Any]
|
|
180
|
+
request_summary: Dict[str, Any]
|
|
181
|
+
valid_until: Optional[datetime] = None
|
|
182
|
+
|
|
183
|
+
def __post_init__(self) -> None:
|
|
184
|
+
if not self.quote_id:
|
|
185
|
+
raise ValidationError("quote_id is required")
|
|
186
|
+
if not isinstance(self.status, RFQQuoteStatus):
|
|
187
|
+
raise ValidationError(f"Invalid RFQ quote status: {self.status}")
|
|
188
|
+
if not isinstance(self.target_label, RFQTargetLabel):
|
|
189
|
+
raise ValidationError(f"Invalid RFQ target label: {self.target_label}")
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def converged(self) -> bool:
|
|
193
|
+
"""Whether the solved price matches the target within tight tolerance."""
|
|
194
|
+
return is_close(self.achieved_price, self.target_value, abs_tol=1e-8)
|
|
195
|
+
|
|
196
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
197
|
+
"""Serialize quote payload for reporting and tests."""
|
|
198
|
+
return {
|
|
199
|
+
"quote_id": self.quote_id,
|
|
200
|
+
"quoted_at": self.quoted_at.isoformat(),
|
|
201
|
+
"status": self.status.value,
|
|
202
|
+
"field_path": self.field_path,
|
|
203
|
+
"field_label": self.field_label,
|
|
204
|
+
"solved_value": self.solved_value,
|
|
205
|
+
"target_label": self.target_label.value,
|
|
206
|
+
"target_value": self.target_value,
|
|
207
|
+
"achieved_price": self.achieved_price,
|
|
208
|
+
"residual": self.residual,
|
|
209
|
+
"engine_summary": dict(self.engine_summary),
|
|
210
|
+
"request_summary": dict(self.request_summary),
|
|
211
|
+
"valid_until": (
|
|
212
|
+
self.valid_until.isoformat() if self.valid_until is not None else None
|
|
213
|
+
),
|
|
214
|
+
}
|