Backtesting Your Strategy
Learn to properly backtest strategies on historical data before risking real capital. Avoid the pitfalls.
Backtesting: Prove Your Edge Before Trading
Backtesting is running your strategy on historical data to see hypothetical results. It's not predictive, but it shows if your edge existed in the past.
The Four Critical Components
- Clean Data: OHLCV with proper adjustments for splits/dividends
- Signal Generation: Your indicators generating buy/sell signals
- Execution Simulation: Slippage, commissions, and order fills
- Metrics: Performance calculations
Key Performance Metrics
| Metric | Formula | What It Means | What's Good |
|---|---|---|---|
| CAGR | (Ending Value / Starting Value)^(1/Years) - 1 | Annualized return | >15% |
| Sharpe Ratio | (Return - Risk-Free) / Volatility | Risk-adjusted return | >1.0 is good |
| Max Drawdown | Largest peak-to-trough decline | Worst loss experienced | <-30% preferred |
| win Rate | Profitable Trades / Total Trades | Percentage of wins | >50% is good |
| Profit Factor | Gross Profit / Gross Loss | Ratio of wins to losses | >2.0 is strong |
# Backtest calculation pseudocode
portfolio_value = [initial_capital]
for each day in historical_data:
generate_signals()
for each open_position:
check_exit_conditions()
execute_new_trades()
daily_pnl = (portfolio_value[-1] - initial_capital)
portfolio_value.append(portfolio_value[-1] + daily_pnl)
# Calculate metrics
returns = np.diff(portfolio_value) / portfolio_value[:-1]
cagr = (portfolio_value[-1] / initial_capital) ^ (1 / years) - 1
sharpe = (returns.mean() * 252 - risk_free_rate) / (returns.std() * np.sqrt(252))
max_dd = (np.min(portfolio_value) - portfolio_value[-1]) / portfolio_value[-1]
The Pitfalls: Look-Ahead Bias and Overfitting
- Lookahead Bias: Using data from the future in your signal. E.g., "Buy if tomorrow's close is higher" = cheating.
- Overfitting: Strategy works perfectly on 2020-2024 but fails in 2025. You've fit to noise, not signal.
- Survivorship Bias: Only testing on stocks still alive (failed companies excluded). Skews returns up.
- Ignoring Costs: Forgetting commissions and slippage. A 12% return becomes 10% after costs.
- Data Mining: Testing 1000 parameter combinations and picking the best one. 1 in 1000 will be profitable by chance.
How to Avoid Overfitting
Use walk-forward analysis: train on 2000-2010, test on 2010-2015, train on 2005-2015, test on 2015-2020, etc. Results should be consistent across windows.
Common Backtesting Mistakes to Avoid
- Testing only on the most recent bull market (bias toward bull strategies)
- Using a single stock (not representative)
- Not accounting for portfolio management (what if you're in 10 trades and 9 hit stops simultaneously?)
- Not trading enough to be statistically significant (fewer than 50 trades = random noise)
- Trading only during market hours (what about gaps overnight?)
The Real Test
The ultimate test is LIVE PAPER TRADING. Run your strategy on real market data with virtual money. If it works in paper trading for 3+ months, consider real capital.
This Platform's Backtest Tool
Use the Lab's backtest feature to test strategies across different time periods, assets, and parameter combinations.
Customizing Your Asset Universe
Most templates default to a small set of liquid stocks (like AAPL, MSFT, SPY). To scale your strategy, you need to define which symbols it should trade.
- Manual Selection: Explicitly list symbols in your strategy configuration.
- Comment-Based Selection: Use the // symbols: TICKER1,TICKER2 pattern in your code.
- Dynamic Selection: Target specific market segments (e.g., Top 20 Market Cap).
How to Update Your Selection
In your strategy, you can easily switch from a single symbol to a specific selection by updating the header comment. The platform engine handles the "looping" for you—it calls your signal function for each symbol in your list automatically.
# OPTION A: Single Symbol
// symbols: AAPL
# The engine calls your function once for AAPL
# OPTION B: Manual Selection
// symbols: AAPL,NVDA,TSLA
# The engine calls your function 3 times (once for each stock)
# OPTION C: Dynamic Selection
// symbols: dynamic:top_20_market_cap
# The engine calls your function 20 times for the top liquid assets
Simplified Multi-Asset Logic
Because the platform handles the iteration, your signal logic stays clean and focused on a single asset. You don't need to write complex loops.
// This code works for 1 stock OR 100 stocks automatically
function handleData(context, data) {
const symbol = context.symbol; // The engine passes the current symbol here
const prices = data.history(symbol, 'close', 50);
const fast = sma(prices, 10);
const slow = sma(prices, 30);
if (fast > slow) {
orderTargetPercent(symbol, 1.0); // Places order for THIS symbol
}
}Why use Top 20 Market Cap?
By focusing on the largest 20 stocks by market capitalization, your algorithms benefit from the tightest bid-ask spreads and highest liquidity. This minimizes "slippage"—the difference between your expected price and actual execution price—which is critical for high-frequency or momentum strategies.
- Backtesting simulates your strategy on historical data
- Lookahead bias and overfitting are the biggest pitfalls
- Key metrics: CAGR, Sharpe ratio, max drawdown, win rate
- A backtest on data 2020-2024 is meaningless—test on multiple market regimes