9、流动性螺旋的量化指标(三):Pastor-Stambaugh流动性因子

聊到流动性因子,很多人第一反应就是Amihud非流动性指标。没错,那个指标确实经典,用起来也顺手。但说实话,它有个硬伤——它只捕捉了价格冲击的大小,却忽略了价格冲击的 reversibility,也就是可逆性。

什么意思呢?

你想想看,如果一个订单把价格打下去5毛钱,但下一秒价格就弹回来了,这说明流动性其实还不错,市场有深度。但如果价格被打下去之后,半天都回不来,那才是真正的流动性枯竭。Amihud指标把这两种情况混为一谈,而Pastor-Stambaugh因子,恰恰就是冲着这个痛点来的。

核心思想:Pastor-Stambaugh流动性因子衡量的是——订单流对价格的持续性影响。影响越大,流动性越差。

9.1 因子构建逻辑

我记得第一次读Pastor和Stambaugh那篇2003年的论文时,第一反应是:这方法真够巧妙的。他们用了一个非常直观的回归模型:

r_{i,t+1} = α + β₁ · r_{i,t} + β₂ · sign(r_{i,t}) · v_{i,t} + ε_{i,t+1}

其中:

  • r_{i,t+1}:股票i在t+1期的超额收益
  • r_{i,t}:股票i在t期的收益
  • v_{i,t}:股票i在t期的成交额(以美元计)
  • sign(r_{i,t}):收益的符号(+1或-1)

关键就在那个交互项 sign(r_{i,t}) · v_{i,t} 上。它的系数β₂,就是我们要的流动性度量。

为什么会这样?

逻辑其实很简单:如果市场流动性好,那么今天的成交额(订单流)不应该对明天的收益有预测能力。但如果流动性差,大额订单会留下"痕迹"——比如今天的大额买入(正收益+高成交额)会推高价格,明天价格就会回调。这个回调的幅度,就是β₂。

嗯,这里要注意:β₂通常是负值。绝对值越大,说明流动性越差。

个人经验:我在做A股因子研究时发现,Pastor-Stambaugh因子在小市值股票上的信号特别强。大市值股票流动性本身就比较好,这个因子的区分度反而没那么明显。所以如果你做的是沪深300成分股,这个因子可能不如Amihud好用。

9.2 因子构建步骤

好,理论说完了,咱们直接上代码。我习惯用Python来实现,因为pandas处理面板数据确实方便。

import pandas as pd
import numpy as np
from statsmodels.regression.linear_model import OLS

def build_ps_factor(returns, volume, window=60):
    """
    构建Pastor-Stambaugh流动性因子
    
    参数:
        returns: DataFrame, 个股日收益率 (index=日期, columns=股票代码)
        volume: DataFrame, 个股日成交额 (index=日期, columns=股票代码)
        window: int, 滚动回归窗口
    
    返回:
        ps_factor: DataFrame, 流动性因子值
    """
    stocks = returns.columns
    ps_factor = pd.DataFrame(index=returns.index, columns=stocks)
    
    for stock in stocks:
        r = returns[stock].dropna()
        v = volume[stock].dropna()
        
        # 对齐数据
        data = pd.concat([r, v], axis=1, keys=['ret', 'vol'])
        data = data.dropna()
        
        if len(data) < window:
            continue
        
        # 构建回归变量
        data['sign_ret'] = np.sign(data['ret'])
        data['interaction'] = data['sign_ret'] * data['vol']
        data['ret_lag'] = data['ret'].shift(1)
        
        # 滚动回归
        for i in range(window, len(data)):
            y = data['ret'].iloc[i-window+1:i+1]
            X = data[['ret_lag', 'interaction']].iloc[i-window:i]
            X = sm.add_constant(X)
            
            try:
                model = OLS(y, X).fit()
                # 取交互项系数,取负值(流动性越好,值越大)
                ps_factor.iloc[i, stocks.get_loc(stock)] = -model.params['interaction']
            except:
                ps_factor.iloc[i, stocks.get_loc(stock)] = np.nan
    
    return ps_factor

