Lab: Map the Structure of a Real Chart
Reading a chart feels objective while you are doing it. This lab is designed to show you, with your own numbers, how much of it is not — and, more usefully, exactly which parts.
What you will produce
Section titled “What you will produce”Three completed structure maps, on three different instruments, each built by the same eight-step procedure. Then a disagreement log: the same three charts read a second time, independently, either by a partner or by you after a gap, with the two readings compared element by element.
The deliverable that matters is the log, not the maps. By the end you should be able to say something like “our prior-month levels agreed exactly, our swing zones agreed on four out of six, and our trendlines shared no anchor points at all” — which is a far more useful thing to know about chart reading than any opinion about whether it works.
Budget about 45 minutes: 25 for your own three maps, 10 for the second reading, 10 for the comparison and the write-up.
Before you start
Section titled “Before you start”You need:
- AmiBroker with a daily database containing at least three years of history for three liquid instruments. Free end-of-day data is entirely sufficient; nothing here needs a subscription, a live feed or the Professional edition.
- Three instruments chosen for variety, not for interest. Pick one that trended persistently over the period, one that spent most of the period range-bound, and one that changed character somewhere in the middle. Choose them by glancing at three-year charts and do not look more closely than that yet.
- A partner, ideally. Failing that, the same person 24 hours later, working from the procedure and not from the earlier map. Self-comparison is weaker evidence — you will remember some of your marks — but it still works, and the memory effect biases the result towards agreement, which means any disagreement you do find is real.
- The helper formula below, saved in the Formula Editor.
The marking procedure
Section titled “The marking procedure”Follow these in order. The order is deliberate: the steps that need no judgement come first, so that they cannot be influenced by the ones that do.
The eight steps, in order
- 1. Fix the frameSymbol, interval, date range, scale, bars visible — written down
- 2. Mark swing points by ruleCentred n-bar pivots, n stated, not by eye
- 3. Convert points into zonesHalf-width = a stated multiple of ATR
- 4. Keep only zones with two or more touchesThe anti-cherry-picking step
- 5. Add the zero-discretion levelsPrevious month high and low, round numbers
- 6. Draw exactly one trendlineAnd write its full justification
- 7. Mark consolidations by the meterPercentile threshold, not impression
- 8. Write the structure briefIncluding what you do not know
Step 1: fix the frame
Section titled “Step 1: fix the frame”Set the interval to daily, choose a fixed date range and stick to it, choose linear or logarithmic scaling and stick to that, and note roughly how many bars are on screen.
This step exists because the structure you find depends on all four. A level that is obvious across 250 bars can be invisible across 1,000. A trendline that is intact on a log scale can be broken on a linear one. Two people who have not agreed the frame are not reading the same chart, and any disagreement between them is uninformative.
Record it like this:
Symbol: ABC Interval: Daily Range: 2022-01-01 to 2024-12-31Scale: semi-log Bars visible: about 750Step 2: mark swing points by rule
Section titled “Step 2: mark swing points by rule”Apply the helper formula. It marks a bar as a swing high when that bar is the highest of the
PivotN bars either side of it, and a swing low by the mirror rule. Set PivotN to 5 and
leave it there for all three instruments.
You are not choosing these points. That is the entire idea. If a peak you consider important is not marked, it is not a five-bar pivot, and your disagreement is with the rule rather than with the chart — which you may then argue about explicitly.
Step 3: convert points into zones
Section titled “Step 3: convert points into zones”The formula draws a band of half-width ZoneMult × ATR(20) around the most recent two swing
highs and the most recent two swing lows. Use 0.3 for all three instruments.
Read the zone half-width off the title bar and write it down in price units for each symbol. You will need it when you compare readings: “we both marked a zone at about 47” means nothing until you know whether “about” is 0.10 or 1.50.
Step 4: keep only zones with two or more touches
Section titled “Step 4: keep only zones with two or more touches”Now do something the formula cannot do for you. For each of the four zones on the chart, count how many separate occasions price entered the zone — separate meaning that price left the zone for at least ten bars in between. Discard every zone with fewer than two.
This is the step that hurts, and it is the most valuable one in the lab. The zones you are about to discard are often the ones you find most convincing, because you remember the single dramatic reaction that formed them. A single reaction at a price is exactly what a swing high is; it is not additional evidence that the price matters.
Step 5: add the zero-discretion levels
Section titled “Step 5: add the zero-discretion levels”Two families, both drawn by the formula or trivially added:
- the previous calendar month’s high and low, plotted as staircases;
- the round numbers nearest current price, at whatever grid step you fixed in the earlier lesson.
Nobody can disagree about where these are. They are the control group: when you compare readings later, any disagreement here is a data or procedure error, not a difference of interpretation.
Step 6: draw exactly one trendline
Section titled “Step 6: draw exactly one trendline”One. Not the three or four you can see. Use AmiBroker’s trend line tool from the Draw
toolbar or the Insert menu, enable right extension in its properties, and give it a Study ID
of "SU" or "RE" so you can reference it later.
Then write the justification in full. It must contain all six of the following, because these are the six free choices identified in the trendlines lesson:
Anchors: 2022-06-17 low and 2022-10-13 lowAnchored to: wicks (bar lows)Scale: semi-logDirection: rising supportTouches: 3 (the two anchors plus 2023-03-10)Redraw rule: none - if this line breaks I will record the break, not redrawThe redraw line is not optional. Committing in advance to not redrawing is what turns the line from a description into a claim.
Step 7: mark consolidations by the meter
Section titled “Step 7: mark consolidations by the meter”Add the consolidation meter from the previous lesson in a second pane, or use the tightness ribbon that the helper formula already draws along the bottom of the price pane. A consolidation, for the purposes of this lab, is five or more consecutive bars whose range percentile is below 20.
Note the start and end dates of each such episode. Do not add episodes that look like consolidations but do not meet the rule, and do not remove ones that meet it but do not look right. Both temptations will arise; resisting them is the exercise.
Step 8: write the structure brief
Section titled “Step 8: write the structure brief”Roughly 120 words per instrument, covering: the current structural state in the vocabulary from Part 4, the surviving zones with their widths, the one trendline and its status, the consolidations found, and — this is the part people skip — what you do not know.
A worked example of the last sentence, so that the standard is clear:
I do not know whether the 47.0 zone matters, because both of its touches occurred during the same six-week period and may be one episode rather than two. I do not know whether the trendline would survive on a linear scale; I did not check.
The helper formula
Section titled “The helper formula”Complete runnable AFL
// structure-map.afl// Part 5 - Lab: Map the Structure of a Real Chart//// The lab's marking tool. It applies ONE stated rule to each element of the// map, so that two people running it on the same symbol over the same range see// the same picture. Whatever you then add by hand is, by definition, the// discretionary part of your reading - which is exactly what the lab measures.//// What it draws:// * centred n-bar swing highs and lows (a bar that is the highest, or lowest,// of the PivotN bars on either side of it);// * a zone of stated half-width around the last two swing highs and the last// two swing lows;// * the previous calendar month's high and low, which involve no judgement// at all;// * a ribbon along the bottom marking bars whose range sits in the tightest// part of its own recent history.//// DELIBERATE LOOK-AHEAD - read this before reusing any line below.// A centred pivot cannot be known until PivotN bars after it happened. The// CONDITION here is computed without look-ahead: at each bar it asks whether the// bar PivotN bars ago was the extreme of the surrounding window. To draw the// marker AT the pivot rather than PivotN bars to its right, the formula then// shifts that flag with Ref( flag, +PivotN ), which reads a future bar. That is// acceptable because the result is only ever drawn. Never copy that line into a// Buy or Sell rule: it would use data that did not exist at the time. The last// PivotN bars of the chart therefore cannot show a confirmed pivot yet.//// Assumptions: daily bars; at least RankLen + RangeLen bars of history before the// percentile readings mean anything; a split-adjusted series.
_SECTION_BEGIN("Structure map");
PivotN = Param( "Pivot half-width (bars each side)", 5, 2, 30, 1 );ZoneMult = Param( "Zone half-width (x ATR)", 0.3, 0.05, 2, 0.05 );AtrLen = Param( "ATR period", 20, 2, 100, 1 );RangeLen = Param( "Range window (bars)", 20, 5, 120, 1 );RankLen = Param( "Tightness lookback (bars)", 250, 50, 1000, 10 );TightPct = Param( "Tight threshold (percentile)", 20, 1, 50, 1 );
PivotWindow = 2 * PivotN + 1;
// Ref( High, -PivotN ) is the centre bar of the window that ends today, so this// comparison is made PivotN bars AFTER the pivot. No look-ahead in the test.IsPivotHigh = Ref( High, -PivotN ) == HHV( High, PivotWindow );IsPivotLow = Ref( Low, -PivotN ) == LLV( Low, PivotWindow );
// Drawing only: move the confirmed flag back onto the pivot bar itself.PivotHighHere = Ref( IsPivotHigh, PivotN );PivotLowHere = Ref( IsPivotLow, PivotN );
HalfWidth = ZoneMult * ATR( AtrLen );
// The price of the most recent pivot of each kind, and the one before it.LastHigh = ValueWhen( PivotHighHere, High, 1 );PrevHigh = ValueWhen( PivotHighHere, High, 2 );LastLow = ValueWhen( PivotLowHere, Low, 1 );PrevLow = ValueWhen( PivotLowHere, Low, 2 );
// Previous calendar month's extremes. Nobody has to agree with anybody about// where these are, which makes them the control group for the lab.NewMonth = Month() != Ref( Month(), -1 );MonthHigh = HighestSince( NewMonth, High );MonthLow = LowestSince( NewMonth, Low );PrevMonthHigh = ValueWhen( NewMonth, Ref( MonthHigh, -1 ) );PrevMonthLow = ValueWhen( NewMonth, Ref( MonthLow, -1 ) );
RangeWidth = 100 * SafeDivide( HHV( High, RangeLen ) - LLV( Low, RangeLen ), Close, 0 );IsTight = PercentRank( RangeWidth, RankLen ) < TightPct;
Plot( Close, "Close", colorDefault, styleCandle );
PlotOHLC( LastHigh + HalfWidth, LastHigh + HalfWidth, LastHigh - HalfWidth, LastHigh - HalfWidth, "Swing-high zone (latest)", ColorBlend( colorRed, colorWhite, 0.72 ), styleCloud | styleNoLabel );
PlotOHLC( PrevHigh + HalfWidth, PrevHigh + HalfWidth, PrevHigh - HalfWidth, PrevHigh - HalfWidth, "Swing-high zone (previous)", ColorBlend( colorRed, colorWhite, 0.86 ), styleCloud | styleNoLabel );
PlotOHLC( LastLow + HalfWidth, LastLow + HalfWidth, LastLow - HalfWidth, LastLow - HalfWidth, "Swing-low zone (latest)", ColorBlend( colorBlue, colorWhite, 0.72 ), styleCloud | styleNoLabel );
PlotOHLC( PrevLow + HalfWidth, PrevLow + HalfWidth, PrevLow - HalfWidth, PrevLow - HalfWidth, "Swing-low zone (previous)", ColorBlend( colorBlue, colorWhite, 0.86 ), styleCloud | styleNoLabel );
Plot( PrevMonthHigh, "Previous month high", colorDarkGreen, styleStaircase );Plot( PrevMonthLow, "Previous month low", colorDarkGreen, styleStaircase );
// The tightness ribbon uses its own scale so it sits along the bottom of the// pane instead of squashing the price axis.Plot( IsTight, "Tight range", ColorBlend( colorOrange, colorWhite, 0.5 ), styleArea | styleOwnScale | styleNoLabel, 0, 20 );
PlotShapes( IIf( PivotHighHere, shapeSmallCircle, shapeNone ), colorRed, 0, High, 14 );PlotShapes( IIf( PivotLowHere, shapeSmallCircle, shapeNone ), colorBlue, 0, Low, -14 );
PivotHighCount = LastValue( Cum( PivotHighHere ) );PivotLowCount = LastValue( Cum( PivotLowHere ) );
Title = Name() + " " + Interval( 2 ) + " | pivots: " + PivotWindow + "-bar centred (" + NumToStr( PivotHighCount, 1.0 ) + " highs, " + NumToStr( PivotLowCount, 1.0 ) + " lows on loaded bars)" + " | zone half-width " + WriteVal( HalfWidth, 1.2 ) + " | last swing high " + WriteVal( LastHigh, 1.2 ) + ", last swing low " + WriteVal( LastLow, 1.2 );
_SECTION_END();To do the mechanical part of the map — pivots, zones, prior-month levels, tightness — by a stated rule, so that everything left over is visibly your judgement rather than invisibly mixed in with the arithmetic.
How it works
Section titled “How it works”Pivots. A bar is a swing high when Ref(High, -PivotN) equals HHV(High, 2*PivotN+1):
in words, the bar in the middle of the window is the highest of it. Because this is evaluated
PivotN bars after the pivot, the condition contains no look-ahead.
Drawing the pivots in the right place. To put the marker on the pivot bar rather than
PivotN bars to its right, the formula shifts the confirmed flag forward with
Ref(flag, +PivotN). That is a deliberate forward reference, and the header comment says so
in capitals. It is legitimate here for one reason only: the array is drawn and never feeds a
signal. A consequence you will see on screen is that the last PivotN bars never carry a
marker, because their pivot status is not yet decided.
Zones. ValueWhen(condition, array, n) returns the value of an array at the n-th most
recent occurrence of a condition, so ValueWhen(PivotHighHere, High, 2) is the price of the
swing high before last. Four such values, each turned into a band by adding and subtracting
ZoneMult * ATR(AtrLen) and drawn with PlotOHLC in styleCloud.
Prior-month levels. A new month is detected by Month() != Ref(Month(), -1).
HighestSince(NewMonth, High) runs a high-water mark from the start of the current month —
note the argument order, condition first and array second, which is the reverse of how the
name reads. Taking its value one bar before each month change and holding it with ValueWhen
gives the completed previous month’s extreme.
Tightness ribbon. The same range-width percentile as the consolidation meter, plotted
with styleOwnScale and an explicit maximum so that it occupies a strip at the bottom of the
pane instead of flattening the price axis.
Key functions
Section titled “Key functions”Ref(array, period)— negative shifts back, positive shifts forward into data that did not exist at the time.ValueWhen(expression, array, n)— the array’s value at the n-th most recent occurrence.HighestSince(expression, array)/LowestSince(expression, array)— running extreme since a condition was last true. Condition first.PercentRank(array, range)— position within the array’s own recent history, 0 to 100.PlotOHLC(...)withstyleCloud— fills a band.PlotShapes(shape, colour, layer, yposition, offset)— places a marker; a positive offset raises it above the bar, a negative one drops it below.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Set PivotN to 2 and count the markers. Set it to 15 and count again. The counts should
differ by a large multiple, and — this is the point — the set of marked points at 15 is not
simply a subset of the set at 2 in the way you might expect, because a wider window can
promote a different bar within a cluster. Two people using different pivot widths are not
disagreeing about the market; they are running different rules.
Then check the look-ahead claim directly. Note the date of the last marked swing high. Now shorten the chart’s date range so it ends ten bars earlier, and look again. Markers near the new right-hand edge will have disappeared, because their confirming bars are no longer loaded. That is the confirmation lag made visible.
Common errors
Section titled “Common errors”Extension
Section titled “Extension”Add a second zone pair for the third most recent swing high and low, using ValueWhen(..., 3), and observe how quickly the chart becomes unreadable. The clutter is the argument for
step 4: a map that marks everything communicates nothing.
Comparing two independent readings
Section titled “Comparing two independent readings”Now the measurement. Put the two maps of the same instrument side by side and fill in this table. Do it for each of the three instruments.
| Element | Reading A | Reading B | Agreed? | Notes |
|---|---|---|---|---|
| Frame (range, scale) | Should always agree | |||
| Previous month high | Should always agree | |||
| Previous month low | Should always agree | |||
| Surviving zones (count) | ||||
| Zones agreeing within one half-width | The key number | |||
| Trendline anchor 1 (date) | ||||
| Trendline anchor 2 (date) | ||||
| Trendline still intact? | ||||
| Consolidation episodes (count) | ||||
| Episodes with overlapping dates | ||||
| Structural classification | Trend / range / transition |
Two zones “agree” when the centre of one falls inside the other. Two consolidation episodes “agree” when their date ranges overlap by at least half the shorter one. Fix those two conventions before you start scoring, not after you see the results.
Then compute three simple fractions per instrument: the proportion of zones that agreed, the proportion of consolidation episodes that agreed, and whether the two structural classifications matched.
What the disagreement tells you
Section titled “What the disagreement tells you”Almost everyone finds the same shape of result, and it is worth predicting it before you look so that you can be surprised if you are wrong.
| Element | Typical agreement | Why |
|---|---|---|
| Previous month high and low | Total | No parameters, no judgement |
| Round numbers | Total, once the grid step is agreed | The only choice is the step |
Swing points at a fixed PivotN |
Total | A rule computed it, not a person |
| Surviving zones after step 4 | Partial | The touch count involves judgement about what counts as leaving the zone |
| Consolidation episodes | Partial to good | The rule is objective; the boundaries are fuzzy |
| Trendline anchors | Poor | Several free choices, made independently |
| Structural classification | Mixed | Depends on which zones each reader kept |
The lesson is not “chart reading is worthless”. It is more precise and more useful than that: discretion enters at identifiable points, and those points are where disagreement concentrates. The parts of a chart reading that a rule produced are reproducible. The parts that a person produced are not, and the gap between two readings is a direct measurement of how much of your analysis was you.
That has three practical consequences for everything that follows in this course.
First, when you eventually test an idea, test the reproducible version of it. A test of “hand-drawn support” is untestable in principle, because no two runs are the same study. A test of “the lowest low of the previous 60 bars” is a real experiment.
Second, when a rule-based version of an idea performs worse than your discretionary version seemed to, consider both explanations before choosing the flattering one. Perhaps your judgement adds something the rule misses. Perhaps your judgement had the benefit of already knowing what happened next. This lab does not settle which, but it should have made the second explanation feel much more live than it did an hour ago.
Third, the discretion ladder from the table above is a design tool. When you build a screening rule in Part 12 or a system in Part 27, prefer the elements near the top. Not because the ones lower down are wrong, but because you can find out whether the ones at the top are.
Deliverables checklist
Section titled “Deliverables checklist”- Three frame records, written before marking.
- Three structure maps produced by the eight-step procedure.
- Zone survival counts for each instrument, with the discard ratio.
- One trendline per instrument with its six-line justification.
- Consolidation episodes listed with start and end dates.
- Three 120-word briefs, each containing an explicit “what I do not know”.
- A comparison table per instrument, with the two agreement fractions computed.
- Two or three sentences on which elements disagreed most, and why you think so.
You have applied a fixed procedure rather than an impression, and you have measured how much two applications of it by two people have in common. The elements produced by rules agreed; the elements produced by judgement did not, and the trendline anchors probably agreed least of all.
You have also met, on your own data, the two habits this part exists to install: discarding levels that rest on a single event, and writing down the choices — frame, pivot width, zone width, anchors, scale — that were invisible until you were asked to record them.
The next lesson takes one of the reproducible elements and asks the question this whole part has been circling: does it actually do anything?
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AmiBroker User's Guide — Charting guide§ Using drawing toolsamibroker.com/guide/h_charting.html2026-08-31
- 02AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 03AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
- 04AFL Function Reference — HighestSinceamibroker.com/guide/afl/highestsince.html2026-08-31
- 05AFL Function Reference — LowestSinceamibroker.com/guide/afl/lowestsince.html2026-08-31
- 06AFL Function Reference — PercentRankamibroker.com/guide/afl/percentrank.html2026-08-31
- 07AFL Function Reference — PlotShapesamibroker.com/guide/afl/plotshapes.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.