QuanTide-weekly第1期

news/2024/12/22 14:11:39/

本周Po文

这周我们共发表5篇文章。《基于 XGBoost 的组合策略…》等两篇详细讲解了机器学习构建组合策略的框架和常见问题。


文章要点与结论:

  1. 通过两阶段式方案实现多因子、多资产的组合策略构建。第一阶段基于XGBoost构建多个多因子单标的模型,第二阶段通过经典的均值方差方案进行组合优化。
  2. 应该基于分类而不是回归来构建机器学习模型。基于时序的价格预测几乎没有意义(价格序列不具有平稳性)。
  3. 分类模型是以多因子为特征,以未来一周期的涨跌幅度digitize化作为标签来训练构建的。
  4. 对XGBoost模型而言,基本上无须考虑因子标准化。因子标准化甚至有可能引入副作用;但另一方面,因子标准化也有利于正则惩罚项。
  5. 损失函数与度量函数的差别在于,损失函数要能计算梯度。MAPE函数的不可导性决定了它不适合作为损失函数。
  6. 在金融领域,相对误差比RMSE更有意义,因此我们介绍了SMAPE – 这是一个可作为损失函数的函数。

这两篇文章我们也集结后,发在本文末尾。

在这一期的量化工具专栏中,我们发表了获得NASA奖章的男人,带来了这些IPython技巧。IPython是非常轻量的交互式编程工具,尽管它的所有功能都可以在Notebook中找到,但它更轻,但仍然长袖擅舞,颇有飞燕之姿。

在不要去写书 除非是要与你的灵魂交流中,我们披露了《Python高效编程实践指南》出版过程中的一些冏事。这本书会对量化人构建稳健的交易系统非常有帮助。


话音刚落,周五就发生了Windows蓝屏事件,正是因为CrowedStrike缺乏完善的CI/CD,才导致这一事件发生。而CI/CD正是本书详细介绍的内容之一。

在问薪无愧,最全威的量化自学路线图中,我们介绍了Algos.org编写的这份量化自学大纲。

我们也将在8月底前,推出自己的量化自学路线图。除了更好的本地化之外(英文版中,一些工具、网站和数据源对国内市场不支持),我们还将更清晰地理出从入门到精通、从小白到不同的岗位的学习路径。

在大纲没有出来之前,大家可以暂时参照这份路线图:

在这里插入图片描述


基于XGBoost构建多因子策略

如何在投资组合策略中运用上机器学习方法? 最近,我们翻了下之前存过的论文,决定对《A portfolio Strategy Based on XGBoost Regression and Monte Carlo Method》这篇论文进行解读。

这是论文抽象出来的一个基本框架图。

在这里插入图片描述

这个框架能解决的什么问题呢?我们知道,在一个投资组合策略中,要重点考虑的第一个问题是,如何从给定的 universe 中,选择一部分股票纳入策略股票池;其次要考虑,这部分股票的持仓如何分配,使之在这个组合上,达到风险收益比最高。


在这里插入图片描述

后一部分,最经典的方法就是运用 MPT 理论,寻找有效投资前沿。这里既可以用凸优化求解,也可以使用蒙特卡洛方案。这一部分,我们之前有一个系列文章:投资组合理论与实战,从基本概念到实战细节,都讲得非常清楚,这里就不详述了。

如何从 universe 中选择股票进入股票池? 这在单因子模型中比较容易解决,就是选择因子分层中,表现最佳的那个分层 (tier) 的股票进入股票池。各标的的权重可以按因子载荷来分配,也可以使用 MPT 方法。

但如何在多因子模型中选择股票进入股票池?这一直是一个难题。我们常常提到的 Barra 模型也只是一个风控模型,并不能选择出最佳的股票池出来。


论文的思路是,将股票的纳入选择看成一个回归问题,即,通过多因子的训练,找出最能被模型预测的那些股票进入股票池

作者给出的结果是,在 2021、2020 和 2019 年,龙头股票投资组合的回报率分别为 27.86%, 6.20%和 23.26%。不过,作者并没有给出基准比较,此外,也没有深入分析,如果这些结果有超额收益的话,这些超额是来自于 MPT 呢,还是来自于 XGBoost。

