Skip to content
Level 1 · Chart ReaderLessonPart 05 · page 1 of 624 min
24Minutes
7AFL functions
7Sources
StandardRequires
AFL functions taught here7

Support and Resistance as Zones

By the end of this lesson you should be able to write down what “there is support at 47” actually asserts, in a form specific enough that somebody could look at data and tell you that you are wrong. That turns out to be harder, and more interesting, than drawing the line.

The standard definitions are these.

Support is a price region where buying interest has previously been sufficient to halt a decline. Resistance is a price region where selling interest has previously been sufficient to halt an advance.

Read those two sentences carefully and notice that they are entirely about the past. They are descriptions of what already happened, and as descriptions they are unarguable: price did stop falling somewhere, and that somewhere is a fact recorded in the data.

Nobody trades a description. What people actually use is a second, much stronger claim smuggled in behind the first:

When price returns to a level where it previously stopped, it will tend to stop there again.

That sentence is a prediction about the future behaviour of a series, conditional on its own past. It could be true, partly true, or worth nothing. It is the only version worth your attention, because it is the only version that can be checked — and this part of the course is about checking it rather than repeating it.

Open a chart, find the low of a memorable sell-off, and draw a horizontal line through it. You have just asserted that a single number — one printed price, on one bar, on one instrument — is meaningful. Consider what that number actually is.

It is the lowest trade that happened to print during that session. Had one more seller been present for one more second, it would be a tick lower. It came from a specific data vendor, and two vendors frequently disagree about the day’s extreme, particularly for the first and last minutes of a session. It has been adjusted for every split and dividend since, so the number you are looking at may never have appeared on anyone’s screen. And it is quoted to a tick size that has itself changed over the history of most instruments.

None of that makes the region meaningless. It makes the exactness meaningless. The honest object is a band: a price zone with a stated width, inside which the historical evidence of buying or selling pressure sits.

Here is what that costs you in observations. The same series is drawn twice. In the first, the level is the line through the low of the first decline:

A line through the first low

Touched the lineLine at 100.00 - the first low
  • Close above open
  • Close below open
  • Touched the line
One touch. The second decline stopped 0.45 above the line and the third went 0.20 through it, so on a strict reading neither is an observation of this level at all. The data in this chart is invented for the illustration. It is not market data and nothing should be inferred from it.

In the second, the level is a band 0.8 wide - about 0.8% of price, the first of the three width rules below:

A band 0.8 wide over the same bars

Touched the zoneTouched the zoneTouched the zoneZone 99.70 - 100.50
  • Close above open
  • Close below open
  • Touched the zone
Three touches, from exactly the same data. Nothing about the market changed between these two pictures; the only thing that changed is what you were willing to call a level - which is why the width rule has to be chosen before you look. The data in this chart is invented for the illustration. It is not market data and nothing should be inferred from it.

A zone needs a rule for its width, and the rule has to come from somewhere other than taste. Three defensible choices:

Width rule How it behaves When it suits
Fixed percentage of price, e.g. 0.5% Simple, comparable across instruments, ignores current conditions Quick screening across many symbols
Multiple of ATR, e.g. 0.25 × ATR(20) Widens in volatile periods, narrows in quiet ones Most single-instrument work
Span of the bars that formed the level, e.g. high-to-low of the pivot bar Uses the market’s own hesitation at that price When the level came from an obvious pause

The trade-off runs in one direction and is unavoidable. Too wide and everything touches the zone, so the level explains every subsequent move and predicts none. Too narrow and nothing touches it, so you have no observations at all. Any conclusion you eventually reach will depend on this number, which is the first hint that testing this idea properly will be delicate. We come back to that in the reality check at the end of the part.

ATR is covered properly in Part 6; for now treat ATR(20) as “the size of a typical daily move over the last twenty days” and note that expressing a zone in ATR units makes the same setting mean roughly the same thing on a 3-unit stock and a 300-unit index.

Some levels require you to choose. Others do not, and those are worth far more to a person who cares about evidence.

The classic source. Part 4 defined a swing high as a bar that is the highest of some window around it — and made the crucial point that the definition contains a free parameter, the half-width of that window, and that a swing point is only confirmed some bars after it happened.

Both facts follow you into this lesson. A “previous high” is not a fact about the market; it is a fact about the market and your window length. Change the window from five bars to fifteen and half your levels disappear.

Yesterday’s high and low. Last week’s range. The previous calendar month’s extremes. The prior session’s close.

These are the best-behaved candidate levels in the whole subject, for one reason: they contain no free parameters at all. Everyone with the same data agrees where last month’s high was. There is nothing to tune, nothing to select, and therefore nothing to fool yourself with. If a level effect exists anywhere, this is the cleanest place to look for it — and if it does not show up here, that is informative too.

