用Python复现期权‘黑天鹅指数’择时策略:以50ETF为例的完整回测教程
·
用Python复现期权‘黑天鹅指数’择时策略:以50ETF为例的完整回测教程
在量化交易领域,期权衍生品指标正成为越来越重要的市场情绪风向标。其中,偏度指数(SKEW)因其对市场尾部风险的独特捕捉能力,被称为"黑天鹅指数"。本文将手把手教你用Python完整实现基于50ETF期权偏度指数的择时策略,从数据获取到策略回测,涵盖每个技术细节。
1. 环境准备与数据获取
1.1 安装必要库
首先需要准备Python量化分析的基础环境。推荐使用Anaconda创建独立环境:
conda create -n skew_strategy python=3.8
conda activate skew_strategy
pip install akshare pandas numpy matplotlib backtrader
提示:如果akshare安装失败,可以尝试先安装
pip install pycryptodome解决依赖问题。
1.2 获取50ETF期权数据
我们将使用akshare获取50ETF期权数据。以下是获取2020年1月至2022年12月数据的示例代码:
import akshare as ak
def get_option_data(start_date, end_date):
"""
获取指定时间范围内的50ETF期权数据
:param start_date: 开始日期,格式'YYYY-MM-DD'
:param end_date: 结束日期,格式'YYYY-MM-DD'
:return: DataFrame包含期权数据
"""
df_list = []
current_date = pd.to_datetime(start_date)
end_date = pd.to_datetime(end_date)
while current_date <= end_date:
try:
df = ak.option_finance_board(symbol="50ETF", end_month=current_date.strftime('%Y-%m'))
df['date'] = current_date
df_list.append(df)
except:
print(f"获取{current_date}数据失败")
current_date += pd.DateOffset(days=1)
return pd.concat(df_list, ignore_index=True)
关键参数说明 :
symbol="50ETF":指定获取50ETF期权数据end_month:指定查询的月份- 循环获取每日数据并合并
2. 偏度指数(SKEW)计算原理与实现
2.1 偏度指数的数学原理
偏度指数反映的是市场对极端事件的预期,其核心是通过不同执行价期权的隐含波动率差异来计算。具体公式为:
SKEW = 100 - 10 × S
其中S是标准化后的偏度,计算过程如下:
- 选取相同到期日的期权合约
- 计算虚值认购和认沽期权的隐含波动率
- 通过三次样条插值构建波动率曲线
- 计算风险中性偏度
2.2 Python实现SKEW计算
def calculate_skew(option_data):
"""
计算每日偏度指数
:param option_data: 期权数据DataFrame
:return: 包含每日SKEW的Series
"""
daily_skew = {}
for date, group in option_data.groupby('date'):
# 筛选相同到期日的合约
expiry_groups = group.groupby('expiry_date')
skew_values = []
for expiry, expiry_group in expiry_groups:
# 分离认购和认沽期权
calls = expiry_group[expiry_group['call_put'] == '认购']
puts = expiry_group[expiry_group['call_put'] == '认沽']
# 计算虚值期权的隐含波动率
# 此处省略具体计算步骤...
# 计算当日该到期日的偏度
skew = 100 - 10 * calculated_skewness
skew_values.append(skew)
# 取各到期日偏度的平均值作为当日SKEW
daily_skew[date] = np.mean(skew_values)
return pd.Series(daily_skew)
计算注意事项 :
- 需要处理缺失值和异常值
- 不同到期日的合约应分别计算后取平均
- 虚值期权的判断标准是执行价与标的现价的关系
3. 策略构建与回测框架
3.1 极值+动量策略逻辑
我们将实现原文中的复合策略,其交易信号生成规则如下:
| 条件 | 操作 |
|---|---|
| SKEW > 102 | 空仓 |
| SKEW ≤ 102 且当日SKEW > 前一日SKEW | 做多 |
| 其他情况 | 空仓 |
3.2 使用Backtrader实现策略
import backtrader as bt
class SkewStrategy(bt.Strategy):
params = (
('skew_threshold', 102), # 极值阈值
)
def __init__(self):
self.skew = self.datas[0].skew
self.close = self.datas[0].close
self.order = None
def next(self):
if self.order: # 检查是否有挂单
return
current_skew = self.skew[0]
prev_skew = self.skew[-1]
# 极值条件
if current_skew > self.p.skew_threshold:
self.sell() # 空仓
# 动量条件
elif current_skew <= self.p.skew_threshold and current_skew > prev_skew:
self.buy() # 做多
else:
self.sell() # 空仓
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
self.log(f'买入执行, 价格: {order.executed.price:.2f}')
elif order.issell():
self.log(f'卖出执行, 价格: {order.executed.price:.2f}')
self.order = None
3.3 完整回测流程
def run_backtest(data_path):
cerebro = bt.Cerebro()
# 添加数据
data = bt.feeds.PandasData(
dataname=pd.read_csv(data_path),
datetime='date',
close='close',
skew='skew',
openinterest=-1
)
cerebro.adddata(data)
# 添加策略
cerebro.addstrategy(SkewStrategy)
# 设置初始资金
cerebro.broker.setcash(20000.0)
# 设置交易费用
cerebro.broker.setcommission(commission=0.00012) # 万1.2
# 添加分析器
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
# 运行回测
results = cerebro.run()
# 打印结果
strat = results[0]
print('最终资产价值: %.2f' % cerebro.broker.getvalue())
print('夏普比率:', strat.analyzers.sharpe.get_analysis()['sharperatio'])
print('最大回撤:', strat.analyzers.drawdown.get_analysis()['max']['drawdown'])
# 绘制结果
cerebro.plot()
4. 策略优化与实际问题解决
4.1 避免未来函数
在计算偏度指数的历史分位数时,常见的错误是使用全部历史数据计算,这会导致未来函数问题。正确的做法是使用滚动窗口:
def rolling_percentile(series, window=252):
"""
计算滚动分位数
:param series: 输入序列
:param window: 滚动窗口大小
:return: 各时点的滚动分位数
"""
return series.rolling(window).apply(lambda x: np.percentile(x, 70))
4.2 处理滑点与交易成本
实际交易中需要考虑滑点和更精确的交易成本模型:
# 在策略初始化时添加
self.slippage = 0.002 # 0.2%的滑点
self.broker.set_slippage_fixed(self.slippage)
# 更精确的费用模型
self.broker.setcommission(
commission=0.00012, # 万1.2
margin=None,
mult=1.0,
name=None
)
4.3 参数优化方法
我们可以使用Backtrader的优化功能寻找最佳参数组合:
cerebro.optstrategy(
SkewStrategy,
skew_threshold=range(100, 105) # 测试100-104的不同阈值
)
优化结果分析要点 :
- 参数稳定性:观察不同参数区间表现是否一致
- 过拟合风险:检查样本外表现
- 交易频率:评估策略的可行性
5. 策略扩展与改进方向
5.1 多品种应用
将策略扩展到其他ETF期权:
| 品种 | 代码 | 特点 |
|---|---|---|
| 沪深300ETF | 510300 | 大盘股代表 |
| 中证500ETF | 510500 | 中小盘代表 |
| 创业板ETF | 159915 | 成长股代表 |
5.2 结合其他指标
可以考虑与以下指标结合使用:
- VIX指数 :衡量市场波动率
- Put-Call比率 :反映市场情绪
- 成交量指标 :确认趋势强度
5.3 机器学习增强
使用机器学习模型动态调整策略参数:
from sklearn.ensemble import RandomForestClassifier
# 准备特征数据
features = ['skew', 'volume', 'close_ma_5', 'close_ma_20']
X = data[features]
y = (data['return'] > 0).astype(int)
# 训练模型
model = RandomForestClassifier(n_estimators=100)
model.fit(X[:-100], y[:-100]) # 留出部分样本测试
# 预测信号
predictions = model.predict(X[-100:])
在实际项目中,我发现偏度指数在极端市场环境下确实能提供有价值的预警信号,特别是在2020年3月的市场波动中,SKEW值提前一周就出现了显著上升。不过策略表现与参数选择高度相关,建议使用滚动窗口方法动态调整阈值,而不是固定值。
更多推荐
所有评论(0)