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,453 @@
1
+ # coding=utf-8
2
+ """
3
+ 数据预处理工具类
4
+
5
+ 替代 QuantStudio.FactorDataBase.FactorOperation.DataPreprocessingFun
6
+ 提供因子数据预处理的各种操作:标准化、去极值、缺失值处理、正交化等
7
+ """
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ from scipy import stats
12
+ from typing import Optional, Callable
13
+
14
+
15
+ class DataPreprocessingFun:
16
+ """
17
+ 数据预处理函数集合
18
+
19
+ 提供因子数据预处理的各种静态方法,包括:
20
+ - 标准化:Z-Score、Rank、分位数
21
+ - 去极值:Winsorize
22
+ - 缺失值填充:常量、函数、回归
23
+ - 正交化:Gram-Schmidt
24
+ """
25
+
26
+ @staticmethod
27
+ def regressChangeRate(x: np.ndarray) -> np.ndarray:
28
+ """
29
+ 回归变化率
30
+
31
+ 对输入数据进行回归分析,返回回归系数作为变化率度量
32
+ """
33
+ if not isinstance(x, np.ndarray):
34
+ x = np.array(x)
35
+
36
+ if x.ndim < 2:
37
+ return x
38
+
39
+ n_factor = x.shape[0] if x.ndim > 2 else 1
40
+ result = np.zeros(x.shape[1:]) if x.ndim > 2 else np.zeros(x.shape)
41
+
42
+ if x.ndim > 2:
43
+ for i in range(x.shape[1]):
44
+ for j in range(x.shape[2]):
45
+ y = x[0, i, j] if x.shape[0] > 0 else x[i, j]
46
+ if n_factor > 1:
47
+ X = np.vstack([x[k, i, j] for k in range(1, n_factor)]).T
48
+ X = np.column_stack([np.ones(len(X)), X])
49
+ try:
50
+ beta = np.linalg.lstsq(X, y, rcond=None)[0]
51
+ result[i, j] = beta[1] if len(beta) > 1 else beta[0]
52
+ except Exception:
53
+ result[i, j] = np.nan
54
+ else:
55
+ result[i, j] = y[-1] - y[0] if len(y) > 1 else y[0]
56
+ else:
57
+ for i in range(x.shape[1]):
58
+ y = x[:, i]
59
+ t = np.arange(len(y))
60
+ try:
61
+ beta = np.polyfit(t, y, 1)[0]
62
+ result[i] = beta
63
+ except Exception:
64
+ result[i] = np.nan
65
+
66
+ return result
67
+
68
+ @staticmethod
69
+ def standardizeZScore(
70
+ data: np.ndarray,
71
+ mask: Optional[np.ndarray] = None,
72
+ cat_data: Optional[np.ndarray] = None,
73
+ avg_weight: Optional[np.ndarray] = None,
74
+ dispersion_weight: Optional[np.ndarray] = None,
75
+ avg_statistics: str = "平均值",
76
+ dispersion_statistics: str = "标准差",
77
+ other_handle: str = "填充None",
78
+ **kwargs
79
+ ) -> np.ndarray:
80
+ """Z-Score 标准化"""
81
+ result = data.copy().astype(float)
82
+ valid_mask = ~np.isnan(data)
83
+
84
+ if mask is not None:
85
+ valid_mask = valid_mask & (mask == 1)
86
+
87
+ if cat_data is None:
88
+ if avg_weight is not None and dispersion_weight is not None:
89
+ mean_val = np.nansum(data * avg_weight) / np.nansum(avg_weight * valid_mask)
90
+ weighted_diff_sq = ((data - mean_val) * avg_weight) ** 2
91
+ std_val = np.sqrt(
92
+ np.nansum(weighted_diff_sq) / np.nansum(avg_weight * valid_mask)
93
+ )
94
+ else:
95
+ mean_val = np.nanmean(data[valid_mask]) if valid_mask.any() else 0
96
+ std_val = np.nanstd(data[valid_mask]) if valid_mask.any() else 1
97
+
98
+ if std_val == 0 or np.isnan(std_val):
99
+ std_val = 1
100
+
101
+ result[valid_mask] = (data[valid_mask] - mean_val) / std_val
102
+ result[~valid_mask] = np.nan
103
+ else:
104
+ unique_cats = np.unique(cat_data[~np.isnan(cat_data)])
105
+ for cat in unique_cats:
106
+ cat_mask = valid_mask & (cat_data == cat)
107
+ if cat_mask.any():
108
+ cat_data_arr = data[cat_mask]
109
+ if avg_weight is not None:
110
+ w = avg_weight[cat_mask]
111
+ cat_weight = w * ~np.isnan(cat_data_arr)
112
+ mean_val = np.nansum(cat_data_arr * w) / np.nansum(cat_weight)
113
+ cat_diff_sq = ((cat_data_arr - mean_val) * w) ** 2
114
+ std_val = np.sqrt(np.nansum(cat_diff_sq) / np.nansum(cat_weight))
115
+ else:
116
+ mean_val = np.nanmean(cat_data_arr)
117
+ std_val = np.nanstd(cat_data_arr)
118
+
119
+ if std_val == 0 or np.isnan(std_val):
120
+ std_val = 1
121
+
122
+ result[cat_mask] = (data[cat_mask] - mean_val) / std_val
123
+
124
+ uncategorized_mask = valid_mask & np.isnan(cat_data)
125
+ if uncategorized_mask.any():
126
+ result[uncategorized_mask] = np.nan
127
+
128
+ return result
129
+
130
+ @staticmethod
131
+ def standardizeRank(
132
+ data: np.ndarray,
133
+ mask: Optional[np.ndarray] = None,
134
+ cat_data: Optional[np.ndarray] = None,
135
+ ascending: bool = True,
136
+ uniformization: bool = True,
137
+ perturbation: bool = False,
138
+ offset: float = 0.5,
139
+ other_handle: str = "填充None",
140
+ **kwargs
141
+ ) -> np.ndarray:
142
+ """Rank 标准化"""
143
+ result = np.zeros_like(data, dtype=float)
144
+ valid_mask = ~np.isnan(data)
145
+
146
+ if mask is not None:
147
+ valid_mask = valid_mask & (mask == 1)
148
+
149
+ if cat_data is None:
150
+ valid_data = data[valid_mask]
151
+ if len(valid_data) > 0:
152
+ ranks = stats.rankdata(valid_data, ascending=ascending)
153
+ if uniformization:
154
+ ranks = ranks / (len(ranks) + 1)
155
+ if perturbation:
156
+ ranks = ranks + np.random.uniform(-offset, offset, len(ranks))
157
+ result[valid_mask] = ranks
158
+ result[~valid_mask] = np.nan
159
+ else:
160
+ unique_cats = np.unique(cat_data[~np.isnan(cat_data)])
161
+ for cat in unique_cats:
162
+ cat_mask = valid_mask & (cat_data == cat)
163
+ valid_cat_data = data[cat_mask]
164
+ if len(valid_cat_data) > 0:
165
+ ranks = stats.rankdata(valid_cat_data, ascending=ascending)
166
+ if uniformization:
167
+ ranks = ranks / (len(ranks) + 1)
168
+ if perturbation:
169
+ ranks = ranks + np.random.uniform(-offset, offset, len(ranks))
170
+ result[cat_mask] = ranks
171
+
172
+ uncategorized_mask = valid_mask & np.isnan(cat_data)
173
+ if uncategorized_mask.any():
174
+ result[uncategorized_mask] = np.nan
175
+
176
+ return result
177
+
178
+ @staticmethod
179
+ def fillNaNByValue(
180
+ data: np.ndarray,
181
+ mask: Optional[np.ndarray] = None,
182
+ cat_data: Optional[np.ndarray] = None,
183
+ fill_value: float = 0,
184
+ fill_method: str = "常数",
185
+ other_handle: str = "填充None",
186
+ **kwargs
187
+ ) -> np.ndarray:
188
+ """按值填充 NaN"""
189
+ result = data.copy()
190
+ nan_mask = np.isnan(data)
191
+
192
+ if mask is not None:
193
+ nan_mask = nan_mask & (mask == 1)
194
+
195
+ if fill_method == "常数":
196
+ result[nan_mask] = fill_value
197
+ elif fill_method == "均值":
198
+ valid_data = data[~nan_mask]
199
+ fill_val = np.nanmean(valid_data) if len(valid_data) > 0 else fill_value
200
+ result[nan_mask] = fill_val
201
+ elif fill_method == "中位数":
202
+ valid_data = data[~nan_mask]
203
+ fill_val = np.nanmedian(valid_data) if len(valid_data) > 0 else fill_value
204
+ result[nan_mask] = fill_val
205
+ elif fill_method == "前向填充":
206
+ df = pd.DataFrame(result)
207
+ result = df.ffill().values
208
+ result[nan_mask] = np.nan
209
+ elif fill_method == "后向填充":
210
+ df = pd.DataFrame(result)
211
+ result = df.bfill().values
212
+ result[nan_mask] = np.nan
213
+
214
+ return result
215
+
216
+ @staticmethod
217
+ def winsorize(
218
+ data: np.ndarray,
219
+ mask: Optional[np.ndarray] = None,
220
+ cat_data: Optional[np.ndarray] = None,
221
+ winsorize_lower: float = 0.01,
222
+ winsorize_upper: float = 0.01,
223
+ fill_value: Optional[float] = None,
224
+ fill_method: str = "均值方差",
225
+ boundary_method: str = "边界值",
226
+ other_handle: str = "填充None",
227
+ **kwargs
228
+ ) -> np.ndarray:
229
+ """Winsorize 去极值处理"""
230
+ if not (0 <= winsorize_lower <= 1) or not (0 <= winsorize_upper <= 1):
231
+ raise ValueError(
232
+ f"winsorize bounds must be in [0, 1], "
233
+ f"got lower={winsorize_lower}, upper={winsorize_upper}"
234
+ )
235
+ if winsorize_lower + winsorize_upper > 1:
236
+ total = winsorize_lower + winsorize_upper
237
+ raise ValueError(
238
+ f"winsorize_lower + winsorize_upper must be <= 1, got {total}"
239
+ )
240
+
241
+ result = data.copy().astype(float)
242
+ valid_mask = ~np.isnan(data)
243
+
244
+ if mask is not None:
245
+ valid_mask = valid_mask & (mask == 1)
246
+
247
+ if cat_data is None:
248
+ valid_data = data[valid_mask]
249
+ if len(valid_data) > 0:
250
+ lower_bound = np.nanpercentile(valid_data, winsorize_lower * 100)
251
+ upper_bound = np.nanpercentile(valid_data, (1 - winsorize_upper) * 100)
252
+
253
+ if fill_method == "均值方差":
254
+ mean_val = np.nanmean(valid_data)
255
+ std_val = np.nanstd(valid_data)
256
+ result[valid_mask & (data < lower_bound)] = mean_val - 2 * std_val
257
+ result[valid_mask & (data > upper_bound)] = mean_val + 2 * std_val
258
+ else:
259
+ result[valid_mask & (data < lower_bound)] = lower_bound
260
+ result[valid_mask & (data > upper_bound)] = upper_bound
261
+ else:
262
+ unique_cats = np.unique(cat_data[~np.isnan(cat_data)])
263
+ for cat in unique_cats:
264
+ cat_mask = valid_mask & (cat_data == cat)
265
+ valid_cat_data = data[cat_mask]
266
+ if len(valid_cat_data) > 0:
267
+ lower_bound = np.nanpercentile(valid_cat_data, winsorize_lower * 100)
268
+ upper_bound = np.nanpercentile(valid_cat_data, (1 - winsorize_upper) * 100)
269
+
270
+ if fill_method == "均值方差":
271
+ mean_val = np.nanmean(valid_cat_data)
272
+ std_val = np.nanstd(valid_cat_data)
273
+ result[cat_mask & (data < lower_bound)] = mean_val - 2 * std_val
274
+ result[cat_mask & (data > upper_bound)] = mean_val + 2 * std_val
275
+ else:
276
+ result[cat_mask & (data < lower_bound)] = lower_bound
277
+ result[cat_mask & (data > upper_bound)] = upper_bound
278
+
279
+ return result
280
+
281
+ @staticmethod
282
+ def standardizeQuantile(
283
+ data: np.ndarray,
284
+ mask: Optional[np.ndarray] = None,
285
+ cat_data: Optional[np.ndarray] = None,
286
+ ascending: bool = True,
287
+ perturbation: bool = False,
288
+ other_handle: str = "填充None",
289
+ **kwargs
290
+ ) -> np.ndarray:
291
+ """分位数标准化"""
292
+ return DataPreprocessingFun.standardizeRank(
293
+ data=data,
294
+ mask=mask,
295
+ cat_data=cat_data,
296
+ ascending=ascending,
297
+ uniformization=True,
298
+ perturbation=perturbation,
299
+ offset=0.5,
300
+ other_handle=other_handle,
301
+ **kwargs
302
+ )
303
+
304
+ @staticmethod
305
+ def fillNaNByFun(
306
+ data: np.ndarray,
307
+ mask: Optional[np.ndarray] = None,
308
+ cat_data: Optional[np.ndarray] = None,
309
+ val_fun: Optional[Callable] = None,
310
+ other_handle: str = "填充None",
311
+ **kwargs
312
+ ) -> np.ndarray:
313
+ """按函数值填充 NaN"""
314
+ result = data.copy().astype(float)
315
+ nan_mask = np.isnan(data)
316
+
317
+ if mask is not None:
318
+ nan_mask = nan_mask & (mask == 1)
319
+
320
+ if val_fun is None:
321
+ val_fun = lambda x, n: np.zeros(n) + np.nanmean(x)
322
+
323
+ n = np.sum(nan_mask)
324
+ if n > 0:
325
+ non_nan_data = data[~nan_mask]
326
+ if len(non_nan_data) > 0:
327
+ fill_values = val_fun(non_nan_data, n)
328
+ else:
329
+ fill_values = np.zeros(n) + np.nan
330
+ result[nan_mask] = fill_values[:n]
331
+
332
+ return result
333
+
334
+ @staticmethod
335
+ def fillNaNByRegress(
336
+ data: np.ndarray,
337
+ mask: Optional[np.ndarray] = None,
338
+ cat_data: Optional[np.ndarray] = None,
339
+ X: Optional[np.ndarray] = None,
340
+ intercept: bool = True,
341
+ weight_data: Optional[np.ndarray] = None,
342
+ dummy_data: Optional[np.ndarray] = None,
343
+ other_handle: str = "填充None",
344
+ **kwargs
345
+ ) -> np.ndarray:
346
+ """按回归预测值填充 NaN"""
347
+ result = data.copy().astype(float)
348
+ nan_mask = np.isnan(data)
349
+
350
+ if mask is not None:
351
+ nan_mask = nan_mask & (mask == 1)
352
+
353
+ if X is None or X.size == 0:
354
+ return result
355
+
356
+ valid_rows = ~nan_mask
357
+ nan_rows = nan_mask
358
+
359
+ if np.sum(valid_rows) < 2 or np.sum(nan_rows) < 1:
360
+ return result
361
+
362
+ X_valid = X[valid_rows] if X.ndim > 1 else X[valid_rows].reshape(-1, 1)
363
+ y_valid = data[valid_rows]
364
+
365
+ if intercept:
366
+ X_valid = np.column_stack([np.ones(len(X_valid)), X_valid])
367
+
368
+ try:
369
+ beta = np.linalg.lstsq(X_valid, y_valid, rcond=None)[0]
370
+
371
+ X_nan = X[nan_rows] if X.ndim > 1 else X[nan_rows].reshape(-1, 1)
372
+ if intercept:
373
+ X_nan = np.column_stack([np.ones(len(X_nan)), X_nan])
374
+
375
+ y_pred = X_nan @ beta
376
+ result[nan_rows] = y_pred
377
+ except Exception:
378
+ pass
379
+
380
+ return result
381
+
382
+ @staticmethod
383
+ def orthogonalize(
384
+ data: np.ndarray,
385
+ mask: Optional[np.ndarray] = None,
386
+ cat_data: Optional[np.ndarray] = None,
387
+ X: Optional[np.ndarray] = None,
388
+ method: str = "gram_schmidt",
389
+ weight_data: Optional[np.ndarray] = None,
390
+ dummy_data: Optional[np.ndarray] = None,
391
+ other_handle: str = "填充None",
392
+ intercept: bool = True,
393
+ **kwargs
394
+ ) -> np.ndarray:
395
+ """正交化处理"""
396
+ result = data.copy().astype(float)
397
+ valid_mask = ~np.isnan(data)
398
+
399
+ if mask is not None:
400
+ valid_mask = valid_mask & (mask == 1)
401
+
402
+ if X is None or X.size == 0:
403
+ return result
404
+
405
+ if method == "gram_schmidt":
406
+ for i in range(result.shape[0]):
407
+ row_mask = valid_mask[i]
408
+ if not row_mask.any():
409
+ continue
410
+
411
+ x_valid = X[row_mask]
412
+ y_valid = result[i, row_mask]
413
+
414
+ if len(x_valid) < 2:
415
+ continue
416
+
417
+ try:
418
+ x_mean = np.nanmean(x_valid, axis=0)
419
+ x_centered = x_valid - x_mean
420
+ norm = np.linalg.norm(x_centered, axis=0)
421
+ norm[norm == 0] = 1
422
+ x_orthogonal = x_centered / norm
423
+
424
+ beta = np.linalg.lstsq(x_orthogonal, y_valid, rcond=None)[0]
425
+ y_pred = x_orthogonal @ beta
426
+ result[i, row_mask] = y_valid - y_pred + np.nanmean(y_valid)
427
+ except Exception:
428
+ continue
429
+ else:
430
+ for i in range(result.shape[0]):
431
+ row_mask = valid_mask[i]
432
+ if not row_mask.any():
433
+ continue
434
+
435
+ x_valid = X[row_mask]
436
+ y_valid = result[i, row_mask]
437
+
438
+ if len(x_valid) < 2:
439
+ continue
440
+
441
+ try:
442
+ if intercept:
443
+ X_design = np.column_stack([np.ones(len(x_valid)), x_valid])
444
+ else:
445
+ X_design = x_valid
446
+
447
+ beta = np.linalg.lstsq(X_design, y_valid, rcond=None)[0]
448
+ y_pred = X_design @ beta
449
+ result[i, row_mask] = y_valid - y_pred + np.nanmean(y_valid)
450
+ except Exception:
451
+ continue
452
+
453
+ return result
@@ -0,0 +1,46 @@
1
+ # -*- coding: utf-8 -*-
2
+ """DataSource - 顶层数据源标记基类 (Phase 3.3)
3
+
4
+ QuantNodes 有两个独立的数据接入子树:
5
+
6
+ - ``FileFormatLoader`` (文件): 按 path/key 产出 pd.DataFrame
7
+ (H5 / CSV / NPY / Parquet)
8
+ - ``BaseDBNode`` (数据库): 按 SQL 产出 pd.DataFrame
9
+ (SQLite / DuckDB / MySQL / ClickHouse / CSV / Parquet)
10
+
11
+ 两者接口差异大 (文件是面板矩阵语义, 数据库是 SQL 语义), 强行合并会
12
+ 泄漏抽象。``DataSource`` 因此被设计成**最小化标记基类**, 只统一:
13
+
14
+ 1. ``close()`` 生命周期 (释放文件句柄 / 数据库连接)
15
+ 2. 上下文管理器协议 (``with`` 自动 close)
16
+ 3. "产出 pd.DataFrame" 的语义约定 (不强制具体读取签名)
17
+
18
+ 子树各自保留专用接口 (``load`` vs ``query``)。
19
+ """
20
+ from abc import ABC, abstractmethod
21
+
22
+
23
+ class DataSource(ABC):
24
+ """所有数据源的顶层标记 + 生命周期协议。
25
+
26
+ Subclasses:
27
+ FileFormatLoader: 文件格式适配器 (H5/CSV/NPY/Parquet)
28
+ BaseDBNode: 数据库节点 (SQLite/DuckDB/MySQL/ClickHouse/CSV/Parquet)
29
+
30
+ 共同契约:
31
+ - 产出 pd.DataFrame (具体读取方法由子树定义)
32
+ - close() 释放底层资源
33
+ - 支持 ``with`` 上下文管理器
34
+ """
35
+
36
+ @abstractmethod
37
+ def close(self) -> None:
38
+ """释放底层资源 (文件句柄 / 数据库连接)。"""
39
+ raise NotImplementedError
40
+
41
+ def __enter__(self) -> "DataSource":
42
+ return self
43
+
44
+ def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
45
+ self.close()
46
+ return False
@@ -0,0 +1,178 @@
1
+ # coding=utf-8
2
+ """
3
+ core/events.py — 轻量级进程内事件总线 (Tier 0: Foundation)
4
+
5
+ 替代 v2.x 中已删除的 agent/bus/ 设计 (2026-06-23 nanobot 重构遗留)。
6
+
7
+ 设计原则:
8
+ - 同步语义: handler 在 publish 线程同步执行
9
+ - 单进程: 不支持跨进程/分布式
10
+ - 解耦: publisher 与 subscriber 互不感知
11
+ - 失败隔离: handler 异常不影响 publisher 和其他 handler
12
+
13
+ 使用示例:
14
+ from QuantNodes.core.events import get_event_bus, Events
15
+
16
+ bus = get_event_bus()
17
+
18
+ def on_factor_mined(event):
19
+ logger.info("New factor: %s", event.payload["formula"])
20
+
21
+ bus.subscribe(Events.FACTOR_MINED, on_factor_mined)
22
+ bus.publish_sync(Events.FACTOR_MINED, formula="rank(close)", ir=0.5)
23
+
24
+ Event 命名规范:
25
+ `<domain>.<action>` — 例如 factor.mined / backtest.completed
26
+ 所有事件常量定义在 Events 类中 (避免散落的字符串字面量)
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import logging
32
+ from dataclasses import dataclass, field
33
+ from typing import Any, Callable, Dict, List, Optional
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+
38
+ @dataclass
39
+ class Event:
40
+ """事件对象
41
+
42
+ Args:
43
+ name: 事件名 (建议从 Events 类选取)
44
+ payload: 事件数据 (字典形式)
45
+ source: 触发源标识 (可选, 用于调试)
46
+ """
47
+
48
+ name: str
49
+ payload: Dict[str, Any] = field(default_factory=dict)
50
+ source: str = ""
51
+
52
+ def __post_init__(self) -> None:
53
+ if not isinstance(self.name, str) or not self.name:
54
+ raise ValueError("Event.name must be a non-empty string")
55
+
56
+
57
+ HandlerType = Callable[[Event], None]
58
+
59
+
60
+ class EventBus:
61
+ """进程内事件总线 (同步)
62
+
63
+ 线程安全: 本类内部无锁,订阅/发布应在同一线程或加外部锁。
64
+ """
65
+
66
+ def __init__(self) -> None:
67
+ self._subscribers: Dict[str, List[HandlerType]] = {}
68
+
69
+ def subscribe(self, event_name: str, handler: HandlerType) -> None:
70
+ """订阅事件。
71
+
72
+ 同一 handler 可多次订阅同一事件, 触发时执行多次。
73
+ """
74
+ self._subscribers.setdefault(event_name, []).append(handler)
75
+
76
+ def unsubscribe(self, event_name: str, handler: HandlerType) -> None:
77
+ """取消订阅。handler 不存在时静默忽略。"""
78
+ if event_name in self._subscribers:
79
+ try:
80
+ self._subscribers[event_name].remove(handler)
81
+ except ValueError:
82
+ pass
83
+
84
+ def publish(self, event: Event) -> None:
85
+ """发布事件。同步调用所有订阅者。
86
+
87
+ 异常隔离: 单个 handler 抛异常不影响其他 handler 和 publisher。
88
+ """
89
+ handlers = self._subscribers.get(event.name, [])
90
+ for handler in handlers:
91
+ try:
92
+ handler(event)
93
+ except Exception as e:
94
+ logger.warning(
95
+ "Event handler %r failed for %s: %s",
96
+ handler,
97
+ event.name,
98
+ e,
99
+ )
100
+
101
+ def publish_sync(self, name: str, source: str = "", **payload: Any) -> None:
102
+ """便捷发布接口。"""
103
+ self.publish(Event(name=name, payload=payload, source=source))
104
+
105
+ def clear(self) -> None:
106
+ """清空所有订阅 (主要用于测试)。"""
107
+ self._subscribers.clear()
108
+
109
+ def subscriber_count(self, event_name: str) -> int:
110
+ """返回某事件的订阅者数量 (主要用于测试)。"""
111
+ return len(self._subscribers.get(event_name, []))
112
+
113
+ def event_names(self) -> List[str]:
114
+ """返回所有已注册的事件名 (主要用于测试)。"""
115
+ return list(self._subscribers.keys())
116
+
117
+
118
+ # 全局单例
119
+ _global_bus: Optional[EventBus] = None
120
+
121
+
122
+ def get_event_bus() -> EventBus:
123
+ """获取全局 EventBus 单例。
124
+
125
+ 延迟创建: 首次调用时实例化, 后续返回同一对象。
126
+ 测试中可用 reset_event_bus() 重置。
127
+ """
128
+ global _global_bus
129
+ if _global_bus is None:
130
+ _global_bus = EventBus()
131
+ return _global_bus
132
+
133
+
134
+ def reset_event_bus() -> None:
135
+ """重置全局 EventBus (主要用于测试隔离)。"""
136
+ global _global_bus
137
+ _global_bus = None
138
+
139
+
140
+ class Events:
141
+ """事件名常量集中定义
142
+
143
+ 命名规范: `<domain>.<action>`
144
+ - factor.* 因子挖掘相关
145
+ - backtest.* 回测相关
146
+ - agent.* Agent 交互相关
147
+ - monitor.* 监控相关
148
+ - workflow.* 工作流相关
149
+ """
150
+
151
+ # factor 事件
152
+ FACTOR_MINED = "factor.mined"
153
+ FACTOR_EVALUATED = "factor.evaluated"
154
+ FACTOR_REJECTED = "factor.rejected"
155
+
156
+ # backtest 事件
157
+ BACKTEST_COMPLETED = "backtest.completed"
158
+ BACKTEST_FAILED = "backtest.failed"
159
+
160
+ # agent 事件
161
+ AGENT_MESSAGE = "agent.message"
162
+ AGENT_TOOL_CALLED = "agent.tool_called"
163
+
164
+ # monitor 事件
165
+ DRIFT_DETECTED = "monitor.drift_detected"
166
+
167
+ # workflow 事件
168
+ WORKFLOW_ROUND_START = "workflow.round_start"
169
+ WORKFLOW_ROUND_END = "workflow.round_end"
170
+
171
+
172
+ __all__ = [
173
+ "Event",
174
+ "EventBus",
175
+ "Events",
176
+ "get_event_bus",
177
+ "reset_event_bus",
178
+ ]
@@ -0,0 +1,22 @@
1
+ """EvolutionLoop — 多轮演化主循环。
2
+
3
+ 公开 API:
4
+ - EvolutionSetting: Pydantic 配置
5
+ - FactorCandidate: 候选因子 dataclass
6
+ - Hypothesizer / Mutator / Crosser: 3 个 LLM-based operators
7
+ - EvolutionLoop: 主控循环
8
+ - EvolutionResult: 结果 dataclass
9
+ """
10
+ from .settings import EvolutionSetting
11
+ from .operators import Crosser, FactorCandidate, Hypothesizer, Mutator
12
+ from .loop import EvolutionLoop, EvolutionResult
13
+
14
+ __all__ = [
15
+ "EvolutionSetting",
16
+ "FactorCandidate",
17
+ "Hypothesizer",
18
+ "Mutator",
19
+ "Crosser",
20
+ "EvolutionLoop",
21
+ "EvolutionResult",
22
+ ]