基于时间轴数据合成 bar的位置计算算法实现

前言: Pyhton 结束循环的方法是抛出 StopIteration 异常,涉及到异常栈操作,可见其性能非常的低。
当采用多重循环且循环基数巨大的算法时。 额,喝完一杯咖啡之后,还没有任何输出。

当你试图运行一下测试Code时, 你会哭的:

def func7(loop0, loop1):
    for i in range(loop0):
        for j in range(loop1):
            a=0
>>> %timeit func7(100000,100000)

在我的机器上运行同样的 C 程序用时 19~20 秒之间。 好吧,我承认,这样写是很愚蠢的。
但是我们在设计程序时,未预期参数值大小的情况下就会写成类似代码。当同事运行你的代码时,肯定会皱眉的。



场景导入

闲话少说,来看看最近我遇到的一个场景。当我们使用股票相关软件时,就会有分时图:1min, 5min, 15min 1day , 方便我们对不同频率的数据查看与分析。

合成bar是基于时间轴的,当数据不足够合成的一根完整 bar 时。我们采用已有的数据合成,直至合成一根完整的bar。保证每一个时间点都对应一个合成的bar。 若等数据完整才合成时一根bar,可能会导致结尾的时间段为空。

假设已经拥有 1min 和 5min 的时间轴,现在要根据 1min 的数据来合成 5min 的bar, 我们要做的就是在时间轴计算处合成的位置。

分析

基于时间轴的数据合成。 请看以下数据:

1min5min
9:019:05
9:029:10
9:03
9:04
9:05
9:06
9:07
9:08

合成9:02之间连续bar的数据包括[9:01,9:02]
合成9:05之间连续bar的数据包括[9:01, 9:02, 9:03, 9:04, 9:05]
合成9:08之间连续bar的数据包括[9:06, 9:07, 9:08]
合成9:10之间连续bar的数据包括[9:06, 9:07, 9:08]

已知数据是按照时间排序的,若合成每一根 bar 的起始位置确定了,就可以分别合成bar了。所以我们单独抽出一个函数计算 begin_pos 和 end_pos。 有的人可能会问, 为什么不一次性合成呢,因为还需在其它地方使用这两个输出。

寻找合成bar的位置就转化为 高频向低频左插入位置查找, 理解这一点非常的重要。

实现

数据有序时寻找插入位置的算法有:

bisect.bisect_left(a, x, lo=0, hi=len(a))

Locate the insertion point for x in a to maintain sorted order. The parameters lo and hi may be used to specify a subset of the list which should be considered; by default the entire list is used. If x is already present in a, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use as the first parameter to list.insert() assuming that a is already sorted.

The returned insertion point i partitions the array a into two halves so that all(val < x for val in a[lo:i]) for the left side and all(val >= x for val in a[i:hi]) for the right side.

bisect.bisect_left


numpy.searchsorted(a, v, side=’left’, sorter=None)

Find indices where elements should be inserted to maintain order.
Find the indices into a sorted array a such that, if the corresponding elements in v were inserted before the
indices, the order of a would be preserved.

numpy.searchsorted


ok 知道关键算法之后,看看实现过程

import numpy as np
from bisect import bisect_left

def calc_begin_end_pos(high_freq_tl, low_freq_tl): 
    """ 基于时间轴,计算时间合成 bar 的位置

    :param high_freq_tl: numpy.ndarray, 高频时间轴 eg: 1min 
    :param low_freq_tl: numpy.ndarray, 低频时间轴 eg: 2month

    :return: begin_pos, end_pos
    """
    # from bisect import bisect_left
    # c = [bisect_left(low_freq_tl, i) for i in high_freq_tl]
    c = np.searchsorted(low_freq_tl, high_freq_tl, side='left')
    a,b = np.unique(c, return_counts=True)
    d = dict(zip(a, np.add.accumulate(b)-b[0]))

    begin_pos = np.array([d[i] for i in c])
    end_pos = np.arange(high_freq_tl.size)

    return begin_pos, end_pos

此代码比较含蓄

d = dict(zip(a, np.add.accumulate(b)-b[0]))

a 是 c 中 唯一的且按照顺序排好的值,b 是 a 在 c 中的个数。
先对 b 做了一次累加求和,然后减去 b[0] 保证从 0 开始

这样就构造了一个词典,每个值在时间轴对应的开始计算的起始位置。


方便计算,一般会把时间点转为整型或者浮点型。
假装这是时间轴

h = np.arange(1,300001)
l = np.arange(0,300005, 5)

calc_begin_end_pos(h, l)

赶紧测试下性能吧,这个任务就交给读者您了, 笔者是测过了的!

总结

之前采用一个比较 Low 的方法,主要思路是将 M:N 计算位置转为 M:1:N的算法,1代表一个中间变量,没计算一个位置要经过双重循环,当高低频跨度较小时,无谓的循环较少。跨度大时,每比较一个时间点跨度范围的线性计算量。总之,应对场景开发与优化是最好不过了。

加好友

如果你和我有共同爱好,加好友一起学习吧!
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值