Download our free Fair Value Gap indicator for ThinkorSwim. It shades every open bull and bear gap as a cloud, filters out narrow gaps with an ATR setting, and clears each gap once price fills it.
Download IndicatorIn this tutorial I build a fair value gap indicator for ThinkorSwim from scratch, one line of thinkScript at a time. It finds the three-candle gaps price leaves behind on a fast move, shades each one as a cloud on your chart, and takes the cloud off once price fills the gap.
Before writing any code, I measured more than 116,000 fair value gaps across 650 stocks and ETFs and 11 years of daily bars. Half of them were completely filled within 5 trading days. Gaps at least half an ATR wide held on their first retest about 9 times out of 10, so the indicator has an ATR filter built in that keeps only the wider gaps.
A fair value gap is a three-candle pattern. Candle 2 moves so fast that candle 1’s high and candle 3’s low don’t overlap. The empty space between them is the fair value gap. Price ran through that area so quickly that almost nothing traded there.
The bearish version is the same pattern flipped upside down. Candle 3’s high sits below candle 1’s low, and candle 2 is the fast candle again.
In thinkScript, candle 3 is the current bar and candle 1 is 2 bars back, so each side comes down to one comparison:
low > high[2].high < low[2].You’ve probably heard of fair value gaps, but you may not have seen numbers put to them. I tested more than 116,000 of them across 650 liquid stocks and ETFs, using 11 years of daily bars.
The retest is the normal path for a fair value gap, so you can plan for it. Size matters too. Narrow gaps are mostly noise, which is why the indicator includes a filter that keeps the wider ones.

The indicator tracks the most recent bull gap and the most recent bear gap. When a new gap forms on the same side, it takes over from the previous one.
TI_FairValueGap.If you’ve never added a custom study before, our walkthrough on how to import a ThinkScript study into ThinkorSwim covers the same steps with screenshots.
Both inputs show up in the study settings, so you can change them without touching the code.
| Input | Default | What it controls |
|---|---|---|
minGap |
0.5 | The minimum gap width, as a multiple of ATR. Raise it to see only wide gaps, lower it to see more of them. |
ATRLength |
14 | The number of bars in the ATR average that minGap is measured against. |
In the video I raised minGap from 0.5 to 1 to see only the really wide gaps, then dropped it to 0.1 to see almost every gap on the chart. Try a few values on the symbols you trade and keep the threshold you like.
To tell a wide gap from a narrow one, I use ATR as the yardstick. ThinkorSwim has a built-in ATR function you could call instead. I wrote it out with Average and TrueRange so you can see what it calculates, and pulled the length out as an input.
input minGap = 0.5;
input ATRLength = 14;
def ATR = Average(TrueRange(high, close, low), ATRLength);
Each gap has two conditions. The candles can’t overlap, and the size of the gap, found by subtracting the two prices, has to be at least minGap times the ATR. With the default of 0.5, a gap has to be at least 50% of the ATR on your current timeframe.
def bullGap = low > high[2] and (low - high[2]) >= minGap * ATR;
def bearGap = high < low[2] and (low[2]- high) >= minGap * ATR;
While building this, I used AddChartBubble to check that each gap showed up where I expected it. That line is still at the bottom of the code, commented out, if you want to use it to debug your own changes.
ThinkorSwim doesn’t have arrays, so I declare bullTop and bullBottom first and set them inside an if/else block. On the bar where a bull gap forms, the top is the current low and the bottom is the high from 2 bars ago. On every bar after that, each variable takes its value from the previous bar, which carries the gap forward.
def bullTop;
def bullBottom;
if (bullGap) {
bullTop = low;
bullBottom = high[2];
} else if low <= bullBottom[1]{
bullTop = Double.nan;
bullBottom = Double.nan;
}
else {
bullTop = bullTop[1];
bullBottom = bullBottom[1];
};
The middle branch clears the gap. Once the low trades down to the bottom of the bull gap, both values reset to Double.nan and the cloud comes off the chart. The bear side mirrors this: the top is the low from 2 bars ago, the bottom is the current high, and the gap clears once the high reaches the top.
If you plot bullTop directly, you get one continuous line that connects each gap to the next. To fix that, the plot skips the bar where a gap forms by returning Double.nan on that bar, so each new gap starts fresh.
plot BullGapTop = if bullGap then Double.nan else BullTop;
plot BullGapBottom = if bullGap then Double.nan else BullBottom ;
plot BearGapTop = if BearGap then Double.nan else BearTop;
plot BearGapBottom = if BearGap then Double.nan else BearBottom;
From there, AddCloud shades the space between each top and bottom. The code also sets each line to cyan or orange with a line weight of 2 so the edges are easy to see.
AddCloud(BullGapTop, BullGapBottom, color.cyan, color.cyan);
AddCloud(BearGapTop, BearGapBottom, color.orange, color.orange);
This is what I look for on the Bank of America daily chart from the video.
I use the gaps as levels to plan around. The data shows price comes back to them often, and the indicator shows you exactly where they sit, so line up the retest with the rest of your setup before you take a trade.
Pro members get a Part 2 of this tutorial. In it, I build on this indicator to create a scan that finds stocks the moment they come back to test an open fair value gap. We also build a dashboard script for the scan tab that shows each stock’s fair value gap levels, color coded so they’re quick to read.
Educational content only. Nothing here is a trade recommendation or investment advice.
input minGap = 0.5;
input ATRLength = 14;
def ATR = Average(TrueRange(high, close, low), ATRLength);
#bull gaps
#bear gaps
def bullGap = low > high[2] and (low – high[2]) >= minGap * ATR;
def bearGap = high < low[2] and (low[2]- high) >= minGap * ATR;
def bullTop;
def bullBottom;
if (bullGap) {
bullTop = low;
bullBottom = high[2];
} else if low <= bullBottom[1]{
bullTop = Double.nan;
bullBottom = Double.nan;
}
else {
bullTop = bullTop[1];
bullBottom = bullBottom[1];
};
def bearTop;
def bearBottom;
if (bearGap) {
bearTop = low[2];
bearBottom = high;
} else if (high >= bearTop[1]){
bearTop = Double.nan;
bearBottom = Double.nan;
}
else {
bearTop = bearTop[1];
bearBottom = bearBottom[1];
};
plot BullGapTop = if bullGap then Double.nan else BullTop;
plot BullGapBottom = if bullGap then Double.nan else BullBottom ;
plot BearGapTop = if BearGap then Double.nan else BearTop;
plot BearGapBottom = if BearGap then Double.nan else BearBottom;
BullGapTop.setDefaultColor(color.cyan);
BullGapBottom.setDefaultColor(color.cyan);
BearGapTop.setDefaultColor(color.orange);
BearGapBottom.setDefaultColor(color.orange);
AddCloud(BullGapTop, BullGapBottom, color.cyan, color.cyan);
AddCloud(BearGapTop, BearGapBottom, color.orange, color.orange);
BullGapTop.setLineWeight(2);
BullGapBottom.setLineWeight(2);
BearGapTop.setLineWeight(2);
BearGapBottom.setLineWeight(2);
#AddChartBubble(bullGap, close, bullGap, color.yellow);Want the .ts file and the one-click import? Both come with a free account.
Here are some resources that you may find useful:
Join 40,000+ traders using institutional-grade tools for ThinkOrSwim.
Get the Bundle