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

本文涉及的产品
云原生数据库 PolarDB MySQL 版,Serverless 5000PCU 100GB
云原生数据库 PolarDB PostgreSQL 版,企业版 4核16GB
推荐场景:
HTAP混合负载
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
简介: Python 金融编程第二版(四)

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


基于 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  # ①
In [185]: ear = h5.create_earray('/', 'ear',  # ②
                                atom=tb.Float64Atom(),  # ③
                                shape=(0, n))  # ④
In [186]: type(ear)
Out[186]: tables.earray.EArray
In [187]: rand = np.random.standard_normal((n, n))  # ⑤
          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)  # ⑥
          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

这定义了固定的列数。

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

单个值的原子dtype对象。

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

具有随机数的ndarray对象…

… 多次附加。

对于不会导致聚合的内存外计算,需要另一个相同形状(大小)的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))')  # ①
In [194]: expr.set_output(out, append_mode=True)  # ②
In [195]: %time expr.eval()  # ③
          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()  # ④
          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])

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

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

这启动了表达式的评估。

这将整个 EArray 读入内存。

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

In [200]: import numexpr as ne  # ①
In [201]: expr = '3 * sin(out_) + sqrt(abs(out_))'  # ②
In [202]: ne.set_num_threads(1)  # ③
Out[202]: 4
In [203]: %time ne.evaluate(expr)[0, :10]  # ④
          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)  # ⑤
Out[204]: 1
In [205]: %time ne.evaluate(expr)[0, :10]  # ⑥
          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*

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

数值表达式作为 str 对象。

将线程数设置为仅一个。

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

将线程数设置为四。

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

通过 TsTables 进行 I/O 操作。

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

示例数据

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

In [208]: no = 5000000  # ①
          co = 3  # ②
          interval = 1. / (12 * 30 * 24 * 60)  # ③
          vol = 0.2  # ④
In [209]: %%time
          rn = np.random.standard_normal((no, co))  # ⑤
          rn[0] = 0.0  # ⑥
          paths = 100 * np.exp(np.cumsum(-0.5 * vol ** 2 * interval +
                  vol * np.sqrt(interval) * rn, axis=0))  # ⑦
          paths[0] = 100  # ⑧
          CPU times: user 932 ms, sys: 204 ms, total: 1.14 s
          Wall time: 1.2 s

时间步数。

时间序列的数量。

年份间隔作为年分数。

波动率。

标准正态分布的随机数。

初始随机数设为 0。

基于 Euler 离散化的模拟。

将路径的初始值设为 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')


图 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)  # ①
              ts1 = tb.Float64Col(pos=1)  # ②
              ts2 = tb.Float64Col(pos=2)  # ②
              ts3 = tb.Float64Col(pos=3)  # ②
In [218]: h5 = tb.open_file(path + 'tstab.h5', 'w')  # ③
In [219]: ts = h5.create_ts('/', 'ts', ts_desc)  # ④
In [220]: %time ts.append(df)  # ⑤
          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

时间戳的列。

存储数字数据的列。

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

基于ts_desc对象创建TsTable对象。

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

数据检索

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

In [223]: read_start_dt = dt.datetime(2019, 2, 1, 0, 0)  # ①
          read_end_dt = dt.datetime(2019, 2, 5, 23, 59)  # ②
In [224]: %time rows = ts.read_range(read_start_dt, read_end_dt)  # ③
          CPU times: user 80.5 ms, sys: 36.2 ms, total: 117 ms
          Wall time: 116 ms
In [225]: rows.info()  # ④
          <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()  # ④
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')

时间间隔的开始时间。

时间间隔的结束时间。

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

DataFrame对象有几十万行数据。


图 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()  # ①
In [235]: %%time
          for _ in range(100):  # ②
              d = random.randint(1, 24)  # ③
              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()  # ④
          <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

连接到TsTable对象。

数据检索重复多次。

起始日值被随机化。

最后检索到的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

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

.randint(1, 24) # ③

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() # ④

<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

