最近在整理和复习一些经典量化因子的时候,萌生了一个想法:

干脆开一个小系列,把这101个常见的量化因子,一篇篇拆开来聊一遍。

每篇文章,我都会选一个因子,从最原始的公式出发,带着大家一起看看它到底想表达什么,适合用在哪些场景,又有哪些地方值得改进或优化。

今天是这个系列的第一篇,就从 Alpha#1 开始。

公式长这样:

Plaintext
Alpha#1 = (rank(Ts_ArgMax(SignedPower(((returns < 0) ? stddev(returns, 20) : close), 2.), 5)) - 0.5)

别急,看着复杂,实际上逻辑并不难。今天就来跟大家聊聊,这个Alpha#1到底想表达什么,它背后又隐含了怎样的交易思路。

一步步拆开来看

首先,从最外层看,整个因子的大体流程是这样的:

  1. 处理数据: 根据收益率情况,选取不同的指标(波动率或收盘价)。
  2. 加工数据: 进行平方运算,放大差异。
  3. 时间窗口内寻找极值: 在最近5天内找出最极端的一天。
  4. 排名归一化: 对所有股票进行横向排名,并稍微中心化结果。

下面详细讲讲每一块的逻辑。

1. returns < 0 ? stddev(returns, 20) : close

这一段是个简单的条件判断:

  • 如果今天收益率是负的(跌了),就取过去20天收益率的标准差(也就是波动率)。
  • 如果今天收益率是正的或0(没跌甚至涨了),就取今天的收盘价

所以这个因子有一个很明显的思路:

下跌关注波动,上涨关注价格本身。

亏的时候看“抖动得厉不厉害”,赚的时候直接盯价格。

这种思路挺符合实际交易的:

如果一只股票在跌,而且波动剧烈,通常意味着风险加大;

如果在涨,价格越高,趋势可能越强。

2. SignedPower(..., 2.)

接着,把上面选出来的那个值平方,而且保留正负号。

平方的作用很简单:放大强信号,让差异更加明显。

而保留正负号,则避免丢失方向感(涨还是跌)。

这一步可以理解为:“强调那些真正特别的股票,不太关注中庸的走势”。

3. Ts_ArgMax(..., 5)

然后,在最近5天内,看这个加工后的值,在第几天达到最大。

举个例子,如果今天是周五,那么就看本周五天里面,哪一天表现最极端。

这个操作的核心思想是:

看谁最近最“出圈”。

不一定要求一直优秀,只要有那么一两天特别突出,也能上榜。

4. rank(...) - 0.5

最后,对所有股票的这个极值位置进行横向排名。

排名靠前的(意味着最近某天特别活跃或者极端的)股票得高分。

再减去0.5,相当于把分布平移到中心点附近,方便后续建模或者组合优化。

总结一下

这个Alpha#1实际上在做的事情很有趣:

  • 下跌时关注风险(波动性)
  • 上涨时关注趋势(价格本身)
  • 强调最近5天内有没有特别极端的表现
  • 把极端程度在股票之间排序,筛选出表现突出的标的

通俗说,就是:

“跌的时候看慌不慌,涨的时候看高不高;过去五天谁最猛,谁就优先。”

这样的因子在实际策略里可以搭配很多玩法,比如动量策略里的选股过滤器,或者作为多因子模型中的一环。

Alpha#1 因子计算代码

Python
import pandas as pd
import numpy as np

def calc_alpha1(df):
    """
    计算Alpha#1因子
    输入: df,一个包含 'close' 和 'returns' 列的 DataFrame
    输出: df,新增 'alpha1' 列
    """
    df = df.copy()

    # 计算过去20日收益率标准差
    df['stddev_20'] = df['returns'].rolling(window=20, min_periods=1).std()

    # 根据条件选择
    condition = df['returns'] < 0
    df['selected_value'] = np.where(condition, df['stddev_20'], df['close'])

    # SignedPower(x, 2),即平方,同时保留符号
    df['signed_power'] = np.sign(df['selected_value']) * (np.abs(df['selected_value']) ** 2)

    # 计算最近5日内 signed_power 最大值出现在哪一天(0是今天,1是昨天...)
    def ts_argmax(series):
        return np.argmax(series[::-1])

    df['ts_argmax_5'] = df['signed_power'].rolling(window=5, min_periods=1).apply(ts_argmax, raw=True)

    # 在横截面上进行排名(每天对所有股票排名)
    # 如果是单只股票的单一序列,这里演示简单版
    # 如果是多股票的话,需要 groupby date 后 rank

    # 简单处理,归一化到 [0,1] 区间
    df['rank'] = df['ts_argmax_5'].rank(pct=True)

    # 减去0.5,中心化
    df['alpha1'] = df['rank'] - 0.5

    return df

# 示例:假设你已经有一个DataFrame,包含了close和returns
# df = pd.DataFrame({
#     'close': [...],
#     'returns': [...],
#     'date': [...]
# })

# 直接调用
# result = calc_alpha1(df)
# print(result[['close', 'returns', 'alpha1']])

rolling(window=5) 这种滑动窗口默认是向前看的(不包含未来信息),所以适合真实的回测环境。另外,这里用了 np.argmax(series[::-1]) 是因为在rolling窗口里,pandas默认顺序是从老到新,我们倒过来看最近的日子。

如果是多只股票的panel数据(比如不同股票代码+不同日期),要在date那一列上分组排名,比如用:

df['rank'] = df.groupby('date')['ts_argmax_5'].rank(pct=True)

可能的改进方向

讲到这里,其实我们可以顺便思考一下:

这个Alpha#1,虽然逻辑清晰,但里面的一些细节参数,并不一定对所有市场、所有品种都最优。想要让它真正适配自己的交易策略,动手微调和探索是非常有必要的。

比如,可以从下面几个角度去改造和优化:

1. 调整参数细节

  • stddev(returns, 20) 这里用了20天的标准差,作为衡量下跌时的波动。
  • ArgMax 5天窗口是找最近5天的局部极值。

但问题是,不同品种的波动周期差异很大。

比如短线热门股5天就风云变幻,而大盘蓝筹可能20天都波澜不惊。

可以尝试调成10天、30天、60天,甚至动态调整,看对整体因子效果有什么变化。

2. 换一种波动测量方式

目前默认是用标准差(stddev)来量波动,但其实还有很多其他选项,比如:

  • ATR(Average True Range),它在金融交易里更能反映“真实的价格波动”。
  • 收益率均方根(RMS),或者一些自定义的低延迟波动指标。

换一种波动的定义,可能让因子更敏感、更早捕捉到拐点。

3. 根据市值等属性分组优化

还有一个思路,就是不是一刀切用一个因子去套所有股票。

比如小盘股波动大、流动性差,大盘股波动小、趋势稳定——在同一个因子公式下,它们的行为模式可能完全不一样。

可以试试看:

  • 把股票按市值、成交额、波动率做分组。
  • 在不同组别里分别计算和测试因子效果。

有时候,单一因子在全部股票上效果一般,但在某一小群体里能发挥非常好的效果。