cnhkmcp 2.3.3__py3-none-any.whl → 2.3.5__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 (59) hide show
  1. cnhkmcp/__init__.py +1 -1
  2. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/main.py +7 -0
  3. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242//321/211/320/266/320/246/321/206/320/274/320/261/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +8 -0
  4. cnhkmcp/untracked/APP/Tranformer/validator.py +149 -32
  5. cnhkmcp/untracked/APP/ace_lib.py +8 -0
  6. cnhkmcp/untracked/APP/blueprints/parsetab.py +60 -0
  7. cnhkmcp/untracked/APP/blueprints/validator.py +1080 -0
  8. cnhkmcp/untracked/APP/requirements.txt +0 -0
  9. cnhkmcp/untracked/APP/static/decoder.js +164 -38
  10. cnhkmcp/untracked/APP/static/simulator.js +15 -15
  11. cnhkmcp/untracked/APP/templates/feature_engineering.html +78 -78
  12. cnhkmcp/untracked/APP/templates/idea_house.html +49 -49
  13. cnhkmcp/untracked/APP/templates/index.html +58 -58
  14. cnhkmcp/untracked/APP/templates/inspiration_house.html +64 -64
  15. cnhkmcp/untracked/APP/templates/paper_analysis.html +21 -21
  16. cnhkmcp/untracked/APP/templates/simulator.html +38 -38
  17. cnhkmcp/untracked/APP/templates/transformer_web.html +24 -24
  18. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/SKILL.md +15 -15
  19. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927658009727000.json +10 -0
  20. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927658519220600.json +10 -0
  21. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659002708800.json +10 -0
  22. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659510920900.json +10 -0
  23. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental28_GLB_delay1/fundamental28_GLB_1_idea_1769927659982673800.json +10 -0
  24. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/validator.py +153 -32
  25. cnhkmcp/untracked/APP//321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +54 -0
  26. cnhkmcp/untracked/arxiv_api.py +7 -0
  27. cnhkmcp/untracked/back_up/forum_functions.py +8 -0
  28. cnhkmcp/untracked/forum_functions.py +8 -0
  29. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/forum_functions.py +8 -0
  30. cnhkmcp/untracked/mcp/321/206/320/246/320/227/321/204/342/225/227/342/225/242/321/210/320/276/342/225/221/321/205/320/255/320/253/321/207/320/231/320/2302_/321/205/320/266/320/222/321/206/320/256/320/254/321/205/320/236/320/257/321/207/320/231/320/230/321/205/320/240/320/277/321/205/320/232/320/270/321/204/342/225/225/320/235/321/204/342/225/221/320/226/321/206/342/225/241/320/237/321/210/320/267/320/230/321/205/320/251/320/270/321/205/342/226/221/342/226/222/321/210/320/277/320/245/321/210/342/224/220/320/251/321/204/342/225/225/320/272/platform_functions.py +8 -1
  31. cnhkmcp/untracked/platform_functions.py +7 -0
  32. cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/validator.py +149 -32
  33. cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/SKILL.md +8 -2
  34. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/ace.log +0 -0
  35. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/idea_context.json +15 -0
  36. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/__init__.py +0 -0
  37. cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/parse_idea_file.py +33 -1
  38. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/parsetab.py +60 -0
  39. cnhkmcp/untracked/skills/brain-inspectRawTemplate-create-Setting/scripts/validator.py +1086 -0
  40. cnhkmcp/untracked//321/211/320/225/320/235/321/207/342/225/234/320/276/321/205/320/231/320/235/321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/230/320/241_/321/205/320/276/320/231/321/210/320/263/320/225/321/205/342/224/220/320/225/321/210/320/266/320/221/321/204/342/225/233/320/255/321/210/342/225/241/320/246/321/205/320/234/320/225.py +8 -0
  41. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/METADATA +1 -1
  42. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/RECORD +59 -47
  43. /cnhkmcp/untracked/{skills/brain-inspectTemplate-create-Setting → APP/hkSimulator}/ace.log +0 -0
  44. /cnhkmcp/untracked/{skills/brain-inspectTemplate-create-Setting/scripts/__init__.py → APP/hkSimulator/autosim_20260201_172428.log} +0 -0
  45. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/.gitignore +0 -0
  46. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/ace_lib.py +0 -0
  47. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/config.json +0 -0
  48. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/fundamental28_GLB_1_idea_1769874845978315000.json +0 -0
  49. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/helpful_functions.py +0 -0
  50. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/build_alpha_list.py +0 -0
  51. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/fetch_sim_options.py +0 -0
  52. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/load_credentials.py +0 -0
  53. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/process_template.py +0 -0
  54. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/scripts/resolve_settings.py +0 -0
  55. /cnhkmcp/untracked/skills/{brain-inspectTemplate-create-Setting → brain-inspectRawTemplate-create-Setting}/sim_options_snapshot.json +0 -0
  56. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/WHEEL +0 -0
  57. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/entry_points.txt +0 -0
  58. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/licenses/LICENSE +0 -0
  59. {cnhkmcp-2.3.3.dist-info → cnhkmcp-2.3.5.dist-info}/top_level.txt +0 -0
