Backtesting Stock Trading Strategies with Excel: A Simple Approach

The Excel Backtesting Framework: Precision Without Coding

Backtesting in Excel offers a transparent, auditable alternative to black-box platforms. Unlike Python, where a single syntax error can silently corrupt results, Excel forces you to see every calculation, every cell reference, and every assumption. This granular visibility is critical for retail traders who need to understand why a strategy fails, not just that it fails.

The core philosophy of this approach is row-by-row simulation. You will structure your historical price data so that each row represents a single trading day (or bar). Your strategy logic will be applied to each row sequentially, calculating signals, positions, and equity curves in real-time. This method avoids the pitfalls of array formulas and volatile functions, ensuring your backtest is both accurate and computationally efficient.

What You Will Need:

  1. Excel 2016 or newer (365 preferred for dynamic arrays).
  2. High-quality historical data: Daily OHLC (Open, High, Low, Close) data for at least 500 trading days. Sources: Yahoo Finance (export to CSV), Alpha Vantage (free API), or your broker’s data feed.
  3. A clear trading rule: Define your entry and exit criteria in plain English before touching Excel. Example: “Buy when the 10-day simple moving average crosses above the 30-day SMA. Sell when it crosses below.”

Data Preparation: The Non-Negotiable Foundation

Poor data quality is the leading cause of false backtest results. Follow this protocol strictly.

  1. Import & Sort: Paste your data into a new worksheet called Raw Data. Ensure columns are: Date, Open, High, Low, Close, Volume. Sort by Date (Oldest to Newest).
  2. Create a Master Worksheet: Copy all data to a new sheet named Backtest. Add 10 empty columns to the right of Volume. This will be your calculation zone.
  3. Remove Corporate Actions: If you are using adjusted close prices, ensure they are adjusted for splits and dividends. Unadjusted data will make your equity curve appear jumpy and inaccurate. If your data lacks adjustment, apply a simple divisor: In a new column, calculate Adj Close = Close / (Latest Close / Latest Adjusted Close).
  4. Date Formatting: Convert all dates to Excel’s serial number format (YYYY-MM-DD). This ensures chronological sorting and enables date-based formulas later.
  5. Zero-Volume Days: Remove any rows where Volume is 0 and Close is not 0 (these are usually data glitches, not true trading halts).

Final Column Layout (Backtest Sheet):
| A | B | C | D | E | F | G | H | I | J | K | L |
|—|—|—|—|—|—|—|—|—|—|—|—|
| Date | Open | High | Low | Close | Volume | SMA_10 | SMA_30 | Signal | Position | Equity | PnL |


Core Strategy Logic: Moving Average Crossover

We will build a classic dual moving average crossover system. It is simple, robust, and excellent for demonstrating Excel’s power.

Step 1: Calculate Indicators (Columns G & H)

  • G (SMA_10): In cell G2 (first data row), enter:
    =IF(ROW()>10,AVERAGE(E2:E11),"")
    Drag down. This calculates the 10-day simple moving average. The IF statement prevents errors in the first 9 rows.

  • H (SMA_30): In cell H2, enter:
    =IF(ROW()>30,AVERAGE(E2:E31),"")
    Drag down. Ensure you have at least 30 rows of data before this formula returns a value.

Step 2: Generate Signals (Column I)

A signal is a discrete event, not a continuous state. We use 1 for a Buy signal, -1 for a Sell signal, and 0 for no action.

  • In cell I2: Enter 0 (no signal on the first day).
  • In cell I3: Enter this logic:
    =IF(AND(G3>H3,G2<=H2),1,IF(AND(G3

    =H2),-1,0))
    Drag down.

Explanation:

  • G3>H3 (today’s short MA > today’s long MA) AND G2<=H2 (yesterday’s short MA <= yesterday’s long MA) = Golden Cross (Buy).
  • The reverse = Death Cross (Sell).

Step 3: Track Position (Column J)

Position indicates your market exposure: 1 for fully long, 0 for flat, -1 for short. We will use a sequential state machine.

  • In cell J2: Enter 0 (starting flat).
  • In cell J3: Enter:
    =IF(I3=1,1,IF(I3=-1,0,J2))
    Drag down.

Critical Logic:

  • If today’s signal is 1 (Buy), position becomes 1.
  • If today’s signal is -1 (Sell), position becomes 0 (we exit long, no shorting in this basic version).
  • Otherwise, repeat yesterday’s position (J2).

Step 4: Calculate Daily PnL (Column L)

This is where accuracy matters. We assume you trade at the close price of the signal day.

  • In cell K2: Enter your initial capital, e.g., $100,000.
  • In cell L3: Calculate the daily profit.
    =IF(J3=1,K2*(E3/E2-1),IF(J3=0,IF(J2=1,K2*(E3/E2-1),0),0))
    Drag down.

Breakdown:

  • If position is 1 (we are invested), profit = previous equity * (today’s close / yesterday’s close – 1).
  • If position is 0 but yesterday’s position was 1 (we sold today), we still capture today’s price movement.
  • If we are flat both days, PnL = 0.

Step 5: Calculate Equity Curve (Column K)

  • In cell K3: Enter =K2 + L3.
  • Drag down. This gives you the running equity after each day’s trading.

Advanced Enhancements: Realism and Cost

Adding transaction costs and slippage transforms your backtest from theoretical to actionable.

Slippage & Commission Model (Modify Column L):
Create a new column M for “Trade Cost”. A common model is a fixed $10 commission per trade plus 0.1% slippage on the traded volume.

  • In cell M2: Enter 0.
  • In cell M3: Enter:
    =IF(I30, 10 + (0.001 * K2 * ABS(J3-J2)), 0)
    Drag down.

