标签工程:Triple Barrier 与元标签(Meta-Labeling)在机器学习量化中的应用
用 Triple Barrier 给机器学习量化打标签,再用元标签(Meta-Labeling)过滤主信号:把「该下注」和「不该下注」分开。
监督学习做方向预测,第一步不是调模型,而是打标签。标签错了,模型再花哨也是在对噪声过拟合。
最朴素的标签是「明天涨了标 +1、跌了标 −1」。它的问题很致命:它只看期末一个点的符号,把中途涨 5% 又跌回来的路径,和一路阴跌的路径当成同一类;它还把「涨 0.1%」和「涨 10%」都叫 +1,信息损失巨大。更隐蔽的是,这种标签天然和近期收益高度相关,模型很容易学到「昨天涨今天就涨」这种伪规律。
本文用两段真实计算(8 年日度合成序列、MA 金叉主信号)讲清两件事:Triple Barrier 怎么把路径信息编码进标签,以及 Meta-Labeling 怎么让机器学习「这次该不该真的下注」。所有数字和配图都由文末代码真实生成。
一、Triple Barrier:用三条边界定义一次交易的结果#
对每一个入场点 ,我们同时画三条边界:
- 上边界(止盈):价格触及 ;
- 下边界(止损):价格触及 ;
- 时间边界:固定持有 个交易日。
从 开始往前看,第一个被触发的边界决定标签:
- 先触上边界 → 标签 (做多止盈);
- 先触下边界 → 标签 (做多止损);
- 时间到了还没触 → 标签 (到期平仓)。
关键点在于:标签不再只看「期末价格」,而是看「路径上哪个边界先到」。一段「先冲 +5% 再回落」的行情,会被标成 +1 而不是 0 或 −1——这正是朴素标签丢失的信息。

下面这段是文章所有实验的核心:给定价格序列和三条边界,逐起点扫描 horizon 内第一个触发的边界。
import numpy as np
def triple_barrier(P, h=20, u=0.05, d=0.05):
n = len(P)
labels = np.zeros(n, dtype=int) # +1 / -1 / 0
first_hit = np.full(n, np.nan) # 首个触发距起点的交易日数
for t in range(n - h):
pt = P[t]
seg = P[t+1 : t+1+h] / pt - 1.0 # 相对起点 t 的累计收益
up = np.where(seg >= u)[0]
dn = np.where(seg <= -d)[0]
if len(up) and (len(dn) == 0 or up[0] < dn[0]):
labels[t] = 1; first_hit[t] = up[0] + 1
elif len(dn):
labels[t] = -1; first_hit[t] = dn[0] + 1
# 否则时间边界触发 -> 0
return labels, first_hitpython二、标签分布:Triple Barrier 编码了「方向」#
我们用一段约 8 年(2017 个交易日)、年化漂移 8%、波动 18% 的合成 GBM,参数取 日、,把 Triple Barrier 标签和朴素「20 日后符号」标签放在一起比:
- Triple Barrier:、、;
- 朴素标签:涨 、平 、跌 。

差别很明显:朴素标签把一切塞进「涨/跌」二元,而 Triple Barrier 多出一个 类——它表示「这 20 天里价格既没冲到 +5% 也没跌破 −5%,在中间晃荡」。这一类在风险管理里极其重要:它对应的是「信号失效、该少动」的区间,朴素标签完全看不见它。
三、风险封顶:单笔收益被边界锁死#
把标签直接变成交易规则:标签 就赚 ,标签 就亏 ,标签 拿到期末收益。对比「无脑持有 20 日」:
| 指标 | Triple Barrier 策略 | 朴素持有 20 日 |
|---|---|---|
| 单笔均值 | −0.417% | −0.605% |
| 单笔 Std | 4.07% | 5.10% |
| 胜率 | 44.8% | 43.5% |
| 最差单笔 | −5.0%(被下边界封死) | −14.0% |

Triple Barrier 的均值略高、波动更低,最关键的差异在右尾被砍掉:朴素持有会出现 −14% 的灾难单笔,而 Triple Barrier 最惨也就 −5%(下边界)。换句话说,标签本身就是一道风控——它把每笔交易的最大亏损/盈利预先钉死,逼着策略讨论「该不该承担这笔风险」,而不是赌一个遥远的期末价格。
四、元标签 Meta-Labeling:让 ML 决定「下不下注」#
到这里有个分工问题。主模型(比如均线金叉、动量)擅长回答方向,但它不擅长回答时机和规模。López de Prado 的元标签框架非常巧妙:
- 主模型给一个副作用信号(如 MA20 > MA50 → 看多);
- 用主信号方向的 Triple Barrier 是否先触上边界 当二元标签 (盈利=1,否则=0);
- 训练一个元模型,用波动率、RSI、成交量异常、动量、价格偏离度等辅助特征预测 ;
- 实战中:只在主信号看多、且元模型预测盈利概率高时,才真的下注。
方向交给可解释的主模型,时机/规模交给 ML——既保留了主模型的经济含义,又用数据过滤掉「看多但不该动」的噪声交易。
我们在一个真实的弱主信号上验证。主信号是 MA20/MA50 金叉(长期看它在本样本里其实是亏损的),元模型用 5 个特征训练逻辑回归,按时间切分训练/测试。测试段 233 笔主信号交易里,盈利占比仅 22.3%,元模型测试 AUC=0.620——排名有真实技能,但校准偏弱(平均预测概率 0.28,全部低于 0.5)。因此我们用标准做法:按预测概率取前 50% 下注,而不是硬卡 0.5 阈值。

