Skip to content
Level 3 · AFL DeveloperProjectPart 16 · page 4 of 460 min
60Minutes
16AFL functions
10Sources
StandardRequires
AFL functions taught here16

Project: Market Breadth Dashboard

At the end of this project you will have a three-panel breadth sheet driven entirely by composites you built yourself, over a universe you chose, with an eligibility rule you can state, and a validation procedure that would catch it being wrong. Everything runs on end-of-day data in either edition of AmiBroker.

Budget an hour. About fifteen minutes of that is settings and validation, and that is the fifteen minutes that decides whether the other forty-five produced anything worth looking at.

The order these steps have to happen in

  1. SettingsPad and align, with a reference symbol that exists
  2. UniverseA watch list you can describe in one sentence
  3. ScanThe builder writes eight composites
  4. ValidateArithmetic, contributor count, one hand count
  5. ChartThree panes, one formula, three parameter settings
  6. Re-scanEvery time new quotes arrive

Work through this checklist before writing anything:

  1. A daily database with at least three years of history for most members. The builder requires 252 bars before a member is allowed to contribute, so a database of two-year histories produces a dashboard that is empty for its first year and thin thereafter.
  2. Analysis → Settings → General: tick “Pad and align all data to reference symbol:” and type a symbol that exists in this database into the field beside it. Confirm it exists by selecting it in the Symbol window first. If it does not exist, no padding happens and nothing tells you.
  3. A watch list containing the members you want counted. “All symbols” is tempting and almost always wrong, because it sweeps in indices, currency pairs, your own composites and anything else you have ever imported. Note that when you combine several categories in the Filter settings window they are combined with a logical AND, not an OR.
  4. Range: All quotations, so that the composite covers the whole history rather than the part you happen to be looking at.
  5. A note of what you chose. Universe, date, member count, eligibility rule. You will not remember in three weeks, and every number the dashboard produces is conditional on all four.

A breadth dashboard is only useful if each element answers a question you would otherwise have to guess at. Three questions are enough:

  • How broad is the current trend? Answered by the percentage of members above their own long-term average, with the short-term average beside it for contrast.
  • What are the extremes doing? Answered by new highs minus new lows, as a percentage of members, because the tails often move before the middle does.
  • What is the accumulated participation history? Answered by the advance/decline line.
Composite Field Holds Feeds
~BR_MEMBERS V Eligible members on that bar Every denominator
~BR_ADV V Members that closed up A/D line
~BR_DEC V Members that closed down A/D line
~BR_UNC V Members that closed unchanged The arithmetic check
~BR_ABOVE50 V Members above their own 50-bar average Fast participation
~BR_ABOVE200 V Members above their own 200-bar average Slow participation, regime
~BR_NEWHIGH V Members at a 252-bar high Net new highs
~BR_NEWLOW V Members at a 252-bar low Net new highs

Eight composites, all written by one scan, all in the Volume field of their own symbol. The unchanged count earns its place purely as a test: it is the third term that makes advances plus declines plus unchanged equal the member count, and without that identity you have no cheap way to prove the set is consistent.

The design decision worth arguing about is the eligibility rule. There are two defensible choices:

Separate denominators. Each measure uses its own eligibility test — 50 bars for the fast average, 200 for the slow one, 252 for the high/low count. Each percentage is then computed over the largest possible set of members. The cost is that the three percentages are computed over three different populations, so comparing them with each other is comparing three different markets.

One denominator. Every measure requires the same 252 bars of history, so all three percentages are computed over exactly the same members and can be compared directly. The cost is that a company listed eleven months ago contributes nothing at all, and the whole dashboard is silent for the first 252 bars of the database.

This project takes the second option, because a dashboard is a device for comparing measures with each other. The cost is real and you should state it whenever you show the output: a market with heavy new issuance will have a growing share of its activity invisible to this panel.

Three practical points. First, an index constituent list from a website is today’s list, not the list as it stood in 2015 — the survivorship problem from Lesson 1 enters here, at the moment you build the watch list. Second, exclude indices and your own composites: the atcFlagCompositeGroup flag handles composites, but an index left in the watch list will be counted as though it were a member. Third, a universe of forty symbols produces a percentage that moves in steps of two and a half, which is usable for learning and too coarse to publish.

One scan that writes all eight composites, with a single eligibility rule applied identically to every measure, and a structure that makes adding a ninth measure a one-line change.

