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,141 @@
1
+ # coding: utf-8
2
+ """统一数据加载器 / Unified Data Loader
3
+
4
+ Migrated from ~/Public/单因子回测/factor_utils.py (Factor class)
5
+ Security: all exec()/eval() eliminated.
6
+
7
+ Phase H3 (2026-06-20):
8
+ - HDF5 stores are cached per file path in self._h5_stores. Previously
9
+ every load_h5() opened + closed a new HDFStore; now stores stay open
10
+ for the loader lifetime. Read-mode HDFStore is thread-safe.
11
+ - Axis data (stock + index) cached after first get_axis() call.
12
+ add_index() calls get_axis() on every load (10+ per pipeline run),
13
+ previously 2 H5 opens per call.
14
+ """
15
+
16
+ import pandas as pd
17
+
18
+ from QuantNodes.research.factor_test.utils.file_loaders import _FILE_LOADERS
19
+
20
+
21
+ class DataLoader:
22
+ """统一数据加载接口, 支持 H5/CSV/NPY/Parquet"""
23
+
24
+ def __init__(self, api_path: str = './testdata/test_h5_new/'):
25
+ self.api = api_path if api_path.endswith('/') else api_path + '/'
26
+ self._h5_stores: dict[str, pd.HDFStore] = {}
27
+ self._axis_cache: dict[str, tuple] = {}
28
+
29
+ def _get_store(self, path: str) -> pd.HDFStore:
30
+ """Get or lazily open a read-mode HDFStore for `path`."""
31
+ store = self._h5_stores.get(path)
32
+ if store is None:
33
+ store = pd.HDFStore(path, mode='r')
34
+ self._h5_stores[path] = store
35
+ return store
36
+
37
+ def close(self) -> None:
38
+ """Close all cached HDFStores. Call at end of pipeline run."""
39
+ for store in self._h5_stores.values():
40
+ try:
41
+ store.close()
42
+ except Exception:
43
+ pass
44
+ self._h5_stores.clear()
45
+ self._axis_cache.clear()
46
+
47
+ def __del__(self) -> None:
48
+ try:
49
+ self.close()
50
+ except Exception:
51
+ pass
52
+
53
+ def load_h5(self, filename: str, key: str) -> pd.DataFrame:
54
+ """从 H5 文件加载数据 (H3: 复用 cached HDFStore)"""
55
+ path = self.api + filename
56
+ return _FILE_LOADERS['.h5'].load(
57
+ path, key=key, store_getter=self._get_store
58
+ )
59
+
60
+ def load_csv(self, path: str) -> pd.DataFrame:
61
+ """从 CSV 加载数据"""
62
+ return _FILE_LOADERS['.csv'].load(path)
63
+
64
+ def load_npy(self, path: str) -> pd.DataFrame:
65
+ """从 NPY 加载数据"""
66
+ return _FILE_LOADERS['.npy'].load(path)
67
+
68
+ def load_parquet(self, path: str) -> pd.DataFrame:
69
+ """从 Parquet 加载数据"""
70
+ return _FILE_LOADERS['.parquet'].load(path)
71
+
72
+ def load_custom(self, data_dir: tuple) -> pd.DataFrame:
73
+ """从自定义路径加载因子 (H5/CSV/NPY/Parquet)"""
74
+ dir_path, filename = data_dir
75
+ if filename.endswith('.csv'):
76
+ if dir_path.endswith('/') or dir_path.endswith('\\'):
77
+ return self.load_csv(dir_path + filename)
78
+ else:
79
+ # filename itself is a full path
80
+ return self.load_csv(filename)
81
+ elif filename.endswith('.npy'):
82
+ return self.load_npy(dir_path + filename)
83
+ elif filename.endswith('.parquet'):
84
+ if dir_path.endswith('/') or dir_path.endswith('\\'):
85
+ return self.load_parquet(dir_path + filename)
86
+ else:
87
+ # filename itself is a full path
88
+ return self.load_parquet(filename)
89
+ elif dir_path.endswith('.h5'):
90
+ store = self._get_store(dir_path)
91
+ return store.get(filename)
92
+ else:
93
+ raise ValueError(f"不支持的数据格式: {dir_path}, {filename}")
94
+
95
+ def load_factor(self, factor_dir: str, factor_name: str) -> pd.DataFrame:
96
+ """加载因子数据 (统一入口)"""
97
+ if factor_dir.endswith('.h5'):
98
+ return self.load_h5(factor_dir, factor_name)
99
+ ext = '.' + factor_dir.rsplit('.', 1)[-1] if '.' in factor_dir else ''
100
+ if ext in _FILE_LOADERS and ext != '.h5':
101
+ return _FILE_LOADERS[ext].load(factor_dir)
102
+ return self.load_custom((factor_dir, factor_name))
103
+
104
+ def get_stock_axis(self) -> tuple:
105
+ """获取股票列表和交易日序列"""
106
+ stklist = self.load_h5('stk_daily.h5', 'stklist')
107
+ trade_dt = self.load_h5('stk_daily.h5', 'trade_dt')
108
+ return stklist, trade_dt
109
+
110
+ def get_index_axis(self) -> tuple:
111
+ """获取指数列表和交易日序列"""
112
+ indexlist = self.load_h5('index_daily.h5', 'indexlist')
113
+ trade_dt = self.load_h5('index_daily.h5', 'trade_dt')
114
+ return indexlist, trade_dt
115
+
116
+ def get_axis(self, axis_type: str = 'stock') -> tuple:
117
+ """获取轴数据 (H3: cached after first call)"""
118
+ if axis_type in self._axis_cache:
119
+ return self._axis_cache[axis_type]
120
+ if axis_type == 'stock':
121
+ result = self.get_stock_axis()
122
+ elif axis_type == 'index':
123
+ result = self.get_index_axis()
124
+ else:
125
+ raise ValueError(f"不支持的 axis_type: {axis_type}")
126
+ self._axis_cache[axis_type] = result
127
+ return result
128
+
129
+ def add_index(self, factor: pd.DataFrame, axis_type: str = 'stock') -> pd.DataFrame:
130
+ """给因子添加标准索引 (行=交易日, 列=股票/指数)"""
131
+ factor = factor.copy()
132
+ assetlist, trade_dt = self.get_axis(axis_type)
133
+ factor.index = trade_dt.iloc[:, 0].values
134
+ factor.columns = assetlist.iloc[:, 0].values
135
+ return factor
136
+
137
+ def valid_shape(self, factor: pd.DataFrame, axis_type: str = 'stock') -> bool:
138
+ """检查因子 shape 是否为标准矩阵"""
139
+ assetlist, trade_dt = self.get_axis(axis_type)
140
+ expected = (len(trade_dt), len(assetlist))
141
+ return factor.shape == expected
@@ -0,0 +1,232 @@
1
+ # coding: utf-8
2
+ """日期工具 / Date Utilities
3
+
4
+ Migrated from ~/Public/单因子回测/date_utils.py
5
+ """
6
+
7
+ import pandas as pd
8
+ import numpy as np
9
+ from datetime import datetime
10
+
11
+
12
+ def valid_date(trade_dts) -> bool:
13
+ """验证日期格式, 必须为 yyyymmdd 数值型
14
+
15
+ v3.0.0 fix: use ``.iloc[0]`` instead of ``[0]`` for indexing the
16
+ ``dtypes`` Series. The previous code assumed integer column labels
17
+ (e.g. ``pd.DataFrame(dates, columns=[0])``), but after
18
+ ``resample_trade_date`` renames columns to strings the index
19
+ becomes string-keyed, and ``dtypes[0]`` raises ``KeyError: 0``.
20
+ """
21
+ if isinstance(trade_dts, pd.DataFrame):
22
+ num_lens = len(str(trade_dts.iloc[0, 0]))
23
+ data_type = trade_dts.dtypes.iloc[0]
24
+ elif isinstance(trade_dts, pd.Series):
25
+ num_lens = len(str(trade_dts.iloc[0]))
26
+ data_type = trade_dts.dtypes
27
+ else:
28
+ return False
29
+ return (num_lens == 8) and (data_type == 'int64')
30
+
31
+
32
+ def datenum_to_datetime(trade_dt: pd.DataFrame) -> pd.DataFrame:
33
+ """将 yyyymmdd int 转换为 datetime"""
34
+ trade_dt = trade_dt.copy()
35
+ if isinstance(trade_dt, pd.Series):
36
+ trade_dt = trade_dt.to_frame()
37
+ # v3.0.0 fix: ``DataFrame.applymap`` was removed in pandas 3.0; use ``.map``.
38
+ return trade_dt.map(lambda x: datetime.strptime(str(int(x)), '%Y%m%d'))
39
+
40
+
41
+ def datetime_to_datenum(trade_dt: pd.DataFrame) -> pd.DataFrame:
42
+ """将 datetime 转换为 yyyymmdd int"""
43
+ trade_dt = trade_dt.copy()
44
+ # v3.0.0 fix: ``DataFrame.applymap`` was removed in pandas 3.0; use ``.map``.
45
+ return trade_dt.map(lambda x: int(datetime.strftime(x, '%Y%m%d')))
46
+
47
+
48
+ def chg_idx_to_datestr(data):
49
+ """将 Series/DataFrame 的 int index 转换为 'yyyy/mm/dd' 字符串"""
50
+ data = data.copy()
51
+ date_idx = data.index.tolist()
52
+ date_idx_str = list(map(
53
+ lambda x: str(x)[:4] + '/' + str(x)[4:6] + '/' + str(x)[6:], date_idx
54
+ ))
55
+ data.index = date_idx_str
56
+ return data
57
+
58
+
59
+ def resample_trade_date(trade_dt: pd.DataFrame, rule=('M', 'end')) -> pd.DataFrame:
60
+ """将交易日重采样为周/月/季
61
+
62
+ Args:
63
+ trade_dt: yyyymmdd int DataFrame, 单列.
64
+ rule: ``(mode, position)`` 两元组.
65
+
66
+ - ``mode``: ``'W'`` (周) / ``'M'`` (月) / ``'Q'`` (季)
67
+ - ``position``: ``'end'`` 或 ``'begin'``
68
+
69
+ L1 (2026-06-21) 新增 alias::
70
+
71
+ 'beg' / 'start' / 'first' → 'begin'
72
+ 'last' → 'end'
73
+
74
+ 用户不再因为写 ``'beg'`` 这类自然简写而踩 ``不支持的 position``
75
+ 错误. 未在 alias 表内的值仍抛 ``ValueError``.
76
+
77
+ Returns:
78
+ DataFrame: 重采样后的 yyyymmdd int 日期序列.
79
+
80
+ Raises:
81
+ ValueError: 日期格式非法 / rule 不是 2 元 tuple / mode 不在 W/M/Q /
82
+ position 既不在 alias 也不是 begin/end.
83
+ """
84
+ trade_date = trade_dt.copy()
85
+ trade_date.columns = ['trade_dt']
86
+ if not valid_date(trade_date):
87
+ raise ValueError("日期格式有误, 请输入 yyyymmdd int 型")
88
+
89
+ trade_date = datenum_to_datetime(trade_date)
90
+
91
+ if not isinstance(rule, tuple) or len(rule) != 2:
92
+ raise ValueError("请输入正确的调仓模式 ('M', 'end')")
93
+
94
+ mode, position = rule
95
+
96
+ # L1 (2026-06-21): 友好 alias 表, 用户写 'beg' 等同 'begin'.
97
+ _POSITION_ALIASES = {
98
+ 'beg': 'begin',
99
+ 'start': 'begin',
100
+ 'first': 'begin',
101
+ 'last': 'end',
102
+ }
103
+ position = _POSITION_ALIASES.get(position, position)
104
+
105
+ if mode in ('W', 'M', 'Q'):
106
+ period_func = {
107
+ 'W': lambda x: x.weekday(),
108
+ 'M': lambda x: x.month,
109
+ 'Q': lambda x: x.quarter,
110
+ }[mode]
111
+ trade_date['period'] = trade_date.iloc[:, 0].apply(period_func)
112
+
113
+ if position == 'end':
114
+ trade_date['diff'] = trade_date['period'].diff(-1)
115
+ trade_date = trade_date.loc[trade_date['diff'] != 0, 'trade_dt'].to_frame()
116
+ elif position == 'begin':
117
+ trade_date['diff'] = trade_date['period'].diff(1)
118
+ trade_date = trade_date.loc[trade_date['diff'] != 0, 'trade_dt'].to_frame()
119
+ else:
120
+ raise ValueError(f"不支持的 position: {position}")
121
+ else:
122
+ raise ValueError(f"不支持的 resample mode: {mode}")
123
+
124
+ return datetime_to_datenum(trade_date)
125
+
126
+
127
+ def get_adjust_date(trade_dt: pd.DataFrame, beg_date: int, end_date: int,
128
+ adj_mode=('M', 'end')) -> pd.DataFrame:
129
+ """根据起始日、截止日、调仓模式确定调仓日"""
130
+ trade_dt = trade_dt.copy()
131
+
132
+ if type(beg_date) is not type(end_date) or not isinstance(beg_date, int):
133
+ raise ValueError("起始日与截止日格式不一致或非 int 型")
134
+
135
+ if not isinstance(adj_mode, tuple) or len(adj_mode) != 2:
136
+ raise ValueError("请输入正确的调仓模式 ('M', 'end')")
137
+
138
+ if not valid_date(trade_dt):
139
+ raise ValueError("trade_dt 格式有误")
140
+
141
+ if isinstance(trade_dt, pd.Series):
142
+ trade_dt = trade_dt.to_frame()
143
+
144
+ # 调整起止时间到实际交易日
145
+ try:
146
+ beg_date_new = trade_dt[trade_dt >= beg_date].dropna().iloc[0, 0]
147
+ end_date_new = trade_dt[trade_dt <= end_date].dropna().iloc[-1, 0]
148
+ except Exception:
149
+ raise ValueError(
150
+ f"获取测试起止日期出错, 现有数据: {trade_dt.iloc[0, 0]} ~ {trade_dt.iloc[-1, 0]}"
151
+ )
152
+
153
+ mode = adj_mode[0]
154
+ if mode in ('M', 'W'):
155
+ adj_date = resample_trade_date(trade_dt, adj_mode)
156
+ adj_date = adj_date[(adj_date >= beg_date) & (adj_date <= end_date)].dropna()
157
+ adj_date = adj_date.astype(trade_dt.iloc[0].values)
158
+ elif mode == 'D':
159
+ beg_idx = np.where(trade_dt.iloc[:, 0] == beg_date_new)[0][0]
160
+ end_idx = np.where(trade_dt.iloc[:, 0] == end_date_new)[0][0]
161
+ adj_date = trade_dt.iloc[beg_idx:end_idx + 1:adj_mode[1]]
162
+ elif mode == 'custom':
163
+ adj_date = adj_mode[1]
164
+ if not valid_date(adj_date):
165
+ raise ValueError("自定义调仓日格式有误")
166
+ else:
167
+ raise ValueError(f"不支持的调仓模式: {mode}")
168
+
169
+ return adj_date
170
+
171
+
172
+ def offset_date(date_input, trade_dt_all, n, mode='D', if_modify=False):
173
+ """日期偏移
174
+
175
+ H6: replaced ``arr[arr <= x][-1]`` (O(M) boolean mask per query) with
176
+ ``arr[idx-1]`` where ``idx = np.searchsorted(arr, x, side='right')``
177
+ (O(log M) per query). Cumulative gain is K * (M -> log M) where K is
178
+ the size of date_input and M is the size of trade_dt_all.
179
+
180
+ L2 (2026-06-21): 显式越界检查.
181
+
182
+ 修复: 原 ``adj_date.iloc[adj_date_idx + n]`` 在负索引时**不会** raise
183
+ IndexError — pandas/numpy 负索引会 wrap-around 到末尾, 导致 n=-1
184
+ (回退 1 天) 错误返回最后一个调仓日. 现改为显式计算 final_idx, 越界
185
+ 元素按 ``if_modify`` 决定:
186
+
187
+ - ``if_modify=False`` (默认): 显式 raise ``IndexError``, 错误信息
188
+ 列出越界 idx.
189
+ - ``if_modify=True``: ``np.clip`` 到 [0, len(adj_date)-1] 边界.
190
+
191
+ 行为变化: 之前 silent 错误 (返回末尾日期) → 现在明确报错. 任何依赖
192
+ wrap-around 的上游代码 (经 grep 确认无) 会受影响.
193
+ """
194
+ if isinstance(trade_dt_all, pd.DataFrame):
195
+ trade_dt_all = trade_dt_all.iloc[:, 0]
196
+
197
+ if mode == 'D':
198
+ adj_date = trade_dt_all
199
+ adj_values = np.asarray(trade_dt_all.values).ravel()
200
+ elif mode in ('W', 'M', 'Q'):
201
+ adj_date = resample_trade_date(trade_dt_all.to_frame(), (mode, 'end'))
202
+ # resample_trade_date returns a DataFrame; flatten to 1D for searchsorted.
203
+ adj_values = np.asarray(adj_date.iloc[:, 0].values).ravel()
204
+ else:
205
+ raise ValueError(f"不支持的偏移模式: {mode}")
206
+
207
+ date_input_arr = np.asarray(list(date_input))
208
+ # For each x, find largest idx where adj_values[idx] <= x (searchsorted).
209
+ insert_idx = np.searchsorted(adj_values, date_input_arr, side='right')
210
+ date_last_idx = np.clip(insert_idx - 1, 0, len(adj_values) - 1)
211
+ date_last = adj_values[date_last_idx]
212
+ # Use the same 1D representation for the lookup index.
213
+ if isinstance(adj_date, pd.DataFrame):
214
+ adj_index = pd.Index(adj_date.iloc[:, 0])
215
+ else:
216
+ adj_index = pd.Index(adj_date)
217
+ adj_date_idx = adj_index.get_indexer(date_last)
218
+
219
+ # L2 (2026-06-21): 显式越界检查 (修复 pandas 负索引 wrap-around silent bug).
220
+ final_idx = adj_date_idx + n
221
+ out_of_bounds = (final_idx < 0) | (final_idx >= len(adj_date))
222
+ if out_of_bounds.any():
223
+ if if_modify:
224
+ final_idx = np.clip(final_idx, 0, len(adj_date) - 1)
225
+ else:
226
+ raise IndexError(
227
+ f"offset_date 越界 (n={n}): "
228
+ f"final_idx={final_idx[out_of_bounds].tolist()} "
229
+ f"不在 [0, {len(adj_date) - 1}] 范围. "
230
+ f"设置 if_modify=True 可裁剪到边界."
231
+ )
232
+ return np.array(adj_date.iloc[final_idx])
@@ -0,0 +1,150 @@
1
+ # coding: utf-8
2
+ """文件格式 Adapter (Phase 3.3)
3
+
4
+ 将 ``DataLoader`` 按扩展名硬编码的读取逻辑抽成 Adapter 族 + registry,
5
+ 新增格式只需加一个 ``FileFormatLoader`` 子类并注册, 无需改 DataLoader。
6
+
7
+ 每个 adapter 暴露统一的 ``load(path, *, key=None, store_getter=None)`` 签名:
8
+ - 扁平文件 (csv/npy/parquet): 只用 ``path``, 忽略 key/store_getter
9
+ - H5: 用 ``store_getter(path)`` 取得 (可缓存的) HDFStore, 按 ``key``
10
+ 查找 (与 DataLoader.load_h5 的 key 归一化 fallback 一致)
11
+
12
+ 读取行为与重构前的 ``DataLoader.load_*`` bitwise 等价。
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from abc import abstractmethod
17
+ from typing import Callable, Dict, Optional
18
+
19
+ import numpy as np
20
+ import pandas as pd
21
+
22
+ from QuantNodes.core.data_source import DataSource
23
+
24
+
25
+ class FileFormatLoader(DataSource):
26
+ """单一文件格式的读取适配器。
27
+
28
+ Attributes:
29
+ extensions: 该 adapter 负责的扩展名 (含点, 小写), 如 ``('.csv',)``。
30
+ """
31
+
32
+ extensions: tuple = ()
33
+
34
+ @abstractmethod
35
+ def load(
36
+ self,
37
+ path: str,
38
+ *,
39
+ key: Optional[str] = None,
40
+ store_getter: Optional[Callable[[str], pd.HDFStore]] = None,
41
+ ) -> pd.DataFrame:
42
+ """读取 ``path`` 处的数据为 DataFrame。
43
+
44
+ Args:
45
+ path: 文件路径 (H5 为文件路径, 其余为完整路径)。
46
+ key: H5 数据集 key (仅 H5 使用)。
47
+ store_getter: 返回可缓存 HDFStore 的回调 (仅 H5 使用),
48
+ 用于复用 DataLoader 的 ``_h5_stores`` 缓存。
49
+ """
50
+ raise NotImplementedError
51
+
52
+ def close(self) -> None:
53
+ """adapter 自身无状态; H5Store 生命周期由 DataLoader 管理。"""
54
+ return None
55
+
56
+
57
+ class CSVLoader(FileFormatLoader):
58
+ extensions = ('.csv',)
59
+
60
+ def load(self, path, *, key=None, store_getter=None):
61
+ return pd.read_csv(path, index_col=0)
62
+
63
+
64
+ class NPYLoader(FileFormatLoader):
65
+ extensions = ('.npy',)
66
+
67
+ def load(self, path, *, key=None, store_getter=None):
68
+ return pd.DataFrame(np.load(path, allow_pickle=True))
69
+
70
+
71
+ class ParquetLoader(FileFormatLoader):
72
+ extensions = ('.parquet',)
73
+
74
+ def load(self, path, *, key=None, store_getter=None):
75
+ return pd.read_parquet(path)
76
+
77
+
78
+ class H5Loader(FileFormatLoader):
79
+ extensions = ('.h5',)
80
+
81
+ def load(self, path, *, key=None, store_getter=None):
82
+ if store_getter is None:
83
+ raise ValueError("H5Loader.load requires a store_getter callback")
84
+ store = store_getter(path)
85
+ # 标准化 key: HDFStore 自动加 / 前缀
86
+ norm_key = key if key.startswith('/') else '/' + key
87
+ if norm_key in store.keys():
88
+ return store.get(norm_key)
89
+ if key in store.keys():
90
+ return store.get(key)
91
+ raise KeyError(
92
+ f"Key '{key}' not found in {path}. Available: {store.keys()}"
93
+ )
94
+
95
+
96
+ _FILE_LOADERS: Dict[str, FileFormatLoader] = {}
97
+
98
+
99
+ def _register_default_loaders() -> None:
100
+ for cls in (H5Loader, CSVLoader, NPYLoader, ParquetLoader):
101
+ loader = cls()
102
+ for ext in loader.extensions:
103
+ _FILE_LOADERS[ext] = loader
104
+
105
+
106
+ _register_default_loaders()
107
+
108
+
109
+ def build_file_loader(ext: str) -> FileFormatLoader:
110
+ """按扩展名 (含点) 返回对应的 FileFormatLoader 实例。
111
+
112
+ Args:
113
+ ext: 文件扩展名, 含点, 如 ``'.csv'``。
114
+
115
+ Returns:
116
+ FileFormatLoader 实例。
117
+
118
+ Raises:
119
+ ValueError: 扩展名未注册。
120
+ """
121
+ loader = _FILE_LOADERS.get(ext)
122
+ if loader is None:
123
+ raise ValueError(
124
+ f"Unsupported file extension: {ext}. "
125
+ f"Available: {sorted(_FILE_LOADERS)}"
126
+ )
127
+ return loader
128
+
129
+
130
+ def register_file_loader(loader: FileFormatLoader) -> None:
131
+ """注册一个新的文件格式 adapter (供扩展)。
132
+
133
+ Args:
134
+ loader: FileFormatLoader 实例, 其 ``extensions`` 决定负责的扩展名。
135
+
136
+ Raises:
137
+ ValueError: extensions 为空或某扩展名已注册。
138
+ """
139
+ if not loader.extensions:
140
+ raise ValueError("loader.extensions must be non-empty")
141
+ for ext in loader.extensions:
142
+ if ext in _FILE_LOADERS:
143
+ raise ValueError(f"extension '{ext}' already registered")
144
+ for ext in loader.extensions:
145
+ _FILE_LOADERS[ext] = loader
146
+
147
+
148
+ def available_extensions() -> list:
149
+ """返回已注册的扩展名列表 (排序)。"""
150
+ return sorted(_FILE_LOADERS)
@@ -0,0 +1,37 @@
1
+ """Phase J6 (2026-06-20): Chinese labels for long-short and group analysis output.
2
+
3
+ Consolidates the hard-coded 中文 column labels used in long_short_node.py
4
+ (group_result dict + DataFrame columns) and group_analyzer_node.py's
5
+ yearly-eva dict keys.
6
+
7
+ Reuse from tests via:
8
+ from QuantNodes.research.factor_test.utils.labels import (
9
+ LONG, SHORT, LONG_EXCESS, SHORT_EXCESS, LONGSHORT,
10
+ L_S_COLS, NET_COLS,
11
+ )
12
+ """
13
+ from __future__ import annotations
14
+
15
+
16
+ # Column labels for group results
17
+ LONG: str = "多头"
18
+ SHORT: str = "空头"
19
+ LONG_EXCESS: str = "多头超额"
20
+ SHORT_EXCESS: str = "空头超额"
21
+ LONGSHORT: str = "多空"
22
+
23
+ # Standard 3-column order for L/S evaluation tables
24
+ L_S_COLS: list[str] = [LONG_EXCESS, SHORT_EXCESS, LONGSHORT]
25
+
26
+ # Standard 5-column order for the net value table
27
+ NET_COLS: list[str] = [LONG, SHORT, LONG_EXCESS, SHORT_EXCESS, LONGSHORT]
28
+
29
+ __all__ = [
30
+ "LONG",
31
+ "SHORT",
32
+ "LONG_EXCESS",
33
+ "SHORT_EXCESS",
34
+ "LONGSHORT",
35
+ "L_S_COLS",
36
+ "NET_COLS",
37
+ ]
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+ """指标提取工具 / Metrics Extractor.
3
+
4
+ 从单次回测的 ``ctx`` (12 节点输出 dict) 提取关键评估指标,
5
+ 供 ``TrajectoryEntry.metrics`` / Evolution rank 使用.
6
+
7
+ Phase R2 (2026-06-19): 从 pipeline_runner.py 末尾抽出, 单一职责.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+
13
+ def extract_metrics_from_ctx(ctx: dict) -> dict:
14
+ """从单次回测 ctx 提取关键指标 (IC, Rank IC, ICIR, Sharpe, ARR, MDD, Calmar).
15
+
16
+ Args:
17
+ ctx: ``PipelineRunner.run()`` 的返回值, 通常含
18
+ ``ICAnalyzer`` / ``LongShort`` 两个键.
19
+
20
+ Returns:
21
+ ``{ic_mean, rank_ic_mean, ic_ir, sharpe, arr, mdd, calmar}`` 的子集
22
+ (缺失字段不会出现在结果里).
23
+ """
24
+ metrics: dict = {}
25
+ ic = ctx.get("ICAnalyzer") or {}
26
+ ic_result = ic.get("ic_result") if isinstance(ic, dict) else None
27
+ if isinstance(ic_result, dict):
28
+ for src_key, dst_key in (
29
+ ("IC均值", "ic_mean"),
30
+ ("Rank IC均值", "rank_ic_mean"),
31
+ ("ICIR", "ic_ir"),
32
+ ):
33
+ if src_key in ic_result and ic_result[src_key] is not None:
34
+ try:
35
+ metrics[dst_key] = float(ic_result[src_key])
36
+ except (TypeError, ValueError):
37
+ pass
38
+ ls = ctx.get("LongShort") or {}
39
+ if isinstance(ls, dict):
40
+ for src_key, dst_key in (
41
+ ("sharpe", "sharpe"),
42
+ ("annualized_return", "arr"),
43
+ ("max_drawdown", "mdd"),
44
+ ("calmar", "calmar"),
45
+ ):
46
+ if src_key in ls and ls[src_key] is not None:
47
+ try:
48
+ metrics[dst_key] = float(ls[src_key])
49
+ except (TypeError, ValueError):
50
+ pass
51
+ return metrics
52
+
53
+
54
+ # 向后兼容别名 (老代码或测试可能 import 私有名)
55
+ _extract_metrics_from_ctx = extract_metrics_from_ctx