diff --git a/.DS_Store b/.DS_Store index 2a9a07b..d30ef56 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/ChanLun.py b/ChanLun.py index bca059a..1528e38 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -32,7 +32,6 @@ class ChanLun(): time30 = 30 time60 = 60 time4h = 240 - last_peak = {'high': 0, 'low': float('inf')} def create_all_data(self, dataframe, ticker_indicator): all_data = dict() all_data['1m'] = dataframe @@ -78,15 +77,6 @@ class ChanLun(): #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM") return Chan_FX_TYPE.BOTTOM return Chan_FX_TYPE.UNKNOWN - def get_macd(self, df): - fast = 8 - slow = 15 - period = 2 - macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period) - df['macd'] = macd['macd'] - df['macdsignal'] = macd['macdsignal'] - df['macdhist'] = macd['macdhist'] - return df def plot_dataframe(self, dataframe): klc_list = self.get_klc_list(dataframe) bi_list= self.cal_bi_list(klc_list) @@ -200,13 +190,6 @@ class ChanLun(): for df in df_list: state_list.append(self.get_klc_state_list(df)) return state_list - def print_data(self, dataframe): - klc_list = self.get_klc_list(dataframe) - bi_list = self.cal_bi_list(klc_list) - seg_list = self.get_seg_list(bi_list) - bsp_list, zs_list = self.calculate_zs(bi_list, seg_list) - bi_macd_div_list = self.get_bi_macd_div_list(bi_list, dataframe) - seg_macd_div_list = self.get_seg_macd_div_list(seg_list, dataframe) def resample_bsp_list(self, bsp_list, dataframe): bsp_index = 0 resampled_bsp_list = [] @@ -287,10 +270,6 @@ class ChanLun(): c, v ] - if h > self.last_peak['high']: - self.last_peak['high'] = h - if l < self.last_peak['low']: - self.last_peak['low'] = l #klu = KLU(self.create_item_dict(item_data, GetColumnNameFromFieldList(fields))) klu = ChanKLU(time_str, o, h, l, c, v) #print(klu.time, klu.open, klu.high, klu.low, klu.close, klu.volume) @@ -314,31 +293,6 @@ class ChanLun(): return df['volume_ratio'] def calculate_zs(self, bi_list, seg_list): return self.get_zs_list(bi_list, seg_list) - def get_full_klc_list(self, dataframe): - klc_list = self.get_klc_list(dataframe) - bi_list = self.cal_bi_list(klc_list) - return klc_list - def print_bi_klc(self, dataframe): - klc_list = self.get_klc_list(dataframe) - bi_list = self.cal_bi_list(klc_list) - rsi_list = dataframe['rsi'] - fx_list = [] - for klc in klc_list: - if klc.klc_fx_type != Chan_KLC_FX.UNKNOWN: - if klc.bi.dir == Chan_BI_DIR.UP and klc.volume_ratio > 2: - print(klc.start_time, klc.end_time, klc.klc_fx_type, klc.rsi, klc.volume_ratio) - fx_list.append(klc) - elif klc.bi.dir == Chan_BI_DIR.DOWN and klc.volume_ratio > 2: - print(klc.start_time, klc.end_time, klc.klc_fx_type, klc.rsi, klc.volume_ratio) - fx_list.append(klc) - bi_start_index_list = [] - for bi in bi_list: - bi_start_index_list.append(bi.start_klc.index) - if bi.end_klc: - bi.cal_macdhist() - bi.cal_macd_div() - print("Bi:", bi.start_time, bi.end_time, bi.dir, bi.macd_hist, bi.macd_div) - klc_index_count = 0 def get_seg_list(self, bi_list): seg_list = [] up_bi_list = [] @@ -617,157 +571,7 @@ class ChanLun(): break """ return seg_list - - def get_bi_zs_list(self, bi_list): - """识别笔中枢列表 - - 与线段中枢不同,笔中枢是由连续的同向笔构成,是更细粒度的中枢结构 - - Args: - bi_list: 笔列表 - - Returns: - bi_zs_list: 笔中枢列表 - """ - bi_zs_list = [] - if len(bi_list) < 3: # 至少需要3个笔才能形成中枢 - print("笔数量不足,无法形成中枢") - return bi_zs_list - - last_zs = None - first_bi_out = None - in_again = False - bi_out_count = 0 - - # 遍历所有笔,识别中枢 - for i in range(2, len(bi_list)): - # 确保当前笔和前两个笔都是完成的 - if not bi_list[i].end_klc or not bi_list[i-1].end_klc or not bi_list[i-2].end_klc: - continue - - current_bi = bi_list[i] - prev_bi = bi_list[i-1] - prev_prev_bi = bi_list[i-2] - - # 如果没有中枢或上一个中枢已完成 - if len(bi_zs_list) == 0 or (last_zs and last_zs.is_sure): - # 检查是否是三个连续同向笔 - if (current_bi.dir == prev_bi.dir == prev_prev_bi.dir): - # 创建潜在中枢 - if current_bi.dir == Chan_BI_DIR.UP: - # 向上的三笔区间定义中枢 - # 中枢的上沿:取三个笔的终点的最小值 - # 中枢的下沿:取三个笔的起点的最大值 - zd = max(prev_prev_bi.start_klc.low, prev_bi.start_klc.low, current_bi.start_klc.low) - zg = min(prev_prev_bi.end_klc.high, prev_bi.end_klc.high, current_bi.end_klc.high) - - # 确保中枢有效(上沿大于下沿) - if zg > zd: - print(f"发现向上笔中枢: 起始时间={prev_prev_bi.start_klc.start_time}, ZG={zg}, ZD={zd}") - zs = ChanZS(prev_prev_bi.start_klc, zg, zd) - zs.start_bi = prev_prev_bi - zs.start_idx = i-2 - zs.end_bi = current_bi - zs.end_idx = i - zs.end_klc = current_bi.end_klc - zs.type = "BI_ZS" - zs.direction = Chan_ZS_DIR.UP - zs.sure_time = None # 中枢尚未确认完成 - bi_zs_list.append(zs) - last_zs = zs - else: - # 向下的三笔区间定义中枢 - # 中枢的上沿:取三个笔的起点的最小值 - # 中枢的下沿:取三个笔的终点的最大值 - zg = min(prev_prev_bi.start_klc.high, prev_bi.start_klc.high, current_bi.start_klc.high) - zd = max(prev_prev_bi.end_klc.low, prev_bi.end_klc.low, current_bi.end_klc.low) - - # 确保中枢有效(上沿大于下沿) - if zg > zd: - print(f"发现向下笔中枢: 起始时间={prev_prev_bi.start_klc.start_time}, ZG={zg}, ZD={zd}") - zs = ChanZS(prev_prev_bi.start_klc, zg, zd) - zs.start_bi = prev_prev_bi - zs.start_idx = i-2 - zs.end_bi = current_bi - zs.end_idx = i - zs.end_klc = current_bi.end_klc - zs.type = "BI_ZS" - zs.direction = Chan_ZS_DIR.DOWN - zs.sure_time = None # 中枢尚未确认完成 - bi_zs_list.append(zs) - last_zs = zs - # 处理已有的未完成中枢 - elif last_zs and not last_zs.is_sure: - # 当前笔与中枢最后一笔方向相同,可能延伸中枢 - if current_bi.dir == prev_bi.dir: - if last_zs.direction == Chan_ZS_DIR.UP and current_bi.dir == Chan_BI_DIR.UP: - # 检查是否仍在中枢内:向上时终点高价在中枢区间内 - if current_bi.end_klc.high >= last_zs.zd and current_bi.end_klc.high <= last_zs.zg: - print(f"延伸向上笔中枢: 终点时间={current_bi.end_klc.end_time}") - # 延伸中枢 - last_zs.end_klc = current_bi.end_klc - last_zs.end_bi = current_bi - last_zs.end_idx = i - else: - # 笔离开中枢,记录第一个离开的笔 - if not first_bi_out: - first_bi_out = current_bi - bi_out_count += 1 - print(f"笔离开向上中枢: 时间={current_bi.end_klc.end_time}, 价格={current_bi.end_klc.high}, 中枢上沿={last_zs.zg}") - else: - if not in_again: - # 第二次离开,确认中枢完成 - print(f"确认向上笔中枢完成: 时间={current_bi.end_klc.end_time}") - last_zs.is_sure = True - last_zs.sure_bi = current_bi - last_zs.sure_time = current_bi.end_klc.end_time - elif last_zs.direction == Chan_ZS_DIR.DOWN and current_bi.dir == Chan_BI_DIR.DOWN: - # 检查是否仍在中枢内:向下时终点低价在中枢区间内 - if current_bi.end_klc.low <= last_zs.zg and current_bi.end_klc.low >= last_zs.zd: - print(f"延伸向下笔中枢: 终点时间={current_bi.end_klc.end_time}") - # 延伸中枢 - last_zs.end_klc = current_bi.end_klc - last_zs.end_bi = current_bi - last_zs.end_idx = i - else: - # 笔离开中枢,记录第一个离开的笔 - if not first_bi_out: - first_bi_out = current_bi - bi_out_count += 1 - print(f"笔离开向下中枢: 时间={current_bi.end_klc.end_time}, 价格={current_bi.end_klc.low}, 中枢下沿={last_zs.zd}") - else: - if not in_again: - # 第二次离开,确认中枢完成 - print(f"确认向下笔中枢完成: 时间={current_bi.end_klc.end_time}") - last_zs.is_sure = True - last_zs.sure_bi = current_bi - last_zs.sure_time = current_bi.end_klc.end_time - # 方向改变,判断是否破坏中枢 - else: - # 方向改变可能导致重新进入中枢或破坏中枢 - # 向上中枢被向下笔破坏:低点低于中枢下沿 - # 向下中枢被向上笔破坏:高点高于中枢上沿 - if (last_zs.direction == Chan_ZS_DIR.UP and current_bi.end_klc.low < last_zs.zd) or \ - (last_zs.direction == Chan_ZS_DIR.DOWN and current_bi.end_klc.high > last_zs.zg): - # 破坏中枢 - print(f"笔中枢被破坏: 方向={current_bi.dir}, 时间={current_bi.end_klc.end_time}") - last_zs.is_sure = True - last_zs.sure_bi = current_bi - last_zs.sure_time = current_bi.end_klc.end_time - elif first_bi_out: - # 重新进入中枢 - print(f"笔重新进入中枢: 时间={current_bi.end_klc.end_time}") - in_again = True - first_bi_out = None - # 延伸中枢 - last_zs.end_klc = current_bi.end_klc - last_zs.end_bi = current_bi - last_zs.end_idx = i - - # 打印识别结果 - print(f"笔中枢识别完成,共找到 {len(bi_zs_list)} 个笔中枢") - return bi_zs_list - #------------------------------------------------------------------- + def cal_bi_list(self, klc_list): bi_list = [] last_top = None diff --git a/K线动能理论.txt b/K线动能理论.txt index 20e22e1..3ecfb76 100644 --- a/K线动能理论.txt +++ b/K线动能理论.txt @@ -7,12 +7,11 @@ MACD归零轴的两种情况,两者是或的关系,满足任意一种都是 高位空 当MACD的黄白线远离零轴运行时,与零轴有一定的距离,形成了零轴的高危形态。随着K线出现缓慢上涨或者下跌,或者盘整,MACD的能量柱出现衰减,同时能量柱与MACD黄白线形成空间夹角,随着能量柱越来越小,夹角越来越大形成高位空。这种容易形成回调下跌,特别是导致次一级的MACD穿越零轴 - 穿越零轴的定义,需要同时满足以下条件 1. 在某个时间级别,k线的价格或者指数有效击穿当前时间级别的EMA52 -2. MACD黄翔慢线有效击穿零轴 +2. MACD黄线慢线有效击穿零轴 -MACD黄白线喝零轴的几种形态: +MACD黄白线和零轴的几种形态: 离开零轴 当MACD黄白线穿过零轴那么进入第一阶段离开零轴,此时能量柱变化越来越大,不断增长,k线加速上涨 高位 @@ -28,7 +27,7 @@ MACD黄白线喝零轴的几种形态: 有效的定义是:当前K线正好击穿EMA52的支撑位后,如果当前这个K线收盘后的第二根K线任然保持在EMA52之下才算有效击穿,如果只是上下影线击穿,后期K线任然运行在EMA52之上不算有效击穿,MACD同理 零轴缠绕/纠缠 具体是指MACD跟零轴无限接近或者缠绕的状态,或者是已经完成第一次归零轴调整之后,在等待大级别调整的时候。 -分为无限接近喝上下缠绕状态。代表本级别已经调整完毕,即不产生反弹支撑,也不形成阻力压力,不考虑次级别的技术形态,通过更大的时间级别或者其他时间级别进行分析 +分为无限接近和上下缠绕状态。代表本级别已经调整完毕,即不产生反弹支撑,也不形成阻力压力,不考虑次级别的技术形态,通过更大的时间级别或者其他时间级别进行分析 隐形形态 当MACD的黄白线发生交叉时,必有相应的能量柱释放出来。金叉,则会释放零轴之上的能量柱,反之死叉,则会释放出零轴之下的能量柱。如果黄白线无交叉,而k线出现上涨或者下跌,则代表能量柱的隐形状态,代表K线的运行无能量配合,那么这种上涨或者下跌就变成无效的结果。无能量配合的上涨必下跌,无能量配合的下跌必反弹 @@ -73,29 +72,29 @@ MACD黄白线在穿零轴的时候与零轴的距离比较近,同时黄白线 1. 黄白线穿零轴后未远离零轴形成一定高度,而是靠近零轴运行 2. 能量柱的衰减导致其与黄白线之间形成了一定的夹角空位 3. 黄白线在运行的过程中发生了交叉而放出反向能量柱 -弱支撑,弱反弹。如果当前时间级别内部逻辑关系走完,这个时间级别则被视为无效时间级别。如果某个时间级别的盘口形态是零轴倒挂,那么这个时间级别很容易直接击穿零轴,而无法形成有效的反弹喝反抽行情。 +弱支撑,弱反弹。如果当前时间级别内部逻辑关系走完,这个时间级别则被视为无效时间级别。如果某个时间级别的盘口形态是零轴倒挂,那么这个时间级别很容易直接击穿零轴,而无法形成有效的反弹和反抽行情。 -零轴粘合喝到挂的有效性 +零轴粘合和倒挂的有效性 在上涨行情中,当K线处在零轴粘合或者零轴倒挂的形态时,如果K线的价格处在当前级别的EMA52之上或者处在多级别EMA均线交汇处之上和附近时,此时由于K线受到EMA均线的支撑,零轴粘合或者零轴倒挂反而容易形成强支撑的特点。此时需要结合MACD的形态和K线均线支撑综合分析盘面。 线段 -上涨线段市值MACD的黄白线第一次上穿零轴到下一次下穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的卖点,阶段性高点。 -下跌线段市值MACD的黄白线第一次下穿零轴到下一次上穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的买点,阶段性低点。 +上涨线段是指MACD的黄白线第一次上穿零轴到下一次下穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的卖点,阶段性高点。 +下跌线段是指MACD的黄白线第一次下穿零轴到下一次上穿零轴中间的运行区域,以黄线穿零轴为准。在这个线段找到阶段性的买点,阶段性低点。 1. 同一条线段是比较背离的区域,背离的比较不可再跨线段的区域进行 2. 线段可以将不同时间级别的K线化繁为简,一个时间级别的形态只需要确定盘面所处的线段即可 背离 -在K线分析中,背离市值价格跟能量之间的相悖性,当价格创出阶段性新高点,而推动价格上涨的能量出现衰减,这种情况就是背离,也就是说价格和能量之间产生了不匹配关系。 +在K线分析中,背离是指价格跟能量之间的相悖性,当价格创出阶段性新高点,而推动价格上涨的能量出现衰减,这种情况就是背离,也就是说价格和能量之间产生了不匹配关系。 顶背离 - 确认卖点 在某个时间级别,MACD运行在零轴上方,当K线的价格走势一峰比一峰高,价格一直处在上涨趋势中时,MACD的黄白线或者能量柱的高度却一波比一波低,即当价格的高点比前一次价格的高点高,而MACD指标的高点比前一次高点低,这种形态称为顶背离形态。需要注意的是,这两次高点在运行的过程中,MACD的黄白线始终处在同一条上涨线段周期中,不能跨线段比较。 -顶背离包括黄白线背离喝柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最高点作为参考点,K线上影线最高点作为K线的参考点,能量堆的最高点可能和K线的最高点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。 +顶背离包括黄白线背离和柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最高点作为参考点,K线上影线最高点作为K线的参考点,能量堆的最高点可能和K线的最高点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。 底背离 - 确认买点 在某个时间级别,MACD运行在零轴下方,一般出现价格的低位区。当K线的价格走势持续下跌,而MACD的黄白线或者能量柱却持续靠近零轴,即当前价格的低点比前一次低点好要低,而MACD指标的低点却比前一次低点高,但是下跌能量却在衰减的现象,于是价格跌无可跌,是短期买入信号 -底背离包括黄白线背离喝柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最低点作为参考点,K线上影线最低点作为K线的参考点,能量堆的最低点可能和K线的最低点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。 +底背离包括黄白线背离和柱背离两种情况,也可能出现多次背离的情况。线背离以白线的最低点作为参考点,K线上影线最低点作为K线的参考点,能量堆的最低点可能和K线的最低点不是一一对应,但是不影响判断,可以使用能量堆的面积进行计算。 -顶底背离高点有效性的方法 +顶底背离高低点有效性的方法 1. 通过顶底分型确认顶底背离的高低点 2. 通过次级别的背离确认当前级别的背离高点,这里的次级别不是单指一级次级别,可以是多级的 @@ -107,7 +106,7 @@ K线经过一波上涨或者下跌后,MACD的黄白线由高位回零轴且未 单位调整周期之内的背离是价格跟能量柱之间的关系,同时单位调整周期之内的背离,解决的是归零轴的需求 连续跳空的应用和意义 单位调整周期之内,能量堆连接在一起的时候,出现的价格跟能量柱之间的关系即为连续跳空,而连续跳空的意义 -1. 连续跳空背离解决归零轴的需求,主要是小级别的走势,比如5分钟的连续跳空背离则为归零轴走势,应为5分钟级别只包含一个3分钟级别,是5分钟内的小级别,因此,5分钟级别如果出现连续跳空背离,会导致5分钟级别MACD黄白线归零轴走势 +1. 连续跳空背离解决归零轴的需求,主要是小级别的走势,比如5分钟的连续跳空背离则为归零轴走势,因为5分钟级别只包含一个3分钟级别,是5分钟内的小级别,因此,5分钟级别如果出现连续跳空背离,会导致5分钟级别MACD黄白线归零轴走势 2. 连续跳空更大的作用是确认穿零轴之后的第一个背离参考点,当MACD黄白线穿零轴的时候,最重要的是确认当前线段的1号参考点,有了1号参考点,后续行情才有参考的对象。连续跳空往往发生在MACD黄白线刚刚穿零轴的位置,一般由零轴粘合的形态演化而成连续跳空,这是因为零轴粘合具有强支撑的特点,容易形成跳空走势。 穿零轴时的参考点确认方法 @@ -128,7 +127,7 @@ K线经过一波上涨或者下跌后,MACD的黄白线由高位回零轴且未 单位周期之内 + 隐形 + 分立跳空 + 背离 + 黄白线高位空 跳空产生的原理:跳空的产生是当前级别之下的小级别归零轴反弹或反抽导致的,比如1小时时间级别的单位调整周期跳空,是因为1小时时间级别之内包含3分钟,5分钟,15分钟,30分钟这些下级别。1小时级别的MACD归零轴的途中,必然导致其内部的小级别先于本级别归零轴。因此,当这些小级别归零轴后,如果产生反弹反抽的走势,则会导致当前1小时级别的MACD黄白线再一次被拉高,此时便产生了跳空的走势。 -时间级别在高位中归零轴的顺序是:由小级别到大级别一次归零轴。当K线经过一轮拉升下跌后,当前级别的MACD黄白线处在高位,此时如果K线进入调整状态,则这个时间级别所包含的小级别由小到大一次归零轴,直至本级别归零轴为止,如此才完成了次级别的单位调整周期的调整。 +时间级别在高位中归零轴的顺序是:由小级别到大级别依此归零轴。当K线经过一轮拉升下跌后,当前级别的MACD黄白线处在高位,此时如果K线进入调整状态,则这个时间级别所包含的小级别由小到大依次归零轴,直至本级别归零轴为止,如此才完成了次级别的单位调整周期的调整。 单位周期之内的分立跳空顶背离 MACD黄白线在零轴之上第一个单位调整周期 + 分立跳空背离(一次或多次) + 黄白线处在零轴的高位空 + 分立跳空隐形状态 @@ -147,7 +146,7 @@ MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离( 单位调整周期之间的背离,其核心的本质是描述某个时间级别在线段中的运行逻辑,即为线段完成调整的重要依据。 单位调整周期之间的背离是为了满足线段调整的需求 -判断某个时间级别线段结束趋势趋势的依据为:在某个时间级别的线段中,第二个单位调整周期与第一个单位调整周期之间产生背离关系,即为次线段终结的依据,而后出现的单位调整周期无论归零轴多少次,冲能量产生的逻辑上都是依次减弱直至趋于零。 +判断某个时间级别线段结束趋势的依据为:在某个时间级别的线段中,第二个单位调整周期与第一个单位调整周期之间产生背离关系,即为此线段终结的依据,而后出现的单位调整周期无论归零轴多少次,从能量产生的逻辑上都是依次减弱直至趋于零。 单位调整周期之间的背离而穿零轴变盘的依据是:当某个级别在线段中出现周期之间的背离形态,同时完成了线段的调整,但是其长级别MACD的黄白线处在高位空的形态时,当前级别的背离会导致穿零轴走势。 @@ -160,7 +159,7 @@ MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离( 2. 当前级别完成线段调整,长级别MACD的黄白线无限接近零轴 线段背离 -当某个时间级别升级之后,便产生了一条新的线段,如果线段和线段之间构成相悖关系时,我们称为线段背离,而线段背离则必然导致当前时间级别穿零轴。线段背离比较的时两个线段中最高或最低的白线DIF。 +当某个时间级别升级之后,便产生了一条新的线段,如果线段和线段之间构成相悖关系时,我们称为线段背离,而线段背离则必然导致当前时间级别穿零轴。线段背离比较的是两个线段中最高或最低的白线DIF。 动能不足 如果K线走势中不破前高点上涨动能衰减,或者不破前低点,下跌动能也衰减,这种形态称为动能不足,本质上和背离是一样的,都是能量衰减的一种变现,背离所具有的属性和原理适用于动能不足。 @@ -177,9 +176,9 @@ MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离( 2. 确认主级别归零轴后的形态属性 K线价格要触碰到EMA52均线的位置附近,同时MACD的黄白线无限接近零轴。也就是说,K线的价格一旦触碰到EMA52的位置附近,因为这个位置能否形成支撑反弹,主要是看归零轴的这两个条件能否一直保持,并开始归零轴反弹的第一个过程,即主级别所包含的小级别在零轴之下的超跌反弹。 3. 在归零轴的形态满足条件的情况下,确认子级别是否有高位空的形态 -当主级别第一次触碰到当前级别EMA52均线的位置附近时,我们要看包含在主级别之下的小级别在零轴的下方是否产生了高位空的形态。只有当这些小级别出现高位空的形态时,才能出现有效的归零轴的超跌反弹,而超跌反弹的变现则为主级别归零轴后出现的止跌反弹的走势。 +当主级别第一次触碰到当前级别EMA52均线的位置附近时,我们要看包含在主级别之下的小级别在零轴的下方是否产生了高位空的形态。只有当这些小级别出现高位空的形态时,才能出现有效的归零轴的超跌反弹,而超跌反弹的变向则为主级别归零轴后出现的止跌反弹的走势。 4. 确认零轴之下的最大子级别运行底部变盘的四个阶段 -当主级别进入底部区域后,小级别的超跌反弹将逐级别开始,而时间级别的运行逻辑则是冲小级别一次运行。因此,当主级别包含的小级别零轴之下的最后一个子级别完成调整后,主级别的归零轴反弹才正式开启。这些零轴之下的子级别的运行逻辑即为主级别底部变盘的四个阶段。 +当主级别进入底部区域后,小级别的超跌反弹将逐级别开始,而时间级别的运行逻辑则是从小级别依次运行。因此,当主级别包含的小级别零轴之下的最后一个子级别完成调整后,主级别的归零轴反弹才正式开启。这些零轴之下的子级别的运行逻辑即为主级别底部变盘的四个阶段。 第一阶段:零轴之下的子级别的单边下跌 第二阶段:零轴之下的子级别的超跌反弹 第三阶段:零轴之下的子级别的归零轴反抽之后产生背离/动能不足 diff --git a/strategies/ChanLun_BTC_K.py b/strategies/ChanLun_BTC_K.py index 626cb25..e294254 100644 --- a/strategies/ChanLun_BTC_K.py +++ b/strategies/ChanLun_BTC_K.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) # freqtrade plot-dataframe --strategy ChanLun_BTC_K --datadir user_data/data/binance -c ./user_data/Chan/config/ChanLun_BTC_K.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_K.json --strategy ChanLun_BTC_K --strategy-path ./user_data/Chan/strategies -# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_K.json --strategy ChanLun_BTC_K --strategy-path ./user_data/Chan/strategies --timerange=20250721- +# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_K.json --strategy ChanLun_BTC_K --strategy-path ./user_data/Chan/strategies --timerange=20250701- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_K.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_BTC_K --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_K.json -e 200 --timerange=20250201-20250401 @@ -38,16 +38,29 @@ class ChanLun_BTC_K(IStrategy): # 策略参数 minimal_roi = { - "0": 0.05, # 5% 利润即可退出 - "30": 0.03, # 30分钟后3%利润退出 - "60": 0.02, # 1小时后2%利润退出 - "120": 0.01 # 2小时后1%利润退出 + "0": 0.004, # 0.4% + "15": 0.006, # 15分钟0.6% + "30": 0.008, # 30分钟0.8% + "60": 0.01 # 60分钟1.0% } stoploss = -0.03 # 3%止损 + use_custom_stoploss = True + startup_candle_count = 200 + + def get_ticker_indicator(self) -> int: + """返回基础时间框架的分钟数(如 '1m' -> 1)。""" + tf = str(self.timeframe).strip().lower() + if tf.endswith('m'): + return int(tf[:-1]) + if tf.endswith('h'): + return int(tf[:-1]) * 60 + if tf.endswith('d'): + return int(tf[:-1]) * 60 * 24 + return 1 # 时间框架 - timeframe = '1m' + timeframe = '5m' # 指标参数 macd_fast = 12 @@ -73,25 +86,117 @@ class ChanLun_BTC_K(IStrategy): # EMA均线 dataframe['ema_24'] = ta.EMA(dataframe, timeperiod=self.ema_short) dataframe['ema_52'] = ta.EMA(dataframe, timeperiod=self.ema_long) + + # 多时间周期(3x、5x、15x)聚合与指标 + base_min = self.get_ticker_indicator() + intervals = { + 'x3': base_min * 3, + 'x5': base_min * 5, + 'x15': base_min * 15, + 'x60': base_min * 60, + } + + def build_htf(df_resampled: DataFrame, suffix: str) -> DataFrame: + macd_htf = ta.MACD(df_resampled, fastperiod=self.macd_fast, slowperiod=self.macd_slow, signalperiod=self.macd_signal) + df_resampled[f'macd_{suffix}'] = macd_htf['macd'] + df_resampled[f'macdsignal_{suffix}'] = macd_htf['macdsignal'] + df_resampled[f'macdhist_{suffix}'] = macd_htf['macdhist'] + df_resampled[f'ema_24_{suffix}'] = ta.EMA(df_resampled, timeperiod=self.ema_short) + df_resampled[f'ema_52_{suffix}'] = ta.EMA(df_resampled, timeperiod=self.ema_long) + # ATR及其百分比(用于波动过滤/动态止损) + df_resampled[f'atr_{suffix}'] = ta.ATR(df_resampled, timeperiod=14) + df_resampled[f'atr_pct_{suffix}'] = df_resampled[f'atr_{suffix}'] / df_resampled['close'] + # 近零轴/方向 + zero_dist = np.sqrt(np.square(df_resampled[f'macd_{suffix}']) + np.square(df_resampled[f'macdsignal_{suffix}'])) + zero_dist_ema = zero_dist.ewm(span=50, adjust=False).mean() + zero_eps = zero_dist_ema * 0.2 + df_resampled[f'above_zero_{suffix}'] = (df_resampled[f'macd_{suffix}'] > 0) & (df_resampled[f'macdsignal_{suffix}'] > 0) + df_resampled[f'below_zero_{suffix}'] = (df_resampled[f'macd_{suffix}'] < 0) & (df_resampled[f'macdsignal_{suffix}'] < 0) + df_resampled[f'near_zero_{suffix}'] = (np.abs(df_resampled[f'macd_{suffix}']) < zero_eps) & (np.abs(df_resampled[f'macdsignal_{suffix}']) < zero_eps) + df_resampled[f'hist_increasing_{suffix}'] = df_resampled[f'macdhist_{suffix}'] > df_resampled[f'macdhist_{suffix}'].shift(1) + df_resampled[f'hist_decreasing_{suffix}'] = df_resampled[f'macdhist_{suffix}'] < df_resampled[f'macdhist_{suffix}'].shift(1) + # 高位:远离零轴 + df_resampled[f'high_position_{suffix}'] = zero_dist > (zero_dist_ema * 1.5) + # 金叉/死叉 + df_resampled[f'macd_cross_up_{suffix}'] = (df_resampled[f'macd_{suffix}'] > df_resampled[f'macdsignal_{suffix}']) & (df_resampled[f'macd_{suffix}'].shift(1) <= df_resampled[f'macdsignal_{suffix}'].shift(1)) + df_resampled[f'macd_cross_down_{suffix}'] = (df_resampled[f'macd_{suffix}'] < df_resampled[f'macdsignal_{suffix}']) & (df_resampled[f'macd_{suffix}'].shift(1) >= df_resampled[f'macdsignal_{suffix}'].shift(1)) + return df_resampled[[ + 'date', + 'close', + f'macd_{suffix}', f'macdsignal_{suffix}', f'macdhist_{suffix}', + f'ema_24_{suffix}', f'ema_52_{suffix}', + f'atr_{suffix}', f'atr_pct_{suffix}', + f'above_zero_{suffix}', f'below_zero_{suffix}', f'near_zero_{suffix}', f'hist_increasing_{suffix}', f'hist_decreasing_{suffix}', + f'high_position_{suffix}', f'macd_cross_up_{suffix}', f'macd_cross_down_{suffix}' + ]] + + for suf, minutes in intervals.items(): + df_res = resample_to_interval(dataframe, minutes) + df_htf = build_htf(df_res, suf) + dataframe = resampled_merge(dataframe, df_htf) - # 零轴判断 + # 动态阈值与距离定义 + # 距离零轴的合成距离,用于高位/近零判定 + dataframe['macd_abs'] = np.abs(dataframe['macd']) + dataframe['macdsignal_abs'] = np.abs(dataframe['macdsignal']) + dataframe['zero_dist'] = np.sqrt(np.square(dataframe['macd']) + np.square(dataframe['macdsignal'])) + dataframe['zero_dist_ema'] = dataframe['zero_dist'].ewm(span=50, adjust=False).mean() + # 近零动态阈值(零轴“无限接近”的量化) + dataframe['zero_eps'] = (dataframe['zero_dist_ema'] * 0.2).clip(lower=1e-8) + + # 零轴判断(方向与近零) dataframe['above_zero'] = (dataframe['macd'] > 0) & (dataframe['macdsignal'] > 0) dataframe['below_zero'] = (dataframe['macd'] < 0) & (dataframe['macdsignal'] < 0) - dataframe['cross_zero'] = ( - (dataframe['macd'].shift(1) < 0) & (dataframe['macd'] > 0) | - (dataframe['macdsignal'].shift(1) < 0) & (dataframe['macdsignal'] > 0) + dataframe['near_zero_fast'] = dataframe['macd_abs'] < dataframe['zero_eps'] + dataframe['near_zero_slow'] = dataframe['macdsignal_abs'] < dataframe['zero_eps'] + dataframe['near_zero'] = dataframe['near_zero_fast'] & dataframe['near_zero_slow'] + # MACD与信号线穿越零轴(当根事件,用于阶段/线段识别) + dataframe['cross_zero_up'] = ( + ((dataframe['macd'].shift(1) <= 0) & (dataframe['macd'] > 0)) | + ((dataframe['macdsignal'].shift(1) <= 0) & (dataframe['macdsignal'] > 0)) ) + dataframe['cross_zero_down'] = ( + ((dataframe['macd'].shift(1) >= 0) & (dataframe['macd'] < 0)) | + ((dataframe['macdsignal'].shift(1) >= 0) & (dataframe['macdsignal'] < 0)) + ) + dataframe['cross_zero'] = dataframe['cross_zero_up'] | dataframe['cross_zero_down'] + + # 价格触碰/接近EMA52(文档:K线触碰EMA52附近) + dataframe['price_above_ema52'] = dataframe['close'] > dataframe['ema_52'] + dataframe['price_below_ema52'] = dataframe['close'] < dataframe['ema_52'] + dataframe['price_near_ema52'] = (np.abs(dataframe['close'] - dataframe['ema_52']) / dataframe['ema_52']) < 0.003 + dataframe['touch_zero_by_price'] = dataframe['price_near_ema52'] + # MACD白线(DIF)无限接近零轴(文档:白线靠近零轴) + dataframe['touch_zero_by_macd'] = dataframe['near_zero_fast'] + + # 有效击穿/突破EMA52与零轴(延后确认,信号在确认K线产生,避免前瞻) + # 上破EMA52,并在2根K线后仍然在其上 + cond_break_up = (dataframe['close'].shift(2) > dataframe['ema_52'].shift(2)) & ( + (dataframe['close'].shift(3) <= dataframe['ema_52'].shift(3)) + ) + dataframe['effective_break_ema52_up'] = cond_break_up.fillna(False) + # 下破EMA52,并在2根K线后仍然在其下 + cond_break_down = (dataframe['close'].shift(2) < dataframe['ema_52'].shift(2)) & ( + (dataframe['close'].shift(3) >= dataframe['ema_52'].shift(3)) + ) + dataframe['effective_break_ema52_down'] = cond_break_down.fillna(False) + + # 黄线(慢线:DEA)有效击穿零轴(2根K线后确认) + cond_dea_up = (dataframe['macdsignal'].shift(2) > 0) & (dataframe['macdsignal'].shift(3) <= 0) + cond_dea_down = (dataframe['macdsignal'].shift(2) < 0) & (dataframe['macdsignal'].shift(3) >= 0) + dataframe['effective_dea_cross_up'] = cond_dea_up.fillna(False) + dataframe['effective_dea_cross_down'] = cond_dea_down.fillna(False) # 高位空形态检测 # 高位空:MACD黄白线处于高位,K线缓慢上涨或横盘,能量柱衰减,形成夹角 - dataframe['high_position'] = ( - # MACD黄白线远离零轴(高位) - ((dataframe['macd'] > 50) & (dataframe['macdsignal'] > 50)) | - ((dataframe['macd'] < -50) & (dataframe['macdsignal'] < -50)) - ) + # 高位:距离零轴远离,采用动态阈值(> 1.5x 距离均值) + dataframe['high_position'] = dataframe['zero_dist'] > (dataframe['zero_dist_ema'] * 1.5) # 能量柱衰减检测 dataframe['histogram_decreasing'] = dataframe['macdhist'] < dataframe['macdhist'].shift(1) dataframe['histogram_increasing'] = dataframe['macdhist'] > dataframe['macdhist'].shift(1) + # 线条“横盘”(变化不大):3根之前差值很小 + dataframe['macd_flat_3'] = (np.abs(dataframe['macd'] - dataframe['macd'].shift(3)) < dataframe['zero_eps']) + dataframe['macdsignal_flat_3'] = (np.abs(dataframe['macdsignal'] - dataframe['macdsignal'].shift(3)) < dataframe['zero_eps']) # 高位空形态:高位 + 能量柱衰减 + 黄白线横盘 dataframe['high_position_empty'] = ( @@ -100,26 +205,148 @@ class ChanLun_BTC_K(IStrategy): # K线缓慢上涨或横盘(价格变化不大) (abs(dataframe['close'] - dataframe['close'].shift(3)) / dataframe['close'].shift(3) < 0.02) & # MACD黄白线横盘(变化不大) - (abs(dataframe['macd'] - dataframe['macd'].shift(3)) < 0.05) & - (abs(dataframe['macdsignal'] - dataframe['macdsignal'].shift(3)) < 0.05) + dataframe['macd_flat_3'] & + dataframe['macdsignal_flat_3'] ) - - # 归零轴检测 - dataframe['near_zero'] = ( - (abs(dataframe['macd']) < 0.1) & (abs(dataframe['macdsignal']) < 0.1) + # 归零轴四种走势(近似量化) + # 1) 触碰EMA52(由上至下或下至上) + dataframe['zero_touch_ema52'] = dataframe['price_near_ema52'] + # 2) 白线无限接近零轴 + dataframe['zero_near_fastline'] = dataframe['near_zero_fast'] + # 3) 零轴粘合:刚穿零轴后,|黄白线|均小,hist不释放反向能量柱,斜率小(横向) + small_lines = (dataframe['macd_abs'] < dataframe['zero_eps'] * 1.2) & (dataframe['macdsignal_abs'] < dataframe['zero_eps'] * 1.2) + same_side_hist = ( + ((dataframe['macdhist'] >= 0) & dataframe['cross_zero_up']) | + ((dataframe['macdhist'] <= 0) & dataframe['cross_zero_down']) ) + dataframe['zero_axis_adhesion'] = small_lines & same_side_hist + # 4) K线先触碰EMA52,而黄白线未归零 + dataframe['zero_touch_price_first'] = dataframe['price_near_ema52'] & (~dataframe['near_zero']) + + # 零轴纠缠:黄白线反复在近零区上下缠绕(5根内多次变号或绝对值很小) + near_zero_many = dataframe['near_zero'].rolling(5).sum() >= 3 + sign_flip_fast = (np.sign(dataframe['macd']) != np.sign(dataframe['macd'].shift(1))) + sign_flip_slow = (np.sign(dataframe['macdsignal']) != np.sign(dataframe['macdsignal'].shift(1))) + dataframe['zero_axis_entanglement'] = near_zero_many | (sign_flip_fast & sign_flip_slow & dataframe['near_zero']) + + # 零轴倒挂:靠近零轴、能量柱衰减形成夹角、黄白线交叉并释放反向能量柱 + macd_cross = ((dataframe['macd'] > dataframe['macdsignal']) & (dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1))) | ( + (dataframe['macd'] < dataframe['macdsignal']) & (dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1)) + ) + hist_flip = np.sign(dataframe['macdhist']) != np.sign(dataframe['macdhist'].shift(1)) + dataframe['zero_axis_inverted'] = dataframe['near_zero'] & dataframe['histogram_decreasing'] & macd_cross & hist_flip - # 价格与EMA52关系 - dataframe['price_above_ema52'] = dataframe['close'] > dataframe['ema_52'] - dataframe['price_below_ema52'] = dataframe['close'] < dataframe['ema_52'] - dataframe['price_near_ema52'] = abs(dataframe['close'] - dataframe['ema_52']) / dataframe['ema_52'] < 0.01 + # 隐形形态:无能量配合 + # 高位隐形:远离零轴、价格继续拉升/下跌,但hist未释放同向能量 + dataframe['hidden_high_bull'] = dataframe['above_zero'] & dataframe['high_position'] & (dataframe['close'] > dataframe['close'].shift(1)) & (dataframe['macdhist'] <= 0) + dataframe['hidden_high_bear'] = dataframe['below_zero'] & dataframe['high_position'] & (dataframe['close'] < dataframe['close'].shift(1)) & (dataframe['macdhist'] >= 0) + # 归零轴隐形:近零时应释放的支撑/压力能量未出现 -> 可能反向穿零 + dataframe['hidden_zero_bull_fail'] = dataframe['near_zero'] & dataframe['price_near_ema52'] & (dataframe['macdhist'] <= 0) + dataframe['hidden_zero_bear_fail'] = dataframe['near_zero'] & dataframe['price_near_ema52'] & (dataframe['macdhist'] >= 0) - # 背离检测 + # 斜率/拐点/金叉死叉(上下文门控) + dataframe['ema24_slope_up'] = dataframe['ema_24'] > dataframe['ema_24'].shift(1) + dataframe['ema24_slope_down'] = dataframe['ema_24'] < dataframe['ema_24'].shift(1) + dataframe['hist_turn_up'] = (dataframe['macdhist'] > dataframe['macdhist'].shift(1)) & (dataframe['macdhist'].shift(1) <= dataframe['macdhist'].shift(2)) + dataframe['hist_turn_down'] = (dataframe['macdhist'] < dataframe['macdhist'].shift(1)) & (dataframe['macdhist'].shift(1) >= dataframe['macdhist'].shift(2)) + dataframe['macd_cross_up'] = (dataframe['macd'] > dataframe['macdsignal']) & (dataframe['macd'].shift(1) <= dataframe['macdsignal'].shift(1)) + dataframe['macd_cross_down'] = (dataframe['macd'] < dataframe['macdsignal']) & (dataframe['macd'].shift(1) >= dataframe['macdsignal'].shift(1)) + + # 线段与单位调整周期(近似) - 需在背离检测之前生成 + # 线段:以黄线穿零轴划分段(上穿为上涨线段,下穿为下跌线段) + seg_change = (((dataframe['macdsignal'] <= 0) & (dataframe['macdsignal'].shift(1) > 0)) | ((dataframe['macdsignal'] >= 0) & (dataframe['macdsignal'].shift(1) < 0))) + dataframe['segment_id'] = seg_change.cumsum().fillna(0).astype(int) + # 单位调整周期:由近零出发-远离-回到近零(用近零作为粗略起止标记) + dataframe['near_zero_flag'] = dataframe['near_zero'].astype(int) + dataframe['unit_cycle_id'] = (dataframe['near_zero_flag'].diff().fillna(0) > 0).cumsum().astype(int) + + # 背离检测(线段内) dataframe = self.detect_divergence(dataframe) - + # 跳空检测 dataframe = self.detect_gaps(dataframe) + + # V字反转:近零+收敛+突破横盘区 + price_break = dataframe['close'] > dataframe['close'].rolling(10).max().shift(1) + macd_converge = dataframe['histogram_decreasing'].rolling(4).sum() >= 3 + dataframe['v_reversal'] = dataframe['near_zero'] & dataframe['price_above_ema52'] & macd_converge & price_break + + # 抢底原理(第三阶段:底背离/动能不足触发) + momentum_lack = (dataframe['below_zero'] & dataframe['histogram_decreasing'] & (dataframe['close'] <= dataframe['close'].shift(1))) + dataframe['bottom_snap_buy'] = dataframe['near_zero'] & (dataframe['bottom_divergence'] | momentum_lack) + + # 归零轴强支撑(零轴粘合 + EMA52支撑) + dataframe['zero_adhesion_support'] = dataframe['zero_axis_adhesion'] & dataframe['price_near_ema52'] & dataframe['price_above_ema52'] + + # 高周期门控(5x 与 60x),注意列名经过 resampled_merge 改名:resample_{minutes}_ + min3 = intervals['x3'] + min5 = intervals['x5'] + min15 = intervals['x15'] + min60 = intervals['x60'] + ema24_5 = f'resample_{min5}_ema_24_x5' + ema52_5 = f'resample_{min5}_ema_52_x5' + above0_5 = f'resample_{min5}_above_zero_x5' + near0_5 = f'resample_{min5}_near_zero_x5' + macds_5 = f'resample_{min5}_macdsignal_x5' + d5 = f'resample_{min5}_date' + close5 = f'resample_{min5}_close' + ema24_60 = f'resample_{min60}_ema_24_x60' + ema52_60 = f'resample_{min60}_ema_52_x60' + macds_60 = f'resample_{min60}_macdsignal_x60' + above0_60 = f'resample_{min60}_above_zero_x60' + near0_60 = f'resample_{min60}_near_zero_x60' + date_60 = f'resample_{min60}_date' + + dataframe['htf_buy_gate'] = ( + (dataframe.get(ema24_60, np.nan) > dataframe.get(ema52_60, np.nan)) & + ((dataframe.get(above0_60, False)) | (dataframe.get(near0_60, False)) | (dataframe.get(macds_60, np.nan) >= 0)) + ).fillna(False) + dataframe['htf_sell_gate'] = ( + (dataframe.get(ema24_60, np.nan) < dataframe.get(ema52_60, np.nan)) | (dataframe.get(macds_60, np.nan) < 0) + ).fillna(False) + + # 1m 对 60m 均线关系 + dataframe['price_above_ema52_x60'] = (dataframe['close'] > dataframe.get(ema52_60, np.nan)).fillna(False) + dataframe['price_near_ema52_x60'] = (np.abs(dataframe['close'] - dataframe.get(ema52_60, np.nan)) / dataframe.get(ema52_60, np.nan) < 0.003).fillna(False) + # 提供无前缀别名,供买卖条件使用(60m) + dataframe['near_zero_x60'] = dataframe.get(near0_60, False) + dataframe['macdsignal_x60'] = dataframe.get(macds_60, np.nan) + dataframe['ema24_x60'] = dataframe.get(ema24_60, np.nan) + dataframe['ema52_x60'] = dataframe.get(ema52_60, np.nan) + dataframe['hist_increasing_x60'] = dataframe.get(f'resample_{min60}_hist_increasing_x60', False) + dataframe['hist_decreasing_x60'] = dataframe.get(f'resample_{min60}_hist_decreasing_x60', False) + dataframe['macd_cross_up_60m'] = dataframe.get(f'resample_{min60}_macd_cross_up_x60', False) + dataframe['macd_cross_down_60m'] = dataframe.get(f'resample_{min60}_macd_cross_down_x60', False) + # 60m边界触发:仅在新60m开始的一根1m上允许交易(直接比较,避免时区转换问题) + d60 = dataframe.get(date_60) + dataframe['is_new_60m'] = d60.ne(d60.shift(1)).fillna(False) + # 5m边界(用于5m信号仅在新5m产生) + d5s = dataframe.get(d5) + dataframe['is_new_5m'] = d5s.ne(d5s.shift(1)).fillna(False) + # 60m开始后前5分钟内也允许交易 + if 'date' in dataframe.columns: + dt_delta60 = (dataframe['date'] - d60) + dataframe['within_first_60m5'] = dt_delta60.dt.total_seconds().div(60).between(0, 10).fillna(False) + else: + dataframe['within_first_60m5'] = dataframe['is_new_60m'] + # 60m波动过滤 + dataframe['atr_pct_x60'] = dataframe.get(f'resample_{min60}_atr_pct_x60', np.nan) + dataframe['htf_vol_ok'] = (dataframe['atr_pct_x60'] > 0.0005).fillna(False) + # 价格接近1h EMA24(小回踩判定) + dataframe['price_near_ema24_x60'] = (np.abs(dataframe['close'] - dataframe.get('ema24_x60', np.nan)) / dataframe.get('ema24_x60', np.nan) < 0.0015).fillna(False) + # 5m别名与突破判定 + dataframe['near_zero_x5'] = dataframe.get(near0_5, False) + dataframe['macd_cross_up_5m'] = dataframe.get(f'resample_{min5}_macd_cross_up_x5', False) + dataframe['macd_cross_down_5m'] = dataframe.get(f'resample_{min5}_macd_cross_down_x5', False) + dataframe['ema24_x5'] = dataframe.get(ema24_5, np.nan) + dataframe['ema52_x5'] = dataframe.get(ema52_5, np.nan) + dataframe['close_x5'] = dataframe.get(close5, np.nan) + # 5m突破:新5m且收盘突破近20根5m最高收盘 + dataframe['breakout_5m'] = ( + dataframe['is_new_5m'] & + (dataframe['close_x5'] > dataframe['close_x5'].rolling(20).max().shift(1)) + ).fillna(False) return dataframe @@ -127,48 +354,61 @@ class ChanLun_BTC_K(IStrategy): """ 检测背离形态 """ - # 顶背离检测 - dataframe['top_divergence'] = False - dataframe['bottom_divergence'] = False - - for i in range(self.divergence_lookback, len(dataframe)): - # 顶背离:价格创新高,MACD未创新高 - if (dataframe['close'].iloc[i] > dataframe['close'].iloc[i-self.divergence_lookback:i].max() and - dataframe['macd'].iloc[i] < dataframe['macd'].iloc[i-self.divergence_lookback:i].max() and - dataframe['above_zero'].iloc[i]): - dataframe.loc[dataframe.index[i], 'top_divergence'] = True - - # 底背离:价格创新低,MACD未创新低 - if (dataframe['close'].iloc[i] < dataframe['close'].iloc[i-self.divergence_lookback:i].min() and - dataframe['macd'].iloc[i] > dataframe['macd'].iloc[i-self.divergence_lookback:i].min() and - dataframe['below_zero'].iloc[i]): - dataframe.loc[dataframe.index[i], 'bottom_divergence'] = True - - return dataframe + # 顶/底背离(限制在同一线段内比较,避免跨段) + df = dataframe + df['top_divergence'] = False + df['bottom_divergence'] = False + + # 线段内的滚动极值(不跨段) + seg_group = df.groupby('segment_id', group_keys=False) + seg_close_max_prev = seg_group['close'].apply(lambda s: s.cummax().shift(1)) + seg_macd_max_prev = seg_group['macd'].apply(lambda s: s.cummax().shift(1)) + seg_close_min_prev = seg_group['close'].apply(lambda s: s.cummin().shift(1)) + seg_macd_min_prev = seg_group['macd'].apply(lambda s: s.cummin().shift(1)) + + cond_top = (df['close'] > seg_close_max_prev) & (df['macd'] < seg_macd_max_prev) & df['above_zero'] + cond_bottom = (df['close'] < seg_close_min_prev) & (df['macd'] > seg_macd_min_prev) & df['below_zero'] + df.loc[cond_top.fillna(False), 'top_divergence'] = True + df.loc[cond_bottom.fillna(False), 'bottom_divergence'] = True + + return df def detect_gaps(self, dataframe: DataFrame) -> DataFrame: """ 检测跳空形态 """ - # 连续跳空检测 - dataframe['continuous_gap'] = False - dataframe['separate_gap'] = False - - for i in range(5, len(dataframe)): - # 连续跳空:能量柱连续增长 - if (dataframe['histogram_increasing'].iloc[i-2:i+1].all() and - dataframe['macdhist'].iloc[i] > 0 and - dataframe['macdhist'].iloc[i] > dataframe['macdhist'].iloc[i-1]): - dataframe.loc[dataframe.index[i], 'continuous_gap'] = True - - # 分立跳空:能量柱被反向能量柱分隔 - if (i > 10 and - dataframe['macdhist'].iloc[i] > 0 and - dataframe['macdhist'].iloc[i-5:i].min() < 0 and - dataframe['macdhist'].iloc[i] > dataframe['macdhist'].iloc[i-5:i].max()): - dataframe.loc[dataframe.index[i], 'separate_gap'] = True - - return dataframe + # 连续跳空/分立跳空(单位周期内的近似判定) + df = dataframe + df['continuous_gap'] = False + df['separate_gap'] = False + + # 能量柱包含在黄白线之内:|hist| <= max(|macd|, |signal|) + hist_within_lines = df['macdhist'].abs() <= np.maximum(df['macd_abs'], df['macdsignal_abs']) + + for i in range(5, len(df)): + # 连续跳空:同向能量柱在单位周期中由衰减转为增长,且能量柱包含在线内 + if ( + df['histogram_increasing'].iloc[i-2:i+1].all() and + hist_within_lines.iloc[i-2:i+1].all() and + ((df['macdhist'].iloc[i] > 0) | (df['macdhist'].iloc[i] < 0)) and + # 近似单位周期:最近出现过near_zero且当前未再次near_zero + (df['near_zero'].iloc[i-10:i].any()) and (not df['near_zero'].iloc[i]) + ): + df.loc[df.index[i], 'continuous_gap'] = True + + # 分立跳空:两个同向能量堆被反向能量柱分隔,且远离零轴 + if i > 10: + recent_hist = df['macdhist'].iloc[i-10:i+1] + same_dir = (recent_hist.max() > 0 and recent_hist.min() < 0) + far_from_zero = df['high_position'].iloc[i] + if same_dir and far_from_zero: + # 当前是同向新峰,且此前5根内出现过反向柱 + if (df['macdhist'].iloc[i] > 0 and (recent_hist.iloc[-6:-1] < 0).any() and df['macdhist'].iloc[i] > recent_hist.iloc[:-1].max()) or ( + df['macdhist'].iloc[i] < 0 and (recent_hist.iloc[-6:-1] > 0).any() and df['macdhist'].iloc[i] < recent_hist.iloc[:-1].min() + ): + df.loc[df.index[i], 'separate_gap'] = True + + return df def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ @@ -176,48 +416,54 @@ class ChanLun_BTC_K(IStrategy): """ conditions = [] - # 条件1: 底背离确认买点 + # 条件1: 60m门控 + 底背离确认买点(同段内) conditions.append( dataframe['bottom_divergence'] & dataframe['below_zero'] & - dataframe['price_near_ema52'] + (dataframe['price_near_ema52_x60'] | dataframe['near_zero_x60'] | dataframe['near_zero']) & + (dataframe['ema24_slope_up']) & dataframe['htf_vol_ok'] & + (dataframe['htf_buy_gate']) & (dataframe['is_new_60m'] | dataframe['within_first_60m5']) ) # 条件2: 单位调整周期内的连续跳空背离 conditions.append( dataframe['continuous_gap'] & dataframe['below_zero'] & - dataframe['near_zero'] + dataframe['near_zero'] & + (dataframe['ema24_slope_up']) & dataframe['htf_vol_ok'] & + (dataframe['price_near_ema52_x60'] | dataframe['near_zero_x60'] | dataframe['price_near_ema24_x60']) & + (dataframe['htf_buy_gate']) ) - # 条件3: 底部形态V字反转 + # 条件3: 底部形态V字反转 或 5m突破 + conditions.append( + ( + dataframe['v_reversal'] & dataframe['macd_cross_up'] | + (dataframe['breakout_5m'] & dataframe['macd_cross_up_5m']) + ) & + (dataframe['price_above_ema52_x60'] | (dataframe['macdsignal_x60'] >= 0)) & dataframe['htf_vol_ok'] & + (dataframe['htf_buy_gate']) + ) + + # 条件4: 抢底原理(第三阶段:底背离/动能不足叠加) + conditions.append( + dataframe['bottom_snap_buy'] & (dataframe['macd_cross_up'] | dataframe['hist_turn_up']) & + (dataframe['price_near_ema52_x60'] | dataframe['near_zero_x60'] | dataframe['price_near_ema24_x60']) & (dataframe['htf_buy_gate']) & dataframe['htf_vol_ok'] + ) + + # 条件5: 归零轴反弹(近零+EMA52附近+能量回升) conditions.append( - dataframe['price_above_ema52'] & dataframe['near_zero'] & + (dataframe['price_near_ema52_x60'] | dataframe['price_near_ema24_x60']) & dataframe['histogram_increasing'] & - (dataframe['close'] > dataframe['close'].shift(5)) + (dataframe['close'] > dataframe['close'].shift(1)) & + (dataframe['ema24_slope_up']) & (dataframe['htf_buy_gate']) & dataframe['htf_vol_ok'] ) - # 条件4: 抢底原理(第三阶段背离/动能不足) + # 条件6: 零轴粘合强支撑买入(文档强调强支撑、弱反弹) conditions.append( - dataframe['below_zero'] & - dataframe['near_zero'] & - dataframe['histogram_decreasing'] & - (dataframe['macd'] > dataframe['macd'].shift(3)) # MACD开始收敛 - ) - - # 条件5: 归零轴反弹 - conditions.append( - dataframe['near_zero'] & - dataframe['price_near_ema52'] & - dataframe['histogram_increasing'] & - (dataframe['close'] > dataframe['close'].shift(1)) - ) - - # 条件6: 零轴之下高位空形态(归零轴需求) - conditions.append( - dataframe['high_position_empty'] & - dataframe['below_zero'] + dataframe['zero_adhesion_support'] & dataframe['macd_cross_up'] & + (dataframe['price_above_ema52_x60'] | dataframe['near_zero_x60'] | dataframe['price_near_ema24_x60']) & (dataframe['htf_buy_gate']) & dataframe['htf_vol_ok'] ) if conditions: @@ -233,7 +479,7 @@ class ChanLun_BTC_K(IStrategy): """ conditions = [] - # 条件1: 顶背离确认卖点 + # 条件1: 顶背离确认卖点(同段内) conditions.append( dataframe['top_divergence'] & dataframe['above_zero'] @@ -242,28 +488,26 @@ class ChanLun_BTC_K(IStrategy): # 条件2: 高位空形态 conditions.append( dataframe['high_position_empty'] & - dataframe['above_zero'] + dataframe['above_zero'] & + (dataframe['macd_cross_down'] | dataframe['hist_turn_down']) ) - # 条件3: 穿零轴下跌 + # 条件3: 有效穿零轴下跌(慢线有效击穿 + EMA52下) conditions.append( - dataframe['cross_zero'] & + (dataframe['effective_dea_cross_down'] | dataframe['cross_zero_down'] | dataframe['htf_sell_gate']) & dataframe['price_below_ema52'] & - (dataframe['macd'] < 0) + (dataframe['ema24_slope_down']) ) # 条件4: 能量柱隐形形态(无能量配合的上涨) conditions.append( - dataframe['above_zero'] & - (dataframe['macdhist'] < 0) & - (dataframe['close'] > dataframe['close'].shift(1)) + dataframe['hidden_high_bull'] & (dataframe['macd_cross_down'] | dataframe['hist_turn_down']) ) - # 条件5: 线段背离(价格创新高但MACD未创新高) + # 条件5: 零轴倒挂(弱支撑弱反弹,易继续下行) conditions.append( - dataframe['above_zero'] & - (dataframe['close'] > dataframe['close'].shift(10).max()) & - (dataframe['macd'] < dataframe['macd'].shift(10).max()) + dataframe['zero_axis_inverted'] & + dataframe['above_zero'] ) if conditions: