首页 > 编程语言 >Python-金融编程第二版-GPT-重译--四-

Python-金融编程第二版-GPT-重译--四-

时间:2024-06-09 20:23:20浏览次数:13  
标签:raw Python docs -- 重译 https master com png

Python 金融编程第二版(GPT 重译)(四)

原文:annas-archive.org/md5/d2f94efd019a2e2cb5c4fa9f260d63c

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:金融时间序列

时间的唯一目的是让一切不是同时发生。

阿尔伯特·爱因斯坦

金融时间序列数据是金融领域最重要的数据类型之一。这是按日期和/或时间索引的数据。例如,随着时间的推移,股票价格代表金融时间序列数据。类似地,随时间变化的欧元/美元汇率代表金融时间序列;汇率在较短的时间间隔内报价,一系列这样的报价则是汇率的时间序列。

没有任何金融学科能够不考虑时间因素而存在。这与物理学和其他科学基本相同。在 Python 中处理时间序列数据的主要工具是pandaspandas的原始和主要作者 Wes McKinney 在 AQR Capital Management,一家大型对冲基金的分析员时开始开发这个库。可以肯定地说pandas是从头开始设计用于处理金融时间序列数据的。

本章主要基于两个以逗号分隔值(CSV)文件形式的金融时间序列数据集。它沿以下线路进行:

“金融数据”

这一部分介绍了使用pandas处理金融时间序列数据的基础知识:数据导入,导出摘要统计信息,计算随时间变化的数据和重采样。

“滚动统计”

在金融分析中,滚动统计起着重要作用。这些统计数据通常在一个固定的时间间隔内进行计算,并在整个数据集上“滚动前进”。简单移动平均线(SMAs)是一个流行的例子。这一部分说明了pandas如何支持计算这种统计数据。

“相关性分析”

这一部分提供了一个基于标普 500 股票指数和 VIX 波动率指数的金融时间序列数据的案例研究。它为两个指数都呈现负相关的模式提供了一些支持。

“高频数据”

高频数据或者说 tick 数据在金融领域已经变得司空见惯。这一部分处理 tick 数据。pandas再次在处理这样的数据集时表现强大。

金融数据

这一部分处理的是一个以 CSV 文件形式存储的本地金融数据集。从技术上讲,这些文件只是由逗号分隔单个值的数据行结构的文本文件。在导入数据之前,首先进行一些软件包导入和自定义。

In [1]: import numpy as np
        import pandas as pd
        from pylab import mpl, plt
        plt.style.use('seaborn')
        mpl.rcParams['font.family'] = 'serif'
        %matplotlib inline

数据导入

pandas提供了许多不同的函数和DataFrame方法来导入以不同格式存储的数据(CSV、SQL、Excel 等)以及将数据导出为不同的格式(详见第九章)。下面的代码使用pd.read_csv()函数从 CSV 文件中导入时间序列数据集。¹

In [2]: filename = '../../source/tr_eikon_eod_data.csv'  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [3]: !head -5 $filename  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

        Date,AAPL.O,MSFT.O,INTC.O,AMZN.O,GS.N,SPY,.SPX,.VIX,EUR=,XAU=,GDX,GLD
        2010-01-04,30.57282657,30.95,20.88,133.9,173.08,113.33,1132.99,20.04,1.4411,1120.0,47.71,109.8
        2010-01-05,30.625683660000004,30.96,20.87,134.69,176.14,113.63,1136.52,19.35,1.4368,1118.65,48.17,109.7
        2010-01-06,30.138541290000003,30.77,20.8,132.25,174.26,113.71,1137.14,19.16,1.4412,1138.5,49.34,111.51
        2010-01-07,30.082827060000003,30.452,20.6,130.0,177.67,114.19,1141.69,19.06,1.4318,1131.9,49.1,110.82

In [4]: data = pd.read_csv(filename,  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
                           index_col=0, ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
                           parse_dates=True)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [5]: data.info()  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

        <class 'pandas.core.frame.DataFrame'>
        DatetimeIndex: 1972 entries, 2010-01-04 to 2017-10-31
        Data columns (total 12 columns):
        AAPL.O    1972 non-null float64
        MSFT.O    1972 non-null float64
        INTC.O    1972 non-null float64
        AMZN.O    1972 non-null float64
        GS.N      1972 non-null float64
        SPY       1972 non-null float64
        .SPX      1972 non-null float64
        .VIX      1972 non-null float64
        EUR=      1972 non-null float64
        XAU=      1972 non-null float64
        GDX       1972 non-null float64
        GLD       1972 non-null float64
        dtypes: float64(12)
        memory usage: 200.3 KB

1

指定路径和文件名。

2

显示原始数据的前五行(Linux/Mac)。

3

传递给pd.read_csv()函数的文件名。

4

这指定第一列将被处理为索引。

5

这另外指定索引值的类型为日期时间。

6

结果的DataFrame对象。

在这个阶段,金融分析师可能会通过检查数据或将其可视化(参见图 8-1)来首次查看数据。

In [6]: data.head()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[6]:                AAPL.O  MSFT.O  INTC.O  AMZN.O    GS.N     SPY     .SPX   .VIX  \
        Date
        2010-01-04  30.572827  30.950   20.88  133.90  173.08  113.33  1132.99  20.04
        2010-01-05  30.625684  30.960   20.87  134.69  176.14  113.63  1136.52  19.35
        2010-01-06  30.138541  30.770   20.80  132.25  174.26  113.71  1137.14  19.16
        2010-01-07  30.082827  30.452   20.60  130.00  177.67  114.19  1141.69  19.06
        2010-01-08  30.282827  30.660   20.83  133.52  174.31  114.57  1144.98  18.13

                      EUR=     XAU=    GDX     GLD
        Date
        2010-01-04  1.4411  1120.00  47.71  109.80
        2010-01-05  1.4368  1118.65  48.17  109.70
        2010-01-06  1.4412  1138.50  49.34  111.51
        2010-01-07  1.4318  1131.90  49.10  110.82
        2010-01-08  1.4412  1136.10  49.84  111.37

In [7]: data.tail()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[7]:             AAPL.O  MSFT.O  INTC.O   AMZN.O    GS.N     SPY     .SPX   .VIX  \
        Date
        2017-10-25  156.41   78.63   40.78   972.91  241.71  255.29  2557.15  11.23
        2017-10-26  157.41   78.76   41.35   972.43  241.72  255.62  2560.40  11.30
        2017-10-27  163.05   83.81   44.40  1100.95  241.71  257.71  2581.07   9.80
        2017-10-30  166.72   83.89   44.37  1110.85  240.89  256.75  2572.83  10.50
        2017-10-31  169.04   83.18   45.49  1105.28  242.48  257.15  2575.26  10.18

                      EUR=     XAU=    GDX     GLD
        Date
        2017-10-25  1.1812  1277.01  22.83  121.35
        2017-10-26  1.1650  1266.73  22.43  120.33
        2017-10-27  1.1608  1272.60  22.57  120.90
        2017-10-30  1.1649  1275.86  22.76  121.13
        2017-10-31  1.1644  1271.20  22.48  120.67

In [8]: data.plot(figsize=(10, 12), subplots=True)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
        # plt.savefig('../../images/ch08/fts_01.png');

1

前五行 …

2

… 最后五行显示。

3

这通过多个子图可视化完整数据集。

fts 01

图 8-1. 金融时间序列数据的线图

使用的数据来自汤森路透(TR)Eikon 数据 API。在 TR 世界中,金融工具的符号称为“路透社仪器代码”或RICs。单个RICs代表的金融工具是:

In [9]: instruments = ['Apple Stock', 'Microsoft Stock',
                       'Intel Stock', 'Amazon Stock', 'Goldman Sachs Stock',
                       'SPDR S&P 500 ETF Trust', 'S&P 500 Index',
                       'VIX Volatility Index', 'EUR/USD Exchange Rate',
                       'Gold Price', 'VanEck Vectors Gold Miners ETF',
                       'SPDR Gold Trust']

In [10]: for pari in zip(data.columns, instruments):
             print('{:8s} | {}'.format(pari[0], pari[1]))

         AAPL.O   | Apple Stock
         MSFT.O   | Microsoft Stock
         INTC.O   | Intel Stock
         AMZN.O   | Amazon Stock
         GS.N     | Goldman Sachs Stock
         SPY      | SPDR S&P 500 ETF Trust
         .SPX     | S&P 500 Index
         .VIX     | VIX Volatility Index
         EUR=     | EUR/USD Exchange Rate
         XAU=     | Gold Price
         GDX      | VanEck Vectors Gold Miners ETF
         GLD      | SPDR Gold Trust

摘要统计

下一步,金融分析师可能会采取的步骤是查看数据集的不同摘要统计信息,以了解它的“感觉”。

In [11]: data.info()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

         <class 'pandas.core.frame.DataFrame'>
         DatetimeIndex: 1972 entries, 2010-01-04 to 2017-10-31
         Data columns (total 12 columns):
         AAPL.O    1972 non-null float64
         MSFT.O    1972 non-null float64
         INTC.O    1972 non-null float64
         AMZN.O    1972 non-null float64
         GS.N      1972 non-null float64
         SPY       1972 non-null float64
         .SPX      1972 non-null float64
         .VIX      1972 non-null float64
         EUR=      1972 non-null float64
         XAU=      1972 non-null float64
         GDX       1972 non-null float64
         GLD       1972 non-null float64
         dtypes: float64(12)
         memory usage: 200.3 KB

In [12]: data.describe().round(2)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[12]:         AAPL.O   MSFT.O   INTC.O   AMZN.O     GS.N      SPY     .SPX     .VIX  \
         count  1972.00  1972.00  1972.00  1972.00  1972.00  1972.00  1972.00  1972.00
         mean     86.53    40.59    27.70   401.15   163.61   172.84  1727.54    17.21
         std      34.04    14.39     5.95   257.12    37.17    42.33   424.35     5.92
         min      27.44    23.01    17.66   108.61    87.70   102.20  1022.58     9.19
         25%      57.57    28.12    22.23   202.66   144.23   132.64  1325.53    13.25
         50%      84.63    36.54    26.41   306.42   162.09   178.80  1783.81    15.65
         75%     111.87    50.08    33.74   559.45   184.11   208.01  2080.15    19.20
         max     169.04    83.89    45.49  1110.85   252.89   257.71  2581.07    48.00

                   EUR=     XAU=      GDX      GLD
         count  1972.00  1972.00  1972.00  1972.00
         mean      1.25  1352.47    34.50   130.60
         std       0.12   195.38    15.44    19.46
         min       1.04  1051.36    12.47   100.50
         25%       1.13  1214.56    22.22   116.77
         50%       1.29  1288.82    26.59   123.90
         75%       1.35  1491.98    49.77   145.43
         max       1.48  1897.10    66.63   184.59

1

.info()提供有关DataFrame对象的一些元信息。

2

.describe() 提供每列有用的标准统计数据。

提示

