Python-机器学习算法交易实用指南-二-

424 阅读1小时+

Python 机器学习算法交易实用指南(二)

原文:zh.annas-archive.org/md5/fcb09c483bdb21866eb6782158d1f8d5

译者:飞龙

协议:CC BY-NC-SA 4.0

第四章:阿尔法因子研究

算法交易策略受信号驱动,该信号指示何时购买或出售资产以产生相对于基准的正收益。资产收益的一部分不受基准暴露的影响被称为阿尔法,因此这些信号也被称为阿尔法因子

阿尔法因子旨在基于可用的市场、基本或替代数据预测投资领域资产的价格走势。一个因子可能结合一个或多个输入变量,但在每次策略评估因子时为每个资产假定一个单一值。交易决策通常依赖于跨资产的相对值。交易策略通常基于多个因子发出的信号,我们将看到机器学习ML)模型特别适合有效地整合各种信号以进行更准确的预测。

阿尔法交易策略工作流程研究阶段的设计、评估和结合阿尔法因子是关键步骤,如下图所示。我们将在这个第四章中专注于研究阶段,策略评估,并在下一章中关注执行阶段。本书的其余部分将着重于使用 ML 来发现和结合阿尔法因子。请看下面的图:

本章将使用简单的均值回归因子介绍用 Python 编写的算法交易模拟器zipline,并促进对给定投资领域的阿尔法因子进行测试。在下一章中,我们还将在组合上回测交易策略时使用zipline。接下来,我们将讨论评估阿尔法因子预测性能的关键指标,包括信息系数和信息比率,这导致了主动管理的基本定律。

特别是,本章将讨论以下主题:

  • 如何表征、证明和衡量关键类型的阿尔法因子

  • 如何使用金融特征工程创建阿尔法因子

  • 如何离线使用zipline测试单个阿尔法因子

  • 如何在 Quantopian 上使用zipline结合阿尔法因子并识别更复杂的信号

  • 信息系数IC)如何衡量阿尔法因子的预测性能

  • 如何使用alphalens评估预测性能和换手率

工程化阿尔法因子

阿尔法因子是包含预测信号的市场、基本和替代数据的转换。它们旨在捕获驱动资产收益的风险。一组因子描述了基本的、全面的变量,如增长、通货膨胀、波动性、生产力和人口风险。另一组包括可交易的投资风格,如市场组合、价值增长投资和动量投资。

还有一些因素可以根据金融市场的经济学或机构设置,或者投资者行为来解释价格的变动,包括这种行为的已知偏差。因素背后的经济理论可以是合理的,其中因子在长期内具有高回报,以弥补它们在不好的时期的低回报,或者是行为的,其中因子风险溢价来自于可能偏见的、或者不完全理性的代理人行为,这种行为不会被套利掉。

不断寻找和发现可能更好地捕捉已知或反映新收益驱动因素的新因素。Research Affiliates 的联合创始人、管理近 2000 亿美元的 Jason Hsu 在 2015 年确定了约 250 个因素,在知名期刊中发表了经验证据,并估计该数字每年可能增加 40 个因素。为了避免假发现并确保因子提供一致的结果,它应该有一个有意义的经济直觉,使得它可信地反映市场会补偿的风险。

数据转换包括简单的算术,如某一变量随时间的绝对或相对变化、数据系列之间的比率,或者在时间窗口内的聚合,例如简单或指数移动平均。它们还包括从价格模式的技术分析中出现的计算,比如需求与供应的相对强度指数和从证券基本分析中熟悉的众多指标。

重要的因子类别

在理想化的世界中,风险因子的类别应该彼此独立(正交),产生正的风险溢价,并形成一个完整的集合,涵盖了资产在给定类别中的所有风险维度,并解释了系统风险。实际上,这些要求只会近似成立。我们将介绍如何使用无监督学习来推导合成的、数据驱动的风险因子,特别是在 第十二章,无监督学习 中的主成分和独立成分分析。

我们将审查从市场、基本和替代数据衍生的因子的关键类别,以及用于捕捉它们的典型指标。我们还将演示如何在 Quantopian 平台上测试算法时实现这些因子,使用内置因子、使用 numpypandas 进行自定义计算,或者使用 talib 库进行技术分析。

动量和情绪因子

动量投资遵循这样的格言:趋势是你的朋友,或者让你的赢家继续奔跑。动量风险因子旨在做多表现良好的资产,同时做空表现不佳的资产,而且要在一定期间内表现良好。

依赖这一因素的策略的前提是资产价格表现出趋势,反映在正的串行相关性中。这种价格动量会违背有效市场假设的假设,即过去的价格回报本身不能预测未来的表现。尽管存在相反的理论论据,但价格动量策略在各类资产中产生了正的回报,并且是许多交易策略的重要组成部分。

理由

动量效应的原因指向投资者行为、持续的供求失衡、风险资产和经济之间的正反馈循环,或者市场微观结构。

行为原因反映了投资者对市场新闻的反应偏差,因为投资者以不同的速度处理新信息。在对新闻的初始反应之后,投资者经常会推断过去的行为,并产生价格动量。上世纪九十年代晚期科技股市场泡沫期间的上涨就是一个极端例子。恐惧和贪婪心理也促使投资者增加对获胜资产的曝光,并继续出售亏损资产。

动量也可以有基本的驱动因素,比如风险资产与经济之间的正反馈循环。经济增长推动了股票市场,而由此产生的财富效应通过更高的支出再次反馈到经济中,从而推动增长。价格和经济之间的正反馈往往会使股票和信贷的动量延伸到比债券、外汇和商品更长的时间跨度,其中负反馈会引发反转,需要更短的投资视野。动量的另一个原因可能是由于市场摩擦造成的持续的供需失衡,例如,当商品生产需要较长时间来调整以适应需求趋势时。石油生产可能会滞后于经济繁荣引起的需求增加多年,持续的供应短缺可能会引发并支持价格上涨的动量。

市场微观结构效应也可能会产生与行为模式相关的价格动量,这些行为模式促使投资者购买产品并实施模仿其偏见的策略。例如,根据交易智慧割损和让利润增长的原则,投资者使用交易策略,如止损、恒定比例组合保险(CPPI)、动态对冲、或者基于期权的策略,如保护性买入期权。这些策略会产生动量,因为它们意味着在资产表现不佳时进行预先承诺的出售,并在其表现优异时进行购买。同样,风险平价策略(见下一章)倾向于购买低波动率资产,这些资产通常表现出积极的业绩,并出售高波动率资产,这些资产通常表现不佳。使用这些策略自动平衡投资组合会加强价格动量。

关键指标

动量因子通常是通过识别趋势和模式来推导价格时间序列的变化得到的。它们可以基于绝对回报或相对回报构建,通过比较资产的横截面或分析资产的时间序列,在传统资产类别内或跨越传统资产类别,并在不同的时间范围内进行。

下表列出了一些流行的示意性指标:

因子描述
相对强度指标 (RSI)RSI 比较股票的最近价格变动,以识别股票是否被过度买入或过度卖出。高 RSI(例如,超过 70)表示过度买入,低 RSI(例如低于 30)表示过度卖出。它使用给定数量的前几个交易日的平均价格变动与正价格变动  和负价格变动  计算: 
价格动量此因子计算给定数量的前几个交易日的总回报。在学术文献中,通常使用过去 12 个月,但排除最近的一个月,因为最近价格波动经常观察到短期反转效应,但也广泛使用较短的周期。
12 个月价格动量 Vol Adj考虑波动率因子的 12 个月价格动量调整将前 12 个月的总回报归一化,方法是将其除以这些回报的标准偏差。
价格加速度价格加速度使用线性回归在较长和较短周期的每日价格上计算趋势的梯度(根据波动率调整),例如,一年和三个月的交易日,并比较斜率的变化作为价格加速度的衡量。
百分比偏离 52 周最高价此因子使用最近 52 周的最高价格与最近价格之间的百分差异。

其他情绪指标包括以下内容:

因子描述
盈利预测数量此度量指标根据共识估计数量对股票进行排名,作为分析师覆盖范围和信息不确定性的代理。较高的值更可取。
推荐 N 个月变化此因子根据前 N 个月的共识推荐变化对股票进行排名,改善是可取的(无论是从强卖出到卖出还是从买入到强买入等)。
流通股份 12 个月变化此因子衡量公司上市股份计数在过去 12 个月的变化,其中负变化意味着股份回购,这是可取的,因为它表明管理层认为股票相对于其内在价值和未来价值而言便宜。
目标价格变动的 6 个月变化此指标跟踪平均分析师目标价格的 6 个月变化,较高的正变化自然更可取。
净收益修订这一因素表示盈利预测的上调和下调之间的差异,以修订总数的百分比表示。
流通股份的卖空量这个指标是目前被卖空的流通股份的百分比,即由借入股份并在以后的某一天回购它的投资者出售的股份,而这个投资者在猜测其价格将下跌。因此,高水平的卖空量表明负面情绪,并预示着未来的表现不佳。

价值因素

相对于其基本价值而言价格较低的股票往往会产生超过市值加权基准的回报。价值因素反映了这种相关性,并旨在提供购买被低估资产的信号,即那些相对便宜的资产,并卖出那些被高估和昂贵的资产。因此,任何价值策略的核心都是一个估计或代理资产公平或基本价值的估值模型。公平价值可以定义为绝对价格水平、相对于其他资产的价差,或者资产应该交易的范围(例如,两个标准偏差)。

价值策略依赖于价格回归到资产的公平价值。它们假设价格仅因行为效应(例如过度反应或群体行为)或流动性效应(例如临时市场影响或长期供求摩擦)而暂时偏离公平价值。由于价值因素依赖于价格回归,它们通常表现出与动量因素相反的性质。对于股票来说,价值股的对立面是由于增长预期而具有高估值的成长股。

价值因素可以实现广泛的系统性策略,包括基本和市场估值、统计套利和跨资产相对价值。它们通常被实施为无暴露于其他传统或替代风险因素的多空投资组合。

基本价值策略从取决于目标资产类别的经济和基本指标中得出公平资产价值。在固定收益、货币和大宗商品方面,指标包括资本账户余额水平和变动、经济活动、通货膨胀或资金流动等。在股票和企业信用方面,价值因素可以追溯到格雷厄姆和多德在上世纪三十年代的《证券分析》中,后来被沃伦·巴菲特所著名化。股票价值方法将股票价格与基本指标(例如账面价值、销售额、净利润或各种现金流量指标)进行比较。

市值策略使用统计学或机器学习模型来识别由流动性供给不足引起的错价。统计套利和指数套利是突出的例子,捕捉短期时间范围内临时市场影响的回归(我们将在下一章中讨论配对交易)。在较长的时间范围内,市值交易还利用股票和商品的季节效应。

跨资产相对价值策略侧重于不同资产的相对错价。例如,可转换债券套利涉及对单个公司的股票、信用和波动性之间的相对价值进行交易。相对价值还包括信用和股票波动性之间的交易,利用信用信号交易股票或商品和股票之间的相对价值交易。

理性

存在价值效应的理性和行为学解释都有。我们将引用一些来自丰富研究的著名例子,并在 GitHub 存储库中列出更多参考资料。

在理性、高效的市场观点中,价值溢价是对较高的实际或感知风险的补偿。研究人员提出了证据,表明价值公司比精益和更灵活的成长公司在适应不利的经济环境方面的灵活性更小,或者价值股的风险与高财务杠杆和更不确定的未来收益有关。研究还表明,价值和小市值投资组合对宏观冲击的敏感性也比成长和大市值投资组合更高。

从行为学的角度来看,价值溢价可以通过损失规避和心理会计偏见来解释。投资者可能对具有强劲近期表现的资产的损失较少担心,因为之前收益提供了保障。这种损失规避偏见导致投资者认为股票的风险较以前较低,并以较低的利率贴现其未来现金流。相反,较差的近期表现可能导致投资者提高资产的贴现率。不同的回报预期导致了价值溢价,因为相对于基本面的高价格倍数的成长股在过去表现良好,但是在未来,投资者将要求较低的平均回报,因为他们对风险的偏见认知较低,而价值股则相反。

关键指标

从基本数据计算出了大量的估值代理。这些因素可以作为机器学习估值模型的输入,用于预测价格。在接下来的章节中,我们将看到一些这些因素在实践中如何被使用的例子:

因素描述
现金流收益该比率将每股运营现金流除以股价。较高的比率意味着股东获得更好的现金回报(如果通过股息或股票回购支付,或者利润投资于业务中)。
自由现金流收益率该比率将每股自由现金流(反映必要费用和投资后可分配的现金金额)除以股价。较高且增长的自由现金流收益率通常被视为超额表现的信号。
投入资本的现金流回报率 (CFROIC)CFROIC 衡量公司的现金流盈利能力。它将经营现金流除以投入资本,定义为总债务加净资产。更高的回报意味着企业在给定的投入资本量下有更多现金,为股东创造更多价值。
现金流与总资产比该比率将经营现金流除以总资产,表示公司相对于其资产可以生成多少现金,较高的比率类似于 CFROIC。
企业价值的自由现金流量该比率衡量公司相对于其企业价值(以股权和债务的合计价值衡量)生成的自由现金流量。
EBITDA 与企业价值比该比率衡量了公司的 EBITDA(利润前利息、税项、折旧和摊销),即相对于其企业价值的现金流量代理。
收益率(1 年滞后)该比率将过去 12 个月的收益总和除以最后市场(收盘)价格。
收益率(1 年预测)该比率将滚动 12 个月的分析师盈利预期除以最后价格,而不是实际历史盈利,其中共识是预测的(可能是加权的)平均值。
PEG 比率价格/盈利增长(PEG)比率将一定时期内的股票价格盈利(P/E)比率除以公司的盈利增长率。该比率通过公司的盈利增长调整支付的价格(由 P/E 比率衡量)。
预测的 1 年前 PE 相对于行业的预测的市盈率相对于相应的行业市盈率。它旨在通过考虑行业在估值上的差异来减轻通用市盈率的行业偏见。
销售收益率该比率衡量股票的估值相对于其产生收入的能力。其他条件相等,具有较高历史销售价格比率的股票预计会表现出色。
销售收益率 FY1前瞻性销售价格比率使用分析师销售预测,与(加权的)平均值结合。
帐面价值收益率该比率将历史帐面价值除以股价。
股息收益率当前年化股息除以最近收盘价。贴现现金流量估值假设公司的市值等于其未来现金流的现值。

波动性和规模因素

低波动因子捕捉到具有低于平均水平的波动率、贝塔或特有风险的股票的超额回报。具有较大市值的股票往往具有较低的波动性,因此传统的size因子通常与较新的波动性因子结合使用。

低波动性异常是一种与金融基本原理相矛盾的实证难题。资本资产定价模型CAPM)和其他资产定价模型断言,高风险应该获得更高的回报,但在许多市场和长时间内,相反情况发生,较低风险的资产表现优于其风险较高的同行。

理由

低波动性异常与有效市场假说和 CAPM 假设相矛盾。相反,已提出了几种行为解释。

彩票效应建立在个人承担类似彩票的投注的经验证据上,这些投注存在小额预期损失但可能有较大潜在赢利,即使这种大赢利可能性相当低。如果投资者认为低价格、波动性大的股票的风险-回报特征类似于彩票,那么它可能是一种有吸引力的投注。因此,投资者可能会为高波动性股票支付过高的价格,并由于其偏见偏好而为低波动性股票支付过低的价格。代表性偏差表明,投资者将一些广为人知的高波动性股票的成功推广到所有高波动性股票,而忽视了这些股票的投机性质。

投资者可能也过于自信地认为他们能够预测未来,对于风险更大、结果更不确定的波动性股票,他们的意见差异更大。由于通过持有资产而不是通过做空来表达积极观点更容易,即持有一个资产,乐观主义者可能会超过悲观主义者,继续推动波动性股票的价格上涨,导致较低的回报。

此外,投资者在牛市和危机期间的行为不同。在牛市期间,贝塔的离散度要低得多,因此低波动性股票甚至不会表现得差,而在危机期间,投资者寻求或保持低波动性股票,贝塔的离散度增加。因此,低波动性资产和投资组合在长期内表现更好。

关键指标

用于识别低波动性股票的度量覆盖了广泛的领域,其中一个端是实现的波动率(标准差),另一个端是预测(隐含)波动率和相关性。一些人将低波动性操作化为低贝塔。对于不同的度量,支持波动性异常的证据似乎是强有力的。

质量因素

质量因子旨在捕捉高盈利、运营高效、安全、稳定和良好治理的公司相对于市场的超额回报。市场似乎也会奖励相对盈利的确定性,并惩罚盈利波动性较高的股票。向高质量企业倾斜的投资组合长期以来一直被依靠基本分析的股票选手所提倡,但在量化投资中是一个相对较新的现象。主要挑战在于如何使用定量指标一致和客观地定义质量因子,考虑到质量的主观性。

基于独立质量因子的策略往往表现出逆周期性,因为投资者支付溢价以减小下行风险并推高估值。因此,质量因子经常与其他风险因子结合在一起,最常见的是与价值因子结合以产生合理价格的质量策略。长短期质量因子往往具有负市场β值,因为它们做多质量股票,这些股票也是低波动性的,同时做空更具波动性、低质量的股票。因此,质量因子通常与低波动性和动量因子呈正相关,与价值和广泛的市场暴露呈负相关。

理由

质量因子可能会暗示超额表现,因为持续的盈利能力、现金流量稳定增长、审慎的杠杆使用、对资本市场融资的低需求或长期内的低金融风险支撑了对股票的需求,并在长期内支撑了这些公司的股价。从公司财务的角度来看,质量公司通常会谨慎管理其资本,减少过度杠杆或过度资本化的风险

行为解释表明,投资者对质量信息存在反应不足,类似于动量交易的理由,投资者追逐赢家并卖出输家。另一个支持质量溢价的论点是类似于成长股的群体效应。基金经理可能会发现,即使一家公司的基本面强劲但价格昂贵,也比购买更具波动性(风险性)的价值股更容易被合理化。

关键指标

质量因子依赖于从资产负债表和利润表计算出的指标,这些指标表明了高利润或现金流边际、经营效率、财务实力和更广泛的竞争力,因为这意味着公司能够持续一段时间内保持盈利能力。

因此,质量常常通过毛利率(最近已添加到法玛—法朗奇因子模型中,参见第七章,线性模型)、投入资本回报率、低盈利波动性或各种盈利能力、盈利质量和杠杆指标的组合进行衡量,以下表格列出了一些选项。

盈余管理主要通过操纵应计项目来实施。因此,应计项目的大小通常被用作盈余质量的代理:相对于资产较高的总应计项目会使低盈余质量更有可能。然而,这并不一定清楚,因为应计项目既可以反映盈余操纵,也可以像未来业务增长的会计估计一样反映出来。

因子描述
资产周转率此因子衡量公司使用资产(需要资本)生产收入的效率,并通过将销售额除以总资产来计算;更高的周转率更好。
资产周转率 12 个月变化此因子衡量管理层在过去一年中利用资产产生收入的效率变化。通常预计,效率改善水平最高的股票将表现优异。
流动比率流动比率是衡量公司偿付短期债务能力的流动性指标。它将公司的流动资产与流动负债进行比较,从质量的角度来看,流动比率较高更好。
利息保障此因子衡量公司支付债务利息的能力。它是通过将公司的息税前利润(EBIT)除以其利息支出来计算的。较高的比率是可取的。
杠杆与股本相比,负债显著多于权益的公司被认为是高度杠杆的。负债权益比通常与前景呈负相关,杠杆越低越好。
分红比率向股东支付的股息金额。分红比率较高的股票被分配到前十分位,而分红比率较低的股票则被分配到底十分位。
净资产收益率ROE根据历史净资产收益率对股票进行排名,并将净资产收益率最高的股票分配到前十分位。

如何将数据转化为因子

基于对关键因子类别、它们的原理和流行指标的概念理解,一个关键任务是确定可能更好地捕捉之前所述的回报驱动因素所包含的风险的新因子,或者找到新因子。在任何情况下,比较创新因子与已知因子的表现将是重要的,以确定增量信号增益。

有用的 pandas 和 NumPy 方法

NumPy 和 pandas 是自定义因子计算的关键工具。数据目录中的 Notebook 00-data-prep.ipynb 包含了如何创建各种因子的示例。该笔记本使用由 GitHub 仓库根目录中的 data 文件夹中的 get_data.py 脚本生成并以 HDF5 格式存储的数据以提供更快的访问。请参阅 GitHub 仓库中的 Chapter 2 目录下的笔记本 storage_benchmarks.ipynb,以比较 pandas DataFramesparquetHDF5csv 存储格式。

以下示例说明了从原始股票数据计算选定因子的一些关键步骤。有关详细信息和我们在此处省略的可视化效果,请参见笔记本。

加载数据

我们加载了 Quandl 股价数据集,涵盖了 2000 年至 2018 年的美国股票市场,使用 pd.IndexSlicepd.MultiIndex 执行切片操作,选择调整后的收盘价,并将列进行转置,将 DataFrame 转换为宽格式,其中列中包含股票代码,行中包含时间戳:

idx = pd.IndexSlice
with pd.HDFStore('../../data/assets.h5') as store:
    prices = store['quandl/wiki/prices'].loc[idx['2000':'2018', :], 
                   'adj_close'].unstack('ticker')

prices.info()
DatetimeIndex: 4706 entries, 2000-01-03 to 2018-03-27
Columns: 3199 entries, A to ZUMZ

从日频率重新采样为月频率

为了减少训练时间并尝试针对更长时间范围的策略,我们使用可用的调整后的收盘价将商业日数据转换为月末频率:

monthly_prices = prices.resample('M').last()

计算动量因子

为了捕捉捕捉时间序列动态,例如动量模式,我们使用 pct_change(n_periods) 计算历史回报,即由 lags 指定的各种月期的回报。然后,我们使用 .stack() 将宽格式结果转换回长格式,使用 .pipe() 应用 .clip() 方法到结果的 DataFrame 并将回报截尾到 [1%, 99%] 的水平;即我们在这些百分位数上限制异常值。

最后,我们使用几何平均数对回报进行归一化处理。使用 .swaplevel() 更改 MultiIndex 索引级别的顺序后,我们获得了从 1 到 12 个月的六个周期的合并月回报:

outlier_cutoff = 0.01
data = pd.DataFrame()
lags = [1, 2, 3, 6, 9, 12]
for lag in lags:
    data[f'return_{lag}m'] = (monthly_prices
                           .pct_change(lag)
                           .stack()
                           .pipe(lambda x: x.clip(lower=x.quantile(outlier_cutoff),
                        upper=x.quantile(1-outlier_cutoff)))
                           .add(1)
                           .pow(1/lag)
                           .sub(1)
                           )
data = data.swaplevel().dropna()
data.info()

MultiIndex: 521806 entries, (A, 2001-01-31 00:00:00) to (ZUMZ, 2018-03-
                             31 00:00:00)
Data columns (total 6 columns):
return_1m 521806 non-null float64
return_2m 521806 non-null float64
return_3m 521806 non-null float64
return_6m 521806 non-null float64
return_9m 521806 non-null float64
return_12m 521806 non-null float6

我们可以使用这些结果来计算动量因子,这些因子基于较长周期的回报与最近一个月的回报之间的差异,以及基于312个月回报之间的差异,如下所示:

