How to Interpret Backtest Results
Problem
You've run a backtest and received performance metrics, but you're not sure how to interpret them or whether your strategy is actually profitable.
Prerequisites
- Completed backtest with results
- Basic understanding of trading metrics
- Realistic expectations for strategy performance
Understanding Performance Metrics
1. Total Trades
What it is: The total number of trades executed during the backtest period.
{
"totalTrades": 127,
"winningTrades": 68,
"losingTrades": 59
}
Interpretation:
- Minimum: 30-50 trades for statistical significance
- Ideal: 100+ trades for reliable metrics
- Too few: Results may not be statistically significant
- Too many: May indicate overtrading
Example Analysis:
function analyzeTradeCoun(results) {
if (results.totalTrades < 30) {
return 'Insufficient trades - extend backtest period or adjust strategy'
} else if (results.totalTrades < 100) {
return 'Adequate trades - results are moderately reliable'
} else {
return 'Excellent trade count - results are statistically significant'
}
}
2. Win Rate
What it is: Percentage of winning trades out of total trades.
Formula: (winningTrades / totalTrades) × 100
{
"winRate": 53.5
}
Interpretation by Strategy Type:
- Scalping: 60-70% (high frequency, small profits)
- Day Trading: 50-60% (moderate frequency)
- Swing Trading: 40-50% (lower frequency, larger profits)
- Position Trading: 35-45% (very low frequency, very large profits)
Important: Win rate alone doesn't determine profitability. A 40% win rate can be highly profitable if winners are much larger than losers.
Example:
// Strategy A: 70% win rate
// Average win: ₹100, Average loss: ₹300
// Net: (70 × ₹100) - (30 × ₹300) = -₹2,000 (LOSING)
// Strategy B: 40% win rate
// Average win: ₹500, Average loss: ₹150
// Net: (40 × ₹500) - (60 × ₹150) = ₹11,000 (WINNING)
3. Profit Factor
What it is: Ratio of gross profit to gross loss.
Formula: totalProfit / totalLoss
{
"totalProfit": 45000,
"totalLoss": 25000,
"profitFactor": 1.8
}
Interpretation:
- < 1.0: Losing strategy (losses exceed profits)
- 1.0 - 1.5: Marginally profitable (risky)
- 1.5 - 2.0: Good strategy (acceptable)
- 2.0 - 3.0: Excellent strategy (strong)
- > 3.0: Outstanding (verify for overfitting)
Example Analysis:
function interpretProfitFactor(pf) {
if (pf < 1.0) {
return {
rating: 'Poor',
action: 'Strategy loses money - do not trade',
color: 'red'
}
} else if (pf < 1.5) {
return {
rating: 'Marginal',
action: 'Barely profitable - needs improvement',
color: 'orange'
}
} else if (pf < 2.0) {
return {
rating: 'Good',
action: 'Acceptable for live trading with caution',
color: 'yellow'
}
} else if (pf < 3.0) {
return {
rating: 'Excellent',
action: 'Strong strategy - suitable for live trading',
color: 'green'
}
} else {
return {
rating: 'Outstanding',
action: 'Verify results - may be overfitted',
color: 'blue'
}
}
}
4. Sharpe Ratio
What it is: Risk-adjusted return metric measuring return per unit of risk.
Formula: (Return - RiskFreeRate) / StandardDeviation
{
"sharpeRatio": 1.85
}
Interpretation:
- < 0: Strategy loses money
- 0 - 1.0: Poor risk-adjusted returns
- 1.0 - 2.0: Good risk-adjusted returns
- 2.0 - 3.0: Excellent risk-adjusted returns
- > 3.0: Outstanding (rare, verify for errors)
What it means:
- Sharpe of 1.5 = You earn 1.5 units of return for every unit of risk
- Higher is better - indicates consistent returns with lower volatility
- Accounts for volatility, not just total return
Example:
// Strategy A: 50% return, 40% volatility
// Sharpe = (50 - 5) / 40 = 1.125 (moderate)
// Strategy B: 35% return, 15% volatility
// Sharpe = (35 - 5) / 15 = 2.0 (excellent)
// Strategy B is better despite lower returns!
5. Maximum Drawdown
What it is: Largest peak-to-trough decline in account value.
{
"maxDrawdown": 15.5
}
Interpretation:
- < 10%: Excellent (very low risk)
- 10-20%: Good (acceptable risk)
- 20-30%: Moderate (higher risk, manageable)
- 30-50%: High (significant risk, stressful)
- > 50%: Extreme (very difficult to recover)
Psychological Impact:
- 20% drawdown = Can you handle seeing your account down 20%?
- 30% drawdown = Most traders panic and stop the strategy
- 50% drawdown = Requires 100% return just to break even
Example:
function assessDrawdownRisk(maxDD) {
return {
drawdown: maxDD,
recoveryRequired: (maxDD / (100 - maxDD) * 100).toFixed(1) + '%',
psychologicalDifficulty: maxDD < 20 ? 'Manageable' :
maxDD < 30 ? 'Challenging' :
maxDD < 50 ? 'Very Difficult' : 'Extreme',
recommendation: maxDD < 20 ? 'Acceptable for most traders' :
maxDD < 30 ? 'Only for experienced traders' :
'Reduce position size or improve strategy'
}
}
// Example: 33% drawdown
// Recovery required: 49.3% (need 49.3% gain to break even)
// Difficulty: Very Difficult
6. Average Win vs Average Loss
What it is: Average profit per winning trade vs average loss per losing trade.
{
"averageWin": 850,
"averageLoss": 420
}
Interpretation:
- Ratio > 2.0: Excellent (winners are 2x losers)
- Ratio 1.5-2.0: Good (winners are 1.5-2x losers)
- Ratio 1.0-1.5: Acceptable (winners slightly larger)
- Ratio < 1.0: Poor (losers larger than winners)
Example:
function analyzeWinLossRatio(avgWin, avgLoss) {
const ratio = avgWin / avgLoss
return {
ratio: ratio.toFixed(2),
interpretation: ratio > 2.0 ? 'Excellent - let winners run' :
ratio > 1.5 ? 'Good - solid risk/reward' :
ratio > 1.0 ? 'Acceptable - room for improvement' :
'Poor - losers too large or winners too small',
requiredWinRate: (1 / (1 + ratio) * 100).toFixed(1) + '%'
}
}
// Example: avgWin = 850, avgLoss = 420
// Ratio: 2.02
// Required win rate to break even: 33.1%
// (You only need to win 33% of trades to be profitable!)
7. Total Profit/Loss
What it is: Net profit or loss over the backtest period.
{
"totalProfit": 45000,
"totalLoss": 25000,
"netProfit": 20000,
"returnPercentage": 20.0
}
Interpretation:
- Consider return percentage, not absolute profit
- Compare to buy-and-hold benchmark
- Account for time period (annualize returns)
Annualized Return Calculation:
function annualizeReturn(returnPct, days) {
const years = days / 365
const annualizedReturn = (Math.pow(1 + returnPct / 100, 1 / years) - 1) * 100
return annualizedReturn.toFixed(2)
}
// Example: 20% return over 6 months
// Annualized: 44.0% per year
8. Risk-Reward Ratio
What it is: Average profit per trade relative to average loss.
{
"riskRewardRatio": 2.02
}
Interpretation:
- 1:1: Break even at 50% win rate
- 1:2: Break even at 33% win rate
- 1:3: Break even at 25% win rate
Example:
function calculateBreakEvenWinRate(riskReward) {
return (1 / (1 + riskReward) * 100).toFixed(1) + '%'
}
// 1:2 risk-reward = need 33.3% win rate to break even
// 1:3 risk-reward = need 25.0% win rate to break even
Complete Results Analysis
function analyzeBacktestResults(results) {
const analysis = {
// Trade Count
tradeCount: {
value: results.totalTrades,
status: results.totalTrades >= 100 ? 'excellent' :
results.totalTrades >= 50 ? 'good' :
results.totalTrades >= 30 ? 'adequate' : 'insufficient'
},
// Win Rate
winRate: {
value: results.winRate,
status: results.winRate >= 55 ? 'excellent' :
results.winRate >= 45 ? 'good' :
results.winRate >= 35 ? 'acceptable' : 'poor'
},
// Profit Factor
profitFactor: {
value: results.profitFactor,
status: results.profitFactor >= 2.0 ? 'excellent' :
results.profitFactor >= 1.5 ? 'good' :
results.profitFactor >= 1.2 ? 'acceptable' : 'poor'
},
// Sharpe Ratio
sharpeRatio: {
value: results.sharpeRatio,
status: results.sharpeRatio >= 2.0 ? 'excellent' :
results.sharpeRatio >= 1.0 ? 'good' :
results.sharpeRatio >= 0.5 ? 'acceptable' : 'poor'
},
// Max Drawdown
maxDrawdown: {
value: results.maxDrawdown,
status: results.maxDrawdown <= 15 ? 'excellent' :
results.maxDrawdown <= 25 ? 'good' :
results.maxDrawdown <= 35 ? 'acceptable' : 'poor'
},
// Overall Rating
overallRating: function() {
const scores = Object.values(this).filter(v => v.status)
const excellentCount = scores.filter(s => s.status === 'excellent').length
const goodCount = scores.filter(s => s.status === 'good').length
const poorCount = scores.filter(s => s.status === 'poor').length
if (poorCount > 2) return 'Not Ready for Live Trading'
if (excellentCount >= 3) return 'Excellent - Ready for Live Trading'
if (goodCount >= 3) return 'Good - Ready with Caution'
return 'Needs Improvement'
}
}
return analysis
}
// Example usage
const results = {
totalTrades: 127,
winningTrades: 68,
losingTrades: 59,
winRate: 53.5,
profitFactor: 1.8,
sharpeRatio: 1.85,
maxDrawdown: 15.5,
totalProfit: 45000,
totalLoss: 25000,
averageWin: 850,
averageLoss: 420
}
const analysis = analyzeBacktestResults(results)
console.log('Overall Rating:', analysis.overallRating())
Red Flags to Watch For
1. Too Good to Be True
Warning Signs:
- Win rate > 80%
- Profit factor > 4.0
- Sharpe ratio > 3.5
- Max drawdown < 5%
Likely Causes:
- Look-ahead bias
- Overfitting
- Data errors
- Unrealistic assumptions
2. Inconsistent Performance
Warning Signs:
- Few large winners, many small losers
- Profit concentrated in few trades
- Long losing streaks
- Erratic equity curve
Action: Review trade distribution and equity curve
3. Insufficient Data
Warning Signs:
- < 30 total trades
- Trades clustered in short period
- Missing data gaps
- Single market condition tested
Action: Extend backtest period or adjust strategy
Comparison Benchmarks
function compareToBenchmark(results, benchmarkReturn) {
const strategyReturn = (results.totalProfit - results.totalLoss) /
results.initialBalance * 100
return {
strategyReturn: strategyReturn.toFixed(2) + '%',
benchmarkReturn: benchmarkReturn.toFixed(2) + '%',
outperformance: (strategyReturn - benchmarkReturn).toFixed(2) + '%',
worthIt: strategyReturn > benchmarkReturn * 1.5 // Must beat by 50%
}
}
// Example: Compare to Nifty 50 return
const comparison = compareToBenchmark(results, 12.5)
// Strategy: 20%, Benchmark: 12.5%, Outperformance: 7.5%
Decision Matrix
function shouldTradeLive(results) {
const criteria = {
sufficientTrades: results.totalTrades >= 50,
profitablePF: results.profitFactor >= 1.5,
goodSharpe: results.sharpeRatio >= 1.0,
acceptableDD: results.maxDrawdown <= 25,
positiveReturn: results.totalProfit > results.totalLoss
}
const passedCount = Object.values(criteria).filter(v => v).length
if (passedCount === 5) {
return 'GO LIVE - All criteria met'
} else if (passedCount >= 4) {
return 'PROCEED WITH CAUTION - Most criteria met'
} else if (passedCount >= 3) {
return 'PAPER TRADE FIRST - Some concerns'
} else {
return 'DO NOT TRADE - Needs significant improvement'
}
}