# --- Do not remove these libs --- from freqtrade.strategy import IStrategy from pandas import DataFrame import talib.abstract as ta import numpy as np # 1小时短线趋势追踪策略 V7 - EMA + MACD 综合趋势判断 # 使用 EMA8, EMA24, EMA72, EMA168 + MACD 识别趋势阶段和强度 # freqtrade backtesting -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange=20250101- # freqtrade download-data -c ./user_data/Chan/config/EMA_Pattern.json -t 1h --pairs BTC/USDT:USDT --timerange=20250101- class EMA_Pattern(IStrategy): """ 1小时K线短线趋势追踪策略 V7 - EMA + MACD 核心逻辑: 1. EMA多头排列 + MACD金叉确认 = 趋势启动(最佳入场) 2. EMA发散 + MACD动能增加 = 趋势发展(可追涨) 3. MACD动能减弱/顶背离 = 趋势成熟(减仓) 4. EMA死叉 + MACD死叉 = 趋势结束(出场) """ can_short: bool = True timeframe: str = "1h" process_only_new_candles: bool = True # EMA参数 ema_fast: int = 8 ema_short: int = 24 ema_mid: int = 72 ema_long: int = 168 # 趋势强度阈值(放宽以增加交易机会) min_trend_strength: int = 65 # ADX阈值 min_adx: int = 25 # 有趋势就交易 # ROI - V6最佳参数(无杠杆) minimal_roi = { "120": 0.02, # 5天后 2%止盈 "72": 0.03, # 3天后 3%止盈 "24": 0.05, # 1天后 5%止盈 "0": 0.08, # 立即 8%止盈 } # 固定止损 stoploss: float = -0.05 # 5%止损 # 追踪止损 trailing_stop: bool = True trailing_stop_positive: float = 0.025 # 盈利2.5%后启动 trailing_stop_positive_offset: float = 0.04 # 盈利4%后才触发 trailing_only_offset_is_reached: bool = True # 禁用自定义止损(让追踪止损工作) use_custom_stoploss: bool = False # 只做多(做空效果差) can_short: bool = False startup_candle_count: int = 250 def custom_stoploss(self, pair: str, trade, current_time, current_rate, current_profit, after_fill, **kwargs) -> float: """ 动态止损 V3 - 保护利润但不过早出场: 1. 盈利超过6%,止损移到盈利3%(锁定一半利润) 2. 盈利超过3%,止损移到盈利1% 3. 盈利超过1.5%,止损移到保本 4. 长时间亏损才考虑缩紧止损 """ # 持仓时间(小时) trade_duration = (current_time - trade.open_date_utc).total_seconds() / 3600 # 盈利时动态止损 - 阶梯式保护利润 if current_profit > 0.06: # 盈利超过6%,锁定3%利润 return -0.03 elif current_profit > 0.03: # 盈利超过3%,锁定1%利润 return -0.02 elif current_profit > 0.015: # 盈利超过1.5%,移到保本 return -0.005 # 长时间持仓亏损(超过96小时=4天),才缩紧止损 if trade_duration > 96 and current_profit < -0.03: return -0.04 # 缩紧到4% # 默认使用配置的止损 return self.stoploss def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if dataframe is None or dataframe.empty: return dataframe # ==================== EMA指标 ==================== dataframe['ema8'] = ta.EMA(dataframe, timeperiod=self.ema_fast) dataframe['ema24'] = ta.EMA(dataframe, timeperiod=self.ema_short) dataframe['ema72'] = ta.EMA(dataframe, timeperiod=self.ema_mid) dataframe['ema168'] = ta.EMA(dataframe, timeperiod=self.ema_long) # ==================== MACD指标 ==================== macd = ta.MACD(dataframe, fastperiod=12, slowperiod=26, signalperiod=9) dataframe['macd'] = macd['macd'] dataframe['macd_signal'] = macd['macdsignal'] dataframe['macd_hist'] = macd['macdhist'] # MACD辅助指标 dataframe['macd_hist_change'] = dataframe['macd_hist'] - dataframe['macd_hist'].shift(1) dataframe['macd_hist_ma'] = dataframe['macd_hist'].rolling(5).mean() # ADX趋势强度 dataframe['adx'] = ta.ADX(dataframe, timeperiod=14) # ATR波动率 dataframe['atr'] = ta.ATR(dataframe, timeperiod=14) dataframe['atr_pct'] = dataframe['atr'] / dataframe['close'] * 100 # 成交量 dataframe['volume_ma'] = dataframe['volume'].rolling(20).mean() dataframe['volume_ratio'] = dataframe['volume'] / dataframe['volume_ma'] # ==================== 均线排列 ==================== dataframe['bull_align'] = ( (dataframe['ema8'] > dataframe['ema24']) & (dataframe['ema24'] > dataframe['ema72']) & (dataframe['ema72'] > dataframe['ema168']) ) dataframe['bear_align'] = ( (dataframe['ema8'] < dataframe['ema24']) & (dataframe['ema24'] < dataframe['ema72']) & (dataframe['ema72'] < dataframe['ema168']) ) # 简化排列(短中期) dataframe['bull_align_short'] = ( (dataframe['ema8'] > dataframe['ema24']) & (dataframe['ema24'] > dataframe['ema72']) ) dataframe['bear_align_short'] = ( (dataframe['ema8'] < dataframe['ema24']) & (dataframe['ema24'] < dataframe['ema72']) ) # ==================== 均线斜率 ==================== lookback = 5 dataframe['slope_ema8'] = (dataframe['ema8'] - dataframe['ema8'].shift(lookback)) / dataframe['ema8'].shift(lookback) * 100 dataframe['slope_ema24'] = (dataframe['ema24'] - dataframe['ema24'].shift(lookback)) / dataframe['ema24'].shift(lookback) * 100 dataframe['slope_ema72'] = (dataframe['ema72'] - dataframe['ema72'].shift(lookback)) / dataframe['ema72'].shift(lookback) * 100 dataframe['slope_ema168'] = (dataframe['ema168'] - dataframe['ema168'].shift(lookback)) / dataframe['ema168'].shift(lookback) * 100 dataframe['slope_bull_confirm'] = ( (dataframe['slope_ema8'] > 0) & (dataframe['slope_ema24'] > 0) & (dataframe['slope_ema72'] > 0) ) dataframe['slope_bear_confirm'] = ( (dataframe['slope_ema8'] < 0) & (dataframe['slope_ema24'] < 0) & (dataframe['slope_ema72'] < 0) ) # ==================== 均线间距 ==================== dataframe['spread_total'] = (dataframe['ema8'] - dataframe['ema168']) / dataframe['ema168'] * 100 dataframe['spread_short'] = (dataframe['ema8'] - dataframe['ema24']) / dataframe['ema24'] * 100 dataframe['spread_change'] = dataframe['spread_total'] - dataframe['spread_total'].shift(lookback) dataframe['spread_ma'] = dataframe['spread_total'].rolling(20).mean() spread_abs = dataframe['spread_total'].abs() dataframe['spread_pct_30'] = spread_abs.rolling(50, min_periods=20).quantile(0.3) dataframe['spread_pct_70'] = spread_abs.rolling(50, min_periods=20).quantile(0.7) # ==================== MACD交叉信号 ==================== # MACD金叉(MACD线上穿信号线) dataframe['macd_golden_cross'] = ( (dataframe['macd'] > dataframe['macd_signal']) & (dataframe['macd'].shift(1) <= dataframe['macd_signal'].shift(1)) ) # MACD死叉 dataframe['macd_death_cross'] = ( (dataframe['macd'] < dataframe['macd_signal']) & (dataframe['macd'].shift(1) >= dataframe['macd_signal'].shift(1)) ) # MACD零轴上方金叉(更强信号) dataframe['macd_strong_golden'] = dataframe['macd_golden_cross'] & (dataframe['macd'] > 0) # ==================== MACD动能判断 ==================== # 柱状图动能增加(多头) dataframe['macd_momentum_up'] = ( (dataframe['macd_hist'] > 0) & (dataframe['macd_hist'] > dataframe['macd_hist'].shift(1)) & (dataframe['macd_hist'].shift(1) > dataframe['macd_hist'].shift(2)) ) # 柱状图动能增加(空头) dataframe['macd_momentum_down'] = ( (dataframe['macd_hist'] < 0) & (dataframe['macd_hist'] < dataframe['macd_hist'].shift(1)) & (dataframe['macd_hist'].shift(1) < dataframe['macd_hist'].shift(2)) ) # 柱状图动能减弱(顶部信号) dataframe['macd_momentum_weakening'] = ( (dataframe['macd_hist'] > 0) & (dataframe['macd_hist'] < dataframe['macd_hist'].shift(1)) & (dataframe['macd_hist'].shift(1) < dataframe['macd_hist'].shift(2)) ) # ==================== 趋势阶段(EMA + MACD综合判断)==================== # 趋势启动期:EMA开始排列 + MACD金叉 + 柱状图正向增长 dataframe['trend_start_bull'] = ( dataframe['bull_align_short'] & # 至少短中期排列 (dataframe['macd'] > dataframe['macd_signal']) & # MACD金叉状态 (dataframe['macd_hist'] > 0) & # 柱状图为正 (dataframe['macd_hist'] > dataframe['macd_hist'].shift(1)) & # 动能增加 (spread_abs < dataframe['spread_pct_70']) # 发散度不是最大 ) # 趋势发展期:完整排列 + MACD在零轴上方 + 持续放量 dataframe['trend_develop_bull'] = ( dataframe['bull_align'] & # 完整4线排列 (dataframe['macd'] > 0) & # MACD在零轴上方 (dataframe['macd'] > dataframe['macd_signal']) & # 金叉状态 dataframe['slope_bull_confirm'] & # 斜率确认 (dataframe['spread_change'] > 0) # 发散度扩大 ) # 趋势成熟期:发散度大 + MACD动能减弱 dataframe['trend_mature_bull'] = ( dataframe['bull_align'] & (dataframe['macd'] > 0) & dataframe['macd_momentum_weakening'] & # 动能减弱 (spread_abs > dataframe['spread_pct_70']) # 发散度很大 ) # 趋势结束:EMA开始死叉 + MACD死叉 dataframe['trend_end_bull'] = ( (dataframe['ema8'] < dataframe['ema24']) & # EMA8死叉EMA24 (dataframe['macd'] < dataframe['macd_signal']) & # MACD死叉 (dataframe['macd_hist'] < 0) # 柱状图转负 ) # 空头阶段(镜像) dataframe['trend_start_bear'] = ( dataframe['bear_align_short'] & (dataframe['macd'] < dataframe['macd_signal']) & (dataframe['macd_hist'] < 0) & (dataframe['macd_hist'] < dataframe['macd_hist'].shift(1)) & (spread_abs < dataframe['spread_pct_70']) ) dataframe['trend_develop_bear'] = ( dataframe['bear_align'] & (dataframe['macd'] < 0) & (dataframe['macd'] < dataframe['macd_signal']) & dataframe['slope_bear_confirm'] & (dataframe['spread_change'] < 0) ) dataframe['trend_end_bear'] = ( (dataframe['ema8'] > dataframe['ema24']) & (dataframe['macd'] > dataframe['macd_signal']) & (dataframe['macd_hist'] > 0) ) # ==================== 趋势强度计算 (0-100) ==================== # 1. 排列得分 (0-25) bull_align_score = ( (dataframe['ema8'] > dataframe['ema24']).astype(int) + (dataframe['ema24'] > dataframe['ema72']).astype(int) + (dataframe['ema72'] > dataframe['ema168']).astype(int) + (dataframe['close'] > dataframe['ema8']).astype(int) ) * 6.25 bear_align_score = ( (dataframe['ema8'] < dataframe['ema24']).astype(int) + (dataframe['ema24'] < dataframe['ema72']).astype(int) + (dataframe['ema72'] < dataframe['ema168']).astype(int) + (dataframe['close'] < dataframe['ema8']).astype(int) ) * 6.25 dataframe['align_score'] = np.maximum(bull_align_score, bear_align_score) # 2. 斜率得分 (0-25) dataframe['slope_score'] = np.clip(np.abs(dataframe['slope_ema24']) * 10, 0, 25) # 3. MACD动能得分 (0-25) - 替换原来的间距得分 macd_hist_norm = dataframe['macd_hist'].abs() / dataframe['close'] * 1000 dataframe['macd_score'] = np.clip(macd_hist_norm * 5, 0, 25) # 4. ADX得分 (0-25) dataframe['adx_score'] = np.clip(dataframe['adx'] - 15, 0, 25) # 综合强度 dataframe['trend_strength'] = ( dataframe['align_score'] + dataframe['slope_score'] + dataframe['macd_score'].fillna(12.5) + dataframe['adx_score'] ) # ==================== 入场信号 ==================== # EMA金叉 dataframe['ema8_cross_ema24_up'] = ( (dataframe['ema8'] > dataframe['ema24']) & (dataframe['ema8'].shift(1) <= dataframe['ema24'].shift(1)) ) dataframe['ema8_cross_ema24_down'] = ( (dataframe['ema8'] < dataframe['ema24']) & (dataframe['ema8'].shift(1) >= dataframe['ema24'].shift(1)) ) # 回踩EMA24反弹 dataframe['pullback_buy'] = ( (dataframe['low'].shift(1) <= dataframe['ema24'].shift(1) * 1.005) & (dataframe['close'] > dataframe['ema8']) & (dataframe['close'] > dataframe['open']) & (dataframe['macd'] > dataframe['macd_signal']) # MACD确认 ) # 突破前高 dataframe['high_break'] = dataframe['close'] > dataframe['high'].rolling(24).max().shift(1) dataframe['low_break'] = dataframe['close'] < dataframe['low'].rolling(24).min().shift(1) # ==================== 大周期趋势过滤 ==================== dataframe['big_trend_bull'] = ( (dataframe['ema168'] > dataframe['ema168'].shift(24)) & (dataframe['close'] > dataframe['ema168']) ) dataframe['big_trend_bear'] = ( (dataframe['ema168'] < dataframe['ema168'].shift(24)) & (dataframe['close'] < dataframe['ema168']) ) return dataframe def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if dataframe is None or dataframe.empty: return dataframe # ==================== 做多入场1:趋势启动(EMA排列 + MACD确认)==================== # 核心条件:EMA开始多头排列 + MACD金叉 + 动能增加 dataframe.loc[ ( dataframe['bull_align_short'] & # 至少短中期排列(EMA8>24>72) (dataframe['macd'] > dataframe['macd_signal']) & # MACD金叉 (dataframe['macd_hist'] > 0) & # 柱状图为正 (dataframe['macd_hist'] > dataframe['macd_hist'].shift(1)) & # 动能增加 (dataframe['close'] > dataframe['ema24']) & # 价格在EMA24上方 (dataframe['adx'] > 20) & # 有趋势 (dataframe['slope_ema8'] > 0) & # EMA8向上 (dataframe['volume_ratio'] > 0.7) ), ["enter_long", "enter_tag"], ] = (1, "trend_start") # ==================== 做多入场2:强势突破(突破前高 + MACD确认)==================== dataframe.loc[ ( dataframe['bull_align'] & # 完整排列 dataframe['high_break'] & # 突破前高 (dataframe['macd'] > dataframe['macd_signal']) & # MACD金叉状态 (dataframe['adx'] > self.min_adx) & # 强趋势 (dataframe['trend_strength'] >= self.min_trend_strength - 10) & (dataframe['volume_ratio'] > 0.9) ), ["enter_long", "enter_tag"], ] = (1, "bull_breakout") # ==================== 做多入场3:回踩反弹(趋势中回调)==================== dataframe.loc[ ( dataframe['bull_align'] & # 完整排列 (dataframe['low'].shift(1) <= dataframe['ema24'].shift(1) * 1.01) & # 回踩EMA24附近 (dataframe['close'] > dataframe['open']) & # 阳线 (dataframe['close'] > dataframe['ema8']) & # 收盘价回到EMA8上方 (dataframe['macd'] > 0) & # MACD在零轴上方 (dataframe['macd'] > dataframe['macd_signal']) & # 金叉状态 (dataframe['trend_strength'] >= self.min_trend_strength - 15) ), ["enter_long", "enter_tag"], ] = (1, "bull_pullback") return dataframe def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: if dataframe is None or dataframe.empty: return dataframe # ==================== 多头出场1:趋势结束(EMA死叉 + MACD死叉)==================== dataframe.loc[ dataframe['trend_end_bull'] & # EMA8死叉 + MACD死叉 + 柱状图转负 (dataframe['close'] < dataframe['ema24']), # 价格确认跌破EMA24 ["exit_long", "exit_tag"], ] = (1, "trend_end") # ==================== 多头出场2:趋势反转(完全转为空头)==================== dataframe.loc[ dataframe['bear_align'] & # 完全空头排列 (dataframe['macd'] < dataframe['macd_signal']) & # MACD死叉 (dataframe['macd_hist'] < 0) & # 柱状图为负 (dataframe['close'] < dataframe['ema72']) & # 价格跌破EMA72 (dataframe['close'].shift(1) < dataframe['ema72'].shift(1)), # 连续2根确认 ["exit_long", "exit_tag"], ] = (1, "trend_reverse") # ==================== 空头出场1:趋势结束 ==================== dataframe.loc[ dataframe['trend_end_bear'] & (dataframe['close'] > dataframe['ema24']), ["exit_short", "exit_tag"], ] = (1, "trend_end") # ==================== 空头出场2:趋势反转 ==================== dataframe.loc[ dataframe['bull_align'] & (dataframe['macd'] > dataframe['macd_signal']) & (dataframe['macd_hist'] > 0) & (dataframe['close'] > dataframe['ema72']) & (dataframe['close'].shift(1) > dataframe['ema72'].shift(1)), ["exit_short", "exit_tag"], ] = (1, "trend_reverse") return dataframe def get_ticker_indicator(self): return int(self.timeframe[:-1])