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,583 @@
1
+ """EvolutionLoop — 多轮演化主循环。
2
+
3
+ 调用流程:
4
+ 1. round 0: 为每个 direction 调 Hypothesizer.hypothesize()
5
+ 2. round 1..N: 调 ParentSelector 选 parent, 再调 Mutator/Crosser 生成子代
6
+ 3. 每轮通过 callback (PipelineRunner.run_candidate) 评估
7
+ 4. 评估结果写入 TrajectoryPool
8
+
9
+ 设计原则:
10
+ - EvolutionLoop 不直接执行回测, 通过 callback 委托
11
+ - callback 接受 FactorCandidate, 返回 (passed: bool, metrics: dict, feedback: FactorFeedback)
12
+ - 这样 Loop 可与任意 runner / sandbox 配合
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from dataclasses import dataclass, field
17
+ from typing import Callable, Optional
18
+
19
+ from ..feedback import FactorFeedback
20
+ from ..quality_gate import QualityGateNode
21
+ from ..trajectory import (
22
+ ParentSelector,
23
+ TrajectoryEntry,
24
+ TrajectoryPool,
25
+ )
26
+ from ..knowledge import KnowledgeBase, RAGEvaluator
27
+ from .operators import Crosser, FactorCandidate, Hypothesizer, Mutator
28
+ from .settings import EvolutionSetting
29
+
30
+
31
+ # 回调签名: candidate -> (passed, metrics, feedback)
32
+ EvaluateFn = Callable[[FactorCandidate], tuple[bool, dict, "FactorFeedback"]]
33
+
34
+
35
+ @dataclass
36
+ class EvolutionResult:
37
+ """单次演化实验的最终结果。"""
38
+ best_entries: list[TrajectoryEntry] = field(default_factory=list)
39
+ all_entries: list[TrajectoryEntry] = field(default_factory=list)
40
+ rounds_completed: int = 0
41
+ rejected_count: int = 0
42
+ total_count: int = 0
43
+
44
+
45
+ class EvolutionLoop:
46
+ """演化主循环。
47
+
48
+ Args:
49
+ settings: EvolutionSetting
50
+ pool: TrajectoryPool (持久化)
51
+ quality_gate: QualityGateNode (可选, pre-backtest 拦截)
52
+ evaluate_fn: 评估回调 (必填) — 接 FactorCandidate, 返回 (passed, metrics, feedback)
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ settings: EvolutionSetting,
58
+ pool: TrajectoryPool,
59
+ quality_gate: Optional[QualityGateNode] = None,
60
+ evaluate_fn: Optional[EvaluateFn] = None,
61
+ selector: Optional[ParentSelector] = None,
62
+ knowledge_base: Optional[KnowledgeBase] = None,
63
+ rag_top_k: int = 3,
64
+ max_ancestor_depth: int = 2,
65
+ max_descendant_depth: int = 2,
66
+ use_compress: bool = False,
67
+ compressor=None,
68
+ rag_evaluator: Optional[RAGEvaluator] = None,
69
+ workers: int = 1,
70
+ ):
71
+ self.settings = settings
72
+ self.pool = pool
73
+ self.quality_gate = quality_gate
74
+ self.evaluate_fn = evaluate_fn
75
+ self.selector = selector or ParentSelector(
76
+ strategy=settings.parent_selection_strategy,
77
+ metric=settings.metric,
78
+ top_percent_threshold=settings.top_percent_threshold,
79
+ )
80
+ self.knowledge_base = knowledge_base
81
+ self.rag_top_k = rag_top_k
82
+ self.max_ancestor_depth = max_ancestor_depth
83
+ self.max_descendant_depth = max_descendant_depth
84
+ self.use_compress = use_compress
85
+ self.compressor = compressor
86
+ self.rag_evaluator = rag_evaluator
87
+ self.workers = workers
88
+ self.snapshot_path: str | None = None # ProcessPool: 预序列化路径
89
+ self.rag_metrics_history: list[dict] = [] # 每 round 评估结果
90
+ self.metric_collector = None # 延迟注入, 用于 streaming
91
+ self.hypothesizer = Hypothesizer(
92
+ model=settings.hypothesizer.model,
93
+ max_correction_attempts=settings.hypothesizer.max_correction_attempts,
94
+ seed=settings.hypothesizer.seed,
95
+ knowledge_base=knowledge_base,
96
+ rag_top_k=rag_top_k,
97
+ max_ancestor_depth=max_ancestor_depth,
98
+ max_descendant_depth=max_descendant_depth,
99
+ use_compress=use_compress,
100
+ compressor=compressor,
101
+ )
102
+ self.mutator = Mutator(
103
+ model=settings.mutator.model,
104
+ max_correction_attempts=settings.mutator.max_correction_attempts,
105
+ seed=settings.mutator.seed,
106
+ )
107
+ self.crosser = Crosser(
108
+ model=settings.crosser.model,
109
+ max_correction_attempts=settings.crosser.max_correction_attempts,
110
+ seed=settings.crosser.seed,
111
+ )
112
+
113
+ def _stream_metrics(self, round_idx: int, directions: list[str]) -> None:
114
+ """每轮调用, 更新 metric_collector (如有注入)。"""
115
+ if self.metric_collector is None:
116
+ return
117
+ from ..monitoring import (
118
+ RagMetrics,
119
+ )
120
+ # RAG: H7 - 取最后一条匹配即可 (O(1) vs 之前的 O(R) 扫描)。
121
+ # rag_metrics_history 按 round 追加, 该 round 通常只有 1 条。
122
+ if self.rag_metrics_history and self.rag_metrics_history[-1].get("round") == round_idx:
123
+ m = self.rag_metrics_history[-1]
124
+ self.metric_collector.add_rag(RagMetrics(
125
+ round=m.get("round", round_idx),
126
+ n_queries=m.get("n_queries", 0),
127
+ hit_at_5=m.get("hit_at_5", 0.0),
128
+ ndcg_at_5=m.get("ndcg_at_5", 0.0),
129
+ mrr=m.get("mrr", 0.0),
130
+ diversity=m.get("diversity", 1.0),
131
+ ))
132
+ # Evolution: 累积统计
133
+ self.metric_collector.update_evolution_from_pool(self.pool, round_idx)
134
+ # Quality: 3 通道
135
+ self.metric_collector.update_quality_from_pool(self.pool, round_idx)
136
+
137
+ def sync_knowledge_base(self) -> int:
138
+ """从 pool 同步未索引 entry 到 KB, 返回新加数。"""
139
+ if self.knowledge_base is None:
140
+ return 0
141
+ return self.knowledge_base.sync_from_pool()
142
+
143
+ def _evaluate_rag(
144
+ self,
145
+ round_idx: int,
146
+ directions: list[str],
147
+ ) -> None:
148
+ """RAG 评估: 用 directions 作 query, 评估 Top-K 检索质量。
149
+
150
+ 简化版 ground truth: 当前 pool 中所有 entry 都视为相关
151
+ (实际使用应提供 query→relevant 映射)。
152
+ 结果写入 self.rag_metrics_history。
153
+ """
154
+ if not directions or self.rag_evaluator is None:
155
+ return
156
+ # 构造 token_lists: 每 query 取 Top-K 个 entry 的 token 化文本
157
+ retrieved: list[list[str]] = []
158
+ relevant: list[list[str]] = []
159
+ relevance_scores: list[dict[str, float]] = []
160
+ lineage_ids: list[list[str]] = []
161
+ token_lists: list[list[list[str]]] = []
162
+
163
+ all_entry_ids = {e.entry_id for e in self.pool.all()}
164
+ for d in directions:
165
+ results = self.knowledge_base.query(d, top_k=self.rag_top_k)
166
+ ids = [e.entry_id for e, _ in results]
167
+ retrieved.append(ids)
168
+ relevant.append(list(all_entry_ids)) # 简化: 全部视为相关
169
+ relevance_scores.append({eid: 1.0 for eid in ids})
170
+ # lineage = 检索结果的 ancestors + descendants
171
+ lin_set: set[str] = set()
172
+ from ..knowledge import expand_lineage
173
+ for eid in ids:
174
+ expanded = expand_lineage(
175
+ self.pool, eid,
176
+ max_ancestor_depth=self.max_ancestor_depth,
177
+ max_descendant_depth=self.max_descendant_depth,
178
+ )
179
+ for _, e in expanded["ancestors"] + expanded["descendants"]:
180
+ lin_set.add(e.entry_id)
181
+ lineage_ids.append(list(lin_set))
182
+ # tokens: 用 name + hypothesis 简单分词
183
+ tokens_per_entry: list[list[str]] = []
184
+ for e, _ in results:
185
+ cfg = (e.config_snapshot or {}).get("factor", {}) if e else {}
186
+ toks = []
187
+ if cfg.get("name"):
188
+ toks += cfg["name"].lower().split("_")
189
+ if cfg.get("hypothesis"):
190
+ toks += cfg["hypothesis"].lower().split()
191
+ tokens_per_entry.append(toks)
192
+ token_lists.append(tokens_per_entry)
193
+
194
+ report = self.rag_evaluator.evaluate(
195
+ queries=directions,
196
+ retrieved=retrieved,
197
+ relevant=relevant,
198
+ relevance_scores=relevance_scores,
199
+ lineage_ids=lineage_ids,
200
+ token_lists=token_lists,
201
+ )
202
+ self.rag_metrics_history.append({
203
+ "round": round_idx,
204
+ "n_queries": report.n_queries,
205
+ "hit_at_5": report.hit_at_5,
206
+ "hit_at_10": report.hit_at_10,
207
+ "ndcg_at_5": report.ndcg_at_5,
208
+ "ndcg_at_10": report.ndcg_at_10,
209
+ "mrr": report.mrr,
210
+ "lineage_coverage": report.lineage_coverage,
211
+ "diversity": report.diversity,
212
+ })
213
+
214
+ def run(
215
+ self,
216
+ initial_directions: list[str] | None = None,
217
+ initial_candidates: list[FactorCandidate] | None = None,
218
+ ) -> EvolutionResult:
219
+ """执行完整演化循环。
220
+
221
+ Args:
222
+ initial_directions: round 0 用 — 调 Hypothesizer 生成的假设列表
223
+ initial_candidates: round 0 用 — 直接提供的 FactorCandidate 列表
224
+
225
+ Returns:
226
+ EvolutionResult: 含 best entries + 统计
227
+ """
228
+ if self.evaluate_fn is None:
229
+ raise ValueError("evaluate_fn 不能为 None")
230
+
231
+ result = EvolutionResult()
232
+ no_improve_counter = 0
233
+ best_metric_so_far = float("-inf")
234
+ directions = list(initial_directions or [])
235
+
236
+ # ------------------------------------------------------------------
237
+ # Round 0: 原始候选 (批量评估, 支持并行)
238
+ # ------------------------------------------------------------------
239
+ round0_candidates = self._build_round0(
240
+ initial_directions or [], initial_candidates or [],
241
+ )
242
+ round0_results = self._batch_evaluate_and_record(
243
+ round0_candidates, round_idx=0,
244
+ ops=["original"] * len(round0_candidates),
245
+ parent_ids_list=[[] for _ in round0_candidates],
246
+ )
247
+ for entry in round0_results:
248
+ result.all_entries.append(entry)
249
+ if entry.feedback and entry.feedback.decision:
250
+ result.total_count += 1
251
+ else:
252
+ result.rejected_count += 1
253
+ best_metric_so_far = _update_best(
254
+ best_metric_so_far, entry, self.settings.metric, no_improve_counter,
255
+ )
256
+ result.rounds_completed = 1
257
+ self._stream_metrics(0, directions or [])
258
+
259
+ # ------------------------------------------------------------------
260
+ # Round 1..N: 演化 (workers=1: 串行; workers>1: 并行多候选)
261
+ # ------------------------------------------------------------------
262
+ for round_idx in range(1, self.settings.max_rounds + 1):
263
+ if self.knowledge_base is not None:
264
+ self.knowledge_base.sync_from_pool()
265
+ if self.rag_evaluator is not None and self.knowledge_base is not None:
266
+ self._evaluate_rag(round_idx, directions or [])
267
+
268
+ # 生成本轮候选
269
+ round_candidates: list[FactorCandidate] = []
270
+ round_parent_ids_list: list[list[str]] = []
271
+ round_ops: list[str] = []
272
+
273
+ # parents_per_round 控制 mutation 父数 (默认 1)
274
+ n_mutation_parents = max(1, self.settings.parents_per_round)
275
+ parents_m = self.selector.select(self.pool, n=n_mutation_parents)
276
+ for pm in parents_m:
277
+ pc = FactorCandidate(
278
+ factor_id=pm.entry_id,
279
+ name=pm.feedback.factor_name if pm.feedback else "",
280
+ expression=str(pm.config_snapshot.get("factor", {}).get("expression", "")),
281
+ )
282
+ child_m = self.mutator.mutate(pc)
283
+ round_candidates.append(child_m)
284
+ round_parent_ids_list.append([pm.entry_id])
285
+ round_ops.append("mutation")
286
+
287
+ # crossover 固定需要 2 parents
288
+ parents_x = self.selector.select(self.pool, n=2)
289
+ if len(parents_x) >= 2:
290
+ pcs = [
291
+ FactorCandidate(
292
+ factor_id=e.entry_id,
293
+ name=e.feedback.factor_name if e.feedback else "",
294
+ expression=str(e.config_snapshot.get("factor", {}).get("expression", "")),
295
+ )
296
+ for e in parents_x
297
+ ]
298
+ child_x = self.crossover(pcs[0], pcs[1])
299
+ round_candidates.append(child_x)
300
+ round_parent_ids_list.append([parents_x[0].entry_id, parents_x[1].entry_id])
301
+ round_ops.append("crossover")
302
+
303
+ if not round_candidates:
304
+ break
305
+
306
+ # 批量评估
307
+ batch_entries = self._batch_evaluate_and_record(
308
+ round_candidates, round_idx,
309
+ ops=round_ops, parent_ids_list=round_parent_ids_list,
310
+ )
311
+
312
+ best_in_round = -1.0
313
+ for entry in batch_entries:
314
+ result.all_entries.append(entry)
315
+ if entry.feedback and entry.feedback.decision:
316
+ result.total_count += 1
317
+ else:
318
+ result.rejected_count += 1
319
+ m = float((entry.metrics or {}).get(self.settings.metric, 0) or 0)
320
+ if m > best_in_round:
321
+ best_in_round = m
322
+ if best_in_round > best_metric_so_far:
323
+ best_metric_so_far = best_in_round
324
+ no_improve_counter = 0
325
+ else:
326
+ no_improve_counter += 1
327
+ result.rounds_completed = round_idx
328
+
329
+ # Streaming: 每轮更新 MetricCollector (如有注入)
330
+ self._stream_metrics(round_idx, directions)
331
+
332
+ if (
333
+ self.settings.early_stop_patience > 0
334
+ and no_improve_counter >= self.settings.early_stop_patience
335
+ ):
336
+ break
337
+
338
+ result.best_entries = self.pool.best(
339
+ top_n=self.settings.top_n, metric=self.settings.metric,
340
+ )
341
+ return result
342
+
343
+ # ------------------------------------------------------------------
344
+ # 内部: round 0 构建
345
+ # ------------------------------------------------------------------
346
+
347
+ def _build_round0(
348
+ self,
349
+ directions: list[str],
350
+ candidates: list[FactorCandidate],
351
+ ) -> list[FactorCandidate]:
352
+ """混合 directions + candidates, 调 Hypothesizer 生成 round 0。"""
353
+ out = list(candidates)
354
+ for d in directions:
355
+ cand = self.hypothesizer.hypothesize(direction=d, description=d)
356
+ out.append(cand)
357
+ return out
358
+
359
+ def _batch_evaluate_and_record(
360
+ self,
361
+ candidates: list[FactorCandidate],
362
+ round_idx: int = 0,
363
+ ops: list[str] | None = None,
364
+ parent_ids_list: list[list[str]] | None = None,
365
+ ) -> list[TrajectoryEntry]:
366
+ """批量评估 candidate, 写入 pool, 返回 entry 列表。
367
+
368
+ workers=1 → 串行; workers>1 → ProcessPoolExecutor 并行。
369
+ Quality gate 在主进程中对每个 candidate 串行检查 (避免跨进程 pickle 问题)。
370
+ """
371
+ n = len(candidates)
372
+ if ops is None:
373
+ ops = ["original"] * n
374
+ if parent_ids_list is None:
375
+ parent_ids_list = [[] for _ in range(n)]
376
+
377
+ # 1. Quality gate 短路 (串行, 避免跨进程 pickle)
378
+ valid: list[tuple[int, FactorCandidate, list[str], str]] = []
379
+ for i, (c, op, pids) in enumerate(zip(candidates, ops, parent_ids_list)):
380
+ if self.quality_gate is not None:
381
+ gate = self.quality_gate.check({
382
+ "factor_id": c.factor_id,
383
+ "name": c.name,
384
+ "expression": c.expression,
385
+ "hypothesis": c.hypothesis,
386
+ "description": c.description,
387
+ })
388
+ if not gate["passed"]:
389
+ entry = TrajectoryEntry(
390
+ entry_id=c.factor_id,
391
+ round_idx=round_idx,
392
+ operation=op,
393
+ parent_ids=pids,
394
+ config_snapshot={"factor": {
395
+ "name": c.name, "expression": c.expression,
396
+ "hypothesis": c.hypothesis, "description": c.description,
397
+ }},
398
+ feedback=FactorFeedback(
399
+ factor_id=c.factor_id, factor_name=c.name,
400
+ decision=False, summary=gate["feedback"].summary,
401
+ ),
402
+ metrics={},
403
+ )
404
+ self.pool.add(entry)
405
+ continue
406
+ valid.append((i, c, pids, op))
407
+
408
+ # 收集 rejected entries
409
+ rejected_entries: list[TrajectoryEntry] = []
410
+ for i in range(len(candidates)):
411
+ if i not in {v[0] for v in valid}:
412
+ rejected_entries.append(self.pool.get(candidates[i].factor_id))
413
+
414
+ # 2. 评估
415
+ entries_map: dict[int, TrajectoryEntry] = {}
416
+ to_eval = [c for _, c, _, _ in valid]
417
+ if not to_eval:
418
+ return rejected_entries
419
+
420
+ if self.workers <= 1:
421
+ # 串行: evaluate_fn 返回 tuple (passed, metrics, feedback) 或 dict
422
+ raw_results = [self.evaluate_fn(c) for c in to_eval]
423
+ elif self.snapshot_path is not None:
424
+ # ProcessPool 模式 (真实并行, snapshot 预序列化)
425
+ from ..parallel import parallel_evaluate
426
+ raw_results = parallel_evaluate(
427
+ to_eval, self.evaluate_fn, max_workers=self.workers,
428
+ snapshot_path=self.snapshot_path,
429
+ )
430
+ else:
431
+ # ThreadPool 模式 (无需 pickle, I/O 密集场景)
432
+ from ..parallel import parallel_evaluate, make_worker_evaluate
433
+ worker_fn = make_worker_evaluate(self.evaluate_fn, sleep_ms=0)
434
+ raw_results = parallel_evaluate(
435
+ to_eval, worker_fn, max_workers=self.workers,
436
+ )
437
+
438
+ # 统一转为 dict (evaluate_fn 可能返回 tuple 或 dict)
439
+ results_list = []
440
+ for r in raw_results:
441
+ if isinstance(r, dict):
442
+ results_list.append(r)
443
+ elif isinstance(r, tuple) and len(r) >= 3:
444
+ passed, metrics, feedback = r[0], r[1], r[2]
445
+ results_list.append({
446
+ "passed": bool(passed),
447
+ "metrics": metrics or {},
448
+ "feedback_dict": {
449
+ "factor_id": getattr(feedback, "factor_id", ""),
450
+ "factor_name": getattr(feedback, "factor_name", ""),
451
+ "decision": getattr(feedback, "decision", passed),
452
+ "summary": getattr(feedback, "summary", ""),
453
+ "metadata": getattr(feedback, "metadata", {}),
454
+ "channels": {
455
+ k.value: {"passed": v.passed, "detail": v.detail, "score": v.score}
456
+ for k, v in getattr(feedback, "channels", {}).items()
457
+ },
458
+ } if feedback is not None else None,
459
+ "error": None,
460
+ })
461
+ else:
462
+ results_list.append({
463
+ "passed": False, "metrics": {},
464
+ "feedback_dict": None, "error": str(r),
465
+ })
466
+
467
+ for (i, c, pids, op), res in zip(valid, results_list):
468
+ entry = self._make_entry_from_result(
469
+ c, res, operation=op, parent_ids=pids, round_idx=round_idx,
470
+ )
471
+ self.pool.add(entry)
472
+ entries_map[i] = entry
473
+
474
+ # 3. 返回全部 entry (包括 quality_gate rejected 的)
475
+ all_entries: list[TrajectoryEntry] = []
476
+ for i in range(len(candidates)):
477
+ if i in entries_map:
478
+ all_entries.append(entries_map[i])
479
+ else:
480
+ # quality_gate rejected → 从 pool 获取
481
+ all_entries.append(self.pool.get(candidates[i].factor_id))
482
+ return all_entries
483
+
484
+ def _make_entry_from_result(
485
+ self,
486
+ candidate: FactorCandidate,
487
+ result: dict,
488
+ operation: str,
489
+ parent_ids: list[str],
490
+ round_idx: int = 0,
491
+ ) -> TrajectoryEntry:
492
+ """从 evaluate result dict 构造 TrajectoryEntry。"""
493
+ passed = bool(result.get("passed", False))
494
+ metrics = result.get("metrics", {})
495
+ feedback_dict = result.get("feedback_dict")
496
+ if feedback_dict is None:
497
+ feedback = FactorFeedback(
498
+ factor_id=candidate.factor_id,
499
+ factor_name=candidate.name,
500
+ decision=passed,
501
+ summary=result.get("error") or "ok",
502
+ metadata=metrics,
503
+ )
504
+ else:
505
+ feedback = FactorFeedback(
506
+ factor_id=feedback_dict.get("factor_id") or candidate.factor_id,
507
+ factor_name=feedback_dict.get("factor_name") or candidate.name,
508
+ decision=feedback_dict.get("decision", passed),
509
+ summary=feedback_dict.get("summary", ""),
510
+ metadata=feedback_dict.get("metadata", {}),
511
+ )
512
+ return TrajectoryEntry(
513
+ entry_id=candidate.factor_id,
514
+ round_idx=round_idx,
515
+ operation=operation,
516
+ parent_ids=parent_ids,
517
+ config_snapshot={
518
+ "factor": {
519
+ "name": candidate.name,
520
+ "expression": candidate.expression,
521
+ "hypothesis": candidate.hypothesis,
522
+ "description": candidate.description,
523
+ },
524
+ },
525
+ feedback=feedback,
526
+ metrics=metrics,
527
+ )
528
+
529
+ def crossover(
530
+ self,
531
+ parent1: FactorCandidate,
532
+ parent2: FactorCandidate,
533
+ ) -> FactorCandidate:
534
+ """Public 代理, 便于外部直接调用。"""
535
+ return self.crosser.crossover(parent1, parent2)
536
+
537
+ # ------------------------------------------------------------------
538
+ # 内部: 评估 + 记录
539
+ # ------------------------------------------------------------------
540
+
541
+ def _evaluate_and_record(
542
+ self,
543
+ candidate: FactorCandidate,
544
+ operation: str,
545
+ parent_ids: list[str],
546
+ round_idx: int = 0,
547
+ ) -> TrajectoryEntry:
548
+ """评估单个 candidate, 写入 TrajectoryPool
549
+ (遗留方法, 建议迁移到 _batch_evaluate_and_record)。"""
550
+ results = self._batch_evaluate_and_record(
551
+ [candidate], round_idx=round_idx,
552
+ ops=[operation], parent_ids_list=[parent_ids],
553
+ )
554
+ return results[0]
555
+
556
+ def _evaluate_candidate(
557
+ self,
558
+ candidate: FactorCandidate,
559
+ ) -> tuple[bool, dict, FactorFeedback]:
560
+ """EvolutionLoop.evaluate_fn 回调 (PipelineRunner 用)。"""
561
+ if self.evaluate_fn is not None and self.evaluate_fn is not self._evaluate_candidate:
562
+ return self.evaluate_fn(candidate)
563
+ return False, {}, FactorFeedback(
564
+ factor_id=candidate.factor_id,
565
+ factor_name=candidate.name,
566
+ decision=False, summary="evaluate_fn not set",
567
+ )
568
+
569
+
570
+ def _update_best(
571
+ current_best: float,
572
+ entry: TrajectoryEntry,
573
+ metric: str,
574
+ no_improve_counter: int,
575
+ ) -> float:
576
+ """更新 best metric (向后兼容, 旧逻辑保留)。"""
577
+ val = float((entry.metrics or {}).get(metric, 0) or 0)
578
+ if val > current_best:
579
+ return val
580
+ return current_best
581
+
582
+
583
+