3. 低波动因子构建:波动率的计算、排序与回测框架

各位同学,咱们今天聊点实在的。低波动策略的核心,说白了就是找到那些「涨得慢、跌得也慢」的股票。那怎么量化这个「慢」呢?答案就是——波动率。

我个人习惯把波动率比作股票的「心跳」。心跳太快,容易猝死;心跳太慢,可能是个僵尸。我们要找的,就是那种心跳平稳、长期健康的股票。

3.1 历史波动率:最朴素的度量

历史波动率,就是看过去一段时间股价的「上蹿下跳」程度。计算方式很简单:

  1. 取对数收益率:ln(Pt / Pt-1)
  2. 计算标准差:对收益率序列求标准差
  3. 年化处理:乘以 sqrt(252)(一年约252个交易日)

嗯,这里要注意一点。我见过很多新手直接拿收盘价算波动率,结果被分红、送股搞得数据全是坑。一定要用复权价格,最好是后复权。

核心公式:
历史波动率 = std(ln(Pt/Pt-1)) × sqrt(252)

代码实现也很直接:

import numpy as np
import pandas as pd

def calc_historical_vol(price_series, window=20):
    """
    计算历史波动率
    :param price_series: 复权价格序列
    :param window: 滚动窗口,默认20天
    """
    log_ret = np.log(price_series / price_series.shift(1))
    hist_vol = log_ret.rolling(window).std() * np.sqrt(252)
    return hist_vol

你想想看,为什么用20天?其实没有标准答案。我在做A股回测时,20天刚好覆盖一个月的交易天数,能较好地捕捉短期波动特征。但如果你做的是高频策略,可能用5天更合适。

3.2 已实现波动率:更精细的度量

历史波动率有个问题——它只用了收盘价。但一天之内,股价可能上蹿下跳好几次。这时候就需要已实现波动率了。

已实现波动率,说白了就是把一天内的分钟收益率平方加起来。公式长这样:

已实现波动率公式:
RVt = sqrt( Σ ri² ) × sqrt(252)

其中 ri 是第 i 分钟的收益率。

我曾经在实盘中吃过亏。用5分钟数据算RV,结果发现和用30分钟数据算出来的结果差异巨大。后来才意识到,采样频率越高,噪声越大。我个人建议:A股用5分钟或15分钟数据比较合适,既能捕捉日内波动,又不会引入太多微观结构噪声。

def calc_realized_vol(minute_returns, freq=5):
    """
    计算已实现波动率
    :param minute_returns: 分钟收益率序列
    :param freq: 采样频率(分钟)
    """
    # 重采样到指定频率
    resampled = minute_returns.resample(f'{freq}min').sum()
    rv = np.sqrt((resampled ** 2).sum()) * np.sqrt(252)
    return rv
避坑指南: 计算已实现波动率时,一定要剔除开盘前和收盘后的数据。我刚开始做的时候没注意,把集合竞价的数据也算进去了,结果波动率直接翻倍。

3.3 波动率排序与分组

算完波动率,下一步就是排序和分组。这一步很关键,直接决定了你的因子是否有效。

我的做法是:

  1. 每月末,计算每只股票过去20天的波动率
  2. 按波动率从小到大排序,分成10组(Decile)
  3. 做多最低波动组,做空最高波动组

你可能会问:为什么是10组?不是5组或20组?

嗯,这里有个经验之谈。分组太少,组内股票差异不够大;分组太多,每组股票太少,统计意义下降。10组是个折中方案,也是学术界常用的做法。

分组 波动率范围 预期表现
第1组(最低波动) 0% - 15% 低收益、低风险
第5组(中等波动) 15% - 30% 中等收益、中等风险
第10组(最高波动) 30%以上 高收益、高风险
注意: 低波动不等于低收益。很多研究表明,长期来看,低波动组合的风险调整后收益反而更高。这就是所谓的「低波动异象」。

3.4 低波动因子的回测框架搭建

好了,现在因子有了,分组也做好了。接下来就是搭建回测框架,验证这个因子到底能不能赚钱。

我习惯用事件驱动型回测框架。核心逻辑是这样的:

class LowVolBacktest:
    def __init__(self, data, rebalance_freq='M'):
        self.data = data  # 包含价格、波动率的数据
        self.rebalance_freq = rebalance_freq
        
    def run(self):
        # 1. 在每个调仓日计算波动率
        # 2. 按波动率排序分组
        # 3. 买入最低波动组,卖出最高波动组
        # 4. 计算组合收益
        # 5. 统计绩效指标
        pass

具体实现时,有几个关键点要注意:

  • 避免前视偏差:计算波动率时只能用历史数据,不能用未来数据
  • 考虑交易成本:低波动策略换手率不高,但也不能忽略
  • 市值中性化:低波动股票往往是大盘股,要剔除市值影响

我曾经犯过一个低级错误——用当天的收盘价计算波动率,然后当天就买入。结果回测收益高得离谱,实盘却一塌糊涂。后来才发现,这是典型的前视偏差。

我的建议: 回测时,用T-1日的波动率来预测T日的收益。这样更接近真实交易场景。

下面这张图,是我整理的低波动因子构建与回测的完整流程:

低波动因子构建与回测流程 步骤1:数据获取 日线/分钟线价格 步骤2:波动率计算 历史/已实现波动率 步骤3:排序分组 10组Decile 步骤4:构建组合 做多低波/做空高波 步骤5:回测执行 事件驱动/定期调仓 步骤6:绩效评估 夏普比率/最大回撤 反馈优化 关键注意事项 • 避免前视偏差:只用历史数据计算波动率 • 考虑交易成本:滑点和佣金不可忽略 • 市值中性化:剔除市值对波动率的影响

这张图把整个流程串起来了。从数据获取到绩效评估,每一步都有坑。但只要你按照这个框架来,至少能保证回测结果不会太离谱。

最后说一句:回测只是起点,不是终点。我见过太多人回测收益翻倍,实盘却亏得底裤都不剩。原因无他——回测框架太理想化。所以,在搭建框架时,尽量把现实因素考虑进去,比如交易成本、流动性限制、冲击成本等。

本章小结:
• 历史波动率:简单但粗糙,适合快速筛选
• 已实现波动率:精细但数据要求高,适合深度分析
• 排序分组:10组是黄金标准,兼顾统计意义和可操作性
• 回测框架:事件驱动型最灵活,但要注意前视偏差

专注资料整理