---
title: "概念-AlphaGPT-回测评分与仓位映射"
date: 2026-07-21
tags: [AlphaGPT, 择时, 回测, 夏普, 概念]
source: "AlphaGPT择时框架"
---

## 一句话

回测把因子值这张表,依次翻译成仓位、扣手续费、乘真实收益,最后用夏普比率打一个分——
这个分数衡量"这条公式又赚钱又稳吗",它就是指挥模型进化的**指挥棒(reward)**。

对应代码:`model_core/backtest.py` 的 `TimingBacktest.evaluate`。

---

## 五步流水线

```
因子值表 [70,728]
  ① 标准化   把因子拉到统一尺度
  ② 仓位映射  因子值 → 该买还是该卖(-1~1)
  ③ 扣成本    换手要付手续费
  ④ 算收益    仓位 × 未来收益 = 赚了多少
  ⑤ 打分      每品种算夏普 → 聚合成一个分数
```

---

## ① 标准化

沿时间轴用 **median/MAD 稳健标准化**(非均值/标准差),把每个品种的因子序列拉成
以0为中心。用中位数防止单个爆炸值(如某天算出999999)带偏整批数据。口径与
[[概念-AlphaGPT-数据加载与期货缺失值处理#^择时标准化]]一致。

## ② 仓位映射(最关键一步)

标准化后的 `z` 翻译成实际仓位:正=做多,负=做空,大小=下注轻重。**四种组合**由两个开关决定:

**开关1 `direction`**:`long_short`(-1~1能空) / `long_only`(0~1只多)
**开关2 `position_map`**:

| 方式 | 翻译 | 特点 |
|---|---|---|
| **tanh** | `pos = tanh(z)` S型曲线 | 信号越强仓位越重,**连续调仓**,换手高 |
| **threshold** | z>0.5满仓多(+1),z<-0.5满仓空(-1),中间空仓(0) | **开关式**,只在穿越阈值时动,**省手续费** |

> **重要澄清**:"择时" ≠ "只能 -1/0/1"。tanh 的连续仓位也是合法择时(按把握程度下注,
> 凯利式);threshold 才是离散 -1/0/1。期货/成本敏感场景更适合 threshold。 ^连续vs离散

> **口径一致性**:挖矿时的 `position_map` 应和应用时保持一致。否则"按连续仓位挑出的
> 公式拿去跑离散仓位",优化目标和实际用的不是一回事,夏普对不上。 ^口径一致

### 具体例子:一条因子如何变成仓位与盈亏

以螺纹钢(RB)连续 5 个交易日为例。阈值取 ±0.5。**关键先理解 `z`**:它不是因子原始值,
而是"因子相对它自己历史的高低分"——z≈0 是平常水平,z 越正=异常地高(倾向做多),
z 越负=异常地低(倾向做空)。

| 交易日 | 因子原始值 | z(相对高低) | tanh 仓位 | threshold 仓位 | 次日真实收益 | tanh 盈亏 | threshold 盈亏 |
|---|---|---|---|---|---|---|---|
| D1 | 0.2 | +0.3(略高) | tanh(0.3)=**+0.29** | z<0.5 → **0** | +2% | +0.58% | 0 |
| D2 | 1.5 | +2.0(异常高) | tanh(2.0)=**+0.96** | z>0.5 → **+1**(满多) | +3% | +2.89% | +3% |
| D3 | 0.1 | 0.0(平常) | tanh(0)=**0** | **0** | −1% | 0 | 0 |
| D4 | −1.2 | −1.5(异常低) | tanh(−1.5)=**−0.91** | z<−0.5 → **−1**(满空) | −2% | **+1.82%** | **+2%** |
| D5 | −0.3 | −0.4(略低) | tanh(−0.4)=**−0.38** | −0.5<z<0.5 → **0** | +1% | −0.38% | 0 |

**从这张表能一眼看懂四件事**:

1. **信号越强,tanh 仓位越重**:D1 只是略高→小仓 0.29;D2 异常高→几乎满仓 0.96。tanh
   是"按把握程度下注",不是全有全无。
2. **threshold 只在信号够强时动**:D1、D5 虽然有方向,但没穿过 ±0.5,直接空仓 0——所以它
   换手低、省手续费。
3. **做空遇跌 = 赚钱**(D4):仓位 −0.91(或 −1),次日跌 2%,`(−0.91)×(−2%)=+1.82%`。
   负负得正,这就是 long_short 能在下跌里赚钱的机制。 ^做空数字例
4. **方向判断错就亏**(D5):做了小仓空(−0.38),结果次日涨 1%,亏 0.38%。仓位错了照样赔。

> **一句话贯通**:因子原始值本身没法交易(0.2 是买还是卖?说不清);先跟自己历史比算出
> `z`(高低分),再用 tanh 或阈值压成仓位,**这时才有了"买多少/空多少"的交易含义**,
> 能和次日收益相乘算盈亏。这就是"因子→仓位"这一步存在的全部理由。 ^仓位映射例子

## ③ 扣成本

调仓要付手续费。换手 = `|今天仓位 - 昨天仓位|`,成本 = `换手 × (cost_bps/1e4)`。
换手越频繁扣越多——这就是 threshold 省钱的原因(仓位不常变)。

## ④ 算收益

`gross = 仓位 × target_ret`(未来收益)。精髓:**做空(-1)遇上下跌(-3%),乘出来是+3%**
——空头也能赚钱,这是 long_short 的价值。`net = gross - 成本`。 ^做空赚钱

## ⑤ 打分

**每品种算绩效**(默认夏普):`夏普 = mean(net)/std(net) × √252`。赚得多且稳→夏普高。
(另有 calmar=收益/最大回撤、cumret=纯累计收益 备选。)

> ⚠️ 那个 `√252` 是**日频年化假设**,是"化妆",对公式排名无影响(常数缩放),但绝对值
> 在分钟数据下会失真。详见 [[概念-AlphaGPT-择时框架的频率无关设计]]。

**防作弊**:某品种全程几乎不交易(交易次数<`MIN_TRADES=5`)直接罚 **-10分**。因为
"永远空仓"的策略收益/波动都是0,夏普可能虚高,这是钻空子。 ^防退化

**聚合成一个分数**:
- `cross_section`(默认):取所有品种夏普的**中位数**。不用平均是为了防某个品种运气爆棚
  拉高平均,中位数看的是"大多数品种上都管用",更抗过拟合。 ^median聚合
- `single_asset`:取平均(通常只1个品种)。

---

## 一句话收尾

因子值 → 稳健标准化 → 仓位映射(四组合)→ 扣成本净PnL → 逐品种夏普 → 中位数聚合 =
一个标量分数。这个分数就是 [[概念-AlphaGPT-REINFORCE训练闭环]] 里 RL 的 reward。
结果里的 OOS 就是这个夏普,只不过在"模型没见过的时间段"上算——见
[[概念-AlphaGPT-walk-forward样本外验证]]。

## 相关笔记
- [[MOC-AlphaGPT择时框架]] — 架构入口
- [[概念-AlphaGPT-REINFORCE训练闭环]] — 分数如何变成学习信号
- [[概念-AlphaGPT-walk-forward样本外验证]] — OOS 怎么算
- [[概念-AlphaGPT-择时框架的频率无关设计]] — √252 的频率假设

