Deap因子挖掘基础框架完成|量化私募投研的典型工作流程

原创文章第551篇,专注“AI量化投资、世界运行的规律、个人成长与财富自由"。

继续因子挖掘:

因子挖掘,其实是一个“开放式的”,有很多tricks的工作,其实遗传算法不复杂。

说白了,根据你给定的“符号集”——就是表达式函数:比如ts_rank, log, exp等,然后你也可以使用技术分析的指标,反正就是函数。

一般遗传算法库都会有一个生成符号表达式的规则树,deap在这方面做得比gplearn好得多。——用强化学习来实现因子挖掘的逻辑也是类似的,后续我们会实现。

完整的代码如下:

import copy

from deap_patch import *  # noqa
from deap import base, creator, gp
from deap import tools
from datafeed.mining.deap_alpha.add_ops import *
from datafeed.dataloader import CSVDataloader
from config import DATA_DIR_QUOTES
import pandas as pd
from loguru import logger

def convert_inverse_prim(prim, args):
    """
    Convert inverse prims according to:
    [Dd]iv(a,b) -> Mul[a, 1/b]
    [Ss]ub(a,b) -> Add[a, -b]
    We achieve this by overwriting the corresponding format method of the sub and div prim.
    """
    prim = copy.copy(prim)

    converter = {
        'Add': lambda *args_: "{}+{}".format(*args_),
        'Mul': lambda *args_: "{}*{}".format(*args_),
        'fsub': lambda *args_: "{}-{}".format(*args_),
        'fdiv': lambda *args_: "{}/{}".format(*args_),
        'fmul': lambda *args_: "{}*{}".format(*args_),
        'fadd': lambda *args_: "{}+{}".format(*args_),
        # 'fmax': lambda *args_: "max_({},{})".format(*args_),
        # 'fmin': lambda *args_: "min_({},{})".format(*args_),

        'isub': lambda *args_: "{}-{}".format(*args_),
        'idiv': lambda *args_: "{}/{}".format(*args_),
        'imul': lambda *args_: "{}*{}".format(*args_),
        'iadd': lambda *args_: "{}+{}".format(*args_),
        # 'imax': lambda *args_: "max_({},{})".format(*args_),
        # 'imin': lambda *args_: "min_({},{})".format(*args_),
    }

    prim_formatter = converter.get(prim.name, prim.format)

    return prim_formatter(*args)


def stringify_for_sympy(f):
    """Return the expression in a human readable string.
    """
    string = ""
    stack = []
    for node in f:
        stack.append((node, []))
        while len(stack[-1][1]) == stack[-1][0].arity:
            prim, args = stack.pop()
            string = convert_inverse_prim(prim, args)
            if len(stack) == 0:
                break  # If stack is empty, all nodes should have been seen
            stack[-1][1].append(string)
    # print(string)
    return string


def calc_ic(x, y):
    """个体fitness函数"""
    ic = pd.Series(x.corr(y))
    return ic


def map_exprs(evaluate, invalid_ind, gen, label, split_date):
    names, features = [], []
    for i, expr in enumerate(invalid_ind):
        names.append(f'GP_{i:04d}')
        features.append(stringify_for_sympy(expr))

    features = [f.lower() for f in features]
    #for name, feature in zip(names, features):
    #    print(name, ':', feature)

    all_names = names.copy()
    all_names.append(label)
    all_features = features.copy()
    all_features.append('label(close,5)')

    df = CSVDataloader(path=DATA_DIR_QUOTES.resolve(), symbols=['510300.SH']).load(all_features, all_names)
    df.set_index([df['symbol'], df.index], inplace=True)
    # df.dropna(inplace=True)

    # 将IC划分成训练集与测试集
    df_train = df[df.index.get_level_values(1) < split_date]
    df_valid = df[df.index.get_level_values(1) >= split_date]
    #print(df_train)
    #print(names, features)

    ic_train = df_train[names].groupby(level=0, group_keys=False).agg(lambda x: calc_ic(x, df_train[label])).mean()
    ic_valid = df_valid[names].groupby(level=0, group_keys=False).agg(lambda x: calc_ic(x, df_valid[label])).mean()
    #print('ic_train', ic_train)
    #print('ic_valid', ic_valid)

    results = {}
    for name, factor in zip(names, features):
        results[factor] = {'ic_train': ic_train.loc[name],
                           'ic_valid': ic_valid.loc[name],
                           }
    #print(results)
    return [(v['ic_train'], v['ic_valid']) for v in results.values()]


def init_pset():
    pset = gp.PrimitiveSetTyped("MAIN", [], RET_TYPE)
    pset = add_constants(pset)
    pset = add_operators(pset)
    pset = add_factors(pset)
    return pset


def init_creator():
    # 可支持多目标优化
    # TODO 必须元组,1表示找最大值,-1表示找最小值
    FITNESS_WEIGHTS = (1.0, 1.0)
    creator.create("FitnessMulti", base.Fitness, weights=FITNESS_WEIGHTS)
    creator.create("Individual", gp.PrimitiveTree, fitness=creator.FitnessMulti)
    return creator


