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,277 @@
1
+ # coding=utf-8
2
+ """Preprocess strategies (Phase 2.2, Strategy pattern).
3
+
4
+ 将原 factor_preprocess_node.py::_preprocess_vectorized 中 3 类硬编码 if
5
+ 分支 (missing fill / de-extreme / normalise) 抽象为独立的 Strategy 类:
6
+
7
+ MissingFillStrategy (ABC)
8
+ ├── PassThroughMissing # 默认无操作
9
+ └── IndustryAverageMissing # ind_avg: 行业内均值填充
10
+
11
+ DeExtremeStrategy (ABC)
12
+ ├── PassThroughExtreme # 默认无操作
13
+ ├── MedianAbsoluteDeviationExtreme # median ± n * MAD
14
+ └── PercentileShrinkExtreme # quantile clip
15
+
16
+ NormStrategy (ABC)
17
+ ├── PassThroughNorm # 默认无操作
18
+ ├── ZScoreNorm # (x - mean) / std
19
+ └── RankToNormalNorm # rank → scipy.stats.norm.ppf
20
+
21
+ build_preprocess_strategies(missing, extreme, norm) -> (Missing, Extreme, Norm)
22
+
23
+ 每个 strategy 的 apply() 接受 in-place 输入 + 必要 kwargs, 返回处理后的
24
+ DataFrame. _preprocess_vectorized 退化为简单的 3 行 dispatch.
25
+
26
+ 新增预处理类型 (如 winsorize 自定义 quantile / rank_to_uniform) 只需新增
27
+ 一个 Strategy 子类, _preprocess_vectorized 无需修改.
28
+ """
29
+ from __future__ import annotations
30
+
31
+ from abc import ABC, abstractmethod
32
+ from typing import Any, Optional, Tuple
33
+
34
+ import numpy as np
35
+ import pandas as pd
36
+ from scipy.stats import norm as scipy_norm
37
+
38
+
39
+ # ============================================================================
40
+ # MissingFillStrategy
41
+ # ============================================================================
42
+
43
+ class MissingFillStrategy(ABC):
44
+ """缺失值填充策略. apply() 返回处理后的 DataFrame."""
45
+
46
+ name: str = ""
47
+
48
+ @abstractmethod
49
+ def apply(
50
+ self, result: pd.DataFrame, industry: Optional[pd.DataFrame] = None,
51
+ ) -> pd.DataFrame:
52
+ raise NotImplementedError
53
+
54
+
55
+ class PassThroughMissing(MissingFillStrategy):
56
+ """默认: 不做缺失值填充."""
57
+
58
+ name = "passthrough"
59
+
60
+ def apply(
61
+ self, result: pd.DataFrame, industry: Optional[pd.DataFrame] = None,
62
+ ) -> pd.DataFrame:
63
+ return result
64
+
65
+
66
+ class IndustryAverageMissing(MissingFillStrategy):
67
+ """ind_avg: 用 (date, industry) 组内的均值填充缺失值.
68
+
69
+ 行为与原 _preprocess_vectorized line 120-141 一致.
70
+ """
71
+
72
+ name = "ind_avg"
73
+
74
+ def apply(
75
+ self, result: pd.DataFrame, industry: Optional[pd.DataFrame] = None,
76
+ ) -> pd.DataFrame:
77
+ if industry is None:
78
+ return result
79
+ industry_aligned = industry.reindex(
80
+ index=result.index, columns=result.columns,
81
+ )
82
+ # Stack to long format for groupby transform on (date, industry).
83
+ long = pd.DataFrame({
84
+ "factor": result.stack(),
85
+ "ind": industry_aligned.stack(),
86
+ }).dropna(subset=["ind"])
87
+ long = long[long["ind"] > 0]
88
+ if long.empty:
89
+ return result
90
+ date_level = long.index.get_level_values(0)
91
+ group_mean = long.groupby([date_level, "ind"])["factor"].transform("mean")
92
+ filled = long["factor"].fillna(group_mean)
93
+ if isinstance(filled.index, pd.MultiIndex):
94
+ filled_wide = filled.unstack(level=1)
95
+ else:
96
+ filled_wide = filled.unstack(level=-1)
97
+ result.loc[filled_wide.index, filled_wide.columns] = filled_wide.values
98
+ return result
99
+
100
+
101
+ # ============================================================================
102
+ # DeExtremeStrategy
103
+ # ============================================================================
104
+
105
+ class DeExtremeStrategy(ABC):
106
+ """去极值策略. apply() 返回处理后的 DataFrame."""
107
+
108
+ name: str = ""
109
+
110
+ @abstractmethod
111
+ def apply(
112
+ self, result: pd.DataFrame, *,
113
+ mad_n: float = 3.0, pct_low: float = 0.01, pct_high: float = 0.99,
114
+ ) -> pd.DataFrame:
115
+ raise NotImplementedError
116
+
117
+
118
+ class PassThroughExtreme(DeExtremeStrategy):
119
+ """默认: 不去极值."""
120
+
121
+ name = "passthrough"
122
+
123
+ def apply(
124
+ self, result: pd.DataFrame, *,
125
+ mad_n: float = 3.0, pct_low: float = 0.01, pct_high: float = 0.99,
126
+ ) -> pd.DataFrame:
127
+ return result
128
+
129
+
130
+ class MedianAbsoluteDeviationExtreme(DeExtremeStrategy):
131
+ """median: 用 median ± n * MAD 截断.
132
+
133
+ 行为与原 _preprocess_vectorized line 144-151 一致.
134
+ """
135
+
136
+ name = "median"
137
+
138
+ def apply(
139
+ self, result: pd.DataFrame, *,
140
+ mad_n: float = 3.0, pct_low: float = 0.01, pct_high: float = 0.99,
141
+ ) -> pd.DataFrame:
142
+ median_per_row = result.median(axis=1)
143
+ mad_per_row = (result.sub(median_per_row, axis=0)).abs().median(axis=1)
144
+ lower = median_per_row - mad_n * mad_per_row
145
+ upper = median_per_row + mad_n * mad_per_row
146
+ return result.clip(lower=lower, upper=upper, axis=0)
147
+
148
+
149
+ class PercentileShrinkExtreme(DeExtremeStrategy):
150
+ """pct_shrink: 用 [pct_low, pct_high] 分位数截断.
151
+
152
+ 行为与原 _preprocess_vectorized line 152-155 一致.
153
+ """
154
+
155
+ name = "pct_shrink"
156
+
157
+ def apply(
158
+ self, result: pd.DataFrame, *,
159
+ mad_n: float = 3.0, pct_low: float = 0.01, pct_high: float = 0.99,
160
+ ) -> pd.DataFrame:
161
+ q1 = result.quantile(pct_low, axis=1)
162
+ q2 = result.quantile(pct_high, axis=1)
163
+ return result.clip(lower=q1, upper=q2, axis=0)
164
+
165
+
166
+ # ============================================================================
167
+ # NormStrategy
168
+ # ============================================================================
169
+
170
+ class NormStrategy(ABC):
171
+ """标准化策略. apply() 返回处理后的 DataFrame."""
172
+
173
+ name: str = ""
174
+
175
+ @abstractmethod
176
+ def apply(self, result: pd.DataFrame) -> pd.DataFrame:
177
+ raise NotImplementedError
178
+
179
+
180
+ class PassThroughNorm(NormStrategy):
181
+ """默认: 不标准化."""
182
+
183
+ name = "passthrough"
184
+
185
+ def apply(self, result: pd.DataFrame) -> pd.DataFrame:
186
+ return result
187
+
188
+
189
+ class ZScoreNorm(NormStrategy):
190
+ """zscore: (x - mean) / std. ddof=1 与原代码一致 (line 160)."""
191
+
192
+ name = "zscore"
193
+
194
+ def apply(self, result: pd.DataFrame) -> pd.DataFrame:
195
+ mean_per_row = result.mean(axis=1)
196
+ std_per_row = result.std(axis=1, ddof=1)
197
+ std_per_row = std_per_row.replace(0, np.nan)
198
+ return result.sub(mean_per_row, axis=0).div(std_per_row, axis=0)
199
+
200
+
201
+ class RankToNormalNorm(NormStrategy):
202
+ """norm: 排名 → scipy.stats.norm.ppf. 边界 rank=0/1 clip 到 (min/2, (max+1)/2).
203
+
204
+ 行为与原 _preprocess_vectorized line 163-184 一致.
205
+ """
206
+
207
+ name = "norm"
208
+
209
+ def apply(self, result: pd.DataFrame) -> pd.DataFrame:
210
+ ranks = result.rank(axis=1, pct=True)
211
+ # Clip ranks away from 0 and 1 (avoid -inf/+inf from ppf).
212
+ min_rank = ranks.where(ranks > 0).min(axis=1) * 0.5
213
+ max_rank = (ranks.where(ranks < 1).max(axis=1) + 1) * 0.5
214
+ # Default fallback for rows where min_rank/max_rank is NaN.
215
+ min_rank = min_rank.fillna(0.01)
216
+ max_rank = max_rank.fillna(0.99)
217
+ lower = pd.DataFrame(
218
+ np.broadcast_to(min_rank.values[:, None], ranks.shape),
219
+ index=ranks.index, columns=ranks.columns,
220
+ )
221
+ upper = pd.DataFrame(
222
+ np.broadcast_to(max_rank.values[:, None], ranks.shape),
223
+ index=ranks.index, columns=ranks.columns,
224
+ )
225
+ ranks = ranks.where(ranks.notna(), np.nan) # preserve NaN
226
+ ranks = ranks.clip(lower=lower, upper=upper, axis=1)
227
+ return pd.DataFrame(
228
+ scipy_norm.ppf(ranks.values, 0, 1),
229
+ index=ranks.index, columns=ranks.columns,
230
+ )
231
+
232
+
233
+ # ============================================================================
234
+ # Factory
235
+ # ============================================================================
236
+
237
+ def build_missing_strategy(missing: str) -> MissingFillStrategy:
238
+ """根据配置名构造 MissingFillStrategy. 未知值 → PassThroughMissing."""
239
+ table = {
240
+ "ind_avg": IndustryAverageMissing,
241
+ }
242
+ cls = table.get(missing, PassThroughMissing)
243
+ return cls()
244
+
245
+
246
+ def build_extreme_strategy(extreme: str) -> DeExtremeStrategy:
247
+ """根据配置名构造 DeExtremeStrategy. 未知值 → PassThroughExtreme."""
248
+ table = {
249
+ "median": MedianAbsoluteDeviationExtreme,
250
+ "pct_shrink": PercentileShrinkExtreme,
251
+ }
252
+ cls = table.get(extreme, PassThroughExtreme)
253
+ return cls()
254
+
255
+
256
+ def build_norm_strategy(norm: str) -> NormStrategy:
257
+ """根据配置名构造 NormStrategy. 未知值 → PassThroughNorm."""
258
+ table = {
259
+ "zscore": ZScoreNorm,
260
+ "norm": RankToNormalNorm,
261
+ }
262
+ cls = table.get(norm, PassThroughNorm)
263
+ return cls()
264
+
265
+
266
+ def build_preprocess_strategies(
267
+ missing: str, extreme: str, norm: str,
268
+ ) -> Tuple[MissingFillStrategy, DeExtremeStrategy, NormStrategy]:
269
+ """一次性构造 3 个 strategy.
270
+
271
+ Phase 2.2: 替代 _preprocess_vectorized 内的硬编码 if 链.
272
+ """
273
+ return (
274
+ build_missing_strategy(missing),
275
+ build_extreme_strategy(extreme),
276
+ build_norm_strategy(norm),
277
+ )
@@ -0,0 +1,112 @@
1
+ # coding: utf-8
2
+ """Node 11: 风险因子相关性 / Risk Correlation Node
3
+
4
+ Migrated from factor_performance.py:879-937 corr_riskfactor()
5
+ Security: exec()/eval() eliminated.
6
+ """
7
+
8
+ import logging
9
+
10
+ import numpy as np
11
+ import pandas as pd
12
+
13
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
14
+ from QuantNodes.research.factor_test.nodes.configs import RiskCorrelationNodeConfig
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class RiskCorrelationNode(PydanticConfigNode):
20
+ """与风险因子的 Spearman 秩相关 + 稳定系数
21
+
22
+ 输入: factor_neutral, risk_factors
23
+ 输出: {mean: DataFrame, stability: DataFrame}
24
+ """
25
+
26
+ ConfigSchema = RiskCorrelationNodeConfig
27
+ _ALIASES = {"_factors": "factors"}
28
+
29
+ def _execute(self, input_data=None, **kwargs) -> dict:
30
+ context = kwargs.get('context', {})
31
+ factor_data = self._factor_data(context)
32
+ if factor_data is None:
33
+ raise ValueError("因子数据缺失")
34
+
35
+ loader = self._ctx_load(context, '_loader')
36
+ if loader is None:
37
+ raise ValueError("数据加载器缺失")
38
+
39
+ return self._calc_risk_correlation(factor_data, loader, self._factors)
40
+
41
+ def _calc_risk_correlation(self, factor_data, loader, risk_factors_config):
42
+ """计算与风险因子的相关性"""
43
+ adj_dates = factor_data.index.tolist()
44
+
45
+ # 确定风险因子列表
46
+ if risk_factors_config == 'all':
47
+ try:
48
+ risk_keys = loader.get_apikeys('risk_factor.h5')
49
+ risk_keys = [k[1:] for k in risk_keys] # 去掉前导 /
50
+ risk_factors = [('risk_factor.h5', k) for k in risk_keys]
51
+ except Exception:
52
+ risk_factors = []
53
+ else:
54
+ risk_factors = risk_factors_config
55
+
56
+ if not risk_factors:
57
+ return {'mean': pd.DataFrame(), 'stability': pd.DataFrame()}
58
+
59
+ # 加载风险因子 (安全版本: 字典查找替代 exec)
60
+ risk_data_dict = {}
61
+ for file_key, factor_key in risk_factors:
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_dict[factor_key] = rf
70
+ except Exception as e:
71
+ logger.warning(f"加载风险因子 {factor_key} 失败: {e}")
72
+
73
+ if not risk_data_dict:
74
+ return {'mean': pd.DataFrame(), 'stability': pd.DataFrame()}
75
+
76
+ risk_names = list(risk_data_dict.keys())
77
+
78
+ # 计算每期相关系数
79
+ corr_all_list = []
80
+ for date_i in adj_dates:
81
+ risk_i = pd.DataFrame()
82
+ for name in risk_names:
83
+ rf = risk_data_dict[name]
84
+ if date_i in rf.index:
85
+ risk_i[name] = rf.loc[date_i]
86
+
87
+ factor_i = factor_data.loc[date_i]
88
+ merged = pd.merge(
89
+ factor_i.to_frame('factor'), risk_i,
90
+ left_index=True, right_index=True
91
+ )
92
+ if len(merged) > 2:
93
+ corr_i = merged.corr(method='spearman')
94
+ corr_i['date'] = date_i
95
+ corr_all_list.append(corr_i)
96
+
97
+ if not corr_all_list:
98
+ return {'mean': pd.DataFrame(), 'stability': pd.DataFrame()}
99
+
100
+ corr_all = pd.concat(corr_all_list)
101
+ corr_all['group'] = corr_all['date']
102
+
103
+ corr_mean = corr_all.groupby('group').mean()
104
+ corr_std = corr_all.groupby('group').std(ddof=1)
105
+ corr_stab = corr_mean / corr_std
106
+ corr_stab = corr_stab.reindex(index=corr_mean.columns.tolist())
107
+ corr_stab.replace(np.inf, np.nan, inplace=True)
108
+
109
+ return {
110
+ 'mean': corr_mean,
111
+ 'stability': corr_stab,
112
+ }
@@ -0,0 +1,110 @@
1
+ # coding: utf-8
2
+ """Node 2: 样本池筛选 / Sample Pool Filter Node
3
+
4
+ Migrated from factor_utils.py:155-234 select_range()
5
+ """
6
+
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
11
+ from QuantNodes.research.factor_test.nodes.configs import SamplePoolNodeConfig
12
+ from QuantNodes.research.factor_test.utils.constants import (
13
+ resolve_index_mapping, resolve_industry_map,
14
+ )
15
+
16
+
17
+ class SamplePoolFilterNode(PydanticConfigNode):
18
+ """根据指数范围和行业筛选样本池
19
+
20
+ 输入: context["LoadData"] 的输出
21
+ 输出: stock_sample (1=选中, nan=剔除)
22
+
23
+ M9: index_mapping 可自定义,合并全局默认 + 节点自定义覆盖
24
+ M12: i18n_name_map 可自定义行业代码→名称映射,合并全局默认 + 节点自定义覆盖
25
+ """
26
+
27
+ ConfigSchema = SamplePoolNodeConfig
28
+ _ALIASES = {
29
+ "_sample_index": "sample_index",
30
+ "_sample_industry": "sample_industry",
31
+ "_sample_customdir": "sample_index_customdir",
32
+ }
33
+
34
+ def __init__(self, name="SamplePoolFilter", config=None, **kwargs):
35
+ super().__init__(name, config, **kwargs)
36
+ # M9: 合并全局默认 INDEX_MAPPING + 节点自定义覆盖
37
+ self._index_mapping = resolve_index_mapping({
38
+ "INDEX_MAPPING": self.cfg.index_mapping
39
+ } if self.cfg.index_mapping else None)
40
+ # M12: 合并全局默认 INDUSTRY_MAPPING + 节点自定义覆盖
41
+ self._i18n_name_map = resolve_industry_map({
42
+ "INDUSTRY_MAP": self.cfg.i18n_name_map
43
+ } if self.cfg.i18n_name_map else None)
44
+
45
+ def _execute(self, input_data=None, **kwargs) -> pd.DataFrame:
46
+ context = kwargs.get('context', {})
47
+ load_data = context.get('LoadData') or input_data or {}
48
+
49
+ stklist = load_data['stklist']
50
+ trade_dt = load_data['trade_dt']
51
+ loader = load_data['_loader']
52
+
53
+ n_dt = len(trade_dt)
54
+ n_stk = len(stklist)
55
+
56
+ # 指数筛选
57
+ index_filt = np.ones((n_dt, n_stk))
58
+ if self._sample_index != 'all':
59
+ if self._sample_index in self._index_mapping:
60
+ h5_file, key = self._index_mapping[self._sample_index]
61
+ if_index = loader.load_h5(h5_file, key)
62
+ if_index = if_index.replace(np.nan, 0)
63
+ index_filt = index_filt * if_index.values
64
+ elif self._sample_index == 'ZZ800':
65
+ if_300 = loader.load_h5('stk_daily.h5', 'id_300').replace(np.nan, 0)
66
+ if_500 = loader.load_h5('stk_daily.h5', 'id_500').replace(np.nan, 0)
67
+ index_filt = index_filt * (if_300 + if_500).values
68
+ elif self._sample_index == 'custom' and self._sample_customdir:
69
+ if_index = loader.load_custom(self._sample_customdir)
70
+ if_index = if_index.replace(np.nan, 0)
71
+ index_filt = index_filt * if_index.values
72
+ else:
73
+ raise ValueError(f"不支持的指数: {self._sample_index}")
74
+
75
+ # 行业筛选
76
+ industry_filt = np.ones((n_dt, n_stk))
77
+ if self._sample_industry != 'all':
78
+ if isinstance(self._sample_industry, tuple):
79
+ ind_key, ind_name = self._sample_industry
80
+ else:
81
+ ind_key = 'id_citic1'
82
+ ind_name = self._sample_industry
83
+
84
+ id_industry = loader.load_h5('stk_daily.h5', ind_key)
85
+ # M12: i18n 映射: self._i18n_name_map
86
+ if self._i18n_name_map and ind_key in self._i18n_name_map:
87
+ ind_name = self._i18n_name_map[ind_key]
88
+ id_name_data = loader.load_h5(
89
+ 'stk_daily.h5', f'ind_name_{ind_key.replace("id_", "").upper()}'
90
+ )
91
+
92
+ # 找到行业名称对应的编号
93
+ ind_idx = np.where(id_name_data.values == ind_name)[0]
94
+ if len(ind_idx) > 0:
95
+ ind_num = ind_idx[0] + 1 # 行业编号从 1 开始
96
+ industry_filt[id_industry.values != ind_num] = 0
97
+ else:
98
+ raise ValueError(f"行业 '{ind_name}' 未找到")
99
+
100
+ # 合并筛选
101
+ stock_sample = index_filt * industry_filt
102
+ stock_sample[stock_sample > 0] = 1
103
+ stock_sample = pd.DataFrame(
104
+ stock_sample,
105
+ index=trade_dt.iloc[:, 0].values,
106
+ columns=stklist.iloc[:, 0].values,
107
+ )
108
+ stock_sample = stock_sample.replace(0, np.nan)
109
+
110
+ return stock_sample
@@ -0,0 +1,92 @@
1
+ # coding: utf-8
2
+ """Node 3: 可交易性筛选 / Tradability Filter Node
3
+
4
+ Migrated from factor_utils.py:250-308 valid_tradable()
5
+ Security: exec() replaced with dict lookup.
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+
11
+ from QuantNodes.research.factor_test.nodes._base import PydanticConfigNode
12
+ from QuantNodes.research.factor_test.nodes.configs import TradabilityNodeConfig
13
+
14
+
15
+ class TradabilityFilterNode(PydanticConfigNode):
16
+ """验证股票可交易性 (ST/停牌/涨跌停/新股/自定义追踪)
17
+
18
+ 输入: context["LoadData"] 的输出 + context["SamplePoolFilter"] 的输出
19
+ 输出: tradable (1=可交易, nan=不可)
20
+ """
21
+
22
+ ConfigSchema = TradabilityNodeConfig
23
+ _ALIASES = {"_tradable_setting": "tradable"}
24
+
25
+ def _execute(self, input_data=None, **kwargs) -> pd.DataFrame:
26
+ context = kwargs.get('context', {})
27
+ load_data = self._ctx(context)
28
+ sample = context.get('SamplePoolFilter')
29
+
30
+ loader = load_data['_loader']
31
+ stklist = load_data['stklist']
32
+ trade_dt = load_data['trade_dt']
33
+
34
+ n_dt = len(trade_dt)
35
+ n_stk = len(stklist)
36
+
37
+ # 加载可交易性数据 (优先从 context, 回退到 loader)
38
+ def _get(key):
39
+ if key in load_data:
40
+ return load_data[key]
41
+ return loader.load_h5('stk_daily.h5', key) if loader else None
42
+
43
+ st = _get('st')
44
+ suspend = _get('suspend')
45
+ ud_limit = _get('ud_limit')
46
+ if ud_limit is not None:
47
+ ud_limit = ud_limit.abs()
48
+ ipo_days = _get('ipo_days')
49
+
50
+ # 初始化: 全部可交易
51
+ if_tradable = pd.DataFrame(
52
+ np.ones((n_dt, n_stk)),
53
+ index=trade_dt.iloc[:, 0].values,
54
+ columns=stklist.iloc[:, 0].values
55
+ )
56
+
57
+ s = self._tradable_setting
58
+
59
+ if s.no_st:
60
+ if_tradable[st == 1] = np.nan
61
+
62
+ if s.no_suspended:
63
+ if_tradable[suspend == 1] = np.nan
64
+
65
+ if s.no_up_down_limit:
66
+ if_tradable[ud_limit == 1.0] = np.nan
67
+
68
+ if s.min_ipo_days:
69
+ if_tradable[ipo_days < s.min_ipo_days] = np.nan
70
+
71
+ # 自定义追踪条件 (安全版本: 字典查找替代 exec)
72
+ if s.trace:
73
+ trace_data_map = {
74
+ 'suspend': suspend,
75
+ 'st': st,
76
+ 'ud_limit': ud_limit,
77
+ }
78
+ for trace_key, (m, n) in s.trace.items():
79
+ if trace_key not in trace_data_map:
80
+ raise ValueError(f"不支持的追踪条件: {trace_key}")
81
+ trace_data = trace_data_map[trace_key]
82
+ if m > 0:
83
+ trace_sum = trace_data.rolling(m).sum().shift(1)
84
+ else:
85
+ trace_sum = trace_data.rolling(-m).sum().shift(m)
86
+ if_tradable[trace_sum >= n] = np.nan
87
+
88
+ # 合并样本池筛选
89
+ if sample is not None:
90
+ if_tradable = if_tradable * sample
91
+
92
+ return if_tradable