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,209 @@
1
+ """Retriever — 因子知识库检索抽象。
2
+
3
+ 抽象:
4
+ BaseRetriever: interface
5
+ TFIDFRetriever: 基于 sklearn TfidfVectorizer (默认实现)
6
+ IdentityRetriever: 无 embedding, 仅按文本匹配 (fallback / 测试)
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ import re
12
+ from collections import Counter
13
+ from typing import Protocol, runtime_checkable
14
+
15
+
16
+ @runtime_checkable
17
+ class BaseRetriever(Protocol):
18
+ """Retriever 协议。
19
+
20
+ 方法:
21
+ add(doc_id, text) -> None: 添加文档
22
+ query(text, top_k) -> list[(doc_id, score)]: 检索 top-k, 降序
23
+ save(path) / load(path): 可选持久化
24
+ """
25
+
26
+ def add(self, doc_id: str, text: str) -> None: ...
27
+ def query(self, text: str, top_k: int = 5) -> list[tuple[str, float]]: ...
28
+
29
+
30
+ # ============================================================================
31
+ # TFIDFRetriever (sklearn)
32
+ # ============================================================================
33
+
34
+ class TFIDFRetriever:
35
+ """基于 sklearn TfidfVectorizer 的稀疏检索器。
36
+
37
+ Args:
38
+ token_pattern: sklearn 分词正则 (默认支持 alnum + 下划线)
39
+
40
+ v3.0.0 graceful degradation: if ``sklearn`` is not installed,
41
+ ``TFIDFRetriever`` falls back to :class:`IdentityRetriever`
42
+ (pure-Python TF-IDF) instead of raising ``ModuleNotFoundError``.
43
+ A ``RuntimeWarning`` is emitted so users can install sklearn
44
+ explicitly: ``pip install scikit-learn``.
45
+ """
46
+
47
+ def __init__(self, token_pattern: str | None = None):
48
+ try:
49
+ from sklearn.feature_extraction.text import TfidfVectorizer
50
+ self._vectorizer = TfidfVectorizer(
51
+ token_pattern=token_pattern or r"(?u)\b\w+\b",
52
+ lowercase=True,
53
+ )
54
+ self._sklearn = True
55
+ except ImportError:
56
+ import warnings
57
+ warnings.warn(
58
+ "scikit-learn not installed; TFIDFRetriever falling back to "
59
+ "IdentityRetriever (pure-Python). Install scikit-learn for "
60
+ "the full sklearn-backed retrieval: pip install scikit-learn",
61
+ RuntimeWarning,
62
+ stacklevel=2,
63
+ )
64
+ # Delegate to IdentityRetriever (we'll forward add/query through
65
+ # the same interface by storing an internal instance)
66
+ self._fallback = IdentityRetriever()
67
+ self._sklearn = False
68
+ self._vectorizer = None
69
+ self._doc_ids = None # sentinel; not used in fallback mode
70
+ self._matrix = None
71
+ return
72
+
73
+ self._doc_ids: list[str] = []
74
+ self._texts: list[str] = []
75
+ self._matrix = None # 懒构建
76
+
77
+ def add(self, doc_id: str, text: str) -> None:
78
+ """添加一条文档, 标记 matrix 失效。"""
79
+ if not self._sklearn:
80
+ # Fallback to IdentityRetriever (pure-Python TF-IDF)
81
+ return self._fallback.add(doc_id, text)
82
+ if doc_id in self._doc_ids:
83
+ # 更新: 移除旧的
84
+ idx = self._doc_ids.index(doc_id)
85
+ self._doc_ids.pop(idx)
86
+ self._texts.pop(idx)
87
+ self._doc_ids.append(doc_id)
88
+ self._texts.append(text)
89
+ self._matrix = None
90
+
91
+ def _ensure_matrix(self):
92
+ if self._matrix is None and self._texts:
93
+ self._matrix = self._vectorizer.fit_transform(self._texts)
94
+
95
+ def query(self, text: str, top_k: int = 5) -> list[tuple[str, float]]:
96
+ """返回 top_k 文档, 列表 [(doc_id, score)] 降序。"""
97
+ if not self._sklearn:
98
+ return self._fallback.query(text, top_k)
99
+ if not self._doc_ids:
100
+ return []
101
+ self._ensure_matrix()
102
+ q_vec = self._vectorizer.transform([text])
103
+ # cosine = (q · d) / (||q|| * ||d||), TfidfVectorizer 已 L2 normalize
104
+ scores = (self._matrix @ q_vec.T).toarray().ravel()
105
+ ranked = sorted(
106
+ enumerate(scores), key=lambda x: x[1], reverse=True,
107
+ )[:top_k]
108
+ return [(self._doc_ids[i], float(s)) for i, s in ranked if s > 0]
109
+
110
+ def __len__(self) -> int:
111
+ if not self._sklearn:
112
+ return len(self._fallback)
113
+ return len(self._doc_ids)
114
+
115
+
116
+ # ============================================================================
117
+ # IdentityRetriever (纯 Python, 无 sklearn 依赖)
118
+ # ============================================================================
119
+
120
+ _WORD_RE = re.compile(r"(?u)\b\w+\b")
121
+
122
+
123
+ def _tokenize(text: str) -> list[str]:
124
+ return [t.lower() for t in _WORD_RE.findall(text)]
125
+
126
+
127
+ def _compute_idf(docs: list[list[str]]) -> dict[str, float]:
128
+ """IDF = log(N / df) + 1, 平滑版。"""
129
+ n = len(docs)
130
+ df: Counter = Counter()
131
+ for tokens in docs:
132
+ for unique in set(tokens):
133
+ df[unique] += 1
134
+ return {term: math.log((n + 1) / (df_t + 1)) + 1 for term, df_t in df.items()}
135
+
136
+
137
+ class IdentityRetriever:
138
+ """纯 Python TF-IDF (无 sklearn 依赖), 用于测试和小规模场景。
139
+
140
+ 实现: 词频 × IDF, cosine 相似度。
141
+ """
142
+
143
+ def __init__(self):
144
+ self._doc_ids: list[str] = []
145
+ self._tokenized: list[list[str]] = []
146
+ self._idf: dict[str, float] = {}
147
+ self._norms: list[float] = []
148
+ self._dirty = True
149
+
150
+ def add(self, doc_id: str, text: str) -> None:
151
+ if doc_id in self._doc_ids:
152
+ idx = self._doc_ids.index(doc_id)
153
+ self._doc_ids.pop(idx)
154
+ self._tokenized.pop(idx)
155
+ self._doc_ids.append(doc_id)
156
+ self._tokenized.append(_tokenize(text))
157
+ self._dirty = True
158
+
159
+ def _rebuild(self) -> None:
160
+ self._idf = _compute_idf(self._tokenized)
161
+ self._norms = []
162
+ for tokens in self._tokenized:
163
+ tf = Counter(tokens)
164
+ vec = {t: (tf[t] / max(len(tokens), 1)) * self._idf.get(t, 0.0)
165
+ for t in tokens}
166
+ self._norms.append(math.sqrt(sum(v * v for v in vec.values())) or 1e-9)
167
+ self._dirty = False
168
+
169
+ def query(self, text: str, top_k: int = 5) -> list[tuple[str, float]]:
170
+ if not self._doc_ids:
171
+ return []
172
+ if self._dirty:
173
+ self._rebuild()
174
+ q_tokens = _tokenize(text)
175
+ q_tf = Counter(q_tokens)
176
+ q_vec = {t: (q_tf[t] / max(len(q_tokens), 1)) * self._idf.get(t, 0.0)
177
+ for t in q_tokens}
178
+ q_norm = math.sqrt(sum(v * v for v in q_vec.values())) or 1e-9
179
+ scores: list[tuple[int, float]] = []
180
+ for i, tokens in enumerate(self._tokenized):
181
+ tf = Counter(tokens)
182
+ d_vec = {t: (tf[t] / max(len(tokens), 1)) * self._idf.get(t, 0.0)
183
+ for t in tokens}
184
+ dot = sum(q_vec.get(t, 0) * d_vec.get(t, 0) for t in q_vec)
185
+ sim = dot / (q_norm * self._norms[i])
186
+ if sim > 0:
187
+ scores.append((i, sim))
188
+ scores.sort(key=lambda x: x[1], reverse=True)
189
+ return [(self._doc_ids[i], float(s)) for i, s in scores[:top_k]]
190
+
191
+ def __len__(self) -> int:
192
+ return len(self._doc_ids)
193
+
194
+
195
+ # ============================================================================
196
+ # Factory
197
+ # ============================================================================
198
+
199
+ def make_retriever(kind: str = "tfidf") -> BaseRetriever:
200
+ """构造 retriever。
201
+
202
+ Args:
203
+ kind: "tfidf" (sklearn, 优先) / "identity" (纯 Python)
204
+ """
205
+ if kind == "tfidf":
206
+ return TFIDFRetriever()
207
+ if kind == "identity":
208
+ return IdentityRetriever()
209
+ raise ValueError(f"未知 retriever kind: {kind!r}")
@@ -0,0 +1,81 @@
1
+ # coding=utf-8
2
+ """
3
+ LambdaNode 便捷节点类
4
+
5
+ 用于快速将函数包装为节点,适合简单的转换逻辑。
6
+ """
7
+
8
+ from typing import Any, Callable, Dict
9
+ from QuantNodes.core.node import BaseNode, SerializationError
10
+ from QuantNodes.core.serializable import serializable
11
+
12
+
13
+ @serializable
14
+ class LambdaNode(BaseNode):
15
+ """
16
+ 将函数包装为节点
17
+
18
+ Examples:
19
+ >>> # 简单函数
20
+ >>> add_one = LambdaNode(lambda x: x + 1)
21
+ >>> add_one.execute(5) # 6
22
+ >>>
23
+ >>> # 更复杂的逻辑
24
+ >>> rolling_mean = LambdaNode(
25
+ ... lambda df, ctx: df.rolling(ctx.config['window']).mean(),
26
+ ... config={'window': 20}
27
+ ... )
28
+ """
29
+
30
+ def __init__(
31
+ self, func: Callable[[Any, Any], Any], name: str = None,
32
+ config: Dict[str, Any] = None,
33
+ ):
34
+ """
35
+ Args:
36
+ func: 执行函数,签名为 func(input_data, context) -> result
37
+ name: 节点名称,默认为函数名
38
+ config: 配置字典
39
+ """
40
+ self.func = func
41
+ name = name or (func.__name__ if hasattr(func, '__name__') else 'Lambda')
42
+ super().__init__(name=name, config=config)
43
+
44
+ def _execute(self, input_data: Any = None, **kwargs) -> Any:
45
+ return self.func(input_data, self)
46
+
47
+ def _get_serializable_fields(self) -> Dict[str, Any]:
48
+ """返回需要序列化的额外字段"""
49
+ func = self.func
50
+
51
+ if hasattr(func, "__name__") and func.__name__ == "<lambda>":
52
+ raise SerializationError(
53
+ "LambdaNode with anonymous lambda cannot be serialized. "
54
+ "Please use a named function instead."
55
+ )
56
+
57
+ return {
58
+ "func": {
59
+ "type": "named_function",
60
+ "module": func.__module__,
61
+ "qualname": func.__qualname__
62
+ }
63
+ }
64
+
65
+ @classmethod
66
+ def _from_dict_impl(cls, data: Dict[str, Any]) -> 'LambdaNode':
67
+ """从字典反序列化重建 LambdaNode"""
68
+ import importlib
69
+
70
+ func_info = data["func"]
71
+ if func_info["type"] != "named_function":
72
+ raise ValueError(f"Unsupported function type: {func_info['type']}")
73
+
74
+ module = importlib.import_module(func_info["module"])
75
+ func = getattr(module, func_info["qualname"])
76
+
77
+ return LambdaNode(
78
+ func=func,
79
+ name=data.get("name"),
80
+ config=data.get("config", {})
81
+ )
@@ -0,0 +1,22 @@
1
+ """Monitoring — 3 类指标中央收集 + Plotly dashboard。
2
+
3
+ 公开 API:
4
+ - RagMetrics / EvolutionMetrics / QualityMetrics (数据类)
5
+ - MetricCollector (中央收集器)
6
+ - generate_dashboard_html (HTML 报告)
7
+ """
8
+ from .collector import (
9
+ EvolutionMetrics,
10
+ MetricCollector,
11
+ QualityMetrics,
12
+ RagMetrics,
13
+ )
14
+ from .dashboard import generate_dashboard_html
15
+
16
+ __all__ = [
17
+ "RagMetrics",
18
+ "EvolutionMetrics",
19
+ "QualityMetrics",
20
+ "MetricCollector",
21
+ "generate_dashboard_html",
22
+ ]
@@ -0,0 +1,292 @@
1
+ """MetricCollector — 3 类指标的中央收集器。
2
+
3
+ 3 类指标:
4
+ - RAG: HitRate@K / NDCG@K / MRR / Diversity (per-round)
5
+ - Evo: pool_size / total_count / rejected_count / best_sharpe
6
+ - Quality: 各 QualityGate 通道 pass/fail (per-round)
7
+
8
+ 数据来源:
9
+ - RAG: EvolutionLoop.rag_metrics_history
10
+ - Evo: TrajectoryPool + EvolutionResult
11
+ - Quality: TrajectoryEntry.feedback.channels
12
+
13
+ 数据流:
14
+ 演化 → MetricCollector.update(loop_result) → 3 类指标 append → JSON / Dashboard
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from dataclasses import asdict, dataclass, field
20
+ from datetime import datetime
21
+ from pathlib import Path
22
+
23
+ import pandas as pd
24
+
25
+ from ..feedback import FeedbackChannel
26
+ from ..trajectory import TrajectoryPool
27
+ from QuantNodes.core.path_utils import ensure_parent
28
+
29
+
30
+ # ============================================================================
31
+ # 数据类
32
+ # ============================================================================
33
+
34
+ @dataclass
35
+ class RagMetrics:
36
+ """单轮 RAG 评估指标 (Week 10)。
37
+
38
+ 字段命名包含 K (5/10) 是历史设计, 为保持序列化兼容保留。
39
+ 自定义 K (如 K=3, K=20) 通过 `extra: dict[str, float]` 扩展,
40
+ 避免 dataclass 字段爆炸。
41
+ """
42
+ round: int
43
+ n_queries: int
44
+ hit_at_5: float = 0.0
45
+ hit_at_10: float = 0.0
46
+ ndcg_at_5: float = 0.0
47
+ ndcg_at_10: float = 0.0
48
+ mrr: float = 0.0
49
+ lineage_coverage: float = 0.0
50
+ diversity: float = 0.0
51
+ extra: dict[str, float] = field(default_factory=dict)
52
+ timestamp: str = ""
53
+
54
+ def __post_init__(self):
55
+ if not self.timestamp:
56
+ self.timestamp = datetime.now().isoformat()
57
+
58
+ def get(self, key: str, default: float = 0.0) -> float:
59
+ """统一访问入口: 优先 `extra`, 回退命名字段 (兼容 hit_at_5 形式)。"""
60
+ if key in self.extra:
61
+ return self.extra[key]
62
+ return getattr(self, key, default)
63
+
64
+
65
+ @dataclass
66
+ class EvolutionMetrics:
67
+ """单轮演化统计。"""
68
+ round: int
69
+ pool_size: int = 0
70
+ total_count: int = 0
71
+ rejected_count: int = 0
72
+ best_metric: float = 0.0
73
+ best_factor_name: str = ""
74
+ timestamp: str = ""
75
+
76
+ def __post_init__(self):
77
+ if not self.timestamp:
78
+ self.timestamp = datetime.now().isoformat()
79
+
80
+
81
+ @dataclass
82
+ class QualityMetrics:
83
+ """单轮 Quality Gate 通道统计。"""
84
+ round: int
85
+ code_pass: int = 0
86
+ code_fail: int = 0
87
+ value_pass: int = 0
88
+ value_fail: int = 0
89
+ llm_pass: int = 0
90
+ llm_fail: int = 0
91
+ timestamp: str = ""
92
+
93
+ def __post_init__(self):
94
+ if not self.timestamp:
95
+ self.timestamp = datetime.now().isoformat()
96
+
97
+
98
+ # ============================================================================
99
+ # MetricCollector
100
+ # ============================================================================
101
+
102
+ class MetricCollector:
103
+ """3 类指标中央收集器。
104
+
105
+ 用法:
106
+ collector = MetricCollector()
107
+ collector.update_from_loop(loop, result)
108
+ collector.update_quality_from_pool(pool, round_idx=N)
109
+ collector.save(Path("metrics.json"))
110
+ """
111
+
112
+ def __init__(self):
113
+ self.rag_history: list[RagMetrics] = []
114
+ self.evolution_history: list[EvolutionMetrics] = []
115
+ self.quality_history: list[QualityMetrics] = []
116
+
117
+ # ------------------------------------------------------------------
118
+ # RAG 指标
119
+ # ------------------------------------------------------------------
120
+
121
+ def add_rag(self, metrics: RagMetrics) -> None:
122
+ """添加 1 轮 RAG 指标。"""
123
+ self.rag_history.append(metrics)
124
+
125
+ def update_from_loop(self, loop, result) -> None:
126
+ """从 EvolutionLoop.rag_metrics_history 提取 RAG 指标。"""
127
+ for m in loop.rag_metrics_history:
128
+ self.add_rag(RagMetrics(
129
+ round=m.get("round", 0),
130
+ n_queries=m.get("n_queries", 0),
131
+ hit_at_5=m.get("hit_at_5", 0.0),
132
+ hit_at_10=m.get("hit_at_10", 0.0),
133
+ ndcg_at_5=m.get("ndcg_at_5", 0.0),
134
+ ndcg_at_10=m.get("ndcg_at_10", 0.0),
135
+ mrr=m.get("mrr", 0.0),
136
+ lineage_coverage=m.get("lineage_coverage", 0.0),
137
+ diversity=m.get("diversity", 0.0),
138
+ ))
139
+ # 添加最终演化统计
140
+ self.add_evolution(EvolutionMetrics(
141
+ round=result.rounds_completed,
142
+ pool_size=result.total_count + result.rejected_count,
143
+ total_count=result.total_count,
144
+ rejected_count=result.rejected_count,
145
+ best_metric=(
146
+ result.best_entries[0].metrics.get("sharpe", 0.0)
147
+ if result.best_entries else 0.0
148
+ ),
149
+ best_factor_name=(
150
+ result.best_entries[0].feedback.factor_name
151
+ if result.best_entries and result.best_entries[0].feedback
152
+ else ""
153
+ ),
154
+ ))
155
+
156
+ # ------------------------------------------------------------------
157
+ # 演化统计
158
+ # ------------------------------------------------------------------
159
+
160
+ def add_evolution(self, metrics: EvolutionMetrics) -> None:
161
+ self.evolution_history.append(metrics)
162
+
163
+ def update_evolution_from_pool(
164
+ self, pool: TrajectoryPool, round_idx: int = 0,
165
+ metric: str = "sharpe", # M4: 监控指标可调 (默认 sharpe)
166
+ ) -> None:
167
+ """从 TrajectoryPool 提取当前统计。"""
168
+ passed = sum(1 for e in pool.all() if e.feedback and e.feedback.decision)
169
+ rejected = pool.size - passed
170
+ best_val = 0.0
171
+ best_name = ""
172
+ for e in pool.all():
173
+ val = (e.metrics or {}).get(metric, 0)
174
+ if val > best_val:
175
+ best_val = val
176
+ if e.feedback:
177
+ best_name = e.feedback.factor_name
178
+ self.add_evolution(EvolutionMetrics(
179
+ round=round_idx, pool_size=pool.size,
180
+ total_count=passed, rejected_count=rejected,
181
+ best_metric=best_val, best_factor_name=best_name,
182
+ ))
183
+
184
+ # ------------------------------------------------------------------
185
+ # Quality Gate 通道统计
186
+ # ------------------------------------------------------------------
187
+
188
+ def add_quality(self, metrics: QualityMetrics) -> None:
189
+ self.quality_history.append(metrics)
190
+
191
+ def update_quality_from_pool(
192
+ self, pool: TrajectoryPool, round_idx: int = 0,
193
+ ) -> None:
194
+ """从 pool 中 round_idx 过滤, 统计每通道 pass/fail。"""
195
+ code_pass = code_fail = value_pass = value_fail = llm_pass = llm_fail = 0
196
+ for e in pool.all():
197
+ if e.round_idx != round_idx:
198
+ continue
199
+ if not e.feedback or not e.feedback.channels:
200
+ continue
201
+ for ch, fb in e.feedback.channels.items():
202
+ passed = fb.passed
203
+ if ch == FeedbackChannel.CODE:
204
+ code_pass += int(passed)
205
+ code_fail += int(not passed)
206
+ elif ch == FeedbackChannel.VALUE:
207
+ value_pass += int(passed)
208
+ value_fail += int(not passed)
209
+ elif ch == FeedbackChannel.LLM:
210
+ llm_pass += int(passed)
211
+ llm_fail += int(not passed)
212
+ self.add_quality(QualityMetrics(
213
+ round=round_idx,
214
+ code_pass=code_pass, code_fail=code_fail,
215
+ value_pass=value_pass, value_fail=value_fail,
216
+ llm_pass=llm_pass, llm_fail=llm_fail,
217
+ ))
218
+
219
+ # ------------------------------------------------------------------
220
+ # 序列化
221
+ # ------------------------------------------------------------------
222
+
223
+ def to_dict(self) -> dict:
224
+ return {
225
+ "rag": [asdict(m) for m in self.rag_history],
226
+ "evolution": [asdict(m) for m in self.evolution_history],
227
+ "quality": [asdict(m) for m in self.quality_history],
228
+ "generated_at": datetime.now().isoformat(),
229
+ }
230
+
231
+ def save(self, path: Path | str) -> None:
232
+ """保存为 JSON。"""
233
+ path = Path(path)
234
+ ensure_parent(path)
235
+ with path.open("w", encoding="utf-8") as f:
236
+ json.dump(self.to_dict(), f, ensure_ascii=False, indent=2)
237
+
238
+ @classmethod
239
+ def load(cls, path: Path | str) -> "MetricCollector":
240
+ """从 JSON 加载。"""
241
+ path = Path(path)
242
+ if not path.exists():
243
+ return cls()
244
+ data = json.loads(path.read_text(encoding="utf-8"))
245
+ c = cls()
246
+ for m in data.get("rag", []):
247
+ c.rag_history.append(RagMetrics(**m))
248
+ for m in data.get("evolution", []):
249
+ c.evolution_history.append(EvolutionMetrics(**m))
250
+ for m in data.get("quality", []):
251
+ c.quality_history.append(QualityMetrics(**m))
252
+ return c
253
+
254
+ def append_json(self, path: Path | str) -> None:
255
+ """追加写入 JSON (streaming 模式: 读旧数据 → 追加 → 写回)。
256
+
257
+ 读已有文件 → 合并 → 写回, 支持多轮逐步写入。
258
+ """
259
+ path = Path(path)
260
+ existing = self.load(path) if path.exists() else MetricCollector()
261
+ # 合并: 保留已有的, 追加新的 (跳过已存在的 round)
262
+ existing_rounds = {m.round for m in existing.rag_history}
263
+ for m in self.rag_history:
264
+ if m.round not in existing_rounds:
265
+ existing.rag_history.append(m)
266
+ evo_existing = {m.round for m in existing.evolution_history}
267
+ for m in self.evolution_history:
268
+ if m.round not in evo_existing:
269
+ existing.evolution_history.append(m)
270
+ qg_existing = {m.round for m in existing.quality_history}
271
+ for m in self.quality_history:
272
+ if m.round not in qg_existing:
273
+ existing.quality_history.append(m)
274
+ existing.save(path)
275
+
276
+ def save_csv(self, path_prefix: Path | str) -> None:
277
+ """保存 3 类指标为 3 个 CSV。"""
278
+ path_prefix = Path(path_prefix)
279
+ ensure_parent(path_prefix)
280
+ for name, history in (
281
+ ("rag", self.rag_history),
282
+ ("evolution", self.evolution_history),
283
+ ("quality", self.quality_history),
284
+ ):
285
+ if history:
286
+ pd.DataFrame([asdict(m) for m in history]).to_csv(
287
+ path_prefix.with_name(f"{path_prefix.name}_{name}.csv"),
288
+ index=False,
289
+ )
290
+
291
+ def __len__(self) -> int:
292
+ return len(self.rag_history) + len(self.evolution_history) + len(self.quality_history)