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,560 @@
1
+ # coding=utf-8
2
+ """
3
+ 因子评估器 - 6维度评估 + 相关性去重
4
+
5
+ 维度: 收益、稳定性、分散度、换手率、单调性、覆盖率
6
+
7
+ ⚠️ DeprecationWarning (v2.7.0+, since 2026-06-23):
8
+ 本模块进入 deprecation 周期。新代码请迁移到
9
+ `QuantNodes.research.quant_alpha.operator_vocab.OperatorVocab`。
10
+
11
+ 迁移理由:
12
+ - 12-lambda 硬编码 namespace 替换为 162 算子
13
+ - 修复 3 个 latent bug(ts_corr/ts_cov/rank/zscore)
14
+ - per-date over() 语义正确
15
+ - 完整元数据(difficulty / category_tags / examples / composes_with)
16
+
17
+ Phase 时间表:
18
+ - Phase A (current): 本文件仍可用,行为完全兼容
19
+ - Phase B (v2.9+): 本类变 thin wrapper,内部调新子包
20
+ - Phase C (v3.0): 归档到 _legacy_3c/,破坏性变更
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import math
26
+ import warnings
27
+ from dataclasses import dataclass, field
28
+ from typing import Dict, List, Optional
29
+
30
+ import numpy as np
31
+ import polars as pl
32
+
33
+ from QuantNodes.research._legacy_3c.factor_miner import FactorCandidate
34
+
35
+ _DEPRECATION_MSG = (
36
+ "QuantNodes.research.factor_evaluator 已弃用 (DeprecationWarning)。"
37
+ "请迁移到 QuantNodes.research.quant_alpha.operator_vocab.OperatorVocab "
38
+ "(162 算子 + 修复 3 个 latent bug + 完整元数据)。"
39
+ "Phase B (v2.9+): 本类变 thin wrapper。"
40
+ "Phase C (v3.0): 归档到 _legacy_3c/。"
41
+ )
42
+ warnings.warn(_DEPRECATION_MSG, DeprecationWarning, stacklevel=2)
43
+
44
+
45
+ @dataclass
46
+ class FactorEvaluationResult:
47
+ """6维度因子评估结果"""
48
+ candidate: FactorCandidate
49
+ factor_values: Optional[pl.Series] = None
50
+
51
+ # 维度1: 收益
52
+ ic_mean: float = 0.0
53
+ ic_std: float = 0.0
54
+ icir: float = 0.0
55
+ rank_ic_mean: float = 0.0
56
+
57
+ # 维度2: 稳定性
58
+ rolling_ic_mean: float = 0.0
59
+ rolling_ic_std: float = 0.0
60
+ stability_score: float = 0.0
61
+
62
+ # 维度3: 分散度
63
+ avg_corr_with_existing: float = 0.0
64
+ diversification_score: float = 1.0
65
+
66
+ # 维度4: 换手率
67
+ turnover: float = 0.0
68
+ turnover_cost: float = 0.0
69
+
70
+ # 维度5: 单调性
71
+ group_returns: List[float] = field(default_factory=list)
72
+ monotonicity_score: float = 0.0
73
+
74
+ # 维度6: 覆盖率
75
+ coverage: float = 0.0
76
+
77
+ # 综合判定
78
+ is_valid: bool = False
79
+ fail_reasons: List[str] = field(default_factory=list)
80
+ overall_score: float = 0.0
81
+ dimension_scores: Dict[str, float] = field(default_factory=dict)
82
+
83
+
84
+ @dataclass
85
+ class EvalConfig:
86
+ """评估配置"""
87
+ ic_threshold: float = 0.03
88
+ icir_threshold: float = 0.5
89
+ stability_threshold: float = 0.6
90
+ corr_threshold: float = 0.7
91
+ turnover_threshold: float = 0.5
92
+ monotonicity_threshold: float = 0.7
93
+ coverage_threshold: float = 0.8
94
+ n_groups: int = 5
95
+ rolling_window: int = 20
96
+
97
+ # 综合评分权重
98
+ weights: Dict[str, float] = field(default_factory=lambda: {
99
+ "return": 0.30,
100
+ "stability": 0.20,
101
+ "diversification": 0.20,
102
+ "turnover": 0.15,
103
+ "monotonicity": 0.10,
104
+ "coverage": 0.05,
105
+ })
106
+
107
+
108
+ class FactorEvaluator:
109
+ """6维度因子评估器"""
110
+
111
+ def __init__(self, config: EvalConfig = None):
112
+ self.config = config or EvalConfig()
113
+
114
+ def evaluate(
115
+ self,
116
+ candidate: FactorCandidate,
117
+ data: pl.DataFrame,
118
+ date_column: str = "date",
119
+ code_column: str = "code",
120
+ forward_return_column: str = "forward_return",
121
+ existing_factors: Optional[List[pl.Series]] = None,
122
+ ) -> FactorEvaluationResult:
123
+ """评估单个候选因子"""
124
+ result = FactorEvaluationResult(candidate=candidate)
125
+
126
+ try:
127
+ # 计算因子值
128
+ factor_values = self._compute_factor(candidate.formula, data)
129
+ if factor_values is None:
130
+ result.fail_reasons.append("公式计算失败")
131
+ return result
132
+
133
+ result.factor_values = factor_values
134
+
135
+ # 合并因子值和收益率
136
+ eval_df = data.with_columns(factor_values.alias("_factor"))
137
+
138
+ # 过滤无效值
139
+ eval_df = eval_df.filter(
140
+ pl.col("_factor").is_not_null() &
141
+ pl.col(forward_return_column).is_not_null()
142
+ )
143
+
144
+ if len(eval_df) < 10:
145
+ result.fail_reasons.append("有效数据不足")
146
+ return result
147
+
148
+ # 维度6: 覆盖率
149
+ result.coverage = self._compute_coverage(data, factor_values)
150
+ result.dimension_scores["coverage"] = result.coverage
151
+
152
+ # 维度1: 收益 (IC/IR)
153
+ self._compute_return_dimension(result, eval_df, date_column)
154
+ result.dimension_scores["return"] = min(abs(result.icir) / 1.0, 1.0)
155
+
156
+ # 维度2: 稳定性
157
+ self._compute_stability_dimension(result, eval_df, date_column)
158
+ result.dimension_scores["stability"] = result.stability_score
159
+
160
+ # 维度4: 换手率
161
+ self._compute_turnover_dimension(result, eval_df, date_column, code_column)
162
+ result.dimension_scores["turnover"] = max(1.0 - result.turnover, 0.0)
163
+
164
+ # 维度5: 单调性
165
+ self._compute_monotonicity_dimension(result, eval_df, forward_return_column)
166
+ result.dimension_scores["monotonicity"] = result.monotonicity_score
167
+
168
+ # 维度3: 分散度
169
+ if existing_factors:
170
+ self._compute_diversification_dimension(
171
+ result, factor_values, existing_factors
172
+ )
173
+ else:
174
+ result.avg_corr_with_existing = 0.0
175
+ result.diversification_score = 1.0
176
+ result.dimension_scores["diversification"] = result.diversification_score
177
+
178
+ # 综合评分
179
+ result.overall_score = self._compute_overall_score(result)
180
+ result.is_valid = self._check_validity(result)
181
+
182
+ except Exception as e:
183
+ result.fail_reasons.append(f"评估异常: {e}")
184
+
185
+ return result
186
+
187
+ def deduplicate(
188
+ self,
189
+ results: List[FactorEvaluationResult],
190
+ corr_threshold: float = None,
191
+ ) -> List[FactorEvaluationResult]:
192
+ """相关性去重 (贪心聚类)"""
193
+ threshold = corr_threshold or self.config.corr_threshold
194
+
195
+ # 按综合评分降序排序
196
+ sorted_results = sorted(
197
+ results, key=lambda r: r.overall_score, reverse=True
198
+ )
199
+
200
+ selected: List[FactorEvaluationResult] = []
201
+ for r in sorted_results:
202
+ if r.factor_values is None:
203
+ continue
204
+
205
+ # 检查与已选因子的相关性
206
+ is_duplicate = False
207
+ for s in selected:
208
+ if s.factor_values is None:
209
+ continue
210
+ corr = self._spearman_corr(r.factor_values, s.factor_values)
211
+ if abs(corr) > threshold:
212
+ is_duplicate = True
213
+ break
214
+
215
+ if not is_duplicate:
216
+ selected.append(r)
217
+
218
+ return selected
219
+
220
+ # ==================== 因子计算 ====================
221
+
222
+ def _compute_factor(
223
+ self, formula: str, data: pl.DataFrame
224
+ ) -> Optional[pl.Series]:
225
+ """安全计算因子值"""
226
+ try:
227
+ namespace = {
228
+ "pl": pl,
229
+ "ts_mean": lambda col, w: col.rolling_mean(w),
230
+ "ts_std": lambda col, w: col.rolling_std(w),
231
+ "ts_max": lambda col, w: col.rolling_max(w),
232
+ "ts_min": lambda col, w: col.rolling_min(w),
233
+ "ts_delta": lambda col, w: col - col.shift(w),
234
+ "ts_lag": lambda col, w: col.shift(w),
235
+ "ts_pct_change": lambda col, w: col.pct_change(w),
236
+ "ts_corr": lambda c1, c2, w: c1.rolling_corr(c2, w),
237
+ "ts_cov": lambda c1, c2, w: c1.rolling_cov(c2, w),
238
+ "rank": lambda col: col.rank(),
239
+ "zscore": lambda col: (col - col.mean()) / (col.std() + 1e-8),
240
+ }
241
+ for col_name in data.columns:
242
+ namespace[col_name] = data[col_name]
243
+
244
+ result = eval(formula, {"__builtins__": {}}, namespace)
245
+ if isinstance(result, pl.Series):
246
+ return result
247
+ if isinstance(result, pl.Expr):
248
+ return data.select(result).to_series()
249
+ return None
250
+ except Exception:
251
+ return None
252
+
253
+ # ==================== 维度1: 收益 ====================
254
+
255
+ def _compute_return_dimension(
256
+ self,
257
+ result: FactorEvaluationResult,
258
+ eval_df: pl.DataFrame,
259
+ date_column: str,
260
+ ):
261
+ """计算 IC/IR/Rank IC"""
262
+ # 按日期分组计算 IC
263
+ ic_series = (
264
+ eval_df
265
+ .group_by(date_column)
266
+ .agg(
267
+ pl.corr("_factor", "forward_return").alias("ic")
268
+ )
269
+ .sort(date_column)
270
+ )
271
+
272
+ ic_values = ic_series["ic"].drop_nulls()
273
+ if len(ic_values) == 0:
274
+ return
275
+
276
+ result.ic_mean = float(ic_values.mean())
277
+ result.ic_std = float(ic_values.std()) if len(ic_values) > 1 else 0.0
278
+ result.icir = (
279
+ result.ic_mean / (result.ic_std + 1e-8) if result.ic_std > 0 else 0.0
280
+ )
281
+
282
+ # Rank IC
283
+ rank_ic_series = (
284
+ eval_df
285
+ .group_by(date_column)
286
+ .agg(
287
+ pl.corr(
288
+ pl.col("_factor").rank(),
289
+ pl.col("forward_return").rank(),
290
+ ).alias("rank_ic")
291
+ )
292
+ .sort(date_column)
293
+ )
294
+ rank_ic_values = rank_ic_series["rank_ic"].drop_nulls()
295
+ if len(rank_ic_values) > 0:
296
+ result.rank_ic_mean = float(rank_ic_values.mean())
297
+
298
+ # ==================== 维度2: 稳定性 ====================
299
+
300
+ def _compute_stability_dimension(
301
+ self,
302
+ result: FactorEvaluationResult,
303
+ eval_df: pl.DataFrame,
304
+ date_column: str,
305
+ ):
306
+ """计算滚动IC的稳定性"""
307
+ # 按日期分组计算 IC
308
+ ic_by_date = (
309
+ eval_df
310
+ .group_by(date_column)
311
+ .agg(pl.corr("_factor", "forward_return").alias("ic"))
312
+ .sort(date_column)
313
+ )
314
+
315
+ ic_values = ic_by_date["ic"].drop_nulls().to_list()
316
+ if len(ic_values) < self.config.rolling_window:
317
+ result.stability_score = 0.0
318
+ return
319
+
320
+ # 滚动IC
321
+ rolling_ics = []
322
+ for i in range(self.config.rolling_window, len(ic_values) + 1):
323
+ window = ic_values[i - self.config.rolling_window : i]
324
+ rolling_ics.append(np.mean(window))
325
+
326
+ if rolling_ics:
327
+ result.rolling_ic_mean = float(np.mean(rolling_ics))
328
+ result.rolling_ic_std = float(np.std(rolling_ics))
329
+
330
+ # 稳定性 = 1 - (滚动IC的标准差 / 均值的绝对值)
331
+ mean_abs = abs(result.rolling_ic_mean) + 1e-8
332
+ result.stability_score = max(0.0, 1.0 - result.rolling_ic_std / mean_abs)
333
+
334
+ # ==================== 维度3: 分散度 ====================
335
+
336
+ def _compute_diversification_dimension(
337
+ self,
338
+ result: FactorEvaluationResult,
339
+ factor_values: pl.Series,
340
+ existing_factors: List[pl.Series],
341
+ ):
342
+ """计算与已有因子的平均相关性"""
343
+ if not existing_factors:
344
+ result.avg_corr_with_existing = 0.0
345
+ result.diversification_score = 1.0
346
+ return
347
+
348
+ corrs = []
349
+ for ef in existing_factors:
350
+ corr = self._spearman_corr(factor_values, ef)
351
+ if not math.isnan(corr):
352
+ corrs.append(abs(corr))
353
+
354
+ if corrs:
355
+ result.avg_corr_with_existing = float(np.mean(corrs))
356
+ result.diversification_score = max(0.0, 1.0 - result.avg_corr_with_existing)
357
+ else:
358
+ result.avg_corr_with_existing = 0.0
359
+ result.diversification_score = 1.0
360
+
361
+ # ==================== 维度4: 换手率 ====================
362
+
363
+ def _compute_turnover_dimension(
364
+ self,
365
+ result: FactorEvaluationResult,
366
+ eval_df: pl.DataFrame,
367
+ date_column: str,
368
+ code_column: str,
369
+ ):
370
+ """计算因子排名变化率 (换手率)"""
371
+ # 按日期计算排名
372
+ ranked = eval_df.with_columns(
373
+ pl.col("_factor").rank().alias("_rank")
374
+ )
375
+
376
+ # 按股票分组,计算排名变化
377
+ dates = sorted(ranked[date_column].unique().to_list())
378
+ if len(dates) < 2:
379
+ result.turnover = 0.0
380
+ return
381
+
382
+ rank_changes = []
383
+ for i in range(1, min(len(dates), 50)): # 最多取50天
384
+ prev = (
385
+ ranked
386
+ .filter(pl.col(date_column) == dates[i - 1])
387
+ .select([code_column, "_rank"])
388
+ .rename({"_rank": "prev_rank"})
389
+ )
390
+ curr = (
391
+ ranked
392
+ .filter(pl.col(date_column) == dates[i])
393
+ .select([code_column, "_rank"])
394
+ .rename({"_rank": "curr_rank"})
395
+ )
396
+ merged = prev.join(curr, on=code_column, how="inner")
397
+ if len(merged) > 0:
398
+ change = (
399
+ (merged["curr_rank"] - merged["prev_rank"]).abs().mean()
400
+ / len(merged)
401
+ )
402
+ rank_changes.append(float(change))
403
+
404
+ if rank_changes:
405
+ result.turnover = float(np.mean(rank_changes))
406
+ result.turnover_cost = result.turnover * 0.001 # 简单估计
407
+
408
+ # ==================== 维度5: 单调性 ====================
409
+
410
+ def _compute_monotonicity_dimension(
411
+ self,
412
+ result: FactorEvaluationResult,
413
+ eval_df: pl.DataFrame,
414
+ forward_return_column: str,
415
+ ):
416
+ """计算5组分位收益的单调性"""
417
+ n_groups = self.config.n_groups
418
+
419
+ # 按因子值分组
420
+ quantiles = eval_df.select(
421
+ pl.col("_factor").quantile_segmentation(n_groups).alias("_group")
422
+ )
423
+ eval_df = eval_df.with_columns(quantiles["_group"])
424
+
425
+ # 计算每组平均收益
426
+ group_returns = (
427
+ eval_df
428
+ .group_by("_group")
429
+ .agg(pl.col(forward_return_column).mean().alias("avg_return"))
430
+ .sort("_group")
431
+ )
432
+
433
+ returns = group_returns["avg_return"].to_list()
434
+ result.group_returns = [float(r) for r in returns if r is not None]
435
+
436
+ # 计算单调性评分
437
+ if len(result.group_returns) < 2:
438
+ result.monotonicity_score = 0.0
439
+ return
440
+
441
+ # 计算 Spearman 秩相关 (组号 vs 平均收益)
442
+ n = len(result.group_returns)
443
+ x_rank = list(range(n))
444
+ y_sorted = sorted(range(n), key=lambda i: result.group_returns[i])
445
+ y_rank = [0] * n
446
+ for rank, idx in enumerate(y_sorted):
447
+ y_rank[idx] = rank
448
+
449
+ # Spearman 相关
450
+ d_sq_sum = sum((x_rank[i] - y_rank[i]) ** 2 for i in range(n))
451
+ spearman = 1.0 - (6.0 * d_sq_sum) / (n * (n * n - 1))
452
+ result.monotonicity_score = max(0.0, spearman)
453
+
454
+ # ==================== 维度6: 覆盖率 ====================
455
+
456
+ def _compute_coverage(
457
+ self, data: pl.DataFrame, factor_values: pl.Series
458
+ ) -> float:
459
+ """计算因子值覆盖率"""
460
+ total = len(factor_values)
461
+ if total == 0:
462
+ return 0.0
463
+ non_null = factor_values.drop_nulls()
464
+ return len(non_null) / total
465
+
466
+ # ==================== 综合评分 ====================
467
+
468
+ def _compute_overall_score(self, result: FactorEvaluationResult) -> float:
469
+ """计算综合评分"""
470
+ weights = self.config.weights
471
+ score = 0.0
472
+
473
+ for dim, weight in weights.items():
474
+ if dim in result.dimension_scores:
475
+ score += weight * result.dimension_scores[dim]
476
+
477
+ return score
478
+
479
+ def _check_validity(self, result: FactorEvaluationResult) -> bool:
480
+ """检查是否通过所有维度阈值"""
481
+ reasons = []
482
+
483
+ if abs(result.ic_mean) < self.config.ic_threshold:
484
+ reasons.append(f"IC {result.ic_mean:.4f} < {self.config.ic_threshold}")
485
+
486
+ if abs(result.icir) < self.config.icir_threshold:
487
+ reasons.append(f"ICIR {result.icir:.4f} < {self.config.icir_threshold}")
488
+
489
+ if result.stability_score < self.config.stability_threshold:
490
+ reasons.append(
491
+ f"稳定性 {result.stability_score:.4f} < {self.config.stability_threshold}"
492
+ )
493
+
494
+ if result.avg_corr_with_existing > self.config.corr_threshold:
495
+ reasons.append(
496
+ f"相关性 {result.avg_corr_with_existing:.4f} > {self.config.corr_threshold}"
497
+ )
498
+
499
+ if result.turnover > self.config.turnover_threshold:
500
+ reasons.append(
501
+ f"换手率 {result.turnover:.4f} > {self.config.turnover_threshold}"
502
+ )
503
+
504
+ if result.monotonicity_score < self.config.monotonicity_threshold:
505
+ reasons.append(
506
+ f"单调性 {result.monotonicity_score:.4f} < {self.config.monotonicity_threshold}"
507
+ )
508
+
509
+ if result.coverage < self.config.coverage_threshold:
510
+ reasons.append(
511
+ f"覆盖率 {result.coverage:.4f} < {self.config.coverage_threshold}"
512
+ )
513
+
514
+ result.fail_reasons = reasons
515
+ return len(reasons) == 0
516
+
517
+ # ==================== 工具方法 ====================
518
+
519
+ @staticmethod
520
+ def _spearman_corr(a: pl.Series, b: pl.Series) -> float:
521
+ """计算 Spearman 秩相关"""
522
+ min_len = min(len(a), len(b))
523
+ if min_len < 3:
524
+ return 0.0
525
+
526
+ a_vals = a[:min_len].to_list()
527
+ b_vals = b[:min_len].to_list()
528
+
529
+ # 过滤 None/NaN
530
+ pairs = [(x, y) for x, y in zip(a_vals, b_vals) if x is not None and y is not None]
531
+ if len(pairs) < 3:
532
+ return 0.0
533
+
534
+ a_clean, b_clean = zip(*pairs)
535
+ n = len(a_clean)
536
+
537
+ # 排序得到秩
538
+ a_ranked = _rank(list(a_clean))
539
+ b_ranked = _rank(list(b_clean))
540
+
541
+ # Spearman 公式
542
+ d_sq = sum((a_ranked[i] - b_ranked[i]) ** 2 for i in range(n))
543
+ return 1.0 - (6.0 * d_sq) / (n * (n * n - 1))
544
+
545
+
546
+ def _rank(values: List[float]) -> List[float]:
547
+ """计算平均秩"""
548
+ n = len(values)
549
+ indexed = sorted(range(n), key=lambda i: values[i])
550
+ ranks = [0.0] * n
551
+ i = 0
552
+ while i < n:
553
+ j = i
554
+ while j < n - 1 and values[indexed[j + 1]] == values[indexed[j]]:
555
+ j += 1
556
+ avg_rank = (i + j) / 2.0 + 1.0
557
+ for k in range(i, j + 1):
558
+ ranks[indexed[k]] = avg_rank
559
+ i = j + 1
560
+ return ranks