cnhkmcp 2.3.0__py3-none-any.whl → 2.3.2__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 (55) hide show
  1. cnhkmcp/__init__.py +1 -1
  2. cnhkmcp/untracked/APP/Tranformer/parsetab.py +60 -0
  3. cnhkmcp/untracked/APP/Tranformer/validator.py +78 -4
  4. cnhkmcp/untracked/APP/static/inspiration.js +46 -4
  5. cnhkmcp/untracked/APP/templates/index.html +33 -0
  6. cnhkmcp/untracked/APP/trailSomeAlphas/enhance_template.py +132 -6
  7. cnhkmcp/untracked/APP/trailSomeAlphas/run_pipeline.py +135 -85
  8. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/SKILL.md +17 -0
  9. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-data-feature-engineering/output_report/GLB_delay1_fundamental72_ideas.md +415 -0
  10. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/final_expressions.json +76 -0
  11. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852468022627100.json +22 -0
  12. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852468554457600.json +14 -0
  13. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852469133324600.json +8 -0
  14. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852469704433900.json +10 -0
  15. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852470248911900.json +10 -0
  16. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852470805192900.json +8 -0
  17. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852471380158000.json +10 -0
  18. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852471944247400.json +22 -0
  19. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852472483548800.json +14 -0
  20. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852473053891800.json +22 -0
  21. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852473617716000.json +22 -0
  22. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852474172815700.json +14 -0
  23. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852474735778500.json +10 -0
  24. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852475315478500.json +14 -0
  25. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852475912897000.json +8 -0
  26. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852476474911100.json +10 -0
  27. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852978914367200.json +10 -0
  28. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852979426164800.json +10 -0
  29. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852979945511100.json +10 -0
  30. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852980480251500.json +10 -0
  31. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769852981007315500.json +10 -0
  32. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854621979784200.json +10 -0
  33. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854622483457900.json +10 -0
  34. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854623010559800.json +10 -0
  35. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854623572902300.json +5 -0
  36. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_1_idea_1769854624091016000.json +10 -0
  37. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv +330 -0
  38. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv.bak_1769852868 +330 -0
  39. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/data/fundamental72_GLB_delay1/fundamental72_GLB_delay1.csv.bak_1769854511 +330 -0
  40. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/ace.log +12 -0
  41. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/fetch_dataset.py +7 -1
  42. cnhkmcp/untracked/APP/trailSomeAlphas/skills/brain-feature-implementation/scripts/validator.py +80 -4
  43. cnhkmcp/untracked/APP/trailSomeAlphas/skills/template_final_enhance/op/321/206/320/220/342/225/227/321/207/342/225/227/320/243.md +24 -18
  44. 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 +27 -2
  45. cnhkmcp/untracked/back_up/platform_functions.py +2 -2
  46. 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 +2 -2
  47. cnhkmcp/untracked/platform_functions.py +2 -2
  48. cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/parsetab.py +60 -0
  49. cnhkmcp/untracked/skills/alpha-expression-verifier/scripts/validator.py +78 -4
  50. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/METADATA +1 -1
  51. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/RECORD +55 -22
  52. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/WHEEL +0 -0
  53. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/entry_points.txt +0 -0
  54. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/licenses/LICENSE +0 -0
  55. {cnhkmcp-2.3.0.dist-info → cnhkmcp-2.3.2.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,15 @@
1
1
  2026-01-30 01:46:34,276 - ace - ERROR -
2
2
  Incorrect email or password
3
3
 
4
+ 2026-01-31 17:23:24,903 - ace - ERROR -
5
+ Incorrect email or password
6
+
7
+ 2026-01-31 17:23:25,385 - ace - ERROR -
8
+ Incorrect email or password
9
+
10
+ 2026-01-31 17:23:25,859 - ace - ERROR -
11
+ Incorrect email or password
12
+
13
+ 2026-01-31 17:23:26,330 - ace - ERROR -
14
+ Incorrect email or password
15
+
@@ -32,6 +32,12 @@ def main():
32
32
  parser.add_argument("--delay", type=int, default=1, help="Delay (default: 1)")
33
33
  parser.add_argument("--universe", default="TOP3000", help="Universe (default: TOP3000)")
34
34
  parser.add_argument("--instrument-type", default="EQUITY", dest="instrument_type", help="Instrument Type (default: EQUITY)")
35
+ parser.add_argument(
36
+ "--data-type",
37
+ default="MATRIX",
38
+ choices=["MATRIX", "VECTOR"],
39
+ help="Data type to request from BRAIN datafields (MATRIX or VECTOR). Default: MATRIX",
40
+ )
35
41
 
36
42
  args = parser.parse_args()
37
43
 
@@ -83,7 +89,7 @@ def main():
83
89
  delay=args.delay,
84
90
  universe=args.universe,
85
91
  instrument_type=args.instrument_type,
86
- data_type="ALL",
92
+ data_type=args.data_type,
87
93
  )
