Python 金融编程第二版(四)(2)

简介: Python 金融编程第二版(四)

Python 金融编程第二版(四)(1)https://developer.aliyun.com/article/1559417


滚动统计

在金融传统中,通常称为滚动统计,也称为金融指标金融研究。这样的滚动统计是金融图表分析师和技术交易员的基本工具,例如。本节仅处理单个金融时间序列。

In [25]: sym = 'AAPL.O'
In [26]: data = pd.DataFrame(data[sym])
In [27]: data.tail()
Out[27]:             AAPL.O
         Date
         2017-10-25  156.41
         2017-10-26  157.41
         2017-10-27  163.05
         2017-10-30  166.72
         2017-10-31  169.04

概览

使用pandas很容易得出标准滚动统计。

In [28]: window = 20  # ①
In [29]: data['min'] = data[sym].rolling(window=window).min()  # ②
In [30]: data['mean'] = data[sym].rolling(window=window).mean()  # ③
In [31]: data['std'] = data[sym].rolling(window=window).std()  # ④
In [32]: data['median'] = data[sym].rolling(window=window).median()  # ⑤
In [33]: data['max'] = data[sym].rolling(window=window).max()  # ⑥
In [34]: data['ewma'] = data[sym].ewm(halflife=0.5, min_periods=window).mean() 

定义窗口,即要包含的索引值的数量。

计算滚动最小值。

计算滚动均值。

计算滚动标准差。

计算滚动中位数。

计算滚动最大值。

这将计算指数加权移动平均值,衰减以半衰期0.5来计算。

要推导出更专业的金融指标,通常需要使用其他软件包(例如,使用Cufflinks进行金融图表,参见“交互式二维绘图”)。也可以通过.apply()方法轻松地应用自定义指标。

下面的代码显示了部分结果,并可视化了计算的滚动统计的选择部分(参见图 8-5)。

In [35]: data.dropna().head()
Out[35]:                AAPL.O        min       mean       std     median        max  \
         Date
         2010-02-01  27.818544  27.437544  29.580892  0.933650  29.821542  30.719969
         2010-02-02  27.979972  27.437544  29.451249  0.968048  29.711113  30.719969
         2010-02-03  28.461400  27.437544  29.343035  0.950665  29.685970  30.719969
         2010-02-04  27.435687  27.435687  29.207892  1.021129  29.547113  30.719969
         2010-02-05  27.922829  27.435687  29.099892  1.037811  29.419256  30.719969
                          ewma
         Date
         2010-02-01  27.805432
         2010-02-02  27.936337
         2010-02-03  28.330134
         2010-02-04  27.659299
         2010-02-05  27.856947
In [36]: ax = data[['min', 'mean', 'max']].iloc[-200:].plot(
             figsize=(10, 6), style=['g--', 'r--', 'g--'], lw=0.8)  # ①
         data[sym].iloc[-200:].plot(ax=ax, lw=2.0);  # ②
         # plt.savefig('../../images/ch08/fts_05.png');

绘制最后 200 个数据行的三个滚动统计。

将原始时间序列数据添加到图表中。


图 8-5。最小、平均、最大值的滚动统计

技术分析示例

与基本分析相比,滚动统计是所谓的技术分析中的主要工具,基本分析侧重于财务报告和被分析股票公司的战略位置等方面。

基于技术分析的几十年历史的交易策略基于两个简单移动平均线(SMAs)。这个想法是,当短期 SMA 高于长期 SMA 时,交易员应该持有一支股票(或者一般的金融工具),当情况相反时,应该空仓。这些概念可以通过pandasDataFrame对象的功能来精确描述。

当给定了window参数规范并且有足够的数据时,通常才会计算滚动统计。如图 8-6 所示,SMAs 时间序列仅从有足够数据的那天开始。

In [37]: data['SMA1'] = data[sym].rolling(window=42).mean()  # ①
In [38]: data['SMA2'] = data[sym].rolling(window=252).mean()  # ②
In [39]: data[[sym, 'SMA1', 'SMA2']].tail()
Out[39]:             AAPL.O        SMA1        SMA2
         Date
         2017-10-25  156.41  157.610952  139.862520
         2017-10-26  157.41  157.514286  140.028472
         2017-10-27  163.05  157.517619  140.221210
         2017-10-30  166.72  157.597857  140.431528
         2017-10-31  169.04  157.717857  140.651766
