diff --git a/ChanLun.py b/ChanLun.py index e1c099b..936053b 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -53,7 +53,7 @@ class ChanLun(): self.time_M_symbols = ['2M', '3M', '6M', '1y'] self.time_symbols = ['1m', '2m', '3m', '5m', '10m', '15m', '20m', '30m', '45m','1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d', '1w', '2w', '1M', '3M', '6M', '1y'] self.tf_df_dict = {} - self.ema_symbols = ['1m', '2m', '3m', '5m', '10m', '15m', '20m', '30m', '45m', '1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d'] + self.ema_symbols = ['5m', '10m', '15m', '20m', '30m', '45m', '1h', '2h', '4h', '6h', '8h', '12h', '16h', '1d', '2d', '3d'] self.tf_df = TF_DF() def init_data(self, dataframe, intervals, timeframes): for index in range(0, len(intervals)): @@ -61,6 +61,7 @@ class ChanLun(): interval = intervals[index] self.tf_df_dict[timeframe] = TF_DF(dataframe, interval, timeframe) def init_dataframes(self, dataframe_m=None, dataframe_15m=None, dataframe_h=None, dataframe_d=None, dataframe_w=None, dataframe_M=None): + self.tf_df_dict = {} if dataframe_m is not None: self.tf_df_dict['1m'] = TF_DF(dataframe_m, 1, '1m') self.init_data(dataframe_m, self.time_m_intervals, self.time_m_symbols) diff --git a/config/EMA_Pattern.json b/config/EMA_Pattern.json index 96a3377..31386f9 100644 --- a/config/EMA_Pattern.json +++ b/config/EMA_Pattern.json @@ -12,7 +12,7 @@ "trading_mode": "futures", "margin_mode": "isolated", "can_short" : true, - "timeframe" : "15m", + "timeframe" : "1h", "process_only_new_candles" : false, "unfilledtimeout": { "entry": 1, diff --git a/strategies/ChanLun_EMA52.py b/strategies/ChanLun_EMA52.py index 1bdde95..1ca26ca 100644 --- a/strategies/ChanLun_EMA52.py +++ b/strategies/ChanLun_EMA52.py @@ -89,11 +89,15 @@ class ChanLun_EMA52(IStrategy): # startup_candle_count = 1600 big_tf = '1h' small_tf = '15m' - last_time = datetime.now() + last_time = None chan = ChanLun() last_order = None last_trade = None pair = 'BTC/USDT:USDT' + long_tf = '1h' + short_tf = '15m' + long_time = 60 + short_time = 15 def informative_pairs(self): return [(self.pair, "1h"), (self.pair, "1d"), @@ -102,16 +106,31 @@ class ChanLun_EMA52(IStrategy): (self.pair, "1w"), ] def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14) - if self.last_time + timedelta(minutes=1) < datetime.now(): + long_df = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h') + long_df['entry_long'] = self.long_entry_condition(long_df) + dataframe['rsi'] = ta.RSI(long_df, timeperiod=14) + if self.last_time is None or self.last_time + timedelta(minutes=1) < datetime.now(): self.last_time = datetime.now() logger.info("init_dataframes----------------------------") last_price = dataframe.iloc[-1]['close'] + macdstr = str(long_df.iloc[-1]['macd']) + " " + str(long_df.iloc[-1]['macdsignal']) + " " + str(long_df.iloc[-1]['macdhist'])) date = dataframe.iloc[-1]['date'] tf_ema52_list = self.chan.check_price_ema52(last_price) self.init_dataframes(dataframe) - logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list)) + logger.info("Date: " + date.strftime('%Y-%m-%d %H:%M:%S') + " Price: " + str(last_price) + " EMA52_list: " + str(tf_ema52_list) + " MACD: " + macdstr) return dataframe + def long_entry_condition(self, long_df): + long_df['ema52'] = ta.EMA(long_df, timeperiod=52) + long_df['dir52'] = long_df['close'] - long_df['ema52'] + long_df['ema156'] = ta.EMA(long_df, timeperiod=156) + long_df['dir156'] = long_df['close'] - long_df['ema156'] + long_df_macd = ta.MACD(long_df, fast=12, slow=26, signal=9) + long_df['macdsignal'] = long_df_macd['macdsignal'] + long_df['macd'] = long_df_macd['macd'] + long_df['macdhist'] = long_df_macd['macdhist'] + long_entry_condition = (long_df['dir52'] > 0) & (long_df['dir156'] > 0) & (long_df['macdhist'] > 0) + return long_entry_condition + def init_dataframes(self, dataframe_1m): dataframe_15m = self.dp.get_pair_dataframe(pair=self.pair, timeframe='15m') dataframe_1h = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1h') diff --git a/strategies/EMA_Pattern.py b/strategies/EMA_Pattern.py index 3e26e6e..f0e427c 100644 --- a/strategies/EMA_Pattern.py +++ b/strategies/EMA_Pattern.py @@ -1,115 +1,437 @@ # --- Do not remove these libs --- -from statistics import median from freqtrade.strategy import IStrategy -from technical.util import resample_to_interval, resampled_merge from pandas import DataFrame import talib.abstract as ta -from technical import qtpylib +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- -### Now you can use logger.info('asfd') to log -# freqtrade plot-dataframe --strategy EMA_Pattern --datadir user_data/data/binance -c ./user_data/Chan/EMA_Pattern.json --timerange=20250309- -# freqtrade backtesting -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange=20251030- -# freqtrade download-data -c ./user_data/Chan/config/EMA_Pattern.json -t 1m 3m 5m 15m 30m 1h --pairs BTC/USDT:USDT --timerange=20250405- -# freqtrade download-data -c ./user_data/Chan/config/EMA_Pattern.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101- -# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/EMA_Pattern.json -e 200 --timerange=20250201-20250901 -# freqtrade edge -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 -# freqtrade plot-dataframe -c ./user_data/Chan/config/EMA_Pattern.json --strategy EMA_Pattern --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 class EMA_Pattern(IStrategy): - time1h = 1440 - can_short: bool = True - timeframe: str = "1m" - process_only_new_candles: bool = False + """ + 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 - # ROI 与止损可根据需要在配置中覆盖 - minimal_roi = { - "60": 0.005, - "30": 0.01, - "0": 0.02, - } - stoploss: float = -0.30 - # 需要的历史K线数量(包含EMA等指标预热) - startup_candle_count: int = 200 + 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 - dataframe = self.add_indicators(dataframe) - return dataframe - def add_indicators(self, dataframe: DataFrame) -> DataFrame: - macd = ta.MACD(dataframe, timeperiod=12, fastperiod=12, slowperiod=26, signalperiod=9) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - dataframe['ema6'] = ta.EMA(dataframe, timeperiod=6) - dataframe['ema12'] = ta.EMA(dataframe, timeperiod=12) - dataframe['ema24'] = ta.EMA(dataframe, timeperiod=24) - dataframe['ema52'] = ta.EMA(dataframe, timeperiod=52) - dataframe['adx'] = ta.ADX(dataframe, timeperiod=14) - dataframe['strong_trend'] = dataframe['adx'] > 25 - dataframe['UP_Pattern'] = (dataframe['ema6'] > dataframe['ema12']) & (dataframe['ema12'] > dataframe['ema24']) & (dataframe['ema24'] > dataframe['ema52']) - dataframe['DOWN_Pattern'] = (dataframe['ema6'] < dataframe['ema12']) & (dataframe['ema12'] < dataframe['ema24']) & (dataframe['ema24'] < dataframe['ema52']) - dataframe['UP_Confirm'] = (dataframe['ema6'] > dataframe['ema6'].shift(1)) & (dataframe['ema12'] > dataframe['ema12'].shift(1)) & (dataframe['ema24'] > dataframe['ema24'].shift(1)) & (dataframe['ema52'] > dataframe['ema52'].shift(1)) - dataframe['DOWN_Confirm'] = (dataframe['ema6'] < dataframe['ema6'].shift(1)) & (dataframe['ema12'] < dataframe['ema12'].shift(1)) & (dataframe['ema24'] < dataframe['ema24'].shift(1)) & (dataframe['ema52'] < dataframe['ema52'].shift(1)) - dataframe['EMA52_Cross_EMA24_UP'] = (dataframe['ema52'] < dataframe['ema24']) & (dataframe['ema52'].shift(1) > dataframe['ema24'].shift(1)) - dataframe['EMA52_Cross_EMA24_DOWN'] = (dataframe['ema52'] > dataframe['ema24']) & (dataframe['ema52'].shift(1) < dataframe['ema24'].shift(1)) - dataframe['Price_Above_EMA52'] = (dataframe['close'] > dataframe['ema52']) - dataframe['Price_Below_EMA52'] = (dataframe['close'] < dataframe['ema52']) - dataframe['MACD_Above_Zero'] = (dataframe['macd'] > 0) & (dataframe['macdsignal'] > 0) - dataframe['MACD_Below_Zero'] = (dataframe['macd'] < 0) & (dataframe['macdsignal'] < 0) - dataframe['BUY_END'] = dataframe['close'] < dataframe['ema52'] - dataframe['SELL_END'] = dataframe['close'] > dataframe['ema52'] - return dataframe - def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - if dataframe is None or dataframe.empty: - return dataframe - dataframe.loc[ - ( - (dataframe['UP_Pattern']) & - (dataframe['UP_Confirm']) & - (dataframe['Price_Above_EMA52']) & - (dataframe['MACD_Above_Zero']) & - (dataframe['EMA52_Cross_EMA24_UP']) & - (dataframe['strong_trend']) - ), - ["enter_long", "enter_tag"], - ] = (1, "ema_up_trend") + 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 - dataframe.loc[ - ( - (dataframe['DOWN_Pattern']) & - (dataframe['DOWN_Confirm']) & - (dataframe['Price_Below_EMA52']) & - (dataframe['MACD_Below_Zero']) & - (dataframe['EMA52_Cross_EMA24_DOWN']) & - (dataframe['strong_trend']) - ), - ["enter_short", "enter_tag"], - ] = (1, "ema_down_trend") + 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 - 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 populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - if dataframe is None or dataframe.empty: - return dataframe - dataframe.loc[ - ( - (dataframe['BUY_END']) | - (dataframe['DOWN_Pattern']) | - (dataframe['EMA52_Cross_EMA24_DOWN']) - ), - ["exit_long", "exit_tag"], - ] = (1, "ema_long_exit") - - dataframe.loc[ - ( - (dataframe['SELL_END']) | - (dataframe['UP_Pattern']) | - (dataframe['EMA52_Cross_EMA24_UP']) - ), - ["exit_short", "exit_tag"], - ] = (1, "ema_short_exit") - - return dataframe - def get_ticker_indicator(self): - return int(self.timeframe[:-1]) \ No newline at end of file + def get_ticker_indicator(self): + return int(self.timeframe[:-1]) \ No newline at end of file