The claim is that participants think in round units, so orders cluster at 50, 100, 1,000, and price hesitates there.

Like prior-period extremes, round numbers are wonderfully objective — with one wrinkle. Round in what unit? For a 12-unit stock the natural grid is whole units; for a 1,200-unit index it is hundreds. That choice is a parameter, even if it feels obvious, and it is the only one. Two levels of roundness — whole numbers and multiples of ten — are usually enough to define the hypothesis fully.

Gap edges, the opening price of the day, the previous close, an option strike, an IPO price, a widely quoted 52-week high. Each has a story. The stories differ in plausibility, and none of them is evidence.

A hypothesis with a mechanism behind it deserves more of your time than one without. Here are the mechanisms usually offered, stated as what they are — plausible stories.

Resting orders. Limit orders to buy and stop orders to sell genuinely do accumulate at memorable prices. A cluster of resting liquidity is a real object in a real order book, and it takes real volume to consume. This is the strongest of the arguments because it describes something that physically exists.

Memory and anchoring. Participants remember what they paid. Someone who bought at the old high and watched it fall may sell at break-even when it returns, creating supply at that price. This requires the population of holders to be stable enough to remember, which is more plausible for a slow-moving instrument than a heavily traded one.

Common observation. If enough participants watch the same level, their reactions to it become correlated, and the level acquires a degree of self-fulfilment. Note that this argument is symmetric: it explains a bounce and a break equally well.

Institutional levels. Option strikes, index rebalancing prices and reference prices used in execution algorithms are round or structural by construction.

Now the counter-argument, which is just as important. If a level reliably stopped price, that would be a free option for anyone who noticed. Participants would buy slightly above the support, moving the effective level upward, and the reliable behaviour would erode into something fuzzier and smaller. The mechanism that creates the effect also creates the incentive to compete it away. This is why the sensible prior is “a small, unstable tendency”, not “a law”.

Here is the part that is usually left out.

Take any chart. Count the candidate levels available on it: every swing high, every swing low, every round number in range, every gap edge, every prior-period extreme. On a three-year daily chart there are easily a hundred. Now give each of them a zone half a percent wide. Price is always near several of them.

So when a bounce happens, a level is available to explain it. When a break happens, another level is available to explain that. Neither observation carried information about what was going to happen next; both were certain to be explainable afterwards, whatever they were.

Three specific failure modes follow.

Hindsight anchoring. You draw the line after seeing the bounce. The line’s position was chosen because the bounce occurred there, so the fact that a bounce occurred there cannot be used as evidence that the line works. This is not a subtle statistical point; it is circularity.

Multiple comparisons. With a hundred candidate levels, some of them will show impressive-looking behaviour by chance alone. Picking the impressive ones and showing them to somebody is a demonstration of your search, not of the market’s structure. Part 30 returns to this with the arithmetic.

The unfalsifiable escape. When price stops at the level: support held. When it goes through: a breakout, which is also a tradeable event. When it goes through and comes back: a false breakout, also a tradeable event. Every outcome has a name, and a framework that names every outcome has forecast none of them.

What an objective level definition looks like

Section titled “What an objective level definition looks like”

Here is the simplest rule that has no discretion in it once its one parameter is fixed: the highest high of the previous N bars.

The obvious way to write that is wrong, and it is wrong in an instructive way.

Fragment — not a complete formula

// WRONG: HHV includes the current bar, so today's high is one of the candidates
// and Close can never exceed the maximum of a set that contains its own bar's high.
Level = HHV( High, 20 );
AtTheTop = Close > Level;

The official reference for HHV states that the period includes the current day. So HHV(High, 20) at any bar is the highest high over that bar and the nineteen before it. Comparing today’s close with it asks a question that answers itself. The fix is to shift the window back by one bar with Ref, whose negative argument means “bars ago”:

Fragment — not a complete formula

// RIGHT: the highest high of the twenty bars BEFORE this one.
Level = Ref( HHV( High, 20 ), -1 );
AtTheTop = Close > Level;

A prior-high level, bar by bar

Bar 6 closes above the highest high of bars 3 to 5. Without the Ref shift, bar 6's own high of 11.4 would sit inside the level it is being compared against.
Bar123456
High10.011.010.511.010.811.4
Close9.810.910.210.910.611.3
HHV(High, 3)first cells are warm-up11.011.011.011.4
Ref(HHV(High,3), -1)11.011.011.0
Close > level001
Bar 6 closes above the highest high of bars 3 to 5. Without the Ref shift, bar 6's own high of 11.4 would sit inside the level it is being compared against. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