避坑指南:我曾经在构建这个因子时踩过一个坑——成交额的单位问题。如果不同股票之间成交额量级差异太大(比如茅台和ST股),回归系数会严重失真。我建议先对成交额做横截面标准化,或者用成交额的对数形式。另外,窗口期我一般选60个交易日,太短了回归不稳定,太长了又不够灵敏。

9.3 因子在多因子模型中的应用

Pastor-Stambaugh因子不是孤立使用的。在实际的多因子模型中,它通常作为流动性维度的代表因子出现。我一般把它放在这样一个框架里:

Pastor-Stambaugh因子在多因子模型中的位置 多因子模型 动量因子 价值因子 流动性因子 Amihud指标 换手率 P-S因子 组合优化 风险预警 因子择时

在实际应用中,我通常把Pastor-Stambaugh因子和Amihud指标、换手率放在一起,构成一个流动性因子组合。这三个指标各有侧重:

流动性指标 衡量维度 优势 劣势
Amihud非流动性 价格冲击大小 计算简单,数据易得 忽略价格可逆性
换手率 交易活跃度 直观易懂 受市值影响大
Pastor-Stambaugh 价格冲击持续性 捕捉流动性螺旋 计算复杂,需高频数据

9.4 实战中的注意事项

说几个我在实战中总结出来的要点:

  1. 频率问题:Pastor-Stambaugh因子对数据频率很敏感。我试过用日频数据构建,效果还行。但如果用5分钟高频数据,因子质量会提升一个档次。当然,数据成本也上去了。
  2. 行业中性化:这个因子有明显的行业特征。金融股和科技股的流动性模式完全不同。我习惯在做完因子值之后,对每个行业做横截面标准化,消除行业偏差。
  3. 与市场状态的关系:在市场恐慌时期,Pastor-Stambaugh因子的预测能力会显著增强。说白了,流动性螺旋往往发生在最需要流动性的时候。我曾在2015年股灾期间回测过,这个因子对后续1-2周的收益预测能力是平时的3倍以上。
  4. 因子衰减速度:这个因子的半衰期大约在3-6个月。也就是说,如果你用半年前的数据算出来的因子值,基本没什么用了。我建议每个月更新一次因子值。

我的一个小技巧:在做多因子模型时,我通常把Pastor-Stambaugh因子作为风险因子而非alpha因子来使用。什么意思呢?就是用它来对冲流动性风险,而不是用它来选股。这样做的原因是,流动性因子的收益不稳定,但作为风险暴露的度量,它非常可靠。

9.5 一个简单的回测框架

最后,给你一个简单的回测思路。假设我们构建了一个包含Pastor-Stambaugh因子的多因子模型:

# 伪代码示例
def backtest_ps_factor(returns, volume, factors):
    # 1. 构建PS因子
    ps = build_ps_factor(returns, volume)
    
    # 2. 与其他因子合并
    all_factors = pd.concat([factors, ps], axis=1)
    
    # 3. 横截面回归(Fama-MacBeth风格)
    # 每月做一次回归,得到因子暴露
    exposures = []
    for month in unique_months:
        y = returns.loc[month]  # 下月收益
        X = all_factors.loc[month-1]  # 上月因子值
        model = OLS(y, X).fit()
        exposures.append(model.params)
    
    # 4. 分析PS因子的显著性
    ps_coefs = [e['ps'] for e in exposures]
    t_stat = np.mean(ps_coefs) / (np.std(ps_coefs) / np.sqrt(len(ps_coefs)))
    
    return t_stat

嗯,这个框架虽然简单,但足够你验证Pastor-Stambaugh因子在你关注的市场上是否有效。我建议你先在沪深300上跑一遍,看看结果。如果t统计量绝对值大于2,那恭喜你,这个因子在你的市场上是有价值的。

好了,关于Pastor-Stambaugh流动性因子,今天就聊到这里。记住,流动性因子的核心价值不在于它本身能赚多少钱,而在于它能帮你识别什么时候该跑。毕竟,在流动性枯竭的时候,你手里有再好的alpha策略也白搭。


无相订单流研究社 微信Lucian808555