Four free V-Score scans for ThinkorSwim find stocks pulling back to VWAP: two strict scans with trend and CCI filters, and two wide scans without them.
Download ScanIn this tutorial I build 4 V-Score scans for ThinkorSwim from our free VScore indicator. Each one finds stocks reverting to the mean, the volume weighted average price, from above or from below. Two are strict scans that also check the trend clouds and the CCI. The other two are wide scans that only check the V-Score.
Most of the work is cutting the indicator code down until the scan engine accepts it, and I make every change on camera. If you want to know what the V-Score measures first, start with the VScore indicator tutorial. This page is for traders who already use it and want ThinkorSwim to find the pullbacks for them.
The V-Score scans look for reversion to the mean: stocks that traded above or below VWAP and are now heading back to it. The V-Score is the number of standard deviations between the close and VWAP. Our VScore indicator page covers how it’s calculated and what the clouds mean.
The scans come in two strengths. The strict pair, which I call the signal scans, also requires a trend and a CCI reading. The wide pair casts a bigger net.
| Scan | V-Score on the previous bar | V-Score now | Trend cloud | CCI | In the video |
|---|---|---|---|---|---|
| Bull signal | Above 0 | 0.3 or below | Winning zone above 0 | Above -100 | Many results, including Amazon |
| Bear signal | Below 0 | 0.3 or above | Winning zone below 0 | Below 100 | No results on a down day |
| Wide bull | Above 0 | 0.3 or below | Not checked | Not checked | 47 results, including Nike |
| Wide bear | Below 0 | -0.3 or above | Not checked | Not checked | 3 results: EDIT, TZA and VXX |
Because the code keeps the clouds, the signal scans can use the most recent trend to decide which pullbacks to look at: to the 0 line, the 1, the 2 and so on.
The VScore indicator code throws an error when you paste it into a custom scan filter, because it’s too complex for the scan engine. A scan doesn’t draw anything on a chart, so the drawing code can go:
AddCloud lineAddLabel lineThat leaves one error: “Exactly one plot expected.” The indicator has a plot for the V-Score, each level line, the two 0.3 lines and both signals. A scan filter needs exactly one, so every plot except the signal you want becomes a def.
The bullish signal scan takes the full VScore code and 7 steps. Add the VScore indicator to a chart first. If you don’t have it, our guide on how to import a study into ThinkorSwim shows how to add the download.
AddCloud and AddLabel.Double.nan to 0, so it returns true or false.The finished signal line reads:
plot BullSignal = if (cloud1 == one or cloud2 == one or cloud1 == two or cloud2 == two or cloud1 == three or cloud2 ==three) and (VScore <= 0.3 and Vscore[1] >0) and CCI() > -100 then 1 else 0;
It checks 4 things. The winning cloud is above 0, so the V-Score spent most of the lookback above the mean.
The V-Score is now 0.3 or below, close to the mean, and on the previous bar it was above 0, so price came from above. The CCI is above -100, so there isn’t a ton of selling pressure. In the video the lookback was 120 bars. The indicator download defaults barsGoBack to 90, so change it to 120 to match the video.
To check the results, I opened Amazon on a daily chart. The V-Score had stayed above the mean, and a bullish arrow plotted on that day’s bar.
The bearish signal scan is the same code with the two signals swapped. Click the edit icon on the scan, change BullSignal to def and BearSignal to plot, and change the 2 to 1 and Double.nan to 0.
plot BearSignal = if (cloud1 == negOne or cloud2 == negOne or cloud1 == negTwo or cloud2 == negTwo or cloud1 == negThree or cloud2 ==negThree) and (VScore >= 0.3 and Vscore[1] < 0) and CCI() < 100 then 1 else 0;
It checks that the winning cloud is below 0 and the V-Score was below 0 on the previous bar.
The CCI has to be below 100 at its default settings, so there isn’t heavy buying pressure. Save it as a second scan query.
The bearish V-Score test in the indicator code reads 0.3 or above, so it needs the score to move from below 0 to 0.3 or higher. When I built the wide bearish scan later in the video, I set that test to -0.3 or above instead, to catch any move back within 0.3 below the mean. Make the same change here if you want the two scans to match.
When I ran the bearish signal scan, it returned no results. The market was down a little more than 2.5% that day, and a day like that is unlikely to offer pullback entries to go short.
The wide V-Score scans drop the cloud and CCI checks and keep only the reversion test. To build the bullish one, copy the BullSignal line into a new plot named signal, change BullSignal and BearSignal to def, and delete the cloud and CCI conditions. Written out from the video, the line reads:
plot signal = if (VScore <= 0.3 and Vscore[1] > 0) then 1 else 0;
If the V-Score was above the mean and is now 0.3 or below, the scan returns true.
In the video it returned 47 results. Nike was one: the V-Score had been above 0 on the previous bar and pulled back to the mean, and the chart also showed a bullish arrow.
The wide bearish scan flips the test. I first forgot to change the 0.3, then set it to -0.3, because I care about shorting anywhere above that level. Written out from the video:
plot signal = if (VScore >= -0.3 and Vscore[1] < 0) then 1 else 0;
It returned 3 results: EDIT, TZA and VXX.
On EDIT, the V-Score had been below the mean and had pulled back to it, sitting slightly above 0, at a point that would otherwise count as an entry.
The VScore price label shows the price at any standard deviation level, which most z-score indicators leave you to guess. Right-click the study on the chart, choose Edit study, and change the devStop input. Here’s what the label showed in the video:
| Stock | Scan | devStop | Label price | How I used it |
|---|---|---|---|---|
| Nike | Wide bull | Zero (0) | $80.83 | The mean, a level to park a long order |
| EDIT | Wide bear | NegOne (-1) | $21.39 | Target for a short, at a level that held as support before |
| EDIT | Wide bear | One (1) | $28.52 | Stop for the same short |
On Nike you might guess price was close to the mean. The label shows the exact price, so if a pullback to 0 is your condition to go long, you can have your order parked there ahead of time. On EDIT, the -1 and 1 labels gave the target and the stop before the trade.
The V-Score scans build a watchlist of stocks pulling back toward their mean. Open each result on a chart, as I do with Amazon, Nike and EDIT in the video, and check that the clouds and arrows match what the scan says. Then use the price label to see the levels around price before deciding anything.
A scan hit is a read on a pullback, not an entry by itself. Set the stop and the target from the label first, and size the position so a stop-out costs a small, fixed share of your account. On a day like the 2.5% drop in the video, the strict bearish scan can come back empty.
Our upper VScore has its own one-line scan that calls the study by name, covered on the VScore indicator page. For another way to scan around VWAP, try our anchored VWAP scans.
The V-Score scans download is a .txt file with 4 ThinkorSwim shared links: Bull Signal and Bear Signal (more filter criteria), and Wide Bull Scan and Wide Bear Signal (less filter criteria). To load one:
The shared links are the original scans. The Code tab on this page shows the bullish signal scan rebuilt from the steps in the video, starting from the Lower VScore download, plus notes for the other 3. For the same custom filter steps on a simpler scan, see our increasing volume scans, and browse more on the scans page.
# V-Score Bullish Signal Scan for ThinkOrSwim
# TOS Indicators
# Full tutorial: tosindicators.com/scans/vscore-scans
#
# Rebuilt from the steps in the video: the Lower VScore study download
# (tosindicators.com/indicators/vscore) with the alerts, painting strategies, AddCloud and
# AddLabel removed, every plot except BullSignal changed to def, and BullSignal returning 1 or 0.
# The video used barsGoBack = 120. The indicator download defaults to 90.
# The original 4 scans are the ThinkOrSwim shared links (tos.mx) in this page's download.
declare lower;
input anchorDate = 20210101;
input barsGoBack = 90;
input showStopLabel = yes;
input devStop = {default One, Two, Three, Zero, NegOne, NegTwo, NegThree, Custom};
input customDev = 0.15;
def chosenDev;
switch(devStop){
case One:
chosenDev = 1;
case Two:
chosenDev = 2;
case Three:
chosenDev = 3;
case Zero:
chosenDev = 0;
case NegOne:
chosenDev = -1;
case NegTwo:
chosenDev = -2;
case NegThree:
chosenDev = -3;
case Custom:
chosenDev = customDev;
}
def postAnchorDate = if GetYYYYMMDD() >= anchorDate then 1 else 0;
def yyyyMmDd = getYyyyMmDd();
def periodIndx = if getAggregationPeriod() < AggregationPeriod.HOUR then yyyyMMDD else postAnchorDate;
def isPeriodRolled = compoundValue(1, periodIndx != periodIndx[1], yes);
def volumeSum;
def volumeVwapSum;
def volumeVwap2Sum;
if (isPeriodRolled) {
volumeSum = volume;
volumeVwapSum = volume * vwap;
volumeVwap2Sum = volume * Sqr(vwap);
} else {
volumeSum = compoundValue(1, volumeSum[1] + volume, volume);
volumeVwapSum = compoundValue(1, volumeVwapSum[1] + volume * vwap, volume * vwap);
volumeVwap2Sum = compoundValue(1, volumeVwap2Sum[1] + volume * Sqr(vwap), volume * Sqr(vwap));
}
def price = volumeVwapSum / volumeSum;
def deviation = Sqrt(Max(volumeVwap2Sum / volumeSum - Sqr(price), 0));
def VScore = if (((price - close)*(-1))/deviation) > 5 or (((price - close)*(-1))/deviation) < -5 then 0 else (((price - close)*(-1))/(deviation));
def zero = 0;
def one = 1;
def two = 2;
def three = 3;
def negOne = -1;
def negTwo = -2;
def negThree = -3;
def posInt = 0.3;
def negInt = -0.3;
def stopPrice = (chosenDev)*(deviation) + (price);
def zeroAndOne = if VScore > zero and VScore <= one then 1 else 0;
def oneAndTwo = if VScore > one and VScore <= two then 1 else 0;
def twoAndThree = if VScore > two and VScore <= three then 1 else 0;
def negZeroAndOne = if VScore > negOne and VScore < zero then 1 else 0;
def negOneAndTwo = if VScore > negTwo and VScore <= negOne then 1 else 0;
def negTwoAndThree = if VScore > negThree and VScore <= negTwo then 1 else 0;
def cloud1;
def cloud2;
if Sum(zeroAndOne, barsGoBack) > Sum(OneAndTwo,barsGoBack) and Sum(zeroAndOne, barsGoBack) > Sum(twoAndThree,barsGoBack) and Sum(zeroAndOne, barsGoBack) > Sum(negZeroAndOne,barsGoBack) and Sum(zeroAndOne, barsGoBack) > Sum(negoneAndTwo,barsGoBack) and Sum(zeroAndOne, barsGoBack) > Sum(negtwoAndThree,barsGoBack){
cloud1 = zero;
cloud2 = one;
}
else if Sum(OneAndTwo, barsGoBack) > Sum(zeroAndOne,barsGoBack) and Sum(OneAndTwo, barsGoBack) > Sum(twoAndThree,barsGoBack) and Sum(OneAndTwo, barsGoBack) > Sum(negZeroAndOne,barsGoBack) and Sum(OneAndTwo, barsGoBack) > Sum(negoneAndTwo,barsGoBack) and Sum(OneAndTwo, barsGoBack) > Sum(negtwoAndThree,barsGoBack){
cloud1 = one;
cloud2 = two;
}
else if Sum(twoAndThree, barsGoBack) > Sum(zeroAndOne,barsGoBack) and Sum(twoAndThree, barsGoBack) > Sum(oneAndTwo,barsGoBack) and Sum(twoAndThree, barsGoBack) > Sum(negZeroAndOne,barsGoBack) and Sum(twoAndThree, barsGoBack) > Sum(negoneAndTwo,barsGoBack) and Sum(twoAndThree, barsGoBack) > Sum(negtwoAndThree,barsGoBack){
cloud1 = two;
cloud2 = three;
}
else if Sum(negZeroAndOne, barsGoBack) > Sum(zeroAndOne,barsGoBack) and Sum(negZeroAndOne, barsGoBack) > Sum(oneAndTwo,barsGoBack) and Sum(negZeroAndOne, barsGoBack) > Sum(twoAndThree,barsGoBack) and Sum(negZeroAndOne, barsGoBack) > Sum(negoneAndTwo,barsGoBack) and Sum(negZeroAndOne, barsGoBack) > Sum(negtwoAndThree,barsGoBack){
cloud1 = zero;
cloud2 = negOne;
}
else if Sum(negoneAndTwo, barsGoBack) > Sum(zeroAndOne,barsGoBack) and Sum(negoneAndTwo, barsGoBack) > Sum(oneAndTwo,barsGoBack) and Sum(negoneAndTwo, barsGoBack) > Sum(twoAndThree,barsGoBack) and Sum(negoneAndTwo, barsGoBack) > Sum(negZeroAndOne,barsGoBack) and Sum(negoneAndTwo, barsGoBack) > Sum(negtwoAndThree,barsGoBack){
cloud1 = negOne;
cloud2 = negTwo;
}
else if Sum(negtwoAndThree, barsGoBack) > Sum(zeroAndOne,barsGoBack) and Sum(negtwoAndThree, barsGoBack) > Sum(oneAndTwo,barsGoBack) and Sum(negtwoAndThree, barsGoBack) > Sum(twoAndThree,barsGoBack) and Sum(negtwoAndThree, barsGoBack) > Sum(negZeroAndOne,barsGoBack) and Sum(negtwoAndThree, barsGoBack) > Sum(negOneAndTwo,barsGoBack){
cloud1 = negTwo;
cloud2 = negThree;
}
else {
cloud1 = Double.nan;
cloud2 = Double.nan;
}
plot BullSignal = if (cloud1 == one or cloud2 == one or cloud1 == two or cloud2 == two or cloud1 == three or cloud2 ==three) and (VScore <= 0.3 and Vscore[1] >0) and CCI() > -100 then 1 else 0;
def BearSignal = if (cloud1 == negOne or cloud2 == negOne or cloud1 == negTwo or cloud2 == negTwo or cloud1 == negThree or cloud2 ==negThree) and (VScore >= 0.3 and Vscore[1] < 0) and CCI() < 100 then 2 else Double.nan;
# Bearish signal scan: change BullSignal to def, change BearSignal to plot,
# and end the BearSignal line with "then 1 else 0;" instead of "then 2 else Double.nan;".
# Its test reads VScore >= 0.3 as in the indicator; the wide bearish scan in the video uses -0.3.
#
# Wide bullish scan (no cloud or CCI check), as the only plot:
# plot signal = if (VScore <= 0.3 and Vscore[1] > 0) then 1 else 0;
#
# Wide bearish scan, as the only plot:
# plot signal = if (VScore >= -0.3 and Vscore[1] < 0) then 1 else 0;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