Complete runnable AFL

breadth-composite-builder.afl
/* Market breadth composite builder
-------------------------------------------------------------------------
Part 16 - Market Breadth, project formula 1 of 2.
Run this with the SCAN button in the Analysis window. It writes eight
artificial symbols, each holding one per-bar count in its Volume field:
~BR_MEMBERS eligible members on that bar (the denominator)
~BR_ADV members that closed up
~BR_DEC members that closed down
~BR_UNC members that closed unchanged
~BR_ABOVE50 members trading above their own 50-bar average
~BR_ABOVE200 members trading above their own 200-bar average
~BR_NEWHIGH members making a new 252-bar high
~BR_NEWLOW members making a new 252-bar low
ONE DENOMINATOR, ON PURPOSE
Every measure uses the same eligibility test, so every percentage on the
dashboard has the same denominator and the measures can be compared with
each other. The cost is real and you should know you are paying it: a
symbol contributes nothing until it has HistoryBars bars of its own
history, so recently listed companies are invisible to this dashboard for
their first year.
ASSUMPTIONS
1. Daily bars.
2. "Pad and align all data to reference symbol" is ON in Analysis ->
Settings -> General, with a reference symbol that exists here. If the
named symbol does not exist, AmiBroker pads nothing and says nothing.
3. "Apply to" defines the universe. Point it at a watch list you can
describe in one sentence, not at "All symbols".
4. The database holds today's survivors. Read the survivorship section
of the lesson before drawing conclusions from the 2008 readings.
*/
// ---- Configuration -------------------------------------------------------
Prefix = "~BR_";
HistoryBars = 252; // bars a member needs before it is allowed to vote
FastMAPeriod = 50;
SlowMAPeriod = 200;
HighLowWindow = 252; // about 52 weeks of daily bars
CompositeFlags = atcFlagDeleteValues | atcFlagCompositeGroup | atcFlagTimeStamp;
// One call site for every composite, so the flags and the field code cannot
// drift apart between measures.
procedure CountIntoComposite( condition, compositename, flags )
{
AddToComposite( IsTrue( condition ), compositename, "V", flags );
}
// ---- Eligibility ---------------------------------------------------------
// BarIndex() is zero-based and, since version 5.30, always starts at zero even
// when QuickAFL is active, so this is a reliable "has enough history" test.
Eligible = BarIndex() >= HistoryBars AND NOT IsNull( Close );
PreviousClose = Ref( Close, -1 );
// ---- The measures --------------------------------------------------------
Advancing = Eligible AND Close > PreviousClose;
Declining = Eligible AND Close < PreviousClose;
Unchanged = Eligible AND Close == PreviousClose;
AboveFast = Eligible AND Close > MA( Close, FastMAPeriod );
AboveSlow = Eligible AND Close > MA( Close, SlowMAPeriod );
// HHV and LLV include the current bar, so ">= HHV" is "today set the high".
NewHigh = Eligible AND High >= HHV( High, HighLowWindow );
NewLow = Eligible AND Low <= LLV( Low, HighLowWindow );
// ---- Scan half -----------------------------------------------------------
if( Status( "action" ) == actionScan )
{
CountIntoComposite( Eligible, Prefix + "MEMBERS", CompositeFlags );
CountIntoComposite( Advancing, Prefix + "ADV", CompositeFlags );
CountIntoComposite( Declining, Prefix + "DEC", CompositeFlags );
CountIntoComposite( Unchanged, Prefix + "UNC", CompositeFlags );
CountIntoComposite( AboveFast, Prefix + "ABOVE50", CompositeFlags );
CountIntoComposite( AboveSlow, Prefix + "ABOVE200", CompositeFlags );
CountIntoComposite( NewHigh, Prefix + "NEWHIGH", CompositeFlags );
CountIntoComposite( NewLow, Prefix + "NEWLOW", CompositeFlags );
Buy = 0;
Sell = 0;
_exit();
}
// ---- Every other context -------------------------------------------------
// AddToComposite detects its context and does nothing in indicator mode
// unless you opt in with a flag, so dropping this on a chart is harmless.
// It is still a mistake, and the title says so rather than leaving an empty
// pane that looks like a broken formula.
Plot( Close, "Close", colorDefault, styleCandle );
Title = "Breadth composite builder - this file is meant to be run with the "
+ "Scan button in the Analysis window. Nothing was written.";

