437 lines
19 KiB
Python
437 lines
19 KiB
Python
# --- 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]) |