for lag in [2,3,6,9,12]:
    data[f'momentum_{lag}'] = data[f'return_{lag}m'].sub(data.return_1m)
data[f'momentum_3_12'] = data[f'return_12m'].sub(data.return_3m)

使用滞后回报和不同持有期

为了将滞后值用作当前观察到的输入变量或特征,我们使用 .shift() 方法将历史回报移至当前期间:

for t in range(1, 7):
    data[f'return_1m_t-{t}'] = data.groupby(level='ticker').return_1m.shift(t)

类似地,为了计算各种持有期的回报,我们使用之前计算的标准化期回报,并将其向后移动以与当前金融特征对齐:

for t in [1,2,3,6,12]:
    data[f'target_{t}m'] = data.groupby(level='ticker')[f'return_{t}m'].shift(-t)

计算因子贝塔系数

我们将在第八章 时间序列模型 中介绍 Fama—French 数据,使用线性回归估计资产对常见风险因子的暴露。已经通过实证研究表明,Fama—French 的五个因子,即市场风险、规模、价值、经营利润能力和投资,在解释资产回报方面是有效的,并且常用于评估投资组合的风险/回报特征。因此,在旨在预测未来回报的模型中,自然而然地包括过去的因子暴露作为金融特征。

我们可以使用 pandas-datareader 访问历史因子回报,并使用 pyfinance 库中的 PandasRollingOLS 滚动线性回归功能来估计历史暴露,具体如下:

factors = ['Mkt-RF', 'SMB', 'HML', 'RMW', 'CMA']
factor_data = web.DataReader('F-F_Research_Data_5_Factors_2x3', 
              'famafrench', start='2000')[0].drop('RF', axis=1)
factor_data.index = factor_data.index.to_timestamp()
factor_data = factor_data.resample('M').last().div(100)
factor_data.index.name = 'date'
factor_data = factor_data.join(data['return_1m']).sort_index()

T = 24
betas = (factor_data
         .groupby(level='ticker', group_keys=False)
         .apply(lambda x: PandasRollingOLS(window=min(T, x.shape[0]-1), y=x.return_1m, x=x.drop('return_1m', axis=1)).beta))

我们将在第七章中更详细地探讨 Fama—French 因子模型和线性回归,线性模型。请参阅笔记本以获取更多示例。

内置 Quantopian 因子

附带的笔记本factor_library.ipynb包含了许多示例因子,这些因子要么由 Quantopian 平台提供,要么从 Jupyter Notebook 使用的研究 API 可用的数据源计算而来。

有内置因子可以与量化 Python 库一起使用,特别是numpypandas,从各种相关数据源中导出更复杂的因子,例如美国股票价格、Morningstar 基本面和投资者情绪。

例如,销售收益率的倒数价格销售比是 Morningstar 基本面数据集的一部分。它可以作为一种管道的一部分,随着我们介绍zipline库而进一步描述。

TA-Lib

TA-Lib 库包括许多技术因素。Python 实现可用于本地使用,例如与ziplinealphalens,也可在 Quantopian 平台上使用。笔记本还演示了使用 TA-Lib 可用的几个技术指标。

寻找信号 - 如何使用 zipline

历史上,Alpha 因子使用单一输入和简单的启发式方法、阈值或分位数截止值来识别买入或卖出信号。ML 已被证明可以从更多样化和更大量的输入数据中提取信号,包括基于历史模式分析的其他 Alpha 因子。因此,算法交易策略今天利用了大量的 Alpha 信号,其中许多单独可能较弱,但在与 ML 算法通过其他模型驱动或传统因子相结合时可以产生可靠的预测。

开源zipline库是由众包量化投资基金 Quantopian(www.quantopian.com/)维护和用于生产中的事件驱动回测系统,以便促进算法开发和实时交易。它自动化算法对交易事件的反应,并为其提供当前和历史的点时间数据,避免了前瞻偏差。

您可以离线使用它与数据包一起研究和评估 alpha 因子。在 Quantopian 平台上使用它时,您将获得更广泛的基本和替代数据。我们还将在本章中演示 Quantopian 研究环境,并在下一章中演示回测 IDE。本节的代码位于此章节的 GitHub 存储库文件夹的01_factor_research_evaluation子目录中。

安装完成并在执行第一个算法之前,您需要摄入一个数据包,默认情况下由 Quandl 社区维护的数据,涵盖了 3000 家美国上市公司的股价、股息和拆股信息。您需要一个 Quandl API 密钥来运行下面存储数据的代码,该代码将数据存储在您的主目录下的 ~/.zipline/data/<bundle> 文件夹中:

$ QUANDL_API_KEY=<yourkey> zipline ingest [-b <bundle>]

架构 - 事件驱动交易模拟

zipline 算法会在初始设置后的指定期间内运行,并在特定事件发生时执行其交易逻辑。这些事件由交易频率驱动,也可以由算法进行调度,并导致 zipline 调用某些方法。算法通过一个 context 字典维护状态,并通过包含 PIT(point-in-time) 当前和历史数据的 data 变量接收可执行的信息。如果有任何交易,算法将返回一个包含组合绩效指标的 DataFrame,以及可以用于记录因子值等用户定义的指标。

您可以通过命令行、Jupyter Notebook 和使用 run_algorithm() 函数来执行算法。

算法需要一个 initialize() 方法,在模拟开始时调用一次。该方法可用于向所有其他算法方法可用的 context 字典中添加属性,或注册执行更复杂数据处理的 pipelines,例如基于 alpha 因子逻辑对证券进行过滤。

算法执行通过可选方法进行,这些方法可以由 zipline 自动调度或在用户定义的间隔内调用。方法 before_trading_start() 在市场开盘前每日调用,主要用于识别算法可能在当天交易的一组证券。方法 handle_data() 每分钟调用一次。

Pipeline API 便于从历史数据中为一组证券定义和计算 alpha 因子。Pipeline 定义了在一张表中为一组证券生成 PIT 值的计算。它需要使用 initialize() 方法进行注册,然后可以按照自动或自定义的时间表执行。该库提供了许多内置的计算方法,如移动平均线或布林带,可以快速计算标准因子,同时也允许创建自定义因子,下面我们将进行说明。

最重要的是,Pipeline API 使得 alpha 因子研究模块化,因为它将 alpha 因子计算与算法的其余部分(包括交易订单的下达和执行、以及组合持仓、价值等的簿记)分开。

市场数据中的单一 alpha 因子

我们首先将在离线环境中说明 zipline alpha 因子研究工作流程。特别是,我们将开发和测试一个简单的均值回归因子,该因子衡量了最近表现与历史平均水平的偏离程度。短期反转是一种常见策略,利用了股价上涨很可能会在从少于一分钟到一个月的时间跨度内回归到均值的弱预测模式。有关详细信息,请参阅 Notebook single_factor_zipline.ipynby

为此,该因子计算了相对于过去一年的滚动月收益率的最后一个月收益率的 z 得分。此时,我们不会下达任何订单,只是为了简单说明如何实现 CustomFactor 并在模拟过程中记录结果。

在一些基本设置之后,MeanReversionCustomFactor 的子类,并定义了一个 compute() 方法。它创建了默认输入的月度收益率,其默认窗口也是一年,因此 monthly_return 变量将在给定日的 Quandl 数据集中有 252 行,每个证券一个列。

compute_factors() 方法创建了一个 MeanReversion 因子实例,并创建了 longshortranking 流水线列。前两个包含布尔值,可以用来下订单,而后者反映了整体排名,以评估整体因子表现。此外,它使用内置的 AverageDollarVolume 因子来限制计算范围,以更流动的股票:

from zipline.api import attach_pipeline, pipeline_output, record
from zipline.pipeline import Pipeline, CustomFactor
from zipline.pipeline.factors import Returns, AverageDollarVolume
from zipline import run_algorithm

MONTH, YEAR = 21, 252
N_LONGS = N_SHORTS = 25
VOL_SCREEN = 1000

class MeanReversion(CustomFactor):
    """Compute ratio of latest monthly return to 12m average,
       normalized by std dev of monthly returns"""
    inputs = [Returns(window_length=MONTH)]
    window_length = YEAR

    def compute(self, today, assets, out, monthly_returns):
        df = pd.DataFrame(monthly_returns)
        out[:] = df.iloc[-1].sub(df.mean()).div(df.std())

def compute_factors():
    """Create factor pipeline incl. mean reversion,
        filtered by 30d Dollar Volume; capture factor ranks"""
    mean_reversion = MeanReversion()
    dollar_volume = AverageDollarVolume(window_length=30)
    return Pipeline(columns={'longs'  : mean_reversion.bottom(N_LONGS),
                             'shorts' : mean_reversion.top(N_SHORTS),
                             'ranking': 
                          mean_reversion.rank(ascending=False)},
                          screen=dollar_volume.top(VOL_SCREEN))

这一结果将使我们能够下达多空订单。我们将在下一章看到如何通过选择重新平衡周期和根据新信号调整投资组合持仓来构建投资组合。

initialize() 方法注册了 compute_factors() 流水线,而 before_trading_start() 方法确保该流水线每天都会运行一次。record() 函数将流水线的 ranking 列以及当前资产价格添加到 run_algorithm() 函数返回的性能 DataFrame 中:

def initialize(context):
    """Setup: register pipeline, schedule rebalancing,
        and set trading params"""
    attach_pipeline(compute_factors(), 'factor_pipeline')

def before_trading_start(context, data):
    """Run factor pipeline"""
    context.factor_data = pipeline_output('factor_pipeline')
    record(factor_data=context.factor_data.ranking)
    assets = context.factor_data.index
    record(prices=data.current(assets, 'price'))

最后,以 UTC 时间定义开始和结束 Timestamp 对象,设置资本基础,并使用对关键执行方法的引用执行 run_algorithm()performance DataFrame 包含嵌套数据,例如,prices 列由每个单元格的 pd.Series 组成。因此,当以 pickle 格式存储时,后续数据访问更容易:

start, end = pd.Timestamp('2015-01-01', tz='UTC'), pd.Timestamp('2018-
             01-01', tz='UTC')
capital_base = 1e7

performance = run_algorithm(start=start,
                            end=end,
                            initialize=initialize,
                            before_trading_start=before_trading_start,
                            capital_base=capital_base)

performance.to_pickle('single_factor.pickle')

我们将在下一节使用存储在 performance DataFrame 中的因子和定价数据,评估不同持有期的因子表现,但首先,我们将看一下如何通过组合 Quantopian 平台上多样化的数据源中的几个 alpha 因子来创建更复杂的信号。

结合来自多样化数据源的因子

Quantopian 研究环境专门用于快速测试预测性 Alpha 因子。该过程非常相似,因为它基于 zipline,但提供了更丰富的数据源访问。下面的代码示例说明了如何计算 Alpha 因子,不仅来自以前的市场数据,还来自基本和替代数据。有关详细信息,请参阅笔记本 multiple_factors_quantopian_research.ipynb

Quantopian 免费提供了几百个 MorningStar 基本变量,还包括 stocktwits 信号作为替代数据源的示例。还有自定义的宇宙定义,例如 QTradableStocksUS,它应用了几个过滤器来限制回测宇宙,使其仅包含可能在现实市场条件下可交易的股票:

from quantopian.research import run_pipeline
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.data.morningstar import income_statement, 
     operation_ratios, balance_sheet
from quantopian.pipeline.data.psychsignal import stocktwits
from quantopian.pipeline.factors import CustomFactor, 
     SimpleMovingAverage, Returns
from quantopian.pipeline.filters import QTradableStocksUS

我们将使用自定义的 AggregateFundamentals 类来使用最后报告的基本数据点。这旨在解决基本面每季度报告一次的事实,并且 Quantopian 目前没有提供一种轻松的方法来聚合历史数据,比如按照滚动基础获取最后四个季度的总和:

class AggregateFundamentals(CustomFactor):
    def compute(self, today, assets, out, inputs):
        out[:] = inputs[0]

我们将再次使用上述代码中的自定义 MeanReversion 因子。我们还将使用 rank() 方法的 mask 参数为给定的宇宙定义计算几个其他因子:

def compute_factors():
    universe = QTradableStocksUS()

    profitability = (AggregateFundamentals(inputs=
                     [income_statement.gross_profit],
                                           window_length=YEAR) /
                     balance_sheet.total_assets.latest).rank(mask=universe)

    roic = operation_ratios.roic.latest.rank(mask=universe)
    ebitda_yield = (AggregateFundamentals(inputs=
                             [income_statement.ebitda],
                                          window_length=YEAR) /
                    USEquityPricing.close.latest).rank(mask=universe)
    mean_reversion = MeanReversion().rank(mask=universe)
    price_momentum = Returns(window_length=QTR).rank(mask=universe)
    sentiment = SimpleMovingAverage(inputs=
                            [stocktwits.bull_minus_bear],

                            window_length=5).rank(mask=universe)

    factor = profitability + roic + ebitda_yield + mean_reversion + 
             price_momentum + sentiment

    return Pipeline(
            columns={'Profitability'      : profitability,
                     'ROIC'               : roic,
                     'EBITDA Yield'       : ebitda_yield,
                     "Mean Reversion (1M)": mean_reversion,
                     'Sentiment'          : sentiment,
                     "Price Momentum (3M)": price_momentum,
                     'Alpha Factor'       : factor})

该算法使用了一种简单的方法来结合六个个体因子,只需为这些因子的每个因子的资产排名简单相加。我们不想使用等权重,而是希望考虑相对重要性和预测未来收益的增量信息。下一章的机器学习算法将允许我们完全这样做,使用相同的回测框架。

执行也依赖于 run_algorithm(),但 Quantopian 平台上的返回 DataFrame 只包含由 Pipeline 创建的因子值。这很方便,因为此数据格式可用作 alphalens 的输入,这是用于评估 Alpha 因子预测性能的库。

分离信号和噪音 - 如何使用 alphalens

Quantopian 开源了 Python 库 alphalens,用于预测股票因子的性能分析,与我们将在下一章中探讨的回测库 zipline 和投资组合绩效与风险分析库 pyfolio 集成得很好。

alphalens 便于分析 Alpha 因子的预测能力,涉及:

  • 信号与随后收益的相关性

  • 基于信号的等权或因子加权组合的盈利能力(基于信号的子集)

  • 因子周转率以指示潜在交易成本

  • 特定事件期间的因子表现

  • 通过行业细分的前述分解

分析可以使用泪水图或单独的计算和绘图进行。在线存储库中有泪水图以节省一些空间。

创建前瞻性收益和因子分位数

要使用alphalens,我们需要为资产的一组信号提供信号,例如MeanReversion因子的排名,以及投资于给定持有期的资产的前向回报。有关详细信息,请参阅笔记本03_performance_eval_alphalens.ipynb

我们将从single_factor.pickle文件中恢复prices(相应的factor_data)如下:

performance = pd.read_pickle('single_factor.pickle')

prices = pd.concat([df.to_frame(d) for d, df in performance.prices.items()],axis=1).T
prices.columns = [re.findall(r"\[(.+)\]", str(col))[0] for col in 
                  prices.columns]
prices.index = prices.index.normalize()
prices.info()

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 755 entries, 2015-01-02 to 2017-12-29
Columns: 1661 entries, A to ZTS
dtypes: float64(1661)

GitHub 存储库中的alpha factor evaluation笔记本详细介绍了如何以特定于行业的方式进行评估。

我们可以使用get_clean_factor_and_forward_returns实用程序函数创建所需格式的alphalens输入数据,该函数还返回给定持有期的信号分位数和前向回报:

HOLDING_PERIODS = (5, 10, 21, 42)
QUANTILES = 5
alphalens_data = get_clean_factor_and_forward_returns(factor=factor_data,
                                     prices=prices,
                                     periods=HOLDING_PERIODS,
                                     quantiles=QUANTILES)

Dropped 14.5% entries from factor data: 14.5% in forward returns computation and 0.0% in binning phase (set max_loss=0 to see potentially suppressed Exceptions). max_loss is 35.0%, not exceeded: OK!

alphalens_data数据框包含给定资产在给定日期的投资的回报,以及因子值,即该日期上资产的MeanReversion排名,以及相应的分位数值:

日期资产5 天10 天21 天42 天因子因子分位数
01/02/15A0.07%-5.70%-2.32%4.09%26184
AAL-3.51%-7.61%-11.89%-10.23%10882
AAP1.10%-5.40%-0.94%-3.81%7911
AAPL2.45%-3.05%8.52%15.62%29175
ABBV-0.17%-2.05%-6.43%-13.70%29525

对于评估信号的预测能力,前向回报和信号分位数是基础。通常,一个因子应该对不同分位数的回报产生明显不同的结果,例如对于因子值的底部五分位数应该产生负回报,而对于顶部分位数应该产生正回报。

因子分位数的预测性能

首先,我们想要可视化因子分位数的平均期间回报。我们可以使用performance模块中的内置函数mean_return_by_quantileplotting模块中的plot_quantile_returns_bar

from alphalens.performance import mean_return_by_quantile
from alphalens.plotting import plot_quantile_returns_bar
mean_return_by_q, std_err = mean_return_by_quantile(alphalens_data)
plot_quantile_returns_bar(mean_return_by_q);

结果是一张条形图,根据因子信号的分位数,将四种不同持有期的前向回报均值进行了分解。正如您所见,底部五分位数产生的结果明显更为负面,而顶部五分位数除了最长持有期外产生的结果更为正面:

对于第一和第四分位数,10 天的持有期提供了稍微更好的结果。我们还想看到由每个信号分位数驱动的投资随时间的表现。我们将计算每日收益率,而不是 5 天持有期的平均收益率,alphalens将调整期间收益率以解决每日信号和较长持有期之间的不匹配(有关详细信息,请参阅文档):

from alphalens.plotting import plot_cumulative_returns_by_quantile
mean_return_by_q_daily, std_err =     
     mean_return_by_quantile(alphalens_data, by_date=True)
plot_cumulative_returns_by_quantile(mean_return_by_q_daily['5D'], 
     period='5D');

结果线图显示,在这三年的大部分时间里,前两个五分位显着优于后两个五分位。然而,如前图所示,第四个五分位的信号产生的性能优于第一个五分位的性能:

对于交易策略有用的因子显示出前述模式,其中累积收益沿着明显不同的路径发展,因为这允许具有更低资本要求和相应更低整体市场暴露的多空策略。

然而,我们还需要考虑期间收益的分散而不仅仅是平均值。为此,我们可以依靠内置的plot_quantile_returns_violin

from alphalens.plotting import plot_quantile_returns_violin
plot_quantile_returns_violin(mean_return_by_q_daily);

这个分布图突出显示每日收益的范围相当广泛,并且尽管均值不同,但分布之间的分离非常有限,因此,在任何给定的日子里,不同五分位之间的性能差异可能相当有限:

虽然我们专注于单一 alpha 因子的评估,但我们在忽略与交易执行相关的实际问题时,会简化问题,我们将在下一章中详细介绍适当的回测时再放松这些问题。其中一些包括:

  • 交易成本

  • 滑点,即决策价格与交易执行价格之间的差异,例如,由于市场影响引起的

信息系数

本书大部分内容都关于使用 ML 模型设计 alpha 因子。ML 是关于优化某些预测目标的,本节中,我们将介绍用于衡量 alpha 因子性能的关键指标。我们将定义 alpha 为超过基准的平均收益。

这导致了信息比率IR),它通过将 alpha 除以跟踪风险来衡量每单位风险带来的平均超额收益。当基准是无风险利率时,IR 对应于众所周知的夏普比率,并且我们将强调在典型情况下收益不服从正态分布时出现的关键统计测量问题。我们还将解释主动管理的基本法则,将 IR 分解为预测技巧的组合和策略有效利用预测技巧的能力。

alpha 因子的目标是准确地预测未来收益的方向。因此,一个自然的性能度量是 alpha 因子预测与目标资产未来收益之间的相关性。

更好地使用非参数的 Spearman 等级相关系数来衡量两个变量之间关系的好坏,该系数衡量使用单调函数描述关系的能力,与测量线性关系强度的 Pearson 相关系数相对。

我们可以使用 alphalens 获取信息系数,alphalens 底层依赖于 scipy.stats.spearmanr(请参阅存储库以获取有关如何直接使用 scipy 获取 p 值的示例)。factor_information_coefficient 函数计算周期性的相关性,plot_ic_ts 创建一个带有一个月移动平均线的时间序列图:

from alphalens.performance import factor_information_coefficient
from alphalens.plotting import plot_ic_ts
ic = factor_information_coefficient(alphalens_data)
plot_ic_ts(ic[['5D']])

这个时间序列图显示了具有显著正移动平均 IC 的延长时期。如果有足够的机会应用这种预测技能,即使 IC 为 0.05 或甚至 0.1,也可以实现显著的超额收益,因为主动管理的基本定律将说明:

年均 IC 的图表突出显示了因子的性能历史上的不均匀性:

ic = factor_information_coefficient(alphalens_data)
ic_by_year = ic.resample('A').mean()
ic_by_year.index = ic_by_year.index.year
ic_by_year.plot.bar(figsize=(14, 6))

这会产生以下图表:

如本例中所示,信息系数低于 0.05 的情况下,虽然低但显著,可以相对于基准产生正的残差收益,我们将在下一节中看到。create_summary_tear_sheet(alphalens_data) 创建 IC 摘要统计信息,其中风险调整 IC 的结果是将平均 IC 除以 IC 的标准差得到的,这也是通过使用 scipy.stats.ttest_1sampIC = 0 的双侧 t 检验进行的:

5 天10 天21 天42 天
IC 平均值0.010.020.010.00
IC 标准差0.140.130.120.12
风险调整 IC0.100.130.100.01
2.683.532.530.14
p-value(IC)0.010.000.010.89
IC 偏度0.410.220.190.21
IC 峰度0.18-0.33-0.42-0.27

因子周转率

因子周转率衡量与给定分位数相关联的资产经常变化的频率,即调整投资组合以适应信号序列需要多少交易。更具体地说,它衡量了当前处于因子分位数中的资产份额,在上个期间不处于该分位数的资产份额。以下表格是通过此命令生成的:

create_turnover_tear_sheet(alphalens_data)

加入基于五分位数的投资组合的资产份额相当高,表明交易成本对利用预测性能构建收益的挑战:

平均周转率5 天10 天21 天42 天
分位数 159%83%83%41%
分位数 274%80%81%65%
分位数 376%80%81%68%
分位数 474%81%81%64%
分位数 557%81%81%39%

对因子周转率的另一种观点是资产排名与因子之间的相关性在各种持有期间的情况,也是撕裂单的一部分:

5 天10 天21 天42 天
平均因子排名自相关0.7110.452-0.031-0.013

一般来说,更稳定性更好,以保持交易成本可控。

Alpha 因子资源

研究过程需要设计和选择与其信号预测能力相关的阿尔法因子。算法交易策略通常会基于多个为每个资产发送信号的阿尔法因子。这些因子可以使用 ML 模型进行聚合,以优化各种信号如何转化为关于个别仓位的时间和大小决策,我们将在后续章节中看到。

替代算法交易库

