Backtesting
Backtesting allows you to test your trading strategy against historical data to evaluate its performance before risking real money.
What is Backtesting?
Backtesting simulates your strategy's performance using historical market data. It helps you:
- Evaluate strategy profitability
- Identify potential issues
- Optimize parameters
- Build confidence before live trading
Backtesting Limitations
Backtesting results don't guarantee future performance. Real trading involves factors not present in backtesting like slippage, network delays, and market impact.
Basic Backtesting
Prerequisites
- Strategy: A working Freqtrade strategy
- Data: Historical market data for your pairs
- Configuration: Proper bot configuration
Download Historical Data
First, download data for backtesting:
# Download 30 days of data for specific pairs
freqtrade download-data --pairs BTC/USDT ETH/USDT --days 30 --timeframes 5m 1h
# Download data for all pairs in your config
freqtrade download-data --config config.json --days 30
# Download data for specific exchange
freqtrade download-data --exchange binance --pairs BTC/USDT --days 30
Run Basic Backtest
# Basic backtest with default settings
freqtrade backtesting --strategy MyStrategy
# Backtest with specific timerange
freqtrade backtesting --strategy MyStrategy --timerange 20230101-20230601
# Backtest specific pairs
freqtrade backtesting --strategy MyStrategy --pairs BTC/USDT ETH/USDT
Backtest Parameters
Time Range Options
# Specific date range
--timerange 20230101-20230601
# Last N days
--timerange 20230501-
# From specific date to now
--timerange -20230601
# Relative timeframes
--timerange 20230101-20230201
Data Options
# Use specific timeframe
--timeframe 5m
# Enable position stacking (multiple entries per pair)
--enable-position-stacking
# Set starting balance
--dry-run-wallet 1000
# Custom fee
--fee 0.001
Output Options
# Export trades to file
--export trades
# Export signals
--export signals
# Detailed breakdown per pair
--breakdown day week month
# Cache data for faster subsequent runs
--cache none
Advanced Backtesting
Strategy Comparison
Compare multiple strategies:
# Compare two strategies
freqtrade backtesting --strategy-list Strategy1 Strategy2 --export trades
# Compare with different parameters
freqtrade backtesting --strategy MyStrategy --timerange 20230101-20230301
freqtrade backtesting --strategy MyStrategy --timerange 20230301-20230601
Multi-Timeframe Analysis
# Backtest on multiple timeframes
freqtrade backtesting --strategy MyStrategy --timeframes 5m 15m 1h
# Use informative timeframes
freqtrade backtesting --strategy MyStrategy --timeframe 5m
Custom Configuration
Create a backtesting-specific config:
{
"strategy": "MyStrategy",
"max_open_trades": 5,
"stake_amount": 100,
"dry_run_wallet": 10000,
"timeframe": "5m",
"exchange": {
"name": "binance",
"pair_whitelist": [
"BTC/USDT",
"ETH/USDT",
"ADA/USDT"
]
},
"backtesting": {
"enable_protections": true,
"trade_limit": 1000
}
}
Understanding Results
Key Metrics
Performance Metrics:
- Total Return: Overall profit/loss percentage
- Absolute Return: Total profit/loss in stake currency
- Total Trades: Number of completed trades
- Win Rate: Percentage of profitable trades
Risk Metrics:
- Maximum Drawdown: Largest peak-to-trough decline
- Sharpe Ratio: Risk-adjusted return measure
- Sortino Ratio: Downside risk-adjusted return
- Calmar Ratio: Return vs. maximum drawdown
Trade Metrics:
- Average Trade Duration: Time per trade
- Best/Worst Trade: Highest profit/loss trades
- Profit Factor: Ratio of gross profit to gross loss
Sample Output
=============== SUMMARY METRICS ===============
| Metric | Value |
|-----------------------------|----------------|
| Backtesting from | 2023-01-01 |
| Backtesting to | 2023-06-01 |
| Max open trades | 3 |
| Total/Daily Avg Trades | 150 / 1.0 |
| Starting balance | 1000 USDT |
| Final balance | 1250 USDT |
| Absolute return | 250 USDT |
| Total return % | 25.0% |
| Avg. stake amount | 100 USDT |
| Total trade volume | 15000 USDT |
| Best Pair | BTC/USDT 15.2% |
| Worst Pair | ADA/USDT -2.1% |
| Best trade | 8.5% |
| Worst trade | -5.2% |
| Best day | 45 USDT |
| Worst day | -25 USDT |
| Days win/draw/lose | 89 / 12 / 50 |
| Avg. Duration Winners | 4:20:00 |
| Avg. Duration Loser | 2:15:00 |
| Max Drawdown | 8.5% |
| Drawdown Start | 2023-03-15 |
| Drawdown End | 2023-03-28 |
| Market change | 12.3% |
===============================================
Backtesting Best Practices
Data Quality
- Sufficient History: Use at least 3-6 months of data
- Multiple Market Conditions: Include bull, bear, and sideways markets
- Recent Data: Ensure data reflects current market structure
- Complete Data: Avoid gaps in historical data
Strategy Validation
- Out-of-Sample Testing: Reserve recent data for final validation
- Walk-Forward Analysis: Test on rolling time windows
- Cross-Validation: Test on different time periods
- Robustness Testing: Vary parameters slightly
Avoiding Overfitting
- Simple Strategies: Start with basic logic
- Limited Parameters: Avoid too many optimizable variables
- Economic Logic: Ensure strategy makes intuitive sense
- Multiple Timeframes: Test on different periods
Common Backtesting Mistakes
Look-Ahead Bias
# WRONG: Using future data
dataframe['signal'] = dataframe['close'].shift(-1) > dataframe['close']
# CORRECT: Using only past data
dataframe['signal'] = dataframe['close'] > dataframe['close'].shift(1)
Survivorship Bias
# Include delisted pairs in historical analysis
# Use comprehensive pair lists from the testing period
Unrealistic Assumptions
- Perfect Fills: All orders fill at exact prices
- No Slippage: No price impact from orders
- Instant Execution: No network delays
- Perfect Timing: Signals act on candle close
Backtesting Strategies
Parameter Optimization
Use hyperopt for systematic optimization:
# Optimize strategy parameters
freqtrade hyperopt --strategy MyStrategy --hyperopt-loss SharpeHyperOptLoss --epochs 100
# Optimize specific spaces
freqtrade hyperopt --strategy MyStrategy --spaces buy sell roi stoploss --epochs 50
Monte Carlo Analysis
Test strategy robustness:
# In your strategy
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Add noise to test robustness
if self.config.get('backtest_noise', False):
noise = np.random.normal(0, 0.001, len(dataframe))
dataframe['close'] = dataframe['close'] * (1 + noise)
return dataframe
Sensitivity Analysis
Test parameter sensitivity:
# Test different stop losses
freqtrade backtesting --strategy MyStrategy --stoploss -0.05
freqtrade backtesting --strategy MyStrategy --stoploss -0.10
freqtrade backtesting --strategy MyStrategy --stoploss -0.15
Advanced Analysis
Trade Analysis
Analyze individual trades:
# Load backtest results
from freqtrade.data.btanalysis import load_backtest_data
results = load_backtest_data('user_data/backtest_results/')
trades = results['trades']
# Analyze trade duration
import pandas as pd
trades['duration_hours'] = (trades['close_date'] - trades['open_date']).dt.total_seconds() / 3600
# Analyze by pair
pair_performance = trades.groupby('pair')['profit_ratio'].agg(['count', 'mean', 'std'])
Drawdown Analysis
# Calculate rolling drawdown
def calculate_drawdown(returns):
cumulative = (1 + returns).cumprod()
running_max = cumulative.expanding().max()
drawdown = (cumulative - running_max) / running_max
return drawdown
# Apply to backtest results
trades['cumulative_profit'] = trades['profit_abs'].cumsum()
drawdown = calculate_drawdown(trades['profit_ratio'])
max_drawdown = drawdown.min()
Performance Attribution
# Analyze performance by time period
trades['month'] = trades['open_date'].dt.to_period('M')
monthly_performance = trades.groupby('month')['profit_abs'].sum()
# Analyze performance by market conditions
# (requires additional market data)
Validation Techniques
Walk-Forward Analysis
# Test on rolling windows
freqtrade backtesting --strategy MyStrategy --timerange 20230101-20230201
freqtrade backtesting --strategy MyStrategy --timerange 20230201-20230301
freqtrade backtesting --strategy MyStrategy --timerange 20230301-20230401
Cross-Validation
# Split data into folds
import numpy as np
from sklearn.model_selection import TimeSeriesSplit
# Use time series cross-validation
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, test_idx in tscv.split(data):
# Run backtest on each fold
pass
Bootstrap Analysis
# Bootstrap trade results
import numpy as np
def bootstrap_trades(trades, n_bootstrap=1000):
results = []
for _ in range(n_bootstrap):
sample = trades.sample(len(trades), replace=True)
total_return = sample['profit_ratio'].sum()
results.append(total_return)
return np.array(results)
# Calculate confidence intervals
bootstrap_results = bootstrap_trades(trades)
confidence_interval = np.percentile(bootstrap_results, [2.5, 97.5])
Next Steps
After backtesting your strategy:
- Hyperopt - Optimize parameters systematically
- Paper Trading - Test with live data
- Strategy Analysis - Deep dive into performance
- Live Trading - Deploy with real money
Resources
- Backtesting FAQ - Common questions and answers
- Strategy Examples - Community strategies
- Analysis Tools - Jupyter notebook examples
- Performance Metrics - Detailed metric explanations