Explanation:

  • I30 checks if a trade occurred today.
  • 10 is the fixed commission.
  • 0.001 * K2 * ABS(J3-J2) calculates slippage. ABS(J3-J2) is 1 when you enter or exit.

Revised PnL (Column L): Change L3 to:
=IF(J3=1,K2*(E3/E2-1),IF(J3=0,IF(J2=1,K2*(E3/E2-1),0),0)) - M3

Now, your equity curve accurately reflects the drag of real-world trading.


Performance Metrics: The Scorecard

You cannot evaluate a strategy without metrics. Build a summary section at the top of your sheet (rows 1-15).

  • Total Return: =K_latest / K_initial - 1
  • CAGR (Compound Annual Growth Rate): =(K_latest/K_initial)^(252 / ROWS(A:A)) - 1
    (Assumes 252 trading days per year. Adjust ROWS(A:A) to count only days with data).
  • Max Drawdown: This is the hardest formula. Use a helper column N:
    In N2: =K2
    In N3: =MAX(N2,K3) (This tracks the running peak of equity).
    Then, Max Drawdown % = =MIN((K3-N3)/N3) across all rows.
  • Sharpe Ratio: =AVERAGE(L3:L1000) / STDEV.S(L3:L1000) * SQRT(252)
    (Assumes you are using daily returns. Lower risk-free rate if desired).
  • Win Rate: =COUNTIF(L3:L1000,">0") / COUNTIF(L3:L1000,"0")
  • Number of Trades: =COUNTIF(I3:I1000,"0") / 2 (Each transaction has an entry and exit).

Optimization: Parameter Sweeping (Data Tables)

Excel’s Data Table feature is an underutilized gem for testing multiple SMA lengths (e.g., 5/15, 10/30, 20/50) without manual edits.

  1. Create a new worksheet called Optimizer.
  2. Set up a grid: Column A = SMA_Short values, Row 1 = SMA_Long values.
  3. In cell A1, enter a reference to your backtest’s Total Return cell (e.g., ='Backtest'!B10).
  4. You will need to link your backtest’s SMA_10 and SMA_30 formulas to two dedicated cells on the Backtest sheet (e.g., Optimizer!$B$2 and Optimizer!$C$2).
  5. Select the grid range (including A1), go to Data > What-If Analysis > Data Table.
  6. For Column input cell, select Optimizer!$B$2 (SMA_Short). For Row input cell, select Optimizer!$C$2 (SMA_Long).
  7. Press OK. Excel will run the backtest for every combination and populate the grid with Total Return values.

Caution: Data Tables recalculate the entire sheet every time. If your backtest has 1000 rows and you test 100 combinations, this will be slow. Consider setting Calculation Mode to Manual (Formulas > Calculation Options) and pressing F9 to recalculate.


Common Pitfalls and How to Avoid Them

  1. Look-Ahead Bias: Ensure your signals use only data available up to the close of day t. Your SMA formulas must reference E2, not E1, for the signal on day 3. The formula =IF(G3>H3...) correctly uses today’s MA, which is calculated from today’s close—that is fine. The bias occurs if you accidentally reference tomorrow’s close.
  2. Survivorship Bias: Your historical data set only includes companies that still exist. For long-only strategies, this inflates returns. Use index ETF data (e.g., SPY) to avoid this.
  3. Overfitting: If you test 100 parameters and pick the best one, the results are likely random noise. Use in-sample (e.g., first 70% of data) for optimization, then out-of-sample (last 30%) for validation.
  4. Ignoring Volume: A price crossing is weak if volume is declining. Add a volume filter:
    =IF(AND(G3>H3,G2AVERAGE(F2:F10)),1,... )
    (This adds a condition that today’s volume exceeds the 10-day average).
  5. Holiday/Partial Days: If your data includes half-days or missing days, the ROW() function in SMA calculations will be misaligned. Use the COUNTIF method to base your rolling windows on actual data rows.

Visual Verification: Charting Your Equity

Raw numbers are necessary, but a chart reveals regime changes and drawdown shapes.

  1. Insert a Line Chart.
  2. Add series for:
    • Close Price (Column E, secondary axis).
    • Equity Curve (Column K, primary axis).
    • SMA_10 and SMA_30 (Columns G and H, secondary axis).
  3. Adjust axes: Equity on the left, Price on the right.
  4. Zoom in on 2015, 2018, and 2020 to visually confirm that drawdowns align with market crashes. If your equity curve is smooth during a crash, your calculations likely have an error.

Pro Tip: Overlay your Position column (J) as a scatter plot with vertical lines. This instantly shows when you were in the market versus flat, allowing you to spot if your exit logic fired too late.


Excel Formulas Reference Card

Purpose Formula (Assumes row 3 is current)
10-Day SMA =AVERAGE(E2:E11)
30-Day SMA =AVERAGE(E2:E31)
Golden Cross Signal =IF(AND(G3>H3,G2<=H2),1,0)
Death Cross Signal =IF(AND(G3

=H2),-1,0)

Combined Signal =IF(AND(G3>H3,G2<=H2),1,IF(AND(G3

=H2),-1,0))

Position (Long/Flat) =IF(I3=1,1,IF(I3=-1,0,J2))
Daily PnL (No Costs) =IF(J3=1,K2*(E3/E2-1),IF(J3=0,IF(J2=1,K2*(E3/E2-1),0),0))
Trade Cost =IF(I30,10 + (0.001*K2*ABS(J3-J2)),0)
Net PnL = [Daily PnL] - [Trade Cost]
Running Peak Equity =MAX(K2,N2)
Max Drawdown =MIN((K3-N3)/N3)

Our Sponsors


Latest Posts

Something went wrong. Please refresh the page and/or try again.

Discover more from DNS Research

Subscribe now to keep reading and get access to the full archive.

Continue reading