第二十五讲:进阶话题——基于波动率的动态仓位调整
各位同学,欢迎来到进阶话题。前面我们讲了基差套利的入场、出场、止损,但有个核心问题一直没细聊——仓位到底该下多重?
说实话,这个问题比选品种、定阈值都关键。你策略再牛,仓位管理一塌糊涂,照样爆仓。我见过太多人,策略回测曲线漂亮得像教科书,实盘一上,三天就扛不住了。为什么?仓位没动态调。
今天我们就聊一个进阶玩法:基于波动率的动态仓位调整。说白了,就是让市场告诉你,现在该下多少注。
为什么需要动态仓位?
先问个问题:你平时怎么定仓位?
很多人是固定手数,比如每次开10手。或者固定资金比例,比如总资金的5%。这两种方式,在波动率平稳时还行。但市场一抽风,问题就来了。
举个例子。假设你开仓1手,止损10个点。波动率低的时候,一天波动也就20点,很舒服。突然某天波动率翻倍,一天能跑50点。你原来的止损位,可能开盘就被打穿。仓位还是那个仓位,但风险已经变了。
我2019年做螺纹钢基差套利时就吃过这个亏。当时波动率很低,我按常规仓位下了30手。结果政策一出,波动率瞬间飙升,账户回撤直接超过5%。虽然方向没错,但仓位太重,被迫减仓,利润全吐回去了。
所以,仓位必须跟着波动率走。波动率低,多下点;波动率高,少下点。这才是科学的资金管理。
核心逻辑:波动率与仓位成反比
动态仓位调整的核心公式其实很简单:
目标仓位 = 基准仓位 × (基准波动率 / 当前波动率)
这里:
- 基准波动率:你回测时市场的平均波动水平
- 当前波动率:最近N天的实际波动率
- 基准仓位:你在基准波动率下愿意下的仓位
举个例子。你回测时市场日均波动20点,你设的基准仓位是10手。现在市场波动率变成40点,那目标仓位就是:
10手 × (20 / 40) = 5手
波动率翻倍,仓位减半。反过来,波动率降到10点,仓位就变成20手。这样,每笔交易承担的风险金额基本一致。
核心思想:不是控制仓位大小,而是控制每笔交易的风险敞口。波动率变了,仓位跟着变,风险才能稳定。
波动率怎么算?
波动率的计算方法很多,我个人最常用的是历史波动率(HV)和ATR(平均真实波幅)。
1. 历史波动率(HV)
就是过去N天收益率的标准差。一般取20天或60天。计算很简单:
import numpy as np
import pandas as pd
def calc_hv(price, window=20):
log_ret = np.log(price / price.shift(1))
hv = log_ret.rolling(window).std() * np.sqrt(252) # 年化
return hv
这里乘以sqrt(252)是为了年化,方便不同品种之间比较。实际做仓位调整时,用日波动率就够了,不用年化。
2. ATR(平均真实波幅)
ATR是技术分析里常用的波动率指标。它考虑了跳空缺口,比单纯看收盘价更全面。
def calc_atr(high, low, close, window=14):
tr = np.maximum(high - low,
np.abs(high - close.shift(1)),
np.abs(low - close.shift(1)))
atr = tr.rolling(window).mean()
return atr
我个人习惯用ATR。为什么?因为它直观。ATR=50,意味着今天平均波动50个点。你设止损30点,那大概率会被打。用ATR来定仓位,更贴近实际交易。
小技巧:如果你做的是跨期套利,建议用价差的ATR,而不是单腿的。因为套利的核心是价差波动,不是单边波动。我早期犯过这个错,用单腿波动率去管套利仓位,结果经常偏大或偏小。
完整的动态仓位调整流程
好了,理论讲完,上实战代码。下面是一个完整的动态仓位调整函数:
def dynamic_position_size(signal, price_df, base_pos=10,
atr_window=20, risk_factor=0.02):
"""
基于ATR的动态仓位调整
signal: 信号DataFrame,包含entry_time, direction
price_df: 价格数据,包含high, low, close
base_pos: 基准仓位
atr_window: ATR计算窗口
risk_factor: 风险因子,控制每笔交易风险占总资金比例
"""
# 计算ATR
atr = calc_atr(price_df['high'], price_df['low'],
price_df['close'], atr_window)
# 计算基准ATR(回测期间的平均值)
base_atr = atr.median() # 用中位数更稳健
# 生成仓位调整因子
atr_ratio = base_atr / atr
# 限制调整范围,防止极端值
atr_ratio = atr_ratio.clip(0.3, 3.0)
# 计算最终仓位
signal['position_size'] = (base_pos * atr_ratio).round().astype(int)
# 风险校验:确保每笔风险不超过总资金的2%
capital = 1000000 # 假设总资金100万
risk_per_unit = atr * risk_factor # 每手风险金额
max_pos = capital * 0.02 / risk_per_unit
signal['position_size'] = signal[['position_size', max_pos]].min(axis=1)
return signal
这段代码做了三件事:
- 计算当前ATR和基准ATR的比值
- 用比值调整仓位,并限制在0.3倍到3倍之间
- 用固定比例风险模型做二次校验,防止仓位过大
注意:限制调整范围非常重要。我见过有人不设限,结果波动率极低时仓位放大到10倍,市场一恢复波动,直接爆仓。0.3到3倍是个比较合理的范围,你可以根据自己的风险偏好调整。
波动率调整的几种变体
上面讲的是最基础的版本。实际应用中,还有几种变体,我简单介绍一下:
1. 指数平滑波动率
用EWMA(指数加权移动平均)代替简单移动平均。这样近期的波动率权重更大,反应更快。
def calc_ewma_vol(price, lambda_=0.94):
log_ret = np.log(price / price.shift(1))
var = log_ret.ewm(span=2/(1-lambda_)-1).var()
return np.sqrt(var)
lambda取0.94是RiskMetrics的标准做法。我个人觉得做套利的话,反应太快反而容易过度调整,0.97左右更合适。
2. 分位数调整
不是直接用波动率比值,而是看当前波动率处于历史分布的什么位置。比如:
def quantile_adjustment(atr, window=252):
rank = atr.rolling(window).rank(pct=True)
# 分位数越低,仓位越大;分位数越高,仓位越小
pos_factor = 1 - rank # 或者用 2 - 2*rank 等
return pos_factor.clip(0.2, 2.0)
这种方法的好处是,不受极端值影响。即使波动率突然飙到天上去,调整因子也不会无限趋近于0。
3. 波动率锥(Volatility Cone)
这是比较高级的玩法。不同时间周期的波动率表现不同,比如短期波动率高、长期波动率低。波动率锥就是看当前波动率在同类周期中的位置。
具体实现比较复杂,这里不展开。简单说,就是分别计算5天、10天、20天、60天的波动率,然后看它们的历史分布。如果20天波动率处于90%分位,那说明中期波动率异常高,仓位应该大幅降低。
实战中的注意事项
讲几个我踩过的坑:
第一,不要频繁调整仓位。 波动率每天变,仓位也跟着每天变?那交易成本就够你受的。我建议每周调整一次,或者只在开仓时调整。持仓期间除非波动率发生重大变化(比如超过2倍),否则不动。
第二,注意波动率的聚集效应。 波动率高的时期往往持续一段时间。如果你在波动率飙升时减仓,之后波动率一直高,你就一直轻仓,可能错过行情。我的做法是:用慢速波动率(比如60天ATR)做仓位基准,用快速波动率(比如10天ATR)做风险预警。两者结合,既不过度反应,也不反应迟钝。
第三,不同品种的波动率特性不同。 股指期货的波动率比商品期货低,但爆发力强。商品里,原油和黄金的波动率特性又不一样。我建议每个品种单独计算基准波动率,不要用统一标准。
总结一下:动态仓位调整不是万能药,但它能让你在波动率变化时保持风险一致。我做了这么多年交易,最大的体会就是——活得久比赚得快更重要。动态仓位调整,就是帮你活得久的工具之一。
知识体系图
下面这张图总结了本章的核心逻辑:
嗯,这张图把整个流程串起来了。从输入数据开始,经过波动率计算、仓位调整、风险校验,最后输出最终仓位。每一步都有对应的代码实现,你可以直接拿去用。
好了,这一讲就到这里。动态仓位调整是个大话题,今天只讲了最核心的部分。如果你在实际应用中遇到问题,欢迎交流。记住,仓位管理不是限制你赚钱,而是保护你不亏大钱。
公众号:蓝海资料掘金营,微信deep3321