Skip to main content

Seasonal Analysis Indicator for ThinkOrSwim (Free Download)

IndicatorFreeBeginner23 min video164 lines of codeAug 1, 2021Updated Oct 21, 2026

Free seasonal analysis indicator for ThinkOrSwim. 12 monthly labels show how often each month closed above its open and its average move.

Seasonal Analysis on a SPY chart in ThinkOrSwim
On this page

Overview

In this tutorial I build a seasonal analysis indicator for ThinkOrSwim from scratch. It adds 12 labels to a daily chart, one per month, and each label shows how often that month closed above its opening price and the average move from the month's first open to its last close.

Seasonality websites usually track a handful of markets and don't show how they calculate. With this indicator you pick the symbol, whether that's a stock, an ETF or a futures contract, and you set the lookback with the chart's time range. It's for traders who want to know a market's seasonal tendency before they look for a trade.

Key Takeaways

  • The Seasonal Analysis indicator is a free ThinkOrSwim study that prints 12 labels, January to December. Each label shows the share of that month's instances that closed above the month's open, and the average open-to-close move.
  • A label turns green when more than 50% of the months closed up and the average move is above 0. Every other label is yellow.
  • The lookback is the chart's time range: in the video, a 5-year daily chart gives 5-year figures and a 20-year chart gives 20-year figures.
  • In the video, Procter & Gamble's July read 100% | 6.05% on a 5-year chart, against 100% | 3.15% for XLP. These are historical readings for the 5 years before the recording.
  • The study runs only on daily and higher charts. An Assert line shows an error message instead of the labels on intraday charts.

What Seasonal Analysis Measures

Seasonal analysis is the study of how a market has behaved during the same part of the calendar in past years. Our Seasonal Analysis indicator works month by month: for every January on the chart, it measures the move from January's first open to January's last close, and it does the same for the other 11 months.

Third-party seasonality websites give similar numbers. The one I show at the start of the video covers the past 20 years, with each month's percentage return and how often it happened. For the S&P 500, it shows September and October as usually slightly bearish.

The shortcoming is that you're limited to the markets the site tracks, and you can't see how it calculates. Building the indicator yourself fixes both: you choose the market and the lookback, and you can read the code.

How to Read the Seasonal Analysis Labels

Each Seasonal Analysis label shows a month code and 2 numbers, for example Jan: 60% | 2.83%. The table breaks down each part.

Part of the labelWhat it meansWhere it comes from in the code
Jan:The monthText in the AddLabel line
60%Share of Januaries on the chart that closed above their opening pricejanUp / janTotalCount
2.83%Average open-to-close move across all of those Januaries, up and downjanAvgDiff
Green labelMore than 50% up months and an average move above 0The color condition in AddLabel
Yellow labelAnything elseThe else branch

The color rule needs both numbers. In the video, Procter & Gamble's January closed up 60% of the time over 5 years, but its average move was negative, so the label stayed yellow: no edge that month. A month at exactly 50% is yellow too, because the test is greater than 0.5.

The labels count every bar loaded on the chart, so the chart's time range is your lookback. In the video, a daily chart set to 5 years shows the past 5 years, and switching it to 20 years turns every label into a 20-year figure.

Examples From the Video

The seasonal analysis examples in the video are historical readings for the symbol and lookback shown. I recorded the video on the first day of August and didn't say the year. Both code files and the download date from 2021, so the 5-year figures most likely cover roughly August 2016 to July 2021, and the 20-year figures roughly August 2001 to July 2021.

SymbolLookbackMonthClosed upAverage moveLabel
S&P 500 (SPY)5 yearsJanuary60%+2.83%Green
S&P 500 (SPY)5 yearsFebruary60%Below 0Yellow
S&P 5005 yearsSeptember, OctoberNot read outNot read outYellow
S&P 50020 yearsOctober65%A little under 1%Green
Procter & Gamble (PG)5 yearsJanuary60%NegativeYellow
Procter & Gamble (PG)5 yearsFebruary40%-3.07%Yellow
Procter & Gamble (PG)5 yearsMarchNot read out+2.32%Green
Procter & Gamble (PG)5 yearsJuly100%+6.05%Green
Procter & Gamble (PG)5 yearsAugust100%+2.83%Green
XLP5 yearsJuly100%+3.15%Green