def init_toolbox(creator):
    toolbox = base.Toolbox()
    pset = init_pset()
    toolbox.register("expr", gp.genHalfAndHalf, pset=pset, min_=2, max_=5)
    toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.expr)
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)
    toolbox.register("evaluate", print)  # 不单独做评估了,在map中一并做了

    toolbox.register("select", tools.selTournament, tournsize=3)  # 目标优化
    # toolbox.register("select", tools.selNSGA2)  # 多目标优化 FITNESS_WEIGHTS = (1.0, 1.0)
    toolbox.register("mate", gp.cxOnePoint)
    toolbox.register("expr_mut", gp.genFull, min_=0, max_=2)
    toolbox.register("mutate", gp.mutUniform, expr=toolbox.expr_mut, pset=pset)

    import operator
    toolbox.decorate("mate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))
    toolbox.decorate("mutate", gp.staticLimit(key=operator.attrgetter("height"), max_value=17))

    from datetime import datetime
    dt1 = datetime(2021, 1, 1)
    LABEL_y = 'return_5'
    from itertools import count
    toolbox.register('map', map_exprs, gen=count(), label=LABEL_y, split_date=dt1)

    return toolbox


if __name__ == '__main__':
    import warnings

    warnings.filterwarnings('ignore', category=RuntimeWarning)

    logger.info('开始Deap因子挖掘...')
    random.seed(9527)
    creator = init_creator()
    toolbox = init_toolbox(creator)
    n = 100
    pop = toolbox.population(n=n)
    logger.info('完成初代种群初始化:{}个'.format(n))
    hof = tools.HallOfFame(10)

    # 只统计一个指标更清晰
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    # 打补丁后,名人堂可以用nan了,如果全nan会报警
    stats.register("avg", np.nanmean, axis=0)
    stats.register("std", np.nanstd, axis=0)
    stats.register("min", np.nanmin, axis=0)
    stats.register("max", np.nanmax, axis=0)

    # 使用修改版的eaMuPlusLambda
    population, logbook = eaMuPlusLambda(pop, toolbox,
                                         # 选多少个做为下一代,每次生成多少新个体
                                         mu=150, lambda_=100,
                                         # 交叉率、变异率,代数
                                         cxpb=0.5, mutpb=0.1, ngen=2,
                                         # 名人堂参数
                                         # alpha=0.05, beta=10, gamma=0.25, rho=0.9,
                                         stats=stats, halloffame=hof, verbose=True,
                                         # 早停
                                         early_stopping_rounds=5)

    print('=' * 60)
    print(logbook)

    print('=' * 60)


    def print_population(population):

        for p in population:
            expr = stringify_for_sympy(p)
            print(expr, p.fitness)


print_population(hof)

这里当然还有很多工作要完善,但如果懂代码的同学,应该已经看出它的价值了,大家可以前往下载:

AI量化实验室——2024量化投资的星辰大海

我们还会同步做一件事情——单因子评估。

说实话,构造一个因子很容易,遗传算法、强化学习或者ChatGPT来生成。你分分钟可以拿到成千上万上因子。

关键是如何分析因子,如何知道这个因子有用,以及怎么用——如何构建有效策略?

吾日三省吾身

昨天与星球管理员们开会讨论,分享星球下一步的计划,以及可以提供更多的服务等议题。

我的分享主要是,也是近期逐步想明白的——“职业投资的能力”。

这个能力如何量化呢?——应该是一个量化私募团队的能力。

一般10-100亿规模的公司,投研团队大致3-6人左右。

这与传统做应用开发的团队比较像。

其实,如果你是全栈,甚至可以自己搞定(外包一部分周边的活)。

量化私募投研的典型工作流程可以概括为以下几个步骤:

数据收集与整理:这是量化投资的基础环节,涉及到从各种数据源获取原始数据,并进行清洗和整理,以便后续分析使用。

因子挖掘与筛选:在数据准备好之后,下一步是通过统计学方法从大量数据中提取有用的信息因子,这些因子将用于构建投资模型。

策略设计与建模:根据选定的因子,设计并构建具体的量化投资策略。这一步通常需要编程实现,如使用Python等工具来编写和测试策略。

风险管理与优化:在策略建模完成后,需要对策略进行风险评估和优化,以确保策略在面对市场波动时能够保持稳定的表现。这可能包括设置止损点、分散投资等方法。

策略回测与验证:通过历史数据对策略进行回测,检验策略的有效性和稳健性。回测是量化投资中非常重要的一步,它帮助投资者了解策略在不同市场条件下的表现。

实施策略:一旦策略经过充分的回测并且表现良好,就可以开始实施策略。这可能包括连接交易平台、设置自动交易等操作。——这一步类似wonder trader这样的平台帮助到我们。

持续监控与调整:在策略实施过程中,需要持续监控其表现,并根据市场变化和策略表现进行必要的调整和优化。

数据是基础,这个毋庸置疑,“garbage in, garbage out”。但普通人和小团队,大家收集或者采购的数据都大差不差,这里做出差异化的可能性不高,尽量做得和大家一样即可。

重点是因子挖掘和策略设计,然后持续优化调整。

AI量化实验室——2024量化投资的星辰大海

Deap因子挖掘:比gplearn强100倍(代码+数据)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI量化投资实验室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值