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,306 @@
|
|
|
1
|
+
"""Factor Runtime Query Service(T4)——离线、point-in-time 安全的因子查询面
|
|
2
|
+
|
|
3
|
+
语义(T4 Task AC + R2-5/R2-6):
|
|
4
|
+
- 消费面只依赖 T2 Store Protocol(不联网、不依赖 Provider 类型);
|
|
5
|
+
- resolve_window:lookback 按交易日确定性解析(anchor 必须是交易日;
|
|
6
|
+
含 anchor 当日、往回 lookback-1 个交易日),日历不足结构化报错;
|
|
7
|
+
- get_snapshot:显式 target_as_of + freshness 策略(R2-5,T5 前裁定)——
|
|
8
|
+
默认 EXACT:只消费目标业务日的值,目标日无值绝不因历史旧值冒充
|
|
9
|
+
PRESENT;LATEST_KNOWN:显式回退最近可得历史值,覆盖报告标注 stale;
|
|
10
|
+
输入顺序归一(排序+去重)后透传 Store;Store 层强制
|
|
11
|
+
available_at <= decision_time(T2/T1 PIT 门禁);
|
|
12
|
+
- coverage_report:逐 identity×instrument 输出封闭枚举 FactorCoverageState
|
|
13
|
+
(R1-4):PRESENT / MISSING / UNAVAILABLE / NOT_YET_PUBLISHED;空请求
|
|
14
|
+
结构化拒绝(R2-6:空 cells 上 all() 为 True 的假 complete 不允许);
|
|
15
|
+
未发布/缺失的区分用 Store 显式 probe_status 存在性/状态查询(R2-6,
|
|
16
|
+
替代任意 +3650 天远期探针),只读 status、不产生决策可见值;
|
|
17
|
+
- 相同请求(含输入顺序变化)输出确定性一致(T1 Snapshot 自身归一保证);
|
|
18
|
+
- 本 Service 真实结构满足 IFactorQuery(R1-1,isinstance 守护)。
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from datetime import date, datetime
|
|
23
|
+
|
|
24
|
+
from quant_core.app.factor.constants import (
|
|
25
|
+
FactorCoverageState,
|
|
26
|
+
FactorFreshness,
|
|
27
|
+
FactorStatus,
|
|
28
|
+
)
|
|
29
|
+
from quant_core.app.factor.exceptions import (
|
|
30
|
+
InsufficientFactorHistoryError,
|
|
31
|
+
InvalidFactorRangeError,
|
|
32
|
+
)
|
|
33
|
+
from quant_core.app.factor.interface.factor_query_store import IFactorQueryStore
|
|
34
|
+
from quant_core.app.factor.schema.factor_contract import (
|
|
35
|
+
FactorIdentity,
|
|
36
|
+
FactorSnapshot,
|
|
37
|
+
FactorSnapshotSelection,
|
|
38
|
+
FactorValue,
|
|
39
|
+
)
|
|
40
|
+
from quant_core.app.factor.schema.factor_query_result_schema import ProbeCellStatus
|
|
41
|
+
from quant_core.app.factor.visibility import next_trading_day_open
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class CoverageState:
|
|
46
|
+
"""单 identity×instrument 的覆盖状态(封闭枚举,R1-4;R2-5 新鲜度证据)
|
|
47
|
+
|
|
48
|
+
- value_as_of:实际消费值的归属业务日(PRESENT 时有值);
|
|
49
|
+
- stale:LATEST_KNOWN 回退消费了早于 target_as_of 的旧值(EXACT 下恒
|
|
50
|
+
False——EXACT 不回退,旧值不进快照)。
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
state: FactorCoverageState
|
|
54
|
+
value_as_of: date | None = None
|
|
55
|
+
stale: bool = False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class FactorCoverageReport:
|
|
60
|
+
"""截面覆盖报告(键:identity.logical_key × instrument_id)
|
|
61
|
+
|
|
62
|
+
complete 仅在全部格子 PRESENT 且无 stale 回退时为 True——MISSING/
|
|
63
|
+
UNAVAILABLE/NOT_YET_PUBLISHED 一律不算完整(R1-4/R2-5)。
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
cells: dict[tuple[tuple, str], CoverageState]
|
|
67
|
+
target_as_of: date
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def complete(self) -> bool:
|
|
71
|
+
return all(
|
|
72
|
+
c.state is FactorCoverageState.PRESENT and not c.stale for c in self.cells.values()
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class FactorRuntimeService:
|
|
77
|
+
"""因子运行时查询(Strategy/Backtest 唯一因子消费面)"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
query: IFactorQueryStore,
|
|
82
|
+
trading_days: list[date],
|
|
83
|
+
) -> None:
|
|
84
|
+
self._query = query
|
|
85
|
+
self._trading_days = sorted(trading_days)
|
|
86
|
+
|
|
87
|
+
# ------------------------------------------------------------------
|
|
88
|
+
# lookback / warmup 解析
|
|
89
|
+
# ------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
def resolve_window(self, anchor: date, lookback: int) -> tuple[date, date]:
|
|
92
|
+
"""warmup 语义:anchor 往回 lookback 个交易日 + anchor 当日 → [start, end]
|
|
93
|
+
|
|
94
|
+
确定性:同一 (anchor, lookback) 恒等映射;anchor 非交易日或日历
|
|
95
|
+
不足 → InsufficientFactorHistoryError(不静默截断)。
|
|
96
|
+
"""
|
|
97
|
+
if lookback < 1:
|
|
98
|
+
raise InvalidFactorRangeError(f"lookback 必须 ≥1,收到 {lookback}")
|
|
99
|
+
days = self._trading_days
|
|
100
|
+
try:
|
|
101
|
+
pos = days.index(anchor)
|
|
102
|
+
except ValueError:
|
|
103
|
+
raise InsufficientFactorHistoryError(
|
|
104
|
+
f"anchor {anchor} 不是交易日(或不在日历范围内)"
|
|
105
|
+
) from None
|
|
106
|
+
start_pos = pos - lookback
|
|
107
|
+
if start_pos < 0:
|
|
108
|
+
raise InsufficientFactorHistoryError(
|
|
109
|
+
f"交易日历不足:anchor {anchor} 往回 {lookback} 个交易日越界"
|
|
110
|
+
)
|
|
111
|
+
return days[start_pos], days[pos]
|
|
112
|
+
|
|
113
|
+
def resolve_target_as_of(self, decision_time: datetime) -> date:
|
|
114
|
+
"""EXACT 默认目标业务日 = 已完整发布的最新因子日
|
|
115
|
+
|
|
116
|
+
日频因子 as_of = 当日收盘(15:00),最早可见于下一交易日 09:30
|
|
117
|
+
(Q2=A)——因此目标日是「下一交易日开盘 ≤ decision_time」的最近
|
|
118
|
+
交易日(严格早于 decision 当日的上一个交易日);日历无先行日 →
|
|
119
|
+
结构化报错。
|
|
120
|
+
"""
|
|
121
|
+
target = None
|
|
122
|
+
for d in self._trading_days:
|
|
123
|
+
nxt = next_trading_day_open(d, self._trading_days)
|
|
124
|
+
if nxt is not None and nxt <= decision_time:
|
|
125
|
+
target = d
|
|
126
|
+
if target is None:
|
|
127
|
+
raise InsufficientFactorHistoryError(
|
|
128
|
+
f"交易日历中不存在 {decision_time} 前已完整发布的因子日,无法确定目标业务日"
|
|
129
|
+
)
|
|
130
|
+
return target
|
|
131
|
+
|
|
132
|
+
# ------------------------------------------------------------------
|
|
133
|
+
# 截面 / 序列查询
|
|
134
|
+
# ------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
async def get_snapshot(
|
|
137
|
+
self,
|
|
138
|
+
identities: list[FactorIdentity],
|
|
139
|
+
instrument_ids: list[str],
|
|
140
|
+
decision_time: datetime,
|
|
141
|
+
*,
|
|
142
|
+
target_as_of: date | None = None,
|
|
143
|
+
freshness: FactorFreshness = FactorFreshness.EXACT,
|
|
144
|
+
dataset_revision: str | None = None,
|
|
145
|
+
) -> FactorSnapshot:
|
|
146
|
+
"""decision_time 可见截面(R2-5:目标业务日 + 新鲜度策略)
|
|
147
|
+
|
|
148
|
+
- EXACT(默认):只取 target_as_of 当日的值(Store as_of_on 过滤),
|
|
149
|
+
目标日无值不回退旧值;
|
|
150
|
+
- LATEST_KNOWN:显式回退——取可见的最高修订 × 最新 as_of(不早于
|
|
151
|
+
语义由 Store 的可见性谓词保证),stale 由 coverage_report 标注;
|
|
152
|
+
- dataset_revision:pin 旧数据集修订复现旧回测(R2-2)。
|
|
153
|
+
"""
|
|
154
|
+
target = target_as_of or self.resolve_target_as_of(decision_time)
|
|
155
|
+
norm_ids = sorted(set(instrument_ids))
|
|
156
|
+
norm_idents = sorted(set(identities), key=lambda i: i.logical_key)
|
|
157
|
+
rows = await self._query.list_snapshot(
|
|
158
|
+
tuple(norm_idents),
|
|
159
|
+
tuple(norm_ids),
|
|
160
|
+
decision_time,
|
|
161
|
+
dataset_revision=dataset_revision,
|
|
162
|
+
as_of_on=target if freshness is FactorFreshness.EXACT else None,
|
|
163
|
+
)
|
|
164
|
+
return FactorSnapshot(
|
|
165
|
+
values=tuple(rows),
|
|
166
|
+
decision_time=decision_time,
|
|
167
|
+
selection=FactorSnapshotSelection(
|
|
168
|
+
identities=tuple(norm_idents),
|
|
169
|
+
instrument_ids=tuple(norm_ids),
|
|
170
|
+
target_as_of=target,
|
|
171
|
+
freshness=freshness,
|
|
172
|
+
dataset_revision=dataset_revision,
|
|
173
|
+
),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
async def get_series(
|
|
177
|
+
self,
|
|
178
|
+
identity: FactorIdentity,
|
|
179
|
+
instrument_id: str,
|
|
180
|
+
start: datetime,
|
|
181
|
+
end: datetime,
|
|
182
|
+
decision_time: datetime,
|
|
183
|
+
*,
|
|
184
|
+
dataset_revision: str | None = None,
|
|
185
|
+
) -> tuple[FactorValue, ...]:
|
|
186
|
+
"""单因子×单标的 [start, end] 可见历史(start > end 拒绝;R2-2 pin)"""
|
|
187
|
+
if start > end:
|
|
188
|
+
raise InvalidFactorRangeError(f"start {start} 晚于 end {end}")
|
|
189
|
+
return await self._query.list_series(
|
|
190
|
+
identity, instrument_id, start, end, decision_time, dataset_revision=dataset_revision
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------
|
|
194
|
+
# 覆盖报告
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
async def coverage_report(
|
|
198
|
+
self,
|
|
199
|
+
snapshot: FactorSnapshot,
|
|
200
|
+
identities: list[FactorIdentity],
|
|
201
|
+
instrument_ids: list[str],
|
|
202
|
+
decision_time: datetime,
|
|
203
|
+
*,
|
|
204
|
+
target_as_of: date | None = None,
|
|
205
|
+
freshness: FactorFreshness | None = None,
|
|
206
|
+
dataset_revision: str | None = None,
|
|
207
|
+
) -> FactorCoverageReport:
|
|
208
|
+
"""逐格输出封闭枚举覆盖状态(R1-4/R2-5/R2-6/R3 P1-2/P1-4)
|
|
209
|
+
|
|
210
|
+
- 空请求结构化拒绝(R2-6:空 cells 上 all()→True 的假 complete);
|
|
211
|
+
- R3 P1-2:snapshot.decision_time 必须等于 decision_time——报告只
|
|
212
|
+
基于同一时点可见行生成,未来快照不得回传过去决策时点;
|
|
213
|
+
- EXACT:只认目标业务日的可见行——目标日无值一律不 PRESENT
|
|
214
|
+
(历史旧值不回退);LATEST_KNOWN:回退旧值时标 stale + value_as_of;
|
|
215
|
+
- 可见行 AVAILABLE → PRESENT;只有 UNAVAILABLE → UNAVAILABLE;
|
|
216
|
+
缺格进入 Store probe_status 同格探查(R3 P1-4:证据只来自目标
|
|
217
|
+
业务日 + 修订选择的同一批行,不聚合跨日状态):目标日存在
|
|
218
|
+
AVAILABLE/NOT_YET_PUBLISHED 数据行 → NOT_YET_PUBLISHED;目标日
|
|
219
|
+
数据本身就是 MISSING/UNAVAILABLE → 如实保留;目标日无行 → MISSING。
|
|
220
|
+
- 探查结果只进报告,不进 snapshot(不产生决策可见值)。
|
|
221
|
+
- 省略 target_as_of/freshness/dataset_revision 时继承 Snapshot 的冻结
|
|
222
|
+
选择上下文;显式给出非 None 值只能与原选择一致,不能重解释截面。
|
|
223
|
+
"""
|
|
224
|
+
if not identities or not instrument_ids:
|
|
225
|
+
raise ValueError("coverage_report 拒绝空请求(identities/instrument_ids 不得为空)")
|
|
226
|
+
# R3 P1-2:报告绑定同一时点的 Snapshot——未来时点快照回传过去决策时点
|
|
227
|
+
# 会泄漏 PIT 覆盖资格与完整性(Snapshot 自身构造门只约束它自己的
|
|
228
|
+
# decision_time,不约束调用方传入的判定时点)
|
|
229
|
+
if snapshot.decision_time != decision_time:
|
|
230
|
+
raise ValueError(
|
|
231
|
+
f"snapshot.decision_time {snapshot.decision_time} 与报告 "
|
|
232
|
+
f"decision_time {decision_time} 不一致(覆盖报告只接受同一时点的快照)"
|
|
233
|
+
)
|
|
234
|
+
selection = snapshot.selection
|
|
235
|
+
if selection is None:
|
|
236
|
+
raise ValueError("snapshot 缺少查询选择上下文,不能生成覆盖报告")
|
|
237
|
+
if selection.identities != tuple(sorted(set(identities), key=lambda i: i.logical_key)):
|
|
238
|
+
raise ValueError("snapshot identities 与覆盖请求不一致")
|
|
239
|
+
if selection.instrument_ids != tuple(sorted(set(instrument_ids))):
|
|
240
|
+
raise ValueError("snapshot instrument_ids 与覆盖请求不一致")
|
|
241
|
+
if target_as_of is not None and selection.target_as_of != target_as_of:
|
|
242
|
+
raise ValueError("snapshot target_as_of 与覆盖请求不一致")
|
|
243
|
+
if freshness is not None and selection.freshness is not freshness:
|
|
244
|
+
raise ValueError("snapshot freshness 与覆盖请求不一致")
|
|
245
|
+
if dataset_revision is not None and selection.dataset_revision != dataset_revision:
|
|
246
|
+
raise ValueError("snapshot dataset_revision 与覆盖请求不一致")
|
|
247
|
+
target = selection.target_as_of
|
|
248
|
+
freshness = selection.freshness
|
|
249
|
+
dataset_revision = selection.dataset_revision
|
|
250
|
+
want = {(i.logical_key, inst) for i in identities for inst in instrument_ids}
|
|
251
|
+
by_cell: dict[tuple[tuple, str], dict[date, set]] = {}
|
|
252
|
+
for v in snapshot.values:
|
|
253
|
+
key = (v.identity.logical_key, v.instrument_id)
|
|
254
|
+
if key not in want:
|
|
255
|
+
continue
|
|
256
|
+
if freshness is FactorFreshness.EXACT and v.as_of.date() != target:
|
|
257
|
+
continue # EXACT:目标日之外的行不参与判定(不回退旧值)
|
|
258
|
+
by_cell.setdefault(key, {}).setdefault(v.as_of.date(), set()).add(v.status)
|
|
259
|
+
|
|
260
|
+
missing_keys = want - set(by_cell)
|
|
261
|
+
probe: dict[tuple[tuple, str], ProbeCellStatus] = {}
|
|
262
|
+
if missing_keys:
|
|
263
|
+
probe = await self._query.probe_status(
|
|
264
|
+
tuple(sorted({i for i in identities}, key=lambda i: i.logical_key)),
|
|
265
|
+
tuple(sorted(set(instrument_ids))),
|
|
266
|
+
target_as_of=target,
|
|
267
|
+
dataset_revision=dataset_revision,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
def _pick(statuses: set[FactorStatus] | frozenset[FactorStatus]) -> FactorCoverageState:
|
|
271
|
+
if FactorStatus.AVAILABLE in statuses:
|
|
272
|
+
return FactorCoverageState.PRESENT
|
|
273
|
+
if FactorStatus.UNAVAILABLE in statuses:
|
|
274
|
+
return FactorCoverageState.UNAVAILABLE
|
|
275
|
+
return FactorCoverageState.MISSING
|
|
276
|
+
|
|
277
|
+
cells: dict[tuple[tuple, str], CoverageState] = {}
|
|
278
|
+
for ident in identities:
|
|
279
|
+
for inst in instrument_ids:
|
|
280
|
+
key = (ident.logical_key, inst)
|
|
281
|
+
by_day = by_cell.get(key, {})
|
|
282
|
+
if by_day:
|
|
283
|
+
value_day = max(by_day)
|
|
284
|
+
statuses = by_day[value_day]
|
|
285
|
+
cells[key] = CoverageState(
|
|
286
|
+
state=_pick(statuses),
|
|
287
|
+
value_as_of=value_day,
|
|
288
|
+
stale=value_day < target,
|
|
289
|
+
)
|
|
290
|
+
continue
|
|
291
|
+
cell_probe = probe.get(key)
|
|
292
|
+
future = cell_probe.statuses if cell_probe is not None else set()
|
|
293
|
+
# R3 P1-4:probe 证据只来自目标业务日的同一批行——AVAILABLE
|
|
294
|
+
# 且未进快照 = 数据已写入但尚未到可见时间 → NOT_YET_PUBLISHED;
|
|
295
|
+
# 目标日行本身 MISSING/UNAVAILABLE → 如实保留;无行 → MISSING
|
|
296
|
+
published = FactorStatus.AVAILABLE in future or (
|
|
297
|
+
FactorStatus.NOT_YET_PUBLISHED in future
|
|
298
|
+
)
|
|
299
|
+
if future and published:
|
|
300
|
+
cells[key] = CoverageState(FactorCoverageState.NOT_YET_PUBLISHED)
|
|
301
|
+
elif future:
|
|
302
|
+
# Store 中的数据本身就是 MISSING/UNAVAILABLE → 如实保留
|
|
303
|
+
cells[key] = CoverageState(_pick(future))
|
|
304
|
+
else:
|
|
305
|
+
cells[key] = CoverageState(FactorCoverageState.MISSING)
|
|
306
|
+
return FactorCoverageReport(cells=cells, target_as_of=target)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""FactorSelectionRunner —— 历史时点选股编排。
|
|
2
|
+
|
|
3
|
+
纯编排:IFactorQuery.get_snapshot → SelectionService.select。
|
|
4
|
+
不生成 OrderIntent、不触碰 Portfolio/Execution——SelectionResult 由策略
|
|
5
|
+
(或组装层适配器)显式翻译为 Signal。decision_time 即查询上界;
|
|
6
|
+
identities = definition 的加权因子 + filter 因子。
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from datetime import date, datetime
|
|
11
|
+
|
|
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
|
|
15
|
+
from quant_core.app.factor.schema.selection_types import (
|
|
16
|
+
SelectionDefinition,
|
|
17
|
+
SelectionExposure,
|
|
18
|
+
SelectionResult,
|
|
19
|
+
)
|
|
20
|
+
from quant_core.app.factor.service.selection_service import SelectionService
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FactorSelectionRunner:
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
query: IFactorQuery,
|
|
27
|
+
selection: SelectionService | None = None,
|
|
28
|
+
) -> None:
|
|
29
|
+
self._query = query
|
|
30
|
+
self._selection = selection or SelectionService()
|
|
31
|
+
|
|
32
|
+
async def select(
|
|
33
|
+
self,
|
|
34
|
+
definition: SelectionDefinition,
|
|
35
|
+
universe: tuple[str, ...] | list[str],
|
|
36
|
+
decision_time: datetime,
|
|
37
|
+
*,
|
|
38
|
+
target_as_of: date | None = None,
|
|
39
|
+
freshness: FactorFreshness = FactorFreshness.EXACT,
|
|
40
|
+
dataset_revision: str | None = None,
|
|
41
|
+
exposures: dict[str, SelectionExposure] | None = None,
|
|
42
|
+
) -> SelectionResult:
|
|
43
|
+
return (
|
|
44
|
+
await self.run(
|
|
45
|
+
definition,
|
|
46
|
+
universe,
|
|
47
|
+
decision_time,
|
|
48
|
+
target_as_of=target_as_of,
|
|
49
|
+
freshness=freshness,
|
|
50
|
+
dataset_revision=dataset_revision,
|
|
51
|
+
exposures=exposures,
|
|
52
|
+
)
|
|
53
|
+
).result
|
|
54
|
+
|
|
55
|
+
async def run(
|
|
56
|
+
self,
|
|
57
|
+
definition: SelectionDefinition,
|
|
58
|
+
universe: tuple[str, ...] | list[str],
|
|
59
|
+
decision_time: datetime,
|
|
60
|
+
*,
|
|
61
|
+
target_as_of: date | None = None,
|
|
62
|
+
freshness: FactorFreshness = FactorFreshness.EXACT,
|
|
63
|
+
dataset_revision: str | None = None,
|
|
64
|
+
exposures: dict[str, SelectionExposure] | None = None,
|
|
65
|
+
) -> "FactorSelectionRun":
|
|
66
|
+
identities: list[FactorIdentity] = list(
|
|
67
|
+
dict.fromkeys(
|
|
68
|
+
[f.identity for f in definition.factors] + [f.identity for f in definition.filters]
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
snapshot = await self._query.get_snapshot(
|
|
72
|
+
identities,
|
|
73
|
+
list(universe),
|
|
74
|
+
decision_time,
|
|
75
|
+
target_as_of=target_as_of,
|
|
76
|
+
freshness=freshness,
|
|
77
|
+
dataset_revision=dataset_revision,
|
|
78
|
+
)
|
|
79
|
+
return FactorSelectionRun(
|
|
80
|
+
snapshot=snapshot,
|
|
81
|
+
result=self._selection.select(definition, universe, snapshot, exposures),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True)
|
|
86
|
+
class FactorSelectionRun:
|
|
87
|
+
snapshot: FactorSnapshot
|
|
88
|
+
result: SelectionResult
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""已下载发布定义复用既有表达式/窗口/面板核心,不联网、不访问管理库。"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
from quant_core.app.data.schema.adjustment_schema import AdjustmentType
|
|
6
|
+
from quant_core.app.data.schema.market_schema import BarResponse
|
|
7
|
+
from quant_core.app.factor.computation.factor_batch_evaluator import FactorBatchResult
|
|
8
|
+
from quant_core.app.factor.computation.python_factor_executor import (
|
|
9
|
+
execute_python_panel,
|
|
10
|
+
execute_python_windows,
|
|
11
|
+
)
|
|
12
|
+
from quant_core.app.factor.interface.factor_local_data import FactorLocalData
|
|
13
|
+
from quant_core.app.factor.schema.factor_input_schema import FactorInputRequest
|
|
14
|
+
from quant_core.app.factor.schema.local_factor_schema import LocalFactorRequest
|
|
15
|
+
from quant_core.app.factor.schema.panel_policy import PANEL_UNITS
|
|
16
|
+
from quant_core.app.factor.service.factor_input_service import prepare_research_panel
|
|
17
|
+
from quant_core.app.factor.service.factor_local_execution_service import execute_local_factors
|
|
18
|
+
from quant_core.common.local_run import content_digest
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def execute_published_factors(
|
|
22
|
+
request: LocalFactorRequest, data: FactorLocalData
|
|
23
|
+
) -> FactorBatchResult:
|
|
24
|
+
"""静态定义在本地显式执行;记录完整输入证据并保留空值网格。"""
|
|
25
|
+
from quant_core.app.factor.computation.factor_source_loader import load_formulas
|
|
26
|
+
|
|
27
|
+
values, input_digests = {}, []
|
|
28
|
+
for publication in request.definitions:
|
|
29
|
+
spec = publication.definition.computation_spec
|
|
30
|
+
assert spec is not None
|
|
31
|
+
if spec.frequency.value != "1d":
|
|
32
|
+
raise ValueError("本地发布定义执行当前仅支持日线")
|
|
33
|
+
if spec.input_kind == "expression":
|
|
34
|
+
expressions = load_formulas(spec.python_source, spec.parameters)
|
|
35
|
+
if set(expressions) != {spec.name}:
|
|
36
|
+
raise ValueError("已发布表达式必须仅输出定义名称对应的因子")
|
|
37
|
+
expression = expressions[spec.name]
|
|
38
|
+
if expression.lookback > spec.lookback or set(expression.required_fields) - set(
|
|
39
|
+
spec.inputs
|
|
40
|
+
):
|
|
41
|
+
raise ValueError("公式实际窗口/字段超出发布定义声明")
|
|
42
|
+
result = await execute_local_factors(
|
|
43
|
+
expressions,
|
|
44
|
+
data,
|
|
45
|
+
instrument_ids=request.instrument_ids,
|
|
46
|
+
start=request.start,
|
|
47
|
+
end=request.end,
|
|
48
|
+
view=request.view,
|
|
49
|
+
exclude_st=request.exclude_st,
|
|
50
|
+
)
|
|
51
|
+
values.update(result.values)
|
|
52
|
+
input_digests.append(result.input_digest)
|
|
53
|
+
continue
|
|
54
|
+
fields = tuple(name for name in spec.inputs if name in PANEL_UNITS)
|
|
55
|
+
if spec.input_kind == "window":
|
|
56
|
+
fields = tuple(name for name in PANEL_UNITS if name != "vwap")
|
|
57
|
+
if not fields or any(name not in PANEL_UNITS and name != "bar/1d" for name in spec.inputs):
|
|
58
|
+
raise ValueError("发布定义需要当前快照不支持的数据字段")
|
|
59
|
+
panel = await prepare_research_panel(
|
|
60
|
+
data,
|
|
61
|
+
FactorInputRequest(
|
|
62
|
+
instrument_ids=request.instrument_ids,
|
|
63
|
+
start=request.start,
|
|
64
|
+
end=request.end,
|
|
65
|
+
fields=fields,
|
|
66
|
+
lookback=spec.lookback,
|
|
67
|
+
view=request.view,
|
|
68
|
+
exclude_st=request.exclude_st,
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
contract = spec.parameters.get("input_contract")
|
|
72
|
+
if contract is not None and contract != {
|
|
73
|
+
"universe_ref": panel.universe_ref,
|
|
74
|
+
"price_basis": panel.price_basis,
|
|
75
|
+
"units": panel.units,
|
|
76
|
+
}:
|
|
77
|
+
raise ValueError("面板股票池、价格口径或单位与发布定义不匹配")
|
|
78
|
+
input_digests.append(panel.input_digest)
|
|
79
|
+
if spec.input_kind == "panel":
|
|
80
|
+
computed = execute_python_panel(spec, panel)
|
|
81
|
+
if any(
|
|
82
|
+
not row.eligible and computed[(row.as_of, row.instrument_id)] is not None
|
|
83
|
+
for row in panel.rows
|
|
84
|
+
):
|
|
85
|
+
raise ValueError("面板因子不能为不参与计算的标的返回非空值")
|
|
86
|
+
else:
|
|
87
|
+
computed = {}
|
|
88
|
+
for instrument in request.instrument_ids:
|
|
89
|
+
rows = sorted(
|
|
90
|
+
(row for row in panel.rows if row.instrument_id == instrument),
|
|
91
|
+
key=lambda row: row.as_of,
|
|
92
|
+
)
|
|
93
|
+
bars = await data.list_bars(
|
|
94
|
+
instrument, "1d", rows[0].as_of, rows[-1].as_of, len(rows) + 1
|
|
95
|
+
)
|
|
96
|
+
if request.view.adjustment_type is not AdjustmentType.NONE:
|
|
97
|
+
bars = (
|
|
98
|
+
await data.get_adjusted_bars(
|
|
99
|
+
instrument,
|
|
100
|
+
"1d",
|
|
101
|
+
request.view,
|
|
102
|
+
rows[0].as_of,
|
|
103
|
+
rows[-1].as_of,
|
|
104
|
+
len(rows) + 1,
|
|
105
|
+
)
|
|
106
|
+
).bars
|
|
107
|
+
previous_closes = {bar.timestamp: bar.previous_close for bar in bars}
|
|
108
|
+
input_digests.append(
|
|
109
|
+
content_digest(
|
|
110
|
+
json.dumps(
|
|
111
|
+
[
|
|
112
|
+
(ts.isoformat(), str(value) if value is not None else None)
|
|
113
|
+
for ts, value in sorted(previous_closes.items())
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
calls, keys = [], []
|
|
119
|
+
for index, row in enumerate(rows):
|
|
120
|
+
window = rows[max(0, index - spec.lookback + 1) : index + 1]
|
|
121
|
+
key = (row.as_of, instrument)
|
|
122
|
+
if len(window) < spec.lookback or any(
|
|
123
|
+
not item.eligible
|
|
124
|
+
or any(
|
|
125
|
+
item.fields[name] is None
|
|
126
|
+
for name in ("open", "high", "low", "close", "volume")
|
|
127
|
+
)
|
|
128
|
+
for item in window
|
|
129
|
+
):
|
|
130
|
+
computed[key] = None
|
|
131
|
+
continue
|
|
132
|
+
window_bars = [
|
|
133
|
+
BarResponse.model_validate(
|
|
134
|
+
{
|
|
135
|
+
"instrument_id": instrument,
|
|
136
|
+
"timestamp": item.as_of,
|
|
137
|
+
"frequency": "1d",
|
|
138
|
+
"suspended": False,
|
|
139
|
+
"previous_close": previous_closes.get(item.as_of),
|
|
140
|
+
**item.fields,
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
for item in window
|
|
144
|
+
]
|
|
145
|
+
keys.append(key)
|
|
146
|
+
calls.append((spec.python_source, window_bars, spec.parameters))
|
|
147
|
+
computed.update(zip(keys, execute_python_windows(calls), strict=True))
|
|
148
|
+
values[spec.name] = {
|
|
149
|
+
(ts, instrument): value
|
|
150
|
+
for (ts, instrument), value in computed.items()
|
|
151
|
+
if request.start <= ts.date() <= request.end
|
|
152
|
+
}
|
|
153
|
+
return FactorBatchResult(
|
|
154
|
+
values=values,
|
|
155
|
+
input_digest=content_digest(json.dumps(input_digests)),
|
|
156
|
+
definition_digest=content_digest(
|
|
157
|
+
json.dumps([item.content_digest for item in request.definitions])
|
|
158
|
+
),
|
|
159
|
+
lookback=max(item.definition.lookback for item in request.definitions),
|
|
160
|
+
)
|