结果(测试段,逐笔复利):
| 策略 | 下注笔数 | 终值 | 最大回撤 | 单笔均值 | Sharpe |
|---|---|---|---|---|---|
| 主信号全仓 | 233 | 0.142 | −91.9% | −0.750% | −2.91 |
| 元标签过滤 | 117 | 0.917 | −67.1% | +0.014% | +0.05 |
元标签没把亏损变盈利,但它把一个灾难性的策略(−86% 终值、−92% 回撤)改造成了接近盈亏平衡(−8% 终值)、回撤收缩 25 个百分点、交易次数减半的版本。元标签的价值从来不是「点石成金」,而是「把仓位集中在更可能盈利的那一半信号上」——在弱信号上,这一点就值回票价。
五、完整实现(串起来)#
# ---- 特征 ----
def sma(x, w):
c = np.cumsum(np.insert(x, 0, 0.0)); return (c[w:] - c[:-w]) / w
ma20, ma50 = sma(P, 20), sma(P, 50)
signal = (ma20 > ma50).astype(int)
sig_days = np.where((signal == 1) & (np.arange(N) < N - H))[0]
# 元标签:主信号方向 + Triple Barrier 是否先触上边界
y = np.array([1 if labels[t] == 1 else 0 for t in sig_days])
# 辅助特征(务必只用 t 及之前的信息,禁止前视)
vol20 = rolling_vol(P, 20); rsi14 = rsi(P); vol_z = zscore(vol)
mom = P / np.roll(P, 20) - 1; dist = (P - ma20) / ma20
X = np.vstack([vol20, rsi14, vol_z, mom, dist]).T
# 时序切分:前 70% 训练,后 30% 测试(防前视)
cut = int(len(sig_days) * 0.70)
w = train_logistic(X[:cut], y[:cut]) # 手写梯度下降即可
p_te = predict(w, X[cut:])
take = p_te >= np.median(p_te) # 前 50% 下注pythontrain_logistic / predict 用 numpy 梯度下降实现即可(标准化后用学习率 0.05、迭代 3000 次),不依赖任何外部库——这正是本专栏「纯 numpy 可复现」的一贯做法。
六、真实陷阱(踩坑清单)#
- 前视偏差:Triple Barrier 的「第一个触发点」必须只用到 之后的价格,但特征里绝不能混进未来的量(如用期末波动率当特征)。特征与标签同源,等于把答案塞进题目。
- 标签不平衡:对称边界下,时间边界触发往往最多(本文 类占 44%),直接训练元模型会退化成「永远预测 0」。务必用时序切分 + 校准/排序阈值,而非朴素卡 0.5。
- 参数敏感: 决定一切。边界太窄→几乎全触发,太宽→几乎全到期;horizon 太长→标签被噪声淹没。它们本质是止损/止盈/持有期的交易假设,要和经济意义一起调。
- 成本吞噬:本文标签是基于毛收益的。实盘里小盈(如 +0.3%)被手续费和冲击成本一刮就变亏,元标签应以净收益而非毛收益为标签。
- 过拟合主信号噪声:元模型若用 Purged K-fold 以外的普通交叉验证,会因为有重叠的滚动窗口而产生信息泄漏(这是另一个大主题)。本例用严格时序切分规避,但多标的、多参数扫描时务必上 purged CV。
七、结论#
标签工程是机器学习量化的「第一公里」。Triple Barrier 把「路径」还给了标签,让模型学到的是「这笔交易怎么结束」而非「期末一个点的符号」;Meta-Labeling 则把 ML 放在它最擅长的地方——给一个已有方向信号做「该不该下注」的二级判断。二者合起来,你的因子/信号负责「看哪里」,ML 负责「什么时候扣扳机」,各司其职,也比端到端黑箱更可解释、更抗过拟合。
所有图表与统计数字(标签分布、单笔收益、元标签 AUC 与权益曲线)均由 generate_triple_barrier_images.py 真实计算生成。