In [40]: data[[sym, 'SMA1', 'SMA2']].plot(figsize=(10, 6));  # ③
         # plt.savefig('../../images/ch08/fts_06.png');

计算短期 SMA 的值。

计算长期 SMA 的值。

可视化股价数据和两条 SMAs 时间序列。


图 8-6. 苹果股价和两条简单移动平均线

在这个背景下,简单移动平均线(SMAs)仅仅是实现目标的手段。它们被用来推导出实施交易策略的定位。图 8-7 通过数值1来可视化多头头寸,数值-1来可视化空头头寸。头寸的变化(在视觉上)由表示 SMAs 时间序列的两条线的交叉触发。

In [41]: data.dropna(inplace=True)  # ①
In [42]: data['positions'] = np.where(data['SMA1'] > data['SMA2'],  # ②
                                      1,  # ③
                                      -1)  # ④
In [43]: ax = data[[sym, 'SMA1', 'SMA2', 'positions']].plot(figsize=(10, 6),
                                                       secondary_y='positions')
         ax.get_legend().set_bbox_to_anchor((0.25, 0.85));
         # plt.savefig('../../images/ch08/fts_07.png');

仅保留完整的数据行。

如果短期 SMA 值大于长期 SMA 值…

…买入股票(放置1)…

…否则卖空股票(放置-1)。


图 8-7. 苹果股价,两条简单移动平均线和头寸

在这里隐含地推导出的交易策略本质上只导致了很少的交易:只有当头寸价值变化(即发生交叉)时,才会进行交易。包括开仓和平仓交易,在总计中这将只增加到六次交易。

相关性分析

作为如何使用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 的对数收益随时间变化

在这种情况下,pandasscatter_matrix()绘图函数非常方便用于可视化。它将两个系列的对数收益相互绘制,可以在对角线上添加直方图或核密度估计器(KDE)(请参见图 8-11)。

In [53]: pd.plotting.scatter_matrix(rets,  # ①
                                    alpha=0.2,  # ②
                                    diagonal='hist',  # ③
                                    hist_kwds={'bins': 35},  # ④
                                    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)  # ①
In [55]: ax = rets.plot(kind='scatter', x='.SPX', y='.VIX', figsize=(10, 6))  # ②
         ax.plot(rets['.SPX'], np.polyval(reg, rets['.SPX']), 'r', lw=2);  # ③
         # plt.savefig('../../images/ch08/fts_12.png');

这实现了线性 OLS 回归。

这将对数收益绘制为散点图…

… 添加了线性回归线。


图 8-12。S&P 500 和 VIX 的对数收益作为散点矩阵

相关性

最后,直接考虑相关性度量。考虑到两种度量,一种是考虑完整数据集的静态度量,另一种是显示一定时间内相关性的滚动度量。图 8-13  说明了相关性确实随时间变化,但鉴于参数设置,始终为负。这确实强有力地支持了 S&P 500 和 VIX 指数之间甚至强烈的负相关的事实。

In [56]: rets.corr()  # ①
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))  # ②
         ax.axhline(rets.corr().iloc[0, 1], c='r');  # ③
         # 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)  # ①
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 及其科学堆栈库(如NumPypandasPyTables)的甜蜜点。这样大小的数据集也可以在内存中进行分析,利用今天的 CPU 和 GPU 通常会获得较高的速度。但是,必须将数据读入 RAM 并将结果写入磁盘,同时确保满足今天的性能要求。

本章涉及以下主题:

“使用 Python 进行基本 I/O”

Python 具有内置函数,可以对任何对象进行序列化并将其存储到磁盘上,然后从磁盘中读取到 RAM 中;除此之外,在处理文本文件和SQL数据库时,Python 也很强大。NumPy还提供了专用函数,用于快速二进制存储和检索ndarray对象。

“使用 pandas 进行 I/O”

pandas库提供了丰富的便利函数和方法,用于读取存储在不同格式中的数据(例如,CSVJSON)并将数据写入不同格式的文件。