Download breadth-composite-builder.afl96 lines

Configuration collects every number that a reader might want to change — the ticker prefix, the history requirement, the two average lengths and the high/low window — into one block at the top, and builds the flag expression once.

CountIntoComposite is a procedure wrapping the AddToComposite call. It exists for one reason: with eight measures to write, a hand-repeated call is eight chances to use a different field code or a different flag expression by accident. Passing the condition through IsTrue() inside the procedure also guarantees that every composite receives a clean 0 or 1, whatever the caller passed in.

Eligibility is BarIndex() >= HistoryBars AND NOT IsNull( Close ). BarIndex() is zero-based and, since version 5.30, starts at zero even when QuickAFL is active, so it is a reliable count of how much history a symbol has at each bar. The IsNull test catches padded bars in a database where alignment has inserted them.

The measures are ordinary single-symbol AFL, each one gated by Eligible. Because HHV and LLV include the current bar, High >= HHV( High, HighLowWindow ) reads as “today set the highest high of the window”, which is what a new 52-week high means.

The scan half makes the eight calls, silences Buy and Sell, and exits. Everything else plots the price and prints a title saying the file was meant to be scanned — because AddToComposite is a documented no-op in indicator mode, an empty pane would otherwise look like a broken formula.

  • AddToComposite( array, "ticker", "field", flags ) accumulates array into field of the artificial symbol, additively across every symbol the run visits.
  • BarIndex() returns the zero-based bar number; it is the documented fast equivalent of Cum(1) - 1.
  • HHV( array, periods ) and LLV return the highest and lowest value over the preceding periods bars, current bar included.
  • IsTrue( array ) returns 1 where a value is neither empty nor zero, which is how a possibly-Null condition becomes a definite count of 0 or 1.
  • _exit() ends execution at that point, so the scan does no chart work.

Set “Apply to” to your watch list, Range to All quotations, and press Scan.

The run reports no signals. On a few hundred daily symbols it takes seconds to a minute or two; on the Standard edition, which runs two threads per Analysis window against Professional’s thirty-two, expect it to take proportionally longer. Every AddToComposite call takes a global lock, so eight composites over a large universe is meaningfully slower than one.

Before charting anything, run the audit from Lesson 2 against these composites. Open composite-audit.afl, change the four ticker names at the top from ~ADV, ~DEC, ~UNC and ~MEMBERS to ~BR_ADV, ~BR_DEC, ~BR_UNC and ~BR_MEMBERS, select ~BR_MEMBERS, set “Apply to” to Current symbol and Range to All quotations, turn the parameter to Show only problem bars: Yes, and press Explore.

A correct build returns no rows at all. Any row it does return is a bar where the counts do not add up or the contributor count moved, and you should understand why before going any further.

One chart formula that reads all eight composites and draws whichever of the three panels you select in the Parameters dialog, so that three panes of a chart sheet can share a single file.

Complete runnable AFL

