Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (2024)

原文:annas-archive.org/md5/4fb09a9dfb72ac3c3c3d6af2574807be

译者:飞龙

协议:CC BY-NC-SA 4.0

人工智能和机器学*反映了技术的自然演进,增强的计算能力使计算机能够处理大数据集并分析数据,以识别模式和异常值。

黑石集团(2019 年)

金融建模有着悠久的历史,并完成了许多成功的任务,但同时也因模型缺乏灵活性包容性而受到激烈批评。2007-2008 年的金融危机加剧了这场辩论,也为金融建模领域的创新和不同方法铺平了道路。

当然,金融危机并不是促使金融领域人工智能应用增长的唯一因素。数据的可用性和计算能力的增加是推动金融领域人工智能应用和加强研究的另外两大驱动力,始于上世纪 90 年代。

金融稳定委员会(2017 年)强调了这一事实的有效性:

许多人工智能和机器学*的应用或使用“案例”已经存在。这些使用案例的采用受到了供应因素的推动,如技术进步、金融部门数据和基础设施的可用性,以及需求因素,如盈利需求、与其他公司的竞争以及金融监管的要求。

作为金融建模的一个分支,金融风险管理随着人工智能的采用在金融决策过程中的角色不断增强而不断发展。在他著名的书中,博斯特罗姆(2014 年)指出人类历史上有两次重要的革命:农业革命和工业革命。这两次革命产生了如此深远的影响,以至于任何类似规模的第三次革命将在两周内使世界经济翻倍。更引人注目的是,如果第三次革命由人工智能完成,影响将更为深远。

因此,人们对通过利用大数据和理解风险过程的复杂结构来塑造金融风险管理的人工智能应用寄予了极高的期望,规模前所未有。

通过这项研究,我旨在填补关于基于机器学*的金融应用的空白,以便改进金融模型的预测和测量性能。参数模型存在低方差和高偏差的问题;机器学*模型以其灵活性可以解决这一问题。此外,金融领域一个常见的问题是数据分布的变化总是对模型结果的可靠性构成威胁,但机器学*模型可以自适应地变化模式,使模型更好地拟合。因此,在金融领域存在着对可应用机器学*模型的巨大需求和需求,而这本书的主要区别在于包括全新的基于机器学*的金融风险管理建模方法。

简言之,本书旨在转变当前以参数模型为基础的金融风险管理局面。这种转变的主要动机是基于机器学*模型的高精度金融模型的最新发展。因此,本书适合那些在金融和机器学*方面有一些初步知识的人,我只是简要解释了这些主题。

因此,本书的目标读者包括但不限于金融风险分析师、金融工程师、风险协调员、风险建模师、模型验证员、量化风险分析师、投资组合分析员以及对金融和数据科学感兴趣的人士。

鉴于目标读者的背景,具备金融和数据科学初级水平的知识将使您能够从本书中获益最多。然而,并不意味着来自不同背景的人士不能理解本书的主题。只要花足够的时间并参考本书以外的其他金融和数据科学书籍,不同背景的读者也能掌握这些概念。

本书共有 10 章:

第一章,“风险管理基础”

本章介绍了风险管理的主要概念。在定义风险和风险类型(如市场风险、信用风险、操作风险和流动性风险)之后,进行了讨论。解释了风险管理,包括其重要性以及如何用于减少损失。还讨论了可以解决市场失灵的不对称信息,重点讨论了信息不对称和逆向选择。

第二章,“时间序列建模介绍”

本章展示了使用传统模型进行时间序列应用,即移动平均模型、自回归模型和自回归积分移动平均模型。我们学*如何使用 API 访问金融数据以及如何应用它。本章的主要目的是为比较传统时间序列方法与时间序列建模的最新发展提供一个基准,这是下一章的主要焦点。

第三章,“时间序列建模的深度学*”

本章介绍了用于时间序列建模的深度学*工具。循环神经网络和长短期记忆是我们能够处理具有时间维度数据的两种方法。本章还对深度学*模型在时间序列建模中的适用性留下了深刻印象。

第四章,“基于机器学*的波动率预测”

金融市场的增强整合导致金融市场长期不确定性的增加,这进一步突显了波动性的重要性。波动性用于衡量风险程度,这是金融领域的主要任务之一。本章讨论了基于支持向量回归、神经网络、深度学*和贝叶斯方法的新型波动性建模。为了比较性能,还采用了传统的 ARCH 和 GARCH 类型模型。

第五章,“建模市场风险”

在这里,采用基于机器学*的模型来提升传统市场风险模型的估算性能,即价值风险(VaR)和预期损失(ES)。VaR 是一种定量方法,用于衡量由于市场波动而导致的公允价值潜在损失,该损失在定义的时间段内且在定义的置信水平下不会超过。而 ES 则关注分布尾部,指的是大额和意外损失。VaR 模型使用去噪协方差矩阵进行开发,而 ES 则通过整合数据的流动性维度进行开发。

第六章,“信贷风险估计”

本章介绍了一种基于全面的机器学*方法来估计信贷风险。机器学*模型基于过去的信贷信息以及其他数据进行应用。方法始于风险分桶,这是巴塞尔协议建议的,并继续使用不同的模型:贝叶斯估计、马尔可夫链模型、支持向量分类、随机森林、神经网络和深度学*。在章节的最后部分,将比较这些模型的性能。

第七章,“流动性建模”

在本章中,高斯混合模型用于对被认为是风险管理中被忽视的流动性进行建模。该模型使我们能够整合流动性代理的不同方面,以便更稳健地捕捉流动性对金融风险的影响。

第八章,“建模操作风险”

本章涵盖了操作风险,这可能导致失败,主要是由于公司内部的弱点。操作风险的几个来源中,欺诈风险是最耗时且对公司运营最为有害的之一。在这里,我们将主要关注欺诈,并开发新的方法来基于机器学*模型实现更好的欺诈应用性能。

第九章,“企业治理风险度量:股价暴跌”

本章介绍了一种全新的建模企业治理风险的方法:股价暴跌。许多研究发现股价暴跌与企业治理之间存在实证联系。本章尝试通过最小协方差行列式模型揭示企业治理风险组成部分与股价暴跌之间的关系。

第十章,“金融中的合成数据生成和隐马尔可夫模型”

在这里,我们使用合成数据来估算不同的金融风险。本章的目的是突出合成数据的出现,这有助于我们减少有限历史数据的影响。合成数据使我们能够拥有足够大且高质量的数据,从而提高模型的质量。

本书使用以下排版约定:

斜体

表示新术语、网址、电子邮件地址、文件名和文件扩展名。

Constant width

用于程序清单,以及段落内引用程序元素,例如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

Constant width bold

显示用户应按照字面意义输入的命令或其他文本。

Constant width italic

显示应由用户提供值或由上下文确定的值的文本。

注意

此元素表示一般注意事项。

警告

此元素表示警告或注意。

附加材料(代码示例、练*等)可在https://github.com/abdullahkarasan/mlfrm下载。

如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com

本书旨在帮助您完成工作。一般情况下,如果本书提供示例代码,则可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需联系我们请求许可。例如,编写使用本书中数个代码块的程序不需要许可。销售或分发 O'Reilly 图书的示例代码需要许可。引用本书回答问题并引用示例代码不需要许可。将本书中大量示例代码整合到产品文档中需要许可。

我们感谢,但不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Python 金融风险管理机器学* by Abdullah Karasan(O’Reilly)。版权 2022 Abdullah Karasan,978-1-492-08525-6。”

如果您认为您对代码示例的使用超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。

注意

超过 40 年来,O’Reilly Media为企业的成功提供技术和业务培训、知识和洞察。

我们独特的专家和创新者网络通过图书、文章和我们的在线学*平台分享他们的知识和专长。O’Reilly 的在线学*平台让您随时访问现场培训课程、深度学*路径、互动编码环境,以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。有关更多信息,请访问http://oreilly.com

请将有关此书的评论和问题发送给出版商:

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-998-9938(美国或加拿大)

  • 707-829-0515(国际或本地)

  • 707-829-0104(传真)

我们有这本书的网页,列出勘误、示例和任何额外信息。您可以访问https://oreil.ly/ml-for-fin-risk-mgmt

发送邮件至bookquestions@oreilly.com评论或询问有关此书的技术问题。

有关我们的图书和课程的新闻和信息,请访问http://oreilly.com

在 Facebook 上找到我们:http://facebook.com/oreilly

在 Twitter 上关注我们:http://twitter.com/oreillymedia

在 YouTube 上关注我们:http://www.youtube.com/oreillymedia

决定写这本书并非一时冲动。我觉得缺乏涵盖主要金融风险管理模型与机器学*模型的源资料。这本书是将机器学*模型应用于金融风险管理问题的努力。我得出结论,这本书应该从各个角度有所不同,如提供理论和实证方法,并包含所有可复制的代码。当我与 O’Reilly 的 Michelle Smith 分享这个想法时,得到了她的认可和持续的鼓励。Michelle 对这个项目寄予厚望,并在整个过程中给予了支持,对此我非常感激。

随着书的新章节到来,与我的编辑米歇尔·克罗宁(Michele Cronin)举行的信息丰富且有趣的每周会议使我保持在正确的轨道上,并帮助我获得编辑视角。随着进展,每一章都带来了新的挑战,需要不懈的日夜来应对。然而,我花费的时间越多,发现不准确、错别字和其他类型错误就越困难。这正是技术审阅者们宝贵的反馈发挥作用的时候。我要感谢梅赫梅特·本图尔克(Mehmet Benturk)、哈里奥姆·塔特萨特(Hariom Tatsat)、艾萨克·雷亚(Isaac Rhea)、迪米特里·比安科(Dimitri Bianco)、麦克莱恩·马歇尔(McKlayne Marshall)和迈克尔·谢勒(Michael Shearer)为使这本书成为今天的样子所付出的努力。

另外,我要感谢丹尼·埃尔凡鲍姆(Danny Elfanbaum)和兰迪·巴拉班(Randy Balaban)对文本一致性的迅速和有益的评论。经历了一个漫长而曲折的一年后,我终于完成了这段让人疲惫但又启发人的人生里程碑,充满希望,希望这本书能为那些希望在金融中学*机器学*的人指引道路。

我要向那些为这本书作出贡献的人表达我最深的感激之情。

在 2007 年,没有人会想到风险功能会发生如此大的变化。人们自然而然地期望下一个十年变化会减少。然而,我们相信情况可能正好相反。

Harle, Havas, and Samandari (2016)

风险管理是一个不断发展的过程。长期的风险管理实践无法跟上最新发展,也不能成为预测即将发生的危机的先兆,因此不可避免地要进行持续演进。监控和采纳由风险管理过程中的结构性突破带来的变化非常重要。采纳这些变化意味着重新定义风险管理的组成部分和工具,而这正是本书的主题。

在金融领域,传统上经验研究主要集中在统计推断上。计量经济学建立在统计推断的基础上。这类模型关注底层数据的结构、生成过程和变量之间的关系。然而,机器学*(ML)模型并不假定定义底层数据生成过程,而是被视为预测的手段(Lommers, El Harzli, and Kim 2021)。因此,ML 模型往往更加数据中心化和注重预测精度。

此外,在金融领域,数据稀缺和不可用性一直是一个问题,可以预见的是,计量经济模型在这些情况下表现不佳。鉴于 ML 模型通过合成数据生成提供的数据不可用解决方案,这些模型已经成为金融领域的重要议题,而金融风险管理当然也不例外。

在详细讨论这些工具之前,值得介绍风险管理的主要概念,这些概念将贯穿本书。这些概念包括风险、风险类型、风险管理、回报以及一些与风险管理相关的概念。

风险始终存在,但理解和评估风险比单纯知道它要困难得多,因为风险具有抽象的性质。风险被视为一种危险,可能是预期的,也可能是意外的。预期风险是被定价的,但意外风险却几乎无法计算,因此可能是毁灭性的。

正如你所想象的那样,对于“风险”的定义没有普遍共识。然而,从金融的角度来看,风险指的是公司可能面临的潜在损失或不确定性水平。 McNeil, Alexander, and Paul (2015) 对风险的定义有所不同,如下所述:

任何可能对组织实现其目标和执行其战略能力造成不利影响的事件或行动,或者作为损失或低于预期回报的可量化可能性。

这些定义侧重于风险的下行,暗示成本与风险同步,但也应该注意它们之间并不一定是一对一的关系。例如,如果风险是可预期的,那么所产生的成本相对较低(甚至可以忽略不计),而不是意外风险的情况。

所有的金融投资都是为了获取利润,也称为收益。更正式地说,收益是在给定时间段内投资所获得的收益。因此,收益指的是风险的正面。在整本书中,风险和收益将分别指向风险的下行和上行风险。

正如你可以想象的,风险和回报之间存在一种权衡:假设风险越高,则实现的回报就越大。由于找到最优解是一项艰巨的任务,因此这种权衡是金融领域最具争议的问题之一。然而,Markowitz(1952)提出了这个长期问题的一个直观和吸引人的解决方案。他对风险的定义直至那时模糊不清,是清晰而干净的,并导致金融研究领域的转变。Markowitz 使用标准偏差 σ R i 来量化风险。这个直观的定义允许研究人员在金融中使用数学和统计学。标准偏差可以用数学方式定义为(Hull 2012):

σ = 𝔼 ( R 2 ) - [𝔼(R)] 2

其中 R𝔼 分别指年度收益率和期望。本书多次使用符号 𝔼 表示期望收益,因为这是我们在定义风险时讨论的概率。在涉及投资组合方差时,协方差出现在图景中,公式变为:

σ p 2 = w a 2 σ a 2 + w b 2 σ b 2 + 2 w a w b Cov ( r a , r b )

其中 w 表示权重,σ 2 是方差,Cov 是协方差矩阵。

对先前得到的方差进行平方根运算得到投资组合标准偏差:

σ p = σ p 2

换句话说,投资组合的预期收益率是各个收益率的加权平均值,可以表示为:

𝔼 ( R ) = i n w i R i = w 1 R 1 + w 2 R 2 + w n R n

让我们通过可视化探索风险与回报的关系。为此,构建一个假设的投资组合,并用 Python 计算必要的统计数据:

In [1]: import statsmodels.api as sm import numpy as np import plotly.graph_objs as go import matplotlib.pyplot as plt import plotly import warnings warnings.filterwarnings('ignore')In [2]: n_assets = 5 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) n_simulation = 500 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [3]: returns = np.random.randn(n_assets, n_simulation) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [4]: rand = np.random.rand(n_assets) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) weights = rand/sum(rand) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) def port_return(returns): rets = np.mean(returns, axis=1) cov = np.cov(rets.T, aweights=weights, ddof=1) portfolio_returns = np.dot(weights, rets.T) portfolio_std_dev = np.sqrt(np.dot(weights, np.dot(cov, weights))) return portfolio_returns, portfolio_std_dev ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)In [5]: portfolio_returns, portfolio_std_dev = port_return(returns) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)In [6]: print(portfolio_returns) print(portfolio_std_dev) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) 0.012968706503879782 0.023769932556585847In [7]: portfolio = np.array([port_return(np.random.randn(n_assets, i)) for i in range(1, 101)]) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)In [8]: best_fit = sm.OLS(portfolio[:, 1], sm.add_constant(portfolio[:, 0]))\ .fit().fittedvalues ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png)In [9]: fig = go.Figure() fig.add_trace(go.Scatter(name='Risk-Return Relationship', x=portfolio[:, 0], y=portfolio[:, 1], mode='markers')) fig.add_trace(go.Scatter(name='Best Fit Line', x=portfolio[:, 0], y=best_fit, mode='lines')) fig.update_layout(xaxis_title = 'Return', yaxis_title = 'Standard Deviation', width=900, height=470) fig.show() ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (1)

考虑的资产数量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (2)

进行的模拟次数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (3)

从正态分布生成随机样本用作收益率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (4)

生成随机数以计算权重

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (5)

计算权重

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (6)

用于计算预期组合收益和组合标准偏差的函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (7)

调用函数的结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (8)

打印预期组合收益和组合标准偏差的结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (9)

重新运行函数 100 次

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (10)

为了绘制最佳拟合线,运行线性回归

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (11)

为了可视化目的绘制交互式图

图1-1,通过前述的 Python 代码生成,证实了风险和回报是同步变化的,但这种相关性的大小取决于个别股票和金融市场条件。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (12)

图 1-1. 风险-回报关系

金融风险管理是处理来自金融市场的不确定性的过程。它涉及评估组织面临的金融风险,并制定与内部优先事项和政策一致的管理策略(Horcher 2011)。

根据此定义,由于每个组织面临不同类型的风险,公司应对风险的方式是完全独特的。每家公司都应该对风险进行适当评估,并采取必要的行动。然而,这并不意味着一旦确定了风险,公司就需要尽可能地减轻风险。

因此,风险管理并不是要不惜一切代价减轻风险。减轻风险可能需要牺牲回报,而且可以容忍到一定程度,因为公司寻求的是尽可能高的回报和尽可能低的风险。因此,要最大化利润同时降低风险应该是一个细致而明确定义的任务。

管理风险是有成本的,尽管处理风险需要特定的公司政策,但存在一般的可能性风险策略框架:

忽略

在这种策略中,公司接受所有风险及其后果,并更愿意不采取任何行动。

转移

这种策略涉及通过套期保值或其他方式将风险转移给第三方。

减轻

公司制定减轻风险的策略部分是因为其有害影响可能被认为难以承受和/或超过附加的利益。

接受

如果公司采用接受风险的策略,他们会正确识别风险并承认其益处。换句话说,当假设某些活动所产生的特定风险为股东带来价值时,可以选择这种策略。

主要的金融风险

金融公司在其业务生命周期内面临各种风险。这些风险可以按不同类别划分,以便更容易识别和评估。主要的金融风险类型包括市场风险、信用风险、流动性风险和操作风险,但这并非穷尽列表。然而,本书将关注主要的金融风险类型。让我们来看看这些风险类别。

市场风险

此风险由金融市场因素变动引起。例如,利率上升可能严重影响持有空头的公司。

关于市场风险的另一个来源可以举例说明:汇率。从事国际贸易的公司,其商品以美元计价,极易受到美元汇率变动的影响。

可想而知,任何一种商品价格的变化都可能对公司的财务可持续性构成威胁。有许多基本因素直接影响商品价格,包括市场参与者、运输成本等。

信用风险

信用风险是最普遍的风险之一。当交易对手未能履行债务时,它就会出现。例如,如果借款人无法偿还款项,那么信用风险就会显现。信用质量恶化也是通过减少组织可能拥有的证券市场价值而带来的风险之一(Horcher 2011)。

流动性风险

直到 2007 年至 2008 年的金融危机严重冲击了金融市场,流动性风险才受到重视。流动性指投资者执行交易的速度和便利程度。这也被称为交易流动性风险。流动性风险的另一个方面是资金流动性风险,这可以定义为筹集现金或可用信贷来资助公司运营的能力。

如果一家公司无法在短期内将其资产变现,这将属于流动性风险类别,并且对公司的财务管理和声誉非常不利。

操作风险

管理操作风险并不是一项明确和可预见的任务,由于风险的错综复杂和内部性质,它会占用公司大量资源。相关问题包括:

  • 金融公司如何有效管理风险?

  • 他们为这项任务分配了必要的资源吗?

  • 公司的可持续性风险重要性是否得到了正确评估?

顾名思义,操作风险是指公司或行业内在运作或外部事件对公司日常运营、盈利能力或可持续性构成威胁时产生的风险。操作风险包括欺诈活动、未能遵守法规或内部程序、由于缺乏培训而造成的损失等。

那么,如果一家公司面临一个或多个这些风险并且毫无准备,会发生什么?尽管这种情况并不经常发生,但历史事件告诉我们答案:公司可能会违约并陷入重大的金融崩溃。

大规模金融崩溃

风险管理有多重要?这个问题可以通过一本长达数百页的书来回答,但事实上,金融机构中风险管理的兴起已经说服了我们。例如,2007 年至 2008 年的全球金融危机被描述为“风险管理的巨大失败”(Buchholtz and Wiggins 2019),尽管这只是冰山一角。在金融系统崩溃的背景下,无数次风险管理失败为其铺平了道路。要理解这一崩溃,我们需要深入研究过去的金融风险管理失败。一家名为 Long-Term Capital Management (LTCM)的对冲基金提供了一个生动的金融崩溃例子。

LTCM 组建了一个由顶尖学术界人士和实践者组成的团队。这导致资金流入该公司,并开始以 10 亿美元进行交易。到 1998 年,LTCM 控制了超过 1,000 亿美元,并且大量投资于包括俄罗斯在内的一些新兴市场。俄罗斯债务违约深刻影响了 LTCM 的投资组合,由于避险需求增加¹,它遭受了严重打击,导致了破产(Bloomfield 2003)。

Metallgesellschaft (MG) 是另一家因糟糕的金融风险管理而不复存在的公司。MG 主要在天然气和石油市场运营。由于其高度的敞口,在天然气和石油价格大幅下跌后,MG 需要资金。平仓空头头寸导致了大约 15 亿美元的损失。

Amaranth Advisors (AA) 是另一家因大量投资于单一市场且误判由此投资产生的风险而破产的对冲基金。到 2006 年,AA 吸引了约 90 亿美元的资产管理,但由于天然气期货和期权价格的下跌,几乎损失了其中的一半。AA 的违约归因于天然气价格低迷和误导性的风险模型(Chincarini 2008)。

Stulz 的论文,“Risk Management Failures: What Are They and When Do They Happen?”(2008)总结了可能导致违约的主要风险管理失误:

  • 对已知风险的错误测量

  • 未能考虑风险

  • 未能向高层管理层传达风险

  • 未能监控风险

  • 未能管理风险

  • 未能使用适当的风险度量标准

因此,全球金融危机并不是导致监管机构和机构重新设计其金融风险管理的唯一事件。相反,它是使杯水满溢的最后一滴,在危机之后,监管机构和机构都吸取了教训并改进了他们的流程。最终,这一系列事件导致了金融风险管理的兴起。

尽管在理论上很直观,完全理性的决策者假设,这是现代金融理论的主要基石,但太完美以至于不真实。因此,行为经济学家攻击了这一观念,声称心理学在决策过程中起着关键作用:

做决策就像说散文——人们一直在做,有意或无意。因此,决策主题被许多学科共享,从数学和统计学,经济学和政治科学,到社会学和心理学。

卡尼曼和特弗斯基(1984 年)

信息不对称和金融风险管理息息相关,公司资产估值的不确定性可能会提高融资成本,对公司的可持续性构成威胁(参见 DeMarzo 和 Duffie 1995 年以及 Froot,Scharfstein 和 Stein 1993 年)。

因此,前述失败的根源深藏于这样一个完美的假设世界中,理性决策者生活在其中,无法解释它们。在这一点上,人类的本能和一个不完美的世界开始发挥作用,学科的混合提供了更合理的解释。逆向选择和道德风险是解释市场失败的两个突出类别。

逆向选择

逆向选择 是一种信息不对称的类型,其中一方试图利用其信息优势。当卖方比买方更了解时,这种现象就会出现。阿克洛夫(1978 年)完美地称之为“柠檬市场”。在这个框架内,“柠檬”指的是低质量的商品。

考虑一个市场,有柠檬车和高质量车,买家知道他们可能买到柠檬车,这降低了均衡价格。然而,卖家比买家更了解汽车是柠檬还是高质量的情况。因此,在这种情况下,交换的好处可能消失,没有交易发生。

由于其复杂性和不透明性,危机前的抵押市场是逆向选择的一个典型例子。借款人对于他们的支付意愿和能力了解得比贷方更多。通过贷款证券化(即抵押贷款支持的证券),金融风险得以形成。从那时起,抵押贷款的发起者比将它们以抵押贷款支持的证券形式出售给投资者的人更了解风险。

让我们尝试使用 Python 模拟逆向选择。在保险业中很容易观察到这种现象,因此我想重点研究该行业来模拟逆向选择。

假设消费者的效用函数是:

U ( x ) = e γx

其中 x 是收入,γ 是一个参数,它取值介于 0 和 1 之间。

注意

效用函数是用来表示消费者对商品和服务偏好的工具,对于风险厌恶的个体而言,它是凹的。

此示例的最终目标是基于消费者效用来决定是否购买保险。

出于实践目的,我假设收入为 2 美元,事故成本为 1.5 美元。

现在是时候计算损失的概率,π,它是外生给定的,并且均匀分布。

作为最后一步,为了找到平衡,我必须定义保险覆盖的供求。以下代码块显示了如何建模逆向选择:

In [10]: import matplotlib.pyplot as plt import numpy as np plt.style.use('seaborn')In [11]: def utility(x): return(np.exp(x ** gamma)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [12]: pi = np.random.uniform(0,1,20) pi = np.sort(pi) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [13]: print('The highest three probability of losses are {}' .format(pi[-3:])) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) The highest three probability of losses are [0.834261 0.93542452 0.97721866]In [14]: y = 2 c = 1.5 Q = 5 D = 0.01 gamma = 0.4In [15]: def supply(Q): return(np.mean(pi[-Q:]) * c) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [16]: def demand(D): return(np.sum(utility(y - D) > pi * utility(y - c) + (1 - pi) * utility(y))) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png)In [17]: plt.figure() plt.plot([demand(i) for i in np.arange(0, 1.9, 0.02)], np.arange(0, 1.9, 0.02), 'r', label='insurance demand') plt.plot(range(1,21), [supply(j) for j in range(1,21)], 'g', label='insurance supply') plt.ylabel("Average Cost") plt.xlabel("Number of People") plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (13)

编写风险厌恶效用函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (14)

从均匀分布生成随机样本

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (15)

选择最后三个项目

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (16)

编写供应保险合同函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (17)

编写保险合同需求函数

图 1-2 显示了保险供求曲线。令人惊讶的是,两条曲线都是向下倾斜的,意味着随着更多人需求合同并加入合同,风险降低,从而影响合同价格。

直线代表保险供应和合同的平均成本,而另一条显示逐步下降的线条表示保险合同的需求。随着我们从风险客户开始分析,并且随着合同的增加越来越多的人,风险水平与平均成本呈平行下降。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (18)

图 1-2. 逆向选择

道德风险

市场失灵也是由于信息不对称造成的。在道德风险情况下,合同的一方承担比另一方更多的风险。正式地,道德风险可以定义为更知情的一方利用其掌握的私密信息损害他人的情况。

为了更好地理解道德风险,可以从信贷市场给出一个简单的例子:假设实体 A 需要信贷用于项目融资,而这项目被认为是可行的。如果实体 A 将贷款用于向银行 C 的信贷债务支付,而未事先通知贷款银行,则会出现道德风险。在分配信贷时,银行可能会遇到道德风险情况,这是由于信息不对称导致的,这降低了银行的放贷意愿,并且是银行在信贷分配过程中投入大量精力的原因之一。

一些人认为美联储委员会(Fed)为 LTCM 进行的救援行动可以被视为一种道德风险,因为 Fed 以不诚信的方式订立合同。

本章介绍了金融风险管理的主要概念,旨在确保我们都在同一页面上。这些术语和概念将在本书中经常使用。

此外,还讨论了一种行为方法,攻击财务代理的理由,以便我们拥有更全面的工具来解释金融风险的来源。

在下一章中,我们将讨论时间序列方法,这是金融分析的主要支柱之一,因为大多数金融数据都具有时间维度,这需要特别注意和技术来处理。

本章引用的文章和章节:

  • Akerlof, George A. 1978. “柠檬市场:质量不确定性和市场机制。”《经济不确定性》,235-251。学术出版社。

  • Buchholtz, Alec, 和 Rosalind Z. Wiggins. 2019. “所学到的经验:Thomas C. Baxter, Jr., Esq.”《金融危机杂志》1, no. (1): 202-204。

  • Chincarini, Ludwig. 2008. “风险管理案例研究:从 Amaranth Advisors Llc 的崩溃中汲取教训。”《应用金融杂志》18 (1): 152-74。

  • DeMarzo, Peter M., 和 Darrell Duffie. 1995. “公司套期保值和套期会计的激励”。《金融研究评论》8 (3): 743-771。

  • Froot, Kenneth A., David S. Scharfstein, 和 Jeremy C. Stein. 1993. “风险管理:协调企业投资和融资政策。”《金融杂志》48 (5): 1629-1658。

  • Harle, P., A. Havas, 和 H. Samandari. 2016. 《银行风险管理的未来》。麦肯锡全球研究所。

  • Kahneman, D., 和 A. Tversky. 1984. “选择、价值和框架。美国心理学协会。”《美国心理学家》。39 (4): 341-350。

  • Lommers, Kristof, Ouns El Harzli, 和 Jack Kim. 2021. “机器学*与金融研究的对抗”。在 SSRN 3788349 上可用。

  • Markowitz H. 1952. “投资组合选择”。《金融杂志》。7 (1): 177—91。

  • Stulz, René M. 2008. “风险管理的失败:它们是什么以及何时发生?”《应用公司金融杂志》20 (4): 39-48。

本章引用的书籍:

  • Bloomfield, S. 2013. 《公司治理的理论与实践:一种综合方法》。剑桥:剑桥大学出版社。

  • Horcher, Karen A. 2011. 《金融风险管理要点》。第 32 卷。新泽西州霍博肯:约翰·威利和儿子出版社。

  • Hull, John. 2012. 《风险管理与金融机构》。第 733 卷。新泽西州霍博肯:约翰·威利和儿子出版社。

  • McNeil, Alexander J., Rüdiger Frey, 和 Paul Embrechts. 2015. 《定量风险管理:概念、技术和工具》,修订版。新泽西州普林斯顿:普林斯顿大学出版社。

¹ 质量之飞 是指投资者避开股票等风险资产,转而在诸如政府发行的债券等较安全资产上建立长期头寸。

通过大量的过去数据来研究市场行为,比如货币或股票价格的高频买卖报价。正是数据的丰富性使得市场的经验研究成为可能。虽然不可能进行控制实验,但可以在历史数据上进行广泛测试。

Sergio Focardi(1997)

一些模型更好地解释了某些现象;某些方法以稳固的方式捕捉了事件的特征。时间序列建模是一个很好的例子,因为绝大多数金融数据都有一个时间维度,这使得时间序列应用成为金融的必备工具。简单来说,数据的排序和其相关性很重要。

本书的这一章将讨论经典的时间序列模型,并比较这些模型的性能。深度学*的时间序列分析将在第三章中介绍;这是一种在数据准备和模型结构方面完全不同的方法。经典模型包括移动平均(MA)、自回归(AR)和自回归积分移动平均(ARIMA)模型。这些模型共同的特点是历史观测值所包含的信息。如果这些历史观测值是来自误差项,我们称之为移动平均;如果这些观测值来自时间序列本身,称为自回归。另一个模型 ARIMA 是这些模型的延伸。

这是 Brockwell 和 Davis(2016)对时间序列的正式定义:

时间序列是一组观测值 X t ,每个观测值都记录在特定时间 t 。离散时间序列…是在观测值所记录的时间集合 T 0 是一个离散集合时的时间序列,例如当观测是在固定时间间隔下进行时的情况。连续时间序列是当观测值在一段时间内连续记录时得到的。

让我们观察具有时间维度的数据是什么样子。图 2-1 展示了 1980 年至 2020 年的油价,下面的 Python 代码向我们展示了绘制这个图的一种方法:

In [1]: import quandl import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') plt.style.use('seaborn')In [2]: oil = quandl.get("NSE/OIL", authtoken="insert you api token", start_date="1980-01-01", end_date="2020-01-01") ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [3]: plt.figure(figsize=(10, 6)) plt.plot(oil.Close) plt.ylabel('$') plt.xlabel('Date') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (19)

从 Quandl 数据库中提取数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (20)

图 2-1。1980 年至 2020 年的油价

API(Application Programming Interface)是一种用于使用代码检索数据的工具。我们将在整本书中使用不同的 API。在前面的练*中,我们使用了 Quandl API。

Quandl API 允许我们从 Quandl 网站访问金融、经济和替代数据。要获取您的 Quandl API,请先访问Quandl 网站,然后按照必要的步骤获取您自己的 API 密钥。

从前面提供的定义可以理解,时间序列模型可以适用于多个领域,例如:

  • 医疗保健

  • 金融

  • 经济学

  • 网络分析

  • 天文学

  • 天气

时间序列方法的优越性来自于这样一个观念:时间上的观察相关性更好地解释了当前值。拥有具有时间相关结构的数据意味着违反了著名的独立同分布(IID)假设,该假设是许多模型的核心。

因此,由于时间上的相关性,通过其自身的历史值可以更好地理解当期股票价格的动态。我们如何理解数据的动态?这是一个我们可以通过详细说明时间序列的组件来解决的问题。

时间序列有四个组成部分:趋势、季节性、周期性和残差。在 Python 中,我们可以使用seasonal_decompose函数轻松可视化时间序列的组件:

In [4]: import yfinance as yf import numpy as np import pandas as pd import datetime import statsmodels.api as sm from statsmodels.tsa.stattools import adfuller from statsmodels.tsa.seasonal import seasonal_decomposeIn [5]: ticker = '^GSPC' ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) start = datetime.datetime(2015, 1, 1) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) end = datetime.datetime(2021, 1, 1) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) SP_prices = yf.download(ticker, start=start, end=end, interval='1mo')\ .Close ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) [*********************100%***********************] 1 of 1 completedIn [6]: seasonal_decompose(SP_prices, period=12).plot() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (21)

标示标准普尔 500 指数的股票代码

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (22)

确定开始和结束日期

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (23)

获取标准普尔 500 指数的收盘价

在图2-2 的顶部面板中,我们可以看到原始数据的绘图,在第二个面板中,可以观察到显示上升趋势的趋势。在第三个面板中,展示了季节性,最后呈现了显示不规则波动的残差。您可能会想知道周期性组件在哪里;噪声和周期性组件被放在残差组件下面。

熟悉时间序列组件对进一步分析至关重要,这样我们就能够了解数据的特征并提出合适的模型。让我们从趋势组件开始。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (24)

图 2-2. 标准普尔 500 时间序列分解

趋势

趋势表示在给定时间段内增长或减少的一般趋势。一般来说,当时间序列的起点和终点不同时,或者呈现上升/下降斜率时,就存在趋势。以下代码显示了趋势的样子:

In [7]: plt.figure(figsize=(10, 6)) plt.plot(SP_prices) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.title('S&P-500 Prices') plt.ylabel('$') plt.xlabel('Date') plt.show()

除了标准普尔 500 指数价格暴跌的时期之外,我们在图2-3 中看到在 2010 年至 2020 年之间有明显的上升趋势。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (25)

图 2-3. 标准普尔 500 价格

线图并不是了解趋势的唯一选项。相反,我们还有一些其他强大的工具来完成这个任务。因此,在这一点上,谈论两个重要的统计概念是值得的:

  • 自相关函数

  • 部分自相关函数

自相关函数(ACF)是分析时间序列当前值与其滞后值之间关系的统计工具。绘制 ACF 使我们能够很容易地观察到时间序列中的序列依赖性:

ρ ^ ( h ) = Cov(X t ,X t-h ) Var(X t )

图2-4 表示了 ACF 图。垂直线表示相关系数;第一条线表示序列与其 0 滞后的相关性,即它是与自身的相关性。第二条线指示系列在时间 t - 1 和 t 处的相关性。基于这些,我们可以得出结论,S&P 500 显示出连续依赖性。由于在 ACF 图中表示线的相关系数以缓慢的方式衰减,当前值与 S&P 500 数据的滞后值之间存在强烈的依赖关系。

下面是我们如何在 Python 中绘制 ACF 图:

In [8]: sm.graphics.tsa.plot_acf(SP_prices, lags=30) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.xlabel('Number of Lags') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (26)

绘制 ACF

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (27)

图2-4. S&P 500 的 ACF 图

现在的问题是,自相关的可能来源是什么?以下是一些原因:

  • 自相关的主要来源是“延续”,意味着前一个观察对当前观察有影响。

  • 模型误设。

  • 测量误差,基本上是观察值与实际值之间的差异。

  • 放弃一个变量,这个变量有解释力。

偏自相关函数(PACF)是检验时间序列当前值 X tX t-p , p 之间关系的另一种方法。ACF 通常被认为是 MA(q)模型中的有用工具,因为 PACF 的衰减速度不快,但接*于 0。然而,ACF 的模式更适用于 MA。另一方面,PACF 在 AR(p)过程中表现良好。

PACF 提供了关于时间序列当前值与滞后值之间相关性的信息,控制其他相关性。

一眼看不出正在发生什么是不容易的。让我给你一个例子。假设我们要计算偏相关系数 X tX t-h

用数学方式表示:

ρ ^ ( h ) = Cov(X t ,X t-h |X t-1 ,X t-2 ...X t-h-1 ) Var(X t |X t-1 ,X t-2 ,...,X t-h-1 )Var(X t-h |X t-1 ,X t-2 ,...,X t-h-1 )

其中 h 是滞后。看看下面 Python 代码中 S&P 500 的 PACF 图的片段:

In [9]: sm.graphics.tsa.plot_pacf(SP_prices, lags=30) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.xlabel('Number of Lags') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (28)

绘制 PACF

图2-5 展示了原始 S&P 500 数据的 PACF。在解释 PACF 时,我们关注超出表示置信区间的深色区域的尖峰。图2-5 显示在不同滞后时存在一些尖峰,但滞后 10 超出了置信区间。因此,选择包含所有滞后值直到滞后 10 的模型可能是明智的选择。

正如前面讨论的,PACF 度量了系列当前值与滞后值之间的相关性,以便孤立其中效应。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (29)

图2-5. 标准普尔 500 的偏自相关函数(PACF)图

季节性

季节性存在于如果一段时间内出现定期波动的情况下。例如,能源使用可以显示出季节性特征。更具体地说,能源使用在一年中的某些时段内会上下波动。

为了展示我们如何检测季节性成分,让我们使用联邦储备经济数据库(FRED),该数据库包括来自 80 多个来源的超过 500,000 个经济数据系列,涵盖银行业、就业、汇率、国内生产总值、利率、贸易和国际交易等多个领域:

In [10]: from fredapi import Fred import statsmodels.api as smIn [11]: fred = Fred(api_key='insert you api key')In [12]: energy = fred.get_series("CAPUTLG2211A2S", observation_start="2010-01-01", observation_end="2020-12-31") ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) energy.head(12)Out[12]: 2010-01-01 83.7028 2010-02-01 84.9324 2010-03-01 82.0379 2010-04-01 79.5073 2010-05-01 82.8055 2010-06-01 84.4108 2010-07-01 83.6338 2010-08-01 83.7961 2010-09-01 83.7459 2010-10-01 80.8892 2010-11-01 81.7758 2010-12-01 85.9894 dtype: float64In [13]: plt.plot(energy) plt.title('Energy Capacity Utilization') plt.ylabel('$') plt.xlabel('Date') plt.show()In [14]: sm.graphics.tsa.plot_acf(energy, lags=30) plt.xlabel('Number of Lags') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (30)

访问 2010 年至 2020 年期间的 FRED 能源利用率

图2-6 显示在* 10 年的时间段内出现周期性的上升和下降,每年的前几个月利用率较高,然后在年底时下降,证实能源利用率存在季节性。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (31)

图2-6. 能源利用率的季节性

ACF 图也可以提供关于季节性的信息,因为周期性的上升和下降可以在 ACF 中观察到。图2-7 展示了在季节性存在的情况下的相关结构。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (32)

图2-7. 能源利用率的自相关函数(ACF)

周期性

如果数据没有显示固定周期的运动会怎样?此时,周期性就进入了视野。当周期性变化高于趋势时,就存在周期性。有些人在某种意义上混淆了周期性和季节性,因为它们都表现出扩张和收缩。然而,我们可以将周期性看作是商业周期,它们需要很长时间来完成周期,而上升和下降则在很长的时间范围内发生。因此,周期性与季节性不同之处在于没有固定周期的波动。周期性的一个例子可能是房屋购买(或销售),这取决于抵押贷款利率。也就是说,当抵押贷款利率下降(或上升)时,会促使房屋购买(或销售)增加。

残差

残差被称为时间序列的不规则成分。从技术角度讲,残差等于观测值与相关拟合值之间的差异。我们可以将其视为模型中的剩余部分。

正如我们之前讨论过的,时间序列模型缺乏一些核心假设,但这并不意味着时间序列模型没有假设。我想强调最突出的假设之一,即稳定性

稳定性意味着时间序列的统计特性,如均值、方差和协方差随时间不变。

有两种形式的稳定性:

弱平稳性

如果X t 的联合分布与时间索引的移位版本相同,则称时间序列X t为平稳的。

  • X t 有有限方差,𝔼 ( X t 2 ) < , t

  • X t 的均值是恒定的,仅取决于时间,𝔼 ( X t ) = μ , t

  • 协方差结构,γ ( t , t + h ),仅依赖于时间差:

γ ( h ) = γ h + γ ( t + h , t )

换句话说,时间序列应具有有限的方差,均值恒定且协方差结构是时间差的函数。

强平稳性

如果X t1 , X t2 , . . . X tk 的联合分布与集合X t1+h , X t2+h , . . . X tk+h 的平移版本相同,则称为强平稳性。因此,强平稳性意味着随机过程的随机变量的分布与时间索引的移位相同。

现在的问题是为什么我们需要平稳性?原因有两个。

首先,在估计过程中,随着时间的推移,具有某种分布是至关重要的。换句话说,如果时间序列的分布随时间变化,它就变得不可预测,无法建模。

时间序列模型的最终目标是预测。为了做到这一点,我们首先应该估计系数,这对应于机器学*中的学*。一旦我们学*并进行预测分析,我们假设在估计中数据的分布保持不变,即我们有相同的估计系数。如果情况不是这样,我们应该重新估计系数,因为我们无法用先前的估计系数进行预测。

存在结构性突变,比如金融危机,会导致分布的转变。我们需要谨慎和分开地处理这个时期。

具有平稳性的另一个原因是,根据假设,一些统计模型要求数据平稳,但这并不意味着一些模型仅仅需要平稳。相反,所有模型都要求数据平稳,但即使你输入非平稳数据,某些模型会通过设计将其转换为平稳数据并处理它。

图 2-4 显示了由于时间序列滞后之间高相关性的持续性,导致了缓慢衰减的滞后,从而非平稳。

通常情况下,检测非平稳性有两种方法:可视化和统计方法。后者显然是检测非平稳性的更好、更稳健的方法。然而,为了提高我们的理解,让我们从 ACF 开始。ACF 衰减缓慢意味着数据是非平稳的,因为它在时间上呈现出强烈的相关性。这正是我在 S&P 500 数据中观察到的。

我们首先需要检查并确定数据是否平稳。可视化是一个好的但最终是不够的工具。相反,需要更强大的统计方法,增广迪基-富勒(ADF)检验提供了这种方法。假设置信区间设置为 95%,以下结果表明数据不是平稳的:

In [15]: stat_test = adfuller(SP_prices)[0:2] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) print("The test statistic and p-value of ADF test are {}" .format(stat_test)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) The test statistic and p-value of ADF test are (0.030295120072926063, 0.9609669053518538)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (33)

检验平稳性的 ADF 检验

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (34)

ADF 检验的检验统计量和 p 值

进行差分是一种有效的去除平稳性的技术。这意味着仅仅是将序列的当前值减去其第一个滞后值,即 x t - x t-1 ,下面的 Python 代码展示了如何应用这一技术(并创建了图 2-8 和 2-9):

In [16]: diff_SP_price = SP_prices.diff() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [17]: plt.figure(figsize=(10, 6)) plt.plot(diff_SP_price) plt.title('Differenced S&P-500 Price') plt.ylabel('$') plt.xlabel('Date') plt.show()In [18]: sm.graphics.tsa.plot_acf(diff_SP_price.dropna(),lags=30) plt.xlabel('Number of Lags') plt.show()In [19]: stat_test2 = adfuller(diff_SP_price.dropna())[0:2] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) print("The test statistic and p-value of ADF test after differencing are {}"\ .format(stat_test2)) The test statistic and p-value of ADF test after differencing are (-7.0951058730170855, 4.3095548146405375e-10)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (35)

对 S&P 500 价格进行差分

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (36)

基于差分后的 S&P 500 数据的 ADF 检验结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (37)

图 2-8. 去趋势后的 S&P 500 价格

经过第一次差分后,我们重新运行 ADF 测试来查看是否奏效,结果是肯定的。ADF 的极低 p 值告诉我,标准普尔 500 数据现在是平稳的。

这可以从提供的线图中观察到 Figure2-8。与原始标准普尔 500 图不同,此图表现出围绕均值的波动,具有类似的波动性,意味着我们有了一个稳定的序列。

Figure2-9 显示在滞后 7 处只有一个统计上显著的相关结构。

不用说,趋势并非非平稳的唯一指标。季节性也是其另一个来源,现在我们将学*一种处理方法。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (38)

图 2-9. 去趋势的标准普尔 500 价格

首先,看一看能量容量利用率的 ACF 在 Figure2-7 中,显示出周期性的起伏,这是非平稳的迹象。

为了消除季节性,我们首先应用resample方法来计算年均值,这个值将作为下面公式中的分母:

Seasonal Index = ValueofaSeasonalTimeSeries SeasonalAverage

因此,应用的结果,季节性指数,为我们提供了去季节化的时间序列。下面的代码展示了我们如何在 Python 中编写这个公式:

In [20]: seasonal_index = energy.resample('Q').mean() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [21]: dates = energy.index.year.unique() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) deseasonalized = [] for i in dates: for j in range(1, 13): deseasonalized.append((energy[str(i)][energy[str(i)]\ .index.month==j])) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) concat_deseasonalized = np.concatenate(deseasonalized) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [22]: deseason_energy = [] for i,s in zip(range(0, len(energy), 3), range(len(seasonal_index))): deseason_energy.append(concat_deseasonalized[i:i+3] / seasonal_index.iloc[s]) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) concat_deseason_energy = np.concatenate(deseason_energy) deseason_energy = pd.DataFrame(concat_deseason_energy, index=energy.index) deseason_energy.columns = ['Deaseasonalized Energy'] deseason_energy.head()Out[22]: Deaseasonalized Energy 2010-01-01 1.001737 2010-02-01 1.016452 2010-03-01 0.981811 2010-04-01 0.966758 2010-05-01 1.006862In [23]: sm.graphics.tsa.plot_acf(deseason_energy, lags=10) plt.xlabel('Number of Lags') plt.show()In [24]: sm.graphics.tsa.plot_pacf(deseason_energy, lags=10) plt.xlabel('Number of Lags') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (39)

计算能量利用率季度均值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (40)

定义进行季节性分析的年份

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (41)

计算季节性指数公式的分子

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (42)

拼接去季节化能量利用率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (43)

使用预定义公式计算季节性指数

Figure2-10 表明在滞后 1 和 2 处存在统计上显著的相关性,但 ACF 不显示任何周期特征,这是另一种说法是去季节化。

类似地,在 Figure2-11 中,尽管在某些滞后处有峰值,但 PACF 不显示任何周期性的起伏。因此,我们可以说使用季节性指数公式去季节化了数据。

现在我们得到的是能量容量利用率中较少的周期性起伏,这意味着数据被去季节化了。

最后,我们准备继续讨论时间序列模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (44)

图 2-10. 去季节化能量利用率的 ACF

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (45)

图 2-11. 能量利用率的去季节化 PACF

传统时间序列模型是单变量模型,它们遵循以下阶段:

识别

在这个过程中,我们使用 ACF 和 PACF 探索数据,识别模式并进行统计测试。

估计

我们通过适当的优化技术来估计系数。

诊断

在估计之后,我们需要检查信息准则或者 ACF/PACF 是否表明模型是有效的。如果是这样,我们将继续进入预测阶段。

预测

这部分更多关于模型的性能。在预测中,我们根据我们的估计预测未来值。

图2-12 展示了建模过程。因此,在识别变量和估计过程之后,执行模型。只有在运行适当的诊断之后,我们才能进行预测分析。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (46)

图2-12. 建模过程

在建模具有时间维度的数据时,我们应考虑时间上相邻点的相关性。这种考虑将我们引向时间序列建模。我在建模时间序列时的目标是拟合模型并理解时间上随机波动的统计特性。

回顾关于 IID 过程的讨论,它是最基本的时间序列模型,有时被称为 白噪声。让我们谈谈白噪声的概念。

如果时间序列 ϵ t 满足以下条件,则称其为白噪声:

ϵ t W N ( 0 , σ ϵ 2 )Corr ( ϵ t , ϵ s ) = 0 , t s

换句话说,ϵ t 的均值为 0,方差是恒定的。此外,ϵ t 的连续项之间没有相关性。可以说白噪声过程是平稳的,并且白噪声的图表在时间上表现为围绕均值以随机方式波动。然而,由于白噪声由不相关序列组成,从预测的角度来看并不是一个吸引人的模型。不相关序列阻碍我们预测未来值。

正如我们从以下代码片段和 图2-13 可以观察到的那样,白噪声围绕均值振荡,并且完全是随机的:

In [25]: mu = 0 std = 1 WN = np.random.normal(mu, std, 1000) plt.plot(WN) plt.xlabel('Number of Simulations') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (47)

图2-13. 白噪声过程

从这一点开始,我们需要在运行时间序列模型之前确定最佳滞后数。正如您可以想象的那样,决定最佳滞后数是一项具有挑战性的任务。最常用的方法是 ACF、PACF 和信息准则。ACF 和 PACF 已经讨论过;有关信息准则以及具体的阿卡伊凯信息准则(AIC)的更多信息,请参阅以下侧边栏。

请注意,如果所提议的模型是有限维的,则需要谨慎对待 AIC。这一事实由 Hurvich 和 Tsai(1989)很好地阐述:

如果真实模型是无限维的,这在实践中似乎是最现实的情况,AIC 提供了一个渐*有效的选择有限维逼*模型。然而,如果真实模型是有限维的,那么渐*有效的方法,例如,Akaike 的 FPE(Akaike 1970)、AIC 和 Parzen 的 CAT(Parzen 1977),不提供一致的模型顺序选择。

让我们开始学*经典的时间序列模型之一,移动平均模型。

移动平均模型

MA 和残差是密切相关的模型。MA 可以被视为一个平滑模型,因为它倾向于考虑残差的滞后值。为了简单起见,让我们从 MA(1)开始:

X t = ϵ t + α ϵ t-1

只要α 0,它就具有非平凡的相关结构。直观地说,MA(1)告诉我们时间序列仅受到ϵ tϵ t-1的影响。

一般形式上,MA(q)变为:

X t = ϵ t + α 1 ϵ t-1 + α 2 ϵ t-2 ... + α q ϵ t-q

从这一点开始,为了保持一致,我们将对两家主要科技公司,即苹果和微软的数据进行建模。雅虎财经提供了一个方便的工具,可访问相关股票在 2019 年 1 月 1 日至 2021 年 1 月 1 日期间的收盘价。

作为第一步,我们删除了缺失值,并检查数据是否平稳,结果显示苹果和微软的股票价格都没有预期的平稳结构。因此,接下来的步骤是对这些数据进行一阶差分以使其平稳,并将数据分割为训练集测试集。下面的代码(生成图2-14)展示了我们如何在 Python 中实现这一点:

In [26]: ticker = ['AAPL', 'MSFT'] start = datetime.datetime(2019, 1, 1) end = datetime.datetime(2021, 1, 1) stock_prices = yf.download(ticker, start, end, interval='1d')\ .Close ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) [*********************100%***********************] 2 of 2 completedIn [27]: stock_prices = stock_prices.dropna()In [28]: for i in ticker: stat_test = adfuller(stock_prices[i])[0:2] print("The ADF test statistic and p-value of {} are {}"\ .format(i,stat_test)) The ADF test statistic and p-value of AAPL are (0.29788764759932335, 0.9772473651259085) The ADF test statistic and p-value of MSFT are (-0.8345360070598484, 0.8087663305296826)In [29]: diff_stock_prices = stock_prices.diff().dropna()In [30]: split = int(len(diff_stock_prices['AAPL'].values) * 0.95) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) diff_train_aapl = diff_stock_prices['AAPL'].iloc[:split] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) diff_test_aapl = diff_stock_prices['AAPL'].iloc[split:] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) diff_train_msft = diff_stock_prices['MSFT'].iloc[:split] ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) diff_test_msft = diff_stock_prices['MSFT'].iloc[split:] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)In [31]: diff_train_aapl.to_csv('diff_train_aapl.csv') ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) diff_test_aapl.to_csv('diff_test_aapl.csv') diff_train_msft.to_csv('diff_train_msft.csv') diff_test_msft.to_csv('diff_test_msft.csv')In [32]: fig, ax = plt.subplots(2, 1, figsize=(10, 6)) plt.tight_layout() sm.graphics.tsa.plot_acf(diff_train_aapl,lags=30, ax=ax[0], title='ACF - Apple') sm.graphics.tsa.plot_acf(diff_train_msft,lags=30, ax=ax[1], title='ACF - Microsoft') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (48)

检索月度收盘股价

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (49)

将数据分割为 95%和 5%

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (50)

将苹果股价数据的 95%分配给训练集

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (51)

将苹果股票价格数据的 5%分配给测试集

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (52)

将微软股票价格数据的 95%分配给训练集

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (53)

将微软股票价格数据的 5%分配给测试集

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (54)

保存数据以备将来使用

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (55)

图2-14. 差分后的 ACF

观察图2-14 的顶部面板,我们可以看到在某些滞后期存在显著的峰值,因此我们将选择短 MA 模型的滞后期为 9,长 MA 模型的滞后期为 22。这意味着我们在 MA 建模中将使用 9 作为短期顺序,22 作为长期顺序:

In [33]: short_moving_average_appl = diff_train_aapl.rolling(window=9).mean() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) long_moving_average_appl = diff_train_aapl.rolling(window=22).mean() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [34]: fig, ax = plt.subplots(figsize=(10, 6)) ax.plot(diff_train_aapl.loc[start:end].index, diff_train_aapl.loc[start:end], label='Stock Price', linestyle='--') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) ax.plot(short_moving_average_appl.loc[start:end].index, short_moving_average_appl.loc[start:end], label = 'Short MA', linestyle='solid') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) ax.plot(long_moving_average_appl.loc[start:end].index, long_moving_average_appl.loc[start:end], label = 'Long MA', linestyle='solid') ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) ax.legend(loc='best') ax.set_ylabel('Price in $') ax.set_title('Stock Prediction-Apple') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (56)

苹果股票的短窗口移动平均

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (57)

苹果股票的长窗口移动平均

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (58)

苹果股票第一阶差分的折线图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (59)

可视化苹果公司短窗口 MA 结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (60)

可视化苹果公司长窗口 MA 结果

图2-15 展示了短期 MA 模型结果(实线)和长期 MA 模型结果(虚线点划线)。正如预期的那样,短期 MA 相对于苹果股票价格的日常变动更为敏感,而长期 MA 生成的预测更为平滑,这是合理的。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (61)

图2-15. 苹果 MA 模型预测结果

在下一步中,我们尝试使用不同窗口的 MA 模型来预测微软的股票价格。但在继续之前,让我说一下,选择适当的短期和长期 MA 分析窗口对于良好的建模至关重要。在图2-14 的底部面板中,似乎存在 2 和 22 的显著峰值,因此我们将在短期和长期 MA 分析中使用这些滞后期。确定窗口长度后,我们将数据拟合到以下应用的 MA 模型中:

In [35]: short_moving_average_msft = diff_train_msft.rolling(window=2).mean() long_moving_average_msft = diff_train_msft.rolling(window=22).mean()In [36]: fig, ax = plt.subplots(figsize=(10, 6)) ax.plot(diff_train_msft.loc[start:end].index, diff_train_msft.loc[start:end], label='Stock Price', linestyle='--') ax.plot(short_moving_average_msft.loc[start:end].index, short_moving_average_msft.loc[start:end], label = 'Short MA', linestyle='solid') ax.plot(long_moving_average_msft.loc[start:end].index, long_moving_average_msft.loc[start:end], label = 'Long MA', linestyle='-.') ax.legend(loc='best') ax.set_ylabel('$') ax.set_xlabel('Date') ax.set_title('Stock Prediction-Microsoft') plt.show()

类似地,基于短期 MA 分析的预测通常比长期 MA 模型更为敏感,如图2-16 所示。但在微软的情况下,短期 MA 预测似乎非常接*实际数据。这是我们在时间序列模型中所期望的,即具有短期视角的窗口能够更好地捕捉数据的动态变化,从而帮助我们获得更好的预测性能。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (62)

图2-16. 微软 MA 模型预测结果

自回归模型

连续项的依赖结构是 AR 模型最显著的特征,因为在这个模型中,当前值被回归到它自己的滞后值上。因此,我们基本上通过其过去的值的线性组合来预测时间序列 X t 的当前值。从数学上讲,AR(p)的一般形式可以写为:

X t = c + α 1 X t-1 + α 2 X t-2 ... + α p X t-p + ϵ t

其中 ϵ t 表示残差,c 是截距项。AR(p)模型意味着过去到第p阶的值在一定程度上对 X t 有解释力。如果关系记忆较短,则可能用较少的滞后数来建模 X t

我们已经讨论了时间序列的一个主要性质,即平稳性;另一个重要性质是可逆性。在介绍了 AR 模型之后,现在是展示 MA 过程的可逆性的时候了。如果它可以转化为一个无限的 AR 模型,则称为可逆。

在某些情况下,MA 可以被写成一个无限的 AR 过程。这些情况是具有平稳的协方差结构、确定性部分和可逆的 MA 过程。这样一来,我们就有了另一个称为无限 AR模型,这要归功于假设 | α | < 1

X t = ϵ t + α ϵ t-1= ϵ t + α ( X t-1 - α ϵ t-2 )= ϵ t + α X t-1 - α 2 ϵ t-2= ϵ t + α X t-1 - α 2 ( X t-2 + α ϵ t-3 )= ϵ t + α X t-1 - α 2 X t-2 + α 3 ϵ t-3 )= ...= α X t-1 - α 2 X t-2 + α 3 ϵ t-3 - α 4 ϵ t-4 + ... - (-α) n ϵ t-n

经过必要的数学运算后,方程式得到如下形式:

α n ϵ t-n = ϵ t - i=0 n-1 α i X t-i

在这种情况下,如果 | α | < 1 ,那么 n

𝔼 (ϵ t - i=0 n-1 α i X t-i ) 2 = 𝔼 ( α 2n ϵ t-n 2 )

最终,MA(1)过程变为:

ϵ t = i=0 α i X t-i

由于 AR 和 MA 过程之间的对偶性,可以将 AR(1) 表示为无限 MA,即 MA( )。换句话说,AR(1) 过程可以表示为创新过去值的函数:

X t = ϵ t + θ X t-1= θ ( θ X t-2 + ϵ t-1 ) + ϵ t= θ 2 X t-2 + θ ϵ t-1 + ϵ t= θ 2 ( θ X t-3 + θ ϵ t-2 ) θ ϵ t-1 + ϵ tX t = ϵ t + ϵ t-1 + θ 2 ϵ t-2 + ... + θ t X t

n 时,θ t 0 ,因此我可以将 AR(1) 表示为无限的 MA 过程。

在以下分析中,我们运行 AR 模型来预测苹果和微软的股票价格。与 MA 不同,偏自相关函数是找出 AR 模型中最优阶数的有用工具。这是因为在 AR 中,我们的目标是找出时间序列在两个不同时间点之间的关系,比如说 X tX t-k ,为此我们需要滤除中间滞后的影响,得到图 2-17 和 2-18:

In [37]: sm.graphics.tsa.plot_pacf(diff_train_aapl, lags=30) plt.title('PACF of Apple') plt.xlabel('Number of Lags') plt.show()In [38]: sm.graphics.tsa.plot_pacf(diff_train_msft, lags=30) plt.title('PACF of Microsoft') plt.xlabel('Number of Lags') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (63)

图 2-17. 苹果的偏自相关函数(PACF)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (64)

图 2-18. 微软的偏自相关函数(PACF)

在图 2-17,从苹果股票价格的一阶差分中获得,我们观察到在滞后 29 时有一个显著的峰值,在图 2-18,微软股票也有类似的在滞后 26 时的峰值。因此,29 和 26 是我们将在苹果和微软建模 AR 时使用的滞后数:

In [39]: from statsmodels.tsa.ar_model import AutoReg import warnings warnings.filterwarnings('ignore')In [40]: ar_aapl = AutoReg(diff_train_aapl.values, lags=29) ar_fitted_aapl = ar_aapl.fit() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [41]: ar_predictions_aapl = ar_fitted_aapl.predict(start=len(diff_train_aapl), end=len(diff_train_aapl)\ + len(diff_test_aapl) - 1, dynamic=False) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [42]: for i in range(len(ar_predictions_aapl)): print('==' * 25) print('predicted values:{:.4f} & actual values:{:.4f}'\ .format(ar_predictions_aapl[i], diff_test_aapl[i])) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) ================================================== predicted values:1.6511 & actual values:1.3200 ================================================== predicted values:-0.8398 & actual values:0.8600 ================================================== predicted values:-0.9998 & actual values:0.5600 ================================================== predicted values:1.1379 & actual values:2.4600 ================================================== predicted values:-0.1123 & actual values:3.6700 ================================================== predicted values:1.7843 & actual values:0.3600 ================================================== predicted values:-0.9178 & actual values:-0.1400 ================================================== predicted values:1.7343 & actual values:-0.6900 ================================================== predicted values:-1.5103 & actual values:1.5000 ================================================== predicted values:1.8224 & actual values:0.6300 ================================================== predicted values:-1.2442 & actual values:-2.6000 ================================================== predicted values:-0.5438 & actual values:1.4600 ================================================== predicted values:-0.1075 & actual values:-0.8300 ================================================== predicted values:-0.6167 & actual values:-0.6300 ================================================== predicted values:1.3206 & actual values:6.1000 ================================================== predicted values:0.2464 & actual values:-0.0700 ================================================== predicted values:0.4489 & actual values:0.8900 ================================================== predicted values:-1.3101 & actual values:-2.0400 ================================================== predicted values:0.5863 & actual values:1.5700 ================================================== predicted values:0.2480 & actual values:3.6500 ================================================== predicted values:0.0181 & actual values:-0.9200 ================================================== predicted values:0.9913 & actual values:1.0100 ================================================== predicted values:0.2672 & actual values:4.7200 ================================================== predicted values:0.8258 & actual values:-1.8200 ================================================== predicted values:0.1502 & actual values:-1.1500 ================================================== predicted values:0.5560 & actual values:-1.0300In [43]: ar_predictions_aapl = pd.DataFrame(ar_predictions_aapl) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) ar_predictions_aapl.index = diff_test_aapl.index ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png)In [44]: ar_msft = AutoReg(diff_train_msft.values, lags=26) ar_fitted_msft = ar_msft.fit() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)In [45]: ar_predictions_msft = ar_fitted_msft.predict(start=len(diff_train_msft), end=len(diff_train_msft)\ +len(diff_test_msft) - 1, dynamic=False) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)In [46]: ar_predictions_msft = pd.DataFrame(ar_predictions_msft) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) ar_predictions_msft.index = diff_test_msft.index ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (65)

使用 AR 模型拟合苹果股票数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (66)

预测苹果的股票价格

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (67)

比较预测值和实际观察值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (68)

将数组转换为数据框以分配索引

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (69)

将测试数据索引分配给预测值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (70)

使用 AR 模型拟合微软股票数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (71)

预测微软的股票价格

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (72)

将数组转换为数据框以分配索引

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (73)

将测试数据索引分配给预测值

下面的代码,导致图 2-19,显示基于 AR 模型的预测。实线代表苹果和微软股票价格的预测,虚线表示真实数据。结果表明,MA 模型在捕捉股票价格方面优于 AR 模型:

In [47]: fig, ax = plt.subplots(2,1, figsize=(18, 15)) ax[0].plot(diff_test_aapl, label='Actual Stock Price', linestyle='--') ax[0].plot(ar_predictions_aapl, linestyle='solid', label="Prediction") ax[0].set_title('Predicted Stock Price-Apple') ax[0].legend(loc='best') ax[1].plot(diff_test_msft, label='Actual Stock Price', linestyle='--') ax[1].plot(ar_predictions_msft, linestyle='solid', label="Prediction") ax[1].set_title('Predicted Stock Price-Microsoft') ax[1].legend(loc='best') for ax in ax.flat: ax.set(xlabel='Date', ylabel='$') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (74)

图 2-19. AR 模型预测结果

自回归积分移动平均模型

ARIMA 是时间序列的过去值和白噪声的函数。ARIMA 被提出作为 AR 和 MA 的泛化,但它们没有积分参数,这有助于我们用原始数据来训练模型。在这方面,即使我们包括非平稳数据,ARIMA 通过正确定义积分参数使其变得平稳。

ARIMA 有三个参数,分别是pdq。正如之前的时间序列模型所熟悉的,pq分别指的是 AR 和 MA 的阶数。d参数控制级别差异。如果d = 1,那么它等同于一阶差分,如果值为 0,则表示模型是 ARIMA。

d大于 1 是可能的,但不像d为 1 那样普遍。ARIMA(p, 1, q)方程的结构如下:

X t = α 1 d X t-1 + α 2 d X t-2 ... + α p d X t-p + ϵ t + β 1 ϵ t-1 + β 2 ϵ t-2 ... + β q ϵ t-q

其中d指的是差分。

作为被广泛接受和适用的模型,让我们讨论 ARIMA 模型的优缺点以更熟悉它。

优点

  • ARIMA 允许我们使用原始数据工作,而不考虑其是否平稳。

  • 它在高频数据下表现良好。

  • 与其他模型相比,它对数据波动不太敏感。

缺点

  • ARIMA 可能无法捕捉季节性。

  • 它在长序列和短期(日常,小时)数据中表现更好。

  • 由于 ARIMA 没有自动更新,分析期间不应观察到结构性突变。

  • ARIMA 过程中没有调整会导致不稳定性。

现在,让我们看看如何使用相同的股票(即苹果和微软)来运行 ARIMA。但是,这次使用不同的短期滞后结构来比较结果与 AR 和 MA 模型:

In [48]: from statsmodels.tsa.arima_model import ARIMAIn [49]: split = int(len(stock_prices['AAPL'].values) * 0.95) train_aapl = stock_prices['AAPL'].iloc[:split] test_aapl = stock_prices['AAPL'].iloc[split:] train_msft = stock_prices['MSFT'].iloc[:split] test_msft = stock_prices['MSFT'].iloc[split:]In [50]: arima_aapl = ARIMA(train_aapl,order=(9, 1, 9)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) arima_fit_aapl = arima_aapl.fit() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [51]: arima_msft = ARIMA(train_msft, order=(6, 1, 6)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) arima_fit_msft = arima_msft.fit() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [52]: arima_predict_aapl = arima_fit_aapl.predict(start=len(train_aapl), end=len(train_aapl)\ + len(test_aapl) - 1, dynamic=False) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) arima_predict_msft = arima_fit_msft.predict(start=len(train_msft), end=len(train_msft)\ + len(test_msft) - 1, dynamic=False) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)In [53]: arima_predict_aapl = pd.DataFrame(arima_predict_aapl) arima_predict_aapl.index = diff_test_aapl.index arima_predict_msft = pd.DataFrame(arima_predict_msft) arima_predict_msft.index = diff_test_msft.index ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (75)

配置用于苹果股票的 ARIMA 模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (76)

将 ARIMA 模型拟合到苹果的股票价格。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (77)

配置用于微软股票的 ARIMA 模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (78)

将 ARIMA 模型拟合到微软的股票价格。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (79)

基于 ARIMA 预测苹果股票价格。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (80)

基于 ARIMA 预测微软股价。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (81)

形成预测的索引。

接下来的片段,导致 图 2-20 显示了基于苹果和微软股票价格的预测结果,由于我们采用了 AR 和 MA 模型的短期订单,因此结果并非完全不同:

In [54]: fig, ax = plt.subplots(2, 1, figsize=(18, 15)) ax[0].plot(diff_test_aapl, label='Actual Stock Price', linestyle='--') ax[0].plot(arima_predict_aapl, linestyle='solid', label="Prediction") ax[0].set_title('Predicted Stock Price-Apple') ax[0].legend(loc='best') ax[1].plot(diff_test_msft, label='Actual Stock Price', linestyle='--') ax[1].plot(arima_predict_msft, linestyle='solid', label="Prediction") ax[1].set_title('Predicted Stock Price-Microsoft') ax[1].legend(loc='best') for ax in ax.flat: ax.set(xlabel='Date', ylabel='$') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (82)

图 2-20. ARIMA 预测结果。

在这一点上,讨论一种用于时间序列模型最优滞后选择的替代方法是值得的。这里我应用的方法是 AIC,以选择适当的滞后数目。请注意,即使 AIC 的结果建议 (4, 0, 4),但模型不会以这些顺序收敛。因此,改为应用 (4, 1, 4):

In [55]: import itertoolsIn [56]: p = q = range(0, 9) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) d = range(0, 3) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) pdq = list(itertools.product(p, d, q)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) arima_results_aapl = [] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) for param_set in pdq: try: arima_aapl = ARIMA(train_aapl, order=param_set) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) arima_fitted_aapl = arima_aapl.fit() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) arima_results_aapl.append(arima_fitted_aapl.aic) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) except: continue print('**'*25) print('The Lowest AIC score is' + \ '{:.4f} and the corresponding parameters are {}'.format( \ pd.DataFrame(arima_results_aapl).where( \ pd.DataFrame(arima_results_aapl).T.notnull().all()).min()[0], pdq[arima_results_aapl.index(min(arima_results_aapl))])) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) ************************************************** The Lowest AIC score is 1951.9810 and the corresponding parameters are (4, 0, 4)In [57]: arima_aapl = ARIMA(train_aapl, order=(4, 1, 4)) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png) arima_fit_aapl = arima_aapl.fit() ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)In [58]: p = q = range(0, 6) d = range(0, 3) pdq = list(itertools.product(p, d, q)) arima_results_msft = [] for param_set in pdq: try: arima_msft = ARIMA(stock_prices['MSFT'], order=param_set) arima_fitted_msft = arima_msft.fit() arima_results_msft.append(arima_fitted_msft.aic) except: continue print('**' * 25) print('The lowest AIC score is {:.4f} and parameters are {}' .format(pd.DataFrame(arima_results_msft) .where(pd.DataFrame(arima_results_msft).T.notnull()\ .all()).min()[0], pdq[arima_results_msft.index(min(arima_results_msft))])) ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png) ************************************************** The Lowest AIC score is 2640.6367 and the corresponding parameters are (4, 2, 4)In [59]: arima_msft = ARIMA(stock_prices['MSFT'], order=(4, 2 ,4)) ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png) arima_fit_msft= arima_msft.fit() ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png)In [60]: arima_predict_aapl = arima_fit_aapl.predict(start=len(train_aapl), end=len(train_aapl)\ +len(test_aapl) - 1, dynamic=False) ![12](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/12.png) arima_predict_msft = arima_fit_msft.predict(start=len(train_msft), end=len(train_msft)\ + len(test_msft) - 1, dynamic=False) ![12](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/12.png)In [61]: arima_predict_aapl = pd.DataFrame(arima_predict_aapl) arima_predict_aapl.index = diff_test_aapl.index arima_predict_msft = pd.DataFrame(arima_predict_msft) arima_predict_msft.index = diff_test_msft.index

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (83)

定义 AR 和 MA 订单的范围。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (84)

定义一个区间差分项。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (85)

pdq 进行迭代应用。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (86)

创建一个空列表以存储 AIC 值。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (87)

配置以适应苹果数据的 ARIMA 模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (88)

运行包含所有可能滞后的 ARIMA 模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (89)

将 AIC 值存储到列表中。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (90)

打印苹果数据的最低 AIC 值。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (91)

配置和拟合具有最佳顺序的 ARIMA 模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (92)

运行包括所有可能滞后项的 ARIMA 模型,针对微软的数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (93)

将 ARIMA 模型拟合到微软数据的最佳订单

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (94)

预测苹果和微软的股票价格

苹果和微软的订单识别分别为(4, 1, 4)和(4, 2, 4)。ARIMA 在预测股票价格方面表现出色,如下所示。然而,请注意,订单识别不当会导致拟合不佳,进而产生令人不满意的预测结果。以下代码,导致 Figure2-21 显示了这些结果:

In [62]: fig, ax = plt.subplots(2, 1, figsize=(18, 15)) ax[0].plot(diff_test_aapl, label='Actual Stock Price', linestyle='--') ax[0].plot(arima_predict_aapl, linestyle='solid', label="Prediction") ax[0].set_title('Predicted Stock Price-Apple') ax[0].legend(loc='best') ax[1].plot(diff_test_msft, label='Actual Stock Price', linestyle='--') ax[1].plot(arima_predict_msft, linestyle='solid', label="Prediction") ax[1].set_title('Predicted Stock Price-Microsoft') ax[1].legend(loc='best') for ax in ax.flat: ax.set(xlabel='Date', ylabel='$') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (95)

图 2-21. ARIMA 预测结果

时间序列分析在金融分析中起着核心作用。这仅仅是因为大多数金融数据都具有时间维度,因此这类数据应谨慎建模。本章对具有时间维度的数据进行了初步建模尝试,为此,我们采用了经典的时间序列模型,即 MA、AR,最后是 ARIMA。但你认为这就是全部吗?绝对不是!在下一章中,我们将看到如何使用深度学*模型对时间序列进行建模。

本章引用的文章:

  • Cavanaugh, J. E., and A. A. Neath. 2019. “Akaike 信息准则:背景、推导、性质、应用、解释及改进。” Wiley Interdisciplinary Reviews: Computational Statistics 11 (3): e1460.

  • Hurvich, Clifford M., and Chih-Ling Tsai. 1989. “小样本中的回归和时间序列模型选择。” Biometrika 76 (2): 297-30.

本章引用的书籍:

  • Brockwell, Peter J., and Richard A. Davis. 2016. 时间序列和预测导论。Springer.

  • Focardi, Sergio M. 1997. 市场建模:新理论和技术。The Frank J. Fabozzi Series, Vol. 14. 纽约:John Wiley and Sons.

... 是的,图灵机可以在有足够内存和时间的情况下计算任何可计算函数,但自然界必须实时解决问题。为此,它利用了类似于地球上最强大的计算机的大规模并行处理器的大脑神经网络。在它们上面高效运行的算法最终将获胜。

Terrence J. Sejnowski(2018)

深度学*最*成为一种流行词有其充分的理由,尽管最*试图改进深度学*实践并不是第一次尝试。然而,深度学*为何*二十年来备受推崇,这是很容易理解的。深度学*是一个抽象的概念,用几句话来定义它是很困难的。与神经网络(NN)不同,深度学*具有更复杂的结构,隐藏层定义了复杂性。因此,一些研究人员使用隐藏层的数量作为区分神经网络和深度学*的比较基准,这是一种有用但不特别严格的区分方式。一个更好的定义可以澄清这种区别。

在高层次上,深度学*可以定义为:

深度学*方法是多层次表示学*¹方法,通过组合简单但非线性的模块,在每个级别将表示从一个级别(从原始输入开始)转换为稍高、稍抽象的级别的表示。

Le Cunn 等人(2015)

深度学*的应用可以追溯到上世纪 40 年代,当时 Norbert Wiener 出版了《控制论》。在 1980 年代和 1990 年代之间,连接主义思维主导了这一领域。如今,深度学*的最新发展,比如反向传播和神经网络,塑造了我们所知道的这一领域。基本上,深度学*经历了三波发展,因此我们可能会想知道为什么深度学*如今如此盛行?Goodfellow 等人(2016)列举了一些可能的原因,包括:

  • 数据规模增加

  • 模型规模增加

  • 提高准确性、复杂性和实际影响

现代技术和数据可用性似乎为一个深度学*时代铺平了道路,在这个时代,我们提出了新的数据驱动方法,使我们能够使用非常规模型对时间序列建模。这一发展引发了深度学*的新浪潮。在能够包括更长时间段的能力方面,两种方法表现出色:循环神经网络(RNN)和长短期记忆网络(LSTM)。在本节中,我们将简要讨论理论背景后,集中讨论这些模型在 Python 中的实用性。

RNN 具有神经网络结构,至少有一个反馈连接,使得网络可以学*序列。反馈连接导致循环,使我们能够揭示非线性特性。这种类型的连接带来了一个新而非常有用的属性:记忆。因此,RNN 不仅可以利用输入数据,还可以利用先前的输出,这在时间序列建模时显得非常吸引人。

RNN 有多种形式,例如:

一对一

一对一的 RNN 包含单个输入和单个输出,这使得它成为最基本的 RNN 类型。

一对多

在这种形式中,RNN 对单个输入产生多个输出。

多对一

不同于一对多结构,多对一具有多个输入和单个输出。

多对多

这种结构具有多个输入和输出,被称为 RNN 的最复杂结构。

在 RNN 中,隐藏单元将其自身的输出反馈到神经网络中,使得 RNN 具有递归层(不同于前馈神经网络),这使得它成为建模时间序列数据的合适方法。因此,在 RNN 中,神经元的激活来自前一个时间步,表明 RNN 将其表示为网络实例的累积状态(Buduma 和 Locascio 2017)。

正如 Nielsen(2019)总结的那样:

  • RNN 按顺序逐个时间步处理。

  • 网络的状态从一个时间步保持不变到另一个时间步。

  • 一个 RNN 根据时间步更新其状态。

这些维度在 Figure3-1 中有所说明。正如可以看到的那样,右侧的 RNN 结构具有时间步长,这是它与前馈网络之间的主要区别。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (96)

图 3-1. RNN 结构²

RNN 具有三维输入,包括:

  • 批处理大小

  • 时间步长

  • 特征数

批处理大小 表示观察次数或数据行数。时间步 是馈送模型的次数。最后,特征数 是每个样本的列数。

我们将从以下代码开始:

In [1]: import numpy as np import pandas as pd import math import datetime import yfinance as yf import matplotlib.pyplot as plt import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.callbacks import EarlyStopping from tensorflow.keras.layers import (Dense, Dropout, Activation, Flatten, MaxPooling2D, SimpleRNN) from sklearn.model_selection import train_test_splitIn [2]: n_steps = 13 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) n_features = 1 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [3]: model = Sequential() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) model.add(SimpleRNN(512, activation='relu', input_shape=(n_steps, n_features), return_sequences=True)) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) model.add(Dropout(0.2)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) model.add(Dense(256, activation = 'relu')) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) model.add(Flatten()) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) model.add(Dense(1, activation='linear')) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png)In [4]: model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mse']) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)In [5]: def split_sequence(sequence, n_steps): X, y = [], [] for i in range(len(sequence)): end_ix = i + n_steps if end_ix > len(sequence) - 1: break seq_x, seq_y = sequence[i:end_ix], sequence[end_ix] X.append(seq_x) y.append(seq_y) return np.array(X), np.array(y) ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (97)

定义预测步骤的数量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (98)

将特征数定义为 1

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (99)

调用一个顺序模型来运行 RNN

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (100)

确定隐藏神经元的数量、激活函数和输入形状

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (101)

添加一个 dropout 层以防止过拟合

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (102)

添加一个具有 relu 激活函数的 256 个神经元的隐藏层

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (103)

将模型展平以将三维矩阵转换为向量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (104)

添加具有 linear 激活函数的输出层

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (105)

编译 RNN 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (106)

创建一个依赖变量 y

在配置模型并生成一个依赖变量之后,让我们提取数据并为苹果和微软的股票价格运行预测:

In [6]: ticker = ['AAPL', 'MSFT'] start = datetime.datetime(2019, 1, 1) end = datetime.datetime(2020, 1 ,1) stock_prices = yf.download(ticker,start=start, end = end, interval='1d')\ .Close [*********************100%***********************] 2 of 2 completedIn [7]: diff_stock_prices = stock_prices.diff().dropna()In [8]: split = int(len(diff_stock_prices['AAPL'].values) * 0.95) diff_train_aapl = diff_stock_prices['AAPL'].iloc[:split] diff_test_aapl = diff_stock_prices['AAPL'].iloc[split:] diff_train_msft = diff_stock_prices['MSFT'].iloc[:split] diff_test_msft = diff_stock_prices['MSFT'].iloc[split:]In [9]: X_aapl, y_aapl = split_sequence(diff_train_aapl, n_steps) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) X_aapl = X_aapl.reshape((X_aapl.shape[0], X_aapl.shape[1], n_features)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [10]: history = model.fit(X_aapl, y_aapl, epochs=400, batch_size=150, verbose=0, validation_split = 0.10) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [11]: start = X_aapl[X_aapl.shape[0] - n_steps] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) x_input = start ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) x_input = x_input.reshape((1, n_steps, n_features))In [12]: tempList_aapl = [] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) for i in range(len(diff_test_aapl)): x_input = x_input.reshape((1, n_steps, n_features)) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) yhat = model.predict(x_input, verbose=0) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) x_input = np.append(x_input, yhat) x_input = x_input[1:] tempList_aapl.append(yhat) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)In [13]: X_msft, y_msft = split_sequence(diff_train_msft, n_steps) X_msft = X_msft.reshape((X_msft.shape[0], X_msft.shape[1], n_features))In [14]: history = model.fit(X_msft, y_msft, epochs=400, batch_size=150, verbose=0, validation_split = 0.10)In [15]: start = X_msft[X_msft.shape[0] - n_steps] x_input = start x_input = x_input.reshape((1, n_steps, n_features))In [16]: tempList_msft = [] for i in range(len(diff_test_msft)): x_input = x_input.reshape((1, n_steps, n_features)) yhat = model.predict(x_input, verbose=0) x_input = np.append(x_input, yhat) x_input = x_input[1:] tempList_msft.append(yhat)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (107)

调用 split_sequence 函数来定义回顾期

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (108)

将训练数据转换成三维情况

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (109)

将 RNN 模型拟合到苹果的股票价格

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (110)

定义苹果预测的起始点

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (111)

重命名变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (112)

创建一个空列表以存储预测结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (113)

调整用于预测的 x_input 的形状

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (114)

运行苹果股票的预测

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (115)

yhat 存储到 tempList_aapl

为了可视化,使用以下代码块,结果为 图3-2:

In [17]: fig, ax = plt.subplots(2,1, figsize=(18,15)) ax[0].plot(diff_test_aapl, label='Actual Stock Price', linestyle='--') ax[0].plot(diff_test_aapl.index, np.array(tempList_aapl).flatten(), linestyle='solid', label="Prediction") ax[0].set_title('Predicted Stock Price-Apple') ax[0].legend(loc='best') ax[1].plot(diff_test_msft, label='Actual Stock Price', linestyle='--') ax[1].plot(diff_test_msft.index,np.array(tempList_msft).flatten(), linestyle='solid', label="Prediction") ax[1].set_title('Predicted Stock Price-Microsoft') ax[1].legend(loc='best') for ax in ax.flat: ax.set(xlabel='Date', ylabel='$') plt.show()

图3-2 展示了苹果和微软的股票价格预测结果。简单地观察可以很容易地发现,在预测模型的性能方面还有改进的空间。

即使我们可以有令人满意的预测性能,也不应忽视 RNN 模型的缺点。该模型的主要缺点是:

  • 梯度消失或梯度爆炸问题(请参阅以下说明详细解释)。

  • 训练 RNN 是一项非常困难的任务,因为它需要大量数据。

  • 当使用 tanh 激活函数时,RNN 无法处理非常长的序列。

注意

梯度消失是深度学*场景中常见的问题,如果不恰当设计,梯度消失问题会出现。在反向传播过程中,如果梯度趋向变小,则意味着神经元学*速度太慢,优化停滞不前。

与梯度消失问题不同,梯度爆炸问题发生在反向传播的微小变化导致优化过程中权重的巨大更新时。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (116)

图 3-2. RNN 预测结果

Haviv 等人(2019)明确指出了 RNN 的缺点:

这是因为网络依赖于其过去的状态,并通过这些状态依赖于整个输入历史。这种能力带来了代价——RNN 被认为很难训练(Pascanu 等人 2013a)。这种困难通常与在尝试在长时间内传播错误时出现的梯度消失有关(Hochreiter 1998)。当训练成功时,网络的隐藏状态代表了这些记忆。理解这种表示在训练过程中是如何形成的,可以为改进与记忆相关的任务的学*开辟新的途径。

LSTM 深度学*方法由 Hochreiter 和 Schmidhuber(1997)开发,主要基于门控循环单元(GRU)。

GRU 是为了解决梯度消失问题而提出的,这在神经网络结构中很常见,当权重更新变得太小,无法对网络产生显著变化时,就会发生梯度消失。GRU 由两个门组成:更新重置。当检测到早期观察非常重要时,我们不会更新隐藏状态。同样,当早期观察不重要时,会导致状态重置。

如 正如之前讨论的,RNN 最吸引人的特性之一是它能够连接过去和现在的信息。然而,当长期依赖出现时,这种能力就会失败。长期依赖意味着模型从早期的观察中学*。

例如,让我们来审视以下句子:

各国都有自己的货币,例如美国,人们用美元进行交易……

在短期依赖的情况下,人们知道下一个预测的词是关于货币的,但如果问到是哪种货币呢?事情变得复杂,因为我们可能在文中早些时候提到过各种货币,暗示了长期依赖。需要回溯到更早的部分,找到与使用美元的国家相关的内容。

LSTM 试图解决 RNN 在长期依赖方面的弱点。LSTM 拥有一个非常有用的工具来去除不必要的信息,从而提高效率。LSTM 使用门控机制,使其能够忘记无关的数据。这些门包括:

忘记门的作用是筛选出必要和不必要的信息,以使 LSTM 比 RNN 更加高效。在这样做的过程中,当信息无关时,激活函数的值sigmoid变为零。忘记门的公式可以表示为:

F t = σ ( X t W I + h t-1 W f + b f )

其中 σ 是激活函数,h t-1 是先前的隐藏状态,W IW f 是权重,最后,b f 是遗忘单元中的偏置参数。

输入门由当前时间步的 X t 和上一个时间步的隐藏状态 t - 1 提供。输入门的目标是确定应将信息添加到长期状态的程度。可以如此表述输入门:

I t = σ ( X t W I + h t-1 W f + b I )

输出门基本上决定了应该读取的输出程度,并且工作如下:

O t = σ ( X t W o + h t-1 W o + b I )

这些门不是 LSTM 的唯一组成部分。其他组成部分包括:

  • 候选记忆单元

  • 记忆单元

  • 隐藏状态

候选记忆单元决定信息传递到单元状态的程度。不同的是,候选单元中的激活函数是 tanh,并且采用以下形式:

C t ^ = ϕ ( X t W c + h t-1 W c + b c )

内存单元允许 LSTM 记住或遗忘信息:

C t = F t C + t - 1 + I t C t ^

其中 是哈达玛积。

在这种循环网络中,隐藏状态是循环信息的工具。记忆单元将输出门与隐藏状态联系起来:

h t = ϕ ( c t ) O t

图3-3 展示了 LSTM 结构。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (117)

图3-3. LSTM 结构

现在,让我们使用 LSTM 预测股票价格:

In [18]: from tensorflow.keras.layers import LSTMIn [19]: n_steps = 13 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) n_features = 1 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [20]: model = Sequential() model.add(LSTM(512, activation='relu', input_shape=(n_steps, n_features), return_sequences=True)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) model.add(Dropout(0.2)) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) model.add(LSTM(256,activation='relu')) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) model.add(Flatten())![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) model.add(Dense(1, activation='linear')) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)In [21]: model.compile(optimizer='rmsprop', loss='mean_squared_error', metrics=['mse']) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png)In [22]: history = model.fit(X_aapl, y_aapl, epochs=400, batch_size=150, verbose=0, validation_split = 0.10) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png)In [23]: start = X_aapl[X_aapl.shape[0] - 13] x_input = start x_input = x_input.reshape((1, n_steps, n_features))

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (118)

定义预测步骤的数量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (119)

将特征数定义为 1

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (120)

确定隐藏神经元的数量,激活函数为 relu,以及输入形状

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (121)

添加一个 dropout 层以防止过拟合

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (122)

添加一个具有 256 个神经元的额外隐藏层,使用 relu 激活函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (123)

将模型展平以向量化三维矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (124)

添加一个具有 linear 激活函数的输出层

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (125)

使用均方根传播 rmsprop 和均方误差(MSE) mean_squared_error 编译 LSTM

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (126)

将 LSTM 模型拟合到苹果的股票价格

注意

根均方传播(RMSProp)是一种优化方法,其中我们计算每个权重的平方梯度的移动平均值。然后找出权重的差异,用于计算新权重:

v t = ρ v t-1 + 1 - ρ g t 2Δ w t = - ν η+ϵ g tw t+1 = w t + Δ w t

按照相同的步骤,并给出微软的股票价格,进行预测分析:

In [24]: tempList_aapl = [] for i in range(len(diff_test_aapl)): x_input = x_input.reshape((1, n_steps, n_features)) yhat = model.predict(x_input, verbose=0) x_input = np.append(x_input, yhat) x_input = x_input[1:] tempList_aapl.append(yhat)In [25]: history = model.fit(X_msft, y_msft, epochs=400, batch_size=150, verbose=0, validation_split = 0.10)In [26]: start = X_msft[X_msft.shape[0] - 13] x_input = start x_input = x_input.reshape((1, n_steps, n_features))In [27]: tempList_msft = [] for i in range(len(diff_test_msft)): x_input = x_input.reshape((1, n_steps, n_features)) yhat = model.predict(x_input, verbose=0) x_input = np.append(x_input, yhat) x_input = x_input[1:] tempList_msft.append(yhat)

下面的代码创建了显示预测结果的图表(图 3-4):

In [28]: fig, ax = plt.subplots(2, 1, figsize=(18, 15)) ax[0].plot(diff_test_aapl, label='Actual Stock Price', linestyle='--') ax[0].plot(diff_test_aapl.index, np.array(tempList_aapl).flatten(), linestyle='solid', label="Prediction") ax[0].set_title('Predicted Stock Price-Apple') ax[0].legend(loc='best') ax[1].plot(diff_test_msft, label='Actual Stock Price', linestyle='--') ax[1].plot(diff_test_msft.index, np.array(tempList_msft).flatten(), linestyle='solid', label="Prediction") ax[1].set_title('Predicted Stock Price-Microsoft') ax[1].legend(loc='best') for ax in ax.flat: ax.set(xlabel='Date', ylabel='$') plt.show()

LSTM 似乎在捕捉极值方面优于 RNN。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (127)

图 3-4。LSTM 预测结果

本章讨论了基于深度学*预测股票价格的问题。使用的模型是 RNN 和 LSTM,它们具有处理更长时间周期的能力。这些模型并未显示出显著的改进,但仍可用于建模时间序列数据。在我们的案例中,LSTM 考虑了 13 步的回溯期进行预测。对于扩展,将多个特征包含在基于深度学*的模型中是一个明智的方法,而这是参数化时间序列模型所不允许的。

在下一章中,我们将讨论基于参数化和 ML 模型的波动率预测,以便比较它们的性能。

本章引用的文章:

  • Ding, Daizong 等人。2019 年。“时间序列预测中极端事件建模。” 第 25 届 ACM SIGKDD 国际数据挖掘会议论文集。1114-1122。

  • Haviv,Doron,Alexander Rivkind 和 Omri Barak。2019 年。“理解和控制递归神经网络中的记忆。” arXiv 预印本。arXiv:1902.07275。

  • Hochreiter, Sepp 和 Jürgen Schmidhuber. 1997 年。“长短期记忆。” 神经计算 9(8):1735-1780。

  • LeCun,Yann,Yoshua Bengio 和 Geoffrey Hinton。2015 年。“深度学*。” 自然 521(7553):436-444。

本章引用的书籍:

  • Buduma,N.和 N. Locascio。2017 年。深度学*基础:设计下一代机器智能算法。Sebastopol:O’Reilly。

  • Goodfellow,I.,Y. Bengio 和 A. Courville。2016 年。深度学*。剑桥,MA:MIT 出版社。

  • Nielsen, A. 2019. 实用时间序列分析:统计与机器学*预测. Sebastopol: O’Reilly.

  • Patterson, Josh, 和 Adam Gibson. 2017. 深度学*:从业者的视角. Sebastopol: O’Reilly.

  • Sejnowski, Terrence J. 2018. 深度学*革命. 剑桥, 麻省: MIT Press.

¹ 表征学*帮助我们以独特的方式定义一个概念。例如,如果任务是检测某物是否为圆形,则边缘起着关键作用,因为圆形没有边缘。因此,我们可以使用颜色、形状和大小来为对象创建一个表示。本质上,这就是人脑的工作方式,我们知道深度学*结构受到大脑功能的启发。

² Patterson 等人, 2017. “深度学*:从业者的视角.”

条件回报分布最关键的特征可能是其二阶矩结构,这在经验上是分布的主导时变特征。这一事实催生了关于回报波动建模和预测的大量文献。

Andersen 等人(2003)

“有些概念易于理解但难以定义。这也适用于波动性。” 这可能是马科维茨之前某位生前的话,因为他对波动性的建模非常清晰和直观。马科维茨提出了他著名的投资组合理论,在其中他将波动性定义为标准差,从那时起,金融领域与数学更加紧密地联系在一起。

波动性在金融中是支柱,因为它不仅为投资者提供信息信号,还是各种金融模型的输入。波动性如此重要的原因是什么?答案强调了不确定性的重要性,这是金融模型的主要特征。

金融市场的增加整合导致了这些市场长期不确定性的延续,进而强调了波动性的重要性,即金融资产价值变动的程度。作为风险的代理,波动性是许多领域(包括资产定价和风险管理)中最重要的变量之一。它的强烈存在和延迟使得必须进行建模。作为风险度量,波动性在 1996 年生效的巴塞尔协议后在风险管理中扮演了关键角色(Karasan 和 Gaygisiz 2020)。

在 Black(1976)开创性研究之后,关于波动率估计的大量文献逐渐涌现,包括 Andersen 和 Bollerslev(1997)、Raju 和 Ghosh(2004)、Dokuchaev(2014)以及 De Stefani 等人(2017)。我们谈论的是使用 ARCH 和 GARCH 类型模型进行波动率预测的长期传统,这些模型存在一些可能导致失败的缺陷,如波动率聚类、信息不对称等。尽管这些问题被不同模型解决,但是最*金融市场的波动加上机器学*的发展使得研究人员重新思考了波动率估计。

本章旨在展示如何利用基于机器学*的模型提升预测性能。我们将介绍各种机器学*算法,包括支持向量回归、神经网络和深度学*,以便进行预测性能比较。

对波动率进行建模相当于对不确定性进行建模,以便更好地理解和处理不确定性,从而使我们能够对真实世界有足够好的*似。为了评估建议模型在多大程度上符合实际情况,我们需要计算回报波动率,也称为实现波动率。实现波动率是实现方差的平方根,即回报的平方和。实现波动率用于计算波动率预测方法的性能。以下是回报波动率的公式:

σ ^ = 1 n-1 n=1 N (r n -μ) 2

其中rμ分别是回报和回报均值,n是观察次数。

让我们看看如何在 Python 中计算回报波动率:

In [1]: import numpy as np from scipy.stats import norm import scipy.optimize as opt import yfinance as yf import pandas as pd import datetime import time from arch import arch_model import matplotlib.pyplot as plt from numba import jit from sklearn.metrics import mean_squared_error as mse import warnings warnings.filterwarnings('ignore')In [2]: stocks = '^GSPC' start = datetime.datetime(2010, 1, 1) end = datetime.datetime(2021, 8, 1) s_p500 = yf.download(stocks, start=start, end = end, interval='1d') [*********************100%***********************] 1 of 1 completedIn [3]: ret = 100 * (s_p500.pct_change()[1:]['Adj Close']) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) realized_vol = ret.rolling(5).std()In [4]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol.index,realized_vol) plt.title('Realized Volatility- S&P-500') plt.ylabel('Volatility') plt.xlabel('Date') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (128)

根据调整后的收盘价格计算标准普尔 500 指数的回报。

图表4-1 展示了 2010 年至 2021 年间标准普尔 500 指数的实现波动率。最引人注目的观察结果是在 COVID-19 大流行期间出现的波动率尖峰。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (129)

图 4-1. 实现波动率—标准普尔 500 指数

波动率估计方式对相关分析的可靠性和准确性有着不可否认的影响。因此,本章既涉及经典波动率预测技术,也涉及基于机器学*的波动率预测技术,旨在展示后者更为优越的预测性能。为了比较全新的基于机器学*的模型,我们从建模经典波动率模型开始。一些非常著名的经典波动率模型包括但不限于以下几种:

  • ARCH

  • GARCH

  • GJR-GARCH

  • EGARCH

现在是深入经典波动率模型的时候了。让我们从 ARCH 模型开始。

早期对波动率建模的一种尝试由 Eagle (1982)提出,称为 ARCH 模型。ARCH 模型是一个基于历史资产回报的单变量模型。ARCH(p)模型具有以下形式:

σ t 2 = ω + k=1 p α k (r t-k ) 2

其中均值模型为:

r t = σ t ϵ t

其中ϵ t被假设为正态分布。在这个参数模型中,我们需要满足一些假设条件以确保严格的正方差。在这方面,以下条件应当成立:

  • ω > 0

  • α k 0

所有这些方程告诉我们,ARCH 是一个单变量且非线性的模型,其中波动率是通过过去回报的平方来估计的。ARCH 最显著的特征之一是具有时变条件方差的性质¹,因此 ARCH 能够模拟被称为波动率聚集的现象——即大变化往往会后续出现大的变化,不论其方向如何,而小变化则往往会后续出现小的变化,正如 Mandelbrot (1963)所描述的。因此,一旦市场发布重要公告,可能会导致巨大的波动。

下面的代码块显示了如何绘制聚类图以及其效果:

In [5]: retv = ret.values ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [6]: plt.figure(figsize=(10, 6)) plt.plot(s_p500.index[1:], ret) plt.title('Volatility clustering of S&P-500') plt.ylabel('Daily returns') plt.xlabel('Date') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (130)

将返回的数据框转换为numpy表示形式

与实现波动性峰值类似,图4-2 表明存在一些大幅波动,毫不奇怪,这些起伏通常发生在重要事件,比如 2020 年中期的 COVID-19 大流行期间。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (131)

图4-2. 波动性聚类—标准普尔 500 指数

尽管 ARCH 模型具有诸如简单性、非线性、易用性和用于预测的调整等吸引人的特征,但它也存在一些缺点:

  • 对正面和负面冲击的反应相等

  • 诸如对参数的限制等强假设

  • 由于对大幅波动的缓慢调整可能导致误预测

这些缺点促使研究人员进行 ARCH 模型的扩展工作,尤其是 Bollerslev(1986)和 Taylor(1986)提出的 GARCH 模型,我们稍后将讨论。

现在让我们使用 ARCH 模型来预测波动性。首先,让我们生成我们自己的 Python 代码,然后与arch库中的内置函数进行比较,看看它们之间的差异:

In [7]: n = 252 split_date = ret.iloc[-n:].index ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [8]: sgm2 = ret.var() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) K = ret.kurtosis() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) alpha = (-3.0 * sgm2 + np.sqrt(9.0 * sgm2 ** 2 - 12.0 * (3.0 * sgm2 - K) * K)) / (6 * K) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) omega = (1 - alpha) * sgm2 ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) initial_parameters = [alpha, omega] omega, alphaOut[8]: (0.6345749196895419, 0.46656704131150534)In [9]: @jit(nopython=True, parallel=True) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) def arch_likelihood(initial_parameters, retv): omega = abs(initial_parameters[0]) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) alpha = abs(initial_parameters[1]) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) T = len(retv) logliks = 0 sigma2 = np.zeros(T) sigma2[0] = np.var(retv) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) for t in range(1, T): sigma2[t] = omega + alpha * (retv[t - 1]) ** 2 ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png) logliks = np.sum(0.5 * (np.log(sigma2)+retv ** 2 / sigma2)) ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png) return logliksIn [10]: logliks = arch_likelihood(initial_parameters, retv) logliksOut[10]: 1453.127184488521In [11]: def opt_params(x0, retv): opt_result = opt.minimize(arch_likelihood, x0=x0, args = (retv), method='Nelder-Mead', options={'maxiter': 5000}) ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png) params = opt_result.x ![12](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/12.png) print('\nResults of Nelder-Mead minimization\n{}\n{}' .format(''.join(['-'] * 28), opt_result)) print('\nResulting params = {}'.format(params)) return paramsIn [12]: params = opt_params(initial_parameters, retv) Results of Nelder-Mead minimization ---------------------------- final_simplex: (array([[0.70168795, 0.39039044], [0.70163494, 0.3904423 ], [0.70163928, 0.39033154]]), array([1385.79241695, 1385.792417, 1385.79241907])) fun: 1385.7924169507244 message: 'Optimization terminated successfully.' nfev: 62 nit: 33 status: 0 success: True x: array([0.70168795, 0.39039044]) Resulting params = [0.70168795 0.39039044]In [13]: def arch_apply(ret): omega = params[0] alpha = params[1] T = len(ret) sigma2_arch = np.zeros(T + 1) sigma2_arch[0] = np.var(ret) for t in range(1, T): sigma2_arch[t] = omega + alpha * ret[t - 1] ** 2 return sigma2_archIn [14]: sigma2_arch = arch_apply(ret)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (132)

定义拆分位置并将拆分数据分配给split变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (133)

计算标准普尔 500 指数的方差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (134)

计算标准普尔 500 指数的峰度

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (135)

确定斜率系数α的初始值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (136)

确定常数项ω的初始值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (137)

使用并行处理以减少处理时间

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (138)

取绝对值并将初始值分配给相关变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (139)

确定波动性的初始值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (140)

迭代标准普尔 500 指数的方差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (141)

计算对数似然

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (142)

最小化对数似然函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (143)

为优化参数创建变量params

我们通过自己的优化方法和 ARCH 方程建模波动率。但是如何与内置的 Python 代码进行比较呢?这些内置代码可以从arch库导入,非常容易应用。内置函数的结果如下;事实证明这两个结果非常相似:

In [15]: arch = arch_model(ret, mean='zero', vol='ARCH', p=1).fit(disp='off') print(arch.summary()) Zero Mean - ARCH Model Results \=============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: ARCH Log-Likelihood: -4063.63Distribution: Normal AIC: 8131.25Method: Maximum Likelihood BIC: 8143.21No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:56:56 Df Model: 0 Volatility Model======================================================================== coef std err t P>|t| 95.0% Conf. Int.------------------------------------------------------------------------omega 0.7018 5.006e-02 14.018 1.214e-44 [ 0.604, 0.800]alpha[1] 0.3910 7.016e-02 5.573 2.506e-08 [ 0.253, 0.529]========================================================================Covariance estimator: robust

尽管开发我们自己的代码总是有帮助并提高我们的理解,但这并不意味着不需要使用内置函数或库。相反,这些函数在效率和易用性方面使我们的生活更加轻松。

我们只需创建一个 for 循环并定义适当的信息标准。在这里,我们将选择贝叶斯信息准则(BIC)作为模型选择方法和选择滞后项。之所以使用 BIC 是因为只要我们有足够大的样本,BIC 就是一个可靠的模型选择工具,如 Burnham 和 Anderson(2002 和 2004)所述。现在,我们从 1 到 5 个滞后项迭代 ARCH 模型:

In [16]: bic_arch = [] for p in range(1, 5): ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) arch = arch_model(ret, mean='zero', vol='ARCH', p=p)\ .fit(disp='off') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) bic_arch.append(arch.bic) if arch.bic == np.min(bic_arch): ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) best_param = p arch = arch_model(ret, mean='zero', vol='ARCH', p=best_param)\ .fit(disp='off') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) print(arch.summary()) forecast = arch.forecast(start=split_date[0]) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) forecast_arch = forecast Zero Mean - ARCH Model Results==============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: ARCH Log-Likelihood: -3712.38Distribution: Normal AIC: 7434.75Method: Maximum Likelihood BIC: 7464.64No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:56:58 Df Model: 0 Volatility Model========================================================================== coef std err t P>|t| 95.0% Conf. Int.--------------------------------------------------------------------------omega 0.2798 2.584e-02 10.826 2.580e-27 [ 0.229, 0.330]alpha[1] 0.1519 3.460e-02 4.390 1.136e-05 [8.406e-02, 0.220]alpha[2] 0.2329 3.620e-02 6.433 1.249e-10 [ 0.162, 0.304]alpha[3] 0.1917 3.707e-02 5.170 2.337e-07 [ 0.119, 0.264]alpha[4] 0.1922 4.158e-02 4.623 3.780e-06 [ 0.111, 0.274]========================================================================== Covariance estimator: robustIn [17]: rmse_arch = np.sqrt(mse(realized_vol[-n:] / 100, np.sqrt(forecast_arch\ .variance.iloc[-len(split_date):] / 100))) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) print('The RMSE value of ARCH model is {:.4f}'.format(rmse_arch)) The RMSE value of ARCH model is 0.0896In [18]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(forecast_arch.variance.iloc[-len(split_date):] / 100, label='Volatility Prediction-ARCH') plt.title('Volatility Prediction with ARCH', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (144)

在指定的间隔内迭代 ARCH 参数p

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (145)

使用不同的p值运行 ARCH 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (146)

寻找最小的 BIC 分数来选择最佳模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (147)

使用最佳p值运行 ARCH 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (148)

基于优化的 ARCH 模型预测波动率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (149)

计算均方根误差(RMSE)分数

基于我们的第一个模型的波动率预测结果显示在图4-3 中。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (150)

图 4-3. 使用 ARCH 进行波动率预测

GARCH 模型是将滞后条件方差合并到 ARCH 模型中的扩展。因此,通过添加p个滞后条件方差,改进了 ARCH 模型,使得 GARCH 模型在多元意义上是一个条件方差的自回归移动平均模型,具有p个滞后平方收益和q个滞后条件方差。GARCH(p, q)可以表达为:

σ t 2 = ω + k=1 q α k r t-k 2 + k=1 p β k σ t-k 2

其中ωβα是待估计的参数,pq是模型中的最大滞后。为了保持一致的 GARCH,应满足以下条件:

  • ω > 0

  • β 0

  • α 0

  • β + α < 1

ARCH 模型无法捕捉历史创新的影响。然而,作为更简约的模型,GARCH 模型可以解释历史创新的变化,因为 GARCH 模型可以被表达为无限阶的 ARCH。让我们看看 GARCH 如何可以显示为无限阶的 ARCH:

σ t 2 = ω + α r t-1 2 + β σ t-1 2

然后用σ t-1 2替换ω + α r t-2 2 + β σ t-2 2

σ t 2 = ω + α r t-1 2 + β ( ω + α r t-2 2 σ t-2 2 )= ω ( 1 + β ) + α r t-1 2 + β α r t-2 2 + β 2 σ t-2 2 )

现在,让我们用 σ t-2 2 替换为 ω + α r t-3 2 + β σ t-3 2 并进行必要的数学运算,以便我们得到:

σ t 2 = ω ( 1 + β + β 2 + . . . ) + α k=1 β k-1 r t-k

与 ARCH 模型类似,Python 中使用 GARCH 建模波动率的方法不止一种。让我们尝试用优化技术开发自己的基于 Python 的代码。接下来,将使用 arch 库来预测波动率:

In [19]: a0 = 0.0001 sgm2 = ret.var() K = ret.kurtosis() h = 1 - alpha / sgm2 alpha = np.sqrt(K * (1 - h ** 2) / (2.0 * (K + 3))) beta = np.abs(h - omega) omega = (1 - omega) * sgm2 initial_parameters = np.array([omega, alpha, beta]) print('Initial parameters for omega, alpha, and beta are \n{}\n{}\n{}' .format(omega, alpha, beta)) Initial parameters for omega, alpha, and beta are 0.43471178001576827 0.512827280537482 0.02677799855546381In [20]: retv = ret.valuesIn [21]: @jit(nopython=True, parallel=True) def garch_likelihood(initial_parameters, retv): omega = initial_parameters[0] alpha = initial_parameters[1] beta = initial_parameters[2] T = len(retv) logliks = 0 sigma2 = np.zeros(T) sigma2[0] = np.var(retv) for t in range(1, T): sigma2[t] = omega + alpha * (retv[t - 1]) ** 2 + beta * sigma2[t-1] logliks = np.sum(0.5 * (np.log(sigma2) + retv ** 2 / sigma2)) return logliksIn [22]: logliks = garch_likelihood(initial_parameters, retv) print('The Log likelihood is {:.4f}'.format(logliks)) The Log likelihood is 1387.7215In [23]: def garch_constraint(initial_parameters): alpha = initial_parameters[0] gamma = initial_parameters[1] beta = initial_parameters[2] return np.array([1 - alpha - beta])In [24]: bounds = [(0.0, 1.0), (0.0, 1.0), (0.0, 1.0)]In [25]: def opt_paramsG(initial_parameters, retv): opt_result = opt.minimize(garch_likelihood, x0=initial_parameters, constraints=np.array([1 - alpha - beta]), bounds=bounds, args = (retv), method='Nelder-Mead', options={'maxiter': 5000}) params = opt_result.x print('\nResults of Nelder-Mead minimization\n{}\n{}'\ .format('-' * 35, opt_result)) print('-' * 35) print('\nResulting parameters = {}'.format(params)) return paramsIn [26]: params = opt_paramsG(initial_parameters, retv) Results of Nelder-Mead minimization ----------------------------------- final_simplex: (array([[0.03918956, 0.17370549, 0.78991502], [0.03920507, 0.17374466, 0.78987403], [0.03916671, 0.17377319, 0.78993078], [0.03917324, 0.17364595, 0.78998753]]), array([979.87109624, 979.8710967 , 979.87109865, 979.8711147 ])) fun: 979.8710962352685 message: 'Optimization terminated successfully.' nfev: 178 nit: 102 status: 0 success: True x: array([0.03918956, 0.17370549, 0.78991502]) ----------------------------------- Resulting parameters = [0.03918956 0.17370549 0.78991502]In [27]: def garch_apply(ret): omega = params[0] alpha = params[1] beta = params[2] T = len(ret) sigma2 = np.zeros(T + 1) sigma2[0] = np.var(ret) for t in range(1, T): sigma2[t] = omega + alpha * ret[t - 1] ** 2 + beta * sigma2[t-1] return sigma2

我们从自己的 GARCH 代码中获得的参数大约是:

  • ω = 0.0392

  • α = 0.1737

  • β = 0.7899

现在,让我们尝试使用内置的 Python 函数:

In [28]: garch = arch_model(ret, mean='zero', vol='GARCH', p=1, o=0, q=1)\ .fit(disp='off') print(garch.summary()) Zero Mean - GARCH Model Results==============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: GARCH Log-Likelihood: -3657.62Distribution: Normal AIC: 7321.23Method: Maximum Likelihood BIC: 7339.16No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:57:08 Df Model: 0Volatility Model============================================================================coef std err t P>|t| 95.0% Conf. Int.----------------------------------------------------------------------------omega 0.0392 8.422e-03 4.652 3.280e-06 [2.268e-02,5.569e-02]alpha[1] 0.1738 2.275e-02 7.637 2.225e-14 [ 0.129, 0.218]beta[1] 0.7899 2.275e-02 34.715 4.607e-264 [ 0.745, 0.835]============================================================================ Covariance estimator: robust

内置函数确认我们的工作很棒,因为通过内置代码获取的参数几乎与我们的参数相同,所以我们已经学会如何编写 GARCH 和 ARCH 模型来预测波动率。

使用 GARCH(1, 1) 很容易,但我们如何知道参数是否是最优的呢?让我们根据最低的 BIC 值确定最优参数集(同时生成 图4-4):

In [29]: bic_garch = [] for p in range(1, 5): for q in range(1, 5): garch = arch_model(ret, mean='zero',vol='GARCH', p=p, o=0, q=q)\ .fit(disp='off') bic_garch.append(garch.bic) if garch.bic == np.min(bic_garch): best_param = p, q garch = arch_model(ret, mean='zero', vol='GARCH', p=best_param[0], o=0, q=best_param[1])\ .fit(disp='off') print(garch.summary()) forecast = garch.forecast(start=split_date[0]) forecast_garch = forecast Zero Mean - GARCH Model Results==============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: GARCH Log-Likelihood: -3657.62Distribution: Normal AIC: 7321.23Method: Maximum Likelihood BIC: 7339.16No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:57:10 Df Model: 0Volatility Model============================================================================ coef std err t P>|t| 95.0% Conf. Int.----------------------------------------------------------------------------omega 0.0392 8.422e-03 4.652 3.280e-06 [2.268e-02, 5.569e-02]alpha[1] 0.1738 2.275e-02 7.637 2.225e-14 [ 0.129, 0.218]beta[1] 0.7899 2.275e-02 34.715 4.607e-264 [ 0.745, 0.835]============================================================================ Covariance estimator: robustIn [30]: rmse_garch = np.sqrt(mse(realized_vol[-n:] / 100, np.sqrt(forecast_garch\ .variance.iloc[-len(split_date):] / 100))) print('The RMSE value of GARCH model is {:.4f}'.format(rmse_garch)) The RMSE value of GARCH model is 0.0878In [31]: plt.figure(figsize=(10,6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(forecast_garch.variance.iloc[-len(split_date):] / 100, label='Volatility Prediction-GARCH') plt.title('Volatility Prediction with GARCH', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (151)

图4-4. 使用 GARCH 预测波动性

GARCH 模型很好地适应了收益的波动性,部分原因在于其波动性聚类特性,部分原因在于 GARCH 不假定收益独立,这使得它能够解释收益的厚尾特性。然而,尽管具有这些有用的特性和直观性,GARCH 不能对冲击的非对称响应进行建模(Karasan 和 Gaygisiz 2020)。为了解决这个问题,Glosten、Jagannathan 和 Runkle(1993)提出了 GJR-GARCH 模型。

GJR-GARCH 模型在模拟公告的非对称效应方面表现良好,即坏消息的影响大于好消息。换句话说,在存在不对称性的情况下,损失的分布比收益的分布有更厚的尾部。该模型的方程包含一个额外的参数,γ,其形式如下:

σ t 2 = ω + k=1 q ( α k r t-k 2 + γ r t-k 2 I ( ϵ t-1 < 0 ) ) + k=1 p β k σ t-k 2

其中 γ 控制公告的非对称性,如果

γ = 0

对过去的冲击反应是相同的。

γ > 0

对过去的负向冲击反应比正向冲击更强。

γ < 0

对过去的正向冲击反应比负向冲击更强。

现在让我们通过使用 BIC 找到最佳参数值并产生结果图图4-5 来运行 GJR-GARCH 模型:

In [32]: bic_gjr_garch = [] for p in range(1, 5): for q in range(1, 5): gjrgarch = arch_model(ret, mean='zero', p=p, o=1, q=q)\ .fit(disp='off') bic_gjr_garch.append(gjrgarch.bic) if gjrgarch.bic == np.min(bic_gjr_garch): best_param = p, q gjrgarch = arch_model(ret,mean='zero', p=best_param[0], o=1, q=best_param[1]).fit(disp='off') print(gjrgarch.summary()) forecast = gjrgarch.forecast(start=split_date[0]) forecast_gjrgarch = forecast Zero Mean - GJR-GARCH Model Results==============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: GJR-GARCH Log-Likelihood: -3593.36Distribution: Normal AIC: 7194.73Method: Maximum Likelihood BIC: 7218.64No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:57:14 Df Model: 0Volatility Model============================================================================= coef std err t P>|t| 95.0% Conf. Int.-----------------------------------------------------------------------------omega 0.0431 7.770e-03 5.542 2.983e-08 [2.784e-02,5.829e-02]alpha[1] 0.0386 3.060e-02 1.261 0.207 [-2.139e-02,9.855e-02]gamma[1] 0.2806 4.818e-02 5.824 5.740e-09 [ 0.186, 0.375]beta[1] 0.7907 2.702e-02 29.263 3.029e-188 [ 0.738, 0.844]============================================================================= Covariance estimator: robustIn [33]: rmse_gjr_garch = np.sqrt(mse(realized_vol[-n:] / 100, np.sqrt(forecast_gjrgarch\ .variance.iloc[-len(split_date):] / 100))) print('The RMSE value of GJR-GARCH models is {:.4f}' .format(rmse_gjr_garch)) The RMSE value of GJR-GARCH models is 0.0882In [34]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(forecast_gjrgarch.variance.iloc[-len(split_date):] / 100, label='Volatility Prediction-GJR-GARCH') plt.title('Volatility Prediction with GJR-GARCH', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (152)

图4-5. 使用 GJR-GARCH 进行波动率预测

与 GJR-GARCH 模型一同,由 Nelson(1991)提出的 EGARCH 模型是另一种控制不对称公告效应的工具。此外,它以对数形式规定,因此无需添加限制以避免负波动:

log ( σ t 2 ) = ω + k=1 p β k log σ t-k 2 + k=1 q α i |r k-1 | σ t-k 2 + k=1 q γ k r t-k σ t-k 2

EGARCH 方程的主要区别在于方程左侧的方差取对数。这表明了杠杆效应,意味着过去资产回报与波动率之间存在负相关。如果 γ < 0 ,则表明存在杠杆效应;如果 γ 0 ,则显示了波动率的不对称性。

按照之前使用的相同步骤,让我们使用 EGARCH 模型来建模波动率(导致图4-6):

In [35]: bic_egarch = [] for p in range(1, 5): for q in range(1, 5): egarch = arch_model(ret, mean='zero', vol='EGARCH', p=p, q=q)\ .fit(disp='off') bic_egarch.append(egarch.bic) if egarch.bic == np.min(bic_egarch): best_param = p, q egarch = arch_model(ret, mean='zero', vol='EGARCH', p=best_param[0], q=best_param[1])\ .fit(disp='off') print(egarch.summary()) forecast = egarch.forecast(start=split_date[0]) forecast_egarch = forecast Zero Mean - EGARCH Model Results==============================================================================Dep. Variable: Adj Close R-squared: 0.000Mean Model: Zero Mean Adj. R-squared: 0.000Vol Model: EGARCH Log-Likelihood: -3676.18Distribution: Normal AIC: 7358.37Method: Maximum Likelihood BIC: 7376.30No. Observations: 2914Date: Mon, Sep 13 2021 Df Residuals: 2914Time: 21:57:19 Df Model: 0Volatility Model============================================================================= coef std err t P>|t| 95.0% Conf. Int.-----------------------------------------------------------------------------omega 2.3596e-03 6.747e-03 0.350 0.727 [-1.086e-02,1.558e-02]alpha[1] 0.3266 3.427e-02 9.530 1.567e-21 [ 0.259, 0.394]beta[1] 0.9456 1.153e-02 82.023 0.000 [ 0.923, 0.968]============================================================================= Covariance estimator: robustIn [36]: rmse_egarch = np.sqrt(mse(realized_vol[-n:] / 100, np.sqrt(forecast_egarch.variance\ .iloc[-len(split_date):] / 100))) print('The RMSE value of EGARCH models is {:.4f}'.format(rmse_egarch)) The RMSE value of EGARCH models is 0.0904In [37]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(forecast_egarch.variance.iloc[-len(split_date):] / 100, label='Volatility Prediction-EGARCH') plt.title('Volatility Prediction with EGARCH', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (153)

图4-6. 使用 EGARCH 进行波动率预测

鉴于表4-1 所示的 RMSE 结果,这里使用的模型中表现最佳和最差的分别是 GARCH 和 EGARCH。但在我们使用的模型性能中并没有太大差异。特别是在坏消息/好消息公告期间,由于市场的不对称性,EGARCH 和 GJR-GARCH 的表现可能会有所不同。

表4-1. 所有四种模型的 RMSE 结果

模型RMSE
ARCH0.0896
GARCH0.0878
GJR-GARCH0.0882
EGARCH0.0904

到目前为止,我们已经讨论了经典波动率模型,但从现在开始,我们将看到如何利用机器学*和贝叶斯方法来建模波动率。在机器学*的背景下,支持向量机和神经网络将是首要探索的模型。让我们开始吧。

支持向量机(SVM)是一种监督学*算法,可用于分类和回归。SVM 的目标是找到一个分隔两个类别的线。听起来容易,但挑战在于:几乎有无数条线可以用来区分类别。但我们正在寻找的是能够完美区分类别的最优线。

在线性代数中,最优线称为超平面,它最大化了属于不同类别但最接*超平面的点之间的距离。两个点(支持向量)之间的距离称为间隔。因此,在支持向量机中,我们试图最大化支持向量之间的间隔。

SVM 用于分类被称为支持向量分类(SVC)。保留 SVM 的所有特性,它也可应用于回归。同样,在回归中,目标是找到最小化误差并最大化间隔的超平面。这种方法称为支持向量回归(SVR),在本部分中,我们将应用此方法于 GARCH 模型。结合这两个模型得到SVR-GARCH

下面的代码展示了我们在 Python 中运行 SVR-GARCH 之前的准备工作。这里最关键的步骤是获取独立变量,即实现波动率和历史回报的平方:

In [38]: from sklearn.svm import SVR from scipy.stats import uniform as sp_rand from sklearn.model_selection import RandomizedSearchCVIn [39]: realized_vol = ret.rolling(5).std() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) realized_vol = pd.DataFrame(realized_vol) realized_vol.reset_index(drop=True, inplace=True)In [40]: returns_svm = ret ** 2 returns_svm = returns_svm.reset_index() del returns_svm['Date']In [41]: X = pd.concat([realized_vol, returns_svm], axis=1, ignore_index=True) X = X[4:].copy() X = X.reset_index() X.drop('index', axis=1, inplace=True)In [42]: realized_vol = realized_vol.dropna().reset_index() realized_vol.drop('index', axis=1, inplace=True)In [43]: svr_poly = SVR(kernel='poly', degree=2) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) svr_lin = SVR(kernel='linear') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) svr_rbf = SVR(kernel='rbf') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (154)

计算实现波动率,并将其命名为realized_vol

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (155)

为每个 SVR 核函数创建新变量

让我们运行并查看我们第一个使用线性核的 SVR-GARCH 应用程序(并生成图4-7);我们将使用 RMSE 指标比较这些应用程序:

In [44]: para_grid = {'gamma': sp_rand(), 'C': sp_rand(), 'epsilon': sp_rand()} ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) clf = RandomizedSearchCV(svr_lin, para_grid) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) clf.fit(X.iloc[:-n].values, realized_vol.iloc[1:-(n-1)].values.reshape(-1,)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) predict_svr_lin = clf.predict(X.iloc[-n:]) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [45]: predict_svr_lin = pd.DataFrame(predict_svr_lin) predict_svr_lin.index = ret.iloc[-n:].indexIn [46]: rmse_svr = np.sqrt(mse(realized_vol.iloc[-n:] / 100, predict_svr_lin / 100)) print('The RMSE value of SVR with Linear Kernel is {:.6f}' .format(rmse_svr)) The RMSE value of SVR with Linear Kernel is 0.000462In [47]: realized_vol.index = ret.iloc[4:].indexIn [48]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(predict_svr_lin / 100, label='Volatility Prediction-SVR-GARCH') plt.title('Volatility Prediction with SVR-GARCH (Linear)', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (156)

识别调整超参数空间

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (157)

使用RandomizedSearchCV进行超参数调整

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (158)

用线性核将 SVR-GARCH 拟合到数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (159)

基于最后 252 个观察值预测波动率,并将其存储在predict_svr_lin

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (160)

图4-7. SVR-GARCH 线性核波动率预测

图4-7 展示了预测值和实际观察结果。通过直观分析,我们可以看出 SVR-GARCH 表现良好。你可以猜到,如果数据集是线性可分的,线性核函数效果良好;这也被奥卡姆剃刀所推荐。³ 但如果数据集不是线性可分的呢?接下来我们继续使用径向基函数(RBF)和多项式核函数。前者使用椭圆形曲线围绕观察结果,而后者不同于前两者,专注于样本的组合。现在让我们看看它们是如何工作的。

让我们从使用 RBF 核的 SVR-GARCH 应用程序开始,这是一个将数据投影到新向量空间的函数。从实际角度来看,使用不同核的 SVR-GARCH 应用程序并不是一个费时的过程;我们所需做的只是切换核名称,如下所示(并产生图4-8):

In [49]: para_grid ={'gamma': sp_rand(), 'C': sp_rand(), 'epsilon': sp_rand()} clf = RandomizedSearchCV(svr_rbf, para_grid) clf.fit(X.iloc[:-n].values, realized_vol.iloc[1:-(n-1)].values.reshape(-1,)) predict_svr_rbf = clf.predict(X.iloc[-n:])In [50]: predict_svr_rbf = pd.DataFrame(predict_svr_rbf) predict_svr_rbf.index = ret.iloc[-n:].indexIn [51]: rmse_svr_rbf = np.sqrt(mse(realized_vol.iloc[-n:] / 100, predict_svr_rbf / 100)) print('The RMSE value of SVR with RBF Kernel is {:.6f}' .format(rmse_svr_rbf)) The RMSE value of SVR with RBF Kernel is 0.000970In [52]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(predict_svr_rbf / 100, label='Volatility Prediction-SVR_GARCH') plt.title('Volatility Prediction with SVR-GARCH (RBF)', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (161)

图4-8. 带有 SVR-GARCH RBF 核的波动率预测

RMSE 分数和可视化均表明,带有线性核的 SVR-GARCH 优于带有 RBF 核的 SVR-GARCH。带有线性和 RBF 核的 SVR 的 RMSE 分别为 0.000462 和 0.000970。因此,带有线性核的 SVR 表现良好。

最后,让我们尝试带有多项式核的 SVR-GARCH。结果表明,它具有最高的 RMSE(0.002386),这意味着它是这三种不同应用中表现最差的核。可以在图4-9 中找到带有多项式核的 SVR-GARCH 的预测性能:

In [53]: para_grid = {'gamma': sp_rand(), 'C': sp_rand(), 'epsilon': sp_rand()} clf = RandomizedSearchCV(svr_poly, para_grid) clf.fit(X.iloc[:-n].values, realized_vol.iloc[1:-(n-1)].values.reshape(-1,)) predict_svr_poly = clf.predict(X.iloc[-n:])In [54]: predict_svr_poly = pd.DataFrame(predict_svr_poly) predict_svr_poly.index = ret.iloc[-n:].indexIn [55]: rmse_svr_poly = np.sqrt(mse(realized_vol.iloc[-n:] / 100, predict_svr_poly / 100)) print('The RMSE value of SVR with Polynomial Kernel is {:.6f}'\ .format(rmse_svr_poly)) The RMSE value of SVR with Polynomial Kernel is 0.002386In [56]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol/100, label='Realized Volatility') plt.plot(predict_svr_poly/100, label='Volatility Prediction-SVR-GARCH') plt.title('Volatility Prediction with SVR-GARCH (Polynomial)', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (162)

图4-9. 带有 SVR-GARCH 多项式核的波动率预测

神经网络是深度学*的构建模块。在神经网络中,数据经过多个阶段处理以做出决策。每个神经元将点积的结果作为输入,并在激活函数中使用它来做出决策:

z = w 1 x 1 + w 2 x 2 + b

其中b是偏差,w是权重,x是输入数据。

在此过程中,输入数据在隐藏层和输出层中以各种方式进行数学处理。一般而言,神经网络有三种类型的层:

  • 输入层

  • 隐藏层

  • 输出层

图4-10 可以帮助说明各层之间的关系。

输入层包括原始数据。从输入层到隐藏层,我们学*系数。根据网络结构,可能有一个或多个隐藏层。网络的隐藏层越多,它就越复杂。隐藏层位于输入层和输出层之间,通过激活函数执行非线性变换。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (163)

图4-10. 神经网络结构

最后,输出层是生成输出并做出决策的层。

在机器学*中,梯度下降被应用于寻找最小化成本函数的最优参数,但在神经网络中仅使用梯度下降不可行,因为神经网络内部具有链式结构。因此,提出了一种称为反向传播的新概念来最小化成本函数。反向传播的理念基于计算观察到的输出与实际输出之间的误差,然后将此误差传递到隐藏层。因此,我们向后移动,主要方程式采取以下形式:

δ l = δJ δz j l

其中z是线性变换,δ表示误差。这里还有很多要说的,但为了使我们保持在正确的轨道上,我们将在这里停止。对于那些想深入了解神经网络背后数学的人,请参阅 Wilmott(2013)和 Alpaydin(2020)。

现在,我们使用MLPRegressor模块从 scikit-learn 应用基于神经网络的波动率预测,即使在 Python 中运行神经网络有各种选项。⁴ 鉴于我们介绍的神经网络结构,结果如下:

In [57]: from sklearn.neural_network import MLPRegressor ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) NN_vol = MLPRegressor(learning_rate_init=0.001, random_state=1) para_grid_NN = {'hidden_layer_sizes': [(100, 50), (50, 50), (10, 100)], 'max_iter': [500, 1000], 'alpha': [0.00005, 0.0005 ]} ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) clf = RandomizedSearchCV(NN_vol, para_grid_NN) clf.fit(X.iloc[:-n].values, realized_vol.iloc[1:-(n-1)].values.reshape(-1, )) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) NN_predictions = clf.predict(X.iloc[-n:]) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [58]: NN_predictions = pd.DataFrame(NN_predictions) NN_predictions.index = ret.iloc[-n:].indexIn [59]: rmse_NN = np.sqrt(mse(realized_vol.iloc[-n:] / 100, NN_predictions / 100)) print('The RMSE value of NN is {:.6f}'.format(rmse_NN)) The RMSE value of NN is 0.000583In [60]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(NN_predictions / 100, label='Volatility Prediction-NN') plt.title('Volatility Prediction with Neural Network', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (164)

导入MLPRegressor模块

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (165)

通过设置三个隐藏层和不同的神经元数量来配置 NN 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (166)

将 NN 模型拟合到训练数据中⁵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (167)

基于最后 252 个观察值预测波动性,并将其存储在NN_predictions变量中

图4-11 展示了基于 NN 模型的波动性预测结果。尽管其表现合理,我们可以通过改变隐藏神经元的数量来生成一个深度学*模型。为此,我们可以应用 Keras 库,这是 Python 对人工神经网络的接口。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (168)

图 4-11. 带有 NN 的波动性预测

现在是使用深度学*来预测波动性的时候了。基于 Keras,配置网络结构非常容易。我们只需要确定特定层的神经元数量。这里,第一和第二个隐藏层的神经元数量分别为 256 和 128。由于波动性属于连续型,我们只有一个输出神经元:

In [61]: import tensorflow as tf from tensorflow import keras from tensorflow.keras import layersIn [62]: model = keras.Sequential( [layers.Dense(256, activation="relu"), layers.Dense(128, activation="relu"), layers.Dense(1, activation="linear"),]) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [63]: model.compile(loss='mse', optimizer='rmsprop') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [64]: epochs_trial = np.arange(100, 400, 4) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) batch_trial = np.arange(100, 400, 4) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) DL_pred = [] DL_RMSE = [] for i, j, k in zip(range(4), epochs_trial, batch_trial): model.fit(X.iloc[:-n].values, realized_vol.iloc[1:-(n-1)].values.reshape(-1,), batch_size=k, epochs=j, verbose=False) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) DL_predict = model.predict(np.asarray(X.iloc[-n:])) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) DL_RMSE.append(np.sqrt(mse(realized_vol.iloc[-n:] / 100, DL_predict.flatten() / 100))) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) DL_pred.append(DL_predict) print('DL_RMSE_{}:{:.6f}'.format(i+1, DL_RMSE[i])) DL_RMSE_1:0.000551 DL_RMSE_2:0.000714 DL_RMSE_3:0.000627 DL_RMSE_4:0.000739In [65]: DL_predict = pd.DataFrame(DL_pred[DL_RMSE.index(min(DL_RMSE))]) DL_predict.index = ret.iloc[-n:].indexIn [66]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100,label='Realized Volatility') plt.plot(DL_predict / 100,label='Volatility Prediction-DL') plt.title('Volatility Prediction with Deep Learning', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (169)

通过决定层数和神经元的数量来配置网络结构

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (170)

编译模型并设置损失和优化器

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (171)

使用np.arange决定周期和批处理大小

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (172)

拟合深度学*模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (173)

预测波动性基于从训练阶段获得的权重

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (174)

通过展平预测计算 RMSE 得分

结果表明,当我们将周期数和批处理大小设为 100 时,我们得到最小的 RMSE 得分。这表明增加模型复杂性并不一定意味着高预测性能。关键是要找到复杂性和预测性能之间的平衡点。否则,模型很容易过拟合。

图4-12 展示了由前述代码推导出的波动率预测结果,这表明深度学*也为建模波动率提供了一个强大的工具。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (175)

图 4-12. 使用深度学*进行波动率预测

我们处理概率的方式在于区分经典(或频率主义)和贝叶斯方法上占据了中心位置。根据前者,相对频率将收敛于真实概率。然而,贝叶斯应用基于主观解释。不像频率主义者,贝叶斯统计学家将概率分布视为不确定的,并且在新信息出现时进行修正。

由于这两种方法对概率的不同解释,似然度—定义为在给定一组参数的情况下观察到的事件的概率—计算方式也有所不同。

从联合密度函数开始,我们可以给出似然函数的数学表示:

( θ | x 1 , x 2 , . . . , x p ) = Pr ( x 1 , x 2 , . . . , x p | θ )

在可能的θ值中,我们正在尝试决定哪一个更有可能。在由似然函数提出的统计模型下,观察到的数据x 1 , . . . , x p 是最有可能的。

实际上,你对基于这种方法的方法很熟悉,即最大似然估计。在定义了贝叶斯和频率主义方法的主要区别之后,是时候更深入地探讨贝叶斯定理了。

贝叶斯方法基于条件分布,该分布表明概率评估一个不确定事件的程度。因此,贝叶斯应用建议了一个规则,可用于根据新信息更新持有的信念:

当我们对参数有一些先验信息时,使用贝叶斯估计。例如,在查看样本以估计分布的均值之前,我们可能有一些先验信念认为它接*于 2,在 1 到 3 之间。在我们有小样本的情况下,这种先验信念尤为重要。在这种情况下,我们有兴趣结合数据告诉我们的东西,即从样本计算出的值,以及我们的先验信息。

Rachev 等人,2008

与频率学应用类似,贝叶斯估计基于概率密度 Pr ( x | θ ) 。然而,正如我们之前讨论的那样,贝叶斯和频率学方法对待参数集 θ 有所不同。频率学家假设 θ 是固定的,而在贝叶斯设置中,θ 被视为一个随机变量,其概率称为先验密度 Pr ( θ ) 。好吧,我们还有另一个未知术语,但别担心——这很容易理解。

根据这些信息,我们可以使用先验密度 Pr ( θ ) 来估计 ( x | θ ) ,并得出以下公式。在需要估计给定观察数据的参数条件分布时,使用先验。

Pr ( θ | x 1 , x 2 , . . . , x p ) = (x 1 ,x 2 ,...,x p |θ)Pr(θ) Pr(x 1 ,x 2 ,...,x p )

或者

Pr ( θ | d a t a ) = (data|θ)Pr(θ) Pr(data)

其中

  • Pr ( θ | d a t a ) 是后验密度,给出了观察到的数据后关于参数的信息。

  • ( d a t a | θ ) 是似然函数,估计了给定参数时数据的概率。

  • Pr ( θ ) 是先验概率。这是参数的概率。先验基本上是关于估计的初始信念。

  • 最后,Pr 是证据,用于更新先验。

因此,贝叶斯定理表明后验密度与先验和似然项成正比,但与证据项成反比。由于证据用于缩放,我们可以描述这个过程如下:

Posterior Likelihood × prior

其中 意味着“与...成比例”。

在这个背景下,贝叶斯定理听起来很吸引人,不是吗?确实如此,但它也带来了一个代价,即分析难度。即使贝叶斯定理在理论上直观,但从广泛应用的角度来看,分析解往往难以获得。然而,好消息是数值方法提供了解决这一概率模型的可靠手段。

为了应对贝叶斯定理中的计算问题,提出了一些解决方案,包括*似解决方案:

  • 积分逼*(Quadrature approximation)

  • 最大后验估计(MAP)(见第六章讨论)

  • 格点法(Grid approach)

  • 基于抽样的方法(Sampling-based approach)

  • Metropolis–Hastings 方法

  • 吉布斯采样器(Gibbs sampler)

  • 无返回采样器(No U-Turn sampler)

在这些方法中,让我们把注意力集中在 Metropolis–Hastings 算法(M-H),这将是我们建模贝叶斯定理的方法。M-H 方法依赖于马尔可夫链蒙特卡罗(MCMC)方法。因此,在继续之前,让我们谈谈 MCMC 方法。

马尔可夫链蒙特卡罗(Markov Chain Monte Carlo)

马尔可夫链是用来描述状态间转移概率的模型。如果当前状态s t 的概率只依赖于最*的前一状态s t-1,则该链被称为马尔可夫链

Pr ( s t | s t-1 , s t-2 , . . . , s t-p ) = Pr ( s t | s t-1 )

因此,MCMC 依赖于马尔可夫链来找到具有最高后验概率的参数空间θ。随着样本量的增加,参数值逐渐逼*后验密度:

lim j+ θ j D Pr ( θ | x )

其中D表示分布*似。实现的参数空间的值可以用来推断后验。简而言之,MCMC 方法帮助我们从后验密度中收集 IID 样本,以便计算后验概率。

为了说明这一点,我们可以参考图4-13。该图显示了从一个状态转移到另一个状态的概率。为了简单起见,我们将概率设置为 0.2,表明从“学*”到“睡觉”的转移概率为 0.2:

In [67]: import quantecon as qe from quantecon import MarkovChain import networkx as nx from pprint import pprintIn [68]: P = [[0.5, 0.2, 0.3], [0.2, 0.3, 0.5], [0.2, 0.2, 0.6]] mc = qe.MarkovChain(P, ('studying', 'travelling', 'sleeping')) mc.is_irreducibleOut[68]: TrueIn [69]: states = ['studying', 'travelling', 'sleeping'] initial_probs = [0.5, 0.3, 0.6] state_space = pd.Series(initial_probs, index=states, name='states')In [70]: q_df = pd.DataFrame(columns=states, index=states) q_df = pd.DataFrame(columns=states, index=states) q_df.loc[states[0]] = [0.5, 0.2, 0.3] q_df.loc[states[1]] = [0.2, 0.3, 0.5] q_df.loc[states[2]] = [0.2, 0.2, 0.6]In [71]: def _get_markov_edges(Q): edges = {} for col in Q.columns: for idx in Q.index: edges[(idx,col)] = Q.loc[idx,col] return edges edges_wts = _get_markov_edges(q_df) pprint(edges_wts) {('sleeping', 'sleeping'): 0.6, ('sleeping', 'studying'): 0.2, ('sleeping', 'travelling'): 0.2, ('studying', 'sleeping'): 0.3, ('studying', 'studying'): 0.5, ('studying', 'travelling'): 0.2, ('travelling', 'sleeping'): 0.5, ('travelling', 'studying'): 0.2, ('travelling', 'travelling'): 0.3}In [72]: G = nx.MultiDiGraph() G.add_nodes_from(states) for k, v in edges_wts.items(): tmp_origin, tmp_destination = k[0], k[1] G.add_edge(tmp_origin, tmp_destination, weight=v, label=v) pos = nx.drawing.nx_pydot.graphviz_layout(G, prog='dot') nx.draw_networkx(G, pos) edge_labels = {(n1, n2):d['label'] for n1, n2, d in G.edges(data=True)} nx.draw_networkx_edge_labels(G , pos, edge_labels=edge_labels) nx.drawing.nx_pydot.write_dot(G, 'mc_states.dot')

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (176)

图4-13. 不同状态的相互作用

有两种常见的 MCMC 方法:M-H 方法和吉布斯采样器。在这里,我们深入探讨前者。

Metropolis–Hastings 方法

M-H 方法允许我们使用两步高效的抽样过程。首先,我们从提议密度中抽取样本,然后决定是否接受或拒绝它。

q ( θ | θ t-1 )是一个提议密度,θ是一个参数空间。整个 M-H 算法可以总结如下:

  1. 从参数空间 θ 中选择 θ 1 的初始值。

  2. 从提议密度中选择新的参数值 θ 2,为了方便起见,可以是高斯分布或均匀分布。

  3. 计算以下接受概率:

    Pr a ( θ , θ t-1 ) = m i n ( 1 , p(θ )/q(θ |θ t-1 ) p(θ t-1 )/q(θ t-1 |θ ) )

  4. 如果 Pr a ( θ * , θ t-1 ) 大于从均匀分布 U(0,1) 中抽取的样本值,则从第 2 步开始重复此过程。

嗯,这看起来有点吓人,但别担心;我们在 Python 中内置了代码,使得 M-H 算法的应用变得更加容易。我们使用 PyFlux 库利用贝叶斯定理。让我们应用 M-H 算法来预测波动率:

In [73]: import pyflux as pf from scipy.stats import kurtosisIn [74]: model = pf.GARCH(ret.values, p=1, q=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) print(model.latent_variables) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) model.adjust_prior(1, pf.Normal()) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) model.adjust_prior(2, pf.Normal()) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) x = model.fit(method='M-H', iterations='1000') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) print(x.summary()) Index Latent Variable Prior Prior Hyperparameters V.I. Dist Transform ======== ========================= =============== ========================= ========== ========== 0 Vol Constant Normal mu0: 0, sigma0: 3 Normal exp 1 q(1) Normal mu0: 0, sigma0: 0.5 Normal logit 2 p(1) Normal mu0: 0, sigma0: 0.5 Normal logit 3 Returns Constant Normal mu0: 0, sigma0: 3 Normal None Acceptance rate of Metropolis-Hastings is 0.0023 Acceptance rate of Metropolis-Hastings is 0.23925 Tuning complete! Now sampling. Acceptance rate of Metropolis-Hastings is 0.239175 GARCH(1,1) ======================================================= ================================================== Dependent Variable: Series Method: Metropolis Hastings Start Date: 1 Unnormalized Log Posterior: -3635.1348 End Date: 2913 AIC: 7278.269645045323 Number of observations: 2913 BIC: 7302.177400073161 ====================================================================== ==================================== Latent Variable Median Mean 95% Credibility Interval ======================================== ================== ================== ========================= Vol Constant 0.04 0.0398 (0.0315 | 0.0501) q(1) 0.1936 0.194 (0.1638 | 0.2251) p(1) 0.7736 0.7737 (0.7438 | 0.8026) Returns Constant 0.0866 0.0855 (0.0646 | 0.1038) ====================================================================== ==================================== NoneIn [75]: model.plot_z([1, 2]) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) model.plot_fit(figsize=(15, 5)) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) model.plot_ppc(T=kurtosis, nsims=1000) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (177)

使用 PyFlux 库配置 GARCH 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (178)

打印潜变量(参数)的估计结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (179)

调整模型潜变量的先验设定

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (180)

使用 M-H 过程拟合模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (181)

绘制潜变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (182)

绘制拟合模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (183)

绘制后验检查的直方图

对于基于贝叶斯的 GARCH 模型的波动率预测,将我们迄今为止所做的结果可视化是值得的。

图 4-14 展示了潜变量的分布情况。潜变量 q 集中在 0.2 左右,而另一个潜变量 p 大部分取值介于 0.7 和 0.8 之间。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (184)

图 4-14. 潜变量

图 4-15 表明了去均值波动率系列和基于贝叶斯方法的 GARCH 预测结果。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (185)

图 4-15. 模型拟合

图 4-16 可视化了贝叶斯模型的后验预测结果与数据,以便我们能够检测系统性差异(如果有的话)。垂直线代表了测试统计量,结果表明观察值大于我们模型的值。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (186)

图 4-16. 后验预测

在完成培训部分后,我们准备好进入下一阶段,即预测。预测分析是为了 252 步之后,根据实现的波动率计算 RMSE:

In [76]: bayesian_prediction = model.predict_is(n, fit_method='M-H') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) Acceptance rate of Metropolis-Hastings is 0.11515 Acceptance rate of Metropolis-Hastings is 0.1787 Acceptance rate of Metropolis-Hastings is 0.2675 Tuning complete! Now sampling. Acceptance rate of Metropolis-Hastings is 0.2579In [77]: bayesian_RMSE = np.sqrt(mse(realized_vol.iloc[-n:] / 100, bayesian_prediction.values / 100)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) print('The RMSE of Bayesian model is {:.6f}'.format(bayesian_RMSE)) The RMSE of Bayesian model is 0.004047In [78]: bayesian_prediction.index = ret.iloc[-n:].index

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (187)

样本内波动率预测

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (188)

计算 RMSE 分数

最终,我们准备观察贝叶斯方法的预测结果,下面的代码为我们生成了 图 4-17:

In [79]: plt.figure(figsize=(10, 6)) plt.plot(realized_vol / 100, label='Realized Volatility') plt.plot(bayesian_prediction['Series'] / 100, label='Volatility Prediction-Bayesian') plt.title('Volatility Prediction with M-H Approach', fontsize=12) plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (189)

图 4-17. 贝叶斯波动率预测

图 4-17 根据基于 M-H 方法的贝叶斯方法进行波动率预测,似乎在 2020 年底出现了过度预测。该方法的整体表现显示,并不是最佳方法之一。

波动率预测是理解金融市场动态的关键,因为它帮助我们衡量不确定性。话虽如此,它被用作许多金融模型的输入,包括风险模型。这些事实强调了准确波动率预测的重要性。传统上,诸如 ARCH、GARCH 及其扩展的参数方法已被广泛使用,但这些模型存在僵化的问题。为了解决这个问题,数据驱动的模型显得很有前景,本章试图利用这些模型,即 SVM、NN 和基于深度学*的模型。结果表明,数据驱动模型优于参数模型。

在下一章中,市场风险作为核心金融风险主题将从理论和实证角度进行讨论,并将 ML 模型纳入以进一步改善对该风险的估计。

本章引用的文章:

  • Andersen, Torben G., Tim Bollerslev, Francis X. Diebold, and Paul Labys. 2003. “Modeling and Forecasting Realized Volatility.” Econometrica 71 (2): 579-625.

  • Andersen, Torben G., and Tim Bollerslev. 1997. “Intraday Periodicity And Volatility Persistence in Financial Markets.” Journal of Empirical Finance 4 (2-3): 115-158.

  • Black, Fischer. 1976. “Studies of Stock Market Volatility Changes.” 1976 Proceedings of the American Statistical Association Business and Economic Statistics Section.

  • Bollerslev, T. 1986. “Generalized Autoregressive Conditional Heteroskedasticity.” Journal of Econometrics 31 (3): 307-327. 3): 542-547.

  • Burnham, Kenneth P., 和 David R. Anderson. 2004. “多模型推断:理解模型选择中的 AIC 和 BIC。” 社会方法与研究 33 (2): 261-304.

  • Eagle, Robert F. 1982. “带有英国通货膨胀方差估计的自回归条件异方差性。” 计量经济学 50 (4): 987-1008.

  • De Stefani, Jacopo, Olivier Caelen, Dalila Hattab, 和 Gianluca Bontempi. 2017. “机器学*用于多步预测波动率代理。” MIDAS@ PKDD/ECML, 17-28.

  • Dokuchaev, Nikolai. 2014. “从股票价格短期时间序列估计波动性。” 非参数统计学杂志 26 (2): 373-384.

  • Glosten, L. R., R. Jagannathan, 和 D. E. Runkle 1993. “关于股票名义超额收益的期望值和波动率之间关系。” 金融学杂志 48 (5): 1779-1801.

  • Karasan, Abdullah, 和 Esma Gaygisiz. 2020. “波动率预测与风险管理:一种 SVR-GARCH 方法。” 金融数据科学杂志 2 (4): 85-104.

  • Mandelbrot, Benoit. 1963. “统计经济学的新方法。” 政治经济学杂志 71 (5): 421-440.

  • Nelson, Daniel B. 1991. 资产收益的条件异方差性:一种新方法。 计量经济学 59 (2): 347-370.

  • Raju, M. T., 和 Anirban Ghosh. 2004. “股市波动性:国际比较。” 印度证券交易委员会。

本章引用的书籍:

  • Alpaydin, E. 2020. 机器学*导论. 剑桥:MIT 出版社。

  • Burnham, Kenneth P., 和 David R. Anderson. 2002. 模型选择与多模型推断:实用信息论方法. 纽约:Springer-Verlag.

  • Focardi, Sergio M. 1997. 市场建模:新理论与技术. The Frank J. Fabozzi Series, Vol. 14. 纽约:John Wiley and Sons.

  • Rachev, Svetlozar T., John SJ Hsu, Biliana S. Bagasheva, 和 Frank J. Fabozzi. 2012. 金融中的贝叶斯方法. 纽约:John Wiley and Sons.

  • Taylor, S. 1986. 金融时间序列建模. Chichester: Wiley.

  • Wilmott, Paul. 2019. 机器学*:应用数学导论. Panda Ohana Publishing.

¹ 条件方差意味着波动率估计是资产收益的过去值的函数。

² 关于这些函数的更多信息,请参阅 Andrew Ng 的讲义

³ 奥卡姆剃刀,也称为简约法则,指出在一组解释中,简单的解释最为可能和可信。

⁴ 在这些备选方案中,TensorFlow、PyTorch 和 NeuroLab 是最突出的库。

⁵ 更详细信息,请参阅MLPClassifier文档

基于历史数据驱动的风险度量假设未来会遵循过去的模式。你需要理解这种假设的局限性。更重要的是,你需要建模那些这种模式会崩溃的场景。

迈尔斯·肯尼迪

风险在金融中无处不在,但难以量化。首要的是要知道如何区分金融风险的来源,因为使用相同的工具应对来自不同来源的风险可能并不明智。

因此,区别对待金融风险的各种来源至关重要,因为这些不同风险的影响以及用于减轻它们的工具是完全不同的。假设公司面临着大幅市场波动,那么其投资组合中的所有资产都容易受到这些波动带来的风险影响。然而,应开发出不同的工具来应对源自客户资料的风险。此外,请记住,不同的风险因素对资产价格的贡献非常显著。所有这些例子都表明,在金融领域处理风险因素需要仔细考虑。

正如前面简要讨论过的那样,这些风险主要是市场风险、信用风险、流动性风险和操作风险。显然,还可以添加其他类型,但可以将它们视为这四种主要风险类型的分支,这将是本章的重点。

市场风险是指由金融指标变化引起的风险,例如汇率、利率、通货膨胀等。市场风险可以被定义为在资产负债表和非资产负债表头寸中由市场价格波动引起的损失风险(BIS 2020)。现在让我们看看这些因素如何影响市场风险。假设通货膨胀率上升对金融机构当前的盈利能力构成威胁,因为通货膨胀会对利率施加压力,这反过来影响借款人的资金成本。这些实例可能会被放大,但我们还应注意这些金融风险来源之间的相互作用。也就是说,当一个金融风险来源变化时,其他风险来源不能保持不变。因此,在某种程度上,金融指标是相互关联的,这意味着这些风险来源的相互作用应该被考虑进去。

如你所想象的那样,有不同的工具来管理市场风险。其中,最显著和广泛接受的工具是价值-at-risk(VaR)和预期缺失(ES)。本章的最终目标是利用机器学*的最新发展来增强这些方法。在这个时刻,引人入胜地提出以下问题可能会很诱人:

  • 传统模型在金融中失败了吗?

  • 机器学*模型的独特之处是什么?

我将从解决第一个问题开始。传统模型无法解决的首要挑战是金融系统的复杂性。由于某些强烈的假设或者简单地无法捕捉数据引入的复杂性,长期以来的传统模型开始被基于机器学*的模型所取代。

Prado(2020 年)生动地表达了这一事实:

考虑到现代金融系统的复杂性,研究人员不太可能通过对数据的视觉检查或运行几个回归分析来揭示理论的构成要素。

要解决第二个问题,智慧之举应是思考机器学*模型的工作逻辑。与旧统计方法相对,机器学*模型试图揭示变量之间的关联,识别关键变量,并使我们能够找出这些变量对因变量的影响,而无需一个完备的理论支持。这实际上是机器学*模型之美,因为它们让我们能够发现理论,而不是依赖于理论:

统计学和机器学*中的许多方法,原则上既可用于预测也可用于推断。然而,统计方法长期以来专注于推断,这是通过创建和拟合特定于项目的概率模型来实现的...

相比之下,机器学*集中于利用通用学*算法在通常是丰富且难以处理的数据中找出模式来进行预测。

Bzdok(2018 年,第 232 页)

在接下来的部分,我们将开始讨论市场风险模型。首先,我们将讨论 VaR 和 ES 模型的应用。在讨论这些传统模型的应用后,我们将学*如何通过采用基于机器学*的方法来改进它们。让我们开始吧。

VaR 模型起源于一位 J.P.摩根的高管提出的要求,他希望有一份汇总报告,显示 J.P.摩根在某一天可能面临的损失以及风险。这份报告将向高管们展示机构承担的聚合风险。计算市场风险的方法被称为 VaR。这份报告是 VaR 的起点,现在它已经如此普及,不仅机构喜欢使用 VaR,监管机构也要求其采用。

VaR 的采用可以追溯到上世纪 90 年代,尽管它已经有了许多扩展和新提出的模型,但仍在使用中。它有何吸引力?答案来自 Kevin Dowd(2002 年,第 10 页):

VaR 数字有两个重要特征。首先,它提供了一个跨不同头寸和风险因素的风险的常见一致度测量。它使我们能够衡量与固定收益头寸相关的风险,比如以与股票头寸相关的风险衡量相比较且保持一致。VaR 为我们提供了一个常见的风险标尺,而这个标尺使机构能够以之前不可能的新方式来管理风险。VaR 的另一个特征是它考虑了不同风险因素之间的相关性。如果两个风险相互抵消,VaR 允许这种抵消并告诉我们总体风险相当低。

实际上,VaR 回答了投资者最常见的问题之一:我的投资的最大预期损失是多少?

VaR 为这个问题提供了一个非常直观和实用的答案。在这方面,它用于衡量公司在给定期间和预定义置信区间内的最坏预期损失。假设某项投资的日 VaR 为$1 million,并且置信区间为 95%。这意味着有 5%的机会,投资者可能会在一天内遭受超过$1 million 的损失。

根据这一定义,我们可以确定 VaR 的组成部分是置信区间、时间段、资产或投资组合的价值以及标准偏差,因为我们正在谈论风险。

总结来说,在 VaR 分析中需要强调一些重要点:

  • VaR 需要估计损失的概率。

  • VaR 集中在潜在的损失上。我们谈论的不是实际的或已实现的损失;相反,VaR 是一种损失预测。

  • VaR 有三个关键要素:

    • 定义损失水平的标准偏差。

    • 固定的时间跨度用于评估风险。

    • 置信区间。

VaR 可以通过三种不同的方法来衡量:

  • 方差-协方差 VaR

  • 历史模拟 VaR

  • 蒙特卡洛 VaR

方差-协方差方法

方差-协方差方法也被称为参数方法,因为假设观察值服从正态分布。方差-协方差方法在回报被认为是正态分布时很普遍。参数形式的假设使得方差-协方差方法的应用变得容易。

就像所有的 VaR 方法一样,我们可以使用单一资产或投资组合。然而,使用投资组合需要仔细处理,因为需要估计相关结构和投资组合方差。在这一点上,相关性变得重要,并且使用历史数据来计算相关性、均值和标准偏差。在用 ML 方法增强时,相关结构将是我们的主要关注点。

假设我们有一个由单一资产组成的投资组合,如图 5-1 所示。显示该资产的回报为零,标准差为 1,如果持有期为 1,则可以通过该资产的价值和相应的 Z 值以及标准差计算相应的 VaR 值。因此,正态性假设使事情变得更容易,但这是一个强假设,因为不能保证资产回报服从正态分布;相反,大多数资产回报并不服从正态分布。此外,由于正态性假设,可能无法捕捉到尾部的潜在风险。因此,正态性假设是有代价的。参见以下内容:

In [1]: import pandas as pd import numpy as np import matplotlib.pyplot as plt import datetime import yfinance as yf from scipy.stats import norm import requests from io import StringIO import seaborn as sns; sns.set() import warnings warnings.filterwarnings('ignore') plt.rcParams['figure.figsize'] = (10,6)In [2]: mean = 0 std_dev = 1 x = np.arange(-5, 5, 0.01) y = norm.pdf(x, mean, std_dev) pdf = plt.plot(x, y) min_ylim, max_ylim = plt.ylim() plt.text(np.percentile(x, 5), max_ylim * 0.9, '95%:${:.4f}' .format(np.percentile(x, 5))) plt.axvline(np.percentile(x, 5), color='r', linestyle='dashed', linewidth=4) plt.title('Value at Risk Illustration') plt.show()In [3]: mean = 0 std_dev = 1 x = np.arange(-5, 5, 0.01) y = norm.pdf(x, mean, std_dev) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) pdf = plt.plot(x, y) min_ylim, max_ylim = plt.ylim() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) plt.text(np.percentile(x, 5), max_ylim * 0.9, '95%:${:.4f}' .format(np.percentile(x, 5))) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) plt.axvline(np.percentile(x, 5), color='r', linestyle='dashed', linewidth=4) plt.title('Value at Risk Illustration') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (190)

基于给定的x、均值和标准差生成概率密度函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (191)

限制 x 轴和 y 轴

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (192)

指定x位于x数据的 5%分位数的位置

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (193)

图 5-1. VaR 插图
注意

根据 Fama(1965)的研究,由于厚尾和不对称性,股票价格回报并不服从正态分布。这一实证观察意味着股票回报的峰度高于正态分布。

高峰度对应于厚尾,这能够捕捉极端负回报。由于方差-协方差方法无法捕捉厚尾,因此无法估计可能发生的极端负回报,特别是在危机期间。

让我们看看如何在 Python 中应用方差-协方差 VaR。为了说明,让我们考虑一个双资产组合。方差-协方差 VaR 的公式如下:

VaR = V σ p t Z ασ p = w 1 2 σ 1 2 + w 2 2 σ 2 2 + ρ w 1 w 2 σ 1 σ 2σ p = w 1 σ 1 + w 2 + σ + 2 w 1 w 2 1,2

要在代码中应用此方法,我们从以下内容开始:

In [4]: def getDailyData(symbol): parameters = {'function': 'TIME_SERIES_DAILY_ADJUSTED', 'symbol': symbol, 'outputsize':'full', 'datatype': 'csv', 'apikey': 'insert your api key here'} ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) response = requests.get('https://www.alphavantage.co/query', params=parameters) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) csvText = StringIO(response.text) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) data = pd.read_csv(csvText, index_col='timestamp') return dataIn [5]: symbols = ["IBM", "MSFT", "INTC"] stock3 = [] for symbol in symbols: stock3.append(getDailyData(symbol)[::-1]['close'] ['2020-01-01': '2020-12-31']) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) stocks = pd.DataFrame(stock3).T stocks.columns = symbolsIn [6]: stocks.head()Out[6]: IBM MSFT INTC timestamp 2020-01-02 135.42 160.62 60.84 2020-01-03 134.34 158.62 60.10 2020-01-06 134.10 159.03 59.93 2020-01-07 134.19 157.58 58.93 2020-01-08 135.31 160.09 58.97

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (194)

确定从 Alpha Vantage 提取数据时要使用的参数。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (195)

向 Alpha Vantage 网站发出请求。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (196)

打开响应文件,该文件以文本格式存在。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (197)

反转涵盖 2019-01 至 2019-12 期间的数据,并附加 IBM、MSFT 和 INTC 的每日股价。

注意

Alpha Vantage 是一家与主要交易所和机构合作的数据提供公司。利用 Alpha Vantage 的 API,可以获取各种时间间隔(日内、日常、每周等)的股票价格、股票基本面和外汇信息。有关更多信息,请访问Alpha Vantage 的网站

然后我们进行计算:

In [7]: stocks_returns = (np.log(stocks) - np.log(stocks.shift(1))).dropna() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) stocks_returnsOut[7]: IBM MSFT INTC timestamp 2020-01-03 -0.008007 -0.012530 -0.012238 2020-01-06 -0.001788 0.002581 -0.002833 2020-01-07 0.000671 -0.009160 -0.016827 2020-01-08 0.008312 0.015803 0.000679 2020-01-09 0.010513 0.012416 0.005580 ... ... ... ... 2020-12-24 0.006356 0.007797 0.010679 2020-12-28 0.001042 0.009873 0.000000 2020-12-29 -0.008205 -0.003607 0.048112 2020-12-30 0.004352 -0.011081 -0.013043 2020-12-31 0.012309 0.003333 0.021711 [252 rows x 3 columns]In [8]: stocks_returns_mean = stocks_returns.mean() weights = np.random.random(len(stocks_returns.columns)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) weights /= np.sum(weights) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) cov_var = stocks_returns.cov() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) port_std = np.sqrt(weights.T.dot(cov_var).dot(weights)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png)In [9]: initial_investment = 1e6 conf_level = 0.95In [10]: def VaR_parametric(initial_investment, conf_level): alpha = norm.ppf(1 - conf_level, stocks_returns_mean, port_std) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) for i, j in zip(stocks.columns, range(len(stocks.columns))): VaR_param = (initial_investment - initial_investment * (1 + alpha))[j] ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) print("Parametric VaR result for {} is {} " .format(i, VaR_param)) VaR_param = (initial_investment - initial_investment * (1 + alpha)) print('--' * 25) return VaR_paramIn [11]: VaR_param = VaR_parametric(initial_investment, conf_level) VaR_param Parametric VaR result for IBM is 42606.16125893139 Parametric VaR result for MSFT is 41024.50194348814 Parametric VaR result for INTC is 43109.25240851776 --------------------------------------------------Out[11]: array([42606.16125893, 41024.50194349, 43109.25240852])

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (198)

计算对数收益率。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (199)

绘制权重的随机数。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (200)

生成权重。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (201)

计算协方差矩阵。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (202)

查找投资组合标准偏差。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (203)

使用百分点函数 (ppf) 计算特定值的 Z 分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (204)

估算方差-协方差 VaR 模型

VaR 随时间跨度变化,这意味着持有资产的时间越长,投资者越容易受到风险的影响。如图 5-2 所示,VaR 随持有时间的增加而增加。此外,持有期是投资组合清算的最长期限。考虑到报告目的,30 天可能更适合投资者。因此,我们将在下面的代码中说明该期限,在其中生成图 5-2。

In [12]: var_horizon = [] time_horizon = 30 for j in range(len(stocks_returns.columns)): for i in range(1, time_horizon + 1): var_horizon.append(VaR_param[j] * np.sqrt(i)) plt.plot(var_horizon[:time_horizon], "o", c='blue', marker='*', label='IBM') plt.plot(var_horizon[time_horizon:time_horizon + 30], "o", c='green', marker='o', label='MSFT') plt.plot(var_horizon[time_horizon + 30:time_horizon + 60], "o", c='red', marker='v', label='INTC') plt.xlabel("Days") plt.ylabel("USD") plt.title("VaR over 30-day period") plt.legend() plt.show()

方差-协方差方法的优缺点如下:

优势

  • 易于计算

  • 不需要大量样本

缺点

  • 观察值呈正态分布

  • 在非线性结构下效果不佳

  • 需要计算协方差矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (205)

图 5-2. 不同时间跨度下的 VaR

因此,即使假设正态性听起来很吸引人,也可能不是估算 VaR 的最佳方法,特别是当资产回报不符合正态分布时。幸运的是,还有另一种不需要正态性假设的方法,即历史模拟 VaR 模型。

历史模拟方法

强假设,如正态分布,可能导致估算不准确。解决此问题的方法是历史模拟 VaR。这是一种经验方法:我们不使用参数化方法,而是找到百分位数,这相当于方差-协方差方法中的 Z 表等效物。假设置信区间为 95%,则使用 5% 代替 Z 表的值,我们需要做的就是将该百分位数乘以初始投资。

历史模拟 VaR 的步骤如下:

  1. 获取投资组合(或单个资产)的资产回报

  2. 根据置信区间找到相应的返回百分位数

  3. 将该百分位数乘以初始投资

若要在代码中执行此操作,我们可以定义以下函数:

In [13]: def VaR_historical(initial_investment, conf_level): ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) Hist_percentile95 = [] for i, j in zip(stocks_returns.columns, range(len(stocks_returns.columns))): Hist_percentile95.append(np.percentile(stocks_returns.loc[:, i], 5)) print("Based on historical values 95% of {}'s return is {:.4f}" .format(i, Hist_percentile95[j])) VaR_historical = (initial_investment - initial_investment * (1 + Hist_percentile95[j])) print("Historical VaR result for {} is {:.2f} " .format(i, VaR_historical)) print('--' * 35)In [14]: VaR_historical(initial_investment,conf_level) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) Based on historical values 95% of IBM's return is -0.0371 Historical VaR result for IBM is 37081.53 ---------------------------------------------------------------------- Based on historical values 95% of MSFT's return is -0.0426 Historical VaR result for MSFT is 42583.68 ---------------------------------------------------------------------- Based on historical values 95% of INTC's return is -0.0425 Historical VaR result for INTC is 42485.39 ----------------------------------------------------------------------

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (206)

计算股票回报的 95% 百分位数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (207)

估算历史模拟 VaR

历史模拟 VaR 方法隐含地假设历史价格变动具有类似的模式,即不存在结构性断裂。此方法的优缺点如下:

优势

  • 无分布假设

  • 适用于非线性结构

  • 易于计算

缺点

  • 需要大样本

  • 需要高计算能力

蒙特卡洛模拟 VaR

在深入探讨蒙特卡罗模拟 VaR 估计之前,简要介绍一下蒙特卡罗模拟会很有好处。蒙特卡罗是一种计算数值逼*的高效工具,用于没有闭式解的情况下进行估计。蒙特卡罗依赖于从给定分布中重复生成的随机样本。

蒙特卡罗背后的逻辑由 Glasserman(2003, p. 11)定义如下:

蒙特卡罗方法基于概率与体积的类比。测度的数学形式化了概率的直观概念,将事件与一组结果相关联,并将事件的概率定义为其相对于可能结果宇宙的体积或测度。蒙特卡罗以反向方式使用此标识,通过解释体积为概率来计算集合的体积。

从应用的角度来看,蒙特卡罗与历史模拟 VaR 非常相似,但它不使用历史观察值。相反,它从给定分布中生成随机样本。蒙特卡罗通过提供可能结果和概率之间的联系来帮助决策者,使其成为金融中高效和适用的工具。

数学蒙特卡罗可以如下定义:

X 1 , X 2 , , X n 为独立同分布的随机变量,并且 f(x) 是实值函数。大数法则表明:

𝖤 ( f ( X ) ) 1 N i N f ( X i )

简而言之,蒙特卡罗模拟什么都在做随机生成样本并计算它们的均值。在计算上,它遵循以下步骤:

  1. 定义域

  2. 生成随机数

  3. 迭代和汇总结果

确定数学 π 的方法是蒙特卡罗应用的简单但有说明性的例子。

假设我们有一个半径为 r = 1 的圆,面积为 4. 圆的面积是 π ,而试图将圆放入的正方形的面积是 4. 比率结果如下:

π 4

留下 π 不变,圆和面积之间的比例可以定义为:

Circumference circle Area square = m n

一旦我们平衡这些方程式,结果是:

π = 4 x m n

如果我们逐步进行,首先是定义域,即 [-1, 1]。因此,圆内部的数字满足 x 2 + y 2 1

第二步是生成随机数以满足这一给定条件。换句话说,我们需要具有均匀分布的随机样本,在 Python 中这是一项相当简单的任务。为了练*,我将使用 NumPy 库生成 100 个均匀分布的随机数:

In [15]: x = np.random.uniform(-1, 1, 100) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) y = np.random.uniform(-1, 1, 100)In [16]: sample = 100 def pi_calc(x, y): point_inside_circle = 0 for i in range(sample): if np.sqrt(x[i] ** 2 + y[i] ** 2) <= 1: ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) point_inside_circle += 1 print('pi value is {}'.format(4 * point_inside_circle/sample))In [17]: pi_calc(x,y) pi value is 3.2In [18]: x = np.random.uniform(-1, 1, 1000000) y = np.random.uniform(-1, 1, 1000000)In [19]: sample = 1000000 def pi_calc(x, y): point_inside_circle = 0 for i in range(sample): if np.sqrt(x[i] ** 2 + y[i] ** 2) < 1: point_inside_circle += 1 print('pi value is {:.2f}'.format(4 * point_inside_circle/sample))In [20]: pi_calc(x,y) pi value is 3.14In [21]: sim_data = pd.DataFrame([]) num_reps = 1000 n = 100 for i in range(len(stocks.columns)): mean = np.random.randn(n).mean() std = np.random.randn(n).std() temp = pd.DataFrame(np.random.normal(mean, std, num_reps)) sim_data = pd.concat([sim_data, temp], axis=1) sim_data.columns = ['Simulation 1', 'Simulation 2', 'Simulation 3']In [22]: sim_dataOut[22]: Simulation 1 Simulation 2 Simulation 3 0 1.587297 -0.256668 1.137718 1 0.053628 -0.177641 -1.642747 2 -1.636260 -0.626633 0.393466 3 1.088207 0.847237 0.453473 4 -0.479977 -0.114377 -2.108050 .. ... ... ... 995 1.615190 0.940931 0.172129 996 -0.015111 -1.149821 -0.279746 997 -0.806576 -0.141932 -1.246538 998 1.609327 0.582967 -1.879237 999 -0.943749 -0.286847 0.777052 [1000 rows x 3 columns]In [23]: def MC_VaR(initial_investment, conf_level): MC_percentile95 = [] for i, j in zip(sim_data.columns, range(len(sim_data.columns))): MC_percentile95.append(np.percentile(sim_data.loc[:, i], 5)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) print("Based on simulation 95% of {}'s return is {:.4f}" .format(i, MC_percentile95[j])) VaR_MC = (initial_investment - initial_investment * (1 + MC_percentile95[j])) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) print("Simulation VaR result for {} is {:.2f} " .format(i, VaR_MC)) print('--' * 35)In [24]: MC_VaR(initial_investment, conf_level) Based on simulation 95% of Simulation 1's return is -1.7880 Simulation VaR result for Simulation 1 is 1787990.69 ---------------------------------------------------------------------- Based on simulation 95% of Simulation 2's return is -1.6290 Simulation VaR result for Simulation 2 is 1628976.68 ---------------------------------------------------------------------- Based on simulation 95% of Simulation 3's return is -1.5156 Simulation VaR result for Simulation 3 is 1515623.93 ----------------------------------------------------------------------

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (208)

从均匀分布生成随机数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (209)

检查点是否在半径为 1 的圆内

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (210)

计算每个股票回报的 95%并将结果附加到名为 MC_percentile95 的列表中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (211)

估计蒙特卡罗 VaR

波动性无处不在,但找出哪种波动性最有价值却是一项艰巨的任务。一般而言,市场上存在两种信息:噪音信号。前者仅产生随机信息,而后者通过提供有价值的信息使投资者能够赚钱。举例来说,考虑市场上的两个主要参与者:一个使用嘈杂信息的噪音交易者,以及利用信号或内幕信息的知情交易者。噪音交易者的交易动机受到随机行为的驱动。因此,市场中的信息流被认为是某些噪音交易者的买入信号和其他人的卖出信号。

然而,知情交易者被认为是理性的,因为他们能够评估信号,因为他们知道这是私人信息。

因此,连续的信息流应该谨慎处理。简而言之,来自噪音交易者的信息可以视为噪音,而来自内部人士的信息则可视为信号,而这才是重要的信息。不能区分噪音和信号的投资者可能会未能获利和/或正确评估风险。

现在问题转变为如何区分金融市场中信息流的不同。我们如何区分噪音和信号?我们如何利用这些信息?

现在是讨论马尔琴科-帕斯图尔定理的时候了,这有助于有均匀协方差矩阵。马尔琴科-帕斯图尔定理允许我们利用协方差矩阵的特征值从噪音中提取信号。

注意

A nxn 是一个方阵。那么,λ A 的特征值,x n 是对应的特征向量,如果

A x = λ x

其中 x n 0 .

特征值特征向量 在金融上下文中有特殊含义。特征向量表示协方差矩阵的方差,而特征值显示特征向量的大小。具体来说,最大特征向量对应最大方差,其大小等于相应的特征值。由于数据中存在噪声,某些特征值可以被视为随机的,因此有必要检测并过滤这些特征值,以保留信号。

为了区分噪声和信号,我们将 Marchenko–Pastur 定理的概率密度函数(PDF)拟合到嘈杂的协方差中。Marchenko–Pastur 定理的 PDF 取以下形式(Prado 2020):

f ( λ ) = T N ( λ t - λ ) ( λ - λ - ) if λ [ λ - λ - ] 0 , if λ [ λ - λ - ]

其中 λ + λ - 分别为最大和最小特征值。

在以下代码块中,这是对 Prado (2020) 提供的代码稍作修改,我们将生成 Marchenko–Pastur 分布和核密度的概率密度函数,这将允许我们以非参数化的方式对随机变量进行建模。然后,将 Marchenko–Pastur 分布拟合到数据中:

In [25]: def mp_pdf(sigma2, q, obs): lambda_plus = sigma2 * (1 + q ** 0.5) ** 2 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) lambda_minus = sigma2 * (1 - q ** 0.5) ** 2 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) l = np.linspace(lambda_minus, lambda_plus, obs) pdf_mp = 1 / (2 * np.pi * sigma2 * q * l) \ * np.sqrt((lambda_plus - l) * (l - lambda_minus)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) pdf_mp = pd.Series(pdf_mp, index=l) return pdf_mpIn [26]: from sklearn.neighbors import KernelDensity def kde_fit(bandwidth,obs,x=None): kde = KernelDensity(bandwidth, kernel='gaussian') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) if len(obs.shape) == 1: kde_fit=kde.fit(np.array(obs).reshape(-1, 1)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) if x is None: x=np.unique(obs).reshape(-1, 1) if len(x.shape) == 1: x = x.reshape(-1, 1) logprob = kde_fit.score_samples(x) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) pdf_kde = pd.Series(np.exp(logprob), index=x.flatten()) return pdf_kdeIn [27]: corr_mat = np.random.normal(size=(10000, 1000)) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) corr_coef = np.corrcoef(corr_mat, rowvar=0) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) sigma2 = 1 obs = corr_mat.shape[0] q = corr_mat.shape[0] / corr_mat.shape[1] def plotting(corr_coef, q): ev, _ = np.linalg.eigh(corr_coef) ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png) idx = ev.argsort()[::-1] eigen_val = np.diagflat(ev[idx]) ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png) pdf_mp = mp_pdf(1., q=corr_mat.shape[1] / corr_mat.shape[0], obs=1000) ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png) kde_pdf = kde_fit(0.01, np.diag(eigen_val)) ![12](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/12.png) ax = pdf_mp.plot(title="Marchenko-Pastur Theorem", label="M-P", style='r--') kde_pdf.plot(label="Empirical Density", style='o-', alpha=0.3) ax.set(xlabel="Eigenvalue", ylabel="Frequency") ax.legend(loc="upper right") plt.show() return pltIn [28]: plotting(corr_coef, q);

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (212)

计算最大期望特征值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (213)

计算最小期望特征值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (214)

生成 Marchenko-Pastur 分布的概率密度函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (215)

初始化核密度估计

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (216)

将核密度拟合到观测值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (217)

评估观测数据上的对数密度模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (218)

从正态分布生成随机样本

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (219)

将协方差矩阵转换为相关矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (220)

计算相关矩阵的特征值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (221)

将 NumPy 数组转换为对角矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (222)

调用 mp_pdf 估算 Marchenko–Pastur 分布的概率密度函数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (223)

调用 kde_fit 对数据进行核分布拟合

结果显示的图 5-3 表明 Marchenko–Pastur 分布很好地适配了数据。多亏了 Marchenko–Pastur 定理,我们能区分噪声和信号;现在我们可以称经过噪声过滤的数据为去噪数据。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (224)

图 5-3. 拟合 Marchenko–Pastur 分布

到目前为止,我们已经讨论了去噪协方差矩阵的主要步骤,以便将其插入 VaR 模型中,这被称为去噪 VaR估计。去噪协方差矩阵的意义在于从数据中剔除不必要的信息(噪音)。因此,我们可以只利用市场的信号,将注意力集中在重要的事件上。

去噪协方差矩阵包括以下阶段:¹

  1. 基于相关矩阵计算特征值和特征向量。

  2. 使用核密度估计,找到特定特征值的特征向量。

  3. 将 Marchenko–Pastur 分布拟合到核密度估计中。

  4. 使用 Marchenko–Pastur 分布找到最大理论特征值。

  5. 计算大于理论值的特征值的平均值。

  6. 使用这些新的特征值和特征向量来计算去噪相关矩阵。

  7. 通过新的相关矩阵计算去噪协方差矩阵。

让我们看看如何使用 Python 中的portfoliolab库几行代码就可以应用找到去噪协方差矩阵:

In [29]: import portfoliolab as plIn [30]: risk_estimators = pl.estimators.RiskEstimators()In [31]: stock_prices = stocks.copy()In [32]: cov_matrix = stocks_returns.cov() cov_matrixOut[32]: IBM MSFT INTC IBM 0.000672 0.000465 0.000569 MSFT 0.000465 0.000770 0.000679 INTC 0.000569 0.000679 0.001158In [33]: tn_relation = stock_prices.shape[0] / stock_prices.shape[1] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) kde_bwidth = 0.25 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) cov_matrix_denoised = risk_estimators.denoise_covariance(cov_matrix, tn_relation, kde_bwidth) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) cov_matrix_denoised = pd.DataFrame(cov_matrix_denoised, index=cov_matrix.index, columns=cov_matrix.columns) cov_matrix_denoisedOut[33]: IBM MSFT INTC IBM 0.000672 0.000480 0.000589 MSFT 0.000480 0.000770 0.000638 INTC 0.000589 0.000638 0.001158In [34]: def VaR_parametric_denoised(initial_investment, conf_level): port_std = np.sqrt(weights.T.dot(cov_matrix_denoised) .dot(weights)) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) alpha = norm.ppf(1 - conf_level, stocks_returns_mean, port_std) for i, j in zip(stocks.columns,range(len(stocks.columns))): print("Parametric VaR result for {} is {} ".format(i,VaR_param)) VaR_params = (initial_investment - initial_investment * (1 + alpha)) print('--' * 25) return VaR_paramsIn [35]: VaR_parametric_denoised(initial_investment, conf_level) Parametric VaR result for IBM is [42606.16125893 41024.50194349 43109.25240852] Parametric VaR result for MSFT is [42606.16125893 41024.50194349 43109.25240852] Parametric VaR result for INTC is [42606.16125893 41024.50194349 43109.25240852] --------------------------------------------------Out[35]: array([42519.03744155, 40937.37812611, 43022.12859114])In [36]: symbols = ["IBM", "MSFT", "INTC"] stock3 = [] for symbol in symbols: stock3.append(getDailyData(symbol)[::-1]['close'] ['2007-04-01': '2009-02-01']) stocks_crisis = pd.DataFrame(stock3).T stocks_crisis.columns = symbolsIn [37]: stocks_crisisOut[37]: IBM MSFT INTC timestamp 2007-04-02 95.21 27.74 19.13 2007-04-03 96.10 27.87 19.31 2007-04-04 96.21 28.50 19.38 2007-04-05 96.52 28.55 19.58 2007-04-09 96.62 28.57 20.10 ... ... ... ... 2009-01-26 91.60 17.63 13.38 2009-01-27 91.66 17.66 13.81 2009-01-28 94.82 18.04 14.01 2009-01-29 92.51 17.59 13.37 2009-01-30 91.65 17.10 12.90 [463 rows x 3 columns]In [38]: stock_prices = stocks_crisis.copy()In [39]: stocks_returns = (np.log(stocks) - np.log(stocks.shift(1))).dropna()In [40]: cov_matrix = stocks_returns.cov()In [41]: VaR_parametric(initial_investment, conf_level) Parametric VaR result for IBM is 42606.16125893139 Parametric VaR result for MSFT is 41024.50194348814 Parametric VaR result for INTC is 43109.25240851776 --------------------------------------------------Out[41]: array([42606.16125893, 41024.50194349, 43109.25240852])In [42]: VaR_parametric_denoised(initial_investment, conf_level) Parametric VaR result for IBM is [42606.16125893 41024.50194349 43109.25240852] Parametric VaR result for MSFT is [42606.16125893 41024.50194349 43109.25240852] Parametric VaR result for INTC is [42606.16125893 41024.50194349 43109.25240852] --------------------------------------------------Out[42]: array([42519.03744155, 40937.37812611, 43022.12859114])

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (225)

将观测数T与变量数N相关联

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (226)

确定核密度估计的带宽

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (227)

生成去噪协方差矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (228)

将去噪协方差矩阵整合到 VaR 公式中

传统应用的 VaR 与去噪 VaR 之间的差异在危机期间更加显著。在危机期间,资产之间的相关性增加,有时被称为相关性崩溃。我们将评估危机的影响以验证这一现象,并为此使用 2017 年至 2018 年的危机。然而,运行此分析需要危机的确切开始和结束日期;我们将从国家经济研究局(NBER)获取此信息,该机构宣布商业周期。

结果表明,在危机期间,相关性和因此 VaR 值变得更高。

现在,我们成功地使用了经过去噪的协方差矩阵来获得基于机器学*的风险价值(VaR),而不是直接从数据计算得到的经验矩阵。尽管 VaR 具有吸引力并且易于使用,但它并不是一个一致的风险度量,这需要满足某些条件或公理。你可以把这些公理看作是风险度量的技术要求。

α ( 0 , 1 )成为一个固定的置信水平,而(ω𝖯)则是一个概率空间,其中ω代表样本空间,表示样本空间的子集,而𝖯则是概率度量。

注意

为了说明,假设ω是投掷硬币事件中所有可能结果的集合,ω = {H, T}。 可以看作是连续两次投掷硬币, = 2 ω = 2 2。最后,概率度量,𝖯,是得到尾巴(0.5)的几率。

这里是一致风险度量的四个公理:

平移不变性

对于所有结果Y和常数a ,我们有

V a R ( Y + a ) = V a R ( Y ) + a

这意味着如果将无风险金额a添加到投资组合中,则会降低a的 VaR。

子可加性

对于所有Y 1Y 2,我们有

V a R ( Y 1 + Y 2 ) V a R ( Y 1 ) + V a R ( Y 2 )

此公理强调了在风险管理中分散化的重要性。假设Y 1Y 2作为两个资产:如果它们都包含在投资组合中,则结果的 VaR 比分开持有它们低。让我们检查 VaR 是否满足子可加性假设:

In [43]: asset1 = [-0.5, 0, 0.1, 0.4] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) VaR1 = np.percentile(asset1, 90) print('VaR for the Asset 1 is {:.4f}'.format(VaR1)) asset2 = [0, -0.5, 0.01, 0.4] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) VaR2 = np.percentile(asset2, 90) print('VaR for the Asset 2 is {:.4f}'.format(VaR2)) VaR_all = np.percentile(asset1 + asset2, 90) print('VaR for the portfolio is {:.4f}'.format(VaR_all)) VaR for the Asset 1 is 0.3100 VaR for the Asset 2 is 0.2830 VaR for the portfolio is 0.4000In [44]: asset1 = [-0.5, 0, 0.05, 0.03] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) VaR1 = np.percentile(asset1, 90) print('VaR for the Asset 1 is {:.4f}'.format(VaR1)) asset2 = [0, -0.5, 0.02, 0.8] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) VaR2 = np.percentile(asset2,90) print('VaR for the Asset 2 is {:.4f}'.format(VaR2)) VaR_all = np.percentile(asset1 + asset2 , 90) print('VaR for the portfolio is {:.4f}'.format(VaR_all)) VaR for the Asset 1 is 0.0440 VaR for the Asset 2 is 0.5660 VaR for the portfolio is 0.2750

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (229)

第一个资产的资产回报率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (230)

第二资产的资产回报率

结果表明,投资组合的 VaR 比各个单独的 VaR 之和要低,这是由于分散化通过降低风险来减少投资组合的 VaR。

正齐次性

对于所有结果 Ya > 0,我们有

V a R ( a Y ) = a V a R ( Y )

这意味着投资组合的风险和价值是同步变化的——也就是说,如果投资组合的价值增加了 a,风险也会增加 a

单调性

对于任意两个结果 Y 1Y 2 ,如果 Y 1 Y 2 ,那么:

V a R ( Y 2 ) V a R ( Y 1 )

起初,这可能看起来令人困惑,但从单调性来看,这在更高资产回报情况下暗示着更低的 VaR。

现在我们知道 VaR 不是一个一致的风险度量。然而,VaR 并不是我们估算市场风险的唯一工具。预期缺失是另一种一致的市场风险度量。

与 VaR 不同,ES 关注的是分布的尾部。更具体地说,ES 使我们能够考虑市场上的意外风险。然而,这并不意味着 ES 和 VaR 是两个完全不同的概念。相反,它们是相关的——也就是说,可以用 VaR 来表达 ES。

让我们假设损失分布是连续的;然后 ES 可以数学上定义为:

E S α = 1 1-α α 1 q u d u

其中 q 表示损失分布的分位数。ES 公式表明,它不过是损失的加权平均值,( 1 - α ) %

让我们用 q u 和 VaR 进行替代,得到以下方程:

E S α = 1 1-α α 1 V a R u d u

或者,它是超过 VaR 的损失的均值:

E S α = 𝖤 ( L | L > V a R α )

损失分布可以是连续的或离散的,你可以想象,如果是离散形式,ES 会不同,比如

E S α = 1 1-α n=0 1 max ( L n ) Pr ( L n )

其中 m a x ( L n ) 表示最大的第 n th 损失,而 Pr ( L n ) 表示第 n th 最高损失的概率。在代码中,我们可以将其公式化为:

In [45]: def ES_parametric(initial_investment , conf_level): alpha = - norm.ppf(1 - conf_level,stocks_returns_mean,port_std) for i, j in zip(stocks.columns, range(len(stocks.columns))): VaR_param = (initial_investment * alpha)[j] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) ES_param = (1 / (1 - conf_level)) \ * initial_investment \ * norm.expect(lambda x: x, lb = norm.ppf(conf_level, stocks_returns_mean[j], port_std), loc = stocks_returns_mean[j], scale = port_std) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) print(f"Parametric ES result for {i} is {ES_param}")In [46]: ES_parametric(initial_investment, conf_level) Parametric ES result for IBM is 52776.42396231898 Parametric ES result for MSFT is 54358.083277762125 Parametric ES result for INTC is 52273.33281273264

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (231)

估算方差-协方差 VaR

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (232)

给定置信区间,基于 VaR 估算 ES

ES 也可以基于历史观察值计算。与历史模拟 VaR 方法类似,可以放宽参数假设。为此,首先找到对应于 95%的第一个收益(或损失),然后大于 95%的观察值的均值给出我们的结果。

下面是我们在代码中所做的事情:

In [47]: def ES_historical(initial_investment, conf_level): for i, j in zip(stocks_returns.columns, range(len(stocks_returns.columns))): ES_hist_percentile95 = np.percentile(stocks_returns.loc[:, i], 5) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) ES_historical = stocks_returns[str(i)][stocks_returns[str(i)] <= ES_hist_percentile95]\ .mean() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) print("Historical ES result for {} is {:.4f} " .format(i, initial_investment * ES_historical))In [48]: ES_historical(initial_investment, conf_level) Historical ES result for IBM is -64802.3898 Historical ES result for MSFT is -65765.0848 Historical ES result for INTC is -88462.7404

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (233)

计算收益的 95%

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (234)

基于历史观察值估算 ES

到目前为止,我们已经看到了如何以传统方式建模预期缺失。现在,是时候引入基于机器学*的方法,进一步提升 ES 模型的估算性能和可靠性了。

正如讨论的那样,ES 为我们提供了一个连贯的风险度量来衡量市场风险。然而,尽管我们将金融风险区分为市场、信用、流动性和操作风险,但这并不意味着这些风险完全无关。相反,它们在某种程度上是相关的。也就是说,一旦金融危机冲击市场,市场风险随着信贷额度的下降而上升,进而增加流动性风险。

安东尼亚德斯(2014 年,第 6 页)支持这一观点:

公共流动性资产池是流动性风险可能影响抵押信贷供给的资源约束。

在 2007 年至 2008 年的金融危机期间,银行融资条件受到压力的主要来源来自批发融资市场中经历的资金流动性不足。

忽视风险的流动性维度可能导致低估市场风险。因此,将 ES 与流动性风险结合起来可能会更准确和可靠地估算。听起来很吸引人,但我们如何找到流动性的代理呢?

文献中,报价价差是常用于流动性建模的衡量标准。简而言之,报价价差 是买方愿意支付的最高可用价格(买价)与卖方愿意接受的最低价格(卖价)之间的差异。因此,报价价差提供了衡量交易成本的工具。

注意

流动性 可以定义为在极短的时间内进行交易并且不对市场价格产生显著影响的便利性。主要有两种流动性的主要度量:

市场流动性

资产交易的便利程度。

资金流动性

投资者可以获取资金的便利程度。

流动性及其所带来的风险将在第七章中详细讨论。

在某种程度上,报价价差是交易成本的良好指标,也是流动性的良好代理,因为交易成本是流动性的组成部分之一。根据其关注点的不同,可以以多种方式定义价差。以下是我们将用来将流动性风险纳入 ES 模型的报价价差:

有效差价

Effective spread = 2 | ( P t - P mid ) |

其中 P t 是时间 t 时的交易价格,P mid 是报价中点( ( P ask - P bid ) / 2 )。

比例报价差价

Proportional quoted spread = ( P ask - P bid ) / P mid

其中 P ask 是卖出价格,P bidP mid 分别是买入价格和中间价格。

报价差价

Quoted spread = P ask - P bid

比例有效传播

Proportional effective spread = 2 ( | P t - P mid | ) / P mid

当交易以高于报价中点的价格执行时,买方发起的交易发生。同样,当交易以低于报价中点的价格执行时,卖方发起的交易发生。然后我们可以描述有效成本如下:

Effective cost = ( P t - P mid ) / P mid for buyer-initiated ( P mid / P t ) / P mid for seller-initiated

现在我们需要找到一种方法将这些买卖差价纳入 ES 模型中,以便能够考虑流动性风险和市场风险。我们将采用两种不同的方法来完成这项任务。第一种方法是采用买卖差价的横截面均值,如 Chordia 等人所建议的(2000 年)和 Pástor 和 Stambaugh(2003 年)。第二种方法是应用主成分分析(PCA),如 Mancini 等人(2013 年)所提出的。

横截面均值只是买卖差价的逐行平均。使用此方法,我们能够生成市场范围内的流动性度量。平均公式如下:

L M,t = 1 N i N L i,t

其中 L M,t 是市场流动性,L i,t 是个体流动性度量,即我们的买卖差价。然后我们可以计算

E S L = E S + Liquidity costE S L = 1 1-α α 1 V a R u d u + 1 2 P last ( μ + k σ )

where

  • P last 是收盘股价

  • μ 是传播的平均值

  • k 是用于容纳尾部厚尾的缩放因子

  • σ 是传播的标准差

要将这些方法转换为代码,我们将执行以下操作:

In [49]: bid_ask = pd.read_csv('bid_ask.csv') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [50]: bid_ask['mid_price'] = (bid_ask['ASKHI'] + bid_ask['BIDLO']) / 2 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) buyer_seller_initiated = [] for i in range(len(bid_ask)): if bid_ask['PRC'][i] > bid_ask['mid_price'][i]: ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) buyer_seller_initiated.append(1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) else: buyer_seller_initiated.append(0) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) bid_ask['buyer_seller_init'] = buyer_seller_initiatedIn [51]: effective_cost = [] for i in range(len(bid_ask)): if bid_ask['buyer_seller_init'][i] == 1: effective_cost.append((bid_ask['PRC'][i] - bid_ask['mid_price'][i]) / bid_ask['mid_price'][i]) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) else: effective_cost.append((bid_ask['mid_price'][i] - bid_ask['PRC'][i])/ bid_ask['mid_price'][i]) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) bid_ask['effective_cost'] = effective_costIn [52]: bid_ask['quoted'] = bid_ask['ASKHI'] - bid_ask['BIDLO'] ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) bid_ask['prop_quoted'] = (bid_ask['ASKHI'] - bid_ask['BIDLO']) /\ bid_ask['mid_price'] ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) bid_ask['effective'] = 2 * abs(bid_ask['PRC'] - bid_ask['mid_price']) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) bid_ask['prop_effective'] = 2 * abs(bid_ask['PRC'] - bid_ask['mid_price']) /\ bid_ask['PRC'] ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png)In [53]: spread_meas = bid_ask.iloc[:, -5:] spread_meas.corr()Out[53]: effective_cost quoted prop_quoted effective \ effective_cost 1.000000 0.441290 0.727917 0.800894 quoted 0.441290 1.000000 0.628526 0.717246 prop_quoted 0.727917 0.628526 1.000000 0.514979 effective 0.800894 0.717246 0.514979 1.000000 prop_effective 0.999847 0.442053 0.728687 0.800713 prop_effective effective_cost 0.999847 quoted 0.442053 prop_quoted 0.728687 effective 0.800713 prop_effective 1.000000In [54]: spread_meas.describe()Out[54]: effective_cost quoted prop_quoted effective prop_effective count 756.000000 756.000000 756.000000 756.000000 756.000000 mean 0.004247 1.592583 0.015869 0.844314 0.008484 std 0.003633 0.921321 0.007791 0.768363 0.007257 min 0.000000 0.320000 0.003780 0.000000 0.000000 25% 0.001517 0.979975 0.010530 0.300007 0.003029 50% 0.003438 1.400000 0.013943 0.610000 0.006874 75% 0.005854 1.962508 0.019133 1.180005 0.011646 max 0.023283 8.110000 0.055451 6.750000 0.047677In [55]: high_corr = spread_meas.corr().unstack()\ .sort_values(ascending=False).drop_duplicates() ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/9.png) high_corr[(high_corr > 0.80) & (high_corr != 1)] ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/10.png)Out[55]: effective_cost prop_effective 0.999847 effective effective_cost 0.800894 prop_effective effective 0.800713 dtype: float64In [56]: sorted_spread_measures = bid_ask.iloc[:, -5:-2]In [57]: cross_sec_mean_corr = sorted_spread_measures.mean(axis=1).mean() ![11](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/11.png) std_corr = sorted_spread_measures.std().sum() / 3 ![12](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/12.png)In [58]: df = pd.DataFrame(index=stocks.columns) last_prices = [] for i in symbols: last_prices.append(stocks[i].iloc[-1]) ![13](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/13.png) df['last_prices'] = last_pricesIn [59]: def ES_parametric(initial_investment, conf_level): ES_params = [ ] alpha = - norm.ppf(1 - conf_level, stocks_returns_mean, port_std) for i,j in zip(stocks.columns,range(len(stocks.columns))): VaR_param = (initial_investment * alpha)[j] ES_param = (1 / (1 - conf_level)) \ * norm.expect(lambda x: VaR_param, lb = conf_level) ES_params.append(ES_param) return ES_paramsIn [60]: ES_params = ES_parametric(initial_investment, conf_level) for i in range(len(symbols)): print(f'The ES result for {symbols[i]} is {ES_params[i]}') The ES result for IBM is 145760.89803654602 The ES result for MSFT is 140349.84772375744 The ES result for INTC is 147482.03450111256In [61]: k = 1.96 for i, j in zip(range(len(symbols)), symbols): print('The liquidity Adjusted ES of {} is {}' .format(j, ES_params[i] + (df.loc[j].values[0] / 2) * (cross_sec_mean_corr + k * std_corr))) ![14](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/14.png) The liquidity Adjusted ES of IBM is 145833.08767607837 The liquidity Adjusted ES of MSFT is 140477.40110495212 The liquidity Adjusted ES of INTC is 147510.60526566216

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (235)

导入 bid_ask 数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (236)

计算中间价格

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (237)

定义买方和卖方发起交易的条件

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (238)

如果满足以上条件,则返回 1,并将其附加到 buyer_seller_initiated 列表中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (239)

如果不满足以上条件,则返回 0,并将其附加到 buyer_seller_initiated 列表中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (240)

如果 buyer_seller_initiated 变量取值为 1,则运行相应的有效成本公式

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (241)

如果 buyer_seller_initiated 变量取值为 0,则运行相应的有效成本公式

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (242)

计算报价、比例报价、有效和比例有效传播

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (243)

获得相关矩阵并按列列出它们

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (244)

对相关性大于 80% 的排序

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (245)

计算传播测量的横截面均值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (246)

获取价差的标准偏差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (247)

stocks数据中过滤最后观察到的股票价格

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (248)

估算调整后的 ES(预期损失)

PCA 是用于减少维度的方法。它用尽可能少的组件提取尽可能多的信息。如果我们以图 5-4 为例,从五个特征中,我们可能选择两个组件。因此,我们通过选择组件数来减少维度,牺牲了信息,因为取决于我们选择的截止点,我们选择了组件数量,丢掉了多少组件就丢掉了多少信息。

更具体地说,当图 5-4 变得平坦时,这意味着我们保留的信息较少,这是 PCA 的截止点。但是,选择合适的组件数量并不容易,因为在截止点和保留信息之间存在权衡。一方面,截止点越高(我们有更多的组件),我们保留的信息就越多(我们减少的维度就越少)。另一方面,截止点越低(我们有更少的组件),我们保留的信息就越少(我们减少的维度就越高)。得到平坦的屏幕图并不是选择合适组件数量的唯一标准,那么选择适当的组件数量的可能标准是什么呢?以下是 PCA 选择合适组件数量的可能标准:

  • 解释方差大于 80%

  • 多个特征值

  • 当屏幕图变得平坦时

注意

请注意,流动性调整也可以应用于 VaR。 VaR 的相同过程也适用。从数学上讲,

V a R L = σ p t Z α + 1 2 P last ( μ + k σ )

此应用留给读者。

然而,减少维度并不是我们唯一可以利用的东西。在这个例子中,我们应用 PCA 是为了获得流动性的独特特征,因为 PCA 为我们从数据中过滤出最重要的信息:

In [62]: from sklearn.decomposition import PCA from sklearn.preprocessing import StandardScalerIn [63]: scaler = StandardScaler() spread_meas_scaled = scaler.fit_transform(np.abs(spread_meas)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) pca = PCA(n_components=5) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) prin_comp = pca.fit_transform(spread_meas_scaled) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [64]: var_expl = np.round(pca.explained_variance_ratio_, decimals=4) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) cum_var = np.c*msum(np.round(pca.explained_variance_ratio_, decimals=4)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) print('Individually Explained Variances are:\n{}'.format(var_expl)) print('=='*30) print('Cumulative Explained Variances are: {}'.format(cum_var)) Individually Explained Variances are: [0.7494 0.1461 0.0983 0.0062 0. ] ============================================================ Cumulative Explained Variances are: [0.7494 0.8955 0.9938 1. 1. ]In [65]: plt.plot(pca.explained_variance_ratio_) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) plt.xlabel('Number of Components') plt.ylabel('Variance Explained') plt.title('Scree Plot') plt.show()In [66]: pca = PCA(n_components=2) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png) pca.fit(np.abs(spread_meas_scaled)) prin_comp = pca.transform(np.abs(spread_meas_scaled)) prin_comp = pd.DataFrame(np.abs(prin_comp), columns = ['Component 1', 'Component 2']) print(pca.explained_variance_ratio_*100) [65.65640435 19.29704671]In [67]: def myplot(score, coeff, labels=None): xs = score[:, 0] ys = score[:, 1] n = coeff.shape[0] scalex = 1.0 / (xs.max() - xs.min()) scaley = 1.0 / (ys.max() - ys.min()) plt.scatter(xs * scalex * 4, ys * scaley * 4, s=5) for i in range(n): plt.arrow(0, 0, coeff[i, 0], coeff[i, 1], color = 'r', alpha=0.5) if labels is None: plt.text(coeff[i, 0], coeff[i, 1], "Var"+str(i), color='black') else: plt.text(coeff[i,0 ], coeff[i, 1], labels[i], color='black') plt.xlabel("PC{}".format(1)) plt.ylabel("PC{}".format(2)) plt.grid()In [68]: spread_measures_scaled_df = pd.DataFrame(spread_meas_scaled, columns=spread_meas.columns)In [69]: myplot(np.array(spread_measures_scaled_df)[:, 0:2], np.transpose(pca.components_[0:2,:]), list(spread_measures_scaled_df.columns)) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (249)

标准化价差测量值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (250)

将主成分数确定为 5

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (251)

将主成分应用于spread_measures_scaled

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (252)

观察五个主成分的解释方差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (253)

观察五个主成分的累积解释方差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (254)

绘制屏幕图(图 5-4)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (255)

根据屏幕图,确定使用两个组件进行 PCA 分析

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (256)

绘制双标图(图 5-5)以观察组件和特征之间的关系

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (257)

图 5-4. PCA 屏幕图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (258)

图 5-5. PCA 双标图

现在我们拥有所有必要的信息,并通过整合这些信息,我们能够计算流动性调整的 ES。毫不奇怪,下面的代码显示,与标准 ES 应用相比,流动性调整的 ES 提供了更大的值。这意味着在我们的 ES 估计中包含了流动性维度,导致了更高的风险:

In [70]: prin_comp1_rescaled = prin_comp.iloc[:,0] * prin_comp.iloc[:,0].std()\ + prin_comp.iloc[:, 0].mean() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) prin_comp2_rescaled = prin_comp.iloc[:,1] * prin_comp.iloc[:,1].std()\ + prin_comp.iloc[:, 1].mean() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) prin_comp_rescaled = pd.concat([prin_comp1_rescaled, prin_comp2_rescaled], axis=1) prin_comp_rescaled.head()Out[70]: Component 1 Component 2 0 1.766661 1.256192 1 4.835170 1.939466 2 3.611486 1.551059 3 0.962666 0.601529 4 0.831065 0.734612In [71]: mean_pca_liq = prin_comp_rescaled.mean(axis=1).mean() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) mean_pca_liqOut[71]: 1.0647130086973815In [72]: k = 1.96 for i, j in zip(range(len(symbols)), symbols): print('The liquidity Adjusted ES of {} is {}' .format(j, ES_params[i] + (df.loc[j].values[0] / 2) * (mean_pca_liq + k * std_corr))) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) The liquidity Adjusted ES of IBM is 145866.2662997893 The liquidity Adjusted ES of MSFT is 140536.02510785797 The liquidity Adjusted ES of INTC is 147523.7364940803

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (259)

计算流动性调整 ES 公式的流动性部分,用于第一主成分

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (260)

计算流动性调整 ES 公式的流动性部分,用于第二主成分

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (261)

计算两个主成分的横截面均值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (262)

估算流动性调整的 ES

市场风险一直受到关注,因为它显示了公司在市场事件带来的风险方面的脆弱程度。在金融风险管理教材中,往往会找到 VaR 和 ES 模型,这两个模型在理论和实践中都是突出和常用的模型。在本章中,在介绍这些模型之后,介绍了重新审视和改进模型估计的模型。为此,我们首先尝试区分噪声和信号的信息流,这被称为去噪。然后,我们使用去噪的协方差矩阵来改进 VaR 估计。

接下来,我们讨论了 ES 模型作为一个连贯的风险度量。我们用来改进这个模型的方法是基于流动性的方法,通过这个方法,我们重新审视了 ES 模型,并使用流动性组件增强了它,以便能够考虑流动性风险来估计 ES。

市场风险估计的进一步改进是可能的,但我们在这里的目标是提供一个一般的概念和必要的工具,以为基于 ML 的市场风险方法提供一个体面的基础。但是,您可以进一步应用不同的工具。在下一章中,我们将讨论像巴塞尔银行监督委员会(BCBS)等监管机构建议的信贷风险建模,然后使用基于 ML 的方法丰富这个模型。

本章引用的文章:

  • Antoniades, Adonis. 2016. “流动性风险与 2007-2008 年信贷紧缩: 基于抵押贷款申请的微观数据的证据。” 金融与数量分析杂志 51 (6): 1795-1822。

  • Bzdok, D., N. Altman, and M. Krzywinski. 2018. “要点: 统计学与机器学*。” 自然方法 15 (4): 233-234.

  • BIS,市场风险 RWA 的计算,2020 年。

  • Chordia, Tarun, Richard Roll, 和 Avanidhar Subrahmanyam. 2000. “流动性的共性。” 金融经济学杂志 56 (1): 3-28。

  • Mancini, Loriano, Angelo Ranaldo, 和 Jan Wrampelmeyer. 2013. “外汇市场流动性:测量、共性和风险溢价。” 金融学杂志 68 (5): 1805-1841。

  • Pástor, Ľuboš, 和 Robert F. Stambaugh. 2003. “流动性风险与预期股票回报。” 政治经济学杂志 111 (3): 642-685。

本章引用的书籍有:

  • Dowd, Kevin. 2003. 市场风险测量导论. Hoboken, NJ: John Wiley and Sons.

  • Glasserman, Paul. 金融工程中的蒙特卡罗方法. 2013. 随机建模与应用概率系列,第 53 卷。纽约:Springer Science & Business Media。

  • M. López De Prado. 2020. 资产管理的机器学*. 剑桥:剑桥大学出版社。

¹ 此过程的详细信息可在Hudson and Thames找到。

² 更多信息请参阅NBER 的网站

尽管市场风险得到了更好的研究,但银行经济资本的较大部分通常用于信用风险。因此,传统标准方法的信用风险测量、分析和管理的复杂性可能不符合其重要性。

Uwe Wehrspohn (2002)

金融机构的主要角色是建立一个渠道,使资金从盈余实体流向赤字实体。因此,金融机构确保了金融系统中的资本配置,并在这些交易中获取利润。

但是,金融机构处理的一个重要风险是信用风险。这是一个如此巨大的风险,以至于如果没有它,资本配置可能会更少成本更高效。信用风险是指借款人无法履行其债务时出现的风险。换句话说,当借款人违约时,他们未能偿还其债务,这导致金融机构遭受损失。

信用风险及其目标可以用更正式的方式定义(BCBS 和 BIS 2000 年):

信用风险最简单的定义是银行借款人或交易对手可能无法按照约定的条件履行其义务的潜力。信用风险管理的目标是通过在可接受的参数内保持信用风险敞口,从而最大化银行的风险调整后的回报率。

估算信用风险是一项如此艰巨的任务,以至于一个监管机构巴塞尔密切监视金融市场的最新发展,并制定规定以加强银行的资本要求。对于银行拥有强大的资本要求的重要性在于,银行应在动荡时期拥有资本缓冲。

政策制定者普遍认为,为确保金融体系的稳定,金融机构应设定最低资本要求,因为一连串的违约可能导致金融市场崩溃,而金融机构向彼此提供抵押品。那些试图规避这种资本要求的人在2007—2008 年抵押贷款危机期间吃了苦头。

当然,至少确保最低资本要求对金融机构来说是一种负担,因为资本是它们无法向赤字实体输送以盈利的资产。因此,管理信用风险等于进行盈利和高效的交易。

在这方面,本章展示了如何利用前沿的机器学*模型估计信用风险。我们从信用风险的理论背景开始讨论。毫无疑问,在信用风险分析中有许多主题,但我们专注于违约概率以及如何引入机器学*方法来估计它。为此,通过聚类方法对客户进行分段,以便可以分别为这些数据拟合模型。这样做可以更好地适应不同客户段的信用风险数据分布变化。根据得到的聚类,引入了包括贝叶斯方法在内的机器学*和深度学*模型来建模信用风险。

除了违约概率(即借款人未能偿还债务的可能性)之外,信用风险还具有三个定义特征:

暴露

这指的是可能违约或其履行能力发生不利变化的方当事人。

可能性

此方当事人可能会违约其义务的可能性。

恢复率

如果违约发生,可以收回多少。

BIS 提出了全球金融信用管理标准,称为巴塞尔协议。目前有三个巴塞尔协议。1988 年的巴塞尔 I 协议规定,要求持有资本至少相当于风险加权资产的 8%。

巴塞尔 I 协议包括第一个资本测量系统,这是在拉美债务危机爆发后创建的。在巴塞尔 I 中,资产分类如下:

  • 0% 用于无风险资产。

  • 20% 用于向其他银行提供贷款。

  • 50% 用于住宅抵押贷款

  • 100% 用于企业债务

1999 年,巴塞尔 II 根据三大支柱对巴塞尔 I 进行了修订。

  • 最低资本要求,旨在发展和扩展 1988 年协议中制定的标准化规则

  • 对机构资本充足性和内部评估过程的监督审查

  • 利用信息披露作为加强市场纪律和鼓励良好银行业务实践的杠杆

最后一项协议,2010 年的巴塞尔 III,是不可避免的。在 2007-2008 年的抵押贷款危机加剧之际。它引入了一套新的措施,进一步增强了流动性和恶劣治理实践。例如,引入了股本要求,以防止在金融系统中发生连续的失败,即所谓的多米诺效应,在金融动荡和危机时期。因此,巴塞尔 III 要求在表 6-1 中列出的银行金融比率。

表 6-1. 巴塞尔 III 要求的金融比率

金融比率公式
一级资本比率权益资本 风险加权资产 > = 4 . 5 %
杠杆比率一级资本 平均总资产 > = 3 %
流动性覆盖率优质流动性资产存量 未来 30 个日历日的总净现金流出 > = 100 %

巴塞尔协议 II 建议银行实施标准化方法或基于内部评级(IRB)的方法来估计信用风险。标准化方法超出了本书的范围,但感兴趣的读者可以参考《信用风险标准化方法》BIS 的咨询文件

现在让我们专注于内部评级方法(IRB);这种内部评估的关键参数包括:

Expected loss = EAD × LGD × PD

其中PD是违约概率,LGD是违约时的预期损失(取值范围在 0 到 1 之间),EAD是违约时的暴露。

估计信用风险最重要和具有挑战性的部分是建模违约概率,本章的目标主要是提出一个机器学*模型来解决这个问题。在继续之前,还有一个在估计信用风险中有时被忽视或忽略的重要问题:风险分桶

风险分桶只是将信用价值相似的借款人分组。风险分桶背后的故事是获得同质性的组或簇,以便我们能更好地估计信用风险。将不同风险借款人一视同仁可能导致预测不佳,因为模型无法同时捕捉数据的完全不同特征。因此,通过根据风险性将借款人分成不同的组别,风险分桶使我们能够做出准确的预测。

风险分桶可以通过不同的统计方法来实现,但我们将应用聚类技术,最终得到使用 K-means 算法形成的同质性聚类。

我们生活在数据时代,但这并不意味着我们总能找到正在寻找的数据。相反,若不应用数据整理和清理技术,很少能找到它。

当数据具有依赖变量时,当然更易处理,同时还能帮助我们获得更准确的结果。然而,有时我们需要揭示数据的隐藏特征——也就是说,如果借款人的风险性未知,我们应该提出一种基于他们风险性分组的解决方案。

聚类是提出的方法,用于创建这些组或。最佳聚类应该使得空间上的簇之间距离较远:

聚类将数据实例分组到子集中,使得相似实例归于同一组,而不同实例则属于不同组。这样一来,实例被组织成一个有效的表示,以描述被抽样的总体。

Rokach 和 Maimon(2005)

存在不同的聚类方法,但 K 均值算法适合我们的目的,即为信用风险分析创建风险分组。在 K 均值中,观测值在簇内的距离是基于簇中心,即质心来计算的。根据到质心的距离,将观测值聚类。这种距离可以通过不同的方法来衡量。其中,以下是最为知名的度量方法:

欧几里得

i=1 n (p i -q i ) 2

闵可夫斯基

( i=1 n | p i - q i | p ) 1/p

曼哈顿

i=1 n | p i - q i |

聚类的目标是最小化质心与观测值之间的距离,以便将相似的观测值放置在同一簇中。这种逻辑基于这样的直觉:观测值越相似,它们之间的距离就越小。因此,我们寻求最小化观测值与质心之间的距离,这另一种说法是我们在最小化质心和观测值之间的平方误差和:

i=1 K xC i (C i -x) 2

其中x是观测值,C i 是第t个簇的质心。然而,考虑到观测值的数量和簇的组合,搜索区域可能过大难以处理。这可能听起来有点吓人,但别担心:我们有期望-最大化(E-M)算法来支持我们的聚类。由于 K 均值没有闭式解,我们正在寻找一个*似解,而 E-M 算法提供了这个解。在期望-最大化(E-M)算法中,E表示将观测值分配给最*的质心,M表示通过更新参数完成数据生成过程。

在期望-最大化(E-M)算法中,观测值与质心之间的距离被迭代地最小化。算法的工作步骤如下:

  1. 选择k个随机点作为质心。

  2. 根据选择的距离度量计算观察和* n *质心之间的距离。基于这些距离,将每个观察分配到最*的集群。

  3. 根据分配更新集群中心。

  4. 从步骤 2 开始重复该过程,直到质心不再改变。

现在,我们使用 K-means 聚类进行风险分桶。要确定最优的簇数,将采用不同的技术。首先,我们使用肘部法则,这基于惯性

惯性被计算为观察到它们最*的质心的平方距离之和。其次,引入* Silhouette 分数* 作为工具来确定最优簇的数量。这个值在 1 到-1 之间。值为 1 表示观察接*正确的质心并且正确分类。然而,-1 显示观察未正确分组。Silhouette 分数的强度在于考虑到簇内距离和簇间距离。Silhouette 分数的公式如下:

Silhouette score = x-y max(x,y)

其中 * x * 是簇之间的平均距离,* y * 是簇内的平均距离。

第三种方法是Calinski-Harabasz (CH),也称为方差比准则。CH 方法的公式如下:

CH = SS B SS W × N-k k-1

其中 S S B 表示簇间方差, S S W 表示簇内方差,* N * 是观测数,* k * 是簇的数目。根据这些信息,我们寻找高 CH 分数,因为簇间方差(簇内方差越小)越大,越有利于找到最优簇的数量。

最后的方法是间隙分析。Tibshirani 等人(2001 年)提出了一种独特的方法,通过这种方法,我们能够基于参考分布找到最优的簇数。根据 Tibshirani 等人的类似符号,设 d ii ex ijx i ej 之间的欧几里德距离,让 C ri th 簇,表示* r *数的观测:

j (x ij -x i ej ) 2

所有集群中观察到的成对距离之和为:

D r = i,i e C r d i,i e

簇内平方和, W k ,为:

W k = r=1 k 1 2 n r D r

其中 n 是样本大小,W k 的期望为:

W k = l o g ( p n / 12 ) - ( 2 / p ) l o g ( k ) + c o n s t a n t

其中 pk 分别为维度和质心。让我们使用德国信用风险数据创建一个实践练*。该数据是从 Kaggle 平台 收集的,变量的解释如下:

  • 年龄:数值型

  • 性别:男性、女性

  • 工作:0—无技能和非居民,1—无技能和居民,2—技术工人,3—高技能工人

  • 住房:自有、租赁、免费

  • 储蓄账户:少、中等、相当丰富、丰富

  • 支票账户:数值型

  • 信用额度:数值型

  • 期限:数值型

  • 目的:汽车、家具/设备、收音机/电视、家用电器、维修、教育、商业、度假/其他

估计最佳聚类的值将是最大化间隙统计量的值,因为间隙统计量是不同 k 值的总内部聚类变化和它们在相应数据的空值参考分布下的期望值之间的差异。当我们获得最高的间隙值时,决策就做出了。

在以下代码块中,我们导入德国信用数据集并且删除了不必要的列。数据集包含分类和数值两种值,需要分别处理,我们很快就会做到这一点:

In [1]: import pandas as pdIn [2]: credit = pd.read_csv('credit_data_risk.csv')In [3]: credit.head()Out[3]: Unnamed: 0 Age Sex Job Housing Saving accounts Checking account \ 0 0 67 male 2 own NaN little 1 1 22 female 2 own little moderate 2 2 49 male 1 own little NaN 3 3 45 male 2 free little little 4 4 53 male 2 free little little Credit amount Duration Purpose Risk 0 1169 6 radio/TV good 1 5951 48 radio/TV bad 2 2096 12 education good 3 7882 42 furniture/equipment good 4 4870 24 car badIn [4]: del credit['Unnamed: 0'] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (263)

删除不必要的列名为 Unnamed: 0

摘要统计数据如下代码所示。根据统计数据,客户的平均年龄约为 35 岁,平均工作类型为技术工人,平均信用额度和期限分别为 3,271 和 21。此外,摘要统计数据告诉我们 信用额度 变量显示出相对较高的标准差,正如预期的那样。期限年龄 变量具有非常相似的标准差,但期限的变化范围在一个较窄的区间内,其最小值和最大值分别为 4 和 72。由于 工作 是一个离散变量,因此预期低离散度,并且我们确实得到了:

In [5]: credit.describe()Out[5]: Age Job Credit amount Duration count 1000.000000 1000.000000 1000.000000 1000.000000 mean 35.546000 1.904000 3271.258000 20.903000 std 11.375469 0.653614 2822.736876 12.058814 min 19.000000 0.000000 250.000000 4.000000 25% 27.000000 2.000000 1365.500000 12.000000 50% 33.000000 2.000000 2319.500000 18.000000 75% 42.000000 2.000000 3972.250000 24.000000 max 75.000000 3.000000 18424.000000 72.000000

接下来,通过直方图检查数据集中数值变量的分布,结果显示没有一个变量遵循正态分布。正如我们在 图 6-1 中看到的那样,年龄信用额度期限 变量呈正偏态分布。

In [6]: import matplotlib.pyplot as plt import seaborn as sns; sns.set() plt.rcParams["figure.figsize"] = (10,6) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [7]: numerical_credit = credit.select_dtypes(exclude='O') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [8]: plt.figure(figsize=(10, 8)) k = 0 cols = numerical_credit.columns for i, j in zip(range(len(cols)), cols): k +=1 plt.subplot(2, 2, k) plt.hist(numerical_credit.iloc[:, i]) plt.title(j)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (264)

设定一个固定的图像尺寸

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (265)

删除对象类型变量以获取所有数值变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (266)

图 6-1. 信用风险数据直方图

图 6-1 显示了年龄、工作、信用额度和期限变量的分布。除了 工作 变量是离散变量外,所有其他变量都呈偏态分布。

首先介绍肘部法,在下面的代码片段中,并得到结果 图6-2。为了找到最佳的聚类数,我们观察曲线的斜率,并决定曲线变平的截止点——也就是说,曲线的斜率变小。随着曲线变平,所述聚类内点的惯性减少,这对于聚类是有益的。另一方面,随着我们允许惯性减少,聚类数量增加,这使得分析更加复杂。考虑到这种权衡,停止标准是曲线变平的点。在代码中:

In [9]: from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans import numpy as npIn [10]: scaler = StandardScaler() scaled_credit = scaler.fit_transform(numerical_credit) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [11]: distance = [] for k in range(1, 10): kmeans = KMeans(n_clusters=k) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) kmeans.fit(scaled_credit) distance.append(kmeans.inertia_) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [12]: plt.plot(range(1, 10), distance, 'bx-') plt.xlabel('k') plt.ylabel('Inertia') plt.title('The Elbow Method') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (267)

应用标准化以进行缩放

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (268)

运行 K-means 算法

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (269)

计算 inertia 并存储到名为 distance 的列表中

图6-2 显示在四个聚类后曲线变得平缓。因此,肘部法建议我们在四个聚类停止。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (270)

图 6-2. 肘部法

下面的代码给出了从聚类 2 到 10 的 Silhouette 分数,结果为 图6-3,x 轴上的 Silhouette 分数。给定由虚线表示的平均 Silhouette 分数,最佳聚类数为两个:

In [13]: from sklearn.metrics import silhouette_score ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) from yellowbrick.cluster import SilhouetteVisualizer ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [14]: fig, ax = plt.subplots(4, 2, figsize=(25, 20)) for i in range(2, 10): km = KMeans(n_clusters=i) q, r = divmod(i, 2) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) visualizer = SilhouetteVisualizer(km, colors='yellowbrick', ax=ax[q - 1][r]) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) visualizer.fit(scaled_credit) ax[q - 1][r].set_title("For Cluster_"+str(i)) ax[q - 1][r].set_xlabel("Silhouette Score")

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (271)

导入 silhouette_score 模块以计算 Silhouette 分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (272)

导入 SilhouetteVisualizer 模块以绘制 Silhouette 图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (273)

使用 divmod 配置标签,因为它返回商 (q) 和余数 (r)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (274)

绘制 Silhouette 分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (275)

图 6-3. Silhouette 分数

如前所述,CH 方法是一种寻找最佳聚类的方便工具,以下代码显示了如何在 Python 中使用该方法,结果为 图6-4。我们寻找最高的 CH 分数,并且我们将看到它在聚类 2 处获得:

In [15]: from yellowbrick.cluster import KElbowVisualizer ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) model = KMeans() visualizer = KElbowVisualizer(model, k=(2, 10), metric='calinski_harabasz', timings=False) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) visualizer.fit(scaled_credit) visualizer.show()Out[]: <Figure size 576x396 with 0 Axes>

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (276)

导入 KElbowVisualizer 以绘制 CH 分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (277)

可视化 CH 指标

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (278)

图 6-4. CH 方法

图6-4 显示肘部出现在第二个聚类,表明停在两个聚类是最佳决定。

寻找最佳聚类数的最后一步是间隙分析,结果为 图6-5:

In [16]: from gap_statistic.optimalK import OptimalK ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [17]: optimalK = OptimalK(n_jobs=8, parallel_backend='joblib') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) n_clusters = optimalK(scaled_credit, cluster_array=np.arange(1, 10)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [18]: gap_result = optimalK.gap_df ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) gap_result.head()Out[18]: n_clusters gap_value gap* ref_dispersion_std sk \ 0 1.0 0.889755 5738.286952 54.033596 0.006408 1 2.0 0.968585 4599.736451 366.047394 0.056195 2 3.0 1.003974 3851.032471 65.026259 0.012381 3 4.0 1.044347 3555.819296 147.396138 0.031187 4 5.0 1.116450 3305.617917 27.894622 0.006559 sk* diff diff* 0 6626.296782 -0.022635 6466.660374 1 5328.109873 -0.023008 5196.127130 2 4447.423150 -0.009186 4404.645656 3 4109.432481 -0.065543 4067.336067 4 3817.134689 0.141622 3729.880829In [19]: plt.plot(gap_result.n_clusters, gap_result.gap_value) min_ylim, max_ylim = plt.ylim() plt.axhline(np.max(gap_result.gap_value), color='r', linestyle='dashed', linewidth=2) plt.title('Gap Analysis') plt.xlabel('Number of Cluster') plt.ylabel('Gap Value') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (279)

导入 OptimalK 模块以计算间隙统计量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (280)

运行间隙统计量并使用并行化

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (281)

根据间隙统计确定聚类数量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (282)

存储间隙分析结果

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (283)

图 6-5. 差距分析

我们在图6-5 中观察到一个急剧增加的情况,直到间隙值达到其峰值,并且分析建议在找到最佳聚类数量的最大值时停止。在本例中,我们发现在第 5 个聚类处的值,因此这是截断点。

综合这些讨论,选择了两个聚类作为最佳聚类数量,并相应进行了 K-means 聚类分析。为了说明,考虑到聚类分析,让我们用以下方式可视化 2-D 聚类结果,导致图6-6:

In [20]: kmeans = KMeans(n_clusters=2) clusters = kmeans.fit_predict(scaled_credit)In [21]: plt.figure(figsize=(10, 12)) plt.subplot(311) plt.scatter(scaled_credit[:, 0], scaled_credit[:, 2], c=kmeans.labels_, cmap='viridis') plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 2], s = 80, marker= 'x', color = 'k') plt.title('Age vs Credit') plt.subplot(312) plt.scatter(scaled_credit[:, 0], scaled_credit[:, 2], c=kmeans.labels_, cmap='viridis') plt.scatter(kmeans.cluster_centers_[:, 0], kmeans.cluster_centers_[:, 2], s = 80, marker= 'x', color = 'k') plt.title('Credit vs Duration') plt.subplot(313) plt.scatter(scaled_credit[:, 2], scaled_credit[:, 3], c=kmeans.labels_, cmap='viridis') plt.scatter(kmeans.cluster_centers_[:, 2], kmeans.cluster_centers_[:, 3], s = 120, marker= 'x', color = 'k') plt.title('Age vs Duration') plt.show()

图6-6 展示了观察行为,交叉符号x表示聚类中心,即质心。年龄表示更分散的数据,而age变量的质心位于credit变量的上方。在图6-6 的第二个子图中展示了两个连续变量,即creditduration,我们观察到清晰分离的聚类。该图表明,与credit变量相比,持续时间变量更为波动。在最后一个子图中,通过散点分析检验了ageduration之间的关系。结果表明,在这两个变量之间存在许多重叠的观察结果。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (284)

图 6-6. K-means 聚类

获得聚类后,我们能够以同样方式处理具有类似特征的客户,即如果提供具有相似分布的数据,则模型可以更轻松和更稳定地学*。相反,对整个样本使用所有客户可能导致预测效果差且不稳定。

本节最终涉及使用贝叶斯估计计算违约概率,但首先让我们看看逻辑回归,以便进行比较。¹

逻辑回归是一种分类算法,在金融行业广泛应用。换句话说,它提出了解决分类问题的回归方法。逻辑回归旨在预测离散输出,并考虑了一些独立变量。

X为自变量集合,Y为二进制(或多项式)输出。然后,条件概率为:

Pr ( Y = 1 | X = x )

可以理解为:给定X的值,Y为 1 的概率是多少?由于逻辑回归的因变量是概率型的,我们需要确保因变量的取值只能在 0 到 1 之间。

为此,应用一种称为logistic(logit)转换的修改,它简单地是概率比的对数(p / 1 - p):

l o g ( p 1-p )

逻辑回归模型的形式如下所示:

l o g ( p 1-p ) = β 0 + β 1 x

解决p的结果如下:

p = e β 0 +β 1 x 1+e β 0 +β 1 x

让我们首先准备数据应用。首先,我们将集群区分为 0 和 1。信用数据有一列名为risk,表示客户的风险水平。接下来,检查集群 0 和集群 1 中每种风险的观察次数;结果显示,集群 0 和 1 中有 571 名好客户和 129 名好客户。在代码中:

In [22]: clusters, counts = np.unique(kmeans.labels_, return_counts=True) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [23]: cluster_dict = {} for i in range(len(clusters)): cluster_dict[i] = scaled_credit[np.where(kmeans.labels_==i)] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [24]: credit['clusters'] = pd.DataFrame(kmeans.labels_) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [25]: df_scaled = pd.DataFrame(scaled_credit) df_scaled['clusters'] = credit['clusters']In [26]: df_scaled['Risk'] = credit['Risk'] df_scaled.columns = ['Age', 'Job', 'Credit amount', 'Duration', 'Clusters', 'Risk']In [27]: df_scaled[df_scaled.Clusters == 0]['Risk'].value_counts() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)Out[27]: good 571 bad 193 Name: Risk, dtype: int64In [28]: df_scaled[df_scaled.Clusters == 1]['Risk'].value_counts() ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png)Out[28]: good 129 bad 107 Name: Risk, dtype: int64

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (285)

获取集群编号

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (286)

基于集群编号,区分集群并将它们存储在名为cluster_dict的字典中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (287)

使用 K-means 标签创建clusters

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (288)

观察集群内类别的观察次数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (289)

查找每个类别的观察次数

接下来,我们绘制了几个条形图,以显示每个风险级别类别的观察次数差异(图 6-7 和 6-8):

In [29]: df_scaled[df_scaled.Clusters == 0]['Risk'].value_counts()\ .plot(kind='bar', figsize=(10, 6), title="Frequency of Risk Level");In [30]: df_scaled[df_scaled.Clusters == 1]['Risk'].value_counts()\ .plot(kind='bar', figsize=(10, 6), title="Frequency of Risk Level");

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (290)

图 6-7. 第一个集群风险水平的频率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (291)

图 6-8. 第二个集群风险水平的频率

基于我们之前定义的集群,我们可以通过直方图分析风险水平的频率。图6-7 显示,在第一个集群中,风险水平的分布是不平衡的,而在 图6-8 中,好和坏风险水平的频率更加平衡,如果不是完全平衡的话。

此时,让我们退后一步,专注于一个完全不同的问题:类不平衡。在信用风险分析中,类不平衡问题并不少见。当一个类占主导地位时,就会出现类不平衡。例如,在我们的案例中,根据从第一个集群获得的数据,我们有 571 名信用记录良好的客户和 193 名信用记录不良的客户。可以清楚地看到,信用记录良好的客户占据了上风;这基本上就是我们所说的类不平衡。

处理此问题有多种方法:上采样、下采样、合成少数类过采样技术(SMOTE)和编辑最*邻规则(ENN)。为了利用混合方法,我们将结合 SMOTE 和 ENN,以清理类之间不必要的重叠观测,这将帮助我们检测到最佳的平衡比率,并进而提高预测性能(Tuong 等,2018)。将不平衡数据转换为平衡数据将是预测违约概率的第一步,但请注意,我们仅将此技术应用于从第一个聚类中获取的数据。

现在,我们接下来进行训练-测试拆分。为此,我们需要将分类变量Risk转换为离散变量。类别good取值为 1,bad取值为 0。在训练-测试拆分中,将 80%的数据用于训练样本,20%用于测试样本:

In [31]: from sklearn.model_selection import train_test_splitIn [32]: df_scaled['Risk'] = df_scaled['Risk'].replace({'good': 1, 'bad': 0}) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [33]: X = df_scaled.drop('Risk', axis=1) y = df_scaled.loc[:, ['Risk', 'Clusters']]In [34]: X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)In [35]: first_cluster_train = X_train[X_train.Clusters == 0].iloc[:, :-1] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) second_cluster_train = X_train[X_train.Clusters == 1].iloc[:, :-1] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (292)

变量的离散化

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (293)

根据第一个聚类创建数据,并从X_train中删除最后一列

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (294)

根据第二个聚类创建数据,并从X_train中删除最后一列

在这些准备工作完成后,我们准备继续运行逻辑回归以预测违约概率。我们将使用的库名为statsmodels,它允许有一个汇总表。以下结果基于第一个聚类数据。根据结果,agecredit amountjob变量与客户的信用价值呈正相关,而dependentduration变量之间存在负相关。这一发现表明,在 1%的显著性水平上,所有估计系数都显示出统计上显著的结果。一般的解释是,持续时间的下降和信用额度、年龄和工作的增加都意味着违约的高概率:

In [36]: import statsmodels.api as sm from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_auc_score, roc_curve from imblearn.combine import SMOTEENN ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) import warnings warnings.filterwarnings('ignore')In [37]: X_train1 = first_cluster_train y_train1 = y_train[y_train.Clusters == 0]['Risk'] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) smote = SMOTEENN(random_state = 2) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) X_train1, y_train1 = smote.fit_resample(X_train1, y_train1.ravel()) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) logit = sm.Logit(y_train1, X_train1) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) logit_fit1 = logit.fit() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) print(logit_fit1.summary()) Optimization terminated successfully. Current function value: 0.479511 Iterations 6 Logit Regression Results==============================================================================Dep. Variable: y No. Observations: 370Model: Logit Df Residuals: 366Method: MLE Df Model: 3Date: Wed, 01 Dec 2021 Pseudo R-squ.: 0.2989Time: 20:34:31 Log-Likelihood: -177.42converged: True LL-Null: -253.08Covariance Type: nonrobust LLR p-value: 1.372e-32================================================================================ coef std err z P>|z| [0.025 0.975]--------------------------------------------------------------------------------Age 1.3677 0.164 8.348 0.000 1.047 1.689Job 0.4393 0.153 2.873 0.004 0.140 0.739Credit amount 1.3290 0.305 4.358 0.000 0.731 1.927Duration -1.2709 0.246 -5.164 0.000 -1.753 -0.789================================================================================

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (295)

导入SMOTEENN以处理类别不平衡问题

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (296)

根据聚类 0 和风险水平创建y_train

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (297)

运行带有随机状态为 2 的SMOTEENN方法

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (298)

将不平衡数据转换为平衡数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (299)

配置逻辑回归模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (300)

运行逻辑回归模型

随后,通过基于聚类创建不同的数据集进行预测分析。为了测试的目的,以下分析是在测试数据上进行的,并导致图表 6-9 中的结果:

In [38]: first_cluster_test = X_test[X_test.Clusters == 0].iloc[:, :-1] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) second_cluster_test = X_test[X_test.Clusters == 1].iloc[:, :-1] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [39]: X_test1 = first_cluster_test y_test1 = y_test[y_test.Clusters == 0]['Risk'] pred_prob1 = logit_fit1.predict(X_test1) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [40]: false_pos, true_pos, _ = roc_curve(y_test1.values, pred_prob1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) auc = roc_auc_score(y_test1, pred_prob1) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) plt.plot(false_pos,true_pos, label="AUC for cluster 1={:.4f} " .format(auc)) plt.plot([0, 1], [0, 1], linestyle = '--', label='45 degree line') plt.legend(loc='best') plt.title('ROC-AUC Curve 1') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (301)

基于第 0 类别的聚类创建第一组测试数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (302)

基于第一类别的聚类创建第二组测试数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (303)

使用X_test1进行预测

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (304)

使用roc_curve函数获取假阳性和真阳性

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (305)

计算roc-auc分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (306)

图 6-9. 第一个聚类的 ROC-AUC 曲线

ROC-AUC 曲线是在存在不平衡数据时的一个方便工具。图6-9 中的 ROC-AUC 曲线表明模型的性能并不是很好,因为它仅略高于 45 度线。一般来说,根据测试结果,一个良好的 ROC-AUC 曲线应该接* 1,意味着有接*完美的分离。

继续使用从第二个聚类获得的第二组训练样本,jobdurationage的估计系数表现出正相关,表明job类型为1且持续时间较长的客户倾向于违约,而credit amount变量与因变量呈负相关。然而,所有估计系数在 95%置信区间下都不显著;因此,进一步解释这些发现是毫无意义的。

类似于我们对第一组测试数据所做的操作,我们创建第二组测试数据来运行预测以绘制 ROC-AUC 曲线,结果显示为图6-10:

In [41]: X_train2 = second_cluster_train y_train2 = y_train[y_train.Clusters == 1]['Risk'] logit = sm.Logit(y_train2, X_train2) logit_fit2 = logit.fit() print(logit_fit2.summary()) Optimization terminated successfully. Current function value: 0.688152 Iterations 4 Logit Regression Results==============================================================================Dep. Variable: Risk No. Observations: 199Model: Logit Df Residuals: 195Method: MLE Df Model: 3Date: Wed, 01 Dec 2021 Pseudo R-squ.: -0.0008478Time: 20:34:33 Log-Likelihood: -136.94converged: True LL-Null: -136.83Covariance Type: nonrobust LLR p-value: 1.000================================================================================ coef std err z P>|z| [0.025 0.975]--------------------------------------------------------------------------------Age 0.0281 0.146 0.192 0.848 -0.259 0.315Job 0.1536 0.151 1.020 0.308 -0.142 0.449Credit amount -0.1090 0.115 -0.945 0.345 -0.335 0.117Duration 0.1046 0.126 0.833 0.405 -0.142 0.351================================================================================In [42]: X_test2 = second_cluster_test y_test2 = y_test[y_test.Clusters == 1]['Risk'] pred_prob2 = logit_fit2.predict(X_test2)In [43]: false_pos, true_pos, _ = roc_curve(y_test2.values, pred_prob2) auc = roc_auc_score(y_test2, pred_prob2) plt.plot(false_pos,true_pos,label="AUC for cluster 2={:.4f} " .format(auc)) plt.plot([0, 1], [0, 1], linestyle = '--', label='45 degree line') plt.legend(loc='best') plt.title('ROC-AUC Curve 2') plt.show()

根据测试数据,图6-10 中显示的结果比先前的应用要差,这可以通过 0.4064 的 AUC 分数确认。考虑到这些数据,我们远不能说逻辑回归在使用德国信用风险数据集建模违约概率方面做得很好。

现在我们将使用不同的模型来看看逻辑回归在相对于其他方法建模此类问题中的表现如何。因此,在接下来的部分,我们将研究最大后验概率(MAP)贝叶斯估计和马尔可夫链蒙特卡洛(MCMC)方法。然后,我们将使用几个知名的 ML 模型——如 SVM、随机森林和使用MLPRegressor的神经网络——来探索这些方法,并将使用 TensorFlow 测试深度学*模型。这个应用将展示哪个模型在建模违约概率方面表现更好。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (307)

图 6-10. 第二个聚类的 ROC-AUC 曲线

使用贝叶斯模型进行违约概率估计

在这部分中,我们将使用 Python 包PYMC3进行贝叶斯估计,预测违约的概率。但是,使用PYMC3运行贝叶斯分析有几种方法,对于第一个应用程序,我们将使用第四章中讨论的 MAP 分布。作为一个快速提醒,鉴于代表性后验分布,MAP 在这种情况下成为一个有效的模型。此外,我们选择具有确定性变量(p)的贝叶斯模型,该变量完全由其父变量决定,即agejobcredit amountduration

让我们将贝叶斯分析的结果与逻辑回归进行比较:

In [44]: import pymc3 as pm ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) import arviz as az ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [45]: with pm.Model() as logistic_model1: ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) beta_age = pm.Normal('coeff_age', mu=0, sd=10) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) beta_job = pm.Normal('coeff_job', mu=0, sd=10) beta_credit = pm.Normal('coeff_credit_amount', mu=0, sd=10) beta_dur = pm.Normal('coeff_duration', mu=0, sd=10) p = pm.Deterministic('p', pm.math.sigmoid(beta_age * X_train1['Age'] + beta_job * X_train1['Job'] + beta_credit * X_train1['Credit amount'] + beta_dur * X_train1['Duration'])) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) with logistic_model1: observed = pm.Bernoulli("risk", p, observed=y_train1) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) map_estimate = pm.find_MAP() ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)Out[]: <IPython.core.display.HTML object>In [46]: param_list = ['coeff_age', 'coeff_job', 'coeff_credit_amount', 'coeff_duration'] params = {} for i in param_list: params[i] = [np.round(map_estimate[i], 6)] ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/8.png) bayesian_params = pd.DataFrame.from_dict(params) print('The result of Bayesian estimation:\n {}'.format(bayesian_params)) The result of Bayesian estimation: coeff_age coeff_job coeff_credit_amount coeff_duration 0 1.367247 0.439128 1.32721 -1.269345

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (308)

导入PYMC3

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (309)

导入arviz用于贝叶斯模型的探索性分析

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (310)

将贝叶斯模型标识为logistic_model1

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (311)

确定变量的假设分布为正态分布,具有定义的musigma参数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (312)

使用第一个样本运行确定性模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (313)

运行伯努利分布来建模因变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (314)

将 MAP 模型拟合到数据中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (315)

将所有估计系数的结果存储在params 中,保留六位小数

最引人注目的观察是估计系数之间的差异非常小,可以忽略不计。差异出现在小数部分。以信用金额变量的估计系数为例,我们在逻辑回归中估计系数为 1.3290,在贝叶斯分析中为 1.3272。

当比较基于第二簇数据的分析结果时,故事基本相同:

In [47]: with pm.Model() as logistic_model2: beta_age = pm.Normal('coeff_age', mu=0, sd=10) beta_job = pm.Normal('coeff_job', mu=0, sd=10) beta_credit = pm.Normal('coeff_credit_amount', mu=0, sd=10) beta_dur = pm.Normal('coeff_duration', mu=0, sd=10) p = pm.Deterministic('p', pm.math.sigmoid(beta_age * second_cluster_train['Age'] + beta_job * second_cluster_train['Job'] + beta_credit * second_cluster_train['Credit amount'] + beta_dur * second_cluster_train['Duration'])) with logistic_model2: observed = pm.Bernoulli("risk", p, observed=y_train[y_train.Clusters == 1] ['Risk']) map_estimate = pm.find_MAP()Out[]: <IPython.core.display.HTML object>In [48]: param_list = [ 'coeff_age', 'coeff_job', 'coeff_credit_amount', 'coeff_duration'] params = {} for i in param_list: params[i] = [np.round(map_estimate[i], 6)] bayesian_params = pd.DataFrame.from_dict(params) print('The result of Bayesian estimation:\n {}'.format(bayesian_params)) The result of Bayesian estimation: coeff_age coeff_job coeff_credit_amount coeff_duration 0 0.028069 0.153599 -0.109003 0.104581

最显著的差异出现在duration变量中。这个变量的估计系数在逻辑回归和贝叶斯估计中分别为 0.1046 和 0.1045。

我们不是寻找局部最大值,有时很难得到,而是基于抽样过程寻找*似期望。在贝叶斯设置中,这称为 MCMC。正如我们在第四章中讨论的,其中最著名的方法之一是 Metropolis-Hastings(M-H)算法。

基于 M-H 算法的贝叶斯估计的 Python 代码如下所示,并导致图6-11 的结果。因此,我们抽取 10,000 个后验样本,模拟两个独立的马尔可夫链的后验分布。代码中还提供了估计系数的摘要表:

In [49]: import logging ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) logger = logging.getLogger('pymc3') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) logger.setLevel(logging.ERROR) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [50]: with logistic_model1: step = pm.Metropolis() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) trace = pm.sample(10000, step=step,progressbar = False) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) az.plot_trace(trace) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png) plt.show()In [51]: with logistic_model1: display(az.summary(trace, round_to=6)[:4]) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/7.png)Out[]: mean sd hdi_3% hdi_97% mcse_mean \ coeff_age 1.392284 0.164607 1.086472 1.691713 0.003111 coeff_job 0.448694 0.155060 0.138471 0.719332 0.002925 coeff_credit_amount 1.345549 0.308100 0.779578 1.928159 0.008017 coeff_duration -1.290292 0.252505 -1.753565 -0.802707 0.006823 mcse_sd ess_bulk ess_tail r_hat coeff_age 0.002200 2787.022099 3536.314548 1.000542 coeff_job 0.002090 2818.973167 3038.790307 1.001246 coeff_credit_amount 0.005670 1476.746667 2289.532062 1.001746 coeff_duration 0.004826 1369.393339 2135.308468 1.001022

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (316)

导入logging包以抑制警告消息

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (317)

为日志记录命名包

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (318)

抑制错误而不引发异常

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (319)

启动 M-H 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (320)

使用 10,000 个样本运行模型并忽略进度条

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (321)

创建简单的后验概率图使用plot_trace

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (322)

打印摘要结果的前四行

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (323)

图 6-11. 使用 M—H 进行贝叶斯估计的第一个聚类

结果表明,预测性能应该非常接*逻辑回归的性能,因为这两个模型的估计系数非常相似。

在 Figure6-11 中,我们看到了虚线和实线。给定第一个聚类数据,位于 Figure6-11 左侧的图表显示了相关参数的样本值。虽然这不是我们目前的重点,但我们可以观察到最后一个图中的确定性变量p

类似地,基于第二个聚类的 M-H 贝叶斯估计结果与逻辑回归非常接*。然而,从 MAP 应用程序获得的结果更好,这主要是因为 M-H 使用随机抽样。然而,这种小偏差的另一个潜在原因我们将讨论。

至于我们从第二个聚类获得的数据,可以在下面的代码中看到 M-H 贝叶斯估计的结果,该代码还创建了 Figure6-12 中显示的图表:

In [52]: with logistic_model2: step = pm.Metropolis() trace = pm.sample(10000, step=step,progressbar = False) az.plot_trace(trace) plt.show()In [53]: with logistic_model2: display(az.summary(trace, round_to=6)[:4])Out[]: mean sd hdi_3% hdi_97% mcse_mean \ coeff_age 0.029953 0.151466 -0.262319 0.309050 0.002855 coeff_job 0.158140 0.153030 -0.125043 0.435734 0.003513 coeff_credit_amount -0.108844 0.116542 -0.328353 0.105858 0.003511 coeff_duration 0.103149 0.128264 -0.142609 0.339575 0.003720 mcse_sd ess_bulk ess_tail r_hat coeff_age 0.002019 2823.255277 3195.005913 1.000905 coeff_job 0.002485 1886.026245 2336.516309 1.000594 coeff_credit_amount 0.002483 1102.228318 1592.047959 1.002032 coeff_duration 0.002631 1188.042552 1900.179695 1.000988

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (324)

图 6-12. 使用 M—H 进行贝叶斯估计的第二个聚类

现在让我们讨论 M-H 模型的局限性,这可能会揭示模型结果之间的差异。M-H 算法的一个缺点是对步长的敏感性。小步骤阻碍了收敛过程。相反,大步骤可能导致高拒绝率。此外,M-H 可能会受到罕见事件的影响——由于这些事件的概率很低,需要大样本才能获得可靠的估计——这在本例中是我们关注的焦点。

现在,让我们考虑如果我们使用 SVM 预测违约概率会发生什么,并将其性能与逻辑回归进行比较。

使用支持向量机进行违约概率估计

SVM 被认为是一个参数化模型,并且在高维数据上表现良好。在多变量设置中的违约案例可能为运行 SVM 提供了丰富的数据。在继续之前,简要讨论我们将用来运行超参数调整的新方法是HalvingRandomSearchCV

HalvingRandomSearchCV使用迭代选择,以使用更少的资源,从而提高性能并节省时间。HalvingRandomSearchCV尝试使用连续减半来识别候选参数的最佳值。这个过程的逻辑如下:

  1. 评估所有参数组合,在第一次迭代中利用一定数量的训练样本。

  2. 在第二次迭代中,使用选定的一些参数和大量的训练样本。

  3. 直到最后一次迭代只包括得分最高的候选模型。

使用信用数据集,我们使用支持向量分类(SVC)预测违约的概率。同样,根据我们在本章最初部分执行的聚类,我们使用两个不同的数据集。结果如下所示:

In [54]: from sklearn.svm import SVC from sklearn.experimental import enable_halving_search_cv ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) from sklearn.model_selection import HalvingRandomSearchCV ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) import timeIn [55]: param_svc = {'gamma': [1e-6, 1e-2], 'C':[0.001,.09,1,5,10], 'kernel':('linear','rbf')}In [56]: svc = SVC(class_weight='balanced') halve_SVC = HalvingRandomSearchCV(svc, param_svc, scoring = 'roc_auc', n_jobs=-1) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) halve_SVC.fit(X_train1, y_train1) print('Best hyperparameters for first cluster in SVC {} with {}'. format(halve_SVC.best_score_, halve_SVC.best_params_)) Best hyperparameters for first cluster in SVC 0.8273860106443562 with {'kernel': 'rbf', 'gamma': 0.01, 'C': 1}In [57]: y_pred_SVC1 = halve_SVC.predict(X_test1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) print('The ROC AUC score of SVC for first cluster is {:.4f}'. format(roc_auc_score(y_test1, y_pred_SVC1))) The ROC AUC score of SVC for first cluster is 0.5179

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (325)

导入库以启用连续减半搜索。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (326)

导入库以运行减半搜索。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (327)

使用并行处理运行减半搜索。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (328)

运行预测分析。

SVM 中的一个重要步骤是超参数调整。使用减半搜索方法,我们试图找到最佳的kernelgammaC的组合。结果表明,两个不同样本之间唯一的区别在于gammaC超参数上。在第一个聚类中,最优的C得分为 1,而在第二个聚类中为 0.001。更高的C值表示我们应选择更小的边距以获得更好的分类效果。至于gamma超参数,两个聚类都采用相同的值。较低的gamma意味着支持向量对决策的影响更大。最佳的核函数是高斯核,两个聚类的gamma值均为 0.01。

AUC 性能指标表明,SVC 的预测性能略低于逻辑回归。具体来说,SVC 的 AUC 为 0.5179,这意味着 SVC 在第一个聚类中表现不如逻辑回归。

第二个聚类显示,SVC 的性能甚至比第一个聚类稍差,这表明 SVC 在这些数据上表现不佳,因为这些数据不太可能清晰地分离,这暗示 SVC 在低维空间中表现不佳。

In [58]: halve_SVC.fit(X_train2, y_train2) print('Best hyperparameters for second cluster in SVC {} with {}'. format(halve_SVC.best_score_, halve_SVC.best_params_)) Best hyperparameters for second cluster in SVC 0.5350758636788049 with {'kernel': 'rbf', 'gamma': 0.01, 'C': 0.001}In [59]: y_pred_SVC2 = halve_SVC.predict(X_test2) print('The ROC AUC score of SVC for first cluster is {:.4f}'. format(roc_auc_score(y_test2, y_pred_SVC2))) The ROC AUC score of SVC for first cluster is 0.5000

好吧,也许我们已经足够使用参数化方法了,让我们转向非参数化方法。现在,非参数化 这个词可能听起来令人困惑,但它只是一个具有无限参数的模型,随着观察数量的增加而变得更加复杂。随机森林是机器学*中最适用的非参数模型之一,我们接下来会讨论这个模型。

使用随机森林进行违约概率估计

随机森林分类器是我们可以用来建模违约概率的另一个模型。虽然随机森林在高维情况下失败,但我们的数据并不那么复杂,而随机森林的优势在于在大量样本存在时具有良好的预测性能,因此可以认为随机森林模型可能会胜过 SVC 模型。

使用减半搜索方法,我们试图找出最佳的 n_estimatorscriterionmax_featuresmax_depthmin_samples_split 组合。结果表明,对于第一个集群,我们使用 n_estimators 为 300,min_samples_split 为 10,max_depth 为 6,采用基尼系数的方法,以及 sqrt 作为 max_features。至于第二个集群,我们有两种不同的最优超参数,如下所示。在基于树的模型中,较大的深度意味着更复杂的模型。话虽如此,提议用于第二个集群的模型稍微复杂一些。max_features 超参数在样本间似乎有所不同;在第一个集群中,通过 number of features 选择了最大特征数。

鉴于第一个集群的数据,AUC 得分为 0.5387 表明随机森林相比其他模型有更好的性能:

In [60]: from sklearn.ensemble import RandomForestClassifierIn [61]: rfc = RandomForestClassifier(random_state=42)In [62]: param_rfc = {'n_estimators': [100, 300], 'criterion' :['gini', 'entropy'], 'max_features': ['auto', 'sqrt', 'log2'], 'max_depth' : [3, 4, 5, 6], 'min_samples_split':[5, 10]}In [63]: halve_RF = HalvingRandomSearchCV(rfc, param_rfc, scoring = 'roc_auc', n_jobs=-1) halve_RF.fit(X_train1, y_train1) print('Best hyperparameters for first cluster in RF {} with {}'. format(halve_RF.best_score_, halve_RF.best_params_)) Best hyperparameters for first cluster in RF 0.8890871444218126 with {'n_estimators': 300, 'min_samples_split': 10, 'max_features': 'sqrt', 'max_depth': 6, 'criterion': 'gini'}In [64]: y_pred_RF1 = halve_RF.predict(X_test1) print('The ROC AUC score of RF for first cluster is {:.4f}'. format(roc_auc_score(y_test1, y_pred_RF1))) The ROC AUC score of RF for first cluster is 0.5387

下面的代码展示了基于第二个集群的随机森林运行:

In [65]: halve_RF.fit(X_train2, y_train2) print('Best hyperparameters for second cluster in RF {} with {}'. format(halve_RF.best_score_, halve_RF.best_params_)) Best hyperparameters for second cluster in RF 0.6565 with {'n_estimators': 100, 'min_samples_split': 5, 'max_features': 'auto', 'max_depth': 5, 'criterion': 'entropy'}In [66]: y_pred_RF2 = halve_RF.predict(X_test2) print('The ROC AUC score of RF for first cluster is {:.4f}'. format(roc_auc_score(y_test2, y_pred_RF2))) The ROC AUC score of RF for first cluster is 0.5906

随机森林在第二个集群中具有更好的预测性能,AUC 得分为 0.5906。鉴于随机森林的预测性能,我们可以得出结论,随机森林更擅长拟合数据。这部分是由于数据的低维特性,因为当数据维度低且观察数目大时,随机森林证明是一个不错的选择。

使用神经网络进行违约概率估计

鉴于违约概率估计的复杂性,揭示数据的隐藏结构是一项艰巨的任务,但神经网络结构在处理此问题时表现出色,因此它是这类任务的理想候选模型。在设置神经网络模型时,使用 GridSearchCV 来优化隐藏层的数量、优化技术和学*率。

在运行模型时,我们首先使用MLP库,它允许我们控制许多参数,包括隐藏层大小、优化技术(求解器)和学*率。比较两个簇的优化超参数表明,唯一的区别在于隐藏层中神经元的数量。因此,在第一个簇中,第一个隐藏层中的神经元数量较大。但是,在第二个簇中,第二个隐藏层中的神经元数量较大。

以下代码表明基于第一簇数据的性能只有轻微改善。换句话说,AUC 变为 0.5263,仅比随机森林略差:

In [67]: from sklearn.neural_network import MLPClassifierIn [68]: param_NN = {"hidden_layer_sizes": [(100, 50), (50, 50), (10, 100)], "solver": ["lbfgs", "sgd", "adam"], "learning_rate_init": [0.001, 0.05]}In [69]: MLP = MLPClassifier(random_state=42)In [70]: param_halve_NN = HalvingRandomSearchCV(MLP, param_NN, scoring = 'roc_auc') param_halve_NN.fit(X_train1, y_train1) print('Best hyperparameters for first cluster in NN are {}'. format(param_halve_NN.best_params_)) Best hyperparameters for first cluster in NN are {'solver': 'lbfgs', 'learning_rate_init': 0.05, 'hidden_layer_sizes': (100, 50)}In [71]: y_pred_NN1 = param_halve_NN.predict(X_test1) print('The ROC AUC score of NN for first cluster is {:.4f}'. format(roc_auc_score(y_test1, y_pred_NN1))) The ROC AUC score of NN for first cluster is 0.5263

从第二簇中获得的 ROC-AUC 得分为 0.6155,具有 10 个和 100 个神经元的两个隐藏层。此外,最佳优化技术为adam,最佳初始学*率为 0.05。这是我们获得的最高 AUC 得分,表明神经网络能够捕捉复杂和非线性数据的动态,如下所示:

In [72]: param_halve_NN.fit(X_train2, y_train2) print('Best hyperparameters for first cluster in NN are {}'. format(param_halve_NN.best_params_)) Best hyperparameters for first cluster in NN are {'solver': 'lbfgs', 'learning_rate_init': 0.05, 'hidden_layer_sizes': (10, 100)}In [73]: y_pred_NN2 = param_halve_NN.predict(X_test2) print('The ROC AUC score of NN for first cluster is {:.4f}'. format(roc_auc_score(y_test2, y_pred_NN2))) The ROC AUC score of NN for first cluster is 0.6155

使用深度学*进行违约概率估计

现在让我们来看看使用 TensorFlow 通过KerasClassifier控制超参数的深度学*模型的性能。

我们在这个模型中调整的超参数是批量大小、epoch 和 dropout 率。由于违约概率是一个分类问题,Sigmoid 激活函数似乎是最优的函数选择。深度学*基于神经网络的结构,但提供了更复杂的结构,因此预计能更好地捕捉数据的动态,从而使我们具有更好的预测性能。

如下所示,第二个样本的预测性能出现了问题,然而,AUC 得分为 0.5628:

In [74]: from tensorflow import keras from tensorflow.keras.wrappers.scikit_learn import KerasClassifier ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) from tensorflow.keras.layers import Dense, Dropout from sklearn.model_selection import GridSearchCV import tensorflow as tf import logging ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) tf.get_logger().setLevel(logging.ERROR) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [75]: def DL_risk(dropout_rate,verbose=0): model = keras.Sequential() model.add(Dense(128,kernel_initializer='normal', activation = 'relu', input_dim=4)) model.add(Dense(64, kernel_initializer='normal', activation = 'relu')) model.add(Dense(8,kernel_initializer='normal', activation = 'relu')) model.add(Dropout(dropout_rate)) model.add(Dense(1, activation="sigmoid")) model.compile(loss='binary_crossentropy', optimizer='rmsprop') return model

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (329)

导入KerasClassifier以运行网格搜索

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (330)

导入logging以抑制警告消息

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (331)

为 TensorFlow 命名以记录日志

给定了 dropout、批量大小和 epoch 的优化超参数,深度学*模型在我们迄今使用的模型中产生了最佳性能,AUC 得分为 0.5614。MLPClassifier 和本章中使用的深度学*模型之间的区别在于隐藏层中的神经元数量。从技术上讲,这两个模型都是具有不同结构的深度学*模型。

In [76]: parameters = {'batch_size': [10, 50, 100], 'epochs': [50, 100, 150], 'dropout_rate':[0.2, 0.4]} model = KerasClassifier(build_fn = DL_risk) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) gs = GridSearchCV(estimator = model, param_grid = parameters, scoring = 'roc_auc') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [77]: gs.fit(X_train1, y_train1, verbose=0) print('Best hyperparameters for first cluster in DL are {}'. format(gs.best_params_)) Best hyperparameters for first cluster in DL are {'batch_size': 10, 'dropout_rate': 0.2, 'epochs': 50}

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (332)

调用一个名为DL_risk的预定义函数以优化超参数运行。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (333)

应用网格搜索

In [78]: model = KerasClassifier(build_fn = DL_risk, ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) dropout_rate = gs.best_params_['dropout_rate'], verbose = 0, batch_size = gs.best_params_['batch_size'], ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) epochs = gs.best_params_['epochs']) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) model.fit(X_train1, y_train1) DL_predict1 = model.predict(X_test1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) DL_ROC_AUC = roc_auc_score(y_test1, pd.DataFrame(DL_predict1.flatten())) print('DL_ROC_AUC is {:.4f}'.format(DL_ROC_AUC)) DL_ROC_AUC is 0.5628

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (334)

使用最优的 dropout 率超参数运行深度学*算法

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (335)

运行具有最佳批大小超参数的深度学*算法

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (336)

运行具有最佳超参数(epoch number)的深度学*算法

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (337)

在展平预测后计算 ROC-AUC 分数

In [79]: gs.fit(X_train2.values, y_train2.values, verbose=0) print('Best parameters for second cluster in DL are {}'. format(gs.best_params_)) Best parameters for second cluster in DL are {'batch_size': 10, 'dropout_rate': 0.2, 'epochs': 150}In [80]: model = KerasClassifier(build_fn = DL_risk, dropout_rate= gs.best_params_['dropout_rate'], verbose = 0, batch_size = gs.best_params_['batch_size'], epochs = gs.best_params_['epochs']) model.fit(X_train2, y_train2) DL_predict2 = model.predict(X_test2) DL_ROC_AUC = roc_auc_score(y_test2, DL_predict2.flatten()) print('DL_ROC_AUC is {:.4f}'.format(DL_ROC_AUC)) DL_ROC_AUC is 0.5614

这一发现证实了 DL 模型在金融建模中变得越来越受欢迎。然而,在工业界,由于网络结构的不透明性,建议将该方法与传统模型结合使用。

信用风险分析有着悠久的传统,但仍然是一项具有挑战性的任务。本章尝试提出一种全新的基于机器学*的方法来解决这个问题,并获得更好的预测性能。在本章的第一部分,提供了与信用风险相关的主要概念。然后,我们将一个众所周知的参数模型,即 logistic 回归,应用到了德国信用风险数据中。然后,比较了 logistic 回归与基于 MAP 和 M-H 的贝叶斯估计的性能。最后,应用了核心机器学*模型—即 SVC、随机森林和带有深度学*的 NNs,并比较了所有模型的性能。

在下一章中,将介绍一个被忽视的维度风险:流动性风险。自 2007-2008 年金融危机以来,人们对流动性风险的重视程度大幅增长,并已成为风险管理的重要组成部分。

本章引用的文章:

  • 银行监督管理委员会(Basel Committee on Banking Supervision)和国际清算银行(Bank for International Settlements)。2000 年。“信用风险管理原则。”国际清算银行。

  • Le, Tuong, Mi Young Lee, Jun Ryeol Park, 和 Sung Wook Baik. 2018. “破产预测的过采样技术:来自交易数据集的新特征。” 对称性 10 (4): 79。

  • Tibshirani, Robert, Guenther Walther, 和 Trevor Hastie. 2001. “通过间隙统计量估计数据集中的簇数。” 英国皇家统计学会杂志:B 系列(统计方法) 63 (2): 411-423。

本章引用的书籍和博士论文:

  • Rokach, Lior, 和 Oded Maimon. 2005. “聚类方法。” 在 数据挖掘与知识发现手册,321-352. 波士顿:斯普林格出版社。

  • Wehrspohn, Uwe. 2002. “信用风险评估:建模-分析-管理。”哈佛大学博士论文。

¹ 运行 logistic 回归以初始化贝叶斯估计中的先验结果是有用的。

当音乐停止时,就流动性而言,情况会变得复杂。但只要音乐还在播放,你就必须起身跳舞。我们仍在跳舞。

查克·普林斯(2007 年)

流动性是金融风险的另一个重要来源。然而,长期以来,流动性一直被忽视,金融行业为了建模风险而不考虑流动性付出了巨大代价。

流动性风险的成因是与完全市场和对称信息范式的偏离,这可能导致道德风险和逆向选择。在这种条件持续存在的情况下,流动性风险在金融系统中是普遍存在的,可以引发融资和市场流动性之间的恶性循环,促使系统性流动性风险(Nikolaou 2009)。

利用变量变化值与其对实际市场的影响之间的时间滞后是建模成功的标准之一。例如,利率在某种程度上与实际市场动态有所不同,而且需要一些时间来稳定下来。除此之外,不确定性是传统资产定价模型中唯一的风险来源;然而,这与现实相去甚远。为了填补金融模型与实际市场动态之间的差距,流动性维度显得尤为重要。具有流动性的模型可以更好地调整自身以适应金融市场的发展,因为流动性影响了资产的所需回报率,也影响了不确定性水平。因此,流动性在估计违约概率方面是非常重要的一个维度(Gaygisiz,Karasan 和 Hekimoglu 2021)。

自 2007 年至 2008 年全球抵押贷款危机爆发以来,流动性的重要性备受关注。在这次危机中,大多数金融机构都受到了流动性压力的严重打击,导致监管机构和中央银行采取了一些严格的措施。从那时起,关于包含流动性的必要性的讨论更加激烈,这起于可交易证券的缺乏。

流动性概念是多方面的。总的来说,流动性资产的定义取决于在不造成相当价格影响的情况下出售大量资产的程度。这也被称为交易成本。然而,这并不是流动性的唯一重要方面。在压力时期,弹性成为投资者寻求迅速价格发现的突出特征(Sarr 和 Lybek 2002)。这是 Kyle(1985 年)指出的:“流动性是一个难以捉摸和难以捉摸的概念,部分原因是因为它包含了市场的许多交易特性。”

话虽如此,流动性是一个模糊的概念,要定义它,我们需要关注其不同的维度。在文献中,不同的研究人员提出了不同的流动性维度,但为了本书的目的,我们将确定流动性的四个定义特征:

紧密度

在同一时间以相同价格交易资产的能力。这指的是交易中发生的交易成本。如果交易成本高,买入和卖出价格之间的差异将很大,反之亦然。因此,狭窄的交易成本定义了市场的紧密程度。

即时性

大量的买入或卖出订单可以进行交易的速度。流动性的这一维度为我们提供了有关金融市场的宝贵信息,低即时性指的是市场某些部分(如清算、结算等)的功能失效。

深度

大量的买家和卖家存在,他们能够以不同价格满足大量订单。

弹性

市场从非均衡状态恢复的速度。这可以被看作是一个价格恢复过程,其中订单不平衡迅速消失。

鉴于流动性的定义和相互联系,不难看出建模流动性是一项艰巨的任务。在文献中,提出了许多不同类型的流动性模型,然而,考虑到流动性的多维性,根据其捕捉的维度对数据进行聚类可能是明智的选择。为此,我们将提出不同的流动性指标来代表所有四个维度。这些流动性指标包括基于交易量的指标、基于交易成本的指标、基于价格影响的指标和市场影响指标。对于所有这些维度,将使用几种不同的流动性代理。

使用聚类分析,这些流动性指标将被聚类,这有助于我们理解投资者应该关注哪一部分流动性,因为已知在不同的时间段内经济中存在不同维度的流动性。因此,一旦我们完成了聚类分析,我们就会得到更少的流动性指标。为了进行聚类分析,我们将使用高斯混合模型(GMM)和高斯混合 Copula 模型(GMCM)来解决这个问题。GMM 是一个广泛认可的聚类模型,在椭圆分布下表现良好。GMCM 是 GMM 的扩展,我们在其中包括 Copula 分析来考虑相关性。我们将详细讨论这些模型,所以让我们从识别基于不同流动性维度的流动性指标开始。

流动性的作用最终被金融从业者和经济学家认可,这使得理解和发展流动性测量更加重要。现有文献集中于单一指标,很难概念化像流动性这样难以捉摸的概念。相反,我们将涵盖四个维度,以开发更全面的应用:

  • 交易量

  • 交易成本

  • 价格影响

  • 市场影响

让我们从基于交易量的流动性指标开始。

基于交易量的流动性指标

当市场具有深度时,大额订单得到满足,即深度的金融市场有能力满足大量订单。这反过来提供了关于市场的信息,如果市场缺乏深度,则市场中会出现订单不平衡和不连续性。鉴于市场的深度,基于交易量的流动性衡量标准可以用来区分流动性和非流动性资产。此外,基于交易量的流动性衡量标准与买卖价差密切相关:较大的买卖价差意味着低交易量,而较窄的买卖价差则意味着高交易量(Huong 和 Gregoriou,2020)。

正如你可以想象的那样,流动性变化的大部分来自交易活动。Blume、Easley 和 O'Hara (1994) 强调了基于交易量的方法的重要性,称交易量产生的信息是从其他统计数据中无法提取的。

为了正确表达流动性的深度维度,将引入以下基于交易量的衡量标准:

  • 流动性比率

  • Hui-Heubel 比率

  • 周转率

流动性比率

此比率衡量需要多少交易量才能引发价格变动 1%:

L R it = t=1 T P it V it t=1 T |PC it |

其中 P it 是第 t 天股票 i 的总价格,V it 表示第 t 天股票 i 的交易量,最后,| P C it | 是时间 tt - 1 之间价格的绝对差值。

比率 L R it 越高,资产 i 的流动性也越高。这意味着更高的交易量,P it V it ,以及低价格差异,| P C it | ,代表高流动性水平。反之,如果需要低交易量来引发价格变动,则称此资产为非流动性资产。显然,这一概念框架更侧重于价格方面,而非市场中通常存在的时间或执行成本问题(Gabrielsen、Marzo 和 Zagaglia,2011)。

首先导入数据并通过以下代码观察。正如可以轻易观察到的那样,数据集中的主要变量包括询价(ASKHI)、出价(BIDLO)、开盘价(OPENPRC)、交易价格(PRC),以及成交量(VOL)、收益(RET)、加权收益率(vwretx)和流通股份数量(SHROUT):

In [1]: import pandas as pd import numpy as np import matplotlib.pyplot as plt import warnings warnings.filterwarnings("ignore") plt.rcParams['figure.figsize'] = (10, 6) pd.set_option('use_inf_as_na', True)In [2]: liq_data = pd.read_csv('bid_ask.csv')In [3]: liq_data.head()Out[3]: Unnamed: 0 Date EXCHCD TICKER COMNAM BIDLO ASKHI PRC \ 0 1031570 2019-01-02 3.0 INTC INTEL CORP 45.77 47.470 47.08 1 1031571 2019-01-03 3.0 INTC INTEL CORP 44.39 46.280 44.49 2 1031572 2019-01-04 3.0 INTC INTEL CORP 45.54 47.570 47.22 3 1031573 2019-01-07 3.0 INTC INTEL CORP 46.75 47.995 47.44 4 1031574 2019-01-08 3.0 INTC INTEL CORP 46.78 48.030 47.74 VOL RET SHROUT OPENPRC vwretx 0 18761673.0 0.003196 4564000.0 45.960 0.001783 1 32254097.0 -0.055013 4564000.0 46.150 -0.021219 2 35419836.0 0.061362 4564000.0 45.835 0.033399 3 22724997.0 0.004659 4564000.0 47.100 0.009191 4 22721240.0 0.006324 4564000.0 47.800 0.010240

计算一些流动性指标需要进行滚动窗口估算,例如计算五天的出价价格。为了完成这项任务,使用以下代码生成名为rolling_five的列表:

In [4]: rolling_five = [] for j in liq_data.TICKER.unique(): for i in range(len(liq_data[liq_data.TICKER == j])): rolling_five.append(liq_data[i:i+5].agg({'BIDLO': 'min', 'ASKHI': 'max', 'VOL': 'sum', 'SHROUT': 'mean', 'PRC': 'mean'})) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [5]: rolling_five_df = pd.DataFrame(rolling_five) rolling_five_df.columns = ['bidlo_min', 'askhi_max', 'vol_sum', 'shrout_mean', 'prc_mean'] liq_vol_all = pd.concat([liq_data,rolling_five_df], axis=1)In [6]: liq_ratio = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): liq_ratio.append((liq_vol_all['PRC'][i+1:i+6] * liq_vol_all['VOL'][i+1:i+6]).sum()/ (np.abs(liq_vol_all['PRC'][i+1:i+6].mean() - liq_vol_all['PRC'][i:i+5].mean())))

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (338)

计算五天窗口所需的统计量

现在,我们有了最低出价、最高询价、成交量总和、流通股份数量的均值,以及每五天的交易价格均值。

汇-霍伯尔比率

另一个捕捉市场深度的指标是汇-霍伯尔流动性比率,也称为L HH

L HH = P max -P min P min / V / P ¯ × shrout

其中P maxP min分别显示在确定的周期内的最高和最低价格。P ¯是在确定的周期内的平均收盘价。在分子中的是股票价格的百分比变化,分母中的是成交量除以市值,即P ¯ × shrout。汇-霍伯尔流动性度量的最显著特征之一是它适用于单一股票,而不仅仅适用于投资组合。

如 Gabrielsen、Marzo 和 Zagaglia(2011)所讨论的,P maxP min可以由买卖价差取代,但由于买卖价差波动性低,它往往会向下偏倚。

要计算汇-霍伯尔流动性比率,首先在一个列表中拥有流动性指标,然后将所有这些指标添加到数据框中,以获得全面的数据:

In [7]: Lhh = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): Lhh.append((liq_vol_all['PRC'][i:i+5].max() - liq_vol_all['PRC'][i:i+5].min()) / liq_vol_all['PRC'][i:i+5].min() / (liq_vol_all['VOL'][i:i+5].sum() / liq_vol_all['SHROUT'][i:i+5].mean() * liq_vol_all['PRC'][i:i+5].mean()))

成交率

成交率长期以来一直被视为流动性的代理指标。它基本上是波动率与流通股份数量的比率:

L R it = 1 D it t=1 T Vol it t=1 T shrout it

其中 D it 表示交易日数,V o l it 是时间 t 的交易股数,shrout it 显示时间 t 的流通股数。较高的换手率表明流动性水平较高,因为换手率意味着交易频率。由于换手率包括流通股数,这使其成为更微妙的流动性指标。

成交量比率是根据日常数据计算的,然后所有基于交易量的流动性指标都转换为数据框并包含在liq_vol_all中:

In [8]: turnover_ratio = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): turnover_ratio.append((1/liq_vol_all['VOL'].count()) * (np.sum(liq_vol_all['VOL'][i:i+1]) / np.sum(liq_vol_all['SHROUT'][i:i+1])))In [9]: liq_vol_all['liq_ratio'] = pd.DataFrame(liq_ratio) liq_vol_all['Lhh'] = pd.DataFrame(Lhh) liq_vol_all['turnover_ratio'] = pd.DataFrame(turnover_ratio)

基于交易成本的流动性指标

在现实世界中,买家和卖家并不会在一个无摩擦的环境中神奇地相遇。相反,需要中介(经纪人和交易商)、设备(计算机等)、耐心(交易不能立即实现)以及规则书来规定如何处理订单并将其转化为交易。此外,大型机构投资者的订单足以影响市场价格。所有这些都意味着存在交易成本,而如何构建一个市场(以及更广泛的市场)以遏制这些成本是一个微妙而复杂的挑战(Baker and Kıymaz (2013))。这导致了交易成本的出现。

交易成本 是投资者在交易中必须承担的费用。它指与交易执行相关的任何费用。可以区分交易成本为显性和隐性成本。前者涉及订单处理、税费和经纪费,而后者包括更潜在的成本,如买卖价差、执行时机等。

交易成本与流动性的紧密度和即时性维度相关。高交易成本会阻止投资者交易,这反过来会减少市场上的买家和卖家数量,使交易场所从更为集中的市场向分散的市场演变,导致市场变得浅薄(Sarr and Lybek 2002)。在交易成本低的情况下,投资者愿意交易,这将导致一个繁荣的交易环境,市场将更加集中。

类似地,在低交易成本的环境中有大量的买家和卖家指的是大量订单在短时间内交易。因此,即时性是流动性的另一个维度,与交易成本密切相关。

尽管关于买卖价差的优良性及这些模型提供的保证仍在进行讨论,但买卖价差是广泛认可的交易成本代理。 在买卖价差作为交易成本分析的程度上,它也是资产转换为现金(或现金等价物)的流动性良好指标。 不详细讨论,买卖价差可以通过报价价差、有效价差和实现价差方法来衡量。 因此乍一看,似乎奇怪地计算买卖价差,可以通过这些方法轻松计算。 但这在现实中并非如此。 当交易无法在报价内实现时,这时的价差不再是这些方法基于的观察到的价差。

百分比报价和有效买卖价差

另外两个知名的买卖价差是百分比报价百分比有效买卖价差。 报价价差衡量完成交易的成本,即买卖价差的差异。 报价价差有不同的形式,但为了缩放起见,我们将选择百分比报价价差:

Percentage quoted spread = P ask -P bid P mid

其中 P ask 是股票的卖出价,P bid 是股票的买入价。

有效点差衡量了交易价格与中间价格之间的偏差,有时被称为股票的真实基础价值。当交易发生在报价内或外时,更好的交易成本衡量是基于实际交易价格计算的百分比有效半点差,其按百分比计算(Bessembinder 和 Venkataraman 2010):

Effective spread = 2|P t -P mid | P mid

其中 P t 是股票的交易价格,P mid 是交易时期的买卖盘中点。

计算百分比报价和有效买卖价差相对容易,如下所示:

In [10]: liq_vol_all['mid_price'] = (liq_vol_all.ASKHI + liq_vol_all.BIDLO) / 2 liq_vol_all['percent_quoted_ba'] = (liq_vol_all.ASKHI - liq_vol_all.BIDLO) / \ liq_vol_all.mid_price liq_vol_all['percent_effective_ba'] = 2 * abs((liq_vol_all.PRC - liq_vol_all.mid_price)) / \ liq_vol_all.mid_price

Roll 的点差估计

最初和最重要的传播措施之一是由 Roll(1984)提出的。Roll spread 可以定义为:

Roll = - cov ( Δ p t , Δ p t-1 )

其中 Δ p tΔ p t-1 是时间 t 和时间 t – 1 的价格差异,cov 表示这些价格差异之间的协方差。

假设市场是有效的¹,并且观察到的价格变化的概率分布是稳定的,Roll 的价差是由价格变化的串行相关性良好代理流动性的事实所激发。

计算 Roll 的价差中最重要的一点是,正协方差并不是定义良好的,并且几乎占了案例的一半。文献提出了几种方法来解决这个缺点,我们将在接下来采纳 Harris 的方法(1990 年):

In [11]: liq_vol_all['price_diff'] = liq_vol_all.groupby('TICKER')['PRC']\ .apply(lambda x:x.diff()) liq_vol_all.dropna(inplace=True) roll = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): roll_cov = np.cov(liq_vol_all['price_diff'][i:i+5], liq_vol_all['price_diff'][i+1:i+6]) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) if roll_cov[0,1] < 0: ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) roll.append(2 * np.sqrt(-roll_cov[0, 1])) else: roll.append(2 * np.sqrt(np.abs(roll_cov[0, 1]))) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (339)

计算五天窗口内价格差异的协方差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (340)

检查协方差为负的情况

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (341)

在正协方差的情况下,采用 Harris 的方法

Corwin-Schultz spread

Corwin-Schultz spread 相当直观且易于应用。它主要基于以下假设:每日的高低价通常由买方和卖方发起,观察到的价格变化可以分解为有效价格波动和买卖价差。因此,一天内高低价的比率反映了股票的方差和买卖价差(Corwin 和 Schultz 2012; Abdi 和 Ranaldo 2017)。

该价差提出了一种基于每日高低价的全新方法,其逻辑由 Corwin 和 Schultz (2012) 概述为“连续单日的价格范围之和反映了 2 天的波动性和两倍的价差,而一个 2 天期间的价格范围反映了 2 天的波动性和一个价差”:

S = 2(e α -1) 1+e αα = 2β-β 3-22 - γ 3-22β = 𝔼 ( j=0 1 [ln(H t+j 0 L t+j 0 )] 2 )γ = 𝔼 ( j=0 1 [ln(H t+1 0 L t+1 0 )] 2 )

其中 H t A ( L t A ) 表示第 t 天的实际高(低)价格,H t oL t o 表示第 t 天观察到的高(低)股票价格。

Corwin-Schultz 价差需要多个步骤来计算,因为它包括许多变量。以下代码展示了我们进行此计算的方式:

In [12]: gamma = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): gamma.append((max(liq_vol_all['ASKHI'].iloc[i+1], liq_vol_all['ASKHI'].iloc[i]) - min(liq_vol_all['BIDLO'].iloc[i+1], liq_vol_all['BIDLO'].iloc[i])) ** 2) gamma_array = np.array(gamma)In [13]: beta = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): beta.append((liq_vol_all['ASKHI'].iloc[i+1] - liq_vol_all['BIDLO'].iloc[i+1]) ** 2 + (liq_vol_all['ASKHI'].iloc[i] - liq_vol_all['BIDLO'].iloc[i]) ** 2) beta_array = np.array(beta)In [14]: alpha = ((np.sqrt(2 * beta_array) - np.sqrt(beta_array)) / (3 - (2 * np.sqrt(2)))) - np.sqrt(gamma_array / (3 - (2 * np.sqrt(2)))) CS_spread = (2 * np.exp(alpha - 1)) / (1 + np.exp(alpha))In [15]: liq_vol_all = liq_vol_all.reset_index() liq_vol_all['roll'] = pd.DataFrame(roll) liq_vol_all['CS_spread'] = pd.DataFrame(CS_spread)

基于价格影响的流动性度量标准

在本节中,我们将介绍基于价格影响的流动性度量标准,通过这些标准我们能够评估价格对成交量和周转率的敏感程度。回想一下,弹性是指市场对新订单的响应能力。如果市场对新订单敏感——即,新订单纠正了市场的不平衡——那么就说它具有弹性。因此,鉴于成交量和/或周转率的变化,高价格调整量意味着弹性,反之亦然。

我们有三种基于价格影响的流动性度量标准需要讨论:

  • Amihud 流动性测量

  • Florackis、Andros 和 Alexandros(2011 年)价格影响比率

  • 交易弹性系数(CET)

Amihud 流动性

这种流动性代理是一种著名且广为认可的衡量标准。Amihud 流动性(2002 年)基本上衡量了收益对交易量敏感性。更具体地说,它让我们了解到在交易量变化 1 美元时,绝对收益的变化情况。Amihud 流动性测量,或简称 ILLIQ,广为学术界和从业者所知:

ILLIQ = 1 D it d=1 D it |R itd | V itd

其中 R itd 表示月 td 天的股票收益,V itd 表示当月 t 的美元交易量,D 是月 t 的观察天数。

Amihud 测量法比许多其他流动性指标具有两个优势。首先,Amihud 测量法的构造简单,利用每日收益率与成交量比率的绝对值来捕捉价格影响。其次,该测量法与预期股票回报率有很强的正相关关系(Lou 和 Tao,2017 年)。

Amihud 流动性测量并不难计算。然而,在直接计算 Amihud 测量之前,需要计算股票的交易金额:

In [16]: dvol = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): dvol.append((liq_vol_all['PRC'][i:i+5] * liq_vol_all['VOL'][i:i+5]).sum()) liq_vol_all['dvol'] = pd.DataFrame(dvol)In [17]: amihud = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): amihud.append((1 / liq_vol_all['RET'].count()) * (np.sum(np.abs(liq_vol_all['RET'][i:i+1])) / np.sum(liq_vol_all['dvol'][i:i+1])))

价格影响比率

Florackis, Andros, 和 Alexandros (2011) 旨在改进 Amihud 流动性比率,并提出了一种新的流动性指标,即 Return-to-Turnover (RtoTR)。作者列举了 Amihud 流动性指标的缺点:

  • 它在不同市值的股票之间无法比较。

  • 它忽视了投资者的持有期限。

为了应对这些缺点,Florackis, Andros 和 Alexandros 提出了一个新的测量方法 RtoTR,用换手率取代了 Amihud 模型的成交量比率,以便新的测量方法能够捕捉交易频率:

RtoTR = 1 D it d=1 D it |R itd | TR itd

其中 T R itd 是股票id日的月份t的货币交易量,其余部分与 Amihud 流动性测量中相同。

这个测量方法与 Amihud 的测量一样容易计算,并且没有尺寸偏差,因为它包括捕捉交易频率的换手率。这也帮助我们检验价格和规模效应。

以下提供了价格影响比率的计算:

In [18]: florackis = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): florackis.append((1 / liq_vol_all['RET'].count()) * (np.sum(np.abs(liq_vol_all['RET'][i:i+1]) / liq_vol_all['turnover_ratio'][i:i+1])))

交易弹性系数

CET 是一种旨在弥补时间相关流动性指标(如每单位时间的交易次数和订单数)缺陷的流动性指标。这些指标被采用来评估市场即时性对流动性水平的影响程度。

市场即时性和 CET 是相辅相成的,因为它衡量了交易量的价格弹性,如果价格对交易量具有响应性(即弹性),这就意味着更高水平的市场即时性:

CET = %ΔV %ΔP

其中 % Δ V 指的是交易量变化,% Δ P 表示价格变化。

下面提供了 CET 公式的 Python 代码。作为此应用程序的第一部分,计算了成交量和价格的百分比差异。然后将所有基于价格影响的流动性指标存储在liq_vol_all数据框中:

In [19]: liq_vol_all['vol_diff_pct'] = liq_vol_all.groupby('TICKER')['VOL']\ .apply(lambda x: x.diff()).pct_change() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) liq_vol_all['price_diff_pct'] = liq_vol_all.groupby('TICKER')['PRC']\ .apply(lambda x: x.diff()).pct_change() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [20]: cet = [] for j in liq_vol_all.TICKER.unique(): for i in range(len(liq_vol_all[liq_vol_all.TICKER == j])): cet.append(np.sum(liq_vol_all['vol_diff_pct'][i:i+1])/ np.sum(liq_vol_all['price_diff_pct'][i:i+1]))In [21]: liq_vol_all['amihud'] = pd.DataFrame(amihud) liq_vol_all['florackis'] = pd.DataFrame(florackis) liq_vol_all['cet'] = pd.DataFrame(cet)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (342)

计算百分比成交量差异

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (343)

计算百分比价格差异

基于市场影响的流动性指标

在金融领域,识别信息来源至关重要,因为未知的信息来源可能会误导投资者,并导致意外后果。例如,市场上涨产生的价格波动并不能像个别股票上涨产生的那样提供相同的信息。因此,应该以一种正确捕捉价格变动的方式识别新的信息来源。

为完成此任务,我们使用资本资产定价模型(CAPM),通过该模型我们可以区分系统性风险和非系统性风险。CAPM 中的著名斜率系数表示系统性风险,去除市场风险后,非系统性风险归因于个别股票。

正如 Sarr 和 Lybek (2002) 所引用的,Hui-Heubel 采用了以下方法:

R i = α + β R m + u i

其中 R ii th 股票的每日收益率,u i 是特有的或非系统性风险。

一旦我们从方程中估计出残差,u i,它被回归到波动率,V i,并且 V i 的估计系数给出了相关股票的流动性水平,也称为特有风险:

u i 2 = γ 1 + γ 2 V i + e i

其中 u i 2 表示平方残差,V i 是每日成交量的百分比变化,e i 是残差项。

较大的 γ 2 意味着较大的价格波动,这让我们对股票的流动性有了直观的了解。相反,较小的 γ 2 导致较小的价格波动,表明流动性水平较高。在代码中,我们有:

In [22]: import statsmodels.api as smIn [23]: liq_vol_all['VOL_pct_change'] = liq_vol_all.groupby('TICKER')['VOL']\ .apply(lambda x: x.pct_change()) liq_vol_all.dropna(subset=['VOL_pct_change'], inplace=True) liq_vol_all = liq_vol_all.reset_index()In [24]: unsys_resid = [] for i in liq_vol_all.TICKER.unique(): X1 = liq_vol_all[liq_vol_all['TICKER'] == i]['vwretx'] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) y = liq_vol_all[liq_vol_all['TICKER'] == i]['RET'] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) ols = sm.OLS(y, X1).fit() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) unsys_resid.append(ols.resid) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (344)

将所有股票的成交量加权收益率作为自变量。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (345)

将所有股票的收益率作为因变量。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (346)

使用定义的变量运行线性回归模型。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (347)

将线性回归的残差存储为非系统性因子。

然后我们计算基于市场影响的流动性比率:

In [25]: market_impact = {} for i, j in zip(liq_vol_all.TICKER.unique(), range(len(liq_vol_all['TICKER'].unique()))): X2 = liq_vol_all[liq_vol_all['TICKER'] == i]['VOL_pct_change'] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) ols = sm.OLS(unsys_resid[j] ** 2, X2).fit() print('***' * 30) print(f'OLS Result for {i}') print(ols.summary()) market_impact[j] = ols.resid ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)********************************************************************************OLS Result for INTC OLS Regression Results================================================================================Dep. Variable: y R-squared (uncentered): 0.157Model: OLS Adj. R-squared (uncentered) 0.154Method: Least Squares F-statistic: 46.31Date: Thu, 02 Dec 2021 Prob (F-statistic): 7.53e-11Time: 15:33:38 Log-Likelihood: 1444.9No. Observations: 249 AIC: -2888.Df Residuals: 248 BIC: -2884.Df Model: 1Covariance Type: nonrobust================================================================================ coef std err t P>|t| [0.025 0.975]--------------------------------------------------------------------------------VOL_pct_change 0.0008 0.000 6.805 0.000 0.001 0.001==============================================================================Omnibus: 373.849 Durbin-Watson: 1.908Prob(Omnibus): 0.000 Jarque-Bera (JB): 53506.774Skew: 7.228 Prob(JB): 0.00Kurtosis: 73.344 Cond. No. 1.00==============================================================================Notes:[1] R² is computed without centering (uncentered) since the model does notcontain a constant.[2] Standard Errors assume that the covariance matrix of the errors iscorrectly specified.********************************************************************************OLS Result for MSFT OLS Regression Results================================================================================Dep. Variable: y R-squared (uncentered): 0.044Model: OLS Adj. R-squared (uncentered): 0.040Method: Least Squares F-statistic: 11.45Date: Thu, 02 Dec 2021 Prob (F-statistic): 0.000833Time: 15:33:38 Log-Likelihood: 1851.0No. Observations: 249 AIC: -3700.Df Residuals: 248 BIC: -3696.Df Model: 1Covariance Type: nonrobust================================================================================ coef std err t P>|t| [0.025 0.975]--------------------------------------------------------------------------------VOL_pct_change 9.641e-05 2.85e-05 3.383 0.001 4.03e-05 0.000==============================================================================Omnibus: 285.769 Durbin-Watson: 1.533Prob(Omnibus): 0.000 Jarque-Bera (JB): 11207.666Skew: 4.937 Prob(JB): 0.00Kurtosis: 34.349 Cond. No. 1.00==============================================================================Notes:[1] R² is computed without centering (uncentered) since the model does notcontain a constant.[2] Standard Errors assume that the covariance matrix of the errors iscorrectly specified.********************************************************************************OLS Result for IBM OLS Regression Results================================================================================Dep. Variable: y R-squared (uncentered): 0.134Model: OLS Adj. R-squared (uncentered): 0.130Method: Least Squares F-statistic: 38.36Date: Thu, 02 Dec 2021 Prob (F-statistic): 2.43e-09Time: 15:33:38 Log-Likelihood: 1547.1No. Observations: 249 AIC: -3092.Df Residuals: 248 BIC: -3089.Df Model: 1Covariance Type: nonrobust================================================================================ coef std err t P>|t| [0.025 0.975]--------------------------------------------------------------------------------VOL_pct_change 0.0005 7.43e-05 6.193 0.000 0.000 0.001==============================================================================Omnibus: 446.818 Durbin-Watson: 2.034Prob(Omnibus): 0.000 Jarque-Bera (JB): 156387.719Skew: 9.835 Prob(JB): 0.00Kurtosis: 124.188 Cond. No. 1.00==============================================================================Notes:[1] R² is computed without centering (uncentered) since the model does notcontain a constant.[2] Standard Errors assume that the covariance matrix of the errors iscorrectly specified.

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (348)

将所有股票成交量的百分比变化作为自变量。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (349)

市场影响是这个线性回归的残差。

然后,我们将市场影响纳入我们的数据框,并观察到目前为止我们引入的所有流动性度量的摘要统计数据:

In [26]: append1 = market_impact[0].append(market_impact[1]) liq_vol_all['market_impact'] = append1.append(market_impact[2]) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [27]: cols = ['vol_diff_pct', 'price_diff_pct', 'price_diff', 'VOL_pct_change', 'dvol', 'mid_price'] liq_measures_all = liq_vol_all.drop(liq_vol_all[cols], axis=1)\ .iloc[:, -11:] liq_measures_all.dropna(inplace=True) liq_measures_all.describe().TOut[27]: count mean std min \ liq_ratio 738.0 7.368514e+10 2.569030e+11 8.065402e+08 Lhh 738.0 3.340167e-05 5.371681e-05 3.966368e-06 turnover_ratio 738.0 6.491127e-03 2.842668e-03 1.916371e-03 percent_quoted_ba 738.0 1.565276e-02 7.562850e-03 3.779877e-03 percent_effective_ba 738.0 8.334177e-03 7.100304e-03 0.000000e+00 roll 738.0 8.190794e-01 6.066821e-01 7.615773e-02 CS_spread 738.0 3.305464e-01 1.267434e-01 1.773438e-40 amihud 738.0 2.777021e-15 2.319450e-15 0.000000e+00 florackis 738.0 2.284291e-03 1.546181e-03 0.000000e+00 cet 738.0 -1.113583e+00 3.333932e+01 -4.575246e+02 market_impact 738.0 8.614680e-05 5.087547e-04 -1.596135e-03 25% 50% 75% max liq_ratio 1.378496e+10 2.261858e+10 4.505784e+10 3.095986e+12 Lhh 1.694354e-05 2.368095e-05 3.558960e-05 5.824148e-04 turnover_ratio 4.897990e-03 5.764112e-03 7.423111e-03 2.542853e-02 percent_quoted_ba 1.041887e-02 1.379992e-02 1.878123e-02 5.545110e-02 percent_effective_ba 3.032785e-03 6.851479e-03 1.152485e-02 4.656669e-02 roll 4.574986e-01 6.975982e-01 1.011879e+00 4.178873e+00 CS_spread 2.444225e-01 3.609800e-01 4.188028e-01 5.877726e-01 amihud 1.117729e-15 2.220439e-15 3.766086e-15 1.320828e-14 florackis 1.059446e-03 2.013517e-03 3.324181e-03 7.869841e-03 cet -1.687807e-01 5.654237e-01 1.660166e+00 1.845917e+02 market_impact -3.010645e-05 3.383862e-05 1.309451e-04 8.165527e-03

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (350)

将市场影响附加到 liq_vol_all 数据框中。

这些是我们在通过 GMM 建模流动性过程中利用的流动性度量。现在让我们通过概率无监督学*算法来讨论这一点。

如果我们的数据具有几个模式,代表数据的不同方面会发生什么?或者让我们将其放在流动性指标的背景下,你如何用不同的均值方差来建模流动性指标?正如你可以想象的那样,由流动性指标组成的数据是多模式的,意味着存在几个不同的高概率集群,我们的任务是找出哪种模型最适合这类数据。

显然,提议的模型应包括多个组件的混合,并且在不知道具体流动性度量的情况下,应基于从这些度量中获得的值进行聚类。总结一下,我们将有一个包含所有流动性度量的大数据集,假设我们暂时忘记为这些度量分配标签,我们需要一个能够呈现这些度量不同分布的模型,而不知道标签的情况下。这个模型就是 GMM,它使我们能够在不知道变量名称的情况下建模多模态数据。

考虑到之前介绍的不同流动性指标的不同重点,如果我们设法对这些数据进行建模,那意味着我们可以在不同时间捕捉到不同的流动性水平。例如,高波动率期间的流动性无法以与低波动率期间相同的方式建模。在相似的情况下,考虑到市场的深度,我们需要关注这些不同流动性的方面。GMM 为我们提供了解决这个问题的工具。

简而言之,如果市场经历繁荣期,这与高波动性重合,基于成交量和交易成本的度量将是不错的选择;如果市场出现价格不连续,基于价格的度量将是最佳选择。当然,我们不是在谈论一刀切的度量方法——有些情况下,混合度量可能效果更好。

正如 VanderPlas(2016)所述,对于 K-means 要成功,聚类模型必须具有圆形特征。然而,许多金融变量展示出非圆形形状,这使得通过 K-means 进行建模变得困难。正如可以清楚地观察到的那样,流动性指标重叠并且没有圆形形状,因此具有其概率特性的 GMM 将是建模这类数据的良好选择,正如 Fraley 和 Raftery(1998)所述:

混合模型方法在聚类中的一个优势是它允许使用*似贝叶斯因子来比较模型。这提供了一个系统的方式,不仅可以选择模型的参数化(因而是聚类方法),还可以选择聚类数目。

在这部分中,我们希望导入 GMM 中所需的库。此外,我们还进行了缩放处理,这是聚类中的一个重要步骤,因为数据框中存在混合数值。在接下来的代码的最后部分中,我们绘制了直方图以观察数据中的多模态性(参见图7-1)。这是我们在本节开头讨论的现象。

In [28]: from sklearn.mixture import GaussianMixture from sklearn.preprocessing import StandardScalerIn [29]: liq_measures_all2 = liq_measures_all.dropna() scaled_liq = StandardScaler().fit_transform(liq_measures_all2)In [30]: kwargs = dict(alpha=0.5, bins=50, stacked=True) plt.hist(liq_measures_all.loc[:, 'percent_quoted_ba'], **kwargs, label='TC-based') plt.hist(liq_measures_all.loc[:, 'turnover_ratio'], **kwargs, label='Volume-based') plt.hist(liq_measures_all.loc[:, 'market_impact'], **kwargs, label='Market-based') plt.title('Multi Modality of the Liquidity Measures') plt.legend() plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (351)

图7-1. 流动性指标的多模态性

现在,考虑到交易成本、交易量和市场流动性指标,我们可以在图7-1 中轻松观察到多峰性(即三个峰值)。由于尺度问题,基于价格影响的流动性维度未包含在直方图中。

现在,让我们运行 GMM 模型,看看如何对流动性指标进行聚类。但首先,一个常见的问题是:我们应该有多少个簇?为了解决这个问题,我们将再次使用 BIC,并生成如图7-2 所示的图表:

In [31]: n_components = np.arange(1, 10) clusters = [GaussianMixture(n, covariance_type='spherical', random_state=0).fit(scaled_liq) for n in n_components] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.plot(n_components, [m.bic(scaled_liq) for m in clusters]) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) plt.title('Optimum Number of Components') plt.xlabel('n_components') plt.ylabel('BIC values') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (352)

根据不同的簇数量生成不同的 BIC 值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (353)

绘制给定组件数量的 BIC 值线图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (354)

图7-2. 最佳组件数量

图7-2 告诉我们,在第三个簇之后,曲线似乎趋于平缓,这是停止的理想点。

使用以下代码,我们能够检测数据最佳表示状态。术语state代表的是具有最高后验概率的簇。这意味着这个特定的状态最能解释数据的动态。在这种情况下,概率为 0.55 的 State-3 是最有可能解释数据动态的状态:

In [32]: def cluster_state(data, nstates): gmm = GaussianMixture(n_components=nstates, covariance_type='spherical', init_params='kmeans') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) gmm_fit = gmm.fit(scaled_liq) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) labels = gmm_fit.predict(scaled_liq) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) state_probs = gmm.predict_proba(scaled_liq) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) state_probs_df = pd.DataFrame(state_probs, columns=['state-1','state-2', 'state-3']) state_prob_means = [state_probs_df.iloc[:, i].mean() for i in range(len(state_probs_df.columns))] ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) if np.max(state_prob_means) == state_prob_means[0]: print('State-1 is likely to occur with a probability of {:4f}' .format(state_prob_means[0])) elif np.max(state_prob_means) == state_prob_means[1]: print('State-2 is likely to occur with a probability of {:4f}' .format(state_prob_means[1])) else: print('State-3 is likely to occur with a probability of {:4f}' .format(state_prob_means[2])) return state_probsIn [33]: state_probs = cluster_state(scaled_liq, 3) print(f'State probabilities are {state_probs.mean(axis=0)}') State-3 is likely to occur with a probability of 0.550297 State probabilities are [0.06285593 0.38684657 0.5502975 ]

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (355)

配置 GMM 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (356)

使用缩放后的数据拟合 GMM 模型

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (357)

运行预测

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (358)

获取状态概率

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (359)

计算三个状态概率的平均值

好了,将 GMM 应用于聚类流动性指标并提取代表其一维数据的可能状态,这样做是合理的吗?因为在数据末尾,我们最终得到了只有一个具有最高概率的簇。但是,如果我们应用 PCA 来完全理解哪些变量与当前状态相关联,您会怎么想呢?在 PCA 中,我们能够通过载荷建立组件和特征之间的桥梁,以便分析哪些流动性指标具有特定时期的定义特征。

首先,让我们应用 PCA 并创建一个 scree 图(图7-3),以确定我们正在处理的组件数量:

In [34]: from sklearn.decomposition import PCAIn [35]: pca = PCA(n_components=11) components = pca.fit_transform(scaled_liq) plt.plot(pca.explained_variance_ratio_) plt.title('Scree Plot') plt.xlabel('Number of Components') plt.ylabel('% of Explained Variance') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (360)

图 7-3. Scree plot

基于图 7-3,我们将决定停留在第 3 个成分。

现在我们确定了组分的数量,让我们使用三个组分和 GMM 重新运行 PCA。与之前的 GMM 应用类似,后验概率被计算并分配给一个名为state_probs的变量:

In [36]: def gmm_pca(data, nstate): pca = PCA(n_components=3) components = pca.fit_transform(data) mxtd = GaussianMixture(n_components=nstate, covariance_type='spherical') gmm = mxtd.fit(components) labels = gmm.predict(components) state_probs = gmm.predict_proba(components) return state_probs,pcaIn [37]: state_probs, pca = gmm_pca(scaled_liq, 3) print(f'State probabilities are {state_probs.mean(axis=0)}') State probabilities are [0.7329713 0.25076855 0.01626015]

接下来,我们找出概率最高的状态,结果显示是 State-1,概率为 73%:

In [38]: def wpc(): state_probs_df = pd.DataFrame(state_probs, columns=['state-1', 'state-2', 'state-3']) state_prob_means = [state_probs_df.iloc[:, i].mean() for i in range(len(state_probs_df.columns))] if np.max(state_prob_means) == state_prob_means[0]: print('State-1 is likely to occur with a probability of {:4f}' .format(state_prob_means[0])) elif np.max(state_prob_means) == state_prob_means[1]: print('State-2 is likely to occur with a probability of {:4f}' .format(state_prob_means[1])) else: print('State-3 is likely to occur with a probability of {:4f}' .format(state_prob_means[2])) wpc() State-1 is likely to occur with a probability of 0.732971

现在让我们关注找出哪些流动性度量最重要的加载分析。这种分析表明turnover_ratiopercent_quoted_bapercent_effective_baamihudflorackis比率是组成 State-1 的流动性比率。以下代码显示了结果:

In [39]: loadings = pca.components_.T * np.sqrt(pca.explained_variance_) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) loading_matrix = pd.DataFrame(loadings, columns=['PC1', 'PC2', 'PC3'], index=liq_measures_all.columns) loading_matrixOut[39]: PC1 PC2 PC3 liq_ratio 0.116701 -0.115791 -0.196355 Lhh -0.211827 0.882007 -0.125890 turnover_ratio 0.601041 -0.006381 0.016222 percent_quoted_ba 0.713239 0.140103 0.551385 percent_effective_ba 0.641527 0.154973 0.526933 roll -0.070192 0.886080 -0.093126 CS_spread 0.013373 -0.299229 -0.092705 amihud 0.849614 -0.020623 -0.488324 florackis 0.710818 0.081948 -0.589693 cet -0.035736 0.101575 0.001595 market_impact 0.357031 0.095045 0.235266

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (361)

计算 PCA 的载荷

鉴于金融市场的复杂性和精密性,不可能提出一种适合所有情况的风险模型。因此,金融机构开发了自己的信用、流动性、市场和操作风险模型,以便更有效和现实地管理面临的风险。然而,这些金融机构面临的最大挑战之一是风险的相关性,也被称为风险的联合分布,正如 Rachev 和 Stein(2009)所述:

随着次贷危机的出现和随后的信贷紧缩,学者、从业者、哲学家和记者开始寻找导致动荡和(几乎)前所未有的市场恶化的原因和失败... 对华尔街及全球各地使用的几种方法和模型提出的批评,在许多情况下都是把它们置于错误的光线之下。除了风险和问题被证券化、分级和包装了信贷市场底层资产以及评级机构不幸和有些误导性的角色所蒙蔽之外,数学模型被用于市场,而这些模型现在由于它们在极端市场阶段无法捕捉风险而受到质疑。

建模“极端市场阶段”的任务引导我们到联合分布的概念,通过它可以用单一分布来模拟多个风险。

忽略风险相互作用的模型注定会失败。在这方面,提出了一个直观但简单的方法:copulas

Copula 是一个函数,将个体风险的边际分布映射到多变量分布,从而产生许多标准均匀随机变量的联合分布。如果我们使用已知分布,比如正态分布,可以很容易地建模变量的联合分布,称为双变量正态分布。然而,挑战在于定义这两个变量之间的相关结构,而这正是 copulas 的应用之处(Hull 2012)。

使用 Sklar 定理,让F成为X i的边际连续累积分布函数(CDF)。 CDF 变换将随机变量映射到在[0,1]中均匀分布的标量。然而,所有这些边际 CDF 的联合分布不遵循均匀分布,而是一个 Copula 函数(Kasa and Rajan 2020):

C : [0,1] i [ 0 , 1 ]

其中i表示边际 CDF 的数量。换句话说,在双变量情况下,i取值为 2,函数变为:

C : [0,1] 2 [ 0 , 1 ]

因此:

F ( x 1 , x 2 ) C ( F 1 ( x 1 ) , . . . , F i ( x i ) )

其中C是联合分布的边际分布给定的唯一 Copula,且F i连续。

或者,Copula 函数可以由单独的边际密度描述:

f ( x ) = C ( F 1 ( x 1 ) , . . . , F i ( x i ) ) j=1 i f j ( x j )

其中f ( x )表示多变量密度,f j是第j th个资产的边际密度。

我们不能完成我们的讨论而不说明我们需要满足的 Copula 的假设。这是 Bouye(2000)的假设:

  1. C = S 1 × S 2 ,其中S 1S 2是[0,1]的非空子集。

  2. C是一个增函数,使得0 u 1 u 2 10 v 1 v 2 1

    C ( [ u 1 , v 1 ] × [ u 2 , v 2 ] ) C ( u 2 , v 2 ) - C ( u 2 , v 2 ) - C ( u 1 , v 2 ) + C ( u 1 , u 1 ) 0

  3. 对于S 1中的每个uS 2中的每个vC(u, 1) = uC(1, v) = v

在对 Copula 进行了长时间的理论讨论之后,您可能会想到在 Python 中编写其代码的复杂性。别担心,我们有一个库可以做到这一点,而且很容易应用。Python 中用于 Copula 的库的名称称为 Copulae,在接下来的工作中我们将使用它:

In [40]: from copulae.mixtures.gmc.gmc import GaussianMixtureCopula ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [41]: _, dim = scaled_liq.shape gmcm = GaussianMixtureCopula(n_clusters=3, ndim=dim) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [42]: gmcm_fit = gmcm.fit(scaled_liq,method='kmeans', criteria='GMCM', eps=0.0001) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) state_prob = gmcm_fit.params.prob print(f'The state {np.argmax(state_prob) + 1} is likely to occur') print(f'State probabilities based on GMCM are {state_prob}') The state 2 is likely to occur State probabilities based on GMCM are [0.3197832 0.34146341 0. 33875339]

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (362)

copulae导入GaussianMixtureCopula

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (363)

配置 GMCM 与簇和维数的数量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (364)

拟合 GMCM

结果表明,当考虑相关性时,State-2 占优势,但后验概率非常接*,这意味着当流动性指标之间的相关性考虑进来时,流动性的共同性突出。

*十多年来,流动性风险一直处于微观视野中,因为它本身是重要的风险来源,并且与其他金融风险具有很高的相关性。

本章介绍了一种基于 GMM 的新型流动性建模方法,该方法允许我们对多变量数据进行建模并生成聚类。鉴于这些聚类的后验概率,我们能够确定哪个聚类代表了数据的定义特征。然而,如果不考虑流动性指标的相关结构,我们的模型可能不是对现实的良好表示。因此,为了解决这个问题,我们引入了 GMCM,并通过考虑变量间的相关结构重新定义了定义性聚类。

在完成流动性建模之后,我们现在准备讨论另一个重要的金融风险来源:运营风险。运营风险可能由于各种原因而产生,但我们将通过欺诈活动来讨论运营风险。

本章引用的文章:

  • Abdi, Farshid, and Angelo Ranaldo. 2017. “A Simple Estimation of Bid-Ask Spreads from Daily Close, High, and Low Prices.” The Review of Financial Studies 30 (12): 4437-4480.

  • Baker, H. Kent, and Halil Kiymaz, eds. 2013. Market Microstructure in Emerging and Developed Markets: Price Discovery, Information Flows, and Transaction Costs. Hoboken, New Jersey: John Wiley and Sons.

  • Bessembinder, Hendrik, and Kumar Venkataraman. 2010. “Bid–Ask Spreads.” in Encyclopedia of Quantitative Finance, edited b. Rama Cont. Hoboken, NJ: John Wiley and Sons.

  • Blume, Lawrence, David Easley, and Maureen O’Hara. 1994 “Market Statistics and Technical Analysis: The Role of Volume.” The Journal of Finance 49 (1): 153-181.

  • Bouyé, Eric, Valdo Durrleman, Ashkan Nikeghbali, Gaël Riboulet, and Thierry Roncalli. 2000. “Copulas for Finance: A Reading Guide and Some Applications.” Available at SSRN 1032533.

  • Chuck, Prince. 2007. “Citigroup Chief Stays Bullish on Buy-Outs.” Financial Times. https://oreil.ly/nKOZk.

  • Corwin, Shane A., and Paul Schultz. 2012. “A Simple Way to Estimate Bid‐Ask Spreads from Daily High and Low Prices.” The Journal of Finance 67 (2): 719-760.

  • Florackis, Chris, Andros Gregoriou, and Alexandros Kostakis. 2011. “Trading Frequency and Asset Pricing on the London Stock Exchange: Evidence from a New Price Impact Ratio.” Journal of Banking and Finance 35 (12): 3335-3350.

  • Fraley, Chris, and Adrian E. Raftery. 1998. “How Many Clusters? Which Clustering Method? Answers via Model-Based Cluster Analysis.” The Computer Journal 41 (8): 578-588.

  • Gabrielsen, Alexandros, Massimiliano Marzo, 和 Paolo Zagaglia. 2011. “Measuring Market Liquidity: An Introductory Survey.” SRN 电子期刊

  • Harris, Lawrence. 1990. “Statistical Properties of the Roll Serial Covariance Bid/Ask Spread Estimator.” 金融学杂志 45 (2): 579-590。

  • Gaygisiz, Esma, Abdullah Karasan, 和 Alper Hekimoglu. 2021. “Analyses of factors of Market Microstructure: Price impact, liquidity, and Volatility.” 优化(即将发表)。

  • Kasa, Siva Rajesh, 和 Vaibhav Rajan. 2020. “Improved Inference of Gaussian Mixture Copula Model for Clustering and Reproducibility Analysis using Automatic Differentiation.” arXiv 预印本 arXiv:2010.14359。

  • Kyle, Albert S. 1985. “Continuous Auctions and Insider Trading.” 计量经济学 53 (6): 1315-1335。

  • Le, Huong, 和 Andros Gregoriou. 2020. “How Do You Capture Liquidity? A Review of the Literature on Low‐Frequency Stock Liquidity.” 经济调查杂志 34 (5): 1170-1186。

  • Lou, Xiaoxia, 和 Tao Shu. 2017. “Price Impact or Trading Volume: Why Is the Amihud (2002) measure Priced?.” 金融研究评论 30 (12): 4481-4520。

  • Nikolaou, Kleopatra. 2009. “Liquidity (Risk) concepts: Definitions and Interactions.” 欧洲央行工作论文系列 1008。

  • Rachev, S. T., W. Sun, 和 M. stein. 2009. “Copula Concepts in Financial Markets.” Institutionell 投资组合 (4): 12-15。

  • Roll, Richard. 1984. “A Simple Implicit Measure of the Effective Bid‐Ask Spread in an Efficient Market.” 金融学杂志 29 (4): 1127-1139。

  • Sarr, Abdourahmane, 和 Tonny Lybek. 2002. “Measuring liquidity in financial markets.” IMF Working Papers (02/232): 1-64。

本章引用的书籍和在线资料:

  • Hull, John. 2012. 风险管理和金融机构。新泽西州霍博肯:约翰·威利和儿子。

  • VanderPlas, Jake. 2016. Python 数据科学手册:处理数据的基本工具。塞巴斯托波尔:奥莱利。

¹ 有效市场 指当前价格多么快速地反映了有关基础资产价值的所有可用信息。

...并不一定是最大的错误行为造成最严重的打击;即使是最小的事件也可能导致股价暴跌。

Dunnett, Levy, and Simoes (2005)

到目前为止,我们已经讨论了三种主要的财务风险:市场风险、信用风险和流动性风险。现在是时候讨论操作风险了,这种风险比其他类型的财务风险更加模糊不清。这种模糊性源于金融机构可能面临的风险来源种类繁多,从而可能导致巨大的损失。

操作风险是由于内部流程、人员和系统的不足或失败,或外部事件导致的直接或间接损失的风险(BIS 2019)。请注意,损失可以是直接的和/或间接的。一些直接损失包括:

  • 来自司法程序的法律责任

  • 由于盗窃或资产减少而导致的减值

  • 源自税收、许可、罚款等的合规性。

  • 业务中断

间接成本与机会成本相关,机构的决策可能引发一系列事件,导致未来某个不确定时间的损失。

通常,金融机构会分配一定金额来覆盖源自操作风险的损失,即所谓的意外损失。然而,分配适当的资金以覆盖意外损失并非易事。必须确定正确的意外损失金额;否则,要么投入更多资金,导致资金闲置并产生机会成本,要么分配少于所需资金,导致流动性问题。

正如我们之前简要提到的,操作风险可以采取多种形式。其中,我们将重点放在欺诈风险上,这被认为是最普遍和最具破坏性的操作风险类型之一。

如果损失是由金融机构内部引起的,则欺诈可能通常被描述为一种故意行为、误述或遗漏,旨在欺骗他人,导致受害者遭受损失或施行者获得利益(OCC 2019)。如果是由第三方实施的,则可以称为外部欺诈。

为什么欺诈成为金融机构的主要关注点?什么会增加欺诈行为的可能性?为了回答这些问题,我们可以参考三个重要因素:

  • 全球化

  • 缺乏适当的风险管理

  • 经济压力

全球化导致金融机构在全球范围内扩展其业务,这带来了复杂性,增加了腐败、贿赂和任何违法行为的可能性,因为金融机构开始在他们没有先前知识的环境中运营。

缺乏适当的风险管理一直以来都是欺诈的最明显原因。误导性报告和流氓、未经授权的交易为欺诈行为埋下了种子。巴林银行案例是一个非常著名的例子,尼克·利森(Barings)在此案中,一名年轻的巴林银行交易员运行了一个投机交易和随后的掩盖操作,使用会计把戏导致巴林银行损失了高达 13 亿美元的巨额财富。因此,当缺乏明确定义的风险管理政策以及良好建立的风险文化时,员工可能倾向于实施欺诈行为。

另一个导致欺诈行为的动机可能是员工经济状况恶化。特别是在经济衰退期间,员工可能会被诱使从事欺诈活动。此外,金融机构本身可能会采用非法操作(如会计把戏)来摆脱经济衰退的困境。

欺诈不仅会造成巨额损失,而且还会对公司的声誉构成威胁,这可能会进一步影响公司的长期可持续性。以安然公司为例,这是一个典型的会计欺诈案例,于 2001 年爆发。安然公司成立于 1985 年,曾经是美国和世界上最大的公司之一。让我简要告诉你这场大崩溃的故事。

由于安然公司在能源市场面临的压力,高管们被迫依赖可疑的会计实践,导致通过写入巨额未实现未来收益的方式产生的虚高利润。多亏了举报人雪伦·沃特金斯(Sherron Watkins),她曾是前企业发展副总裁,现代金融史上最大的欺诈案例之一得以曝光。此事件也强调了预防欺诈活动的重要性,否则可能导致个人或公司声誉或财务崩溃造成巨大损失。

在本章中,我们旨在介绍一种基于机器学*的模型来检测欺诈或可能的欺诈操作。这是一个应不断发展的领域,以赶在作恶者之前。与欺诈相关的数据集可以分为标记未标记数据。为了同时考虑两者,我们首先应用监督学*算法,然后使用无监督学*算法,假装我们没有标签,尽管我们将使用的数据集确实包含标签。

我们用于欺诈分析的数据集称为 Brandon Harris 创建的信用卡交易欺诈检测数据集。信用卡欺诈并不罕见,目标是检测欺诈可能性并通知银行,以便银行可以进行尽职调查。这是银行保护自身免受巨额损失的方式。根据尼尔森报告(2020 年),支付卡欺诈损失达到创纪录的 320.4 亿美元,相当于每 100 美元总交易量的 6.8 美分。

这个数据集是各种属性类型混合的很好的例子,因为我们有连续、离散和名义数据。您可以在Kaggle上找到这些数据。表 8-1 提供了数据的解释。

表 8-1. 属性及其解释

属性解释
trans_date_trans_time交易日期
cc_num客户的信用卡号码
merchant进行交易的商家
amt交易金额
first客户的名字
last客户的姓氏
gender客户的性别
street, city, state客户的地址
zip交易的邮政编码
lat客户的纬度
long客户的经度
city_pop城市的人口
job客户职业的类型
dob客户的出生日期
trans_num每笔交易的唯一交易号
unix_time交易的 Unix 时间
merch_lat商家的纬度
merch_long商家的经度
is_fraud交易是否欺诈

正如您可能注意到的,如果不同类别的观察数量相对均衡,机器学*算法的效果会更好——也就是说,它在平衡数据上运行得更好。在欺诈案例中我们没有平衡的数据,因此这被称为类别不平衡。在第六章中,我们学*了如何处理类别不平衡问题,我们将在本章再次使用这个技能。

让我们开始吧。首先,查看信用卡交易欺诈检测数据集中变量的数据类型:

In [1]: import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy.stats import zscore import warnings warnings.filterwarnings('ignore')In [2]: fraud_data = pd.read_csv('fraudTrain.csv') del fraud_data['Unnamed: 0']In [3]: fraud_data.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 1296675 entries, 0 to 1296674 Data columns (total 22 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 trans_date_trans_time 1296675 non-null object 1 cc_num 1296675 non-null int64 2 merchant 1296675 non-null object 3 category 1296675 non-null object 4 amt 1296675 non-null float64 5 first 1296675 non-null object 6 last 1296675 non-null object 7 gender 1296675 non-null object 8 street 1296675 non-null object 9 city 1296675 non-null object 10 state 1296675 non-null object 11 zip 1296675 non-null int64 12 lat 1296675 non-null float64 13 long 1296675 non-null float64 14 city_pop 1296675 non-null int64 15 job 1296675 non-null object 16 dob 1296675 non-null object 17 trans_num 1296675 non-null object 18 unix_time 1296675 non-null int64 19 merch_lat 1296675 non-null float64 20 merch_long 1296675 non-null float64 21 is_fraud 1296675 non-null int64 dtypes: float64(5), int64(5), object(12) memory usage: 217.6+ MB

我们可以看到,我们拥有所有类型的数据:对象、整数和浮点数。然而,大多数变量是对象类型,因此需要进一步分析将这些分类变量转换为数值变量。

在这种分析中,因变量非常重要,因为它通常具有需要注意的不平衡特征。如下片段所示(及其结果图 Figure8-1),表明观察数量极不均衡:

In [4]: plt.pie(fraud_data['is_fraud'].value_counts(), labels=[0, 1]) plt.title('Pie Chart for Dependent Variable'); print(fraud_data['is_fraud'].value_counts()) plt.show() 0 1289169 1 7506 Name: is_fraud, dtype: int64

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (365)

图 8-1. 因变量的饼图

如我们所见,非欺诈案例的观察数量为 1,289,169,而欺诈案例仅有 7,506,所以我们知道数据是高度不平衡的,这是预料之中的。

此时,我们可以使用一个非常不同的工具来检测缺失观测数量。这个工具被称为missingno,它还为我们提供了一个用于缺失值的可视化模块(如 Figure8-2 所示):

In [5]: import missingno as msno ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) msno.bar(fraud_data) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (366)

导入missingno

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (367)

创建一个用于缺失值的条形图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (368)

图8-2. 缺失观测

图8-2 表明在顶部每个变量的非缺失观测数量,而在左侧我们可以看到非缺失值的百分比。这个分析表明数据没有缺失值。

在下一步中,首先我们将日期变量 trans_date_trans_time 转换为适当的格式,然后我们将时间分解为天和小时,假设欺诈活动在特定时间段内激增。分析欺诈对变量的不同类别的影响是有意义的。为此,我们将使用柱状图。更清楚地看到,欺诈案件数量可能会随着某些变量类别的变化而变化。但是在性别变量中保持不变,这意味着性别对欺诈活动没有影响。另一个引人注目且显而易见的观察是,欺诈案件每天和每小时变化非常大。这可以在生成的 图8-3 中得到视觉确认。

In [6]: fraud_data['time'] = pd.to_datetime(fraud_data['trans_date_trans_time']) del fraud_data['trans_date_trans_time']In [7]: fraud_data['days'] = fraud_data['time'].dt.day_name() fraud_data['hour'] = fraud_data['time'].dt.hourIn [8]: def fraud_cat(cols): k = 1 plt.figure(figsize=(20, 40)) for i in cols: categ = fraud_data.loc[fraud_data['is_fraud'] == 1, i].\ value_counts().sort_values(ascending=False).\ reset_index().head(10) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.subplot(len(cols) / 2, len(cols) / 2, k) bar_plot = plt.bar(categ.iloc[:, 0], categ[i]) plt.title(f'Cases per {i} Categories') plt.xticks(rotation='45') k+= 1 return categ, bar_plotIn [9]: cols = ['job', 'state', 'gender', 'category', 'days', 'hour'] _, bar_plot = fraud_cat(cols) bar_plot

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (369)

根据欺诈活动对 fraud_data 进行排序,按升序排列

基于分析和我们对欺诈分析的先前知识,我们可以决定在建模中使用的变量数量。分类变量进行排序,以便我们可以使用 pd.get_dummies 创建虚拟变量:

In [10]: cols=['amt','gender','state','category', 'city_pop','job','is_fraud','days','hour'] fraud_data_df=fraud_data[cols]In [11]: cat_cols=fraud_data[cols].select_dtypes(include='object').columnsIn [12]: def one_hot_encoded_cat(data, cat_cols): for i in cat_cols: df1 = pd.get_dummies(data[str(i)], prefix=i, drop_first=True) data.drop(str(i), axis=1, inplace=True) data = pd.concat([data, df1], axis=1) return dataIn [13]: fraud_df = one_hot_encoded_cat(fraud_data_df, cat_cols)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (370)

图8-3. 柱状图变量

在分类变量分析之后,讨论数值变量 amountpopulationhour 之间的交互作用是值得的。相关性分析为我们提供了一个强有力的工具,用于确定这些变量之间的相互作用,而生成的热图(图8-4)表明相关性非常低:

In [14]: num_col = fraud_data_df.select_dtypes(exclude='object').columns fraud_data_df = fraud_data_df[num_col] del fraud_data_df['is_fraud']In [15]: plt.figure(figsize=(10,6)) corrmat = fraud_data_df.corr() top_corr_features = corrmat.index heat_map = sns.heatmap(corrmat, annot=True, cmap="viridis")

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (371)

图8-4. 热图

我们已经确定了使用交互、缺失值和创建虚拟变量来确定变量的特殊特征。现在我们准备继续并运行欺诈分析的 ML 模型。我们即将运行的模型包括:

  • 逻辑回归

  • 决策树

  • 随机森林

  • XGBoost

如你所想象的那样,在进行建模之前保持数据平衡是关键。尽管有许多方法可以获得平衡数据,但我们将选择欠采样方法,因为它的性能更好。欠采样是一种将多数类别与少数类别匹配的技术,如 图8-5 所示。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (372)

图8-5. 欠采样

或者,从多数类中删除观察次数,直到获得与少数类相同数量的观察次数。我们将在以下代码块中应用欠采样,其中独立和依赖变量分别命名为X_undery_under。接下来,将随机分割训练和测试集以获取训练和测试分割:

In [16]: from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.model_selection import GridSearchCV from sklearn.model_selection import RandomizedSearchCV from sklearn.metrics import (classification_report, confusion_matrix, f1_score)In [17]: non_fraud_class = fraud_df[fraud_df['is_fraud'] == 0] fraud_class = fraud_df[fraud_df['is_fraud'] == 1]In [18]: non_fraud_count,fraud_count=fraud_df['is_fraud'].value_counts() print('The number of observations in non_fraud_class:', non_fraud_count) print('The number of observations in fraud_class:', fraud_count) The number of observations in non_fraud_class: 1289169 The number of observations in fraud_class: 7506In [19]: non_fraud_under = non_fraud_class.sample(fraud_count) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) under_sampled = pd.concat([non_fraud_under, fraud_class], axis=0) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) X_under = under_sampled.drop('is_fraud',axis=1) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) y_under = under_sampled['is_fraud'] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [20]: X_train_under, X_test_under, y_train_under, y_test_under =\ train_test_split(X_under, y_under, random_state=0)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (373)

fraud_count进行抽样

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (374)

将包含欺诈案例的数据与不包含欺诈案例的数据合并

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (375)

通过去除is_fraud创建独立变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (376)

通过is_fraud创建依赖变量

使用欠采样方法后,让我们现在运行之前描述的一些分类模型,并观察这些模型在检测欺诈方面的表现:

In [21]: param_log = {'C': np.logspace(-4, 4, 4), 'penalty': ['l1', 'l2']} log_grid = GridSearchCV(LogisticRegression(), param_grid=param_log, n_jobs=-1) log_grid.fit(X_train_under, y_train_under) prediction_log = log_grid.predict(X_test_under)In [22]: conf_mat_log = confusion_matrix(y_true=y_test_under, y_pred=prediction_log) print('Confusion matrix:\n', conf_mat_log) print('--' * 25) print('Classification report:\n', classification_report(y_test_under, prediction_log)) Confusion matrix: [[1534 310] [ 486 1423]] -------------------------------------------------- Classification report: precision recall f1-score support 0 0.76 0.83 0.79 1844 1 0.82 0.75 0.78 1909 accuracy 0.79 3753 macro avg 0.79 0.79 0.79 3753 weighted avg 0.79 0.79 0.79 3753

首先,让我们看一下混淆矩阵。混淆矩阵表明误报和漏报的观察次数分别为 310 和 486。我们将在基于成本的方法中使用混淆矩阵。

F1 分数是用于衡量这些模型性能的指标。它呈现了召回率和精确率的加权平均值,使其成为这种情况下的理想衡量标准。

第二个模型是决策树,在建模欺诈方面表现良好。调整超参数后,F1 分数显著提高,表明决策树表现相对良好。与逻辑回归相比,误报和漏报观察次数显著减少:

In [23]: from sklearn.tree import DecisionTreeClassifierIn [24]: param_dt = {'max_depth': [3, 5, 10], 'min_samples_split': [2, 4, 6], 'criterion': ['gini', 'entropy']} dt_grid = GridSearchCV(DecisionTreeClassifier(), param_grid=param_dt, n_jobs=-1) dt_grid.fit(X_train_under, y_train_under) prediction_dt = dt_grid.predict(X_test_under)In [25]: conf_mat_dt = confusion_matrix(y_true=y_test_under, y_pred=prediction_dt) print('Confusion matrix:\n', conf_mat_dt) print('--' * 25) print('Classification report:\n', classification_report(y_test_under, prediction_dt)) Confusion matrix: [[1795 49] [ 84 1825]] -------------------------------------------------- Classification report: precision recall f1-score support 0 0.96 0.97 0.96 1844 1 0.97 0.96 0.96 1909 accuracy 0.96 3753 macro avg 0.96 0.96 0.96 3753 weighted avg 0.96 0.96 0.96 3753

根据共同信念,作为集成模型的随机森林优于决策树。然而,在这种情况下,这只有在决策树预测不稳定且不同样本的预测结果差异巨大时才成立,而这里并非如此。正如您从以下结果中可以观察到的那样,即使随机森林的 F1 分数为 87,它也不如决策树表现好:

In [26]: from sklearn.ensemble import RandomForestClassifierIn [27]: param_rf = {'n_estimators':[20,50,100] , 'max_depth':[3,5,10], 'min_samples_split':[2,4,6], 'max_features':['auto', 'sqrt', 'log2']} rf_grid = GridSearchCV(RandomForestClassifier(), param_grid=param_rf, n_jobs=-1) rf_grid.fit(X_train_under, y_train_under) prediction_rf = rf_grid.predict(X_test_under)In [28]: conf_mat_rf = confusion_matrix(y_true=y_test_under, y_pred=prediction_rf) print('Confusion matrix:\n', conf_mat_rf) print('--' * 25) print('Classification report:\n', classification_report(y_test_under, prediction_rf)) Confusion matrix: [[1763 81] [ 416 1493]] -------------------------------------------------- Classification report: precision recall f1-score support 0 0.81 0.96 0.88 1844 1 0.95 0.78 0.86 1909 accuracy 0.87 3753 macro avg 0.88 0.87 0.87 3753 weighted avg 0.88 0.87 0.87 3753

我们将看看的最终模型是 XGBoost,其产生了与决策树类似的结果,其 F1 分数为 97:

In [29]: from xgboost import XGBClassifierIn [30]: param_boost = {'learning_rate': [0.01, 0.1], 'max_depth': [3, 5, 7], 'subsample': [0.5, 0.7], 'colsample_bytree': [0.5, 0.7], 'n_estimators': [10, 20, 30]} boost_grid = RandomizedSearchCV(XGBClassifier(), param_boost, n_jobs=-1) boost_grid.fit(X_train_under, y_train_under) prediction_boost = boost_grid.predict(X_test_under)In [31]: conf_mat_boost = confusion_matrix(y_true=y_test_under, y_pred=prediction_boost) print('Confusion matrix:\n', conf_mat_boost) print('--' * 25) print('Classification report:\n', classification_report(y_test_under, prediction_boost)) Confusion matrix: [[1791 53] [ 75 1834]] -------------------------------------------------- Classification report: precision recall f1-score support 0 0.96 0.97 0.97 1844 1 0.97 0.96 0.97 1909 accuracy 0.97 3753 macro avg 0.97 0.97 0.97 3753 weighted avg 0.97 0.97 0.97 3753

综合所有申请情况,以下是总结结果:

表 8-2. 使用欠采样对欺诈进行建模的结果

模型F1 分数
逻辑回归0.79
决策树0.96
随机森林0.87
XGBoost0.97

基于成本的欺诈检查

欠采样为处理不平衡数据提供了方便的工具。然而,它也伴随着成本,其中最大的成本是丢弃重要的观察结果。尽管不同的采样程序可以应用于诸如医疗保健、欺诈等敏感分析领域,但应注意性能指标未能考虑不同误分类对经济影响的程度。因此,如果某种方法提出了不同的误分类成本,它被称为成本敏感分类器。让我们考虑欺诈案例,这是成本敏感分析的一个典型例子。在这种类型的分析中,明显假阳性比假阴性成本低。更准确地说,假阳性意味着阻止一个已经合法的交易。这类分类的成本往往与行政和机会成本相关,例如检测所花费的时间和精力,以及金融机构可能因交易而失去的潜在收益。

然而,未能检测到欺诈(即出现假阴性)对于一家公司意义重大,因为这可能暗示着各种内部弱点以及设计不良的运营程序。未能检测到真实欺诈的公司可能会承担大笔财务成本,包括交易金额,更不用说因声誉受损而产生的成本了。前一种成本将加重公司的负担,但后者既无法量化也不能被忽视。

如您所见,为不同的误分类分配不同的成本需求使我们能够找到更为显著、现实的解决方案。为简单起见,我们假设假阴性和真阳性的成本分别为交易金额和 2。表8-3 总结了结果。另一种评估成本敏感性的方法是假设常数假阴性,就像其他情况一样。然而,这种方法被认为是不现实的。

表 8-3. 成本敏感矩阵

模型F1 分数
真阳性 = 2假阴性 = 交易金额
假阳性 = 2真阴性 = 0

因此,一个机构可能面临的总成本将采取以下形式:

Cost = i=1 N y i ( c i C TP i + ( 1 - c i ) C FN i ) + ( 1 - y i ) c i C FP i

其中c i是预测标签,y i是实际标签,N是观察数,C TP iC FP i对应于行政成本,在我们的案例中为 2。C FN i代表交易金额。

现在,有了这些信息,让我们重新审视考虑成本敏感方法的 ML 模型,并计算这些模型的成本变化。然而,在我们开始之前,值得注意的是,成本敏感模型不是快速处理型的,因此由于我们有大量观察结果,从中抽样来及时地对数据建模是明智的。一个类相关的成本度量如下:

In [32]: fraud_df_sampled = fraud_df.sample(int(len(fraud_df) * 0.2)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [33]: cost_fp = 2 cost_fn = fraud_df_sampled['amt'] cost_tp = 2 cost_tn = 0 cost_mat = np.array([cost_fp * np.ones(fraud_df_sampled.shape[0]), cost_fn, cost_tp * np.ones(fraud_df_sampled.shape[0]), cost_tn * np.ones(fraud_df_sampled.shape[0])]).T ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [34]: cost_log = conf_mat_log[0][1] * cost_fp + conf_mat_boost[1][0] * \ cost_fn.mean() + conf_mat_log[1][1] * cost_tp ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) cost_dt = conf_mat_dt[0][1] * cost_fp + conf_mat_boost[1][0] * \ cost_fn.mean() + conf_mat_dt[1][1] * cost_tp ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) cost_rf = conf_mat_rf[0][1] * cost_fp + conf_mat_boost[1][0] * \ cost_fn.mean() + conf_mat_rf[1][1] * cost_tp ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) cost_boost = conf_mat_boost[0][1] * cost_fp + conf_mat_boost[1][0] * \ cost_fn.mean() + conf_mat_boost[1][1] * cost_tp ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (377)

fraud_df数据中抽样

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (378)

计算成本矩阵

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (379)

计算每个模型的总成本

计算总成本使我们能够采用不同的方法来评估模型性能。具有高 F1 分数的模型预计总成本较低,这就是我们在表8-4 中看到的情况。逻辑回归的总成本最高,而 XGBoost 的总成本最低。

表 8-4. 总成本

模型总成本
逻辑回归5995
决策树5351
随机森林5413
XGBoost5371

节省分数

在成本改善中可以使用不同的指标,而节省分数绝对是其中之一。为了能够定义节省,让我们给出成本的公式。

Bahnsen、Aouada 和 Ottersten(2014)清楚地解释了以下节省分数公式:

Cost(f(S)) = i=1 N y i (c i C TP i +(1-c i )C FN i ) + (1-y i ) (c i C FP i +(1-c i )C TN i )

其中TPFNFPTN分别是真正例,假负例,假正例和真负例。对于训练集S上的每个观察ic i 是预测标签。y i是类标签,取值为 1 或 0,即y 0 , 1。我们的节省公式如下:

Saving(f(S)) = Cost(f(S))-Cost l (S) Cost l (S)

其中 C o s t l = m i n C o s t ( f 0 ( S ) ) , C o s t ( f 1 ( S ) ) 其中 f 0 预测类别 0, c 0 ,而 f 1 预测类别 1 中的观测, c 1

在代码中,我们有以下内容:

In [35]: import joblib import sys sys.modules['sklearn.externals.joblib'] = joblib from costcla.metrics import cost_loss, savings_score from costcla.models import BayesMinimumRiskClassifierIn [36]: X_train, X_test, y_train, y_test, cost_mat_train, cost_mat_test = \ train_test_split(fraud_df_sampled.drop('is_fraud', axis=1), fraud_df_sampled.is_fraud, cost_mat, test_size=0.2, random_state=0)In [37]: saving_models = [] saving_models.append(('Log. Reg.', LogisticRegression())) saving_models.append(('Dec. Tree', DecisionTreeClassifier())) saving_models.append(('Random Forest', RandomForestClassifier()))In [38]: saving_score_base_all = [] for name, save_model in saving_models: sv_model = save_model sv_model.fit(X_train, y_train) y_pred = sv_model.predict(X_test) saving_score_base = savings_score(y_test, y_pred, cost_mat_test) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) saving_score_base_all.append(saving_score_base) print('The saving score for {} is {:.4f}'. format(name, saving_score_base)) print('--' * 20) The saving score for Log. Reg. is -0.5602 ---------------------------------------- The saving score for Dec. Tree is 0.6557 ---------------------------------------- The saving score for Random Forest is 0.4789 ----------------------------------------In [39]: f1_score_base_all = [] for name, save_model in saving_models: sv_model = save_model sv_model.fit(X_train, y_train) y_pred = sv_model.predict(X_test) f1_score_base = f1_score(y_test, y_pred, cost_mat_test) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) f1_score_base_all.append(f1_score_base) print('The F1 score for {} is {:.4f}'. format(name, f1_score_base)) print('--' * 20) The F1 score for Log. Reg. is 0.0000 ---------------------------------------- The F1 score for Dec. Tree is 0.7383 ---------------------------------------- The F1 score for Random Forest is 0.7068 ----------------------------------------

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (380)

计算节省分数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (381)

计算 F1 分数

警告

请注意,如果您使用的是 sklearn 版本 0.23 或更高版本,则需要将其降级到 0.22 版本以使用 costcla 库。这是由于 costcla 库内部的 sklearn.external.six 包。

表8-5 显示,决策树在三个模型中具有最高的节省分数,有趣的是,逻辑回归产生了负的节省分数,这意味着假阴性和假阳性预测的数量相当大,从而扩大了节省分数公式的分母。

表 8-5. 节省分数

模型节省分数F1 分数
逻辑回归-0.56020.0000
决策树0.65570.7383
随机森林0.47890.7068

成本敏感建模

到目前为止,我们已经讨论了节省分数和成本敏感的概念,现在我们准备运行成本敏感的逻辑回归、决策树和随机森林。我们在这里要解决的问题是,如果考虑到误分类的不同成本来建模欺诈会发生什么?这如何影响节省分数?

为了进行这项调查,我们将使用 costcla 库。这个库是专门为采用成本敏感分类器而创建的,其中考虑了不同的误分类成本。因为,正如之前讨论的那样,传统的欺诈模型假设所有正确分类和错误分类的示例都具有相同的成本,这在欺诈中是不正确的(Bahnsen 2021)。

应用成本敏感模型后,使用节省分数来比较以下代码中的模型:

In [40]: from costcla.models import CostSensitiveLogisticRegression from costcla.models import CostSensitiveDecisionTreeClassifier from costcla.models import CostSensitiveRandomForestClassifierIn [41]: cost_sen_models = [] cost_sen_models.append(('Log. Reg. CS', CostSensitiveLogisticRegression())) cost_sen_models.append(('Dec. Tree CS', CostSensitiveDecisionTreeClassifier())) cost_sen_models.append(('Random Forest CS', CostSensitiveRandomForestClassifier()))In [42]: saving_cost_all = [] for name, cost_model in cost_sen_models: cs_model = cost_model cs_model.fit(np.array(X_train), np.array(y_train), cost_mat_train) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) y_pred = cs_model.predict(np.array(X_test)) saving_score_cost = savings_score(np.array(y_test), np.array(y_pred), cost_mat_test) saving_cost_all.append(saving_score_cost) print('The saving score for {} is {:.4f}'. format(name, saving_score_cost)) print('--'*20) The saving score for Log. Reg. CS is -0.5906 ---------------------------------------- The saving score for Dec. Tree CS is 0.8419 ---------------------------------------- The saving score for Random Forest CS is 0.8903 ----------------------------------------In [43]: f1_score_cost_all = [] for name, cost_model in cost_sen_models: cs_model = cost_model cs_model.fit(np.array(X_train), np.array(y_train), cost_mat_train) y_pred = cs_model.predict(np.array(X_test)) f1_score_cost = f1_score(np.array(y_test), np.array(y_pred), cost_mat_test) f1_score_cost_all.append(f1_score_cost) print('The F1 score for {} is {:.4f}'. format(name, f1_score_cost)) print('--'*20) The F1 score for Log. Reg. CS is 0.0000 ---------------------------------------- The F1 score for Dec. Tree CS is 0.3281 ---------------------------------------- The F1 score for Random Forest CS is 0.4012 ----------------------------------------

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (382)

通过迭代训练成本敏感模型

根据 表8-6,最佳和最差的救助分数分别在随机森林和逻辑回归中获得。这证实了两个重要事实:首先,它意味着随机森林具有低数量的不准确观察结果,其次,这些不准确的观察结果成本较低。准确地说,用随机森林建模欺诈生成了极少数量的假阴性,这是救助分数公式的分母。

表 8-6. 成本敏感模型的救助分数

模型救助分数F1 分数
逻辑回归-0.59060.0000
决策树0.84140.3281
随机森林0.89130.4012

贝叶斯最小风险

贝叶斯决策还可用于考虑成本敏感性的欺诈建模。贝叶斯最小风险方法基于使用不同成本(或损失)和概率的决策过程。数学上,如果预测某笔交易是欺诈,整体风险定义如下:

R ( c f | S ) = L ( c f | y f ) P ( c f | S ) + L ( c f | y l ) P ( c l | S )

另一方面,如果预测某笔交易合法,则整体风险如下:

R ( c l | S ) = L ( c l | y l ) P ( c l | S ) + L ( c l | y f ) P ( c f | S )

其中y fy l分别是欺诈案例和合法案例的实际类别。L ( c f | y f )表示检测到欺诈并且实际类别为欺诈时的成本。类似地,L ( c l | y l )表示预测交易为合法且实际类别为合法时的成本。相反,L ( c f | y l )L ( c l | y f )计算表8-3 中的非对角元素的成本。前者计算预测交易为欺诈但实际类别不是时的成本,后者显示交易为合法但实际类别为欺诈时的成本。P ( c l | S )表示给定S时有合法交易的预测概率,P ( c f | S )表示给定S时有欺诈交易的预测概率。

或者,贝叶斯最小风险公式可以解释为:

R ( c f | S ) = C admin P ( c f | S ) + C admin P ( c l | S )R ( c l | S ) = 0 + C amt P ( c l | S )

其中admin是管理成本,amt是交易金额。话虽如此,如果交易是欺诈,那么它就被标记为欺诈:

R ( c f | S ) R ( c l | S )

或者:

C admin P ( c f | S ) + C admin P ( c l | S ) C amt P ( c l | S )

现在是时候在 Python 中应用贝叶斯最小风险模型了。再次使用三个模型并使用 F1 分数进行比较:F1 分数的结果可以在 表8-7 中找到,并且决策树具有最高的 F1 分数,逻辑回归具有最低的 F1 分数。因此,储蓄分数的顺序是相反的,显示出成本敏感方法的有效性:

In [44]: saving_score_bmr_all = [] for name, bmr_model in saving_models: f = bmr_model.fit(X_train, y_train) y_prob_test = f.predict_proba(np.array(X_test)) f_bmr = BayesMinimumRiskClassifier() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) f_bmr.fit(np.array(y_test), y_prob_test) y_pred_test = f_bmr.predict(np.array(y_prob_test), cost_mat_test) saving_score_bmr = savings_score(y_test, y_pred_test, cost_mat_test) saving_score_bmr_all.append(saving_score_bmr) print('The saving score for {} is {:.4f}'.\ format(name, saving_score_bmr)) print('--' * 20) The saving score for Log. Reg. is 0.8064 ---------------------------------------- The saving score for Dec. Tree is 0.7343 ---------------------------------------- The saving score for Random Forest is 0.9624 ----------------------------------------In [45]: f1_score_bmr_all = [] for name, bmr_model in saving_models: f = bmr_model.fit(X_train, y_train) y_prob_test = f.predict_proba(np.array(X_test)) f_bmr = BayesMinimumRiskClassifier() f_bmr.fit(np.array(y_test), y_prob_test) y_pred_test = f_bmr.predict(np.array(y_prob_test), cost_mat_test) f1_score_bmr = f1_score(y_test, y_pred_test) f1_score_bmr_all.append(f1_score_bmr) print('The F1 score for {} is {:.4f}'.\ format(name, f1_score_bmr)) print('--'*20) The F1 score for Log. Reg. is 0.1709 ---------------------------------------- The F1 score for Dec. Tree is 0.6381 ---------------------------------------- The F1 score for Random Forest is 0.4367 ----------------------------------------

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (383)

调用贝叶斯最小风险分类器库

表 8-7. 基于 BMR 的 F1 分数

模型储蓄分数F1 分数
逻辑回归0.80640.1709
决策树0.73430.6381
随机森林0.96240.4367

要创建这些数据的绘图,我们执行以下操作(导致 图8-6):

In [46]: savings = [saving_score_base_all, saving_cost_all, saving_score_bmr_all] f1 = [f1_score_base_all, f1_score_cost_all, f1_score_bmr_all] saving_scores = pd.concat([pd.Series(x) for x in savings]) f1_scores = pd.concat([pd.Series(x) for x in f1]) scores = pd.concat([saving_scores, f1_scores], axis=1) scores.columns = ['saving_scores', 'F1_scores']In [47]: model_names = ['Log. Reg_base', 'Dec. Tree_base', 'Random Forest_base', 'Log. Reg_cs', 'Dec. Tree_cs', 'Random Forest_cs', 'Log. Reg_bayes', 'Dec. Tree_bayes', 'Random Forest_bayes']In [48]: plt.figure(figsize=(10, 6)) plt.plot(range(scores.shape[0]), scores["F1_scores"], "--", label='F1Score') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.bar(np.arange(scores.shape[0]), scores['saving_scores'], 0.6, label='Savings') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) _ = np.arange(len(model_names)) plt.xticks(_, model_names) plt.legend(loc='best') plt.xticks(rotation='vertical') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (384)

根据使用的模型绘制 F1 分数的折线图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (385)

根据使用的模型绘制条形图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (386)

图 8-6. F1 和储蓄分数

图8-6 展示了我们迄今所采用的模型在 F1 和储蓄分数上的表现。据此,成本敏感和贝叶斯最小风险模型超过了基础模型,符合预期。

无监督学*模型也用于以提取数据的隐藏特征的方式检测欺诈活动。这种方法比监督模型的最显着优势是不需要应用抽样过程来解决数据不平衡问题。无监督模型本质上不需要关于数据的任何先验知识。为了了解无监督学*模型在这类数据上的表现,我们将探讨自组织映射(SOM)和自动编码器模型。

自组织映射

SOM 是一种从高维空间获得低维空间的无监督方法。这是由芬兰学者 Teuvo Kohonen 在 1980 年代提出的方法,并且广泛传播开来。SOM 是一种人工神经网络,因此它是基于竞争学*的,输出神经元竞争被激活。被激活的神经元称为获胜神经元,每个神经元都有相邻的权重,因此输出空间中节点的空间位置表明了输入空间中的固有统计特征(Haykin 1999)。

SOM 方法的最显著特征如下(Asan 和 Ercan 2012):

  • 关于变量分布没有任何假设

  • 变量之间的依赖结构

  • 处理非线性结构

  • 处理嘈杂和缺失数据

让我们逐步了解 SOM 技术的重要步骤。正如你可能猜到的那样,第一步是识别获胜节点或激活的神经元。获胜节点是通过距离度量来识别的,即曼哈顿距离、切比雪夫距离和欧氏距离。在这些距离度量中,欧氏距离是最常用的,因为它在梯度下降过程中表现良好。因此,给定以下欧几里得公式,我们可以找到样本和权重之间的距离:

( x t - w i ( t ) ) = i = 1 n (x tj -w tji ) 2 , i = 1 , 2 , . . . , n

其中x为样本,w为权重,获胜节点k(t)显示在 Equation 8-1 中。

方程 8-1. 识别获胜节点

k ( t ) = arg min x ( t ) - w ) i ( t )

另一个重要的步骤是更新权重。给定学*率和邻域大小,应用以下更新:

w i ( t + 1 ) = w i ( t ) + λ [ x ( t ) - w i ( t ) ]

其中w i ( t )为第t th次迭代中获胜神经元i的权重,而λ为学*率。

Richardson、Risien 和 Shillington(2003)指出,权重适应速率随着其远离获胜节点而衰减。这由邻域函数h ki ( t )定义,其中i是邻居的索引。在邻域函数中,最著名的是具有以下形式的高斯函数:

h ki ( t ) = e x p ( - d ki 2 2σ 2 (t) )

其中d ki 2表示获胜神经元和相关神经元之间的距离,σ 2 ( t )表示在第t次迭代时的半径。

考虑到这一切,更新过程如公式 8-2 所示。

公式 8-2. 更新权重

w i ( t + 1 ) = w i ( t ) + λ h ki ( t ) [ x ( t ) - w i ( t ) ]

就是这样,但我知道这个过程有点乏味。因此,让我们总结一下步骤:

  1. 初始化权重:将权重赋予随机值是最常见的方法。

  2. 使用公式 8-1 找到获胜神经元。

  3. 根据公式 8-2 给出的更新权重。

  4. 根据公式 8-2 的结果调整参数,通过将t设置为t + 1。

我们已经知道我们使用的欺诈数据中有两类,因此我们的自组织映射应具有两行一列的结构。您可以在以下代码中找到其应用:

In [49]: from sklearn.preprocessing import StandardScaler standard = StandardScaler() scaled_fraud = standard.fit_transform(X_under)In [50]: from sklearn_som.som import SOM som = SOM(m=2, n=1, dim=scaled_fraud.shape[1]) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) som.fit(scaled_fraud) predictions_som = som.predict(np.array(scaled_fraud))In [51]: predictions_som = np.where(predictions_som == 1, 0, 1)In [52]: print('Classification report:\n', classification_report(y_under, predictions_som)) Classification report: precision recall f1-score support 0 0.56 0.40 0.47 7506 1 0.53 0.68 0.60 7506 accuracy 0.54 15012 macro avg 0.54 0.54 0.53 15012 weighted avg 0.54 0.54 0.53 15012

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (387)

配置 SOP

检查分类报告后,明显地,F1 得分与其他方法所得相似。这证实了当我们没有标记数据时,SOM 在检测欺诈方面是一个有用的模型。在以下代码中,我们生成图8-7,显示了实际和预测类别:

In [53]: fig, ax = plt.subplots(nrows=1, ncols=2, figsize=(8, 6)) x = X_under.iloc[:,0] y = X_under.iloc[:,1] ax[0].scatter(x, y, alpha=0.1, cmap='Greys', c=y_under) ax[0].title.set_text('Actual Classes') ax[1].scatter(x, y, alpha=0.1, cmap='Greys', c=predictions_som) ax[1].title.set_text('SOM Predictions')

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (388)

图 8-7. SOM 预测

自编码器

自编码器是一种无监督深度学*模型,训练通过隐藏层将输入转换为输出。然而,自编码器的网络结构与其他结构不同,自编码器由两部分组成:编码器解码器

编码器作为特征提取函数,解码器作为重构函数。举例来说,让x为输入,h为隐藏层。然后,编码器函数为h = f ( x ) ,解码器函数通过 r = g ( h ) 进行重构。如果自编码器通过简单复制学*,即 g ( f ( x ) ) = x,这不是理想的情况,因为自编码器寻求特征提取。这相当于仅复制输入的相关方面(Goodfellow et al. 2016)。

因此,自编码器具有一种网络结构,可以以较低维度的方式压缩知识,形成原始输入的表示。给定编码器和解码器函数,有不同类型的自编码器。其中,我们将讨论三种最常用的自编码器,以保持我们的方向:

  • 欠完备自编码器

  • 稀疏自编码器

  • 去噪自编码器

欠完备自编码器

这是最基本的自编码器类型,因为隐藏层h的维度比训练数据x小。因此,神经元数量少于训练数据的数量。这种自编码器的目标是通过最小化损失函数来捕捉数据的潜在属性,即 𝕃 ( x , g ( f ( x ) ) ) ,其中 𝕃 是损失函数。

自编码器在机器学*中著名地面临着偏差-方差的权衡,即自编码器旨在良好地重构输入同时具有低维表示。为了解决这个问题,我们将介绍稀疏和去噪自编码器。

稀疏自编码器

稀疏自编码器提出了一种解决这种权衡的方法,通过对重构误差施加稀疏性。在稀疏自编码器中,有两种强制正则化的方法。第一种方法是应用 L 1 正则化。在这种情况下,自编码器的优化变为(Banks, Koenigstein, and Giryes 2020):

arg min g,f 𝕃 ( x , g ( f ( x ) ) ) + λ ( h )

其中 g ( f ( x ) ) 是解码器,h 是编码器的输出。图8-8 展示了稀疏自编码器。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (389)

图 8-8. 稀疏自编码器模型结构

正则化稀疏自编码器的第二种方法是使用 Kullback-Leibler(KL)散度,它通过测量它们之间的距离来简单地告诉我们两个概率分布的相似性。KL 散度可以用数学方式表达为:

𝕃 ( x , x ^ ) + j K L ( ρ ρ ^ )

其中ρρ ^分别是理想分布和观察分布。

降噪自编码器

降噪自编码器的思想是,与使用惩罚项λ相反,向输入数据添加噪声,并从这种变化的构建(即重建)中学*。因此,与最小化𝕃 ( x , g ( f ( x ) ) )相反,降噪自编码器提供最小化以下损失函数:

𝕃 ( x , g ( f ( x ^ ) ) )

其中x ^是通过添加例如高斯噪声获得的损坏输入。图 8-9 说明了这个过程。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (390)

图 8-9. 降噪自编码器模型结构

在下面的代码中,我们将使用 Keras 中的自编码器模型。在继续之前,它使用标准缩放器进行了缩放,然后使用批量大小为 200 和 100 个时期,我们能够得到令人满意的预测结果。然后,我们将从自编码器模型创建一个重构误差表,以与真实类进行比较,结果表明这些模型的均值和标准差彼此接*:

In [54]: from sklearn.preprocessing import StandardScaler from tensorflow import keras from tensorflow.keras.layers import Dense, Dropout from keras import regularizersIn [55]: fraud_df[['amt','city_pop','hour']] = StandardScaler().\ fit_transform(fraud_df[['amt','city_pop','hour']])In [56]: X_train, X_test = train_test_split(fraud_df, test_size=0.2, random_state=123) X_train[X_train['is_fraud'] == 0] X_train = X_train.drop(['is_fraud'], axis=1).values y_test = X_test['is_fraud'] X_test = X_test.drop(['is_fraud'], axis=1).valuesIn [57]: autoencoder = keras.Sequential() autoencoder.add(Dense(X_train_under.shape[1], activation='tanh', activity_regularizer=regularizers.l1(10e-5), input_dim= X_train_under.shape[1])) #encoder autoencoder.add(Dense(64, activation='tanh')) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) autoencoder.add(Dense(32, activation='relu')) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) #decoder autoencoder.add(Dense(32, activation='elu')) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) autoencoder.add(Dense(64,activation='tanh')) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) autoencoder.add(Dense(X_train_under.shape[1], activation='elu')) autoencoder.compile(loss='mse', optimizer='adam') autoencoder.summary(); Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 566) 320922 _________________________________________________________________ dense_1 (Dense) (None, 64) 36288 _________________________________________________________________ dense_2 (Dense) (None, 32) 2080 _________________________________________________________________ dense_3 (Dense) (None, 32) 1056 _________________________________________________________________ dense_4 (Dense) (None, 64) 2112 _________________________________________________________________ dense_5 (Dense) (None, 566) 36790 ================================================================= Total params: 399,248 Trainable params: 399,248 Non-trainable params: 0 _________________________________________________________________

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (391)

在编码器和解码器部分分别标识了 64 和 32 个隐藏层。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (392)

在编码器和解码器部分分别标识了 32 和 64 个隐藏层。

配置自编码器模型后,下一步是拟合和预测。在进行预测后,我们使用摘要统计数据来检查模型的质量,因为这是查看重构是否有效的可靠方法:

In [58]: batch_size = 200 epochs = 100In [59]: history = autoencoder.fit(X_train, X_train, shuffle=True, epochs=epochs, batch_size=batch_size, validation_data=(X_test, X_test), verbose=0).historyIn [60]: autoencoder_pred = autoencoder.predict(X_test) mse = np.mean(np.power(X_test - autoencoder_pred, 2), axis=1) error_df = pd.DataFrame({'reconstruction_error': mse, 'true_class': y_test}) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) error_df.describe()Out[60]: reconstruction_error true_class count 259335.000000 259335.000000 mean 0.002491 0.005668 std 0.007758 0.075075 min 0.000174 0.000000 25% 0.001790 0.000000 50% 0.001993 0.000000 75% 0.003368 0.000000 max 2.582811 1.000000

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (393)

创建一个名为error_df的表格,以比较从模型获得的结果与真实数据

最后,我们创建我们的图表(图 8-10):

In [61]: plt.figure(figsize=(10, 6)) plt.plot(history['loss'], linewidth=2, label='Train') plt.plot(history['val_loss'], linewidth=2, label='Test') plt.legend(loc='upper right') plt.title('Model loss') plt.ylabel('Loss') plt.xlabel('Epoch') plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (394)

图 8-10. 自编码器性能

图8-10 展示了我们使用线图进行的自动编码器建模的结果,我们可以看到测试损失结果比训练损失更加波动,但平均而言,平均损失是相似的。

欺诈因为几个原因在金融领域是一个热门话题。严格的监管、声誉损失以及欺诈带来的成本是打击它的主要原因。直到最*,欺诈对金融机构来说一直是一个大问题,因为建模欺诈没有产生令人满意的结果,因此金融机构不得不投入更多资源来处理这一现象。多亏了机器学*的最新进展,我们现在可以利用各种工具来对抗欺诈,本章专门介绍了这些模型并比较了它们的结果。这些模型涵盖了从逻辑回归这样的参数化方法到自动编码器这样的深度学*模型。

在下一章中,我们将探讨一种名为股价崩盘风险的不同财务风险模型,这将使我们能够了解公司治理的健康状况。这对于财务风险管理非常重要,因为风险管理最终根植于公司管理。希望在公司治理不善的公司中期望低风险是幼稚的。

本章引用的文章:

  • Asan, Umut 和 Secil Ercan. 2012. “自组织映射简介。” 在 工业工程中的计算智能系统 中,由 Cengiz Kahraman 编辑。295-315 页。 巴黎:Atlantis Press。

  • Bahnsen, Alejandro Correa, Djamia Aouada 和 Björn Ottersten. 2014. “基于示例的成本敏感逻辑回归用于信用评分。” 在 第 13 届国际机器学*与应用会议 中,263-269 页。 IEEE。

  • Bank, Dor, Noam Koenigstein 和 Raja Giryes. 2020. “自动编码器。” arXiv 预印本 arXiv:2003.05991。

  • Dunnett, Robert S., Cindy B. Levy 和 Antonio P. Simoes. 2005. “运营风险的隐藏成本。” McKinsey St Company.

  • Richardson, Anthony J., C. Risien 和 Frank Alan Shillington. 2003. “使用自组织映射识别卫星图像中的模式。” 海洋学进展 59 (2-3): 223-239。

本章引用的书籍和在线资源:

  • Bahnsen, Alejandro Correa. 2021. “引言:基于示例的成本敏感分类。” https://oreil.ly/5eCsJ

  • Goodfellow, Ian, Yoshua Bengio 和 Aaron Courville. 2016. 深度学*。剑桥:MIT 出版社。

  • Nilsen. 2020. “卡片欺诈损失达到 28.65 亿美元。” Nilsen 报告。 https://oreil.ly/kSls7

  • Office of the Comptroller of the Currency. 2019. “运营风险:欺诈风险管理原则。” CC 公告。 https://oreil.ly/GaQez

  • Simon, Haykin. 1999. 神经网络:全面的基础,第二版。新泽西州恩格尔伍德克利夫斯:Prentice-Hall。

理解公司治理不仅启发了在富裕经济体中或许边缘改进的讨论,还可以在需要进行重大制度改变的地方激发主要的制度变革。

Shleifer 和 Vishny (1997)

您认为可以通过风险度量来评估公司治理的质量吗?根据最*的研究,答案是肯定的。公司治理与风险度量之间的联系已经通过股价崩盘风险建立起来,这被称为个别股票大幅下跌的风险。这种关联引发了该领域的大量研究。

发现股价崩盘的决定因素的重要性在于识别低(或高)质量公司治理的根本原因。识别这些根本原因有助于公司集中解决问题管理领域,增强公司的运行绩效并提升其声誉。这反过来降低了股价崩盘的风险,增加了公司的总收入。

股价崩盘为投资者和风险管理人员提供了关于公司公司治理强弱的信号。公司治理被定义为指导和控制公司的方式,以及公司是否或不是“促进公司公平性、透明度和责任性”(Wolfensohn 1999)的方式。

根据这一定义,公司治理有三大支柱:

公平

这一原则涉及对所有股东的平等待遇。

透明度

及时告知股东有关公司事件的任何信息称为透明度。这意味着与不愿向股东披露信息相对立的状态。

责任制

这与制定一个既公平、又平衡和可理解的行为准则相关,通过该准则向股东呈现公司位置的公平、平衡和可理解的评估。

责任制是控制代理成本的一种工具,代理成本是由股东和管理层的利益冲突所引起的成本。代理成本是信息不对称的另一个来源,因为管理者和股东拥有的信息量不同。当管理者和股东的利益发生分歧时,就会产生冲突。更确切地说,管理者一方面希望最大化自己的权力和财富。另一方面,股东则希望找到一种方法来最大化股东价值。这两个目标可能发生冲突,由于管理者信息上的优势,一些公司政策可能旨在增加管理者的权力和财富,损害股东利益。

因此,股价崩盘可能是有关企业治理质量的警告信号。例如,在信息不对称存在的情况下,代理理论表明外部利益相关者让管理者生成更不透明的财务报告以隐瞒不好的消息(Hutton、Marcus 和 Tehranian 2009)。这种现象的更*期解释被称为自主披露理论(Bae、Lim 和 Wei 2006)。根据这一理论,公司倾向于立即宣布好消息,但他们积累负面信息。当积累的负面信息达到临界点时,将引发大幅度下降。由于隐瞒公司的不好消息会阻止及时采取纠正措施,一旦积累的坏消息向市场发布,投资者将修正其未来预期,价格必然会突然下降,这被称为崩盘风险(Hutton、Marcus 和 Tehranian 2009 以及 Kim 2011)。

此外,不透明的财务报告与问责原则相关,导致管理者不愿透露不好的消息。这导致公司财务状况的不公平呈现,并进一步增加了未来股价崩盘的可能性(Bleck 和 Liu (2007),Myers (2006),以及 Kim 和 Zhang (2013))。

因此,企业治理与股价崩盘之间的关联在各种方式中是显而易见的。在本章中,我们首先介绍股价度量,然后看看我们如何将这些度量应用于检测崩盘。

我们将首先从 Center for Research in Security Prices (CRSP) 和 Compustat 数据库获取一些数据,然后识别股价崩盘的主要决定因素。

注意

CRSP 自 1960 年以来为学术研究和支持课堂教学提供数据。CRSP 在金融、经济和相关领域具有高质量的数据。欲了解更多信息,请参阅CRSP 网站

类似地,自 1962 年以来,Compustat 数据库提供了关于全球公司的财务、经济和市场信息。它是 S&P 全球市场情报的产品。欲了解更多信息,请参阅Compustat 宣传册

有关股价崩盘的文献正在增长,并且不同的崩盘度量方法由不同的研究人员采用。在介绍基于机器学*的崩盘度量方法之前,值得比较这些不同方法的利弊。

文献中使用的主要崩盘度量方法包括:

  • 下至上波动 (DUVOL)

  • 负偏度系数 (NCSKEW)

  • 崩盘

DUVOL 是一种基于“下跌”和“上涨”周公司特定回报标准偏差的常见崩盘测量方法。下跌周是指公司特定周股票回报低于财年内每周回报的均值的周。相反,上涨周是指公司特定周股票回报高于财年内每周回报的均值的周。数学上描述为:

DUVOL = log ( (n u-1 ) down R it 2 (n d-1 ) up R it 2 )

其中n是股票i在年度t的交易周数,n u是上涨周数,而n d则是下跌周数。在一年内,具有低于年度均值的公司特定回报的周被称为下跌周,而具有高于年度均值的公司特定回报的周被称为上涨周。

NCSKEW 通过取每日回报的第三阶负值,并将其除以(日回报标准差的样本模拟)的第三次幂来计算(Chen,Hong 和 Stein 2001):

NCSKEW = - (n(n-1) 3/2 R it 3 ) ((n-1)(n-2)(R it 2 ) 3/2 )

NCSKEW 和 DUVOL 指标的值越高,崩盘风险越高。

另一方面,CRASH 测量是根据与公司特定周回报的距离计算的。也就是说,如果回报低于均值的 3.09 倍(有时为 3.2 倍),则 CRASH 取值为 1,否则为 0。

毫不奇怪,基于 ML 的算法引起了极大关注,因为它们攻击了基于规则的模型的弱点,并展现出良好的预测性能。因此,我们将尝试使用名为最小协方差行列式(MCD)的 ML 方法来估计股价崩盘风险。 MCD 是一种用于检测具有椭圆对称和单峰数据集中异常值的方法。使用 MCD 估计器检测股票回报中的异常值,并将其作为依赖变量用于逻辑面板回归,以探索崩盘风险的根本原因。

MCD 估计器提供了一种在检测异常值方面稳健且一致的方法。这很重要,因为异常值可能会对多变量分析产生巨大影响。正如 Finch(2012)总结的那样,多变量分析中的异常值可能会扭曲相关系数,导致偏倚估计。

MCD 的算法可以如下给出:

  1. 基于数据检测初始的稳健聚类。

  2. 为每个聚类计算均值向量M a和正定¹协方差矩阵 a

  3. 计算每个聚类中每个观测的 MCD。

  4. 将具有较小 MCD 的新观测分配给聚类。

  5. 基于最小 MCD 选择一半的样本h,并计算从h得到的 M a a

  6. 重复步骤 2 到 5,直到h没有变化的余地。

  7. 如果 c p = χ p,0.95 2 小于 d 2 ,则检测异常值。

MCD 的优势在于其可解释性、可调性、低计算时间需求和鲁棒性:

可解释性

可解释性是模型背后算法能够解释的程度。MCD 假设数据呈椭圆分布,并且通过马哈拉诺比斯距离度量计算异常值。

注意

马哈拉诺比斯距离是多变量设置中使用的距离度量。在各种距离度量中,马哈拉诺比斯以其能够检测异常值的能力脱颖而出,尽管它是一种计算昂贵的方法,因为它考虑了变量之间的相互关系结构。

数学上,马哈拉诺比斯距离的公式如下:

d m ( x , μ ) = (x-μ) T -1 ( x - μ ) .

其中 μ = 𝔼 ( X ) 是协方差矩阵,X 是一个向量。

可调性

可调性强调具有数据依赖模型的重要性,允许其在一致基础上进行校准,以便捕捉结构变化。

低计算时间

这指的是快速计算协方差矩阵并避免使用整个样本。相反,MCD 使用一半的样本,其中不包括异常值,以确保异常观测不会使 MCD 的位置或形状偏差。

鲁棒性

在 MCD 中使用一半的样本也确保了其鲁棒性,因为这意味着在污染下模型是一致的(Hubert et al. 2018)。

现在我们将应用 MCD 方法来检测股票收益中的异常值,并将结果用作因变量。因此,如果股价崩盘,因变量取值为 1,否则为 0。

从经验的角度来看,在 Python 中有一个内置库可以运行这个算法,即椭圆包络,我们将利用它。

至此,我们已经讨论了股价崩盘检测的理论背景。从这一点出发,我们将集中精力在实证部分,并看看如何将理论应用到实践中。在这个过程中,我们不会仅仅关注股价崩盘检测。在提出基于机器学*的股价崩盘检测方法后,我们将深入研究崩盘的根本原因。为了做到这一点,鉴于大量的文献,我们将使用多个变量来观察它们如何以及在多大程度上影响股价崩盘的发生。因此,本章的目标是双重的:检测股价崩盘和识别崩盘的根本原因。请记住,关于股价崩盘检测及影响其崩盘的变量有许多不同且竞争的观点。

在这个分析中,我们将使用以下公司的股票和资产负债表信息:

苹果AT&T巴西银行美国银行
CISCO可口可乐康卡斯特杜邦公司
埃克森美孚公司Facebook福特汽车通用电气
英特尔公司强生公司摩根大通默克公司
微软Motus GI Holdings Inc.甲骨文公司辉瑞公司
宝洁公司Sherritt International Corp.Sirius XM Holdings Inc.Trisura Group Ltd.
瑞士银行威瑞森沃尔玛富国银行公司

要继续,我们需要计算每周特定公司的回报率,但我们的数据是日常的,所以让我们开始并进行必要的编码:

In [1]: import pandas as pd import matplotlib.pyplot as plt import numpy as np import seaborn as sns; sns.set() pd.set_option('use_inf_as_na', True) import warnings warnings.filterwarnings('ignore')In [2]: crash_data = pd.read_csv('crash_data.csv')In [3]: crash_data.head()Out[3]: Unnamed: 0 RET date TICKER vwretx BIDLO ASKHI PRC \ 0 27882462 0.041833 20100104 BAC 0.017045 15.12 15.750 15.69 1 27882463 0.032505 20100105 BAC 0.003362 15.70 16.210 16.20 2 27882464 0.011728 20100106 BAC 0.001769 16.03 16.540 16.39 3 27882465 0.032947 20100107 BAC 0.002821 16.51 17.185 16.93 4 27882466 -0.008860 20100108 BAC 0.004161 16.63 17.100 16.78 VOL 0 180845100.0 1 209521200.0 2 205257900.0 3 320868400.0 4 220104600.0In [4]: crash_data.date = pd.to_datetime(crash_data.date, format='%Y%m%d') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) crash_data = crash_data.set_index('date') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (395)

将日期列转换为适当的日期格式

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (396)

将日期设置为索引

作为提醒,我们使用的数据已从 CRSP 和 Compustat 收集。表9-1 提供了数据的简要说明。

表 9-1. 属性和解释

属性解释
RET股票回报率
vwretx成交量加权回报
BIDLO最低竞价价格
ASKHI最高出价价格
PRC交易价格
VOL交易量

给定这些数据,让我们计算每周的平均回报,并使用前四只股票生成图9-1。为了进行这些计算,我们还将计算其他变量的每周平均值,因为在接下来的过程中我们将使用它们:

In [5]: crash_dataw = crash_data.groupby('TICKER').resample('W').\ agg({'RET':'mean', 'vwretx':'mean', 'VOL':'mean', 'BIDLO':'mean', 'ASKHI':'mean', 'PRC':'mean'}) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [6]: crash_dataw = crash_dataw.reset_index() crash_dataw.dropna(inplace=True) stocks = crash_dataw.TICKER.unique()In [7]: plt.figure(figsize=(12, 8)) k = 1 for i in stocks[: 4]: ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) plt.subplot(2, 2, k) plt.hist(crash_dataw[crash_dataw.TICKER == i]['RET']) plt.title('Histogram of '+i) k+=1 plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (397)

计算每股周回报率以及其他变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (398)

选择前四只股票

图9-1 显示了我们的前四只股票,即苹果、美国银行、巴西银行和康卡斯特的直方图。如预期的那样,分布似乎是正态的,但我们现在有了一般来说显示过渡峰度分布的回报率。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (399)

图 9-1。返回直方图

接下来,我们计算返回以排除市场影响,这被称为公司特定收益。 为了计算公司特定的周收益,我们根据以下方程进行线性回归:

r j,t = α 0 + β 1 r m,t-2 + β 2 r m,t-1 + β 3 r m,t + β 4 r m,t+1 + β 5 r m,t+2 + ϵ j,t

其中r j,t是第 j 家公司在第 t 周的回报,r mt 是第 t 周 CRSP 市场回报的加权值。通过 1 + 对数来缩放这个回归的残差,提供了公司特定的回报。

根据扩展的市场模型,可以计算出公司特定的周收益,如下所示:W i,t = l n ( 1 + ϵ i,t ) (Kim, Li, and Zhang 2011):

In [8]: import statsmodels.api as sm residuals = [] for i in stocks: Y = crash_dataw.loc[crash_dataw['TICKER'] == i]['RET'].values X = crash_dataw.loc[crash_dataw['TICKER'] == i]['vwretx'].values X = sm.add_constant(X) ols = sm.OLS(Y[2:-2], X[2:-2] + X[1:-3] + X[0:-4] + \ X[3:-1] + X[4:]).fit() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) residuals.append(ols.resid)In [9]: residuals = list(map(lambda x: np.log(1 + x), residuals)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [10]: crash_data_sliced = pd.DataFrame([]) for i in stocks: crash_data_sliced = crash_data_sliced.\ append(crash_dataw.loc[crash_dataw.TICKER == i] [2:-2]) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) crash_data_sliced.head()Out[10]: TICKER date RET vwretx VOL BIDLO ASKHI \ 2 AAPL 2010-01-24 -0.009510 -0.009480 25930885.00 205.277505 212.888450 3 AAPL 2010-01-31 -0.005426 -0.003738 52020594.00 198.250202 207.338002 4 AAPL 2010-02-07 0.003722 -0.001463 26953208.40 192.304004 197.378002 5 AAPL 2010-02-14 0.005031 0.002970 19731018.60 194.513998 198.674002 6 AAPL 2010-02-21 0.001640 0.007700 16618997.25 201.102500 203.772500 PRC 2 208.146752 3 201.650398 4 195.466002 5 196.895200 6 202.636995

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (400)

按预定义的方程进行线性回归

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (401)

计算残差的 1 + 对数

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (402)

删除前两个和最后两个观察值以与以前的数据对齐

在所有这些准备工作之后,我们准备运行椭圆信封以检测崩盘。

只识别了两个参数:support_fractioncontamination。前者参数用于控制要包含在原始 MCD 估计支持中的点的比例,而后者用于确定数据集中异常值的比例:

In [11]: from sklearn.covariance import EllipticEnvelope envelope = EllipticEnvelope(contamination=0.02, support_fraction=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) ee_predictions = {} for i, j in zip(range(len(stocks)), stocks): envelope.fit(np.array(residuals[i]).reshape(-1, 1)) ee_predictions[j] = envelope.predict(np.array(residuals[i]) .reshape(-1, 1)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [12]: transform = [] for i in stocks: for j in range(len(ee_predictions[i])): transform.append(np.where(ee_predictions[i][j] == 1, 0, -1)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png)In [13]: crash_data_sliced = crash_data_sliced.reset_index() crash_data_sliced['residuals'] = np.concatenate(residuals) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) crash_data_sliced['neg_outliers'] = np.where((np.array(transform)) \ == -1, 1, 0) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) crash_data_sliced.loc[(crash_data_sliced.neg_outliers == 1) & (crash_data_sliced.residuals > 0), 'neg_outliers'] = 0 ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (403)

使用contaminationsupport_fraction分别为 2 和 1 运行椭圆信封

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (404)

预测崩溃

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (405)

将崩溃转换为所需形式

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (406)

获得一个一维的numpy数组以在数据框中创建一个新列

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (407)

对崩溃进行最终转换,命名为neg_outliers

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (408)

摆脱分布的正侧(即右尾)的崩溃

以下代码块用于可视化算法是否正确捕获了崩盘。在此分析中,使用了通用汽车、英特尔、强生和摩根大通。如下图所示,该算法运行良好,并且识别出了分布的负面(显示为黑色条形)崩盘:

In [14]: plt.figure(figsize=(12, 8)) k = 1 for i in stocks[8:12]: plt.subplot(2, 2, k) crash_data_sliced['residuals'][crash_data_sliced.TICKER == i]\ .hist(label='normal', bins=30, color='gray') outliers = crash_data_sliced['residuals'] [(crash_data_sliced.TICKER == i) & (crash_data_sliced.neg_outliers > 0)] outliers.hist(color='black', label='anomaly') plt.title(i) plt.legend() k += 1 plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (409)

图 9-2. 异常直方图

从这一点开始,我们将使用两个不同的数据集,因为在这个分析中需要资产负债表信息。因此,我们将我们的周数据转换为年度数据,以便将此数据与资产负债表信息(包括年度信息)合并。此外,计算崩盘风险,即股价崩盘风险的年度平均值和标准差也是必要的:

In [15]: crash_data_sliced = crash_data_sliced.set_index('date') crash_data_sliced.index = pd.to_datetime(crash_data_sliced.index)In [16]: std = crash_data.groupby('TICKER')['RET'].resample('W').std()\ .reset_index() crash_dataw['std'] = pd.DataFrame(std['RET']) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [17]: yearly_data = crash_data_sliced.groupby('TICKER')['residuals']\ .resample('Y').agg({'residuals':{'mean', 'std'}})\ .reset_index() yearly_data.columns = ['TICKER', 'date', 'mean', 'std'] yearly_data.head()Out[17]: TICKER date mean std 0 AAPL 2010-12-31 0.000686 0.008291 1 AAPL 2011-12-31 0.000431 0.009088 2 AAPL 2012-12-31 -0.000079 0.008056 3 AAPL 2013-12-31 -0.001019 0.009096 4 AAPL 2014-12-31 0.000468 0.006174In [18]: merge_crash = pd.merge(crash_data_sliced.reset_index(), yearly_data, how='outer', on=['TICKER', 'date']) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [19]: merge_crash[['annual_mean', 'annual_std']] = merge_crash\ .sort_values(by=['TICKER', 'date'])\ .iloc[:, -2:]\ .fillna(method='bfill') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) merge_crash['residuals'] = merge_crash.sort_values(by=['TICKER', 'date'])\ ['residuals']\ .fillna(method='ffill') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) merge_crash = merge_crash.drop(merge_crash.iloc[: ,-4:-2], axis=1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (410)

对数据进行重采样以计算回报的平均值和标准差

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (411)

根据Tickerdate基于yearly_datacrash_data_sliced合并

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (412)

对年度数据进行向后填充

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (413)

删除列以防止混淆

在文献中,最广泛使用的股价崩盘测量之一是崩盘风险,因为它具有离散型,使其成为比较目的的便利工具。

现在让我们用 Python 生成崩盘风险。我们将使用前一段生成的merge_crash数据。考虑到崩盘风险的公式,我们检查周回报是否低于平均值的 3.09 个标准差。如果是,标记为 1,表示崩盘,否则标记为 0。结果将显示,13502 个观察中有 44 次崩盘。

在最后一个块(In [22])中,崩盘风险度量是年度化的,以便我们能够将其包含在我们的最终数据中:

In [20]: crash_risk_out = [] for j in stocks: for k in range(len(merge_crash[merge_crash.TICKER == j])): if merge_crash[merge_crash.TICKER == j]['residuals'].iloc[k] < \ merge_crash[merge_crash.TICKER == j]['annual_mean'].iloc[k] - \ 3.09 * \ merge_crash[merge_crash.TICKER == j]['annual_std'].iloc[k]: crash_risk_out.append(1) else: crash_risk_out.append(0)In [21]: merge_crash['crash_risk'] = crash_risk_out merge_crash['crash_risk'].value_counts()Out[21]: 0 13476 1 44 Name: crash_risk, dtype: int64In [22]: merge_crash = merge_crash.set_index('date') merge_crash_annual = merge_crash.groupby('TICKER')\ .resample('1Y')['crash_risk'].sum().reset_index()

如果您像我们在这里所做的那样使用多只股票,计算 DUVOL 和 NCSKEW 并不是一件容易的事情。第一步是确定下跌和上涨周。提醒一下,下跌(或上涨)周是指周回报小于(或大于)年回报的周。在以下代码块的最后部分,我们计算了计算 DUVOL 和 NCSKEW 崩盘测量所需的必要因素,例如下跌周的平方残差:

In [23]: down = [] for j in range(len(merge_crash)): if merge_crash['residuals'].iloc[j] < \ merge_crash['annual_mean'].iloc[j]: down.append(1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) else: down.append(0) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [24]: merge_crash = merge_crash.reset_index() merge_crash['down'] = pd.DataFrame(down) merge_crash['up'] = 1 - merge_crash['down'] down_residuals = merge_crash[merge_crash.down == 1]\ [['residuals', 'TICKER', 'date']] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) up_residuals = merge_crash[merge_crash.up == 1]\ [['residuals', 'TICKER', 'date']] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png)In [25]: down_residuals['residuals_down_sq'] = down_residuals['residuals'] ** 2 down_residuals['residuals_down_cubic'] = down_residuals['residuals'] **3 up_residuals['residuals_up_sq'] = up_residuals['residuals'] ** 2 up_residuals['residuals_up_cubic'] = up_residuals['residuals'] ** 3 down_residuals['down_residuals'] = down_residuals['residuals'] up_residuals['up_residuals'] = up_residuals['residuals'] del down_residuals['residuals'] del up_residuals['residuals']In [26]: merge_crash['residuals_sq'] = merge_crash['residuals'] ** 2 merge_crash['residuals_cubic'] = merge_crash['residuals'] ** 3

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (414)

如果条件返回真,则将 1 添加到down列表中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (415)

如果条件返回真,则将 0 添加到down列表中

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (416)

创建一个名为down_residuals的新变量,包括下跌周

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (417)

创建一个名为up_residuals的新变量,包括上升周

下一步是将down_residualsup_residualsmerge_crash合并。然后,我们指定并年化我们想要检查的所有变量,以确定哪些变量在解释股价崩盘方面最重要:

In [27]: merge_crash_all = merge_crash.merge(down_residuals, on=['TICKER', 'date'], how='outer') merge_crash_all = merge_crash_all.merge(up_residuals, on=['TICKER', 'date'], how='outer')In [28]: cols = ['BIDLO', 'ASKHI', 'residuals', 'annual_std', 'residuals_sq', 'residuals_cubic', 'down', 'up', 'residuals_up_sq', 'residuals_down_sq', 'neg_outliers'] merge_crash_all = merge_crash_all.set_index('date') merge_grouped = merge_crash_all.groupby('TICKER')[cols]\ .resample('1Y').sum().reset_index() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) merge_grouped['neg_outliers'] = np.where(merge_grouped.neg_outliers >= 1, 1, 0) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (418)

指定和年化感兴趣的变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (419)

转换大于 1 的负离群观察,如果有的话

还有两个重要的问题有待解答:我们有多少下周和上周,以及它们的总和是多少?这些问题很重要,因为上下周的数量分别对应 DUVOL 公式中的n un d。所以让我们来做这个计算:

In [29]: merge_grouped = merge_grouped.set_index('date') merge_all = merge_grouped.groupby('TICKER')\ .resample('1Y').agg({'down':['sum', 'count'], 'up':['sum', 'count']})\ .reset_index() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) merge_all.head()Out[29]: TICKER date down up sum count sum count 0 AAPL 2010-12-31 27 1 23 1 1 AAPL 2011-12-31 26 1 27 1 2 AAPL 2012-12-31 28 1 26 1 3 AAPL 2013-12-31 24 1 29 1 4 AAPL 2014-12-31 22 1 31 1In [30]: merge_grouped['down'] = merge_all['down']['sum'].values merge_grouped['up'] = merge_all['up']['sum'].values merge_grouped['count'] = merge_grouped['down'] + merge_grouped['up']

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (420)

计算上周和下周的年化总和及数量

最后,我们准备使用到目前为止获得的所有输入来计算 DUVOL 和 NCSKEW:

In [31]: merge_grouped = merge_grouped.reset_index()In [32]: merge_grouped['duvol'] = np.log(((merge_grouped['up'] - 1) * merge_grouped['residuals_down_sq']) / ((merge_grouped['down'] - 1) * merge_grouped['residuals_up_sq'])) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [33]: merge_grouped['duvol'].mean()Out[33]: -0.023371498758114867In [34]: merge_grouped['ncskew'] = - (((merge_grouped['count'] * (merge_grouped['count'] - 1) ** (3 / 2)) * merge_grouped['residuals_cubic']) / (((merge_grouped['count'] - 1) * (merge_grouped['count'] - 2)) * merge_grouped['residuals_sq'] ** (3 / 2))) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [35]: merge_grouped['ncskew'].mean()Out[35]: -0.031025284134663118In [36]: merge_grouped['crash_risk'] = merge_crash_annual['crash_risk'] merge_grouped['crash_risk'] = np.where(merge_grouped.crash_risk >= 1, 1, 0)In [37]: merge_crash_all_grouped2 = merge_crash_all.groupby('TICKER')\ [['VOL', 'PRC']]\ .resample('1Y').mean().reset_index() merge_grouped[['VOL', 'PRC']] = merge_crash_all_grouped2[['VOL', 'PRC']] merge_grouped[['ncskew','duvol']].corr()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (421)

计算 DUVOL

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (422)

计算 NCSKEW

DUVOL 表示回报量的年均值以下部分与年均值以上部分的比例。因此,较高的 DUVOL 意味着左偏分布或更高的崩盘概率。考虑到 DUVOL 的平均值为-0.0233,我们可以得出结论,股票在指定期间内不太可能崩盘。

另一方面,NCSKEW 比较尾部的形状——即左尾比右尾更长时,股票价格往往会崩盘。预期地,NCSKEW 与 DUVOL 之间的相关性很高,确认了虽然两个测量方法通过不同方式获取相同的信息。

由于我们正在寻找能够解释股价崩盘风险的变量,本节提供了骨干分析。由于数据中既有股票又有时间序列,面板数据分析是一种合适的分析技术。

至少有三个因素促成了面板数据研究的几何增长(Hsiao 2014):

  1. 数据可用性

  2. 模型人类行为复杂性的能力,要比单一横截面或时间序列数据更强。

  3. 其挑战性的方法论

简而言之,面板数据分析结合了时间序列和横截面数据,因此比单独使用时间序列和横截面分析具有更多优势。Ullah(1998)总结了这些优势:

明显的好处是数据集更大,变量之间的变异性更大,且共线性较低,这比典型的横截面或时间序列数据要好得多。借助更多、更具信息量的数据,可以获得更可靠的估计,并在更少限制的假设下测试更复杂的行为模型。面板数据集的另一个优点是它们能够控制个体异质性……特别是,面板数据集更能研究动态行为的复杂问题。

由于我们的数据是离散类型的,逻辑面板应用程序满足了这一需求。然而,面板数据分析的库较少,而逻辑面板应用更是如此。我们将使用的库是 Python 计量经济学模型模块(pyeconometrics),它有一些高级模型,包括:

  • 固定效应逻辑回归(Logit)

  • 随机效应逻辑回归(Logit 和 Probit)

  • Tobit I(截断数据的线性回归)

注意

一个潜在的内生性问题是时间不变的遗漏变量,这需要考虑。为了控制这一点,我们将使用固定效应逻辑面板模型。

要运行逻辑面板应用程序,使用 pyeconometrics 模块,但安装此库的方式有些不同。请访问其 GitHub 仓库 获取更多信息。

警告

安装 pyeconometrics 与我们使用的一些库和模块的安装方式有些不同。为了确保你正确安装该库,请访问其 GitHub 仓库

现在让我们介绍在本分析中将使用的变量。在获取股票价格崩盘度量后,是时候讨论哪些变量在估计股票价格崩盘风险时是重要的了。表 9-2 列出了自变量。

表 9-2。用于股票价格崩盘分析的自变量

变量解释
规模(log_size公司拥有的总资产的对数。
应收账款(rect应收账款/债务人。
房产、厂房和设备(ppegt总房产、厂房和设备。
平均周转率(dturn
NCSKEW(ncskew公司特定周收益的负偏度系数,是公司特定周收益的三阶矩除以三次标准差的负数。
公司特定收益(residuals当年公司特定周收益的平均值。
总资产回报率(RoA当年资产回报率,即净利润与总资产的比率。
标准差(annual_std当年公司特定周收益的标准差。
公司特定情绪 (firm_sent)通过 PCA 获得的公司特定投资者情绪测量。

回报资产和杠杆变量是通过资产负债表数据计算的:

In [38]: bs = pd.read_csv('bs_v.3.csv') bs['Date'] = pd.to_datetime(bs.datadate, format='%Y%m%d') bs['annual_date'] = bs['Date'].dt.yearIn [39]: bs['RoA'] = bs['ni'] / bs['at'] bs['leverage'] = bs['lt'] / bs['at']In [40]: merge_grouped['annual_date'] = merge_grouped['date'].dt.year bs['TICKER'] = bs.tic del bs['tic']

下一步是获取剩余变量,合并资产负债表数据(bs)和股票相关数据(merge_crash_all_grouped):

In [41]: merge_ret_bs = pd.merge(bs, merge_grouped, on=['TICKER', 'annual_date'])In [42]: merge_ret_bs2 = merge_ret_bs.set_index('Date') merge_ret_bs2 = merge_ret_bs2.groupby('TICKER').resample('Y').mean() merge_ret_bs2.reset_index(inplace=True)In [43]: merge_ret_bs2['vol_csho_diff'] = (merge_ret_bs2.groupby('TICKER') ['VOL'].shift(-1) / merge_ret_bs2.groupby('TICKER') ['csho'].shift(-1)) merge_ret_bs2['dturn1'] = merge_ret_bs2['VOL'] / merge_ret_bs2['csho'] merge_ret_bs2['dturn'] = merge_ret_bs2['vol_csho_diff'] - \ merge_ret_bs2['dturn1']In [44]: merge_ret_bs2['p/e'] = merge_ret_bs2['PRC'] / merge_ret_bs2['ni'] merge_ret_bs2['turnover_rate'] = merge_ret_bs2['VOL'] / \ merge_ret_bs2['csho'] merge_ret_bs2['equity_share'] = merge_ret_bs2['ceq'] / \ (merge_ret_bs2['ceq'] + merge_ret_bs2['dt']) merge_ret_bs2['firm_size'] = np.log(merge_ret_bs2['at']) merge_ret_bs2['cefd'] = (((merge_ret_bs2['at'] - merge_ret_bs2['lt']) / merge_ret_bs2['csho']) - merge_ret_bs2['PRC']) / (merge_ret_bs2['at'] - merge_ret_bs2['lt']) / merge_ret_bs2['csho']In [45]: merge_ret_bs2 = merge_ret_bs2.set_index('Date') merge_ret_bs2['buying_volume'] = merge_ret_bs2['VOL'] * \ (merge_ret_bs2['PRC'] - merge_ret_bs2['BIDLO']) / \ (merge_ret_bs2['ASKHI'] - merge_ret_bs2['BIDLO']) merge_ret_bs2['selling_volume'] = merge_ret_bs2['VOL'] * \ (merge_ret_bs2['ASKHI'] - merge_ret_bs2['PRC']) / \ (merge_ret_bs2['ASKHI'] - merge_ret_bs2['BIDLO']) buying_volume = merge_ret_bs2.groupby('TICKER')['buying_volume'] \ .resample('Y').sum().reset_index() selling_volume = merge_ret_bs2.groupby('TICKER')['selling_volume'] \ .resample('Y').sum().reset_index() del buying_volume['TICKER'] del buying_volume['Date']In [46]: buy_sel_vol = pd.concat([buying_volume,selling_volume], axis=1) buy_sel_vol['bsi'] = (buy_sel_vol.buying_volume - buy_sel_vol.selling_volume) / \ (buy_sel_vol.buying_volume + buy_sel_vol.selling_volume)In [47]: merge_ret_bs2 = merge_ret_bs2.reset_index() merge_ret_bs2 = pd.merge(buy_sel_vol ,merge_ret_bs2, on=['TICKER', 'Date'])

除了公司特定情绪外,其余变量在解释股价暴跌风险方面广泛使用且非常有用。

推导出一个指数并将其用作代理在研究人员中非常流行,当难以找到合适的变量来代表一个现象时。例如,假设您认为公司特定情绪是一个包含关于股价暴跌非常有力见解的变量,但如何提出一个代表公司特定情绪的变量?为了解决这个问题,我们可以考虑所有与公司特定情绪有关的变量,然后识别关系,使用主成分分析创建一个指数。这正是我们将要做的事情。

尽管股价暴跌风险的一些众所周知的决定因素,一个被认为被忽视的重要方面是公司特定投资者情绪。可以直观地说,根据投资者对公司的看法,股价可能上涨或下跌。也就是说,如果投资者倾向于对个别股票感到乐观,他们很可能会购买该资产,从而推动股价上涨或下跌(尹和田,2017 年)。

在这方面,市盈率(P/E)、周转率(TURN)、股权份额(EQS)、封闭式基金折价(CEFD)、杠杆(LEV)、买卖量(BSI)用于识别公司特定情绪。这些变量的解释详见表格 9-3。

表格 9-3. 用于公司特定情绪的变量

变量解释
市盈率 (p/e)每股市值 / 每股收益
周转率 (turnover_rate)总交易股数 / 平均流通股数
股权份额 (equity_share)普通股
封闭式基金折价 (cefd)通过首次公开发行筹集固定金额的资产
杠杆 (leverage)长期负债总额和流动负债总额之和 / 总资产
买卖量 (bsi)买入(卖出)量是与买入(卖出)交易相关的股份数量

要正确捕捉公司特定情绪,我们需要尽可能多地提取信息,而 PCA 是一种方便的工具来完成这项任务:

In [48]: from sklearn.preprocessing import StandardScaler from sklearn.decomposition import PCAIn [49]: firm_sentiment = merge_ret_bs2[['p/e', 'turnover_rate', 'equity_share', 'cefd', 'leverage', 'bsi']] firm_sentiment = firm_sentiment.apply(lambda x: x.fillna(x.mean()), axis=0) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [50]: firm_sentiment_std = StandardScaler().fit_transform(firm_sentiment) pca = PCA(n_components=6) pca_market_sentiment = pca.fit_transform(firm_sentiment_std) print('Explained Variance Ratios per Component are:\n {}'\ .format(pca.explained_variance_ratio_)) Explained Variance Ratios per Component are: [0.35828322 0.2752777 0.15343653 0.12206041 0.06681776 0.02412438]In [51]: loadings_1 = pd.DataFrame(pca.components_.T * np.sqrt(pca.explained_variance_), columns=['PC1', 'PC2', 'PC3', 'PC4', 'PC5', 'PC6'], index=firm_sentiment.columns) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) loadings_1Out[51]: PC1 PC2 PC3 PC4 PC5 PC6 p/e -0.250786 0.326182 0.911665 0.056323 0.000583 0.021730 turnover_rate -0.101554 0.854432 -0.197381 0.201749 0.428911 -0.008421 equity_share -0.913620 -0.162406 -0.133783 0.224513 -0.031672 0.271443 cefd 0.639570 -0.118671 0.038422 0.754467 -0.100176 0.014146 leverage 0.917298 0.098311 0.068633 -0.264369 0.089224 0.265335 bsi 0.006731 0.878526 -0.173740 -0.044127 -0.446735 0.022520In [52]: df_loading1 = pd.DataFrame(loadings_1.mean(axis=1)) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) df_loading1Out[52]: 0 p/e 0.177616 turnover_rate 0.196289 equity_share -0.124254 cefd 0.204626 leverage 0.195739 bsi 0.040529In [53]: firm_sentiment = pd.DataFrame(np.dot(pca_market_sentiment, np.array(df_loading1))) merge_ret_bs2['firm_sent'] = firm_sentiment

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (423)

用均值填充缺失值

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (424)

计算载荷

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (425)

取负载的横截面平均值

在获得了特征的负载之后,横截面平均分量的结果如下:

SENT i , t = 0 . 177 P/E i,t + 0 . 196 TURN i,t - 0 . 124 EQS i,t + 0 . 204 CEFD i,t + 0 . 195 LEV i,t + 0 . 040 BSI i,t

结果表明,除了股权份额外,公司特定情绪受到所有变量的正面影响。此外,杠杆和周转率对公司特定情绪的影响最大。

我们还有一步要走:解释逻辑面板数据分析。在此之前,应定义自变量和因变量,并使用必要的库来完成:

In [54]: merge_ret_bs2['log_size'] = np.log(merge_ret_bs2['at'])In [55]: merge_ret_bs2.set_index(['TICKER', 'Date'], inplace=True)In [56]: X = (merge_ret_bs2[['log_size', 'rect', 'ppegt', 'dturn', 'ncskew', 'residuals', 'RoA', 'annual_std', 'firm_sent']]).shift(1) X['neg_outliers'] = merge_ret_bs2['neg_outliers']

逻辑面板数据分析向我们展示了哪些变量与从椭圆包络算法获得的neg_outliers(股价崩盘度量)具有统计上显著的关系。结果表明,除了ppegt残差之外,所有其他变量在常规置信区间内具有统计学显著性。具体而言,log_sizedturnfirm_sentannual_std确实会触发崩盘。

结果表明,公司特定投资者情绪指数的系数为正,在金融上具有重要意义,并且在 1%水平上具有统计学显著性。文献表明,在情绪高涨时,在乐观预期的压力下,管理者倾向于加速好消息的发布,但隐瞒坏消息以维持积极的环境(Bergman 和 Roychowdhury 2008)。因此,结果表明情绪与崩盘风险之间存在正相关关系。

鉴于所有这些变量与neg_outliers之间表现出强有力的统计上显著的关系,我们能够进行可靠的预测分析:

In [57]: from pyeconometrics.panel_discrete_models import FixedEffectPanelModel from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_scoreIn [58]: FE_ML = FixedEffectPanelModel() FE_ML.fit(X, 'neg_outliers') FE_ML.summary() ====================================================================== ========== Dep. Variable: neg_outliers Pseudo R-squ.: 0.09611 Model: Panel Fixed Effects Logit Log-Likelihood: -83.035 Method: MLE LL-Null: -91.864 No. Observations: 193 LLR p-value: 0.061 Df Model: 9 Converged: True ====================================================================== coef std err t P>|t| [95.0% Conf. Int.] ---------------------------------------------------------------------- _cons -2.5897 1.085 -2.387 0.008 -4.716 -0.464 log_size 0.1908 0.089 2.155 0.016 0.017 0.364 rect -0.0000 0.000 -4.508 0.000 -0.000 -0.000 ppegt -0.0000 0.000 -0.650 0.258 -0.000 0.000 dturn 0.0003 0.000 8.848 0.000 0.000 0.000 ncskew -0.2156 0.089 -2.420 0.008 -0.390 -0.041 residuals -0.3843 1.711 -0.225 0.411 -3.737 2.968 RoA 1.4897 1.061 1.404 0.080 -0.590 3.569 annual_std 1.9252 0.547 3.517 0.000 0.852 2.998 firm_sent 0.6847 0.151 4.541 0.000 0.389 0.980 ----------------------------------------------------------------------

为了比较,这次将因变量替换为crash_risk,这也是离散类型。通过这种比较,我们能够比较模型的优良程度以及可能的预测能力。鉴于我们模型的优良度量,R 2,以neg_outliers作为因变量的模型具有更高的解释力。然而,请注意,R 2并不是唯一用于比较模型优良度的指标。由于这个讨论超出了本书的范围,我不会详细讨论。

除此之外,显而易见的是,某些估计系数的符号在这两个模型之间是不同的。例如,根据文献,公司情绪(firm_sent)应该具有正符号,因为一旦投资者情绪激增,隐瞒坏消息的行为就会增加,导致股价崩盘风险上升。这些重要观察结果被包含在先前的模型中,该模型包含了我们新引入的因变量neg_outliers。具有neg_outliers的模型产生了更好且更可靠的预测:

In [59]: del X['neg_outliers'] X['crash_risk'] = merge_ret_bs2['crash_risk']In [60]: FE_crash = FixedEffectPanelModel() FE_crash.fit(X, 'crash_risk') FE_crash.summary() ====================================================================== Dep. Variable: crash_risk Pseudo R-squ.: 0.05324 Model: Panel Fixed Effects Logit Log-Likelihood: -55.640 Method: MLE LL-Null: -58.769 No. Observations: 193 LLR p-value: 0.793 Df Model: 9 Converged: True ====================================================================== coef std err t P>|t| [95.0% Conf. Int.] ---------------------------------------------------------------------- _cons -3.1859 1.154 -2.762 0.003 -5.447 -0.925 log_size 0.2012 0.094 2.134 0.016 0.016 0.386 rect -0.0000 0.000 -1.861 0.031 -0.000 0.000 ppegt -0.0000 0.000 -0.638 0.262 -0.000 0.000 dturn 0.0001 0.000 2.882 0.002 0.000 0.000 ncskew 0.3840 0.114 3.367 0.000 0.160 0.608 residuals 3.3976 2.062 1.648 0.050 -0.644 7.439 RoA 2.5096 1.258 1.994 0.023 0.043 4.976 annual_std 2.4094 0.657 3.668 0.000 1.122 3.697 firm_sent -0.0041 0.164 -0.025 0.490 -0.326 0.318 ----------------------------------------------------------------------

在本章中,我们学*了如何使用机器学*检测股票价格崩盘。使用 MCD 方法,检测到了市场调整后具体股票价格回报中的负异常,并将其定义为股票价格崩盘风险指标。结果表明,情绪与崩盘风险之间存在正向关系,表明在高情绪时期,面对乐观预期的压力,管理者倾向于隐瞒不良消息,而这些积累的不良消息导致大幅度下跌。

另外,还获得了其他股价崩盘指标,即 NCSKEW、DUVOL 和崩盘风险。我们在分析中使用了 NCSKEW 和崩盘风险作为自变量和因变量。

逻辑面板分析显示,带有neg_outliers的模型估计系数与文献一致,使其比带有crash_risk的模型更有用,同时提高了其预测分析的可靠性。

在下一章中,我们将介绍金融圈中一个全新且备受欢迎的话题:合成数据生成及其在风险管理中的应用。

本章引用的文章和书籍:

  • Bae, Kee‐Hong, Chanwoo Lim, 和 KC John Wei. 2006. “公司治理与全球股票市场条件偏斜。” 商业杂志 79 (6): 2999-3028.

  • Bergman, Nittai K., 和 Sugata Roychowdhury. 2008. “投资者情绪与企业披露。” 会计研究期刊 46 (5): 1057-1083.

  • Bleck, Alexander, 和 Xuewen Liu. 2007. “市场透明度与会计制度。” 会计研究期刊 45 (2): 229-256.

  • Chen, Joseph, Harrison Hong, 和 Jeremy C. Stein. 2001. “预测崩盘:交易量、过去回报和股票价格条件偏斜。” 金融经济学期刊 61 (3): 345-381.

  • Hubert, Mia, Michiel Debruyne, 和 Peter J. Rousseeuw. 2018. “最小协方差行列式及其扩展。” 2018. Wiley 跨学科统计评论 10 (3): e1421.

  • Hutton, Amy P., Alan J. Marcus, 和 Hassan Tehranian. 2009. “不透明的财务报告、R2 和崩盘风险。” 金融经济学期刊 94 (1): 67-86.

  • Hsiao, Cheng. 2014. 面板数据分析。剑桥大学出版社。

  • Kim J. B., Li Y., 和 Zhang L. 2011. “企业避税与股票价格崩盘风险:公司层面分析。” 金融经济学期刊 100 (3): 639-662.

  • Kim, Jeong‐Bon, 和 Liandong Zhang. 2014. “财务报告不透明度与预期崩盘风险:基于隐含波动率的证据。” 现代会计研究 31 (3): 851-875.

  • Jin, Li, 和 Stewart C. Myers. 2006. “全球范围内的 R2:新理论和新测试。” 金融经济学期刊 79 (2): 257-292.

  • Finch, Holmes. 2012. “通过离群检测方法分布变量。” 心理学前沿 (3): 211。

  • Wolfensohn, James. 1999. “印度公司治理条款的批判性研究。” 金融时报 25 (4). Retrieved from https://oreil.ly/EnLaQ.

  • Shleifer, Andrei, 和 Robert W. Vishny. 1997. “公司治理概览。” 金融学杂志 52 (2): 737-783.

  • Ullah, Aman,编。 1998. 应用经济统计手册。博卡拉顿:CRC 出版社。

  • Yin, Yugang, 和 Rongfu Tian. 2017. “投资者情绪、财务报告质量和股价崩盘风险:空头销售限制的作用。” 新兴市场金融与贸易 53 (3): 493-510。

¹ 所有正特征值的对称矩阵称为正定矩阵

数据不一定要根植于真实世界才具有价值:它可以被制造出来,并且可以填补或难以获取的空白。

Ahuja (2020)

由于对保密性和数据需求增加的关注,合成数据生成在金融领域越来越受到关注。因此,为什么不使用合成数据来代替真实数据,只要它模拟了所需的统计特性呢?听起来很吸引人,不是吗?合成数据生成是本章的一部分;另一部分专注于另一个不被重视但相当重要和有趣的主题:隐马尔可夫模型(HMM)。你可能想问:合成数据和 HMM 之间的共同点是什么?嗯,我们可以从 HMM 生成合成数据——这是本章的一个目标。另一个目标是介绍这两个重要主题,因为它们经常在机器学*中使用。

金融数据的保密性、敏感性和成本极大地限制了其使用。这反过来阻碍了金融领域有用知识的进展和传播。合成数据解决了这些缺点,并帮助研究人员和从业者进行分析并传播结果。

合成数据是通过模仿真实数据的统计特性生成的数据。尽管有一种观念认为数据必须以其原始形式建模,但是从真实数据生成合成数据并不是唯一的创建方式(Patki, Wedge, 和 Veeramachaneni, 2016)。实际上,我们可以通过三种方式生成合成数据:

  • 合成数据可以从真实数据中生成。这个过程的工作流程从获取真实数据开始,然后进行建模以揭示数据的分布,最后从这个现有模型中抽取合成数据。

  • 合成数据可以通过模型或知识获得。一般来说,这种类型的合成数据生成可以通过使用现有的模型或研究人员的知识来应用。

  • 混合过程包括前两个步骤,因为有时只有一部分数据变得可用,这部分真实数据用于生成合成数据,合成数据的另一部分可以从模型中获得。

我们很快将看到如何应用这些技术来生成合成数据。根据其性质,合成数据生成过程在隐私和实用性之间有着不可妥协的权衡。确切地说,从未公开的真实数据生成合成数据会产生高实用性。然而,合成数据生成的实用性在很大程度上取决于真实公共数据的去识别和聚合。合成数据生成的实用性取决于成功的建模或分析师的专业知识。

在数据生成过程中,隐私-效用权衡是数据生成过程的上下文。这在图 10-1 中有所体现。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (426)

图 10-1. 隐私-效用权衡

正如您可以想象的那样,有各种工具可以用来衡量合成数据的有效性;然而,我们将把注意力限制在四种常用的方法上:KL 散度,可区分性,ROC 曲线,以及比较主要统计数据,如均值、中位数等。由于 KL 散度和 ROC 曲线分别在第八章和第六章中已讨论过,我们将跳过这些内容,直接开始介绍可区分性方法。

可区分性,顾名思义,试图通过使用一个分类模型来区分真实和合成记录,如果是真实数据则分配为 1,否则为 0。如果输出接* 1,则预测记录为真实数据,否则预测为合成数据,使用倾向得分(El Emam 2020)。

另一种方法简单而强大,基于比较真实数据和合成数据的主要统计数据。根据所采用的模型,可以比较真实数据和合成数据的均值(或其他统计数据),以了解合成数据模仿真实数据的程度。

让我们讨论一下合成数据生成的优缺点:

优点

数据的可用性增加

合成数据生成使我们具备了一个强大的工具,可以克服获取真实数据的困难,这可能既昂贵又专有。

改善分析技能

作为真实数据的良好代理,合成数据可以在各种分析过程中使用,从而提升我们对特定主题的理解。此外,合成数据可以用于标记,为高度准确的分析铺平道路。

处理常见的统计问题

合成数据生成可以减轻由真实数据引起的问题。真实数据可能存在问题,如缺失值、异常值等,这些问题会严重影响模型的性能。合成数据为处理这些统计问题提供了一个工具,因此我们可能会获得改善的建模性能。

缺点

无法保护机密性

由于网络攻击,合成数据可能成为私人信息泄露的来源。例如,客户的凭证可以通过反向工程获得。

质量关注

在合成数据生成过程中需要考虑两个重要因素:研究人员的能力和数据的特性。这两点决定了合成数据生成的质量过程。如果这些因素缺乏,合成数据的质量可能会较低。

首先从真实数据生成合成数据,然后从模型中生成。我们将使用fetch_california_housing的真实数据生成合成数据,同时在此过程中使用 CTGAN 库(CTGANSynthesizer)。CTGAN 库基于生成对抗网络(GANs),使我们能够生成与原始数据高度相似的合成数据。在生成合成数据时,训练步骤的数量由epoch参数控制,这使我们能够在短时间内获得合成数据:

In [1]: from sklearn.datasets import fetch_california_housing ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) import pandas as pd import numpy as np import matplotlib. pyplot as plt import yfinance as yf import datetime import warnings warnings.filterwarnings('ignore')In [2]: X, y = fetch_california_housing(return_X_y=True) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [3]: import numpy as np california_housing=np.column_stack([X, y]) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) california_housing_df=pd.DataFrame(california_housing)In [4]: from ctgan import CTGANSynthesizer ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) ctgan = CTGANSynthesizer(epochs=10) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png) ctgan.fit(california_housing_df) synt_sample = ctgan.sample(len(california_housing_df)) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/6.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (427)

导入sklearn中的fetch_california_housing数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (428)

fetch_california_housing生成自变量和因变量

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (429)

使用stack函数堆叠两个数组

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (430)

导入CTGANSynthesizer用于生成合成数据

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (431)

使用epoch为 10 初始化从CTGANSynthesizer生成合成数据的过程

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (432)

生成样本

生成合成数据后,可以通过描述性统计检查合成数据的相似性。描述性统计一直非常方便,但我们还有另一个工具,来自 Synthetic Data Vault(SDV)的evaluate包。此函数的输出将是一个介于 0 和 1 之间的数字,表示两个表的相似程度,0 表示最差,1 表示最佳。此外,生成过程的结果可以被可视化(在生成的图表 10-2 和 10-3 中),并与真实数据进行比较,以便充分理解合成数据是否很好地代表了真实数据:

In [5]: california_housing_df.describe()Out[5]: 0 1 2 3 4 \ count 20640.000000 20640.000000 20640.000000 20640.000000 20640.000000 mean 3.870671 28.639486 5.429000 1.096675 1425.476744 std 1.899822 12.585558 2.474173 0.473911 1132.462122 min 0.499900 1.000000 0.846154 0.333333 3.000000 25% 2.563400 18.000000 4.440716 1.006079 787.000000 50% 3.534800 29.000000 5.229129 1.048780 1166.000000 75% 4.743250 37.000000 6.052381 1.099526 1725.000000 max 15.000100 52.000000 141.909091 34.066667 35682.000000 5 6 7 8 count 20640.000000 20640.000000 20640.000000 20640.000000 mean 3.070655 35.631861 -119.569704 2.068558 std 10.386050 2.135952 2.003532 1.153956 min 0.692308 32.540000 -124.350000 0.149990 25% 2.429741 33.930000 -121.800000 1.196000 50% 2.818116 34.260000 -118.490000 1.797000 75% 3.282261 37.710000 -118.010000 2.647250 max 1243.333333 41.950000 -114.310000 5.000010In [6]: synt_sample.describe()Out[6]: 0 1 2 3 4 \ count 20640.000000 20640.000000 20640.000000 20640.000000 20640.000000 mean 4.819246 28.954316 6.191938 1.172562 2679.408170 std 3.023684 13.650675 2.237810 0.402990 2127.606868 min -0.068225 -2.927976 0.877387 -0.144332 -468.985777 25% 2.627803 19.113346 4.779587 0.957408 1148.179104 50% 4.217247 29.798105 5.779768 1.062072 2021.181784 75% 6.254332 38.144114 7.058157 1.285233 3666.957652 max 19.815551 54.219486 15.639807 3.262196 12548.941245 5 6 7 8 count 20640.000000 20640.000000 20640.000000 20640.000000 mean 3.388233 36.371957 -119.957959 2.584699 std 1.035668 2.411460 2.306550 1.305122 min 0.650323 32.234033 -125.836387 0.212203 25% 2.651633 34.081107 -122.010873 1.579294 50% 3.280092 36.677974 -119.606385 2.334144 75% 3.994524 38.023437 -118.080271 3.456931 max 7.026720 43.131795 -113.530352 5.395162In [7]: from sdv.evaluation import evaluate ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) evaluate(synt_sample, california_housing_df) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)Out[7]: 0.4773175572768998In [8]: from table_evaluator import TableEvaluator ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/3.png) table_evaluator = TableEvaluator(california_housing_df, synt_sample) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/4.png) table_evaluator.visual_evaluation() ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/5.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (433)

导入evaluate包以评估合成和真实数据的相似性

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (434)

运行evaluate包对我们的真实数据和合成数据进行评估

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (435)

导入TableEvaluator以视觉检查合成数据和真实数据之间的相似性

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (436)

使用真实数据和合成数据运行TableEvaluator

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (437)

使用visual_evaluation包进行视觉分析

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (438)

图 10-2. 合成数据生成的评估-1

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (439)

图 10-3. 合成数据生成的评估-2

图 10-2 和 10-3 允许我们通过均值、标准偏差和热图直观比较真实数据和合成数据的性能。尽管evaluation有许多不同的工具,但现在值得我们把注意力集中在这些工具上。

正如你所见,从真实数据生成合成数据并不难。现在让我们走一遍根据模型生成合成数据的过程。我将使用sklearn,这是一个用于机器学*应用的瑞士军刀库,既可以用于分类模型也可以用于回归模型。make_regression对于生成回归模型的合成数据非常有用。同样,make_classification生成用于运行分类模型的合成数据。以下代码还生成了 Figure10-4:

In [9]: from sklearn.datasets import make_regression ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) import matplotlib.pyplot as plt from matplotlib import cmIn [10]: X, y = make_regression(n_samples=1000, n_features=3, noise=0.2, random_state=123) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) plt.scatter(X[:, 0], X[:, 1], alpha= 0.3, cmap='Greys', c=y)In [11]: plt.figure(figsize=(18, 18)) k = 0 for i in range(0, 10): X, y = make_regression(n_samples=100, n_features=3, noise=i, random_state=123) k+=1 plt.subplot(5, 2, k) profit_margin_orange = np.asarray([20, 35, 40]) plt.scatter(X[:, 0], X[:, 1], alpha=0.3, cmap=cm.Greys, c=y) plt.title('Synthetic Data with Different Noises: ' + str(i)) plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (440)

导入make_regression

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (441)

用 1000 个样本、3 个特征和噪声的标准偏差生成回归的合成数据

Figure10-4 显示了在生成合成数据时变化噪声的效果。正如预期的那样,随着标准偏差的增加,noise参数会越来越大。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (442)

图 10-4. 使用不同噪声生成合成数据

那么为分类生成合成数据如何呢?听起来很容易。我们将按照与回归非常相似的过程。这次,我们将使用make_classification包。在生成合成数据之后,将通过散点图观察不同类别数量的影响(Figure10-5):

In [12]: from sklearn.datasets import make_classification ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [13]: plt.figure(figsize=(18, 18)) k = 0 for i in range(2, 6): X, y = make_classification(n_samples=100, n_features=4, n_classes=i, n_redundant=0, n_informative=4, random_state=123) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) k+=1 plt.subplot(2, 2, k) plt.scatter(X[: ,0], X[:, 1], alpha=0.8, cmap='gray', c=y) plt.title('Synthetic Data with Different Classes: ' + str(i)) plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (443)

导入make_classification

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (444)

用 100 个样本、4 个特征和 4 个信息特征生成分类的合成数据

Figure10-5 展示了在合成数据生成中具有不同类别的效果;在这种情况下,使用类别 2 到 5 生成了合成数据。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (445)

图 10-5. 使用不同类别生成合成数据

从无监督学*中生成合成数据也是可能的。make_blobs 是一个可用于此目的的包。因此,我们将生成合成数据,并观察不同聚类数对合成数据的影响,并生成 Figure10-6:

In [14]: from sklearn.datasets import make_blobs ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)In [15]: X, y = make_blobs(n_samples=100, centers=2, n_features=2, random_state=0) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png)In [16]: plt.figure(figsize=(18, 18)) k = 0 for i in range(2, 6): X, y = make_blobs(n_samples=100, centers=i, n_features=2, random_state=0) k += 1 plt.subplot(2, 2, k) my_scatter_plot = plt.scatter(X[:, 0], X[:, 1], alpha=0.3, cmap='gray', c=y) plt.title('Synthetic Data with Different Clusters: ' + str(i)) plt.show()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (446)

sklearn 导入 make_blobs

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (447)

生成具有 100 个样本、2 个中心和 2 个特征的合成数据

Figure10-6 显示了具有不同聚类的合成数据的外观。

到目前为止,我们已经学*了如何使用真实数据和模型生成合成数据,同时使用监督学*(回归和分类)和无监督学*。从现在开始,我们将探索 HMM 并学*如何使用它。从财务角度来看,我们将通过因子投资来完成这项任务。因子投资并不是一个新话题,但在著名的法马-弗伦奇三因子模型(法马和弗伦奇,1993 年)之后,它变得越来越吸引人。我们将看到 HMM 对识别经济中不同状态的影响,并在投资策略中加以考虑。最后,我们将能够通过夏普比率比较基于法马-弗伦奇三因子模型和 HMM 的因子投资的效果。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (448)

图 10-6. 使用不同噪声生成合成数据

HMM 给出了我们对顺序数据的概率分布,这是由隐藏状态的马尔可夫过程建模的。HMM 使我们能够估计从一个状态到另一个状态的概率转移。

为了说明,让我们考虑股市,其中股票上涨、下跌或保持不变。随机选择一个状态——比如上涨。下一个状态可能是上涨、下跌或保持不变。在这种情况下,状态被认为是一个隐藏状态,因为我们不能确定市场下一个状态将是什么。

总体上,HMM 有两个基本假设:第一,所有观察值仅依赖于当前状态,并且在条件上独立于其他变量;第二,转移概率是均匀的,并且仅依赖于当前隐藏状态(Wang, Lin 和 Mikhelson,2020 年)。

法玛和法 rench(1993)提出的模型为扩展 CAPM 的进一步研究铺平了道路。该模型提出全新的解释变量来解释股票回报的变化。该模型的三个因子包括市场风险( R m - R f ),小市值与大市值之间的差异(SMB)和高市值与低市值之间的差异(HML)。我们将在下文简要讨论这些因子,因为我们将在模型中使用它们。

( R m - R f )基本上是市场投资组合的回报减去无风险利率,这是一个由政府发行的 T 票或类似资产代理的假设利率。

SMB 是大小效应的代理。大小效应是用来解释公司财务中的几个现象的重要变量。它通过不同的变量如总资产的对数来表示。法玛-法 rench 通过计算小市值公司和大市值公司之间的回报来考虑大小效应。

第三个因子是 HML,它代表高账面市值比和低账面市值比之间的回报差异,比较公司的账面价值与市场价值。

实证研究表明,较小的 SMB,较高的 HML 以及较小的( R m - R f )会提升股票回报率。从理论上讲,在运行法玛-法 rench 三因子模型之前识别状态将提升模型的性能。为了验证这一点在真实数据中是否成立,让我们运行包含或不包含 HMM 的因子投资模型。

数据来自肯尼斯·法 rench 数据库。如下所示,包含在数据中的变量有:DateMkt-RFSMBHMLRF。结果显示,所有变量都是数值型的,除了如预期的日期。为了节省处理模型的时间,数据已经被修剪到从 2000-01-03 开始:

In [17]: ff = pd.read_csv('FF3.csv', skiprows=4) ff = ff.rename(columns={'Unnamed: 0': 'Date'}) ff = ff.iloc[:-1] ff.head()Out[17]: Date Mkt-RF SMB HML RF 0 19260701 0.10 -0.24 -0.28 0.009 1 19260702 0.45 -0.32 -0.08 0.009 2 19260706 0.17 0.27 -0.35 0.009 3 19260707 0.09 -0.59 0.03 0.009 4 19260708 0.21 -0.36 0.15 0.009In [18]: ff.info() <class 'pandas.core.frame.DataFrame'> RangeIndex: 24978 entries, 0 to 24977 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Date 24978 non-null object 1 Mkt-RF 24978 non-null float64 2 SMB 24978 non-null float64 3 HML 24978 non-null float64 4 RF 24978 non-null float64 dtypes: float64(4), object(1) memory usage: 975.8+ KBIn [19]: ff['Date'] = pd.to_datetime(ff['Date']) ff.set_index('Date', inplace=True) ff_trim = ff.loc['2000-01-01':]In [20]: ff_trim.head()Out[20]: Mkt-RF SMB HML RF Date 2000-01-03 -0.71 0.61 -1.40 0.021 2000-01-04 -4.06 0.01 2.06 0.021 2000-01-05 -0.09 0.18 0.19 0.021 2000-01-06 -0.73 -0.42 1.27 0.021 2000-01-07 3.21 -0.49 -1.42 0.021

好吧,我们已经得到了解释股票回报背后动态的变量,但是这是哪一种股票回报?它应该是代表经济总体状况的回报。这种类型变量的一个潜在候选者是标普 500 交易所交易基金(ETF)。

请注意

ETF 是一种特殊类型的投资基金和交易所交易产品,跟踪行业、商品等。SPDR 标普 500 ETF(SPY)是一个非常著名的例子,跟踪标普 500 指数。一些其他的 ETF 包括:

  • 全球前 2000 名 ETF 股票(VXUS)

  • 能源选择部门 SPDR 基金(XLE)

  • iShares Edge MSCI Min Vol USA ETF(USMV)

  • iShares 晨星大型市值 ETF(JKD)

我们将从 2000-01-03 到 2021-04-30 收集每日的 SPY 收盘价,以匹配我们研究的时期。获取数据后,我们将 ff_trimSP_ETF 合并,这样就得到了包括返回率和波动率的数据,隐藏状态也是在这些数据上确定的:

In [21]: ticker = 'SPY' start = datetime.datetime(2000, 1, 3) end = datetime.datetime(2021, 4, 30) SP_ETF = yf.download(ticker, start, end, interval='1d').Close [*********************100%***********************] 1 of 1 completedIn [22]: ff_merge = pd.merge(ff_trim, SP_ETF, how='inner', on='Date')In [23]: SP = pd.DataFrame() SP['Close']= ff_merge['Close']In [24]: SP['return'] = (SP['Close'] / SP['Close'].shift(1))-1![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (449)

计算 SPY 的收益率

假设经济有三种状态:上升、下降和恒定。在此基础上,我们使用完全协方差运行 HMM,表示独立组件,并进行了 100 次迭代(n_iter)。以下代码块展示了如何应用高斯 HMM 并预测隐藏状态:

In [25]: from hmmlearn import hmmIn [26]: hmm_model = hmm.GaussianHMM(n_components=3, covariance_type="full", n_iter=100)In [27]: hmm_model.fit(np.array(SP['return'].dropna()).reshape(-1, 1))![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) hmm_predict = hmm_model.predict(np.array(SP['return'].dropna()) .reshape(-1, 1))![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) df_hmm = pd.DataFrame(hmm_predict)In [28]: ret_merged = pd.concat([df_hmm,SP['return'].dropna().reset_index()], axis=1) ret_merged.drop('Date',axis=1, inplace=True) ret_merged.rename(columns={0:'states'}, inplace=True) ret_merged.dropna().head()Out[28]: states return 0 1 -0.039106 1 1 0.001789 2 1 -0.016071 3 1 0.058076 4 2 0.003431

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (450)

使用返回数据拟合高斯 HMM

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (451)

给定返回数据,预测隐藏状态

在预测隐藏状态之后,返回数据与隐藏状态连接,这样我们就能看到哪个返回值属于哪个状态。

现在让我们检查运行高斯 HMM 分析后得到的结果。在以下代码块中,我们计算了不同状态的均值和标准差,还估计了协方差、初始概率和转移矩阵:

In [29]: ret_merged['states'].value_counts()Out[29]: 0 3014 2 2092 1 258 Name: states, dtype: int64In [30]: state_means = [] state_std = [] for i in range(3): state_means.append(ret_merged[ret_merged.states == i]['return'] .mean()) state_std.append(ret_merged[ret_merged.states == i]['return'] .std()) print('State Means are: {:.4f}'.format(state_means)) print('State Standard Deviations are: {:.4f}'.format(state_std)) State Means are: [0.0009956956923795376, -0.0018371952883552139, -0. 0005000714110860054] State Standard Deviations are: [0.006006540155737148, 0. 03598912028897813, 0.01372712345328388]In [31]: print(f'HMM means\n {hmm_model.means_}') print(f'HMM covariances\n {hmm_model.covars_}') print(f'HMM transition matrix\n {hmm_model.transmat_}') print(f'HMM initial probability\n {hmm_model.startprob_}') HMM means [[ 0.00100365] [-0.002317 ] [-0.00036613]] HMM covariances [[[3.85162047e-05]] [[1.26647594e-03]] [[1.82565269e-04]]] HMM transition matrix [[9.80443302e-01 1.20922866e-06 1.95554886e-02] [1.73050704e-08 9.51104459e-01 4.88955238e-02] [2.67975578e-02 5.91734590e-03 9.67285096e-01]] HMM initial probability [0.00000000e+000 1.00000000e+000 2.98271922e-120]

每个状态的观察数量见 Table10-1。

表 10-1. 每个状态的观察结果

状态观察次数收益均值协方差
030140.00103.8482e-05
12092-0.00231.2643e-05
2258-0.00031.8256e-05

我们假设经济有三种状态,但这一假设基于理论。然而,如果我们要确保,有一个强大且方便的工具可以应用:Elbow Analysis。在运行高斯 HMM 后,我们得到了似然结果,如果没有改进的空间——即,似然值变得相对稳定——这就是我们可以停止分析的时刻。根据以下结果(连同相应的 Figure10-7),三个组件是一个不错的选择:

In [32]: sp_ret = SP['return'].dropna().values.reshape(-1,1) n_components = np.arange(1, 10) clusters = [hmm.GaussianHMM(n_components=n, covariance_type="full").fit(sp_ret) for n in n_components] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) plt.plot(n_components, [m.score(np.array(SP['return'].dropna())\ .reshape(-1,1)) for m in clusters]) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/2.png) plt.title('Optimum Number of States') plt.xlabel('n_components') plt.ylabel('Log Likelihood')In [33]: hmm_model = hmm.GaussianHMM(n_components=3, covariance_type="full", random_state=123).fit(sp_ret) hidden_states = hmm_model.predict(sp_ret)

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (452)

基于高斯 HMM 通过列表推导创建十个聚类

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (453)

根据组件数量计算对数似然

Figure10-7 显示了每个状态的似然值。很容易观察到,在第三个组件之后,曲线变得更加平坦。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (454)

图 10-7. 高斯 HMM 谱图

现在让我们可视化通过高斯 HMM 获得的状态,并生成图 10-8:

In [34]: from matplotlib.dates import YearLocator, MonthLocator from matplotlib import cmIn [35]: df_sp_ret = SP['return'].dropna() hmm_model = hmm.GaussianHMM(n_components=3, covariance_type="full", random_state=123).fit(sp_ret) hidden_states = hmm_model.predict(sp_ret) fig, axs = plt.subplots(hmm_model.n_components, sharex=True, sharey=True, figsize=(12, 9)) colors = cm.gray(np.linspace(0, 0.7, hmm_model.n_components)) for i, (ax, color) in enumerate(zip(axs, colors)): mask = hidden_states == i ax.plot_date(df_sp_ret.index.values[mask], df_sp_ret.values[mask], ".-", c=color) ax.set_title("Hidden state {}".format(i + 1), fontsize=16) ax.xaxis.set_minor_locator(MonthLocator()) plt.tight_layout()

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (455)

图 10-8. 高斯 HMM 状态

图 10-8 展示了隐藏状态的行为,正如预期的那样,这些状态的分布与彼此完全不同,突显了识别状态的重要性。

鉴于所得状态,SPY 的回报率不同,这是我们预期的。在所有这些准备工作之后,我们可以继续并使用和不使用高斯 HMM 运行法玛-弗雷奇三因子模型。建模后,我们将计算夏普比率,以确定哪一个具有更好的风险调整回报。使用高斯 HMM 进行的分析显示了接* 0.0981 的夏普比率:

In [36]: ret_merged.groupby('states')['return'].mean()Out[36]: states 0 0.000996 1 -0.001837 2 -0.000500 Name: return, dtype: float64In [37]: ff_merge['return'] = ff_merge['Close'].pct_change() ff_merge.dropna(inplace=True)In [38]: split = int(len(ff_merge) * 0.9) train_ff= ff_merge.iloc[:split].dropna() test_ff = ff_merge.iloc[split:].dropna()In [39]: hmm_model = hmm.GaussianHMM(n_components=3, covariance_type="full", n_iter=100, init_params="")In [40]: predictions = [] for i in range(len(test_ff)): hmm_model.fit(train_ff) adjustment = np.dot(hmm_model.transmat_, hmm_model.means_) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/ml-fin-rsk-mgt-py/img/1.png) predictions.append(test_ff.iloc[i] + adjustment[0]) predictions = pd.DataFrame(predictions)In [41]: std_dev = predictions['return'].std() sharpe = predictions['return'].mean() / std_dev print('Sharpe ratio with HMM is {:.4f}'.format(sharpe))Out[41]: Sharpe ratio with HMM is 0.0981

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (456)

基于转移矩阵的调整

运行法玛-弗雷奇三因子模型的传统方法是应用线性回归,以下代码块就是这样做的。在运行线性回归之后,我们可以进行预测,然后计算夏普比率。我们会发现,与高斯 HMM 相比,线性回归产生了较低的夏普比率(0.0589):

In [42]: import statsmodels.api as smIn [43]: Y = train_ff['return'] X = train_ff[['Mkt-RF', 'SMB', 'HML']]In [44]: model = sm.OLS(Y, X) ff_ols = model.fit() print(ff_ols.summary()) OLS Regression Results================================================================================Dep. Variable: return R-squared (uncentered): 0.962Model: OLS Adj. R-squared (uncentered): 0.962Method: Least Squares F-statistic: 4.072e+04Date: Tue, 30 Nov 2021 Prob (F-statistic): 0.00Time: 00:05:02 Log-Likelihood: 22347.No. Observations: 4827 AIC: -4.469e+04Df Residuals: 4824 BIC: -4.467e+04Df Model: 3Covariance Type: nonrobust============================================================================== coef std err t P>|t| [0.025 0.975]------------------------------------------------------------------------------Mkt-RF 0.0098 2.82e-05 348.173 0.000 0.010 0.010SMB -0.0017 5.71e-05 -29.005 0.000 -0.002 -0.002HML -6.584e-05 5.21e-05 -1.264 0.206 -0.000 3.63e-05==============================================================================Omnibus: 1326.960 Durbin-Watson: 2.717Prob(Omnibus): 0.000 Jarque-Bera (JB): 80241.345Skew: 0.433 Prob(JB): 0.00Kurtosis: 22.955 Cond. No. 2.16==============================================================================Notes:[1] R² is computed without centering (uncentered) since the model does notcontain a constant.[2] Standard Errors assume that the covariance matrix of the errors iscorrectly specified.In [45]: ff_pred = ff_ols.predict(test_ff[["Mkt-RF", "SMB", "HML"]]) ff_pred.head()Out[45]: Date 2019-03-14 -0.000340 2019-03-15 0.005178 2019-03-18 0.004273 2019-03-19 -0.000194 2019-03-20 -0.003795 dtype: float64In [46]: std_dev = ff_pred.std() sharpe = ff_pred.mean() / std_dev print('Sharpe ratio with FF 3 factor model is {:.4f}'.format(sharpe))Out[46]: Sharpe ratio with FF 3 factor model is 0.0589

这一结果表明,高斯 HMM 提供了更好的风险调整回报,使其在资产配置等分析中非常有用。

以下分析试图展示如果基于未见数据预测指数回报的状态,可以用于未来分析的回测情况:

In [47]: split = int(len(SP['return']) * 0.9) train_ret_SP = SP['return'].iloc[split:].dropna() test_ret_SP = SP['return'].iloc[:split].dropna()In [48]: hmm_model = hmm.GaussianHMM(n_components=3, covariance_type="full", n_iter=100) hmm_model.fit(np.array(train_ret_SP).reshape(-1, 1)) hmm_predict_vol = hmm_model.predict(np.array(test_ret_SP) .reshape(-1, 1)) pd.DataFrame(hmm_predict_vol).value_counts()Out[48]: 0 4447 1 282 2 98 dtype: int64

正如我们讨论过的,HMM 为我们进一步扩展分析提供了有力的方式,以获取更可靠和准确的结果。在结束本章之前,展示使用高斯 HMM 生成合成数据的过程是值得的。为此,我们首先应定义我们的初始参数。这些参数是:初始概率(startprob)、转移矩阵(transmat)、均值(means)和协方差(covars)。定义了参数之后,我们可以运行高斯 HMM,并应用随机抽样过程,以得到我们需要的观测数,本例中为 1,000。以下代码生成了图表 10-9 和 10-10:

In [49]: startprob = hmm_model.startprob_ transmat = hmm_model.transmat_ means = hmm_model.means_ covars = hmm_model.covars_In [50]: syn_hmm = hmm.GaussianHMM(n_components=3, covariance_type="full")In [51]: syn_hmm.startprob_ = startprob syn_hmm.transmat_ = transmat syn_hmm.means_ = means syn_hmm.covars_ = covarsIn [52]: syn_data, _ = syn_hmm.sample(n_samples=1000)In [53]: plt.hist(syn_data) plt.show()In [54]: plt.plot(syn_data, "--") plt.show()

基于合成数据的分布和线性图可见于图表 10-9 和 10-10。由于我们从高斯 HMM 中得到了足够大的样本量,我们观察到数据呈正态分布。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (457)

图 10-9. 高斯 HMM 合成数据直方图

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (458)

图 10-10. 高斯 HMM 合成数据线性图

在这一最后一章中,我们讨论了两个相对新颖但颇具前景的主题。合成数据生成使我们能够在没有真实数据或违反保密情况下进行分析,因此对于从业者在这些情况下是一个救命稻草。在本章的第二部分,我们研究了高斯 HMM 及其在金融分析中的有用性,然后使用高斯 HMM 生成了合成数据。

我们看到高斯 HMM 如何帮助我们在投资组合配置中获得更好的结果,但值得注意的是,这并不是我们可以应用 HMM 的唯一领域。相反,研究人员利用这种方法的领域还有许多,可以肯定的是将会有更多的应用出现。

本章引用的文章和书籍:

  • Ahuja, Ankana. 2020. “合成数据的前景”。 《金融时报》https://oreil.ly/qphEN.

  • El Emam, Khaled, Lucy Mosquera, 和 Richard Hoptroff. 2020. 实用的合成数据生成:平衡隐私与数据广泛可用性。 Sebastopol: O’Reilly.

  • Fama, Eugene F., 和 Kenneth R. French. 1993. “股票和债券收益的共同风险因素。” 《金融经济学杂志》 33 (3): 56.

  • Patki, Neha, Roy Wedge, 和 Kalyan Veeramachaneni. 2016. “合成数据保险库。” 在 2016 年 IEEE 国际数据科学与高级分析会议(DSAA)中,399-410 页.

  • Wang, Matthew, Yi-Hong Lin, 和 Ilya Mikhelson. 2020. “隐马尔可夫模型的制度切换因子投资。” 《风险与金融管理杂志》 13 (12): 311.

本书试图展示如何将机器学*和深度学*模型整合起来解决金融问题。当然,这并不意味着本书包含了所有必要步骤以在行业中部署模型,但我尽力聚焦于我认为读者最感兴趣的主题。

AI 的最新发展表明,几乎所有传统的金融模型在预测性能方面都被 AI 模型超越,我相信采用这些模型并在行业中获得改进的预测性能将帮助金融从业者做出更好的决策。

然而,尽管这些发展和围绕 AI 的最*炒作,AI 模型的部署水平远未达到应有的水平。这些模型的不透明性显然是首要原因。不过,不断进行的大幅改进努力使得获得更可解释的 AI 模型成为可能,正如普拉多所主张的那样,ML 是否不透明取决于使用它的人,而不是 ML 算法本身。

因此,在我看来,长期使用参数模型以及对范式转变的抵制是慢慢而不情愿地接受 AI 模型的主要原因。

希望这本书为接受 AI 模型铺平道路,并提供一个顺畅方便的过渡以开始使用它们。

Python-机器学习金融风险管理-全- - 绝不原创的飞龙 - 博客园 (2024)

References

Top Articles
Latest Posts
Article information

Author: Rubie Ullrich

Last Updated:

Views: 6144

Rating: 4.1 / 5 (72 voted)

Reviews: 95% of readers found this page helpful

Author information

Name: Rubie Ullrich

Birthday: 1998-02-02

Address: 743 Stoltenberg Center, Genovevaville, NJ 59925-3119

Phone: +2202978377583

Job: Administration Engineer

Hobby: Surfing, Sailing, Listening to music, Web surfing, Kitesurfing, Geocaching, Backpacking

Introduction: My name is Rubie Ullrich, I am a enthusiastic, perfect, tender, vivacious, talented, famous, delightful person who loves writing and wants to share my knowledge and understanding with you.