pandas提供了许多方法来快速查看新导入的金融时间序列数据集的概述,例如.info().describe()。它们还允许快速检查导入过程是否按预期工作(例如,DataFrame对象是否确实具有DatetimeIndex作为索引)。

当然,也有选项来自定义要推导和显示的统计信息类型。

In [13]: data.mean()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[13]: AAPL.O      86.530152
         MSFT.O      40.586752
         INTC.O      27.701411
         AMZN.O     401.154006
         GS.N       163.614625
         SPY        172.835399
         .SPX      1727.538342
         .VIX        17.209498
         EUR=         1.252613
         XAU=      1352.471593
         GDX         34.499391
         GLD        130.601856
         dtype: float64

In [14]: data.aggregate(min,  ![2                         np.mean,  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
                         np.std,  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
                         np.median,  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
                         max]  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
         ).round(2)
Out[14]:         AAPL.O  MSFT.O  INTC.O   AMZN.O    GS.N     SPY     .SPX   .VIX  EUR=  \
         min      27.44   23.01   17.66   108.61   87.70  102.20  1022.58   9.19  1.04
         mean     86.53   40.59   27.70   401.15  163.61  172.84  1727.54  17.21  1.25
         std      34.04   14.39    5.95   257.12   37.17   42.33   424.35   5.92  0.12
         median   84.63   36.54   26.41   306.42  162.09  178.80  1783.81  15.65  1.29
         max     169.04   83.89   45.49  1110.85  252.89  257.71  2581.07  48.00  1.48

                    XAU=    GDX     GLD
         min     1051.36  12.47  100.50
         mean    1352.47  34.50  130.60
         std      195.38  15.44   19.46
         median  1288.82  26.59  123.90
         max     1897.10  66.63  184.59

1

每列的均值。

2

每列的最小值。

3

每列的均值。

4

每列的标准偏差。

5

每列的最大值。

6

使用.aggregate()方法还允许传递自定义函数。

随时间变化

大多数统计分析方法,例如,通常基于时间序列随时间的变化,而不是绝对值本身。有多种选项可以计算时间序列随时间的变化,包括:绝对差异、百分比变化和对数(对数)收益。

首先是绝对差异,对于这个pandas提供了一个特殊的方法。

In [15]: data.diff().head()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[15]:               AAPL.O  MSFT.O  INTC.O  AMZN.O  GS.N   SPY  .SPX  .VIX    EUR=  \
         Date
         2010-01-04       NaN     NaN     NaN     NaN   NaN   NaN   NaN   NaN     NaN
         2010-01-05  0.052857   0.010   -0.01    0.79  3.06  0.30  3.53 -0.69 -0.0043
         2010-01-06 -0.487142  -0.190   -0.07   -2.44 -1.88  0.08  0.62 -0.19  0.0044
         2010-01-07 -0.055714  -0.318   -0.20   -2.25  3.41  0.48  4.55 -0.10 -0.0094
         2010-01-08  0.200000   0.208    0.23    3.52 -3.36  0.38  3.29 -0.93  0.0094

                      XAU=   GDX   GLD
         Date
         2010-01-04    NaN   NaN   NaN
         2010-01-05  -1.35  0.46 -0.10
         2010-01-06  19.85  1.17  1.81
         2010-01-07  -6.60 -0.24 -0.69
         2010-01-08   4.20  0.74  0.55

In [16]: data.diff().mean()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[16]: AAPL.O    0.070252
         MSFT.O    0.026499
         INTC.O    0.012486
         AMZN.O    0.492836
         GS.N      0.035211
         SPY       0.072968
         .SPX      0.731745
         .VIX     -0.005003
         EUR=     -0.000140
         XAU=      0.076712
         GDX      -0.012801
         GLD       0.005515
         dtype: float64

1

.diff()提供了两个索引值之间的绝对变化。

2

当然,还可以应用聚合操作。

从统计学的角度来看,绝对变化不是最佳选择,因为它们依赖于时间序列数据本身的比例。因此,通常更喜欢百分比变化。以下代码推导了金融背景下的百分比变化或百分比收益(也称为:简单收益),并可视化其每列的均值(参见 图 8-2)。

In [17]: data.pct_change().round(3).head()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[17]:             AAPL.O  MSFT.O  INTC.O  AMZN.O   GS.N    SPY   .SPX   .VIX   EUR=  \
         Date
         2010-01-04     NaN     NaN     NaN     NaN    NaN    NaN    NaN    NaN    NaN
         2010-01-05   0.002   0.000  -0.000   0.006  0.018  0.003  0.003 -0.034 -0.003
         2010-01-06  -0.016  -0.006  -0.003  -0.018 -0.011  0.001  0.001 -0.010  0.003
         2010-01-07  -0.002  -0.010  -0.010  -0.017  0.020  0.004  0.004 -0.005 -0.007
         2010-01-08   0.007   0.007   0.011   0.027 -0.019  0.003  0.003 -0.049  0.007

                      XAU=    GDX    GLD
         Date
         2010-01-04    NaN    NaN    NaN
         2010-01-05 -0.001  0.010 -0.001
         2010-01-06  0.018  0.024  0.016
         2010-01-07 -0.006 -0.005 -0.006
         2010-01-08  0.004  0.015  0.005

In [18]: data.pct_change().mean().plot(kind='bar', figsize=(10, 6));  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
         # plt.savefig('../../images/ch08/fts_02.png');

1

.pct_change()计算两个索引值之间的百分比变化。

2

结果的均值作为条形图可视化。

fts 02

图 8-2. 百分比变化的均值作为条形图

作为百分比收益的替代,可以使用对数收益。在某些情况下,它们更容易处理,因此在金融背景下通常更受欢迎。² 图 8-3 展示了单个金融时间序列的累积对数收益。这种类型的绘图导致某种形式的归一化

In [19]: rets = np.log(data / data.shift(1))  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [20]: rets.head().round(3)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[20]:             AAPL.O  MSFT.O  INTC.O  AMZN.O   GS.N    SPY   .SPX   .VIX   EUR=  \
         Date
         2010-01-04     NaN     NaN     NaN     NaN    NaN    NaN    NaN    NaN    NaN
         2010-01-05   0.002   0.000  -0.000   0.006  0.018  0.003  0.003 -0.035 -0.003
         2010-01-06  -0.016  -0.006  -0.003  -0.018 -0.011  0.001  0.001 -0.010  0.003
         2010-01-07  -0.002  -0.010  -0.010  -0.017  0.019  0.004  0.004 -0.005 -0.007
         2010-01-08   0.007   0.007   0.011   0.027 -0.019  0.003  0.003 -0.050  0.007

                      XAU=    GDX    GLD
         Date
         2010-01-04    NaN    NaN    NaN
         2010-01-05 -0.001  0.010 -0.001
         2010-01-06  0.018  0.024  0.016
         2010-01-07 -0.006 -0.005 -0.006
         2010-01-08  0.004  0.015  0.005

In [21]: rets.cumsum().apply(np.exp).plot(figsize=(10, 6));  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
         # plt.savefig('../../images/ch08/fts_03.png');

1

这以向量化方式计算对数收益。

2

结果的子集。

3

这绘制了随时间累积的对数收益;首先调用.cumsum()方法,然后将np.exp()应用于结果。

fts 03

图 8-3. 随时间累积的对数收益

重采样

对于金融时间序列数据,重采样是一项重要的操作。通常,这采取上采样的形式,意味着例如,具有每日观测的时间序列被重采样为具有每周或每月观测的时间序列。这也可能意味着将金融 Tick 数据系列重采样为一分钟间隔(也称为:柱)。

In [22]: data.resample('1w', label='right').last().head()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[22]:                AAPL.O  MSFT.O  INTC.O  AMZN.O    GS.N     SPY     .SPX   .VIX  \
         Date
         2010-01-10  30.282827   30.66   20.83  133.52  174.31  114.57  1144.98  18.13
         2010-01-17  29.418542   30.86   20.80  127.14  165.21  113.64  1136.03  17.91
         2010-01-24  28.249972   28.96   19.91  121.43  154.12  109.21  1091.76  27.31
         2010-01-31  27.437544   28.18   19.40  125.41  148.72  107.39  1073.87  24.62
         2010-02-07  27.922829   28.02   19.47  117.39  154.16  106.66  1066.19  26.11

                       EUR=     XAU=    GDX     GLD
         Date
         2010-01-10  1.4412  1136.10  49.84  111.37
         2010-01-17  1.4382  1129.90  47.42  110.86
         2010-01-24  1.4137  1092.60  43.79  107.17
         2010-01-31  1.3862  1081.05  40.72  105.96
         2010-02-07  1.3662  1064.95  42.41  104.68

In [23]: data.resample('1m', label='right').last().head()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[23]:                AAPL.O   MSFT.O  INTC.O  AMZN.O    GS.N       SPY     .SPX  \
         Date
         2010-01-31  27.437544  28.1800   19.40  125.41  148.72  107.3900  1073.87
         2010-02-28  29.231399  28.6700   20.53  118.40  156.35  110.7400  1104.49
         2010-03-31  33.571395  29.2875   22.29  135.77  170.63  117.0000  1169.43
         2010-04-30  37.298534  30.5350   22.84  137.10  145.20  118.8125  1186.69
         2010-05-31  36.697106  25.8000   21.42  125.46  144.26  109.3690  1089.41

                      .VIX    EUR=     XAU=    GDX      GLD
         Date
         2010-01-31  24.62  1.3862  1081.05  40.72  105.960
         2010-02-28  19.50  1.3625  1116.10  43.89  109.430
         2010-03-31  17.59  1.3510  1112.80  44.41  108.950
         2010-04-30  22.05  1.3295  1178.25  50.51  115.360
         2010-05-31  32.07  1.2267  1213.81  49.86  118.881

In [24]: rets.cumsum().resample('1m', label='right').last(
                                   ).plot(figsize=(10, 6));  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
         # plt.savefig('../../images/ch08/fts_04.png');

1

EOD 数据被重采样为

2

结果的子集。

3

这绘制了随时间累积的对数收益;首先调用.cumsum()方法,然后将np.exp()应用于结果。

fts 04

图 8-4. 随时间重采样的累积对数收益(每月)
注意

在重新取样时,默认情况下,pandas采用区间的左标签(或索引值)。为了保持金融一致性,请确保使用右标签(索引值)和通常是区间中最后一个可用数据点。否则,可能会在金融分析中引入预见偏差。^(3)

滚动统计

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

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  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [29]: data['min'] = data[sym].rolling(window=window).min()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [30]: data['mean'] = data[sym].rolling(window=window).mean()  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [31]: data['std'] = data[sym].rolling(window=window).std()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [32]: data['median'] = data[sym].rolling(window=window).median()  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [33]: data['max'] = data[sym].rolling(window=window).max()  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

In [34]: data['ewma'] = data[sym].ewm(halflife=0.5, min_periods=window).mean()  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)

1

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

2

计算滚动最小值。

3

计算滚动均值。

4

计算滚动标准差。

5

计算滚动中位数。

6

计算滚动最大值。

7

