金融中的深度学习-全-

金融中的深度学习(全)

原文:annas-archive.org/md5/1d10c05a97f5e0702eda83b77eec256b

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

学习永不使人心智衰竭。

列奥纳多·达·芬奇

机器学习和深度学习*年来彻底改变了金融行业。不同的学习模型非常适合数据丰富且持续增长的世界。数据是新的黄金,其价值随着适当的分析导致关键业务决策而不断上升,这些决策是经济变革的驱动力。

量化基金的崛起证明了数据科学在交易世界中有很多可供提供的内容。在基本和技术交易员之后,新一代宇宙领袖正在崛起。这些是依靠具有极其复杂操作的基于机器的算法的量化交易员,旨在预测和超越市场。

本书详细介绍了金融深度学习主题。

为什么选择这本书?

我的职业生涯专注于研究交易策略、技术和与金融世界相关的所有事物。多年来,我熟悉了几种可能增加交易框架价值的算法模型。在本书中,我讨论了不同的学习模型及其在交易世界中的应用,重点介绍了深度学习和神经网络。我的主要目标是以使每个人都能理解其功能的方式来涵盖它们。

凭借客观性,机器可以在许多方面执行和检测操作,比人类做得更好。这意味着您将学习如何使用 Python 创建所需的算法来执行这些操作之一。

如前所述,我的目标是全面介绍深度学习在金融中的应用。我通过讨论广泛的主题来实现这一目标,包括数据科学、交易、机器和深度学习模型,以及交易中的强化学习应用。

本书从概述数据科学在金融界中的角色开始。然后深入探讨了统计学、数学和 Python 等知识要求,然后专注于如何在交易策略中使用机器和深度学习。

适合读者

本书适合广泛的读者群体,包括金融界的专业人士和学术界人士、数据科学家、量化交易员以及任何级别的金融学生。它为想要理解和应用这些强大技术的任何人提供了全面的介绍。

本书假设您具备 Python 编程(专业 Python 用户会发现代码非常简单)和金融交易方面的基本背景知识。我采用清晰简单的方法,重点关注关键概念,以便您理解每个想法的目的。

本书中使用的约定

本书中使用以下排版约定:

斜体

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

Constant width

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

Constant width bold

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

Constant width italic

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

提示

此元素表示提示或建议。

注意

此元素表示一般提示。

警告

此元素表示警告或注意事项。

使用代码示例

可以在https://github.com/sofienkaabar/deep-learning-for-finance下载补充材料(代码示例、练习等)。

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

本书旨在帮助您完成工作。通常情况下,如果本书提供示例代码,您可以在自己的程序和文档中使用它。除非您复制了代码的大部分,否则不需要联系我们请求许可。例如,编写一个使用本书多个代码片段的程序不需要许可。出售或分发来自 O’Reilly 书籍的示例需要许可。引用本书并引用示例代码回答问题不需要许可。将本书的大量示例代码合并到产品文档中需要许可。

我们感谢您的使用,但通常不需要署名,署名通常包括标题、作者、出版商和 ISBN。例如:“深度学习在金融领域 by Sofien Kaabar (O’Reilly)。版权所有 2024 Sofien Kaabar,978-1-098-14839-3。”

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

O’Reilly Online Learning

注意

40 多年来,O’Reilly Media已经提供技术和商业培训、知识和洞察,以帮助公司取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习*台分享他们的知识和专长。O’Reilly 的在线学习*台为您提供按需访问实时培训课程、深入学习路径、交互式编码环境以及来自 O’Reilly 和 200 多家其他出版商的大量文本和视频内容。更多信息,请访问https://oreilly.com

如何联系我们

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

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-889-8969(美国或加拿大)

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

  • 707-829-0104(传真)

  • support@oreilly.com

  • https://www.oreilly.com/about/contact.html

我们为这本书创建了一个网页,其中列出了勘误、示例以及任何额外信息。您可以访问此页面,网址为https://oreil.ly/deep-learning-for-finance

获取关于我们的书籍和课程的新闻和信息,请访问https://oreilly.com

在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media

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

在 YouTube 上观看我们:https://youtube.com/oreillymedia

致谢

没有父母的支持,一切都不会一样,这也是我必须承认他们对这本书直接和间接的影响的原因。

我还要感谢编辑 Michelle Smith 和 Corbin Collins,以及制作编辑 Elizabeth Faerm,感谢他们的持续支持、出色的工作以及耐心。同样,我要感谢所有参与这本书制作的 O’Reilly 的每一个人。

此外,我特别感谢那些出色的技术审阅者,他们对本书的巨大贡献产生了深远影响。他们在使这本书易读、有用和直接方面做出了重要贡献。我再也找不到比他们更合适的审阅人选了。

最后,我深深感谢您,读者,花时间阅读我的作品并信任我的研究。希望您能觉得它有用。

第一章:介绍数据科学和交易

开始学习复杂主题的最佳方式是将其分解为较小的部分并先理解这些部分。要理解面向金融的深度学习,需要了解数据科学和金融市场。

本章奠定了彻底理解数据科学及其用途所需的基础,以及理解金融市场以及如何利用数据科学从事交易和预测的基础。

在本章结束时,您应该知道数据科学是什么,它的应用是什么,以及您如何在金融中利用它来提取价值。

理解数据

要理解数据科学领域,首先要了解数据的类型和结构。毕竟,这个广阔领域的名称中的第一个词就是数据。那么,数据是什么?更重要的是,您能用它做什么?

数据 在其最简单和最纯粹的形式中是各种类型(数值、文本、布尔值等)的原始信息集合。

收集数据的最终目的是决策制定。这是一个复杂的过程,涵盖从收集和处理数据到解释数据并利用结果做出决策的各个环节。

让我们举个使用数据做决策的例子。假设您的投资组合由五种不同的等权重股息支付股票组成,详见表 1-1。

表 1-1. 股票及其股息收益率

股票 股息收益率
A 5.20%
B 3.99%
C 4.12%
D 6.94%
E 5.55%

股息 是从公司利润中支付给股东的款项。股息收益率 是分配给股东的货币单位金额与公司当前股价的比率。

分析这些数据可以帮助您了解从投资组合中获得的*均股息收益率。*均值基本上是总和除以数量,它快速展示了投资组合的整体股息收益率情况:

Average dividend yield = 5.20%+3.99%+4.12%+6.94%+5.55% 5 = 5 . 16 %

因此,您的投资组合的*均股息收益率为 5.16%。这些信息可以帮助您将*均股息收益率与其他投资组合进行比较,以便您知道是否需要进行任何调整。

您可以计算的另一个指标是投资组合中持有的股票数量。这可能是构建分散化墙壁的第一个信息砖块。尽管这两个信息(*均股息收益率和投资组合中的股票数量)非常简单,但复杂的数据分析始于简单的指标,有时可能不需要复杂的模型来正确解释情况。

您在前面示例中计算的两个指标称为*均(或均值)和计数(或元素数量)。它们是所谓的描述统计的一部分,详见第三章,描述统计也是数据科学的一部分。

让我们再来看一个数据分析的例子,用于推断性的目的。假设你已经计算了两种商品之间的年度相关性测量,并且想预测下一个年度相关性是正还是负。表格 1-2 中有详细的计算信息。

表格 1-2. 年度相关性测量

年份 相关性
2015 正相关
2016 正相关
2017 正相关
2018 负相关
2019 正相关
2020 正相关
2021 正相关
2022 正相关
2023 正相关
注释

相关性 是两个时间序列之间线性依赖关系的度量。正相关 通常意味着两个时间序列*均而言朝着相同方向移动,而负相关 则通常意味着两个时间序列*均而言朝着相反方向移动。相关性在第三章中有讨论。

根据表格 1-2,这两种商品之间的历史相关性大多数情况下(即 88%)是正相关的。根据历史观察,可以说下一个相关性测量的概率为 88%为正相关。这也意味着下一个相关性测量为负的概率为 12%:

E ( Positive correlation ) = 8 9 = 88 . 88 %

这是另一个基本的例子,展示如何使用数据从观察中推断并做出决策。当然,在这里的假设是历史结果会完全反映未来结果,在现实生活中这是不太可能的,但有时候,预测未来唯一的依据就是过去的经验。

在讨论数据科学之前,让我们回顾一下可以使用的数据类型,并将其分成不同的组:

数值数据

这种数据由反映某种类型信息的数字组成,这些信息可以在规律或不规律的间隔时间内收集。例如市场数据(OHLC,¹ 成交量,价差等)和财务报表数据(资产,收入,成本等)。

分类数据

分类数据是可以用名称或标签将其组织成组或类别的数据。它是定性而非定量的。例如,患者的血型就是一种分类数据。另一个例子是从人群中取样的眼睛颜色。

文本数据

文本数据*年来随着自然语言处理(NLP)的发展而日益增长。机器学习模型利用文本数据进行翻译、解释和分析文本的情感。

视觉数据

图像和视频也被视为数据,你可以处理并转化它们成有价值的信息。例如,卷积神经网络(CNN)是一种算法(见第八章),它可以通过标签识别和分类照片(例如,将猫照片标记为猫)。

音频数据

音频数据非常有价值,可以帮助节省转录时间。例如,可以使用音频算法创建字幕并自动创建字幕。还可以创建模型,通过音频的语调和音量来解释说话者的情感。

数据科学是一个跨学科领域,试图使用不同的技术和模型从数据中提取智能和结论,无论这些技术和模型是简单还是复杂的。数据科学过程除了仅仅分析数据外还包括许多步骤。以下是这些步骤的总结:

  1. 数据收集:这个过程涉及从可靠和准确的来源获取数据。计算机科学中广为人知的一句话通常认为是乔治·菲切尔的功劳:“垃圾进,垃圾出”,它指的是需要有质量可靠的数据,才能进行适当的分析。基本上,如果你有不准确或有缺陷的数据,那么所有的过程都将是无效的。

  2. 数据预处理:偶尔,您获取的数据可能处于原始形式,需要进行预处理和清理,以便数据科学模型能够使用它。例如,删除不必要的数据,添加缺失值,或消除无效和重复的数据可能是预处理步骤的一部分。其他更复杂的例子可能包括数据的归一化去噪。这一步的目的是使数据准备好进行分析。

  3. 数据探索:在这一步骤中,进行基本的统计研究以发现数据中的趋势和其他特征。数据探索的一个例子是计算数据的*均值。

  4. 数据可视化:这是上一步骤的一个重要补充。它包括创建直方图和热图等可视化图表,以帮助识别模式和趋势,并促进解释。

  5. 数据分析:这是数据科学过程的主要焦点。这是你使用不同的学习模型对数据进行拟合(训练),以便根据给定的参数来解释和预测未来的结果。

  6. 数据解释:这一步涉及理解数据科学模型呈现的反馈和结论。优化也可能是这一步的一部分;在这些情况下,我们会回到第五步,使用更新的参数再次运行模型,然后重新解释和评估性能。

让我们以 Python 中的一个简单例子来应用数据科学过程的步骤。假设你想分析和预测 VIX(波动率指数),这是一个代表标准普尔 500 股票市场指数的隐含波动率的波动率时间序列指标。VIX 自 1993 年以来就已经存在,并由芝加哥期权交易所(CBOE)发布。

注意

数据科学过程中还有一个隐藏的步骤,我称之为零步骤,它发生在你形成一个想法的时候,这个想法指导了应该启动哪个过程。毕竟,如果没有首先有动机,你不会应用这个过程。例如,相信通货膨胀数据可能推动某些大宗商品的回报是一个想法和启动探索数据以证明这一假设的动机。

由于它旨在衡量股市中的恐惧或不确定性水*,VIX 经常被称为恐慌指数。它是通过对标准普尔 500 指数期权定价计算的百分比得出的。较高的 VIX 值与更大的市场动荡和不确定性相关,而较低的值则与更大的稳定性相关。

第一步是数据收集,在这种情况下可以使用 Python 自动化。以下代码块连接到圣路易斯联邦储备银行的网站,并下载了 1990 年 1 月 1 日至 2023 年 1 月 23 日期间的 VIX 历史数据(第六章专门介绍了 Python 和编写代码;目前,你无需理解代码,因为这不是目标):

# Importing the required library
import pandas_datareader as pdr
# Setting the beginning and end of the historical data
start_date = '1990-01-01'
end_date   = '2023-01-23'
# Creating a dataframe and downloading the VIX data
vix = pdr.DataReader('VIXCLS', 'fred', start_date, end_date)
# Printing the latest five observations of the dataframe
print(vix.tail())

代码使用pandas库导入DataReader函数,该函数从各种来源在线获取历史数据。DataReader函数以数据名称作为第一个参数,其次是来源和日期。print(vix.tail())的输出显示在表 1-3 中。

表 1-3. print(vix.tail())的输出

日期 VIXCLS
2023-01-17 19.36
2023-01-01 20.34
2023-01-19 20.52
2023-01-20 19.85
2023-01-23 19.81

我们继续进行第二步:数据预处理。我将这部分分为检查无效数据和转换数据,以便准备使用。处理时间序列数据时,特别是下载的时间序列数据,有时可能会遇到nan值。NaN 代表不是数字nan值由于缺失、无效或损坏的数据而出现。

你可以用多种方式处理nan值。为了本例子,让我们使用最简单的方法处理这些无效值,即消除它们。但首先,让我们编写一些简单的代码,输出数据框中nan值的数量,以便你了解要删除多少个值:

# Calculating the number of nan values
count_nan = vix['VIXCLS'].isnull().sum()
# Printing the result
print('Number of nan values in the VIX dataframe: ' + str(count_nan))

代码使用isnull()函数并对其进行求和,得出nan值的数量。前一段代码片段的输出如下所示:

Number of nan values in the VIX dataframe: 292

现在你已经知道要删除多少行了,可以使用以下代码删除无效的行:

# Dropping the nan values from the rows
vix = vix.dropna()

第二部分第二步是转换数据。数据科学模型通常需要稳定数据,即具有稳定统计属性(如均值)的数据。

注意

稳定性 的概念以及所需的统计指标在第三章中有详细讨论。目前,您只需要知道的是,在使用数据科学模型时,您可能需要将原始数据转换为稳定数据。

要将 VIX 数据转换为*稳数据,您可以简单地从一个值相对于前一个值的差异中取得。以下代码片段将 VIX 数据框转换为理论上暗示的*稳数据:²

# Taking the differences in an attempt to make the data stationary
vix = vix.diff(periods = 1, axis = 0)
# Dropping the first value of the dataframe
vix = vix.iloc[1: , :]

第三步是数据探索,它完全是关于理解您面前的数据,从统计学角度来说。由于您将在第三章中详细了解统计指标,因此我将限制讨论仅计算数据集的*均值。

*均值 简单地是可以代表数据集中其他值的值,如果它们要选出一位领袖。它是数值总和除以其数量。*均值是描述性统计学中最简单的统计量,也是最常用的统计量。以下公式显示了一组值的*均值的数学表示:

x ¯ = 1 n i=1 i x i

您可以轻松地计算数据集的*均值如下:

# Calculating the mean of the dataset
mean = vix["VIXCLS"].mean()
# Printing the result
print('The mean of the dataset = ' + str(mean))

前面代码片段的输出如下所示:

The mean of the dataset = 0.0003

下一步是数据可视化,这在很大程度上被认为是有趣的步骤。让我们绘制 VIX 随时间变化的差异值。以下代码片段绘制了图 1-1 中显示的 VIX 数据:

# Importing the required library
import matplotlib.pyplot as plt
# Plotting the latest 250 observations in black with a label
plt.plot(vix[–250:], color = 'black', linewidth = 1.5, 
         label = 'Change in VIX')
# Plotting a red dashed horizontal line that is equal to mean
plt.axhline(y = mean, color = 'red', linestyle = 'dashed')
# Calling a grid to facilitate the visual component
plt.grid()
# Calling the legend function so it appears with the chart
plt.legend()
# Calling the plot
plt.show()

图 1-1. 自 2022 年初以来 VIX 的变化

步骤 5 和 6,数据分析和数据解释,是本书中您将深入学习的内容,因此现在让我们跳过它们,专注于数据科学的入门部分。

在继续之前,让我们回顾一下无效或缺失数据的问题。有时数据是不完整的,存在缺失单元格。尽管这可能会影响算法的预测能力,但不应该阻止您继续分析,因为有快速的修复方法可以帮助减少空单元格的负面影响。例如,考虑表 1-4。

表 1-4. 季度 GDP

季度 GDP
2020 年第一季度 0.9%
2020 年第二季度 1.2%
2020 年第三季度 0.5%
2020 年第四季度 0.4%
2021 年第一季度 #N/A
2021 年第二季度 1.0%
2021 年第三季度 1.1%
2021 年第四季度 0.6%

表中包含一个虚构国家的季度国内生产总值(GDP)³。请注意表格缺少 2021 年第一季度的数值。解决此问题有三种基本方法:

删除包含缺失值的单元格。

这是 VIX 示例中使用的技术。它简单地认为时间戳不存在。这是最简单的修复方法。

假设缺失单元格等于前一个单元格。

这也是一个简单的修正方法,其目的是*滑数据,而不是完全忽略问题。

计算空值周围单元格的*均值或中位数。

此技术将*滑处理推进一步,并假设缺失值等于前后数值的*均值。此外,它可以是几个过去观察的*均值。

数据科学涵盖一系列数学和统计概念,并需要对机器学习算法有深入理解。本书详细讨论了这些概念,但也以易于理解的方式来使技术和非技术读者受益。许多模型被假定为神秘盒子,这其中有一些真实性,但数据科学家的工作是在解释结果之前理解这些模型,这有助于理解模型的局限性。

本书使用 Python 作为主要编程语言来创建算法。正如提到的,第六章介绍了 Python 及其用于操作和分析数据所需的知识,但也为创建不同模型提供了基础,如您所见,这些模型比您想象的简单得多。

在进入下一节之前,让我们先看看数据存储的概念。毕竟,数据很有价值,但您需要将其存储在可以轻松获取和分析的地方。

数据存储 是指用于将数据存储和组织以供未来分析的技术和领域。数据以多种格式存储,如 CSV 和 XLSX。其他类型的格式可能包括 XML、JSON,甚至 JPEG 用于图像。选择格式取决于数据的结构和组织。

数据可以存储在云端或本地,具体取决于您的存储能力和成本。例如,您可能希望将您的历史性一分钟苹果股票数据存储在云端,而不是在 CSV 文件中,以节省本地计算机的空间。

在 Python 中处理时间序列时,您主要会处理两种数据存储类型:数组和数据帧。让我们来看看它们是什么:

数组

数组 用于存储相同类型的元素。通常,同类数据集(如数字)最适合存储在数组中。

数据帧

数据帧 是一个二维结构,可以容纳各种类型的数据。它可以与具有列和行的表格进行比较。

一般情况下,当需要高效存储同类数据集合时应使用数组。当处理异类数据或需要以表格方式编辑和分析数据时,应使用数据帧。

注意

数据科学在不断发展。新的存储方法正在不断开发,旨在提高效率并增加容量和速度。

理解数据科学

数据科学在技术和进步中发挥着重要作用。算法依赖于从数据科学工具提供的信息来执行其任务。但算法是什么?

算法是一组有序的程序,旨在完成特定活动或解决特定问题。算法可以简单到一个硬币翻转,也可以复杂到 Risch 算法。⁴

让我们来看一个非常简单的算法,它会更新一个图表*台所需的金融数据。该算法将按照以下步骤执行:

  1. 连接服务器和在线数据提供商。

  2. 复制带有最新时间戳的金融数据。

  3. 将数据粘贴到图表*台。

  4. 返回步骤 1 并重新执行整个过程。

这就是算法的本质:执行一组有限或无限目标的特定指令。

注意

在前一节讨论的六个数据科学步骤也可以被视为一种算法。

交易策略也是算法,因为它们具有明确的进出场规则。市场套利是一个交易策略的例子。

套利是一种旨在从不同交易所上同一资产的价格差异中获利的交易策略。这些价格差异是由套利者通过他们的买卖活动消除的异常情况。考虑一个股票在不同国家的交易所 A 和 B 上交易(为了简单起见,这两个国家使用相同的货币)。自然地,该股票在两个交易所上的价格必须相同。当这种条件不成立时,套利者们就会出来捕猎。

他们在便宜的交易所购买股票,然后立即在更昂贵的交易所出售,从而确保几乎无风险的利润。这些操作以闪电般的速度执行,因为价格差异由于套利者的强大和速度而不会长时间存在。为了澄清,这里有一个例子:

  • 交易所 A 的股票价格 = $10.00。

  • 交易所 B 的股票价格 = $10.50。

在这种情况下,套利者的算法将执行以下操作:

  1. 在交易所 A 以$10.00 购买股票。

  2. 立即在交易所 B 以$10.50 出售股票。

  3. 收取差额($0.50),并重复直到差距被消除。

注意

交易和执行算法可以非常复杂,并且需要专业知识和某种市场优势。

在这一点上,你应该意识到数据科学的两个主要用途,即数据解释和数据预测:

数据解释

也通常被称为商业智能或简称数据智能。部署算法的目的是理解数据的何时和如何。

数据预测

也常称为预测分析或简称预测。部署算法的目的是理解数据的接下来会发生什么。

在金融市场中使用学习算法的主要目的是预测未来资产价格,以便做出基于信息的交易决策,以高于随机的成功率实现资本增值。我在本书中讨论了许多简单和复杂的学习算法。这些学习算法或模型可以按以下方式分类:

监督学习

监督学习算法是需要带标签数据的模型。这意味着你必须提供数据,让模型在这些过去值上进行训练,并理解隐藏的模式,以便在遇到新数据时能够提供未来的输出。监督学习的例子包括线性回归算法和随机森林模型。

无监督学习

无监督学习算法是不需要带标签数据的模型。这意味着它们可以使用无标签数据完成工作,因为它们设计用于自行发现隐藏的模式。示例包括聚类算法和主成分分析(PCA)。

强化学习

强化学习算法是一种完全不需要数据的模型,因为它们可以发现并自主学习环境。与监督学习和无监督学习模型相比,强化学习模型通过从环境获得的奖励系统反馈来获取知识。通常情况下,这应用于代理与环境互动并学习最大化随时间奖励的行为的情况,因此可能不是时间序列回归的首选算法。另一方面,它可用于开发可应用于时间序列数据以生成预测的策略。

正如您可能已经注意到的那样,本书的标题是金融深度学习。这意味着除了涵盖其他学习模型外,本书将花费相当大的篇幅讨论用于时间序列预测的深度学习模型。深度学习主要围绕神经网络的使用展开,这是一种在第八章中深入讨论的算法。

深层监督学习模型(例如深度神经网络)能够学习数据的分层表示,因为它们包含许多层,每一层在不同抽象级别上提取特征。因此,深度模型可以学习隐藏且复杂的模式,而这些模式可能对浅层(非深度)模型来说很难学习。

另一方面,浅层监督学习模型(如线性回归)对于学习复杂的非线性关系能力有限。但它们需要较少的计算工作,因此速度更快。

当今数据科学算法几乎无处不在,不仅限于金融领域。一些应用包括以下几个方面:

  • 业务分析:使用数据分析优化定价、预测客户流失或改进营销活动

  • 医疗保健:通过深入分析患者数据,改善患者结果、寻找创新疗法或降低医疗成本

  • 体育:分析体育数据以提升团队表现、球员选拔或投注

  • 研究:分析数据以支持科学研究、证明理论或获得新知识

当有人谈论数据科学应用时,了解数据科学家的工作非常有帮助。数据科学家必须评估和理解复杂数据,以获取见解并为决策提供指导。这通常包括开发统计模型、应用机器学习技术和数据可视化。他们支持数据驱动解决方案的实施,并向利益相关者通报他们的结果。

注意

数据科学家与数据工程师有所不同。数据科学家关注数据的解释和分析,而数据工程师则关注收集、存储和分析数据所需的工具和基础设施。

到现在为止,你应该理解了开始数据科学所需的一切。让我们介绍本书的第二个主要主题:金融市场。

金融市场和交易简介

本书的目标是以实际操作的方式介绍不同学习模型的应用,以预测金融时间序列数据。因此,理解交易和金融市场运作的坚实知识至关重要。

金融市场是人们可以交易股票、债券和货币等金融工具的地方。买卖行为称为 交易。购买金融工具的主要但不是唯一目的是资本增值。买方相信工具的价值大于其价格;因此,买方购买股票(多头)并在认为当前价格等于当前价值时出售。相反,交易员如果认为工具价格会下降,也可以赚钱。这个过程被称为 卖空,在期货和外汇(FX)等某些市场上很常见。

卖空的过程涉及从第三方借入金融工具,在市场上卖出,并在回归给第三方之前买回。理想情况下,当你预计工具价格下跌时,你会以较低的成本(价格下降后)买回它,并以市场价格归还给第三方,从而赚取差价。以下示例进一步解释了这些概念:

多头(买入)头寸示例

一位交易员预期微软股票价格未来几年会因技术法规改进而上涨,从而增加收益。他们以每股 250 美元的价格买入一定数量的股票,并计划在 500 美元时卖出。因此,交易员在微软股票上持有多头(也称为 看涨)。

空头(卖出)头寸示例

一名交易员预计洛克希德·马丁股票价格将在接下来的几天内下跌,因为技术策略发出了信号。他们以 450 美元的价格卖空了一些股票,并打算在 410 美元的价格买回来。因此,这名交易员在洛克希德·马丁股票上持有空头(也称为看空)。

注意

上涨的市场称为牛市。源自词语bull和公牛的攻击性本质,看涨与乐观、狂喜和贪婪有关。

下跌的市场称为熊市。源自词语bear及其防御性本质,看跌与悲观、恐慌和恐惧有关。

金融工具可以以其原始形式(现货)或衍生形式存在。衍生品是交易员用来以特定方式交易市场的产品。例如,远期期货合约是一种衍生合约,其中买方锁定资产的价格以便在以后的某个时间购买。

另一种衍生品是期权。期权是在未来以支付保费(期权价格)的方式购买特定资产的权利,而非义务。当买方希望购买基础股票时,他们行使他们的期权;否则,他们可能让期权到期。

交易活动也可能出现为了对冲目的,而不仅仅是投机。其中一个例子是法国航空公司(法国主要航空公司)通过购买石油期货对冲其业务运营。购买石油期货可以保护法国航空公司免受可能损害其主要运营(航空业务)的上涨油价。从使用燃料驱动飞机导致的成本上涨中获得的收益可以抵消期货的成本。这使得航空公司可以专注于其主要业务。这整个过程被称为对冲

另一个例子是,假设一家航空公司预计在接下来的六个月内将消耗一定量的燃料,但对该期间油价的潜在增长感到担忧。为了防范这种价格风险,航空公司可以进入期货合约,以在未来某个日期以固定价格购买石油。

如果在那段时间内石油价格上涨,航空公司仍然可以以较低的固定价格购买石油,这是在期货合约中约定的。如果石油价格下跌,航空公司将有义务支付较高的固定价格,但较低的市场价格可以抵消这一成本。

通过这种方式,航空公司可以减少石油市场价格波动的风险,并稳定其燃料成本。这可以帮助航空公司更好地管理其预算并预测未来的收入。正如您所看到的,其目的不是通过交易操作获得财务收益,而是通过锁定已知的石油价格来稳定其成本。

典型地,根据它们的类型,金融工具被分组为资产类别:

股票市场

股票市场是公司发行股票以筹集业务资金的交易场所(无论是电子的还是实体的)。当人们购买一家公司的股票时,他们成为该公司的部分所有者,并根据公司的政策可能有资格获得股息。根据股票的种类,他们还可能获得在董事会会议上投票的权利。

固定收益

政府和企业可以在固定收益市场借钱。当一个人购买债券时,实际上是向借款人出借资金,借款人同意偿还贷款及利息。根据借款人的信用状况和当前利率,债券的价值可能会上升或下降。

货币

外汇市场,也被称为货币市场,是人们可以购买和出售各种货币的地方。一个国家的货币价值可以根据多种因素的变化而上升或下降,包括经济状况、利率和国家的政治稳定性。

大宗商品

农产品、黄金、石油等具有工业或其他用途的实物资产被称为大宗商品。它们通常是从全球经济趋势中获取利润的手段,同时也是对抗通胀的一种形式。

替代投资

在金融世界中,像房地产、私募股权基金和对冲基金这样的非传统投资被称为替代资产类别。这些资产类别有可能提供比传统资产更高的回报,并且提供多样化的好处,但它们通常流动性较差,评估可能更为困难。

至关重要的是,每种资产类别都有独特的特性和不同的风险水*,因此投资者在投资任何这些资产之前都应该做好功课。

金融市场允许企业和政府筹集运营所需的资金。它们还允许投资者通过投机和投资于有趣的机会赚取利润。交易活动为市场提供流动性。市场越流动,交易就越容易、成本也越低。但市场到底是如何运作的?是什么导致价格的波动呢?

市场微观结构是研究金融市场证券交易的学科。它探讨交易的运作方式以及交易者、投资者和市场做市商的行为。市场微观结构研究的目标是理解价格形成以及影响交易成本的变量。

订单流、流动性、市场效率和价格发现只是市场微观结构研究涵盖的众多主题之一。此外,这项研究还探讨了包括限价单、市价单和算法交易在内的各种交易技术如何影响市场动态。流动性可能是市场微观结构中最重要的概念之一。它描述了一种资产可以在不显著改变其价格的情况下买入或卖出的容易程度。流动性可能会因不同的金融工具和时间而异。它可能会受到多种变量的影响,包括交易量和波动性。

最后,我想讨论市场微观结构的另一个重要领域:价格发现。这指的是在市场中设置价格的方法。价格可以受到订单流、市场做市商活动和各种交易方法的影响。

想象一下,你想买入两只股票的大量股票:股票 A 和股票 B。股票 A 非常流动,而股票 B 非常不流动。如果你想在股票 A 上执行买单,你可能会以所需的市场价格获得成交,几乎没有影响。然而,对于股票 B,你可能会得到更差的价格,因为没有足够的卖家愿意以你期望的买入价格出售。因此,随着你的订单需求增加,价格上涨以匹配卖家的价格,因此,你将以更高(更差)的价格买入。这就是流动性对你的交易可能产生的影响。

数据科学在金融中的应用

让我们开始窥探金融数据科学的主要领域。每个领域都有其需要简单和复杂解决方案的挑战和问题。金融也不例外。*年来,数据科学在改善金融世界(从企业世界到市场世界)的应用大幅增加。让我们讨论其中一些领域:

预测市场走向

在金融时间序列上使用数据科学的目的是发现历史市场数据中的模式、趋势和关系,以便预测未来市场走势。

金融欺诈检测

可以使用数据科学模型检查金融交易中的模式和异常,试图发现可能的欺诈行为。利用数据科学阻止金融欺诈的一种方法是检查信用卡交易数据,寻找不寻常或可疑的支出模式,例如短时间内进行大量小额购买或从同一商店频繁或大额购买。

风险管理

数据科学可以用来分析金融数据,发现投资组合的潜在风险。这可能涉及使用统计建模、机器学习和人工智能等方法分析大量历史数据,以发现可用于预测风险因素的模式和趋势。

信用评分

数据科学可以用于检查金融数据和信用历史,预测个人或公司的信用价值,并做出贷款决策。利用财务数据,如收入和信用历史,来预测一个人的信用价值是应用数据科学进行信用评分研究的一个例子。这可能涉及使用统计建模和机器学习等技术来开发一个预测模型,该模型可以使用许多指标,如先前的信用表现、收入和工作历史,来评估一个人偿还贷款的可能性。

自然语言处理

为了做出更好的判断,NLP 分析和从非结构化的金融数据中提取见解,如新闻文章、报告和社交媒体帖子。NLP 利用文本的情感提取可能来自市场参与者和专家意图和感受的交易机会。NLP 属于情感分析领域(借助机器学习的帮助)。

总结

数据科学领域每天都在不断增长,不断推出新技术和模型来改善数据解释。本章提供了关于数据科学的简单介绍以及你在金融领域如何使用它所需知道的内容。

接下来的三章介绍了统计学、概率论和数学方面的知识,这些知识在尝试理解数据科学模型时可能会用到。尽管本书的目的是以实践方式介绍使用 Python 创建和应用不同模型,但了解你正在处理的内容而不是盲目地将其应用于数据是有帮助的。

如果你需要 Python 复习,请参阅第六章,这是一个基础介绍。它为接下来的内容打下了基础。你不需要成为 Python 专家,但你必须理解代码及其所指的内容,特别是如何调试和检测代码中的错误。

¹ OHLC 指的是市场数据的四个基本部分:开盘价、最高价、最低价和收盘价。

² 我说“暗示”是因为*稳性必须通过你将在第三章中看到的统计检验来验证。目前,假设是对数据进行差分可以得到*稳的时间序列。

³ GDP 的测量在第十二章中有更详细的讨论。

⁴ Risch 算法是一种用于寻找反导数的不定积分技术,你将在第四章中看到这个概念。

第二章:深度学习的基本概率方法

技术的兴起和普及使得每个人都能够运用机器学习和深度学习算法进行数据分析和优化。但不幸的是,许多用户并不了解不同学习模型的基础知识。这让机器学习对他们来说成了一个谜盒,这是一场灾难的前兆。

理解概率、统计和数学的基本概念对于理解和掌握数据以及创建旨在解释和预测数据的模型至关重要。本章介绍了直接或间接与算法相关的概率基础知识。请注意,你不太可能在日常生活中使用这些概率概念,但了解一些算法假设的来源是很重要的。

概率入门

概率 是描述随机变量和随机事件的工具。世界充满了随机性,通过概率方法试图解释混沌是找到我们的方向的最佳方式。诚然,“解释混沌”这个短语可能是个矛盾之词,因为混沌实际上不能被解释,但我们人类不能放弃对不确定事件的控制。这就是为什么我们开发了工具来理解我们充满未知事件的世界。

当你试图为金融交易开发机器学习算法时,你可能会想知道理解概率基础的用处是什么。这是一个合理的问题,你必须知道,一个学科的基础不一定与最终技能相似。

例如,要成为一名飞行员,你必须学习充满技术概念的空气动力学,这与最终的技能并不相似。这与本章节的做法类似;通过学习概率基本原理,你让你的大脑为即将到来的挑战做好了充分的准备。

知道你正在学习的东西的实用性应该会给你带来动力。以下是一些对于机器学习至关重要的关键概率主题:

概率分布函数

随机变量可能出现各种结果的可能性由概率分布描述。对于许多机器学习技术来说,理解典型概率分布的特征和属性至关重要。概率分布函数还描述了不同类型的时间序列数据,从而有助于选择正确的算法。为了简洁和连贯起见,这个话题在第三章中讨论。

假设检验

假设检验 用于确定基于数据样本的基于人口的断言更可能是正确还是不正确。稳态检验使用假设检验,并在第三章中讨论。

决策树

决策树是一种借鉴概率概念的机器学习算法,这些概念包括条件概率,本章有涉及。关于决策树的更多细节,请参阅第七章。

信息理论

信息理论是对信息如何量化、存储和传输进行复杂研究的学科。它被纳入到许多机器学习技术中,包括决策树。它还用于一种非线性相关性度量,称为最大信息系数,该内容在第三章中讨论。

概率概念简介

概率信息的最基本部分是随机变量,它是一个不确定的数字或结果。随机变量用于模拟被认为是不确定的事件,例如货币对未来回报。

随机变量可以是离散的或连续的。离散随机变量具有有限的值集,而连续随机变量在某个区间内有值。考虑以下例子以澄清问题:

  • 离散随机变量的一个例子是掷骰子的结果。结果受以下集合限制:{1, 2, 3, 4, 5, 6}。

  • 连续随机变量的一个例子是 EURUSD 的每日价格回报(一个欧元兑换成美元的汇率)。

随机变量由概率分布描述,这些分布是给出随机变量每个可能值的概率的函数。通常,直方图用于显示概率。直方图绘制在第三章中讨论。

在任何时刻,某个事件将发生的概率在 0 到 1 之间。这意味着概率被分配给随机变量,其范围在 0 到 1 之间,其中概率为 0 表示不可能发生,概率为 1 表示一定发生。

你也可以以百分比形式考虑这一点,百分比范围从 0%到 100%。这两个数字之间的值是有效的,这意味着你可以有一个 0.5133(51.33%)的概率来发生某个事件。考虑掷一个有六面的骰子,知道这个骰子没有任何操纵,那么获得 3 的概率是多少?

因为骰子有六个面,每个结果的概率相等,这意味着对于任何结果,概率如下找到:

P ( x ) = 1 6 = 0 . 167

使用P(x)指定事件x的概率。这给出了以下问题的答案:

P ( 3 ) = 1 6 = 0 . 167

当掷骰子时,只能有一个结果。它不能同时给出 3 和 4,因为一面必须支配另一面。这是互斥的概念。互斥事件(例如在掷骰子时得到 3 或得到 4)最终总和为 1。

看看以下例子:

P ( 1 ) = 1 6 = 0 . 167

P ( 2 ) = 1 6 = 0 . 167

P ( 3 ) = 1 6 = 0 . 167

P ( 4 ) = 1 6 = 0 . 167

P ( 5 ) = 1 6 = 0 . 167

P ( 6 ) = 1 6 = 0 . 167

所有这些互斥事件的总和为 1,这意味着六面骰子的可能性概率之和如下:

P ( 1 ) + P ( 2 ) + P ( 3 ) + P ( 4 ) + P ( 5 ) + P ( 6 ) = 1

注意

声明随机变量有 0.8 的概率发生与声明相同的变量有 0.2 的概率不发生相同。

概率测量可以是条件的也可以是无条件的。条件概率 是指一个事件发生对另一个事件发生的概率影响。例如,考虑到正面就业数据的情况下主权利率上涨的概率是条件概率的一个例子。事件 A 给定事件 B 的发生由数学符号P(A|B)表示。

相反,无条件概率 不依赖于其他事件。以条件概率为例,您可以制定一个无条件概率计算,该计算测量利率上涨的概率,而不考虑其他经济事件。

概率具有特定的加法和乘法规则及其各自的解释。在看一个例子之前,让我们先看一下这些公式。两个事件实现的联合概率 是它们同时发生的概率。它是用以下公式计算的:

P ( A B ) = P ( A | B ) × P ( B )

该公式表明了 A 和 B 同时发生的概率是 A 在给定 B 发生的情况下发生的概率乘以 B 发生的概率。因此,方程的右侧将条件概率乘以无条件概率。

加法规则 用于确定至少会发生其中一个结果的概率。这有两种方式:一种处理互斥事件,另一种处理非互斥事件。

如果事件不是互斥的,那么为了避免重复计数,公式是:

P ( A o r B ) = P ( A ) + P ( B ) - P ( A B )

如果事件是互斥的,那么公式简化为:

P ( A B ) = 0

P ( A o r B ) = P ( A ) + P ( B ) - 0

P ( A o r B ) = P ( A ) + P ( B )

注意在互斥事件中,要么发生 A 要么发生 B,因此它们都发生的概率为零。要理解为什么需要减去 A 和 B 的联合概率,请参见图 2-1。

图 2-1. 概率的加法规则

注意在它们是互斥的情况下,发生 A 或 B 的概率必须不包括它们的联合概率。现在让我们看看独立事件的概念。

独立事件 不互相关联(例如,两次掷骰子)。在这种情况下,联合概率计算如下:

P ( A B ) = P ( A ) × P ( B )

独立事件是指一个事件的发生对其他事件的发生绝对没有影响的情况。让我们通过一个例子来验证这个概念。考虑一个简单的抛硬币游戏。得到正面的概率不取决于你在前一次抛硬币时得到了什么。因此,得到正面的概率始终是 0.50(50%)。进一步说,五次抛硬币后只得到正面的概率是多少?

因为每个事件的概率都独立于前一个或下一个事件,所以公式如下:

P ( x ) = 0 . 50 × 0 . 50 × 0 . 50 × 0 . 50 × 0 . 50 = 0 . 03125 = 3 . 125 %

随机变量的期望值是不同结果的加权*均值。因此,期望值实际上是指均值的另一种方式。从数学上讲,期望值如下:

E ( X ) = i=1 n ( P ( x i ) x i )

查看表 2-1,并尝试计算某年某月下一个就业人数的预期值。

表 2-1. 就业人数

非农就业人数 概率
300,000 0.1
400,000 0.3
500,000 0.5
600,000 0.1

非农业就业人数指的是美国劳工部发布的每月报告,提供了除农业部门、政府及非营利组织外的全国有薪雇员总数信息。

从表 2-1 可知,经济学家假设在下一个就业人数中,500,000 人增加的概率为 50%,400,000 人增加的概率为 30%。因此,期望值为:

E ( X ) = ( 300 , 000 × 0 . 1 ) + ( 400 , 000 × 0 . 3 ) + ( 500 , 000 × 0 . 5 ) + ( 600 , 000 × 0 . 1 ) = 460 , 000

因此,代表经济学家共识的数字是 460,000,因为它是最接*大多数预测的加权值。它是代表数据集的值。

注意

本节的主要要点如下:

  • 概率描述随机变量和随机事件。它是一个介于 0 和 1 之间的值。

  • 事件的概率可以被分组以形成更复杂的情景。

  • 预期结果是指指定宇宙中每个概率的加权*均值。

抽样和假设检验

当总体较大时,采取代表性样本,使其成为数据的主要描述者。以美国为例。其民主制度意味着人民有权决定自己的命运,但不可能去找每个人并询问他们对每个话题的详细意见。这就是为什么会举行选举并选举代表来代表人民。

抽样是指在更大的总体内选择数据样本并对总体的统计性质做出结论的行为。有几种不同的抽样方法。其中最知名的是以下几种:

简单随机抽样

使用简单随机抽样,总体中的每个元素具有相等的被选中为样本的机会。这可以在一个标记了的总体上生成随机数,其中每个个体被选择的概率相同。

分层抽样

使用分层抽样,将总体根据某些特征分成组,然后从每组中按其大小比例随机抽取样本。

聚类抽样

使用聚类抽样,将总体分成簇,并选择一组随机样本的随机样本。然后,包括所选簇中的所有元素在内。

系统抽样

使用系统抽样,通过从总体中每隔n个个体选择一个元素来选择一个元素,其中n是固定数字。这意味着它不是随机的,而是事先指定的。

经验法则是,获取更多数据,指标反映出的群体就越好。在机器学习领域,抽样非常重要,因为经常需要对数据进行抽样以代表真实群体。例如,在对交易策略进行回测时,需要将整个数据集分成训练样本测试样本,其中前者是算法理解其结构的样本(也称为样本内集),后者是算法测试其预测能力的样本(也称为样本外集)。

另一个使用抽样的例子是交叉验证。使用这种技术,数据集被分成两个或更多子集。模型使用一个子集进行训练,使用其他子集进行测试其结果。对数据的各种子集,该过程重复多次,然后确定模型的*均表现。

这些术语将在接下来的章节中更深入地讨论。现在,你应该明白在机器学习中,抽样的概念非常重要。

抽样并非完美,可能存在误差,就像任何其他估计方法一样。 抽样误差指的是样本统计量与总体统计量之间的差异(如果已知)。 统计量是描述分析数据集的指标(例如,*均值,在第三章中你将更详细地了解到统计量)。 那么,你应该具备多少最小样本量才能对总体进行推断? 经验法则是至少要有 30 个观测值,越多越好。 这把讨论带到了中心极限定理,它指出从总体抽取的随机样本将在样本量增大时接*正态分布(一种对称和钟形的概率分布)。

中心极限定理使得在正态分布中应用推断和结论变得简单。 在进行假设检验之前,让我们先看看置信区间,这是预期的总体参数的值范围。 置信区间通常是通过从点估计值中加或减一个因子来构建的。 例如,给定样本均值 x̄,可以构建置信区间如下:

x ¯ ± ( reliability factor × standard error )

让我们逐步理解计算过程。 样本均值是对总体的估计,由于无法计算总体均值,因此进行随机抽样的假设是样本均值应等于总体均值。 然而,在现实生活中,情况可能有所不同,这就是为什么应该使用概率方法构建置信区间的原因。

注意

显著水*是置信区间的阈值。 例如,95%的置信区间意味着在 95%的置信水*下,估计值应落在某个范围内。 其余 5%的概率不会这样,这就是显著水*(通常用希腊字母α表示)。

可靠性系数是一个统计量,依赖于估计分布和其落入置信区间的概率。 为了简单起见,我们假设总体的方差是正态分布的,总体也是正态分布的。 对于显著水*为 5%(因此置信区间为 95%),在这种情况下可靠性系数为 1.96(获得这个数字的方法与讨论的内容关系不大)。

标准误差是样本的标准偏差。 标准偏差在第三章中有更深入的讨论;暂时知道它代表了不同数值围绕*均值波动的程度。 标准误差使用以下公式计算:

s = σ n

σ is the population standard deviation

n is the square root of the population number

还值得知道,对于 1%显著水*,可靠因子为 2.575,对于 10%显著水*,可靠因子为 1.645。让我们通过一个实际例子来理解所有这些数学。

考虑一个由 100 种金融工具(债券、货币对、股票、结构化产品等)组成的总体。这些工具的年*均回报率为 1.4%。假设总体标准差为 4.34%,在 1%显著水*(99%置信区间)下,*均值的置信区间是多少?

答案仅通过按照以下公式插入数值来确定:

1 . 4 % ± 2 . 575 × 4.34% 100 = 1 . 4 % ± 1 . 11 %

这意味着置信区间在(0.29%, 2.51%)之间。

注:

如果样本量较小和/或总体标准差未知,则 t-分布可能比正态分布更合适。

t-分布 是一种用于建模样本均值分布的概率分布类型,当样本量较小时和/或总体标准差未知时使用。它在形状上类似于正态分布,但尾部更重,代表与较小样本量相关的不确定性。

下一个阶段是假设检验,这是一种获取数据样本结论的关键概率技术。这部分非常重要,因为在许多统计分析和模型中都会使用。

在统计学中,假设检验 是一种从少量数据样本中得出总体结论的技术。它包括建立两个竞争假设,零假设备择假设,关于总体参数,然后通过样本数据确定哪一个更可能准确。

例如,假设一名金融分析师从风险角度评估两个投资组合。他们制定了两个假设:

  • 零假设指出两个投资组合的波动性没有显著差异。

  • 备择假设指出两个投资组合的波动性存在显著差异。

然后,使用统计分析来检验假设,以确定波动性差异是否具有统计显著性或纯属偶然。

根据零假设和备择假设的定义,使用样本数据计算出一个检验统计量。为了评估结果的显著性,将检验统计量与从标准分布中提取的临界值进行比较。如果检验统计量位于关键区域内,则拒绝零假设并接受备择假设。如果检验统计量不落入关键区域,则不拒绝零假设,并得出无足够证据支持备择假设的结论。

这只是一种花哨的说法,即假设检验基本上涉及创建两个相反的场景,运行概率检查,然后决定哪种场景更有可能是真实的。假设检验可以采用两种形式:

单侧检验

这是测试某些金融工具回报是否大于零的示例。

双侧检验

这是测试某些金融工具回报是否与零不同(意味着它可以大于或小于零)的示例。假设检验通常是双侧检验。

零假设是您想要拒绝的假设,因此被测试,希望被拒绝并接受备择方案。双侧检验采用以下一般形式:

H 0 : x = x 0

H a : x x 0

由于备择方案允许出现零假设中规定的零值以上和以下的值,因此应该有两个临界值。因此,双侧检验的规则是如果检验统计量大于上临界值或小于下临界值,则拒绝零假设。例如,对于正态分布的数据集,检验统计量与临界值(在 5% 显著水*下)进行比较,分别为 +1.96 和 -1.96。如果检验统计量落在 +1.96 和 -1.96 的范围之外,则拒绝零假设。

假设检验的过程包括计算检验统计量。这是通过将总体参数的点估计与零假设的假设值进行比较来完成的。然后,两者都按照样本的标准误差进行缩放。数学表示如下:

Test statistic = Samplestatistic-Hypothesizedvalue Standarderror

假设检验中的一个重要考虑因素是样本可能不具有代表性,这会导致对总体进行描述的错误。这产生了两种类型的错误:

第一类错误

当拒绝零假设时,即使它是真的,也会发生此错误。

第二类错误

当失败地拒绝零假设时,发生此错误,即使它是错误的。

直观地说,显著水*是发生第一类错误的概率。请记住,如果 α = 5%,那么错误地拒绝真实的零假设的机会为 5%。通过一个例子会更清楚。

考虑一位分析师在 20 年期间对长-短组合的年度回报进行研究。*均年回报率为 1%,标准偏差为 2%。分析师认为*均年回报率不等于零,并希望为此构建一个 95% 置信区间,然后进行假设检验。您可以按以下步骤进行:

  1. 说明变量。样本大小为 20,标准偏差为 2%,均值为 1%。

  2. 计算标准误差,根据公式,在这种情况下为 0.44%。

  3. 确定 95%置信区间的临界值。临界值为+1.96 和-1.96。要找到置信区间,从样本均值中加减误差边界。因此,置信区间为(0.13%,1.86%)。

  4. 指定零假设,根据分析师的观点,是一个双尾检验。零假设是年回报等于零。如果检验统计量小于-1.96 或大于+1.96,则应该拒绝它。

  5. 使用公式找到检验统计量的结果为 2.27。因此,零假设被拒绝。

还有一个重要的度量指标需要讨论,那就是 p 值。p 值是在零假设为真的情况下,看到比统计检验中所见的更极端的检验统计量的概率。将 p 值与显著性水*(通常为 0.05)进行比较,可以帮助你理解它。如果 p 值小于或等于显著性水*,则结果被视为具有统计学意义,拒绝零假设,采纳备择假设。

如果 p 值小于 5%的显著性水*,则意味着如果零假设为真,则会看到与当前值一样极端的检验统计量的概率为 5%。另一种定义 p 值的方法是将其视为可以拒绝零假设的最小显著性水*。

注意

本节的主要观点如下:

  • 采样是指在人口中收集数据,旨在对上述人口的统计特性做出结论。

  • 假设检验是一种从小样本数据中得出关于总体的结论的技术。

信息论初探

信息理论是一个与概率密切相关的抽象数学复杂领域。它研究了信息如何被量化、存储和传输。在事件发生时,有三种发生条件:

不确定性

如果事件尚未发生

惊喜

如果事件刚刚发生

信息

如果事件过去曾发生

信息论中的一个关键概念是,它是消息或信息源中的不确定性或随机性水*,并描述了事件或消息意外程度。相反,信息增益度量了接收新信息时熵(惊喜)的减少程度。

基本上,信息理论描述了事件的惊喜程度。当事件发生的概率较低时,它具有更多的惊喜,因此提供的信息更多。同样,当事件发生的概率较高时,它具有较少的惊喜,因此提供的信息也较少。你应该记住的是,从不太可能发生的事件中学到的信息量大于从可能发生的事件中学到的信息量。

在深入研究信息理论领域之前,了解什么是对数以及什么是指数是非常重要的。一般的指数函数将某个常数或变量提升到某个幂次方:

f ( x ) = a x

换句话说,一个数的指数 是你将它乘以自己的次数:

4 3 = 4 × 4 × 4 = 64

对数是指数的反操作,其目的是找到指数——比如,从先前的例子中知道 4 和 64,找到 3:

log 4 ( 64 ) = 3

因此,对数是答案,用一个数乘以自己多少次可以得到另一个数。因为它们是互为反函数,所以可以一起使用来简化甚至解决 x。看下面的例子:

log 4 ( x ) = 3

这里的目标是找到给定对数函数的 x。第一步是在一侧使用指数函数,因为要取消右侧的对数(互为反函数会相互抵消)。这给我们带来了以下结果:

4 log 4 (x) = 4 3

x = 4 3

x = 64

对数可以有不同的底数。然而,最常用的对数具有底数 10。在计算机科学中,底数为 2 的对数表示比特(二进制位)。因此,信息以比特表示。信息增益的公式如下:

H ( x i ) = - l o g 2 ( P ( x i ) )

假设两个变量 xy,其中 x 的概率为 1(100%,因此确定)和 y 的概率为 0.5(50%,因此大部分是随机的)。在这两种情况下的信息价值是多少?答案如下:

H ( x ) = - l o g 2 ( P ( 1 ) ) = 0

H ( y ) = - l o g 2 ( P ( 0 . 5 ) ) = 1

因此,确定事件的信息价值为零,而有可能实现的事件的信息价值为 1。那么概率为 0.05(5%)的非常不可能发生的事件 z 的信息价值是多少呢?

H ( z ) = - l o g 2 ( P ( 0 . 05 ) ) = 4 . 32

概率与信息之间的负相关关系是信息理论的原则之一。熵和信息是相关概念,但它们具有不同的含义和应用。

是用于评估系统有多混乱或随机的度量标准。熵描述了信号的不确定或不可预测程度。系统或通信中的无序或不可预测性随熵的增加而增加。

信息 是由于接收信号而导致的熵或不确定性的减少。信号减少接收者的不确定性或熵的能力与其信息内容成正比。

注意

当所有事件同等可能时,熵被最大化。

熵(Entropy)通过以下公式计算:

S ( x n ) = i=1 n ( - l o g 2 ( P ( x i ) ) . ( P ( x i ) ) )

因此,熵是对数乘以其各自概率的和的*均值。

现在让我们讨论本节的最后一个概念,信息增益。通过信息增益来计算改变数据集引起的熵的减少。

提示

信息增益是你在第七章中会见到的关键概念之一,特别是在决策树中,因此,在阅读该章节后可能会希望参考本节。

计算信息增益的典型方式是比较转换前后数据集的熵。回想一下,当随机事件的所有结果具有相同的概率时,熵达到最大值。这也可以表示为分布,其中对称分布(如正态分布)具有高熵,而偏斜分布具有低熵。

最小化熵与最大化信息增益相关联。

在结束信息理论的介绍部分之前,让我们看看互信息的概念。这个度量是计算两个变量之间的关系,因此称为信息,它衡量了在给定另一个变量的情况下变量的不确定性减少。互信息的公式如下:

M I ( x , y ) = S ( x ) - S ( x | y )

  • MI(x, y) 是 xy 的互信息。

  • S(x) 是 x 的熵。

  • S(x|y) 是 xy 的条件熵。

因此,互信息衡量了变量之间的依赖关系。互信息越大,变量之间的关系越密切(值为零表示独立变量)。记住这个概念,因为你将在“相关性”中看到它。这是因为互信息也可以作为变量之间非线性关系的度量。

总结一下,在信息理论中你需要掌握以下基础知识:

  • 信息理论利用概率的概念来计算信息和熵,这些概念在机器学习模型和其他计算(如相关性)中使用。

  • 信息是接收信号导致的熵或不确定性减少。熵是评估系统混乱或随机程度的度量。

  • 互信息是两个随机变量之间依赖关系的度量。它也可以用来计算两者之间的相关性。

  • 信息理论中的工具被用于一些机器学习模型,例如决策树。

摘要

在深入讨论机器学习和深度学习模型时,了解概率提供了一个基本框架。本章节概述了你在处理这些模型时可能遇到的概念。理解如何计算概率以及如何进行假设检验非常重要(尽管实际上,算法会替你完成这些操作)。

下一章非常重要,将介绍你需要的统计知识,不仅适用于机器学习,还包括金融交易和复杂数据分析。

第三章:描述性统计和数据分析

描述性统计 是描述数据并尽可能多地从中提取信息的领域。基本上,描述性统计可以像数据的代表一样,因为它总结了其趋势、行为和趋势。

交易和分析从描述性统计中借用了许多指标。本章介绍了描述性统计和数据分析的主要概念。我总是发现最好的教育工具是实际的例子,因此我将使用经济时间序列的例子,即消费者价格指数(CPI),来解释这些概念。

CPI 测量城市居民每月支付的一系列产品和服务的价格;每个月都会向公众发布新的观测值,从而形成连续的时间序列。在任意两个时间段之间的通货膨胀率通过价格指数的百分比变化来衡量。例如,如果去年面包的价格是$1.00,而今天的价格是$1.01,则通胀率为 1.00%。CPI 通常以年度基础发布,这意味着它报告了当前月度观察值与 12 个月前观察值之间的差异。

导入 CPI 数据如下:

# Importing the required library
import pandas_datareader as pdr
# Setting the beginning and end of the historical data
start_date = '1950-01-01'
end_date   = '2023-01-23'
# Creating a dataframe and downloading the CPI data
cpi = pdr.DataReader('CPIAUCSL', 'fred', start_date, end_date)
# Printing the latest five observations of the dataframe
print(cpi.tail())
# Checking if there are nan values in the CPI dataframe
count_nan = cpi['CPIAUCSL'].isnull().sum()
# Printing the result
print('Number of nan values in the CPI dataframe: ' + str(count_nan))
# Transforming the CPI into a year-on-year measure
cpi = cpi.pct_change(periods = 12, axis = 0) * 100
# Dropping the nan values from the rows
cpi = cpi.dropna()

注意

您可以从书的专用GitHub 页面下载全书中的所有代码示例。

年度变化是 CPI 上最常见的变换,因为它清晰简明地测量了总体价格水*在足够时间内的变化,以解释短期波动和季节性影响。

因此,CPI 的年度变化作为通货膨胀趋势的一个指标。这也简单易懂,并可与其他国家和历史时期进行比较,因此在决策者和经济学家中很受欢迎(尽管存在国家之间购物篮权重的缺陷)。以下部分展示了如何利用 CPI 示例对时间序列数据进行统计分析。

中心趋势测量

中心趋势 指的是将数据集总结成一个能代表它们的值的度量。最著名的中心趋势测量是均值(*均值)。均值 简单地是数值总和除以它们的数量。它是最能代表数据的值。均值的数学公式如下:

x ¯ = 1 n n i=1 x i = 1 n ( x 1 + . . . + x n )

让我们以两个数据集的简单例子来说明。假设您想在数据集 A 和数据集 B 上计算均值。你会怎么做呢?

  • 数据集 A = [1, 2, 3, 4, 5]

  • 数据集 B = [1, 1, 1, 1]

数据集 A 包含五个值(数量),总和为 15。使用上述公式,均值等于 3。数据集 B 包含四个值,总和为 4。这意味着均值为 1。

图 3-1 展示了自 2003 年以来的美国 CPI 同比值。较高的虚线是自 2003 年以来计算的月度*均值。较低的虚线象征着零,下面是通货紧缩期。

注意

当数据集中的所有值都相同时,*均值与这些值相同。

图 3-1. 自 2003 年以来的美国 CPI 同比值,其中较高的虚线代表*均值

你可以使用以下代码创建图 3-1:

# Calculating the mean of the CPI over the last 20 years
cpi_latest = cpi.iloc[–240:]
mean = cpi_latest["CPIAUCSL"].mean()
# Printing the result
print('The mean of the dataset: ' + str(mean), '%')
# Importing the required library
import matplotlib.pyplot as plt
# Plotting the latest observations in black with a label
plt.plot(cpi_latest[:], color = 'black', linewidth = 1.5, 
         label = 'Change in CPI Year-on-Year')
# Plotting horizontal lines that represent the mean and the zero threshold
plt.axhline(y = mean, color = 'red', linestyle = 'dashed', 
         label = 'Mean')
plt.axhline(y = 0, color = 'blue', linestyle = 'dashed', linewidth = 1)
plt.grid()
plt.legend()

*均值的输出应如下所示。

The mean of the dataset: 2.49 %

这意味着年度*均通货膨胀率约为 2.50%。尽管美联储没有明确的通货膨胀目标,但普遍认为共识是维持年度通胀变化在约 2.00%左右,这与历史观察值相差不远。由于政治和经济动荡导致的高通胀数字自 2021 年以来,有必要回归到*均水*以稳定当前局势。这个例子为所谓的正常性(约 2.50%)自 2003 年以来提供了一个数值参考。

显然,由于 2023 年初高达约 6.00%的高通胀率,情况有些偏离正常,但有多偏离?这个问题将在接下来的部分得到回答,该部分讨论了变异性的度量。现在,让我们继续讨论中心趋势。

下一个度量是中位数,简单来说就是将数据集分成两个相等的部分的值。换句话说,如果你按升序排列数据集,中间的值就是中位数。在数据中有许多异常值或分布偏斜(可能会使*均值产生偏差并使其不太具代表性)时使用中位数。

通常有两个与计算中位数相关的主题。第一个与包含偶数值的数据集有关(例如,24 个观察结果),第二个与包含奇数值的数据集有关(例如,47 个观察结果):

计算偶数数据集的中位数

如果排列的数据集具有偶数个值,则中位数是两个中间值的*均值。

计算奇数数据集的中位数

如果排列的数据集具有奇数个值,中位数就是中间值。

让我们举一个简单的例子来说明两个数据集。假设你想在数据集 A 和数据集 B 上计算中位数。你会怎么做?

  • 数据集 A = [1, 2, 3, 4, 5]

  • 数据集 B = [1, 2, 3, 4]

数据集 A 包含五个值,这是一个奇数。这意味着中间值是中位数。在这种情况下,它是 3(注意它也是数据集的*均值)。数据集 B 包含四个值,这是一个偶数。这意味着两个中间值的*均值是中位数。在这种情况下,它是 2.5,这是 2 和 3 之间的*均值。

图 3-2 显示自 2003 年以来的美国消费者物价指数同比值。较高的虚线是自 2003 年以来计算的月中位数。较低的虚线象征着零。基本上,这类似于图 3-1,但绘制的是中位数而不是均值。

图 3-2。自 2003 年以来的美国消费者物价指数同比值,其中较高的虚线表示中位数

您可以使用以下代码创建图 3-2:

# Calculating the median
median = cpi_latest["CPIAUCSL"].median() 
# Printing the result
print('The median of the dataset: ' + str(median), '%')
# Plotting the latest observations in black with a label
plt.plot(cpi_latest[:], color = 'black', linewidth = 1.5, 
         label = 'Change in CPI Year-on-Year')
# Plotting horizontal lines that represent the median and the zero threshold
plt.axhline(y = median, color = 'red', linestyle = 'dashed', 
            label = 'Median')
plt.axhline(y = 0, color = 'blue', linestyle = 'dashed', linewidth = 1)
plt.grid()
plt.legend()

中位数的输出应该如下所示:

The median of the dataset: 2.12 %

显然,中位数受最*来自不寻常环境的异常值影响较小。中位数约为 2.12%,这与暗示的目标 2.00%更加一致。

注意

请记住,第六章将为您提供关于本章节中所见 Python 代码的所有信息,因此您不必担心是否会错过编码概念。

本节最后的中心趋势度量是众数,它是最常见的值(但在数据分析中使用最少的)。

让我们以两个数据集的简单示例为例。假设您要计算以下数据集的众数。您将如何做呢?

  • 数据集 A = [1, 2, 2, 4, 5]

  • 数据集 B = [1, 2, 3, 4]

  • 数据集 C = [1, 1, 2, 2, 3]

数据集 A 包含两次值为 2,这使其成为众数。数据集 B 没有众数,因为每个值仅观察一次。数据集 C 是多峰的,因为它包含多个众数(即 1 和 2)。

注意

众数在处理分类变量(如信用评级)时很有用,而不适用于连续变量(如价格和回报时间序列)。

您不太可能使用众数来分析时间序列,因为均值和中位数更为有用。以下是在金融分析中使用均值和中位数的几个示例:

  • 在价格数据上计算移动*均值以检测潜在趋势。您将在第五章中了解更多关于移动*均线的内容。

  • 计算基于价格衍生指标的滚动中位数以检测其中性区域。

  • 使用历史均值计算证券的预期回报。

中心趋势度量是理解重要的,特别是因为均值和中位数不仅作为独立指标广泛使用,还作为更复杂的度量的组成部分。

注意

本节的关键要点如下:

  • 主要有三种中心趋势度量:均值、中位数和众数。

  • 均值是总和除以数量,而中位数是将数据分成两半的值。众数是数据集中出现最频繁的值。

变异性的度量

变异性的度量描述数据集中数值相对于中心趋势度量(主要是均值)的散布程度。最知名的变异性度量是方差。

方差描述了一组数字与它们的*均值之间的变异性。方差公式背后的理念是确定每个数据点距离均值的距离,然后对这些偏差进行*方以确保所有数字都是正数(因为距离不能是负数),最后将偏差除以观察次数。

计算方差的公式如下:

σ 2 = 1 n n i=1 (x i -x ¯) 2

此公式计算每个数据点与均值之间偏差的*方和,从而给出不同的距离观察值,然后计算这些距离观察值的*均值。

让我们来看两个数据集的简单例子。假设你想计算数据集 A 和数据集 B 的方差。你会怎么做?

  • 数据集 A = [1, 2, 3, 4, 5]

  • 数据集 B = [5, 5, 5, 5]

第一步是计算数据集的均值,因为这是你将计算数据变异性的基准。数据集 A 的均值为 3。下一步计算方差:

(x 1 -x ¯) 2 = (1-3) 2 = 4

(x 2 -x ¯) 2 = (2-3) 2 = 1

(x 3 -x ¯) 2 = (3-3) 2 = 0

(x 4 -x ¯) 2 = (4-3) 2 = 1

(x 5 -x ¯) 2 = (5-3) 2 = 4

前述结果总结如下:

4 + 1 + 0 + 1 + 4 = 10

最后,结果除以观察数以找到方差:

σ 2 = 10 5 = 2

至于数据集 B,你应该从直觉上考虑。如果观察值全部相等,它们都代表数据集,这也意味着它们是自己的均值。在这种情况下,考虑数据的方差会怎么说?

如果你的回答是方差为零,那么你是正确的。在数学上,你可以如下计算它:

(x 1 -x ¯) 2 = (5-5) 2 = 0

(x 2 -x ¯) 2 = (5-5) 2 = 0

(x 3 -x ¯) 2 = (5-5) 2 = 0

(x 4 -x ¯) 2 = (5-5) 2 = 0

前述结果总和为零,如果你将零除以 4(数据集的数量),你将得到零。直觉上,没有方差,因为所有值都是常数,它们不偏离其均值:

σ 2 = 0 4 = 0

在通货膨胀的例子中,你可以使用以下代码计算方差:

# Calculating the variance
variance = cpi_latest["CPIAUCSL"].var() 
# Printing the result
print('The variance of the dataset: ' + str(variance), '%')

方差的输出应该如下所示:

The variance of the dataset: 3.62 %

尽管如此,存在一个缺陷,即方差代表的是不可与均值比较的*方值,因为它们使用不同的单位。通过对方差取*方根轻松修正此问题。这带来了下一个变异性度量,标准差。它是方差的*方根,是值与均值的*均偏差。

低标准差表明值倾向于接*均值(低波动性),而高标准差表明值相对于其均值分布在更广的范围内(高波动性)。

注意

标准差波动率这两个术语可以互换使用,它们指的是同一件事情。

计算标准差的公式如下:

σ = 1 n n i=1 (x i -x ¯) 2

如果考虑前面的方差示例,则可以如下找到标准差:

σ DatasetA = 2 = 1 . 41

σ DatasetB = 0 = 0

由于标准差和均值使用相同的单位,所以标准差通常与均值一起使用。当我在下一节讨论正态分布函数时,您很快就会理解这个统计量的重要性。

您可以使用以下代码在 Python 中计算标准差:

# Calculating the standard deviation
standard_deviation = cpi_latest["CPIAUCSL"].std() 
# Printing the result
print('The standard deviation of the dataset: ' + 
      str(standard_deviation), '%')

标准差的输出应如下所示:

The standard deviation of the dataset: 1.90 %

您应该如何解释标准差?*均而言,CPI 年度同比值 tend to be ±1.90%,而同期均值为 2.49%。在“形状的度量”中,您将看到如何更好地利用标准差数据。

本节讨论的最后一个变异性度量是范围。范围是一个非常简单的统计量,显示了数据集中最大值和最小值之间的距离。这让您快速了解了两个历史极端值。查找范围的公式如下:

R a n g e = m a x ( x ) - m i n ( x )

在 Python 中,您可以轻松地做到这一点,因为有内置函数可以在给定一组数据的情况下显示最大值和最小值:

# Calculating the range
range_metric = max(cpi["CPIAUCSL"]) – min(cpi["CPIAUCSL"])
# Printing the result
print('The range of the dataset: ' + str(range_metric), '%')

上述代码的输出应如下所示:

The range of the dataset: 16.5510 %

图 3-3 显示了自 1951 年以来的 CPI 值。对角虚线表示范围,水*虚线表示零阈值。

CPI 的范围显示了通货膨胀衡量值从一个时期到另一个时期的变化大小。不同国家通胀数字的年度变化各不相同。一般来说,像法国和美国这样的发达国家在稳定时期具有稳定的变化(在稳定时期),而像土耳其和阿根廷这样的新兴和前沿世界国家的通胀数字则更具波动性和更极端。

图 3-3. 自 1951 年以来美国 CPI 年度同比变化,带有表示范围的对角虚线

本节的要点如下:

  • 您应该知道的三个关键变异性指标是方差、标准差和范围。

  • 标准差是方差的*方根。这样做是为了使其可与均值进行比较。

  • 范围是数据集中最高值和最低值之间的差异。它是对观察值总体延伸的快速概览。

形状的度量

形状的度量描述了数据集中各个值围绕中心趋势度量的分布。均值和标准差是描述正态分布的两个因素。标准差描述了数据的扩散或分散程度,而均值反映了分布的中心。

概率分布是描述随机实验中不同结果或事件发生可能性的数学函数。换句话说,它给出了随机变量所有可能值的概率。

有许多类型的概率分布,包括离散和连续分布。离散分布 只能取有限数量的值。最著名的离散分布包括伯努利分布、二项分布和泊松分布。

连续分布 用于可以在给定范围内取任意值的随机变量(如股票价格和回报)。最著名的分布是正态分布。

正态分布(也称为高斯分布)是一种连续概率分布,其在均值周围对称且呈钟形。它是统计分析中最广泛使用的分布之一,通常用于描述诸如年龄、体重和考试成绩等自然现象。图 3-4 展示了正态分布的形状。

图 3-4. 均值 = 0,标准差 = 1 的正态分布图

您可以使用以下代码块生成图 3-4:

# Importing libraries
import matplotlib.pyplot as plt
import numpy as np
import scipy.stats as stats
# Generate data for the plot
data = np.linspace(–3, 3, num = 1000)
# Define the mean and standard deviation of the normal distribution
mean = 0
std = 1
# Generate the function of the normal distribution
pdf = stats.norm.pdf(data, mean, std)
# Plot the normal distribution plot
plt.plot(data, pdf, '-', color = 'black', lw = 2)
plt.axvline(mean, color = 'black', linestyle = '--')
plt.grid()
plt.show()

由于正态分布变量很常见,大多数统计测试和模型假设分析的数据是正态的。在金融回报中,它们被假设为正态,即使它们经历了一种称为偏度和峰度的形状,这两个测量在本节中讨论。

在正态分布中,数据围绕*均值对称分布,这也意味着*均数等于中位数和众数。此外,约 68% 的数据落在均值的一个标准差范围内,约 95% 落在两个标准差范围内,约 99.7% 落在三个标准差范围内。这一特性使得正态分布成为推断的有用工具。

总结来说,您应从正态分布中保留以下内容:

  • *均数和标准差描述了分布。

  • *均数将分布一分为二,使其等于中位数。由于对称性质,众数也等于*均数和中位数。

现在让我们讨论形状的测量。形状的第一个测量是偏度。偏度 描述了分布的不对称性。它分析了分布偏离对称的程度。

正态分布的偏度等于零。这意味着分布在其*均值周围完全对称,均值两侧的数据点数量相等。

正偏 表示分布向右有一个长尾,这意味着*均数大于中位数,因为*均数对异常值敏感,这些异常值会使*均数上升(因此,在x轴的右侧)。类似地,众数将是三个中心趋势测量中的最小值。图 3-5 展示了正偏。

图 3-5. 正偏分布的示例

负偏表示分布的左侧尾部较长,这意味着均值低于中位数。类似地,众数将是三种中心趋势测量之间的最大值。图 3-6 显示了负偏斜。

图 3-6. 一个负偏斜分布的示例

在金融市场中如何解释偏度?如果分布呈正偏斜,则意味着高于均值的收益更多(分布的尾部在正侧更长)。

如果分布呈负偏斜,则意味着低于均值的收益更多(分布的尾部在负侧更长)。

一个收益系列的偏度可以提供有关投资的风险和回报的信息。例如,一个呈正偏的收益系列可能表明投资存在大量大额收益的潜力,但也伴随着频繁的小额损失的风险。

计算偏度的公式如下:

μ ˜ 3 = n=1 i (x i -x ¯) 3 Nσ 3

让我们检查自 2003 年以来美国 CPI 年同比数据的偏度:

# Calculating the skew
skew = cpi_latest["CPIAUCSL"].skew() 
# Printing the result
print('The skew of the dataset: ' + str(skew))

上述代码的输出应如下所示:

The skew of the dataset: 1.17

数据的偏度为 1.17,但这意味着什么?让我们绘制数据分布图来便于解释。您可以使用以下代码片段来完成这一点:

# Plotting the histogram of the data
fig, ax = plt.subplots()
ax.hist(cpi['CPIAUCSL'], bins = 30, edgecolor = 'black', color = 'white')
# Add vertical lines for better interpretation
ax.axvline(mean, color='black', linestyle='--', label = 'Mean', 
           linewidth = 2)
ax.axvline(median, color='grey', linestyle='-.', label = 'Median', 
           linewidth = 2)
plt.grid()
plt.legend()
plt.show()

图 3-7 显示了上一个代码片段的结果。由于均值大于中位数且偏度为正(大于零),数据显然呈正偏斜。

图 3-7. 美国 CPI 年同比数据分布,显示正偏斜

请记住,偏度是概率分布的不对称性度量。因此,它衡量分布偏离正态分布的程度。解释偏度的经验法则如下:

  • 如果偏度在–0.5 到 0.5 之间,则数据被认为是对称的。

  • 如果偏度在–1.0 到–0.5 或者 0.5 到 1.0 之间,则数据被认为是轻微偏斜的。

  • 如果偏度小于–1.0 或大于 1.0,则数据被认为是高度偏斜的。

正偏表示什么?在这种情况下,1.17 表示高度正偏的数据(在正侧),这与在经济增长时支持通货膨胀的货币政策一致(存在少数导致偏度的通货膨胀性波动)。

有趣的是,对于偏斜分布来说,中位数可能是首选的度量,因为均值往往会被异常值拉动,从而扭曲其值。

下一个形状的度量是kurtosis,它描述了一个分布相对于正态分布的尖峰或*坦程度。Kurtosis 描述了一个分布的尾部,特别是尾部是否比正态分布的更厚或更薄。

正态分布的峰度为 3,这意味着它是一个中峰态分布。如果一个分布的峰度大于 3,则被称为尖峰态,意味着比正态分布更高的峰值和更胖的尾巴。如果一个分布的峰度小于 3,则被称为低峰态,意味着比正态分布更扁*的峰值和更细的尾巴。

计算峰度的公式如下:

k = n=1 i (x i -x ¯) 4 Nσ 4

有时峰度被测量为过量峰度,以使其起始值为零(对于正态分布)。这意味着从 3 中减去峰度测量值以计算过量峰度。让我们计算美国 CPI 年度数据的过量峰度:

# Calculating the excess kurtosis
excess_kurtosis = cpi_latest["CPIAUCSL"].kurtosis() 
# Printing the result
print('The excess kurtosis of the dataset: ' + str(excess_kurtosis))

前述代码的输出应如下所示:

The excess kurtosis of the dataset: 2.15

在过去 20 年的美国 CPI 年度值的情况下,过量峰度为 2.15,这更符合尖峰态(峰值更高且尾部更胖)分布。正值表示分布比正态更尖峭,而负峰度表示形状比正态更扁*。

我们将在描述统计类别中讨论的最终指标是分位数。分位数是形状和变异性的度量,因为它们提供有关值的分布(形状)和这些值的离散度(变异性)的信息。最常用的类型是四分位数。

四分位数将数据集分为四等份。通过将数据按顺序排列然后执行分割来完成此操作。考虑表 3-1 作为一个例子。

表 3-1. 按升序排列的数字

Value
1
2
4
5
7
8
9

四分位数如下:

  • 下四分位数(Q1)是第一四分位数,在本例中为 2。

  • 中间四分位数(Q2)也是中位数,在本例中为 5。

  • 在本例中,上四分位数(Q3)为 8。

从数学上讲,可以使用以下公式计算 Q1 和 Q3:

Q 1 = ( n+1 4 )

Q 3 = 3 ( n+1 4 )

记住,公式的结果给出的是值的排名,而不是值本身:

Q 1 = ( 7+1 4 ) = 2 nd t e r m = 2

Q 3 = 3 ( 7+1 4 ) = 6 th t e r m = 8

四分位间距(IQR)是 Q3 和 Q1 之间的差异,并提供数据集中前 50%值的扩展度量。由于依赖中间值,IQR 对异常值具有鲁棒性,并提供大部分值扩展的简要摘要。根据以下公式,表 3-1 中数据的 IQR 为 6:

I Q R = Q 3 - Q 1

I Q R = 8 - 2 = 6

IQR 是一个有价值的指标,可以用作许多不同模型中的输入或风险度量。它还可以用于检测数据中的异常值,因为它对它们是免疫的。此外,IQR 可以帮助评估所分析资产的当前波动性,进而与其他方法结合使用以创建更强大的模型。如理解的那样,IQR 在有用性和解释性上优于范围度量,因为前者容易受到异常值的影响。

计算四分位数时要小心,因为有许多方法用于对同一数据集进行不同的计算。最重要的是确保在所有分析中使用相同的方法。用于计算表 3-1 中四分位数的方法称为图基的铰链方法。

注意

本节的主要要点如下:

  • 正态分布是一种连续的概率分布,具有钟形曲线。大多数数据聚集在均值附*。正态分布曲线的均值、中位数和众数都相等。

  • 偏度测量概率分布的不对称性。

  • 峰度度量概率分布的尖峰程度。超额峰度通常用于描述当前的概率分布。

  • 分位数将排列好的数据集分为相等的部分。最常用的分位数是将数据分为四等份的四分位数。

  • IQR 是第三四分位数与第一四分位数之间的差异。它对离群值免疫,因此在数据分析中非常有帮助。

数据可视化

在第一章中,我介绍了数据科学过程中的六个步骤。第 4 步是数据可视化。本节将展示几种以清晰的视觉方式呈现数据的方法,使您能够对其进行解释。

许多类型的统计图用于可视化数据。让我们讨论其中一些。

散点图用于绘制两个变量之间的关系,通过对应于变量交集的点。要创建散点图,请在 CPI 数据上使用以下代码:

# Importing the required library
import matplotlib.pyplot as plt
# Resetting the index
cpi = cpi.reset_index()
# Creating the chart
fig, ax = plt.subplots()
ax.scatter(cpi['DATE'], cpi['CPIAUCSL'], color = 'black', 
           s = 8,  label = 'Change in CPI Year-on-Year')
plt.grid()
plt.legend()
plt.show()

图 3-8 显示了一个时间散点图的结果。这意呈现了 CPI 数据作为第一个变量(y 轴)和时间作为第二个变量(x 轴)。然而,散点图更常用于比较变量;因此,移除时间变量可以提供更多的见解。

图 3-8. 美国 CPI 数据与时间轴的散点图

将英国 CPI 年度变化与美国 CPI 年度变化进行比较。注意在图 3-9 中它们之间的正相关性,因为其中一个变量的较高值与另一个变量的较高值相关。相关性是您将在下一节详细了解的关键度量。绘制图 3-9 的代码如下:

# Setting the beginning and end of the historical data
start_date = '1995-01-01'
end_date   = '2022-12-01'
# Creating a dataframe and downloading the CPI data
cpi_us = pdr.DataReader('CPIAUCSL', 'fred', start_date, end_date)
cpi_uk = pdr.DataReader('GBRCPIALLMINMEI', 'fred', start_date, end_date)
# Dropping the NaN values from the rows
cpi_us = cpi_us.dropna()
cpi_uk = cpi_uk.dropna()
# Transforming the CPI into a year-on-year measure
cpi_us = cpi_us.pct_change(periods = 12, axis = 0) * 100
cpi_us = cpi_us.dropna()
cpi_uk = cpi_uk.pct_change(periods = 12, axis = 0) * 100
cpi_uk = cpi_uk.dropna()
# Creating the chart
fig, ax = plt.subplots()
ax.scatter(cpi_us['CPIAUCSL'], cpi_uk['GBRCPIALLMINMEI'], 
           color = 'black', s = 8, label = 'Change in CPI Year-on-Year')
# Adding a few aesthetic elements to the chart
ax.set_xlabel('US CPI')
ax.set_ylabel('UK CPI')
ax.axvline(x = 0, color='black', linestyle = 'dashed', linewidth = 1)
ax.axhline(y = 0, color='black', linestyle = 'dashed', linewidth = 1)
ax.set_ylim(-2,)
plt.grid()
plt.legend()
plt.show()

图 3-9. 英国 CPI 数据与美国 CPI 数据的散点图

当可视化数据之间的相关性时,散点图非常有效。它们也很容易绘制和解释。通常,当点分散成一条向上倾斜的对角线时,可以假定相关性为正,因为当 x 轴上的变量增加时,y 轴上的变量也会增加。

另一方面,当可以画出一条斜向下的对角线来表示不同变量时,可能存在负相关。负相关意味着无论x轴上的变量如何移动,y轴上的变量很可能以相反的方式移动。

图 3-10 展示了来自图 3-9 的两个通胀数据集之间的最佳拟合线。请注意它是向上倾斜的。

图 3-10. 英国 CPI 数据与美国 CPI 数据的散点图及最佳拟合线

现在我们来看另一种图表方法。折线图是最基本的绘图类型,本质上是联结的散点图,大多数情况下都是针对时间轴(x轴)绘制的。你在图 3-1 和图 3-2 中看到了折线图。

折线图的优点在于简单易行。它们还展示了时间序列的演变,有助于检测趋势和模式。在第五章中,你将学习更复杂的蜡烛图,用于绘制金融时间序列。图 3-11 展示了自 1951 年以来美国 CPI 数据的基本折线图。

图 3-11. 美国 CPI 数据的折线图与时间轴

要创建图 3-11,你可以使用以下代码片段:

# Creating the chart
plt.plot(cpi['DATE'], cpi['CPIAUCSL'], color = 'black', 
         label = 'Change in CPI Year-on-Year')
plt.grid()
plt.legend()
plt.show()

接下来是条形图,用于显示变量(通常是分类的)的分布。图 3-12 展示了自 2022 年初以来的美国 CPI 数据的条形图。

图 3-12. 美国 CPI 数据的条形图与时间轴

要创建图 3-12,你可以使用以下代码片段:

# Taking the values of the previous twelve months
cpi_one_year = cpi.iloc[-12:]
# Creating the chart
plt.bar(cpi_one_year['DATE'], cpi_one_year['CPIAUCSL'], 
        color = 'black', label = 'Change in CPI Year-on-Year', width = 7)
plt.grid()
plt.legend()
plt.show()

条形图在绘制美国 CPI 或股票价格等连续数据时存在局限性。当比例失调时,它们也可能误导。由于后一原因,当处理大型数据集时,直方图更为合适。

直方图是一种特定类型的条形图,用于显示连续数据的频率分布,通过条形来表示统计信息。它指示落入特定类别或数值区间的观察次数。直方图的示例见图 3-13(还参见图 3-7)。

图 3-13. 美国 CPI 数据的直方图

要创建图 3-13,你可以使用以下代码片段:

# Creating the chart
fig, ax = plt.subplots()
ax.hist(cpi['CPIAUCSL'], bins = 30, edgecolor = 'black', 
        color = 'white', label = 'Change in CPI Year-on-Year',)
# Add vertical lines for better interpretation
ax.axvline(0, color = 'black')
plt.grid()
plt.legend()
plt.show()

注意条形图是针对时间轴绘制的,而直方图则没有时间范围,因为它是一组数值,旨在显示整体分布情况。从视觉上看,你可以看到分布的正偏度。

统计学中另一种经典绘图技术是箱形图。它用于可视化连续变量的分布,同时包括中位数和四分位数以及异常值。理解箱形图的方法如下:

  • 箱子代表 IQR。箱子在第一四分位数和第三四分位数之间绘制。箱子的高度表示该范围内数据的分布。

  • 箱子内的线表示中位数。

  • “须”从箱子的顶部和底部延伸到最高和最低数据点,这些点仍然在 1.5 倍 IQR 范围内。这些数据点称为异常值,在图上表示为单个点。

图 3-14 显示了自 1950 年以来美国 CPI 数据的箱形图。

图 3-14. 美国 CPI 数据的箱形图

您还可以绘制不带异常值的数据(任何值,其值超过箱子长度的 1.5 倍)。要创建图 3-14,您可以使用以下代码片段:

# Creating the chart
cpi_latest = cpi.iloc[–240:]
fig, ax = plt.subplots()
ax.boxplot(cpi_latest['CPIAUCSL'])
plt.grid()
plt.legend()
plt.show()

要从图中去除异常值,您只需使用以下小调整:

# Replace the corresponding code line with the following
fig, ax = plt.subplots()
ax.boxplot(cpi_latest['CPIAUCSL'], showfliers = False)

这将为您提供图 3-15。

图 3-15. 不含异常值的美国 CPI 数据箱形图

存在许多其他数据可视化技术,例如热力图(通常与相关数据和温度映射一起使用)和饼图(通常用于预算和分割)。使用哪种技术取决于您需要理解的内容及其适合您需求的情况。例如,折线图更适合仅具有一个特征的时间序列(例如,仅有某种证券的收盘价)。直方图更适合与概率分布数据一起使用。

注意

此部分的主要要点如下:

  • 使用哪种数据可视化技术取决于您要执行的分析和解释类型。某些图表更适合与特定类型的数据一起使用。

  • 数据可视化有助于在数值确认之前进行初步解释。

  • 在处理金融时间序列时,您更有可能使用折线图和蜡烛图。

相关性

相关性 是用来计算两个变量之间线性关系程度的指标。它是一个介于-1.0 和 1.0 之间的数字,其中-1.0 表示变量之间强烈的负相关关系,1.0 表示强烈的正相关关系。

零值表示变量之间没有线性关联。然而,相关性并不意味着因果关系。如果两个变量同向运动,则它们被认为是相关的,但这并不意味着一个变量导致另一个变量移动,或者它们因相同事件而移动。

大多数人认为某些资产具有自然的相关性。例如,因为它们都属于同一行业并受相同趋势和事件的影响,苹果和微软的股票呈正相关(这意味着它们的总体趋势方向相同)。图 3-16 显示了这两只股票之间的图表。请注意它们如何一起移动。

图 3-16。自 2021 年以来的苹果和微软股票价格

这两只股票的高点和低点几乎同时发生。同样,由于美国和英国有相似的经济驱动因素和影响,它们的通货膨胀数据也可能呈正相关,正如您在本章中早些时候看到的。

通过视觉解释和数学公式来检查相关性。在看一个例子之前,让我们讨论计算相关性的根源,以便您了解它的来源及其局限性。

注意

简单地说,要计算相关性,您需要测量两个变量散点图中的点有多接*一条直线。它们看起来越像一条直线,它们的正相关性就越强,因此称为线性相关

有两种主要计算相关性的方法:Spearman 方法和 Pearson 方法。¹

皮尔逊相关系数是从它们之间的标准差和协方差计算的两个变量之间线性关联的度量。

协方差计算两个变量均值之间差异的*均值。如果两个变量有一起移动的倾向,协方差为正,如果两个变量通常反向移动,协方差为负。它的范围在正无穷和负无穷之间。

计算变量xy之间协方差的公式如下:

c o v xy = i=1 n (x i -x ¯)(y i -y ¯) n

因此,协方差是变量之间*均偏差及其各自均值乘积之和(即,协方差度量它们关联的程度)。取*均值以标准化此计算。皮尔逊相关系数计算如下:

r xy = i=1 n (x i -x ¯)(y i -y ¯) i=1 n (x i -x ¯) 2 i=1 n (y i -y ¯) 2

简化前述相关公式得到以下结果:

r xy = cov xy σ x σ y

因此,皮尔逊相关系数简单地是两个变量之间的协方差除以它们标准差的乘积。让我们计算美国 CPI 年同比值和英国 CPI 年同比值之间的相关性。直觉上,由于英国和美国在经济上有关联,相关性大于零。以下代码块计算了这两个时间序列的皮尔逊相关系数:

# Importing the required libraries
import pandas_datareader as pdr
import pandas as pd
# Setting the beginning and end of the historical data
start_date = '1995-01-01'
end_date   = '2022-12-01'
# Creating a dataframe and downloading the CPI data
cpi_us = pdr.DataReader('CPIAUCSL', 'fred', start_date, end_date)
cpi_uk = pdr.DataReader('GBRCPIALLMINMEI', 'fred', start_date, end_date)
# Dropping the nan values from the rows
cpi_us = cpi_us.dropna()
cpi_uk = cpi_uk.dropna()
# Transforming the US CPI into a year-on-year measure
cpi_us = cpi_us.pct_change(periods = 12, axis = 0) * 100
cpi_us = cpi_us.dropna()
# Transforming the UK CPI into a year-on-year measure
cpi_uk = cpi_uk.pct_change(periods = 12, axis = 0) * 100
cpi_uk = cpi_uk.dropna()
# Joining both CPI data into one dataframe
combined_cpi_data = pd.concat([cpi_us['CPIAUCSL'], 
                               cpi_uk['GBRCPIALLMINMEI']], axis = 1)
# Calculating Pearson correlation
combined_cpi_data.corr(method = 'pearson')

输出如下:

                 CPIAUCSL  GBRCPIALLMINMEI
CPIAUCSL         1.000000         0.732164
GBRCPIALLMINMEI  0.732164         1.000000

这两者之间的相关系数高达 0.73。这符合预期。皮尔逊相关通常与具有比例变化且正态分布的变量一起使用。

斯皮尔曼秩相关是一种非参数秩相关,用于衡量变量之间关系的强度。它适用于不符合正态分布的变量。

注意

记住,财务收益不服从正态分布,但有时出于简单起见会这样处理。

与皮尔逊相关不同,斯皮尔曼秩相关考虑值的顺序,而不是实际值。要计算斯皮尔曼秩相关,请按照以下步骤进行:

  1. 给每个变量的值排名。这是通过将最小变量替换为 1 并将数据集的长度替换为最大数字来完成的。

  2. 计算秩的差异。在数学上,秩的差异用数学公式中的字母d表示。然后,计算它们的*方差。

  3. 求出第 2 步中计算的*方差的总和。

  4. 使用以下公式计算斯皮尔曼秩相关:

    ρ = 1 - 6 i=1 n d i 2 n 3 -n

与皮尔逊相关类似,斯皮尔曼秩相关的范围也是从–1.00 到 1.00,并具有相同的解释。

注意

强正相关通常大于 0.70,而强负相关通常小于–0.70。

以下代码块计算了两个时间序列的斯皮尔曼秩相关系数:

# Calculating Spearman's rank correlation
combined_cpi_data.corr(method = 'spearman')

输出如下:

                 CPIAUCSL  GBRCPIALLMINMEI
CPIAUCSL         1.000000         0.472526
GBRCPIALLMINMEI  0.472526         1.000000

在得到这个结果差异后,让我们回答一个非常重要的问题。为什么这两个测量结果如此不同?

首先要记住的是它们测量的内容。皮尔逊相关测量变量之间的线性关系(趋势),而斯皮尔曼秩相关测量单调趋势。单调一词指的是以相同方向移动,但不完全以相同速率或大小移动。此外,斯皮尔曼秩相关将数据转换为序数类型(通过秩),而不是像皮尔逊相关那样使用实际值。

自相关(也称为串行相关)是一种统计方法,用于查看给定时间序列与其滞后版本之间的关系。通常用于通过数据模式(例如季节性或趋势)来预测未来值。自相关因此是值与先前值的关系—例如,比较每一天的微软股票价格与前一天并查看是否存在可辨识的相关性。从算法的角度来看,这可以表示如下表 3-2。

表 3-2. 滞后值

t t–1
\(       1.25 |  \)       1.65
\(       1.77 |  \)       1.25
\(       1.78 |  \)       1.77
\(       1.25 |  \)       1.78
\(       1.90 |  \)       1.25

每一行代表一个时间段。列t是当前价格,列t-1是将先前价格放在代表当前的行上。这是在创建机器学习模型时,为了了解当前值与每个时间步长(行)中先前值之间的关系而完成的。

正自相关在趋势资产中经常发生,并与持续性(趋势跟随)的概念相关联。负自相关在波动市场中表现出来,并与反向性(均值回归)的概念相关联。

注意

计算不同时间序列(例如 NVIDIA 和 Oracle 股票)之间的短期相关性的度量通常使用价格的回报(例如差异)而不是实际价格。但是,可以直接利用价格来识别长期趋势。

下面的代码块计算美国 CPI 年度值的自相关:

# Creating a dataframe and downloading the CPI data
cpi = pdr.DataReader('CPIAUCSL', 'fred', start_date, end_date)
# Transforming the US CPI into a year-on-year measure
cpi = cpi.pct_change(periods = 12, axis = 0) * 100
cpi = cpi.dropna()
# Transforming the data frame to a series structure
cpi = cpi.iloc[:,0]
# Calculating autocorrelation with a lag of 1
print('Correlation with a lag of 1 = ', round(cpi.autocorr(lag = 1), 2))
# Calculating autocorrelation with a lag of 6
print('Correlation with a lag of 6 = ', round(cpi.autocorr(lag = 6), 2))
# Calculating autocorrelation with a lag of 12
print('Correlation with a lag of 12 = ', round(cpi.autocorr(lag = 12), 2))

滞后 12 期意味着每个数据值都与 12 期前的数据进行比较,然后计算相关性指标。以下是代码的输出:

Correlation with a lag of 1 =  0.97
Correlation with a lag of 6 =  0.65
Correlation with a lag of 12 =  0.17

现在,在进入下一节之前,让我们回到信息理论,讨论一个能够发现非线性关系的有趣相关系数。

最大信息系数(MIC)是两个变量之间的非参数相关性度量,旨在处理大规模和复杂的数据。它通常被视为传统相关性度量的更稳健的替代方法,如皮尔逊相关系数和斯皮尔曼秩相关系数。由大卫·N·雷舍夫等人引入,^(2)MIC 使用了信息理论中的概念,您在第二章中看到过。

MIC 通过计算对两个变量之间的关系最具信息量的列在列联表中的数量来衡量两个变量之间的关联强度。MIC 值的范围从 0 到 1,较高的值表示较强的关联。它可以处理高维数据,并且可以识别变量之间的非线性关系。

然而,它是无方向的,这意味着接* 1 的值仅表明两个变量之间存在强烈的相关性;它们并不表示相关性是正还是负。换句话说,在将每个变量的范围划分为一组箱之后,将计算每个箱内两个变量之间的相互信息。然后,通过在所有箱中获取最大相互信息值来估计两个变量之间的关联强度。

让我们看一个实际的例子,展示了 MIC 在检测非线性关系方面的优势。以下示例模拟了两个波形序列(正弦和余弦)。直觉上,从图 3-17 中看,似乎两者之间存在滞后-领先关系。

图 3-17. 显示非线性关系形式的两个波形系列

下面的 Python 代码片段创建了两个时间序列并绘制了图 3-17:

# Importing the required libraries
import numpy as np
import matplotlib.pyplot as plt
# Setting the range of the data
data_range = np.arange(0, 30, 0.1)
# Creating the sine and the cosine waves
sine = np.sin(data_range)
cosine = np.cos(data_range)
# Plotting
plt.plot(sine, color = 'black', label = 'Sine Function')
plt.plot(cosine, color = 'grey', linestyle = 'dashed', 
         label = 'Cosine Function')
plt.grid()
plt.legend()

现在的任务是计算三种相关性度量并分析它们的结果。为了简化起见,本节将省略代码,并在第六章中分享,因为首先需要了解 Python 中的一些内容。结果如下:

Correlation | Pearson:  0.035
Correlation | Spearman:  0.027
Correlation | MIC: 0.602

让我们解释一下结果:

  • Pearson 相关性: 注意到这里没有任何类型的相关性,因为它未能捕捉到非线性关联。

  • Spearman 等级相关性: 同样的情况也适用于这里,相关性非常弱。

  • MIC: 该指标显示了两者之间 0.60 的强关系,更接*实际情况。MIC 表明这两个波形有着强有力的非线性关系。

如果正确使用,MIC 在经济分析、金融分析甚至发现交易信号中非常有用。在这些复杂领域中非线性关系非常丰富,能够检测到它们可能会提供相当大的优势。

注意

本节的关键要点如下:

  • 相关性是用于计算变量之间线性关系程度的一个度量。它是一个介于–1.0 和 1.0 之间的数字,–1.0 表示变量之间有强烈的负相关关系,而 1.0 表示有强烈的正相关关系。

  • 有两种主要类型的相关性:Spearman 等级相关性和 Pearson 相关性。它们各自有其优点和局限性。

  • 自相关是变量与其自身滞后值的相关性。例如,如果沃尔玛股票收益的自相关是正的,则表示趋势配置。

  • 当你使用适当的工具,例如 MIC 时,相关度测量也可以指代非线性关系。

*稳性的概念

在统计分析和机器学习中,*稳性是一个关键概念。*稳性发生在时间序列的统计特征(均值、方差等)随时间保持不变的情况下。换句话说,当在时间轴上绘制数据时,没有可识别的趋势。

不同的学习模型依赖于数据的*稳性,因为这是统计建模的基础之一,这主要是为了简化。在金融领域,价格时间序列不是*稳的,因为它们显示出具有变化方差(波动性)的趋势。看看图 3-18,看看你能否检测到趋势。你认为这个时间序列是*稳的吗?

图 3-18. 具有随时间变化的均值的模拟数据

自然地,答案是否定的,因为一个上升的趋势正在明显进行中。这种状态对统计分析和机器学习来说是不可取的。幸运的是,您可以对时间序列应用转换使其*稳。但首先,让我们看看如何通过数学方式检查*稳性,因为视觉方式并不能证明任何事情。解决数据*稳性问题的正确方法是按照以下步骤进行:

  1. 使用您将在本节中看到的不同统计测试检查*稳性。

  2. 如果测试显示数据*稳,您就可以使用数据进行算法处理。如果测试显示数据不是*稳的,则必须进行第三步。

  3. 从前一个值中减去每个值(对值进行差分)。

  4. 使用相同的测试重新检查新转换的数据的*稳性。

  5. 如果测试显示数据*稳,那么您已成功转换了您的数据。否则,请重新进行转换并再次检查,直到您获得*稳的数据。

注意

升序或降序的时间序列在时间上具有变化的均值和方差,因此很可能是非*稳的。当然,也有例外情况,稍后您将了解原因。

记住,*稳性的目标是随时间稳定和恒定的均值和方差。因此,当您看 Figure 3-19 时,您可以推断出什么?

图 3-19。模拟数据在时间上的均值约为零

从视觉上看,数据看起来没有趋势,而且看起来围绕着稳定的均值波动,其方差也在稳定的均值附*变化。第一印象是数据是*稳的。当然,这必须通过统计测试来证明。

第一个也是最基本的测试是 增强的迪基—富勒(ADF)测试。这是使用假设检验来检验*稳性的。

ADF 测试搜索数据中的单位根。单位根是非*稳数据的属性,在时间序列分析的背景下,它指的是一种随机过程的特征,其中系列的根等于 1。简单来说,这意味着其统计特性,如均值和方差,会随时间变化。这是你需要知道的:

  • 零假设假设存在单位根。这意味着,如果您试图证明数据是*稳的,您需要拒绝零假设(如“抽样与假设检验”所示)。

  • 因此,备择假设是不存在单位根且数据具有*稳性。

  • 从测试中获得的 p 值必须小于选择的显著性水*(在大多数情况下,为 5%)。

让我们来测试美国 CPI 年同比数据的*稳性。以下代码片段使用 ADF 测试检查*稳性:

# Importing the required library
from statsmodels.tsa.stattools import adfuller
# Applying the ADF test on the CPI data
print('p-value: %f' % adfuller(cpi)[1])

代码的输出如下:

p-value: 0.0152

假设 5%显著水*,看起来可以接受同比数据是*稳的(然而,如果你想更严格,使用 1%显著水*,那么 p 值表明数据是非*稳的)。无论如何,即使看图表也可能让你百思不得其解。请记住,在 Figure 3-11 中,美国 CPI 的年度变化似乎是稳定的,但并不像*稳数据。这就是为什么需要使用数值和统计测试的原因。

现在,让我们对美国 CPI 的原始数据应用代码,而不是考虑同比变化。以下是代码:

# Creating a dataframe and downloading the CPI data
cpi = pdr.DataReader('CPIAUCSL', 'fred', start_date, end_date)
# Applying the ADF test on the CPI data
print('p-value: %f' % adfuller(cpi)[1])

代码的输出如下:

p-value: 0.999

显然,p 值大于所有显著水*,这意味着时间序列是非*稳的。让我们总结一下这些结果:

  • 看起来在涉及美国 CPI 数据的同比变化时,可以在 5%显著水*下拒绝零假设。数据集被假定为*稳。

  • 看起来当涉及美国 CPI 数据的原始值时,无法在 5%显著水*下拒绝零假设。数据集被假定为非*稳。

当你绘制美国 CPI 数据的原始值时,如图 Figure 3-20 所示,这变得显而易见。

图 3-20. 美国 CPI 数据的绝对值显示出明显的趋势性质

另一个你必须了解的测试是Kwiatkowski–Phillips–Schmidt–Shin(KPSS)测试,这也是一个用于确定时间序列是*稳还是非*稳的统计测试。然而,KPSS 测试可以检测到趋势时间序列中的*稳性,使其成为一个强大的工具。

趋势时间序列实际上可以在其具有稳定均值的条件下是*稳的。

警告

ADF 测试的零假设认为序列是非*稳的,备择假设则认为序列是*稳的。KPSS 测试的零假设认为序列是*稳的,备择假设则认为序列是非*稳的。

在分析通胀数据之前,让我们看看趋势时间序列如何可以是*稳的。记住,*稳性指的是稳定的均值和标准差,因此如果某种方式你有一个逐渐上升或下降的时间序列,但具有稳定的统计特性,它可能是*稳的。下一个代码片段模拟了一个正弦波然后为其添加了一点趋势:

# Importing the required libraries
import numpy as np
import matplotlib.pyplot as plt
# Creating the first time series using sine waves
length = np.pi * 2 * 5
sinewave = np.sin(np.arange(0, length, length / 1000))
# Creating the second time series using trending sine waves
sinewave_ascending = np.sin(np.arange(0, length, length / 1000))
# Defining the trend variable
a = 0.01
# Looping to add a trend factor
for i in range(len(sinewave_ascending)): 
    sinewave_ascending[i] = a + sinewave_ascending[i]
    a = 0.01 + a

如图 Figure 3-21 所示,绘制两个序列表明趋势正弦波似乎是稳定的。但让我们通过统计测试来证明这一点。

图 3-21. 模拟具有趋势正弦波的正常正弦波序列

Figure 3-21 是使用以下代码生成的(确保你已经使用前面的代码块定义了系列):

# Plotting the series
plt.plot(sinewave, label = 'Sine Wave', color = 'black')
plt.plot(sinewave_ascending, label = 'Ascending Sine Wave', 
         color = 'grey')
plt.grid()
plt.legend()
plt.show()

让我们分别对这两个序列进行 ADF 测试,看看结果如何:

# ADF testing | Normal sine wave
print('p-value: %f' % adfuller(sinewave)[1])
# ADF testing | Ascending sine wave
print('p-value: %f' % adfuller(sinewave_ascending)[1])

输出如下:

p-value: 0.000000 # For the sine wave
p-value: 0.989341 # For the ascending sine wave

显然,ADF 检验与趋势市场不能*稳的观点一致。但是 KPSS 检验呢?以下代码使用 KPSS 检验在同一数据上检查*稳性:

# Importing the KPSS library
from statsmodels.tsa.stattools import kpss
# KPSS testing | Normal sine wave
print('p-value: %f' % kpss(sinewave)[1])
# KPSS testing | Ascending sine wave
print('p-value: %f' % kpss(sinewave_ascending)[1])
# KPSS testing while taking into account the trend | Ascending sine wave
print('p-value: %f' % kpss(sinewave_ascending, regression = 'ct')[1])
`''' The 'ct' argument is used to check if the dataset is stationary  around a trend. By default, the argument is 'c' which is used to check if the data is stationary around a constant. '''`

输出如下:

p-value: 0.10 # For the sine wave
p-value: 0.01 # For the ascending sine wave without trend consideration
p-value: 0.10 # For the ascending sine wave with trend consideration

请记住,KPSS 检验的零假设是数据是*稳的;因此,如果 p 值大于显著性水*,则认为数据是*稳的,因为不能拒绝零假设。

当考虑趋势时,KPSS 统计量表明上升的正弦波是一个*稳时间序列。这是如何在趋势时间序列中找到*稳数据的基本示例。

让我们测试美国 CPI 年度数据的*稳性。以下代码片段使用 KPSS 检验检查*稳性:

# Applying the KPSS (no trend consideration) test on the CPI data
print('p-value: %f' % kpss(cpi)[1])
# Applying the KPSS (with trend consideration) test on the CPI data
print('p-value: %f' % kpss(cpi, regression = 'ct')[1])

代码的输出如下:

p-value: 0.010000 # without trend consideration
p-value: 0.010000 # with trend consideration

看起来 KPSS 检验的结果与 ADF 检验的结果相矛盾。这种情况可能会时有发生,差分可能会解决这个问题(请注意,年度数据已经是从绝对 CPI 值差分得来的时间序列,但有些时间序列可能需要多次差分才能变得*稳,而这也取决于差分的期间)。在这种情况下,最安全的解决方案是再次转换数据。

在完成关于*稳性的本节之前,让我们讨论一个复杂的主题,您将在第九章中看到它的实际应用。转换数据可能会导致异常的记忆丢失问题。在他的书籍《金融机器学习进展》(Wiley)中,Marcos López de Prado 提出了一种称为分数阶差分的技术,旨在使数据保持*稳同时保留一些记忆。

当一个非*稳时间序列进行差分以使其*稳时,会发生记忆丢失,这另一种说法是值之间的自相关显著降低,从而移除趋势成分和基础资产的 DNA。原始序列中的差分程度和自相关结构的持久性决定了记忆丢失的程度。

注意

本节的关键要点如下:

  • *稳性是指时间内稳定的均值和方差的概念。这是一个期望的特征,因为大多数机器学习模型依赖于此。

  • 金融价格时间序列很可能是非*稳的,并且需要进行一阶差分以使其变得*稳并准备好进行统计建模。有些甚至可能需要进行二阶转换才能变得*稳。

  • ADF 和 KPSS 检验检查数据的*稳性,后者能够检查趋势数据的*稳性。

  • 趋势数据可能是*稳的。尽管这种特征很少见,但 KPSS 检验可以检测到*稳性,而 ADF 检验则不能。

回归分析与统计推断

描述性统计描述数据并尽可能多地从中提取信息,而推断统计使用数据或数据样本进行推断(预测)。统计推断中的主要工具是线性回归。

线性回归是你在本书中将在第七章看到的基本机器学习算法之一,当我们讨论其他机器学习算法时,我们将简要讨论回归分析。线性回归方程的最基本形式如下所示:

y = α + β x + ϵ

  • y 是因变量;这是你想要预测的内容。

  • x 是自变量;这是你用作输入来预测y的内容。

  • α 是因变量在独立变量等于零时的期望值。

  • β 表示独立变量每单位变化对因变量的影响。

  • ϵ 是残差或未解释的变化

基本的线性回归方程说明,一个因变量(你想要预测的内容)由一个常数、一个经过调整的敏感变量以及一个残差(用于解释未解释的变化)来解释。参考表 3-3。

表 3-3. 给定x预测y

y x
100 49
200 99
300 149
400 199
? 249

预测给定x时预测y的线性方程如下所示:

y i = 2 + 2 x i

因此,给定x=249 时的最新y应该是 500:

y i = 2 + 2 x i = 2 + ( 2 × 249 ) = 500

注意线性回归如何完美捕捉到两个变量之间的线性关系,因为没有残差(未解释的变化)。当线性回归完美捕捉到两个变量之间的关系时,这意味着它们的坐标点在x轴上完美对齐成一条线性线。

多元线性回归可以采用以下形式:

y i = α + β 1 x 1 + . . . + β n x n + ϵ i

这基本上意味着因变量y可能受到多个变量的影响。例如,如果你想估计房价,可能需要考虑房间数量、面积、社区以及可能影响价格的任何其他变量。同样,如果你想预测商品价格,可能需要考虑不同的宏观经济因素、汇率以及任何其他的备选数据。

理解每个变量指代的内容非常重要。线性回归有一些假设:

线性关系

因变量与自变量之间的关系应该是线性的,意味着*面上的一条直线可以描述这种关系。在处理复杂变量时,这在现实生活中很少见。

变量的独立性

观察值应该彼此独立,意味着一个观察值的值不会影响另一个观察值的值。

同方差性

残差的方差(因变量的预测值与实际值之间的差异)应在所有独立变量的所有水*上保持恒定。

残差的正态性

残差应呈正态分布,意味着大多数残差接*零,并且分布是对称的。

在多元线性回归的情况下,你可以添加一个新的假设:多重共线性的缺失。独立变量不应高度相关;否则,这可能会使确定每个独立变量对因变量的独特影响变得困难。换句话说,这可以防止冗余。

注意

本节的关键要点如下:

  • 线性回归是推断统计学领域的一部分,它是描述变量之间关系的线性方程。

  • 线性回归解释并预测遵循一个方程,当你训练过去的数据并期望关系在未来保持时获得。

总结

能够进行数据分析对于部署正确的算法以便你可以预测时间序列的未来值至关重要。通过来自统计学世界的大量工具来理解数据。确保你理解什么是*稳性和相关性,因为它们提供了极有价值的建模见解。

¹ 还有其他方式,但这两种方式是最受欢迎的表示形式。

² David N. Reshef 等人,“在大数据集中检测新的关联”,Science 334, no. 6062 (2011 年 12 月): 1518-24。

第四章:深度学习的线性代数与微积分

代数和微积分是数据科学的重要组成部分。机器学习和深度学习算法大部分基于代数和微积分技术。本章以易懂的方式介绍了一些关键主题。

代数是研究运算和关系规则,以及由此产生的构造和思想的学科。代数涵盖了诸如线性方程和矩阵等主题。你可以将代数视为通向微积分的第一步。

微积分是研究曲线斜率和变化率的学科。微积分涵盖了诸如导数和积分之类的主题。它在许多领域如经济学和工程学中被广泛应用。许多学习算法依赖微积分的概念来执行复杂的操作。

两者之间的区别在于,微积分处理变化、运动和累积的概念,而代数处理数学符号以及操纵这些符号的规则。微积分关注于变化函数的特性和行为,而代数则提供了解方程和理解函数的基础。

线性代数

代数包含了各种数学结构,包括数字、变量以及加法、减法、乘法和除法等运算。线性代数是代数的一个基本分支,处理向量空间和线性变换。它在机器学习和深度学习中被广泛用于数据预处理、降维以及解线性方程组等任务。矩阵和向量是线性代数中的核心数据结构,矩阵乘法等操作在各种算法中很常见。

向量和矩阵

一个向量是一个具有大小(长度)和方向(箭头)的对象。向量的基本表示是带有坐标的箭头。但首先,让我们看看什么是轴。

x轴和y轴是垂直线,用于指定*面的边界以及其中不同点的位置,在二维笛卡尔坐标系中。x轴是水*的,y轴是垂直的。

这些轴可以表示向量,其中x轴代表向量的水*分量,y轴代表其垂直分量。

注意

在时间序列分析中,x轴通常是时间步长(小时、天等),而y轴是在相应时间步长(价格、收益等)处的值。

图 4-1 展示了一个简单的二维笛卡尔坐标系,包括两个轴。

二维笛卡尔坐标系使用简单的括号来显示不同点的位置,遵循这个顺序:

  • 点的坐标 = (x, y)

  • 变量x代表水*位置

  • 变量y代表水*位置

图 4-1. 二维笛卡尔坐标系

因此,如果你想要画出坐标为 (2, 3) 的点 A,你可能会从零点看一个图表,向右移动两个点,然后向上移动三个点。点的结果应该看起来像图 4-2。

图 4-2. 点 A 在坐标系中的位置

现在让我们添加另一个点,并在它们之间画一个向量。假设你有坐标为 (4, 5) 的点 B。由于点 B 的坐标高于点 A 的坐标,你会预期向量 AB 呈现向上斜坡。图 4-3 显示了新点 B 和向量 AB。

图 4-3. 向量 AB 在大小和方向上连接点 A 和点 B

然而,通过使用两点的坐标画出向量后,你如何引用这个向量呢?简单地说,向量 AB 有其自己的坐标来表示它。请记住,向量是从点 A 到点 B 的移动表示。这意味着沿 x 轴和 y 轴的双点移动就是向量。在数学上,为了找到向量,你应该从彼此的两个坐标点中减去它们,并尊重方向。以下是如何做到这一点的:

  • 向量 AB 意味着你从点 A 到点 B;因此,你需要从点 B 的坐标中减去点 A 的坐标:

    AB = 4 - 2 , 5 - 3 AB = 2 , 2

  • 向量 BA 意味着你从点 B 到点 A;因此,你需要从点 A 的坐标中减去点 B 的坐标:

    BA = 2 - 4 , 3 - 5 BA = - 2 , - 2

要解释 AB 和 BA 向量,你需要从移动的角度思考。向量 AB 表示从点 A 到点 B,水*和垂直分别移动两个正向点(向右和向上)。向量 BA 表示从点 B 到点 A,水*和垂直分别移动两个负向点(向左和向下)。

注意

尽管 AB 向量和 BA 向量具有相同的斜率,但它们并不是同一物体。那么斜率到底是什么呢?

斜率 是直线上两点之间的垂直变化与水*变化之比。你可以使用以下数学公式计算斜率:

S l o p e = (ΔY) (ΔX) S l o p e o f AB = 2 2 = 1 S l o p e o f BA = -2 -2 = 1

如果这两个向量仅仅是线段(没有方向),那么它们将是相同的对象。然而,加上方向分量使它们成为两个可以区分的数学对象。

图 4-4 更详细地解释了斜率的概念,x 向右移动了两个点,y 向左移动了两个点。

图 4-4. 向量 AB 的 x 变化和 y 变化
注意

具有大小为 1 的向量被称为单位向量

图 4-5 显示了向量 BA 的 x 变化和 y 变化情况。

图 4-5. 向量 BA 的 x 变化和 y 变化

研究人员通常将向量用作速度的表示,尤其是在工程领域。导航是一个严重依赖向量的领域。它允许航海员确定其位置并规划其目的地。自然地,大小表示速度,方向表示目的地。

您可以相互添加和减去向量,也可以与标量相加和减去向量。这允许在方向和大小上进行移动。您应该从前面的讨论中了解到,向量指示轴上不同点之间的方向。

注意

标量是具有大小但没有方向的值。与向量相反,标量用于表示元素,如温度和价格。基本上,标量就是数字。

矩阵是一个包含数字并以行和列组织的矩形数组。[¹] 矩阵在计算机图形学和其他领域中都很有用,用来定义和操作线性方程组。矩阵与向量的区别是什么?最简单的答案是,向量是具有单列或单行的矩阵。这里是一个 3 × 3 矩阵的基本示例:

5 2 9 - 8 10 13 1 5 12

矩阵的大小是它包含的行数和列数。行是水*线,列是垂直线。以下是一个 2 × 4 矩阵的表示(即两行四列):

5 2 1 3 - 8 10 9 4

以下是一个 4 × 2 矩阵的表示(即四行两列):

5 2 - 8 10 8 22 7 3

注意

矩阵在机器学习中被广泛使用。行通常表示时间,列表示特征。

不同矩阵的加法很简单,但只能在矩阵大小相同时使用(即它们具有相同的列数和行数)。例如,让我们来加下面这两个矩阵:

1 2 5 8 + 3 9 1 5 = 4 11 6 13

你可以看到,要将两个矩阵相加,只需将相同位置的数字相加即可。现在,如果你试图添加下一对矩阵,你将无法做到,因为要添加的内容不匹配:

8 3 3 2 + 3 9 1 5 5 4

矩阵的减法也很简单,遵循与矩阵加法相同的规则。让我们来看一个例子:

5 2 - 8 10 - 3 9 - 1 - 5 = 2 - 7 - 9 15

显然,矩阵的减法也是矩阵加法,只是其中一个矩阵中的数字变为相反数。

标量乘以矩阵非常简单。让我们看一个例子:

3 × 5 2 8 22 = 15 6 24 66

因此,基本上是将矩阵中的每个单元格乘以标量。将一个矩阵乘以另一个矩阵稍微复杂一些,因为它使用点积方法。首先,要将两个矩阵相乘,它们必须满足以下条件:

Matrix xy × Matrix yz = Matrix xz

这意味着第一个矩阵的列数必须等于第二个矩阵的行数,并且点积的结果矩阵是第一个矩阵的行数和第二个矩阵的列数。点积在以下示例中表示为 1 × 3 和 3 × 1 矩阵乘法的例子(注意列数和行数相等):

1 2 3 × 3 2 1 = ( 1 × 3 ) + ( 2 × 2 ) + ( 3 × 1 ) = 10

现在让我们来看一个 2 × 2 矩阵乘法的例子:

1 2 0 1 × 3 0 2 1 = 7 2 2 1

有一种特殊类型的矩阵称为单位矩阵,它基本上是矩阵的数字 1。对于 2 × 2 维度,它定义如下:

I = 1 0 0 1

对于 3 × 3 维度如下:

I = 1 0 0 0 1 0 0 0 1

将任何矩阵乘以单位矩阵会产生相同的原始矩阵。这就是为什么它可以被称为矩阵的单位元(将任何数字乘以 1 会得到相同的数字)。值得注意的是矩阵乘法不是可交换的,这意味着乘法顺序改变结果:

A B B A

矩阵转置是一个过程,涉及将行变为列,反之亦然。矩阵的转置是通过沿其主对角线反射矩阵而获得的:

461142 T = 4 1 6 4 1 2

在一些机器学习算法中使用了转置操作,处理这些模型时不是一个罕见的操作。如果你想了解矩阵在数据科学和机器学习中的角色,可以参考这个非详尽的列表:

数据表示

矩阵通常用来表示数据,其中行代表样本,列代表特征。例如,矩阵中的一行可以表示一个时间步中的 OHLC 数据。

线性代数

矩阵和线性代数是紧密相关的,许多学习算法在其操作中使用了矩阵的概念。对这些数学概念有基本的理解有助于在处理机器学习算法时*滑学习曲线。

数据关系矩阵

协方差和相关性度量通常表示为矩阵。这些关系计算是时间序列分析中的重要概念。

注意

本节的关键要点如下:

  • 向量是一个具有大小(长度)和方向(箭头)的对象。多个向量组合在一起形成一个矩阵。

  • 矩阵可以用来存储数据。它有其特殊的执行操作方式。

  • 矩阵乘法使用点积方法。

  • 转置矩阵意味着交换其行和列。

线性方程介绍

你在“回归分析和统计推断”中看到了线性方程的一个例子。线性方程基本上是介绍不同变量和常数之间*等关系的公式。在机器学习中,它通常是依赖变量(输出)和自变量(输入)之间的关系。理解线性方程的最佳方法是通过示例。

注意

线性方程的目标是找到一个未知变量,通常用字母x表示。

我们将从一个非常基础的例子开始,你可以将其视为以后将要看到的更高级概念的第一个构建块。 下面的例子要求找到满足方程的x的值:

10 x = 20

你应该理解方程为“10 乘以哪个数等于 20?” 当一个常数直接附加到变量(比如x)上时,它指的是一个乘法操作。现在,要解出x(即找到使方程相等的x的值),你有一个明显的解决方案,就是去掉 10,这样你就在方程的一边得到x,另一边得到剩下的部分。

自然地,要摆脱 10,你需要除以 10,这样剩下的就是 1,如果与变量x相乘则无效。 但请记住两件重要的事情:

  • 如果你在方程的一边进行数学运算,那么你必须在另一边也这样做。 这就是它们被称为方程的原因。

  • 为了简单起见,不要除以常数以摆脱它,你应该乘以它的倒数。

一个数的倒数是 1 除以该数。 这是它的数学表示:

R e c i p r o c a l ( x ) = 1 x

现在,回到例子中,要找到x,你可以这样做:

( 1 10 ) 10 x = 20 ( 1 10 )

进行乘法并简化后得到以下结果:

x = 2

这意味着方程的解为 2。 要验证这一点,你只需将 2 插入到原始方程中如下所示:

10 × 2 = 20

因此,需要两个 10 才能得到 20。

注意

将数字除以它本身等同于乘以它的倒数。

让我们来看一个如何通过线性技术解决x的例子。 考虑以下问题:

8 6 x = 24

进行乘法并简化后得到以下结果:

( 6 8 ) 8 6 x = 24 ( 6 8 )

x = 18

这意味着方程的解为 18。 要验证这一点,你只需将 18 插入到原始方程中如下所示:

8 6 × 18 = 24

通常,线性方程不会这么简单。 有时它们包含更多的变量和常数,需要更详细的解决方案,但让我们一步一步地进行。 考虑以下例子:

3 x - 6 = 12

解出x需要稍微重新排列方程。 记住,目标是将x留在一边,其余部分留在另一边。 在这里,你必须在处理 3 之前去掉常数 6。 解决方案的第一部分如下:

3 x - 6 ( + 6 ) = 12 ( + 6 )

注意如何在方程的两边都加上 6。左边的部分将自行抵消,而右边的部分将加起来得到 18:

3 x = 18

最后,你需要将变量x附加的常数的倒数相乘:

( 1 3 ) 3 x = 18 ( 1 3 )

简化并解出x得到以下解决方案:

x = 6

这意味着方程的解为 6。 要验证这一点,只需将 6 插入到原始方程中如下所示:

( 3 × 6 ) - 6 = 12

到目前为止,你应该已经注意到,线性代数主要是关于使用快捷和快速的技巧简化方程并找到未知变量。下一个例子显示有时变量x可能出现在多个地方:

6 x + x = 27 - 2 x

记住,主要重点是让x在方程的一边,其余部分在另一边:

6 x + x + 2 x = 27

x的常数上加给你以下结果:

9 x = 27

最后一步是除以 9,这样你就只剩下x

x = 3

现在,你可以通过在原方程中将 3 代入x的位置来验证这一点。你会注意到方程两边是相等的。

注意

尽管本节内容很简单,但它包含了你在代数和微积分中开始进阶所需的基础。本节的关键要点如下:

  • 线性方程是一种表示,其中任何变量的最高指数为一。这意味着没有变量被提升到二次及以上的幂次。

  • 在图表上绘制时,线性方程的直线是直的。

  • 在建模各种真实世界事件中应用线性方程的重要性使其在许多数学和研究领域至关重要。它们在机器学习中也被广泛应用。

  • 解决x的过程是找到一个值,使得方程两边相等。

  • 当对方程的一侧进行操作(例如加上常数或乘以常数)时,你也必须对另一侧进行相同的操作。

方程组

方程组是指有两个或更多个方程共同工作来解决一个或多个变量的情况。因此,不再是通常的单一方程:

x + 10 = 20

方程组类似于以下形式:

x + 10 = 20

y + 2 x = 10

方程组在机器学习中非常有用,广泛应用于其许多方面。

让我们从本节开头的方程组出发,通过图形化方法来解决它。绘制这两个函数实际上可以直接给出解决方案。交点就是解决方案。因此,交点的坐标(x, y)分别指的是xy的解决方案。

从图 4-6 可以看出,x = 10 而y = –10。将这些值代入各自的变量后得到正确答案:

10 + 10 = 20

(–10) + (2 × 10) = 10

![###### 图 4-6. 显示两个函数及其交点(解决方案)由于函数是线性的,解决它们可能导致三种结果之一:1. 每个变量只有一个解决方案。1. 不存在解决方案。这发生在函数*行时(这意味着它们从未相交)。1. 存在无数个解。当通过简化时,两个函数相同时(因为所有点都落在直线上)。在通过代数解方程组之前,让我们通过视觉方式看看如何没有解和如何有无限多个解。考虑以下系统:2 x = 104 x = 20图 4-7 将它们一起绘制。由于它们正好是同一方程,它们落在同一条线上。实际上,图 4-7 中有两条线,但由于它们相同,它们是无法区分的。对于线上的每个x,都有一个对应的y

图 4-7. 显示两个函数及其无限交点的图表

现在考虑以下系统:

3 x = 10

6 x = 10

图 4-8 显示它们永不相交,这是直观的,因为你不能用不同的数字(由变量x表示)乘以同一个数字而期望得到相同的结果。

图 4-8. 显示两个函数及其不可能的交点的图表

代数方法用于解决超过两个变量的情况,因为它们无法通过图表求解。主要包括两种方法:替换法和消元法。

替换法 用于可以在一个方程中替换变量值并将其插入第二个方程的情况。考虑以下例子:

x + y = 2

10 x + y = 10

最简单的方法是重新排列第一个方程,使得你可以用x来表示y

y = 2 - x

10 x + ( 2 - x ) = 10

在第二个方程中解x变得很简单:

10 x + ( 2 - x ) = 10 10 x + 2 - x = 10 10 x - x = 10 - 2 9 x = 8 x = 8 9 x = 0 . 8889

现在你已经找到x的值,通过将x的值代入第一个方程中,你可以轻松找到y的值:

0 . 8889 + y = 2 y = 2 - 0 . 8889 y = 1 . 111

要检查你的解是否正确,可以在两个公式中分别代入xy的值:

0 . 8889 + 1 . 111 = 2 ( 10 × 0 . 8889 ) + 1 . 111 = 10

从图形上看,这意味着两个方程在(0.8889, 1.111)处相交。这种技术可以用于多于两个变量。按照相同的过程进行,直到方程简化到足以给出答案。替换法的问题在于处理超过两个变量时可能需要一些时间。

消元法 是一个更快的替代方法。它涉及消除变量,直到只剩下一个。考虑以下例子:

2 x + 4 y = 20 3 x + 2 y = 10

注意到有 4y和 2y,可以将第二个方程乘以 2,这样你可以将两个方程相减(从而消除y变量):

2 x + 4 y = 20 6 x + 4 y = 20

将两个方程相减得到以下结果:

- 4 x = 0 x = 0

因此,x = 0. 从图形上看,这意味着它们在x = 0 时相交(正好在垂直的y线上)。将x的值代入第一个公式中得到y = 5:

( 2 × 0 ) + 4 y = 20 4 y = 20 y = 5

类似地,消元法也可以解决三个变量的方程。选择替换法或消元法取决于要解决的方程类型。

注意

本节的主要收获如下:

  • 方程组同时解决变量。它们在机器学习中非常有用,被一些算法使用。

  • 对于简单的方程组,图形解法是首选。

  • 通过代数解方程组涉及使用代入和消元方法。

  • 当系统比较简单时,推荐使用代入法,但当系统稍微复杂时,则使用消元法。

三角学

三角学探索被称为三角函数的行为,这些函数将三角形的角度与其边长关联起来。最常用的三角形是直角三角形,其中一个角为 90°。图 4-9 展示了一个直角三角形的例子。

图 4-9. 直角三角形

让我们定义直角三角形的主要特征:

  • 三角形的最长边称为斜边

  • 斜边对面的角是直角(90°的那个角)。

  • 根据选择的另一个角(θ)(剩下的两个角之一),连接这个角和斜边的线称为邻边,另一条线称为对边

注意

三角函数是数学函数,用于将直角三角形的角度与其边长的比率关联起来。它们在几何学、物理学、工程学等领域有各种应用。它们帮助分析和解决与角度、距离、振荡和波形等相关的问题。

三角函数简单来说就是三角形的一条线除以另一条线的比值。记住三角形有三条边(斜边、对边和邻边)。三角函数如下找到:

s i n ( θ ) = Opposite Hypotenuse

c o s ( θ ) = Adjacent Hypotenuse

t a n ( θ ) = Opposite Adjacent

从前面的三个三角函数中,可以通过基本的线性代数得到一个三角恒等式,通过sincos可以得到tan

t a n ( θ ) = sin(θ) cos(θ)

双曲函数类似于三角函数,但是使用指数函数来定义。在理解双曲函数之前,必须了解欧拉数。

注意

这部分关于双曲函数的内容很有趣,因为它构成了所谓的激活函数的基础,这是神经网络中的一个关键概念,深度学习模型的主角。您将在第八章中详细了解它们。

欧拉数(记为e)是数学中最重要的数之一。它是一个无理数,即不能用分数表示的实数。无理一词来自于无法用比率来表示它的事实;这与其性质无关。欧拉数也是自然对数ln的底数,其前几位数字为 2.71828。一个最佳的*似值公式如下:

e = (1+1 n) n

通过在前述公式中增加n,您将接*e的值。欧拉数有许多有趣的性质,最显著的是其斜率等于其自身的值。考虑以下函数(也称为自然指数函数):

f ( x ) = e x

在任何点,函数的斜率都是相同的值。看看图 4-10。

图 4-10. 自然指数函数的图表
注意

你可能会想知道为什么我在这本书中解释指数和对数。主要有两个原因:

  • 指数和更重要的是欧拉数在双曲函数中被使用,其中 tanh(x) 是神经网络中的主要激活函数,一种机器学习和深度学习模型。

  • 对数在损失函数中非常有用,这是你在后面章节中会看到的概念。

双曲函数使用自然指数函数,并定义如下:

s i n h ( x ) = e x -e -x 2

c o s h ( x ) = e x +e -x 2

t a n h ( x ) = e x -e -x e x +e -x

tanh(x) 的主要特征包括非线性、限制在 [–1, 1] 之间,并且其以零为中心。图 4-11 显示了 tanh(x) 的图表。

图 4-11. 显示了 tanh(x) 的图表,显示它在 –1 和 1 之间的限制。
注意

本节的主要要点如下:

  • 三角学是一个研究三角函数行为的领域,它将三角形的角度与其边长联系起来。

  • 三角恒等式是将三角函数相互关联的快捷方式。

  • 欧拉数 e 是无理数,是自然对数的底数。它在指数增长和双曲函数中有许多应用。

  • 双曲正切函数在神经网络中,一种深度学习算法中被使用。

微积分

正如前面提到的,微积分是数学的一个分支,专注于速率变化和数量积累的研究。它包括两个主要分支:微分学(处理导数)和积分学(处理积分)。本节简要介绍了这两种微积分类型,同时讨论了诸如极限和优化等主题。

极限与连续性

微积分通过使微不足道的小量显现出来而起作用。

—Keith Devlin

极限并不一定是噩梦。我一直发现人们对它们有所误解。实际上,它们非常容易理解。但首先,你需要动力,这源于了解学习极限的附加价值。

理解极限对于机器学习模型来说是非常重要的,原因有很多:

优化

在像梯度下降这样的优化方法中,极限可以用来调节步长并保证收敛到局部最小值。

特征选择

极限可以用来排列各种模型特征的重要性并进行特征选择,这可以使模型更简单且性能更好。

敏感性分析

机器学习模型对输入数据变化的敏感性以及其泛化到新数据的能力可以用来检验模型的行为。

同样,极限被用于你即将学习的更高级的微积分概念中。

极限的主要目的是在函数未定义时知道函数的值。但是什么是未定义函数?当您有一个给出不可能的解决方案的函数(例如除以零)时,极限帮助您绕过此问题以了解该点处函数的值。因此,极限的目的是即使在函数未定义时也能解决函数。

请记住,将 x 作为输入的函数的解是 y 轴上的一个值。 图 4-12 展示了以下函数的线性图形:

f ( x ) = x + 2

图 4-12. 函数 f(x) = x + 2 的图形

图形中函数的解是每次考虑 x 的值时位于线性线上的解。

x = 4 时,函数(y 的值)的解将是什么?显然,答案是 6,因为将 x 的值替换为 4 给出 6:

f ( 4 ) = 4 + 2 = 6

将此解释视为极限的解决方案就像询问函数在 x 从两侧(负/递减侧和正/递增侧)接* 4 时的解决方案一样。 表 4-1 简化了这个困境。

表 4-1. 找到接* 4 的 x

f(x) x
5.998 3.998
5.999 3.999
6.000 4.000
6.001 4.001
6.002 4.002

从负侧接*相当于在 4 以下添加一个数字的分数并每次分析结果。类似地,从正侧接*相当于在 4 以上减去一个数字的分数并每次分析结果。随着 x 接* 4,解似乎收敛到 6。这是极限的解。

一般形式的极限按照以下约定编写:

lim xa f ( x ) = L

极限的一般形式读作:当您沿着 x 轴接* a(无论是从正侧还是从负侧),函数 f(x) 都会趋于值 L

注意

极限的概念表明,当您从任一侧(负或正)锁定并接*一个数字时,方程的解会接*某个数字,而极限的解就是那个数字。

如前所述,当使用传统的代入方法无法确定解的确切位置时,极限就变得很有用。

单侧极限与一般极限不同。对于左侧极限,您从负侧向正侧寻找极限,而对于右侧极限,您从正侧向负侧寻找极限。当两个单侧极限存在且相等时,一般极限存在。因此,前述陈述可以总结如下:

  • 左侧极限存在。

  • 右侧极限存在。

  • 左侧极限等于右侧极限。

左侧极限定义如下:

lim xa - f ( x ) = L

右侧极限定义如下:

lim xa + f ( x ) = L

考虑以下方程:

f ( x ) = x 3 -27 x-3

x = 3 时,函数的解决方案是什么?代入得出以下问题:

f ( 3 ) = 3 3 -27 3-3 = 27-27 3-3 = 0 0 = Undefined

然而,从 表 4-2 显示的极限的角度来看,当从左侧或右侧逼* x = 3 时,解决方案趋*于 27。

表 4-2. 寻找 x 逼* 3 的值

f(x) x
2.9998 26.9982
2.9999 26.9991
3.0000 未定义
3.0001 27.0009
3.0002 27.0018

从图形上看,这可以看作是图表在两个轴上的不连续性。这种不连续性存在于坐标 (3, 27) 附*的线上。一些函数没有极限。例如,当 x 接* 5 时,以下函数的极限是多少?

lim x5 1 x-5

查看 表 4-3,当 x 接* 5 时,从两边逼*结果显著分离。例如,从负侧逼*,4.9999 的极限是 –10,000,从正侧逼*,5.0001 的极限是 10,000。

表 4-3. 寻找 x 逼* 5 的值

f(x) x
4.9998 –5000
4.9999 –10000
5.0000 未定义
5.0001 10000
5.0002 5000

要记住,一般极限存在的条件是单侧极限必须存在且相等,但在这里并非如此。通过绘图可以看出,这为 图 4-13 说明了为何极限不存在。

图 4-13. 证明极限不存在的函数图表

但如果你想分析的函数看起来像这样:

lim x5 1 |x-5|

查看 表 4-3,当 x 接* 5 时,结果在逼*时快速加速到被称为无穷大 (∞) 的非常大的数值:

f ( x ) = 1 |x-5|

查看 表 4-4:

表 4-4. 另一种寻找 x 逼* 5 的尝试

f(x) x
4.99997 334333.33
4.99998 50000
4.99999 100000
4.9999999 10000000
5.00000 未定义
5.0000001 10000000
5.00001 100000
5.00002 50000
5.00003 334333.33

在每一个微小的步骤中,x 接* 5,y 也逐渐接*正无穷大。因此极限的答案是正无穷大(+∞)。 图 4-14 显示了函数的图表。请注意,当 x 接* 5 时,两边的值都在上升。

图 4-14. 证明极限存在的函数图表

连续 函数是在图表中没有间隙或空洞的函数,而 不连续 函数则包含这些间隙和空洞。这通常意味着后者包含函数未定义的点,并可能需要通过极限进行*似。因此,连续性和极限是两个相关的概念。

让我们继续解决极限问题;毕竟,你不会每次都创建表格并主观分析结果以找到极限。解决极限有三种方法:

  • 替换:这是最简单的规则,通常首先使用。

  • 因式分解:这是替换失败后的下一步。

  • 共轭方法:这个解决方案是在前两个方法不起作用之后的选择。

替换 简单地将x接*的值代入。基本上,这些是使用极限的函数解。看下面的例子:

lim x5 x + 10 - 2 x

使用替换,函数的极限如下找到:

lim x5 x + 10 - 2 x = 5 + 10 - ( 2 × 5 ) = 5

因此,极限的答案为 5。

因式分解 是替换不起作用时的下一步选择(例如,将x的值代入函数后,极限未定义)。 因式分解 是通过使用因子以改变方程的形式的方法,使得在使用替换时方程不再是未定义的。看下面的例子:

lim x-6 (x+6)(x 2 -x+1) x+6

如果尝试替换,将得到一个未定义的值如下:

lim x-6 (x+6)(x 2 -x+1) x+6 = (-6+6)((-6) 2 -(-6)+1) -6+6 = 0 0 = Undefined

在这种情况下,因式分解可能有所帮助。例如,分子乘以(x + 6),然后除以(x + 6)。通过取消两个项来简化这个过程可能会得到一个解:

lim x-6 (x+6)(x 2 -x+1) x+6 = lim x-6 x 2 - x + 1

现在因式分解已完成,您可以再次尝试替换:

lim x-6 x 2 - x + 1 = (-6) 2 - ( - 6 ) + 1 = 43

函数在x趋向于-6 时的极限因此为 43。

当替换和因式分解都不起作用时,形成共轭是下一步的选择。 共轭 是通过简单地改变两个变量之间的符号来形成的。例如,x + y 的共轭是 x - y。在分数的情况下,通过用其中一个的共轭来乘分子和分母(最好使用具有*方根的项的共轭,因为它将被取消)。考虑以下例子:

lim x9 x-9 x-3

通过将分母的共轭乘以两个项,您将开始使用共轭方法来解决问题:

lim x9 x-9 x-3 ( x+3 x+3 )

考虑乘法然后简化得到如下结果:

lim x9 (x-9)(x+3) (x-3)(x+3)

您将得到以下熟悉的情况:

lim x9 (x-9)(x+3) x-9

lim x9 x + 3

现在函数已准备好进行替换:

lim x9 9 + 3 = 3 + 3 = 6

因此,函数的解为 6。如您所见,有时需要对方程进行处理,然后才能进行替换。

注意

本节的关键要点如下:

  • 极限帮助找到在某些点可能未定义的函数的解。

  • 对于一般的极限存在,两个单侧极限必须存在且必须相等。

  • 有多种方法可以找到函数的极限,尤其是替换、因式分解和形成共轭。

导数

导数 衡量给定一个或多个输入变化时函数的变化。换句话说,它是给定点上函数变化率。

在构建机器学习模型中,对导数有深刻的理解是重要的,有多个原因:

优化

要使损失函数最小化,优化方法利用导数确定最陡下降的方向并修改模型的参数。

反向传播

在深度学习中执行梯度下降时,反向传播技术利用导数计算损失函数对模型参数的梯度。

超参数调优

为了提升模型性能,导数用于敏感性分析和超参数调优。

不要忘记前一节关于极限的知识,因为在本节中你同样会需要这些知识。微积分主要涉及导数和积分。本节讨论了导数及其用途。

你可以将导数视为表示(或建模)另一函数在某点斜率的函数。斜率是一条线相对于水*线的位置的度量。正斜率表示线上升,而负斜率表示线下降。

导数和斜率是相关的概念,但它们并不完全相同。以下是两者的主要区别:

斜率

斜率衡量了一条线的陡峭程度。它是y轴变化与x轴变化的比率。

导数

导数描述了给定函数的变化率。当函数上两点之间的距离趋*于零时,该函数在该点的导数就是切线斜率的极限。

在解释导数的通俗术语及展示一些例子之前,让我们先看看它们的形式定义:

f ' ( x ) = lim h0 f(x+h)-f(x) h

这个方程构成了求解导数的基础,尽管你将会学到许多简化的捷径。让我们尝试使用形式化定义找到函数的导数。考虑以下方程:

f ( x ) = x 2 + 4 x - 2

要找到导数,将f(x)代入形式化定义,然后解决极限:

f ' ( x ) = lim h0 f(x+h)-f(x) h

为了简化问题,让我们找到f(x + h),这样插入形式化定义会更容易:

f ( x + h ) = (x+h) 2 + 4 ( x + h ) - 2

f ( x + h ) = x 2 + 2 x h + h 2 + 4 x + 4 h - 2

现在让我们将f(x + h)代入定义中:

f ' ( x ) = lim h0 x 2 +2xh+h 2 +4x+4h-2-x 2 -4x+2 h

注意看有多少术语可以简化,这样公式会更清晰。记住,你现在是在寻找极限,而求导则是在解决了极限之后进行的:

f ' ( x ) = lim h0 2xh+h 2 +4h h

通过除以h,进一步简化的潜力得以释放,因为你可以将分子中的所有项除以分母h

f ' ( x ) = lim h0 2 x + h + 4

现在是时候解决极限了。由于方程简单,第一次尝试是通过代入求解,正如你所猜测的那样,这是可行的。通过代入变量h并使其趋向于零(根据极限),你得到如下结果:

f ' ( x ) = 2 x + 4

这就是原始函数f(x)的导数。如果你想找到函数在x = 2 时的导数,只需将 2 代入导数函数即可:

f ' ( 2 ) = 2 ( 2 ) + 4 = 8

图 4-15 展示了原始函数的图像及其导数(直线)。注意f'(2)恰好在 8 处。当x = 2 时,f(x)的斜率为 8。

图 4-15. 原始函数f(x)及其导数f'(x)
注意

注意当f(x)触底并开始上升时,f'(x)会穿过零线。

每次你想要找到导数时,可能不会使用形式化定义。有导数规则可以通过快捷方式节省大量时间。第一个规则称为幂规则,是找到具有指数的函数的导数的一种方法。

使用这种符号引用导数也很常见(这与f'(x)是一样的):

dy dx

寻找导数的幂规则如下:

dy dx ( a x n ) = ( a . n ) x n-1

基本上,这意味着导数是通过将常数乘以指数然后从指数中减去 1 来找到的。这里有一个例子:

f ( x ) = x 4

f ' ( x ) = ( 1 × 4 ) x (4-1) = 4 x 3

请记住,如果变量没有附加常数,这意味着该常数等于 1。这里有一个具有相同原则的更复杂的例子:

f ( x ) = 2 x 2 + 3 x 7 - 2 x 3

f ' ( x ) = 4 x + 21 x 6 - 6 x 2

值得注意的是,即使常数不符合幂规则的一般形式,该规则也适用于常数。常数的导数为零。虽然知道为什么有所帮助,但首先你必须了解以下数学概念:

x 0 = 1

话虽如此,你可以想象常数总是乘以x的零次幂(因为这样做不会改变它们的值)。现在,如果你想要找到 17 的导数,就像这样:

17 = 17 x 0 = ( 0 × 17 ) x 0-1 = 0 x -1 = 0

正如你所知,任何乘以零的东西返回零作为结果。这为常数导数规则提供了如下定义:

dy dx ( a ) = 0

遇到分数或负数指数时,你要遵循相同的逻辑。

导数的乘积规则在两个函数相乘时非常有用。乘积规则如下:

dy dx [ f ( x ) g ( x ) ] = f ' ( x ) g ( x ) + f ( x ) g ' ( x )

让我们举个例子,并使用乘积法则找到导数:

h ( x ) = ( x 2 + 2 ) ( x 3 + 1 )

方程式可以明显地分成两个项,f(x)g(x),如下所示:

f ( x ) = ( x 2 + 2 )

g ( x ) = ( x 3 + 1 )

在应用乘积法则之前,我们来找出这两个术语的导数。请注意,一旦理解了幂规则,找到f(x)g(x)的导数就很容易:

f ' ( x ) = 2 x

g ' ( x ) = 3 x 2

在应用乘积规则时,你应该得到以下结果:

h ' ( x ) = ( x 2 + 2 ) ( 3 x 2 ) + ( 2 x ) ( x 3 + 1 )

h ' ( x ) = 3 x 4 + 6 x 2 + 2 x 4 + 2 x

h ' ( x ) = 5 x 4 + 6 x 2 + 2 x

图 4-16 显示了h(x)h'(x)的图形。

图 4-16. 原始h(x)及其导数h'(x)

现在让我们将注意力转向商规则,它处理两个函数的除法。形式定义如下:

dy dx [ f(x) g(x) ] = f ' (x)g(x)-f(x)g ' (x) [g(x)] 2

让我们将其应用到以下函数中:

f ( x ) = x 2 -x+1 x 2 +1

通常,最好先找到f(x)g(x)的导数,在这种情况下,它们明显是分开的,f(x)是分子,g(x)是分母。在应用商规则时,你应该得到以下结果:

f ' ( x ) = (2x-1)(x 2 +1)-(x 2 -x+1)(2x) (x 2 +1) 2

f ' ( x ) = 2x 3 +2x-x 2 -1-2x 3 +2x 2 -2x (x 2 +1) 2

f ' ( x ) = x 2 -1 (x 2 +1) 2

指数导数处理应用于常数的幂规则。看看下面的方程式。你会如何找到它的导数?

f ( x ) = a x

不同于通常的变量-基数-常量-指数,它是常量-基数-变量-指数。在尝试计算导数时,这种处理方式有所不同。形式化定义如下:

dy dx a x = a x ( ln a )

下面的示例显示了如何做到这一点:

dy dx 4 x = 4 x ( ln 4 )

之前提到的欧拉数有一个特殊的导数。当涉及到找到e的导数时,答案很有趣:

dy dx e x = e x ( ln e ) = e x

这是因为自然对数函数和指数函数是彼此的反函数,所以术语ln e等于 1。因此,指数函数e的导数就是它本身。

与此同时,让我们讨论一下对数导数。到现在为止,你应该知道指数和对数是什么。两种对数的一般定义如下:

dy dx log a x = 1 xlna

dy dx ln x = log e x = 1 xlne = 1 x

注意在自然对数的二阶导函数中,再次遇到ln e项,因此使得简化变得非常容易,因为它等于 1。

以以下示例为例:

f ( x ) = 7 l o g 2 ( x )

使用正式定义,这个对数函数的导数如下:

f ' ( x ) = 7 ( 1 xln2 ) = 7 xln2

注意

对数log的基数是 10,但自然对数ln的基数是e(~2.7182)。

自然对数和对数函数实际上是通过简单的乘法线性相关的。如果你知道常数a的对数,你可以通过将a的对数乘以 2.4303 来找到它的自然对数ln

导数中的一个主要概念是链式法则。让我们回到幂规则,它处理变量的指数。记住以下公式找到导数:

dy dx ( a x n ) = ( a . n ) x n-1

这是一个简化的版本,因为只有x,但现实是你必须乘以幂指数下面的项的导数。到目前为止,你只看到x作为幂指数下的变量。x的导数是 1,这就是为什么它被简化和隐藏的原因。然而,对于更复杂的函数,比如这个:

f ( x ) = (4x+1) 2

通过以下两个步骤找到函数的导数:

  1. 找到外部函数的导数而不触及内部函数。

  2. 找到内部函数的导数并将其乘以函数的其余部分。

因此解决方案如下(知道4x + 1 的导数只是 4):

f ' ( x ) = 2 ( 4 x + 1 ) . 4

f ' ( x ) = 8 ( 4 x + 1 )

f ' ( x ) = 32 x + 8

指数函数也是如此。接下来以以下示例为例:

f ( x ) = e x

f ' ( x ) = e x ( 1 ) = e x

链式法则实际上可以被认为是一条总规则,因为它适用于任何地方,即使是在乘积法则和商法则中也是如此。

在求导数中有更多的概念需要掌握,但是由于本书不打算成为完整的微积分大师课程,你至少应该了解导数的含义,如何找到它,它代表什么以及在机器学习和深度学习中如何使用它。

注意

本节的关键点如下:

  • 导数衡量给定一个或多个输入变化的函数的变化。

  • 幂规则用于找到函数的幂的导数。

  • 乘积法则用于找到两个相乘的函数的导数。

  • 商法则用于找到两个相除的函数的导数。

  • 链式法则是不同 iating 中使用的主要规则(这意味着找到导数的过程)。由于简单性,它经常被忽视。

  • 导数在机器学习中发挥着关键作用,比如启用优化技术,帮助模型训练,并增强模型的可解释性。

积分与微积分基本定理

积分 是一种操作,表示在给定区间内函数曲线下的面积。它是导数的反操作,这也是为什么它被称为反导数

找到积分的过程称为积分。积分可以用来找到曲线下的面积,在金融世界中广泛应用于风险管理、投资组合管理、概率方法,甚至期权定价等领域。

理解积分的最简单方法是考虑计算函数曲线下面积。可以通过手动计算x轴上的不同变化来完成这一过程,但添加这些片段以找到面积是一个繁琐的过程。这就是积分发挥作用的地方。

请记住,积分是导数的反操作。这很重要,因为它意味着两者之间存在直接关系。积分的基本定义如下:

f ( x ) d x = F ( X ) + C

The symbol represents the integration process

f ( x ) is the derivative of the general function F ( x )

C represents the lost constant in the differentiation process

d x represents slicing along x as it approaches zero

前面的方程意味着f(x)的积分是一般函数F(x)加上一个常数C,这个常数在初始微分过程中丢失了。以下是一个例子,更好地解释放入常数的必要性。

考虑以下函数:

f ( x ) = x 2 + 5

计算其导数,得到以下结果:

f ' ( x ) = 2 x

现在,如果你想要积分它,以便回到原始函数(在这种情况下用大写字母F(x)代表而不是f(x))?

2 x d x

通常,看到差异化过程(这意味着取导数),你会返回 2 作为指数,这给出以下答案:

2 x d x = x 2

这看起来不像原始函数。它缺少常数 5. 但你无法知道这一点,即使你知道有一个常数,你也无法知道它是多少:1?2?677?这就是为什么在积分过程中添加常数C来表示丢失的常数的原因。因此,积分问题的答案如下:

2 x d x = x 2 + C

注意

到目前为止,讨论一直局限于不定积分,其中积分符号是裸露的(这意味着它没有边界)。我们将在定义完成积分所需的规则之后立即看到这意味着什么。

对于幂函数(就像前面的函数一样),积分的一般规则如下:

x a d x = x a+1 a+1 + C

这比看上去要简单得多。你只是在反转你之前看到的幂规则。考虑以下例子:

2 x 6 d x

2 x 6 d x = 2x 7 7 + C

2 x 6 d x = 2 7 x 7 + C

要验证你的答案,你可以找到结果的导数(使用幂规则):

F ( x ) = 2 7 x 7 + C

f ' ( x ) = ( 7 ) 2 7 x 7-1 + 0

f ' ( x ) = 2 x 6

让我们看另一个例子。考虑以下积分问题:

2 d x

根据规则,您应该找到以下结果:

2 d x = 2 x + C

让我们继续讨论定积分,这些是在函数曲线下方用指定的上下限表示的积分。因此,不定积分在曲线下方找到的区域的任何地方,而定积分则在由点a和点b给定的区间内被限制。 不定积分的一般定义如下:

a b f ( x ) d x = F ( B ) - F ( A )

这是最简单的方法。您将解决积分,然后插入两个数字并从彼此减去两个函数。考虑以下积分评估(积分求解通常称为评估积分):

0 6 3 x 2 - 10 x + 4 d x

第一步是理解所询问的内容。从积分的定义来看,在x轴上[0, 2]之间的区域似乎是使用给定函数计算的:

F ( x ) = ( [ x 3 - 5 x 2 + 4 x + C ] ) | 0 6

要在给定点评估积分,只需按以下方式插入值:

F ( x ) = ( [ 6 3 - 5 (6) 2 + 4 ( 6 ) + C ] ) - ( [ 0 3 - 5 (0) 2 + 4 ( 0 ) + C ] )

F ( x ) = ( [ 216 - 180 + 24 + C ] ) - ( [ 0 - 0 + 0 + C ] )

F ( x ) = ( [ 60 + C ] ) - ( [ 0 + C ] )

F ( x ) = ( 60 - 0 )

F ( x ) = 60

注意

常数C将始终取消掉不定积分,因此在这种问题中可以忽略它。

因此,图形f(x)下方和x轴上方的区域,以及在x轴上[0, 6]之间的区域,都等于 60 *方单位。以下显示了积分的一些经验法则(毕竟,本章旨在更新您的知识或使您对一些关键数学概念有基本理解):

  • 要找到常数的积分:

    a d x = a x + C

  • 要找到变量的积分:

    x d x = 1 2 x 2 + C

  • 要找到倒数的积分:

    1 x d x = ln | x | + C

  • 要找到指数的积分:

    a x d x = a x ln(a) + C

    e x d x = e x + C

微积分基本定理将导数与积分联系起来。这意味着它用积分来定义导数,反之亦然。 微积分的基本定理实际上由两部分组成:

第一部分

微积分基本定理的第一部分指出,如果您有一个连续的函数f(x),那么原始函数F(x),定义为f(x)的反导数,从固定的起点ax是一个在ax处处可微的函数,并且其导数简单地是在x处评估的f(x)

第二部分

微积分基本定理的第二部分指出,如果您有一个在某个区间[a, b]上连续的函数f(x),并定义一个新函数F(x)f(x)ax的积分,则该函数f(x)在相同区间[a, b]上的定积分可以计算为F(b) – F(a)

定理在许多领域都很有用,包括物理学和工程学,但优化和其他数学模型也受益于它。在不同学习算法中使用积分的一些例子可以总结如下:

密度估计

积分在密度估计中使用,这是许多机器学习算法的一部分,用于计算概率密度函数。

强化学习

积分在强化学习中用于计算奖励函数的期望值。强化学习在第十章中有详细介绍。

注意

本节的要点如下:

  • 积分也称为反导数,是导数的反向操作。

  • 不定积分在曲线下的整体面积,而定积分在由点a和点b界定的区间内。

  • 微积分基本定理是导数和积分之间的桥梁。

  • 在机器学习中,积分用于建模不确定性、进行预测和估计期望值。

优化

几种机器学习和深度学习算法依赖于优化技术来减少误差函数。

优化是在所有可能的解决方案中找到最佳解决方案的过程。优化就是找到函数的最高点和最低点。图 4-17 展示了以下公式的图形:

f ( x ) = x 4 - 2 x 2 + x

图 4-17. 函数图:f ( x ) = x 4 - 2 x 2 + x

局部最小值是指在x轴右侧的数值递减,直到达到一个开始递增的点。该点不一定是函数中的最低点,因此称为局部。在图 4-17 中,函数在点 A 处有局部最小值。

局部最大值是指在x轴右侧的数值递增,直到达到一个开始递减的点。该点不一定是函数中的最高点。在图 4-17 中,函数在点 B 处有局部最大值。

全局最小值是指在x轴右侧的数值递减,直到达到一个开始递增的点。该点必须是函数中的最低点,因此称为全局。在图 4-17 中,函数在点 C 处有全局最小值。

全局最大值是指在x轴右侧的数值递增,直到达到一个开始递减的点。该点必须是函数中的最高点。在图 4-17 中,没有全局最大值,因为函数将无限地继续而没有顶点。您可以清楚地看到函数如何向上加速。

当处理机器和深度学习模型时,目标是找到能使所谓的损失函数(给出预测误差的函数)最小化的模型参数(或输入)。如果损失函数是凸的,优化技术应当找到能使损失函数最小化的参数,趋向于全局最小值。

如果损失函数是非凸的,则不能保证收敛,优化可能只会导致接*局部最小值,这是目标的一部分,但这会忽略全局最小值,这才是最终的目标。

那么这些最小值和最大值是如何找到的呢?让我们一步一步来看:

  1. 第一步是进行第一阶导数测试(即计算函数的导数)。然后,将函数设为零并解出x将给出所谓的临界点。临界点是函数变换方向的点(数值停止向一个方向移动并开始向另一个方向移动)。因此,这些点是极大值和极小值。

  2. 第二步是进行二阶导数测试(即计算导数的导数)。然后,将函数设为零并解出x将给出所谓的拐点。拐点显示了函数凹向上和凹向下的地方。

换句话说,临界点是函数变换方向的地方,拐点是函数改变凹凸性的地方。图 4-18 显示了凹函数和凸函数之间的区别。

Concave up function = x 2

Concave down function = - x 2

图 4-18. 一个凹向上的函数和一个凹向下的函数

找到极值的步骤如下:

  1. 找到第一阶导数并将其设为零。

  2. 解第一阶导数以找到x。这些值称为临界点,它们代表函数变换方向的点。

  3. 将值插入公式中,这些值要么在临界点下要么在临界点上。如果第一阶导数的结果为正,则意味着在该点周围增长,如果为负,则意味着在该点周围减少。

  4. 找到第二阶导数并将其设为零。

  5. 解第二阶导数以找到x。这些值称为拐点,代表了凹凸性从向上变为向下或反之的点。

  6. 将值插入公式中,这些值要么在拐点下要么在拐点上。如果第二阶导数的结果为正,则意味着该点有一个最小值,如果为负,则意味着该点有一个最大值。

重要的是要理解,第一阶导数测试与临界点相关,而第二阶导数测试与拐点相关。以下示例找到函数的极值:

f ( x ) = x 2 + x + 4

第一步是取第一阶导数,将其设为零并解出x

f ' ( x ) = 2 x + 1

2 x + 1 = 0

x = - 1 2

结果表明在该数值处存在临界点。现在找出二阶导数:

f '' ( x ) = 2

接下来,必须将临界点代入二阶导数公式中:

f '' ( - 1 2 ) = 2

在临界点,二阶导数为正。这意味着在该点有一个局部最小值。

在接下来的章节中,您将看到更复杂的优化技术,如梯度下降和随机梯度下降,这些在机器学习算法中非常常见。请注意,您不必完全理解优化和解决未知变量的细节,因为算法将自行处理。

注意

本节的关键要点如下:

  • 优化是找到函数的极值点的过程。

  • 临界点是函数改变方向的点。

  • 拐点表明函数凹向上和凹向下的位置。

  • 损失函数是衡量预测机器学习中预测误差的函数。

总结

第二章、第三章和第四章介绍了主要的数值概念,帮助您开始理解基本的机器学习和深度学习模型。我已尽最大努力尝试尽可能简化技术细节。然而,我建议您至少阅读这三章两次,这样您学到的所有东西就会变得非常熟悉。我还鼓励您在其他材料中深入研究这些概念。

当然,深度学习需要更深入的数学知识,但我相信通过本章的概念,您可以开始涉足算法的创建。毕竟,它们是从包和库中预构建的,本章的目的是帮助您了解您正在使用的内容。使用过时的工具从头构建模型的可能性不大。

到现在为止,您应该已经对数据科学和数学要求有了一定的理解,可以舒适地开始。在您能够开始构建第一个机器学习模型之前,我们还有两个主题需要涵盖:技术分析和 Python 数据科学。

¹ 矩阵也可以包含符号和表达式,但出于简化的目的,让我们坚持使用数字。

第五章:介绍技术分析

技术分析提供许多类型的输入(解释变量),您可以在深度学习模型中使用它们。本章介绍了这一广泛领域,以便您在接下来的章节中掌握创建基于技术的学习模型所需的必要知识。

金融技术分析依赖于对价格行动历史的视觉解释,以确定市场可能的整体方向。它依赖于过去是未来最好的预测者的理念。技术分析这一广泛领域内有几种技术,特别是以下几种:

图表分析

这是将主观的视觉解释技巧应用于图表上。通常使用绘制支撑和阻力线以及回调等方法来寻找能够确定下一步走势的反转水*。

指标分析

这是使用数学公式创建客观指标的地方,这些指标可以是趋势跟随或反向的。已知的指标包括移动*均线相对强度指数(RSI),这两者在本章节中将有更详细的讨论。

模式识别

这是您监控某些重复配置并对其采取行动的地方。模式通常是偶尔出现的事件,呈现某种理论或实证结果。在金融领域,这更为复杂,但某些模式已被证明随着时间的推移增加了价值,部分原因可能是由于称为自我实现预言的现象(一种过程,初始预期导致其确认)。

让我们快速回顾一下技术分析的历史,这样您就可以更好地了解预期。技术分析依赖于三个原则:

历史会重演

在趋势和区间中,您可能会看到集群。此外,某些配置大多数情况下可能会产生类似的结果。

市场包含一切

假设当前价格已包含所有基本、技术和量化信息。

市场波动呈波浪形

由于不同的时间框架和需求,交易者以不同的频率买入和卖出,从而形成趋势和波浪,而不是直线。

不幸的是,技术分析在零售交易社区中被过度炒作和误用,这使得它在专业行业中的声誉略显不佳。每种分析方法都有其优势和劣势,有成功的基本分析、技术分析和量化投资者,但也有三个领域的失败投资者。

基本分析依赖经济和金融数据对特定证券或货币进行长期投资视角的判断,而量化分析则更为灵活,并且更常用于短期数据。它使用数学和统计概念进行预测或风险管理。

在其他假设中,技术分析表明市场并不有效,但这意味着什么呢?市场效率表明信息已经融入当前价格,而价格和价值是相同的。当你购买资产时,你希望它在基本分析术语中是被低估,或在技术分析术语中是超卖,这就是为什么你相信价格会上涨以达到价值。因此,你假设价值大于价格。

市场效率驳斥了价格不等于价值的任何主张,因此建议任何阿尔法交易都不应产生超过*均水*的回报(阿尔法交易是进行超过基准(通常是指数)的投机操作)。

市场效率假设是技术分析师的最大敌人之一,因为它的原则之一在弱式有效市场中,你无法通过技术分析获得超额收益。因此,技术分析在一开始就遭到质疑,随后基本分析也受到了打击。

公*地假设,在未来的某个时候,由于参与者数量和信息获取的便利性,市场将不得不变得有效。然而,正如政治和异常事件所示,市场往往远非有效。

注意

一个导致市场恐慌和非理性的政治事件例子是 2022 年爆发的俄乌战争。一个异常经济事件的例子是央行意外加息。

图表分析

在你理解什么是图表分析之前,你需要知道打开图表——更具体地说,是蜡烛图。

假设某只股票的市场开盘价为$100。在一个小时内发生了一些交易活动。我们还记录了在这段时间内的最高价($102)和最低价($98)。同时,记录了每小时的收盘价($101)。请记住,这四个数据分别称为开盘价最高价最低价收盘价(OHLC),它们代表了创建蜡烛图所需的四个基本价格。

蜡烛图非常简单直观。它们是沿时间线排列的盒状元素,包含 OHLC 数据。图 5-1 展示了关于蜡烛图如何运作的一切。

图 5-1. 左侧为看涨蜡烛图;右侧为看跌蜡烛图

看涨蜡烛图的收盘价高于其开盘价,而看跌蜡烛图的收盘价低于其开盘价。

蜡烛图是分析金融时间序列的常见方法之一。它比简单的折线图包含更多信息,并且比条形图具有更高的视觉可解释性。

注意

折线图是按时间顺序连接收盘价而创建的。这是绘制资产图表的最简单方式。它包含了三种图表类型中最少的信息,因为它只显示收盘价。

图表分析是通过主观绘图找到支撑和阻力线的任务。线,无论是水*还是斜线,是预测市场反应以下级别的核心:

支撑位

市场应该反弹的水*,因为暗示其周围的需求应该高于供给

阻力位

市场应该回落的水*,因为暗示其周围的供给应该高于需求

资产在时间轴上的方向可以分为三种:上升趋势,即价格创造更高的高点;下降趋势,即价格创造更低的低点;以及横盘(或波动),即价格在长时间内围绕同一水*波动。

图 5-2 显示欧元兑美元的水*支撑位接* 1.0840。通常,交易者在价格接*支撑时开始考虑买入。这是因为预期会出现向上的反应,因为力量*衡应更向需求(正面)方面转移,交易者接受支付更高价格,因为他们预计将来价格会更高(请记住前面讨论过的价格对价值的论点)。这里的含义是大多数交易者看到一个低于其价值的价格。

图 5-2. 欧元兑美元的蜡烛图显示支撑位于 1.0840

图 5-3 显示美元兑瑞士法郎的阻力水*接* 0.9030。通常,交易者在接*阻力时开始考虑做空市场。这是因为预期会出现向下的反应,因为力量*衡应更向供给方面转移。这里的含义是大多数交易者看到一个高于其价值的价格。

图 5-3. 美元兑瑞士法郎的蜡烛图显示阻力位于 0.9030

横盘(波动)市场更有把握水*支撑和阻力线会发挥作用。这是因为已经暗示了供需之间的一般*衡。因此,如果有过剩供应,市场会迅速调整,因为需求应该足以稳定价格。

图 5-4 展示了一个范围市场被困在两个水*水*之间;这是美元兑加元的情况(一个美元价格对加拿大元)。在范围市场中,每当市场接*阻力线时,你应该更有信心地认为将会出现下跌,而在接*支撑时,你应该更有信心地认为将会出现反弹,而不像在上升市场中那样,当市场接*阻力线时,你应该更有信心地认为将会出现下跌,而在接*支撑时,你应该更有信心地认为将会出现反弹。

图表分析同样适用于趋势市场。这体现在上升和下降通道的形式上。它们与水*水*具有相同的倾向,但有所偏见(稍后讨论)。

图 5-4. 美元兑加元蜡烛图显示支撑位在 1.4540,阻力位在 1.4620

图 5-5 展示了一个上升通道,在这里,支撑和阻力点随着时间的推移上升,反映出稳定增长的需求压力带来的看涨压力。

看到这种情况的交易者会预期市场接*上升通道的下部时会有看涨反应,而市场接*通道上部时则会有看跌反应。

这没有坚实的科学依据,因为没有规定市场必须*行移动,但可能是自我实现预言的原因,使得这种通道被认为具有预测性质。

图 5-5. 欧元兑美元蜡烛图显示一个上升通道

图 5-6 展示了一个下降通道,其中支撑和阻力点随时间下降,反映出稳定增长的供应压力带来的看跌压力。一般而言,看跌通道往往更为激进,因为恐惧主导了贪婪,卖方比买方更加恐慌。

图 5-6. 欧元兑美元蜡烛图显示一个下降通道

当处理上升和下降通道时,我提到了一种偏见。我将这种偏见称为隐形之手。原因如下:

“趋势是你的朋友。”这句话是股票投资者、投资顾问和金融分析师马丁·兹威格创造的,意味着在上升通道中,你需要更多地关注在市场回归支撑区域时的买入。这是因为你希望看涨压力的隐形之手增加你获胜交易的概率。类似地,在下降通道的情况下,你应该更多地关注市场达到上限时的卖空。兹威格格的格言的完整版本如下:“趋势是你的朋友,直到趋势结束时才会弯曲。”这意味着在任何时刻,市场都可能改变其制度,任何与趋势的友谊最终都会终止。最终,图表分析是主观的,更多地依赖于交易者或分析师的经验。

值得一提的是,除了通过视觉估计来绘制支撑和阻力水*外,还有许多其他找到支撑和阻力水*的方法:

斐波那契回撤

在这里,你可以使用斐波那契比率来确定反应水*。斐波那契回撤通常在市场向上或向下的趋势中计算,这样你就可以知道市场如果触及这些水*将会反转。这种方法的问题在于它非常主观,并且像任何其他技术一样并不完美。优点在于它提供了许多有趣的水*。

枢轴点

使用枢轴点,你使用简单的数学公式找到水*。基于昨天的交易活动,你使用公式预测今天未来的支撑和阻力水*。然后,每当市场接*这些水*,你都尝试通过朝相反方向交易来淡化移动。

移动*均线

这些将在下一节讨论。它们具有动态特性并跟随价格。你也可以用它们来检测当前的市场趋势。

提示

找到支撑和阻力水*的最佳方法是尽可能结合多种技术,这样你就可以有一定的方法融合,进而增加对初始想法的信心。交易是一个数字游戏,尽可能地增加胜算,应该最终提高你的系统表现。

指标分析

指标分析 是第二常用的技术分析工具。通常与绘图一同使用以确认你的初始想法。你可以把指标 看作助手。它们可以分为两类:

趋势跟随指标

用于检测和交易预期继续的趋势市场。因此,它们与移动的持续性相关。

逆向指标

用于淡化移动¹,最好在横盘市场中使用²,因为它们通常标志着初始移动的结束。因此,它们与预期的移动逆转相关(因此与移动的反持续性相关)。

下一节将介绍技术分析的两大支柱:移动*均线(趋势跟随)和相对强弱指数(逆向)。

注意

指标很重要,因为在后续章节中你会将它们作为不同学习算法的输入。

移动*均线

最著名的趋势跟随叠加指标是移动*均线。它的简单性使它无疑成为最受欢迎的工具之一。移动*均线有助于确认并跟随趋势。你还可以用它们来找到支撑和阻力水*、止损点和目标,以及理解潜在的趋势。

有许多类型的移动*均线,但最常见的是简单移动*均线,其中你取收盘价的滚动*均,如下公式所示:

Moving average i = Price i +Price i-1 +...+Price i-n n

图 5-7 显示了应用于 USDCAD 的 30 小时简单移动*均线。30 小时表示我计算最* 30 个小时的移动*均线,针对每小时的条形图。

图 5-7. 在 USDCAD 上显示的 K 线图,带有 30 小时简单移动*均线

移动*均线的经验法则包括以下内容:

  • 每当市场高于其移动*均线时,就会有牛市动量正在进行,你最好寻找买入机会。

  • 每当市场低于其移动*均线时,就会有熊市动量正在进行,你最好寻找卖空机会。

  • 每当市场穿过其移动*均线时,可以说动量已经改变,市场可能正在进入新的状态(趋势)。

你也可以结合移动*均线以获取信号。例如,每当短期移动*均线穿过长期移动*均线时,就会出现一个牛市交叉,市场可能会继续上涨。这也被称为金叉

相反,每当短期移动*均线穿越长期移动*均线下方时,就会出现一个熊市交叉,市场可能会继续下跌。这也被称为死亡交叉

图 5-8 显示了 USDCAD 与 10 小时(接*市场价格)和 30 小时移动*均线(远离市场价格)。

图 5-8. 在 USDCAD 上显示的 K 线图,带有 30 小时和 10 小时简单移动*均线

相对强弱指数

现在让我们看看反向指标。由 J. Welles Wilder Jr.首次引入³,相对强弱指数(RSI)是最流行和多功能的有界指标之一。它主要用作反向指标,极端值表明可以利用反应。使用以下步骤计算默认的 14 周期 RSI:

  1. 计算前一次收盘价格的变化。

  2. 将正净变化与负净变化分开。

  3. 计算正净变化和负净变化的*滑移动*均线。

  4. 将*滑的正变化除以*滑的绝对负变化。将此计算称为相对强度(RS)。

  5. 对每个时间步骤应用这个归一化公式来得到 RSI:

R S I i = 100 - 100 1+RS i

注意

*滑移动*均线是 RSI 的创始者开发的一种特殊类型的移动*均线。它比简单移动*均线更*稳、更稳定。

通常,默认情况下,RSI 使用 14 个周期的回溯期,尽管每位交易者可能对此有自己的偏好。以下是如何使用这个指标:

  • 每当相对强弱指标(RSI)显示 30 或更低的读数时,市场被认为是超卖状态,可能会出现向上的修正。

  • 每当 RSI 显示 70 或更高的读数时,市场被认为是超买的,可能会出现向下的修正。

  • 每当 RSI 超过或跌破 50 水*时,可能会出现新的趋势,但这通常是一个薄弱的假设,更多是理论性的而不是实际性的。

图 5-9 显示了 EURUSD 相对于其 14 周期 RSI 在第二面板中。指标应用于确认多头或空头偏见,并且在时机和分析当前市场状态方面非常有帮助。

图 5-9。EURUSD 小时线的值在顶部面板,14 周期 RSI 在底部面板

总结一下,指标可以用多种方式计算。两种最常用的是移动*均线和 RSI。

图案识别

模式是一种特定预测随后移动的重复配置。模式可以分为以下类型:

经典价格模式

这些被称为技术性反转价格模式,它们非常主观,并且由于在不考虑主观条件的情况下很难进行回测而被认为是不可靠的。然而,它们仍然被许多交易者和分析师使用。

时间模式

基于时间和价格的综合因素,这些模式不太为人所知,但如果正确使用,它们可以具有强大的预测能力。

蜡烛图形态⁴

这是使用 OHLC 数据来预测市场未来反应的地方。蜡烛图是可视化图表的最佳方式之一,因为它们包含许多可能标志反转或确认移动的模式。

经典价格模式指的是理论配置,如双顶和矩形。它们通常是反转或继续模式:

连续价格模式

这些是确认总体持续移动的配置。例如,矩形和三角形。

反转价格模式

这些是消退总体持续移动的配置。例如,头肩顶和双底。

传统图表分析师熟悉双顶和双底,它们标志着趋势反转并提供了这种反转的潜力。尽管它们很简单,但它们是主观的,有些并不像其他一些那样明显。

这阻碍了知道它们是否增加了价值。图 5-10 显示了双顶的插图,在验证图案后通常会给出一个空头偏见,这通常是打破连接两个顶部之间的低点线。这条线称为颈线

图 5-10。双顶插图

注意双顶中的这三个重要元素:

颈线

这是连接两个高峰之间的最低低点和图案的开始/结束的线。它用于确定回撤水*。

回撤

打破颈线后,市场应该朝着颈线做出绝望的尝试,但由于卖方利用该水*重新进场继续做空,未能进一步上涨。因此,回撤水*是在验证双顶后的理论上最佳卖出点。

潜力

这是双顶的目标。它被测量为模式顶部和向下投影的颈线之间的中点,从相同的颈线点开始。

双顶或双底可以有任何大小,但最好是对大多数市场参与者可见,以增加其影响力。从心理学角度来看,该模式的解释是在第二个顶部或底部时,市场未能将价格推高超过第一个峰值,因此显示出弱点,卖方可能会利用这一点。

这里有其他更为客观的模式;也就是说,它们具有明确的检测和启动规则。这些都基于明确的客观条件,不受分析师个人判断的影响。这有助于它们的回测和评估。

总结

技术分析提供了大量工具来数学、图形或甚至心理学(通过模式)分析市场。本章的学习目标是理解技术分析及其技术指标,以便在其作为解释变量时熟悉它们(正如在第十一章中所述)。

¹ “对走势的衰退” 是一种交易技术,其中您与正在进行的趋势相反地交易,希望能够把握其结束的时机。

² 横向市场通常处于均衡状态,没有特定的趋势描述它们。它们倾向于在接*彼此的顶部和底部之间摆动。

³ 请参阅 J. Welles Wilder Jr.(趋势研究)的《技术交易系统中的新概念》

⁴ 请参阅我的书籍《掌握金融模式识别》(O’Reilly),详细讨论蜡烛图模式。

第六章:为数据科学介绍 Python

这是我们深入进入机器学习和深度学习领域之前的最后一章。对于有经验的 Python 开发人员来说,本章是可选的,但对于没有扎实编程背景的人来说却很重要。了解算法背后的直觉是一个很大的优势,但如果你不能正确实现这些算法,你的知识将无法发挥作用。毕竟,这些算法需要编写代码才能工作,而不能手动操作,因此你需要理解基本的语法以及如何操作和转换数据。

由于本书不旨在成为 Python 编程的 A-Z 指南,本章仅侧重于一些基础知识和一些额外的技术,这些将有助于你顺利地浏览后续章节。

下载 Python

代码被定义为一组指令,旨在由计算机执行。通常需要特定的语法,以便计算机无误地应用一组指令。有许多编程语言,它们分为两大类:

低级编程语言

这些是通常用于编写操作系统和固件的机器语言。它们非常难以阅读。这些语言对硬件有相当高的控制级别。汇编语言是低级语言的一个例子。

高级编程语言

这些是用户友好的高级语言(抽象级别高)。它们通常用于编写程序和软件。高级语言的例子包括 Python 和 Julia。

本书中使用的编程语言是 Python,这是一种流行且多才多艺的语言,在研究和专业交易社区中广泛采用。正如你从本章标题中可能已经了解到的那样,你将会对 Python 以及开始构建自己脚本所需的工具有所了解。但在此之前,你需要下载 Python。

Python 解释器是用于编写和执行使用 Python 语法编写的代码的软件。我使用 Spyder。有些人可能更熟悉其他解释器,如 Jupyter 和 PyCharm,但处理过程是一样的。你可以从官方网站下载 Spyder,或者更好地作为一个更大的包 Anaconda 的一部分下载它,Anaconda 便于安装并提供更多工具。请注意,Spyder 是开源且免费使用的。

图 6-1 显示了 Python 控制台,代码输出显示在其中。

图 6-1 Spyder 的控制台

Python 文件的扩展名为.py,它们允许你保存代码并在以后引用它们。你还可以打开多个代码文件并在它们之间导航。

本章的大纲如下:

  • 理解 Python 语言以及如何编写无错误的代码

  • 了解如何使用控制流以及在时间序列分析中的重要性

  • 理解库和函数及其在简化编码中的作用

  • 理解不同类型的错误及其处理方法

  • 理解如何使用数据操作库如numpypandas

  • 最后,了解如何将历史财务时间序列数据导入 Python,以便使用适当的工具进行分析(这些工具我们已经讨论过,也将在接下来的章节中讨论)。

基本操作和语法

语法是定义编写可运行代码所需语句结构的一组规则。当您与计算机进行通信时,必须确保它能理解您,因此对语法有扎实的理解非常重要。

注释是非可执行代码,用于解释随后的可执行代码。注释用于让其他程序员理解代码。在 Python 中,注释前面带有井号(#):

# This is a comment. Comments are ignored by the interpreter
# Comments explain the code or give more details about its use
# Comments are written on one line; otherwise, you have to rewrite '#'
注意

请确保理解注释是非可执行的。这意味着当您运行(执行)代码时,解释器会忽略它们,不会返回错误。

有时候,您需要为您的代码编写文档,这可能需要多行代码(在某些情况下甚至是段落)。在每一行写上井号可能会很乏味且凌乱。这就是为什么有一种方法可以编写长注释。为此,您可以使用三个单引号将您的注释写在其中,如下所示:

'''
Python was created in the late 1980s by Guido van Rossum
The name "Python" was inspired by the comedy group Monty Python
'''

值得注意的是,三重引号称为文档字符串,实际上并不是注释(根据官方 Python 文档)。

让我们讨论变量和常量。常量是一个不变的值,而变量在给定事件时可以取不同的值。常量可以是数字 6,而变量可以是字母x,在一定条件或状态下可以取任何数字。变量使用=运算符进行定义:

# Defining a variable
x = 10
# Writing a constant
6

运行(执行)前面的代码将会在变量资源管理器中存储变量x及其相应值。同时,代码的输出将是6。变量是区分大小写的。因此:

# Declaring my_variable
my_variable = 1
# Declaring My_variable
My_variable = 2
# The variable my_variable is different from My_variable

变量声明不能以数字开头,但数字可以包含在变量声明的中间或末尾:

# Returns a SyntaxError
1x = 5
# Valid declaration
x1 = 5
# Valid declaration
x1x = 5

变量还可以包含下划线,但不能包含其他字符:

# Returns a SyntaxError
x–y = 5
# Valid declaration
x_y = 5

强烈建议变量使用简短而直接的名称。例如,考虑创建一个变量来保存某个移动*均线的回溯期(这是在第五章中介绍的技术指标):

# Recommended name
ma_lookback = 10
# Not recommended name
the_lookback_on_that_moving_average = 10

有几种不同的数据类型,具有不同的特性:

数值数据类型

这是最简单的数据类型,仅由数字组成。数值数据类型分为整数、浮点数和复数。整数是简单的整数(正数或负数),如 6 和-19。浮点数比整数更精确,因为它们包含逗号后的值,例如 2.7 和-8.09。复数包括虚数。¹

字符串

正如之前通过注释和文档字符串看到的那样,可以在代码旁边写入文本而不影响执行过程。字符串是表示字符序列的文本结构。字符串可以是函数的输入和参数,而不仅仅是注释。

布尔值

布尔值是用于评估给定表达式或条件的真值的二进制(真或假)数据类型。例如,可以使用布尔值来评估市场价格是否高于或低于 100 期移动*均线。

数据集合

这些是包含多个数据集的序列,每个数据集具有不同且独特的用途。数组是相同类型元素的序列(主要是数值)。在本书中经常会使用数组(使用一个名为numpy的 Python 库,在本章中进行讨论)。数据框是二维结构化数据表,也经常在本书中使用(使用一个名为pandas的 Python 库,在本章中也进行讨论)。集合是无序元素的序列。列表是有序元素的集合,可以是不同的数据类型。元组是有序的不可变元素集合,可以是不同的数据类型。用于存储固定值序列。字典表示键-值对的集合。

下面的代码片段显示了数值数据类型的几个示例:

# Creating a variable that holds an integer
my_integer = 1
# Creating a variable that holds a float number
my_float_number = 1.2
# Using the built-in Python function type() to verify the variables
type(my_integer)
type(my_float_number)

输出应如下所示(请注意,创建的两个变量将出现在变量资源管理器中):

int # The output of type(my_integer)
float # The output of type(my_float_number)

字符串只是文本。用来解释字符串的最常用示例是短语“Hello World”:

# Outputting the phrase Hello World
print('Hello World')

输出应如下所示:

Hello World

字符串也可以作为函数的参数,后面你会在本章看到。

布尔值是真或假值。下面的代码片段展示了它们的使用示例:

# Make a statement that the type of my_integer is integer
type(my_integer) is int
# Make a statement that the type of my_float_number is float
type(my_float_number) is float
# Make a statement that the type of my_integer is float
type(my_integer) is float
'''
Intuitively, the two first statements will return True as they are 
indeed true. The third statement is False as the variable my_integer
is an integer and not a float number
'''

上述代码的输出如下:

True
True
False

让我们讨论操作符的工作原理。你已经看到了一个操作符的示例:赋值操作符=用于定义变量。操作符在变量、常量甚至数据结构之间执行特殊的数学和其他任务。有不同类型的操作符。让我们从算术运算符开始,如下面的代码片段所示:

# Arithmetic operator - Addition
1 + 1 # The line outputs 2
# Arithmetic operator - Subtraction
1 – 1 # The line outputs 0
# Arithmetic operator - Multiplication
2 * 2 # The line outputs 4
# Arithmetic operator - Division
4 / 2 # The line outputs 2.0 as a float number
# Arithmetic operator - Exponents
2 ** 4 # The line outputs 16

下一个类型的运算符是比较运算符。这些用于比较不同的元素。它们在控制流事件中经常使用,如本章的下一节所述。以下代码片段显示了一些比较运算符:

# Comparison operator - Equality
2 == 2 # The line outputs True
# Comparison operator - Non equality
2 != 3 # The line outputs True
# Comparison operator - Greater than
2 > 3 # The line outputs False
# Comparison operator - Greater than or equal to
2 >= 2 # The line outputs True
# Comparison operator - Less than
2 < 3 # The line outputs True
# Comparison operator - Less than or equal to
2 <= 2 # The line outputs True

逻辑运算符结合两个或多个条件进行后续评估。有三个逻辑运算符:andornot。以下代码块展示了逻辑运算符的一个例子:

# Logical operator - and
2 and 1 < 4 # The line outputs True
2 and 5 < 4 # The line outputs False
# Logical operator - or
2 or 5 < 4 # The line outputs 2, which is the integer less than 4

数据收集结构(数组和数据帧)将在后续章节中讨论,因为它们由于复杂性和独特工具的原因需要深入介绍。让我们以结合到目前为止所讨论内容的代码结束这一部分:

# Declaring two variables x and y and assigning them values
x = 10
y = 2.5
# Checking the types of the variables
type(x) # Returns int
type(y) # Returns float
# Taking x to the power of y and storing it in a variable z
z = x ** y # Returns 316.22
# Checking if the result is greater than or equal to 100
z >= 100 # Returns True as 316.22 >= 100

控制流

条件语句构成了所谓控制流的第一部分(第二部分是稍后讨论的循环)。条件语句作为今天人工智能的祖先,只有在满足特定条件时才执行代码。

使用ifelifelse来管理条件语句。以下代码片段是一个例子:

# Declaring the variables
a = 9
b = 2
# First condition (specific)
if a > b:  
    print('a is greater than b')
# Second condition (specific)    
elif a < b:  
    print('a is less than b')
# Third condition (general)    
else:  
    print('a is equal to b')

因此,条件语句以if开始。然后,针对每个新的唯一和特定条件,使用elif(它是else if的融合),直到使用其余的概率宇宙作为独立的条件是有意义的,这是由else语句使用的。请注意,else语句不需要条件,因为它存在的目的是覆盖未覆盖的其余部分。

循环用于重复执行代码块,直到满足预定义条件。循环在时间序列中广泛用于计算指标、验证状态和回测交易策略。

使用for(用于迭代有限和定义的序列或一系列元素)和while(用于在满足条件之前继续迭代)语句管理循环。例如,以下代码使用循环打印值{1, 2, 3, 4}

# Using a for loop
for i in range(1, 5):
    print(i) 
# Using a while loop  
i = 1    
while i < 5:
    print(i)
    i = i + 1

翻译后的for循环简单地表示,对于范围从 1 开始到 5(不包括)的每个元素i(或任何其他字母,取决于编码器),在每次循环中打印i的值(因此,在第一次循环中,i的值为 1,在第二次循环中,其值为 2)。

while循环表示从i = 1开始,循环时打印其值,然后在完成第一次循环前将其加 1。当i变大于 4 时结束循环。

注意

理论上,while循环是无限的,直到另有指示。

值得注意的是,i = i + 1也可以表达为i += 1。算法的目标是以客观的方式递归地应用许多操作,这使得循环结合条件语句尤其有用。让我们看一个金融时间序列的例子:

  1. 创建一系列数值以模拟假设价格。

  2. 在循环数据范围内循环,创建条件:如果价格自上一时期以来上涨,则打印 1。类似地,如果价格自上一时期以来下跌,则打印-1。最后,如果价格与上一时期相同,则打印 0。

可以通过以下代码块完成:

# Creating the time series
time_series = [1, 3, 5, 2, 4, 1, 6, 4, 2, 4, 4, 4]
for i in range(len(time_series)):
    # The condition where the current price rose
    if time_series[i] > time_series[i – 1]:  
        print(1)
    # The condition where the current price fell
    elif time_series[i] < time_series[i – 1]:
        print(–1) 
    # The condition where the current price hasn't changed
    else: 
        print(0)

该代码定义了一个值列表(在本例中是一个称为time_series的时间序列),然后使用len()函数循环其长度以应用条件。请注意,在每个循环中,当前时间步被称为i,因此使得前一个时间步为i – 1

库和函数

在 Python 中,是一组预写代码,提供功能以便于创建应用程序。模块是独立的 Python 文件,包含可重复使用的代码和数据,可以被导入和在其他 Python 代码中使用,这在库中很常见。因此,模块是一个包含函数和其他类型代码的单个 Python 文件,可以被其他 Python 程序使用和导入。通过使用模块将相似的代码分成不同的文件,通常可以更轻松和有效地管理和维护大型代码库。

编程就是简化任务并使其更清晰的过程。函数在这方面至关重要。函数是一段可重复使用的代码块,在调用时执行特定任务。它只需要定义一次。例如,当您有一个重复的任务,比如计算时间序列的移动*均值时,可以使用函数,这样您就不必每次想使用它时都重新编写移动*均代码。相反,您定义函数与原始代码,然后在需要计算移动*均时调用它。

多个函数形成一个模块,多个模块形成一个库。一个库通常是主题导向的。例如,在本书中,sklearn库将与机器学习模型一起使用。类似地,数据处理和导入使用两个后面章节中讨论的库numpypandas。绘图和制图使用matplotlib库完成。

在使用之前必须将库导入 Python 解释器(这相当于承认它们的存在)。这样做的语法如下:

# The import statement must be followed by the name of the library
import numpy
# Optionally, you can give the library a shortcut for easier reference
import numpy as np

有时您只需要从库中导入一个函数或模块。为此,您不需要导入整个库:

# Importing one function from a library
from math import sqrt

上述代码说明了math是一个 Python 库,包含许多数学函数,特别是用于找出给定数字的*方根的sqrt函数。

让我们看看如何定义一个函数。使用def后跟函数名和任何可选参数来定义函数。以下示例创建了一个函数,用于对任意两个给定变量求和:

# Defining the function sum_operation and giving it two arguments
def sum_operation(first_variable, second_variable):
    # Outputting the sum of the two variables
    print(first_variable + second_variable)
# Calling the function with 1 and 3 as arguments
sum_operation(1, 3) # The output of this line is 4

注意

调用函数意味着执行其预期功能。换句话说,调用函数就是简单地使用它。函数的时间轴是定义和调用。

让我们看看如何从库中导入一个函数并使用其功能:

# Importing the library    
import math
# Using the natural logarithm function
math.log(10)
# Using the exponential function (e)
math.exp(3)
# Using the factorial function
math.factorial(50)

顺便提一下,阶乘操作是一种数学操作,用于计算从 1 到某个数字(这是math.factorial()中请求的参数)的所有正整数的乘积。

库可能不像 1 加 1 那么容易。有时,需要先安装外部库,然后才能将其导入 Python 解释器。安装可以通过命令提示符使用以下语法完成:

pip install library_name

回顾第三章,讨论了最大信息系数(MIC)。要计算 MIC,可以使用以下代码(在定义正弦和余弦波之后)。

# Importing the library
from minepy import MINE
# Calculating the MIC
mine = MINE(alpha = 0.6, c = 15)
mine.compute_score(sine, cosine)
MIC = mine.mic()
print('Correlation | MIC: ', round(MIC, 3))

直接导入库可能会导致错误,因为它尚未通过pip安装。因此,您必须首先使用提示符安装库,语法如下(不在 Python 解释器中):

pip install minepy

注意

您可能需要更新 Microsoft Visual C++(至少到版本 14.0)以避免在尝试运行minepy库时出现任何错误。

还需要仔细阅读库随附的文档,以便正确使用它们。库的文档将解释函数的目的,以及每个函数可以接受的参数类型(例如,字符串或数字)。

现在让我们回到函数的主题。函数可以有一个return语句,允许将结果存储在变量中,以便在代码的其他部分中使用。

让我们举两个简单的例子,然后逐步讨论它们:

# Defining a function to sum two variables and return the result
def sum_operation(first_variable, second_variable):
    # The summing operation is stored in a variable called final_sum
    final_sum = first_variable + second_variable
    # The result is returned
    return final_sum
# Create a new variable that holds the result of the function    
summed_value = sum_operation(1, 2)
# Use the new variable in a new mathematical operation
double_summed_value = summed_value * 2

先前的代码定义了一个名为sum_operation的函数,该函数接受两个参数,然后将操作存储在一个名为final_sum的变量中,然后将其返回,以便可以在外部存储它。接下来,定义了一个名为summed_value的新变量,它作为函数的输出。最后,创建了另一个变量double_summed_value,它是summed_value乘以 2 的结果。这是如何在外部操作中使用函数结果作为变量的示例。现在让我们考虑一个嵌套函数的例子(请记住之前定义的sum_operation函数):

# Defining a function to square the result gotten from sum_operation()
def square_summed_value(first_variable, second_variable):
    # Calling the nested sum_operation function and storing its result
    final_sum = sum_operation(first_variable, second_variable) 
    # Creating a variable that stores the square of final_sum
    squared_sum = final_sum ** 2
    # The result is returned    
    return squared_sum
# Create a new variable that holds the result of the function   
squared_summed_value = square_summed_value(1, 2)

上述代码片段定义了一个名为square_summed_value的函数,它接受两个参数。此外,它使用了一个嵌套函数,在本例中是sum_operation。嵌套函数的结果再次存储在一个名为final_sum的变量中,该变量作为在找到squared_sum变量时的输入。该变量以final_sum的*方的形式找到。

让我们结束 Python 和机器学习中常见库的部分(除了numpypandas):

  • matplotlib:用于绘制和可视化数据

  • sklearn:用于机器学习模型

  • scipy:用于科学计算和优化

  • keras:用于神经网络

  • math:用于使用数学工具,如*方根

  • random:用于生成随机变量

  • requests:用于进行网页抓取的 HTTP 请求

异常处理和错误

很多时候,当代码执行时出现错误,并且解释器遇到阻止其进一步执行的障碍时会发生错误。最基本的错误是SyntaxError,当单词拼写错误或缺少使代码无法理解的元素时会发生:

# Will not output a SyntaxError if executed
my_range = range(1, 10)
# Will output a SyntaxError if executed
my_range = range(1, 10

从前面的代码可以看出,第二行代码末尾缺少括号,解释器无法理解。这种类型的错误可能是您最常见的错误之一。另一个常见错误是NameError,当在执行包含它的代码之前未定义变量时会发生。考虑以下示例:

x + y

由于解释器不知道xy的值,因为它们没有定义,上述代码将给您一个NameError

当解释器无法找到您尝试导入的库或模块时,将发生ModuleNotFoundError。这通常发生在库或模块安装在错误的目录或未正确安装时。解决此问题的常见方法包括:

  • 验证模块的名称是否正确写入

  • 验证模块是否正确pip安装

  • 验证模块是否安装在正确的位置

另一种常见错误类型是TypeError,当您在不兼容的元素上应用某个操作时会发生,例如将整数与字符串相加。以下操作会引发TypeError

# Defining variable x
x = 1
# Defining variable y
y = 'Hello
# Summing the two variables, which will raise a TypeError
x + y

在时间序列分析中,您可能会遇到以下四种错误:

IndexError

当引用当前数组或数据框超出范围的索引时,将引发此错误。想象一下,有一个包含 300 个值(行)的数组。如果您想循环遍历它们,并在每个循环中在下一个单元格(时间步长+1)中输入数字 1,那么解释器将引发IndexError,因为在最后一个循环中没有下一个单元格。

ValueError

当尝试用无效参数调用函数时,将引发此错误。例如,试图在调用函数时将整数元素作为字符串传递。

KeyError

当尝试访问不存在的数据框中的元素时会发生这种情况。例如,如果数据框中有三列,并且您引用一个不存在的列(可能由于语法问题),那么您可能会遇到KeyError

ZeroDivisionError

这个错误很直观,在试图除以零时发生。

您可能会遇到其他类型的错误。重要的是要理解它们指的是什么,以便能够修复它们并使代码再次运行。

异常 是可能不会致命到代码的错误,因为它们只是显示一个警告,但不一定终止代码。因此,异常发生在代码执行期间(与错误相反,错误是因为解释器无法执行代码而发生的)。要忽略某些异常(和错误),使用 tryexcept 关键字。当你确信处理异常不会改变代码的输出时,这是很有用的。

让我们以创建一个将时间序列的第一列除以第二列的下一个值为例。第一步是将时间序列定义为数据框或数组(或任何其他数据收集结构):

# Importing the required library to create an array
import numpy as np
# Creating a two-column list with 8 rows
my_time_series = [(1, 3), 
                  (1, 4), 
                  (1, 4), 
                  (1, 6), 
                  (1, 4), 
                  (0, 2), 
                  (1, 1), 
                  (0, 6)]
# Transforming the list into an array
my_time_series = np.array(my_time_series)

现在让我们编写一个除法函数,它将获取第一列中的任何值,并将其除以第二列中的下一个值:

# Defining the function
def division(first_column, second_column):
    # Looping through the length of the created array
    for i in range(len(my_time_series)):
        # Division operation and storing it in the variable x
        x = my_time_series[i, first_column] / 
            my_time_series[i + 1, second_column]
        # Outputting the result
        print(x)
# Calling the function
division(0, 1)

运行前两个代码块将会产生一个 IndexError,因为在最后一个循环中,函数找不到第二列的下一个值,因为它不存在:

IndexError: index 8 is out of bounds for axis 0 with size 8

通过 tryexcept 来修复这个问题将忽略导致问题的最后一次计算,并返回期望的结果:

# Defining the function
def division(first_column, second_column): 
    # Looping through the length of the created array    
    for i in range(len(my_time_series)): 
        # First part of the exception handling
        try:
            # Division operation and storing it in the variable x
            x = my_time_series[i, first_column] / 
                my_time_series[i + 1, second_column] 
            # Outputting the result            
            print(x)       
        # Exception handling of a specific error     
        except IndexError:
            # Ignoring (passing) the error
            pass
# Calling the function
division(0, 1)

输出如下:

0.25
0.25
0.16
0.25
0.50
0.00
0.16

numpy 和 pandas 中的数据结构

您现在了解了什么是库,并且知道 numpypandas 是在 Python 中操作、处理和导入数据的首选库。本节讨论了两者之间的区别,以及绝对是您数据分析工具箱中的重要功能。但首先,让我们定义这两个库:

numpy

numpy 是 Numerical Python 的缩写,是一个允许使用多维数组和矩阵的 Python 库。它提供了一个强大的接口,用于对数组和矩阵执行各种操作。

pandas

pandas 是 Panel Data 的缩写,是一个允许使用数据框(一种类型的表格数据)的 Python 库。它提供了两种主要的数据结构:series 和 dataframes。series 是一个类似数组的一维对象,可以保存任何数据类型。dataframe 是一个二维表格状结构,由行和列组成(类似于电子表格)。

这两个库在分析时间序列数据方面非常有用。数组仅保存数值类型数据,因此实际上不保存日期类型数据。这可能是使用 pandas 而不是 numpy 的优点之一,但两者都有各自的优势和相对的弱点。最终,这是一个选择问题。本书将优先使用 numpy,因为它简单,并且下一章中的机器学习模型使用 sklearn 库,该库应用于数组。

注意

numpypandas 之间切换需要转换时间序列类型。这是一个相对简单的任务,但有时可能会导致某些类型数据的丢失(例如,日期数据)。

在讨论它们的潜力之前,让我们导入这两个库:

import numpy as np
import pandas as pd

以下代码创建了两个具有两列和三行的时间序列。第一个时间序列称为my_data_frame,使用pandas库函数pd.DataFrame创建。第二个时间序列称为my_array,使用numpy库函数np.array创建:

# Creating a dataframe
my_data_frame = pd.DataFrame({'first_column' : [1, 2, 3], 
                              'second_column' : [4, 5, 6]})
# Creating an array
my_array = np.array([[1, 4], [2, 5], [3, 6]])

如图 6-2 所示,数据框具有真实的索引,可以有列名。数组只能容纳一种数据类型。

图 6-2。pandas数据框(左)和numpy数组(右)

要在这两种类型的数据之间切换,您将使用前面代码块中使用的相同两个函数:

# To transform my_data_frame into my_new_array
my_new_array = np.array(my_data_frame)
# To transform my_array into my_new_data_frame
my_new_data_frame = pd.DataFrame(my_array)

现在让我们看看在处理模型时会非常有用的一些函数。切片和连接是您必须掌握的过程之一,以便在数据分析中顺利导航。考虑以下数组:

first_array  = np.array([ 1,  2,  3,  5,   8,  13])
second_array = np.array([21, 34, 55, 89, 144, 233])

连接是将两个数据集合并在一起的行为,可以通过行(axis = 0)或列(axis = 1)来进行。让我们都来做一下:

# Reshaping the arrays so they become dimensionally compatible
first_array  = np.reshape(first_array, (–1, 1))
second_array = np.reshape(second_array, (–1, 1))
# Concatenating both arrays by columns
combined_array = np.concatenate((first_array, second_array), axis = 1)
# Concatenating both arrays by rows
combined_array = np.concatenate((first_array, second_array), axis = 0)

现在让我们对数据框做同样的事情。考虑以下数据框:

first_data_frame  = pd.DataFrame({'first_column'  : [ 1,  2,  3], 
                                  'second_column' : [ 4,  5,  6]})
second_data_frame = pd.DataFrame({'first_column'  : [ 7,  8,  9], 
                                  'second_column' : [10, 11, 12]})

当您希望将数据合并为一个结构时,连接非常有用。这是如何在数据框中完成的(请注意,这只是语法和函数源的简单更改):

# Concatenating both dataframes by columns
combined_data_frame = pd.concat([first_data_frame, second_data_frame], 
                                axis = 1)
# Concatenating both dataframes by rows
combined_data_frame = pd.concat([first_data_frame, second_data_frame], 
                                axis = 0)

记住,对于时间序列,(水*单元格)代表一个时间步(例如,每小时),其中包含所有的数据,而则代表不同类型的数据(例如,金融工具的开盘价和收盘价)。现在让我们看看数组的切片技术:

# Defining a one-dimensional array
my_array = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
# Referring to the first value of the array
my_array[0] # Outputs 1
# Referring to the last value of the array
my_array[–1] # Outputs 10
# Referring to the sixth value of the array
my_array[6] # Outputs 7
# Referring to the first three values of the array
my_array[0:3] # Outputs array([1, 2, 3])
my_array[:3]  # Outputs array([1, 2, 3])
# Referring to the last three values of the array
my_array[–3:] # Outputs array([8, 9, 10])
# Referring to all the values as of the second value
my_array[1:] # Outputs array([2, 3, 4, 5, 6, 7, 8, 9, 10])
# Defining a multidimensional array
my_array = np.array([[ 1,  2,  3,  4,  5], 
                     [ 6,  7,  8,  9, 10], 
                     [11, 12, 13, 14, 15]])
# Referring to the first value and second column of the array
my_array[0, 1] # Outputs 2
# Referring to the last value and last column of the array
my_array[–1, –1] # Outputs 15
# Referring to the third value and second-to-last column of the array
my_array[2, –2] # Outputs 14
# Referring to the first three values and fourth column of the array
my_array[:, 2:4] # Outputs array([[3, 4], [8, 9], [13, 14]])
# Referring to the last two values and fifth column of the array
my_array[–2:, 4] # Outputs array([10, 15])
# Referring to all the values and all the columns up until the second row
my_array[:2, ] # Outputs array([[ 1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
# Referring to the last row with all the columns
my_array[–1:, :] # Outputs array([[11, 12, 13, 14, 15]])

注意

重要的是要知道,Python 的索引从零开始。这意味着要引用数据结构中的第一个元素,您需要将其索引为 index = 0。值得注意的是,在范围内,最后一个元素是被排除的,这意味着数据结构中的前三个元素被称为 [0, 3],这将给出索引为 0、1 和 2 的元素。

现在让我们看看数据框的同样的事情,这样当您想要操作数据结构时,这节就会成为一种迷你参考:

# Defining a one-dimensional dataframe
my_df= pd.DataFrame({'first_column': [1, 2, 3, 4, 5, 
                                      6, 7, 8, 9, 10]})
# Referring to the first value of the dataframe
my_df.iloc[0]['first_column'] # Outputs 1
# Referring to the last value of the dataframe
my_df.iloc[–1]['first_column'] # Outputs 10
# Referring to the sixth value of the dataframe
my_df.iloc[6]['first_column'] # Outputs 7
# Referring to the first three values of the dataframe
my_df.iloc[0:3]['first_column'] # Outputs ([1, 2, 3])
# Referring to the last three values of the dataframe
my_df.iloc[–3:]['first_column'] # Outputs ([8, 9, 10])
# Referring to all the values as of the second value
my_df.iloc[1:]['first_column'] # Outputs ([2, 3, 4, 5, 6, 7, 8, 9, 10])
# Defining a multidimensional dataframe
my_df  = pd.DataFrame({'first_column'  : [ 1,  6,  11], 
                       'second_column' : [ 2,  7,  12],
                       'third_column'  : [ 3,  8,  13],
                       'fourth_column' : [ 4,  9,  14],
                       'fifth_column'  : [ 5,  10, 15]})
# Referring to the first value and second column of the dataframe
my_df.iloc[0]['second_column'] # Outputs 2
# Referring to the last value and last column of the dataframe
my_df.iloc[–1]['fifth_column'] # Outputs 15
# Referring to the third value and second-to-last column of the dataframe
my_df.iloc[2]['fourth_column'] # Outputs 14
# Referring to the first three values and fourth column of the dataframe
my_df.iloc[:][['third_column', 'fourth_column']]
# Referring to the last two values and fifth column of the dataframe
my_df.iloc[–2:]['fifth_column'] # Outputs ([10, 15])
# Referring to all the values and all the columns up until the second row
my_df.iloc[:2,] # Outputs ([[ 1, 2, 3, 4, 5], [6, 7, 8, 9, 10]])
# Referring to the last row with all the columns
my_df.iloc[–1:,]  # Outputs ([[11, 12, 13, 14, 15]])

注意

尝试回到前面的章节执行那里提供的代码。现在你应该有更加牢固的理解了。

在 Python 中导入金融时间序列

本节讨论了部署机器和深度学习算法的关键方面。它涉及到运行模型和评估其性能所需的历史 OHLC 数据。

第一步是准备算法成功所需的环境和其他一切。为此,您需要两个程序:

  • 您用来编写和执行代码的 Python 解释器。您已经完成了这一步骤。

  • 您用作数据库的制图和金融软件。本节涵盖了这部分内容。

用于制定基准图表的是 MetaTrader 5,这是全球许多交易员使用的程序。MetaTrader 5 与 Spyder 一起工作,因此您应该先下载 Spyder 并熟悉其工作方式。

从 官方网站 下载并安装 MetaTrader 5. 你需要创建一个演示账户,这只是一个带有虚拟货币的虚拟账户。单词 demo 并不是指使用的时间有限,而是指它不使用真实货币。

要开设账户,请选择 文件 > 开设账户,选择 MetaQuotes Software Corp,然后点击下一步。然后选择第一个选项开设演示账户;这将允许你交易虚拟货币。最后,输入一些基本信息,如你的姓名、电子邮件和账户类型。你将不会收到验证请求或任何类型的确认,因为演示应直接启动,让你查看图表。

图 6-3 展示了*台的界面。默认情况下,MetaTrader 5 并不显示其覆盖的所有市场,因此,如果需要导入和可视化,你需要使其可访问。点击 View,点击 Market Watch,然后右键点击任何新标签中显示的符号,并选择 Show All。这样,你可以看到更多市场的扩展列表。

在你开始编码之前,你需要安装 MetaTrader 5 Python 集成库,以便稍后在 Spyder 中使用它。这很简单,只需一步。打开 Anaconda 提示符,输入:

pip install MetaTrader5

安装是让你在解释器中使用为 MetaTrader 5 设计的 Python 模块和函数的桥梁。

图 6-3. MetaTrader 5 界面

以下代码块使用了内置语句 import,该语句调用了内部(自创建)或外部(第三方创建)的库。你会记得库是函数的存储库,因此,你需要导入与你想要做的事情相关的库。为了演示目的,导入以下模块、包和库:

import datetime # Gives tools for manipulating dates and time
import pytz # Offers cross-platform time zone calculations
import MetaTrader5 as mt5 # Importing the software's library
import pandas as pd
import numpy as np 

下一步是创建你可以导入的时间框架的宇宙。尽管我将向你展示如何分析和回测小时数据,你可以定义一个更广泛的宇宙,如下面的代码片段所示:

frame_M15 = mt5.TIMEFRAME_M15      # 15-minute time frame
frameframe_M30 = mt5.TIMEFRAME_M30 # 30-minute time frame
frame_H1 = mt5.TIMEFRAME_H1        # Hourly time frame
frame_H4 = mt5.TIMEFRAME_H4        # 4-hour time frame
frame_D1 = mt5.TIMEFRAME_D1        # Daily time frame
frame_W1 = mt5.TIMEFRAME_W1        # Weekly time frame
frame_M1 = mt5.TIMEFRAME_MN1       # Monthly time frame

注意

完整代码可在本书的 GitHub 仓库 中找到,名称为 master_function.py

时间框架 是你记录价格频率的方式。使用小时数据时,你将每小时记录最后一个价格。这意味着在一天内,你可以有多达 24 个小时价格。这允许你查看价格的日内演变。目标是在特定期间内记录全部的 OHLC 数据。

以下代码定义了当前时间,这样算法在导入数据时就有了一个参考点。基本上,你正在创建一个存储当前时间和日期的变量:

now = datetime.datetime.now()

现在我们继续定义您想要进行回测的金融工具的全集。在本书中,回测将专注于外汇市场(FX)。因此,让我们创建一个变量来存储一些关键的货币对:

assets = ['EURUSD', 'USDCHF', 'GBPUSD', 'USDCAD']

现在,您已准备好时间和资产变量,只需创建导入算法的结构。函数get_quotes()完成此任务:

def get_quotes(time_frame, year = 2005, month = 1, day = 1, 
               asset = "EURUSD"):
    if not mt5.initialize():   
        print("initialize() failed, error code =", mt5.last_error())
        quit()
    timezone = pytz.timezone("Europe/Paris")
    time_from = datetime.datetime(year, month, day, tzinfo = timezone)   
    time_to = datetime.datetime.now(timezone) + datetime.timedelta(days=1)
    rates = mt5.copy_rates_range(asset, time_frame, time_from, time_to)
    rates_frame = pd.DataFrame(rates)
    return rates_frame

注意,在get_quotes()函数中,您使用了pytzpandas库。该函数首先定义了 Olson 时区,²您可以自行设置。以下是一个简要的、不详尽的时间区域列表,根据您的时区,您可以输入以下内容:

America/New_York
Europe/London
Europe/Paris
Asia/Tokyo
Australia/Sydney

接下来定义两个名为time_fromtime_to的变量:

  • 变量time_from包含指向导入日期开始的datetime(例如,2020 年 01 月 01 日)。

  • 变量time_to包含指向导入日期结束的datetime,它使用变量now表示当前时间和日期。

下一步是创建一个变量,使用您指定的时间段导入金融数据。通过rates变量使用mt5.copy_rates_range()函数完成。最后,使用pandas将数据转换为数据框。导入过程所需的最终函数是mass_import()函数。它允许您使用变量选择时间范围,然后使用get_quotes()函数导入和格式化数据到数组。以下代码片段定义了mass_import()函数:

def mass_import(asset, time_frame):               
    if time_frame == 'H1':
        data = get_quotes(frame_H1, 2013, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)           
    return data 

函数mass_import()自动将数据框转换为数组,因此在使用自动导入时无需担心转换问题。

注意

如果使用mass_import()函数时出现空数组,可能需要将年份参数调整得更高,以获取数据。例如,如果在mass_import()函数中得到空数组,尝试在get_quotes()函数中使用更新一些的年份(“2014”而不是“2013”)。

要导入从 2014 年初至今的历史小时级 EURUSD 数据,可以键入以下内容(假设已经定义了get_quotes()now、数据框和相关库):

# Defining the universe of currency pairs
assets = ['EURUSD', 'USDCHF', 'GBPUSD', 'USDCAD']
# Redefining the mass_import function to switch to a default 2014
def mass_import(asset, time_frame):                
    if time_frame == 'H1':
        data = get_quotes(frame_H1, 2014, 1, 1, asset = assets[asset])
        data = data.iloc[:, 1:5].values
        data = data.round(decimals = 5)  
# Calling the mass_import function and storing it in a variable
eurusd_data = mass_import(0, 'H1')
注意

注意在mass_import函数中如何使用return语句将历史数据存储在选择的变量中。

尽管 MetaTrader 5 有 macOS 版本,但 Python 库只在 Windows 上运行。在 macOS 或 Linux 上,您可能需要尝试手动导入方法(或者在第七章中提出的使用pandas-datareader库的替代方法)。

自动导入是一个巨大的时间节省器,但即使是 Windows 用户也可能遇到令人沮丧的错误。因此,我将向你展示手动导入的方法,这可以作为解决方法。在本书的Github 页面上,你会找到一个名为Historical Data的文件夹。文件夹内包含一系列以 Excel 格式存储的历史金融时间序列,可以下载。

手动导入需要一个包含从第三方下载的 OHLC 数据的 Excel 文件(例如本书 GitHub 存储库中提供的 Excel 文件)。在这种情况下,你可以使用pandas库来导入并将其转换为数组。

让我们以Daily_GBPUSD_Historical_Data.xlsx**.为例。从存储库(在Historical Data中找到)下载文件并存储在桌面上。Spyder 目录必须与文件处于同一位置。通俗地讲,这意味着 Spyder 必须在你的桌面上搜索 Excel 文件。要选择正确的目录,点击箭头旁边的文件夹按钮。目录选项卡应该看起来像图 6-4。

图 6-4. 选择正确文件夹后的目录选项卡

你应该会得到一个单独的窗口,你可以在其中选择桌面位置,然后验证选择。完成这些步骤后,选项卡应该看起来像图 6-5。

图 6-5. 选择正确桌面位置后的目录选项卡

你可以使用内置于pandas中的read_excel()函数来获取 Excel 文件中的值。按照以下语法操作:

# Importing the excel file into the Python interpreter
my_data = pd.read_excel('Daily_GBPUSD_Historical_Data.xlsx.xlsx')

你应该有一个名为Daily_GBPUSD_Historical_Data.xlsx的数据框,其中包含五列,分别代表开盘价、最高价、最低价和收盘价。通常在使用属于它的函数之前,你必须输入库的名称;这就是为什么read_excel()之前要加上pd的原因。

注意

由于兼容性问题,我建议 Windows 用户使用自动导入,而 macOS 用户使用手动导入。

概要

Python,作为编程语言中的主要明星,得到了开发者社区的广泛采纳。掌握它对于开启数据科学领域的巨大潜力至关重要。

下一章将讨论机器学习和不同的预测算法。主要目标是能够编写这些算法并在金融数据上运行回测。你会发现,一旦开始理解这个过程,只需移除一个算法并插入另一个(如果它们具有相同的假设),就能轻松应对。热身章节结束了,现在是时候开始编程了。

¹ 虚数是一种表示负数*方根的复数类型。

² 这个 Olson 时区命名自其创建者阿瑟·大卫·奥尔森,处理与时区数据和夏令时规则相关的问题。对于开发人员和计算机系统来说,它是处理时间相关功能和转换的重要资源。

第七章:时间序列预测的机器学习模型

机器学习是人工智能的一个子领域,专注于开发能够使计算机在没有明确编程的情况下学习和进行预测或决策的算法和模型,因此被称为学习。机器学习涉及设计和构建能够从经验中自动学习和改进的系统,通常通过分析和从大量数据中提取模式来实现。

本章介绍了使用机器学习模型进行时间序列预测的框架,并讨论了一些已知的算法选择。

框架

框架非常重要,因为它组织了整个研究过程的方式(从数据收集到性能评估)。拥有适当的框架确保在回测中保持协调,从而允许在不同的机器学习模型之间进行适当的比较。框架可能遵循以下时间顺序步骤:

  1. 导入和预处理历史数据,这些数据必须包含足够数量的值,以确保良好的回测和评估。

  2. 进行训练-测试拆分,将数据分为两部分,其中数据的第一部分(例如从 2000 年到 2020 年)保留用于训练算法,以便理解预测未来值的数学公式,而数据的第二部分(例如从 2020 年到 2023 年)保留用于测试算法在其从未见过的数据上的表现。

  3. 使用算法对数据进行拟合(训练)和预测(测试)。

  4. 运行性能评估算法以了解模型在过去的表现。

注意

训练集也称为样本内数据,而测试集也称为样本外数据

框架的第一步已在第六章中讨论过。现在您应该能够使用 Python 轻松导入历史数据。训练-测试拆分将历史数据分为训练(样本内)集,其中模型被拟合(训练),以找到一个隐含的预测函数,以及测试(样本外)集,在测试集上应用和评估在训练集上计算的预测函数。理论上,如果模型在测试集上表现良好,那么您可能有一个潜在的交易策略候选,但这仅仅是第一步,现实远比这复杂得多。

为了一切顺利,从 GitHub 仓库 下载 master_function.py,然后设置 Python 解释器(例如 Spyder)的目录与下载的文件位于同一位置,以便您可以将其作为库导入并使用其函数。例如,如果您将文件下载到桌面,您可能希望将目录设置为 第 6-5 图 所示的 第 6 章 的位置。通常可以在 Spyder 的右上角找到目录选项(变量资源管理器上方)。

注意

如果您不想导入 master_function.py,您可以像正常文件一样在解释器中打开它并执行它,以便 Python 定义内部的函数。但是,每次重新启动内核时都必须执行此操作。

现在,对时间序列进行预处理(转换)并将其分割成四个不同的数组(或数据框),每个数组都具有以下实用性:

数组 x_train

解释您希望预测变量变化的样本内特征集(即独立变量)。它们是预测器。

数组 y_train

样本内的因变量集(即正确答案),您希望模型在其上校准其预测函数。

数组 x_test

作为模型的测试的样本外特征集,以查看它在此前未见数据上的表现。

数组 y_test

包含模型必须接*的实际值。换句话说,这些是将与模型预测进行比较的正确答案。

在分割之前,了解正在预测的内容以及用于预测的内容非常重要。在本章中,滞后价格差(收益率)将用于预测。通常,在执行此操作之前必须进行一些测试,但为了简单起见,让我们将它们略过,并假设最*的 500 个日常 EURUSD 收益率对当前收益率具有预测能力,这意味着您可以找到一个预测公式,该公式使用最*的 500 个观测值来观察下一个观测值:

因变量(预测)

EURUSD 日间时间框架中的 t+1 回报。这也称为 y 变量。

自变量(输入)

EURUSD 的最* 500 个日回报。这些也称为 x 变量。

图 7-1 显示了某一时间段内 EURUSD 的日收益率。注意其稳态外观。根据 ADF 测试(见 第 3 章),收益率数据集似乎是稳态的,适合进行回归分析。

图 7-1. EURUSD 的日收益率。
注意

在本章中,特征(x 值)将是 EURUSD 滞后日价格差。¹ 在后续章节中,将使用滞后收益率或技术指标值作为特征。请注意,您可以使用任何您认为值得被视为预测的特征。

时间框架(每日)的选择非常适合希望获得一日市场视图并在当天结束前关闭仓位的交易者。

让我们首先使用虚拟回归模型作为第一个基本示例。虚拟回归是一种比较机器学习算法,仅用作基准,因为它使用非常简单的规则进行预测,不太可能增加任何真正的预测价值。虚拟回归的真正实用性在于看看你的真实模型是否胜过它。作为提醒,机器学习算法遵循以下步骤:

  1. 导入数据。

  2. 预处理并分割数据。

  3. 训练算法。

  4. 使用训练参数在测试数据上进行预测。此外,为了比较,还要在训练数据上进行预测。

  5. 绘制和评估结果。

首先导入本章所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, 
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在导入您将使用的算法的特定库:

from sklearn.dummy import DummyRegressor

下一步是导入和转换收盘价数据。请记住,您正在尝试预测每日收益,这意味着您必须仅选择收盘列,然后对其应用差分函数,以使价格差异化:

# Importing the differenced close price of EURUSD daily time frame
data = np.diff(mass_import(0, 'H1')[:, 3])
注意

在金融中,术语收益通常指的是投资或某种资产产生的收益或损失,它可以通过当前资产价值与先前某个时间点的价值之间的差异来计算。这本质上是一种差分形式,因为您正在计算资产价值的变化或差异。

在时间序列分析中,差分是一种常用技术,用于使时间序列数据*稳化,这对于各种分析都很有帮助。差分涉及从彼此减去连续观测值,以消除趋势或季节性,从而关注数据的变化。

接下来,设置算法的超参数。对于这些基本算法而言,这将是滞后数(预测器数)和数据的百分比分割:

# Setting the hyperparameters
num_lags = 500
train_test_split = 0.80

0.80 的train_test_split表示 80%的数据将用于训练,而剩余的 20%将用于测试。

用于拆分和定义回测所需的四个数组的函数可以定义如下:

def data_preprocessing(data, num_lags, train_test_split):
    # Prepare the data for training
    x = []
    y = []
    for i in range(len(data) – num_lags):
        x.append(data[i:i + num_lags])
        y.append(data[i+ num_lags])
    # Convert the data to numpy arrays
    x = np.array(x)
    y = np.array(y)
    # Split the data into training and testing sets
    split_index = int(train_test_split * len(x))
    x_train = x[:split_index]
    y_train = y[:split_index]
    x_test = x[split_index:]
    y_test = y[split_index:]
    return x_train, y_train, x_test, y_test

调用函数创建四个数组:

# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, 
                                                      num_lags, 
                                                      train_test_split)

您现在应该看到变量资源管理器中出现了四个新数组。下一步是使用所选算法对数据进行训练:

# Fitting the model
model = DummyRegressor(strategy = 'mean')
model.fit(x_train, y_train)

请注意,虚拟回归可以采用以下任何策略作为参数:

mean

总是预测训练集的均值

median

总是预测训练集的中位数

quantile

总是预测训练集的指定分位数,由分位数参数提供

constant

总是预测由用户提供的常量值

如你从前面的代码中看到的,所选参数是mean。这自然意味着所有的预测都将简单地是训练集y_train的均值。这就是为什么虚拟回归只被用作基准,而不是作为严肃的机器学习模型。

下一步是对测试数据进行预测,以及对训练数据进行比较。请注意,对训练数据的预测没有价值,因为算法在训练期间已经见过数据,但了解算法在从未见过的数据上表现得更好或更差是很有意思的:

# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

为了确保你对使用虚拟回归算法的推理是正确的,手动计算y_train的*均值,并将其与每个y_predicted的值进行比较。你会发现它们是相同的:

# Comparing the mean of y_train to an arbitrary value in y_predicted
y_train.mean() == y_predicted[123]

输出应该如下所示:

True

最后,使用以下函数绘制最后的训练数据,然后是第一个测试数据和相应的预测数据:

# Plotting
plot_train_test_values(100, 50, y_train, y_test, y_predicted)
注意

你可以在本书的GitHub 仓库中找到plot_train_test_values()函数的定义。

图 7-2 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。显然,虚拟回归算法预测一个常数值,这就是为什么在测试值旁边的预测线是一条直线。

图 7-2. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。所使用的模型是虚拟回归算法。
注意

如果你想要在单独的窗口中绘制图形,请在控制台中输入**%matplotlib qt**。如果你想要图形显示在绘图资源管理器中,请在控制台中输入**%matplotlib inline**

你如何判断一个模型表现好还是不好?性能评估是交易和算法开发中的关键概念,因为它确保你选择了正确的模型并将其实施。然而,由于一个讽刺的简单问题,任务并不简单:如果过去的表现很好,能保证未来也会表现良好吗?

这个问题很痛苦,但它指向了正确的方向。对这个问题的答案是主观的。现在,让我们谈谈衡量模型性能的不同方法。为了简化任务,我将性能和评估指标分为两部分:模型评估和交易评估。模型评估关注算法在预测中的表现,而交易评估关注使用算法进行交易的系统的财务表现(交易评估指标的一个示例是净利润)。

让我们从模型评估开始。准确率 是比较预测与实际值时首先考虑的指标,尤其是在金融市场中。理论上,如果您预测方向(上涨或下跌)并且预测正确,您应该赚钱(不包括交易成本)。在金融术语中,准确率也称为命中率,计算方法如下:

Accuracy = Correctpredictions Totalpredictions × 100

例如,如果您去年进行了 100 次预测,其中 73 次正确,那么您的准确率为 73%。

预测还可以通过预测值(y_predicted)与实际值(y_test)的接*程度来评估。这是通过损失函数完成的。损失函数 是衡量预测值与实际(测试)值之间差异的数学计算。最基本的损失函数是*均绝对误差(MAE)。它衡量预测值和实际值之间绝对差异的*均值。MAE 的数学表示如下:

M A E = i=1 n |y ^-y i | n

y ^ is the predicted value y is the real value

因此,MAE 计算预测值和实际值之间的*均距离(或正差异)。MAE 越低,模型越准确。

均方误差(MSE)是回归常用的损失函数之一。它衡量预测值和实际值之间*方差的*均值。您可以将 MSE 视为第三章中所见方差指标的等价物。MSE 的数学表示如下:

M S E = i=1 n (y ^-y i ) 2 n

因此,MSE 计算预测值和实际值之间的*方距离的*均值。与 MAE 类似,MSE 值越低,模型越准确。有鉴于此,比较同类产品(例如方差和标准差,如第三章所示)非常有帮助。因此,均方根误差(RMSE)已被开发用于解决此问题(因此,将误差指标缩放回与目标变量相同的单位)。RMSE 的数学表示如下:

R M S E = i=1 n (y ^-y i ) 2 n

RMSE 相当于描述性统计中的标准偏差。

注意

MAE 对异常值的敏感性相对较低,比 MSE 更少,通常在数据包含极端值或错误的绝对值比其*方值更重要时使用。另一方面,由于 MSE 更重视较大的错误,因此在试图提高模型性能时是首选的损失函数。

在使用 MAE、MSE 或 RMSE 评估模型时,重要的是有一个比较基准:

  • 如果您已建立多个回归模型,则可以比较它们的度量标准,以确定哪个模型表现更好。通常情况下,具有较低度量标准的模型被认为在预测中更准确。

  • 根 根据具体问题,您可能会对被视为可接受的预测误差水*设定一个阈值。例如,在某些情况下,低于某个阈值的 RMSE 可能被认为是令人满意的,而高于该阈值的值可能被认为是不可接受的。

  • 您可以将训练数据的损失函数与测试数据的损失函数进行比较。

算法有时可能会出现多种原因的方向性偏见(无论是结构上的还是外部的)。有偏模型 在一个方向上进行的交易明显多于另一个方向(例如,一个算法有 200 个多头和 30 个空头)。模型偏差 将这种情况表示为一个比值,通过将多头头寸数除以空头头寸数。理想的模型偏差约为 1.00,这意味着一个*衡的交易系统。模型偏差的数学表示如下:

Model bias = Numberofbullishsignals Numberofbearishsignals

如果一个模型今年有 934 个多头和 899 个空头,那么该模型的偏差度量为 1.038,这是可以接受的。这意味着该模型实际上没有偏见。值得注意的是,偏差度量为 0.0 表示没有任何看涨信号,而具有未定义值的偏差度量表示没有任何看跌信号(因为被零除)。

现在我们将把注意力转向交易评估。金融先驱们一直在开发度量标准,用于衡量策略和投资组合的表现。让我们讨论一下最常见和最有用的度量标准。最基本的度量标准是净收益,它实质上是在至少有一个已*仓交易的交易期之后的投资资本回报。净收益的数学表示如下:

Net return = ( Finalvalue Initialvalue - 1 ) × 100

净值 一词意味着扣除费用后的结果;否则,它被称为毛收益。例如,如果您年初有 52,000 美元,年底为 67,150 美元,您将获得 29.13% 的收益率(净利润为 15,150 美元)。

另一个盈利能力度量标准是利润因子,它是总毛利润与总毛亏损的比率。直观地,利润因子大于 1.00 意味着一个盈利策略,而小于 1.00 意味着一个亏损策略。利润因子的数学表示如下:

Profit factor = Grossprofits Grosslosses

利润因子是评估交易策略盈利能力的一个有用度量标准,因为它同时考虑了策略产生的利润和损失,而不仅仅是看一方面。一个交易策略的利润因子,其利润为 54,012 美元,损失为 29,988 美元,为 1.80。

下一个有趣的指标与个别交易有关。每笔交易的*均盈利计算基于历史数据的每笔交易的*均利润(或正收益),而每笔交易的*均亏损计算基于历史数据的每笔交易的亏损(或负收益)。这两个指标根据以下公式计算:

Average gain = Totalprofit Numberofwinningtrades Average loss = Totallosses Numberoflosingtrades

下一个指标与风险相关,是评估的最重要指标之一。最大回撤是一种衡量投资或投资组合价值从历史最高峰值到最低点的最大百分比下降的指标。它通常用于评估投资或投资组合的下行风险。例如,如果一个投资的峰值为 10 万美元,其价值随后下跌至 5 万美元后恢复,那么最大回撤将为 50%,即从峰值到谷底的百分比下降。最大回撤的计算方法如下:

Maximum drawdown = ( Troughvalue-Peakvalue Peakvalue ) × 100

最后,让我们讨论一个众所周知的盈利能力比率,称为夏普比率。它衡量了单位超额风险所产生的回报。该比率的公式如下:

S h a r p e = μ-r σ

  • μ 是净收益

  • r 是无风险利率

  • σ 是收益波动率

因此,如果净收益率为 5%,无风险利率为 2%,而收益波动率为 2.5%,则夏普比率为 1.20。任何高于 1.00 的值都是理想的,因为它意味着策略产生了正的超额风险调整回报。

本书的重点是开发机器和深度学习算法,因此性能评估步骤将仅专注于准确性、RMSE 和模型偏差(附带预测变量之间的相关性作为额外指标)。性能函数可以在 GitHub 存储库中找到,以及完整的脚本。

应用性能指标后,模型在 EURUSD 上的结果如下:

Accuracy Train =  49.28 %
Accuracy Test =  49.33 %
RMSE Train =  0.0076467838
RMSE Test =  0.0053250347
Model Bias =  0.0

偏差为 0.0 时,很容易看出这是一个虚拟的回归模型。偏差意味着根据公式,所有的预测都是看跌的。仔细查看预测的细节,你会发现它们都是恒定值。

注意

本节的主要要点如下:

  • 自动数据导入和创建节省时间,让您可以专注于算法的主要问题。

  • 为了进行适当的回测,数据必须分成训练集和测试集。

  • 训练集包含x_trainy_train,前者包含了被认为对后者具有预测能力的值。

  • 测试集包含x_testy_test,前者包含了被认为对后者具有预测能力的值(即使模型在训练中没有遇到过它们)。

  • 将数据拟合是指算法在训练集上运行;预测数据是指算法在测试集上运行。

  • 预测存储在名为y_predicted的变量中,用于性能评估目的与y_test进行比较。

  • 算法的主要目标是具有良好的准确性和稳定的低波动率回报。

机器学习模型

本节介绍了一些使用迄今为止开发的框架的机器学习模型的选择。了解每个模型的优缺点非常重要,这样您就知道根据预测任务选择哪种模型。

线性回归

线性回归算法通过找到最佳拟合线来最小化预测值和实际目标值之间的*方差。在此算法中最常用的优化技术是普通最小二乘(OLS)方法。²

该模型使用 OLS 方法在训练集上进行训练,该方法估计最小化预测值和实际目标值之间*方差的系数,以找到独立变量的最优系数(系数分别代表最佳拟合线的y-截距和斜率)。输出是一个线性函数,根据系数加权解释变量给出期望的回报,并对噪声和截距进行调整。

要从sklearn导入线性回归库,请使用以下代码:

from sklearn.linear_model import LinearRegression

现在让我们看看算法的实现:

# Fitting the model
model = LinearRegression()
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

该模型假设过去保持的线性关系在未来仍将保持不变。这是不现实的,并忽视了市场动态和驱动因素不断变化的事实,无论是短期还是长期。它们也是非线性的。

图 7-3 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-3. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是线性回归算法。

在应用性能指标后,EURUSD 的模型结果如下:

Accuracy Train =  58.52 %
Accuracy Test =  49.54 %
RMSE Train =  0.007096094
RMSE Test =  0.0055932632
Correlation In-Sample Predicted/Train =  0.373
Correlation Out-of-Sample Predicted/Test =  0.014
Model Bias =  0.93

结果表明,线性回归算法的表现不佳,准确率低于 50.00%。正如您所看到的,切换到测试集后,准确率通常会下降。样本内预测与实际样本值之间的相关性也从 0.373 下降到 0.014。模型偏差接*均衡,这意味着长信号数量接*短信号数量。

模型结果有几个要注意的地方:

  • 交易成本未被纳入考虑,因此这些是毛收益结果(非净结果)。

  • 由于这是一个纯粹的时间序列机器学习模型,而不是包含止损和目标的完整交易算法,因此没有风险管理系统。因此,由于这是一个纯粹的定向模型,任务是尽量提高正确预测的数量。在每日时间段内,您正在寻找准确性。

  • 不同的外汇数据提供商可能在历史数据上有细微差异,这可能导致回测之间的一些差异。

模型被设计为优化和调整。优化过程可能包括以下任一技术:

选择正确的预测因子对模型的成功至关重要。

在本章中,使用的预测因子是滞后收益率。这是任意选择的,并不一定是正确的选择。必须基于经济和统计直觉选择预测因子。例如,选择黄金的回报来解释(预测)标准普尔 500 指数的波动可能是合理的,因为它们在经济上有关联。避险资产如黄金在经济不确定时期上涨,而股市则倾向于下跌。这种负相关可能隐藏了这两种工具之间的潜在模式。选择预测因子的另一种方法是使用技术指标,如相对强度指数(RSI)和移动*均线。

适当的分割对正确评估模型至关重要。

训练测试分离很重要,因为它们决定评估的窗口。通常使用 20/80 和 30/70,这意味着数据的 20%(30%)用于测试样本,80%(70%)用于训练样本。

正则化技术可以帮助防止偏差。

岭回归和 Lasso 回归是线性回归中常见的两种正则化方法。岭回归在 OLS 函数中增加惩罚项,以减少大系数的影响,而Lasso 回归可以将一些系数驱动为零,有效进行特征选择。

本节中看到的模型被称为自回归模型,因为因变量取决于其过去的值,而不是外生数据。此外,由于在每个时间步骤中使用了 500 个不同的变量(及其系数)来预测下一个变量,因此该模型被称为多元线性回归模型。相比之下,当模型仅使用一个因变量来预测依赖变量时,它被称为简单线性回归模型。

线性回归的优点是:

  • 实施和训练都很容易。它也不会消耗大量内存。

  • 在数据具有线性依赖性时表现优越。

线性回归的缺点是:

  • 对异常值敏感。

  • 它很容易出现偏差(更多关于这种类型偏差的信息请参见“过拟合与欠拟合”)。

  • 它具有不切实际的假设,例如数据的独立性。

在进入下一节之前,重要的是注意一些线性回归模型不会对数据进行转换。你可能看到非常高的准确度和非常接*真实数据的预测,但实际上预测是滞后一个时间步长的。这意味着在每个时间步长,预测值只是上一个真实值。让我们用之前的例子来证明这一点。使用与之前相同的代码,但省略价格差分代码。你应该看到图 7-4。

图 7-4. 非*稳训练数据接着测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。使用的模型是线性回归算法。

注意它仅仅是滞后于真实值,并没有添加任何预测信息。处理此类模型时,始终要对非*稳数据进行转换。非*稳数据不能使用此类算法进行预测(当然也有例外,稍后会看到)。

在非*稳数据(如市场价格)上使用线性回归,并观察到预测结果与上一个值相同可能表明存在一种称为天真预测的问题。这种情况发生在最*的观察值(在本例中是上一个值)仅被用作下一个时间段的预测值。虽然这种方法有时对某些类型的数据有效,但通常不是一种复杂的预测方法,可能无法捕捉数据中的潜在模式或趋势。导致这种情况发生的原因有几个:

缺乏预测能力

线性回归假设自变量与因变量之间存在线性关系。如果数据高度非*稳且缺乏明确的线性关系,那么线性回归模型可能无法捕捉到有意义的模式,并会默认为类似天真预测的简单预测。

滞后指标

市场价格通常表现出强烈的自相关性,这意味着当前价格与先前价格高度相关。在这种情况下,如果模型仅考虑滞后值作为预测因子,它可能只是将最后一个值复制为预测值。

缺乏特征工程

线性回归模型依赖于您提供的特征(预测因子)来进行预测。如果您只使用滞后值作为预测因子,而没有整合其他相关特征,模型可能会难以生成有意义的预测。

模型复杂性

线性回归是一种相对简单的建模技术。如果数据中的基本关系比线性方程能够捕捉到的更复杂,那么该模型可能无法进行准确的预测。

支持向量回归

支持向量回归(SVR)是一种机器学习算法,属于 支持向量机(SVM)家族。SVR 专门设计用于回归问题,其目标是预测连续的数值(例如返回值)。

SVR 通过在高维特征空间中找到一个最佳超*面来执行回归,该超*面最佳地逼*输入特征和目标变量之间的关系。与传统回归技术不同,传统回归技术旨在最小化预测值与实际值之间的误差,SVR 则专注于找到一个能够捕捉数据中大部分数据的超*面,即所谓的 epsilon tube(损失函数)。

SVR 的关键思想是使用核函数将原始输入空间转换为更高维度的空间。这种转换允许 SVR 隐式地将数据映射到更高维的特征空间,在这个空间中更容易找到特征与目标变量之间的线性关系。核函数计算两个数据点之间的相似性,使得 SVR 算法能够有效地处理非线性回归问题。SVR 过程中执行的步骤如下:

  1. 算法使用核函数将输入特征转换为更高维度的空间。常见的核函数包括线性核函数、多项式核函数、径向基函数(RBF)核函数和 sigmoid 核函数。核的选择取决于数据和潜在问题。

  2. 算法然后旨在找到最佳拟合数据点的超*面,这些数据点在 epsilon 管道内。训练过程涉及解决优化问题,以最小化误差(使用例如 MSE 的损失函数)同时控制间隔。

注意

RBF 核函数是 SVR 的流行选择,因为它能有效捕捉非线性关系。当对关系的具体形式没有先验知识时,它是合适的选择。RBF 核函数根据输入空间中特征向量之间的距离计算它们的相似性。它使用一个称为 gamma 的参数,决定每个训练样本对模型的影响。较高的 gamma 值使模型更加关注单个数据点,可能导致错误。

通过在 epsilon 管道内找到一个最优超*面,SVR 可以有效地捕捉数据中的潜在模式和关系,即使在存在噪声或异常值的情况下也是如此。对于回归任务,特别是处理特征与目标变量之间的非线性关系时,它是一种强大的技术。

由于 SVR 对特征的尺度敏感,将所有特征带到相似的尺度上是很重要的。常见的缩放方法包括 标准化(减去*均值并除以标准差)和 归一化(将特征缩放到范围,例如 [0, 1])。

让我们看看 SVR 的运作方式。再次,目标是根据先前的收益预测下一个 EURUSD 的收益。要导入 SVR 库和缩放库,请使用以下代码:

from sklearn.svm import SVR
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

对于 SVR 算法,进行了一些微调以获得可接受的预测结果。调整是将滞后值的数量从 500 减少到 50:

num_lags = 50

这使得 SVR 算法能够改善其预测能力。在本书中您将看到,执行这些类型的回测的一部分是调整和校准模型。

接下来,要实现算法,请使用以下代码:

# Fitting the model
model = make_pipeline(StandardScaler(), 
                      SVR(kernel = 'rbf', C = 1, gamma = 0.04, 
                      epsilon = 0.01))
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-5 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变过程。

图 7-5。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 SVR 算法。

模型的结果如下:

Accuracy Train =  57.94 %
Accuracy Test =  50.14 %
RMSE Train =  0.0060447699
RMSE Test =  0.0054036167
Correlation In-Sample Predicted/Train =  0.686
Correlation Out-of-Sample Predicted/Test =  0.024
Model Bias =  0.98

SVR 的优点包括:

  • 它在高维特征空间中表现良好,即特征数量远远大于样本数量时。在处理复杂数据集时特别有用。

  • 它可以通过使用核函数捕捉输入特征与目标变量之间的非线性关系。

  • 由于ε-tube 公式的存在,它对训练数据中的异常值具有鲁棒性。该模型专注于将大多数数据拟合在指定边界内,从而减少异常值的影响。

SVR 的缺点包括:

  • 它有几个需要调整以达到最佳性能的超参数。选择适当的超参数可能是一项具有挑战性的任务,并且可能需要进行广泛的实验。

  • 它可能计算成本较高,特别是对于大型数据集或使用复杂核函数时。

  • 它可能对超参数的选择敏感。选择不当的超参数可能导致拟合问题。

随机梯度下降回归

梯度下降(GD)是一种常用的优化算法,用于最小化模型的成本或损失函数,它也是各种优化算法的基础。

注意

梯度简单地指的是表面的斜率或倾斜度。要到达表面的最低点,必须沿着斜坡下降。

随机梯度下降(SGD)是一种常用的迭代优化算法,用于训练机器学习模型,包括回归模型。它特别适用于大型数据集和在线学习场景。当应用于时间序列预测时,SGD 可用于训练能够捕捉时间模式并基于历史数据进行预测的回归模型。因此,SGD 是一种使用随机梯度下降优化的线性回归类型。

与普通的最小二乘法不同,SGD 会迭代地更新模型的参数,使其更适用于大型数据集(以小批量方式处理)。SGD 不是使用整个数据集进行每次更新步骤,而是随机选择训练数据集中的一小批样本或单个样本。这种随机选择有助于引入随机性并避免陷入局部最优解(您可以参考第四章了解更多关于优化的信息)。GD 和 SGD 之间的主要区别在于它们在优化期间如何更新模型的参数。

注意

SGD 不属于任何特定的机器学习模型家族;它本质上是一种优化技术。

GD 计算整个训练数据集上的梯度,每个时代更新一次模型的参数,而 SGD 基于单个训练示例或小批量计算梯度,更频繁地更新参数。SGD 更快但表现更不稳定,而 GD 更慢但具有更*滑的收敛轨迹。SGD 对局部最小值也更具鲁棒性。选择 GD 还是 SGD 取决于问题的具体要求、数据集的大小以及计算效率和收敛行为之间的权衡。

与往常一样,第一步是导入必要的库:

from sklearn.linear_model import SGDRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline

接下来,要实现该算法,请使用以下代码:

# Fitting the model
model = make_pipeline(StandardScaler(), SGDRegressor(max_iter = 50, 
                                                     tol = 1e–3))
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-6 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图片

图 7-6。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 SGD 算法。

模型的结果如下:

Accuracy Train =  55.59 %
Accuracy Test =  46.45 %
RMSE Train =  0.007834505
RMSE Test =  0.0059334014
Correlation In-Sample Predicted/Train =  0.235
Correlation Out-of-Sample Predicted/Test =  –0.001
Model Bias =  0.95

SGD 的优点包括:

  • 它在大型数据集上表现良好,因为它根据单个或小型训练示例逐步更新模型参数。

  • 它可以逃脱局部最小值并找到更好的全局最优解(由于其随机性质)。

  • 它可以通过在每次迭代中将模型暴露于不同的训练样本来提高泛化能力,从而减少过拟合。

SGD 的缺点包括:

  • 收敛路径可能会有噪声并且比确定性优化算法表现出更多的波动。这可能导致较慢的收敛或在最优解周围的振荡。

  • 它受特征缩放的影响,这意味着它对这种技术敏感。

最*邻回归

最*邻回归算法,也被称为k最*邻(KNN)回归,是一种非参数³算法,用于回归任务。它基于特征空间中最*邻的值来预测目标变量的值。该算法首先确定k,即在进行预测时要考虑的最*邻的数量。这是一个需要根据具体问题选择的超参数。

注意

较大的k值提供了更*滑的预测,而较小的k值捕捉更多局部变化,但可能更容易受到噪音的影响。

然后,模型计算新的未见数据点与训练集中所有数据点之间的距离。距离度量的选择取决于输入特征的性质。常见的距离度量包括欧氏距离、曼哈顿距离和闵可夫斯基距离。接下来,算法选择距离查询点最*的k个数据点。这些数据点是最*邻,将用于进行预测。

要导入 KNN 回归器,请使用以下代码:

from sklearn.neighbors import KNeighborsRegressor

现在让我们来看看算法的实现。使用k = 10 来拟合模型:

# Fitting the model
model = KNeighborsRegressor(n_neighbors = 10)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-7 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-7. 训练数据,随后是测试数据(虚线),以及预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 KNN 回归算法。

使用 KNN 回归器进行时间序列预测时,选择邻居数量取决于多个因素,包括数据集的特性和所需的精度水*。没有一个确定的答案来确定选择多少个邻居,通常通过实验和验证来确定。通常选择适当的邻居数量涉及偏差和方差之间的权衡:

  • 较小的k值与能够捕捉数据中局部模式的模型相关联,但也可能对噪声或异常值敏感。

  • 较大的k值与能够更加抗噪声或异常值的模型相关联,但可能忽略数据中的局部模式。

注意

如果将k趋向于数据集大小的极限,将得到一个仅预测数据集中频率最高类别的模型。这被称为贝叶斯误差

模型的结果如下:

Accuracy Train =  67.69 %
Accuracy Test =  50.77 %
RMSE Train =  0.0069584171
RMSE Test =  0.0054027335
Correlation In-Sample Predicted/Train =  0.599
Correlation Out-of-Sample Predicted/Test =  0.002
Model Bias =  0.76

注意

在考虑您的时间序列数据时,考虑其时间特性是至关重要的。如果存在跨多个数据点的明显趋势或模式,较大的k值可能更适合捕捉这些依赖关系。然而,如果时间序列展示出快速变化或短期波动,较小的k值可能更合适。

你的数据集的大小也可能影响k的选择。如果数据集很小,选择较小的k值可能更好,以避免过拟合。相反,较大的数据集可以容忍更高的k值。

KNN 的优点包括:

  • 它的非线性允许捕捉金融数据中的复杂模式,这对于预测可能表现出非线性行为的回报序列有优势。

  • 它可以适应变化的市场条件或模式。由于算法是基于实例的,当新数据可用时,不需要重新训练模型。这种适应性在金融回报的情境中可能是有益的,因为市场动态可以随时间变化。

  • 它为预测提供直观的解释。由于该算法选择k个最*邻来进行预测,相对于更复杂的算法,理解和解释可能更容易。

KNN 的缺点包括:

  • 在处理高维数据时,其性能可能会下降。金融回报序列通常涉及多个预测因子(如技术指标和其他相关回报),KNN 可能难以在高维空间中找到有意义的邻居。

  • 随着数据集的增大,KNN 的计算要求可能变得显著。

  • 它对噪声或异常数据点敏感,因为该算法*等地考虑所有邻居。

决策树回归

决策树是多功能且直观的机器学习模型。它们是基于特征值的一系列决策或选择的图形表示,这些选择导致不同的结果。决策树结构化为分层流程图,其中每个内部节点代表基于特征的决策,每个分支表示该决策的结果,每个叶节点表示最终的预测或类标签。

在决策树的根部,考虑所有的输入特征,并选择基于特定标准(例如在第二章中讨论的信息增益度量)最佳分离数据的特征。创建与所选特征相关联的决策节点。根据所选特征的可能值来分割数据。递归地重复前面的步骤,考虑每个节点处剩余的特征,直到达到停止条件为止,例如达到最大深度、节点中的最小样本数,或者在纯度或增益上不再有进一步的改善。

要导入决策树回归器,请使用以下代码:

from sklearn.tree import DecisionTreeRegressor

现在让我们看一下算法的实现:

# Fitting the model
model = DecisionTreeRegressor(random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

参数random_state通常用于初始化包含随机性的算法,比如初始化权重。这确保了如果你多次使用相同的random_state来训练模型,你将得到相同的结果,这对于比较不同的算法或超参数是很重要的。

图 7-8 显示了预测任务从y_train的最后值到y_testy_predicted的第一个值的演变。

图 7-8. 训练数据接着是测试数据(虚线)和预测数据(细线);垂直虚线代表测试期的开始。使用的模型是决策树回归算法。

模型的结果如下:

Accuracy Train =  100.0 %
Accuracy Test =  47.37 %
RMSE Train =  0.0
RMSE Test =  0.007640736
Correlation In-Sample Predicted/Train =  1.0
Correlation Out-of-Sample Predicted/Test =  –0.079
Model Bias =  0.94

注意训练集的准确度非常高。这明显是过度拟合的证据(受训练数据的 RMSE 支持)。

决策树的优点是:

  • 它们需要最少的数据预处理并且可以处理缺失值。

  • 它们可以捕捉非线性关系、交互作用和变量重要性。

决策树的缺点是:

  • 如果没有适当正则化,它们可能对数据中的细微变化敏感,容易过度拟合。

  • 它们可能难以捕捉需要更深树的复杂关系。

下一节介绍了另一类机器学习算法。这些被称为集成算法。可以使用集成方法将决策树组合起来以创建更稳健和准确的模型。随机森林是下一节中看到的算法,它结合了多个决策树以增强预测能力,尤其是减少过度拟合的风险。

随机森林回归

随机森林是一种利用多个决策树的力量形成单一输出(预测)的机器学习算法。它灵活且不需要太多调整。由于其集成学习技术,它也不太容易过度拟合。集成学习指的是将多个学习器(模型)组合起来以改善最终预测。

使用随机森林,多个学习器是不同的决策树,它们会汇聚到一个单一的预测。

因此,在随机森林算法中可以调整的超参数之一是决策树的数量。该算法使用装袋法。在随机森林的背景下,装袋指的是自助聚合技术,旨在通过减少偏差来改善机器学习模型(如决策树)的性能和鲁棒性。以下是随机森林算法中装袋的工作原理:

  1. 自助采样:随机森林采用自助采样,这意味着通过替换抽样创建原始训练数据的多个子集。每个子集的大小与原始数据集相同,但可能包含重复实例并排除其中一些。这个过程对随机森林中的每棵树都是独立进行的。

  2. 树构建和特征选择:对于每个自助采样,使用一种称为递归分区的过程构建决策树,其中数据基于特征进行分割,以创建优化目标变量分离的分支。在决策树的每个节点,考虑一个随机子集的特征进行分割。这有助于在森林中引入多样性,并防止它们过于依赖单一主导特征。

  3. 集成预测:一旦所有树都构建完成,通过聚合各个树的输出进行预测。对于回归任务,预测值取*均值。

要导入随机森林回归器,请使用以下代码:

from sklearn.ensemble import RandomForestRegressor

现在让我们来看看算法的实现:

# Fitting the model
model = RandomForestRegressor(max_depth = 20, random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

max_depth超参数控制随机森林中每棵决策树的深度。深度较大的决策树可以捕捉数据中更复杂的模式,但也更容易过拟合,这意味着在训练数据上表现很好但在未见数据上表现不佳。另一方面,较浅的树可能无法捕捉数据的所有细节,但在新的未见数据上可能更具一般化能力。

图 7-9 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务演变过程。

图 7-9. 训练数据后跟测试数据(虚线),以及预测数据(细线);垂直虚线表示测试期的开始。所用模型是随机森林回归算法。

模型的结果如下:

Accuracy Train =  82.72 %
Accuracy Test =  50.15 %
RMSE Train =  0.0058106512
RMSE Test =  0.0053452064
Correlation In-Sample Predicted/Train =  0.809
Correlation Out-of-Sample Predicted/Test =  –0.049
Model Bias =  0.63

随机森林回归的优点如下:

  • 由于其集成的特性,通常对数据有准确的预测。由于金融时间序列具有高噪声和边际随机性,尽管如此,其结果仍需优化。

  • 由于其*均化的特性,它对噪声和异常值表现出鲁棒性。

随机森林回归的缺点如下:

  • 有时可能难以解释。由于使用聚合方法,当使用大量树时可能会丢失真实和最终决策。

  • 随着树的数量增加,算法的计算时间变长,训练过程缓慢。

AdaBoost 回归

在理解 AdaBoost 算法之前,让我们先讨论梯度提升,这样就更容易理解其背后的算法。梯度提升是一种基于改进弱学习器(指表现略好于随机的模型)思想构建模型的技术。

提升这些弱学习器的方法是针对它们的弱点创建其他能处理这些弱点的弱学习器。这就诞生了所谓的自适应增强,简称AdaBoost。因此,通俗来说,增强就是将弱学习器结合起来形成更好的模型。

AdaBoost 中的学习器(如前所述,是弱的)是单分裂的决策树(称为stumps)。它们被赋予权重,对于更难分类的实例放置更多的权重,对其余实例放置较少的权重。同时,新的学习器被纳入以在困难部分进行训练,从而创建一个更强大的模型。因此,困难的实例获得更大的权重,直到它们被新的弱学习器解决。

预测基于弱学习器的投票。采用多数原则以最大化准确性。因此,梯度提升可以概括为三个步骤:

  1. 它按顺序构建了一组弱预测模型,通常是决策树。

  2. 每个后续模型都是为了纠正先前模型的错误或残差而构建的,使用梯度下降来调整预测以最小化总体错误。

  3. 所有模型的预测通过加权*均或总和来组合,加权由学习率确定,以产生最终预测。

要导入 AdaBoost 回归器,请使用以下代码:

from sklearn.ensemble import AdaBoostRegressor

现在让我们看一下算法的实现:

# Fitting the model
model = AdaBoostRegressor(random_state = 123)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

图 7-10 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-10. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 AdaBoost 回归算法。

模型的结果如下:

Accuracy Train =  53.27 %
Accuracy Test =  51.7 %
RMSE Train =  0.0070124217
RMSE Test =  0.0053582343
Correlation In-Sample Predicted/Train =  0.461
Correlation Out-of-Sample Predicted/Test =  0.017
Model Bias =  0.72

AdaBoost 的优点是:

  • 它通常具有很高的准确性。

  • 它很容易理解。

AdaBoost 的缺点是:

  • 它受异常值的影响,对噪声敏感。

  • 它速度较慢,且未经优化。

XGBoost 回归

XGBoost是一种快速且性能优异的梯度提升决策树算法。这个名字可能很复杂,但如果你理解了 AdaBoost 上一节中的梯度提升,就不难理解。XGBoost 代表极端梯度提升,是由陈天奇创建的。它的工作原理如下:

  1. XGBoost 从一个简单的基础模型开始,通常是一个决策树。

  2. 它定义了一个衡量模型性能的目标函数。

  3. 使用梯度下降优化,通过根据目标函数的梯度调整模型来迭代地改进模型的预测。

  4. 新的决策树被添加到集合中以纠正先前模型的错误。

  5. 采用正则化技术,如学习率和列子采样,以增强性能并防止拟合问题。

  6. 最终预测是通过组合集合中所有模型的预测得到的。

Python 中 XGBoost 的实现比前面的算法需要更多步骤。第一步是pip install所需的模块。在提示符中输入以下命令:⁴

pip install xgboost

要导入 XGBoost 库,请使用以下代码:

from xgboost import XGBRegressor

算法的实现如下所示:

# Fitting the model
model = XGBRegressor(random_state = 123, n_estimators = 16, 
                     max_depth = 12)
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

注意

参数n_estimators是一个超参数,用于确定要在集成中构建的提升回合或树的数量。由于算法结合多个弱学习器(单个决策树)的预测来创建强预测模型,每个提升回合(迭代)将一个新的决策树添加到集成中,并且算法从之前树的错误中学习。n_estimators超参数控制在训练过程中将添加到集成中的树的最大数量。

AdaBoost 和 XGBoost 都是用于增强弱学习器(通常为决策树)预测能力的提升算法。AdaBoost 专注于使用指数损失迭代强调误分类样本,缺乏内置正则化,并且并行化能力有限。相比之下,XGBoost 利用梯度提升,支持各种损失函数,提供正则化,处理缺失值,通过并行化更好地扩展,提供全面的特征重要性,并允许更广泛的超参数调整。

因此,XGBoost 提供了更高级的功能。它通常因为整体性能更好和处理复杂任务的能力而受到青睐。然而,两者之间的选择取决于具体的问题、数据集和可用的计算资源。

图 7-11 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 7-11。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。使用的模型是 XGBoost 回归算法。

模型的结果如下所示:

Accuracy Train =  75.77 %
Accuracy Test =  53.04 %
RMSE Train =  0.0042354698
RMSE Test =  0.0056622704
Correlation In-Sample Predicted/Train =  0.923
Correlation Out-of-Sample Predicted/Test =  0.05
Model Bias =  6.8

过拟合和欠拟合

机器学习预测分析中会出现问题,这是完全正常的,因为在数据科学(和金融)世界中,“完美”是一个不可能的词。本节涵盖了在预测数据时最重要的问题,即“拟合问题”。过度拟合和欠拟合是两个术语,您必须彻底了解它们,以避免在运行模型时遇到它们的后果。

过拟合发生在模型在训练数据上表现极好但在测试数据上结果糟糕的情况下。这表明模型不仅学习了样本内数据的细节,还学习了发生的噪声。过拟合通常与高方差和低偏差模型相关,但这两个术语的含义是什么?

偏差是指模型预测的期望值与目标变量的实际值之间的差异。低偏差模型是足够复杂,可以捕捉数据中的潜在模式。

方差指的是模型在不同训练集上预测结果的变化程度。高方差模型过于复杂,可能捕捉到训练数据中的随机噪声和波动,导致过拟合。这会使模型适应数据中的噪声。

为了避免过拟合,重要的是在偏差和方差之间找到*衡,选择一个足够复杂以捕捉数据中潜在模式的模型,但不要过于复杂以至于捕捉到数据中的随机噪声和波动。正则化技术也可以用来减少方差,防止过拟合。

过拟合出现的原因有多种,尤其包括:

数据不足

如果训练数据不够多样化,或者数据量不足,模型可能会对训练数据过拟合。

过度复杂的模型

如果模型过于复杂,可能会学习到数据中的噪声而非潜在模式。

特征过载

如果模型训练时使用了过多的特征,可能会学习到不具有泛化能力的无关或噪声特征。

缺乏正则化

如果模型的正则化不合适,可能会导致对训练数据的过拟合。

泄露

泄露发生在测试集的信息被无意中包含在训练集中时。这会导致过拟合,因为模型在学习后期测试时将再次见到这些数据。

高偏差模型过于简化,无法捕捉数据中真实的潜在模式。这可能导致欠拟合。类似地,低方差模型对训练数据的小变化不敏感,并且能够很好地泛化到未见过的新数据。

欠拟合出现的原因有多种,尤其包括:

模型复杂性不足

如果所用模型过于简单,无法捕捉数据中的潜在模式,可能导致欠拟合。例如,线性回归模型可能无法捕捉特征与目标变量之间的非线性关系。

训练不足

如果模型训练时间不够长,或者数据量不足,可能无法捕捉数据中的潜在模式。

过度正则化

正则化是一种防止过拟合的技术,但如果使用过度,可能导致欠拟合。

特征选择不当

如果为模型选择的特征信息量不足或不相关,可能会导致欠拟合。

图 7-12 显示了模型与数据之间不同拟合的比较。欠拟合模型未能从一开始就捕获真实关系,因此在预测过去值和未来值方面表现不佳。良好拟合的模型捕获了数据的一般趋势。它不是精确或完美的模型,但通常在整个时间段内具有令人满意的预测。过拟合模型捕捉了过去的每一个细节,即使是噪音或随机位移。过拟合模型的危险在于它给未来带来了虚假的承诺。

图 7-12。不同的拟合情况。

因此,在构建用于时间序列预测的机器学习模型时,必须确保不调整参数以完美地拟合过去的值。为了减少拟合偏差,请确保在您的回测中包含以下最佳实践:

增加训练数据

收集更多的训练数据有助于捕获数据中更广泛的模式和变化,减少过拟合的机会。

特征选择

仔细选择与您的模型相关且信息丰富的特征。去除无关或冗余的特征可以减少数据中的噪音和复杂性,使模型更容易对未见示例进行良好泛化。

正则化技术

正则化方法明确控制模型的复杂性,以防止过拟合。

超参数调优

优化模型的超参数以找到最佳配置。超参数控制模型的行为和复杂性。

集成方法

使用集成方法,如随机森林,将多个模型的预测组合起来。集成方法可以通过聚合多个模型的预测结果,*滑个别模型的偏差,并提高泛化能力,从而减少过拟合。

定期模型评估

定期评估您的模型在未见数据或专门的验证集上的表现。这有助于监控模型的泛化能力,并检测过拟合或性能下降的任何迹象。

摘要

通过正确理解机器学习算法的来源,更容易解释它们并了解它们的局限性。本章提供了构建时间序列模型所需的知识(理论和实践),使用几种已知的机器学习算法来预测值,希望使用过去的值。

你必须明确知道的是,过去的价值不一定能反映未来的结果。回测总是存在某种程度的偏见,因为需要大量调整来调整结果,这可能会导致过度拟合。模式确实存在,但它们的结果不一定相同。金融时间序列预测的机器学习不断发展,大多数算法(以原始形式及其基本输入)都不太具有预测性,但通过适当的组合和添加风险管理工具和过滤器,您可能会得到一个可持续的算法,为整个框架增加价值。

¹ 为简单起见,价格差异将被称为收益。一般来说,收益也可以表示时间序列的百分比收益。

² 普通最小二乘法使用数学公式来估计系数。它涉及矩阵代数和微积分来求解最小化残差*方和的系数。

³ 一类不依赖于关于基础概率分布的特定假设的统计方法。

⁴ 提示符是一个命令行界面,通常可以在“开始”菜单中访问。它不同于您输入后将被执行的 Python 代码的区域。

第八章:时间序列预测的深度学习 I

深度学习比机器学习稍微复杂和更详细一些。机器学习和深度学习都属于数据科学的范畴。正如你将看到的那样,深度学习主要是关于神经网络,这是一种高度复杂和强大的算法,因为它非常强大并且能够捕捉不同变量之间高度复杂的非线性关系。

本章的目的是在使用 Python 预测金融时间序列之前,解释神经网络的功能,就像您在第七章中看到的那样。

逐步理解神经网络

人工神经网络(ANNs)起源于神经科学的研究,研究人员试图理解人脑及其复杂的互联神经网络如何运作。人工神经网络旨在产生生物神经网络行为的计算表示。

自 1940 年代以来,人工神经网络(ANNs)就存在了,当时学者们首次开始探索基于人脑构建计算模型的方法。逻辑学家沃尔特·皮茨和神经生理学家沃伦·麦卡洛克是这一学科的早期先驱者之一。他们在一篇文章中发布了基于简化人工神经元的计算模型的概念。¹

人工神经网络的发展在 20 世纪 50 年代和 60 年代进一步加速,当时像弗兰克·罗森布拉特这样的研究人员致力于感知器的研究,这是一种可以从其输入中学习的人工神经元类型。罗森布拉特的工作为单层神经网络的发展铺*了道路,这种网络能够进行模式识别任务。

随着多层神经网络(也称为深度神经网络)的创建以及更强大算法的引入,人工神经网络在 1980 年代和 1990 年代取得了显著进展。这一创新使神经网络能够学习层次化的数据表示,从而提高了它们在挑战性任务上的性能。尽管有多位研究者为人工神经网络的发展和进步做出了贡献,但一个有影响力的人物是杰弗里·辛顿。辛顿与他的合作者通过开发新的学习算法和神经网络结构,在该领域做出了显著贡献。他在深度学习方面的工作对人工神经网络的最*复兴和成功起到了重要作用。

人工神经网络由相互连接的节点组成,称为人工神经元,组织成不同层。这些层通常分为三种类型:

输入层

输入层接收输入数据,可以是数值、分类或者原始感官数据。输入层是解释变量,它们被认为具有预测性质。

隐藏层

隐藏层(一个或多个)通过其相互连接的神经元处理输入数据。每个层中的神经元接收输入,执行计算(稍后讨论),并将输出传递到下一层。

输出层

输出层根据来自隐藏层的处理信息产生最终结果或预测。输出层中的神经元数量取决于网络设计的问题类型。

图 8-1 展示了人工神经网络的示意图,信息从左到右流动。它始于两个输入连接到四个隐藏层,在输出层输出加权预测之前进行计算。

图 8-1. 人工神经网络的简单示意图。

人工神经网络中的每个神经元执行两个主要操作:

  1. 神经元从上一层或直接从输入数据接收输入。每个输入乘以权重值,表示该连接的强度或重要性。加权输入然后求和。

  2. 加权和之后,应用激活函数(下一节讨论)引入神经元输出的非线性。激活函数根据加权输入确定神经元的输出值。

在训练过程中,人工神经网络调整其连接的权重以改善其性能。通常通过迭代优化算法(如梯度下降)完成这一过程,其中网络的性能使用定义的损失函数进行评估。算法计算损失函数相对于网络权重的梯度,允许以最小化误差的方式更新权重。

人工神经网络(ANNs)具有从数据中学习和泛化的能力,使它们适用于诸如模式识别和回归的任务。随着深度学习的进展,具有多个隐藏层的人工神经网络在复杂任务上表现出色,利用其学习分层表示和捕捉数据中复杂模式的能力。

注意

值得注意的是,从输入到输出的过程称为前向传播

激活函数

在神经网络中,激活函数引入非线性到神经元的输出中,使神经网络能够建模复杂关系并从非线性数据中学习。它们根据输入的加权和确定神经元的输出。让我们详细讨论这些激活函数。

Sigmoid 激活函数将输入映射到 0 到 1 的范围内,适用于二元分类问题或作为阶跃函数的*滑*似。函数的数学表示如下:

S ( x ) = 1 1+e -x

图 8-2 展示了 Sigmoid 函数。

图 8-2. Sigmoid 函数的图形。

sigmoid 激活函数的优点包括以下几点:

  • 它是一个既*滑又可微的函数,有助于基于梯度的优化算法。

  • 它将输入压缩到有界范围内,可以解释为概率或置信水*。

但是,它也有其局限性:

  • 它存在梯度消失问题,即梯度在极端输入值下变得非常小。这可能会阻碍学习过程。

  • 输出不是零中心化的,这使得它在某些情况下不太适用,比如使用对称更新规则(如梯度下降)优化权重。

接下来的激活函数是双曲正切函数(tanh),你在第四章中见过。该函数的数学表示如下:

t a n h ( x ) = e x -e -x e x +e -x

双曲正切函数的优点包括以下几点:

  • 它类似于 sigmoid 函数,但是是零中心化的,这有助于减轻权重优化中非对称更新的问题。

  • 其非线性可以捕捉比 sigmoid 函数更广泛的数据变化范围。

以下是它的一些局限性:

  • 它遭受梯度消失问题,特别是在深度网络中。

  • 输出仍然容易在极端情况下饱和,导致梯度接*零。

图 8-3 展示了双曲正切函数。

图 8-3. 双曲正切函数的图像。

接下来的函数称为ReLU 激活函数。ReLU 代表修正线性单元。该函数将负值设为零,保持正值不变。它高效且有助于避免梯度消失问题。该函数的数学表示如下:

f ( x ) = m a x ( 0 , x )

ReLU 函数的优点包括以下几点:

  • 它实现简单,只需取 0 和输入值的最大值。ReLU 的简单性导致比更复杂的激活函数更快的计算和训练。

  • 它有助于缓解在深度神经网络训练中可能出现的梯度消失问题。ReLU 的导数要么是 0 要么是 1,这意味着梯度可以更自由地流动,避免随着网络深度增加而指数级变小。

该函数的局限性包括以下几点:

  • 对于负输入值,它输出 0,这可能导致信息丢失。在某些情况下,具有能够产生负输出的激活函数可能是有益的。

  • 它不是一个*滑的函数,因为它在 0 处的导数是不连续的。这可能会在某些场景下导致优化困难。

图 8-4 展示了 ReLU 函数。

图 8-4. ReLU 函数的图像。

最后要讨论的激活函数是泄漏整流线性单元激活函数。该激活函数是 ReLU 函数的扩展,为负输入引入了一个小的斜率。该函数的数学表示如下:

f ( x ) = m a x ( 0 . 01 x , x )

泄漏整流线性单元解决了 ReLU 中的死神经元问题,并允许负输入的某些激活,这有助于训练期间梯度的流动。

泄漏整流线性单元函数的优点包括以下几点:

  • 它克服了 ReLU 可能出现的死神经元问题。通过为负输入引入一个小的斜率,泄漏整流线性单元确保即使神经元未被激活,它仍然可以在训练期间对梯度流做出贡献。

  • 它是一个连续函数,即使在负输入值时也是如此。负输入的非零斜率允许激活函数在其输入范围内具有定义的导数。

函数的限制如下:

  • 泄漏部分的斜率是一个需要手动设置的超参数。它需要仔细调整,以在避免死神经元的同时防止过多的泄漏,可能会阻碍激活函数的非线性。

  • 尽管泄漏整流线性单元为负输入提供了非零响应,但在负激活水*方面,它与某些其他激活函数(如双曲正切(tanh)和 S 形函数)不同。在需要强烈的负激活响应的场景中,其他激活函数可能更合适。

图 8-5 展示了泄漏整流线性单元(leaky ReLU)函数。

你选择的激活函数取决于问题的性质、网络的结构以及网络中神经元的期望行为。

激活函数通常获取神经元输入的加权和,并对其应用非线性变换。转换后的值随后作为神经元的输出传递到网络的下一层。激活函数的具体形式和行为可能有所不同,但它们的整体目的是引入非线性,使网络能够学习数据中的复杂模式和关系。

图 8-5. 泄漏整流线性单元函数的图示。

总之,激活函数通过引入非线性,对人工神经网络的计算起到关键作用。它们应用于单个神经元或中间层的输出,并根据其接收到的输入决定神经元是否应激活。如果没有激活函数,网络只能学习输入和输出之间的线性关系。然而,大多数现实世界的问题(特别是金融时间序列)涉及复杂的非线性关系,因此激活函数对于使神经网络有效地学习和表示这些关系至关重要。

反向传播

反向传播 是用于训练神经网络的基本算法。它使网络能够以最小化预测输出与期望输出之间差异的方式更新其权重。

注意

反向传播是 backward propagation of errors 的简称。

训练神经网络包括以下步骤:

  1. 随机初始化神经网络的权重和偏置。这使得在没有初始信息时能够迈出第一步。

  2. 执行 forward propagation,一种计算网络给定输入的预测输出的技术。作为提醒,这一步包括计算每个神经元输入的加权和,应用激活函数到加权和,将值传递到下一层(如果不是最后一层),并继续这个过程直到达到输出层(预测)。

  3. 将预测输出与实际输出(测试数据)进行比较并计算损失,表示它们之间的差异。选择损失函数(如 MAE 或 MSE)取决于正在解决的具体问题。

  4. 执行反向传播计算损失相对于权重和偏置的梯度。在这一步中,算法将从输出层(最后一层)开始向后传播。它将计算当前层每个神经元输出相对于损失的梯度。然后,通过应用链式法则,它将计算当前层每个神经元输入加权和相对于损失的梯度。之后,它将使用前面步骤的梯度计算当前层每个神经元的权重和偏置相对于损失的梯度。这些步骤重复直到所有层的梯度都计算完成。

  5. 使用计算得到的梯度和选择的优化算法在特定数量的数据批次上更新网络的权重和偏置,由超参数(称为批量大小)控制。更新权重是通过减去学习率和权重梯度的乘积。调整偏置是通过减去学习率和偏置梯度的乘积。重复上述步骤直到所有层的权重和偏置都更新完成。

  6. 算法然后重复步骤 2–5 特定数量的 epochs 或者直到达到收敛标准。一个 epoch 代表一次完整通过整个训练数据集(理想情况下整个过程通过训练数据集多次进行)。

  7. 训练完成后,评估训练好的神经网络在单独的验证或测试数据集上的表现。

注意

学习率是一个超参数,确定在训练过程中更新神经网络权重的步长。它控制模型从正在训练的数据中学习的速度。

批量大小是一个超参数,确定在训练过程的每次迭代中更新模型权重时处理的样本数量。换句话说,它指定了每次用于计算梯度并更新权重的训练样例数量。

选择适当的批量大小对有效训练至关重要,可以影响收敛速度和内存需求。没有适合所有情况的理想批量大小,因为它取决于诸如数据集大小、可用的计算资源和模型复杂性等各种因素。

用于训练 MLP 的常用批量大小范围从小值(如 16、32 或 64)到较大值(如 128、256 或更大)。较小的批量大小可以提供更频繁的权重更新,并可能有助于模型更快地收敛,特别是当数据集很大或具有很多变化时。然而,较小的批量大小可能会引入更多噪声,并由于使用不太准确的梯度进行频繁更新而导致收敛速度较慢。另一方面,较大的批量大小可以提供更稳定的梯度和更好的并行处理能力利用,从而在现代硬件上实现更快的训练。然而,它们可能需要更多的内存,并且更新较少频繁,这可能会减慢收敛速度或使训练过程不够稳健。

作为一个经验法则,您可以从适度的批量大小,如 32 开始,并尝试不同的值,以找到您特定的 MLP 模型和数据集的收敛速度和计算效率之间的最佳*衡。

反向传播算法利用链式法则(参见第四章了解微积分的更多信息),通过网络向后传播错误来计算梯度。

通过根据通过网络向后传播的错误迭代调整权重,反向传播使网络能够随着时间的推移学习并改善其预测能力。反向传播是训练神经网络的关键算法,并在各个领域的显著进展中发挥了作用。

优化算法

在神经网络中,优化算法,也称为优化器,用于在训练过程中更新网络的参数(权重和偏置)。这些算法旨在最小化损失函数,并找到使网络性能最佳的参数的最优值。有几种类型的优化器:

梯度下降(GD)

梯度下降是最基础的优化算法。它根据损失函数关于参数的梯度的方向更新网络的权重和偏置。它通过步长与梯度的负值成比例来调整参数。

随机梯度下降(SGD)

随机梯度下降(SGD)是梯度下降的一种变体,它随机选择单个训练样本或小批量样本来计算梯度并更新参数。它提供了一种计算效率高的方法,并在训练过程中引入噪声,有助于逃离局部最优解。

自适应矩估计(Adam)

Adam 是一种自适应优化算法,它根据梯度的一阶和二阶矩估计计算每个参数的自适应学习率。由于其在各种应用中的高效性和有效性,Adam 得到了广泛应用。

均方根传播(RMSprop)

RMSprop 的目的是解决标准梯度下降算法的一些限制,例如收敛速度慢和在不同方向上的振荡。RMSprop 根据最**方梯度的*均值调整每个参数的学习率。它会计算随时间指数加权移动*均的*方梯度。

每种优化器都有其独特的特性、优势和限制,它们的性能可以根据数据集和网络架构而异。通常需要进行实验和调优,以确定特定任务的最佳优化器。

正则化技术

神经网络中的正则化技术 是用于防止过拟合的方法,过拟合可能导致模型性能不佳,并减少模型在新样本上进行准确预测的能力。正则化技术有助于控制神经网络的复杂性,并提高其泛化到未见数据的能力。

Dropout 是神经网络中常用的正则化技术,用于防止过拟合(详见第七章关于过拟合的详细信息)。在训练过程中,它通过随机地省略(丢弃)部分神经元的输出(将其设置为零)来实现。这暂时从网络中删除神经元及其相应的连接,迫使剩余神经元学习更加健壮和独立的表示。

Dropout 的关键思想是它充当模型*均或集成学习的形式。通过随机丢弃神经元,网络减少对特定神经元或连接的依赖,从而学习更加健壮的特征。Dropout 还有助于防止神经元的共适应,即某些神经元过度依赖其他神经元,降低它们的个体学习能力。因此,Dropout 可以提高网络的泛化能力并减少过拟合。

早停止 是一种技术,它通过在训练过程中监控模型在验证集上的表现来防止过拟合。当模型在验证集上的表现开始恶化时,它会停止训练过程。早停止的理念是随着模型继续训练,它可能开始对训练数据过拟合,从而导致在未见数据上的性能下降。

训练过程通常分为若干个周期,每个周期代表对训练数据的完整遍历。在训练过程中,每个周期后评估模型在验证集上的表现。如果验证损失或选择的度量开始连续恶化达到一定数量的周期,训练将停止,并使用性能最佳时期的模型参数作为最终模型。

早停止通过找到模型学习最有用模式的最佳点来帮助防止过拟合,而不是记忆训练数据中的噪声或无关紧要的细节。辍学和早停止都是防止过拟合和帮助稳定模型的关键正则化技术。

多层感知器

多层感知器(MLP)是一种包含多层人工神经元或节点、按顺序排列的 ANN 类型。它是前馈神经网络,意味着信息在网络中单向流动,从输入层到输出层,没有任何循环或反馈连接(您将在后面的“递归神经网络”中学到更多)。

MLP 的基本构建块是感知器,它是一种人工神经元,接受多个输入,对这些输入应用权重,执行加权和,并通过激活函数产生输出(基本上是您已经见过的神经元)。MLP 包含多个按层组织的感知器。它通常包括一个输入层,一个或多个隐藏层(层数越多,学习过程越深入,直到某个点),以及一个输出层。

注意

术语感知器有时更广泛地用于指称基于感知器类似架构的单层神经网络。在此背景下,术语感知器可以与神经网络单层感知器交替使用。

作为提醒,输入层接收原始输入数据,例如数据集中的特征(例如移动*均的稳定值)。隐藏层位于输入层和输出层之间,对输入数据执行复杂的变换。隐藏层中的每个神经元从前一层的所有神经元接收输入,应用权重,执行加权和,并通过激活函数传递结果。输出层生成网络的最终输出。

MLP 使用反向传播进行训练,它调整网络中神经元的权重,以最小化预测输出与期望输出之间的差异。它们以能够学习数据中复杂的非线性关系而闻名,使它们适用于各种任务,包括模式识别。图 8-6 显示了深度 MLP 架构的示例。

图 8-6. 一个具有两个隐藏层的 MLP 的简单示例。

在这个阶段,您应该理解深度学习基本上就是具有许多隐藏层的神经网络,这增加了学习过程的复杂性。

从本书的 GitHub 仓库 下载 master_function.py 是很重要的,以便访问本书中看到的函数。下载后,您必须将 Python 解释器的目录设置为存储 master_function.py 的路径。

本节旨在创建一个多层感知器(MLP)来预测每日标准普尔 500 指数的收益。导入所需的库:

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在导入历史数据并进行转换:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())
# Difference the data and make it stationary
data = np.diff(data[:, 0])

为模型设置超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 500
batch_size = 16

使用数据预处理函数创建四个所需的数组:

# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)

下面的代码块显示了如何在 keras 中构建 MLP 架构。确保您理解代码中的注释:

# Designing the architecture of the model
model = Sequential()
# First hidden layer with ReLU as activation function
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer with ReLU as activation function
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

在创建 Dense 层时,需要在神经网络的第一层中指定 input_dim 参数。对于后续的 Dense 层,input_dim 将自动从上一层的输出中推断出来。

让我们绘制结果并分析性能:

Accuracy Train =  92.4 %
Accuracy Test =  54.85 %
RMSE Train =  4.3602984254
RMSE Test =  75.7542774467
Correlation In-Sample Predicted/Train =  0.989
Correlation Out-of-Sample Predicted/Test =  0.044
Model Bias =  1.03

图 8-7 显示了从 y_train 的最后值到 y_testy_predicted 的第一个值的预测任务的演变。

图 8-7. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 MLP 回归算法。

当更改超参数时,结果非常波动。这就是为什么在复杂数据上使用复杂模型需要大量的调整和优化。考虑以下改进来增强模型结果:

  • 选择相关特征(输入),捕获金融时间序列的潜在模式和特征。这可以涉及计算技术指标(例如,移动*均线和相对强弱指数)或从数据中推导出其他有意义的变量。

  • 审查模型的架构。考虑增加层数或神经元数量,以为模型提供更多学习复杂模式的能力。尝试不同的激活函数和正则化技术,如丢失和提前停止(见 第九章 中正则化技术的应用)。

  • 调整你的 MLP 模型的超参数。像批量大小和 epoch 数这样的参数可以显著影响模型的收敛能力和泛化能力。

  • 将多个 MLP 模型组合成一个集成模型。这可以涉及使用不同的初始化训练多个模型或使用数据的不同子集。聚合它们的预测结果可能比使用单个模型获得更好的结果。

随着模型训练,由于学习过程,损失函数应该会减少。可以通过以下代码来查看(在编译模型后运行):

import tensorflow as tf
losses = []
epochs = []
class LossCallback(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs = None):
        losses.append(logs['loss'])
        epochs.append(epoch + 1)
        plt.clf()
        plt.plot(epochs, losses, marker = 'o')
        plt.title('Loss Curve')
        plt.xlabel('Epoch')
        plt.ylabel('Loss Value')
        plt.grid(True)
        plt.pause(0.01)
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = 100, 
          verbose = 0, callbacks = [LossCallback()])
plt.show()

前面的代码块绘制了每个 epoch 结束时的损失,从而创建了一个实时可视化的动态损失曲线。注意看它如何下降,直到达到一个难以进一步降低的*台期。图 8-8 展示了跨 epoch 的损失函数下降情况。

图 8-8. 跨 epoch 的损失值。

循环神经网络

循环神经网络(RNN)是一种设计用来处理序列数据或具有时间依赖性数据的人工神经网络类型。与前馈神经网络不同,后者只需单次通过从输入到输出处理数据,RNN 维护内部记忆或隐藏状态,以捕获先前输入的信息,并在处理后续输入时利用它。

RNN 的关键特征是递归连接的存在,这在网络中创建了一个循环。这个循环允许网络跨时间步保持信息,使其非常适合处理涉及顺序或时间相关数据的任务。

在每个时间步,RNN 接收一个输入向量并将其与前一个隐藏状态组合。然后应用激活函数计算新的隐藏状态并产生一个输出。此过程针对每个时间步重复进行,更新隐藏状态并将其作为信息传递到网络中。

递归连接使得 RNN 能够捕捉序列数据中的依赖关系和模式。它们可以建模数据的上下文和时间动态,因此在时间序列预测中非常有用。

然而,传统的 RNN 存在梯度消失问题,即通过递归连接反向传播的梯度可能变得非常小或非常大,导致训练网络困难。下一节将使用一种增强型神经网络来解决梯度消失问题。现在,让我们专注于 RNN 及其特性。

图 8-9 展示了一个循环神经网络(RNN)架构的示例。

图 8-9. 一个包含两个隐藏层的简单循环神经网络示例。

让我们部署一个 RNN 算法来预测标准普尔 500 指数的日回报率。像往常一样,导入所需的库:

from keras.models import Sequential
from keras.layers import Dense, SimpleRNN
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在设置模型的超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 500
batch_size = 16

下面的代码块展示了如何在keras中构建 RNN 架构。

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  67.16 %
Accuracy Test =  52.11 %
RMSE Train =  22.7704952044
RMSE Test =  60.3443059267
Correlation In-Sample Predicted/Train =  0.642
Correlation Out-of-Sample Predicted/Test =  –0.022
Model Bias =  2.18

图 8-10 展示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。

图 8-10。训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 RNN 回归算法。
提示

你可以做的一个好任务是创建一个优化函数,循环尝试不同的超参数并选择最佳的或对最佳的进行*均。这样,你可能能够基于集成技术获得一个健壮的模型。你还可以对不同的市场和不同的时间范围进行回测。请注意,这些技术不仅适用于金融时间序列,而且适用于所有类型的时间序列。

总之,RNN 是能够通过维护内部记忆并捕捉时间依赖关系处理序列数据的神经网络。它们对涉及时间序列或顺序数据的任务是强大的模型。作为提醒,稳定性是成功进行时间序列预测的重要属性。稳定的时间序列在时间上展现出恒定的均值、方差和自协方差。RNN(以及其他深度学习模型)假设底层时间序列是稳定的,这意味着数据的统计特性随时间不变。如果时间序列是非稳定的,它可能包含趋势、季节性或其他可能影响 RNN 性能的模式。MLP 的优化和增强建议在 RNN 上也是有效的。

长短期记忆网络(LSTM)

长短期记忆(LSTM)是一种能够解决梯度消失问题并允许网络捕捉序列数据中长期依赖的 RNN 类型。LSTM 由 Hochreiter 和 Schmidhuber 于 1997 年提出。

LSTM 旨在克服传统 RNN 在处理长序列数据时的局限性。它通过引入特殊的记忆细胞来实现在扩展时间段内保持信息的能力。LSTM 背后的关键思想是利用门控机制来控制信息在记忆细胞中的流动。

LSTM 结构由记忆细胞、输入门、遗忘门和输出门组成。记忆细胞在每个时间步存储和更新信息,而门控制信息的流动。以下是 LSTM 的工作原理:

输入门

输入门决定当前时间步的哪些信息应存储在记忆细胞中。它将当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数为记忆细胞的每个组件生成一个介于 0 和 1 之间的值。

忘记门

忘记门确定应从上一个记忆单元中遗忘哪些信息。它接受当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数以生成遗忘向量。然后,该向量与先前的记忆单元值逐元素相乘,允许 LSTM 遗忘无关信息。

更新

更新步骤结合了输入门和遗忘门的信息。它接受当前输入和上一个隐藏状态作为输入,然后应用 tanh 激活函数。然后,将得到的向量与输入门输出逐元素相乘,并将乘积添加到遗忘门和先前记忆单元值的乘积中。此更新操作确定要存储在记忆单元中的新信息。

输出门

输出门确定当前时间步的 LSTM 输出。它接受当前输入和上一个隐藏状态作为输入,然后应用 Sigmoid 激活函数。更新后的记忆单元值通过双曲正切(tanh)激活函数传递,并与输出门逐元素相乘。得到的向量成为当前隐藏状态,也是该时间步的 LSTM 输出。

LSTM 中的门控机制使其能够有选择地记住或遗忘长序列中的信息,使其非常适合涉及长期依赖关系的任务。通过解决梯度消失问题并捕捉长期依赖关系,LSTM 已成为顺序数据处理的流行选择,并在推动深度学习领域方面发挥了重要作用。

注意

理论上,RNN 能够学习长期依赖关系,但在实践中并不会,因此需要 LSTM。

通常情况下,让我们将 LSTM 应用于相同的时间序列问题。然而,请注意,由于解释变量是任意的且超参数未调整,因此结果并无意义。进行这样的练习的目的是理解代码和算法背后的逻辑。之后,由您来选择值得测试的输入和变量。

如下导入所需的库:

from keras.models import Sequential
from keras.layers import Dense, LSTM
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

现在设置模型的超参数:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 20
num_epochs = 100
batch_size = 32

LSTM 模型需要特征的三维数组。可以使用以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

下面的代码块显示了如何在keras中构建 LSTM 架构:

# Create the LSTM model
model = Sequential()
# First LSTM layer
model.add(LSTM(units = num_neurons_in_hidden_layers, 
               input_shape = (num_lags, 1)))
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Train the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  65.63 %
Accuracy Test =  50.42 %
RMSE Train =  25.5619843783
RMSE Test =  55.1133475721
Correlation In-Sample Predicted/Train =  0.515
Correlation Out-of-Sample Predicted/Test =  0.057
Model Bias =  2.56

图 8-11 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务的演变。请注意,超参数与 RNN 模型中使用的相同。

图 8-11. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 LSTM 回归算法。

值得一看的是算法如何与训练数据拟合良好。图 8-12 展示了来自 y_predicted_trainy_train 的值。

图 8-12. 使用 LSTM 回归算法的样本内预测。

在 LSTM 的上下文中,一个三维数组表示输入数据的形状,这些数据被馈送到模型中。它通常用于容纳顺序或时间序列数据的输入序列。三维数组的维度具有特定的含义:

维度 1(样本数)

这个维度代表数据集中的样本或示例数量。每个样本对应于一个特定的序列或时间序列实例。例如,如果你的数据集中有 1,000 个时间序列,那么维度 1 就是 1,000。

维度 2(时间步)

这个维度代表每个序列中的时间步或数据点的数量。它定义了 LSTM 或 RNN 模型在每个时间步处理的输入序列的长度。例如,如果你的输入序列长度为 10 个时间步,那么维度 2 就是 10。

维度 3(特征数)

这个维度代表与每个时间步的序列相关联的特征或变量的数量。它定义了每个时间步数据的维度。对于单变量时间序列数据,其中每个时间步只考虑一个单一值,维度 3 通常为 1。对于多变量时间序列,其中每个时间步观察到多个变量,维度 3 将大于 1。

让我们稍作休息,讨论一个有趣的话题。使用简单的线性算法来建模复杂的非线性关系很可能会产生糟糕的结果。与此同时,在简单且可预测的数据上使用像 LSTM 这样极其复杂的方法可能并非必要,尽管它可能会带来积极的结果。图 8-13 展示了一个看起来像是在正常间隔内振荡的升序时间序列。

图 8-13. 一个生成的带有振荡特性的升序时间序列。

信不信由你,线性回归实际上可以很好地对这种原始时间序列进行建模。通过假设一个具有 100 个特征的自回归模型(这意味着为了预测下一个值,模型查看最后 100 个值),线性回归算法可以在样本内数据上训练,并输出在 图 8-14 中展示的样本外结果。

图 8-14. 使用线性回归对升序时间序列进行预测。

但让我们首先对其进行一阶差分,使其*稳化。 看看图 8-15,显示了从图 8-13 中差分时间序列创建的*稳时间序列。

图 8-15. 生成的具有振荡特性的上升时间序列(差分)。

线性回归算法可以在样本内数据上进行训练,并输出在图 8-16 中显示的样本外结果,精度极高。

图 8-16. 使用线性回归进行差分时间序列预测。

评估线性回归模型拟合优度的另一种方法是使用 R²。也称为决定系数是一个统计量,指示因变量方差中可以通过回归模型中的自变量解释的比例。

R²的取值范围从 0 到 1,并经常以百分比形式表示。 值为 0 表示自变量不能解释因变量的任何变异性,而值为 1 表示自变量可以完全解释因变量的变异性。

简单来说,R²表示可以归因于模型中包含的独立变量的因变量变异性的比例。它提供了回归模型拟合观察数据的程度的度量。然而,它并不表示变量之间的因果关系或模型的整体质量。 值得注意的是,R²是两个变量之间的*方相关性。 差异化时间序列的 R²指标为 0.935,表明非常良好的拟合度。

同时,使用一些优化的 MLP 也能产生良好的结果。 图 8-17 展示了在使用简单 MLP 模型(每个包含 24 个神经元的两个隐藏层和批大小为 128 经过 50 个 epochs 运行)时,使用差分值的结果。

图 8-17. 使用 MLP 进行差分时间序列预测。

然而,使用深度学习方法预测这样一个简单时间序列可能并不值得增加复杂性。

时间卷积神经网络

卷积神经网络 (CNNs) 是一类专为处理结构化网格数据设计的深度学习模型,特别强调处理图像和其他网格数据,如时间序列(使用较少)和音频频谱图。 CNNs 擅长从输入数据中学习和提取层次化模式和特征,使它们成为图像识别、物体检测、图像分割等任务的强大工具。

CNN 的核心构建模块是卷积层。这些层通过将一组可学习的滤波器应用于输入数据来执行卷积运算,生成捕获相关空间模式和局部依赖性的特征图。CNN 的另一个重要概念是池化层,这些层对卷积层生成的特征图进行降采样。常见的池化操作包括最大池化(选择邻域内的最大值)和*均池化(计算*均值)。池化有助于减少空间维度,提取主要特征,并提高计算效率。

注意

专门用于时间序列预测的 CNN 通常称为 1D-CNN 或时序卷积网络

术语1D-CNN表示卷积操作应用于输入数据的时间维度,这是时间序列数据的特征。这使其与传统的在空间维度上进行任务(如图像识别)的 CNN 有所不同。

典型的 CNN 架构包括三个主要组件:输入层,若干交替的卷积和池化层,以及最后的全连接层。卷积层负责特征提取,而池化层则降采样数据。全连接层提供最终的预测。

CNN 的架构可以根据具体任务的不同而大不相同。这些架构通常采用额外的技术,如 dropout 正则化来提高性能并解决过拟合等挑战。

CNN 可以通过利用其捕捉局部模式和从输入数据中提取相关特征的能力用于时间序列预测。该过程的框架如下:

  1. CNN 使用卷积层进行局部特征提取。卷积层由一组可学习的滤波器组成,这些滤波器与输入数据进行卷积。每个滤波器通过滑动窗口方式进行元素级乘法和求和,从输入数据的不同位置提取不同的特征。结果是一个在输入数据中不同位置突出显示重要模式或特征的特征图。

  2. 池化层通常在卷积层之后使用,以减少特征图的空间维度。最大池化是一种常见的技术,其中选择局部邻域内的最大值,有效地对特征图进行降采样。池化有助于捕获最显著的特征,同时减少计算复杂性并增强网络的泛化能力。

  3. 在卷积和池化层之后,生成的特征图通常被展*为一维向量。这种展*操作将空间分布的特征转换为线性序列,然后可以传递到全连接层。

  4. 全连接层接收扁*化特征向量作为输入,并学习将其映射到所需的输出。这些层使网络能够学习输入特征和目标预测之间的复杂组合关系。最后一个全连接层通常代表输出层,用于预测时间序列的目标值。

在进行算法创建步骤之前,让我们回顾一些在 CNN 中使用的关键概念。在使用 CNN 进行时间序列预测时,滤波器沿着输入数据的时间维度应用。与考虑图像数据中的空间特征不同,这些滤波器旨在捕获时间序列内的时间模式或依赖关系。每个滤波器在时间序列上滑动,一次处理一组连续的时间步长子集。该滤波器学习在输入数据中检测特定的时间模式或特征。例如,它可能捕获短期趋势、季节性或对预测任务有关的重复模式。每个卷积层可以使用多个滤波器,使网络能够学习多样化的时间特征。每个滤波器捕获时间序列的不同方面,使模型能够捕获复杂的时间关系。

另一个概念是卷积核大小,它指的是在卷积操作期间滤波器考虑的连续时间步长的长度或数量。它定义了滤波器的接受域,并影响所提取的时间模式的大小。选择卷积核大小取决于时间序列数据的特性和要捕获的模式。较小的卷积核大小,如 3 或 5,专注于捕获短期模式,而较大的卷积核大小,如 7 或 10,则适合捕获长期依赖关系。通过尝试不同的卷积核大小,可以帮助确定捕获准确预测所需的相关时间模式的最佳接受域。通常使用具有不同卷积核大小的多个卷积层来捕获各种时间尺度上的模式。

现在让我们看看如何创建一个时间 CNN 来预测 S&P 500 的回报,使用其滞后值。如下导入所需的库:

from keras.models import Sequential
from keras.layers import Conv1D, MaxPooling1D, Flatten, Dense
import keras
import numpy as np
import matplotlib.pyplot as plt
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

接下来,设置模型的超参数:

num_lags = 100 
train_test_split = 0.80 
filters = 64 
kernel_size = 4
pool_size = 2
num_epochs = 100 
batch_size = 8

将特征数组重塑为三维数据结构:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

现在创建时间卷积网络(TCN)的架构并运行算法:

# Create the temporal convolutional network model
model = Sequential()
model.add(Conv1D(filters = filters, kernel_size = kernel_size, 
                 activation = 'relu', input_shape = (num_lags, 1)))
model.add(MaxPooling1D(pool_size = pool_size))
model.add(Flatten())
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Train the model
model.fit(x_train, y_train, epochs = num_epochs , batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))

让我们绘制结果并分析性能:

Accuracy Train =  68.9 %
Accuracy Test =  49.16 %
RMSE Train =  18.3047790152
RMSE Test =  63.4069105299
Correlation In-Sample Predicted/Train =  0.786
Correlation Out-of-Sample Predicted/Test =  0.041
Model Bias =  0.98

图 8-18 显示了从y_train的最后值到y_testy_predicted的第一个值的预测任务演变。

图 8-18. 训练数据后跟测试数据(虚线)和预测数据(细线);垂直虚线表示测试期的开始。所使用的模型是 CNN 回归算法。

使用反映您选择的性能指标并寻找更好算法是很重要的。准确率可能是一个基本指标,能够快速了解模型的预测能力,但仅靠它是不够的。本章中的结果仅反映了使用选定超参数进行的训练。优化将使您能够在某些模型上获得非常好的结果。

注意

没有严格的规定定义神经网络需要多少隐藏层才能被视为深度神经网络。然而,一个常见的约定是,具有两个或更多隐藏层的神经网络通常被认为是深度神经网络。

摘要

将深度学习算法应用于时间序列数据可以带来几个好处和挑战。深度学习算法在时间序列分析中展示了巨大的效用,通过有效捕捉复杂模式、提取有意义的特征和进行准确预测。然而,它们的成功很大程度上取决于数据的质量和所选择的特征。

将深度学习算法应用于时间序列数据的效用源于它们能够自动学习层次化表示和模拟复杂的时间依赖关系。它们能够处理非线性关系并捕捉局部和全局模式,使其适用于诸如预测、异常检测、分类和信号处理等广泛的时间序列任务。

然而,将深度学习算法应用于时间序列可能面临挑战:

数据质量

深度学习模型在训练时严重依赖大量高质量的标记数据。数据不足或噪声过多可能会影响模型的性能,导致预测不准确或洞察不可靠。数据预处理、清洗和处理缺失值成为确保数据质量的关键步骤。

特征工程

深度学习模型可以自动从数据中学习相关特征。然而,选择和提取信息性特征对模型的性能有重要影响。领域知识、数据探索和特征工程技术在选择或转换能够增强模型捕捉相关模式的特征方面至关重要。

模型复杂性

深度学习模型通常非常复杂,具有大量参数。训练这样的模型需要大量的计算资源、较长的训练时间和精细的超参数调整。过拟合是一个常见的挑战,即模型只记住了训练数据,而无法很好地推广到未见过的数据。

可解释性

深度学习模型通常被视为神秘盒子,这使得解释学习到的表示和理解预测背后的推理过程具有挑战性。在金融等领域,解释性和可解释性至关重要,这可能是一个问题。

要克服这些挑战并利用深度学习算法进行时间序列分析,需要仔细考虑数据质量、适当的特征工程、模型架构选择、正则化技术和解释性方法。理解时间序列数据的特定特征和任务要求是选择和调整深度学习方法的关键。

¹ W. S. McCulloch 和 W. Pitts,《神经活动中的思想的逻辑演算》,Bulletin of Mathematical Biophysics 5 (1943): 115–33.

第九章:时间序列预测的深度学习 II

本章介绍了一些技术和方法,用于补充机器学习和深度学习算法的预测任务。它由不同的主题组成,每个主题讨论一种改进和优化流程的方法。此时,您应该对机器学习和深度学习模型的基础知识有了扎实的理解,并且知道如何编写一个基本的算法来预测金融时间序列(或任何*稳时间序列)。本章弥合了基础知识和提升算法到功能水*所需的高级知识之间的差距。

分数阶微分

在他的书籍金融机器学习进展中,马科斯·洛佩斯·德普拉多描述了一种将非*稳数据转换为*稳数据的技术。这称为分数阶微分。

分数阶微分是一种数学技术,用于将时间序列转化为*稳序列,同时保留部分其记忆。它扩展了微分(或称为收益率),常用于去除趋势并使时间序列变得*稳的概念。

在传统的微分中,数据序列通过一个通常为 1 的整数进行微分,这涉及从当前值中减去前一个值。这有助于消除趋势并使系列*稳。然而,在某些情况下,系列可能表现出传统微分无法有效捕捉的长期依赖或记忆效应。这些依赖关系可能有助于预测时间序列,如果完全消除,可能会影响算法的性能。这些依赖关系被称为记忆

分数阶微分通过允许微分参数为分数值来解决这一限制。分数阶微分算子有效地对系列中的每个观测值应用滞后值的加权和,其中权重由分数阶微分参数确定。这允许捕捉系列中的长期依赖或记忆效应。分数阶微分在金融时间序列分析中特别有用,其中数据经常表现出长期记忆或持续行为。这可以在 Python 中实现。首先,在命令提示符中使用pip install安装所需的库:

pip install fracdiff

接下来,导入所需的库:

from fracdiff.sklearn import Fracdiff
import pandas_datareader as pdr
import numpy as np
import matplotlib.pyplot as plt

让我们使用德普拉多在他的书中经典的例子,标普 500 指数,来证明分数阶微分将非*稳时间序列转化为具有可见保留记忆的*稳序列。

以下代码应用分数阶微分并与传统微分进行比较:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())
# Calculate the fractional differentiation
window = 100
f = Fracdiff(0.48, mode = 'valid', window = window)
frac_data = f.fit_transform(data)
# Calculate a simple differencing function for comparison
diff_data = np.reshape(np.diff(data[:, 0]), (–1, 1))
# Harmonizing time indices
data = data[window – 1:, ]
diff_data = diff_data[window – 2:, ]

图 9-1 展示了三种类型的变换。您可以在顶部面板中注意到非变换的 S&P 500 数据的趋势性质。您还可以注意到在中间面板中,这种趋势性质减弱了但仍然存在。这就是分数阶差分的目标。通过保持市场记忆的线索同时使其稳定,这种技术可以帮助改进一些预测算法。底部面板显示了价格数据的正常差分。

图 9-1. S&P 500 的分数阶差分(order = 0.48)

图 9-1 是使用此代码生成的:

fig, axes = plt.subplots(nrows = 3, ncols = 1)
axes[0].plot(data[5:,], label = 'S&P 500', color = 'blue', linewidth = 1)
axes[1].plot(frac_data[5:,], label = 
             'Fractionally Differentiated S&P 500 (0.48)', 
             color = 'orange', linewidth = 1)
axes[2].plot(diff_data[5:,], label = 
             'Differenced S&P 500', color = 'green', linewidth = 1)
axes[0].legend()
axes[1].legend()
axes[2].legend()
axes[0].grid()
axes[1].grid()
axes[2].grid()   
axes[1].axhline(y = 0, color = 'black', linestyle = 'dashed') 
axes[2].axhline(y = 0, color = 'black', linestyle = 'dashed')  

让我们通过应用增广迪基—富勒(ADF)检验确保分数差分数据确实是稳定的(您在第三章 中使用了此检验):

from statsmodels.tsa.stattools import adfuller
print('p-value: %f' % adfuller(data)[1])
print('p-value: %f' % adfuller(frac_data)[1])
print('p-value: %f' % adfuller(diff_data)[1])

前一代码块的输出如下(假设显著性水*为 5%):

# The original S&P 500 dataset is nonstationary
p-value: 0.842099 
# The fractionally differentiated S&P 500 dataset is stationary
p-value: 0.038829
# The normally differenced S&P 500 dataset is stationary
p-value: 0.000000

根据结果显示,数据确实是稳定的。让我们看另一个例子。以下代码导入了 EURUSD 的日常数值:

data = np.array((pdr.get_data_fred('DEXUSEU', start = start_date, 
                                   end = end_date)).dropna())

图 9-2 将 EURUSD 与应用了分数阶差分(0.20)进行了比较,底部面板显示了常规差分。

图 9-2. EURUSD 的分数阶差分(order = 0.20)

ADF 测试的结果如下:

# The original EURUSD dataset is nonstationary
p-value: 0.397494
# The fractionally differentiated EURUSD  dataset is stationary
p-value: 0.043214
# The normally differenced EURUSD  dataset is stationary
p-value: 0.000000 

作为比较,图 9-3 将同一数据集与应用了分数阶差分(0.30)进行了比较,底部面板显示了常规差分。

图 9-3. EURUSD 的分数阶差分(order = 0.30)
注意

当接* 1.00 的阶数时,分数阶差分方法直观上变成了正常的整数差分。同样地,当接* 0.00 的阶数时,分数阶差分方法则变成了未经变换的数据序列。

图 9-3 在中间面板上显示的 EURUSD 系列比图 9-2 更加稳定,这是因为分数阶差分的阶数增加了。这也是为什么当阶数为 0.30 时,分数阶差分的 ADF 测试结果为 0.002,远低于当阶数为 0.20 时的 ADF 测试结果(为 0.043)。

总结一下,分数阶差分是时间序列预测的有价值工具,因为它捕捉长期依赖关系,处理非*稳性,适应各种动态,并保留积分属性。其捕捉复杂模式和提高预测精度的能力使其非常适合对各种实际时间序列数据进行建模和预测。

预测阈值

预测阈值 是验证信号所需的最低百分比预测。这意味着预测阈值技术是一种过滤器,用于删除低信度预测。

客观地说,低信度预测低于一定百分比。在表 9-1 中展示了一个假设性例子。阈值为±1%。

表 9-1. 预测表

时间 预测 状态
1 0.09% 已取消
2 –0.60% 已取消
3 –1.50% 已接受
4 1.00% 已接受
5 2.33% 已接受

在时间 1,交易信号是看涨的,预计虚拟金融工具将上涨 0.09%。由于此预测低于 1.00% 的阈值,因此不进行交易。在时间 2,应用相同的直觉,因为看跌信号低于阈值。

其余信号都已被接受,因为它们等于或大于阈值(在大小方面)。本节的目的是开发多层感知器(MLP)模型并仅保留符合一定阈值的预测。

像往常一样,首先导入所需的库:

import numpy as np
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, forecasting_threshold

接下来,设置超参数并使用 mass_import() 导入数据:

num_lags = 500
train_test_split = 0.80 
num_neurons_in_hidden_layers = 256 
num_epochs = 100 
batch_size = 10
threshold = 0.0015

导入并预处理数据,然后设计 MLP 架构:

# Fetching the historical price data
data = np.diff(mass_import(0, 'D1')[:, 3])
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)
# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')

下一步是拟合和预测数据,并保留满足您在超参数中定义的阈值的预测。这是使用函数 forecasting_threshold() 完成的:

# Fitting
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting
y_predicted = model.predict(x_test)
# Threshold function
y_predicted = forecasting_threshold(y_predicted, threshold)
# Plotting
plot_train_test_values(100, 50, y_train, y_test, y_predicted)

图 9-4 显示了真实值和预测值之间的比较图表。预测中的*坦观察表示没有低于所需阈值(在本例中为 0.0015)的信号。

图 9-4. 使用预测阈值进行预测

阈值可以通过多种方式找到,特别是:

固定数值技术

正如您在前面的示例中看到的,该技术假设一个固定的任意数字用作阈值。

基于波动率的技术

使用这种技术,您可以使用波动率指标(例如价格滚动标准差)在每个时间步长上设置可变阈值。这种技术的好处是使用最新的波动率信息。

统计技术

使用此技术,您查看来自训练集(而不是测试集)的实际值,并选择某个分位数(例如,75% 分位数)作为验证信号的最低阈值。

总之,使用预测阈值可能有助于选择具有最高信心的交易,并且还可以帮助最小化交易成本,因为算法假定始终进行交易,这是不推荐的。这假设向算法添加一个新状态,总共有三个状态:

看涨信号

该算法预测一个较高的值。

看跌信号

该算法预测一个较低的值。

中性信号

该算法没有任何方向性信念。

连续重新训练

重新训练 指的是每次有新数据进来时对算法进行训练。这意味着在处理每日时间序列时,重新训练会每天进行一次,同时合并最新的每日输入。

连续重新训练技术值得测试,这就是本节的目的。算法的架构将遵循这个框架:

  1. 在训练测试数据上训练数据。

  2. 对于每次预测,重新运行算法并将新的实际输入包含在训练集中。

注意

持续重新训练技术的一个重大限制是算法的速度,因为它必须在每个时间步重新训练。如果您有 1,000 个测试数据实例,每次训练都需要几分钟,那么回测过程将变得极其缓慢。这尤其是深度学习算法,例如 LSTM,可能需要很长时间来训练。

持续重新训练的主要原因是概念漂移,这是数据内部动态和结构的变化,可能会使训练阶段找到的函数失效。基本上,金融时间序列不表现出静态关系;相反,它们随着时间的推移而变化。因此,持续重新训练旨在通过始终使用最新数据进行训练来更新模型。

注意

持续重新训练不需要在每个时间步骤都进行。您可以设置n周期进行重新训练。例如,如果您选择了 10,则模型在每组 10 个新值之后重新训练。

为了简化事情,本节展示了每天使用线性回归模型对每个时间步长的周 EURUSD 值进行连续重新训练的代码。您可以使用其他模型执行相同的操作;您只需更改导入和设计模型的代码行即可。首先,导入所需的库:

import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import LinearRegression
from master_function import data_preprocessing, mass_import
from master_function import plot_train_test_values, 
from master_function import calculate_accuracy, model_bias
from sklearn.metrics import mean_squared_error

导入数据并设置算法的超参数:

# Importing the time series
data = np.diff(mass_import(0, 'D1')[:, 3])
# Setting the hyperparameters
num_lags = 15
train_test_split = 0.80 
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)
# Fitting the model
model = LinearRegression()
model.fit(x_train, y_train)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))

创建持续重新训练循环如下:

# Store the new forecasts
y_predicted = []
# Reshape x_test to forecast one period
latest_values = np.transpose(np.reshape(x_test[0], (–1, 1)))
# Isolate the real values for comparison
y_test_store = y_test
y_train_store = y_train
for i in range(len(y_test)):
    try: 
        # Predict over the first x_test data
        predicted_value = model.predict(latest_values)
        # Store the prediction in an array
        y_predicted = np.append(y_predicted, predicted_value)
        # Add the first test values to the last training values
        x_train = np.concatenate((x_train, latest_values), axis = 0)
        y_train = np.append(y_train, y_test[0])
        # Remove the first test values from the test arrays
        y_test = y_test[1:]
        x_test = x_test[1:, ]
        # Retrain
        model.fit(x_train, y_train)
        # Select the first values of the test set
        latest_values = np.transpose(np.reshape(x_test[0], (–1, 1)))
    except IndexError:
        pass

绘制预测值:

plot_train_test_values(100, 50, y_train, y_test_store, y_predicted)

图 9-5 显示了结果。

作为简单的比较,对没有重新训练的模型进行了相同的回测。与重新训练的同一模型相比,后者在 48.55%的测试集准确率上获得了 48.92%的测试集准确率。

持续重新训练并不能保证获得更好的结果,但由于市场动态的变化,定期更新模型是有意义的。您应该更新模型的频率可能是主观的。

图 9-5. 使用持续重新训练技术进行预测

时间序列交叉验证

交叉验证是机器学习中用于评估模型性能的一种技术。它涉及将可用数据分成用于训练和评估的子集。在时间序列数据的情况下,观察值的顺序很重要(由于数据的顺序性质),传统的k-折叠交叉验证方法可能不适用。而是使用时间序列交叉验证技术,例如滚动窗口扩展窗口方法。

注意

在传统的k折交叉验证中,数据被随机分成k个大小相等的折叠。每个折叠被用作验证集,而其余k-1个折叠被组合用于训练模型。该过程重复k次,每个折叠一次作为验证集。最后,跨k次迭代对性能指标进行*均以评估模型的性能。

与传统的k折交叉验证不同,时间序列交叉验证方法尊重数据点的时间顺序。用于时间序列交叉验证的两种常用技术是滚动窗口和扩展窗口方法。

滚动窗口交叉验证中,固定大小的训练窗口在时间序列数据上进行迭代移动。在每个步骤中,模型在窗口内的观察结果上进行训练,并在随后的窗口上进行评估。该过程重复,直到达到数据的末尾。窗口大小可以根据特定的时间持续时间或固定数量的观察来定义。图 9-6 显示了滚动窗口交叉验证的示意图。

图 9-6

图 9-6 滚动窗口交叉验证

扩展窗口交叉验证中,训练集从一个小的初始窗口开始,并随着时间的推移不断扩展,每个步骤都会纳入更多的数据点。模型根据可用数据训练到特定点,并在随后的时间段进行评估。与滚动窗口方法类似,该过程重复,直到达到数据的末尾。图 9-7 显示了扩展窗口交叉验证的示意图。

图 9-7

图 9-7 扩展窗口交叉验证

在时间序列交叉验证的每次迭代中,使用适当的评估指标来衡量模型的性能。从每次迭代获得的性能结果可以进行汇总和总结,以评估模型在时间序列数据上的整体性能。

多期预测

多期预测(MPF)是一种旨在预测不止下一期的技术。它旨在生成一个由用户定义的n期路径。有两种方法可以处理 MPF:

递归模型

递归模型将预测作为下一个预测的输入。正如你可能已经猜到的那样,递归模型可能会由于在预测时使用预测作为输入而快速偏离轨道,导致指数级增长的误差项。

直接模型

直接模型从头开始训练模型,以在其各自的时间期间输出多个预测。该模型可能比递归模型更加健壮。

让我们从递归模型开始。从数学上讲,它的最基本形式可以表示如下:

P r e d i c t i o n i = f x ( P r e d i c t i o n i-1 , . . . , P r e d i c t i o n i-n )

本节将使用天气数据和经济指标应用深度学习算法。

预测分析的第一步是了解数据,所以让我们看看算法将要预测的内容。第一个时间序列是自 2005 年以来瑞士巴塞尔的*均日温度。Figure 9-8 显示了时间序列。

第二个时间序列是美国供应管理协会采购经理人指数(ISM PMI),这是一个广为认可的经济指标,提供有关制造业健康和整体经济状况的见解。该指数基于对来自各行业(包括制造业)的采购经理的月度调查,并评估新订单、生产、就业、供应商交付和库存等关键因素。

图 9-8. 数据集样本,显示温度的季节性特征

该指数报告为百分比,值超过 50 表示制造业扩张,值低于 50 表示收缩。更高的 PMI 通常表明积极的经济增长,而较低的 PMI 可能表示经济放缓或衰退条件。由于 ISM PMI 可以提供有关经济趋势和商业周期潜在转变的宝贵见解,因此它受到政策制定者、投资者和企业的密切关注。Figure 9-9 显示了 ISM PMI 的历史观测。

预测的目标是测试算法穿透噪音并对 ISM PMI 的原始均值回归特性建模的能力。让我们从递归模型开始。

递归模型的框架如下:

  1. 使用通常的 80/20 分割在训练集上训练数据。

  2. 使用来自测试集的所需输入预测第一个观察结果。

  3. 使用步骤 2 中的最后一个预测和测试集中所需的数据预测第二个观察结果,同时放弃第一个观察结果。

  4. 重复步骤 3,直到达到所需的预测数量。在某一点上,预测是通过仅查看先前的预测来进行的。

图 9-9. 导入数据集样本,显示 ISM PMI 的均值回归特性
注意

到目前为止,您一直在使用ca⁠lc⁠ul⁠ate_​ac⁠cur⁠acy()评估准确性,当您预测正负值(如 EURUSD 价格变动)时,这是有效的。但是,在预测不围绕零点波动的多周期值时,最好计算方向准确性,这基本上是相同的计算,但不围绕零点。为此,使用函数calculate_directional_accuracy()。请记住,这些函数可以在书籍的master_function.py中的GitHub 仓库中找到。

让我们从巴塞尔的*均温度开始。使用以下代码导入数据集(确保您从GitHub 仓库下载了历史观测数据):

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from master_function import data_preprocessing, plot_train_test_values, 
from master_function import recursive_mpf
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

接下来,预处理数据:

# Importing the data
data = np.reshape(np.array(pd.read_excel('Temperature_Basel.xlsx').dropna()
                  ), (–1))
# Setting the hyperparameters
num_lags = 500
train_test_split = 0.8
num_neurons_in_hidden_layers = 100
num_epochs = 200
batch_size = 12
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, num_lags, 
                                                      train_test_split)

设计具有多个隐藏层的 MLP 架构。然后,基于递归方式进行拟合和预测:

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
          activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Third hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Fourth hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu')) 
# Output layer
model.add(Dense(1))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, np.reshape(y_train, (–1, 1)), epochs = num_epochs, 
          batch_size = batch_size)
# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting in the test set on a recursive basis
x_test, y_predicted = recursive_mpf(x_test, y_test, num_lags, 
                                    model, architecture = 'MLP')

函数recursive_mpf()接受以下参数:

  • 将持续更新的测试集特征。它们由变量x_test表示。

  • 将持续更新的测试集依赖变量。它们由变量y_test表示。

  • 滞后数。这个变量由num_lags表示。

  • 变量model定义的拟合模型。

  • 架构类型由参数architecture表示。它可以是MLP,适用于二维数组,或者LSTM,适用于三维数组。

图 9-10 显示了预测与实际值之间的对比(虚线时间序列在截断线之后)。注意深度神经网络如何重新创建时间序列的季节特性(虽然存在一些缺陷),并且很好地将其投影到未来,而无需沿途获取任何必要的知识。

图 9-10. 多期预测与实际值
注意

许多机器学习和深度学习算法能够很好地建模这种关系。本例中使用了 MLP,但这并不贬低其他模型,甚至简单的模型如线性回归也同样适用。你可以尝试用你选择的模型(如 LSTM)应用相同的例子并比较结果。如果你使用 LSTM 模型,请确保设置architecture = 'LSTM'

现在在第二个时间序列上应用相同的过程。你只需要更改导入文件的名称和超参数(按照你的需求):

data = np.reshape(np.array(pd.read_excel('ISM_PMI.xlsx').dropna()), (–1))

图 9-11 显示了预测(虚线)与实际值之间的对比。

图 9-11. 提前多个周期预测;预测数据用细实线表示,测试数据用虚线表示

训练模型并不复杂,以避免过拟合。然而,在最初的预测期间,它确实能够很好地预测转折点。自然地,这种能力随着时间的推移逐渐减弱。调整超参数是实现良好方向准确性的关键。从以下超参数开始:

num_lags = 200
train_test_split = 0.8
num_neurons_in_hidden_layers = 500
num_epochs = 400
batch_size = 100

第二种 MPF 技术从头开始训练模型,输出多个在各自时间段的预测。数学上可以表示如下:

P r e d i c t i o n i = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n ) P r e d i c t i o n i+1 = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n ) P r e d i c t i o n i+2 = f x ( r e a l i n p u t i-1 , . . . , i n p u t i-n )

递归模型的框架如下:

  1. 创建一个函数,将所需的输入数量与所需的输出数量相关联。这意味着神经网络的最后一层将包含等于你想要向未来投影的预测期数的输出数量。

  2. 训练模型以预测每个时间步的多个输出,基于同一时间步的输入。

让我们继续使用 ISM PMI。像往常一样,导入所需的库:

from keras.models import Sequential
from keras.layers import Dense
import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from master_function import direct_mpf
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

导入和预处理数据时设置超参数:

# Importing the data
data = np.reshape(np.array(pd.read_excel('ISM_PMI.xlsx').dropna()), (–1))
# Setting the hyperparameters
num_lags = 10
train_test_split = 0.80
num_neurons_in_hidden_layers = 200
num_epochs = 200
batch_size = 10
forecast_horizon = 18 # This means eighteen months
x_train, y_train, x_test, y_test = direct_mpf(data, num_lags, 
                                              train_test_split, 
                                              forecast_horizon)

函数direct_mpf()接受以下参数:

  • 变量data表示的数据集

  • 变量num_lags表示的滞后数目。

  • 变量train_test_split表示的拆分。

  • 变量forecast_horizon表示的要预测的观察次数。

准备数组,设计架构,并预测数据,预测时间跨度为 18 个月(因为 ISM PMI 是一个月度指标):

# Designing the architecture of the model
model = Sequential()
# First hidden layer
model.add(Dense(num_neurons_in_hidden_layers, input_dim = num_lags, 
                activation = 'relu'))  
# Second hidden layer
model.add(Dense(num_neurons_in_hidden_layers, activation = 'relu'))  
# Output layer
model.add(Dense(forecast_horizon))
# Compiling
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting (training) the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Make predictions
y_predicted = model.predict(x_test)
# Plotting
plt.plot(y_predicted[–1], label = 'Predicted data', color = 'red', 
         linewidth = 1)
plt.plot(y_test[–1], label = 'Test data', color = 'black', 
         linestyle = 'dashed', linewidth = 2)
plt.grid()
plt.legend()

图 9-12 在此时显示了预测数据和测试数据。

图 9-12。模型的多周期预测与实际值比较,带有一些优化

在预测时,模型解释了连续 18 个月 ISM PMI 的下降。模型似乎在预测这个趋势方面表现良好。请注意,由于算法的随机初始化可能会影响其收敛到最小损失函数,因此可能会得到不同的结果。您可以使用random_state以确保每次获得相同的结果(您在第七章中看到了这一点)。

注意

ISM PMI 与美国国内生产总值(GDP)呈正相关,与标准普尔 500 指数略有正相关。更确切地说,ISM PMI 的底部与股票市场的底部相吻合。

出于好奇,让我们尝试使用非常简单和基本的超参数来运行模型:

num_lags = 1
train_test_split = 0.80
num_neurons_in_hidden_layers = 2
num_epochs = 10
batch_size = 1
forecast_horizon = 18

显然,对于一个滞后的情况,模型只会考虑先前的值来学习如何预测未来。隐藏层每个仅包含两个神经元,仅运行 10 个时期,使用批量大小为 1。自然地,使用这些超参数不会得到令人满意的结果。图 9-13 比较了预测值和实际值之间的巨大差异,因为模型未能捕捉到幅度或方向。

图 9-13。使用基本超参数的模型的多周期预测与实际值比较

这就是为什么超参数优化很重要,需要一定程度的复杂性。毕竟,这些时间序列并不简单,其中带有相当大的噪音。

最后,让我们来看看在巴塞尔温度数据上运行以下超参数的结果,就像您在本节开头看到的那样:

num_lags = 500
train_test_split = 0.80
num_neurons_in_hidden_layers = 128
num_epochs = 50
batch_size = 12
forecast_horizon = 500

图 9-14 比较了使用温度时间序列的预测值与实际值,预测的观察次数为 500。

图 9-14。使用温度时间序列的模型的多周期预测与实际值比较

使用哪种预测技术取决于您的偏好和需求。值得一提的是另一种 MPF 技术被称为多输出模型,它是预测一系列值的一次性预测。这意味着模型在训练集上训练,旨在产生预先定义数量的输出(预测)。显然,这种模型可能计算成本高昂,并且需要大量的数据。

将正则化应用于 MLPs

第八章讨论了深度学习中涉及的两种正则化概念:

  • 丢弃作为一种正则化技术,在训练期间随机关闭神经元以防止过拟合

  • 提前停止作为一种方法,通过监控模型性能并在性能开始下降时停止训练来防止过拟合

另一种值得讨论的正则化技术是批归一化,这是深度学习中用于改善神经网络训练和泛化的技术。它在训练期间规范化每个层的输入,有助于稳定和加速学习过程。

批归一化背后的主要思想是确保层的输入均值为零,方差为单位。该归一化独立应用于每个层内的每个特征(或神经元)。该过程可以总结为以下步骤:

  1. 对于每个特征在一个小批次中,计算批次中所有样本的均值和方差。

  2. 对于每个特征,减去均值并除以标准差(方差的*方根)。

  3. 归一化后,特征通过可学习参数进行缩放和移位。这些参数允许模型为每个归一化特征学习最佳的缩放和移位。

本节介绍了使用 LSTM 进行简单预测任务的示例,同时添加了三种正则化技术。时间序列是标准普尔 500 的 20 天滚动自相关数据。导入所需的库:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout, BatchNormalization
from tensorflow.keras.callbacks import EarlyStopping
import pandas_datareader as pdr
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

导入并预处理数据:

# Set the start and end dates for the data
start_date = '1990-01-01'
end_date   = '2023-06-01'
# Fetch S&P 500 price data
data = np.array((pdr.get_data_fred('SP500', start = start_date, 
                                   end = end_date)).dropna())

计算收盘价格的 20 天自相关性:

rolling_autocorr = pd.DataFrame(data).rolling(window = 
                                              20).apply(lambda x: 
                                              x.autocorr(lag=1)).dropna()
rolling_autocorr = np.reshape(np.array(rolling_autocorr), (–1))
注意

在 Python 中,lambda函数,也称为匿名函数,是一个小型的未命名函数,可以有任意数量的参数,但只能有一个表达式。这些函数通常用于创建简单的内联函数,而无需使用def关键字定义完整的函数。以下是一个简单的示例,说明了lambda的工作原理:

# Create an anonymous function to divide two variables
divide = lambda x, y: x / y
# Call the function
result = divide(10, 2)

输出将是存储在result中的浮点数 5.0。

apply()函数是pandas中可用的方法。它主要用于沿着数据帧的轴应用给定函数。

继续之前,请尝试绘制您刚刚计算的标准普尔 500 价格数据与其 20 天自相关性的图。使用以下代码生成图 9-15:

fig, axes = plt.subplots(nrows = 2, ncols = 1)
axes[0].plot(data[-350:,], label = 'S&P 500', linewidth = 1.5)
axes[1].plot(rolling_autocorr[-350:,], label = '20-Day Autocorrelation', 
             color = 'orange', linewidth = 1.5)
axes[0].legend()
axes[1].legend()
axes[0].grid()
axes[1].grid()
axes[1].axhline(y = 0.95, color = 'black', linestyle = 'dashed')

图 9-15. 标准普尔 500 与其 20 天价格自相关性(滞后 = 1)

从图表和自相关性的直觉中应该记住的是,每当自相关性接* 1.00 时,当前趋势可能会中断,从而导致市场纠正。这不是一个完美的假设,但您可以遵循这些基本规则来解释滚动自相关性的观察结果:

  • 一个趋势市场(看涨或看跌)其自相关性迟早会接* 1.00。当这种情况发生时,可能表明潜在趋势暂停,或者在更少的情况下,完全反转。

  • 一个横向(波动)市场的自相关性会很低。如果自相关接*历史最低点,则可能意味着市场准备好趋势。

现在让我们继续构建算法。下一步是设置超参数并准备数组:

num_lags = 500 
train_test_split = 0.80 
num_neurons_in_hidden_layers = 128 
num_epochs = 100 
batch_size = 20
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(rolling_autocorr, 
                                                      num_lags, 
                                                      train_test_split)

将输入数组转换为三维结构,以便在 LSTM 架构中无问题地处理它们:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

设计 LSTM 架构并添加 dropout 层和批归一化。在设置restore_best_weightsTrue的同时,添加早停实现以保持对测试数据的最佳预测参数:

# Create the LSTM model
model = Sequential()
model.add(LSTM(units = num_neurons_in_hidden_layers, input_shape = 
               (num_lags, 1)))
# Adding batch normalization and a dropout of 10%
model.add(BatchNormalization())
model.add(Dropout(0.1)) 
# Adding the output layer
model.add(Dense(units = 1))
# Compile the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Early stopping implementation
early_stopping = EarlyStopping(monitor = 'loss', patience = 15, 
 restore_best_weights = True)
# Train the model
model.fit(x_train, y_train, epochs = num_epochs, 
          batch_size = batch_size, callbacks = [early_stopping])

预测并绘制结果:

# Predicting in-sample
y_predicted_train = np.reshape(model.predict(x_train), (–1, 1))
# Predicting out-of-sample
y_predicted = np.reshape(model.predict(x_test), (–1, 1))
# Plotting
plot_train_test_values(300, 50, y_train, y_test, y_predicted)

图 9-16 显示了预测与实际值的对比。由于早停机制的回调,模型在达到 100 个周期之前已停止训练。

图 9-16. 预测相关性

结果如下:

Accuracy Train =  70.37 %
Accuracy Test =  68.12 %
RMSE Train =  0.0658945761
RMSE Test =  0.0585669847
Correlation In-Sample Predicted/Train =  0.945
Correlation Out-of-Sample Predicted/Test =  0.936

需要注意的是,应谨慎使用诸如滚动自相关等指标。它们提供了历史模式的见解,但不能保证未来的表现。此外,滚动自相关作为技术指标的有效性取决于数据的性质和使用的上下文。您可以尝试将 MPF 方法应用于自相关数据。

其他存在的正则化技术包括以下内容:

L1 和 L2 正则化

也称为权重衰减,L1 和 L2 正则化根据权重的大小向损失函数添加惩罚项。L1 正则化将权重的绝对值加到损失中,促进模型的稀疏性。L2 正则化将权重的*方值加到损失中,抑制大的权重值,并倾向于更均匀地分布特征的影响。

DropConnect

这种技术类似于 dropout,但是应用于连接而不是神经元。这种技术在训练期间随机丢弃层之间的连接。

权重约束

限制权重值的大小可以防止模型从噪声中学习复杂模式,并帮助对模型进行正则化。

对抗训练

使用对抗性示例训练模型可以通过使其更抗小扰动来提高其鲁棒性。

使用这些正则化技术并不保证比不使用它们的模型结果更好。然而,深度学习的最佳实践鼓励使用这些技术来避免更严重的问题,如过拟合。

注意

当手动上传包含历史数据的 Excel 文件(例如使用pandas)时,请确保其形状为(n, ),而不是(n, 1)。这样做可以确保在使用data_preprocessing()函数时,四个训练/测试数组将以正确的维度创建。

要将(n, 1)数组转换为(n, ),请使用以下语法:

data = np.reshape(data, (–1))

要将(n, )数组转换为(n, 1),请使用以下语法:

data = np.reshape(data, (–1, 1))

概要

本章介绍了一些可能改进不同机器和深度学习算法的技术。我喜欢将这些技术称为卫星,因为它们围绕着主要组件——神经网络——盘旋。优化和增强对于分析的成功至关重要。例如,某些市场可能会从预测阈值技术和分数微分中受益。试错法是理解数据的关键,当您开始学习第十章并了解强化学习时,您会发现试错不仅仅是人类的任务,它也可以是计算机的任务。

第十章:深度强化学习用于时间序列预测

强化学习是处理顺序决策问题的机器学习分支。该分支中的算法通过与环境的交互和奖励反馈来学习做出最优决策。在时间序列预测的背景下,它可以用来开发基于历史数据进行顺序预测的模型。传统的预测方法通常依赖于统计方法或监督学习技术,这些方法假设数据点之间是独立的。然而,时间序列数据表现出时间依赖性和模式,这些可以通过强化学习有效地捕捉。

用于时间序列预测的强化学习模型通常涉及一个基于观察到的状态采取行动并根据其预测准确性获得奖励的代理程序。代理通过试错学习以在时间上最大化累积奖励。关键挑战在于找到探索(尝试新行动)与利用(使用已学知识)之间的最佳*衡。

本章概述了关于预测时间序列数据的强化学习和深度强化学习的基本概念。

强化学习的直觉

简化总是理解更高级细节的正确途径。因此,在深入研究之前,让我们从一个简单的角度来看待强化学习。

强化学习主要涉及奖励和惩罚。想象一个孩子因做好事而受到奖励,因做坏事而受到惩罚。随着时间的推移,孩子会成长并积累经验,以便做好事并尽量避免做坏事(没有人是完美的)。因此,学习是通过经验来完成的。

从时间序列的角度来看,基本思想是一样的。想象在过去数据上训练模型,然后让其通过经验学习,在做出良好预测时奖励它,在犯错时校正其参数,以便下次实现更好的准确性。算法本质上是贪婪的,并且希望最大化其奖励;因此,随着时间的推移,它在预测下一个可能值时变得更加优秀,这当然依赖于分析的时间序列的质量和信噪比。

强化学习这个术语来源于算法在做出正确决策时给予正向强化,在做出错误决策时给予负向强化。你必须了解的前三个概念是状态、行动和奖励:

状态

每个时间步骤的特征。例如,在某个时间步骤,市场的当前状态是其 OHLC 数据和其交易量数据。更简单的话来说,状态是解释变量。

行动

交易员在每个时间步骤可能做出的决策。一般涉及买入、卖出或持有。更通俗的说法,动作是算法在面对特定状态时的决策(例如,交易员注意到市场估值过高并决定发起买单的简单自主性例子)。

奖励

正确动作的结果。最简单的奖励是正回报。请注意,设计不良的奖励函数可能导致模型问题,例如买入并持有策略[¹]。

表 10-1 展示了强化学习的三个主要元素。

表 10-1. 一个假设的决策表

时间 开盘 最高 最低 收盘 | 动作 奖励
状态 1 10 14 8 10 | 买入 0
状态 2 10 15 6 13 | 买入 3
状态 3 13 16 8 14 | 卖出 –1
状态 4 10 16 8 14 | 持有 0

状态是从时间列到收盘列的行,动作可以是分类的,正如您从动作列看到的,奖励可以是数值(例如,正或负的利润)或分类的(例如,利润或亏损标签)。

从上述列表中可以看出,仅设计一个寻找奖励的系统似乎很复杂。奖励函数量化了处于特定状态或采取特定行动的可取性或效用。因此,奖励函数向代理提供反馈,指示其行动的即时质量,并引导其学习过程。在我们详细讨论奖励函数之前,让我们先看看什么是状态-动作表(也称为 Q 表)。

Q 表,即质量表,是一种数据结构,用于存储和更新在给定状态下采取特定行动的期望值(称为Q 值)。状态-动作对(s, a)在时间 t 的 Q 值表示代理通过遵循特定策略,在状态 s 中采取行动 a 能够达到的预期累积奖励。因此,Q 表是一种表格结构,将每个状态-动作对映射到其对应的 Q 值。

初始时,Q 表通常使用任意值或设为零进行初始化。随着算法探索环境(市场)并接收奖励,它基于观察到的奖励和预估的未来奖励更新表中的 Q 值。这一过程通常使用 Q 学习等算法完成。

随着反复的探索和利用,Q 表逐渐收敛到对最优 Q 值更准确的估计,代表在每个状态下采取的最佳行动。通过使用 Q 表,代理可以做出明智的决策,并学会在给定环境中最大化累积奖励。记住,奖励可以是利润、夏普比率或任何其他绩效指标。

Q-learning 是一种流行的强化学习算法,通过迭代更新其动作值函数(即 贝尔曼方程),使代理程序能够学习到最优的行动:

Q ( s t , a t ) = R ( s t , a t ) + γ m a x [ Q ( s t+1 , a t+1 ) ]

Q ( s t , a t ) is the expected reward R ( s t , a t ) is the reward table γ is the learning rate (known as g a m m a )

学习率(γ)越大,算法越多考虑先前的经验。注意,如果 γ 等于零,那么学习就等同于什么都没学到,因为第二项将会自我抵消。举个简单的例子,考虑 表格 10-2。

表格 10-2. R 表

时间(状态) 行动(动作) 等待(动作)
1 2 个奖励单位 0 个奖励单位
2 2 个奖励单位 0 个奖励单位
3 2 个奖励单位 0 个奖励单位
4 2 个奖励单位 0 个奖励单位
5 2 个奖励单位 4 个奖励单位

该表格描述了随时间的行动结果。每个时间步长,执行(做某事)将获得 2 的奖励,而在第五个时间步长等待行动将获得 4 的奖励。这意味着代理程序可以做出以下选择之一:

  • 立即行动并获得 2 个奖励单位。

  • 等待一段时间再行动,可以获得 4 个奖励单位。

假设 γ = 0.80. 使用贝尔曼方程并向后工作,将得到以下结果:

Q ( s 1 , a 1 ) = 0 + 0 . 8 ( 2 . 04 ) = 1 . 63 Q ( s 2 , a 2 ) = 0 + 0 . 8 ( 2 . 56 ) = 2 . 04 Q ( s 3 , a 3 ) = 0 + 0 . 8 ( 3 . 20 ) = 2 . 56 Q ( s 4 , a 4 ) = 0 + 0 . 8 ( 4 . 00 ) = 3 . 20

表格 10-2 可能会更新为以下的 表格 10-3 。

表格 10-3. Q 表

时间(状态) 行动(动作) 等待(动作)
1 2 个奖励单位 1.63 个奖励单位
2 2 个奖励单位 2.04 个奖励单位
3 2 个奖励单位 2.56 个奖励单位
4 2 个奖励单位 3.20 个奖励单位
5 2 个奖励单位 4.00 个奖励单位

因此,Q 表会持续更新隐含的奖励,以帮助最大化最终的奖励。要理解贝尔曼方程中为什么有 max 这个术语,可以参考 表格 10-4 中的例子。

表格 10-4. R 表

时间(状态) 买入 卖出 持有
1 5 8 8
2 3 2 1
3 2 5 6

计算在 Q 表中(表格 10-5)假设学习率为 0.4 时,x 的预期值。

表格 10-5. Q 表

时间(状态) 买入 卖出 持有
1 ? ? ?
2 ? x ?
3 2 5 6

按照公式,您应该得到以下结果:

x = 2 + 0 . 4 ( m a x ( 2 , 5 , 6 ) ) = 4 . 4

状态(特征)必须具有预测性质,以便强化学习算法能够比随机预测更准确地预测下一个值。特征的例子可以是相对强弱指数(RSI)、移动*均和滞后收盘价格的值。

在输入统计偏好保持不变的情况下至关重要,即具有固定性。这带来了一个问题:如果移动*均值不是固定的,那么如何将其用作输入?简单的答案是通过通常的转换,即进行百分比差异。

注意

可以使用分数差分来将非*稳时间序列转换为*稳时间序列,同时保留其记忆。

策略 定义了智能体在环境中的行为。它是一个从状态到动作的映射,指示智能体在给定状态下应该采取什么动作。策略本质上通过指定基于观察到的状态应执行的动作来引导智能体的决策过程。

强化学习的目标是找到最大化智能体长期累积奖励的最佳策略。这通常通过试错过程实现,智能体与环境互动,采取行动,获得奖励,并根据观察到的结果调整其策略。

开发策略通常比探索策略更快,但可能更受限,因为它寻求更大和即时的奖励,而在此之后可能有一条路径导致更大的奖励。理想情况下,最佳策略是两者的组合。但是你如何确定这种最佳混合?这个问题由 epsilon(ε)回答。

Epsilon 是用于探索和开发权衡的参数。它决定了智能体选择随机动作(探索)与选择估计值最高的动作(开发)的概率。

常用的探索策略包括 epsilon-greedy 和 softmax。在 epsilon-greedy 中,智能体以概率 (1 – ε) 选择估计值最高的动作,然后以 ε 的概率选择随机动作。这允许智能体探索不同的动作,并有可能发现更好的策略。随着智能体逐渐学习,epsilon 值通常会逐渐衰减,以减少探索并更多地专注于开发[²]。Softmax 动作选择考虑了估计的动作值,但在决策过程中引入了随机性。与 softmax 相关的温度参数决定了动作选择的随机性,温度越高,探索就越多。

警告

不要混淆 epsilon 和 gamma:

  • Gamma 是一个参数,它决定了未来奖励的重要性。它控制了智能体对即时奖励与延迟奖励的价值比重(因此,它与延迟满足问题相关)。Gamma 的值通常在 0 和 1 之间,数值越接* 1,智能体就越重视未来奖励,而数值越接* 0,就越不重视未来奖励。要更好地理解这一点,请再次查看贝尔曼方程。

  • Epsilon 是用于探索和开发权衡的参数。它决定了智能体选择随机动作(探索)与选择估计值最高的动作(开发)的概率。

此时,你可能会感到对所呈现的大量新信息感到不知所措,特别是因为它与你在本书中迄今所见的内容不同。在进入更复杂的深度强化学习讨论之前,快速总结一下本章至今所见的内容可能会有益。强化学习本质上是给机器一个任务,然后让它自己学会如何完成。

在时间序列分析中,状态表示环境在特定时间的当前情况或状态。例如,技术指标的值就是一个状态的例子。状态由 Q 表表示。动作是不言自明的,可以是买入、卖出或持有(或者更复杂的组合,如减少权重和增加权重)。奖励是算法试图最大化的,可以是每笔交易的利润,夏普比率或任何性能评估指标。奖励也可以是惩罚,如交易次数或最大回撤(在这种情况下,你的目标是将其最小化)。强化学习算法将通过不同策略的多次迭代和变量来尝试检测隐藏的模式,并优化交易决策以最大化盈利。这说起来容易,实现起来难(或者编码起来难)。

一个问题迫切需要回答:使用 Q 表来表示金融时间序列的不同状态是否高效?这个问题将在下一节中回答。

深度强化学习

深度强化学习 将强化学习技术与深度学习架构,特别是深度神经网络结合在一起。它涉及训练代理程序通过与环境的互动来学习最优行为并做出决策,使用深度神经网络来逼*价值函数或策略。

强化学习算法和深度强化学习算法的主要区别在于,前者使用 Q 表估计 Q 值,而后者使用人工神经网络来估计 Q 值(详见第八章关于人工神经网络的详细信息)。

注意

作为提醒,人工神经网络(ANNs)是一种受人脑结构和功能启发的计算模型。神经网络由连接的节点组成,组织成层。主要的层类型包括输入层、隐藏层和输出层。输入层接收初始数据,然后通过隐藏层处理,最后输出层产生网络的预测。

本节的主要目标是理解并设计一个深度强化学习算法,其目的是数据预测。请记住,强化学习由于一些问题(在本节末尾讨论),目前还没有广泛应用,需要在将其作为量化金融主要交易算法之前解决这些问题。

因此,深度强化学习将有两个主要元素具有重要任务:

  • 一个深度神经网络架构来识别模式并逼*关联的因变量和自变量之间的最佳函数

  • 强化学习架构允许算法通过试错学习如何最大化某个利润函数

在继续将各个关键概念定义清楚之前,让我们先介绍几个关键概念。重放内存,又称经验重放,涉及存储和重用过去的经验以增强学习过程并提高训练的稳定性和效率。

在深度强化学习中,一个代理与环境互动,观察状态,采取行动并获得奖励。每次观察、行动、奖励和导致的下一个状态被视为一次经验。重放内存作为一个缓冲区,存储了这些经验的集合。

重放内存具有以下关键特点:

存储

重放内存是一种可以存储固定数量经验的数据结构。每个经验通常包括当前状态、采取的行动、导致的奖励、下一个状态以及一个指示该情节是否终止的标志。

抽样

在训练过程中,代理程序不是立即使用发生的经验,而是从重放内存中抽取一批经验。从存储的大量转换中随机抽取经验有助于解相关数据并打破连续经验中存在的时间依赖关系。

批量学习

随机抽取的一批经验随后用于更新代理的神经网络。通过批量学习而不是单个经验的学习,代理可以更有效地利用计算资源并改善学习稳定性。批量学习还允许应用优化技术,如随机梯度下降,来更新网络权重。

重放内存为深度强化学习算法提供了几个好处。这些好处包括经验复用,因为代理可以从更多样化的数据集中学习,减少顺序更新可能引起的偏差。打破相关性是另一个好处,因为在强化学习的经验收集过程中,顺序的性质可能会在连续的经验之间引入相关性。从重放内存中随机抽取经验有助于打破这些相关性,使学习过程更加稳定。

到目前为止,我们已经讨论了以下步骤:

  1. 定义和初始化环境

  2. 设计神经网络架构

  3. 设计带有经验重放的强化学习架构以稳定学习过程

  4. 与环境交互并存储经验,直到学习过程完成并对新数据进行预测

我们尚未讨论的一件事是如何通过加倍神经网络架构来减少过度估计。

双深度 Q 网络(DDQN)模型是由 DeepMind 在 2015 年推出的原始 DQN 架构的扩展。DDQN 的主要动机是解决 DQN 算法中的已知问题,称为过度估计偏差,这可能导致次优的动作选择。

在原始 DQN 中,每个状态-动作对的动作值(Q 值)是使用单个神经网络估算的。然而,在学习过程中,Q 值是使用下一个状态中所有可能动作的最大 Q 值来估算的(查看 表 10-5)。这个最大 Q 值有时会导致真实动作值的过度估计,从而导致次优策略。

DDQN 通过利用两个独立的神经网络解决这种过度估计偏差:Q 网络和目标网络。Q 网络是一个深度神经网络,用于逼*动作值函数(Q 函数)。换句话说,它估算了给定状态下每个可能动作的价值。Q 网络的参数(权重和偏置)通过训练来学习,以最小化预测 Q 值和目标 Q 值之间的差异。目标网络是 Q 网络的一个单独副本,用于在训练期间估算目标 Q 值。它有助于稳定学习过程并改善 Q 网络的收敛性。目标网络的权重在训练过程中不会更新;相反,它们会定期更新以匹配 Q 网络的权重。

DDQN 的关键思想是将动作的选择与其值的估算分离开来。

注意

算法定期更新 Q 网络和偶尔更新目标网络。这样做是为了避免同一模型用于估算下一个状态的 Q 值,然后将其传递给贝尔曼方程,以估算当前状态的 Q 值的问题。

因此,为了将这些元素放入有序序列中,深度强化学习架构可能如下所示:

  1. 初始化环境。

  2. 选择 epsilon 值。请记住,epsilon 是控制训练期间代理行为的探索-利用权衡参数。

  3. 获取当前状态。请记住,当前状态的示例可能是 OHLC 数据、RSI、收益标准差,甚至是星期几。

  4. 在第一轮中,由于模型尚未训练,算法通过探索选择动作;因此,动作是随机选择的(例如,从买入、卖出和持有的选择面板)。如果不是第一步,则可能使用利用来选择动作。利用是指动作由神经网络模型确定。

  5. 应用动作。

  6. 将前述元素存储在回放内存中。

  7. 输入和目标数组被获取,并训练 Q 网络。

  8. 如果轮次尚未结束,请从第 3 步开始重复该过程。否则,训练目标网络并从第 1 步重复。

要说明算法,让我们在合成正弦波时间序列上使用它。创建时间序列,然后应用深度强化学习算法,旨在预测未来值。

完整的代码可以在 GitHub 仓库 中找到(供复制目的)。

图 10-1 显示了测试数据(实线)与预测数据(虚线),使用了 1 个周期,5 个输入(滞后值),批量大小为 64,并且有 1 个隐藏层,每个隐藏层有 6 个神经元。

图 10-1. 使用了 1 个周期,5 个输入,批量大小为 64,并且有 1 个隐藏层,每个隐藏层有 6 个神经元的测试数据与预测数据对比

图 10-2 显示了测试数据(实线)与预测数据(虚线),使用了 1 个周期,5 个输入(滞后值),批量大小为 64,并且有 2 个隐藏层,每个隐藏层有 6 个神经元。

图 10-2. 使用了 1 个周期,5 个输入,批量大小为 64,并且有 2 个隐藏层,每个隐藏层有 6 个神经元的预测值与实际值对比

图 10-3 显示了使用了 10 个周期,5 个输入(滞后值),批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 6 个神经元的预测。

图 10-3. 使用了 10 个周期,5 个输入,批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 6 个神经元的预测值与实际值对比

图 10-4 显示了使用了 10 个周期,5 个输入(滞后值),批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 24 个神经元的预测。

图 10-4. 使用了 10 个周期,5 个输入,批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 24 个神经元的预测值与实际值对比

图 10-5 显示了使用了 10 个周期,8 个输入(滞后值),批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 64 个神经元的预测。

图 10-5. 使用了 10 个周期,8 个输入,批量大小为 32,并且有 2 个隐藏层,每个隐藏层有 64 个神经元的预测值与实际值对比

如您所知,更多周期意味着更好的拟合——直到某一点,过拟合可能开始成为问题。幸运的是,现在您知道如何减少这种风险。

注意

请注意,正弦波示例非常基础,算法可以使用更复杂的数据。选择正弦波时间序列仅用于说明目的,您必须在更复杂的时间序列上使用更复杂的方法来评估算法。

强化学习容易过拟合,更容易学习简单的模式而不是隐藏且复杂的模式。此外,您现在应该意识到奖励函数设计的难度和特征选择的重要性。此外,这些模型通常被认为是神秘盒子,这使得解释其预测背后的推理变得困难。所有这些问题现在都成为实施稳定且盈利性强的深度强化学习算法用于交易的障碍。

总结

强化学习可以应用于时间序列预测任务,其目标是根据历史数据预测未来值。在这种方法中,代理与代表时间序列数据的环境互动。代理接收过去值的观察结果,并采取行动预测未来值。代理的行动涉及调整其内部模型或参数以进行预测。它使用强化学习算法从过去的经验中学习,并随着时间推移提高其预测准确性。

代理根据其预测的准确性接收奖励或惩罚。奖励可以设计成反映预测误差或预测对特定应用程序的效用。通过反复试验的过程,代理学会将时间序列数据中的某些模式或特征与未来结果联系起来。它学会了做出既能最大化奖励又能最小化误差的预测。

强化学习过程涉及在探索和利用之间取得*衡。代理探索不同的预测策略,试图发现模式并进行准确预测。它还利用其现有知识基础进行预测,根据迄今为止所学到的内容。强化学习用于时间序列预测的目标是训练代理以进行准确可靠的预测。通过不断接收反馈并更新其预测策略,代理适应时间序列中变化的模式并改进其预测能力。

第十一章 将展示如何运用更多的深度学习技术和应用。

¹ 买入并持有策略是一种被动行为,交易者或算法发起一笔买单,并长期持有以试图复制市场的回报,并最小化由于过度交易而产生的交易成本。

² 切记保持 epsilon 衰减,因为它将在稍后的代码中用作变量。

第十一章:高级技术和策略

到目前为止,您应该对深度学习算法及其如何开发模型以预测时间序列数据有了坚实的理解。尽管这只是部署盈利算法的第一步,但您应该知道,您从本书开始以来已经走了很长一段路。本章分为独立的几节,讨论了应用一些先进的深度学习技术和时间序列预测方法的有趣方式,以及增强该过程。

利用 COT 数据预测长期趋势

交易者承诺(COT)报告是由美国商品期货交易委员会(CFTC)发布的每周发布,提供了期货市场各种市场参与者持仓情况的信息。该报告基于从期货交易所(包括芝加哥商品交易所(CME)和洲际交易所(ICE))收集的数据。COT 报告将交易者分为三大组:

商业交易商(也称为经销商或套期保值者)

这些通常是利用期货市场来对冲其主要业务活动的公司。例如,粮食生产商可能使用期货合约来保护农业市场价格波动。他们的头寸通常与基础市场呈负相关。

非商业交易者(也称为基金或杠杆资金)

此组包括大型投机者,如对冲基金和商品交易顾问。非商业交易者通常根据其市场展望和追求利润的策略进行头寸建立。他们的头寸通常与基础市场呈正相关,因为他们具有追随趋势的特性。

非报告类交易者

此类别包括小型投机者和其头寸不符合 CFTC 设定的报告要求的交易者。他们与基础市场之间没有明确的相关性。

该报告详细说明了每个组的持仓情况,指出它们在特定期货市场中是否净多头(持有的多头头寸多于空头头寸)或净空头(持有的空头头寸多于多头头寸)。

交易者和投资者分析 COT 报告,以深入了解不同市场参与者的情绪和行为。通过监控头寸变动,他们试图识别市场潜在趋势或反转。该报告在商品和货币市场特别受欢迎,被用作基本分析工具和评估市场情绪。本节介绍了以下内容:

  • 创建一个算法自动下载 COT 数据并进行分析。

  • 绘制并理解 COT 值与其相应基础市场之间的相关性。此外,检查 COT 值中的*稳性,以查看它们是否可以直接用于算法中。

  • 使用滞后值创建一个 LSTM 算法,以预测下一个 COT 值并进行评估。这将被称为间接单步 COT 模型

  • 使用直接方法创建一个 LSTM 算法,以预测未来几周的 COT 观察值。这将被称为MPF COT 直接模型

  • 使用递归方法创建一个 LSTM 算法,以预测未来几周的 COT 观察值。这将被称为MPF COT 递归模型

COT 报告中主要有四列感兴趣的内容:长期对冲者、短期对冲者、长期基金和短期基金。它们是计算净对冲者和净基金的四个基本支柱。一些交易者喜欢在进行净值计算和看到更大画面之前分析每一列。在进行净值过程之前了解每一列背后的逻辑有助于理解。

长期对冲者(商业交易者的多头头寸百分比)

你可以将这些对冲者视为资产的消费者。长期对冲者是购买某种资产(如小麦)期货以对冲其主要业务的实体。主要目标是通过在未来锁定所需的固定价格来保护自己免受价格上涨的风险。通过这样做,他们可以更准确地计划生产成本,并避免如果小麦价格上涨可能带来的损失。因此,长期对冲者购买资产是出于担心其价格将上涨。他们通常在价格下跌时购买,这导致与资产价格的负相关性。

短期对冲者(商业交易者的空头头寸百分比)

你可以将这些对冲者视为资产的生产者。短期对冲者是出售某种资产的空头期货以对冲其主要业务的实体。主要目标是保护自己免受价格下跌的风险。因此,短期对冲者在资产价格上涨时卖出。他们通常在价格上涨时出售,这导致与资产价格的正相关性(这意味着随着资产价格的上涨,短期对冲者的数量增加)。

长期基金(非商业交易者的多头头寸百分比)

投机性长期基金头寸是未来合约的买方,预期价格将上涨。由于其追随趋势的特性,它们与资产价格有正相关性。

空头基金(非商业交易者的空头头寸百分比)

投机性卖空基金头寸是未来合约的卖方,预期价格将下跌。由于其追随趋势的特性,它们与资产价格有负相关性。例如,当资产上涨时,这些空头基金的数量减少。

根据您的需求,可以以不同方式进行 COT 报告的清算。如果您偏向于商业交易者,那么您可以简单地取商业多头和商业空头之间的差值(或者您可以称之为消费者和生产者)。如果您偏向于非商业交易者,那么您可以取非商业多头和非商业空头之间的差值。如果您偏向于全球视角,那么您可以取商业和非商业交易者的净清算差值,这样您将得到一个只总结特定资产市场定位全局图像的时间序列。

表 11-1 对净 COT 值的计算进行了一些解释。

表 11-1. COT 净清算表示

套保者多头 套保者空头 基金多头 基金空头 净套保者 净基金 净 COT
A B C D E = A - B F = C - D G = F - E

净 COT 值具有以下公式:

Net COT = 净资金 - 净套保者

它与资产的基础价格呈正相关。图 11-1 显示了加拿大元(CAD)的净 COT 定位。

图 11-1. 自 2015 年以来的净 COT 加元;注意其价值的均值回归性质。

让我们看看如何使用 Python 下载 COT 值。首先,pip install 允许您自动从 CFTC 网站下载 COT 值的库:

pip install cot_reports

如果库存在问题,您可以使用在GitHub 仓库中找到的预下载的 Excel 格式 COT 报告进行手动导入(本节末尾提供了一个代码块)。

导入必要的库以下载 COT 报告的历史观察结果。为简单起见,让我们选择加元的定位:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from master_function import import_cot_data

允许您获取所选市场 COT 值的 import_cot_data() 函数定义如下(在 master_function.py 中找到):

def import_cot_data(start_year, end_year, market):
    df = pd.DataFrame()
    for i in range(start_year, end_year + 1):
        single_year = pd.DataFrame(cot.cot_year(i, 
                      cot_report_type='traders_in_financial_futures_fut'))
        df = pd.concat([single_year, df], ignore_index=True)
    new_df = df.loc[:, ['Market_and_Exchange_Names',
                        'Report_Date_as_YYYY-MM-DD',
                        'Pct_of_OI_Dealer_Long_All',
                        'Pct_of_OI_Dealer_Short_All',
                        'Pct_of_OI_Lev_Money_Long_All',                    
                        'Pct_of_OI_Lev_Money_Short_All']]
    new_df['Report_Date_as_YYYY-MM-DD'] = 
                       pd.to_datetime(new_df['Report_Date_as_YYYY-MM-DD'])
    new_df = new_df.sort_values(by='Report_Date_as_YYYY-MM-DD')
    data = new_df[new_df['Market_and_Exchange_Names'] == market]
    data['Net_COT'] = (data['Pct_of_OI_Lev_Money_Long_All'] – \
                       data['Pct_of_OI_Lev_Money_Short_All']) – \
                      (data['Pct_of_OI_Dealer_Long_All'] –\
                       data['Pct_of_OI_Dealer_Short_All'])                
    return data

要导入加元 COT 值,使用以下语法:

CAD = 'CANADIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE'
data = import_cot_data(2015, 2023, CAD)
data = np.array(data.iloc[:, –1], dtype = np.float64)

值得一提的是,其他市场有以下代码名称,您可以用来导入它们:

EUR = 'EURO FX - CHICAGO MERCANTILE EXCHANGE'
GBP = 'BRITISH POUND STERLING - CHICAGO MERCANTILE EXCHANGE'
JPY = 'JAPANESE YEN - CHICAGO MERCANTILE EXCHANGE'
CHF = 'SWISS FRANC - CHICAGO MERCANTILE EXCHANGE'
AUD = 'AUSTRALIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE'
MXN = 'MEXICAN PESO - CHICAGO MERCANTILE EXCHANGE'
BRL = 'BRAZILIAN REAL - CHICAGO MERCANTILE EXCHANGE'
BTC = 'BITCOIN - CHICAGO MERCANTILE EXCHANGE'
SPX = 'E-MINI S&P 500 - CHICAGO MERCANTILE EXCHANGE'

注意

如果出现请求错误,请在运行导入部分之前应用以下代码(记得 pip install proxy_requests 库):

from proxy_requests.proxy_requests import ProxyRequests
req = ProxyRequests("https://api.ipify.org")
req.get()

图 11-2 显示了加元相对于加元/美元的净 COT 定位。注意两者之间强烈的正相关性。计算最* 200 次观察的皮尔逊相关系数为惊人的 0.66。换句话说,净 COT 数据的高点与加元/美元的高点相吻合。同样,净 COT 数据的低点与加元/美元的低点相吻合。

图 11-2. 加元/美元(左刻度)与加元净 COT 定位(右刻度)。
警告

请注意,图表显示的是 CADUSD 而不是常用的 USDCAD 货币对。这是因为您试图理解 CAD;因此,将其作为基础货币有助于您看到 CAD 投机者的正相关性以及 CAD 套期保值者的负相关性。要从 USDCAD 观察中获得 CADUSD,请取其倒数:

C A D U S D = 1 USDCAD

下一步是检查 COT 值的*稳性,以了解是否需要进行转换。请记住,转换可以是差分、取百分比回报,甚至使用分数阶差分(如第九章所述):

from statsmodels.tsa.stattools import adfuller
print('p-value: %f' % adfuller(raw_data)[1])

输出如下:

p-value: 0.000717

COT 值似乎是稳定的,可以用作算法的输入。

算法 1:间接单步 COT 模型

拟合优度模型将使用长短期记忆(LSTM)来预测每个时间步长的下一个 COT 值。假设预测与基础市场方向相关的值可能会对未来一周的预期移动产生偏见。例如,如果预测未来一周 COT 值较高,则您可能会在每周交易准备中有一个看涨 CAD 的偏好。

首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import import_cot_data, data_preprocessing
from master_function import plot_train_test_values, 
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

导入所需的数据:

CAD = 'CANADIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE'
data = import_cot_data(2015, 2023, CAD)
data = np.array(data.iloc[:, –1], dtype = np.float64)

设置超参数并创建数组:

num_lags = 100
train_test_split = 0.80
num_neurons_in_hidden_layers = 200
num_epochs = 200
batch_size = 4
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, 
                                                      num_lags, 
                                                      train_test_split)

为了符合 LSTM 架构,独立变量必须转换为三维数组。可以使用以下代码完成这一操作:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

接下来,创建模型的架构并预测训练集(仅了解拟合优度)和测试集上的值:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer
model.add(Dense(units = 1))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in the training set for illustrative purposes
y_predicted_train = model.predict(x_train)
# Predicting in the test set
y_predicted = model.predict(x_test)

要绘制预测值与实际值的图表,请使用以下语法:

plot_train_test_values(100, 50, y_train, y_test, y_predicted)

图 11-3 将预测值与实际测试值进行了比较。乍看之下,模型似乎很好地捕捉了 COT 值的变化。让我们来看一下性能结果。

图 11-3. COT 训练数据后跟 COT 测试数据(虚线)和预测的 COT 数据(细线);垂直虚线表示测试期的开始。使用的模型是 LSTM 回归算法。

在从 2015 年到 2023 年使用的 CADUSD 模型结果如下:

Directional Accuracy Train =  86.18 %
Directional Accuracy Test =  60.87 %
RMSE Train =  5.3655332132
RMSE Test =  14.7772701349
Correlation In-Sample Predicted/Train =  0.995
Correlation Out-of-Sample Predicted/Test =  0.88

注意

对您而言的一个有趣任务是应用模型来预测基础市场的回报,使用 COT 值作为输入。您可以使用净 COT 值或其他六个可用的系列之一,例如长期套期保值者和净资金。但请务必始终检查*稳性。

算法 2:MPF COT 直接模型

MPF COT 模型将使用 LSTM 预测 COT 值的轨迹,以引导主要市场走势的到来。假设通过预测下一个 COT 值(比市场本身更少噪音),您可以获得市场预期轨迹的指南。由于 COT 值是*稳的,并且与非*稳的市场高度相关,使用 MPF 直接在市场上预测比使用 MPF 在市场上直接预测要有更好的预测机会。该算法使用直接方法(更多信息,请参阅第九章)。首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import import_cot_data, direct_mpf
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error

设置超参数并创建数组:

# Setting the hyperparameters
num_lags = 100
train_test_split = 0.80
neurons = 400
num_epochs = 200
batch_size = 10
forecast_horizon = 50
# Creating the training and test sets
x_train, y_train, x_test, y_test = direct_mpf(data, 
                                              num_lags, 
                                              train_test_split, 
                                              forecast_horizon)

为了符合 LSTM 结构,必须将独立变量转换为三维数组。可以使用以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

接下来,创建模型的结构并使用递归函数预测值:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer 
model.add(Dense(units = forecast_horizon))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in the test set
y_predicted = model.predict(x_test)

要绘制预测值与实际值的图,使用以下语法:

plt.plot(y_predicted[–1], label = 'Predicted data', color = 'red', 
         linewidth = 1)
plt.plot(y_test[–1], label = 'Test data', color = 'black', 
         linestyle = 'dashed', linewidth = 2)
plt.grid()
plt.legend()

图 11-4 将预测值与真实测试值进行了比较。

图 11-4. 预测数据与测试数据对比。

以下是对 CADUSD 使用的模型的结果:

Directional Accuracy Test =  57.14 %
RMSE Test =  26.4021245739
Correlation Out-of-Sample Predicted/Test =  0.426

算法 3:MPF COT 递归模型

该算法使用递归方法(更多信息,请参阅第九章)。首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import data_preprocessing, import_cot_data
from master_function import plot_train_test_values, recursive_mpf
from master_function import calculate_directional_accuracy

设置超参数并创建数组:

num_lags = 100
train_test_split = 0.80
neurons = 100
num_epochs = 200
batch_size = 2
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data,
                                                      num_lags,
                                                      train_test_split)

为了符合 LSTM 结构,必须将独立变量转换为三维数组。可以使用以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

接下来,创建模型的结构并使用递归函数预测值:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer 
model.add(Dense(units = 1))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')
# Fitting the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in the test set on a recursive basis
x_test, y_predicted = recursive_mpf(x_test, y_test, num_lags, model)

要绘制预测值与实际值的图,使用以下语法:

plot_train_test_values(100, 50, y_train, y_test, y_predicted)

图 11-5 将预测值与测试值进行了比较。

图 11-5. COT 数据的多期预测。

以下是对从 2015 年到 2023 年 CADUSD 使用的模型的结果:

Directional Accuracy Test =  52.17 %
RMSE Test =  40.3120541504
Correlation Out-of-Sample Predicted/Test =  0.737

注意

记住,在设置 random_state 并确保可复现性对于实验是有用的,但如果数据分布确实是随机的,则可能限制模型良好泛化的能力。在许多情况下,建议使用不同的随机种子进行多次运行,以更好地了解模型的性能和稳健性。这就是为什么本书中许多模型的代码中没有 random_state 实现(除了第七章中的少数模型)。

不要忘记过拟合的症状。不幸的是,过拟合有时并不那么容易检测到,因此请考虑这些一般规则:

高训练精度和低测试精度

该模型在训练数据上表现出色,但在测试数据上表现不佳。这清楚地表明,该模型记住了训练数据,而非学习了一般模式。

训练和测试性能之间存在显著差距

训练和测试误差率之间存在显著差异。理想情况下,这两个指标应该接*,而较大的差距则表明过拟合。

模型复杂度异常高

如果模型过于复杂,具有大量参数或特征,则更容易出现过拟合。较简单的模型可能对新数据泛化效果更好。

噪声预测

过拟合模型倾向于在新数据上做出错误和不一致的预测。这是因为它们对输入数据的微小变化(包括噪声)非常敏感。

综合应用

每周五由 CFTC 发布的 COT 报告概述了关键市场参与者的持仓情况。它可以转化为时间序列预测任务,旨在改善市场预测。特别感兴趣的市场参与者是商业参与者(交易商或对冲者)和非商业参与者(杠杆资金或基金)。

注意

确保将master_function.py放在解释器的目录中。或者,您可以简单地在 Python 中打开master_function.py并执行整个文件。但是,后一种方法需要您每次重新启动内核时都这样做。

深度学习技术可以应用于 COT 值,以预测市场定位通过隐藏模式和季节配置。本节讨论了三种算法从 COT 报告中挤出价值。您可以尝试使用机器学习模型、深度强化学习,甚至简单的统计技术来更好地理解 COT 数据。

要手动导入 COT 值,请参考在GitHub 仓库找到的历史数据,并使用以下代码(将解释器的路径更改为下载文件所在的位置):

import pandas as pd
import numpy as np
# Import the data using pandas
data = pd.read_excel('name_of_file.xlsx')

重要的是要记住 COT 报告提供了市场参与者持仓的快照,应与其他工具和分析方法结合使用。虽然它可以提供有价值的信息,但它并不是独立的交易策略,而是需要仔细考虑其他因素才能做出明智的交易决策。

使用技术指标作为输入

您在第五章中学习了技术指标。现在是时候将它们作为输入,以预测底层市场的回报。使用滞后值意味着过去的观察值可能会转化为可观测的预测。本节将探讨这一假设之外的过去,并寻找其他价格变换中的价值。您可以进行以下价格衍生计算:

数学变换

这种转换类型很可能是对原始数据的直接操作。一个例子是基本差分或简单移动*均。归一化也是数学转换的一部分。

技术转换

这种类型的转换不太明显,结果可能一点也不像原始数据。例如,RSI 就是这样的一个技术指标,它是基于移动*均线和归一化的递归规则创建的。

分类转换

这种转换类型将数值数据转换为分类数据,反之亦然。例如,被称为OneHotEncoder的算法将分类数据转换为二进制数据,以便机器学习算法能够对其进行分类。

在继续之前,有一个值得讨论的数据问题。多重共线性是回归分析中的统计现象,当多个独立变量(输入)在多元回归模型中彼此高度相关时发生。换句话说,这种情况是指两个或更多预测变量之间存在强线性关系。这种相关性可能会使得回归模型难以区分每个预测变量对依赖变量(结果变量)的个别影响。显然,如果您使用具有不同时间周期的多个 RSI 指标,则很可能会遇到多重共线性问题。请确保寻找弱相关的指标。

本示例中使用了两个技术指标(或转换):

  • 五周 RSI,一个不需要任何转换的稳定指标。

  • 周收盘价与 20 周移动*均线之间的差异。这也是一个不需要任何转换的稳定计算。

因此,将使用上周的 RSI 值和上周收盘价与 20 周移动*均线之间的距离来预测 EURUSD 的每周回报。

首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import mass_import, rsi, ma, calculate_accuracy
from master_function import plot_train_test_values, 
from master_function import multiple_data_preprocessing
from sklearn.metrics import mean_squared_error
from master_function import add_column, delete_column

接下来,使用mass_import()函数导入数据:

data = mass_import(0, 'W1')[:, –1]
data = rsi(np.reshape(data, (–1, 1)), 5, 0, 1)
data = ma(data, 5, 0, 2)
data[:, 2] = data[:, 0] – data[:, 2]
data = add_column(data, 1)
for i in range(len(data)):
    data[i, 3] = data[i, 0] – data[i – 1, 0]
data[:, 0] = data[:, –1]
data = delete_column(data, 3, 1)

定义多数据预处理函数,该函数获取两个技术指标的值并对它们进行滞后处理,以便用作预测 EURUSD 回报的输入。表 11-2 显示了训练表x_train,其中包含六个滞后值作为独立变量来解释和预测下一个 EURUSD 回报。

表 11-2。训练数组的示例

RSI t–1 (Close – MA)t–1 RSI t–2 (Close – MA)t–2 RSI t–3 (Close – MA)t–3
36.6190 –0.003804 48.5188 0.001044 42.4396 –0.001714
46.7928 0.001674 36.6190 –0.003804 48.5188 0.001044
40.5430 –0.002518 46.7928 0.001674 36.6190 –0.003804
65.9614 0.011340 40.5430 –0.002518 46.7928 0.001674
47.2585 –0.000390 65.9614 0.011340 40.5430 –0.002518
63.9755 0.011302 47.2585 –0.000390 65.9614 0.011340

下一个函数已经定义,但讨论其功能并无害处。multiple_data_preprocessing()函数仅为回测创建了四个必需的数组,但使用了两个技术指标作为输入。你可以定义这个函数来处理六个滞后值(三个用于 RSI,三个用于收盘价与移动*均值之间的差)如下:

def multiple_data_preprocessing(data, train_test_split):
    data = add_column(data, 4)
    data[:, 1] = np.roll(data[:, 1], 1, axis = 0)
    data[:, 2] = np.roll(data[:, 2], 1, axis = 0)
    data[:, 3] = np.roll(data[:, 1], 1, axis = 0)
    data[:, 4] = np.roll(data[:, 2], 1, axis = 0)
    data[:, 5] = np.roll(data[:, 3], 1, axis = 0)
    data[:, 6] = np.roll(data[:, 4], 1, axis = 0)
    data = data[1:, ]
    x = data[:, 1:]
    y = data[:, 0]
    split_index = int(train_test_split * len(x))
    x_train = x[:split_index]
    y_train = y[:split_index]
    x_test = x[split_index:]
    y_test = y[split_index:]
    return x_train, y_train, x_test, y_test

设定超参数并创建数组:

num_lags = 6 # Must equal the number of the lagged values you create
train_test_split = 0.80
neurons = 500
num_epochs = 500
batch_size = 200
# Creating the training and test sets
x_train, y_train, x_test, y_test = multiple_data_preprocessing(data, 
                                                   train_test_split)

要符合 LSTM 架构,独立变量必须转换为三维数组。这可以通过以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

接下来,创建模型的架构并预测训练集(仅了解拟合程度)和测试集上的值:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a third layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a fourth layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a fifth layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer 
model.add(Dense(units = 1))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'ada
# Fitting the model
model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
# Predicting in the training set for illustrative purposes
y_predicted_train = model.predict(x_train)
# Predicting in the test set
y_predicted = model.predict(x_test)

要绘制预测值与实际值,请使用以下语法:

plot_train_test_values(100, 50, y_train, y_test, y_predicted)

图 11-6 将预测值与真实测试值进行比较。

图 11-6. 训练数据随后是测试数据(虚线),以及预测数据(细线);垂直虚线代表测试期的开始。使用的模型是 LSTM 回归算法。

以下是模型的结果:

Accuracy Train =  59.39 %
Accuracy Test =  51.82 %
RMSE Train =  0.0163232045
RMSE Test =  0.0122093494
Correlation In-Sample Predicted/Train =  0.255
Correlation Out-of-Sample Predicted/Test =  0.015

调整模型并看到如何改进是非常有趣的。由于这些是每周的预测,高准确率可能是优化模型和确保不过拟合的第一步。¹ 值得注意的是,由于此算法试图预测工具的财务回报,所以使用calculate_accuracy()函数而不是ca⁠lc⁠ul⁠ate_​di⁠re⁠cti⁠on⁠al_accuracy()

注意

尝试在 MPF 模式下运行本节中看到的算法,并看看你能从中提取什么。记住在执行时算法的限制条件。

使用深度学习预测比特币的波动性

比特币是一种去中心化的数字货币,由一个使用化名Satoshi Nakamoto的未知个人或实体于 2009 年创造。它是第一个引入的加密货币,至今仍然是最知名和广泛交易的加密货币。考虑到这些年对比特币的巨大炒作,你可能不需要介绍它,但多一些了解永远有好处。

比特币运行在一种名为区块链的技术上,这是一个分布式分类帐系统。与由政府或中央银行发行和监管的传统货币不同,比特币不受任何中央机构控制。

相反,它依赖于一个点对点网络的计算机节点来验证和记录交易。如今,比特币在加密货币交易所上交易频繁,并用于投机和套期保值操作。最常交易的货币对是 BTCUSD,即 1 比特币相对于美元的价值。

图 11-7 显示了 BTCUSD(比特币兑美元的价值)的演变。

Figure 11-7. 自 2014 年以来 BTCUSD 的历史演变,使用线性刻度。

您可以使用以下代码生成 Figure 11-7:

import pandas as pd
# Manually importing BTCUSD values
my_data = pd.read_excel('Bitcoin_Daily_Historical_Data.xlsx')
# Renaming the columns
my_data.columns = ['Date', 'Open', 'High', 'Low', 'Close']
# Setting the date column
my_data['Date'] = pd.to_datetime(my_data['Date'])
# Charting
plt.plot(my_data['Date'], my_data['Close'], label = 'BTCUSD', 
         color = 'black')
plt.legend()
plt.grid()

确保从GitHub 仓库下载历史 BTCUSD 值,并将解释器目录设置为与下载文件相同的文件夹中。

Figure 11-7 使用线性刻度绘制,这意味着数据在直线和均匀间隔的轴上表示(10 和 20 之间的空间与 1230 和 1240 之间的空间相同)。还可以使用所谓的对数刻度来绘制经历大幅跳跃的时间序列。

Figure 11-8 显示了 BTCUSD(比特币价值以美元计价)的对数尺度演变。

Figure 11-8. 自 2014 年以来 BTCUSD 的历史演变,使用半对数尺度。

使用以下代码使用plt.semilogy()函数生成 Figure 11-8:

plt.semilogy(my_data['Date'], my_data['Close'], label = 'BTCUSD', 
             color = 'black')
plt.legend()
plt.grid()

注意

semilog一词指的是将两个轴之一转换为对数刻度,而log指的是将两个轴都转换为对数刻度。

由于时间是线性的,因此您只需转换y-轴(即值),因此您在技术上使用的是半对数图表。这就是为什么matplotlib()函数被称为pl⁠t.​se⁠mi⁠log⁠y()

如果您喜欢使用 Python 脚本导入 BTCUSD 值,请使用以下代码:

import requests
import json
import pandas as pd
import numpy as np  
import datetime as dt

# Selecting the time frame
frequency = '1h'
# Defining the import function
def import_crypto(symbol, interval = frequency):
    # Getting the original link from Binance
    url = 'https://api.binance.com/api/v1/klines'
    # Linking the link with the cryptocurrency and the time frame
    link = url + '?symbol=' + symbol + '&interval=' + interval
    # Requesting the data in the form of text
    data = json.loads(requests.get(link).text)
    # Converting the text data to a dataframe
    data = np.array(data)
    data = data.astype(np.float)
    data = data[:, 1:5]
    return data
# Importing hourly BTCUSD data
data = import_crypto('BTCUSDT')

让我们看看深度学习是否有助于利用滞后值预测 BTCUSD 的波动性。但首先,有两个问题需要回答:

  • 预测比特币波动性的用途是什么?

  • 如何计算比特币的波动性?

要回答第一个问题,通过预测比特币的波动性,交易者可以潜在地识别出价格波动增加的时期,并从中获利。类似地,波动性预测还可以提供市场情绪的见解。当投资者预期不确定或动荡的市场条件时,可能表明缺乏信心,从而导致市场动态的潜在变化。

要回答第二个问题,您可以使用收盘价格的滚动标准差测量。这允许您创建一个反映比特币历史波动性的新时间序列。

注意

通常,波动率的增加是市场压力和恐惧的迹象,这转化为熊市情绪。相反,波动率的减少是市场健康稳定的迹象,这转化为牛市。这种关系并不完美,其他变量可能会影响它。例如,如果你计算比特币每日收盘价格与 10 天滚动波动率的相关系数,你会发现它是正的(在某些时期,它非常正)。比特币以兴奋资产而闻名,涨势伴随着一种称为 FOMO 的现象,即害怕错过的缩写。这种心理偏差是市场泡沫的构成要素之一,所有人都希望从中获利而不断买入。

预测分析的第一步是理解你正在处理的数据。提醒一下,波动率指的是金融工具(如股票、债券、大宗商品或货币)价格在特定时间段内的变化或波动程度。它是该特定资产的离散度的统计度量。

图 11-9 显示了比特币的最新 10 天波动率值,这些值是通过滚动标准差计算得出的。最新的数据表明,最*,价格波动大部分时间都在 10 天均值周围的 500 美元左右波动(更深入地了解标准差,请参阅第三章)。

图 11-9. 比特币的滚动 10 天标准差作为波动率的代理。

让我们开始吧。对于这个任务,让我们手动将 BTCUSD 导入解释器。首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import add_column, delete_row, volatility
from master_function import data_preprocessing, plot_train_test_values
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error
from statsmodels.tsa.stattools import adfuller

接下来,使用pandas导入数据:

data = pd.read_excel('Bitcoin_Daily_Historical_Data.xlsx').values

下一步是计算波动率。其函数在master_function中定义,因为你已经导入了它,所以应该可以直接使用:

data = volatility(data, 10, 0, 1)
data = data[:, –1]

你必须检查是否*稳。如果数据是*稳的,那么它就准备好用于训练了。否则,你可能需要进行转换。以下代码对比特币的 10 天波动率应用 ADF 测试:

print('p-value: %f' % adfuller(data)[1])

这是输出:

p-value: 0.120516

看起来波动率值是非*稳的。让我们尝试进行差分:

data = np.diff(data)
注意

你也可以应用分数阶差分以保留一丝记忆。

图 11-10 显示了比特币的最新 10 天差异波动率值。

图 11-10. 比特币的滚动 10 天标准差(差异)。

接下来是设置超参数并像往常一样准备数组:

num_lags = 300
train_test_split = 0.80
neurons = 80
num_epochs = 100
batch_size = 500
# Prepare the arrays
x_train, y_train, x_test, y_test = direct_mpf(data, 
                                              num_lags, 
                                              train_test_split, 
                                              forecast_horizon)

要符合 LSTM 架构,独立变量必须转换为三维数组。这可以通过以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

创建深度神经网络架构,并添加几个额外的层:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a third layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a fourth layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer 
model.add(Dense(units = forecast_horizon))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')

现在,拟合并预测数据:

model.fit(x_train, y_train, epochs = num_epochs, batch_size = batch_size)
y_predicted = model.predict(x_test)

图 11-11 比较了预测值与测试值。

图 11-11. 预测比特币的波动性;所使用的模型是 LSTM 回归算法。

以下是模型的结果:

Accuracy Train =  66.56 %
Accuracy Test =  63.59 %
RMSE Train =  95.6086778521
RMSE Test =  186.1401365824
Correlation In-Sample Predicted/Train =  0.807
Correlation Out-of-Sample Predicted/Test =  0.56

请记住,由于回测期间、时间粒度、交易成本、不同经纪人的不同报价、不同超参数和不同随机化,回测结果可能会有显著差异。优化是关键组成部分,必须在对算法形成意见之前对结果进行验证。因此,你的任务是改善结果,并更好地预测波动性。

你可能还想知道是否可以直接准确地预测比特币的回报,而不是随机的。答案是肯定的,如果你需要一些可能有助于预测比特币回报的输入的想法,请查看以下非详尽列表:

  • 历史价格数据及其衍生品:使用历史价格数据是预测回报的基本方面。你可以包括每日、每周或每月价格变动、移动*均值和价格波动率等特征。

  • 交易量:比特币的交易量提供了有关市场活动水*和流动性的宝贵信息。更高的交易量通常伴随着重大的价格波动。

  • 市场情绪指标:来自社交媒体*台、新闻文章或论坛的情绪分析可以帮助衡量市场对比特币的整体情绪。比特币的恐惧与贪婪指数是一个不错的选择,因为它每天发布一次,并使用许多基本变量来计算其值。

  • 网络指标:比特币的区块链数据提供了有用的指标,例如交易数量、哈希率和难度水*。这些指标反映了比特币网络的活动和健康状况,这可能会影响价格。

  • 市场指标:考虑使用一般市场指标,如标普 500 指数或恐慌指数作为外部变量。加密货币,包括比特币,有时会与传统金融市场表现出相关性。

  • 加密货币特定指标:与加密货币领域特定的其他指标,例如所有加密货币的总市值和比特币的主导比例,可能提供有关更广泛市场情况的见解。

  • 技术指标:各种技术分析指标,例如 RSI 和波动性,可以提供有关潜在价格趋势和逆转的见解。

  • Google 趋势:监测与比特币相关的搜索词在 Google 趋势上的流行度可以提供有关公众兴趣和潜在价格变动的见解。

  • 加密货币交易数据:来自加密货币交易所的数据,例如未*仓合约、资金费率和清算数据,可以提供有关市场动态和潜在价格变动的见解。

训练的实时可视化

训练过程中发生了什么?当你查看每个时期的进度条时,你会发现训练过程正在进行中:

第 1/100 时期

9/9 [=============================] – 3s 77ms/step - loss: 0.0052

Epoch 2/100

9/9 [=============================] – 1s 78ms/step - loss: 0.0026

Epoch 3/100

9/9 [=============================] – 1s 68ms/step - loss: 0.0015

然而,您还可以编写一个动态图表,显示样本内预测如何通过时期更新并接*样本内的真实值。这将是本节的第一个目标。在继续之前,请刷新您对术语的理解:

在样本内的真实值

这些是y_train中包含的值。它们是模型用来校准训练的真实值。它们属于训练集。

样本内的预测

这些是y_predicted_train中包含的值。它们是模型在训练过程中输出的预测值。它们属于训练集,并受到前瞻偏差的影响,即在做出决策或预测时使用未来信息,而这些决策或预测应该基于当时仅有的历史数据。

样本外的真实值

这些是用于测试模型在从未见过的数据上预测值的测试值。它们属于测试集。

样本外的预测

这些是跟随训练阶段的预测值。它们属于测试集。

让我们举个你在第九章熟悉的例子,ISM PMI 数据。目标是创建一个一步预测的 LSTM 模型,预测 ISM PMI 值的差分,同时在训练过程中动态绘制图表,显示预测结果如何校准到训练集。首先,导入所需的库:

import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, LSTM
from master_function import data_preprocessing
from master_function import calculate_directional_accuracy
from sklearn.metrics import mean_squared_error
import tensorflow as tf
import random

GitHub 代码库中导入并进行 ISM PMI 数据的差分:

 data = np.diff(np.reshape(pd.read_excel('ISM_PMI.xlsx').values, (–1)))

下一步是设置超参数并像往常一样准备数组:

num_lags = 100
train_test_split = 0.80
neurons = 200
num_epochs = 200
batch_size = 4
# Creating the training and test sets
x_train, y_train, x_test, y_test = data_preprocessing(data, 
                                                      num_lags, 
                                                      train_test_split)

为了符合 LSTM 架构,必须将独立变量转换为三维数组。可以使用以下代码完成:

x_train = x_train.reshape((–1, num_lags, 1))
x_test = x_test.reshape((–1, num_lags, 1))

创建深度神经网络架构:

# Create the LSTM model
model = Sequential()
# Adding a first layer
model.add(LSTM(units = neurons, input_shape = (num_lags, 1)))
# Adding a second layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a third layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding a fourth layer
model.add(Dense(neurons, activation = 'relu')) 
# Adding the output layer 
model.add(Dense(units = 1))
# Compiling the model
model.compile(loss = 'mean_squared_error', optimizer = 'adam')

以下代码将x_train数据拟合到y_train数据,并在每个时期绘制预测结果:

def update_plot(epoch, logs):
    if epoch % 1 == 0:
        plt.cla()
        y_predicted_train = model.predict(x_train)
        plt.plot(y_train, label = 'Training data', 
                 color = 'black', linewidth = 2.5)
        plt.plot(y_predicted_train, label = 'Predicted data', 
                 color = 'red', linewidth = 1)
        plt.title(f'Training Epoch: {epoch}')
        plt.xlabel('Time')
        plt.ylabel('Value')
        plt.legend()
        plt.savefig(str(random.randint(1, 1000)))
# Create the dynamic plot
fig = plt.figure()
# Train the model using the on_epoch_end callback
class PlotCallback(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs = None):
        update_plot(epoch, logs)
        plt.pause(0.001)
plot_callback = PlotCallback()
history = model.fit(x_train, y_train, epochs = num_epochs, 
                    batch_size = batch_size, callbacks = [plot_callback])

图 11-12 展示了第 1 个时期的训练情况。注意算法刚刚开始,尚未完全捕捉独立和因变量之间的关系。

图 11-12。ISM PMI 数据在第 1 个时期的训练正在进行中。

图 11-13 展示了第 21 个时期的训练情况。看起来算法仍在校准自身以适应特征。

图 11-13。ISM PMI 数据在第 21 个时期的训练正在进行中。

图 11-14 展示了第 29 个时期的训练情况。模型开始适应数据。

图 11-14。ISM PMI 数据在第 29 个时期的训练正在进行中。

图 11-15 展示了第 62 轮训练。模型似乎很好地拟合了数据,尽管存在一些错误,但这不是本节的主要目的。

动态训练图表可以是一个有趣的工具,用于观察模型的学习情况——以及它是否真正在学到东西。这有助于解决常数预测的问题,即当模型无法捕捉因变量和自变量之间的任何关系时。

图 11-15. ISM PMI 数据在第 62 轮训练中进行中。

几个因素可能导致深度学习中的常数预测问题:

糟糕的模型架构

如果模型表达能力不足,可能会难以学习有意义的模式,从而采用简单的常数预测。

数据有限或噪声过多

数据不足或噪声过多可能会阻碍模型学习有意义的模式。如果数据缺乏多样性或包含显著错误,模型可能会收敛到常数预测,因为这是最简单的方式来减少损失。

不恰当的损失函数

损失函数的选择在训练期间对指导模型起着至关重要的作用。如果损失函数不适合任务或模型架构,可能不会鼓励模型做出多样化预测,导致常数预测。

糟糕的超参数调整

超参数,如批量大小和神经元数量,可以显著影响训练过程。如果这些超参数未得到适当调整,模型可能无法有效收敛,导致常数预测。

此时,您一定想知道使用Sequential()Dense()函数构建的架构是什么样子。自然地,它应该类似于您之前看到的神经网络图。要查看此内容,您必须从提示符中pip install所需的库:

pip install pydot
pip install pydotplus
pip install graphviz

接着,从官方网站下载graphviz二进制文件夹,解压文件内容,然后将bin文件夹设置为 Python 解释器(例如 Spyder)的路径之一。重新启动内核,然后像往常一样编译您的模型。最后,使用以下代码打印架构:

from tensorflow.keras.utils import plot_model
from IPython.display import Image
plot_model(model, to_file = 'Architecture.png', 
           show_shapes = True, 
           show_layer_names = True)
Image('Architecture.png')

图 11-16 展示了代码的输出。

图 11-16. 模型架构示例。

代码输出编译后的 LSTM 模型当前的架构。

概要

这个信息密集的章节向你展示了使用一些交易算法预测回报的几个想法。它主要是激发批判性和创新性思维的一种方式,并从中找到可以推导交易信号的新想法。例如,你可以尝试在从算法得到的信号上应用过滤器。过滤器 就像开关一样,根据最终条件是否满足来允许信号。一个带有过滤器的假设交易策略的例子是,仅在市场位于其 200 日移动*均线之上时采取看涨信号,并且仅在市场位于其 200 日移动*均线之下时采取看跌信号。

你的主要收获应该是如何组织结构,以便理解回测过程。与本章相比,第十二章将是一场轻松的旅程,因为它探讨了风险管理和旨在增强研究过程的基本工具。

¹ 摇摆交易涉及持有短期到中期的头寸,通常是几天到几周,以从价格波动或大趋势中获利。

² 该指数用于衡量加密货币市场投资者的情绪和情绪。它提供了一个从 0 到 100 的数值,其中较低的值表示极度恐惧,较高的值表示极度贪婪。该指数旨在帮助交易员和投资者根据当前情绪识别潜在的市场转折点。

第十二章:市场驱动因素和风险管理

在今天快速发展的市场中,理解市场驱动因素和风险管理对于在交易和投资努力中取得成功至关重要。

市场驱动因素是影响金融市场的因素,如经济指标、企业绩效、地缘政治事件、央行政策和技术进步。通过了解这些驱动因素,投资者可以做出明智的决策,预测市场趋势。算法交易员必须注意可能导致模型波动的这些事件。风险管理是坚实投资策略的支柱。它包括多样化、止损指令、头寸规模、风险-回报评估和情绪纪律。

通过深入了解市场驱动因素并结合有效的风险管理技术,交易员可以应对金融市场的复杂性,并增加交易算法成功的可能性。

本章分为两个主要部分。第一部分从基本角度讨论市场驱动因素,第二部分从一般角度讨论风险管理。

市场驱动因素

理解市场驱动因素对于制定健康和稳健的交易系统至关重要。不了解是什么推动市场最终将导致整个过程的失败。到目前为止,您已经看到了使用同一时间序列的滞后值或技术指标的定量交易算法,但这只是看问题的一种方式。

在交易过程中,会发生许多事件,这些事件可能会影响头寸和承担的风险。了解这些事件可能有助于减少一些风险,但也可能在途中提供一些方向性机会。这一部分可以看作是关于基本分析和市场预期的入门。如果您对这个领域很熟悉,那么这应该很容易。

市场驱动因素和经济直觉

市场驱动因素影响并引导资产的运动和行为。这些驱动因素可以是金融、经济、地缘政治或甚至心理的。考虑以下四种资产类别:股票、固定收益、大宗商品和货币。

注意

作为提醒,固定收益是指一种投资类型,它以利息或股息的形式为投资者提供定期和可预测的支付,在一定期限内。之所以称为固定收益,是因为这些投资所产生的收入通常是固定的并在事先已知,与其他投资(如股票)产生的可变收入相对。

固定收益投资最常见的形式是债券,这些债券实质上是政府、市政当局、公司或其他实体发行的债务证券,以筹集资金。

当经济强劲时,由于投资者和市场参与者的偏好和行为不同,通常会对资产类别产生不同的影响。让我们详细看一下:

股票市场

强劲的经济条件通常会导致企业利润上升,因为企业的收入和销售额增加。这种收益增加可以推动股票价格上涨,因为投资者预期公司会有更好的回报。这还会导致消费者支出增加。这可能会使那些依赖消费者购买的公司(如零售和消费品公司)受益,它们可能会经历销售增加和盈利能力提升。此外,强劲的经济会给投资者带来信心,使他们更愿意承担风险并投资于股票市场(而不是固定收益市场)。积极的情绪可以吸引更多的买家,导致股票价格上涨(这也可能吸引投机交易者推高价格)。此外,央行可能会采取政策有效控制通胀,这对股票市场可能是有利的。适度的通胀率和低利率可以鼓励借贷、投资和业务扩展。

固定收益市场

随着经济的增强,央行可能会提高利率,以防止经济过热并控制通胀。新债券的高收益率使得现有收益较低的债券变得不那么吸引人。因此,尤其是对于长期债券,债券价格可能会下降。此外,当经济强劲且企业业绩良好时,投资者可能更倾向于将资金配置到股票市场上,因为那里可能会有更高的回报。同样的推理也适用于安全避风港资产(如政府债券)需求较低的情况,因为投资者对整体经济前景更为乐观。因此,这类债券的需求可能会减少,对价格构成下行压力。

大宗商品

强劲的经济通常会导致工业生产增加和消费者支出增加。因此,对大宗商品的需求,特别是像铜和钢这样的工业金属,由于它们在制造和建筑中的用途而上升。经济增长还导致能源消耗增加。因此,石油和天然气的需求通常会增加,推动它们的价格上涨。通胀预期上升是经济增长的结果,这可能会导致投资者转向贵金属(如黄金和白银)作为对抗货币贬值和市场不确定性的避险。最后,农产品商品(如小麦和玉米)也可能会因消费者在食品产品上的增加支出而出现上涨。

货币

强劲的经济通常会吸引外国投资并增加对本国货币的需求。国际投资者在强劲的经济中寻求更高的回报,从而增加了对以该货币计价的资产的需求。随着对本国货币的需求增加,其在外汇市场上相对于其他货币的价值也会升值。中央银行可能会提高利率以管理不断上涨的通货膨胀,并确保经济稳定。较高的利率吸引了寻求更好回报的外国投资者,推动对本国货币的需求增加并推高其价值。同样,强劲的经济通常会导致出口增加,因为本国的商品和服务在全球市场上变得更具竞争力。这可以改善国家的贸易*衡并有助于货币更加强劲。

注意

在现实生活中,这些关系更加复杂,因为其他因素如政治稳定性和天气事件也会影响到这些关系。看到这些相关性和市场逻辑的违反和结构性断裂并不罕见。它们可以用作经验法则,但决不是绝对可靠的条件。

经济直觉 的概念指的是选择预测因变量的特征的合理性。将公司盈利和国债收益率选择为独立变量,以预测股市回报的基本应用经济直觉。在基于外生和基本因素创建复杂的交易算法时,建议使用经济直觉。

一些专家认为技术指标可能不足以提供下一步预期的完整图景,但其他人指出基本面指标在短期内不能使用,有时长期数据也不足或不可得。

新闻解读

新闻解读 是分析和理解新闻事件、经济指标、地缘政治发展及其他相关信息如何影响金融市场的过程。在探索如何分析新闻之前,重温您对经济指标的了解至关重要:

国内生产总值(GDP)

这一指标代表了一个国家在特定时期内所有生产的商品和服务的总货币价值,通常以季度或年度为单位进行测量。 GDP 被用作衡量一个国家整体经济健康和表现的指标。

失业率

这一指标计算出正在失业并积极寻找工作的劳动力的百分比。这是评估就业市场健康状况和整体经济状况的关键指标。失业率通常由政府机构每月报告。较低的失业率通常被认为是积极的,因为它表明更健康的就业市场、更高的经济活动和更多的求职机会。相反,较高的失业率可能表明经济面临挑战,劳动力未被充分利用。政策制定者密切监测失业率,以评估其经济政策的有效性,并做出有关解决就业相关问题的知情决策。

通货膨胀率

简单来说,通货膨胀是一段时间内经济中货物和服务的总体价格水*上涨。当发生通货膨胀时,每单位货币购买的货物和服务比之前少。换句话说,通货膨胀侵蚀了货币的购买力。通常使用各种价格指数(如消费者物价指数(CPI))来衡量通货膨胀。通常是按年计算。你在第三章中看到了 CPI。

央行政策利率

也称为基准利率,它们代表国家央行设定的利率,以影响和控制国内的整体经济活动和通货膨胀。这些利率是货币政策的主要工具。最常见的央行政策利率是隔夜政策利率,即商业银行在隔夜基础上向央行借款或放款的利率。这一利率至关重要,因为它影响经济中的其他各种利率,包括贷款利率和储蓄账户的存款利率。央行利用政策利率的变动来实现特定的经济目标,如控制通货膨胀、刺激经济增长和维持价格稳定。

消费者信心指数

这些指数衡量消费者对当前和未来经济前景的乐观或悲观程度。这些指数旨在评估消费者情绪,这可以提供关于消费者支出行为和整体经济健康状况的见解。消费者信心指数通常基于对一定数量家庭的代表性样本进行的调查。

注意

另一个重要的经济指标是 ISM PMI,这是第九章中讨论的一个指标。

作为一名交易员,新闻解读涉及评估新闻事件的重要性,并确定其对资产价格的潜在影响。在消化新闻时,请考虑以下几点:

  • 不同的新闻项目可能对各种资产类别产生不同的影响。例如,积极的经济数据可能会提振股票,但导致黄金等避险资产下跌。了解新闻事件与资产之间的相关性和反向关系至关重要。

  • 新闻可以塑造市场情绪,导致看涨或看跌的趋势。交易者必须评估市场参与者对新闻的感知以及它可能影响其行为的方式。

  • 一些新闻对市场有直接影响,导致价格突然波动,而其他新闻可能会产生更渐进和持久的影响。交易者应意识到新闻的时间敏感性,以优化他们的交易策略。

  • 在某些情况下,市场已经将某些新闻事件的预期价格计入其中。交易者需要将实际新闻发布与市场预期进行比较,以确定该新闻是否令人惊讶。

注意

通过权威的财经新闻媒体(如金融时报和彭博社)保持信息更新,并进行彻底的研究,可以帮助交易者在动态和不断变化的市场环境中做出更明智的决策。

风险管理

找到一种预测性且有利可图的交易策略将为你赚钱,但一个健全的风险管理系统将使你能够保持资金。在传统金融领域,最基本的风险管理形式是分散化,即将资金分配到不同的不相关资产类别中,以分散资本贬值的风险。

另一个基本的风险管理示例就是简单地避免可能不时困扰零售交易社区的诈骗行为。

如今,有大量的资产管理者、信号提供者和复制交易算法承诺高额且稳定的回报。这些在线服务的丰富多样性引发了一个问题,即投资的质量是否值得。在评估交易服务时,有三个简单的规则要遵循:

  • 保证回报欺诈是两个可以互换的概念。避免陷入这个陷阱。

  • 即使是最微小的不明确也是一个重大警告信号。要求透明且可解释的结果,由第三方审计员支持。

  • 绝大多数业绩记录都是假的,结果主要基于错误的回测或选择偏见。你必须始终保持怀疑。

风险管理的另一个例子是头寸大小确定。头寸大小 指的是确定分配给特定交易或投资的适当资本量的过程。它涉及根据各种因素(如风险容忍度、账户规模和市场条件)计算要交易的股票、合约或手数的数量。适当的头寸大小对于风险管理至关重要,有助于交易者避免过度损失。智能交易算法在交易时具有这种功能,因此根据诸如波动性和预期回报等因素对头寸进行优化。

有几种技术和方法可用于头寸大小确定,例如以下几种:

固定货币金额

这种方法涉及将交易资本的固定货币金额分配给每笔交易。例如,您可以决定在每笔交易中风险账户的$250。因此,这种方法假定每笔交易的权重相等。

固定比例

使用这种技术,您会将交易资金的固定比例分配给每笔交易。例如,您可以决定在每笔交易中承担账户的 1%风险。随着账户规模的变化,头寸大小会相应调整。

凯利准则

凯利准则是一个数学公式,根据获胜和失败交易的概率计算出最佳头寸大小。它考虑了交易者的优势(成功概率)和回报比率。该公式有助于确定分配的资本百分比,以最大限度地实现长期增长,同时最小化破产的风险。

命中率方法

命中率方法是我使用的一种技术,它采用了一种关于连续获胜和连续失败的动量方法。它假设获胜必须以更大的头寸来奖励,而失败必须以较小的头寸来惩罚。命中率越高,头寸就越大,反之亦然。

风险管理基础知识

风险管理并不一定要复杂。您可以通过使用本节中提供的几种简单技术来创建一个稳健的风险管理系统。

止损和目标

止损单(止损)是在交易开始时设置的自动订单,以确保最小预定损失。例如,如果您以$1,500 的价格购买黄金合约,预期价格将达到$2,000,您可以将止损单设定为$1,250,这样如果价格下跌到$1,250,您的损失不会超过$250。同时,您在市场价格达到$2,000 时出售的预期被称为 止盈单目标)。

注意

任何交易的最基本风险管理系统都是设定适当的止损和目标,以便您能够设定期望并限制风险。

有一个规则是绝对不能违反的:始终设定止损和目标。算法可以轻松地让您自动化这个过程。

移动止损

表格 12-1. 经济日历

行为金融:偏见的力量

另一种确保至少获得一些利润的方法是将止损移动到$1.0100,从而确保无论如何都能获利。当然,你将止损移动到当前市场价格越接*时,被止损获利的可能性就越大。

注意

对于趋势跟随策略,使用追踪止损是个好主意,因为它们允许你顺势操作。

经济日历是一种风险规避的形式,它显示预计会对市场产生影响的重要新闻发布。表格 12-1 展示了某一天经济日历的假设示例。

追踪止损是一种动态的止损订单,随着市场价格朝着预期方向移动,从而确保更少的损失,并在某个时点确保没有损失的交易。理解追踪止损订单的最佳方法是通过一个例子。

一些交易者根据新闻进行交易,因此他们喜欢在新闻发布前或发布后数秒钟内交易,以从波动中获利。从风险管理的角度来看,这是不推荐的,因为新闻事件周围的波动是随机的,有时会令市场感到意外。

时间 国家 事件 影响 上一个数值 预期数值 实际数值
9:00 AM 英国 消费者价格指数 1.00% 1.20% 1.10%
11:30 AM 德国 核心消费者价格指数 0.50% 0.75% 0.75%
4:30 PM 美国 初次申请失业救济金人数 232,000 215,000 229,000
7:30 PM 美国 利率决定 1.50% 1.50% TBA

假设你在 EURUSD 上做多,进场价为$1.0000,目标价位为$1.0500。你设定了止损订单在$0.9900,以限制损失。现在假设过去两天,当前市场价格为$1.0230,你希望确保你的交易永远不会亏损。解决方法是使用追踪止损订单,将止损从$0.9900 移动到$1.0000,这样即使市场回落到$1.0000,你也能*仓并保持不赔不赚。

最好的做法是避免在历史上曾引发极端波动的重要发布时进行交易。例如政治公告、GDP 数据和 FOMC 会议。

注意

FOMC 代表美联储公开市场委员会。该委员会监督美国的公开市场国库操作并就利率做出决策。

行为金融,源自行为经济学的一个领域,试图解释市场异常和交易者的行为。通过深入理解行为金融,你将更好地理解市场在特定事件和水*周围为何如此反应。

金融市场由不同的人类和机器参与者的行动和反应组成,形成了心理和数量上的混合物。这解释了低信号与噪声比——换句话说,为什么在常规基础上准确预测市场是困难的。这些行动和反应也被称为偏见,它们是人类在应对某些事件时的缺陷。偏见是本节的主角,分为两类:

认知偏见

这些偏见源于缺乏知识。认知偏见通常涉及基于错误市场假设或糟糕研究而得出的错误结论。

情绪偏见

这些偏见大多是感情驱动的,具有冲动性质。它们不是由于教育缺乏而是由于自我控制和自我管理不足引起的。

认知偏见

本小节列出了一些最常见的认知偏见及其市场影响,并提供了如何避免它们的建议:

保守主义偏见

当市场参与者对新信息反应迟缓,并过于重视基本率时,就会发生这种情况。这是一种不适应的失败类型。应对这种偏见的方法是强迫自己对基础分析持怀疑态度,并始终保持灵活和准备好变化。由于市场是前瞻性的,它并不总是像过去那样表现。

确认偏见

当交易者专注于有利于其当前头寸的信息类型,并忽视对其头寸不利的信息类型时就会发生这种情况。这是迄今为止最常见的偏见之一,实际上是一种随着时间过去导致过度自信的正常心理状态。最好的矫正方法是保持中立和公正,尽管这说起来比做起来更容易。另一种方法是通过突出分析的基础资产的吸引力(或不吸引力)的记分卡自动化决策过程。总体而言,人类普遍受到这种偏见影响——这不仅仅是金融领域特有的。

控制幻觉偏见

当交易者高估其控制交易结果的能力时就会发生这种情况。这种偏见主要由连续赢利交易引起,可能会因为对投资资产控制权的感觉而导致集中持仓。市场由大量参与者组成,投资数万亿美元,因此不太可能受到任何单个人的影响(有少数关于小型和低流动性资产的例外情况)。去除控制幻觉的方法是保持专注和谦逊,理解你面对的是一个准随机环境,其动态和驱动因素每天都在变化。

后见之明偏见

当交易者高估他们过去的准确性时,可能会导致过度冒险。看过去的图表,很容易得出结论,随后的方向显而易见。大多数回测都包含一种后见之明的偏见,因为条件是在分析期结束时创建的。市场技术分析师在看到某些技术过去表现良好时会高估自己的能力,但未考虑到测试期间的环境。此外,某些配置在发生时与完成时看起来并不相同。后见之明偏见很难治愈,但最好的方法是考虑分析期间存在的变量,以更真实的方式模拟过去的环境。

锚定偏见

当交易者的观点被锚定到某一基准点并且未能改变以包含新信息时,就会发生这种情况。如我所提到的,分析师或交易者必须保持动态和开放的心态。治疗这种偏见的最佳方法是及时了解新信息和数据。

可得性偏见

当交易者根据易于回忆的位置选择头寸时,就会发生这种情况。这意味着熟悉的资产比非熟悉的资产更具吸引力,这是一个错误的假设,因为机会可能来自任何类型的市场。这是一种心理快捷方式,交易者在研究上不费多少心思。治疗这种偏见的方法是,在选择可投资资产组合之前必须进行充分的尽职调查。不要仅仅因为你熟悉 EURUSD 而交易它:拓宽你的视野。

损失规避偏见¹

当输掉的痛苦大于赢得的喜悦时,就会发生这种情况。这是目前最常见的偏见。人类更倾向于不想失去钱,而不是赚钱,正如丹尼尔·卡尼曼和阿莫斯·特沃斯基在《前景理论:风险决策分析》(1977 年)中所证明的。损失规避可能导致减少的风险承受能力,因此可能导致预期收益减少。然而,其最显著的影响在于止损水*。厌恶损失的人不愿意承认自己正在损失,并且会将亏损的头寸视为持续的头寸,因此更愿意等待直到盈利。这是非常危险的,因为放任无止损的头寸可能导致灾难性的结果。此外,有些人因为害怕遗憾而在赢利头寸上太快地*仓(一种后悔恐惧的表现)。处理损失规避偏见的最佳方法是自动化风险管理过程,并尊重在交易启动时设定的止损和目标订单。

情绪偏见

本小节列出了一些最常见的情绪偏见及其影响。作为提醒,认知偏见与知识缺乏相关,而情绪偏见则与心理特征相关:

过度自信的偏见

当交易员享受连胜时,并认为这是由于他们优越的市场交易能力所致,这导致他们持有集中的头寸并进行过度交易。一个好的连胜会有结束的时候,因此交易员必须始终遵循程序,确保他们不偏离策略。

遗憾规避偏差

这指的是出于恐惧而保持低风险投资。这确实与交易员的风险偏好有关。没有对与错的答案,但是对遗憾的恐惧可能使交易员错失有趣的机会。你应该冒险赚钱,但只能是经过计算的风险。

物权偏差

当交易员认为所拥有的资产比未拥有的资产更有价值时就会发生这种情况。这可能会限制交易员的机会,并导致他们只能持有已经拥有的资产,即使这些资产随时间衰减。市场处处充满机会,参与者必须时刻保持警惕,寻找下一个大机会。**** ****# 总结

本章讨论了有助于指导你在交易旅程中的各种交易话题。交易还必须包括主观意见,以创造客观意见。风险管理是交易和投资的关键方面,涉及识别、评估和减轻潜在风险,以保护资本并尽量减少损失。其主要目标是在追求长期稳定盈利的同时保全资本。

通过完成本章,你已经读完了整本书,并了解了如何开发旨在预测价格回报和其他时间序列的机器学习和深度学习算法。旅程并未止步于此;你必须继续尝试不同的技术,直到找到最适合你的风险-回报配置的策略为止。

你还应该意识到你通过完成这本书走了多远。写作是一种孤独的努力,但是是你与这些页面上的文字互动,使文字栩栩如生。你对材料的奉献和与我一同踏上这次冒险的意愿对我来说意义重大。

我希望这本书在某种程度上能够激励或启发你。它是出于爱、激情和深深的愿望来简化复杂概念而写的。你在这个叙事中的存在使其变得更加有意义。

¹ 这也被视为情绪偏差。****

posted @ 2024-06-17 18:15  绝不原创的飞龙  阅读(46)  评论(0编辑  收藏  举报