A free Squeeze histogram backtester for ThinkorSwim: it trades the first yellow bar after 7 red, or the first blue after 7 cyan, with an RSI filter.
Download BacktesterIn this tutorial I build a Squeeze histogram backtester for ThinkorSwim to answer one question: is the histogram useful for catching momentum swings? It goes long on the first yellow bar after 7 red histogram bars in a row, goes short on the first blue bar after 7 cyan bars, and exits at the first sign the momentum is fading.
It’s a strategy, so ThinkorSwim totals the profit and loss on whatever chart and timeframe you load. An RSI filter cuts some of the noise, and inputs change the bar count, the RSI levels and which sides it trades. It’s for Squeeze traders who read the histogram and want to test it first.
The Squeeze histogram is the bar plot of ThinkorSwim’s free built-in Squeeze study, and each bar is one of 4 colors. The color comes from two checks: is the bar above or below zero, and is it higher or lower than the bar before it?
The study’s other output, the dots, tells you whether there’s a squeeze. This backtester ignores the dots and works only with the histogram. Our Squeeze hub covers the dots, and the MTF Squeeze indicator shows them for many timeframes at once.
| Color | Zero line | Compared with the previous bar | What it says about momentum |
|---|---|---|---|
| Cyan | Above 0 | Higher | Bullish momentum is increasing |
| Blue | Above 0 | Lower | Bullish momentum is fading |
| Red | Below 0 | Lower | Bearish momentum is increasing |
| Yellow | Below 0 | Higher | Bearish momentum is fading |


