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,293 @@
1
+ # coding=utf-8
2
+ """Translate ``.env`` QUANTNODES__* vars into HKUDS nanobot config.json.
3
+
4
+ Nanobot 0.2.1 reads a JSON config (``.agent/nanobot_config.json`` by
5
+ convention) with shape (verified against ``nanobot.config.schema.Config``)::
6
+
7
+ {
8
+ "agents": {"defaults": {"workspace": "<path>", "model": "...", "provider": "openai"}},
9
+ "providers": {
10
+ "<slot_name>": {
11
+ "api_key": "sk-...",
12
+ "api_base": "https://api.openai.com/v1",
13
+ "api_type": "chat_completions" | "responses" | "auto"
14
+ },
15
+ ...
16
+ },
17
+ "channels": {
18
+ "websocket": {"enabled": true, "host": "127.0.0.1", "port": 8765, ...},
19
+ "feishu": {"enabled": false, "app_id": "...", "app_secret": "...", ...}
20
+ }
21
+ }
22
+
23
+ Provider slots (defined in ProvidersConfig schema):
24
+ - ``openai`` — OpenAI direct
25
+ - ``anthropic`` — Anthropic direct
26
+ - ``azure_openai`` — Azure OpenAI
27
+ - ``bedrock`` — AWS Bedrock
28
+ - ``custom`` — Generic OpenAI-compatible endpoint
29
+ - ``ollama`` — Ollama local (api_base=http://localhost:11434/v1)
30
+ - ``lm_studio`` — LM Studio local
31
+ - ``vllm`` — vLLM local
32
+ - ``openrouter`` — OpenRouter aggregator
33
+ - ``deepseek`` — DeepSeek direct
34
+ - ``groq`` — Groq
35
+ - ``gemini`` — Google Gemini
36
+ - ``minimax`` — MiniMax (provider model aliases)
37
+ - ... (30+ total)
38
+
39
+ Channels (defined as ``ChannelsConfig`` extras in upstream schema):
40
+
41
+ - ``websocket`` — nanobot WebSocket channel + WebUI SPA host. Default port 18080.
42
+ - ``feishu`` — Feishu/Lark bot (WebSocket long connection, no public IP needed).
43
+ Requires ``FEISHU_APP_ID`` + ``FEISHU_APP_SECRET`` env vars.
44
+
45
+ Slot resolution (URL-based heuristics):
46
+ - base URL contains ``anthropic`` -> ``anthropic`` (or ``minimax_anthropic`` if MiniMax endpoint)
47
+ - base URL contains ``azure`` -> ``azure_openai``
48
+ - base URL contains ``ollama`` -> ``ollama``
49
+ - otherwise (incl. local OpenAI-compatible) -> ``custom``
50
+
51
+ ``agents.defaults.provider`` is set to the resolved slot name so that the
52
+ upstream ``_match_provider`` helper finds the right provider config.
53
+ """
54
+
55
+ from __future__ import annotations
56
+
57
+ import json
58
+ import logging
59
+ import os
60
+ from pathlib import Path
61
+ from typing import Any, Dict, List, Optional
62
+
63
+ from QuantNodes.constants import DEFAULT_HOST, DEFAULT_WEBSOCKET_PORT, DEFAULT_LLM_MODEL
64
+
65
+ logger = logging.getLogger(__name__)
66
+
67
+
68
+ CONFIG_FILENAME = "nanobot_config.json"
69
+
70
+
71
+ def _resolve_slot(base_url: str, api_key: str) -> tuple[str, str]:
72
+ """Return (slot_name, api_type) inferred from URL/key hints.
73
+
74
+ ``slot_name`` matches one of the predefined slots in ProvidersConfig
75
+ schema (e.g. ``openai``, ``anthropic``, ``azure_openai``, ``custom``).
76
+ ``api_type`` is the request API surface (``chat_completions``,
77
+ ``responses``, or ``auto``).
78
+ """
79
+ u = (base_url or "").lower()
80
+ if "anthropic" in u and "minimax" not in u and "minimaxi" not in u:
81
+ # Anthropic native endpoint (api.anthropic.com or proxied clones).
82
+ return "anthropic", "auto"
83
+ if "azure" in u:
84
+ return "azure_openai", "responses"
85
+ if "ollama" in u or ":11434" in u:
86
+ return "ollama", "auto"
87
+ # v3.0.0: MiniMax provider (``api.minimaxi.com`` /
88
+ # ``api.minimax.com``) exposes an OpenAI-compatible ``/v1/chat/completions``
89
+ # endpoint, NOT the Anthropic ``/v1/messages`` surface — so the
90
+ # ``minimax_anthropic`` slot (which forces Anthropic schema) would 404.
91
+ # Use the ``custom`` slot with ``chat_completions`` API type instead.
92
+ if "minimaxi" in u or "minimax" in u:
93
+ return "custom", "chat_completions"
94
+ if api_key and api_key.startswith("sk-ant-"):
95
+ return "anthropic", "auto"
96
+ # Default: OpenAI-compatible endpoint goes to ``custom`` slot
97
+ # (which is the documented fallback for OpenAI-compatible URLs).
98
+ return "custom", "chat_completions"
99
+
100
+
101
+ def _build_websocket_config(
102
+ host: str = DEFAULT_HOST,
103
+ port: int = DEFAULT_WEBSOCKET_PORT,
104
+ ws_token: Optional[str] = None,
105
+ ) -> Dict[str, Any]:
106
+ """Build the WebSocket channel config block.
107
+
108
+ The WebSocket channel is the upstream nanobot gateway. It serves:
109
+
110
+ - The WebUI SPA on port 18080 (mounted via ``ChannelManager(webui_static_dist=True)``)
111
+ - The WebSocket endpoint at ``ws://{host}:{port}/{path}?token=...``
112
+ - REST surface for sessions / messages / commands / sidebar state
113
+
114
+ Configuration knobs (all map to ``nanobot.channels.websocket.WebSocketConfig``):
115
+
116
+ - ``host`` — bind address (default 127.0.0.1, loopback only)
117
+ - ``port`` — WS port (default 8765; the SPA lives on gateway.port)
118
+ - ``path`` — WS upgrade path (default ``/``)
119
+ - ``websocketRequiresToken`` — require token for handshake (default True)
120
+ - ``streaming`` — enable streaming responses (default True)
121
+ - ``allowFrom`` — list of allowed ``client_id`` values (default ``["*"]``)
122
+ """
123
+ return {
124
+ "enabled": True,
125
+ "host": host,
126
+ "port": port,
127
+ "path": "/",
128
+ "tokenIssuePath": "/webui/token",
129
+ "websocketRequiresToken": True,
130
+ "allowFrom": ["*"],
131
+ "streaming": True,
132
+ # v3.0.0: when binding to 0.0.0.0 (all interfaces), nanobot's
133
+ # WebSocketConfig validates that a token is set — this prevents
134
+ # unauthenticated access from the LAN. Use NANOBOT_WS_TOKEN env
135
+ # var if provided; otherwise auto-generate one on startup.
136
+ **({"token": ws_token} if ws_token else {}),
137
+ }
138
+
139
+
140
+ def _build_feishu_config(env: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
141
+ """Build the Feishu channel config block from env vars.
142
+
143
+ Required env vars (Stage 5.4):
144
+
145
+ - ``FEISHU_APP_ID`` — application ID from Feishu Open Platform
146
+ - ``FEISHU_APP_SECRET`` — application secret
147
+
148
+ Optional:
149
+
150
+ - ``FEISHU_ENCRYPT_KEY`` — event encryption key
151
+ - ``FEISHU_VERIFICATION_TOKEN`` — event verification token
152
+ - ``FEISHU_DOMAIN`` (``feishu`` / ``lark``) — domain selector
153
+ - ``FEISHU_GROUP_POLICY`` (``mention`` / ``open``) — group chat policy
154
+ - ``FEISHU_REPLY_TO_MESSAGE`` — whether to quote the user's message
155
+
156
+ If required env vars are missing the channel is disabled (returns
157
+ ``{"enabled": False}``). The Feishu SDK (``lark_oapi``) is an *optional*
158
+ runtime dep — even when this config block is enabled, the channel will
159
+ refuse to start if the SDK isn't installed.
160
+
161
+ Reference: ``nanobot.channels.feishu.FeishuConfig``
162
+ """
163
+ env = env or os.environ
164
+ app_id = env.get("FEISHU_APP_ID", "").strip()
165
+ app_secret = env.get("FEISHU_APP_SECRET", "").strip()
166
+ if not (app_id and app_secret):
167
+ return {"enabled": False}
168
+
169
+ allow_from_raw = env.get("FEISHU_ALLOW_FROM", "").strip()
170
+ allow_from: List[str] = (
171
+ [s.strip() for s in allow_from_raw.split(",") if s.strip()]
172
+ if allow_from_raw else []
173
+ )
174
+
175
+ return {
176
+ "enabled": True,
177
+ "appId": app_id,
178
+ "appSecret": app_secret,
179
+ "encryptKey": env.get("FEISHU_ENCRYPT_KEY", "").strip(),
180
+ "verificationToken": env.get("FEISHU_VERIFICATION_TOKEN", "").strip(),
181
+ "allowFrom": allow_from,
182
+ "domain": env.get("FEISHU_DOMAIN", "feishu").strip() or "feishu",
183
+ "groupPolicy": env.get("FEISHU_GROUP_POLICY", "mention").strip() or "mention",
184
+ "replyToMessage": env.get("FEISHU_REPLY_TO_MESSAGE", "false").lower() in ("1", "true", "yes"),
185
+ "streaming": True,
186
+ }
187
+
188
+
189
+ def build_nanobot_config(
190
+ workspace: Path,
191
+ user_config: Optional[Dict[str, Any]] = None,
192
+ channel_overrides: Optional[Dict[str, Any]] = None,
193
+ ) -> Dict[str, Any]:
194
+ """Build a nanobot-compatible config dict from env + user_config.
195
+
196
+ ``channel_overrides`` lets the caller (typically the FastAPI runtime)
197
+ force specific channel settings — e.g. websocket host/port derived
198
+ from ``NANOBOT_GATEWAY_HOST``/``NANOBOT_GATEWAY_PORT``.
199
+ """
200
+ user_config = user_config or {}
201
+ channel_overrides = channel_overrides or {}
202
+
203
+ api_key = user_config.get("api_key") or os.environ.get("QUANTNODES__LLM__API_KEY", "")
204
+ base_url = user_config.get("api_base") or os.environ.get("QUANTNODES__LLM__BASE_URL", "")
205
+ model = user_config.get("model") or os.environ.get("QUANTNODES__LLM__MODEL", DEFAULT_LLM_MODEL)
206
+
207
+ slot, api_type = _resolve_slot(base_url, api_key)
208
+
209
+ provider_block: Dict[str, Any] = {}
210
+ # ``api_type`` is only allowed on the ``openai`` slot (upstream schema
211
+ # validation); other slots always use the provider-default API surface.
212
+ if slot == "openai":
213
+ provider_block["apiType"] = api_type
214
+ if api_key:
215
+ provider_block["apiKey"] = api_key
216
+ if base_url:
217
+ provider_block["apiBase"] = base_url
218
+
219
+ # ── Channels ────────────────────────────────────────────────────────
220
+ channels: Dict[str, Any] = {}
221
+
222
+ ws_override = channel_overrides.get("websocket") or {}
223
+ ws_enabled = ws_override.get("enabled", True)
224
+ if ws_enabled:
225
+ ws_host = ws_override.get("host", DEFAULT_HOST)
226
+ # v3.0.0: when binding to 0.0.0.0, nanobot requires a token for security.
227
+ # Use NANOBOT_WS_TOKEN env var if set; otherwise auto-generate one.
228
+ ws_token = os.environ.get("NANOBOT_WS_TOKEN", "")
229
+ if not ws_token and ws_host in ("0.0.0.0", "::"):
230
+ import secrets
231
+ ws_token = secrets.token_urlsafe(32)
232
+ logger.info("Auto-generated WebSocket token for LAN access: %s...", ws_token[:12])
233
+ channels["websocket"] = _build_websocket_config(
234
+ host=ws_host,
235
+ port=int(ws_override.get("port", DEFAULT_WEBSOCKET_PORT)),
236
+ ws_token=ws_token or None,
237
+ )
238
+ else:
239
+ # Caller explicitly disabled websocket — emit the block anyway so
240
+ # nanobot sees ``enabled: false`` and skips it deterministically.
241
+ channels["websocket"] = {"enabled": False}
242
+
243
+ fs_override = channel_overrides.get("feishu") or {}
244
+ feishu_config = _build_feishu_config()
245
+ if fs_override:
246
+ # Allow caller to force enabled/disabled even when env vars are
247
+ # missing (useful for tests).
248
+ feishu_config.update(fs_override)
249
+ channels["feishu"] = feishu_config
250
+
251
+ # ── Top-level ───────────────────────────────────────────────────────
252
+ # v3.1.0: MCP servers removed from auto-generated config.
253
+ # QuantTools are registered directly on the agent's ToolRegistry
254
+ # via ``register_all_quant_tools()`` in nanobot_runtime.py.
255
+ # The MCP server can be started independently for external clients:
256
+ # python -m QuantNodes.mcp_server --transport http --port 8765
257
+ # Or via ``quantnodes serve --mcp`` (optional subprocess).
258
+ config: Dict[str, Any] = {
259
+ "agents": {
260
+ "defaults": {
261
+ "workspace": str(workspace),
262
+ "model": model,
263
+ "provider": slot,
264
+ },
265
+ },
266
+ "providers": {
267
+ slot: provider_block,
268
+ },
269
+ "channels": channels,
270
+ }
271
+
272
+ if "max_tokens" in user_config:
273
+ config["agents"]["defaults"]["maxTokens"] = int(user_config["max_tokens"])
274
+
275
+ return config
276
+
277
+
278
+ def write_nanobot_config(workspace: Path, config: Dict[str, Any]) -> Path:
279
+ """Persist ``config`` as JSON inside ``workspace`` and return the path."""
280
+ workspace.mkdir(parents=True, exist_ok=True)
281
+ target = workspace / CONFIG_FILENAME
282
+ target.write_text(json.dumps(config, indent=2, ensure_ascii=False), encoding="utf-8")
283
+ logger.info("Wrote nanobot config: %s", target)
284
+ return target
285
+
286
+
287
+ __all__ = [
288
+ "build_nanobot_config",
289
+ "write_nanobot_config",
290
+ "CONFIG_FILENAME",
291
+ "_build_websocket_config",
292
+ "_build_feishu_config",
293
+ ]
@@ -0,0 +1,19 @@
1
+ # coding=utf-8
2
+ """
3
+ 核心引擎模块 (v3.0.0 精简版)
4
+
5
+ v3.0.0 之前本目录包含自写的 loop/runner/memory/dream/...,已全部由
6
+ HKUDS/nanobot 0.2.1 上游替代。
7
+
8
+ 当前保留:
9
+ - quant_dream.py - 量化专属 Dream 钩子(QuantDreamHook)
10
+ - dream.py - 向后兼容 shim
11
+ """
12
+
13
+ from .quant_dream import DreamEngine, QuantDreamHook, QuantDreamInsight
14
+
15
+ __all__ = [
16
+ "DreamEngine",
17
+ "QuantDreamHook",
18
+ "QuantDreamInsight",
19
+ ]
@@ -0,0 +1,47 @@
1
+ # coding=utf-8
2
+ """
3
+ 向后兼容 shim — 量化专属 Dream 引擎。
4
+
5
+ v3.0.0 起,Dream 相关实现已迁移到 ``QuantNodes.agent.core.quant_dream``。
6
+ 本模块保留旧导入路径 ``from QuantNodes.agent.core.dream import ...`` 的可用性。
7
+
8
+ 替代映射:
9
+ - ``DreamEngine`` -> ``QuantNodes.agent.core.quant_dream.DreamEngine``
10
+ - ``DreamStore`` -> 已删除(由 nanobot 上游 ``MemoryStore`` 替代,见 .agent/memory/)
11
+ - ``MemoryStore`` -> 已删除(由 nanobot 上游 ``nanobot.agent.memory.MemoryStore`` 替代)
12
+ - ``MemoryManager`` -> 已删除(迁移到 nanobot 的 MEMORY.md/SOUL.md 文件系统约定)
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import warnings
18
+
19
+ from .quant_dream import DreamEngine, QuantDreamHook, QuantDreamInsight
20
+
21
+ warnings.warn(
22
+ "QuantNodes.agent.core.dream is a backward-compatibility shim. "
23
+ "Import from QuantNodes.agent.core.quant_dream instead. "
24
+ "DreamStore/MemoryStore/MemoryManager have been replaced by the "
25
+ "upstream nanobot memory subsystem.",
26
+ DeprecationWarning,
27
+ stacklevel=2,
28
+ )
29
+
30
+
31
+ class _RemovedSymbol:
32
+ """Sentinel for symbols removed in v3.0.0 (nanobot upstream replacement)."""
33
+
34
+
35
+ DreamStore = _RemovedSymbol
36
+ MemoryStore = _RemovedSymbol
37
+ MemoryManager = _RemovedSymbol
38
+
39
+
40
+ __all__ = [
41
+ "DreamEngine",
42
+ "DreamStore",
43
+ "MemoryStore",
44
+ "MemoryManager",
45
+ "QuantDreamHook",
46
+ "QuantDreamInsight",
47
+ ]
@@ -0,0 +1,274 @@
1
+ # coding=utf-8
2
+ """QuantDreamHook - 量化专属 Dream 钩子(v3.0.0 新增)。
3
+
4
+ 挂在 HKUDS nanobot 的 AgentHook 系统上,扩展通用 Dream 整合(上游
5
+ nanobot/agent/memory.py::Dream + Consolidator)以覆盖量化场景:
6
+
7
+ 1. **因子洞察** - 提取 IC 表现好的因子,反思构造逻辑
8
+ 2. **回测模式** - 记录过拟合/未来函数/手续费过高等问题模式
9
+ 3. **策略启发** - 跨策略对比,识别共同成功因素
10
+ 4. **风险事件** - 大回撤/极端行情下的策略表现
11
+ 5. **代码模式** - LLM 生成的常见代码 bug
12
+
13
+ 输出:``.agent/memory/topic-quant-dream.md``
14
+
15
+ 向后兼容:
16
+ - ``QuantNodes.agent.core.dream.DreamEngine`` 仍可用(re-export 自本模块)
17
+ - 旧 ``QuantNodes.agent.core.dream.DreamStore`` 由 nanobot upstream 替代
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import logging
24
+ import re
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Any, Dict, List, Optional
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ FACTOR_KEYWORDS = (
33
+ "alpha", "factor", "ic", "rank", "momentum", "reversal",
34
+ "value", "quality", "volatility",
35
+ )
36
+ BACKTEST_KEYWORDS = (
37
+ "backtest", "sharpe", "drawdown", "annualized", "win_rate",
38
+ "profit_loss_ratio", "icir",
39
+ )
40
+ STRATEGY_KEYWORDS = (
41
+ "strategy", "pipeline", "weight", "rebalance", "portfolio",
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class QuantDreamInsight:
47
+ """Single quant dream entry."""
48
+
49
+ id: str = ""
50
+ type: str = ""
51
+ content: str = ""
52
+ insights: List[str] = field(default_factory=list)
53
+ confidence: float = 0.7
54
+ tags: List[str] = field(default_factory=list)
55
+ timestamp: str = ""
56
+ source: str = "quant_dream"
57
+
58
+ def __post_init__(self):
59
+ if not self.id:
60
+ import uuid
61
+ self.id = f"dream-{uuid.uuid4().hex[:12]}"
62
+
63
+ def to_markdown(self) -> str:
64
+ lines = [f"### {self.timestamp[:10] or 'unknown'} - {self.type}", f"- {self.content}"]
65
+ for insight in self.insights:
66
+ lines.append(f" - {insight}")
67
+ if self.tags:
68
+ lines.append(f" - tags: {', '.join(self.tags)}")
69
+ return "\n".join(lines) + "\n"
70
+
71
+
72
+ class QuantDreamHook:
73
+ """QuantNodes 专属 Dream 钩子,注入到 nanobot 的 _extra_hooks 列表。
74
+
75
+ Usage::
76
+
77
+ from QuantNodes.agent.core.quant_dream import QuantDreamHook
78
+ from QuantNodes.agent import Agent
79
+ agent = Agent(workspace=".agent")
80
+ agent.loop._extra_hooks.append(QuantDreamHook(agent.workspace))
81
+ """
82
+
83
+ DEFAULT_INTERVAL = 10
84
+ DEFAULT_MIN_CONFIDENCE = 0.5
85
+
86
+ def __init__(
87
+ self,
88
+ workspace: Path,
89
+ interval: int = DEFAULT_INTERVAL,
90
+ min_confidence: float = DEFAULT_MIN_CONFIDENCE,
91
+ ):
92
+ self.workspace = Path(workspace).expanduser().resolve()
93
+ self.memory_dir = self.workspace / "memory"
94
+ self.memory_dir.mkdir(parents=True, exist_ok=True)
95
+ self.topic_file = self.memory_dir / "topic-quant-dream.md"
96
+ self.interval = interval
97
+ self.min_confidence = min_confidence
98
+ self._counter: Dict[str, int] = {}
99
+
100
+ def should_analyze(self, user_content: str, assistant_content: str) -> bool:
101
+ """Return True iff the conversation touches any quant topic.
102
+
103
+ Uses word-boundary regex matching to avoid false positives like
104
+ ``"ic" in "nice"`` or ``"day" in "today"``.
105
+ """
106
+ import re
107
+
108
+ combined = (user_content + " " + assistant_content).lower()
109
+ for kw in FACTOR_KEYWORDS + BACKTEST_KEYWORDS + STRATEGY_KEYWORDS:
110
+ if re.search(rf"\b{re.escape(kw)}\b", combined):
111
+ return True
112
+ return False
113
+
114
+ def analyze_session(
115
+ self,
116
+ session_key: str,
117
+ user_content: str,
118
+ assistant_content: str,
119
+ ) -> Optional[QuantDreamInsight]:
120
+ """Run quant-specific analysis on a completed session turn.
121
+
122
+ Lightweight heuristic — no LLM call. Returns ``None`` if no insight.
123
+ """
124
+ if not self.should_analyze(user_content, assistant_content):
125
+ return None
126
+
127
+ kind = self._classify(user_content + " " + assistant_content)
128
+ content, insights = self._extract_insights(user_content, assistant_content, kind)
129
+
130
+ if not insights:
131
+ return None
132
+
133
+ from datetime import datetime, timezone
134
+ return QuantDreamInsight(
135
+ type=kind,
136
+ content=content,
137
+ insights=insights,
138
+ confidence=0.7,
139
+ tags=[kind, "auto"],
140
+ timestamp=datetime.now(timezone.utc).isoformat(),
141
+ source="quant_dream",
142
+ )
143
+
144
+ def append(self, dream: QuantDreamInsight) -> None:
145
+ """Append an insight to the topic file (idempotent append)."""
146
+ if dream.confidence < self.min_confidence:
147
+ return
148
+ existing = self.topic_file.read_text(encoding="utf-8") if self.topic_file.exists() else "# Quant Dream Insights\n\n"
149
+ self.topic_file.write_text(existing + dream.to_markdown(), encoding="utf-8")
150
+ logger.info("Quant dream appended: type=%s", dream.type)
151
+
152
+ def get_recent_dreams(self, limit: int = 20) -> List[QuantDreamInsight]:
153
+ """Parse the topic file and return the most recent ``limit`` insights.
154
+
155
+ Lightweight parser — sufficient for the API listing endpoint.
156
+ """
157
+ if not self.topic_file.exists():
158
+ return []
159
+
160
+ text = self.topic_file.read_text(encoding="utf-8")
161
+ dreams: List[QuantDreamInsight] = []
162
+ current_type = ""
163
+ current_content = ""
164
+ current_insights: List[str] = []
165
+ current_tags: List[str] = []
166
+ current_date = ""
167
+
168
+ for raw_line in text.splitlines():
169
+ line = raw_line.rstrip()
170
+ if line.startswith("### "):
171
+ if current_content or current_insights:
172
+ dreams.append(self._build(
173
+ current_date, current_type, current_content,
174
+ current_insights, current_tags,
175
+ ))
176
+ current_date = line[4:14] if len(line) >= 14 else ""
177
+ rest = line[4:].split(" - ", 1)
178
+ current_type = rest[1] if len(rest) > 1 else rest[0]
179
+ current_content = ""
180
+ current_insights = []
181
+ current_tags = []
182
+ elif line.startswith("- "):
183
+ current_content = line[2:]
184
+ elif line.startswith(" - "):
185
+ item = line[4:]
186
+ if item.startswith("tags: "):
187
+ current_tags = [t.strip() for t in item[6:].split(",") if t.strip()]
188
+ else:
189
+ current_insights.append(item)
190
+
191
+ if current_content or current_insights:
192
+ dreams.append(self._build(
193
+ current_date, current_type, current_content,
194
+ current_insights, current_tags,
195
+ ))
196
+
197
+ return list(reversed(dreams[-limit:]))
198
+
199
+ @staticmethod
200
+ def _build(date: str, type_: str, content: str, insights: List[str], tags: List[str]) -> QuantDreamInsight:
201
+ return QuantDreamInsight(
202
+ id=f"dream-{hash((date, type_, content)) & 0xffffffffffff:012x}",
203
+ type=type_,
204
+ content=content,
205
+ insights=insights,
206
+ tags=tags,
207
+ confidence=0.7,
208
+ timestamp=f"{date}T00:00:00Z" if date else "",
209
+ source="quant_dream",
210
+ )
211
+
212
+ def tick(self, session_key: str) -> None:
213
+ """Increment session counter; returns whether to fire analysis this round."""
214
+ n = self._counter.get(session_key, 0) + 1
215
+ self._counter[session_key] = n
216
+ return None # analysis is triggered externally via analyze_session()
217
+
218
+ @staticmethod
219
+ def _classify(text: str) -> str:
220
+ lc = text.lower()
221
+ if any(kw in lc for kw in FACTOR_KEYWORDS):
222
+ return "factor_insight"
223
+ if any(kw in lc for kw in BACKTEST_KEYWORDS):
224
+ return "backtest_pattern"
225
+ if any(kw in lc for kw in STRATEGY_KEYWORDS):
226
+ return "strategy_heuristic"
227
+ return "general"
228
+
229
+ @staticmethod
230
+ def _extract_insights(user: str, assistant: str, kind: str) -> tuple:
231
+ first_sentence = re.split(r"[.!?\n]", assistant.strip(), maxsplit=1)[0][:200]
232
+ return (first_sentence or f"{kind} analysis", [assistant[:300]])
233
+
234
+
235
+ class DreamEngine:
236
+ """向后兼容 shim — 旧 ``QuantNodes.agent.core.dream.DreamEngine`` API。
237
+
238
+ 委托到 QuantDreamHook。保留名称以避免破坏 api/services/dream_service.py。
239
+ """
240
+
241
+ def __init__(self, workspace: Path | str = None, *, dream_store=None):
242
+ # Accept legacy ``dream_store=`` kwarg for backward compatibility.
243
+ # The actual store (nanobot MemoryStore) replaces this in v3.0.0;
244
+ # we only need a workspace path for QuantDreamHook.
245
+ if workspace is None and dream_store is not None:
246
+ workspace = getattr(dream_store, "workspace", None) or getattr(dream_store, "memory_dir", None)
247
+ if workspace is None:
248
+ workspace = Path(".agent")
249
+ self.workspace = Path(workspace)
250
+ self.hook = QuantDreamHook(self.workspace)
251
+
252
+ def analyze_conversation(self, user_message: str, assistant_response: str) -> Optional[QuantDreamInsight]:
253
+ return self.hook.analyze_session("default", user_message, assistant_response)
254
+
255
+ def generate_dream(self, dream_type: str, content: str, source: str = "",
256
+ insights=None, confidence: float = 0.7, tags=None) -> QuantDreamInsight:
257
+ from datetime import datetime, timezone
258
+ dream = QuantDreamInsight(
259
+ type=dream_type, content=content, insights=list(insights or []),
260
+ confidence=confidence, tags=list(tags or []), source=source,
261
+ timestamp=datetime.now(timezone.utc).isoformat(),
262
+ )
263
+ self.hook.append(dream)
264
+ return dream
265
+
266
+
267
+ __all__ = [
268
+ "QuantDreamHook",
269
+ "QuantDreamInsight",
270
+ "DreamEngine",
271
+ "FACTOR_KEYWORDS",
272
+ "BACKTEST_KEYWORDS",
273
+ "STRATEGY_KEYWORDS",
274
+ ]