行为学原理反映了投资者对市场新闻反应不足(Hong, Lim, and Stein, 2000)和反应过度(Barberis, Shleifer, and Vishny, 1998)的偏见,因为投资者处理新信息的速度不同。在最初对新闻反应不足的情况下,投资者往往会推断出过去的行为并创造出价格动量。90年代末市场泡沫期间科技股的反弹就是一个极端的例子。恐惧和贪婪的心理也促使投资者增加对盈利资产的投资,并继续出售亏损的资产(Jegadeesh和Titman,2011)。
在理性、有效市场观点中,价值溢价补偿了较高的实际或感知风险。研究人员提出的证据表明,价值型公司在适应不利的经济环境方面的灵活性不如精干和更灵活的成长型公司。或者价值股的风险与高财务杠杆和更不确定的未来收益有关。价值型和小盘股投资组合也被证明对宏观冲击比成长型和大盘股投资组合更敏感(Lakonishok, Shleifer, and Vishny, 1994)。
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
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)
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)
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
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)
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']])
第4章 金融特征工程——如何研究阿尔法因子
算法交易策略是由信号驱动的,这些信号指示何时买入或卖出资产,以产生相对于基准(如指数)的卓越收益。一项资产的收益中没有被这个基准解释的部分被称为阿尔法,因此,旨在产生这种不相关的收益的信号也被称为阿尔法因子。
如果你已经熟悉机器学习,你可能知道,特征工程是成功预测的一个关键因素。这在交易中也不例外。然而,数十年来,投资领域对市场如何运作,以及在解释或预测价格走势方面,哪些特征可能比其他特征更有效的研究尤其丰富。本章提供了一个概述,作为你自己寻找阿尔法因子的起点。
我们还将预览如何使用交易模拟器Zipline来评估(传统)阿尔法因子的预测性能。我们将讨论关键的阿尔法因子指标,如信息系数和因子周转率。在第6章 "机器学习过程 "中,我们将深入介绍使用机器学习的交易策略的回测,其中包括我们在本书中用来评估交易策略的ML4T工作流程。
特别是,本章将讨论以下主题:
你可以在GitHub资源库的相应目录中找到本章的代码示例和其他资源的链接。笔记本包括彩 {MOD}版本的图片。附录,阿尔法因子库,包含了关于金融特征工程的额外信息,包括100多个工作实例,你可以在自己的策略中加以利用。
4.1 阿尔法因子在实践中——从数据到信号
阿尔法因子是对原始数据的转化,旨在预测资产价格走势。它们被设计用来捕捉驱动资产收益的风险。一个因子可以结合一个或几个输入,但每次策略评估因子以获得信号时,都会为每个资产输出一个单一数值。交易决策可能依赖于不同资产的相对因子值或单一资产的模式。
阿尔法因子的设计、评估和组合是算法交易策略工作流研究阶段的关键步骤,如图 4.1 所示:
本章的重点是研究阶段;下一章则是执行阶段。然后,本书的其余部分将集中讨论如何利用机器学习从数据中学习新的因子,并有效地汇总来自多个阿尔法因子的信号。
阿尔法因子是包含预测信号的市场、基本面和非传统数据的转换。一些因子描述了基本的整个经济的变量,如增长、通货膨胀、波动性、生产力和人口风险。其他因子代表投资风格,如价值或增长,以及可以交易的动量投资,因此由市场定价。还有一些因子可以解释基于金融市场的经济或制度环境或投资者行为(包括这种行为的已知偏差)的价格变动。
因子背后的经济理论可以是理性的,因此从长远来看,因子具有高收益,以弥补其在经济不景气时期的低收益。它也可以是行为性的,其中因子风险溢价是由于代理人可能偏颇或不完全理性的行为造成的,这些行为没有被套利。
人们不断寻找和发现新的因子,这些因子可以更好地捕捉已知的或反映新的收益驱动因子。管理着近2000亿美元的Research Affiliates的联合创始人Jason Hsu确定了大约250个因子,这些因子在2015年之前已在知名期刊上发表了经验证据。他估计这个数字可能会以每年40个因子增长。
为了避免错误发现并确保因子提供一致的结果,它应该具有基于各种既定因子类别(如动量,价值,波动性或质量)及其基本原理的有意义的经济直觉,我们将在下一节中概述。这使得该因子反映市场可以补偿的风险变得更加合理。
阿尔法因子是通过对原始市场、基本面或非传统数据进行转换而产生的。使用简单的算术,如变量在一段时间内的绝对或相对变化,数据序列之间的比率,或时间窗口上的聚合(如简单或指数移动平均)。它们还包括从价格和成交量模式的技术分析中出现的指标,如需求与供应的相对强度指数和证券基本面分析中熟悉的众多指标。Kakushadze(2016)列出了101个阿尔法因子的公式,在撰写本文时,其中80%被WorldQuant对冲基金用于生产。
对预测高于市场收益的阿尔法因子的现代研究是由Eugene Fama(获得2013年诺贝尔经济学奖)和Kenneth French领导的,他们提供了关于规模和价值因子的证据(1993)。这项工作导致了三因子和五因子模型,我们将在第七章 "线性模型——从风险因子到收益预测 "中讨论这些模型,使用作者在其网站上提供的因子收益的每日数据。Andrew Ang(2014年)对现代因子投资做了一个很好的、更近期的概述,他是BlackRock公司这一学科的负责人,该公司管理着接近7万亿美元的资金。
正如我们将在本书中看到的那样,机器学习已被证明在学习直接从更多样化,更大的输入数据集中提取信号方面非常有效,而无需使用规定的公式。然而,正如我们也将看到的那样,阿尔法因子仍然是机器学习模型的有用输入,机器学习模型以比手动设置规则更优化的方式组合其信息内容。
因此,今天的算法交易策略利用了大量的信号,其中许多信号可能单独是较弱的,但当与其他模型驱动的或传统的因子结合在一起时,通过机器学习算法可以产生可靠的预测。
4.2 基于几十年的因子研究
在理想化的世界中,风险因子应该彼此独立,产生正的风险溢价,并形成一个完整的集合,涵盖风险的所有维度,并解释特定类别中资产的系统性风险。在实践中,这些要求仅近似地存在,并且不同因子之间存在重要的相关性。例如,小公司的动量往往更强(Hou,Xue和Zhang,2015)。我们将在第13章“数据驱动的风险因子和无监督学习的资产分配”中展示如何使用无监督学习(特别是主成分分析)来推导合成的、数据驱动的风险因子。
在本节中,我们将回顾金融研究和交易应用中突出的几个关键因子类别,解释它们的经济原理,并介绍通常用于捕获这些收益驱动因子的指标。
在下一节中,我们将演示如何使用NumPy库和pandas库实现其中一些因子,使用TA-Lib库进行技术分析,并演示如何使用Zipline回测库评估因子。我们还将重点介绍Zipline中内置的一些因子,这些因子在Quantopian平台上可用。
4.2.1 动量和情绪——趋势是你的朋友
动量投资是最成熟的因子策略之一,自Jegadeesh和Titman(1993年)以来,美国股票市场的量化证据为其提供了支持。它遵循的格言是:趋势是你的朋友,或者让你的赢家运行。动量因子的目的是在表现良好的资产上做多,而在一定时期内表现不佳的资产上做空。2000亿美元对冲基金AQR的创始人Clifford Asness,最近提出了八个不同资产类别和市场的动量效应的证据(Asness, Moskowitz, and Pedersen, 2013)。
使用此因子策略的前提是资产价格表现出趋势,反映在序列正相关性中。这种价格动量违背了有效市场的假设,该假设认为仅靠过去的价格收益无法预测未来的表现。尽管理论上有相反的论点,但价格动量策略在各种资产类别中产生了正收益,并且是许多交易策略的重要组成部分。
图4.2中的图表显示了根据对各种阿尔法因子的暴露而形成的投资组合的历史表现(使用来自Fama-French网站的数据)。因子赢家减去输家(Winner Minus Loser,WML)代表了包含美国股票的投资组合在前2-12个月的收益率中分别处于最高和最低的三个分位数的表现差异。
在2008年危机之前,动量因子的表现大大超过了其他突出的风险因子。其他因子包括高-低(High-Minus-Low,HML)价值因子,强-弱(Robust-Minus-Weak,RMW)盈利能力因子,以及保守-激进(Conservative-Minus-Aggressive,CMA)投资因子。股权溢价是市场收益率(例如,标准普尔500指数)与无风险利率之间的差额。
4.2.1.1 为什么动量和情绪会可能推动超额收益?
动量效应的原因指向投资者行为,持续的供需失衡,风险资产与经济之间的正反馈循环或市场微观结构。
行为学原理反映了投资者对市场新闻反应不足(Hong, Lim, and Stein, 2000)和反应过度(Barberis, Shleifer, and Vishny, 1998)的偏见,因为投资者处理新信息的速度不同。在最初对新闻反应不足的情况下,投资者往往会推断出过去的行为并创造出价格动量。90年代末市场泡沫期间科技股的反弹就是一个极端的例子。恐惧和贪婪的心理也促使投资者增加对盈利资产的投资,并继续出售亏损的资产(Jegadeesh和Titman,2011)。
动量也可以有基本面驱动力,如风险资产和经济之间的正反馈循环。经济增长推动了股票,由此产生的财富效应通过增加支出反馈到经济中,再次推动了增长。价格和经济之间的正反馈往往使股票和信贷的动量比债券、FOEX和大宗商品的动量更长,因为后者的负反馈会产生逆转,需要更短的投资期限。动量的另一个原因可能是市场摩擦导致的持续的供需失衡。一个例子是商品生产在适应需求变化方面的延迟。石油生产可能会滞后于经济繁荣带来的更高需求多年,持续的供应短缺会引发并支持价格上涨的动量(Novy-Marx,2015)。
在较短的日内范围内,市场微观结构效应也可以创造价格动能,因为投资者实施模仿其偏见的策略。例如,投资者使用交易策略,如止损,恒定比例投资组合保险(Constant Proportion Portfolio Insurance,CPPI),动态增量对冲或基于期权的策略,如保护性看跌。这些策略创造了动量,因为它们意味着在资产表现不佳时卖出,并在表现优异时买入。
同样的,风险平价策略(见下一章)倾向于买入通常表现为正的低波动率资产,卖出通常表现为负的高波动率资产(见本章后面的波动率和规模异常部分)。使用这些策略的投资组合的自动再平衡倾向于加强价格动量。
4.2.1.2 如何衡量动量和情绪
动量因子通常是通过识别趋势和模式从价格时间序列的变化中得出的。它们可以通过比较资产的横截面或分析资产的时间序列,在传统资产类别内或跨传统资产类别,以及在不同的时间范围内,基于绝对或相对收益来构建。
下表列出了几个常用的说明性指标(公式见附录):
其他情绪指标包括以下指标;像分析师估计这样的输入可以从Quandl或Bloomberg等数据提供者那里获得,其中包括:
还有许多数据提供者旨在提供从社交媒体构建的情绪指标,例如Twitter。我们将在本书的第3部分中使用自然语言处理创建自己的情绪指标。
4.2.2 价值因子——猎取基本面的廉价货
价格相对于其基本价值较低的股票,往往会带来超过市值加权基准的收益。价值因子反映了这种相关性,旨在为相对便宜的被低估的资产发出买入信号,为被高估的资产发出卖出信号。因此,任何价值策略的核心是一个估计资产公允或基本价值的模型。公允价值可以被定义为一个绝对的价格水平,相对于其他资产的价差,或者一个资产应该交易的范围。
4.2.2.1 相对价值策略
价值策略依赖于价格向资产公允价值的均值回归。他们认为,由于过度反应或羊群效应等行为因子,或流动性效应(如暂时的市场影响或长期的供需摩擦),价格只会暂时偏离公允价值。价值因子往往表现出与动量因子相反的属性,因为它们依赖于均值回归。对于股票来说,价值型股票的反面是由于增长预期而具有高估值的增长型股票。
价值因子使一系列广泛的系统策略成为可能,包括基本面和市场估值以及跨资产相对价值。它们通常被统称为统计套利(Statistical Arbitrage,StatArb)策略,作为市场中性的多/空投资组合来实施,不接触其他传统或非传统风险因子。
4.2.2.2 基本面价值策略
基本面价值策略从取决于目标资产类别的经济和基本面指标中得出公允资产价值。在固定收益,货币和大宗商品中,指标包括资本账户余额,经济活动,通货膨胀或资金流动的水平和变化。对于股票和企业信贷,价值因子可以追溯到Graham和Dodd之前提到的《证券分析》。股权价值方法将股票价格与基本指标(如账面价值、销售收入、底线收益或各种现金流指标)进行比较。
4.2.2.3 市场价值策略
市场价值策略使用统计或机器学习模型来识别由于流动性提供的低效率而导致的错误定价。统计套利和指数套利是突出的例子,它们可以在短时间内捕捉到临时市场影响的回调。(我们将在第9章 "用于波动率预测和统计套利的时间序列模型 "中介绍配对交易。) 在较长的时间范围内,市场价值交易也利用了股票和大宗商品的季节性效应。
4.2.2.4 跨资产相对价值策略
跨资产相对价值策略侧重于跨资产类别的错误定价。例如,可转换债券套利涉及可转为股票的债券与单一公司的相关股票之间的相对价值交易。相对价值策略还包括信贷和股票波动之间的交易,利用信贷信号来交易股票或商品和相关股票之间的交易。
4.2.2.5 为什么价值因子有助于预测收益?
对于价值效应的存在,既有理性的解释,也有行为上的解释,价值效应被定义为价值型股票组合相对于成长型股票组合的超额收益,其中前者的市场价值较低,后者相对于基本面的市场价值较高。我们将从大量的研究中举出几个突出的例子(例如,见Fama和French,1998,以及Asness、Moskowitz和Pedersen,2013)。
在理性、有效市场观点中,价值溢价补偿了较高的实际或感知风险。研究人员提出的证据表明,价值型公司在适应不利的经济环境方面的灵活性不如精干和更灵活的成长型公司。或者价值股的风险与高财务杠杆和更不确定的未来收益有关。价值型和小盘股投资组合也被证明对宏观冲击比成长型和大盘股投资组合更敏感(Lakonishok, Shleifer, and Vishny, 1994)。
从行为学的角度来看,价值溢价可以用损失厌恶和心理账户偏差来解释。投资者可能不太关心近期表现强劲的资产的损失,因为之前的收益提供了缓冲。这种损失规避偏见促使投资者认为股票的风险比以前小,并以较低的利率折算其未来现金流。反之,近期表现不佳可能导致投资者提高资产的贴现率。
这些不同的收益预期会产生价值溢价:相对于基本面而言价格较高的成长股在过去表现良好,但由于投资者对低风险的偏见,他们会要求未来的平均收益率较低,而价值股的情况则相反。
4.2.2.6 如何捕捉价值效应
从基本面数据中计算出了大量的估值代理。这些因子可以结合起来作为机器学习估值模型的输入,以预测资产价格。下面的例子适用于股票,我们将在下面的章节中看到这些因子中的一些是如何使用的。
第2章“市场和基础数据——来源和技术”,讨论了如何从公司文件中获取用于计算这些指标的基本面数据。
4.2.3 波动率和规模异常
规模效应是较早的风险因子之一,与低市值的股票的超额表现有关(见本节开头的图4.2)。最近,低波动率因子被证明可以捕捉到波动率、贝塔值或特异性风险低于平均水平的股票的超额收益。市值较大的股票往往具有较低的波动率,因此传统的规模因子往往与较新的波动率因子相结合。
低波动性异常是一个与金融基本原理相悖的经验难题。资本资产定价模型(Capital Asset Pricing Model,CAPM)和其他资产定价模型断言,风险越高,收益越高(我们将在下一章中详细讨论),但在众多市场和长期内,情况恰恰相反,风险较小的资产表现优于风险较高的同类产品。
图4.3是1990-2019年标准普尔500指数的滚动平均值与VIX指数的对比图,VIX指数是衡量标准普尔100指数的价内期权的隐含波动率。它说明了股票收益率和这个衡量波动率的指标在这一时期是如何逆向移动的,其负相关度为-0.54。除了这种总体效应外,还有证据表明,对VIX指数变化更敏感的股票表现更差(Ang等人,2006)。
4.2.3.1 为什么波动率和规模可以预测收益?
低波动率异常现象与有效市场假说和CAPM的假设相矛盾。为了解释它的存在,人们提出了一些行为学的解释。
彩票效应建立在经验证据之上,即个人接受类似于彩票的赌注,预期损失小,但潜在赢面大,即使这种大赢家可能有相当低的概率。如果投资者认为低价、波动大的股票的风险收益状况就像彩票,那么它就可能是一个有吸引力的赌注。因此,由于投资者的偏向性偏好,他们可能会对高波动性股票支付过高的价格,而对低波动性股票支付过低的价格。
代表性偏差表明,投资者将少数广为人知的波动性股票的成功推断为所有波动性股票,而忽略了这类股票的投机性。
投资者也可能对自己预测未来的能力过于自信,对于结果更不确定的波动性股票,他们的意见分歧更大。由于做多(即拥有资产)比做空更容易表达积极观点,乐观主义者的数量可能会超过悲观主义者,并继续推高波动股票的价格,导致收益率下降。
此外,投资者在牛市和危机期间的行为是不同的。在牛市期间,贝塔值的分散度要低得多,因此低波动率股票的表现不会太差,如果有的话。而在危机期间,投资者寻求或保留低波动率股票,贝塔值的分散度增加。因此,从长期来看,低波动率的资产和投资组合表现更好。
4.2.3.2 如何衡量波动性和规模
用来识别低波动率股票的指标涵盖了一个广泛的范围,一端是实现波动率(标准差),另一端是预测(隐含波动率)和相关性。有些人将低波动率操作为低贝塔。支持波动率异常的证据对于不同的指标来说似乎都很有力(Ang,2014)。
4.2.4 量化投资的质量因子
质量因子的目的是捕捉那些盈利能力强、运营效率高、安全、稳定和管理良好的公司所获得的超额收益,简而言之就是高质量。市场似乎还奖励相对收益的确定性,并惩罚收益波动大的股票。
长期以来,依赖基本面分析的选股者一直主张投资组合向高质量企业倾斜,但在量化投资中这是一种相对较新的现象。鉴于质量的主观性,主要的挑战是如何使用定量指标一致和客观地定义质量因子。
基于独立的质量因子的策略往往以反周期的方式表现出来,因为投资者支付溢价以尽量减少下行风险并推高估值。出于这个原因,质量因子经常与其他风险因子结合在一个多因子策略中,最常见的是与价值结合,以产生合理价格的质量策略。
多空质量因子往往具有负的市场贝塔值,因为它们是做多也是低波动性的优质股票,并做空波动性更大的低质量股票。因此,质量因子往往与低波动性和动量因子正相关,而与价值和广泛的市场暴露负相关。
4.2.4.1 为什么质量是重要的
质量因子可能预示着业绩优异,因为持续盈利能力、现金流稳定增长、审慎杠杆、资本市场融资需求低或财务风险低等优越的基本面支撑着对股票的需求,并长期支撑着这些公司的价格。从企业融资的角度来看,一家优质公司通常会谨慎管理其资本,并降低过度杠杆或过度资本化的风险。
一种行为解释表明,投资者对质量信息反应不足,类似于动量的基本原理,投资者追逐赢家并出售输家。
质量溢价的另一个论点是羊群效应,类似于成长型股票。基金经理可能会发现,购买一家基本面强劲的公司更容易证明其合理性,即使它变得越来越昂贵,而不是一只波动性更大(风险性)的价值型股票。
4.2.4.2 如何衡量资产质量
质量因子依赖于从资产负债表和利润表中计算出来的指标,这些指标表明了反映在高利润或现金流利润率、运营效率、财务实力和更广泛的竞争力中的盈利能力,因为它意味着能够随着时间推移维持盈利地位的能力。
因此,质量是用毛利润率、投资资本收益率、低收益波动率,或各种利润率、收益质量和杠杆率指标的组合来衡量的。毛利润率最近被加入到Fama–French因子模型中;见第7章“线性模型——从风险因子到收益预测”)。一些选项列在下表中。
盈利管理主要是通过操纵应计项目来进行的。因此,应计项目的规模经常被用作盈利质量的代表:相对于资产而言,应计项目总额越高,盈利质量越低。然而,这并不明确,因为应计项目和对未来业务增长的会计估计一样,都可以反映出收益操纵:
掌握了已被证明与异常收益有不同程度关联的阿尔法因子的高层次分类,我们现在将开始从市场、基本面和非传统数据中开发我们自己的金融特征。
4.3 预测收益的阿尔法因子工程
基于对关键因子类别、其原理和流行指标的概念性理解,一个关键的任务是确定新的因子,这些因子可能会更好地捕捉到之前布置的收益驱动因子所体现的风险。无论哪种情况,都必须将创新因子的表现与已知因子的表现进行比较,以确定增量的信号收益。
促进数据转化为因子的关键工具包括用于数值计算的Python库,NumPy库和pandas库,以及用于技术分析的专门库TA-Lib的Python包装器。替代品包括Zura Kakushadze在2016年的论文《101个阿尔法公式》中开发的阿尔法表达式,并由alphatools库实现。此外,Quantopian平台提供了大量的内置因子,以加快研究过程。
为了将一个或多个因子应用于投资领域,我们可以使用Zipline回测库(也包括一些内置的因子),并使用Alphalens库使用下一节讨论的指标来评估其性能。
4.3.1 如何使用pandas库和NumPy库来设计因子
NumPy库和pandas库是自定义因子计算的关键工具。本节演示如何使用它们来快速计算产生各种阿尔法因子的转换。如果你不熟悉这些库,特别是我们将在本书中使用的pandas库,请参阅GitHub资源库中本章的自述文件,以获取文档和教程的链接。
alpha_factors_in_practice目录下的feature_engineering.ipynb笔记本包含如何创建各种因子的示例。该笔记本使用GitHub资源库根目录下的data文件夹中的create_data.ipynb笔记本生成的数据,这些数据以HDF5格式存储,以便更快地访问。参见GitHub 存储库中第2章目录下的笔记本storage_benchmarks.ipynb,以了解parquet、HDF5和CSV对pandas DataFrames存储格式的比较。
用于科学计算的NumPy库是由Travis Oliphant在2005年创建的,它整合了自90年代中期以来开发的老的Numeric和Numarray库。它被组织在一个叫做ndarray的高性能n维数组数据结构中,实现了与MATLAB相媲美的功能。
pandas库是在2008年出现的,当时Wes McKinney在AQR资本管理公司工作。它提供了DataFrame数据结构,该结构基于NumPy的ndarray,但允许使用基于标签的索引进行更友好的数据操作。它包括一系列的计算工具,特别适合于金融数据,包括丰富的时间序列操作和自动日期对齐,我们将在此探讨。
下面的章节说明了将原始股票价格数据转化为选定因子的一些步骤。参见笔记本feature_engineering.ipynb,以了解更多的细节和可视化,我们在这里省略了一些内容,以节省空间。关于如何使用pandas库和NumPy库的文档和教程的链接,请参见GitHub上本章的README中所列的资源。
4.3.1.1 加载、切分和重塑数据
在加载美国股票的Quandl Wiki股价数据后,我们通过将pd.IndexSlice应用于pd.MultiIndex来选择2000-18年的时间片,该时间片包含时间戳和股票信息。然后,我们使用.stack()方法选择并取消调整后的收盘价列,将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
4.3.1.2 重新采样——从每天到每月的频率
为了减少训练时间和实验更长的时间范围的策略,我们使用可用的调整后的收盘价将业务日数据转换成月末频率。
monthly_prices = prices.resample('M').last()
4.3.1.3 如何计算多个历史时期的收益
为了捕捉像动量模式这样的时间序列动态,我们使用pct_change(n_periods)方法计算历史上的多期收益,其中n_periods确定滞后期的数量。然后,我们使用.stack()将宽格式的结果转换为长格式,使用.pipe()将.clip()方法应用于生成的DataFrame,并在[1%,99%]水平上对收益进行缩尾调整(winsorize);也就是说,我们将异常值限制在这些百分位数。
最后,我们使用几何平均数对收益进行归一化。在使用.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
我们可以使用这些结果,根据较长时期收益率与最近月度收益率之间的差异,以及3个月和12个月收益率之间的差异来计算动量因子,如下所示:
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)
4.3.1.4 利用滞后收益和不同的持有期
为了使用滞后值作为输入变量或与当前观测值相关的特征,我们使用.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))
该笔记本还演示了如何计算不同收益序列的各种描述性统计,并使用seaborn库可视化其相关性。
4.3.1.5 计算因子贝塔
我们将在第7章 "线性模型——从风险因子到收益预测 "中介绍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))
如前所述,我们将在第7章“线性模型——从风险因子到收益预测”中更详细地探讨Fama-French因子模型和线性回归。有关其他示例,请参阅笔记本feature_engineering.ipynb,包括计算滞后和前向收益。
4.3.1.6如何添加动量因子
我们可以使用1个月和3个月的结果来计算简单的动量因子。下面的代码例子显示了如何计算较长时期的收益率和最近的月度收益率之间的差异,以及对于3个月和12个月的收益率之间的差异:
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)
4.3.1.7 添加时间指标以捕捉季节性影响
基本因子还包括季节性的异常,如1月效应,据观察1月的股票收益率较高,可能是出于税收原因。这和其他季节性效应可以通过代表特定时间段(如1年和/或1个月)的指标变量进行建模。这些变量可以产生如下:
dates = data.index.get_level_values('date') data['year'] = dates.year data['month'] = dates.month
4.3.1.8 如何构造滞后收益率特征
如果想使用滞后的收益率,也就是以前时期的收益率作为输入变量或特征来训练一个学习收益率模式的模型来预测未来的收益率,你可以使用.shift()方法将历史收益率向上移动到当前时期。下面的例子将1到6个月前的收益率向上移动了相应的滞后期,这样它们就与当前月份的观察值相关联了。
for t in range(1, 7): data[f'return_1m_t-{t}'] = data.groupby(level='ticker').return_1m.shift(t)
4.3.1.9 如何构造前向收益率特征
同样,你可以使用带有负周期的.shift(),为当前期间创建前向收益,即将来将发生的收益(假设您的数据按升序排序):
for t in [1,2,3,6,12]: data[f'target_{t}m'] = (data.groupby(level='ticker')[f'return_{t}m'].shift(-t))
从第6章 "机器学习过程 "开始,当我们训练机器学习模型时,我们将使用前向收益。
4.3.2 如何使用TA-Lib库创建技术阿尔法因子
TA-Lib是一个用C++编写的开源库,带有Python接口,被交易软件开发者广泛使用。它包含200多个流行的技术分析指标的标准化实现;也就是说,这些指标只使用市场数据,即价格和成交量信息。
TA-Lib库与pandas库和NumPy库兼容,使其使用非常简单。下面的例子展示了如何计算两个流行的指标。
布林带由一个简单移动平均线(Simple moving average,SMA)组成,在之下和之上环绕着两条滚动标准差的条线。它的出现是为了当价格分别在两个条线的上侧或下侧以外的地方跌出时,对潜在的超买/超卖情况进行可视化。发明者约翰·布林格实际上推荐了一个由22条规则组成的交易系统,以产生交易信号。
我们可以计算布林线,为了比较,本节前面描述的关于流行的阿尔法因子的相对强度指数如下。
我们加载单个股票的调整后收盘价——在本例中为 AAPL:
with pd.HDFStore(DATA_STORE) as store: data = (store['quandl/wiki/prices'] .loc[idx['2007':'2010', 'AAPL'], ['adj_open', 'adj_high', 'adj_low', 'adj_close','adj_volume']] .unstack('ticker') .swaplevel(axis=1) .loc[:, 'AAPL'] .rename(columns=lambda x: x.replace('adj_', '')))
然后,我们将一维的pd.Series通过相关的TA-Lib函数:
from talib import RSI, BBANDS up, mid, low = BBANDS(data.close, timeperiod=21, nbdevup=2, nbdevdn=2, matype=0) rsi = RSI(adj_close, timeperiod=14)
然后,我们在DataFrame中收集结果,并将布林线与AAPL股价和RSI与30/70线绘制在一起,这表明多头/空头机会:
data = pd.DataFrame({'AAPL': data.close, 'BB Up': up, 'BB Mid': mid, 'BB down': low, 'RSI': rsi}) fig, axes= plt.subplots(nrows=2, figsize=(15, 8)) data.drop('RSI', axis=1).plot(ax=axes[0], lw=1, title='Bollinger Bands') data['RSI'].plot(ax=axes[1], lw=1, title='Relative Strength Index') axes[1].axhline(70, lw=1, ls='--', c='k') axes[1].axhline(30, lw=1, ls='--', c='k')
结果如图4.4所示,相当混杂——这两个指标都表明在危机后的早期复苏期间,当价格继续上涨时,出现了超买情况:
4.3.3 使用卡尔曼滤波降噪阿尔法因子
数据中的噪声概念与信号处理领域有关,它旨在从发送的信号中检索正确的信息,例如,以电磁波的形式通过空气。当电磁波在空间中移动时,环境干扰可以以噪声的形式加入到原本纯净的信号中,使得有必要在接收后将两者分开。
卡尔曼滤波器于1960年问世,在许多需要处理噪声数据的应用中变得非常流行,因为它允许对基础信号进行更准确的估计。
这种技术除了在时间序列分析中使用外,还被广泛用于计算机视觉中的物体跟踪,支持飞机和宇宙飞船的定位和导航,以及基于噪声传感器数据控制机器人运动。
降噪同样用于数据科学、金融和其他领域,这意味着原始数据包含有用的信息,例如,就交易信号而言,需要从无关的、外部信息中提取和分离出来。显然,我们不知道真正的信号这一事实有时会使这种分离相当具有挑战性。
我们将首先回顾卡尔曼滤波器是如何工作的,以及它为实现其目标做出了哪些假设。然后,我们将演示如何使用pykalman库将其应用于金融数据。
4.3.3.1 卡尔曼滤波是如何工作的?
卡尔曼滤波器是一个连续数据的动态线性模型,如时间序列,当新信息到来时,它能适应新信息。它不是像移动平均数那样使用一个固定大小的窗口,也不是像指数移动平均数那样使用一组给定的权重,而是根据一个概率模型,将新数据纳入其对时间序列当前值的估计之中。
更具体地说,卡尔曼滤波器是观察序列z_1,z_2,\cdots,z_T和相应的隐藏状态序列x_1,x_2,\cdots,x_T的概率模型(采用我们将在此演示的pykalman库的符号)。这可以用下面的图来表示:
从技术上讲,卡尔曼滤波器采用贝叶斯方法,在给定状态变量x的测量值z的情况下,随时间传播其后验分布(见第10章“贝叶斯机器学习——动态夏普比率和对冲交易”,了解关于贝叶斯推理的更多细节)。我们也可以把它看作是一种无监督的算法,用于跟踪连续状态空间中的单一对象。在这里,我们将把对象看作是,例如,证券的价值或收益,或阿尔法因子(见第13章“数据驱动的风险因子和无监督学习的资产配置”)。
为了从一连串可能实时出现的观测数据中恢复隐藏状态,该算法在两个步骤之间反复进行:
该算法的基本思想如下:关于动态系统的某些假设和相应的测量历史将使我们能够以最大限度地提高先前测量的概率的方式估计系统的状态。
为了达到恢复隐藏状态的目的,卡尔曼滤波做出如下假设:
因此,卡尔曼滤波与隐马尔可夫模型相似,不同之处在于隐藏变量的状态空间是连续的,隐藏变量和观测变量都具有正态分布,记为\mathcal{N}(\mu,\sigma),均值\mu,标准差\sigma。
在数学术语中,模型的关键组成部分(以及pykalman实现中的相应参数)是:
卡尔曼滤波的优点之一是它能灵活地适应具有变化的分布特征的非平稳数据(关于平稳性的更多细节,请参见第9章”用于波动预测和统计套利的时间序列模型“)。
主要的缺点是金融数据经常违反的线性和高斯噪声的假设。为了解决这些缺点,卡尔曼滤波已被扩展到具有非线性动力学系统,其形式是扩展的和无痕的卡尔曼滤波。粒子滤波器是一种替代方法,它使用基于抽样的蒙特卡洛方法来估计非正态分布。
4.3.3.2 如何应用pykalman库使用卡尔曼滤波器
卡尔曼滤波对于随时间变化的数据值或模型参数的滚动估计特别有用。这是因为它在每一个时间步长上都会根据新的观测值来调整其估计值,并倾向于更多地考虑最近的观测值。
除了传统的移动平均数,卡尔曼滤波不要求我们指定用于估计的窗口的长度。相反,我们从对隐藏状态的均值和协方差的估计开始,让卡尔曼滤波器根据周期性观测来修正我们的估计。本节的代码实例在笔记本kalman_filter_and_wavelets.ipynb中。
下面的代码示例展示了如何使用卡尔曼滤波器来平滑2008-09年期间的标准普尔500股票价格序列:
with pd.HDFStore(DATA_STORE) as store: sp500 = store['sp500/stooq'].loc['2008': '2009', 'close']
我们用单位协方差矩阵和零均值初始化卡尔曼滤波器(参见pykalman文档中关于处理选择适当初始值的难题的建议):
from pykalman import KalmanFilter kf = KalmanFilter(transition_matrices = [1], observation_matrices = [1], initial_state_mean = 0, initial_state_covariance = 1, observation_covariance=1, transition_covariance=.01)
然后,我们运行filter方法触发前向算法,该算法迭代估计隐状态,即时间序列的均值:
state_means, _ = kf.filter(sp500)
最后,我们添加移动平均线进行比较,并绘制结果:
sp500_smoothed = sp500.to_frame('close') sp500_smoothed['Kalman Filter'] = state_means for months in [1, 2, 3]: sp500_smoothed[f'MA ({months}m)'] = (sp500.rolling(window=months * 21).mean()) ax = sp500_smoothed.plot(title='Kalman Filter vs Moving Average', figsize=(14, 6), lw=1, rot=0)
4.3.4 任何使用小波处理你的噪声信号
小波与傅里叶分析有关,傅里叶分析将不同频率的正弦波和余弦波结合起来,以逼近噪声信号。傅里叶分析对于将信号从时域转换到频域特别有用,而小波则有助于过滤掉可能出现在不同尺度上的特定模式,而这些模式又可能对应于一个频率范围。
小波是将离散或连续时间信号分解成不同尺度分量的函数或波样模式。小波变换,反过来,表示一个函数使用小波作为有限长度波形的缩放和平移副本。与傅里叶变换相比,这种变换对于具有不连续和尖锐峰值的函数具有优势,并且可以逼近非周期性或非平稳信号。
为了对信号进行去噪,你可以使用小波收缩和阈值处理方法。首先,你选择一个特定的小波模式来分解一个数据集。小波变换产生的系数对应于数据集中的详细信息。
阈值处理的想法是简单地省略所有低于特定截止点的系数,假设它们代表了不需要代表的真实信号的微小细节。然后,这些剩余的系数被用于反小波变换以重建(去噪)数据集。
现在我们将使用pywavelets库,将小波应用于有噪声的股票数据。下面的代码例子说明了如何使用带有Daubechies 6小波和不同阈值的正向和反向小波变换对标普500指数的收益进行去噪。
首先,我们生成2008-09年期间的标普500指数的每日收益率:
signal = (pd.read_hdf(DATA_STORE, 'sp500/stooq') .loc['2008': '2009'] .close.pct_change() .dropna())
然后,我们从众多的内置小波函数中选择一个Daubechies小波:
import pywt pywt.families(short=False) ['Haar', 'Daubechies', 'Symlets', 'Coiflets', 'Biorthogonal', 'Reverse biorthogonal', 'Discrete Meyer (FIR Approximation)', 'Gaussian', 'Mexican hat wavelet', 'Morlet wavelet', 'Complex Gaussian wavelets', 'Shannon wavelets’, 'Frequency B-Spline wavelets', 'Complex Morlet wavelets']
Daubechies 6小波由一个缩放函数\Psi和小波函数\varphi定义(有关详细信息,请参阅PyWavelet文档和附带的笔记本kalman_filter_and_wavelets.ipynb用于所有内置小波函数)。
给定一个小波函数,我们首先使用.wavedec函数对回波信号进行分解,得到小波变换的系数。接下来,我们过滤掉高于给定阈值的所有系数,然后使用逆变换.waverec仅使用这些系数重建信号:
wavelet = "db6" for i, scale in enumerate([.1, .5]): coefficients = pywt.wavedec(signal, wavelet, mode='per') coefficients[1:] = [pywt.threshold(i, value=scale*signal.max(), mode='soft') for i in coefficients[1:]] reconstructed_signal = pywt.waverec(coefficients, wavelet, mode='per') signal.plot(color="b", alpha=0.5, label='original signal', lw=2, title=f'Threshold Scale: {scale:.1f}', ax=axes[i]) pd.Series(reconstructed_signal, index=signal.index).plot(c='k', label='DWT smoothing}', linewidth=1, ax=axes[i])
笔记本展示了如何在不同的阈值下应用这种去噪技术,结果图(如图4.8所示)清楚地显示了更高的阈值如何产生一个明显更平滑的序列:
4.4 从信号到交易——Zipline库回测
开源库Zipline是一个事件驱动的回测系统。它生成市场事件,以模拟算法交易策略的反应,并跟踪其表现。一个特别重要的功能是,它为算法提供了历史时间点数据,以避免前瞻性偏差。
该库已被众包量化投资基金Quantopian推广,该基金在生产中使用它来促进算法开发和实时交易。
在本节中,我们将简要演示它的基本功能。第8章”ML4T工作流——从模型到策略回测“,包含了更详细的介绍,为我们准备更复杂的用例。
4.4.1 如何对单因子策略进行回测
您可以离线使用Zipline库与数据包一起研究和评估阿尔法因子。当在Quantopian平台上使用它时,您将获得更广泛的基本面和非传统数据集。我们还将在本章中演示Quantopian平台的研究环境,并在下一章中演示回测集成开发环境。本节的代码在本章的GitHub资源库文件夹的01_factor_research_evaluation子目录中,包括安装说明和为Zipline库的依赖定制的环境。
关于安装,请参见GitHub上本章README中的说明。安装完成后,在执行第一个算法之前,你需要导入一个数据包,默认情况下,该数据包包括Quandl社区维护的3000家美国上市公司的股票价格、股息和拆分数据。
您需要一个Quandl API密钥来运行以下代码,它将数据存储在您的home文件夹~/.zipline/data/<bundle>:
$ QUANDL_API_KEY=<yourkey> zipline ingest [-b <bundle>]
4.4.1.1 市场数据中的单阿尔法因子
我们将首先说明离线环境中的Zipline阿尔法因子研究工作流程。特别是,我们将开发和测试一个简单的均值回归因子,以衡量近期的表现偏离历史平均水平的程度。
短期反转是一种常见的策略,它利用了股票价格有可能在不到1分钟至1个月的范围内恢复到滚动平均值的弱预测模式。详见笔记本single_factor_zipline.ipynb。
为此,该因子计算最近一个月的收益相对于过去一年的滚动月度收益的z值。此时,我们不会下任何订单,只是说明了CustomFactor的实现,并在模拟过程中记录结果。
Zipline库包括许多常见操作的内置因子(详见GitHub上链接的Quantopian文档)。虽然这通常是方便和充分的,但在其他情况下,我们希望以不同的方式转换可用的数据。为此,Zipline库提供了CustomFactor类,它为我们指定广泛的计算方法提供了很大的灵活性。它通过Numpy使用证券横截面可用的各种特性和自定义回溯期来实现这一点。
为此,在一些基本设置之后,MeanReversion子类化为CustomFactor并定义了一个compute()方法。它在一个同样默认的一年之久的窗口中创建了默认的月度收益率输入,这样月度收益率变量就会在给定的一天为Quandl数据集中的每只证券提供252行和一列。
compute_factors()方法创建一个MeanReversion因子实例,并创建多头、空头和排序管道列。前两者包含可用于下订单的布尔值,后者反映了综合排名,以评价整体因子性能。此外,它使用内置的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()函数将管道的排名列以及当前资产价格添加到run_algorithm()函数返回的性能数据框架中:
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术语定义开始和结束时间戳对象,设置一个资本基数,并执行run_algorithm(),引用关键执行方法。性能DataFrame包含嵌套数据,例如,价格列由每个单元格的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')
我们将在下一节使用存储在性能DataFrame中的因子和定价数据来评估不同持有期的因子表现,但首先,我们将看看如何通过结合Quantopian平台上不同数据源的几个阿尔法因子来创建更复杂的信号。
4.4.1.2 内置Quantopian因子
附带的笔记本factor_library_quantopian.ipynb包含许多示例因子,这些因子要么由Quantopian平台提供,要么使用Jupyter笔记本中使用研究API从可用的数据源计算的。
有一些内置的因子可以与Python定量库——特别是NumPy和pandas——结合使用,从广泛的相关数据源(如美国股票价格、晨星基本面和投资者情绪)得出更复杂的因子。
例如,市盈率可作为晨星基本面数据集的一部分。它可以作为管道的一部分使用,我们将在介绍Zipline库时进一步说明。
4.4.2 组合来自不同数据源的因子
Quantopian的研究环境是为快速测试预测性阿尔法因子而定制的。这个过程非常相似,因为它建立在Zipline的基础上,但提供了更丰富的数据源访问。下面的代码样本说明了如何不仅从市场数据中计算阿尔法因子,就像以前做的那样,而且还从基本面和非传统数据中计算阿尔法因子。详情见笔记本multiple_factors_quantopian_research.ipynb。
Quantopian免费提供了几百个晨星基本变量,还包括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()方法的掩码参数为给定的全局定义计算其他几个因子。
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只包含由管道创建的因子值。这很方便,因为这种数据格式可以作为Alphalens的输入,Alphalens是用于评估阿尔法因子预测性能的库。
在Zipline中使用TA-Lib TA-Lib库包括许多技术因子。一个Python实现可供本地使用,例如与Zipline库和Alphalens库一起使用,它也可在Quantopian平台上使用。该笔记本还说明了使用TA-lib库的几个技术指标。
4.4.3 使用Alphalens库从噪声中分离信号
Quantopian已经开源Python Alphalens库,用于预测性股票因子的性能分析。它与Zipline回测库和投资组合性能和风险分析库pyfolio整合得很好,我们将在下一章进行探讨。
Alphalens库促进了对有关阿尔法因子预测能力的分析:
分析可以使用tearsheet库或单独的计算和绘图来进行。为了节省一些空间,在线存储库中展示了这些tearsheet。
4.4.3.1 创建远期收益和因子分位数
为了利用Alphalens库,我们需要提供两个输入:
详情请参阅笔记本06_performance_eval_alphalens.ipynb。
我们将从single_factor.pickle文件中恢复价格,具体步骤如下(对于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)
我们可以使用get_clean_ factor_and_forward_returns多功能函数,从Zipline输出中以所需格式生成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 DataFrame包含给定日期对给定资产在指定持有期内的投资收益,以及因子值,即该资产在该日期的均值回归排名和相应的分位数值:
远期收益和信号量级是评估信号预测能力的基础。通常情况下,一个因子应该为不同的量级提供明显不同的收益,如因子值的底部五分位数为负收益,顶部五分位数为正收益。
4.4.3.2 按因子分位数预测性能
作为第一步,我们希望按因子分位数可视化平均周期收益。我们可以使用performance模块的内置函数mean_return_by_quantile和plot模块的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);
结果是一个柱状图,它根据因子信号的五分位数来分解四个不同持有期的远期收益的平均值。
正如你在图4.9中所看到的,除了最长的持有期之外,底部的五分位数产生的负面结果明显多于顶部的五分位数:
10D持有期在整个交易期内为第一和第四四分位数提供了略好的结果。
我们还希望看到每个信号五分位数驱动的投资随时间的表现。为此,我们计算5D持有期的每日收益,而不是平均收益。Alphalens调整期间收益,以考虑到每日信号与较长的持有期之间的不匹配(详情请参阅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');
图4.10中的线图显示,在这三年的大部分时间里,前两个五分位数的表现明显优于后两个五分位数。然而,正如前面的图表所显示的,由于它们在2017年期间的相对表现,第四个五分位数的信号产生了比最高五分位数的信号稍好的表现:
一个对交易策略有用的因子显示了前面的模式,即累积收益沿着明显的不同路径发展,因为这允许一个资本要求较低的多空策略,并相应地降低对整个市场的风险。
然而,我们还需要考虑到周期收益的分散性,而不仅仅是平均数。为此,我们可以依靠内置的plot_quantile_returns_violin:
from alphalens.plotting import plot_quantile_returns_violin plot_quantile_returns_violin(mean_return_by_q_daily);
图4.11所示的这一分布图突出表明,日收益率的范围相当广泛。尽管均值不同,但分布的分离非常有限,因此,在任意给定的一天,不同五分位数之间表现的性能差异可能相当有限。
当我们专注于单个阿尔法因子的评估时,我们忽略了与交易执行有关的实际问题,从而简化了事情,当我们在下一章处理适当的回测时,我们将放松这些问题。其中包括:
4.4.3.4 信息系数
本书的大部分内容是关于使用机器学习模型设计阿尔法因子。机器学习是关于优化一些预测目标,在这一节中,我们将介绍用于衡量阿尔法因子性能的关键指标。我们将把阿尔法定义为超过基准的平均收益。
这就产生了信息率(Information ratio,IR),它通过将阿尔法除以跟踪风险来衡量每单位风险承担的平均超额收益。当基准是无风险利率时,信息比率对应着众所周知的夏普比率,我们将强调在典型情况下,当回报非正态分布时,出现的关键统计测量问题。我们还将解释主动管理的基本定律,它将信息比率分解为预测技能和有效利用这些预测技能的战略能力的组合。
阿尔法因子的目标是对未来收益进行准确的方向性预测。因此,一个自然的业绩衡量标准是阿尔法因子的预测与目标资产的远期收益之间的相关性。
最好使用非参数斯皮尔曼等级相关系数,它衡量两个变量之间的关系可以用单调函数描述得多好,而不是皮尔森相关,它衡量线性关系的强度。
我们可以使用Alphalens库获得信息系数(Information coefficient,IC),在底层它依赖于scipy.stats.spearmanr(关于如何直接使用scipy获得p值的例子,见repo)。factor_information_coefficient函数计算了周期性的相关,plot_ic_ts创建了一个带有1个月移动平均值的时间序列图:
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']])
图4.12中的时间序列图显示了移动平均线信息系数明显为正的延长期。如果有足够的机会应用这种预测技巧,0.05甚至0.1的信息系数就可以有明显的超额收益,主动管理的基本规律将说明这一点:
年度平均信息系数的图表强调了该因子的表现在历史上是如何的不平衡:
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))
这就产生了图4.13所示的图表:
在本例中,低于0.05的信息系数虽低但显著,这一点我们将在下一节中看到,可以产生相对于基准的正剩余收益。create_summary_tear_sheet(alphalens_data)命令用于创建信息系数汇总统计信息。
风险调整后的信息系数由平均信息系数除以信息系数的标准差得到,同时使用scipy.stats.ttest_1samp进行零假设IC = 0的双侧t检验。
4.4.3.5 因子周转率
因子周转率衡量的是与特定四分位数相关的资产的变化频率,也就是说,需要多少次交易来调整投资组合以适应信号序列。更具体地说,它衡量的是目前处于某个因子四分位的资产在上一时期不在该四分位的份额。以下表格由这个命令产生:
create_turnover_tear_sheet(alphalens_data)
加入以五分之一为基础的投资组合的资产份额相当高,这表明交易成本对从预测性能中获益构成了挑战:
关于因子周转率的另一个观点是,在不同的持有期,由于该因子在不同持有期间的资产等级之间的相关性,也是tearsheet的一部分。
一般来说,为了更稳定最好保持交易成本可控。
4.5 阿尔法因子资源
研究过程需要根据信号的预测能力来设计和选择阿尔法因子。一个算法交易策略通常会建立在为每个资产发送信号的多个阿尔法因子上。这些因子可以使用机器学习模型进行聚合,以优化各种信号如何转化为关于个头寸的时间和规模的决策,我们将在随后的章节中看到这一点。
4.5.1 非传统算法交易库
其他用于算法交易和数据收集的开源Python库包括以下内容(参见GitHub的链接):
4.6 总结
在这一章中,我们介绍了一系列的阿尔法因子,几十年来,专业投资者一直在使用这些因子来设计和评估策略。我们介绍了它们是如何工作的,并说明了一些被认为是驱动其表现的经济机制。我们这样做是因为对因子如何产生超额收益的坚实理解有助于创新新的因子。
我们还介绍了几个工具,你可以用来从各种数据源中生成你自己的因子,并展示了卡尔曼滤波和小波如何让我们平滑噪声数据,以希望能检索出更清晰的信号。
最后,我们提供了Zipline库的短暂体验用于离线和Quantopian在线平台上的交易算法的事件驱动模拟。你看到了如何实现一个简单的均值回归因子,以及如何以一种简单的方式结合多个因子来驱动一个基本策略。我们还研究了Alphalens库,它允许评估信号的预测性能和交易周转率。
而投资组合的构建过程则采用了更广泛的视角,目的在于从风险和收益的角度来确定仓位的最佳规模。在下一章,投资组合优化和策略评估中,我们将转向在投资组合过程中平衡风险和收益的各种策略。我们还将更详细地研究在一组有限的历史数据上对交易策略进行回测的挑战,以及如何应对这些挑战。
一周热门 更多>