因子表达式完美重构 | Qlib Alpha158因子库复现 (代码+数据)

原创文章第447篇,专注“AI量化投资、个人成长与财富自由"。

本周星球代码计划——因子分析,因子挖掘

1、(因子表达式优化)Alpha158以及world quant101部分因子实现。

2、基于lightgbm的因子筛选。 

3、优秀因子的单因子分析。 

4、deepalphagen和gplearn部分代码优化。

或者可以这么说,当前主流私募基金的做法就是因子挖掘。——手工挖,遗传算法,机器学习等。然后合成因子。

因为因子到策略,还有很多人工可以干预的环节。

完全所谓端到端,哪怕就是现在最强的GPT-5也做不到。当下大模型可以真正落地的场景,还是Agent生态,通过多环节的串联+工具来实现预期的目标。

投资也如此。

最多到端对端挖掘因子,然后把因子拿出来做策略。

因子表达式重构

两个包装器,一个按symbol来groupby,另一个按date来groupby

def calc_by_date(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        other_args = []
        se_args = []
        se_names = []
        for arg in args:
            if type(arg) is not pd.Series:
                other_args.append(arg)
            else:
                se_args.append(arg)
                se_names.append(arg.name)
        if len(se_args) == 1:
            ret = se_args[0].groupby(level=0, group_keys=False).apply(lambda x: func(x, *other_args, **kwargs))
        elif len(se_args) > 1:
            count = len(se_args)
            df = pd.concat(se_args, axis=1)
            df.index = se_args[0].index
            ret = df.groupby(level=0, group_keys=False).apply(
                lambda sub_df: func(*[sub_df[name] for name in se_names], *other_args))
            ret.index = df.index
        return ret

    return wrapper


def calc_by_symbol(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        other_args = []
        se_args = []
        se_names = []
        for arg in args:
            if type(arg) is not pd.Series:
                other_args.append(arg)
            else:
                se_args.append(arg)
                se_names.append(arg.name)
        if len(se_args) == 1:
            ret = se_args[0].groupby(level=1, group_keys=False).apply(lambda x: func(x, *other_args, **kwargs))
        elif len(se_args) > 1:
            count = len(se_args)
            df = pd.concat(se_args, axis=1)
            df.index = se_args[0].index
            ret = df.groupby(level=1, group_keys=False).apply(
                lambda sub_df: func(*[sub_df[name] for name in se_names], *other_args))
            ret.index = df.index
        return ret

    return wrapper

目前,需要使用截面排序rank,需要截面排序。

这样做的好处,就是所有的计算子函数都不需要变更了,加上这个包装器即可:

@calc_by_symbol
def ta_atr(high, low, close, period=14):
    se = talib.ATR(high, low, close, period)
    se = pd.Series(se)
    se.index = high.index
    return se
@calc_by_date
def rank(se: pd.Series):
    ret = se.rank(pct=True)
    return ret

这样csv_dataloader加载数据,因子计算速度也快了很多,最重要的是,我们解决了rank,ts_rank嵌套的问题。

图片

Alpha158因子库

代码完全移植过来了,qlib的158因子,大家感兴趣可以把alpha360也迁移过来。

图片

代码在如下位置:

图片

from datafeed import AlphaBase


class Alpha158(AlphaBase):

    def get_fields_names(self):
        # ['CORD30', 'STD30', 'CORR5', 'RESI10', 'CORD60', 'STD5', 'LOW0',
        # 'WVMA30', 'RESI5', 'ROC5', 'KSFT', 'STD20', 'RSV5', 'STD60', 'KLEN']
        fields = []
        names = []

        # kbar
        fields += [
            "(close-open)/open",
            "(high-low)/open",
            "(close-open)/(high-low+1e-12)",
            "(high-greater(open, close))/open",
            "(high-greater(open, close))/(high-low+1e-12)",
            "(less(open, close)-low)/open",
            "(less(open, close)-low)/(high-low+1e-12)",
            "(2*close-high-low)/open",
            "(2*close-high-low)/(high-low+1e-12)",
        ]
        names += [
            "KMID",
            "KLEN",
            "KMID2",
            "KUP",
            "KUP2",
            "KLOW",
            "KLOW2",
            "KSFT",
            "KSFT2",
        ]

        # =========== price ==========
        feature = ["OPEN", "HIGH", "LOW", "CLOSE"]
        windows = range(5)
        for field in feature:
            field = field.lower()
            fields += ["shift(%s, %d)/close" % (field, d) if d != 0 else "%s/close" % field for d in windows]
            names += [field.upper() + str(d) for d in windows]

        # ================ volume ===========
        fields += ["shift(volume, %d)/(volume+1e-12)" % d if d != 0 else "volume/(volume+1e-12)" for d in windows]
        names += ["VOLUME" + str(d) for d in windows]

        # ================= rolling ====================

        windows = [5, 10, 20, 30, 60]
        fields += ["shift(close, %d)/close" % d for d in windows]
        names += ["ROC%d" % d for d in windows]

        fields += ["mean(close, %d)/close" % d for d in windows]
        names += ["MA%d" % d for d in windows]

        fields += ["std(close, %d)/close" % d for d in windows]
        names += ["STD%d" % d for d in windows]

        #fields += ["slope(close, %d)/close" % d for d in windows]
        #names += ["BETA%d" % d for d in windows]

        fields += ["max(high, %d)/close" % d for d in windows]
        names += ["MAX%d" % d for d in windows]

        fields += ["min(low, %d)/close" % d for d in windows]
        names += ["MIN%d" % d for d in windows]

        fields += ["quantile(close, %d, 0.8)/close" % d for d in windows]
        names += ["QTLU%d" % d for d in windows]

        fields += ["quantile(close, %d, 0.2)/close" % d for d in windows]
        names += ["QTLD%d" % d for d in windows]

        #fields += ["ts_rank(close, %d)" % d for d in windows]
        #names += ["RANK%d" % d for d in windows]

        fields += ["(close-min(low, %d))/(max(high, %d)-min(low, %d)+1e-12)" % (d, d, d) for d in windows]
        names += ["RSV%d" % d for d in windows]

        fields += ["idxmax(high, %d)/%d" % (d, d) for d in windows]
        names += ["IMAX%d" % d for d in windows]

        fields += ["idxmin(low, %d)/%d" % (d, d) for d in windows]
        names += ["IMIN%d" % d for d in windows]

        fields += ["(idxmax(high, %d)-idxmin(low, %d))/%d" % (d, d, d) for d in windows]
        names += ["IMXD%d" % d for d in windows]

        fields += ["corr(close, log(volume+1), %d)" % d for d in windows]
        names += ["CORR%d" % d for d in windows]

        fields += ["corr(close/shift(close,1), log(volume/shift(volume, 1)+1), %d)" % d for d in windows]
        names += ["CORD%d" % d for d in windows]

        fields += ["mean(close>shift(close, 1), %d)" % d for d in windows]
        names += ["CNTP%d" % d for d in windows]

        fields += ["mean(close<shift(close, 1), %d)" % d for d in windows]
        names += ["CNTN%d" % d for d in windows]

        fields += ["mean(close>shift(close, 1), %d)-mean(close<shift(close, 1), %d)" % (d, d) for d in windows]
        names += ["CNTD%d" % d for d in windows]

        fields += [
            "sum(greater(close-shift(close, 1), 0), %d)/(sum(Abs(close-shift(close, 1)), %d)+1e-12)" % (d, d)
            for d in windows
        ]
        names += ["SUMP%d" % d for d in windows]

        fields += [
            "sum(greater(shift(close, 1)-close, 0), %d)/(sum(Abs(close-shift(close, 1)), %d)+1e-12)" % (d, d)
            for d in windows
        ]
        names += ["SUMN%d" % d for d in windows]

        fields += [
            "(sum(greater(close-shift(close, 1), 0), %d)-sum(greater(shift(close, 1)-close, 0), %d))"
            "/(sum(Abs(close-shift(close, 1)), %d)+1e-12)" % (d, d, d)
            for d in windows
        ]
        names += ["SUMD%d" % d for d in windows]

        fields += ["mean(volume, %d)/(volume+1e-12)" % d for d in windows]
        names += ["VMA%d" % d for d in windows]

        fields += ["std(volume, %d)/(volume+1e-12)" % d for d in windows]
        names += ["VSTD%d" % d for d in windows]

        fields += [
            "std(Abs(close/shift(close, 1)-1)*volume, %d)/(mean(Abs(close/shift(close, 1)-1)*volume, %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["WVMA%d" % d for d in windows]

        fields += [
            "sum(greater(volume-shift(volume, 1), 0), %d)/(sum(Abs(volume-shift(volume, 1)), %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["VSUMP%d" % d for d in windows]

        fields += [
            "sum(greater(shift(volume, 1)-volume, 0), %d)/(sum(Abs(volume-shift(volume, 1)), %d)+1e-12)"
            % (d, d)
            for d in windows
        ]
        names += ["VSUMN%d" % d for d in windows]

        fields += [
            "(sum(greater(volume-shift(volume, 1), 0), %d)-sum(greater(shift(volume, 1)-volume, 0), %d))"
            "/(sum(Abs(volume-shift(volume, 1)), %d)+1e-12)" % (d, d, d)
            for d in windows
        ]
        names += ["VSUMD%d" % d for d in windows]

        return fields, names

吾日三省吾身

“若无闲事在心头,便是人间好时节”。

这里的闲事,就是短期内,没有确定deadline要做的事情。

当然,本身很多事情,随着时间的推移,变得可有可无,或者就是随手的事情。

但在强迫症的眼里,既然要做,那就尽快做完,然后痛快地玩。

强迫症很难与杂事共处,尤其是带着不确定的事情。

要做就尽快做,不做就不做。

确实很多时候可以带来自律,给人专业,靠谱的感觉。

但过犹不及,对于家人,身边的朋友有时候也会造成困扰。

历史文章:

Quantlab3.3代码发布:全新引擎 | 静待花开:年化13.9%,回撤小于15% | lightGBM实现排序学习

创业板指布林带突破策略:年化12.8%,回撤20%+| Alphalens+streamlit单因子分析框架(代码+数据)

轮动策略模板重写,先来一个年化21%的策略(代码+数据)

去掉底层回测引擎,完全自研,增加超参数优化,因子自动挖掘,机器模型交易。

飞狐量化——AI驱动的量化。(持续给大家写代码的,交付最前沿AI量化技术和策略的星球)AI量化实验室——2024量化投资的星辰大海

关于我:CFA,北大光华金融硕士,十年量化投资实战。 / CTO,全栈技术,AI大模型 。——应该是金融圈最懂技术的男人