A momentum shift on the Squeeze histogram is the first bar of a new color after a long run of one color. The goal of this tutorial is to test whether those turning points are useful for catching momentum swings.
The first example in the video is a bullish trade on the S&P 500. The histogram printed more than 7 red bars, then its first yellow bar, and the backtester bought there. It rode the move as the bars kept rising through zero and sold on the first blue bar, the first sign the momentum was stalling.
The Squeeze histogram backtester has two entry conditions, one bullish and one bearish, and each depends on the run of bars that came before. Every entry and exit uses the closing price of the bar where its condition is true.
| Rule | Long trade | Short trade |
|---|---|---|
| Setup | 7 red bars in a row, ending on the previous bar | 7 cyan bars in a row, ending on the previous bar |
| Trigger bar | Yellow | Blue |
| RSI filter | RSI below 40 (oversold) | RSI above 60 (overbought) |
| Entry | Buy to open at the close | Sell to open at the close |
| Exit | Sell to close at the close of the first bar where the histogram is lower than the bar before | Buy to close at the close of the first bar where the histogram is higher than the bar before |
| On/off switch | longEntries |
shortEntries |
The exit tries to ride the momentum for as long as possible. Read the code closely, though: the long exit fires on the first lower bar of any color. If a yellow bar is followed by a red one, the trade closes there, below zero, without waiting for a blue bar.
The Squeeze histogram backtester is a strategy, so you create it from the Strategies tab. That step is very important: only a strategy can place the simulated orders that produce a profit and loss report.
AddOrder line in the editor while you build, so you don’t get an error right away.The download is the same code as a .ts strategy file. If you’d rather import it, our guide on how to import a file into ThinkorSwim covers the steps.
The Squeeze histogram backtester has 5 inputs. Double-click the backtester on your chart to change any of them from the studies menu instead of editing the code.
input consecutiveHistogramBars = 7;
input RSIOverboughtVal = 60;
input RSIOversoldVal = 40;
input longEntries = yes;
input shortEntries = yes;
The table explains what each one controls.
| Input | Default | What it controls |
|---|---|---|
consecutiveHistogramBars |
7 | How many cyan or red bars in a row come before the shift |
RSIOverboughtVal |
60 | The RSI has to be above this for a short entry |
RSIOversoldVal |
40 | The RSI has to be below this for a long entry |
longEntries |
yes | Turns long trades on or off |
shortEntries |
yes | Turns short trades on or off |
The Squeeze histogram backtester code has 6 parts, written in the order below. I tested each one on the chart before moving on, and the snippets are copied from the download.
The most basic piece is the histogram itself, brought over from the built-in Squeeze study.
def histogram = TTM_Squeeze().histogram;
The name before the dot is the study, and histogram after it picks the plot. One line, and the strategy can read every histogram bar.
Splitting the histogram into its 4 colors makes the rest of the code much easier to write.
#Cyan, Blue, Yellow and Red Histogram Bar Definitions
def cyan = histogram > histogram[1] and histogram > 0;
def blue = histogram < histogram[1] and histogram > 0;
def red = histogram < histogram[1] and histogram < 0;
def yellow = histogram > histogram[1] and histogram < 0;
Each line is the color table above written in thinkScript. histogram[1] is the previous bar’s value.
To test the color definitions, I added a label that prints one of them. On a chart whose last bar was red, it showed 1. After I swapped red for cyan, it showed 0. That’s a test line from the video and isn’t in the download:
AddLabel(yes, red, Color.YELLOW);
Our AddLabel tutorial covers labels in more depth.
The setup needs a run of cyan bars or red bars, and the Sum function counts them.
#Consecutive Momentum Histogram Bars
def minCyanBars = Sum(cyan, consecutiveHistogramBars) >= consecutiveHistogramBars;
def minRedBars = Sum(red, consecutiveHistogramBars) >= consecutiveHistogramBars;
A cyan bar is 1 and any other bar is 0. If the sum over the last 7 bars is at least 7, all 7 were cyan.
I tested it with a yellow chart bubble. The first bubble appeared on the 7th cyan bar in a row, and bubbles kept plotting on every bar after it until the first blue bar.
#Momentum Shifts in Histogram
def momoDown = minCyanBars[1] and blue;
def momoUp = minRedBars[1] and yellow;
momoDown needs 7 cyan bars that were true on the previous bar and a blue bar now. momoUp needs 7 red bars on the previous bar and a yellow bar now.
The bubbles landed on the first bar of the new color, as expected. One of them was a false signal: momentum turned back up about 2 bars later. Filtering signals like that is the next step.
The RSI filter tries to cut false signals by asking whether the market is overbought or oversold. You can use any indicator you like here. I used the built-in RSI to keep things simple.
#RSI
def RSIOverbought = RSI() > RSIOverboughtVal;
def RSIOversold = RSI() < RSIOversoldVal;
RSI() with empty parentheses uses the built-in study’s default settings, a 14-period RSI. The 60 and 40 thresholds sit closer to 50 than the 70 and 30 lines the RSI study draws.
On a daily SPY chart in the video, adding the oversold filter to the bullish shifts removed a few bubbles on the left and quite a few on the right. Then I turned the hard-coded 60, 40 and 7 into inputs.
AddOrder is the function that places the simulated trades, and it’s the bulk of how a backtester works.
AddOrder(OrderType.Buy_To_Open, longEntries and momoUp and RSIOversold, close);
AddOrder(OrderType.Sell_To_Close, histogram < histogram[1], close);
AddOrder(OrderType.Sell_To_Open, shortEntries and momoDown and RSIOverbought, close);
AddOrder(OrderType.Buy_To_Close, histogram > histogram[1], close);
The buy order fires on a bullish shift in oversold territory, and the sell-to-close fires on the first lower histogram bar. The short side mirrors it with sell to open and buy to close.
The longEntries and shortEntries inputs went in last, so you can see which side contributes to the P&L. These AddOrder lines don’t set a trade size, so ThinkorSwim uses the default trade size from Global Strategy Settings.
ThinkorSwim marks each of the backtester’s orders on the chart (pink text in the video) and draws its running profit and loss in a floating P&L graph under the price. If you don’t see the graph, open Global Strategy Settings and turn on Display floating P&L with strategies.
For the trade list, right-click the strategy on the chart and choose Show report. In the video, I checked the most recent trade that way. It was a short opened on a blue bar, the histogram rose on the very next bar, and the report listed it as a loser, as expected.
The report gets harder to use as the sample grows. On 5 days of data it’s manageable, but 30 days gives many more trades to comb through for the win and loss ratio and the average gain and loss.
Then, first on the SPY daily chart and later on a 5-minute chart over 5 days, I changed one thing at a time and read the P&L after each change:
On the SPY daily chart over about 5 years, the short entries caused more of the losses, which makes sense with a bull run inside that window. I read out only P&L totals for the free version, without trade counts, so I don’t compare them here. Before you compare two totals of your own, check how many trades each report holds.
The Squeeze histogram backtester in this tutorial measures P&L, and the member version adds the statistics. Volatility Box members have a version that puts the trade stats in labels on the chart. It’s included with the membership, with a tutorial on how it’s built, as part of the Squeeze Course. The course has a Histogram Pro Backtester module.
| Feature | Free download (this tutorial) | Member version (Squeeze Course) |
|---|---|---|
| Entries and exits | Momentum shifts on the histogram | Momentum shifts on the histogram |
| Long and short switches | Yes | Yes |
| Where the stats are | Floating P&L and Show report | Labels and strategy bubbles on the chart |
| Stats shown | Total P&L, trade list | Winners, losers, average win %, average loss %, average trade duration, per side |
The Squeeze histogram results in the video come from the member version’s labels, which give winners and losers for each side. These are historical results from the charts loaded when I recorded, not a forecast, and most of the samples are small.
| Chart in the video | Period | Long side | Short side |
|---|---|---|---|
| S&P, 5-minute | 30 days | 12 winners, 32 losers | 17 winners, 23 losers |
| S&P, 5-minute | 5 days | Counts not read out | 9 winners, 1 loser |
| SPY, 5-minute | 5 days | 2 winners, 2 losers | 10 winners, 1 loser |
| Apple, 30-minute, extended hours off | 90 days | 4 winners, 4 losers | 1 winner, 10 losers |
On the SPY 5-day run, long trades lasted about 9.5 five-minute bars on average, about 50 minutes per trade. The short trades lasted a little longer, and the average short winner was much greater than the average short loser.
Apple’s 30-minute chart was basically the reverse of the S&P: the short side, the strongest side on the S&P, won 1 trade out of 11. The same rules behave differently on each market and timeframe, so test the one you trade.
The Squeeze histogram backtester is a starting point for research. With the free version, the work is manual: study the reports and look for the common patterns and tendencies. How many winners, how many losers, and is it the long or the short side driving the P&L?
The code has no stop loss. A trade stays open until the histogram turns, so before you trade a histogram shift, decide where your stop goes and size the position so a stop-out costs a small, fixed share of your account.
The Squeeze histogram backtester tests one part of the Squeeze. These free tools cover other parts:
AddOrder.Most problems with the Squeeze histogram backtester come from where it was created or how strict the filters are.
AddOrder lines are in.AddOrder works only in a strategy.#Written by TOS Indicators 2022
#Home of the Volatility Box
#Strategy: TTM Squeeze Histogram Backtester
#Full Tutorial Link: tosindicators.com/backtesters/ttm-squeeze-histogram
#Squeeze Course Link: tosindicators.com/squeeze-course
input consecutiveHistogramBars = 7;
input RSIOverboughtVal = 60;
input RSIOversoldVal = 40;
input longEntries = yes;
input shortEntries = yes;
def histogram = TTM_Squeeze().histogram;
#Cyan, Blue, Yellow and Red Histogram Bar Definitions
def cyan = histogram > histogram[1] and histogram > 0;
def blue = histogram < histogram[1] and histogram > 0;
def red = histogram < histogram[1] and histogram < 0;
def yellow = histogram > histogram[1] and histogram < 0;
#Consecutive Momentum Histogram Bars
def minCyanBars = Sum(cyan, consecutiveHistogramBars) >= consecutiveHistogramBars;
def minRedBars = Sum(red, consecutiveHistogramBars) >= consecutiveHistogramBars;
#Momentum Shifts in Histogram
def momoDown = minCyanBars[1] and blue;
def momoUp = minRedBars[1] and yellow;
#RSI
def RSIOverbought = RSI() > RSIOverboughtVal;
def RSIOversold = RSI() < RSIOversoldVal;
AddOrder(OrderType.Buy_To_Open, longEntries and momoUp and RSIOversold, close);
AddOrder(OrderType.Sell_To_Close, histogram < histogram[1], close);
AddOrder(OrderType.Sell_To_Open, shortEntries and momoDown and RSIOverbought, close);
AddOrder(OrderType.Buy_To_Close, histogram > histogram[1], close);Want the .ts file and the one-click import? Both come with a free account.
Join 40,000+ traders using institutional-grade tools for ThinkOrSwim.
Get the Bundle