# 单因子运算符选择的完整思考逻辑链

> 前提：我们已经有一个单因子 \(A\)，且 **A 越高 = 股票越好，A 越低 = 股票越差**。下面的逻辑链告诉你：
> 
> - 应该先问哪些问题；
> - 在每个问题下，不同答案对应 **应该想起哪些运算符**；


---

## 第 0 步：先把 A 变成“干净版 A_clean”

无论最终怎么构造 Alpha，几乎总是先做 **基本清洗 + 标准化**。

### 0.1 A 是否有明显极端值 / 脏点 / 长尾？

- 典型现象：
  - 分布里有极少数特别大的点（可能是错误或极端噪音）；
  - 这些点如果不处理，会主导整个因子。

- 若 **是**，考虑：
  - `winsorize(A, std = k)`：按标准差裁剪两侧尾部；
  - 或使用 `right_tail` / `left_tail` 在某个阈值外直接 NaN：
    - `right_tail(A, minimum)`：低于 minimum 的视为 NaN，仅用右尾；
    - `left_tail(A, maximum)`：高于 maximum 的视为 NaN，仅用左尾。

- 若 **否**，可以保留原始 A 进入下一步。

> 这一层的目标：**让极值不会“毁掉”排序和后续运算。**

### 0.2 是否需要把 A 映射到一个“标准形态”？

问自己：

> 之后我会不会把这个因子和别的因子组合，或者需要一个“偏离均值”的直觉？

- 若 **是**，常用三种“标准形态”：

  1. **标准差度量**：----正态分布
     - `zscore(A)`：\(Z = (A - \bar{A}) / \sigma(A)\)
     - 适用：想用“距平均多少个标准差”来衡量好坏。

  2. **分位/排序度量**： -----平均分布
     - `rank(A)`：返回 [0,1] 之间的分位；
     - 适用：只相信排序，不相信绝对差距。

  3. **分布拉伸（强调头尾）**：
     - `quantile(A, driver="gaussian"|"cauchy"|"uniform")`
     - 适用：想让中间区间集中在 0 附近，而“极好/极差”拉到更远，以增强尾部权重。

- 若 **否**，可以继续用原始 A，仅在后续分段时使用阈值。

> 得到的结果可以统一记为 `A_clean`，后续所有推理都基于它。

---

## 第 1 步：A 的 **全区间** 是否都有信号？

核心问题：

> 我是否相信，从最小到最大，**A 的每一段区间**都大致满足“高好低差”，没有明显“中间是噪音”“某一段是反向/无效”？

### 1.1 如果 **是**：只需要“形状控制”，不需要切区间

此时你只是在回答：

> 我要不要放大极端、压缩极端，还是保持线性？

常见选择：

1. **保持线性**（最简单、解释性最好）：
   - 直接使用 `A_clean`。

2. **放大尾部**（极好/极差更重要）：
   - `signed_power(A_clean, y)`：
     - A 有正负且符号有意义（好/坏方向）时使用；
     - `y > 1`：放大尾部，极端好/差股票更突出；
   - 或 `power(A_clean, y)`（在 A≥0 的场景）。

3. **抑制尾部**（不想极端值过度主导）：
   - `s_log_1p(A_clean)`：对正负两侧同时压缩；
   - 或 `power(A_clean, 0<y<1)`：整体变得更平坦。

> 这一支路线的特点：**A 的排序在全区间保持有效**，你只是改变了“好坏之间的距离感”。

### 1.2 如果 **否**：只有部分区间有信号 → 进入“区间结构”分支

即你认为：

- 只有高端/低端/两端某些区间有用；
- 中间、极值区域等可能是噪音或反向。

→ 转到第 2 步。

---

## 第 2 步：信号集中在哪些区间？（头部 / 尾部 / 两端）

在这一层，你要回答：

> 是不是只有“高值端”“低值端”或“两端”有用？中段是否应视为噪音？

### 2.1 只对 **高值端** 有信号（右尾）

- 例如：只有 ESG 非常高的公司体现了真正“高质量”；中等 ESG 和略高并没有明显超额收益。

**逻辑：**

1. 把 A 变成分位/分布形式：
   - `Ar = rank(A_clean)` 或 `Aq = quantile(A_clean, "gaussian")`。

2. 定一个“高端阈值”：
   - 例如 `high = greater(Ar, 0.8)`（前 20%）。

3. 构造分段：
   - 高端：使用 `A_clean` 或放大的版本；
   - 非高端：
     - 要完全不交易 → 赋 `NaN` 或 `0`；
     - 要轻仓参与 → 给一个缩放系数（如 0.3×A）。

**涉及运算符：**
- `rank`, `quantile`, `greater`, `if_else`, `trade_when`, `right_tail`。

### 2.2 只对 **低值端** 有信号（左尾）

