""" 盘整背驰策略 (PanZhengBeiChi Strategy) 基于缠论的盘整背驰进行交易: - 盘整背驰:同级别走势中,Ai与Ai+2比较力度减弱 - 顶背驰(卖点):价格创新高或接近,但MACD力度明显减弱 - 底背驰(买点):价格创新低或接近,但MACD力度明显减弱 使用命令: freqtrade backtesting -c ./user_data/Chan/config/Local_Test.json \ --strategy PanZhengBeiChiStrategy --strategy-path ./user_data/Chan/strategies \ --timerange=20250301- """ import logging from datetime import datetime from typing import Optional import numpy as np import pandas as pd import talib.abstract as ta from pandas import DataFrame from technical.util import resample_to_interval, resampled_merge from freqtrade.strategy import IStrategy logger = logging.getLogger(__name__) class PanZhengBeiChiStrategy(IStrategy): """ 盘整背驰策略 核心逻辑: 1. 在5分钟级别识别同级别走势段(Ai) 2. 比较Ai与Ai+2的MACD力度,判断盘整背驰 3. 盘整顶背驰(i+2为偶数)-> 卖出 4. 盘整底背驰(i+2为奇数)-> 买入 """ INTERFACE_VERSION: int = 3 # === 基础配置 === timeframe = '1m' informative_timeframe = '5m' can_short = True can_long = True startup_candle_count: int = 2000 # 需要足够的数据来识别走势段 # === 止损止盈配置 === stoploss = -0.02 # 2% 硬止损 use_custom_stoploss = False # Trailing stop trailing_stop = True trailing_stop_positive = 0.008 # 回撤 0.8% 触发退出 trailing_stop_positive_offset = 0.015 # 盈利 1.5% 后才开始追踪 trailing_only_offset_is_reached = True # ROI - 调整止盈策略 minimal_roi = { "0": 0.015, # 1.5% 立即止盈(更保守) "30": 0.01, # 30分钟后 1% "120": 0.008, # 2小时后 0.8% } order_types = { "entry": "market", "exit": "market", "stoploss": "market", "stoploss_on_exchange": False, } # === 盘整背驰参数 === same_level_timeframe = 5 # 5分钟级别 pivot_window = 4 # 转折点确认窗口(增大减少噪音) min_segment_length = 5 # 最小段长度(K线数)(增大减少假信号) # 背驰判断参数(更严格) beichi_price_threshold = 1.10 # 价格涨幅/跌幅阈值(允许10%范围内,更严格) beichi_macd_threshold = 0.75 # MACD力度阈值(低于75%即背驰,更严格) def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """计算指标并识别盘整背驰""" ticker = self.get_ticker_indicator() # Resample 到 5m 进行同级别分解 dataframe_5m = resample_to_interval(dataframe, ticker * self.same_level_timeframe) # 在 5m 上计算指标 dataframe_5m = self.add_indicators_5m(dataframe_5m) # 识别盘整背驰 dataframe_5m = self.identify_panzheng_beichi(dataframe_5m) # 合并回 1m dataframe dataframe = resampled_merge(dataframe, dataframe_5m) # 在 1m 上也计算基础指标 dataframe = self.add_indicators_1m(dataframe) return dataframe def add_indicators_5m(self, dataframe: DataFrame) -> DataFrame: """在5m级别计算指标""" # MACD 用于识别背驰 macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) dataframe['macd'] = macd['macd'] dataframe['macdsignal'] = macd['macdsignal'] dataframe['macdhist'] = macd['macdhist'] # EMA 用于识别趋势 dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12) dataframe['ema26'] = ta.EMA(dataframe, timeperiod=26) dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema200'] = ta.EMA(dataframe, timeperiod=200) # EMA趋势方向 dataframe['ema_trend_up'] = (dataframe['ema12'] > dataframe['ema26']) & (dataframe['ema26'] > dataframe['ema50']) dataframe['ema_trend_dn'] = (dataframe['ema12'] < dataframe['ema26']) & (dataframe['ema26'] < dataframe['ema50']) # 价格与EMA200关系 dataframe['price_above_ema200'] = dataframe['close'] > dataframe['ema200'] dataframe['price_below_ema200'] = dataframe['close'] < dataframe['ema200'] # 趋势强度 dataframe['ema12_slope'] = dataframe['ema12'].diff(5) / dataframe['ema12'].shift(5) dataframe['ema26_slope'] = dataframe['ema26'].diff(5) / dataframe['ema26'].shift(5) # 强趋势判断 dataframe['strong_uptrend'] = ( (dataframe['ema12_slope'] > 0) & (dataframe['ema26_slope'] > 0) & (dataframe['price_above_ema200']) ) dataframe['strong_downtrend'] = ( (dataframe['ema12_slope'] < 0) & (dataframe['ema26_slope'] < 0) & (dataframe['price_below_ema200']) ) # RSI dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) # ATR 用于波动率过滤 dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe['atr_mean'] = dataframe['atr'].rolling(window=20).mean() dataframe['volatility_ok'] = dataframe['atr'] > dataframe['atr_mean'] * 0.8 return dataframe def add_indicators_1m(self, dataframe: DataFrame) -> DataFrame: """在1m级别计算基础指标""" dataframe['rsi_1m'] = ta.RSI(dataframe, timeperiod=14) dataframe['volume_mean'] = dataframe['volume'].rolling(window=20).mean() # MACD 用于1m级别确认 macd_1m = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) dataframe['macd_1m'] = macd_1m['macd'] dataframe['macdsignal_1m'] = macd_1m['macdsignal'] dataframe['macdhist_1m'] = macd_1m['macdhist'] # MACD交叉 dataframe['macd_cross_up'] = ( (dataframe['macd_1m'] > dataframe['macdsignal_1m']) & (dataframe['macd_1m'].shift(1) <= dataframe['macdsignal_1m'].shift(1)) ) dataframe['macd_cross_dn'] = ( (dataframe['macd_1m'] < dataframe['macdsignal_1m']) & (dataframe['macd_1m'].shift(1) >= dataframe['macdsignal_1m'].shift(1)) ) return dataframe def identify_panzheng_beichi(self, dataframe: DataFrame) -> DataFrame: """ 识别盘整背驰 核心逻辑: 1. 识别局部转折点(高低点) 2. 构建同级别走势段(Ai) 3. 比较Ai与Ai+2的MACD力度 4. 判断盘整背驰:价格涨幅相近但MACD力度减弱 """ df = dataframe.copy() window = self.pivot_window lookback = window + 1 # 初始化列 df['ai_index'] = -1 df['ai_type'] = 0 # 1: 上涨, -1: 下跌 df['ai_high'] = np.nan df['ai_low'] = np.nan df['ai_macd_max'] = np.nan df['ai_macd_min'] = np.nan df['beichi_long'] = False # 盘整底背驰(买入信号) df['beichi_short'] = False # 盘整顶背驰(卖出信号) # 识别局部高点 df['temp_high'] = df['high'].shift(window) df['is_pivot_high'] = ( (df['temp_high'] == df['temp_high'].rolling(window=lookback).max()) & (df['temp_high'].notna()) ) # 识别局部低点 df['temp_low'] = df['low'].shift(window) df['is_pivot_low'] = ( (df['temp_low'] == df['temp_low'].rolling(window=lookback).min()) & (df['temp_low'].notna()) ) # 逐行处理,识别走势段和背驰 ai_list = [] current_ai_start = None current_ai_type = None last_pivot_idx = None for i in range(window, len(df)): # 检查新的转折点 is_new_pivot = False pivot_type = None if df.iloc[i]['is_pivot_high']: is_new_pivot = True pivot_type = 'high' elif df.iloc[i]['is_pivot_low']: is_new_pivot = True pivot_type = 'low' if is_new_pivot and last_pivot_idx is not None: # 完成一个走势段 if current_ai_start is not None: seg_df = df.iloc[current_ai_start:last_pivot_idx] if len(seg_df) >= self.min_segment_length: high_val = seg_df['high'].max() low_val = seg_df['low'].min() macd_max = seg_df['macd'].max() macd_min = seg_df['macd'].min() # 判断走势类型 if current_ai_type is None: if high_val > df.iloc[current_ai_start]['close']: current_ai_type = 1 else: current_ai_type = -1 ai_info = { 'start': current_ai_start, 'end': last_pivot_idx, 'type': current_ai_type, 'high': high_val, 'low': low_val, 'macd_max': macd_max, 'macd_min': macd_min, } ai_list.append(ai_info) # 标记该段 df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_index')] = len(ai_list) - 1 df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_type')] = current_ai_type df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_high')] = high_val df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_low')] = low_val df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_macd_max')] = macd_max df.iloc[current_ai_start:last_pivot_idx, df.columns.get_loc('ai_macd_min')] = macd_min # 判断背驰(Ai与Ai+2比较) if len(ai_list) >= 3: ai = ai_list[-3] # Ai ai_plus_2 = ai_list[-1] # Ai+2 if ai['type'] == ai_plus_2['type']: # 上涨段:比较向上力度 if ai['type'] == 1: price_chg = (ai_plus_2['high'] - ai_plus_2['low']) / ai_plus_2['low'] if ai_plus_2['low'] > 0 else 0 price_chg_prev = (ai['high'] - ai['low']) / ai['low'] if ai['low'] > 0 else 0 macd_chg = ai_plus_2['macd_max'] macd_chg_prev = ai['macd_max'] # 顶背驰:价格涨幅相近但MACD力度减弱 if price_chg <= price_chg_prev * self.beichi_price_threshold and \ macd_chg < macd_chg_prev * self.beichi_macd_threshold: idx = len(ai_list) - 1 # i+2的索引 if idx % 2 == 0: # 偶数 -> 卖出 df.iloc[last_pivot_idx, df.columns.get_loc('beichi_short')] = True else: # 奇数 -> 买入 df.iloc[last_pivot_idx, df.columns.get_loc('beichi_long')] = True # 下跌段:比较向下力度 else: price_chg = abs((ai_plus_2['high'] - ai_plus_2['low']) / ai_plus_2['low']) if ai_plus_2['low'] > 0 else 0 price_chg_prev = abs((ai['high'] - ai['low']) / ai['low']) if ai['low'] > 0 else 0 macd_chg = abs(ai_plus_2['macd_min']) macd_chg_prev = abs(ai['macd_min']) # 底背驰:价格跌幅相近但MACD力度减弱 if price_chg <= price_chg_prev * self.beichi_price_threshold and \ macd_chg < macd_chg_prev * self.beichi_macd_threshold: idx = len(ai_list) - 1 if idx % 2 == 0: # 偶数 -> 卖出 df.iloc[last_pivot_idx, df.columns.get_loc('beichi_short')] = True else: # 奇数 -> 买入 df.iloc[last_pivot_idx, df.columns.get_loc('beichi_long')] = True # 更新当前段信息 if pivot_type == 'high': current_ai_type = -1 # 高点后向下 else: current_ai_type = 1 # 低点后向上 current_ai_start = last_pivot_idx if is_new_pivot: last_pivot_idx = i return df def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: ticker = self.get_ticker_indicator() resample_col = f"resample_{ticker * self.same_level_timeframe}_" # 5m 级别指标列名 beichi_long_col = f"{resample_col}beichi_long" beichi_short_col = f"{resample_col}beichi_short" ai_type_col = f"{resample_col}ai_type" rsi_5m_col = f"{resample_col}rsi" ema_trend_up_col = f"{resample_col}ema_trend_up" ema_trend_dn_col = f"{resample_col}ema_trend_dn" volatility_ok_col = f"{resample_col}volatility_ok" strong_uptrend_col = f"{resample_col}strong_uptrend" strong_downtrend_col = f"{resample_col}strong_downtrend" price_above_ema200_col = f"{resample_col}price_above_ema200" price_below_ema200_col = f"{resample_col}price_below_ema200" # === 做多入场 === # 条件:盘整底背驰 + 强上升趋势确认 dataframe.loc[ ( # 核心信号:盘整底背驰 (dataframe[beichi_long_col] == True) & # 强上升趋势确认(更严格) (dataframe[strong_uptrend_col] == True) & # RSI 确认(更严格:只在大趋势中操作) (dataframe[rsi_5m_col] > 40) & (dataframe[rsi_5m_col] < 60) & # 1m 指标确认 (dataframe['macd_1m'] > dataframe['macdsignal_1m']) & # 成交量确认 (dataframe['volume'] > dataframe['volume_mean'] * 1.5) ), ['enter_long', 'enter_tag'] ] = (1, "pzbc_long") # === 做空入场 === # 条件:盘整顶背驰 + 强下降趋势确认(更严格) dataframe.loc[ ( # 核心信号:盘整顶背驰 (dataframe[beichi_short_col] == True) & # 强下降趋势确认 (dataframe[strong_downtrend_col] == True) & # RSI 确认 (dataframe[rsi_5m_col] > 40) & (dataframe[rsi_5m_col] < 60) & # 1m 指标确认 (dataframe['macd_1m'] < dataframe['macdsignal_1m']) & # 成交量确认 (dataframe['volume'] > dataframe['volume_mean'] * 1.5) ), ['enter_short', 'enter_tag'] ] = (1, "pzbc_short") return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ 出场逻辑 多头出场: 1. 出现盘整顶背驰 2. 趋势转弱 空头出场: 1. 出现盘整底背驰 2. 趋势转弱 """ ticker = self.get_ticker_indicator() resample_col = f"resample_{ticker * self.same_level_timeframe}_" beichi_long_col = f"{resample_col}beichi_long" beichi_short_col = f"{resample_col}beichi_short" ai_type_col = f"{resample_col}ai_type" rsi_5m_col = f"{resample_col}rsi" ema_trend_dn_col = f"{resample_col}ema_trend_dn" strong_downtrend_col = f"{resample_col}strong_downtrend" strong_uptrend_col = f"{resample_col}strong_uptrend" # === 多头出场 === dataframe.loc[ ( # 出现盘整顶背驰 -> 退出多头 (dataframe[beichi_short_col] == True) | # 趋势转弱 ( (dataframe[ai_type_col] == -1) & (dataframe[rsi_5m_col] > 55) ) | # 强下跌趋势 (dataframe[strong_downtrend_col] == True) ), ['exit_long', 'exit_tag'] ] = (1, "pzbc_exit_long") # === 空头出场 === dataframe.loc[ ( # 出现盘整底背驰 -> 退出空头 (dataframe[beichi_long_col] == True) | # 趋势转弱 ( (dataframe[ai_type_col] == 1) & (dataframe[rsi_5m_col] < 45) ) | # 强上涨趋势 (dataframe[strong_uptrend_col] == True) ), ['exit_short', 'exit_tag'] ] = (1, "pzbc_exit_short") return dataframe def get_ticker_indicator(self) -> int: """获取 timeframe 的分钟数""" return int(self.timeframe[:-1])