I don't call out the label colors for Procter & Gamble's February or XLP's July in the video. Those two come from the code's color rule: 40% is below the 50% line, so February is yellow, and 100% with a positive average makes July green.

S&P 500

On the S&P 500 over 5 years, September and October were yellow, in line with the third-party site, while January was green at 60% up and +2.83%. Switching the chart to 20 years turned October green, at 65% up, though its average gain was a little under 1%, which I called nothing too noteworthy.

Procter & Gamble and XLP

Procter & Gamble shows why a single-stock reading is useful, since the third-party site would only give you the consumer staples sector. Over 5 years, January and February were yellow, March turned green at +2.32%, April and May averaged negative moves, and June through September were all green.

To find the strongest summer month, I first looked for months that closed higher 100% of the time, which left July and August. The average move separated them: July averaged +6.05% and August +2.83%. The consumer staples ETF XLP also closed higher in July 100% of the time, with a smaller average of +3.15%, so PG outperformed XLP in July over those 5 years.

I also switched the chart to S&P 500 futures to show that futures symbols work too, without reading out figures.

Sample size is the main caveat. A 5-year chart has about 5 Julys on it, so 100% means every one of those Julys closed higher, and one down July would cut the share by about 20 points. A green label tells you what past months did, and the next one can still go the other way.

How to Add the Seasonal Analysis Indicator to ThinkOrSwim

The Seasonal Analysis indicator is a free download, and you need a free account on our site to get it. To add it to ThinkOrSwim:

  1. Download the .zip and unzip it.
  2. In ThinkOrSwim, click the Studies icon on the chart toolbar and select the Studies tab.
  3. Click Import in the lower left corner, choose TI_SeasonalAnalysisSTUDY.ts and click Open.
  4. Add the study to a daily chart of the symbol you want to study.
  5. Set the chart's time range to the lookback you want, such as 5 years or 20 years. The 12 labels show along the top of the chart.

To build it yourself, click the Studies icon, then Create, name the study (I used TI_SeasonalAnalysis) and follow the steps below. Our guide on how to import a study into ThinkOrSwim covers the import in a video.

How the Seasonal Analysis Code Works

The Seasonal Analysis code is written once for January and then copied for the other 11 months. I planned it that way in the video: every January variable starts with jan, so a find-and-replace turns the block into February, March and so on.

Step 1: Define the Month

This step’s code is free with an account. Create one or sign in.

getMonth() is a ThinkOrSwim function that returns the month number of each bar, and 1 is January, so jan is true on every January bar. To check it, I add a test chart bubble that prints on every January day:

This step’s code is free with an account. Create one or sign in.

The bubbles print a 1 on every day of January and stop on January 29, with February 1 as the next candle. That bubble line is a test from the video and isn't in the download.

Step 2: Record the Month's Open and Close

This step’s code is free with an account. Create one or sign in.

janOpen takes the open on the first January bar, where today is January and yesterday wasn't, and carries that value forward. janClose takes the close on the last January bar, where today is January and the next bar isn't. The [-1] looks one bar ahead.

In the video, test bubbles show the carried open matching the first candle of each January on the SPY chart: 375.31 for one January and 323.54 for another. One January's close came in at 321.73, on its final day.

Step 3: Calculate the Monthly Move

This step’s code is free with an account. Create one or sign in.

On the last bar of January, janDiff is the close minus the open, divided by the open: the month's move as a decimal. Every other bar gets 0. In the video, one January tested at 0.0525, a 5.25% move. AsPercent turns the decimal into a percentage when the label prints it.

The janOpen > 0 check is only in the current download. The video version divides without it.

Step 4: Count Up, Down and Total Months

TotalSum adds a value across every bar on the chart, and only the last bar of each January can add a 1.

This step’s code is free with an account. Create one or sign in.

janUp counts the Januaries that closed above their open, janDown counts those that closed at or below it, and janTotalCount counts all of them.

Step 5: Average the Monthly Moves

This step’s code is free with an account. Create one or sign in.

janDiff is 0 on every bar except each January's last, so TotalSum(janDiff) adds up the January moves. Dividing by the number of Januaries gives the average move.