88
94
 
89
95
  if df is None or df.empty:
@@ -170,7 +170,10 @@ supported_functions = {
170
170
  'scale_down': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
171
171
 
172
172
  # Arithmetic 类别函数
173
- 'add': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # add(x, y, filter=false)
173
+ # add(x, y, ..., filter=false)
174
+ # NOTE: add() is variadic (>=2 terms) with an optional boolean filter flag.
175
+ # We validate it with custom logic in validate_function().
176
+ 'add': {'min_args': 2, 'max_args': 101, 'arg_types': ['expression'] * 101},
174
177
  'multiply': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 99 + ['boolean'], 'param_names': ['x', 'y', 'filter']}, # multiply(x, y, ..., filter=false)
175
178
  'sign': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
176
179
  'subtract': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # subtract(x, y, filter=false)
@@ -501,6 +504,10 @@ class ExpressionValidator:
501
504
  if not function_info:
502
505
  return [f"未知函数: {function_name}"]
503
506
 
507
+ # Custom validation for variadic functions with optional flags
508
+ if function_name == 'add':
509
+ return self._validate_add(args, is_in_group_arg)
510
+
504
511
  errors = []
505
512
 
506
513
  # 检查参数数量
@@ -591,9 +598,9 @@ class ExpressionValidator:
591
598
  if arg.node_type != 'number':
592
599
  errors.append(f"参数 {arg_index+1} 应该是一个数字,但得到 {arg.node_type}")
593
600
  elif expected_type == 'boolean':
594
- # 布尔值可以是数字(0/1)
595
- if arg.node_type != 'number':
596
- errors.append(f"参数 {arg_index+1} 应该是一个布尔值(0/1),但得到 {arg.node_type}")
601
+ # 布尔值可以是 true/false 或数字(0/1)
602
+ if arg.node_type not in {'boolean', 'number'}:
603
+ errors.append(f"参数 {arg_index+1} 应该是一个布尔值(true/false 或 0/1),但得到 {arg.node_type}")
597
604
  elif expected_type == 'field':
598
605
  if arg.node_type != 'field' and arg.node_type != 'category':
599
606
  # 允许field或category作为字段参数
@@ -610,6 +617,75 @@ class ExpressionValidator:
610
617
  # group函数的category参数可以是任何类型(field、category等),不进行类型校验
611
618
 
612
619
  return errors
620
+
621
+ def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
622
+ """Validate add(x, y, ..., filter=false).
623
+
624
+ Rules:
625
+ - At least 2 positional expression terms.
626
+ - Optional filter flag can be provided as:
627
+ - named argument: filter=<boolean>
628
+ - last positional argument: <boolean>
629
+ """
630
+ errors: List[str] = []
631
+
632
+ if len(args) < 2:
633
+ return [f"函数 add 需要至少 2 个参数,但只提供了 {len(args)}"]
634
+
635
+ named_filter_nodes: List[ASTNode] = []
636
+ positional_nodes: List[ASTNode] = []
637
+
638
+ for arg in args:
639
+ if isinstance(arg, dict) and arg.get('type') == 'named':
640
+ name = arg.get('name')
641
+ value = arg.get('value')
642
+ if name != 'filter':
643
+ errors.append(f"函数 add 不存在参数 '{name}'")
644
+ continue
645
+ if not hasattr(value, 'node_type'):
646
+ errors.append("函数 add 的参数 filter 格式错误")
647
+ continue
648
+ named_filter_nodes.append(value)
649
+ elif isinstance(arg, dict) and arg.get('type') == 'positional':
650
+ value = arg.get('value')
651
+ if hasattr(value, 'node_type'):
652
+ positional_nodes.append(value)
653
+ else:
654
+ errors.append("函数 add 的位置参数格式错误")
655
+ elif hasattr(arg, 'node_type'):
656
+ positional_nodes.append(arg)
657
+ else:
658
+ errors.append("函数 add 的参数格式错误")
659
+
660
+ if len(named_filter_nodes) > 1:
661
+ errors.append("函数 add 的参数 'filter' 只能出现一次")
662
+
663
+ positional_filter_node: Optional[ASTNode] = None
664
+ # Only infer a positional filter flag when:
665
+ # - no named filter is provided
666
+ # - there are at least 3 positional args (x, y, filter)
667
+ # - the last arg is boolean or numeric 0/1
668
+ if not named_filter_nodes and len(positional_nodes) >= 3:
669
+ last = positional_nodes[-1]
670
+ if last.node_type == 'boolean' or (last.node_type == 'number' and last.value in {0, 1}):
671
+ positional_filter_node = positional_nodes.pop()
672
+
673
+ if len(positional_nodes) < 2:
674
+ errors.append(f"函数 add 需要至少 2 个输入项(不含filter),但只提供了 {len(positional_nodes)}")
675
+
676
+ # Validate all term inputs as expressions (no-op, but keep recursion behavior consistent)
677
+ for idx, node in enumerate(positional_nodes):
678
+ errors.extend(self._validate_arg_type(node, 'expression', idx, 'add', is_in_group_arg))
679
+
680
+ # Validate filter, if present (named takes precedence; if both present, that's an error)
681
+ if positional_filter_node is not None and named_filter_nodes:
682
+ errors.append("函数 add 的 filter 不能同时用位置参数和命名参数传递")
683
+ if positional_filter_node is not None:
684
+ errors.extend(self._validate_arg_type(positional_filter_node, 'boolean', len(positional_nodes), 'add', is_in_group_arg))
685
+ if named_filter_nodes:
686
+ errors.extend(self._validate_arg_type(named_filter_nodes[0], 'boolean', len(positional_nodes), 'add', is_in_group_arg))
687
+
688
+ return errors
613
689
 
