0

我有(或实际上正在开发)一个程序(一些配对交易策略),它执行以下操作:

  1. 检索位于 postgres 数据库中的较大数据(财务数据:约 100 只股票的日期时间指数和股票价格)的子集。
  2. 清理数据(放弃 >30% NaN 的股票)并计算回报和指数(相对于每只股票的第一次观察)
  3. 找到股票对的所有组合,计算相关性(实际上是一些类似的度量,但在这里太重要了)
  4. 将相关性最高的对排名到最低或仅选择相关性 > 定义阈值的对,即 0.9
  5. 检查每一对的协整,双向!并根据它们的测试值对它们进行排名
  6. 选择要交易的前 n 个,即 10 对,并根据移动平均线和标准计算一些信号
  7. 检索“样本外”窗口并交易股票
  8. 在日志中记录每天的回报(即 5 天内)
  9. 计算一些统计数据

在这 9 个步骤之后,重新开始,检索另一个训练窗口并执行分析......

我的方法是 - 如果你看到更好的东西,请更正:
1. 从程序中提取尽可能多的功能
2. 通过多个培训和交易窗口循环步骤 1-9

和我由此产生的问题(受到论坛中许多线程的启发,即如何使您的 python 代码运行得更快

  • 如何确定我的代码的哪一部分可以并行运行?
  • 不知何故,这对我来说似乎微不足道:应用什么技术来“重写”代码,以便它可以使用多处理?
  • 也不总是很明显:将循环重写为函数,总是要查看任何特定的角度?
  • numba.jit()“ ”所有功能有意义吗?
  • 我应该将数据的所有格式更改为float64? 会发生什么不利情况?(目前它们是“标准”数字)
  • 是否有任何清单可以让我看到何时可以对循环进行矢量化?

请为许多 - 相当概念性的 - 问题道歉,但我认为,如果我能理解以上所有“痛点”,这将真正提高我的“逻辑”理解,并且对新的 python 加入者也非常有益。

4

1 回答 1

2

重要的问题可以产生但简化的答案:

性能提升需要多多关注,~ [man*decades]...

简而言之,不要期望阅读一些示例并成为这方面的专家。

第一:糟糕的算法永远不会仅仅通过一些(半)自动转换来改进。智能重构可能会在原生纯 python 代码(下面的示例)中提高 100% 的性能,但是精心设计的代码,匹配代码执行设备的接近硅的特性将从其他角度显示这种努力,作为上述〜+ 100%性能繁荣的代码改进,一旦转换为jit编译单元,性能几乎相同。这意味着,在精心设计的高性能代码中,过早的优化可能会变得毫无用处。至少你已经被警告过了。

python是一个很棒的工具,我喜欢它几乎无限精确的数学。然而,与计算机科学家和粉丝们更愿意承认的相比,同时兼顾极致精度和极致性能似乎更接近海森堡原理。只是花了很长时间在做这两件事,才能把它紧凑成几句话的段落。


numba.jit()Q4:所有功能都“”有意义吗?

numba是稳定代码库的好工具,所以让我们从它开始:

numba.jit()自动化转换的低挂果实很容易用工具挑选出来。基准测试将帮助您减少代码不需要携带的几乎所有开销。

如果依赖于代码元素,那么仍在发展的numba.jit()代码转换器无法转码,那么您就完成了。numba从最初的版本开始就使用它,{ list | dict | class | ... }对于任何进一步梦想将代码(自动)转换为更接近硅片的梦想都是杀手。此外,所有引用的函数都必须能够获得numba.jit(),所以几乎忘记了使用一些易于翻译的高级import代码库numba,如果它们的原始代码没有系统地设计numba的话。


Q5:我应该将我的数据的所有格式都更改为float64?
会发生什么不利情况?(目前它们是“标准”数字)

float32除了 -domain 中内存占用的静态大小减半之外[SPACE],还有一些主要缺点。

一些模块(通常是那些继承自 FORTRAN 数值求解器和类似遗产的模块)会自动将任何外部传递的数据转换为其本地float64副本(因此两者[SPACE][TIME]惩罚都会增长,超出您的控制范围)。

最好期望在 -domain 中增加代码执行惩罚[TIME],因为未对齐的单元边界很昂贵(这深入到代码的汇编级别以及 CPU 指令集和缓存层次结构以掌握该级别的所有细节) .

在下面的基准测试中,执行速度可能会慢近 3 倍。float32


Q6:是否有任何清单可以让我看到何时可以对循环进行矢量化?

自动矢量化转换器不亚于诺贝尔奖的目标。

一些调整可以由聪明和方便的设计师完成。对于任何更复杂的操作,不要指望在这个领域有任何容易实现的成果,而不是微不足道的广播或一些易于设计的 numpy striding trics。

专业的代码转换包很昂贵(有人必须支付在许多 [人*年] 中收集的专业知识),并且通常只有在大规模部署时才能调整其投资回报率。


Q1:如何确定我的代码的哪一部分可以并行运行?

您很高兴不必将代码设计为以真正的方式运行[PARALLEL],而是以“公正”的[CONCURRENT]方式运行。如果有人说并行,请检查系统是否确实需要满足真正的[PARALLEL]进程调度的所有条件,在大多数情况下,“公正”的[CONCURRENT]调度正是演讲者所要求的(细节超出了本文的范围) . Python GIL 分步锁定可防止任何基于子流程的工作流拆分,但这是有代价的,因此请收获您的处理独立性,因为这将奖励您的意图,而不会支付任何额外开销附加成本的惩罚,如果违背开销严格的阿姆达尔定律的规则。


基准,基准,基准:

实际的成本/效果比必须在相应版本的 python、numba、CPU / 缓存架构上进行验证,因此基准测试是确认任何改进的唯一方法(成本)。

下面的例子显示了一个简单的指数移动平均函数的保存,以几种或多或少的智能方式实现。

def plain_EMA_fromPrice(                   N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice_float32(           N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float32_nopython(  N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice_float64(           N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice_float64_nopython(  N_period, aPriceVECTOR ):
    ...    


def plain_EMA_fromPrice2(                  N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )" )
def numba_EMA_fromPrice2_float32(          N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float32[:]( int32, float32[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float32_nopython( N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )" )
def numba_EMA_fromPrice2_float64(          N_period, aPriceVECTOR ):
    ...
@numba.jit(                                                         "float64[:]( int64, float64[:] )", nopython = True, nogil = True )
def numba_EMA_fromPrice2_float64_nopython( N_period, aPriceVECTOR ):
    ...

通过代码重构和内存对齐来
提高性能,接下来是:710 [us] -> 160 [us]
-> 12 ~ 17 [us]numba.jit()

>>> aPV_32 = np.arange( 100, dtype = np.float32 )
>>> aPV_64 = np.arange( 100, dtype = np.float32 )

>>> aClk.start();_ = plain_EMA_fromPrice(                  18, aPV_32 );aClk.stop()
715L
723L
712L
722L
975L

>>> aClk.start();_ = plain_EMA_fromPrice(                  18, aPV_64 );aClk.stop()
220L
219L
216L
193L
212L
217L
218L
215L
217L
217L

>>> aClk.start();_ = numba_EMA_fromPrice_float32(          18, aPV_32 );aClk.stop()
199L
15L
16L
16L
17L
13L
16L
12L

>>> aClk.start();_ = numba_EMA_fromPrice_float64(          18, aPV_64 );aClk.stop()
170L
16L
16L
16L
18L
14L
16L
14L
17L

>>> aClk.start();_ = numba_EMA_fromPrice_float64_nopython( 18, aPV_64 );aClk.stop()
16L
17L
17L
16L
12L
16L
14L
16L
15L

>>> aClk.start();_ = plain_EMA_fromPrice2(                 18, aPV_32 );aClk.stop()
648L
654L
662L
648L
647L

>>> aClk.start();_ = plain_EMA_fromPrice2(                 18, aPV_64 );aClk.stop()
165L
166L
162L
162L
162L
163L
162L
162L

>>> aClk.start();_ = numba_EMA_fromPrice2_float32(         18, aPV_32 );aClk.stop()
43L
45L
43L
41L
41L
42L

>>> aClk.start();_ = numba_EMA_fromPrice2_float64(         18, aPV_64 );aClk.stop()
17L
16L
15L
17L
17L
17L
12L

>>> aClk.start();_ = numba_EMA_fromPrice2_float64_nopython( 18, aPV_64 );aClk.stop()
16L
15L
15L
14L
17L
15L
于 2018-03-31T12:24:04.030 回答