概率仓位管理 Bet Sizing:把模型置信度变成头寸大小
模型输出的不只是方向,还有一个被大多数人扔掉的数字:预测概率。Bet Sizing 把它捡回来变成头寸大小——AFML 的映射 m=2Φ(z)−1(z 为概率对 0.5 的标准化偏离)在低置信区间比线性映射 2p−1 更保守、高置信区间加速抬升,天然抑制'刚过 0.5 就重仓'的赌徒行为。1500 日模拟实测同一个预测器三种下注方式:全押 p>0.5 的 Sharpe 1.83/最大回撤 -20.2%,高置信过滤 p>0.6 的 1.79/-7.8%,概率仓位 1.95/-1.8%——收益没变多,但资金暴露按确信度重新分配后回撤压缩一个数量级。两个工程细节决定实盘可行性:仓位离散化(step=0.2 把累计换手压缩 1.5 倍而信号形状不变,否则概率的小数点抖动全变成手续费);并发信号平均法(同一标的多个存活信号取均值聚合,新信号进场老信号结算时仓位平滑过渡)。前提警告:整套体系建立在概率校准之上,未校准的原始分类器输出直接喂进映射函数比全押更危险(中高阶)
一句话版本#
方向预测告诉你往哪边下注,预测概率告诉你该下多大。Bet Sizing 用一个映射函数把模型置信度翻译成头寸比例——同一个预测器,换一种下注方式,最大回撤可以差一个数量级。这不是玄学,是资金暴露的重新分配。
被扔掉的那个数字#
几乎所有分类器都输出两样东西:一个方向(涨/跌),一个概率(这个方向有多大把握)。
大多数量化管线的做法是:拿走方向,扔掉概率。predict() 返回 1 就满仓买,返回 0 就空仓。模型说「我有 51% 的把握」和「我有 85% 的把握」,在执行端得到的是完全相同的头寸。
这在直觉上就说不通。一个 51% 概率的信号,期望优势薄得像纸——你几乎是在抛硬币。一个 85% 概率的信号(假设校准良好),是模型职业生涯里为数不多的高光时刻。用同样的资金对待这两者,等于把大部分风险预算浪费在最没把握的交易上。
这个系列的上一站,元标注(Meta-Labeling)已经给出了架构:一级模型定方向,二级模型输出「这个信号该不该信」的概率。Bet Sizing 是这个架构的最后一公里——把二级模型的概率输出,翻译成具体的头寸大小。
López de Prado 在《Advances in Financial Machine Learning》第十章给出的翻译方案,值得逐行拆开看。
从概率到仓位:映射函数的形状#
最朴素的想法是线性映射:仓位 。概率 0.5 对应空仓,概率 1.0 对应满仓,中间线性插值。
AFML 的方案多绕了一步。先把概率对 0.5 的偏离做标准化:
这个 是检验统计量的形状——分子是「概率偏离随机猜测多少」,分母是伯努利分布的标准差。它回答的问题是:这个概率偏离 0.5 的程度,有多少统计显著性?
然后把 塞进标准正态 CDF,拉回 区间:
import numpy as np
from scipy.stats import norm
def bet_size(p):
"""AFML 第十章:预测概率 -> 目标仓位(占最大仓位的比例)"""
z = (p - 0.5) / np.sqrt(p * (1 - p))
return 2 * norm.cdf(z) - 1
# 感受一下形状
for p in [0.51, 0.55, 0.60, 0.70, 0.85, 0.95]:
print(f"p={p:.2f} -> 仓位 {bet_size(p):.3f} (线性映射给 {2*p-1:.3f})")python两种映射画在一起:

形状差异是这个方法的全部灵魂:
- 低置信区间(p 在 0.5 附近):AFML 映射比线性映射更保守。p=0.55 时线性给 0.10 仓位,AFML 给约 0.08。概率刚过半的信号,统计上和抛硬币难以区分,仓位就该趴着。
- 高置信区间(p > 0.7):AFML 映射加速抬升,比线性更快接近满仓。因为 的分母 在 p 靠近 1 时缩小,同样的概率增量换来更大的显著性增量。
一句话:这个映射内置了「统计显著性门槛」——它对「勉强过线」的信号天然吝啬,对「真正有把握」的信号才慷慨。这恰好是人类交易员最难执行的纪律。
实测:同一个预测器,三种下注方式#
映射函数的形状说服不了人,净值曲线才能。我做了一个受控实验:
- 1500 个交易日,每天有一个交易机会,结果是 +1% 或 -1%(简化的单位风险)
- 真实胜率是一条缓慢变化的曲线(0.35 到 0.72 之间摆动)——市场有时给机会,有时不给
- 模型的预测概率 = 真实胜率 + 观测噪声(标准差 0.05)——校准良好但有噪声,这是对现实中一个体面模型的合理近似
三种执行方式同场竞技:
- 全押:p > 0.5 就满仓
- 高置信过滤:p > 0.6 才满仓(元标注文章里的过滤思路)
- 概率仓位:AFML 映射 + 0.1 步长离散化,p ≤ 0.5 空仓

