# REQUEST · 池级特征 f12/f13/f14 全历史补全 —— 两个口径待研究侧裁定

- 提出方：Mac 实操侧（zt-mac）
- 日期：2026-09-14
- 触发：0914 闭环事故（`88438d5` 重建模型时丢了 f12/f13/f14 → scan `KeyError`）已按记录版模型恢复并补跑完成。
  **用户裁定：方向是"把 14 特征补全"，即使强度系数需要重算也接受。**
- 请研究侧回复：下面 (a)(c) 两项口径裁定 + 是否由研究侧产出补全后的 `strength_baseline.csv` / `strength_model.json`。

---

## 0. 为什么要"补全"而不是"重算"

`strength_baseline.csv`（6013 行）中 **f12_crowd_pool / f13_pool_zt / f14_pool_avgret 只有 170 行有值**，全部是 `scan_daily_tb.py` 追加的 0901 之后近端行；**回测区 5700+ 行从来没有过这三列**。

后果：模型里这三列的权重，是从 **≤12 个含收益样本**拟合出来的。Mac 侧实测（dry-run，未落库）：

| 做法 | f12 rho | f13 rho | f14 rho |
|---|---|---|---|
| 现行记录版模型（唯一权威） | +0.0560 | −0.0519 | +0.0002 |
| `build_strength_model_v3.rebuild()` 从现行 baseline 重算 | +0.8000 | +0.8000 | +0.8000 |

重算全部退化成 **+0.8**（含收益样本仅 4 行，spearman 无意义）——所以必须先把样本补全，再谈重算权重。

---

## 1. 待裁定 (a)：池级统计取「信号日」还是「买入日」（现行存在前视）

三列口径（`scan_daily_tb.py`）：
- `f13_pool_zt` = 当日该池 `pct_chg >= 9.8` 家数 / 20
- `f14_pool_avgret` = 当日该池成分股平均 `pct_chg`（百分数单位）
- `f12_crowd_pool` = 当日该池信号数 / 池历史日均信号数

**两条写入路径口径不一致：**

| 路径 | 池统计所取之日 | 行内 `buy_date` | 是否前视 |
|---|---|---|---|
| 主路径（每次 scan，L301-337） | `target` = **信号日** | target 的**次一交易日** | 否 ✅ |
| 近端回补路径（L370-391） | `target` = **buy_date 当日** | = target | **是** ❌ |

**实测反解**（对 170 行中每个「池 × buy_date」唯一反查池统计落在哪一天）：hs300 / zz500 / zz1000 **全部唯一命中 buy_date 当日**；zz1000 例：`buy_date=20260903` 存 `f14=-0.112262`，而 0903 池均值 = −0.112262（0902 = −1.197138）；且该行 `chain_for_buy` 定出的 tb = **20260902**（次一交易日才买入）。

即：**这 170 行存的是信号日次日的池信息，实时扫描时不可能拿到**。

**请裁定**：全历史补全统一取 *信号日（tb）* 还是 *买入日（buy_date）*？Mac 侧建议取 **信号日**（消除前视，与主路径一致），但这会改动 170 行既有值，属口径变更，需你方定。

---

## 2. 待裁定 (c)：f12 分母的自引用/循环依赖

现行 `crowd_denom(bdf, pool, bd)`（`scan_daily_tb.py` L44）：
```
分母 = baseline 中 (pool==pool) & (buy_date < bd) 的历史行按 buy_date 计数的日均值
```
分母**取自 baseline 自身**，而 baseline 的组成又由策略（阈值/参数）决定 → 全历史重算时，同一日的 f12 会随 baseline 整体组成漂移（这正是 0912 `bc56a28` 想缓解的"重跑自称重"，但只把边界改成 `buy_date < bd`，未去掉自引用）。

**请裁定**：是否改为**不依赖 baseline** 的池级定义，例如
`f12 = 当日该池达标信号数 ÷ 该池过去 N 日均达标信号数`（N 与达标定义请指定），
或你方另有定稿定义。定标后 Mac 侧按新定义回填全历史。

---

## 3. 附带发现：池统计不可逐位还原（成分股名单漂移）

池统计依赖**当时**的成分股名单，本地只有现行名单。用现行名单回算历史：

- hs300 / zz500 / zz1000：**可精确复现**（f13、f14 逐位一致）
- zz2000（占 5782 含收益行的 72%）：f13 精确一致，**f14 差约 0.2%**
  例：`buy_date=20260903` 存 `f14=-0.319503`，现行名单回算 = `-0.320158`（n≈1998）

→ 历史池特征只能是**近似重建**，请一并在裁定里明确容差（建议以"不影响池内排序"为准，不需逐位还原）。

---

## 4. 建议的执行分工（供参考）

1. 研究侧出 (a)(c) 口径裁定（若含全历史池统计的定稿定义，请一并给公式）。
2. Mac 侧一次性生成「池 × 交易日」统计表（缓存 CSV）→ 按裁定口径回填 5700+ 行 → 先在 `/tmp` 副本跑出对照报告（新旧权重/分池 rho/分档统计）。
3. 裁定通过后再落库：重算 `percentiles` / `raw_scores` / `strength`，两端按 `daily_reconcile` 对账。
4. 收尾：删掉 `build_strength_v4.py` 的 `to_csv(_bl)`（它无权写 baseline，0913/0914 两次事故都源于此），v3/v4 两个构建器合并为一。

## 5. 现状（已恢复，供对账基准）

- 模型：记录版 14 键（`git checkout 88438d5^ -- backtest_zt_full/strength_model.json`），权重与你方 `patches/RESEARCH_PRODUCTS_INDEX.md` 的 model 逐键相同。
- 0914 当日：`20260914,24.89,90272.92,15,90297.81`（nav 逐值与你方一致）。
- 唯一未落库的本地改动：`scan_daily_tb.py` 两处打分由 `for fc in FCOLS` 改为 `for fc in w`（当前与 14 键等价，仅为抗未来模型增删特征）。
