Running an algorithmic trading bot is one of those things that sounds glamorous until you watch your carefully crafted strategy bleed money because your stop loss logic is garbage.
I've been running a Freqtrade-based crypto bot on Bybit for several months now, trading BTC/USDT, ETH/USDT, SOL/USDT, and a handful of altcoin futures pairs. Over the past 3 months, I systematically tested four different stop loss approaches to figure out which one actually protects capital without killing profitability.
Here's what I found.
Method 1: Fixed Percentage Stop Loss
The simplest approach. Set a fixed percentage below your entry price. If the trade drops by that amount, you're out.
I tested three variants: -2%, -3.5%, and -6%.
# Freqtrade strategy — fixed percentage stop loss
class MyStrategy(IStrategy):
stoploss = -0.035 # -3.5% fixed stop loss
# No trailing, no custom logic
trailing_stop = False
Pros:
- Dead simple to implement and understand
- Predictable risk per trade
- Easy to calculate position sizing
Cons:
- Doesn't adapt to market volatility — a -2% stop on BTC during a volatile week is basically guaranteed to get hit
- Too tight = stopped out on noise; too wide = excessive losses on real reversals
The -2% stop had a terrible win rate (41%) because crypto is volatile enough that normal price action triggers it constantly. The -6% stop had better win rates but the average loss was painful. -3.5% was the sweet spot for my strategy's typical holding period of 4-18 hours.
Method 2: Trailing Stop Loss
A trailing stop follows the price upward and locks in profits. If BTC moves 5% in your favor, the stop moves up with it, so you capture some of that gain even if it reverses.
class MyStrategy(IStrategy):
stoploss = -0.035
# Trailing stop configuration
trailing_stop = True
trailing_stop_positive = 0.01 # Start trailing at +1% profit
trailing_stop_positive_offset = 0.02 # Only activate after +2% profit
trailing_only_offset_is_reached = True
Pros:
- Locks in profits on strong moves
- Lets winners run while protecting gains
- Great in trending markets
Cons:
- Whipsaws destroy you in crypto. The market pumps 3%, your trail activates, then a 1.5% pullback (totally normal) stops you out right before it pumps another 5%.
- You end up capturing the middle of moves instead of the full move
- In choppy/ranging markets, it's worse than a fixed stop
In my backtests, trailing stops performed about 8% worse than the fixed -3.5% stop in terms of total profit. The whipsaw problem is real — crypto moves in jagged patterns, not smooth trends.
Method 3: ATR-Based Dynamic Stop Loss
This is where things get interesting. ATR (Average True Range) measures how much an asset typically moves in a given period. By setting your stop as a multiple of ATR, it automatically adapts to current volatility.
When BTC is calm, your stop is tight. When it's volatile, your stop gives more room.
import talib.abstract as ta
class MyStrategy(IStrategy):
stoploss = -0.10 # Wide safety net (ATR handles the real stop)
def populate_indicators(self, dataframe, metadata):
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
return dataframe
def custom_stoploss(self, pair, trade, current_time,
current_rate, current_profit, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1]
if last_candle['atr'] > 0:
# Stop loss at 2x ATR below current price
atr_stop = (last_candle['atr'] * 2) / current_rate
return -atr_stop
return -0.035 # Fallback to fixed stop
Pros:
- Automatically adapts to volatility — no manual tuning per asset
- Works well across different pairs (BTC, ETH, SOL all have different volatility profiles)
- Reduces false stops during volatile periods
- More sophisticated risk management
Cons:
- More complex to implement and debug
- ATR can lag during sudden volatility spikes
- Harder to calculate exact risk per trade in advance
- The multiplier (2x ATR in my case) still needs tuning
ATR-based stops performed ~5% better than fixed stops across all pairs combined. The biggest improvement was on altcoins, where volatility varies dramatically day-to-day.
Method 4: Time-Based Exit
This one is underrated. Instead of (or in addition to) a price-based stop, you exit the trade if it hasn't reached a profit threshold after N hours. The logic: if your signal was right, the move should happen within a reasonable timeframe. If it hasn't moved, your thesis is probably wrong.
from datetime import timedelta
class MyStrategy(IStrategy):
stoploss = -0.035
def custom_exit(self, pair, trade, current_time,
current_rate, current_profit, **kwargs):
# Exit if trade is open > 24 hours and profit < 1%
trade_duration = current_time - trade.open_date_utc
if trade_duration > timedelta(hours=24) and current_profit < 0.01:
return 'time_exit_24h'
# Exit if trade is open > 48 hours regardless
if trade_duration > timedelta(hours=48):
return 'time_exit_48h'
return None
Pros:
- Frees up capital from stagnant positions
- Reduces exposure time (less time in market = less risk)
- Catches trades that would otherwise slowly bleed with fees
- Complements price-based stops perfectly
Cons:
- You might exit right before a delayed move
- Requires understanding your strategy's typical winning timeframe
- Not useful if your strategy has highly variable holding periods
The 24-hour time exit was a game-changer. It eliminated a whole category of trades that would sit at -0.5% to -1.5% for days, slowly bleeding funding fees. These "zombie trades" were dragging down my overall performance more than I realized.
Results: Head-to-Head Comparison
Here are the actual backtest results across 3 months of crypto futures data (BTC, ETH, SOL, AVAX, LINK on 5m candles):
| Method | Win Rate | Avg Profit/Trade | Max Drawdown | Total Profit |
|---|---|---|---|---|
| Fixed -2% | 41.2% | -0.18% | -18.4% | -12.3% |
| Fixed -3.5% | 58.7% | +0.42% | -11.2% | +28.6% |
| Fixed -6% | 63.1% | +0.31% | -16.8% | +19.4% |
| Trailing (from +2%) | 54.3% | +0.38% | -12.7% | +22.1% |
| ATR-based (2x) | 61.4% | +0.51% | -9.8% | +33.2% |
| Time exit (24h) only | 55.8% | +0.29% | -13.1% | +18.7% |
| Hybrid: -3.5% + 24h time | 62.8% | +0.54% | -8.9% | +36.4% |
What I Actually Use Now
After all this testing, my production strategy uses a hybrid approach: a fixed -3.5% stop loss combined with a 24-hour time-based exit.
class TrendRiderStrategy(IStrategy):
stoploss = -0.035 # Fixed -3.5% stop
trailing_stop = False
def custom_exit(self, pair, trade, current_time,
current_rate, current_profit, **kwargs):
trade_duration = current_time - trade.open_date_utc
# Time exit: 24h with < 1% profit
if trade_duration > timedelta(hours=24) and current_profit < 0.01:
return 'time_exit'
# Take profit at 4%+
if current_profit >= 0.04:
return 'take_profit'
return None
Why this combo works:
- The -3.5% stop handles real reversals and limits downside
- The 24h time exit cleans up zombie trades that go nowhere
- Together they give the lowest max drawdown (-8.9%) of any method tested
- The win rate stays above 62% which is solid for crypto
Key Insight
The "best" stop loss doesn't exist in isolation. It depends entirely on:
- Your strategy's holding period — short-term scalps need tighter stops; swing trades need room to breathe
- The market regime — trending markets favor trailing stops; ranging markets favor fixed + time exits
- Your pair's volatility profile — what works for BTC won't work for a small-cap altcoin
The real lesson is: backtest every stop loss method against YOUR specific strategy, on YOUR specific pairs, for YOUR specific timeframe. Don't copy someone else's -2% stop and wonder why you're getting stopped out every other trade.
And please, for the love of all that is holy, don't trade crypto without a stop loss. I've seen what happens. It's not pretty.
If you're building a trading bot, check out TrendRider at trendrider.net — we share our signals and results transparently.
Top comments (0)