- 例如：只有“极差 ESG”的公司才会有明显负面溢价，普通差/一般的没那么强。

**逻辑类似 2.1**：

1. `Ar = rank(A_clean)`；
2. `low = less(Ar, 0.2)`；
3. 对低端：用负方向或反向信号；
4. 对其它：0 或轻仓。

**涉及运算符：**
- `rank`, `less`, `left_tail`, `if_else`, `trade_when`。

### 2.3 只有 **两端** 有信号，中间是噪音

- 例如：
  - 极好公司会持续超额收益；
  - 极差公司会持续跑输；
  - 中等公司只是市场 beta，不值得下注。

**逻辑：**

1. `Ar = rank(A_clean)`；
2. 定上下阈值：
   - `high = greater(Ar, 0.8)`；
   - `low  = less(Ar, 0.2)`；
3. 分段：
   - 高端：正向权重（如直接用 A_clean，或 zscore）；
   - 低端：负向权重（如 `-A_clean` 或 `reverse(A_clean)`）；
   - 中间：0 或很小值。

**涉及运算符：**
- `rank`, `bucket`, `greater/less`, `if_else`, `trade_when`。

> 在第 2 步，你主要在用：**rank/bucket + 逻辑运算符 + trade_when**，实现各种“头尾逻辑/中段视为噪音”的结构。

---

## 第 3 步：信号是“全市场可比”，还是“只能在某些组内可比”？

关键问题：

> A 的绝对水平能否 **跨行业/国家/size** 直接比较？
>
> 还是只能在同一 group 内（如同一行业）比较？

### 3.1 若只能在 group 内比较（典型：ESG、估值、盈利率等）

在这类场景：
- 不同行业/国家间 A 的平均水平不同且无法直接比较；
- 你更关心“在所在行业内的相对位置”。

**应想到的运算符：**

1. **组内标准化/排序：**
   - `group_rank(A_clean, group)`：组内排名 0–1；
   - `group_zscore(A_clean, group)`：组内 zscore；
   - `group_scale(A_clean, group)`：组内线性缩放到 [0,1]。

2. 再在组内做第 2 步那种“头尾/中段噪音”逻辑：
   - 如：只取行业内 top 20% 的 ESG：
     - 对 `group_rank` 的高端使用；
   - 或行业内极好/极差两端建双向因子。

3. 行业暴露控制：
   - `group_normalize(core, group)`：每个 group 内绝对值和=1 或满足某种约束。

> 这一步其实是在回答：**“我是在比较谁和谁”**。若答案是“只在组内比较”，所有 rank/zscore 操作都应换成 group 版本。

### 3.2 若可以全局比较

- 例如：某些纯价格衍生因子、横截面标准化指标；
- 维持第 1–2 步的全局处理即可，不必使用 group 运算符。

---

## 第 4 步：你关注的是“当前时点的高低”，还是“相对自身历史的模式”？

现在的问题变成：

> 对 A 来说，我更关心：
> - 此刻在全市场/组内的高低（**截面视角**），还是；
> - 它相对自己历史的高低、变化、事件（**时序视角**）？

### 4.1 截面视角为主（只看“今天谁更好”）

- 之前第 1–3 步已经足够；
- 可以直接进入最后的交易/约束步骤。

### 4.2 时序视角：关心 “A 随时间的行为”

这时要思考：**“时间维度上的哪种模式有信号？”**

#### 4.2.1 看“当前值在自身历史中的位置”

- 问：
  > 这只股票目前的 A，是它最近一段时间里的高位还是低位？

- 对应运算符：
  - `ts_rank(A_clean, d)`：当前值在过去 d 天里的分位；
  - `ts_zscore(A_clean, d)`：当前值距 d 天均值多少个标准差；
  - `ts_scale(A_clean, d)`：类似 0–1 归一。

- 然后对 `ts_rank/ts_zscore` 再走 **第 1–3 步的逻辑**：
  - 如：只有当 `ts_rank(A_clean, 60)` 非常高/低时才认为有信号。

#### 4.2.2 看“变化/趋势”：A 是否在**变好/变坏**

- 问：
  > “变好”比“绝对水平高”更重要吗？

- 对应运算符：
  - `ts_delta(A_clean, d)`：\(A_t - A_{t-d}\)；
  - `ts_mean(A_clean, d)` / `ts_median(A_clean,d)`：平滑最近 d 天水平；
  - `ts_std_dev(A_clean, d)`：波动是否稳定；
  - `ts_regression(A_clean, ts_step(), d, rettype=slope)`：线性趋势。

- 然后：
  - 把这些输出当成新的单因子，再用 **第 1–3 步** 对它做“全区间/头尾/组内”等判断。

#### 4.2.3 看“事件驱动”：A 的变化点是事件

