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,226 @@
1
+ # coding=utf-8
2
+ """
3
+ 回测运行工具
4
+
5
+ 封装 QuantNodes 回测引擎,执行真实回测。
6
+ """
7
+
8
+ from typing import Any, Dict
9
+ import re
10
+
11
+ from QuantNodes.agent.tools.base import Tool
12
+
13
+
14
+ class BacktestTool(Tool):
15
+ """回测运行工具
16
+
17
+ 通过 CodeSandbox 安全执行策略代码,提取节点,
18
+ 然后运行 Strategy→Risk→Broker 回测流程。
19
+
20
+ pipeline_code 示例:
21
+ import pandas as pd
22
+ from QuantNodes.backtest.strategy_node import MAStrategyNode
23
+ from QuantNodes.backtest.broker_node import SimulatedBrokerNode
24
+
25
+ strategy = MAStrategyNode(config={'short_window': 5, 'long_window': 20})
26
+ broker = SimulatedBrokerNode(config={'cash': 100000, 'commission': 0.001})
27
+ quote_data = pd.read_csv('data.csv')
28
+ """
29
+
30
+ CODE_BLOCK_PATTERN = re.compile(r'```(?:python)?\s*(.*?)```', re.DOTALL)
31
+
32
+ def __init__(self):
33
+ pass
34
+
35
+ @property
36
+ def name(self) -> str:
37
+ return "backtest"
38
+
39
+ @property
40
+ def description(self) -> str:
41
+ return (
42
+ "运行策略回测,返回回测结果(交易次数、最终资金、手续费等)。"
43
+ "pipeline_code 中需创建 strategy、broker 变量和 quote_data DataFrame。"
44
+ )
45
+
46
+ @property
47
+ def parameters(self) -> Dict[str, Any]:
48
+ return {
49
+ "type": "object",
50
+ "properties": {
51
+ "pipeline_code": {
52
+ "type": "string",
53
+ "description": (
54
+ "策略Pipeline代码,需创建 strategy、broker 变量和 quote_data DataFrame"
55
+ ),
56
+ },
57
+ "start_date": {
58
+ "type": "string",
59
+ "description": "回测开始日期,格式YYYY-MM-DD"
60
+ },
61
+ "end_date": {
62
+ "type": "string",
63
+ "description": "回测结束日期,格式YYYY-MM-DD"
64
+ },
65
+ "initial_cash": {
66
+ "type": "number",
67
+ "description": "初始资金",
68
+ "default": 100000
69
+ },
70
+ "commission": {
71
+ "type": "number",
72
+ "description": "手续费率",
73
+ "default": 0.001
74
+ }
75
+ },
76
+ "required": ["pipeline_code"]
77
+ }
78
+
79
+ @property
80
+ def read_only(self) -> bool:
81
+ return False
82
+
83
+ @property
84
+ def concurrency_safe(self) -> bool:
85
+ return False
86
+
87
+ async def execute(
88
+ self,
89
+ pipeline_code: str,
90
+ start_date: str = None,
91
+ end_date: str = None,
92
+ initial_cash: float = 100000,
93
+ commission: float = 0.001,
94
+ **kwargs
95
+ ) -> Dict[str, Any]:
96
+ result = {
97
+ "status": "success",
98
+ "summary": {},
99
+ "config": {
100
+ "start_date": start_date,
101
+ "end_date": end_date,
102
+ "initial_cash": initial_cash,
103
+ "commission": commission,
104
+ }
105
+ }
106
+
107
+ try:
108
+ from QuantNodes.ai.sandbox import CodeSandbox
109
+ from QuantNodes.backtest.strategy_node import StrategyNode
110
+ from QuantNodes.backtest.broker_node import SimulatedBrokerNode
111
+ from QuantNodes.backtest.risk_node import RiskNode
112
+
113
+ sandbox = CodeSandbox()
114
+ extracted_code = self._extract_code(pipeline_code)
115
+
116
+ if not extracted_code:
117
+ result["status"] = "error"
118
+ result["errors"] = ["No valid code found in pipeline_code"]
119
+ return result
120
+
121
+ validation = sandbox.validate(extracted_code)
122
+ if not validation.is_safe:
123
+ result["status"] = "error"
124
+ result["errors"] = validation.errors
125
+ result["security_status"] = "unsafe"
126
+ return result
127
+
128
+ result["security_status"] = "safe"
129
+
130
+ import pandas as pd
131
+ import numpy as np
132
+ context = {
133
+ "pd": pd,
134
+ "np": np,
135
+ "QuantNodes": __import__("QuantNodes"),
136
+ }
137
+
138
+ namespace = sandbox.validate_and_execute(extracted_code, context)
139
+
140
+ strategy = None
141
+ broker = None
142
+ risk_nodes = []
143
+ quote_data = None
144
+
145
+ for name, obj in namespace.items():
146
+ if isinstance(obj, StrategyNode) and not isinstance(obj, RiskNode):
147
+ strategy = obj
148
+ elif isinstance(obj, SimulatedBrokerNode):
149
+ broker = obj
150
+ elif isinstance(obj, RiskNode):
151
+ risk_nodes.append(obj)
152
+
153
+ quote_data = namespace.get("quote_data") or namespace.get("data")
154
+
155
+ if strategy is None:
156
+ result["status"] = "error"
157
+ result["errors"] = [
158
+ "No StrategyNode found. Create a strategy variable in your code, e.g.:\n"
159
+ "from QuantNodes.backtest.strategy_node import MAStrategyNode\n"
160
+ "strategy = MAStrategyNode(config={'short_window': 5, 'long_window': 20})"
161
+ ]
162
+ return result
163
+
164
+ if quote_data is None:
165
+ result["status"] = "error"
166
+ result["errors"] = [
167
+ "No quote_data found. Create a 'quote_data' DataFrame in your code, e.g.:\n"
168
+ "quote_data = pd.read_csv('data.csv')"
169
+ ]
170
+ return result
171
+
172
+ if broker is None:
173
+ from QuantNodes.backtest.broker_node import SimulatedBrokerNode
174
+ broker = SimulatedBrokerNode(config={
175
+ "cash": initial_cash,
176
+ "commission": commission,
177
+ })
178
+
179
+ if start_date and end_date and "date" in quote_data.columns:
180
+ quote_data = quote_data[
181
+ (quote_data["date"] >= start_date) &
182
+ (quote_data["date"] <= end_date)
183
+ ]
184
+
185
+ orders_result = strategy.execute(quote_data)
186
+
187
+ filtered_orders = orders_result
188
+ for risk in risk_nodes:
189
+ risk_result = risk.execute((filtered_orders, {}))
190
+ from QuantNodes.backtest.strategy_node import OrdersResult
191
+ filtered = OrdersResult()
192
+ filtered.orders = risk_result.passed_orders
193
+ filtered.signals = orders_result.signals
194
+ filtered_orders = filtered
195
+
196
+ trade_result = broker.execute((filtered_orders, quote_data))
197
+
198
+ result["summary"] = {
199
+ "total_trades": len(trade_result.trades) if hasattr(trade_result, "trades") else 0,
200
+ "final_cash": trade_result.cash if hasattr(trade_result, "cash") else initial_cash,
201
+ "total_commission": (
202
+ trade_result.commission if hasattr(trade_result, "commission") else 0
203
+ ),
204
+ "strategy": strategy.__class__.__name__,
205
+ "broker": broker.__class__.__name__,
206
+ "risk_nodes": [r.__class__.__name__ for r in risk_nodes],
207
+ "data_rows": len(quote_data),
208
+ }
209
+
210
+ result["nodes"] = {
211
+ "strategy": strategy.__class__.__name__,
212
+ "broker": broker.__class__.__name__,
213
+ "risk_nodes": [r.__class__.__name__ for r in risk_nodes],
214
+ }
215
+
216
+ except Exception as e:
217
+ result["status"] = "error"
218
+ result["errors"] = [str(e)]
219
+
220
+ return result
221
+
222
+ def _extract_code(self, code: str) -> str:
223
+ match = self.CODE_BLOCK_PATTERN.search(code)
224
+ if match:
225
+ return match.group(1).strip()
226
+ return code.strip()
@@ -0,0 +1,133 @@
1
+ # coding=utf-8
2
+ """Tool base class — inherits from HKUDS nanobot 0.2.1 ``Tool``.
3
+
4
+ v3.0.0 迁移到上游后,quant 工具的父类由本地 ``Tool`` 改为
5
+ ``nanobot.agent.tools.base.Tool``。本文件保留薄包装以兼容 14 个 quant
6
+ 工具的 ``from .base import Tool`` 导入路径。
7
+
8
+ **v3.0.0 Stage 5.3** 起,``nanobot-ai`` 为可选依赖。本文件在未装 extras
9
+ 时提供本地降级父类 ``Tool``,让 quant 工具可在没有 nanobot 的环境下
10
+ 独立使用(CLI、API、Wiki 等场景不需要 agent)。当用户调用 ``Agent(...)``
11
+ 或注册工具到 nanobot registry 时才会触发真正的 nanobot import。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from abc import ABC
17
+ from dataclasses import dataclass
18
+ from typing import Any, Dict
19
+
20
+ # Note: do NOT ``from QuantNodes.agent import NANOBOT_AVAILABLE`` here — this
21
+ # file is imported as part of ``QuantNodes.agent.__init__`` (via
22
+ # ``nanobot_bridge.py -> tools/__init__.py -> base.py``) and a back-import
23
+ # would create a circular dependency with partially-initialized module state.
24
+ # Instead we probe nanobot here, independently.
25
+ try:
26
+ from nanobot.agent.tools.base import Tool as _NanobotTool
27
+
28
+ _NANOBOT_AVAILABLE = True
29
+ except ImportError: # pragma: no cover - exercised in nanobot-less envs
30
+ _NanobotTool = ABC # type: ignore[assignment,misc]
31
+ _NANOBOT_AVAILABLE = False
32
+
33
+
34
+ @dataclass
35
+ class ToolExecutionResult:
36
+ """工具执行结果(向后兼容)。"""
37
+
38
+ tool_name: str
39
+ success: bool
40
+ content: Any
41
+ error: str | None = None
42
+
43
+
44
+ if _NANOBOT_AVAILABLE: # pragma: no cover - exercised in [agent] installs
45
+ class Tool(_NanobotTool, ABC): # type: ignore[misc]
46
+ """所有 quant 工具的薄包装父类 — 继承自 nanobot 的 ``Tool``。
47
+
48
+ 仅补充:
49
+ - ``to_openai_schema`` 别名(与 ``to_schema`` 同语义,保留旧名)
50
+ - ``_dispatch`` 辅助方法(量化工具内部 action 分发用)
51
+ """
52
+
53
+ @property
54
+ def to_openai_schema(self) -> Any:
55
+ """向后兼容别名 — 调用上游 ``to_schema``。"""
56
+ return self.to_schema
57
+
58
+ async def _dispatch(self, action: str, registry: Dict[str, Any], **kwargs: Any) -> Any:
59
+ """Look up ``action`` in ``registry`` and call it with kwargs.
60
+
61
+ Replaces 4-times-repeated::
62
+
63
+ fn = dispatch.get(action)
64
+ if not fn: raise ValueError(...)
65
+ return await fn(**kwargs)
66
+
67
+ Subclasses call ``return await self._dispatch(action, {...})`` from execute().
68
+ """
69
+ fn = registry.get(action)
70
+ if not fn:
71
+ raise ValueError(f"Unknown action: {action}")
72
+ return await fn(**kwargs)
73
+ else:
74
+
75
+ class Tool(ABC): # type: ignore[no-redef]
76
+ """Stand-alone Tool ABC for quant-only deployments (no nanobot).
77
+
78
+ When ``nanobot-ai`` is not installed, quant tools still need a
79
+ concrete parent class so that ``class MyTool(Tool)`` works. This
80
+ is the lightest possible stand-in: just enough to allow
81
+ ``from .base import Tool`` and the ``_dispatch`` helper.
82
+
83
+ ``to_openai_schema`` is implemented locally by synthesising the
84
+ standard OpenAI function-calling schema from ``name`` /
85
+ ``description`` / ``parameters``. This keeps monitor/MCP tools
86
+ introspectable in quant-only environments (e.g. dashboard,
87
+ docs) where the upstream nanobot ``Tool.to_schema`` is not
88
+ available. Tool registration with nanobot's ``ToolRegistry`` and
89
+ dispatch through ``AgentLoop`` still require the [agent] extra.
90
+ """
91
+
92
+ name: str = ""
93
+ description: str = ""
94
+ parameters: Dict[str, Any] = {}
95
+
96
+ def to_openai_schema(self) -> Dict[str, Any]:
97
+ """OpenAI function-calling schema synthesised from attrs.
98
+
99
+ Mirrors the call signature of nanobot's ``Tool.to_schema``
100
+ (a method, not a property) so callers can do
101
+ ``tool.to_openai_schema()`` uniformly across both code paths.
102
+
103
+ Returns:
104
+ ``{"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}``
105
+ """
106
+ return {
107
+ "type": "function",
108
+ "function": {
109
+ "name": self.name,
110
+ "description": self.description,
111
+ "parameters": self.parameters,
112
+ },
113
+ }
114
+
115
+ async def execute(self, **kwargs: Any) -> Any: # pragma: no cover
116
+ raise RuntimeError(
117
+ "Tool.execute() requires nanobot-ai. "
118
+ "Install: pip install 'quantnodes[agent]'"
119
+ )
120
+
121
+ async def _dispatch(self, action: str, registry: Dict[str, Any], **kwargs: Any) -> Any:
122
+ """Look up ``action`` in ``registry`` and call it with kwargs.
123
+
124
+ Available even without nanobot, so quant tools' dispatcher
125
+ patterns work in pure-quant deployments.
126
+ """
127
+ fn = registry.get(action)
128
+ if not fn:
129
+ raise ValueError(f"Unknown action: {action}")
130
+ return await fn(**kwargs)
131
+
132
+
133
+ __all__ = ["Tool", "ToolExecutionResult"]
@@ -0,0 +1,207 @@
1
+ # coding=utf-8
2
+ """
3
+ 代码搜索工具
4
+
5
+ 提供 grep 内容搜索、文件名查找、带上下文的代码搜索。
6
+ 路径限制在 workspace 内。
7
+ """
8
+
9
+ import os
10
+ import re
11
+ from pathlib import Path
12
+ from typing import Any, Dict
13
+
14
+ from .base import Tool
15
+ from ._workspace import WorkspaceTool
16
+
17
+
18
+ class CodeSearchTool(WorkspaceTool, Tool):
19
+ """代码搜索工具
20
+
21
+ 在工作目录中搜索代码内容和文件名。
22
+ """
23
+
24
+ MAX_RESULTS = 50
25
+ MAX_CONTEXT_LINES = 3
26
+
27
+ def __init__(self, workspace: str | Path):
28
+ super().__init__(workspace)
29
+
30
+ @property
31
+ def name(self) -> str:
32
+ return "code_search"
33
+
34
+ @property
35
+ def description(self) -> str:
36
+ return "代码搜索工具:grep 内容搜索、按模式查找文件、带上下文的代码搜索"
37
+
38
+ @property
39
+ def parameters(self) -> Dict[str, Any]:
40
+ return {
41
+ "type": "object",
42
+ "properties": {
43
+ "action": {
44
+ "type": "string",
45
+ "enum": ["grep", "find_files", "search_code"],
46
+ "description": "搜索类型",
47
+ },
48
+ "pattern": {
49
+ "type": "string",
50
+ "description": "搜索模式(正则表达式或 glob)",
51
+ },
52
+ "path": {
53
+ "type": "string",
54
+ "description": "搜索目录(相对于工作目录),默认为整个工作目录",
55
+ },
56
+ "include": {
57
+ "type": "string",
58
+ "description": "文件名过滤模式,如 *.py 或 *.{ts,js}",
59
+ },
60
+ "context_lines": {
61
+ "type": "integer",
62
+ "description": "上下文行数(search_code 时使用)",
63
+ "default": 3,
64
+ },
65
+ },
66
+ "required": ["action", "pattern"],
67
+ }
68
+
69
+ @property
70
+ def read_only(self) -> bool:
71
+ return True
72
+
73
+ def _safe_path(self, rel_path: str) -> Path:
74
+ """Resolve workspace-relative path (Phase J1: now inherited)."""
75
+ return WorkspaceTool._safe_path(self, rel_path)
76
+
77
+ def _match_include(self, filename: str, include: str | None) -> bool:
78
+ """检查文件名是否匹配 include 模式"""
79
+ if not include:
80
+ return True
81
+ # 支持 *.py 或 *.{ts,js} 格式
82
+ if "," in include:
83
+ patterns = [p.strip().lstrip("*") for p in include.split(",")]
84
+ return any(filename.endswith(p) for p in patterns)
85
+ ext = include.lstrip("*")
86
+ return filename.endswith(ext)
87
+
88
+ async def execute(self, action: str, **kwargs: Any) -> Any:
89
+ return await self._dispatch(action, {
90
+ "grep": self._grep,
91
+ "find_files": self._find_files,
92
+ "search_code": self._search_code,
93
+ }, **kwargs)
94
+
95
+ async def _grep(
96
+ self, pattern: str = "", path: str = "", include: str = "", **kw
97
+ ) -> Dict[str, Any]:
98
+ search_dir = self._safe_path(path)
99
+ if not search_dir.exists():
100
+ return {"error": f"Directory not found: {path}"}
101
+
102
+ try:
103
+ regex = re.compile(pattern, re.IGNORECASE)
104
+ except re.error as e:
105
+ return {"error": f"Invalid regex: {e}"}
106
+
107
+ results = []
108
+ for root, dirs, files in os.walk(search_dir):
109
+ # 跳过隐藏目录和 __pycache__
110
+ dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
111
+
112
+ for fname in files:
113
+ if not self._match_include(fname, include):
114
+ continue
115
+ fpath = Path(root) / fname
116
+ if fpath.stat().st_size > 512 * 1024: # 跳过 > 512KB
117
+ continue
118
+
119
+ try:
120
+ lines = fpath.read_text(encoding="utf-8", errors="replace").splitlines()
121
+ for i, line in enumerate(lines, 1):
122
+ if regex.search(line):
123
+ results.append({
124
+ "file": str(fpath.relative_to(self.workspace)),
125
+ "line": i,
126
+ "content": line.rstrip()[:200],
127
+ })
128
+ if len(results) >= self.MAX_RESULTS:
129
+ return {
130
+ "results": results,
131
+ "truncated": True,
132
+ "total": len(results),
133
+ }
134
+ except (PermissionError, OSError):
135
+ continue
136
+
137
+ return {"results": results, "truncated": False, "total": len(results)}
138
+
139
+ async def _find_files(self, pattern: str = "", path: str = "", **kw) -> Dict[str, Any]:
140
+ search_dir = self._safe_path(path)
141
+ if not search_dir.exists():
142
+ return {"error": f"Directory not found: {path}"}
143
+
144
+ matches = sorted(search_dir.glob(pattern))
145
+ results = []
146
+ for m in matches:
147
+ if m.is_file():
148
+ results.append({
149
+ "path": str(m.relative_to(self.workspace)),
150
+ "size": m.stat().st_size,
151
+ })
152
+ if len(results) >= self.MAX_RESULTS:
153
+ break
154
+
155
+ return {"matches": results, "total": len(results)}
156
+
157
+ async def _search_code(
158
+ self, query: str = "", path: str = "", include: str = "",
159
+ context_lines: int = 3, **kw,
160
+ ) -> Dict[str, Any]:
161
+ search_dir = self._safe_path(path)
162
+ if not search_dir.exists():
163
+ return {"error": f"Directory not found: {path}"}
164
+
165
+ try:
166
+ regex = re.compile(query, re.IGNORECASE)
167
+ except re.error as e:
168
+ return {"error": f"Invalid regex: {e}"}
169
+
170
+ results = []
171
+ for root, dirs, files in os.walk(search_dir):
172
+ dirs[:] = [d for d in dirs if not d.startswith(".") and d != "__pycache__"]
173
+
174
+ for fname in files:
175
+ if not self._match_include(fname, include):
176
+ continue
177
+ fpath = Path(root) / fname
178
+ if fpath.stat().st_size > 512 * 1024:
179
+ continue
180
+
181
+ try:
182
+ lines = fpath.read_text(encoding="utf-8", errors="replace").splitlines()
183
+ for i, line in enumerate(lines):
184
+ if regex.search(line):
185
+ start = max(0, i - context_lines)
186
+ end = min(len(lines), i + context_lines + 1)
187
+ context = []
188
+ for j in range(start, end):
189
+ prefix = ">>>" if j == i else " "
190
+ context.append(f"{prefix} {j + 1}: {lines[j].rstrip()[:150]}")
191
+
192
+ results.append({
193
+ "file": str(fpath.relative_to(self.workspace)),
194
+ "line": i + 1,
195
+ "match": line.rstrip()[:200],
196
+ "context": "\n".join(context),
197
+ })
198
+ if len(results) >= self.MAX_RESULTS:
199
+ return {
200
+ "results": results,
201
+ "truncated": True,
202
+ "total": len(results),
203
+ }
204
+ except (PermissionError, OSError):
205
+ continue
206
+
207
+ return {"results": results, "truncated": False, "total": len(results)}