Skip to main contentSkip to content
Squeeze Setups A live squeeze scanner built around the Slingshot Squeeze, with 22 years of backtest results behind it. See it, $99/month →
TOS Indicators
  • Tools

    Categories

    • Indicators
    • Backtesters
    • Scans
    • Dashboards
    • thinkScript
    • Member Resources
    Browse Full Library

    Featured Tutorials

    Fair Value Gap Indicator
    Fair Value Gap Indicator
    Indicators

    Download our free Fair Value Gap indicator for ThinkorSwim. It shades every...

    Learn more →
    False Breakout Scanner for ThinkorSwim (Free Scan Pair)
    False Breakout Scanner for ThinkorSwim (Free Scan Pair)
    Scans

    A two-scan pair that flags failed breakouts and failed breakdowns with volume...

    Learn more →
    Heiken Ashi Trend Indicator
    Heiken Ashi Trend Indicator
    Indicators

    Download our Custom Heiken Ashi indicator for ThinkOrSwim. Full ThinkScript code, formula...

    Learn more →

    Popular Posts

    Unusual Volume Pro Scans
    Unusual Volume Pro Scans
    Scans

    4 additional scans to find unusual volume overlapping with key...

    Learn more →
    High Short Interest Near 52 Week Highs (thinkScript)
    High Short Interest Near 52 Week Highs (thinkScript)
    Scans

    Build a simple scan in which to find stocks that...

    Learn more →
    Upcoming Earnings with High Short Interest
    Upcoming Earnings with High Short Interest
    Scans

    Build a scan to find stocks that are likely to...

    Learn more →
  • Courses
    Squeeze Course
    Squeeze Course
    19 Modules

    Scan, backtest, and trade the TTM Squeeze setup with precision.

    View Course →
    Earnings Course
    Earnings Course
    3 Modules

    Master earnings plays with free indicators and proven strategies for ThinkOrSwim.

    View Course →
    V-Shaped Reversals
    V-Shaped Reversals
    7 Modules

    Identify and trade powerful V-shaped reversal patterns with confidence and precision.

    View Course →
    Fibonacci Trading
    Fibonacci Trading
    4 Modules

    Learn to trade Fibonacci retracements and extensions in ThinkOrSwim effectively.

    View Course →
  • Products
    Futures Volatility Box Premium
    Futures Volatility Box

    Volatility models for 10 major futures markets, including the micros.

    Explore Futures VB →
    Stock Volatility Box Premium
    Stock Volatility Box

    Volatility levels for 600+ stocks and ETFs, with a live scanner.

    Explore Stock VB →
    Opening Range Breakouts Premium
    Opening Range Breakouts

    Scans 600+ stocks every 2 seconds for opening range breaks, with a backtester.

    Explore ORB Setups →
My Account
All Indicators Part of TTM Squeeze →
Beginner 21 minutes ThinkOrSwim

Multi-Time Frame (MTF) Squeeze Indicator for ThinkorSwim

A free multi-time frame (MTF) Squeeze indicator for ThinkorSwim: 18 labels from monthly to 1-minute turn red when that timeframe is in a squeeze.

Download Indicator
How to install in ThinkOrSwim →
Table of Contents
  • Key Takeaways
  • What the MTF Squeeze Indicator Shows
  • Free Download vs the Member Version
  • Why the Indicator Rebuilds the Squeeze From Source Code
  • The Multi-Time Frame Framework
  • How to Build It in ThinkorSwim
  • How the Code Works
  • Testing It Against the Built-in Squeeze
  • How to Read the Labels
  • Customizing and Stacking the Labels
  • Common Problems

In this tutorial I build a multi-time frame (MTF) Squeeze indicator for ThinkorSwim that shows the default Squeeze on every timeframe at a glance. Each timeframe gets a label on the chart, from M for monthly down to 1m, red when that timeframe is in a squeeze and dark green when it isn’t.

The indicator rebuilds the Squeeze from Bollinger Band and Keltner Channel source code, because the built-in function tends to give false squeeze signals inside multi-time frame code. It’s for Squeeze traders who check several timeframes, and for anyone who wants a reusable multi-time frame template in thinkScript.

Key Takeaways

  • The MTF Squeeze indicator for ThinkorSwim plots 18 labels, from monthly (M) down to 1-minute (1m). A label is red when that timeframe is in a squeeze and dark green when it isn’t.
  • It uses ThinkorSwim’s default Squeeze settings: length 20, nK 1.5, nBB 2 and an alert line of 1.
  • A timeframe is in a squeeze when nBB times the standard deviation, divided by nK times the average true range, is below 1. That means the Bollinger Bands are inside the Keltner Channels.
  • The indicator rebuilds the Squeeze from source code because the built-in Squeeze function tends to give false squeeze signals inside an if/else block that uses aggregation periods.
  • ThinkorSwim can only read timeframes equal to or higher than your chart’s, so on a 5-minute chart the 1m to 4m labels don’t appear.

What the MTF Squeeze Indicator Shows

The MTF Squeeze indicator is a row of chart labels, one per timeframe, that shows whether each timeframe is in a squeeze. It saves you from flipping through charts: one glance tells you which timeframes have the default squeeze.

Each label is red when that timeframe has a squeeze and dark green when it doesn’t. The free download covers 18 timeframes. The table lists each label with the aggregation period and the period string the code uses for it.