其他用于算法交易和数据收集的开源 Python 库包括(在 GitHub 上查看链接):

  • QuantConnect 是 Quantopian 的竞争对手。

  • WorldQuant 提供在线竞赛,并招募社区贡献者加入众包对冲基金。

  • Alpha Trading Labs 提供以 Quantopian 类似的商业模式为基础的高频重点测试基础设施。

  • Python 算法交易库(PyAlgoTrade)专注于回测,并支持模拟交易和实时交易。它允许您使用历史数据评估交易策略的想法,并力图以最小的努力完成此任务。

  • pybacktest 是一个使用 pandas 的矢量化回测框架,旨在简洁、简单和快速(该项目目前暂停)。

  • ultrafinance 是一个较旧的项目,结合了实时金融数据收集、分析和交易策略的回测。

  • 使用 Python 进行交易提供了量化交易课程以及一系列用于量化交易的函数和类。

  • 交互经纪商提供了一个用于在其平台上进行实时交易的 Python API。

总结

在本章中,我们介绍了 zipline 库用于事件驱动的交易算法模拟,既可以离线进行,也可以在 Quantopian 在线平台上进行。我们已经说明了从市场、基本和替代数据中设计和评估单个阿尔法因子以推导出用于算法交易策略的信号,并展示了结合多个因子的一种简单方法。我们还介绍了允许对信号的预测性能和交易换手率进行全面评估的 alphalens 库。

投资组合构建过程反过来采用更广泛的视角,旨在从风险和回报的角度来看最佳仓位大小。我们现在将转向平衡投资组合过程中风险和回报的各种策略。我们还将更详细地探讨在有限历史数据集上进行回测交易策略的挑战以及如何解决这些挑战。

第五章:策略评估

Alpha 因子驱动一种算法策略,该策略转化为交易,进而产生一个投资组合。由产生的投资组合的回报和风险决定了策略的成功。测试策略需要模拟由算法生成的投资组合,以验证其在市场条件下的表现。策略评估包括针对历史数据进行回测以优化策略参数,并进行前向测试以验证样本内性能与新的、样本外数据,并避免根据特定过去情况定制策略而产生的错误发现。

在投资组合背景下,正的资产回报可以以非线性方式抵消负的价格波动,使得投资组合回报的整体变化小于投资组合头寸变化的加权平均值,除非它们的回报完全且正相关。哈里·马科维茨于 1952 年发展了现代投资组合管理理论,该理论基于多元化,从而产生了均值-方差优化:对于给定的资产组合,可以优化投资组合权重以减少风险,该风险以给定预期回报水平的回报标准差来衡量。

资本资产定价模型CAPM)将风险溢价引入为持有资产的均衡报酬,以补偿对单一风险因子——市场的暴露,该风险无法分散。随着额外的风险因子和更细粒度的暴露选择的出现,风险管理已经发展得更加复杂。凯利规则是一种流行的动态投资组合优化方法,即在一段时间内选择一系列持仓;它已经被 1968 年的爱德华·索普从其原始应用于赌博的形式成功地改编为股票市场。

因此,有几种优化投资组合的方法,包括将机器学习ML)应用于学习资产之间的层次关系,并将其持有视为相互补充或替代品,以便于投资组合风险配置。

在本章中,我们将涵盖以下主题:

  • 如何基于 Alpha 因子构建和测试投资组合使用zipline

  • 如何衡量投资组合的风险和回报

  • 如何使用pyfolio评估投资组合绩效

  • 如何使用均值-方差优化和替代方法管理投资组合权重

  • 如何使用机器学习在投资组合背景下优化资产配置

本章的代码示例位于配套 GitHub 存储库的05_strategy_evaluation_and_portfolio_management目录中。

如何使用 zipline 构建和测试投资组合

在上一章中,我们介绍了 zipline 来模拟从跨时间序列市场、基本面和替代数据中计算 alpha 因子。现在我们将利用 alpha 因子来推导并执行买入和卖出信号。我们将延迟优化投资组合权重到本章稍后,并且现在,只将相同价值的仓位分配给每个持仓。本节代码在 01_trading_zipline 子目录中。

计划交易和投资组合再平衡

我们将使用上一章开发的自定义 MeanReversion 因子,可以在 alpha_factor_zipline_with_trades.py 中查看其实现。

compute_factors() 方法创建的 Pipeline 返回一个表格,其中包含最后一个月回报与年均值的标准差归一化之间差异最大的 25 支股票的长列和短列。它还将股票范围限制在过去 30 个交易日平均交易量最高的 500 支股票上。before_trading_start() 确保每天执行管道并记录结果,包括当前价格。

新的 rebalance() 方法向 exec_trades() 方法提交了交易订单,这些订单由管道标记为长头寸和短头寸的资产,权重相等。它还清除了不再包含在因子信号中的任何当前持仓:

def exec_trades(data, assets, target_percent):
    """Place orders for assets using target portfolio percentage"""
    for asset in assets:
        if data.can_trade(asset) and not get_open_orders(asset):
            order_target_percent(asset, target_percent)

def rebalance(context, data):
    """Compute long, short and obsolete holdings; place trade orders"""
    factor_data = context.factor_data
    assets = factor_data.index

    longs = assets[factor_data.longs]
    shorts = assets[factor_data.shorts]
    divest = context.portfolio.positions.keys() - longs.union(shorts)

    exec_trades(data, assets=divest, target_percent=0)
    exec_trades(data, assets=longs, target_percent=1 / N_LONGS)
    exec_trades(data, assets=shorts, target_percent=-1 / N_SHORTS)

rebalance() 方法根据一周开始时由 schedule_function() 实用程序设置的 date_rulestime_rules 运行,在 market_open 之后立即运行,根据内置的 US_EQUITIES 日历规定(有关规则的详细信息,请参阅文档)。您还可以指定交易佣金,既以相对比例,也以最低金额。还有一个定义滑点的选项,滑点是交易决策和执行之间价格不利变化的成本:

def initialize(context):
    """Setup: register pipeline, schedule rebalancing,
        and set trading params"""
    attach_pipeline(compute_factors(), 'factor_pipeline')
    schedule_function(rebalance,
                      date_rules.week_start(),
                      time_rules.market_open(),
                      calendar=calendars.US_EQUITIES)

    set_commission(us_equities=commission.PerShare(cost=0.00075, min_trade_cost=.01))
    set_slippage(us_equities=slippage.VolumeShareSlippage(volume_limit=0.0025, price_impact=0.01))

算法在调用 run_algorithm() 函数后继续执行,并返回相同的回测性能 DataFrame。现在我们将转向常见的投资组合回报和风险度量,以及如何使用 pyfolio 库计算它们。

如何使用 pyfolio 衡量绩效

机器学习是关于优化目标函数的。在算法交易中,目标是整体投资组合的回报和风险,通常相对于一个基准(可能是现金或无风险利率)。

有几个指标可以评估这些目标。我们将简要回顾最常用的指标以及如何使用 pyfolio 库计算它们,该库也被 zipline 和 Quantopian 使用。我们还将回顾如何在 Quantopian 上应用这些指标来测试算法交易策略。

我们将使用一些简单的符号:R 表示一个周期简单组合回报的时间序列,R=(r[1], ..., r[T]),从日期 1 到 TR^f =(r^f[1], ..., r^f[T])* 为相匹配的无风险利率的时间序列,使得 R^([e])=R-R^([f]) =(r[1]-r^f[1],..., r[T]-r^f[T]) 是超额回报。

夏普比率

Ex-ante 夏普比率SR)比较了投资组合预期超额回报与该超额回报的波动性之间的关系,其波动性由其标准差来衡量。它衡量了单位风险带来的平均超额回报:

预期回报和波动性不可观测,但可以通过历史数据进行如下估计:

除非无风险利率波动(如新兴市场),否则超额回报和原始回报的标准差将相似。当夏普比率与除无风险利率以外的基准一起使用时,例如标准普尔 500 指数,称为信息比率。在这种情况下,它衡量了投资组合的超额回报,也称为阿尔法,相对于跟踪误差,即投资组合回报与基准回报的偏差。

对于独立同分布iid)的回报,夏普比率估计量的分布的推导用于统计显著性检验,是根据大样本统计理论将 μ̂ 和 σ̂² 应用于中心极限定理的结果。

然而,金融回报往往违反 iid 假设。安德鲁·罗(Andrew Lo)已推导出对于平稳但具有自相关的回报,需要调整分布和时间聚合的必要调整。这很重要,因为投资策略的时间序列特性(例如,均值回归、动量和其他形式的串行相关)对于夏普比率估计本身可能有非常重要的影响,特别是当将夏普比率从高频数据年化时(Lo 2002)。

主动管理的基本定律

信息比率IR)意味着相对于额外承担的风险而言表现优异。主动管理的基本定律将 IR 分解为信息系数IC)作为预测技能的衡量标准,以及运用这种技能进行独立投注的能力。它总结了频繁参与(高广度)和出色表现(高 IC)的重要性:

IC 衡量了 alpha 因子与其信号产生的前向回报之间的相关性,并捕捉了经理的预测技能的准确性。策略的广度通过投资者在给定时间段内进行的独立投注数量来衡量,两个值的乘积与 IR 成正比,也称为评估风险(Treynor and Black)。

此框架已扩展以包括转移系数(TC),以反映可能限制信息比率低于其他情况下可实现水平的投资组合约束(例如,对卖空的限制)。 TC 代表了管理者将见解转化为投资组合投注的效率(Clarke 等人,2002 年)。

基本定律很重要,因为它突出了超额表现的关键驱动因素:准确的预测和能够进行独立预测并据此采取行动都很重要。在实践中,具有广泛投资决策集合的管理者可以在信息系数在 0.05 到 0.15 之间时获得显著的风险调整超额回报(如果有空间,可能包括模拟图表)。

在实践中,由于预测之间的横截面和时间序列相关性,估算策略的广度是困难的。

使用 pyfolio 进行样本内外表现分析

Pyfolio 通过许多标准指标方便地进行样本内外投资组合表现和风险分析。 它生成涵盖收益、头寸和交易分析的泪水图,以及在市场压力时期使用几种内置场景的事件风险分析,并包括贝叶斯样本外表现分析。

它依赖于投资组合收益和头寸数据,并且还可以考虑交易活动的交易成本和滑点损失。这些指标是使用可以独立使用的 empyrical 库计算的。

zipline 回测引擎生成的性能 DataFrame 可以转换为所需的 pyfolio 输入。

从 alphalens 获取 pyfolio 输入数据

然而,pyfolio 也可以直接与 alphalens 集成,并允许使用 create_pyfolio_input 创建 pyfolio 输入数据:

from alphalens.performance import create_pyfolio_input

qmin, qmax = factor_data.factor_quantile.min(), 
             factor_data.factor_quantile.max()
input_data = create_pyfolio_input(alphalens_data,   
                                  period='1D',
                                  capital=100000,
                                  long_short=False,
                                  equal_weight=False,
                                  quantiles=[1, 5],
                                  benchmark_period='1D')
returns, positions, benchmark = input_data

有两种选项可以指定如何生成投资组合权重:

  • long_short: 如果为 False,则权重将对应于因子值除以其绝对值,以便负因子值生成空头。 如果为 True,则首先对因子值进行均值化,以使多头和空头相互抵消,投资组合是市场中性的。

  • equal_weight: 如果为 True,且 long_shortTrue,则资产将被分成两个相等大小的组,前半部分组成多头头寸,后半部分组成空头头寸。

如果 factor_data 包含每个资产的行业信息,则还可以为组创建多头-空头投资组合。

从 zipline 回测获取 pyfolio 输入数据

通过 extract_rets_pos_txn_from_zipline 可将 zipline 回测结果转换为所需的 pyfolio 输入:

returns, positions, transactions = 
         extract_rets_pos_txn_from_zipline(backtest)

步进式测试样本外收

测试交易策略涉及对历史数据进行回测以微调 alpha 因子参数,以及对新市场数据进行前向测试以验证策略在样本外的表现良好或参数是否过于针对特定历史情况。

Pyfolio 允许指定一个样本外期间来模拟前向测试。在测试策略以获得统计上可靠的结果时,有许多方面需要考虑,我们将在这里讨论。

plot_rolling_returns 函数显示累计样本内和样本外回报与用户定义的基准(我们使用标准普尔 500 指数)的对比:

from pyfolio.plotting import plot_rolling_returns
plot_rolling_returns(returns=returns,
                     factor_returns=benchmark_rets,
                     live_start_date='2017-01-01',
                     cone_std=(1.0, 1.5, 2.0))

图中包含一个锥形,显示扩展的置信区间,以指示基于随机游走假设的情况下不太可能出现的样本外收益。在模拟的 2017 年样本外期间,我们的策略表现不佳:

总体表现统计

pyfolio 提供了几个分析函数和图表。perf_stats 摘要显示了年度和累计回报、波动率、偏度和回报的峰度以及 SR。以下附加指标(也可以单独计算)最重要:

  • 最大回撤: 从先前峰值的最高百分比损失

  • Calmar 比率: 年度组合回报相对于最大回撤

  • Omega 比率: 针对回报目标的概率加权收益与损失的比率,默认为零

  • Sortino 比率: 相对于下行标准差的超额回报

  • 尾部比率: 右尾(收益,第 95 百分位数的绝对值)与左尾(损失,第 5 百分位数的绝对值)的比率

  • 每日风险价值 (VaR): 对应于每日平均值以下两个标准偏差的损失

  • Alpha: 由基准回报未解释的投资组合回报

  • 贝塔: 对基准的暴露

from pyfolio.timeseries import perf_stats
perf_stats(returns=returns, 
           factor_returns=benchmark_rets, 
           positions=positions, 
           transactions=transactions)

对于从 MeanReversion 因子派生的模拟的多空投资组合,我们获得以下性能统计数据:

指标全部样本内样本外指标全部样本内样本外
年度回报率1.80%0.60%4.20%偏度0.340.400.09
累计回报5.40%1.10%4.20%峰度3.703.372.59
年度波动率5.80%6.30%4.60%尾部比率0.910.881.03
夏普比率0.330.120.92每日风险价值-0.7%-0.8%-0.6%
Calmar 比率0.170.061.28总杠杆0.380.370.42
稳定性0.490.040.75每日换手率4.70%4.40%5.10%
最大回撤-10.10%-10.10%-3.30%Alpha0.010.000.04
Omega 比率1.061.021.18贝塔0.150.160.03
Sortino 比率0.480.181.37

有关计算和解释投资组合风险和回报指标的详细信息,请参见附录。

回撤期和因子暴露

plot_drawdown_periods(returns)函数绘制了投资组合的主要回撤期,而其他几个绘图函数显示了滚动夏普比率和滚动因子暴露度,包括对市场贝塔或法玛-法国大小、增长和动量因子的暴露度:

fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(16, 10))
axes = ax.flatten()

plot_drawdown_periods(returns=returns, ax=axes[0])
plot_rolling_beta(returns=returns, factor_returns=benchmark_rets, 
                  ax=axes[1])
plot_drawdown_underwater(returns=returns, ax=axes[2])
plot_rolling_sharpe(returns=returns)

此图突出显示了各种撕裂表中包含的可视化子集,说明了 pyfolio 如何让我们深入了解绩效特征和对风险和收益基本驱动因素的暴露:

建模事件风险

Pyfolio 还包括各种事件的时间线,您可以使用它来比较投资组合在此期间与基准的表现,例如,在 2015 年秋天的英国脱欧公投后的抛售期间:

interesting_times = extract_interesting_date_ranges(returns=returns)
interesting_times['Fall2015'].to_frame('pf') \
 .join(benchmark_rets) \
 .add(1).cumprod().sub(1) \
 .plot(lw=2, figsize=(14, 6), title='Post-Brexit Turmoil')

结果绘图如下所示:

如何避免回测的陷阱

回测是使用历史数据模拟算法策略,目的是识别适用于新市场条件的模式。除了在不断变化的市场中预测不确定未来的一般挑战外,许多因素使得将样本内表现误认为发现真实模式变得非常可能。这些因素包括数据的方面、策略模拟的实施以及与统计测试及其解释相关的缺陷。使用更多计算资源、更大数据集和更复杂算法的风险使在噪声中发现明显模式的可能性增加。

我们将列出最严重和最常见的方法论错误,并参考多重测试的文献以获取更多详细信息。我们还将介绍通货紧缩夏普比率,以说明在分析中使用相同一组财务数据时如何调整重复试验导致的指标。

数据挑战

由于数据问题对回测有效性的挑战包括前瞻性偏差、生存偏差和异常值控制。

前瞻性偏差

从过去数据导出的交易规则的测试将在用于制定规则的样本数据中包含了实际上在数据所指代的时间点上并不可用或已知的信息时产生偏见结果。

此偏差的典型来源是未考虑报告的财务数据的常见事后校正。股票拆分或反向拆分也可能产生前瞻性偏差。当计算收益率时,每股收益数据来自公司财务报表,其频率较低,而市场价格至少每天都可获得。因此,EPS 和价格数据都需要同时调整拆分。

解决方法在于对进入回测的所有数据关联的时间戳进行仔细分析,以确保仅使用点时间数据。高质量的数据提供商,如 Compustat,确保满足这些标准。当点时间数据不可用时,需要假设关于报告滞后的情况。

生存者偏差

生存者偏差是在仅包含当前活跃证券数据并省略随着时间消失的资产(例如破产、退市或收购)时出现的。通常情况下,不再是投资范围一部分的证券表现不佳,包含这些情况可能会正向偏移回测结果。

自然的解决方法是验证数据集是否包括随时间可用的所有证券,而不仅仅是在运行测试时仍然可用的证券。

离群值控制

在分析之前的数据准备通常包括处理极端值,例如通过 Winsorize 或修剪。挑战在于确定真正不代表分析期的离群值,而不是当时市场环境的组成部分的极端值。许多市场模型假设在观察到极端值更频繁时的正常分布数据,如脂尾分布所暗示的那样。

解决方法涉及对极端值的概率进行仔细分析,并根据这一现实调整策略参数。

代表性时期

如果使用的时间段不足以充分反映当前环境,缺乏相关市场体制方面,并且不包含足够的数据点或捕获到不太可能重复出现的极端历史事件,则回测将不会产生代表性结果,无法推广到未来时期。

解决方法包括使用包括重要市场现象的样本期间,或生成反映相关市场特征的合成数据(有关实施指南,请参见资源部分)。

实施问题

与历史模拟实施相关的实际问题包括未能标记至市场,即准确反映基础市场价格并考虑到回撤,对交易的可用性、成本或市场影响,或者信号和交易执行的时间做出不切实际的假设。

标记至市场表现

尽管这种策略在回测过程中表现良好,但随着时间的推移可能导致无法接受的损失或波动。

解决方法包括随时间绘制性能或计算(滚动)风险度量,如风险价值VaR)或 Sortino 比率(有关详细信息,请参见附录)。

交易成本

该策略可能假设需要对手方的空头交易,持有市场流动性较低的资产可能会在交易时引起市场波动,或者低估由于经纪费或滑点(即决定交易的市场价格与随后执行价格之间的差异)而产生的成本。

解决方案包括将宇宙限制在高度流动性并且对交易和滑点成本进行现实参数假设(如前述zipline示例所示)。 这还可以防止包括高衰减和因此周转率高的不稳定因子信号。

交易时机

模拟可能对α因子信号评估和结果交易的时间进行不切实际的假设。 例如,当下一笔交易仅在通常相差很大的开盘价时,可能会在收盘价时评估信号。 结果,如果使用收盘价来评估交易绩效,则回测将会有相当大的偏差。

解决方案涉及对信号到达顺序,交易执行和绩效评估的精心编排。

数据窥视和回测过拟合

对于回测有效性,包括已发表结果的最突出挑战与在策略选择过程中进行多次测试而发现虚假模式相关。 在同一数据上测试不同候选者之后选择策略可能会导致选择偏差,因为正面结果更可能是由于性能度量本身的随机性质。 换句话说,策略过于定制,或者过度拟合,以使得数据产生欺骗性的积极结果。

因此,回测表现并不具有信息性,除非报告试验次数以便评估选择偏差的风险。 在实际或学术研究中很少会出现这种情况,这引发了对许多已发表声明有效性的怀疑。

将回测过度拟合到特定数据集的风险不仅来自直接运行大量测试,还包括基于已知什么有效什么无效的先验知识设计的策略,也就是,他人在相同数据上运行的不同回测的知识。 结果,在实践中很难避免回测过拟合。

解决方案包括基于投资或经济理论而不是广泛的数据挖掘工作来选择进行测试的测试。 这还意味着在各种情境和场景中进行测试,包括可能在合成数据上进行测试。

最小回测长度和通胀后的夏普比率

Marcos Lopez de Prado (www.quantresearch.info/) 广泛发表了关于回测风险以及如何检测或避免其的文章。 这包括回测过拟合的在线模拟器 (datagrid.lbl.gov/backtest/)。

另一个结果包括估算投资者应该要求的回测最小长度,考虑到尝试的试验次数,以避免选择在给定试验次数期间具有预期外样本 SR 为零的给定策略的情况。这意味着,例如,如果只有两年的每日回测数据可用,则不应尝试超过七种策略变化,如果只有五年的每日回测数据可用,则不应尝试超过 45 种策略变化。有关实现细节,请参阅参考文献。

De Lopez Prado 和 Bailey (2014) 还推导出一个通货膨胀的 SR,用于计算 SR 在控制多重测试、非正常收益和较短样本长度的通货膨胀效应的情况下具有统计显著性的概率(有关03_multiple_testing子目录的deflated_sharpe_ratio.py的 Python 实现和相关公式的推导,请参阅参考文献)。

回测的最优停止

除了将回测限制在可以从理论上证明的策略上,而不仅仅是数据挖掘练习上,一个重要的问题是何时停止进行额外的测试。

根据最优停止理论中秘书问题的解决方案,建议根据以下经验法则进行决策:测试一组合理策略的随机样本的 1/e(大约 37%) 的性能。然后,继续测试,直到找到一种优于之前测试的策略。

此规则适用于测试几种替代方案的情况,目标是尽快选择接近最佳方案,同时最小化假阳性风险。

如何管理投资组合风险和收益

投资组合管理旨在在金融工具中建立仓位,以实现与基准相关的所需风险回报平衡。在每个周期中,经理选择优化多样化以降低风险并实现目标收益的仓位。在不同周期内,将重新平衡仓位,以考虑由价格变动导致的权重变化,以实现或维持目标风险配置。

多样化使我们能够通过利用价格变动之间的相互作用来降低给定预期收益的风险,因为一种资产的收益可以弥补另一种资产的损失。哈里·马科维茨于 1952 年发明了现代投资组合理论MPT),并提供了通过选择适当的投资组合权重来优化多样化的数学工具。马科维茨展示了投资组合风险,以投资组合收益的标准偏差来衡量,如何取决于所有资产的收益之间的协方差和它们的相对权重。这种关系意味着存在一种有效的投资组合边界,该边界最大化了投资组合收益,同时给定最大水平的投资组合风险。

然而,均值-方差前沿对于计算所需的输入估计非常敏感,例如预期回报、波动率和相关性。在实践中,将这些输入约束以减少抽样误差的均值-方差组合表现得更好。这些受限制的特殊情况包括等权、最小方差和风险均摊组合。

资本资产定价模型(CAPM)是建立在 MPT 风险-回报关系基础上的资产估值模型。它引入了一个概念,即投资者可以在市场均衡状态下期望持有风险资产的风险溢价;该溢价补偿了货币时间价值和无法通过分散化消除的整体市场风险(与特定资产的特异风险相对)。不可分散风险的经济基础包括宏观驱动因素对业务风险的影响,从而影响股票回报或债券违约。因此,资产的预期回报 E[r[i]]是无风险利率 r[f]和与资产暴露于市场组合预期超额回报 r[m]的风险溢价的总和,超额回报以无风险利率为基础:

