4、波动率目标策略:根据市场波动率动态调整仓位,实现稳定的风险暴露
做量化交易这些年,我见过太多人栽在同一个坑里——市场波动大的时候满仓干,波动小的时候也满仓干。结果呢?大波动时一把亏光,小波动时赚不到钱。说白了,这就是典型的「风险暴露失控」。
波动率目标策略,就是来解决这个问题的。它的核心思想很简单:市场波动大的时候,我少买点;市场波动小的时候,我多买点。这样,不管市场怎么折腾,你的账户风险敞口始终保持在同一个水平线上。
核心公式:
目标仓位 = 目标波动率 / 当前市场波动率 × 基准仓位
举个例子:假设你设定的目标波动率是20%,当前市场波动率是40%,那你的仓位就应该是 20%/40% = 50%。
为什么需要波动率目标?
我刚开始做CTA策略的时候,犯过一个低级错误。当时回测曲线漂亮得不得了,年化收益30%,最大回撤才8%。结果实盘跑了三个月,回撤直接干到25%。
后来复盘才发现,问题出在波动率上。回测那段时间市场波动率一直很低,我的策略仓位很重。但实盘一上来就遇到波动率飙升,仓位没变,风险却翻了好几倍。嗯,这就是典型的「回测幸存者偏差」。
波动率目标策略能帮你解决三个核心问题:
- 风险预算一致:不管市场怎么变,你的VaR(在险价值)始终保持稳定
- 避免黑天鹅:高波动时自动减仓,相当于给账户上了保险
- 提升复利效应:低波动时加仓,高波动时减仓,长期下来资金曲线更平滑
如何计算当前波动率?
这里我分享两种常用的方法,都是我在项目中实际用过的。
方法一:简单历史波动率
用过去N天的收益率标准差来估算。我个人习惯用20个交易日,也就是差不多一个自然月。
import numpy as np
import pandas as pd
def calc_historical_volatility(prices, window=20):
"""
计算历史波动率
prices: 价格序列
window: 滚动窗口,默认20天
"""
log_returns = np.log(prices / prices.shift(1))
rolling_vol = log_returns.rolling(window).std() * np.sqrt(252)
return rolling_vol
小提示:为什么要乘以sqrt(252)?因为我们要把日波动率年化。252是一年的交易日数。如果你做日内交易,那就乘以sqrt(252*390),390是美股一天的分钟数。
方法二:EWMA(指数加权移动平均)
这个方法更灵敏,对近期数据赋予更高权重。我记得在2018年做股指期货策略时,就用这个方法提前捕捉到了波动率突变。
def calc_ewma_volatility(prices, lambda_=0.94):
"""
EWMA波动率估计
lambda_: 衰减因子,RiskMetrics推荐0.94
"""
log_returns = np.log(prices / prices.shift(1))
var = log_returns.ewm(alpha=1-lambda_).var()
return np.sqrt(var * 252)
注意:EWMA的lambda参数很敏感。0.94适合日频数据,如果是小时级别数据,我建议用0.97左右。这个参数调不好,波动率估计会严重滞后。
目标波动率怎么定?
这个问题没有标准答案。我一般会参考两个维度:
| 策略类型 | 建议目标波动率 | 说明 |
|---|---|---|
| 趋势跟踪 | 15%-25% | 趋势策略本身波动大,目标设低一点 |
| 均值回归 | 10%-15% | 回归策略相对稳定,可以适当提高 |
| 高频策略 | 5%-10% | 高频追求稳定,波动率要压得低 |
| 多策略组合 | 8%-12% | 组合本身有分散效果,目标适中 |
你想想看,如果你做的是趋势跟踪,目标波动率设到30%,那遇到大行情时仓位可能被压到极低,反而吃不到利润。反过来,设得太低,平时仓位太重,一个小波动就能让你爆仓。
完整的波动率目标策略实现
下面这个代码是我在实盘中使用过的简化版本。它包含了波动率计算、仓位调整、风险监控三个模块。
class VolatilityTargetStrategy:
def __init__(self, target_vol=0.20, max_leverage=3.0):
self.target_vol = target_vol # 目标年化波动率
self.max_leverage = max_leverage # 最大杠杆倍数
self.current_vol = None
def update_volatility(self, prices):
"""更新当前波动率"""
self.current_vol = calc_historical_volatility(prices)
return self.current_vol
def calculate_position(self, base_position):
"""根据波动率调整仓位"""
if self.current_vol is None or self.current_vol == 0:
return base_position
# 波动率调整因子
vol_ratio = self.target_vol / self.current_vol
# 限制最大杠杆
adjusted_position = base_position * vol_ratio
adjusted_position = np.clip(adjusted_position,
-self.max_leverage,
self.max_leverage)
return adjusted_position
def risk_check(self, position, capital):
"""风险检查"""
position_value = position * capital
risk_exposure = position_value * self.current_vol
if risk_exposure > self.target_vol * capital * 1.2:
print(f"⚠️ 风险暴露超标: {risk_exposure:.2%}")
return False
return True
波动率目标策略的核心逻辑
为了让你更直观地理解,我画了一张流程图。这张图展示了一个完整的波动率目标策略是如何运作的。
实战中的避坑指南
我曾经在实盘里踩过一个坑,分享出来希望你别重蹈覆辙。
坑一:波动率突变时的滞后性
历史波动率天然有滞后性。当市场突然暴跌时,波动率还没算出来,你的仓位还是满的。我建议加一个「波动率突变检测」:如果当日涨跌幅超过过去20日均值的3倍标准差,立即触发减仓,不等波动率计算。
坑二:目标波动率设得太激进
我记得有个学员,把目标波动率设到30%,结果遇到2015年股灾,波动率飙到80%,仓位被压到30%以下。虽然没亏钱,但反弹时仓位太低,错过了大行情。后来我建议他把目标波动率降到15%,效果反而更好。
坑三:忽略交易成本
波动率目标策略需要频繁调仓。如果交易成本太高,调仓的摩擦成本会吃掉所有收益。我一般会加一个「调仓阈值」:只有当目标仓位和当前仓位偏差超过5%时,才执行调仓。
总结一下:
波动率目标策略不是万能药,但它能帮你解决最头疼的问题——资金曲线的大起大落。说白了,就是让风险暴露变得可控。你想想看,如果你的账户每天的风险敞口都一样,那睡觉是不是也踏实多了?
我的个人习惯:每次上线新策略前,我都会先用波动率目标跑三个月模拟盘。看看在不同市场环境下,仓位是怎么变化的。如果发现某个极端行情下仓位被压到0,那就说明目标波动率设得太低了,需要调整。