, ) - ts_delta(implied_volatility_put_, )`。
+* **核心思想**: Call與Put隱含波動變化差捕捉情緒轉折;可做多情緒改善、做空情緒惡化。
+* **優化**: 加`trade_when(abs(skew)>thr)`門檻;財報前後縮窗;行業中性。
+
+### 8. 殘差動量精簡版
+* **表達式**: `res = regression_neut(returns, ); ts_mean(res, )`。
+* **核心思想**: 先剝離市場/風格暴露,再對特異收益做動量;較原版多重回歸更輕量。
+* **優化**: 使用`ts_decay_linear`增加近期權重;行業內`group_rank`提升截面穩定度。
+
+### 9. 分紅/現金流組間殘差(簡版)
+* **表達式**: `alpha = ts_zscore(ts_backfill(,90)); g = group_mean(alpha, , ); resid = alpha - g; group_zscore(resid, )`。
+* **核心思想**: 先回填平滑,再对組均值做殘差,捕捉組內相對高/低分紅或現金流質量。
+* **適用場景**: fnd8/fnd6/topdiv等分紅現金流字段;行業/國家分組。
+* **優化**: 權重可用log(cap)或vol逆;對resid再做`ts_mean`平滑。
+
+---
+
+## 模板格式说明
+
+每个模板使用以下占位符格式:
+- `` - 时间序列操作符,如 `ts_rank`, `ts_mean`, `ts_delta`, `ts_ir`, `ts_stddev`, `ts_zscore`
+- `` - 分组操作符,如 `group_rank`, `group_neutralize`, `group_zscore`
+- `` - 向量操作符,如 `vec_avg`, `vec_sum`, `vec_max`, `vec_min`, `vec_stddev`
+- `` - 数据字段占位符
+- `` - 时间窗口参数,常用值: `{5, 22, 66, 126, 252, 504}`
+- `` - 分组字段,如 `industry`, `sector`, `subindustry`, `market`
+
+---
+
+## 第一部分:基础结构模板 (TPL-001 ~ TPL-010)
+
+### TPL-001: 基本面时序排名
+```
+模板: ((, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore`, `ts_delta`, `ts_ir` | 时序比较操作 |
+| `` | `group_rank`, `group_zscore`, `group_neutralize` | 截面比较操作 |
+| `` | 基本面字段: `eps`, `sales`, `assets`, `roe`, `roa` | 公司财务数据 |
+| `` | `66`, `126`, `252` | 季度/半年/年 |
+| `` | `industry`, `sector` | 行业分组 |
+
+**示例**:
+```
+group_rank(ts_rank(eps, 252), industry)
+group_zscore(ts_ir(sales, 126), sector)
+```
+
+---
+
+### TPL-002: 利润/规模比率模板
+```
+模板: (/, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore`, `ts_mean`, `ts_delta` | 时序操作 |
+| `` | `net_income`, `ebitda`, `operating_income`, `gross_profit` | 利润类字段 |
+| `` | `assets`, `cap`, `sales`, `equity` | 规模类字段 |
+| `` | `66`, `126`, `252` | 中长期窗口 |
+
+**示例**:
+```
+ts_rank(net_income/assets, 252)
+ts_zscore(ebitda/cap, 126)
+ts_rank(operating_income/cap, 252)^2
+```
+
+---
+
+### TPL-003: 向量数据处理模板 (VECTOR字段必用)
+```
+模板: ((), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_mean`, `ts_delta`, `ts_ir`, `ts_zscore` | 时序操作 |
+| `` | `vec_avg`, `vec_sum`, `vec_max`, `vec_min`, `vec_stddev` | 向量聚合 |
+| `` | 分析师数据: `anl4_*`, `analyst_*`, `oth41_*` | VECTOR类型字段 |
+| `` | `22`, `66`, `126` | 短中期窗口 |
+
+**示例**:
+```
+ts_delta(vec_avg(anl4_eps_mean), 22)
+ts_rank(vec_sum(analyst_estimate), 66)
+ts_ir(vec_avg(oth41_s_west_eps_ftm_chg_3m), 126)
+```
+
+---
+
+### TPL-004: 双重中性化模板
+```
+模板:
+a = (, );
+a1 = group_neutralize(a, bucket(rank(cap), range=""));
+group_neutralize(a1, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_zscore`, `ts_rank`, `ts_ir` | 时序操作 |
+| `` | 任意数据字段 | 主信号 |
+| `` | `66`, `126`, `252` | 时间窗口 |
+| `` | `"0.1,1,0.1"`, `"0,1,0.1"` | 市值分组范围 |
+| `` | `industry`, `sector`, `subindustry` | 行业分组 |
+
+**示例**:
+```
+a = ts_zscore(fnd72_s_pit_or_is_q_spe_si, 252);
+a1 = group_neutralize(a, bucket(rank(cap), range="0.1,1,0.1"));
+group_neutralize(a1, subindustry)
+```
+
+---
+
+### TPL-005: 回归中性化模板
+```
+模板:
+a = (, );
+a1 = group_neutralize(a, bucket(rank(cap), range=""));
+a2 = group_neutralize(a1, );
+b = ts_zscore(cap, );
+b1 = group_neutralize(b, );
+regression_neut(a2, b1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_zscore`, `ts_rank` | 时序操作 |
+| `` | 基本面或其他字段 | 主信号 |
+| `` | `252`, `504` | 长期窗口 |
+| `` | `"0.1,1,0.1"` | 市值分组 |
+| `` | `subindustry`, `sector` | 行业分组 |
+
+---
+
+### TPL-006: 基本面动量模板
+```
+模板: log(ts_mean(, )) - log(ts_mean(, ))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `anl4_{data}_{stats}`, 基本面字段 | 数据字段 |
+| `` | `20`, `44` | 短期窗口 |
+| `` | `44`, `126` | 长期窗口 |
+
+**示例**:
+```
+log(ts_mean(anl4_eps_mean, 44)) - log(ts_mean(anl4_eps_mean, 20))
+```
+
+---
+
+### TPL-007: 财报事件驱动模板
+```
+模板:
+event = ts_delta(, -1);
+if_else(event != 0, , nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `assets`, `sales`, `eps` | 基本面字段 |
+| `` | 主信号表达式 | 事件发生时的Alpha |
+
+**扩展版**:
+```
+change = if_else(days_from_last_change() == , ts_delta(close, ), nan)
+```
+
+---
+
+### TPL-008: 标准化回填模板
+```
+模板: (winsorize(ts_backfill(, ), std=), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_decay_linear`, `ts_zscore` | 时序操作 |
+| `` | 低频数据字段 | 需要回填的字段 |
+| `` | `115`, `120`, `180` | 回填窗口 |
+| `` | `4`, `3`, `5` | winsorize标准差 |
+| `` | `10`, `22`, `60` | 操作窗口 |
+
+**示例**:
+```
+ts_decay_linear(-densify(zscore(winsorize(ts_backfill(anl4_adjusted_netincome_ft, 115), std=4))), 10)
+ts_rank(winsorize(ts_backfill(, 120), std=4), 60)
+```
+
+---
+
+### TPL-009: 信号质量分组模板
+```
+模板:
+signal = (, );
+credit_quality = bucket(rank(ts_delay(signal, 1), rate=0), range="");
+group_neutralize((signal, k=), credit_quality)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore` | 信号计算 |
+| `` | 任意数据字段 | 主字段 |
+| `` | `60`, `120` | 窗口 |
+| `` | `"0.2,1,0.2"` | 分组范围 |
+| `` | `ts_weighted_decay` | 衰减操作 |
+| `` | `0.5`, `0.3` | 衰减系数 |
+
+---
+
+### TPL-010: 复合分组中性化
+```
+模板: group_neutralize(, densify()*1000 + densify())
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `subindustry`, `sector` | 主分组 |
+| `` | `country`, `exchange` | 次分组 |
+
+---
+
+## 第二部分:量价类模板 (TPL-101 ~ TPL-120)
+
+### TPL-101: 换手率反转
+```
+模板: -(volume/sharesout, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_mean`, `ts_rank`, `ts_std_dev` | 时序统计 |
+| `` | `5`, `22`, `66` | 短中期窗口 |
+
+**示例**:
+```
+-ts_mean(volume/sharesout, 22)
+-ts_std_dev(volume/sharesout, 22)
+```
+
+---
+
+### TPL-102: 量稳换手率 (STR)
+```
+模板: -ts_std_dev(volume/sharesout, )/ts_mean(volume/sharesout, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `20`, `22` | 波动计算窗口 |
+| `` | `20`, `22` | 均值计算窗口 |
+
+**优化版**:
+```
+模板: -group_neutralize(ts_std_dev(volume/sharesout, )/ts_mean(volume/sharesout, ), bucket(rank(cap), range="0.1,1,0.1"))
+```
+
+---
+
+### TPL-103: 价格反转模板
+```
+模板: -(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_delta`, `ts_mean`, `ts_rank` | 时序操作 |
+| `` | `close`, `returns`, `close/open-1`, `open/ts_delay(close,1)-1` | 价格/收益字段 |
+| `` | `3`, `5`, `22` | 短期窗口 |
+
+**示例**:
+```
+-ts_delta(close, 5) # 价格变化反转
+-ts_mean(returns, 22) # 收益均值反转
+-ts_mean(close/open-1, 22) # 日内收益反转
+-(open/ts_delay(close,1)-1) # 隔夜收益反转
+```
+
+---
+
+### TPL-104: 价格乖离率
+```
+模板: -(close - ts_mean(close, ))/ts_mean(close, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `5`, `22`, `66` | MA周期 |
+
+---
+
+### TPL-105: 量价相关性
+```
+模板: -ts_corr(, , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `close`, `returns`, `abs(returns)` | 价格类 |
+| `` | `volume`, `volume/sharesout`, `adv20` | 成交量类 |
+| `` | `22`, `66`, `126` | 相关性窗口 |
+
+---
+
+### TPL-106: 跳跃因子
+```
+模板: -group_neutralize(ts_mean((close/open-1) - log(close/open), ), bucket(rank(cap), range="0.1,1,0.1"))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `22`, `30`, `66` | 平均窗口 |
+
+**带成交量增强版**:
+```
+模板: -group_neutralize(ts_mean((close/open-1) - log(close/open), ) * ts_rank(volume, 5), bucket(rank(cap), range="0.1,1,0.1"))
+```
+
+---
+
+### TPL-107: 指数衰减动量
+```
+模板: -ts_decay_exp_window(, , factor=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns`, `returns*(volume/sharesout)`, `close/open-1` | 收益类字段 |
+| `` | `22`, `66`, `126` | 衰减窗口 |
+| `` | `0.04`, `0.1`, `0.5`, `0.9` | 衰减因子,越小衰减越快 |
+
+---
+
+### TPL-108: 成交量周期函数 (VOC)
+```
+模板:
+m_minus = ts_mean(volume, ) - ts_mean(volume, );
+delta = (ts_max(m_minus, ) - m_minus)/(ts_max(m_minus, ) - ts_min(m_minus, ));
+*delta + *ts_delay(delta, 1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `30`, `66` | 长期均值窗口 |
+| `` | `10`, `22` | 短期均值窗口 |
+| `` | `0.33`, `0.5` | 当日权重 |
+| `` | `0.67`, `0.5` | 前日权重 |
+
+---
+
+### TPL-109: 市场相关性因子
+```
+模板:
+mkt_ret = group_mean(returns, 1, market);
+pt = ts_corr(returns, mkt_ret, );
+rank(1/(2*(1-pt)))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `10`, `22`, `66` | 相关性窗口 |
+
+---
+
+### TPL-110: 成交量趋势模板
+```
+模板: ts_decay_linear(volume/ts_sum(volume, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `252`, `504` | 长期总量窗口 |
+| `` | `10`, `22` | 衰减窗口 |
+
+---
+
+### TPL-111: VWAP收益相关
+```
+模板:
+returns > - ? (ts_ir(ts_corr(ts_returns(vwap, 1), ts_delay(group_neutralize(, market), ), ), )) : -1
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `0.1`, `0.05` | 收益阈值 |
+| `` | 任意数据字段 | 信号字段 |
+| `` | `30`, `60` | 延迟窗口 |
+| `` | `90`, `120` | 相关性窗口 |
+
+---
+
+### TPL-112: 动量因子创建
+```
+模板: ts_sum(winsorize(ts_backfill(, ), std=4.0), *21) - ts_sum(winsorize(ts_backfill(, ), std=4.0), *21)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns`, 基本面字段 | 数据字段 |
+| `` | `120`, `180` | 回填窗口 |
+| `` | `6`, `12` | 长期月数 |
+| `` | `1`, `0.1*n` | 短期月数 |
+
+---
+
+### TPL-113: 线性衰减排名
+```
+模板: -ts_rank(ts_decay_linear(, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `percent`, 任意时序信号 | 输入信号 |
+| `` | `10`, `22`, `150` | 衰减窗口 |
+| `` | `50`, `126` | 排名窗口 |
+
+---
+
+## 第三部分:情绪/新闻类模板 (TPL-201 ~ TPL-220)
+
+### TPL-201: 情绪差值模板
+```
+模板: (rank(ts_backfill(, )) - rank(ts_backfill(, )), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_mean`, `ts_rank`, `ts_zscore` | 时序操作 |
+| `` | 正面情绪字段 | 积极信号 |
+| `` | 负面情绪字段 | 消极信号 |
+| `` | `20`, `30` | 回填窗口 |
+| `` | `5`, `22` | 比较窗口 |
+
+---
+
+### TPL-202: 新闻情绪回归残差
+```
+模板:
+sentiment = ts_backfill(ts_delay((), 1), );
+vhat = ts_regression(volume, sentiment, );
+ehat = -ts_regression(returns, vhat, );
+group_rank(ehat, bucket(rank(cap), range="0,1,0.1"))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `vec_avg`, `vec_sum` | 情绪聚合方式 |
+| `` | `scl12_sentiment`, `snt_buzz_ret`, `nws18_relevance` | 情绪数据 |
+| `` | `20`, `30` | 回填窗口 |
+| `` | `120`, `250` | 成交量回归窗口 |
+| `` | `250`, `750` | 收益回归窗口 |
+
+---
+
+### TPL-203: 社交媒体情绪
+```
+模板: rank((scl12_alltype_buzzvec) * (scl12_sentiment))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `vec_sum`, `vec_avg` | 向量聚合 |
+
+**带条件版**:
+```
+模板:
+sent_vol = vec_sum(scl12_alltype_buzzvec);
+trade_when(rank(sent_vol) > 0.95, -zscore(scl12_buzz)*sent_vol, -1)
+```
+
+---
+
+### TPL-204: 条件情绪过滤
+```
+模板:
+group_rank(
+sigmoid(if_else(ts_zscore(, ) > , ts_zscore(, ), 0)),
+
+)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 情绪字段 | 情绪数据 |
+| `` | `22`, `30`, `66` | zscore窗口 |
+| `` | `1`, `1.5`, `2` | z-score阈值 |
+| `` | `industry`, `sector` | 分组字段 |
+
+---
+
+### TPL-205: 情绪+波动率复合
+```
+模板: log(1 + sigmoid(ts_zscore(, )) * sigmoid(ts_zscore(, )))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 情绪字段 | 情绪数据 |
+| `` | `option8_*`, 波动率字段 | 波动率数据 |
+| `` | `30`, `66` | 情绪窗口 |
+| `` | `30`, `66` | 波动率窗口 |
+
+---
+
+### TPL-206: 指数衰减情绪
+```
+模板: ts_decay_exp_window(vec_avg(), , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `mws85_sentiment`, `nws18_ber` | 情绪向量字段 |
+| `` | `10`, `22` | 衰减窗口 |
+| `` | `0.9`, `0.7` | 衰减因子 |
+
+**双情绪组合**:
+```
+decayed_sentiment_1 = ts_decay_exp_window(vec_avg(mws85_sentiment), 10, 0.9);
+decayed_sentiment_2 = ts_decay_exp_window(vec_avg(nws18_ber), 10, 0.9);
+decayed_sentiment_1 + decayed_sentiment_2
+```
+
+---
+
+### TPL-207: 新闻结果排名
+```
+模板:
+percent = ts_rank(vec_stddev(), );
+-ts_rank(ts_decay_linear(percent, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `nws12_prez_result2` | 新闻数据 |
+| `` | `50`, `66` | 排名窗口 |
+| `` | `150`, `252` | 衰减窗口 |
+
+---
+
+### TPL-208: 分组行业提取情绪
+```
+模板: scale(group_extra(ts_sum(sigmoid(ts_backfill(, )), ) - ts_sum(sigmoid(ts_backfill(, )), ), 0.5, densify(industry)))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 情绪或基本面字段 | 数据字段 |
+| `` | `180`, `252` | 回填窗口 |
+| `` | `3`, `5` | 求和窗口 |
+
+---
+
+## 第四部分:期权类模板 (TPL-301 ~ TPL-320)
+
+### TPL-301: 期权希腊字母差值
+```
+模板: ( - , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `group_rank`, `group_neutralize`, `group_zscore` | 分组操作 |
+| `` | `put_delta`, `put_gamma`, `put_theta`, `put_vega` | Put希腊字母 |
+| `` | `call_delta`, `call_gamma`, `call_theta`, `call_vega` | Call希腊字母 |
+| `` | `industry`, `sector` | 分组字段 |
+
+---
+
+### TPL-302: 期权价格信号
+```
+模板: group_rank((()/close, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_scale`, `ts_rank`, `ts_zscore` | 时序操作 |
+| `` | `vec_max`, `vec_avg` | 向量操作 |
+| `` | 期权价格字段 | 期权数据 |
+| `` | `66`, `120`, `252` | 时间窗口 |
+| `` | `industry`, `sector` | 分组字段 |
+
+---
+
+### TPL-303: 期权波动率信号
+```
+模板: sigmoid(( - , ))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_ir`, `ts_stddev`, `ts_zscore`, `ts_mean` | 波动性操作 |
+| `` | 期权高价字段 | 期权最高价 |
+| `` | 期权收盘价字段 | 期权收盘价 |
+| `` | `120`, `250`, `504` | 长期窗口 |
+
+**说明**: 期权波动类因子通常需要较长窗口(120-504天)来捕捉稳定信号
+
+---
+
+### TPL-304: 隐含波动率比率
+```
+模板: (implied_volatility_call_/parkinson_volatility_, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore`, `ts_delta` | 时序操作 |
+| `` | `120`, `270` | 期权期限 |
+| `` | `66`, `126`, `252` | 窗口 |
+
+---
+
+### TPL-305: Put-Call成交量比
+```
+模板: (pcr_vol_, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_delta`, `ts_zscore` | 时序操作 |
+| `` | `10`, `30`, `60` | 期限 |
+| `` | `22`, `66`, `126` | 窗口 |
+
+---
+
+### TPL-306: 期权盈亏平衡点
+```
+模板: group_rank(ts_zscore(/close, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `call_breakeven_10`, `put_breakeven_10` | 盈亏平衡字段 |
+| `` | `66`, `126`, `252` | 窗口 |
+| `` | `sector`, `industry` | 分组 |
+
+---
+
+## 第五部分:分析师类模板 (TPL-401 ~ TPL-420)
+
+### TPL-401: 分析师预期变化
+```
+模板: (tail(tail(, lower=, upper=, newval=), lower=-, upper=-, newval=-))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `vec_avg`, `vec_sum` | 向量聚合 |
+| `` | `oth41_s_west_eps_ftm_chg_3m`, `anl4_eps_chg` | 预期变化字段 |
+| `` | `0.25`, `0.1` | 下截断值 |
+| `` | `1000`, `100` | 上截断值 |
+
+---
+
+### TPL-402: 剥离动量的分析师因子
+```
+模板:
+afr = ();
+short_mom = ts_mean(returns - group_mean(returns, 1, market), );
+long_mom = ts_delay(ts_mean(returns - group_mean(returns, 1, market), ), );
+regression_neut(regression_neut(afr, short_mom), long_mom)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `vec_avg`, `vec_sum` | 向量聚合 |
+| `` | 分析师数据字段 | 一致预期等 |
+| `` | `5`, `10` | 短期动量窗口 |
+| `` | `20`, `22` | 长期动量窗口 |
+
+---
+
+### TPL-403: 分析师覆盖度过滤
+```
+模板:
+coverage_filter = ts_sum((), ) > ;
+if_else(coverage_filter, , nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `vec_count` | 统计分析师数量 |
+| `` | 分析师向量字段 | 分析师数据 |
+| `` | `66`, `90`, `126` | 统计窗口 |
+| `` | `2`, `3`, `5` | 最小覆盖数量 |
+| `` | 主信号表达式 | 待过滤的Alpha |
+
+---
+
+### TPL-404: 老虎哥回归模板
+```
+模板: group_rank(ts_regression(ts_zscore(, ), ts_zscore(vec_sum(), ), ), densify(sector))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意MATRIX字段 | Y变量 |
+| `` | 任意VECTOR字段 | X变量 |
+| `` | `252`, `504` | 回归窗口 |
+
+**说明**: 经典回归模板,适用于基本面与分析师数据组合
+
+---
+
+### TPL-405: 分析师预期时序变化
+```
+模板: ts_mean(vec_avg(), ) - ts_mean(vec_avg(), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `anl4_eps_mean`, `anl4_revenue_mean` | 分析师预测 |
+| `` | `22`, `44` | 短期窗口 |
+| `` | `66`, `126` | 长期窗口 |
+
+---
+
+### TPL-406: 三因子组合模板
+```
+模板:
+my_group = market;
+rank(
+group_rank(ts_decay_linear(volume/ts_sum(volume, 252), 10), my_group) *
+group_rank(ts_rank(vec_avg(), ), my_group) *
+group_rank(-ts_delta(close, 5), my_group)
+)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 基本面VECTOR字段 | 基本面数据 |
+| `` | `252`, `504` | 排名窗口 |
+
+---
+
+### TPL-407: 分析师FCF比率
+```
+模板: ts_rank(vec_avg() / vec_avg(), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `anl4_fcf_value` | 自由现金流预测 |
+| `` | `anl4_netprofit_low`, `anl4_netprofit_mean` | 利润预测 |
+| `` | `66`, `126`, `252` | 排名窗口 |
+
+---
+
+## 第六部分:中性化技术模板 (TPL-501 ~ TPL-515)
+
+### TPL-501: 市值分组中性化
+```
+模板: group_neutralize(, bucket(rank(cap), range=""))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号表达式 | 待中性化的Alpha |
+| `` | `"0.1,1,0.1"`, `"0,1,0.1"` | 分组范围 |
+
+---
+
+### TPL-502: 双重中性化 (行业+市值)
+```
+模板:
+a1 = group_neutralize(, bucket(rank(cap), range=""));
+group_neutralize(a1, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `"0.1,1,0.1"` | 市值分组 |
+| `` | `industry`, `sector`, `subindustry` | 行业分组 |
+
+---
+
+### TPL-503: 回归中性化
+```
+模板: regression_neut(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `log(cap)`, `ts_ir(returns, 126)`, `ts_std_dev(returns, 22)` | 待剥离因子 |
+
+**多层回归中性化**:
+```
+模板: regression_neut(regression_neut(, ), )
+```
+
+---
+
+### TPL-504: 中性化顺序优化
+```
+模板:
+a = ts_zscore(, );
+a1 = group_neutralize(a, );
+a2 = group_neutralize(a1, bucket(rank(cap), range=""))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意数据字段 | 主信号 |
+| `` | `252` | zscore窗口 |
+| `` | `industry`, `subindustry` | 行业分组 |
+| `` | `"0.1,1,0.1"` | 市值分组 |
+
+**说明**: 先行业中性化再市值中性化,与反向顺序效果可能不同
+
+---
+
+### TPL-505: sta1分组中性化
+```
+模板: group_neutralize(, sta1_top3000c20)
+```
+**说明**: 使用预定义的sta1分组进行中性化
+
+---
+
+## 第七部分:条件交易模板 (TPL-601 ~ TPL-620)
+
+### TPL-601: 流动性过滤
+```
+模板: trade_when(volume > adv20 * , , -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `0.618`, `0.5`, `1` | 流动性阈值 |
+| `` | 主信号 | 原始Alpha |
+
+**反向流动性**:
+```
+trade_when(volume < adv20, , -1)
+```
+
+---
+
+### TPL-602: 波动率过滤
+```
+模板: trade_when(ts_rank(ts_std_dev(returns, ), ) < , , -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `5`, `10`, `22` | 波动计算窗口 |
+| `` | `126`, `180`, `252` | 排名窗口 |
+| `` | `0.8`, `0.9` | 波动率阈值 |
+| `` | 主信号 | 原始Alpha |
+
+---
+
+### TPL-603: 极端收益过滤
+```
+模板: trade_when(abs(returns) < , , abs(returns) > )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `0.075`, `0.05` | 入场阈值 |
+| `` | `0.1`, `0.095` | 出场阈值 |
+| `` | 主信号 | 原始Alpha |
+
+---
+
+### TPL-604: 市值过滤
+```
+模板: trade_when(rank(cap) > , , -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `0.3`, `0.5` | 市值排名阈值 |
+| `` | 主信号 | 原始Alpha |
+
+---
+
+### TPL-605: 触发条件交易
+```
+模板:
+triggerTradeexp = (ts_arg_max(volume, ) < 1) && (volume > ts_sum(volume, )/);
+triggerExitexp = -1;
+trade_when(triggerTradeexp, , triggerExitexp)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `5`, `10` | 判断窗口 |
+| `` | `-rank(ts_delta(close, 2))` | 主信号 |
+
+---
+
+### TPL-606: 组合条件交易
+```
+模板:
+my_group2 = bucket(rank(cap), range="0,1,0.1");
+trade_when(volume > adv20, group_neutralize(, my_group2), -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 复合信号 | 主信号 |
+
+---
+
+### TPL-607: 条件排名交易
+```
+模板:
+a = (, );
+trade_when(rank(a) > , -zscore()*a, -rank(a))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore` | 时序操作 |
+| `` | 任意字段 | 条件字段 |
+| `` | 任意字段 | 信号字段 |
+| `` | `25`, `66` | 窗口 |
+| `` | `0.03`, `0.1` | 下阈值 |
+| `` | `0.25`, `0.5` | 上阈值 |
+
+---
+
+## 第八部分:复合多因子模板 (TPL-701 ~ TPL-720)
+
+### TPL-701: 三因子乘积
+```
+模板:
+my_group = market;
+rank(
+group_rank((, ), my_group) *
+group_rank((, ), my_group) *
+group_rank((, ), my_group)
+)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_decay_linear`, `ts_rank` | 第一因子操作 |
+| `` | `ts_rank`, `ts_zscore` | 第二因子操作 |
+| `` | `-ts_delta` | 第三因子操作(反转) |
+| `` | `volume/ts_sum(volume, 252)` | 成交量趋势 |
+| `` | `vec_avg({Fundamental})` | 基本面信号 |
+| `` | `close` | 价格信号 |
+| ``, ``, `` | 各因子窗口 | 时间参数 |
+
+---
+
+### TPL-702: 波动率条件反转
+```
+模板:
+vol = ts_std_dev(, );
+vol_mean = group_mean(vol, 1, market);
+flip_ret = if_else(vol < vol_mean, -, );
+-ts_mean(flip_ret, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns`, `close/open-1` | 收益字段 |
+| `` | `20`, `22` | 窗口参数 |
+
+**说明**: 低波动环境做反转,高波动环境做动量
+
+---
+
+### TPL-703: 恐惧指标组合
+```
+模板:
+fear = ts_mean(
+abs(returns - group_mean(returns, 1, market)) /
+(abs(returns) + abs(group_mean(returns, 1, market)) + 0.1),
+
+);
+-group_neutralize(fear * , bucket(rank(cap), range="0.1,1,0.1"))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `20`, `22` | 恐惧指标窗口 |
+| `` | 主信号表达式 | 待组合信号 |
+
+---
+
+### TPL-704: 债务杠杆相关性
+```
+模板: group_neutralize(ts_zscore(, ) * ts_corr(, returns, ), sector)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `debt_to_equity`, `debt/assets` | 杠杆字段 |
+| `` | `60`, `126` | zscore窗口 |
+| `` | `20`, `66` | 相关性窗口 |
+
+---
+
+### TPL-705: 模型数据信号
+```
+模板: -
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `mdl175_01dtsv`, `mdl175_01icc` | 模型字段 |
+
+**带排名版**:
+```
+rank(group_rank(ts_rank(ts_backfill(, 5), 5), sta1_top3000c20))
+```
+
+---
+
+### TPL-706: 回归zscore模板
+```
+模板: ts_regression(ts_zscore(, ), ts_zscore(, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | MATRIX字段 | Y变量 |
+| `` | MATRIX字段或vec_sum(VECTOR) | X变量 |
+| `` | `252`, `500`, `504` | 回归窗口 |
+
+---
+
+### TPL-707: 分组Delta模板
+```
+模板: group_neutralize(ts_delta(, ), sector)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意数据字段 | 主字段 |
+| `` | `22`, `66`, `126` | 差分窗口 |
+
+---
+
+## 第九部分:数据预处理模板 (TPL-801 ~ TPL-815)
+
+### TPL-801: Winsorize截断
+```
+模板: winsorize(, std=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 原始数据 |
+| `` | `3`, `4`, `5` | 截断标准差 |
+
+---
+
+### TPL-802: Sigmoid归一化
+```
+模板: sigmoid((, ))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_zscore`, `ts_ir`, `ts_rank` | 时序操作 |
+| `` | 任意字段 | 原始数据 |
+| `` | `22`, `66`, `252` | 窗口 |
+
+---
+
+### TPL-803: 数据回填
+```
+模板: ts_backfill(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 低频数据字段 | 需要回填的字段 |
+| `` | `115`, `120`, `180`, `252` | 回填窗口 |
+
+---
+
+### TPL-804: 条件替换
+```
+模板: if_else(is_not_nan(), , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主字段 | 可能有NaN的字段 |
+| `` | 替代字段或值 | NaN时的替代 |
+
+---
+
+### TPL-805: 极端值替换
+```
+模板: tail(tail(, lower=, upper=, newval=), lower=-, upper=-, newval=-)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 原始数据 |
+| `` | `0.25`, `0.1` | 下界 |
+| `` | `100`, `1000` | 上界 |
+
+---
+
+### TPL-806: 组合预处理
+```
+模板: (winsorize(ts_backfill(, ), std=), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore`, `ts_mean` | 时序操作 |
+| `` | 低频字段 | 需要处理的字段 |
+| `` | `120`, `180` | 回填窗口 |
+| `` | `4` | winsorize参数 |
+| `` | `22`, `66` | 操作窗口 |
+
+---
+
+### TPL-807: ts_min/ts_max替代
+```
+模板: ts_backfill(if_else(ts_arg_min(, ) == 0, , nan), 120)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 原始数据 |
+| `` | `22`, `66`, `126` | 窗口 |
+
+**说明**: 当ts_min/ts_max不可用时的替代方案
+
+---
+
+## 第十部分:高级统计模板 (TPL-901 ~ TPL-920)
+
+### TPL-901: 高阶矩模板 (ts_moment)
+```
+模板: ((ts_moment(, , k=), ))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `rank`, `zscore`, `sigmoid` | 标准化操作 |
+| `` | `group_rank`, `group_zscore` | 分组操作 |
+| `` | 任意MATRIX字段 | 数据字段 |
+| `` | `22`, `66`, `126` | 窗口 |
+| `` | `2`, `3`, `4` | k=2方差, k=3偏度, k=4峰度 |
+
+**说明**: ts_moment(x, d, k)计算k阶中心矩
+
+---
+
+### TPL-902: 协偏度/协峰度模板
+```
+模板: (ts_co_skewness(, , ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `group_rank`, `group_zscore` | 分组操作 |
+| `` | `returns`, `close` | 第一变量 |
+| `` | `volume`, `vwap` | 第二变量 |
+| `` | `66`, `126`, `252` | 窗口 |
+
+**协峰度版**:
+```
+模板: (ts_co_kurtosis(, , ), )
+```
+
+---
+
+### TPL-903: 偏相关模板 (ts_partial_corr)
+```
+模板: group_rank(ts_partial_corr(, , , ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns`, 收益相关 | Y变量 |
+| `` | 任意字段 | X变量 |
+| `` | `group_mean(returns, 1, market)` | 控制变量(市场收益) |
+| `` | `60`, `126`, `252` | 窗口 |
+| `` | `sector`, `industry` | 分组 |
+
+**说明**: 计算两变量偏相关,控制第三变量影响
+
+---
+
+### TPL-904: 三元相关模板 (ts_triple_corr)
+```
+模板: group_rank(ts_triple_corr(, , , ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns` | 第一变量 |
+| `` | `volume` | 第二变量 |
+| `` | 基本面字段 | 第三变量 |
+| `` | `60`, `126` | 窗口 |
+| `` | `sector`, `industry` | 分组 |
+
+---
+
+### TPL-905: Theil-Sen回归模板
+```
+模板: group_rank(ts_theilsen(, , ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意MATRIX字段 | Y变量 |
+| `` | 任意MATRIX字段或`ts_step(1)` | X变量 |
+| `` | `126`, `252`, `500` | 窗口 |
+| `` | `sector`, `industry` | 分组 |
+
+**说明**: Theil-Sen回归比普通回归更鲁棒
+
+---
+
+### TPL-906: 多项式回归残差
+```
+模板: ts_poly_regression(, , , k=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | Y变量 | 被解释变量 |
+| `` | X变量 | 解释变量 |
+| `` | `126`, `252` | 窗口 |
+| `` | `1`, `2`, `3` | 多项式阶数, k=2为二次回归 |
+
+**说明**: 返回 y - Ey (残差)
+
+---
+
+### TPL-907: 向量中性化模板
+```
+模板: ts_vector_neut(, , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 待中性化Alpha |
+| `` | `returns`, `cap` | 风险因子 |
+| `` | `22`, `66`, `126` | 窗口(不宜过长,计算慢) |
+
+**分组向量中性化**:
+```
+模板: group_vector_neut(, , )
+```
+
+---
+
+### TPL-908: 加权衰减模板
+```
+模板: group_neutralize(ts_weighted_decay(, k=), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 待衰减Alpha |
+| `` | `0.3`, `0.5`, `0.7` | 衰减系数 |
+| `` | `bucket(rank(cap), range="0.1,1,0.1")` | 分组 |
+
+---
+
+### TPL-909: 回归斜率模板
+```
+模板: ts_regression(ts_zscore(, ), ts_step(1), , rettype=2)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意MATRIX字段 | 数据字段 |
+| `` | `252`, `500` | 窗口 |
+
+**说明**: rettype=2返回斜率,用于检测趋势
+
+---
+
+### TPL-910: 最小最大压缩模板
+```
+模板: ts_min_max_cps(, , f=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `22`, `66`, `126` | 窗口 |
+| `` | `2`, `0.5` | 压缩因子 |
+
+**等价公式**: `x - f * (ts_min(x, d) + ts_max(x, d))`
+
+---
+
+## 第十一部分:事件驱动模板 (TPL-1001 ~ TPL-1020)
+
+### TPL-1001: 数据变化天数模板
+```
+模板: if_else(days_from_last_change() == , , nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 基本面字段 | 监测变化的字段 |
+| `` | `1`, `2`, `5` | 距离变化的天数 |
+| `` | `ts_delta(close, 5)`, 主信号 | 事件触发时的Alpha |
+
+**动态衰减版**:
+```
+模板: / (1 + days_from_last_change())
+```
+
+---
+
+### TPL-1002: 最近差值模板
+```
+模板: (last_diff_value(, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore` | 时序操作 |
+| `` | 任意字段 | 数据字段 |
+| `` | `60`, `90`, `120` | 回溯窗口 |
+| `` | `22`, `66` | 操作窗口 |
+
+**说明**: 返回过去d天内最近一次不同于当前值的历史值
+
+---
+
+### TPL-1003: 缺失值计数模板
+```
+模板: -ts_count_nans(ts_backfill(, ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 分析师数据等 | 可能有缺失的字段 |
+| `` | `5`, `10` | 回填窗口 |
+| `` | `20`, `30` | 计数窗口 |
+
+**应用**: 分析师覆盖度信号,缺失越少覆盖越好
+
+---
+
+### TPL-1004: 位置最大/最小模板
+```
+模板: if_else(ts_arg_max(, ) == , , nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `volume`, 任意字段 | 监测字段 |
+| `` | `5`, `10` | 窗口 |
+| `` | `0`, `1` | 0表示今天是最大值 |
+| `` | 主信号 | 条件满足时的Alpha |
+
+**组合条件**:
+```
+模板: (ts_arg_max(, ) == ts_arg_max(, )) * ( + )
+```
+
+---
+
+### TPL-1005: 财报发布事件模板
+```
+模板:
+event_signal = if_else(ts_delta(, 1) != 0, , nan);
+ts_decay_linear(event_signal, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `assets`, `sales`, `eps` | 基本面字段 |
+| `` | `ts_delta(close, 5)`, 主信号 | 事件Alpha |
+| `` | `10`, `22` | 衰减窗口 |
+
+---
+
+### TPL-1006: 动态Decay事件驱动
+```
+模板:
+decay_weight = 1 / (1 + days_from_last_change());
+ * decay_weight
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 事件触发字段 |
+| `` | 主信号 | 原始Alpha |
+
+---
+
+### TPL-1007: 盈利公告模板
+```
+模板:
+surprise = - ;
+if_else(days_from_last_change() < , surprise, nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `eps` | 实际值 |
+| `` | `vec_avg(anl4_eps_mean)` | 预测值 |
+| `` | `5`, `10` | 事件有效窗口 |
+
+---
+
+## 第十二部分:信号处理模板 (TPL-1101 ~ TPL-1120)
+
+### TPL-1101: 黄金比例幂变换
+```
+模板: signed_power(, 0.618)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号表达式 | 原始Alpha |
+
+**其他幂次**:
+```
+signed_power(, 0.5) # 平方根
+signed_power(, 2) # 平方增强
+```
+
+---
+
+### TPL-1102: 尾部截断模板
+```
+模板: right_tail(, minimum=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0`, `0.1` | 最小阈值 |
+
+**左尾版**:
+```
+模板: left_tail(, maximum=)
+```
+
+---
+
+### TPL-1103: Clamp边界限制
+```
+模板: clamp(, lower=, upper=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `-1`, `-0.5` | 下界 |
+| `` | `1`, `0.5` | 上界 |
+
+---
+
+### TPL-1104: 分数映射模板
+```
+模板: fraction()
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+
+**说明**: 将连续变量映射到分布内的相对位置
+
+---
+
+### TPL-1105: NaN外推模板
+```
+模板: nan_out(, lower=, upper=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `-3`, `-5` | 下界 |
+| `` | `3`, `5` | 上界 |
+
+**说明**: 将超出范围的值替换为NaN
+
+---
+
+### TPL-1106: Purify数据清洗
+```
+模板: purify()
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 需要清洗的数据 |
+
+**说明**: 自动化数据清洗,减少噪声和异常值
+
+---
+
+### TPL-1107: 条件保留模板
+```
+模板: keep(, , period=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | ` > 0` | 保留条件 |
+| `` | `3`, `5`, `10` | 滚动窗口 |
+
+**示例**:
+```
+keep(returns, returns > 0, period=3) # 只保留正收益
+```
+
+---
+
+### TPL-1108: 缩放降维模板
+```
+模板: -scale_down((, ), constant=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_mean`, `ts_rank` | 时序操作 |
+| `` | `returns`, 任意字段 | 数据字段 |
+| `` | `2`, `5` | 窗口 |
+| `` | `0.1`, `0.05` | 缩放常数 |
+
+---
+
+### TPL-1109: Truncate截断模板
+```
+模板: truncate(, maxPercent=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0.01`, `0.05` | 截断百分比 |
+
+---
+
+### TPL-1110: 组合Normalize模板
+```
+模板: group_normalize(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `sector`, `industry` | 分组 |
+
+**等价公式**: `alpha / group_sum(abs(alpha), group)`
+
+---
+
+## 第十三部分:Turnover控制模板 (TPL-1201 ~ TPL-1215)
+
+### TPL-1201: 目标换手率Hump
+```
+模板: ts_target_tvr_hump(, lambda_min=0, lambda_max=1, target_tvr=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0.1`, `0.15`, `0.2` | 目标换手率 |
+
+---
+
+### TPL-1202: Delta限制换手率
+```
+模板: ts_target_tvr_delta_limit(, , lambda_min=0, lambda_max=1, target_tvr=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | 辅助因子 | 限制因子 |
+| `` | `0.1`, `0.15` | 目标换手率 |
+
+---
+
+### TPL-1203: Hump衰减组合
+```
+模板: hump_decay(, hump=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0.001`, `0.01` | Hump参数 |
+
+**嵌套版**:
+```
+hump(hump_decay(, hump=0.001))
+```
+
+---
+
+### TPL-1204: 平均+Hump模板
+```
+模板: -ts_mean(ts_target_tvr_hump(group_rank(, country), lambda_min=0, lambda_max=1, target_tvr=), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `0.1` | 目标换手率 |
+| `` | `5`, `10` | 平均窗口 |
+
+---
+
+### TPL-1205: 简单Hump模板
+```
+模板: hump(, hump=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0.01`, `0.001`, `0.0001` | Hump参数 |
+
+**示例**:
+```
+hump(-ts_delta(close, 5), hump=0.01)
+```
+
+---
+
+## 第十四部分:回填与覆盖模板 (TPL-1301 ~ TPL-1315)
+
+### TPL-1301: 分组回填模板
+```
+模板: group_backfill(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 需要回填的字段 |
+| `` | `sector`, `industry`, `market` | 分组字段 |
+
+**说明**: 使用组内最近值填充NaN
+
+---
+
+### TPL-1302: 嵌套回填排名
+```
+模板: rank(group_backfill(, ))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `sector`, `industry` | 分组 |
+
+---
+
+### TPL-1303: 覆盖度过滤
+```
+模板: group_count(is_nan(), market) > ? : nan
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 检测字段 |
+| `` | `40`, `50` | 最小覆盖数 |
+| `` | 主信号 | 原始Alpha |
+
+---
+
+### TPL-1304: NaN替换模板
+```
+模板: if_else(is_not_nan(), , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `0`, `0.5`, `nan` | 默认值 |
+
+---
+
+### TPL-1305: 综合数据清洗
+```
+模板: (winsorize(group_backfill(ts_backfill(, ), ), std=), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `ts_rank`, `ts_zscore` | 时序操作 |
+| `` | 低频字段 | 数据字段 |
+| `` | `120`, `180` | 时序回填窗口 |
+| `` | `sector`, `industry` | 分组回填 |
+| `` | `4` | winsorize参数 |
+| `` | `66`, `126` | 操作窗口 |
+
+---
+
+## 第十五部分:组合提取模板 (TPL-1401 ~ TPL-1415)
+
+### TPL-1401: group_extra填补模板
+```
+模板: group_extra(, , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `0.5`, `1` | 权重 |
+| `` | `densify(industry)`, `sector` | 分组 |
+
+**说明**: 用组均值填补缺失值
+
+---
+
+### TPL-1402: 组合提取sigmoid
+```
+模板: scale(group_extra(ts_sum(sigmoid(ts_backfill(, )), ) - ts_sum(sigmoid(ts_backfill(, )), ), 0.5, densify(industry)))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `180` | 回填窗口 |
+| `` | `3` | 求和窗口 |
+
+---
+
+### TPL-1403: PnL反馈模板
+```
+模板: if_else(inst_pnl() > , , nan)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+| `` | `0`, `-0.05` | PnL阈值 |
+
+**说明**: 基于单标的PnL进行条件交易
+
+---
+
+### TPL-1404: 流动性加权模板
+```
+模板: * log(volume)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+
+**说明**: 将仓位偏向高流动性股票
+
+---
+
+### TPL-1405: 市值回归中性化
+```
+模板: regression_neut(, log(cap))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 主信号 | 原始Alpha |
+
+**说明**: 剥离市值因子影响
+
+---
+
+## 第十六部分:百分位与分位数模板 (TPL-1501 ~ TPL-1510)
+
+### TPL-1501: 时序百分位模板
+```
+模板: ts_percentage(, , percentage=)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `22`, `66`, `126` | 窗口 |
+| `` | `0.5`, `0.25`, `0.75` | 百分位 |
+
+---
+
+### TPL-1502: 分位数模板
+```
+模板: (ts_quantile(, , ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `rank`, `zscore` | 标准化 |
+| `` | 任意字段 | 数据字段 |
+| `` | `66`, `126` | 窗口 |
+| `` | `0.25`, `0.5`, `0.75` | 分位数 |
+| `` | `22` | 操作窗口 |
+
+---
+
+### TPL-1503: Max-Min比率模板
+```
+模板: ts_max_diff(, ) / ts_av_diff(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `22`, `66` | 窗口 |
+
+---
+
+### TPL-1504: 中位数模板
+```
+模板: - ts_median(, )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | 任意字段 | 数据字段 |
+| `` | `22`, `66`, `252` | 窗口 |
+
+---
+
+### TPL-1505: 累积乘积模板
+```
+模板: ts_product(1 + , )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `returns`, 收益率字段 | 收益字段 |
+| `` | `5`, `22`, `66` | 窗口 |
+
+**说明**: 计算累积收益
+
+---
+
+## 第十七部分:实战表达式模板 (TPL-1601 ~ TPL-1700)
+
+**说明**: 以下模板从社区高票帖子中提取,为实际验证过的表达式格式。
+
+### TPL-1601: ts_max/ts_min替代公式
+```
+模板: {data} - ts_max_diff({data}, {d}) # 等效于 ts_max
+模板: (({data} - ts_max_diff({data}, {d})) * ts_scale({data}, {d}) - {data}) / (ts_scale({data}, {d}) - 1) # 等效于 ts_min
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{data}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `22`, `66`, `126` | 窗口 |
+
+**应用**: 当平台不支持ts_max/ts_min时的替代方案
+
+---
+
+### TPL-1602: 线性衰减权重公式
+```
+模板: weight = {d} + ts_step(0); ts_sum({data} * weight, {d}) / ts_sum(weight, {d}) # 等效于 ts_decay_linear
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{data}` | 任意字段 | 数据字段 |
+| `{d}` | `10`, `22`, `66` | 衰减窗口 |
+
+---
+
+### TPL-1603: 组归一化公式
+```
+模板: {data} / group_sum(abs({data}), {group}) # 等效于 group_normalize
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{data}` | 任意字段 | 数据字段 |
+| `{group}` | `industry`, `sector` | 分组字段 |
+
+---
+
+### TPL-1604: IR+峰度组合模板
+```
+模板:
+rank_data = rank({field});
+ts_ir(rank_data, {d}) + ts_kurtosis(rank_data, {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | `volume`, `returns`, 任意字段 | 数据字段 |
+| `{d}` | `22`, `66` | 窗口 |
+
+**说明**: IR和峰度组合捕捉信号强度和分布特征
+
+---
+
+### TPL-1605: VWAP相关性信号
+```
+模板: returns > -{threshold} ? (ts_ir(ts_corr(ts_returns(vwap, 1), ts_delay(group_neutralize({field}, market), {d1}), {d2}), {d2})) : -1
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意数据字段 | 信号字段 |
+| `{threshold}` | `0.1`, `0.05` | 收益过滤阈值 |
+| `{d1}` | `30`, `60` | 延迟窗口 |
+| `{d2}` | `90`, `120` | 相关性窗口 |
+
+---
+
+### TPL-1606: 球队硬币因子 (ballteam_coin)
+```
+模板:
+# 基础版
+rank(ballteam_coin)
+
+# 市值中性化版
+group_neutralize(rank(ballteam_coin), bucket(rank(assets), range='0.1,1,0.1'))
+```
+**说明**: 经典球队vs硬币因子,用于捕捉收益持续性
+
+---
+
+### TPL-1607: 偏度因子模板
+```
+模板: -group_rank(ts_skewness(returns, {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `22`, `66`, `126` | 偏度计算窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 负偏度股票往往表现更好
+
+---
+
+### TPL-1608: 熵信号模板
+```
+模板: ts_zscore({field}, {d1}) * ts_entropy({field}, {d2})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | `returns`, 任意字段 | 信号字段 |
+| `{d1}` | `14`, `22` | zscore窗口 |
+| `{d2}` | `14`, `22` | 熵窗口 |
+
+**说明**: 结合标准化和不确定性度量
+
+---
+
+### TPL-1609: 分析师动量短长差模板
+```
+模板: log(ts_mean(anl4_{data}_{stats}, {d_short})) - log(ts_mean(anl4_{data}_{stats}, {d_long}))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{data}` | `eps`, `revenue`, `netprofit` | 分析师预测类型 |
+| `{stats}` | `mean`, `low`, `high` | 统计量类型 |
+| `{d_short}` | `20`, `44` | 短期窗口 |
+| `{d_long}` | `44`, `126` | 长期窗口 |
+
+---
+
+### TPL-1610: 目标换手率分组排名
+```
+模板: -ts_mean(ts_target_tvr_hump(group_rank({field}, country), lambda_min=0, lambda_max=1, target_tvr={target}), {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意字段 | 数据字段 |
+| `{target}` | `0.1`, `0.15` | 目标换手率 |
+| `{d}` | `5`, `10` | 平均窗口 |
+
+---
+
+### TPL-1611: 最大差/均值差比率
+```
+模板: ts_max_diff({field}, {d}) / ts_av_diff({field}, {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意字段 | 数据字段 |
+| `{d}` | `22`, `66` | 窗口 |
+
+**说明**: 捕捉极端值相对于平均变化的幅度
+
+---
+
+### TPL-1612: 模型数据三层嵌套
+```
+模板:
+a = rank(group_rank(ts_rank(ts_backfill({model_field}, 5), 5), sta1_top3000c20));
+trade_when(rank(a) > 0.03, -zscore(ts_zscore({model_field}, 25)) * a, 0.25 - rank(a))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{model_field}` | `mdl175_01icc`, `mdl175_01dtsv` | 模型字段 |
+
+---
+
+### TPL-1613: 量价触发条件交易
+```
+模板:
+triggerTradeexp = (ts_arg_max(volume, {d}) < 1) && (volume > ts_sum(volume, {d}) / {d});
+triggerExitexp = -1;
+alphaexp = -rank(ts_delta(close, 2));
+trade_when(triggerTradeexp, alphaexp, triggerExitexp)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `5`, `10` | 窗口 |
+
+**说明**: 今日成交量为近期最大且高于均值时交易
+
+---
+
+### TPL-1614: 情绪成交量交易
+```
+模板:
+sent_vol = vec_sum(scl12_alltype_buzzvec);
+trade_when(rank(sent_vol) > 0.95, -zscore(scl12_buzz) * sent_vol, -1)
+```
+**说明**: 高情绪量时反向交易情绪
+
+---
+
+### TPL-1615: 双层中性化模板
+```
+模板:
+a = ts_zscore({field}, 252);
+a1 = group_neutralize(a, industry);
+a2 = group_neutralize(a1, bucket(rank(cap), range='0.1,1,0.1'))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意字段 | 数据字段 |
+
+**说明**: 先行业后市值的双重中性化
+
+---
+
+### TPL-1616: 相关性计算公式
+```
+模板:
+a = {field1};
+b = {field2};
+p = {d};
+c = ts_mean(ts_av_diff(a, p) * ts_av_diff(b, p), p);
+c / ts_std_dev(a, p) / ts_std_dev(b, p) # 近似 ts_corr
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field1}` | `close`, `returns` | 第一字段 |
+| `{field2}` | `volume`, `open` | 第二字段 |
+| `{d}` | `5`, `22` | 窗口 |
+
+---
+
+### TPL-1617: 回归中性化双因子
+```
+模板:
+afr = vec_avg({analyst_field});
+short_mom = ts_mean(returns - group_mean(returns, 1, market), {d_short});
+long_mom = ts_delay(ts_mean(returns - group_mean(returns, 1, market), {d_long}), {d_long});
+regression_neut(regression_neut(afr, short_mom), long_mom)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{analyst_field}` | 分析师VECTOR字段 | 分析师数据 |
+| `{d_short}` | `5`, `10` | 短期动量窗口 |
+| `{d_long}` | `20`, `22` | 长期动量窗口 |
+
+**说明**: 剥离短期和长期动量后的分析师因子
+
+---
+
+### TPL-1618: 回归斜率趋势检测
+```
+模板: ts_regression(ts_zscore({field}, {d}), ts_step(1), {d}, rettype=2)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `252`, `500` | 窗口 |
+
+**说明**: rettype=2返回回归斜率,检测长期趋势
+
+---
+
+### TPL-1619: 三因子乘积组合
+```
+模板:
+my_group = market;
+rank(
+group_rank(ts_decay_linear(volume / ts_sum(volume, 252), 10), my_group) *
+group_rank(ts_rank(vec_avg({fundamental}), {d}), my_group) *
+group_rank(-ts_delta(close, 5), my_group)
+)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{fundamental}` | 基本面VECTOR字段 | 基本面数据 |
+| `{d}` | `252`, `504` | 排名窗口 |
+
+**说明**: 成交量趋势 × 基本面排名 × 价格反转
+
+---
+
+### TPL-1620: 波动率条件反转
+```
+模板:
+vol = ts_std_dev(returns, {d});
+vol_mean = group_mean(vol, 1, market);
+flip_ret = if_else(vol < vol_mean, -returns, returns);
+-ts_mean(flip_ret, {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `20`, `22` | 窗口 |
+
+**说明**: 低波动做反转,高波动做动量
+
+---
+
+### TPL-1621: 恐惧指标复合
+```
+模板:
+fear = ts_mean(
+abs(returns - group_mean(returns, 1, market)) /
+(abs(returns) + abs(group_mean(returns, 1, market)) + 0.1),
+{d}
+);
+-group_neutralize(fear * {signal}, bucket(rank(cap), range='0.1,1,0.1'))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `20`, `22` | 窗口 |
+| `{signal}` | 主信号 | 待组合信号 |
+
+---
+
+### TPL-1622: 财务质量单因子
+```
+模板: group_neutralize(rank({fundamental_field}), bucket(rank(cap), range='0,1,0.1'))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{fundamental_field}` | `roe`, `roa`, `net_income/assets` | 财务质量指标 |
+
+---
+
+### TPL-1623: 老虎哥回归模板
+```
+模板: group_rank(ts_regression(ts_zscore({field1}, {d}), ts_zscore(vec_sum({field2}), {d}), {d}), densify(sector))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field1}` | 任意MATRIX字段 | Y变量 |
+| `{field2}` | 任意VECTOR字段 | X变量 |
+| `{d}` | `252`, `504` | 回归窗口 |
+
+---
+
+### TPL-1624: 综合数据清洗模板
+```
+模板: ts_decay_linear(-densify(zscore(winsorize(ts_backfill({field}, 115), std=4))), 10)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 低频字段如 `anl4_adjusted_netincome_ft` | 需要处理的字段 |
+
+---
+
+### TPL-1625: 延迟最大值位置模板
+```
+模板: ts_max({field}, {d}) = ts_delay({field}, ts_arg_max({field}, {d})) # 等效公式
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意字段 | 数据字段 |
+| `{d}` | `22`, `66` | 窗口 |
+
+---
+
+### TPL-1626: 数据探索通用模板
+```
+模板: zscore(ts_delta(rank(ts_zscore({field}, {d1})), {d2}))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 待探索数据字段 |
+| `{d1}` | `60`, `126`, `252` | zscore窗口 |
+| `{d2}` | `5`, `10`, `22` | delta窗口 |
+
+**说明**: 顾问推荐的新数据探索模板,可替换op和时间参数
+
+---
+
+### TPL-1627: 自定义衰减权重模板
+```
+模板:
+weight = {d} + ts_step(0); # 线性递增权重
+ts_sum({data} * weight, {d}) / ts_sum(weight, {d}) # 加权平均
+
+# 替代版 (ts_step递减)
+ts_sum({alpha} * ts_step(1), {d}) / ts_sum(ts_step(1), {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{data}` | 任意字段 | 数据字段 |
+| `{alpha}` | 主信号 | 原始Alpha |
+| `{d}` | `10`, `22`, `66` | 衰减窗口 |
+
+**说明**: 当没有ts_decay_linear权限时的替代方案
+
+---
+
+### TPL-1628: log_diff相对增长模板
+```
+模板: group_rank(log_diff({field}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 财务指标如 `sales`, `eps`, `assets` | 数据字段 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 检测相对增长率,对乘性变化更敏感
+
+---
+
+### TPL-1629: ts_product累积收益模板
+```
+模板: group_rank(ts_product(1 + {ret_field}, {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{ret_field}` | `returns`, 收益率字段 | 收益字段 |
+| `{d}` | `22`, `66`, `126` | 窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 计算累积收益排名
+
+---
+
+### TPL-1630: ts_percentage阈值模板
+```
+模板:
+high_threshold = ts_percentage({field}, {d}, percentage=0.5);
+low_threshold = ts_percentage({field}, {d}, percentage=0.5);
+{signal}
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | `close`, 价格字段 | 阈值计算字段 |
+| `{d}` | `22`, `66` | 窗口 |
+| `{signal}` | 主信号 | 条件信号 |
+
+**说明**: 用于震荡带突破策略的阈值构建
+
+---
+
+### TPL-1631: 动量反转切换模板
+```
+模板:
+mom = ts_sum(returns, {d_long}) - ts_sum(returns, {d_short});
+reversal = -ts_delta(close, {d_short});
+if_else(ts_rank(ts_std_dev(returns, {d_short}), {d_long}) > 0.5, mom, reversal)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d_short}` | `5`, `10` | 短期窗口 |
+| `{d_long}` | `22`, `66` | 长期窗口 |
+
+**说明**: 高波动环境用动量,低波动环境用反转
+
+---
+
+### TPL-1632: 市场收益率近似模板 (CHN)
+```
+模板:
+value = rank(cap) > 0.9 ? cap : 0;
+market_return = group_sum(returns * value, country) / group_sum(value, country);
+market_return
+```
+**说明**: 用市值加权近似沪深300指数收益率,设置neutralization=NONE, decay=0
+
+---
+
+### TPL-1633: Beta回归中性化模板
+```
+模板:
+market_return = group_mean(returns, 1, market);
+ts_regression({field}, market_return, {d}) # 返回残差(Y - E[Y])
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 待中性化字段 |
+| `{d}` | `126`, `252` | 回归窗口 |
+
+**说明**: 使用一元线性回归剥离市场因子
+
+---
+
+### TPL-1634: ts_moment高阶矩k值模板
+```
+模板: ts_moment({field}, {d}, k={k})
+
+k=2: 方差 (等价于 ts_std_dev^2)
+k=3: 偏度 (等价于 ts_skewness)
+k=4: 峰度 (等价于 ts_kurtosis)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `22`, `66`, `126` | 窗口 |
+| `{k}` | `2`, `3`, `4` | 阶数 |
+
+---
+
+### TPL-1635: 龙头股因子增强模板
+```
+模板: sigmoid(rank(star_pm_global_rank))
+```
+**说明**: 对龙头股因子进行sigmoid增强
+
+---
+
+### TPL-1636: purify数据清洗嵌套模板
+```
+模板: group_rank(ts_rank(purify({field}), {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意字段 | 待清洗数据 |
+| `{d}` | `22`, `66` | 排名窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: purify自动化清洗异常值和噪声
+
+---
+
+### TPL-1637: 理想振幅因子模板
+```
+模板:
+amplitude = (high - low) / close;
+ideal_amp = ts_percentage(amplitude, {d}, percentage=0.5);
+group_rank(amplitude - ideal_amp, {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `22`, `66` | 百分位窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 实际振幅偏离理想振幅的程度
+
+---
+
+### TPL-1638: 异同离差乖离率因子 (MACD风格)
+```
+模板:
+ema_short = ts_decay_exp_window({field}, {d_short}, 0.9);
+ema_long = ts_decay_exp_window({field}, {d_long}, 0.9);
+dif = ema_short - ema_long;
+ts_zscore(dif, {d_signal})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | `close`, 价格字段 | 数据字段 |
+| `{d_short}` | `12`, `22` | 短期EMA窗口 |
+| `{d_long}` | `26`, `66` | 长期EMA窗口 |
+| `{d_signal}` | `9`, `22` | 信号线窗口 |
+
+---
+
+### TPL-1639: 收益率条件筛选反转
+```
+模板:
+high_ret = ts_rank(returns, {d1}) > 0.8;
+low_ret = ts_rank(returns, {d1}) < 0.2;
+if_else(high_ret, -returns, if_else(low_ret, returns, 0))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d1}` | `22`, `66` | 排名窗口 |
+
+**说明**: 只对极端收益做反转
+
+---
+
+### TPL-1640: 三阶模板优化版
+```
+模板: (((, ), ), )
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `` | `group_rank`, `group_zscore` | 外层分组操作 |
+| `` | `ts_rank`, `ts_delta`, `ts_mean` | 中层时序操作 |
+| `` | `ts_zscore`, `ts_rank`, `ts_ir` | 内层时序操作 |
+| `` | 任意字段 | 数据字段 |
+| `` | `60`, `126`, `252` | 内层窗口 |
+| `` | `5`, `22`, `66` | 外层窗口 |
+| `` | `sector`, `industry` | 分组 |
+
+**说明**: 经典三阶嵌套结构,可灵活替换各层操作符
+
+---
+
+### TPL-1641: ts_entropy信号检测模板
+```
+模板: ts_entropy({field}, {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | `returns`, `volume`, 任意MATRIX字段 | 数据字段 |
+| `{d}` | `14`, `22`, `66` | 窗口 |
+
+**说明**: 衡量时序数据的不确定性,高熵值表示更多随机性
+
+---
+
+### TPL-1642: 熵+ZScore组合模板
+```
+模板: ts_zscore({field}, {d}) * ts_entropy({field}, {d})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `14`, `22` | 窗口 |
+
+**说明**: RSI超买超卖 + 熵不确定性组合,捕捉可能的修正
+
+---
+
+### TPL-1643: ts_ir+ts_entropy信号组合
+```
+模板:
+signal = ts_ir({field}, {d}) + ts_entropy({field}, {d});
+group_rank(signal, {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `22`, `66` | 窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: IR(信息比率)和Entropy组合捕捉信号稳定性和分布特征
+
+---
+
+### TPL-1644: trade_when市值过滤模板
+```
+模板: trade_when(rank(cap) > {threshold}, {alpha}, -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{threshold}` | `0.3`, `0.5`, `0.7` | 市值排名阈值 |
+| `{alpha}` | 主信号 | 原始Alpha |
+
+**说明**: 仅交易大市值股票,降低prod corr
+
+---
+
+### TPL-1645: trade_when盈利过滤模板
+```
+模板: trade_when(eps > {threshold} * est_eps, group_rank((eps - est_eps)/est_eps, industry), -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{threshold}` | `1.0`, `1.1`, `1.2` | 盈利超预期比例 |
+
+**说明**: 只交易盈利超预期的股票
+
+---
+
+### TPL-1646: trade_when量价触发模板
+```
+模板:
+triggerTrade = (ts_arg_max(volume, {d}) < 1) && (volume > ts_sum(volume, {d})/{d});
+trade_when(triggerTrade, {alpha}, -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `5`, `10` | 判断窗口 |
+| `{alpha}` | `-rank(ts_delta(close, 2))` | 主信号 |
+
+**说明**: 量价突破触发条件交易
+
+---
+
+### TPL-1647: trade_when情绪量过滤模板
+```
+模板:
+sent_vol = vec_sum({sentiment_vec});
+trade_when(rank(sent_vol) > {threshold}, -zscore({sentiment_field}) * sent_vol, -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{sentiment_vec}` | `scl12_alltype_buzzvec` 等VECTOR字段 | 情绪向量 |
+| `{sentiment_field}` | `scl12_buzz`, `scl12_sentiment` | 情绪字段 |
+| `{threshold}` | `0.9`, `0.95` | 情绪量阈值 |
+
+**说明**: 高情绪量时反向交易情绪
+
+---
+
+### TPL-1648: bucket市值分组中性化模板
+```
+模板:
+my_group2 = bucket(rank(cap), range='{range}');
+group_neutralize({alpha}, my_group2)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{range}` | `'0,1,0.1'`, `'0.1,1,0.1'` | 分桶范围 |
+| `{alpha}` | 主信号 | 原始Alpha |
+
+**说明**: 按市值分桶进行中性化,去除规模效应
+
+---
+
+### TPL-1649: group_zscore时序组合模板
+```
+模板: group_zscore(ts_ir({field}, {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `22`, `66`, `126` | IR窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 在分组内进行IR的Z-score标准化
+
+---
+
+### TPL-1650: scale+rank+ts组合模板
+```
+模板: scale(rank(ts_zscore({field}, {d})))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `66`, `126`, `252` | 窗口 |
+
+**说明**: 多层标准化处理信号
+
+---
+
+### TPL-1651: Betting Against Beta模板
+```
+模板:
+market_return = group_mean(returns, 1, market);
+beta = ts_regression(returns, market_return, {d}, rettype=2);
+-group_rank(beta, industry)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `126`, `252` | 回归窗口 |
+
+**说明**: 反Beta投注因子,做多低Beta股票
+
+---
+
+### TPL-1652: 跳跃因子模板
+```
+模板:
+jump_up = ts_count(returns > ts_std_dev(returns, {d}) * {threshold}, {d});
+jump_down = ts_count(returns < -ts_std_dev(returns, {d}) * {threshold}, {d});
+group_rank(jump_down - jump_up, {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `22`, `66` | 统计窗口 |
+| `{threshold}` | `2`, `2.5`, `3` | 标准差倍数 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 统计尾部跳跃事件的不对称性
+
+---
+
+### TPL-1653: 量小换手率模板
+```
+模板:
+turnover = volume / sharesout;
+low_turnover = ts_percentage(turnover, {d}, percentage=0.2);
+group_rank(turnover < low_turnover, {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `22`, `66` | 百分位窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 识别低换手率状态
+
+---
+
+### TPL-1654: 隔夜收益因子模板
+```
+模板:
+overnight_ret = open / ts_delay(close, 1) - 1;
+group_rank(ts_mean(overnight_ret, {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `5`, `22`, `66` | 平均窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 隔夜"拉锯战"因子
+
+---
+
+### TPL-1655: sta1分组三因子模板
+```
+模板:
+a = rank(group_rank(ts_rank(ts_backfill({field1}, {d1}), {d2}), sta1_top3000c20));
+trade_when(rank(a) > {threshold}, -zscore(ts_zscore({field2}, {d3})) * a, {exit_threshold} - rank(a))
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field1}` | 任意字段 | 第一因子字段 |
+| `{field2}` | 模型字段如`mdl175_01dtsv` | 第二因子字段 |
+| `{d1}`, `{d2}`, `{d3}` | 各窗口参数 | 时间窗口 |
+| `{threshold}` | `0.03`, `0.1` | 入场阈值 |
+| `{exit_threshold}` | `0.25`, `0.5` | 出场阈值 |
+
+**说明**: 使用sta1预定义分组的复合策略
+
+---
+
+### TPL-1656: macro泛化模板
+```
+模板: group_rank(ts_delta(ts_zscore({macro_field}, {d1}), {d2}), country)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{macro_field}` | 宏观数据字段 | 宏观数据 |
+| `{d1}` | `126`, `252` | zscore窗口 |
+| `{d2}` | `5`, `22` | delta窗口 |
+
+**说明**: 基于Labs分析macro的泛化模板
+
+---
+
+### TPL-1657: ASI broker模板
+```
+模板:
+signal = group_rank(ts_rank({broker_field}, {d}), market);
+trade_when(volume > adv20, signal, -1)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{broker_field}` | broker数据字段 | 券商数据 |
+| `{d}` | `22`, `66` | 排名窗口 |
+
+**说明**: ASI区域broker因子,需设置max_trade=ON
+
+---
+
+### TPL-1658: Earnings超预期模板
+```
+模板:
+surprise = (actual_eps - est_eps) / abs(est_eps);
+group_rank(ts_zscore(surprise, {d}), industry)
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `66`, `126` | zscore窗口 |
+
+**说明**: 盈利超预期因子
+
+---
+
+### TPL-1659: CCI技术指标模板
+```
+模板:
+tp = (high + low + close) / 3;
+cci = (tp - ts_mean(tp, {d})) / (0.015 * ts_mean(abs(tp - ts_mean(tp, {d})), {d}));
+group_rank(-cci, {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{d}` | `14`, `20` | CCI窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 商品通道指数(CCI)反转策略
+
+---
+
+### TPL-1660: 0.618黄金比例幂变换模板
+```
+模板:
+power_signal = signed_power({field}, 0.618);
+group_rank(ts_zscore(power_signal, {d}), {group})
+```
+| 占位符 | 可选值 | 说明 |
+|--------|--------|------|
+| `{field}` | 任意MATRIX字段 | 数据字段 |
+| `{d}` | `66`, `126` | zscore窗口 |
+| `{group}` | `sector`, `industry` | 分组 |
+
+**说明**: 使用黄金比例0.618进行幂次变换
+
+---
+
+## 附录A:标准时间窗口
+
+| 窗口代号 | 天数 | 含义 |
+|---------|------|------|
+| `d_week` | 5 | 一周 |
+| `d_month` | 22 | 一月 |
+| `d_quarter` | 66 | 一季度 |
+| `d_half` | 126 | 半年 |
+| `d_year` | 252 | 一年 |
+| `d_2year` | 504 | 两年 |
+
+**使用规则**:
+- 反转因子: 短窗口 `{3, 5, 22}`
+- 动量因子: 中窗口 `{22, 66}`
+- 长期趋势: 长窗口 `{126, 252, 504}`
+- 回归/波动: 超长窗口 `{250, 500, 750}`
+
+---
+
+## 附录B:常用操作符分类
+
+### 时序操作符 ``
+| 操作符 | 用途 |
+|--------|------|
+| `ts_mean` | 移动平均 |
+| `ts_rank` | 时序排名 |
+| `ts_delta` | 差分 |
+| `ts_std_dev` | 移动标准差 |
+| `ts_ir` | 信息比率 |
+| `ts_zscore` | 时序Z-score |
+| `ts_corr` | 滚动相关性 |
+| `ts_regression` | 滚动回归 |
+| `ts_decay_linear` | 线性衰减 |
+| `ts_decay_exp_window` | 指数衰减 |
+| `ts_sum` | 滚动求和 |
+| `ts_backfill` | 数据回填 |
+| `ts_arg_min` | 最小值位置 |
+| `ts_arg_max` | 最大值位置 |
+| `ts_max` | 滚动最大值 |
+| `ts_min` | 滚动最小值 |
+| `ts_delay` | 延迟 |
+| `ts_moment` | k阶中心矩 |
+| `ts_co_skewness` | 协偏度 |
+| `ts_co_kurtosis` | 协峰度 |
+| `ts_partial_corr` | 偏相关 |
+| `ts_triple_corr` | 三元相关 |
+| `ts_theilsen` | Theil-Sen回归 |
+| `ts_poly_regression` | 多项式回归残差 |
+| `ts_vector_neut` | 向量中性化 |
+| `ts_weighted_decay` | 加权衰减 |
+| `ts_min_max_cps` | 最小最大压缩 |
+| `ts_max_diff` | 与最大值差 |
+| `ts_av_diff` | 与均值差 |
+| `ts_quantile` | 分位数 |
+| `ts_percentage` | 百分位 |
+| `ts_median` | 中位数 |
+| `ts_product` | 累积乘积 |
+| `ts_count_nans` | NaN计数 |
+| `ts_scale` | 时序缩放 |
+| `ts_target_tvr_hump` | 目标换手率Hump |
+| `ts_target_tvr_delta_limit` | Delta换手率限制 |
+
+### 分组操作符 ``
+| 操作符 | 用途 |
+|--------|------|
+| `group_rank` | 分组排名 |
+| `group_neutralize` | 分组中性化 |
+| `group_zscore` | 分组Z-score |
+| `group_mean` | 分组均值 |
+| `group_sum` | 分组求和 |
+| `group_extra` | 分组提取/填补 |
+| `group_backfill` | 分组回填 |
+| `group_normalize` | 分组归一化 |
+| `group_vector_neut` | 分组向量中性化 |
+| `group_vector_proj` | 分组向量投影 |
+| `group_count` | 分组计数 |
+| `group_std_dev` | 分组标准差 |
+
+### 向量操作符 ``
+| 操作符 | 用途 |
+|--------|------|
+| `vec_avg` | 向量平均 |
+| `vec_sum` | 向量求和 |
+| `vec_max` | 向量最大 |
+| `vec_min` | 向量最小 |
+| `vec_stddev` | 向量标准差 |
+| `vec_count` | 向量计数 |
+| `vec_norm` | 向量归一化 |
+| `vec_zscore` | 向量Z-score |
+| `vec_range` | 向量范围 |
+
+### 事件/时间操作符
+| 操作符 | 用途 |
+|--------|------|
+| `days_from_last_change` | 距离上次变化天数 |
+| `last_diff_value` | 最近不同值 |
+| `ts_step` | 时间步长 |
+
+### 信号处理操作符
+| 操作符 | 用途 |
+|--------|------|
+| `signed_power` | 带符号幂变换 |
+| `clamp` | 边界限制 |
+| `left_tail` | 左尾截断 |
+| `right_tail` | 右尾截断 |
+| `fraction` | 分数映射 |
+| `nan_out` | NaN外推 |
+| `purify` | 数据清洗 |
+| `keep` | 条件保留 |
+| `scale_down` | 缩放降维 |
+| `hump` | Hump平滑 |
+| `hump_decay` | Hump衰减 |
+
+### 其他常用操作符
+| 操作符 | 用途 |
+|--------|------|
+| `rank` | 截面排名 |
+| `zscore` | 截面Z-score |
+| `sigmoid` | Sigmoid归一化 |
+| `winsorize` | 极端值截断 |
+| `truncate` | 截断 |
+| `tail` | 尾部处理 |
+| `scale` | 缩放 |
+| `filter` | 过滤 |
+| `densify` | 稠密化 |
+| `bucket` | 分桶 |
+| `log` | 对数 |
+| `abs` | 绝对值 |
+| `if_else` | 条件判断 |
+| `trade_when` | 条件交易 |
+| `regression_neut` | 回归中性化 |
+| `regression_proj` | 回归投影 |
+| `is_nan` | NaN检测 |
+| `is_not_nan` | 非NaN检测 |
+| `inst_pnl` | 单标的PnL |
+| `convert` | 单位转换 |
+| `pasteurize` | 去无效值 |
+
+---
+
+## 附录C:数据字段分类
+
+### 量价类 ``
+```
+close, open, high, low, vwap
+returns, volume, adv20, sharesout, cap
+```
+
+### 基本面类 ``
+```
+assets, sales, ebitda, net_income, eps, operating_income
+goodwill, debt, cash, equity, gross_profit
+fnd6_*, fnd72_*, mdl175_*, mdl163_*
+debt_to_equity, roe, roa
+```
+
+### 分析师类 `` (VECTOR)
+```
+anl4_eps_mean, anl4_eps_low, anl4_eps_high
+anl4_revenue_mean, anl4_fcf_value, anl4_netprofit_mean
+anl4_adjusted_netincome_ft, anl4_bvps_flag
+oth41_s_west_*, analyst_*
+```
+
+### 情绪类 ``
+```
+scl12_sentiment, scl12_buzz, scl12_alltype_buzzvec
+snt_value, snt_buzz, snt_buzz_ret, snt_buzz_bfl
+nws18_relevance, nws18_ber
+nws12_prez_result2, nws12_prez_short_interest
+mws85_sentiment, mws46_mcv
+```
+
+### 期权类 ``
+```
+option8_*, option14_*
+implied_volatility_call_120, implied_volatility_call_270
+parkinson_volatility_120, parkinson_volatility_270
+pcr_vol_10, pcr_vol_30
+put_delta, call_delta, put_gamma, call_gamma
+put_theta, call_theta, put_vega, call_vega
+call_breakeven_10, put_breakeven_10
+```
+
+### 模型类 ``
+```
+mdl175_01dtsv, mdl175_01icc
+mdl163_*, mdl*
+```
+
+### 分组类 ``
+```
+industry, sector, subindustry
+market, country, exchange
+sta1_top3000c20, sta1_*
+pv13_*, pv27_*
+```
diff --git a/simple72/Tranformer/validator.py b/simple72/Tranformer/validator.py
new file mode 100755
index 0000000..2fdaa4c
--- /dev/null
+++ b/simple72/Tranformer/validator.py
@@ -0,0 +1,1261 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+表达式验证器 - 使用抽象语法树验证字符串表达式格式是否正确
+
+本模块实现了一个能够检测字符串表达式格式是否正确的系统,基于PLY(Python Lex-Yacc)
+构建词法分析器和语法分析器,识别表达式中的操作符、函数和字段,并验证其格式正确性。
+"""
+
+import re
+import sys
+import json
+import os
+from typing import List, Dict, Any, Optional, Tuple
+
+# 尝试导入PLY库,如果不存在则提供安装提示
+try:
+ import ply.lex as lex
+ import ply.yacc as yacc
+except ImportError:
+ print("错误: 需要安装PLY库。请运行 'pip install ply' 来安装。")
+ sys.exit(1)
+
+# 1. 定义支持的操作符和函数
+supported_functions = {
+ # Group 类别函数
+ 'group_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ # group_mean(x, w, group)
+ 'group_mean': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'category']},
+ 'group_median': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_rank': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_vector_proj': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'category']},
+ 'group_normalize': {'min_args': 2, 'max_args': 5, 'arg_types': ['expression', 'category', 'expression', 'expression', 'expression']},
+ 'group_extra': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'category']},
+ 'group_backfill': {'min_args': 3, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'expression'], 'param_names': ['x', 'cat', 'days', 'std']},
+ 'group_scale': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_count': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_zscore': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_std_dev': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_sum': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_neutralize': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'category']},
+ 'group_multi_regression': {'min_args': 4, 'max_args': 9, 'arg_types': ['expression'] * 9},
+ 'group_cartesian_product': {'min_args': 2, 'max_args': 2, 'arg_types': ['category', 'category']},
+ 'combo_a': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression']},
+
+ # Transformational 类别函数
+ 'right_tail': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'bucket': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # 第二个参数可以是string类型的range参数
+ 'tail': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'expression']},
+ 'left_tail': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'trade_when': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression']},
+ 'generate_stats': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+
+ # Cross Sectional 类别函数
+ 'winsorize': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['x', 'std']},
+ 'rank': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'regression_proj': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'vector_neut': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'regression_neut': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'multi_regression': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 100}, # 支持多个自变量
+
+ # Time Series 类别函数
+ 'ts_std_dev': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_mean': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_delay': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_corr': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
+ 'ts_zscore': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_returns': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'mode'], 'keyword_only': True},
+ 'ts_product': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_backfill(x, d)
+ 'ts_backfill': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
+ 'days_from_last_change': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'last_diff_value': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_scale(x, d, constant=0)
+ 'ts_scale': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
+ # Platform: ts_entropy(x, d)
+ 'ts_entropy': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'd']},
+ 'ts_step': {'min_args': 1, 'max_args': 1, 'arg_types': ['number']},
+ 'ts_sum': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_co_kurtosis': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
+ 'inst_tvr': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_decay_exp_window': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'factor'], 'keyword_only': True},
+ 'ts_av_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_kurtosis': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_min_max_diff(x, d, f=0.5)
+ 'ts_min_max_diff': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
+ 'ts_arg_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_max': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_min_max_cps(x, d, f=2)
+ 'ts_min_max_cps': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'f'], 'keyword_only': True},
+ # Platform: ts_rank(x, d, constant=0)
+ 'ts_rank': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'constant'], 'keyword_only': True},
+ 'ts_ir': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_theilsen': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
+ # Platform: hump_decay(x, p=0)
+ 'hump_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'p'], 'keyword_only': True},
+ # Platform: ts_weighted_decay(x, k=0.5)
+ 'ts_weighted_decay': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'k'], 'keyword_only': True},
+ # Platform: ts_quantile(x, d, driver="gaussian")
+ 'ts_quantile': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'string'], 'param_names': ['x', 'd', 'driver'], 'keyword_only': True},
+ 'ts_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_count_nans': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_covariance': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
+ 'ts_co_skewness': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number']},
+ 'ts_min_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_decay_linear(x, d, dense=false)
+ 'ts_decay_linear': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'boolean'], 'param_names': ['x', 'd', 'dense'], 'keyword_only': True},
+ # Platform: jump_decay(x, d, sensitivity=0.5, force=0.1)
+ 'jump_decay': {'min_args': 2, 'max_args': 4, 'arg_types': ['expression', 'number', 'number', 'number'], 'param_names': ['x', 'd', 'sensitivity', 'force'], 'keyword_only': True},
+ # Platform: ts_moment(x, d, k=0)
+ 'ts_moment': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'k'], 'keyword_only': True},
+ 'ts_arg_min': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ '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},
+ 'ts_skewness': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_max_diff': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'kth_element': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'number', 'number']},
+ 'hump': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'hump']},
+ 'ts_median': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_delta': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ # Platform: ts_poly_regression(y, x, d, k=1) and k must be keyword if provided
+ '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},
+ '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},
+ '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},
+ '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},
+ # Platform: ts_delta_limit(x, y, limit_volume=0.1)
+ 'ts_delta_limit': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'y', 'limit_volume'], 'keyword_only': True},
+
+ # Special 类别函数
+ 'inst_pnl': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'self_corr': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'in': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # 注意:这是关键字
+ 'universe_size': {'min_args': 0, 'max_args': 0, 'arg_types': []},
+
+ # Missing functions from operators.py
+ 'quantile': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'driver', 'sigma']}, # quantile(x, driver = gaussian, sigma = 1.0)
+ 'normalize': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'boolean', 'number']}, # normalize(x, useStd = false, limit = 0.0)
+ 'zscore': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']}, # zscore(x)
+
+ # Logical 类别函数
+ 'or': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # 注意:这是关键字
+ 'and': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # 注意:这是关键字
+ 'not': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']}, # 注意:这是关键字
+ 'is_nan': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'is_not_nan': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'less': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'equal': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'greater': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'is_finite': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'if_else': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression']},
+ 'not_equal': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'less_equal': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'greater_equal': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+
+ # Vector 类别函数
+ 'vec_kurtosis': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_min': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_count': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_sum': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_skewness': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_max': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_avg': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_range': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_choose': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'nth']},
+ 'vec_powersum': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
+ 'vec_stddev': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_percentage': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'percentage']},
+ 'vec_ir': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'vec_norm': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'ts_percentage': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'd', 'percentage']},
+ 'signed_power': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+ 'ts_product': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number']},
+
+ # Additional functions from test cases
+ 'rank_by_side': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'number', 'number'], 'param_names': ['x', 'rate', 'scale']},
+ 'log_diff': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'nan_mask': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']},
+ 'ts_partial_corr': {'min_args': 4, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'number']},
+ 'ts_triple_corr': {'min_args': 4, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'number']},
+ 'clamp': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'lower', 'upper']},
+ 'keep': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'condition', 'period']},
+ 'replace': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'target', 'dest']},
+ 'filter': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'h', 't']},
+ 'one_side': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'string'], 'param_names': ['x', 'side']},
+ 'scale_down': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'number'], 'param_names': ['x', 'constant']},
+
+ # Arithmetic 类别函数
+ # add(x, y, ..., filter=false)
+ # NOTE: add() is variadic (>=2 terms) with an optional boolean filter flag.
+ # We validate it with custom logic in validate_function().
+ 'add': {'min_args': 2, 'max_args': 101, 'arg_types': ['expression'] * 101},
+ 'multiply': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 99 + ['boolean'], 'param_names': ['x', 'y', 'filter']}, # multiply(x, y, ..., filter=false)
+ 'sign': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'subtract': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # subtract(x, y, filter=false)
+ 'pasteurize': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'log': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'purify': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'arc_tan': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'max': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 100}, # max(x, y, ...)
+ 'to_nan': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'boolean']}, # to_nan(x, value=0, reverse=false)
+ 'abs': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'sigmoid': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'divide': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # divide(x, y)
+ 'min': {'min_args': 2, 'max_args': 100, 'arg_types': ['expression'] * 100}, # min(x, y, ...)
+ 'tanh': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'nan_out': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'lower', 'upper']}, # nan_out(x, lower=0, upper=0)
+ 'signed_power': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # signed_power(x, y)
+ 'inverse': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'round': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'sqrt': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 's_log_1p': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'reverse': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']}, # -x
+ 'power': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression']}, # power(x, y)
+ 'densify': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ 'floor': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression']},
+ # Appended missing operators
+ 'arc_cos': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['x']},
+ 'arc_sin': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['x']},
+ 'ceiling': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['x']},
+ 'exp': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['x']},
+ 'fraction': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['x']},
+ 'round_down': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['x', 'f']},
+ 'is_not_finite': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'negate': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'ts_rank_gmean_amean_diff': {'min_args': 5, 'max_args': 5, 'arg_types': ['expression', 'expression', 'expression', 'expression', 'number'], 'param_names': ['input1', 'input2', 'input3', '...', 'd']},
+ 'ts_vector_neut': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'y', 'd']},
+ 'ts_vector_proj': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'number'], 'param_names': ['x', 'y', 'd']},
+ 'scale': {'min_args': 1, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'expression'], 'param_names': ['x', 'scale', 'longscale', 'shortscale']},
+ 'generalized_rank': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['open', 'm']},
+ 'rank_gmean_amean_diff': {'min_args': 4, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'expression'], 'param_names': ['input1', 'input2', 'input3', '...']},
+ 'truncate': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['x', 'maxPercent']},
+ 'vector_proj': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['x', 'y']},
+ 'vec_filter': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['vec', 'value']},
+ 'group_coalesce': {'min_args': 4, 'max_args': 4, 'arg_types': ['expression', 'expression', 'expression', 'expression'], 'param_names': ['original_group', 'group2', 'group3', '…']},
+ 'group_percentage': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'category', 'expression'], 'param_names': ['x', 'group', 'percentage']},
+ 'group_vector_neut': {'min_args': 3, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['x', 'y', 'g']},
+ 'convert': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['x', 'mode']},
+ 'reduce_avg': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['input', 'threshold']},
+ 'reduce_choose': {'min_args': 2, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['input', 'nth', 'ignoreNan']},
+ 'reduce_count': {'min_args': 2, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['input', 'threshold']},
+ 'reduce_ir': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_kurtosis': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_max': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_min': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_norm': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_percentage': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['input', 'percentage']},
+ 'reduce_powersum': {'min_args': 1, 'max_args': 3, 'arg_types': ['expression', 'expression', 'expression'], 'param_names': ['input', 'constant', 'precise']},
+ 'reduce_range': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_skewness': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+ 'reduce_stddev': {'min_args': 1, 'max_args': 2, 'arg_types': ['expression', 'expression'], 'param_names': ['input', 'threshold']},
+ 'reduce_sum': {'min_args': 1, 'max_args': 1, 'arg_types': ['expression'], 'param_names': ['input']},
+}
+
+# 2. 定义group类型字段
+group_fields = {
+ 'sector', 'subindustry', 'industry', 'exchange', 'country', 'market'
+}
+
+# 3. 有效类别集合
+valid_categories = group_fields
+
+# 4. 字段命名模式 - 只校验字段是不是数字字母下划线组成
+field_patterns = [
+ re.compile(r'^[a-zA-Z0-9_]+$'), # 只允许数字、字母和下划线组成的字段名
+]
+
+# 4. 抽象语法树节点类型
+class ASTNode:
+ """抽象语法树节点基类"""
+ def __init__(self, node_type: str, children: Optional[List['ASTNode']] = None,
+ value: Optional[Any] = None, line: Optional[int] = None):
+ self.node_type = node_type # 'function', 'operator', 'field', 'number', 'expression'
+ self.children = children or []
+ self.value = value
+ self.line = line
+
+ def __str__(self) -> str:
+ return f"ASTNode({self.node_type}, {self.value}, line={self.line})"
+
+ def __repr__(self) -> str:
+ return self.__str__()
+
+class ExpressionValidator:
+ """表达式验证器类"""
+
+ def __init__(self):
+ """初始化词法分析器和语法分析器"""
+ # 构建词法分析器
+ self.lexer = lex.lex(module=self, debug=False)
+ # 构建语法分析器
+ self.parser = yacc.yacc(module=self, debug=False)
+ # 错误信息存储
+ self.errors = []
+ # Cache for unit inference (unit/scalar/category)
+ self._unit_cache: Dict[int, str] = {}
+ # Cache for derived category detection (bucket/group_cartesian_product outputs)
+ self._derived_category_cache: Dict[int, bool] = {}
+
+ # 词法分析器规则
+ tokens = ('FUNCTION', 'FIELD', 'NUMBER', 'LPAREN', 'RPAREN',
+ 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'COMMA', 'CATEGORY',
+ 'EQUAL', 'ASSIGN', 'IDENTIFIER', 'STRING', 'GREATER', 'LESS', 'GREATEREQUAL', 'LESSEQUAL', 'NOTEQUAL', 'BOOLEAN')
+
+ # 忽略空白字符
+ t_ignore = ' \t\n'
+
+ # 操作符 - 注意顺序很重要,长的操作符要放在前面
+ t_PLUS = r'\+'
+ t_MINUS = r'-'
+ t_TIMES = r'\*'
+ t_DIVIDE = r'/'
+ t_LPAREN = r'\('
+ t_RPAREN = r'\)'
+ t_COMMA = r','
+ t_EQUAL = r'=='
+ t_NOTEQUAL = r'!='
+ t_GREATEREQUAL = r'>='
+ t_LESSEQUAL = r'<='
+ t_GREATER = r'>'
+ t_LESS = r'<'
+ t_ASSIGN = r'='
+
+ # 数字(整数和浮点数)
+ def t_NUMBER(self, t):
+ r'\d+\.?\d*'
+ if '.' in t.value:
+ t.value = float(t.value)
+ else:
+ t.value = int(t.value)
+ return t
+
+ # 字符串 - 需要放在所有其他标识符规则之前
+ def t_STRING(self, t):
+ r"'[^']*'|\"[^\"]*\""
+ # 去除引号
+ t.value = t.value[1:-1]
+ return t
+
+ # 函数和字段名
+ def t_IDENTIFIER(self, t):
+ r'[a-zA-Z_][a-zA-Z0-9_]*'
+ # 检查是否为布尔值
+ if t.value.lower() in {'true', 'false'}:
+ t.type = 'BOOLEAN'
+ t.value = t.value.lower() # 转换为小写以保持一致性
+ else:
+ # 查看当前token后面的字符,判断是否为参数名(后面跟着'=')
+ lexpos = t.lexpos
+ next_chars = ''
+ if lexpos + len(t.value) < len(t.lexer.lexdata):
+ # 查看当前token后面的字符,跳过空格
+ next_pos = lexpos + len(t.value)
+ while next_pos < len(t.lexer.lexdata) and t.lexer.lexdata[next_pos].isspace():
+ next_pos += 1
+ if next_pos < len(t.lexer.lexdata):
+ next_chars = t.lexer.lexdata[next_pos:next_pos+1]
+
+ # 如果后面跟着'=',则为参数名
+ if next_chars == '=':
+ t.type = 'IDENTIFIER'
+ # 如果后面跟着'(',则为函数名
+ elif next_chars == '(':
+ t.type = 'FUNCTION'
+ t.value = t.value.lower() # 转换为小写以保持一致性
+ # 检查是否为参数名(支持更多参数名)
+ elif t.value in {'std', 'k', 'lambda_min', 'lambda_max', 'target_tvr', 'range', 'buckets', 'lag', 'rettype', 'mode', 'nth', 'constant', 'percentage', 'driver', 'sigma', 'rate', 'scale', 'filter', 'lower', 'upper', 'target', 'dest', 'event', 'sensitivity', 'force', 'h', 't', 'period', 'stddev', 'factor', 'k', 'useStd', 'limit', 'gaussian', 'uniform', 'cauchy'}:
+ t.type = 'IDENTIFIER'
+ # 检查是否为函数名(不区分大小写)
+ elif t.value.lower() in supported_functions:
+ t.type = 'FUNCTION'
+ t.value = t.value.lower() # 转换为小写以保持一致性
+ # 检查是否为有效类别
+ elif t.value in valid_categories:
+ t.type = 'CATEGORY'
+ # 检查是否为字段名
+ elif self._is_valid_field(t.value):
+ t.type = 'FIELD'
+ else:
+ # 其他标识符,保留为IDENTIFIER类型
+ t.type = 'IDENTIFIER'
+ return t
+
+ # 行号跟踪
+ def t_newline(self, t):
+ r'\n+'
+ t.lexer.lineno += len(t.value)
+
+ # 错误处理
+ def t_error(self, t):
+ if t:
+ # 检查是否为非法字符
+ if not re.match(r'[a-zA-Z0-9_\+\-\*/\(\)\,\s=<>!]', t.value[0]):
+ # 这是一个非法字符
+ self.errors.append(f"非法字符 '{t.value[0]}' (行 {t.lexer.lineno})")
+ else:
+ # 这是一个非法标记
+ self.errors.append(f"非法标记 '{t.value}' (行 {t.lexer.lineno})")
+ # 跳过这个字符,继续处理
+ t.lexer.skip(1)
+ else:
+ self.errors.append("词法分析器到达文件末尾")
+
+ # 语法分析器规则
+ def p_expression(self, p):
+ """expression : comparison
+ | expression EQUAL comparison
+ | expression NOTEQUAL comparison
+ | expression GREATER comparison
+ | expression LESS comparison
+ | expression GREATEREQUAL comparison
+ | expression LESSEQUAL comparison"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ASTNode('binop', [p[1], p[3]], {'op': p[2]})
+
+ def p_comparison(self, p):
+ """comparison : term
+ | comparison PLUS term
+ | comparison MINUS term"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ASTNode('binop', [p[1], p[3]], {'op': p[2]})
+
+ def p_term(self, p):
+ """term : factor
+ | term TIMES factor
+ | term DIVIDE factor"""
+ if len(p) == 2:
+ p[0] = p[1]
+ else:
+ p[0] = ASTNode('binop', [p[1], p[3]], {'op': p[2]})
+
+ def p_factor(self, p):
+ """factor : NUMBER
+ | STRING
+ | FIELD
+ | CATEGORY
+ | IDENTIFIER
+ | BOOLEAN
+ | MINUS factor
+ | LPAREN expression RPAREN
+ | function_call"""
+ if len(p) == 2:
+ # 数字、字符串、字段、类别或标识符
+ if p.slice[1].type == 'NUMBER':
+ p[0] = ASTNode('number', value=p[1])
+ elif p.slice[1].type == 'STRING':
+ p[0] = ASTNode('string', value=p[1])
+ elif p.slice[1].type == 'FIELD':
+ p[0] = ASTNode('field', value=p[1])
+ elif p.slice[1].type == 'CATEGORY':
+ p[0] = ASTNode('category', value=p[1])
+ elif p.slice[1].type == 'BOOLEAN':
+ p[0] = ASTNode('boolean', value=p[1])
+ elif p.slice[1].type == 'IDENTIFIER':
+ p[0] = ASTNode('identifier', value=p[1])
+ else:
+ p[0] = p[1]
+ elif len(p) == 3:
+ # 一元负号
+ p[0] = ASTNode('unop', [p[2]], {'op': p[1]})
+ elif len(p) == 4:
+ # 括号表达式
+ p[0] = p[2]
+ else:
+ # 函数调用
+ p[0] = p[1]
+
+ def p_function_call(self, p):
+ '''function_call : FUNCTION LPAREN args RPAREN'''
+ p[0] = ASTNode('function', p[3], p[1])
+
+ def p_args(self, p):
+ '''args : arg_list
+ | empty'''
+ if len(p) == 2 and p[1] is not None:
+ p[0] = p[1]
+ else:
+ p[0] = []
+
+ def p_arg_list(self, p):
+ '''arg_list : arg
+ | arg_list COMMA arg'''
+ if len(p) == 2:
+ p[0] = [p[1]]
+ else:
+ p[0] = p[1] + [p[3]]
+
+ def p_arg(self, p):
+ '''arg : expression
+ | IDENTIFIER ASSIGN expression'''
+ if len(p) == 2:
+ p[0] = {'type': 'positional', 'value': p[1]}
+ else:
+ p[0] = {'type': 'named', 'name': p[1], 'value': p[3]}
+
+ def p_empty(self, p):
+ '''empty :'''
+ p[0] = None
+
+ # 语法错误处理
+ def p_error(self, p):
+ if p:
+ self.errors.append(f"语法错误在位置 {p.lexpos}: 非法标记 '{p.value}'")
+ else:
+ self.errors.append("语法错误: 表达式不完整")
+
+ def _is_valid_field(self, field_name: str) -> bool:
+ """检查字段名是否符合模式"""
+ for pattern in field_patterns:
+ if pattern.match(field_name):
+ return True
+ return False
+
+ def validate_function(self, node: ASTNode, is_in_group_arg: bool = False) -> List[str]:
+ """验证函数调用的参数数量和类型"""
+ function_name = node.value
+ args = node.children
+ function_info = supported_functions.get(function_name)
+
+ if not function_info:
+ return [f"未知函数: {function_name}"]
+
+ # Custom validation for variadic functions with optional flags
+ if function_name == 'add':
+ return self._validate_add(args, is_in_group_arg)
+
+ errors = []
+
+ # Keyword-only enforcement for optional parameters.
+ # If enabled, only the required leading arguments can be positional.
+ keyword_only_from = function_info.get('keyword_only_from')
+ if keyword_only_from is None and function_info.get('keyword_only'):
+ keyword_only_from = function_info.get('min_args', 0)
+
+ # 检查参数数量
+ if len(args) < function_info['min_args']:
+ errors.append(f"函数 {function_name} 需要至少 {function_info['min_args']} 个参数,但只提供了 {len(args)}")
+ elif len(args) > function_info['max_args']:
+ errors.append(f"函数 {function_name} 最多接受 {function_info['max_args']} 个参数,但提供了 {len(args)}")
+
+ # 处理参数验证
+ # 跟踪已使用的位置参数索引
+ positional_index = 0
+
+ # 对于所有函数,支持命名参数
+ for arg in args:
+ if isinstance(arg, dict):
+ if arg['type'] == 'named':
+ # 命名参数
+ if 'param_names' in function_info and arg['name'] in function_info['param_names']:
+ # 查找参数在param_names中的索引
+ param_index = function_info['param_names'].index(arg['name'])
+ if param_index < len(function_info['arg_types']):
+ expected_type = function_info['arg_types'][param_index]
+ arg_errors = self._validate_arg_type(arg['value'], expected_type, param_index, function_name, is_in_group_arg)
+ errors.extend(arg_errors)
+ # 对于winsorize函数,支持std和clip参数
+ elif function_name == 'winsorize' and arg['name'] in ['std', 'clip']:
+ arg_errors = self._validate_arg_type(arg['value'], 'number', 0, function_name, is_in_group_arg)
+ errors.extend(arg_errors)
+ # 对于bucket函数,支持'range'和'buckets'参数
+ elif function_name == 'bucket' and arg['name'] in ['range', 'buckets']:
+ # range和buckets参数应该是string类型
+ arg_errors = self._validate_arg_type(arg['value'], 'string', 1, function_name, is_in_group_arg)
+ errors.extend(arg_errors)
+ else:
+ errors.append(f"函数 {function_name} 不存在参数 '{arg['name']}'")
+ elif arg['type'] == 'positional':
+ # 位置参数(字典形式)
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
+ param_name = None
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
+ param_name = function_info['param_names'][positional_index]
+ if param_name:
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
+ else:
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
+ else:
+ # 验证位置参数的类型
+ if positional_index < len(function_info['arg_types']):
+ expected_type = function_info['arg_types'][positional_index]
+ arg_errors = self._validate_arg_type(arg['value'], expected_type, positional_index, function_name, is_in_group_arg)
+ errors.extend(arg_errors)
+ positional_index += 1
+ else:
+ # 其他字典类型参数
+ errors.append(f"参数 {positional_index+1} 格式错误")
+ positional_index += 1
+ else:
+ # 位置参数(直接ASTNode形式)
+ if keyword_only_from is not None and positional_index >= keyword_only_from:
+ param_name = None
+ if 'param_names' in function_info and positional_index < len(function_info['param_names']):
+ param_name = function_info['param_names'][positional_index]
+ if param_name:
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数 '{param_name}='")
+ else:
+ errors.append(f"函数 {function_name} 的第{positional_index+1}个参数必须使用命名参数")
+ else:
+ # 验证位置参数的类型
+ if positional_index < len(function_info['arg_types']):
+ expected_type = function_info['arg_types'][positional_index]
+ arg_errors = self._validate_arg_type(arg, expected_type, positional_index, function_name, is_in_group_arg)
+ errors.extend(arg_errors)
+ positional_index += 1
+
+ return errors
+
+ def _validate_arg_type(self, arg: ASTNode, expected_type: str, arg_index: int, function_name: str, is_in_group_arg: bool = False) -> List[str]:
+ """验证参数类型是否符合预期"""
+ errors = []
+
+ def _is_number_like(node: ASTNode) -> bool:
+ if node is None:
+ return False
+ if node.node_type == 'number':
+ return True
+ if node.node_type == 'unop' and isinstance(node.value, dict) and node.value.get('op') in {'-', '+'}:
+ if node.children and hasattr(node.children[0], 'node_type'):
+ return _is_number_like(node.children[0])
+ return False
+
+ # Unit compatibility check
+ # bucket()/group_cartesian_product() output a derived category (grouping key).
+ # It can only be consumed where a category/grouping key is expected.
+ if self._is_derived_category(arg) and expected_type != 'category':
+ errors.append(
+ f"Incompatible unit for input of \"{function_name}\" at index {arg_index}, expected \"Unit[]\", found \"Unit[Group:1]\""
+ )
+ return errors
+
+ # 首先检查是否是group类型字段,如果是则只能用于Group类型函数
+ # 但是如果当前函数是group_xxx或在group函数的参数链中,则允许使用
+ if arg.node_type == 'category' and arg.value in group_fields:
+ if not (function_name.startswith('group_') or is_in_group_arg):
+ errors.append(f"Group类型字段 '{arg.value}' 只能用于Group类型函数的参数中")
+
+ # 然后验证参数类型是否符合预期
+ if expected_type == 'expression':
+ # 表达式可以是任何有效的AST节点
+ pass
+ elif expected_type == 'number':
+ # 允许 -1 这类一元负号数字常量(解析为 unop(number))
+ if not _is_number_like(arg):
+ errors.append(f"参数 {arg_index+1} 应该是一个数字,但得到 {arg.node_type}")
+ elif expected_type == 'boolean':
+ # 布尔值可以是 true/false 或数字(0/1)
+ if arg.node_type not in {'boolean', 'number'}:
+ errors.append(f"参数 {arg_index+1} 应该是一个布尔值(true/false 或 0/1),但得到 {arg.node_type}")
+ elif expected_type == 'field':
+ if arg.node_type != 'field' and arg.node_type != 'category':
+ # 允许field或category作为字段参数
+ errors.append(f"参数 {arg_index+1} 应该是一个字段,但得到 {arg.node_type}")
+ elif arg.node_type == 'field' and not self._is_valid_field(arg.value):
+ errors.append(f"无效的字段名: {arg.value}")
+ elif expected_type == 'category':
+ if not function_name.startswith('group_'):
+ # 非group函数的category参数必须是category类型且在valid_categories中
+ if arg.node_type != 'category':
+ errors.append(f"参数 {arg_index+1} 应该是一个类别,但得到 {arg.node_type}")
+ elif arg.value not in valid_categories:
+ errors.append(f"无效的类别: {arg.value}")
+ # group函数的category参数可以是任何类型(field、category等),不进行类型校验
+
+ return errors
+
+ def _infer_unit(self, node: ASTNode) -> str:
+ """Infer the Unit kind of an AST node.
+
+ Returns:
+ 'unit' - regular numeric time-series Unit[]
+ 'scalar' - literals (numbers/booleans/strings)
+ 'category' - category/grouping keys (industry/sector or derived via bucket/cartesian)
+ """
+ if node is None:
+ return 'unit'
+
+ cache_key = id(node)
+ cached = self._unit_cache.get(cache_key)
+ if cached is not None:
+ return cached
+
+ unit = 'unit'
+
+ if node.node_type in {'number', 'boolean', 'string'}:
+ unit = 'scalar'
+ elif node.node_type in {'field', 'identifier'}:
+ unit = 'unit'
+ elif node.node_type == 'category':
+ unit = 'category'
+ elif node.node_type in {'unop', 'binop'}:
+ child_units = [self._infer_unit(child) for child in node.children if hasattr(child, 'node_type')]
+ unit = 'category' if 'category' in child_units else 'unit'
+ elif node.node_type == 'function':
+ fname = node.value
+ if fname in {'bucket', 'group_cartesian_product'}:
+ unit = 'category'
+ else:
+ first_arg = None
+ for child in node.children:
+ if isinstance(child, dict):
+ if child.get('type') == 'positional':
+ first_arg = child.get('value')
+ break
+ else:
+ first_arg = child
+ break
+ if hasattr(first_arg, 'node_type'):
+ unit = self._infer_unit(first_arg)
+ else:
+ unit = 'unit'
+
+ self._unit_cache[cache_key] = unit
+ return unit
+
+ def _is_derived_category(self, node: ASTNode) -> bool:
+ """Return True if node is a derived category/grouping key (e.g., bucket/cartesian output)."""
+ if node is None:
+ return False
+
+ cache_key = id(node)
+ cached = self._derived_category_cache.get(cache_key)
+ if cached is not None:
+ return cached
+
+ derived = False
+ if node.node_type == 'function':
+ if node.value in {'bucket', 'group_cartesian_product'}:
+ derived = True
+ else:
+ function_info = supported_functions.get(node.value, {})
+ arg_types = function_info.get('arg_types', [])
+ param_names = function_info.get('param_names', [])
+
+ positional_index = 0
+ for child in node.children:
+ if isinstance(child, dict):
+ if child.get('type') == 'named':
+ name = child.get('name')
+ value = child.get('value')
+
+ expected_type = None
+ if name in param_names:
+ param_index = param_names.index(name)
+ if param_index < len(arg_types):
+ expected_type = arg_types[param_index]
+
+ if expected_type == 'category':
+ continue
+
+ if self._is_derived_category(value):
+ derived = True
+ break
+ elif child.get('type') == 'positional':
+ value = child.get('value')
+ expected_type = arg_types[positional_index] if positional_index < len(arg_types) else None
+
+ if expected_type != 'category' and self._is_derived_category(value):
+ derived = True
+ break
+ positional_index += 1
+ else:
+ expected_type = arg_types[positional_index] if positional_index < len(arg_types) else None
+ if expected_type != 'category' and self._is_derived_category(child):
+ derived = True
+ break
+ positional_index += 1
+ elif node.node_type in {'unop', 'binop'}:
+ derived = any(
+ self._is_derived_category(child)
+ for child in node.children
+ if hasattr(child, 'node_type')
+ )
+
+ self._derived_category_cache[cache_key] = derived
+ return derived
+
+ def _validate_add(self, args: List[Any], is_in_group_arg: bool = False) -> List[str]:
+ """Validate add(x, y, ..., filter=false).
+
+ Rules:
+ - At least 2 positional expression terms.
+ - Optional filter flag can be provided as:
+ - named argument: filter=
+ - last positional argument: or 0/1
+ """
+ errors: List[str] = []
+
+ if len(args) < 2:
+ return [f"函数 add 需要至少 2 个参数,但只提供了 {len(args)}"]
+
+ named_filter_nodes: List[ASTNode] = []
+ positional_nodes: List[ASTNode] = []
+
+ for arg in args:
+ if isinstance(arg, dict) and arg.get('type') == 'named':
+ name = arg.get('name')
+ value = arg.get('value')
+ if name != 'filter':
+ errors.append(f"函数 add 不存在参数 '{name}'")
+ continue
+ if not hasattr(value, 'node_type'):
+ errors.append("函数 add 的参数 filter 格式错误")
+ continue
+ named_filter_nodes.append(value)
+ elif isinstance(arg, dict) and arg.get('type') == 'positional':
+ value = arg.get('value')
+ if hasattr(value, 'node_type'):
+ positional_nodes.append(value)
+ else:
+ errors.append("函数 add 的位置参数格式错误")
+ elif hasattr(arg, 'node_type'):
+ positional_nodes.append(arg)
+ else:
+ errors.append("函数 add 的参数格式错误")
+
+ if len(named_filter_nodes) > 1:
+ errors.append("函数 add 的参数 'filter' 只能出现一次")
+
+ positional_filter_node: Optional[ASTNode] = None
+ # Only infer a positional filter flag when:
+ # - no named filter is provided
+ # - there are at least 3 positional args (x, y, filter)
+ # - the last arg is boolean or numeric 0/1
+ if not named_filter_nodes and len(positional_nodes) >= 3:
+ last = positional_nodes[-1]
+ if last.node_type == 'boolean' or (last.node_type == 'number' and last.value in {0, 1}):
+ positional_filter_node = positional_nodes.pop()
+
+ if len(positional_nodes) < 2:
+ errors.append(f"函数 add 需要至少 2 个输入项(不含filter),但只提供了 {len(positional_nodes)}")
+
+ for idx, node in enumerate(positional_nodes):
+ errors.extend(self._validate_arg_type(node, 'expression', idx, 'add', is_in_group_arg))
+
+ if positional_filter_node is not None and named_filter_nodes:
+ errors.append("函数 add 的 filter 不能同时用位置参数和命名参数传递")
+ if positional_filter_node is not None:
+ errors.extend(self._validate_arg_type(positional_filter_node, 'boolean', len(positional_nodes), 'add', is_in_group_arg))
+ if named_filter_nodes:
+ errors.extend(self._validate_arg_type(named_filter_nodes[0], 'boolean', len(positional_nodes), 'add', is_in_group_arg))
+
+ return errors
+
+ def validate_ast(self, ast: Optional[ASTNode], is_in_group_arg: bool = False) -> List[str]:
+ """递归验证抽象语法树"""
+ if not ast:
+ return ["无法解析表达式"]
+
+ errors = []
+
+ # 根据节点类型进行验证
+ if ast.node_type == 'function':
+ # 检查当前函数是否是group函数
+ is_group_function = ast.value.startswith('group_')
+ # 确定当前是否在group函数的参数链中
+ current_in_group_arg = is_in_group_arg or is_group_function
+ # 验证函数
+ function_errors = self.validate_function(ast, current_in_group_arg)
+ errors.extend(function_errors)
+
+ # 递归验证子节点时使用current_in_group_arg
+ for child in ast.children:
+ if isinstance(child, dict):
+ # 命名参数,验证其值
+ if 'value' in child and hasattr(child['value'], 'node_type'):
+ child_errors = self.validate_ast(child['value'], current_in_group_arg)
+ errors.extend(child_errors)
+ elif hasattr(child, 'node_type'):
+ child_errors = self.validate_ast(child, current_in_group_arg)
+ errors.extend(child_errors)
+ elif ast.node_type in ['unop', 'binop']:
+ # 对操作符的子节点进行验证
+ for child in ast.children:
+ if hasattr(child, 'node_type'):
+ child_errors = self.validate_ast(child, is_in_group_arg)
+ errors.extend(child_errors)
+ elif ast.node_type == 'field':
+ # 验证字段名
+ if not self._is_valid_field(ast.value):
+ errors.append(f"无效的字段名: {ast.value}")
+ else:
+ # 递归验证子节点
+ for child in ast.children:
+ if isinstance(child, dict):
+ # 命名参数,验证其值
+ if 'value' in child and hasattr(child['value'], 'node_type'):
+ child_errors = self.validate_ast(child['value'], is_in_group_arg)
+ errors.extend(child_errors)
+ elif hasattr(child, 'node_type'):
+ child_errors = self.validate_ast(child, is_in_group_arg)
+ errors.extend(child_errors)
+
+ return errors
+
+ def _process_semicolon_expression(self, expression: str) -> Tuple[bool, str]:
+ """处理带有分号的表达式,将其转换为不带分号的简化形式
+
+ Args:
+ expression: 要处理的表达式字符串
+
+ Returns:
+ Tuple[bool, str]: (是否成功, 转换后的表达式或错误信息)
+ """
+ def _top_level_equals_positions(stmt: str) -> List[int]:
+ """返回所有“顶层赋值”等号位置。
+
+ 仅统计括号外(()[]{})、引号外、且不属于比较操作符(==,!=,<=,>=)的 '='。
+ 这样可以避免把关键字参数(如 rettype=0)误判为赋值语句。
+ """
+ positions: List[int] = []
+ paren_depth = 0
+ bracket_depth = 0
+ brace_depth = 0
+ in_single_quote = False
+ in_double_quote = False
+ escape = False
+
+ for i, ch in enumerate(stmt):
+ if escape:
+ escape = False
+ continue
+ if ch == '\\':
+ escape = True
+ continue
+
+ if in_single_quote:
+ if ch == "'":
+ in_single_quote = False
+ continue
+ if in_double_quote:
+ if ch == '"':
+ in_double_quote = False
+ continue
+
+ if ch == "'":
+ in_single_quote = True
+ continue
+ if ch == '"':
+ in_double_quote = True
+ continue
+
+ if ch == '(':
+ paren_depth += 1
+ continue
+ if ch == ')':
+ paren_depth = max(0, paren_depth - 1)
+ continue
+ if ch == '[':
+ bracket_depth += 1
+ continue
+ if ch == ']':
+ bracket_depth = max(0, bracket_depth - 1)
+ continue
+ if ch == '{':
+ brace_depth += 1
+ continue
+ if ch == '}':
+ brace_depth = max(0, brace_depth - 1)
+ continue
+
+ if paren_depth or bracket_depth or brace_depth:
+ continue
+
+ if ch != '=':
+ continue
+
+ prev_ch = stmt[i - 1] if i > 0 else ''
+ next_ch = stmt[i + 1] if i + 1 < len(stmt) else ''
+ if prev_ch in ['=', '!', '<', '>'] or next_ch == '=':
+ continue
+
+ positions.append(i)
+
+ return positions
+
+ def _keyword_arg_names(stmt: str):
+ """提取函数调用中的命名参数名(如 rettype=0 中的 rettype)。
+
+ 只收集括号/中括号/大括号内部出现的 name= 形式,避免把脚本级赋值误当作命名参数。
+ """
+ names = set()
+ paren_depth = 0
+ bracket_depth = 0
+ brace_depth = 0
+ in_single_quote = False
+ in_double_quote = False
+ escape = False
+
+ i = 0
+ while i < len(stmt):
+ ch = stmt[i]
+
+ if escape:
+ escape = False
+ i += 1
+ continue
+ if ch == '\\':
+ escape = True
+ i += 1
+ continue
+
+ if in_single_quote:
+ if ch == "'":
+ in_single_quote = False
+ i += 1
+ continue
+ if in_double_quote:
+ if ch == '"':
+ in_double_quote = False
+ i += 1
+ continue
+
+ if ch == "'":
+ in_single_quote = True
+ i += 1
+ continue
+ if ch == '"':
+ in_double_quote = True
+ i += 1
+ continue
+
+ if ch == '(':
+ paren_depth += 1
+ i += 1
+ continue
+ if ch == ')':
+ paren_depth = max(0, paren_depth - 1)
+ i += 1
+ continue
+ if ch == '[':
+ bracket_depth += 1
+ i += 1
+ continue
+ if ch == ']':
+ bracket_depth = max(0, bracket_depth - 1)
+ i += 1
+ continue
+ if ch == '{':
+ brace_depth += 1
+ i += 1
+ continue
+ if ch == '}':
+ brace_depth = max(0, brace_depth - 1)
+ i += 1
+ continue
+
+ inside_container = bool(paren_depth or bracket_depth or brace_depth)
+
+ if inside_container and (ch.isalpha() or ch == '_'):
+ start = i
+ i += 1
+ while i < len(stmt) and (stmt[i].isalnum() or stmt[i] == '_'):
+ i += 1
+ name = stmt[start:i]
+
+ j = i
+ while j < len(stmt) and stmt[j].isspace():
+ j += 1
+
+ if j < len(stmt) and stmt[j] == '=':
+ next_ch = stmt[j + 1] if j + 1 < len(stmt) else ''
+ if next_ch != '=':
+ names.add(name.lower())
+ continue
+
+ i += 1
+
+ return names
+
+ # 检查表达式是否以分号结尾
+ if expression.strip().endswith(';'):
+ return False, "表达式不能以分号结尾"
+
+ # 分割表达式为语句列表
+ statements = [stmt.strip() for stmt in expression.split(';') if stmt.strip()]
+ if not statements:
+ return False, "表达式不能为空"
+
+ # 存储变量赋值
+ variables = {}
+
+ # 处理每个赋值语句(除了最后一个)
+ for i, stmt in enumerate(statements[:-1]):
+ eq_positions = _top_level_equals_positions(stmt)
+ if not eq_positions:
+ return False, f"第{i+1}个语句必须是赋值语句(使用=符号)"
+ if len(eq_positions) > 1:
+ return False, f"第{i+1}个语句只能包含一个赋值符号(=)"
+
+ real_equals_pos = eq_positions[0]
+
+ # 分割变量名和值
+ var_name = stmt[:real_equals_pos].strip()
+ var_value = stmt[real_equals_pos + 1:].strip()
+
+ # 检查变量名是否有效
+ if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_name):
+ return False, f"第{i+1}个语句的变量名'{var_name}'无效,只能包含字母、数字和下划线,且不能以数字开头"
+
+ var_name_lower = var_name.lower() # 变量名不区分大小写
+
+ # 检查变量名是否在后续表达式中使用
+ # 这里不需要,因为后面的表达式会检查
+
+ # 检查变量值中使用的变量是否已经定义
+ # 简单检查:提取所有可能的变量名
+ kw_names = _keyword_arg_names(var_value)
+ used_vars = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', var_value)
+ for used_var in used_vars:
+ used_var_lower = used_var.lower()
+ if used_var_lower in kw_names:
+ continue
+ if used_var_lower not in variables:
+ # 检查是否是函数名
+ if used_var not in supported_functions:
+ # 对于单个字母或简单单词,不自动视为字段名,要求先定义
+ if len(used_var) <= 2:
+ return False, f"第{i+1}个语句中使用的变量'{used_var}'未在之前定义"
+ # 对于较长的字段名,仍然允许作为字段名
+ elif not self._is_valid_field(used_var):
+ return False, f"第{i+1}个语句中使用的变量'{used_var}'未在之前定义"
+
+ # 将之前定义的变量替换到当前值中
+ for existing_var, existing_val in variables.items():
+ # 使用单词边界匹配,避免替换到其他单词的一部分
+ var_value = re.sub(rf'\b{existing_var}\b', existing_val, var_value)
+
+ # 存储变量
+ variables[var_name_lower] = var_value
+
+ # 处理最后一个语句(实际的表达式)
+ final_stmt = statements[-1]
+
+ # 检查最后一个语句是否是赋值语句
+ if _top_level_equals_positions(final_stmt):
+ return False, "最后一个语句不能是赋值语句"
+
+ # 检查最后一个语句中使用的变量是否已经定义
+ kw_names = _keyword_arg_names(final_stmt)
+ used_vars = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', final_stmt)
+ for used_var in used_vars:
+ used_var_lower = used_var.lower()
+ if used_var_lower in kw_names:
+ continue
+ if used_var_lower not in variables:
+ # 检查是否是函数名
+ if used_var not in supported_functions:
+ # 对于单个字母或简单单词,不自动视为字段名,要求先定义
+ if len(used_var) <= 2:
+ return False, f"最后一个语句中使用的变量'{used_var}'未在之前定义"
+
+ # 允许直接使用字段名/类别名(如 close/industry)
+ if self._is_valid_field(used_var) or used_var_lower in valid_categories or used_var_lower in group_fields:
+ continue
+
+ return False, f"最后一个语句中使用的变量'{used_var}'未在之前定义"
+
+ # 将变量替换到最后一个表达式中
+ final_expr = final_stmt
+ for var_name, var_value in variables.items():
+ final_expr = re.sub(rf'\b{var_name}\b', var_value, final_expr)
+
+ return True, final_expr
+
+ def check_expression(self, expression: str) -> Dict[str, Any]:
+ """
+ 检查表达式格式是否正确
+
+ Args:
+ expression: 要验证的表达式字符串
+
+ Returns:
+ 包含验证结果的字典
+ """
+ # 重置错误列表
+ self.errors = []
+ # Reset unit inference cache for this expression
+ self._unit_cache = {}
+ self._derived_category_cache = {}
+
+ try:
+ expression = expression.strip()
+ if not expression:
+ return {
+ 'valid': False,
+ 'errors': ['表达式不能为空'],
+ 'tokens': [],
+ 'ast': None
+ }
+
+ # 处理带有分号的表达式
+ if ';' in expression:
+ success, result = self._process_semicolon_expression(expression)
+ if not success:
+ return {
+ 'valid': False,
+ 'errors': [result],
+ 'tokens': [],
+ 'ast': None
+ }
+ expression = result
+
+ # 重置词法分析器的行号
+ self.lexer.lineno = 1
+
+ # 词法分析(用于调试)
+ self.lexer.input(expression)
+ tokens = []
+ # 调试:打印识别的标记
+ # print(f"\n调试 - 表达式: {expression}")
+ # print("识别的标记:")
+ for token in self.lexer:
+ # print(f" - 类型: {token.type}, 值: '{token.value}', 位置: {token.lexpos}")
+ tokens.append(token)
+
+ # 重新设置词法分析器的输入,以便语法分析器使用
+ self.lexer.input(expression)
+ self.lexer.lineno = 1
+
+ # 语法分析
+ ast = self.parser.parse(expression, lexer=self.lexer)
+
+ # 验证AST
+ validation_errors = self.validate_ast(ast)
+
+ # 合并所有错误
+ all_errors = self.errors + validation_errors
+
+ # 检查括号是否匹配
+ bracket_count = 0
+ for char in expression:
+ if char == '(':
+ bracket_count += 1
+ elif char == ')':
+ bracket_count -= 1
+ if bracket_count < 0:
+ all_errors.append("括号不匹配: 右括号过多")
+ break
+ if bracket_count > 0:
+ all_errors.append("括号不匹配: 左括号过多")
+
+ return {
+ 'valid': len(all_errors) == 0,
+ 'errors': all_errors,
+ 'tokens': tokens,
+ 'ast': ast
+ }
+ except Exception as e:
+ return {
+ 'valid': False,
+ 'errors': [f"解析错误: {str(e)}"],
+ 'tokens': [],
+ 'ast': None
+ }
+
+
+
diff --git a/simple72/__init__.py b/simple72/__init__.py
new file mode 100644
index 0000000..e38997d
--- /dev/null
+++ b/simple72/__init__.py
@@ -0,0 +1 @@
+# Alpha Transformer -
diff --git a/simple72/config.json b/simple72/config.json
new file mode 100644
index 0000000..5cbfe58
--- /dev/null
+++ b/simple72/config.json
@@ -0,0 +1,15 @@
+{
+ "brain": {
+ "username": "jack0210_@hotmail.com",
+ "password": "!QAZ2wsx+0913"
+ },
+ "llm": {
+ "api_key": "sk-cp-l_as8mjqPhsOIny9IFKZ8jzA92z1c0eRwchldhEf4KzQjs9cjVknV2o7VNCcvYUXsXFq7uF4aSgp2RxxmUHLXwPGKgIvzedM70_XUIXiBB3gu_UmLDQLfh4",
+ "base_url": "https://api.minimaxi.com/v1",
+ "model": "MiniMax-M2.7"
+ },
+ "transformer": {
+ "top_n_datafield": 30,
+ "data_type": "MATRIX"
+ }
+}
diff --git a/simple72/config.json.example b/simple72/config.json.example
new file mode 100644
index 0000000..5a01353
--- /dev/null
+++ b/simple72/config.json.example
@@ -0,0 +1,15 @@
+{
+ "brain": {
+ "username": "your_brain_username",
+ "password": "your_brain_password"
+ },
+ "llm": {
+ "api_key": "your_llm_api_key",
+ "base_url": "https://api.moonshot.cn/v1",
+ "model": "kimi-k2.5"
+ },
+ "transformer": {
+ "top_n_datafield": 50,
+ "data_type": "MATRIX"
+ }
+}
diff --git a/simple72/main.py b/simple72/main.py
new file mode 100644
index 0000000..05a69f6
--- /dev/null
+++ b/simple72/main.py
@@ -0,0 +1,404 @@
+# FastAPI 应用主入口
+from fastapi import FastAPI, Request
+from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse, FileResponse
+from fastapi.staticfiles import StaticFiles
+from fastapi.templating import Jinja2Templates
+import sys
+import os
+import json
+import subprocess
+import uuid
+import asyncio
+import zipfile
+from datetime import datetime
+
+# 创建 FastAPI 应用实例
+app = FastAPI(title="Alpha Transformer", version="1.0.0")
+
+# Chrome DevTools 健康检查端点(可选,阻止 404 日志)
+@app.get("/.well-known/appspecific/com.chrome.devtools.json")
+async def chrome_devtools_check():
+ """Chrome DevTools 健康检查"""
+ return {"status": "ok"}
+
+# 提供模板目录中的 CSS 和 JS 文件
+@app.get("/styles.css")
+async def get_styles():
+ """提供 styles.css 文件"""
+ from fastapi.responses import FileResponse
+ return FileResponse("templates/styles.css")
+
+@app.get("/app.js")
+async def get_app_js():
+ """提供 app.js 文件"""
+ from fastapi.responses import FileResponse
+ return FileResponse("templates/app.js")
+
+# 配置 Jinja2 模板引擎,用于渲染 HTML 页面
+templates = Jinja2Templates(directory="templates")
+
+# 存储正在运行的任务信息(task_id -> 任务状态)
+transformer_tasks = {}
+
+# 全局配置变量
+app_config = {}
+
+def load_config():
+ """
+ 加载配置文件
+ 读取 config.json 文件,如果有则加载,否则返回空字典
+ """
+ config_path = os.path.join(os.path.dirname(__file__), 'config.json')
+ if os.path.exists(config_path):
+ try:
+ with open(config_path, 'r', encoding='utf-8') as f:
+ config = json.load(f)
+ print(f"✓ 已加载配置文件: {config_path}")
+ return config
+ except Exception as e:
+ print(f"⚠ 加载配置文件失败: {e}")
+ return {}
+ else:
+ print(f"⚠ 配置文件不存在: {config_path}")
+ return {}
+
+# 启动时加载配置
+app_config = load_config()
+
+
+@app.get("/", response_class=HTMLResponse)
+async def home():
+ # 读取并返回前端首页 HTML
+ with open("templates/index.html", "r", encoding="utf-8") as f:
+ return f.read()
+
+
+@app.get("/api/config/defaults")
+async def get_config_defaults():
+ """
+ 获取默认配置
+ 返回 config.json 中的配置作为表单默认值
+ """
+ return JSONResponse(content={
+ "success": True,
+ "config": app_config
+ })
+
+
+@app.post("/api/config/save")
+async def save_config(request: Request):
+ """
+ 保存配置到 config.json
+ 用于在页面上修改配置后保存
+ """
+ try:
+ data = await request.json()
+
+ # 更新全局配置
+ global app_config
+ app_config = data
+
+ # 写入文件
+ config_path = os.path.join(os.path.dirname(__file__), 'config.json')
+ with open(config_path, 'w', encoding='utf-8') as f:
+ json.dump(data, f, indent=4, ensure_ascii=False)
+
+ return JSONResponse(content={
+ "success": True,
+ "message": "配置已保存"
+ })
+ except Exception as e:
+ return JSONResponse(
+ status_code=500,
+ content={"success": False, "error": str(e)}
+ )
+
+
+@app.post("/api/generate")
+async def generate_alpha(request: Request):
+ """
+ 生成 Alpha 变种的 API 端点
+ 接收前端表单数据,启动 Transformer 脚本执行 Alpha 生成任务
+ """
+ print("=" * 50)
+ print("收到生成变种请求")
+
+ try:
+ # 解析请求数据
+ data = await request.json()
+ print(f"请求数据: alpha_id={data.get('alpha_id')}, llm_model={data.get('llm_model')}")
+
+ # 生成唯一任务 ID
+ task_id = str(uuid.uuid4())
+ print(f"生成任务 ID: {task_id}")
+
+ # 定义必须提交的字段
+ required_fields = [
+ "alpha_id",
+ "llm_api_key",
+ "llm_base_url",
+ "llm_model",
+ "brain_username",
+ "brain_password"
+ ]
+
+ # 检查必填字段是否完整
+ for field in required_fields:
+ if not data.get(field):
+ print(f"缺少必填字段: {field}")
+ return JSONResponse(
+ status_code=400,
+ content={"success": False, "error": f"Missing required field: {field}"}
+ )
+
+ # 获取脚本所在目录和 Transformer 子目录
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ transformer_dir = os.path.join(script_dir, 'Tranformer')
+ print(f"Transformer 目录: {transformer_dir}")
+
+ # 构建传递给 Transformer 脚本的配置
+ config = {
+ "LLM_model_name": data.get('llm_model'),
+ "LLM_API_KEY": data.get('llm_api_key'),
+ "llm_base_url": data.get('llm_base_url'),
+ "username": data.get('brain_username'),
+ "password": data.get('brain_password'),
+ "alpha_id": data.get('alpha_id'),
+ "top_n_datafield": int(data.get('top_n_datafield', 50)),
+ "user_region": data.get('user_region'),
+ "user_universe": data.get('user_universe'),
+ "user_delay": int(data.get('user_delay')) if data.get('user_delay') else None,
+ "user_category": data.get('user_category'),
+ "user_data_type": data.get('user_data_type', 'MATRIX')
+ }
+ print(f"配置已构建: LLM_model={config['LLM_model_name']}, alpha_id={config['alpha_id']}")
+
+ # 将配置写入临时 JSON 文件,供 Transformer 脚本读取
+ config_path = os.path.join(transformer_dir, f'config_{task_id}.json')
+ with open(config_path, 'w', encoding='utf-8') as f:
+ json.dump(config, f, indent=4)
+ print(f"配置文件已写入: {config_path}")
+
+ try:
+ # 启动 Transformer.py 子进程执行 Alpha 生成
+ print(f"启动 Transformer 脚本...")
+ process = subprocess.run(
+ [sys.executable, '-u', os.path.join(transformer_dir, 'Transformer.py'), config_path],
+ cwd=transformer_dir,
+ capture_output=True,
+ text=True,
+ timeout=600,
+ env={**os.environ, "PYTHONIOENCODING": "utf-8"}
+ )
+ print(f"Transformer 脚本执行完成,返回码: {process.returncode}")
+
+ # 定义输出文件路径
+ output_file = os.path.join(transformer_dir, 'output', 'Alpha_generated_expressions_success.json')
+ candidates_file = os.path.join(transformer_dir, 'output', 'Alpha_candidates.json')
+ error_file = os.path.join(transformer_dir, 'output', 'Alpha_generated_expressions_error.json')
+
+ # 构建响应数据
+ result = {
+ "success": True,
+ "alpha_id": data.get('alpha_id'),
+ "stdout": process.stdout,
+ "stderr": process.stderr,
+ "return_code": process.returncode
+ }
+
+ # 读取成功生成的表达式
+ if os.path.exists(output_file):
+ print(f"读取成功表达式文件: {output_file}")
+ with open(output_file, 'r', encoding='utf-8') as f:
+ result['expressions_success'] = json.load(f)
+ else:
+ print(f"成功表达式文件不存在: {output_file}")
+ result['expressions_success'] = []
+
+ # 读取候选表达式
+ if os.path.exists(candidates_file):
+ print(f"读取候选表达式文件: {candidates_file}")
+ with open(candidates_file, 'r', encoding='utf-8') as f:
+ result['candidates'] = json.load(f)
+ else:
+ print(f"候选表达式文件不存在: {candidates_file}")
+ result['candidates'] = []
+
+ # 读取生成失败的表达式
+ if os.path.exists(error_file):
+ print(f"读取错误表达式文件: {error_file}")
+ with open(error_file, 'r', encoding='utf-8') as f:
+ result['expressions_error'] = json.load(f)
+ else:
+ print(f"错误表达式文件不存在: {error_file}")
+ result['expressions_error'] = []
+
+ print(f"成功: {len(result['expressions_success'])} 个, 候选: {len(result['candidates'])} 个, 错误: {len(result['expressions_error'])} 个")
+ print("=" * 50)
+ return JSONResponse(content=result)
+
+ finally:
+ # 清理临时配置文件
+ if os.path.exists(config_path):
+ os.remove(config_path)
+ print(f"已清理临时配置文件: {config_path}")
+
+ except subprocess.TimeoutExpired:
+ print("任务执行超时 (600秒)")
+ return JSONResponse(
+ status_code=408,
+ content={"success": False, "error": "Task timeout (600s)"}
+ )
+ except Exception as e:
+ print(f"执行异常: {str(e)}")
+ return JSONResponse(
+ status_code=500,
+ content={"success": False, "error": str(e)}
+ )
+
+
+@app.post("/api/transformer/login-and-fetch-options")
+async def login_and_fetch_options(request: Request):
+ """
+ 登录 BRAIN 并获取地区、Delay、Universe、类别等选项
+ 用于填充高级选项表单
+ """
+ try:
+ data = await request.json()
+ username = data.get('username')
+ password = data.get('password')
+
+ if not username or not password:
+ return JSONResponse(
+ status_code=400,
+ content={'success': False, 'error': 'Username and password are required'}
+ )
+
+ # 添加 Transformer 目录到 sys.path
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ transformer_dir = os.path.join(script_dir, 'Tranformer')
+ if transformer_dir not in sys.path:
+ sys.path.append(transformer_dir)
+
+ # 导入必要的模块
+ from Tranformer.ace_lib import SingleSession, get_instrument_type_region_delay
+ import pandas as pd
+
+ # 创建新的会话实例
+ session = SingleSession()
+ session.auth = (username, password)
+
+ brain_api_url = "https://api.worldquantbrain.com"
+ response = session.post(brain_api_url + "/authentication")
+
+ if response.status_code == 201:
+ # 认证成功
+ pass
+ elif response.status_code == 401:
+ return JSONResponse(
+ status_code=401,
+ content={'success': False, 'error': 'Authentication failed: Invalid credentials'}
+ )
+ else:
+ return JSONResponse(
+ status_code=400,
+ content={'success': False, 'error': f'Authentication failed: {response.status_code}'}
+ )
+
+ # 获取 region/delay/universe 选项
+ df = get_instrument_type_region_delay(session)
+
+ # 获取数据类别
+ categories_resp = session.get(brain_api_url + "/data-categories")
+ categories = []
+ if categories_resp.status_code == 200:
+ categories_data = categories_resp.json()
+ if isinstance(categories_data, list):
+ categories = categories_data
+ elif isinstance(categories_data, dict):
+ categories = categories_data.get('results', [])
+
+ # 转换 DataFrame 为前端需要的嵌套字典结构
+ # 结构: Region -> Delay -> Universe
+ df_equity = df[df['InstrumentType'] == 'EQUITY']
+
+ options = {}
+ for _, row in df_equity.iterrows():
+ region = row['Region']
+ delay = row['Delay']
+ universes = row['Universe'] # 这是一个列表
+
+ if region not in options:
+ options[region] = {}
+
+ # 将 delay 转换为字符串作为字典的键
+ delay_str = str(delay)
+ if delay_str not in options[region]:
+ options[region][delay_str] = universes
+
+ return JSONResponse(content={
+ 'success': True,
+ 'options': options,
+ 'categories': categories
+ })
+
+ except Exception as e:
+ print(f"登录获取选项失败: {str(e)}")
+ return JSONResponse(
+ status_code=500,
+ content={'success': False, 'error': str(e)}
+ )
+
+
+@app.get("/api/health")
+async def health_check():
+ """健康检查端点,用于验证服务是否正常运行"""
+ return {"status": "healthy", "service": "alpha-transformer"}
+
+
+@app.get("/api/download/{alpha_id}")
+async def download_results(alpha_id: str):
+ """
+ 下载生成结果的 zip 压缩包
+ 包含三个 JSON 文件:success, candidates, error
+ """
+ try:
+ transformer_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Tranformer')
+ output_dir = os.path.join(transformer_dir, 'output')
+
+ # 检查文件是否存在
+ files_to_zip = {
+ 'Alpha_generated_expressions_success.json': os.path.join(output_dir, 'Alpha_generated_expressions_success.json'),
+ 'Alpha_candidates.json': os.path.join(output_dir, 'Alpha_candidates.json'),
+ 'Alpha_generated_expressions_error.json': os.path.join(output_dir, 'Alpha_generated_expressions_error.json')
+ }
+
+ # 生成时间戳
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+ zip_filename = f"{alpha_id}_{timestamp}.zip"
+ zip_path = os.path.join(output_dir, zip_filename)
+
+ # 创建 zip 文件
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
+ for arcname, filepath in files_to_zip.items():
+ if os.path.exists(filepath):
+ zipf.write(filepath, arcname)
+
+ # 返回文件
+ return FileResponse(
+ zip_path,
+ media_type='application/zip',
+ filename=zip_filename
+ )
+
+ except Exception as e:
+ return JSONResponse(
+ status_code=500,
+ content={"success": False, "error": str(e)}
+ )
+
+
+if __name__ == "__main__":
+ # 启动 FastAPI 应用,使用 uvicorn 作为 ASGI 服务器
+ import uvicorn
+ uvicorn.run(app, host="0.0.0.0", port=8000)
diff --git a/simple72/requirements.txt b/simple72/requirements.txt
new file mode 100644
index 0000000..16a8f4a
--- /dev/null
+++ b/simple72/requirements.txt
@@ -0,0 +1,7 @@
+fastapi>=0.100.0
+uvicorn>=0.20.0
+requests>=2.28.0
+openai>=1.0.0
+pandas>=2.0.0
+pydantic>=2.0.0
+jinja2>=3.0.0
diff --git a/simple72/running_error.txt b/simple72/running_error.txt
new file mode 100644
index 0000000..5255c86
--- /dev/null
+++ b/simple72/running_error.txt
@@ -0,0 +1,10 @@
+{
+ "success": true,
+ "alpha_id": "MP5gWQva",
+ "stdout": "✓ 已加载模板总结文件: /Users/jack/source/mySpace/mycode/my_project/py/alpha_odoo/alpha_tools/simple72/Tranformer/template_summary.md\n✓ 已从命令行参数加载配置: /Users/jack/source/mySpace/mycode/my_project/py/alpha_odoo/alpha_tools/simple72/Tranformer/config_51a505a0-322f-45f2-9180-98c25b2e731c.json\n✓ 使用内置模板总结\n--- 正在启动 BRAIN 会话... ---\nNew session created (ID: 12659217744) with authentication response: 201, {'user': {'id': 'YC93384'}, 'token': {'expiry': 14400.0}, 'permissions': ['BEFORE_AND_AFTER_PERFORMANCE_V2', 'BRAIN_LABS', 'BRAIN_LABS_JUPYTER_LAB', 'CONSULTANT', 'MULTI_SIMULATION', 'PROD_ALPHAS', 'REFERRAL', 'VISUALIZATION', 'WORKDAY']} (新会话已创建)\n--- 正在认证 LLM Gateway... ---\n✓ LLM Gateway 认证成功\n\n--- 正在获取 Alpha ID: MP5gWQva 的详情... ---\nNew session created (ID: 12659217744) with authentication response: 201, {'user': {'id': 'YC93384'}, 'token': {'expiry': 14400.0}, 'permissions': ['BEFORE_AND_AFTER_PERFORMANCE_V2', 'BRAIN_LABS', 'BRAIN_LABS_JUPYTER_LAB', 'CONSULTANT', 'MULTI_SIMULATION', 'PROD_ALPHAS', 'REFERRAL', 'VISUALIZATION', 'WORKDAY']} (新会话已创建)\nstatus_code 429, sleep 3 seconds\nLLM Gateway Authentication successful. (LLM网关认证成功)\n--- Calling LLM to propose templates... (正在调用LLM生成模板...) ---\nLLM Gateway Authentication successful. (LLM网关认证成功)\n--- Calling LLM to propose templates... (正在调用LLM生成模板...) ---\nAlpha MP5gWQva description updated on platform. (Alpha描述已在平台更新)\nNew session created (ID: 12659217744) with authentication response: 201, {'user': {'id': 'YC93384'}, 'token': {'expiry': 14400.0}, 'permissions': ['BEFORE_AND_AFTER_PERFORMANCE_V2', 'BRAIN_LABS', 'BRAIN_LABS_JUPYTER_LAB', 'CONSULTANT', 'MULTI_SIMULATION', 'PROD_ALPHAS', 'REFERRAL', 'VISUALIZATION', 'WORKDAY']} (新会话已创建)\n✓ LLM Gateway 认证成功\nAlpha Details Retrieved (已获取Alpha详情):\n{\n \"settings\": {\n \"instrumentType\": \"EQUITY\",\n \"region\": \"IND\",\n \"universe\": \"TOP500\",\n \"delay\": 1,\n \"decay\": 12,\n \"neutralization\": \"SLOW_AND_FAST\",\n \"truncation\": 0.02,\n \"pasteurization\": \"ON\",\n \"unitHandling\": \"VERIFY\",\n \"nanHandling\": \"ON\",\n \"maxTrade\": \"OFF\",\n \"maxPosition\": \"OFF\",\n \"language\": \"FASTEXPR\",\n \"visualization\": false,\n \"startDate\": \"2014-01-01\",\n \"endDate\": \"2023-12-31\"\n },\n \"expression\": {\n \"code\": \"divide(avg_pct_change_estimate_next_year_earnings_7d, add(analysts_count_revising_up_quarter2_earnings_30d, 0.0001))\",\n \"description\": \"{\\n \\\"text\\\": \\\"\\\\nWe need to generate a new, improved description for the alpha code.\\\\n\\\\nThe code:\\\\n\\\\ndivide(avg_pct_change_estimate_next_year_earnings_7d, add(analysts_count_revising_up_quarter2_earnings_30d, 0.0001))\\\\n\\\\nSo the alpha is dividing the average percent change in next-year earnings estimates over the past 7 days by the number of analysts revising up Q2 earnings over the last 30 days plus a small constant.\\\\n\\\\nWe need to produce an improved description: explain investment idea, rationale for data used, rationale for operators used.\\\\n\\\\nWe need to format as:\\\\n\\\\n\\\\\\\"Idea: xxxxx\\\\\\\\nRationale for data used: xxxxx\\\\\\\\nRationale for operators used: xxxxx\\\\\\\"\\\\n\\\\nWe should produce a description that clarifies the alpha: The alpha tries to measure the momentum in earnings estimate revisions relative to the breadth of analyst revisions, possibly indicating the strength of upward sentiment. By dividing the short-term (7d) average percentage change in next-year earnings estimates by the count of analysts revising up Q2 earnings (with a small floor), it normalizes the magnitude of estimate changes by the number of analysts, adjusting for market breadth. The small constant avoids division by zero.\\\\n\\\\nRationale for data used: avg_pct_change_estimate_next_year_earnings_7d captures recent changes in forward earnings expectations; analysts_count_revising_up_quarter2_earnings_30d captures recent positive revisions for near-term quarter; using next-year vs quarter provides longer horizon vs near-term; combining them reflects both magnitude and breadth.\\\\n\\\\nRationale for operators: divide normalizes the magnitude by breadth; add ensures non-zero denominator; maybe we can mention the constant 0.0001 is to avoid division by zero.\\\\n\\\\nThe description should be clear and concise, suitable for a quantitative alpha description.\\\\n\\\\nPotential nuance: The alpha may be interpreted as a measure of how large the per-analyst estimate change is, relative to the number of analysts revising up; if many analysts revise up but the average change is small, the ratio may be low; if a few analysts raise estimates dramatically, the ratio may be high. This can signal either concentrated strong conviction or broad moderate revisions.\\\\n\\\\nWe might also mention that the 0.0001 constant ensures stability and prevents division by zero while having negligible effect on scaling.\\\\n\\\\nWe need to incorporate the idea that the alpha uses a small denominator offset to avoid extreme values.\\\\n\\\\nThus the description will be something like:\\\\n\\\\nIdea: The alpha captures the intensity of recent upward revisions in earnings estimates by dividing the short\\\\u2011term percentage change in next\\\\u2011year consensus earnings by the count of analysts raising Q2 earnings. It thus reflects the average magnitude of upward revisions per revising analyst, combining the speed of estimate moves with the breadth of positive revisions.\\\\n\\\\nRationale for data used: ... etc.\\\\n\\\\nRationale for operators used: ... etc.\\\\n\\\\nLet's produce that. Use proper punctuation.\\\\n\\\\nMake sure to mention the constant 0.0001 for stability.\\\\n\\\\nOk.\\\\n\\\\n\\\\nIdea: The alpha measures the intensity of recent upward earnings revisions by taking the short\\\\u2011term percentage change in consensus estimates for the next fiscal year and scaling it by the breadth of analysts that have raised their second\\\\u2011quarter estimates. In other words, it reflects how large the average upward revision is per revising analyst, combining the momentum of estimate changes with the number of analysts turning bullish.\\\\n\\\\nRationale for data used: \\\\n- **avg_pct_change_estimate_next_year_earnings_7d** captures the most recent (7\\\\u2011day) directional momentum in forward\\\\u2011year earnings expectations, giving a timely signal of how fast analysts are revising their views. \\\\n- **analysts_count_revising_up_quarter2_earnings_30d** measures the breadth of positive sentiment over a slightly longer (30\\\\u2011day) window for the nearer\\\\u2011term quarter, indicating how many analysts are turning optimistic. By pairing a long\\\\u2011horizon momentum metric with a near\\\\u2011term breadth metric, the alpha blends the strength of the revision trend with the consensus behind it.\\\\n\\\\nRationale for operators used: \\\\n- **divide** normalizes the magnitude of the estimate change by the count of revising analysts, producing a \\\\u201cper\\\\u2011analyst\\\\u201d revision intensity that is comparable across stocks regardless of how many analysts cover them. \\\\n- **add(..., 0.0001)** introduces a tiny constant to the denominator to avoid division\\\\u2011by\\\\u2011zero when no analysts have revised up, ensuring numerical stability without materially affecting the ratio\\\\u2019s scaling. The small offset is negligible in normal conditions but prevents extreme values or errors in thin\\\\u2011coverage names.\\\"\\n}\",\n \"operatorCount\": 2\n }\n}\n\n============================================================\n[Step 2/5] 正在生成 Alpha 模板提议...\n============================================================\ncurrent seed alpha detail (当前种子Alpha详情): {'code': 'divide(avg_pct_change_estimate_next_year_earnings_7d, add(analysts_count_revising_up_quarter2_earnings_30d, 0.0001))', 'description': '{\\n \"text\": \"\\\\nWe need to generate a new, improved description for the alpha code.\\\\n\\\\nThe code:\\\\n\\\\ndivide(avg_pct_change_estimate_next_year_earnings_7d, add(analysts_count_revising_up_quarter2_earnings_30d, 0.0001))\\\\n\\\\nSo the alpha is dividing the average percent change in next-year earnings estimates over the past 7 days by the number of analysts revising up Q2 earnings over the last 30 days plus a small constant.\\\\n\\\\nWe need to produce an improved description: explain investment idea, rationale for data used, rationale for operators used.\\\\n\\\\nWe need to format as:\\\\n\\\\n\\\\\"Idea: xxxxx\\\\\\\\nRationale for data used: xxxxx\\\\\\\\nRationale for operators used: xxxxx\\\\\"\\\\n\\\\nWe should produce a description that clarifies the alpha: The alpha tries to measure the momentum in earnings estimate revisions relative to the breadth of analyst revisions, possibly indicating the strength of upward sentiment. By dividing the short-term (7d) average percentage change in next-year earnings estimates by the count of analysts revising up Q2 earnings (with a small floor), it normalizes the magnitude of estimate changes by the number of analysts, adjusting for market breadth. The small constant avoids division by zero.\\\\n\\\\nRationale for data used: avg_pct_change_estimate_next_year_earnings_7d captures recent changes in forward earnings expectations; analysts_count_revising_up_quarter2_earnings_30d captures recent positive revisions for near-term quarter; using next-year vs quarter provides longer horizon vs near-term; combining them reflects both magnitude and breadth.\\\\n\\\\nRationale for operators: divide normalizes the magnitude by breadth; add ensures non-zero denominator; maybe we can mention the constant 0.0001 is to avoid division by zero.\\\\n\\\\nThe description should be clear and concise, suitable for a quantitative alpha description.\\\\n\\\\nPotential nuance: The alpha may be interpreted as a measure of how large the per-analyst estimate change is, relative to the number of analysts revising up; if many analysts revise up but the average change is small, the ratio may be low; if a few analysts raise estimates dramatically, the ratio may be high. This can signal either concentrated strong conviction or broad moderate revisions.\\\\n\\\\nWe might also mention that the 0.0001 constant ensures stability and prevents division by zero while having negligible effect on scaling.\\\\n\\\\nWe need to incorporate the idea that the alpha uses a small denominator offset to avoid extreme values.\\\\n\\\\nThus the description will be something like:\\\\n\\\\nIdea: The alpha captures the intensity of recent upward revisions in earnings estimates by dividing the short\\\\u2011term percentage change in next\\\\u2011year consensus earnings by the count of analysts raising Q2 earnings. It thus reflects the average magnitude of upward revisions per revising analyst, combining the speed of estimate moves with the breadth of positive revisions.\\\\n\\\\nRationale for data used: ... etc.\\\\n\\\\nRationale for operators used: ... etc.\\\\n\\\\nLet\\'s produce that. Use proper punctuation.\\\\n\\\\nMake sure to mention the constant 0.0001 for stability.\\\\n\\\\nOk.\\\\n\\\\n\\\\nIdea: The alpha measures the intensity of recent upward earnings revisions by taking the short\\\\u2011term percentage change in consensus estimates for the next fiscal year and scaling it by the breadth of analysts that have raised their second\\\\u2011quarter estimates. In other words, it reflects how large the average upward revision is per revising analyst, combining the momentum of estimate changes with the number of analysts turning bullish.\\\\n\\\\nRationale for data used: \\\\n- **avg_pct_change_estimate_next_year_earnings_7d** captures the most recent (7\\\\u2011day) directional momentum in forward\\\\u2011year earnings expectations, giving a timely signal of how fast analysts are revising their views. \\\\n- **analysts_count_revising_up_quarter2_earnings_30d** measures the breadth of positive sentiment over a slightly longer (30\\\\u2011day) window for the nearer\\\\u2011term quarter, indicating how many analysts are turning optimistic. By pairing a long\\\\u2011horizon momentum metric with a near\\\\u2011term breadth metric, the alpha blends the strength of the revision trend with the consensus behind it.\\\\n\\\\nRationale for operators used: \\\\n- **divide** normalizes the magnitude of the estimate change by the count of revising analysts, producing a \\\\u201cper\\\\u2011analyst\\\\u201d revision intensity that is comparable across stocks regardless of how many analysts cover them. \\\\n- **add(..., 0.0001)** introduces a tiny constant to the denominator to avoid division\\\\u2011by\\\\u2011zero when no analysts have revised up, ensuring numerical stability without materially affecting the ratio\\\\u2019s scaling. The small offset is negligible in normal conditions but prevents extreme values or errors in thin\\\\u2011coverage names.\"\\n}', 'operatorCount': 2}\n\n[Step 1/5] 正在调用 LLM 生成 Alpha 模板...\n - 模型: MiniMax-M2.7\n - 数据类型: MATRIX\nAn error occurred while calling the LLM (调用LLM时发生错误): unhashable type: 'slice'\nFailed to generate proposed alpha templates. (生成提议模板失败)\n",
+ "stderr": "",
+ "return_code": 1,
+ "expressions_success": [],
+ "candidates": [],
+ "expressions_error": []
+}
\ No newline at end of file
diff --git a/simple72/templates/app.js b/simple72/templates/app.js
new file mode 100644
index 0000000..54f6bac
--- /dev/null
+++ b/simple72/templates/app.js
@@ -0,0 +1,290 @@
+document.addEventListener('DOMContentLoaded', async () => {
+ try {
+ const response = await fetch('/api/config/defaults');
+ const result = await response.json();
+
+ if (result.success && result.config) {
+ const config = result.config;
+
+ if (config.brain) {
+ if (config.brain.username) {
+ document.getElementById('brainUsername').value = config.brain.username;
+ }
+ if (config.brain.password) {
+ document.getElementById('brainPassword').value = config.brain.password;
+ }
+ }
+
+ if (config.llm) {
+ if (config.llm.api_key) {
+ document.getElementById('llmApiKey').value = config.llm.api_key;
+ }
+ if (config.llm.base_url) {
+ document.getElementById('llmBaseUrl').value = config.llm.base_url;
+ }
+ if (config.llm.model) {
+ document.getElementById('llmModel').value = config.llm.model;
+ }
+ }
+
+ if (config.transformer) {
+ if (config.transformer.top_n_datafield) {
+ document.getElementById('topNDatafield').value = config.transformer.top_n_datafield;
+ }
+ if (config.transformer.data_type) {
+ document.getElementById('dataType').value = config.transformer.data_type;
+ }
+ }
+ }
+ } catch (error) {
+ console.error('加载默认配置失败:', error);
+ }
+});
+
+const form = document.getElementById('transformerForm');
+const submitBtn = document.getElementById('submitBtn');
+const downloadBtn = document.getElementById('downloadBtn');
+const loginAndFetchBtn = document.getElementById('loginAndFetchBtn');
+const regionSelect = document.getElementById('region');
+const delaySelect = document.getElementById('delay');
+const universeSelect = document.getElementById('universe');
+const dataTypeSelect = document.getElementById('dataType');
+const categoryButtons = document.getElementById('category-buttons');
+
+let optionsData = {};
+
+dataTypeSelect.addEventListener('change', function() {
+ if (this.value === 'VECTOR') {
+ if (!confirm("请确保您输入的原型Alpha中正确地使用了vector operator,否则极容易造成数据类型错误!")) {
+ this.value = 'MATRIX';
+ }
+ }
+});
+
+loginAndFetchBtn.addEventListener('click', async () => {
+ const username = document.getElementById('brainUsername').value.trim();
+ const password = document.getElementById('brainPassword').value;
+
+ if (!username || !password) {
+ alert('请先填写BRAIN用户名和密码');
+ return;
+ }
+
+ loginAndFetchBtn.disabled = true;
+ loginAndFetchBtn.textContent = '正在登录...';
+
+ try {
+ const response = await fetch('/api/transformer/login-and-fetch-options', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password })
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ optionsData = result.options;
+
+ populateRegionSelect();
+ regionSelect.disabled = false;
+
+ if (result.categories) {
+ populateCategoryButtons(result.categories);
+ }
+
+ loginAndFetchBtn.textContent = '登录成功';
+ } else {
+ alert('登录失败: ' + result.error);
+ loginAndFetchBtn.disabled = false;
+ loginAndFetchBtn.textContent = '登录BRAIN并获取选项';
+ }
+ } catch (error) {
+ alert('登录出错: ' + error.message);
+ loginAndFetchBtn.disabled = false;
+ loginAndFetchBtn.textContent = '登录BRAIN并获取选项';
+ }
+});
+
+function populateRegionSelect() {
+ while (regionSelect.options.length > 1) {
+ regionSelect.remove(1);
+ }
+
+ const regions = Object.keys(optionsData);
+ regions.forEach(region => {
+ const option = document.createElement('option');
+ option.value = region;
+ option.textContent = region;
+ regionSelect.appendChild(option);
+ });
+}
+
+function populateCategoryButtons(categories) {
+ categories.forEach(category => {
+ const btn = document.createElement('button');
+ btn.type = 'button';
+ btn.dataset.value = category.id || category;
+ btn.textContent = category.name || category;
+ btn.onclick = function() { toggleCategory(this); };
+ btn.className = 'btn';
+ btn.style.cssText = 'padding: 4px 12px; font-size: 11px;';
+ categoryButtons.appendChild(btn);
+ });
+}
+
+function toggleCategory(btn) {
+ const allBtn = document.getElementById('cat-all');
+ const isAllBtn = (btn === allBtn);
+
+ if (isAllBtn) {
+ allBtn.style.backgroundColor = '#000080';
+ allBtn.style.color = 'white';
+
+ const otherBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
+ otherBtns.forEach(b => {
+ b.style.backgroundColor = '#c0c0c0';
+ b.style.color = 'black';
+ });
+ } else {
+ if (btn.style.backgroundColor === 'rgb(0, 0, 128)') {
+ btn.style.backgroundColor = '#c0c0c0';
+ btn.style.color = 'black';
+ } else {
+ btn.style.backgroundColor = '#000080';
+ btn.style.color = 'white';
+ }
+
+ const anySelected = categoryButtons.querySelectorAll('button:not(#cat-all)');
+ let hasSelected = false;
+ anySelected.forEach(b => {
+ if (b.style.backgroundColor === 'rgb(0, 0, 128)') {
+ hasSelected = true;
+ }
+ });
+
+ if (hasSelected) {
+ allBtn.style.backgroundColor = '#c0c0c0';
+ allBtn.style.color = 'black';
+ } else {
+ allBtn.style.backgroundColor = '#000080';
+ allBtn.style.color = 'white';
+ }
+ }
+}
+
+regionSelect.addEventListener('change', () => {
+ const selectedRegion = regionSelect.value;
+
+ delaySelect.innerHTML = '';
+ universeSelect.innerHTML = '';
+ delaySelect.disabled = true;
+ universeSelect.disabled = true;
+
+ if (selectedRegion && optionsData[selectedRegion]) {
+ const delays = Object.keys(optionsData[selectedRegion]);
+ delays.forEach(delay => {
+ const option = document.createElement('option');
+ option.value = delay;
+ option.textContent = delay;
+ delaySelect.appendChild(option);
+ });
+ delaySelect.disabled = false;
+ }
+});
+
+delaySelect.addEventListener('change', () => {
+ const selectedRegion = regionSelect.value;
+ const selectedDelay = delaySelect.value;
+
+ universeSelect.innerHTML = '';
+ universeSelect.disabled = true;
+
+ if (selectedRegion && selectedDelay && optionsData[selectedRegion][selectedDelay]) {
+ const universes = optionsData[selectedRegion][selectedDelay];
+ universes.forEach(universe => {
+ const option = document.createElement('option');
+ option.value = universe;
+ option.textContent = universe;
+ universeSelect.appendChild(option);
+ });
+ universeSelect.disabled = false;
+ }
+});
+
+form.addEventListener('submit', async (e) => {
+ e.preventDefault();
+
+ const formData = {
+ alpha_id: document.getElementById('alphaId').value.trim(),
+ llm_api_key: document.getElementById('llmApiKey').value.trim(),
+ llm_base_url: document.getElementById('llmBaseUrl').value.trim(),
+ llm_model: document.getElementById('llmModel').value.trim(),
+ brain_username: document.getElementById('brainUsername').value.trim(),
+ brain_password: document.getElementById('brainPassword').value.trim(),
+ top_n_datafield: parseInt(document.getElementById('topNDatafield').value) || 50,
+ data_type: document.getElementById('dataType').value || 'MATRIX'
+ };
+
+ const region = document.getElementById('region').value;
+ const delay = document.getElementById('delay').value;
+ const universe = document.getElementById('universe').value;
+
+ if (region) formData.user_region = region;
+ if (delay) formData.user_delay = parseInt(delay);
+ if (universe) formData.user_universe = universe;
+
+ const allBtn = document.getElementById('cat-all');
+ let selectedCategories = [];
+
+ if (allBtn.style.backgroundColor !== 'rgb(0, 0, 128)') {
+ const categoryBtns = categoryButtons.querySelectorAll('button:not(#cat-all)');
+ categoryBtns.forEach(btn => {
+ if (btn.style.backgroundColor === 'rgb(0, 0, 128)') {
+ selectedCategories.push(btn.dataset.value);
+ }
+ });
+
+ if (selectedCategories.length > 0) {
+ formData.user_category = selectedCategories;
+ }
+ }
+
+ submitBtn.disabled = true;
+ submitBtn.textContent = '处理中...';
+
+ try {
+ const response = await fetch('/api/generate', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(formData)
+ });
+
+ const result = await response.json();
+
+ if (result.success) {
+ const successCount = result.expressions_success ? result.expressions_success.length : 0;
+ const candidateCount = result.candidates ? result.candidates.length : 0;
+ const errorCount = result.expressions_error ? result.expressions_error.length : 0;
+ alert('生成完成!成功: ' + successCount + ' 个, 候选: ' + candidateCount + ' 个, 错误: ' + errorCount + ' 个');
+
+ // 显示下载按钮
+ downloadBtn.style.display = 'block';
+ downloadBtn.onclick = function() {
+ const alphaId = document.getElementById('alphaId').value.trim();
+ window.location.href = '/api/download/' + alphaId;
+ };
+ } else {
+ alert('生成失败: ' + (result.error || '未知错误'));
+ downloadBtn.style.display = 'none';
+ }
+
+ } catch (error) {
+ alert('请求失败: ' + error.message);
+ downloadBtn.style.display = 'none';
+ } finally {
+ submitBtn.disabled = false;
+ submitBtn.textContent = '生成变种';
+ }
+});
diff --git a/simple72/templates/index.html b/simple72/templates/index.html
new file mode 100644
index 0000000..ff8c886
--- /dev/null
+++ b/simple72/templates/index.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Alpha Transformer -
+
+
+
+
+
+
+
+
+
使用说明
+
+ - 输入您的种子Alpha ID,系统将基于BRAIN平台生成多个变种
+ - 配置您的LLM(支持OpenAI兼容接口)和BRAIN凭证
+ - 点击生成按钮,系统将自动完成所有处理并返回结果
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/simple72/templates/styles.css b/simple72/templates/styles.css
new file mode 100644
index 0000000..bb98cf6
--- /dev/null
+++ b/simple72/templates/styles.css
@@ -0,0 +1,343 @@
+/* Monokai Theme for Alpha Transformer */
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+/* Monokai Color Palette */
+:root {
+ --monokai-bg: #272822;
+ --monokai-fg: #f8f8f2;
+ --monokai-comment: #75715e;
+ --monokai-red: #f92672;
+ --monokai-orange: #fd971f;
+ --monokai-yellow: #e6db74;
+ --monokai-green: #a6e22e;
+ --monokai-cyan: #66d9ef;
+ --monokai-blue: #268bd2;
+ --monokai-purple: #ae81ff;
+ --monokai-dark: #1e1f1c;
+ --monokai-border: #3e3d32;
+}
+
+body {
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 14px;
+ background: var(--monokai-dark);
+ min-height: 100vh;
+ padding: 20px;
+ color: var(--monokai-fg);
+}
+
+.container {
+ max-width: 900px;
+ margin: 0 auto;
+ background: var(--monokai-bg);
+ border: 2px solid var(--monokai-border);
+ border-radius: 8px;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
+}
+
+.header {
+ background: linear-gradient(135deg, var(--monokai-dark) 0%, var(--monokai-bg) 100%);
+ color: var(--monokai-green);
+ padding: 20px;
+ font-weight: bold;
+ font-size: 18px;
+ border-bottom: 2px solid var(--monokai-border);
+ border-radius: 8px 8px 0 0;
+}
+
+.header h1 {
+ color: var(--monokai-green);
+ text-shadow: 0 0 10px rgba(166, 226, 46, 0.3);
+}
+
+.header p {
+ color: var(--monokai-comment);
+ font-size: 14px;
+ margin-top: 5px;
+}
+
+.content {
+ padding: 20px;
+}
+
+.form-section {
+ margin-bottom: 20px;
+ background: var(--monokai-dark);
+ border: 1px solid var(--monokai-border);
+ border-radius: 6px;
+ padding: 20px;
+}
+
+.form-section h2 {
+ color: var(--monokai-cyan);
+ margin-bottom: 15px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--monokai-border);
+ font-size: 16px;
+ font-weight: bold;
+}
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 8px;
+ color: var(--monokai-orange);
+ font-weight: normal;
+ font-size: 13px;
+}
+
+.form-group input,
+.form-group select {
+ width: 100%;
+ padding: 10px 12px;
+ border: 1px solid var(--monokai-border);
+ border-radius: 4px;
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 13px;
+ background: var(--monokai-bg);
+ color: var(--monokai-fg);
+ transition: all 0.3s ease;
+}
+
+.form-group input:focus,
+.form-group select:focus {
+ outline: none;
+ border-color: var(--monokai-cyan);
+ box-shadow: 0 0 0 2px rgba(102, 217, 239, 0.2);
+}
+
+.form-group input::placeholder {
+ color: var(--monokai-comment);
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 15px;
+}
+
+.btn {
+ padding: 10px 20px;
+ background: var(--monokai-purple);
+ color: var(--monokai-bg);
+ border: none;
+ border-radius: 4px;
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 13px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.btn:hover {
+ background: #c5a3ff;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(174, 129, 255, 0.3);
+}
+
+.btn:active {
+ transform: translateY(0);
+}
+
+.btn:disabled {
+ background: var(--monokai-comment);
+ color: var(--monokai-dark);
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
+
+.submit-btn {
+ width: 100%;
+ padding: 14px 28px;
+ background: linear-gradient(135deg, var(--monokai-green) 0%, #8bc34a 100%);
+ color: var(--monokai-bg);
+ border: none;
+ border-radius: 6px;
+ font-size: 15px;
+ font-weight: bold;
+ margin-top: 10px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.submit-btn:hover {
+ background: linear-gradient(135deg, #b8e068 0%, #9ccc65 100%);
+ box-shadow: 0 4px 15px rgba(166, 226, 46, 0.4);
+}
+
+.instructions {
+ background: var(--monokai-dark);
+ border: 1px solid var(--monokai-border);
+ border-radius: 6px;
+ padding: 20px;
+ margin-bottom: 20px;
+}
+
+.instructions h3 {
+ color: var(--monokai-yellow);
+ margin-bottom: 12px;
+ font-size: 15px;
+ font-weight: bold;
+}
+
+.instructions ul {
+ color: var(--monokai-fg);
+ padding-left: 25px;
+}
+
+.instructions li {
+ margin-bottom: 8px;
+ line-height: 1.6;
+}
+
+.instructions li::marker {
+ color: var(--monokai-red);
+}
+
+#category-buttons {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+ padding: 12px;
+ border: 1px solid var(--monokai-border);
+ border-radius: 4px;
+ background: var(--monokai-bg);
+}
+
+/* Loading animation */
+.loading {
+ display: none;
+ text-align: center;
+ padding: 40px;
+ background: var(--monokai-dark);
+ border: 1px solid var(--monokai-border);
+ border-radius: 6px;
+ margin-top: 20px;
+}
+
+.loading.active {
+ display: block;
+}
+
+.spinner {
+ border: 3px solid var(--monokai-border);
+ border-top: 3px solid var(--monokai-cyan);
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+ margin: 0 auto 15px;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Error message */
+.error-message {
+ background: rgba(249, 38, 114, 0.1);
+ color: var(--monokai-red);
+ padding: 15px;
+ margin-top: 15px;
+ border: 1px solid var(--monokai-red);
+ border-radius: 4px;
+ display: none;
+ font-weight: bold;
+}
+
+.error-message.active {
+ display: block;
+}
+
+/* Result section */
+.result-section {
+ margin-top: 20px;
+ display: none;
+}
+
+.result-section.active {
+ display: block;
+}
+
+.result-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 10px;
+ padding: 12px;
+ background: var(--monokai-dark);
+ color: var(--monokai-green);
+ font-weight: bold;
+ border: 1px solid var(--monokai-border);
+ border-radius: 4px 4px 0 0;
+}
+
+.result-content {
+ background: var(--monokai-bg);
+ border: 1px solid var(--monokai-border);
+ border-radius: 0 0 4px 4px;
+ padding: 15px;
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.result-content pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
+ font-size: 12px;
+ color: var(--monokai-fg);
+ line-height: 1.5;
+}
+
+.btn-copy {
+ padding: 6px 14px;
+ background: var(--monokai-cyan);
+ color: var(--monokai-bg);
+ border: none;
+ border-radius: 4px;
+ font-size: 12px;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+}
+
+.btn-copy:hover {
+ background: #87e8f5;
+ box-shadow: 0 2px 8px rgba(102, 217, 239, 0.3);
+}
+
+/* Scrollbar styling */
+::-webkit-scrollbar {
+ width: 10px;
+ height: 10px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--monokai-dark);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--monokai-comment);
+ border-radius: 5px;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--monokai-purple);
+}
+
+/* Selection styling */
+::selection {
+ background: var(--monokai-purple);
+ color: var(--monokai-bg);
+}