powerquant-cli 0.2.6__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.
- powerquant_cli-0.2.6.dist-info/METADATA +62 -0
- powerquant_cli-0.2.6.dist-info/RECORD +355 -0
- powerquant_cli-0.2.6.dist-info/WHEEL +4 -0
- powerquant_cli-0.2.6.dist-info/entry_points.txt +2 -0
- powerquant_cli-0.2.6.dist-info/licenses/LICENSE +36 -0
- powerquant_cli-0.2.6.dist-info/licenses/licenses/LICENSE.vnpy +21 -0
- pqdata/__init__.py +6 -0
- pqdata/client.py +389 -0
- pqdata/download.py +60 -0
- pqdata/exceptions.py +14 -0
- pqdata/schema/__init__.py +1 -0
- pqdata/schema/adjustment_schema.py +67 -0
- pqdata/schema/bar_content_schema.py +24 -0
- pqdata/schema/bar_schema.py +56 -0
- pqdata/schema/corporate_action_schema.py +46 -0
- pqdata/schema/history_schema.py +61 -0
- pqdata/schema/pit_daily_window.py +16 -0
- pqdata/schema/query_schema.py +53 -0
- pqdata/schema/quote_schema.py +73 -0
- pqdata/schema/reference_schema.py +48 -0
- pqdata/schema/reliability_schema.py +152 -0
- quant_cli/__init__.py +0 -0
- quant_cli/api.py +39 -0
- quant_cli/app/__init__.py +0 -0
- quant_cli/app/backtest/__init__.py +0 -0
- quant_cli/app/backtest/cli/__init__.py +0 -0
- quant_cli/app/backtest/cli/local_backtest_cli.py +72 -0
- quant_cli/app/backtest/local_backtest_dependency.py +10 -0
- quant_cli/app/backtest/local_backtest_schema.py +49 -0
- quant_cli/app/backtest/repository/__init__.py +0 -0
- quant_cli/app/backtest/repository/backtest_local_repository.py +67 -0
- quant_cli/app/backtest/service/__init__.py +0 -0
- quant_cli/app/backtest/service/local_backtest_service.py +271 -0
- quant_cli/app/data/__init__.py +0 -0
- quant_cli/app/data/cli/__init__.py +0 -0
- quant_cli/app/data/cli/data_cli.py +78 -0
- quant_cli/app/data/data_dependency.py +35 -0
- quant_cli/app/data/repository/__init__.py +0 -0
- quant_cli/app/data/repository/parquet_repository.py +124 -0
- quant_cli/app/data/schema/__init__.py +0 -0
- quant_cli/app/data/schema/dataset_audit_schema.py +37 -0
- quant_cli/app/data/schema/dataset_request_schema.py +28 -0
- quant_cli/app/data/schema/parquet_file_schema.py +17 -0
- quant_cli/app/data/service/__init__.py +0 -0
- quant_cli/app/data/service/bar_cache_service.py +287 -0
- quant_cli/app/data/service/dataset_audit_service.py +137 -0
- quant_cli/app/data/service/dataset_load_service.py +115 -0
- quant_cli/app/data/service/dataset_prepare_service.py +187 -0
- quant_cli/app/factor/__init__.py +0 -0
- quant_cli/app/factor/cli/__init__.py +0 -0
- quant_cli/app/factor/cli/factor_definition_cli.py +81 -0
- quant_cli/app/factor/cli/local_factor_cli.py +102 -0
- quant_cli/app/factor/cli/local_selection_cli.py +58 -0
- quant_cli/app/factor/interface/__init__.py +0 -0
- quant_cli/app/factor/interface/local_selection_store.py +12 -0
- quant_cli/app/factor/local_factor_dependency.py +38 -0
- quant_cli/app/factor/repository/__init__.py +0 -0
- quant_cli/app/factor/repository/factor_local_repository.py +154 -0
- quant_cli/app/factor/repository/selection_local_repository.py +67 -0
- quant_cli/app/factor/schema/local_selection_schema.py +56 -0
- quant_cli/app/factor/service/__init__.py +0 -0
- quant_cli/app/factor/service/factor_data_prepare_service.py +181 -0
- quant_cli/app/factor/service/local_factor_query_service.py +146 -0
- quant_cli/app/factor/service/local_factor_service.py +120 -0
- quant_cli/app/factor/service/local_selection_service.py +139 -0
- quant_cli/app/job/__init__.py +0 -0
- quant_cli/app/job/cli/__init__.py +0 -0
- quant_cli/app/job/cli/local_job_cli.py +69 -0
- quant_cli/app/job/schema/__init__.py +0 -0
- quant_cli/app/job/schema/local_job_schema.py +11 -0
- quant_cli/app/job/service/__init__.py +0 -0
- quant_cli/app/job/service/local_job_service.py +60 -0
- quant_cli/app/quant_cli.py +49 -0
- quant_cli/app/research/__init__.py +0 -0
- quant_cli/app/research/cli/__init__.py +0 -0
- quant_cli/app/research/cli/local_research_cli.py +93 -0
- quant_cli/app/research/local_research_dependency.py +10 -0
- quant_cli/app/research/repository/__init__.py +0 -0
- quant_cli/app/research/repository/research_local_repository.py +67 -0
- quant_cli/app/research/schema/__init__.py +0 -0
- quant_cli/app/research/schema/local_research_schema.py +26 -0
- quant_cli/app/research/service/__init__.py +0 -0
- quant_cli/app/research/service/local_research_execution_service.py +105 -0
- quant_cli/app/research/service/local_research_service.py +101 -0
- quant_cli/app/strategy/__init__.py +0 -0
- quant_cli/app/strategy/cli/__init__.py +0 -0
- quant_cli/app/strategy/cli/strategy_cli.py +104 -0
- quant_cli/common/__init__.py +0 -0
- quant_cli/common/constants.py +7 -0
- quant_cli/common/database/__init__.py +0 -0
- quant_cli/common/database/duckdb_connection.py +30 -0
- quant_cli/common/http/__init__.py +0 -0
- quant_cli/common/http/definition_client.py +54 -0
- quant_cli/common/utils/__init__.py +0 -0
- quant_cli/common/utils/cli_utils.py +37 -0
- quant_cli/common/utils/platform_utils.py +11 -0
- quant_cli/common/utils/runtime_environment.py +12 -0
- quant_cli/examples/cached_factor_screen.py +165 -0
- quant_cli/examples/factors/ma_close_10.py +10 -0
- quant_cli/examples/factors/max_drawdown_20d.py +18 -0
- quant_cli/examples/factors/momentum_20d.py +13 -0
- quant_cli/examples/factors/multi_intraday_screen.py +16 -0
- quant_cli/examples/factors/risk_adjusted_momentum_20d.py +18 -0
- quant_cli/examples/factors/volume_ratio_5d.py +14 -0
- quant_cli/examples/factors/volume_weighted_close_pressure.py +14 -0
- quant_cli/examples/full_market_screen.py +189 -0
- quant_cli/examples/local_quant_validation.py +196 -0
- quant_cli/examples/published_definition_validation.py +219 -0
- quant_cli/examples/research/factor_pit_coverage.py +87 -0
- quant_cli/examples/research/ipo_first_day.py +348 -0
- quant_cli/examples/selection/README.md +59 -0
- quant_cli/examples/selection/backtest.json +18 -0
- quant_cli/examples/selection/buy_hold.py +7 -0
- quant_cli/examples/selection/compute.json +15 -0
- quant_cli/examples/selection/prepare.json +13 -0
- quant_cli/examples/selection/select.json +44 -0
- quant_cli/examples/strategies/bar_roundtrip.py +12 -0
- quant_cli/examples/strategies/buy_and_hold.py +11 -0
- quant_cli/examples/strategies/contrarian_timing.py +14 -0
- quant_cli/examples/strategies/factor_gate_vwap.py +22 -0
- quant_cli/examples/strategies/ipo_next_day_vwap.py +31 -0
- quant_cli/examples/strategies/momentum_timing.py +14 -0
- quant_cli/examples/strategies/monthly_equal_weight.py +19 -0
- quant_cli/examples/strategies/session_roundtrip.py +16 -0
- quant_cli/examples/strategies/trend_drawdown_guard.py +22 -0
- quant_cli/examples/strategies/weekly_risk_adjusted_rotation.py +61 -0
- quant_cli/examples/unified_snapshot_validation.py +260 -0
- quant_core/__init__.py +0 -0
- quant_core/app/SPEC.md +18 -0
- quant_core/app/__init__.py +0 -0
- quant_core/app/backtest/SPEC.md +65 -0
- quant_core/app/backtest/__init__.py +1 -0
- quant_core/app/backtest/backtest_input_schema.py +20 -0
- quant_core/app/backtest/backtest_run.py +393 -0
- quant_core/app/backtest/data_guard.py +77 -0
- quant_core/app/backtest/historical_clock.py +26 -0
- quant_core/app/backtest/legacy_result_schema.py +34 -0
- quant_core/app/backtest/metrics.py +194 -0
- quant_core/app/backtest/performance_report.py +61 -0
- quant_core/app/backtest/report_schema.py +64 -0
- quant_core/app/backtest/schema.py +477 -0
- quant_core/app/backtest/service/__init__.py +0 -0
- quant_core/app/backtest/service/backtest_execution_service.py +114 -0
- quant_core/app/data/__init__.py +0 -0
- quant_core/app/data/constants.py +284 -0
- quant_core/app/data/domain/__init__.py +0 -0
- quant_core/app/data/domain/trading_quantity_value.py +57 -0
- quant_core/app/data/exceptions.py +95 -0
- quant_core/app/data/interface/__init__.py +0 -0
- quant_core/app/data/interface/adjustment_factor_query.py +21 -0
- quant_core/app/data/interface/market_data_query.py +71 -0
- quant_core/app/data/interface/market_data_verification.py +20 -0
- quant_core/app/data/interface/market_event_source.py +11 -0
- quant_core/app/data/interface/market_revision_query.py +8 -0
- quant_core/app/data/interface/market_runtime_repository.py +44 -0
- quant_core/app/data/interface/market_time_slice_source.py +11 -0
- quant_core/app/data/repository/__init__.py +0 -0
- quant_core/app/data/schema/__init__.py +1 -0
- quant_core/app/data/schema/adjustment_schema.py +67 -0
- quant_core/app/data/schema/corporate_action_schema.py +46 -0
- quant_core/app/data/schema/event_schema.py +506 -0
- quant_core/app/data/schema/exchange_operating_state.py +37 -0
- quant_core/app/data/schema/exchange_state_schema.py +106 -0
- quant_core/app/data/schema/history_schema.py +48 -0
- quant_core/app/data/schema/instrument_identity.py +69 -0
- quant_core/app/data/schema/market_schema.py +128 -0
- quant_core/app/data/schema/pit_daily_window.py +16 -0
- quant_core/app/data/schema/quote_schema.py +73 -0
- quant_core/app/data/schema/reliability_schema.py +152 -0
- quant_core/app/data/schema/snapshot_schema.py +82 -0
- quant_core/app/data/schema/trading_state_schema.py +211 -0
- quant_core/app/data/service/__init__.py +0 -0
- quant_core/app/data/service/calendar_coverage_service.py +63 -0
- quant_core/app/data/service/prepared_data_verification_service.py +40 -0
- quant_core/app/data/service/prepared_market_service.py +189 -0
- quant_core/app/data/service/price_adjustment_service.py +235 -0
- quant_core/app/factor/LICENSE.vnpy +21 -0
- quant_core/app/factor/__init__.py +13 -0
- quant_core/app/factor/builtin/__init__.py +0 -0
- quant_core/app/factor/builtin/alpha101_recipe.py +1095 -0
- quant_core/app/factor/builtin/alpha158_recipe.py +1284 -0
- quant_core/app/factor/builtin/factor_recipe_catalog.py +100 -0
- quant_core/app/factor/computation/__init__.py +0 -0
- quant_core/app/factor/computation/factor_batch_evaluator.py +141 -0
- quant_core/app/factor/computation/factor_expression.py +106 -0
- quant_core/app/factor/computation/factor_source_loader.py +24 -0
- quant_core/app/factor/computation/operators/__init__.py +0 -0
- quant_core/app/factor/computation/operators/arithmetic_operator.py +182 -0
- quant_core/app/factor/computation/operators/cross_section_operator.py +66 -0
- quant_core/app/factor/computation/operators/time_series_operator.py +342 -0
- quant_core/app/factor/computation/panel_evaluator.py +59 -0
- quant_core/app/factor/computation/panel_expression.py +193 -0
- quant_core/app/factor/computation/python_factor_executor.py +87 -0
- quant_core/app/factor/computation/universe_filter.py +25 -0
- quant_core/app/factor/constants.py +114 -0
- quant_core/app/factor/exceptions.py +35 -0
- quant_core/app/factor/factor_dependency.py +65 -0
- quant_core/app/factor/interface/__init__.py +0 -0
- quant_core/app/factor/interface/factor_compute.py +12 -0
- quant_core/app/factor/interface/factor_execution.py +15 -0
- quant_core/app/factor/interface/factor_local_data.py +42 -0
- quant_core/app/factor/interface/factor_panel_compute.py +18 -0
- quant_core/app/factor/interface/factor_panel_input.py +12 -0
- quant_core/app/factor/interface/factor_query_store.py +39 -0
- quant_core/app/factor/interface/public/__init__.py +0 -0
- quant_core/app/factor/interface/public/factor_diagnostics.py +18 -0
- quant_core/app/factor/interface/public/factor_exposure_query.py +12 -0
- quant_core/app/factor/interface/public/factor_local.py +15 -0
- quant_core/app/factor/interface/public/factor_query.py +57 -0
- quant_core/app/factor/interface/public/factor_selection.py +20 -0
- quant_core/app/factor/model/__init__.py +0 -0
- quant_core/app/factor/repository/__init__.py +0 -0
- quant_core/app/factor/schema/__init__.py +1 -0
- quant_core/app/factor/schema/diagnostic_types.py +42 -0
- quant_core/app/factor/schema/factor_artifact_schema.py +36 -0
- quant_core/app/factor/schema/factor_computation_schema.py +102 -0
- quant_core/app/factor/schema/factor_compute_request.py +24 -0
- quant_core/app/factor/schema/factor_contract.py +257 -0
- quant_core/app/factor/schema/factor_input_schema.py +32 -0
- quant_core/app/factor/schema/factor_panel.py +65 -0
- quant_core/app/factor/schema/factor_publication_schema.py +41 -0
- quant_core/app/factor/schema/factor_query_result_schema.py +12 -0
- quant_core/app/factor/schema/factor_result_schema.py +6 -0
- quant_core/app/factor/schema/local_factor_schema.py +42 -0
- quant_core/app/factor/schema/panel_policy.py +13 -0
- quant_core/app/factor/schema/selection_report.py +116 -0
- quant_core/app/factor/schema/selection_types.py +135 -0
- quant_core/app/factor/service/__init__.py +1 -0
- quant_core/app/factor/service/factor_compute_service.py +140 -0
- quant_core/app/factor/service/factor_diagnostic_runner.py +134 -0
- quant_core/app/factor/service/factor_diagnostics_service.py +140 -0
- quant_core/app/factor/service/factor_exposure_service.py +24 -0
- quant_core/app/factor/service/factor_input_service.py +258 -0
- quant_core/app/factor/service/factor_local_execution_service.py +51 -0
- quant_core/app/factor/service/factor_panel_service.py +86 -0
- quant_core/app/factor/service/factor_runtime_service.py +306 -0
- quant_core/app/factor/service/factor_selection_runner.py +88 -0
- quant_core/app/factor/service/published_factor_execution_service.py +160 -0
- quant_core/app/factor/service/selection_service.py +234 -0
- quant_core/app/factor/visibility.py +22 -0
- quant_core/app/opportunity/SPEC.md +45 -0
- quant_core/app/opportunity/__init__.py +1 -0
- quant_core/app/order_execution/SPEC.md +64 -0
- quant_core/app/order_execution/__init__.py +1 -0
- quant_core/app/order_execution/bar_matcher.py +55 -0
- quant_core/app/order_execution/constants.py +142 -0
- quant_core/app/order_execution/domain/__init__.py +45 -0
- quant_core/app/order_execution/domain/execution_batch.py +108 -0
- quant_core/app/order_execution/domain/order_entity.py +197 -0
- quant_core/app/order_execution/domain/order_event.py +87 -0
- quant_core/app/order_execution/domain/order_policy.py +29 -0
- quant_core/app/order_execution/domain/order_value.py +142 -0
- quant_core/app/order_execution/fee_model.py +60 -0
- quant_core/app/order_execution/interface/__init__.py +0 -0
- quant_core/app/order_execution/interface/order_account.py +10 -0
- quant_core/app/order_execution/interface/order_trading_rules.py +10 -0
- quant_core/app/order_execution/interface/public/__init__.py +0 -0
- quant_core/app/order_execution/interface/public/execution_account.py +53 -0
- quant_core/app/order_execution/interface/public/order_events.py +19 -0
- quant_core/app/order_execution/interface/public/order_execution.py +107 -0
- quant_core/app/order_execution/interface/public/order_runtime.py +30 -0
- quant_core/app/order_execution/interface/public/order_submission.py +17 -0
- quant_core/app/order_execution/order_event_bus.py +28 -0
- quant_core/app/order_execution/order_execution_dependency.py +48 -0
- quant_core/app/order_execution/order_submission_service.py +301 -0
- quant_core/app/order_execution/schema/__init__.py +0 -0
- quant_core/app/order_execution/schema/order_request_schema.py +88 -0
- quant_core/app/order_execution/simulation_broker.py +883 -0
- quant_core/app/order_execution/slippage_model.py +40 -0
- quant_core/app/order_execution/tradability_policy.py +174 -0
- quant_core/app/order_execution/validators.py +40 -0
- quant_core/app/portfolio/SPEC.md +69 -0
- quant_core/app/portfolio/__init__.py +0 -0
- quant_core/app/portfolio/domain/__init__.py +32 -0
- quant_core/app/portfolio/domain/entitlement_entity.py +45 -0
- quant_core/app/portfolio/domain/portfolio_entity.py +511 -0
- quant_core/app/portfolio/domain/portfolio_event.py +59 -0
- quant_core/app/portfolio/domain/portfolio_value.py +21 -0
- quant_core/app/portfolio/domain/position_entity.py +165 -0
- quant_core/app/portfolio/domain/reservation_entity.py +69 -0
- quant_core/app/portfolio/interface/__init__.py +0 -0
- quant_core/app/portfolio/interface/public/__init__.py +0 -0
- quant_core/app/portfolio/interface/public/portfolio.py +77 -0
- quant_core/app/portfolio/interface/public/simulation_account.py +16 -0
- quant_core/app/portfolio/ledger_schema.py +130 -0
- quant_core/app/portfolio/order_account_listener.py +36 -0
- quant_core/app/portfolio/portfolio_dependency.py +21 -0
- quant_core/app/portfolio/portfolio_schema.py +204 -0
- quant_core/app/portfolio/simulated_portfolio.py +427 -0
- quant_core/app/research/SPEC.md +57 -0
- quant_core/app/research/__init__.py +0 -0
- quant_core/app/research/adapter/__init__.py +0 -0
- quant_core/app/research/adapter/market_label_query.py +84 -0
- quant_core/app/research/interface/__init__.py +0 -0
- quant_core/app/research/interface/research_dataset.py +47 -0
- quant_core/app/research/interface/research_evaluation.py +19 -0
- quant_core/app/research/schema/__init__.py +0 -0
- quant_core/app/research/schema/comparison_schema.py +12 -0
- quant_core/app/research/schema/dataset_schema.py +112 -0
- quant_core/app/research/schema/research_schema.py +120 -0
- quant_core/app/research/service/__init__.py +0 -0
- quant_core/app/research/service/research_dataset_service.py +80 -0
- quant_core/app/research/service/research_evaluation_service.py +56 -0
- quant_core/app/research/service/research_service.py +171 -0
- quant_core/app/strategy/SPEC.md +215 -0
- quant_core/app/strategy/__init__.py +1 -0
- quant_core/app/strategy/constants.py +87 -0
- quant_core/app/strategy/exceptions.py +32 -0
- quant_core/app/strategy/interface/__init__.py +0 -0
- quant_core/app/strategy/interface/public/__init__.py +0 -0
- quant_core/app/strategy/interface/public/portfolio_construction.py +19 -0
- quant_core/app/strategy/interface/public/strategy.py +30 -0
- quant_core/app/strategy/interface/public/strategy_market_query.py +53 -0
- quant_core/app/strategy/interface/public/strategy_portfolio_reader.py +33 -0
- quant_core/app/strategy/interface/strategy_factor_reader.py +19 -0
- quant_core/app/strategy/interface/strategy_order_reader.py +14 -0
- quant_core/app/strategy/schema/__init__.py +0 -0
- quant_core/app/strategy/schema/portfolio_schema.py +28 -0
- quant_core/app/strategy/schema/portfolio_target.py +70 -0
- quant_core/app/strategy/schema/python_strategy_schema.py +94 -0
- quant_core/app/strategy/schema/screener_schema.py +51 -0
- quant_core/app/strategy/schema/signal_schema.py +33 -0
- quant_core/app/strategy/schema/strategy_publication_schema.py +32 -0
- quant_core/app/strategy/schema/strategy_schema.py +96 -0
- quant_core/app/strategy/service/__init__.py +0 -0
- quant_core/app/strategy/service/factor_top_n_strategy.py +103 -0
- quant_core/app/strategy/service/portfolio_construction_service.py +142 -0
- quant_core/app/strategy/service/python_strategy_service.py +65 -0
- quant_core/app/strategy/service/screener_service.py +77 -0
- quant_core/app/strategy/service/store_factor_reader.py +120 -0
- quant_core/app/strategy/service/strategy_market_view.py +120 -0
- quant_core/app/strategy/service/strategy_runtime_service.py +319 -0
- quant_core/app/strategy/service/strategy_scheduler.py +50 -0
- quant_core/app/strategy/strategies/__init__.py +7 -0
- quant_core/app/strategy/strategies/momentum_volume_breakout.py +157 -0
- quant_core/app/strategy/strategy_context.py +316 -0
- quant_core/app/strategy/strategy_dependency.py +101 -0
- quant_core/app/trading_runtime/SPEC.md +70 -0
- quant_core/app/trading_runtime/__init__.py +1 -0
- quant_core/app/trading_runtime/domain/__init__.py +0 -0
- quant_core/app/trading_runtime/replay/__init__.py +0 -0
- quant_core/app/trading_runtime/replay/daily_state_expand.py +49 -0
- quant_core/app/trading_runtime/replay/exchange_state_resolver.py +90 -0
- quant_core/app/trading_runtime/replay/market_runtime_service.py +573 -0
- quant_core/app/trading_runtime/replay/market_state_resolver.py +150 -0
- quant_core/app/trading_runtime/runtime_ports.py +64 -0
- quant_core/app/trading_runtime/runtime_schema.py +129 -0
- quant_core/app/trading_runtime/runtime_session.py +361 -0
- quant_core/app/trading_runtime/strategy_portfolio_adapter.py +58 -0
- quant_core/app/trading_runtime/trading_rule_book.py +37 -0
- quant_core/app/trading_runtime/trading_rule_query.py +12 -0
- quant_core/common/__init__.py +0 -0
- quant_core/common/exceptions/__init__.py +0 -0
- quant_core/common/exceptions/base.py +89 -0
- quant_core/common/local_run.py +45 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""纯函数式横截面筛选;身份精确、不补零、不联网。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal, localcontext
|
|
4
|
+
from statistics import median
|
|
5
|
+
|
|
6
|
+
from quant_core.app.factor.constants import FactorStatus
|
|
7
|
+
from quant_core.app.factor.schema.factor_contract import FactorIdentity, FactorSnapshot, FactorValue
|
|
8
|
+
from quant_core.app.factor.schema.selection_types import (
|
|
9
|
+
ExcludedInstrument,
|
|
10
|
+
FactorTransform,
|
|
11
|
+
MissingPolicy,
|
|
12
|
+
NeutralizationMethod,
|
|
13
|
+
NormalizationMethod,
|
|
14
|
+
SelectedInstrument,
|
|
15
|
+
SelectionDefinition,
|
|
16
|
+
SelectionExposure,
|
|
17
|
+
SelectionResult,
|
|
18
|
+
SortDirection,
|
|
19
|
+
WinsorizationMethod,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SelectionService:
|
|
24
|
+
def select(
|
|
25
|
+
self,
|
|
26
|
+
definition: SelectionDefinition,
|
|
27
|
+
universe: tuple[str, ...] | list[str],
|
|
28
|
+
snapshot: FactorSnapshot,
|
|
29
|
+
exposures: dict[str, SelectionExposure] | None = None,
|
|
30
|
+
) -> SelectionResult:
|
|
31
|
+
cells: dict[tuple[tuple, str], FactorValue] = {}
|
|
32
|
+
for row in snapshot.values:
|
|
33
|
+
key = (row.identity.logical_key, row.instrument_id)
|
|
34
|
+
if key in cells:
|
|
35
|
+
raise ValueError(f"selection 截面单格存在多个 as_of:{key}")
|
|
36
|
+
cells[key] = row
|
|
37
|
+
|
|
38
|
+
needed = tuple(
|
|
39
|
+
dict.fromkeys(
|
|
40
|
+
[f.identity for f in definition.factors] + [f.identity for f in definition.filters]
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
eligible: dict[str, dict[tuple, Decimal]] = {}
|
|
44
|
+
excluded: list[ExcludedInstrument] = []
|
|
45
|
+
for iid in sorted(set(universe)):
|
|
46
|
+
values: dict[tuple, Decimal] = {}
|
|
47
|
+
failure: tuple[str, FactorIdentity] | None = None
|
|
48
|
+
for ident in needed:
|
|
49
|
+
row = cells.get((ident.logical_key, iid))
|
|
50
|
+
if row is None:
|
|
51
|
+
failure = ("missing", ident)
|
|
52
|
+
break
|
|
53
|
+
if row.status is not FactorStatus.AVAILABLE or row.value is None:
|
|
54
|
+
failure = (row.status.value, ident)
|
|
55
|
+
break
|
|
56
|
+
values[ident.logical_key] = row.value
|
|
57
|
+
if failure is not None:
|
|
58
|
+
if definition.missing_policy is MissingPolicy.ERROR:
|
|
59
|
+
raise ValueError(f"selection {failure[0]}: {iid} / {failure[1].logical_key}")
|
|
60
|
+
excluded.append(ExcludedInstrument(iid, *failure))
|
|
61
|
+
continue
|
|
62
|
+
failed_filter = next(
|
|
63
|
+
(
|
|
64
|
+
f
|
|
65
|
+
for f in definition.filters
|
|
66
|
+
if (f.minimum is not None and values[f.identity.logical_key] < f.minimum)
|
|
67
|
+
or (f.maximum is not None and values[f.identity.logical_key] > f.maximum)
|
|
68
|
+
),
|
|
69
|
+
None,
|
|
70
|
+
)
|
|
71
|
+
if failed_filter is not None:
|
|
72
|
+
excluded.append(ExcludedInstrument(iid, "filter", failed_filter.identity))
|
|
73
|
+
continue
|
|
74
|
+
eligible[iid] = values
|
|
75
|
+
|
|
76
|
+
if any(
|
|
77
|
+
factor.transform.neutralization is not NeutralizationMethod.NONE
|
|
78
|
+
for factor in definition.factors
|
|
79
|
+
):
|
|
80
|
+
for instrument_id in eligible:
|
|
81
|
+
exposure = (exposures or {}).get(instrument_id)
|
|
82
|
+
if exposure is None or exposure.available_at is None:
|
|
83
|
+
raise ValueError(f"neutralization 缺少有可见时间的 exposure: {instrument_id}")
|
|
84
|
+
if exposure.available_at > snapshot.decision_time:
|
|
85
|
+
raise ValueError(f"neutralization exposure 在决策时点不可见: {instrument_id}")
|
|
86
|
+
processed: dict[tuple, dict[str, Decimal]] = {}
|
|
87
|
+
for factor in definition.factors:
|
|
88
|
+
raw = {iid: values[factor.identity.logical_key] for iid, values in eligible.items()}
|
|
89
|
+
transformed = _transform(raw, factor.transform)
|
|
90
|
+
processed[factor.identity.logical_key] = _neutralize(
|
|
91
|
+
transformed,
|
|
92
|
+
factor.transform.neutralization,
|
|
93
|
+
exposures or {},
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
ranked: list[SelectedInstrument] = []
|
|
97
|
+
for iid in eligible:
|
|
98
|
+
contributions = tuple(
|
|
99
|
+
(
|
|
100
|
+
f.identity,
|
|
101
|
+
f.weight
|
|
102
|
+
* processed[f.identity.logical_key][iid]
|
|
103
|
+
* (Decimal(-1) if f.direction is SortDirection.ASC else Decimal(1)),
|
|
104
|
+
)
|
|
105
|
+
for f in definition.factors
|
|
106
|
+
)
|
|
107
|
+
ranked.append(
|
|
108
|
+
SelectedInstrument(
|
|
109
|
+
iid, sum((c for _, c in contributions), Decimal(0)), contributions, 0
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
ranked.sort(key=lambda r: (-r.score, r.instrument_id))
|
|
114
|
+
selected = tuple(
|
|
115
|
+
SelectedInstrument(r.instrument_id, r.score, r.contributions, rank)
|
|
116
|
+
for rank, r in enumerate(ranked[: definition.top_n], 1)
|
|
117
|
+
)
|
|
118
|
+
excluded.extend(
|
|
119
|
+
ExcludedInstrument(
|
|
120
|
+
r.instrument_id, "top_n", score=r.score, contributions=r.contributions
|
|
121
|
+
)
|
|
122
|
+
for r in ranked[definition.top_n :]
|
|
123
|
+
)
|
|
124
|
+
return SelectionResult(selected, tuple(sorted(excluded, key=lambda r: r.instrument_id)))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _transform(values: dict[str, Decimal], config: FactorTransform) -> dict[str, Decimal]:
|
|
128
|
+
result = dict(values)
|
|
129
|
+
if not result:
|
|
130
|
+
return result
|
|
131
|
+
ordered = sorted(result.values())
|
|
132
|
+
if config.winsorization is WinsorizationMethod.PERCENTILE:
|
|
133
|
+
low = _quantile(ordered, config.lower_quantile)
|
|
134
|
+
high = _quantile(ordered, config.upper_quantile)
|
|
135
|
+
result = {key: min(max(value, low), high) for key, value in result.items()}
|
|
136
|
+
elif config.winsorization is WinsorizationMethod.MAD:
|
|
137
|
+
center = Decimal(str(median(ordered)))
|
|
138
|
+
mad = Decimal(str(median([abs(value - center) for value in ordered])))
|
|
139
|
+
if mad:
|
|
140
|
+
radius = config.mad_scale * Decimal("1.4826") * mad
|
|
141
|
+
result = {
|
|
142
|
+
key: min(max(value, center - radius), center + radius)
|
|
143
|
+
for key, value in result.items()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if config.normalization is NormalizationMethod.RANK:
|
|
147
|
+
return _rank_percentile(result)
|
|
148
|
+
if config.normalization is NormalizationMethod.ZSCORE:
|
|
149
|
+
mean = sum(result.values(), Decimal(0)) / Decimal(len(result))
|
|
150
|
+
variance = sum(((value - mean) ** 2 for value in result.values()), Decimal(0)) / Decimal(
|
|
151
|
+
len(result)
|
|
152
|
+
)
|
|
153
|
+
if not variance:
|
|
154
|
+
return {key: Decimal(0) for key in result}
|
|
155
|
+
with localcontext() as context:
|
|
156
|
+
context.prec = 28
|
|
157
|
+
std = variance.sqrt()
|
|
158
|
+
return {key: (value - mean) / std for key, value in result.items()}
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _quantile(ordered: list[Decimal], quantile: Decimal) -> Decimal:
|
|
163
|
+
if len(ordered) == 1:
|
|
164
|
+
return ordered[0]
|
|
165
|
+
position = quantile * Decimal(len(ordered) - 1)
|
|
166
|
+
lower = int(position)
|
|
167
|
+
fraction = position - Decimal(lower)
|
|
168
|
+
return ordered[lower] + (ordered[min(lower + 1, len(ordered) - 1)] - ordered[lower]) * fraction
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _rank_percentile(values: dict[str, Decimal]) -> dict[str, Decimal]:
|
|
172
|
+
"""平均秩百分位;并列值获得相同分数,单元素截面取 0.5。"""
|
|
173
|
+
if len(values) == 1:
|
|
174
|
+
return {next(iter(values)): Decimal("0.5")}
|
|
175
|
+
groups: dict[Decimal, list[str]] = {}
|
|
176
|
+
for instrument_id, value in values.items():
|
|
177
|
+
groups.setdefault(value, []).append(instrument_id)
|
|
178
|
+
ranks: dict[str, Decimal] = {}
|
|
179
|
+
cursor = 1
|
|
180
|
+
denominator = Decimal(len(values) - 1)
|
|
181
|
+
for value in sorted(groups):
|
|
182
|
+
members = groups[value]
|
|
183
|
+
average_rank = (Decimal(cursor) + Decimal(cursor + len(members) - 1)) / Decimal(2)
|
|
184
|
+
percentile = (average_rank - Decimal(1)) / denominator
|
|
185
|
+
ranks.update({instrument_id: percentile for instrument_id in members})
|
|
186
|
+
cursor += len(members)
|
|
187
|
+
return ranks
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _neutralize(
|
|
191
|
+
values: dict[str, Decimal],
|
|
192
|
+
method: NeutralizationMethod,
|
|
193
|
+
exposures: dict[str, SelectionExposure],
|
|
194
|
+
) -> dict[str, Decimal]:
|
|
195
|
+
if method is NeutralizationMethod.NONE:
|
|
196
|
+
return values
|
|
197
|
+
missing = sorted(set(values) - exposures.keys())
|
|
198
|
+
if missing:
|
|
199
|
+
raise ValueError(f"neutralization 缺少 exposure: {missing[:5]}")
|
|
200
|
+
if not values:
|
|
201
|
+
return {}
|
|
202
|
+
# Frisch–Waugh–Lovell:行业+市值必须同时残差化 y 与 log(size),
|
|
203
|
+
# 不能只去行业均值后再对原始市值回归,否则会重新引入行业暴露。
|
|
204
|
+
result = dict(values)
|
|
205
|
+
groups: dict[str, list[str]] = {}
|
|
206
|
+
if method in (NeutralizationMethod.INDUSTRY, NeutralizationMethod.INDUSTRY_SIZE):
|
|
207
|
+
for instrument_id in result:
|
|
208
|
+
industry = exposures[instrument_id].industry
|
|
209
|
+
if industry is None:
|
|
210
|
+
raise ValueError(f"industry exposure 缺失: {instrument_id}")
|
|
211
|
+
groups.setdefault(industry, []).append(instrument_id)
|
|
212
|
+
else:
|
|
213
|
+
groups["intercept"] = list(result)
|
|
214
|
+
sizes: dict[str, Decimal] = {}
|
|
215
|
+
if method in (NeutralizationMethod.SIZE, NeutralizationMethod.INDUSTRY_SIZE):
|
|
216
|
+
for instrument_id in result:
|
|
217
|
+
size = exposures[instrument_id].size
|
|
218
|
+
if size is None or not size.is_finite() or size <= 0:
|
|
219
|
+
raise ValueError(f"size exposure 必须大于 0: {instrument_id}")
|
|
220
|
+
sizes[instrument_id] = size.ln()
|
|
221
|
+
for members in groups.values():
|
|
222
|
+
mean = sum((result[key] for key in members), Decimal(0)) / Decimal(len(members))
|
|
223
|
+
for key in members:
|
|
224
|
+
result[key] -= mean
|
|
225
|
+
if sizes:
|
|
226
|
+
size_mean = sum((sizes[key] for key in members), Decimal(0)) / Decimal(len(members))
|
|
227
|
+
for key in members:
|
|
228
|
+
sizes[key] -= size_mean
|
|
229
|
+
if sizes:
|
|
230
|
+
variance = sum((size**2 for size in sizes.values()), Decimal(0))
|
|
231
|
+
if variance:
|
|
232
|
+
slope = sum((sizes[key] * result[key] for key in result), Decimal(0)) / variance
|
|
233
|
+
result = {key: result[key] - slope * sizes[key] for key in result}
|
|
234
|
+
return result
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""因子可见时间计算(Q2=A:日频因子下一交易日开盘起可读)
|
|
2
|
+
|
|
3
|
+
Provider 无关(Tushare mapper 与 Strategy 本地计算共用同一实现,不复制):
|
|
4
|
+
value_as_of 当日收盘后产生的因子值,最早可见时间 = 下一交易日 09:30
|
|
5
|
+
(Asia/Shanghai);无「下一交易日」证据(日历末端)时不臆造,返回 None
|
|
6
|
+
由调用方降级 NOT_YET_PUBLISHED。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import date, datetime, time
|
|
10
|
+
from zoneinfo import ZoneInfo
|
|
11
|
+
|
|
12
|
+
_CN = ZoneInfo("Asia/Shanghai")
|
|
13
|
+
_NEXT_OPEN = time(9, 30)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def next_trading_day_open(day: date, trading_days: list[date]) -> datetime | None:
|
|
17
|
+
"""day 的下一交易日 09:30(CN);day 不在日历或无下一交易日 → None"""
|
|
18
|
+
days = sorted(trading_days)
|
|
19
|
+
for d in days:
|
|
20
|
+
if d > day:
|
|
21
|
+
return datetime.combine(d, _NEXT_OPEN, tzinfo=_CN)
|
|
22
|
+
return None
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Opportunity SPEC
|
|
2
|
+
|
|
3
|
+
## 1. Domain Purpose
|
|
4
|
+
|
|
5
|
+
`opportunity` 负责把 Strategy 在离散市场时刻产生的观察维护成可跟踪的交易机会生命周期,统一支撑盘前观察池与盘中机会发现。
|
|
6
|
+
|
|
7
|
+
## 2. Responsibilities
|
|
8
|
+
|
|
9
|
+
- 识别同一 Strategy 与 Instrument 对应的机会身份。
|
|
10
|
+
- 保存形成机会时可用的证据快照、触发条件、失效条件和有效期。
|
|
11
|
+
- 根据 Strategy Observation 推进机会状态。
|
|
12
|
+
- 提供当前机会及其历史变化的查询能力。
|
|
13
|
+
- 形成盘前 Watchlist 等查询投影。
|
|
14
|
+
|
|
15
|
+
## 3. Non-Responsibilities
|
|
16
|
+
|
|
17
|
+
- 不重新计算 Strategy。
|
|
18
|
+
- 不产生或执行 Order。
|
|
19
|
+
- 不维护资金、持仓或收益。
|
|
20
|
+
- 不直接调用消息渠道。
|
|
21
|
+
- 不表示使用者是否接受某个机会。
|
|
22
|
+
|
|
23
|
+
## 4. Main Concepts
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
Strategy Observation
|
|
27
|
+
↓
|
|
28
|
+
Opportunity
|
|
29
|
+
DETECTED → WATCHING → TRIGGERED
|
|
30
|
+
│ │
|
|
31
|
+
├──> EXPIRED │
|
|
32
|
+
└────────────┴──> INVALIDATED
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `Opportunity`:某策略在某标的上的一个可跟踪机会实例。
|
|
36
|
+
- `EvidenceSnapshot`:机会在该时刻实际可用且可追溯的证据。
|
|
37
|
+
- `ValidityWindow`:机会允许继续存在或触发的时间范围。
|
|
38
|
+
- `OpportunityChange`:创建、触发、失效或过期等状态事实。
|
|
39
|
+
- `Watchlist`:对 WATCHING Opportunity 的查询投影,不拥有独立事实。
|
|
40
|
+
|
|
41
|
+
## 5. Cross-Domain Dependencies
|
|
42
|
+
|
|
43
|
+
- 只消费 Strategy 的稳定 Observation 契约;该契约尚未实现前不得用自由文本代替。
|
|
44
|
+
- 时间判断使用 Market/Trading Runtime 提供的市场时间,不读取 wall clock 推断回测时间。
|
|
45
|
+
- Notification 可以消费 OpportunityChange,本域不得直接调用渠道 SDK。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Opportunity Domain - 交易机会生命周期。"""
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Order Execution SPEC
|
|
2
|
+
|
|
3
|
+
## 1. Domain Purpose
|
|
4
|
+
|
|
5
|
+
`order_execution` 负责把明确的 `OrderIntent` 转化为 Order,并维护订单从接受、等待、撮合到成交、撤销、拒绝或过期的生命周期。
|
|
6
|
+
|
|
7
|
+
## 2. Responsibilities
|
|
8
|
+
|
|
9
|
+
- 校验并接受或拒绝 OrderIntent。
|
|
10
|
+
- 创建 Order 并维护 OrderStatus。
|
|
11
|
+
- 根据 ExecutionPolicy 与后续市场事实撮合订单。
|
|
12
|
+
- 计算模拟成交价格、费用和滑点。
|
|
13
|
+
- 产生 OrderUpdate 与 Fill。
|
|
14
|
+
- 为 Historical Backtest、Paper、Live 提供一致的订单语义。
|
|
15
|
+
|
|
16
|
+
## 3. Non-Responsibilities
|
|
17
|
+
|
|
18
|
+
- 不选择标的或产生 Signal。
|
|
19
|
+
- 不负责仓位计算和组合资金分配。
|
|
20
|
+
- 不维护最终现金、持仓与损益。
|
|
21
|
+
- 不推进 Runtime Clock。
|
|
22
|
+
- 不维护 Opportunity。
|
|
23
|
+
|
|
24
|
+
## 4. Main Concepts
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
OrderIntent
|
|
28
|
+
↓
|
|
29
|
+
ACCEPTED / REJECTED
|
|
30
|
+
↓
|
|
31
|
+
Order(PENDING)
|
|
32
|
+
├──> PARTIALLY_FILLED ──> FILLED
|
|
33
|
+
├──> CANCELLED
|
|
34
|
+
└──> EXPIRED
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- `OrderIntent`:完成仓位计算、交易约束和下单前风险检查后的明确下单意图。
|
|
38
|
+
- `Order`:已被订单执行领域接受并持续维护生命周期的订单。
|
|
39
|
+
- `Fill`:一次已确认的实际成交记录,至少包含关联订单、标的、方向、数量、价格、费用和成交时间。
|
|
40
|
+
|
|
41
|
+
一张 Order 可以没有 Fill,也可以因部分成交产生多条 Fill。创建或接受 Order 本身不能改变 Portfolio;只有 Fill 可以触发交易状态变化。
|
|
42
|
+
|
|
43
|
+
## 5. Execution Implementations
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
Order Execution Contract
|
|
47
|
+
├── Simulated Order Execution Historical Backtest / Paper Trading
|
|
48
|
+
└── Broker Order Execution Live Trading
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
实现可以不同,但 OrderIntent、Order、OrderStatus、OrderUpdate 与 Fill 的 Canonical 语义必须一致。
|
|
52
|
+
|
|
53
|
+
## 6. Cross-Domain Dependencies
|
|
54
|
+
|
|
55
|
+
- 消费 Order Planning 产生的 OrderIntent。
|
|
56
|
+
- 撮合时只消费 Market 的 Canonical 市场事实。
|
|
57
|
+
- 输出 Fill 给 Portfolio。
|
|
58
|
+
- 由 Trading Runtime 编排,但不依赖其具体运行模式实现。
|
|
59
|
+
|
|
60
|
+
## 7. Domain Boundary
|
|
61
|
+
|
|
62
|
+
`Order` 是订单生命周期的唯一聚合根。`OrderIntent`、`OrderTransition`、`Fill`、`ExecutionAttempt`、`FeeBreakdown` 与决策结果是不可变值或领域事实;入口 Schema 只负责协议校验和投影。
|
|
63
|
+
|
|
64
|
+
`app/order_execution/domain/` 不依赖 Pydantic、FastAPI、Typer、SQLAlchemy、Repository 实现或外部数据 SDK。模拟回测、Paper 与 Live Adapter 必须复用该领域语义,不得各自维护订单状态机。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Order Execution Domain - 订单生命周期与成交。"""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Bar 撮合定价;Broker 管理订单,Matcher 解释行情成交价格。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from quant_core.app.data.schema.market_schema import BarResponse
|
|
6
|
+
from quant_core.app.order_execution.constants import ExecutionPolicy, OrderSide, OrderType
|
|
7
|
+
from quant_core.app.order_execution.domain.order_policy import SlippageModelProtocol
|
|
8
|
+
from quant_core.app.order_execution.domain.order_value import OrderSnapshot
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BarMatcher:
|
|
12
|
+
"""当前收盘、下一开盘和日 VWAP 的统一定价实现。"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, slippage_model: SlippageModelProtocol) -> None:
|
|
15
|
+
self._slippage_model = slippage_model
|
|
16
|
+
|
|
17
|
+
def execution_price(self, order: OrderSnapshot, bar: BarResponse) -> Decimal | None:
|
|
18
|
+
intent = order.intent
|
|
19
|
+
if intent.execution_policy is ExecutionPolicy.DAILY_VWAP:
|
|
20
|
+
if intent.order_type is not OrderType.MARKET:
|
|
21
|
+
return None
|
|
22
|
+
if bar.frequency != "1d" or bar.amount is None or bar.volume <= 0:
|
|
23
|
+
return None
|
|
24
|
+
vwap = bar.amount / Decimal(bar.volume)
|
|
25
|
+
if not vwap.is_finite() or vwap <= 0:
|
|
26
|
+
return None
|
|
27
|
+
if not (bar.low - Decimal("0.01") <= vwap <= bar.high + Decimal("0.01")):
|
|
28
|
+
return None
|
|
29
|
+
return self._slippage_model.apply(intent.side, vwap)
|
|
30
|
+
if intent.execution_policy is ExecutionPolicy.CURRENT_BAR_CLOSE:
|
|
31
|
+
if intent.limit_price is not None and (
|
|
32
|
+
(intent.side is OrderSide.BUY and bar.close > intent.limit_price)
|
|
33
|
+
or (intent.side is OrderSide.SELL and bar.close < intent.limit_price)
|
|
34
|
+
):
|
|
35
|
+
return None
|
|
36
|
+
price = self._slippage_model.apply(intent.side, bar.close)
|
|
37
|
+
if intent.limit_price is not None:
|
|
38
|
+
return (
|
|
39
|
+
min(price, intent.limit_price)
|
|
40
|
+
if intent.side is OrderSide.BUY
|
|
41
|
+
else max(price, intent.limit_price)
|
|
42
|
+
)
|
|
43
|
+
return price
|
|
44
|
+
if intent.order_type is OrderType.MARKET:
|
|
45
|
+
return self._slippage_model.apply(intent.side, bar.open)
|
|
46
|
+
assert intent.limit_price is not None
|
|
47
|
+
if intent.side is OrderSide.BUY:
|
|
48
|
+
if bar.low > intent.limit_price:
|
|
49
|
+
return None
|
|
50
|
+
reference = min(bar.open, intent.limit_price)
|
|
51
|
+
return min(self._slippage_model.apply(intent.side, reference), intent.limit_price)
|
|
52
|
+
if bar.high < intent.limit_price:
|
|
53
|
+
return None
|
|
54
|
+
reference = max(bar.open, intent.limit_price)
|
|
55
|
+
return max(self._slippage_model.apply(intent.side, reference), intent.limit_price)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Order Execution Domain 常量 - 订单生命周期枚举(Canonical 唯一定义点)
|
|
3
|
+
|
|
4
|
+
语义 SSOT 见 `app/order_execution/SPEC.md` 与 DECISIONS.md 2026-09-04
|
|
5
|
+
(Native Backtest 唯一权威路径 / 通用市场交易事实与可替换 Policy /
|
|
6
|
+
短线 MVP 订单 DAY 有效期)。
|
|
7
|
+
|
|
8
|
+
维度边界(不得混用):
|
|
9
|
+
- OrderType:订单价格形态(市价或限价)。
|
|
10
|
+
- ExecutionPolicy:成交时点策略(当前 Bar、下一 Bar 或日 VWAP)。
|
|
11
|
+
- TimeInForce:有效期(MVP 仅 DAY,不跨日执行)。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from enum import Enum
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class OrderSide(str, Enum):
|
|
18
|
+
"""订单方向(Order Execution 专用)
|
|
19
|
+
|
|
20
|
+
与 Strategy 的 SignalSide(LONG/NONE/SHORT)类型隔离;
|
|
21
|
+
策略通过交易 API 明确指定买卖方向。
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
BUY = "buy"
|
|
25
|
+
SELL = "sell"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OrderType(str, Enum):
|
|
29
|
+
"""订单价格形态。"""
|
|
30
|
+
|
|
31
|
+
MARKET = "market"
|
|
32
|
+
LIMIT = "limit"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ExecutionPolicy(str, Enum):
|
|
36
|
+
"""成交时点:当前 Bar 收盘、后续 Bar 开盘或日 VWAP。
|
|
37
|
+
|
|
38
|
+
CURRENT_BAR_CLOSE 在当前合法可见 Bar 上撮合;NEXT_BAR_OPEN 只使用后续 Bar。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
CURRENT_BAR_CLOSE = "current_bar_close"
|
|
42
|
+
NEXT_BAR_OPEN = "next_bar_open"
|
|
43
|
+
DAILY_VWAP = "daily_vwap"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TimeInForce(str, Enum):
|
|
47
|
+
"""订单有效期维度(MVP 仅 DAY)
|
|
48
|
+
|
|
49
|
+
DAY:市场约束解除后当日可继续尝试;所属 Exchange Session CLOSE 时
|
|
50
|
+
仍未成交则 EXPIRED;不跨日继续执行旧短线信号。实际扫描由 T5 实现。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
DAY = "day"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class OrderStatus(str, Enum):
|
|
57
|
+
"""订单生命周期状态(REJECTED 不是订单状态:被拒 Intent 不创建 Order)
|
|
58
|
+
|
|
59
|
+
终态:FILLED / CANCELLED / EXPIRED;终态后禁止继续迁移。
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
PENDING = "pending"
|
|
63
|
+
PARTIALLY_FILLED = "partially_filled"
|
|
64
|
+
FILLED = "filled"
|
|
65
|
+
CANCELLED = "cancelled"
|
|
66
|
+
EXPIRED = "expired"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class OrderAckStatus(str, Enum):
|
|
70
|
+
"""submit 阶段结果(与 OrderStatus 分离)"""
|
|
71
|
+
|
|
72
|
+
ACCEPTED = "accepted"
|
|
73
|
+
REJECTED = "rejected"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class TradabilityDecision(str, Enum):
|
|
77
|
+
"""TradabilityPolicy 的市场决策(只表达「当前能否成交」)
|
|
78
|
+
|
|
79
|
+
REJECT 不属于本枚举:Intent 静态非法由 submit REJECT 表达,
|
|
80
|
+
执行阶段终止由 OrderUpdate(CANCELLED/EXPIRED) 表达。
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
ALLOW = "allow"
|
|
84
|
+
DEFER = "defer"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ExecutionAttemptOutcome(str, Enum):
|
|
88
|
+
"""一次实际撮合检查的结果(FILLED 与 DEFERRED)"""
|
|
89
|
+
|
|
90
|
+
DEFERRED = "deferred"
|
|
91
|
+
FILLED = "filled"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ExecutionDeferReason(str, Enum):
|
|
95
|
+
"""DEFER 的结构化原因(仅市场暂时限制;Order 保持 PENDING)
|
|
96
|
+
|
|
97
|
+
只允许「当日可能变化」的市场事实原因。账户条件不足(资金 /
|
|
98
|
+
可卖持仓)不会因等待自动恢复,属执行终止:
|
|
99
|
+
OrderUpdate(CANCELLED) + OrderUpdateReason.INSUFFICIENT_*_AT_EXECUTION。
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
NO_ELIGIBLE_BAR = "no_eligible_bar"
|
|
103
|
+
SUSPENDED = "suspended"
|
|
104
|
+
HALTED = "halted"
|
|
105
|
+
SESSION_NOT_TRADABLE = "session_not_tradable"
|
|
106
|
+
TRADING_STATE_UNKNOWN = "trading_state_unknown"
|
|
107
|
+
PRICE_LIMIT_BUY_BLOCKED = "price_limit_buy_blocked"
|
|
108
|
+
PRICE_LIMIT_SELL_BLOCKED = "price_limit_sell_blocked"
|
|
109
|
+
LIMIT_NOT_TOUCHED = "limit_not_touched"
|
|
110
|
+
INSUFFICIENT_BAR_LIQUIDITY = "insufficient_bar_liquidity"
|
|
111
|
+
INVALID_DAILY_VWAP = "invalid_daily_vwap"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class OrderUpdateReason(str, Enum):
|
|
115
|
+
"""OrderUpdate 的结构化原因码(终态迁移必填)"""
|
|
116
|
+
|
|
117
|
+
INSUFFICIENT_CASH_AT_EXECUTION = "insufficient_cash_at_execution"
|
|
118
|
+
INSUFFICIENT_AVAILABLE_POSITION_AT_EXECUTION = "insufficient_available_position_at_execution"
|
|
119
|
+
FILLED = "filled"
|
|
120
|
+
PARTIALLY_FILLED = "partially_filled"
|
|
121
|
+
CANCELLED = "cancelled"
|
|
122
|
+
EXPIRED_AT_SESSION_CLOSE = "expired_at_session_close"
|
|
123
|
+
# T4R R3 finding 2(方案 A+C):粗粒度 DAY 生命周期结构化原因码
|
|
124
|
+
EXPIRED_AT_COARSE_SESSION_END = "expired_at_coarse_session_end"
|
|
125
|
+
EXPIRED_AT_BACKTEST_END = "expired_at_backtest_end"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class OrderRejectReason(str, Enum):
|
|
129
|
+
"""OrderAck(REJECTED) 的结构化拒绝原因(禁止任意字符串)"""
|
|
130
|
+
|
|
131
|
+
INVALID_INTENT = "invalid_intent"
|
|
132
|
+
INVALID_INSTRUMENT = "invalid_instrument"
|
|
133
|
+
INVALID_QUANTITY = "invalid_quantity"
|
|
134
|
+
UNSUPPORTED_ORDER_TYPE = "unsupported_order_type"
|
|
135
|
+
UNSUPPORTED_EXECUTION_POLICY = "unsupported_execution_policy"
|
|
136
|
+
UNSUPPORTED_TIME_IN_FORCE = "unsupported_time_in_force"
|
|
137
|
+
DUPLICATE_INTENT = "duplicate_intent"
|
|
138
|
+
# Exchange Session 已收盘期间收到的 Intent(DAY 不跨日执行;用户裁定
|
|
139
|
+
# 2026-09-06:submit 阶段结构化拒绝,不创建 Order,不复用 OrderUpdate 原因码)
|
|
140
|
+
SESSION_CLOSED = "session_closed"
|
|
141
|
+
RESERVATION_FAILED = "reservation_failed"
|
|
142
|
+
MISSING_REFERENCE_PRICE = "missing_reference_price"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Order Execution 纯领域模型。"""
|
|
2
|
+
|
|
3
|
+
from quant_core.app.order_execution.domain.order_entity import (
|
|
4
|
+
ORDER_TRANSITIONS,
|
|
5
|
+
TERMINAL_ORDER_STATUSES,
|
|
6
|
+
Order,
|
|
7
|
+
is_terminal_order_status,
|
|
8
|
+
validate_order_transition,
|
|
9
|
+
)
|
|
10
|
+
from quant_core.app.order_execution.domain.order_event import (
|
|
11
|
+
ExecutionAttempt,
|
|
12
|
+
Fill,
|
|
13
|
+
OrderTransition,
|
|
14
|
+
)
|
|
15
|
+
from quant_core.app.order_execution.domain.order_policy import (
|
|
16
|
+
FeeModelProtocol,
|
|
17
|
+
SlippageModelProtocol,
|
|
18
|
+
TradabilityPolicyProtocol,
|
|
19
|
+
)
|
|
20
|
+
from quant_core.app.order_execution.domain.order_value import (
|
|
21
|
+
FeeBreakdown,
|
|
22
|
+
OrderAck,
|
|
23
|
+
OrderIntent,
|
|
24
|
+
OrderSnapshot,
|
|
25
|
+
TradabilityDecision,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"ORDER_TRANSITIONS",
|
|
30
|
+
"TERMINAL_ORDER_STATUSES",
|
|
31
|
+
"ExecutionAttempt",
|
|
32
|
+
"FeeBreakdown",
|
|
33
|
+
"Fill",
|
|
34
|
+
"FeeModelProtocol",
|
|
35
|
+
"Order",
|
|
36
|
+
"OrderAck",
|
|
37
|
+
"OrderIntent",
|
|
38
|
+
"OrderSnapshot",
|
|
39
|
+
"OrderTransition",
|
|
40
|
+
"SlippageModelProtocol",
|
|
41
|
+
"TradabilityDecision",
|
|
42
|
+
"TradabilityPolicyProtocol",
|
|
43
|
+
"is_terminal_order_status",
|
|
44
|
+
"validate_order_transition",
|
|
45
|
+
]
|