这将计算指数加权移动平均值,衰减以半衰期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)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
         data[sym].iloc[-200:].plot(ax=ax, lw=2.0);  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
         # plt.savefig('../../images/ch08/fts_05.png');

1

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

2

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

fts 05

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

技术分析示例

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

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

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

In [37]: data['SMA1'] = data[sym].rolling(window=42).mean()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [38]: data['SMA2'] = data[sym].rolling(window=252).mean()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

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));  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
         # plt.savefig('../../images/ch08/fts_06.png');

1

计算短期 SMA 的值。

2

计算长期 SMA 的值。

3

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

fts 06

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

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

In [41]: data.dropna(inplace=True)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [42]: data['positions'] = np.where(data['SMA1'] > data['SMA2'],  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
                                      1,  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
                                      -1)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

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');

1

仅保留完整的数据行。

2

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

3

…买入股票(放置1)…

4

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

fts 07

图 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');

fts 08

图 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');

fts 09

图 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');

fts 10

图 8-10。S&P 500 和 VIX 的对数收益随时间变化

在这种情况下,pandasscatter_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');

1

要绘制的数据集。

2

点的不透明度参数为alpha

3

放置在对角线上的内容;这里是列数据的直方图。

4

这些是要传递给直方图绘图函数的关键字。

fts 11

图 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');

1

这实现了线性 OLS 回归。

2

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

3

… 添加了线性回归线。

fts 12

图 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');

1

整个DataFrame的相关矩阵。

2

这会绘制随时间变化的滚动相关性……

3

… 并将静态值添加到绘图中作为水平线。

fts 13

图 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');

1

计算每一行数据的Mid价格。

fts 14

图 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');

fts 15

图 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  ![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)

1

从标准库导入 pickle 模块。

2

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

3

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

4

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

5

以二进制模式打开文件进行写入(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

1

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

2

关闭文件。

3

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

4

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

5

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

6

ab 转换为 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

1

序列化 andarray 版本并保存。

2

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

3

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

那么如何将两个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()

1

这检索到了存储的对象第一个

2

这检索到了存储的对象第二个

很明显,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*

1

存储包含两个ndarray对象的dict对象。

2

检索dict对象。

然而,这种方法要求一次性写入和读取所有对象。考虑到它带来的更高便利性,这可能是一个可以接受的折中方案。

读写文本文件

文本处理可以被视为 Python 的一个优势。事实上,许多公司和科学用户正是用 Python 来完成这项任务的。使用 Python,你有多种选择来处理str对象,以及一般的文本文件。

假设有一个相当大的数据集要共享为逗号分隔值(CSV)文件。尽管这些文件具有特殊的内部结构,但它们基本上是纯文本文件。以下代码创建一个虚拟数据集作为ndarray对象,一个DatetimeIndex对象,将两者组合并将数据存储为 CSV 文本文件。

In [26]: import pandas as pd

In [27]: rows = 5000  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
         a = np.random.standard_normal((rows, 5)).round(4)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [28]: a  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[28]: array([[-0.9627,  0.1326, -2.012 , -0.299 , -1.4554],
                [ 0.8918,  0.8904, -0.3396, -2.3485,  2.0913],
                [-0.1899, -0.9574,  1.0258,  0.6206, -2.4693],
                ...,
                [ 1.4688, -1.268 , -0.4778,  1.4315, -1.4689],
                [ 1.1162,  0.152 , -0.9363, -0.7869, -0.1147],
                [-0.699 ,  0.3206,  0.3659, -1.0282, -0.4151]])

In [29]: t = pd.date_range(start='2019/1/1', periods=rows, freq='H')  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [30]: t  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[30]: DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 01:00:00',
                        '2019-01-01 02:00:00', '2019-01-01 03:00:00',
                        '2019-01-01 04:00:00', '2019-01-01 05:00:00',
                        '2019-01-01 06:00:00', '2019-01-01 07:00:00',
                        '2019-01-01 08:00:00', '2019-01-01 09:00:00',
                        ...
                        '2019-07-27 22:00:00', '2019-07-27 23:00:00',
                        '2019-07-28 00:00:00', '2019-07-28 01:00:00',
                        '2019-07-28 02:00:00', '2019-07-28 03:00:00',
                        '2019-07-28 04:00:00', '2019-07-28 05:00:00',
                        '2019-07-28 06:00:00', '2019-07-28 07:00:00'],
                       dtype='datetime64[ns]', length=5000, freq='H')

In [31]: csv_file = open(path + 'data.csv', 'w')  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [32]: header = 'date,no1,no2,no3,no4,no5\n'  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [33]: csv_file.write(header)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[33]: 25

In [34]: for t_, (no1, no2, no3, no4, no5) in zip(t, a):  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
             s = '{},{},{},{},{},{}\n'.format(t_, no1, no2, no3, no4, no5)  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
             csv_file.write(s)  ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)

In [35]: csv_file.close()

In [36]: ll $path*

         -rw-r--r--  1 yves  staff  284621 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.csv

1

定义数据集的行数。

2

创建具有随机数的ndarray对象。

3

创建一个适当长度的DatetimeIndex对象(每小时间隔)。

4

打开一个供写入的文件(w)。

5

定义标题行(列标签)并将其写为第一行。

6

数据以行为单位组合…

7

…转换为str对象…

8

…并逐行写入(追加到 CSV 文本文件中)。

另一种方法也类似。首先,打开现有的CSV文件。其次,使用file对象的.readline().readlines()方法逐行读取其内容:

In [37]: csv_file = open(path + 'data.csv', 'r')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [38]: for i in range(5):
             print(csv_file.readline(), end='')  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

         date,no1,no2,no3,no4,no5
         2019-01-01 00:00:00,-0.9627,0.1326,-2.012,-0.299,-1.4554
         2019-01-01 01:00:00,0.8918,0.8904,-0.3396,-2.3485,2.0913
         2019-01-01 02:00:00,-0.1899,-0.9574,1.0258,0.6206,-2.4693
         2019-01-01 03:00:00,-0.0217,-0.7168,1.7875,1.6226,-0.4857

In [39]: csv_file.close()

In [40]: csv_file = open(path + 'data.csv', 'r')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [41]: content = csv_file.readlines()  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [42]: content[:5]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[42]: ['date,no1,no2,no3,no4,no5\n',
          '2019-01-01 00:00:00,-0.9627,0.1326,-2.012,-0.299,-1.4554\n',
          '2019-01-01 01:00:00,0.8918,0.8904,-0.3396,-2.3485,2.0913\n',
          '2019-01-01 02:00:00,-0.1899,-0.9574,1.0258,0.6206,-2.4693\n',
          '2019-01-01 03:00:00,-0.0217,-0.7168,1.7875,1.6226,-0.4857\n']

In [43]: csv_file.close()

1

打开文件以供读取(r)。

2

逐行读取文件内容并打印。

3

一次性读取文件内容…

4

… 其结果是一个包含所有行的list对象,每行作为单独的str对象。

CSV文件如此重要且常见,以至于 Python 标准库中有一个csv模块,简化了 CSV 文件的处理。csv模块的两个有用的读取器(迭代器)对象都返回一个list对象的list对象,或者一个list对象的dict对象。

In [44]: import csv

In [45]: with open(path + 'data.csv', 'r') as f:
             csv_reader = csv.reader(f)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
             lines = [line for line in csv_reader]

In [46]: lines[:5]  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[46]: [['date', 'no1', 'no2', 'no3', 'no4', 'no5'],
          ['2019-01-01 00:00:00', '-0.9627', '0.1326', '-2.012', '-0.299', '-1.4554'],
          ['2019-01-01 01:00:00', '0.8918', '0.8904', '-0.3396', '-2.3485', '2.0913'],
          ['2019-01-01 02:00:00', '-0.1899', '-0.9574', '1.0258', '0.6206', '-2.4693'],
          ['2019-01-01 03:00:00', '-0.0217', '-0.7168', '1.7875', '1.6226', '-0.4857']]

In [47]: with open(path + 'data.csv', 'r') as f:
             csv_reader = csv.DictReader(f)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
             lines = [line for line in csv_reader]

In [48]: lines[:3]  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[48]: [OrderedDict([('date', '2019-01-01 00:00:00'),
                       ('no1', '-0.9627'),
                       ('no2', '0.1326'),
                       ('no3', '-2.012'),
                       ('no4', '-0.299'),
                       ('no5', '-1.4554')]),
          OrderedDict([('date', '2019-01-01 01:00:00'),
                       ('no1', '0.8918'),
                       ('no2', '0.8904'),
                       ('no3', '-0.3396'),
                       ('no4', '-2.3485'),
                       ('no5', '2.0913')]),
          OrderedDict([('date', '2019-01-01 02:00:00'),
                       ('no1', '-0.1899'),
                       ('no2', '-0.9574'),
                       ('no3', '1.0258'),
                       ('no4', '0.6206'),
                       ('no5', '-2.4693')])]

In [49]: !rm -f $path*

1

csv.reader()将每一行都返回为一个list对象。

2

csv.DictReader()将每一行都返回为OrderedDict,它是dict对象的一种特殊情况。

SQL 数据库

Python 可以与任何类型的SQL数据库一起工作,并且通常也可以与任何类型的NoSQL数据库一起工作。在这种情况下,SQL代表结构化查询语言。Python 默认提供的一个SQL关系数据库是SQLite3。借助它,可以轻松地说明 Python 对SQL数据库的基本方法:²

In [50]: import sqlite3 as sq3

In [51]: con = sq3.connect(path + 'numbs.db')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [52]: query = 'CREATE TABLE numbs (Date date, No1 real, No2 real)'  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [53]: con.execute(query)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[53]: <sqlite3.Cursor at 0x1054efb20>

In [54]: con.commit()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [55]: q = con.execute  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [56]: q('SELECT * FROM sqlite_master').fetchall()  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
Out[56]: [('table',
           'numbs',
           'numbs',
           2,
           'CREATE TABLE numbs (Date date, No1 real, No2 real)')]

1

打开数据库连接;如果不存在,则创建一个文件。

2

这是一个创建包含三列的表的SQL查询。³

3

执行查询…

4

… 并提交更改。

5

这为con.execute()方法定义了一个简短的别名。

6

这获取关于数据库的元信息,将刚创建的表显示为单个对象。

现在有了一个带有表的数据库文件,可以使用数据填充该表。每行由一个datetime对象和两个float对象组成:

In [57]: import datetime

In [58]: now = datetime.datetime.now()
         q('INSERT INTO numbs VALUES(?, ?, ?)', (now, 0.12, 7.3))  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[58]: <sqlite3.Cursor at 0x1054efc70>

In [59]: np.random.seed(100)

In [60]: data = np.random.standard_normal((10000, 2)).round(4)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [61]: %%time
         for row in data:  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
             now = datetime.datetime.now()
             q('INSERT INTO numbs VALUES(?, ?, ?)', (now, row[0], row[1]))
         con.commit()

         CPU times: user 111 ms, sys: 3.22 ms, total: 115 ms
         Wall time: 116 ms