“使用 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  # ①
        import numpy as np
        from random import gauss  # ②
In [3]: a = [gauss(1.5, 2) for i in range(1000000)]  # ③
In [4]: path = '/Users/yves/Documents/Temp/data/'  # ④
In [5]: pkl_file = open(path + 'data.pkl', 'wb')  # ⑤

从标准库导入 pickle 模块。

导入 gauss 以生成正态分布的随机数。

创建一个更大的 list 对象,并填充随机数。

指定存储数据文件的路径。

以二进制模式打开文件进行写入(wb)。

用于序列化和反序列化 Python 对象的两个主要函数是 pickle.dump()(用于写入对象)和 pickle.load()(用于将对象加载到内存中):

In [6]: %time pickle.dump(a, pkl_file)  # ①
        CPU times: user 23.4 ms, sys: 10.1 ms, total: 33.5 ms
        Wall time: 31.9 ms
In [7]: pkl_file.close()  # ②
In [8]: ll $path*  # ③
        -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')  # ④
In [10]: %time b = pickle.load(pkl_file)  # ⑤
         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))  # ⑥
Out[13]: True

序列化对象 a 并将其保存到文件中。

关闭文件。

显示磁盘上的文件及其大小(Mac/Linux)。

以二进制模式打开文件进行读取(rb)。

从磁盘读取对象并进行反序列化。

ab 转换为 ndarrary 对象,np.allclose() 验证两者包含相同的数据(数字)。

使用 pickle 存储和检索单个对象显然非常简单。那么两个对象呢?

In [14]: pkl_file = open(path + 'data.pkl', 'wb')
In [15]: %time pickle.dump(np.array(a), pkl_file)  # ①
         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)  # ②
         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*  # ③
         -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

序列化 andarray 版本并保存。

序列化 a 的平方 ndarray 版本并保存。

文件现在的大小大约是之前的两倍。

那么如何将两个ndarray对象读回内存?

In [19]: pkl_file = open(path + 'data.pkl', 'rb')
In [20]: x = pickle.load(pkl_file)  # ①
         x[:4]
Out[20]: array([ 3.08041661, -0.65863877,  3.32662484,  0.77225328])
In [21]: y = pickle.load(pkl_file)  # ②
         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)  # ①
         pkl_file.close()
In [24]: pkl_file = open(path + 'data.pkl', 'rb')
         data = pickle.load(pkl_file)  # ②
         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 金融编程第二版(四)(3)https://developer.aliyun.com/article/1559419

相关文章
|
2天前
|
存储 SQL 数据可视化
Python 金融编程第二版(四)(1)
Python 金融编程第二版(四)
9 0
|
2天前
|
数据可视化 Python
Python 金融编程第二版(三)(4)
Python 金融编程第二版(三)
12 2
|
2天前
|
存储 数据可视化 API
Python 金融编程第二版(三)(5)
Python 金融编程第二版(三)
8 1
|
2天前
|
存储 机器学习/深度学习 关系型数据库
Python 金融编程第二版(四)(5)
Python 金融编程第二版(四)
8 0
|
2天前
|
存储 SQL 数据库
Python 金融编程第二版(四)(4)
Python 金融编程第二版(四)
8 0
|
2天前
|
SQL 存储 数据库
Python 金融编程第二版(四)(3)
Python 金融编程第二版(四)
8 0
|
2月前
|
人工智能 安全 Java
Python 多线程编程实战:threading 模块的最佳实践
Python 多线程编程实战:threading 模块的最佳实践
175 5
|
2月前
|
安全 调度 Python
什么是Python中的事件驱动编程?如何使用`asyncio`模块实现异步事件处理?
【2月更文挑战第4天】【2月更文挑战第9篇】什么是Python中的事件驱动编程?如何使用`asyncio`模块实现异步事件处理?
|
2月前
|
缓存 分布式计算 自然语言处理
Python语言的函数编程模块
Python语言的函数编程模块
|
2月前
|
并行计算 程序员 API
Python多进程编程:利用multiprocessing模块实现并行计算
Python多进程编程:利用multiprocessing模块实现并行计算
433 0