# Moving Average Crossover Backtester for ThinkOrSwim (Free Download)

Backtester Free Beginner 21 min video 29 lines of code Dec 26, 2020 Updated Oct 21, 2026

A free moving average crossover backtester for ThinkOrSwim. Pick the length and type of a fast and a slow average, and read the P/L of every crossover.

## Overview

In this tutorial I build a moving average crossover backtester for ThinkOrSwim to answer one of the most common questions in trading: which moving average crossover is best? The strategy buys when a fast moving average crosses above a slow one, sells when it crosses back below, and ThinkOrSwim tracks the profit and loss of every trade.

Every part of the crossover is an input, so you can test an 8 EMA against a 21 EMA, then against a 34, then as simple or Wilders averages, from the settings window. It's Episode 23 of our How to thinkScript series and it's written for beginners, including traders who have never written a line of thinkScript.

## Key Takeaways

- The moving average crossover backtester is a free ThinkOrSwim strategy that buys when a fast moving average crosses above a slow one and sells to close when it crosses back below.

- It defaults to an 8-period EMA against a 21-period EMA, 100 shares per trade, with long entries on and short entries off.

- Each average can be simple, exponential, weighted, Wilders or Hull. ThinkOrSwim stores those types as the numbers 0 to 4, and the backtester reads them to print a label such as "8 EMA x 21 EMA".

- In the video, on a 5-year daily chart of the S&P 500, the 8 EMA x 21 EMA crossover showed $11,741 of P/L against $10,322 for the 8 EMA x 34 EMA. The video doesn't state the trade count for either run.

- Switching to the 8 and 21 simple moving averages raised the 5-year figure to $11,772. Repeating the test on a 5-day, 5-minute chart showed red P/L bars.

## What the Moving Average Crossover Backtester Answers

The moving average crossover backtester answers one question: has a given crossover made money on a given chart? My example in the video asks whether the 8-period exponential moving average crossing above the 21-period EMA has been a good time to enter on the S&P 500, with the exit when the 8 crosses back below the 21.

The next question is what happens if you change the 21 to a 34. If the profit and loss goes up, the 8 and 34 is the better pair on that chart. If it goes down, the 8 and 21 wins. Repeat that with other pairs, like an 8 EMA against a 50 SMA, to learn which crossovers suit your favorite tickers.

A ThinkOrSwim strategy is what makes this possible. Most of our tutorials build studies, the normal indicator files. A study can show you where two averages cross. A strategy places simulated orders at those crosses, so it tells you how effective the crossover has been.

## How to Create the Backtester in ThinkOrSwim

The moving average crossover backtester is a strategy, so you create it from the Strategies tab instead of the Studies tab.

- Click the Studies icon on your chart.

- Switch to the Strategies tab and click Create. A new strategy opens with AddOrder code already in the editor, which is an easy check that you're on the right tab.

- Give it a name. I called mine TI moving average crossover.

- Replace the starter code with the code from the Code tab on this page, then click OK and Apply.

- Click Global Strategy Settings and turn on the option to display the Floating P/L study with strategies. Click OK.

The Floating P/L study is the row of histogram bars under the chart. It tracks the strategy's running profit and loss, including any trade that hasn't closed yet, and it updates each time you change an input.