In [62]: q('SELECT * FROM numbs').fetchmany(4)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[62]: [('2018-01-18 10:05:24.043286', 0.12, 7.3),
          ('2018-01-18 10:05:24.071921', -1.7498, 0.3427),
          ('2018-01-18 10:05:24.072110', 1.153, -0.2524),
          ('2018-01-18 10:05:24.072160', 0.9813, 0.5142)]

In [63]: q('SELECT * FROM numbs WHERE no1 > 0.5').fetchmany(4)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[63]: [('2018-01-18 10:05:24.072110', 1.153, -0.2524),
          ('2018-01-18 10:05:24.072160', 0.9813, 0.5142),
          ('2018-01-18 10:05:24.072257', 0.6727, -0.1044),
          ('2018-01-18 10:05:24.072319', 1.619, 1.5416)]

In [64]: pointer = q('SELECT * FROM numbs')  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

In [65]: for i in range(3):
             print(pointer.fetchone())  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)

         ('2018-01-18 10:05:24.043286', 0.12, 7.3)
         ('2018-01-18 10:05:24.071921', -1.7498, 0.3427)
         ('2018-01-18 10:05:24.072110', 1.153, -0.2524)

In [66]: rows = pointer.fetchall()  ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
         rows[:3]
Out[66]: [('2018-01-18 10:05:24.072160', 0.9813, 0.5142),
          ('2018-01-18 10:05:24.072184', 0.2212, -1.07),
          ('2018-01-18 10:05:24.072202', -0.1895, 0.255)]

1

将单行(或记录)写入numbs表。

2

创建一个较大的虚拟数据集作为ndarray对象。

3

迭代ndarray对象的行。

4

从表中检索多行。

5

相同但在no1列的值上有条件。

6

定义一个指针对象…

7

…它的行为类似于生成器对象。

8

.fetchall()检索所有剩余的行。

最后,如果不再需要,可能会想要删除数据库中的表对象。

In [67]: q('DROP TABLE IF EXISTS numbs')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[67]: <sqlite3.Cursor at 0x1054eff80>

In [68]: q('SELECT * FROM sqlite_master').fetchall()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[68]: []

In [69]: con.close()  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [70]: !rm -f $path*  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

1

从数据库中删除表格。

2

此操作后不再存在表格对象。

3

关闭数据库连接。

4

从磁盘中删除数据库文件。

SQL数据库是一个相当广泛的主题;事实上,在本章中无法对其进行任何重要的涵盖,因为它太广泛且复杂了。基本信息如下:

  • Python 与几乎所有的数据库技术都能很好地集成。

  • 基本的SQL语法主要由所使用的数据库确定;其余部分如我们所说,都是Pythonic的。

接下来会有几个基于SQLite3的示例。

写入和读取 NumPy 数组

NumPy本身有函数可以以方便和高效的方式写入和读取ndarray对象。在某些情况下,这节省了很多工作,比如当你必须将NumPydtype对象转换为特定的数据库类型时(例如对于SQLite3)。为了说明NumPy有时可以有效替代基于SQL的方法,以下代码复制了之前使用NumPy的示例。

代码使用NumPynp.arange()函数生成一个存储了datetime对象的ndarray对象,而不是使用pandas:⁴

In [71]: dtimes = np.arange('2019-01-01 10:00:00', '2025-12-31 22:00:00',
                           dtype='datetime64[m]')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [72]: len(dtimes)
Out[72]: 3681360

In [73]: dty = np.dtype([('Date', 'datetime64[m]'),
                         ('No1', 'f'), ('No2', 'f')])  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [74]: data = np.zeros(len(dtimes), dtype=dty)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [75]: data['Date'] = dtimes  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [76]: a = np.random.standard_normal((len(dtimes), 2)).round(4)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [77]: data['No1'] = a[:, 0]  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
         data['No2'] = a[:, 1]  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

In [78]: data.nbytes  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
Out[78]: 58901760

1

创建一个带有datetime作为dtypendarray对象。

2

用于记录数组的特殊dtype对象。

3

用特殊dtype实例化的ndarray对象。

4

这将填充Date列。

5

假数据集……

6

…这填充了No1No2列。

7

记录数组的大小(以字节为单位)。

保存ndarray对象是高度优化的,因此非常快速。大约 60 MB 的数据在磁盘上保存约 0.1 秒(这里使用 SSD)。大小为 480 MB 的较大ndarray对象在磁盘上保存大约需要 1 秒钟。

In [79]: %time np.save(path + 'array', data)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

         CPU times: user 4.06 ms, sys: 99.3 ms, total: 103 ms
         Wall time: 107 ms

In [80]: ll $path*  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

         -rw-r--r--  1 yves  staff  58901888 Jan 18 10:05 /Users/yves/Documents/Temp/data/array.npy

In [81]: %time np.load(path + 'array.npy')  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

         CPU times: user 1.81 ms, sys: 47.4 ms, total: 49.2 ms
         Wall time: 46.7 ms

Out[81]: array([('2019-01-01T10:00',  1.51310003,  0.69730002),
                ('2019-01-01T10:01', -1.722     , -0.4815    ),
                ('2019-01-01T10:02',  0.8251    ,  0.3019    ), ...,
                ('2025-12-31T21:57',  1.37199998,  0.64459997),
                ('2025-12-31T21:58', -1.25419998,  0.1612    ),
                ('2025-12-31T21:59', -1.1997    , -1.097     )],
               dtype=[('Date', '<M8[m]'), ('No1', '<f4'), ('No2', '<f4')])

In [82]: %time data = np.random.standard_normal((10000, 6000)).round(4)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

         CPU times: user 2.81 s, sys: 354 ms, total: 3.17 s
         Wall time: 3.23 s

In [83]: data.nbytes  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[83]: 480000000

In [84]: %time np.save(path + 'array', data)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

         CPU times: user 23.9 ms, sys: 878 ms, total: 902 ms
         Wall time: 964 ms

In [85]: ll $path*  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

         -rw-r--r--  1 yves  staff  480000080 Jan 18 10:05 /Users/yves/Documents/Temp/data/array.npy

In [86]: %time np.load(path + 'array.npy')  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

         CPU times: user 1.95 ms, sys: 441 ms, total: 443 ms
         Wall time: 441 ms

Out[86]: array([[ 0.3066,  0.5951,  0.5826, ...,  1.6773,  0.4294, -0.2216],
                [ 0.8769,  0.7292, -0.9557, ...,  0.5084,  0.9635, -0.4443],
                [-1.2202, -2.5509, -0.0575, ..., -1.6128,  0.4662, -1.3645],
                ...,
                [-0.5598,  0.2393, -2.3716, ...,  1.7669,  0.2462,  1.035 ],
                [ 0.273 ,  0.8216, -0.0749, ..., -0.0552, -0.8396,  0.3077],
                [-0.6305,  0.8331,  1.3702, ...,  0.3493,  0.1981,  0.2037]])

In [87]: !rm -f $path*

1

这将记录的ndarray对象保存到磁盘上。

2

磁盘上的大小几乎与内存中的大小相同(由于二进制存储)。

3

这会从磁盘加载记录的ndarray对象。

4

一个较大的普通ndarray对象。

这些示例说明,在这种情况下,写入磁盘主要受硬件限制,因为 480 MB/s 大致代表了标准 SSD 在撰写本文时的宣传写入速度(512 MB/s)。

无论如何,可以预期,与使用标准 SQL 数据库或使用标准 pickle 库进行序列化相比,这种形式的数据存储和检索速度要快得多。有两个原因:首先,数据主要是数字;其次,NumPy 实现了二进制存储,几乎将开销降低到零。当然,使用这种方法不具备 SQL 数据库的功能,但是随后的部分将显示 PyTables 将在这方面提供帮助。

pandas 中的 I/O

pandas 的一个主要优势之一是它可以原生地读取和写入不同的数据格式,其中包括:

  • CSV(逗号分隔值)

  • SQL(结构化查询语言)

  • XLS/XSLX(微软 Excel 文件)

  • JSONJavaScript 对象表示法

  • HTML(超文本标记语言)

表 9-1 列出了 pandasDataFrame 类的支持格式以及相应的导入和导出函数/方法。导入函数所接受的参数在 [Link to Come] 中列出并描述(根据函数,可能适用其他约定)。

表 9-1. 导入导出函数和方法

格式 输入 输出 备注
CSV pd.read_csv() .to_csv() 文本文件
XLS/XLSX pd.read_excel() .to_excel() 电子表格
HDF pd.read_hdf() .to_hdf() HDF5 数据库
SQL pd.read_sql() .to_sql() SQL
JSON pd.read_json() .to_json() JavaScript 对象表示法
MSGPACK pd.read_msgpack() .to_msgpack() 可移植二进制格式
HTML pd.read_html() .to_html() HTML 代码
GBQ pd.read_gbq() .to_gbq() Google Big Query 格式
DTA pd.read_stata() .to_stata() 格式 104, 105, 108, 113-115, 117
任何 pd.read_clipboard() .to_clipboard() 例如,从 HTML 页面
任何 pd.read_pickle() .to_pickle() (结构化的)Python 对象

测试案例再次是一个较大的 float 对象集合:

In [88]: data = np.random.standard_normal((1000000, 5)).round(4)

In [89]: data[:3]
Out[89]: array([[ 0.4918,  1.3707,  0.137 ,  0.3981, -1.0059],
                [ 0.4516,  1.4445,  0.0555, -0.0397,  0.44  ],
                [ 0.1629, -0.8473, -0.8223, -0.4621, -0.5137]])

为此,我们还将重新审视 SQLite3 并将其性能与使用 pandas 的替代格式进行比较。

SQL 数据库

至于 SQLite3 的一切,现在应该都很熟悉了。

In [90]: filename = path + 'numbers'

In [91]: con = sq3.Connection(filename + '.db')

In [92]: query = 'CREATE TABLE numbers (No1 real, No2 real,\
 No3 real, No4 real, No5 real)'  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [93]: q = con.execute
         qm = con.executemany

In [94]: q(query)
Out[94]: <sqlite3.Cursor at 0x1054e2260>

1

一张具有五列实数(float 对象)的表格。

这次,可以应用 .executemany() 方法,因为数据在一个单一的 ndarray 对象中可用。读取和处理数据与以前一样工作。查询结果也可以轻松可视化(参见 图 9-1)。

In [95]: %%time
         qm('INSERT INTO numbers VALUES (?, ?, ?, ?, ?)', data)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
         con.commit()

         CPU times: user 7.16 s, sys: 147 ms, total: 7.3 s
         Wall time: 7.39 s

In [96]: ll $path*

         -rw-r--r--  1 yves  staff  52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db

In [97]: %%time
         temp = q('SELECT * FROM numbers').fetchall()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
         print(temp[:3])

         [(0.4918, 1.3707, 0.137, 0.3981, -1.0059), (0.4516, 1.4445, 0.0555, -0.0397, 0.44), (0.1629, -0.8473, -0.8223, -0.4621, -0.5137)]
         CPU times: user 1.86 s, sys: 138 ms, total: 2 s
         Wall time: 2.07 s