这也是我们要对这篇论文进行解读的地方。希望通过我们的解读,你也可以学习到,究竟应该如何分析他人的论文,从中汲取正确的部分。

错误的回归

论文中使用的是 XGBoost 回归模型。这可能是值得商榷的地方。在资产定价模型中,我们要预测的是股票在截面上的强弱,而不是在时序上的走势

论文作者这里使用的方法是训练一个回归模型,从而使得它能较好地预测次日(或者后面一段时间的走势)。

在这里插入图片描述

右图是论文作者得到的结果之一。看起来模型能比较完美地预测次日走势。


显然,由于 XGBoost 回归模型本身没有预测股票强弱的能力,所以,即使通过回归模型找出了完美拟合的股票,也没有任何意义。因为一支下跌中的股票,也可能被完美地拟合出来。所以,论文中提到的收益,即使有超额,很可能也来自于 MPT 理论。

但是,作者仍然给出了一个如何通过 XGBoost 来寻找多因子模型中表现最佳个股的线索。我们只需要把它改造成一个分类模型,然后通过分类模型,筛选出表现最好的股票就可以了

在这里插入图片描述

训练集中的 X 部分不用改变,但我们需要重新设定标签,即 y 部分。对给定的因子 X i X_i Xi,对应的 y i y_i yi需要能反映是上涨或者下跌。如果有可能,我们可以将标签设置为 5 类,-2 表示大跌,2 表示大涨,中间部分以此类推。

然后构造分类器进行训练。训练完成后,通过模型预测出来属于大涨标签的,我们就放入股票池,此时可以平均分配权重,也可以通过 MPT 理论来进行优化。

端到端训练及新的网络架构

论文作者使用的框架是两阶段式的,即先选择股票进入策略池,再通过 MPT 优化权重。


但即使是在第一阶段,它仍然是两段式的。每次进行训练时,它都只使用了一个标的的多因子数据。因此,如果 universe 中有 1000 支标的,就要训练出 1000 个模型(这是论文中暗示的方法,也可以考虑一个模型,训练 1000 次)。

这么做的原因是技术限制。XGBoost 只支持二维输入。如果我们要使用多个标的的多个因子同时进行训练,就必须使用 panel 格式的数据,或者将多个标的的多个因子进行一维展开。但如果标的数过多,展开后的训练会很困难。

也就是,由于技术限制,要么进行单因子的多标的同时训练,要么进行多因子的单标的训练。

但是,论文作者在这里给出了一个方法,就是你可以分别训练多个标的各自的模型,然后同样分别进行预测,然后再通过 MAPE 进行评估。当我们改成分类模型之后,可以简单地看分类结果,也可以结合分类的 metric 评估(在时序维度上的),选择准确性和分类结果都好的标的,纳入策略股票池。

损失函数与度量函数

接下来,我们要分析 MAPE 这个函数在论文中的使用。以此为契机,适当深入一点机器学习的原理,讲以下两个知识点:

1. 损失函数和度量函数
2. XGBoost模型,因子数据是否要标准化

损失函数与度量函数

在机器学习中,有两类重要的函数,一类是目标函数(objective function),又称损失函数(loss function);一类是度量函数(metrics)

在这里插入图片描述

损失函数用于模型训练。在训练过程中,通过梯度下降等方法,使得损失函数的值不断减小,直到无法继续下降为止,模型就训练完成。

训练完成之后的模型,将在test数据集上进行测试,并将预测的结果与真实值进行对比。为了将这个对比过程数值化,我们就引入了度量函数(metrics)

在sklearn中,提供了大量的损失函数和度量函数。下图列举了部分Sklearn提供的损失函数和度量函数:


在这里插入图片描述

可以看出,度量函数的个数远多于损失函数,这是为什么呢?

在论文中,论文作者并没有披露他通过xgboost训练的具体过程,只是说直接使用了xgboost的database,这个表述有点奇怪,我们可以理解为在参数上使用了XGBoost的默认值好了。


但是他重点提到了使用MAPE,从过程来看,是在把MAPE当成度量函数进行事后评估。

