Moving Average Crossover Backtester

20 mins
Beginner-Friendly
youtube-video-thumbnail
Table of Contents
    Add a header to begin generating the table of contents
    Table of Contents
      Add a header to begin generating the table of contents

      Introduction

      For the 23rd episode of “How to thinkScript,” we’re going to build a moving average crossover backtester to help us answer one simple question:

      What is the best moving average crossover strategy for a particular stock?

      This thinkScript tutorial is going to be beginner-friendly, for those of you who are newer to the thinkScript language.

      Moving Average Crossover Backtester for ThinkOrSwim

      With the moving average crossover backtester that we create in this episode, we’ll be able to very easily test two moving averages against one another.

      For example, let’s say you wanted to see if an 8 EMA x 21 EMA Crossover is actually a profitable strategy. Well, now you can test it!

      Maybe you’re curious what happens if you change that 21 EMA to a 34 EMA. Or perhaps, a Simple Moving Average instead of an Exponential.

      What about when the moving averages cross in the opposite direction? Is that a good time to get out? Or should you use a close below the slower moving average?

      We’ll answer all of these questions and more, with the Moving Average Crossover Backtester (phew, that’s a long name!) that we build.

      So, let’s get started!

      Volatility Box Invite

      We are TOS Indicators.com, home of the Volatility Box.

      The Volatility Box is our secret tool, to help us consistently profit from the market place. We’re a small team, and we spend hours every day, after the market close, doing nothing but studying thousands of data points to keep improving and perfecting the Volatility Box price ranges.

      We have two different Volatility Boxes - a Futures Volatility Box and a Stock Volatility Box.

      Futures Volatility Box - Trade Major Markets With an Edge

      Designed For: Futures, Micro-Futures and Index Market Traders
      Supported Models: Hourly Volatility Box models
      Supported Markets: 10 Major Futures Markets

      The Futures Volatility Box comes with:

      • 5 Volatility Models for each market
      • Support for 10 Futures Markets (/ES, /NQ, /YM, /RTY, /CL, /GC, /SI, /ZB, /HG, /NG)
      • Video Setup Guide
      • Trade Plan
      • Access to all members-only resources, including Squeeze Course

      Learn More About the Futures Volatility Box

      Trade futures and micro futures with a consistent volatility edge

      Stock Volatility Box - Powerful Web Based Volatility Platform

      Designed For: Stock and Options Traders
      Supported Models: Hourly and Daily Volatility Box models
      Supported Markets: 10,000+ Stocks and ETFs (new markets added frequently)

      A Stock Volatility Box membership includes access to: 

      • Live Scanner - A powerful scanner we've built from scratch, to scan 10,000 symbols every 2 seconds for new volatility breaches
      • Dashboard - A quick and easy way to view daily volatility model levels online
      • Short Interest Scanner - Short interest, Squeeze, and EMA data to find short squeezes
      • Squeeze Course - All of our proprietary squeeze tools, including robust backtesters
      • All Members Only Indicators - We don't nickel and dime you. Everything really is included.
      • And much more!

      Learn More About the Stock Volatility Box

      Trade stocks and options with a consistent volatility edge

      Moving Avg. Crossover thinkScript Requirements

      Before we start writing any code, it’s important to first understand and map out what we’re trying to build.

      thinkScript Goal for Moving Average Crossover Backtester

      As a user, if we use the 8 period EMA crossing with the 21 period EMA as an example… the following would be considered our “requirements”

      • We should be able to change the moving average length from 8 to 13 or 34 or any other number we wanted
      • We should be able to change the moving average type, from Exponential to Simple, Hull, Wilders, etc.
      • We should be able to test moving average crossovers to the bullish side, along with the bearish side

      With our objectives clearly stated, we can now translate this to some thinkScript code.

      Organizing All of the Input Variables

      Let’s start by creating an organized list of all of the input variables that we will need to define.

      An input variable in thinkScript is a variable in which the user can control and change the parameters directly via the study’s menu:

      Input variables in ThinkOrSwim

      In the screenshot above, the user can change the moving average length (among many other things), symbolizing the importance of an input variable.

      The benefit of using a thinkScript input variable is that you can change aspects of the code in the studies menu, instead of needing to open and edit the code.

      For our backtester, here are the input variables we’ll need:

      input longEntries = yes;
      input shortEntries = no;
      
      input fastMALength = 8;
      input slowMALength = 21;
      input averageTypeFast = AverageType.Exponential;
      input averageTypeSlow = AverageType.Exponential;
      input quantity = 100;

      The “FastMALength” variable allows us to change the length of the fast moving average, while the “SlowMALength” variable allows us to change the slow moving average’s length.

      The AverageType variable in thinkScript also unlocks all of the different moving averages that ThinkOrSwim supports. This list includes the Simple, Exponential, Weighted, Wilder’s and Hull — which is important later in the tutorial.

      We separate this out by fast and slow moving averages as well, giving you granular control.

      Finally, we have our strategy specific variables, which includes the quantity for shares/contracts used in the backtest P/L reports. We also have the “longsOnly” and “shortsOnly” variables, allowing us to switch between bullish and bearish moving average crosses.

      How to Create a Moving Average in thinkScript

      Now that we have all of the variables defined which give us context clues about the moving average we want, it’s time to actually build it.

      To define a moving average, we can use the “MovingAverage()” thinkScript function.

      The format is the following:

      • MovingAverage(averageType, priceType, length);

      To create our two moving averages, we can use the function above and plug in the input variables in the appropriate places:

      plot fastMA = MovingAverage(averageTypeFast, close, fastMALength);
      plot slowMA = MovingAverage(averageTypeSlow, close, slowMALength);

      We also have the fastMA and slowMA set as a “plot” variable, which will show the appropriate moving averages on your ThinkOrSwim charts.

      If you don’t wish to see the moving averages plotted, you can simply change the “plot” variable above to a “def” variable.

      AddOrder() Function Code

      Finally, we need to define when and how to take a “trade.”

      To write that code, we can simply insert the appropriate labels in their respective parameters:

      • AddOrder(Buy/Sell to Open/Close, Boolean trigger variable, price, quantity);

      And so, with all of the code we have so far, that translates to:

      AddOrder(OrderType.Buy_To_Open, (longEntries) and fastMA crosses above slowMA, close, quantity);
      AddOrder(OrderType.Sell_To_Close, fastMA crosses below slowMA, close, quantity);
      
      AddOrder(OrderType.Sell_To_Open, (shortEntries) and fastMA crosses below slowMA, close, quantity);
      AddOrder(OrderType.Buy_To_Close, fastMA crosses above slowMA, close, quantity);

      The “Buy to Open” orders are for longs only, in which we’re checking if the fast moving average has crosses above the slow moving average.

      If it does, then we enter the trade at the closing price, with the default quantity.

      We stay in this trade all the way until the fast moving average crosses below the slow moving average.

      Moving Average Crossover Labels

      It would be nice if our backtester told us exactly what moving average crossover scenario we were testing.

      Here’s an example of what the final labels will look like:

      To display that information, we’ll use the Add Label ThinkOrSwim function.

      Each moving average type also has a key identifier associated with it, which will be helpful when we decide what to show on our labels.

      Here’s a helpful breakdown of each AverageType, and it’s identifier value:

      • Simple Moving Average = 0
      • Exponential Moving Average = 1
      • Weighted Moving Average = 2
      • Wilder’s Moving Average = 3
      • Hull Moving Average = 4

      We can use the integer values above to create a boolean test in the labels, which displays the appropriate moving average in text form.

      AddLabel(yes, fastMALength + (if averageTypeFast == 0 then " Simple" else if averageTypeFast == 1 then " EMA" else if averageTypeFast == 2 then " Weighted" else if averageTypeFast == 3 then " Wilders" else " Hull") + " x " + slowMALength + (if averageTypeSlow == 0 then " Simple" else if averageTypeSlow == 1 then " EMA" else if averageTypeSlow == 2 then " Weighted" else if averageTypeSlow == 3 then " Wilders" else " Hull"), color.yellow);

      And with that, our basic backtester is done.

      Testing Moving Average Crossovers on S&P 500

      Let’s use the backtester we’ve built to test different moving average crossover strategies on the S&P 500 (SPY). We’re going to use a daily time frame chart, going back 5 years.

      Using the 8 EMA x 21 EMA crossover on the SPY Daily Chart, we see have a P/L of $5,662:

      8 EMA x 21 EMA Moving Average Crossover on SPY

      Now, let’s go ahead and tweak one variable to test the improvement. Let’s change our slower moving average from 21 to 34. Re-running that, our 8 EMA x 34 EMA crossover leads to a slight decrease in P/L:

      8 EMA x 34 EMA Moving Average Crossover on SPY

      Now, let’s add in one further twist. Let’s change this to a Simple Moving Average, looking at the 8 x 21 fast and slow lengths:

      That P/L increased to $5,654 over the same time period we were measuring:

      8 x 34 Simple Moving Average Crossover on SPY

      And finally, let’s see what happens if we change our slower moving average’s length from 34 to 13. Do we see an increase then?

      8 x 13 Simple Moving Average Crossover on SPY

      Yes we do! In fact, the 8 Simple Moving Average crossing above the 13 Simple Moving Average has been the most effective crossover, from the ones we just tested.

      And just like that, we’ve built a quick, yet useful moving average crossover using just a few lines of thinkScript code.

      downloads

      Download the Santa Claus Rally Backtester for ThinkorSwim.

      The download contains a STRATEGY.ts file, which you can directly import into your ThinkOrSwim platform.

      Download Backtester

      Download the Santa Claus Rally Backtester for ThinkorSwim.

      The download contains a STRATEGY.ts file, which you can directly import into your ThinkOrSwim platform.

      Have your own idea?

      Let us help you turn your trading strategy into a powerful indicator, scan and backtester.