614
690
  def validate_ast(self, ast: Optional[ASTNode], is_in_group_arg: bool = False) -> List[str]:
615
691
  """递归验证抽象语法树"""
@@ -194,27 +194,36 @@ days_from_last_change(A) / last_diff_value(A,d)
194
194
  场景:
195
195
  因子是“事件驱动型”的(评级变动、ESG 评级更新等),只在变动后若干天内才有信号;
196
196
  可以构造“距上次变化时间”的信号。
197
- 七、Reduce / Combo 等(reduce_ / combo_a / self_corr 等)*
197
+ 七、Vector Operator*
198
+ 这些用于处理“向量类型数据”,需要先生成统计特征才能用。
198
199
 
199
- 这些更多在多因子/多 Alpha 组合层面用,但单因子也有场景:
200
+ 补充:常用 Vector Operators(Combo, Regular)
200
201
 
201
- reduce_*(avg / max / min / stddev / percentage等)
202
+ > 一句话描述:Vector 字段(例如一只股票一天里的一串向量值)不能直接参与普通的算术/时序/截面运算,通常需要先用 `vec_*` 把它“降维”为标量特征(均值/分位/波动/偏度等),再进入后续流程。
202
203
 
203
- 场景:
204
- 你想从一个“多维度向量”提炼单值,例如同一公司的多条 ESG 子项合成一个指标;
205
- 或在历史上把 A 的波动性/偏度/峰度作为新的单因子。
206
- combo_a(alphas, ...)
204
+ - `vec_avg(x)`(base):对向量求均值。例:输入 (2,3,5,6,3,8,10) 输出 37/7=5.29
205
+ - `vec_sum(x)`(base):对向量求和。例:输入 (2,3,5,6,3,8,10) 输出 37
206
+ - `vec_count(x)`(genius):向量元素个数
207
+ - `vec_choose(x, nth=k)`(genius):取向量中第 k 个元素(从 0 开始计数)
208
+ - `vec_max(x)`(genius):向量最大值
209
+ - `vec_min(x)`(genius):向量最小值
210
+ - `vec_range(x)`(genius):向量极差(max-min)
211
+ - `vec_stddev(x)`(genius):向量标准差
212
+ - `vec_ir(x)`(genius):向量信息比率(均值/标准差)
213
+ - `vec_skewness(x)`(genius):向量偏度
214
+ - `vec_kurtosis(x)`(genius):向量峰度
215
+ - `vec_norm(x)`(genius):向量范数(所有元素绝对值之和)
216
+ - `vec_percentage(x, percentage=0.5)`(genius):向量分位数(如 0.5 为中位数)
217
+ - `vec_powersum(x, constant=2)`(genius):向量幂和(对每个元素做幂后求和)
218
+ - `vec_filter(vec, value=nan)`(genius):按值过滤向量(可一次过滤多个值,例如 `"nan 0 10"`)。注意:输出仍然是 VECTOR 类型(还未降维)。
219
+
220
+ 注意:
221
+ - Vector type 数据不能直接用;你必须先用一个 `vec_*` 把它变成标量特征后再做后续组合。
222
+ - `vec_*` 只能作用在 VECTOR 类型数据上;普通 MATRIX 字段不能直接喂给 `vec_*`。
207
223
 
