chanlun/indicators/ta.py 接口兼容 talib.abstract,实现代码实际用到的 SMA/MA/EMA/RSI/ATR/MACD/BBANDS;chanlun/pipeline/resample.py 替代 technical.util.resample_to_interval。调用点只改 import,逻辑未动。 暖机长度与平滑种子按 TA-Lib 的约定实现,差一根 K 线就会让下游所有 笔/线段/中枢整体位移。其中 MACD 需特别处理:TA-Lib 让快慢两条 EMA 在同一根 K 线出首值,因而快线的种子取 x[slow-fast:slow] 的均值,而非 从 fastperiod-1 一路递推——两者在百元价位上相差约 0.17。 BBANDS 是有意的分歧:TA-Lib 用 sumsq/n - mean² 求方差,短窗口远离零 时灾难性抵消(timeperiod=2 误差 8.7e-7),本实现用 rolling std,对 50 位精度基准误差为 0。项目实际使用的周期两者一致到 1e-10。 顺带清理 12 个文件中 16 处从未调用的 talib/technical 导入。 验证:9440 组随机对拨;真实 K 线端到端比对 add_indicators 全部 33 个 指标列,NaN 模式一致、MACD 柱符号 100% 相同;屏蔽两个包后 60 个模块 均可导入。新增 test_ta_compat.py 将输出逐 bar 钉在 TA-Lib 上,但该文件 在 TA-Lib 缺失时静默跳过,改动 ta.py 需在装有 TA-Lib 的环境复跑。 Co-authored-by: Cursor <cursoragent@cursor.com>
316 lines
12 KiB
Python
316 lines
12 KiB
Python
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import timedelta
|
|
from decimal import Decimal
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
from pandas import DataFrame
|
|
|
|
from chanlun.core.ChanBI import ChanBI
|
|
from chanlun.core.ChanBIZS import ChanBIZS
|
|
from chanlun.core.ChanBSP import ChanBSP
|
|
from chanlun.core.ChanEnum import (
|
|
Chan_BI_DIR,
|
|
Chan_BSP_DIR,
|
|
Chan_BSP_TYPE,
|
|
Chan_FX_TYPE,
|
|
Chan_K_DIR,
|
|
Chan_KLC_FX,
|
|
Chan_KLC_STATE,
|
|
Chan_KLINE_DIR,
|
|
Chan_KLU_PATTERN,
|
|
Chan_PRICE_TREND,
|
|
Chan_SEG_DIR,
|
|
Chan_ZS_DIR,
|
|
)
|
|
from chanlun.core.ChanKLC import ChanKLC
|
|
from chanlun.core.ChanKLU import ChanKLU
|
|
from chanlun.core.ChanSBI import ChanSBI
|
|
from chanlun.core.ChanSEG import ChanSEG
|
|
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
|
|
from chanlun.indicators.ChanMACD import ChanMACD
|
|
|
|
|
|
class SegBuilderMixin:
|
|
def get_seg_list(self, bi_list):
|
|
seg_list = []
|
|
up_bi_list = []
|
|
down_bi_list = []
|
|
last_up_bi = None
|
|
last_down_bi = None
|
|
last_up_sbi = None
|
|
last_down_sbi = None
|
|
last_seg = None
|
|
up_sbi_list = []
|
|
down_sbi_list = []
|
|
look_for_bottom = False
|
|
look_for_top = False
|
|
for bi in bi_list:
|
|
#print(len(up_sbi_list), len(down_sbi_list))
|
|
if len(seg_list) > 0:
|
|
# Last seg is up
|
|
if last_seg.dir == Chan_SEG_DIR.UP:
|
|
if bi.dir == Chan_BI_DIR.DOWN:
|
|
if len(down_sbi_list) > 1:
|
|
# Check down sbi inclusion
|
|
included = last_down_sbi.check_bi_included(bi)
|
|
if not included:
|
|
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
|
last_down_sbi.set_next(down_sbi)
|
|
last_down_sbi.set_end_bi(last_down_bi)
|
|
down_sbi.set_pre(last_down_sbi)
|
|
down_sbi_list.append(down_sbi)
|
|
fx = last_down_sbi.check_fx()
|
|
# Found top
|
|
if fx == Chan_FX_TYPE.TOP:
|
|
if look_for_top:
|
|
seg_list[-2].set_sure(bi)
|
|
look_for_top = False
|
|
#print(bi.start_time, look_for_top, "UP 1")
|
|
# Has gap and search for bottom fx
|
|
if last_down_sbi.has_fx_gap:
|
|
look_for_bottom = True
|
|
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
|
|
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
|
seg_list.append(seg)
|
|
last_seg.set_next(seg)
|
|
seg.set_pre(last_seg)
|
|
last_seg = seg
|
|
up_sbi_list = []
|
|
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
|
up_sbi_list.append(last_up_sbi)
|
|
#up_sbi_list.append(last_up_sbi)
|
|
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 1")
|
|
#print(bi.start_time, look_for_top, "UP 2")
|
|
# No gap end SEG
|
|
else:
|
|
if look_for_bottom:
|
|
look_for_bottom = False
|
|
last_seg.set_start_bi(last_down_sbi.start_bi)
|
|
seg_list[-2].set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
|
up_sbi_list = []
|
|
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
|
up_sbi_list.append(last_up_sbi)
|
|
last_seg.add_bi(bi)
|
|
#up_sbi_list.append(last_up_sbi)
|
|
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 2")
|
|
#print(bi.start_time, look_for_top, "UP 3")
|
|
else:
|
|
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
|
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
|
seg_list.append(seg)
|
|
last_seg.set_next(seg)
|
|
seg.set_pre(last_seg)
|
|
last_seg = seg
|
|
#print(last_down_sbi.end_bi.start_time, "Normal UP SEG", last_up_sbi.start_bi.start_time, bi.start_time)
|
|
#l_up_sbi = up_sbi_list[-1]
|
|
up_sbi_list = []
|
|
last_up_sbi = ChanSBI(last_up_bi, len(up_sbi_list), last_up_bi.dir)
|
|
up_sbi_list.append(last_up_sbi)
|
|
#up_sbi_list.append(last_up_sbi)
|
|
#print(last_up_bi.start_time, last_up_sbi.start_bi.start_time, "Reset up sbi list 3")
|
|
last_down_sbi = down_sbi
|
|
last_seg.add_bi(bi)
|
|
else:
|
|
if len(down_sbi_list) == 1:
|
|
included = last_down_sbi.check_bi_included(bi)
|
|
if not included:
|
|
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
|
last_down_sbi.set_next(down_sbi)
|
|
last_down_sbi.set_end_bi(last_down_bi)
|
|
down_sbi.set_pre(last_down_sbi)
|
|
down_sbi_list.append(down_sbi)
|
|
last_down_sbi = down_sbi
|
|
#print(bi.start_time, look_for_top, "UP 4")
|
|
last_seg.add_bi(bi)
|
|
|
|
else:
|
|
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
|
down_sbi_list.append(last_down_sbi)
|
|
last_seg.add_bi(bi)
|
|
#print(bi.start_time, look_for_top, "UP 5")
|
|
else:
|
|
if last_up_sbi:
|
|
included = last_up_sbi.check_bi_included(bi)
|
|
if not included:
|
|
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
|
last_up_sbi.set_next(up_sbi)
|
|
last_up_sbi.set_end_bi(last_up_bi)
|
|
up_sbi.set_pre(last_up_sbi)
|
|
up_sbi_list.append(up_sbi)
|
|
last_up_sbi = up_sbi
|
|
#print(bi.start_time, look_for_top, "UP 6")
|
|
last_seg.add_bi(bi)
|
|
|
|
# Last seg is down
|
|
else:
|
|
if bi.dir == Chan_BI_DIR.UP:
|
|
if len(up_sbi_list) > 1:
|
|
# Check down sbi inclusion
|
|
included = last_up_sbi.check_bi_included(bi)
|
|
if not included:
|
|
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
|
last_up_sbi.set_next(up_sbi)
|
|
last_up_sbi.set_end_bi(last_up_bi)
|
|
up_sbi.set_pre(last_up_sbi)
|
|
up_sbi_list.append(up_sbi)
|
|
fx = last_up_sbi.check_fx()
|
|
# Found bottom
|
|
if fx == Chan_FX_TYPE.BOTTOM:
|
|
if look_for_bottom:
|
|
seg_list[-2].set_sure(bi)
|
|
look_for_bottom = False
|
|
#print(bi.start_time, look_for_top, "DOWN 1")
|
|
# Has gap and search for bottom fx
|
|
if last_up_sbi.has_fx_gap:
|
|
look_for_top = True
|
|
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
|
|
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
|
seg_list.append(seg)
|
|
last_seg.set_next(seg)
|
|
seg.set_pre(last_seg)
|
|
last_seg = seg
|
|
down_sbi_list = []
|
|
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
|
down_sbi_list.append(last_down_sbi)
|
|
#down_sbi_list.append(last_down_sbi)
|
|
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 1")
|
|
#print(bi.start_time, look_for_top, "DOWN 2")
|
|
# No gap end SEG
|
|
else:
|
|
if look_for_top:
|
|
look_for_top = False
|
|
last_seg.set_start_bi(last_up_sbi.start_bi)
|
|
seg_list[-2].set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
|
down_sbi_list = []
|
|
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
|
down_sbi_list.append(last_down_sbi)
|
|
last_seg.add_bi(bi)
|
|
#down_sbi_list.append(last_down_sbi)
|
|
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 2")
|
|
#print(bi.start_time, look_for_top, "DOWN 3")
|
|
else:
|
|
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
|
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
|
#print(last_up_sbi.start_bi.start_time)
|
|
last_seg.set_next(seg)
|
|
seg.set_pre(last_seg)
|
|
seg_list.append(seg)
|
|
last_seg = seg
|
|
#print(last_up_sbi.end_bi.start_time, "Normal DOWN SEG", last_down_sbi.start_bi.start_time, bi.start_time)
|
|
down_sbi_list = []
|
|
last_down_sbi = ChanSBI(last_down_bi, len(down_sbi_list), last_down_bi.dir)
|
|
down_sbi_list.append(last_down_sbi)
|
|
#down_sbi_list.append(last_down_sbi)
|
|
#print(last_down_bi.start_time, last_down_sbi.start_bi.start_time, "Reset down sbi list 3")
|
|
last_up_sbi = up_sbi
|
|
last_seg.add_bi(bi)
|
|
else:
|
|
if len(up_sbi_list) == 1:
|
|
#last_up_sbi = up_sbi_list[-1]
|
|
included = last_up_sbi.check_bi_included(bi)
|
|
if not included:
|
|
up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
|
last_up_sbi.set_next(up_sbi)
|
|
last_up_sbi.set_end_bi(last_up_bi)
|
|
up_sbi.set_pre(last_up_sbi)
|
|
up_sbi_list.append(up_sbi)
|
|
last_up_sbi = up_sbi
|
|
last_seg.add_bi(bi)
|
|
#print(bi.start_time, look_for_top, "DOWN 4")
|
|
else:
|
|
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
|
up_sbi_list.append(last_up_sbi)
|
|
last_seg.add_bi(bi)
|
|
#print(bi.start_time, look_for_top, "DOWN 5")
|
|
else:
|
|
if last_down_sbi:
|
|
included = last_down_sbi.check_bi_included(bi)
|
|
if not included:
|
|
down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
|
last_down_sbi.set_next(down_sbi)
|
|
last_down_sbi.set_end_bi(last_down_bi)
|
|
down_sbi.set_pre(last_down_sbi)
|
|
down_sbi_list.append(down_sbi)
|
|
last_down_sbi = down_sbi
|
|
last_seg.add_bi(bi)
|
|
#print(bi.start_time, look_for_top, look_for_bottom, "DOWN 6")
|
|
# len(seg_list) = 0
|
|
else:
|
|
if bi.check_overlap():
|
|
if bi.dir == Chan_BI_DIR.UP:
|
|
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
|
last_up_bi = bi
|
|
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
|
seg_list.append(seg)
|
|
last_seg = seg
|
|
#print(bi.start_time, 'Create first UP SEG')
|
|
else:
|
|
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
|
last_down_bi = bi
|
|
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
|
seg_list.append(seg)
|
|
last_seg = seg
|
|
#print(bi.start_time, 'Create first DOWN SEG')
|
|
if bi.dir == Chan_BI_DIR.UP:
|
|
last_up_bi = bi
|
|
up_bi_list.append(bi)
|
|
else:
|
|
last_down_bi = bi
|
|
down_bi_list.append(bi)
|
|
"""
|
|
if len(seg_list) > 1:
|
|
seg = seg_list[-1]
|
|
last_seg = seg_list[-2]
|
|
last_seg_bi = last_seg.bi_list[-3]
|
|
bi_index = seg.start_bi.index
|
|
for i in range(bi_index, len(bi_list) - 1):
|
|
# last seg is down
|
|
if seg.dir == Chan_SEG_DIR.UP:
|
|
if bi_list[i].dir == Chan_BI_DIR.UP:
|
|
last_seg_peak = last_seg_bi.high
|
|
if bi_list[i].high > last_seg_peak:
|
|
# The confirmed
|
|
print("Last UP seg is broken, create a new seg. 1")
|
|
seg.pre_set_end_bi(bi_list[i])
|
|
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
|
seg_list.append(seg)
|
|
last_seg = seg_list[-2]
|
|
if len(last_seg.bi_list) > 3:
|
|
last_seg_bi = last_seg.bi_list[-3]
|
|
|
|
else:
|
|
if bi_list[i].dir == Chan_BI_DIR.DOWN:
|
|
last_seg_peak = last_seg_bi.low
|
|
if bi_list[i].low < last_seg_peak:
|
|
print("Last DOWN seg is broken, create a new seg. 1")
|
|
seg.pre_set_end_bi(bi_list[i])
|
|
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
|
|
seg_list.append(seg)
|
|
last_seg = seg_list[-2]
|
|
if len(last_seg.bi_list) > 3:
|
|
last_seg_bi = last_seg.bi_list[-3]
|
|
else:
|
|
if len(seg_list) == 1:
|
|
last_seg = seg_list[-1]
|
|
bi_index = last_seg.bi_list[0].index
|
|
for i in range(bi_index, len(bi_list) - 1):
|
|
if i > bi_index + 2:
|
|
last_seg_peak = bi_list[i-2].high
|
|
# last seg is down
|
|
if last_seg.dir == Chan_SEG_DIR.DOWN:
|
|
if bi_list[i].dir == Chan_BI_DIR.UP:
|
|
if bi_list[i].high > last_seg_peak:
|
|
print("Last seg is broken, create a new seg. 2")
|
|
last_seg.pre_set_end_bi(bi_list[i-1])
|
|
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
|
|
seg_list.append(seg)
|
|
last_seg = seg
|
|
last_seg_bi = bi_list[i]
|
|
break
|
|
"""
|
|
#self.cal_bi_zs(seg_list)
|
|
return seg_list
|