Candles: a simple volatility indicator for the forex market
The distance from high to low is the most honest volatility reading available on a chart. It needs no parameters and hides nothing — which is exactly why it is worth understanding before reaching for ATR.
Volatility is not direction. It tells you how far price is travelling per unit of time, which determines how wide a stop has to be, how big a position can be, and whether a strategy has room to work at all.
Raw range versus ATR
The raw range of a candle is High - Low. The Average True Range adds two things: it averages over N bars, and it accounts for gaps by taking the largest of three distances — current range, previous close to current high, and previous close to current low.
In forex the gap correction matters far less than in equities, because the market trades continuously from Sunday evening to Friday evening. The one place it matters is the weekend gap, and there it matters a lot.
| Raw range | ATR | |
|---|---|---|
| Parameters | None | Period (default 14) |
| Reacts to a single bar | Immediately | Diluted by the average |
| Handles gaps | No | Yes |
| Good for | Seeing what just happened | Sizing and stops |
A minimal range indicator
This draws each bar's range as a histogram and colours it by whether it exceeds the recent average. That single comparison is most of the value — an absolute pip number means nothing without context.
// CandleRange.mq4 — bar range, coloured against its own average
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_color1 clrSteelBlue
#property indicator_color2 clrIndianRed
input int AvgPeriod = 20;
double normal[], elevated[];
int OnInit()
{
SetIndexBuffer(0, normal); SetIndexStyle(0, DRAW_HISTOGRAM, STYLE_SOLID, 2);
SetIndexBuffer(1, elevated); SetIndexStyle(1, DRAW_HISTOGRAM, STYLE_SOLID, 2);
IndicatorShortName("Range(" + IntegerToString(AvgPeriod) + ")");
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total, const int prev_calculated,
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 start = MathMax(prev_calculated - 1, AvgPeriod);
for(int i = start; i < rates_total; i++)
{
int bar = rates_total - 1 - i; // series index
double range = (high[i] - low[i]) / Point;
double sum = 0;
for(int k = 1; k <= AvgPeriod; k++)
sum += (high[i-k] - low[i-k]) / Point;
double avg = sum / AvgPeriod;
normal[bar] = (range <= avg) ? range : EMPTY_VALUE;
elevated[bar] = (range > avg) ? range : EMPTY_VALUE;
}
return(rates_total);
}
Normalising so numbers compare
Forty pips means something different on USDJPY than on EURCHF, and something different in August than in October. Divide the current range by the average and you get a unitless ratio that is comparable across pairs and periods.
- Ratio below 0.7 — compression. Breakout strategies get chopped up here; range strategies do well.
- Ratio around 1.0 — ordinary conditions, whatever the instrument's ordinary happens to be.
- Ratio above 2.0 — something happened. Often news. Stops sized for normal conditions are about to be irrelevant.
Using it for position size
The standard application is inverting it: risk a fixed amount of money, let volatility set the stop distance, and let the stop distance set the lot size. This keeps the money at risk constant while the market changes underneath you.
// risk-based lot size from a volatility stop
double riskMoney = AccountBalance() * 0.01; // 1% of balance
double atrPips = iATR(NULL, 0, 14, 1) / Point;
double stopPips = atrPips * 1.5; // stop = 1.5 x ATR
double pipValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lots = riskMoney / (stopPips * pipValue);
lots = MathFloor(lots / MarketInfo(Symbol(), MODE_LOTSTEP))
* MarketInfo(Symbol(), MODE_LOTSTEP);
Note the 1 as the last argument to iATR — that reads the closed bar. Using 0 reads the bar still forming, whose range grows as you watch, which means your position size depends on what second you happened to click.
Session structure shows up immediately
Plot average range by hour of day and the market's shape becomes obvious: a quiet Asian session, an expansion at the London open, the largest ranges during the London–New York overlap, and a decline afterwards.
This is the cheapest useful filter in trading. A breakout system that trades the Asian range and a breakout system that trades the London open are not the same system, even if the code is identical.
Where the honesty runs out
Range tells you what already happened. It has no predictive content on its own — compression is followed by expansion eventually, but "eventually" is not a trade. Treat it as a measuring instrument for sizing and filtering, not as a signal generator.