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,958 @@
1
+ # coding=utf-8
2
+ """
3
+ 配置执行器
4
+
5
+ 执行策略配置,生成 Polars 表达式并计算。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import importlib.util
11
+ import warnings
12
+ from typing import Dict, Any, Optional, List, Tuple
13
+ import polars as pl
14
+
15
+ from .types import StrategyConfig, ExecutionResult
16
+ from QuantNodes.operators import ts, sec, math, composite
17
+
18
+
19
+ class ExprParser:
20
+ """递归下降表达式解析器
21
+
22
+ 支持:
23
+ - 简单列引用: "close"
24
+ - 数字字面量: "20", "3.14"
25
+ - 函数调用: "rolling_mean(close, 20)"
26
+ - 方法链: "close.rolling_mean(20)"
27
+ - 算术运算: "close / close.shift(20) - 1"
28
+ - 一元运算: "-rank(close_ma_diff)"
29
+ - 括号分组: "(close + volume) / 2"
30
+ """
31
+
32
+ def __init__(self, executor: 'ConfigExecutor'):
33
+ self.executor = executor
34
+ self._get_operator = None
35
+
36
+ def _lazy_import(self):
37
+ if self._get_operator is None:
38
+ from QuantNodes.operators.proxy import get_operator
39
+ self._get_operator = get_operator
40
+
41
+ def parse(self, expr_str: str) -> pl.Expr:
42
+ """解析表达式字符串为 Polars Expr"""
43
+ self._pos = 0
44
+ self._expr = expr_str.strip()
45
+ result = self._parse_additive()
46
+ return result
47
+
48
+ def _current(self) -> str:
49
+ self._skip_whitespace()
50
+ if self._pos >= len(self._expr):
51
+ return ''
52
+ return self._expr[self._pos]
53
+
54
+ def _skip_whitespace(self):
55
+ while self._pos < len(self._expr) and self._expr[self._pos] in ' \t':
56
+ self._pos += 1
57
+
58
+ def _consume(self, ch: str):
59
+ self._skip_whitespace()
60
+ if self._pos < len(self._expr) and self._expr[self._pos] == ch:
61
+ self._pos += 1
62
+ else:
63
+ raise ValueError(
64
+ f"Expected '{ch}' at position {self._pos}, "
65
+ f"got '{self._expr[self._pos] if self._pos < len(self._expr) else 'EOF'}'"
66
+ )
67
+
68
+ def _parse_additive(self) -> pl.Expr:
69
+ """加减法: term (('+' | '-') term)*"""
70
+ left = self._parse_multiplicative()
71
+
72
+ while self._current() in ('+', '-'):
73
+ op = self._current()
74
+ self._pos += 1
75
+ right = self._parse_multiplicative()
76
+
77
+ if op == '+':
78
+ left = left + right
79
+ else:
80
+ left = left - right
81
+
82
+ return left
83
+
84
+ def _parse_multiplicative(self) -> pl.Expr:
85
+ """乘除法: unary (('*' | '/') unary)*"""
86
+ left = self._parse_unary()
87
+
88
+ while self._current() in ('*', '/'):
89
+ op = self._current()
90
+ self._pos += 1
91
+ right = self._parse_unary()
92
+
93
+ if op == '*':
94
+ left = left * right
95
+ else:
96
+ left = left / right
97
+
98
+ return left
99
+
100
+ def _parse_unary(self) -> pl.Expr:
101
+ """一元运算: ('-' | '+') primary | primary"""
102
+ if self._current() == '-':
103
+ self._pos += 1
104
+ operand = self._parse_primary()
105
+ return pl.lit(0) - operand
106
+ elif self._current() == '+':
107
+ self._pos += 1
108
+ return self._parse_primary()
109
+ return self._parse_primary()
110
+
111
+ def _parse_primary(self) -> pl.Expr:
112
+ """主项: number | column | function_call | method_chain | '(' expr ')'"""
113
+ self._skip_whitespace()
114
+
115
+ # 括号表达式
116
+ if self._current() == '(':
117
+ self._consume('(')
118
+ expr = self._parse_additive()
119
+ self._consume(')')
120
+ return expr
121
+
122
+ # 数字字面量
123
+ cur = self._current()
124
+ is_digit = cur.isdigit()
125
+ is_dot_number = (
126
+ cur == '.'
127
+ and self._pos + 1 < len(self._expr)
128
+ and self._expr[self._pos + 1].isdigit()
129
+ )
130
+ if is_digit or is_dot_number:
131
+ return self._parse_number()
132
+
133
+ # 标识符 (列名/函数名/方法名)
134
+ if self._current().isalpha() or self._current() == '_':
135
+ return self._parse_identifier()
136
+
137
+ raise ValueError(f"Unexpected character '{self._current()}' at position {self._pos}")
138
+
139
+ def _parse_number(self) -> pl.Expr:
140
+ """解析数字字面量"""
141
+ start = self._pos
142
+ while self._pos < len(self._expr) and (
143
+ self._expr[self._pos].isdigit() or self._expr[self._pos] == '.'
144
+ ):
145
+ self._pos += 1
146
+
147
+ num_str = self._expr[start:self._pos]
148
+ if '.' in num_str:
149
+ return pl.lit(float(num_str))
150
+ return pl.lit(int(num_str))
151
+
152
+ def _parse_identifier(self) -> pl.Expr:
153
+ """解析标识符 (可能是列名、函数调用或方法链)"""
154
+ name = self._parse_name()
155
+
156
+ self._skip_whitespace()
157
+
158
+ # 函数调用: name(args)
159
+ if self._current() == '(':
160
+ return self._parse_func_call(name)
161
+
162
+ # 方法链: name.method(args) 或 name.method
163
+ if self._current() == '.':
164
+ return self._parse_method_chain(name)
165
+
166
+ # 简单列引用
167
+ return pl.col(name)
168
+
169
+ def _parse_name(self) -> str:
170
+ """解析标识符名称"""
171
+ start = self._pos
172
+ while self._pos < len(self._expr) and (
173
+ self._expr[self._pos].isalnum() or self._expr[self._pos] == '_'
174
+ ):
175
+ self._pos += 1
176
+
177
+ if self._pos == start:
178
+ raise ValueError(f"Expected identifier at position {self._pos}")
179
+
180
+ return self._expr[start:self._pos]
181
+
182
+ def _parse_func_call(self, func_name: str) -> pl.Expr:
183
+ """解析函数调用: func_name(arg1, arg2, ...)"""
184
+ self._consume('(')
185
+
186
+ # 读取原始参数字符串,使用 executor 的 _parse_func_args 解析
187
+ # 这样数字参数会返回 Python int/float,列名会返回 pl.col()
188
+ args_str = self._read_func_args()
189
+ args, kwargs = self.executor._parse_func_args(args_str)
190
+
191
+ # 查找算子函数
192
+ self._lazy_import()
193
+ op_func = self._get_operator(func_name) if self._get_operator else None
194
+
195
+ if op_func is not None and args:
196
+ # 将第一个参数转换为表达式(如果还不是的话)
197
+ first_arg = args[0] if isinstance(args[0], pl.Expr) else pl.col(str(args[0]))
198
+ rest_args = args[1:]
199
+ return op_func(first_arg, *rest_args, **kwargs)
200
+
201
+ # 如果没有找到算子,尝试作为 Polars 方法
202
+ if args:
203
+ first_arg = args[0] if isinstance(args[0], pl.Expr) else pl.col(str(args[0]))
204
+ rest_args = args[1:]
205
+ return getattr(first_arg, func_name)(*rest_args, **kwargs)
206
+
207
+ return pl.col(func_name)
208
+
209
+ def _read_func_args(self) -> str:
210
+ """读取函数参数字符串(从当前位置到匹配的右括号,消耗 ')')
211
+
212
+ 注意: 此方法会消耗右括号 ')',调用后不需要再 consume ')'
213
+ """
214
+ start = self._pos
215
+ depth = 1
216
+ while self._pos < len(self._expr) and depth > 0:
217
+ if self._expr[self._pos] == '(':
218
+ depth += 1
219
+ elif self._expr[self._pos] == ')':
220
+ depth -= 1
221
+ self._pos += 1
222
+ # 返回括号内的内容(不含两端括号)
223
+ return self._expr[start:self._pos - 1]
224
+
225
+ def _parse_method_chain(self, obj_name: str) -> pl.Expr:
226
+ """解析方法链: obj.method(args) 或 obj.method"""
227
+ self._consume('.')
228
+ method_name = self._parse_name()
229
+
230
+ self._skip_whitespace()
231
+
232
+ if self._current() == '(':
233
+ # 方法调用: obj.method(args)
234
+ # 注意: _read_func_args 已经消耗了 ')'
235
+ args_str = self._read_func_args()
236
+
237
+ self._lazy_import()
238
+ op_func = self._get_operator(method_name) if self._get_operator else None
239
+
240
+ first_arg = pl.col(obj_name)
241
+ args, kwargs = self.executor._parse_func_args(args_str)
242
+
243
+ if op_func is not None:
244
+ return op_func(first_arg, *args, **kwargs)
245
+
246
+ # 回退到 Polars 原生方法
247
+ return getattr(first_arg, method_name)(*args, **kwargs)
248
+
249
+ # 属性访问: obj.method (不支持,回退到列引用)
250
+ return pl.col(f"{obj_name}.{method_name}")
251
+
252
+
253
+ class ConfigExecutor:
254
+ """配置执行器"""
255
+
256
+ # executor category → registry category 映射
257
+ _CATEGORY_MAP = {
258
+ "time_series": "time",
259
+ "section": "section",
260
+ "math": "point",
261
+ "composite": "point",
262
+ "talib": "talib",
263
+ }
264
+
265
+ def __init__(self):
266
+ self._expressions: Dict[str, pl.Expr] = {}
267
+ self._cache: Dict[str, Any] = {}
268
+
269
+ def _load_custom_operators(self, custom_operators: List) -> None:
270
+ """加载自定义算子
271
+
272
+ 支持两种配置格式:
273
+ - str: 文件路径,自动发现 custom_* 函数,注册到 point 分类
274
+ - dict: {source, category, functions} 显式指定分类和函数列表
275
+
276
+ Args:
277
+ custom_operators: ValidationConfig.custom_operators 列表
278
+ """
279
+ if not custom_operators:
280
+ return
281
+
282
+ from QuantNodes.operators.proxy import register_operator
283
+
284
+ for entry in custom_operators:
285
+ # 解析配置
286
+ if isinstance(entry, str):
287
+ source_path = entry
288
+ category = "point"
289
+ functions = None
290
+ else:
291
+ source_path = entry.get("source", "")
292
+ category = entry.get("category", "point")
293
+ functions = entry.get("functions")
294
+
295
+ if not source_path:
296
+ continue
297
+
298
+ # importlib 动态加载
299
+ try:
300
+ spec = importlib.util.spec_from_file_location("custom_ops", source_path)
301
+ if spec is None or spec.loader is None:
302
+ warnings.warn(f"无法加载自定义算子文件: {source_path}")
303
+ continue
304
+ module = importlib.util.module_from_spec(spec)
305
+ spec.loader.exec_module(module)
306
+ except Exception as e:
307
+ warnings.warn(f"加载自定义算子文件失败 {source_path}: {e}")
308
+ continue
309
+
310
+ # 注册算子
311
+ registered = 0
312
+ for name in dir(module):
313
+ if name.startswith("_"):
314
+ continue
315
+ if functions and name not in functions:
316
+ continue
317
+ if not functions and not name.startswith("custom_"):
318
+ continue
319
+
320
+ func = getattr(module, name)
321
+ if not callable(func):
322
+ continue
323
+
324
+ register_operator(self._CATEGORY_MAP.get(category, "point"), name=name)(func)
325
+ registered += 1
326
+
327
+ if registered > 0:
328
+ print(
329
+ f"[ConfigExecutor] 已注册 {registered} 个自定义算子 "
330
+ f"(来源: {source_path}, 分类: {category})"
331
+ )
332
+
333
+ def run(
334
+ self,
335
+ config: StrategyConfig,
336
+ data: pl.LazyFrame
337
+ ) -> ExecutionResult:
338
+ """执行配置
339
+
340
+ Args:
341
+ config: 策略配置
342
+ data: 数据 LazyFrame
343
+
344
+ Returns:
345
+ ExecutionResult 对象
346
+ """
347
+ result = ExecutionResult(status="success")
348
+
349
+ try:
350
+ # 0. 加载自定义算子
351
+ self._load_custom_operators(config.validation.custom_operators)
352
+
353
+ # 1. 生成因子表达式
354
+ for factor in config.factors:
355
+ expr = self._parse_expr(factor.expr)
356
+ self._expressions[factor.name] = expr
357
+ result.factors[factor.name] = expr
358
+
359
+ # 2. 执行运算
360
+ for op in config.operations:
361
+ expr = self._apply_operator(op)
362
+
363
+ # 处理 List[Expr] 返回值(如 rank_sort)
364
+ if isinstance(expr, list):
365
+ for i, e in enumerate(expr):
366
+ key = f"{op.name}__{i}" # 使用双下划线避免与用户命名冲突
367
+ self._expressions[key] = e
368
+ result.factors[op.name] = expr
369
+ else:
370
+ self._expressions[op.name] = expr
371
+ result.factors[op.name] = expr
372
+
373
+ # 3. 计算组合因子
374
+ for comp in config.composite:
375
+ expr = self._parse_expr(comp.formula)
376
+ self._expressions[comp.name] = expr
377
+ result.factors[comp.name] = expr
378
+
379
+ # 4. 生成计算计划
380
+ self._execute_plan(data, result)
381
+
382
+ except Exception as e:
383
+ result.status = "error"
384
+ result.errors.append(str(e))
385
+
386
+ return result
387
+
388
+ def run_backtest(
389
+ self,
390
+ config: StrategyConfig,
391
+ data: pl.LazyFrame
392
+ ) -> ExecutionResult:
393
+ """执行回测
394
+
395
+ Args:
396
+ config: 策略配置
397
+ data: 数据 LazyFrame
398
+
399
+ Returns:
400
+ ExecutionResult 对象
401
+ """
402
+ result = self.run(config, data)
403
+
404
+ if config.backtest is None:
405
+ return result
406
+
407
+ bt = config.backtest
408
+ code_col = config.data.code_column if config.data else "code"
409
+ date_col = config.data.date_column if config.data else "date"
410
+
411
+ try:
412
+ # 使用 result.data (已包含计算列) 进行筛选
413
+ working_data = result.data if result.data is not None else data
414
+
415
+ # 筛选 Universe
416
+ universe_codes = self._resolve_universe(bt.universe)
417
+ if universe_codes is not None:
418
+ schema = working_data.collect_schema()
419
+ # 使用 code_column(内部标准名),兼容 PascalCase fallback
420
+ effective_code_col = code_col
421
+ if effective_code_col not in schema:
422
+ pascal = code_col[0].upper() + code_col[1:] if code_col else ""
423
+ if pascal in schema:
424
+ effective_code_col = pascal
425
+ if effective_code_col in schema:
426
+ working_data = working_data.filter(
427
+ pl.col(effective_code_col).is_in(universe_codes)
428
+ )
429
+
430
+ # 筛选日期(兼容 String 和 Date 类型)
431
+ if bt.start_date:
432
+ start_parts = list(map(int, bt.start_date.split("-")))
433
+ start_date = pl.date(start_parts[0], start_parts[1], start_parts[2])
434
+ date_series = pl.col(date_col)
435
+ schema = working_data.collect_schema()
436
+ date_dtype = schema.get(date_col)
437
+ # 如果是 String 类型,先转换为 Date
438
+ if date_dtype == pl.Utf8 or date_dtype == pl.String:
439
+ date_series = date_series.str.to_date()
440
+ working_data = working_data.filter(date_series >= start_date)
441
+ if bt.end_date:
442
+ end_parts = list(map(int, bt.end_date.split("-")))
443
+ end_date = pl.date(end_parts[0], end_parts[1], end_parts[2])
444
+ date_series = pl.col(date_col)
445
+ schema = working_data.collect_schema()
446
+ date_dtype = schema.get(date_col)
447
+ if date_dtype == pl.Utf8 or date_dtype == pl.String:
448
+ date_series = date_series.str.to_date()
449
+ working_data = working_data.filter(date_series <= end_date)
450
+
451
+ # 计算信号 (取最后一个因子作为信号)
452
+ signal_name = None
453
+ if config.composite:
454
+ signal_name = config.composite[-1].name
455
+ elif config.operations:
456
+ signal_name = config.operations[-1].name
457
+ elif config.factors:
458
+ signal_name = config.factors[-1].name
459
+
460
+ if signal_name is not None:
461
+ expr = self._expressions.get(signal_name)
462
+ if expr is not None:
463
+ # 兼容两种阈值命名
464
+ buy_threshold = bt.signals.get("buy_threshold",
465
+ bt.signals.get("long_threshold", 0.05))
466
+ sell_threshold = bt.signals.get("sell_threshold",
467
+ bt.signals.get("short_threshold", -0.03))
468
+
469
+ # 生成交易信号
470
+ working_data = working_data.with_columns([
471
+ pl.when(expr > buy_threshold).then(1)
472
+ .when(expr < sell_threshold).then(-1)
473
+ .otherwise(0).alias("signal")
474
+ ])
475
+
476
+ result.backtest = {
477
+ "signals": working_data.select(date_col, code_col, "signal"),
478
+ "config": {
479
+ "start_date": bt.start_date,
480
+ "end_date": bt.end_date,
481
+ "initial_cash": bt.initial_cash,
482
+ "commission": bt.commission,
483
+ "slippage": bt.slippage,
484
+ },
485
+ "buy_threshold": buy_threshold,
486
+ "sell_threshold": sell_threshold,
487
+ }
488
+ result.data = working_data
489
+ except Exception as e:
490
+ result.warnings.append(f"回测配置解析警告: {str(e)}")
491
+
492
+ return result
493
+
494
+ def _parse_expr(self, expr_str: str) -> pl.Expr:
495
+ """解析表达式字符串
496
+
497
+ 使用递归下降解析器,支持:
498
+ - 简单列引用: "close"
499
+ - 函数调用: "rolling_mean(close, 20)"
500
+ - 方法链: "close.rolling_mean(20)"
501
+ - 算术运算: "close / close.shift(20) - 1"
502
+ - 一元运算: "-rank(close_ma_diff)"
503
+ - 括号分组: "(close + volume) / 2"
504
+ """
505
+ parser = ExprParser(self)
506
+ return parser.parse(expr_str)
507
+
508
+ def _parse_func_args(self, args_str: str) -> Tuple[List, Dict]:
509
+ """解析函数参数字符串
510
+
511
+ Returns:
512
+ (positional_args, keyword_args)
513
+ """
514
+ import re
515
+
516
+ positional = []
517
+ keyword = {}
518
+
519
+ if not args_str.strip():
520
+ return positional, keyword
521
+
522
+ # 处理嵌套括号的分割
523
+ parts = []
524
+ depth = 0
525
+ current = ""
526
+ for ch in args_str:
527
+ if ch == '(':
528
+ depth += 1
529
+ current += ch
530
+ elif ch == ')':
531
+ depth -= 1
532
+ current += ch
533
+ elif ch == ',' and depth == 0:
534
+ parts.append(current.strip())
535
+ current = ""
536
+ else:
537
+ current += ch
538
+ if current.strip():
539
+ parts.append(current.strip())
540
+
541
+ for part in parts:
542
+ # 检查是否是 keyword=value 格式
543
+ kw_match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', part)
544
+ if kw_match:
545
+ key = kw_match.group(1)
546
+ value = self._parse_value(kw_match.group(2).strip())
547
+ keyword[key] = value
548
+ else:
549
+ positional.append(self._parse_value(part))
550
+
551
+ return positional, keyword
552
+
553
+ def _parse_value(self, value_str: str):
554
+ """解析单个值
555
+
556
+ 注意: 对于数字,返回 Python 原生类型(int/float),
557
+ 而不是 Polars 表达式。这是为了正确传递参数给算子函数。
558
+ """
559
+ value_str = value_str.strip()
560
+
561
+ # 去除引号
562
+ if (value_str.startswith('"') and value_str.endswith('"')) or \
563
+ (value_str.startswith("'") and value_str.endswith("'")):
564
+ return value_str[1:-1]
565
+
566
+ # 尝试解析为整数
567
+ try:
568
+ return int(value_str)
569
+ except ValueError:
570
+ pass
571
+
572
+ # 尝试解析为浮点数
573
+ try:
574
+ return float(value_str)
575
+ except ValueError:
576
+ pass
577
+
578
+ # 纯数字字符串(可能是字符串格式的数字)
579
+ if value_str.isdigit():
580
+ return int(value_str)
581
+
582
+ # 尝试作为列引用
583
+ return pl.col(value_str)
584
+
585
+ def _apply_operator(self, op) -> pl.Expr:
586
+ """应用算子
587
+
588
+ 优先级: 硬编码 dispatch > registry fallback > 最终兜底
589
+ """
590
+ op_type = op.type
591
+ category = op.category
592
+ inputs = op.inputs
593
+ params = op.params
594
+
595
+ # 获取输入表达式
596
+ input_exprs = []
597
+ for name in inputs:
598
+ if name in self._expressions:
599
+ input_exprs.append(self._expressions[name])
600
+ else:
601
+ input_exprs.append(pl.col(name))
602
+
603
+ if not input_exprs:
604
+ return pl.col(inputs[0]) if inputs else pl.lit(0)
605
+
606
+ # 1. 硬编码 dispatch(优先)
607
+ result = None
608
+ if op_type == "time_series":
609
+ result = self._apply_ts_operator(category, input_exprs, params)
610
+ elif op_type == "section":
611
+ result = self._apply_sec_operator(category, input_exprs, params)
612
+ elif op_type == "math":
613
+ result = self._apply_math_operator(category, input_exprs[0], params)
614
+ elif op_type == "composite":
615
+ result = self._apply_composite_operator(category, input_exprs, params)
616
+ elif op_type == "talib":
617
+ result = self._apply_talib_operator(category, input_exprs, params)
618
+
619
+ if result is not None:
620
+ return result
621
+
622
+ # 2. registry fallback: 从 operators.proxy 查找
623
+ try:
624
+ from QuantNodes.operators.proxy import get_operator
625
+ op_func = get_operator(category)
626
+ if op_func is not None:
627
+ return op_func(*input_exprs, **params)
628
+ except Exception:
629
+ pass
630
+
631
+ # 3. 最终兜底
632
+ return input_exprs[0]
633
+
634
+ def _get_op(self, name: str):
635
+ """懒加载获取 operators.proxy 算子"""
636
+ from QuantNodes.operators.proxy import get_operator
637
+ return get_operator(name)
638
+
639
+ def _apply_ts_operator(
640
+ self,
641
+ category: str,
642
+ input_exprs: List[pl.Expr],
643
+ params: Dict[str, Any]
644
+ ) -> pl.Expr:
645
+ """应用时间序列算子
646
+
647
+ Args:
648
+ category: 算子名称
649
+ input_exprs: 输入表达式列表(单输入算子取 [0],双输入算子取 [0],[1])
650
+ params: 参数字典
651
+ """
652
+ expr = input_exprs[0]
653
+ window = params.get("window", 20)
654
+ alpha = params.get("alpha", 0.5)
655
+ periods = params.get("periods", 1)
656
+
657
+ # 单输入滚动算子
658
+ if category == "ts_mean":
659
+ return ts.ts_mean(expr, window)
660
+ elif category == "ts_std":
661
+ return ts.ts_std(expr, window)
662
+ elif category == "ts_max":
663
+ return ts.ts_max(expr, window)
664
+ elif category == "ts_min":
665
+ return ts.ts_min(expr, window)
666
+ elif category == "ts_sum":
667
+ return ts.ts_sum(expr, window)
668
+ elif category == "ts_prod":
669
+ return ts.ts_prod(expr, window)
670
+ elif category == "ts_median":
671
+ return ts.ts_median(expr, window)
672
+ elif category == "ts_rank":
673
+ return ts.ts_rank(expr, window)
674
+ elif category == "ts_argmax":
675
+ return self._get_op("ts_argmax")(expr, window)
676
+ elif category == "ts_argmin":
677
+ return self._get_op("ts_argmin")(expr, window)
678
+
679
+ # 双输入滚动算子
680
+ elif category == "ts_cov":
681
+ return ts.ts_cov(expr, input_exprs[1], window)
682
+ elif category == "ts_corr":
683
+ return ts.ts_corr(expr, input_exprs[1], window)
684
+
685
+ # 差分与变化
686
+ elif category == "ts_delta":
687
+ return ts.ts_delta(expr, periods)
688
+ elif category == "ts_pct_change":
689
+ return ts.ts_pct_change(expr, periods)
690
+ elif category == "ts_lag":
691
+ return ts.ts_lag(expr, periods)
692
+
693
+ # 指数加权移动算子(单输入)
694
+ elif category == "ewm_mean":
695
+ return ts.ewm_mean(expr, alpha=alpha)
696
+ elif category == "ewm_std":
697
+ return ts.ewm_std(expr, alpha=alpha)
698
+ elif category == "ewm_var":
699
+ return self._get_op("ewm_var")(expr, alpha=alpha)
700
+
701
+ # 指数加权移动算子(双输入)
702
+ elif category == "ewm_corr":
703
+ return ts.ewm_corr(expr, input_exprs[1], alpha=alpha)
704
+ elif category == "ewm_cov":
705
+ return self._get_op("ewm_cov")(expr, input_exprs[1], alpha=alpha)
706
+
707
+ return None
708
+
709
+ def _apply_sec_operator(
710
+ self,
711
+ category: str,
712
+ input_exprs: List[pl.Expr],
713
+ params: Dict[str, Any]
714
+ ) -> pl.Expr:
715
+ """应用截面算子"""
716
+ expr = input_exprs[0]
717
+
718
+ if category == "rank":
719
+ return sec.rank(expr)
720
+ elif category == "zscore":
721
+ return sec.zscore(expr)
722
+ elif category == "winsorize":
723
+ return sec.winsorize(
724
+ expr,
725
+ params.get("lower", 0.01),
726
+ params.get("upper", 0.01)
727
+ )
728
+ elif category == "neutralize":
729
+ return sec.neutralize_market(expr)
730
+ elif category == "scale":
731
+ return sec.scale(expr)
732
+ elif category == "percentile":
733
+ return sec.percentile(expr)
734
+ elif category == "rank_ic":
735
+ return sec.rank_ic(expr, input_exprs[1])
736
+ elif category == "ic":
737
+ return sec.ic(expr, input_exprs[1])
738
+ elif category == "group_norm":
739
+ return sec.group_norm(
740
+ expr, pl.col(params["group"]), params.get("method", "zscore")
741
+ )
742
+ elif category == "group_winsorize":
743
+ return sec.group_winsorize(
744
+ expr, pl.col(params["group"]),
745
+ params.get("lower", 0.01), params.get("upper", 0.01)
746
+ )
747
+
748
+ return None
749
+
750
+ def _apply_math_operator(
751
+ self,
752
+ category: str,
753
+ expr: pl.Expr,
754
+ params: Dict[str, Any]
755
+ ) -> pl.Expr:
756
+ """应用数学算子"""
757
+ value = params.get("value", 1.0)
758
+
759
+ if category == "add":
760
+ return math.add(expr, value)
761
+ elif category == "sub":
762
+ return math.sub(expr, value)
763
+ elif category == "mul":
764
+ return math.mul(expr, value)
765
+ elif category == "div":
766
+ return math.div(expr, value)
767
+ elif category == "log":
768
+ return math.log(expr)
769
+ elif category == "abs":
770
+ return math.abs(expr)
771
+ elif category == "pow":
772
+ return math.pow(expr, params.get("exponent", 2))
773
+ elif category == "log1p":
774
+ return math.log1p(expr)
775
+ elif category == "sqrt":
776
+ return math.sqrt(expr)
777
+ elif category == "sign":
778
+ return math.sign(expr)
779
+ elif category == "clip":
780
+ return math.clip(expr, params.get("lower"), params.get("upper"))
781
+ elif category == "floor":
782
+ return math.floor(expr)
783
+ elif category == "ceil":
784
+ return math.ceil(expr)
785
+ elif category == "round":
786
+ return math.round(expr, params.get("decimals", 2))
787
+ elif category == "nan_to_null":
788
+ return math.nan_to_null(expr)
789
+ elif category == "fill_null":
790
+ return math.fill_null(expr, params.get("value", 0.0))
791
+ elif category == "fill_zero":
792
+ return math.fill_zero(expr)
793
+ elif category == "sin":
794
+ return math.sin(expr)
795
+ elif category == "cos":
796
+ return math.cos(expr)
797
+ elif category == "tan":
798
+ return math.tan(expr)
799
+ elif category == "arcsin":
800
+ return math.arcsin(expr)
801
+ elif category == "arccos":
802
+ return math.arccos(expr)
803
+ elif category == "arctan":
804
+ return math.arctan(expr)
805
+
806
+ return None
807
+
808
+ def _apply_composite_operator(
809
+ self,
810
+ category: str,
811
+ exprs: List[pl.Expr],
812
+ params: Dict[str, Any]
813
+ ) -> pl.Expr:
814
+ """应用组合算子"""
815
+ if category == "weighted_sum":
816
+ default_weights = [1.0 / len(exprs)] * len(exprs)
817
+ weights = params.get("weights", default_weights)
818
+ return composite.weighted_sum(exprs, weights)
819
+ elif category == "weighted_avg":
820
+ return composite.weighted_avg(exprs)
821
+ elif category == "max":
822
+ return composite.max(exprs)
823
+ elif category == "min":
824
+ return composite.min(exprs)
825
+ elif category == "blend":
826
+ alpha = params.get("alpha", 0.5)
827
+ return composite.blend(exprs[0], exprs[1], alpha)
828
+ elif category == "abs_max":
829
+ return composite.abs_max(exprs)
830
+ elif category == "combine":
831
+ return composite.combine(exprs, params.get("method", "sum"))
832
+ elif category == "select_top":
833
+ return composite.select_top(
834
+ exprs[0], params.get("n", 10), params.get("ascending", False)
835
+ )
836
+ elif category == "filter_positive":
837
+ return composite.filter_positive(exprs[0])
838
+ elif category == "filter_negative":
839
+ return composite.filter_negative(exprs[0])
840
+ elif category == "abs_filter":
841
+ return composite.abs_filter(exprs[0], params.get("threshold", 0.0))
842
+ elif category == "rank_sort":
843
+ return composite.rank_sort(exprs, params.get("weights"))
844
+
845
+ return None
846
+
847
+ def _resolve_universe(self, universe: str) -> Optional[List[str]]:
848
+ """解析 Universe 配置,返回股票代码列表。
849
+
850
+ 支持格式:
851
+ - "all" / "" → None (不过滤)
852
+ - "A_stock" → 预留 A 股全市场(当前不过滤,返回 None)
853
+ - "/path/to/codes.txt" → 从文件读取,每行一个代码
854
+ - "000001.SZ,600000.SH" → 逗号分隔的代码列表
855
+
856
+ Returns:
857
+ 股票代码列表,或 None 表示不过滤
858
+ """
859
+ if not universe or universe.strip().lower() in ("all", "*"):
860
+ return None
861
+
862
+ # 预留: A 股全市场
863
+ if universe.strip().lower() in ("a_stock", "a-share", "cn_stock"):
864
+ return None
865
+
866
+ # 文件路径
867
+ from pathlib import Path
868
+ u = universe.strip()
869
+ if Path(u).is_file():
870
+ codes = []
871
+ with open(u, encoding="utf-8") as f:
872
+ for line in f:
873
+ line = line.strip()
874
+ if line and not line.startswith("#"):
875
+ codes.append(line)
876
+ return codes if codes else None
877
+
878
+ # 逗号分隔的代码列表
879
+ if "," in u:
880
+ codes = [c.strip() for c in u.split(",") if c.strip()]
881
+ return codes if codes else None
882
+
883
+ # 单个代码
884
+ return [u]
885
+
886
+ def _apply_talib_operator(
887
+ self,
888
+ category: str,
889
+ input_exprs: List[pl.Expr],
890
+ params: Dict[str, Any]
891
+ ) -> pl.Expr:
892
+ """应用 TA-Lib 技术分析算子
893
+
894
+ 直接从 factor_functions 注册表查找 talib_* 算子。
895
+ YAML 配置示例:
896
+ type: talib
897
+ category: talib_rsi
898
+ inputs: [close]
899
+ params: {timeperiod: 14}
900
+ """
901
+ from QuantNodes.operators.proxy import get_operator
902
+ op_func = get_operator(category)
903
+ if op_func is not None:
904
+ return op_func(*input_exprs, **params)
905
+ return None
906
+
907
+ def _execute_plan(
908
+ self,
909
+ data: pl.LazyFrame,
910
+ result: ExecutionResult
911
+ ) -> None:
912
+ """执行计算计划
913
+
914
+ 保留原始列 (date, code, close等) 并添加计算的因子列。
915
+ 使用 with_columns 逐层添加,确保中间表达式可被下游引用。
916
+ """
917
+ if not self._expressions:
918
+ result.data = data
919
+ return
920
+
921
+ # 用 with_columns 逐个添加计算列,解决表达式间依赖
922
+ computed = data
923
+ for name, expr in self._expressions.items():
924
+ # 检查是否来自 List[Expr] 的拆分结果(双下划线格式:name__0, name__1)
925
+ if "__" in name:
926
+ base_name, idx = name.rsplit("__", 1)
927
+ if idx.isdigit():
928
+ computed = computed.with_columns(expr.alias(base_name))
929
+ continue
930
+
931
+ computed = computed.with_columns(expr.alias(name))
932
+
933
+ result.data = computed
934
+
935
+ def get_expressions(self) -> Dict[str, pl.Expr]:
936
+ """获取生成的表达式"""
937
+ return self._expressions
938
+
939
+ def compile(
940
+ self,
941
+ config: StrategyConfig,
942
+ data: pl.LazyFrame
943
+ ) -> pl.LazyFrame:
944
+ """编译配置为 LazyFrame
945
+
946
+ Args:
947
+ config: 策略配置
948
+ data: 数据 LazyFrame
949
+
950
+ Returns:
951
+ 计算后的 LazyFrame
952
+ """
953
+ result = self.run(config, data)
954
+
955
+ if result.is_success and hasattr(result, "data"):
956
+ return result.data
957
+
958
+ return data