In [98]: %%time
         query = 'SELECT * FROM numbers WHERE No1 > 0 AND No2 < 0'
         res = np.array(q(query).fetchall()).round(3)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

         CPU times: user 770 ms, sys: 73.9 ms, total: 844 ms
         Wall time: 854 ms

In [99]: res = res[::100]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
         plt.figure(figsize=(10, 6))
         plt.plot(res[:, 0], res[:, 1], 'ro')  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
         plt.savefig('../../images/ch09/io_01.png');

1

将整个数据集一次性插入表中。

2

以单步操作从表中检索所有行。

3

检索行的选择并将其转换为 ndarray 对象。

4

绘制查询结果的子集。

io 01

图 9-1. 查询结果的散点图(选择)

从 SQL 到 pandas

一个通常更高效的方法,然而,是使用 pandas 读取整个表或查询结果。当您能够将整个表读入内存时,分析查询通常可以比使用 SQL 基于磁盘的方法执行得快得多。

使用 pandas 读取整个表与将其读入 NumPy ndarray 对象大致需要相同的时间。在这里和那里,瓶颈是 SQL 数据库。

In [100]: %time data = pd.read_sql('SELECT * FROM numbers', con)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

          CPU times: user 2.11 s, sys: 175 ms, total: 2.29 s
          Wall time: 2.33 s

In [101]: data.head()
Out[101]:       No1     No2     No3     No4     No5
          0  0.4918  1.3707  0.1370  0.3981 -1.0059
          1  0.4516  1.4445  0.0555 -0.0397  0.4400
          2  0.1629 -0.8473 -0.8223 -0.4621 -0.5137
          3  1.3064  0.9125  0.5142 -0.7868 -0.3398
          4 -0.1148 -1.5215 -0.7045 -1.0042 -0.0600

1

将表的所有行读入名为 dataDataFrame 对象中。

数据现在在内存中。这样可以进行更快的分析。加速通常是一个数量级或更多。pandas 也可以处理更复杂的查询,尽管它既不意味着也不能替代 SQL 数据库,当涉及复杂的关系数据结构时。多个条件组合的查询结果显示在 图 9-2 中。

In [102]: %time data[(data['No1'] > 0) & (data['No2'] < 0)].head()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

          CPU times: user 19.4 ms, sys: 9.56 ms, total: 28.9 ms
          Wall time: 27.5 ms

Out[102]:        No1     No2     No3     No4     No5
          2   0.1629 -0.8473 -0.8223 -0.4621 -0.5137
          5   0.1893 -0.0207 -0.2104  0.9419  0.2551
          8   1.4784 -0.3333 -0.7050  0.3586 -0.3937
          10  0.8092 -0.9899  1.0364 -1.0453  0.0579
          11  0.9065 -0.7757 -0.9267  0.7797  0.0863

In [103]: %%time
          res = data[['No1', 'No2']][((data['No1'] > 0.5) | (data['No1'] < -0.5))
                               & ((data['No2'] < -1) | (data['No2'] > 1))]  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 20.6 ms, sys: 9.18 ms, total: 29.8 ms
          Wall time: 28 ms

In [104]: plt.figure(figsize=(10, 6))
          plt.plot(res['No1'], res['No2'], 'ro');
          plt.savefig('../../images/ch09/io_02.png');

1

两个条件逻辑上组合。

2

逻辑上组合了四个条件。

io 02

图 9-2. 查询结果的散点图(选择)

预期地,使用 pandas 的内存分析能力会显著加速,只要 pandas 能够复制相应的 SQL 语句。

使用 pandas 的另一个优点不仅仅是这个,因为 pandasPyTables 等紧密集成 — 后续部分的主题。在这里,知道它们的组合可以显著加速 I/O 操作就足够了。如下所示:

In [105]: h5s = pd.HDFStore(filename + '.h5s', 'w')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [106]: %time h5s['data'] = data  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 33 ms, sys: 43.3 ms, total: 76.3 ms
          Wall time: 85.8 ms

In [107]: h5s  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[107]: <class 'pandas.io.pytables.HDFStore'>
          File path: /Users/yves/Documents/Temp/data/numbers.h5s

In [108]: h5s.close()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

1

打开 HDF5 数据库文件进行写入;在 pandas 中创建一个 HDFStore 对象。

2

完整的 DataFrame 对象通过二进制存储存储在数据库文件中。

3

HDFStore 对象的信息。

4

关闭数据库文件。

与使用 SQLite3 相比,整个来自原始 SQL 表的所有数据的 DataFrame 写入速度快得多。读取甚至更快:

In [109]: %%time
          h5s = pd.HDFStore(filename + '.h5s', 'r')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
          data_ = h5s['data']  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
          h5s.close()  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 8.24 ms, sys: 21.2 ms, total: 29.4 ms
          Wall time: 28.5 ms

In [110]: data_ is data  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[110]: False

In [111]: (data_ == data).all()  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[111]: No1    True
          No2    True
          No3    True
          No4    True
          No5    True
          dtype: bool

In [112]: np.allclose(data_, data)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[112]: True

In [113]: ll $path*  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

          -rw-r--r--  1 yves  staff  52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db
          -rw-r--r--  1 yves  staff  48007192 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.h5s

1

打开 HDF5 数据库文件进行读取。

2

DataFrame 被读取并存储在内存中作为 data_

3

关闭数据库文件。

4

这两个 DataFrame 对象不相同。

5

然而,它们现在包含相同的数据。

6

SQL 表相比,二进制存储通常具有更小的大小开销。

CSV 文件中的数据

交换金融数据最广泛使用的格式之一是 CSV 格式。尽管它并没有真正标准化,但它可以被任何平台处理,并且绝大多数与数据和金融分析有关的应用程序都可以处理。前一节展示了如何使用标准 Python 功能将数据写入 CSV 文件并从 CSV 文件中读取数据(参见“读写文本文件”)。pandas 使得整个过程更加方便,代码更加简洁,并且总体执行更快(还可以参见图 9-3):

In [114]: %time data.to_csv(filename + '.csv')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

          CPU times: user 6.82 s, sys: 277 ms, total: 7.1 s
          Wall time: 7.54 s

In [115]: ll $path

          total 282184
          -rw-r--r--  1 yves  staff  43834157 Jan 18 10:05 numbers.csv
          -rw-r--r--  1 yves  staff  52633600 Jan 18 10:05 numbers.db
          -rw-r--r--  1 yves  staff  48007192 Jan 18 10:05 numbers.h5s

In [116]: %time df = pd.read_csv(filename + '.csv')  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 1.4 s, sys: 124 ms, total: 1.53 s
          Wall time: 1.58 s

In [117]: df[['No1', 'No2', 'No3', 'No4']].hist(bins=20, figsize=(10, 6));
          plt.savefig('../../images/ch09/io_03.png');

1

.to_csv() 方法将 DataFrame 数据以 CSV 格式写入磁盘。

2

然后 pd.read_csv() 以新的 DataFrame 对象的形式将其再次读入内存。

io 03

图 9-3. 选定列的直方图

Excel 文件中的数据

尽管处理 Excel 电子表格是本书的后续章节的主题,但以下代码简要地演示了 pandas 如何以 Excel 格式写入数据并从 Excel 电子表格中读取数据。在这种情况下,我们将数据集限制为 100,000 行:

In [118]: %time data[:100000].to_excel(filename + '.xlsx')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

          CPU times: user 23.2 s, sys: 498 ms, total: 23.7 s
          Wall time: 23.9 s

In [119]: %time df = pd.read_excel(filename + '.xlsx', 'Sheet1')  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 5.47 s, sys: 74.7 ms, total: 5.54 s
          Wall time: 5.57 s

In [120]: df.cumsum().plot(figsize=(10, 6));
          plt.savefig('../../images/ch09/io_04.png');
In [121]: ll $path*

          -rw-r--r--  1 yves  staff  43834157 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.csv
          -rw-r--r--  1 yves  staff  52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db
          -rw-r--r--  1 yves  staff  48007192 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.h5s
          -rw-r--r--  1 yves  staff   4032639 Jan 18 10:06 /Users/yves/Documents/Temp/data/numbers.xlsx

In [122]: rm -f $path*

1

.to_excel() 方法将 DataFrame 数据以 XLSX 格式写入磁盘。

2

然后 pd.read_excel() 以新的 DataFrame 对象的形式将其再次读入内存,同时指定要从中读取的工作表。

io 04

图 9-4. 所有列的线性图

生成包含较小数据子集的 Excel 电子表格文件需要相当长的时间。这说明了电子表格结构所带来的额外开销。

对生成的文件进行检查后发现,DataFrameHDFStore 结合是最紧凑的选择(使用压缩,正如本章后面所述,进一步增加了优势)。与文本文件相比,作为 CSV 文件的相同数量的数据的大小要大一些。这是处理 CSV 文件时性能较慢的另一个原因,另一个原因是它们只是“普通”文本文件。

使用 PyTables 进行快速 I/O

PyTablesHDF5数据库标准的 Python 绑定(参见http://www.hdfgroup.org)。它专门设计用于优化 I/O 操作的性能,并充分利用可用的硬件。库的导入名称是tables。与pandas类似,当涉及到内存分析时,PyTables既不能也不意味着是对SQL数据库的完全替代。然而,它带来了一些进一步缩小差距的特性。例如,一个PyTables数据库可以有很多表,它支持压缩和索引以及对表的非平凡查询。此外,它可以有效地存储NumPy数组,并具有其自己的数组数据结构的风格。

首先,一些导入:

In [123]: import tables as tb  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
          import datetime as dt

1

包名是PyTables,导入名称是tables

与表格一起工作

PyTables提供了一种基于文件的数据库格式,类似于SQLite3。⁵。以下是打开数据库文件并创建表格的示例:

In [124]: filename = path + 'pytab.h5'

In [125]: h5 = tb.open_file(filename, 'w')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [126]: row_des = {
              'Date': tb.StringCol(26, pos=1),  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
              'No1': tb.IntCol(pos=2),  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
              'No2': tb.IntCol(pos=3),  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
              'No3': tb.Float64Col(pos=4),  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              'No4': tb.Float64Col(pos=5)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              }

In [127]: rows = 2000000

In [128]: filters = tb.Filters(complevel=0)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

In [129]: tab = h5.create_table('/', 'ints_floats',  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
                                row_des,  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
                                title='Integers and Floats',  ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
                                expectedrows=rows,  ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/9.png)
                                filters=filters)  ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/10.png)

In [130]: type(tab)
Out[130]: tables.table.Table

