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,309 @@
1
+ # coding: utf-8
2
+ """E2E: 完整 12 节点 + 3 轮演化 + 报告生成。
3
+
4
+ 用法:
5
+ # 1. 准备数据 (HDF5)
6
+ python -m QuantNodes.research.factor_test.e2e.data_prep \\
7
+ --output-dir /tmp/e2e_data/
8
+
9
+ # 2. 运行 E2E
10
+ python -m QuantNodes.research.factor_test.e2e.run_evolution_e2e \\
11
+ --data-path /tmp/e2e_data/ \\
12
+ --directions momentum,reversal,volatility \\
13
+ --max-rounds 3
14
+
15
+ 输出:
16
+ {output_dir}/
17
+ ├── trajectory/ # TrajectoryPool (Parquet + JSON)
18
+ │ ├── trajectories.parquet
19
+ │ └── {entry_id}.json
20
+ ├── feedback.parquet # FactorFeedback 持久化 (如启用)
21
+ ├── evolution_report.html # 可视化报告
22
+ └── evolution_summary.json # 演化统计
23
+
24
+ E2E 验证:
25
+ 1. PipelineRunner.from_dict() 不报错
26
+ 2. 12 节点全部执行成功
27
+ 3. QualityGate 拦截低质量因子
28
+ 4. TrajectoryPool 写入 5+ entries (3 original + 1 mutation + 1 crossover)
29
+ 5. Visualization HTML 包含 5 个 figure
30
+ 6. RAG 评估指标 > 0 (说明检索非空)
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import argparse
35
+ import json
36
+ import sys
37
+ from datetime import datetime, timedelta
38
+ from pathlib import Path
39
+
40
+ import pandas as pd
41
+
42
+ from QuantNodes.core.knowledge import (
43
+ IdentityRetriever,
44
+ KnowledgeBase,
45
+ RAGEvaluator,
46
+ )
47
+ from QuantNodes.core.path_utils import ensure_dir
48
+ from QuantNodes.core.visualization import generate_html
49
+ from QuantNodes.research.factor_test.config import SingleFactorTestConfig
50
+ from QuantNodes.research.factor_test.config_builder import (
51
+ SingleFactorTestConfigBuilder,
52
+ )
53
+ from QuantNodes.research.factor_test.pipeline_runner import PipelineRunner
54
+
55
+
56
+ def _build_config(
57
+ data_path: str,
58
+ factor_name: str,
59
+ factor_dir: str,
60
+ directions: list[str],
61
+ output_dir: str,
62
+ max_rounds: int = 3,
63
+ enable_quality_gate: bool = True,
64
+ ) -> SingleFactorTestConfig:
65
+ """构造 SingleFactorTestConfig (M6: 移除未用的 enable_kb 参数)。
66
+
67
+ Phase 3.4: 改用 SingleFactorTestConfigBuilder 流式构造 (bitwise 等价)。
68
+ """
69
+ # H12: 不再硬编码 20260101/20260630, 默认 1 年前到 1 个月前 (滚动)
70
+ one_year_ago = int((datetime.now() - timedelta(days=365)).strftime('%Y%m%d'))
71
+ one_month_ago = int((datetime.now() - timedelta(days=30)).strftime('%Y%m%d'))
72
+ return (
73
+ SingleFactorTestConfigBuilder()
74
+ .factor(
75
+ factor_name, factor_dir,
76
+ hypothesis=directions[0] if directions else "momentum",
77
+ description=f"e2e test factor: {factor_name}",
78
+ )
79
+ .dates(one_year_ago, one_month_ago)
80
+ .adj_mode(["M", "end"])
81
+ .sample("all", "all")
82
+ .tradable(
83
+ no_st=True, no_suspended=True, no_up_down_limit=False,
84
+ min_ipo_days=360,
85
+ )
86
+ .preprocess(missing="", extreme="median", norm="zscore")
87
+ .neutralize(industry=False, risk=False, risk_factors=[])
88
+ .ic(min_group_size=5)
89
+ .groups(5, direction=1, floor_mode="group", hedge="equal")
90
+ .longshort(direction=1)
91
+ .score(enabled=True)
92
+ .risk_corr("")
93
+ .output(output_dir, fmt=["json"])
94
+ .feedback(enabled=False)
95
+ .quality_gate(enabled=enable_quality_gate)
96
+ .evolution(
97
+ enabled=True, max_rounds=max_rounds,
98
+ parent_selection_strategy="top_percent_plus_random",
99
+ top_percent_threshold=0.5,
100
+ metric="sharpe",
101
+ early_stop_patience=0,
102
+ )
103
+ .data_path(data_path)
104
+ .load_keys(
105
+ ["cp", "id_citic1", "mv_float", "st", "suspend", "ud_limit", "ipo_days"]
106
+ )
107
+ .build()
108
+ )
109
+
110
+
111
+
112
+ def _inject_prepared_data(runner: PipelineRunner, data_path: Path) -> None:
113
+ """预填 _context['LoadData'], 跳过 LoadDataNode 真实 H5 读取。
114
+
115
+ 名称: 从已 prepare 好的 H5 数据 (data_prep.py 输出) 注入到 ctx,
116
+ 与"合成"无关 (R4 重命名: ``_inject_synthetic_data`` → ``_inject_prepared_data``).
117
+ """
118
+ # 读 H5 实际数据
119
+ cp = pd.read_hdf(data_path / "stk_daily.h5", key="cp")
120
+ st = pd.read_hdf(data_path / "stk_daily.h5", key="st")
121
+ suspend = pd.read_hdf(data_path / "stk_daily.h5", key="suspend")
122
+ ud_limit = pd.read_hdf(data_path / "stk_daily.h5", key="ud_limit")
123
+ ipo_days = pd.read_hdf(data_path / "stk_daily.h5", key="ipo_days")
124
+ industry = pd.read_hdf(data_path / "stk_daily.h5", key="id_citic1")
125
+ mv = pd.read_hdf(data_path / "stk_daily.h5", key="mv_float")
126
+ factor = pd.read_hdf(data_path / f"{runner.config.factor.name}.h5", key="data")
127
+ index_cp = pd.read_hdf(data_path / "index_daily.h5", key="index_cp")
128
+ stklist = pd.read_hdf(data_path / "stklist.h5", key="data")
129
+ trade_dt = pd.read_hdf(data_path / "trade_dt.h5", key="data")
130
+
131
+ runner._context["LoadData"] = {
132
+ "factor": factor,
133
+ "price": cp,
134
+ "id_citic1": industry,
135
+ "mv_float": mv,
136
+ "st": st,
137
+ "suspend": suspend,
138
+ "ud_limit": ud_limit,
139
+ "ipo_days": ipo_days,
140
+ "index_cp": index_cp,
141
+ "stklist": stklist,
142
+ "trade_dt": trade_dt,
143
+ "_loader": _build_loader(str(data_path)),
144
+ }
145
+
146
+
147
+ # 向后兼容: R4 重命名前的旧名称
148
+ _inject_synthetic_data = _inject_prepared_data
149
+
150
+
151
+ def _build_loader(data_path: str):
152
+ """构造 DataLoader (供 RiskCorrelationNode 使用)。"""
153
+ from QuantNodes.research.factor_test.utils.data_loader import DataLoader
154
+ return DataLoader(data_path)
155
+
156
+
157
+ def _build_parser() -> argparse.ArgumentParser:
158
+ """构造 E2E 演化 CLI 参数解析器。"""
159
+ parser = argparse.ArgumentParser(description="E2E 演化运行")
160
+ parser.add_argument("--data-path", required=True, help="data_prep 输出目录")
161
+ parser.add_argument("--factor-name", default="momentum_20d", help="起始因子名")
162
+ parser.add_argument("--directions", default="momentum,reversal,volatility",
163
+ help="逗号分隔的研究方向 (用作 RAG query + 初始 directions)")
164
+ parser.add_argument("--output-dir", default="/tmp/e2e_output/",
165
+ help="输出目录 (报告 / trajectory / summary)")
166
+ parser.add_argument("--max-rounds", type=int, default=3)
167
+ parser.add_argument("--disable-quality-gate", action="store_true",
168
+ help="禁用 QualityGate (默认启用)")
169
+ # M22-M23: 演化参数可配置 (默认保持原有值不变)
170
+ parser.add_argument("--rag-top-k", type=int, default=3,
171
+ help="RAG 检索 Top-K (默认 3)")
172
+ parser.add_argument("--ancestor-depth", type=int, default=2,
173
+ help="知识谱系祖先深度 (默认 2)")
174
+ parser.add_argument("--descendant-depth", type=int, default=2,
175
+ help="知识谱系列脉深度 (默认 2)")
176
+ parser.add_argument("--no-compress", action="store_true",
177
+ help="禁用谱系压缩 (默认启用)")
178
+ return parser
179
+
180
+
181
+ def main():
182
+ parser = _build_parser()
183
+ args = parser.parse_args()
184
+
185
+ data_path = Path(args.data_path)
186
+ output_dir = Path(args.output_dir)
187
+ ensure_dir(output_dir)
188
+ directions = [d.strip() for d in args.directions.split(",") if d.strip()]
189
+
190
+ print("=" * 70)
191
+ print("E2E 演化实验")
192
+ print(f" data_path: {data_path}")
193
+ print(f" output_dir: {output_dir}")
194
+ print(f" directions: {directions}")
195
+ print(f" max_rounds: {args.max_rounds}")
196
+ print("=" * 70)
197
+
198
+ # 1. 构造 config + runner
199
+ cfg = _build_config(
200
+ data_path=str(data_path),
201
+ factor_name=args.factor_name,
202
+ factor_dir=f"{args.factor_name}.h5",
203
+ directions=directions,
204
+ output_dir=str(output_dir),
205
+ max_rounds=args.max_rounds,
206
+ enable_quality_gate=not args.disable_quality_gate,
207
+ )
208
+ runner = PipelineRunner(cfg)
209
+ _inject_prepared_data(runner, data_path)
210
+ print(f"\n[1/5] 注入 LoadData: factor={runner._context['LoadData']['factor'].shape}")
211
+
212
+ # 2. 先单次回测 (验证 12 节点)
213
+ print("\n[2/5] 单次回测 (验证 12 节点 + QualityGate)...")
214
+ try:
215
+ ctx = runner.run()
216
+ print(f" ✓ 12 节点全部执行, ctx keys: {list(ctx.keys())}")
217
+ # 打印 IC
218
+ if "ICAnalyzer" in ctx:
219
+ ic_res = ctx["ICAnalyzer"].get("ic_result", {})
220
+ print(f" IC均值: {ic_res.get('IC均值', 'N/A')}")
221
+ except Exception as e:
222
+ print(f" ✗ 单次回测失败: {e}")
223
+ return 1
224
+
225
+ # 3. 设置演化组件
226
+ print("\n[3/5] 构造演化组件...")
227
+ pool = runner._build_trajectory_pool()
228
+ quality_gate = runner._build_quality_gate()
229
+
230
+ kb = KnowledgeBase(IdentityRetriever(), pool=pool)
231
+ evaluator = RAGEvaluator()
232
+ print(f" ✓ TrajectoryPool: {pool.base_dir}")
233
+ print(f" ✓ QualityGateNode: {'enabled' if quality_gate else 'disabled'}")
234
+ print(" ✓ KnowledgeBase + RAGEvaluator (TF-IDF always enabled)")
235
+
236
+ # 4. 演化循环
237
+ print(f"\n[4/5] 演化循环 ({args.max_rounds} 轮)...")
238
+ from QuantNodes.core.evolution import EvolutionLoop, EvolutionSetting as ES
239
+ settings = ES(
240
+ enabled=True, max_rounds=args.max_rounds,
241
+ parent_selection_strategy="top_percent_plus_random",
242
+ top_percent_threshold=0.5,
243
+ metric="sharpe", seed=42,
244
+ )
245
+ loop = EvolutionLoop(
246
+ settings, pool=pool,
247
+ quality_gate=quality_gate,
248
+ evaluate_fn=runner._evaluate_candidate,
249
+ knowledge_base=kb,
250
+ rag_evaluator=evaluator,
251
+ rag_top_k=args.rag_top_k,
252
+ max_ancestor_depth=args.ancestor_depth,
253
+ max_descendant_depth=args.descendant_depth,
254
+ use_compress=not args.no_compress,
255
+ )
256
+ result = loop.run(initial_directions=directions)
257
+ print(
258
+ f" ✓ 演化完成: {result.rounds_completed} 轮, "
259
+ f"总数 {result.total_count}, 拒绝 {result.rejected_count}"
260
+ )
261
+ if kb is not None:
262
+ print(f" ✓ KB 已索引 {len(kb)} entry")
263
+ print(f" ✓ RAG 评估历史: {len(loop.rag_metrics_history)} 轮")
264
+ if loop.rag_metrics_history:
265
+ m = loop.rag_metrics_history[-1]
266
+ print(f" Round {m['round']}: HR@5={m['hit_at_5']:.3f} NDCG@5={m['ndcg_at_5']:.3f}")
267
+
268
+ # 5. 报告生成
269
+ print("\n[5/5] 生成报告...")
270
+ report_path = output_dir / "evolution_report.html"
271
+ try:
272
+ generate_html(pool, metric="sharpe",
273
+ title=f"E2E 演化报告: {data_path.name}",
274
+ output_path=report_path)
275
+ print(f" ✓ HTML 报告: {report_path} ({report_path.stat().st_size} bytes)")
276
+ except Exception as e:
277
+ print(f" ✗ HTML 报告失败: {e}")
278
+
279
+ summary = {
280
+ "data_path": str(data_path),
281
+ "output_dir": str(output_dir),
282
+ "directions": directions,
283
+ "max_rounds": args.max_rounds,
284
+ "pool_size": pool.size,
285
+ "rounds_completed": result.rounds_completed,
286
+ "total_count": result.total_count,
287
+ "rejected_count": result.rejected_count,
288
+ "best_entries": [
289
+ {
290
+ "id": e.entry_id,
291
+ "name": e.feedback.factor_name if e.feedback else "",
292
+ "operation": e.operation,
293
+ "round": e.round_idx,
294
+ "sharpe": (e.metrics or {}).get("sharpe", 0),
295
+ }
296
+ for e in result.best_entries[:5]
297
+ ],
298
+ "rag_metrics_history": loop.rag_metrics_history,
299
+ }
300
+ summary_path = output_dir / "evolution_summary.json"
301
+ summary_path.write_text(json.dumps(summary, ensure_ascii=False, indent=2))
302
+ print(f" ✓ JSON 摘要: {summary_path}")
303
+
304
+ print(f"\n{'=' * 70}\n✓ E2E 完成\n{'=' * 70}")
305
+ return 0
306
+
307
+
308
+ if __name__ == "__main__":
309
+ sys.exit(main())
@@ -0,0 +1,231 @@
1
+ # coding: utf-8
2
+ """演化适配器 / Evolution Adapter.
3
+
4
+ 把 ``PipelineRunner`` 与 ``QuantNodes.core.evolution`` 框架对接:
5
+
6
+ - 构造 ``QualityGateNode`` (按 ``cfg.quality_gate.enabled``)
7
+ - 构造 ``TrajectoryPool`` (按 ``cfg.evolution.enabled``)
8
+ - 构造 ``EvolutionLoop`` (含 evaluate_fn)
9
+ - ``evaluate_candidate`` / ``run_one_factor`` 子例程
10
+ - ``run_evolution`` 主入口 (含 ProcessPool 快照预序列化 + Streaming 集成)
11
+
12
+ Phase R2 (2026-06-19): 从 pipeline_runner.py 抽出, 单一职责.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import logging
18
+ from contextlib import contextmanager
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Optional
21
+
22
+ from QuantNodes.core.evolution import (
23
+ EvolutionLoop,
24
+ EvolutionResult,
25
+ EvolutionSetting,
26
+ FactorCandidate,
27
+ )
28
+ from QuantNodes.core.feedback import FactorFeedback
29
+ from QuantNodes.core.monitoring import MetricCollector, generate_dashboard_html
30
+ from QuantNodes.core.parallel.worker_process import prepare_snapshot
31
+ from QuantNodes.core.quality_gate import (
32
+ FactorZoo,
33
+ QualityGateNode,
34
+ QualityGateSetting,
35
+ )
36
+ from QuantNodes.core.trajectory import TrajectoryPool
37
+ from QuantNodes.research.factor_test.utils.metrics_extractor import (
38
+ extract_metrics_from_ctx,
39
+ )
40
+
41
+ logger = logging.getLogger(__name__)
42
+
43
+ if TYPE_CHECKING:
44
+ from QuantNodes.research.factor_test.pipeline_runner import PipelineRunner
45
+
46
+
47
+ def build_quality_gate(runner: "PipelineRunner") -> Optional[QualityGateNode]:
48
+ """根据 ``cfg.quality_gate.enabled`` 构造 ``QualityGateNode``."""
49
+ if not runner.config.quality_gate.enabled:
50
+ return None
51
+ zoo_path = (
52
+ Path(runner.config.quality_gate.zoo_path)
53
+ if runner.config.quality_gate.zoo_path
54
+ else None
55
+ )
56
+ zoo = FactorZoo(zoo_path) if zoo_path is not None else FactorZoo()
57
+ return QualityGateNode(QualityGateSetting(), zoo=zoo)
58
+
59
+
60
+ def build_trajectory_pool(runner: "PipelineRunner") -> Optional[TrajectoryPool]:
61
+ """根据 ``cfg.evolution.enabled`` 构造 ``TrajectoryPool``."""
62
+ if not runner.config.evolution.enabled:
63
+ return None
64
+ if runner.config.evolution.pool_dir:
65
+ base = Path(runner.config.evolution.pool_dir)
66
+ else:
67
+ base = Path(runner.config.output.dir) / "trajectory"
68
+ return TrajectoryPool(base)
69
+
70
+
71
+ def build_evolution_loop(
72
+ runner: "PipelineRunner",
73
+ pool: TrajectoryPool,
74
+ quality_gate: Optional[QualityGateNode],
75
+ workers: int = 1,
76
+ ) -> EvolutionLoop:
77
+ """构造 ``EvolutionLoop``, evaluate_fn 委托给 ``runner._evaluate_candidate``."""
78
+ settings = EvolutionSetting(
79
+ enabled=True,
80
+ max_rounds=runner.config.evolution.max_rounds,
81
+ parents_per_round=runner.config.evolution.parents_per_round,
82
+ parent_selection_strategy=runner.config.evolution.parent_selection_strategy,
83
+ top_percent_threshold=runner.config.evolution.top_percent_threshold,
84
+ metric=runner.config.evolution.metric,
85
+ early_stop_patience=runner.config.evolution.early_stop_patience,
86
+ )
87
+ return EvolutionLoop(
88
+ settings=settings,
89
+ pool=pool,
90
+ quality_gate=quality_gate,
91
+ evaluate_fn=runner._evaluate_candidate,
92
+ workers=workers,
93
+ )
94
+
95
+
96
+ def evaluate_candidate(
97
+ runner: "PipelineRunner",
98
+ candidate: FactorCandidate,
99
+ ) -> tuple[bool, dict, FactorFeedback]:
100
+ """``EvolutionLoop`` 评估回调: 单次回测 + metrics + feedback.
101
+
102
+ Returns:
103
+ ``(passed, metrics, feedback)``
104
+ """
105
+ if not isinstance(candidate, FactorCandidate):
106
+ raise TypeError(f"expected FactorCandidate, got {type(candidate)}")
107
+
108
+ try:
109
+ ctx = run_one_factor(runner, candidate)
110
+ except Exception as e: # noqa: BLE001
111
+ return False, {}, FactorFeedback(
112
+ factor_id=candidate.factor_id,
113
+ factor_name=candidate.name,
114
+ decision=False,
115
+ summary=f"evaluate failed: {e}",
116
+ )
117
+
118
+ passed = bool(ctx.get("status") != "rejected")
119
+ metrics = extract_metrics_from_ctx(ctx)
120
+ factor_id = str(candidate.factor_id)
121
+ factor_name = str(candidate.name)
122
+ feedback = FactorFeedback(
123
+ factor_id=factor_id,
124
+ factor_name=factor_name,
125
+ decision=passed,
126
+ summary="ok" if passed else "rejected",
127
+ metadata=metrics,
128
+ )
129
+ if runner.config.feedback.enabled and "Feedback" in ctx:
130
+ for node_fb in ctx["Feedback"].values():
131
+ for ch, ch_fb in node_fb.channels.items():
132
+ feedback.channels[ch] = ch_fb
133
+ return passed, metrics, feedback
134
+
135
+
136
+ def run_one_factor(runner: "PipelineRunner", candidate: FactorCandidate) -> dict:
137
+ """执行单次回测 (12 节点), 临时把 candidate 的 expression / name 注入 ``cfg.factor``.
138
+
139
+ 不写 TrajectoryPool, 仅返回 ctx.
140
+ """
141
+ if not isinstance(candidate, FactorCandidate):
142
+ raise TypeError(f"expected FactorCandidate, got {type(candidate)}")
143
+ with override_factor_config(runner, candidate):
144
+ return runner.run()
145
+
146
+
147
+ @contextmanager
148
+ def override_factor_config(runner: "PipelineRunner", candidate: FactorCandidate):
149
+ """Temporarily replace runner.config.factor.name (and expression if empty).
150
+
151
+ Phase J5 (2026-06-20): extracts the mutate-and-restore pattern from
152
+ run_one_factor() into a reusable context manager. Future callers
153
+ that need the same override (e.g. CLI flags, parallel workers) can
154
+ reuse without re-implementing the cleanup logic.
155
+ """
156
+ cfg = runner.config.factor
157
+ orig_name = cfg.name
158
+ cfg.name = candidate.name
159
+ if not getattr(cfg, "expression", ""):
160
+ try:
161
+ cfg.expression = candidate.expression # type: ignore[attr-defined]
162
+ except Exception:
163
+ pass
164
+ try:
165
+ yield
166
+ finally:
167
+ cfg.name = orig_name
168
+
169
+
170
+ def run_evolution(
171
+ runner: "PipelineRunner",
172
+ initial_directions: list[str] | None = None,
173
+ initial_candidates: list[FactorCandidate] | None = None,
174
+ workers: int = 1,
175
+ ) -> EvolutionResult:
176
+ """多轮演化主入口.
177
+
178
+ Args:
179
+ runner: ``PipelineRunner`` 实例
180
+ initial_directions: round 0 用的研究假设列表 (Hypothesizer 处理)
181
+ initial_candidates: round 0 用的直接候选 (跳过 Hypothesizer)
182
+ workers: 并行数 (1=串行, >1=ThreadPool/ProcessPool 并行)
183
+
184
+ Returns:
185
+ ``EvolutionResult``: best entries + 统计
186
+
187
+ Raises:
188
+ ValueError: ``cfg.evolution.enabled=False``
189
+ """
190
+ if not runner.config.evolution.enabled:
191
+ raise ValueError("config.evolution.enabled=False, 无法运行演化")
192
+
193
+ pool = build_trajectory_pool(runner)
194
+ quality_gate = build_quality_gate(runner)
195
+ loop = build_evolution_loop(runner, pool, quality_gate, workers=workers)
196
+
197
+ # workers > 1 + 有 _loader → 预序列化供 ProcessPool
198
+ if workers > 1 and "_loader" in runner._context.get("LoadData", {}):
199
+ snapshot = prepare_snapshot(
200
+ runner.config, runner._context,
201
+ factor_path=getattr(runner.config.factor, "factor_dir", None),
202
+ )
203
+ snap_path = Path(pool.base_dir) / "_snapshot.pkl"
204
+ snapshot.save(snap_path)
205
+ loop.snapshot_path = str(snap_path)
206
+ logger.info(" [ProcessPool] 预序列化快照: %s", snap_path)
207
+
208
+ # 连接 MetricCollector (streaming)
209
+ collector = MetricCollector()
210
+ loop.metric_collector = collector
211
+
212
+ result = loop.run(
213
+ initial_directions=initial_directions,
214
+ initial_candidates=initial_candidates,
215
+ )
216
+
217
+ # 演化结束后, 追加写入 JSON + 生成 streaming dashboard
218
+ if pool.size > 0:
219
+ metrics_json = pool.base_dir / "metrics.json"
220
+ collector.append_json(metrics_json)
221
+ logger.info(" [Streaming] 指标追加: %s", metrics_json)
222
+ dashboard_html = pool.base_dir.parent / "dashboard_streaming.html"
223
+ generate_dashboard_html(
224
+ collector,
225
+ title=f"演化 Dashboard (Streaming): {runner.config.factor.name}",
226
+ output_path=str(dashboard_html),
227
+ streaming=True,
228
+ )
229
+ logger.info(" [Streaming] Dashboard: %s", dashboard_html)
230
+
231
+ return result
@@ -0,0 +1,102 @@
1
+ # coding: utf-8
2
+ """FactorFeedback 包装器 / Feedback Wrapper.
3
+
4
+ 把 5 个分析节点 (IC/Group/LongShort/Score/RiskCorrelation) 的返回值
5
+ 统一包装成 ``FactorFeedback`` 对象, 聚合到 ``ctx['Feedback']``,
6
+ 并可选持久化到 Parquet.
7
+
8
+ Phase R2 (2026-06-19): 从 pipeline_runner.py 抽出, 单一职责.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from QuantNodes.core.feedback import (
17
+ FactorFeedback,
18
+ FeedbackChannel,
19
+ FeedbackCollector,
20
+ LLMJudge,
21
+ ensure_feedback,
22
+ )
23
+ from QuantNodes.core.path_utils import ensure_dir
24
+ from QuantNodes.research.factor_test.config import SingleFactorTestConfig
25
+
26
+
27
+ ANALYSIS_NODES: tuple[str, ...] = (
28
+ "ICAnalyzer", "GroupAnalyzer", "LongShort", "FactorScore", "RiskCorrelation",
29
+ )
30
+
31
+
32
+ def build_feedback(
33
+ ctx: dict,
34
+ factor_id: str,
35
+ factor_name: str,
36
+ cfg: SingleFactorTestConfig,
37
+ judge: Optional[LLMJudge],
38
+ ) -> dict[str, FactorFeedback]:
39
+ """包装 5 个分析节点返回值为 FactorFeedback.
40
+
41
+ 包装策略:
42
+ - 节点返回 dict → ``ensure_feedback()`` 创建 metadata-only FactorFeedback
43
+ - 节点已是 FactorFeedback → 直接用其 channels
44
+ - 都通过同一个 FeedbackCollector 聚合, 共享 ``factor_id``
45
+ - ``judge`` 不为 None 时追加 LLM 一致性通道
46
+ """
47
+ feedbacks: dict[str, FactorFeedback] = {}
48
+ for node_name in ANALYSIS_NODES:
49
+ result = ctx.get(node_name)
50
+ if result is None:
51
+ continue
52
+ collector = FeedbackCollector(factor_id, factor_name)
53
+ fb = ensure_feedback(result, factor_id, factor_name)
54
+ for _ch, ch_fb in fb.channels.items():
55
+ collector.add_feedback(ch_fb)
56
+ if not fb.channels:
57
+ collector.add(
58
+ channel=FeedbackChannel.VALUE,
59
+ passed=fb.decision,
60
+ detail=fb.summary or f"{node_name} 节点无显式通道反馈",
61
+ score=1.0 if fb.decision else 0.0,
62
+ )
63
+ if judge is not None:
64
+ hypothesis = getattr(cfg.factor, "hypothesis", "") or ""
65
+ description = getattr(cfg.factor, "description", "") or ""
66
+ expression = getattr(cfg.factor, "expression", "") or ""
67
+ if hypothesis or description or expression:
68
+ llm_fb = judge.judge(hypothesis, description, expression)
69
+ collector.add_feedback(llm_fb)
70
+ feedbacks[node_name] = collector.finalize(
71
+ summary=fb.summary or f"{node_name} 节点执行完成",
72
+ )
73
+ return feedbacks
74
+
75
+
76
+ def maybe_persist_feedback(
77
+ feedbacks: dict[str, FactorFeedback],
78
+ cfg: SingleFactorTestConfig,
79
+ ) -> None:
80
+ """可选: 持久化 Feedback 到 Parquet.
81
+
82
+ Args:
83
+ feedbacks: ``build_feedback`` 返回的 dict
84
+ cfg: SingleFactorTestConfig, 需 ``cfg.feedback.output_dir`` 不为 None
85
+ """
86
+ if cfg.feedback.output_dir is None:
87
+ return
88
+ out = Path(cfg.feedback.output_dir)
89
+ ensure_dir(out)
90
+ parquet_path = out / "feedback.parquet"
91
+ for node_name, fb in feedbacks.items():
92
+ fb.save_parquet(parquet_path)
93
+
94
+
95
+ def maybe_build_judge(cfg: SingleFactorTestConfig) -> Optional[LLMJudge]:
96
+ """若 ``cfg.feedback.judge_enabled`` 为 True, 构建 LLMJudge; 否则 None."""
97
+ if not cfg.feedback.judge_enabled:
98
+ return None
99
+ return LLMJudge(
100
+ model=cfg.feedback.judge_model,
101
+ max_correction_attempts=cfg.feedback.judge_max_attempts,
102
+ )
@@ -0,0 +1,7 @@
1
+ # coding: utf-8
2
+ """iFinD Database - 包装同花顺 iFinD API 为 DataLoader 兼容接口"""
3
+
4
+ from .ifind_database import IFinDDatabase
5
+ from .fetcher import IFindFetcher, IFindFetcherStub
6
+
7
+ __all__ = ['IFinDDatabase', 'IFindFetcher', 'IFindFetcherStub']