本公众号所推送的内容经由中信期货授权后发布,均为中信期货已公开的信息,不保证文中观点或陈述的完整性、准确性和更新及时性,且不作任何担保。本公众号所推送文章不构成任何形式的投资建议或销售要约,期货有风险,投资需谨慎。本篇报告将以“组合优化”为目标的凸优化方法与神经网络结合起来,构建了完整的期货组合量化投资框架。我们利用该框架回测部分期货市场的若干量价类因子,获得了较好的组合收益表现。
多因子策略是量化投研领域的经典策略之一,其在多类别金融市场均受到长时间的研究与应用;而无论何种市场,标的资产池的权重配置在多因子模型中扮演了“决定策略收益稳健性”的重要角色。因此,组合权重优化是多因子模型的一个重要课题。
聚焦到期货市场,经典的多因子研究包括alpha因子的挖掘和期货组合权重优化计算。市面常见的逻辑认为:任意期货都同时暴露于多种不同的风险因素下,这些风险因素的共同作用形成了期货合约价格的波动;通过对不同的风险因素以alpha因子的形式来有效刻画,我们可以实现对期货收益率的分解,从而研究期货合约价格波动的原因;此外,最优的投资组合则应当是经过“剔除其余不稳定的因素干扰、充分暴露于alpha因子、并一般通过凸优化方法将对收益率的预测转化为组合权重”等这样若干步处理后的结果;这些步骤中也涉及到一些针对于组合权重的约束条件的设置。而本报告区别于上述经典逻辑,尝试了基于深度学习的组合优化方法。本文以期货市场及其量价多因子为基础,将以“组合优化”为目标的凸优化与神经网络结合起来,构建了完整的期货组合量化投资框架。这里的“输入”是期货合约的基础行情数据,通过“构造若干量价因子、神经网络进行量价多因子的提取与合成、可分凸优化层传播梯度来优化期货组合权重、定义期货组合的收益率为损失度(目标)函数、以该损失度来优化整个神经网络等”来生成日频的期货组合权重,此即为“输出”。在合理的参数配置中,模型均能获得较好的收益表现。如每日调仓时,年化收益率约为18%、年化波动率约为5%、夏普率3.49。风险提示:本报告中所涉及的资产配比和模型应用仅为回溯举例,并不构成推荐建议。
一、问题背景
多因子策略是量化投研领域的经典策略之一,其在多类别金融市场均受到长时间的研究与应用;而无论何种市场,标的资产池的权重配置在多因子模型中扮演了决定策略收益稳健性的重要角色。因此,组合权重优化是多因子模型的一个重要课题。聚焦到期货市场,经典的多因子研究包括alpha因子的挖掘和期货组合权重优化计算。市面常见的逻辑认为:任意期货都同时暴露于多种不同的风险因素下,这些风险因素的共同作用形成了期货合约价格的波动;通过对不同的风险因素以alpha因子的形式有效刻画,我们可以实现对期货收益率的分解,从而研究期货合约价格波动的原因;而最优的投资组合则是应当是经过“剔除其余不稳定的因素干扰、充分暴露于alpha因子、并一般通过凸优化方法将对收益率的预测转化为组合权重”等这样若干步处理后的结果;这些步骤中也涉及到一些针对于组合权重的约束条件的设置。而本报告尝试了区别于上述经典逻辑的新方法,总体思路基于深度学习的组合优化。本文以期货市场及其量价多因子为基础,将以组合优化为目标的凸优化与神经网络结合起来,构建了完整的期货组合量化投资框架。这里的“输入”是期货合约的基础行情数据,通过“构造若干量价因子、神经网络进行量价多因子的提取与合成、可分凸优化层传播梯度来优化期货组合权重、定义期货组合的收益率为损失度(目标)函数、以该损失度来优化整个神经网络等”来生成日频的期货组合权重,此即为“输出”。
二、模型搭建设置与说明
经典组合优化中,常见的组合权重的生成方式包括风险预算模型、rankIC加权、由Barra风险暴露衍生的因子收益率加权等。本报告将要探讨的“凸优化方法与神经网络训练结合”的方式与这些经典的权重生成方法有较大的差异,因此本节将详尽的介绍中间步骤,包括:底层资产数据及考虑的量价类因子介绍、由存取数据衍生的数据解析方法汇总、基础的等权多因子分层回测、常用函数的定义、神经网络层的设计、训练方式的构造、循环-退出/早退-模型保存等机制的定义、模型测试及回测等。
(一)收盘价、收益率及因子数据
这里的底层标的资产样本池为29个不同商品/金融期货主力合约,样本区间为自2015年12月24日至2022年11月28日的1600多个交易日。![]()
因子层面:本篇报告仅考虑量价类的6个因子,分别是“3日动量”('mom_d3'),“243日动量”('mom_d243'),“10日动量”('mom_d10'),“243日最小二乘回归”('ols_d243'),“5日量价相关性”('cv_d5')和“ 61日振幅”('amp_d61_g4')。其相应的因子构造逻辑可参见本团队之前的相关研报。(二)数据解析——以适当的格式存取数据
商品期货和金融期货数据本身以及基于其行情类数据构造得到的量价类因子数据,有其自身的特点。其中之一则是由于不同期货合约上市日期不同以及量价类因子不同的回看期,造成了大量空值的出现。对于此类型数据,直接的方法涉及稀疏矩阵的存取。在数值分析中,稀疏矩阵是指其元素大部分为零的矩阵。反之,如果大部分元素都非零,则这个矩阵是稠密的。在科学与工程领域中求解线性模型时经常出现大型的稀疏矩阵。由于过大的尺寸,标准的算法经常无法操作这些稀疏矩阵。因此,在使用计算机存储和操作稀疏矩阵时,经常需要修改标准算法以利用矩阵的稀疏结构。由于其自身的稀疏特性,通过压缩可以大大节省稀疏矩阵的内存代价。具体落实到我们这里,体现为:商品期货和金融期货数据本身以及基于其行情类数据构造得到的量价类因子数据总体非常大,而我们基于单个项目需求发起的研究通常是与数据总体的一个非常小的子集进行交互,这就导致了极大的稀疏性。当处理稀疏矩阵时,将它们存储为一个完整的矩阵(从这里开始称为稠密矩阵)是非常低效的。这是因为一个完整的数组为每个条目占用一块内存,所以一个n x m数组需要n x m块内存。从简单的逻辑角度来看,存储这么多零是没有意义的。在python中,稀疏数据结构在scipy中得到了有效的实现,具体包含7类稀疏矩阵(如csc_matrix,bsr_matrix等)。实现背后的思想很简单:我们不将所有值存储在稠密矩阵中,而是以某种格式存储非零值(例如,使用它们的行和列索引)。另一种则是简单的数据预处理后再进行“数据解析”,即将一种数据格式转换为另一种可读格式。这个操作的出发点是:由于训练时需要反复读取数据,所花费的时间会比较大;因此我们可以选择最合适的方式将其存到本地,便于之后训练时的多次调用。这里我们对常用的数据解析方法做了一下对比,如下表所示。注:这里使用的机器是2 GHz 四核处理器及其上安装的python3.8.8和spyder4.2.5。![]()
从上表我们可以看到,hdf5文件的读写相对较快,其次是pkl文件,再次是npy和bin这两种格式的文件,而txt则明显耗时较多;空间占用角度,前四者没有显著差异,而后者txt比较占空间,不推荐使用;此外,我们也注意到bin和txt格式在存储、读取过程中对于类型/形状处理的要求。综上,基于我们这里考虑的这份“1683个交易日、29个商品/金融期货主力合约、6个量价因子”的数据样本而言,最优的格式我们推荐hdf5、npy和pkl。
我们得到的这个结论,符合对于上述5种格式效能的一般认知;当然随着数据量的增大,也可进一步做出更鲜明的区分。那么下文中具体使用到的格式为h5py文件。第一次导入数据后在一段时间内会于缓存中保存,这时候再次导入会很快。所以在训练神经网络时,除了第一个epoch运行较慢外,后面读取的速度都会较第一次有所提升。(三)等权合成多因子的分层回测
这里我们先给出基于等权方式合成多因子的经典分层回测结果,如下图所示:![]()
上述回测净值曲线对应的净值分析如下:
![]()
1.类ARGS()
类的构造函数,用于初始化类的内部状态,为类的属性设置默认的值。
2.类MyDataset(Dataset)
Dataset自torch.utils.data导入。torch.utils.data.Dataset是代表自定义数据集方法的类,用户可以通过继承该类来自定义自己的数据集类,在继承时要求用户重载__len__()和__getitem__()这两个方法。__len__():返回的是数据集的大小。我们构建的数据集是一个对象,而数据集不像序列类型(列表、元组、字符串)那样可以直接用len()来获取序列的长度,该方法__len__()的目的就是方便像序列那样直接获取对象的长度。如果A是一个类,a是类A的实例化对象,当A中定义了该方法__len__(),len(a)则返回对象的大小。__getitem__():实现索引数据集中的某一个数据。我们知道,序列可以通过索引的方法获取序列中任意元素,__getitem__()则实现了能够通过索引的方法获取对象中任意元素。此外,我们可以在__getitem__()中实现数据预处理。
(五)神经网络层的准备
这里我们使用了一个五层的神经网络:头部输入层、尾部输出层和中间的三个隐含层。具体细节如下:![]()
第一层:输入层;
第二层:全连接层fc_1,之后使用rectification之一ReLU();第三层:全连接层fc_2,之后使用rectification之一Tanh();第四层:cvxpylayer层,使用前需把第三层的输出拉成一维,使用时两种选择SparseMaxLayer();从“添加新层”的角度来看,后续的拓展方向之一是“考虑不同的均方误差计算方式”从而来构造新的神经网络层;值得一提的是我们这里使用到了凸优化层cvxpylayers。简要来讲,它是一个python库,在CVXPY的基础上整合了PyTorch、JAX和TensorFlow的接口,便于用户在构造神经网络时来调用这些框架里的层以及建立可分的凸优化层。具体而言,cvxpylayers始见于2019年第33届NeurIPS会议的论文《Differentiable convex
optimization layers》,Stephen Boyd等作者针对更一般化的凸优化问题提出了可分凸优化层cvxpylayers。作者引入了规范凸规划(Disciplined convex
programming)的一类子问题——规范参数化规划(Disciplined parametrized
programming),然后表明每一个规范参数化项目可以表征为“仿射-求解器-仿射”的模式(Affine-Solver-Affine)“。其中的第一个“仿射”是“从模型参数到求解问题所涉及数据”的仿射映射,第二个“仿射”则是从求解器的解到原问题的解的的仿射映射。这样的构造使得求导变得可能,使我们不仅能用凸优化的方法完成优化,还能求出凸优化输出值对模型中带优化参数的梯度,以便神经网络进行梯度反向传播。![]()
(六)训练方式的构造
考虑到总的时间序列长度为1683个交易日,我们这里的“训练-验证”集的选取及神经网络训练效果的呈现方式原则如下:1.最外层for循环设置:从第400个交易日到第1300个交易日、滚动式地每隔100个交易日的进行一次循环(总共10次),以此来客观输出训练效果;2.
for循环中数据载入:对每一次循环(每隔100个交易日),我们以“使用end_date铆定时间点、回看lookback(200)确定时间序列长度”的方式来提取因子和收益率的子集,然后将这lookback(200)个因子子集和收益率子集分别拼装成(200个交易日*29个商品/金融期货品种,6个量价因子)的本轮循环因子表和(200个交易日*29个商品/金融期货品种,1列收益率)的本轮循环收益率表;3. 关于训练集与验证集的划分:区别于机器学习中经典的“80-20划分”,我们这里使用了更个性化的截取。具体而言,对于每一次for循环中重新载入的本轮循环因子表和本轮循环收益率表,我们分别取其前2900条数据(100*29个商品/金融期货)作为本轮循环因子表的训练集和本轮循环收益率表的训练集;接着,我们再选自第3509条((100+21)*29个商品/金融期货)开始至最末的数据作为本轮循环因子表的验证集和本轮循环收益率表的验证集(注意:这里的100和21都是技术性数字,可以根据自己的研究需要进行调整,原则是训练集和验证集的数据互不相交);4.经典训练步骤的沿用:在用pytorch训练模型时,我们在遍历epochs的过程中采用了经典神经网络训练“三步走”的方式,即依次使用了optimizer.zero_grad(),loss.backward()和optimizer.step()三个函数。简要来讲,这三个函数的作用是先将梯度归零(optimizer.zero_grad()),然后反向传播计算得到每个参数的梯度值(loss.backward()),最后通过梯度下降执行一步参数更新(optimizer.step())。optimizer.zero_grad():鉴于训练过程较常用到mini-batch方法,这一操作的目的是在“反向传播和梯度下降”之前清除与上一个batch数据相关的梯度。该函数会遍历模型的所有参数,通过p.grad.detach_()方法截断反向传播的梯度流,再通过p.grad.zero_()函数将每个参数的梯度值设为0,即上一次的梯度记录被清空;
loss.backward():PyTorch的反向传播(即tensor.backward())是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。如果没有进行tensor.backward()的话,梯度值将会是None,因此loss.backward()要写在optimizer.step()之前;optimizer.step():step()函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。应当区分的是,optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的;其中,成本=1-(累计训练所得权重-上一次训练训练)*成本率,而累计训练所得权重则有若干轮历史训练所得权重按交易日期拼接而成。该损失度函数定义的目的是最大化总收益。注意:后文中,我们也将这里定义的损失度函数loss取负值(负负得正)命名为(训练/验证的)得分。注:这里的成本率我们设置为千分之三。
(七)循环、退出/早停、模型保存
1.总的训练步数n_epochs=50;warm_up轮(热身)设置为5;停止时运行所至步数、最优得分所对应步数皆初始化为0;3.如果前warm_up轮验证集分数一直在下降则退出重新初始化后再训练;5.如果验证得分>最优得分且训练步数>warm_up轮数,则将验证得分赋值给最优得分、当前步数复制给最优得分所对应步数、保存该模型以便于回测时调用;同时早停标识符stop_steps赋值0;6.如果上一步的条件不满足,则早停标识符stop_steps加1;如果早停标识符大于训练容忍度(patience),则早停结束训练(实际操作中,我们这里训练容忍度设置为10);7.期货品种每日最大权重上限设置为0.075。
(八)测试预测权重
对于上述训练-验证得到的最优模型,我们这里进行测试。下图是神经网络循环训练-验证过程中的得分(损失度的相反数与IC值的对比):![]()
![]()
下图是测试集上所得的关于全体入选期货品种每日权重的部分示意图:
![]()
(九)回测
我们这里对整个区间进行回测。除了前面已经提到的:29个期货主力合约、6个量价类因子、2015年12月到2022年12月的回测区间,我们再实际回测中还考虑到了了调仓频率win的概念,目的是为了对经神经网络训练得到的权重这样一个时间序列进行平滑、以消除极个别特殊现象对整体趋势的影响。![]()
![]()
![]()
三、总结
本篇报告以期货市场及其量价多因子为基础,将以组合优化为目标的凸优化与神经网络结合起来,构建了完整的期货组合量化投资框架。这里的“输入”是期货合约的基础行情数据,通过“构造若干量价因子、神经网络进行量价多因子的提取与合成、可分凸优化层传播梯度来优化期货组合权重、定义期货组合的收益率为损失度(目标)函数、以该损失度来优化整个神经网络等”来生成日频的期货组合权重,此即为“输出”。在合理的参数配置中,模型均能获得较好的收益表现。如每日调仓时,年化收益率约为18%、年化波动率约为5%、夏普率3.49。
“研而有信”是金融策略团队成果分享平台,覆盖大类资产配置、产品策略、金融期货策略、期权与ETF策略。团队致力于为市场带来专业、特色的研究产品和服务,感谢您的关注!