Swings, Highs and Lows
Point at a chart and say “that is a swing high” and everyone in the room nods. Ask the room to write down the rule they just applied and the nodding stops. This lesson is about closing that gap: taking a judgement that feels like perception, turning it into an arithmetic test, and being honest about what the conversion costs.
By the end you will have one working swing definition, a clear account of what changing its single parameter does to your chart, and — the part that matters most for everything after Part 8 — the habit of asking of any visual claim: at what moment could this have been known?
What a swing point is supposed to be
Section titled “What a swing point is supposed to be”Informally, a swing high is a bar that stands above its neighbours: price rose into it and fell away from it. A swing low is the mirror image. Almost every structural idea in technical analysis is built on these two objects. Higher highs and higher lows, support and resistance levels, trendline anchors, measured moves, the necklines of head-and-shoulders patterns — all of them are statements about swing points, so they inherit whatever vagueness the swing definition contains.
The word doing all the hidden work is neighbours. Above which neighbours? How many? The bar next to it, or the twenty bars on either side? The moment you have to answer, you discover there is nothing in the data that answers for you.
The lookback problem
Section titled “The lookback problem”Here is the difficulty stated plainly. On any chart, the highest bar of the last three is one thing; the highest bar of the last thirty is usually a different bar; the highest bar of the whole history is a third. All three descriptions are true simultaneously. “Swing high” is not a property a bar has. It is a property of a bar and a window, and the window is supplied by you.
This has an unpleasant corollary. If you look at a chart and mark the swing highs by eye, you are applying a window — but not a constant one. Your eye adapts to the zoom level, to how much of the pane a move occupies, and to what you are hoping to find. Zoom out and the small swings you marked stop being swings. Zoom in and new ones appear. The set of swing points you produce is a function of your zoom setting, and you almost certainly did not intend that.
A definition you can write down
Section titled “A definition you can write down”The standard objective definition is an n-bar pivot, also called a fractal:
A bar is a swing high of strength n if its high is the highest high in the window that extends n bars either side of it.
Nothing is left to interpretation. n is a number you choose, the window is
2n + 1 bars wide, and the test is a comparison. In AFL, a rolling maximum over that
window is HHV( High, 2*n + 1 ), and the middle bar of the window — n bars back
from wherever we are standing — is Ref( High, -n ). The candidate is a swing high
exactly when those two are equal.
That is the whole rule:
Fragment — not a complete formula
Strength = 2;Window = 2 * Strength + 1;
CandidateHigh = Ref( High, -Strength ); // the bar in the middle of the windowSwingHigh = CandidateHigh == HHV( High, Window );Two details make it work. HHV() is documented as including the current bar in its
window, so a window of 2n + 1 bars covers the candidate plus exactly n bars on
each side. And both sides of the comparison are the same stored number pulled from the
same array — Ref selects an element of High, and HHV returns an element of
High — so the equality test is exact rather than an approximate float comparison.
Evaluated bar by bar
Section titled “Evaluated bar by bar”Here is that definition with Strength = 2, run across ten bars. Read it as one
column per bar: each row is an array, and the last row is the answer the rule gives at
that bar.
Ref(High, -2) == HHV(High, 5), evaluated on every bar
| Bar | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|
High | 10.0 | 10.6 | 11.4 | 11.0 | 10.2 | 10.5 | 11.1 | 11.9 | 11.5 | 11.2 |
Ref(High, -2) | — | — | 10.0 | 10.6 | 11.4 | 11.0 | 10.2 | 10.5 | 11.1 | 11.9 |
HHV(High, 5) | — | — | — | — | 11.4 | 11.4 | 11.4 | 11.9 | 11.9 | 11.9 |
SwingHigh | — | — | — | — | 1 | 0 | 0 | 0 | 0 | 1 |
Marker belongs on bar | — | — | — | — | 3 | — | — | — | — | 8 |
Three things are visible in that table that are hard to see on a chart.
The rule produces one value per bar, like every AFL expression. It is not a search that returns a list of swing points; it is a Boolean array with a 1 on the bars where the test passed. That is the array way of thinking, and Part 8 is devoted to it.
The 1 does not appear on the swing bar. It appears two bars later, because that is when the window is complete. Bar 3 is the swing; bar 5 is when you could first have known.
And the first four bars return nothing at all. Their window is not full yet. The
official pages for HHV and LLV do not state what those functions return before
their window fills, so the formula in this lesson refuses to answer there rather than
trusting an undocumented value — a small, unglamorous habit that prevents a whole
class of silent wrong answers.
Confirmation lag is not a bug
Section titled “Confirmation lag is not a bug”The life of one swing high
- Bar printsNothing is known yet; it is just a bar
- n bars passEach one either exceeds it or does not
- Test appliedIs the candidate still the window maximum?
- ConfirmedEarliest moment the answer exists
- Marker drawn backOnto the bar it describes, n bars ago
A swing high of strength n cannot be identified before n bars have printed after it. This is not a limitation of AmiBroker, of AFL, or of the definition. It is logical: the claim “nothing higher came in the next n bars” is a claim about the next n bars, and until they exist there is nothing to check.
Every downstream idea inherits this. “Price made a higher high” cannot be asserted until the higher high has been confirmed. A structure classification built on two confirmed swings is late by at least the strength of the swing definition. A support level anchored on a swing low did not exist as a level on the day the low was made.
The lag is also the reason larger is not better. A strength of 20 produces few, clean, important-looking swing points, and each of them is confirmed 20 bars late. A strength of 2 produces a cluttered chart confirmed 2 bars late. You are trading noise against timeliness, and there is no setting that wins both.
Ties, gaps and other edges
Section titled “Ties, gaps and other edges”Three edge cases will eventually bite you, so decide about them deliberately.
Ties. If two bars in the window share the highest high, both satisfy == and both
are marked. On a liquid instrument at four decimal places this is rare; on a
low-priced instrument with a coarse tick size, or in a quiet range, it is not. If you
need exactly one swing per window, you must add a tie-break — for instance requiring
the candidate to be strictly greater than the bars before it and greater than or
equal to the bars after.
Using high and low, or using close. The definition above uses High for swing
highs and Low for swing lows, which is conventional and matches how people read a
chart. Defining swings on closing prices instead is entirely legitimate, produces a
different set of points, and is more robust on instruments whose intraday extremes are
unreliable. Neither is correct in the abstract. State which you used.
Not enough history. With fewer than 2n + 1 bars loaded, the rule can never fire.
On a chart zoomed to sixty bars with a strength of 40, you will see nothing at all and
conclude the formula is broken.
Why every swing definition is a choice
Section titled “Why every swing definition is a choice”The n-bar pivot is not the only option, and knowing the alternatives is what stops you treating your first choice as the natural one.
A percentage-change definition ignores bar counts entirely and marks a new swing whenever price reverses by more than some percentage from the last extreme. This is what a zig-zag indicator does. It adapts to volatility in a way that a fixed bar count does not, and it has its own hidden parameter — the percentage — plus a much nastier property.
They remain useful for what the documentation recommends them for: pattern and trend recognition, and drawing. So this compiles, draws a beautiful chart, and must never feed a trading decision:
Fragment — not a complete formula
// Illustration of what NOT to build a signal on.// The official Peak() page states this function may look into the future.LastPeak = Peak( High, 5, 1 );An ATR-scaled definition sits between the two: require the candidate to exceed its neighbours by some multiple of average true range, so the threshold breathes with volatility. It has two parameters instead of one, which is a cost.
None of these is the true definition. There is no true definition. There are only definitions you have stated, whose parameters you have tested, and which you apply the same way every time.
The formula
Section titled “The formula”Put the definition on a chart, so that you can see which bars it selects, change its one parameter and watch the selection change, and read off how late each confirmation was. The formula deliberately does two things at once: it draws the marker on the bar the swing actually happened, because that is the useful picture, and it reports in the title how many bars after the fact the confirmation arrived, because that is the fact the picture hides.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// swing-points.afl// Part 4 - Swings, Highs and Lows//// Marks swing highs and swing lows using one explicit, stated definition://// a bar is a swing high if its High is the highest High of the window that// extends Strength bars either side of it.//// The test is applied Strength bars AFTER the candidate bar, which is the// earliest moment at which the answer is knowable. No future bar is read. The// arrow is then drawn back onto the candidate bar so the chart shows where the// swing was, while the title reports when it could first have been confirmed.//// Assumptions:// - Any instrument, any interval.// - A swing point is a definition, not a market fact. Change Strength and the// set of swing points changes with it. That is the lesson, not a defect.// - Ties are possible: if two bars in the window share the highest High, both// satisfy the definition. Decide deliberately whether you mind.
_SECTION_BEGIN("Swing points");
Strength = Param( "Swing strength (bars either side)", 3, 1, 25, 1 );Window = 2 * Strength + 1;
// Cum(1) counts 1, 2, 3, ... across the bars the formula was given, so it is a// simple way to discard the warm-up bars where the rolling window is not full.// The User's Guide does not document what HHV returns before its window fills,// so the formula refuses to answer there rather than trusting an undefined value.BarNumber = Cum( 1 );HasWindow = BarNumber >= Window;
// The candidate sits in the middle of the window: Strength bars back from the// bar we are standing on.CandidateHigh = Ref( High, -Strength );CandidateLow = Ref( Low, -Strength );
// HHV and LLV include the current bar, so a window of 2*Strength+1 bars covers// the candidate plus exactly Strength bars either side of it.WindowHigh = HHV( High, Window );WindowLow = LLV( Low, Window );
// The comparison is exact rather than approximate because both sides are the// same stored value pulled from the same array, not two separate calculations.SwingHighConfirmed = HasWindow AND CandidateHigh == WindowHigh;SwingLowConfirmed = HasWindow AND CandidateLow == WindowLow;
GraphXSpace = 6;
Plot( Close, "Price", colorDefault, styleCandle );
// The last argument shifts the marker back onto the candidate bar. The anchor// price is the candidate's own High or Low, so the arrow lands at the right// height. The offset before it is in screen pixels: positive is up.PlotShapes( IIf( SwingHighConfirmed, shapeDownArrow, shapeNone ), colorDarkRed, 0, CandidateHigh, 14, -Strength );
PlotShapes( IIf( SwingLowConfirmed, shapeUpArrow, shapeNone ), colorDarkGreen, 0, CandidateLow, -14, -Strength );
// The most recent confirmed swing points, and how long ago the confirmation// arrived. What BarsSince returns BEFORE the condition has ever been true is// not documented, so treat the two "bars ago" figures as meaningless until at// least one arrow of that kind is actually on the chart.LastSwingHigh = ValueWhen( SwingHighConfirmed, CandidateHigh, 1 );LastSwingLow = ValueWhen( SwingLowConfirmed, CandidateLow, 1 );BarsSinceHigh = BarsSince( SwingHighConfirmed );BarsSinceLow = BarsSince( SwingLowConfirmed );
_N( Title = Name() + " - " + Interval( 2 ) + StrFormat( " - swing strength %g, window %g bars\n", Strength, Window ) + StrFormat( "Last confirmed swing high %g, confirmed %g bars ago\n", LastSwingHigh, BarsSinceHigh ) + StrFormat( "Last confirmed swing low %g, confirmed %g bars ago\n", LastSwingLow, BarsSinceLow ) + StrFormat( "Every arrow appeared %g bars after the bar it points at.", Strength ) );
_SECTION_END();How it works
Section titled “How it works”The first block reads the strength and computes the window width. The second block
builds the warm-up guard: Cum( 1 ) counts 1, 2, 3, … across the bars the formula was
given, so comparing it with the window width identifies the bars where the rolling
window is not yet full.
The third block is the definition itself, applied to highs and to lows. The fourth
draws it. The last argument of each PlotShapes() call is -Strength, which shifts
the marker back onto the bar it describes; the anchor price is the candidate bar’s own
high or low, so the arrow lands at the right height rather than at the height of
whatever bar happens to be current.
The title then reports the two most recent confirmed swings, how long ago they were confirmed, and a standing reminder of the lag.
Key functions
Section titled “Key functions”Ref( array, period )shifts an array in time. A negative period looks back; a positive period looks forward, and the official page says so explicitly. EveryRefin this formula is negative, which is what makes it safe.HHV( array, periods )andLLV( array, periods )are rolling extremes over a trailing window that includes the current bar.Cum( array )is a running total from the first bar of the range.Cum( 1 )is the documented idiom for a bar counter.ValueWhen( expression, array, n )returns the value the array had on the n-th most recent bar where the expression was true. Note the argument order — the condition comes first, and swapping the first two arguments compiles without complaint.BarsSince( expression )counts bars since the expression was last true.PlotShapes( shape, colour, layer, yposition, offset, XShift )draws glyphs. Theoffsetis in screen pixels, not price units, and its sign is inverted from intuition: negative shifts down.XShiftshifts the glyphs by a number of bars.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Set the strength to 3 and pick any marked swing high. Count the bars either side and confirm none of them has a higher high — that is the definition, checked by hand.
Then do the test that matters. Note the date of the most recent arrow. Count forward three bars from it and confirm that no arrow existed on the chart until that later bar had printed. The easiest way to see this is to zoom the chart so that the last visible bar is the swing bar itself: the arrow is not there.
Common errors
Section titled “Common errors”- No arrows anywhere. Fewer than
2 × Strength + 1bars loaded, or the strength set higher than the visible history supports. - Arrows in unexpected places on a thin instrument. Ties. Two bars in the window share the same high, and both satisfy the rule.
- Arrows overlapping the candles. The pixel
offsetis a matter of taste and chart height. Adjust the14and-14, and remember thatGraphXSpaceopens up head and foot room in the pane. - A swing that “should” be there is missing. Almost always correct behaviour: one bar within the window exceeded the candidate. Zoom in and check the highs rather than the shape.
Extension
Section titled “Extension”Add a second strength — say 3 and 10 — and plot both sets of markers in different colours on one chart. The relationship between them is worth an hour of study on its own: the larger set is a subset of the smaller one, and the bars where the two agree are the turns that survive a change of scale.
What changed
Section titled “What changed”You arrived with a phrase, “swing high”, that felt like an observation. You leave with
a rule, a parameter, and a bill. The rule is Ref( High, -n ) == HHV( High, 2n+1 ).
The parameter is n, and it has no correct value. The bill is n bars of
confirmation lag, which cannot be paid off, negotiated down, or ignored without
producing results that could not have been achieved in real time.
The next lesson stacks confirmed swings against each other to build the higher-high, higher-low vocabulary — and inherits every one of these caveats.
Check your understanding
Sources for this lesson
10 verified · checked 2026-08-31
- 01AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 02AFL Function Reference — HHVamibroker.com/guide/afl/hhv.html2026-08-31
- 03AFL Function Reference — LLVamibroker.com/guide/afl/llv.html2026-08-31
- 04AFL Function Reference — Cumamibroker.com/guide/afl/cum.html2026-08-31
- 05AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
- 06AFL Function Reference — PlotShapesamibroker.com/guide/afl/plotshapes.html2026-08-31
- 07AFL Function Reference — Zigamibroker.com/guide/afl/zig.html2026-08-31
- 08AFL Function Reference — Peakamibroker.com/guide/afl/peak.html2026-08-31
- 09AFL Function Reference — Troughamibroker.com/guide/afl/trough.html2026-08-31
- 10AFL Function Reference — BarsSinceamibroker.com/guide/afl/barssince.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.