Label Timeframe Aggregation check Price period
M Monthly AggregationPeriod.Month period="Month"
W Weekly AggregationPeriod.week period="week"
4D 4-day AggregationPeriod.four_days period="4 days"
3D 3-day AggregationPeriod.three_days period="3 days"
2D 2-day AggregationPeriod.two_days period="2 days"
D Daily AggregationPeriod.day period="day"
4h 4-hour AggregationPeriod.four_hours period="4 Hours"
2h 2-hour AggregationPeriod.two_hours period="2 Hours"
1h 1-hour AggregationPeriod.hour period="1 Hour"
30m 30-minute AggregationPeriod.Thirty_Min period="30 Min"
20m 20-minute AggregationPeriod.Twenty_Min period="20 Min"
15m 15-minute AggregationPeriod.Fifteen_Min period="15 Min"
10m 10-minute AggregationPeriod.Ten_Min period="10 Min"
5m 5-minute AggregationPeriod.Five_Min period="5 Min"
4m 4-minute AggregationPeriod.Four_Min period="4 Min"
3m 3-minute AggregationPeriod.Three_Min period="3 Min"
2m 2-minute AggregationPeriod.Two_Min period="2 Min"
1m 1-minute AggregationPeriod.Min period="1 Min"

Labels only appear for timeframes equal to or higher than your chart’s. ThinkorSwim can read higher timeframes from the chart you’re on, but nothing lower. On a 5-minute chart you’ll see 5m and up, and you won’t know whether the 1, 2, 3 or 4-minute charts have a squeeze. The higher labels also need enough candle data on your chart to calculate.

Free Download vs the Member Version

The free MTF Squeeze indicator tracks one compression, the default mid compression that the built-in Squeeze uses. Volatility Box members have a second version in Module 9 of the Squeeze Course that takes the code one step further.

Feature Free download (built in this tutorial) Member version (Module 9)
Compressions 1, the default mid compression 3, fast, mid and slow
Squeeze colors Red Gray for fast, red for mid, yellow for slow
No squeeze Dark green Black in the video
Changing colors Edit the AddLabel lines Pick colors in the studies menu

In the video, I changed the member version’s no-squeeze color from black to dark green in the studies menu, clicked Apply, and every label without a squeeze turned dark green. Colors you can set from the menu also help if you’re colorblind.

Why the Indicator Rebuilds the Squeeze From Source Code

The built-in Squeeze function in ThinkorSwim tends to give false squeeze signals inside multi-time frame code. That happens when you reference it inside an if/else block that also uses aggregation periods. It doesn’t return the squeeze alert value you’d expect, so the indicator doesn’t work the way you want.

ThinkorSwim’s own documentation describes the Squeeze as a measure of two indicators and their relationship: the Bollinger Bands and the Keltner Channels. The squeeze is on when the Bollinger Bands fall inside the Keltner Channels. The Squeeze’s source code is hidden, but the Bollinger Band and Keltner Channel source code isn’t, so you can rebuild it from those pieces.

To speed things along, I used code that a member sent us from another thinkScript author’s Blogspot, which recreates the Squeeze. If you use that code, leave the author’s credit and Blogspot link in your header. Our download keeps it on the first lines.

The math comes down to one ratio. Bollinger Band width is a multiple of the standard deviation (nBB), and Keltner Channel width is a multiple of the average true range (nK). The code divides the first by the second and compares the result with the alert line of 1. Below 1, the Bollinger Bands are inside the Keltner Channels, and that timeframe is in a squeeze.

The Multi-Time Frame Framework

The MTF Squeeze is built on the drag-and-drop multi-time frame module from our MTF DMI indicator. Because that code was written to be reusable, I could lift the same framework and drop in the squeeze math. You could do the same for EMA crossovers or your own set of indicators.

  • Declare first. Each timeframe’s variables are declared above its if/else block.
  • Check the timeframe. The if tests whether the chart’s aggregation period is less than or equal to the timeframe you want to measure, then assigns the values.
  • Default to 0. The else sets every variable to 0 to keep things simple.
  • Prefix every name. Each variable starts with its timeframe, like monthPrice, so a find-and-replace turns the monthly block into the weekly one.

If you only need one higher timeframe, such as a daily level on an intraday chart, our tutorial on plotting higher time frame indicators with aggregation periods covers that simpler case.

How to Build It in ThinkorSwim

Building the MTF Squeeze takes one monthly block of code, then a copy of that block for every other timeframe.

  1. Click the Studies icon, click Create, and give the study a name.
  2. Add a header comment that credits the Blogspot source code with its link.
  3. Define the global variables: length, alert line, nK, nBB and the rest.
  4. Write the monthly block: the declarations, the if/else, and 2 labels.
  5. Click Apply, then check the M label against the built-in Squeeze on a monthly chart.
  6. Copy the block into a text editor, replace month with the next timeframe, fix the label text and the period string, and paste it back. Repeat down to 1 minute.

If you’d rather start from the finished file, download it and follow our guide on how to import a study into ThinkorSwim.

How the Code Works

The MTF Squeeze code is a short global section followed by 18 blocks that each follow the same pattern. Once you understand the monthly block, you understand all of them.

Step 1: Global Variables

Global variables are the settings that don’t change from one timeframe to the next. These are the default values of the built-in Squeeze.

##Global Variables
def length = 20;
def AlertLine = 1;
def nk = 1.5;
def nBB = 2;
def averageTpype = AverageType.SIMPLE;
def displace = 0;
def trueRangeAverageType = AverageType.SIMPLE;

To change the compression, change nk here. To set a value from the studies menu, change its def to input.

I renamed the source’s nBB factor to nBB so the code is easier to copy and paste later.

Step 2: Declare the Monthly Variables

Each monthly variable is declared once, above the if/else, so both branches can assign it.

def monthPrice;
def monthATR;
def monthSDev;
def monthDenom;
def monthBBSInd;
def monthSqueeze;
def monthAggregationPeriod;

The download adds monthAggregationPeriod, a flag that hides the label on charts that can’t read the monthly timeframe.

Step 3: The Monthly If Block

The if block checks that the chart is monthly or lower, then calculates the squeeze on monthly bars. Inside it, you assign values without def, because the variables already exist.

