第5周测试:我来复现“因子切割论”,想怎么切就怎么切!

学满释放学满释放··485 浏览

一 通过自然语言构建因子

1.1 导入市值中性化模板

图片1.png

  • 注:模板可以找小助理要,市值中性化模板的python源代码我将贴在文章末尾;
  • 为什么要市值中性化
  • 市值中性化是为了在因子选股回测的时候(不属于因子挖掘时候使用)防止选到的股票集中在固定的某些股票当中。
  • 通俗的说:你以为挖到了alpha,但其实是小市值风格因子的另一种体现。

1.2 点击python代码输入,让AI生成因子

  • 问AI:你知道“因子切割论“吗,将这里的动量因子,用因子切割论的方法,使用成交量来切割。其他不变。
    图片2.png
  • 点击:确认应用+保存代码

1.3 运行工作流,查看效果。因子预测效果非常不好!

图片3.png

  • 几乎没有预测能力:IC和RankIC都约等于0,分组也不具备单调性。
  • 为什么呢

1.4 查看AI写的因子源代码,发现它理解错了!

  • AI说:你提到的“因子切割论”,通常可以理解为:
    先用一个“解释因子”(这里是成交量)去解释、“切割”目标因子(这里是动量),得到对该解释因子正交的残差部分;然后再在这个被切割后的因子上做后续处理(你原来是再对市值做正交)。 基于你给的原代码,我只做了一件核心改动:在“动量 → 市值正交”的原流程前面,插入了一步“成交量切割动量”,其他保持不变。
  • 我说这里的因子切割论,你理解错了!!!
  • 我说:因子切割论是:比如将20日内,每天涨跌幅(动量)按照成交量排序,取成交量排名前10的涨跌幅累加,再取后排名后10日涨跌幅累加。将两个累计涨跌幅相除。按照这个思路,修改代码,其他不变。
  • 让AI根据我的输入,再次修改代码。
    图片4.png

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

图片6.png

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

图片7.png

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

1.7 切分对象、切分工具,我都修改了。效果提升明显!

图片8.png

  • 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']       # Series, MultiIndex: (date, symbol)
        mcap   = factors['market_cap']  # Series, MultiIndex: (date, symbol)
        idx    = close.index

        # --- Step 1: 动量(20日对数收益)
        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')

        # --- Step 2: 截面 rank
        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))

        # --- Step 3: 去均值(消除截距)
        y = ranked_mom - ranked_mom.groupby(level='date').transform('mean')
        x = ranked_mcap - ranked_mcap.groupby(level='date').transform('mean')

        # --- Step 4: 每日截面 OLS 回归系数
        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)

        # --- Step 5: 残差(正交化后的动量)
        resid = y - beta * x
        resid.name = 'mom_orth_to_mcap'

        return resid

评论

还没有评论,来说点什么吧
0 / 2000