理论上,市场组合包含所有可投资资产,并且在均衡状态下将由所有理性投资者持有。在实践中,广义价值加权指数近似于市场,例如,用于美国股票投资的标普 500 指数。β[i]测量对市场组合超额回报的暴露。如果 CAPM 有效,则截距分量α[i]应为零。实际上,CAPM 的假设通常不成立,α值捕捉了未通过广义市场暴露解释的回报。

随着时间的推移,研究发现了非传统的风险溢价来源,例如动量或股票价值效应,解释了一些原始α值。经济上的合理性,例如投资者对新信息的过度反应或反应不足的行为偏见,为暴露于这些替代风险因素的风险溢价提供了理论支持。它们演变成了旨在捕捉这些替代贝塔的投资风格,这些投资风格也以专门的指数基金的形式可交易。在分离了这些替代风险溢价的贡献之后,真正的α值仅限于特异资产回报和管理者调整风险敞口的能力。

过去几十年来,有效市场假说(EMH)已经得到完善,以纠正 CAPM 的许多原始缺陷,包括信息不完全以及与交易、融资和代理相关的成本。许多行为偏见具有相同的效果,而一些摩擦被建模为行为偏见。

机器学习在使用市场、基本面和先前章节讨论的替代数据源的监督和无监督学习技术推导新的阿尔法因子方面发挥着重要作用。机器学习模型的输入包括原始数据和经过工程化处理以捕获信息信号的特征。机器学习模型还用于组合单个预测信号并提供更高聚合预测能力。

近几十年来,现代投资组合理论和实践已经发生了重大变化。我们将介绍:

  • 均值-方差优化及其缺陷

  • 诸如最小风险和 1/n 配置之类的替代方案

  • 风险平价方法

  • 风险因子方法

均值-方差优化

MPT 解决了最小化波动性以实现给定预期回报,或者在给定波动水平下最大化回报的最优投资组合权重。关键的前提输入是预期资产回报、标准差和协方差矩阵。

工作原理

多样化的原因在于投资组合回报的方差取决于资产的协方差,并且可以通过包括具有不完全相关性的资产将其降低到低于资产方差的加权平均值。特别是,给定投资组合权重向量 ω 和协方差矩阵 Σ,投资组合方差 σ[PF] 定义为:

马科维茨表明,最大化期望投资组合回报以满足目标风险的问题具有等价的对偶表示,即最小化投资组合风险以满足目标期望回报水平。因此,优化问题变为:

Python 中的有效前沿

我们可以使用 scipy.optimize.minimize 和资产回报、标准差和协方差矩阵的历史估计来计算有效前沿。 代码可以在本章的 repo 的 efficient_frontier 子文件夹中找到,并实现以下步骤序列:

  1. 模拟使用狄利克雷分布生成随机权重,并使用历史回报数据计算每个样本投资组合的均值、标准差和夏普比:
def simulate_portfolios(mean_ret, cov, rf_rate=rf_rate, short=True):
    alpha = np.full(shape=n_assets, fill_value=.01)
    weights = dirichlet(alpha=alpha, size=NUM_PF)
    weights *= choice([-1, 1], size=weights.shape)

    returns = weights @ mean_ret.values + 1
    returns = returns ** periods_per_year - 1
    std = (weights @ monthly_returns.T).std(1)
    std *= np.sqrt(periods_per_year)
    sharpe = (returns - rf_rate) / std

    return pd.DataFrame({'Annualized Standard Deviation': std,
                         'Annualized Returns': returns,
                         'Sharpe Ratio': sharpe}), weights
  1. 设置二次优化问题,以求解给定回报的最小标准差或最大夏普比。为此,定义衡量关键指标的函数:
def portfolio_std(wt, rt=None, cov=None):
    """Annualized PF standard deviation"""
    return np.sqrt(wt @ cov @ wt * periods_per_year)

def portfolio_returns(wt, rt=None, cov=None):
    """Annualized PF returns"""
    return (wt @ rt + 1) ** periods_per_year - 1

def portfolio_performance(wt, rt, cov):
    """Annualized PF returns & standard deviation"""
    r = portfolio_returns(wt, rt=rt)
    sd = portfolio_std(wt, cov=cov)
    return r, sd 
  1. 定义一个目标函数,表示 scipyminimize 函数要优化的负夏普比,考虑到权重被限制在 [-1, 1] 范围内,并且绝对值之和为一:
def neg_sharpe_ratio(weights, mean_ret, cov):
    r, sd = portfolio_performance(weights, mean_ret, cov)
    return -(r - rf_rate) / sd

weight_constraint = {'type': 'eq',
                     'fun': lambda x: np.sum(np.abs(x)) - 1}

def max_sharpe_ratio(mean_ret, cov, short=True):
    return minimize(fun=neg_sharpe_ratio,
                    x0=x0,
                    args=(mean_ret, cov),
                    method='SLSQP',
                    bounds=((-1 if short else 0, 1),) * n_assets,
                    constraints=weight_constraint,
                    options={'tol':1e-10, 'maxiter':1e4})
  1. 通过迭代一系列目标回报范围并解决相应的最小方差投资组合来计算有效前沿。投资组合风险和回报的优化问题以及权重作为函数的约束可以如下制定:
def neg_sharpe_ratio(weights, mean_ret, cov):
    r, sd = pf_performance(weights, mean_ret, cov)
    return -(r - RF_RATE) / sd

def pf_volatility(w, r, c):
    return pf_performance(w, r, c)[1]

def efficient_return(mean_ret, cov, target):
    args = (mean_ret, cov)
    def ret_(weights):
        return pf_ret(weights, mean_ret)

    constraints = [{'type': 'eq', 'fun': lambda x: ret_(x) - 
                     target},
                   {'type': 'eq', 'fun': lambda x: np.sum(x) - 1}]
    bounds = ((0.0, 1.0),) * n_assets
    return minimize(pf_volatility,
                    x0=x0,
                    args=args, method='SLSQP',
                    bounds=bounds,
                    constraints=constraints)
  1. 解决方案需要在可接受值范围内进行迭代,以识别最优的风险收益组合:
def min_vol_target(mean_ret, cov, target, short=True):

    def ret_(wt):
        return portfolio_returns(wt, mean_ret)

    constraints = [{'type': 'eq', 'fun': lambda x: ret_(x) - target},
                     weight_constraint]

    bounds = ((-1 if short else 0, 1),) * n_assets
    return minimize(portfolio_std, x0=x0, args=(mean_ret, cov),
                    method='SLSQP', bounds=bounds,
                    constraints=constraints,
                    options={'tol': 1e-10, 'maxiter': 1e4})

def efficient_frontier(mean_ret, cov, ret_range):
    return [min_vol_target(mean_ret, cov, ret) for ret in ret_range]

模拟产生了可行投资组合的子集,有效前沿确定了在样本内数据可行的最优收益风险组合。下图显示了结果,包括最小方差投资组合和最大化夏普比率的投资组合,以及我们在以下章节讨论的几种替代优化策略产生的若干投资组合。

投资组合优化可以在交易策略的每一次评估步骤中运行,以优化仓位。

挑战与缺陷

前述的均值方差前沿示例说明了样本内、向后看的优化。实际上,投资组合优化需要前瞻性输入。预期收益难以准确估计。

协方差矩阵的估计可能更加可靠,这导致了几种替代方法的出现。然而,具有相关资产的协方差矩阵会引发计算挑战,因为优化问题需要求矩阵的逆。高条件数导致数值不稳定性,从而产生马尔科维茨诅咒:需要更多分散化(通过相关投资机会),算法产生的权重越不可靠。

许多投资者更喜欢使用具有较少繁琐输入要求的投资组合优化技术。我们现在介绍几种旨在解决这些缺点的替代方法,包括基于机器学习的较新方法。

均值方差优化的替代方案

对于均值方差优化问题的准确输入存在挑战,这导致采用了几种实用的替代方法,限制均值、方差或两者,或者省略更具挑战性的回报估计,如风险平价方法。

均等权重投资组合

简单投资组合提供了有用的基准,以衡量生成过度拟合风险的复杂模型的增值。最简单的策略——等权重投资组合——已被证明是表现最佳的策略之一。

2009 年,德米格尔、加拉皮和乌帕尔(de Miguel, Garlappi, and Uppal)比较了各种均值方差优化器产生的投资组合的样本外表现,包括鲁棒贝叶斯估计器、投资组合约束和投资组合的最优组合,与简单的 1/N 规则相比。他们发现,1/N 投资组合的夏普比率高于每个资产类别的头寸,这是由于样本外复杂优化的估计误差成本通常超过了其收益的好处所致。

均等权重投资组合也包括在上述有效前沿图中。

最小方差投资组合

另一个选择是全球最小方差GMV)组合,它优先考虑风险最小化。它显示在有效边界图中,并可以通过使用均值-方差框架最小化投资组合标准差来计算如下:

def min_vol(mean_ret, cov, short=True):
    return minimize(fun=portfolio_std,
                    x0=x0,
                    args=(mean_ret, cov),
                    method='SLSQP',
                    bounds=bounds = ((-1 if short else 0, 1),) * 
                          n_assets,
                          constraints=weight_constraint,
                          options={'tol': 1e-10, 'maxiter': 1e4})

相应的min.波动性组合位于上述有效边界上。

全球投资组合优化 - 黑-利特曼方法

Black 和 Litterman(1992)的全球投资组合优化方法将经济模型与统计学习相结合,因此在许多情况下生成的预期收益估计是合理的,因此很受欢迎。

这种技术背离了市场是由 CAPM 均衡模型所隐含的均值-方差投资组合的假设,并且建立在这样一个事实之上,即观察到的市场资本化可以被视为市场分配的最优权重。市场权重反映了市场价格,而市场价格又体现了市场对未来收益的预期。

因此,这种方法可以从市场足够接近均衡的假设开始逆向工程出未来预期收益,并且允许投资者使用缩小估计器将这些估计值调整到他们自己的信念中。该模型可以解释为是投资组合优化的贝叶斯方法。我们将在第九章中介绍贝叶斯方法,贝叶斯机器学习

如何确定下注的大小 - 凯利法则

凯利法则在赌博中有着悠久的历史,因为它提供了在一系列变化的(但是有利的)赔率下,为了最大化期末财富而在每一次下注上押注多少的指导。这个法则于 1956 年由约翰·凯利发布,他是贝尔实验室的克劳德·香农的同事,作品名为《信息率的新解释》。他被新晋问答节目《$64,000 问题》上观众对候选人下注的情况所吸引,这些观众利用西海岸的三小时延迟获取关于获胜者的内部消息。

凯利将其法则与香农的信息理论联系起来,以求解在赔率有利但存在不确定性时最优的长期资本增长下注。他的法则将每场比赛成功的几率的对数财富最大化,并且包含了隐含的破产保护,因为 log(0)是负无穷,所以凯利赌徒自然会避免失去一切。

下注的最佳大小

凯利开始分析具有二元胜负结果的游戏。关键变量有:

  • b:赔率定义了每 1 美元下注的赢得金额。赔率=5/1 意味着如果下注赢了,将获得 5 美元的收益,加上 1 美元的本金。

  • p:概率定义了有利结果的可能性。

  • f:要下注的当前资本份额。

  • V:由于下注而产生的资本价值。

凯利法则的目标是最大化无限次重复下注的价值增长率,G

WL分别代表赢和输的次数时,那么:

我们可以通过对f最大化来最大化增长率G,如下所示,使用sympy进行说明:

from sympy import symbols, solve, log, diff

share, odds, probability = symbols('share odds probability')
Value = probability * log(1 + odds * share) + (1 - probability) * log(1 
        - share)
solve(diff(Value, share), share)

[(odds*probability + probability - 1)/odds]

我们得到了要投注的资本的最佳份额:

最优投资 - 单一资产

在金融市场的背景下,结果和替代方案都更加复杂,但凯利规则的逻辑仍然适用。这一规则由首次将其成功应用于赌博(在《击败庄家》中描述)的埃德·索普推广,后来他开始了成功的对冲基金普林斯顿/纽波特合伙企业。

对于连续结果,资本的增长率由不同回报的概率分布的积分定义,可以进行数值优化:

我们可以使用scipy.optimize模块来解决这个表达式以得到最优f^* 的值:

def norm_integral(f, m, st):
    val, er = quad(lambda s: np.log(1+f*s)*norm.pdf(s, m, st), m-3*st, 
                   m+3*st)
    return -val

def norm_dev_integral(f, m, st):
    val, er = quad(lambda s: (s/(1+f*s))*norm.pdf(s, m, st), m-3*st, 
                   m+3*st)
    return val

m = .058
s = .216
# Option 1: minimize the expectation integral
sol = minimize_scalar(norm_integral, args=(
                m, s), bounds=[0., 2.], method='bounded')
print('Optimal Kelly fraction: {:.4f}'.format(sol.x))

最优投资 - 多资产

我们将使用一个涉及各种股票的例子。E. Chan(2008)阐述了如何到达凯利规则的多资产应用,并且结果等同于(可能是杠杆的)从均值方差优化中获得的最大夏普比率组合。

计算涉及精度矩阵(协方差矩阵的逆矩阵)和回报矩阵的点积:

mean_returns = monthly_returns.mean()
cov_matrix = monthly_returns.cov()
precision_matrix = pd.DataFrame(inv(cov_matrix), index=stocks, columns=stocks)
kelly_wt = precision_matrix.dot(mean_returns).values

凯利组合也显示在有效前沿图表中(通过标准化使绝对权重总和为一)。许多投资者倾向于减少凯利权重以减少该策略的波动性,半凯利已经变得特别流行。

风险平价

过去 15 年的两次全球股票市场危机,持续上升的收益曲线以及利率的普遍下降使风险平价看起来像是一个特别引人注目的选择。许多机构为风险平价分配了战略性的资产配置,以进一步实现投资组合的多样化。

一个简单的风险平价分配方案根据它们方差的倒数来分配资产,忽略相关性,特别是回报预测:

var = monthly_returns.var()
risk_parity_weights = var / var.sum()

风险平价投资组合也显示在本节开头的有效前沿图表中。

风险因素投资

估计输入的另一种框架是逐步解析推动资产风险和回报的基础决定因素或因子。如果我们了解因子如何影响回报,并且我们了解这些因子,我们将能够构建更稳健的投资组合。

因子投资的概念超越了资产类别标签,以最大化分散化的好处来看待潜在的因子风险。与区分投资工具的标签(例如对冲基金或私募股权)不同,因子投资旨在根据基本风险因素的暴露差异来识别不同的风险-回报配置文件。均值-方差投资的天真方法将(人为的)分组视为不同的资产类别,并将其插入均值-方差优化器。因子投资认识到这些分组与传统资产类别共享许多相同的因子风险。分散化的好处可能被夸大,正如投资者在上一次危机中发现的那样,当有风险资产类别之间的相关性增加时,由于暴露于相同的潜在因子风险。

分层风险平价

均值-方差优化对预期收益和这些收益的协方差的估计非常敏感。当收益高度相关时,协方差矩阵的求逆也变得更加具有挑战性和不准确,这在实践中经常发生。其结果被称为马科维茨诅咒:当分散投资更重要时,因为投资相关,传统的投资组合优化器可能会产生不稳定的解决方案。分散化的好处可能会被错误的估计所抵消。正如讨论的那样,即使是天真的等权重投资组合也可以在样本外击败均值-方差和基于风险的优化。

更健壮的方法已经包括了额外的约束(Clarke et al., 2002),贝叶斯先验(Black and Litterman, 1992),或者使用缩小估计器使精度矩阵更加稳定(Ledoit 和 Wolf [2003],在 scikit-learn 中可用(scikit-learn.org/stable/modules/generated/sklearn.covariance.LedoitWolf.html)。与此相反,分层风险平价(HRP)利用无监督机器学习实现了更优异的样本外投资组合配置。

最近的投资组合优化创新利用图论和分层聚类构建投资组合的三个步骤(Lopez de Prado, 2015):

  1. 定义一个距离度量,使相关资产彼此接近,并应用单链接聚类来识别分层关系。

  2. 利用分层相关结构来准对角化协方差矩阵。

  3. 使用自上而下的反方差加权,利用递归二分搜索将聚类资产视为投资组合建设中的互补而不是替代品,并减少自由度的数量。

Raffinot(2016)提出了构建分层聚类投资组合HCP)的相关方法。从概念上讲,金融市场等复杂系统往往具有结构,并且通常以层次方式组织,而层次结构中元素之间的相互作用塑造了系统的动态。相关矩阵也缺乏层次结构的概念,这使得权重可以自由变化,并可能以意想不到的方式变化。

JPM 已经在各种股票市场上测试了 HRP 和 HCP。特别是,与天真的多样化、最大多样化投资组合或 GMV 投资组合相比,HRP 产生了相等或更高的风险调整回报和夏普比率。

我们将在第十二章中介绍 Python 实现,无监督学习

摘要

在本章中,我们涵盖了投资组合管理的重要主题,这涉及将投资头寸组合起来以管理风险与回报的权衡目标。我们介绍了pyfolio来计算和可视化关键的风险和回报指标,并比较了各种算法的表现。

我们看到准确预测对于优化投资组合权重和最大化分散效益是多么重要。我们还探讨了 ML 如何通过从资产收益协方差矩阵中学习层次关系来促进更有效的投资组合构建。

现在我们将继续本书的第二部分,重点介绍 ML 模型的使用。这些模型将通过更有效地利用更多样化的信息来捕捉比目前更突出的简单 Alpha 因子更复杂的模式,从而产生更准确的预测。

我们将开始通过使用交叉验证来训练、测试和调整线性回归和分类模型,以实现稳健的样本外表现。我们还将把这些模型嵌入到定义和回测算法交易策略的框架中,这是我们在过去两章中介绍的。

第六章:机器学习过程

在本章中,我们将开始说明您可以如何使用广泛的监督和非监督机器学习ML)模型进行算法交易。我们将在演示与各种 Python 库相关的相关应用之前,解释每个模型的假设和用例。模型的分类将包括:

  • 用于截面、时间序列和面板数据的回归和分类的线性模型

  • 广义加法模型,包括非线性基于树的模型,如决策树

  • 集成模型,包括随机森林和梯度提升机

  • 用于降维和聚类的非监督线性和非线性方法

  • 神经网络模型,包括循环和卷积架构

  • 强化学习模型

我们将这些模型应用到本书第一部分介绍的市场、基本和替代数据来源中。我们将进一步建立在迄今为止涵盖的内容之上,向您展示如何将这些模型嵌入到算法交易策略中,以生成或结合阿尔法因子,或优化投资组合管理流程,并评估其性能。

许多这些模型及其用途具有共同的几个方面。本章涵盖了这些共同的方面,以便我们可以在后续章节中专注于模型特定的用法。它们包括通过优化目标或损失函数从数据中学习功能关系的总体目标。它们还包括密切相关的测量模型性能的方法。

我们区分了无监督和监督学习,以及监督回归和分类问题,并概述了算法交易的用例。我们将使用监督学习进行统计推断输入和输出数据之间关系与使用未来输入进行未来输出预测之间的对比。我们还说明了预测错误是由于模型的偏差或方差,或者数据中的噪声信号比高而引起的。最重要的是,我们提供了诊断错误来源并改善模型性能的方法。

在本章中,我们将涵盖以下主题:

  • 使用数据进行监督和非监督学习的原理

  • 如何应用机器学习工作流程

  • 如何为回归和分类制定损失函数

  • 如何训练和评估监督学习模型

  • 偏差-方差权衡如何影响预测错误

  • 如何诊断和解决预测错误

  • 如何使用交叉验证训练模型来管理偏差-方差权衡

  • 如何使用 scikit-learn 实现交叉验证

  • 为什么金融数据的性质需要不同的样本外测试方法

如果您对机器学习已经非常熟悉,请随意跳过,直接开始学习如何使用线性模型为算法交易策略生成和组合阿尔法因子。

从数据中学习

ML 有许多定义,它们都围绕数据中有意义的模式的自动检测。两个著名的例子包括:

  • AI 先驱亚瑟·塞缪尔森在 1959 年将 ML 定义为计算机科学的一个子领域,赋予计算机在没有明确编程的情况下学习的能力。

  • 托尼·米切尔,该领域的一位当前领军人物,1998 年更加具体地确定了一个明确定义的学习问题:一个计算机程序根据任务和绩效指标从经验中学习,看任务的绩效是否随经验提高而提高。

经验以训练数据的形式呈现给算法。与以往试图构建解决问题的机器的尝试的主要区别在于,算法用于做出决策的规则是从数据中学习的,而不是被编程或硬编码——这是上世纪 80 年代突出的专家系统的情况。

自动学习的关键挑战在于识别训练数据中的模式,在将模型的学习泛化到新数据时这些模式是有意义的。模型可能识别的潜在模式数量庞大,而训练数据仅构成了算法未来执行任务所需的更大现象集合的一部分样本。可能从给定输入生成给定输出的函数数量无限,这使得搜索过程在没有对可接受函数集合施加限制的情况下无法解决。

算法能够学习的模式类型受到其假设空间的大小以及样本数据中包含的信息量的限制。假设空间的大小在算法之间有很大的变化。一方面,这种限制使得成功的搜索成为可能,另一方面,它暗示了归纳偏见,因为算法从训练样本推广到新数据。

因此,关键挑战变成了如何选择具有足够大的假设空间以包含学习问题解决方案的模型,同时又足够小以确保在给定训练数据大小的情况下可靠的泛化。随着越来越多的信息化数据,具有更大假设空间的模型将会成功。

无免费午餐定理指出没有通用的学习算法。相反,学习者的假设空间必须根据关于任务领域的先验知识来定制,以便搜索有意义的模式成功。在本章中,我们将密切关注模型对特定任务的数据关系所做的假设,并强调通过数据探索获得的经验证据与这些假设匹配的重要性。掌握任务所需的过程可以区分为监督学习、无监督学习和强化学习。

监督学习

监督学习是最常用的机器学习类型。本书的大部分章节将致力于学习该类别中模型的各种应用。术语监督意味着有一个结果变量指导学习过程,即,它教会算法任务的正确解决方案正在学习。监督学习旨在将从个别样本学到的输入和输出数据之间的功能关系泛化,并将其应用于新数据。

输出变量也可根据领域不同交替称为标签、目标、结果、内生或左手变量。我们将对观测值i = 1, ..., N使用y[i],或者在向量表示中使用y。某些任务由多个结果表示,也称为多标签问题。监督学习问题的输入数据也称为特征、外生和右手变量,用x[i ]表示i = 1, ..., N的一系列特征,或者在矩阵表示中使用X

解决监督学习问题的解决方案是一个函数(),它表示模型从样本中学到的输入-输出关系,并逼近真实关系,用表示。这个函数可以用来推断变量之间的统计关系,甚至可能是因果关系,超出样本范围,或者用来预测新输入数据的输出。

两个目标面临着一个重要的权衡:更复杂的模型有更多移动部件,能够表示更微妙的关系,但也可能更难以检查。它们也可能过度拟合并学习到训练样本特有的随机噪音,而不是代表输入-输出关系一般模式的系统信号。另一方面,过于简单的模型会忽略信号并产生偏倚的结果。这种权衡在监督学习中被称为偏差-方差权衡,但概念上这也适用于其他形式的机器学习,过于复杂的模型可能在训练数据之外表现不佳。

