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,69 @@
1
+ """Knowledge RAG — 因子知识库 + RAG prompt + 谱系展开 (Week 8)
2
+ + 谱系压缩 (Week 9) + 评估指标 (Week 10)。
3
+
4
+ 公开 API:
5
+ - BaseRetriever (Protocol)
6
+ - TFIDFRetriever (sklearn 实现)
7
+ - IdentityRetriever (纯 Python fallback / 测试)
8
+ - make_retriever(kind)
9
+ - KnowledgeBase
10
+ - build_rag_prompt() (含谱系 RAG + 压缩)
11
+ - expand_lineage() / expand_lineage_batch() (Week 8)
12
+ - Compressor / compress_lineage() (Week 9)
13
+ - 5 个评估指标 + RAGEvaluator (Week 10)
14
+ """
15
+ from .retriever import (
16
+ BaseRetriever,
17
+ IdentityRetriever,
18
+ TFIDFRetriever,
19
+ make_retriever,
20
+ )
21
+ from .knowledge_base import KnowledgeBase
22
+ from .lineage_compress import Compressor, CompressedLineage, compress_lineage
23
+ from .lineage_expand import expand_lineage, expand_lineage_batch
24
+ from .rag_prompt import build_rag_prompt
25
+ from .metrics import (
26
+ EvalReport,
27
+ QueryResult,
28
+ RAGEvaluator,
29
+ dcg_at_k,
30
+ hit_rate_at_k,
31
+ intra_list_diversity,
32
+ jaccard_similarity,
33
+ lineage_coverage,
34
+ mean_hit_rate_at_k,
35
+ mean_lineage_coverage,
36
+ mean_ndcg_at_k,
37
+ mean_reciprocal_rank,
38
+ ndcg_at_k,
39
+ reciprocal_rank,
40
+ )
41
+
42
+ __all__ = [
43
+ "BaseRetriever",
44
+ "TFIDFRetriever",
45
+ "IdentityRetriever",
46
+ "make_retriever",
47
+ "KnowledgeBase",
48
+ "expand_lineage",
49
+ "expand_lineage_batch",
50
+ "Compressor",
51
+ "CompressedLineage",
52
+ "compress_lineage",
53
+ "build_rag_prompt",
54
+ # Week 10 评估指标
55
+ "dcg_at_k",
56
+ "hit_rate_at_k",
57
+ "intra_list_diversity",
58
+ "jaccard_similarity",
59
+ "lineage_coverage",
60
+ "mean_hit_rate_at_k",
61
+ "mean_lineage_coverage",
62
+ "mean_ndcg_at_k",
63
+ "mean_reciprocal_rank",
64
+ "ndcg_at_k",
65
+ "reciprocal_rank",
66
+ "EvalReport",
67
+ "QueryResult",
68
+ "RAGEvaluator",
69
+ ]
@@ -0,0 +1,217 @@
1
+ """KnowledgeBase — 因子知识库 (TrajectoryEntry + Retriever)。"""
2
+ from __future__ import annotations
3
+
4
+ from pathlib import Path
5
+ from typing import Any, Optional
6
+
7
+ import pandas as pd
8
+ from pydantic import BaseModel, Field
9
+
10
+ from ..trajectory import TrajectoryEntry, TrajectoryPool
11
+ from .retriever import BaseRetriever, make_retriever
12
+ from QuantNodes.core.path_utils import ensure_parent
13
+
14
+
15
+ # H19: 默认字段权重 (外部可覆盖)
16
+ DEFAULT_FIELD_WEIGHTS: dict[str, float] = {
17
+ "name": 3.0,
18
+ "expression": 2.0,
19
+ "hypothesis": 2.5,
20
+ "description": 2.0,
21
+ "summary": 1.0,
22
+ }
23
+
24
+
25
+ class KnowledgeBaseSetting(BaseModel):
26
+ """H19: KnowledgeBase 字段权重配置 (Pydantic)。"""
27
+ field_weights: dict[str, float] = Field(
28
+ default_factory=lambda: dict(DEFAULT_FIELD_WEIGHTS),
29
+ description="字段权重: 控制检索文本构造时各字段的重复次数",
30
+ )
31
+
32
+
33
+ class KnowledgeBase:
34
+ """因子知识库。
35
+
36
+ Args:
37
+ retriever: BaseRetriever 实现 (默认 TFIDF)
38
+ pool: 可选 TrajectoryPool 同步源
39
+ setting: H19 字段权重配置 (None=默认)
40
+ field_weights: H19 字段权重 dict (None=默认, 兼容旧 API)
41
+ """
42
+
43
+ def __init__(
44
+ self,
45
+ retriever: Optional[BaseRetriever] = None,
46
+ pool: Optional[TrajectoryPool] = None,
47
+ setting: Optional[KnowledgeBaseSetting] = None,
48
+ field_weights: Optional[dict[str, float]] = None,
49
+ ):
50
+ self.retriever = retriever or make_retriever("tfidf")
51
+ self.pool = pool
52
+ self._entry_ids: set[str] = set() # 已索引的 entry_id
53
+ # H19: 字段权重 — setting 优先, field_weights 其次, 默认最后
54
+ if setting is not None:
55
+ self._field_weights = dict(setting.field_weights)
56
+ elif field_weights is not None:
57
+ self._field_weights = dict(field_weights)
58
+ else:
59
+ self._field_weights = dict(DEFAULT_FIELD_WEIGHTS)
60
+
61
+ # ------------------------------------------------------------------
62
+ # CRUD
63
+ # ------------------------------------------------------------------
64
+
65
+ def add(self, entry: TrajectoryEntry) -> None:
66
+ """添加 entry 到知识库 (含 retriever indexing)。"""
67
+ text = self._entry_to_text(entry)
68
+ self.retriever.add(entry.entry_id, text)
69
+ self._entry_ids.add(entry.entry_id)
70
+
71
+ def add_many(self, entries) -> int:
72
+ """批量添加, 返回新加入数。"""
73
+ n = 0
74
+ for e in entries:
75
+ if e.entry_id not in self._entry_ids:
76
+ self.add(e)
77
+ n += 1
78
+ return n
79
+
80
+ def sync_from_pool(self) -> int:
81
+ """从 pool 同步所有未索引 entry。"""
82
+ if self.pool is None:
83
+ return 0
84
+ return self.add_many(self.pool.all())
85
+
86
+ # ------------------------------------------------------------------
87
+ # 检索
88
+ # ------------------------------------------------------------------
89
+
90
+ def query(
91
+ self,
92
+ text: str,
93
+ top_k: int = 5,
94
+ min_score: float = 0.0,
95
+ ) -> list[tuple[TrajectoryEntry, float]]:
96
+ """返回 top_k 检索结果, 包含 entry 对象 + 相似度。
97
+
98
+ 排序: score 降序
99
+ """
100
+ results = self.retriever.query(text, top_k=top_k)
101
+ if not self.pool:
102
+ return [(None, score) for _, score in results]
103
+ out: list[tuple[TrajectoryEntry, float]] = []
104
+ for doc_id, score in results:
105
+ if score < min_score:
106
+ continue
107
+ try:
108
+ entry = self.pool.get(doc_id)
109
+ except KeyError:
110
+ continue
111
+ out.append((entry, score))
112
+ return out
113
+
114
+ def query_with_lineage(
115
+ self,
116
+ text: str,
117
+ top_k: int = 5,
118
+ max_ancestor_depth: int = 2,
119
+ max_descendant_depth: int = 2,
120
+ ) -> list[dict[str, Any]]:
121
+ """检索 + 附加谱系 (parents + children, 可配深度) 上下文。
122
+
123
+ Args:
124
+ text: 查询文本
125
+ top_k: 检索数量
126
+ max_ancestor_depth: 祖先深度 (1=parent, 2=grandparent, ...)
127
+ max_descendant_depth: 后裔深度
128
+ """
129
+ results = self.query(text, top_k=top_k)
130
+ out = []
131
+ for entry, score in results:
132
+ ctx = {
133
+ "entry": entry,
134
+ "score": score,
135
+ "ancestors": [],
136
+ "descendants": [],
137
+ "parents": [],
138
+ "children": [],
139
+ }
140
+ if self.pool and entry is not None:
141
+ from .lineage_expand import expand_lineage
142
+ expanded = expand_lineage(
143
+ self.pool, entry.entry_id,
144
+ max_ancestor_depth=max_ancestor_depth,
145
+ max_descendant_depth=max_descendant_depth,
146
+ )
147
+ ctx["ancestors"] = [e for _, e in expanded["ancestors"]]
148
+ ctx["descendants"] = [e for _, e in expanded["descendants"]]
149
+ # 兼容旧字段 (depth=1)
150
+ ctx["parents"] = [e for d, e in expanded["ancestors"] if d == 1]
151
+ ctx["children"] = [e for d, e in expanded["descendants"] if d == 1]
152
+ out.append(ctx)
153
+ return out
154
+
155
+ # ------------------------------------------------------------------
156
+ # 持久化 (Parquet 索引 + JSON 单文件 - 与 TrajectoryPool 复用)
157
+ # ------------------------------------------------------------------
158
+
159
+ def save(self, path: Path | str) -> None:
160
+ """保存知识库索引 (entry_id 列表 + retriever 文本)。"""
161
+ path = Path(path)
162
+ ensure_parent(path)
163
+ rows = []
164
+ if self.pool:
165
+ for e in self.pool.all():
166
+ rows.append({
167
+ "entry_id": e.entry_id,
168
+ "text": self._entry_to_text(e),
169
+ })
170
+ df = pd.DataFrame(rows)
171
+ df.to_parquet(path, index=False)
172
+
173
+ @classmethod
174
+ def load(
175
+ cls,
176
+ path: Path | str,
177
+ pool: Optional[TrajectoryPool] = None,
178
+ retriever: Optional[BaseRetriever] = None,
179
+ ) -> "KnowledgeBase":
180
+ """从 Parquet 索引加载 (需配合 pool)。"""
181
+ path = Path(path)
182
+ df = pd.read_parquet(path)
183
+ kb = cls(retriever=retriever, pool=pool)
184
+ for _, row in df.iterrows():
185
+ kb.retriever.add(str(row["entry_id"]), str(row["text"]))
186
+ kb._entry_ids.add(str(row["entry_id"]))
187
+ return kb
188
+
189
+ # ------------------------------------------------------------------
190
+ # 内部
191
+ # ------------------------------------------------------------------
192
+
193
+ def _entry_to_text(self, entry: TrajectoryEntry) -> str:
194
+ """把 entry 转为可检索文本 (按字段权重重复关键 token)。"""
195
+ parts: list[str] = []
196
+ cfg = entry.config_snapshot or {}
197
+ factor_cfg = cfg.get("factor", {}) if isinstance(cfg, dict) else {}
198
+ for src, key in (
199
+ (factor_cfg.get("name", ""), "name"),
200
+ (factor_cfg.get("expression", ""), "expression"),
201
+ (factor_cfg.get("hypothesis", ""), "hypothesis"),
202
+ (factor_cfg.get("description", ""), "description"),
203
+ ):
204
+ weight = int(self._field_weights.get(key, 1))
205
+ parts.extend([str(src)] * weight)
206
+ if entry.feedback:
207
+ parts.extend(
208
+ [str(entry.feedback.summary or "")]
209
+ * int(self._field_weights.get("summary", 1))
210
+ )
211
+ if entry.metrics:
212
+ for k, v in entry.metrics.items():
213
+ parts.append(f"{k}={v}")
214
+ return " ".join(parts)
215
+
216
+ def __len__(self) -> int:
217
+ return len(self._entry_ids)
@@ -0,0 +1,196 @@
1
+ """谱系压缩 — LLM 总结祖先/后裔链为 1 段简短描述, 减少 token。
2
+
3
+ 设计:
4
+ - Compressor 类 (类似 LLMJudge 的协议)
5
+ - compress_lineage(entries, relation) -> str
6
+ - 默认 mock 模式: 启发式拼接 (1 行 / entry, 含 name + operation + sharpe)
7
+ - 支持自定义 llm_callable (真实 LLM)
8
+ - 限制输出 token: max_tokens (默认 200 chars)
9
+
10
+ 用途:
11
+ build_rag_prompt(..., use_compress=True) 时,
12
+ 每个示例的 ancestors / descendants 段先压缩为 1 行, 再注入 prompt。
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from dataclasses import dataclass
18
+ from typing import Callable, Optional
19
+
20
+
21
+ # 单个 entry 的压缩模板 (heuristic 模式)
22
+ _HEURISTIC_ENTRY_TEMPLATE = "{name} ({op} r{round}, sharpe={sharpe:.2f})"
23
+
24
+
25
+ @dataclass
26
+ class CompressedLineage:
27
+ """压缩结果。"""
28
+ summary: str # 1 段简短总结
29
+ original_count: int # 压缩前 entry 数
30
+ compressed_chars: int # 压缩后字符数
31
+ method: str # "llm" / "heuristic"
32
+
33
+
34
+ # ============================================================================
35
+ # Compressor
36
+ # ============================================================================
37
+
38
+ class Compressor:
39
+ """谱系压缩器。
40
+
41
+ Args:
42
+ model: "mock" (默认启发式) / "deepseek-v3" / 其他 (需 llm_callable)
43
+ max_tokens: 输出最大字符数 (默认 200)
44
+ llm_callable: 自定义 LLM 调用函数, 接受 prompt 返回 string
45
+ """
46
+
47
+ def __init__(
48
+ self,
49
+ model: str = "mock",
50
+ max_tokens: int = 200,
51
+ llm_callable: Optional[Callable] = None,
52
+ ):
53
+ self.model = model
54
+ self.max_tokens = max_tokens
55
+ if llm_callable is None and model != "mock":
56
+ raise ValueError(
57
+ f"model={model!r} requires an explicit llm_callable. "
58
+ "Inject via get_llm_gateway() at the call site."
59
+ )
60
+ self._llm_callable = llm_callable
61
+
62
+ def compress(
63
+ self,
64
+ entries: list, # list of (depth, TrajectoryEntry) 或 TrajectoryEntry
65
+ relation: str = "ancestors", # "ancestors" / "descendants"
66
+ ) -> CompressedLineage:
67
+ """压缩一组 entry 为 1 段简短总结。
68
+
69
+ Args:
70
+ entries: lineage expand 后的 (depth, entry) 列表
71
+ relation: 关系类型 (ancestors / descendants), 仅用于 prompt 上下文
72
+
73
+ Returns:
74
+ CompressedLineage
75
+ """
76
+ # 规整化: (depth, entry)
77
+ normalized: list[tuple[int, object]] = []
78
+ for item in entries:
79
+ if isinstance(item, tuple) and len(item) == 2:
80
+ normalized.append(item)
81
+ else:
82
+ normalized.append((0, item))
83
+
84
+ if not normalized:
85
+ return CompressedLineage(
86
+ summary="", original_count=0, compressed_chars=0, method="heuristic"
87
+ )
88
+
89
+ if self._llm_callable is not None:
90
+ summary, method = self._llm_summarize(normalized, relation)
91
+ elif self.model == "mock":
92
+ summary = self._heuristic_summary(normalized, relation)
93
+ method = "heuristic"
94
+ else:
95
+ raise NotImplementedError(
96
+ f"真实 LLM '{self.model}' 未实现, 请提供 llm_callable 或使用 model='mock'"
97
+ )
98
+
99
+ # 截断到 max_tokens
100
+ if len(summary) > self.max_tokens:
101
+ summary = summary[: self.max_tokens - 3] + "..."
102
+
103
+ return CompressedLineage(
104
+ summary=summary,
105
+ original_count=len(normalized),
106
+ compressed_chars=len(summary),
107
+ method=method,
108
+ )
109
+
110
+ # ------------------------------------------------------------------
111
+ # 启发式 (无 LLM 依赖)
112
+ # ------------------------------------------------------------------
113
+
114
+ def _heuristic_summary(
115
+ self,
116
+ entries: list[tuple[int, object]],
117
+ relation: str,
118
+ ) -> str:
119
+ """启发式: 每 entry 1 行, name + operation + sharpe。"""
120
+ relation_arrow = "↑" if relation == "ancestors" else "↓"
121
+ parts: list[str] = []
122
+ for depth, entry in entries:
123
+ cfg = (entry.config_snapshot or {}).get("factor", {})
124
+ name = cfg.get("name", entry.entry_id[:8])
125
+ sharpe = (entry.metrics or {}).get("sharpe", 0)
126
+ entry_str = _HEURISTIC_ENTRY_TEMPLATE.format(
127
+ name=name, op=entry.operation,
128
+ round=entry.round_idx, sharpe=sharpe,
129
+ )
130
+ parts.append(f"{relation_arrow}d{depth} {entry_str}")
131
+ return " ; ".join(parts)
132
+
133
+ # ------------------------------------------------------------------
134
+ # LLM
135
+ # ------------------------------------------------------------------
136
+
137
+ def _llm_summarize(
138
+ self,
139
+ entries: list[tuple[int, object]],
140
+ relation: str,
141
+ ) -> tuple[str, str]:
142
+ """真实 LLM 总结。Returns (summary, effective_method)."""
143
+ prompt = self._build_prompt(entries, relation)
144
+ raw = self._llm_callable(prompt)
145
+ try:
146
+ data = json.loads(raw)
147
+ return str(data.get("summary", "")), "llm"
148
+ except (json.JSONDecodeError, TypeError, KeyError):
149
+ # 解析失败, fallback 到启发式
150
+ return self._heuristic_summary(entries, relation), "heuristic"
151
+
152
+ def _build_prompt(
153
+ self,
154
+ entries: list[tuple[int, object]],
155
+ relation: str,
156
+ ) -> str:
157
+ """构造 LLM prompt。"""
158
+ lines: list[str] = []
159
+ for depth, entry in entries:
160
+ cfg = (entry.config_snapshot or {}).get("factor", {})
161
+ sharpe = (entry.metrics or {}).get("sharpe", 0)
162
+ lines.append(
163
+ f" depth={depth}, name={cfg.get('name', entry.entry_id[:8])}, "
164
+ f"op={entry.operation}, sharpe={sharpe}, "
165
+ f"desc={cfg.get('description', '')}"
166
+ )
167
+ entries_text = "\n".join(lines)
168
+ return (
169
+ f"请总结以下 {len(entries)} 个 {relation} 的核心设计思路 (限 {self.max_tokens} 字):\n"
170
+ f"{entries_text}\n\n"
171
+ f"返回 JSON: {{\"summary\": \"你的总结\"}}"
172
+ )
173
+
174
+
175
+ # ============================================================================
176
+ # 模块级便捷函数
177
+ # ============================================================================
178
+
179
+ def compress_lineage(
180
+ entries,
181
+ relation: str = "ancestors",
182
+ model: str = "mock",
183
+ max_tokens: int = 200,
184
+ llm_callable: Optional[Callable] = None,
185
+ ) -> CompressedLineage:
186
+ """便捷函数: 一次性压缩。
187
+
188
+ Args:
189
+ entries: (depth, entry) 列表
190
+ relation: "ancestors" / "descendants"
191
+ model: 同 Compressor
192
+ max_tokens: 同 Compressor
193
+ llm_callable: 同 Compressor
194
+ """
195
+ c = Compressor(model=model, max_tokens=max_tokens, llm_callable=llm_callable)
196
+ return c.compress(entries, relation=relation)
@@ -0,0 +1,123 @@
1
+ """谱系展开 — 从根 entry 出发, BFS 收集 ancestors/descendants 到指定深度。
2
+
3
+ 输出格式:
4
+ {
5
+ 'root': TrajectoryEntry,
6
+ 'ancestors': [(depth, entry), ...], # depth=1=parent, 2=grandparent, ...
7
+ 'descendants': [(depth, entry), ...],
8
+ }
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from collections import deque
13
+
14
+ from ..trajectory import TrajectoryEntry, TrajectoryPool, children_of
15
+
16
+
17
+ def expand_lineage(
18
+ pool: TrajectoryPool,
19
+ root_id: str,
20
+ max_ancestor_depth: int = 2,
21
+ max_descendant_depth: int = 2,
22
+ max_ancestors: int = 8,
23
+ max_descendants: int = 8,
24
+ ) -> dict:
25
+ """BFS 展开谱系, 返回 root + ancestors + descendants。
26
+
27
+ Args:
28
+ pool: TrajectoryPool
29
+ root_id: 中心 entry ID
30
+ max_ancestor_depth: 上溯深度 (1=parent, 2=grandparent, ...)
31
+ max_descendant_depth: 下探深度
32
+ max_ancestors: 最多收集多少 ancestor (避免 token 爆炸)
33
+ max_descendants: 最多收集多少 descendant
34
+
35
+ Returns:
36
+ dict: {'root', 'ancestors': [(depth, entry)], 'descendants': [(depth, entry)]}
37
+
38
+ H8 (2026-06-20): all_ids set is computed ONCE at function start
39
+ instead of inside every BFS step. Previously each BFS iteration
40
+ re-iterated pool.all() (O(N)) and rebuilt a set, making the total
41
+ cost O(K * N) where K is the BFS depth/width. Now: O(N) total.
42
+ """
43
+ all_ids = {e.entry_id for e in pool.all()}
44
+
45
+ if root_id not in all_ids:
46
+ return {"root": None, "ancestors": [], "descendants": []}
47
+
48
+ try:
49
+ root = pool.get(root_id)
50
+ except KeyError:
51
+ return {"root": None, "ancestors": [], "descendants": []}
52
+
53
+ ancestors: list[tuple[int, TrajectoryEntry]] = []
54
+ descendants: list[tuple[int, TrajectoryEntry]] = []
55
+
56
+ # ------------------------------------------------------------------
57
+ # Ancestors: BFS 上溯
58
+ # ------------------------------------------------------------------
59
+ visited_up: set[str] = {root_id}
60
+ queue: deque = deque([(root, 0)])
61
+ while queue and len(ancestors) < max_ancestors:
62
+ node, depth = queue.popleft()
63
+ if depth >= max_ancestor_depth:
64
+ continue
65
+ for pid in node.parent_ids:
66
+ if pid in visited_up or pid not in all_ids:
67
+ continue
68
+ visited_up.add(pid)
69
+ try:
70
+ parent = pool.get(pid)
71
+ except KeyError:
72
+ continue
73
+ ancestors.append((depth + 1, parent))
74
+ queue.append((parent, depth + 1))
75
+
76
+ # ------------------------------------------------------------------
77
+ # Descendants: BFS 下探
78
+ # ------------------------------------------------------------------
79
+ visited_down: set[str] = {root_id}
80
+ queue = deque([(root, 0)])
81
+ while queue and len(descendants) < max_descendants:
82
+ node, depth = queue.popleft()
83
+ if depth >= max_descendant_depth:
84
+ continue
85
+ for child in children_of(pool, node.entry_id):
86
+ if child.entry_id in visited_down:
87
+ continue
88
+ visited_down.add(child.entry_id)
89
+ descendants.append((depth + 1, child))
90
+ queue.append((child, depth + 1))
91
+
92
+ # 排序: 浅的在前, 同深度内按 round_idx 升序
93
+ ancestors.sort(key=lambda x: (x[0], x[1].round_idx))
94
+ descendants.sort(key=lambda x: (x[0], x[1].round_idx))
95
+
96
+ return {
97
+ "root": root,
98
+ "ancestors": ancestors,
99
+ "descendants": descendants,
100
+ }
101
+
102
+
103
+ def expand_lineage_batch(
104
+ pool: TrajectoryPool,
105
+ root_ids: list[str],
106
+ max_ancestor_depth: int = 2,
107
+ max_descendant_depth: int = 2,
108
+ max_ancestors: int = 8,
109
+ max_descendants: int = 8,
110
+ ) -> list[dict]:
111
+ """批量展开, 去重。"""
112
+ seen: dict[str, dict] = {}
113
+ for rid in root_ids:
114
+ if rid in seen:
115
+ continue
116
+ seen[rid] = expand_lineage(
117
+ pool, rid,
118
+ max_ancestor_depth=max_ancestor_depth,
119
+ max_descendant_depth=max_descendant_depth,
120
+ max_ancestors=max_ancestors,
121
+ max_descendants=max_descendants,
122
+ )
123
+ return list(seen.values())
@@ -0,0 +1,43 @@
1
+ """RAG 评估指标 (Week 10)。
2
+
3
+ 公开 API:
4
+ - 5 个核心指标函数:
5
+ hit_rate_at_k / mean_hit_rate_at_k
6
+ ndcg_at_k / dcg_at_k / mean_ndcg_at_k
7
+ reciprocal_rank / mean_reciprocal_rank
8
+ lineage_coverage / mean_lineage_coverage
9
+ intra_list_diversity / jaccard_similarity
10
+ - RAGEvaluator: 汇总多 query 评估
11
+ - EvalReport / QueryResult: 结果数据类
12
+ """
13
+ from .metrics import (
14
+ dcg_at_k,
15
+ hit_rate_at_k,
16
+ intra_list_diversity,
17
+ jaccard_similarity,
18
+ lineage_coverage,
19
+ mean_hit_rate_at_k,
20
+ mean_lineage_coverage,
21
+ mean_ndcg_at_k,
22
+ mean_reciprocal_rank,
23
+ ndcg_at_k,
24
+ reciprocal_rank,
25
+ )
26
+ from .evaluator import EvalReport, QueryResult, RAGEvaluator
27
+
28
+ __all__ = [
29
+ "dcg_at_k",
30
+ "hit_rate_at_k",
31
+ "intra_list_diversity",
32
+ "jaccard_similarity",
33
+ "lineage_coverage",
34
+ "mean_hit_rate_at_k",
35
+ "mean_lineage_coverage",
36
+ "mean_ndcg_at_k",
37
+ "mean_reciprocal_rank",
38
+ "ndcg_at_k",
39
+ "reciprocal_rank",
40
+ "EvalReport",
41
+ "QueryResult",
42
+ "RAGEvaluator",
43
+ ]