Python 金融交易实用指南(一)(1)https://developer.aliyun.com/article/1523859
概要
在本章中,我们学习了什么时候算法交易比手动交易更具优势,金融资产类别是什么,最常用的订单类型是什么,限价订单簿是什么,以及订单是如何由金融交易所匹配的。
我们还讨论了算法交易系统的关键组成部分 - 核心基础设施和量化基础设施,包括交易策略、执行、限价订单簿、持仓、PnL 管理、回测、交易后分析和风险管理。
在下一章中,我们将讨论 Python 在算法交易中的价值。
第二部分:深入了解用于金融数据集分析的 Python 库
本节将深入介绍核心 Python 库 NumPy 和 pandas,这些库用于分析和操作大型数据框。我们还将涵盖与 pandas 密切相关的可视化库 Matplotlib。最后,我们将介绍 statsmodels 和 scikit-learn 库,这些库允许更高级的金融数据集分析。
本节包括以下章节:
- 第二章*, Python 中的探索性数据分析*
- 第三章*, 使用 NumPy 进行高速科学计算*
- 第四章*, 使用 Pandas 进行数据操作和分析*
- 第五章*, 使用 Matplotlib 进行数据可视化*
- 第六章*, 统计估计、推断和预测*
第二章:Python 中的探索性数据分析
本章重点介绍探索性数据分析(EDA),这是处理任何数据集的第一步。EDA 的目标是将数据加载到最适合进一步分析的数据结构中,以识别和纠正任何错误/坏数据,并获得对数据的基本见解——字段的类型有哪些;它们是否是分类的;有多少缺失值;字段之间的关系等等。
这些是本章讨论的主要话题:
- EDA 介绍
- 用于 EDA 的特殊 Python 库
技术要求
本章中使用的 Python 代码可以在书的代码库中的Chapter02/eda.ipynb笔记本中找到。
EDA 介绍
EDA 是从感兴趣的结构化/非结构化数据中获取、理解和得出有意义的统计见解的过程。这是在对数据进行更复杂的分析之前的第一步,例如从数据中预测未来的期望。在金融数据的情况下,EDA 有助于获得后续用于构建盈利交易信号和策略的见解。
EDA 指导后续决策,包括使用或避免哪些特征/信号,使用或避免哪些预测模型,并验证和引入关于变量性质和它们之间关系的正确假设,同时否定不正确的假设。
EDA 也很重要,可以理解样本(完整数据集的代表性较小数据集)统计数据与总体(完整数据集或终极真相)统计数据之间的差异,并在绘制关于总体的结论时记住这一点,基于样本观察。因此,EDA 有助于减少后续可能的搜索空间;否则,我们将浪费更多的时间后来构建不正确/不重要的模型或策略。
必须以科学的心态来对待 EDA。有时,我们可能会基于轶事证据而不是统计证据得出不充分验证的结论。
基于轶事证据的假设受到以下问题的影响:
- 不具有统计学意义——观测数量太少。
- 选择偏见——假设只是因为它首先被观察到而产生的。
- 确认偏见——我们对假设的内在信念会偏向于我们的结果。
- 观察中的不准确性。
让我们探索 EDA 涉及的不同步骤和技术,使用真实数据集。
EDA 的步骤
以下是 EDA 涉及的步骤列表(我们将在接下来的子章节中逐个进行讨论):
- 加载必要的库并进行设置
- 数据收集
- 数据整理/整理
- 数据清洗
- 获得描述性统计
- 数据的可视化检查
- 数据清洗
- 高级可视化技术
加载必要的库并进行设置
我们将使用numpy、pandas和matplotlib,这些库可以通过以下代码加载:
%matplotlib inline import numpy as np import pandas as pd from scipy import stats import seaborn as sn import matplotlib.pyplot as plt import mpld3 mpld3.enable_notebook() import warnings warnings.filterwarnings('ignore') pd.set_option('display.max_rows', 2)
我们使用mpld3库来启用 Jupyter 的matplotlib图表内的缩放。 前面代码块的最后一行指定了应显示pandas DataFrame 的最大行数为两行。
数据收集
数据收集通常是 EDA 的第一步。 数据可能来自许多不同的来源(逗号分隔值(CSV)文件、Excel 文件、网页抓取、二进制文件等),通常需要正确标准化和首先正确格式化在一起。
对于这个练习,我们将使用存储在.csv格式中的 5 年期间的三种不同交易工具的数据。 这些工具的身份故意没有透露,因为这可能泄露它们的预期行为/关系,但我们将在练习结束时透露它们的身份,以直观地评估我们对它们进行的 EDA 的表现如何。
让我们从加载我们可用的数据集开始,将其加载到三个 DataFrame(A,B和C)中,如下所示:
A = pd.read_csv('A.csv', parse_dates=True, index_col=0); A
DataFrame A的结构如下所示:
图 2.1 – 从 A.csv 文件构造的 DataFrame
类似地,让我们加载 DataFrame B,如下所示:
B = pd.read_csv('B.csv', parse_dates=True, index_col=0); B
DataFrame B的结构如下所示:
图 2.2 – 从 B.csv 文件构造的 DataFrame
最后,让我们将C数据加载到一个 DataFrame 中,如下所示:
C = pd.read_csv('C.csv', parse_dates=True, index_col=0); C
我们看到C有以下字段:
图 2.3 – 从 C.csv 文件构造的 DataFrame
如我们所见,所有三个数据源的格式都是2015-05-15和2020-05-14。
数据整理/处理
数据很少是以可直接使用的格式提供的。 数据整理/处理指的是从初始原始来源操纵和转换数据的过程,使其成为结构化的、格式化的和易于使用的数据集。
让我们使用pandas.DataFrame.join(...)来合并这些 DataFrame,并对齐它们以具有相同的DateTimeIndex格式。 使用lsuffix=和rsuffix=参数,我们将_A,_B和_C后缀分配给来自三个 DataFrame 的列,如下所示:
merged_df = A.join(B, how='outer', lsuffix='_A', sort=True).join(C, how='outer', lsuffix='_B', rsuffix='_C', sort=True) merged_df
我们将检查我们刚刚创建的merged_df DataFrame,并确保它具有我们从所有三个 DataFrame 中预期的所有字段(仅显示前七列)。 DataFrame 可以在这里看到:
图 2.4 – 通过合并 DataFrame A、B 和 C 构造的 DataFrame
请注意,原始三个数据框(A、B 和 C)分别有 1,211、1,209 和 1,206 行,但合并后的数据框有 1,259 行。这是因为我们使用了外部连接,它使用了所有三个数据框的日期的并集。当它在特定日期的特定数据框中找不到值时,它会将该数据框的字段的那个位置放置一个 NaN 值。
数据清洗
数据清洗是指处理来自缺失数据、不正确数据值和异常值的数据错误的过程。
在我们的示例中,merged_df 的许多字段都缺失原始数据集和不同日期数据框合并而来的字段。
让我们首先检查是否存在所有值都缺失(NaN)的行,如下所示:
merged_df[merged_df.isnull().all(axis=1)]
结果表明,我们没有任何所有字段都缺失的行,如我们所见:
图 2.5 – DataFrame 表明没有所有字段都缺失的行。
现在,让我们找出有多少行存在至少一个字段缺失/NaN 的,如下所示:
merged_df[['Close_A', 'Close_B', 'Close_C']].isnull().any(axis=1).sum()
因此,结果显示我们的 1,259 行中有 148 行具有一个或多个字段缺失值,如下所示:
148
对于我们的进一步分析,我们需要有效的 Close 价格。因此,我们可以通过运行以下代码删除所有三个工具的任何 Close 价格缺失的行:
valid_close_df = merged_df.dropna(subset=['Close_A', 'Close_B', 'Close_C'], how='any')
删除缺失的 Close 价格后,我们不应该再有缺失的 Close 价格字段,如下代码段所示:
valid_close_df[['Close_A', 'Close_B', 'Close_C']].isnull().any(axis=1).sum()
结果证实,不再存在任何 Close_A、Close_B 或 Close_C 字段为 NaN 值的行,如我们所见:
0
让我们检查新的 DataFrame,如下所示:
valid_close_df
这是结果(仅显示前七列):
图 2.6 – 没有任何收盘价缺失/NaN 值的结果 DataFrame
如预期的那样,我们删除了具有任何收盘价缺失/NaN 值的 148 行。
接下来,让我们处理任何其他字段具有 NaN 值的行,首先了解有多少这样的行。我们可以通过运行以下代码来做到这一点:
valid_close_df.isnull().any(axis=1).sum()
这是该查询的输出:
165
因此,存在 165 行至少有一些字段缺失值。
让我们快速检查一下至少有一些字段缺失值的几行,如下所示:
valid_close_df[valid_close_df.isnull().any(axis=1)]
以下显示了一些具有一些缺失值的行(仅显示前七列),如下所示:
图 2.7 – DataFrame 表明仍然有一些行存在一些缺失值
因此,我们可以看到 2015-05-18(在前述截屏中不可见)的 Low_C 字段和 2020-05-01 的 Open_B 字段有 NaN 值(当然还有其他 163 个)。
让我们使用 pandas.DataFrame.fillna(...) 方法与一种称为 backfill 的方法 —— 这使用缺失值后的下一个有效值来填充缺失值。代码如下所示:
valid_close_complete = valid_close_df.fillna(method='backfill')
让我们看看 backfilling 的影响,如下所示:
valid_close_complete.isnull().any(axis=1).sum()
现在,这是查询的输出:
0
正如我们所看到的,在进行 backfill 操作之后,任何行的任何字段都不再有缺失或 NaN 值。
获取描述性统计数据
下一步是生成数据的关键基本统计信息,以便熟悉每个字段,使用 DataFrame.describe(...) 方法。代码如下所示:
pd.set_option('display.max_rows', None) valid_close_complete.describe()
请注意,我们已经增加了要显示的 pandas DataFrame 的行数。
这是运行 pandas.DataFrame.describe(…) 后的输出,仅显示了前七列:
图 2.8 – 有效关闭完整 DataFrame 的描述统计
前面的输出为我们的 DataFrame 中的每个字段提供了快速摘要统计信息。
从 图 2.8 的关键观察点可以总结如下:
Volume_C的所有统计值都为0,这意味着每一行的Volume_C值都设置为0。因此,我们需要移除此列。Open_C的最小值为-400,这不太可能是真实的,原因如下:
a) 其他价格字段 ——High_C、Low_C、Close_C和Adj Close_C—— 的所有最小值都约为9,因此Open_C具有-400的最小值是没有意义的。
b) 考虑到Open_C的第 25 个百分位数为12.4,其最小值不太可能远低于此。
c) 资产的价格应为非负数。Low_C的最大值为330,这同样不太可能,原因如下:
a) 出于先前所述的相同原因,Open_C是不正确的。
b) 此外,考虑到Low_C应始终低于High_C,根据定义,一天中的最低价格必须低于当天的最高价格。
让我们将所有 pandas DataFrame 的输出恢复为只有两行,如下所示:
pd.set_option('display.max_rows', 2)
现在,让我们移除所有三个工具的 Volume 字段,使用以下代码:
prices_only = valid_close_complete.drop(['Volume_A', 'Volume_B', 'Volume_C'], axis=1) prices_only
而 prices_only DataFrame 具有以下数据(仅显示前七列):
图 2.9 – 仅价格的 DataFrame
预期之中的是,我们移除了三个工具的交易量列之后,将 DataFrame 维度减少到 1111 × 15 —— 这些以前是 1111 × 18。
数据的视觉检查
似乎没有任何明显的错误或不一致之处,所以让我们快速可视化价格,看看这是否符合我们从描述性统计中学到的内容。
首先,我们将从A的价格开始,因为根据描述性统计摘要,我们期望这些是正确的。代码如下所示:
valid_close_complete['Open_A'].plot(figsize=(12,6), linestyle='--', color='black', legend='Open_A') valid_close_complete['Close_A'].plot(figsize=(12,6), linestyle='-', color='grey', legend='Close_A') valid_close_complete['Low_A'].plot(figsize=(12,6), linestyle=':', color='black', legend='Low_A') valid_close_complete['High_A'].plot(figsize=(12,6), linestyle='-.', color='grey', legend='High_A')
输出与我们的期望一致,我们可以根据统计数据和下面截图中显示的图表得出A的价格是有效的结论:
图 2.10 – 展示了交易工具 A 的开盘价、收盘价、最高价和最低价在 5 年内的价格
现在,让我们绘制 C 的价格图,看看图表是否提供了关于我们怀疑某些价格不正确的进一步证据。代码如下所示:
valid_close_complete['Open_C'].plot(figsize=(12,6), linestyle='--', color='black', legend='Open_C') valid_close_complete['Close_C'].plot(figsize=(12,6), linestyle='-', color='grey', legend='Close_C') valid_close_complete['Low_C'].plot(figsize=(12,6), linestyle=':', color='black', legend='Low_C') valid_close_complete['High_C'].plot(figsize=(12,6), linestyle='-.', color='grey', legend='High_C')
输出证实了Open_C和Low_C具有一些与其他值极端相距甚远的错误值—这些是异常值。下面的截图显示了说明这一点的图表:
图 2.11 – 展示了 C 价格中正负两个方向的大异常值的图表
我们需要进行一些进一步的数据清理,以消除这些异常值,以便我们不从数据中得出不正确的统计见解。
检测和移除异常值最常用的两种方法是四分位数范围(IQR)和 Z 分数。
Python 金融交易实用指南(一)(3)https://developer.aliyun.com/article/1523861