| 执行方式 | Sharpe | 最大回撤 |
|---|---|---|
| 全押 p>0.5 | 1.83 | -20.2% |
| 过滤 p>0.6 满仓 | 1.79 | -7.8% |
| 概率仓位(AFML 映射) | 1.95 | -1.8% |
读这张表的正确姿势:
Sharpe 的提升是次要的(1.83 → 1.95,来自低置信期减少暴露),回撤的压缩才是主角(-20.2% → -1.8%,一个数量级)。原因不复杂:全押策略的深回撤全部发生在真实胜率跌破 0.5 的区间——模型的概率输出其实已经在预警(p 掉到 0.5 附近),但二值化执行把这个预警信号删掉了。概率仓位在这些区间自动把暴露降到接近零,它没有比全押策略「更会预测」,它只是没有装作没看见模型自己的犹豫。
高置信过滤(p>0.6)是个中间态:它砍掉了低置信交易,但保留了「过线就满仓」的悬崖式执行——p=0.61 和 p=0.85 拿到同样的仓位,回撤改善就停在 -7.8%。
工程细节一:离散化,别让小数点变成手续费#
概率是连续的,模型每天的输出会在小数点后两三位抖动。如果仓位严格跟随概率,你会每天做一堆 0.83 → 0.81 → 0.84 的微调——每一次都是真实的交易成本,而这些抖动几乎全是噪声。
AFML 的解法朴素到令人安心:把仓位按固定步长取整。
def discretize(m, step=0.2):
"""仓位离散化:只有跨过半个 step 才真正调仓"""
return np.round(m / step) * steppython在模拟的概率序列上实测:

step=0.2 的离散化把 250 日累计换手从 11.7 压缩到 7.6(1.5 倍),而仓位曲线的宏观形状——什么时候重仓、什么时候轻仓——完全没变。被过滤掉的全是概率噪声引起的碎步调仓。
step 的选择是个成本-精度权衡:步长越大换手越省,但仓位对置信度变化的响应越迟钝。0.1-0.25 是常见区间;高换手成本的市场(比如 A 股的印花税 + 冲击成本)应该取大值。
工程细节二:并发信号的平均法#
真实管线里还有一个绕不开的问题:同一个标的上,多个信号同时存活。
CUSUM 过滤器每隔几天触发一个事件,每个事件经三重障碍标注要持有 5-20 天——前一个信号还没结算,后一个信号又进场了。每个信号都有自己的方向和概率,此刻的总仓位听谁的?
AFML 的答案:取当前所有活跃信号的平均。
def average_active_bets(signals, t):
"""signals: [(t_start, t_end, size), ...],size 已含方向正负
返回 t 时刻的聚合仓位"""
active = [s for (t0, t1, s) in signals if t0 <= t <= t1]
return np.mean(active) if active else 0.0python
这个平均法有两个被低估的好处:
- 仓位天然平滑。新信号进场时,聚合仓位从 N 个信号的均值变成 N+1 个的均值——移动是渐进的,不会因为单个信号跳一根悬崖。老信号结算离场同理。
- 多空信号自动对冲。如果模型在趋势末端开始输出反向信号,聚合仓位会先降到零再转向——这比任何人工设计的「趋势衰竭退出规则」都自然,因为它直接来自模型置信度的结构。
前提警告:一切建立在校准之上#
整篇文章有一个不能省略的脚注,我把它放大写在这里:
Bet Sizing 假设概率是校准的——模型说 70%,长期看真的有 70% 的次数是对的。
而大多数分类器的原始输出不满足这一点。随机森林的 predict_proba 系统性向 0.5 收缩,boosting 类模型经常向两端过度自信。把未校准的概率喂进映射函数,等于用一把刻度错乱的尺子量体裁衣——高置信区间的加速抬升会精确放大模型的过度自信,结果比全押更糟。
上线前的必做检查:
from sklearn.calibration import calibration_curve, CalibratedClassifierCV
# 1. 画校准曲线:预测概率 vs 实际频率,越贴对角线越好
prob_true, prob_pred = calibration_curve(y_test, p_hat, n_bins=10)
# 2. 不合格就套一层校准器(金融数据注意用 Purged CV 切分)
calibrated = CalibratedClassifierCV(base_model, method='isotonic', cv=purged_cv)python这和元标注文章末尾的警告是同一条:仓位函数上线前必须验证概率校准,否则比不用更糟。Bet Sizing 把这个要求从「建议」升级成了「地基」。
A 股落地注记#
- T+1 的仓位调整摩擦:离散化步长建议取大(0.2-0.25),因为当天卖出的部分资金次日才能再买入,碎步调仓的机会成本比 T+0 市场更高。
- 不做空的截断:A 股个股默认仅多头,p < 0.5 的信号映射成空仓而不是空头——这会让聚合仓位的分布整体右偏,回测时注意基准对比应该用同样截断后的暴露。
- 涨跌停的执行缺口:目标仓位算出来了但停牌/涨跌停买不进,实际仓位和目标仓位会出现持续偏离。回测里必须用「实际成交后的仓位」而不是「目标仓位」计算净值,否则高波动段的收益被系统性高估。
定位与边界#
Bet Sizing 在 AFML 管线中的位置是最后一环:bar 采样 → CUSUM 事件 → 三重障碍标注 → 元标注 → 概率仓位。它不产生 alpha——模拟里三种执行方式的收益来源完全相同——它做的是把有限的风险预算按置信度重新分配。
它救不了的东西:一级信号没有期望优势(概率仓位只会让你「精确地慢慢亏」)、概率严重未校准(放大灾难)、样本太少导致概率估计本身方差巨大(映射函数的输入就是噪声)。
它擅长的东西:把「模型犹豫时少下注」这条所有人都懂但没人执行的纪律,变成一行代码。
本文实验代码基于 numpy/scipy/matplotlib,模拟参数见文中说明。系列上一篇:元标注 Meta-Labeling:把方向和仓位拆成两个模型。