无监督学习

在解决无监督学习问题时,我们只观察特征,没有测量结果。任务不是预测未来结果或推断变量之间的关系,而是在没有任何结果信息指导搜索过程的情况下,在数据中找到结构。

通常,无监督算法旨在学习输入数据的新表示,这对其他任务是有用的。这包括提出标签以识别观察结果之间的共性,或者是一个总结性描述,捕捉到相关信息,同时需要数据点或特征。无监督学习算法在他们试图发现的结构的性质方面也不同于监督学习算法所做出的假设。

应用

无监督学习有几种有用的用途可应用于算法交易,包括以下内容:

  • 将具有相似风险和回报特征的证券分组在一起(请参阅本章的分层风险平价(这是关于投资组合优化的))

  • 找到驱动大量证券表现的少数风险因素

  • 识别系统地不同的交易和价格模式,可能具有较高的风险

  • 识别文档体系(例如,收益电话转录)中的潜在主题,这些主题包括那些文档中最重要的方面

在高层次上,这些应用依赖于识别聚类的方法和降低数据维度的方法。

聚类算法

聚类算法使用相似性度量来识别包含相似信息的观察或数据属性。它们通过将大量数据点分配给较少数量的聚类来概括数据集,以使聚类成员彼此之间的关系比与其他聚类成员更密切。

聚类算法主要在于它们将产生何种类型的聚类,这意味着对数据生成过程有不同的假设,列举如下:

  • K 均值聚类:数据点属于相等大小的k个椭圆形聚类之一

  • 高斯混合模型:数据点是由任何各种多元正态分布生成的

  • 基于密度的聚类:聚类具有任意形状,并且仅由附近数据点的最小数量的存在来定义

  • 层次聚类:数据点属于由逐渐合并较小聚类形成的各种超集组

降维

降维生成新的数据,捕捉源数据中包含的最重要信息。这些算法不是将现有数据分组成聚类,而是将现有数据转换为使用显著较少的特征或观察来表示原始信息的新数据集。

算法在生成新数据集的性质上存在差异,如下列表所示:

  • 主成分分析(PCA):找到捕获现有数据集中大部分方差的线性变换

  • 流形学习:确定一个非线性转换,产生数据的低维表示

  • 自编码器:使用神经网络以最小的信息损失对数据进行非线性压缩

我们将在接下来的几章中更深入地探讨线性、非线性和基于神经网络的无监督学习模型,包括自然语言处理NLP)在主题建模和 Word2vec 特征提取形式的重要应用。

强化学习

强化学习是第三种 ML 类型。它旨在选择在给定描述上下文或环境的输入数据集的情况下产生最高奖励的行动。它既动态又交互:正面和负面奖励的流影响算法的学习,现在采取的行动可能影响环境和未来奖励。

在已学习到可以产生某种奖励的行动的开发和可能增加未来奖励的新行动之间的权衡导致了一种试错方法。强化学习使用动态系统理论,特别是使用马尔可夫决策过程的最优控制来优化代理的学习,其信息不完全。

强化学习与监督学习不同,监督学习中训练数据既提供了上下文又提供了算法的正确决策。它专为交互式设置设计,其中结果只在一段时间后才可用,并且学习必须以在线或连续的方式进行,因为代理获取新经验。然而,人工智能AI中一些最显著的进展涉及使用深度学习来近似动作、环境和未来奖励之间的功能关系的强化学习。它还与无监督学习不同,因为反馈的后果将可用,尽管会有延迟。

强化学习特别适用于算法交易,因为在不确定、动态环境中追求最大化回报的代理概念与与金融市场互动的投资者或交易策略有很多共同之处。这种方法已成功应用于游戏代理,尤其是围棋,但也用于复杂的视频游戏。它还用于机器人技术——例如,自动驾驶汽车——或根据用户交互个性化服务,例如网站提供的内容。我们将在第二十一章介绍强化学习方法,用于构建算法交易策略,强化学习

机器学习工作流程

为算法交易策略开发一个机器学习解决方案需要系统化的方法,以最大化成功的机会,同时节约资源。使过程透明且可复制非常重要,以便促进协作、维护和后续的改进。

下图概述了从问题定义到部署预测解决方案的关键步骤:

整个过程在整个序列中是迭代的,不同阶段需要的工作量会根据项目而变化,但这个过程通常应包括以下步骤:

  1. 确定问题框架,确定目标指标,并定义成功。

  2. 源,清理和验证数据。

  3. 理解你的数据并生成信息性特征。

  4. 选择一个或多个适合你的数据的机器学习算法。

  5. 训练,测试和调整你的模型。

  6. 使用你的模型解决原始问题。

我们将在接下来的章节中通过一个简单的例子来演示这些步骤,以阐明一些关键点。

基本步骤- k 最近邻。

本章书籍的 GitHub 存储库中的machine_learning_workflow.ipynb笔记本包含了几个示例,演示了使用房价数据集进行机器学习工作流程。

我们将使用相当简单的k 最近邻KNN)算法,它允许我们解决回归和分类问题。

在其默认的sklearn实现中,它识别出与欧几里德距离最近的k个数据点来进行预测。在分类或回归情况下,它分别预测邻居中最频繁的类别或平均结果。

确定问题框架-目标和指标。

任何机器学习练习的起点都是其旨在解决的最终用例。有时,这个目标将是统计推断,以识别变量之间的关联或甚至因果关系。然而,最常见的目标通常是直接预测结果以产生交易信号。

推断和预测都使用指标来评估模型实现其目标的程度。我们将重点放在常见的目标函数和相应的误差度量上,用于可以通过输出变量类型来区分的预测模型:连续输出变量意味着回归问题,分类变量意味着分类问题,而有序分类变量的特殊情况则意味着排名问题。

问题可能是多个 alpha 因子的高效组合,并且可以构建为一个回归问题,旨在预测回报,一个旨在预测未来价格走势方向的二元分类问题,或者一个旨在将股票分配到不同绩效类别的多类问题。在接下来的部分中,我们将介绍这些目标,并探讨如何衡量和解释相关的误差度量。

预测与推断的对比

由监督学习算法产生的功能关系可用于推断——即获得有关结果生成方式的见解——或用于预测——即为未知或未来输入生成准确的输出估计(由表示),用X表示未来输入)。

对于算法交易,推断可用于估计资产回报对风险因素的因果或统计依赖性,而预测可用于预测风险因素。将两者结合起来可以产生资产价格的预测,进而可以转化为交易信号。

统计推断涉及从样本数据中推断出关于潜在概率分布或总体参数的结论。可能的结论包括有关个别变量分布特征的假设检验,或关于变量之间数值关系的存在或强度的假设检验。它们还包括统计指标的点估计或区间估计。

推断取决于首次生成数据的过程的假设。我们将审查这些假设以及用于线性模型推断的工具,其中它们被很好地确立。更复杂的模型对输入和输出之间的结构关系做出较少的假设,而是更开放地处理函数逼近任务,同时将数据生成过程视为黑盒。这些模型,包括决策树、集成模型和神经网络,在用于预测任务时专注并经常优于其他模型。然而,随机森林最近获得了一种我们将在后面介绍的推断框架。

因果推断

因果推断旨在识别关系,以便某些输入值暗示某些输出值,例如,一定数量的宏观变量组合导致特定资产价格以某种方式变化,假设所有其他变量保持不变。

关于两个或多个变量之间关系的统计推断会产生相关性度量,这些度量只有在满足几个其他条件时才能解释为因果关系——例如,当已经排除了替代解释或逆向因果关系时。满足这些条件需要一个实验设置,其中所有感兴趣的相关变量都可以完全控制,以隔离因果关系。或者,准实验性设置以随机方式使观察单位暴露于输入变化,以排除其他可观察或不可观察的特征对环境变化观察效果的影响。

这些条件很少被满足,因此推断性结论需要谨慎对待。同样适用于预测模型的性能,后者也依赖于特征和输出之间的统计关联,该关联可能会随着不属于模型一部分的其他因素而改变。

KNN 模型的非参数性质不利于推断,因此我们将推迟工作流程中的这一步,直到我们在下一章遇到线性模型为止。

回归问题

回归问题旨在预测连续变量。均方根误差RMSE)是最流行的损失函数和误差度量,其中之一是因为它是可微的。损失是对称的,但在计算中较大的误差权重更重。使用平方根的优势在于以目标变量的单位度量误差。与RMSE 对数误差RMSLE)结合使用相同的度量在目标受指数增长影响时是适当的,因为其不对称惩罚使得负误差权重小于正误差。您也可以先对目标进行对数转换,然后使用 RMSE,正如我们在本节后面的示例中所做的那样。

平均绝对误差MAE)和中位数绝对误差MedAE)是对称的,但不会使较大的误差权重更重。MedAE 对异常值具有鲁棒性。

解释的方差分数计算模型解释的目标方差所占比例,介于 0 和 1 之间。R²分数或决定系数产生相同的结果,即残差的平均值为 0,但在其他情况下可能不同。特别地,当在样本外数据上计算时(或者对于没有截距的线性回归),它可以为负。

下表定义了用于计算的公式以及可从metrics模块导入的相应sklearn函数。scoring参数与自动化的训练-测试函数(例如cross_val_scoreGridSearchCV)结合使用,我们稍后将在本节介绍,并在附带的笔记本中进行演示:

名称公式sklearn评分参数
均方误差mean_squared_errorneg_mean_squared_error
平均平方对数误差mean_squared_log_errorneg_mean_squared_log_error
平均绝对误差mean_absolute_errorneg_mean_absolute_error
中位数绝对误差median_absolute_errorneg_median_absolute_error
解释的方差explained_variance_scoreexplained_variance
R2 分数r2_scorer2

下图显示了笔记本中房价回归的各种误差指标:

sklearn函数还支持多标签评估——即将多个结果值分配给单个观测值;有关更多详细信息,请参阅 GitHub 上引用的文档(github.com/PacktPublishing/Hands-On-Machine-Learning-for-Algorithmic-Trading/tree/master/Chapter06)。

分类问题

分类问题具有分类结果变量。 大多数预测器将输出一个分数,指示观测是否属于某个类。 在第二步中,这些分数然后被转换为实际预测。

在二元情况下,我们将标记类为正面和负面,分数通常在零之间变化或相应地进行归一化。 一旦将分数转换为 0-1 预测,就可能有四种结果,因为两个现有类别中的每一个都可以被正确或错误地预测。 如果有超过两个类别,则可以有更多的情况,如果您区分了几种潜在的错误。

所有的误差指标都是从预测在 2 x 2 混淆矩阵的四个领域中进行的,该矩阵将实际类与预测类相关联。 表中列出的指标,如准确性,评估了给定阈值的模型:

分类器不一定需要输出校准过的概率,而应该产生相对于彼此的分数,以区分正面和负面案例。 因此,阈值是一个决策变量,可以和应该优化,考虑到正确和错误预测的成本和收益。 更低的阈值意味着更多的正面预测,可能会出现日益上升的假阳性率,而更高的阈值则可能相反。

接收操作特性和曲线下面积

接收器操作特性 (ROC) 曲线允许我们根据其性能可视化、组织和选择分类器。它计算了使用任何预测分数作为阈值产生的所有真正例率 (TPR) 和假正例率 (FPR) 的组合。然后将这些对绘制在一个边长为一的正方形上。

进行随机预测(考虑类别不平衡)的分类器平均产生相等的 TPR 和 FPR,使得组合位于对角线上,这成为基准情况。由于性能不佳的分类器将受益于重新标记预测,因此该基准也成为最低水平。

曲线下面积 (AUC) 被定义为 ROC 图中的曲线下面积,其范围在 0.5 到最大值 1 之间变化。它是分类器的得分能够如何排列数据点的类成员身份的摘要度量。更具体地说,分类器的 AUC 具有重要的统计性质,表示分类器将随机选择的正实例排在随机选择的负实例之前的概率,这等价于 Wilcoxon 秩和检验。此外,AUC 不敏感于类别不平衡,这是它的好处。

精确度-召回率曲线

当对其中一个类别的预测特别感兴趣时,精确度和召回率曲线可视化这些误差度量之间的权衡,以及不同阈值的情况。两种度量都评估了对特定类别的预测质量。以下列表显示了它们如何应用于正类:

  • 召回率 衡量了对于给定阈值,分类器预测为正的实际正类成员的比例。它源于信息检索,衡量了搜索算法成功识别的相关文档的比例。

  • 精确度 相反,衡量了正确的正预测的比例。

召回率通常随着阈值降低而增加,但精确度可能会降低。精确度-召回率曲线可视化可达到的组合,并允许根据错过大量相关案例或产生质量较低的预测的成本和收益优化阈值。

F1 分数是给定阈值下精确度和召回率的调和平均数,并可用于在考虑这两个度量应承担的相对权重的情况下进行阈值的数值优化。

以下图表说明了 ROC 曲线及其相应的 AUC 以及精确度-召回率曲线和 F1 分数,使用精确度和召回率的相等权重,产生阈值为 0.37。图表取自附带笔记本,您可以在其中找到作用于二值化房价的 KNN 分类器的代码:

收集和准备数据

我们已经讨论了市场、基本和替代数据的采购的重要方面,并将继续使用各种这些来源的示例来说明各种模型的应用。

除了我们将通过 Quantopian 平台访问的市场和基本数据外,当我们探索自然语言处理时,我们还将获取和转换文本数据,当我们查看图像处理和识别时,我们也会获取和转换图像数据。除了获取、清理和验证数据以将其与通常以时间序列格式可用的交易数据相关联之外,将其存储在能够快速访问以便快速探索和迭代的格式中也很重要。我们推荐使用 HDF 和 parquet 格式。对于更大的数据量,Apache Spark 是最佳解决方案。

探索、提取和工程化特征

理解个体变量的分布以及结果和特征之间的关系是选择合适算法的基础。这通常从诸如散点图之类的可视化开始,如伴随笔记本中所示(并在以下图片中显示),但也包括从线性指标,如相关性,到非线性统计量,如我们介绍信息系数时遇到的 Spearman 秩相关系数的数字评估。它还包括信息论量度,如相互信息,如下一小节所示:

散点图

系统性的探索性分析也是通常成功预测模型的最重要的一个组成部分的基础:工程化的特征,这些特征提取了数据中包含的信息,但这些信息在原始形式下不一定能被算法访问到。特征工程受益于领域专业知识、统计学和信息论的应用,以及创造力。

它依赖于巧妙选择的数据转换,这些转换能有效地挖掘出输入和输出数据之间的系统关系。有许多选择,包括异常值检测和处理、功能转换以及多个变量的组合,包括无监督学习。我们将在整个过程中举例说明,但强调这个特性最好通过经验学习。Kaggle 是一个很好的地方,可以从其他数据科学家那里学习,他们与 Kaggle 社区分享了他们的经验。

使用信息论评估特征

特征和结果之间的相互信息MI)是两个变量之间相互依赖的一种度量。它将相关性的概念扩展到非线性关系。更具体地说,它量化了通过另一个随机变量获取的关于一个随机变量的信息。

MI 的概念与随机变量的基本熵概念密切相关。熵量化了随机变量中包含的信息量。形式上,两个随机变量XY的互信息—I(X, Y)—被定义如下:

sklearn 函数实现了 feature_selection.mutual_info_regression,计算所有特征与连续结果之间的互信息,以选择最有可能包含预测信息的特征。还有一个分类版本(请参阅文档以了解更多详情)。笔记本 mutual_information.ipynb 包含了对我们在第四章中创建的金融数据的应用,Alpha 因子研究

选择一个 ML 算法

本书的剩余部分将介绍几个模型族,从线性模型,其对输入和输出变量之间的功能关系的性质做出了相当强的假设,到深度神经网络,其假设极少。如前言中提到的,更少的假设将需要更多具有关于关系的显著信息的数据,以便学习过程能够成功。

我们将在引入这些模型时概述关键假设及其测试方法。

设计和调整模型

ML 过程包括基于模型泛化误差估计的诊断和管理模型复杂性的步骤。一个无偏估计需要一个统计上合理和有效的程序,以及与输出变量类型一致的错误度量,这也决定了我们是否处理回归、分类或排名问题。

偏差-方差的权衡

ML 模型在预测新输入数据的结果时所产生的错误可以分解为可减少和不可减少的部分。不可减少的部分是由数据中未测量的随机变化(噪音)引起的,比如相关但缺失的变量或自然变化。广义误差的可减少部分又可以分解为偏差和方差。二者都是由机器学习算法对真实功能关系和假设之间的差异造成的,如下列表所详述的:

  • 偏差引起的错误:假设过于简单,无法捕捉真实功能关系的复杂性。因此,每当模型试图学习真实功能时,它都会犯系统性错误,平均而言,预测也将受到类似的偏差影响。这也被称为欠拟合

  • 方差导致的错误:算法过于复杂,以至于忽视了真实关系。它不是捕捉真实关系,而是过拟合数据并从噪声中提取模式。结果,它从每个样本中学习到不同的函数关系,并且外样预测将有很大的变化。

欠拟合与过拟合

以下图表说明了通过使用越来越复杂的多项式来近似余弦函数并测量样本内误差而产生的过拟合。更具体地说,我们绘制 10 个带有一些添加噪声的随机样本(n = 30)以学习不同复杂度的多项式(请参阅附带笔记本中的代码)。每次,模型预测新数据点,我们捕获这些预测的均方误差,以及这些误差的标准差。

以下图表中的左侧面板显示了一个一次多项式;一条直线明显地欠拟合真实函数。然而,估计的直线不会从真实函数的一个抽样到下一个抽样有明显的差异。中间面板显示了一个五次多项式在[0, 1]区间上合理地近似了真实关系。另一方面,一个十五次多项式几乎完美地拟合了小样本,但提供了对真实关系的很差估计:它对样本数据点的随机变化过拟合,而且学习的函数会随着每个抽样而变化。

管理权衡

让我们通过尝试学习带有一些添加噪声的九次余弦函数的泰勒级数近似来进一步说明过拟合与欠拟合的影响。在以下图表中,我们绘制真实函数的随机样本,并拟合欠拟合、过拟合和提供近似正确灵活度的多项式。然后我们进行外样预测并测量 RMSE。

三次多项式的高偏差但低方差与第一个面板中可见的各种预测误差的低偏差但极高方差相比。左侧面板显示了由减去真实函数值产生的误差分布。直线欠拟合情况下产生了糟糕的样本内拟合,并且在样本外明显偏离目标。过拟合模型显示了最佳的样本内拟合以及最小的误差分散,但代价是样本外的大方差。与真实模型的函数形式匹配的适当模型明显在样本外表现最佳。

以下截图的右侧面板显示了实际预测而不是错误,以演示不同类型的拟合在实践中的样子:

学习曲线

学习曲线绘制了根据用于学习函数关系的数据集大小演变的训练和测试错误。它是诊断给定模型的偏差-方差权衡的有用工具,因为误差会表现出不同的行为。具有高偏差的模型将具有高但相似的训练误差,无论是样本内还是样本外,而过度拟合的模型将具有非常低的训练误差。

外样本错误的下降说明过拟合模型可能会受益于额外的数据或限制模型复杂性的工具,如正则化,而不拟合的模型需要使用更多特征或以其他方式增加模型的复杂性,如下图所示:

如何使用交叉验证进行模型选择

当您的用例中有多个候选模型(即算法)可用时,选择其中一个的行为称为模型选择问题。模型选择旨在确定在给定新数据的情况下产生最低预测误差的模型。

对这种泛化误差的无偏估计需要在未参与模型训练的数据上进行测试。因此,我们只使用部分可用数据来训练模型,并将另一部分数据留出来测试模型。为了获得对预测误差的无偏估计,绝对不能泄漏有关测试集的任何信息到训练集中,如下图所示:

有几种可用于拆分可用数据的方法,这些方法在用于训练的数据量、误差估计的方差、计算强度以及在拆分数据时是否考虑数据的结构方面不同。

如何在 Python 中实现交叉验证

我们将通过展示一个具有十个观察值的模拟数据集的索引如何分配到训练集和测试集(详见cross_validation.py),来说明将数据拆分为训练集和测试集的各种选项,如下所示:

data = list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

基本的训练测试拆分

对于将数据拆分为训练集和测试集的单次拆分,请使用sklearn.model_selection.train_test_split,其中shuffle参数默认确保观察值的随机选择,而这又可以通过设置random_state来复制。还有一个stratify参数,对于分类问题,确保训练集和测试集将包含大致相同的每个类的份额,如下图所示:

train_test_split(data, train_size=.8)
[[8, 7, 4, 10, 1, 3, 5, 2], [6, 9]]

在这种情况下,我们使用除行号69之外的所有数据来训练模型,这些数据将用于生成预测并测量给定know标签上的错误。这种方法对于快速评估很有用,但对拆分很敏感,并且测试误差估计的标准误差将更高。

交叉验证

交叉验证 (CV) 是一种常用的模型选择策略。CV 的主要思想是将数据拆分一次或多次,以便每个拆分都被用作一次验证集,剩余部分用作训练集:数据的一部分(训练样本)用于训练算法,剩余部分(验证样本)用于估计算法的风险。然后,CV 选择具有最小估计风险的算法。

虽然数据拆分启发式方法非常一般化,但 CV 的一个关键假设是数据是 独立同分布 (IID) 的。在接下来的章节中,我们将看到,对于时间序列数据,通常情况并非如此,需要采用不同的方法。

使用保留测试集

在基于验证分数选择超参数时,请注意,由于多次测试,此验证分数存在偏差,并不再是泛化误差的良好估计。为了对误差率进行无偏估计,我们必须从新数据集中估计分数,如下图所示:

因此,我们使用数据的三向拆分,如前图所示:一部分用于交叉验证,并反复拆分为训练集和验证集。剩余部分作为保留集保留,只有在交叉验证完成后才使用它生成无偏的测试误差估计。我们将在下一章开始构建机器学习模型时说明这种方法。

KFold 迭代器

sklearn.model_selection.KFold 迭代器生成几个不相交的拆分,并将这些拆分中的每一个分配给验证集,如下面的代码所示:

kf = KFold(n_splits=5)
for train, validate in kf.split(data):
    print(train, validate)

[2 3 4 5 6 7 8 9] [0 1]
[0 1 4 5 6 7 8 9] [2 3]
[0 1 2 3 6 7 8 9] [4 5]
[0 1 2 3 4 5 8 9] [6 7]
[0 1 2 3 4 5 6 7] [8 9]

除了拆分数量外,大多数 CV 对象还接受一个 shuffle 参数,以确保随机性。为了使结果可重现,设置 random_state 如下所示:

kf = KFold(n_splits=5, shuffle=True, random_state=42)
for train, validate in kf.split(data):
    print(train, validate)

[0 2 3 4 5 6 7 9] [1 8]
[1 2 3 4 6 7 8 9] [0 5]
[0 1 3 4 5 6 8 9] [2 7]
[0 1 2 3 5 6 7 8] [4 9]
[0 1 2 4 5 7 8 9] [3 6]

留一出 CV

原始 CV 实现使用留一出方法,每次将每个观测值一次用作验证集,如下面的代码所示:

loo = LeaveOneOut()
for train, validate in loo.split(data):
    print(train, validate)

