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,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
+ ]