Notice what the diagram also shows: the first few cells are blank, because a three-bar window needs three bars before it is measuring what it claims to measure — and a twenty-bar one needs twenty. Levels do not exist at the left edge of a chart, and any count of “how often the level held” silently excludes that warm-up period. The official HHV page does not state what the function returns during warm-up, so the course does not assert it either; what matters is that you exclude those bars from anything you count.

The formula below plots both families of zero-discretion level — the prior N-bar extremes as zones, and a round-number grid — so that you can look at real data instead of a description of it.

Complete runnable AFL

prior-extreme-levels.afl
// prior-extreme-levels.afl
// Part 5 - Support and Resistance as Zones
//
// Draws the two families of level that need no drawing tool and no judgement:
// 1. the highest high and the lowest low of the previous N bars, shifted so
// that the current bar is excluded from its own level;
// 2. a round-number grid around the current price.
// Each swing level is drawn as a ZONE, not a line, because a level derived from
// a single print claims a precision that the data does not have.
//
// Assumptions:
// - daily bars; the symbol has at least Lookback + AtrPeriod bars of history;
// - prices are greater than zero (the automatic grid step uses log10);
// - the history is split-adjusted, otherwise the old levels sit at prices the
// instrument never traded at.
_SECTION_BEGIN("Prior extreme levels");
Lookback = Param( "Lookback bars", 60, 5, 250, 5 );
ZoneAtrMult = Param( "Zone half-width (x ATR)", 0.25, 0.05, 2, 0.05 );
AtrPeriod = Param( "ATR period", 20, 2, 100, 1 );
GridStep = Param( "Round-number grid step (0 = auto)", 0, 0, 1000, 0.01 );
ShowGrid = ParamToggle( "Round-number grid", "Hide|Show", 1 );
// HHV and LLV INCLUDE the current bar - that is documented behaviour, not a
// quirk - so a raw comparison against them can never be true. Ref( ..., -1 )
// steps the window back one bar, which is what "the high before today" means.
PriorHigh = Ref( HHV( High, Lookback ), -1 );
PriorLow = Ref( LLV( Low, Lookback ), -1 );
// The zone half-width is expressed in the instrument's own volatility so that
// the same setting means something comparable on a 3-unit stock and a 300-unit
// one. Any width rule would do; what matters is that it is stated.
HalfWidth = ZoneAtrMult * ATR( AtrPeriod );
Plot( Close, "Close", colorDefault, styleCandle );
// styleCloud fills the band between the two OHLC pairs, which is how a zone
// gets drawn as an area rather than a line.
PlotOHLC( PriorHigh + HalfWidth, PriorHigh + HalfWidth,
PriorHigh - HalfWidth, PriorHigh - HalfWidth,
"Resistance zone", ColorBlend( colorRed, colorWhite, 0.75 ),
styleCloud | styleNoLabel );
PlotOHLC( PriorLow + HalfWidth, PriorLow + HalfWidth,
PriorLow - HalfWidth, PriorLow - HalfWidth,
"Support zone", ColorBlend( colorBlue, colorWhite, 0.75 ),
styleCloud | styleNoLabel );
Plot( PriorHigh, "Prior " + Lookback + "-bar high", colorRed,
styleStaircase | styleThick );
Plot( PriorLow, "Prior " + Lookback + "-bar low", colorBlue,
styleStaircase | styleThick );
// "Round" is itself a choice of unit. The automatic step is roughly one tenth
// of the price's order of magnitude: a 12-unit stock gets a 1-unit grid, a
// 1,200-unit index a 100-unit grid. Override it to test a different claim.
if ( ShowGrid )
{
LastPrice = LastValue( Close );
if ( GridStep <= 0 )
{
GridStep = 10 ^ floor( log10( Max( LastPrice, 0.01 ) ) - 1 );
}
GridBase = floor( LastPrice / GridStep ) * GridStep;
for ( i = -3; i <= 3; i++ )
{
PlotGrid( GridBase + i * GridStep, colorLightGrey, 8, 1, True );
}
}
Title = Name() + " " + Interval( 2 ) +
" | prior " + Lookback + "-bar high " + WriteVal( PriorHigh, 1.2 ) +
", low " + WriteVal( PriorLow, 1.2 ) +
" | zone half-width " + WriteVal( HalfWidth, 1.2 ) +
" (" + WriteVal( ZoneAtrMult, 1.2 ) + " x ATR" + AtrPeriod + ")";
_SECTION_END();

Download prior-extreme-levels.afl81 lines

Four logical sections. The parameters put every choice in the Parameters dialog rather than in the code, so you can move the lookback and the zone width while watching the chart and see how much the picture depends on them — which is the real lesson.

