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,100 @@
|
|
|
1
|
+
"""内置特征目录:配方只是候选规格,注册及入库必须经过显式发布。"""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from functools import lru_cache
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from quant_core.app.factor.computation.panel_evaluator import evaluate_panel
|
|
9
|
+
from quant_core.app.factor.schema.factor_computation_schema import FactorComputationSpec
|
|
10
|
+
from quant_core.app.factor.schema.factor_panel import FactorPanel
|
|
11
|
+
from quant_core.app.factor.schema.factor_result_schema import PanelValues
|
|
12
|
+
|
|
13
|
+
from .alpha101_recipe import ALPHA101_RECIPES
|
|
14
|
+
from .alpha101_recipe import UNSUPPORTED_ALPHA101 as UNSUPPORTED_ALPHA101
|
|
15
|
+
from .alpha158_recipe import ALPHA158_RECIPES
|
|
16
|
+
|
|
17
|
+
REFERENCE_COMMIT = "fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09"
|
|
18
|
+
RECIPES = {**ALPHA158_RECIPES, **ALPHA101_RECIPES}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_recipe(name: str, panel: FactorPanel, expected_digest: str) -> PanelValues:
|
|
22
|
+
"""按发布定义的实现摘要执行内置配方。"""
|
|
23
|
+
if expected_digest != implementation_digest():
|
|
24
|
+
raise ValueError("内置计算实现与发布定义摘要不一致,必须使用对应版本代码")
|
|
25
|
+
recipe, lookback, required = RECIPES[name]
|
|
26
|
+
return evaluate_panel(panel, recipe, lookback, required)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lru_cache(maxsize=1)
|
|
30
|
+
def implementation_digest() -> str:
|
|
31
|
+
"""固定算子和配方文件摘要,防止同一发布定义随代码升级静默漂移。"""
|
|
32
|
+
digest = hashlib.sha256()
|
|
33
|
+
for name in (
|
|
34
|
+
"computation/panel_expression.py",
|
|
35
|
+
"computation/universe_filter.py",
|
|
36
|
+
"computation/panel_evaluator.py",
|
|
37
|
+
"computation/python_factor_executor.py",
|
|
38
|
+
"computation/operators/time_series_operator.py",
|
|
39
|
+
"computation/operators/cross_section_operator.py",
|
|
40
|
+
"computation/operators/arithmetic_operator.py",
|
|
41
|
+
"builtin/alpha158_recipe.py",
|
|
42
|
+
"builtin/alpha101_recipe.py",
|
|
43
|
+
"builtin/factor_recipe_catalog.py",
|
|
44
|
+
):
|
|
45
|
+
digest.update(name.encode())
|
|
46
|
+
digest.update(
|
|
47
|
+
(Path(__file__).parent.parent / name)
|
|
48
|
+
.read_text(encoding="utf-8")
|
|
49
|
+
.replace("\r\n", "\n")
|
|
50
|
+
.encode()
|
|
51
|
+
)
|
|
52
|
+
return digest.hexdigest()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def recipe_specs(
|
|
56
|
+
family: str,
|
|
57
|
+
*,
|
|
58
|
+
universe_ref: str,
|
|
59
|
+
price_basis: str,
|
|
60
|
+
units: dict[str, str],
|
|
61
|
+
instrument_ids: tuple[str, ...] = (),
|
|
62
|
+
) -> list[FactorComputationSpec]:
|
|
63
|
+
"""将股票池政策、单位与价格口径绑定到不可变定义版本。"""
|
|
64
|
+
if family not in {"alpha158", "alpha101"} or not universe_ref or not price_basis:
|
|
65
|
+
raise ValueError("必须指定支持的特征集、历史股票池政策及价格口径")
|
|
66
|
+
contract = {"universe_ref": universe_ref, "price_basis": price_basis, "units": units}
|
|
67
|
+
implementation = implementation_digest()
|
|
68
|
+
version_payload = {"input_contract": contract, "implementation_digest": implementation}
|
|
69
|
+
digest = hashlib.sha256(json.dumps(version_payload, sort_keys=True).encode()).hexdigest()
|
|
70
|
+
specs = []
|
|
71
|
+
for name, (_, lookback, fields) in RECIPES.items():
|
|
72
|
+
if not name.startswith(family + "_"):
|
|
73
|
+
continue
|
|
74
|
+
if set(fields) - units.keys():
|
|
75
|
+
raise ValueError(f"特征 {name} 缺少输入单位声明")
|
|
76
|
+
specs.append(
|
|
77
|
+
FactorComputationSpec(
|
|
78
|
+
name=name,
|
|
79
|
+
version=f"vnpy-{REFERENCE_COMMIT[:12]}-{digest}",
|
|
80
|
+
description=f"{family} 固定参考版本配方;预热 {lookback} 期",
|
|
81
|
+
input_kind="panel",
|
|
82
|
+
inputs=["bar/1d", *fields],
|
|
83
|
+
lookback=lookback,
|
|
84
|
+
parameters={
|
|
85
|
+
"input_contract": contract,
|
|
86
|
+
"implementation_digest": implementation,
|
|
87
|
+
},
|
|
88
|
+
python_source=(
|
|
89
|
+
"from quant_core.app.factor.builtin.factor_recipe_catalog import run_recipe\n\n"
|
|
90
|
+
"def compute_panel(panel, parameters):\n"
|
|
91
|
+
f" return run_recipe({name!r}, panel, parameters['implementation_digest'])\n"
|
|
92
|
+
),
|
|
93
|
+
metadata={
|
|
94
|
+
"reference_commit": REFERENCE_COMMIT,
|
|
95
|
+
"family": family,
|
|
96
|
+
"universe_instruments": sorted(set(instrument_ids)),
|
|
97
|
+
},
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
return specs
|
|
File without changes
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""本地批量公式求值,共用基础字段与子表达式,不改变已有 Alpha 算子口径。"""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from dataclasses import asdict, dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
|
|
9
|
+
import polars as pl
|
|
10
|
+
|
|
11
|
+
from quant_core.app.factor.computation.factor_expression import FactorExpression
|
|
12
|
+
from quant_core.app.factor.computation.panel_expression import PanelExpression
|
|
13
|
+
from quant_core.app.factor.schema.factor_panel import FactorPanel
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class FactorBatchResult:
|
|
18
|
+
"""结果及输入、公式证据;本入口不持久化或发布因子。"""
|
|
19
|
+
|
|
20
|
+
values: dict[str, dict[tuple[datetime, str], Decimal | None]]
|
|
21
|
+
input_digest: str
|
|
22
|
+
definition_digest: str
|
|
23
|
+
lookback: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def execute_factors(
|
|
27
|
+
factors: dict[str, FactorExpression],
|
|
28
|
+
panel: FactorPanel,
|
|
29
|
+
*,
|
|
30
|
+
start: datetime,
|
|
31
|
+
end: datetime,
|
|
32
|
+
output_instruments: tuple[str, ...] | None = None,
|
|
33
|
+
) -> FactorBatchResult:
|
|
34
|
+
"""先在完整参与面板计算,再裁剪输出;缺少交易时间网格时拒绝压缩窗口。"""
|
|
35
|
+
if not factors or start.utcoffset() is None or end.utcoffset() is None or start > end:
|
|
36
|
+
raise ValueError("因子或输出时间范围非法")
|
|
37
|
+
fields = set().union(*(factor.required_fields for factor in factors.values()))
|
|
38
|
+
if not fields.issubset(panel.units):
|
|
39
|
+
raise ValueError(f"面板缺少字段: {sorted(fields - panel.units.keys())}")
|
|
40
|
+
rows = sorted(panel.rows, key=lambda row: (row.as_of, row.instrument_id))
|
|
41
|
+
instruments = sorted({row.instrument_id for row in rows})
|
|
42
|
+
timestamps = sorted({row.as_of for row in rows})
|
|
43
|
+
if not rows or len(rows) != len(instruments) * len(timestamps):
|
|
44
|
+
raise ValueError("面板必须保留完整日期×证券网格,缺失行情以空值表示")
|
|
45
|
+
if output_instruments is not None and not set(output_instruments).issubset(instruments):
|
|
46
|
+
raise ValueError("输出证券不属于参与计算的面板")
|
|
47
|
+
frame = pl.DataFrame(
|
|
48
|
+
{
|
|
49
|
+
"datetime": [row.as_of for row in rows],
|
|
50
|
+
"vt_symbol": [row.instrument_id for row in rows],
|
|
51
|
+
"eligible": [row.eligible for row in rows],
|
|
52
|
+
**{
|
|
53
|
+
field: [
|
|
54
|
+
float(value) if (value := row.fields.get(field)) is not None else None
|
|
55
|
+
for row in rows
|
|
56
|
+
]
|
|
57
|
+
for field in fields
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
schema_overrides={field: pl.Float64 for field in fields},
|
|
61
|
+
)
|
|
62
|
+
memo: dict[FactorExpression, PanelExpression] = {}
|
|
63
|
+
|
|
64
|
+
def evaluate(node: FactorExpression) -> PanelExpression:
|
|
65
|
+
if node in memo:
|
|
66
|
+
return memo[node]
|
|
67
|
+
if node.operation == "field":
|
|
68
|
+
result = PanelExpression(frame.select("datetime", "vt_symbol", node.field))
|
|
69
|
+
elif node.operation == "constant":
|
|
70
|
+
result = PanelExpression(
|
|
71
|
+
frame.select("datetime", "vt_symbol").with_columns(pl.lit(node.value).alias("data"))
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
arguments = [evaluate(argument) for argument in node.arguments]
|
|
75
|
+
left = arguments[0]
|
|
76
|
+
if node.operation in {"add", "sub", "mul", "div"}:
|
|
77
|
+
right = arguments[1]
|
|
78
|
+
result = {
|
|
79
|
+
"add": PanelExpression.__add__,
|
|
80
|
+
"sub": PanelExpression.__sub__,
|
|
81
|
+
"mul": PanelExpression.__mul__,
|
|
82
|
+
"div": PanelExpression.__truediv__,
|
|
83
|
+
}[node.operation](left, right)
|
|
84
|
+
else:
|
|
85
|
+
value = pl.col("data")
|
|
86
|
+
if node.operation == "ref":
|
|
87
|
+
expression = value.shift(node.window).over("vt_symbol")
|
|
88
|
+
elif node.operation == "mean":
|
|
89
|
+
expression = value.rolling_mean(node.window, min_samples=node.window).over(
|
|
90
|
+
"vt_symbol"
|
|
91
|
+
)
|
|
92
|
+
elif node.operation == "std":
|
|
93
|
+
expression = value.rolling_std(
|
|
94
|
+
node.window, min_samples=node.window, ddof=0
|
|
95
|
+
).over("vt_symbol")
|
|
96
|
+
elif node.operation == "rank":
|
|
97
|
+
left = PanelExpression(
|
|
98
|
+
left.df.with_columns(
|
|
99
|
+
pl.when(frame["eligible"]).then(value).otherwise(None).alias("data")
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
expression = value.rank(method="average").over("datetime") / value.count().over(
|
|
103
|
+
"datetime"
|
|
104
|
+
)
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError(f"不支持的算子: {node.operation}")
|
|
107
|
+
result = PanelExpression(
|
|
108
|
+
left.df.select("datetime", "vt_symbol", expression.alias("data"))
|
|
109
|
+
)
|
|
110
|
+
result = PanelExpression(
|
|
111
|
+
result.df.with_columns(
|
|
112
|
+
pl.when(pl.col("data").is_finite())
|
|
113
|
+
.then(pl.col("data"))
|
|
114
|
+
.otherwise(None)
|
|
115
|
+
.alias("data")
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
memo[node] = result
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
values = {}
|
|
122
|
+
allowed = set(output_instruments or instruments)
|
|
123
|
+
for name, factor in factors.items():
|
|
124
|
+
result = evaluate(factor)
|
|
125
|
+
values[name] = {
|
|
126
|
+
(row.as_of, row.instrument_id): Decimal(str(value))
|
|
127
|
+
if value is not None and row.eligible
|
|
128
|
+
else None
|
|
129
|
+
for row, value in zip(rows, result.df["data"], strict=True)
|
|
130
|
+
if start <= row.as_of <= end and row.instrument_id in allowed
|
|
131
|
+
}
|
|
132
|
+
return FactorBatchResult(
|
|
133
|
+
values=values,
|
|
134
|
+
input_digest=panel.input_digest,
|
|
135
|
+
definition_digest=hashlib.sha256(
|
|
136
|
+
json.dumps(
|
|
137
|
+
{key: asdict(value) for key, value in sorted(factors.items())}, sort_keys=True
|
|
138
|
+
).encode()
|
|
139
|
+
).hexdigest(),
|
|
140
|
+
lookback=max(factor.lookback for factor in factors.values()),
|
|
141
|
+
)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""量价公式树:声明字段和组合窗口,计算阶段不访问外部数据。"""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
Operation = Literal["field", "constant", "add", "sub", "mul", "div", "ref", "mean", "std", "rank"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class FactorExpression:
|
|
12
|
+
"""不可变公式;窗口长度包含当前行,复合窗口沿依赖链累加。"""
|
|
13
|
+
|
|
14
|
+
operation: Operation
|
|
15
|
+
arguments: tuple["FactorExpression", ...] = ()
|
|
16
|
+
field: str = ""
|
|
17
|
+
value: float = 0.0
|
|
18
|
+
window: int = 1
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def required_fields(self) -> frozenset[str]:
|
|
22
|
+
if self.operation == "field":
|
|
23
|
+
return frozenset({self.field})
|
|
24
|
+
return frozenset().union(*(argument.required_fields for argument in self.arguments))
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def lookback(self) -> int:
|
|
28
|
+
history = max((argument.lookback for argument in self.arguments), default=1)
|
|
29
|
+
if self.operation == "ref":
|
|
30
|
+
return history + self.window
|
|
31
|
+
if self.operation in {"mean", "std"}:
|
|
32
|
+
return history + self.window - 1
|
|
33
|
+
return history
|
|
34
|
+
|
|
35
|
+
def __add__(self, other: "FactorExpression | float") -> "FactorExpression":
|
|
36
|
+
return FactorExpression("add", (self, _expression(other)))
|
|
37
|
+
|
|
38
|
+
def __sub__(self, other: "FactorExpression | float") -> "FactorExpression":
|
|
39
|
+
return FactorExpression("sub", (self, _expression(other)))
|
|
40
|
+
|
|
41
|
+
def __mul__(self, other: "FactorExpression | float") -> "FactorExpression":
|
|
42
|
+
return FactorExpression("mul", (self, _expression(other)))
|
|
43
|
+
|
|
44
|
+
def __truediv__(self, other: "FactorExpression | float") -> "FactorExpression":
|
|
45
|
+
return FactorExpression("div", (self, _expression(other)))
|
|
46
|
+
|
|
47
|
+
def __radd__(self, other: float) -> "FactorExpression":
|
|
48
|
+
return _expression(other) + self
|
|
49
|
+
|
|
50
|
+
def __rsub__(self, other: float) -> "FactorExpression":
|
|
51
|
+
return _expression(other) - self
|
|
52
|
+
|
|
53
|
+
def __rmul__(self, other: float) -> "FactorExpression":
|
|
54
|
+
return _expression(other) * self
|
|
55
|
+
|
|
56
|
+
def __rtruediv__(self, other: float) -> "FactorExpression":
|
|
57
|
+
return _expression(other) / self
|
|
58
|
+
|
|
59
|
+
def __neg__(self) -> "FactorExpression":
|
|
60
|
+
return self * -1.0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _expression(value: FactorExpression | float) -> FactorExpression:
|
|
64
|
+
if isinstance(value, FactorExpression):
|
|
65
|
+
return value
|
|
66
|
+
if not isinstance(value, (float, int)) or not math.isfinite(value):
|
|
67
|
+
raise ValueError("公式常量必须为有限数值")
|
|
68
|
+
return FactorExpression("constant", value=float(value))
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def Factor(field: str) -> FactorExpression:
|
|
72
|
+
"""引用输入面板字段;单位及可见性由面板契约校验。"""
|
|
73
|
+
if not field or not field.isidentifier():
|
|
74
|
+
raise ValueError("因子字段名非法")
|
|
75
|
+
return FactorExpression("field", field=field)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _window(operation: Operation, factor: FactorExpression, window: int) -> FactorExpression:
|
|
79
|
+
if isinstance(window, bool) or not isinstance(window, int) or window < 1:
|
|
80
|
+
raise ValueError("时序窗口必须为正整数")
|
|
81
|
+
return FactorExpression(operation, (factor,), window=window)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def REF(factor: FactorExpression, window: int) -> FactorExpression:
|
|
85
|
+
"""沿输入的完整交易时间轴延迟指定期数。"""
|
|
86
|
+
return _window("ref", factor, window)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def MA(factor: FactorExpression, window: int) -> FactorExpression:
|
|
90
|
+
"""完整窗口均值;窗口内缺值不做自动填充。"""
|
|
91
|
+
return _window("mean", factor, window)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def STD(factor: FactorExpression, window: int) -> FactorExpression:
|
|
95
|
+
"""完整窗口总体标准差,ddof=0。"""
|
|
96
|
+
return _window("std", factor, window)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def PCT_CHANGE(factor: FactorExpression, window: int = 1) -> FactorExpression:
|
|
100
|
+
"""期末值与延迟值之比减一;零分母在计算时成为缺值。"""
|
|
101
|
+
return factor / REF(factor, window) - 1.0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def RANK(factor: FactorExpression) -> FactorExpression:
|
|
105
|
+
"""默认升序平均名次 / 非空参与数量;只支持这个明确的首批语义。"""
|
|
106
|
+
return FactorExpression("rank", (factor,))
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""可信因子源码加载,不读取文件或连接存储。"""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from quant_core.app.factor.computation.factor_expression import FactorExpression
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_formulas(source: str, parameters: dict | None = None) -> dict[str, FactorExpression]:
|
|
9
|
+
"""加载可信 Python 定义;约定 factors 字典,不提供代码沙箱。"""
|
|
10
|
+
namespace: dict = {"__name__": "factor_user_code", "parameters": dict(parameters or {})}
|
|
11
|
+
exec(compile(source, "<factors>", "exec"), namespace)
|
|
12
|
+
factors = namespace.get("factors")
|
|
13
|
+
if (
|
|
14
|
+
not isinstance(factors, dict)
|
|
15
|
+
or not factors
|
|
16
|
+
or any(
|
|
17
|
+
not isinstance(name, str)
|
|
18
|
+
or re.fullmatch(r"[a-z][a-z0-9_]*", name) is None
|
|
19
|
+
or not isinstance(value, FactorExpression)
|
|
20
|
+
for name, value in factors.items()
|
|
21
|
+
)
|
|
22
|
+
):
|
|
23
|
+
raise ValueError("源码必须定义非空 factors: dict[str, FactorExpression]")
|
|
24
|
+
return factors
|
|
File without changes
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Derived from vn.py fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09.
|
|
2
|
+
# Copyright (c) 2015-present, Xiaoyou Chen. MIT; see app/factor/LICENSE.vnpy.
|
|
3
|
+
"""
|
|
4
|
+
Math Functions
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
from quant_core.app.factor.computation.panel_expression import PanelExpression
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def less(feature1: PanelExpression, feature2: PanelExpression | float) -> PanelExpression:
|
|
13
|
+
"""Return the minimum value between two features"""
|
|
14
|
+
if isinstance(feature2, PanelExpression):
|
|
15
|
+
df_merged: pl.DataFrame = feature1.df.join(feature2.df, on=["datetime", "vt_symbol"])
|
|
16
|
+
else:
|
|
17
|
+
df_merged = feature1.df.with_columns(pl.lit(feature2).alias("data_right"))
|
|
18
|
+
|
|
19
|
+
df: pl.DataFrame = df_merged.select(
|
|
20
|
+
pl.col("datetime"),
|
|
21
|
+
pl.col("vt_symbol"),
|
|
22
|
+
pl.min_horizontal("data", "data_right").over("vt_symbol").alias("data"),
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return PanelExpression(df)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def greater(feature1: PanelExpression, feature2: PanelExpression | float) -> PanelExpression:
|
|
29
|
+
"""Return the maximum value between two features"""
|
|
30
|
+
if isinstance(feature2, PanelExpression):
|
|
31
|
+
df_merged: pl.DataFrame = feature1.df.join(feature2.df, on=["datetime", "vt_symbol"])
|
|
32
|
+
|
|
33
|
+
else:
|
|
34
|
+
df_merged = feature1.df.with_columns(pl.lit(feature2).alias("data_right"))
|
|
35
|
+
|
|
36
|
+
df: pl.DataFrame = df_merged.select(
|
|
37
|
+
pl.col("datetime"),
|
|
38
|
+
pl.col("vt_symbol"),
|
|
39
|
+
pl.max_horizontal("data", "data_right").over("vt_symbol").alias("data"),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
return PanelExpression(df)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def log(feature: PanelExpression) -> PanelExpression:
|
|
46
|
+
"""Calculate the natural logarithm of the feature"""
|
|
47
|
+
df: pl.DataFrame = feature.df.select(
|
|
48
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").log().over("vt_symbol")
|
|
49
|
+
)
|
|
50
|
+
return PanelExpression(df)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def abs(feature: PanelExpression) -> PanelExpression:
|
|
54
|
+
"""Calculate the absolute value of the feature"""
|
|
55
|
+
df: pl.DataFrame = feature.df.select(
|
|
56
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").abs().over("vt_symbol")
|
|
57
|
+
)
|
|
58
|
+
return PanelExpression(df)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def sign(feature: PanelExpression) -> PanelExpression:
|
|
62
|
+
"""Calculate the sign of the feature"""
|
|
63
|
+
df: pl.DataFrame = feature.df.select(
|
|
64
|
+
pl.col("datetime"),
|
|
65
|
+
pl.col("vt_symbol"),
|
|
66
|
+
pl.when(pl.col("data") > 0)
|
|
67
|
+
.then(1)
|
|
68
|
+
.when(pl.col("data") < 0)
|
|
69
|
+
.then(-1)
|
|
70
|
+
.otherwise(0)
|
|
71
|
+
.alias("data"),
|
|
72
|
+
)
|
|
73
|
+
return PanelExpression(df)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def quesval(
|
|
77
|
+
threshold: float,
|
|
78
|
+
feature1: PanelExpression,
|
|
79
|
+
feature2: PanelExpression | float | int,
|
|
80
|
+
feature3: PanelExpression | float | int,
|
|
81
|
+
) -> PanelExpression:
|
|
82
|
+
"""Return feature2 if threshold < feature1, otherwise feature3"""
|
|
83
|
+
df_merged = feature1.df
|
|
84
|
+
|
|
85
|
+
if isinstance(feature2, PanelExpression):
|
|
86
|
+
df_merged = df_merged.join(feature2.df, on=["datetime", "vt_symbol"], suffix="_true")
|
|
87
|
+
else:
|
|
88
|
+
df_merged = df_merged.with_columns(pl.lit(feature2).alias("data_true"))
|
|
89
|
+
|
|
90
|
+
if isinstance(feature3, PanelExpression):
|
|
91
|
+
df_merged = df_merged.join(feature3.df, on=["datetime", "vt_symbol"], suffix="_false")
|
|
92
|
+
else:
|
|
93
|
+
df_merged = df_merged.with_columns(pl.lit(feature3).alias("data_false"))
|
|
94
|
+
|
|
95
|
+
df: pl.DataFrame = df_merged.with_columns(
|
|
96
|
+
pl.when(threshold < pl.col("data"))
|
|
97
|
+
.then(pl.col("data_true"))
|
|
98
|
+
.otherwise(pl.col("data_false"))
|
|
99
|
+
.alias("data")
|
|
100
|
+
).select(["datetime", "vt_symbol", "data"])
|
|
101
|
+
|
|
102
|
+
return PanelExpression(df)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def quesval2(
|
|
106
|
+
threshold: PanelExpression,
|
|
107
|
+
feature1: PanelExpression,
|
|
108
|
+
feature2: PanelExpression | float | int,
|
|
109
|
+
feature3: PanelExpression | float | int,
|
|
110
|
+
) -> PanelExpression:
|
|
111
|
+
"""Return feature2 if threshold < feature1, otherwise feature3."""
|
|
112
|
+
df_merged: pl.DataFrame = threshold.df.join(
|
|
113
|
+
feature1.df, on=["datetime", "vt_symbol"], suffix="_cond"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if isinstance(feature2, PanelExpression):
|
|
117
|
+
df_merged = df_merged.join(feature2.df, on=["datetime", "vt_symbol"], suffix="_true")
|
|
118
|
+
else:
|
|
119
|
+
df_merged = df_merged.with_columns(pl.lit(feature2).alias("data_true"))
|
|
120
|
+
|
|
121
|
+
if isinstance(feature3, PanelExpression):
|
|
122
|
+
df_merged = df_merged.join(feature3.df, on=["datetime", "vt_symbol"], suffix="_false")
|
|
123
|
+
else:
|
|
124
|
+
df_merged = df_merged.with_columns(pl.lit(feature3).alias("data_false"))
|
|
125
|
+
|
|
126
|
+
df: pl.DataFrame = df_merged.with_columns(
|
|
127
|
+
pl.when(pl.col("data_cond") < pl.col("data"))
|
|
128
|
+
.then(pl.col("data_true"))
|
|
129
|
+
.otherwise(pl.col("data_false"))
|
|
130
|
+
.alias("data")
|
|
131
|
+
).select(["datetime", "vt_symbol", "data"])
|
|
132
|
+
|
|
133
|
+
return PanelExpression(df)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def pow1(base: PanelExpression, exponent: float) -> PanelExpression:
|
|
137
|
+
"""Safe power operation for PanelExpression (handles negative base values)"""
|
|
138
|
+
df: pl.DataFrame = base.df.with_columns(
|
|
139
|
+
pl.when(pl.col("data") > 0)
|
|
140
|
+
.then(pl.col("data").pow(exponent))
|
|
141
|
+
.when(pl.col("data") < 0)
|
|
142
|
+
.then(pl.lit(-1) * pl.col("data").abs().pow(exponent))
|
|
143
|
+
.otherwise(0)
|
|
144
|
+
.alias("data")
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return PanelExpression(df)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def pow2(base: PanelExpression, exponent: PanelExpression) -> PanelExpression:
|
|
151
|
+
"""Power operation between two PanelExpression objects (base^exponent)
|
|
152
|
+
|
|
153
|
+
handle logic:
|
|
154
|
+
- base > 0: calculate base^exponent
|
|
155
|
+
- base < 0 and exponent is integer: calculate -1 * |base|^exponent
|
|
156
|
+
- other cases (base = 0, exponent is NaN, negative base and non-integer exponent): return 0
|
|
157
|
+
|
|
158
|
+
用 floor 判断整数幂,避免将 NaN 强制转换为整数导致异常。
|
|
159
|
+
"""
|
|
160
|
+
base_renamed = base.df.rename({"data": "base_data"})
|
|
161
|
+
exp_renamed = exponent.df.rename({"data": "exp_data"})
|
|
162
|
+
|
|
163
|
+
df_merged: pl.DataFrame = base_renamed.join(
|
|
164
|
+
exp_renamed, on=["datetime", "vt_symbol"], how="left"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
df: pl.DataFrame = df_merged.with_columns(
|
|
168
|
+
pl.when(pl.col("base_data") > 0)
|
|
169
|
+
.then(pl.col("base_data").pow(pl.col("exp_data")))
|
|
170
|
+
.when(
|
|
171
|
+
(pl.col("base_data") < 0)
|
|
172
|
+
& (~pl.col("exp_data").is_nan())
|
|
173
|
+
& (pl.col("exp_data").floor() == pl.col("exp_data"))
|
|
174
|
+
)
|
|
175
|
+
.then((-1) * pl.col("base_data").abs().pow(pl.col("exp_data")))
|
|
176
|
+
.otherwise(pl.lit(None))
|
|
177
|
+
.fill_nan(None)
|
|
178
|
+
.fill_null(0)
|
|
179
|
+
.alias("data")
|
|
180
|
+
).select(["datetime", "vt_symbol", "data"])
|
|
181
|
+
|
|
182
|
+
return PanelExpression(df)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Derived from vn.py fa5206fe63836f3f8cd1ebd7168fbd19a5e2ff09.
|
|
2
|
+
# Copyright (c) 2015-present, Xiaoyou Chen. MIT; see app/factor/LICENSE.vnpy.
|
|
3
|
+
"""
|
|
4
|
+
Cross Section Operators
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
from quant_core.app.factor.computation.panel_expression import PanelExpression
|
|
10
|
+
from quant_core.app.factor.computation.universe_filter import mask_cross_section
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cs_rank(feature: PanelExpression) -> PanelExpression:
|
|
14
|
+
"""Perform cross-sectional ranking"""
|
|
15
|
+
feature = mask_cross_section(feature)
|
|
16
|
+
df: pl.DataFrame = feature.df.select(
|
|
17
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").rank().over("datetime")
|
|
18
|
+
)
|
|
19
|
+
return PanelExpression(df)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def cs_mean(feature: PanelExpression) -> PanelExpression:
|
|
23
|
+
"""Calculate cross-sectional mean"""
|
|
24
|
+
feature = mask_cross_section(feature)
|
|
25
|
+
df: pl.DataFrame = feature.df.select(
|
|
26
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").mean().over("datetime")
|
|
27
|
+
)
|
|
28
|
+
return PanelExpression(df)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def cs_std(feature: PanelExpression) -> PanelExpression:
|
|
32
|
+
"""Calculate cross-sectional standard deviation"""
|
|
33
|
+
feature = mask_cross_section(feature)
|
|
34
|
+
df: pl.DataFrame = feature.df.select(
|
|
35
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").std().over("datetime")
|
|
36
|
+
)
|
|
37
|
+
return PanelExpression(df)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def cs_sum(feature: PanelExpression) -> PanelExpression:
|
|
41
|
+
"""Calculate cross-sectional sum"""
|
|
42
|
+
feature = mask_cross_section(feature)
|
|
43
|
+
df: pl.DataFrame = feature.df.select(
|
|
44
|
+
pl.col("datetime"), pl.col("vt_symbol"), pl.col("data").sum().over("datetime")
|
|
45
|
+
)
|
|
46
|
+
return PanelExpression(df)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cs_scale(feature: PanelExpression) -> PanelExpression:
|
|
50
|
+
"""Scale the feature by the sum of absolute values in the cross section"""
|
|
51
|
+
feature = mask_cross_section(feature)
|
|
52
|
+
abs_feature = abs(feature)
|
|
53
|
+
sum_abs = cs_sum(abs_feature)
|
|
54
|
+
|
|
55
|
+
df_merged: pl.DataFrame = feature.df.join(
|
|
56
|
+
sum_abs.df, on=["datetime", "vt_symbol"], suffix="_sum"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
df: pl.DataFrame = df_merged.with_columns(
|
|
60
|
+
pl.when(pl.col("data_sum") != 0)
|
|
61
|
+
.then(pl.col("data") / pl.col("data_sum"))
|
|
62
|
+
.otherwise(0)
|
|
63
|
+
.alias("data")
|
|
64
|
+
).select(["datetime", "vt_symbol", "data"])
|
|
65
|
+
|
|
66
|
+
return PanelExpression(df)
|