breadth-dashboard.afl
/* Market breadth dashboard
-------------------------------------------------------------------------
Part 16 - Market Breadth, project formula 2 of 2.
Reads the eight composites written by breadth-composite-builder.afl and
draws one of three panels, chosen in the Parameters dialog (Ctrl+R):
Participation percent of members above their 50-bar and
200-bar averages
New highs and lows net new 252-bar highs, as a percent of members
Advance/decline the cumulative advance/decline line
Apply it three times, once per panel, to build a three-pane breadth sheet.
WHERE TO PLOT IT
On ~BR_MEMBERS, or on a broad index symbol whose trading calendar matches
the universe. Foreign() aligns foreign data to the CURRENT symbol, so
plotting this on a share that does not trade every day silently deletes
composite bars from the picture.
WHAT IT IS NOT
None of these lines forecasts anything. They describe how widely a move
was shared at the time it happened, which is a different and much smaller
claim than "the market is about to turn".
*/
_SECTION_BEGIN( "Market breadth dashboard" );
// ---- Configuration -------------------------------------------------------
Prefix = "~BR_";
Panel = ParamList( "Panel",
"Participation|New highs and lows|Advance/decline", 0 );
// The A/D line is a running total, so it must see the whole history rather
// than only the bars currently on screen.
SetBarsRequired( sbrAll, sbrAll );
// ---- Read the composites -------------------------------------------------
Members = Nz( Foreign( Prefix + "MEMBERS", "V" ) );
Adv = Nz( Foreign( Prefix + "ADV", "V" ) );
Dec = Nz( Foreign( Prefix + "DEC", "V" ) );
Unc = Nz( Foreign( Prefix + "UNC", "V" ) );
AboveFast = Nz( Foreign( Prefix + "ABOVE50", "V" ) );
AboveSlow = Nz( Foreign( Prefix + "ABOVE200", "V" ) );
NewHighs = Nz( Foreign( Prefix + "NEWHIGH", "V" ) );
NewLows = Nz( Foreign( Prefix + "NEWLOW", "V" ) );
// SafeDivide returns the third argument wherever the divisor is zero, which
// is exactly what happens on bars before any member was eligible.
PctAboveFast = 100 * SafeDivide( AboveFast, Members, 0 );
PctAboveSlow = 100 * SafeDivide( AboveSlow, Members, 0 );
PctNetNewHi = 100 * SafeDivide( NewHighs - NewLows, Members, 0 );
NetAdvances = Adv - Dec;
ADLine = Cum( NetAdvances );
// ---- Health check --------------------------------------------------------
// Two ways a breadth panel lies: the composites are missing, or they were
// built by a scan that did not finish. Both are visible in the numbers.
Mismatch = Adv + Dec + Unc - Members;
LatestMembers = LastValue( Members );
if( LatestMembers > 0 )
Health = StrFormat( "contributors %g arithmetic check %g (must be 0)",
SelectedValue( Members ), SelectedValue( Mismatch ) );
else
Health = "NO COMPOSITE DATA - run breadth-composite-builder.afl as a Scan first";
// ---- Panels --------------------------------------------------------------
if( Panel == "Participation" )
{
Plot( PctAboveSlow, "Above 200-bar MA", colorBlue, styleLine | styleThick, 0, 100 );
Plot( PctAboveFast, "Above 50-bar MA", colorSkyblue, styleLine, 0, 100 );
PlotGrid( 20, colorLightGrey );
PlotGrid( 50, colorDarkGrey );
PlotGrid( 80, colorLightGrey );
Title = StrFormat( "%s participation: %.1f per cent above the 200-bar "
+ "average, %.1f per cent above the 50-bar average - ",
Name(), SelectedValue( PctAboveSlow ),
SelectedValue( PctAboveFast ) ) + Health;
}
else if( Panel == "New highs and lows" )
{
Plot( PctNetNewHi, "Net new highs, per cent of members",
IIf( PctNetNewHi >= 0, colorGreen, colorRed ), styleHistogram );
PlotGrid( 0, colorDarkGrey );
Title = StrFormat( "%s net new highs %.2f per cent of members "
+ "(%g new highs, %g new lows) - ",
Name(), SelectedValue( PctNetNewHi ),
SelectedValue( NewHighs ), SelectedValue( NewLows ) )
+ Health;
}
else
{
Plot( ADLine, "A/D line", colorBlue, styleLine | styleThick );
Plot( NetAdvances, "Net advances", colorLightGrey,
styleHistogram | styleOwnScale | styleNoLabel );
Title = StrFormat( "%s A/D line %g net advances %g - ",
Name(), SelectedValue( ADLine ),
SelectedValue( NetAdvances ) ) + Health;
}
_SECTION_END();

Download breadth-dashboard.afl107 lines

Reading. Eight Foreign() calls, each wrapped in Nz(). The wrapper matters: the documentation does not define what Foreign() returns for a symbol that does not exist, and on a database where the scan has not yet run, none of these do. Nz() turns Null, NaN and Infinity into zero, so a missing composite produces a flat zero line rather than an error or a blank pane.

Deriving. SafeDivide( numerator, Members, 0 ) produces the percentages and returns zero wherever the member count is zero, which it is on every bar before the first member became eligible. Division by zero here would otherwise propagate Infinity through the whole panel.

The health line. Mismatch recomputes the arithmetic identity, and the title reports it on every panel. If the member count on the last bar is zero, the title says so in words instead of drawing a flat line that looks like a real reading of zero per cent.

The panels. ParamList returns the selected string, and the three branches plot, respectively, the two participation percentages against a fixed 0-to-100 scale with grid lines at 20, 50 and 80; the net new high percentage as a histogram coloured by sign with a zero grid line; and the A/D line with net advances behind it on its own scale.

