LGBRanker排序算法重构,29个行业轮动滚动回测长期年化11.1%(代码与数据下载)
原创文章第262篇,专注“个人成长与财富自由、世界运作的逻辑与投资"。
聚焦目标,做有意义的事情。
之于当下而言,可能有两件事:一是AI量化可以产出交付实盘的有效策略;二是如何有效切入AIGC。
今天先来看看因子分析。
代码在这个位置:alphalens相对复杂,其实我们一般也仅看ic和ric两个数值,我们就自己实现了。

from typing import Tuple
import pandas as pd
def calc_ic(pred: pd.Series, label: pd.Series, date_col="date", dropna=False) -> Tuple[pd.Series, pd.Series]:
df = pd.DataFrame({"pred": pred, "label": label})
ic = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"]))
ric = df.groupby(date_col).apply(lambda df: df["pred"].corr(df["label"], method="spearman"))
if dropna:
return ic.dropna(), ric.dropna()
else:
return ic, ric
if __name__ == '__main__':
from engine.datafeed.dataloader import CSVDataloader
from engine.config import etfs_indexes
from engine.config import DATA_INDEX
loader = CSVDataloader(DATA_INDEX.resolve(), etfs_indexes.values(), start_date="20120101")
df = loader.load(fields=['ta("MACD",close)','shift(close,-5)/close-1'], names=['factor','label'])
print(df)
ic, ir = calc_ic(pred=df['label'], label=df['factor'],dropna=True)
print(ic.mean(), ir.mean())
ic.plot()
import matplotlib.pyplot as plt
plt.show()
5天的RSI值为未来5天收益的因子分析:ic/ric均值为0.04,是有相关性的。

目前看,即便0.04,0.05的因子,对于未来5天收益率的分类准确性,样本外仍然是10%左右。如果是仅预测未来是上涨还是下跌的2分类,样本外可以达到60%多。
因此,我们仍然把希望寄托在排序上。
投资无外乎选股、择时。而择时本身也是一种广义的选股。
而选择本身就是一种排序,择其优者而选之。
大家最熟悉的分类算法,在AI量化也算一种排序。就是通过分类算法,预测未来股票上涨概率。然后按概率值排序——这里对应的就是pointwise。这里有一个明显的问题在于,我们对每一支股票的截面数据进行预测,但没有关心股票与股票之间的相对关系,而我们知道,预测收益概率是非常难的事情,而判断它们之间的相对优劣则更为容易。
重构GBMRanker

import numpy as np
from sklearn.model_selection import train_test_split
import lightgbm as lgb
from engine.datafeed.dataset import DataSet
import joblib, os
from engine.models.model_base import ModelBase
class LGBRanker(ModelBase):
def __init__(self, name, load_model, feature_cols=None):
super(LGBRanker, self).__init__(name, load_model)
self.feature_cols = feature_cols
self.label_col = 'label'
def _prepare_groups(self, df):
df['day'] = df.index
group = df.groupby('day')['day'].count()
print(group.values)
return group.values
def predict(self, data):
data = data.copy(deep=True)
if self.feature_cols:
data = data[self.feature_cols]
pred = self.ranker.predict(data)
return pred
def train(self, df, split_date):
if split_date:
df_train = df[df.index < split_date]
df_val = df[df.index >= split_date]
query_train = self._prepare_groups(df_train.copy(deep=True))
query_val = self._prepare_groups(df_val.copy(deep=True))
ranker = lgb.LGBMRanker()
ranker.fit(df_train[self.feature_cols], df_train[self.label_col], group=query_train,
eval_set=[(df_val[self.feature_cols], df_val[self.label_col])], eval_group=[query_val],
eval_at=[1, 2, 5, 10, 20], early_stopping_rounds=50)
self.ranker = ranker
print(ranker.n_features_)
print(ranker.feature_importances_)
print(ranker.feature_name_)
score, names = zip(*sorted(zip(ranker.feature_importances_, ranker.feature_name_), reverse=True))
print(score)
print(names)
if __name__ == '__main__':
from engine.config import etfs_indexes
from engine.datafeed.dataloader import CSVDataloader
from engine.config import DATA_INDEX
from engine.datafeed.alpha import AlphaLit
loader = CSVDataloader(path=DATA_INDEX.resolve(), symbols=etfs_indexes.values())
ds = DataSet("行业指数数据集", loader=loader, handler=AlphaLit(), cache=False)
print(ds.data)
df = ds.data
df.dropna(inplace=True)
print(df)
LGBRanker('', feature_cols=ds.get_feature_names(), load_model=False).train(df, '2022-01-01')
# lightgbm.plot_importance(ranker, figsize=(12, 8))

实证告诉我们一个结论:
因子不是越多越好,哪怕GBDT可以筛选因子,但因子之间是会冲突,不恰当的加入多余的因子,会降低训练的性能!这里就需要验证因子之间的相关性,正效性。——所以,不要试图一股恼把一堆因子加到模型里,让模型自己去筛选。——如果真这样,那就太简单了。
模型回测——滚动回测

from engine.config import etfs_indexes
from engine.datafeed.dataset import DataSet
from engine.env import Env
from engine.config import etfs_indexes
from engine.datafeed.dataloader import CSVDataloader
from engine.config import DATA_INDEX
from engine.datafeed.alpha import AlphaLit
loader = CSVDataloader(path=DATA_INDEX.resolve(), symbols=etfs_indexes.values())
ds = DataSet("行业指数数据集", loader=loader, handler=AlphaLit(), cache=False)
print(ds.data)
from engine.algo.algos import *
from engine.algo.algo_weights import *
from engine.algo.algo_model import ModelWFA
from engine.models.lgb_ranker import LGBRanker
from engine.env import Env
model = LGBRanker(name='滚动回测——排序',load_model=False, feature_cols=ds.get_feature_names())
# model.train(ds.data, '2022-01-01')
env = Env(ds.data)
env.set_algos([
RunWeekly(),
ModelWFA(model=model),
SelectTopK(K=2, order_by='pred_score', b_ascending=False),
WeightEqually()
])
env.backtest_loop()
env.show_results()

之于这第二件事,有效切入AIGC。当下很多自媒体,甚至很多技术人员,都是看热闹阶段,或者在周边用chatGPT的api或者作图的api,所谓的prompt,生成一些文章或者一些图片。真正有效参与到这个生产力提升的环节,普通人或者创业公司,要从头训练大模型即不现实也不需要。把大模型看成传统“预训练”模型就好,传统我们也不会去从零开始训练bert或者GPT,而是在预训练的基础上进行微调,对于通用大模型,逻辑类似。
而且我们切入的方向一定是垂直方向,专有知识解决领域问题。我想到最好落地的仍然是金融,与AI量化类似,FinGPT应该也会很有意思。之有做FinRL的团队开源了FinGPT,至少这个构想是特别好的。后续会持续关注下。
明天的文章,讲讲finGPT的代码。
代码与数据已经发布至星球,请大家请往下载。