@@ -65,51 +65,65 @@ supported_functions = {
65
65
  'ts_delay': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
66
66
  'ts_corr': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
67
67
  'ts_zscore': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
68
- 'ts_returns': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'mode']},
68
+ 'ts_returns': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'mode'], 'keyword_only': True},
69
69
  'ts_product': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
70
- 'ts_backfill': {'min_args': 2, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'string']},
70
+ # Platform: ts_backfill(x, d)
71
+ 'ts_backfill': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
71
72
  'days_from_last_change': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
72
73
  'last_diff_value': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
73
- 'ts_scale': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
74
- 'ts_entropy': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'd', 'buckets']},
74
+ # Platform: ts_scale(x, d, constant=0)
75
+ 'ts_scale': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
76
+ # Platform: ts_entropy(x, d)
77
+ 'ts_entropy': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
75
78
  'ts_step': {'min_args': 1, 'max_args': 1, 'arg_types': ['number']},
76
79
  'ts_sum': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
77
80
  'ts_co_kurtosis': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
78
81
  'inst_tvr': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
79
- 'ts_decay_exp_window': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'factor']},
82
+ 'ts_decay_exp_window': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'factor'], 'keyword_only': True},
80
83
  'ts_av_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
81
84
  'ts_kurtosis': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
82
- 'ts_min_max_diff': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
85
+ # Platform: ts_min_max_diff(x, d, f=0.5)
86
+ 'ts_min_max_diff': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
83
87
  'ts_arg_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
84
88
  'ts_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
85
- 'ts_min_max_cps': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
86
- 'ts_rank': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
89
+ # Platform: ts_min_max_cps(x, d, f=2)
90
+ 'ts_min_max_cps': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
91
+ # Platform: ts_rank(x, d, constant=0)
92
+ 'ts_rank': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
87
93
  'ts_ir': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
88
94
  'ts_theilsen': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
89
- 'hump_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number']},
90
- 'ts_weighted_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number']},
91
- 'ts_quantile': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'string']},
95
+ # Platform: hump_decay(x, p=0)
96
+ 'hump_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'p'], 'keyword_only': True},
97
+ # Platform: ts_weighted_decay(x, k=0.5)
98
+ 'ts_weighted_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'k'], 'keyword_only': True},
99
+ # Platform: ts_quantile(x, d, driver="gaussian")
100
+ 'ts_quantile': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'string'], 'param_names': ['x', 'd', 'driver'], 'keyword_only': True},
92
101
  'ts_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
93
102
  'ts_count_nans': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
94
103
  'ts_covariance': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
95
104
  'ts_co_skewness': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
96
105
  'ts_min_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
97
- 'ts_decay_linear': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'boolean']},
98
- 'jump_decay': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'number', 'expression', 'number', 'number'], 'param_names': ['x', 'd', 'stddev', 'sensitivity', 'force']},
99
- 'ts_moment': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'k']},
106
+ # Platform: ts_decay_linear(x, d, dense=false)
107
+ 'ts_decay_linear': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'boolean'], 'param_names': ['x', 'd', 'dense'], 'keyword_only': True},
108
+ # Platform: jump_decay(x, d, sensitivity=0.5, force=0.1)
109
+ 'jump_decay': {'min_args': 2, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'd', 'sensitivity', 'force'], 'keyword_only': True},
110
+ # Platform: ts_moment(x, d, k=0)
111
+ 'ts_moment': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'k'], 'keyword_only': True},
100
112
  'ts_arg_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
101
- 'ts_regression': {'min_args': 3, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'lag', 'rettype']},
113
+ 'ts_regression': {'min_args': 3, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'lag', 'rettype'], 'keyword_only': True},
102
114
  'ts_skewness': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
103
115
  'ts_max_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
104
116
  'kth_element': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
105
117
  'hump': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'hump']},
106
118
  'ts_median': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
107
119
  'ts_delta': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
