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,911 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
"""
|
|
3
|
+
alpha_gpt.py - AlphaGptWorkflow 协调器(M5 核心)
|
|
4
|
+
|
|
5
|
+
5 智能体编排 + 5 轮迭代主循环:
|
|
6
|
+
1. spawn alpha-gpt-idea-generator → ideas
|
|
7
|
+
2. spawn alpha-gpt-formula-translator → formulas
|
|
8
|
+
3. spawn alpha-gpt-evaluator → evaluations
|
|
9
|
+
4. spawn alpha-gpt-reflector → verdicts + suggestions
|
|
10
|
+
5. spawn alpha-gpt-critic (仅末轮) → final_pool
|
|
11
|
+
|
|
12
|
+
LLM 调用复用 nanobot upstream(见 .agent/agents/alpha-gpt-*.md),
|
|
13
|
+
不引入新 LLM provider。workflow 只负责状态管理和 spawn 协调。
|
|
14
|
+
|
|
15
|
+
Usage::
|
|
16
|
+
|
|
17
|
+
from QuantNodes.research.quant_alpha.workflow import (
|
|
18
|
+
AlphaGptWorkflow, AlphaGptConfig,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
config = AlphaGptConfig(
|
|
22
|
+
objective="捕捉 A 股反转效应",
|
|
23
|
+
iterations=5,
|
|
24
|
+
pool_size=10,
|
|
25
|
+
llm_provider="deepseek",
|
|
26
|
+
)
|
|
27
|
+
workflow = AlphaGptWorkflow(config=config, data=df)
|
|
28
|
+
result = workflow.run()
|
|
29
|
+
|
|
30
|
+
for f in result.final_pool:
|
|
31
|
+
print(f.formula, f.ir)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
from dataclasses import dataclass, field
|
|
38
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
|
|
42
|
+
from .state import (
|
|
43
|
+
AlphaGptState,
|
|
44
|
+
IdeaRecord,
|
|
45
|
+
FormulaRecord,
|
|
46
|
+
EvaluationRecord,
|
|
47
|
+
ReflectionRecord,
|
|
48
|
+
FinalFormulaRecord,
|
|
49
|
+
)
|
|
50
|
+
from ..llm.parser import (
|
|
51
|
+
parse_idea_generator_output,
|
|
52
|
+
parse_formula_translator_output,
|
|
53
|
+
parse_evaluator_output,
|
|
54
|
+
parse_reflector_output,
|
|
55
|
+
parse_critic_output,
|
|
56
|
+
validate_formula_operators,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
logger = logging.getLogger(__name__)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class AlphaGptConfig:
|
|
64
|
+
"""Alpha-GPT 工作流配置"""
|
|
65
|
+
|
|
66
|
+
objective: str
|
|
67
|
+
iterations: int = 5
|
|
68
|
+
pool_size: int = 10
|
|
69
|
+
top_k: int = 10
|
|
70
|
+
min_ir_threshold: float = 0.5
|
|
71
|
+
max_mutual_ic_threshold: float = 0.7
|
|
72
|
+
|
|
73
|
+
forward_returns: Sequence[int] = (1, 5, 20)
|
|
74
|
+
date_column: str = "date"
|
|
75
|
+
code_column: str = "code"
|
|
76
|
+
|
|
77
|
+
llm_provider: str = "deepseek"
|
|
78
|
+
llm_model: Optional[str] = None
|
|
79
|
+
temperature: float = 0.7
|
|
80
|
+
|
|
81
|
+
# 各阶段温度参数(覆盖 temperature)
|
|
82
|
+
temperature_idea_gen: float = 0.8 # 鼓励创新
|
|
83
|
+
temperature_formula: float = 0.4 # 需要精确
|
|
84
|
+
temperature_reflector: float = 0.6 # 平衡
|
|
85
|
+
temperature_critic: float = 0.3 # 需要稳定
|
|
86
|
+
|
|
87
|
+
spawn_timeout_seconds: float = 30.0
|
|
88
|
+
|
|
89
|
+
a_share_focus: bool = True
|
|
90
|
+
enable_backtest: bool = False
|
|
91
|
+
top_k_backtest: int = 10
|
|
92
|
+
|
|
93
|
+
custom_few_shot: Optional[List[Dict[str, Any]]] = None
|
|
94
|
+
|
|
95
|
+
# 自定义反馈(用于多轮迭代,注入到 IdeaGenerator)
|
|
96
|
+
custom_feedback: Optional[str] = None
|
|
97
|
+
|
|
98
|
+
# Γ 约束(用于逻辑驱动因子生成)
|
|
99
|
+
gamma: Optional[Any] = None # CompiledConstraint from logic_mining.compiler
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@dataclass
|
|
103
|
+
class AlphaGptResult:
|
|
104
|
+
"""Alpha-GPT 工作流最终结果"""
|
|
105
|
+
|
|
106
|
+
objective: str
|
|
107
|
+
iterations_completed: int
|
|
108
|
+
total_formulas: int
|
|
109
|
+
final_pool: List[FinalFormulaRecord] = field(default_factory=list)
|
|
110
|
+
summary: Dict[str, Any] = field(default_factory=dict)
|
|
111
|
+
elapsed_seconds: float = 0.0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class AlphaGptWorkflow:
|
|
115
|
+
"""Alpha-GPT 工作流协调器
|
|
116
|
+
|
|
117
|
+
每轮 5 个 spawn(multi-process via nanobot upstream)。
|
|
118
|
+
支持两种运行模式:
|
|
119
|
+
|
|
120
|
+
- run(): 同步执行(mock 友好)
|
|
121
|
+
- run_stream(): 流式输出事件(生产用)
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(
|
|
125
|
+
self,
|
|
126
|
+
config: AlphaGptConfig,
|
|
127
|
+
data: Any = None,
|
|
128
|
+
data_path: Optional[str] = None,
|
|
129
|
+
llm_client: Optional[Any] = None,
|
|
130
|
+
output_dir: Optional[str] = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
self.config = config
|
|
133
|
+
self.data = data
|
|
134
|
+
self.data_path = data_path
|
|
135
|
+
# llm_client=None → 使用 _mock_llm_response (Stage 1)
|
|
136
|
+
# llm_client=LLMGateway → 使用真实 LLM (Stage 2)
|
|
137
|
+
self.llm_client = llm_client
|
|
138
|
+
# 完整保存 LLM 原始输出(用于调试截断/解析失败)
|
|
139
|
+
self.output_dir = output_dir
|
|
140
|
+
self._llm_raw_dir: Optional[Any] = None
|
|
141
|
+
if output_dir:
|
|
142
|
+
from pathlib import Path
|
|
143
|
+
self._llm_raw_dir = Path(output_dir) / "llm_raw"
|
|
144
|
+
self._llm_raw_dir.mkdir(parents=True, exist_ok=True)
|
|
145
|
+
self.state = AlphaGptState(
|
|
146
|
+
objective=config.objective,
|
|
147
|
+
iterations_total=config.iterations,
|
|
148
|
+
)
|
|
149
|
+
self._cache_evaluator_results: Dict[str, EvaluationRecord] = {}
|
|
150
|
+
|
|
151
|
+
def run(self) -> AlphaGptResult:
|
|
152
|
+
"""同步执行工作流"""
|
|
153
|
+
import time
|
|
154
|
+
|
|
155
|
+
start = time.time()
|
|
156
|
+
for round_idx in range(1, self.config.iterations + 1):
|
|
157
|
+
logger.info("Round %d/%d starting", round_idx, self.config.iterations)
|
|
158
|
+
self._run_one_round(round_idx)
|
|
159
|
+
if round_idx == self.config.iterations:
|
|
160
|
+
self._run_critic()
|
|
161
|
+
|
|
162
|
+
final_pool = self._select_final_pool()
|
|
163
|
+
elapsed = time.time() - start
|
|
164
|
+
|
|
165
|
+
summary = self._build_summary(final_pool)
|
|
166
|
+
return AlphaGptResult(
|
|
167
|
+
objective=self.config.objective,
|
|
168
|
+
iterations_completed=self.config.iterations,
|
|
169
|
+
total_formulas=len(self.state.all_formulas),
|
|
170
|
+
final_pool=final_pool,
|
|
171
|
+
summary=summary,
|
|
172
|
+
elapsed_seconds=elapsed,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
def _run_one_round(self, round_idx: int) -> None:
|
|
176
|
+
"""一轮完整 5 步骤(除 critic)"""
|
|
177
|
+
self.state.round_idx_hint = round_idx
|
|
178
|
+
ideas = self._step_idea_generator(round_idx)
|
|
179
|
+
self.state.all_ideas.extend(ideas)
|
|
180
|
+
|
|
181
|
+
formulas = self._step_formula_translator(round_idx, ideas)
|
|
182
|
+
self.state.all_formulas.extend(formulas)
|
|
183
|
+
|
|
184
|
+
evaluations = self._step_evaluator(round_idx, formulas)
|
|
185
|
+
self.state.all_evaluations.extend(evaluations)
|
|
186
|
+
|
|
187
|
+
if round_idx < self.config.iterations:
|
|
188
|
+
reflection = self._step_reflector(round_idx, evaluations)
|
|
189
|
+
self.state.all_reflections.append(reflection)
|
|
190
|
+
|
|
191
|
+
def _step_idea_generator(self, round_idx: int) -> List[IdeaRecord]:
|
|
192
|
+
"""Step 1: spawn idea-generator"""
|
|
193
|
+
prev_reflection = (
|
|
194
|
+
self.state.all_reflections[-1].to_dict()
|
|
195
|
+
if self.state.all_reflections
|
|
196
|
+
else None
|
|
197
|
+
)
|
|
198
|
+
prompt = self._build_idea_prompt(round_idx, prev_reflection)
|
|
199
|
+
raw, thinking = self._call_llm("alpha-gpt-idea-generator", prompt)
|
|
200
|
+
parsed = parse_idea_generator_output(raw)
|
|
201
|
+
if not parsed.ok:
|
|
202
|
+
logger.warning("idea-generator parse failed: %s", parsed.error)
|
|
203
|
+
return []
|
|
204
|
+
data = parsed.data or {}
|
|
205
|
+
ideas_data = data.get("ideas", [])[: self.config.pool_size]
|
|
206
|
+
|
|
207
|
+
# Tier 1+2: 解析 thinking → ThinkingRecord
|
|
208
|
+
thinking_record = None
|
|
209
|
+
if thinking:
|
|
210
|
+
from QuantNodes.research.quant_alpha.llm.parser import parse_thinking_block
|
|
211
|
+
op_vocab = set(self._get_available_operators())
|
|
212
|
+
thinking_record = parse_thinking_block(thinking, op_vocab=op_vocab)
|
|
213
|
+
|
|
214
|
+
ideas = []
|
|
215
|
+
for i in ideas_data:
|
|
216
|
+
idea = IdeaRecord.from_dict(i, round_idx)
|
|
217
|
+
if thinking:
|
|
218
|
+
idea.thinking = thinking
|
|
219
|
+
if thinking_record:
|
|
220
|
+
idea.hypothesis = thinking_record.hypothesis or None
|
|
221
|
+
idea.mechanism = thinking_record.mechanism or None
|
|
222
|
+
idea.mentioned_ops = list(thinking_record.mentioned_ops)
|
|
223
|
+
ideas.append(idea)
|
|
224
|
+
return ideas
|
|
225
|
+
|
|
226
|
+
def _step_formula_translator(
|
|
227
|
+
self,
|
|
228
|
+
round_idx: int,
|
|
229
|
+
ideas: List[IdeaRecord],
|
|
230
|
+
) -> List[FormulaRecord]:
|
|
231
|
+
"""Step 2: spawn formula-translator"""
|
|
232
|
+
if not ideas:
|
|
233
|
+
return []
|
|
234
|
+
available_ops = self._get_available_operators()
|
|
235
|
+
data_columns = self._get_data_columns()
|
|
236
|
+
prev_ideas = self._serialize_ideas_for_translator(ideas)
|
|
237
|
+
prompt = self._build_formula_prompt(round_idx, prev_ideas, available_ops, data_columns)
|
|
238
|
+
raw, thinking = self._call_llm("alpha-gpt-formula-translator", prompt)
|
|
239
|
+
parsed = parse_formula_translator_output(raw)
|
|
240
|
+
if not parsed.ok:
|
|
241
|
+
logger.warning("formula-translator parse failed: %s", parsed.error)
|
|
242
|
+
return []
|
|
243
|
+
data = parsed.data or {}
|
|
244
|
+
formulas_data = data.get("formulas", [])
|
|
245
|
+
|
|
246
|
+
# Tier 1+2: 解析 thinking
|
|
247
|
+
thinking_record = None
|
|
248
|
+
if thinking:
|
|
249
|
+
from QuantNodes.research.quant_alpha.llm.parser import parse_thinking_block
|
|
250
|
+
thinking_record = parse_thinking_block(thinking, op_vocab=set(available_ops))
|
|
251
|
+
|
|
252
|
+
# 共享 thinking 给所有 formulas(同一轮 translator 调用)
|
|
253
|
+
shared_thinking = thinking or None
|
|
254
|
+
shared_hypothesis = (thinking_record.hypothesis if thinking_record else "") or None
|
|
255
|
+
shared_mentioned_ops = (
|
|
256
|
+
list(thinking_record.mentioned_ops) if thinking_record else []
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
result = []
|
|
260
|
+
for i, fd in enumerate(formulas_data):
|
|
261
|
+
formula_str = fd.get("formula", "")
|
|
262
|
+
err = validate_formula_operators(formula_str)
|
|
263
|
+
if err:
|
|
264
|
+
logger.info("formula op-validation warning (will try anyway): %s (%s)", formula_str, err)
|
|
265
|
+
|
|
266
|
+
# Γ 约束校验
|
|
267
|
+
if self.config.gamma is not None:
|
|
268
|
+
passed, reason = self.config.gamma.validate(formula_str)
|
|
269
|
+
if not passed:
|
|
270
|
+
logger.info("Γ 校验失败,丢弃公式: %s - %s", formula_str, reason)
|
|
271
|
+
continue
|
|
272
|
+
|
|
273
|
+
formula_record = FormulaRecord(
|
|
274
|
+
formula_id=f"FORMULA-{round_idx}-{i+1}",
|
|
275
|
+
idea_id=fd.get("idea_id", ""),
|
|
276
|
+
formula=formula_str,
|
|
277
|
+
round_discovered=round_idx,
|
|
278
|
+
complexity=fd.get("complexity", 0),
|
|
279
|
+
a_share_compatible=fd.get("a_share_compatible", True),
|
|
280
|
+
)
|
|
281
|
+
if shared_thinking:
|
|
282
|
+
formula_record.thinking = shared_thinking
|
|
283
|
+
formula_record.hypothesis = shared_hypothesis
|
|
284
|
+
formula_record.mentioned_ops = shared_mentioned_ops
|
|
285
|
+
result.append(formula_record)
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
def _step_evaluator(
|
|
289
|
+
self,
|
|
290
|
+
round_idx: int,
|
|
291
|
+
formulas: List[FormulaRecord],
|
|
292
|
+
) -> List[EvaluationRecord]:
|
|
293
|
+
"""Step 3: spawn evaluator(同步调用 alpha_evaluate 工具)
|
|
294
|
+
|
|
295
|
+
不真走 nanobot spawn(那是框架级)。直接用 alpha_evaluate tool 评估。
|
|
296
|
+
"""
|
|
297
|
+
if not formulas:
|
|
298
|
+
return []
|
|
299
|
+
try:
|
|
300
|
+
from QuantNodes.agent.tools.alpha_evaluate import AlphaEvaluateTool
|
|
301
|
+
from QuantNodes.agent.tools.alpha_backtest import AlphaBacktestTool
|
|
302
|
+
|
|
303
|
+
tool = AlphaEvaluateTool()
|
|
304
|
+
formulas_str = [f.formula for f in formulas]
|
|
305
|
+
result = _run_async(
|
|
306
|
+
tool.execute(
|
|
307
|
+
formulas=formulas_str,
|
|
308
|
+
data=self.data,
|
|
309
|
+
data_path=self.data_path,
|
|
310
|
+
forward_returns=list(self.config.forward_returns),
|
|
311
|
+
date_column=self.config.date_column,
|
|
312
|
+
code_column=self.config.code_column,
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
except Exception as exc:
|
|
316
|
+
logger.exception("alpha_evaluate tool failed")
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
evals_data = result.get("evaluations", [])
|
|
320
|
+
out: List[EvaluationRecord] = []
|
|
321
|
+
for fd, ed in zip(formulas, evals_data):
|
|
322
|
+
if ed.get("status") == "success":
|
|
323
|
+
metrics = ed.get("metrics", {})
|
|
324
|
+
out.append(
|
|
325
|
+
EvaluationRecord(
|
|
326
|
+
formula_id=fd.formula_id,
|
|
327
|
+
formula=fd.formula,
|
|
328
|
+
status="success",
|
|
329
|
+
ic_mean=metrics.get("ic_mean", 0.0),
|
|
330
|
+
ic_std=metrics.get("ic_std", 0.0),
|
|
331
|
+
ir=metrics.get("ir", 0.0),
|
|
332
|
+
ic_decay=metrics.get("ic_decay", {}),
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
else:
|
|
336
|
+
out.append(
|
|
337
|
+
EvaluationRecord(
|
|
338
|
+
formula_id=fd.formula_id,
|
|
339
|
+
formula=fd.formula,
|
|
340
|
+
status="failed",
|
|
341
|
+
error_msg=ed.get("error_msg", ""),
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
return out
|
|
345
|
+
|
|
346
|
+
def _step_reflector(
|
|
347
|
+
self,
|
|
348
|
+
round_idx: int,
|
|
349
|
+
evaluations: List[EvaluationRecord],
|
|
350
|
+
) -> ReflectionRecord:
|
|
351
|
+
"""Step 4: spawn reflector"""
|
|
352
|
+
evals_dict = [e.to_dict() for e in evaluations]
|
|
353
|
+
prompt = self._build_reflector_prompt(round_idx, evals_dict)
|
|
354
|
+
raw, thinking = self._call_llm("alpha-gpt-reflector", prompt)
|
|
355
|
+
parsed = parse_reflector_output(raw)
|
|
356
|
+
if not parsed.ok:
|
|
357
|
+
logger.warning("reflector parse failed: %s", parsed.error)
|
|
358
|
+
return ReflectionRecord(
|
|
359
|
+
round_idx=round_idx,
|
|
360
|
+
verdicts=[],
|
|
361
|
+
suggestions={},
|
|
362
|
+
)
|
|
363
|
+
data = parsed.data or {}
|
|
364
|
+
record = ReflectionRecord(
|
|
365
|
+
round_idx=round_idx,
|
|
366
|
+
verdicts=data.get("formula_feedback", []),
|
|
367
|
+
suggestions=data.get("next_round_suggestions", {}),
|
|
368
|
+
)
|
|
369
|
+
# Tier 1+2: 提取 insights
|
|
370
|
+
if thinking:
|
|
371
|
+
record.thinking = thinking
|
|
372
|
+
analysis = data.get("analysis", {})
|
|
373
|
+
key_insights = analysis.get("key_insights", [])
|
|
374
|
+
if isinstance(key_insights, list):
|
|
375
|
+
record.key_insights = [str(s) for s in key_insights]
|
|
376
|
+
return record
|
|
377
|
+
|
|
378
|
+
def _run_critic(self) -> None:
|
|
379
|
+
"""Step 5: spawn critic(仅末轮)"""
|
|
380
|
+
all_evals = [e.to_dict() for e in self.state.all_evaluations]
|
|
381
|
+
all_refl = [r.to_dict() for r in self.state.all_reflections]
|
|
382
|
+
prompt = self._build_critic_prompt(all_evals, all_refl)
|
|
383
|
+
raw, _thinking = self._call_llm("alpha-gpt-critic", prompt)
|
|
384
|
+
parsed = parse_critic_output(raw)
|
|
385
|
+
if not parsed.ok:
|
|
386
|
+
logger.warning("critic parse failed: %s", parsed.error)
|
|
387
|
+
return
|
|
388
|
+
data = parsed.data or {}
|
|
389
|
+
self.state.critic_output = data
|
|
390
|
+
|
|
391
|
+
def _select_final_pool(self) -> List[FinalFormulaRecord]:
|
|
392
|
+
"""从 evaluations 中选 top-K(代码方式,不依赖 critic LLM)
|
|
393
|
+
|
|
394
|
+
直接从所有成功评估中按 IR 排序选 top-K,然后做互信息去重。
|
|
395
|
+
"""
|
|
396
|
+
# 直接从 evaluations 排序(不依赖 critic LLM)
|
|
397
|
+
successful = [e for e in self.state.all_evaluations if e.status == "success"]
|
|
398
|
+
successful.sort(key=lambda e: abs(e.ir), reverse=True)
|
|
399
|
+
top = successful[: self.config.top_k]
|
|
400
|
+
logger.info("[_select_final_pool] %d successful, %d top selected", len(successful), len(top))
|
|
401
|
+
final_pool = [
|
|
402
|
+
FinalFormulaRecord(
|
|
403
|
+
rank=i + 1,
|
|
404
|
+
formula_id=e.formula_id,
|
|
405
|
+
formula=e.formula,
|
|
406
|
+
ic_mean=e.ic_mean,
|
|
407
|
+
ir=e.ir,
|
|
408
|
+
round_discovered=int(e.formula_id.split("-")[1]) if "-" in e.formula_id else 0,
|
|
409
|
+
selection_reason=f"IR={e.ir:.3f} (auto-selected by code)",
|
|
410
|
+
risk_notes=[],
|
|
411
|
+
)
|
|
412
|
+
for i, e in enumerate(top)
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
logger.info("[_select_final_pool] before dedup: %d formulas", len(final_pool))
|
|
416
|
+
|
|
417
|
+
# 互信息去重
|
|
418
|
+
if self.config.max_mutual_ic_threshold < 1.0 and self.data is not None:
|
|
419
|
+
try:
|
|
420
|
+
from QuantNodes.research.quant_alpha.evaluation.evaluators.polars_evaluator import (
|
|
421
|
+
deduplicate_mutual_ic,
|
|
422
|
+
)
|
|
423
|
+
from QuantNodes.research.quant_alpha.operator_vocab import OperatorVocab
|
|
424
|
+
|
|
425
|
+
vocab = OperatorVocab.default()
|
|
426
|
+
|
|
427
|
+
def get_values(record: FactorMetrics) -> Optional[Any]:
|
|
428
|
+
try:
|
|
429
|
+
# Find the formula from final_pool
|
|
430
|
+
for r in final_pool:
|
|
431
|
+
if r.formula_id == record.formula_id:
|
|
432
|
+
return vocab.evaluate(r.formula, self.data)
|
|
433
|
+
return None
|
|
434
|
+
except Exception as e:
|
|
435
|
+
logger.debug("[_select_final_pool] eval failed for %s: %s", record.formula_id, e)
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
# 转换为 FactorMetrics 格式用于去重
|
|
439
|
+
from QuantNodes.research.quant_alpha.evaluation.contracts import FactorMetrics
|
|
440
|
+
metrics_list = [
|
|
441
|
+
FactorMetrics(
|
|
442
|
+
formula_id=r.formula_id,
|
|
443
|
+
status="success",
|
|
444
|
+
ic_mean=r.ic_mean,
|
|
445
|
+
ir=r.ir,
|
|
446
|
+
overall_score=r.ir,
|
|
447
|
+
)
|
|
448
|
+
for r in final_pool
|
|
449
|
+
]
|
|
450
|
+
|
|
451
|
+
deduped = deduplicate_mutual_ic(
|
|
452
|
+
metrics_list,
|
|
453
|
+
get_values,
|
|
454
|
+
threshold=self.config.max_mutual_ic_threshold,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
logger.info("[_select_final_pool] dedup: %d -> %d (threshold=%.2f)", len(metrics_list), len(deduped), self.config.max_mutual_ic_threshold)
|
|
458
|
+
|
|
459
|
+
# 重建 final_pool
|
|
460
|
+
deduped_ids = {m.formula_id for m in deduped}
|
|
461
|
+
final_pool = [r for r in final_pool if r.formula_id in deduped_ids]
|
|
462
|
+
|
|
463
|
+
# 重新编号
|
|
464
|
+
for i, r in enumerate(final_pool):
|
|
465
|
+
r.rank = i + 1
|
|
466
|
+
|
|
467
|
+
except Exception as e:
|
|
468
|
+
logger.warning("互信息去重失败: %s", e, exc_info=True)
|
|
469
|
+
|
|
470
|
+
logger.info("[_select_final_pool] after dedup: %d formulas", len(final_pool))
|
|
471
|
+
return final_pool
|
|
472
|
+
|
|
473
|
+
def _build_summary(
|
|
474
|
+
self, final_pool: List[FinalFormulaRecord],
|
|
475
|
+
) -> Dict[str, Any]:
|
|
476
|
+
successful = [e for e in self.state.all_evaluations if e.status == "success"]
|
|
477
|
+
irs = [e.ir for e in successful]
|
|
478
|
+
cat_dist: Dict[str, int] = {}
|
|
479
|
+
for f in final_pool:
|
|
480
|
+
cat = f.category or "unknown"
|
|
481
|
+
cat_dist[cat] = cat_dist.get(cat, 0) + 1
|
|
482
|
+
return {
|
|
483
|
+
"total_evaluated": len(self.state.all_evaluations),
|
|
484
|
+
"successful": len(successful),
|
|
485
|
+
"failed": len(self.state.all_evaluations) - len(successful),
|
|
486
|
+
"selected": len(final_pool),
|
|
487
|
+
"avg_ir": float(np.mean(irs)) if irs else 0.0,
|
|
488
|
+
"best_ir": float(np.max(irs)) if irs else 0.0,
|
|
489
|
+
"category_distribution": cat_dist,
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
# ------------------------------------------------------------------
|
|
493
|
+
# Prompt 构建
|
|
494
|
+
# ------------------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
def _build_idea_prompt(self, round_idx: int, prev_reflection: Any) -> str:
|
|
497
|
+
# P3 (fix/explanation-truncation): 使用 "rationale" 而非 "description"
|
|
498
|
+
# 避免 LLM 混淆 description 与 formula-translator 的 explanation 字段
|
|
499
|
+
schema = (
|
|
500
|
+
'{"round": 1, "ideas": ['
|
|
501
|
+
'{"id": "IDEA-1-1", "name": "20日反转", "category": "reversal", '
|
|
502
|
+
'"rationale": "经济直觉1-2句(与formula explanation区分)", "expected_direction": "long", '
|
|
503
|
+
'"suggested_lookback": 20, "a_share_compatible": true, '
|
|
504
|
+
'"orthogonal_to": ["IDEA-1-2"], "complexity_hint": "simple"}'
|
|
505
|
+
']}'
|
|
506
|
+
)
|
|
507
|
+
prompt = (
|
|
508
|
+
f"You are the Alpha-GPT IdeaGenerator. "
|
|
509
|
+
f"Generate {self.config.pool_size} alpha ideas for objective={self.config.objective!r}. "
|
|
510
|
+
f"round={round_idx}, a_share_focus={self.config.a_share_focus}. "
|
|
511
|
+
f"previous_reflection={prev_reflection}. "
|
|
512
|
+
f"6 categories: momentum/reversal/value/quality/volatility/liquidity. "
|
|
513
|
+
f"Each idea must have id (IDEA-{{round}}-{{idx}}), name, category, "
|
|
514
|
+
f"rationale (经济直觉 <150 chars, brief!), expected_direction (long/short/both), "
|
|
515
|
+
f"suggested_lookback, a_share_compatible, orthogonal_to, "
|
|
516
|
+
f"complexity_hint (simple/medium/complex). "
|
|
517
|
+
f"Output STRICT JSON (no markdown, no code blocks) matching this schema: {schema}"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
# Tier 2: 结构化推理指令(在 <think> 块中输出推理)
|
|
521
|
+
available_ops_summary = ", ".join(self._get_available_operators()[:30])
|
|
522
|
+
thinking_template = (
|
|
523
|
+
"\n\n## 推理要求 (Tier 2: Structured Reasoning)\n"
|
|
524
|
+
"在生成 JSON 之前,先在 <think> 块中按以下结构输出你的推理:\n"
|
|
525
|
+
"```\n"
|
|
526
|
+
"<think>\n"
|
|
527
|
+
"HYPOTHESIS: <一句话经济假设,如 'A 股散户过度反应导致 20 日反转'>\n"
|
|
528
|
+
"MECHANISM: <为什么在 A 股有效,提及 T+1/散户主导/涨跌停等特殊约束>\n"
|
|
529
|
+
f"OPERATOR_RATIONALE: <为什么选这些算子,从 {available_ops_summary} 等中选>\n"
|
|
530
|
+
"PARAMETER_RATIONALE: <为什么这个窗口参数,如 20 日对应学术文献经典窗口>\n"
|
|
531
|
+
"RISK: <什么情况下因子会失效,如 流动性危机 / 政策切换 / ST 股扰动>\n"
|
|
532
|
+
"SUGGESTED_OPS: <逗号分隔的算子名,如 rank,ts_mean,div>\n"
|
|
533
|
+
"</think>\n"
|
|
534
|
+
"```\n"
|
|
535
|
+
"然后输出 JSON(不要 markdown 包装)。thinking 内容会被保留用于下游 MCTS 引导。\n"
|
|
536
|
+
)
|
|
537
|
+
prompt += thinking_template
|
|
538
|
+
|
|
539
|
+
# 注入自定义反馈(用于多轮迭代)
|
|
540
|
+
if self.config.custom_feedback:
|
|
541
|
+
prompt += f"\n\n## 历史反馈(来自上一轮 MCTS 搜索)\n{self.config.custom_feedback}\n"
|
|
542
|
+
|
|
543
|
+
return prompt
|
|
544
|
+
|
|
545
|
+
def _build_formula_prompt(
|
|
546
|
+
self,
|
|
547
|
+
round_idx: int,
|
|
548
|
+
ideas_payload: List[Dict[str, Any]],
|
|
549
|
+
available_ops: List[str],
|
|
550
|
+
data_columns: List[str],
|
|
551
|
+
) -> str:
|
|
552
|
+
schema = (
|
|
553
|
+
'{"round": 1, "formulas": ['
|
|
554
|
+
'{"id": "FORMULA-1-1", "idea_id": "IDEA-1-1", '
|
|
555
|
+
'"formula": "rank(-ts_mean(returns, 20))", '
|
|
556
|
+
'"complexity": 3, "a_share_compatible": true, '
|
|
557
|
+
'"explanation": "20日反转因子"}'
|
|
558
|
+
']}'
|
|
559
|
+
)
|
|
560
|
+
prompt = (
|
|
561
|
+
f"You are the Alpha-GPT FormulaTranslator. "
|
|
562
|
+
f"Translate these ideas to polars formulas. round={round_idx}. "
|
|
563
|
+
f"ideas={ideas_payload}. "
|
|
564
|
+
f"a_share_focus={self.config.a_share_focus}. "
|
|
565
|
+
f"Each formula must have id (FORMULA-{{round}}-{{idx}}), idea_id, "
|
|
566
|
+
f"formula (function call format like op(arg1, arg2)), complexity, "
|
|
567
|
+
f"a_share_compatible, explanation. "
|
|
568
|
+
f"CRITICAL: Use ONLY function call format. NO arithmetic operators (+,-,*,/). "
|
|
569
|
+
f"NO missing parentheses. Output STRICT JSON (no markdown) matching: {schema}. "
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# Tier 2: 公式翻译也用 thinking 块说明算子选择
|
|
573
|
+
prompt += (
|
|
574
|
+
"\n\n## 推理要求 (Tier 2)\n"
|
|
575
|
+
"在 JSON 前用 <think> 块说明:\n"
|
|
576
|
+
"```\n"
|
|
577
|
+
"<think>\n"
|
|
578
|
+
"HYPOTHESIS: <该 idea 的核心经济假设,引用 idea.description>\n"
|
|
579
|
+
"MECHANISM: <公式如何捕捉该机制>\n"
|
|
580
|
+
"OPERATOR_RATIONALE: <为什么用这些算子>\n"
|
|
581
|
+
"PARAMETER_RATIONALE: <为什么用这些窗口/参数>\n"
|
|
582
|
+
"RISK: <公式的潜在失效模式>\n"
|
|
583
|
+
"SUGGESTED_OPS: <公式中实际使用的算子列表>\n"
|
|
584
|
+
"</think>\n"
|
|
585
|
+
"```\n"
|
|
586
|
+
"CRITICAL: explanation 字段必须 <80 字符!\n"
|
|
587
|
+
"禁止在 explanation 中包含 HYPOTHESIS/MECHANISM 等结构化字段。\n"
|
|
588
|
+
"explanation 只描述公式的'做什么',不解释'为什么'。\n"
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
# 注入 Γ 约束(更清晰的格式)
|
|
592
|
+
if self.config.gamma is not None:
|
|
593
|
+
gamma = self.config.gamma
|
|
594
|
+
|
|
595
|
+
# 构建约束说明
|
|
596
|
+
constraints = []
|
|
597
|
+
|
|
598
|
+
# 算子白名单
|
|
599
|
+
if gamma.operator_whitelist:
|
|
600
|
+
ops = sorted(gamma.operator_whitelist)
|
|
601
|
+
constraints.append(f"ALLOWED OPERATORS: {', '.join(ops)}")
|
|
602
|
+
constraints.append(f"You MUST use ONLY these operators. Do NOT use any other operators.")
|
|
603
|
+
|
|
604
|
+
# 变量白名单
|
|
605
|
+
if gamma.variable_whitelist:
|
|
606
|
+
vars_ = sorted(gamma.variable_whitelist)
|
|
607
|
+
constraints.append(f"ALLOWED VARIABLES: {', '.join(vars_)}")
|
|
608
|
+
constraints.append(f"You MUST use ONLY these variables. Do NOT use any other variables.")
|
|
609
|
+
|
|
610
|
+
# 参数范围
|
|
611
|
+
if gamma.parameter_ranges:
|
|
612
|
+
constraints.append(f"PARAMETER RANGES:")
|
|
613
|
+
for op, (lo, hi) in sorted(gamma.parameter_ranges.items()):
|
|
614
|
+
constraints.append(f" - {op}: window must be between {lo} and {hi}")
|
|
615
|
+
|
|
616
|
+
# 符号约束
|
|
617
|
+
if gamma.sign_constraint is not None:
|
|
618
|
+
direction = "POSITIVE (+1)" if gamma.sign_constraint > 0 else "NEGATIVE (-1)"
|
|
619
|
+
constraints.append(f"SIGN CONSTRAINT: Overall factor direction must be {direction}")
|
|
620
|
+
|
|
621
|
+
# 添加到 prompt
|
|
622
|
+
if constraints:
|
|
623
|
+
prompt += "\n\n=== Γ CONSTRAINTS (MUST FOLLOW) ===\n"
|
|
624
|
+
prompt += "\n".join(constraints)
|
|
625
|
+
prompt += "\n===================================\n"
|
|
626
|
+
|
|
627
|
+
# 添加示例
|
|
628
|
+
if gamma.operator_whitelist and "rank" in gamma.operator_whitelist and "ts_corr" in gamma.operator_whitelist:
|
|
629
|
+
prompt += "\nEXAMPLE FORMULA (follows constraints):\n"
|
|
630
|
+
prompt += " sign(-ts_corr(rank(open), rank(volume), 10))\n"
|
|
631
|
+
|
|
632
|
+
# 添加可用算子和变量(来自 OperatorVocab)
|
|
633
|
+
prompt += f"\navailable_operators={available_ops}. "
|
|
634
|
+
prompt += f"data_columns={data_columns}. "
|
|
635
|
+
|
|
636
|
+
prompt += f"Output STRICT JSON only."
|
|
637
|
+
return prompt
|
|
638
|
+
|
|
639
|
+
def _build_reflector_prompt(self, round_idx: int, evaluations: List[Dict[str, Any]]) -> str:
|
|
640
|
+
schema = (
|
|
641
|
+
'{"round": 1, "analysis": {"best_categories": ["reversal"], '
|
|
642
|
+
'"worst_categories": ["liquidity"], "key_insights": ["..."]}, '
|
|
643
|
+
'"formula_feedback": [{"formula_id": "FORMULA-1-1", "formula": "...", '
|
|
644
|
+
'"verdict": "keep", "reason": "...", "improvements": ["..."]}]}'
|
|
645
|
+
)
|
|
646
|
+
return (
|
|
647
|
+
f"You are the Alpha-GPT Reflector. "
|
|
648
|
+
f"Reflect on round {round_idx} evaluations. "
|
|
649
|
+
f"evaluations={evaluations}. "
|
|
650
|
+
f"Output STRICT JSON (no markdown) with: round, analysis (best_categories, "
|
|
651
|
+
f"worst_categories, key_insights), formula_feedback (formula_id, formula, "
|
|
652
|
+
f"verdict (keep/mutate/drop), reason, improvements). "
|
|
653
|
+
f"Schema: {schema}\n\n"
|
|
654
|
+
f"## 推理要求 (Tier 2)\n"
|
|
655
|
+
f"在 JSON 前用 <think> 块说明:\n"
|
|
656
|
+
f"<think>\n"
|
|
657
|
+
f"KEY_INSIGHTS: <3-5 条本轮核心洞察,列出成功/失败模式>\n"
|
|
658
|
+
f"NEXT_ROUND_FOCUS: <下一轮应聚焦的方向,如某类算子/参数/逻辑>\n"
|
|
659
|
+
f"RISK_PATTERNS: <本轮发现的失效模式,下轮需规避>\n"
|
|
660
|
+
f"</think>"
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
def _build_critic_prompt(
|
|
664
|
+
self, all_evaluations: List[Dict[str, Any]], all_reflections: List[Dict[str, Any]]
|
|
665
|
+
) -> str:
|
|
666
|
+
schema = (
|
|
667
|
+
'{"final_pool": [{"rank": 1, "formula_id": "FORMULA-1-1", '
|
|
668
|
+
'"formula": "...", "metrics": {"ic_mean": 0.045, "ir": 2.05, '
|
|
669
|
+
'"sharpe": 1.65, "max_drawdown": -0.123}, '
|
|
670
|
+
'"selection_reason": "...", "risk_notes": ["..."], '
|
|
671
|
+
'"category": "reversal", "round_discovered": 1}], '
|
|
672
|
+
'"summary": {"total_evaluated": 50}}'
|
|
673
|
+
)
|
|
674
|
+
return (
|
|
675
|
+
f"You are the Alpha-GPT Critic. "
|
|
676
|
+
f"Select final top-{self.config.top_k} from all rounds. "
|
|
677
|
+
f"min_ir_threshold={self.config.min_ir_threshold}. "
|
|
678
|
+
f"max_mutual_ic_threshold={self.config.max_mutual_ic_threshold}. "
|
|
679
|
+
f"all_evaluations={all_evaluations}. all_reflections={all_reflections}. "
|
|
680
|
+
f"Output STRICT JSON (no markdown) with: final_pool (rank, formula_id, "
|
|
681
|
+
f"formula, metrics (ic_mean, ir, sharpe, max_drawdown), selection_reason, "
|
|
682
|
+
f"risk_notes, category, round_discovered), summary. "
|
|
683
|
+
f"Schema: {schema}\n\n"
|
|
684
|
+
f"## 推理要求 (Tier 2)\n"
|
|
685
|
+
f"在 JSON 前用 <think> 块说明选 top-{self.config.top_k} 的标准。\n"
|
|
686
|
+
f"<think>\n"
|
|
687
|
+
f"SELECTION_CRITERIA: <你如何权衡 IR/IC/sharpe/drawdown>\n"
|
|
688
|
+
f"DIVERSITY: <如何保证 final_pool 的类别/算子多样性>\n"
|
|
689
|
+
f"RISK_FILTERS: <剔除了什么类型的因子(过拟合/高换手/低 IC decay 等)>\n"
|
|
690
|
+
f"</think>"
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# ------------------------------------------------------------------
|
|
694
|
+
# LLM 调用(mock 友好)
|
|
695
|
+
# ------------------------------------------------------------------
|
|
696
|
+
|
|
697
|
+
def _call_llm(self, agent_id: str, prompt: str) -> Tuple[str, str]:
|
|
698
|
+
"""调用 LLM(mock 时返回预定义 JSON),返回 (content, thinking) tuple。
|
|
699
|
+
|
|
700
|
+
若 output_dir 已设置,会把每次 LLM 调用的完整 prompt/response 持久化到
|
|
701
|
+
{output_dir}/llm_raw/{agent_id}_{round_idx}_{ts}.json,方便后续分析
|
|
702
|
+
截断、解析失败等问题。
|
|
703
|
+
|
|
704
|
+
如果 LLM client 是 LLMGateway 且支持 ``complete_with_thinking``,
|
|
705
|
+
则同时返回 thinking 块(MiniMax M3 的 <think>...</think>)。
|
|
706
|
+
"""
|
|
707
|
+
import inspect
|
|
708
|
+
import json
|
|
709
|
+
import time as _time
|
|
710
|
+
from pathlib import Path
|
|
711
|
+
from QuantNodes.ai.llm.gateway import LLMGateway
|
|
712
|
+
|
|
713
|
+
temperature = self._get_temperature_for_agent(agent_id)
|
|
714
|
+
ts = int(_time.time() * 1000)
|
|
715
|
+
|
|
716
|
+
thinking = ""
|
|
717
|
+
raw = ""
|
|
718
|
+
|
|
719
|
+
# 实际调用 LLM(或 mock)
|
|
720
|
+
if self.llm_client is not None:
|
|
721
|
+
# Tier 1: LLMGateway 路径 → 同时获取 thinking
|
|
722
|
+
if isinstance(self.llm_client, LLMGateway) and hasattr(
|
|
723
|
+
self.llm_client, 'complete_with_thinking'
|
|
724
|
+
):
|
|
725
|
+
persist_dir = str(self._llm_raw_dir) if self._llm_raw_dir else None
|
|
726
|
+
raw, thinking = self.llm_client.complete_with_thinking(
|
|
727
|
+
agent_id=agent_id, prompt=prompt,
|
|
728
|
+
temperature=temperature,
|
|
729
|
+
persist_thinking_dir=persist_dir,
|
|
730
|
+
)
|
|
731
|
+
elif hasattr(self.llm_client, 'complete'):
|
|
732
|
+
# 兼容不同 mock 接口(部分 mock 不接受 temperature 关键字)
|
|
733
|
+
try:
|
|
734
|
+
sig = inspect.signature(self.llm_client.complete)
|
|
735
|
+
if "temperature" in sig.parameters:
|
|
736
|
+
raw = self.llm_client.complete(
|
|
737
|
+
agent_id=agent_id, prompt=prompt, temperature=temperature
|
|
738
|
+
)
|
|
739
|
+
else:
|
|
740
|
+
raw = self.llm_client.complete(agent_id=agent_id, prompt=prompt)
|
|
741
|
+
except (TypeError, ValueError):
|
|
742
|
+
raw = self.llm_client.complete(agent_id=agent_id, prompt=prompt)
|
|
743
|
+
else:
|
|
744
|
+
raw = self.llm_client(prompt)
|
|
745
|
+
else:
|
|
746
|
+
raw = _mock_llm_response(agent_id, prompt, self.state, self.config)
|
|
747
|
+
|
|
748
|
+
# 完整持久化(无截断)
|
|
749
|
+
if self._llm_raw_dir is not None:
|
|
750
|
+
try:
|
|
751
|
+
round_idx = self.state.round_idx_hint
|
|
752
|
+
safe_agent = agent_id.replace("/", "_").replace(" ", "_")
|
|
753
|
+
out_file = self._llm_raw_dir / f"r{round_idx}_{safe_agent}_{ts}.json"
|
|
754
|
+
out_file.write_text(
|
|
755
|
+
json.dumps(
|
|
756
|
+
{
|
|
757
|
+
"agent_id": agent_id,
|
|
758
|
+
"round_idx": round_idx,
|
|
759
|
+
"temperature": temperature,
|
|
760
|
+
"prompt": prompt,
|
|
761
|
+
"response": raw,
|
|
762
|
+
"response_length": len(raw),
|
|
763
|
+
"thinking": thinking,
|
|
764
|
+
"thinking_length": len(thinking),
|
|
765
|
+
"ts": ts,
|
|
766
|
+
},
|
|
767
|
+
ensure_ascii=False,
|
|
768
|
+
indent=2,
|
|
769
|
+
),
|
|
770
|
+
encoding="utf-8",
|
|
771
|
+
)
|
|
772
|
+
except Exception as exc:
|
|
773
|
+
logger.warning("保存 LLM raw 失败: %s", exc)
|
|
774
|
+
return raw, thinking
|
|
775
|
+
|
|
776
|
+
def _get_temperature_for_agent(self, agent_id: str) -> float:
|
|
777
|
+
"""根据 agent_id 返回对应的温度参数"""
|
|
778
|
+
if "idea-generator" in agent_id:
|
|
779
|
+
return self.config.temperature_idea_gen
|
|
780
|
+
elif "formula-translator" in agent_id:
|
|
781
|
+
return self.config.temperature_formula
|
|
782
|
+
elif "reflector" in agent_id:
|
|
783
|
+
return self.config.temperature_reflector
|
|
784
|
+
elif "critic" in agent_id:
|
|
785
|
+
return self.config.temperature_critic
|
|
786
|
+
else:
|
|
787
|
+
return self.config.temperature
|
|
788
|
+
|
|
789
|
+
# ------------------------------------------------------------------
|
|
790
|
+
# 数据 metadata
|
|
791
|
+
# ------------------------------------------------------------------
|
|
792
|
+
|
|
793
|
+
def _get_available_operators(self) -> List[str]:
|
|
794
|
+
try:
|
|
795
|
+
from QuantNodes.research.quant_alpha.operator_vocab import (
|
|
796
|
+
list_vocab_operators,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
return list(list_vocab_operators())
|
|
800
|
+
except Exception:
|
|
801
|
+
from ..llm.parser import ALLOWED_OPERATORS
|
|
802
|
+
|
|
803
|
+
return sorted(ALLOWED_OPERATORS)
|
|
804
|
+
|
|
805
|
+
def _get_data_columns(self) -> List[str]:
|
|
806
|
+
if self.data is None:
|
|
807
|
+
return ["close", "open", "high", "low", "vol", "vwap"]
|
|
808
|
+
try:
|
|
809
|
+
return list(self.data.columns)
|
|
810
|
+
except Exception:
|
|
811
|
+
return ["close", "open", "high", "low", "vol"]
|
|
812
|
+
|
|
813
|
+
@staticmethod
|
|
814
|
+
def _serialize_ideas_for_translator(ideas: List[IdeaRecord]) -> List[Dict[str, Any]]:
|
|
815
|
+
return [i.to_dict() for i in ideas]
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
# ==============================================================================
|
|
819
|
+
# Mock LLM(默认返回 valid JSON)
|
|
820
|
+
# ==============================================================================
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _mock_llm_response(
|
|
824
|
+
agent_id: str,
|
|
825
|
+
prompt: str,
|
|
826
|
+
state: AlphaGptState,
|
|
827
|
+
config: Any = None,
|
|
828
|
+
) -> str:
|
|
829
|
+
"""Mock LLM 返回(让 workflow 在无 API key 时也能端到端跑通)
|
|
830
|
+
|
|
831
|
+
- idea-generator: 返回 pool_size 个简单想法
|
|
832
|
+
- formula-translator: 返回 ideas 数量的简单公式
|
|
833
|
+
- evaluator: mock(evaluator 实际用 tool 计算,跳过)
|
|
834
|
+
- reflector: 返回 keep verdicts
|
|
835
|
+
- critic: 返回空 final_pool(fallback 路径)
|
|
836
|
+
"""
|
|
837
|
+
import json
|
|
838
|
+
|
|
839
|
+
pool_size = (config.pool_size if config is not None else state.iterations_total)
|
|
840
|
+
round_idx = state.round_idx_hint
|
|
841
|
+
if "idea-generator" in agent_id:
|
|
842
|
+
ideas = []
|
|
843
|
+
categories = ["reversal", "momentum", "volatility", "value", "quality", "liquidity"]
|
|
844
|
+
for i in range(pool_size):
|
|
845
|
+
ideas.append({
|
|
846
|
+
"id": f"IDEA-{round_idx}-{i+1}",
|
|
847
|
+
"name": f"想法-{i+1}",
|
|
848
|
+
"category": categories[i % len(categories)],
|
|
849
|
+
"description": f"Mock idea {i+1}",
|
|
850
|
+
"expected_direction": "long",
|
|
851
|
+
"suggested_lookback": 20,
|
|
852
|
+
"a_share_compatible": True,
|
|
853
|
+
"orthogonal_to": [],
|
|
854
|
+
"complexity_hint": "simple",
|
|
855
|
+
})
|
|
856
|
+
return json.dumps({"round": round_idx, "ideas": ideas}, ensure_ascii=False)
|
|
857
|
+
|
|
858
|
+
if "formula-translator" in agent_id:
|
|
859
|
+
ideas_count = pool_size
|
|
860
|
+
formulas = []
|
|
861
|
+
for i in range(ideas_count):
|
|
862
|
+
formulas.append({
|
|
863
|
+
"id": f"FORMULA-{round_idx}-{i+1}",
|
|
864
|
+
"idea_id": f"IDEA-{round_idx}-{i+1}",
|
|
865
|
+
"formula": "sub(close, ts_mean(close, 10))",
|
|
866
|
+
"complexity": 3,
|
|
867
|
+
"a_share_compatible": True,
|
|
868
|
+
"explanation": "Mock formula",
|
|
869
|
+
})
|
|
870
|
+
return json.dumps({"round": round_idx, "formulas": formulas}, ensure_ascii=False)
|
|
871
|
+
|
|
872
|
+
if "reflector" in agent_id:
|
|
873
|
+
return json.dumps({
|
|
874
|
+
"round": round_idx,
|
|
875
|
+
"formula_feedback": [],
|
|
876
|
+
"next_round_suggestions": {},
|
|
877
|
+
}, ensure_ascii=False)
|
|
878
|
+
|
|
879
|
+
if "critic" in agent_id:
|
|
880
|
+
return json.dumps({"final_pool": []}, ensure_ascii=False)
|
|
881
|
+
|
|
882
|
+
return json.dumps({})
|
|
883
|
+
|
|
884
|
+
|
|
885
|
+
# ==============================================================================
|
|
886
|
+
# 辅助
|
|
887
|
+
# ==============================================================================
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def _run_async(coro: Any) -> Any:
|
|
891
|
+
"""同步调用 async coroutine"""
|
|
892
|
+
import asyncio
|
|
893
|
+
|
|
894
|
+
try:
|
|
895
|
+
loop = asyncio.get_event_loop()
|
|
896
|
+
if loop.is_running():
|
|
897
|
+
import concurrent.futures
|
|
898
|
+
|
|
899
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as ex:
|
|
900
|
+
fut = ex.submit(asyncio.run, coro)
|
|
901
|
+
return fut.result()
|
|
902
|
+
return loop.run_until_complete(coro)
|
|
903
|
+
except RuntimeError:
|
|
904
|
+
return asyncio.run(coro)
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
__all__ = [
|
|
908
|
+
"AlphaGptConfig",
|
|
909
|
+
"AlphaGptResult",
|
|
910
|
+
"AlphaGptWorkflow",
|
|
911
|
+
]
|