The download has the same code as a strategy file. To import it instead of pasting, follow our guide on [how to import a thinkScript file into ThinkOrSwim](https://tosindicators.com/thinkscript/how-to-import-thinkorswim).

## The Inputs

The moving average crossover backtester has 7 inputs. Each one appears in the strategy's settings window, which you open by double-clicking the strategy, so you can run a new test without opening the code.

Input Default What it controls

longEntries yes Buys to open when the fast average crosses above the slow one

shortEntries no Sells to open when the fast average crosses below the slow one

fastMALength 8 Length of the fast moving average

slowMALength 21 Length of the slow moving average

averageTypeFast Exponential Type of the fast average: Simple, Exponential, Weighted, Wilders or Hull

averageTypeSlow Exponential Type of the slow average, with the same 5 choices

quantity 100 Shares per order. Change it to 1 for futures.

Short entries default to no because you'll usually test one direction at a time: crosses above for bullish trades, or crosses below for bearish trades. To test the short side, set shortEntries to yes and longEntries to no.

## How the Code Works

The moving average crossover backtester code has 4 parts: the inputs, the two moving averages, a label, and the orders. The snippets below come from the downloaded file. If you're new to thinkScript, the first step explains the three kinds of variables it uses.

### Step 1: Input, Def and Plot

An input is a value the user controls from the settings window. A def calculates a value without drawing it. A plot calculates a value and draws it on the chart.

In the video I tried to write the fast moving average as an input, and ThinkOrSwim returned an "invalid statement" error. A function result like a moving average has to be a def or a plot. Changing it to def made the code compile. I kept it as a plot so I could see the line and confirm the code was doing what I expected.

### Step 2: The Two Moving Averages

(This step's code is free with an account.)

MovingAverage is a built-in thinkScript function that takes 3 parameters: the average type, the price, and the length. Because the type and the length come from inputs, these 2 lines cover every average type in the dropdown. Both averages use the close. In the video, the fast line plots in cyan and the slow line in pink.

### Step 3: The Average Types Behind the Dropdown

AverageType is built into thinkScript. When an input is set to AverageType.Exponential , ThinkOrSwim turns it into a dropdown with 5 choices. Each choice also has a whole-number value, which I showed in the video by printing the input in a yellow label in the top left corner of the chart.

Average type Value in ThinkOrSwim Text in the backtester's label

Simple 0 Simple

Exponential 1 EMA

Weighted 2 Weighted

Wilders 3 Wilders

Hull 4 Hull

### Step 4: The Label

The label turns those numbers back into text, so the chart always shows which crossover you're testing. With the defaults it prints "8 EMA x 21 EMA" in yellow. The full AddLabel line is in the Code tab.

The label joins 5 pieces: the fast length, the fast type's name, an "x", the slow length and the slow type's name. Each name comes from a chain of if/else checks on the type's number, starting with if averageTypeFast == 0 then " Simple" else if averageTypeFast == 1 then " EMA" . Number 2 prints Weighted, 3 prints Wilders and anything else prints Hull.

When you change an input, the label changes with it. Our [AddLabel tutorial](https://tosindicators.com/thinkscript/labels) covers labels in more detail.

### Step 5: The Orders

AddOrder is the function that places the simulated trades. Each call here passes 4 values: the order type, the condition that has to be true, the price, and the quantity. Every order here fills at the close of the signal bar for the quantity input.

(This step's code is free with an account.)

The first line buys to open when the fast average crosses above the slow one, if longEntries is yes. The second sells to close on the cross back below.

The short side mirrors it: sell to open on a cross below when shortEntries is yes, and buy to close on a cross back above. ThinkOrSwim also has BUY_AUTO and SELL_AUTO order types, and the download leaves a BUY_AUTO line commented out at the bottom of the file.

## Results From the Video

The moving average crossover backtester produced these figures on the S&P 500 in the video. They are historical results from the charts I had loaded when I recorded, for 100 shares per order, including open P/L.

The video gives each chart's length but no dates, and it never shows a trade count or a win rate. The one count in the table is mine, from the trades I point out on the 1-year chart.

Crossover tested Chart P/L shown Trades

8 EMA x 21 EMA, entry order only (no exit yet) 1 year, daily $9,080 1 position, never closed

8 EMA x 21 EMA 1 year, daily $5,662 Not shown. In my walk-through of the chart I point out 3 closed trades, 1 of them a loss, plus 1 open trade.

8 EMA x 34 EMA 1 year, daily $5,353 Not stated

8 EMA x 34 EMA 5 years, daily $10,322 Not stated

8 EMA x 21 EMA 5 years, daily $11,741 Not stated

8 Simple x 21 Simple 5 years, daily $11,772 Not stated

Wilders averages 5 years, daily Dropped substantially. No figure read out. Not stated

8 Simple x 34 Simple 5 years, daily Lower than $11,772. No figure read out. Not stated

Not restated 5 days, 5-minute Red P/L bars. No figure read out. Not stated

The first run had only the entry order, so the strategy bought and never sold. Adding the exit lowered the P/L and created more trades, one of them a loss. On that chart, buying the first crossover and holding did better than trading every crossover.

The 21 beat the 34 on both the 1-year and the 5-year chart. Switching both averages to simple raised the 5-year result a little, while Wilders averages and the 8 and 34 simple pair came in lower. Of the pairs I tested on 5 years of daily S&P 500 bars, the 8 and 21 simple moving averages showed the highest P/L.

Then I loaded a 5-day, 5-minute chart and repeated the test. The red P/L bars said the crossover was a bad idea on the S&P 500 over those 5 days. Last, I switched the symbol to the Qs (QQQ) on a daily chart, and the moving averages, trades and P/L all updated with the same settings.

## How to Run Your Own Tests

A crossover test with this backtester is a series of single changes: write down the P/L, change one input, and compare.

- Set the number to beat. Run the defaults first and note the P/L. In the video, $10,322 was the number to beat before I changed the 34 back to a 21.

- Change one input at a time. Test the length first, then the type. If you change both together, you can't tell which one moved the result.

- Test more history and other timeframes. Going from 1 year to 5 years of daily bars changed both totals. The daily tests made money, while the 5-minute test showed red P/L bars.

- Look at how many trades sit behind a total. On the 1-year chart, the total came from a handful of trades, and one trade can swing a sample that small.

The backtester tells you how a crossover has behaved on one chart in the past. Treat a crossover as a read on the trend, and use the backtest to decide which pair is worth a closer look.

The code has no stop loss, so a losing trade stays open until the averages cross back. Before you trade a crossover, decide where your stop goes and size the position so a stop-out costs a small, fixed share of your account.

## Ways to Extend the Backtester

The moving average crossover backtester is a starting point. At the end of the video I suggest experimenting with your own entry and exit criteria.

- Test pullbacks instead of crossovers. Our moving average pullback backtester tests buying and selling pullbacks into key moving averages.

- Require a full stack. Our stacked moving averages indicator plots the 8, 21 and 34 EMAs with the 50 and 200 SMAs, and draws an arrow when they move into a stacked formation.

- See the crossover as a cloud. Our moving average clouds indicator uses the same 8 and 21 EMA defaults and shades the space between them green or red.

- Add a higher timeframe. Our multiple time frame moving averages indicator plots daily and hourly averages on an intraday chart.

- Compare a slower crossover. Our research on the golden cross over 20 years of the S&P 500 tests a long-term version of the same idea.

For more strategies built with AddOrder , browse our free [ThinkOrSwim backtesters](https://tosindicators.com/backtesters).

## Common Problems

The moving average crossover backtester has three common problems, and each one has a quick fix.

- No P/L bars under the chart. Turn on the Floating P/L study in Global Strategy Settings.

- An "invalid statement" error on a moving average line. The line starts with input. Change it to def or plot.

- The code doesn't place any trades. Check that you created it on the Strategies tab, and that longEntries or shortEntries is set to yes.

## The backtester on real data

Drawn from real daily bars with the code on this page, through Oct 20, 2026. The backtester buys 100 shares at the close of the bar where the fast average crosses above the slow one, and sells at the close when it crosses back, exactly as the AddOrder lines in the code do. Dividends and commissions are left out.

### SPY over the last year, 8 EMA x 21 EMA

SPY since Oct 1, 2025. Numbered cyan arrows are buys; each sell shows that trade's profit or loss on 100 shares.

# Bought At Sold At 100 shares

1 Nov 28, 2025 $683.39 Feb 5, 2026 $677.62 -$577

2 Feb 9, 2026 $693.95 Feb 12, 2026 $681.27 -$1,268

3 Feb 26, 2026 $689.30 Feb 27, 2026 $685.99 -$331

4 Apr 9, 2026 $679.91 Jun 11, 2026 $737.76 +$5,785

5 Jun 12, 2026 $741.75 Jun 24, 2026 $733.24 -$851

6 Jul 1, 2026 $745.76 Jul 23, 2026 $738.18 -$758

7 Aug 3, 2026 $757.67 Sep 10, 2026 $757.83 +$16

8 Sep 21, 2026 $773.52 Open $769.05 -$447 open

Since Oct 1, 2025 the 8 x 21 EMA crossover opened and closed 7 trades on SPY: 2 winners and 5 losers, $2,016 in total on 100 shares. It bought again on Sep 21, 2026 at $773.52 and is still in that trade. The small losses come in sideways stretches, when the averages cross back and forth; the gains come from the long runs in between.

### Which crossover did best since 2016?

The question the backtester was built to answer. Here are four common crossovers on SPY, QQQ and IWM from January 4, 2016 to October 20, 2026, with 100 shares each time, next to simply buying 100 shares and holding them.

8 x 21 EMA 8 x 34 EMA 21 x 50 EMA 50 x 200 SMA Buy and hold

SPY $39,183 51 trades, 51% won, worst drop $7,178 $41,782 36 trades, 53% won, worst drop $6,152 $37,095 18 trades, 50% won, worst drop $12,329 $35,218 3 trades, 67% won, worst drop $11,645 $56,803 worst drop $12,115

QQQ $37,202 55 trades, 40% won, worst drop $10,123 $40,397 38 trades, 47% won, worst drop $8,092 $32,676 23 trades, 57% won, worst drop $10,320 $49,353 3 trades, 100% won, worst drop $12,346 $60,813 worst drop $14,389

IWM $12,385 60 trades, 40% won, worst drop $7,502 $8,966 46 trades, 39% won, worst drop $7,687 $6,763 27 trades, 41% won, worst drop $8,582 $1,009 6 trades, 33% won, worst drop $7,970 $16,577 worst drop $8,035

Total profit on 100 shares, closed trades plus any open one at the Oct 20, 2026 close. "Worst drop" is the largest fall in that profit from its high, in dollars.

None of the 12 crossover results made more than buying and holding. What the crossovers did buy was time out of the market: 10 of the 12 had a smaller worst drop than buy and hold.

The full thinkScript code is free with an account: [create one](https://tosindicators.com/register) or [sign in](https://tosindicators.com/login) on this page.

## Download

Moving Average Crossover Backtester Download the Moving Average Crossover Backtester for ThinkOrSwim. The download is a .zip with one strategy file, TI_MovingAverageCrossoverSTRATEGY.ts, the same code as the Code tab on this page. Import it as a strategy, then open its settings to change the lengths, average types, direction and quantity. If you haven't imported a file before, here's [how to import into your ThinkOrSwim platform](https://tosindicators.com/thinkscript/how-to-import-thinkorswim).

[Download free](https://tosindicators.com/backtesters/moving-average-crossover)

## Video: Moving Average Crossover Backtester, built line by line

21 minutes on YouTube.

[Watch on YouTube](https://www.youtube.com/watch?v=UXdHLxM7hOU)

## Questions

What is a moving average crossover strategy?

A moving average crossover strategy enters a trade when a faster moving average crosses a slower one. In the backtester I build, the strategy buys when the 8-period EMA crosses above the 21-period EMA and sells to close when it crosses back below. A short version does the reverse.

How do I backtest a moving average crossover in ThinkOrSwim?

Build it as a strategy. Click the Studies icon, switch to the Strategies tab, click Create, and paste in the backtester code. Then open Global Strategy Settings and turn on the Floating P/L study. The bars under the chart show the strategy's running profit and loss, and they update each time you change a length or an average type.

Which moving average crossover did best in the video?

On a 5-year daily chart of the S&P 500, the 8 and 21 simple moving averages showed the highest P/L I tested, $11,772. The 8 and 21 EMAs showed $11,741 and the 8 and 34 EMAs $10,322. These are historical results for 100 shares per order, and the video doesn't state how many trades each run took.

What do the AverageType numbers mean in thinkScript?

Each moving average type in ThinkOrSwim has a whole-number value: Simple is 0, Exponential is 1, Weighted is 2, Wilders is 3 and Hull is 4. The backtester's label checks that number to print the type as text, so the chart shows a label such as 8 EMA x 21 EMA for the crossover you are testing.

How do I test short trades with the crossover backtester?

Open the strategy's settings and set shortEntries to yes and longEntries to no. The strategy then sells to open when the fast average crosses below the slow one and buys to close when it crosses back above. Shorts default to no because it's cleaner to test one direction at a time.

Why do I get an invalid statement error on my moving average line?

The line is probably declared as an input. A moving average calculated with a function like MovingAverage has to be a def, which calculates it without drawing it, or a plot, which draws it on the chart. In the video, changing input to def made the code compile, and changing it to plot drew the line.

Can I run the crossover backtester on intraday charts?

Yes. The strategy runs on whatever symbol and timeframe you load, and the P/L recalculates for that chart. In the video, the crossovers made money on daily S&P 500 charts, but repeating the test on a 5-day, 5-minute chart showed red P/L bars, so test each timeframe you plan to trade.

How do I use the moving average crossover backtester on futures?

Change the quantity input from its default of 100 shares to 1. Every order in the strategy uses the quantity input, so the P/L scales with it. The lengths, average types and direction settings work the same way on a futures chart.

## Related tools

[Moving Average Pullback Backtester: Backtester · Free](https://tosindicators.com/backtesters/moving-average-pullback)[Stacked Moving Averages: Indicator · Free](https://tosindicators.com/indicators/stacked-moving-averages)[Moving Average Clouds: Indicator · Free](https://tosindicators.com/indicators/moving-average-clouds)[Multiple Time Frame Moving Averages: Indicator · Free](https://tosindicators.com/indicators/multiple-moving-averages)[Reversion to 50-Day Moving Average: Indicator · Free](https://tosindicators.com/indicators/reversion-to-50-sma)[TTM Squeeze Backtester: Backtester · Free](https://tosindicators.com/backtesters/ttm-squeeze-backtester)

## Get each new indicator by email

One email on Sundays with the week's new tool and its code. A free account also unlocks every .txt download.

No credit card. Unsubscribe anytime.
