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,91 @@
1
+ # coding: utf-8
2
+ """Pydantic 化节点公共基类 / Pydantic-config Node Base.
3
+
4
+ 抽取 12 节点 __init__ 的 Union 校验样板 (~144 行重复) 到此处.
5
+ 子类只需声明 ``ConfigSchema = XxxNodeConfig`` 即可:
6
+
7
+ >>> class FooNode(PydanticConfigNode):
8
+ ... ConfigSchema = FooNodeConfig
9
+ ... def _execute(self, input_data=None, **kw): ...
10
+
11
+ 构造时:
12
+ - ``isinstance(config, ConfigSchema)`` → 直接使用
13
+ - ``dict / None`` → ``ConfigSchema.model_validate``
14
+ - 其他类型 → ``TypeError``
15
+ """
16
+
17
+ from typing import Any, ClassVar, Optional, Union
18
+
19
+ from pydantic import BaseModel
20
+
21
+ from QuantNodes.core.node import BaseNode
22
+
23
+
24
+ class PydanticConfigNode(BaseNode):
25
+ """统一处理 12 节点的 ``Union[dict, ConfigSchema, None]`` 配置入参.
26
+
27
+ 子类必须设置 ``ConfigSchema``; 构造完成后, 实例上挂载:
28
+
29
+ - ``self.cfg`` : ``ConfigSchema`` 实例 (推荐访问入口)
30
+ - ``self.config``: ``dict`` (BaseNode 协议要求)
31
+ - ``self._xxx`` : 由 ``_ALIASES`` 声明的 cfg 字段别名 (向后兼容)
32
+
33
+ 自动 alias 规则 (Phase G3):
34
+ 声明 ``_ALIASES = {"_data_path": "data_path", ...}`` 后, 基类
35
+ ``__init__`` 自动 copy cfg 字段到 self._xxx。list/tuple 字段会被浅拷贝
36
+ 以避免下游误改 cfg。
37
+ """
38
+
39
+ ConfigSchema: ClassVar[type[BaseModel]]
40
+ _ALIASES: ClassVar[dict[str, str]] = {}
41
+
42
+ def __init__(
43
+ self,
44
+ name: Optional[str] = None,
45
+ config: Union[dict, BaseModel, None] = None,
46
+ **kwargs: Any,
47
+ ) -> None:
48
+ Schema = self.ConfigSchema
49
+ if isinstance(config, Schema):
50
+ cfg = config
51
+ super().__init__(name, cfg.model_dump(), **kwargs)
52
+ elif isinstance(config, dict) or config is None:
53
+ cfg = Schema.model_validate(config or {})
54
+ super().__init__(name, config, **kwargs)
55
+ else:
56
+ raise TypeError(
57
+ f"config must be dict/None/{Schema.__name__}, "
58
+ f"got {type(config).__name__}"
59
+ )
60
+ self.cfg: BaseModel = cfg
61
+ # Auto-apply aliases
62
+ for alias, field in self._ALIASES.items():
63
+ value = getattr(self.cfg, field)
64
+ if isinstance(value, list):
65
+ value = list(value) # shallow copy
66
+ elif isinstance(value, tuple):
67
+ value = list(value)
68
+ setattr(self, alias, value)
69
+
70
+ def _ctx(self, context: dict) -> dict:
71
+ """Drill into context['LoadData'] dict (Phase G4)."""
72
+ return context.get("LoadData", {})
73
+
74
+ def _ctx_load(self, context: dict, key: str, default=None):
75
+ """Get one key from context['LoadData'] (Phase G4).
76
+
77
+ Replaces 13 sites of: ``context.get('LoadData', {}).get(KEY)``.
78
+ """
79
+ return self._ctx(context).get(key, default)
80
+
81
+ def _factor_data(self, context: dict):
82
+ """Return FactorNeutralize output if set, else FactorPreprocess (Phase G4).
83
+
84
+ Replaces the 4-line pattern in 4 analysis nodes (ic_analyzer,
85
+ factor_score, group_analyzer, risk_correlation). Returns None
86
+ if neither is set, leaving None-handling to the caller.
87
+ """
88
+ neutralized = context.get("FactorNeutralize")
89
+ if neutralized is not None:
90
+ return neutralized
91
+ return context.get("FactorPreprocess")
@@ -0,0 +1,48 @@
1
+ # coding: utf-8
2
+ """Node 4: 调仓日生成 / Adjust Date Node
3
+
4
+ Migrated from date_utils.py:134-191 get_adjust_date()
5
+ """
6
+
7
+ import pandas as pd
8
+
9
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
10
+ from QuantNodes.research.factor_test.nodes.configs import AdjustDateNodeConfig
11
+ from QuantNodes.research.factor_test.utils.date_utils import get_adjust_date
12
+
13
+
14
+ class AdjustDateNode(PydanticConfigNode):
15
+ """根据起始日、截止日、调仓模式生成调仓日序列
16
+
17
+ 输入: context["LoadData"] 的输出
18
+ 输出: adj_dates (yyyymmdd int DataFrame)
19
+ """
20
+
21
+ ConfigSchema = AdjustDateNodeConfig
22
+ _ALIASES = {
23
+ "_adj_date_beg": "adj_date_beg",
24
+ "_adj_date_end": "adj_date_end",
25
+ "_adj_mode": "adj_mode",
26
+ }
27
+
28
+ def _execute(self, input_data=None, **kwargs) -> pd.DataFrame:
29
+ # T0-3: H10 启动校验 (避免静默跑废日期)
30
+ if self._adj_date_beg is None or self._adj_date_end is None:
31
+ raise ValueError(
32
+ f"AdjustDateNode 需要 adj_date_beg 和 adj_date_end 字段 "
33
+ f"(H10: 默认 None, 当前 beg={self._adj_date_beg}, end={self._adj_date_end})"
34
+ )
35
+
36
+ context = kwargs.get('context', {})
37
+ load_data = context.get('LoadData') or input_data or {}
38
+
39
+ trade_dt = load_data['trade_dt']
40
+
41
+ adj_dates = get_adjust_date(
42
+ trade_dt,
43
+ self._adj_date_beg,
44
+ self._adj_date_end,
45
+ tuple(self._adj_mode)
46
+ )
47
+
48
+ return adj_dates
@@ -0,0 +1,240 @@
1
+ # coding: utf-8
2
+ """12 节点 Pydantic 配置模型 / Node Configuration Models
3
+
4
+ Phase 3.1 T0: 12 节点的 __init__ 改 Union 接受 (dict / *Config / None),
5
+ 内部 model_validate() 校验, 保留 self._xxx 实例属性 (向后兼容 5 处测试).
6
+
7
+ 拼写错 (extra="forbid") 立即 ValidationError, 跨进程 model_dump() → dict 链稳定.
8
+
9
+ Phase R3-A (2026-06-19): Schema 收敛
10
+ - 8 节点 Config 直接继承 config.py 子模型 (避免字段重复定义)
11
+ - 4 节点 (LoadData/AdjustDate/SamplePool/Report) 字段语义不同, 保留独立定义
12
+ - 单一真值源: 改默认值只改 config.py 一处即可同步到 nodes/configs.py
13
+
14
+ Phase 1.4: register_node_config 装饰器
15
+ - 替代手工维护的 NODE_CONFIG_SCHEMAS 路由表
16
+ - 用法: @register_node_config("GroupAnalyzer") class GroupAnalyzerNodeConfig(GroupSetting): ...
17
+ - 自动注册到 NODE_CONFIG_SCHEMAS, 避免新增节点时漏改路由表
18
+ """
19
+
20
+ from typing import Optional
21
+ from pydantic import BaseModel, Field, ConfigDict
22
+
23
+ from QuantNodes.research.factor_test.config import (
24
+ FactorSetting,
25
+ GroupSetting,
26
+ ICSetting,
27
+ LongShortSetting,
28
+ PreprocessSetting,
29
+ RiskCorrelationSetting,
30
+ ScoreSetting,
31
+ TradableSetting,
32
+ )
33
+
34
+
35
+ _FORBID = ConfigDict(extra="forbid")
36
+
37
+
38
+ class _NodeBase(BaseModel):
39
+ """12 节点 Config 公共基类
40
+ - extra="forbid": 拼写错立即 ValidationError (新防线)
41
+ """
42
+ model_config = _FORBID
43
+
44
+
45
+ # ── 节点名 → Config Schema 路由表 (Phase 1.4: 装饰器自动注册) ─────
46
+ NODE_CONFIG_SCHEMAS: dict[str, type[BaseModel]] = {}
47
+
48
+
49
+ def register_node_config(node_name: str):
50
+ """装饰器: 将 Config Schema 注册到 NODE_CONFIG_SCHEMAS[node_name]。
51
+
52
+ Phase 1.4: 替代手工维护的路由表。新增节点 Config 时只需:
53
+ @register_node_config("MyNode")
54
+ class MyNodeConfig(BaseModel):
55
+ ...
56
+
57
+ Args:
58
+ node_name: 节点在 pipeline 中的名称 (与 BaseNode 子类的注册名一致)
59
+
60
+ Returns:
61
+ 装饰器函数, 接受 Config 类并原样返回 (同时写入 NODE_CONFIG_SCHEMAS)
62
+
63
+ Raises:
64
+ TypeError: 装饰的不是 pydantic BaseModel 子类
65
+ ValueError: 重复注册同一 node_name (且类不同)
66
+ """
67
+ def decorator(cls: type[BaseModel]) -> type[BaseModel]:
68
+ if not issubclass(cls, BaseModel):
69
+ raise TypeError(
70
+ f"@register_node_config({node_name!r}) requires a pydantic "
71
+ f"BaseModel subclass, got {cls!r}"
72
+ )
73
+ if node_name in NODE_CONFIG_SCHEMAS:
74
+ existing = NODE_CONFIG_SCHEMAS[node_name]
75
+ if existing is not cls:
76
+ raise ValueError(
77
+ f"NODE_CONFIG_SCHEMAS[{node_name!r}] already registered "
78
+ f"to {existing.__name__}; refusing to overwrite with {cls.__name__}"
79
+ )
80
+ NODE_CONFIG_SCHEMAS[node_name] = cls
81
+ return cls
82
+ return decorator
83
+
84
+
85
+ # ── 注册所有 12 节点 Config (Phase 1.4: 装饰器替代手工表) ─────────
86
+ @register_node_config("LoadData")
87
+ class LoadDataNodeConfig(_NodeBase):
88
+ """Node 1: LoadDataNode 配置
89
+
90
+ P-2: data_path 改 Field(...) 必填, 启动时报 ValidationError (None/缺字段).
91
+ 现有 test_data_loader_edges.py:309-316 已显式提供 load_keys 但缺 data_path,
92
+ 需更新为提供 data_path (load_keys 测试不变).
93
+ """
94
+ data_path: str = Field(..., description="数据根目录 (P-2 必填, 启动校验)")
95
+ load_keys: list = Field(
96
+ default_factory=lambda: [
97
+ "stklist", "trade_dt", "cp", "id_citic1", "mv_float",
98
+ "st", "suspend", "ud_limit", "ipo_days",
99
+ ],
100
+ description="需要加载的数据 key 列表 (M7 默认含 tradability 必需键)",
101
+ )
102
+ factor: Optional[FactorSetting] = Field(
103
+ default=None, description="因子配置 (None=不加载因子)",
104
+ )
105
+
106
+
107
+ @register_node_config("SamplePoolFilter")
108
+ class SamplePoolNodeConfig(_NodeBase):
109
+ """Node 2: SamplePoolFilterNode 配置
110
+
111
+ M9: index_mapping 可自定义,合并全局默认 INDEX_MAPPING。
112
+ M12: i18n_name_map 可自定义行业代码→名称映射,覆盖全局默认 INDUSTRY_MAPPING。
113
+
114
+ 字段语义独立于 PreprocessSetting (sample_index/sample_industry 是节点专属),
115
+ 保留独立定义.
116
+ """
117
+ sample_index: str = Field(
118
+ default="all", description="样本池: all/HS300/ZZ500/ZZ800/custom",
119
+ )
120
+ sample_industry: str = Field(
121
+ default="all", description="行业筛选: all/中信行业名",
122
+ )
123
+ sample_index_customdir: Optional[tuple] = Field(
124
+ default=None, description="自定义样本池路径 (sample_index=custom 时必填)",
125
+ )
126
+ index_mapping: Optional[dict[str, tuple[str, str]]] = Field(
127
+ default=None, description="自定义指数映射 (覆盖全局默认 INDEX_MAPPING)",
128
+ )
129
+ i18n_name_map: Optional[dict[str, str]] = Field(
130
+ default=None, description="自定义行业代码→名称映射 (覆盖全局默认 INDUSTRY_MAPPING)",
131
+ )
132
+
133
+
134
+ @register_node_config("TradabilityFilter")
135
+ class TradabilityNodeConfig(_NodeBase):
136
+ """Node 3: TradabilityFilterNode 配置"""
137
+ tradable: TradableSetting = Field(
138
+ default_factory=TradableSetting,
139
+ description="可交易性配置 (no_st/no_suspended/no_up_down_limit/min_ipo_days/trace)",
140
+ )
141
+
142
+
143
+ @register_node_config("AdjustDate")
144
+ class AdjustDateNodeConfig(_NodeBase):
145
+ """Node 4: AdjustDateNode 配置
146
+
147
+ H10 兼容: adj_date_beg/end 默认 None → _execute 启动校验抛 ValueError,
148
+ 避免静默跑废日期范围.
149
+
150
+ 字段语义独立 (adj_date_beg/end 在 PreprocessSetting 中是预处理日期窗口,
151
+ 在节点中是调仓日生成范围), 保留独立定义.
152
+ """
153
+ adj_date_beg: Optional[int] = Field(
154
+ default=None, description="起始日期 yyyymmdd (None → 启动报错)",
155
+ )
156
+ adj_date_end: Optional[int] = Field(
157
+ default=None, description="截止日期 yyyymmdd (None → 启动报错)",
158
+ )
159
+ adj_mode: list = Field(
160
+ default_factory=lambda: ["M", "end"],
161
+ description="调仓模式: [mode, position], mode=M/W/Q/D, position=end/start",
162
+ )
163
+
164
+
165
+ @register_node_config("FactorPreprocess")
166
+ class PreprocessNodeConfig(BaseModel):
167
+ """Node 5: FactorPreprocessNode 配置 (R3-A: 继承 config.py::PreprocessSetting 子集字段).
168
+
169
+ 字段全部从 PreprocessSetting 继承 (missing/extreme/norm/mad_n/pct_low/pct_high/i18n_name_map),
170
+ 通过 model_dump(include={...}) 切片传入。改 mad_n 默认值只需改 PreprocessSetting 一处.
171
+ """
172
+ model_config = _FORBID
173
+ missing: str = PreprocessSetting.model_fields["missing"]
174
+ extreme: str = PreprocessSetting.model_fields["extreme"]
175
+ norm: str = PreprocessSetting.model_fields["norm"]
176
+ mad_n: float = PreprocessSetting.model_fields["mad_n"]
177
+ pct_low: float = PreprocessSetting.model_fields["pct_low"]
178
+ pct_high: float = PreprocessSetting.model_fields["pct_high"]
179
+ i18n_name_map: Optional[dict[str, str]] = PreprocessSetting.model_fields["i18n_name_map"]
180
+
181
+
182
+ @register_node_config("FactorNeutralize")
183
+ class NeutralizeNodeConfig(BaseModel):
184
+ """Node 6: FactorNeutralizeNode 配置 (R3-A: 继承 PreprocessSetting 中性化字段)."""
185
+ model_config = _FORBID
186
+ industry_neutral: bool = PreprocessSetting.model_fields["industry_neutral"]
187
+ risk_neutral: bool = PreprocessSetting.model_fields["risk_neutral"]
188
+ risk_factors: list = PreprocessSetting.model_fields["risk_factors"]
189
+
190
+
191
+ @register_node_config("ICAnalyzer")
192
+ class ICAnalyzerNodeConfig(ICSetting):
193
+ """Node 7: ICAnalyzerNode 配置 (R3-A: 继承 ICSetting)."""
194
+ model_config = _FORBID
195
+
196
+
197
+ @register_node_config("GroupAnalyzer")
198
+ class GroupAnalyzerNodeConfig(GroupSetting):
199
+ """Node 8: GroupAnalyzerNode 配置 (R3-A: 继承 GroupSetting)."""
200
+ model_config = _FORBID
201
+
202
+
203
+ @register_node_config("LongShort")
204
+ class LongShortNodeConfig(LongShortSetting):
205
+ """Node 9: LongShortNode 配置 (R3-A: 继承 LongShortSetting)."""
206
+ model_config = _FORBID
207
+
208
+
209
+ @register_node_config("FactorScore")
210
+ class ScoreNodeConfig(ScoreSetting):
211
+ """Node 10: FactorScoreNode 配置 (R3-A: 继承 ScoreSetting).
212
+
213
+ T0-2: 4 字段 (enabled/n_industries=29/n_size_groups=3/n_quantile_groups=5)
214
+ 全部继承自 ScoreSetting.
215
+ """
216
+ model_config = _FORBID
217
+
218
+
219
+ @register_node_config("RiskCorrelation")
220
+ class RiskCorrelationNodeConfig(RiskCorrelationSetting):
221
+ """Node 11: RiskCorrelationNode 配置 (R3-A: 继承 RiskCorrelationSetting)."""
222
+ model_config = _FORBID
223
+
224
+
225
+ @register_node_config("FactorTestReport")
226
+ class ReportNodeConfig(_NodeBase):
227
+ """Node 12: FactorTestReportNode 配置
228
+
229
+ P-1: dir 路径优先级 env QUANTNODES_OUTPUT_DIR > expanduser > default='./output/'.
230
+
231
+ 字段语义独立 (OutputSetting.dir vs ReportNodeConfig.dir 同名但生命周期不同),
232
+ 保留独立定义.
233
+ """
234
+ dir: str = Field(
235
+ default="./output/", description="输出目录 (P-1: env > expanduser > default)",
236
+ )
237
+ format: list = Field(
238
+ default_factory=lambda: ["parquet", "json"],
239
+ description="输出格式",
240
+ )
@@ -0,0 +1,87 @@
1
+ # coding=utf-8
2
+ """Node 6: 因子中性化 / Factor Neutralize Node
3
+
4
+ Migrated from factor_utils.py:534-625 neutralize()
5
+
6
+ Phase 2.1 (Chain of Responsibility):
7
+ - 原 70 行 _neutralize 三个 if/elif 分支 (industry only / risk only / both)
8
+ 替换为 chain dispatch.
9
+ - 中性化逻辑 (设计矩阵 X 组装) 抽到 nodes/neutralizers.py:
10
+ Neutralizer (ABC) / IndustryNeutralizer / RiskNeutralizer
11
+ build_neutralizer_chain() / apply_neutralizer_chain()
12
+ - 新增中性化类型 (如 StyleNeutralizer) 只需新增一个 Neutralizer 子类,
13
+ _execute 无需修改.
14
+ """
15
+
16
+ import logging
17
+
18
+ import pandas as pd
19
+
20
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
21
+ from QuantNodes.research.factor_test.nodes.configs import NeutralizeNodeConfig
22
+ from QuantNodes.research.factor_test.nodes.neutralizers import (
23
+ apply_neutralizer_chain,
24
+ build_neutralizer_chain,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ class FactorNeutralizeNode(PydanticConfigNode):
31
+ """行业/风险因子中性化 (OLS 残差)
32
+
33
+ 输入: factor_std, industry, risk_factors
34
+ 输出: factor_neutral
35
+ """
36
+
37
+ ConfigSchema = NeutralizeNodeConfig
38
+ _ALIASES = {
39
+ "_if_industry": "industry_neutral",
40
+ "_if_risk": "risk_neutral",
41
+ "_risk_factor_specs": "risk_factors",
42
+ }
43
+
44
+ def _execute(self, input_data=None, **kwargs) -> pd.DataFrame:
45
+ context = kwargs.get('context', {})
46
+ factor_std = context.get('FactorPreprocess')
47
+ if factor_std is None:
48
+ raise ValueError("因子预处理数据缺失")
49
+
50
+ if not self._if_industry and not self._if_risk:
51
+ return factor_std
52
+
53
+ industry = self._ctx_load(context, 'id_citic1')
54
+ if industry is None and self._if_industry:
55
+ raise ValueError("行业数据缺失")
56
+
57
+ # 加载风险因子
58
+ risk_data = []
59
+ if self._if_risk and self._risk_factor_specs:
60
+ loader = self._ctx_load(context, '_loader')
61
+ for file_key, factor_key in self._risk_factor_specs:
62
+ try:
63
+ if file_key == 'risk_factor.h5':
64
+ rf = loader.load_h5(file_key, factor_key)
65
+ else:
66
+ rf = loader.load_custom((file_key, factor_key))
67
+ if loader.valid_shape(rf):
68
+ rf = loader.add_index(rf)
69
+ risk_data.append(rf)
70
+ except Exception as e:
71
+ logger.warning(f"加载风险因子 {factor_key} 失败: {e}")
72
+
73
+ return self._neutralize(factor_std, self._if_industry, industry,
74
+ self._if_risk, risk_data)
75
+
76
+ def _neutralize(self, factor_i, if_industry, industry, if_risk, risk_data):
77
+ """中性化处理 (Phase 2.1: 委托给 chain).
78
+
79
+ Phase 2.1 行为完全等价于旧实现:
80
+ - chain 为空 → 返回 factor_i (全 nan, 与原 _neutralize 入口一致)
81
+ - 4 种 flag 组合的输出与原 branch 1/2/3 bitwise 一致
82
+ """
83
+ chain = build_neutralizer_chain(if_industry, if_risk, industry, risk_data)
84
+ if not chain:
85
+ # 无 neutralizer 时返回原 factor (保留 nan 模式), 与旧 line 40 一致
86
+ return factor_i
87
+ return apply_neutralizer_chain(factor_i, chain)
@@ -0,0 +1,222 @@
1
+ # coding: utf-8
2
+ """Node 5: 因子预处理 / Factor Preprocess Node
3
+
4
+ Migrated from factor_utils.py:310-532 preprocess_onePeriod + preprocess_factor
5
+ Phase 1: 原样迁移保持行为一致性
6
+ Phase 2: 逐步替换为 QuantNodes section_ops 算子
7
+ Phase 3 (H5, 2026-06-20): vectorised _preprocess_one_period's per-date
8
+ Python loop into DataFrame-level operations (groupby/transform/clip/sub).
9
+ Expected 10-100x speedup on the preprocess step.
10
+ """
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ from scipy.stats import norm as scipy_norm
15
+
16
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
17
+ from QuantNodes.research.factor_test.nodes.configs import PreprocessNodeConfig
18
+ from QuantNodes.research.factor_test.nodes.preprocess_strategies import (
19
+ build_preprocess_strategies,
20
+ )
21
+
22
+
23
+ class FactorPreprocessNode(PydanticConfigNode):
24
+ """因子预处理: 缺失值填充 + 去极值 + 标准化
25
+
26
+ 输入: factor, tradable, adj_dates, industry
27
+ 输出: factor_std (预处理后的因子, 仅调仓日)
28
+
29
+ Phase 2.2 (Strategy pattern): 3 类预处理 (missing fill / de-extreme /
30
+ normalise) 抽到 preprocess_strategies.py, _preprocess_vectorized 退化为
31
+ 简单的 3 行 dispatch. 新增策略类型 (如 winsorize) 只需新增一个 Strategy
32
+ 子类, 本文件无需修改.
33
+ """
34
+
35
+ ConfigSchema = PreprocessNodeConfig
36
+ _ALIASES = {
37
+ "_missing": "missing",
38
+ "_extreme": "extreme",
39
+ "_norm": "norm",
40
+ "_mad_n": "mad_n",
41
+ "_pct_low": "pct_low",
42
+ "_pct_high": "pct_high",
43
+ "_i18n_name_map": "i18n_name_map",
44
+ }
45
+
46
+ def _execute(self, input_data=None, **kwargs) -> pd.DataFrame:
47
+ context = kwargs.get('context', {})
48
+ factor = self._ctx_load(context, 'factor')
49
+ tradable = context.get('TradabilityFilter')
50
+ adj_dates = context.get('AdjustDate')
51
+
52
+ if factor is None:
53
+ raise ValueError("因子数据缺失")
54
+ if tradable is None:
55
+ tradable = pd.DataFrame(np.ones_like(factor.values))
56
+
57
+ # 合并可交易性
58
+ tradable_factor = factor * tradable
59
+
60
+ # 提取调仓日的因子值
61
+ adj_date_values = (
62
+ adj_dates.iloc[:, 0].values
63
+ if isinstance(adj_dates, pd.DataFrame)
64
+ else adj_dates
65
+ )
66
+ tradable_factor_adj = tradable_factor.loc[tradable_factor.index.isin(adj_date_values)]
67
+ tradable_adj = tradable.loc[tradable.index.isin(adj_date_values)]
68
+
69
+ # 行业数据
70
+ industry = self._ctx_load(context, 'id_citic1')
71
+ industry_adj = (
72
+ industry.loc[industry.index.isin(adj_date_values)]
73
+ if industry is not None else None
74
+ )
75
+
76
+ # Vectorised preprocessing (H5, 2026-06-20).
77
+ result = self._preprocess_vectorized(
78
+ tradable_factor_adj, tradable_adj, industry_adj,
79
+ missing=self._missing, extreme=self._extreme, norm=self._norm,
80
+ )
81
+
82
+ # 清理索引
83
+ if hasattr(result.index, 'get_level_values'):
84
+ result.index = result.index.get_level_values(0).values
85
+ if hasattr(result.columns, 'get_level_values'):
86
+ result.columns = result.columns.get_level_values(0).values
87
+
88
+ return result
89
+
90
+ # ------------------------------------------------------------------
91
+ # Vectorised preprocessing (H5, 2026-06-20, refactored Phase 2.2)
92
+ # ------------------------------------------------------------------
93
+ def _preprocess_vectorized(
94
+ self,
95
+ tradable_factor: pd.DataFrame,
96
+ tradable: pd.DataFrame,
97
+ industry: pd.DataFrame | None,
98
+ *,
99
+ missing: str,
100
+ extreme: str,
101
+ norm: str,
102
+ ) -> pd.DataFrame:
103
+ """Apply missing-fill + de-extreme + normalise across all dates.
104
+
105
+ Replaces the previous ``apply(_preprocess_one_period, axis=1)`` loop.
106
+ Each step is a vectorised DataFrame operation, dispatched via
107
+ strategy pattern (Phase 2.2).
108
+
109
+ 1. Tradable mask (per-row): factor where tradable != 0 & notnan,
110
+ else NaN.
111
+ 2. Missing fill ('ind_avg'): per (date, industry) group mean.
112
+ 3. De-extreme ('median'): per-date median +/- n * MAD.
113
+ 4. De-extreme ('pct_shrink'): per-date quantiles.
114
+ 5. Norm ('zscore'): per-date (x - mean) / std.
115
+ 6. Norm ('norm'): per-date rank -> scipy.stats.norm.ppf.
116
+
117
+ Behavioural parity: produces same NaN pattern and same numeric
118
+ result as the per-date loop, to within floating-point noise.
119
+
120
+ Phase 2.2: 3 个 strategy 通过 build_preprocess_strategies() 构造,
121
+ apply() 顺序串接. 原硬编码 if 链消除.
122
+ """
123
+ result = tradable_factor.copy()
124
+
125
+ # 1. Tradable mask (vectorised)
126
+ if tradable is not None:
127
+ tradable_mask = tradable.reindex(index=result.index, columns=result.columns)
128
+ tradable_mask = tradable_mask.notna() & (tradable_mask != 0)
129
+ result = result.where(tradable_mask, np.nan)
130
+
131
+ # Phase 2.2: dispatch via 3 strategies
132
+ missing_s, extreme_s, norm_s = build_preprocess_strategies(missing, extreme, norm)
133
+ result = missing_s.apply(result, industry=industry)
134
+ result = extreme_s.apply(
135
+ result, mad_n=self._mad_n,
136
+ pct_low=self._pct_low, pct_high=self._pct_high,
137
+ )
138
+ result = norm_s.apply(result)
139
+
140
+ return result
141
+
142
+ # ------------------------------------------------------------------
143
+ # Legacy per-date loop (kept for reference / fallback)
144
+ # ------------------------------------------------------------------
145
+ def _preprocess_one_period(self, factor_i, tradable_adj, industry_adj, method):
146
+ """对单日因子值进行预处理 (legacy per-date loop, kept for reference).
147
+
148
+ H5 (2026-06-20): _execute now uses _preprocess_vectorized instead.
149
+ This method is preserved for unit-testing the legacy semantics but
150
+ is no longer called from the pipeline.
151
+ """
152
+ factor_i_new_all = factor_i.copy()
153
+
154
+ # 获取当日可交易和行业数据
155
+ date_key = factor_i.name[0] if hasattr(factor_i.name, '__len__') else factor_i.name
156
+
157
+ if tradable_adj is not None and date_key in tradable_adj.index:
158
+ tradable_i = tradable_adj.loc[date_key]
159
+ else:
160
+ return factor_i_new_all
161
+
162
+ if industry_adj is not None and date_key in industry_adj.index:
163
+ ind_i = industry_adj.loc[date_key]
164
+ else:
165
+ ind_i = pd.Series(np.nan, index=factor_i.index)
166
+
167
+ # 合并数据
168
+ df = pd.DataFrame({
169
+ 'factor': factor_i,
170
+ 'ind': ind_i,
171
+ 'tradable': tradable_i,
172
+ }, index=factor_i.index)
173
+
174
+ # 剔除不可交易
175
+ df = df.loc[df['tradable'].notna() & (df['tradable'] != 0)]
176
+ if df.empty:
177
+ return factor_i_new_all
178
+
179
+ df['ind'] = df['ind'].replace(np.nan, 0)
180
+ df['factor_filled'] = df['factor'].copy()
181
+
182
+ # 1. 缺失值处理
183
+ if method['missing'] == 'ind_avg':
184
+ has_ind = df['ind'] > 0
185
+ if has_ind.any():
186
+ df.loc[has_ind, 'factor_filled'] = (
187
+ df.loc[has_ind].groupby('ind')['factor']
188
+ .transform(lambda x: x.fillna(x.mean()))
189
+ )
190
+
191
+ # 2. 去极值
192
+ if method['extreme'] == 'median':
193
+ n = self._mad_n # M5: 可调
194
+ d_m = df['factor_filled'].dropna().median()
195
+ d_mad = (df['factor_filled'] - d_m).abs().dropna().median()
196
+ df['factor_filled'] = df['factor_filled'].clip(d_m - n * d_mad, d_m + n * d_mad)
197
+ elif method['extreme'] == 'pct_shrink':
198
+ q1 = df['factor_filled'].quantile(self._pct_low) # M5: 可调
199
+ q2 = df['factor_filled'].quantile(self._pct_high) # M5: 可调
200
+ df['factor_filled'] = df['factor_filled'].clip(q1, q2)
201
+
202
+ # 3. 标准化
203
+ if method['norm'] == 'zscore':
204
+ f_mean = df['factor_filled'].dropna().mean()
205
+ f_std = df['factor_filled'].dropna().std(ddof=1)
206
+ if f_std > 0:
207
+ df['factor_filled'] = (df['factor_filled'] - f_mean) / f_std
208
+ elif method['norm'] == 'norm':
209
+ valid = df['factor_filled'].notna()
210
+ if valid.sum() > 1:
211
+ df.loc[valid, 'rank'] = df.loc[valid, 'factor_filled'].rank(pct=True)
212
+ # 处理 0 和 1 的边界
213
+ ranks = df['rank'].dropna()
214
+ df['rank'] = df['rank'].clip(
215
+ lower=ranks[ranks > 0].min() * 0.5 if (ranks > 0).any() else 0.01,
216
+ upper=(ranks[ranks < 1].max() + 1) * 0.5 if (ranks < 1).any() else 0.99
217
+ )
218
+ df['factor_filled'] = scipy_norm.ppf(df['rank'], 0, 1)
219
+
220
+ # 写回
221
+ factor_i_new_all.loc[df.index] = df['factor_filled'].values
222
+ return factor_i_new_all