在XGBoost中,如果没有特别指定目标函数,那么默认会使用带正则惩罚的RMSE(rooted mean square error)函数。RMSE也可以作为度量函数,在论文中,作者没有使用RMSE作为度量函数,而是选择了MAPE(mean absolute percentage error),原因何在?如果MAPE在这个场景下比RMSE更好,又为何不在训练中使用MAPE?

看上去无论目标函数也好,度量函数也好,都要使得预测值与真实值越接近越好。既然都有这个特性,为什么还需要区分这两类函数呢?

要回答这些问题,就要了解XGBoost的训练原理,核心是:它是如何求梯度下降的。

XGBoost:二阶泰勒展开

XGBoost是一种提升(Boosting)算法,它通过多个弱学习器叠加,构成一个强学习器。每次迭代时,新的树会修正现有模型的残差,即预测值与真实值之间的差异。这个差异的大小,就由目标函数来计算。

在XGBoost中,多个弱学习器的叠加采用了加法模型,


即最终的预测是所有弱学习器输出的加权和。这种模型允许我们使用泰勒展开来近似损失函数,从而进行高效的优化。

XGBoost对目标函数的优化是通过泰勒二阶展开,再求二阶导来实现的。使用二阶导数,XGBoost可以实现更快速的收敛,因为它不仅考虑了梯度的方向,还考虑了损失函数的形状。

f ( x ) ≈ f ( a ) + f ′ ( a ) ( x − a ) + f ′ ′ ( a ) 2 ! ( x − a ) 2 f(x) \approx f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 f(x)f(a)+f(a)(xa)+2!f′′(a)(xa)2

正是由于XGBoost内部优化原理,决定了我们选择目标函数时,目标函数必须是二阶可导的。

RMSE是二阶可导的,但MAPE不是:MAPE从定义上来看,它的取值可以为零,在这些零值点附近连一阶导都不存在,就更不用说二阶导了。下图是MAPE的公式:

$$

\text{MAPE} = 100\frac{1}{n}\sum_{i=1}^{n}\left|\frac{\text{实际值} - \text{预测值}}{\text{实际值}} \right|
$$

当预测值与实际值一致时,MAPE的值就会取零。

如何选择目标函数?

选择MAPE作为度量函数,不仅仅是便于在不同的模型之间进行比较,在金融领域它还有特殊的重要性:


我们更在乎预测值与真实值之间的相对误差,而不是绝对误差。在交易中,百分比才是王者。正因为这个原因,如果在训练时,能够使用MAPE作为目标函数,这样预测出来的准确度,要比我们通过RSME训练出来的准确度,更接近实际应用。

这就是在具体领域,我们改进算法的一个切入点。已经有人发明了被称为SMAPE的损失函数,它的公式是:

$$

\text{SMAPE} = \frac{100}{n} \sum_{t=1}^n \frac{\left|F_t-A_t\right|}{(|A_t|+|F_t|)/2}
$$

到目前为止,sklearn还没有提供这个函数,但我们可以自己实现,并通过sklearn的make_scorer方法接入到sklearn系统中:


from sklearn.metrics import make_scorerdef smape(y_true, y_pred):return np.mean(2.0 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred)))smape_scorer = make_scorer(smape, greater_is_better=False)# 使用举例:在GridSearchCV中使用
grid_search = GridSearchCV(estimator=model, param_grid=params, scoring=smape_scorer)

Question: 既然训练中不能使用MAPE,那么论文在测试中,又为何要使用MAPE呢?

答案其实很简单,是为了便于在多个模型之间进行比较。在论文作者的算法中,每支股票都必须有自己的模型。由于每支股票的绝对价格不一样,因此,它们的RSME是不一样的,而MAPE相当于一个归一化的指标,从而可以在不同的模型之间进行比较,最终选择出误差最小的模型对应的股票,纳入策略股池。

但我们前面也提到过,论文作者的这个模型没有意义,改用分类模型会好一些。如果改用分类模型的话,损失函数也不再是RSME了,度量函数也不能是MAPE了。


标准化

论文中还提到,在训练之前,他将因子数据进行了标准化。

