一 通过自然语言构建因子
1.1 导入市值中性化模板

- 注:模板可以找小助理要,市值中性化模板的python源代码我将贴在文章末尾;
- 为什么要市值中性化?
- 市值中性化是为了在因子选股回测的时候(不属于因子挖掘时候使用)防止选到的股票集中在固定的某些股票当中。
- 通俗的说:你以为挖到了alpha,但其实是小市值风格因子的另一种体现。
1.2 点击python代码输入,让AI生成因子
- 问AI:你知道“因子切割论“吗,将这里的动量因子,用因子切割论的方法,使用成交量来切割。其他不变。

- 点击:确认应用+保存代码
1.3 运行工作流,查看效果。因子预测效果非常不好!

- 几乎没有预测能力:IC和RankIC都约等于0,分组也不具备单调性。
- 为什么呢?
1.4 查看AI写的因子源代码,发现它理解错了!
- AI说:你提到的“因子切割论”,通常可以理解为:
先用一个“解释因子”(这里是成交量)去解释、“切割”目标因子(这里是动量),得到对该解释因子正交的残差部分;然后再在这个被切割后的因子上做后续处理(你原来是再对市值做正交)。 基于你给的原代码,我只做了一件核心改动:在“动量 → 市值正交”的原流程前面,插入了一步“成交量切割动量”,其他保持不变。
- 我说:这里的因子切割论,你理解错了!!!
- 我说:因子切割论是:比如将20日内,每天涨跌幅(动量)按照成交量排序,取成交量排名前10的涨跌幅累加,再取后排名后10日涨跌幅累加。将两个累计涨跌幅相除。按照这个思路,修改代码,其他不变。
- 让AI根据我的输入,再次修改代码。

1.5 一次没跑通,报错了,AI根据日志一键智能修复。

1.6 跑通了,稍微有点预测能力,但是效果还是不好!

- 效果不好体现在:IC、Rank IC 约等于0,就没有预测能力!
- 我的思考:在A股,动量因子已经被广泛应用,因此不容易找到Alpha。
- 那么如何魔改因子切割论呢? 答案是:你想怎么切就怎么切!
- 具体表现为:可以改造切分主体、切分工具。
- 比如:
- 被切分的对象:可以是动量、波动率、成交量的变化率等等
- 切分的工具:可以是成交量、上下影线对实体占比、技术指标KDJ的J值等等。
1.7 切分对象、切分工具,我都修改了。效果提升明显!

- RankIC 绝对值从 0.0 => 0.05。
- |ICIR| 绝对值超过 0.5。
- 5分组单调性明显,t统计量绝对值超过2.0.
补充:因子挖掘需要我们广泛开动脑筋,多阅读机构研报,多去尝试!复制下方市值中性化的代码,快去试试吧!
- AI提示词:因子切割论:比如将20日内,每天涨跌幅(动量)按照成交量排序,取成交量排名前10的涨跌幅累加,再取后排名后10日涨跌幅累加。将两个累计涨跌幅相除或相减。
import pandas as pd
import numpy as np
class MomentumMarketCapOrthogonalFactor(Factor):
def _to_series(self, x, index, name=None):
"""强制把 ndarray / DataFrame / Series 转成 Series,并对齐索引"""
if isinstance(x, pd.Series):
s = x
elif isinstance(x, pd.DataFrame):
if x.shape[1] == 1:
s = x.iloc[:, 0]
else:
s = pd.Series(x.values.ravel(), index=index)
else:
s = pd.Series(np.asarray(x).reshape(-1), index=index)
if name is not None:
s.name = name
return s
def calculate(self, factors):
close = factors['close']
mcap = factors['market_cap']
idx = close.index
try:
momentum_raw = RETURNS(close, 20)
except NameError:
momentum_raw = np.log(close).groupby(level='symbol').diff(20)
momentum = self._to_series(momentum_raw, index=idx, name='momentum')
ranked_mom = momentum.groupby(level='date').rank(pct=True)
ranked_mcap = (np.log(self._to_series(mcap, idx, 'mcap'))
.groupby(level='date').rank(pct=True))
y = ranked_mom - ranked_mom.groupby(level='date').transform('mean')
x = ranked_mcap - ranked_mcap.groupby(level='date').transform('mean')
num = (x * y).groupby(level='date').transform('sum')
den = (x * x).groupby(level='date').transform('sum')
beta = (num / den.replace(0, np.nan)).fillna(0.0)
resid = y - beta * x
resid.name = 'mom_orth_to_mcap'
return resid