SetBarsRequired( sbrAll, sbrAll ) sits above all three because the A/D line is a Cum(), and since version 5.30 Cum() no longer forces a full-history evaluation by itself.

  • ParamList( "name", "a|b|c", default ) returns the chosen string. It is what lets one file serve three panes.
  • SafeDivide( x, y, valueifzerodiv ) returns the third argument wherever y is zero. All three arguments are required.
  • PlotGrid( level, color ) draws a constant horizontal line far more cheaply than plotting a constant array, which is the documented reason to prefer it.
  • SelectedValue( array ) returns the value at the bar the cursor is on, which is what makes the title track the crosshair.
  • StrFormat( formatstring, ... ) builds the title. Numbers use %f, %e or %g; there are no integers in AFL, so %d will not work.

Select ~BR_MEMBERS as the chart symbol, or a broad index whose trading calendar matches your universe. Apply the file three times to three panes, and set each pane’s Panel parameter (Ctrl+R) to a different value.

The dashboard is now drawing lines. Nothing so far establishes that the lines are right. Five checks, in the order that catches the most problems for the least effort.

1. The arithmetic identity. Already done above with the audit exploration. It catches interrupted scans, mixed flag settings and mismatched universes. Repeat it after every change to the builder.

2. The contributor count against the watch list. Open the watch list and note how many symbols it contains. The last bar of ~BR_MEMBERS should equal that number minus any members that do not yet have 252 bars of history. If it is dramatically lower, your “Apply to” selection was not the list you thought it was — remember that combining categories in the Filter settings window applies a logical AND.

3. A hand count on one date. Choose a date. Run an exploration over the same universe with Filter = DateNum() == 1250612; (substitute your date; DateNum() codes a date as 10000 * (year - 1900) + 100 * month + day) and a column showing Close > MA( Close, 200 ). Count the ones. That number must equal ~BR_ABOVE200 on that date. This is the only check that tests the condition rather than the plumbing, so do it at least once for each measure you rely on.

4. Direction on a large index day. Find a day the index you follow fell by more than two per cent. On that day, ~BR_DEC should overwhelmingly exceed ~BR_ADV. If the counts are balanced, or the wrong way round, your composite and your index are not describing the same market — usually because the universe is wrong, or because the calendar alignment is putting contributions on the wrong bar.

5. Shape against the index over a long window. Chart the A/D line above the index and compare their broad shape over ten years. They should rise and fall together in the large. A modest divergence is the interesting part of the measure; total disagreement is a bug.

Symptom Cause Fix
Scan finishes, no composites appear Explore or Backtest was pressed instead of Scan Press Scan; AddToComposite is a no-op elsewhere unless a flag opts in
Counts roughly double after each run atcFlagDeleteValues missing from the flag expression Restore bit 1, or use atcFlagDefaults
Every panel flat at zero Composites absent, or the Prefix string differs between builder and dashboard Check the symbol tree for the exact ticker names
Percentages exceed 100 Numerator and denominator use different eligibility tests One Eligible array, used by every measure
Dashboard empty for the first year of history The 252-bar eligibility rule, working as designed Reduce HistoryBars, and accept less comparable percentages
Member count jitters day to day Alignment off, or the reference symbol does not exist Tick “Pad and align” and verify the reference symbol is in the database
A/D line level changes when you zoom Cum() no longer forces all bars SetBarsRequired( sbrAll, sbrAll );
Composite history has gaps on the chart Plotted on an illiquid symbol; Foreign() aligns to the current symbol Chart it on ~BR_MEMBERS or a broad index
Panel unchanged after downloading new quotes A composite is stored data Re-run the scan; consider #pragma sequence or the Batch window
Scan takes far longer than expected Eight cross-symbol writes, each taking a global lock; two threads on Standard Reduce the number of composites, or narrow the universe
Parameters dialog shows nothing The Parameters dialog is per chart pane Click the pane, then press Ctrl+R
Early history uses an old definition The last scan covered a shorter range than the one before Re-scan with Range set to All quotations

Each of these is a genuine piece of work rather than a cosmetic change. Attempt them in order; the first is the easiest and the last is the most instructive.

Volume breadth. Add ~BR_ADVVOL and ~BR_DECVOL, accumulating Volume rather than a count for advancing and declining members. Their ratio divided by the advancing/declining issue ratio is the Arms index construction, and building it from a universe you defined makes a useful contrast with the built-in Trin(), which works only with composites AmiBroker calculated itself.

