原创文章第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)
这里当然还有很多工作要完善,但如果懂代码的同学,应该已经看出它的价值了,大家可以前往下载:
我们还会同步做一件事情——单因子评估。
说实话,构造一个因子很容易,遗传算法、强化学习或者ChatGPT来生成。你分分钟可以拿到成千上万上因子。
关键是如何分析因子,如何知道这个因子有用,以及怎么用——如何构建有效策略?
吾日三省吾身
昨天与星球管理员们开会讨论,分享星球下一步的计划,以及可以提供更多的服务等议题。
我的分享主要是,也是近期逐步想明白的——“职业投资的能力”。
这个能力如何量化呢?——应该是一个量化私募团队的能力。
一般10-100亿规模的公司,投研团队大致3-6人左右。
这与传统做应用开发的团队比较像。
其实,如果你是全栈,甚至可以自己搞定(外包一部分周边的活)。
量化私募投研的典型工作流程可以概括为以下几个步骤:
数据收集与整理:这是量化投资的基础环节,涉及到从各种数据源获取原始数据,并进行清洗和整理,以便后续分析使用。
因子挖掘与筛选:在数据准备好之后,下一步是通过统计学方法从大量数据中提取有用的信息因子,这些因子将用于构建投资模型。
策略设计与建模:根据选定的因子,设计并构建具体的量化投资策略。这一步通常需要编程实现,如使用Python等工具来编写和测试策略。
风险管理与优化:在策略建模完成后,需要对策略进行风险评估和优化,以确保策略在面对市场波动时能够保持稳定的表现。这可能包括设置止损点、分散投资等方法。
策略回测与验证:通过历史数据对策略进行回测,检验策略的有效性和稳健性。回测是量化投资中非常重要的一步,它帮助投资者了解策略在不同市场条件下的表现。
实施策略:一旦策略经过充分的回测并且表现良好,就可以开始实施策略。这可能包括连接交易平台、设置自动交易等操作。——这一步类似wonder trader这样的平台帮助到我们。
持续监控与调整:在策略实施过程中,需要持续监控其表现,并根据市场变化和策略表现进行必要的调整和优化。
数据是基础,这个毋庸置疑,“garbage in, garbage out”。但普通人和小团队,大家收集或者采购的数据都大差不差,这里做出差异化的可能性不高,尽量做得和大家一样即可。
重点是因子挖掘和策略设计,然后持续优化调整。