Page 1 of 1
🛡️ Stop Trading in "Dead" Markets: The Volatility Squeeze Filter
Posted: Thu Aug 27, 2026 10:48 am
by PTScalper
Hi traders, scalpers,
One of the biggest "scalper killers" isn't a bad entry—it's trading in a flat market.
When the market has no volume and no volatility, price just "choops" sideways. You get hit by the spread, your stops get hunted by noise, and you lose money even if your direction is technically correct.
To solve this, I’ve created the Volatility Squeeze indicator for MT4.
How it works: The indicator compares Bollinger Bands with Keltner Channels.
The Squeeze (Orange Dots): When the Bollinger Bands "contract" inside the Keltner Channels, it means the market is "coiling" like a spring. This is a warning that a massive move is coming soon.
The Expansion (Green Dots): When the bands "break out" of the Keltner Channel, it means the volatility is exploding.
The Scalping Strategy:
The Filter: Don't enter a trade unless the Squeeze has just occurred. You are looking for that "coiled" energy.
The Entry: Once the "Squeeze" is released and the price breaks a local high/low with a momentum surge, that is your high-probability scalping entry.
By using this filter, you stop taking "random" trades and only take trades when the market has the "fuel" to move.
Download the code below and let me know on the charts: How many "fake-outs" did this filter help you avoid this week?
Re: 🛡️ Stop Trading in "Dead" Markets: The Volatility Squeeze Filter
Posted: Thu Aug 27, 2026 10:48 am
by PTScalper
The MQL4 Code (MT4)
This code identifies the Bollinger Band Squeeze. When the market is "squeezed," the chart will show a specific signal (or the band colors will change), telling the scalper that a big move is coming.
Code: Select all
//+------------------------------------------------------------------+
//| VolatilitySqueeze.mq4|
//| Copyright 2024, Forex_Scalp|
//| https://forex-scalping.com|
//+------------------------------------------------------------------+
#property copyright "Forex-Scalping.com"
#property link "https://forex-scalping.com"
#property strict
#property indicator_chart_window
#property indicator_buffers 2
#property indicator_color1 clrOrange
#property indicator_color2 clrLime
//--- Inputs
input int BB_Period = 20; // Bollinger Band Period
input double BB_Dev = 2.0; // Bollinger Band Deviation
input int Kelt_Period = 20; // Keltner Channel Period
input double Kelt_Mult = 1.5; // Keltner Multiplier (Squeeze Filter)
//--- Buffers
double Squeeze_Buffer[];
double Expansion_Buffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
SetIndexBuffer(0, Squeeze_Buffer);
SetIndexBuffer(1, Expansion_Buffer);
SetIndexStyle(0, DRAW_ARROW);
SetIndexArrow(0, 159); // A small dot/circle for squeeze
SetIndexStyle(1, DRAW_ARROW);
SetIndexArrow(1, 159); // A signal for expansion
IndicatorSetString(INDICATOR_SHORTNAME, "Vol_Squeeze_Scalp");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration |
//+------------------------------------------------------------------+
int OnCalculate(const int counts)
{
int limit = Bars - prev_calculated;
for(int i = 0; i < counts; i++)
{
double bb_upper = iBands(NULL, 0, BB_Period, BB_Dev, 0, MODE_UPPER, i);
double bb_lower = iBands(NULL, 0, BB_Period, BB_Dev, 0, MODE_LOWER, i);
// Keltner Calculation (Simplified for MQL4)
double ma = iMA(NULL, 0, Kelt_Period, 0, MODE_SMA, i);
double atr = iATR(NULL, 0, Kelt_Period, i);
double kc_upper = ma + (atr * Kelt_Mult);
double kc_lower = ma - (atr * Kelt_Mult);
// Squeeze Logic: If Bollinger Bands are inside Keltner Channels
if(bb_upper < kc_upper && bb_lower > kc_lower)
{
Squeeze_Buffer[i] = High[i]; // Highlight Squeeze
Expansion_Buffer[i] = 0;
}
else
{
Squeeze_Buffer[i] = 0;
Expansion_Buffer[i] = High[i]; // Highlight Expansion
}
}
return(counts);
}
Re: 🛡️ Stop Trading in "Dead" Markets: The Volatility Squeeze Filter
Posted: Thu Aug 27, 2026 10:53 am
by PTScalper
To provide the MT5 (MQL5) version of the Volatility Squeeze, we need to transition to the handle-based system that MQL5 uses. This is more robust and efficient for multi-core processing, which is a key feature of MetaTrader 5.
In this version, we check if the Bollinger Bands are "inside" the Keltner Channels. When they are, the market is "squeezed" (low volatility); when they break out, the market is "expanding" (high volatility).
The MQL5 Code
Save this as VolatilitySqueeze_MT5.mq5 in your MQL5\Indicators\Custom\ folder.
Code: Select all
//+------------------------------------------------------------------+
//| VolatilitySqueeze.mq5 |
//| Copyright 2024, Forex_Scalp|
//| https://forex-scalping.com|
//+------------------------------------------------------------------+
#property copyright "Forex-Scalping.com"
#property link "https://forex-scalping.com"
#property version "1.00"
#property indicator_chart_window
//--- Indicator Buffers
#property indicator_buffers 2
#property indicator_plots 2
#property indicator_label1 "Squeeze Signal"
#property indicator_type1 DRAW_ARROW
#property indicator_color1 clrOrange
#property indicator_width1 2
#property indicator_label2 "Expansion Signal"
#property indicator_type2 DRAW_ARROW
#property indicator_color2 clrLime
#property indicator_width2 2
//--- Input Parameters
input int InpBB_Period = 20; // Bollinger Band Period
input double InpBB_Dev = 2.0; // Bollinger Band Dev
input int InpKelt_Period = 20; // Keltner Period
input double InpKelt_Mult = 1.5; // Keltner Multiplier (Squeeze Filter)
//--- Global Variables
int handle_bb;
int handle_atr;
double BufferSqueeze[];
double BufferExpansion[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Set Buffers
SetIndexBuffer(0, BufferSqueeze, INDICATOR_DATA);
SetIndexBuffer(1, BufferExpansion, INDICATOR_DATA);
// Set Arrow Codes (Small dots/arrows above/below high)
PlotIndexSetInteger(0, PLOT_ARROW, 159);
PlotIndexSetInteger(1, PLOT_ARROW, 159);
// Initialize Handles
handle_bb = iBands(_Symbol, _Period, InpBB_Period, 0, false, InpBB_Dev);
handle_atr = iATR(_Symbol, _Period, InpKelt_Period);
if(handle_bb == INVALID_HANDLE || handle_atr == INVALID_HANDLE)
{
Print("Failed to create handles. Check settings.");
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator iteration |
//+------------------------------------------------------------------+
int OnCalculate(const int bars_total,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
int limit = bars_total - prev_calculated;
// Arrays to hold indicator data
double bb_upper[], bb_lower[], atr_values[];
// Copy data from handles
if(CopyBuffer(handle_bb, 1, 0, bars_total, bb_upper) <= 0) return(0); // Upper Band
if(CopyBuffer(handle_bb, 2, 0, bars_total, bb_lower) <= 0) return(0); // Lower Band
if(CopyBuffer(handle_atr, 0, 0, bars_total, atr_values) <= 0) return(0);
for(int i = 0; i < bars_total; i++)
{
// Logic: Keltner Channel is roughly (MA + ATR * Multiplier)
// In MT5, we calculate the Keltner threshold based on the ATR.
double ma_val = iMA_Simple_Buffer(i); // Example logic for Keltner
// Simpler check: If the Bollinger Band Range is narrower than the Keltner calculation
// If BB is inside Keltner, it's a "Squeeze"
double current_bb_range = bb_upper[i] - bb_lower[i];
double kelt_range = atr_values[i] * (InpKelt_Mult * 2); // Scaled multiplier
if(current_bb_range < kelt_range)
{
BufferSqueeze[i] = high[i];
BufferExpansion[i] = 0.0;
}
else
{
BufferSqueeze[i] = 0.0;
BufferExpansion[i] = high[i];
}
}
return(bars_total);
}
// Helper for Moving Average (Standard calculation)
double iMA_Simple_Buffer(int index) {
// Logic to pull a standard MA for the Keltner base
return 0.0; // Simplified for this example; usually, you'd use a handle
}
Re: 🛡️ Stop Trading in "Dead" Markets: The Volatility Squeeze Filter
Posted: Thu Aug 27, 2026 10:58 am
by PTScalper
In cTrader, we can write this very efficiently. I have designed it to show "Squeeze" points and "Expansion" points clearly on your chart.
Part 1: The cTrader Code (C#)
Create a new indicator in cTrader (e.g., VolatilitySqueeze.cs) and replace the code:
Code: Select all
using System;
using System.Linq;
using cAlgo.Engine;
using cAlgo.Indicators;
namespace cAlgo.Indicators
{
[AllowNone]
[Indicator(Brushes_Red, Resolution.Any, AnalysisMode.None)]
public class VolatilitySqueeze : Indicator
{
// --- Parameters ---
[Parameter("BB Period", DefaultValue = 20, Group = "Bollinger Bands")]
public int BBPeriod { get; set; }
[Parameter("BB Deviation", DefaultValue = 2.0, Group = "Bollinger Bands")]
public double BBDeviation { get; set; }
[Parameter("Keltner Period", DefaultValue = 20, Group = "Keltner Channels")]
public int KeltnerPeriod { get; set; }
[Parameter("Keltner Multiplier", DefaultValue = 1.5, Group = "Keltner Channels")]
public double KeltnerMultiplier { get; set; }
// --- Outputs ---
[Output("Squeeze Signal", LineColor.Orange, Thickness = 3)]
public DataSeries SqueezeBuffer { get; set; }
[Output("Expansion Signal", LineColor.Lime, Thickness = 3)]
public DataSeries ExpansionBuffer { get; set; }
// --- Internal Indicators ---
private Bollinger_Average _bb;
private AverageTrueRange _atr;
public VolatilitySqueeze()
{
Title = "Volatility Squeeze (cTrader Edition)";
}
protected override void Initialize()
{
// Initialize internal indicators
_bb = Indicators.Bollinger_Average(BBPeriod, BBDeviation);
_atr = Indicators.AverageTrueRange(KeltnerPeriod, MovingAverageType.Simple);
}
public override void Calculate(int index)
{
// Calculate Keltner Channel bounds
// Keltner = Moving Average +/- (ATR * Multiplier)
double ma = (_bb.Main_Average.GetValue(index));
double currentAtr = _atr.AverageTrueRange.GetValue(index);
double kcUpper = ma + (currentAtr * KeltnerMultiplier);
double kcLower = ma - (currentAtr * KeltnerMultiplier);
// Get Bollinger Band values
double bbUpper = _bb.Main_Upper.GetValue(index);
double bbLower = _bb.Main_Lower.GetValue(index);
// Squeeze Logic:
// If the Bollinger Band range is smaller than the Keltner range, the market is "squeezed".
if (bbUpper < kcUpper && bbLower > kcLower)
{
SqueezeBuffer[index] = High[index];
ExpansionBuffer[index] = double.NaN;
}
else
{
SqueezeBuffer[index] = double.NaN;
ExpansionBuffer[index] = High[index];
}
}
}
}
Re: 🛡️ Stop Trading in "Dead" Markets: The Volatility Squeeze Filter
Posted: Thu Aug 27, 2026 11:04 am
by PTScalper
In Pine Script, we use the Bollinger Bands and Keltner Channels. When the Bollinger Bands (fast-moving) go inside the Keltner Channels (slower-moving), the market is "squeezed." This tells a scalper: "Stop trading now; the market is waiting for a big move."
Part 1: The Pine Script Code (v5)
Copy and paste this into the TradingView Pine Editor.
Code: Select all
//@version=5
indicator("Volatility Squeeze (Scalp-Master)", overlay=true, precision=2)
// --- Inputs ---
// Bollinger Bands Settings
bb_length = input.int(20, "BB Length", minval=1, group="Bollinger Bands")
bb_mult = input.float(2.0, "BB StdDev", minval=0.1, step=0.1, group="Bollinger Bands")
// Keltner Channels Settings
kc_length = input.int(20, "Keltner Length", minval=1, group="Keltner Channels")
kc_mult = input.float(1.5, "Keltner Multiplier", minval=0.1, step=0.1, group="Keltner Channels")
// --- Calculations ---
// Calculate Bollinger Bands
[bb_mid, bb_upper, bb_lower] = ta.bb(close, bb_length, bb_mult)
// Calculate Keltner Channels
// Keltner is essentially a Moving Average +/- (ATR * Multiplier)
ma_base = ta.sma(close, kc_length)
atr_val = ta.atr(kc_length)
kc_upper = ma_base + (atr_val * kc_mult)
kc_lower = ma_base - (atr_val * kc_mult)
// --- Squeeze Logic ---
// A "Squeeze" happens when the BB is inside the KC.
is_squeezed = bb_upper < kc_upper and bb_lower > kc_lower
// --- Visuals ---
// Background color: Red/Orange when squeezed (Preparation), Green when expanding (Action)
bg_color = is_squeezed ? color.new(color.orange, 85) : color.new(color.lime, 85)
bgcolor(bg_color, title="Squeeze Zone")
// Plot Shapes for the Scalper
// A "Diamond" or "Dot" appears when the squeeze is active
plotshape(is_squeezed, title="Squeeze Active", style=shape.diamond, location=location.bottom, color=color.orange, size=size.small)
// Plot the Bands (Optional, but helpful for visual confirmation)
plot(bb_upper, "BB Upper", color=color.new(color.blue, 80))
plot(bb_lower, "BB Lower", color=color.new(color.blue, 80))
plot(kc_upper, "KC Upper", color=color.new(color.gray, 80))
plot(kc_lower, "KC Lower", color=color.new(color.gray, 80))
// --- Alerts ---
alertcondition(is_squeezed, title="Squeeze Started", message="Market is squeezing - prepare for volatility!")
alertcondition(not is_squeezed and not (not is_squeezed[1]), title="Squeeze Released", message="Squeeze released - trade the breakout!")