108
- 'ts_poly_regression': {'min_args': 3, 'max_args': 4, 'arg_types': ['expression', 'expression', 'number', 'number']},
109
- 'ts_target_tvr_decay': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr']},
110
- 'ts_target_tvr_delta_limit': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number']},
111
- 'ts_target_tvr_hump': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number']},
112
- 'ts_delta_limit': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
120
+ # Platform: ts_poly_regression(y, x, d, k=1) and k must be keyword if provided
121
+ 'ts_poly_regression': {'min_args': 3, 'max_args': 4, 'arg_types': ['expression', 'expression', 'number', 'number'], 'param_names': ['y', 'x', 'd', 'k'], 'keyword_only': True, 'keyword_only_from': 3},
122
+ 'ts_target_tvr_decay': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
123
+ 'ts_target_tvr_delta_limit': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'expression', 'number', 'number', 'number'], 'param_names': ['x', 'y', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
124
+ 'ts_target_tvr_hump': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'lambda_min', 'lambda_max', 'target_tvr'], 'keyword_only': True},
125
+ # Platform: ts_delta_limit(x, y, limit_volume=0.1)
126
+ 'ts_delta_limit': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'y', 'limit_volume'], 'keyword_only': True},
113
127
 
114
128
  # Special 类别函数
115
129
  'inst_pnl': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
@@ -276,6 +290,10 @@ class ExpressionValidator:
276
290
  self.parser = yacc.yacc(module=self, debug=False)
277
291
  # 错误信息存储
278
292
  self.errors = []
293
+ # Cache for unit inference (unit/scalar/category)
294
+ self._unit_cache: Dict[int, str] = {}
295
+ # Cache for derived category detection (bucket/group_cartesian_product outputs)
296
+ self._derived_category_cache: Dict[int, bool] = {}
279
297
 
280
298
  # 词法分析器规则