In [131]: tab
Out[131]: /ints_floats (Table(0,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)

1

HDF5二进制存储格式打开数据库文件。

2

用于日期时间信息的date列(作为str对象)。

3

用于存储int对象的两列。

4

用于存储float对象的两列。

5

通过Filters对象,可以指定压缩级别等。

6

表的节点(路径)和技术名称。

7

行数据结构的描述。

8

表的名称(标题)。

9

预期的行数;允许进行优化。

10

用于表格的Filters对象。

为了用数字数据填充表格,生成两个具有随机数字的ndarray对象。一个是随机整数,另一个是随机浮点数。通过一个简单的 Python 循环来填充表格。

In [132]: pointer = tab.row  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [133]: ran_int = np.random.randint(0, 10000, size=(rows, 2))  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [134]: ran_flo = np.random.standard_normal((rows, 2)).round(4)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [135]: %%time
          for i in range(rows):
              pointer['Date'] = dt.datetime.now()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              pointer['No1'] = ran_int[i, 0]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              pointer['No2'] = ran_int[i, 1]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              pointer['No3'] = ran_flo[i, 0]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              pointer['No4'] = ran_flo[i, 1]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
              pointer.append()  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
          tab.flush()  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

          CPU times: user 8.36 s, sys: 136 ms, total: 8.49 s
          Wall time: 8.92 s

In [136]: tab  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
Out[136]: /ints_floats (Table(2000000,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)

In [137]: ll $path*

          -rw-r--r--  1 yves  staff  100156248 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5

1

创建了一个指针对象。

2

具有随机int对象的ndarray对象。

3

具有随机float对象的ndarray对象。

4

datetime对象,两个int和两个float对象被逐行写入。

5

新行被附加。

6

所有写入的行都会被刷新,即作为永久更改提交。

7

更改反映在 Table 对象描述中。

在这种情况下,Python 循环相当慢。 有一种更高效和 Pythonic 的方法可以实现相同的结果,即使用 NumPy 结构化数组。 使用存储在结构化数组中的完整数据集,表的创建归结为一行代码。 请注意,不再需要行描述; PyTables 使用结构化数组的 dtype 对象来推断数据类型:

In [138]: dty = np.dtype([('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'),
                                           ('No3', '<f8'), ('No4', '<f8')])  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [139]: sarray = np.zeros(len(ran_int), dtype=dty)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [140]: sarray[:4]  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[140]: array([(b'', 0, 0,  0.,  0.), (b'', 0, 0,  0.,  0.), (b'', 0, 0,  0.,  0.),
                 (b'', 0, 0,  0.,  0.)],
                dtype=[('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'), ('No3', '<f8'), ('No4', '<f8')])

In [141]: %%time
          sarray['Date'] = dt.datetime.now()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          sarray['No1'] = ran_int[:, 0]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          sarray['No2'] = ran_int[:, 1]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          sarray['No3'] = ran_flo[:, 0]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          sarray['No4'] = ran_flo[:, 1]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          CPU times: user 82.7 ms, sys: 37.9 ms, total: 121 ms
          Wall time: 133 ms

In [142]: %%time
          h5.create_table('/', 'ints_floats_from_array', sarray,
                                title='Integers and Floats',
                                expectedrows=rows, filters=filters)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

          CPU times: user 39 ms, sys: 61 ms, total: 100 ms
          Wall time: 123 ms

Out[142]: /ints_floats_from_array (Table(2000000,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)

1

定义特殊的 dtype 对象。

2

使用零(和空字符串)创建结构化数组。

3

来自 ndarray 对象的几条记录。

4

ndarray 对象的列一次性填充。

5

这将创建 Table 对象,并用数据填充它。

这种方法快了一个数量级,代码更简洁,且实现了相同的结果。

In [143]: type(h5)
Out[143]: tables.file.File

In [144]: h5  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[144]: File(filename=/Users/yves/Documents/Temp/data/pytab.h5, title='', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None))
          / (RootGroup) ''
          /ints_floats (Table(2000000,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)
          /ints_floats_from_array (Table(2000000,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)

In [145]: h5.remove_node('/', 'ints_floats_from_array')  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

1

带有两个 Table 对象的 File 对象的描述。

2

这会删除具有冗余数据的第二个 Table 对象。

Table 对象在大多数情况下的行为与 NumPy 结构化的 ndarray 对象非常相似(另见 图 9-5):

In [146]: tab[:3]  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[146]: array([(b'2018-01-18 10:06:28.516235', 8576, 5991, -0.0528,  0.2468),
                 (b'2018-01-18 10:06:28.516332', 2990, 9310, -0.0261,  0.3932),
                 (b'2018-01-18 10:06:28.516344', 4400, 4823,  0.9133,  0.2579)],
                dtype=[('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'), ('No3', '<f8'), ('No4', '<f8')])

In [147]: tab[:4]['No4']  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[147]: array([ 0.2468,  0.3932,  0.2579, -0.5582])

In [148]: %time np.sum(tab[:]['No3'])  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 64.5 ms, sys: 97.1 ms, total: 162 ms
          Wall time: 165 ms

Out[148]: 88.854299999999697

In [149]: %time np.sum(np.sqrt(tab[:]['No1']))  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 59.3 ms, sys: 69.4 ms, total: 129 ms
          Wall time: 130 ms

Out[149]: 133349920.36892509

In [150]: %%time
          plt.figure(figsize=(10, 6))
          plt.hist(tab[:]['No3'], bins=30);  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          plt.savefig('../../images/ch09/io_05.png');

          CPU times: user 244 ms, sys: 67.6 ms, total: 312 ms
          Wall time: 340 ms

1

通过索引选择行。

2

仅通过索引选择列值。

3

应用 NumPy 通用函数。

4

Table 对象绘制列。

io 05

图 9-5. 列数据的直方图

PyTables 还提供了通过典型的 SQL-like 语句查询数据的灵活工具,如下例所示(其结果如 图 9-6 所示;与 图 9-2 相比,基于 pandas 查询):

In [151]: query = '((No3 < -0.5) | (No3 > 0.5)) & ((No4 < -1) | (No4 > 1))'  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [152]: iterator = tab.where(query)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [153]: %time res = [(row['No3'], row['No4']) for row in iterator]  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 487 ms, sys: 128 ms, total: 615 ms
          Wall time: 637 ms

In [154]: res = np.array(res)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
          res[:3]
Out[154]: array([[ 0.7694,  1.4866],
                 [ 0.9201,  1.3346],
                 [ 1.4701,  1.8776]])

In [155]: plt.figure(figsize=(10, 6))
          plt.plot(res.T[0], res.T[1], 'ro');
          plt.savefig('../../images/ch09/io_06.png');

1

查询作为 str 对象,由逻辑运算符组合的四个条件。

2

基于查询的迭代器对象。

3

通过列表推导收集查询结果的行…

4

… 并转换为 ndarray 对象。

io 06

图 9-6. 列数据的直方图

快速查询

pandasPyTables都能够处理相对复杂的、类似SQL的查询和选择。它们在执行此类操作时都进行了速度优化。但是,与关系型数据库相比,这些方法当然存在限制。但对于大多数数值和金融应用程序,它们通常并不决定性。

正如以下示例所示,使用存储在PyTables中的数据作为Table对象让您感觉就像是在NumPypandas中工作且是内存中的,从语法性能方面都是如此:

In [156]: %%time
          values = tab[:]['No3']
          print('Max %18.3f' % values.max())
          print('Ave %18.3f' % values.mean())
          print('Min %18.3f' % values.min())
          print('Std %18.3f' % values.std())

          Max              5.224
          Ave              0.000
          Min             -5.649
          Std              1.000
          CPU times: user 88.9 ms, sys: 70 ms, total: 159 ms
          Wall time: 156 ms

In [157]: %%time
          res = [(row['No1'], row['No2']) for row in
                  tab.where('((No1 > 9800) | (No1 < 200)) \
 & ((No2 > 4500) & (No2 < 5500))')]

          CPU times: user 78.4 ms, sys: 38.9 ms, total: 117 ms
          Wall time: 80.9 ms

In [158]: for r in res[:4]:
              print(r)

          (91, 4870)
          (9803, 5026)
          (9846, 4859)
          (9823, 5069)

In [159]: %%time
          res = [(row['No1'], row['No2']) for row in
                  tab.where('(No1 == 1234) & (No2 > 9776)')]

          CPU times: user 58.9 ms, sys: 40.1 ms, total: 99 ms
          Wall time: 133 ms

In [160]: for r in res:
              print(r)

          (1234, 9841)
          (1234, 9821)
          (1234, 9867)
          (1234, 9987)
          (1234, 9849)
          (1234, 9800)

使用压缩表

使用PyTables的一个主要优势是它采用的压缩方法。它不仅使用压缩来节省磁盘空间,还利用了在某些硬件场景下改善 I/O 操作性能的压缩。这是如何实现的?当 I/O 成为瓶颈,而 CPU 能够快速(解)压缩数据时,压缩在速度方面的净效果可能是积极的。由于以下示例基于标准 SSD 的 I/O,因此观察不到压缩的速度优势。但是,使用压缩也几乎没有缺点

In [161]: filename = path + 'pytabc.h5'

In [162]: h5c = tb.open_file(filename, 'w')

In [163]: filters = tb.Filters(complevel=5,  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
                               complib='blosc')  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [164]: tabc = h5c.create_table('/', 'ints_floats', sarray,
                                  title='Integers and Floats',
                                  expectedrows=rows, filters=filters)

In [165]: query = '((No3 < -0.5) | (No3 > 0.5)) & ((No4 < -1) | (No4 > 1))'

In [166]: iteratorc = tabc.where(query)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [167]: %time res = [(row['No3'], row['No4']) for row in iteratorc]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          CPU times: user 362 ms, sys: 55.3 ms, total: 418 ms
          Wall time: 445 ms

In [168]: res = np.array(res)
          res[:3]
Out[168]: array([[ 0.7694,  1.4866],
                 [ 0.9201,  1.3346],
                 [ 1.4701,  1.8776]])

1

压缩级别(complevel)可以取 0(无压缩)到 9(最高压缩)的值。

2

使用了经过优化的Blosc压缩引擎(Blosc),该引擎旨在提高性能。

3

给定前面查询的迭代器对象。

4

通过列表推导收集查询结果行。

使用原始数据生成压缩的Table对象并对其进行分析比使用未压缩的Table对象稍慢一些。那么将数据读入ndarray对象呢?让我们来检查一下:

In [169]: %time arr_non = tab.read()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

          CPU times: user 42.9 ms, sys: 69.9 ms, total: 113 ms
          Wall time: 117 ms

In [170]: tab.size_on_disk
Out[170]: 100122200

In [171]: arr_non.nbytes
Out[171]: 100000000

In [172]: %time arr_com = tabc.read()  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 123 ms, sys: 60.5 ms, total: 184 ms
          Wall time: 191 ms

In [173]: tabc.size_on_disk
Out[173]: 40612465

In [174]: arr_com.nbytes
Out[174]: 100000000

In [175]: 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  200312336 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5
          -rw-r--r--  1 yves  staff   40647761 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytabc.h5

In [176]: h5c.close()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

1

从未压缩的Table对象tab中读取。

2

从压缩的Table对象tabc中读取。

3

压缩表的大小显着减小了。

4

关闭数据库文件。

例子表明,与未压缩的Table对象相比,使用压缩的Table对象工作时几乎没有速度差异。但是,磁盘上的文件大小可能会根据数据的质量而显着减少,这有许多好处:

  • 存储成本:存储成本降低了

  • 备份成本:备份成本降低了

  • 网络流量:网络流量减少了

  • 网络速度:存储在远程服务器上并从中检索的速度更快

  • CPU 利用率:为了克服 I/O 瓶颈而增加了 CPU 利用率

使用数组

“Python 基本 I/O”演示了NumPy对于ndarray对象具有内置的快速写入和读取功能。当涉及到存储和检索ndarray对象时,PyTables也非常快速和高效。由于它基于分层数据库结构,因此提供了许多便利功能:

In [177]: %%time
          arr_int = h5.create_array('/', 'integers', ran_int)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
          arr_flo = h5.create_array('/', 'floats', ran_flo)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

          CPU times: user 3.24 ms, sys: 33.1 ms, total: 36.3 ms
          Wall time: 41.6 ms

In [178]: h5  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[178]: File(filename=/Users/yves/Documents/Temp/data/pytab.h5, title='', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None))
          / (RootGroup) ''
          /floats (Array(2000000, 2)) ''
            atom := Float64Atom(shape=(), dflt=0.0)
            maindim := 0
            flavor := 'numpy'
            byteorder := 'little'
            chunkshape := None
          /integers (Array(2000000, 2)) ''
            atom := Int64Atom(shape=(), dflt=0)
            maindim := 0
            flavor := 'numpy'
            byteorder := 'little'
            chunkshape := None
          /ints_floats (Table(2000000,)) 'Integers and Floats'
            description := {
            "Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
            "No1": Int32Col(shape=(), dflt=0, pos=1),
            "No2": Int32Col(shape=(), dflt=0, pos=2),
            "No3": Float64Col(shape=(), dflt=0.0, pos=3),
            "No4": Float64Col(shape=(), dflt=0.0, pos=4)}
            byteorder := 'little'
            chunkshape := (2621,)

In [179]: ll $path*

          -rw-r--r--  1 yves  staff  262344490 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5
          -rw-r--r--  1 yves  staff   40647761 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytabc.h5

In [180]: h5.close()

In [181]: !rm -f $path*

1

存储ran_int ndarray对象。

2

存储ran_flo ndarray对象。

3

更改反映在对象描述中。

将这些对象直接写入HDF5数据库比遍历对象并逐行将数据写入Table对象或使用结构化ndarray对象的方法更快。

基于 HDF5 的数据存储

当涉及到结构化的数值和金融数据时,HDF5分层数据库(文件)格式是一个强大的替代方案,例如,关系数据库。无论是在直接使用PyTables还是与pandas的功能结合使用时,您都可以期望获得几乎达到可用硬件允许的最大 I/O 性能。

内存外计算

PyTables支持内存外操作,这使得可以实现不适合内存的基于数组的计算。为此,请考虑以下基于EArray类的代码。这种类型的对象允许在一维(按行)中扩展,而列数(每行的元素)需要固定。

In [182]: filename = path + 'earray.h5'

In [183]: h5 = tb.open_file(filename, 'w')

In [184]: n = 500  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [185]: ear = h5.create_earray('/', 'ear',  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
                                atom=tb.Float64Atom(),  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
                                shape=(0, n))  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [186]: type(ear)
Out[186]: tables.earray.EArray

In [187]: rand = np.random.standard_normal((n, n))  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
          rand[:4, :4]
Out[187]: array([[-1.25983231,  1.11420699,  0.1667485 ,  0.7345676 ],
                 [-0.13785424,  1.22232417,  1.36303097,  0.13521042],
                 [ 1.45487119, -1.47784078,  0.15027672,  0.86755989],
                 [-0.63519366,  0.1516327 , -0.64939447, -0.45010975]])

In [188]: %%time
          for _ in range(750):
              ear.append(rand)  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
          ear.flush()

          CPU times: user 728 ms, sys: 1.11 s, total: 1.84 s
          Wall time: 2.03 s

In [189]: ear
Out[189]: /ear (EArray(375000, 500)) ''
            atom := Float64Atom(shape=(), dflt=0.0)
            maindim := 0
            flavor := 'numpy'
            byteorder := 'little'
            chunkshape := (16, 500)

In [190]: ear.size_on_disk
Out[190]: 1500032000

1

这定义了固定的列数。

2

EArray对象的路径和技术名称。

3

单个值的原子dtype对象。

4

用于实例化的形状(没有行,n列)。

5

具有随机数的ndarray对象…

6

… 多次附加。

对于不会导致聚合的内存外计算,需要另一个相同形状(大小)的EArray对象。 PyTables+有一个特殊模块可以高效处理数值表达式。它称为Expr,基于数值表达式库numexpr。接下来的代码使用Expr计算之前整个EArray对象中的方程式 9-1 的数学表达式。

方程式 9-1. 示例数学表达式

y = 3 sin ( x ) + | x |

结果存储在out EArray对象中,表达式评估以块方式进行。

In [191]: out = h5.create_earray('/', 'out',
                                atom=tb.Float64Atom(),
                                shape=(0, n))

In [192]: out.size_on_disk
Out[192]: 0

In [193]: expr = tb.Expr('3 * sin(ear) + sqrt(abs(ear))')  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [194]: expr.set_output(out, append_mode=True)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [195]: %time expr.eval()  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 2.98 s, sys: 1.38 s, total: 4.36 s
          Wall time: 3.28 s

Out[195]: /out (EArray(375000, 500)) ''
            atom := Float64Atom(shape=(), dflt=0.0)
            maindim := 0
            flavor := 'numpy'
            byteorder := 'little'
            chunkshape := (16, 500)

In [196]: out.size_on_disk
Out[196]: 1500032000

In [197]: out[0, :10]
Out[197]: array([-1.73369462,  3.74824436,  0.90627898,  2.86786818,  1.75424957,
                 -0.91108973, -1.68313885,  1.29073295, -1.68665599, -1.71345309])

In [198]: %time out_ = out.read()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          CPU times: user 879 ms, sys: 1.11 s, total: 1.99 s
          Wall time: 2.18 s

In [199]: out_[0, :10]
Out[199]: array([-1.73369462,  3.74824436,  0.90627898,  2.86786818,  1.75424957,
                 -0.91108973, -1.68313885,  1.29073295, -1.68665599, -1.71345309])

1

这将基于str对象的表达式转换为Expr对象。

2

这定义了输出为 out EArray 对象。

3

这启动了表达式的评估。

4

这将整个 EArray 读入内存。

考虑到整个操作是在内存之外进行的,可以认为是相当快的,尤其是在标准硬件上执行。作为基准,可以考虑 numexpr 模块的内存性能(也见[Link to Come])。它更快,但并不是很大的优势:

In [200]: import numexpr as ne  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [201]: expr = '3 * sin(out_) + sqrt(abs(out_))'  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [202]: ne.set_num_threads(1)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[202]: 4

In [203]: %time ne.evaluate(expr)[0, :10]  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          CPU times: user 1.72 s, sys: 529 ms, total: 2.25 s
          Wall time: 2.38 s

Out[203]: array([-1.64358578,  0.22567882,  3.31363043,  2.50443549,  4.27413965,
                 -1.41600606, -1.68373023,  4.01921805, -1.68117412, -1.66053597])

In [204]: ne.set_num_threads(4)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[204]: 1

In [205]: %time ne.evaluate(expr)[0, :10]  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)

          CPU times: user 2.29 s, sys: 804 ms, total: 3.09 s
          Wall time: 1.56 s

Out[205]: array([-1.64358578,  0.22567882,  3.31363043,  2.50443549,  4.27413965,
                 -1.41600606, -1.68373023,  4.01921805, -1.68117412, -1.66053597])

In [206]: h5.close()

In [207]: !rm -f $path*

1

导入用于 内存中 评估数值表达式的模块。

2

数值表达式作为 str 对象。

3

将线程数设置为仅一个。

4

使用一个线程在内存中评估数值表达式。

5

将线程数设置为四。

6

使用四个线程在内存中评估数值表达式。

通过 TsTables 进行 I/O 操作。

TsTables 包使用 PyTables 构建了一个高性能的时间序列数据存储。主要的使用场景是“一次写入,多次检索”。这是金融分析中的典型场景,因为数据是在市场上创建的,可能是实时或异步检索,并存储在磁盘上以供以后使用。这样的使用场景可能是一个较大的交易策略回测程序,需要反复使用历史金融时间序列的不同子集。因此,数据检索速度很重要。

示例数据

通常情况下,首先生成一些足够大的示例数据集,以说明 TsTables 的好处。以下代码基于几何布朗运动的模拟生成了三个相当长的金融时间序列(见[Link to Come])。

In [208]: no = 5000000  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
          co = 3  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
          interval = 1. / (12 * 30 * 24 * 60)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
          vol = 0.2  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [209]: %%time
          rn = np.random.standard_normal((no, co))  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
          rn[0] = 0.0  ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
          paths = 100 * np.exp(np.cumsum(-0.5 * vol ** 2 * interval +
                  vol * np.sqrt(interval) * rn, axis=0))  ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
          paths[0] = 100  ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)

          CPU times: user 932 ms, sys: 204 ms, total: 1.14 s
          Wall time: 1.2 s

1

时间步数。

2

时间序列的数量。

3

年份间隔作为年分数。

4

波动率。

5

标准正态分布的随机数。

6

初始随机数设为 0。

7

基于 Euler 离散化的模拟。

8

将路径的初始值设为 100。

由于TsTablespandas DataFrame对象很好地配合,因此数据被转换为这样的对象(另见图 9-7)。

In [210]: dr = pd.date_range('2019-1-1', periods=no, freq='1s')

In [211]: dr[-6:]
Out[211]: DatetimeIndex(['2019-02-27 20:53:14', '2019-02-27 20:53:15',
                         '2019-02-27 20:53:16', '2019-02-27 20:53:17',
                         '2019-02-27 20:53:18', '2019-02-27 20:53:19'],
                        dtype='datetime64[ns]', freq='S')

In [212]: df = pd.DataFrame(paths, index=dr, columns=['ts1', 'ts2', 'ts3'])

In [213]: df.info()

          <class 'pandas.core.frame.DataFrame'>
          DatetimeIndex: 5000000 entries, 2019-01-01 00:00:00 to 2019-02-27 20:53:19
          Freq: S
          Data columns (total 3 columns):
          ts1    float64
          ts2    float64
          ts3    float64
          dtypes: float64(3)
          memory usage: 152.6 MB

In [214]: df.head()
Out[214]:                             ts1         ts2         ts3
          2019-01-01 00:00:00  100.000000  100.000000  100.000000
          2019-01-01 00:00:01  100.018443   99.966644   99.998255
          2019-01-01 00:00:02  100.069023  100.004420   99.986646
          2019-01-01 00:00:03  100.086757  100.000246   99.992042
          2019-01-01 00:00:04  100.105448  100.036033   99.950618

In [215]: df[::100000].plot(figsize=(10, 6));
          plt.savefig('../../images/ch09/io_07.png')

io 07

图 9-7. 金融时间序列的选定数据点

数据存储

TsTables基于特定的基于块的结构存储金融时间序列数据,该结构允许根据某个时间间隔快速检索任意数据子集。为此,该软件包将create_ts()函数添加到PyTables中。以下代码使用了来自PyTablesclass基于描述方法,基于tb.IsDescription类。

In [216]: import tstables as tstab

In [217]: class ts_desc(tb.IsDescription):
              timestamp = tb.Int64Col(pos=0)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
              ts1 = tb.Float64Col(pos=1)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
              ts2 = tb.Float64Col(pos=2)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
              ts3 = tb.Float64Col(pos=3)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [218]: h5 = tb.open_file(path + 'tstab.h5', 'w')  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

In [219]: ts = h5.create_ts('/', 'ts', ts_desc)  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

In [220]: %time ts.append(df)  ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)

          CPU times: user 692 ms, sys: 403 ms, total: 1.1 s
          Wall time: 1.12 s

