quantnodes 3.0.0__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.
- QuantNodes/__init__.py +15 -0
- QuantNodes/__main__.py +14 -0
- QuantNodes/agent/__init__.py +158 -0
- QuantNodes/agent/agents/__init__.py +13 -0
- QuantNodes/agent/agents/definition.py +180 -0
- QuantNodes/agent/agents/manager.py +73 -0
- QuantNodes/agent/config/__init__.py +34 -0
- QuantNodes/agent/config/executor.py +958 -0
- QuantNodes/agent/config/loader.py +427 -0
- QuantNodes/agent/config/templates/bollinger_bands.yaml +84 -0
- QuantNodes/agent/config/templates/dual_ma.yaml +72 -0
- QuantNodes/agent/config/templates/empty.yaml +56 -0
- QuantNodes/agent/config/templates/mean_reversion.yaml +47 -0
- QuantNodes/agent/config/templates/mean_reversion_zscore.yaml +90 -0
- QuantNodes/agent/config/templates/momentum.yaml +81 -0
- QuantNodes/agent/config/templates/momentum_breakout.yaml +84 -0
- QuantNodes/agent/config/templates/rsi_strategy.yaml +72 -0
- QuantNodes/agent/config/templates/volume_price.yaml +86 -0
- QuantNodes/agent/config/types.py +156 -0
- QuantNodes/agent/config_mapper.py +293 -0
- QuantNodes/agent/core/__init__.py +19 -0
- QuantNodes/agent/core/dream.py +47 -0
- QuantNodes/agent/core/quant_dream.py +274 -0
- QuantNodes/agent/cron_jobs.py +314 -0
- QuantNodes/agent/nanobot_bridge.py +242 -0
- QuantNodes/agent/permission/__init__.py +30 -0
- QuantNodes/agent/permission/defaults.py +36 -0
- QuantNodes/agent/permission/evaluate.py +41 -0
- QuantNodes/agent/permission/models.py +59 -0
- QuantNodes/agent/permission/service.py +133 -0
- QuantNodes/agent/providers/__init__.py +11 -0
- QuantNodes/agent/providers/base.py +102 -0
- QuantNodes/agent/providers/quantnodes.py +610 -0
- QuantNodes/agent/providers/rate_limiter.py +326 -0
- QuantNodes/agent/providers/registry.py +163 -0
- QuantNodes/agent/skills/__init__.py +20 -0
- QuantNodes/agent/skills/base.py +118 -0
- QuantNodes/agent/skills/bridge.py +73 -0
- QuantNodes/agent/skills/factor/__init__.py +14 -0
- QuantNodes/agent/skills/factor/correlation.py +99 -0
- QuantNodes/agent/skills/factor/group_backtest.py +114 -0
- QuantNodes/agent/skills/factor/ic_analysis.py +106 -0
- QuantNodes/agent/skills/loader.py +107 -0
- QuantNodes/agent/skills/registry.py +105 -0
- QuantNodes/agent/skills/strategy/__init__.py +16 -0
- QuantNodes/agent/skills/strategy/bollinger.py +86 -0
- QuantNodes/agent/skills/strategy/dual_ma.py +82 -0
- QuantNodes/agent/skills/strategy/momentum.py +74 -0
- QuantNodes/agent/skills/strategy/rsi_reversal.py +99 -0
- QuantNodes/agent/skills_quant/__init__.py +14 -0
- QuantNodes/agent/skills_quant/backtest-analyze/SKILL.md +42 -0
- QuantNodes/agent/skills_quant/config-driven/SKILL.md +72 -0
- QuantNodes/agent/skills_quant/factor-research/SKILL.md +40 -0
- QuantNodes/agent/skills_quant/quant-dream/SKILL.md +55 -0
- QuantNodes/agent/skills_quant/risk-management/SKILL.md +45 -0
- QuantNodes/agent/skills_quant/strategy-design/SKILL.md +43 -0
- QuantNodes/agent/templates/__init__.py +4 -0
- QuantNodes/agent/tools/__init__.py +173 -0
- QuantNodes/agent/tools/_workspace.py +51 -0
- QuantNodes/agent/tools/alpha_backtest.py +328 -0
- QuantNodes/agent/tools/alpha_evaluate.py +493 -0
- QuantNodes/agent/tools/backtest.py +226 -0
- QuantNodes/agent/tools/base.py +133 -0
- QuantNodes/agent/tools/code_search.py +207 -0
- QuantNodes/agent/tools/config_backtest.py +401 -0
- QuantNodes/agent/tools/context.py +97 -0
- QuantNodes/agent/tools/dream_skill.py +77 -0
- QuantNodes/agent/tools/echo.py +38 -0
- QuantNodes/agent/tools/factor.py +231 -0
- QuantNodes/agent/tools/file_ops.py +201 -0
- QuantNodes/agent/tools/git_ops.py +190 -0
- QuantNodes/agent/tools/operator_lookup.py +218 -0
- QuantNodes/agent/tools/output_truncation.py +77 -0
- QuantNodes/agent/tools/path_check.py +43 -0
- QuantNodes/agent/tools/pipeline.py +62 -0
- QuantNodes/agent/tools/registry.py +150 -0
- QuantNodes/agent/tools/sandbox.py +62 -0
- QuantNodes/agent/tools/shell_safety.py +63 -0
- QuantNodes/agent/tools/strategy.py +106 -0
- QuantNodes/agent/tools/task.py +171 -0
- QuantNodes/agent/tools/web_fetch.py +142 -0
- QuantNodes/agent/tools/web_search.py +114 -0
- QuantNodes/agent/tools/wiki.py +370 -0
- QuantNodes/agent/utils/__init__.py +11 -0
- QuantNodes/agent/utils/helpers.py +43 -0
- QuantNodes/agent/utils/prompt_templates.py +30 -0
- QuantNodes/agent/workflows/__init__.py +20 -0
- QuantNodes/agent/workflows/implementations/__init__.py +8 -0
- QuantNodes/agent/workflows/implementations/alpha_gpt.py +508 -0
- QuantNodes/agent/workflows/implementations/mcts.py +442 -0
- QuantNodes/agent/workflows/parsers.py +44 -0
- QuantNodes/agent/workflows/registry.py +119 -0
- QuantNodes/agent/workflows/step_agent.py +219 -0
- QuantNodes/agent/workflows/tool.py +198 -0
- QuantNodes/ai/__init__.py +93 -0
- QuantNodes/ai/llm/__init__.py +75 -0
- QuantNodes/ai/llm/base.py +233 -0
- QuantNodes/ai/llm/decorators.py +281 -0
- QuantNodes/ai/llm/gateway.py +571 -0
- QuantNodes/ai/llm/null.py +76 -0
- QuantNodes/ai/llm/openai.py +435 -0
- QuantNodes/ai/optimizer.py +405 -0
- QuantNodes/ai/prompts/__init__.py +229 -0
- QuantNodes/ai/sandbox.py +371 -0
- QuantNodes/ai/sandbox_pandas_bridge.py +150 -0
- QuantNodes/ai/strategy_gen.py +396 -0
- QuantNodes/backtest/__init__.py +64 -0
- QuantNodes/backtest/backtest_node.py +188 -0
- QuantNodes/backtest/broker_node.py +378 -0
- QuantNodes/backtest/config_runner.py +397 -0
- QuantNodes/backtest/config_strategy.py +64 -0
- QuantNodes/backtest/risk_node.py +360 -0
- QuantNodes/backtest/strategy_node.py +268 -0
- QuantNodes/cache_node/__init__.py +19 -0
- QuantNodes/cache_node/base.py +244 -0
- QuantNodes/cache_node/cache_store.py +99 -0
- QuantNodes/cache_node/metadata.py +100 -0
- QuantNodes/cli/__init__.py +109 -0
- QuantNodes/cli/_helpers.py +511 -0
- QuantNodes/cli/command.py +110 -0
- QuantNodes/cli/commands/__init__.py +69 -0
- QuantNodes/cli/commands/agent.py +158 -0
- QuantNodes/cli/commands/alpha.py +951 -0
- QuantNodes/cli/commands/chat.py +38 -0
- QuantNodes/cli/commands/evolve.py +120 -0
- QuantNodes/cli/commands/factor.py +569 -0
- QuantNodes/cli/commands/init.py +190 -0
- QuantNodes/cli/commands/run.py +259 -0
- QuantNodes/cli/commands/serve.py +398 -0
- QuantNodes/cli/commands/version.py +120 -0
- QuantNodes/cli/enhanced.py +146 -0
- QuantNodes/conf_node/__init__.py +37 -0
- QuantNodes/conf_node/base.py +120 -0
- QuantNodes/conf_node/env_config.py +132 -0
- QuantNodes/conf_node/ini_config.py +70 -0
- QuantNodes/conf_node/json_config.py +69 -0
- QuantNodes/conf_node/yaml_config.py +78 -0
- QuantNodes/constants.py +17 -0
- QuantNodes/core/__init__.py +196 -0
- QuantNodes/core/_lookback_helpers.py +49 -0
- QuantNodes/core/ast_parser.py +198 -0
- QuantNodes/core/base.py +61 -0
- QuantNodes/core/cache_manager.py +344 -0
- QuantNodes/core/cache_utils.py +150 -0
- QuantNodes/core/cond_builder.py +53 -0
- QuantNodes/core/config.py +170 -0
- QuantNodes/core/constants.py +48 -0
- QuantNodes/core/control.py +412 -0
- QuantNodes/core/data_preprocessing.py +453 -0
- QuantNodes/core/data_source.py +46 -0
- QuantNodes/core/events.py +178 -0
- QuantNodes/core/evolution/__init__.py +22 -0
- QuantNodes/core/evolution/loop.py +583 -0
- QuantNodes/core/evolution/operators.py +289 -0
- QuantNodes/core/evolution/settings.py +44 -0
- QuantNodes/core/expression.py +841 -0
- QuantNodes/core/feedback/__init__.py +38 -0
- QuantNodes/core/feedback/channels.py +182 -0
- QuantNodes/core/feedback/collector.py +91 -0
- QuantNodes/core/feedback/dataclass.py +239 -0
- QuantNodes/core/feedback/llm_judge.py +138 -0
- QuantNodes/core/knowledge/__init__.py +69 -0
- QuantNodes/core/knowledge/knowledge_base.py +217 -0
- QuantNodes/core/knowledge/lineage_compress.py +196 -0
- QuantNodes/core/knowledge/lineage_expand.py +123 -0
- QuantNodes/core/knowledge/metrics/__init__.py +43 -0
- QuantNodes/core/knowledge/metrics/evaluator.py +176 -0
- QuantNodes/core/knowledge/metrics/metrics.py +220 -0
- QuantNodes/core/knowledge/rag_prompt.py +196 -0
- QuantNodes/core/knowledge/retriever.py +209 -0
- QuantNodes/core/lambda_node.py +81 -0
- QuantNodes/core/monitoring/__init__.py +22 -0
- QuantNodes/core/monitoring/collector.py +292 -0
- QuantNodes/core/monitoring/dashboard.py +365 -0
- QuantNodes/core/node.py +375 -0
- QuantNodes/core/pandas_utils.py +504 -0
- QuantNodes/core/parallel/__init__.py +15 -0
- QuantNodes/core/parallel/worker.py +140 -0
- QuantNodes/core/parallel/worker_process.py +265 -0
- QuantNodes/core/path_utils.py +73 -0
- QuantNodes/core/pipeline.py +328 -0
- QuantNodes/core/plugin.py +135 -0
- QuantNodes/core/quality_gate/__init__.py +32 -0
- QuantNodes/core/quality_gate/complexity.py +94 -0
- QuantNodes/core/quality_gate/consistency.py +26 -0
- QuantNodes/core/quality_gate/node.py +97 -0
- QuantNodes/core/quality_gate/redundancy.py +51 -0
- QuantNodes/core/quality_gate/settings.py +43 -0
- QuantNodes/core/quality_gate/zoo.py +98 -0
- QuantNodes/core/serializable.py +116 -0
- QuantNodes/core/serialization.py +673 -0
- QuantNodes/core/tools.py +333 -0
- QuantNodes/core/trajectory/__init__.py +25 -0
- QuantNodes/core/trajectory/entry.py +116 -0
- QuantNodes/core/trajectory/lineage.py +67 -0
- QuantNodes/core/trajectory/pool.py +211 -0
- QuantNodes/core/trajectory/selector.py +140 -0
- QuantNodes/core/visualization/__init__.py +33 -0
- QuantNodes/core/visualization/builder.py +233 -0
- QuantNodes/core/visualization/gate_breakdown.py +140 -0
- QuantNodes/core/visualization/lineage_dag.py +203 -0
- QuantNodes/core/visualization/metric_distribution.py +125 -0
- QuantNodes/core/visualization/report.py +68 -0
- QuantNodes/database_node/__init__.py +69 -0
- QuantNodes/database_node/base.py +135 -0
- QuantNodes/database_node/clickhouse_node.py +272 -0
- QuantNodes/database_node/csv_node.py +83 -0
- QuantNodes/database_node/duckdb_node.py +86 -0
- QuantNodes/database_node/factory.py +83 -0
- QuantNodes/database_node/mysql_node.py +100 -0
- QuantNodes/database_node/parquet_node.py +75 -0
- QuantNodes/database_node/sqlite_node.py +67 -0
- QuantNodes/factor_node/__init__.py +50 -0
- QuantNodes/factor_node/factor.py +563 -0
- QuantNodes/factor_node/factor_db.py +421 -0
- QuantNodes/factor_node/factor_functions/__init__.py +252 -0
- QuantNodes/factor_node/factor_functions/_helpers.py +358 -0
- QuantNodes/factor_node/factor_functions/_helpers_debug.py +317 -0
- QuantNodes/factor_node/factor_functions/composite_ops.py +136 -0
- QuantNodes/factor_node/factor_functions/math_ops.py +433 -0
- QuantNodes/factor_node/factor_functions/section_ops.py +290 -0
- QuantNodes/factor_node/factor_functions/talib_ops.py +1293 -0
- QuantNodes/factor_node/factor_functions/time_ops.py +535 -0
- QuantNodes/factor_node/factor_operation.py +1115 -0
- QuantNodes/factor_node/factor_table.py +1073 -0
- QuantNodes/factor_node/quant_nodes_object.py +60 -0
- QuantNodes/mcp_server/__init__.py +27 -0
- QuantNodes/mcp_server/__main__.py +4 -0
- QuantNodes/mcp_server/server.py +272 -0
- QuantNodes/methods/__init__.py +28 -0
- QuantNodes/methods/pipeline.py +100 -0
- QuantNodes/methods/sandbox.py +102 -0
- QuantNodes/monitor/__init__.py +27 -0
- QuantNodes/monitor/agent_tools/__init__.py +5 -0
- QuantNodes/monitor/agent_tools/monitor_tool.py +98 -0
- QuantNodes/monitor/agent_tools/schedule_tool.py +98 -0
- QuantNodes/monitor/agent_tools/version_tool.py +133 -0
- QuantNodes/monitor/monitor/__init__.py +6 -0
- QuantNodes/monitor/monitor/alerter.py +60 -0
- QuantNodes/monitor/monitor/collector.py +164 -0
- QuantNodes/monitor/monitor/dashboard.py +115 -0
- QuantNodes/monitor/monitor/drift.py +190 -0
- QuantNodes/monitor/scheduler/__init__.py +4 -0
- QuantNodes/monitor/scheduler/runner.py +133 -0
- QuantNodes/monitor/scheduler/scheduler.py +184 -0
- QuantNodes/monitor/storage/__init__.py +16 -0
- QuantNodes/monitor/storage/models.py +70 -0
- QuantNodes/monitor/storage/repository.py +407 -0
- QuantNodes/monitor/version/__init__.py +4 -0
- QuantNodes/monitor/version/diff.py +81 -0
- QuantNodes/monitor/version/version_manager.py +182 -0
- QuantNodes/operator_node/__init__.py +28 -0
- QuantNodes/operator_node/base.py +97 -0
- QuantNodes/operator_node/query_node.py +129 -0
- QuantNodes/operator_node/sql_builder.py +125 -0
- QuantNodes/operator_node/sql_utils.py +172 -0
- QuantNodes/operator_node/transform.py +130 -0
- QuantNodes/operators/__init__.py +90 -0
- QuantNodes/operators/_engine.py +108 -0
- QuantNodes/operators/composite.py +161 -0
- QuantNodes/operators/composite_dag.py +667 -0
- QuantNodes/operators/composite_dag_ops.py +343 -0
- QuantNodes/operators/composite_dag_pandas_ops.py +382 -0
- QuantNodes/operators/custom.py +408 -0
- QuantNodes/operators/facade.py +164 -0
- QuantNodes/operators/math.py +163 -0
- QuantNodes/operators/proxy.py +29 -0
- QuantNodes/operators/registry.py +144 -0
- QuantNodes/operators/section.py +99 -0
- QuantNodes/operators/talib.py +757 -0
- QuantNodes/operators/templates.py +95 -0
- QuantNodes/operators/time_series.py +136 -0
- QuantNodes/prompts/__init__.py +20 -0
- QuantNodes/prompts/backtest/__init__.py +12 -0
- QuantNodes/prompts/backtest/factor_based.py +86 -0
- QuantNodes/prompts/backtest/standard.py +73 -0
- QuantNodes/prompts/factor/__init__.py +14 -0
- QuantNodes/prompts/factor/correlation.py +77 -0
- QuantNodes/prompts/factor/group_backtest.py +86 -0
- QuantNodes/prompts/factor/ic_analysis.py +91 -0
- QuantNodes/prompts/strategy/__init__.py +18 -0
- QuantNodes/prompts/strategy/market_neutral.py +96 -0
- QuantNodes/prompts/strategy/mean_reversion.py +107 -0
- QuantNodes/prompts/strategy/momentum.py +160 -0
- QuantNodes/prompts/strategy/pairs_trading.py +107 -0
- QuantNodes/prompts/strategy/trend_following.py +96 -0
- QuantNodes/research/README.md +106 -0
- QuantNodes/research/__init__.py +154 -0
- QuantNodes/research/_legacy_3c/__init__.py +61 -0
- QuantNodes/research/_legacy_3c/auto_researcher.py +289 -0
- QuantNodes/research/_legacy_3c/factor_evaluator.py +560 -0
- QuantNodes/research/_legacy_3c/factor_miner.py +318 -0
- QuantNodes/research/_legacy_3c/mcts_search.py +324 -0
- QuantNodes/research/factor_test/__init__.py +25 -0
- QuantNodes/research/factor_test/config.py +184 -0
- QuantNodes/research/factor_test/config_builder.py +276 -0
- QuantNodes/research/factor_test/e2e/data_prep.py +163 -0
- QuantNodes/research/factor_test/e2e/run_evolution_e2e.py +309 -0
- QuantNodes/research/factor_test/evolution_adapter.py +231 -0
- QuantNodes/research/factor_test/feedback_wrapper.py +102 -0
- QuantNodes/research/factor_test/ifind_db/__init__.py +7 -0
- QuantNodes/research/factor_test/ifind_db/fetcher.py +224 -0
- QuantNodes/research/factor_test/ifind_db/ifind_database.py +689 -0
- QuantNodes/research/factor_test/nodes/__init__.py +1 -0
- QuantNodes/research/factor_test/nodes/_base.py +91 -0
- QuantNodes/research/factor_test/nodes/adjust_date_node.py +48 -0
- QuantNodes/research/factor_test/nodes/configs.py +240 -0
- QuantNodes/research/factor_test/nodes/factor_neutralize_node.py +87 -0
- QuantNodes/research/factor_test/nodes/factor_preprocess_node.py +222 -0
- QuantNodes/research/factor_test/nodes/factor_score_node.py +141 -0
- QuantNodes/research/factor_test/nodes/factor_test_report_node.py +153 -0
- QuantNodes/research/factor_test/nodes/group_analyzer_node.py +317 -0
- QuantNodes/research/factor_test/nodes/ic_analyzer_node.py +112 -0
- QuantNodes/research/factor_test/nodes/load_data_node.py +100 -0
- QuantNodes/research/factor_test/nodes/long_short_node.py +93 -0
- QuantNodes/research/factor_test/nodes/neutralizers.py +222 -0
- QuantNodes/research/factor_test/nodes/preprocess_strategies.py +277 -0
- QuantNodes/research/factor_test/nodes/risk_correlation_node.py +112 -0
- QuantNodes/research/factor_test/nodes/sample_pool_filter_node.py +110 -0
- QuantNodes/research/factor_test/nodes/tradability_filter_node.py +92 -0
- QuantNodes/research/factor_test/pipeline_runner.py +305 -0
- QuantNodes/research/factor_test/pipeline_spec.py +216 -0
- QuantNodes/research/factor_test/utils/__init__.py +26 -0
- QuantNodes/research/factor_test/utils/constants.py +86 -0
- QuantNodes/research/factor_test/utils/data_loader.py +141 -0
- QuantNodes/research/factor_test/utils/date_utils.py +232 -0
- QuantNodes/research/factor_test/utils/file_loaders.py +150 -0
- QuantNodes/research/factor_test/utils/labels.py +37 -0
- QuantNodes/research/factor_test/utils/metrics_extractor.py +55 -0
- QuantNodes/research/factor_test/utils/performance_metrics.py +175 -0
- QuantNodes/research/factor_test/utils/safe_load.py +106 -0
- QuantNodes/research/quant_alpha/CHANGELOG.md +80 -0
- QuantNodes/research/quant_alpha/README.md +142 -0
- QuantNodes/research/quant_alpha/__init__.py +45 -0
- QuantNodes/research/quant_alpha/adapters/__init__.py +99 -0
- QuantNodes/research/quant_alpha/adapters/calculator.py +503 -0
- QuantNodes/research/quant_alpha/adapters/expression.py +387 -0
- QuantNodes/research/quant_alpha/alpha101_design/__init__.py +50 -0
- QuantNodes/research/quant_alpha/alpha101_design/few_shot_examples.py +243 -0
- QuantNodes/research/quant_alpha/alpha101_design/philosophy.py +474 -0
- QuantNodes/research/quant_alpha/alpha158_design/__init__.py +63 -0
- QuantNodes/research/quant_alpha/alpha158_design/few_shot_examples.py +219 -0
- QuantNodes/research/quant_alpha/alpha158_design/philosophy.py +240 -0
- QuantNodes/research/quant_alpha/evaluation/__init__.py +47 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/__init__.py +8 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g1_handcrafted.py +135 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g2_llm_only.py +269 -0
- QuantNodes/research/quant_alpha/evaluation/baselines/g3_alpha_gpt.py +152 -0
- QuantNodes/research/quant_alpha/evaluation/clickhouse_data_loader.py +227 -0
- QuantNodes/research/quant_alpha/evaluation/contracts.py +376 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/__init__.py +6 -0
- QuantNodes/research/quant_alpha/evaluation/evaluators/polars_evaluator.py +545 -0
- QuantNodes/research/quant_alpha/evaluation/mock_data_loader.py +226 -0
- QuantNodes/research/quant_alpha/evaluation/runner.py +243 -0
- QuantNodes/research/quant_alpha/llm/__init__.py +38 -0
- QuantNodes/research/quant_alpha/llm/parser.py +681 -0
- QuantNodes/research/quant_alpha/logic_driven_pipeline.py +411 -0
- QuantNodes/research/quant_alpha/logic_mining/__init__.py +74 -0
- QuantNodes/research/quant_alpha/logic_mining/compiler.py +457 -0
- QuantNodes/research/quant_alpha/logic_mining/generator.py +366 -0
- QuantNodes/research/quant_alpha/logic_mining/models.py +252 -0
- QuantNodes/research/quant_alpha/logic_mining/parser.py +287 -0
- QuantNodes/research/quant_alpha/logic_mining/pipelines.py +297 -0
- QuantNodes/research/quant_alpha/logic_mining/sources.py +149 -0
- QuantNodes/research/quant_alpha/mcts/__init__.py +66 -0
- QuantNodes/research/quant_alpha/mcts/cache.py +262 -0
- QuantNodes/research/quant_alpha/mcts/extension_ops.py +320 -0
- QuantNodes/research/quant_alpha/mcts/feedback.py +825 -0
- QuantNodes/research/quant_alpha/mcts/op_prior.py +180 -0
- QuantNodes/research/quant_alpha/mcts/search.py +540 -0
- QuantNodes/research/quant_alpha/mcts/tree.py +201 -0
- QuantNodes/research/quant_alpha/operator_vocab/__init__.py +50 -0
- QuantNodes/research/quant_alpha/operator_vocab/config.py +54 -0
- QuantNodes/research/quant_alpha/operator_vocab/metadata.py +263 -0
- QuantNodes/research/quant_alpha/operator_vocab/vocabulary.py +481 -0
- QuantNodes/research/quant_alpha/pipeline.py +1027 -0
- QuantNodes/research/quant_alpha/types/__init__.py +27 -0
- QuantNodes/research/quant_alpha/types/constants.py +28 -0
- QuantNodes/research/quant_alpha/types/state.py +205 -0
- QuantNodes/research/quant_alpha/workflow/__init__.py +32 -0
- QuantNodes/research/quant_alpha/workflow/alpha_gpt.py +911 -0
- QuantNodes/research/quant_alpha/workflow/alpha_logics.py +416 -0
- QuantNodes/research/quant_alpha/workflow/state.py +27 -0
- QuantNodes/research/report_reproducer.py +485 -0
- QuantNodes/research/wiki.py +1155 -0
- QuantNodes/symbolic/__init__.py +51 -0
- QuantNodes/symbolic/compiler.py +113 -0
- QuantNodes/symbolic/dialect.py +260 -0
- QuantNodes/symbolic/executor.py +147 -0
- QuantNodes/symbolic/expression.py +234 -0
- QuantNodes/symbolic/functions.py +433 -0
- QuantNodes/symbolic/optimizer.py +165 -0
- QuantNodes/ui_node/__init__.py +30 -0
- QuantNodes/ui_node/base.py +222 -0
- quantnodes-3.0.0.dist-info/METADATA +463 -0
- quantnodes-3.0.0.dist-info/RECORD +399 -0
- quantnodes-3.0.0.dist-info/WHEEL +5 -0
- quantnodes-3.0.0.dist-info/entry_points.txt +24 -0
- quantnodes-3.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""Pipeline Runner / 管线编排器
|
|
3
|
+
|
|
4
|
+
混合模式数据传递:
|
|
5
|
+
- Phase 1 (严格串联): LoadData >> SampleFilter >> TradabilityFilter >>
|
|
6
|
+
AdjustDate >> Preprocess >> Neutralize
|
|
7
|
+
- Phase 2 (Context 共享): ICAnalyzer / GroupAnalyzer / FactorScore / RiskCorrelation
|
|
8
|
+
- Phase 3 (依赖分析): LongShort
|
|
9
|
+
- Phase 4 (输出): FactorTestReport
|
|
10
|
+
|
|
11
|
+
FactorFeedback 集成 (Week 1.5, 可选):
|
|
12
|
+
- feedback.enabled=False: 现有行为完全不变 (向后兼容)
|
|
13
|
+
- feedback.enabled=True: 5 个分析节点返回值自动包装为 FactorFeedback,
|
|
14
|
+
聚合到 ctx['Feedback'], 可选持久化到 feedback.output_dir
|
|
15
|
+
|
|
16
|
+
Phase R2 重构 (2026-06-19):
|
|
17
|
+
- 12 阶段从手写 137 行缩减为 PIPELINE_SPEC 数据驱动 (~30 行 run loop)
|
|
18
|
+
- Feedback 包装拆到 feedback_wrapper.py
|
|
19
|
+
- Evolution 适配拆到 evolution_adapter.py
|
|
20
|
+
- 指标提取拆到 utils/metrics_extractor.py
|
|
21
|
+
- 公共 API (`PipelineRunner.run()` / `.run_evolution()`) 完全兼容
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
import uuid
|
|
28
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
29
|
+
|
|
30
|
+
import pandas as pd
|
|
31
|
+
import yaml
|
|
32
|
+
|
|
33
|
+
from QuantNodes.core.feedback import FactorFeedback
|
|
34
|
+
from QuantNodes.research.factor_test.config import SingleFactorTestConfig
|
|
35
|
+
from QuantNodes.research.factor_test.evolution_adapter import (
|
|
36
|
+
build_evolution_loop,
|
|
37
|
+
build_quality_gate,
|
|
38
|
+
build_trajectory_pool,
|
|
39
|
+
evaluate_candidate,
|
|
40
|
+
run_evolution as _run_evolution,
|
|
41
|
+
run_one_factor,
|
|
42
|
+
)
|
|
43
|
+
from QuantNodes.research.factor_test.feedback_wrapper import (
|
|
44
|
+
ANALYSIS_NODES as _ANALYSIS_NODES, # noqa: F401 re-export 兼容
|
|
45
|
+
build_feedback,
|
|
46
|
+
maybe_build_judge,
|
|
47
|
+
maybe_persist_feedback,
|
|
48
|
+
)
|
|
49
|
+
from QuantNodes.research.factor_test.pipeline_spec import PIPELINE_SPEC, PhaseSpec
|
|
50
|
+
from QuantNodes.research.factor_test.utils.metrics_extractor import (
|
|
51
|
+
extract_metrics_from_ctx as _extract_metrics_from_ctx, # noqa: F401 re-export
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if TYPE_CHECKING:
|
|
55
|
+
from QuantNodes.core.evolution import EvolutionResult, FactorCandidate
|
|
56
|
+
from QuantNodes.core.feedback import LLMJudge
|
|
57
|
+
from QuantNodes.core.quality_gate import QualityGateNode
|
|
58
|
+
from QuantNodes.core.trajectory import TrajectoryPool
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
logger = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class PipelineRunner:
|
|
65
|
+
"""单因子回测管线编排器
|
|
66
|
+
|
|
67
|
+
用法:
|
|
68
|
+
config = SingleFactorTestConfig(...)
|
|
69
|
+
runner = PipelineRunner(config)
|
|
70
|
+
result = runner.run()
|
|
71
|
+
|
|
72
|
+
# 或从 YAML:
|
|
73
|
+
runner = PipelineRunner.from_yaml("config.yaml")
|
|
74
|
+
result = runner.run()
|
|
75
|
+
|
|
76
|
+
FactorFeedback 集成:
|
|
77
|
+
当 config.feedback.enabled=True 时, 5 个分析节点返回值会自动包装为
|
|
78
|
+
FactorFeedback, 聚合到 ctx['Feedback'] = {node_name: FactorFeedback}。
|
|
79
|
+
若 config.feedback.output_dir 不为 None, 还会持久化到该目录。
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
config: SingleFactorTestConfig,
|
|
85
|
+
specs: Optional[List["PhaseSpec"]] = None,
|
|
86
|
+
):
|
|
87
|
+
"""
|
|
88
|
+
Args:
|
|
89
|
+
config: 单因子回测配置.
|
|
90
|
+
specs: PR-QN-2 (2026-06-21) 新增. 自定义 phase 列表 (**追加**到
|
|
91
|
+
标准 12 阶段之后). 默认 None = 使用 ``list(PIPELINE_SPEC)``.
|
|
92
|
+
若要**完全替换** 12 阶段, 用 ``list(PIPELINE_SPEC) + specs``
|
|
93
|
+
显式拼接.
|
|
94
|
+
|
|
95
|
+
Note:
|
|
96
|
+
不传 specs 时, 行为与 PR-QN-2 之前**完全一致**. 4608+ 现有
|
|
97
|
+
tests 无需任何修改.
|
|
98
|
+
"""
|
|
99
|
+
self.config = config
|
|
100
|
+
self._context: dict = {}
|
|
101
|
+
# PR-QN-2: 实例级 _specs (默认 = 标准 12 阶段; 传 specs 时**追加**)
|
|
102
|
+
if specs:
|
|
103
|
+
self._specs: list = list(PIPELINE_SPEC) + list(specs)
|
|
104
|
+
else:
|
|
105
|
+
self._specs = list(PIPELINE_SPEC)
|
|
106
|
+
|
|
107
|
+
@classmethod
|
|
108
|
+
def from_yaml(cls, yaml_path: str) -> "PipelineRunner":
|
|
109
|
+
"""从 YAML 配置文件创建 Runner"""
|
|
110
|
+
with open(yaml_path, "r", encoding="utf-8") as f:
|
|
111
|
+
raw = yaml.safe_load(f)
|
|
112
|
+
return cls(SingleFactorTestConfig(**raw))
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def from_dict(
|
|
116
|
+
cls,
|
|
117
|
+
data: dict,
|
|
118
|
+
extra_phases: Optional[List["PhaseSpec"]] = None,
|
|
119
|
+
) -> "PipelineRunner":
|
|
120
|
+
"""从 dict 创建 Runner
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
data: 配置 dict.
|
|
124
|
+
extra_phases: PR-QN-2 (2026-06-21) 新增. 追加的自定义 phase 列表
|
|
125
|
+
(在标准 12 阶段之后执行). 默认 None = 与 PR 之前完全一致.
|
|
126
|
+
"""
|
|
127
|
+
return cls(SingleFactorTestConfig(**data), specs=extra_phases)
|
|
128
|
+
|
|
129
|
+
# ============================================================
|
|
130
|
+
# Phase R2: 声明式 12 节点 run loop
|
|
131
|
+
# ============================================================
|
|
132
|
+
|
|
133
|
+
def run(self) -> dict:
|
|
134
|
+
"""执行完整单因子回测管线
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
dict: 完整结果, 各节点输出按 ``ctx[node_name]`` 索引
|
|
138
|
+
当 ``config.feedback.enabled=True`` 时, 还包含 'Feedback' 键
|
|
139
|
+
"""
|
|
140
|
+
cfg = self.config
|
|
141
|
+
ctx, pre_seeded = self._seed_ctx()
|
|
142
|
+
|
|
143
|
+
feedback_enabled = cfg.feedback.enabled
|
|
144
|
+
factor_id = str(uuid.uuid4())
|
|
145
|
+
factor_name = cfg.factor.name
|
|
146
|
+
judge = maybe_build_judge(cfg) if feedback_enabled else None
|
|
147
|
+
|
|
148
|
+
logger.info("=" * 60)
|
|
149
|
+
logger.info("单因子回测: %s", cfg.factor.name)
|
|
150
|
+
logger.info(
|
|
151
|
+
"时间范围: %s ~ %s",
|
|
152
|
+
cfg.preprocess.adj_date_beg,
|
|
153
|
+
cfg.preprocess.adj_date_end,
|
|
154
|
+
)
|
|
155
|
+
if feedback_enabled:
|
|
156
|
+
logger.info("FactorFeedback: ENABLED (factor_id=%s...)", factor_id[:8])
|
|
157
|
+
logger.info("=" * 60)
|
|
158
|
+
|
|
159
|
+
for spec in self._specs:
|
|
160
|
+
self._run_phase(spec, ctx, pre_seeded)
|
|
161
|
+
|
|
162
|
+
# FactorFeedback 自动包装 (可选)
|
|
163
|
+
if feedback_enabled:
|
|
164
|
+
ctx["Feedback"] = build_feedback(ctx, factor_id, factor_name, cfg, judge)
|
|
165
|
+
maybe_persist_feedback(ctx["Feedback"], cfg)
|
|
166
|
+
n_passed = sum(1 for fb in ctx["Feedback"].values() if fb.decision)
|
|
167
|
+
logger.info(
|
|
168
|
+
"\n[Feedback] 包装完成: %d 节点, %d 通过",
|
|
169
|
+
len(ctx["Feedback"]),
|
|
170
|
+
n_passed,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
logger.info("\n" + "=" * 60)
|
|
174
|
+
logger.info("单因子回测完成!")
|
|
175
|
+
logger.info("=" * 60)
|
|
176
|
+
return ctx
|
|
177
|
+
|
|
178
|
+
def _seed_ctx(self) -> tuple[dict, bool]:
|
|
179
|
+
"""初始化 ctx; 若已注入 LoadData, 复用之, 否则空 dict.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
(ctx, pre_seeded) — pre_seeded=True 表示 LoadData 已外部注入
|
|
183
|
+
"""
|
|
184
|
+
if "LoadData" in self._context:
|
|
185
|
+
ctx = dict(self._context)
|
|
186
|
+
shape = ctx["LoadData"].get("factor", pd.DataFrame()).shape
|
|
187
|
+
logger.info(" [LoadData 跳过] 使用已注入数据 (factor shape: %s)", shape)
|
|
188
|
+
return ctx, True
|
|
189
|
+
return {}, False
|
|
190
|
+
|
|
191
|
+
def _run_phase(self, spec: PhaseSpec, ctx: dict, pre_seeded: bool) -> None:
|
|
192
|
+
"""执行单个阶段 (含 skip / log)."""
|
|
193
|
+
if spec.skip_if_in_ctx and spec.name in ctx:
|
|
194
|
+
return
|
|
195
|
+
logger.info("\n[Phase %d] %s...", spec.phase_no, spec.title)
|
|
196
|
+
node = spec.node_cls(config=spec.build_cfg(self.config))
|
|
197
|
+
ctx[spec.name] = node.execute(context=ctx)
|
|
198
|
+
if spec.log_summary is not None:
|
|
199
|
+
line = spec.log_summary(self.config, ctx[spec.name])
|
|
200
|
+
if line:
|
|
201
|
+
logger.info(line)
|
|
202
|
+
|
|
203
|
+
# ============================================================
|
|
204
|
+
# 兼容旧 API: 私有方法 facade re-export
|
|
205
|
+
# ============================================================
|
|
206
|
+
|
|
207
|
+
def _build_feedback(
|
|
208
|
+
self,
|
|
209
|
+
ctx: dict,
|
|
210
|
+
factor_id: str,
|
|
211
|
+
factor_name: str,
|
|
212
|
+
judge: Optional["LLMJudge"],
|
|
213
|
+
) -> dict:
|
|
214
|
+
"""向后兼容: 委托给 ``feedback_wrapper.build_feedback``."""
|
|
215
|
+
return build_feedback(ctx, factor_id, factor_name, self.config, judge)
|
|
216
|
+
|
|
217
|
+
def _maybe_persist_feedback(
|
|
218
|
+
self,
|
|
219
|
+
feedbacks: dict,
|
|
220
|
+
cfg: SingleFactorTestConfig,
|
|
221
|
+
) -> None:
|
|
222
|
+
"""向后兼容: 委托给 ``feedback_wrapper.maybe_persist_feedback``."""
|
|
223
|
+
maybe_persist_feedback(feedbacks, cfg)
|
|
224
|
+
|
|
225
|
+
@staticmethod
|
|
226
|
+
def _maybe_build_judge(cfg: SingleFactorTestConfig) -> Optional["LLMJudge"]:
|
|
227
|
+
"""向后兼容: 委托给 ``feedback_wrapper.maybe_build_judge``."""
|
|
228
|
+
return maybe_build_judge(cfg)
|
|
229
|
+
|
|
230
|
+
# ============================================================
|
|
231
|
+
# Week 4: 演化集成 (向后兼容 facade, 实现在 evolution_adapter.py)
|
|
232
|
+
# ============================================================
|
|
233
|
+
|
|
234
|
+
def _build_quality_gate(self) -> Optional["QualityGateNode"]:
|
|
235
|
+
return build_quality_gate(self)
|
|
236
|
+
|
|
237
|
+
def _build_trajectory_pool(self) -> Optional["TrajectoryPool"]:
|
|
238
|
+
return build_trajectory_pool(self)
|
|
239
|
+
|
|
240
|
+
def _build_evolution_loop(
|
|
241
|
+
self,
|
|
242
|
+
pool: "TrajectoryPool",
|
|
243
|
+
quality_gate: Optional["QualityGateNode"],
|
|
244
|
+
workers: int = 1,
|
|
245
|
+
):
|
|
246
|
+
return build_evolution_loop(self, pool, quality_gate, workers=workers)
|
|
247
|
+
|
|
248
|
+
def _evaluate_candidate(
|
|
249
|
+
self,
|
|
250
|
+
candidate: "FactorCandidate",
|
|
251
|
+
) -> tuple[bool, dict, "FactorFeedback"]:
|
|
252
|
+
return evaluate_candidate(self, candidate)
|
|
253
|
+
|
|
254
|
+
def _run_one_factor(self, candidate: "FactorCandidate") -> dict:
|
|
255
|
+
return run_one_factor(self, candidate)
|
|
256
|
+
|
|
257
|
+
def run_evolution(
|
|
258
|
+
self,
|
|
259
|
+
initial_directions: list[str] | None = None,
|
|
260
|
+
initial_candidates: list["FactorCandidate"] | None = None,
|
|
261
|
+
workers: int = 1,
|
|
262
|
+
) -> "EvolutionResult":
|
|
263
|
+
"""多轮演化主入口 (委托给 ``evolution_adapter.run_evolution``)."""
|
|
264
|
+
return _run_evolution(
|
|
265
|
+
self,
|
|
266
|
+
initial_directions=initial_directions,
|
|
267
|
+
initial_candidates=initial_candidates,
|
|
268
|
+
workers=workers,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def context(self) -> dict:
|
|
273
|
+
"""获取当前上下文"""
|
|
274
|
+
return self._context
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ============================================================
|
|
278
|
+
# 便捷函数
|
|
279
|
+
# ============================================================
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def run_single_factor_test(config: dict) -> dict:
|
|
283
|
+
"""便捷函数: 从 dict 配置运行单因子回测
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
config: 配置字典
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
dict: 完整结果
|
|
290
|
+
"""
|
|
291
|
+
runner = PipelineRunner.from_dict(config)
|
|
292
|
+
return runner.run()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def run_single_factor_test_yaml(yaml_path: str) -> dict:
|
|
296
|
+
"""便捷函数: 从 YAML 配置运行单因子回测
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
yaml_path: YAML 配置文件路径
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
dict: 完整结果
|
|
303
|
+
"""
|
|
304
|
+
runner = PipelineRunner.from_yaml(yaml_path)
|
|
305
|
+
return runner.run()
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""声明式 12 节点 Pipeline / Pipeline Spec.
|
|
3
|
+
|
|
4
|
+
把 ``pipeline_runner.run()`` 中 137 行手写阶段拆为 ``PIPELINE_SPEC`` 数据驱动:
|
|
5
|
+
|
|
6
|
+
- ``PhaseSpec`` 描述每阶段的 ``(name, node_cls, build_cfg, log_summary)``
|
|
7
|
+
- 增 / 删 / 调序节点 = 改 ``PIPELINE_SPEC`` 列表 (单点)
|
|
8
|
+
- ``run()`` 主循环 ~10 行
|
|
9
|
+
|
|
10
|
+
Phase R2 (2026-06-19): 从 pipeline_runner.py 抽出, 单一职责.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Any, Callable, Optional
|
|
17
|
+
|
|
18
|
+
from QuantNodes.research.factor_test.config import SingleFactorTestConfig
|
|
19
|
+
from QuantNodes.research.factor_test.nodes.adjust_date_node import AdjustDateNode
|
|
20
|
+
from QuantNodes.research.factor_test.nodes.factor_neutralize_node import FactorNeutralizeNode
|
|
21
|
+
from QuantNodes.research.factor_test.nodes.factor_preprocess_node import FactorPreprocessNode
|
|
22
|
+
from QuantNodes.research.factor_test.nodes.factor_score_node import FactorScoreNode
|
|
23
|
+
from QuantNodes.research.factor_test.nodes.factor_test_report_node import FactorTestReportNode
|
|
24
|
+
from QuantNodes.research.factor_test.nodes.group_analyzer_node import GroupAnalyzerNode
|
|
25
|
+
from QuantNodes.research.factor_test.nodes.ic_analyzer_node import ICAnalyzerNode
|
|
26
|
+
from QuantNodes.research.factor_test.nodes.load_data_node import LoadDataNode
|
|
27
|
+
from QuantNodes.research.factor_test.nodes.long_short_node import LongShortNode
|
|
28
|
+
from QuantNodes.research.factor_test.nodes.risk_correlation_node import RiskCorrelationNode
|
|
29
|
+
from QuantNodes.research.factor_test.nodes.sample_pool_filter_node import SamplePoolFilterNode
|
|
30
|
+
from QuantNodes.research.factor_test.nodes.tradability_filter_node import TradabilityFilterNode
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class PhaseSpec:
|
|
35
|
+
"""单个 Pipeline 阶段描述.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
name: 节点名 (用作 ``ctx[name]`` 键)
|
|
39
|
+
phase_no: 1-12 阶段编号 (仅显示)
|
|
40
|
+
title: 阶段中文标题 (打印 banner 用)
|
|
41
|
+
node_cls: 节点类 (``BaseNode`` 子类)
|
|
42
|
+
build_cfg: ``cfg -> dict`` 构造节点 config
|
|
43
|
+
skip_if_in_ctx: True 时若 ``name in ctx`` 则跳过 (LoadData 注入场景)
|
|
44
|
+
log_summary: ``(cfg, output) -> str`` 阶段成功后的额外日志, 可为 None
|
|
45
|
+
"""
|
|
46
|
+
name: str
|
|
47
|
+
phase_no: int
|
|
48
|
+
title: str
|
|
49
|
+
node_cls: type
|
|
50
|
+
build_cfg: Callable[[SingleFactorTestConfig], dict]
|
|
51
|
+
skip_if_in_ctx: bool = False
|
|
52
|
+
log_summary: Optional[Callable[[SingleFactorTestConfig, Any], str]] = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ── 12 节点 config builder ────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
def _cfg_load(cfg: SingleFactorTestConfig) -> dict:
|
|
58
|
+
return {
|
|
59
|
+
"factor": cfg.factor.model_dump(),
|
|
60
|
+
"data_path": cfg.data_path,
|
|
61
|
+
"load_keys": cfg.load_keys,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _cfg_sample(cfg: SingleFactorTestConfig) -> dict:
|
|
66
|
+
return {
|
|
67
|
+
"sample_index": cfg.preprocess.sample_index,
|
|
68
|
+
"sample_industry": cfg.preprocess.sample_industry,
|
|
69
|
+
"sample_index_customdir": cfg.preprocess.sample_index_customdir,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _cfg_trad(cfg: SingleFactorTestConfig) -> dict:
|
|
74
|
+
return {"tradable": cfg.preprocess.tradable.model_dump()}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _cfg_adj(cfg: SingleFactorTestConfig) -> dict:
|
|
78
|
+
return {
|
|
79
|
+
"adj_date_beg": cfg.preprocess.adj_date_beg,
|
|
80
|
+
"adj_date_end": cfg.preprocess.adj_date_end,
|
|
81
|
+
"adj_mode": cfg.preprocess.adj_mode,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _cfg_preprocess(cfg: SingleFactorTestConfig) -> dict:
|
|
86
|
+
return {
|
|
87
|
+
"missing": cfg.preprocess.missing,
|
|
88
|
+
"extreme": cfg.preprocess.extreme,
|
|
89
|
+
"norm": cfg.preprocess.norm,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _cfg_neutralize(cfg: SingleFactorTestConfig) -> dict:
|
|
94
|
+
return {
|
|
95
|
+
"industry_neutral": cfg.preprocess.industry_neutral,
|
|
96
|
+
"risk_neutral": cfg.preprocess.risk_neutral,
|
|
97
|
+
"risk_factors": cfg.preprocess.risk_factors,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _cfg_ic(cfg: SingleFactorTestConfig) -> dict:
|
|
102
|
+
return {"min_group_size": cfg.analysis.ic.min_group_size}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _cfg_group(cfg: SingleFactorTestConfig) -> dict:
|
|
106
|
+
return {
|
|
107
|
+
"groups": cfg.analysis.group.groups,
|
|
108
|
+
"factor_direction": cfg.analysis.group.factor_direction,
|
|
109
|
+
"floor_mode": cfg.analysis.group.floor_mode,
|
|
110
|
+
"hedge": cfg.analysis.group.hedge,
|
|
111
|
+
"hedge_path": cfg.analysis.group.hedge_path,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _cfg_longshort(cfg: SingleFactorTestConfig) -> dict:
|
|
116
|
+
return {"factor_direction": cfg.analysis.longshort.factor_direction}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _cfg_score(cfg: SingleFactorTestConfig) -> dict:
|
|
120
|
+
return {"enabled": cfg.analysis.score.enabled}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _cfg_risk_corr(cfg: SingleFactorTestConfig) -> dict:
|
|
124
|
+
return {"factors": cfg.analysis.risk_corr.factors}
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _cfg_report(cfg: SingleFactorTestConfig) -> dict:
|
|
128
|
+
return {"dir": cfg.output.dir, "format": cfg.output.format}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ── log_summary 辅助 ──────────────────────────────────────────
|
|
132
|
+
|
|
133
|
+
def _log_adj(cfg, out) -> str:
|
|
134
|
+
return f" 调仓日数: {len(out)}"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _log_preprocess(cfg, out) -> str:
|
|
138
|
+
shape = getattr(out, "shape", "?")
|
|
139
|
+
return f" 预处理后因子形状: {shape}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _log_ic(cfg, out) -> str:
|
|
143
|
+
if not isinstance(out, dict):
|
|
144
|
+
return ""
|
|
145
|
+
ic_result = out.get("ic_result")
|
|
146
|
+
if not isinstance(ic_result, dict):
|
|
147
|
+
return ""
|
|
148
|
+
parts = []
|
|
149
|
+
if ic_result.get("IC均值") is not None:
|
|
150
|
+
parts.append(f"IC均值: {ic_result['IC均值']:.4f}")
|
|
151
|
+
if ic_result.get("ICIR") is not None:
|
|
152
|
+
parts.append(f"ICIR: {ic_result['ICIR']:.4f}")
|
|
153
|
+
return " " + " | ".join(parts) if parts else ""
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _log_group(cfg, out) -> str:
|
|
157
|
+
return f" 分组数: {cfg.analysis.group.groups}"
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# ── 12 节点 Pipeline 声明 ─────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
PIPELINE_SPEC: list[PhaseSpec] = [
|
|
163
|
+
PhaseSpec(
|
|
164
|
+
name="LoadData", phase_no=1, title="数据加载",
|
|
165
|
+
node_cls=LoadDataNode, build_cfg=_cfg_load,
|
|
166
|
+
skip_if_in_ctx=True,
|
|
167
|
+
),
|
|
168
|
+
PhaseSpec(
|
|
169
|
+
name="SamplePoolFilter", phase_no=2, title="样本池筛选",
|
|
170
|
+
node_cls=SamplePoolFilterNode, build_cfg=_cfg_sample,
|
|
171
|
+
),
|
|
172
|
+
PhaseSpec(
|
|
173
|
+
name="TradabilityFilter", phase_no=3, title="可交易性筛选",
|
|
174
|
+
node_cls=TradabilityFilterNode, build_cfg=_cfg_trad,
|
|
175
|
+
),
|
|
176
|
+
PhaseSpec(
|
|
177
|
+
name="AdjustDate", phase_no=4, title="调仓日生成",
|
|
178
|
+
node_cls=AdjustDateNode, build_cfg=_cfg_adj,
|
|
179
|
+
log_summary=_log_adj,
|
|
180
|
+
),
|
|
181
|
+
PhaseSpec(
|
|
182
|
+
name="FactorPreprocess", phase_no=5, title="因子预处理",
|
|
183
|
+
node_cls=FactorPreprocessNode, build_cfg=_cfg_preprocess,
|
|
184
|
+
log_summary=_log_preprocess,
|
|
185
|
+
),
|
|
186
|
+
PhaseSpec(
|
|
187
|
+
name="FactorNeutralize", phase_no=6, title="因子中性化",
|
|
188
|
+
node_cls=FactorNeutralizeNode, build_cfg=_cfg_neutralize,
|
|
189
|
+
),
|
|
190
|
+
PhaseSpec(
|
|
191
|
+
name="ICAnalyzer", phase_no=7, title="IC 分析",
|
|
192
|
+
node_cls=ICAnalyzerNode, build_cfg=_cfg_ic,
|
|
193
|
+
log_summary=_log_ic,
|
|
194
|
+
),
|
|
195
|
+
PhaseSpec(
|
|
196
|
+
name="GroupAnalyzer", phase_no=8, title="分组分析",
|
|
197
|
+
node_cls=GroupAnalyzerNode, build_cfg=_cfg_group,
|
|
198
|
+
log_summary=_log_group,
|
|
199
|
+
),
|
|
200
|
+
PhaseSpec(
|
|
201
|
+
name="LongShort", phase_no=9, title="多空组合",
|
|
202
|
+
node_cls=LongShortNode, build_cfg=_cfg_longshort,
|
|
203
|
+
),
|
|
204
|
+
PhaseSpec(
|
|
205
|
+
name="FactorScore", phase_no=10, title="市值行业分层打分",
|
|
206
|
+
node_cls=FactorScoreNode, build_cfg=_cfg_score,
|
|
207
|
+
),
|
|
208
|
+
PhaseSpec(
|
|
209
|
+
name="RiskCorrelation", phase_no=11, title="风险因子相关性",
|
|
210
|
+
node_cls=RiskCorrelationNode, build_cfg=_cfg_risk_corr,
|
|
211
|
+
),
|
|
212
|
+
PhaseSpec(
|
|
213
|
+
name="FactorTestReport", phase_no=12, title="生成报告",
|
|
214
|
+
node_cls=FactorTestReportNode, build_cfg=_cfg_report,
|
|
215
|
+
),
|
|
216
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
from .data_loader import DataLoader
|
|
3
|
+
from .date_utils import (
|
|
4
|
+
valid_date, datenum_to_datetime, datetime_to_datenum,
|
|
5
|
+
chg_idx_to_datestr, resample_trade_date, get_adjust_date, offset_date,
|
|
6
|
+
)
|
|
7
|
+
from .performance_metrics import calc_max_drawdown, evaluation, cal_net_simple
|
|
8
|
+
from .constants import INDEX_MAPPING, INDEX_CP_MAPPING, INDUSTRY_MAPPING, ANNUAL_DAYS
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"DataLoader",
|
|
12
|
+
"valid_date",
|
|
13
|
+
"datenum_to_datetime",
|
|
14
|
+
"datetime_to_datenum",
|
|
15
|
+
"chg_idx_to_datestr",
|
|
16
|
+
"resample_trade_date",
|
|
17
|
+
"get_adjust_date",
|
|
18
|
+
"offset_date",
|
|
19
|
+
"calc_max_drawdown",
|
|
20
|
+
"evaluation",
|
|
21
|
+
"cal_net_simple",
|
|
22
|
+
"INDEX_MAPPING",
|
|
23
|
+
"INDEX_CP_MAPPING",
|
|
24
|
+
"INDUSTRY_MAPPING",
|
|
25
|
+
"ANNUAL_DAYS",
|
|
26
|
+
]
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""常量定义 / Constants
|
|
3
|
+
|
|
4
|
+
H13-H16: 行业/指数/天数支持外部 JSON 覆盖 (默认内置)。
|
|
5
|
+
用法:
|
|
6
|
+
from QuantNodes.research.factor_test.utils.constants import load_overrides
|
|
7
|
+
overrides = load_overrides(Path("./my_industry_map.json"))
|
|
8
|
+
ind_map = overrides.get("INDUSTRY_MAP", DEFAULT_INDUSTRY_MAP)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, Optional
|
|
13
|
+
|
|
14
|
+
import json
|
|
15
|
+
|
|
16
|
+
# 指数映射 (默认; SZ50 id_50 路由不在 ifind_database 中, 仅作占位)
|
|
17
|
+
INDEX_MAPPING = {
|
|
18
|
+
'HS300': ('stk_daily.h5', 'id_300'),
|
|
19
|
+
'ZZ500': ('stk_daily.h5', 'id_500'),
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# 指数收盘价映射
|
|
23
|
+
INDEX_CP_MAPPING = {
|
|
24
|
+
'HS300': '000300.SH',
|
|
25
|
+
'ZZ500': '000905.SH',
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# 中信行业映射
|
|
29
|
+
INDUSTRY_MAPPING = {
|
|
30
|
+
'id_citic1A': 'ind_name_CITIC_1A',
|
|
31
|
+
'id_citic1': 'ind_name_CITIC_1',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# 年化天数 (A 股 250; 美股/港股 252; 24h 市场 365)
|
|
35
|
+
ANNUAL_DAYS = 250
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def load_overrides(path: Optional[Path | str] = None) -> dict[str, Any]:
|
|
39
|
+
"""加载外部 JSON 覆盖 (H13-H16)。
|
|
40
|
+
|
|
41
|
+
支持覆盖字段:
|
|
42
|
+
- INDUSTRY_MAP: dict[行业 key, 显示名 key]
|
|
43
|
+
- INDEX_MAPPING: dict[指数名, (h5, key)]
|
|
44
|
+
- INDEX_CP_MAPPING: dict[指数名, 代码]
|
|
45
|
+
- ANNUAL_DAYS: int
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
path: JSON 路径, None 或文件不存在 → 返回空 dict (用全部默认)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
dict: 覆盖项 (子集, 只含实际有 override 的字段)
|
|
52
|
+
"""
|
|
53
|
+
if path is None:
|
|
54
|
+
return {}
|
|
55
|
+
path = Path(path)
|
|
56
|
+
if not path.exists():
|
|
57
|
+
return {}
|
|
58
|
+
try:
|
|
59
|
+
with path.open("r", encoding="utf-8") as f:
|
|
60
|
+
return json.load(f)
|
|
61
|
+
except Exception:
|
|
62
|
+
return {}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_industry_map(overrides: dict[str, Any] | None = None) -> dict[str, str]:
|
|
66
|
+
"""解析 INDUSTRY_MAP (含 override 合并)。"""
|
|
67
|
+
base = dict(INDUSTRY_MAPPING)
|
|
68
|
+
if overrides and "INDUSTRY_MAP" in overrides:
|
|
69
|
+
base.update(overrides["INDUSTRY_MAP"])
|
|
70
|
+
return base
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def resolve_index_mapping(overrides: dict[str, Any] | None = None) -> dict[str, tuple]:
|
|
74
|
+
"""解析 INDEX_MAPPING (含 override 合并)。"""
|
|
75
|
+
base = {k: tuple(v) for k, v in INDEX_MAPPING.items()}
|
|
76
|
+
if overrides and "INDEX_MAPPING" in overrides:
|
|
77
|
+
for k, v in overrides["INDEX_MAPPING"].items():
|
|
78
|
+
base[k] = tuple(v)
|
|
79
|
+
return base
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def resolve_annual_days(overrides: dict[str, Any] | None = None) -> int:
|
|
83
|
+
"""解析 ANNUAL_DAYS (含 override)。"""
|
|
84
|
+
if overrides and "ANNUAL_DAYS" in overrides:
|
|
85
|
+
return int(overrides["ANNUAL_DAYS"])
|
|
86
|
+
return ANNUAL_DAYS
|