适用：评级/打分这种“台阶型”指标，或你认为“变动本身”才是 alpha。

- 对应运算符：

  1. `days_from_last_change(A_clean)`：
     - 多少天前 A 曾发生变化；
     - 适合构造“事件后衰减”逻辑：
       - 事件后 5 天信号最强，10 天后视为消失等。

  2. `last_diff_value(A_clean, d)`：
     - 最近一次与当前不同的数值；
     - 可用 \(A_t - A_{prev}\) 表示“升级幅度/降级幅度”。

- 然后同样用逻辑/头尾来设计：
  - 变化幅度越大、时间越近 → 权重越高；
  - 变化很久以前或幅度很小 → 信号减弱。

#### 4.2.4 看“缺失/更新频率”本身

- 对应运算符：
  - `ts_count_nans(A_clean, d)`：最近 d 天内缺失次数；
  - 适用：你怀疑“长期不披露/刚开始披露”的公司有不同的行为。

- 逻辑：
  - nan_cnt=0：数据完整 → 因子可靠；
  - nan_cnt 很高：连续缺失 → 可以惩罚或剔除。

> 小结：第 4 步就是在决定“我要用 **截面型 A** 还是 **时间域衍生的 A_ts** 来继续走余下逻辑”。

---

## 第 5 步：是否只在“信号明显时”才交易或加大仓位？

问题：

> 当 A 只是轻微偏好/偏差时，要不要交易？还是只在信号非常强时才动？

### 5.1 只在信号够强时才交易

- 对应：
  - 使用逻辑/分段函数对 A_clean 或 A_ts 做“开/关”决策。

- 典型做法：
  - 定义强/中/弱三档：
    - `strong = greater(zscore(A_clean), 2)`；
    - `weak   = less(zscore(A_clean), -2)`；
  - 构造：
    - 强信号：用完整 A；
    - 中等信号：半仓；
    - 弱信号：0。

- 涉及运算符：
  - `greater/less`, `if_else`, `trade_when`, `bucket` 等。

### 5.2 即使信号弱也希望有“连续暴露”，但仓位大小分档

- 例如：
  - zscore>2 → 1.5×权重；
  - 1<zscore≤2 → 1×权重；
  - 0<zscore≤1 → 0.5×权重；
  - 其它 → 0 或轻微仓位。

- 同样用分段结构，只是下限不为 0。

> 总之，这一步决定：**是否根据信号强弱，用 piecewise 的方式控制仓位强度。**

---

## 第 6 步：交易性和风险约束（不改变信号，只改变可实施性）

到这里为止，你已经形成了某个 `core` 向量，代表“相对喜好”。

最后一步是把它变成 **可交易的 Alpha 权重**：

1. 控制单股票暴露：
   - `truncate(core, maxPercent=0.01)`：限制单只股票最大权重。

2. 控制整体 booksize 多空结构：
   - `scale(core, scale=1, longscale=1, shortscale=1)`：调整多头、空头总和。

3. 控制行业/国家等暴露：
   - `group_normalize(core, industry)`：行业内平衡多空；
   - 全局再 `normalize(core)` 或 `scale`。

> 这一层不改变“谁更好谁更差”的排序，只让因子变成现实中可执行的投资组合。

---

## 总结：用一句话记住这条逻辑链

当你拿到一个“单因子 A，高好低差”的字段时，可以按下面顺序逐个发问：

1. **干不干净？**
   - 有极值/错误 → `winsorize` / `left_tail` / `right_tail`。
2. **全区间都有信号吗？**
   - 是 → 只调形状：`zscore` / `signed_power` / `s_log_1p` / `quantile`。
   - 否 → 继续问“只有头/尾/两端有信号？” → 用 `rank`+`bucket`+`if_else`/`trade_when` 切区间。
3. **比较对象是谁？全市场还是组内？**
   - 只能组内比 → 把 rank/zscore 换成 `group_rank`/`group_zscore`/`group_scale`，再做第 2 步逻辑。
4. **看当前横截面，还是看相对历史/事件？**
   - 截面：停留在当前 A_clean；
   - 相对历史：先变成 `ts_rank/ts_zscore/ts_delta/ts_mean/ts_std_dev` 或 `days_from_last_change/last_diff_value` 等，再走 1–3 步。
5. **只在信号明显时才交易吗？**
   - 是 → 用 `if_else`/`trade_when` 做分段仓位（强 signal 大仓，弱 signal 小仓或 0）。
6. **最后用什么约束让它可交易？**
   - `truncate` 控制单股票；`scale` 控制整体多空；`group_normalize` 控制行业等暴露。

顺着这条链走，你每一步的“直觉假设”（极值、头尾、组内、时序、事件、强弱）都会自然指向一小撮合适的运算符，而不需要在 operator 列表里 “盲选”。
