Vespa314 / chan.py

开放式的缠论python实现框架,支持形态学/动力学买卖点分析计算,多级别K线联立,区间套策略,可视化绘图,多种数据接入,策略开发,交易系统对接;
MIT License
768 stars 280 forks source link

当数据量越来越大时,性能成指数式下降,附测试报告 #59

Closed appleman4000 closed 1 week ago

appleman4000 commented 2 weeks ago

report.zip cal_seg和CBSPointList.cal这两个方法,占用了整个耗时的75%以上,具体函数见附件测试报告 可能的原因是,但喂给新K线时,都是全量重新计算笔和线段、线段的线段的对应关系 1、如果新k线,没有触发末尾笔发生变化,是否就不用调整呢 2、如果末尾笔发生变化,可能会触发末尾线段、末尾线段的线段,中枢等连锁触发计算,不应该把所有笔和线段重新遍历一遍(时间复杂度为O(n2))

Vespa314 commented 2 weeks ago

如果新k线,没有触发末尾笔发生变化,是否就不用调整呢 ———————————————————————————————— 就是这样实现的

cal_seg计算是最复杂的,但只做增量计算,可以看下随着数量的增加,它的耗时增加是o(?) CBSPointList.cal这个计算每次计算都有一些涉及到全量计算,这个是已知问题,早期糟糕的设计导致的,调整的话除了比较麻烦外,还会对大家当前查找bsp的使用产生影响。(我记一下,空了可以搞个新分支来看看)

appleman4000 commented 2 weeks ago

1724766934317 如图耗时对应代码: def cal_seg(bi_list, seg_list): 总耗时:87秒 seg_list.update(bi_list) : 耗时47秒

计算每一笔属于哪个线段 耗时40秒,时间复杂度O(n2),可以优化这个地方

bi_seg_idx_dict = {}
for seg_idx, seg in enumerate(seg_list):
    for i in range(seg.start_bi.idx, seg.end_bi.idx+1):
        bi_seg_idx_dict[i] = seg_idx
for bi in bi_list:
    bi.set_seg_idx(bi_seg_idx_dict.get(bi.idx, len(seg_list)))  # 找不到的应该都是最后一个线段的
Vespa314 commented 2 weeks ago
def cal_seg(bi_list, seg_list: CSegListComm):
    seg_list.update(bi_list)

    sure_seg_cnt = 0
    if len(seg_list) == 0:
        for bi in bi_list:
            bi.set_seg_idx(0)
        return
    begin_seg: CSeg = seg_list[-1]
    for seg in seg_list[::-1]:
        if seg.is_sure:
            sure_seg_cnt += 1
        else:
            sure_seg_cnt = 0
        begin_seg = seg
        if sure_seg_cnt > 2:
            break

    cur_seg: CSeg = seg_list[-1]
    for bi in bi_list[::-1]:
        if bi.seg_idx is not None and bi.idx < begin_seg.start_bi.idx:
            break
        if bi.idx >= cur_seg.end_bi.idx:
            bi.set_seg_idx(cur_seg.idx+1)
            continue
        if bi.idx >= cur_seg.start_bi.idx:
            bi.set_seg_idx(cur_seg.idx)
            continue
        else:
            assert cur_seg.pre
            cur_seg = cur_seg.pre
            bi.set_seg_idx(cur_seg.idx)

简单搞了一版,可以帮忙一起验一下性能和正确性。

可读性还比较差,确认没问题了再慢慢优化。。

appleman4000 commented 2 weeks ago
 if bi.idx >= cur_seg.end_bi.idx:
            bi.set_seg_idx(cur_seg.idx+1)
            continue
 if bi.idx >= cur_seg.start_bi.idx:
            bi.set_seg_idx(cur_seg.idx)
            continue

更改下先后顺序

if bi.idx >= cur_seg.start_bi.idx:
            bi.set_seg_idx(cur_seg.idx)
            continue
 if bi.idx >= cur_seg.end_bi.idx:
            bi.set_seg_idx(cur_seg.idx+1)
            continue

之后,和之前版本实验结果一致,且耗时几乎等于0,提升效果明显

Vespa314 commented 2 weeks ago

顺序不能改,满足后者的一定会满足前者。

appleman4000 commented 2 weeks ago

顺序不改的 Debug/strategy_demo.py 打印出来的交易记录不一致

Vespa314 commented 2 weeks ago

