"""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 import talib.abstract as ta from pandas import DataFrame from technical.util import resample_to_interval 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 BspBuilderMixin: def get_bsp_state(self, dataframe): klu_list = self.get_klu_list(dataframe) klc_list = self.get_klc_list(klu_list) bi_list = self.cal_bi_list(klc_list) seg_list = self.get_seg_list(bi_list) bi_zs_list = self.cal_bi_zs(seg_list) bsp_list = self.find_all_bsp(bi_list, bi_zs_list) bsp_state_list = [0] * len(dataframe) klc_index = 0 for index in range(0, len(dataframe)): if klc_index == len(klc_list): klc_index = len(klc_list) - 1 klc = klc_list[klc_index] if klc.end_klu and klc.end_klu.idx == index: if klc.klc_fx_type == Chan_KLC_FX.TOP2: bi = klc.bi.pre if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.B3: # 第三类买点 bsp_state_list[index] = -1 #print(klc.end_time, "B3") else: bsp_state_list[index] = 0 elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM2: bi = klc.bi.pre if bi and bi.is_sure and bi.end_klc.bsp_type == Chan_BSP_TYPE.S3: # 第三类卖点 bsp_state_list[index] = 1 #print(klc.end_time, "S3") else: bsp_state_list[index] = 0 klc_index += 1 else: bsp_state_list[index] = 0 return bsp_state_list def get_above_zero_bsp(self, klc_list): buy_bsp_list = [] sell_bsp_list = [] above_zero = False buy_bsp = None sell_bsp = None for klc in klc_list: if klc.pre and klc.pre.signal < 0 and klc.signal > 0: above_zero = True if klc.pre and klc.pre.signal > 0 and klc.signal < 0: above_zero = False if above_zero and klc.klc_fx_type == Chan_KLC_FX.BOTTOM2 and klc.macd > 0: buy_bsp = klc buy_bsp_list.append(klc) #print(klc.end_time, "MACD 0轴上穿,回调笔底分型做多") if buy_bsp and klc.pre and klc.pre.macdhist > 0 and klc.macdhist < 0: sell_bsp = klc sell_bsp_list.append(klc) buy_bsp = None #print(klc.end_time, "Sell BSP Found") return buy_bsp_list def find_all_bsp(self, bi_list, bi_zs_list): """ 笔中枢的三类买卖点识别 三类买点:中枢形成后,一笔向上离开中枢(低点 > zg), 随后回拉的一笔低点不跌回中枢(低点 >= zg),确认支撑有效。 三类卖点:中枢形成后,一笔向下离开中枢(高点 < zd), 随后反弹的一笔高点不回到中枢(高点 <= zd),确认压力有效。 参数: bi_list: 笔列表 bi_zs_list: 笔中枢列表(二维列表,每个seg内的中枢列表) 返回: bsp_list: ChanBSP 列表,包含所有识别到的三类买卖点 """ bsp_list = [] if len(bi_list) < 4 or len(bi_zs_list) == 0: return bsp_list for zs in bi_zs_list: if not zs.is_sure or len(zs.bi_list) < 3: continue #print(zs.start_time, zs.end_time, zs.dir, zs.is_sure, len(zs.bi_list)) # 中枢结束后的第一笔(离开笔) last_zs_bi = zs.bi_list[-1] if last_zs_bi.dir == Chan_BI_DIR.UP: if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.low < zs.zd): leave_bi = last_zs_bi.next else: leave_bi = last_zs_bi else: if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd or (last_zs_bi.next and last_zs_bi.next.is_sure and last_zs_bi.next.end_klc.high > zs.zg): leave_bi = last_zs_bi.next else: leave_bi = last_zs_bi #print(zs.zg, zs.zd) if leave_bi is None or not leave_bi.is_sure: continue if (zs.dir == Chan_ZS_DIR.UP and leave_bi.dir == Chan_BI_DIR.UP and leave_bi.end_klc.high < zs.zg and leave_bi.end_klc.high > zs.zd) or (zs.dir == Chan_ZS_DIR.DOWN and leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.end_klc.low < zs.zg and leave_bi.end_klc.low > zs.zd): #print("--------------------", leave_bi.dir, leave_bi.end_klc.high, leave_bi.end_klc.low, zs.zg, zs.zd) leave_bi = leave_bi.next # 三类买点:向上离开中枢后回拉不破 zg #print("Leave bi:", leave_bi.start_time, leave_bi.end_time, leave_bi.dir, leave_bi.is_sure, leave_bi.low, leave_bi.high) if leave_bi.dir == Chan_BI_DIR.UP: first_bsp_bi_div = self.check_bi_div(zs, leave_bi) # 确认一类卖点:离开断能量小于进入段能量 if first_bsp_bi_div: bsp = ChanBSP( leave_bi, len(bsp_list), Chan_BSP_TYPE.S1, Chan_BSP_DIR.SELL, leave_bi.sure_time, zs.index+1, zs, None ) leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S1) bsp_list.append(bsp) # 回拉笔 pullback_bi = leave_bi.next #print(pullback_bi.start_klc.start_time, pullback_bi.dir, pullback_bi.is_sure, pullback_bi.low, pullback_bi.high) if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN: if pullback_bi.low >= zs.zg: # 确认三类买点:回拉笔的低点不跌回中枢 bsp = ChanBSP( pullback_bi, len(bsp_list), Chan_BSP_TYPE.B3, Chan_BSP_DIR.BUY, pullback_bi.sure_time, zs.index+1, zs, None ) pullback_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B3) bsp_list.append(bsp) # 二类卖点 if first_bsp_bi_div: second_bsp_bi = pullback_bi.next if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.high < leave_bi.end_klc.high: # 确认二类卖点:一类卖点后回拉不超过一类卖点高点 bsp = ChanBSP( second_bsp_bi, len(bsp_list), Chan_BSP_TYPE.S2, Chan_BSP_DIR.SELL, second_bsp_bi.sure_time, zs.index+1, zs, None ) second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2) bsp_list.append(bsp) # 三类卖点:向下离开中枢后反弹不破 zd elif leave_bi.dir == Chan_BI_DIR.DOWN: first_bsp_bi_div = self.check_bi_div(zs, leave_bi) # 确认一类买点:离开段能量小于进入段 if first_bsp_bi_div: bsp = ChanBSP( leave_bi, len(bsp_list), Chan_BSP_TYPE.B1, Chan_BSP_DIR.BUY, leave_bi.sure_time, zs.index+1, zs, None ) leave_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B1) bsp_list.append(bsp) # 反弹笔 bounce_bi = leave_bi.next #print(bounce_bi.start_klc.start_time, bounce_bi.dir, bounce_bi.is_sure, bounce_bi.low, bounce_bi.high) if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP: if bounce_bi.high <= zs.zd: # 确认三类卖点:反弹笔的高点不回到中枢 bsp = ChanBSP( bounce_bi, len(bsp_list), Chan_BSP_TYPE.S3, Chan_BSP_DIR.SELL, bounce_bi.sure_time, zs.index+1, zs, None ) bounce_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.S3) bsp_list.append(bsp) # 二类卖点 if first_bsp_bi_div: second_bsp_bi = bounce_bi.next if second_bsp_bi and second_bsp_bi.is_sure and second_bsp_bi.end_klc.low > leave_bi.end_klc.low: # 确认二类买点:一类买点后回拉不超过一类卖点高点 bsp = ChanBSP( second_bsp_bi, len(bsp_list), Chan_BSP_TYPE.B2, Chan_BSP_DIR.BUY, second_bsp_bi.sure_time, zs.index+1, zs, None ) second_bsp_bi.end_klc.set_bsp_type(Chan_BSP_TYPE.B2) bsp_list.append(bsp) return bsp_list def check_bi_div(self, zs, leave_bi): enter_bi = zs.bi_list[0].pre macdhist_div = 0 if enter_bi and enter_bi.dir == leave_bi.dir: macdhist_div = abs(leave_bi.macd_hist) - abs(enter_bi.macd_hist) #print(enter_bi.end_time, leave_bi.end_time, macdhist_div < 0) return macdhist_div < 0 def find_first_bsp(self, bi_list, bi_zs_list): """ 笔中枢的一类买卖点识别 一类买点:下跌趋势中,最后一个中枢完成后,向下离开中枢的笔创新低, 但该笔与进入中枢前的最后一笔下跌形成底背驰(力度减弱), 即趋势力竭的转折点。 一类卖点:上涨趋势中,最后一个中枢完成后,向上离开中枢的笔创新高, 但该笔与进入中枢前的最后一笔上涨形成顶背驰(力度减弱), 即趋势力竭的转折点。 简化判断:中枢形成后,离开中枢的笔(突破笔)本身即为一类买卖点的触发笔。 参数: bi_list: 笔列表 bi_zs_list: 笔中枢列表(扁平列表,每个元素是一个中枢对象) 返回: bsp_list: ChanBSP 列表,包含所有识别到的一类买卖点 """ bsp_list = [] if len(bi_list) < 4 or len(bi_zs_list) == 0: return bsp_list for zs in bi_zs_list: if not zs.is_sure or len(zs.bi_list) < 3: continue # 找到中枢的最后一笔 last_zs_bi = zs.bi_list[-1] # 确定离开笔:中枢最后一笔之后的第一笔 if last_zs_bi.dir == Chan_BI_DIR.UP: # 中枢最后一笔向上,如果没有真正离开中枢,取下一笔 if last_zs_bi.is_sure and last_zs_bi.end_klc.high <= zs.zg: leave_bi = last_zs_bi.next else: leave_bi = last_zs_bi else: # 中枢最后一笔向下,如果没有真正离开中枢,取下一笔 if last_zs_bi.is_sure and last_zs_bi.end_klc.low >= zs.zd: leave_bi = last_zs_bi.next else: leave_bi = last_zs_bi if leave_bi is None or not leave_bi.is_sure: continue # 一类买点:向下离开中枢(leave_bi向下,低点 < zd),趋势力竭 if leave_bi.dir == Chan_BI_DIR.DOWN and leave_bi.low < zs.zd: # 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积 # 缠论原文:两段同向走势的MACD柱状面积比较,面积缩小即为背驰 compare_bi = None for bi in reversed(zs.bi_list): if bi.dir == Chan_BI_DIR.DOWN and bi is not leave_bi: compare_bi = bi break is_divergence = False if compare_bi: # 笔的macd_hist是该笔内所有KLU的macdhist累积面积 leave_macd_area = abs(leave_bi.macd_hist) compare_macd_area = abs(compare_bi.macd_hist) # 价格创新低但MACD面积缩小 = 底背驰 if leave_bi.low <= compare_bi.low and leave_macd_area < compare_macd_area: is_divergence = True # 即使没创新低,MACD面积明显缩小也算背驰 elif leave_macd_area < compare_macd_area * 0.5: is_divergence = True else: # 没有对比笔时,只要离开中枢就算一类买点 is_divergence = True if is_divergence: bsp = ChanBSP( leave_bi, len(bsp_list), Chan_BSP_TYPE.T1, Chan_BSP_DIR.BUY, leave_bi.sure_time, 1, zs, None ) bsp_list.append(bsp) # 一类卖点:向上离开中枢(leave_bi向上,高点 > zg),趋势力竭 elif leave_bi.dir == Chan_BI_DIR.UP and leave_bi.high > zs.zg: # 背驰判断:比较离开笔与中枢内最后一笔同向笔的MACD柱状累积面积 compare_bi = None for bi in reversed(zs.bi_list): if bi.dir == Chan_BI_DIR.UP and bi is not leave_bi: compare_bi = bi break is_divergence = False if compare_bi: leave_macd_area = abs(leave_bi.macd_hist) compare_macd_area = abs(compare_bi.macd_hist) # 价格创新高但MACD面积缩小 = 顶背驰 if leave_bi.high >= compare_bi.high and leave_macd_area < compare_macd_area: is_divergence = True # 即使没创新高,MACD面积明显缩小也算背驰 elif leave_macd_area < compare_macd_area * 0.5: is_divergence = True else: is_divergence = True if is_divergence: bsp = ChanBSP( leave_bi, len(bsp_list), Chan_BSP_TYPE.T1, Chan_BSP_DIR.SELL, leave_bi.sure_time, 1, zs, None ) bsp_list.append(bsp) return bsp_list def find_second_bsp(self, bi_list, first_bsp_list): """ 笔中枢的二类买卖点识别 二类买点:一类买点出现后,价格向上反弹一笔,再回落一笔, 回落笔的低点不跌破一类买点的低点,确认底部成立。 二类卖点:一类卖点出现后,价格向下回落一笔,再反弹一笔, 反弹笔的高点不超过一类卖点的高点,确认顶部成立。 参数: bi_list: 笔列表 first_bsp_list: 一类买卖点列表(find_first_bsp 的返回值) 返回: bsp_list: ChanBSP 列表,包含所有识别到的二类买卖点 """ bsp_list = [] if not first_bsp_list or len(bi_list) < 4: return bsp_list for first_bsp in first_bsp_list: trigger_bi = first_bsp.bi # 一类买卖点的触发笔 if first_bsp.dir == Chan_BSP_DIR.BUY: # 一买之后:trigger_bi 向下 -> 反弹笔(向上) -> 回落笔(向下) # 回落笔的低点 > trigger_bi 的低点 => 二类买点 bounce_bi = trigger_bi.next # 反弹笔(向上) if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP: pullback_bi = bounce_bi.next # 回落笔(向下) if pullback_bi and pullback_bi.is_sure and pullback_bi.dir == Chan_BI_DIR.DOWN: if pullback_bi.low > trigger_bi.low: bsp = ChanBSP( pullback_bi, len(bsp_list), Chan_BSP_TYPE.T2, Chan_BSP_DIR.BUY, pullback_bi.sure_time, 1, first_bsp.zs, None ) bsp_list.append(bsp) elif first_bsp.dir == Chan_BSP_DIR.SELL: # 一卖之后:trigger_bi 向上 -> 回落笔(向下) -> 反弹笔(向上) # 反弹笔的高点 < trigger_bi 的高点 => 二类卖点 drop_bi = trigger_bi.next # 回落笔(向下) if drop_bi and drop_bi.is_sure and drop_bi.dir == Chan_BI_DIR.DOWN: bounce_bi = drop_bi.next # 反弹笔(向上) if bounce_bi and bounce_bi.is_sure and bounce_bi.dir == Chan_BI_DIR.UP: if bounce_bi.high < trigger_bi.high: bsp = ChanBSP( bounce_bi, len(bsp_list), Chan_BSP_TYPE.T2, Chan_BSP_DIR.SELL, bounce_bi.sure_time, 1, first_bsp.zs, None ) bsp_list.append(bsp) return bsp_list