281
299
  tokens = ('FUNCTION', 'FIELD', 'NUMBER', 'LPAREN', 'RPAREN',
@@ -509,6 +527,12 @@ class ExpressionValidator:
509
527
  return self._validate_add(args, is_in_group_arg)
510
528
 
511
529
  errors = []
530
+
531
+ # Keyword-only enforcement for optional parameters.
532
+ # If enabled, only the required leading arguments can be positional.
533
+ keyword_only_from = function_info.get('keyword_only_from')
534
+ if keyword_only_from is None and function_info.get('keyword_only'):
535
+ keyword_only_from = function_info.get('min_args', 0)
512
536
 
513
537
  # 检查参数数量
514
538
  if len(args) < function_info['min_args']:
@@ -545,12 +569,14 @@ class ExpressionValidator:
545
569
  errors.append(f"函数 {function_name} 不存在参数 '{arg['name']}'")
546
570
  elif arg['type'] == 'positional':
547
571
  # 位置参数(字典形式)
548
- # 对于winsorize函数,第二个参数必须是命名参数
549
- if function_name == 'winsorize' and positional_index == 1:
550
- errors.append(f"函数 {function_name} 的第二个参数必须使用命名参数 'std='")
551
- # 对于ts_moment函数,第三个参数必须是命名参数
552
- elif function_name == 'ts_moment' and positional_index == 2:
553
- errors.append(f"函数 {function_name} 的第三个参数必须使用命名参数 'k='")
572
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
573
+ param_name = None
574
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
575
+ param_name = function_info['param_names'][positional_index]
576
+ if param_name:
577
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
578
+ else:
579
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
554
580
  else:
555
581
  # 验证位置参数的类型
556
582
  if positional_index < len(function_info['arg_types']):
@@ -564,12 +590,14 @@ class ExpressionValidator:
564
590
  positional_index += 1
565
591
  else:
566
592
  # 位置参数(直接ASTNode形式)
567
- # 对于winsorize函数,第二个参数必须是命名参数
568
- if function_name == 'winsorize' and positional_index == 1:
569
- errors.append(f"函数 {function_name} 的第二个参数必须使用命名参数 'std='")
570
- # 对于ts_moment函数,第三个参数必须是命名参数
571
- elif function_name == 'ts_moment' and positional_index == 2:
572
- errors.append(f"函数 {function_name} 的第三个参数必须使用命名参数 'k='")
593
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
594
+ param_name = None
595
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
596
+ param_name = function_info['param_names'][positional_index]
597
+ if param_name:
598
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
599
+ else:
600
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
573
601
  else:
574
602
  # 验证位置参数的类型
575
603
  if positional_index < len(function_info['arg_types']):
@@ -583,6 +611,16 @@ class ExpressionValidator:
583
611
  def _validate_arg_type(self, arg: ASTNode, expected_type: str, arg_index: int, function_name: str, is_in_group_arg: bool = False) -> List[str]:
584
612
  """验证参数类型是否符合预期"""
585
613
  errors = []
614
+
615
+ # Unit compatibility check
616
+ # bucket()/group_cartesian_product() output a derived category (grouping key).
617
+ # It can only be consumed where a category/grouping key is expected (typically by group_* operators).
618
+ # It must not flow into normal Unit[] inputs like ts_mean's first argument.
619
+ if self._is_derived_category(arg) and expected_type != 'category':
620
+ errors.append(
621
+ f"Incompatible unit for input of \"{function_name}\" at index {arg_index}, expected \"Unit[]\", found \"Unit[Group:1]\""
622
+ )
623
+ return errors
586
624
 
587
625
  # 首先检查是否是group类型字段,如果是则只能用于Group类型函数
588
626
  # 但是如果当前函数是group_xxx或在group函数的参数链中,则允许使用
@@ -618,6 +656,86 @@ class ExpressionValidator:
618
656
 
619
657
  return errors
620
658
 
659
+ def _infer_unit(self, node: ASTNode) -> str:
660
+ """Infer the Unit kind of an AST node.
661
+
662
+ Returns:
663
+ 'unit' - regular numeric time-series Unit[]
664
+ 'scalar' - literals (numbers/booleans/strings)
665
+ 'category' - category/grouping keys (industry/sector or derived via bucket/cartesian)
666
+ """
667
+ if node is None:
668
+ return 'unit'
669
+
670
+ cache_key = id(node)
671
+ cached = self._unit_cache.get(cache_key)
672
+ if cached is not None:
673
+ return cached
674
+
675
+ unit = 'unit'
676
+
677
+ if node.node_type in {'number', 'boolean', 'string'}:
678
+ unit = 'scalar'
679
+ elif node.node_type in {'field', 'identifier'}:
680
+ unit = 'unit'
681
+ elif node.node_type == 'category':
682
+ unit = 'category'
683
+ elif node.node_type in {'unop', 'binop'}:
684
+ child_units = [self._infer_unit(child) for child in node.children if hasattr(child, 'node_type')]
685
+ unit = 'category' if 'category' in child_units else 'unit'
686
+ elif node.node_type == 'function':
687
+ fname = node.value
688
+ # Group-building functions create derived category outputs
689
+ if fname in {'bucket', 'group_cartesian_product'}:
690
+ unit = 'category'
691
+ else:
692
+ # Default: propagate from the first positional argument if available
693
+ first_arg = None
694
+ for child in node.children:
695
+ if isinstance(child, dict):
696
+ if child.get('type') == 'positional':
697
+ first_arg = child.get('value')
698
+ break
699
+ else:
700
+ first_arg = child
701
+ break
702
+
703
+ if hasattr(first_arg, 'node_type'):
704
+ unit = self._infer_unit(first_arg)
705
+ else:
706
+ unit = 'unit'
707
+
708
+ self._unit_cache[cache_key] = unit
709
+ return unit
710
+
711
+ def _is_derived_category(self, node: ASTNode) -> bool:
712
+ """Return True if node is a derived category/grouping key (e.g., bucket/cartesian output)."""
713
+ if node is None:
714
+ return False
715
+
716
+ cache_key = id(node)
717
+ cached = self._derived_category_cache.get(cache_key)
718
+ if cached is not None:
719
+ return cached
720
+
721
+ derived = False
722
+ if node.node_type == 'function' and node.value in {'bucket', 'group_cartesian_product'}:
723
+ derived = True
724
+ elif node.node_type in {'unop', 'binop'}:
725
+ derived = any(
726
+ self._is_derived_category(child)
727
+ for child in node.children
728
+ if hasattr(child, 'node_type')
729
+ )
730
+ elif node.node_type == 'function':
731
+ derived = any(
732
+ self._is_derived_category(child.get('value')) if isinstance(child, dict) else self._is_derived_category(child)
733
+ for child in node.children
734
+ )
735
+
736
+ self._derived_category_cache[cache_key] = derived
737
+ return derived
738
+
621
739
  def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
622
740
  """Validate add(x, y, ..., filter=false).
623
741
 
@@ -885,6 +1003,9 @@ class ExpressionValidator:
885
1003
  """
886
1004
  # 重置错误列表
887
1005
  self.errors = []
1006
+ # Reset unit inference cache for this expression
1007
+ self._unit_cache = {}
1008
+ self._derived_category_cache = {}
888
1009
 
889
1010
  try:
890
1011
  expression = expression.strip()
@@ -7,6 +7,14 @@ A complete web application for decoding string templates with WorldQuant BRAIN i
7
7
  import subprocess
8
8
  import sys
9
9
  import os
10
+ import io
11
+
12
+ # try set gbk problem
13
+ try:
14
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
15
+ except Exception as e:
16
+ print(e)
17
+ pass
10
18
 
11
19
  def install_requirements():
12
20
  """Install required packages from requirements.txt if they're missing"""
@@ -128,6 +136,7 @@ try:
128
136
  import queue
129
137
  import uuid
130
138
  from datetime import datetime
139
+ from blueprints.validator import ExpressionValidator
131
140
  print("📚 Core packages imported successfully!")
132
141
 
133
142
  # Import ace_lib for simulation options
@@ -407,6 +416,51 @@ def test_simulator_connection():
407
416
  'error': f'Connection failed: {str(e)}'
408
417
  })
