Timeframe
5m
Direction
Long & Short
Stoploss
-15.0%
Trailing Stop
Yes
ROI
0m: 100.0%
Interface Version
N/A
Startup Candles
300
Indicators
5
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
E0V1E_LongShort Strategy (Improved)
====================================
Based on E0V1E + E0V1EN backtest results analysis:
- buy_new performed better than buy_1 (+15% vs -23% in 2024)
- Added time-based exits from E0V1EN
- Added 24h price change filter
- Refined short entry conditions
- Added CooldownPeriod protection
Long Entry: RSI oversold + price below SMA (mean reversion)
Short Entry: RSI overbought + price above SMA (mean reversion)
"""
from datetime import datetime, timedelta
import talib.abstract as ta
import pandas_ta as pta
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import IStrategy
from pandas import DataFrame
from freqtrade.strategy import DecimalParameter, IntParameter
from functools import reduce
import warnings
warnings.simplefilter(action="ignore", category=RuntimeWarning)
class E0V1E_LongShort(IStrategy):
minimal_roi = {"0": 1}
timeframe = '5m'
process_only_new_candles = True
startup_candle_count = 300 # 288 for 24h + buffer
can_short = True
order_types = {
'entry': 'market',
'exit': 'market',
'emergency_exit': 'market',
'force_entry': 'market',
'force_exit': "market",
'stoploss': 'market',
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
'stoploss_on_exchange_market_ratio': 0.99
}
# Tighter stoploss than original -25% (reduces big losses)
stoploss = -0.15
trailing_stop = True
trailing_stop_positive = 0.002
trailing_stop_positive_offset = 0.03
trailing_only_offset_is_reached = True
# ============ LONG PARAMETERS ============
# Based on buy_new success (better than buy_1)
is_optimize_long = True
buy_rsi_fast = IntParameter(20, 50, default=34, space='buy', optimize=is_optimize_long)
buy_rsi_min = IntParameter(15, 40, default=28, space='buy', optimize=is_optimize_long)
buy_sma15 = DecimalParameter(0.93, 0.99, default=0.96, decimals=3, space='buy', optimize=is_optimize_long)
buy_cti = DecimalParameter(-1, 1, default=0.69, decimals=2, space='buy', optimize=is_optimize_long)
# 24h price change filter (from E0V1EN)
buy_24h_min_pct = DecimalParameter(-30.0, 0.0, default=-15.0, decimals=1, space='buy', optimize=True)
buy_24h_max_pct = DecimalParameter(0.0, 100.0, default=50.0, decimals=1, space='buy', optimize=True)
# ============ SHORT PARAMETERS ============
# Stricter conditions to only short in true overbought zones
is_optimize_short = True
sell_rsi_fast = IntParameter(65, 90, default=75, space='sell', optimize=is_optimize_short)
sell_rsi_min = IntParameter(55, 80, default=65, space='sell', optimize=is_optimize_short) # Add RSI lower bound
sell_rsi_max = IntParameter(70, 90, default=80, space='sell', optimize=is_optimize_short)
sell_sma15 = DecimalParameter(1.02, 1.10, default=1.05, decimals=3, space='sell', optimize=is_optimize_short)
sell_cti_min = DecimalParameter(0.0, 0.8, default=0.3, decimals=2, space='sell', optimize=is_optimize_short) # CTI positive range only
# 24h price change filter for shorts
sell_24h_min_pct = DecimalParameter(-50.0, 50.0, default=10.0, decimals=1, space='sell', optimize=True)
sell_24h_max_pct = DecimalParameter(50.0, 200.0, default=100.0, decimals=1, space='sell', optimize=True)
# ============ EXIT PARAMETERS ============
exit_fastx_long = IntParameter(50, 100, default=84, space='sell', optimize=True)
exit_fastx_short = IntParameter(0, 50, default=16, space='sell', optimize=True)
exit_cci_long = IntParameter(50, 150, default=80, space='sell', optimize=True)
exit_cci_short = IntParameter(-150, -50, default=-80, space='sell', optimize=True)
@property
def protections(self):
return [
{
"method": "CooldownPeriod",
"stop_duration_candles": 96 # 8 hours cooldown
}
]
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# RSI indicators
dataframe['sma_15'] = ta.SMA(dataframe, timeperiod=15)
dataframe['cti'] = pta.cti(dataframe["close"], length=20)
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=4)
dataframe['rsi_slow'] = ta.RSI(dataframe, timeperiod=20)
# 24h price change (288 candles = 24h for 5m timeframe)
dataframe['24h_change_pct'] = (dataframe['close'].pct_change(periods=288) * 100)
# Exit indicators
stoch_fast = ta.STOCHF(dataframe, 5, 3, 0, 3, 0)
dataframe['fastk'] = stoch_fast['fastk']
dataframe['cci'] = ta.CCI(dataframe, timeperiod=20)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'enter_tag'] = ''
# ============ LONG ENTRY ============
# Based on buy_new logic (performed better in backtest)
long_conditions = (
(dataframe['rsi_slow'] < dataframe['rsi_slow'].shift(1)) & # RSI declining (momentum weakening)
(dataframe['rsi_fast'] < self.buy_rsi_fast.value) & # Fast RSI oversold
(dataframe['rsi'] > self.buy_rsi_min.value) & # But not too extreme (avoid falling knife)
(dataframe['close'] < dataframe['sma_15'] * self.buy_sma15.value) & # Price below SMA
(dataframe['cti'] < self.buy_cti.value) & # CTI filter
(dataframe['24h_change_pct'] > self.buy_24h_min_pct.value) & # 24h change filter
(dataframe['24h_change_pct'] < self.buy_24h_max_pct.value)
)
dataframe.loc[long_conditions, 'enter_long'] = 1
dataframe.loc[long_conditions, 'enter_tag'] = 'long_mean_reversion'
# ============ SHORT ENTRY ============
# Stricter conditions: only short in true overbought zones
short_conditions = (
(dataframe['rsi_slow'] > dataframe['rsi_slow'].shift(1)) & # RSI rising (momentum increasing)
(dataframe['rsi_fast'] > self.sell_rsi_fast.value) & # Fast RSI overbought (75+)
(dataframe['rsi'] > self.sell_rsi_min.value) & # RSI lower bound (65+) - confirm overbought
(dataframe['rsi'] < self.sell_rsi_max.value) & # RSI upper bound (80-) - avoid extremes
(dataframe['close'] > dataframe['sma_15'] * self.sell_sma15.value) & # Price above SMA (5%+)
(dataframe['cti'] > self.sell_cti_min.value) & # CTI positive range only (0.3+)
(dataframe['24h_change_pct'] > self.sell_24h_min_pct.value) & # 24h filter for shorts
(dataframe['24h_change_pct'] < self.sell_24h_max_pct.value)
)
dataframe.loc[short_conditions, 'enter_short'] = 1
dataframe.loc[short_conditions, 'enter_tag'] = 'short_mean_reversion'
return dataframe
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
if trade.is_short:
# ============ SHORT EXIT LOGIC ============
# Take profit when oversold (price likely to bounce)
if current_profit > 0:
if current_candle["fastk"] < self.exit_fastx_short.value:
return "fastk_profit_exit_short"
# CCI exit for shorts (oversold = time to cover)
if current_profit > -0.03:
if current_candle["cci"] < self.exit_cci_short.value:
return "cci_exit_short"
# Time-based exits for shorts (from E0V1EN)
if current_time - timedelta(hours=7) > trade.open_date_utc:
if current_profit >= -0.05:
return "time_exit_short_7h"
if current_time - timedelta(hours=10) > trade.open_date_utc:
if current_profit >= -0.10:
return "time_exit_short_10h"
else:
# ============ LONG EXIT LOGIC ============
# Take profit when overbought
if current_profit > 0:
if current_candle["fastk"] > self.exit_fastx_long.value:
return "fastk_profit_exit_long"
# CCI exit for longs (overbought = time to sell)
if current_profit > -0.03:
if current_candle["cci"] > self.exit_cci_long.value:
return "cci_exit_long"
# Time-based exits for longs (from E0V1EN)
if current_time - timedelta(hours=7) > trade.open_date_utc:
if current_profit >= -0.05:
return "time_exit_long_7h"
if current_time - timedelta(hours=10) > trade.open_date_utc:
if current_profit >= -0.10:
return "time_exit_long_10h"
return None
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[:, 'exit_long'] = 0
dataframe.loc[:, 'exit_short'] = 0
return dataframe