昨天那个有点问题,顺序还是不能换,第一个大于等于要改成大于:

 if bi.idx > cur_seg.end_bi.idx:
            bi.set_seg_idx(cur_seg.idx+1)
            continue
 if bi.idx >= cur_seg.start_bi.idx:
            bi.set_seg_idx(cur_seg.idx)
            continue
appleman4000 commented 2 weeks ago

可以了,结果一致

appleman4000 commented 2 weeks ago

还有个优化点: 1、CBSPointList.cal 里面有三类买卖点的计算,这个也可以优化 如果我在config里面只配置了1,1p,是不用计算2和3类买卖点的 对于,只生成一类训练样本场景,可以加速 2、CBSPointList.cal 下面两句代码也是全量遍历,是否可以参考上面你写的思路,逆序+退出条件做增量更新呢

        self.lst = [bsp for bsp in self.lst if bsp.klu.idx <= self.last_sure_pos]
        self.bsp_dict = {bsp.bi.get_end_klu().idx: bsp for bsp in self.lst}
Vespa314 commented 2 weeks ago

还有个优化点: 1、CBSPointList.cal 里面有三类买卖点的计算,这个也可以优化 如果我在config里面只配置了1,1p,是不用计算2和3类买卖点的 对于,只生成一类训练样本场景,可以加速 2、CBSPointList.cal 下面两句代码也是全量遍历,是否可以参考上面你写的思路,逆序+退出条件做增量更新呢

        self.lst = [bsp for bsp in self.lst if bsp.klu.idx <= self.last_sure_pos]
        self.bsp_dict = {bsp.bi.get_end_klu().idx: bsp for bsp in self.lst}
  1. 有道理,周末空了搞下。
  2. 不太行,因为是各个类型单独算,往数组里面写的,改造的话需要一个字典实现各个类型单独存一个列表;
appleman4000 commented 2 weeks ago
    def cal(self, bi_list: LINE_LIST_TYPE, seg_list: CSegListComm[LINE_TYPE]):
        # self.lst = [bsp for bsp in self.lst if bsp.klu.idx <= self.last_sure_pos]
        # self.bsp_dict = {bsp.bi.get_end_klu().idx: bsp for bsp in self.lst}
        # self.bsp1_lst = [bsp for bsp in self.bsp1_lst if bsp.klu.idx <= self.last_sure_pos]
        for i in range(len(self.lst) - 1, -1, -1):
            if self.lst[i].klu.idx > self.last_sure_pos:
                if self.lst[i].bi.get_end_klu().idx in self.bsp_dict:
                    del self.bsp_dict[self.lst[i].bi.get_end_klu().idx]
                del self.lst[i]
            else:
                if self.lst[i].bi.get_end_klu().idx not in self.bsp_dict:
                    self.bsp_dict[self.lst[i].bi.get_end_klu().idx] = self.lst[i]
                else:
                    break
        for i in range(len(self.bsp1_lst) - 1, -1, -1):
            if self.bsp1_lst[i].klu.idx > self.last_sure_pos:
                del self.bsp1_lst[i]
            else:
                break

        if BSP_TYPE.T1 in self.config.b_conf.target_types or BSP_TYPE.T1 in self.config.s_conf.target_types or \
                BSP_TYPE.T1P in self.config.b_conf.target_types or BSP_TYPE.T1P in self.config.s_conf.target_types:
            self.cal_seg_bs1point(seg_list, bi_list)
        if BSP_TYPE.T2 in self.config.b_conf.target_types or BSP_TYPE.T2 in self.config.s_conf.target_types or \
                BSP_TYPE.T2S in self.config.b_conf.target_types or BSP_TYPE.T2S in self.config.s_conf.target_types:
            self.cal_seg_bs2point(seg_list, bi_list)
        if BSP_TYPE.T3A in self.config.b_conf.target_types or BSP_TYPE.T3A in self.config.s_conf.target_types or \
                BSP_TYPE.T3B in self.config.b_conf.target_types or BSP_TYPE.T3B in self.config.s_conf.target_types:
            self.cal_seg_bs3point(seg_list, bi_list)

        self.update_last_pos(seg_list)

我修改了一版,你看下是否可行

Vespa314 commented 1 week ago

1类是无论如何要算的; 2,3类由于可以配置只算2买不算2卖这种,故不适合在cal中实现;

参见 commit:https://github.com/Vespa314/chan.py/commit/cfc25b053dbe9dbdbf7b6224b81631ccb6c75dbf