DEX & Algo Trading

Sharpe Ratio Deep Dive: 5 Common Mistakes in Strategy Evaluation

ClawDUX TeamApril 6, 20266 min read2 views

Sharpe Ratio Deep Dive: 5 Common Mistakes in Strategy Evaluation

A strategy with Sharpe 3.0 sounds amazing — until you realize the number is wrong. Here are the five most common mistakes.

Correct Sharpe Calculation

python
import numpy as np
import pandas as pd

def sharpe_ratio(
    returns: pd.Series,
    risk_free_rate: float = 0.05,  # Annual risk-free rate
    periods_per_year: int = 252,   # Trading days
) -> float:
    """Calculate annualized Sharpe ratio correctly."""
    # Convert annual risk-free to per-period
    rf_per_period = (1 + risk_free_rate) ** (1 / periods_per_year) - 1

    excess_returns = returns - rf_per_period
    mean_excess = excess_returns.mean()
    std_excess = excess_returns.std(ddof=1)  # Use sample std

    if std_excess == 0:
        return 0.0

    return mean_excess / std_excess * np.sqrt(periods_per_year)

Mistake #1: Wrong Annualization Factor

python
# WRONG: Using 365 for daily crypto data
wrong_sharpe = returns.mean() / returns.std() * np.sqrt(365)

# RIGHT: Crypto trades 365 days, but vol structure differs
# Use 365 for crypto, 252 for stocks, 12 for monthly
correct_sharpe = returns.mean() / returns.std() * np.sqrt(365)
# But ensure your returns match the frequency!

# Common error: mixing daily returns with monthly frequency
daily_returns = prices.pct_change()  # Daily
monthly_returns = prices.resample('M').last().pct_change()  # Monthly

# These give DIFFERENT Sharpe ratios
sharpe_daily = sharpe_ratio(daily_returns, periods_per_year=365)
sharpe_monthly = sharpe_ratio(monthly_returns, periods_per_year=12)
# They should be similar but won't be identical due to compounding

Mistake #2: Ignoring the Risk-Free Rate

python
# In 2024, the risk-free rate is ~5% (T-bills)
# A strategy returning 8% with 10% vol:

# Without risk-free: 8% / 10% * sqrt(252) = 12.7 (inflated!)
# With risk-free:    3% / 10% * sqrt(252) = 4.76 (realistic)

Mistake #3: Look-Ahead Bias

python
# WRONG: Optimizing parameters on the full dataset
best_sharpe = 0
for window in [5, 10, 20, 50]:
    returns = strategy(full_data, window=window)
    s = sharpe_ratio(returns)
    best_sharpe = max(best_sharpe, s)
# This Sharpe is overfitted!

# RIGHT: Walk-forward or train/test split
train_data = data[:split_point]
test_data = data[split_point:]

# Optimize on train
best_window = optimize_on(train_data)

# Evaluate on test (this is the real Sharpe)
test_returns = strategy(test_data, window=best_window)
real_sharpe = sharpe_ratio(test_returns)

Mistake #4: Survivorship Bias

If you only test on assets that still exist today, you're excluding all the ones that went to zero. This inflates Sharpe by 0.3-0.5 typically.

Mistake #5: Not Accounting for Transaction Costs

python
def sharpe_with_costs(
    returns: pd.Series,
    signals: pd.Series,
    cost_per_trade_bps: float = 10,
) -> float:
    """Sharpe after transaction costs."""
    # Count trades (signal changes)
    trades = (signals.diff().abs() > 0).astype(float)
    trade_costs = trades * cost_per_trade_bps / 10000

    net_returns = returns - trade_costs
    return sharpe_ratio(net_returns)

# A strategy trading 10x/day with 10bps cost:
# Loses 10 * 10 = 100 bps/day = 365% annually in costs alone!

Quick Reference

Sharpe Range Interpretation
< 0 Losing money
0 - 0.5 Below average
0.5 - 1.0 Acceptable
1.0 - 2.0 Good
2.0 - 3.0 Very good
> 3.0 Suspicious — check for errors

On ClawDUX, every listed strategy's Sharpe ratio is independently verified by the AI engine — using the correct annualization, with transaction costs, and without look-ahead bias.

The core logic discussed in this article has been integrated into the ClawDUX API. Access ClawDUX-core for full permissions, or browse the marketplace to discover verified trading strategies.

#sharpe-ratio#metrics#evaluation#backtesting#quant

Related Articles