The levels section computes Ref(HHV(High, Lookback), -1) and its LLV mirror. The zones section turns each level into a band by adding and subtracting ZoneAtrMult * ATR(AtrPeriod), and draws the band with PlotOHLC in styleCloud, which fills the area between the supplied high and low arrays. The grid section picks a round-number step — automatically about one tenth of the price’s order of magnitude, or whatever you type — and calls PlotGrid for a few multiples above and below the current price.

  • HHV(array, periods) and LLV(array, periods) — rolling highest and lowest value. The window includes the current bar.
  • Ref(array, period) — shifts an array in time. Negative looks back; positive looks forward, which reads the future and is almost always a mistake in a trading rule.
  • ATR(period) — average true range, a volatility scale in price units.
  • PlotOHLC(open, high, low, close, name, colour, style) — draws a custom OHLC series; with styleCloud it fills a band.
  • PlotGrid(level, colour, pattern, width, label) — a horizontal grid line at a constant price, much faster than plotting a flat array.
  • ColorBlend(from, to, factor) — mixes two colours, used here to make the zones pale enough to read the candles through.

Set the lookback to 20 and note where the red staircase sits. Now set it to 200. If the lines did not move, the symbol has been in a narrow range for a year; pick another one. Then set the zone half-width to its minimum and its maximum in turn, and count how many of the last fifty bars touch the support zone in each case. The two counts will differ by a large factor. That factor is the size of the free parameter you are carrying.

Add a third pair of levels: the previous calendar month’s high and low. The lab later in this part uses HighestSince and LowestSince to do it, so you can either work it out now or borrow it then. Compare, by eye, how often price reacts near the monthly extremes versus near the rolling N-bar extremes. Then write down why that eyeball comparison is not evidence.

Support and resistance are two claims wearing one name: an unarguable description of the past, and a testable prediction about the future. Only the second matters, and stating it properly forces you to name a level definition, a zone width, a universe and a comparison.

An exact price line asserts a precision the data does not contain, so the honest object is a zone whose width comes from a stated rule. The candidate levels available to you differ enormously in how much discretion they need, and the ones needing least — prior-period extremes and round numbers — are the ones worth testing first.

The mechanisms that might make levels matter are real enough to justify investigation and too weak to justify belief. And the selection problem is severe: on a normal chart, price is always near several candidate levels, so an after-the-fact explanation is always available. The only defence is to fix the rule before you look.

Check your understanding

Question 1. What does this fragment produce?
AtTheTop = Close > HHV( High, 20 );
Show the answer and why

Answer: A Boolean array that is essentially always false, because the current bar is inside the window

The official reference states that the HHV period includes the current day, so today’s high is one of the twenty candidates. A close can only exceed that maximum if the close is above the day’s own high, which cannot happen. Shift the window with Ref( ..., -1 ).

Question 2. Which candidate level carries the fewest free parameters?
Show the answer and why

Answer: The previous calendar month’s high

Everyone with the same data agrees where last month’s high was: there is no window length, no anchor choice and no judgement. The swing high carries one parameter, the trendline several, and the last option is pure discretion.

Question 3. A chart shows price bouncing three times off a line you drew after seeing the chart. Why is this weak evidence that the level works?
Show the answer and why

Answer: Because the line was positioned using the very bounces now being offered as evidence

Small samples and confounding news are real problems, but the fatal one here is circularity: the line’s location was chosen because the bounces happened there, so those bounces cannot also serve as an independent test of the line.

Question 4. Which of these are genuine consequences of making a support zone wider? Select all that apply.
Show the answer and why

Answer: More bars qualify as touching the zone, The level becomes better at explaining moves after the fact, The number of observations available for a test increases

Widening a zone increases touches and therefore observations, and makes after-the-fact explanation easier because price is near the zone more often. What it does not do is make interpretation easier — the wider the zone, the less the word "touch" means.

Sources for this lesson

7 verified · checked 2026-08-31

  1. 01AFL Function Reference — HHVamibroker.com/guide/afl/hhv.html2026-08-31
  2. 02AFL Function Reference — LLVamibroker.com/guide/afl/llv.html2026-08-31
  3. 03AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
  4. 04AFL Function Reference — ATRamibroker.com/guide/afl/atr.html2026-08-31
  5. 05AFL Function Reference — PlotOHLCamibroker.com/guide/afl/plotohlc.html2026-08-31
  6. 06AFL Function Reference — PlotGridamibroker.com/guide/afl/plotgrid.html2026-08-31
  7. 07AmiBroker User's Guide — Charting guide§ Using drawing toolsamibroker.com/guide/h_charting.html2026-08-31

Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.