Timeframe
1h
Direction
Long & Short
Stoploss
-2.5%
Trailing Stop
Yes
ROI
0m: 5.0%, 360m: 2.0%, 720m: 0.0%
Interface Version
3
Startup Candles
100
Indicators
6
freqtrade/freqtrade-strategies
Strategy 003 author@: Gerald Lonlas github@: https://github.com/freqtrade/freqtrade-strategies
"""
BestPossibleStrategy — combines lessons from all tested approaches
Key insights from backtests:
1. V3/4 lost -19% on 2025 because mean reversion buys dips that keep dipping
2. Bandtastic lost -18.6% — BB squeeze signals too slow in choppy markets
3. TrendRider lost -5.3% but had best drawdown (6.4%) — trailing stops work, entries wrong
4. 2026 YTD: ETH -22.5%, BTC -8.5% — SHORTING is the missing piece
This strategy:
- Trend detection first (bull/bear/sideways via 50/200 EMA + ADX)
- In bull: trend-following (buy pullbacks to EMA, trailing stop)
- In bear: SHORT on bounces to EMA (mean reversion to downside)
- In sideways: mean reversion long+short (buy low/sell high)
- Multi-timeframe confirmation (1h + 4h)
- Dynamic position sizing based on ATR volatility
- Hard stoploss + trailing stop on all positions
"""
import talib.abstract as ta
import numpy as np
import pandas as pd
from functools import reduce
from pandas import DataFrame
from freqtrade.strategy import IStrategy, IntParameter, DecimalParameter, merge_informative_pair
class BestPossibleStrategy(IStrategy):
INTERFACE_VERSION = 3
# --- Core settings ---
timeframe = '1h'
startup_candle_count = 100
process_only_new_candles = True
can_short = True # Futures — long+short enabled
# --- Risk management (futures, balanced) ---
stoploss = -0.025 # 2.5% hard stop
trailing_stop = True
trailing_stop_positive = 0.01 # 1% trail once activated
trailing_stop_positive_offset = 0.02 # Activate after +2%
trailing_only_offset_is_reached = True
use_custom_stoploss = False
position_adjustment_enable = False
# --- ROI (let winners grow to 2:1 reward:risk) ---
minimal_roi = {
"0": 0.05, # 5% immediate (minimum winner)
"360": 0.02, # 2% after 15 days (patience pays)
"720": 0, # break even
}
# --- Protections ---
protections = [
{"method": "CooldownPeriod", "stop_duration": 12},
{"method": "StoplossGuard", "lookback_period": 48, "trade_limit": 3, "stop_duration": 24, "only_per_pair": False},
{"method": "MaxDrawdown", "lookback_period": 336, "max_allowed_drawdown": 0.08, "stop_duration": 72, "trade_limit": 5},
]
# --- Hyperopt-ready parameters ---
buy_rsi_low = IntParameter(28, 42, default=33, space='buy')
buy_rsi_high = IntParameter(55, 70, default=62, space='buy')
sell_rsi = IntParameter(68, 82, default=75, space='sell')
adx_threshold = IntParameter(18, 30, default=22, space='buy')
ema_short = IntParameter(8, 21, default=12, space='buy')
ema_long = IntParameter(30, 60, default=50, space='buy')
atr_mult = DecimalParameter(1.5, 3.5, default=2.5, space='sell')
bb_std = IntParameter(1, 3, default=2, space='buy')
def informative_pairs(self):
pairs = self.dp.current_whitelist() if self.dp else []
informative = [(pair, '4h') for pair in pairs]
informative.append(("BTC/USDT:USDT", "1h"))
return informative
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# --- Trend EMAs ---
dataframe['ema_8'] = ta.EMA(dataframe, timeperiod=8)
for p in [12, 20, 30, 50, 200]:
dataframe[f'ema_{p}'] = ta.EMA(dataframe, timeperiod=p)
# --- RSI ---
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
dataframe['rsi_fast'] = ta.RSI(dataframe, timeperiod=7)
# --- ADX for trend strength ---
dataframe['adx'] = ta.ADX(dataframe, timeperiod=14)
dataframe['plus_di'] = ta.PLUS_DI(dataframe, timeperiod=14)
dataframe['minus_di'] = ta.MINUS_DI(dataframe, timeperiod=14)
# --- Bollinger Bands ---
bb = ta.BBANDS(dataframe, timeperiod=20, nbdevup=2.0, nbdevdn=2.0)
dataframe['bb_upper'] = bb['upperband']
dataframe['bb_middle'] = bb['middleband']
dataframe['bb_lower'] = bb['lowerband']
dataframe['bb_width'] = (bb['upperband'] - bb['lowerband']) / bb['middleband']
dataframe['bb_position'] = (dataframe['close'] - dataframe['bb_lower']) / (dataframe['bb_upper'] - dataframe['bb_lower'])
# --- ATR for volatility ---
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
dataframe['atr_percent'] = dataframe['atr'] / dataframe['close'] * 100
# --- MACD ---
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
# --- Volume ---
dataframe['volume_ema'] = ta.EMA(dataframe['volume'], timeperiod=20)
dataframe['volume_ratio'] = dataframe['volume'] / (dataframe['volume_ema'] + 1e-10)
# --- Regime detection ---
dataframe['is_bull'] = (
(dataframe['close'] > dataframe['ema_200']) &
(dataframe['ema_50'] > dataframe['ema_200'])
).astype(int)
dataframe['is_bear'] = (
(dataframe['close'] < dataframe['ema_200']) &
(dataframe['ema_50'] < dataframe['ema_200'])
).astype(int)
# Strong trend (ADX > threshold)
dataframe['strong_trend'] = (dataframe['adx'] > 25).astype(int)
# --- Multi-timeframe (4h) ---
if self.dp:
df_4h = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe='4h')
if len(df_4h) > 0:
df_4h['ema_50'] = ta.EMA(df_4h, timeperiod=50)
df_4h['ema_200'] = ta.EMA(df_4h, timeperiod=200)
df_4h['rsi'] = ta.RSI(df_4h, timeperiod=14)
df_4h['adx'] = ta.ADX(df_4h, timeperiod=14)
dataframe = merge_informative_pair(
dataframe, df_4h[['date', 'ema_50', 'ema_200', 'rsi', 'adx']],
self.timeframe, '4h', ffill=True
)
# BTC sentiment
df_btc = self.dp.get_pair_dataframe(pair='BTC/USDT:USDT', timeframe='1h')
if len(df_btc) > 0:
df_btc['btc_rsi'] = ta.RSI(df_btc, timeperiod=14)
df_btc['btc_ema_200'] = ta.EMA(df_btc, timeperiod=200)
dataframe = merge_informative_pair(
dataframe, df_btc[['date', 'btc_rsi', 'btc_ema_200']],
self.timeframe, '1h', ffill=True
)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
conditions_long = []
conditions_short = []
# === LONG CONDITIONS ===
# In bull regime or recovering
bull_trend = (
(dataframe['is_bull'] == 1) |
((dataframe['close'] > dataframe['ema_50']) & (dataframe['strong_trend'] == 1))
)
# Pullback to EMA support with RSI oversold bounce
pullback_long = (
(dataframe['low'] <= dataframe['ema_50'] * 1.01) &
(dataframe['low'] <= dataframe['bb_middle']) &
(dataframe['rsi'] >= self.buy_rsi_low.value) &
(dataframe['rsi'] <= self.buy_rsi_high.value) &
(dataframe['volume_ratio'] > 0.8) &
(dataframe['close'] > dataframe['open']) # Bullish candle
)
# BB lower band bounce (need volume + MACD confirmation)
bb_bounce_long = (
(dataframe['close'] <= dataframe['bb_lower'] * 1.005) &
(dataframe['rsi'] < 38) &
(dataframe['volume_ratio'] > 1.5) &
(dataframe['macd'] > dataframe['macdsignal']) &
(dataframe['close'] > dataframe['open']) # confirmed bullish candle
)
# Trend continuation (strong uptrend, pullback to EMA)
trend_long = (
(dataframe['close'] > dataframe['ema_200']) &
(dataframe['ema_50'] > dataframe['ema_200']) &
(dataframe['adx'] > self.adx_threshold.value) &
(dataframe['plus_di'] > dataframe['minus_di']) &
(dataframe['rsi'] > 40) & (dataframe['rsi'] < 60) &
(dataframe['close'] > dataframe['ema_20'])
)
# Combine
conditions_long.append(
(bull_trend & pullback_long) | bb_bounce_long | trend_long
)
if conditions_long:
dataframe.loc[
reduce(lambda x, y: x | y, conditions_long),
'enter_long'] = 1
# === SHORT CONDITIONS (prepared for futures mode) ===
# Bear regime or breakdown
bear_trend = (
(dataframe['is_bear'] == 1) |
((dataframe['close'] < dataframe['ema_50']) & (dataframe['strong_trend'] == 1))
)
# Bounce up to resistance in bear market (short the relief rally)
bounce_short = (
(dataframe['high'] >= dataframe['ema_50'] * 0.99) &
(dataframe['high'] >= dataframe['bb_middle']) &
(dataframe['rsi'] > 50) &
(dataframe['close'] < dataframe['open']) & # Bearish candle
(dataframe['volume_ratio'] > 0.8)
)
# BB upper band rejection
bb_bounce_short = (
(dataframe['close'] >= dataframe['bb_upper'] * 0.995) &
(dataframe['rsi'] > 60) &
(dataframe['volume_ratio'] > 1.2) &
(dataframe['macd'] < dataframe['macdsignal']) &
(dataframe['adx'] < 30)
)
# Trend continuation (strong downtrend)
trend_short = (
(dataframe['close'] < dataframe['ema_200']) &
(dataframe['ema_50'] < dataframe['ema_200']) &
(dataframe['adx'] > self.adx_threshold.value) &
(dataframe['minus_di'] > dataframe['plus_di']) &
(dataframe['rsi'] < 60) & (dataframe['rsi'] > 35) &
(dataframe['close'] < dataframe['ema_20'])
)
conditions_short.append(
(bear_trend & bounce_short) | bb_bounce_short | trend_short
)
if conditions_short:
dataframe.loc[
reduce(lambda x, y: x | y, conditions_short),
'enter_short'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# No exit signals — let ROI, trailing stop, and stoploss handle exits
# Exit signals were destroying performance (7.2% win rate on 747 exits)
return dataframe