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,218 @@
1
+ # coding=utf-8
2
+ """
3
+ operator_lookup.py - 算子查询工具
4
+
5
+ 让 agent 动态发现 OperatorVocab 中的 162 个可用算子,
6
+ 获取算子签名、参数、示例,以及校验公式有效性。
7
+
8
+ 用法 (agent 调用)::
9
+
10
+ # 列出所有算子
11
+ operator_lookup(action="list_operators")
12
+ operator_lookup(action="list_operators", category="time")
13
+
14
+ # 获取算子详情
15
+ operator_lookup(action="get_operator_info", name="ts_mean")
16
+
17
+ # 校验公式
18
+ operator_lookup(action="validate_formula", formula="rank(ts_mean(close, 20))")
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import logging
24
+ from typing import Any, Dict, Optional, Sequence
25
+
26
+ from .base import Tool
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ __all__ = ["OperatorLookupTool"]
31
+
32
+
33
+ class OperatorLookupTool(Tool):
34
+ """算子查询工具 — 让 agent 发现可用算子
35
+
36
+ 3 个 action:
37
+ - list_operators: 列出所有算子(可按类别过滤)
38
+ - get_operator_info: 获取单个算子详情(签名、参数、示例)
39
+ - validate_formula: 校验公式是否有效
40
+ """
41
+
42
+ def __init__(self) -> None:
43
+ pass
44
+
45
+ @property
46
+ def name(self) -> str:
47
+ return "operator_lookup"
48
+
49
+ @property
50
+ def description(self) -> str:
51
+ return (
52
+ "查询 QuantNodes OperatorVocab 中的可用算子(162 个)。"
53
+ "用于发现算子、获取用法说明、校验公式有效性。"
54
+ "生成 alpha 因子公式前,应先调用此工具获取可用算子列表。"
55
+ )
56
+
57
+ @property
58
+ def parameters(self) -> Dict[str, Any]:
59
+ return {
60
+ "type": "object",
61
+ "properties": {
62
+ "action": {
63
+ "type": "string",
64
+ "enum": [
65
+ "list_operators",
66
+ "get_operator_info",
67
+ "validate_formula",
68
+ ],
69
+ "description": (
70
+ "list_operators: 列出所有算子(可按 category 过滤)。"
71
+ "get_operator_info: 获取单个算子详情。"
72
+ "validate_formula: 校验公式是否有效。"
73
+ ),
74
+ },
75
+ "category": {
76
+ "type": "string",
77
+ "enum": ["time", "point", "section", "multi_section"],
78
+ "description": (
79
+ "算子类别过滤(list_operators 时使用)。"
80
+ "time=时间序列, point=逐点, section=截面, multi_section=多截面"
81
+ ),
82
+ },
83
+ "name": {
84
+ "type": "string",
85
+ "description": "算子名(get_operator_info 时使用)",
86
+ },
87
+ "formula": {
88
+ "type": "string",
89
+ "description": "公式字符串(validate_formula 时使用)",
90
+ },
91
+ },
92
+ "required": ["action"],
93
+ }
94
+
95
+ @property
96
+ def read_only(self) -> bool:
97
+ return True
98
+
99
+ async def execute(
100
+ self,
101
+ action: str,
102
+ category: Optional[str] = None,
103
+ name: Optional[str] = None,
104
+ formula: Optional[str] = None,
105
+ **kwargs: Any,
106
+ ) -> Dict[str, Any]:
107
+ """执行算子查询"""
108
+ try:
109
+ if action == "list_operators":
110
+ return self._list_operators(category)
111
+ elif action == "get_operator_info":
112
+ if not name:
113
+ return {"error": "name 参数必填"}
114
+ return self._get_operator_info(name)
115
+ elif action == "validate_formula":
116
+ if not formula:
117
+ return {"error": "formula 参数必填"}
118
+ return self._validate_formula(formula)
119
+ else:
120
+ return {"error": f"未知 action: {action}"}
121
+ except Exception as e:
122
+ logger.error("OperatorLookup failed: %s", e)
123
+ return {"error": str(e)}
124
+
125
+ def _list_operators(self, category: Optional[str] = None) -> Dict[str, Any]:
126
+ """列出所有算子(可按类别过滤)"""
127
+ from QuantNodes.research.quant_alpha.operator_vocab import (
128
+ list_vocab_operators,
129
+ get_vocab_metadata,
130
+ )
131
+
132
+ ops = list_vocab_operators(category=category)
133
+ result = []
134
+ for op_name in ops:
135
+ meta = get_vocab_metadata(op_name)
136
+ if meta:
137
+ result.append({
138
+ "name": op_name,
139
+ "category": meta.category,
140
+ "signature": meta.signature,
141
+ "doc": meta.doc,
142
+ })
143
+ else:
144
+ result.append({"name": op_name})
145
+
146
+ return {
147
+ "operators": result,
148
+ "total": len(result),
149
+ "category_filter": category,
150
+ }
151
+
152
+ def _get_operator_info(self, name: str) -> Dict[str, Any]:
153
+ """获取单个算子详情"""
154
+ from QuantNodes.research.quant_alpha.operator_vocab import get_vocab_metadata
155
+
156
+ meta = get_vocab_metadata(name)
157
+ if meta is None:
158
+ return {"error": f"算子 '{name}' 不存在"}
159
+
160
+ return {
161
+ "name": meta.name,
162
+ "category": meta.category,
163
+ "category_tags": meta.category_tags,
164
+ "signature": meta.signature,
165
+ "parameters": meta.parameters,
166
+ "doc": meta.doc,
167
+ "default_window": meta.default_window,
168
+ "examples": meta.examples,
169
+ "difficulty": meta.difficulty,
170
+ "output_dtype": meta.output_dtype,
171
+ "requires_group_by": meta.requires_group_by,
172
+ "composes_with": meta.composes_with,
173
+ }
174
+
175
+ def _validate_formula(self, formula: str) -> Dict[str, Any]:
176
+ """校验公式是否有效"""
177
+ from QuantNodes.research.quant_alpha.operator_vocab import OperatorVocab
178
+ import polars as pl
179
+
180
+ # 构造最小测试数据
181
+ test_data = pl.DataFrame({
182
+ "date": ["2020-01-01", "2020-01-02", "2020-01-03"] * 3,
183
+ "code": ["A"] * 3 + ["B"] * 3 + ["C"] * 3,
184
+ "open": [10.0] * 9,
185
+ "high": [10.5] * 9,
186
+ "low": [9.5] * 9,
187
+ "close": [10.0, 10.1, 10.2, 11.0, 11.1, 11.2, 12.0, 12.1, 12.2],
188
+ "vol": [1000.0] * 9,
189
+ "amount": [10000.0] * 9,
190
+ })
191
+
192
+ try:
193
+ vocab = OperatorVocab.default()
194
+ result = vocab.evaluate(
195
+ formula=formula,
196
+ data=test_data,
197
+ date_column="date",
198
+ code_column="code",
199
+ )
200
+ if result is not None and len(result) == len(test_data):
201
+ return {
202
+ "formula": formula,
203
+ "valid": True,
204
+ "error": None,
205
+ "result_length": len(result),
206
+ }
207
+ else:
208
+ return {
209
+ "formula": formula,
210
+ "valid": False,
211
+ "error": "评估结果为空或长度不匹配",
212
+ }
213
+ except Exception as e:
214
+ return {
215
+ "formula": formula,
216
+ "valid": False,
217
+ "error": str(e),
218
+ }
@@ -0,0 +1,77 @@
1
+ # coding=utf-8
2
+ """
3
+ 输出截断模块
4
+
5
+ 提供输出长度控制功能。
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass
12
+ class TruncatedOutput:
13
+ """截断后的输出"""
14
+ content: str
15
+ truncated: bool
16
+ total_lines: int
17
+ total_bytes: int
18
+ kept_lines: int
19
+ kept_bytes: int
20
+
21
+
22
+ def truncate_output(
23
+ output: str,
24
+ max_lines: int = 10000,
25
+ max_bytes: int = 1024 * 1024,
26
+ ) -> TruncatedOutput:
27
+ """截断输出
28
+
29
+ 策略:
30
+ 1. 保留前 max_lines/2 行
31
+ 2. 保留后 max_lines/2 行
32
+ 3. 中间用省略标记替代
33
+ 4. 同时检查字节限制
34
+
35
+ Args:
36
+ output: 原始输出
37
+ max_lines: 最大行数
38
+ max_bytes: 最大字节数
39
+
40
+ Returns:
41
+ TruncatedOutput 对象
42
+ """
43
+ lines = output.split("\n")
44
+ total_lines = len(lines)
45
+ total_bytes = len(output.encode("utf-8"))
46
+
47
+ truncated = False
48
+ kept_lines = total_lines
49
+ kept_bytes = total_bytes
50
+
51
+ if total_lines > max_lines or total_bytes > max_bytes:
52
+ truncated = True
53
+ half = max_lines // 2
54
+ kept_lines = min(total_lines, max_lines)
55
+
56
+ if total_lines > max_lines:
57
+ kept_lines = max_lines
58
+ head = lines[:half]
59
+ tail = lines[-(max_lines - half):]
60
+ lines = head + ["... (truncated) ..."] + tail
61
+
62
+ result = "\n".join(lines)
63
+
64
+ if len(result.encode("utf-8")) > max_bytes:
65
+ result = result[:max_bytes] + "\n... (truncated by bytes) ..."
66
+ kept_bytes = max_bytes
67
+ else:
68
+ result = output
69
+
70
+ return TruncatedOutput(
71
+ content=result,
72
+ truncated=truncated,
73
+ total_lines=total_lines,
74
+ total_bytes=total_bytes,
75
+ kept_lines=kept_lines,
76
+ kept_bytes=kept_bytes,
77
+ )
@@ -0,0 +1,43 @@
1
+ # coding=utf-8
2
+ """
3
+ 路径检查模块
4
+
5
+ 提供项目边界检查功能。
6
+ """
7
+
8
+ from pathlib import Path
9
+
10
+
11
+ def is_within_project(filepath: str, project_root: str) -> bool:
12
+ """检查文件路径是否在项目目录内
13
+
14
+ Args:
15
+ filepath: 文件路径
16
+ project_root: 项目根目录
17
+
18
+ Returns:
19
+ True if within project, False otherwise
20
+ """
21
+ try:
22
+ file_path = Path(filepath).resolve()
23
+ project_path = Path(project_root).resolve()
24
+ return file_path.is_relative_to(project_path)
25
+ except (ValueError, OSError):
26
+ return False
27
+
28
+
29
+ def assert_within_project(filepath: str, project_root: str) -> None:
30
+ """断言文件路径在项目目录内
31
+
32
+ Raises:
33
+ ExternalDirectoryError: 如果文件在项目外
34
+ """
35
+ if not is_within_project(filepath, project_root):
36
+ raise ExternalDirectoryError(
37
+ f"Access to external directory denied: {filepath}"
38
+ )
39
+
40
+
41
+ class ExternalDirectoryError(Exception):
42
+ """外部目录访问错误"""
43
+ pass
@@ -0,0 +1,62 @@
1
+ # coding=utf-8
2
+ """
3
+ Pipeline工具 - Agent 工具版本
4
+
5
+ 验证 QuantNodes Pipeline 代码的正确性。
6
+
7
+ 实现: 委托给 QuantNodes.methods.pipeline.validate_pipeline,
8
+ 保持 Tool 接口 (async, dict 返回) 以兼容 Agent 工具注册。
9
+ """
10
+
11
+ from typing import Any, Dict
12
+
13
+ from QuantNodes.agent.tools.base import Tool
14
+
15
+
16
+ class PipelineTool(Tool):
17
+ """Pipeline验证工具
18
+
19
+ 验证 QuantNodes Pipeline 代码的正确性,包括:
20
+ - 语法检查
21
+ - 安全检查(CodeSandbox)
22
+ - 节点提取
23
+ """
24
+
25
+ def __init__(self):
26
+ pass
27
+
28
+ @property
29
+ def name(self) -> str:
30
+ return "pipeline"
31
+
32
+ @property
33
+ def description(self) -> str:
34
+ return "验证 QuantNodes Pipeline 代码的正确性,检查语法和结构"
35
+
36
+ @property
37
+ def parameters(self) -> Dict[str, Any]:
38
+ return {
39
+ "type": "object",
40
+ "properties": {
41
+ "code": {
42
+ "type": "string",
43
+ "description": "QuantNodes Pipeline Python代码"
44
+ }
45
+ },
46
+ "required": ["code"]
47
+ }
48
+
49
+ @property
50
+ def read_only(self) -> bool:
51
+ return True
52
+
53
+ async def execute(self, code: str, **kwargs) -> Dict[str, Any]:
54
+ from QuantNodes.methods.pipeline import validate_pipeline
55
+ result = validate_pipeline(code=code)
56
+ return {
57
+ "is_valid": result.is_valid,
58
+ "errors": result.errors,
59
+ "warnings": result.warnings,
60
+ "nodes": result.nodes,
61
+ "security_status": result.security_status,
62
+ }
@@ -0,0 +1,150 @@
1
+ # coding=utf-8
2
+ """
3
+ 工具注册表
4
+
5
+ 管理工具的注册、查询与执行
6
+ """
7
+
8
+ from typing import Any, Dict, List
9
+ import asyncio
10
+
11
+ from .base import Tool, ToolExecutionResult
12
+
13
+
14
+ class ToolRegistry:
15
+ """工具注册表与执行入口"""
16
+
17
+ def __init__(self):
18
+ self._tools: Dict[str, Tool] = {}
19
+ self._cached_schemas: List[Dict[str, Any]] | None = None
20
+
21
+ def register(self, tool: Tool) -> None:
22
+ """注册工具"""
23
+ self._tools[tool.name] = tool
24
+ self._cached_schemas = None
25
+
26
+ def unregister(self, name: str) -> None:
27
+ """注销工具"""
28
+ if name in self._tools:
29
+ del self._tools[name]
30
+ self._cached_schemas = None
31
+
32
+ def get(self, name: str) -> Tool | None:
33
+ """获取工具"""
34
+ return self._tools.get(name)
35
+
36
+ def list_tools(self) -> List[Tool]:
37
+ """列出所有已注册工具"""
38
+ return list(self._tools.values())
39
+
40
+ def get_tool_schemas(self) -> List[Dict[str, Any]]:
41
+ """Return OpenAI function-calling schemas for all tools (cached).
42
+
43
+ v3.0.0 contract: if a tool cannot produce a schema (e.g. the
44
+ local ``Tool`` ABC when ``nanobot-ai`` is not installed), we
45
+ fall back to a minimal ``{"type": "function", "function": {"name": ..., "description": ..., "parameters": {"type": "object", "properties": {}}}}``
46
+ shape so the registry's schema list is always usable.
47
+ """
48
+ if self._cached_schemas is None:
49
+ schemas: List[Dict[str, Any]] = []
50
+ for tool in self._tools.values():
51
+ try:
52
+ schemas.append(tool.to_openai_schema())
53
+ except Exception:
54
+ # Fallback: minimal schema from raw fields
55
+ schemas.append({
56
+ "type": "function",
57
+ "function": {
58
+ "name": tool.name,
59
+ "description": tool.description,
60
+ "parameters": tool.parameters,
61
+ },
62
+ })
63
+ self._cached_schemas = schemas
64
+ return self._cached_schemas
65
+
66
+ async def execute_tool(self, name: str, **kwargs: Any) -> ToolExecutionResult:
67
+ """Execute a single tool by name.
68
+
69
+ v3.0.0 contract: we attempt to call ``tool.cast_params(kwargs)`` and
70
+ ``tool.validate_params(params)`` for forward-compat with the
71
+ upstream nanobot Tool API. If those methods don't exist on the
72
+ tool (e.g. local ``Tool`` ABC when ``nanobot-ai`` is not
73
+ installed, or third-party tools that don't implement them), we
74
+ gracefully fall back to passing the raw ``kwargs`` to
75
+ ``tool.execute()``.
76
+ """
77
+ tool = self._tools.get(name)
78
+ if not tool:
79
+ return ToolExecutionResult(
80
+ tool_name=name,
81
+ success=False,
82
+ content=None,
83
+ error=f"Tool '{name}' not found"
84
+ )
85
+
86
+ try:
87
+ # Optional pre-processing hooks. Missing methods are OK —
88
+ # the bare v3.0.0 ``Tool`` ABC doesn't define them.
89
+ params: Dict[str, Any] = kwargs
90
+ cast = getattr(tool, "cast_params", None)
91
+ if callable(cast):
92
+ params = cast(kwargs)
93
+ validate = getattr(tool, "validate_params", None)
94
+ if callable(validate):
95
+ errors = validate(params)
96
+ if errors:
97
+ return ToolExecutionResult(
98
+ tool_name=name,
99
+ success=False,
100
+ content=None,
101
+ error=f"Parameter validation failed: {', '.join(errors)}"
102
+ )
103
+
104
+ result = await tool.execute(**params)
105
+ return ToolExecutionResult(
106
+ tool_name=name,
107
+ success=True,
108
+ content=result
109
+ )
110
+ except Exception as e:
111
+ return ToolExecutionResult(
112
+ tool_name=name,
113
+ success=False,
114
+ content=None,
115
+ error=str(e)
116
+ )
117
+
118
+ async def execute_tools_parallel(
119
+ self,
120
+ tool_calls: List[Dict[str, Any]],
121
+ ) -> List[ToolExecutionResult]:
122
+ """并发执行多个工具(只读工具并发,有副作用工具串行)"""
123
+ results: List[ToolExecutionResult] = []
124
+
125
+ # 分组:只读工具并发执行,其他串行
126
+ read_only_calls: List[Dict[str, Any]] = []
127
+ write_calls: List[Dict[str, Any]] = []
128
+
129
+ for call in tool_calls:
130
+ tool = self._tools.get(call.get("name", ""))
131
+ if tool and tool.read_only:
132
+ read_only_calls.append(call)
133
+ else:
134
+ write_calls.append(call)
135
+
136
+ # 并发执行只读工具
137
+ if read_only_calls:
138
+ tasks = [
139
+ self.execute_tool(call["name"], **call.get("arguments", {}))
140
+ for call in read_only_calls
141
+ ]
142
+ results.extend(await asyncio.gather(*tasks))
143
+
144
+ # 串行执行有副作用工具
145
+ for call in write_calls:
146
+ name = call.get("name", "")
147
+ result = await self.execute_tool(name, **call.get("arguments", {}))
148
+ results.append(result)
149
+
150
+ return results
@@ -0,0 +1,62 @@
1
+ # coding=utf-8
2
+ """
3
+ 沙箱工具 - Agent 工具版本
4
+
5
+ 封装 QuantNodes AI 模块的 CodeSandbox,用于代码安全验证。
6
+
7
+ 实现: 委托给 self._sandbox.validate() (CodeSandbox 实例)。
8
+ 原本重复的 methods/sandbox.validate_code 现已删除,
9
+ 此模块通过 _sandbox 属性复用 CodeSandbox,避免逻辑重复。
10
+ """
11
+
12
+ from typing import Any, Dict
13
+
14
+ from QuantNodes.ai.sandbox import CodeSandbox as QNCodeSandbox
15
+ from QuantNodes.agent.tools.base import Tool
16
+
17
+
18
+ class SandboxTool(Tool):
19
+ """代码安全沙箱工具
20
+
21
+ 提供代码安全校验,防止执行危险操作。
22
+ """
23
+
24
+ def __init__(self, allow_warnings: bool = False, max_code_length: int = 10000):
25
+ self._sandbox = QNCodeSandbox(
26
+ allow_warnings=allow_warnings,
27
+ max_code_length=max_code_length
28
+ )
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ return "sandbox"
33
+
34
+ @property
35
+ def description(self) -> str:
36
+ return "验证Python代码的安全性,检查危险操作。不执行代码,只返回验证结果"
37
+
38
+ @property
39
+ def parameters(self) -> Dict[str, Any]:
40
+ return {
41
+ "type": "object",
42
+ "properties": {
43
+ "code": {
44
+ "type": "string",
45
+ "description": "待验证的Python代码"
46
+ }
47
+ },
48
+ "required": ["code"]
49
+ }
50
+
51
+ @property
52
+ def read_only(self) -> bool:
53
+ return True
54
+
55
+ async def execute(self, code: str, **kwargs) -> Dict[str, Any]:
56
+ result = self._sandbox.validate(code)
57
+ return {
58
+ "is_safe": result.is_safe,
59
+ "errors": result.errors,
60
+ "warnings": result.warnings,
61
+ "warnings_only": result.warnings_only,
62
+ }
@@ -0,0 +1,63 @@
1
+ # coding=utf-8
2
+ """
3
+ Shell 安全模块
4
+
5
+ 提供命令超时、执行控制等功能。
6
+ """
7
+
8
+ import asyncio
9
+ from dataclasses import dataclass
10
+ from typing import Optional
11
+
12
+
13
+ @dataclass
14
+ class ShellConfig:
15
+ """Shell 执行配置"""
16
+ timeout_seconds: int = 120
17
+ max_output_bytes: int = 1024 * 1024
18
+ max_output_lines: int = 10000
19
+ project_root: Optional[str] = None
20
+
21
+
22
+ async def execute_with_timeout(
23
+ command: str,
24
+ cwd: Optional[str] = None,
25
+ timeout: int = 120,
26
+ env: Optional[dict] = None,
27
+ ) -> tuple[int, str, str]:
28
+ """执行 shell 命令(带超时)
29
+
30
+ Args:
31
+ command: 要执行的命令
32
+ cwd: 工作目录
33
+ timeout: 超时秒数
34
+ env: 环境变量
35
+
36
+ Returns:
37
+ (exit_code, stdout, stderr)
38
+ """
39
+ proc = await asyncio.create_subprocess_shell(
40
+ command,
41
+ stdout=asyncio.subprocess.PIPE,
42
+ stderr=asyncio.subprocess.PIPE,
43
+ cwd=cwd,
44
+ env=env,
45
+ )
46
+
47
+ try:
48
+ stdout, stderr = await asyncio.wait_for(
49
+ proc.communicate(),
50
+ timeout=timeout,
51
+ )
52
+ return proc.returncode or 0, stdout.decode(), stderr.decode()
53
+ except asyncio.TimeoutError:
54
+ proc.kill()
55
+ await proc.wait()
56
+ raise ShellTimeoutError(
57
+ f"Command timed out after {timeout}s: {command[:100]}"
58
+ )
59
+
60
+
61
+ class ShellTimeoutError(Exception):
62
+ """Shell 命令超时"""
63
+ pass