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,231 @@
1
+ # coding=utf-8
2
+ """
3
+ 因子分析工具
4
+
5
+ 提供 IC 分析、相关性分析等功能。
6
+ """
7
+
8
+ from typing import Any, Dict
9
+
10
+ from QuantNodes.agent.tools.base import Tool
11
+
12
+
13
+ class FactorTool(Tool):
14
+ """因子分析工具
15
+
16
+ 对因子进行 IC 分析、相关性分析等。
17
+
18
+ 通过 CodeSandbox 安全执行因子代码,获取因子值,
19
+ 然后计算 IC、ICIR 等统计指标。
20
+
21
+ factor_code 示例:
22
+ import polars as pl
23
+ result = pl.DataFrame({
24
+ "date": ["2024-01-01", "2024-01-01", "2024-01-02", "2024-01-02"],
25
+ "code": ["A", "B", "A", "B"],
26
+ "factor_value": [0.1, 0.2, 0.3, 0.4],
27
+ "forward_return": [0.05, 0.03, 0.02, 0.01],
28
+ })
29
+ """
30
+
31
+ def __init__(self):
32
+ pass
33
+
34
+ @property
35
+ def name(self) -> str:
36
+ return "factor"
37
+
38
+ @property
39
+ def description(self) -> str:
40
+ return (
41
+ "对因子进行IC分析、相关性分析等。"
42
+ "factor_code 中需将结果赋给 result 变量,"
43
+ "result 应包含 date, code, factor_value, forward_return 列。"
44
+ )
45
+
46
+ @property
47
+ def parameters(self) -> Dict[str, Any]:
48
+ return {
49
+ "type": "object",
50
+ "properties": {
51
+ "factor_code": {
52
+ "type": "string",
53
+ "description": (
54
+ "因子的Python代码,结果应赋给 'result' 变量,"
55
+ "包含 date/code/factor_value/forward_return 列"
56
+ ),
57
+ },
58
+ "analysis_type": {
59
+ "type": "string",
60
+ "description": "分析类型:ic(IC分析)、correlation(相关性分析)、both",
61
+ "enum": ["ic", "correlation", "both"],
62
+ "default": "both"
63
+ },
64
+ "start_date": {
65
+ "type": "string",
66
+ "description": "分析开始日期"
67
+ },
68
+ "end_date": {
69
+ "type": "string",
70
+ "description": "分析结束日期"
71
+ }
72
+ },
73
+ "required": ["factor_code", "analysis_type"]
74
+ }
75
+
76
+ @property
77
+ def read_only(self) -> bool:
78
+ return True
79
+
80
+ async def execute(
81
+ self,
82
+ factor_code: str,
83
+ analysis_type: str = "both",
84
+ start_date: str = None,
85
+ end_date: str = None,
86
+ **kwargs
87
+ ) -> Dict[str, Any]:
88
+ result = {
89
+ "status": "success",
90
+ "analysis": {
91
+ "ic": {},
92
+ "correlation": {}
93
+ }
94
+ }
95
+
96
+ try:
97
+ from QuantNodes.ai.sandbox import CodeSandbox
98
+ import polars as pl
99
+
100
+ sandbox = CodeSandbox()
101
+ validation = sandbox.validate(factor_code)
102
+
103
+ if not validation.is_safe:
104
+ result["status"] = "error"
105
+ result["errors"] = validation.errors
106
+ result["security_status"] = "unsafe"
107
+ return result
108
+
109
+ result["security_status"] = "safe"
110
+
111
+ namespace = sandbox.validate_and_execute(factor_code)
112
+ factor_result = namespace.get("result")
113
+
114
+ if factor_result is None:
115
+ result["status"] = "error"
116
+ result["errors"] = [
117
+ "No 'result' variable found. Factor code must assign to 'result', e.g.:\n"
118
+ "result = pl.DataFrame({...})"
119
+ ]
120
+ return result
121
+
122
+ if not isinstance(factor_result, pl.DataFrame):
123
+ result["status"] = "error"
124
+ result["errors"] = [
125
+ f"Expected Polars DataFrame, got {type(factor_result).__name__}. "
126
+ "Factor code should create a Polars DataFrame."
127
+ ]
128
+ return result
129
+
130
+ required_cols = {"factor_value", "forward_return"}
131
+ missing = required_cols - set(factor_result.columns)
132
+ if missing:
133
+ result["status"] = "error"
134
+ result["errors"] = [
135
+ f"Missing required columns: {missing}. "
136
+ f"Available columns: {list(factor_result.columns)}. "
137
+ "Result must have 'factor_value' and 'forward_return' columns."
138
+ ]
139
+ return result
140
+
141
+ if "date" in factor_result.columns and start_date and end_date:
142
+ factor_result = factor_result.filter(
143
+ (pl.col("date") >= start_date) &
144
+ (pl.col("date") <= end_date)
145
+ )
146
+
147
+ if analysis_type in ("ic", "both"):
148
+ result["analysis"]["ic"] = self._compute_ic(factor_result)
149
+
150
+ if analysis_type in ("correlation", "both"):
151
+ result["analysis"]["correlation"] = self._compute_correlation(factor_result)
152
+
153
+ result["data_rows"] = len(factor_result)
154
+ result["columns"] = list(factor_result.columns)
155
+
156
+ except Exception as e:
157
+ result["status"] = "error"
158
+ result["errors"] = [str(e)]
159
+
160
+ return result
161
+
162
+ def _compute_ic(self, df) -> Dict[str, Any]:
163
+ """计算 IC 分析结果"""
164
+ import polars as pl
165
+
166
+ if "date" not in df.columns:
167
+ factor = df["factor_value"]
168
+ fwd = df["forward_return"]
169
+ ic_val = factor.to_frame().select(pl.corr(factor, fwd)).to_series()[0]
170
+ rank_ic_val = factor.rank().to_frame().select(
171
+ pl.corr(factor.rank(), fwd.rank())
172
+ ).to_series()[0]
173
+ return {
174
+ "ic_mean": ic_val,
175
+ "ic_std": 0.0,
176
+ "icir": ic_val,
177
+ "rank_ic_mean": rank_ic_val,
178
+ "note": "Single cross-section IC (no date column for time-series analysis)",
179
+ }
180
+
181
+ ic_series = df.group_by("date").agg([
182
+ pl.corr("factor_value", "forward_return").alias("ic"),
183
+ ]).sort("date")
184
+
185
+ ic_values = ic_series["ic"].drop_nulls()
186
+ ic_mean = ic_values.mean()
187
+ ic_std = ic_values.std()
188
+ icir = ic_mean / (ic_std + 1e-8) if ic_std else 0.0
189
+
190
+ rank_ic_series = df.group_by("date").agg([
191
+ pl.corr(
192
+ pl.col("factor_value").rank(),
193
+ pl.col("forward_return").rank()
194
+ ).alias("rank_ic"),
195
+ ]).sort("date")
196
+
197
+ rank_ic_values = rank_ic_series["rank_ic"].drop_nulls()
198
+ rank_ic_mean = rank_ic_values.mean()
199
+
200
+ return {
201
+ "ic_mean": round(ic_mean, 6) if ic_mean is not None else None,
202
+ "ic_std": round(ic_std, 6) if ic_std is not None else None,
203
+ "icir": round(icir, 6) if icir is not None else None,
204
+ "rank_ic_mean": round(rank_ic_mean, 6) if rank_ic_mean is not None else None,
205
+ "ic_series": ic_series.to_dicts(),
206
+ "n_dates": len(ic_series),
207
+ }
208
+
209
+ def _compute_correlation(self, df) -> Dict[str, Any]:
210
+ """计算相关性分析"""
211
+ import polars as pl
212
+
213
+ result = {}
214
+
215
+ if "factor_value" in df.columns and "forward_return" in df.columns:
216
+ factor = df["factor_value"]
217
+ fwd = df["forward_return"]
218
+ corr = factor.to_frame().select(pl.corr(factor, fwd)).to_series()[0]
219
+ result["factor_return_corr"] = round(corr, 6) if corr is not None else None
220
+
221
+ numeric_cols = [c for c in df.columns if df[c].dtype in (
222
+ pl.Float32, pl.Float64, pl.Int32, pl.Int64
223
+ )]
224
+ if len(numeric_cols) >= 2:
225
+ corr_matrix = df.select(numeric_cols).corr()
226
+ result["correlation_matrix"] = {
227
+ "columns": numeric_cols,
228
+ "values": corr_matrix.to_dicts(),
229
+ }
230
+
231
+ return result
@@ -0,0 +1,201 @@
1
+ # coding=utf-8
2
+ """
3
+ 文件操作工具
4
+
5
+ 提供安全的文件读/写/编辑/列表/glob 操作。
6
+ 路径限制在 workspace 内,防止 path traversal。
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Any, Dict
11
+
12
+ from .base import Tool
13
+ from ._workspace import WorkspaceTool
14
+ from QuantNodes.core.path_utils import ensure_parent
15
+
16
+
17
+ class FileOpsTool(WorkspaceTool, Tool):
18
+ """文件操作工具
19
+
20
+ 安全地读取、写入、编辑文件,列出目录内容,glob 模式匹配。
21
+ 所有路径限制在 workspace 内。
22
+ """
23
+
24
+ MAX_FILE_SIZE = 1 * 1024 * 1024 # 1MB
25
+
26
+ def __init__(self, workspace: str | Path):
27
+ super().__init__(workspace)
28
+
29
+ @property
30
+ def name(self) -> str:
31
+ return "file_ops"
32
+
33
+ @property
34
+ def description(self) -> str:
35
+ return (
36
+ "文件操作工具:读/写/编辑文件、列出目录、glob模式匹配。"
37
+ "所有路径限制在工作目录内。"
38
+ )
39
+
40
+ @property
41
+ def parameters(self) -> Dict[str, Any]:
42
+ return {
43
+ "type": "object",
44
+ "properties": {
45
+ "action": {
46
+ "type": "string",
47
+ "enum": ["read_file", "write_file", "edit_file", "list_files", "glob_files"],
48
+ "description": "操作类型",
49
+ },
50
+ "path": {
51
+ "type": "string",
52
+ "description": "文件或目录路径(相对于工作目录)",
53
+ },
54
+ "content": {
55
+ "type": "string",
56
+ "description": "要写入的内容(write_file 时必需)",
57
+ },
58
+ "old_string": {
59
+ "type": "string",
60
+ "description": "要替换的旧字符串(edit_file 时必需)",
61
+ },
62
+ "new_string": {
63
+ "type": "string",
64
+ "description": "替换后的新字符串(edit_file 时必需)",
65
+ },
66
+ "pattern": {
67
+ "type": "string",
68
+ "description": "glob 模式(glob_files 时必需),如 **/*.py",
69
+ },
70
+ "offset": {
71
+ "type": "integer",
72
+ "description": "读取起始行号(从1开始)",
73
+ "default": 1,
74
+ },
75
+ "limit": {
76
+ "type": "integer",
77
+ "description": "读取最大行数",
78
+ "default": 2000,
79
+ },
80
+ },
81
+ "required": ["action"],
82
+ }
83
+
84
+ @property
85
+ def read_only(self) -> bool:
86
+ return False
87
+
88
+ def _safe_path(self, rel_path: str) -> Path:
89
+ """Resolve workspace-relative path (Phase J1: now inherited)."""
90
+ return WorkspaceTool._safe_path(self, rel_path)
91
+
92
+ async def execute(self, action: str, **kwargs: Any) -> Any:
93
+ return await self._dispatch(action, {
94
+ "read_file": self._read_file,
95
+ "write_file": self._write_file,
96
+ "edit_file": self._edit_file,
97
+ "list_files": self._list_files,
98
+ "glob_files": self._glob_files,
99
+ }, **kwargs)
100
+
101
+ async def _read_file(
102
+ self, path: str = "", offset: int = 1, limit: int = 2000, **kw,
103
+ ) -> Dict[str, Any]:
104
+ target = self._safe_path(path)
105
+ if not target.exists():
106
+ return {"error": f"File not found: {path}"}
107
+ if not target.is_file():
108
+ return {"error": f"Not a file: {path}"}
109
+ if target.stat().st_size > self.MAX_FILE_SIZE:
110
+ return {
111
+ "error": (
112
+ f"File too large ({target.stat().st_size} bytes). "
113
+ f"Max: {self.MAX_FILE_SIZE}"
114
+ )
115
+ }
116
+
117
+ lines = target.read_text(encoding="utf-8", errors="replace").splitlines(keepends=True)
118
+ total = len(lines)
119
+ sliced = lines[max(0, offset - 1) : offset - 1 + limit]
120
+ return {
121
+ "content": "".join(sliced),
122
+ "total_lines": total,
123
+ "offset": offset,
124
+ "lines_returned": len(sliced),
125
+ }
126
+
127
+ async def _write_file(
128
+ self, path: str = "", content: str = "", **kw,
129
+ ) -> Dict[str, Any]:
130
+ target = self._safe_path(path)
131
+ ensure_parent(target)
132
+ target.write_text(content, encoding="utf-8")
133
+ return {
134
+ "status": "ok",
135
+ "bytes_written": len(content.encode("utf-8")),
136
+ "path": str(target.relative_to(self.workspace)),
137
+ }
138
+
139
+ async def _edit_file(
140
+ self, path: str = "", old_string: str = "", new_string: str = "", **kw,
141
+ ) -> Dict[str, Any]:
142
+ if not old_string:
143
+ return {"error": "old_string is required"}
144
+ target = self._safe_path(path)
145
+ if not target.exists():
146
+ return {"error": f"File not found: {path}"}
147
+
148
+ original = target.read_text(encoding="utf-8")
149
+ count = original.count(old_string)
150
+ if count == 0:
151
+ return {"error": "old_string not found in file"}
152
+ if count > 1:
153
+ return {
154
+ "error": (
155
+ f"old_string found {count} times. "
156
+ "Provide more context to make it unique."
157
+ )
158
+ }
159
+
160
+ updated = original.replace(old_string, new_string, 1)
161
+ target.write_text(updated, encoding="utf-8")
162
+ return {
163
+ "status": "ok",
164
+ "replacements": 1,
165
+ "path": str(target.relative_to(self.workspace)),
166
+ }
167
+
168
+ async def _list_files(self, path: str = "", pattern: str = "", **kw) -> Dict[str, Any]:
169
+ target = self._safe_path(path or ".")
170
+ if not target.exists():
171
+ return {"error": f"Directory not found: {path}"}
172
+ if not target.is_dir():
173
+ return {"error": f"Not a directory: {path}"}
174
+
175
+ entries = []
176
+ for entry in sorted(target.iterdir()):
177
+ rel = str(entry.relative_to(self.workspace))
178
+ entries.append({
179
+ "name": entry.name,
180
+ "path": rel,
181
+ "type": "dir" if entry.is_dir() else "file",
182
+ "size": entry.stat().st_size if entry.is_file() else None,
183
+ })
184
+ return {"entries": entries[:200], "total": len(entries)}
185
+
186
+ async def _glob_files(self, pattern: str = "", path: str = "", **kw) -> Dict[str, Any]:
187
+ if not pattern:
188
+ return {"error": "pattern is required for glob_files"}
189
+ base = self._safe_path(path or ".")
190
+ if not base.exists():
191
+ return {"error": f"Directory not found: {path}"}
192
+
193
+ matches = sorted(base.glob(pattern))
194
+ results = []
195
+ for m in matches:
196
+ if m.is_file():
197
+ results.append({
198
+ "path": str(m.relative_to(self.workspace)),
199
+ "size": m.stat().st_size,
200
+ })
201
+ return {"matches": results[:100], "total": len(results)}
@@ -0,0 +1,190 @@
1
+ # coding=utf-8
2
+ """
3
+ Git 操作工具
4
+
5
+ 提供 git status/diff/log/commit 操作。
6
+ 限制在 workspace 目录内执行。
7
+ """
8
+
9
+ import asyncio
10
+ from pathlib import Path
11
+ from typing import Any, Dict, List
12
+
13
+ from .base import Tool
14
+ from ._workspace import WorkspaceTool
15
+
16
+
17
+ class GitOpsTool(WorkspaceTool, Tool):
18
+ """Git 操作工具
19
+
20
+ 安全地执行 git 命令:status、diff、log、commit。
21
+ """
22
+
23
+ def __init__(self, workspace: str | Path):
24
+ super().__init__(workspace)
25
+
26
+ @property
27
+ def name(self) -> str:
28
+ return "git_ops"
29
+
30
+ @property
31
+ def description(self) -> str:
32
+ return "Git 操作工具:查看状态、差异、提交历史、创建提交"
33
+
34
+ @property
35
+ def parameters(self) -> Dict[str, Any]:
36
+ return {
37
+ "type": "object",
38
+ "properties": {
39
+ "action": {
40
+ "type": "string",
41
+ "enum": ["git_status", "git_diff", "git_log", "git_commit"],
42
+ "description": "Git 操作类型",
43
+ },
44
+ "path": {
45
+ "type": "string",
46
+ "description": "文件路径(可选,用于 git_diff)",
47
+ },
48
+ "message": {
49
+ "type": "string",
50
+ "description": "提交消息(git_commit 时必需)",
51
+ },
52
+ "files": {
53
+ "type": "array",
54
+ "items": {"type": "string"},
55
+ "description": "要提交的文件列表(可选,默认提交所有变更)",
56
+ },
57
+ "n": {
58
+ "type": "integer",
59
+ "description": "显示最近 n 条提交(git_log 时使用)",
60
+ "default": 10,
61
+ },
62
+ },
63
+ "required": ["action"],
64
+ }
65
+
66
+ @property
67
+ def read_only(self) -> bool:
68
+ return False
69
+
70
+ @property
71
+ def concurrency_safe(self) -> bool:
72
+ return False
73
+
74
+ async def _run_git(self, *args: str) -> Dict[str, Any]:
75
+ """执行 git 命令"""
76
+ cmd = ["git", "-C", str(self.workspace)] + list(args)
77
+ try:
78
+ proc = await asyncio.create_subprocess_exec(
79
+ *cmd,
80
+ stdout=asyncio.subprocess.PIPE,
81
+ stderr=asyncio.subprocess.PIPE,
82
+ )
83
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
84
+ return {
85
+ "returncode": proc.returncode,
86
+ "stdout": stdout.decode("utf-8", errors="replace").strip(),
87
+ "stderr": stderr.decode("utf-8", errors="replace").strip(),
88
+ }
89
+ except asyncio.TimeoutError:
90
+ return {"error": "Git command timed out after 30s"}
91
+ except FileNotFoundError:
92
+ return {"error": "Git is not installed or not in PATH"}
93
+
94
+ async def execute(self, action: str, **kwargs: Any) -> Any:
95
+ return await self._dispatch(action, {
96
+ "git_status": self._git_status,
97
+ "git_diff": self._git_diff,
98
+ "git_log": self._git_log,
99
+ "git_commit": self._git_commit,
100
+ }, **kwargs)
101
+
102
+ async def _git_status(self, **kw) -> Dict[str, Any]:
103
+ result = await self._run_git("status", "--porcelain")
104
+ if result.get("error"):
105
+ return result
106
+ if result["returncode"] != 0:
107
+ return {"error": result["stderr"] or "git status failed"}
108
+
109
+ lines = result["stdout"].splitlines() if result["stdout"] else []
110
+ files = []
111
+ for line in lines:
112
+ if len(line) >= 3:
113
+ status = line[:2].strip()
114
+ filepath = line[3:]
115
+ files.append({"status": status, "path": filepath})
116
+
117
+ return {"clean": len(files) == 0, "files": files, "total": len(files)}
118
+
119
+ async def _git_diff(self, path: str = "", **kw) -> Dict[str, Any]:
120
+ args = ["diff"]
121
+ if path:
122
+ args.extend(["--", path])
123
+ result = await self._run_git(*args)
124
+ if result.get("error"):
125
+ return result
126
+ if result["returncode"] != 0:
127
+ return {"error": result["stderr"] or "git diff failed"}
128
+
129
+ return {"diff": result["stdout"], "has_changes": bool(result["stdout"])}
130
+
131
+ async def _git_log(self, n: int = 10, **kw) -> Dict[str, Any]:
132
+ result = await self._run_git(
133
+ "log", f"-{n}", "--pretty=format:%H|%an|%ae|%ad|%s", "--date=short"
134
+ )
135
+ if result.get("error"):
136
+ return result
137
+ if result["returncode"] != 0:
138
+ return {"error": result["stderr"] or "git log failed"}
139
+
140
+ commits = []
141
+ for line in result["stdout"].splitlines():
142
+ parts = line.split("|", 4)
143
+ if len(parts) == 5:
144
+ commits.append({
145
+ "hash": parts[0][:8],
146
+ "author": parts[1],
147
+ "email": parts[2],
148
+ "date": parts[3],
149
+ "message": parts[4],
150
+ })
151
+
152
+ return {"commits": commits, "total": len(commits)}
153
+
154
+ async def _git_commit(self, message: str = "", files: List[str] = None, **kw) -> Dict[str, Any]:
155
+ if not message:
156
+ return {"error": "message is required for git_commit"}
157
+
158
+ # Stage files
159
+ if files:
160
+ for f in files:
161
+ result = await self._run_git("add", "--", f)
162
+ if result.get("error"):
163
+ return result
164
+ else:
165
+ result = await self._run_git("add", "-A")
166
+ if result.get("error"):
167
+ return result
168
+
169
+ # Check if there's anything to commit
170
+ status_result = await self._run_git("status", "--porcelain")
171
+ if status_result.get("stdout") == "":
172
+ return {"error": "Nothing to commit (working tree clean)"}
173
+
174
+ # Commit
175
+ result = await self._run_git("commit", "-m", message)
176
+ if result.get("error"):
177
+ return result
178
+ if result["returncode"] != 0:
179
+ return {"error": result["stderr"] or "git commit failed"}
180
+
181
+ # Get commit hash
182
+ hash_result = await self._run_git("rev-parse", "--short", "HEAD")
183
+ commit_hash = hash_result.get("stdout", "unknown")
184
+
185
+ return {
186
+ "status": "ok",
187
+ "commit": commit_hash,
188
+ "message": message,
189
+ "files": files or "all",
190
+ }