208
- 场景:
209
- 多个不同构造方式的同主题因子(比如多个 ESG 变体)组合,提升稳健性;
210
- 单一指标不够可靠,想自动根据历史 IR 给权重。
211
- self_corr(A)
212
224
 
213
- 场景:
214
- 研究同因子在截面上的相关结构,比如 ESG 因子和某个 sector pattern 是否极度重叠;
215
- 更偏研究/调试,不是直接构造单因子。
216
- 小结(对应你说的那些“现实情景”):
217
225
 
226
+ 小结:
218
227
  只头部/尾部有信号:
219
228
  想起:rank + bucket + 逻辑算子(greater/less/if_else/trade_when)、right_tail / left_tail。
220
229
  中间区间是噪音:
@@ -396,11 +405,8 @@ ts_delta(B,d) / days_from_last_change(B)
396
405
  例如 volume 爆量那几天放大 A,平时保持中立:
397
406
  b_spike = greater(ts_delta(B,1), thresh)
398
407
  core = trade_when(b_spike, 1.5*A, A)。
399
- 七、Reduce / Combo 等
400
408
 
401
- 多数用于多维/多因子的情况,不再一一展开;简单原则:
402
409
 
403
- 若 B 其实是一组值(如多个 volume 相关字段),先用 reduce_avg/reduce_max 合成一个 B_agg,再按前面逻辑当作单一 B 处理。
404
410
  最后给一个总的“使用从属 B 的模板公式”
405
411
 
406
412
  可以抽象成:
@@ -2376,6 +2376,7 @@ def inspiration_generate():
2376
2376
  delay = data.get('delay')
2377
2377
  universe = data.get('universe')
2378
2378
  dataset_id = data.get('datasetId')
2379
+ data_type = data.get('dataType') or 'MATRIX'
2379
2380
 
2380
2381
  try:
2381
2382
  import openai
@@ -2391,10 +2392,13 @@ def inspiration_generate():
2391
2392
  if not s:
2392
2393
  return jsonify({'error': 'Not logged in'}), 401
2393
2394
 
2395
+ if data_type not in ("MATRIX", "VECTOR"):
2396
+ data_type = "MATRIX"
2397
+
2394
2398
  operators_df = get_operators(s)
2395
2399
  operators_df = operators_df[operators_df['scope'] == 'REGULAR']
2396
2400
 
2397
- datafields_df = get_datafields(s, region=region, delay=int(delay), universe=universe, dataset_id=dataset_id, data_type="ALL")
2401
+ datafields_df = get_datafields(s, region=region, delay=int(delay), universe=universe, dataset_id=dataset_id, data_type=data_type)
2398
2402
 
2399
2403
  # count the datatype of the datafields_df, if most of them are VECTOR, then we keep the VECTOR category operators in the operators_df, otherwise we remove them
2400
2404
  datatype_counts = datafields_df['type'].value_counts().to_dict()
@@ -2516,6 +2520,7 @@ def inspiration_run_pipeline():
2516
2520
  region = data.get('region')
2517
2521
  delay = data.get('delay')
2518
2522
  universe = data.get('universe')
2523
+ data_type = data.get('dataType') or 'MATRIX'
2519
2524
  api_key = data.get('apiKey')
2520
2525
  base_url = data.get('baseUrl')
2521
2526
  model = data.get('model')
@@ -2541,6 +2546,11 @@ def inspiration_run_pipeline():
2541
2546
 
2542
2547
  def run_process():
2543
2548
  try:
2549
+ if data_type not in ("MATRIX", "VECTOR"):
2550
+ dt = "MATRIX"
2551
+ else:
2552
+ dt = str(data_type)
2553
+
2544
2554
  cmd = [
2545
2555
  sys.executable,
2546
2556
  run_pipeline_path,
@@ -2548,7 +2558,8 @@ def inspiration_run_pipeline():
2548
2558
  '--region', str(region),
2549
2559
  '--delay', str(delay),
2550
2560
  '--dataset-id', str(dataset_id),
2551
- '--universe', str(universe)
2561
+ '--universe', str(universe),
2562
+ '--data-type', dt,
2552
2563
  ]
2553
2564
 
2554
2565
  if api_key:
@@ -2672,6 +2683,13 @@ def inspiration_enhance_template():
2672
2683
  api_key = request.form.get('apiKey')
2673
2684
  base_url = request.form.get('baseUrl')
2674
2685
  model = request.form.get('model')
2686
+ data_type = request.form.get('dataType') or 'MATRIX'
2687
+
2688
+ session_id = request.headers.get('Session-ID') or flask_session.get('brain_session_id')
2689
+ session_info = brain_sessions.get(session_id) if session_id else None
2690
+
2691
+ if data_type not in ("MATRIX", "VECTOR"):
2692
+ data_type = "MATRIX"
2675
2693
 