Step 6: Build the Label

The label prints the month, the up-month share and the average move, with a | divider between them.

This step’s code is free with an account. Create one or sign in.

The label turns green only when the up share is greater than 0.5 and the average move is above 0. Our AddLabel tutorial covers the function in more detail.

Step 7: Block Intraday Charts

Assert stops the study and shows a message when its condition is false. The condition here is a daily or higher aggregation.

This step’s code is free with an account. Create one or sign in.

In the video, the labels disappear on a 1-minute chart, and clicking the icon in the top left of the chart shows the message. I added it because the indicator needs months of data, and you'll often stretch the chart past 5 years.

Copying January to the Other 11 Months

Copying the January block is the fastest way to finish the Seasonal Analysis indicator, because every January variable shares the jan prefix. In the video I do it in the Sublime Text editor:

  1. Copy the January block into a text editor.
  2. Press Ctrl+H and replace jan with feb using Replace All.
  3. Change getMonth() == 1 to getMonth() == 2.
  4. Fix the month code in the label text so it reads Feb:.
  5. Paste the block under January in the study and click Apply.

Repeat the steps for March (3) through December (12). It's busy work, and planning the variable names up front keeps it to a few edits per month.

Video Build vs. Download

The Seasonal Analysis download holds 2 versions of the code. TI_SeasonalAnalysisSTUDY.ts is the current file and the one on the Code tab. The Archive folder holds TI_SeasonalAnalysis_v1.0STUDY.ts, which matches the build in the video.

ItemVideo build (v1.0)Current download
Monthly move(janClose-janOpen)/janOpen on the month's last barSame, only when janOpen is above 0
Up, down and total countsSameSame
Labels and color ruleSameSame
Assert for daily chartsFirst line of the file (I write it last in the video)First line of the file
Test chart bubblesUsed while building, not in the fileNot included

The extra check avoids a division by 0 when a month has no recorded open. In that case the month adds 0 to the average and still counts in the total, as a down month.

What the Seasonal Numbers Leave Out

The Seasonal Analysis indicator measures one thing per month, and a few details follow from the code:

  • Open to close only. The move runs from the month's first open to its last close, so a gap between the previous month's close and the new month's open isn't counted.
  • Flat months count as down. janDown uses less than or equal to 0, so a month that closes exactly at its open is a down month.
  • Every month weighs the same. The average is a plain mean, so one large month can pull it up or down.

How I Use Seasonal Analysis

Seasonal analysis gives me a bias to check against a trade signal. In the video, recorded on the first day of August, I suggest pulling up August on your favorite markets to see what the month has looked like on average, then layering your bullish or bearish signals on top, short side included.

What I'm looking for is a signal that gives a bias one way or the other, with a seasonal tendency that supports it. For Volatility Box members, Edge Signals or any of the indicators in our Squeeze Course make good starting points. Without a membership, you can use the upper V-Score indicator with its signals, or our Simple Breakout Tool.

A green July label describes past Julys. Decide where your stop goes before you enter, and size the position so a stop-out costs a small, fixed share of your account.

More Seasonal Tools

Our other calendar-based tools include the Santa Claus Rally backtester, the Super Bowl indicator and the election backtester. For further reading, our research articles on stock market seasonality patterns and July's seasonal analysis in the S&P 500 are good next stops.

Seasonal analysis on real data

Drawn from real daily bars with the code on this page, through Oct 20, 2026. Each label is one calendar month: the share of those months that closed above their first open, and the average move from the first open to the last close. A label is green when more than half closed up and the average is above 0. The lookback is the chart's time range; I used SPY and 13 other ETFs from January 2016 to September 2026, the full months in our data. October 2026 was still running on October 20, so it is left out.

SPY's twelve labels

Seasonal Analysis indicator on a SPY daily chart in ThinkOrSwim: 12 monthly labels with the share of up months and the average move, January 2016 to September 2026
SPY, 129 complete months. Green: more than half up and a positive average.
MonthYearsClosed upAverage moveLabel
Jan1164%+1.48%Green
Feb1155%-0.31%Yellow
Mar1155%-0.82%Yellow
Apr1173%+2.33%Green
May1191%+1.95%Green
Jun1164%+1.16%Green
Jul11100%+3.11%Green
Aug1173%+1.19%Green
Sep1155%-1.28%Yellow
Oct1050%+0.55%Yellow
Nov1080%+3.79%Green
Dec1070%-0.58%Yellow