409
418
 
419
+ @app.route('/api/validate_expressions', methods=['POST'])
420
+ def validate_expressions():
421
+ """Validate a list of expressions using ExpressionValidator"""
422
+ try:
423
+ data = request.json
424
+ expressions = data.get('expressions', [])
425
+
426
+ if not isinstance(expressions, list):
427
+ return jsonify({'error': 'Expressions must be a list'}), 400
428
+
429
+ validator = ExpressionValidator()
430
+ results = []
431
+ valid_count = 0
432
+ invalid_count = 0
433
+
434
+ # Validate all expressions
435
+ expressions_to_validate = expressions
436
+
437
+ for expr in expressions_to_validate:
438
+ validation_result = validator.check_expression(expr)
439
+ if validation_result['valid']:
440
+ valid_count += 1
441
+ results.append({
442
+ 'expression': expr,
443
+ 'valid': True
444
+ })
445
+ else:
446
+ invalid_count += 1
447
+ results.append({
448
+ 'expression': expr,
449
+ 'valid': False,
450
+ 'errors': validation_result['errors']
451
+ })
452
+
453
+ return jsonify({
454
+ 'results': results,
455
+ 'summary': {
456
+ 'total': len(expressions_to_validate),
457
+ 'valid': valid_count,
458
+ 'invalid': invalid_count
459
+ }
460
+ })
461
+ except Exception as e:
462
+ return jsonify({'error': str(e)}), 500
463
+
410
464
  @app.route('/api/simulator/run', methods=['POST'])
411
465
  def run_simulator_with_params():
412
466
  """Run simulator with user-provided parameters in a new terminal"""
@@ -3,6 +3,13 @@ import xml.etree.ElementTree as ET
3
3
  import os
4
4
  import sys
5
5
  import argparse
6
+ import io
7
+ # try set gbk problem
8
+ try:
9
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
10
+ except Exception as e:
11
+ print(e)
12
+ pass
6
13
 
7
14
  def search_arxiv(query, max_results=10):
8
15
  """Search arXiv for papers"""
@@ -22,6 +22,14 @@ from selenium.common.exceptions import TimeoutException, NoSuchElementException
22
22
  import requests
23
23
  import os
24
24
  import shutil
25
+ import io
26
+
27
+ # try set gbk problem
28
+ try:
29
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
30
+ except Exception as e:
31
+ print(e)
32
+ pass
25
33
 
26
34
  # Initialize forum MCP server
27
35
  try:
@@ -15,6 +15,14 @@ from bs4 import BeautifulSoup
15
15
  from playwright.async_api import async_playwright
16
16
  import requests
17
17
  import os
18
+ import io
19
+
20
+ # try set gbk problem
21
+ try:
22
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
23
+ except Exception as e:
24
+ print(e)
25
+ pass
18
26
 
19
27
  def log(message: str, level: str = "INFO"):
20
28
  """Log message with timestamp."""
@@ -18,7 +18,7 @@ import os
18
18
  import sys
19
19
  import math
20
20
  from time import sleep
21
-
21
+ import io
22
22
  import requests
23
23
  import pandas as pd
24
24
  from mcp.server.fastmcp import FastMCP
@@ -29,6 +29,13 @@ from pathlib import Path
29
29
  # Import the new forum client
30
30
  from forum_functions import forum_client
31
31
 
32
+ # try set gbk problem
33
+ try:
34
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
35
+ except Exception as e:
36
+ print(e)
37
+ pass
38
+
32
39
  # Configure logging
33
40
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
34
41
  logger = logging.getLogger(__name__)
@@ -18,6 +18,13 @@ import os
18
18
  import sys
19
19
  import math
20
20
  from time import sleep
21
+ import io
22
+ # try set gbk problem
23
+ try:
24
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
25
+ except Exception as e:
26
+ print(e)
27
+ pass
21
28
 
22
29
  import requests
23
30
  import pandas as pd