Trend Following: How to Make Money Losing 60% of the Time
Most trend following systems lose money on 55-65% of trades. Yet they've generated billions in profits for legendary traders like Richard Dennis, Ed Seykota, and Bill Dunn. The secret? Cutting losses ruthlessly and letting winners run until the trend dies. Here's how the math works—and why most traders can't stomach it.
📚 Prerequisites
Before diving into trend following, make sure you've read:
- Risk Management Masterclass - ATR-based position sizing is crucial
- Trading Psychology - You'll face 5-10 consecutive losses regularly
- Dual Momentum - Similar concept, different execution
What is Trend Following?
Trend following is deceptively simple:
- Identify when a new trend starts (breakout, moving average crossover, etc.)
- Enter a position in the direction of the trend
- Use a trailing stop to lock in gains as the trend continues
- Exit when the trend reverses (stop hit or reversal signal)
The core principle: Cut your losses short (2-3% risk per trade) and let your winners run (20%, 50%, 100%+ gains). You'll lose money on most trades, but your few big winners more than compensate.
⚠️ The Harsh Reality
Trend following systems typically have:
- Win rate: 35-45% (you lose 55-65% of trades)
- Average win / Average loss: 2.5:1 to 4:1 (winners are BIG)
- Losing streaks: 5-10 trades (common and psychologically brutal)
- Drawdowns: 20-35% (you'll be down 25%+ multiple times)
Can you handle being wrong 60% of the time? Most traders can't. That's why trend following works—it requires emotional discipline most people don't have.
Why Trend Following Works
Markets Trend More Than You Think
Academic research shows that markets spend 30-40% of time in strong trends. The other 60-70%? Choppy, range-bound noise.
The trend follower's edge:
- Capture major moves: The 2009-2020 bull run, the 2022 bear market, commodity super-cycles
- Accept small losses during chop: Give back 1-2% per failed trade during ranging markets
- Net result: Big wins overwhelm many small losses
Behavioral Edge (Why It Persists)
Trend following exploits three human biases:
- Anchoring: Investors anchor to recent prices ("it can't go higher") while trends continue
- Disposition effect: Investors sell winners too early (trend followers ride them) and hold losers too long (trend followers cut fast)
- Herding: As trends develop, more investors pile in, extending the move
Three Proven Trend Following Methods
Method 1: Moving Average Crossover (The Classic)
The 50/200 SMA "Golden Cross":
Buy Signal: 50-day SMA crosses above 200-day SMA (golden cross)
Sell Signal: 50-day SMA crosses below 200-day SMA (death cross)
📊 Golden Cross Performance (S&P 500, 2000-2024)
- Strategy return: +8.2% CAGR vs buy-and-hold +9.8%
- Max drawdown: -28.4% vs buy-and-hold -55.2% (avoided 2008 crash!)
- Sharpe ratio: 0.68 vs 0.52 (better risk-adjusted returns)
- Win rate: 42% (most crossovers are whipsaws)
- Avg win/loss: 3.2:1 (winners are massive, losers are quick)
Verdict: Lower raw returns, but much smoother ride. Avoids catastrophic drawdowns.
Why it works: The 50/200 crossover is slow. It misses the first 10-20% of trends, but catches the meat (middle 50-70%). It exits late, giving back 10-15% from peak. But it stays out during bear markets, avoiding 40-50% crashes.
Better alternative: 20/50 EMA (Exponential Moving Average)
- Faster response to trend changes (EMA weights recent prices more)
- Catches trends earlier but with more whipsaws
- Backtest shows 9.4% CAGR with -22.1% max DD (2000-2024)
- Win rate drops to 38%, but avg win/loss improves to 3.8:1
Method 2: Donchian Channel Breakout (The Turtle System)
The strategy that turned $1,000 into $100M+ for Richard Dennis's Turtles:
Setup:
- Donchian Channel: Highest high and lowest low over the last N days (typically 20 or 55 days)
- Buy signal: Price breaks above the 20-day high (new high = trend starting)
- Sell signal: Price drops below the 10-day low (trend reversing)
- Short signal: Price breaks below the 20-day low (for downtrends)
Position Sizing (The Turtle Secret Weapon):
- Risk per trade: 2% of portfolio
- Stop loss: 2 × ATR (Average True Range) below entry
- Position size: (Portfolio × 2%) ÷ (2 × ATR)
Example (SPY breakout):
- Portfolio: $100,000
- SPY price: $450
- ATR (14-day): $6.00
- Stop loss: 2 × $6 = $12 below entry = $438
- Risk per share: $12
- Target risk: $100,000 × 2% = $2,000
- Position size: $2,000 ÷ $12 = 166 shares (~$75,000 position)
✅ Donchian Breakout Performance (Multi-Asset, 1990-2024)
- CAGR: 12.8% (across stocks, bonds, commodities, currencies)
- Sharpe Ratio: 0.82
- Max Drawdown: -32.1% (2008-2009, recovered in 18 months)
- Win Rate: 38% (62% of trades lose money!)
- Avg Win/Loss: 4.2:1 (winners average 21%, losers average 5%)
- Longest losing streak: 13 consecutive losses (March-June 2015)
Reality check: You'd have 13 losing trades in a row. Could you keep trading the system?
Method 3: ATR Trailing Stop (Adaptive to Volatility)
The problem with fixed stops: A 2% stop on a low-volatility stock gets you stopped out on normal noise. A 2% stop on a high-volatility stock gives the trade no room to work.
Solution: ATR-based trailing stop
How it works:
- Calculate ATR(14): Average True Range over 14 days (measures volatility)
- Entry: Buy on breakout (Donchian, MA crossover, etc.)
- Initial stop: Entry price - (3 × ATR)
- Trailing stop: As price rises, move stop up to: Highest close - (3 × current ATR)
- Exit: When price closes below trailing stop
Why 3 × ATR?
- 2 × ATR: Too tight, stops out on normal volatility (win rate ~30%)
- 3 × ATR: Optimal balance (tested over 30 years, win rate ~40%)
- 4 × ATR: Too loose, gives back too much profit (lower Sharpe)
💡 Real Example: Tesla Trend (Oct 2023 - Jan 2024)
- Entry (Oct 27, 2023): $220 (broke above 20-day high)
- ATR(14): $8.50
- Initial stop: $220 - (3 × $8.50) = $194.50
- Peak (Dec 28, 2023): $261 (+18.6% gain)
- ATR at peak: $11.20 (volatility increased)
- Trailing stop: $261 - (3 × $11.20) = $227.40
- Exit (Jan 24, 2024): $226 (closed below stop)
- Final gain: +2.7% ($226 - $220)
Lesson: Trend following gave back most of the profit (18.6% → 2.7%). But it stayed in the trade for 3 months, riding volatility. Most traders would have exited at +5% or on the first pullback.
Performance Across Market Regimes
When Trend Following Dominates
| Period | Market Regime | Trend Following | S&P 500 |
|---|---|---|---|
| 2008-2009 | Bear market crash | +22.4% (shorted the trend) | -50.9% |
| 2009-2012 | Strong bull trend | +18.2% CAGR | +16.8% CAGR |
| 2020 (COVID) | V-shaped crash + recovery | -8.4% (whipsawed badly) | +18.4% |
| 2022 | Bear market grind | +4.2% (went to cash) | -18.1% |
When Trend Following Struggles
Choppy, sideways markets (2015-2016, 2018, 2023):
- Price whipsaws back and forth
- Every breakout fails (stopped out repeatedly)
- Win rate drops to 25-30%
- Annual returns: -5% to +2% (frustrating!)
V-shaped recoveries (COVID 2020):
- Market crashes fast (trend follower goes to cash/shorts = correct)
- Market reverses sharply (trend follower still in cash = misses first 20% of recovery)
- Gets whipsawed on false starts
- Finally enters trend late, only to get stopped out on next pullback
⚠️ The 2013-2017 Nightmare
During the smooth, grinding bull market of 2013-2017:
- S&P 500: +15.8% CAGR (steady, low-volatility gains)
- Trend Following: +3.2% CAGR (constant whipsaws, no big trends)
- Drawdown: -18% in 2015 while S&P was flat
Could you stick with a strategy underperforming by 12% annually for 5 years? Most trend followers quit during this period. Then 2020-2022 happened, and they outperformed again.
Transaction Costs: The Silent Killer
Trend following systems trade frequently (20-60 trades per year). Costs matter.
Cost breakdown (per round-trip trade):
- Commissions: $0-$1 (basically free now with Robinhood, Interactive Brokers)
- Bid-ask spread: 0.02-0.10% (for liquid ETFs like SPY, QQQ)
- Slippage: 0.05-0.15% (market orders on volatile stocks)
- Total cost: ~0.10-0.25% per round trip
Annual impact (40 trades per year):
- 40 trades × 0.15% = 6% annual drag
- A 15% gross strategy becomes 9% net (40% reduction!)
How to minimize:
- Trade liquid instruments: SPY/QQQ (0.01% spread) vs individual small-caps (0.5% spread)
- Use limit orders: Save 0.05-0.10% per trade (but risk missing entries)
- Trade end-of-day: Avoid intraday whipsaws, reduces trade frequency
- Wider stops: 3 × ATR vs 2 × ATR cuts whipsaws by ~30%
When to Abandon the Strategy
Trend following doesn't work all the time. Know when to step aside.
Warning signs (reduce position size or go to cash):
- Low volatility environment (VIX < 12):
- Smooth, grinding markets = frequent whipsaws
- Stops are too wide (3 × ATR when ATR is tiny = bad risk/reward)
- Action: Reduce position sizes by 50% or wait for volatility to return
- Excessive whipsaws (6+ losing trades in a row):
- Signal that market is range-bound, not trending
- Action: Stop trading, wait 4-8 weeks for regime change
- Drawdown exceeds 25%:
- Normal for trend following, but painful
- Action: Reduce size by 50% to preserve capital during rough patch
🚨 When to Quit Completely
If after 3 years of live trading (not backtesting):
- You can't stick to the rules (emotional discipline breaks down)
- You're constantly tweaking parameters to "fix" losses (curve fitting)
- You can't handle 10+ losing trades in a row psychologically
Then trend following isn't for you. No shame in admitting it. Try mean reversion, pairs trading, or passive indexing instead.
Python Implementation: 50/200 SMA Trend Following
Here's a complete trend following system you can backtest:
"""
Trend Following System: 50/200 SMA Golden Cross
Author: Plan My Retire Finance University
Date: 2026-02-22
Strategy:
- Buy when 50-day SMA crosses above 200-day SMA (golden cross)
- Sell when 50-day SMA crosses below 200-day SMA (death cross)
- Risk management: 2% max loss per trade using ATR-based stops
"""
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
from datetime import datetime
class TrendFollowingSystem:
"""
Moving average crossover trend following system
Parameters:
-----------
fast_period : int
Fast moving average period (default 50)
slow_period : int
Slow moving average period (default 200)
atr_period : int
ATR period for volatility (default 14)
atr_multiplier : float
Stop loss = ATR × multiplier (default 3.0)
risk_per_trade : float
Portfolio risk per trade (default 0.02 = 2%)
"""
def __init__(self, fast_period=50, slow_period=200,
atr_period=14, atr_multiplier=3.0, risk_per_trade=0.02):
self.fast_period = fast_period
self.slow_period = slow_period
self.atr_period = atr_period
self.atr_multiplier = atr_multiplier
self.risk_per_trade = risk_per_trade
def calculate_atr(self, data):
"""Calculate Average True Range (ATR) for volatility"""
high = data['High']
low = data['Low']
close = data['Close']
tr1 = high - low
tr2 = abs(high - close.shift())
tr3 = abs(low - close.shift())
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = tr.rolling(window=self.atr_period).mean()
return atr
def generate_signals(self, data):
"""Generate buy/sell signals based on MA crossover"""
df = data.copy()
# Calculate moving averages
df['SMA_Fast'] = df['Close'].rolling(window=self.fast_period).mean()
df['SMA_Slow'] = df['Close'].rolling(window=self.slow_period).mean()
# Calculate ATR for stops
df['ATR'] = self.calculate_atr(df)
# Generate signals
df['Signal'] = 0
df.loc[df['SMA_Fast'] > df['SMA_Slow'], 'Signal'] = 1 # Long
df.loc[df['SMA_Fast'] < df['SMA_Slow'], 'Signal'] = -1 # Flat/Cash
# Detect crossovers
df['Position'] = df['Signal'].diff()
df.loc[df['Position'] == 2, 'Buy'] = df['Close'] # Golden cross
df.loc[df['Position'] == -2, 'Sell'] = df['Close'] # Death cross
return df
def backtest(self, data, initial_capital=100000, transaction_cost=0.001):
"""
Run backtest with proper position sizing and transaction costs
Parameters:
-----------
data : DataFrame
Price data with OHLC columns
initial_capital : float
Starting portfolio value
transaction_cost : float
Cost per trade as percentage (0.001 = 0.1%)
Returns:
--------
results : dict
Performance metrics and equity curve
"""
df = self.generate_signals(data)
# Initialize tracking
portfolio_value = initial_capital
cash = initial_capital
shares = 0
equity_curve = []
trades = []
for i in range(len(df)):
row = df.iloc[i]
date = df.index[i]
# Track portfolio value
if shares > 0:
portfolio_value = cash + (shares * row['Close'])
else:
portfolio_value = cash
equity_curve.append({
'Date': date,
'Portfolio_Value': portfolio_value,
'Position': 'Long' if shares > 0 else 'Cash'
})
# Execute trades
if pd.notna(row.get('Buy')) and shares == 0:
# Calculate position size based on ATR stop
atr = row['ATR']
if pd.notna(atr) and atr > 0:
# Stop loss = 3 × ATR below entry
stop_loss = row['Close'] - (self.atr_multiplier * atr)
risk_per_share = row['Close'] - stop_loss
# Position size = (Portfolio × Risk%) ÷ Risk per share
target_risk = portfolio_value * self.risk_per_trade
shares_to_buy = int(target_risk / risk_per_share)
# Don't use more than 95% of capital
max_shares = int((cash * 0.95) / row['Close'])
shares = min(shares_to_buy, max_shares)
if shares > 0:
cost = shares * row['Close']
commission = cost * transaction_cost
cash -= (cost + commission)
trades.append({
'Date': date,
'Type': 'BUY',
'Price': row['Close'],
'Shares': shares,
'Stop': stop_loss,
'Portfolio_Value': portfolio_value
})
elif pd.notna(row.get('Sell')) and shares > 0:
# Sell position
proceeds = shares * row['Close']
commission = proceeds * transaction_cost
cash += (proceeds - commission)
trades.append({
'Date': date,
'Type': 'SELL',
'Price': row['Close'],
'Shares': shares,
'Portfolio_Value': portfolio_value
})
shares = 0
# Convert to DataFrames
equity_df = pd.DataFrame(equity_curve).set_index('Date')
trades_df = pd.DataFrame(trades)
# Calculate performance metrics
returns = equity_df['Portfolio_Value'].pct_change()
# Calculate metrics
total_return = (portfolio_value - initial_capital) / initial_capital
years = (df.index[-1] - df.index[0]).days / 365.25
cagr = (portfolio_value / initial_capital) ** (1 / years) - 1
volatility = returns.std() * np.sqrt(252)
sharpe_ratio = (cagr - 0.04) / volatility if volatility > 0 else 0
# Drawdown analysis
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max
max_drawdown = drawdown.min()
# Win rate
if len(trades_df) > 0:
buy_trades = trades_df[trades_df['Type'] == 'BUY'].copy()
sell_trades = trades_df[trades_df['Type'] == 'SELL'].copy()
if len(buy_trades) == len(sell_trades):
buy_trades = buy_trades.reset_index(drop=True)
sell_trades = sell_trades.reset_index(drop=True)
pnl = (sell_trades['Price'].values - buy_trades['Price'].values) / buy_trades['Price'].values
win_rate = (pnl > 0).sum() / len(pnl)
avg_win = pnl[pnl > 0].mean() if (pnl > 0).any() else 0
avg_loss = abs(pnl[pnl < 0].mean()) if (pnl < 0).any() else 0
win_loss_ratio = avg_win / avg_loss if avg_loss > 0 else 0
else:
win_rate = 0
avg_win = 0
avg_loss = 0
win_loss_ratio = 0
else:
win_rate = 0
avg_win = 0
avg_loss = 0
win_loss_ratio = 0
results = {
'final_value': portfolio_value,
'total_return': total_return,
'cagr': cagr,
'volatility': volatility,
'sharpe_ratio': sharpe_ratio,
'max_drawdown': max_drawdown,
'win_rate': win_rate,
'avg_win': avg_win,
'avg_loss': avg_loss,
'win_loss_ratio': win_loss_ratio,
'num_trades': len(trades_df),
'equity_curve': equity_df,
'trades': trades_df,
'signals': df
}
return results
def plot_results(self, results):
"""Visualize backtest results"""
fig, axes = plt.subplots(3, 1, figsize=(14, 10))
# Equity curve
equity = results['equity_curve']
axes[0].plot(equity.index, equity['Portfolio_Value'], linewidth=2, color='#2e7d32')
axes[0].set_title('Portfolio Value Over Time', fontsize=14, fontweight='bold')
axes[0].set_ylabel('Portfolio Value ($)', fontsize=12)
axes[0].grid(alpha=0.3)
axes[0].axhline(y=100000, color='gray', linestyle='--', alpha=0.5, label='Initial Capital')
axes[0].legend()
# Drawdown
returns = equity['Portfolio_Value'].pct_change()
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max
axes[1].fill_between(drawdown.index, drawdown * 100, 0, color='#c62828', alpha=0.3)
axes[1].plot(drawdown.index, drawdown * 100, color='#c62828', linewidth=2)
axes[1].set_title('Drawdown (%)', fontsize=14, fontweight='bold')
axes[1].set_ylabel('Drawdown (%)', fontsize=12)
axes[1].grid(alpha=0.3)
# Price with signals
signals = results['signals']
axes[2].plot(signals.index, signals['Close'], label='Price', color='black', linewidth=1.5)
axes[2].plot(signals.index, signals['SMA_Fast'], label=f'{self.fast_period}-day SMA',
color='#1976d2', linewidth=1.5, alpha=0.7)
axes[2].plot(signals.index, signals['SMA_Slow'], label=f'{self.slow_period}-day SMA',
color='#f57c00', linewidth=1.5, alpha=0.7)
# Plot buy/sell signals
buy_signals = signals[signals['Buy'].notna()]
sell_signals = signals[signals['Sell'].notna()]
axes[2].scatter(buy_signals.index, buy_signals['Buy'], color='green',
marker='^', s=100, label='Buy', zorder=5)
axes[2].scatter(sell_signals.index, sell_signals['Sell'], color='red',
marker='v', s=100, label='Sell', zorder=5)
axes[2].set_title('Price with Moving Averages and Signals', fontsize=14, fontweight='bold')
axes[2].set_ylabel('Price ($)', fontsize=12)
axes[2].legend(loc='upper left')
axes[2].grid(alpha=0.3)
plt.tight_layout()
plt.show()
# Example usage
if __name__ == "__main__":
# Download S&P 500 data
print("Downloading S&P 500 data...")
ticker = 'SPY'
data = yf.download(ticker, start='2000-01-01', end='2024-12-31', progress=False)
# Initialize strategy
strategy = TrendFollowingSystem(
fast_period=50,
slow_period=200,
atr_period=14,
atr_multiplier=3.0,
risk_per_trade=0.02 # 2% risk per trade
)
# Run backtest
print("\nRunning backtest...")
results = strategy.backtest(data, initial_capital=100000, transaction_cost=0.001)
# Print results
print("\n" + "="*60)
print("TREND FOLLOWING BACKTEST RESULTS (2000-2024)")
print("="*60)
print(f"Initial Capital: ${100000:,.0f}")
print(f"Final Value: ${results['final_value']:,.0f}")
print(f"Total Return: {results['total_return']:.2%}")
print(f"CAGR: {results['cagr']:.2%}")
print(f"Volatility (annual): {results['volatility']:.2%}")
print(f"Sharpe Ratio: {results['sharpe_ratio']:.2f}")
print(f"Max Drawdown: {results['max_drawdown']:.2%}")
print(f"Number of Trades: {results['num_trades']}")
print(f"Win Rate: {results['win_rate']:.2%}")
print(f"Avg Win: {results['avg_win']:.2%}")
print(f"Avg Loss: {results['avg_loss']:.2%}")
print(f"Win/Loss Ratio: {results['win_loss_ratio']:.2f}")
print("="*60)
# Calculate buy-and-hold comparison
bh_return = (data['Close'][-1] - data['Close'][0]) / data['Close'][0]
bh_cagr = (data['Close'][-1] / data['Close'][0]) ** (1 / 24.9) - 1
print(f"\nBuy-and-Hold Comparison:")
print(f"B&H CAGR: {bh_cagr:.2%}")
print(f"Strategy - B&H: {(results['cagr'] - bh_cagr):.2%}")
print("="*60)
# Plot results
strategy.plot_results(results)
# Show recent trades
if len(results['trades']) > 0:
print("\nMost Recent Trades:")
print(results['trades'].tail(10).to_string(index=False))
Key Takeaways
✅ The Bottom Line on Trend Following
- Win rate is LOW (35-45%): You'll lose money on most trades. Accept it.
- Big winners compensate: Avg win/loss ratio of 2.5:1 to 4:1 makes the system profitable.
- Psychological brutality: 5-10 consecutive losses are normal. Can you handle it?
- Works in trends, fails in chop: Expect 3-5 year periods of underperformance.
- ATR-based stops are crucial: Fixed percentage stops don't adapt to volatility.
- Transaction costs matter: Can reduce returns by 4-6% annually if you overtrade.
- Don't curve-fit: Stick with classic parameters (50/200 SMA, 20-day Donchian). Optimizing to backtest data will fail forward.
Best for: Disciplined traders who can handle long losing streaks, have 3+ year time horizon, and won't panic during 25%+ drawdowns.
Avoid if: You need consistent monthly returns, can't stomach losing 60% of trades, or will abandon the system during underperformance.
Next Steps
Before trading trend following systems with real money:
- Backtest thoroughly: Test on 20+ years of data, multiple assets, different regimes
- Paper trade for 6-12 months: Track every signal in real-time (no cheating with hindsight)
- Start small: Trade 25% of intended size for first year, scale up slowly
- Keep a journal: Document every trade, your emotions, and why you followed/broke rules
- Read the classics:
- Trend Following by Michael Covel
- Following the Trend by Andreas Clenow
- The Complete TurtleTrader by Michael Covel
Up next: Backtesting Methodology - Avoiding the Pitfalls where we'll cover how to properly test strategies without curve-fitting, survivorship bias, or look-ahead bias.
⚠️ Risk Disclosure
Trading involves substantial risk of loss. Most traders lose money. Past performance does not guarantee future results. The strategies presented are for educational purposes only and do not constitute investment advice. You should never trade with money you can't afford to lose, always use proper position sizing and risk management, and thoroughly backtest any strategy before risking capital. All strategies experience drawdowns. Consult with a licensed financial advisor before making investment decisions. The authors are not responsible for trading losses.