if GetAggregationPeriod() <= AggregationPeriod.Month {
    monthPrice = close(period="Month");
    monthATR = Average(TrueRange(high (period="Month"), close(period="Month"), low(period="Month")), Length);
    monthSDev = stdev(monthPrice, Length);
    monthDenom = (nK*monthATR);
    monthBBSInd = if (monthDenom <> 0, ((nBB * monthSDev) /monthDenom), 0);
    monthSqueeze = if monthBBSInd < AlertLine then 1 else 0;
    monthAggregationPeriod = 1;
}

period="Month" makes each price read monthly bars.

The source code called an average true range function, and ThinkorSwim reported no such function. My workaround was to break it out as the average of TrueRange over the length, with the monthly high, close and low.

Step 4: The Else Block

The else block gives every monthly variable a value of 0. Until each one has a value here, ThinkorSwim shows an error.

else {
    monthPrice = 0;
    monthATR = 0;
    monthSDev = 0;
    monthDenom = 0;
    monthBBSInd = 0;
    monthSqueeze = 0;
    monthAggregationPeriod = 0;
}

With that done, one error is left: at least one plot should be defined. The labels fix that.

Step 5: The Labels

AddLabel takes a condition, the text and a color. One label shows when there’s a monthly squeeze and the other shows when there isn’t.

AddLabel(monthSqueeze and monthAggregationPeriod, "M", color.red);
AddLabel(!monthSqueeze and monthAggregationPeriod,"M", color.dark_green);

In the video I used black for no squeeze. Dark green, which the download uses, is easier to see on a black chart background.

Our AddLabel tutorial covers static and dynamic labels in more depth.

Step 6: Copy the Block to Every Timeframe

Copying the monthly block is where the naming pays off. I paste it into Sublime Text, press Ctrl+H, and replace month with week.

Then change the label text from “M” to “W”. That’s the one piece that’s easy to forget: in the video, my weekly label showed up as a second M until I fixed it.

Some timeframes need a different period string. The aggregation check uses AggregationPeriod.four_days, but inside period I use “4 days” with the number.

