🎸
🚀 Beta Running
PYNGUP: Rebellion against toxic productivity
Beta limited to 100 spots. Tasks become social commitments instead of lonely to-dos.
Learn how to implement a profitable grid trading strategy for cryptocurrency trading bots. This comprehensive guide covers the mathematical foundation, Python implementation, order management, and optimization techniques for automated Bitcoin trading.
Grid trading is a systematic approach that places buy and sell orders at predetermined intervals above and below a base price, creating a "grid" of orders. This strategy profits from market volatility by capturing small price movements in both directions.
Imagine Bitcoin trading at $65,000. A grid trading bot would:
The key to profitable grid trading is optimal spacing between orders:
# Basic grid spacing formula
grid_spacing = current_price * spacing_percentage / 100
# Example: 0.5% spacing at $65,000
grid_spacing = 65000 * 0.5 / 100 = $325
# Buy levels: $64,675, $64,350, $64,025...
# Sell levels: $65,325, $65,650, $65,975...
Each completed grid level generates predictable profit:
# Profit calculation
buy_price = 64675
sell_price = 65325
quantity = 0.001 BTC
trading_fee = 0.001 # 0.1% per trade
gross_profit = (sell_price - buy_price) * quantity
total_fees = (buy_price + sell_price) * quantity * trading_fee
net_profit = gross_profit - total_fees
# Example result: $0.52 profit per 0.001 BTC cycle
| Market Condition | Grid Spacing | Grid Levels | Capital Allocation |
|---|---|---|---|
| High Volatility | 0.3% - 0.5% | 15-20 levels | 5% per level |
| Medium Volatility | 0.5% - 1.0% | 10-15 levels | 7% per level |
| Low Volatility | 0.2% - 0.3% | 20-30 levels | 3% per level |
Create src/strategies/grid_trading.py:
import time
import logging
import math
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
from src.exchanges.binance_client import BinanceClient
from src.config.binance_config import BinanceConfig
@dataclass
class GridLevel:
"""Represents a single grid level"""
price: float
quantity: float
side: str # 'buy' or 'sell'
order_id: Optional[str] = None
status: str = 'pending' # pending, filled, cancelled
filled_time: Optional[float] = None
@dataclass
class GridConfig:
"""Grid trading configuration"""
symbol: str = 'BTCUSDT'
grid_spacing: float = 0.005 # 0.5%
num_grids_up: int = 10
num_grids_down: int = 10
base_order_size: float = 0.001 # BTC
max_position_size: float = 0.1 # Maximum total position
stop_loss_percentage: float = 0.05 # 5% stop loss
take_profit_percentage: float = 0.02 # 2% take profit overall
class GridTradingStrategy:
"""
Advanced Grid Trading Strategy Implementation
Features:
- Dynamic grid adjustment
- Risk management
- Profit tracking
- Order management
"""
def __init__(self, client: BinanceClient, config: GridConfig):
self.client = client
self.config = config
self.logger = logging.getLogger(__name__)
# Strategy state
self.grid_levels: List[GridLevel] = []
self.active_orders: Dict[str, GridLevel] = {}
self.filled_orders: List[GridLevel] = []
# Performance tracking
self.total_profit = 0.0
self.total_trades = 0
self.start_time = time.time()
self.start_balance = 0.0
# Grid center price
self.center_price = 0.0
self.last_update_time = 0.0
def initialize_strategy(self) -> bool:
"""Initialize the grid trading strategy"""
try:
# Get current price
ticker = self.client.get_ticker(self.config.symbol)
if not ticker['success']:
self.logger.error(f"Failed to get ticker: {ticker['error']}")
return False
self.center_price = ticker['price']
self.logger.info(f"Initialized grid around ${self.center_price:,.2f}")
# Get starting balance
account = self.client.get_account_info()
if account['success']:
usdt_balance = account['balances'].get('USDT', {}).get('free', 0)
self.start_balance = usdt_balance
self.logger.info(f"Starting USDT balance: ${usdt_balance:,.2f}")
# Generate initial grid
self._generate_grid_levels()
# Place initial orders
return self._place_initial_orders()
except Exception as e:
self.logger.error(f"Strategy initialization failed: {e}")
return False
def _generate_grid_levels(self):
"""Generate grid levels around center price"""
self.grid_levels.clear()
# Generate buy levels (below center price)
for i in range(1, self.config.num_grids_down + 1):
price = self.center_price * (1 - self.config.grid_spacing * i)
grid_level = GridLevel(
price=price,
quantity=self.config.base_order_size,
side='buy'
)
self.grid_levels.append(grid_level)
# Generate sell levels (above center price)
for i in range(1, self.config.num_grids_up + 1):
price = self.center_price * (1 + self.config.grid_spacing * i)
grid_level = GridLevel(
price=price,
quantity=self.config.base_order_size,
side='sell'
)
self.grid_levels.append(grid_level)
self.logger.info(f"Generated {len(self.grid_levels)} grid levels")
# Log grid levels for debugging
buy_levels = [g for g in self.grid_levels if g.side == 'buy']
sell_levels = [g for g in self.grid_levels if g.side == 'sell']
self.logger.info(f"Buy levels: {len(buy_levels)} orders from "
f"${min(g.price for g in buy_levels):,.2f} to "
f"${max(g.price for g in buy_levels):,.2f}")
self.logger.info(f"Sell levels: {len(sell_levels)} orders from "
f"${min(g.price for g in sell_levels):,.2f} to "
f"${max(g.price for g in sell_levels):,.2f}")
def _place_initial_orders(self) -> bool:
"""Place all initial grid orders"""
success_count = 0
for grid_level in self.grid_levels:
try:
# Place limit order
result = self.client.place_limit_order(
symbol=self.config.symbol,
side=grid_level.side,
amount=grid_level.quantity,
price=grid_level.price
)
if result['success']:
grid_level.order_id = result['order_id']
grid_level.status = 'pending'
self.active_orders[result['order_id']] = grid_level
success_count += 1
self.logger.debug(f"Placed {grid_level.side} order: "
f"{grid_level.quantity} at ${grid_level.price:,.2f}")
else:
self.logger.error(f"Failed to place order: {result['error']}")
grid_level.status = 'failed'
# Rate limiting
time.sleep(0.1)
except Exception as e:
self.logger.error(f"Error placing order: {e}")
grid_level.status = 'failed'
self.logger.info(f"Successfully placed {success_count}/{len(self.grid_levels)} orders")
return success_count > 0
def update_strategy(self) -> Dict:
"""Main strategy update loop"""
try:
# Check for filled orders
filled_orders = self._check_filled_orders()
# Process filled orders
for order in filled_orders:
self._process_filled_order(order)
# Update performance metrics
performance = self._calculate_performance()
# Check risk limits
if self._check_risk_limits():
self.logger.warning("Risk limits exceeded - consider stopping strategy")
self.last_update_time = time.time()
return {
'success': True,
'filled_orders': len(filled_orders),
'active_orders': len(self.active_orders),
'total_profit': self.total_profit,
'performance': performance
}
except Exception as e:
self.logger.error(f"Strategy update failed: {e}")
return {'success': False, 'error': str(e)}
def _check_filled_orders(self) -> List[GridLevel]:
"""Check which orders have been filled"""
filled_orders = []
try:
# Get current open orders
open_orders_result = self.client.get_open_orders(self.config.symbol)
if not open_orders_result['success']:
self.logger.error(f"Failed to get open orders: {open_orders_result['error']}")
return filled_orders
open_order_ids = {order['id'] for order in open_orders_result['orders']}
# Check which of our orders are no longer open (i.e., filled)
for order_id, grid_level in list(self.active_orders.items()):
if order_id not in open_order_ids:
# Order was filled
grid_level.status = 'filled'
grid_level.filled_time = time.time()
filled_orders.append(grid_level)
# Remove from active orders
del self.active_orders[order_id]
# Add to filled orders
self.filled_orders.append(grid_level)
self.logger.info(f"Order filled: {grid_level.side} "
f"{grid_level.quantity} at ${grid_level.price:,.2f}")
return filled_orders
except Exception as e:
self.logger.error(f"Error checking filled orders: {e}")
return filled_orders
def _process_filled_order(self, filled_order: GridLevel):
"""Process a filled order and place corresponding opposite order"""
try:
# Calculate new order parameters
if filled_order.side == 'buy':
# Buy order filled, place sell order one level up
new_price = filled_order.price * (1 + self.config.grid_spacing)
new_side = 'sell'
else:
# Sell order filled, place buy order one level down
new_price = filled_order.price * (1 - self.config.grid_spacing)
new_side = 'buy'
# Create new grid level
new_grid_level = GridLevel(
price=new_price,
quantity=filled_order.quantity,
side=new_side
)
# Place new order
result = self.client.place_limit_order(
symbol=self.config.symbol,
side=new_side,
amount=filled_order.quantity,
price=new_price
)
if result['success']:
new_grid_level.order_id = result['order_id']
new_grid_level.status = 'pending'
self.active_orders[result['order_id']] = new_grid_level
# Calculate profit for this trade cycle
if filled_order.side == 'buy':
# We bought and now placed sell order
expected_profit = (new_price - filled_order.price) * filled_order.quantity
# Subtract trading fees (0.1% per trade)
trading_fees = (filled_order.price + new_price) * filled_order.quantity * 0.001
net_profit = expected_profit - trading_fees
self.total_profit += net_profit
self.total_trades += 1
self.logger.info(f"Trade cycle: Buy ${filled_order.price:,.2f} → "
f"Sell ${new_price:,.2f} = ${net_profit:.2f} profit")
self.logger.info(f"Placed new {new_side} order: "
f"{new_grid_level.quantity} at ${new_price:,.2f}")
else:
self.logger.error(f"Failed to place new order: {result['error']}")
except Exception as e:
self.logger.error(f"Error processing filled order: {e}")
def _calculate_performance(self) -> Dict:
"""Calculate strategy performance metrics"""
try:
current_time = time.time()
runtime_hours = (current_time - self.start_time) / 3600
# Get current account balance
account = self.client.get_account_info()
current_balance = 0.0
if account['success']:
current_balance = account['balances'].get('USDT', {}).get('free', 0)
# Calculate returns
balance_change = current_balance - self.start_balance
profit_percentage = (balance_change / self.start_balance * 100) if self.start_balance > 0 else 0
# Calculate rates
trades_per_hour = self.total_trades / runtime_hours if runtime_hours > 0 else 0
profit_per_hour = self.total_profit / runtime_hours if runtime_hours > 0 else 0
return {
'runtime_hours': runtime_hours,
'total_trades': self.total_trades,
'total_profit': self.total_profit,
'balance_change': balance_change,
'profit_percentage': profit_percentage,
'trades_per_hour': trades_per_hour,
'profit_per_hour': profit_per_hour,
'active_orders': len(self.active_orders),
'avg_profit_per_trade': self.total_profit / self.total_trades if self.total_trades > 0 else 0
}
except Exception as e:
self.logger.error(f"Error calculating performance: {e}")
return {}
def _check_risk_limits(self) -> bool:
"""Check if risk limits are exceeded"""
try:
# Check total position size
total_position = sum(g.quantity for g in self.active_orders.values() if g.side == 'buy')
if total_position > self.config.max_position_size:
self.logger.warning(f"Position size {total_position} exceeds limit {self.config.max_position_size}")
return True
# Check drawdown
account = self.client.get_account_info()
if account['success']:
current_balance = account['balances'].get('USDT', {}).get('free', 0)
drawdown = (self.start_balance - current_balance) / self.start_balance
if drawdown > self.config.stop_loss_percentage:
self.logger.warning(f"Drawdown {drawdown:.2%} exceeds stop loss {self.config.stop_loss_percentage:.2%}")
return True
return False
except Exception as e:
self.logger.error(f"Error checking risk limits: {e}")
return False
def stop_strategy(self) -> bool:
"""Stop the strategy and cancel all open orders"""
try:
cancelled_count = 0
for order_id, grid_level in list(self.active_orders.items()):
result = self.client.cancel_order(order_id, self.config.symbol)
if result['success']:
cancelled_count += 1
grid_level.status = 'cancelled'
del self.active_orders[order_id]
self.logger.info(f"Cancelled order: {order_id}")
else:
self.logger.error(f"Failed to cancel order {order_id}: {result['error']}")
self.logger.info(f"Strategy stopped. Cancelled {cancelled_count} orders.")
# Final performance report
final_performance = self._calculate_performance()
self.logger.info(f"Final performance: {final_performance}")
return True
except Exception as e:
self.logger.error(f"Error stopping strategy: {e}")
return False
def get_status(self) -> Dict:
"""Get current strategy status"""
return {
'center_price': self.center_price,
'grid_levels': len(self.grid_levels),
'active_orders': len(self.active_orders),
'filled_orders': len(self.filled_orders),
'total_profit': self.total_profit,
'total_trades': self.total_trades,
'last_update': self.last_update_time,
'performance': self._calculate_performance()
}
Create src/grid_trading_bot.py:
#!/usr/bin/env python3
"""
Grid Trading Bot - Main execution script
"""
import time
import signal
import sys
import logging
from datetime import datetime
from src.exchanges.binance_client import BinanceClient
from src.strategies.grid_trading import GridTradingStrategy, GridConfig
from src.config.binance_config import BinanceConfig
class GridTradingBot:
"""Main grid trading bot controller"""
def __init__(self):
self.setup_logging()
self.logger = logging.getLogger(__name__)
# Initialize components
self.config = BinanceConfig()
self.client = BinanceClient(self.config)
# Grid strategy configuration
self.grid_config = GridConfig(
symbol='BTCUSDT',
grid_spacing=0.005, # 0.5%
num_grids_up=10,
num_grids_down=10,
base_order_size=0.001, # 0.001 BTC per order
max_position_size=0.1, # Maximum 0.1 BTC position
stop_loss_percentage=0.05, # 5% stop loss
take_profit_percentage=0.02 # 2% take profit
)
self.strategy = GridTradingStrategy(self.client, self.grid_config)
self.running = False
# Setup signal handlers for graceful shutdown
signal.signal(signal.SIGINT, self.signal_handler)
signal.signal(signal.SIGTERM, self.signal_handler)
def setup_logging(self):
"""Configure logging for the bot"""
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('data/logs/grid_trading_bot.log'),
logging.StreamHandler(sys.stdout)
]
)
def signal_handler(self, signum, frame):
"""Handle shutdown signals gracefully"""
self.logger.info(f"Received signal {signum}. Shutting down gracefully...")
self.running = False
def pre_flight_checks(self) -> bool:
"""Perform pre-flight checks before starting"""
self.logger.info("Performing pre-flight checks...")
# Test API connection
if not self.client.test_connection():
self.logger.error("❌ API connection failed")
return False
# Check account balance
account = self.client.get_account_info()
if not account['success']:
self.logger.error(f"❌ Cannot access account: {account['error']}")
return False
usdt_balance = account['balances'].get('USDT', {}).get('free', 0)
btc_balance = account['balances'].get('BTC', {}).get('free', 0)
self.logger.info(f"💰 Account balances:")
self.logger.info(f" USDT: ${usdt_balance:,.2f}")
self.logger.info(f" BTC: {btc_balance:.6f}")
# Calculate required capital
ticker = self.client.get_ticker(self.grid_config.symbol)
if not ticker['success']:
self.logger.error("❌ Cannot get current price")
return False
current_price = ticker['price']
required_usdt = (current_price * self.grid_config.base_order_size *
self.grid_config.num_grids_down)
required_btc = (self.grid_config.base_order_size *
self.grid_config.num_grids_up)
self.logger.info(f"📊 Capital requirements:")
self.logger.info(f" Required USDT: ${required_usdt:,.2f}")
self.logger.info(f" Required BTC: {required_btc:.6f}")
# Check sufficient balance
if usdt_balance < required_usdt:
self.logger.error(f"❌ Insufficient USDT balance")
return False
if btc_balance < required_btc:
self.logger.warning(f"⚠️ Low BTC balance - some sell orders may fail")
# Check if testnet
if self.config.TESTNET:
self.logger.info("🧪 Running on TESTNET - safe for testing")
else:
self.logger.warning("🔴 LIVE TRADING MODE - real money at risk!")
response = input("Type 'CONFIRM' to proceed with live trading: ")
if response != 'CONFIRM':
self.logger.info("Live trading cancelled by user")
return False
self.logger.info("✅ All pre-flight checks passed")
return True
def run(self):
"""Main bot execution loop"""
try:
self.logger.info("🚀 Starting Grid Trading Bot")
# Pre-flight checks
if not self.pre_flight_checks():
self.logger.error("Pre-flight checks failed. Exiting.")
return False
# Initialize strategy
if not self.strategy.initialize_strategy():
self.logger.error("Strategy initialization failed. Exiting.")
return False
self.running = True
self.logger.info("🎯 Grid trading strategy is now active")
# Main execution loop
loop_count = 0
last_status_time = time.time()
while self.running:
try:
# Update strategy
update_result = self.strategy.update_strategy()
if update_result['success']:
loop_count += 1
# Log filled orders
if update_result['filled_orders'] > 0:
self.logger.info(f"📈 {update_result['filled_orders']} orders filled")
# Periodic status report (every 5 minutes)
current_time = time.time()
if current_time - last_status_time > 300: # 5 minutes
self._log_status_report()
last_status_time = current_time
else:
self.logger.error(f"Strategy update failed: {update_result.get('error')}")
# Sleep before next iteration
time.sleep(30) # Update every 30 seconds
except KeyboardInterrupt:
self.logger.info("Keyboard interrupt received")
break
except Exception as e:
self.logger.error(f"Error in main loop: {e}")
time.sleep(60) # Wait longer on errors
# Graceful shutdown
self.logger.info("🛑 Shutting down grid trading bot")
self.strategy.stop_strategy()
return True
except Exception as e:
self.logger.error(f"Critical error in bot execution: {e}")
return False
def _log_status_report(self):
"""Log periodic status report"""
try:
status = self.strategy.get_status()
performance = status['performance']
self.logger.info("📊 === STATUS REPORT ===")
self.logger.info(f"Runtime: {performance.get('runtime_hours', 0):.1f} hours")
self.logger.info(f"Total trades: {performance.get('total_trades', 0)}")
self.logger.info(f"Total profit: ${performance.get('total_profit', 0):.2f}")
self.logger.info(f"Profit percentage: {performance.get('profit_percentage', 0):.2f}%")
self.logger.info(f"Active orders: {status['active_orders']}")
self.logger.info(f"Trades per hour: {performance.get('trades_per_hour', 0):.1f}")
self.logger.info(f"Profit per hour: ${performance.get('profit_per_hour', 0):.2f}")
# Get current price for reference
ticker = self.client.get_ticker(self.grid_config.symbol)
if ticker['success']:
self.logger.info(f"Current BTC price: ${ticker['price']:,.2f}")
self.logger.info("========================")
except Exception as e:
self.logger.error(f"Error generating status report: {e}")
def main():
"""Main entry point"""
bot = GridTradingBot()
success = bot.run()
if success:
print("✅ Grid trading bot completed successfully")
sys.exit(0)
else:
print("❌ Grid trading bot failed")
sys.exit(1)
if __name__ == "__main__":
main()
Implement adaptive grid spacing based on market volatility:
import numpy as np
from typing import List
class VolatilityBasedGridding:
"""Adjust grid spacing based on market volatility"""
def __init__(self, base_spacing: float = 0.005):
self.base_spacing = base_spacing
self.price_history: List[float] = []
def calculate_volatility(self, prices: List[float], window: int = 24) -> float:
"""Calculate rolling volatility"""
if len(prices) < window:
return self.base_spacing
recent_prices = prices[-window:]
returns = np.diff(np.log(recent_prices))
volatility = np.std(returns) * np.sqrt(24) # 24 hours
return volatility
def get_optimal_spacing(self, current_price: float) -> float:
"""Get optimal grid spacing based on current volatility"""
self.price_history.append(current_price)
# Keep only last 100 price points
if len(self.price_history) > 100:
self.price_history = self.price_history[-100:]
volatility = self.calculate_volatility(self.price_history)
# Adjust spacing: higher volatility = wider spacing
if volatility > 0.03: # High volatility
return self.base_spacing * 1.5
elif volatility < 0.01: # Low volatility
return self.base_spacing * 0.7
else:
return self.base_spacing
Enhanced strategy with trend detection:
class TrendAwareGrid:
"""Grid trading with trend awareness"""
def __init__(self, client: BinanceClient):
self.client = client
def detect_trend(self, symbol: str, timeframes: List[str] = ['1h', '4h', '1d']) -> str:
"""Detect overall market trend"""
trend_signals = []
for timeframe in timeframes:
try:
# Get historical data
klines = self.client.client.fetch_ohlcv(symbol, timeframe, limit=50)
closes = [kline[4] for kline in klines] # Closing prices
# Simple moving averages
sma_20 = np.mean(closes[-20:])
sma_50 = np.mean(closes[-50:])
current_price = closes[-1]
# Trend determination
if current_price > sma_20 > sma_50:
trend_signals.append('bullish')
elif current_price < sma_20 < sma_50:
trend_signals.append('bearish')
else:
trend_signals.append('sideways')
except Exception as e:
print(f"Error analyzing {timeframe}: {e}")
trend_signals.append('sideways')
# Aggregate trend signals
bullish_count = trend_signals.count('bullish')
bearish_count = trend_signals.count('bearish')
if bullish_count > bearish_count:
return 'bullish'
elif bearish_count > bullish_count:
return 'bearish'
else:
return 'sideways'
def adjust_grid_for_trend(self, config: GridConfig, trend: str) -> GridConfig:
"""Adjust grid parameters based on trend"""
if trend == 'bullish':
# More buy orders, fewer sell orders
config.num_grids_down = int(config.num_grids_down * 1.5)
config.num_grids_up = int(config.num_grids_up * 0.7)
elif trend == 'bearish':
# Fewer buy orders, more sell orders
config.num_grids_down = int(config.num_grids_down * 0.7)
config.num_grids_up = int(config.num_grids_up * 1.5)
return config
Create src/backtesting/grid_backtest.py:
import pandas as pd
import numpy as np
from typing import Dict, List, Tuple
from datetime import datetime, timedelta
class GridBacktester:
"""Backtest grid trading strategy on historical data"""
def __init__(self, grid_spacing: float = 0.005, base_order_size: float = 0.001):
self.grid_spacing = grid_spacing
self.base_order_size = base_order_size
self.trading_fee = 0.001 # 0.1% per trade
def run_backtest(self, price_data: pd.DataFrame,
start_balance: float = 1000) -> Dict:
"""Run backtest on historical price data"""
# Initialize backtest state
balance = start_balance
btc_holdings = 0.0
trades = []
grid_levels = []
# Performance tracking
max_balance = start_balance
max_drawdown = 0.0
for index, row in price_data.iterrows():
current_price = row['close']
timestamp = row['timestamp'] if 'timestamp' in row else index
# First iteration - set up initial grid
if not grid_levels:
grid_levels = self._create_initial_grid(current_price)
continue
# Check for filled orders
filled_orders = []
remaining_levels = []
for level in grid_levels:
if level['side'] == 'buy' and current_price <= level['price']:
# Buy order filled
if balance >= level['price'] * level['quantity']:
cost = level['price'] * level['quantity']
fee = cost * self.trading_fee
balance -= (cost + fee)
btc_holdings += level['quantity']
trades.append({
'timestamp': timestamp,
'side': 'buy',
'price': level['price'],
'quantity': level['quantity'],
'cost': cost,
'fee': fee,
'balance': balance,
'btc_holdings': btc_holdings
})
filled_orders.append(level)
elif level['side'] == 'sell' and current_price >= level['price']:
# Sell order filled
if btc_holdings >= level['quantity']:
revenue = level['price'] * level['quantity']
fee = revenue * self.trading_fee
balance += (revenue - fee)
btc_holdings -= level['quantity']
trades.append({
'timestamp': timestamp,
'side': 'sell',
'price': level['price'],
'quantity': level['quantity'],
'revenue': revenue,
'fee': fee,
'balance': balance,
'btc_holdings': btc_holdings
})
filled_orders.append(level)
else:
# Order not filled, keep in grid
remaining_levels.append(level)
# Update grid levels
grid_levels = remaining_levels
# Place new orders for filled positions
for filled_order in filled_orders:
new_level = self._create_opposite_order(filled_order, current_price)
if new_level:
grid_levels.append(new_level)
# Update performance metrics
total_value = balance + (btc_holdings * current_price)
max_balance = max(max_balance, total_value)
drawdown = (max_balance - total_value) / max_balance
max_drawdown = max(max_drawdown, drawdown)
# Calculate final results
final_price = price_data.iloc[-1]['close']
final_value = balance + (btc_holdings * final_price)
total_return = (final_value - start_balance) / start_balance
total_trades = len(trades)
return {
'start_balance': start_balance,
'final_balance': final_value,
'total_return': total_return,
'total_return_pct': total_return * 100,
'max_drawdown': max_drawdown * 100,
'total_trades': total_trades,
'trades': trades,
'final_btc_holdings': btc_holdings,
'avg_trades_per_day': total_trades / len(price_data) if len(price_data) > 0 else 0
}
def _create_initial_grid(self, center_price: float) -> List[Dict]:
"""Create initial grid around center price"""
grid_levels = []
# Create buy levels below center price
for i in range(1, 11): # 10 levels down
price = center_price * (1 - self.grid_spacing * i)
grid_levels.append({
'side': 'buy',
'price': price,
'quantity': self.base_order_size
})
# Create sell levels above center price
for i in range(1, 11): # 10 levels up
price = center_price * (1 + self.grid_spacing * i)
grid_levels.append({
'side': 'sell',
'price': price,
'quantity': self.base_order_size
})
return grid_levels
def _create_opposite_order(self, filled_order: Dict, current_price: float) -> Dict:
"""Create opposite order after one is filled"""
if filled_order['side'] == 'buy':
# Create sell order one level up
new_price = filled_order['price'] * (1 + self.grid_spacing)
return {
'side': 'sell',
'price': new_price,
'quantity': filled_order['quantity']
}
else:
# Create buy order one level down
new_price = filled_order['price'] * (1 - self.grid_spacing)
return {
'side': 'buy',
'price': new_price,
'quantity': filled_order['quantity']
}
# Example usage
def run_grid_backtest_example():
"""Example of running grid strategy backtest"""
# Load historical data (you would get this from Binance API)
# For demo, create sample data
dates = pd.date_range(start='2024-01-01', end='2024-01-31', freq='H')
np.random.seed(42)
# Simulate Bitcoin price movement
initial_price = 45000
returns = np.random.normal(0, 0.02, len(dates)) # 2% hourly volatility
prices = [initial_price]
for ret in returns[1:]:
prices.append(prices[-1] * (1 + ret))
price_data = pd.DataFrame({
'timestamp': dates,
'close': prices
})
# Run backtest
backtester = GridBacktester(grid_spacing=0.005, base_order_size=0.001)
results = backtester.run_backtest(price_data, start_balance=1000)
print("=== GRID TRADING BACKTEST RESULTS ===")
print(f"Start Balance: ${results['start_balance']:,.2f}")
print(f"Final Balance: ${results['final_balance']:,.2f}")
print(f"Total Return: {results['total_return_pct']:.2f}%")
print(f"Max Drawdown: {results['max_drawdown']:.2f}%")
print(f"Total Trades: {results['total_trades']}")
print(f"Avg Trades/Day: {results['avg_trades_per_day']:.1f}")
return results
if __name__ == "__main__":
run_grid_backtest_example()
# In your .env file
BINANCE_TESTNET=true
BINANCE_API_KEY=your_testnet_api_key
BINANCE_SECRET_KEY=your_testnet_secret_key
python src/grid_trading_bot.py
tail -f data/logs/grid_trading_bot.log
# Change .env to live credentials
BINANCE_TESTNET=false
BINANCE_API_KEY=your_live_api_key
BINANCE_SECRET_KEY=your_live_secret_key
Based on historical data, a well-tuned grid trading strategy can achieve:
Test different grid spacings to find optimal parameters:
def optimize_grid_parameters():
"""Find optimal grid spacing through backtesting"""
spacings = [0.002, 0.003, 0.005, 0.007, 0.01] # Different spacings to test
results = []
for spacing in spacings:
backtester = GridBacktester(grid_spacing=spacing)
result = backtester.run_backtest(price_data, start_balance=1000)
result['grid_spacing'] = spacing
results.append(result)
# Find best spacing by return/drawdown ratio
best_spacing = max(results, key=lambda x: x['total_return'] / (x['max_drawdown'] + 0.01))
print(f"Optimal grid spacing: {best_spacing['grid_spacing']:.3f}")
print(f"Return: {best_spacing['total_return_pct']:.2f}%")
print(f"Max Drawdown: {best_spacing['max_drawdown']:.2f}%")
return best_spacing
Problem: Few trades, missed opportunities
Solution: Reduce spacing to 0.3-0.5% for higher volatility pairs
Problem: High fees, constant rebalancing
Solution: Increase spacing to 0.7-1.0% and reduce grid levels
Problem: Orders fail due to insufficient balance
Solution: Reduce order sizes or number of grid levels
Problem: Accumulating losing positions
Solution: Implement trend detection and pause grid in strong trends
Your grid trading strategy is now implemented and ready for testing! In the next article, we'll cover:
Always test thoroughly on testnet before using real money! Grid trading can be highly profitable in the right market conditions, but requires careful parameter tuning and risk management.
The next article covers building a professional backtesting framework to validate your grid trading strategy with historical data before risking real capital.
Nikolai Fischer is the founder of Kommune3 (since 2007) and a leading expert in Drupal development and tech entrepreneurship. With 17+ years of experience, he has led hundreds of projects and achieved #1 on Hacker News. As host of the "Kommit mich" podcast and founder of skillution, he combines technical expertise with entrepreneurial thinking. His articles about Supabase, modern web development, and systematic problem-solving have influenced thousands of developers worldwide.
Comments