Reality Check: Do Support Levels Actually Hold?
This lesson does two jobs. It takes the most-repeated sentence in technical analysis and converts it into something a computer could measure. And it establishes the template that every later reality check in this course follows — for RSI in Part 6, for candlestick patterns in Part 7, for volume breakouts in Part 12.
The template matters more than the particular claim. Learn its shape here and you will be able to interrogate any market claim you meet for the rest of your life, including the ones nobody has thought to question yet.
The template
Section titled “The template”Six steps from a slogan to a measurement
- 1. State the claimIn the form its believers would accept, then in a form that could fail
- 2. Define the terms objectivelyEvery noun becomes a formula; every adjective becomes a number
- 3. Choose the universe and the periodBefore looking at any result
- 4. Define the measurementWhat is counted, over what window, from which bar
- 5. Define the comparisonA number without a benchmark means nothing
- 6. Say what would change your mindWrite the threshold down before you run anything
Step 1: state the claim
Section titled “Step 1: state the claim”Here is the version you will find in books, courses and forum posts:
Support tends to hold. When price falls back to a level where buyers previously stepped in, it often stops there and turns.
As stated, this cannot be wrong. “Tends” and “often” have no values attached, “support” has no definition, “hold” has no criterion, and no population of cases is specified. Every possible dataset is consistent with it.
Rewriting it so that it could fail requires five additions — a level definition, a touch definition, a hold definition, a population, and a comparison:
H1. For a stated universe of liquid instruments over a stated period, when the daily low first enters a zone around the lowest low of the previous 60 trading days, the proportion of such episodes in which price does not fall materially below that level over the following 10 trading days is higher than the proportion measured on ordinary bars with no level involved.
That sentence is longer, uglier and infinitely more useful. It can be false.
Step 2: define the level objectively
Section titled “Step 2: define the level objectively”The level has to be computable from data available before the event, with no judgement. Several candidates satisfy that. We must pick one, and pick it now, before seeing anything.
| Candidate level | Free parameters | Notes |
|---|---|---|
| Lowest low of the previous N bars | 1 (the lookback) | Simple, always defined, includes levels nobody would draw |
| Previous calendar month’s low | 0 | The cleanest, but only twelve per year per symbol |
| Confirmed n-bar swing low | 1 (the pivot width) | Closest to what a chartist draws; needs a confirmation lag |
| Round number nearest price | 1 (the grid step) | Zero-discretion, tests a different mechanism |
We choose the first: the lowest low of the previous 60 trading days, measured as of the bar before the touch. The reasons are worth stating, because in your own work you will have to give reasons of the same kind.
It is always defined, so the sample is not concentrated in symbols that happen to produce many pivots. It has one parameter, which we can vary later to see how much the answer depends on it. It is close enough to what a person would call “the recent low” that a positive result would be interesting, and far enough from a hand-drawn line that it is reproducible.
It also has a known weakness: it will nominate levels that no chartist would ever draw, for instance in a symbol that has fallen steadily and whose 60-day low is simply last week. If the claim only holds for “proper” support, this test will understate it. That is a real limitation, and the honest response is to say so now rather than to discover it after seeing an unwelcome result.
In AFL, with the current bar excluded from its own level:
Fragment — not a complete formula
// Ref( ..., -1 ) keeps the touch bar out of the window that defines its level.Level = Ref( LLV( Low, 60 ), -1 );Step 3: define “touch” and “held”
Section titled “Step 3: define “touch” and “held””Two more definitions, and both contain traps.
A touch is a bar whose low enters a zone around the level. The zone half-width is 0.25 × ATR(20), measured — like the level — as of the previous bar.
Fragment — not a complete formula
ZoneHalf = Ref( 0.25 * ATR( 20 ), -1 );InZone = Low <= Level + ZoneHalf AND Low >= Level - ZoneHalf;That alone is not enough, because of the re-arm problem. When price drifts sideways along
a level for three weeks, InZone is true on fifteen consecutive bars. Counting those as
fifteen touches would let a handful of episodes dominate the entire sample, and would tell
you about persistence rather than about support.
So a touch is the first in-zone bar in a 20-bar window:
Fragment — not a complete formula
// True only when this is the only in-zone bar in the trailing 20 bars.Touch = InZone AND Sum( InZone, 20 ) == 1;“Held” needs a distance and a horizon. Ours: over the next 10 bars, the lowest low never falls more than 0.5 × ATR below the level.
Fragment — not a complete formula
// A forward reference. Measuring an outcome IS a forward reference; it is only// illegitimate when the result feeds a trading decision.FutureLow = Ref( LLV( Low, 10 ), 10 );Held = FutureLow >= Level - Ref( 0.5 * ATR( 20 ), -1 );One episode, evaluated
| Bar | t-1 | t | t+1 | t+2 | t+3 | … | t+10 |
|---|---|---|---|---|---|---|---|
Level (from t-1) | 48.00 | 48.00 | 48.00 | 48.00 | 48.00 | 48.00 | 48.00 |
Zone half-width | 0.30 | 0.30 | 0.30 | 0.30 | 0.30 | 0.30 | 0.30 |
Low | 49.10 | 48.20 | 47.90 | 48.60 | 49.40 | — | 50.20 |
InZone | 0 | 1 | 1 | 0 | 0 | — | 0 |
Touch (first in 20 bars) | 0 | 1 | 0 | 0 | 0 | — | 0 |
Lowest low over t+1..t+10 | — | 47.90 | — | — | — | — | — |
Held (47.90 >= 48.00 - 0.60) | — | 1 | — | — | — | — | — |
Six numbers have now been fixed: a 60-bar lookback, a 20-bar ATR, a zone of 0.25 ATR, a 20-bar re-arm window, a 10-bar horizon and a 0.5 ATR break distance. Every one of them is a choice, so there are a great many possible versions of “the test”, and picking the version that gives the nicest answer is exactly the error Part 30 is named after. We fix them now and vary them afterwards as a sensitivity check, reporting the whole range rather than the best point in it.
Step 4: choose the universe and the period
Section titled “Step 4: choose the universe and the period”Decide this before running anything, and write it down where you cannot quietly edit it.
Universe. Something broad enough to have statistical content and liquid enough that the prices are real. A reasonable Level A choice: 100 to 300 of the largest listed equities in a market whose end-of-day history you can obtain free, imported as described in Part 3. Not the symbols you follow. Not the symbols that have done well.
Period. Long enough to contain several different market environments. Something like 2005 to 2024 covers at least one severe bear market, one long expansion, one volatility shock, and several regimes in between. A test run only over a rising market answers a question about rising markets.
Why one regime is one observation
Step 5: define the comparison
Section titled “Step 5: define the comparison”This is the step that separates research from decoration.
Suppose the test comes back: 63% of touches held. Is that impressive? There is no way to know, because “held” as we defined it — price not dropping half an ATR below a price it just visited, within ten days — happens fairly often to any bar in a market that spends most of its history drifting upward. Without a benchmark, 63% is not a result. It is a number.
Two benchmarks, and the formula can produce both.
The unconditional base rate
Section titled “The unconditional base rate”Take ordinary bars — every twentieth bar, so that the forward windows do not overlap — and apply the identical “held” test using that bar’s own low as the reference price. No level, no touch, no story. This measures how often price simply does not fall much further over ten days, in this universe over this period.
If the level result and the base rate are the same, the level contributed nothing.
The placebo level
Section titled “The placebo level”Run everything again with the level displaced by an arbitrary 7%: a price that is not a 60-day low, is not a swing point, is not round, and that no one would call support. The touch rule, the zone, the re-arm window and the hold test are all identical.
This is the stronger of the two controls, because it holds the entire measurement apparatus constant and changes only the one thing under test. If the placebo scores the same, then whatever your machinery is measuring, it is not the level.
Step 6: say what would change your mind
Section titled “Step 6: say what would change your mind”Write this down, in advance, in both directions. An honest form:
If the touch-hold rate exceeds both the base rate and the placebo rate by less than 4 percentage points, in the full period and in a majority of the sub-periods, I will conclude that the 60-day-low level added nothing detectable in this universe.
If it exceeds both by more than 4 percentage points consistently across sub-periods and across a range of zone widths from 0.1 to 0.5 ATR, I will conclude that something is there and is worth a properly costed trading test.
If the result is between those, or is inconsistent across sub-periods, I will conclude that the evidence is inconclusive and say so.
The third branch is the one people leave out, and it is the outcome you should expect most often. A conclusion of “I do not know” is a real conclusion, and it is a great deal more useful than a confident answer produced by squinting.
Pick your own threshold and defend it — but pick it now. A threshold chosen after seeing the result is not a threshold, it is a rationalisation with a number in it.
Running the measurement
Section titled “Running the measurement”The formula is an Exploration: it emits one row per touch, with the level, the zone width,
whether it held, and the forward move. A ParamList switches between the claim and its two
controls, so the same code produces all three numbers.
Complete runnable AFL
// level-touch-test.afl// Part 5 - Reality Check: Do Support Levels Actually Hold?//// An Exploration that emits ONE ROW PER TOUCH of an objectively defined level,// together with what happened over the following HoldBars bars.//// It deliberately does not report a conclusion. It produces the rows; you// aggregate them and compare the result with the two controls this same formula// can produce, using the threshold you wrote down BEFORE running it.//// Run it three times, changing only "What is being measured":// 1. Prior-low level - the claim under test.// 2. Placebo level - the identical machinery on a price displaced by// PlaceboPct%, which nobody would call support.// 3. Unconditional base rate- every SampleEvery-th bar, using that bar's own// low as the reference. This is what "held" scores// when no level is involved at all.// If (1) does not beat (2) and (3) by more than the margin you pre-registered,// the level did no work in this universe over this period.//// Definitions, all fixed before looking at any result, all visible in Parameters:// Level = lowest Low of the previous LevelLen bars, as known one bar before// the touch. Ref( ..., -1 ) keeps the touch bar out of its own level.// Zone = Level +/- ZoneMult x ATR( AtrLen ), also measured one bar before.// Touch = a bar whose Low enters the zone and which is the only such bar in// the trailing ReArmBars window.// Held = over the next HoldBars bars the lowest Low never fell more than// BreakMult x ATR below the level.//// Assumptions and limits:// - daily bars, split- and dividend-adjusted (unadjusted history puts old// levels at prices the instrument never traded at);// - the universe was chosen before any result was seen, and it includes// delisted symbols if you can get them - if it does not, say so;// - the forward measurement reads future bars. That is what an outcome IS,// and it is legitimate here only because nothing feeds a trading rule;// - end the Analysis date range at least HoldBars bars before the last bar in// the database, or the final touches are scored on incomplete windows;// - costs, slippage and tradability are NOT modelled. This measures whether// price behaved differently near the level, not whether you could profit.
_SECTION_BEGIN("Level touch test");
TestMode = ParamList( "What is being measured", "Prior-low level|Placebo level|Unconditional base rate", 0 );
LevelLen = Param( "Level lookback (bars)", 60, 10, 250, 5 );AtrLen = Param( "ATR period", 20, 5, 100, 1 );ZoneMult = Param( "Zone half-width (x ATR)", 0.25, 0.05, 2, 0.05 );BreakMult = Param( "Break distance (x ATR)", 0.5, 0.1, 3, 0.1 );HoldBars = Param( "Forward window (bars)", 10, 1, 60, 1 );ReArmBars = Param( "Re-arm window (bars)", 20, 1, 120, 1 );PlaceboPct = Param( "Placebo displacement (%)", 7, 1, 30, 0.5 );SampleEvery = Param( "Base rate: sample every n bars", 20, 1, 200, 1 );
TrueRange = ATR( AtrLen );
// The reference price under test.PriorLow = Ref( LLV( Low, LevelLen ), -1 );
if ( TestMode == "Placebo level" ){ Level = PriorLow * ( 1 - PlaceboPct / 100 );}else{ if ( TestMode == "Unconditional base rate" ) { // Every sampled bar is its own reference point, so "held" is measured // from an ordinary price with no story attached to it. Level = Low; } else { Level = PriorLow; }}
ZoneHalf = Ref( ZoneMult * TrueRange, -1 );BreakDist = Ref( BreakMult * TrueRange, -1 );
InZone = Low <= Level + ZoneHalf AND Low >= Level - ZoneHalf;
if ( TestMode == "Unconditional base rate" ){ // Sampling every n bars keeps the output manageable and, when n is at least // HoldBars, stops consecutive observations from sharing forward windows. Event = ( BarIndex() % SampleEvery ) == 0;}else{ // Re-arming matters more than it looks. Without it a single slow approach // to a level counts as twenty touches, and a handful of episodes dominates // the whole sample. Event = InZone AND Sum( InZone, ReArmBars ) == 1;}
// The outcome. Ref( ..., +HoldBars ) reads forward, which is what measuring an// outcome means; the window excludes the event bar itself.FutureLow = Ref( LLV( Low, HoldBars ), HoldBars );FutureClose = Ref( Close, HoldBars );
Held = FutureLow >= Level - BreakDist;ForwardPct = 100 * SafeDivide( FutureClose - Close, Close, 0 );
Filter = Event AND NOT IsNull( Level ) AND NOT IsNull( FutureLow ) AND NOT IsNull( ZoneHalf );
// The 1/0 in the "Held" column is the value the summary row averages. Colour is// decoration only - the number is always readable as text.AddColumn( Level, "Level", 1.3 );AddColumn( Low, "Bar low", 1.3 );AddColumn( ZoneHalf, "Zone +/-", 1.3 );AddColumn( BreakDist, "Break distance", 1.3 );AddColumn( Held, "Held 1/0", 1.0, colorDefault, IIf( Held, ColorBlend( colorGreen, colorWhite, 0.8 ), ColorBlend( colorRed, colorWhite, 0.8 ) ) );AddColumn( FutureLow, "Lowest low next " + HoldBars, 1.3 );AddColumn( ForwardPct, "Forward move %", 1.2 );
// Flag 2 adds an AVERAGE row, flag 16 a COUNT row. The average of the Held// column is the hold rate - the single number this whole exercise exists for.AddSummaryRows( 2 | 16, 1.3 );
_SECTION_END();To produce evidence rather than an impression: a table of episodes you can count, sort, export and check, from a definition that another person could re-implement and get the same rows.
How it works
Section titled “How it works”Mode selection comes first. TestMode chooses the prior-low level, the displaced placebo
level or the unconditional base rate, and everything downstream is written once and applied
to whichever was chosen.
Level and zone are both computed with Ref(..., -1) so that the touch bar cannot
influence its own reference price. Event detection applies the re-arm rule, except in base
rate mode where (BarIndex() % SampleEvery) == 0 samples every n-th bar instead.
Outcome uses Ref(LLV(Low, HoldBars), HoldBars), which at bar t is the lowest low over
bars t+1 to t+HoldBars, excluding the touch bar itself. Held compares that with the
level minus the break distance.
Output is Filter plus a set of AddColumn calls, and AddSummaryRows(2 | 16, 1.3)
appends an average row and a count row. The average of a column of ones and zeroes is the
proportion — which is the single number the whole exercise exists to produce.
Key functions
Section titled “Key functions”ParamList(name, values, default)— returns the chosen item as a string, so the mode can be compared with==in anif.LLV(array, periods)withRef(..., -1)— the prior-window low, excluding today.Sum(array, periods)— the rolling count used to implement re-arming.BarIndex()— the zero-based bar number, used with the%modulus operator to sample.IsNull(x)— a synonym ofIsEmpty, used to drop warm-up and end-of-range rows.AddColumn(array, name, format, textColour, backgroundColour)— one exploration column.AddSummaryRows(flags, format)— flag 2 adds an average row, flag 16 a count row.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Before believing any aggregate, verify three individual rows by hand. Pick a row, open that symbol’s chart at that date, and check three things: that the level equals the lowest low of the 60 bars before the touch bar; that the bar’s low really is inside the zone; and that the “Held” verdict matches what the next ten bars did.
Then run a deliberate sanity check: set the break distance to 20 ATR. Every episode should now be recorded as held, and the average should be exactly 1.0. If it is not, the measurement is broken and no amount of interpretation will fix it.
Common errors
Section titled “Common errors”Extension
Section titled “Extension”Add a second outcome column: the forward move measured from the touch bar’s close over 20 bars as well as 10. Then compare the hold rate with the average forward move. It is entirely possible for a level to “hold” frequently while the average forward move is negative, because the definition of holding tolerates a drift downward that is smaller than half an ATR. Two measurements of the same episode disagreeing is not a contradiction; it is a sign that “support held” was a vaguer idea than it looked.
Why this test is harder than it looks
Section titled “Why this test is harder than it looks”Everything above is the easy part. Here is the honest list of what still stands between that table and a trustworthy conclusion.
Overlapping windows break independence. Two touches 5 bars apart share 5 of their 10 forward bars. Nearby symbols in the same sector share market-wide moves on the same dates. The effective number of independent observations is far smaller than the row count, so any significance calculation based on the row count overstates confidence, sometimes by a large factor. The re-arm rule and the base-rate sampling help; they do not fix it.
Trend contamination. In a universe and period that drifted upward, price frequently does not fall much further from wherever it is. A level test conducted only in such a period will show a high hold rate that has nothing to do with levels. This is precisely why the base rate is not optional.
The zone width moves the answer. Widen the zone and touches become more common and more casual; narrow it and only violent approaches qualify. The relationship is not monotonic in any obvious way, and there is no correct width. The only defensible presentation is a curve of hold rate against zone width, with the base rate plotted alongside it.
The garden of forking paths. Level definition, lookback, zone width, re-arm window, horizon, break distance, universe, period, and the definition of “held” itself — nine decisions, most with several defensible values. Trying them all and reporting the best will produce something impressive and meaningless every time. Fixing them in advance is the only real defence, and reporting the sensitivity across all of them is the second best.
Adjusted versus unadjusted history. A split-adjusted series moves every historical price, so levels computed on adjusted data sit where the split-adjusted low was, not where anybody watched price stop. Dividend adjustment does the same thing more gently and more often. Part 2 covers the choice; here it is enough to know that the two versions produce different levels, different touches and different answers.
Data errors manufacture touches. One erroneous low, 15% below the true one, creates a 60-day level that no participant ever saw, and then a touch of it. On free end-of-day data this happens often enough to matter across a few thousand rows.
Survivorship. Restated because it is the largest single bias here: the instruments whose levels most spectacularly failed are the ones most likely to be missing from your database.
Tradability is a separate question. Even a clear positive result would not be a strategy. Trading it requires an entry, an exit, a stop and a size, each of which adds assumptions, and the spread and commission have to come out of whatever the edge turns out to be. Parts 27 to 34 exist because that second journey is longer than the first.
What we are deliberately not doing
Section titled “What we are deliberately not doing”This lesson does not tell you the answer, and that is a decision rather than an omission.
Any number quoted here would come with a universe, a period, a vendor, an adjustment convention and nine parameter choices attached to it, and stripped of those it would be repeated as “the course found that support holds 63% of the time” — which would be exactly the kind of unmoored statistic this part exists to inoculate you against. Worse, it would let you skip the work, and the work is the lesson.
So the deliverable is yours. Run the three modes, record the three numbers with your universe and period beside them, compare them with the threshold you wrote down in step 6, and write two sentences of conclusion — including, if it applies, “the evidence here is inconclusive”.
Keep that record. Part 35 builds a research log properly, and this will be its first entry.
The reality-check template is six steps: state the claim in a form that could fail, define every term objectively, choose the universe and period before looking, define the measurement, define the comparison, and commit in advance to what would change your mind.
Applied to support levels, that meant a 60-day-low level measured one bar early, an ATR-scaled zone, a re-armed touch rule, a ten-bar forward window with an ATR-scaled break distance, and two controls: an unconditional base rate on ordinary bars and a placebo level displaced by an arbitrary percentage.
The obstacles are real and worth respecting: overlapping windows, trend contamination, a zone width that moves the answer, nine forking decisions, adjustment conventions, data errors, survivorship, and the gap between a statistical tendency and a tradable one.
You now have a template and a working measurement. What you do not have is a conclusion, because that one is yours to produce — and the next time somebody tells you that support tends to hold, you know precisely which six questions to ask them.
Check your understanding
Sources for this lesson
8 verified · checked 2026-08-31
- 01AFL Function Reference — LLVamibroker.com/guide/afl/llv.html2026-08-31
- 02AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 03AFL Function Reference — ATRamibroker.com/guide/afl/atr.html2026-08-31
- 04AFL Function Reference — AddColumnamibroker.com/guide/afl/addcolumn.html2026-08-31
- 05AFL Function Reference — AddSummaryRowsamibroker.com/guide/afl/addsummaryrows.html2026-08-31
- 06AFL Function Reference — ParamListamibroker.com/guide/afl/paramlist.html2026-08-31
- 07AFL Function Reference — BarIndexamibroker.com/guide/afl/barindex.html2026-08-31
- 08AmiBroker User's Guide — Explorationamibroker.com/guide/h_exploration.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.