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,504 @@
1
+ # coding=utf-8
2
+ """
3
+ Pandas 工具函数
4
+
5
+ 提供常用的 Pandas 数据处理工具函数,包括:
6
+ - Panel/DataFrame 转换
7
+ - 数据对齐
8
+ - 缺失值处理
9
+ - 数据重塑
10
+ """
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from typing import List, Optional, Union, Any
15
+
16
+
17
+ def panel_to_dataframe(
18
+ panel: Union[dict, List[pd.DataFrame]],
19
+ orientation: str = "ftime"
20
+ ) -> pd.DataFrame:
21
+ """
22
+ 将 Panel(字典或 DataFrame 列表)转换为长格式 DataFrame
23
+
24
+ Args:
25
+ panel: 字典 {item: DataFrame} 或 DataFrame 列表
26
+ orientation: "ftime"(时点在行) 或 "fitem"(item 在行)
27
+
28
+ Returns:
29
+ 长格式 DataFrame,index 为 (time, id),columns 为 item
30
+
31
+ Example:
32
+ >>> data = {f"factor_{i}": df for i, df in enumerate(dfs)}
33
+ >>> result = panel_to_dataframe(data)
34
+ """
35
+ if isinstance(panel, dict):
36
+ dfs = []
37
+ for name, df in panel.items():
38
+ if isinstance(df, pd.DataFrame):
39
+ df = df.copy()
40
+ df.columns = [name] * len(df.columns)
41
+ dfs.append(df.stack().swaplevel())
42
+ elif isinstance(df, pd.Series):
43
+ dfs.append(df)
44
+ if not dfs:
45
+ return pd.DataFrame()
46
+ result = pd.concat(dfs, axis=1)
47
+ result.columns = [c[1] for c in result.columns]
48
+ elif isinstance(panel, list):
49
+ if not panel:
50
+ return pd.DataFrame()
51
+ first = panel[0]
52
+ if isinstance(first, pd.DataFrame):
53
+ dfs = [df.stack() for df in panel]
54
+ result = pd.concat(dfs, axis=1)
55
+ else:
56
+ result = pd.concat(panel, axis=1)
57
+ else:
58
+ raise TypeError(f"panel must be dict or list, got {type(panel)}")
59
+
60
+ if orientation == "ftime":
61
+ result = result.sort_index()
62
+ return result
63
+
64
+
65
+ def dataframe_to_panel(
66
+ df: pd.DataFrame,
67
+ level: Union[str, int] = -1
68
+ ) -> dict:
69
+ """
70
+ 将宽格式 DataFrame 转换为 Panel(字典)
71
+
72
+ Args:
73
+ df: 宽格式 DataFrame,index 为时间,columns 为 item:id
74
+ level: MultiIndex 的层级用于分割成 panel items
75
+
76
+ Returns:
77
+ 字典 {item: DataFrame},每个 DataFrame 的 index 为时间,columns 为 id
78
+
79
+ Example:
80
+ >>> df = pd.DataFrame(...)
81
+ >>> panel = dataframe_to_panel(df, level=0)
82
+ """
83
+ if isinstance(df.columns, pd.MultiIndex):
84
+ items = df.columns.get_level_values(level).unique()
85
+ panel = {}
86
+ for item in items:
87
+ mask = df.columns.get_level_values(level) == item
88
+ panel[item] = df.loc[:, mask].droplevel(level, axis=1)
89
+ return panel
90
+ else:
91
+ return {"_default": df}
92
+
93
+
94
+ def align_dataframes(
95
+ dfs: List[pd.DataFrame],
96
+ how: str = "outer",
97
+ axis: int = 0
98
+ ) -> List[pd.DataFrame]:
99
+ """
100
+ 对齐多个 DataFrame 的索引
101
+
102
+ Args:
103
+ dfs: DataFrame 列表
104
+ how: 对齐方式 ("inner", "outer", "left", "right")
105
+ axis: 对齐轴 (0=行索引, 1=列)
106
+
107
+ Returns:
108
+ 对齐后的 DataFrame 列表
109
+
110
+ Example:
111
+ >>> aligned = align_dataframes([df1, df2, df3])
112
+ """
113
+ if not dfs:
114
+ return []
115
+ if len(dfs) == 1:
116
+ return dfs
117
+
118
+ if axis == 0:
119
+ if how == "outer":
120
+ index = dfs[0].index
121
+ for df in dfs[1:]:
122
+ index = index.union(df.index)
123
+ elif how == "inner":
124
+ index = dfs[0].index
125
+ for df in dfs[1:]:
126
+ index = index.intersection(df.index)
127
+ elif how == "left":
128
+ index = dfs[0].index
129
+ elif how == "right":
130
+ index = dfs[-1].index
131
+ else:
132
+ raise ValueError(f"how must be one of inner/outer/left/right, got {how}")
133
+
134
+ return [df.reindex(index) for df in dfs]
135
+ else:
136
+ if how == "outer":
137
+ columns = dfs[0].columns
138
+ for df in dfs[1:]:
139
+ columns = columns.union(df.columns)
140
+ elif how == "inner":
141
+ columns = dfs[0].columns
142
+ for df in dfs[1:]:
143
+ columns = columns.intersection(df.columns)
144
+ elif how == "left":
145
+ columns = dfs[0].columns
146
+ elif how == "right":
147
+ columns = dfs[-1].columns
148
+
149
+ return [df.reindex(columns, axis=1) for df in dfs]
150
+
151
+
152
+ def forward_fill_panel(
153
+ panel: dict,
154
+ limit: Optional[int] = None
155
+ ) -> dict:
156
+ """
157
+ 对 Panel 中的每个 DataFrame 执行前向填充
158
+
159
+ Args:
160
+ panel: Panel 字典 {item: DataFrame}
161
+ limit: 最大填充期数
162
+
163
+ Returns:
164
+ 填充后的 Panel
165
+
166
+ Example:
167
+ >>> filled = forward_fill_panel(panel, limit=5)
168
+ """
169
+ result = {}
170
+ for name, df in panel.items():
171
+ if isinstance(df, pd.DataFrame):
172
+ result[name] = df.ffill(limit=limit)
173
+ else:
174
+ result[name] = df
175
+ return result
176
+
177
+
178
+ def fillna_by_value(
179
+ df: pd.DataFrame,
180
+ value: Union[float, dict],
181
+ condition: Optional[pd.DataFrame] = None
182
+ ) -> pd.DataFrame:
183
+ """
184
+ 按条件填充 NaN 值
185
+
186
+ Args:
187
+ df: 输入 DataFrame
188
+ value: 填充值,如果是字典则为 {column: value}
189
+ condition: 条件 DataFrame,True 的位置才填充
190
+
191
+ Returns:
192
+ 填充后的 DataFrame
193
+
194
+ Example:
195
+ >>> df = fillna_by_value(df, 0)
196
+ >>> df = fillna_by_value(df, {"col1": -999, "col2": 0})
197
+ """
198
+ result = df.copy()
199
+ if isinstance(value, dict):
200
+ for col, val in value.items():
201
+ if col in result.columns:
202
+ mask = result[col].isna()
203
+ if condition is not None:
204
+ mask = mask & condition[col]
205
+ result.loc[mask, col] = val
206
+ else:
207
+ mask = result.isna()
208
+ if condition is not None:
209
+ mask = mask & condition
210
+ result = result.fillna(value)
211
+ result = result.where(~mask, other=value)
212
+ return result
213
+
214
+
215
+ def winsorize_series(
216
+ s: pd.Series,
217
+ lower: float = 0.01,
218
+ upper: float = 0.01
219
+ ) -> pd.Series:
220
+ """
221
+ 对 Series 进行 Winsorize 去极值处理
222
+
223
+ Args:
224
+ s: 输入 Series
225
+ lower: 下界百分位
226
+ upper: 上界百分位
227
+
228
+ Returns:
229
+ 去极值后的 Series
230
+
231
+ Example:
232
+ >>> s = winsorize_series(factor, lower=0.02, upper=0.02)
233
+ """
234
+ if len(s) == 0:
235
+ return s
236
+
237
+ lower_val = np.nanpercentile(s, lower * 100)
238
+ upper_val = np.nanpercentile(s, (1 - upper) * 100)
239
+
240
+ result = s.clip(lower=lower_val, upper=upper_val)
241
+ return result
242
+
243
+
244
+ def standardize_zscore(
245
+ df: pd.DataFrame,
246
+ columns: Optional[List[str]] = None,
247
+ ddof: int = 1
248
+ ) -> pd.DataFrame:
249
+ """
250
+ Z-Score 标准化
251
+
252
+ Args:
253
+ df: 输入 DataFrame
254
+ columns: 要标准化的列,None 表示全部
255
+ ddof: 标准差自由度
256
+
257
+ Returns:
258
+ 标准化后的 DataFrame
259
+
260
+ Example:
261
+ >>> df = standardize_zscore(factor_df)
262
+ """
263
+ result = df.copy()
264
+ cols = columns if columns is not None else df.columns
265
+
266
+ for col in cols:
267
+ if col in result.columns:
268
+ mean = result[col].mean()
269
+ std = result[col].std(ddof=ddof)
270
+ if std > 0:
271
+ result[col] = (result[col] - mean) / std
272
+ else:
273
+ result[col] = 0
274
+
275
+ return result
276
+
277
+
278
+ def standardize_rank(
279
+ s: pd.Series,
280
+ ascending: bool = True,
281
+ pct: bool = True
282
+ ) -> pd.Series:
283
+ """
284
+ Rank 标准化
285
+
286
+ Args:
287
+ s: 输入 Series
288
+ ascending: 是否升序
289
+ pct: 是否返回百分位 (0-1)
290
+
291
+ Returns:
292
+ Rank 标准化后的 Series
293
+
294
+ Example:
295
+ >>> rank_factor = standardize_rank(factor)
296
+ """
297
+ if len(s) == 0:
298
+ return s
299
+
300
+ rank = s.rank(ascending=ascending, pct=pct)
301
+ return rank
302
+
303
+
304
+ def cross_section_zscore(
305
+ df: pd.DataFrame,
306
+ groupby: Optional[Any] = None
307
+ ) -> pd.DataFrame:
308
+ """
309
+ 截面 Z-Score 标准化
310
+
311
+ 对每个时间点的截面数据进行标准化
312
+
313
+ Args:
314
+ df: 输入 DataFrame,index 为时间,columns 为 ID
315
+ groupby: 分组列(如果有)
316
+
317
+ Returns:
318
+ 标准化后的 DataFrame
319
+
320
+ Example:
321
+ >>> zscored = cross_section_zscore(factor_df)
322
+ """
323
+ if groupby is None:
324
+ return df.apply(lambda x: (x - x.mean()) / x.std() if x.std() > 0 else x - x.mean(), axis=1)
325
+ else:
326
+ return df.groupby(groupby).transform(
327
+ lambda x: (x - x.mean()) / x.std() if x.std() > 0 else x - x.mean()
328
+ )
329
+
330
+
331
+ def cross_section_rank(
332
+ df: pd.DataFrame,
333
+ ascending: bool = False,
334
+ pct: bool = True
335
+ ) -> pd.DataFrame:
336
+ """
337
+ 截面排名标准化
338
+
339
+ Args:
340
+ df: 输入 DataFrame
341
+ ascending: 是否升序
342
+ pct: 是否返回百分位
343
+
344
+ Returns:
345
+ 排名标准化后的 DataFrame
346
+
347
+ Example:
348
+ >>> ranked = cross_section_rank(factor_df)
349
+ """
350
+ return df.rank(ascending=ascending, pct=pct, axis=1)
351
+
352
+
353
+ def shift_df(
354
+ df: pd.DataFrame,
355
+ periods: int,
356
+ freq: Optional[str] = None
357
+ ) -> pd.DataFrame:
358
+ """
359
+ 移动 DataFrame(支持按频率)
360
+
361
+ Args:
362
+ df: 输入 DataFrame
363
+ periods: 移动期数
364
+ freq: 频率字符串(如 "D", "M")
365
+
366
+ Returns:
367
+ 移动后的 DataFrame
368
+
369
+ Example:
370
+ >>> shifted = shift_df(factor_df, periods=1)
371
+ """
372
+ if freq is None:
373
+ return df.shift(periods)
374
+ else:
375
+ if not isinstance(df.index, pd.DatetimeIndex):
376
+ raise ValueError("freq requires DatetimeIndex")
377
+ new_index = df.index.shift(periods, freq=freq)
378
+ return df.set_index(new_index)
379
+
380
+
381
+ def resample_panel(
382
+ panel: dict,
383
+ rule: str,
384
+ agg_func: str = "last"
385
+ ) -> dict:
386
+ """
387
+ 对 Panel 按规则重采样
388
+
389
+ Args:
390
+ panel: Panel 字典
391
+ rule: 重采样规则(如 "M", "Q", "Y")
392
+ agg_func: 聚合函数
393
+
394
+ Returns:
395
+ 重采样后的 Panel
396
+
397
+ Example:
398
+ >>> monthly = resample_panel(daily_panel, rule="M")
399
+ """
400
+ result = {}
401
+ for name, df in panel.items():
402
+ if isinstance(df, pd.DataFrame) and isinstance(df.index, pd.DatetimeIndex):
403
+ result[name] = df.resample(rule).agg(agg_func)
404
+ else:
405
+ result[name] = df
406
+ return result
407
+
408
+
409
+ def melt_panel(
410
+ panel: dict,
411
+ var_name: str = "item",
412
+ value_name: str = "value"
413
+ ) -> pd.DataFrame:
414
+ """
415
+ 将 Panel 转换为长格式
416
+
417
+ Args:
418
+ panel: Panel 字典
419
+ var_name: 变量名列名
420
+ value_name: 值列名
421
+
422
+ Returns:
423
+ 长格式 DataFrame
424
+
425
+ Example:
426
+ >>> long_df = melt_panel(panel)
427
+ """
428
+ dfs = []
429
+ for name, df in panel.items():
430
+ if isinstance(df, pd.DataFrame):
431
+ temp = df.copy()
432
+ temp[var_name] = name
433
+ dfs.append(temp.reset_index())
434
+ if not dfs:
435
+ return pd.DataFrame()
436
+ return pd.concat(dfs, ignore_index=True)
437
+
438
+
439
+ def pivot_long(
440
+ df: pd.DataFrame,
441
+ id_vars: List[str],
442
+ value_vars: List[str],
443
+ var_name: str = "variable",
444
+ value_name: str = "value"
445
+ ) -> pd.DataFrame:
446
+ """
447
+ 将宽格式 DataFrame 转换为长格式(类似 pd.melt)
448
+
449
+ Args:
450
+ df: 输入 DataFrame
451
+ id_vars: ID 列
452
+ value_vars: 值列
453
+ var_name: 变量列名
454
+ value_name: 值列名
455
+
456
+ Returns:
457
+ 长格式 DataFrame
458
+ """
459
+ return pd.melt(
460
+ df, id_vars=id_vars, value_vars=value_vars,
461
+ var_name=var_name, value_name=value_name,
462
+ )
463
+
464
+
465
+ def pivot_wide(
466
+ df: pd.DataFrame,
467
+ index: Union[str, List[str]],
468
+ columns: str,
469
+ values: str,
470
+ aggfunc: str = "last"
471
+ ) -> pd.DataFrame:
472
+ """
473
+ 将长格式 DataFrame 转换为宽格式(类似 pd.pivot)
474
+
475
+ Args:
476
+ df: 输入 DataFrame
477
+ index: 索引列
478
+ columns: 列名来源列
479
+ values: 值来源列
480
+ aggfunc: 聚合函数
481
+
482
+ Returns:
483
+ 宽格式 DataFrame
484
+ """
485
+ return pd.pivot_table(df, index=index, columns=columns, values=values, aggfunc=aggfunc)
486
+
487
+
488
+ __all__ = [
489
+ "panel_to_dataframe",
490
+ "dataframe_to_panel",
491
+ "align_dataframes",
492
+ "forward_fill_panel",
493
+ "fillna_by_value",
494
+ "winsorize_series",
495
+ "standardize_zscore",
496
+ "standardize_rank",
497
+ "cross_section_zscore",
498
+ "cross_section_rank",
499
+ "shift_df",
500
+ "resample_panel",
501
+ "melt_panel",
502
+ "pivot_long",
503
+ "pivot_wide",
504
+ ]
@@ -0,0 +1,15 @@
1
+ """Parallel — 多进程评估池。
2
+
3
+ 公开 API:
4
+ - parallel_evaluate(candidates, evaluate_fn, max_workers): 并行评估
5
+ - make_worker_evaluate(base, sleep_ms): 构造 worker 函数
6
+ - detect_max_workers(default): 推荐 max_workers
7
+ """
8
+ from .worker import _heavy_evaluate, detect_max_workers, make_worker_evaluate, parallel_evaluate
9
+
10
+ __all__ = [
11
+ "parallel_evaluate",
12
+ "make_worker_evaluate",
13
+ "detect_max_workers",
14
+ "_heavy_evaluate",
15
+ ]
@@ -0,0 +1,140 @@
1
+ """多进程评估池 — 并行 evaluate 多个 candidate。
2
+
3
+ 设计:
4
+ - evaluate_fn 必须是顶层函数 (模块级), 才能被 pickle 传给子进程
5
+ - 子进程内执行的 evaluate_fn 接受 FactorCandidate, 返回 (passed, metrics, feedback)
6
+ - EvolutionLoop 在 round 0 / round N 中, 把所有 candidate 通过 ThreadPoolExecutor.map 批量评估
7
+ - 使用 ThreadPool 而非 ProcessPool (runner._evaluate_candidate 是 bound method, 不可 pickle)
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import random as _random_mod
13
+ import time
14
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
15
+ from typing import Callable, Optional
16
+
17
+
18
+ # ── 顶层 pickle-safe evaluate 函数 ──────────────────────────
19
+
20
+ def _heavy_evaluate(
21
+ candidate_dict: dict,
22
+ sleep_ms: int = 0,
23
+ ) -> dict:
24
+ """在子进程中执行 evaluate, 接受 dict (pickle 友好), 返回 dict。
25
+
26
+ Args:
27
+ candidate_dict: FactorCandidate 的 dict 形式
28
+ sleep_ms: 模拟重计算 (测试用, ms)
29
+
30
+ Returns:
31
+ dict 含 passed / metrics / feedback_dict / error
32
+ """
33
+ if sleep_ms > 0:
34
+ time.sleep(sleep_ms / 1000.0)
35
+ # H3: 改用 hashlib.sha256 (跨进程幂等) 替代 hash()
36
+ expression = candidate_dict.get("expression", "")
37
+ digest = hashlib.sha256(expression.encode("utf-8")).digest()
38
+ seed = int.from_bytes(digest[:4], "big")
39
+ rng = _random_mod.Random(seed)
40
+ sharpe = rng.uniform(0.0, 2.0)
41
+ return {
42
+ "passed": True,
43
+ "metrics": {"sharpe": sharpe, "arr": sharpe * 0.1, "ic_mean": 0.04},
44
+ "feedback_dict": {
45
+ "factor_id": candidate_dict.get("factor_id", ""),
46
+ "factor_name": candidate_dict.get("name", ""),
47
+ "decision": True,
48
+ "summary": f"sharpe={sharpe:.2f}",
49
+ "metadata": {},
50
+ "channels": {},
51
+ },
52
+ "error": None,
53
+ }
54
+
55
+
56
+ def make_worker_evaluate(
57
+ base_evaluate: Optional[Callable] = None,
58
+ sleep_ms: int = 0,
59
+ ) -> Callable:
60
+ """构造 workers>1 时的 evaluate 函数。
61
+
62
+ base_evaluate=None → 使用 _heavy_evaluate (mock, pickle-safe)。
63
+ base_evaluate 提供 → 使用它 (线程池, 不需 pickle)。
64
+ """
65
+ if base_evaluate is None:
66
+ return lambda c: _heavy_evaluate(c.__dict__ if hasattr(c, "__dict__") else c, sleep_ms)
67
+ return base_evaluate
68
+
69
+
70
+ # ── 并行评估 (ThreadPoolExecutor) ──────────────────────────
71
+
72
+ def parallel_evaluate(
73
+ candidates: list,
74
+ evaluate_fn: Callable,
75
+ max_workers: int = 4,
76
+ snapshot_path: str | None = None,
77
+ ) -> list[dict]:
78
+ """并行评估多个 candidate, 返回 list。
79
+
80
+ - max_workers=1: 串行
81
+ - snapshot_path 提供: ProcessPoolExecutor (真实并行, 适合大回测)
82
+ - 否则: ThreadPoolExecutor (无需 pickle, 适合 I/O 密集)
83
+
84
+ Args:
85
+ candidates: FactorCandidate 列表
86
+ evaluate_fn: 评估函数 (ThreadPool 时用)
87
+ max_workers: 并行数 (1=串行)
88
+ snapshot_path: 预序列化的 config+context 路径 (ProcessPool 模式)
89
+
90
+ Returns:
91
+ list[tuple|dict|result_dict] 顺序与 candidates 对应
92
+ """
93
+ if max_workers <= 1:
94
+ return [evaluate_fn(c) for c in candidates]
95
+
96
+ if snapshot_path is not None:
97
+ # ProcessPool 模式 (真实并行)
98
+ from .worker_process import subprocess_evaluate
99
+ with ProcessPoolExecutor(max_workers=max_workers) as executor:
100
+ futures = {
101
+ executor.submit(subprocess_evaluate, c.__dict__, snapshot_path): i
102
+ for i, c in enumerate(candidates)
103
+ }
104
+ results: list[Optional[dict]] = [None] * len(candidates)
105
+ for fut in as_completed(futures):
106
+ idx = futures[fut]
107
+ try:
108
+ results[idx] = fut.result()
109
+ except Exception as e:
110
+ results[idx] = {
111
+ "passed": False, "metrics": {},
112
+ "feedback_dict": None, "error": str(e),
113
+ }
114
+ return results # type: ignore
115
+
116
+ # ThreadPool 模式 (无需 pickle)
117
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
118
+ futures = {executor.submit(evaluate_fn, c): i for i, c in enumerate(candidates)}
119
+ results = [None] * len(candidates)
120
+ for fut in as_completed(futures):
121
+ idx = futures[fut]
122
+ try:
123
+ results[idx] = fut.result()
124
+ except Exception:
125
+ results[idx] = (False, {}, None)
126
+ return results # type: ignore
127
+
128
+
129
+ # ── 进程数检测 ──────────────────────────────────────────────
130
+
131
+ def detect_max_workers(default: int = 4) -> int:
132
+ """检测可用 CPU 核数, 返回推荐 max_workers。"""
133
+ import multiprocessing as mp
134
+ try:
135
+ cpu_count = mp.cpu_count()
136
+ if cpu_count is None or cpu_count < 1:
137
+ return default
138
+ return min(cpu_count, default * 2)
139
+ except Exception:
140
+ return default