if GetAggregationPeriod() <= AggregationPeriod.four_days {
    four_daysPrice = close(period="4 days");

That’s how ThinkorSwim’s own examples write it, and it tends to work without glitches. I’ve had problems with other spellings there in the past.

Testing It Against the Built-in Squeeze

The MTF Squeeze labels should match the built-in Squeeze study on each timeframe’s own chart, so I checked them that way as I built them in the video.

  • Monthly, no squeeze. The first chart had no monthly squeeze, and the built-in Squeeze on the monthly chart agreed. Coca-Cola had no monthly squeeze either.
  • Monthly squeeze. On a symbol that did have a monthly squeeze, the M label turned red.
  • Weekly. After I added the weekly block, that chart showed a squeeze on the monthly and none on the weekly.
  • 4-day. The 4D label showed no squeeze, and the 4-day chart confirmed it.

How to Read the Labels

A red MTF Squeeze label tells you that timeframe is in a squeeze with the default settings. It doesn’t tell you which way price breaks or when. Open the red timeframes and read the setup on those charts.

Before you trade a squeeze, decide where your stop goes and size the position so a stop-out costs a small, fixed share of your account. To find squeezes across many stocks instead of one chart, use our free Squeeze dots scan or build a Squeeze dashboard for a watchlist.

Customizing and Stacking the Labels

The MTF Squeeze code is straightforward to extend because every timeframe block has the same shape.

Add the Fast and Slow Compressions

  • More compressions. Add an nK factor 2 and an nK factor 3, then repeat the squeeze code with a 2 and a 3 version of each variable.
  • Inputs instead of fixed values. Change any global def to input to set it from the studies menu.
  • Your own colors. Change color.red and color.dark_green in the AddLabel lines.

Stack Them With the MTF Squeeze Histogram

Volatility Box members who also have the MTF Squeeze Histogram indicator, from Module 8 of the Squeeze Course, can load both indicators, and the two sets of labels plot side by side. The fastest way I know to stack them is to click the hamburger menu on the chart, choose Detach, and shrink the window until the two sets of labels stack on top of one another.

If that makes the chart too skinny, a flex grid gives you more control over how wide each chart is.

Common Problems

Most MTF Squeeze problems come from the chart’s timeframe, a missing value in the else block, or a label name.

  • Lower-timeframe labels are missing. That’s by design. On a 5-minute chart, labels below 5 minutes can’t calculate. Drop to a 1-minute chart to see all 18, and load enough days of data for the higher timeframes to calculate.
  • “At least one plot should be defined.” The study needs output. Add the AddLabel lines.
  • “No such function” on the ATR line. Use Average(TrueRange(high, close, low), length) with the timeframe’s period on each price.
  • Two labels with the same letter. You copied a block and didn’t change the label text.
  • Errors on variables in a new block. Every declared variable needs a value in both the if and the else.

For other errors, our tutorial on how to debug thinkScript code walks through tracking them down.

MTF Squeeze.ts
#TOS Indicators
#Home of the Volatility Box
#Indicator Name: Multi-Time Frame Squeeze 
#Full Tutorial Available Here: www.tosindicators.com/indicators/mtf-squeeze
#Contains TTM_Squeeze Source Code, found here (http://freethinkscript.blogspot.com/2009/05/ttm-like-squeeze-indicator-with.html)



##Global Variables
def length = 20;
def AlertLine = 1;
def nk = 1.5;
def nBB = 2;
def averageTpype = AverageType.SIMPLE;
def displace = 0;
def trueRangeAverageType = AverageType.SIMPLE;

## Month Aggregation Period Variables

def monthPrice;
def monthATR;
def monthSDev;
def monthDenom;
def monthBBSInd;
def monthSqueeze;
def monthAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Month {
    monthPrice = close(period="Month");
    monthATR = Average(TrueRange(high (period="Month"), close(period="Month"), low(period="Month")), Length);
    monthSDev = stdev(monthPrice, Length);
    monthDenom = (nK*monthATR);
    monthBBSInd = if (monthDenom <> 0, ((nBB * monthSDev) /monthDenom), 0);
    monthSqueeze = if monthBBSInd < AlertLine then 1 else 0;
    monthAggregationPeriod = 1;
}
else {
    monthPrice = 0;
    monthATR = 0;
    monthSDev = 0;
    monthDenom = 0;
    monthBBSInd = 0;
    monthSqueeze = 0;
    monthAggregationPeriod = 0;
}
AddLabel(monthSqueeze and monthAggregationPeriod, "M", color.red);
AddLabel(!monthSqueeze and monthAggregationPeriod,"M", color.dark_green);

def weekPrice;
def weekATR;
def weekSDev;
def weekDenom;
def weekBBSInd;
def weekSqueeze;
def WeekAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.week {
    weekPrice = close(period="week");
    weekATR = Average(TrueRange(high (period="week"), close(period="week"), low(period="week")), Length);
    weekSDev = stdev(weekPrice, Length);
    weekDenom = (nK*weekATR);
    weekBBSInd = if (weekDenom <> 0, ((nBB * weekSDev) /weekDenom), 0);
    weekSqueeze = if weekBBSInd < AlertLine then 1 else 0;
    WeekAggregationPeriod = 1;
}
else {
    weekPrice = 0;
    weekATR = 0;
    weekSDev = 0;
    weekDenom = 0;
    weekBBSInd = 0;
    weekSqueeze = 0;
    WeekAggregationPeriod = 0;
}
AddLabel(weekSqueeze and WeekAggregationPeriod, "W", color.red);
AddLabel(!weekSqueeze and WeekAggregationPeriod,"W", color.dark_green);


def four_daysPrice;
def four_daysATR;
def four_daysSDev;
def four_daysDenom;
def four_daysBBSInd;
def four_daysSqueeze;
def FourDayAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.four_days {
    four_daysPrice = close(period="4 days");
    four_daysATR = Average(TrueRange(high (period="4 days"), close(period="4 days"), low(period="4 days")), Length);
    four_daysSDev = stdev(four_daysPrice, Length);
    four_daysDenom = (nK*four_daysATR);
    four_daysBBSInd = if (four_daysDenom <> 0, ((nBB * four_daysSDev) /four_daysDenom), 0);
    four_daysSqueeze = if four_daysBBSInd < AlertLine then 1 else 0;
    FourDayAggregationPeriod = 1;
}
else {
    four_daysPrice = 0;
    four_daysATR = 0;
    four_daysSDev = 0;
    four_daysDenom = 0;
    four_daysBBSInd = 0;
    four_daysSqueeze = 0;
    FourDayAggregationPeriod = 0;
}
AddLabel(four_daysSqueeze and FourDayAggregationPeriod, "4D", color.red);
AddLabel(!four_daysSqueeze and FourDayAggregationPeriod,"4D", color.dark_green);

def three_daysPrice;
def three_daysATR;
def three_daysSDev;
def three_daysDenom;
def three_daysBBSInd;
def three_daysSqueeze;
def ThreeDayMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.three_days {
    three_daysPrice = close(period="3 days");
    three_daysATR = Average(TrueRange(high (period="3 days"), close(period="3 days"), low(period="3 days")), Length);
    three_daysSDev = stdev(three_daysPrice, Length);
    three_daysDenom = (nK*three_daysATR);
    three_daysBBSInd = if (three_daysDenom <> 0, ((nBB * three_daysSDev) /three_daysDenom), 0);
    three_daysSqueeze = if three_daysBBSInd < AlertLine then 1 else 0;
    ThreeDayMinAggregationPeriod = 1;
}
else {
    three_daysPrice = 0;
    three_daysATR = 0;
    three_daysSDev = 0;
    three_daysDenom = 0;
    three_daysBBSInd = 0;
    three_daysSqueeze = 0;
    ThreeDayMinAggregationPeriod = 0;
}
AddLabel(three_daysSqueeze and ThreeDayMinAggregationPeriod, "3D", color.red);
AddLabel(!three_daysSqueeze and ThreeDayMinAggregationPeriod,"3D", color.dark_green);


def two_daysPrice;
def two_daysATR;
def two_daysSDev;
def two_daysDenom;
def two_daysBBSInd;
def two_daysSqueeze;
def TwoDayMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.two_days {
    two_daysPrice = close(period="2 days");
    two_daysATR = Average(TrueRange(high (period="2 days"), close(period="2 days"), low(period="2 days")), Length);
    two_daysSDev = stdev(two_daysPrice, Length);
    two_daysDenom = (nK*two_daysATR);
    two_daysBBSInd = if (two_daysDenom <> 0, ((nBB * two_daysSDev) /two_daysDenom), 0);
    two_daysSqueeze = if two_daysBBSInd < AlertLine then 1 else 0;
    TwoDayMinAggregationPeriod = 1;
}
else {
    two_daysPrice = 0;
    two_daysATR = 0;
    two_daysSDev = 0;
    two_daysDenom = 0;
    two_daysBBSInd = 0;
    two_daysSqueeze = 0;
    TwoDayMinAggregationPeriod = 0;
}
AddLabel(two_daysSqueeze and TwoDayMinAggregationPeriod, "2D", color.red);
AddLabel(!two_daysSqueeze and TwoDayMinAggregationPeriod,"2D", color.dark_green);

def dayPrice;
def dayATR;
def daySDev;
def dayDenom;
def dayBBSInd;
def daySqueeze;
def DayMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.day {
    dayPrice = close(period="day");
    dayATR = Average(TrueRange(high (period="day"), close(period="day"), low(period="day")), Length);
    daySDev = stdev(dayPrice, Length);
    dayDenom = (nK*dayATR);
    dayBBSInd = if (dayDenom <> 0, ((nBB * daySDev) /dayDenom), 0);
    daySqueeze = if dayBBSInd < AlertLine then 1 else 0;
    DayMinAggregationPeriod = 1;
}
else {
    dayPrice = 0;
    dayATR = 0;
    daySDev = 0;
    dayDenom = 0;
    dayBBSInd = 0;
    daySqueeze = 0;
    DayMinAggregationPeriod = 0;
}
AddLabel(daySqueeze and DayMinAggregationPeriod, "D", color.red);
AddLabel(!daySqueeze and DayMinAggregationPeriod,"D", color.dark_green);

def four_hoursPrice;
def four_hoursATR;
def four_hoursSDev;
def four_hoursDenom;
def four_hoursBBSInd;
def four_hoursSqueeze;
def FourHourMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.four_hours {
    four_hoursPrice = close(period="4 Hours");
    four_hoursATR = Average(TrueRange(high (period="4 Hours"), close(period="4 Hours"), low(period="4 Hours")), Length);
    four_hoursSDev = stdev(four_hoursPrice, Length);
    four_hoursDenom = (nK*four_hoursATR);
    four_hoursBBSInd = if (four_hoursDenom <> 0, ((nBB * four_hoursSDev) /four_hoursDenom), 0);
    four_hoursSqueeze = if four_hoursBBSInd < AlertLine then 1 else 0;
    FourHourMinAggregationPeriod = 1;
}
else {
    four_hoursPrice = 0;
    four_hoursATR = 0;
    four_hoursSDev = 0;
    four_hoursDenom = 0;
    four_hoursBBSInd = 0;
    four_hoursSqueeze = 0;
    FourHourMinAggregationPeriod = 0;
}
AddLabel(four_hoursSqueeze and FourHourMinAggregationPeriod, "4h", color.red);
AddLabel(!four_hoursSqueeze and FourHourMinAggregationPeriod,"4h", color.dark_green);

def two_hoursPrice;
def two_hoursATR;
def two_hoursSDev;
def two_hoursDenom;
def two_hoursBBSInd;
def two_hoursSqueeze;
def TwoHourMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.two_hours {
    two_hoursPrice = close(period="2 Hours");
    two_hoursATR = Average(TrueRange(high (period="2 Hours"), close(period="2 Hours"), low(period="2 Hours")), Length);
    two_hoursSDev = stdev(two_hoursPrice, Length);
    two_hoursDenom = (nK*two_hoursATR);
    two_hoursBBSInd = if (two_hoursDenom <> 0, ((nBB * two_hoursSDev) /two_hoursDenom), 0);
    two_hoursSqueeze = if two_hoursBBSInd < AlertLine then 1 else 0;
    TwoHourMinAggregationPeriod = 1;
}
else {
    two_hoursPrice = 0;
    two_hoursATR = 0;
    two_hoursSDev = 0;
    two_hoursDenom = 0;
    two_hoursBBSInd = 0;
    two_hoursSqueeze = 0;
    TwoHourMinAggregationPeriod = 0;
}
AddLabel(two_hoursSqueeze and TwoHourMinAggregationPeriod, "2h", color.red);
AddLabel(!two_hoursSqueeze and TwoHourMinAggregationPeriod,"2h", color.dark_green);

def hourPrice;
def hourATR;
def hourSDev;
def hourDenom;
def hourBBSInd;
def hourSqueeze;
def HourMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.hour {
    hourPrice = close(period="1 Hour");
    hourATR = Average(TrueRange(high (period="1 Hour"), close(period="1 Hour"), low(period="1 Hour")), Length);
    hourSDev = stdev(hourPrice, Length);
    hourDenom = (nK*hourATR);
    hourBBSInd = if (hourDenom <> 0, ((nBB * hourSDev) /hourDenom), 0);
    hourSqueeze = if hourBBSInd < AlertLine then 1 else 0;
    HourMinAggregationPeriod = 1;
}
else {
    hourPrice = 0;
    hourATR = 0;
    hourSDev = 0;
    hourDenom = 0;
    hourBBSInd = 0;
    hourSqueeze = 0;
    HourMinAggregationPeriod = 0;
}
AddLabel(hourSqueeze and HourMinAggregationPeriod, "1h", color.red);
AddLabel(!hourSqueeze and HourMinAggregationPeriod,"1h", color.dark_green);

def Thirty_MinPrice;
def Thirty_MinATR;
def Thirty_MinSDev;
def Thirty_MinDenom;
def Thirty_MinBBSInd;
def Thirty_MinSqueeze;
def ThirtyMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Thirty_Min {
    Thirty_MinPrice = close(period="30 Min");
    Thirty_MinATR = Average(TrueRange(high (period="30 Min"), close(period="30 Min"), low(period="30 Min")), Length);
    Thirty_MinSDev = stdev(Thirty_MinPrice, Length);
    Thirty_MinDenom = (nK*Thirty_MinATR);
    Thirty_MinBBSInd = if (Thirty_MinDenom <> 0, ((nBB * Thirty_MinSDev) /Thirty_MinDenom), 0);
    Thirty_MinSqueeze = if Thirty_MinBBSInd < AlertLine then 1 else 0;
    ThirtyMinAggregationPeriod = 1;
}
else {
    Thirty_MinPrice = 0;
    Thirty_MinATR = 0;
    Thirty_MinSDev = 0;
    Thirty_MinDenom = 0;
    Thirty_MinBBSInd = 0;
    Thirty_MinSqueeze = 0;
    ThirtyMinAggregationPeriod = 0;
}
AddLabel(Thirty_MinSqueeze and ThirtyMinAggregationPeriod, "30m", color.red);
AddLabel(!Thirty_MinSqueeze and ThirtyMinAggregationPeriod,"30m", color.dark_green);

def Twenty_MinPrice;
def Twenty_MinATR;
def Twenty_MinSDev;
def Twenty_MinDenom;
def Twenty_MinBBSInd;
def Twenty_MinSqueeze;
def TwentyMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Twenty_Min {
    Twenty_MinPrice = close(period="20 Min");
    Twenty_MinATR = Average(TrueRange(high (period="20 Min"), close(period="20 Min"), low(period="20 Min")), Length);
    Twenty_MinSDev = stdev(Twenty_MinPrice, Length);
    Twenty_MinDenom = (nK*Twenty_MinATR);
    Twenty_MinBBSInd = if (Twenty_MinDenom <> 0, ((nBB * Twenty_MinSDev) /Twenty_MinDenom), 0);
    Twenty_MinSqueeze = if Twenty_MinBBSInd < AlertLine then 1 else 0;
    TwentyMinAggregationPeriod = 1;
}
else {
    Twenty_MinPrice = 0;
    Twenty_MinATR = 0;
    Twenty_MinSDev = 0;
    Twenty_MinDenom = 0;
    Twenty_MinBBSInd = 0;
    Twenty_MinSqueeze = 0;
    TwentyMinAggregationPeriod = 0;
}
AddLabel(Twenty_MinSqueeze and TwentyMinAggregationPeriod, "20m", color.red);
AddLabel(!Twenty_MinSqueeze and TwentyMinAggregationPeriod,"20m", color.dark_green);


def Fifteen_MinPrice;
def Fifteen_MinATR;
def Fifteen_MinSDev;
def Fifteen_MinDenom;
def Fifteen_MinBBSInd;
def Fifteen_MinSqueeze;
def FifteenMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Fifteen_Min {
    Fifteen_MinPrice = close(period="15 Min");
    Fifteen_MinATR = Average(TrueRange(high (period="15 Min"), close(period="15 Min"), low(period="15 Min")), Length);
    Fifteen_MinSDev = stdev(Fifteen_MinPrice, Length);
    Fifteen_MinDenom = (nK*Fifteen_MinATR);
    Fifteen_MinBBSInd = if (Fifteen_MinDenom <> 0, ((nBB * Fifteen_MinSDev) /Fifteen_MinDenom), 0);
    Fifteen_MinSqueeze = if Fifteen_MinBBSInd < AlertLine then 1 else 0;
    FifteenMinAggregationPeriod = 1;
}
else {
    Fifteen_MinPrice = 0;
    Fifteen_MinATR = 0;
    Fifteen_MinSDev = 0;
    Fifteen_MinDenom = 0;
    Fifteen_MinBBSInd = 0;
    Fifteen_MinSqueeze = 0;
    FifteenMinAggregationPeriod = 0;
}
AddLabel(Fifteen_MinSqueeze and FifteenMinAggregationPeriod, "15m", color.red);
AddLabel(!Fifteen_MinSqueeze and FifteenMinAggregationPeriod,"15m", color.dark_green);

def Ten_MinPrice;
def Ten_MinATR;
def Ten_MinSDev;
def Ten_MinDenom;
def Ten_MinBBSInd;
def Ten_MinSqueeze;
def TenMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Ten_Min {
    Ten_MinPrice = close(period="10 Min");
    Ten_MinATR = Average(TrueRange(high (period="10 Min"), close(period="10 Min"), low(period="10 Min")), Length);
    Ten_MinSDev = stdev(Ten_MinPrice, Length);
    Ten_MinDenom = (nK*Ten_MinATR);
    Ten_MinBBSInd = if (Ten_MinDenom <> 0, ((nBB * Ten_MinSDev) /Ten_MinDenom), 0);
    Ten_MinSqueeze = if Ten_MinBBSInd < AlertLine then 1 else 0;
    TenMinAggregationPeriod = 1;
}
else {
    Ten_MinPrice = 0;
    Ten_MinATR = 0;
    Ten_MinSDev = 0;
    Ten_MinDenom = 0;
    Ten_MinBBSInd = 0;
    Ten_MinSqueeze = 0;
    TenMinAggregationPeriod = 0;
}
AddLabel(Ten_MinSqueeze and TenMinAggregationPeriod, "10m", color.red);
AddLabel(!Ten_MinSqueeze and TenMinAggregationPeriod,"10m", color.dark_green);


def Five_MinPrice;
def Five_MinATR;
def Five_MinSDev;
def Five_MinDenom;
def Five_MinBBSInd;
def Five_MinSqueeze;
def FiveMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Five_Min {
    Five_MinPrice = close(period="5 Min");
    Five_MinATR = Average(TrueRange(high (period="5 Min"), close(period="5 Min"), low(period="5 Min")), Length);
    Five_MinSDev = stdev(Five_MinPrice, Length);
    Five_MinDenom = (nK*Five_MinATR);
    Five_MinBBSInd = if (Five_MinDenom <> 0, ((nBB * Five_MinSDev) /Five_MinDenom), 0);
    Five_MinSqueeze = if Five_MinBBSInd < AlertLine then 1 else 0;
    FiveMinAggregationPeriod = 1;
}
else {
    Five_MinPrice = 0;
    Five_MinATR = 0;
    Five_MinSDev = 0;
    Five_MinDenom = 0;
    Five_MinBBSInd = 0;
    Five_MinSqueeze = 0;
    FiveMinAggregationPeriod = 0;
}
AddLabel(Five_MinSqueeze and FiveMinAggregationPeriod, "5m", color.red);
AddLabel(!Five_MinSqueeze and FiveMinAggregationPeriod,"5m", color.dark_green);


def Four_MinPrice;
def Four_MinATR;
def Four_MinSDev;
def Four_MinDenom;
def Four_MinBBSInd;
def Four_MinSqueeze;
def FourMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Four_Min {
    Four_MinPrice = close(period="4 Min");
    Four_MinATR = Average(TrueRange(high (period="4 Min"), close(period="4 Min"), low(period="4 Min")), Length);
    Four_MinSDev = stdev(Four_MinPrice, Length);
    Four_MinDenom = (nK*Four_MinATR);
    Four_MinBBSInd = if (Four_MinDenom <> 0, ((nBB * Four_MinSDev) /Four_MinDenom), 0);
    Four_MinSqueeze = if Four_MinBBSInd < AlertLine then 1 else 0;
    FourMinAggregationPeriod = 1;
}
else {
    Four_MinPrice = 0;
    Four_MinATR = 0;
    Four_MinSDev = 0;
    Four_MinDenom = 0;
    Four_MinBBSInd = 0;
    Four_MinSqueeze = 0;
    FourMinAggregationPeriod = 0;
}
AddLabel(Four_MinSqueeze and FourMinAggregationPeriod, "4m", color.red);
AddLabel(!Four_MinSqueeze and FourMinAggregationPeriod,"4m", color.dark_green);


def Three_MinPrice;
def Three_MinATR;
def Three_MinSDev;
def Three_MinDenom;
def Three_MinBBSInd;
def Three_MinSqueeze;
def ThreeMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Three_Min {
    Three_MinPrice = close(period="3 Min");
    Three_MinATR = Average(TrueRange(high (period="3 Min"), close(period="3 Min"), low(period="3 Min")), Length);
    Three_MinSDev = stdev(Three_MinPrice, Length);
    Three_MinDenom = (nK*Three_MinATR);
    Three_MinBBSInd = if (Three_MinDenom <> 0, ((nBB * Three_MinSDev) /Three_MinDenom), 0);
    Three_MinSqueeze = if Three_MinBBSInd < AlertLine then 1 else 0;
    ThreeMinAggregationPeriod = 1;
}
else {
    Three_MinPrice = 0;
    Three_MinATR = 0;
    Three_MinSDev = 0;
    Three_MinDenom = 0;
    Three_MinBBSInd = 0;
    Three_MinSqueeze = 0;
    ThreeMinAggregationPeriod = 0;
}
AddLabel(Three_MinSqueeze and ThreeMinAggregationPeriod, "3m", color.red);
AddLabel(!Three_MinSqueeze and ThreeMinAggregationPeriod,"3m", color.dark_green);


def Two_MinPrice;
def Two_MinATR;
def Two_MinSDev;
def Two_MinDenom;
def Two_MinBBSInd;
def Two_MinSqueeze;
def twoMinAggregationPeriod;
if GetAggregationPeriod() <= AggregationPeriod.Two_Min {
    Two_MinPrice = close(period="2 Min");
    Two_MinATR = Average(TrueRange(high (period="2 Min"), close(period="2 Min"), low(period="2 Min")), Length);
    Two_MinSDev = stdev(Two_MinPrice, Length);
    Two_MinDenom = (nK*Two_MinATR);
    Two_MinBBSInd = if (Two_MinDenom <> 0, ((nBB * Two_MinSDev) /Two_MinDenom), 0);
    Two_MinSqueeze = if Two_MinBBSInd < AlertLine then 1 else 0;
    twoMinAggregationPeriod = 1;
}
else {
    Two_MinPrice = 0;
    Two_MinATR = 0;
    Two_MinSDev = 0;
    Two_MinDenom = 0;
    Two_MinBBSInd = 0;
    Two_MinSqueeze = 0;
    twoMinAggregationPeriod = 0;
}
AddLabel(Two_MinSqueeze and twoMinAggregationPeriod, "2m", color.red);
AddLabel(!Two_MinSqueeze and twoMinAggregationPeriod,"2m", color.dark_green);


def One_MinPrice;
def One_MinATR;
def One_MinSDev;
def One_MinDenom;
def One_MinBBSInd;
def One_MinSqueeze;
def minAggregationPeriod;

if GetAggregationPeriod() <= AggregationPeriod.Min {
    One_MinPrice = close(period="1 Min");
    One_MinATR = Average(TrueRange(high (period="1 Min"), close(period="1 Min"), low(period="1 Min")), Length);
    One_MinSDev = stdev(One_MinPrice, Length);
    One_MinDenom = (nK*One_MinATR);
    One_MinBBSInd = if (One_MinDenom <> 0, ((nBB * One_MinSDev) /One_MinDenom), 0);
    One_MinSqueeze = if One_MinBBSInd < AlertLine then 1 else 0;
    minAggregationPeriod = 1;
}
else {
    One_MinPrice = 0;
    One_MinATR = 0;
    One_MinSDev = 0;
    One_MinDenom = 0;
    One_MinBBSInd = 0;
    One_MinSqueeze = 0;
    minAggregationPeriod = 0;
}
AddLabel(One_MinSqueeze and minAggregationPeriod, "1m", color.red);
AddLabel(!One_MinSqueeze and minAggregationPeriod,"1m", color.dark_green);

Want the .ts file and the one-click import? Both come with a free account.

Create Free Account Login
It shows whether the Squeeze is on for several timeframes at once, so you don't have to flip through charts. Our free MTF Squeeze indicator plots 18 labels on a ThinkorSwim chart, from monthly down to 1-minute. Each label is red when that timeframe is in a squeeze and dark green when it isn't.
Download the study file or copy the code from the Code tab. In ThinkorSwim, click the Studies icon, click Create, paste in the code, name the study, and click OK, then Apply. The labels appear on the chart. Use a 1-minute chart to see all 18 timeframes, with enough candle data loaded for the higher ones to calculate.
ThinkorSwim can only read timeframes equal to or higher than the chart you're on. On a 5-minute chart, the indicator shows 5m and every higher timeframe, and it hides the 1, 2, 3 and 4-minute labels. Higher timeframes also need enough candle data on the chart to calculate.
When you reference the built-in Squeeze function inside an if/else block that uses aggregation periods, it tends to give false squeeze signals because it doesn't return the squeeze alert you'd expect. The MTF Squeeze rebuilds the squeeze from standard deviation and average true range instead, which works the way you'd expect.
It uses the built-in Squeeze defaults: a length of 20, an nK factor of 1.5, an nBB factor of 2 and an alert line of 1. They're global variables at the top of the code. Change def to input on any of them if you want to set it from the studies menu.
For each timeframe it multiplies the standard deviation of the close by nBB and the average true range by nK, then divides the first by the second. If the result is below the alert line of 1, the Bollinger Bands are inside the Keltner Channels, and that timeframe's label turns red.
Add an nK factor 2 and an nK factor 3 to the global variables, then repeat the squeeze code with a 2 and a 3 version of each variable. Volatility Box members have a finished version in Module 9 of the Squeeze Course, with all 3 compressions and label colors you can change in the studies menu.
Yes. The framework comes from our MTF DMI indicator and works for things like EMA crossovers or your own indicators. Declare the variables above an if/else, check the aggregation period in the if, set everything to 0 in the else, and prefix each name with its timeframe so find-and-replace builds the next block.

Here are some resources that you may find useful:

  • How to import an indicator into ThinkOrSwim (video tutorial)
  • Multi Time Frame (MTF) DMI indicator
  • Aggregation periods: plot higher time frame indicators on lower time frames
  • Squeeze dots scan for ThinkOrSwim
  • The Squeeze Course (members)
Featured Tools:
Stock Volatility Box

Stock Volatility Box

Spot reversal zones across 600 stocks & ETFs.

  • Hourly & daily models
  • Powerful Live Scanner
  • Built for day traders
Futures Volatility Box

Futures Volatility Box

Pinpoint reversal zones in 10 major futures markets.

  • 5 models (incl. Scalper)
  • ThinkOrSwim & TradingView
  • SPX traders
ORB Setups

ORB Setups

Find the best Opening Range Breakout setups.

  • Powerful real-time scanner
  • Instant backtests
  • 2+ years data

Get Free Access

Create a free account for downloads and new tutorial alerts.

Create Free Account

Related tutorials and research

Aggregation Periods in thinkScript: Plot a Daily Indicator on an Intraday Chart

Aggregation Periods in thinkScript: Plot a Daily Indicator on an Intraday Chart

thinkScript · Beginner
How to Build a Fair Value Gap Indicator in ThinkorSwim

How to Build a Fair Value Gap Indicator in ThinkorSwim

Indicators · Beginner-Friendly
How to Build a Heiken Ashi Trend Dots Indicator in ThinkorSwim

How to Build a Heiken Ashi Trend Dots Indicator in ThinkorSwim

Indicators · Beginner-Friendly
How to Build a Commodities Tracker in ThinkorSwim

How to Build a Commodities Tracker in ThinkorSwim

Indicators · Beginner
Trend Squeeze Indicator for ThinkorSwim

Trend Squeeze Indicator for ThinkorSwim

Indicators · Beginner
Volume Intensity Indicator for ThinkorSwim

Volume Intensity Indicator for ThinkorSwim

Indicators · Beginner-Friendly

Ready to Trade With an Edge?

Join 40,000+ traders using institutional-grade tools for ThinkOrSwim.

Get the Bundle
TOS Indicators

Premium thinkorswim indicators, scans, and trading tools to help you trade smarter.

ThinkOrSwim Tools

  • Indicators
  • Scans
  • Backtesters
  • Dashboards
  • thinkScript
  • Browse All

Courses

  • Squeeze Course
  • Earnings Course
  • V-Shaped Reversals
  • Fibonacci Trading

Products

  • Futures Volatility Box
  • Stock Volatility Box
  • ORB Setups
  • Shop All

Guides

  • TTM Squeeze
  • Automated Trading
  • Volatility Trading
  • Opening Range Breakouts
  • Trade Reports
  • Contact Us

© 2026 TOS Indicators. All rights reserved.

Privacy Policy Terms of Service Disclaimer Accessibility

The information contained on this website is solely for educational purposes, and does not constitute investment advice. The risk of trading in securities markets can be substantial. You must review and agree to our Terms of Service prior to using this site.

U.S. Government Required Disclaimer - Commodity Futures Trading Commission. Futures and options trading has large potential rewards, but also large potential risk. You must be aware of the risks and be willing to accept them in order to invest in the futures and options markets. Don't trade with money you can't afford to lose. This website is neither a solicitation nor an offer to Buy/Sell futures or options. No representation is being made that any account will or is likely to achieve profits or losses similar to those discussed on this website. The past performance of any trading system or methodology is not necessarily indicative of future results.

Individual results may vary, and testimonials are not claimed to represent typical results. All testimonials are by real people, and may not reflect the typical purchaser's experience, and are not intended to represent or guarantee that anyone will achieve the same or similar results.

TOS Indicator's Traders and employees will NEVER manage or offer to manage a customer or individual's options, stocks, currencies, futures, or any financial markets or securities account. If someone claiming to represent or be associated with TOS Indicator solicits you for money or offers to manage your trading account, do not provide any personal information and contact us immediately.

CFTC RULE 4.41 - HYPOTHETICAL OR SIMULATED PERFORMANCE RESULTS HAVE CERTAIN LIMITATIONS. UNLIKE AN ACTUAL PERFORMANCE RECORD, SIMULATED RESULTS DO NOT REPRESENT ACTUAL TRADING. ALSO, SINCE THE TRADES HAVE NOT BEEN EXECUTED, THE RESULTS MAY HAVE UNDER-OR-OVER COMPENSATED FOR THE IMPACT, IF ANY, OF CERTAIN MARKET FACTORS, SUCH AS LACK OF LIQUIDITY, SIMULATED TRADING PROGRAMS IN GENERAL ARE ALSO SUBJECT TO THE FACT THAT THEY ARE DESIGNED WITH THE BENEFIT OF HINDSIGHT. NO REPRESENTATION IS BEING MADE THAT ANY ACCOUNT WILL OR IS LIKELY TO ACHIEVE PROFIT OR LOSSES SIMILAR TO THOSE SHOWN.