“投资范围包括跟踪道琼斯工业指数(DJI)的工具(如DIA ETF)和现金(如BIL)。回归方程用于调整长期波动率,变量为过去24个月DJI月度回报的标准差和短期波动率。投资者在股票指数和无风险利率之间分配财富,使用公式确定最优持仓权重。公式中,风险厌恶参数为3,回报预测基于调整后的长期波动率。投资组合每月重新平衡,初始估算期为60个月。”
资产类别:差价合约(CFDs)、交易所交易基金(ETFs)、基金、期货 | 区域:美国 | 频率:月度 | 市场:股票 | 关键词:波动率
策略概述
投资范围包括单一跟踪道琼斯工业指数(DJI)的工具(例如,DIA ETF)和现金(例如,BIL)。回归方程为𝐴𝐷𝐽_𝐿𝑉𝑡 = 𝐿𝑉𝑡 ‒ 𝛽𝑜𝑙𝑠,𝑡 ∙ 𝑆𝑉𝑡,其中自变量为长期波动率𝐿𝑉𝑡,即过去24个月内DJI月度回报的标准差,年化方法是乘以12的平方,短期波动率𝑆𝑉𝑡基于Welch和Goyal(2008)的方法进行修正,使用OLS贝塔来估计因变量调整后的长期波动率𝐴𝐷𝐽_𝐿𝑉𝑡。
投资者在股票指数和无风险利率之间分配财富,按照公式𝜔^∗𝑡 = 𝑟𝑡+1/(𝛾 * 𝜎𝑡+1^2)确定股票指数的最优持仓权重,其中𝑟𝑡+1是t+1月份的回报预测,𝛾=3为风险厌恶参数,𝜎𝑡+1^2是使用过去60个月的股票回报方差来估计的股票方差。回归公式用于计算下个月的回报预测,独立变量为调整后的长期波动率。初始估算期为60个月,投资组合基于该因子加权,并假设每月重新平衡。
策略合理性
调整后的长期波动率在预测股票回报方面表现出色,尤其是与其他流行预测指标结合时表现更佳。更重要的是,调整后的长期波动率指标所揭示的正向风险-回报关系符合资产定价理论的预测。从上世纪60年代开始,波动率与回报之间的关系便备受关注,但至今仍困扰着量化分析师、科学家和分析师们。Qiu、Rui、Liu、Jing和Li、Yan(2022)的研究为使用调整后的长期波动率预测股票回报作出了重要贡献,帮助理解波动率与预期回报之间复杂的实证关系。
论文来源
Adjusted Long-Term Volatility and Stock Return Predictability [点击浏览原文]
- Rui Qiu, Jing Liu, Yan Li, 四川大学,西南交通大学
<摘要>
我们设计了一个调整后的长期波动率(ADJ_LV)指标,通过消除短期波动率的干扰信息来研究ADJ_LV对股票回报的预测能力。在2000年至2019年的样本中,考虑了19个流行的预测模型,ADJ_LV能够正向预测S&P 500指数下个月的回报,相应的单变量模型表现出最佳的预测性能,样本内调整后的R平方为3.825%,样本外R平方为3.356%,回报增益为5.976%,确定性等价回报增益为4.708,夏普比率增益为0.394。将ADJ_LV作为额外预测因子加入其他19个单变量模型中,显著提高了样本内和样本外的预测性能。此外,我们发现ADJ_LV对长期(1-12个月)股票回报也具有预测能力,并且能够预测行业组合及按规模、市净率、经营风险和投资风险形成的组合的回报。ADJ_LV对股票回报的预测能力在道琼斯工业指数的预测中也表现出稳健性,并且使用替代估算的ADJ_LV同样有效。


回测表现
| 年化收益率 | 3.23% |
| 波动率 | N/A |
| Beta | 0.037 |
| 夏普比率 | N/A |
| 索提诺比率 | -0.466 |
| 最大回撤 | 0% |
| 胜率 | 60% |
完整python代码
from AlgorithmImports import *
from pandas.core.frame import DataFrame
import pandas as pd
import statsmodels.api as sm
# endregion
class ModifiedVolatilityPredictsDJIReturns(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
self.leverage:int = 3
self.equity:Symbol = self.AddEquity("DIA", Resolution.Minute, leverage=self.leverage).Symbol
self.cash:Symbol = self.AddEquity("BIL", Resolution.Minute, leverage=self.leverage).Symbol
self.gamma:float = 3.
self.estimation_period:int = 60
self.std_period:int = 24
self.months_in_year:float = 12.
self.allocation_cap:List[float] = [-0.5, 1.5]
self.recent_month:int = -1
def OnData(self, data: Slice) -> None:
if not(data.ContainsKey(self.equity) and data.ContainsKey(self.cash) and data[self.equity] and data[self.cash]):
return
# monthly rebalance
if self.Time.month == self.recent_month:
return
self.recent_month = self.Time.month
# get monthly closes
period:int = (self.estimation_period + self.std_period) * 2 + 1
history:DataFrame = self.History(self.equity, period * 31, Resolution.Daily)['close'].unstack(level=0)
history = history.groupby(pd.Grouper(freq='M')).last()[self.equity]
if len(history) >= period:
history = history.iloc[-period:]
returns:DataFrame = history.pct_change().iloc[1:]
# long-term volatility
lv:DataFrame = returns.rolling(self.std_period).std() * np.sqrt(self.months_in_year)
lv = lv.dropna()
# short-term volatility
# SVAR is stock variance as in Welch and Goyal (2008)
svar:DataFrame = (returns ** 2).rolling(self.std_period).sum()
svar = svar.dropna()
sv:DataFrame = np.sqrt(svar / self.months_in_year)
lv_mean:DataFrame = lv.rolling(self.estimation_period).mean()
lv_mean = lv_mean.dropna()
sv_mean:DataFrame = sv.rolling(self.estimation_period).mean()
sv_mean = sv_mean.dropna()
# beta estimation
beta_factor:np.ndarray = np.array([((lv.iloc[i-self.estimation_period:i] - lv_mean.iloc[i]) * (sv.iloc[i-self.estimation_period:i] - sv_mean.iloc[i])).sum() for i in range(-1, -(len(lv_mean)+1), -1)])
beta_devisor:np.ndarray = np.array([((sv.iloc[i-self.estimation_period:i] - sv_mean.iloc[i]) ** 2).sum() for i in range(-1, -(len(lv_mean)+1), -1)])
beta:np.ndarray = beta_factor / beta_devisor
adj_lv:np.ndarray = (lv.iloc[-len(beta):].values - (beta * sv.iloc[-len(beta):].values))[-self.estimation_period:]
# regression to predict return
model = self.multiple_linear_regression(adj_lv[:-1], returns.iloc[-len(adj_lv):].values[1:])
ret_pred:float = model.predict(adj_lv[-1])
variance_pred:float = svar.iloc[-1] #lv.iloc[-1] ** 2
# allocation
equity_allocation:float = (ret_pred / (self.gamma * variance_pred))[-1]
equity_allocation = min(max(equity_allocation, min(self.allocation_cap)), max(self.allocation_cap))
cash_allocation:float = 1. - equity_allocation
# trade execution
self.SetHoldings(self.equity, equity_allocation)
self.SetHoldings(self.cash, cash_allocation)
else:
if self.Portfolio.Invested:
self.Liquidate()
def multiple_linear_regression(self, x:np.ndarray, y:np.ndarray):
x:np.ndarray = np.array(x).T
# x = sm.add_constant(x)
result = sm.OLS(endog=y, exog=x).fit()
return result
