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,258 @@
|
|
|
1
|
+
"""由可信本地事实构造交易日网格;复权、缺口与参与范围均进入输入证据。"""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from bisect import bisect_left
|
|
6
|
+
from datetime import date, datetime, time
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from zoneinfo import ZoneInfo
|
|
9
|
+
|
|
10
|
+
from quant_core.app.data.interface.market_data_query import IMarketDataQuery
|
|
11
|
+
from quant_core.app.data.schema.adjustment_schema import (
|
|
12
|
+
AdjustedBarResponse,
|
|
13
|
+
AdjustmentAnchorMode,
|
|
14
|
+
AdjustmentType,
|
|
15
|
+
)
|
|
16
|
+
from quant_core.app.data.schema.market_schema import BarResponse
|
|
17
|
+
from quant_core.app.data.service.calendar_coverage_service import build_calendar_coverage
|
|
18
|
+
from quant_core.app.factor.interface.factor_local_data import FactorLocalData
|
|
19
|
+
from quant_core.app.factor.schema.factor_input_schema import FactorInputRequest
|
|
20
|
+
from quant_core.app.factor.schema.factor_panel import FactorPanel, FactorPanelRow
|
|
21
|
+
from quant_core.app.factor.schema.panel_policy import PANEL_UNITS, PRICE_BASIS, listed_roster_ref
|
|
22
|
+
|
|
23
|
+
_CN = ZoneInfo("Asia/Shanghai")
|
|
24
|
+
_EXCHANGES = {"XSHG", "XSHE", "BJSE"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def prepare_research_panel(data: FactorLocalData, request: FactorInputRequest) -> FactorPanel:
|
|
28
|
+
"""返回包含预热日的矩形面板;上市前、退市后及停牌不参与横截面。"""
|
|
29
|
+
if set(request.fields) - PANEL_UNITS.keys():
|
|
30
|
+
raise ValueError("当前输入准备仅支持量价字段")
|
|
31
|
+
lower = data.start.astimezone(_CN).date()
|
|
32
|
+
upper = data.end.astimezone(_CN).date()
|
|
33
|
+
if request.start < lower or request.end > upper:
|
|
34
|
+
raise ValueError("输出范围超出本地快照,请先准备所需数据")
|
|
35
|
+
instruments = [await data.get_instrument(key) for key in sorted(request.instrument_ids)]
|
|
36
|
+
if any(row.listed_date is None for row in instruments):
|
|
37
|
+
raise ValueError("参与池缺少可信上市日期,不能推断历史成员")
|
|
38
|
+
exchanges = {row.exchange for row in instruments}
|
|
39
|
+
if not exchanges.issubset(_EXCHANGES):
|
|
40
|
+
raise ValueError("首批日线准备仅支持 A 股交易所")
|
|
41
|
+
calendars = []
|
|
42
|
+
for exchange in sorted(exchanges):
|
|
43
|
+
rows = await data.list_calendar_days(exchange, lower, request.end)
|
|
44
|
+
if any(
|
|
45
|
+
row.exchange != exchange or not lower <= row.trade_date <= request.end for row in rows
|
|
46
|
+
):
|
|
47
|
+
raise ValueError("交易日历返回了不属于请求范围的事实")
|
|
48
|
+
if len({row.trade_date for row in rows}) != len(rows):
|
|
49
|
+
raise ValueError("交易日历包含重复日期")
|
|
50
|
+
coverage = build_calendar_coverage(exchange, lower, request.end, rows)
|
|
51
|
+
if coverage.missing_days:
|
|
52
|
+
raise ValueError("本地交易日历不完整,不能推断预热范围")
|
|
53
|
+
calendars.append(coverage.open_days)
|
|
54
|
+
if any(days != calendars[0] for days in calendars[1:]):
|
|
55
|
+
raise ValueError("参与交易所的交易日轴不同,需要独立会话准备")
|
|
56
|
+
days = calendars[0]
|
|
57
|
+
first = bisect_left(days, request.start)
|
|
58
|
+
if first >= len(days):
|
|
59
|
+
raise ValueError("请求区间没有交易日")
|
|
60
|
+
warmup = first - request.lookback + 1
|
|
61
|
+
if warmup < 0:
|
|
62
|
+
raise ValueError(f"本地快照缺少预热交易日,需要至少 {request.lookback} 期")
|
|
63
|
+
calendar = tuple(datetime.combine(day, time(15), _CN) for day in days[warmup:])
|
|
64
|
+
if calendar[0] < data.start or calendar[-1] > data.end:
|
|
65
|
+
raise ValueError("快照未覆盖完整交易日收盘时间")
|
|
66
|
+
view = request.view
|
|
67
|
+
if view.known_at is not None:
|
|
68
|
+
raise ValueError("当前行情快照不支持历史知识时间查询")
|
|
69
|
+
if view.adjustment_type is AdjustmentType.PRE:
|
|
70
|
+
if view.anchor_mode is not AdjustmentAnchorMode.FIXED_DATE:
|
|
71
|
+
raise ValueError("批量面板前复权必须显式使用固定日期锚点")
|
|
72
|
+
if view.anchor_date is None or not lower <= view.anchor_date <= upper:
|
|
73
|
+
raise ValueError("复权锚点超出本地事实范围")
|
|
74
|
+
rows = []
|
|
75
|
+
revisions = []
|
|
76
|
+
membership = []
|
|
77
|
+
for instrument in instruments:
|
|
78
|
+
assert instrument.listed_date is not None
|
|
79
|
+
raw = await data.list_bars(
|
|
80
|
+
instrument.instrument_id, "1d", calendar[0], calendar[-1], len(calendar) + 1
|
|
81
|
+
)
|
|
82
|
+
if len(raw) > len(calendar):
|
|
83
|
+
raise ValueError("本地日线包含重复或额外时间点")
|
|
84
|
+
values = raw
|
|
85
|
+
if raw and view.adjustment_type is not AdjustmentType.NONE:
|
|
86
|
+
adjusted = await data.get_adjusted_bars(
|
|
87
|
+
instrument.instrument_id,
|
|
88
|
+
"1d",
|
|
89
|
+
view,
|
|
90
|
+
calendar[0],
|
|
91
|
+
calendar[-1],
|
|
92
|
+
len(calendar) + 1,
|
|
93
|
+
)
|
|
94
|
+
values = adjusted.bars
|
|
95
|
+
revisions.append(
|
|
96
|
+
(instrument.instrument_id, adjusted.factor_revision, str(adjusted.anchor_date))
|
|
97
|
+
)
|
|
98
|
+
by_time = {bar.timestamp: bar for bar in values}
|
|
99
|
+
if any(
|
|
100
|
+
bar.instrument_id != instrument.instrument_id or bar.frequency != "1d" for bar in values
|
|
101
|
+
):
|
|
102
|
+
raise ValueError("行情包含其他证券或频率")
|
|
103
|
+
if len(by_time) != len(values) or set(by_time) - set(calendar):
|
|
104
|
+
raise ValueError("日线时间与交易日历不一致")
|
|
105
|
+
membership.append(
|
|
106
|
+
(
|
|
107
|
+
instrument.instrument_id,
|
|
108
|
+
str(instrument.listed_date),
|
|
109
|
+
str(instrument.delisted_date),
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
status = {
|
|
113
|
+
f.trade_date: f
|
|
114
|
+
for f in await data.list_daily_facts(
|
|
115
|
+
instrument.instrument_id, calendar[0].date(), calendar[-1].date()
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
for timestamp in calendar:
|
|
119
|
+
listed = instrument.listed_date <= timestamp.date() and (
|
|
120
|
+
instrument.delisted_date is None or timestamp.date() <= instrument.delisted_date
|
|
121
|
+
)
|
|
122
|
+
bar = by_time.get(timestamp)
|
|
123
|
+
fact = status.get(timestamp.date())
|
|
124
|
+
full_day_halt = fact is not None and fact.suspension_status == "full_day"
|
|
125
|
+
if listed and bar is None and not full_day_halt and request.missing_policy == "error":
|
|
126
|
+
raise ValueError(f"行情缺口: {instrument.instrument_id} {timestamp.date()}")
|
|
127
|
+
eligible = listed and bar is not None and not bar.suspended and not full_day_halt
|
|
128
|
+
if request.exclude_st and listed:
|
|
129
|
+
fact = status.get(timestamp.date())
|
|
130
|
+
if fact is None or fact.special_type is None:
|
|
131
|
+
raise ValueError(
|
|
132
|
+
f"历史 ST 状态未知: {instrument.instrument_id} {timestamp.date()}"
|
|
133
|
+
)
|
|
134
|
+
eligible = eligible and fact.special_type == "Normal"
|
|
135
|
+
fields: dict[str, Decimal | None] = dict.fromkeys(request.fields)
|
|
136
|
+
if eligible:
|
|
137
|
+
assert bar is not None
|
|
138
|
+
fields = _bar_fields(bar, request.fields)
|
|
139
|
+
if (
|
|
140
|
+
eligible
|
|
141
|
+
and request.missing_policy == "error"
|
|
142
|
+
and any(value is None for value in fields.values())
|
|
143
|
+
):
|
|
144
|
+
raise ValueError(f"输入字段缺失: {instrument.instrument_id} {timestamp.date()}")
|
|
145
|
+
rows.append(
|
|
146
|
+
FactorPanelRow(
|
|
147
|
+
instrument_id=instrument.instrument_id,
|
|
148
|
+
as_of=timestamp,
|
|
149
|
+
available_at=data.end,
|
|
150
|
+
eligible=eligible,
|
|
151
|
+
fields=fields,
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
return FactorPanel(
|
|
155
|
+
decision_time=data.end,
|
|
156
|
+
price_basis=view.model_dump_json(),
|
|
157
|
+
universe_ref=listed_roster_ref(request.instrument_ids),
|
|
158
|
+
universe_revision=hashlib.sha256(json.dumps(membership).encode()).hexdigest(),
|
|
159
|
+
market_revision=hashlib.sha256(json.dumps([data.revision, revisions]).encode()).hexdigest(),
|
|
160
|
+
units={field: PANEL_UNITS[field] for field in request.fields},
|
|
161
|
+
rows=tuple(rows),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def prepare_decision_panel(
|
|
166
|
+
market: IMarketDataQuery,
|
|
167
|
+
instrument_ids: tuple[str, ...],
|
|
168
|
+
day: date,
|
|
169
|
+
next_day: date,
|
|
170
|
+
lookback: int,
|
|
171
|
+
) -> FactorPanel:
|
|
172
|
+
"""只做输入准备,不计算因子或发布结果;未知上市时间拒绝猜测。"""
|
|
173
|
+
if next_day <= day or lookback < 1 or not instrument_ids:
|
|
174
|
+
raise ValueError("面板窗口参数非法")
|
|
175
|
+
if len(set(instrument_ids)) != len(instrument_ids):
|
|
176
|
+
raise ValueError("面板候选证券不得重复")
|
|
177
|
+
decision_time = datetime.combine(next_day, time(9, 30), tzinfo=_CN)
|
|
178
|
+
as_of = datetime.combine(day, time(15), tzinfo=_CN)
|
|
179
|
+
rows: list[FactorPanelRow] = []
|
|
180
|
+
market_evidence = []
|
|
181
|
+
universe_evidence = []
|
|
182
|
+
for instrument_id in sorted(instrument_ids):
|
|
183
|
+
instrument = await market.get_instrument(instrument_id)
|
|
184
|
+
if instrument.listed_date is None:
|
|
185
|
+
raise ValueError(f"缺少可信上市日期: {instrument_id}")
|
|
186
|
+
universe_evidence.append(
|
|
187
|
+
(instrument_id, str(instrument.listed_date), str(instrument.delisted_date))
|
|
188
|
+
)
|
|
189
|
+
if day < instrument.listed_date:
|
|
190
|
+
rows.append(
|
|
191
|
+
FactorPanelRow(
|
|
192
|
+
instrument_id=instrument_id,
|
|
193
|
+
as_of=as_of,
|
|
194
|
+
available_at=decision_time,
|
|
195
|
+
eligible=False,
|
|
196
|
+
fields=dict.fromkeys(PANEL_UNITS),
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
market_evidence.append((instrument_id, "before_listing"))
|
|
200
|
+
continue
|
|
201
|
+
windows = await market.get_pit_daily_windows(instrument_id, [day], [next_day], lookback)
|
|
202
|
+
if len(windows) != 1 or windows[0].as_of != day or windows[0].anchor_date != next_day:
|
|
203
|
+
raise ValueError("Market 返回的面板窗口与请求不一致")
|
|
204
|
+
window = windows[0]
|
|
205
|
+
market_evidence.append((instrument_id, window.input_digest))
|
|
206
|
+
for bar in window.bars:
|
|
207
|
+
timestamp = bar.timestamp.astimezone(_CN)
|
|
208
|
+
if timestamp.date() > day or bar.instrument_id != instrument_id:
|
|
209
|
+
raise ValueError("Market 返回越界行情")
|
|
210
|
+
eligible = instrument.listed_date <= timestamp.date() and (
|
|
211
|
+
instrument.delisted_date is None or timestamp.date() <= instrument.delisted_date
|
|
212
|
+
)
|
|
213
|
+
fields = _bar_fields(bar, tuple(PANEL_UNITS))
|
|
214
|
+
rows.append(
|
|
215
|
+
FactorPanelRow(
|
|
216
|
+
instrument_id=instrument_id,
|
|
217
|
+
as_of=datetime.combine(timestamp.date(), time(15), tzinfo=_CN),
|
|
218
|
+
# 使用本次窗口的可见时点,避免将决策日复权锚点伪装成历史已知。
|
|
219
|
+
available_at=decision_time,
|
|
220
|
+
eligible=eligible,
|
|
221
|
+
fields=fields,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
if not window.bars or window.bars[-1].timestamp.astimezone(_CN).date() != day:
|
|
225
|
+
rows.append(
|
|
226
|
+
FactorPanelRow(
|
|
227
|
+
instrument_id=instrument_id,
|
|
228
|
+
as_of=as_of,
|
|
229
|
+
available_at=decision_time,
|
|
230
|
+
eligible=False,
|
|
231
|
+
fields=dict.fromkeys(PANEL_UNITS),
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
return FactorPanel(
|
|
235
|
+
decision_time=decision_time,
|
|
236
|
+
price_basis=PRICE_BASIS,
|
|
237
|
+
universe_ref=listed_roster_ref(instrument_ids),
|
|
238
|
+
universe_revision=hashlib.sha256(json.dumps(universe_evidence).encode()).hexdigest(),
|
|
239
|
+
market_revision=hashlib.sha256(json.dumps(market_evidence).encode()).hexdigest(),
|
|
240
|
+
units=PANEL_UNITS,
|
|
241
|
+
rows=tuple(rows),
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _bar_fields(
|
|
246
|
+
bar: BarResponse | AdjustedBarResponse, names: tuple[str, ...]
|
|
247
|
+
) -> dict[str, Decimal | None]:
|
|
248
|
+
"""统一价格字段与 VWAP 转换;是否参与计算由各时点政策决定。"""
|
|
249
|
+
fields: dict[str, Decimal | None] = {}
|
|
250
|
+
for name in names:
|
|
251
|
+
if name == "vwap":
|
|
252
|
+
fields[name] = (
|
|
253
|
+
bar.amount / bar.volume if bar.amount is not None and bar.volume > 0 else None
|
|
254
|
+
)
|
|
255
|
+
else:
|
|
256
|
+
value = getattr(bar, name)
|
|
257
|
+
fields[name] = Decimal(value) if value is not None else None
|
|
258
|
+
return fields
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""PQFactor 本地入口:显式快照、交易时间轴与历史参与池,计算不联网。"""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime, time
|
|
4
|
+
from zoneinfo import ZoneInfo
|
|
5
|
+
|
|
6
|
+
from quant_core.app.data.schema.adjustment_schema import BarViewRequest
|
|
7
|
+
from quant_core.app.factor.computation.factor_batch_evaluator import (
|
|
8
|
+
FactorBatchResult,
|
|
9
|
+
execute_factors,
|
|
10
|
+
)
|
|
11
|
+
from quant_core.app.factor.computation.factor_expression import FactorExpression
|
|
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.service.factor_input_service import prepare_research_panel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def execute_local_factors(
|
|
18
|
+
factors: dict[str, FactorExpression],
|
|
19
|
+
data: FactorLocalData,
|
|
20
|
+
*,
|
|
21
|
+
instrument_ids: tuple[str, ...],
|
|
22
|
+
start: date,
|
|
23
|
+
end: date,
|
|
24
|
+
view: BarViewRequest,
|
|
25
|
+
exclude_st: bool = False,
|
|
26
|
+
output_instruments: tuple[str, ...] | None = None,
|
|
27
|
+
) -> FactorBatchResult:
|
|
28
|
+
"""从已加载快照自动准备公式输入;复用 data 可避免重复读盘,不联网。"""
|
|
29
|
+
if not factors:
|
|
30
|
+
raise ValueError("必须提供至少一个公式")
|
|
31
|
+
fields = sorted(set().union(*(factor.required_fields for factor in factors.values())))
|
|
32
|
+
if not fields:
|
|
33
|
+
raise ValueError("公式必须引用至少一个输入字段")
|
|
34
|
+
request = FactorInputRequest(
|
|
35
|
+
instrument_ids=instrument_ids,
|
|
36
|
+
start=start,
|
|
37
|
+
end=end,
|
|
38
|
+
fields=tuple(fields),
|
|
39
|
+
lookback=max(factor.lookback for factor in factors.values()),
|
|
40
|
+
view=view,
|
|
41
|
+
exclude_st=exclude_st,
|
|
42
|
+
)
|
|
43
|
+
panel = await prepare_research_panel(data, request)
|
|
44
|
+
cn = ZoneInfo("Asia/Shanghai")
|
|
45
|
+
return execute_factors(
|
|
46
|
+
factors,
|
|
47
|
+
panel,
|
|
48
|
+
start=datetime.combine(start, time.min, cn),
|
|
49
|
+
end=datetime.combine(end, time.max, cn),
|
|
50
|
+
output_instruments=output_instruments,
|
|
51
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""面板试算与 Canonical 输出装配;不隐式发布任何研究试算。"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from collections import Counter
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
from quant_core.app.factor.constants import LOCAL_FACTOR_SOURCE, FactorSourceKind, FactorStatus
|
|
8
|
+
from quant_core.app.factor.interface.factor_execution import PanelExecutor
|
|
9
|
+
from quant_core.app.factor.schema.factor_contract import (
|
|
10
|
+
FactorDefinition,
|
|
11
|
+
FactorProvenance,
|
|
12
|
+
FactorValue,
|
|
13
|
+
)
|
|
14
|
+
from quant_core.app.factor.schema.factor_panel import FactorPanel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FactorPanelService:
|
|
18
|
+
"""依赖计算接口,发布与持久化交给现有 Factor 修订流程。"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, runner: PanelExecutor) -> None:
|
|
21
|
+
self._runner = runner
|
|
22
|
+
|
|
23
|
+
async def compute(
|
|
24
|
+
self,
|
|
25
|
+
definitions: list[FactorDefinition],
|
|
26
|
+
panel: FactorPanel,
|
|
27
|
+
*,
|
|
28
|
+
as_of: datetime,
|
|
29
|
+
dataset_revision: str,
|
|
30
|
+
) -> list[FactorValue]:
|
|
31
|
+
"""只输出目标业务时点;所有输入在此次决策时间已经合法可见。"""
|
|
32
|
+
panel = FactorPanel.model_validate(panel.model_dump())
|
|
33
|
+
if as_of.utcoffset() is None or as_of > panel.decision_time:
|
|
34
|
+
raise ValueError("面板输出时点非法")
|
|
35
|
+
if any(row.as_of > as_of for row in panel.rows):
|
|
36
|
+
raise ValueError("面板不得包含输出业务时点之后的输入")
|
|
37
|
+
if len({item.identity for item in definitions}) != len(definitions):
|
|
38
|
+
raise ValueError("重复完整因子身份")
|
|
39
|
+
output_rows = tuple(row for row in panel.rows if row.as_of == as_of)
|
|
40
|
+
history_counts = Counter(row.instrument_id for row in panel.rows)
|
|
41
|
+
input_digest = panel.input_digest
|
|
42
|
+
result = []
|
|
43
|
+
for definition in definitions:
|
|
44
|
+
spec = definition.computation_spec
|
|
45
|
+
if (
|
|
46
|
+
spec is None
|
|
47
|
+
or spec.input_kind != "panel"
|
|
48
|
+
or definition.identity.source != LOCAL_FACTOR_SOURCE
|
|
49
|
+
):
|
|
50
|
+
raise ValueError("必须使用本地面板定义")
|
|
51
|
+
contract = spec.parameters.get("input_contract")
|
|
52
|
+
actual = {
|
|
53
|
+
"universe_ref": panel.universe_ref,
|
|
54
|
+
"price_basis": panel.price_basis,
|
|
55
|
+
"units": panel.units,
|
|
56
|
+
}
|
|
57
|
+
if contract != actual:
|
|
58
|
+
raise ValueError("面板股票池政策、单位或价格口径与定义不一致")
|
|
59
|
+
values = await asyncio.to_thread(self._runner, spec, panel)
|
|
60
|
+
for row in output_rows:
|
|
61
|
+
sufficient = history_counts[row.instrument_id] >= spec.lookback
|
|
62
|
+
value = (
|
|
63
|
+
values[(row.as_of, row.instrument_id)] if row.eligible and sufficient else None
|
|
64
|
+
)
|
|
65
|
+
result.append(
|
|
66
|
+
FactorValue(
|
|
67
|
+
identity=definition.identity,
|
|
68
|
+
instrument_id=row.instrument_id,
|
|
69
|
+
as_of=as_of,
|
|
70
|
+
available_at=panel.decision_time,
|
|
71
|
+
value=value,
|
|
72
|
+
status=FactorStatus.AVAILABLE
|
|
73
|
+
if value is not None
|
|
74
|
+
else FactorStatus.MISSING,
|
|
75
|
+
provenance=FactorProvenance(
|
|
76
|
+
source_kind=FactorSourceKind.LOCAL_CALCULATOR,
|
|
77
|
+
provider_id=LOCAL_FACTOR_SOURCE,
|
|
78
|
+
retrieval_ref="python:compute_panel",
|
|
79
|
+
content_digest=spec.content_digest,
|
|
80
|
+
dataset_revision=dataset_revision,
|
|
81
|
+
provider_revision=panel.market_revision,
|
|
82
|
+
note=f"input_sha256={input_digest};universe_revision={panel.universe_revision}",
|
|
83
|
+
),
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
return result
|