Python 金融编程第二版(GPT 重译)(四)(1)https://developer.aliyun.com/article/1559357
相关性分析
作为如何使用pandas
和金融时间序列数据的进一步说明,考虑标准普尔 500 股指数和 VIX 波动率指数的情况。一个事实是,一般情况下,当标准普尔 500 上涨时,VIX 下降,反之亦然。这涉及相关性而不是因果关系。本节展示了如何为标准普尔 500 和 VIX 之间(高度)负相关的事实提供支持性统计证据。⁴
数据
数据集现在包含两个金融时间序列,都在图 8-8 中可视化。
In [44]: # EOD data from Thomson Reuters Eikon Data API raw = pd.read_csv('../../source/tr_eikon_eod_data.csv', index_col=0, parse_dates=True) In [45]: data = raw[['.SPX', '.VIX']] In [46]: data.tail() Out[46]: .SPX .VIX Date 2017-10-25 2557.15 11.23 2017-10-26 2560.40 11.30 2017-10-27 2581.07 9.80 2017-10-30 2572.83 10.50 2017-10-31 2575.26 10.18 In [47]: data.plot(subplots=True, figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_08.png');
图 8-8. S&P 500 和 VIX 时间序列数据(不同的缩放)
当在单个图中绘制(部分)两个时间序列并进行调整缩放时,两个指数之间的负相关的事实通过简单的视觉检查就已经变得明显。
In [48]: data.loc[:'2012-12-31'].plot(secondary_y='.VIX', figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_09.png');
图 8-9. S&P 500 和 VIX 时间序列数据(相同的缩放)
对数收益率
如上所述,一般的统计分析依赖于收益而不是绝对变化或甚至绝对值。因此,在进行任何进一步分析之前,首先计算对数收益。图 8-10 显示了随时间变化的对数收益的高变异性。对于两个指数都可以发现所谓的波动率集群。总的来说,股票指数波动率高的时期伴随着波动率指数的同样现象。
In [49]: rets = np.log(data / data.shift(1)) In [50]: rets.head() Out[50]: .SPX .VIX Date 2010-01-04 NaN NaN 2010-01-05 0.003111 -0.035038 2010-01-06 0.000545 -0.009868 2010-01-07 0.003993 -0.005233 2010-01-08 0.002878 -0.050024 In [51]: rets.dropna(inplace=True) In [52]: rets.plot(subplots=True, figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_10.png');
图 8-10。S&P 500 和 VIX 的对数收益随时间变化
在这种情况下,pandas
的scatter_matrix()
绘图函数非常方便用于可视化。它将两个系列的对数收益相互绘制,可以在对角线上添加直方图或核密度估计器(KDE)(请参见图 8-11)。
In [53]: pd.plotting.scatter_matrix(rets, ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) alpha=0.2, ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) diagonal='hist', ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) hist_kwds={'bins': 35}, ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png) figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_11.png');
要绘制的数据集。
点的不透明度参数为alpha
。
放置在对角线上的内容;这里是列数据的直方图。
这些是要传递给直方图绘图函数的关键字。
图 8-11。S&P 500 和 VIX 的对数收益作为散点矩阵
OLS 回归
通过所有这些准备,普通最小二乘(OLS)回归分析很方便实现。图 8-12 显示了对数收益的散点图和通过点云的线性回归线。斜率明显为负,支持了关于两个指数之间负相关的事实。
In [54]: reg = np.polyfit(rets['.SPX'], rets['.VIX'], deg=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) In [55]: ax = rets.plot(kind='scatter', x='.SPX', y='.VIX', figsize=(10, 6)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) ax.plot(rets['.SPX'], np.polyval(reg, rets['.SPX']), 'r', lw=2); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) # plt.savefig('../../images/ch08/fts_12.png');
这实现了线性 OLS 回归。
这将对数收益绘制为散点图…
… 添加了线性回归线。
图 8-12。S&P 500 和 VIX 的对数收益作为散点矩阵
相关性
最后,直接考虑相关性度量。考虑到两种度量,一种是考虑完整数据集的静态度量,另一种是显示一定时间内相关性的滚动度量。图 8-13 说明了相关性确实随时间变化,但鉴于参数设置,始终为负。这确实强有力地支持了 S&P 500 和 VIX 指数之间甚至强烈的负相关的事实。
In [56]: rets.corr() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) Out[56]: .SPX .VIX .SPX 1.000000 -0.808372 .VIX -0.808372 1.000000 In [57]: ax = rets['.SPX'].rolling(window=252).corr( rets['.VIX']).plot(figsize=(10, 6)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) ax.axhline(rets.corr().iloc[0, 1], c='r'); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) # plt.savefig('../../images/ch08/fts_13.png');
整个DataFrame
的相关矩阵。
这会绘制随时间变化的滚动相关性……
… 并将静态值添加到绘图中作为水平线。
图 8-13. 标普 500 和 VIX 之间的相关性(静态和滚动)
高频数据
本章介绍了使用pandas
进行金融时间序列分析。金融时间序列的一个特殊情况是 tick 数据集。坦率地说,它们可以更多或更少地以与本章迄今为止使用的 EOD 数据集相同的方式处理。一般来说,使用pandas
导入这些数据集也是相当快的。所使用的数据集包含 17,352 行数据(另见图 8-14)。
In [58]: %%time # data from FXCM Forex Capital Markets Ltd. tick = pd.read_csv('../../source/fxcm_eur_usd_tick_data.csv', index_col=0, parse_dates=True) CPU times: user 23 ms, sys: 3.35 ms, total: 26.4 ms Wall time: 25.1 ms In [59]: tick.info() <class 'pandas.core.frame.DataFrame'> DatetimeIndex: 17352 entries, 2017-11-10 12:00:00.007000 to 2017-11-10 14:00:00.131000 Data columns (total 2 columns): Bid 17352 non-null float64 Ask 17352 non-null float64 dtypes: float64(2) memory usage: 406.7 KB In [60]: tick['Mid'] = tick.mean(axis=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) In [61]: tick['Mid'].plot(figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_14.png');
计算每一行数据的Mid
价格。
图 8-14. 欧元/美元汇率的 tick 数据
处理 tick 数据通常需要对金融时间序列数据进行重新采样。以下代码将 tick 数据重新采样为一分钟的条形数据。例如,这样的数据集(另见图 8-15)用于回测算法交易策略或实施技术分析。
In [62]: tick_resam = tick.resample(rule='1min', label='right').last() In [63]: tick_resam.head() Out[63]: Bid Ask Mid 2017-11-10 12:01:00 1.16406 1.16407 1.164065 2017-11-10 12:02:00 1.16396 1.16397 1.163965 2017-11-10 12:03:00 1.16416 1.16418 1.164170 2017-11-10 12:04:00 1.16417 1.16417 1.164170 2017-11-10 12:05:00 1.16425 1.16427 1.164260 In [64]: tick_resam['Mid'].plot(figsize=(10, 6)); # plt.savefig('../../images/ch08/fts_15.png')
图 8-15. 欧元/美元汇率的一分钟条形数据
结论
本章涉及金融时间序列,可能是金融领域最重要的数据类型。pandas
是处理这种数据集的强大工具包,不仅可以进行高效的数据分析,而且还可以轻松进行可视化。pandas
还有助于从不同来源读取此类数据集,并将此类数据集导出为不同的技术文件格式。这在随后的第九章第九章中进行了说明。
进一步阅读
本章涵盖的主题在书籍形式上有很好的参考资料:
- McKinney, Wes (2017): Python for Data Analysis. 2nd ed., O’Reilly, 北京等地.
- VanderPlas, Jake (2016): Python Data Science Handbook. O’Reilly, 北京等地。
¹ 该文件包含了从汤姆森路透 Eikon 数据 API 检索的不同金融工具的每日结束数据(EOD 数据)。
² 其中一个优点是随时间的可加性,这对于简单的百分比变化/回报并不成立。
³ 预见偏见 ——或者,在其最强形式下,完美预见 ——意味着在金融分析的某个点上使用的数据仅在以后才可用。结果可能是“太好了
”的结果,例如,在回测交易策略时。
⁴ 其背后的一个推理是,当股指下跌时——例如在危机期间——交易量会上升,因此波动性也会上升。当股指上涨时,投资者通常会保持冷静,并且不怎么有动力进行大量交易。特别是,仅持有多头头寸的投资者会试图进一步跟进趋势。
第九章:输入/输出操作
在有数据之前进行理论推断是一个大错误。
福尔摩斯
作为一般规则,无论是在金融环境中还是在其他任何应用程序领域,大多数数据都存储在硬盘驱动器(HDDs)或其他形式的永久存储设备上,如固态硬盘(SSDs)或混合硬盘驱动器。多年来,存储容量一直在稳步增长,而存储单元的成本(例如,每兆字节)一直在稳步下降。
与此同时,存储数据的容量增长速度远远快于即使是最大型机器中可用的典型随机访问内存(RAM)。这使得不仅需要将数据存储到磁盘上以进行永久存储,而且需要通过将数据从 RAM 交换到磁盘,然后再交换回来来弥补 RAM 不足的情况。
因此,在金融应用程序和数据密集型应用程序中,输入/输出(I/O)操作通常是重要的任务。通常,它们代表了性能关键计算的瓶颈,因为 I/O 操作通常无法将数据快速地从 RAM 移动到 RAM¹,然后再从 RAM 移动到磁盘。从某种意义上说,CPU 经常由于 I/O 操作慢而“饥饿
”。
尽管如今的大部分金融和企业分析工作都面临着大数据(例如,PB 级别),但单个分析任务通常使用的数据子集属于中等数据类别。微软研究的一项研究得出了结论:
我们的测量以及其他最近的工作显示,大多数现实世界的分析任务处理的输入量不超过 100GB,但流行的基础设施,如 Hadoop/MapReduce 最初是为 PB 级处理而设计的。
Appuswamy 等人(2013 年)
就频率而言,单个金融分析任务通常处理不超过几 GB 大小的数据,并且这是 Python 及其科学堆栈库(如NumPy
,pandas
和PyTables
)的甜蜜点。这样大小的数据集也可以在内存中进行分析,利用今天的 CPU 和 GPU 通常会获得较高的速度。但是,必须将数据读入 RAM 并将结果写入磁盘,同时确保满足今天的性能要求。
本章涉及以下主题:
“使用 Python 进行基本 I/O”
Python 具有内置函数,可以对任何对象进行序列化并将其存储到磁盘上,然后从磁盘中读取到 RAM 中;除此之外,在处理文本文件和SQL
数据库时,Python 也很强大。NumPy
还提供了专用函数,用于快速二进制存储和检索ndarray
对象。
“使用 pandas 进行 I/O”
pandas
库提供了丰富的便利函数和方法,用于读取存储在不同格式中的数据(例如,CSV
,JSON
)并将数据写入不同格式的文件。
“使用 PyTables 进行快速 I/O”
PyTables
使用具有分层数据库结构和二进制存储的 HDF5
标准来实现对大数据集的快速 I/O 操作;速度通常仅受使用的硬件限制。
“TsTables 的 I/O”
TsTables
是一个构建在 PyTables
之上的包,允许快速存储和检索时间序列数据。
Python 的基本 I/O
Python 本身具有多种 I/O 功能,有些针对性能进行了优化,而其他一些则更注重灵活性。然而,总的来说,它们既可以在交互式环境下使用,也可以在生产环境中轻松应用。
将对象写入磁盘
以供以后使用、文档化或与他人共享,有人可能想要将 Python 对象存储在磁盘上。一个选项是使用 pickle
模块。该模块可以序列化大多数 Python 对象。序列化 指的是将对象(层次结构)转换为字节流;反序列化 是相反的操作。
通常情况下,首先进行一些与绘图相关的导入和自定义:
In [1]: from pylab import plt, mpl plt.style.use('seaborn') mpl.rcParams['font.family'] = 'serif' %matplotlib inline
接下来的示例使用(伪)随机数据,这次存储在 list
对象中:
In [2]: import pickle ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) import numpy as np from random import gauss ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) In [3]: a = [gauss(1.5, 2) for i in range(1000000)] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) In [4]: path = '/Users/yves/Documents/Temp/data/' ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png) In [5]: pkl_file = open(path + 'data.pkl', 'wb') ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
从标准库导入 pickle
模块。
导入 gauss
以生成正态分布的随机数。
创建一个更大的 list
对象,并填充随机数。
指定存储数据文件的路径。
以二进制模式打开文件进行写入(wb
)。
用于序列化和反序列化 Python 对象的两个主要函数是 pickle.dump()
(用于写入对象)和 pickle.load()
(用于将对象加载到内存中):
In [6]: %time pickle.dump(a, pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) CPU times: user 23.4 ms, sys: 10.1 ms, total: 33.5 ms Wall time: 31.9 ms In [7]: pkl_file.close() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) In [8]: ll $path* ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) -rw-r--r-- 1 yves staff 9002006 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.pkl -rw-r--r-- 1 yves staff 163328824 Jan 18 10:05 /Users/yves/Documents/Temp/data/tstb.h5 In [9]: pkl_file = open(path + 'data.pkl', 'rb') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png) In [10]: %time b = pickle.load(pkl_file) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png) CPU times: user 28.7 ms, sys: 15.2 ms, total: 43.9 ms Wall time: 41.9 ms In [11]: a[:3] Out[11]: [3.0804166128701134, -0.6586387748854099, 3.3266248354210206] In [12]: b[:3] Out[12]: [3.0804166128701134, -0.6586387748854099, 3.3266248354210206] In [13]: np.allclose(np.array(a), np.array(b)) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png) Out[13]: True
序列化对象 a
并将其保存到文件中。
关闭文件。
显示磁盘上的文件及其大小(Mac/Linux)。
以二进制模式打开文件进行读取(rb
)。
从磁盘读取对象并进行反序列化。
将 a
和 b
转换为 ndarrary
对象,np.allclose()
验证两者包含相同的数据(数字)。
使用 pickle
存储和检索单个对象显然非常简单。那么两个对象呢?
In [14]: pkl_file = open(path + 'data.pkl', 'wb') In [15]: %time pickle.dump(np.array(a), pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) CPU times: user 26.6 ms, sys: 11.5 ms, total: 38.1 ms Wall time: 36.3 ms In [16]: %time pickle.dump(np.array(a) ** 2, pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) CPU times: user 35.3 ms, sys: 12.7 ms, total: 48 ms Wall time: 46.8 ms In [17]: pkl_file.close() In [18]: ll $path* ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png) -rw-r--r-- 1 yves staff 16000322 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.pkl -rw-r--r-- 1 yves staff 163328824 Jan 18 10:05 /Users/yves/Documents/Temp/data/tstb.h5
序列化 a
的 ndarray
版本并保存。
序列化 a
的平方 ndarray
版本并保存。
文件现在的大小大约是之前的两倍。
那么如何将两个ndarray
对象读回内存?
In [19]: pkl_file = open(path + 'data.pkl', 'rb') In [20]: x = pickle.load(pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) x[:4] Out[20]: array([ 3.08041661, -0.65863877, 3.32662484, 0.77225328]) In [21]: y = pickle.load(pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) y[:4] Out[21]: array([ 9.48896651, 0.43380504, 11.0664328 , 0.59637513]) In [22]: pkl_file.close(
这检索到了存储的对象第一个。
这检索到了存储的对象第二个。
很明显,pickle
根据先进先出(FIFO)原则存储对象。但这存在一个主要问题:用户事先无法获得关于存储在pickle
文件中的内容的元信息。
有时一个有用的解决方法是不存储单个对象,而是存储包含所有其他对象的dict
对象:
In [23]: pkl_file = open(path + 'data.pkl', 'wb') pickle.dump({'x': x, 'y': y}, pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png) pkl_file.close() In [24]: pkl_file = open(path + 'data.pkl', 'rb') data = pickle.load(pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png) pkl_file.close() for key in data.keys(): print(key, data[key][:4]) x [ 3.08041661 -0.65863877 3.32662484 0.77225328] y [ 9.48896651 0.43380504 11.0664328 0.59637513] In [25]: !rm -f $path*
存储包含两个ndarray
对象的dict
对象。
检索dict
对象。
然而,这种方法要求一次性写入和读取所有对象。考虑到它带来的更高便利性,这可能是一个可以接受的折中方案。
Python 金融编程第二版(GPT 重译)(四)(3)https://developer.aliyun.com/article/1559367