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,511 @@
1
+ # coding=utf-8
2
+ """Helper functions and constants used by QuantNodes CLI commands."""
3
+
4
+ import argparse
5
+ import functools
6
+ import os
7
+ import signal
8
+ import socket
9
+ import sys
10
+ import subprocess
11
+ import time
12
+ from pathlib import Path
13
+ from typing import Optional, Sequence
14
+
15
+ from QuantNodes.research.wiki import init_factor_wiki
16
+ from QuantNodes.core.path_utils import ensure_dir
17
+
18
+ PROG_NAME = "quantnodes"
19
+
20
+ # Re-export from QuantNodes.constants (single source of truth)
21
+ from QuantNodes.constants import ( # noqa: E402, F401
22
+ DEFAULT_API_PORT,
23
+ DEFAULT_FRONTEND_PORT,
24
+ DEFAULT_HOST,
25
+ DEFAULT_GATEWAY_PORT,
26
+ )
27
+ PIDFILE_NAME = ".quantnodes.pid"
28
+
29
+
30
+ def is_initialized() -> bool:
31
+ """Check if current directory is already initialized."""
32
+ return Path(".env").exists() or Path("conn.ini").exists() or Path("wiki/index.md").exists()
33
+
34
+
35
+ def get_project_root() -> Path:
36
+ """Get the QuantNodes project root directory.
37
+
38
+ Walks up from this file to find the repo root (containing pyproject.toml).
39
+ Works from any working directory.
40
+ """
41
+ current = Path(__file__).resolve().parent
42
+ while current != current.parent:
43
+ if (current / "pyproject.toml").exists():
44
+ return current
45
+ current = current.parent
46
+ return Path(__file__).resolve().parent.parent
47
+
48
+
49
+ def create_directory_structure():
50
+ """Create necessary directories.
51
+
52
+ Two output roots are used by different subsystems:
53
+ - outputs/ backtest engine results (equity / signals / trades parquets)
54
+ - output/ factor_test pipeline artefacts (per-node parquets + final report)
55
+ """
56
+ dirs = [
57
+ "data",
58
+ ".agent/memory",
59
+ ".agent/dream",
60
+ "output",
61
+ "outputs",
62
+ "logs",
63
+ ]
64
+
65
+ for d in dirs:
66
+ ensure_dir(Path(d))
67
+ print(f" ✓ 创建目录: {d}/")
68
+
69
+
70
+ def init_llmwikify_wiki(force: bool = False) -> bool:
71
+ """Initialize QuantNodes wiki structure."""
72
+ print("\n 初始化 QuantNodes Wiki...")
73
+
74
+ wiki_exists = Path("wiki/index.md").exists()
75
+
76
+ if wiki_exists and not force:
77
+ print(" ⏭️ Wiki 已存在,跳过初始化")
78
+ try:
79
+ init_factor_wiki("wiki", force=False)
80
+ print(" ✓ Wiki 配置已更新")
81
+ return True
82
+ except Exception as e:
83
+ print(f" ⚠ 配置更新失败: {e}")
84
+ return False
85
+
86
+ if wiki_exists and force:
87
+ print(" 🔄 强制重新初始化 Wiki")
88
+
89
+ try:
90
+ result = subprocess.run(
91
+ [sys.executable, "-m", "llmwikify", "init"],
92
+ capture_output=True,
93
+ text=True
94
+ )
95
+ if result.returncode != 0:
96
+ print(f" ⚠ llmwikify 初始化失败: {result.stderr}")
97
+ return False
98
+ print(" ✓ llmwikify 基础结构创建完成")
99
+ except Exception as e:
100
+ print(f" ⚠ llmwikify 初始化失败: {e}")
101
+ return False
102
+
103
+ try:
104
+ init_factor_wiki("wiki", force=True)
105
+ print(" ✓ QuantNodes 专用配置写入完成")
106
+ return True
107
+ except Exception as e:
108
+ print(f" ⚠ 配置写入失败: {e}")
109
+ return False
110
+
111
+
112
+ def get_input_with_default(prompt: str, default: str, required: bool = False) -> str:
113
+ """Get user input with a default value."""
114
+ while True:
115
+ try:
116
+ response = input(f"{prompt} [{default}]: ").strip()
117
+ if not response:
118
+ return default
119
+ return response
120
+ except EOFError:
121
+ if required:
122
+ continue
123
+ return default
124
+
125
+
126
+ # ============================================================================
127
+ # Phase I1: argparse builders for repeated argument groups (2026-06-20)
128
+ # ============================================================================
129
+ # Each builder attaches a common set of CLI args to the given parser.
130
+ # Used by _build_parser() to dedup 6+ repeated patterns across factor-* cmds:
131
+ # --pool-dir (6 sites), --top (2), --metric (2), --title (2),
132
+ # --ancestor-depth/--descendant-depth (2),
133
+ # --min-ipo-days/--min-group-size/--groups (2).
134
+
135
+
136
+ def add_pool_dir_arg(parser: argparse.ArgumentParser) -> None:
137
+ """Attach --pool-dir <path> (required)."""
138
+ parser.add_argument("--pool-dir", required=True, help="Pool 目录路径")
139
+
140
+
141
+ def add_top_arg(parser: argparse.ArgumentParser, default: int = 5) -> None:
142
+ """Attach --top <N>."""
143
+ parser.add_argument("--top", type=int, default=default, help=f"Top-N (默认 {default})")
144
+
145
+
146
+ def add_metric_arg(parser: argparse.ArgumentParser, default: str = "sharpe") -> None:
147
+ """Attach --metric <name>."""
148
+ parser.add_argument("--metric", default=default, help=f"排序指标 (默认 {default})")
149
+
150
+
151
+ def add_title_arg(parser: argparse.ArgumentParser) -> None:
152
+ """Attach --title <text>."""
153
+ parser.add_argument("--title", default=None, help="报告标题")
154
+
155
+
156
+ def add_output_arg(parser: argparse.ArgumentParser, default: str | None = None) -> None:
157
+ """Attach --output <path>."""
158
+ parser.add_argument("--output", default=default, help="HTML 输出路径")
159
+
160
+
161
+ def add_lineage_depth_args(parser: argparse.ArgumentParser) -> None:
162
+ """Attach --ancestor-depth / --descendant-depth (default 2 each)."""
163
+ parser.add_argument("--ancestor-depth", type=int, default=2, help="祖先深度 (默认 2)")
164
+ parser.add_argument("--descendant-depth", type=int, default=2, help="后裔深度 (默认 2)")
165
+
166
+
167
+ def add_cli_overrides(parser: argparse.ArgumentParser) -> None:
168
+ """Attach --min-ipo-days / --min-group-size / --groups (CLI overrides)."""
169
+ parser.add_argument("--min-ipo-days", type=int, default=None,
170
+ help="剔除上市不足 N 日新股 (覆盖 config 默认值 360)")
171
+ parser.add_argument("--min-group-size", type=int, default=None,
172
+ help="计算 IC 最少样本数 (覆盖 config 默认值 5)")
173
+ parser.add_argument("--groups", type=int, default=None,
174
+ help="分组分析分组数 (覆盖 config 默认值 5)")
175
+
176
+
177
+ # ============================================================================
178
+ # Phase I2: cli_safe_run decorator (2026-06-20)
179
+ # ============================================================================
180
+
181
+ def cli_safe_run(func):
182
+ """Decorator: catch Exception, print 错误 + return 1.
183
+
184
+ Replaces the manual ``try: ... except Exception as e: print(f"错误: {e}"); return 1``
185
+ wrapper that was repeated 7+ times in factor.py and evolve.py.
186
+ """
187
+ @functools.wraps(func)
188
+ def wrapper(*args, **kwargs):
189
+ try:
190
+ return func(*args, **kwargs)
191
+ except Exception as e:
192
+ print(f"错误: {e}")
193
+ return 1
194
+ return wrapper
195
+
196
+
197
+ # ============================================================================
198
+ # Phase I3: confirm_section helper (2026-06-20)
199
+ # ============================================================================
200
+
201
+ def confirm_section(
202
+ name: str,
203
+ fields: Sequence[tuple[str, str]],
204
+ default: bool = False,
205
+ ) -> dict | None:
206
+ """Prompt "是否配置 {name}" → on Yes, ask each (label, default) tuple.
207
+
208
+ Used by init.py for ClickHouse / MySQL config blocks. Each block
209
+ was 7 lines of boilerplate (print header + 5 get_input_with_default calls);
210
+ confirm_section collapses to a single call with a field list.
211
+
212
+ Args:
213
+ name: section name (e.g. "ClickHouse").
214
+ fields: sequence of (label, default) pairs to prompt for.
215
+ default: default for the yes/no confirm.
216
+
217
+ Returns:
218
+ Dict of {label: value} on confirmation, or None if declined.
219
+ """
220
+ if not get_yes_no(f"是否配置 {name}", default=default):
221
+ return None
222
+ print(f"\n {name} 配置:")
223
+ return {label: get_input_with_default(f" {label}", default) for label, default in fields}
224
+
225
+
226
+ def get_yes_no(prompt: str, default: bool = True) -> bool:
227
+ """Get yes/no input from user."""
228
+ default_str = "Y/n" if default else "y/N"
229
+ while True:
230
+ try:
231
+ response = input(f"{prompt} ({default_str}): ").strip().lower()
232
+ if not response:
233
+ return default
234
+ if response in ("y", "yes"):
235
+ return True
236
+ if response in ("n", "no"):
237
+ return False
238
+ print(" 请输入 y 或 n")
239
+ except EOFError:
240
+ return default
241
+
242
+
243
+ def get_model_choice() -> str:
244
+ """Get model choice from user."""
245
+ models = {
246
+ "1": "gpt-4",
247
+ "2": "gpt-3.5-turbo",
248
+ "3": "gpt-4-turbo",
249
+ "4": "custom",
250
+ }
251
+
252
+ print(" 选择模型:")
253
+ print(" 1) gpt-4 (默认)")
254
+ print(" 2) gpt-3.5-turbo")
255
+ print(" 3) gpt-4-turbo")
256
+ print(" 4) 自定义")
257
+
258
+ while True:
259
+ try:
260
+ choice = input(" 选择 [1]: ").strip()
261
+ if not choice:
262
+ return "gpt-4"
263
+ if choice in models:
264
+ return models[choice]
265
+ print(" 无效选择,请输入 1-4")
266
+ except EOFError:
267
+ return "gpt-4"
268
+
269
+
270
+ def write_env_file(api_key: str, base_url: str, model: str, duckdb_path: str,
271
+ clickhouse_config: dict, mysql_config: dict) -> None:
272
+ """Write .env configuration file."""
273
+ env_content = f"""# LLM 配置
274
+ QUANTNODES__LLM__API_KEY={api_key}
275
+ QUANTNODES__LLM__BASE_URL={base_url}
276
+
277
+ # 模型配置
278
+ QUANTNODES__LLM__MODEL={model}
279
+
280
+ # DuckDB (默认本地文件)
281
+ QUANTNODES__DUCKDB__PATH={duckdb_path}
282
+
283
+ # 可选数据源
284
+ """
285
+
286
+ if clickhouse_config:
287
+ env_content += f"""QUANTNODES__CLICKHOUSE__HOST={clickhouse_config['host']}
288
+ QUANTNODES__CLICKHOUSE__PORT={clickhouse_config['port']}
289
+ QUANTNODES__CLICKHOUSE__USER={clickhouse_config['user']}
290
+ QUANTNODES__CLICKHOUSE__PASSWORD={clickhouse_config['passwd']}
291
+ QUANTNODES__CLICKHOUSE__DATABASE={clickhouse_config['db']}
292
+
293
+ """
294
+
295
+ if mysql_config:
296
+ env_content += f"""QUANTNODES__MYSQL__HOST={mysql_config['host']}
297
+ QUANTNODES__MYSQL__PORT={mysql_config['port']}
298
+ QUANTNODES__MYSQL__USER={mysql_config['user']}
299
+ QUANTNODES__MYSQL__PASSWORD={mysql_config['passwd']}
300
+ QUANTNODES__MYSQL__DATABASE={mysql_config['db']}
301
+
302
+ """
303
+
304
+ env_content += """# 缓存配置 (H18: 与 IFindFetcher.DEFAULT_CACHE_TTL_S=604800 对齐)
305
+ QUANTNODES__CACHE_ENABLED=true
306
+ QUANTNODES__CACHE_TTL=604800
307
+ """
308
+
309
+ with open(".env", "w", encoding="utf-8") as f:
310
+ f.write(env_content)
311
+ print(" ✓ 创建文件: .env")
312
+
313
+
314
+ def write_conn_ini(duckdb_path: str, clickhouse_config: dict, mysql_config: dict) -> None:
315
+ """Write conn.ini configuration file."""
316
+ conn_content = f"""[DuckDB]
317
+ path = {duckdb_path}
318
+ read_only = False
319
+
320
+ """
321
+
322
+ if clickhouse_config:
323
+ conn_content += f"""[ClickHouse]
324
+ host = {clickhouse_config['host']}
325
+ port = {clickhouse_config['port']}
326
+ user = {clickhouse_config['user']}
327
+ passwd = {clickhouse_config['passwd']}
328
+ db = {clickhouse_config['db']}
329
+
330
+ """
331
+
332
+ if mysql_config:
333
+ conn_content += f"""[MySQL]
334
+ host = {mysql_config['host']}
335
+ port = {mysql_config['port']}
336
+ user = {mysql_config['user']}
337
+ passwd = {mysql_config['passwd']}
338
+ db = {mysql_config['db']}
339
+ """
340
+
341
+ with open("conn.ini", "w", encoding="utf-8") as f:
342
+ f.write(conn_content)
343
+ print(" ✓ 创建文件: conn.ini")
344
+
345
+
346
+ def install_talib() -> bool:
347
+ """Install TA-Lib (optional)."""
348
+ print("\n 安装 TA-Lib...")
349
+ try:
350
+ result = subprocess.run(
351
+ ["pip", "install", "TA-Lib>=0.6.0"],
352
+ capture_output=True,
353
+ text=True
354
+ )
355
+ if result.returncode == 0:
356
+ print(" ✓ TA-Lib 安装完成")
357
+ return True
358
+ else:
359
+ print(" ⚠ TA-Lib 安装失败 (可能需要系统库),跳过继续...")
360
+ return False
361
+ except Exception as e:
362
+ print(f" ⚠ TA-Lib 安装失败: {e},跳过继续...")
363
+ return False
364
+
365
+
366
+ # ============================================================================
367
+ # v3.0.0 Stage 7 — serve/stop/status lifecycle helpers
368
+ # ============================================================================
369
+ #
370
+ # These helpers back the new ``quantnodes serve`` / ``stop`` / ``status`` /
371
+ # ``agent`` subcommands. Design goals:
372
+ # - Reuse from both CLI (foreground or daemon) and FastAPI startup
373
+ # (api/main.py uses ``load_env_file`` to populate os.environ before
374
+ # reading QUANTNODES__LLM__*).
375
+ # - Avoid new dependencies: use only stdlib + httpx (already required).
376
+ # - Keep pidfile simple: one file at project root, single int PID.
377
+
378
+
379
+ def load_env_file(env_path: Optional[Path] = None) -> bool:
380
+ """Load ``.env`` into ``os.environ`` (no override).
381
+
382
+ Returns True if the file existed and was loaded, False otherwise.
383
+ Used by both ``quantnodes serve`` (CLI) and ``api/main.py`` (FastAPI
384
+ lifespan startup) so a single source of truth populates
385
+ ``QUANTNODES__LLM__*`` / ``NANOBOT_GATEWAY_*`` / ``FEISHU_*`` for
386
+ ``os.environ.get(...)`` consumers (config_mapper.py, nanobot_runtime.py).
387
+
388
+ Idempotent: safe to call multiple times. ``override=False`` so an
389
+ explicit shell env var always wins (e.g. ``NANOBOT_GATEWAY_PORT=18090
390
+ quantnodes serve``).
391
+ """
392
+ env_path = Path(env_path) if env_path else Path.cwd() / ".env"
393
+ if not env_path.is_file():
394
+ return False
395
+ try:
396
+ from dotenv import load_dotenv
397
+ load_dotenv(env_path, override=False)
398
+ return True
399
+ except ImportError:
400
+ # python-dotenv is optional. Without it the user must source .env
401
+ # manually (or use shell exports).
402
+ return False
403
+
404
+
405
+ def write_pidfile(pid: int, path: Optional[Path] = None) -> Path:
406
+ """Write ``pid`` to ``.quantnodes.pid`` (project root by default)."""
407
+ path = Path(path) if path else Path.cwd() / PIDFILE_NAME
408
+ path.write_text(str(pid), encoding="utf-8")
409
+ return path
410
+
411
+
412
+ def read_pidfile(path: Optional[Path] = None) -> Optional[int]:
413
+ """Read PID from ``.quantnodes.pid``. Returns None if missing/invalid."""
414
+ path = Path(path) if path else Path.cwd() / PIDFILE_NAME
415
+ if not path.is_file():
416
+ return None
417
+ try:
418
+ return int(path.read_text(encoding="utf-8").strip())
419
+ except (ValueError, OSError):
420
+ return None
421
+
422
+
423
+ def remove_pidfile(path: Optional[Path] = None) -> None:
424
+ """Remove ``.quantnodes.pid`` if it exists (best-effort)."""
425
+ path = Path(path) if path else Path.cwd() / PIDFILE_NAME
426
+ if path.is_file():
427
+ try:
428
+ path.unlink()
429
+ except OSError:
430
+ pass
431
+
432
+
433
+ def is_pid_alive(pid: int) -> bool:
434
+ """Return True if a process with ``pid`` is running.
435
+
436
+ Uses ``kill(pid, 0)`` which doesn't actually send a signal but raises
437
+ ``ProcessLookupError`` if the process is gone. Works on Linux/macOS.
438
+ """
439
+ try:
440
+ os.kill(pid, 0)
441
+ return True
442
+ except (ProcessLookupError, PermissionError):
443
+ return False
444
+ except OSError:
445
+ return False
446
+
447
+
448
+ def is_port_free(port: int, host: str = "127.0.0.1") -> bool:
449
+ """Return True if ``port`` on ``host`` is currently bindable.
450
+
451
+ Tries to bind a TCP socket to the address. ``OSError`` (EADDRINUSE)
452
+ means the port is taken; anything else means it's free. We don't
453
+ actually listen (SO_REUSEADDR + immediate close).
454
+ """
455
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
456
+ try:
457
+ sock.bind((host, port))
458
+ return True
459
+ except OSError:
460
+ return False
461
+
462
+
463
+ def wait_for_health(api_url: str, timeout_s: int = 30,
464
+ poll_interval_s: float = 1.0) -> bool:
465
+ """Poll ``GET {api_url}/api/agent/status`` until ``state == "running"``.
466
+
467
+ Returns True on success, False on timeout. Used by ``quantnodes serve``
468
+ to block until the FastAPI lifespan has wired the nanobot runtime.
469
+ """
470
+ import httpx # local import: avoid hard dep at module-import time
471
+ deadline = time.time() + timeout_s
472
+ last_state = "unreachable"
473
+ while time.time() < deadline:
474
+ try:
475
+ r = httpx.get(f"{api_url}/api/agent/status", timeout=2.0)
476
+ if r.status_code == 200:
477
+ data = r.json()
478
+ last_state = data.get("state", "unknown")
479
+ if last_state == "running":
480
+ return True
481
+ except Exception:
482
+ pass
483
+ time.sleep(poll_interval_s)
484
+ print(f" ⚠ health check timeout after {timeout_s}s (last state={last_state})")
485
+ return False
486
+
487
+
488
+ def is_nanobot_installed() -> bool:
489
+ """Return True if ``nanobot-ai`` is importable.
490
+
491
+ Used by ``quantnodes init`` and ``serve --check-env`` to give a
492
+ friendly non-blocking warning when the optional ``[agent]`` extra
493
+ is not installed. Quant tool library (Wiki/Factor/Backtest/Strategy)
494
+ works without it; only Agent Chat / WebUI / MCP / Feishu need it.
495
+ """
496
+ try:
497
+ from nanobot.agent.tools.base import Tool # noqa: F401
498
+ return True
499
+ except ImportError:
500
+ return False
501
+
502
+
503
+ def print_nanobot_install_hint() -> None:
504
+ """Print a friendly hint for installing the optional ``[agent]`` extra."""
505
+ if is_nanobot_installed():
506
+ return
507
+ print()
508
+ print("ℹ 未检测到 nanobot-ai(量化 agent 可选依赖)")
509
+ print(" 安装后可启用 Agent Chat / WebUI / MCP / 飞书:")
510
+ print(" pip install 'quantnodes[agent]' # 或 'quantnodes[all]'")
511
+ print(" 未安装时量化工具库(Wiki / Factor / Backtest / Strategy)完全可用")
@@ -0,0 +1,110 @@
1
+ # coding=utf-8
2
+ """CLI Command pattern base (Phase 3.1, 2026-06-22).
3
+
4
+ 替代原 cli/__init__.py:159-192 的 34 行 if/elif ladder 派发到 cmd_*
5
+ 函数。新增 subcommand 只需:
6
+ 1. 写一个 Command 子类 (name + add_arguments + run)
7
+ 2. 在 commands/__init__.py 注册到 REGISTRY
8
+ 3. cli/__init__.py:build_parser 自动加 parser, main() 自动 dispatch
9
+
10
+ 向后兼容: 旧的 cmd_* 函数仍 export, ``from QuantNodes.cli import cmd_init`` 等
11
+ 调用方式不变。
12
+
13
+ 设计要点:
14
+ - Command ABC: name (str) + description (str) + add_arguments(subparsers) + run(args)
15
+ - CommandRegistry: register / get / all 三个方法, 支持同名注册报错
16
+ - 不修改 ``argparse.Namespace`` 结构: 仍走 args.command / args.* 字段
17
+ """
18
+ from __future__ import annotations
19
+
20
+ from abc import ABC, abstractmethod
21
+ from typing import TYPE_CHECKING, List, Optional
22
+
23
+ if TYPE_CHECKING:
24
+ import argparse
25
+
26
+
27
+ class Command(ABC):
28
+ """CLI 子命令抽象基类 (Command pattern).
29
+
30
+ 子类需实现:
31
+ - name: 子命令名 (e.g. "init", "factor-info")
32
+ - description: 简短描述 (用于 help)
33
+ - add_arguments(subparsers): 注册该子命令的 argparse 参数
34
+ - run(args) -> int: 执行子命令, 返回 exit code (0 = 成功)
35
+ """
36
+
37
+ name: str = ""
38
+ description: str = ""
39
+
40
+ @abstractmethod
41
+ def add_arguments(self, subparsers: "argparse._SubParsersAction") -> None:
42
+ """注册 argparse 子命令参数.
43
+
44
+ Args:
45
+ subparsers: parser.add_subparsers() 返回的 action,
46
+ 调用 subparsers.add_parser(self.name, ...) 添加.
47
+ """
48
+ raise NotImplementedError
49
+
50
+ @abstractmethod
51
+ def run(self, args: "argparse.Namespace") -> int:
52
+ """执行子命令. 返回 exit code (0 = 成功, 非 0 = 错误)."""
53
+ raise NotImplementedError
54
+
55
+ def __repr__(self) -> str:
56
+ return f"<{self.__class__.__name__} name={self.name!r}>"
57
+
58
+
59
+ class CommandRegistry:
60
+ """CLI 子命令注册表 (Phase 3.1).
61
+
62
+ 提供 register / get / all 三个方法. 重复注册同名 command 抛 ValueError,
63
+ 保证 1 个 name 对应 1 个 handler.
64
+ """
65
+
66
+ def __init__(self) -> None:
67
+ self._cmds: dict[str, Command] = {}
68
+
69
+ def register(self, cmd: Command) -> Command:
70
+ """注册一个 Command. 重复同名抛 ValueError.
71
+
72
+ Returns:
73
+ 传入的 cmd (便于 ``@REGISTRY.register class ...`` 链式调用)
74
+ """
75
+ if not cmd.name:
76
+ raise ValueError(
77
+ f"{cmd!r} has empty .name, refusing to register"
78
+ )
79
+ if cmd.name in self._cmds:
80
+ existing = self._cmds[cmd.name]
81
+ if existing is cmd:
82
+ return cmd # idempotent
83
+ raise ValueError(
84
+ f"Command '{cmd.name}' already registered to {existing!r}, "
85
+ f"refusing to overwrite with {cmd!r}"
86
+ )
87
+ self._cmds[cmd.name] = cmd
88
+ return cmd
89
+
90
+ def get(self, name: str) -> Optional[Command]:
91
+ """按 name 查询. 未找到返回 None."""
92
+ return self._cmds.get(name)
93
+
94
+ def all(self) -> List[Command]:
95
+ """返回所有注册 command 的 list (顺序与注册顺序一致)."""
96
+ return list(self._cmds.values())
97
+
98
+ def names(self) -> List[str]:
99
+ """返回所有注册 name (按注册顺序)."""
100
+ return list(self._cmds.keys())
101
+
102
+ def clear(self) -> None:
103
+ """清空注册表 (仅供测试用)."""
104
+ self._cmds.clear()
105
+
106
+ def __len__(self) -> int:
107
+ return len(self._cmds)
108
+
109
+ def __contains__(self, name: str) -> bool:
110
+ return name in self._cmds
@@ -0,0 +1,69 @@
1
+ # coding=utf-8
2
+ """Command modules for QuantNodes CLI.
3
+
4
+ Each module exposes the command handler(s) it owns. Public re-exports are
5
+ performed by QuantNodes.cli.__init__ so the entry point
6
+ ``from QuantNodes.cli import main`` keeps working unchanged.
7
+
8
+ Phase 3.1 (2026-06-22): 新增 COMMAND_REGISTRY (Command pattern).
9
+ 各 command module 末尾追加 *Command 子类, 此文件在 import 时统一注册到
10
+ REGISTRY. cli/__init__.py:build_parser / main 改为用 registry 派发.
11
+ """
12
+ from QuantNodes.cli.command import CommandRegistry
13
+
14
+ # 模块级 registry (单例)
15
+ COMMAND_REGISTRY = CommandRegistry()
16
+
17
+
18
+ def _register_all() -> None:
19
+ """注册所有 command. 顺序决定 argparse help 中子命令的显示顺序."""
20
+ # 顺序: 初始化 / 服务生命周期 / 启动 / 对话 / agent / 演化 / factor-* / version / help
21
+ from QuantNodes.cli.commands.init import InitCommand
22
+ # v3.0.0 Stage 7: 服务生命周期 (serve / stop / status / logs)
23
+ from QuantNodes.cli.commands.serve import (
24
+ ServeCommand, StopCommand, StatusCommand, LogsCommand,
25
+ )
26
+ from QuantNodes.cli.commands.run import RunCommand
27
+ from QuantNodes.cli.commands.chat import ChatCommand
28
+ # v3.0.0 Stage 7: HTTP 客户端 (agent status / chat / restart)
29
+ from QuantNodes.cli.commands.agent import AgentCommand
30
+ from QuantNodes.cli.commands.evolve import EvolveCommand
31
+ from QuantNodes.cli.commands.alpha import AlphaMctsCommand, AlphaGptCommand, AlphaPipelineCommand
32
+ from QuantNodes.cli.commands.factor import (
33
+ FactorInfoCommand,
34
+ FactorBestCommand,
35
+ FactorVisualCommand,
36
+ FactorDashboardCommand,
37
+ FactorDataFetchCommand,
38
+ FactorRagEvalCommand,
39
+ FactorRagShowCommand,
40
+ )
41
+ from QuantNodes.cli.commands.version import VersionCommand, HelpCommand
42
+
43
+ for cmd in [
44
+ InitCommand(),
45
+ ServeCommand(),
46
+ StopCommand(),
47
+ StatusCommand(),
48
+ LogsCommand(),
49
+ RunCommand(),
50
+ ChatCommand(),
51
+ AgentCommand(),
52
+ EvolveCommand(),
53
+ AlphaMctsCommand(),
54
+ AlphaGptCommand(),
55
+ AlphaPipelineCommand(),
56
+ FactorInfoCommand(),
57
+ FactorBestCommand(),
58
+ FactorVisualCommand(),
59
+ FactorRagShowCommand(),
60
+ FactorRagEvalCommand(),
61
+ FactorDataFetchCommand(),
62
+ FactorDashboardCommand(),
63
+ VersionCommand(),
64
+ HelpCommand(),
65
+ ]:
66
+ COMMAND_REGISTRY.register(cmd)
67
+
68
+
69
+ _register_all()