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,1155 @@
1
+ # coding=utf-8
2
+ """WikiFactorProxy - Wiki 因子库代理层"""
3
+
4
+ from dataclasses import dataclass, field
5
+ from datetime import datetime
6
+ from enum import Enum
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from llmwikify import Wiki, create_wiki
10
+
11
+ from QuantNodes.core.base import FactorError
12
+
13
+
14
+ class FactorSource(Enum):
15
+ RESEARCH_REPORT = "research_report"
16
+ AUTO_RESEARCH = "auto_research"
17
+ MANUAL = "manual"
18
+ DERIVED = "derived"
19
+ IMPORTED = "imported"
20
+
21
+
22
+ class FactorCategory(Enum):
23
+ MOMENTUM = "momentum"
24
+ VALUE = "value"
25
+ QUALITY = "quality"
26
+ VOLATILITY = "volatility"
27
+ SIZE = "size"
28
+ GROWTH = "growth"
29
+ OTHER = "other"
30
+
31
+
32
+ class LogicSource(Enum):
33
+ RESEARCH_REPORT = "research_report"
34
+ MANUAL = "manual"
35
+
36
+
37
+ QUANT_RELATION_TYPES = {
38
+ "uses",
39
+ "correlates_with",
40
+ "derived_from",
41
+ "outperforms",
42
+ "underperforms",
43
+ "similar_to",
44
+ "contradicts",
45
+ "supports",
46
+ "related_to",
47
+ }
48
+
49
+
50
+ @dataclass
51
+ class WikiFactor:
52
+ name: str
53
+ formula: str
54
+ source: FactorSource
55
+ category: FactorCategory
56
+ description: str = ""
57
+ tags: List[str] = field(default_factory=list)
58
+ ic_mean: Optional[float] = None
59
+ ic_std: Optional[float] = None
60
+ icir: Optional[float] = None
61
+ rank_ic_mean: Optional[float] = None
62
+ n_dates: Optional[int] = None
63
+ factor_return_corr: Optional[float] = None
64
+ ic_t_stat: Optional[float] = None
65
+ turnover: Optional[float] = None
66
+ group_returns: Optional[List[Dict]] = None
67
+ used_by_strategies: List[str] = field(default_factory=list)
68
+ strategy_yaml: Optional[str] = None
69
+ wiki_page_name: Optional[str] = None
70
+ created_at: Optional[str] = None
71
+ updated_at: Optional[str] = None
72
+ metadata: Dict[str, Any] = field(default_factory=dict)
73
+
74
+
75
+ @dataclass
76
+ class WikiLogic:
77
+ name: str
78
+ content: str
79
+ source: LogicSource
80
+ extracted_formula: Optional[str] = None
81
+ source_detail: Dict[str, str] = field(default_factory=dict)
82
+ related_strategies: List[str] = field(default_factory=list)
83
+ related_factors: List[str] = field(default_factory=list)
84
+ validation_status: str = "pending"
85
+ wiki_page_name: Optional[str] = None
86
+ created_at: Optional[str] = None
87
+ metadata: Dict[str, Any] = field(default_factory=dict)
88
+
89
+ # === 新增:结构化字段(PR-1/4 向后兼容,全部 Optional) ===
90
+ structured: Optional[Any] = None # WikiLogicStructured (避免循环导入)
91
+ performance_evidence: Optional[Any] = None # LogicPerformanceEvidence
92
+ parent_logic: Optional[str] = None # 衍生自的逻辑名(用于追溯重构链)
93
+ refinement_round: int = 0 # 第几轮外层优化生成/重构
94
+
95
+ def to_structured_dict(self) -> Dict[str, Any]:
96
+ """序列化为字典(便于 JSON 持久化)"""
97
+ return {
98
+ "name": self.name,
99
+ "content": self.content,
100
+ "source": self.source.value if hasattr(self.source, "value") else str(self.source),
101
+ "extracted_formula": self.extracted_formula,
102
+ "validation_status": self.validation_status,
103
+ "parent_logic": self.parent_logic,
104
+ "refinement_round": self.refinement_round,
105
+ "structured": self.structured.to_dict() if self.structured else None,
106
+ "performance_evidence": (
107
+ self.performance_evidence.to_dict()
108
+ if self.performance_evidence and hasattr(self.performance_evidence, "to_dict")
109
+ else self.performance_evidence
110
+ ),
111
+ }
112
+
113
+ @classmethod
114
+ def from_structured_dict(cls, data: Dict[str, Any]) -> "WikiLogic":
115
+ """从字典创建(用于反序列化)"""
116
+ from QuantNodes.research.quant_alpha.logic_mining.models import (
117
+ LogicBehavior,
118
+ LogicCondition,
119
+ LogicPerformanceEvidence,
120
+ WikiLogicStructured,
121
+ )
122
+
123
+ structured = None
124
+ if data.get("structured"):
125
+ try:
126
+ s = data["structured"]
127
+ structured = WikiLogicStructured.from_dict(s)
128
+ except Exception:
129
+ structured = None
130
+
131
+ evidence = None
132
+ if data.get("performance_evidence"):
133
+ try:
134
+ evidence = LogicPerformanceEvidence.from_dict(data["performance_evidence"])
135
+ except Exception:
136
+ evidence = None
137
+
138
+ try:
139
+ source = LogicSource(data.get("source", "research_report"))
140
+ except ValueError:
141
+ source = LogicSource.RESEARCH_REPORT
142
+
143
+ return cls(
144
+ name=data["name"],
145
+ content=data.get("content", ""),
146
+ source=source,
147
+ extracted_formula=data.get("extracted_formula"),
148
+ validation_status=data.get("validation_status", "pending"),
149
+ structured=structured,
150
+ performance_evidence=evidence,
151
+ parent_logic=data.get("parent_logic"),
152
+ refinement_round=data.get("refinement_round", 0),
153
+ )
154
+
155
+
156
+ @dataclass
157
+ class WikiStrategy:
158
+ name: str
159
+ strategy_yaml: str
160
+ description: str = ""
161
+ category: str = "general"
162
+ tags: List[str] = field(default_factory=list)
163
+ backtest_result: Optional[Dict] = None
164
+ created_at: Optional[str] = None
165
+ wiki_page_name: Optional[str] = None
166
+ metadata: Dict[str, Any] = field(default_factory=dict)
167
+
168
+
169
+ @dataclass
170
+ class WikiReproduction:
171
+ report_title: str
172
+ pdf_path: str = ""
173
+ verified_count: int = 0
174
+ failed_count: int = 0
175
+ report_markdown: str = ""
176
+ created_at: Optional[str] = None
177
+ wiki_page_name: Optional[str] = None
178
+ metadata: Dict[str, Any] = field(default_factory=dict)
179
+
180
+
181
+ class WikiProxyError(FactorError):
182
+ code = "WIKI_PROXY_ERROR"
183
+
184
+ def __init__(self, message: str, details: Dict = None):
185
+ super().__init__(message)
186
+ self.details = details or {}
187
+
188
+
189
+ def init_factor_wiki(wiki_path: str, force: bool = False) -> None:
190
+ wiki = create_wiki(wiki_path)
191
+ if not force and wiki.root.exists():
192
+ pass
193
+ else:
194
+ wiki.init()
195
+ wiki_md = """# QuantNodes Strategy Wiki 配置
196
+
197
+ > 本 Wiki 专为量化策略研究设计,用于存储因子、策略、研报逻辑等知识。
198
+
199
+ ## Page Types
200
+
201
+ | Directory | Description |
202
+ |----------|-------------|
203
+ | Factor | 验证有效的因子(通过回测验证) |
204
+ | Logic | 从研报提取的因子逻辑/公式 |
205
+ | Strategy | 策略配置(参数、因子组合、回测设置) |
206
+ | Reproduction | 研报复现对比报告 |
207
+
208
+ ## Relation Types
209
+
210
+ | Relation | Description |
211
+ |----------|-------------|
212
+ | uses | 策略使用因子 |
213
+ | correlates_with | 因子之间相关性 |
214
+ | derived_from | 因子来源于研报逻辑 |
215
+ | related_to | 通用关联 |
216
+ | outperforms | 策略A优于策略B |
217
+ | similar_to | 相似策略/因子 |
218
+ | contradicts | 矛盾/负相关发现 |
219
+ | supports | 回测结果支持策略假设 |
220
+ | validated | 因子已通过回测验证 |
221
+
222
+ ## 操作流程(init 之后)
223
+
224
+ ### 1. 因子研究流程
225
+ ```
226
+ 1. 读取研报 → 提取逻辑 → 写入 wiki/Logic/{topic}.md
227
+ 2. 设计因子 → 配置参数 → 写入 wiki/Factor/{name}.md
228
+ 3. 编写策略 → 使用因子 → 写入 wiki/Strategy/{name}.md
229
+ 4. 运行回测 → 生成报告 → 写入 wiki/Reproduction/{name}.md
230
+ 5. 添加关系 → 连接因子/策略/逻辑
231
+ ```
232
+
233
+ ### 2. 写入因子示例
234
+ ```python
235
+ from QuantNodes.research.wiki import WikiFactorProxy
236
+
237
+ proxy = WikiFactorProxy(wiki_path="wiki")
238
+ factor = WikiFactor(
239
+ name="momentum_20d",
240
+ formula="rank(corr(rank(close), rank(time), 20))",
241
+ category=WikiFactorCategory.MOMENTUM,
242
+ source="研报/某券商Alpha研究.pdf",
243
+ description="20日动量因子"
244
+ )
245
+ proxy.store_factor(factor)
246
+ ```
247
+
248
+ ### 3. 写入策略示例
249
+ ```python
250
+ strategy = WikiStrategy(
251
+ name="momentum_alpha_v1",
252
+ factors=["momentum_20d", "volume_ratio_5d"],
253
+ weight_method="equal_weight",
254
+ rebalance="monthly"
255
+ )
256
+ proxy.store_strategy(strategy)
257
+ ```
258
+
259
+ ## Page Format Examples
260
+
261
+ ### Factor Page
262
+ ```markdown
263
+ ---
264
+ title: momentum_20d
265
+ type: factor
266
+ created: 2026-05-09
267
+ updated: 2026-05-09
268
+ sources: [raw/alpha_research.pdf]
269
+ tags: [momentum, time_series]
270
+ ---
271
+
272
+ # momentum_20d
273
+
274
+ ## 因子公式
275
+ rank(corr(rank(close), rank(time), 20))
276
+
277
+ ## 描述
278
+ 20日动量因子,衡量过去20天的价格动量效应
279
+
280
+ ## 验证结果
281
+ - IC: 0.05 (样本内), 0.03 (样本外)
282
+ - 回测年化收益: 12.3%
283
+ - 最大回撤: -8.5%
284
+
285
+ ## 来源
286
+ - [Source: Alpha研究.pdf](raw/alpha_research.pdf)
287
+
288
+ ## 关联
289
+ - uses: [[logic/momentum_theory]]
290
+ - similar_to: [[factor/momentum_60d]]
291
+ ```
292
+
293
+ ### Strategy Page
294
+ ```markdown
295
+ ---
296
+ title: momentum_alpha_v1
297
+ type: strategy
298
+ created: 2026-05-09
299
+ updated: 2026-05-09
300
+ tags: [momentum, equal_weight]
301
+ ---
302
+
303
+ # momentum_alpha_v1
304
+
305
+ ## 策略描述
306
+ 基于动量因子的等权重组合策略
307
+
308
+ ## 因子组合
309
+ - momentum_20d (权重: 0.5)
310
+ - momentum_60d (权重: 0.5)
311
+
312
+ ## 回测设置
313
+ - 标的: 全市场 A 股
314
+ - 频率: 月度调仓
315
+ - 手续费: 万三
316
+
317
+ ## 回测结果
318
+ - 年化收益: 15.2%
319
+ - 夏普比率: 1.8
320
+ - 最大回撤: -12.3%
321
+
322
+ ## 关联
323
+ - uses: [[factor/momentum_20d]], [[factor/momentum_60d]]
324
+ - derived_from: [[logic/momentum_theory]]
325
+ ```
326
+
327
+ ### Reproduction Page
328
+ ```markdown
329
+ ---
330
+ title: 研报复现_海通Alpha动量
331
+ type: reproduction
332
+ created: 2026-05-09
333
+ updated: 2026-05-09
334
+ sources: [raw/ht_alpha_momentum.pdf]
335
+ ---
336
+
337
+ # 研报复现_海通Alpha动量
338
+
339
+ ## 研报信息
340
+ - 标题: Alpha动量因子研究
341
+ - 机构: 海通证券
342
+ - 日期: 2025-12
343
+
344
+ ## 复现结果
345
+ | 指标 | 研报结果 | 复现结果 | 差异 |
346
+ |------|----------|----------|------|
347
+ | IC | 0.062 | 0.058 | -6.5% |
348
+ | 年化收益 | 18.5% | 16.2% | -12.4% |
349
+
350
+ ## 差异分析
351
+ 1. 样本期间差异(研报2019-2024,复现2020-2025)
352
+ 2. 因子计算细节略有不同
353
+
354
+ ## 结论
355
+ 基本复现成功,差异在可接受范围内
356
+
357
+ ## 关联
358
+ - derived_from: [[logic/momentum_ht]]
359
+ - validates: [[factor/momentum_20d]]
360
+ ```
361
+
362
+ ## 核心工具
363
+
364
+ | 操作 | API |
365
+ |------|-----|
366
+ | 存储因子 | `proxy.store_factor(factor)` |
367
+ | 获取因子 | `proxy.get_factor(name)` |
368
+ | 存储策略 | `proxy.store_strategy(strategy)` |
369
+ | 添加关系 | `proxy.add_relation(from, relation, to)` |
370
+ | 搜索 | `proxy.search_factors(query)` |
371
+
372
+ ## 最佳实践
373
+
374
+ 1. **先写Logic再写Factor** - 从研报提取逻辑,验证后再创建因子
375
+ 2. **回测验证后再存储** - Factor页面应包含验证结果
376
+ 3. **策略引用因子** - Strategy页面使用wikilink引用Factor
377
+ 4. **记录复现过程** - Reproduction页面详细记录差异分析
378
+ 5. **定期更新** - 市场变化后更新因子表现
379
+ """
380
+ wiki.write_page("wiki", wiki_md)
381
+
382
+
383
+ class WikiFactorProxy:
384
+ PAGE_TYPE_FACTOR = "Factor"
385
+ PAGE_TYPE_LOGIC = "Logic"
386
+ PAGE_TYPE_STRATEGY = "Strategy"
387
+ PAGE_TYPE_REPRODUCTION = "Reproduction"
388
+
389
+ def __init__(self, wiki_path: str):
390
+ self.wiki_path = wiki_path
391
+ self._wiki: Optional[Wiki] = None
392
+
393
+ @property
394
+ def wiki(self) -> Wiki:
395
+ if self._wiki is None:
396
+ self._wiki = create_wiki(self.wiki_path)
397
+ if not self._wiki.root.exists():
398
+ self._wiki.init()
399
+ return self._wiki
400
+
401
+ def store_factor(self, factor: WikiFactor) -> str:
402
+ page_name = f"{self.PAGE_TYPE_FACTOR}/{factor.name}"
403
+ content = self._render_factor_markdown(factor)
404
+ self.wiki.write_page(page_name, content)
405
+ factor.wiki_page_name = page_name
406
+ return page_name
407
+
408
+ def get_factor(self, name: str) -> Optional[WikiFactor]:
409
+ page_name = f"{self.PAGE_TYPE_FACTOR}/{name}"
410
+ page_file = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR / f'{name}.md'
411
+ if not page_file.exists():
412
+ return None
413
+ try:
414
+ page_data = self.wiki.read_page(page_name)
415
+ except Exception:
416
+ return None
417
+ return self._parse_factor_from_page(page_name, page_data)
418
+
419
+ def search_factors(self, query: str, limit: int = 10) -> List[WikiFactor]:
420
+ results = self.wiki.search(query, limit=limit)
421
+ factors = []
422
+ for r in results:
423
+ pn = r.get("page_name", "")
424
+ if pn.startswith(f"{self.PAGE_TYPE_FACTOR}/"):
425
+ try:
426
+ page_data = self.wiki.read_page(pn)
427
+ factors.append(self._parse_factor_from_page(pn, page_data))
428
+ except Exception:
429
+ continue
430
+ return factors
431
+
432
+ def list_factors(self, source=None, category=None, tags=None, limit=50) -> List[WikiFactor]:
433
+ factors = []
434
+ page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR
435
+ if not page_type_dir.exists():
436
+ return factors
437
+ for md_file in list(page_type_dir.glob('*.md'))[:limit]:
438
+ page_name = f"{self.PAGE_TYPE_FACTOR}/{md_file.stem}"
439
+ try:
440
+ page_data = self.wiki.read_page(page_name)
441
+ factor = self._parse_factor_from_page(page_name, page_data)
442
+ if factor is None:
443
+ continue
444
+ if source and factor.source != source:
445
+ continue
446
+ if category and factor.category != category:
447
+ continue
448
+ if tags and not any(t in factor.tags for t in tags):
449
+ continue
450
+ factors.append(factor)
451
+ except Exception:
452
+ continue
453
+ return factors
454
+
455
+ def update_factor(self, name: str, updates: Dict) -> bool:
456
+ factor = self.get_factor(name)
457
+ if factor is None:
458
+ return False
459
+ for key, value in updates.items():
460
+ if hasattr(factor, key):
461
+ setattr(factor, key, value)
462
+ factor.updated_at = datetime.now().isoformat()
463
+ self.store_factor(factor)
464
+ return True
465
+
466
+ def delete_factor(self, name: str) -> bool:
467
+ page_file = self.wiki.wiki_dir / self.PAGE_TYPE_FACTOR / f'{name}.md'
468
+ if page_file.exists():
469
+ page_file.unlink()
470
+ self.wiki.build_index()
471
+ return True
472
+ return False
473
+
474
+ def store_logic(self, logic: WikiLogic) -> str:
475
+ page_name = f"{self.PAGE_TYPE_LOGIC}/{logic.name}"
476
+ content = self._render_logic_markdown(logic)
477
+ self.wiki.write_page(page_name, content)
478
+ logic.wiki_page_name = page_name
479
+ return page_name
480
+
481
+ def get_logic(self, name: str) -> Optional[WikiLogic]:
482
+ page_name = f"{self.PAGE_TYPE_LOGIC}/{name}"
483
+ page_file = self.wiki.wiki_dir / self.PAGE_TYPE_LOGIC / f'{name}.md'
484
+ if not page_file.exists():
485
+ return None
486
+ try:
487
+ page_data = self.wiki.read_page(page_name)
488
+ except Exception:
489
+ return None
490
+ return self._parse_logic_from_page(page_name, page_data)
491
+
492
+ def search_logics(self, query: str, limit: int = 10) -> List[WikiLogic]:
493
+ results = self.wiki.search(query, limit=limit)
494
+ logics = []
495
+ for r in results:
496
+ pn = r.get("page_name", "")
497
+ if pn.startswith(f"{self.PAGE_TYPE_LOGIC}/"):
498
+ try:
499
+ page_data = self.wiki.read_page(pn)
500
+ logics.append(self._parse_logic_from_page(pn, page_data))
501
+ except Exception:
502
+ continue
503
+ return logics
504
+
505
+ def list_logics(
506
+ self,
507
+ validated_only: bool = False,
508
+ limit: int = 100,
509
+ ) -> List[WikiLogic]:
510
+ """列出所有 Logic
511
+
512
+ Args:
513
+ validated_only: 仅返回已验证的逻辑
514
+ limit: 最大数量
515
+
516
+ Returns:
517
+ List of WikiLogic
518
+ """
519
+ page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_LOGIC
520
+ if not page_type_dir.exists():
521
+ return []
522
+
523
+ logics = []
524
+ for md_file in list(page_type_dir.glob("*.md"))[:limit]:
525
+ try:
526
+ logic = self.get_logic(md_file.stem)
527
+ if logic is None:
528
+ continue
529
+ if validated_only and logic.validation_status != "validated":
530
+ continue
531
+ logics.append(logic)
532
+ except Exception:
533
+ continue
534
+ return logics
535
+
536
+ def update_logic_evidence(
537
+ self,
538
+ name: str,
539
+ evidence: Any,
540
+ ) -> bool:
541
+ """更新逻辑的回测证据
542
+
543
+ Args:
544
+ name: 逻辑名称
545
+ evidence: LogicPerformanceEvidence 实例
546
+
547
+ Returns:
548
+ True 表示更新成功
549
+ """
550
+ logic = self.get_logic(name)
551
+ if logic is None:
552
+ return False
553
+
554
+ logic.performance_evidence = evidence
555
+ self.store_logic(logic)
556
+ return True
557
+
558
+ def search_logics_by_predicate(
559
+ self,
560
+ variable: Optional[str] = None,
561
+ op: Optional[str] = None,
562
+ ) -> List[WikiLogic]:
563
+ """按谓词查询逻辑
564
+
565
+ Args:
566
+ variable: 市场变量名(可选)
567
+ op: 算子名(可选)
568
+
569
+ Returns:
570
+ 匹配的 WikiLogic 列表
571
+ """
572
+ all_logics = self.list_logics(limit=1000)
573
+ results = []
574
+
575
+ for logic in all_logics:
576
+ if logic.structured is None:
577
+ continue
578
+ match = True
579
+ if variable:
580
+ variables = logic.structured.get_variables()
581
+ if variable not in variables:
582
+ match = False
583
+ if op and match:
584
+ operators = logic.structured.get_operators()
585
+ if op not in operators:
586
+ match = False
587
+ if match:
588
+ results.append(logic)
589
+
590
+ return results
591
+
592
+ def add_relation(self, source_name: str, target_name: str, relation: str) -> bool:
593
+ if relation not in QUANT_RELATION_TYPES:
594
+ raise WikiProxyError(f"Invalid relation type: {relation}")
595
+ relation_entry = {
596
+ "source": source_name,
597
+ "target": target_name,
598
+ "relation": relation,
599
+ "confidence": "EXTRACTED",
600
+ }
601
+ self.wiki.write_relations([relation_entry])
602
+ return True
603
+
604
+ def get_neighbors(self, name: str) -> List[Dict]:
605
+ engine = self.wiki.get_relation_engine()
606
+ try:
607
+ return engine.get_neighbors(name, direction='both')
608
+ except Exception:
609
+ return []
610
+
611
+ def ping(self) -> bool:
612
+ try:
613
+ self.wiki.lint()
614
+ return True
615
+ except Exception:
616
+ return False
617
+
618
+ def status(self) -> Dict:
619
+ try:
620
+ return self.wiki.status()
621
+ except Exception as e:
622
+ return {"error": str(e)}
623
+
624
+ def store_strategy(self, strategy: WikiStrategy) -> str:
625
+ page_name = f"{self.PAGE_TYPE_STRATEGY}/{strategy.name}"
626
+ content = self._render_strategy_markdown(strategy)
627
+ self.wiki.write_page(page_name, content)
628
+ strategy.wiki_page_name = page_name
629
+ return page_name
630
+
631
+ def get_strategy(self, name: str) -> Optional[WikiStrategy]:
632
+ page_name = f"{self.PAGE_TYPE_STRATEGY}/{name}"
633
+ page_file = self.wiki.wiki_dir / self.PAGE_TYPE_STRATEGY / f'{name}.md'
634
+ if not page_file.exists():
635
+ return None
636
+ try:
637
+ page_data = self.wiki.read_page(page_name)
638
+ except Exception:
639
+ return None
640
+ return self._parse_strategy_from_page(page_name, page_data)
641
+
642
+ def list_strategies(
643
+ self,
644
+ category: Optional[str] = None,
645
+ tags: Optional[List[str]] = None,
646
+ limit: int = 50,
647
+ ) -> List[WikiStrategy]:
648
+ strategies = []
649
+ page_type_dir = self.wiki.wiki_dir / self.PAGE_TYPE_STRATEGY
650
+ if not page_type_dir.exists():
651
+ return strategies
652
+ for md_file in list(page_type_dir.glob('*.md'))[:limit]:
653
+ page_name = f"{self.PAGE_TYPE_STRATEGY}/{md_file.stem}"
654
+ try:
655
+ page_data = self.wiki.read_page(page_name)
656
+ strategy = self._parse_strategy_from_page(page_name, page_data)
657
+ if strategy is None:
658
+ continue
659
+ if category and strategy.category != category:
660
+ continue
661
+ if tags and not any(t in strategy.tags for t in tags):
662
+ continue
663
+ strategies.append(strategy)
664
+ except Exception:
665
+ continue
666
+ return strategies
667
+
668
+ def store_reproduction(self, reproduction: WikiReproduction) -> str:
669
+ safe_name = reproduction.report_title.replace('/', '_').replace(' ', '_')
670
+ page_name = f"{self.PAGE_TYPE_REPRODUCTION}/{safe_name}"
671
+ content = self._render_reproduction_markdown(reproduction)
672
+ self.wiki.write_page(page_name, content)
673
+ reproduction.wiki_page_name = page_name
674
+ return page_name
675
+
676
+ def get_reproduction(self, report_title: str) -> Optional[WikiReproduction]:
677
+ safe_name = report_title.replace('/', '_').replace(' ', '_')
678
+ page_name = f"{self.PAGE_TYPE_REPRODUCTION}/{safe_name}"
679
+ page_file = self.wiki.wiki_dir / self.PAGE_TYPE_REPRODUCTION / f'{safe_name}.md'
680
+ if not page_file.exists():
681
+ return None
682
+ try:
683
+ page_data = self.wiki.read_page(page_name)
684
+ except Exception:
685
+ return None
686
+ return self._parse_reproduction_from_page(page_name, page_data)
687
+
688
+ def _render_factor_markdown(self, factor: WikiFactor) -> str:
689
+ lines = ["---"]
690
+ lines.append(f"type: {self.PAGE_TYPE_FACTOR}")
691
+ lines.append(f"name: {factor.name}")
692
+ lines.append(f'formula: "{factor.formula}"')
693
+ lines.append(f"source: {factor.source.value}")
694
+ lines.append(f"category: {factor.category.value}")
695
+ lines.append("tags: [" + ", ".join(factor.tags) + "]")
696
+ if factor.ic_mean is not None:
697
+ lines.append(f"ic_mean: {factor.ic_mean}")
698
+ if factor.ic_std is not None:
699
+ lines.append(f"ic_std: {factor.ic_std}")
700
+ if factor.icir is not None:
701
+ lines.append(f"icir: {factor.icir}")
702
+ if factor.rank_ic_mean is not None:
703
+ lines.append(f"rank_ic_mean: {factor.rank_ic_mean}")
704
+ if factor.n_dates is not None:
705
+ lines.append(f"n_dates: {factor.n_dates}")
706
+ if factor.factor_return_corr is not None:
707
+ lines.append(f"factor_return_corr: {factor.factor_return_corr}")
708
+ if factor.ic_t_stat is not None:
709
+ lines.append(f"ic_t_stat: {factor.ic_t_stat}")
710
+ if factor.turnover is not None:
711
+ lines.append(f"turnover: {factor.turnover}")
712
+ created = factor.created_at or datetime.now().isoformat()
713
+ lines.append(f"created_at: {created}")
714
+ lines.append("---")
715
+ lines.append("## 单因子表现")
716
+ lines.append("| 指标 | 值 |")
717
+ lines.append("|------|-----|")
718
+ if factor.ic_mean is not None:
719
+ lines.append(f"| IC Mean | {factor.ic_mean} |")
720
+ if factor.ic_std is not None:
721
+ lines.append(f"| IC Std | {factor.ic_std} |")
722
+ if factor.icir is not None:
723
+ lines.append(f"| IC IR | {factor.icir} |")
724
+ if factor.rank_ic_mean is not None:
725
+ lines.append(f"| Rank IC Mean | {factor.rank_ic_mean} |")
726
+ if factor.n_dates is not None:
727
+ lines.append(f"| 分析天数 | {factor.n_dates} |")
728
+ if factor.ic_t_stat is not None:
729
+ lines.append(f"| IC T-stat | {factor.ic_t_stat} |")
730
+ if factor.turnover is not None:
731
+ lines.append(f"| 换手率 | {factor.turnover} |")
732
+ lines.append("")
733
+ lines.append("## 相关性")
734
+ if factor.factor_return_corr:
735
+ lines.append(str(factor.factor_return_corr))
736
+ else:
737
+ lines.append("暂无")
738
+ lines.append("")
739
+ lines.append("## 使用记录")
740
+ if factor.used_by_strategies:
741
+ for s in factor.used_by_strategies:
742
+ lines.append(f"- {s}")
743
+ else:
744
+ lines.append("暂无")
745
+ lines.append("")
746
+ lines.append("## 策略配置 (YAML)")
747
+ lines.append("```yaml")
748
+ if factor.strategy_yaml:
749
+ lines.append(factor.strategy_yaml)
750
+ else:
751
+ lines.append("# 暂无")
752
+ lines.append("```")
753
+ return "\n".join(lines)
754
+
755
+ def _parse_factor_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiFactor]:
756
+ content = page_data.get("content", "")
757
+ name = self._page_name_to_name(page_name, self.PAGE_TYPE_FACTOR)
758
+ if not name:
759
+ return None
760
+ source = FactorSource.RESEARCH_REPORT
761
+ category = FactorCategory.OTHER
762
+ formula = ""
763
+ tags = []
764
+ ic_mean = ic_std = icir = rank_ic_mean = None
765
+ n_dates = factor_return_corr = ic_t_stat = turnover = None
766
+ used_by_strategies = []
767
+ strategy_yaml = None
768
+ created_at = None
769
+ in_frontmatter = False
770
+ for line in content.split('\n'):
771
+ ls = line.strip()
772
+ if ls == '---':
773
+ in_frontmatter = not in_frontmatter
774
+ continue
775
+ if in_frontmatter:
776
+ if ls.startswith('source:'):
777
+ try:
778
+ source = FactorSource(ls.split(':', 1)[1].strip())
779
+ except ValueError:
780
+ pass
781
+ elif ls.startswith('category:'):
782
+ try:
783
+ category = FactorCategory(ls.split(':', 1)[1].strip())
784
+ except ValueError:
785
+ pass
786
+ elif ls.startswith('formula:'):
787
+ formula = ls.split(':', 1)[1].strip().strip('"')
788
+ elif ls.startswith('tags:'):
789
+ ts = ls.split(':', 1)[1].strip()
790
+ if ts.startswith('[') and ts.endswith(']'):
791
+ tags = [t.strip() for t in ts[1:-1].split(',') if t.strip()]
792
+ elif ls.startswith('ic_mean:'):
793
+ try:
794
+ ic_mean = float(ls.split(':', 1)[1].strip())
795
+ except ValueError:
796
+ pass
797
+ elif ls.startswith('ic_std:'):
798
+ try:
799
+ ic_std = float(ls.split(':', 1)[1].strip())
800
+ except ValueError:
801
+ pass
802
+ elif ls.startswith('icir:'):
803
+ try:
804
+ icir = float(ls.split(':', 1)[1].strip())
805
+ except ValueError:
806
+ pass
807
+ elif ls.startswith('rank_ic_mean:'):
808
+ try:
809
+ rank_ic_mean = float(ls.split(':', 1)[1].strip())
810
+ except ValueError:
811
+ pass
812
+ elif ls.startswith('n_dates:'):
813
+ try:
814
+ n_dates = int(ls.split(':', 1)[1].strip())
815
+ except ValueError:
816
+ pass
817
+ elif ls.startswith('factor_return_corr:'):
818
+ try:
819
+ factor_return_corr = float(ls.split(':', 1)[1].strip())
820
+ except ValueError:
821
+ pass
822
+ elif ls.startswith('ic_t_stat:'):
823
+ try:
824
+ ic_t_stat = float(ls.split(':', 1)[1].strip())
825
+ except ValueError:
826
+ pass
827
+ elif ls.startswith('turnover:'):
828
+ try:
829
+ turnover = float(ls.split(':', 1)[1].strip())
830
+ except ValueError:
831
+ pass
832
+ elif ls.startswith('created_at:'):
833
+ created_at = ls.split(':', 1)[1].strip()
834
+ y_start = content.find('```yaml')
835
+ if y_start != -1:
836
+ y_end = content.find('```', y_start + 7)
837
+ if y_end != -1:
838
+ yc = content[y_start + 7:y_end].strip()
839
+ if yc and yc != '# 暂无':
840
+ strategy_yaml = yc
841
+ ss = content.find('## 使用记录')
842
+ if ss != -1:
843
+ end = content.find('##', ss + 1)
844
+ sc = content[ss:end if end != -1 else len(content)]
845
+ for sl in sc.split('\n'):
846
+ sl = sl.strip()
847
+ if sl.startswith('-'):
848
+ used_by_strategies.append(sl[1:].strip())
849
+ return WikiFactor(
850
+ name=name,
851
+ formula=formula,
852
+ source=source,
853
+ category=category,
854
+ tags=tags,
855
+ ic_mean=ic_mean,
856
+ ic_std=ic_std,
857
+ icir=icir,
858
+ rank_ic_mean=rank_ic_mean,
859
+ n_dates=n_dates,
860
+ factor_return_corr=factor_return_corr,
861
+ ic_t_stat=ic_t_stat,
862
+ turnover=turnover,
863
+ used_by_strategies=used_by_strategies,
864
+ strategy_yaml=strategy_yaml,
865
+ wiki_page_name=page_name,
866
+ created_at=created_at,
867
+ )
868
+
869
+ def _render_logic_markdown(self, logic: WikiLogic) -> str:
870
+ lines = ["---"]
871
+ lines.append(f"type: {self.PAGE_TYPE_LOGIC}")
872
+ lines.append(f"name: {logic.name}")
873
+ lines.append(f"source: {logic.source.value}")
874
+ if logic.extracted_formula:
875
+ lines.append(f'extracted_formula: "{logic.extracted_formula}"')
876
+ lines.append(f"validation_status: {logic.validation_status}")
877
+ if logic.related_strategies:
878
+ lines.append("related_strategies: [" + ", ".join(logic.related_strategies) + "]")
879
+ if logic.related_factors:
880
+ lines.append("related_factors: [" + ", ".join(logic.related_factors) + "]")
881
+ lines.append(f"created_at: {logic.created_at or datetime.now().isoformat()}")
882
+ lines.append("---")
883
+ lines.append("## 原始描述")
884
+ lines.append(logic.content)
885
+ lines.append("")
886
+ lines.append("## 提取的公式")
887
+ lines.append(logic.extracted_formula or '无')
888
+ lines.append("")
889
+ lines.append("## 关联策略")
890
+ if logic.related_strategies:
891
+ for s in logic.related_strategies:
892
+ lines.append(f"- {s}")
893
+ else:
894
+ lines.append("暂无")
895
+ lines.append("")
896
+ lines.append("## 关联因子")
897
+ if logic.related_factors:
898
+ for s in logic.related_factors:
899
+ lines.append(f"- {s}")
900
+ else:
901
+ lines.append("暂无")
902
+ return "\n".join(lines)
903
+
904
+ def _parse_logic_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiLogic]:
905
+ content = page_data.get("content", "")
906
+ name = self._page_name_to_name(page_name, self.PAGE_TYPE_LOGIC)
907
+ if not name:
908
+ return None
909
+ source = LogicSource.RESEARCH_REPORT
910
+ extracted_formula = None
911
+ validation_status = "pending"
912
+ related_strategies = []
913
+ related_factors = []
914
+ created_at = None
915
+ content_body = content
916
+ in_frontmatter = False
917
+ for line in content.split('\n'):
918
+ ls = line.strip()
919
+ if ls == '---':
920
+ in_frontmatter = not in_frontmatter
921
+ continue
922
+ if in_frontmatter:
923
+ if ls.startswith('source:'):
924
+ try:
925
+ source = LogicSource(ls.split(':', 1)[1].strip())
926
+ except ValueError:
927
+ pass
928
+ elif ls.startswith('extracted_formula:'):
929
+ extracted_formula = ls.split(':', 1)[1].strip().strip('"')
930
+ elif ls.startswith('validation_status:'):
931
+ validation_status = ls.split(':', 1)[1].strip()
932
+ elif ls.startswith('created_at:'):
933
+ created_at = ls.split(':', 1)[1].strip()
934
+ sections = content.split('## ')
935
+ for section in sections:
936
+ if section.startswith('原始描述'):
937
+ lines = section.split('\n')[1:]
938
+ content_lines = []
939
+ for line in lines:
940
+ if line.startswith('## '):
941
+ break
942
+ content_lines.append(line)
943
+ content_body = '\n'.join(content_lines).strip()
944
+ ss = content.find('## 关联策略')
945
+ if ss != -1:
946
+ end = content.find('##', ss + 1)
947
+ sc = content[ss:end if end != -1 else len(content)]
948
+ for sl in sc.split('\n'):
949
+ sl = sl.strip()
950
+ if sl.startswith('-'):
951
+ related_strategies.append(sl[1:].strip())
952
+ ss = content.find('## 关联因子')
953
+ if ss != -1:
954
+ end = content.find('##', ss + 1)
955
+ sc = content[ss:end if end != -1 else len(content)]
956
+ for sl in sc.split('\n'):
957
+ sl = sl.strip()
958
+ if sl.startswith('-'):
959
+ related_factors.append(sl[1:].strip())
960
+ return WikiLogic(
961
+ name=name,
962
+ content=content_body,
963
+ source=source,
964
+ extracted_formula=extracted_formula,
965
+ related_strategies=related_strategies,
966
+ related_factors=related_factors,
967
+ validation_status=validation_status,
968
+ wiki_page_name=page_name,
969
+ created_at=created_at,
970
+ )
971
+
972
+ def _page_name_to_name(self, page_name: str, page_type: str) -> str:
973
+ prefix = f'{page_type}/'
974
+ if page_name.startswith(prefix):
975
+ return page_name[len(prefix):]
976
+ return ''
977
+
978
+ def _render_strategy_markdown(self, strategy: WikiStrategy) -> str:
979
+ lines = ["---"]
980
+ lines.append(f"type: {self.PAGE_TYPE_STRATEGY}")
981
+ lines.append(f"name: {strategy.name}")
982
+ lines.append(f"category: {strategy.category}")
983
+ if strategy.tags:
984
+ lines.append("tags: [" + ", ".join(strategy.tags) + "]")
985
+ lines.append(f"created_at: {strategy.created_at or datetime.now().isoformat()}")
986
+ lines.append("---")
987
+ lines.append(f"## {strategy.name}")
988
+ lines.append("")
989
+ lines.append(strategy.description)
990
+ lines.append("")
991
+ lines.append("## 策略配置")
992
+ lines.append("```yaml")
993
+ lines.append(strategy.strategy_yaml)
994
+ lines.append("```")
995
+ if strategy.backtest_result:
996
+ lines.append("")
997
+ lines.append("## 回测结果")
998
+ lines.append("```json")
999
+ import json
1000
+ lines.append(json.dumps(strategy.backtest_result, indent=2, ensure_ascii=False))
1001
+ lines.append("```")
1002
+ return "\n".join(lines)
1003
+
1004
+ def _parse_strategy_from_page(self, page_name: str, page_data: Dict) -> Optional[WikiStrategy]:
1005
+ content = page_data.get("content", "")
1006
+ name = self._page_name_to_name(page_name, self.PAGE_TYPE_STRATEGY)
1007
+ if not name:
1008
+ return None
1009
+ category = "general"
1010
+ tags = []
1011
+ description = ""
1012
+ strategy_yaml = ""
1013
+ backtest_result = None
1014
+ created_at = None
1015
+ in_frontmatter = False
1016
+ yaml_content = ""
1017
+ in_yaml_block = False
1018
+ json_content = ""
1019
+ in_json_block = False
1020
+ for line in content.split('\n'):
1021
+ ls = line.strip()
1022
+ if ls == '---':
1023
+ if not in_frontmatter:
1024
+ in_frontmatter = True
1025
+ continue
1026
+ else:
1027
+ in_frontmatter = False
1028
+ continue
1029
+ if in_frontmatter:
1030
+ if ls.startswith('category:'):
1031
+ category = ls.split(':', 1)[1].strip()
1032
+ elif ls.startswith('tags:'):
1033
+ ts = ls.split(':', 1)[1].strip()
1034
+ if ts.startswith('[') and ts.endswith(']'):
1035
+ tags = [t.strip() for t in ts[1:-1].split(',') if t.strip()]
1036
+ elif ls.startswith('created_at:'):
1037
+ created_at = ls.split(':', 1)[1].strip()
1038
+ else:
1039
+ if ls == '```yaml':
1040
+ in_yaml_block = True
1041
+ continue
1042
+ elif ls == '```' and in_yaml_block:
1043
+ in_yaml_block = False
1044
+ strategy_yaml = yaml_content.strip()
1045
+ yaml_content = ""
1046
+ continue
1047
+ elif ls == '```json':
1048
+ in_json_block = True
1049
+ continue
1050
+ elif ls == '```' and in_json_block:
1051
+ in_json_block = False
1052
+ import json
1053
+ try:
1054
+ backtest_result = json.loads(json_content)
1055
+ except Exception:
1056
+ pass
1057
+ json_content = ""
1058
+ continue
1059
+ if in_yaml_block:
1060
+ yaml_content += line + "\n"
1061
+ elif in_json_block:
1062
+ json_content += line + "\n"
1063
+ elif description == "" and not ls.startswith('##'):
1064
+ description = line
1065
+ return WikiStrategy(
1066
+ name=name,
1067
+ strategy_yaml=strategy_yaml,
1068
+ description=description.strip(),
1069
+ category=category,
1070
+ tags=tags,
1071
+ backtest_result=backtest_result,
1072
+ created_at=created_at,
1073
+ wiki_page_name=page_name,
1074
+ )
1075
+
1076
+ def _render_reproduction_markdown(self, reproduction: WikiReproduction) -> str:
1077
+ lines = ["---"]
1078
+ lines.append(f"type: {self.PAGE_TYPE_REPRODUCTION}")
1079
+ lines.append(f"report_title: {reproduction.report_title}")
1080
+ if reproduction.pdf_path:
1081
+ lines.append(f"pdf_path: {reproduction.pdf_path}")
1082
+ lines.append(f"verified_count: {reproduction.verified_count}")
1083
+ lines.append(f"failed_count: {reproduction.failed_count}")
1084
+ lines.append(f"created_at: {reproduction.created_at or datetime.now().isoformat()}")
1085
+ lines.append("---")
1086
+ lines.append(f"## {reproduction.report_title}")
1087
+ lines.append("")
1088
+ if reproduction.report_markdown:
1089
+ lines.append("## 研报内容")
1090
+ lines.append(reproduction.report_markdown)
1091
+ lines.append("")
1092
+ lines.append("## 复现结果")
1093
+ lines.append(f"- 验证通过: {reproduction.verified_count}")
1094
+ lines.append(f"- 验证失败: {reproduction.failed_count}")
1095
+ return "\n".join(lines)
1096
+
1097
+ def _parse_reproduction_from_page(
1098
+ self, page_name: str, page_data: Dict,
1099
+ ) -> Optional[WikiReproduction]:
1100
+ content = page_data.get("content", "")
1101
+ name = self._page_name_to_name(page_name, self.PAGE_TYPE_REPRODUCTION)
1102
+ if not name:
1103
+ return None
1104
+ report_title = name
1105
+ pdf_path = ""
1106
+ verified_count = 0
1107
+ failed_count = 0
1108
+ created_at = None
1109
+ in_frontmatter = False
1110
+ in_markdown = False
1111
+ markdown_content = ""
1112
+ for line in content.split('\n'):
1113
+ ls = line.strip()
1114
+ if ls == '---':
1115
+ if not in_frontmatter:
1116
+ in_frontmatter = True
1117
+ continue
1118
+ else:
1119
+ in_frontmatter = False
1120
+ continue
1121
+ if in_frontmatter:
1122
+ if ls.startswith('report_title:'):
1123
+ report_title = ls.split(':', 1)[1].strip()
1124
+ elif ls.startswith('pdf_path:'):
1125
+ pdf_path = ls.split(':', 1)[1].strip()
1126
+ elif ls.startswith('verified_count:'):
1127
+ try:
1128
+ verified_count = int(ls.split(':', 1)[1].strip())
1129
+ except ValueError:
1130
+ pass
1131
+ elif ls.startswith('failed_count:'):
1132
+ try:
1133
+ failed_count = int(ls.split(':', 1)[1].strip())
1134
+ except ValueError:
1135
+ pass
1136
+ elif ls.startswith('created_at:'):
1137
+ created_at = ls.split(':', 1)[1].strip()
1138
+ else:
1139
+ if ls == '## 研报内容':
1140
+ in_markdown = True
1141
+ continue
1142
+ elif ls.startswith('## 复现结果'):
1143
+ in_markdown = False
1144
+ continue
1145
+ if in_markdown:
1146
+ markdown_content += line + "\n"
1147
+ return WikiReproduction(
1148
+ report_title=report_title,
1149
+ pdf_path=pdf_path,
1150
+ verified_count=verified_count,
1151
+ failed_count=failed_count,
1152
+ report_markdown=markdown_content.strip(),
1153
+ created_at=created_at,
1154
+ wiki_page_name=page_name,
1155
+ )