问题描述
解决方案
在 PTrade 策略中使用 GARCH(广义自回归条件异方差)模型预测波动率,主要依赖于 PTrade 支持的第三方库 arch。GARCH 模型常用于量化交易中的风险管理、期权定价或基于波动率的择时策略。
以下是关于如何在 PTrade 中实现 GARCH 模型的详细说明和完整代码示例。
核心步骤
- 获取数据:使用
get_history获取足够长的历史收盘价数据(通常建议 250 个数据点以上以保证模型收敛)。 - 数据预处理:计算对数收益率,并将其放大 100 倍(转换为百分比收益率)。注意:GARCH 优化器在处理非常小的数值(如 0.0001)时容易失败,因此放大数值是必要的技巧。
- 模型拟合:使用
arch.arch_model定义模型(通常为 GARCH(1,1)),并调用fit()进行拟合。 - 预测:使用
forecast()方法预测下一期的方差,并转换为波动率。 - 交易逻辑:根据预测的波动率制定交易决策(例如:波动率过高时平仓避险,波动率低时持仓)。
完整策略代码
import numpy as np
import pandas as pd
from arch import arch_model
def initialize(context):
"""
初始化函数
"""
# 设置标的股票:以上证指数ETF为例,波动率特征较明显
g.security = '510300.SS'
set_universe(g.security)
# 设置GARCH模型所需的历史窗口长度,建议至少一年数据
g.window_size = 500
# 设置波动率阈值,用于触发交易信号
# 注意:这里的阈值是基于放大100倍后的收益率计算的
g.vol_threshold = 1.5
# 设置每天定时运行,避免盘中每分钟计算导致性能问题
# GARCH计算量较大,建议在盘前或收盘运行
run_daily(context, trade_logic, time='09:35')
def trade_logic(context):
"""
每日交易逻辑函数
"""
security = g.security
# 1. 获取历史收盘价数据
# 获取过去 g.window_size 天的数据
hist = get_history(g.window_size, '1d', 'close', security, fq='pre')
# 如果数据长度不足,直接返回
if len(hist) < g.window_size:
log.info("历史数据不足,跳过计算")
return
# 2. 数据预处理
prices = hist['close'].values
# 计算对数收益率: ln(Pt / Pt-1)
# 加上 1e-8 防止除零错误(虽然价格通常不为0)
log_returns = np.diff(np.log(prices))
# 【关键步骤】将收益率放大100倍,转换为百分比收益率
# 这有助于 GARCH 模型的优化器更好地收敛
returns = log_returns * 100
# 3. 定义并拟合 GARCH 模型
try:
# 定义模型:均值模型为Constant,波动率模型为GARCH,p=1, q=1
# dist='Normal' 使用正态分布假设
am = arch_model(returns, mean='Constant', vol='Garch', p=1, q=1, dist='Normal')
# 拟合模型,disp='off' 禁止打印拟合过程中的日志
res = am.fit(disp='off')
# 4. 预测下一期的波动率
# horizon=1 表示预测往后一步
forecasts = res.forecast(horizon=1)
# 获取预测的方差 (Variance)
# forecasts.variance 是一个 DataFrame,取最后一行第一列
next_variance = forecasts.variance.iloc[-1, 0]
# 将方差转换为标准差(波动率)
next_volatility = np.sqrt(next_variance)
log.info("当前预测波动率(百分比): %.4f" % next_volatility)
except Exception as e:
log.error("GARCH 模型拟合失败: %s" % e)
return
# 5. 交易执行逻辑
# 获取当前持仓
position = get_position(security).amount
curr_cash = context.portfolio.cash
# 策略逻辑:
# 如果预测波动率高于阈值,视为风险过大,清仓观望
# 如果预测波动率低于阈值,视为行情平稳,全仓买入
if next_volatility > g.vol_threshold:
if position > 0:
log.info("波动率过高 (%.4f > %.4f),触发卖出信号" % (next_volatility, g.vol_threshold))
order_target(security, 0)
else:
if position == 0:
log.info("波动率处于低位 (%.4f <= %.4f),触发买入信号" % (next_volatility, g.vol_threshold))
order_value(security, curr_cash)
def handle_data(context, data):
# 必须实现的函数,但在本策略中逻辑已通过 run_daily 处理
pass
代码关键点解析
-
库的导入:
PTrade 环境内置了arch库(版本通常为 3.2),直接from arch import arch_model即可使用。 -
数据缩放 (Scaling):
代码中returns = log_returns * 100是非常重要的一步。如果不放大,收益率数值极小(例如 0.0002),GARCH 的优化算法很难找到最优解,经常会报错或不收敛。 -
异常处理:
GARCH 模型拟合是一个数值优化过程,可能会因为数据异常或参数问题导致拟合失败。使用try...except块包裹拟合代码,可以防止策略因数学计算错误而意外终止。 -
性能优化:
GARCH 计算相对耗时。在 PTrade 中,如果选择分钟级别回测,建议不要在handle_data(每分钟运行)中直接调用拟合过程,而是使用run_daily在每天开盘时计算一次,或者降低计算频率。 -
预测结果读取:
res.forecast(horizon=1)返回的结果包含variance(方差)。我们需要取最后一行(基于最新数据预测)的值,并开根号得到波动率(标准差)。
常见问题 Q&A
Q: 为什么我的 GARCH 模型总是报错 "Inequality constraints incompatible"?
A: 这通常是因为输入的数据数值太小。请确保你计算的是百分比收益率(即乘以 100),而不是原始的小数收益率。
Q: 如何选择 GARCH 的参数 p 和 q?
A: 在金融时间序列中,GARCH(1,1) 通常足以捕捉大部分波动率聚集特征。如果需要更复杂的模型,可以通过 AIC 或 BIC 准则进行模型选择,但在实盘策略中,简单的 GARCH(1,1) 鲁棒性通常更好。
Q: 这个策略可以直接用于实盘吗?
A: 示例代码仅展示了技术实现。GARCH 预测的是波动率,波动率高并不代表跌,也可能代表暴涨。通常 GARCH 会配合趋势指标(如均线)或用于期权策略(如波动率套利)使用,单独作为择时指标风险较大。
Q: PTrade 回测速度很慢怎么办?
A: 减少 get_history 获取的数据长度(例如从 1000 减到 250),或者减少调仓频率。GARCH 的拟合是计算密集型任务。