实际上,这也是没有意义的一步。因为XGBoost是决策树模型,它是通过特征值的比较来进行分裂和划分数据的,显然,分裂点的比较,并不依赖数据的量纲,因此,标准化就没有意义,反而可能带来精度损失问题,得不偿失。

hint
如果因子数据使用单精度浮点数储存,那么如果两个小数只在小数点的第7位数字之后才产生差异,这两个数字在比较时,实际上是一样的。如果我们在进行标准化时,把两个原来有大小差异的数字,缩放到了只在第7位数字之后才出现差异,就产生了精度损失。

当然,事情也不能一概而论。XGBoost使用正则化来控制树的复杂度,包括对叶节点的权重进行L2正则化。如果你在训练XGBoost模型时,损失函数加了正则惩罚,而特征未经过标准化,正则化的效果可能会变差。

另外,论文中的方法是,每支股票一个模型,但如果只用一个模型,但拿1000支股票的数据来训练1000次呢?显然,这个时候,就必须要提前进行标准化了。

否则,收敛会很困难(当然,即使使用了标准化,也不保证就能收敛。能否收敛,要看众多股票是否真的具有同样的特征到标签的映射关系)。这并不是XGBoost的要求,而是根据我们使用XGBoost的方法带来的额外要求。

结论

对多数量化人来说,我们不可能像陈天奇那样自己撸一个机器学习框架出来,因此,要用同样的模型,做出更优的结果,就只能在数据标注、目标函数、评估函数和参数调优等方面下功夫了。这往往既需要有较深的领域知识,也要对具体的模型原理有一定的了解。

原文链接


http://www.ppmy.cn/news/1504673.html

相关文章

LeetCode面试题Day1|LeetCode26 删除有序数组中的重复项、LeetCode80 删除有序数组中的重复项Ⅱ

前言: 暑假实在不知道干什么了,做一下力扣的《面试经典150题》吧,记录一下学习轨迹。(如果有要打非中文竞赛或者精进一下英语水平的记得把力扣调成英文) 题目1: 指路: . - 力扣(LeetCode)26…

Git学习——指令笔记

一、初始化配置 1.查看Git版本 git -v2.配置用户名和邮箱 git config --global user.name "your name" git config --global user.email "youremailemail.com"3.保存用户名和密码 git config --global credential.helper store4.查看配置信息 git con…

翻转二叉树 - 力扣(LeetCode)C语言

226. 翻转二叉树 - 力扣(LeetCode)(点击前面链接即可查看题目) 一、题目 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出…

【Pytorch】解决pytorch profiler导出的json文件在浏览器中无法打开的问题

笔者在服务器上进行pytorch profiler的实验,导出的json文件下载到本地之后通过edge://tracing打开时报错(edge和chrome使用的是同一套内核,笔者也测试了在chrome下同样报错): SyntaxError: Unexpected token i, ...&q…

C#实战 - C#实现冒泡算法

作者:逍遥Sean 简介:一个主修Java的Web网站\游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有疑问和建议,请私信或评论留言! 前言 当我们谈论排序…

从实现第一个ArkTs应用开始入门

前言 新建了个鸿蒙学习项目,后续持续学习会把代码放到这里来:鸿蒙项目仓库学习实践版 基本概念 从HarmonyOS NEXT Developer Preview1(API 11)版本开始,HarmonyOS SDK以Kit维度提供了六大领域的开放能力, 包括: 应用…

前端监控项目环境搭建-Kafka

目录 如何把 Docker 容器封装成镜像 Docker 部署 Kafka 集群(mac) Centos 容器启动 rsyslog 服务 Rsyslog 转发日志至 Kafka node-rdkafka2.18.0 使用文档 Librdkafka2.3.0 国内仓库 如何把 Docker 容器封装成镜像 背景:centos 容器…

siRNA药物药效预测(二)(Datawhale AI 夏令营)

一、task2解析 # 读取文件 df_original pd.read_csv("data/train_data.csv") # print(df_original) # 获取原始训练数据的行数 n_original df_original.shape[0] # 读取数据 df_submit pd.read_csv("data/sample_submission.csv")# 合并两个数据集 # a…