In [221]: type(ts)
Out[221]: tstables.tstable.TsTable

In [222]: ls -n $path

          total 306720
          -rw-r--r--  1 501  20  157037368 Jan 18 10:07 tstab.h5

1

时间戳的列。

2

存储数字数据的列。

3

为写入(w)打开HDF5数据库文件。

4

基于ts_desc对象创建TsTable对象。

5

DataFrame对象中的数据附加到TsTable对象。

数据检索

使用TsTables编写数据显然非常快,即使与硬件有关。对数据的块的读取也是如此。方便的是,TaTables返回一个DataFrame对象(另见图 9-8)。

In [223]: read_start_dt = dt.datetime(2019, 2, 1, 0, 0)  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
          read_end_dt = dt.datetime(2019, 2, 5, 23, 59)  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)

In [224]: %time rows = ts.read_range(read_start_dt, read_end_dt)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)

          CPU times: user 80.5 ms, sys: 36.2 ms, total: 117 ms
          Wall time: 116 ms

In [225]: rows.info()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          <class 'pandas.core.frame.DataFrame'>
          DatetimeIndex: 431941 entries, 2019-02-01 00:00:00 to 2019-02-05 23:59:00
          Data columns (total 3 columns):
          ts1    431941 non-null float64
          ts2    431941 non-null float64
          ts3    431941 non-null float64
          dtypes: float64(3)
          memory usage: 13.2 MB

