title: Strategy Examples Library description: Complete working examples of common trading strategies
Strategy Examples Library¶
This section provides complete, working implementations of popular trading strategies. Each example includes the full source code, parameter descriptions, and expected performance characteristics.
Table of Contents¶
Trend Following Strategy (Dual Moving Average)¶
Overview¶
The dual moving average crossover strategy is one of the most fundamental trend-following approaches. It generates buy signals when the short-term moving average crosses above the long-term moving average (golden cross) and sell signals when the short-term crosses below (death cross).
Strategy Code¶
import backtrader as bt
class DualMovingAverageStrategy(bt.Strategy):
"""Dual moving average crossover trend-following strategy.
This strategy buys when the short-term MA crosses above the long-term MA
(golden cross) and sells when the short-term MA crosses below (death cross).
Parameters:
short_period (int): Period for short-term moving average (default: 10)
long_period (int): Period for long-term moving average (default: 30)
position_size (float): Fraction of available cash to use per trade (default: 0.95)
"""
params = (
('short_period', 10),
('long_period', 30),
('position_size', 0.95),
)
def __init__(self):
# Calculate moving averages
self.short_ma = bt.indicators.SMA(self.data.close, period=self.p.short_period)
self.long_ma = bt.indicators.SMA(self.data.close, period=self.p.long_period)
# Crossover indicator: +1 for golden cross, -1 for death cross
self.crossover = bt.indicators.CrossOver(self.short_ma, self.long_ma)
# Track orders to avoid duplicate entries
self.order = None
def next(self):
# Wait for pending order to complete
if self.order:
return
# No position - look for entry
if not self.position:
if self.crossover > 0: # Golden cross
cash = self.broker.getcash()
price = self.data.close[0]
size = int(cash * self.p.position_size / price)
if size > 0:
self.order = self.buy(size=size)
else:
# Have position - look for exit
if self.crossover < 0: # Death cross
self.order = self.close()
def notify_order(self, order):
"""Handle order status updates."""
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
```bash
### Performance Expectations
- **Market Type**: Works best in trending markets
- **Whipsaw Risk**: High in ranging/consolidating markets
- **Win Rate**: Typically 35-45% (relies on few large wins)
- **Risk/Reward**: Can achieve 2:1 or better in strong trends
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| short_period | 5-20 | Shorter = more signals, more noise |
| long_period | 20-60 | Longer = fewer signals, more lag |
- --
## Mean Reversion Strategy (Bollinger Bands)
### Overview
Mean reversion strategies profit from price returning to its average value. Bollinger Bands provide dynamic support/resistance levels based on standard deviations from a moving average, identifying overbought and oversold conditions.
### Strategy Code
```python
import backtrader as bt
class BollingerBandsMeanReversion(bt.Strategy):
"""Bollinger Bands mean reversion strategy.
This strategy identifies overbought/oversold conditions using Bollinger Bands
and enters positions when price shows signs of reverting to the mean.
Entry Rules:
- Buy when price closes below lower band then rises above middle band
- Sell when price closes above upper band then falls below middle band
Parameters:
period (int): Period for Bollinger Bands calculation (default: 20)
devfactor (float): Standard deviation multiplier (default: 2.0)
"""
params = (
('period', 20),
('devfactor', 2.0),
)
def __init__(self):
# Bollinger Bands indicator
self.bband = bt.indicators.BBands(
self.data.close,
period=self.p.period,
devfactor=self.p.devfactor
)
# Track signals
self.oversold = False # Price broke below lower band
self.overbought = False # Price broke above upper band
self.order = None
def next(self):
if self.order:
return
# Check for oversold condition
if self.data.close[0] < self.bband.lines.bot[0]:
self.oversold = True
# Check for overbought condition
if self.data.close[0] > self.bband.lines.top[0]:
self.overbought = True
# Entry: Price rose back above middle band after being oversold
if self.oversold and self.data.close[0] > self.bband.lines.mid[0]:
if not self.position:
cash = self.broker.getcash()
size = int(cash *0.95 / self.data.close[0])
self.order = self.buy(size=size)
self.oversold = False
# Entry: Short when price falls below middle band after being overbought
if self.overbought and self.data.close[0] < self.bband.lines.mid[0]:
if not self.position:
cash = self.broker.getcash()
size = int(cash* 0.95 / self.data.close[0])
self.order = self.sell(size=size)
self.overbought = False
# Exit long positions
if self.position and self.position.size > 0:
if self.data.close[0] > self.bband.lines.top[0]:
self.order = self.close()
# Exit short positions
if self.position and self.position.size < 0:
if self.data.close[0] < self.bband.lines.bot[0]:
self.order = self.close()
def notify_order(self, order):
"""Handle order status updates."""
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
```bash
### Performance Expectations
- **Market Type**: Works best in ranging/oscillating markets
- **Trend Risk**: Can suffer large losses in strong trending markets
- **Win Rate**: Typically 55-65%
- **Risk/Reward**: Aim for 1:1 to 1.5:1
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| period | 15-30 | Affects band responsiveness |
| devfactor | 1.5-2.5 | Wider bands = fewer signals |
- --
## Breakout Strategy (Donchian Channels)
### Overview
Breakout strategies trade momentum when price breaks through significant support or resistance levels. Donchian Channels use the highest high and lowest low over a period to define these levels, making them ideal for catching trending moves early.
### Strategy Code
```python
import backtrader as bt
class DonchianChannelBreakout(bt.Strategy):
"""Donchian Channel breakout strategy.
This classic trend-following strategy buys when price breaks above
the N-period high and sells when price breaks below the N-period low.
Entry Rules:
- Buy when price closes above the highest high of the period
- Sell when price closes below the lowest low of the period
Exit Rules:
- Exit long when price closes below the lowest low
- Exit short when price closes above the highest high
Parameters:
period (int): Lookback period for channel calculation (default: 20)
"""
params = (
('period', 20),
)
def __init__(self):
# Donchian Channel components
self.highest = bt.indicators.Highest(self.data.high, period=self.p.period)
self.lowest = bt.indicators.Lowest(self.data.low, period=self.p.period)
self.order = None
def next(self):
if self.order:
return
# No position - look for breakout entry
if not self.position:
# Breakout above previous high
if self.data.close[0] > self.highest[-1]:
cash = self.broker.getcash()
size = int(cash *0.95 / self.data.close[0])
if size > 0:
self.order = self.buy(size=size)
# Breakdown below previous low
elif self.data.close[0] < self.lowest[-1]:
cash = self.broker.getcash()
size = int(cash* 0.95 / self.data.close[0])
if size > 0:
self.order = self.sell(size=size)
# Long position - look for exit
elif self.position.size > 0:
if self.data.close[0] < self.lowest[-1]:
self.order = self.close()
# Short position - look for exit
else:
if self.data.close[0] > self.highest[-1]:
self.order = self.close()
def notify_order(self, order):
"""Handle order status updates."""
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
```bash
### Performance Expectations
- **Market Type**: Excels in markets with clear trends
- **False Breakouts**: Common in choppy markets
- **Win Rate**: Typically 30-40% (depends on big trends)
- **Risk/Reward**: Can achieve 3:1 in strong trends
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| period | 10-40 | Shorter = more breakouts, more false signals |
- --
## Grid Trading Strategy
### Overview
Grid trading places buy and sell orders at regular intervals above and below a current price level. This strategy profits from market volatility and works best in ranging markets where price oscillates within a defined range.
### Strategy Code
```python
import backtrader as bt
class GridTradingStrategy(bt.Strategy):
"""Grid trading strategy for ranging markets.
This strategy places a grid of buy orders below current price and
sell orders above current price. As price moves, it fills orders
at grid levels and takes profit at the next level.
Parameters:
grid_size (float): Price distance between grid levels (e.g., 0.01 for 1%)
grid_levels (int): Number of grid levels above and below (default: 5)
max_position (int): Maximum concurrent positions (default: 10)
"""
params = (
('grid_size', 0.01), # 1% grid
('grid_levels', 5),
('max_position', 10),
)
def __init__(self):
self.grid_buy_orders = {} # price -> order
self.grid_sell_orders = {} # price -> order
self.grid_initialized = False
self.base_price = None
def next(self):
current_price = self.data.close[0]
# Initialize grid on first bar
if not self.grid_initialized:
self.base_price = current_price
self.initialize_grid(current_price)
self.grid_initialized = True
return
# Check for filled orders and place opposite orders
self.check_filled_orders(current_price)
# Maintain grid levels
self.rebalance_grid(current_price)
def initialize_grid(self, current_price):
"""Initialize buy and sell grid levels."""
for i in range(1, self.p.grid_levels + 1):
buy_price = round(current_price *(1 - self.p.grid_size*i), 2)
sell_price = round(current_price*(1 + self.p.grid_size*i), 2)
# Place buy orders below current price
if len([o for o in self.grid_buy_orders.values() if o]) < self.p.max_position:
order = self.buy(price=buy_price, exectype=bt.Order.Limit)
self.grid_buy_orders[buy_price] = order
# Place sell orders above current price
if len([o for o in self.grid_sell_orders.values() if o]) < self.p.max_position:
order = self.sell(price=sell_price, exectype=bt.Order.Limit)
self.grid_sell_orders[sell_price] = order
def check_filled_orders(self, current_price):
"""Check for filled orders and place profit-taking orders."""
# Check if any buy orders were filled
for price, order in list(self.grid_buy_orders.items()):
if order and order.status == order.Completed:
# Place sell order at next grid level for profit
profit_price = round(price*(1 + self.p.grid_size), 2)
if profit_price not in self.grid_sell_orders:
self.sell(price=profit_price, size=order.executed.size,
exectype=bt.Order.Limit)
del self.grid_buy_orders[price]
# Check if any sell orders were filled
for price, order in list(self.grid_sell_orders.items()):
if order and order.status == order.Completed:
# Place buy order at next grid level for profit
profit_price = round(price*(1 - self.p.grid_size), 2)
if profit_price not in self.grid_buy_orders:
self.buy(price=profit_price, size=abs(order.executed.size),
exectype=bt.Order.Limit)
del self.grid_sell_orders[price]
def rebalance_grid(self, current_price):
"""Maintain grid levels as price moves."""
# Cancel orders that are too far from current price
for price, order in list(self.grid_buy_orders.items()):
if order and price < current_price*(1 - self.p.grid_size*(self.p.grid_levels + 2)):
self.cancel(order)
del self.grid_buy_orders[price]
for price, order in list(self.grid_sell_orders.items()):
if order and price > current_price*(1 + self.p.grid_size* (self.p.grid_levels + 2)):
self.cancel(order)
del self.grid_sell_orders[price]
# Add new grid levels as needed
active_orders = len([o for o in self.grid_buy_orders.values() if o]) + \
len([o for o in self.grid_sell_orders.values() if o])
if active_orders < self.p.max_position:
self.initialize_grid(current_price)
```bash
### Performance Expectations
- **Market Type**: Optimized for ranging/consolidating markets
- **Trend Risk**: Can accumulate losing positions in strong trends
- **Win Rate**: High win rate, small profits per trade
- **Requirements**: Sufficient capital for multiple positions
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| grid_size | 0.005-0.02 | Smaller = more trades, more exposure |
| grid_levels | 3-10 | More levels = more capital required |
| max_position | 5-20 | Limit risk exposure |
- --
## Arbitrage Strategy (Calendar Spread)
### Overview
Calendar spread arbitrage profits from the price difference between near-term and far-term futures contracts. This market-neutral strategy trades the relationship between two related instruments rather than directional price movement.
### Strategy Code
```python
import backtrader as bt
class CalendarSpreadArbitrage(bt.Strategy):
"""Calendar spread arbitrage strategy.
Trades the price difference between near-term and far-term contracts.
Goes long the spread when near price - far price is low (contango),
goes short the spread when near price - far price is high (backwardation).
Parameters:
spread_low (float): Lower threshold to enter long spread
spread_high (float): Upper threshold to enter short spread
"""
params = (
('spread_low', 0.06),
('spread_high', 0.52),
)
def __init__(self):
# Assumes data[0] is near contract, data[1] is far contract
self.near = self.datas[0]
self.far = self.datas[1]
self.spread_position = 0 # 1=long spread, -1=short spread, 0=flat
self.order = None
def next(self):
if self.order:
return
current_spread = self.near.close[0] - self.far.close[0]
# No position - look for entry
if self.spread_position == 0:
# Spread is low - buy near, sell far (long spread)
if current_spread < self.p.spread_low:
self.order = self.buy(data=self.near, size=1)
self.order = self.sell(data=self.far, size=1)
self.spread_position = 1
# Spread is high - sell near, buy far (short spread)
elif current_spread > self.p.spread_high:
self.order = self.sell(data=self.near, size=1)
self.order = self.buy(data=self.far, size=1)
self.spread_position = -1
# Long spread position - look for exit
elif self.spread_position == 1:
if current_spread > self.p.spread_high:
self.close(data=self.near)
self.close(data=self.far)
self.spread_position = 0
# Short spread position - look for exit
elif self.spread_position == -1:
if current_spread < self.p.spread_low:
self.close(data=self.near)
self.close(data=self.far)
self.spread_position = 0
def notify_order(self, order):
"""Handle order status updates."""
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
def notify_trade(self, trade):
"""Log trade completion."""
if trade.isclosed:
print(f'Trade P&L: {trade.pnl:.2f}, Commission: {trade.commission:.2f}')
```bash
### Performance Expectations
- **Market Type**: Futures markets with term structure
- **Market Neutral**: Profits from relative value, not direction
- **Win Rate**: High win rate, steady profits
- **Capital Efficiency**: Requires margin for both legs
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| spread_low | Varies by market | Entry for long spread |
| spread_high | Varies by market | Entry for short spread |
- --
## Momentum Strategy (SuperTrend)
### Overview
The SuperTrend indicator combines trend direction with volatility to produce clear buy and sell signals. It's particularly effective in markets with sustained trends and automatically adjusts to changing volatility conditions.
### Strategy Code
```python
import backtrader as bt
class SuperTrendIndicator(bt.Indicator):
"""SuperTrend indicator.
A trend-following indicator that uses ATR to calculate dynamic
support/resistance levels.
"""
lines = ('supertrend', 'direction')
params = dict(
period=10,
multiplier=3.0,
)
def __init__(self):
self.atr = bt.indicators.ATR(self.data, period=self.p.period)
self.hl2 = (self.data.high + self.data.low) / 2.0
def next(self):
if len(self) < self.p.period + 1:
self.lines.supertrend[0] = self.hl2[0]
self.lines.direction[0] = 1
return
atr = self.atr[0]
hl2 = self.hl2[0]
upper_band = hl2 + self.p.multiplier *atr
lower_band = hl2 - self.p.multiplier*atr
prev_supertrend = self.lines.supertrend[-1]
prev_direction = self.lines.direction[-1]
if prev_direction == 1: # Uptrend
if self.data.close[0] < prev_supertrend:
self.lines.supertrend[0] = upper_band
self.lines.direction[0] = -1
else:
self.lines.supertrend[0] = max(lower_band, prev_supertrend)
self.lines.direction[0] = 1
else: # Downtrend
if self.data.close[0] > prev_supertrend:
self.lines.supertrend[0] = lower_band
self.lines.direction[0] = 1
else:
self.lines.supertrend[0] = min(upper_band, prev_supertrend)
self.lines.direction[0] = -1
class SuperTrendStrategy(bt.Strategy):
"""SuperTrend momentum strategy.
Goes long when trend turns up and exits when trend turns down.
Parameters:
period (int): ATR period for SuperTrend calculation (default: 10)
multiplier (float): ATR multiplier for band width (default: 3.0)
"""
params = (
('period', 10),
('multiplier', 3.0),
)
def __init__(self):
self.supertrend = SuperTrendIndicator(
self.data,
period=self.p.period,
multiplier=self.p.multiplier
)
self.order = None
def next(self):
if self.order:
return
# Buy when trend turns from down to up
if not self.position:
if (self.supertrend.direction[0] == 1 and
self.supertrend.direction[-1] == -1):
cash = self.broker.getcash()
size = int(cash* 0.95 / self.data.close[0])
if size > 0:
self.order = self.buy(size=size)
else:
# Exit when trend turns down
if self.supertrend.direction[0] == -1:
self.order = self.close()
def notify_order(self, order):
"""Handle order status updates."""
if order.status in [order.Submitted, order.Accepted]:
return
self.order = None
```bash
### Performance Expectations
- **Market Type**: Trending markets with sustained moves
- **Whipsaw Risk**: Moderate in choppy markets
- **Win Rate**: Typically 40-50%
- **Risk/Reward**: Can achieve 2:1 or better
### Optimization Parameters
| Parameter | Range | Effect |
|-----------|-------|--------|
| period | 7-15 | Shorter = more sensitive |
| multiplier | 2.0-4.0 | Higher = fewer signals, better trend filter |
- --
## Running These Examples
To use any of these strategies:
```python
import backtrader as bt
import backtrader.feeds as btfeeds
# Create Cerebro engine
cerebro = bt.Cerebro()
# Add your chosen strategy
cerebro.addstrategy(DualMovingAverageStrategy, short_period=10, long_period=30)
# Load data
data = btfeeds.GenericCSVData(
dataname='your_data.csv',
dtformat='%Y-%m-%d',
datetime=0,
open=1,
high=2,
low=3,
close=4,
volume=5,
openinterest=-1
)
cerebro.adddata(data)
# Set initial capital and commission
cerebro.broker.setcash(100000)
cerebro.broker.setcommission(commission=0.001)
# Run
results = cerebro.run()
# Plot
cerebro.plot()
```bash
## Next Steps
- [Custom Indicators](../user_guide/indicators.md) - Create your own indicators
- [Analyzers](../user_guide/analyzers.md) - Evaluate strategy performance
- [Optimization](../user_guide/optimization.md) - Find optimal parameters