Grid trading is a strong first strategy, but profitable bots often combine or switch between approaches. This guide covers three proven alternatives — DCA, mean reversion, and trend following — with runnable Python examples that plug into the same Binance client from the trading bot series.
Series context: This is a companion to part 3 (grid trading strategy). Complete the Python setup and Binance API authentication first. Validate every strategy with the backtesting framework before going live.
Prerequisite: Working BinanceClient from the API guide and a project layout from the Python setup article.
When to Move Beyond Grid Trading
Grid bots excel in sideways, volatile markets. They struggle when:
- Price trends strongly in one direction (inventory piles up on the losing side)
- Volatility collapses (few grid levels fill, capital sits idle)
- You want directional exposure instead of market-neutral income
The strategies below address these gaps. Each shares the same execution layer — only the signal logic changes.
| Strategy | Best Market | Complexity | Risk Profile |
|---|---|---|---|
| Grid | Sideways / ranging | Low | Market neutral |
| DCA | Long-term accumulation | Very low | Directional (long) |
| Mean Reversion | Overextended moves | Medium | Short-term counter-trend |
| Trend Following | Sustained trends | Medium | Directional momentum |
Strategy 1: Dollar-Cost Averaging (DCA)
DCA buys a fixed USDT amount at regular intervals regardless of price. It removes timing decisions and smooths entry over time. Simple, robust, and ideal as a second bot alongside a grid.
Create src/strategies/dca.py:
import time
import logging
from datetime import datetime, timedelta
class DCAStrategy:
"""Buy fixed USDT amount every N hours."""
def __init__(self, client, symbol='BTCUSDT', amount_usdt=50.0, interval_hours=24):
self.client = client
self.symbol = symbol
self.amount_usdt = amount_usdt
self.interval = timedelta(hours=interval_hours)
self.last_buy: datetime | None = None
self.total_invested = 0.0
self.total_btc = 0.0
self.logger = logging.getLogger(__name__)
def should_buy(self) -> bool:
if self.last_buy is None:
return True
return datetime.utcnow() - self.last_buy >= self.interval
def execute(self) -> dict:
if not self.should_buy():
return {'success': True, 'action': 'skip', 'reason': 'interval not reached'}
ticker = self.client.get_ticker(self.symbol)
if not ticker['success']:
return {'success': False, 'error': ticker['error']}
price = ticker['price']
qty = self.amount_usdt / price
result = self.client.place_market_order(
symbol=self.symbol,
side='buy',
amount=qty,
)
if result['success']:
self.last_buy = datetime.utcnow()
self.total_invested += self.amount_usdt
self.total_btc += qty
avg_price = self.total_invested / self.total_btc if self.total_btc else 0
self.logger.info(
f"DCA buy: {qty:.6f} BTC @ ${price:,.2f} | "
f"Avg entry: ${avg_price:,.2f} | Total: ${self.total_invested:,.2f}"
)
return {'success': True, 'action': 'buy', 'price': price, 'qty': qty}
return result
def get_status(self) -> dict:
return {
'total_invested': self.total_invested,
'total_btc': self.total_btc,
'avg_entry': self.total_invested / self.total_btc if self.total_btc else 0,
'last_buy': self.last_buy.isoformat() if self.last_buy else None,
}Running DCA Alongside Grid
Run DCA on a separate pair or schedule (e.g. grid on BTCUSDT, DCA on ETHUSDT weekly). Never share position limits between strategies without explicit allocation in your risk management layer.
Strategy 2: Mean Reversion (Bollinger Bands)
Mean reversion bets that price returns to its average after an extreme move. Bollinger Bands provide a simple signal: buy when price touches the lower band, sell at the middle band (or upper band for full reversion).
Create src/strategies/mean_reversion.py:
import numpy as np
from typing import List, Optional
class MeanReversionStrategy:
"""Bollinger Band mean reversion on 1h candles."""
def __init__(self, client, symbol='BTCUSDT', window=20, num_std=2.0, qty=0.001):
self.client = client
self.symbol = symbol
self.window = window
self.num_std = num_std
self.qty = qty
self.position = 0.0 # BTC held
def fetch_closes(self, limit: int = 50) -> List[float]:
klines = self.client.client.fetch_ohlcv(self.symbol, '1h', limit=limit)
return [k[4] for k in klines]
def compute_bands(self, closes: List[float]) -> dict:
arr = np.array(closes[-self.window:])
sma = arr.mean()
std = arr.std()
return {
'sma': sma,
'upper': sma + self.num_std * std,
'lower': sma - self.num_std * std,
'current': closes[-1],
}
def signal(self) -> Optional[str]:
closes = self.fetch_closes()
if len(closes) < self.window:
return None
bands = self.compute_bands(closes)
price = bands['current']
if price <= bands['lower'] and self.position == 0:
return 'buy'
if price >= bands['sma'] and self.position > 0:
return 'sell'
return None
def execute(self) -> dict:
action = self.signal()
if action is None:
return {'success': True, 'action': 'hold'}
if action == 'buy':
result = self.client.place_market_order(self.symbol, 'buy', self.qty)
if result['success']:
self.position += self.qty
return result
if action == 'sell':
result = self.client.place_market_order(self.symbol, 'sell', self.qty)
if result['success']:
self.position -= self.qty
return result
return {'success': False, 'error': 'unknown action'}Parameter Notes
- window=20, num_std=2.0 — standard starting point on 1h BTC
- Tighter bands (num_std=1.5) → more trades, more false signals
- Wider bands (num_std=2.5) → fewer but higher-conviction entries
Strategy 3: Trend Following (Moving Average Crossover)
Trend following enters when short-term momentum confirms direction. A classic approach: buy when the 20-period SMA crosses above the 50-period SMA, sell on the reverse cross.
Create src/strategies/trend_following.py:
from typing import List, Optional
class TrendFollowingStrategy:
"""SMA crossover trend strategy on 4h candles."""
def __init__(self, client, symbol='BTCUSDT', fast=20, slow=50, qty=0.001):
self.client = client
self.symbol = symbol
self.fast = fast
self.slow = slow
self.qty = qty
self.in_position = False
self.prev_fast_above = None
def fetch_closes(self, limit: int = 60) -> List[float]:
klines = self.client.client.fetch_ohlcv(self.symbol, '4h', limit=limit)
return [k[4] for k in klines]
@staticmethod
def sma(values: List[float], period: int) -> float:
return sum(values[-period:]) / period
def signal(self) -> Optional[str]:
closes = self.fetch_closes()
if len(closes) < self.slow:
return None
fast_sma = self.sma(closes, self.fast)
slow_sma = self.sma(closes, self.slow)
fast_above = fast_sma > slow_sma
action = None
if self.prev_fast_above is not None:
if fast_above and not self.prev_fast_above and not self.in_position:
action = 'buy'
elif not fast_above and self.prev_fast_above and self.in_position:
action = 'sell'
self.prev_fast_above = fast_above
return action
def execute(self) -> dict:
action = self.signal()
if action is None:
return {'success': True, 'action': 'hold'}
result = self.client.place_market_order(self.symbol, action, self.qty)
if result['success']:
self.in_position = action == 'buy'
return resultUnified Strategy Runner
Switch strategies via config without rewriting the execution loop:
STRATEGIES = {
'grid': GridTradingStrategy,
'dca': DCAStrategy,
'mean_reversion': MeanReversionStrategy,
'trend': TrendFollowingStrategy,
}
def run_bot(strategy_name: str, client, config):
strategy = STRATEGIES[strategy_name](client, **config)
while True:
result = strategy.execute()
if not result.get('success'):
logger.error(f"Strategy error: {result.get('error')}")
time.sleep(60)Choosing the Right Strategy
- Start with grid — market neutral, teaches order management (grid guide)
- Add DCA — passive accumulation with zero signal complexity
- Test mean reversion — when you see repeated overextensions in backtests
- Add trend following — only after backtesting confirms edge in trending periods
Never run multiple directional strategies on the same pair without position tracking. Use the backtesting framework to compare Sharpe ratio and max drawdown before choosing.
Next Steps
This guide extends the core build series. Recommended order:
- Python setup for crypto trading bots
- Binance API configuration and authentication
- Grid trading strategy implementation
- Additional trading strategies (this article)
Continue the series:
- Crypto bot backtesting framework — compare grid vs DCA vs mean reversion on historical data
- Paper trading implementation — validate live signals without capital
- Risk management and logging — multi-strategy position limits
- Crypto trading bot troubleshooting — fix API and order errors
- Series overview: How to build a cryptocurrency trading bot
Backtest every strategy before live deployment. A strategy that looks intuitive often loses money once fees and slippage are included.
Next up: validate your chosen strategy with the backtesting framework, then paper trade for at least 30 days.
Comments