Sector-level composites. Change the ticker name so that each member writes into a composite named after its sector:

Fragment — not a complete formula

// inside the scan half of breadth-composite-builder.afl
SectorTicker = "~BR_S" + NumToStr( SectorID(), 1.0 ) + "_ABOVE200";
AddToComposite( IsTrue( AboveSlow ), SectorTicker, "V", CompositeFlags );

SectorID() returns the numeric sector, SectorID(1) returns the name. Note the cost: this turns one composite into as many composites as you have sectors, and every one of them is another global lock during the scan.

A smoothed participation oscillator. Subtract a slow exponential average of net advances from a fast one. Several published breadth oscillators are built this way. Before you tune the two periods, decide how you will tell an improvement from a curve fit — Part 31 and Part 32 exist for exactly that question.

Separate denominators, and a comparison. Build a second set of composites using per-measure eligibility, chart both versions of “percentage above the 200-bar average” together, and measure how far apart they are. This is the cheapest way to find out whether the design decision at the top of this page mattered for your universe.

Export and analyse elsewhere. With a New Analysis window focused, File → Export HTML/CSV writes the result list as displayed. Emit the eight composite values as exploration columns and take them to a spreadsheet or a notebook, where you can compute the forward outcomes that a chart cannot show you.

Wire the regime in. Take the classification from Lesson 3, feed it the ~BR_ABOVE200 and ~BR_MEMBERS composites you have just built, and add its band behind the participation panel. Then run the exploration half and count the episodes. That count is the honest headline for anything you go on to claim about the dashboard.

Check your understanding

Question 1. Why does the builder require 252 bars of history before a member contributes to any measure?
Show the answer and why

Answer: So that every percentage on the dashboard is computed over the same population and the measures can be compared with each other

A single eligibility rule buys comparability between the panels. The cost is that recently listed members are invisible for their first year, which has to be stated whenever the output is shown.

Question 2. Your ~BR_MEMBERS line is flat at 480 for years, then jitters between 455 and 480 for the most recent six months. What is the first thing to check?
Show the answer and why

Answer: Whether the reference symbol named in "Pad and align all data to reference symbol" still has quotes over that period

Alignment fails silently. A reference symbol that stopped updating six months ago leaves the recent period unpadded, and the contributing set then varies bar by bar.

Question 3. Which validation step tests the counting condition rather than the plumbing?
Show the answer and why

Answer: A hand count from an exploration on a single date

The identity and the contributor count would both pass even if the condition itself were wrong, because they only check that the parts are consistent with each other.

Question 4. Which of these will make the dashboard show a picture that is quietly incomplete rather than obviously broken? Select all that apply.
Show the answer and why

Answer: Charting it on a thinly traded share instead of on ~BR_MEMBERS, A reference symbol for alignment that is not in the database, A watch list assembled from a present-day index constituent list

Pressing Explore produces nothing at all, which is obvious. The other three produce plausible-looking output that is measuring something other than what you intended.

Sources for this lesson

10 verified · checked 2026-08-31

  1. 01AFL Function Reference - AddToCompositeamibroker.com/guide/afl/addtocomposite.html2026-08-31
  2. 02AmiBroker User's Guide - Calculating multiple-security statistics with AddToCompositeamibroker.com/guide/a_addtocomposite.html2026-08-31
  3. 03AmiBroker User's Guide - Analysis settings (Pad and align all data to reference symbol)§ General tabamibroker.com/guide/w_settings.html2026-08-31
  4. 04AmiBroker User's Guide - Filter settings (Apply to)amibroker.com/guide/w_filter.html2026-08-31
  5. 05AFL Function Reference - BarIndexamibroker.com/guide/afl/barindex.html2026-08-31
  6. 06AFL Function Reference - HHVamibroker.com/guide/afl/hhv.html2026-08-31
  7. 07AFL Function Reference - SafeDivideamibroker.com/guide/afl/safedivide.html2026-08-31
  8. 08AFL Function Reference - ParamListamibroker.com/guide/afl/paramlist.html2026-08-31
  9. 09AFL Function Reference - SectorIDamibroker.com/guide/afl/sectorid.html2026-08-31
  10. 10AmiBroker User's Guide - Multi-threading in AmiBrokeramibroker.com/guide/h_multithreading.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.