[1 2 3 4 5 6 7 8 9] [0]
[0 2 3 4 5 6 7 8 9] [1]
...
[0 1 2 3 4 5 6 7 9] [8]
[0 1 2 3 4 5 6 7 8] [9]

这样做可以最大化训练的模型数量,增加计算成本。虽然验证集不重叠,但是训练集的重叠被最大化,推高了模型及其预测误差的相关性。因此,对于具有更多拆分的模型,预测误差的方差更高。

留-P-出 CV

与留一出 CV 类似的版本是留-P-出 CV,它生成所有可能的 p 数据行的组合,如下面的代码所示:

lpo = LeavePOut(p=2)
for train, validate in lpo.split(data):
    print(train, validate)

[2 3 4 5 6 7 8 9] [0 1]
[1 3 4 5 6 7 8 9] [0 2]
...
[0 1 2 3 4 5 6 8] [7 9]
[0 1 2 3 4 5 6 7] [8 9]

ShuffleSplit

sklearn.model_selection.ShuffleSplit 对象创建具有潜在重叠验证集的独立拆分,如下面的代码所示:

ss = ShuffleSplit(n_splits=3, test_size=2, random_state=42)
for train, validate in ss.split(data):
    print(train, validate)
[4 9 1 6 7 3 0 5] [2 8]
[1 2 9 8 0 6 7 4] [3 5]
[8 4 5 1 0 6 9 7] [2 3]

使用 scikit-learn 进行参数调整

模型选择通常涉及对使用不同算法(如线性回归和随机森林)或不同配置的模型的外样本性能进行重复交叉验证。不同的配置可能涉及对超参数的更改或包含或排除不同变量。

yellowbricks 库扩展了 sklearn API,生成诊断可视化工具,以便于模型选择过程。这些工具可用于调查特征之间的关系,分析分类或回归错误,监视聚类算法性能,检查文本数据的特性,并帮助进行模型选择。我们将演示提供有关参数调整阶段的有价值信息的验证和学习曲线—有关实现细节,请参阅 machine_learning_workflow.ipynb 笔记本。

使用 yellowbricks 的验证曲线

验证曲线(参见下图左侧面板)可视化单个超参数对模型交叉验证性能的影响。这有助于确定模型是否对给定数据集欠拟合或过拟合。

在我们的示例中,只有一个超参数的KNeighborsRegressor,我们可以清楚地看到,当k的值高于 20 时,模型欠拟合,其中验证错误随着减少邻居数量而下降,从而使我们的模型更复杂,因为它为更多不同的邻居组或特征空间中的区域进行预测。对于小于 20 的值,模型开始过拟合,因为训练和验证错误背离,平均外样本表现迅速恶化,如下图所示:

学习曲线

学习曲线(参见前述图表右侧面板,我们的房价回归示例)有助于确定模型的交叉验证性能是否会受益于更多数据,以及预测错误是更多地受偏差还是方差驱动。

如果训练和交叉验证性能趋于一致,那么更多的数据不太可能改善性能。在这一点上,重要的是评估模型性能是否符合人类基准的预期。如果不是这种情况,那么您应该修改模型的超参数设置,以更好地捕捉特征和结果之间的关系,或者选择一个具有更高复杂性捕捉能力的不同算法。

此外,阴影置信区间显示的训练和测试错误的变化提供了有关预测错误的偏差和方差来源的线索。交叉验证错误周围的变异性是方差的证据,而对训练集的变异性则暗示偏差,这取决于训练错误的大小。

在我们的例子中,交叉验证性能一直在下降,但增量改进已经减少,错误已经趋于稳定,因此从更大的训练集中很少会有很多好处。另一方面,鉴于验证错误的范围与训练错误相比显示出的范围相比,数据显示出相当大的方差。

使用 GridSearchCV 和 pipeline 进行参数调优

由于超参数调优是机器学习工作流的关键组成部分,有一些工具可以自动化这个过程。sklearn库包括一个GridSearchCV接口,该接口可以并行交叉验证所有参数组合,捕获结果,并自动使用在完整数据集上交叉验证中表现最佳的参数设置来训练模型。

实际上,在交叉验证之前,训练集和验证集通常需要进行一些处理。Scikit-learn 提供了Pipeline,以便在由GridSearchCV便利的自动化超参数调优中自动执行任何必要的特征处理步骤。

您可以查看所包含的machine_learning_workflow.ipynb笔记本中的实现示例,以查看这些工具的运行情况。

金融交叉验证中的挑战

迄今为止讨论的交叉验证方法的一个关键假设是训练样本的**独立且相同(iid)**分布。

对于金融数据,情况通常并非如此。相反,金融数据既不独立也不同分布,因为存在序列相关性和时变标准差,也称为异方差性(有关更多详细信息,请参阅接下来的两章)。sklearn.model_selection模块中的TimeSeriesSplit旨在解决时间序列数据的线性顺序。

使用 sklearn 进行时间序列交叉验证

数据的时间序列性意味着交叉验证会产生这样一种情况,即未来的数据将用于预测过去的数据。这充其量是不现实的,最坏的情况下是数据窥探,因为未来数据反映了过去事件的程度。

为了解决时间依赖性,sklearn.model_selection.TimeSeriesSplit对象实现了一个具有扩展训练集的前向测试,其中后续训练集是过去训练集的超集,如下面的代码所示:

tscv = TimeSeriesSplit(n_splits=5)
for train, validate in tscv.split(data):
    print(train, validate)

[0 1 2 3 4] [5]
[0 1 2 3 4 5] [6]
[0 1 2 3 4 5 6] [7]
[0 1 2 3 4 5 6 7] [8]
[0 1 2 3 4 5 6 7 8] [9]

你可以使用max_train_size参数来实现前向交叉验证,其中训练集的大小随时间保持不变,类似于zipline测试交易算法的方式。Scikit-learn 通过子类化方便地设计自定义交叉验证方法,我们将在接下来的章节中实现。

清除、禁止和组合交叉验证

对于金融数据,标签通常是从重叠的数据点派生的,因为收益率是从多个期间的价格计算出来的。在交易策略的背景下,模型的预测结果,可能意味着对某种资产的持仓,只有在稍后,当这个决定被评估时,才能知道,例如,当一个头寸被平仓时。

由此产生的风险包括信息从测试集泄漏到训练集,可能导致人为膨胀的性能,需要通过确保所有数据都是时点性的来解决,也就是说,在用作模型输入时真正可用和已知的时间。马科斯·洛佩斯·德·普拉多在《金融机器学习进展》中提出了几种方法来解决金融数据在交叉验证中的这些挑战,如下所示:

  • 清洗:消除训练数据点,其中评估发生在验证集中一个时间点数据点的预测之后,以避免前瞻性偏差。

  • 禁运:进一步消除跟随测试期间的训练样本。

  • 组合交叉验证:向前步进 CV 严重限制了可以测试的历史路径。相反,给定 T 个观测值,为 N<T 个组计算所有可能的训练/测试拆分,每个组都保持其顺序,并清洗和禁运可能重叠的组。然后,在所有 N-k 组合上训练模型,同时在剩余的 k 组上测试模型。结果是可能历史路径的数量大大增加。

普拉多的《金融机器学习进展》包含了实现这些方法的示例代码;该代码也可以通过新库timeseriescv获得。

总结

在本章中,我们介绍了从数据中学习的挑战,并将监督、无监督和强化模型作为我们将在本书中研究的主要学习形式,以构建算法交易策略。我们讨论了监督学习算法需要对它们试图学习的功能关系进行假设,以限制搜索空间,同时产生归纳偏差,可能导致过度泛化误差。

我们介绍了 ML 工作流程的关键方面,介绍了回归和分类模型的最常见错误度量标准,解释了偏差-方差折衷,并说明了使用交叉验证管理模型选择过程的各种工具。

在接下来的章节中,我们将深入探讨线性模型用于回归和分类,以开发我们的第一个使用 ML 的算法交易策略。

第七章:线性模型

线性模型家族代表了最有用的假设类之一。许多在算法交易中广泛应用的学习算法依赖于线性预测器,因为它们在许多情况下可以被有效地训练,在金融数据嘈杂的情况下相对稳健,并且与金融理论有着密切的联系。线性预测器也直观易懂,易于解释,并且通常能够很好地拟合数据,或者至少提供一个良好的基线。

线性回归已经有 200 多年的历史了,当勒让德和高斯将其应用于天文学并开始分析其统计性质时。此后,许多扩展都改编了线性回归模型和基线普通最小二乘法OLS)方法来学习其参数:

  • 广义线性模型GLM)通过允许响应变量表示除正态分布之外的误差分布来扩展应用范围。GLM 包括用于分类问题中的分类响应变量的 probit 或 logistic 模型。

  • 更多稳健估计方法使得在数据违反基线假设的情况下进行统计推断成为可能,例如,随时间或跨观测存在相关性。这在包含对相同单位的重复观测的面板数据中经常发生,例如,对一组资产的历史回报。

  • 收缩方法旨在改善线性模型的预测性能。它们使用一个复杂度惩罚,偏向于通过减少模型的方差并提高样本外预测性能来改善模型学习的系数。

在实践中,线性模型被应用于推断和预测的回归和分类问题。学术界和工业界研究人员开发的众多资产定价模型利用了线性回归。应用包括识别驱动资产回报的重要因素,例如,作为风险管理的基础,以及对各种时间范围内的回报进行预测。另一方面,分类问题包括方向性价格预测。

在本章中,我们将涵盖以下主题:

  • 线性回归的工作原理及其假设条件

  • 如何训练和诊断线性回归模型

  • 如何使用线性回归预测未来收益

  • 如何使用正则化来提高预测性能

  • logistic 回归的工作原理

  • 如何将回归转换为分类问题

对于代码示例、额外资源和参考资料,请参阅在线 GitHub 仓库中本章的目录。

用于推断和预测的线性回归

顾名思义,线性回归模型假设输出是输入的线性组合的结果。该模型还假设存在随机误差,允许每个观察值偏离预期的线性关系。模型不能完美描述输入和输出之间关系的原因包括,例如,缺少变量,测量或数据收集问题。

如果我们想根据从样本估计的回归参数来对人口中的真实(但未观察到的)线性关系进行统计推断,我们需要对这些误差的统计性质添加假设。基线回归模型做出了这样一个强假设,即错误的分布在错误之间是相同的,并且错误是彼此独立的,也就是说,知道一个错误不会有助于预测下一个错误。独立同分布iid)错误的假设意味着它们的协方差矩阵是由一个代表错误方差的常数与单位矩阵相乘得到的。

这些假设保证了 OLS 方法提供的估计值不仅是无偏的,而且是有效的,即它们具有最低的抽样误差学习算法。然而,在实践中很少满足这些假设。在金融领域,我们经常遇到具有给定截面上重复观察的面板数据。试图估计一组资产对一组风险因素的系统性暴露随时间变化的努力通常会导致时间维度或截面维度中的相关性,或者两者兼有。因此,出现了更多假设与单位矩阵的多个倍数不同的错误协方差矩阵的替代学习算法。

另一方面,学习为线性模型学习有偏参数的方法可能会产生方差较低的估计值,从而提高预测性能。收缩方法通过在线性目标函数中添加惩罚项来降低模型复杂性。惩罚与系数的绝对值大小正相关,因此相对于基线情况,这些系数被收缩。更大的系数意味着更复杂的模型,对输入的变化反应更强烈。正确校准的惩罚可以限制模型系数的增长,超出了最佳偏差-方差权衡建议的范围。

我们将介绍线性模型的基线截面和面板技术以及在关键假设被违反时产生准确估计的重要增强技术。然后,我们将通过估计在算法交易策略开发中无处不在的因子模型来说明这些方法。最后,我们将重点介绍正则化方法。

多元线性回归模型

我们将介绍模型的规范和目标函数、学习其参数的方法、允许推断和诊断这些假设的统计假设,以及扩展以适应这些假设失败情况的模型。

如何构建模型

多元回归模型定义了一个连续的结果变量与p个输入变量之间的线性函数关系,这些输入变量可以是任何类型,但可能需要预处理。相比之下,多元回归是指多个输出在多个输入变量上的回归。

在总体中,线性回归模型对于输出y的单个实例、输入向量 和误差ε具有以下形式:

系数的解释很简单:系数  的值是变量*x[i]*对输出的部分平均影响,在所有其他变量保持不变的情况下。

该模型也可以更简洁地以矩阵形式书写。在这种情况下,yN个输出观测值的向量,X是设计矩阵,其中有N行观测值和p个变量,另外还有一列 1 作为截距,而  是包含p+1个系数的向量 

该模型在其p+1个参数中是线性的,但可以通过相应地选择或转换变量来建模非线性关系,例如通过包含多项式基扩展或对数项。它还可以使用具有虚拟编码的分类变量,并通过创建形如*x[i] . x[j]*的新输入来对变量之间的交互进行建模。

从统计学角度完善模型的构建,以便我们可以测试关于参数的假设,我们需要对误差项进行具体的假设。我们将在首先介绍学习参数的替代方法之后做这个。

如何训练模型

有几种方法可以从数据中学习模型参数  :普通最小二乘法OLS)、最大似然估计MLE)和随机梯度下降SGD)。

最小二乘法

最小二乘法是学习最能够近似输入数据输出的超平面参数的原始方法。顾名思义,最佳近似将最小化输出值与模型表示的超平面之间的平方距离之和。

给定数据点的模型预测与实际结果之间的差异是残差(而真实模型与真实输出在总体中的偏差被称为误差)。因此,在正式术语中,最小二乘估计方法选择系数向量  以最小化残差平方和RSS):

因此,最小二乘系数  计算如下:

最小化 RSS 的最优参数向量是通过将前述表达式对  的导数置零而得到的。这产生了一个唯一的解,假设 X 具有完整的列秩,即输入变量不是线性相关的,如下所示:

y 和 X 通过减去它们各自的均值而被去均值时, 表示输入和输出  之间协方差的比率和输出方差 。还有一种几何解释:最小化 RSS 的系数确保  的向量与由 X 的列张成的子空间  是正交的,并且  是该子空间的正交投影。

最大似然估计

MLE 是估计统计模型参数的重要一般方法。它依赖于似然函数,该函数计算观察到的输出值样本在给定一组输入数据的情况下作为模型参数的函数的可能性。似然性与概率的不同之处在于它不被标准化为从 0 到 1 的范围。

我们可以通过假设误差项的分布(如标准正态分布)为线性回归示例设置似然函数:

.

这使我们能够计算给定输出  在相应输入向量 x[i] 和参数  的条件概率:

假设输出值在给定输入条件下是独立的,样本的似然性与单个输出数据点的条件概率的乘积成比例。由于使用和比使用乘积更容易,我们应用对数来获得对数似然函数:

MLE 的目标是通过选择模型参数最大化实际观察到的输出样本的概率,将观察到的输入视为给定。因此,MLE 参数估计结果来自于最大化(对数)似然函数:

由于正态分布的假设,最大化对数似然函数产生与最小二乘相同的参数解,因为仅有一个表达式与参数有关,即指数中的平方残差。对于其他分布假设和模型,MLE 将产生不同的结果,在许多情况下,最小二乘不适用,我们稍后将在逻辑回归中看到。

梯度下降(Gradient descent)

梯度下降是一种通用的优化算法,将找到平滑函数的稳定点。如果目标函数是凸的,则解将是全局最优解。梯度下降的变体被广泛用于训练复杂的神经网络,也用于计算 MLE 问题的解决方案。

算法使用目标函数的梯度,其中包含相对于参数的偏导数。这些导数指示了在相应参数方向上的微小步长中目标变化的程度。结果表明,函数值的最大变化来自于梯度方向的步长。

因此,当最小化描述例如预测误差成本的函数时,该算法使用训练数据为当前参数值计算梯度,并根据对应梯度分量的负值修改每个参数。结果,目标函数将取得较低的值,并使参数更接近解。当梯度变得很小时,参数值几乎不再变化时,优化停止。

这些步长的大小是学习率,这是一个可能需要调整的关键参数;许多实现包括逐渐增加学习率的选项,随着迭代次数的增加。根据数据的大小,算法可能会多次迭代整个数据集。每次迭代称为一个周期(epoch)。您可以调整的超参数包括周期数和用于停止进一步迭代的容差。

随机梯度下降(Stochastic gradient descent)随机选择一个数据点,并针对该数据点计算梯度,而不是对更大样本的平均值,以实现加速。还有批量版本,每个步骤使用一定数量的数据点。

高斯-马尔科夫定理

要评估模型的统计特性并进行推断,我们需要对残差(即输入未解释部分的属性)做出假设。高斯-马尔科夫定理GMT)定义了 OLS 需要的假设,以产生模型参数的无偏估计!,并且当这些估计在横截面数据的所有线性模型中具有最低标准误差时。

基线多元回归模型做出以下 GMT 假设:

  1. 在总体中,线性成立,,其中是未知但恒定的,而是一个随机误差

  2. 输入变量的数据是来自总体的随机样本

  3. 没有完美的共线性 - 输入变量之间不存在确切的线性关系

  4. 错误在给定任何输入时的条件均值为零:

  5. 同方差性,误差项在给定输入时具有恒定方差:

第四个假设意味着不存在与任何输入变量相关的缺失变量。在前四个假设下,OLS 方法提供无偏估计:包括一个无关变量不会使截距和斜率估计产生偏差,但忽略一个相关变量会使 OLS 估计产生偏差。然后 OLS 也是一致的:随着样本量的增加,估计值收敛到真实值,标准误差变得任意。不幸的是,反之亦然:如果误差的条件期望不为零,因为模型遗漏了一个相关变量或者功能形式错误(即,缺少二次项或对数项),那么所有参数估计都是有偏的。如果误差与任何输入变量相关,则 OLS 也不一致,即添加更多数据不会消除偏差。

如果添加了第五个假设,则 OLS 也会产生最佳线性无偏估计(BLUE),其中最佳意味着估计在所有线性估计器中具有最低标准误差。因此,如果五个假设成立,并且统计推断是目标,那么 OLS 估计是正确的选择。然而,如果目标是预测,那么我们将看到在许多情况下存在其他估计器,它们在一定的偏差情况下换取更低的方差以实现更好的预测性能。

现在我们已经介绍了基本的 OLS 假设,我们可以看看小样本和大样本推断。

如何进行统计推断

在线性回归背景下的推断旨在从样本数据中得出关于总体真实关系的结论。这包括关于总体关系的显著性或特定系数值的假设检验,以及置信区间的估计。

统计推断的关键要素是一个具有已知分布的检验统计量。我们可以假设零假设成立,并计算在样本中观察到该统计量值的概率,这个概率被称为 p 值。如果 p 值低于显著性阈值(通常为五百分之一),那么我们拒绝该假设,因为它使得实际样本值非常不可能。同时,我们接受 p 值反映了我们在拒绝实际上是正确的假设时错误的概率。

除了五个 GMT 假设之外,经典线性模型还假设正态性—总体误差服从正态分布且与输入变量独立。这一假设意味着在输入变量条件下,输出变量服从正态分布。这个强假设允许导出系数的精确分布,进而意味着在小样本中需要的测试统计的精确分布。这一假设通常会失败—例如,资产回报不服从正态分布—但是,幸运的是,正态性下使用的方法也是近似有效的。

我们拥有以下分布特征和测试统计量,近似地满足 GMT 假设 1–5,当正态性成立时则完全满足:

  • 参数估计服从多元正态分布:  。

  • 在 GMT 1–5 下,参数估计已经是无偏的,我们可以使用  对  进行无偏估计,即常数误差方差。

  • 对于关于个别系数的假设检验,t 统计量为 ,并且服从于自由度为 N-p-1t 分布,其中  是  对角线的第 j 个元素。

  • t 分布收敛于正态分布,而正态分布的 97.5 分位数为 1.96,因此,围绕参数估计的 95% 置信区间的一个有用的经验法则是 。包含零的区间意味着我们无法拒绝真实参数为零的零假设,因此对模型无关。

  • F统计量允许对多个参数的限制进行检验,包括整个回归是否显著。它测量了 RSS 的变化(减少),这是由于添加额外变量而导致的。

  • 最后,**拉格朗日乘数(LM)**测试是对F测试的替代,以限制多个限制。

如何诊断和解决问题

诊断验证模型假设,并在解释结果和进行统计推断时防止错误的结论。它们包括拟合优度的测量和关于误差项假设的各种测试,包括残差与正态分布的吻合程度。此外,诊断测试残差方差是否确实恒定,或者是否存在异方差,并且错误是否有条件地不相关或者存在串联相关性,即,知道一个错误是否有助于预测连续的错误。

除了以下概述的测试之外,始终重要的是可视化检查残差,以检测是否存在系统模式,因为这些指示模型缺少一个或多个驱动结果的因素。

拟合优度

拟合优度测量评估模型对结果变异的解释程度。它们有助于评估模型规范的质量,例如,在不同的模型设计之间进行选择。它们在评估拟合度时有所不同。在这里讨论的测量提供样本内信息;当我们在下一节专注于预测模型时,我们将使用样本外测试和交叉验证。

突出的拟合优度测量包括(调整后的),应该最大化,基于最小二乘估计:

  • 度量了模型解释结果数据变异的份额,并计算为,其中 TSS 是结果与其均值的平方偏差之和。它还对应于实际结果值与模型估计(拟合)值之间的平方相关系数。目标是最大化,但随着模型增加更多变量,它永远不会减少,因此会鼓励过度拟合。

  • 调整后的对于增加更多变量对进行惩罚;每个额外变量都需要显著减少 RSS,以产生更好的拟合优度。

或者,需要最小化的是Akaike(AIC)贝叶斯信息准则(BIC),它们基于最大似然估计:

  • ,其中是最大化似然函数的值,k 是参数个数

  • ,其中N是样本量

这两个指标都对复杂性进行了惩罚,BIC 施加了更高的惩罚,因此可能会欠拟合,而 AIC 可能会过拟合相对于 BIC 而言。从概念上讲,AIC 旨在找到最佳描述未知数据生成过程的模型,而 BIC 试图在候选模型集中找到最佳模型。在实践中,当目标是样本内拟合时,可以联合使用这两个标准来引导模型选择;否则,基于交叉验证和泛化误差估计的选择更可取。

异方差性

GMT 假设 5 要求残差协方差采用形状 ,即,一个对角矩阵,其条目等于误差项的恒定方差。异方差性发生在残差方差不恒定但在观察之间不同的情况下。如果残差方差与输入变量呈正相关,即,当误差较大时,与其平均值相差较远的输入值,则 OLS 标准误差估计将过低,因此 t 统计量将被夸大,导致在实际上不存在关系的情况下发现虚假关系。

诊断从对残差进行视觉检查开始。残差中的系统模式表明了针对误差同方差性的零假设进行统计检验的多种替代方案。这些测试包括 Breusch—Pagan 和 White 测试。

有几种方法可以校正 OLS 估计的异方差性:

  • 鲁棒标准误差(有时称为白色标准误差)在计算误差方差时考虑了异方差性,使用所谓的三明治估计器。

  • 聚类标准误差假设您的数据中存在不同的组,这些组在同方差但错误方差在组之间不同。这些组可以是不同的资产类别或来自不同行业的股票。

