老实说,Alpha#5这个因子刚一眼看过去,我就忍不住笑了出来:这哥们明显对VWAP有点意见,而且意见还不小。表达式如下:

Python
(rank((open - (sum(vwap, 10) / 10))) * (-1 * abs(rank((close - vwap)))))

简单拆解一下这坨表达式

我们先来分两段看:

第一段:

Python
rank((open - (sum(vwap, 10) / 10)))

这其实就是把今天的开盘价和过去10天VWAP的平均值做个差,然后取rank。

简单翻译一下:

「今天一开盘,价格是不是比最近10天市场成交的平均成本贵?」

所以,如果开盘价远高于过去10天的VWAP均值,那说明市场一开盘就挺激进(有点FOMO的味道);反之,开盘很低,那可能市场一觉醒来忽然心情不好。

再看第二段:

第二段:

Python
(-1 * abs(rank((close - vwap))))

这段的意思是:收盘价和今天VWAP差多少(绝对值),然后取rank,再乘个 -1。

这就很有意思了。你可以把这理解成一句话:

我不喜欢收盘价离VWAP太远的情况!

VWAP(成交量加权平均价)本质上就是市场今天的“平均心情”。你离它太远了,要么是被拉飞了,要么是被砸穿了。总之不是正常波动,容易出妖股。

这个因子的第二部分,实际上在惩罚那些收盘价离VWAP太远的股票(注意:是无论向上还是向下,只要太远就扣分)。

两段组合起来,就是Alpha#5的性格

第一段是对开盘价高不高的判断,第二段是对收盘价偏不偏离VWAP的“洁癖”。

所以综合来看,这个因子喜欢的股票大概是这样的:

  • 今天一开盘就比较强(或者至少比过去10天VWAP均值高);
  • 但收盘不能离VWAP太远,越贴着VWAP越好。

你可以想象一个画面:市场一早起床,精神抖擞,开得挺高,但随后大家保持冷静,情绪稳定,不疯不魔,最后收盘稳稳地靠近市场平均价格。这种股票是Alpha#5的菜。

这个Alpha在表达什么逻辑?

我的理解是:

它在抓一种“有自信但不过分”的开盘结构 ——

“开盘有情绪,但盘中没有脱缰。”

而这样的结构有可能出现在两种典型场景:

  1. 机构慢慢买入的行情:开盘高开,是有人主动接盘,但尾盘价格被慢慢压回来说明资金节奏温和,可能是量化或者机构在低波动分批进场。
  2. 资金试盘但没被打脸:主力高开试一下市场情绪,发现没人砸,自己也没拉得太猛,最终收在VWAP附近——后面有可能继续拉。

这和某些激进选手不同,他们喜欢追那种“拉爆了VWAP、收盘离地三尺”的票。Alpha#5更像一个温和派的选股者,带点谨慎地说:

我喜欢那些虽然强,但还没引起广泛关注的标的。

要注意的坑

说完夸的部分,咱也要说说这玩意的问题。这个因子的缺陷也挺明显的:

  • 对震荡市或者横盘期的数据特别敏感,容易被VWAP稳定但毫无趋势的股票给骗过去;
  • 收盘靠近VWAP也可能是因为全天都在横着走,这种票根本没啥机会;
  • 它完全忽视了成交量变化, 高开可能是跌深反弹,也可能是诱多割韭菜。

所以,如果你只用这一个Alpha来做策略,可能筛出来的是一些看着稳,实则没有延续性的票。

总结一下

我会把这个Alpha贴上这样的标签:

  • 中性偏多
  • 喜欢开盘有表现
  • 讨厌大起大落
  • 对放量突破型行情不太敏感

在我自己的组合里,我曾经把这个因子放在一个盘中择时模块里当做稳定选股器的一部分,但不会把它当主力Alpha,原因也很简单,它偏保守,容易错过爆发行情。

代码示例

Python
import pandas as pd
import numpy as np

def alpha_5(df):
    """
    计算 Alpha#5 因子值。
    df: DataFrame,包含 'open', 'close', 'vwap' 列,index 为时间。
    返回:一个 Series,因子值。
    """

    # 10日VWAP平均
    vwap_mean_10 = df['vwap'].rolling(window=10).mean()

    # 第一部分:rank(open - mean(vwap, 10))
    part1 = df['open'] - vwap_mean_10
    part1_rank = part1.rank(pct=True)  # 归一化rank到 [0, 1]

    # 第二部分:-1 * abs(rank(close - vwap))
    part2 = df['close'] - df['vwap']
    part2_rank = part2.rank(pct=True)
    part2_result = -1 * np.abs(part2_rank)

    # 因子值 = part1_rank * part2_result
    alpha = part1_rank * part2_result

    return alpha