连接到`TsTable`对象。
数据检索重复多次。
起始日值被随机化。
最后检索到的`DataFrame`对象。
# 结论
基于`SQL`或关系数据库的方法在处理展示了许多单个对象/表之间关系的复杂数据结构时具有优势。在某些情况下,这可能会使它们在纯`NumPy` `ndarray`或`pandas` `DataFrame`方法上的性能劣势成为合理。
金融或一般科学中的许多应用领域可以通过主要基于数组的数据建模方法取得成功。在这些情况下,通过利用原生`NumPy`的 I/O 功能、`NumPy`和`PyTables`功能的组合,或通过`HDF5`-based 存储的`pandas`方法,可以实现巨大的性能提升。当处理大型(金融)时间序列数据集时,尤其是在“一次写入,多次检索”的场景中,`TsTables`特别有用。
虽然最近的一个趋势是使用基于商品硬件的大量计算节点组成的云解决方案,特别是在金融背景下,人们应该仔细考虑哪种硬件架构最适合分析需求。微软的一项研究对这个问题有所启发:
> 我们声称一个“扩展”服务器可以处理这些工作中的每一个,并且在性能、成本、功耗和服务器密度等方面与集群一样好,甚至更好。
> 
> Appuswamy 等人(2013 年)
从事数据分析的公司、研究机构等应该首先分析一般情况下必须完成的具体任务,然后根据以下方面的硬件/软件架构做出决策:
扩展
使用具有标准 CPU 和相对较低内存的许多商品节点的集群
扩展
使用一台或多台强大的服务器,配备多核 CPU,可能还有 GPU 甚至 TPU,当机器学习和深度学习发挥作用时,并拥有大量内存。
扩展硬件规模并应用适当的实现方法可能会显著影响性能。下一章将更多地涉及性能。
# 进一步阅读
本章开头引用的论文以及“结论”部分是一篇不错的文章,也是思考金融分析硬件架构的良好起点:
+   Appuswamy,Raja 等人(2013 年):“`没有人因为购买集群而被解雇。`”微软研究,英格兰剑桥,[*http://research.microsoft.com/apps/pubs/default.aspx?id=179615*](http://research.microsoft.com/apps/pubs/default.aspx?id=179615)。
通常情况下,网络提供了许多有关本章涵盖主题的宝贵资源:
+   对于使用`pickle`对 Python 对象进行序列化,请参阅文档:[*http://docs.python.org/3/library/pickle.html*](http://docs.python.org/3/library/pickle.html)。
+   关于`NumPy`的 I/O 功能概述可在`SciPy`网站上找到:[*http://docs.scipy.org/doc/numpy/reference/routines.io.html*](http://docs.scipy.org/doc/numpy/reference/routines.io.html)。
+   对于使用`pandas`进行 I/O,请参阅在线文档中的相应部分:[*http://pandas.pydata.org/pandas-docs/stable/io.html*](http://pandas.pydata.org/pandas-docs/stable/io.html)。
+   `PyTables`首页提供了教程和详细文档:[*http://www.pytables.org*](http://www.pytables.org)。
+   `TsTables` 的 Github 页面位于[*https://github.com/afiedler/tstables*](https://github.com/afiedler/tstables)。
¹ 这里,我们不区分不同级别的 RAM 和处理器缓存。当前内存架构的最佳使用是一个独立的主题。
² 要了解 Python 可用的数据库连接器的概述,请访问[*https://wiki.python.org/moin/DatabaseInterfaces*](https://wiki.python.org/moin/DatabaseInterfaces)。与直接使用关系型数据库不同,对象关系映射器,例如[SQLAlchemy](https://www.sqlalchemy.org/),通常非常有用。它们引入了一个抽象层,允许更加 Pythonic、面向对象的代码。它们还允许更容易地在后端将一个关系型数据库更换为另一个。
³ 请参阅[*https://www.sqlite.org/lang.html*](https://www.sqlite.org/lang.html)以了解 `SQLite3` 语言方言的概述。
⁴ 请参阅[*http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html*](http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html)。
⁵ 许多其他数据库需要服务器-客户端架构。对于交互式数据和金融分析,基于文件的数据库在一般情况下会更加方便,也足够满足大多数目的。
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
2天前
|
存储 分布式计算 数据可视化
Python 金融编程第二版(四)(2)
Python 金融编程第二版(四)
10 0
|
2天前
|
存储 SQL 数据可视化
Python 金融编程第二版(四)(1)
Python 金融编程第二版(四)
9 0
|
2天前
|
数据可视化 Python
Python 金融编程第二版(三)(4)
Python 金融编程第二版(三)
12 2
|
2天前
|
存储 数据可视化 API
Python 金融编程第二版(三)(5)
Python 金融编程第二版(三)
8 1
|
2天前
|
存储 SQL 数据库
Python 金融编程第二版(四)(4)
Python 金融编程第二版(四)
8 0
|
2天前
|
SQL 存储 数据库
Python 金融编程第二版(四)(3)
Python 金融编程第二版(四)
8 0
|
2天前
|
存储 SQL 数据可视化
Python 金融编程第二版(二)(4)
Python 金融编程第二版(二)
10 1
|
2天前
|
数据挖掘 索引 Python
Python 金融编程第二版(二)(5)
Python 金融编程第二版(二)
7 0
|
2天前
|
存储 索引 Python
Python 金融编程第二版(二)(1)
Python 金融编程第二版(二)
9 2
|
2天前
|
存储 算法 数据建模
Python 金融编程第二版(一)(5)
Python 金融编程第二版(一)
13 2