有几种替代方法可使用不同的假设来估计误差协方差矩阵,当时。以下是statsmodels中提供的选项:

  • 加权最小二乘法 (WLS):适用于异方差误差,其中协方差矩阵仅具有对角元素,如 OLS,但现在允许元素变化。

  • 可行广义最小二乘法 (GLSAR),用于遵循自回归 AR(p)过程的自相关误差(请参阅线性时间序列模型章节)

  • 广义最小二乘法 (GLS) 适用于任意协方差矩阵结构;在存在异方差性或序列相关性时产生高效且无偏的估计。

序列相关性

串行相关意味着线性回归产生的连续残差是相关的,这违反了第四个 GMT 假设。正串行相关意味着标准误差被低估,t-统计量将被夸大,如果忽略这一点,可能导致错误的发现。然而,在计算标准误差时,有纠正串行相关的程序。

Durbin-Watson 统计量诊断串行相关。它检验 OLS 残差不自相关的假设,而不是按照自回归过程(我们将在下一章中探讨)的替代。测试统计量的范围从 0 到 4,接近 2 的值表明非自相关,较低的值表示正,较高的值表示负的自相关。确切的阈值值取决于参数和观测值的数量,需要在表中查找。

多重共线性

当两个或更多自变量高度相关时,就会出现多重共线性。这带来了几个挑战:

  • 很难确定哪些因素影响因变量。

  • 单个 p 值可能会误导——即使变量很重要,p 值可能也很高。

  • 回归系数的置信区间可能过大,甚至可能包括零,这使得无法确定自变量对结果的影响。

没有正式的或基于理论的解决方案来纠正多重共线性。而是尝试删除一个或多个相关的输入变量,或增加样本量。

如何实践运行线性回归

附带的笔记本 linear_regression_intro.ipynb 展示了简单线性回归和多重线性回归,后者使用 OLS 和梯度下降两种方法。对于多元回归,我们生成两个随机输入变量 x[1]x[2],范围从-50 到+50,并计算结果变量作为输入的线性组合加上随机高斯噪声以满足正态性假设 GMT 6:

使用 statsmodels 进行 OLS

我们使用 statsmodels 来估计准确反映数据生成过程的多元回归模型,如下所示:

from statsmodels.api import 
X_ols = add_constant(X)
model = OLS(y, X_ols).fit()
model.summary()

下面是OLS 回归结果的摘要:

OLS 回归结果摘要

摘要的上半部分显示了数据集的特征,即估计方法、观测值和参数的数量,并指出标准误差估计不考虑异方差性。中间面板显示了与人工数据生成过程密切相关的系数值。我们可以确认,摘要结果中间显示的估计值可以使用先前推导的 OLS 公式获得:

beta = np.linalg.inv(X_ols.T.dot(X_ols)).dot(X_ols.T.dot(y))
pd.Series(beta, index=X_ols.columns)

const   50.94
X_1      1.08
X_2      2.93

以下图示了模型对随机生成的数据点拟合的超平面:

超平面

面板的右上部显示了刚讨论过的拟合优度指标,以及拒绝所有系数为零且不相关的假设的 F 检验。同样,t 统计量表明截距和斜率系数都是非常显著的,这并不令人意外。

摘要底部包含了残差诊断。左侧面板显示了用于检验正态性假设的偏度和峰度。Omnibus 和 Jarque—Bera 测试都未能拒绝残差正态分布的零假设。Durbin—Watson 统计量检验残差的序列相关性,并且在值接近 2 的情况下,考虑到 2 个参数和 625 个观测值,未能拒绝无序列相关性的假设。

最后,条件数提供了关于多重共线性的证据:它是包含输入数据的设计矩阵的最大和最小特征值的平方根的比值。值超过 30 表明回归可能存在显著的多重共线性。

statsmodels包含了与笔记本中链接的其他诊断测试。

使用 sklearn 的随机梯度下降

sklearn库在其linear_models模块中包含了一个SGDRegressor模型。要使用该方法学习相同模型的参数,我们需要首先标准化数据,因为梯度对尺度敏感。我们使用StandardScaler()来计算每个输入变量的平均值和标准差,并在拟合步骤中减去平均值并除以标准差,在转换步骤中进行方便的单个fit_transform()命令:

scaler = StandardScaler()
X_ = scaler.fit_transform(X)

然后,我们使用默认值实例化SGDRegressor,除了设置random_state以便复制:

sgd = SGDRegressor(loss='squared_loss', fit_intercept=True, 
                   shuffle=True, random_state=42,  # shuffle training data for better gradient estimates
                   learning_rate='invscaling',     # reduce learning rate over time
                   eta0=0.01, power_t=0.25)        # parameters for learning rate path

现在我们可以拟合sgd模型,为 OLS 模型和sgd模型创建样本内预测,并计算每个模型的均方根误差:

sgd.fit(X=X_, y=y)
resids = pd.DataFrame({'sgd': y - sgd.predict(X_),
                      'ols': y - model.predict(sm.add_constant(X))})
resids.pow(2).sum().div(len(y)).pow(.5)

ols   50.06
sgd   50.06

如预期的那样,两个模型产生相同的结果。我们现在将承担一个更雄心勃勃的项目,使用线性回归来估计多因子资产定价模型。

如何构建线性因子模型

算法交易策略使用线性因子模型来量化资产收益与代表这些收益主要驱动因素的风险来源之间的关系。每个因子风险都带有一个风险溢价,总资产收益可以预期对应于这些风险溢价的加权平均。

因子模型在投资组合管理过程中有多个实际应用,从构建和资产选择到风险管理和绩效评估。随着常见风险因素现在可以交易,因子模型的重要性不断增长:

  • 许多资产的回报摘要通过少量因子来减少在优化投资组合时估算协方差矩阵所需的数据量

  • 对资产或投资组合对这些因子的敞口的估计允许管理由此产生的风险,例如当风险因子本身被交易时,可以通过输入适当的对冲来管理风险

  • 因子模型还允许评估新α因子的增量信号内容。

  • 因子模型还可以帮助评估经理相对于基准的业绩是否确实是由于选择资产和市场时机的技能,或者是否业绩可以解释为投资组合倾向于已知回报驱动因素,这些因素今天可以以低成本、被动管理的基金形式复制,而无需支付主动管理费用。

以下示例适用于股票,但已为所有资产类别确定了风险因子(请参阅 GitHub 存储库中的参考资料)。

从资本资产定价模型到法玛-法国五因子模型

自从资本资产定价模型(CAPM)解释了所有N资产的预期回报,即使用它们对单一因子的各自暴露!到整体市场相对于无风险利率的预期超额回报!以来,风险因子一直是定量模型的关键因素。该模型采用以下线性形式:

这与道德和格雷厄姆经典的基本分析有所不同,后者依赖于公司特征来确定回报。理由是,在总体上,投资者无法通过分散化消除这种所谓的系统风险。因此,在均衡状态下,他们要求持有资产的补偿与其系统风险相称。该模型暗示,在市场有效的情况下,价格立即反映所有公开信息,就不应该有优越的风险调整回报,也就是说,的价值应该为零。

该模型的实证测试使用线性回归,并一直失败,促使人们争论是市场有效还是联合假设的单一因子方面有问题。事实证明,这两个前提都可能是错误的:

  • 约瑟夫·斯蒂格利茨在部分地获得了 2001 年诺贝尔经济学奖,因为他表明市场通常并非完全有效:如果市场有效,那么收集数据就没有价值,因为这些信息已经反映在价格中。但是,如果没有收集信息的动机,很难看到它如何已经反映在价格中。

  • 另一方面,对 CAPM 的理论和实证改进表明,额外因素有助于解释一些不依赖于整体市场敞口的超常风险调整回报的异常,例如较小公司的较高回报。

斯蒂芬·罗斯于 1976 年提出了套利定价理论APT),作为一种允许多种风险因素存在的替代方案,同时避免了市场效率。与 CAPM 相比,它假设由于定价错误而产生的超常回报机会可能存在,但会迅速被套利消除。该理论并未明确规定这些因素,但作者的研究表明,最重要的因素是通货膨胀和工业生产的变化,以及风险溢价或利率期限结构的变化。

肯尼斯·弗伦奇和尤金·法玛(获得 2013 年诺贝尔奖)确定了取决于公司特征的额外风险因素,并且今天被广泛使用。1993 年,法玛—弗伦奇三因子模型在单一 CAPM 风险的基础上增加了公司的相对规模和价值。2015 年,五因子模型进一步扩展了该集合,包括在介入年份已被证明显著的公司盈利能力和投资水平。此外,许多因子模型包括价格动量因子。

法玛—弗伦奇风险因素的计算是指根据反映给定风险因素的度量标准对多样化投资组合的回报差异。通过根据这些度量标准对股票进行排序,然后对超过某一百分位数的股票做多头,对低于某一百分位数的股票做空头,获得这些回报。与风险因素相关的度量标准定义如下:

  • 大小: 市场权益ME

  • 价值: 股权账面价值BE)除以 ME

  • 运营盈利能力(OP):收入减去销售成本/资产

  • 投资: 投资/资产

还有无监督学习技术用于基于数据的风险因素的发现,包括基于因子和主成分分析的方法,我们将在第十二章,无监督学习中进行探讨。

获取风险因素

法玛和弗伦奇通过他们的网站提供了更新的风险因素和研究投资组合数据,您可以使用pandas_datareader库获取这些数据。对于此应用,请参考fama_macbeth.ipynb笔记本以获取更多详细信息。

特别是,我们将使用从首先将股票分成三个规模组,然后对剩余三个公司特定因素中的每个进行两次排序的五个法玛—弗伦奇因子。因此,这些因素涉及根据大小和账面市值、大小和经营盈利能力以及大小和投资进行的 3 x 2 排序形成的三组加权价值组合。风险因素值计算为如下表所述的投资组合PF)的平均回报:

概念标签名称风险因子计算
规模SMB小减大九个小型股票 PF 减去九个大型股票 PF
HML高减低两个价值 PF 减去两个增长 PF(具有低 BE/ME 值)
盈利能力RMW强大减弱两个强劲 OP PF 减去两个弱 OP PF
投资CMA保守减激进两个保守投资组合减去两个激进投资组合
市场Rm-Rf市场的超额回报所有在主要美国交易所上市和上市的公司的价值加权回报,具有良好数据减去一个月期国库券利率

我们将使用从 2010 年至 2017 年期间获得的月频率的回报如下:

import pandas_datareader.data as web
ff_factor = 'F-F_Research_Data_5_Factors_2x3'
ff_factor_data = web.DataReader(ff_factor, 'famafrench', start='2010', end='2017-12')[0]
ff_factor_data.info()

PeriodIndex: 96 entries, 2010-01 to 2017-12
Freq: M
Data columns (total 6 columns):
Mkt-RF 96 non-null float64
SMB 96 non-null float64
HML 96 non-null float64
RMW 96 non-null float64
CMA 96 non-null float64
RF 96 non-null float64

Fama 和 French 还提供了许多我们可以用来说明因子暴露估计的投资组合,以及市场上某个特定时间段可用的风险溢价的价值。我们将使用一个包含 17 个行业投资组合的面板,并以月频率进行计算。我们将从回报中减去无风险利率,因为因子模型使用超额回报:

ff_portfolio = '17_Industry_Portfolios'
ff_portfolio_data = web.DataReader(ff_portfolio, 'famafrench', start='2010', end='2017-12')[0]
ff_portfolio_data = ff_portfolio_data.sub(ff_factor_data.RF, axis=0)
ff_factor_data = ff_factor_data.drop('RF', axis=1)
ff_portfolio_data.info()

PeriodIndex: 96 entries, 2010-01 to 2017-12
Freq: M
Data columns (total 17 columns):
Food     96 non-null float64
Mines    96 non-null float64
Oil      96 non-null float64
...
Rtail    96 non-null float64
Finan    96 non-null float64
Other    96 non-null float64

现在,我们将基于这些面板数据构建一个线性因子模型,使用一种解决一些基本线性回归假设失败的方法。

Fama—Macbeth 回归

鉴于风险因子和投资组合回报的数据,估计投资组合的暴露是有用的,即风险因子驱动投资组合回报的程度,以及给定因子的暴露值的价值,即市场的风险因子溢价是多少。然后,风险溢价可以估算任何投资组合的回报,前提是已知或可以假定因子暴露。

更正式地说,我们将对t=1, ..., T期间的i=1, ..., N资产或投资组合的回报进行测试,每个资产的超额期间回报将被表示为。目标是测试j=1, ..., M因子是否解释了超额回报以及与每个因子相关的风险溢价。在我们的案例中,我们有N=17个投资组合和M=5个因子,每个因子都有 96 个数据周期。

因子模型是针对给定期间内许多股票进行估计的。在这种横截面回归中可能会出现推断问题,因为经典线性回归的基本假设可能不成立。潜在的违规行为包括测量误差,由异方差和串行相关引起的残差协方差,以及多重共线性。

为了解决残差相关引起的推断问题,Fama 和 MacBeth 提出了一种两步法的方法,用于对因子上的横截面回归。两阶段的 Fama—Macbeth 回归旨在估计市场对特定风险因子暴露所奖励的溢价。两个阶段包括:

  • 第一阶段N时间序列回归,对于每个资产或投资组合,它的超额收益对因子进行回归,以估计因子载荷。以矩阵形式,对于每个资产:

  • 第二阶段:T 个横截面回归,每个时间段一个,以估计风险溢价。以矩阵形式,我们获得每个时间段的风险溢价向量

现在我们可以将因子风险溢价计算为时间平均值,并使用假设风险溢价估计在时间上独立来获取 t 统计量,评估它们的个体显著性:

如果我们拥有一个非常大且具代表性的交易风险因子数据样本,我们可以将样本均值用作风险溢价估计。然而,我们通常没有足够长的历史记录,而且样本均值周围的误差范围可能相当大。法马—麦克贝斯方法利用因子与其他资产的协方差来确定因子溢价。资产收益的二阶矩比第一阶矩更容易估计,并且获得更细粒度的数据会显着改善估计,这在均值估计中并不成立。

我们可以实现第一阶段,以获取 17 个因子载荷估计,如下所示:

betas = []
for industry in ff_portfolio_data:
    step1 = OLS(endog=ff_portfolio_data[industry],
                exog=add_constant(ff_factor_data)).fit()
    betas.append(step1.params.drop('const'))

betas = pd.DataFrame(betas,
                     columns=ff_factor_data.columns,
                     index=ff_portfolio_data.columns)
betas.info()
Index: 17 entries, Food  to Other
Data columns (total 5 columns):
Mkt-RF    17 non-null float64
SMB       17 non-null float64
HML       17 non-null float64
RMW       17 non-null float64
CMA       17 non-null float64

对于第二阶段,我们对横截面投资组合的期间收益进行 96 次回归,以估计因子载荷:

lambdas = []
for period in ff_portfolio_data.index:
    step2 = OLS(endog=ff_portfolio_data.loc[period, betas.index],
                exog=betas).fit()
    lambdas.append(step2.params)

lambdas = pd.DataFrame(lambdas,
                       index=ff_portfolio_data.index,
                       columns=betas.columns.tolist())
lambdas.info()
PeriodIndex: 96 entries, 2010-01 to 2017-12
Freq: M
Data columns (total 5 columns):
Mkt-RF    96 non-null float64
SMB       96 non-null float64
HML       96 non-null float64
RMW       96 non-null float64
CMA       96 non-null float64

最后,我们计算 96 个周期的平均值,以获取我们的因子风险溢价估计值:

lambdas.mean()
Mkt-RF    1.201304
SMB       0.190127
HML      -1.306792
RMW      -0.570817
CMA      -0.522821

linear_models库通过各种面板数据模型扩展了statsmodels,并且还实现了两阶段法马—麦克贝斯程序:

model = LinearFactorModel(portfolios=ff_portfolio_data, 
                          factors=ff_factor_data)
res = model.fit()

这为我们提供了相同的结果:

线性因子模型估计摘要

随附的笔记本通过在估计较大的个别股票风险溢价时使用行业虚拟变量来说明了分类变量的使用。

缩小方法:线性回归的正则化

当高斯—马尔可夫假设得到满足时,最小二乘法用于训练线性回归模型将会产生最佳、线性和无偏的系数估计。即使 OLS 关于误差协方差矩阵的假设被违反,类似 GLS 的变种也会表现得相当好。然而,有一些估计器会产生偏斜的系数,以减少方差以达到更低的总体泛化误差。

当线性回归模型包含许多相关变量时,它们的系数将被较差地确定,因为大正系数对 RSS 的影响可能会被相关变量上的同样大的负系数抵消。因此,由于系数的这种摆动空间,模型将具有高方差的倾向,增加模型过度拟合样本的风险。

如何对抗过度拟合

控制过拟合的一种流行技术是正则化,它涉及将惩罚项添加到误差函数中,以阻止系数达到较大的值。换句话说,对系数的大小施加约束可以缓解结果对样本外预测的潜在负面影响。由于过拟合是如此普遍的问题,我们将在所有模型中遇到正则化方法。

在本节中,我们将介绍缩小方法,以解决改进迄今为止讨论的线性模型方法的两个动机:

  • 预测准确性:最小二乘估计的低偏差但高方差表明,通过收缩或将某些系数设置为零,可以减少泛化误差,从而在略微增加偏差的同时减少模型的方差。

  • 解释性:大量的预测变量可能会使结果的解释或传达变得复杂化。牺牲一些细节以限制模型只包括具有最强效果的参数子集可能更可取。

收缩模型通过对其大小施加惩罚来限制回归系数。这些模型通过向目标函数添加一个项来实现此目标,使得收缩模型的系数最小化 RSS 加上一个与系数(绝对值)大小正相关的惩罚项。添加的惩罚将线性回归系数的查找转变为一个约束最小化问题,通常采用以下拉格朗日形式:

正则化参数 λ 决定了惩罚效果的大小,即正则化的强度。一旦 λ 为正,系数将与无约束的最小二乘参数不同,这意味着一个有偏的估计。超参数 λ 应该通过交叉验证来自适应地选择,以最小化预测误差的估计。

收缩模型的区别在于它们如何计算惩罚,即 S 的函数形式。最常见的版本是岭回归,它使用了系数平方的和,而套索模型则基于系数绝对值的和来设置惩罚。

岭回归的工作原理

岭回归通过将惩罚添加到目标函数中来缩小回归系数,该惩罚等于系数的平方和,进而对应于系数向量的 L² 范数:

因此,岭回归系数定义如下:

截距已从惩罚中排除,以使程序独立于为输出变量选择的原点——否则,将常数添加到所有输出值将改变所有斜率参数而不是平行移位。

重要的是通过从每个输入中减去相应的均值并将结果除以输入的标准差来标准化输入,因为岭解对输入的尺度敏感。岭估计器也有一个类似 OLS 情况的闭式解:

在求逆之前,解决方案将缩放后的单位矩阵λI添加到X^TX中,这保证了问题是非奇异的,即使X^T**X没有满秩。这是最初引入该估计量时的动机之一。

岭惩罚导致所有参数的成比例收缩。在正交输入的情况下,岭估计只是最小二乘估计的缩放版本,即:

使用输入矩阵X奇异值分解SVD),我们可以深入了解在更常见的情况下,收缩如何影响非正交输入。中心矩阵的 SVD 表示矩阵的主成分(参见第十一章,梯度提升机,有关无监督学习的内容),按方差降序捕获数据的列空间中的不相关方向。

岭回归会收缩与数据中方差较小的方向相关联的输入变量的系数,而不是收缩与展现更多方差的方向相关联的输入变量的系数。因此,岭回归的隐含假设是,在数据中变化最大的方向在预测输出时最有影响力或最可靠。

套索回归的工作原理

套索,在信号处理中称为基 Pursuit,通过向残差的平方和添加惩罚来缩小系数,但套索惩罚具有稍微不同的效果。套索惩罚是系数向量的绝对值的总和,对应于其 L¹范数。因此,套索估计由以下方式定义:

与岭回归类似,输入需要被标准化。套索惩罚使解决方案非线性,并且与岭回归不同,系数没有闭式表达式。相反,套索解是一个二次规划问题,有可用的高效算法可以计算出对于不同λ值产生的系数整个路径,其计算成本与岭回归相同。

Lasso 惩罚的效果是随着正则化的增加逐渐将一些系数减少到零。因此,Lasso 可用于连续选择一组特征。

如何使用线性回归预测回报

笔记本 linear_regression.ipynb 包含使用 statsmodelssklearn 进行 OLS 的股价预测的示例,以及岭回归和 Lasso 模型。它旨在在 Quantopian 研究平台上作为笔记本运行,并依赖于 第四章 中介绍的 factor_libraryAlpha Factors Research

准备数据

我们需要选择一组股票和一个时间范围,构建和转换我们将用作特征的 alpha 因子,计算我们希望预测的前瞻回报,并可能清理我们的数据。

宇宙创建和时间范围

我们将使用 2014 年和 2015 年的股票数据,来自使用内置过滤器、因子和分类器选择最后 200 个交易日的平均美元成交量最高的 100 支股票的自定义 Q100US 宇宙,根据额外的默认标准进行筛选(请参阅 GitHub 上链接的 Quantopian 文档以获取详细信息)。该宇宙会根据筛选标准动态更新,因此,在任何给定点上可能有 100 支股票,但样本中可能有超过 100 个不同的股票:

def Q100US():
    return filters.make_us_equity_universe(
        target_size=100,
        rankby=factors.AverageDollarVolume(window_length=200),
        mask=filters.default_us_equity_universe_mask(),
        groupby=classifiers.fundamentals.Sector(),
        max_group_weight=0.3,
        smoothing_func=lambda f: f.downsample('month_start'),
    )

目标回报计算

我们将测试不同 lookahead 期间的预测,以确定产生最佳可预测性的最佳持有期,该期间由信息系数衡量。更具体地说,我们使用内置的 Returns 函数计算 1、5、10 和 20 天的收益,结果是在两年内(每年大约有 252 个交易日)对 100 支股票的宇宙产生超过 50,000 个观察值:

lookahead = [1, 5, 10, 20]
returns = run_pipeline(Pipeline({'Returns{}D'.format(i): Returns(inputs=[USEquityPricing.close],
                                          window_length=i+1, mask=UNIVERSE) for i in lookahead},
                                screen=UNIVERSE),
                       start_date=START,
                       end_date=END)
return_cols = ['Returns{}D'.format(i) for i in lookahead]
returns.info()

MultiIndex: 50362 entries, (2014-01-02 00:00:00+00:00, Equity(24 [AAPL])) to (2015-12-31 00:00:00+00:00, Equity(47208 [GPRO]))
Data columns (total 4 columns):
Returns10D    50362 non-null float64
Returns1D     50362 non-null float64
Returns20D    50360 non-null float64
Returns5D     50362 non-null float64

Alpha 因子选择和转换

我们将使用超过 50 个涵盖市场、基本和替代数据的各种因素的特征。笔记本还包括自定义转换,将通常以季度报告频率提供的基本数据转换为滚动年度总额或平均值,以避免过度季节波动。

一旦通过 第四章 中概述的各种流水线计算出因子,Alpha 因子研究,我们使用 pd.concat() 将它们组合起来,分配索引名称,并创建一个用于识别每个数据点的资产的分类变量:

data = pd.concat([returns, value_factors, momentum_factors,
                  quality_factors, payout_factors, growth_factors,
                  efficiency_factors, risk_factors], axis=1).sortlevel()
data.index.names = ['date', 'asset']
data['stock'] = data.index.get_level_values('asset').map(lambda x: x.asset_name)