In [226]: rows.head()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[226]:                            ts1        ts2         ts3
          2019-02-01 00:00:00  52.063640  40.474580  217.324713
          2019-02-01 00:00:01  52.087455  40.471911  217.250070
          2019-02-01 00:00:02  52.084808  40.458013  217.228712
          2019-02-01 00:00:03  52.073536  40.451408  217.302912
          2019-02-01 00:00:04  52.056133  40.450951  217.207481

In [227]: h5.close()

In [228]: (rows[::500] / rows.iloc[0]).plot(figsize=(10, 6));
          plt.savefig('../../images/ch09/io_08.png')

1

时间间隔的开始时间。

2

时间间隔的结束时间。

3

函数ts.read_range()返回时间间隔的DataFrame对象。

4

DataFrame对象有几十万行数据。

io 08

图 9-8. 金融时间序列的特定时间间隔(归一化)

为了更好地说明基于TsTables的数据检索性能,考虑以下基准,该基准检索由三天的一秒钟柱状图组成的 100 个数据块。检索包含 345,600 行数据的DataFrame仅需不到十分之一秒。

In [229]: import random

In [230]: h5 = tb.open_file(path + 'tstab.h5', 'r')

In [231]: ts = h5.root.ts._f_get_timeseries()  ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)

In [235]: %%time
          for _ in range(100):  ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
              d = random.randint(1, 24)  ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
              read_start_dt = dt.datetime(2019, 2, d, 0, 0, 0)
              read_end_dt = dt.datetime(2019, 2, d + 3, 23, 59, 59)
              rows = ts.read_range(read_start_dt, read_end_dt)

          CPU times: user 3.51 s, sys: 1.03 s, total: 4.55 s
          Wall time: 4.62 s

In [233]: rows.info()  ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)

          <class 'pandas.core.frame.DataFrame'>
          DatetimeIndex: 431941 entries, 2019-02-01 00:00:00 to 2019-02-05 23:59:00
          Data columns (total 3 columns):
          ts1    431941 non-null float64
          ts2    431941 non-null float64
          ts3    431941 non-null float64
          dtypes: float64(3)
          memory usage: 13.2 MB

In [234]: !rm $path/tstab.h5

1

连接到TsTable对象。

2

数据检索重复多次。

3

起始日值被随机化。

4

最后检索到的DataFrame对象。

结论

基于SQL或关系数据库的方法在处理展示了许多单个对象/表之间关系的复杂数据结构时具有优势。在某些情况下,这可能会使它们在纯NumPy ndarraypandas DataFrame方法上的性能劣势成为合理。

金融或一般科学中的许多应用领域可以通过主要基于数组的数据建模方法取得成功。在这些情况下,通过利用原生NumPy的 I/O 功能、NumPyPyTables功能的组合,或通过HDF5-based 存储的pandas方法,可以实现巨大的性能提升。当处理大型(金融)时间序列数据集时,尤其是在“一次写入,多次检索”的场景中,TsTables特别有用。

虽然最近的一个趋势是使用基于商品硬件的大量计算节点组成的云解决方案,特别是在金融背景下,人们应该仔细考虑哪种硬件架构最适合分析需求。微软的一项研究对这个问题有所启发:

我们声称一个“扩展”服务器可以处理这些工作中的每一个,并且在性能、成本、功耗和服务器密度等方面与集群一样好,甚至更好。

Appuswamy 等人(2013 年)

从事数据分析的公司、研究机构等应该首先分析一般情况下必须完成的具体任务,然后根据以下方面的硬件/软件架构做出决策:

扩展

使用具有标准 CPU 和相对较低内存的许多商品节点的集群

扩展

使用一台或多台强大的服务器,配备多核 CPU,可能还有 GPU 甚至 TPU,当机器学习和深度学习发挥作用时,并拥有大量内存。

扩展硬件规模并应用适当的实现方法可能会显著影响性能。下一章将更多地涉及性能。

进一步阅读

本章开头引用的论文以及“结论”部分是一篇不错的文章,也是思考金融分析硬件架构的良好起点:

通常情况下,网络提供了许多有关本章涵盖主题的宝贵资源:

¹ 这里,我们不区分不同级别的 RAM 和处理器缓存。当前内存架构的最佳使用是一个独立的主题。

² 要了解 Python 可用的数据库连接器的概述,请访问https://wiki.python.org/moin/DatabaseInterfaces。与直接使用关系型数据库不同,对象关系映射器,例如SQLAlchemy,通常非常有用。它们引入了一个抽象层,允许更加 Pythonic、面向对象的代码。它们还允许更容易地在后端将一个关系型数据库更换为另一个。

³ 请参阅https://www.sqlite.org/lang.html以了解 SQLite3 语言方言的概述。

⁴ 请参阅http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html

⁵ 许多其他数据库需要服务器-客户端架构。对于交互式数据和金融分析,基于文件的数据库在一般情况下会更加方便,也足够满足大多数目的。

标签:raw,Python,docs,--,重译,https,master,com,png
From: https://www.cnblogs.com/apachecn/p/18239950

相关文章

  • Python-金融编程第二版-GPT-重译--三-
    Python金融编程第二版(GPT重译)(三)原文:annas-archive.org/md5/d2f94efd019a2e2cb5c4fa9f260d63c译者:飞龙协议:CCBY-NC-SA4.0第六章:面向对象编程软件工程的目的是控制复杂性,而不是创建它。PamelaZave介绍面向对象编程(OOP)是当今最流行的编程范式之一。正确使用时,它与过......
  • Python-金融编程第二版-GPT-重译--二-
    Python金融编程第二版(GPT重译)(二)原文:annas-archive.org/md5/d2f94efd019a2e2cb5c4fa9f260d63c译者:飞龙协议:CCBY-NC-SA4.0第四章:使用NumPy进行数值计算计算机是无用的。它们只能给出答案。巴勃罗·毕加索介绍本章介绍了Python的基本数据类型和数据结构。尽管Py......
  • Ten Tips for Smarter Google Searches (十个更聪明使用 Google 搜索的技巧)
    TenTipsforSmarterGoogleSearches十个更聪明使用Google搜索的技巧 Date:Dec1,2006Articleisprovidedcourtesyof Que.Returntothearticle MostpeopleuseGoogleinaveryinefficientandoftenineffectivemanner.Ifallyoudoisenterafew......
  • Go - flag
     packagemainimport("flag""fmt")funcmain(){var(nameFlag=flag.String("name","Sam","Nameofthepersontosayhelloto")quietFlag=flag.Bool("quiet"......
  • 对象存储服务的回源特性
    为充分提升基础设施相关预算的投资效率,数据安全性,客户的数据可能分布在多套存储中,按照价格、业务场景等,可以划分为如下形式:本地高端存储,支撑生产类业务本地低端存储,支撑归档类业务、分析型业务云端高端存储,支撑生产类业务、分析型业务云端低端存储,支撑归档类业务依据不同的......
  • 密码工程-大素数
    任务详情0.在openEuler(推荐)或Ubuntu或Windows(不推荐)中完成下面任务利大整数库(GMP或者OpenSSL),参考《密码工程》p113伪代码实现GenerateLargePrime函数(10‘)在测试代码中产生一个在范围l=2^255至u=2^256-1内的素数。(5‘)用OpenSSL验证你产生的素数是不是正确(5’)提交......
  • 第二次Blog
    题目集47-17-4答题判题程序-4分数87作者蔡轲单位南昌航空大学设计实现答题程序,模拟一个小型的测试,要求输入题目信息、试卷信息、答题信息、学生信息、删除题目信息,根据输入题目信息中的标准答案判断答题的结果。本题在答题判题程序-3基础上新增的内容统一附加在输出格式......
  • TypeScript算法每日一题:最富有客户的资产总量(1672)
    作者:前端小王hs阿里云社区博客专家/清华大学出版社签约作者✍/CSDN百万访问博主/B站千粉前端up主题库:力扣题目序号:1672(简单)题目:最富有客户的资产总量给你一个mxn的整数网格accounts,其中accounts[i][j]是第i​​​​​​​​​​​​位客户在第j家银行托管的资产数......
  • Honor of Kings 2024.06.03 50star (S35) AFK
    HonorofKings2024.06.0350star(S35)AFK来个赛季S35总结吧,这个赛季结束以后,可能要和【魔兽世界】一样AFK了,手游来说肯定没法子和WOW相比,干啥都是有队友才好玩。单排荣耀王者,基本都是肉肉队伍我玩的基本都是肉,爆发强的英雄,最好可以是万金油,免控那种无脑我不怎么玩......
  • minio的一个基础使用案例:用户头像上传
    文章目录一、minio下载安装(Windows)二、案例需求分析三、后端接口开发一、minio下载安装(Windows)1.下载minio服务端和客户端minio下载地址2.手动搭建目录/minio/binmc.exeminio.exe/data/logs手动创建minio应用程序目录,如......