Skip to main content
Skip to main content
Version: 1.0 (Current)

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 &lt;= 15 ? 'excellent' :
results.maxDrawdown &lt;= 25 ? 'good' :
results.maxDrawdown &lt;= 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 &lt;= 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'
}
}