October 2026 so far: SPY opened the month at $764.36 and closed Oct 20, 2026 at $769.05, +0.6%.

Did the labels hold up?

A seasonal label describes the past. To see how much it says about the next year, I built every label from 2016 to 2021 only, then checked the months from January 2022 to September 2026 on all 14 ETFs.

387Months with a green 2016-2021 label
60.2%Of those, closed up
52.1%Yellow-label months that closed up
56.0%All months that closed up

SPY, QQQ, IWM, DIA and 10 sector ETFs, 798 months from January 2022 to September 2026. Across the full ten years, the strongest single label was XLRE in Jul (100% up, +3.14% average) and the weakest XLRE in Sep (27% up, -3.83%). Ten or eleven years per month is a small sample; use the labels as context for a setup, not as the setup.

Full thinkScript code

Seasonal Analysis.tsthinkScript · 164 lines
The full code is free with an account

Create a free account or sign in to copy all 164 lines and download the file.

Download

Seasonal Analysis Indicator

Download the Seasonal Analysis indicator for ThinkOrSwim. The download is a .zip with TI_SeasonalAnalysisSTUDY.ts, the current version and the same code as the Code tab, and an Archive folder with TI_SeasonalAnalysis_v1.0STUDY.ts, the version built in the video. Import the current .ts file as a study and use it on a daily chart. If you haven't added a custom study before, here's how to import an indicator into your ThinkOrSwim platform.

Download free

Video: Seasonal Analysis, built line by line

23 minutes on YouTube.

Watch on YouTube

Questions

What is seasonal analysis in trading?

Seasonal analysis looks at how a market has behaved during the same part of the calendar in past years, such as every July. Our indicator measures each month's move from its first open to its last close, then shows how often that move was positive and its average size across the years loaded on your chart.

How do I add the Seasonal Analysis indicator to ThinkOrSwim?

Download the free .zip and unzip it. In ThinkOrSwim, click the Studies icon, select the Studies tab, click Import, choose TI_SeasonalAnalysisSTUDY.ts and click Open. Add the study to a daily chart and set the chart's time range to the lookback you want, such as 5 or 20 years.

What do the two numbers on each label mean?

The first number is the share of that month's instances on the chart that closed above the month's opening price. The second is the average move from the month's first open to its last close. In the video, the S&P 500 read 60% and 2.83% for January on a 5-year chart.

Why is a seasonal analysis label green or yellow?

A label is green when more than 50% of that month's instances closed up and the average move is above 0. Anything else is yellow, including a month at exactly 50%. In the video, Procter & Gamble's January closed up 60% of the time over 5 years, but its average move was negative, so it stayed yellow.

How do I change how many years the indicator covers?

Change the chart's time range. The labels count every bar loaded on the chart, so a 5-year daily chart gives 5-year figures and a 20-year chart gives 20-year figures. In the video, switching the S&P 500 from 5 to 20 years turned October green, at 65% up with an average gain a little under 1%.

Why doesn't the seasonal analysis indicator show on my intraday chart?

The code starts with an Assert line that only lets the study run on daily or higher charts. On an intraday chart, such as 1 minute, the labels disappear. Click the icon in the top left of the chart to see the message: the indicator only works on daily time frame and higher charts.

Does the seasonal analysis indicator work on stocks, ETFs and futures?

Yes. It works on any symbol with enough daily history. In the video I run it on the S&P 500, Procter & Gamble, the consumer staples ETF XLP and S&P 500 futures. Procter & Gamble closed July higher 100% of the time over 5 years, with an average move of 6.05%, against 3.15% for XLP.

How should I use seasonal analysis in my trading?

Use it as a bias. A green month tells you what past months did, and the next one can go either way. In the video I suggest layering a bullish or bearish signal on top, such as Edge Signals, a squeeze indicator, the V-Score or the Simple Breakout Tool. Decide your stop before you enter.

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.