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,171 @@
|
|
|
1
|
+
"""可信本机研究代码的唯一运行入口。"""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import inspect
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import date, datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
from quant_core.app.data.interface.market_data_query import IMarketDataQuery
|
|
11
|
+
from quant_core.app.data.interface.market_revision_query import IMarketRevisionQuery
|
|
12
|
+
from quant_core.app.factor.constants import FactorFreshness
|
|
13
|
+
from quant_core.app.factor.interface.public.factor_query import IFactorQuery
|
|
14
|
+
from quant_core.app.factor.schema.factor_contract import FactorIdentity, FactorSnapshot, FactorValue
|
|
15
|
+
from quant_core.app.research.interface.research_dataset import (
|
|
16
|
+
IResearchDatasetBuilder,
|
|
17
|
+
IResearchDatasetFactory,
|
|
18
|
+
)
|
|
19
|
+
from quant_core.app.research.interface.research_evaluation import IResearchEvaluation
|
|
20
|
+
from quant_core.app.research.schema.research_schema import (
|
|
21
|
+
ResearchEvidence,
|
|
22
|
+
ResearchRunRecord,
|
|
23
|
+
ResearchRunRequest,
|
|
24
|
+
ResearchWorkspace,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class ResearchContext:
|
|
30
|
+
"""研究代码只通过公开查询契约读取历史事实。"""
|
|
31
|
+
|
|
32
|
+
market: IMarketDataQuery
|
|
33
|
+
factors: IFactorQuery | None
|
|
34
|
+
start: date
|
|
35
|
+
end: date
|
|
36
|
+
datasets: IResearchDatasetBuilder | None = None
|
|
37
|
+
evaluation: IResearchEvaluation | None = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class _TrackedFactorQuery:
|
|
41
|
+
"""仅在研究代码实际读取因子时将其版本记入运行证据。"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, query: IFactorQuery) -> None:
|
|
44
|
+
self._query = query
|
|
45
|
+
self.used = False
|
|
46
|
+
self.revisions: set[str] = set()
|
|
47
|
+
|
|
48
|
+
def _track(self, values: tuple[FactorValue, ...], requested_revision: str | None) -> None:
|
|
49
|
+
if requested_revision is not None:
|
|
50
|
+
self.revisions.add(requested_revision)
|
|
51
|
+
self.revisions.update(value.provenance.dataset_revision or "0-legacy" for value in values)
|
|
52
|
+
|
|
53
|
+
async def get_snapshot(
|
|
54
|
+
self,
|
|
55
|
+
identities: list[FactorIdentity],
|
|
56
|
+
instrument_ids: list[str],
|
|
57
|
+
decision_time: datetime,
|
|
58
|
+
*,
|
|
59
|
+
target_as_of: date | None = None,
|
|
60
|
+
freshness: FactorFreshness = FactorFreshness.EXACT,
|
|
61
|
+
dataset_revision: str | None = None,
|
|
62
|
+
) -> FactorSnapshot:
|
|
63
|
+
self.used = True
|
|
64
|
+
snapshot = await self._query.get_snapshot(
|
|
65
|
+
identities,
|
|
66
|
+
instrument_ids,
|
|
67
|
+
decision_time,
|
|
68
|
+
target_as_of=target_as_of,
|
|
69
|
+
freshness=freshness,
|
|
70
|
+
dataset_revision=dataset_revision,
|
|
71
|
+
)
|
|
72
|
+
self._track(snapshot.values, dataset_revision)
|
|
73
|
+
return snapshot
|
|
74
|
+
|
|
75
|
+
async def get_series(
|
|
76
|
+
self,
|
|
77
|
+
identity: FactorIdentity,
|
|
78
|
+
instrument_id: str,
|
|
79
|
+
start: datetime,
|
|
80
|
+
end: datetime,
|
|
81
|
+
decision_time: datetime,
|
|
82
|
+
*,
|
|
83
|
+
dataset_revision: str | None = None,
|
|
84
|
+
) -> tuple[FactorValue, ...]:
|
|
85
|
+
self.used = True
|
|
86
|
+
values = await self._query.get_series(
|
|
87
|
+
identity,
|
|
88
|
+
instrument_id,
|
|
89
|
+
start,
|
|
90
|
+
end,
|
|
91
|
+
decision_time,
|
|
92
|
+
dataset_revision=dataset_revision,
|
|
93
|
+
)
|
|
94
|
+
self._track(values, dataset_revision)
|
|
95
|
+
return values
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def execute_research(
|
|
99
|
+
request: ResearchRunRequest,
|
|
100
|
+
market: IMarketDataQuery,
|
|
101
|
+
revisions: IMarketRevisionQuery,
|
|
102
|
+
*,
|
|
103
|
+
workspace: ResearchWorkspace | None = None,
|
|
104
|
+
factors_query: IFactorQuery | None = None,
|
|
105
|
+
dataset_factory: IResearchDatasetFactory | None = None,
|
|
106
|
+
evaluation: IResearchEvaluation | None = None,
|
|
107
|
+
) -> ResearchRunRecord:
|
|
108
|
+
"""执行显式研究定义并返回记录,不读取或保存管理数据库。"""
|
|
109
|
+
if request.research_id and workspace is None:
|
|
110
|
+
raise ValueError("已注册研究需先显式加载定义")
|
|
111
|
+
source = workspace.python_source if workspace else request.python_source
|
|
112
|
+
assert source is not None
|
|
113
|
+
parameters = {**(workspace.default_parameters if workspace else {}), **request.parameters}
|
|
114
|
+
config = {
|
|
115
|
+
"start": request.start.isoformat(),
|
|
116
|
+
"end": request.end.isoformat(),
|
|
117
|
+
"parameters": parameters,
|
|
118
|
+
}
|
|
119
|
+
data_revisions: dict[str, str] = {}
|
|
120
|
+
result: ResearchEvidence | None = None
|
|
121
|
+
error: str | None = None
|
|
122
|
+
factors = _TrackedFactorQuery(factors_query) if factors_query is not None else None
|
|
123
|
+
datasets = dataset_factory(factors) if dataset_factory and factors else None
|
|
124
|
+
try:
|
|
125
|
+
spec = ResearchWorkspace(
|
|
126
|
+
research_id=workspace.research_id if workspace else "temporary",
|
|
127
|
+
name=workspace.name if workspace else "临时研究",
|
|
128
|
+
python_source=source,
|
|
129
|
+
)
|
|
130
|
+
spec.validate_source()
|
|
131
|
+
data_revisions = await revisions.snapshot(request.start, request.end)
|
|
132
|
+
namespace: dict[str, Any] = {"__name__": "research_user_code"}
|
|
133
|
+
exec(compile(source, "<research>", "exec"), namespace)
|
|
134
|
+
callback = namespace["run"]
|
|
135
|
+
value = callback(
|
|
136
|
+
ResearchContext(
|
|
137
|
+
market,
|
|
138
|
+
factors,
|
|
139
|
+
request.start,
|
|
140
|
+
request.end,
|
|
141
|
+
datasets,
|
|
142
|
+
evaluation,
|
|
143
|
+
),
|
|
144
|
+
parameters,
|
|
145
|
+
)
|
|
146
|
+
if inspect.isawaitable(value):
|
|
147
|
+
value = await value
|
|
148
|
+
result = ResearchEvidence.model_validate(value)
|
|
149
|
+
if result.sample_start != request.start or result.sample_end != request.end:
|
|
150
|
+
raise ValueError("研究输出的样本区间与运行请求不一致")
|
|
151
|
+
result.model_dump(mode="json")
|
|
152
|
+
except Exception as exc:
|
|
153
|
+
result = None
|
|
154
|
+
error = f"{type(exc).__name__}: {exc}"
|
|
155
|
+
if factors is not None and factors.used:
|
|
156
|
+
data_revisions["factor"] = ",".join(sorted(factors.revisions)) or "no_values"
|
|
157
|
+
if datasets is not None:
|
|
158
|
+
data_revisions.update(datasets.revisions)
|
|
159
|
+
record = ResearchRunRecord(
|
|
160
|
+
uuid=str(uuid4()),
|
|
161
|
+
status="completed" if result is not None else "failed",
|
|
162
|
+
research_id=request.research_id,
|
|
163
|
+
source_kind="workspace" if workspace else "temporary",
|
|
164
|
+
source_snapshot=source,
|
|
165
|
+
source_digest=hashlib.sha256(source.encode("utf-8")).hexdigest(),
|
|
166
|
+
config_snapshot=config,
|
|
167
|
+
data_revisions=data_revisions,
|
|
168
|
+
result=result,
|
|
169
|
+
error=error,
|
|
170
|
+
)
|
|
171
|
+
return record
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Strategy Domain SPEC
|
|
2
|
+
|
|
3
|
+
> Strategy 域的架构 SSOT:领域模型、边界、契约与演进范围以本文件为准。
|
|
4
|
+
> 字段级语义见各 schema 模块 docstring;决策背景见根 [DECISIONS.md](../../DECISIONS.md)。
|
|
5
|
+
> **P1 已 Freeze(2026-09-03)**:本域达到 Backtest 准入状态;后续只做增量扩展,不阻塞 Backtest。
|
|
6
|
+
|
|
7
|
+
## 1. 领域定位与公式
|
|
8
|
+
|
|
9
|
+
Strategy 域建立 **统一策略生产体系**:Human / Agent / Python(未来 MyLanguage / UI DSL)
|
|
10
|
+
都生产同一个 Canonical Definition,Runtime 稳定执行它们。
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Strategy Domain =
|
|
14
|
+
Authoring(多入口 → 同一 Canonical Definition)
|
|
15
|
+
+ Factor Definition / Runtime / Result
|
|
16
|
+
+ Screener
|
|
17
|
+
+ Strategy Definition / Runtime
|
|
18
|
+
+ Signal
|
|
19
|
+
+ Validation(Definition / Runtime 两层)
|
|
20
|
+
+ Evaluation(契约;绩效计算归 Backtest)
|
|
21
|
+
+ Human Review(Agent Artifact 进 Paper/Live 的硬门槛)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
核心定位:**Agent First ≠ Agent Direct Execution**——Agent 负责 Generate / Analyze /
|
|
25
|
+
Iterate,但任何 Quant Artifact 都不得绕过 Canonical Contract → Validation → Backtest →
|
|
26
|
+
Human Approval 进入 Live。
|
|
27
|
+
|
|
28
|
+
## 2. 领域模型:Definition / Runtime / Result
|
|
29
|
+
|
|
30
|
+
Factor 与 Strategy 都遵循同一模式:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
Definition(是什么、怎么算) Runtime(执行) Result
|
|
34
|
+
FactorDefinition → FactorService(FactorRuntime) → FactorResult
|
|
35
|
+
StrategyDefinition → StrategyRuntimeService → Signal
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Screener 不是 Strategy 的必经阶段**——它是域内候选集生成能力,只有横截面类策略经过它:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Cross-sectional:Market → Factor → Screener → Strategy → Signal
|
|
42
|
+
Single Instrument Timing:BarEvent → Strategy → Signal(完全不经过 Screener)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
禁止把 Domain 硬编码成 Factor → Screener → Strategy → Signal 的单一路径。
|
|
46
|
+
|
|
47
|
+
## 3. P1 真实执行模型(重要声明)
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
StrategyDefinition(Canonical Artifact / Metadata / 声明式 Contract)
|
|
51
|
+
+
|
|
52
|
+
Python Native Strategy Implementation(StrategyProtocol 实现)
|
|
53
|
+
↓
|
|
54
|
+
StrategyRuntimeService
|
|
55
|
+
↓
|
|
56
|
+
Signal
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> **P1 的 `StrategyDefinition` 是 Canonical Artifact、Metadata 与声明式 Contract;
|
|
60
|
+
> 实际执行由 Python Native Strategy implementation 承载。P1 不宣称已经实现完整的
|
|
61
|
+
> Definition-driven execution。**
|
|
62
|
+
|
|
63
|
+
Definition 回答「这个策略是什么」,Implementation 回答「P1 当前如何执行它」;
|
|
64
|
+
二者通过 `StrategyDefinition.implementation_ref`(dotted path)显式关联,不靠文件名猜测。
|
|
65
|
+
Definition → Compiler/Interpreter → Executable Strategy 的演进属 Agent DSL / MyLanguage /
|
|
66
|
+
UI DSL 阶段(P2+),届时新增编译入口而非第二套 Runtime。
|
|
67
|
+
|
|
68
|
+
## 4. 硬规则
|
|
69
|
+
|
|
70
|
+
1. **Definition / Runtime / Result 概念分离**:Definition 是 Artifact(非运行实例、非 Signal)。
|
|
71
|
+
2. **所有 Authoring 入口收敛到 Canonical Definition**:不建立 Agent 专属策略模型、
|
|
72
|
+
不为 DSL 建第二套 Runtime。MyLanguage 是 Authoring Adapter(Parser → AST → Definition),
|
|
73
|
+
禁止直接驱动 Backtest Engine。
|
|
74
|
+
3. **Strategy 只产生 Signal,不产生 Broker Order**:Strategy 回答「应该做什么」,
|
|
75
|
+
Execution / Backtest 回答「怎么成交」。资产变化只发生在
|
|
76
|
+
`Signal → Backtest/Execution → Order → Fill → Portfolio Update` 链路上。
|
|
77
|
+
4. **Strategy 环境无关**:市场时间只认 `event_time`(业务代码禁止
|
|
78
|
+
`datetime.now()`/`datetime.utcnow()`/`time.time()`);
|
|
79
|
+
数据只经 `StrategyContext` 注入的 Port(禁止直连 AKShare / Repository / DB / Broker);
|
|
80
|
+
同一 Definition 在 Backtest / Paper / Live 下业务逻辑不变(环境由 `StrategyRunMode` 表达,
|
|
81
|
+
不混入 StrategyStatus)。
|
|
82
|
+
5. **非结构化输入必须先转 Canonical FactorResult**:禁止 Strategy 内部直接 prompt LLM、
|
|
83
|
+
自由文本决定买卖;FactorResult 的 NULL = 不可计算(禁止 0 填充,与 Market NULL 语义一致)。
|
|
84
|
+
Strategy 不得重复计算 FactorService 已有的因子(含 MA 等参考线)。
|
|
85
|
+
6. **状态与环境分离**:`StrategyStatus` / `FactorStatus` 描述 Artifact 生命周期;
|
|
86
|
+
`StrategyRunMode` 描述运行环境。
|
|
87
|
+
7. **Agent Artifact 可追溯**:Definition 携带 authoring_source / created_by /
|
|
88
|
+
parent_version / implementation_ref。
|
|
89
|
+
8. **Human Approval 是 Paper/Live 的前置条件**(领域规则,不是 UI 细节)。
|
|
90
|
+
9. **Screener 契约唯一**:`ScreenerService` 的运行输入就是 Canonical `ScreenerSpec`
|
|
91
|
+
(来自 `StrategyDefinition.screening_rules`),不存在 Definition 一套声明模型、
|
|
92
|
+
Runtime 另一套临时 dict 模型。
|
|
93
|
+
|
|
94
|
+
## 5. StrategyContext 状态职责表
|
|
95
|
+
|
|
96
|
+
| 载体 | 归属 | 允许 | 禁止 |
|
|
97
|
+
|---|---|---|---|
|
|
98
|
+
| `StrategyContext.state` | Strategy 自己 | last_signal_time / consecutive_count / trailing_state / 自定义计数器 | **cash / position quantity / available quantity / cost basis / equity** |
|
|
99
|
+
| `StrategyContext.portfolio` | Portfolio / Backtest(只读注入) | 经 `PortfolioReaderProtocol` 读取持仓与资金 | 任何修改(无写方法) |
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
class PortfolioReaderProtocol(Protocol):
|
|
103
|
+
async def get_position(self, instrument_id: str) -> PositionView | None: ...
|
|
104
|
+
async def get_cash(self) -> CashView: ...
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
只读、最小化。禁止提前设计完整 Portfolio Domain / 多账户 / Broker account / 对账。
|
|
108
|
+
|
|
109
|
+
## 6. 目录与职责
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
app/strategy/
|
|
113
|
+
├── constants.py # 枚举(Type/Style/Status/RunMode/Signal…)
|
|
114
|
+
├── exceptions.py # 域内异常
|
|
115
|
+
├── schema/
|
|
116
|
+
│ ├── factor_schema.py # FactorDefinition / FactorResult
|
|
117
|
+
│ ├── strategy_schema.py # StrategyDefinition / RuleCondition / ScreenerSpec
|
|
118
|
+
│ ├── screener_schema.py # ScreenerRequest(spec: ScreenerSpec)/ ScreenerResult
|
|
119
|
+
│ ├── signal_schema.py # Signal
|
|
120
|
+
│ └── portfolio_schema.py # PositionView / CashView(只读视图)
|
|
121
|
+
├── service/
|
|
122
|
+
│ ├── expression.py # 受限公式解释器(ref/ma + 算术,ast 白名单)
|
|
123
|
+
│ ├── factor_service.py # Factor Runtime:注册 Definition → 计算 FactorResult
|
|
124
|
+
│ ├── screener_service.py # ScreenerSpec 驱动:Universe → Filter → Rank → Top N
|
|
125
|
+
│ ├── strategy_protocol.py # StrategyProtocol / StrategyContext / PortfolioReader
|
|
126
|
+
│ └── strategy_runtime_service.py
|
|
127
|
+
└── strategies/
|
|
128
|
+
└── momentum_volume_breakout.py # P1 Demo(Python Native,公式移植自 FactorHub)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**StrategyRuntimeService 职责**(收口):
|
|
132
|
+
|
|
133
|
+
```text
|
|
134
|
+
├── 接收 MarketEvent 或完整 MarketTimeSlice(不做 Bar-centric 假设)
|
|
135
|
+
├── 构造 StrategyContext
|
|
136
|
+
├── 调度 required Factor(经 StrategyFactorProviderProtocol)
|
|
137
|
+
├── 调用 Strategy implementation(on_bar / on_session)
|
|
138
|
+
├── 收集 Signal
|
|
139
|
+
└── 维护 strategy-local runtime state
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Backtest 的稳定调度边界是单时间片入口:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
MarketTimeSlice@T
|
|
146
|
+
|
|
|
147
|
+
v
|
|
148
|
+
run_slice(time_slice) → list[Signal]
|
|
149
|
+
|
|
|
150
|
+
v
|
|
151
|
+
Backtest 执行 Fill / 更新 Portfolio
|
|
152
|
+
|
|
|
153
|
+
v
|
|
154
|
+
MarketTimeSlice@T+1 读取更新后的只读 PortfolioView
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`run_slice()` 在该片回调全部完成后立即返回 Signal Batch,不产生 Order/Fill、
|
|
158
|
+
不修改 Portfolio。`run_slices()` 只是复用 `run_slice()` 的整段回放便利包装;需要在
|
|
159
|
+
片间形成 Portfolio 反馈的 Backtest 必须逐片调用 `run_slice()`。
|
|
160
|
+
|
|
161
|
+
它不是「加载窗口 Bar 的服务」——拉窗口只是 Context 装配的实现细节。
|
|
162
|
+
未知事件按契约忽略;默认消费**已通过运行环境前置验证的数据**(Backtest 启动前经
|
|
163
|
+
`IMarketDataVerification` 检查),不在 `on_bar()` 内重复 `verify_bars()`。
|
|
164
|
+
|
|
165
|
+
## 7. 对 Market 的依赖边界
|
|
166
|
+
|
|
167
|
+
只允许依赖 Market 跨域稳定 Port(见 [market/SPEC.md](../market/SPEC.md) §4):
|
|
168
|
+
|
|
169
|
+
- `IMarketDataQuery`(Pull 行情窗口;经 StrategyContext.market)
|
|
170
|
+
- `IMarketEventSource`(事件流输入)
|
|
171
|
+
- `MarketTimeSlice`(Backtest 注入的完整同市场时刻信息集;时间片 Source 由 Backtest 编排)
|
|
172
|
+
- `IMarketDataVerification`(由 Backtest 前置调用,Strategy 不逐 bar 调用)
|
|
173
|
+
|
|
174
|
+
禁止依赖:Market Repository / Adapter / AKShare / MarketSyncService / Database / Broker。
|
|
175
|
+
|
|
176
|
+
## 8. 与 Backtest / Research 的边界
|
|
177
|
+
|
|
178
|
+
- Backtest 消费 Strategy Runtime 的 Signal 流,负责 Order / Fill / Commission /
|
|
179
|
+
Slippage / SimulatedPortfolio / Metrics;禁止复制 Strategy 逻辑。
|
|
180
|
+
Signal 契约禁止包含 broker_order_id / fill_price / commission / slippage / account_id。
|
|
181
|
+
Backtest 准入链:`BacktestRequest → ensure_backtest_data_ready → MarketTimeSlice Replay →
|
|
182
|
+
StrategyRuntime.run_slice → Signal Batch → Order → Simulated Fill → SimulatedPortfolio → Metrics`;
|
|
183
|
+
每片完成 Portfolio 更新后才能处理下一片。
|
|
184
|
+
- Research 可消费 Definition / FactorResult / Signal / 回测结果做解释;
|
|
185
|
+
自然语言结论不得污染 FactorResult / Signal 契约。
|
|
186
|
+
|
|
187
|
+
## 9. Validation 两层
|
|
188
|
+
|
|
189
|
+
| 层 | 检查内容 | 现状 |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| Definition Validation(静态) | identity/version 格式(Field pattern)、styles 合法、expression/implementation_ref 互斥、规则组非空且 left 非空、required_factors 引用合法(传入已注册因子集校验) | 已实现(`validate_definition()`) |
|
|
192
|
+
| Runtime/Data Validation | 因子窗口是否足够(→ FactorResult NULL);Market Data 健康检查归 Backtest 前置 Guard | 已实现 / 前置 Guard 已具备 |
|
|
193
|
+
|
|
194
|
+
不引入通用 Rule Engine。
|
|
195
|
+
|
|
196
|
+
## 10. 实现范围
|
|
197
|
+
|
|
198
|
+
- **P1(已实现,FROZEN)**:六契约(StrategyDefinition / FactorDefinition / FactorResult /
|
|
199
|
+
Signal / StrategyContext / StrategyProtocol)、ScreenerSpec 统一驱动 ScreenerService、
|
|
200
|
+
PortfolioReader 最小契约、受限 expression 解释器、FactorService(momentum_20d /
|
|
201
|
+
volume_ratio_5d / ma_close_10,公式移植自 FactorHub)、StrategyRuntimeService(事件调度 +
|
|
202
|
+
portfolio 注入 + strategy-local state + 单时间片 `run_slice()` 反馈边界)、MomentumVolumeBreakout Demo、
|
|
203
|
+
Deterministic Replay E2E(两次运行业务结果一致)。
|
|
204
|
+
- **P2+(不阻塞 Backtest)**:Agent Authoring 入口、Strategy/Factor 持久化与生命周期 API、
|
|
205
|
+
Backtest 调用入口与 Evaluation Contract、Human Review lifecycle、MyLanguage Adapter、
|
|
206
|
+
非结构化 Factor(TEXT + confidence)、on_quote/on_news 回调、Paper/Live。
|
|
207
|
+
- **明确不做**:通用 Strategy IR、Visual Builder、AutoML、遗传规划、Multi-Agent 策略委员会、
|
|
208
|
+
复杂 Workflow Engine、通用 Event Bus。
|
|
209
|
+
|
|
210
|
+
## 11. 边界检查
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# app/strategy/ 业务代码中 grep akshare|ts_code|sqlalchemy|datetime.now|time.time
|
|
214
|
+
# 必须为 0 命中(文档性注释提及"禁止…"除外)
|
|
215
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Strategy Domain:Authoring + Factor + Screener + Strategy + Signal(见 SPEC.md)"""
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy 域常量与枚举
|
|
3
|
+
|
|
4
|
+
概念分层(SSOT 见 SPEC):
|
|
5
|
+
- Definition / Runtime / Result 三层分离:Factor 与 Strategy 都遵循
|
|
6
|
+
Definition(是什么、怎么算)→ Runtime(执行)→ Result(Canonical
|
|
7
|
+
FactorValue / Signal,R2-1 起因子值唯一形态是 quant_core.app.factor.FactorValue)。
|
|
8
|
+
- 状态(StrategyStatus / FactorStatus)描述 Artifact 生命周期;
|
|
9
|
+
运行环境(StrategyRunMode)单独定义——同一 Definition 在不同环境下业务逻辑不变。
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from enum import Enum
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StrategyType(str, Enum):
|
|
16
|
+
"""策略决策范式(不是风格:Momentum/Value 等属 StrategyStyle)"""
|
|
17
|
+
|
|
18
|
+
SELECTION = "selection" # 选股型
|
|
19
|
+
TIMING = "timing" # 择时型
|
|
20
|
+
CROSS_SECTIONAL = "cross_sectional" # 横截面因子排名 / 多因子
|
|
21
|
+
EVENT_DRIVEN = "event_driven" # 新闻 / 公告 / 市场事件驱动
|
|
22
|
+
PORTFOLIO = "portfolio" # 组合权重 / 再平衡
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StrategyStyle(str, Enum):
|
|
26
|
+
"""策略风格 Tag(一个策略可含多个 Style)"""
|
|
27
|
+
|
|
28
|
+
MOMENTUM = "momentum"
|
|
29
|
+
TREND = "trend"
|
|
30
|
+
MEAN_REVERSION = "mean_reversion"
|
|
31
|
+
BREAKOUT = "breakout"
|
|
32
|
+
VALUE = "value"
|
|
33
|
+
QUALITY = "quality"
|
|
34
|
+
GROWTH = "growth"
|
|
35
|
+
VOLATILITY = "volatility"
|
|
36
|
+
EVENT = "event"
|
|
37
|
+
MULTI_FACTOR = "multi_factor"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class StrategyStatus(str, Enum):
|
|
41
|
+
"""Strategy Artifact 生命周期(运行环境见 StrategyRunMode,不得混入)"""
|
|
42
|
+
|
|
43
|
+
DRAFT = "draft"
|
|
44
|
+
VALIDATING = "validating"
|
|
45
|
+
BACKTESTED = "backtested"
|
|
46
|
+
REVIEW_REQUIRED = "review_required"
|
|
47
|
+
APPROVED = "approved"
|
|
48
|
+
REJECTED = "rejected"
|
|
49
|
+
PAPER = "paper"
|
|
50
|
+
LIVE = "live"
|
|
51
|
+
ARCHIVED = "archived"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class StrategyRunMode(str, Enum):
|
|
55
|
+
"""Runtime 运行环境:Definition 不变,只换运行环境与 EventSource"""
|
|
56
|
+
|
|
57
|
+
RESEARCH = "research"
|
|
58
|
+
BACKTEST = "backtest"
|
|
59
|
+
PAPER = "paper"
|
|
60
|
+
LIVE = "live"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class AuthoringSource(str, Enum):
|
|
64
|
+
"""Artifact 生产入口(Agent First:Agent 与 Human 走同一 Canonical Definition)"""
|
|
65
|
+
|
|
66
|
+
HUMAN = "human"
|
|
67
|
+
AGENT = "agent"
|
|
68
|
+
PYTHON = "python"
|
|
69
|
+
MYLANGUAGE = "mylanguage" # Authoring Language Adapter,reserved
|
|
70
|
+
DSL = "dsl"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SignalType(str, Enum):
|
|
74
|
+
"""信号类型(Strategy 产生的 Canonical Decision Result)"""
|
|
75
|
+
|
|
76
|
+
ENTER = "enter"
|
|
77
|
+
EXIT = "exit"
|
|
78
|
+
HOLD = "hold"
|
|
79
|
+
WATCH = "watch"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class SignalSide(str, Enum):
|
|
83
|
+
"""信号方向(A股 V1:LONG / NONE;SHORT reserved)"""
|
|
84
|
+
|
|
85
|
+
LONG = "long"
|
|
86
|
+
NONE = "none"
|
|
87
|
+
SHORT = "short" # reserved
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Strategy 域业务异常(Common 拥有机制,Domain 拥有语义)"""
|
|
2
|
+
|
|
3
|
+
from quant_core.common.exceptions.base import AppException, ConflictException, ValidationException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class StrategyPublicationConflict(ConflictException):
|
|
7
|
+
error_code = "STRATEGY_PUBLICATION_CONFLICT"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PublishedStrategyNotFound(AppException):
|
|
11
|
+
error_code = "PUBLISHED_STRATEGY_NOT_FOUND"
|
|
12
|
+
http_status = 404
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class StrategyValidationException(ValidationException):
|
|
16
|
+
"""Strategy/Factor Definition 校验失败(Definition Validation 层)"""
|
|
17
|
+
|
|
18
|
+
error_code = "STRATEGY_VALIDATION_ERROR"
|
|
19
|
+
http_status = 422
|
|
20
|
+
|
|
21
|
+
def __init__(self, message: str = "策略定义校验失败"):
|
|
22
|
+
super().__init__(message)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class StrategyNotRegisteredException(AppException):
|
|
26
|
+
"""指定策略未注册"""
|
|
27
|
+
|
|
28
|
+
error_code = "STRATEGY_NOT_REGISTERED"
|
|
29
|
+
http_status = 404
|
|
30
|
+
|
|
31
|
+
def __init__(self, strategy_id: str):
|
|
32
|
+
super().__init__(f"strategy 未注册: {strategy_id}")
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""策略组合构建公共接口;不产生订单或修改资产。"""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from quant_core.app.factor.schema.selection_types import SelectionResult
|
|
7
|
+
from quant_core.app.strategy.schema.portfolio_target import PortfolioConstraints, TargetPortfolio
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IPortfolioConstruction(Protocol):
|
|
11
|
+
def build(
|
|
12
|
+
self,
|
|
13
|
+
selection: SelectionResult,
|
|
14
|
+
constraints: PortfolioConstraints,
|
|
15
|
+
*,
|
|
16
|
+
current_weights: dict[str, Decimal] | None = None,
|
|
17
|
+
industries: dict[str, str] | None = None,
|
|
18
|
+
holding_sessions: dict[str, int] | None = None,
|
|
19
|
+
) -> TargetPortfolio: ...
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Public Python strategy callback contract."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from quant_core.app.data.schema.event_schema import BarEvent, TradingSessionEvent
|
|
6
|
+
from quant_core.app.strategy.schema.signal_schema import Signal
|
|
7
|
+
from quant_core.app.strategy.strategy_context import StrategyContext
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class IStrategy(Protocol):
|
|
12
|
+
"""策略实现契约(Python Native 策略的第一入口)
|
|
13
|
+
|
|
14
|
+
所有入口(Human / Agent / MyLanguage)最终编译为同一个 Canonical
|
|
15
|
+
StrategyDefinition;Python Native 策略额外实现本 Protocol 以被 Runtime 执行。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def definition(self) -> Any:
|
|
19
|
+
"""返回策略的 Canonical StrategyDefinition"""
|
|
20
|
+
...
|
|
21
|
+
|
|
22
|
+
async def on_bar(self, event: BarEvent, context: "StrategyContext") -> list[Signal]:
|
|
23
|
+
"""Bar 事件驱动的决策入口"""
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
async def on_session(
|
|
27
|
+
self, event: TradingSessionEvent, context: "StrategyContext"
|
|
28
|
+
) -> list[Signal]:
|
|
29
|
+
"""Session 事件驱动的决策入口"""
|
|
30
|
+
...
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""策略已绑定决策时点的行情读取面,不暴露完整 Market 生产能力。"""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from datetime import date, datetime
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
|
|
7
|
+
from quant_core.app.data.schema.adjustment_schema import AdjustedBarSeries, BarViewRequest
|
|
8
|
+
from quant_core.app.data.schema.market_schema import (
|
|
9
|
+
BarResponse,
|
|
10
|
+
InstrumentResponse,
|
|
11
|
+
TradingCalendarDayResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IStrategyMarketQuery(Protocol):
|
|
16
|
+
async def get_bars(
|
|
17
|
+
self,
|
|
18
|
+
instrument_id: str,
|
|
19
|
+
frequency: str,
|
|
20
|
+
start: datetime | None = None,
|
|
21
|
+
end: datetime | None = None,
|
|
22
|
+
limit: int = 1000,
|
|
23
|
+
) -> Sequence[BarResponse]: ...
|
|
24
|
+
|
|
25
|
+
async def get_adjusted_bars(
|
|
26
|
+
self,
|
|
27
|
+
instrument_id: str,
|
|
28
|
+
frequency: str,
|
|
29
|
+
view: BarViewRequest,
|
|
30
|
+
start: datetime | None = None,
|
|
31
|
+
end: datetime | None = None,
|
|
32
|
+
limit: int = 1000,
|
|
33
|
+
) -> AdjustedBarSeries: ...
|
|
34
|
+
|
|
35
|
+
async def get_instrument(self, instrument_id: str) -> InstrumentResponse: ...
|
|
36
|
+
|
|
37
|
+
async def list_instruments_on(
|
|
38
|
+
self, on_date: date, limit: int = 10000
|
|
39
|
+
) -> list[InstrumentResponse]: ...
|
|
40
|
+
|
|
41
|
+
async def search_instruments(
|
|
42
|
+
self,
|
|
43
|
+
keyword: str | None = None,
|
|
44
|
+
instrument_type: str | None = None,
|
|
45
|
+
exchange: str | None = None,
|
|
46
|
+
limit: int = 100,
|
|
47
|
+
) -> list[InstrumentResponse]: ...
|
|
48
|
+
|
|
49
|
+
async def is_trading_day(self, exchange: str, trade_date: date) -> bool: ...
|
|
50
|
+
|
|
51
|
+
async def list_trading_days(
|
|
52
|
+
self, exchange: str, start: date, end: date
|
|
53
|
+
) -> list[TradingCalendarDayResponse]: ...
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Async portfolio view consumed by strategies."""
|
|
2
|
+
|
|
3
|
+
from typing import Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
from quant_core.app.portfolio.ledger_schema import PositionLotSnapshot
|
|
6
|
+
from quant_core.app.portfolio.portfolio_schema import PortfolioSnapshot
|
|
7
|
+
from quant_core.app.strategy.schema.portfolio_schema import CashView, PositionView
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@runtime_checkable
|
|
11
|
+
class IStrategyPortfolioReader(Protocol):
|
|
12
|
+
"""持仓/资金只读能力(V1 由 Backtest 模拟持仓实现;可选依赖)
|
|
13
|
+
|
|
14
|
+
最小读取边界:Strategy 可以读取持仓与资金,但不能拥有或修改——
|
|
15
|
+
资产变化只发生在 Signal → Backtest/Execution → Order → Fill →
|
|
16
|
+
Portfolio Update 链路上。禁止在此扩展多账户/Broker account/对账。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
async def get_position(self, instrument_id: str) -> PositionView | None:
|
|
20
|
+
"""单个标的持仓视图(未持仓返回 None)"""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
async def get_cash(self) -> CashView:
|
|
24
|
+
"""可用资金视图"""
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
async def get_snapshot(self) -> PortfolioSnapshot:
|
|
28
|
+
"""当前账户总值和持仓市值的公开只读快照。"""
|
|
29
|
+
...
|
|
30
|
+
|
|
31
|
+
async def get_position_lots(self) -> tuple[PositionLotSnapshot, ...]:
|
|
32
|
+
"""当前持仓的真实成交批次,包含取得时间和剩余数量。"""
|
|
33
|
+
...
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Strategy consumes already-published PIT factor values; it never computes them."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
from quant_core.app.factor.schema.factor_contract import FactorValue
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class IStrategyFactorReader(Protocol):
|
|
10
|
+
async def read_many(
|
|
11
|
+
self,
|
|
12
|
+
factor_names: tuple[str, ...],
|
|
13
|
+
instrument_ids: tuple[str, ...],
|
|
14
|
+
decision_time: datetime,
|
|
15
|
+
) -> dict[str, dict[str, FactorValue]]: ...
|
|
16
|
+
|
|
17
|
+
async def read(
|
|
18
|
+
self, factor_name: str, instrument_id: str, decision_time: datetime
|
|
19
|
+
) -> FactorValue | None: ...
|