2676
2694
  if not idea_files or not api_key:
2677
2695
  return jsonify({'success': False, 'error': 'Missing ideaFiles or apiKey'}), 400
@@ -2711,12 +2729,19 @@ def inspiration_enhance_template():
2711
2729
  env = os.environ.copy()
2712
2730
  env['IDEA_JSON'] = idea_path
2713
2731
  env['MOONSHOT_API_KEY'] = api_key
2732
+ env['DATA_TYPE'] = str(data_type)
2714
2733
  if base_url:
2715
2734
  env['MOONSHOT_BASE_URL'] = base_url
2716
2735
  if model:
2717
2736
  env['MOONSHOT_MODEL'] = model
2718
2737
  env['PYTHONIOENCODING'] = 'utf-8'
2719
2738
 
2739
+ # Inherit BRAIN auth from the logged-in web session (same as run-pipeline).
2740
+ # Do NOT accept raw credentials from the enhance form.
2741
+ if session_info and session_info.get('username') and session_info.get('password'):
2742
+ env['BRAIN_USERNAME'] = session_info['username']
2743
+ env['BRAIN_PASSWORD'] = session_info['password']
2744
+
2720
2745
  log_queue.put(f"=== 开始处理: {name} ({idx}/{total}) ===")
2721
2746
  proc = subprocess.Popen(
2722
2747
  [sys.executable, enhance_script],
@@ -423,7 +423,7 @@ class BrainApiClient:
423
423
  return False
424
424
 
425
425
  async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
426
- delay: int = 1, universe: str = "TOP3000", theme: str = "false", search: Optional[str] = None) -> Dict[str, Any]:
426
+ delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
427
427
  """Get available datasets."""
428
428
  await self.ensure_authenticated()
429
429
 
@@ -1883,7 +1883,7 @@ async def get_datasets(
1883
1883
  region: str = "USA",
1884
1884
  delay: int = 1,
1885
1885
  universe: str = "TOP3000",
1886
- theme: str = "false",
1886
+ theme: str = "ALL",
1887
1887
  search: Optional[str] = None
1888
1888
  ) -> Dict[str, Any]:
1889
1889
  """
@@ -357,7 +357,7 @@ class BrainApiClient:
357
357
  raise
358
358
 
359
359
  async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
360
- delay: int = 1, universe: str = "TOP3000", theme: str = "false", search: Optional[str] = None) -> Dict[str, Any]:
360
+ delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
361
361
  """Get available datasets."""
362
362
  await self.ensure_authenticated()
363
363
 
@@ -1761,7 +1761,7 @@ async def get_datasets(
1761
1761
  region: str = "USA",
1762
1762
  delay: int = 1,
1763
1763
  universe: str = "TOP3000",
1764
- theme: str = "false",
1764
+ theme: str = "ALL",
1765
1765
  search: Optional[str] = None,
1766
1766
  ) -> Dict[str, Any]:
1767
1767
  """
@@ -357,7 +357,7 @@ class BrainApiClient:
357
357
  raise
358
358
 
359
359
  async def get_datasets(self, instrument_type: str = "EQUITY", region: str = "USA",
360
- delay: int = 1, universe: str = "TOP3000", theme: str = "false", search: Optional[str] = None) -> Dict[str, Any]:
360
+ delay: int = 1, universe: str = "TOP3000", theme: str = "ALL", search: Optional[str] = None) -> Dict[str, Any]:
361
361
  """Get available datasets."""
362
362
  await self.ensure_authenticated()
363
363
 
@@ -1761,7 +1761,7 @@ async def get_datasets(
1761
1761
  region: str = "USA",
1762
1762
  delay: int = 1,
1763
1763
  universe: str = "TOP3000",
1764
- theme: str = "false",
1764
+ theme: str = "ALL",
1765
1765
  search: Optional[str] = None,
1766
1766
  ) -> Dict[str, Any]:
