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.
Files changed (399) hide show
  1. QuantNodes/__init__.py +15 -0
  2. QuantNodes/__main__.py +14 -0
  3. QuantNodes/agent/__init__.py +158 -0
  4. QuantNodes/agent/agents/__init__.py +13 -0
  5. QuantNodes/agent/agents/definition.py +180 -0
  6. QuantNodes/agent/agents/manager.py +73 -0
  7. QuantNodes/agent/config/__init__.py +34 -0
  8. QuantNodes/agent/config/executor.py +958 -0
  9. QuantNodes/agent/config/loader.py +427 -0
  10. QuantNodes/agent/config/templates/bollinger_bands.yaml +84 -0
  11. QuantNodes/agent/config/templates/dual_ma.yaml +72 -0
  12. QuantNodes/agent/config/templates/empty.yaml +56 -0
  13. QuantNodes/agent/config/templates/mean_reversion.yaml +47 -0
  14. QuantNodes/agent/config/templates/mean_reversion_zscore.yaml +90 -0
  15. QuantNodes/agent/config/templates/momentum.yaml +81 -0
  16. QuantNodes/agent/config/templates/momentum_breakout.yaml +84 -0
  17. QuantNodes/agent/config/templates/rsi_strategy.yaml +72 -0
  18. QuantNodes/agent/config/templates/volume_price.yaml +86 -0
  19. QuantNodes/agent/config/types.py +156 -0
  20. QuantNodes/agent/config_mapper.py +293 -0
  21. QuantNodes/agent/core/__init__.py +19 -0
  22. QuantNodes/agent/core/dream.py +47 -0
  23. QuantNodes/agent/core/quant_dream.py +274 -0
  24. QuantNodes/agent/cron_jobs.py +314 -0
  25. QuantNodes/agent/nanobot_bridge.py +242 -0
  26. QuantNodes/agent/permission/__init__.py +30 -0
  27. QuantNodes/agent/permission/defaults.py +36 -0
  28. QuantNodes/agent/permission/evaluate.py +41 -0
  29. QuantNodes/agent/permission/models.py +59 -0
  30. QuantNodes/agent/permission/service.py +133 -0
  31. QuantNodes/agent/providers/__init__.py +11 -0
  32. QuantNodes/agent/providers/base.py +102 -0
  33. QuantNodes/agent/providers/quantnodes.py +610 -0
  34. QuantNodes/agent/providers/rate_limiter.py +326 -0
  35. QuantNodes/agent/providers/registry.py +163 -0
  36. QuantNodes/agent/skills/__init__.py +20 -0
  37. QuantNodes/agent/skills/base.py +118 -0
  38. QuantNodes/agent/skills/bridge.py +73 -0
  39. QuantNodes/agent/skills/factor/__init__.py +14 -0
  40. QuantNodes/agent/skills/factor/correlation.py +99 -0
  41. QuantNodes/agent/skills/factor/group_backtest.py +114 -0
  42. QuantNodes/agent/skills/factor/ic_analysis.py +106 -0
  43. QuantNodes/agent/skills/loader.py +107 -0
  44. QuantNodes/agent/skills/registry.py +105 -0
  45. QuantNodes/agent/skills/strategy/__init__.py +16 -0
  46. QuantNodes/agent/skills/strategy/bollinger.py +86 -0
  47. QuantNodes/agent/skills/strategy/dual_ma.py +82 -0
  48. QuantNodes/agent/skills/strategy/momentum.py +74 -0
  49. QuantNodes/agent/skills/strategy/rsi_reversal.py +99 -0
  50. QuantNodes/agent/skills_quant/__init__.py +14 -0
  51. QuantNodes/agent/skills_quant/backtest-analyze/SKILL.md +42 -0
  52. QuantNodes/agent/skills_quant/config-driven/SKILL.md +72 -0
  53. QuantNodes/agent/skills_quant/factor-research/SKILL.md +40 -0
  54. QuantNodes/agent/skills_quant/quant-dream/SKILL.md +55 -0
  55. QuantNodes/agent/skills_quant/risk-management/SKILL.md +45 -0
  56. QuantNodes/agent/skills_quant/strategy-design/SKILL.md +43 -0
  57. QuantNodes/agent/templates/__init__.py +4 -0
  58. QuantNodes/agent/tools/__init__.py +173 -0
  59. QuantNodes/agent/tools/_workspace.py +51 -0
  60. QuantNodes/agent/tools/alpha_backtest.py +328 -0
  61. QuantNodes/agent/tools/alpha_evaluate.py +493 -0
  62. QuantNodes/agent/tools/backtest.py +226 -0
  63. QuantNodes/agent/tools/base.py +133 -0
  64. QuantNodes/agent/tools/code_search.py +207 -0
  65. QuantNodes/agent/tools/config_backtest.py +401 -0
  66. QuantNodes/agent/tools/context.py +97 -0
  67. QuantNodes/agent/tools/dream_skill.py +77 -0
  68. QuantNodes/agent/tools/echo.py +38 -0
  69. QuantNodes/agent/tools/factor.py +231 -0
  70. QuantNodes/agent/tools/file_ops.py +201 -0
  71. QuantNodes/agent/tools/git_ops.py +190 -0
  72. QuantNodes/agent/tools/operator_lookup.py +218 -0
  73. QuantNodes/agent/tools/output_truncation.py +77 -0
  74. QuantNodes/agent/tools/path_check.py +43 -0
  75. QuantNodes/agent/tools/pipeline.py +62 -0
  76. QuantNodes/agent/tools/registry.py +150 -0
  77. QuantNodes/agent/tools/sandbox.py +62 -0
  78. QuantNodes/agent/tools/shell_safety.py +63 -0
  79. QuantNodes/agent/tools/strategy.py +106 -0
  80. QuantNodes/agent/tools/task.py +171 -0
  81. QuantNodes/agent/tools/web_fetch.py +142 -0
  82. QuantNodes/agent/tools/web_search.py +114 -0
  83. QuantNodes/agent/tools/wiki.py +370 -0
  84. QuantNodes/agent/utils/__init__.py +11 -0
  85. QuantNodes/agent/utils/helpers.py +43 -0
  86. QuantNodes/agent/utils/prompt_templates.py +30 -0
  87. QuantNodes/agent/workflows/__init__.py +20 -0
  88. QuantNodes/agent/workflows/implementations/__init__.py +8 -0
  89. QuantNodes/agent/workflows/implementations/alpha_gpt.py +508 -0
  90. QuantNodes/agent/workflows/implementations/mcts.py +442 -0
  91. QuantNodes/agent/workflows/parsers.py +44 -0
  92. QuantNodes/agent/workflows/registry.py +119 -0
  93. QuantNodes/agent/workflows/step_agent.py +219 -0
  94. QuantNodes/agent/workflows/tool.py +198 -0
  95. QuantNodes/ai/__init__.py +93 -0
  96. QuantNodes/ai/llm/__init__.py +75 -0
  97. QuantNodes/ai/llm/base.py +233 -0
  98. QuantNodes/ai/llm/decorators.py +281 -0
  99. QuantNodes/ai/llm/gateway.py +571 -0
  100. QuantNodes/ai/llm/null.py +76 -0
  101. QuantNodes/ai/llm/openai.py +435 -0
  102. QuantNodes/ai/optimizer.py +405 -0
  103. QuantNodes/ai/prompts/__init__.py +229 -0
  104. QuantNodes/ai/sandbox.py +371 -0
  105. QuantNodes/ai/sandbox_pandas_bridge.py +150 -0
  106. QuantNodes/ai/strategy_gen.py +396 -0
  107. QuantNodes/backtest/__init__.py +64 -0
  108. QuantNodes/backtest/backtest_node.py +188 -0
  109. QuantNodes/backtest/broker_node.py +378 -0
  110. QuantNodes/backtest/config_runner.py +397 -0
  111. QuantNodes/backtest/config_strategy.py +64 -0
  112. QuantNodes/backtest/risk_node.py +360 -0
  113. QuantNodes/backtest/strategy_node.py +268 -0
  114. QuantNodes/cache_node/__init__.py +19 -0
  115. QuantNodes/cache_node/base.py +244 -0
  116. QuantNodes/cache_node/cache_store.py +99 -0
  117. QuantNodes/cache_node/metadata.py +100 -0
  118. QuantNodes/cli/__init__.py +109 -0
  119. QuantNodes/cli/_helpers.py +511 -0
  120. QuantNodes/cli/command.py +110 -0
  121. QuantNodes/cli/commands/__init__.py +69 -0
  122. QuantNodes/cli/commands/agent.py +158 -0
  123. QuantNodes/cli/commands/alpha.py +951 -0
  124. QuantNodes/cli/commands/chat.py +38 -0
  125. QuantNodes/cli/commands/evolve.py +120 -0
  126. QuantNodes/cli/commands/factor.py +569 -0
  127. QuantNodes/cli/commands/init.py +190 -0
  128. QuantNodes/cli/commands/run.py +259 -0
  129. QuantNodes/cli/commands/serve.py +398 -0
  130. QuantNodes/cli/commands/version.py +120 -0
  131. QuantNodes/cli/enhanced.py +146 -0
  132. QuantNodes/conf_node/__init__.py +37 -0
  133. QuantNodes/conf_node/base.py +120 -0
  134. QuantNodes/conf_node/env_config.py +132 -0
  135. QuantNodes/conf_node/ini_config.py +70 -0
  136. QuantNodes/conf_node/json_config.py +69 -0
  137. QuantNodes/conf_node/yaml_config.py +78 -0
  138. QuantNodes/constants.py +17 -0
  139. QuantNodes/core/__init__.py +196 -0
  140. QuantNodes/core/_lookback_helpers.py +49 -0
  141. QuantNodes/core/ast_parser.py +198 -0
  142. QuantNodes/core/base.py +61 -0
  143. QuantNodes/core/cache_manager.py +344 -0
  144. QuantNodes/core/cache_utils.py +150 -0
  145. QuantNodes/core/cond_builder.py +53 -0
  146. QuantNodes/core/config.py +170 -0
  147. QuantNodes/core/constants.py +48 -0
  148. QuantNodes/core/control.py +412 -0
  149. QuantNodes/core/data_preprocessing.py +453 -0
  150. QuantNodes/core/data_source.py +46 -0
  151. QuantNodes/core/events.py +178 -0
  152. QuantNodes/core/evolution/__init__.py +22 -0
  153. QuantNodes/core/evolution/loop.py +583 -0
  154. QuantNodes/core/evolution/operators.py +289 -0
  155. QuantNodes/core/evolution/settings.py +44 -0
  156. QuantNodes/core/expression.py +841 -0
  157. QuantNodes/core/feedback/__init__.py +38 -0
  158. QuantNodes/core/feedback/channels.py +182 -0
  159. QuantNodes/core/feedback/collector.py +91 -0
  160. QuantNodes/core/feedback/dataclass.py +239 -0
  161. QuantNodes/core/feedback/llm_judge.py +138 -0
  162. QuantNodes/core/knowledge/__init__.py +69 -0
  163. QuantNodes/core/knowledge/knowledge_base.py +217 -0
  164. QuantNodes/core/knowledge/lineage_compress.py +196 -0
  165. QuantNodes/core/knowledge/lineage_expand.py +123 -0
  166. QuantNodes/core/knowledge/metrics/__init__.py +43 -0
  167. QuantNodes/core/knowledge/metrics/evaluator.py +176 -0
  168. QuantNodes/core/knowledge/metrics/metrics.py +220 -0
  169. QuantNodes/core/knowledge/rag_prompt.py +196 -0
  170. QuantNodes/core/knowledge/retriever.py +209 -0
  171. QuantNodes/core/lambda_node.py +81 -0
  172. QuantNodes/core/monitoring/__init__.py +22 -0
  173. QuantNodes/core/monitoring/collector.py +292 -0
  174. QuantNodes/core/monitoring/dashboard.py +365 -0
  175. QuantNodes/core/node.py +375 -0
  176. QuantNodes/core/pandas_utils.py +504 -0
  177. QuantNodes/core/parallel/__init__.py +15 -0
  178. QuantNodes/core/parallel/worker.py +140 -0
  179. QuantNodes/core/parallel/worker_process.py +265 -0
  180. QuantNodes/core/path_utils.py +73 -0
  181. QuantNodes/core/pipeline.py +328 -0
  182. QuantNodes/core/plugin.py +135 -0
  183. QuantNodes/core/quality_gate/__init__.py +32 -0
  184. QuantNodes/core/quality_gate/complexity.py +94 -0
  185. QuantNodes/core/quality_gate/consistency.py +26 -0
  186. QuantNodes/core/quality_gate/node.py +97 -0
  187. QuantNodes/core/quality_gate/redundancy.py +51 -0
  188. QuantNodes/core/quality_gate/settings.py +43 -0
  189. QuantNodes/core/quality_gate/zoo.py +98 -0
  190. QuantNodes/core/serializable.py +116 -0
  191. QuantNodes/core/serialization.py +673 -0
  192. QuantNodes/core/tools.py +333 -0
  193. QuantNodes/core/trajectory/__init__.py +25 -0
  194. QuantNodes/core/trajectory/entry.py +116 -0
  195. QuantNodes/core/trajectory/lineage.py +67 -0
  196. QuantNodes/core/trajectory/pool.py +211 -0
  197. QuantNodes/core/trajectory/selector.py +140 -0
  198. QuantNodes/core/visualization/__init__.py +33 -0
  199. QuantNodes/core/visualization/builder.py +233 -0
  200. QuantNodes/core/visualization/gate_breakdown.py +140 -0
  201. QuantNodes/core/visualization/lineage_dag.py +203 -0
  202. QuantNodes/core/visualization/metric_distribution.py +125 -0
  203. QuantNodes/core/visualization/report.py +68 -0
  204. QuantNodes/database_node/__init__.py +69 -0
  205. QuantNodes/database_node/base.py +135 -0
  206. QuantNodes/database_node/clickhouse_node.py +272 -0
  207. QuantNodes/database_node/csv_node.py +83 -0
  208. QuantNodes/database_node/duckdb_node.py +86 -0
  209. QuantNodes/database_node/factory.py +83 -0
  210. QuantNodes/database_node/mysql_node.py +100 -0
  211. QuantNodes/database_node/parquet_node.py +75 -0
  212. QuantNodes/database_node/sqlite_node.py +67 -0
  213. QuantNodes/factor_node/__init__.py +50 -0
  214. QuantNodes/factor_node/factor.py +563 -0
  215. QuantNodes/factor_node/factor_db.py +421 -0
  216. QuantNodes/factor_node/factor_functions/__init__.py +252 -0
  217. QuantNodes/factor_node/factor_functions/_helpers.py +358 -0
  218. QuantNodes/factor_node/factor_functions/_helpers_debug.py +317 -0
  219. QuantNodes/factor_node/factor_functions/composite_ops.py +136 -0
  220. QuantNodes/factor_node/factor_functions/math_ops.py +433 -0
  221. QuantNodes/factor_node/factor_functions/section_ops.py +290 -0
  222. QuantNodes/factor_node/factor_functions/talib_ops.py +1293 -0
  223. QuantNodes/factor_node/factor_functions/time_ops.py +535 -0
  224. QuantNodes/factor_node/factor_operation.py +1115 -0
  225. QuantNodes/factor_node/factor_table.py +1073 -0
  226. QuantNodes/factor_node/quant_nodes_object.py +60 -0
  227. QuantNodes/mcp_server/__init__.py +27 -0
  228. QuantNodes/mcp_server/__main__.py +4 -0
  229. QuantNodes/mcp_server/server.py +272 -0
  230. QuantNodes/methods/__init__.py +28 -0
  231. QuantNodes/methods/pipeline.py +100 -0
  232. QuantNodes/methods/sandbox.py +102 -0
  233. QuantNodes/monitor/__init__.py +27 -0
  234. QuantNodes/monitor/agent_tools/__init__.py +5 -0
  235. QuantNodes/monitor/agent_tools/monitor_tool.py +98 -0
  236. QuantNodes/monitor/agent_tools/schedule_tool.py +98 -0
  237. QuantNodes/monitor/agent_tools/version_tool.py +133 -0
  238. QuantNodes/monitor/monitor/__init__.py +6 -0
  239. QuantNodes/monitor/monitor/alerter.py +60 -0
  240. QuantNodes/monitor/monitor/collector.py +164 -0
  241. QuantNodes/monitor/monitor/dashboard.py +115 -0
  242. QuantNodes/monitor/monitor/drift.py +190 -0
  243. QuantNodes/monitor/scheduler/__init__.py +4 -0
  244. QuantNodes/monitor/scheduler/runner.py +133 -0
  245. QuantNodes/monitor/scheduler/scheduler.py +184 -0
  246. QuantNodes/monitor/storage/__init__.py +16 -0
  247. QuantNodes/monitor/storage/models.py +70 -0
  248. QuantNodes/monitor/storage/repository.py +407 -0
  249. QuantNodes/monitor/version/__init__.py +4 -0
  250. QuantNodes/monitor/version/diff.py +81 -0
  251. QuantNodes/monitor/version/version_manager.py +182 -0
  252. QuantNodes/operator_node/__init__.py +28 -0
  253. QuantNodes/operator_node/base.py +97 -0
  254. QuantNodes/operator_node/query_node.py +129 -0
  255. QuantNodes/operator_node/sql_builder.py +125 -0
  256. QuantNodes/operator_node/sql_utils.py +172 -0
  257. QuantNodes/operator_node/transform.py +130 -0
  258. QuantNodes/operators/__init__.py +90 -0
  259. QuantNodes/operators/_engine.py +108 -0
  260. QuantNodes/operators/composite.py +161 -0
  261. QuantNodes/operators/composite_dag.py +667 -0
  262. QuantNodes/operators/composite_dag_ops.py +343 -0
  263. QuantNodes/operators/composite_dag_pandas_ops.py +382 -0
  264. QuantNodes/operators/custom.py +408 -0
  265. QuantNodes/operators/facade.py +164 -0
  266. QuantNodes/operators/math.py +163 -0
  267. QuantNodes/operators/proxy.py +29 -0
  268. QuantNodes/operators/registry.py +144 -0
  269. QuantNodes/operators/section.py +99 -0
  270. QuantNodes/operators/talib.py +757 -0
  271. QuantNodes/operators/templates.py +95 -0
  272. QuantNodes/operators/time_series.py +136 -0
  273. QuantNodes/prompts/__init__.py +20 -0
  274. QuantNodes/prompts/backtest/__init__.py +12 -0
  275. QuantNodes/prompts/backtest/factor_based.py +86 -0
  276. QuantNodes/prompts/backtest/standard.py +73 -0
  277. QuantNodes/prompts/factor/__init__.py +14 -0
  278. QuantNodes/prompts/factor/correlation.py +77 -0
  279. QuantNodes/prompts/factor/group_backtest.py +86 -0
  280. QuantNodes/prompts/factor/ic_analysis.py +91 -0
  281. QuantNodes/prompts/strategy/__init__.py +18 -0
  282. QuantNodes/prompts/strategy/market_neutral.py +96 -0
  283. QuantNodes/prompts/strategy/mean_reversion.py +107 -0
  284. QuantNodes/prompts/strategy/momentum.py +160 -0
  285. QuantNodes/prompts/strategy/pairs_trading.py +107 -0
  286. QuantNodes/prompts/strategy/trend_following.py +96 -0
  287. QuantNodes/research/README.md +106 -0
  288. QuantNodes/research/__init__.py +154 -0
  289. QuantNodes/research/_legacy_3c/__init__.py +61 -0
  290. QuantNodes/research/_legacy_3c/auto_researcher.py +289 -0
  291. QuantNodes/research/_legacy_3c/factor_evaluator.py +560 -0
  292. QuantNodes/research/_legacy_3c/factor_miner.py +318 -0
  293. QuantNodes/research/_legacy_3c/mcts_search.py +324 -0
  294. QuantNodes/research/factor_test/__init__.py +25 -0
  295. QuantNodes/research/factor_test/config.py +184 -0
  296. QuantNodes/research/factor_test/config_builder.py +276 -0
  297. QuantNodes/research/factor_test/e2e/data_prep.py +163 -0
  298. QuantNodes/research/factor_test/e2e/run_evolution_e2e.py +309 -0
  299. QuantNodes/research/factor_test/evolution_adapter.py +231 -0
  300. QuantNodes/research/factor_test/feedback_wrapper.py +102 -0
  301. QuantNodes/research/factor_test/ifind_db/__init__.py +7 -0
  302. QuantNodes/research/factor_test/ifind_db/fetcher.py +224 -0
  303. QuantNodes/research/factor_test/ifind_db/ifind_database.py +689 -0
  304. QuantNodes/research/factor_test/nodes/__init__.py +1 -0
  305. QuantNodes/research/factor_test/nodes/_base.py +91 -0
  306. QuantNodes/research/factor_test/nodes/adjust_date_node.py +48 -0
  307. QuantNodes/research/factor_test/nodes/configs.py +240 -0
  308. QuantNodes/research/factor_test/nodes/factor_neutralize_node.py +87 -0
  309. QuantNodes/research/factor_test/nodes/factor_preprocess_node.py +222 -0
  310. QuantNodes/research/factor_test/nodes/factor_score_node.py +141 -0
  311. QuantNodes/research/factor_test/nodes/factor_test_report_node.py +153 -0
  312. QuantNodes/research/factor_test/nodes/group_analyzer_node.py +317 -0
  313. QuantNodes/research/factor_test/nodes/ic_analyzer_node.py +112 -0
  314. QuantNodes/research/factor_test/nodes/load_data_node.py +100 -0
  315. QuantNodes/research/factor_test/nodes/long_short_node.py +93 -0
  316. QuantNodes/research/factor_test/nodes/neutralizers.py +222 -0
  317. QuantNodes/research/factor_test/nodes/preprocess_strategies.py +277 -0
  318. QuantNodes/research/factor_test/nodes/risk_correlation_node.py +112 -0
  319. QuantNodes/research/factor_test/nodes/sample_pool_filter_node.py +110 -0
  320. QuantNodes/research/factor_test/nodes/tradability_filter_node.py +92 -0
  321. QuantNodes/research/factor_test/pipeline_runner.py +305 -0
  322. QuantNodes/research/factor_test/pipeline_spec.py +216 -0
  323. QuantNodes/research/factor_test/utils/__init__.py +26 -0
  324. QuantNodes/research/factor_test/utils/constants.py +86 -0
  325. QuantNodes/research/factor_test/utils/data_loader.py +141 -0
  326. QuantNodes/research/factor_test/utils/date_utils.py +232 -0
  327. QuantNodes/research/factor_test/utils/file_loaders.py +150 -0
  328. QuantNodes/research/factor_test/utils/labels.py +37 -0
  329. QuantNodes/research/factor_test/utils/metrics_extractor.py +55 -0
  330. QuantNodes/research/factor_test/utils/performance_metrics.py +175 -0
  331. QuantNodes/research/factor_test/utils/safe_load.py +106 -0
  332. QuantNodes/research/quant_alpha/CHANGELOG.md +80 -0
  333. QuantNodes/research/quant_alpha/README.md +142 -0
  334. QuantNodes/research/quant_alpha/__init__.py +45 -0
  335. QuantNodes/research/quant_alpha/adapters/__init__.py +99 -0
  336. QuantNodes/research/quant_alpha/adapters/calculator.py +503 -0
  337. QuantNodes/research/quant_alpha/adapters/expression.py +387 -0
  338. QuantNodes/research/quant_alpha/alpha101_design/__init__.py +50 -0
  339. QuantNodes/research/quant_alpha/alpha101_design/few_shot_examples.py +243 -0
  340. QuantNodes/research/quant_alpha/alpha101_design/philosophy.py +474 -0
  341. QuantNodes/research/quant_alpha/alpha158_design/__init__.py +63 -0
  342. QuantNodes/research/quant_alpha/alpha158_design/few_shot_examples.py +219 -0
  343. QuantNodes/research/quant_alpha/alpha158_design/philosophy.py +240 -0
  344. QuantNodes/research/quant_alpha/evaluation/__init__.py +47 -0
  345. QuantNodes/research/quant_alpha/evaluation/baselines/__init__.py +8 -0
  346. QuantNodes/research/quant_alpha/evaluation/baselines/g1_handcrafted.py +135 -0
  347. QuantNodes/research/quant_alpha/evaluation/baselines/g2_llm_only.py +269 -0
  348. QuantNodes/research/quant_alpha/evaluation/baselines/g3_alpha_gpt.py +152 -0
  349. QuantNodes/research/quant_alpha/evaluation/clickhouse_data_loader.py +227 -0
  350. QuantNodes/research/quant_alpha/evaluation/contracts.py +376 -0
  351. QuantNodes/research/quant_alpha/evaluation/evaluators/__init__.py +6 -0
  352. QuantNodes/research/quant_alpha/evaluation/evaluators/polars_evaluator.py +545 -0
  353. QuantNodes/research/quant_alpha/evaluation/mock_data_loader.py +226 -0
  354. QuantNodes/research/quant_alpha/evaluation/runner.py +243 -0
  355. QuantNodes/research/quant_alpha/llm/__init__.py +38 -0
  356. QuantNodes/research/quant_alpha/llm/parser.py +681 -0
  357. QuantNodes/research/quant_alpha/logic_driven_pipeline.py +411 -0
  358. QuantNodes/research/quant_alpha/logic_mining/__init__.py +74 -0
  359. QuantNodes/research/quant_alpha/logic_mining/compiler.py +457 -0
  360. QuantNodes/research/quant_alpha/logic_mining/generator.py +366 -0
  361. QuantNodes/research/quant_alpha/logic_mining/models.py +252 -0
  362. QuantNodes/research/quant_alpha/logic_mining/parser.py +287 -0
  363. QuantNodes/research/quant_alpha/logic_mining/pipelines.py +297 -0
  364. QuantNodes/research/quant_alpha/logic_mining/sources.py +149 -0
  365. QuantNodes/research/quant_alpha/mcts/__init__.py +66 -0
  366. QuantNodes/research/quant_alpha/mcts/cache.py +262 -0
  367. QuantNodes/research/quant_alpha/mcts/extension_ops.py +320 -0
  368. QuantNodes/research/quant_alpha/mcts/feedback.py +825 -0
  369. QuantNodes/research/quant_alpha/mcts/op_prior.py +180 -0
  370. QuantNodes/research/quant_alpha/mcts/search.py +540 -0
  371. QuantNodes/research/quant_alpha/mcts/tree.py +201 -0
  372. QuantNodes/research/quant_alpha/operator_vocab/__init__.py +50 -0
  373. QuantNodes/research/quant_alpha/operator_vocab/config.py +54 -0
  374. QuantNodes/research/quant_alpha/operator_vocab/metadata.py +263 -0
  375. QuantNodes/research/quant_alpha/operator_vocab/vocabulary.py +481 -0
  376. QuantNodes/research/quant_alpha/pipeline.py +1027 -0
  377. QuantNodes/research/quant_alpha/types/__init__.py +27 -0
  378. QuantNodes/research/quant_alpha/types/constants.py +28 -0
  379. QuantNodes/research/quant_alpha/types/state.py +205 -0
  380. QuantNodes/research/quant_alpha/workflow/__init__.py +32 -0
  381. QuantNodes/research/quant_alpha/workflow/alpha_gpt.py +911 -0
  382. QuantNodes/research/quant_alpha/workflow/alpha_logics.py +416 -0
  383. QuantNodes/research/quant_alpha/workflow/state.py +27 -0
  384. QuantNodes/research/report_reproducer.py +485 -0
  385. QuantNodes/research/wiki.py +1155 -0
  386. QuantNodes/symbolic/__init__.py +51 -0
  387. QuantNodes/symbolic/compiler.py +113 -0
  388. QuantNodes/symbolic/dialect.py +260 -0
  389. QuantNodes/symbolic/executor.py +147 -0
  390. QuantNodes/symbolic/expression.py +234 -0
  391. QuantNodes/symbolic/functions.py +433 -0
  392. QuantNodes/symbolic/optimizer.py +165 -0
  393. QuantNodes/ui_node/__init__.py +30 -0
  394. QuantNodes/ui_node/base.py +222 -0
  395. quantnodes-3.0.0.dist-info/METADATA +463 -0
  396. quantnodes-3.0.0.dist-info/RECORD +399 -0
  397. quantnodes-3.0.0.dist-info/WHEEL +5 -0
  398. quantnodes-3.0.0.dist-info/entry_points.txt +24 -0
  399. quantnodes-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1027 @@
