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,689 @@
1
+ # coding: utf-8
2
+ """IFinDDatabase - iFinD API 包装为 DataLoader 兼容接口
3
+
4
+ Drop-in replacement for DataLoader, backed by iFinD API.
5
+ 所有 panel 数据返回 (dates × stocks) DataFrame, index=日期(int), columns=股票代码(str).
6
+ """
7
+
8
+ import logging
9
+ import pandas as pd
10
+ import numpy as np
11
+ from pathlib import Path
12
+ from datetime import datetime, timedelta
13
+ from typing import ClassVar, Callable
14
+
15
+ from .fetcher import IFindFetcher
16
+ from QuantNodes.core.path_utils import ensure_dir
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ # ── Route Decorator (B5 refactor, 2026-06-19) ──────────────────
22
+
23
+ def register_route(filename: str, key: str) -> Callable:
24
+ """装饰器: 把方法注册到 IFinDDatabase._ROUTE_TABLE[(filename, key)] = method 名.
25
+
26
+ 使用::
27
+
28
+ @register_route("stk_daily.h5", "cp")
29
+ def _get_prices(self) -> pd.DataFrame:
30
+ ...
31
+
32
+ # 一个方法可注册到多个 (filename, key):
33
+ @register_route("stk_daily.h5", "trade_dt")
34
+ @register_route("index_daily.h5", "trade_dt")
35
+ def _get_trade_dt_raw(self) -> pd.DataFrame:
36
+ ...
37
+
38
+ 装饰器给方法打 ``_routes`` (列表) 标记; 实际表填充在类定义后由
39
+ ``_collect_routes(IFinDDatabase)`` 一次性扫描完成, 保证
40
+ ``load_h5`` 路由查找零开销.
41
+ """
42
+ def deco(method: Callable) -> Callable:
43
+ existing = getattr(method, "_routes", None) # type: ignore[attr-defined]
44
+ if existing is None:
45
+ method._routes = [(filename, key)] # type: ignore[attr-defined]
46
+ else:
47
+ method._routes = existing + [(filename, key)] # type: ignore[attr-defined]
48
+ return method
49
+ return deco
50
+
51
+
52
+ def _collect_routes(cls: type) -> dict[tuple[str, str], str]:
53
+ """扫描类及所有父类, 收集 ``_routes`` 标记的方法.
54
+
55
+ Returns:
56
+ ``{(filename, key): method_name}`` 字典.
57
+ """
58
+ routes: dict[tuple[str, str], str] = {}
59
+ for klass in reversed(cls.__mro__):
60
+ for name, val in klass.__dict__.items():
61
+ if callable(val) and hasattr(val, "_routes"):
62
+ for route_key in val._routes: # type: ignore[attr-defined]
63
+ routes[route_key] = name
64
+ return routes
65
+
66
+
67
+ # ── Week 12: H5 兼容辅助 ─────────────────────────────────────
68
+
69
+ def _df_to_hdf_safe(df: pd.DataFrame) -> pd.DataFrame:
70
+ """转 nullable/Int64 等 HDF5 不支持的 dtype 为兼容 dtype。
71
+
72
+ - Int64 → int64 (含 NaN 时会丢失 NaN, 但 HDF5 不支持 nullable int)
73
+ - int64 → int64
74
+ - 其余保持
75
+ """
76
+ df = df.copy()
77
+ for col in df.columns:
78
+ if df[col].dtype == 'Int64':
79
+ # 全部 NaN → float (否则 .astype(int) 报错)
80
+ if df[col].isna().all():
81
+ df[col] = df[col].astype(float)
82
+ else:
83
+ df[col] = df[col].fillna(0).astype(int)
84
+ if df.index.dtype == 'Int64':
85
+ if df.index.isna().all():
86
+ df.index = df.index.astype(float)
87
+ else:
88
+ df.index = df.index.fillna(0).astype(int)
89
+ return df
90
+
91
+
92
+ # ── 行业代码映射 ──────────────────────────────────────────────
93
+ # H13: 默认 30 申万一级, 可通过 constructor 的 industry_map 参数覆盖
94
+ _DEFAULT_INDUSTRY_MAP = {
95
+ '农林牧渔': 1, '基础化工': 2, '钢铁': 3, '有色金属': 4, '电子': 5,
96
+ '汽车': 6, '家用电器': 7, '食品饮料': 8, '纺织服饰': 9, '轻工制造': 10,
97
+ '医药生物': 11, '公用事业': 12, '交通运输': 13, '房地产': 14, '商贸零售': 15,
98
+ '社会服务': 16, '银行': 17, '非银金融': 18, '综合': 19, '建筑材料': 20,
99
+ '建筑装饰': 21, '电力设备': 22, '国防军工': 23, '计算机': 24, '传媒': 25,
100
+ '通信': 26, '煤炭': 27, '石油石化': 28, '环保': 29, '美容护理': 30,
101
+ }
102
+
103
+
104
+ class IFinDDatabase:
105
+ """iFinD API-backed DataLoader replacement.
106
+
107
+ Usage:
108
+ db = IFinDDatabase(date_beg='20260101', date_end='20260630')
109
+ # 与 DataLoader 完全兼容
110
+ stklist, trade_dt = db.get_stock_axis()
111
+ cp = db.load_h5('stk_daily.h5', 'cp')
112
+ cp_labeled = db.add_index(cp)
113
+ """
114
+
115
+ def __init__(self, api_path: str = '', date_beg: str = '',
116
+ date_end: str = '', universe: str = '沪深300',
117
+ fetcher: IFindFetcher = None,
118
+ industry_map: dict | None = None,
119
+ risk_registry: list[str] | None = None,
120
+ batch_size: int | None = None):
121
+ """
122
+ Args:
123
+ api_path: 兼容 DataLoader, 被忽略
124
+ date_beg: 查询起始日期 (YYYYMMDD, 空=1 年前)
125
+ date_end: 查询截止日期 (空=今天)
126
+ universe: 股票池 ('沪深300', '中证500', 'all')
127
+ fetcher: 注入的 fetcher (测试用 IFindFetcherStub)
128
+ industry_map: H13 行业代码映射 (None=30 申万一级)
129
+ risk_registry: P-4 风险因子注册表 (None=10 默认 Barra 风格因子)
130
+ batch_size: M10 分批查询大小 (None=默认 50, 推荐 50-100 避免限流)
131
+ """
132
+ # H9: 不再硬编码 '20260101', 默认 1 年前 (跨年/跨月可滚动)
133
+ if not date_beg:
134
+ one_year_ago = (datetime.now() - timedelta(days=365)).strftime('%Y%m%d')
135
+ self._date_beg = one_year_ago
136
+ else:
137
+ self._date_beg = date_beg
138
+ self._date_end = date_end or datetime.now().strftime('%Y%m%d')
139
+ self._universe = universe
140
+ self._fetcher = fetcher or IFindFetcher()
141
+ # H13: 行业代码映射可覆盖
142
+ self._industry_map = industry_map if industry_map is not None else _DEFAULT_INDUSTRY_MAP
143
+ # P-4: 风险因子注册表可外部注入, None=10 默认 Barra 风格因子
144
+ self._risk_registry = list(risk_registry) if risk_registry is not None else [
145
+ '/beta', '/momentum', '/size', '/volatility',
146
+ '/value', '/quality', '/growth', '/leverage',
147
+ '/liquidity', '/non_linear_size',
148
+ ]
149
+ # M10: 分批查询大小 (默认 50, 可自定义)
150
+ self._batch_size = batch_size if batch_size is not None else 50
151
+
152
+ # 缓存
153
+ self._stklist = None
154
+ self._indexlist = None
155
+ self._trade_dt = None
156
+ self._stock_prices = None
157
+ self._index_prices = None
158
+ self._stock_info_cache = {} # key -> DataFrame
159
+
160
+ # ── 路由表 (B5: 由 @register_route 装饰器收集, 类定义后一次性填充) ──
161
+ #
162
+ # 原 _ROUTE_TABLE 字面量已迁移到各 _get_* 方法上的 @register_route 装饰器,
163
+ # 见类下方 _collect_routes(IFinDDatabase) 调用.
164
+ # 旧 _ROUTE_TABLE 访问完全兼容 (dict 接口不变).
165
+ _ROUTE_TABLE: ClassVar[dict[tuple[str, str], str]] = {}
166
+
167
+ # ── DataLoader 兼容接口 ─────────────────────────────────
168
+
169
+ def load_h5(self, filename: str, key: str) -> pd.DataFrame:
170
+ """路由到 iFinD 查询, 模拟 H5 加载"""
171
+ route_key = (filename, key)
172
+ if route_key in self._ROUTE_TABLE:
173
+ method_name = self._ROUTE_TABLE[route_key]
174
+ return getattr(self, method_name)()
175
+ raise KeyError(
176
+ f"IFinDDatabase: 未映射的 (filename='{filename}', key='{key}'). "
177
+ f"可用路由: {list(self._ROUTE_TABLE.keys())}"
178
+ )
179
+
180
+ def load_csv(self, path: str) -> pd.DataFrame:
181
+ return pd.read_csv(path, index_col=0)
182
+
183
+ def load_npy(self, path: str) -> pd.DataFrame:
184
+ return pd.DataFrame(np.load(path, allow_pickle=True))
185
+
186
+ def load_parquet(self, path: str) -> pd.DataFrame:
187
+ return pd.read_parquet(path)
188
+
189
+ def load_custom(self, data_dir: tuple) -> pd.DataFrame:
190
+ raise NotImplementedError(
191
+ "IFinDDatabase 不支持自定义路径加载, "
192
+ "请使用 load_h5 或直接通过 fetcher.query()"
193
+ )
194
+
195
+ def load_factor(self, factor_dir: str, factor_name: str) -> pd.DataFrame:
196
+ """因子加载: 从 iFinD 获取因子数据"""
197
+ return self._get_factor(factor_dir, factor_name)
198
+
199
+ def get_stock_axis(self) -> tuple:
200
+ """返回 (stklist, trade_dt), 格式与 DataLoader 一致"""
201
+ return self._get_stock_axis_raw(), self._get_trade_dt_raw()
202
+
203
+ def get_index_axis(self) -> tuple:
204
+ return self._get_index_axis_raw(), self._get_trade_dt_raw()
205
+
206
+ def get_axis(self, axis_type: str = 'stock') -> tuple:
207
+ if axis_type == 'stock':
208
+ return self.get_stock_axis()
209
+ elif axis_type == 'index':
210
+ return self.get_index_axis()
211
+ else:
212
+ raise ValueError(f"不支持的 axis_type: {axis_type}")
213
+
214
+ def add_index(self, factor: pd.DataFrame, axis_type: str = 'stock') -> pd.DataFrame:
215
+ """给因子添加标准索引。iFinD 数据通常已带标签, 仅做验证"""
216
+ factor = factor.copy()
217
+ assetlist, trade_dt = self.get_axis(axis_type)
218
+
219
+ # 如果已有正确维度但缺少标签, 则添加
220
+ expected_dates = trade_dt.iloc[:, 0].values
221
+ expected_assets = assetlist.iloc[:, 0].values
222
+
223
+ if factor.shape == (len(expected_dates), len(expected_assets)):
224
+ if not factor.index.equals(pd.Index(expected_dates)):
225
+ factor.index = expected_dates
226
+ if not factor.columns.equals(pd.Index(expected_assets)):
227
+ factor.columns = expected_assets
228
+ return factor
229
+
230
+ def valid_shape(self, factor: pd.DataFrame, axis_type: str = 'stock') -> bool:
231
+ assetlist, trade_dt = self.get_axis(axis_type)
232
+ return factor.shape == (len(trade_dt), len(assetlist))
233
+
234
+ def get_apikeys(self, filename: str) -> list:
235
+ """风险因子注册表 (P-4: 改为读 self._risk_registry, 可外部注入)"""
236
+ return list(self._risk_registry)
237
+
238
+ # ── 内部数据获取方法 ─────────────────────────────────────
239
+
240
+ def _query_stock_info(self, query: str) -> pd.DataFrame:
241
+ """封装股票信息查询"""
242
+ return self._fetcher.query('stock', 'get_stock_info', {'query': query})
243
+
244
+ def _query_index_data(self, query: str) -> pd.DataFrame:
245
+ """封装指数数据查询"""
246
+ return self._fetcher.query('index', 'index_data', {'query': query})
247
+
248
+ def _get_stock_codes(self) -> list[str]:
249
+ """获取股票池代码列表"""
250
+ if self._stklist is not None:
251
+ return list(self._stklist.iloc[:, 0])
252
+
253
+ if self._universe == 'all':
254
+ query = f'A股市场所有股票代码({self._date_beg[:4]}年)'
255
+ else:
256
+ query = f'{self._universe}成分股列表'
257
+
258
+ df = self._query_index_data(query)
259
+ if df.empty:
260
+ raise RuntimeError(f"无法获取股票池: {self._universe}")
261
+
262
+ # 找到代码列
263
+ code_col = None
264
+ for col in df.columns:
265
+ if df[col].astype(str).str.match(r'\d{6}\.(SH|SZ)').any():
266
+ code_col = col
267
+ break
268
+ if code_col is None:
269
+ code_col = df.columns[0]
270
+
271
+ codes = df[code_col].astype(str).tolist()
272
+ self._stklist = pd.DataFrame(codes)
273
+ return codes
274
+
275
+ def _get_trade_dates(self) -> list[int]:
276
+ """获取交易日历"""
277
+ if self._trade_dt is not None:
278
+ return list(self._trade_dt.iloc[:, 0])
279
+
280
+ # 尝试从指数数据获取交易日
281
+ try:
282
+ beg_year = self._date_beg[:4]
283
+ beg_month = self._date_beg[4:6]
284
+ end_month = self._date_end[4:6]
285
+ query = (
286
+ f'沪深300、中证500{beg_year}年{beg_month}月至{end_month}月的收盘点数'
287
+ )
288
+ df = self._query_index_data(query)
289
+ if not df.empty:
290
+ date_col = None
291
+ for col in df.columns:
292
+ if df[col].astype(str).str.match(r'^\d{8}$').any():
293
+ date_col = col
294
+ break
295
+ if date_col:
296
+ dates = sorted(
297
+ pd.to_numeric(df[date_col], errors='coerce')
298
+ .dropna().astype(int).unique().tolist()
299
+ )
300
+ self._trade_dt = pd.DataFrame(dates)
301
+ return dates
302
+ except Exception:
303
+ pass
304
+
305
+ # fallback: 从价格数据中提取日期
306
+ prices = self._get_prices()
307
+ dates = sorted(prices.index.tolist())
308
+ self._trade_dt = pd.DataFrame(dates)
309
+ return dates
310
+
311
+ @register_route("stk_daily.h5", "stklist")
312
+ def _get_stock_axis_raw(self) -> pd.DataFrame:
313
+ """stklist DataFrame: (N_stocks, 1)"""
314
+ if self._stklist is None:
315
+ self._get_stock_codes()
316
+ return self._stklist
317
+
318
+ @register_route("index_daily.h5", "indexlist")
319
+ def _get_index_axis_raw(self) -> pd.DataFrame:
320
+ """indexlist DataFrame: (N_indices, 1)"""
321
+ if self._indexlist is None:
322
+ query = '沪深300、中证500收盘点数'
323
+ df = self._query_index_data(query)
324
+ if df.empty:
325
+ self._indexlist = pd.DataFrame(['000300.SH', '000905.SH'])
326
+ else:
327
+ code_col = df.columns[0]
328
+ codes = df[code_col].unique().tolist()
329
+ self._indexlist = pd.DataFrame(codes)
330
+ return self._indexlist
331
+
332
+ @register_route("stk_daily.h5", "trade_dt")
333
+ @register_route("index_daily.h5", "trade_dt")
334
+ def _get_trade_dt_raw(self) -> pd.DataFrame:
335
+ """trade_dt DataFrame: (M_dates, 1)"""
336
+ if self._trade_dt is None:
337
+ self._get_trade_dates()
338
+ return self._trade_dt
339
+
340
+ @register_route("stk_daily.h5", "cp")
341
+ def _get_prices(self) -> pd.DataFrame:
342
+ """获取股票收盘价面板 (dates × stocks)"""
343
+ if self._stock_prices is not None:
344
+ return self._stock_prices
345
+
346
+ codes = self._get_stock_codes()
347
+ # 分批查询 (每批最多 self._batch_size 个代码, M10 参数化)
348
+ all_dfs = []
349
+ for i in range(0, len(codes), self._batch_size):
350
+ batch = codes[i:i + self._batch_size]
351
+ code_str = '、'.join(batch)
352
+ beg_year = self._date_beg[:4]
353
+ beg_month = self._date_beg[4:6]
354
+ end_month = self._date_end[4:6]
355
+ query = (
356
+ f'{code_str}{beg_year}年{beg_month}月至{end_month}月的日收盘价'
357
+ )
358
+ df = self._query_stock_info(query)
359
+ if not df.empty:
360
+ all_dfs.append(df)
361
+
362
+ if not all_dfs:
363
+ raise RuntimeError("无法获取股票价格数据")
364
+
365
+ combined = pd.concat(all_dfs, ignore_index=True)
366
+ self._stock_prices = self._pivot_prices(combined)
367
+ return self._stock_prices
368
+
369
+ def _pivot_prices(self, df: pd.DataFrame) -> pd.DataFrame:
370
+ """将长格式价格表转为宽格式 (dates × stocks)"""
371
+ # 找到日期列、代码列、价格列
372
+ date_col = None
373
+ code_col = None
374
+ price_col = None
375
+
376
+ for col in df.columns:
377
+ sample = df[col].astype(str)
378
+ if sample.str.match(r'^\d{8}$').any():
379
+ date_col = col
380
+ elif sample.str.match(r'\d{6}\.(SH|SZ)').any():
381
+ code_col = col
382
+ elif '收盘' in col or '价' in col:
383
+ price_col = col
384
+
385
+ if date_col is None or code_col is None or price_col is None:
386
+ # fallback: 用前3列
387
+ cols = df.columns.tolist()
388
+ code_col = code_col or cols[0]
389
+ date_col = date_col or cols[1] if len(cols) > 1 else cols[0]
390
+ price_col = price_col or cols[2] if len(cols) > 2 else cols[1]
391
+
392
+ pivot = df.pivot_table(
393
+ index=date_col, columns=code_col, values=price_col,
394
+ aggfunc='first'
395
+ )
396
+ pivot.index = pd.to_numeric(pivot.index, errors='coerce').astype('Int64')
397
+ pivot.columns = pivot.columns.astype(str)
398
+ return pivot.sort_index()
399
+
400
+ def _get_stock_info_panel(self, key: str, query_template: str) -> pd.DataFrame:
401
+ """通用股票信息面板获取"""
402
+ if key in self._stock_info_cache:
403
+ return self._stock_info_cache[key]
404
+
405
+ codes = self._get_stock_codes()
406
+ # M10: 分批查询用 self._batch_size
407
+ all_dfs = []
408
+ for i in range(0, len(codes), self._batch_size):
409
+ batch = codes[i:i + self._batch_size]
410
+ code_str = '、'.join(batch)
411
+ query = query_template.format(codes=code_str, beg=self._date_beg, end=self._date_end)
412
+ df = self._query_stock_info(query)
413
+ if not df.empty:
414
+ all_dfs.append(df)
415
+
416
+ if not all_dfs:
417
+ # 返回空面板
418
+ dates = self._get_trade_dates()
419
+ return pd.DataFrame(0, index=dates, columns=codes)
420
+
421
+ combined = pd.concat(all_dfs, ignore_index=True)
422
+ panel = self._pivot_stock_info(combined, codes)
423
+ self._stock_info_cache[key] = panel
424
+ return panel
425
+
426
+ def _pivot_stock_info(self, df: pd.DataFrame, codes: list) -> pd.DataFrame:
427
+ """将长格式转为宽格式 (dates × stocks)"""
428
+ date_col = code_col = value_col = None
429
+ for col in df.columns:
430
+ sample = df[col].astype(str)
431
+ if sample.str.match(r'^\d{8}$').any():
432
+ date_col = col
433
+ elif sample.str.match(r'\d{6}\.(SH|SZ)').any():
434
+ code_col = col
435
+ else:
436
+ if value_col is None:
437
+ value_col = col
438
+
439
+ if date_col and code_col and value_col:
440
+ pivot = df.pivot_table(
441
+ index=date_col, columns=code_col, values=value_col,
442
+ aggfunc='first'
443
+ )
444
+ pivot.index = pd.to_numeric(pivot.index, errors='coerce').astype('Int64')
445
+ # 确保所有股票都在列中
446
+ for c in codes:
447
+ if c not in pivot.columns:
448
+ pivot[c] = 0
449
+ return pivot[codes].sort_index()
450
+
451
+ # fallback
452
+ dates = self._get_trade_dates()
453
+ return pd.DataFrame(0, index=dates, columns=codes)
454
+
455
+ @register_route("stk_daily.h5", "id_citic1")
456
+ def _get_industry(self) -> pd.DataFrame:
457
+ """行业分类面板 (dates × stocks), 值为申万一级代码 (1-30)"""
458
+ return self._get_stock_info_panel(
459
+ 'id_citic1',
460
+ '{codes}的行业分类(申万一级)'
461
+ ).map(lambda x: self._industry_map.get(str(x), 0) if pd.notna(x) else 0)
462
+
463
+ @register_route("stk_daily.h5", "mv_float")
464
+ def _get_market_value(self) -> pd.DataFrame:
465
+ """流通市值面板 (dates × stocks)"""
466
+ return self._get_stock_info_panel(
467
+ 'mv_float',
468
+ '{codes}的流通市值'
469
+ )
470
+
471
+ @register_route("stk_daily.h5", "st")
472
+ def _get_st_status(self) -> pd.DataFrame:
473
+ """ST 状态面板 (dates × stocks), 值为 0/1"""
474
+ return self._get_stock_info_panel(
475
+ 'st',
476
+ '{codes}是否被ST处理'
477
+ ).map(lambda x: 1 if str(x).strip() in ('是', 'True', '1', 'ST') else 0)
478
+
479
+ @register_route("stk_daily.h5", "suspend")
480
+ def _get_suspension(self) -> pd.DataFrame:
481
+ """停牌状态面板 (dates × stocks), 值为 0/1"""
482
+ return self._get_stock_info_panel(
483
+ 'suspend',
484
+ '{codes}是否停牌'
485
+ ).map(lambda x: 1 if str(x).strip() in ('是', 'True', '1', '停牌') else 0)
486
+
487
+ @register_route("stk_daily.h5", "ud_limit")
488
+ def _get_limit(self) -> pd.DataFrame:
489
+ """涨跌停面板 (dates × stocks), 值为 0/1/-1"""
490
+ return self._get_stock_info_panel(
491
+ 'ud_limit',
492
+ '{codes}是否涨跌停'
493
+ ).map(lambda x: 1 if '涨停' in str(x) else (-1 if '跌停' in str(x) else 0))
494
+
495
+ @register_route("stk_daily.h5", "ipo_days")
496
+ def _get_ipo_days(self) -> pd.DataFrame:
497
+ """上市天数面板 (dates × stocks)"""
498
+ panel = self._get_stock_info_panel(
499
+ 'ipo_days',
500
+ '{codes}的上市日期'
501
+ )
502
+ # 将上市日期转为天数
503
+ dates = panel.index
504
+ for col in panel.columns:
505
+ ipo_date = panel[col].iloc[0] if not panel[col].empty else 0
506
+ try:
507
+ ipo_dt = pd.to_datetime(str(int(ipo_date)), format='%Y%m%d')
508
+ panel[col] = [(d - ipo_dt).days if pd.notna(d) else 9999
509
+ for d in pd.to_datetime(dates.astype(str), format='%Y%m%d')]
510
+ except Exception:
511
+ panel[col] = 500
512
+ return panel
513
+
514
+ @register_route("index_daily.h5", "index_cp")
515
+ def _get_index_cp(self) -> pd.DataFrame:
516
+ """指数收盘价面板 (dates × indices)"""
517
+ if self._index_prices is not None:
518
+ return self._index_prices
519
+
520
+ indexlist = self._get_index_axis_raw()
521
+ indices = indexlist.iloc[:, 0].tolist()
522
+ code_str = '、'.join(indices)
523
+ beg_year = self._date_beg[:4]
524
+ beg_month = self._date_beg[4:6]
525
+ end_month = self._date_end[4:6]
526
+ query = (
527
+ f'{code_str}{beg_year}年{beg_month}月至{end_month}月的收盘点数'
528
+ )
529
+ df = self._query_index_data(query)
530
+
531
+ if df.empty:
532
+ dates = self._get_trade_dates()
533
+ self._index_prices = pd.DataFrame(0, index=dates, columns=indices)
534
+ else:
535
+ self._index_prices = self._pivot_prices(df)
536
+ return self._index_prices
537
+
538
+ @register_route("stk_daily.h5", "id_300")
539
+ def _get_hs300_member(self) -> pd.DataFrame:
540
+ """沪深300成分股面板 (dates × stocks), 值为 0/1"""
541
+ return self._get_index_member_panel('000300.SH', '沪深300')
542
+
543
+ @register_route("stk_daily.h5", "id_500")
544
+ def _get_zz500_member(self) -> pd.DataFrame:
545
+ """中证500成分股面板 (dates × stocks), 值为 0/1"""
546
+ return self._get_index_member_panel('000905.SH', '中证500')
547
+
548
+ def _get_index_member_panel(self, index_code: str, index_name: str) -> pd.DataFrame:
549
+ """指数成分股面板"""
550
+ dates = self._get_trade_dates()
551
+ codes = self._get_stock_codes()
552
+
553
+ # 查询成分股
554
+ query = f'{index_name}成分股列表'
555
+ df = self._query_index_data(query)
556
+
557
+ member_codes = set()
558
+ if not df.empty:
559
+ for col in df.columns:
560
+ vals = df[col].astype(str).str.strip()
561
+ member_codes.update(vals[vals.str.match(r'\d{6}\.(SH|SZ)')].tolist())
562
+
563
+ # 构建面板
564
+ panel = pd.DataFrame(0, index=dates, columns=codes)
565
+ for c in codes:
566
+ if c in member_codes:
567
+ panel[c] = 1
568
+ return panel
569
+
570
+ def _get_factor(self, factor_dir: str, factor_name: str) -> pd.DataFrame:
571
+ """通过 iFinD 获取因子数据"""
572
+ beg_year = self._date_beg[:4]
573
+ beg_month = self._date_beg[4:6]
574
+ end_month = self._date_end[4:6]
575
+ query = (
576
+ f'{factor_name}因子{beg_year}年{beg_month}月至{end_month}月'
577
+ )
578
+ df = self._query_stock_info(query)
579
+ if df.empty:
580
+ raise RuntimeError(f"无法获取因子: {factor_name}")
581
+ codes = self._get_stock_codes()
582
+ return self._pivot_stock_info(df, codes)
583
+
584
+ # ── Week 12: 真实数据拉取 + H5 持久化 ────────────────────────
585
+
586
+ def fetch_to_h5(
587
+ self,
588
+ output_dir: str | Path,
589
+ factor_names: list[str] | None = None,
590
+ keys: list[str] | None = None,
591
+ ) -> dict:
592
+ """从 iFinD 拉取数据, 写为 HDF5 格式 (兼容 LoadDataNode)。
593
+
594
+ 输出文件结构:
595
+ {output_dir}/
596
+ ├── stk_daily.h5 # 7 keys: cp, st, suspend, ud_limit,
597
+ │ # ipo_days, id_citic1, mv_float
598
+ ├── index_daily.h5 # 1 key: index_cp
599
+ ├── stklist.h5 # 股票代码列表
600
+ ├── trade_dt.h5 # 交易日历
601
+ └── {factor_name}.h5 # 因子数据 (单 key='data')
602
+
603
+ Args:
604
+ output_dir: 输出目录 (不存在自动创建)
605
+ factor_names: 因子名列表 (空=不拉因子)
606
+ keys: stk_daily.h5 要拉的 key 列表 (默认全 7 个)
607
+
608
+ Returns:
609
+ dict: {file: {key: shape}, ...} 拉取统计
610
+ """
611
+ output_dir = Path(output_dir)
612
+ ensure_dir(output_dir)
613
+ all_keys = ['cp', 'st', 'suspend', 'ud_limit', 'ipo_days', 'id_citic1', 'mv_float']
614
+ if keys is None:
615
+ keys = all_keys
616
+ stats: dict = {}
617
+
618
+ # 1. 拉股票池 + 交易日历
619
+ logger.info("[1/4] 拉股票池 (%s)...", self._universe)
620
+ stklist = self._get_stock_axis_raw()
621
+ trade_dt = self._get_trade_dt_raw()
622
+ stklist.to_hdf(output_dir / 'stklist.h5', key='data', mode='w')
623
+ trade_dt.to_hdf(output_dir / 'trade_dt.h5', key='data', mode='w')
624
+ stats['stklist.h5'] = {'data': stklist.shape}
625
+ stats['trade_dt.h5'] = {'data': trade_dt.shape}
626
+ logger.info(
627
+ " ✓ stklist.h5 (shape=%s), trade_dt.h5 (shape=%s)",
628
+ stklist.shape, trade_dt.shape,
629
+ )
630
+
631
+ # 2. 拉 stk_daily 7 keys
632
+ logger.info("[2/4] 拉 stk_daily.h5 (keys=%s)...", keys)
633
+ stk_daily_stats: dict = {}
634
+ with pd.HDFStore(output_dir / 'stk_daily.h5', mode='w') as store:
635
+ for key in keys:
636
+ if key not in all_keys:
637
+ logger.warning(" ⚠ 跳过未知 key: %s", key)
638
+ continue
639
+ try:
640
+ df = self.load_h5('stk_daily.h5', key)
641
+ if df is None or df.empty:
642
+ logger.warning(" ⚠ %s: 空数据, 跳过", key)
643
+ continue
644
+ # 转换 Int64 (nullable) → int64, HDF5 不支持 nullable int
645
+ df = _df_to_hdf_safe(df)
646
+ store.put(key, df, format='table')
647
+ stk_daily_stats[key] = df.shape
648
+ logger.info(" ✓ %s: shape=%s", key, df.shape)
649
+ except Exception as e:
650
+ logger.error(" ✗ %s 失败: %s", key, e)
651
+ stats['stk_daily.h5'] = stk_daily_stats
652
+
653
+ # 3. 拉 index_daily (沪深300 + 中证500)
654
+ logger.info("[3/4] 拉 index_daily.h5 (index_cp)...")
655
+ try:
656
+ index_cp = self._get_index_cp()
657
+ with pd.HDFStore(output_dir / 'index_daily.h5', mode='w') as store:
658
+ store.put('index_cp', _df_to_hdf_safe(index_cp), format='table')
659
+ stats['index_daily.h5'] = {'index_cp': index_cp.shape}
660
+ logger.info(" ✓ index_cp: shape=%s", index_cp.shape)
661
+ except Exception as e:
662
+ logger.error(" ✗ index_cp 失败: %s", e)
663
+ stats['index_daily.h5'] = {'index_cp': None}
664
+
665
+ # 4. 拉因子 (可选)
666
+ if factor_names:
667
+ logger.info("[4/4] 拉因子 (%s)...", factor_names)
668
+ for fname in factor_names:
669
+ try:
670
+ factor = self._get_factor(fname, fname)
671
+ factor = _df_to_hdf_safe(factor)
672
+ factor.to_hdf(output_dir / f'{fname}.h5', key='data', mode='w')
673
+ stats[f'{fname}.h5'] = {'data': factor.shape}
674
+ logger.info(" ✓ %s: shape=%s", fname, factor.shape)
675
+ except Exception as e:
676
+ logger.error(" ✗ %s 失败: %s", fname, e)
677
+ stats[f'{fname}.h5'] = {'data': None}
678
+ else:
679
+ logger.info("[4/4] 跳过因子拉取 (factor_names 为空)")
680
+
681
+ return stats
682
+
683
+ def get_universe_stocks(self) -> list[str]:
684
+ """获取股票池代码列表 (公开 API, 缓存)。"""
685
+ return self._get_stock_codes()
686
+
687
+
688
+ # ── B5: 类定义后扫描所有 @register_route 标记, 一次性填充 _ROUTE_TABLE ──
689
+ IFinDDatabase._ROUTE_TABLE = _collect_routes(IFinDDatabase)
@@ -0,0 +1 @@
1
+ # coding: utf-8