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,309 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""E2E: 完整 12 节点 + 3 轮演化 + 报告生成。
|
|
3
|
+
|
|
4
|
+
用法:
|
|
5
|
+
# 1. 准备数据 (HDF5)
|
|
6
|
+
python -m QuantNodes.research.factor_test.e2e.data_prep \\
|
|
7
|
+
--output-dir /tmp/e2e_data/
|
|
8
|
+
|
|
9
|
+
# 2. 运行 E2E
|
|
10
|
+
python -m QuantNodes.research.factor_test.e2e.run_evolution_e2e \\
|
|
11
|
+
--data-path /tmp/e2e_data/ \\
|
|
12
|
+
--directions momentum,reversal,volatility \\
|
|
13
|
+
--max-rounds 3
|
|
14
|
+
|
|
15
|
+
输出:
|
|
16
|
+
{output_dir}/
|
|
17
|
+
├── trajectory/ # TrajectoryPool (Parquet + JSON)
|
|
18
|
+
│ ├── trajectories.parquet
|
|
19
|
+
│ └── {entry_id}.json
|
|
20
|
+
├── feedback.parquet # FactorFeedback 持久化 (如启用)
|
|
21
|
+
├── evolution_report.html # 可视化报告
|
|
22
|
+
└── evolution_summary.json # 演化统计
|
|
23
|
+
|
|
24
|
+
E2E 验证:
|
|
25
|
+
1. PipelineRunner.from_dict() 不报错
|
|
26
|
+
2. 12 节点全部执行成功
|
|
27
|
+
3. QualityGate 拦截低质量因子
|
|
28
|
+
4. TrajectoryPool 写入 5+ entries (3 original + 1 mutation + 1 crossover)
|
|
29
|
+
5. Visualization HTML 包含 5 个 figure
|
|
30
|
+
6. RAG 评估指标 > 0 (说明检索非空)
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import argparse
|
|
35
|
+
import json
|
|
36
|
+
import sys
|
|
37
|
+
from datetime import datetime, timedelta
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
|
|
40
|
+
import pandas as pd
|
|
41
|
+
|
|
42
|
+
from QuantNodes.core.knowledge import (
|
|
43
|
+
IdentityRetriever,
|
|
44
|
+
KnowledgeBase,
|
|
45
|
+
RAGEvaluator,
|
|
46
|
+
)
|
|
47
|
+
from QuantNodes.core.path_utils import ensure_dir
|
|
48
|
+
from QuantNodes.core.visualization import generate_html
|
|
49
|
+
from QuantNodes.research.factor_test.config import SingleFactorTestConfig
|
|
50
|
+
from QuantNodes.research.factor_test.config_builder import (
|
|
51
|
+
SingleFactorTestConfigBuilder,
|
|
52
|
+
)
|
|
53
|
+
from QuantNodes.research.factor_test.pipeline_runner import PipelineRunner
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _build_config(
|
|
57
|
+
data_path: str,
|
|
58
|
+
factor_name: str,
|
|
59
|
+
factor_dir: str,
|
|
60
|
+
directions: list[str],
|
|
61
|
+
output_dir: str,
|
|
62
|
+
max_rounds: int = 3,
|
|
63
|
+
enable_quality_gate: bool = True,
|
|
64
|
+
) -> SingleFactorTestConfig:
|
|
65
|
+
"""构造 SingleFactorTestConfig (M6: 移除未用的 enable_kb 参数)。
|
|
66
|
+
|
|
67
|
+
Phase 3.4: 改用 SingleFactorTestConfigBuilder 流式构造 (bitwise 等价)。
|
|
68
|
+
"""
|
|
69
|
+
# H12: 不再硬编码 20260101/20260630, 默认 1 年前到 1 个月前 (滚动)
|
|
70
|
+
one_year_ago = int((datetime.now() - timedelta(days=365)).strftime('%Y%m%d'))
|
|
71
|
+
one_month_ago = int((datetime.now() - timedelta(days=30)).strftime('%Y%m%d'))
|
|
72
|
+
return (
|
|
73
|
+
SingleFactorTestConfigBuilder()
|
|
74
|
+
.factor(
|
|
75
|
+
factor_name, factor_dir,
|
|
76
|
+
hypothesis=directions[0] if directions else "momentum",
|
|
77
|
+
description=f"e2e test factor: {factor_name}",
|
|
78
|
+
)
|
|
79
|
+
.dates(one_year_ago, one_month_ago)
|
|
80
|
+
.adj_mode(["M", "end"])
|
|
81
|
+
.sample("all", "all")
|
|
82
|
+
.tradable(
|
|
83
|
+
no_st=True, no_suspended=True, no_up_down_limit=False,
|
|
84
|
+
min_ipo_days=360,
|
|
85
|
+
)
|
|
86
|
+
.preprocess(missing="", extreme="median", norm="zscore")
|
|
87
|
+
.neutralize(industry=False, risk=False, risk_factors=[])
|
|
88
|
+
.ic(min_group_size=5)
|
|
89
|
+
.groups(5, direction=1, floor_mode="group", hedge="equal")
|
|
90
|
+
.longshort(direction=1)
|
|
91
|
+
.score(enabled=True)
|
|
92
|
+
.risk_corr("")
|
|
93
|
+
.output(output_dir, fmt=["json"])
|
|
94
|
+
.feedback(enabled=False)
|
|
95
|
+
.quality_gate(enabled=enable_quality_gate)
|
|
96
|
+
.evolution(
|
|
97
|
+
enabled=True, max_rounds=max_rounds,
|
|
98
|
+
parent_selection_strategy="top_percent_plus_random",
|
|
99
|
+
top_percent_threshold=0.5,
|
|
100
|
+
metric="sharpe",
|
|
101
|
+
early_stop_patience=0,
|
|
102
|
+
)
|
|
103
|
+
.data_path(data_path)
|
|
104
|
+
.load_keys(
|
|
105
|
+
["cp", "id_citic1", "mv_float", "st", "suspend", "ud_limit", "ipo_days"]
|
|
106
|
+
)
|
|
107
|
+
.build()
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _inject_prepared_data(runner: PipelineRunner, data_path: Path) -> None:
|
|
113
|
+
"""预填 _context['LoadData'], 跳过 LoadDataNode 真实 H5 读取。
|
|
114
|
+
|
|
115
|
+
名称: 从已 prepare 好的 H5 数据 (data_prep.py 输出) 注入到 ctx,
|
|
116
|
+
与"合成"无关 (R4 重命名: ``_inject_synthetic_data`` → ``_inject_prepared_data``).
|
|
117
|
+
"""
|
|
118
|
+
# 读 H5 实际数据
|
|
119
|
+
cp = pd.read_hdf(data_path / "stk_daily.h5", key="cp")
|
|
120
|
+
st = pd.read_hdf(data_path / "stk_daily.h5", key="st")
|
|
121
|
+
suspend = pd.read_hdf(data_path / "stk_daily.h5", key="suspend")
|
|
122
|
+
ud_limit = pd.read_hdf(data_path / "stk_daily.h5", key="ud_limit")
|
|
123
|
+
ipo_days = pd.read_hdf(data_path / "stk_daily.h5", key="ipo_days")
|
|
124
|
+
industry = pd.read_hdf(data_path / "stk_daily.h5", key="id_citic1")
|
|
125
|
+
mv = pd.read_hdf(data_path / "stk_daily.h5", key="mv_float")
|
|
126
|
+
factor = pd.read_hdf(data_path / f"{runner.config.factor.name}.h5", key="data")
|
|
127
|
+
index_cp = pd.read_hdf(data_path / "index_daily.h5", key="index_cp")
|
|
128
|
+
stklist = pd.read_hdf(data_path / "stklist.h5", key="data")
|
|
129
|
+
trade_dt = pd.read_hdf(data_path / "trade_dt.h5", key="data")
|
|
130
|
+
|
|
131
|
+
runner._context["LoadData"] = {
|
|
132
|
+
"factor": factor,
|
|
133
|
+
"price": cp,
|
|
134
|
+
"id_citic1": industry,
|
|
135
|
+
"mv_float": mv,
|
|
136
|
+
"st": st,
|
|
137
|
+
"suspend": suspend,
|
|
138
|
+
"ud_limit": ud_limit,
|
|
139
|
+
"ipo_days": ipo_days,
|
|
140
|
+
"index_cp": index_cp,
|
|
141
|
+
"stklist": stklist,
|
|
142
|
+
"trade_dt": trade_dt,
|
|
143
|
+
"_loader": _build_loader(str(data_path)),
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# 向后兼容: R4 重命名前的旧名称
|
|
148
|
+
_inject_synthetic_data = _inject_prepared_data
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _build_loader(data_path: str):
|
|
152
|
+
"""构造 DataLoader (供 RiskCorrelationNode 使用)。"""
|
|
153
|
+
from QuantNodes.research.factor_test.utils.data_loader import DataLoader
|
|
154
|
+
return DataLoader(data_path)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
158
|
+
"""构造 E2E 演化 CLI 参数解析器。"""
|
|
159
|
+
parser = argparse.ArgumentParser(description="E2E 演化运行")
|
|
160
|
+
parser.add_argument("--data-path", required=True, help="data_prep 输出目录")
|
|
161
|
+
parser.add_argument("--factor-name", default="momentum_20d", help="起始因子名")
|
|
162
|
+
parser.add_argument("--directions", default="momentum,reversal,volatility",
|
|
163
|
+
help="逗号分隔的研究方向 (用作 RAG query + 初始 directions)")
|
|
164
|
+
parser.add_argument("--output-dir", default="/tmp/e2e_output/",
|
|
165
|
+
help="输出目录 (报告 / trajectory / summary)")
|
|
166
|
+
parser.add_argument("--max-rounds", type=int, default=3)
|
|
167
|
+
parser.add_argument("--disable-quality-gate", action="store_true",
|
|
168
|
+
help="禁用 QualityGate (默认启用)")
|
|
169
|
+
# M22-M23: 演化参数可配置 (默认保持原有值不变)
|
|
170
|
+
parser.add_argument("--rag-top-k", type=int, default=3,
|
|
171
|
+
help="RAG 检索 Top-K (默认 3)")
|
|
172
|
+
parser.add_argument("--ancestor-depth", type=int, default=2,
|
|
173
|
+
help="知识谱系祖先深度 (默认 2)")
|
|
174
|
+
parser.add_argument("--descendant-depth", type=int, default=2,
|
|
175
|
+
help="知识谱系列脉深度 (默认 2)")
|
|
176
|
+
parser.add_argument("--no-compress", action="store_true",
|
|
177
|
+
help="禁用谱系压缩 (默认启用)")
|
|
178
|
+
return parser
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def main():
|
|
182
|
+
parser = _build_parser()
|
|
183
|
+
args = parser.parse_args()
|
|
184
|
+
|
|
185
|
+
data_path = Path(args.data_path)
|
|
186
|
+
output_dir = Path(args.output_dir)
|
|
187
|
+
ensure_dir(output_dir)
|
|
188
|
+
directions = [d.strip() for d in args.directions.split(",") if d.strip()]
|
|
189
|
+
|
|
190
|
+
print("=" * 70)
|
|
191
|
+
print("E2E 演化实验")
|
|
192
|
+
print(f" data_path: {data_path}")
|
|
193
|
+
print(f" output_dir: {output_dir}")
|
|
194
|
+
print(f" directions: {directions}")
|
|
195
|
+
print(f" max_rounds: {args.max_rounds}")
|
|
196
|
+
print("=" * 70)
|
|
197
|
+
|
|
198
|
+
# 1. 构造 config + runner
|
|
199
|
+
cfg = _build_config(
|
|
200
|
+
data_path=str(data_path),
|
|
201
|
+
factor_name=args.factor_name,
|
|
202
|
+
factor_dir=f"{args.factor_name}.h5",
|
|
203
|
+
directions=directions,
|
|
204
|
+
output_dir=str(output_dir),
|
|
205
|
+
max_rounds=args.max_rounds,
|
|
206
|
+
enable_quality_gate=not args.disable_quality_gate,
|
|
207
|
+
)
|
|
208
|
+
runner = PipelineRunner(cfg)
|
|
209
|
+
_inject_prepared_data(runner, data_path)
|
|
210
|
+
print(f"\n[1/5] 注入 LoadData: factor={runner._context['LoadData']['factor'].shape}")
|
|
211
|
+
|
|
212
|
+
# 2. 先单次回测 (验证 12 节点)
|
|
213
|
+
print("\n[2/5] 单次回测 (验证 12 节点 + QualityGate)...")
|
|
214
|
+
try:
|
|
215
|
+
ctx = runner.run()
|
|
216
|
+
print(f" ✓ 12 节点全部执行, ctx keys: {list(ctx.keys())}")
|
|
217
|
+
# 打印 IC
|
|
218
|
+
if "ICAnalyzer" in ctx:
|
|
219
|
+
ic_res = ctx["ICAnalyzer"].get("ic_result", {})
|
|
220
|
+
print(f" IC均值: {ic_res.get('IC均值', 'N/A')}")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f" ✗ 单次回测失败: {e}")
|
|
223
|
+
return 1
|
|
224
|
+
|
|
225
|
+
# 3. 设置演化组件
|
|
226
|
+
print("\n[3/5] 构造演化组件...")
|
|
227
|
+
pool = runner._build_trajectory_pool()
|
|
228
|
+
quality_gate = runner._build_quality_gate()
|
|
229
|
+
|
|
230
|
+
kb = KnowledgeBase(IdentityRetriever(), pool=pool)
|
|
231
|
+
evaluator = RAGEvaluator()
|
|
232
|
+
print(f" ✓ TrajectoryPool: {pool.base_dir}")
|
|
233
|
+
print(f" ✓ QualityGateNode: {'enabled' if quality_gate else 'disabled'}")
|
|
234
|
+
print(" ✓ KnowledgeBase + RAGEvaluator (TF-IDF always enabled)")
|
|
235
|
+
|
|
236
|
+
# 4. 演化循环
|
|
237
|
+
print(f"\n[4/5] 演化循环 ({args.max_rounds} 轮)...")
|
|
238
|
+
from QuantNodes.core.evolution import EvolutionLoop, EvolutionSetting as ES
|
|
239
|
+
settings = ES(
|
|
240
|
+
enabled=True, max_rounds=args.max_rounds,
|
|
241
|
+
parent_selection_strategy="top_percent_plus_random",
|
|
242
|
+
top_percent_threshold=0.5,
|
|
243
|
+
metric="sharpe", seed=42,
|
|
244
|
+
)
|
|
245
|
+
loop = EvolutionLoop(
|
|
246
|
+
settings, pool=pool,
|
|
247
|
+
quality_gate=quality_gate,
|
|
248
|
+
evaluate_fn=runner._evaluate_candidate,
|
|
249
|
+
knowledge_base=kb,
|
|
250
|
+
rag_evaluator=evaluator,
|
|
251
|
+
rag_top_k=args.rag_top_k,
|
|
252
|
+
max_ancestor_depth=args.ancestor_depth,
|
|
253
|
+
max_descendant_depth=args.descendant_depth,
|
|
254
|
+
use_compress=not args.no_compress,
|
|
255
|
+
)
|
|
256
|
+
result = loop.run(initial_directions=directions)
|
|
257
|
+
print(
|
|
258
|
+
f" ✓ 演化完成: {result.rounds_completed} 轮, "
|
|
259
|
+
f"总数 {result.total_count}, 拒绝 {result.rejected_count}"
|
|
260
|
+
)
|
|
261
|
+
if kb is not None:
|
|
262
|
+
print(f" ✓ KB 已索引 {len(kb)} entry")
|
|
263
|
+
print(f" ✓ RAG 评估历史: {len(loop.rag_metrics_history)} 轮")
|
|
264
|
+
if loop.rag_metrics_history:
|
|
265
|
+
m = loop.rag_metrics_history[-1]
|
|
266
|
+
print(f" Round {m['round']}: HR@5={m['hit_at_5']:.3f} NDCG@5={m['ndcg_at_5']:.3f}")
|
|
267
|
+
|
|
268
|
+
# 5. 报告生成
|
|
269
|
+
print("\n[5/5] 生成报告...")
|
|
270
|
+
report_path = output_dir / "evolution_report.html"
|
|
271
|
+
try:
|
|
272
|
+
generate_html(pool, metric="sharpe",
|
|
273
|
+
title=f"E2E 演化报告: {data_path.name}",
|
|
274
|
+
output_path=report_path)
|
|
275
|
+
print(f" ✓ HTML 报告: {report_path} ({report_path.stat().st_size} bytes)")
|
|
276
|
+
except Exception as e:
|
|
277
|
+
print(f" ✗ HTML 报告失败: {e}")
|
|
278
|
+
|
|
279
|
+
summary = {
|
|
280
|
+
"data_path": str(data_path),
|
|
281
|
+
"output_dir": str(output_dir),
|
|
282
|
+
"directions": directions,
|
|
283
|
+
"max_rounds": args.max_rounds,
|
|
284
|
+
"pool_size": pool.size,
|
|
285
|
+
"rounds_completed": result.rounds_completed,
|
|
286
|
+
"total_count": result.total_count,
|
|
287
|
+
"rejected_count": result.rejected_count,
|
|
288
|
+
"best_entries": [
|
|
289
|
+
{
|
|
290
|
+
"id": e.entry_id,
|
|
291
|
+
"name": e.feedback.factor_name if e.feedback else "",
|
|
292
|
+
"operation": e.operation,
|
|
293
|
+
"round": e.round_idx,
|
|
294
|
+
"sharpe": (e.metrics or {}).get("sharpe", 0),
|
|
295
|
+
}
|
|
296
|
+
for e in result.best_entries[:5]
|
|
297
|
+
],
|
|
298
|
+
"rag_metrics_history": loop.rag_metrics_history,
|
|
299
|
+
}
|
|
300
|
+
summary_path = output_dir / "evolution_summary.json"
|
|
301
|
+
summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2))
|
|
302
|
+
print(f" ✓ JSON 摘要: {summary_path}")
|
|
303
|
+
|
|
304
|
+
print(f"\n{'=' * 70}\n✓ E2E 完成\n{'=' * 70}")
|
|
305
|
+
return 0
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
sys.exit(main())
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""演化适配器 / Evolution Adapter.
|
|
3
|
+
|
|
4
|
+
把 ``PipelineRunner`` 与 ``QuantNodes.core.evolution`` 框架对接:
|
|
5
|
+
|
|
6
|
+
- 构造 ``QualityGateNode`` (按 ``cfg.quality_gate.enabled``)
|
|
7
|
+
- 构造 ``TrajectoryPool`` (按 ``cfg.evolution.enabled``)
|
|
8
|
+
- 构造 ``EvolutionLoop`` (含 evaluate_fn)
|
|
9
|
+
- ``evaluate_candidate`` / ``run_one_factor`` 子例程
|
|
10
|
+
- ``run_evolution`` 主入口 (含 ProcessPool 快照预序列化 + Streaming 集成)
|
|
11
|
+
|
|
12
|
+
Phase R2 (2026-06-19): 从 pipeline_runner.py 抽出, 单一职责.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from contextlib import contextmanager
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import TYPE_CHECKING, Optional
|
|
21
|
+
|
|
22
|
+
from QuantNodes.core.evolution import (
|
|
23
|
+
EvolutionLoop,
|
|
24
|
+
EvolutionResult,
|
|
25
|
+
EvolutionSetting,
|
|
26
|
+
FactorCandidate,
|
|
27
|
+
)
|
|
28
|
+
from QuantNodes.core.feedback import FactorFeedback
|
|
29
|
+
from QuantNodes.core.monitoring import MetricCollector, generate_dashboard_html
|
|
30
|
+
from QuantNodes.core.parallel.worker_process import prepare_snapshot
|
|
31
|
+
from QuantNodes.core.quality_gate import (
|
|
32
|
+
FactorZoo,
|
|
33
|
+
QualityGateNode,
|
|
34
|
+
QualityGateSetting,
|
|
35
|
+
)
|
|
36
|
+
from QuantNodes.core.trajectory import TrajectoryPool
|
|
37
|
+
from QuantNodes.research.factor_test.utils.metrics_extractor import (
|
|
38
|
+
extract_metrics_from_ctx,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
if TYPE_CHECKING:
|
|
44
|
+
from QuantNodes.research.factor_test.pipeline_runner import PipelineRunner
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def build_quality_gate(runner: "PipelineRunner") -> Optional[QualityGateNode]:
|
|
48
|
+
"""根据 ``cfg.quality_gate.enabled`` 构造 ``QualityGateNode``."""
|
|
49
|
+
if not runner.config.quality_gate.enabled:
|
|
50
|
+
return None
|
|
51
|
+
zoo_path = (
|
|
52
|
+
Path(runner.config.quality_gate.zoo_path)
|
|
53
|
+
if runner.config.quality_gate.zoo_path
|
|
54
|
+
else None
|
|
55
|
+
)
|
|
56
|
+
zoo = FactorZoo(zoo_path) if zoo_path is not None else FactorZoo()
|
|
57
|
+
return QualityGateNode(QualityGateSetting(), zoo=zoo)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def build_trajectory_pool(runner: "PipelineRunner") -> Optional[TrajectoryPool]:
|
|
61
|
+
"""根据 ``cfg.evolution.enabled`` 构造 ``TrajectoryPool``."""
|
|
62
|
+
if not runner.config.evolution.enabled:
|
|
63
|
+
return None
|
|
64
|
+
if runner.config.evolution.pool_dir:
|
|
65
|
+
base = Path(runner.config.evolution.pool_dir)
|
|
66
|
+
else:
|
|
67
|
+
base = Path(runner.config.output.dir) / "trajectory"
|
|
68
|
+
return TrajectoryPool(base)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_evolution_loop(
|
|
72
|
+
runner: "PipelineRunner",
|
|
73
|
+
pool: TrajectoryPool,
|
|
74
|
+
quality_gate: Optional[QualityGateNode],
|
|
75
|
+
workers: int = 1,
|
|
76
|
+
) -> EvolutionLoop:
|
|
77
|
+
"""构造 ``EvolutionLoop``, evaluate_fn 委托给 ``runner._evaluate_candidate``."""
|
|
78
|
+
settings = EvolutionSetting(
|
|
79
|
+
enabled=True,
|
|
80
|
+
max_rounds=runner.config.evolution.max_rounds,
|
|
81
|
+
parents_per_round=runner.config.evolution.parents_per_round,
|
|
82
|
+
parent_selection_strategy=runner.config.evolution.parent_selection_strategy,
|
|
83
|
+
top_percent_threshold=runner.config.evolution.top_percent_threshold,
|
|
84
|
+
metric=runner.config.evolution.metric,
|
|
85
|
+
early_stop_patience=runner.config.evolution.early_stop_patience,
|
|
86
|
+
)
|
|
87
|
+
return EvolutionLoop(
|
|
88
|
+
settings=settings,
|
|
89
|
+
pool=pool,
|
|
90
|
+
quality_gate=quality_gate,
|
|
91
|
+
evaluate_fn=runner._evaluate_candidate,
|
|
92
|
+
workers=workers,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def evaluate_candidate(
|
|
97
|
+
runner: "PipelineRunner",
|
|
98
|
+
candidate: FactorCandidate,
|
|
99
|
+
) -> tuple[bool, dict, FactorFeedback]:
|
|
100
|
+
"""``EvolutionLoop`` 评估回调: 单次回测 + metrics + feedback.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
``(passed, metrics, feedback)``
|
|
104
|
+
"""
|
|
105
|
+
if not isinstance(candidate, FactorCandidate):
|
|
106
|
+
raise TypeError(f"expected FactorCandidate, got {type(candidate)}")
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
ctx = run_one_factor(runner, candidate)
|
|
110
|
+
except Exception as e: # noqa: BLE001
|
|
111
|
+
return False, {}, FactorFeedback(
|
|
112
|
+
factor_id=candidate.factor_id,
|
|
113
|
+
factor_name=candidate.name,
|
|
114
|
+
decision=False,
|
|
115
|
+
summary=f"evaluate failed: {e}",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
passed = bool(ctx.get("status") != "rejected")
|
|
119
|
+
metrics = extract_metrics_from_ctx(ctx)
|
|
120
|
+
factor_id = str(candidate.factor_id)
|
|
121
|
+
factor_name = str(candidate.name)
|
|
122
|
+
feedback = FactorFeedback(
|
|
123
|
+
factor_id=factor_id,
|
|
124
|
+
factor_name=factor_name,
|
|
125
|
+
decision=passed,
|
|
126
|
+
summary="ok" if passed else "rejected",
|
|
127
|
+
metadata=metrics,
|
|
128
|
+
)
|
|
129
|
+
if runner.config.feedback.enabled and "Feedback" in ctx:
|
|
130
|
+
for node_fb in ctx["Feedback"].values():
|
|
131
|
+
for ch, ch_fb in node_fb.channels.items():
|
|
132
|
+
feedback.channels[ch] = ch_fb
|
|
133
|
+
return passed, metrics, feedback
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def run_one_factor(runner: "PipelineRunner", candidate: FactorCandidate) -> dict:
|
|
137
|
+
"""执行单次回测 (12 节点), 临时把 candidate 的 expression / name 注入 ``cfg.factor``.
|
|
138
|
+
|
|
139
|
+
不写 TrajectoryPool, 仅返回 ctx.
|
|
140
|
+
"""
|
|
141
|
+
if not isinstance(candidate, FactorCandidate):
|
|
142
|
+
raise TypeError(f"expected FactorCandidate, got {type(candidate)}")
|
|
143
|
+
with override_factor_config(runner, candidate):
|
|
144
|
+
return runner.run()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@contextmanager
|
|
148
|
+
def override_factor_config(runner: "PipelineRunner", candidate: FactorCandidate):
|
|
149
|
+
"""Temporarily replace runner.config.factor.name (and expression if empty).
|
|
150
|
+
|
|
151
|
+
Phase J5 (2026-06-20): extracts the mutate-and-restore pattern from
|
|
152
|
+
run_one_factor() into a reusable context manager. Future callers
|
|
153
|
+
that need the same override (e.g. CLI flags, parallel workers) can
|
|
154
|
+
reuse without re-implementing the cleanup logic.
|
|
155
|
+
"""
|
|
156
|
+
cfg = runner.config.factor
|
|
157
|
+
orig_name = cfg.name
|
|
158
|
+
cfg.name = candidate.name
|
|
159
|
+
if not getattr(cfg, "expression", ""):
|
|
160
|
+
try:
|
|
161
|
+
cfg.expression = candidate.expression # type: ignore[attr-defined]
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
try:
|
|
165
|
+
yield
|
|
166
|
+
finally:
|
|
167
|
+
cfg.name = orig_name
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def run_evolution(
|
|
171
|
+
runner: "PipelineRunner",
|
|
172
|
+
initial_directions: list[str] | None = None,
|
|
173
|
+
initial_candidates: list[FactorCandidate] | None = None,
|
|
174
|
+
workers: int = 1,
|
|
175
|
+
) -> EvolutionResult:
|
|
176
|
+
"""多轮演化主入口.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
runner: ``PipelineRunner`` 实例
|
|
180
|
+
initial_directions: round 0 用的研究假设列表 (Hypothesizer 处理)
|
|
181
|
+
initial_candidates: round 0 用的直接候选 (跳过 Hypothesizer)
|
|
182
|
+
workers: 并行数 (1=串行, >1=ThreadPool/ProcessPool 并行)
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
``EvolutionResult``: best entries + 统计
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
ValueError: ``cfg.evolution.enabled=False``
|
|
189
|
+
"""
|
|
190
|
+
if not runner.config.evolution.enabled:
|
|
191
|
+
raise ValueError("config.evolution.enabled=False, 无法运行演化")
|
|
192
|
+
|
|
193
|
+
pool = build_trajectory_pool(runner)
|
|
194
|
+
quality_gate = build_quality_gate(runner)
|
|
195
|
+
loop = build_evolution_loop(runner, pool, quality_gate, workers=workers)
|
|
196
|
+
|
|
197
|
+
# workers > 1 + 有 _loader → 预序列化供 ProcessPool
|
|
198
|
+
if workers > 1 and "_loader" in runner._context.get("LoadData", {}):
|
|
199
|
+
snapshot = prepare_snapshot(
|
|
200
|
+
runner.config, runner._context,
|
|
201
|
+
factor_path=getattr(runner.config.factor, "factor_dir", None),
|
|
202
|
+
)
|
|
203
|
+
snap_path = Path(pool.base_dir) / "_snapshot.pkl"
|
|
204
|
+
snapshot.save(snap_path)
|
|
205
|
+
loop.snapshot_path = str(snap_path)
|
|
206
|
+
logger.info(" [ProcessPool] 预序列化快照: %s", snap_path)
|
|
207
|
+
|
|
208
|
+
# 连接 MetricCollector (streaming)
|
|
209
|
+
collector = MetricCollector()
|
|
210
|
+
loop.metric_collector = collector
|
|
211
|
+
|
|
212
|
+
result = loop.run(
|
|
213
|
+
initial_directions=initial_directions,
|
|
214
|
+
initial_candidates=initial_candidates,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# 演化结束后, 追加写入 JSON + 生成 streaming dashboard
|
|
218
|
+
if pool.size > 0:
|
|
219
|
+
metrics_json = pool.base_dir / "metrics.json"
|
|
220
|
+
collector.append_json(metrics_json)
|
|
221
|
+
logger.info(" [Streaming] 指标追加: %s", metrics_json)
|
|
222
|
+
dashboard_html = pool.base_dir.parent / "dashboard_streaming.html"
|
|
223
|
+
generate_dashboard_html(
|
|
224
|
+
collector,
|
|
225
|
+
title=f"演化 Dashboard (Streaming): {runner.config.factor.name}",
|
|
226
|
+
output_path=str(dashboard_html),
|
|
227
|
+
streaming=True,
|
|
228
|
+
)
|
|
229
|
+
logger.info(" [Streaming] Dashboard: %s", dashboard_html)
|
|
230
|
+
|
|
231
|
+
return result
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
"""FactorFeedback 包装器 / Feedback Wrapper.
|
|
3
|
+
|
|
4
|
+
把 5 个分析节点 (IC/Group/LongShort/Score/RiskCorrelation) 的返回值
|
|
5
|
+
统一包装成 ``FactorFeedback`` 对象, 聚合到 ``ctx['Feedback']``,
|
|
6
|
+
并可选持久化到 Parquet.
|
|
7
|
+
|
|
8
|
+
Phase R2 (2026-06-19): 从 pipeline_runner.py 抽出, 单一职责.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Optional
|
|
15
|
+
|
|
16
|
+
from QuantNodes.core.feedback import (
|
|
17
|
+
FactorFeedback,
|
|
18
|
+
FeedbackChannel,
|
|
19
|
+
FeedbackCollector,
|
|
20
|
+
LLMJudge,
|
|
21
|
+
ensure_feedback,
|
|
22
|
+
)
|
|
23
|
+
from QuantNodes.core.path_utils import ensure_dir
|
|
24
|
+
from QuantNodes.research.factor_test.config import SingleFactorTestConfig
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
ANALYSIS_NODES: tuple[str, ...] = (
|
|
28
|
+
"ICAnalyzer", "GroupAnalyzer", "LongShort", "FactorScore", "RiskCorrelation",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def build_feedback(
|
|
33
|
+
ctx: dict,
|
|
34
|
+
factor_id: str,
|
|
35
|
+
factor_name: str,
|
|
36
|
+
cfg: SingleFactorTestConfig,
|
|
37
|
+
judge: Optional[LLMJudge],
|
|
38
|
+
) -> dict[str, FactorFeedback]:
|
|
39
|
+
"""包装 5 个分析节点返回值为 FactorFeedback.
|
|
40
|
+
|
|
41
|
+
包装策略:
|
|
42
|
+
- 节点返回 dict → ``ensure_feedback()`` 创建 metadata-only FactorFeedback
|
|
43
|
+
- 节点已是 FactorFeedback → 直接用其 channels
|
|
44
|
+
- 都通过同一个 FeedbackCollector 聚合, 共享 ``factor_id``
|
|
45
|
+
- ``judge`` 不为 None 时追加 LLM 一致性通道
|
|
46
|
+
"""
|
|
47
|
+
feedbacks: dict[str, FactorFeedback] = {}
|
|
48
|
+
for node_name in ANALYSIS_NODES:
|
|
49
|
+
result = ctx.get(node_name)
|
|
50
|
+
if result is None:
|
|
51
|
+
continue
|
|
52
|
+
collector = FeedbackCollector(factor_id, factor_name)
|
|
53
|
+
fb = ensure_feedback(result, factor_id, factor_name)
|
|
54
|
+
for _ch, ch_fb in fb.channels.items():
|
|
55
|
+
collector.add_feedback(ch_fb)
|
|
56
|
+
if not fb.channels:
|
|
57
|
+
collector.add(
|
|
58
|
+
channel=FeedbackChannel.VALUE,
|
|
59
|
+
passed=fb.decision,
|
|
60
|
+
detail=fb.summary or f"{node_name} 节点无显式通道反馈",
|
|
61
|
+
score=1.0 if fb.decision else 0.0,
|
|
62
|
+
)
|
|
63
|
+
if judge is not None:
|
|
64
|
+
hypothesis = getattr(cfg.factor, "hypothesis", "") or ""
|
|
65
|
+
description = getattr(cfg.factor, "description", "") or ""
|
|
66
|
+
expression = getattr(cfg.factor, "expression", "") or ""
|
|
67
|
+
if hypothesis or description or expression:
|
|
68
|
+
llm_fb = judge.judge(hypothesis, description, expression)
|
|
69
|
+
collector.add_feedback(llm_fb)
|
|
70
|
+
feedbacks[node_name] = collector.finalize(
|
|
71
|
+
summary=fb.summary or f"{node_name} 节点执行完成",
|
|
72
|
+
)
|
|
73
|
+
return feedbacks
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def maybe_persist_feedback(
|
|
77
|
+
feedbacks: dict[str, FactorFeedback],
|
|
78
|
+
cfg: SingleFactorTestConfig,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""可选: 持久化 Feedback 到 Parquet.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
feedbacks: ``build_feedback`` 返回的 dict
|
|
84
|
+
cfg: SingleFactorTestConfig, 需 ``cfg.feedback.output_dir`` 不为 None
|
|
85
|
+
"""
|
|
86
|
+
if cfg.feedback.output_dir is None:
|
|
87
|
+
return
|
|
88
|
+
out = Path(cfg.feedback.output_dir)
|
|
89
|
+
ensure_dir(out)
|
|
90
|
+
parquet_path = out / "feedback.parquet"
|
|
91
|
+
for node_name, fb in feedbacks.items():
|
|
92
|
+
fb.save_parquet(parquet_path)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def maybe_build_judge(cfg: SingleFactorTestConfig) -> Optional[LLMJudge]:
|
|
96
|
+
"""若 ``cfg.feedback.judge_enabled`` 为 True, 构建 LLMJudge; 否则 None."""
|
|
97
|
+
if not cfg.feedback.judge_enabled:
|
|
98
|
+
return None
|
|
99
|
+
return LLMJudge(
|
|
100
|
+
model=cfg.feedback.judge_model,
|
|
101
|
+
max_correction_attempts=cfg.feedback.judge_max_attempts,
|
|
102
|
+
)
|