1
+ # coding=utf-8
2
+ """
3
+ pipeline.py - 端到端因子挖掘流水线(多轮迭代版)
4
+
5
+ 连接 Alpha-GPT → MCTS → 去重 → Wiki,支持多轮迭代和反馈闭环。
6
+
7
+ Usage::
8
+
9
+ from QuantNodes.research.quant_alpha.pipeline import AlphaPipeline, PipelineConfig
10
+
11
+ config = PipelineConfig(
12
+ objective="capture A-share reversal effect",
13
+ wiki_path="wiki/",
14
+ alphagpt_iterations=3,
15
+ mcts_iterations=50,
16
+ termination=TerminationConfig(max_rounds=5, target_factors=10),
17
+ )
18
+ pipeline = AlphaPipeline(config)
19
+ result = pipeline.run(data)
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import logging
26
+ import time
27
+ from dataclasses import dataclass, field
28
+ from pathlib import Path
29
+ from typing import Any, Dict, List, Optional, Tuple
30
+
31
+ import polars as pl
32
+
33
+ from QuantNodes.core.feedback import FactorFeedback
34
+ from QuantNodes.research.quant_alpha.evaluation.contracts import FactorMetrics
35
+ from QuantNodes.research.quant_alpha.evaluation.evaluators.polars_evaluator import (
36
+ deduplicate_mutual_ic,
37
+ )
38
+ from QuantNodes.research.quant_alpha.workflow import (
39
+ AlphaGptConfig,
40
+ AlphaGptWorkflow,
41
+ AlphaGptResult,
42
+ )
43
+ from QuantNodes.research.quant_alpha.mcts.search import (
44
+ MCTSSearch,
45
+ MCTSSearchConfig,
46
+ MCTSSearchResult,
47
+ )
48
+ from QuantNodes.research.quant_alpha.mcts.cache import MCTSCache, MCTSCacheConfig
49
+ from QuantNodes.research.quant_alpha.operator_vocab import OperatorVocab
50
+ from QuantNodes.research.wiki import (
51
+ WikiFactorProxy,
52
+ WikiFactor,
53
+ FactorSource,
54
+ FactorCategory,
55
+ )
56
+
57
+ logger = logging.getLogger(__name__)
58
+
59
+ __all__ = [
60
+ "AlphaPipeline",
61
+ "PipelineConfig",
62
+ "PipelineResult",
63
+ "TerminationConfig",
64
+ "RoundResult",
65
+ "RoundFeedback",
66
+ "EarlyStopping",
67
+ ]
68
+
69
+
70
+ # ==============================================================================
71
+ # 终止条件配置
72
+ # ==============================================================================
73
+
74
+
75
+ @dataclass
76
+ class TerminationConfig:
77
+ """终止条件配置"""
78
+
79
+ # 主要终止条件
80
+ max_rounds: int = 5 # 最大轮次
81
+ target_factors: int = 10 # 目标因子数量
82
+ min_improvement: float = 0.01 # 最小 IR 提升
83
+
84
+ # 早停配置
85
+ early_stopping: bool = True # 是否启用早停
86
+ patience: int = 3 # 连续 N 轮无改善则停止
87
+
88
+ # 超时配置
89
+ timeout_seconds: int = 3600 # 总超时时间(秒)
90
+ round_timeout_seconds: int = 600 # 单轮超时时间(秒)
91
+
92
+
93
+ # ==============================================================================
94
+ # 早停机制
95
+ # ==============================================================================
96
+
97
+
98
+ class EarlyStopping:
99
+ """早停机制
100
+
101
+ 连续 N 轮无改善则停止。
102
+ """
103
+
104
+ def __init__(self, patience: int = 3, min_improvement: float = 0.01):
105
+ self.patience = patience
106
+ self.min_improvement = min_improvement
107
+ self.best_ir: float = 0.0
108
+ self.counter: int = 0
109
+
110
+ def should_stop(self, current_ir: float) -> bool:
111
+ """判断是否应该停止
112
+
113
+ Args:
114
+ current_ir: 当前轮次的最佳 IR
115
+
116
+ Returns:
117
+ True 表示应该停止
118
+ """
119
+ if current_ir > self.best_ir + self.min_improvement:
120
+ self.best_ir = current_ir
121
+ self.counter = 0
122
+ return False
123
+ else:
124
+ self.counter += 1
125
+ return self.counter >= self.patience
126
+
127
+ def reset(self) -> None:
128
+ """重置早停状态"""
129
+ self.best_ir = 0.0
130
+ self.counter = 0
131
+
132
+
133
+ # ==============================================================================
134
+ # 单轮反馈
135
+ # ==============================================================================
136
+
137
+
138
+ @dataclass
139
+ class RoundFeedback:
140
+ """单轮反馈"""
141
+
142
+ round_num: int
143
+ best_ir: float
144
+ avg_ir: float
145
+ valid_count: int
146
+
147
+ # 最佳因子(用于下一轮种子)
148
+ best_formulas: List[str] = field(default_factory=list)
149
+
150
+ # 失败模式(避免重复)
151
+ failed_patterns: List[Dict[str, str]] = field(default_factory=list)
152
+
153
+ # 改进建议
154
+ suggestions: List[str] = field(default_factory=list)
155
+
156
+ # 统计信息
157
+ stats: Dict[str, Any] = field(default_factory=dict)
158
+
159
+ def to_dict(self) -> Dict[str, Any]:
160
+ """转换为字典"""
161
+ return {
162
+ "round_num": self.round_num,
163
+ "best_ir": self.best_ir,
164
+ "avg_ir": self.avg_ir,
165
+ "valid_count": self.valid_count,
166
+ "best_formulas": self.best_formulas,
167
+ "failed_patterns": self.failed_patterns,
168
+ "suggestions": self.suggestions,
169
+ "stats": self.stats,
170
+ }
171
+
172
+ def to_markdown(self) -> str:
173
+ """转换为 Markdown 格式(用于注入 Alpha-GPT)"""
174
+ lines = [f"## Round {self.round_num} 反馈", ""]
175
+
176
+ # 最佳因子
177
+ lines.append("### 最佳因子(IR >= 0.5)")
178
+ if self.best_formulas:
179
+ for i, formula in enumerate(self.best_formulas[:5], 1):
180
+ lines.append(f"{i}. `{formula}`")
181
+ else:
182
+ lines.append("无")
183
+ lines.append("")
184
+
185
+ # 失败模式
186
+ lines.append("### 失败模式(避免重复)")
187
+ if self.failed_patterns:
188
+ for i, pattern in enumerate(self.failed_patterns[:5], 1):
189
+ lines.append(f"{i}. `{pattern['formula']}` - {pattern['reason']}")
190
+ else:
191
+ lines.append("无")
192
+ lines.append("")
193
+
194
+ # 改进建议
195
+ lines.append("### 改进建议")
196
+ if self.suggestions:
197
+ for suggestion in self.suggestions:
198
+ lines.append(f"- {suggestion}")
199
+ else:
200
+ lines.append("无")
201
+ lines.append("")
202
+
203
+ # 统计信息
204
+ lines.append("### 统计信息")
205
+ lines.append(f"- 最佳 IR: {self.best_ir:.4f}")
206
+ lines.append(f"- 平均 IR: {self.avg_ir:.4f}")
207
+ lines.append(f"- 有效因子: {self.valid_count} 个")
208
+ if self.stats.get("improvement_vs_prev") is not None:
209
+ lines.append(f"- 与上轮对比: IR 提升 {self.stats['improvement_vs_prev']:.4f}")
210
+
211
+ return "\n".join(lines)
212
+
213
+
214
+ # ==============================================================================
215
+ # 单轮结果
216
+ # ==============================================================================
217
+
218
+
219
+ @dataclass
220
+ class RoundResult:
221
+ """单轮结果"""
222
+
223
+ round_num: int
224
+ alphagpt_result: Optional[AlphaGptResult] = None
225
+ mcts_result: Optional[MCTSSearchResult] = None
226
+ final_pool: List[FactorMetrics] = field(default_factory=list)
227
+
228
+ # 反馈信息
229
+ feedback: Optional[RoundFeedback] = None
230
+
231
+ # 详细信息
232
+ feedback_details: Dict[str, Any] = field(default_factory=dict)
233
+ mutual_ic_matrix: Dict[str, Dict[str, float]] = field(default_factory=dict)
234
+
235
+ # 统计信息
236
+ elapsed_seconds: float = 0.0
237
+
238
+
239
+ # ==============================================================================
240
+ # 流水线配置
241
+ # ==============================================================================
242
+
243
+
244
+ @dataclass
245
+ class PipelineConfig:
246
+ """流水线配置"""
247
+
248
+ # 研究目标
249
+ objective: str
250
+
251
+ # Wiki 配置
252
+ wiki_path: str = "wiki/"
253
+
254
+ # 终止条件配置
255
+ termination: TerminationConfig = field(default_factory=TerminationConfig)
256
+
257
+ # Alpha-GPT 配置
258
+ alphagpt_iterations: int = 3
259
+ alphagpt_pool_size: int = 10
260
+ alphagpt_top_k: int = 10
261
+
262
+ # MCTS 配置
263
+ mcts_iterations: int = 50
264
+ mcts_max_depth: int = 5
265
+ mcts_dedup_threshold: float = 0.7
266
+
267
+ # 去重配置
268
+ max_mutual_ic: float = 0.7
269
+
270
+ # Alpha-GPT 过滤配置
271
+ min_ir_threshold: float = 0.5
272
+
273
+ # 评估配置
274
+ min_ic_decay_ratio: float = 0.3
275
+ max_turnover: float = 2.0
276
+
277
+ # 通用配置
278
+ top_k: int = 10
279
+ date_column: str = "date"
280
+ code_column: str = "code"
281
+ forward_returns: Tuple[int, ...] = (1, 5, 20)
282
+
283
+ # LLM 配置
284
+ llm_provider: str = "minimax"
285
+ llm_model: Optional[str] = None
286
+ temperature: float = 0.7
287
+
288
+ # 各阶段温度参数
289
+ temperature_idea_gen: float = 0.8 # 鼓励创新
290
+ temperature_formula: float = 0.4 # 需要精确
291
+ temperature_reflector: float = 0.6 # 平衡
292
+ temperature_critic: float = 0.3 # 需要稳定
293
+
294
+ # 输出配置
295
+ output_dir: str = "pipeline_output"
296
+
297
+ # Γ 约束配置(用于逻辑驱动因子生成)
298
+ gamma: Optional[Any] = None # CompiledConstraint from logic_mining.compiler
299
+ structured_logic: Optional[Any] = None # WikiLogicStructured (PR-5 一致性评分)
300
+
301
+
302
+ # ==============================================================================
303
+ # 流水线结果
304
+ # ==============================================================================
305
+
306
+
307
+ @dataclass
308
+ class PipelineResult:
309
+ """流水线结果"""
310
+
311
+ rounds: List[RoundResult] = field(default_factory=list)
312
+ final_pool: List[FactorMetrics] = field(default_factory=list)
313
+ wiki_pages: List[str] = field(default_factory=list)
314
+
315
+ # 全局信息
316
+ all_mcts_nodes: List[Any] = field(default_factory=list)
317
+ global_correlation_matrix: Dict[str, Dict[str, float]] = field(default_factory=dict)
318
+
319
+ elapsed_seconds: float = 0.0
320
+ summary: Dict[str, Any] = field(default_factory=dict)
321
+
322
+
323
+ # ==============================================================================
324
+ # 反馈生成
325
+ # ==============================================================================
326
+
327
+
328
+ def generate_feedback(
329
+ round_result: RoundResult,
330
+ history: List[RoundResult],
331
+ ) -> RoundFeedback:
332
+ """生成单轮反馈
333
+
334
+ Args:
335
+ round_result: 当前轮次结果
336
+ history: 历史轮次结果
337
+
338
+ Returns:
339
+ RoundFeedback
340
+ """
341
+ round_num = round_result.round_num
342
+
343
+ # 1. 提取最佳因子
344
+ best_formulas: List[str] = []
345
+ if round_result.mcts_result:
346
+ for node in round_result.mcts_result.valid_nodes:
347
+ ir = abs(node.metadata.get("ir", 0.0))
348
+ if ir >= 0.5:
349
+ best_formulas.append(node.formula)
350
+ best_formulas = best_formulas[:5]
351
+
352
+ # 2. 提取失败模式
353
+ failed_patterns: List[Dict[str, str]] = []
354
+ if round_result.mcts_result:
355
+ # 使用 tree.all_nodes() 获取所有节点
356
+ for node in round_result.mcts_result.tree.all_nodes():
357
+ fb = round_result.mcts_result.feedback_cache.get(node.formula)
358
+ if fb and not fb.decision:
359
+ failed_patterns.append({
360
+ "formula": node.formula,
361
+ "reason": fb.summary,
362
+ })
363
+ failed_patterns = failed_patterns[:10]
364
+
365
+ # 3. 生成改进建议
366
+ suggestions: List[str] = []
367
+
368
+ # 分析成功因子的算子
369
+ if best_formulas:
370
+ suggestions.append(f"尝试使用类似 {best_formulas[0][:30]}... 的结构")
371
+
372
+ # 分析失败模式
373
+ if failed_patterns:
374
+ reasons = [p["reason"] for p in failed_patterns[:3]]
375
+ suggestions.append(f"避免以下失败模式: {', '.join(reasons)}")
376
+
377
+ # 多样性建议
378
+ if len(history) > 0:
379
+ prev_formulas = []
380
+ for h in history:
381
+ if h.feedback:
382
+ prev_formulas.extend(h.feedback.best_formulas)
383
+ if len(set(prev_formulas)) < len(prev_formulas):
384
+ suggestions.append("增加因子多样性,避免重复")
385
+
386
+ # 4. 计算统计信息
387
+ best_ir = 0.0
388
+ avg_ir = 0.0
389
+ valid_count = 0
390
+
391
+ if round_result.mcts_result:
392
+ valid_nodes = round_result.mcts_result.valid_nodes
393
+ valid_count = len(valid_nodes)
394
+ if valid_nodes:
395
+ irs = [abs(n.metadata.get("ir", 0.0)) for n in valid_nodes]
396
+ best_ir = max(irs)
397
+ avg_ir = sum(irs) / len(irs)
398
+
399
+ # 与上轮对比
400
+ improvement_vs_prev = None
401
+ if history and history[-1].feedback:
402
+ prev_best_ir = history[-1].feedback.best_ir
403
+ improvement_vs_prev = best_ir - prev_best_ir
404
+
405
+ stats = {
406
+ "improvement_vs_prev": improvement_vs_prev,
407
+ "total_mcts_nodes": (
408
+ len(round_result.mcts_result.tree.all_nodes())
409
+ if round_result.mcts_result
410
+ else 0
411
+ ),
412
+ }
413
+
414
+ return RoundFeedback(
415
+ round_num=round_num,
416
+ best_ir=best_ir,
417
+ avg_ir=avg_ir,
418
+ valid_count=valid_count,
419
+ best_formulas=best_formulas,
420
+ failed_patterns=failed_patterns,
421
+ suggestions=suggestions,
422
+ stats=stats,
423
+ )
424
+
425
+
426
+ # ==============================================================================
427
+ # 流水线
428
+ # ==============================================================================
429
+
430
+
431
+ class AlphaPipeline:
432
+ """端到端因子挖掘流水线(多轮迭代版)
433
+
434
+ 连接 Alpha-GPT → MCTS → 去重 → Wiki,支持多轮迭代和反馈闭环。
435
+ """
436
+
437
+ def __init__(self, config: PipelineConfig):
438
+ self.config = config
439
+ self.vocab = OperatorVocab.default()
440
+ self.wiki = WikiFactorProxy(config.wiki_path)
441
+ self._start_time: float = 0.0
442
+ # Tier 4 (feature/thinking-chain): 算子先验
443
+ self._op_prior = self._init_op_prior()
444
+
445
+ def _init_op_prior(self):
446
+ """初始化 OpPrior(从 output_dir/op_prior.json 加载历史)。"""
447
+ from QuantNodes.research.quant_alpha.mcts.op_prior import OpPrior
448
+ if not self.config.output_dir:
449
+ return OpPrior()
450
+ prior_path = Path(self.config.output_dir) / "op_prior.json"
451
+ if prior_path.exists():
452
+ try:
453
+ prior = OpPrior.load(prior_path)
454
+ logger.info(
455
+ "[Pipeline] Loaded OpPrior: %d ops, %d updates from %s",
456
+ len(prior.weights), prior.total_updates, prior_path,
457
+ )
458
+ return prior
459
+ except Exception as e:
460
+ logger.warning("[Pipeline] Failed to load OpPrior: %s", e)
461
+ return OpPrior()
462
+
463
+ def _save_op_prior(self) -> None:
464
+ """持久化 OpPrior 到 output_dir/op_prior.json。"""
465
+ if not self.config.output_dir:
466
+ return
467
+ try:
468
+ from QuantNodes.research.quant_alpha.mcts.op_prior import OpPrior
469
+ prior_path = Path(self.config.output_dir) / "op_prior.json"
470
+ self._op_prior.save(prior_path)
471
+ except Exception as e:
472
+ logger.warning("[Pipeline] Failed to save OpPrior: %s", e)
473
+
474
+ def _update_op_prior_from_mcts(self, mcts_result) -> None:
475
+ """从 MCTS 结果更新 OpPrior(用所有 valid_nodes 的 IR)。"""
476
+ import re as _re
477
+ if mcts_result is None:
478
+ return
479
+ for node in mcts_result.valid_nodes:
480
+ ops = _re.findall(r"\b([a-zA-Z_]\w*)\s*\(", node.formula)
481
+ ir = node.metadata.get("ir", 0.0)
482
+ self._op_prior.update(ops, ir)
483
+
484
+ def run(self, data: pl.DataFrame) -> PipelineResult:
485
+ """运行流水线
486
+
487
+ Args:
488
+ data: 行情数据 DataFrame
489
+
490
+ Returns:
491
+ PipelineResult
492
+ """
493
+ self._start_time = time.time()
494
+ result = PipelineResult()
495
+
496
+ # 预计算前瞻收益(避免重复计算)
497
+ logger.info("[Pipeline] 预计算前瞻收益...")
498
+ data = self._precompute_forward_returns(data)
499
+
500
+ # 初始化早停机制
501
+ early_stopping = EarlyStopping(
502
+ patience=self.config.termination.patience,
503
+ min_improvement=self.config.termination.min_improvement,
504
+ )
505
+
506
+ # 多轮迭代
507
+ for round_num in range(1, self.config.termination.max_rounds + 1):
508
+ logger.info("[Pipeline] ====== Round %d 开始 ======", round_num)
509
+
510
+ # 检查超时
511
+ if self._check_timeout():
512
+ logger.info("[Pipeline] 超时触发,退出循环")
513
+ break
514
+
515
+ # 运行单轮
516
+ round_result = self._run_round(
517
+ round_num, data, result.rounds, early_stopping
518
+ )
519
+ result.rounds.append(round_result)
520
+
521
+ # 检查终止条件
522
+ if self._should_stop(
523
+ round_result.feedback, result.final_pool, early_stopping
524
+ ):
525
+ logger.info("[Pipeline] Round %d 触发终止条件", round_num)
526
+ break
527
+
528
+ logger.info(
529
+ "[Pipeline] Round %d 完成: IR=%.4f, 有效因子=%d",
530
+ round_num,
531
+ round_result.feedback.best_ir if round_result.feedback else 0.0,
532
+ round_result.feedback.valid_count if round_result.feedback else 0,
533
+ )
534
+
535
+ # 最终结果
536
+ result.final_pool = self._select_final_pool(result.rounds)
537
+ result.wiki_pages = self._persist_to_wiki(result.final_pool)
538
+ result.elapsed_seconds = time.time() - self._start_time
539
+ result.summary = self._build_summary(result)
540
+
541
+ # 保存结果
542
+ self._save_pipeline_result(result)
543
+
544
+ logger.info(
545
+ "[Pipeline] 完成: %d 因子, %.1f 秒",
546
+ len(result.final_pool),
547
+ result.elapsed_seconds,
548
+ )
549
+
550
+ return result
551
+
552
+ def _run_round(
553
+ self,
554
+ round_num: int,
555
+ data: pl.DataFrame,
556
+ history: List[RoundResult],
557
+ early_stopping: EarlyStopping,
558
+ ) -> RoundResult:
559
+ """运行单轮迭代
560
+
561
+ Args:
562
+ round_num: 轮次编号
563
+ data: 行情数据
564
+ history: 历史轮次结果
565
+ early_stopping: 早停机制
566
+
567
+ Returns:
568
+ RoundResult
569
+ """
570
+ round_start = time.time()
571
+ round_result = RoundResult(round_num=round_num)
572
+
573
+ # Stage 1: Alpha-GPT
574
+ logger.info("[Pipeline] Round %d - Stage 1: Alpha-GPT", round_num)
575
+ feedback = history[-1].feedback if history else None
576
+ round_result.alphagpt_result = self._run_alphagpt(data, feedback)
577
+
578
+ # Stage 2: MCTS
579
+ logger.info("[Pipeline] Round %d - Stage 2: MCTS", round_num)
580
+ seed_formulas = self._extract_seed_formulas(round_result.alphagpt_result)
581
+ round_result.mcts_result = self._run_mcts(data, seed_formulas)
582
+
583
+ # Stage 3: 合并去重
584
+ logger.info("[Pipeline] Round %d - Stage 3: 合并去重", round_num)
585
+ round_result.final_pool = self._merge_and_dedup(
586
+ round_result.alphagpt_result, round_result.mcts_result, data
587
+ )
588
+
589
+ # Stage 4: 生成反馈
590
+ logger.info("[Pipeline] Round %d - Stage 4: 生成反馈", round_num)
591
+ round_result.feedback = generate_feedback(round_result, history)
592
+
593
+ # 记录耗时
594
+ round_result.elapsed_seconds = time.time() - round_start
595
+
596
+ return round_result
597
+
598
+ def _should_stop(
599
+ self,
600
+ feedback: Optional[RoundFeedback],
601
+ final_pool: List[FactorMetrics],
602
+ early_stopping: EarlyStopping,
603
+ ) -> bool:
604
+ """判断是否应该停止
605
+
606
+ Args:
607
+ feedback: 当前轮次反馈
608
+ final_pool: 当前因子池
609
+ early_stopping: 早停机制
610
+
611
+ Returns:
612
+ True 表示应该停止
613
+ """
614
+ if feedback is None:
615
+ return False
616
+
617
+ # 1. 检查目标因子数量
618
+ if len(final_pool) >= self.config.termination.target_factors:
619
+ logger.info("[Pipeline] 达到目标因子数量: %d", len(final_pool))
620
+ return True
621
+
622
+ # 2. 检查早停
623
+ if self.config.termination.early_stopping:
624
+ if early_stopping.should_stop(feedback.best_ir):
625
+ logger.info(
626
+ "[Pipeline] 早停触发: 连续 %d 轮无改善",
627
+ early_stopping.counter,
628
+ )
629
+ return True
630
+
631
+ # 3. 检查超时
632
+ if self._check_timeout():
633
+ logger.info("[Pipeline] 超时触发")
634
+ return True
635
+
636
+ return False
637
+
638
+ def _check_timeout(self) -> bool:
639
+ """检查是否超时"""
640
+ elapsed = time.time() - self._start_time
641
+ return elapsed > self.config.termination.timeout_seconds
642
+
643
+ def _precompute_forward_returns(self, data: pl.DataFrame) -> pl.DataFrame:
644
+ """预计算所有前瞻收益列(避免每个公式重复计算)
645
+
646
+ 添加列: _fwd_ret_1d, _fwd_ret_5d, _fwd_ret_20d 等
647
+ """
648
+ dc = self.config.date_column
649
+ cc = self.config.code_column
650
+ sorted_df = data.sort([cc, dc])
651
+
652
+ for offset in self.config.forward_returns:
653
+ col_name = f"_fwd_ret_{offset}d"
654
+ if col_name not in sorted_df.columns:
655
+ sorted_df = sorted_df.with_columns(
656
+ (
657
+ (pl.col("close").shift(-offset).over(cc) - pl.col("close"))
658
+ / pl.col("close")
659
+ ).alias(col_name)
660
+ )
661
+ logger.info("[Pipeline] 预计算 %s 完成", col_name)
662
+
663
+ return sorted_df
664
+
665
+ def _run_alphagpt(
666
+ self,
667
+ data: pl.DataFrame,
668
+ feedback: Optional[RoundFeedback] = None,
669
+ ) -> Optional[AlphaGptResult]:
670
+ """运行 Alpha-GPT 工作流
671
+
672
+ Args:
673
+ data: 行情数据
674
+ feedback: 上轮反馈(用于注入)
675
+
676
+ Returns:
677
+ AlphaGptResult
678
+ """
679
+ try:
680
+ config = AlphaGptConfig(
681
+ objective=self.config.objective,
682
+ iterations=self.config.alphagpt_iterations,
683
+ pool_size=self.config.alphagpt_pool_size,
684
+ top_k=self.config.alphagpt_top_k,
685
+ min_ir_threshold=self.config.min_ir_threshold,
686
+ max_mutual_ic_threshold=self.config.max_mutual_ic,
687
+ forward_returns=list(self.config.forward_returns),
688
+ date_column=self.config.date_column,
689
+ code_column=self.config.code_column,
690
+ llm_provider=self.config.llm_provider,
691
+ llm_model=self.config.llm_model,
692
+ temperature=self.config.temperature,
693
+ temperature_idea_gen=self.config.temperature_idea_gen,
694
+ temperature_formula=self.config.temperature_formula,
695
+ temperature_reflector=self.config.temperature_reflector,
696
+ temperature_critic=self.config.temperature_critic,
697
+ gamma=self.config.gamma,
698
+ )
699
+
700
+ # 注入反馈
701
+ if feedback is not None:
702
+ config.custom_feedback = feedback.to_markdown()
703
+ logger.info("[Pipeline] 注入 Round %d 反馈到 Alpha-GPT", feedback.round_num)
704
+
705
+ # 构建 LLM 客户端
706
+ llm_client = self._build_llm_client()
707
+
708
+ workflow = AlphaGptWorkflow(
709
+ config=config,
710
+ data=data,
711
+ llm_client=llm_client,
712
+ output_dir=self.config.output_dir,
713
+ )
714
+ return workflow.run()
715
+
716
+ except Exception as e:
717
+ logger.error("[Pipeline] Alpha-GPT 失败: %s", e)
718
+ return None
719
+
720
+ def _run_mcts(
721
+ self, data: pl.DataFrame, seed_formulas: Optional[List[str]] = None
722
+ ) -> Optional[MCTSSearchResult]:
723
+ """运行 MCTS 搜索"""
724
+ try:
725
+ config = MCTSSearchConfig(
726
+ iterations=self.config.mcts_iterations,
727
+ max_depth=self.config.mcts_max_depth,
728
+ dedup_threshold=self.config.mcts_dedup_threshold,
729
+ forward_returns=self.config.forward_returns,
730
+ date_column=self.config.date_column,
731
+ code_column=self.config.code_column,
732
+ structured_logic=self.config.structured_logic,
733
+ llm_client=self._build_llm_client(),
734
+ op_prior=self._op_prior, # Tier 4
735
+ prior_mix=0.5, # Tier 4
736
+ )
737
+
738
+ cache = MCTSCache(MCTSCacheConfig(enabled=True))
739
+ search = MCTSSearch(config=config, cache=cache)
740
+
741
+ result = search.search(
742
+ data=data,
743
+ seed_formulas=seed_formulas,
744
+ date_column=self.config.date_column,
745
+ code_column=self.config.code_column,
746
+ )
747
+
748
+ # Tier 4: 用本轮 MCTS 有效节点更新 OpPrior
749
+ self._update_op_prior_from_mcts(result)
750
+ self._save_op_prior()
751
+
752
+ return result
753
+
754
+ except Exception as e:
755
+ logger.error("[Pipeline] MCTS 失败: %s", e)
756
+ return None
757
+
758
+ def _extract_seed_formulas(
759
+ self, alphagpt_result: Optional[AlphaGptResult]
760
+ ) -> Optional[List[str]]:
761
+ """从 Alpha-GPT 结果提取种子公式"""
762
+ if alphagpt_result is None:
763
+ return None
764
+
765
+ formulas = [f.formula for f in alphagpt_result.final_pool]
766
+ logger.info("[Pipeline] 提取 %d 个种子公式", len(formulas))
767
+ return formulas
768
+
769
+ def _merge_and_dedup(
770
+ self,
771
+ alphagpt_result: Optional[AlphaGptResult],
772
+ mcts_result: Optional[MCTSSearchResult],
773
+ data: pl.DataFrame,
774
+ ) -> List[FactorMetrics]:
775
+ """合并并去重"""
776
+ all_metrics: List[FactorMetrics] = []
777
+
778
+ # 收集 Alpha-GPT 结果
779
+ if alphagpt_result:
780
+ for f in alphagpt_result.final_pool:
781
+ all_metrics.append(FactorMetrics(
782
+ formula_id=f.formula_id,
783
+ status="success",
784
+ ic_mean=f.ic_mean,
785
+ ir=f.ir,
786
+ overall_score=f.ir,
787
+ ))
788
+
789
+ # 收集 MCTS 结果
790
+ if mcts_result:
791
+ for n in mcts_result.best_k_nodes:
792
+ all_metrics.append(FactorMetrics(
793
+ formula_id=n.entry_id,
794
+ status="success",
795
+ ic_mean=n.metadata.get("ic_mean", 0.0),
796
+ ir=n.metadata.get("ir", 0.0),
797
+ overall_score=n.overall_score,
798
+ ))
799
+
800
+ if not all_metrics:
801
+ return []
802
+
803
+ # 去重
804
+ def get_values(m: FactorMetrics) -> Optional[pl.Series]:
805
+ try:
806
+ formula = self._get_formula_from_metrics(m, alphagpt_result, mcts_result)
807
+ if formula:
808
+ return self.vocab.evaluate(formula, data)
809
+ except Exception:
810
+ pass
811
+ return None
812
+
813
+ deduped = deduplicate_mutual_ic(
814
+ all_metrics,
815
+ get_values,
816
+ threshold=self.config.max_mutual_ic,
817
+ )
818
+
819
+ # 按 |overall_score| 降序排序,取 top_k(修复负 IR 因子被排在最后的 bug)
820
+ deduped.sort(key=lambda m: abs(m.overall_score), reverse=True)
821
+ return deduped[: self.config.top_k]
822
+
823
+ def _select_final_pool(self, rounds: List[RoundResult]) -> List[FactorMetrics]:
824
+ """从所有轮次中选择最终因子池"""
825
+ all_metrics: List[FactorMetrics] = []
826
+
827
+ for round_result in rounds:
828
+ all_metrics.extend(round_result.final_pool)
829
+
830
+ if not all_metrics:
831
+ return []
832
+
833
+ # 按 IR 降序排序,取 top_k
834
+ all_metrics.sort(key=lambda m: abs(m.ir), reverse=True)
835
+ return all_metrics[: self.config.top_k]
836
+
837
+ def _get_formula_from_metrics(
838
+ self,
839
+ metrics: FactorMetrics,
840
+ alphagpt_result: Optional[AlphaGptResult],
841
+ mcts_result: Optional[MCTSSearchResult],
842
+ ) -> Optional[str]:
843
+ """从 metrics 获取公式字符串"""
844
+ # 从 Alpha-GPT 结果查找
845
+ if alphagpt_result:
846
+ for f in alphagpt_result.final_pool:
847
+ if f.formula_id == metrics.formula_id:
848
+ return f.formula
849
+
850
+ # 从 MCTS 结果查找
851
+ if mcts_result:
852
+ for n in mcts_result.best_k_nodes:
853
+ if n.entry_id == metrics.formula_id:
854
+ return n.formula
855
+
856
+ return None
857
+
858
+ def _persist_to_wiki(self, factors: List[FactorMetrics]) -> List[str]:
859
+ """持久化到 Wiki
860
+
861
+ 成功保存到 Wiki 后发布 factor.mined 事件 (P2: 事件系统接入点)。
862
+ """
863
+ pages = []
864
+ for f in factors:
865
+ try:
866
+ wiki_factor = self._to_wiki_factor(f)
867
+ page_name = self.wiki.store_factor(wiki_factor)
868
+ pages.append(page_name)
869
+ logger.info("[Pipeline] 保存到 Wiki: %s", page_name)
870
+
871
+ # P2 事件系统接入: 通知下游 (monitor/、websocket 等)
872
+ try:
873
+ from QuantNodes.core.events import Events, get_event_bus
874
+ get_event_bus().publish_sync(
875
+ Events.FACTOR_MINED,
876
+ source="AlphaPipeline",
877
+ formula_id=f.formula_id,
878
+ page_name=page_name,
879
+ ir=f.ir,
880
+ ic_mean=f.ic_mean,
881
+ )
882
+ except ImportError:
883
+ pass # 事件模块未加载, 静默
884
+ except Exception as e:
885
+ logger.warning("[Pipeline] Wiki 保存失败: %s", e)
886
+
887
+ return pages
888
+
889
+ def _to_wiki_factor(self, metrics: FactorMetrics) -> WikiFactor:
890
+ """转换为 WikiFactor"""
891
+ formula = metrics.formula_id # 简化处理
892
+
893
+ return WikiFactor(
894
+ name=metrics.formula_id,
895
+ formula=formula,
896
+ source=FactorSource.AUTO_RESEARCH,
897
+ category=FactorCategory.OTHER,
898
+ tags=["alpha-pipeline", f"ir={metrics.ir:.3f}"],
899
+ ic_mean=metrics.ic_mean,
900
+ ic_std=metrics.ic_std,
901
+ icir=metrics.ir,
902
+ rank_ic_mean=metrics.rank_ic_mean,
903
+ description=f"Auto-mined factor with IR={metrics.ir:.3f}",
904
+ )
905
+
906
+ def _build_llm_client(self) -> Any:
907
+ """构建 LLM 客户端"""
908
+ try:
909
+ from QuantNodes.ai.llm.gateway import get_llm_gateway
910
+ return get_llm_gateway()
911
+ except Exception as e:
912
+ logger.warning("[Pipeline] LLM 客户端不可用: %s", e)
913
+ return None
914
+
915
+ def _save_pipeline_result(self, result: PipelineResult) -> None:
916
+ """保存流水线结果"""
917
+ output_dir = Path(self.config.output_dir)
918
+ output_dir.mkdir(parents=True, exist_ok=True)
919
+
920
+ # 保存每轮结果
921
+ for round_result in result.rounds:
922
+ self._save_round_result(round_result, output_dir)
923
+
924
+ # 保存最终结果
925
+ self._save_final_result(result, output_dir)
926
+
927
+ logger.info("[Pipeline] 结果已保存到 %s", output_dir)
928
+
929
+ def _save_round_result(
930
+ self, round_result: RoundResult, output_dir: Path
931
+ ) -> None:
932
+ """保存单轮结果"""
933
+ round_dir = output_dir / f"round_{round_result.round_num}"
934
+ round_dir.mkdir(parents=True, exist_ok=True)
935
+
936
+ # 保存反馈信息
937
+ if round_result.feedback:
938
+ feedback_file = round_dir / "feedback.json"
939
+ with open(feedback_file, "w", encoding="utf-8") as f:
940
+ json.dump(round_result.feedback.to_dict(), f, indent=2, ensure_ascii=False)
941
+
942
+ # 保存摘要
943
+ summary = {
944
+ "round_num": round_result.round_num,
945
+ "elapsed_seconds": round_result.elapsed_seconds,
946
+ "alphagpt_factors": (
947
+ len(round_result.alphagpt_result.final_pool)
948
+ if round_result.alphagpt_result
949
+ else 0
950
+ ),
951
+ "mcts_factors": (
952
+ len(round_result.mcts_result.valid_nodes)
953
+ if round_result.mcts_result
954
+ else 0
955
+ ),
956
+ "final_factors": len(round_result.final_pool),
957
+ "best_ir": round_result.feedback.best_ir if round_result.feedback else 0.0,
958
+ }
959
+ summary_file = round_dir / "summary.json"
960
+ with open(summary_file, "w", encoding="utf-8") as f:
961
+ json.dump(summary, f, indent=2, ensure_ascii=False)
962
+
963
+ def _save_final_result(
964
+ self, result: PipelineResult, output_dir: Path
965
+ ) -> None:
966
+ """保存最终结果"""
967
+ final_dir = output_dir / "final"
968
+ final_dir.mkdir(parents=True, exist_ok=True)
969
+
970
+ # 保存最终因子列表
971
+ factors_data = []
972
+ for f in result.final_pool:
973
+ factors_data.append({
974
+ "formula_id": f.formula_id,
975
+ "ir": f.ir,
976
+ "ic_mean": f.ic_mean,
977
+ "overall_score": f.overall_score,
978
+ })
979
+ factors_file = final_dir / "factors.json"
980
+ with open(factors_file, "w", encoding="utf-8") as f:
981
+ json.dump(factors_data, f, indent=2, ensure_ascii=False)
982
+
983
+ # 保存报告
984
+ report_lines = [
985
+ "# Alpha Pipeline 最终报告",
986
+ "",
987
+ f"## 目标",
988
+ f"{self.config.objective}",
989
+ "",
990
+ f"## 统计信息",
991
+ f"- 总轮次: {len(result.rounds)}",
992
+ f"- 最终因子数: {len(result.final_pool)}",
993
+ f"- Wiki 页面数: {len(result.wiki_pages)}",
994
+ f"- 总耗时: {result.elapsed_seconds:.1f} 秒",
995
+ "",
996
+ f"## 最终因子",
997
+ "",
998
+ "| 公式ID | IR | IC Mean | Score |",
999
+ "|--------|-----|---------|-------|",
1000
+ ]
1001
+ for f in result.final_pool:
1002
+ report_lines.append(
1003
+ f"| {f.formula_id} | {f.ir:.4f} | {f.ic_mean:.4f} | {f.overall_score:.4f} |"
1004
+ )
1005
+ report_file = final_dir / "report.md"
1006
+ with open(report_file, "w", encoding="utf-8") as f:
1007
+ f.write("\n".join(report_lines))
1008
+
1009
+ def _build_summary(self, result: PipelineResult) -> Dict[str, Any]:
1010
+ """构建摘要"""
1011
+ return {
1012
+ "objective": self.config.objective,
1013
+ "total_rounds": len(result.rounds),
1014
+ "final_factors": len(result.final_pool),
1015
+ "wiki_pages": len(result.wiki_pages),
1016
+ "elapsed_seconds": result.elapsed_seconds,
1017
+ "best_ir": (
1018
+ max((m.ir for m in result.final_pool), default=0.0)
1019
+ if result.final_pool
1020
+ else 0.0
1021
+ ),
1022
+ "avg_ir": (
1023
+ sum(m.ir for m in result.final_pool) / len(result.final_pool)
1024
+ if result.final_pool
1025
+ else 0.0
1026
+ ),
1027
+ }