---
title: "概念-AlphaGPT-数据加载与期货缺失值处理"
date: 2026-07-21
tags: [AlphaGPT, 择时, 数据处理, 概念]
source: "AlphaGPT择时框架"
---

## 一句话

把用户的"流水账"长表 DataFrame,拆成一摞"考勤表"宽表,再叠成一个"魔方"立方体
`[N品种, F特征, T时间]`;同时用 `valid_mask` 记录期货非永续品种的"可交易真相"。

对应代码:`timing_adapter/df_loader.py` 的 `TimingDataFrameLoader`。

---

## 起点:长表长什么样

用户传进来的 `df` 是"流水账"式的——每一行是"某个品种在某一天"的一条记录:

```
     date       instrument   open   high  ...  volume   settle
  2020-01-02    AP8888.CZC   7740   7791  ...  93362    7752
  2020-01-02    CF8888.CZC   13940  14270 ...  585483   14070
  ...           ...          ...    ...   ...  ...      ...
        ↑ 每行是「一个品种 × 一天」
```

问题:神经网络没法直接吃这种形状,它需要**规整的矩形张量**,每个维度含义固定。

---

## 第1步:pivot —— 把一列摊成一张"考勤表"

`_pivot()` 每次只拿一个特征列(如 `close`),用 `pivot_table` 摊成二维表:

```
         行 = 品种,  列 = 日期  →  [N, T]

              2020-01-02  2020-01-03  ...
  AP8888.CZC     7741        7589     ...
  CF8888.CZC     13975       ...      ...
```

像一张考勤表:行是品种,列是日期,格子里是那天的收盘价。选了 7 个特征列,就摊出
7 张这样的表,每张形状 `[N, T]`。

## 第2步:堆叠 —— 7张表叠成"魔方"

`torch.stack(feat_list, dim=1)` 把这些表像叠透明胶片一样摞起来:

```
   feat_tensor 形状 [N=70, F=7, T=728]
   = 哪个品种 × 哪个特征 × 哪一天

   feat_tensor[5, 3, 100] = 第5个品种、第3个特征、第100天的值
```

**记忆口诀**:`[N, F, T]` = 品种 × 特征 × 时间。这就是模型和虚拟机看到的世界。 ^魔方

---

## 核心:期货缺失值处理(整个 loader 最讲究的地方)

期货品种上市时间不同(如工业硅 si8888 很晚上市),摊成规整矩形后**必然有空格子**。
代码分两种情况处理:

### ① 特征表(`fill=True`):空格子要填
否则滚动均线这类算子会被 NaN 污染。用 `ffill → bfill`(停牌用昨天的值补,开头缺的
用第一个有效值补),但**绝不填 0**——价格填 0 会让收益率 `log(price/0)` 爆炸成无穷大。
这是铁律。 ^绝不fillna0

### ② valid_mask(`fill=False`):填之前先拍"真相快照"
原始价格非 NaN 且 > 0 的格子记 `1`(真能交易),空的记 `0`(上市前/停牌)。

```
  close 特征表(填充后)        valid_mask(真相)
  ┌──────────────┐          ┌──────────────┐
  │ 补 补 74 75 │          │ 0  0  1  1  │  ← 前两天上市前,是"补"出来的
  │ 13 14 14 15 │          │ 1  1  1  1  │  ← 全程都有,真实
  └──────────────┘          └──────────────┘
```

### valid_mask 的完整生命周期
真相判定(价格非NaN且>0)→ 用于剔除低质量品种 → 保留后重新计算 → 乘到 target_ret 上
→ 后续在挖掘/应用里还乘到仓位/因子输出上,形成"填充值不产生真实交易"的闭环。 ^mask生命周期

### 低质量品种直接剔除
有效格子占比低于 `min_valid_ratio`(默认 30%)的品种整个剔除。demo 里 72 个品种
剔除 `IM8888.CFE`、`si8888.GFE` 变成 70 个,就是这一步。

---

## load() 一共产出 4 个张量

| 张量 | 形状 | 是什么 | 比喻 |
|---|---|---|---|
| `feat_tensor` | `[N,F,T]` | 特征立方体 | 魔方 |
| `valid_mask` | `[N,T]` | 可交易真相 | 考勤表:1能/0不能 |
| `target_ret` | `[N,T]` | 未来收益 | 见下 |
| `raw_price` | `[N,T]` | 结算价 | 算收益用 |

### target_ret:防未来函数的关键
```python
entry = roll(price, -1)        # T+1 进场
exit_ = roll(price, -(1+H))    # T+1+H 平仓
fwd = log(exit_ / entry)       # 对数收益
fwd[:, -(1+H):] = 0            # 尾部无未来数据,置0
```
默认 `hold_period H=1`:T日出信号 → T+1进场 → T+2平仓,持有1根K线。进场从 T+1 而非 T
开始,是为了严防未来函数:信号只能用**已知数据**算,赚的必须是信号出现之后的钱。
详见 [[概念-前视偏差]]。

### 特征标准化:逐品种沿时间轴(择时口径)
`_robust_norm_ts` 对每个品种沿自己的时间轴做 median/MAD 稳健标准化(非选股的横截面),
clamp 到 [-5,5]。用中位数/MAD 而非均值/标准差,防单个 outlier 带坏尺度。 ^择时标准化

---

## 一句话收尾

**长表流水账 → 每列摊成考勤表(pivot) → 多张表叠成魔方(stack) → 顺便记下"哪些格子是真的"
(mask)和"每根K线信号赚多少"(target)**。魔方喂给模型当输入,mask 和 target 用来打分。

## 相关笔记
- [[MOC-AlphaGPT择时框架]] — 架构入口
- [[概念-AlphaGPT-后缀表达式与栈式虚拟机]] — 魔方怎么被公式读取
- [[概念-AlphaGPT-择时框架的频率无关设计]] — 为什么"每根K线"而非"每天"
- [[概念-前视偏差]] — target 防未来函数的通用原理

