Timeframe
5m
Direction
Long Only
Stoploss
-8.0%
Trailing Stop
No
ROI
0m: 20.0%
Interface Version
N/A
Startup Candles
100
Indicators
3
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import pandas as pd
import talib.abstract as ta
from datetime import datetime, timedelta
import numpy as np
class TrendEMAStopStrategy(IStrategy):
"""
趋势EMA止损策略
核心逻辑:
1. 趋势过滤:5EMA、20EMA、60EMA 排列 + ADX > 20
2. 开仓:多头排列+RSI超卖做多;空头排列+RSI超买做空
3. 止损止盈:2%止损、4%止盈(2:1盈亏比)
4. 保本止损:盈利≥3%时移动止损至开仓价
5. 风控:连续亏损5笔暂停
"""
# ========== 基础参数 ==========
timeframe = '5m' # 5分钟K线,适合捕捉趋势
max_open_trades = 3 # 最多同时持仓3个交易对
startup_candle_count = 100 # 需要足够K线计算指标
# 止盈止损设置
minimal_roi = {
"0": 0.20, # ROI止盈20%,防止持仓太久
}
stoploss = -0.08 # 8%止损作为底线保护
trailing_stop = False # 不使用追踪止损
# 订单类型
order_types = {
'entry': 'market',
'exit': 'market',
'stoploss': 'market',
'stoploss_on_exchange': False
}
unfilledtimeout = {
'entry': 60,
'exit': 60,
'unit': 'seconds'
}
# 杠杆配置
leverage_config = {
'BTC/USDT:USDT': 5.0,
'ETH/USDT:USDT': 4.0,
'SOL/USDT:USDT': 4.0,
'XRP/USDT:USDT': 3.0,
'DOGE/USDT:USDT': 3.0
}
# ========== 策略参数 ==========
# 趋势过滤参数
EMA_SHORT = 5
EMA_MIDDLE = 20
EMA_LONG = 60
ADX_PERIOD = 14
ADX_THRESHOLD = 20 # ADX > 20 表示趋势明确
# RSI参数
RSI_PERIOD = 14
RSI_OVERSOLD = 35 # 超卖,做多信号
RSI_OVERBOUGHT = 65 # 超买,做空信号
# 止损止盈参数(点数,每个点10美元)
STOP_LOSS_POINTS = 40
TAKE_PROFIT_POINTS = 80
BREAK_EVEN_POINTS = 50 # 盈利超过此点数时移动止损至开仓价
# 风控参数
MAX_CONSECUTIVE_LOSSES = 5 # 连续亏损5笔
LOSS_PAUSE_HOURS = 1 # 暂停开仓1小时
# ========== 风控状态 ==========
def __init__(self, config: dict = {}):
super().__init__(config)
self.consecutive_losses = {} # 每个交易对的连续亏损次数
self.last_loss_time = {} # 最后亏损时间
self.trade_entry_prices = {} # 记录每个交易的开仓价
def informative_pairs(self) -> list:
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
# ========== 趋势指标 ==========
# EMA组合
df['ema_5'] = ta.EMA(df['close'], timeperiod=self.EMA_SHORT)
df['ema_20'] = ta.EMA(df['close'], timeperiod=self.EMA_MIDDLE)
df['ema_60'] = ta.EMA(df['close'], timeperiod=self.EMA_LONG)
# ADX趋势强度
df['adx'] = ta.ADX(df['high'], df['low'], df['close'], timeperiod=self.ADX_PERIOD)
# RSI
df['rsi'] = ta.RSI(df['close'], timeperiod=self.RSI_PERIOD)
# ========== 趋势排列判断 ==========
# 多头排列:5EMA > 20EMA > 60EMA
df['bullish_trend'] = (df['ema_5'] > df['ema_20']) & (df['ema_20'] > df['ema_60'])
# 空头排列:5EMA < 20EMA < 60EMA
df['bearish_trend'] = (df['ema_5'] < df['ema_20']) & (df['ema_20'] < df['ema_60'])
# 趋势明确:ADX > 阈值
df['strong_trend'] = df['adx'] > self.ADX_THRESHOLD
# RSI超买超卖
df['rsi_oversold'] = df['rsi'] < self.RSI_OVERSOLD
df['rsi_overbought'] = df['rsi'] > self.RSI_OVERBOUGHT
return df
def leverage(self, pair: str, current_time: datetime, current_rate: float,
current_profit: float = 0.0, min_stops: float = 0.0,
max_stops: float = 0.0, current_time_rows: DataFrame = None,
**kwargs) -> float:
"""返回固定杠杆配置"""
return self.leverage_config.get(pair, 3.0)
def check_risk_controls(self, pair: str, current_time: datetime, current_rate: float) -> bool:
"""检查风控条件,返回是否允许开仓"""
# 检查连续亏损
if pair in self.consecutive_losses and self.consecutive_losses[pair] >= self.MAX_CONSECUTIVE_LOSSES:
last_loss_time = self.last_loss_time.get(pair)
if last_loss_time:
pause_end_time = last_loss_time + timedelta(hours=self.LOSS_PAUSE_HOURS)
if current_time < pause_end_time:
return False
else:
self.consecutive_losses[pair] = 0
return True
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
df['enter_long'] = 0
df['enter_short'] = 0
if len(df) < self.startup_candle_count:
return df
# ========== 多头开仓信号 ==========
long_conditions = (
# 趋势过滤
df['bullish_trend'] & # 多头排列
df['strong_trend'] & # 趋势明确
# RSI超卖(适合做多)
df['rsi_oversold']
)
df.loc[long_conditions, 'enter_long'] = 1
# ========== 空头开仓信号 ==========
short_conditions = (
# 趋势过滤
df['bearish_trend'] & # 空头排列
df['strong_trend'] & # 趋势明确
# RSI超买(适合做空)
df['rsi_overbought']
)
df.loc[short_conditions, 'enter_short'] = 1
return df
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
df = dataframe.copy()
df['exit_long'] = 0
df['exit_short'] = 0
# 基础退出逻辑(由custom_exit处理)
return df
def custom_entry(self, pair: str, current_time: datetime, current_rate: float,
**kwargs) -> bool:
"""自定义开仓风控检查"""
# 检查风控条件
if not self.check_risk_controls(pair, current_time, current_rate):
return False
return True
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, **kwargs) -> bool:
"""开仓确认时记录开仓价格"""
self.trade_entry_prices[pair] = rate
return True
def custom_exit(self, pair: str, current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> bool:
"""
自定义退出逻辑
止损止盈规则:
1. 固定止损:2%
2. 固定止盈:4%
3. 保本止损:盈利≥3%时移动止损至开仓价
"""
# 获取开仓价格
entry_price = self.trade_entry_prices.get(pair)
if entry_price is None:
return False
# 获取交易方向(从kwargs获取)
is_short = kwargs.get('is_short', False)
# 调整后的止盈止损百分比
STOP_LOSS_PCT = 0.02 # 2%止损
TAKE_PROFIT_PCT = 0.04 # 4%止盈
BREAK_EVEN_PCT = 0.03 # 3%保本
if is_short:
# 空单止损:价格上涨2%
if current_profit < -STOP_LOSS_PCT:
self.record_loss(pair, current_time)
return True
# 空单止盈:价格下跌4%
if current_profit > TAKE_PROFIT_PCT:
return True
# 空单保本止损:盈利≥3%且价格回到开仓价附近
if current_profit > BREAK_EVEN_PCT and current_profit < 0.01:
# 如果价格回到开仓价附近(±0.5%),触发保本止损
if abs(current_profit) < 0.005:
return True
else:
# 多单止损:价格下跌2%
if current_profit < -STOP_LOSS_PCT:
self.record_loss(pair, current_time)
return True
# 多单止盈:价格上涨4%
if current_profit > TAKE_PROFIT_PCT:
return True
# 多单保本止损:盈利≥3%且价格回到开仓价附近
if current_profit > BREAK_EVEN_PCT and current_profit < 0.01:
# 如果价格回到开仓价附近(±0.5%),触发保本止损
if abs(current_profit) < 0.005:
return True
return False
def record_loss(self, pair: str, current_time: datetime):
"""记录亏损,用于连续亏损风控"""
if pair not in self.consecutive_losses:
self.consecutive_losses[pair] = 0
self.consecutive_losses[pair] += 1
self.last_loss_time[pair] = current_time
def confirm_trade_exit(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, exit_reason: str, current_time: datetime,
**kwargs) -> bool:
"""平仓确认时清理状态"""
if pair in self.trade_entry_prices:
del self.trade_entry_prices[pair]
return True