Component 1: Market Dashboard
Requirements
Section titled “Requirements”One pane that answers four questions about the market as a whole, before you look at any individual instrument:
- Is the benchmark above or below its own long-term average, and by how much in units of its own volatility?
- How widely is the move shared? — breadth, from the Part 16 composites.
- Is the range wide or narrow by the benchmark’s own recent standards?
- Which of your sector proxies have been strongest over a fixed window?
None of the four predicts anything. Every one describes a state that already exists, and the dashboard exists so that the state is written down before you start looking for reasons to trade.
Prerequisites
Section titled “Prerequisites”- Part 16 breadth composites, built once
by running
breadth-composite-builder.aflas a Scan over your universe. - The band-plotting technique from Part 10’s analysis panel.
- A benchmark symbol in your database.
Design decisions to document
Section titled “Design decisions to document”Four, and each one changes what the dashboard means. Write your answers in the research log before you run it.
Which benchmark? The dashboard is plotted on it, and Foreign() aligns everything else to the
current symbol’s bars. Choose one whose trading calendar matches your universe. A broad index is
usual; if you trade a sector, a sector index is more informative and a broad one is more comparable.
Which universe were the breadth composites built from? This is the question people skip, and it matters more than the benchmark. Breadth measured over a different universe than the one you trade is a statement about somebody else’s market. If your composites cover 500 large caps and you trade 30 mid caps, the participation figure is not about your candidates.
Which sector proxies? They must exist in your database and share the benchmark’s calendar. Index symbols, sector ETFs or your own composites are all valid; a handful of representative shares is not, because one company’s news becomes “the sector”.
What lookback for “strength”? 63 bars is about a quarter. Longer measures a slower thing. There is no right answer, and the point is that the number is a choice you made rather than one you inherited.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// market-dashboard.afl// Capstone Component 1 - Market Dashboard//// GOAL// One pane that answers four questions about the market as a whole, before// you look at any individual instrument:// 1. Is the benchmark above or below its own long-term average, and by how// much in units of its own volatility?// 2. How widely is the move shared? (breadth, from the Part 16 composites)// 3. Is the range wide or narrow by the benchmark's own recent standards?// 4. Which of your sector proxies have been strongest over a fixed window?//// None of the four predicts anything. Every one of them describes a state// that already exists, and the dashboard exists so that the state is written// down before you start looking for reasons to trade.//// WHERE TO PLOT IT// On the benchmark symbol itself, in its own pane. Foreign() aligns foreign// data to the CURRENT symbol, so plotting this on an instrument that does not// trade every session silently deletes bars from the picture.//// PREREQUISITE// The breadth composites from Part 16. Run breadth-composite-builder.afl as// a Scan over your universe first. If they are missing, the breadth band is// drawn empty and the title says so rather than showing a plausible zero.//// ASSUMPTIONS - write these into your capstone report// Interval daily end-of-day bars.// Benchmark the symbol this pane is plotted on.// Universe whatever universe the breadth composites were built from.// Breadth measured over a different universe than the one you// trade is a statement about somebody else's market.// Sectors the proxy symbols named in the parameter below. They must// exist in the database and share the benchmark's calendar.// Warm-up nothing on this pane means anything until the longest// look-back has filled. The title reports how many bars that is.// Not modelled dividends, currency, and anything at all about the future.
_SECTION_BEGIN( "Capstone market dashboard" );
// The advance/decline line is a running total, so the pane must see every bar// rather than only the ones currently on screen.SetBarsRequired( sbrAll, sbrAll );SetChartOptions( 2, chartWrapTitle );
// ---------------------------------------------------------------- settingsBreadthPrefix = ParamStr( "Breadth composite prefix", "~BR_" );SectorList = ParamStr( "Sector proxy symbols (comma separated)", "" );TrendPeriod = Param( "Benchmark trend average", 200, 20, 400, 10 );AtrPeriod = Param( "ATR period", 20, 2, 100, 1 );RankPeriod = Param( "Volatility percentile look-back", 252, 20, 1000, 1 );SectorPeriod = Param( "Sector strength look-back (bars)", 63, 5, 500, 1 );StretchCap = Param( "Trend band limit (ATR units)", 4, 1, 12, 0.5 );
UpTint = ParamColor( "Positive colour", colorBrightGreen );DownTint = ParamColor( "Negative colour", colorRed );QuietTint = ParamColor( "Neutral colour", colorBlueGrey );GuideTint = ParamColor( "Guide colour", colorLightGrey );
RequiredBars = Max( Max( TrendPeriod, RankPeriod ), SectorPeriod ) + 1;Ready = BarIndex() >= RequiredBars;
// ------------------------------------------------------------- band helper// Identical to the Part 10 analysis panel: minvalue/maxvalue are documented as// applying to styleOwnScale plots only, so a band is produced by choosing// scale bounds that place DataMin at BandBottom and DataMax at BandTop, where// both band figures are pane fractions running 0 at the foot to 1 at the top.function PlotInBand( DataSeries, PlotName, PlotTint, StyleBits, DataMin, DataMax, BandBottom, BandTop ){ local Span, ScaleMin;
Span = ( DataMax - DataMin ) / ( BandTop - BandBottom ); ScaleMin = DataMin - BandBottom * Span;
Plot( DataSeries, PlotName, PlotTint, StyleBits | styleOwnScale | styleNoLabel, ScaleMin, ScaleMin + Span );
return Span;}
// ----------------------------------------------------- 1. benchmark trendTrendLine = MA( Close, TrendPeriod );Volatility = ATR( AtrPeriod );
// Distance from the average in ATR units, so the number means the same thing// on any benchmark, then clipped so one extreme bar cannot flatten the band.Stretch = SafeDivide( Close - TrendLine, Volatility, Null );Stretch = Max( Min( Stretch, StretchCap ), -StretchCap );Stretch = IIf( Ready, Stretch, Null );
StretchTint = IIf( Stretch > 0, UpTint, DownTint );
PlotInBand( 0, "", GuideTint, styleLine | styleNoTitle, -StretchCap, StretchCap, 0.72, 1.00 );PlotInBand( Stretch, "Benchmark distance from trend (ATR units)", StretchTint, styleHistogram | styleThick, -StretchCap, StretchCap, 0.72, 1.00 );
// ------------------------------------------------------------- 2. breadthMembers = Nz( Foreign( BreadthPrefix + "MEMBERS", "V" ) );AboveSlow = Nz( Foreign( BreadthPrefix + "ABOVE200", "V" ) );NewHighs = Nz( Foreign( BreadthPrefix + "NEWHIGH", "V" ) );NewLows = Nz( Foreign( BreadthPrefix + "NEWLOW", "V" ) );
HaveBreadth = LastValue( Members ) > 0;PctAboveSlow = 100 * SafeDivide( AboveSlow, Members, Null );NetNewHighs = 100 * SafeDivide( NewHighs - NewLows, Members, Null );
PlotInBand( 50, "", GuideTint, styleLine | styleDashed | styleNoTitle, 0, 100, 0.40, 0.68 );PlotInBand( PctAboveSlow, "Percent of members above their 200-bar average", QuietTint, styleLine | styleThick, 0, 100, 0.40, 0.68 );
// -------------------------------------------------------- 3. volatilityAtrPercent = 100 * SafeDivide( Volatility, Close, Null );VolRank = IIf( Ready, PercentRank( AtrPercent, RankPeriod ), Null );
VolTint = IIf( VolRank >= 80, DownTint, IIf( VolRank <= 20, UpTint, QuietTint ) );
PlotInBand( 20, "", GuideTint, styleLine | styleDashed | styleNoTitle, 0, 100, 0.00, 0.32 );PlotInBand( 80, "", GuideTint, styleLine | styleDashed | styleNoTitle, 0, 100, 0.00, 0.32 );PlotInBand( VolRank, "Benchmark volatility percentile", VolTint, styleHistogram, 0, 100, 0.00, 0.32 );
// --------------------------------------------------- 4. sector strength// Each proxy's plain percentage change over SectorPeriod bars, read on the// SELECTED bar. This is display only: it reads one bar and must never be used// to build a signal.SectorCount = StrCount( SectorList, "," );if( StrLen( SectorList ) > 0 ) SectorCount = SectorCount + 1;else SectorCount = 0;
SectorText = "";SectorsRead = 0;
for( i = 1; i <= SectorCount; i++ ){ Ticker = StrExtract( SectorList, i - 1 );
if( StrLen( Ticker ) > 0 ) { SectorClose = Foreign( Ticker, "C" );
// A missing symbol yields an empty array. Say so instead of printing a // plausible zero next to the ones that worked. if( SelectedValue( IsNull( SectorClose ) ) ) { SectorText = SectorText + " " + Ticker + " n/a"; } else { SectorMove = SelectedValue( ROC( SectorClose, SectorPeriod ) ); SectorText = SectorText + " " + Ticker + " " + NumToStr( SectorMove, 1.1 ) + "%"; SectorsRead = SectorsRead + 1; } }}
if( SectorCount == 0 ) SectorText = " (no sector proxies configured - set them in the " + "Parameters dialog)";
// ------------------------------------------------------------------ titleTrendWord = WriteIf( Stretch > 0.5, "above trend", WriteIf( Stretch < -0.5, "below trend", "at trend" ) );
BreadthWord = WriteIf( NOT HaveBreadth, "NO COMPOSITE DATA", WriteIf( PctAboveSlow > 60, "broad participation", WriteIf( PctAboveSlow < 40, "narrow participation", "mixed participation" ) ) );
VolWord = WriteIf( VolRank >= 80, "wide range", WriteIf( VolRank <= 20, "narrow range", "ordinary range" ) );
_N( Title = StrFormat( "%s %s bars loaded %g warm-up needed %g\n" + "Trend: %s (%g ATR from the %g-bar average) Volatility: %s (percentile %g)\n" + "Breadth: %s %g%% of %g members above their 200-bar average net new highs %g%%\n" + "Sector %g-bar change:%s\n" + "Every line above describes what has already happened. None of it is a forecast.", Name(), Interval( 2 ), BarCount, RequiredBars, TrendWord, SelectedValue( Stretch ), TrendPeriod, VolWord, SelectedValue( VolRank ), BreadthWord, SelectedValue( PctAboveSlow ), SelectedValue( Members ), SelectedValue( NetNewHighs ), SectorPeriod, SectorText ) );
_SECTION_END();How it works
Section titled “How it works”The bands
Section titled “The bands”PlotInBand() is the same helper as Part 10’s analysis panel, and for the same reason: minvalue
and maxvalue are documented as applying to styleOwnScale plots only, so three series with
different natural ranges can share a pane only if each is given scale bounds that place its data
inside its own horizontal slice.
Three bands, with deliberate gaps between them so a spike in one is never mistaken for part of the next.
The benchmark trend
Section titled “The benchmark trend”Distance from the long average, divided by ATR, then clipped:
Fragment — not a complete formula
Stretch = SafeDivide( Close - TrendLine, Volatility, Null );Stretch = Max( Min( Stretch, StretchCap ), -StretchCap );Dividing by ATR is what makes the number portable across benchmarks. The clip is a display decision — it keeps one extreme bar from flattening the band — and the parameter is named “Trend band limit” rather than something implying the data was changed for analytical reasons.
The breadth band
Section titled “The breadth band”Fragment — not a complete formula
Members = Nz( Foreign( BreadthPrefix + "MEMBERS", "V" ) );AboveSlow = Nz( Foreign( BreadthPrefix + "ABOVE200", "V" ) );
PctAboveSlow = 100 * SafeDivide( AboveSlow, Members, Null );The composites store counts in the Volume field, which is why every read is Foreign( ticker, "V" ).
SafeDivide returns Null on bars where no member was yet eligible, so the line is empty there
rather than showing a plausible zero.
The sector loop
Section titled “The sector loop”Fragment — not a complete formula
Ticker = StrExtract( SectorList, i - 1 );SectorClose = Foreign( Ticker, "C" );
if( SelectedValue( IsNull( SectorClose ) ) ) SectorText = SectorText + " " + Ticker + " n/a";StrExtract pulls the i-th comma-separated item, zero-based. A missing symbol yields an empty
array, and the loop says n/a rather than printing a plausible zero next to the ones that worked —
which is the same fail-loud principle as the breadth health check.
Note that this reads the selected bar only. It is display, and it must never be used to build a signal.
Expected result
Section titled “Expected result”Validation
Section titled “Validation”Check the band mapping. Temporarily plot the constant 100 into the breadth band with the same
bounds. It must land exactly on that band’s top edge. Do the same with 0 and the bottom edge.
Remove both when satisfied.
Check the breadth arithmetic against a source you trust. Pick a date. Count by hand — from the Part 2 exploration or a spreadsheet — how many of your universe members closed above their 200-bar average on that date. It must match the composite. If it does not, the composite was built over a different universe or a different date range than you think.
Check the ATR normalisation. Read the stretch figure from the title on a chosen bar, then verify it by hand: close, minus the moving-average value, divided by an ATR reading from a separate pane. Within rounding they must agree.
Check the warm-up. Scroll to the extreme left edge. The volatility band must be empty for the
first RankPeriod bars and the trend band for the first TrendPeriod. Anything drawn there is a
seeded value, not data.
Check a sector figure by hand. Take one proxy, read its close today and its close 63 bars ago from its own chart, compute the percentage change, and compare with the title.
Common errors
Section titled “Common errors”Everything collapses into one flat line. A plot is missing styleOwnScale, so its scale bounds
are ignored and it rejoins the pane’s shared axis.
Breadth is flat at zero and the title does not say NO COMPOSITE DATA. LastValue( Members ) is
above zero but the individual counts are not — usually a prefix mismatch between the builder and the
dashboard. Check BreadthPrefix.
Breadth data disappears on some bars. You plotted the dashboard on an instrument that does not
trade every session. Foreign() aligns to the current symbol, so bars the current symbol lacks
are silently dropped. Plot it on the benchmark or on the ~BR_MEMBERS composite itself.
A sector shows n/a that you expected to work. The symbol is missing, misspelled, or has no data
in the loaded range. Check it in the symbol tree before assuming the formula is wrong.
The volatility band never leaves the middle. RankPeriod is longer than the loaded history, so
PercentRank never has a full window. Load more bars or shorten the lookback.
The advance/decline figures look wrong after a data update. Composites are stored data, not live
calculations. Re-run the builder scan after every data update — or chain it with #pragma sequence
as Part 12’s workflow lesson
describes.
Extensions
Section titled “Extensions”-
Add the net-new-highs line as a fourth band. The composite is already read; only the plotting is missing. Decide where it goes and what it does to the readability of the other three.
-
Add a divergence note to the title that fires when the benchmark is above its trend average while breadth is below 50 — a state that is interesting precisely because the two disagree. Resist making it a signal; the value is that it names a state you would otherwise scroll past.
-
Build your own sector composites with
AddToComposite()rather than using proxy symbols, so the sector strength is measured over exactly the names you trade. This removes the “different universe” objection entirely and is the more honest version. -
Record the dashboard state daily, appending one line per day to a CSV with the journal technique from Part 12. After six months you have a dataset of your own market states — which is the raw material for asking whether any of them mattered.
What to record for the report
Section titled “What to record for the report”One paragraph in the research log:
- Which benchmark, and why.
- Which universe the composites were built from, and whether it is the universe you trade.
- Which sector proxies, and what they actually represent.
- The lookback periods you chose and why.
- The date you last rebuilt the composites.
Component 9 asks about the first two directly.
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AFL Function Reference — Foreignamibroker.com/guide/afl/foreign.html2026-08-31
- 02AFL Function Reference — Plot§ minvalue and maxvalue (used by styleOwnScale plots ONLY)amibroker.com/guide/afl/plot.html2026-08-31
- 03AFL Function Reference — PercentRankamibroker.com/guide/afl/percentrank.html2026-08-31
- 04AFL Function Reference — SafeDivideamibroker.com/guide/afl/safedivide.html2026-08-31
- 05AFL Function Reference — StrExtractamibroker.com/guide/afl/strextract.html2026-08-31
- 06AFL Function Reference — SetBarsRequiredamibroker.com/guide/afl/setbarsrequired.html2026-08-31
- 07AFL Function Reference — AddToCompositeamibroker.com/guide/afl/addtocomposite.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.