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,135 @@
1
+ # -*- coding: utf-8 -*-
2
+ """数据库节点基类
3
+
4
+ 定义所有数据库节点的统一接口
5
+ """
6
+ from abc import ABC, abstractmethod
7
+ from typing import Any, Optional
8
+ import pandas as pd
9
+
10
+ from QuantNodes.core.data_source import DataSource
11
+
12
+
13
+ class BaseDBNode(DataSource, ABC):
14
+ """数据库节点基类
15
+
16
+ 所有数据库节点必须实现以下接口:
17
+
18
+ Methods:
19
+ connect(): 建立连接
20
+ query(sql, params): 执行查询,返回 DataFrame
21
+ execute(sql, params): 执行 DDL/DML,返回影响行数
22
+ insert_df(df, table, if_exists): 插入 DataFrame
23
+ disconnect(): 关闭连接
24
+ health_check(): 健康检查
25
+
26
+ Note:
27
+ 继承 ``DataSource`` (Phase 3.3) 统一生命周期; ``close()`` 默认
28
+ 委托 ``disconnect()``, 子类无需改动。
29
+ """
30
+
31
+ _conn: Any = None
32
+
33
+ @abstractmethod
34
+ def connect(self) -> Any:
35
+ """建立数据库连接"""
36
+ raise NotImplementedError
37
+
38
+ @abstractmethod
39
+ def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
40
+ """执行 SQL 查询,返回 DataFrame
41
+
42
+ Args:
43
+ sql: SQL 查询语句
44
+ params: 查询参数(可选)
45
+
46
+ Returns:
47
+ pd.DataFrame 查询结果
48
+ """
49
+ raise NotImplementedError
50
+
51
+ @abstractmethod
52
+ def execute(self, sql: str, params: Optional[tuple] = None) -> int:
53
+ """执行 DDL/DML 语句
54
+
55
+ Args:
56
+ sql: SQL 语句
57
+ params: 语句参数(可选)
58
+
59
+ Returns:
60
+ int 影响行数
61
+ """
62
+ raise NotImplementedError
63
+
64
+ @abstractmethod
65
+ def insert_df(self, df: pd.DataFrame, table: str,
66
+ if_exists: str = 'append') -> int:
67
+ """插入 DataFrame 到数据库
68
+
69
+ Args:
70
+ df: 要插入的 DataFrame
71
+ table: 目标表名
72
+ if_exists: 表存在时的行为 ('append', 'replace', 'fail')
73
+
74
+ Returns:
75
+ int 插入行数
76
+ """
77
+ raise NotImplementedError
78
+
79
+ @abstractmethod
80
+ def disconnect(self) -> None:
81
+ """关闭数据库连接"""
82
+ raise NotImplementedError
83
+
84
+ def close(self) -> None:
85
+ """释放资源 (DataSource 协议)。默认委托 ``disconnect()``。"""
86
+ self.disconnect()
87
+
88
+ def health_check(self) -> bool:
89
+ """健康检查
90
+
91
+ Returns:
92
+ bool 连接是否正常
93
+ """
94
+ try:
95
+ self.query("SELECT 1")
96
+ return True
97
+ except Exception:
98
+ return False
99
+
100
+ def show_tables(self) -> list:
101
+ """列出所有表。默认通过 SHOW TABLES 实现。
102
+
103
+ Returns:
104
+ list[str] 表名列表。
105
+
106
+ Note:
107
+ ClickHouse 等需要在 schema 中限定表名的数据库可重写此方法
108
+ (例: ``SHOW TABLES FROM <database>``)。csv/parquet 等无 schema
109
+ 概念的 backend 也应重写, 抛 NotImplementedError 或返回自定义列表。
110
+ """
111
+ result = self.query("SHOW TABLES")
112
+ return result.iloc[:, 0].tolist()
113
+
114
+ def show_databases(self) -> list:
115
+ """列出所有数据库/Schema。默认通过 SHOW DATABASES 实现。
116
+
117
+ Returns:
118
+ list[str] 数据库名列表。
119
+
120
+ Note:
121
+ 单数据库 backend (sqlite, csv, parquet, 单 instance duckdb)
122
+ 可重写返回 ``[self._database]`` 或抛 NotImplementedError。
123
+ """
124
+ result = self.query("SHOW DATABASES")
125
+ return result.iloc[:, 0].tolist()
126
+
127
+ def __enter__(self):
128
+ """上下文管理器入口"""
129
+ self.connect()
130
+ return self
131
+
132
+ def __exit__(self, exc_type, exc_val, exc_tb):
133
+ """上下文管理器出口"""
134
+ self.disconnect()
135
+ return False
@@ -0,0 +1,272 @@
1
+ # -*- coding: utf-8 -*-
2
+ """ClickHouse 节点
3
+
4
+ 支持 HTTP 接口和官方 driver 双接口
5
+ """
6
+ import gzip
7
+ import http.client
8
+ import json
9
+ import urllib.parse
10
+ from collections import namedtuple
11
+ from typing import Optional
12
+
13
+ import pandas as pd
14
+
15
+ from QuantNodes.database_node.base import BaseDBNode
16
+
17
+
18
+ ch_conn_tuple = namedtuple('ch_conn_tuple', ['host', 'port', 'user', 'passwd', 'db'])
19
+
20
+
21
+ class CHBase:
22
+ """ClickHouse HTTP 基础实现"""
23
+
24
+ def __init__(self, name: str, user: str = 'default', passwd: str = '123456',
25
+ host: str = '0.0.0.0', port: int = 8123, db: str = 'default'):
26
+ self.name = name
27
+ self._para = ch_conn_tuple(host, port, user, passwd, db)
28
+ self.accepted_formats = [
29
+ 'DataFrame', 'TabSeparated', 'TabSeparatedRaw', 'TabSeparatedWithNames',
30
+ 'TabSeparatedWithNamesAndTypes', 'CSV', 'CSVWithNames', 'Values', 'Vertical', 'JSON',
31
+ 'JSONCompact', 'JSONEachRow', 'TSKV', 'Pretty', 'PrettyCompact',
32
+ 'PrettyCompactMonoBlock', 'PrettyNoEscapes', 'PrettySpace', 'XML'
33
+ ]
34
+ self.settings = self._merge_settings(None)
35
+ http_get_params = {'user': self._para.user, 'password': self._para.passwd}
36
+ http_get_params.update(self.settings)
37
+ self.http_get_params = http_get_params
38
+
39
+ def SHOWTABLES(self):
40
+ """显示所有表"""
41
+ res = self.get(f'SHOW TABLES FROM {self._para.db}').values
42
+ return res
43
+
44
+ def _create_conn(self):
45
+ """创建 HTTP 连接"""
46
+ url_str = (f"http://{self._para.user}:{self._para.passwd}@{self._para.host}:{self._para.port}")
47
+ components = urllib.parse.urlparse(url_str)
48
+ return http.client.HTTPConnection(components.hostname, port=components.port)
49
+
50
+ @staticmethod
51
+ def _check_sql_select_only(sql: str) -> None:
52
+ """检查 SQL 是否为查询语句"""
53
+ if sql.strip(' \n\t').lower()[:6] not in ['select', 'descri', 'show t', 'show d']:
54
+ first_word = sql.strip(' \n\t').split(' ')[0]
55
+ raise ValueError(
56
+ f'"query" should start with "select" or "describe" or "show", but got "{first_word}"'
57
+ )
58
+
59
+ @staticmethod
60
+ def _transfer_sql_format(sql: str, convert_to: str) -> str:
61
+ """转换 SQL 格式"""
62
+ clickhouse_format = 'JSON' if convert_to is None else 'JSONCompact' if convert_to.lower() == 'dataframe' else convert_to
63
+ query_with_format = (sql.rstrip('; \n\t') + ' format ' + clickhouse_format).replace('\n', ' ').strip(' ')
64
+ return query_with_format
65
+
66
+ def _compression_switched_request(self, query_with_format: str, conn, updated_settings, http_get_params):
67
+ """根据设置决定是否压缩请求"""
68
+ if updated_settings['enable_http_compression'] == 1:
69
+ conn.request('POST', '/?' + urllib.parse.urlencode(http_get_params),
70
+ body=gzip.compress(query_with_format.encode()),
71
+ headers={'Content-Encoding': 'gzip', 'Accept-Encoding': 'gzip'})
72
+ else:
73
+ conn.request('POST', '/?' + urllib.parse.urlencode(http_get_params), body=query_with_format.encode())
74
+ return conn
75
+
76
+ def _get_data(self, conn, updated_settings, auto_close=True):
77
+ """获取响应数据"""
78
+ resp = conn.getresponse()
79
+
80
+ if resp.status == 404:
81
+ error_message = (gzip.decompress(resp.read()).decode() if updated_settings['enable_http_compression'] == 1
82
+ else resp.read().decode())
83
+ if auto_close:
84
+ conn.close()
85
+ raise ValueError(error_message)
86
+ elif resp.status == 401:
87
+ if auto_close:
88
+ conn.close()
89
+ raise ConnectionRefusedError(resp.reason + '. The username or password is incorrect.')
90
+ else:
91
+ if resp.status != 200:
92
+ error_message = (gzip.decompress(resp.read()).decode() if updated_settings['enable_http_compression'] == 1
93
+ else resp.read().decode())
94
+ if auto_close:
95
+ conn.close()
96
+ raise NotImplementedError(f'Unknown Error: status: {resp.status}, reason: {resp.reason}, message: {error_message}')
97
+
98
+ total = bytes()
99
+ while not resp.isclosed():
100
+ total += resp.read(300 * 1024)
101
+ if auto_close:
102
+ conn.close()
103
+ return gzip.decompress(total).decode() if updated_settings['enable_http_compression'] == 1 else total.decode()
104
+
105
+ @staticmethod
106
+ def _load_into_pd(ret_value: str, convert_to: str) -> pd.DataFrame:
107
+ """将返回值转换为 DataFrame"""
108
+ if convert_to.lower() == 'dataframe':
109
+ result_dict = json.loads(ret_value, strict=False)
110
+ dataframe = pd.DataFrame.from_records(
111
+ result_dict['data'],
112
+ columns=[i['name'] for i in result_dict['meta']]
113
+ )
114
+ for i in result_dict['meta']:
115
+ if i['type'] in ['DateTime', 'Nullable(DateTime)']:
116
+ dataframe[i['name']] = pd.to_datetime(dataframe[i['name']])
117
+ return dataframe
118
+ return ret_value
119
+
120
+ def get(self, sql: str, convert_to: str = 'DataFrame', auto_close: bool = True):
121
+ """执行查询并返回结果"""
122
+ conn = self._create_conn()
123
+ self._check_sql_select_only(sql)
124
+ updated_settings = self.settings
125
+ query_with_format = self._transfer_sql_format(sql, convert_to)
126
+ conn = self._compression_switched_request(query_with_format, conn, updated_settings, self.http_get_params)
127
+ ret_value = self._get_data(conn, updated_settings, auto_close=auto_close)
128
+ return self._load_into_pd(ret_value, convert_to)
129
+
130
+ @staticmethod
131
+ def _merge_settings(settings):
132
+ """合并设置"""
133
+ updated_settings = {
134
+ 'enable_http_compression': 1,
135
+ 'send_progress_in_http_headers': 0,
136
+ 'log_queries': 1,
137
+ 'connect_timeout': 10,
138
+ 'receive_timeout': 300,
139
+ 'send_timeout': 300,
140
+ 'output_format_json_quote_64bit_integers': 0,
141
+ 'wait_end_of_query': 0
142
+ }
143
+ if settings is not None:
144
+ invalid_keys = list(set(settings.keys()) - set(updated_settings.keys()))
145
+ if invalid_keys:
146
+ raise ValueError(f'setting "{invalid_keys[0]}" is invalid')
147
+ updated_settings.update(settings)
148
+
149
+ for i in updated_settings:
150
+ updated_settings[i] = 1 if updated_settings[i] is True else 0 if updated_settings[i] is False else updated_settings[i]
151
+ return updated_settings
152
+
153
+ def get_describe_table(self, db: str, table: str) -> pd.DataFrame:
154
+ """获取表结构"""
155
+ return self.get(f'DESCRIBE TABLE {db}.{table}', auto_close=True)
156
+
157
+ def query(self, sql: str) -> pd.DataFrame:
158
+ """执行查询"""
159
+ return self.get(sql, convert_to='DataFrame')
160
+
161
+
162
+ class ClickHouseNode(BaseDBNode):
163
+ """ClickHouse 数据库节点
164
+
165
+ 支持 HTTP 接口和官方 driver 双接口
166
+
167
+ Args:
168
+ host: 主机地址
169
+ port: 端口 (HTTP 默认 8123,Native 默认 9000)
170
+ user: 用户名 (默认 default)
171
+ passwd: 密码
172
+ database: 数据库名 (默认 default)
173
+ interface: 接口类型 ('http' 或 'native',默认 'http')
174
+ pool_size: 连接池大小 (默认 10,可配置)
175
+
176
+ Example:
177
+ >>> # HTTP 接口
178
+ >>> node = ClickHouseNode(
179
+ ... host="localhost",
180
+ ... user="default",
181
+ ... passwd="",
182
+ ... database="default"
183
+ ... )
184
+
185
+ >>> # Native 接口
186
+ >>> node = ClickHouseNode(
187
+ ... host="localhost",
188
+ ... port=9000,
189
+ ... interface="native"
190
+ ... )
191
+ """
192
+
193
+ def __init__(self, host: str, port: int = 8123,
194
+ user: str = 'default', passwd: str = '',
195
+ database: str = 'default',
196
+ interface: str = 'http',
197
+ pool_size: int = 10):
198
+ self._host = host
199
+ self._port = port
200
+ self._user = user
201
+ self._passwd = passwd
202
+ self._database = database
203
+ self._interface = interface
204
+ self._pool_size = pool_size
205
+ self._client = None
206
+ self._http_client = None
207
+
208
+ def connect(self):
209
+ """建立连接"""
210
+ if self._interface == 'native':
211
+ import clickhouse_connect
212
+ self._client = clickhouse_connect.get_client(
213
+ host=self._host,
214
+ port=self._port,
215
+ username=self._user,
216
+ password=self._passwd,
217
+ database=self._database,
218
+ )
219
+ else:
220
+ self._http_client = CHBase(
221
+ name=self._database,
222
+ user=self._user,
223
+ passwd=self._passwd,
224
+ host=self._host,
225
+ port=self._port,
226
+ db=self._database,
227
+ )
228
+ return self._client or self._http_client
229
+
230
+ def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
231
+ """执行查询"""
232
+ client = self._client or self._http_client or self.connect()
233
+ if self._interface == 'native':
234
+ result = client.query(sql)
235
+ if hasattr(result, 'result_rows'):
236
+ return pd.DataFrame(result.result_rows, columns=result.column_names)
237
+ return result
238
+ else:
239
+ return client.get(sql, convert_to='DataFrame')
240
+
241
+ def execute(self, sql: str, params: Optional[tuple] = None) -> int:
242
+ """执行 DDL/DML"""
243
+ client = self._client or self._http_client or self.connect()
244
+ if self._interface == 'native':
245
+ client.command(sql)
246
+ return 0
247
+ else:
248
+ client.query(sql)
249
+ return 0
250
+
251
+ def insert_df(self, df: pd.DataFrame, table: str,
252
+ if_exists: str = 'append') -> int:
253
+ """插入 DataFrame"""
254
+ client = self._client or self._http_client or self.connect()
255
+ if self._interface == 'native':
256
+ client.insert_df(table, df)
257
+ else:
258
+ client.insert(df, self._database, table)
259
+ return len(df)
260
+
261
+ def disconnect(self) -> None:
262
+ """关闭连接"""
263
+ if self._client:
264
+ if self._interface == 'native':
265
+ self._client.close()
266
+ self._client = None
267
+ self._http_client = None
268
+
269
+ def show_tables(self) -> list:
270
+ """ClickHouse 需要按数据库限定表名 (SHOW TABLES FROM <db>)。"""
271
+ result = self.query(f"SHOW TABLES FROM {self._database}")
272
+ return result.iloc[:, 0].tolist()
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ """CSV 读取节点
3
+
4
+ 支持 WHERE 子句过滤
5
+ """
6
+ import os
7
+ from typing import Optional
8
+
9
+ import pandas as pd
10
+
11
+ from QuantNodes.database_node.base import BaseDBNode
12
+
13
+
14
+ class CSVNode(BaseDBNode):
15
+ """CSV 文件读取节点
16
+
17
+ 支持 WHERE 子句过滤
18
+
19
+ Args:
20
+ filepath: CSV 文件绝对路径
21
+ encoding: 字符编码 (默认 utf-8)
22
+ sep: 分隔符 (默认 ,)
23
+
24
+ Example:
25
+ >>> node = CSVNode("/data/users.csv")
26
+ >>> # 全量读取
27
+ >>> df = node.query()
28
+ >>> # 带 WHERE 过滤
29
+ >>> df = node.query("SELECT * WHERE age > 18")
30
+ """
31
+
32
+ def __init__(self, filepath: str, encoding: str = 'utf-8', sep: str = ','):
33
+ self._filepath = filepath
34
+ self._encoding = encoding
35
+ self._sep = sep
36
+ self._data: Optional[pd.DataFrame] = None
37
+
38
+ def connect(self) -> pd.DataFrame:
39
+ """读取 CSV 到内存"""
40
+ self._data = pd.read_csv(
41
+ self._filepath,
42
+ encoding=self._encoding,
43
+ sep=self._sep
44
+ )
45
+ return self._data
46
+
47
+ def query(self, sql: Optional[str] = None, params: Optional[tuple] = None) -> pd.DataFrame:
48
+ """执行查询
49
+
50
+ Args:
51
+ sql: SQL 查询语句(可选),支持 WHERE 子句
52
+ params: 查询参数(暂未使用)
53
+
54
+ Returns:
55
+ pd.DataFrame 查询结果
56
+ """
57
+ data = self._data or self.connect()
58
+
59
+ if sql is None:
60
+ return data
61
+
62
+ if 'WHERE' in sql.upper():
63
+ where_clause = sql.split('WHERE', 1)[1].strip()
64
+ return data.query(where_clause)
65
+
66
+ return data
67
+
68
+ def execute(self, sql: str, params: Optional[tuple] = None) -> int:
69
+ """CSV 节点不支持 execute"""
70
+ raise NotImplementedError("CSVNode 不支持 execute 操作")
71
+
72
+ def insert_df(self, df: pd.DataFrame, table: str,
73
+ if_exists: str = 'append') -> int:
74
+ """CSV 节点不支持 insert"""
75
+ raise NotImplementedError("CSVNode 不支持 insert 操作")
76
+
77
+ def disconnect(self) -> None:
78
+ """释放内存"""
79
+ self._data = None
80
+
81
+ def health_check(self) -> bool:
82
+ """健康检查"""
83
+ return os.path.exists(self._filepath)
@@ -0,0 +1,86 @@
1
+ # -*- coding: utf-8 -*-
2
+ """DuckDB 节点
3
+
4
+ 支持内存模式和文件模式,支持只读模式
5
+ """
6
+ from typing import Optional
7
+
8
+ import pandas as pd
9
+
10
+ from QuantNodes.database_node.base import BaseDBNode
11
+
12
+
13
+ class DuckDBNode(BaseDBNode):
14
+ """DuckDB 数据库节点
15
+
16
+ 支持内存模式和文件模式,支持只读模式
17
+
18
+ Args:
19
+ database: 数据库路径,`:memory:` 表示内存模式,
20
+ 绝对路径表示文件模式
21
+ read_only: 是否只读模式(仅文件模式有效,默认 False)
22
+
23
+ Example:
24
+ >>> # 内存模式
25
+ >>> node = DuckDBNode(":memory:")
26
+
27
+ >>> # 文件模式
28
+ >>> node = DuckDBNode("/data/analysis.duckdb")
29
+
30
+ >>> # 只读模式
31
+ >>> node = DuckDBNode("/data/analysis.duckdb", read_only=True)
32
+ """
33
+
34
+ def __init__(self, database: str = ":memory:", read_only: bool = False):
35
+ self._database = database
36
+ self._read_only = read_only
37
+ self._conn = None
38
+
39
+ def connect(self):
40
+ """建立 DuckDB 连接"""
41
+ import duckdb
42
+ self._conn = duckdb.connect(self._database, read_only=self._read_only)
43
+ return self._conn
44
+
45
+ def query(self, sql: str, params: Optional[tuple] = None) -> pd.DataFrame:
46
+ """执行查询"""
47
+ conn = self._conn or self.connect()
48
+ if params:
49
+ return conn.execute(sql, params).fetchdf()
50
+ return conn.execute(sql).fetchdf()
51
+
52
+ def execute(self, sql: str, params: Optional[tuple] = None) -> int:
53
+ """执行 DDL/DML"""
54
+ conn = self._conn or self.connect()
55
+ if params:
56
+ result = conn.execute(sql, params)
57
+ else:
58
+ result = conn.execute(sql)
59
+ try:
60
+ return result.rowcount
61
+ except Exception:
62
+ return 0
63
+
64
+ def insert_df(self, df: pd.DataFrame, table: str,
65
+ if_exists: str = 'append') -> int:
66
+ """插入 DataFrame"""
67
+ conn = self._conn or self.connect()
68
+ conn.register('temp_df', df)
69
+ if if_exists == 'replace':
70
+ conn.execute(f"DROP TABLE IF EXISTS {table}")
71
+ conn.execute(f"CREATE TABLE {table} AS SELECT * FROM temp_df")
72
+ elif if_exists == 'append':
73
+ try:
74
+ conn.execute(f"INSERT INTO {table} SELECT * FROM temp_df")
75
+ except Exception:
76
+ conn.execute(f"CREATE TABLE {table} AS SELECT * FROM temp_df")
77
+ else:
78
+ conn.execute(f"INSERT INTO {table} SELECT * FROM temp_df")
79
+ conn.unregister('temp_df')
80
+ return len(df)
81
+
82
+ def disconnect(self) -> None:
83
+ """关闭连接"""
84
+ if self._conn:
85
+ self._conn.close()
86
+ self._conn = None
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+ """database_node 工厂 (Phase 3.3)
3
+
4
+ 将"按 source 字符串选择数据库后端"的逻辑收敛到单一注册表驱动工厂,
5
+ 取代散落在调用方 (如 ``agent/tools/config_backtest.py``) 的 if/elif 阶梯。
6
+
7
+ Usage:
8
+ from QuantNodes.database_node import create_db_node
9
+
10
+ node = create_db_node("sqlite", database="/data/x.db")
11
+ node = create_db_node("clickhouse", host="localhost", database="default")
12
+
13
+ # 扩展新后端
14
+ from QuantNodes.database_node import register_db_node
15
+ register_db_node("myback", lambda **p: MyBackendNode(**p))
16
+
17
+ 注意: 工厂只负责 "参数 -> 实例"; 连接参数的来源 (conn.ini / path 解析)
18
+ 仍由调用方负责。
19
+ """
20
+ from typing import Callable, Dict
21
+
22
+ from QuantNodes.database_node.base import BaseDBNode
23
+ from QuantNodes.database_node.sqlite_node import SQLiteNode
24
+ from QuantNodes.database_node.duckdb_node import DuckDBNode
25
+ from QuantNodes.database_node.mysql_node import MySQLNode
26
+ from QuantNodes.database_node.clickhouse_node import ClickHouseNode
27
+ from QuantNodes.database_node.csv_node import CSVNode
28
+ from QuantNodes.database_node.parquet_node import ParquetNode
29
+
30
+ DBNodeBuilder = Callable[..., BaseDBNode]
31
+
32
+ _DB_NODE_BUILDERS: Dict[str, DBNodeBuilder] = {
33
+ "sqlite": lambda **p: SQLiteNode(**p),
34
+ "duckdb": lambda **p: DuckDBNode(**p),
35
+ "mysql": lambda **p: MySQLNode(**p),
36
+ "clickhouse": lambda **p: ClickHouseNode(**p),
37
+ "csv": lambda **p: CSVNode(**p),
38
+ "parquet": lambda **p: ParquetNode(**p),
39
+ }
40
+
41
+
42
+ def create_db_node(source: str, **params) -> BaseDBNode:
43
+ """按 source 字符串创建对应的数据库节点实例。
44
+
45
+ Args:
46
+ source: 后端类型, 见 ``available_sources()``。
47
+ **params: 透传给对应 Node 构造函数的关键字参数。
48
+
49
+ Returns:
50
+ BaseDBNode 子类实例。
51
+
52
+ Raises:
53
+ ValueError: source 未注册。
54
+ """
55
+ builder = _DB_NODE_BUILDERS.get(source)
56
+ if builder is None:
57
+ raise ValueError(
58
+ f"Unsupported data source: {source}. "
59
+ f"Available: {sorted(_DB_NODE_BUILDERS)}"
60
+ )
61
+ return builder(**params)
62
+
63
+
64
+ def register_db_node(source: str, builder: DBNodeBuilder) -> None:
65
+ """注册新的后端 builder (供扩展)。
66
+
67
+ Args:
68
+ source: 后端类型字符串。
69
+ builder: 接收 **params 返回 BaseDBNode 实例的可调用对象。
70
+
71
+ Raises:
72
+ ValueError: source 为空或已存在。
73
+ """
74
+ if not source:
75
+ raise ValueError("source must be a non-empty string")
76
+ if source in _DB_NODE_BUILDERS:
77
+ raise ValueError(f"source '{source}' already registered")
78
+ _DB_NODE_BUILDERS[source] = builder
79
+
80
+
81
+ def available_sources() -> list:
82
+ """返回已注册的后端类型列表 (排序)。"""
83
+ return sorted(_DB_NODE_BUILDERS)