1767
1767
  """
@@ -0,0 +1,60 @@
1
+
2
+ # parsetab.py
3
+ # This file is automatically generated. Do not edit.
4
+ # pylint: disable=W,C,R
5
+ _tabversion = '3.10'
6
+
7
+ _lr_method = 'LALR'
8
+
9
+ _lr_signature = 'ASSIGN BOOLEAN CATEGORY COMMA DIVIDE EQUAL FIELD FUNCTION GREATER GREATEREQUAL IDENTIFIER LESS LESSEQUAL LPAREN MINUS NOTEQUAL NUMBER PLUS RPAREN STRING TIMESexpression : comparison\n| expression EQUAL comparison\n| expression NOTEQUAL comparison\n| expression GREATER comparison\n| expression LESS comparison\n| expression GREATEREQUAL comparison\n| expression LESSEQUAL comparisoncomparison : term\n| comparison PLUS term\n| comparison MINUS termterm : factor\n| term TIMES factor\n| term DIVIDE factorfactor : NUMBER\n| STRING\n| FIELD\n| CATEGORY\n| IDENTIFIER\n| BOOLEAN\n| MINUS factor\n| LPAREN expression RPAREN\n| function_callfunction_call : FUNCTION LPAREN args RPARENargs : arg_list\n| emptyarg_list : arg\n| arg_list COMMA argarg : expression\n| IDENTIFIER ASSIGN expressionempty :'
10
+
11
+ _lr_action_items = {'NUMBER':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,]),'STRING':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,]),'FIELD':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,]),'CATEGORY':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,]),'IDENTIFIER':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[10,10,10,10,10,10,10,10,10,10,10,10,10,44,44,10,]),'BOOLEAN':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,11,]),'MINUS':([0,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17,18,19,20,21,22,23,24,25,27,28,29,30,31,32,33,34,35,36,37,38,44,45,46,47,],[4,22,-8,4,-11,-14,-15,-16,-17,-18,-19,4,-22,4,4,4,4,4,4,4,4,4,4,-20,4,22,22,22,22,22,22,-9,-10,-12,-13,-21,-18,-23,4,4,]),'LPAREN':([0,4,12,14,15,16,17,18,19,20,21,22,23,24,27,46,47,],[12,12,12,27,12,12,12,12,12,12,12,12,12,12,12,12,12,]),'FUNCTION':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,]),'$end':([1,2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,45,],[0,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,-23,]),'EQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[15,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,15,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,15,-18,-23,15,]),'NOTEQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[16,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,16,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,16,-18,-23,16,]),'GREATER':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[17,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,17,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,17,-18,-23,17,]),'LESS':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[18,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,18,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,18,-18,-23,18,]),'GREATEREQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[19,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,19,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,19,-18,-23,19,]),'LESSEQUAL':([1,2,3,5,6,7,8,9,10,11,13,25,26,28,29,30,31,32,33,34,35,36,37,38,43,44,45,49,],[20,-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,20,-18,-23,20,]),'RPAREN':([2,3,5,6,7,8,9,10,11,13,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,48,49,],[-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,38,-30,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,45,-24,-25,-26,-28,-18,-23,-27,-29,]),'COMMA':([2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,40,42,43,44,45,48,49,],[-1,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,-2,-3,-4,-5,-6,-7,-9,-10,-12,-13,-21,46,-26,-28,-18,-23,-27,-29,]),'PLUS':([2,3,5,6,7,8,9,10,11,13,25,28,29,30,31,32,33,34,35,36,37,38,44,45,],[21,-8,-11,-14,-15,-16,-17,-18,-19,-22,-20,21,21,21,21,21,21,-9,-10,-12,-13,-21,-18,-23,]),'TIMES':([3,5,6,7,8,9,10,11,13,25,34,35,36,37,38,44,45,],[23,-11,-14,-15,-16,-17,-18,-19,-22,-20,23,23,-12,-13,-21,-18,-23,]),'DIVIDE':([3,5,6,7,8,9,10,11,13,25,34,35,36,37,38,44,45,],[24,-11,-14,-15,-16,-17,-18,-19,-22,-20,24,24,-12,-13,-21,-18,-23,]),'ASSIGN':([44,],[47,]),}
12
+
13
+ _lr_action = {}
14
+ for _k, _v in _lr_action_items.items():
15
+ for _x,_y in zip(_v[0],_v[1]):
16
+ if not _x in _lr_action: _lr_action[_x] = {}
17
+ _lr_action[_x][_k] = _y
18
+ del _lr_action_items
19
+
20
+ _lr_goto_items = {'expression':([0,12,27,46,47,],[1,26,43,43,49,]),'comparison':([0,12,15,16,17,18,19,20,27,46,47,],[2,2,28,29,30,31,32,33,2,2,2,]),'term':([0,12,15,16,17,18,19,20,21,22,27,46,47,],[3,3,3,3,3,3,3,3,34,35,3,3,3,]),'factor':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[5,25,5,5,5,5,5,5,5,5,5,36,37,5,5,5,]),'function_call':([0,4,12,15,16,17,18,19,20,21,22,23,24,27,46,47,],[13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,]),'args':([27,],[39,]),'arg_list':([27,],[40,]),'empty':([27,],[41,]),'arg':([27,46,],[42,48,]),}
21
+
22
+ _lr_goto = {}
23
+ for _k, _v in _lr_goto_items.items():
24
+ for _x, _y in zip(_v[0], _v[1]):
25
+ if not _x in _lr_goto: _lr_goto[_x] = {}
26
+ _lr_goto[_x][_k] = _y
27
+ del _lr_goto_items
28
+ _lr_productions = [
29
+ ("S' -> expression","S'",1,None,None,None),
30
+ ('expression -> comparison','expression',1,'p_expression','validator.py',386),
31
+ ('expression -> expression EQUAL comparison','expression',3,'p_expression','validator.py',387),
32
+ ('expression -> expression NOTEQUAL comparison','expression',3,'p_expression','validator.py',388),
33
+ ('expression -> expression GREATER comparison','expression',3,'p_expression','validator.py',389),
34
+ ('expression -> expression LESS comparison','expression',3,'p_expression','validator.py',390),
35
+ ('expression -> expression GREATEREQUAL comparison','expression',3,'p_expression','validator.py',391),
36
+ ('expression -> expression LESSEQUAL comparison','expression',3,'p_expression','validator.py',392),
37
+ ('comparison -> term','comparison',1,'p_comparison','validator.py',399),
38
+ ('comparison -> comparison PLUS term','comparison',3,'p_comparison','validator.py',400),
39
+ ('comparison -> comparison MINUS term','comparison',3,'p_comparison','validator.py',401),
40
+ ('term -> factor','term',1,'p_term','validator.py',408),
41
+ ('term -> term TIMES factor','term',3,'p_term','validator.py',409),
42
+ ('term -> term DIVIDE factor','term',3,'p_term','validator.py',410),
43
+ ('factor -> NUMBER','factor',1,'p_factor','validator.py',417),
44
+ ('factor -> STRING','factor',1,'p_factor','validator.py',418),
45
+ ('factor -> FIELD','factor',1,'p_factor','validator.py',419),
46
+ ('factor -> CATEGORY','factor',1,'p_factor','validator.py',420),
47
+ ('factor -> IDENTIFIER','factor',1,'p_factor','validator.py',421),
48
+ ('factor -> BOOLEAN','factor',1,'p_factor','validator.py',422),
49
+ ('factor -> MINUS factor','factor',2,'p_factor','validator.py',423),
50
+ ('factor -> LPAREN expression RPAREN','factor',3,'p_factor','validator.py',424),
51
+ ('factor -> function_call','factor',1,'p_factor','validator.py',425),
52
+ ('function_call -> FUNCTION LPAREN args RPAREN','function_call',4,'p_function_call','validator.py',453),
53
+ ('args -> arg_list','args',1,'p_args','validator.py',457),
54
+ ('args -> empty','args',1,'p_args','validator.py',458),
55
+ ('arg_list -> arg','arg_list',1,'p_arg_list','validator.py',465),
56
+ ('arg_list -> arg_list COMMA arg','arg_list',3,'p_arg_list','validator.py',466),
57
+ ('arg -> expression','arg',1,'p_arg','validator.py',473),
58
+ ('arg -> IDENTIFIER ASSIGN expression','arg',3,'p_arg','validator.py',474),
59
+ ('empty -> <empty>','empty',0,'p_empty','validator.py',481),
60
+ ]
@@ -170,7 +170,10 @@ supported_functions = {
170
170
  'scale_down': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
171
171
 
172
172
  # Arithmetic 类别函数
173
- 'add': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # add(x, y, filter=false)
173
+ # add(x, y, ..., filter=false)
174
+ # NOTE: add() is variadic (>=2 terms) with an optional boolean filter flag.
175
+ # We validate it with custom logic in validate_function().
176
+ 'add': {'min_args': 2, 'max_args': 101, 'arg_types': ['expression'] * 101},
174
177
  'multiply': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 99 + ['boolean'], 'param_names': ['x', 'y', 'filter']}, # multiply(x, y, ..., filter=false)
175
178
  'sign': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
176
179
  'subtract': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # subtract(x, y, filter=false)
@@ -501,6 +504,10 @@ class ExpressionValidator:
501
504
  if not function_info:
502
505
  return [f"未知函数: {function_name}"]
503
506
 
507
+ # Custom validation for variadic functions with optional flags
508
+ if function_name == 'add':
509
+ return self._validate_add(args, is_in_group_arg)
510
+
504
511
  errors = []
505
512
 
506
513
  # 检查参数数量
@@ -591,9 +598,9 @@ class ExpressionValidator:
591
598
  if arg.node_type != 'number':
592
599
  errors.append(f"参数 {arg_index+1} 应该是一个数字,但得到 {arg.node_type}")
593
600
  elif expected_type == 'boolean':
594
- # 布尔值可以是数字(0/1)
595
- if arg.node_type != 'number':
596
- errors.append(f"参数 {arg_index+1} 应该是一个布尔值(0/1),但得到 {arg.node_type}")
601
+ # 布尔值可以是 true/false 或数字(0/1)
602
+ if arg.node_type not in {'boolean', 'number'}:
603
+ errors.append(f"参数 {arg_index+1} 应该是一个布尔值(true/false 或 0/1),但得到 {arg.node_type}")
597
604
  elif expected_type == 'field':
598
605
  if arg.node_type != 'field' and arg.node_type != 'category':
599
606
  # 允许field或category作为字段参数
@@ -610,6 +617,73 @@ class ExpressionValidator:
610
617
  # group函数的category参数可以是任何类型(field、category等),不进行类型校验
611
618
 
612
619
  return errors
620
+
621
+ def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
622
+ """Validate add(x, y, ..., filter=false).
623
+
624
+ Rules:
625
+ - At least 2 positional expression terms.
626
+ - Optional filter flag can be provided as:
627
+ - named argument: filter=<boolean>
628
+ - last positional argument: <boolean> or 0/1
629
+ """
630
+ errors: List[str] = []
631
+
632
+ if len(args) < 2:
633
+ return [f"函数 add 需要至少 2 个参数,但只提供了 {len(args)}"]
634
+
635
+ named_filter_nodes: List[ASTNode] = []
636
+ positional_nodes: List[ASTNode] = []
637
+
638
+ for arg in args:
639
+ if isinstance(arg, dict) and arg.get('type') == 'named':
640
+ name = arg.get('name')
641
+ value = arg.get('value')
642
+ if name != 'filter':
643
+ errors.append(f"函数 add 不存在参数 '{name}'")
644
+ continue
645
+ if not hasattr(value, 'node_type'):
646
+ errors.append("函数 add 的参数 filter 格式错误")
647
+ continue
648
+ named_filter_nodes.append(value)
649
+ elif isinstance(arg, dict) and arg.get('type') == 'positional':
650
+ value = arg.get('value')
651
+ if hasattr(value, 'node_type'):
652
+ positional_nodes.append(value)
653
+ else:
654
+ errors.append("函数 add 的位置参数格式错误")
655
+ elif hasattr(arg, 'node_type'):
656
+ positional_nodes.append(arg)
657
+ else:
658
+ errors.append("函数 add 的参数格式错误")
659
+
660
+ if len(named_filter_nodes) > 1:
661
+ errors.append("函数 add 的参数 'filter' 只能出现一次")
662
+
663
+ positional_filter_node: Optional[ASTNode] = None
664
+ # Only infer a positional filter flag when:
665
+ # - no named filter is provided
666
+ # - there are at least 3 positional args (x, y, filter)
667
+ # - the last arg is boolean or numeric 0/1
668
+ if not named_filter_nodes and len(positional_nodes) >= 3:
669
+ last = positional_nodes[-1]
670
+ if last.node_type == 'boolean' or (last.node_type == 'number' and last.value in {0, 1}):
671
+ positional_filter_node = positional_nodes.pop()
672
+
673
+ if len(positional_nodes) < 2:
674
+ errors.append(f"函数 add 需要至少 2 个输入项(不含filter),但只提供了 {len(positional_nodes)}")
675
+
676
+ for idx, node in enumerate(positional_nodes):
677
+ errors.extend(self._validate_arg_type(node, 'expression', idx, 'add', is_in_group_arg))
678
+
679
+ if positional_filter_node is not None and named_filter_nodes:
680
+ errors.append("函数 add 的 filter 不能同时用位置参数和命名参数传递")
681
+ if positional_filter_node is not None:
682
+ errors.extend(self._validate_arg_type(positional_filter_node, 'boolean', len(positional_nodes), 'add', is_in_group_arg))
683
+ if named_filter_nodes:
684
+ errors.extend(self._validate_arg_type(named_filter_nodes[0], 'boolean', len(positional_nodes), 'add', is_in_group_arg))
685
+
686
+ return errors
613
687
 
614
688
  def validate_ast(self, ast: Optional[ASTNode], is_in_group_arg: bool = False) -> List[str]:
615
689
  """递归验证抽象语法树"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cnhkmcp
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: A comprehensive Model Context Protocol (MCP) server for quantitative trading platform integration
5
5
  Home-page: https://github.com/cnhk/cnhkmcp
6
6
  Author: CNHK