数据清洗 - 缺失数据

接下来,我们删除缺少超过 20% 观察值的行和列,导致损失 6% 的观察值和 3 列:

rows_before, cols_before = data.shape
data = (data
        .dropna(axis=1, thresh=int(len(data) * .8))
        .dropna(thresh=int(len(data.columns) * .8)))
data = data.fillna(data.median())
rows_after, cols_after = data.shape
print('{:,d} rows and {:,d} columns dropped'.format(rows_before - rows_after, cols_before - cols_after))
2,985 rows and 3 columns dropped

此时,我们有 51 个特征和股票的分类标识符:

data.sort_index(1).info()

MultiIndex: 47377 entries, (2014-01-02, Equity(24 [AAPL])) to (2015-12-
                            31, Equity(47208 [GPRO]))
Data columns (total 52 columns):
AssetToEquityRatio             47377 non-null float64
AssetTurnover                  47377 non-null float64
CFO To Assets                  47377 non-null float64
...
WorkingCapitalToAssets         47377 non-null float64
WorkingCapitalToSales          47377 non-null float64
stock                          47377 non-null object
dtypes: float64(51), object(1)

数据探索

对于线性回归模型,重要的是探索特征之间的相关性,以识别多重共线性问题,并检查特征与目标之间的相关性。笔记本包含一个 seaborn clustermap,显示特征相关矩阵的层次结构。它识别出少量高度相关的集群。

对分类变量进行虚拟编码

我们需要将分类变量stock转换为数字格式,以便线性回归可以处理它。为此,我们使用创建每个类别级别的单独列并使用1标记此级别在原始分类列中存在,否则标记为0的虚拟编码。 pandas 函数get_dummies()自动执行虚拟编码。它检测并正确转换类型为对象的列,如下所示。如果您需要对包含整数的列获取虚拟变量,例如,您可以使用columns关键字标识它们:

df = pd.DataFrame({'categories': ['A','B', 'C']})

  categories
0          A
1          B
2          C

pd.get_dummies(df)

   categories_A  categories_B  categories_C
0             1             0             0
1             0             1             0
2             0             0             1

当将所有类别转换为虚拟变量并估计模型时,使用截距(通常情况下)会无意中创建多重共线性:矩阵现在包含冗余信息,不再具有完整的秩,即,变得奇异。通过删除新指标列中的一个来简单避免这种情况。缺失类别级别上的系数现在将由截距(当其他每个类别虚拟为0时始终为1)捕获。使用drop_first关键字相应地更正虚拟变量:

pd.get_dummies(df, drop_first=True)

   categories_B  categories_C
0             0             0
1             1             0
2             0             1

应用于我们的综合特征和回报,我们获得了 181 列,因为有超过 100 只股票作为宇宙定义,它会自动更新股票选择:

X = pd.get_dummies(data.drop(return_cols, axis=1), drop_first=True)
X.info()

MultiIndex: 47377 entries, (2014-01-02 00:00:00+00:00, Equity(24 [AAPL])) to (2015-12-31 00:00:00+00:00, Equity(47208 [GPRO]))
Columns: 181 entries, DividendYield to stock_YELP INC
dtypes: float64(182)
memory usage: 66.1+ MB

创建前瞻回报

目标是预测给定持有期内的回报。因此,我们需要将特征与回报值与相应的未来 1、5、10 或 20 天的回报数据点对齐,对于每个股票。我们通过将 pandas 的.groupby()方法与.shift()方法结合使用来实现这一点:

y = data.loc[:, return_cols]
shifted_y = []
for col in y.columns:
    t = int(re.search(r'\d+', col).group(0))
    shifted_y.append(y.groupby(level='asset')['Returns{}D'.format(t)].shift(-t).to_frame(col))
y = pd.concat(shifted_y, axis=1)
y.info()

MultiIndex: 47377 entries, (2014-01-02, Equity(24 [AAPL])) to (2015-12-31, Equity(47208 [GPRO]))
Data columns (total 4 columns):
Returns1D     47242 non-null float64
Returns5D     46706 non-null float64
Returns10D    46036 non-null float64
Returns20D    44696 non-null float64
dtypes: float64(4)

现在每个回报系列的观察次数不同,因为前向移位在每个股票的尾部创建了缺失值。

使用 statsmodels 进行线性 OLS 回归

我们可以像之前演示的那样使用statsmodels估计线性回归模型的 OLS。我们选择一个前瞻回报,例如一个 10 天的持有期,删除低于 2.5%和高于 97.5%百分位数的异常值,然后相应地拟合模型:

target = 'Returns10D'
model_data = pd.concat([y[[target]], X], axis=1).dropna()
model_data = model_data[model_data[target].between(model_data[target].quantile(.025), 
                                                   model_data[target].quantile(.975))]

model = OLS(endog=model_data[target], exog=model_data.drop(target, axis=1))
trained_model = model.fit()
trained_model.summary()

诊断统计

摘要可在笔记本中保存一些空间,因为变量数量很多。诊断统计显示,鉴于 Jarque—Bera 统计量的高 p 值,不能拒绝残差服从正态分布的假设。

然而,杜宾-沃森统计量为 1.5,因此我们可以在 5%的水平上舒适地拒绝无自相关的零假设。因此,标准误差可能呈正相关。如果我们的目标是了解哪些因素与前向收益显著相关,我们需要使用稳健标准误差重新运行回归(在statsmodels .fit()方法中的一个参数),或者完全使用不同的方法,如允许更复杂误差协方差的面板模型。

使用 sklearn 进行线性 OLS 回归

由于 sklearn 专门用于预测,我们将根据其预测性能使用交叉验证评估线性回归模型。

自定义时间序列交叉验证

我们的数据包括分组的时间序列数据,需要一个自定义的交叉验证函数来提供训练和测试索引,以确保测试数据立即跟随每个股票的训练数据,我们不会无意中产生前瞻性偏差或泄漏。

我们可以使用以下函数实现这一点,该函数返回一个generator,产生训练和测试日期的对。确保训练期的最小长度的训练日期集。对数nfolds取决于参数。不同的测试期不重叠,位于数据中可用的周期末。在使用测试期后,它将成为相应增长长度的训练数据的一部分:

def time_series_split(d=model_data, nfolds=5, min_train=21):
    """Generate train/test dates for nfolds 
    with at least min_train train obs
    """
    train_dates = d[:min_train].tolist()
    n = int(len(dates)/(nfolds + 1)) + 1
    test_folds = [d[i:i + n] for i in range(min_train, len(d), n)]
    for test_dates in test_folds:
        if len(train_dates) > min_train:
            yield train_dates, test_dates
        train_dates.extend(test_dates)

选择特征和目标

我们需要选择适当的回报系列(我们将再次使用 10 天的持有期),并去除异常值。我们还将将回报转换为对数回报,如下所示:

target = 'Returns10D'
outliers = .01
model_data = pd.concat([y[[target]], X], axis=1).dropna().reset_index('asset', drop=True)
model_data = model_data[model_data[target].between(*model_data[target].quantile([outliers, 1-outliers]).values)]

model_data[target] = np.log1p(model_data[target])
features = model_data.drop(target, axis=1).columns
dates = model_data.index.unique()

DatetimeIndex: 45114 entries, 2014-01-02 to 2015-12-16
Columns: 183 entries, Returns10D to stock_YELP INC
dtypes: float64(183)

对模型进行交叉验证

我们将使用 250 个折叠来通常预测历史训练数据后大约 2 天的前向收益。每次迭代从我们的自定义交叉验证函数中获得适当的训练和测试日期,选择相应的特征和目标,然后进行训练和预测。我们捕获根均方误差以及实际值和预测值之间的 Spearman 等级相关性:

nfolds = 250
lr = LinearRegression()

test_results, result_idx, preds = [], [], pd.DataFrame()
for train_dates, test_dates in time_series_split(dates, nfolds=nfolds):
    X_train = model_data.loc[idx[train_dates], features]
    y_train = model_data.loc[idx[train_dates], target]
    lr.fit(X=X_train, y=y_train)

    X_test = model_data.loc[idx[test_dates], features]
    y_test = model_data.loc[idx[test_dates], target]
    y_pred = lr.predict(X_test)

    rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test))
    ic, pval = spearmanr(y_pred, y_test)

    test_results.append([rmse, ic, pval])
    preds = preds.append(y_test.to_frame('actuals').assign(predicted=y_pred))
    result_idx.append(train_dates[-1])

测试结果-信息系数和 RMSE

我们已经从 250 个折叠中捕获了测试预测,并可以计算整体和 21 天滚动平均值:

fig, axes = plt.subplots(nrows=2)
rolling_result = test_result.rolling(21).mean()
rolling_result[['ic', 'pval']].plot(ax=axes[0], title='Information Coefficient')
axes[0].axhline(test_result.ic.mean(), lw=1, ls='--', color='k')
rolling_result[['rmse']].plot(ax=axes[1], title='Root Mean Squared Error')
axes[1].axhline(test_result.rmse.mean(), lw=1, ls='--', color='k')

我们得到以下图表,突显了 IC 和 RMSE 的负相关及其各自的值:

突显 IC 和 RMSE 的负相关的图表

对于整个时期,我们看到信息系数通过实际值和预测值的等级相关性来测量,呈弱正相关且具有统计学显著性:

使用 sklearn 进行岭回归

对于岭回归,我们需要使用关键字alpha来调整正则化参数,该参数对应于我们之前使用的λ。 我们将尝试从 10^(-5)到 10⁵的 21 个值以对数步长进行尝试。

岭惩罚的尺度敏感性要求我们使用StandardScaler对输入进行标准化。 请注意,我们始终从训练集中学习均值和标准差,然后使用.fit_transform()方法将这些学习参数应用于测试集,然后使用.transform()方法。

使用交叉验证调整正则化参数

然后,我们继续使用250个折叠交叉验证超参数值:

nfolds = 250
alphas = np.logspace(-5, 5, 21)
scaler = StandardScaler()

ridge_result, ridge_coeffs = pd.DataFrame(), pd.DataFrame()
for i, alpha in enumerate(alphas):
    coeffs, test_results = [], []
    lr_ridge = Ridge(alpha=alpha)
    for train_dates, test_dates in time_series_split(dates, nfolds=nfolds):
        X_train = model_data.loc[idx[train_dates], features]
        y_train = model_data.loc[idx[train_dates], target]
        lr_ridge.fit(X=scaler.fit_transform(X_train), y=y_train)
        coeffs.append(lr_ridge.coef_)

        X_test = model_data.loc[idx[test_dates], features]
        y_test = model_data.loc[idx[test_dates], target]
        y_pred = lr_ridge.predict(scaler.transform(X_test))

        rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test))
        ic, pval = spearmanr(y_pred, y_test)

        test_results.append([train_dates[-1], rmse, ic, pval, alpha])
    test_results = pd.DataFrame(test_results, columns=['date', 'rmse', 'ic', 'pval', 'alpha'])
    ridge_result = ridge_result.append(test_results)
    ridge_coeffs[alpha] = np.mean(coeffs, axis=0)

交叉验证结果和岭回归系数路径

我们现在可以绘制每个超参数值获得的信息系数,并可视化随着正则化增加而系数值如何变化。 结果显示,我们在λ=10\时获得了最高的 IC 值。 对于这个正则化水平,右侧面板显示,与(几乎)不受约束的模型相比,系数已经显着收缩,λ=10^(-5):

交叉验证结果和岭回归系数路径

前 10 个系数

系数的标准化允许我们通过比较它们的绝对值来得出关于它们相对重要性的结论。 最相关的 10 个系数是:

前 10 个系数

使用 sklearn 的套索回归

套索实现看起来与我们刚刚运行的岭回归模型非常相似。 主要区别在于,套索需要使用迭代坐标下降来找到解决方案,而岭回归可以依赖于闭合形式的解决方案:

nfolds = 250
alphas = np.logspace(-8, -2, 13)
scaler = StandardScaler()

lasso_results, lasso_coeffs = pd.DataFrame(), pd.DataFrame()
for i, alpha in enumerate(alphas):
    coeffs, test_results = [], []
    lr_lasso = Lasso(alpha=alpha)
    for i, (train_dates, test_dates) in enumerate(time_series_split(dates, nfolds=nfolds)):
        X_train = model_data.loc[idx[train_dates], features]
        y_train = model_data.loc[idx[train_dates], target]
        lr_lasso.fit(X=scaler.fit_transform(X_train), y=y_train)

        X_test = model_data.loc[idx[test_dates], features]
        y_test = model_data.loc[idx[test_dates], target]
        y_pred = lr_lasso.predict(scaler.transform(X_test))

        rmse = np.sqrt(mean_squared_error(y_pred=y_pred, y_true=y_test))
        ic, pval = spearmanr(y_pred, y_test)

        coeffs.append(lr_lasso.coef_)
        test_results.append([train_dates[-1], rmse, ic, pval, alpha])
    test_results = pd.DataFrame(test_results, columns=['date', 'rmse', 'ic', 'pval', 'alpha'])
    lasso_results = lasso_results.append(test_results)
    lasso_coeffs[alpha] = np.mean(coeffs, axis=0)

交叉验证信息系数和套索路径

与以前一样,我们可以绘制在交叉验证期间使用的所有测试集的平均信息系数。 我们再次看到,正则化可以提高 IC 超出不受约束模型,以λ=10^(-5)为水平获得最佳的样本外结果。 最优的正则化值与岭回归不同,因为惩罚是相对较小的系数值的绝对值之和,而不是平方值。 我们还可以看到,对于这个正则化水平,系数已经类似地收缩,就像岭回归案例中一样:

交叉验证信息系数和套索路径

总的来说,岭回归和套索将产生类似的结果。 岭回归通常计算速度更快,但套索也通过逐渐将系数减小到零来产生连续的特征子集选择,从而消除了特征。

线性分类

到目前为止讨论的线性回归模型假设定量响应变量。 在本节中,我们将重点讨论对推断和预测建模定性输出变量的方法,这个过程被称为分类,在实践中甚至比回归更频繁地发生。

针对数据点预测定性响应被称为分类该观察,因为它涉及将观察分配到一个类别或类别。 在实践中,分类方法通常为定性变量的每个类别预测概率,然后使用此概率来决定适当的分类。

我们可以忽略输出变量取离散值的事实,并应用线性回归模型尝试使用多个输入变量来预测分类输出。然而,很容易构造出这种方法表现非常糟糕的例子。 此外,当我们知道*y ∈ [0, 1]*时,模型产生大于 1 或小于 0 的值并不直观。

有许多不同的分类技术或分类器可用于预测定性响应。 在本节中,我们将介绍广泛使用的逻辑回归,它与线性回归密切相关。 在接下来的章节中,我们将介绍更复杂的方法,包括决策树和随机森林的广义可加模型,以及梯度提升机和神经网络。

逻辑回归模型

逻辑回归模型源于希望对输出类别的概率建模,给定一个在x中线性的函数,就像线性回归模型一样,同时确保它们总和为一,并保持在[0, 1],正如我们从概率期望的那样。

在本节中,我们介绍逻辑回归模型的目标和函数形式,并描述培训方法。 然后,我们说明如何使用 statsmodels 对宏观数据进行统计推断,并使用 sklearn 实现的正则化逻辑回归来预测价格走势。

目标函数

为了说明,我们将使用输出变量 y,如果在给定时间跨度 d 内股票收益为正,则取值为 1,否则为 0:

我们可以轻松地将 y 扩展到三个类别,其中 0 和 2 反映超过某个阈值的负面和正面价格变动,否则为 1。 然而,与其对y建模,逻辑回归模型对y属于给定一向量α因子或特征的类别的概率进行建模。 换句话说,逻辑回归模型了解包含在模型中的变量值时,股票价格上涨的概率:

逻辑函数

为了防止模型生成超出 [0, 1] 区间的值,我们必须使用一个只在整个 x 的定义域上产生 0 和 1 之间输出的函数来对 p(x) 建模。逻辑函数满足这一要求,并且始终产生一个 S 形曲线(见笔记本示例),因此,无论 X 的值如何,我们都将得到一个合理的预测:

在这里,向量 x 包括一个由 第一个分量捕获的截距,。我们可以转换此表达式以隔离类似线性回归的部分,得到:

p(x)/[1−p(x)] 称为 几率,是表达概率的另一种方式,可能与赌博中熟悉的方式相似,可以取 0 到 ∞ 之间的任意值,其中较低的值也意味着较低的概率,而较高的值意味着较高的概率。

对数几率也称为对数几率(因为它是几率的对数)。因此,逻辑回归表示的对数线性回归 x 看起来与前述的线性回归非常相似。

最大似然估计

系数向量 必须使用可用的训练数据进行估计。虽然我们可以使用(非线性)最小二乘法来拟合 logistic 回归模型,但更一般的最大似然方法更受欢迎,因为它具有更好的统计特性。正如我们刚刚讨论的,使用最大似然拟合 logistic 回归模型的基本直觉是寻找估计值 ,使得预测的概率 尽可能接近实际结果。换句话说,我们试图找到 这样这些估计值在股价上涨的所有情况下都接近 1,否则接近 0 的情况。更正式地说,我们正在寻求最大化似然函数:

使用总和比乘积更容易,因此让我们两边都取对数,得到对数似然函数和 logistic 回归系数的相应定义:

通过将  对  的导数设为零来通过最大化此方程获得 p+1 个所谓的得分方程,在参数中是非线性的,可以使用迭代数值方法来解决凹的对数似然函数。

如何使用 statsmodels 进行推理

我们将演示如何使用 statsmodels 进行逻辑回归,基于一个包含从 1959 年至 2009 年的季度美国宏观数据的简单内置数据集(详见笔记本 logistic_regression_macro_data.ipynb)。

变量及其转换列于以下表格中:

变量描述转换
realgdp实际国内生产总值年增长率
realcons实际个人消费支出年增长率
realinv实际私人国内投资总额年增长率
realgovt实际联邦支出和总投资年增长率
realdpi实际私人可支配收入年增长率
m1M1 名义货币存量年增长率
tbilrate月度 3 个国库券利率水平
unemp季节性调整后的失业率(%)水平
infl通货膨胀率水平
realint实际利率水平

为了得到一个二元目标变量,我们计算季度实际 GDP 年增长率的 20 季度滚动平均值。然后,如果当前增长超过移动平均值,则分配 1,否则分配 0。最后,我们将指标变量移位,以使下一季度的结果与当前季度对齐。

我们使用一个截距,并将季度值转换为虚拟变量,并按以下方式训练逻辑回归模型:

import statsmodels.api as sm

data = pd.get_dummies(data.drop(drop_cols, axis=1), columns=['quarter'], drop_first=True).dropna()
model = sm.Logit(data.target, sm.add_constant(data.drop('target', axis=1)))
result = model.fit()
result.summary()

这产生了我们的模型摘要,其中包含截距的 198 个观测值和 13 个变量:

逻辑回归结果

摘要显示,该模型已使用最大似然进行训练,并在 -67.9 处提供了对数似然函数的最大化值。

LL-Null 值为 -136.42 是只包括截距时对数似然函数的最大化结果。它形成了伪 R² 统计量和 Log-似然比LLR)测试的基础。

伪 R^(2 ) 统计量是最小二乘法下可用的熟悉 R² 的替代品。它是基于空模型 m[0] 和完整模型 m[1] 的最大化对数似然函数的比率计算的,如下所示:

值从 0 变化(当模型不改善似然时)到 1(模型完美拟合时)且对数似然在 0 处最大化。因此,较高的值表明拟合效果更好。

LLR 测试通常比较一个更受限制的模型,并计算如下:

原假设是受限制模型的性能更好,但低的 p 值表明我们可以拒绝这一假设,并更喜欢完整模型而不是空模型。这类似于线性回归的 F 检验(当我们使用 MLE 估计模型时,也可以使用 LLR 测试)。

Z 统计量在线性回归输出中起着与 t 统计量相同的作用,并且与系数估计和其标准误差的比率一样计算。p 值还指示了在假设 H[0]:β = 0(总体系数为零)的情况下观察到测试统计量的概率。我们可以拒绝这个假设,对于截距realconsrealinvrealgovtrealdpiunemp

如何使用逻辑回归进行预测

套索 L[1] 惩罚和岭 L[2] 惩罚都可以与逻辑回归一起使用。它们具有我们刚刚讨论的相同收缩效应,而套索可以再次用于任何线性回归模型的变量选择。

与线性回归一样,对输入变量进行标准化非常重要,因为正则化模型对比例敏感。正则化超参数还需要使用交叉验证进行调整,就像线性回归的情况一样。

如何使用 sklearn 预测价格变动

我们继续价格预测示例,但现在我们将结果变量二值化,以便在 10 天回报为正时取值为 1,否则为 0;请参阅子目录 stock_price_prediction 中的笔记本 logistic_regression.ipynb

target = 'Returns10D'
label = (y[target] > 0).astype(int).to_frame(target)

有了这个新的分类结果变量,我们现在可以使用默认的 L[2]正则化训练逻辑回归。对于逻辑回归,正则化与线性回归相反:λ的值越高,正则化越少,反之亦然。我们使用交叉验证评估 11 个参数值如下:

nfolds = 250
Cs = np.logspace(-5, 5, 11)
scaler = StandardScaler()

logistic_results, logistic_coeffs = pd.DataFrame(), pd.DataFrame()
for C in Cs:
    coeffs = []
    log_reg = LogisticRegression(C=C)
    for i, (train_dates, test_dates) in enumerate(time_series_split(dates, nfolds=nfolds)):
        X_train = model_data.loc[idx[train_dates], features]
        y_train = model_data.loc[idx[train_dates], target]
        log_reg.fit(X=scaler.fit_transform(X_train), y=y_train)

        X_test = model_data.loc[idx[test_dates], features]
        y_test = model_data.loc[idx[test_dates], target]
        y_pred = log_reg.predict_proba(scaler.transform(X_test))[:, 1]

        coeffs.append(log_reg.coef_.squeeze())
        logistic_results = (logistic_results
                            .append(y_test
                                    .to_frame('actuals')
                                    .assign(predicted=y_pred, C=C)))
    logistic_coeffs[C] = np.mean(coeffs, axis=0)

然后我们使用前一章讨论的 roc_auc_score 来比较各种正则化参数的预测准确度:

auc_by_C = logistic_results.groupby('C').apply(lambda x: roc_auc_score(y_true=x.actuals.astype(int), 
                                                         y_score=x.predicted))

我们可以再次绘制 AUC 结果,以显示超参数值范围以及系数路径,该路径显示系数在最佳正则化值 10² 处略微收缩时预测精度的提高:

AUC 和 Logistic Ridge 路径

摘要

在本章中,我们介绍了使用线性模型进行回归和分类的重要基线案例的第一个机器学习模型。我们探讨了两个任务的目标函数的制定,学习了各种训练方法,并学习了如何将模型用于推理和预测。

我们将这些新的机器学习技术应用于估计线性因子模型,这些模型对于管理风险、评估新的阿尔法因子和归因绩效非常有用。我们还应用线性回归和分类来完成第一个预测任务,即在绝对和方向性方面预测股票收益。

在下一章中,我们将讨论重要的线性时间序列模型主题,这些模型旨在捕捉单变量和多变量情况下的串行相关模式。我们还将学习关于新的交易策略,因为我们将探讨基于协整概念的配对交易,该概念捕捉了两个股价序列之间的动态相关性。