Skip to content
Level 3 · AFL DeveloperLessonPart 15 · page 3 of 526 min
26Minutes
11AFL functions
8Sources
StandardRequires
AFL functions taught here11

Sectors and Intermarket Analysis

A stock that rose eight per cent last month while every one of its competitors rose nine has told you almost nothing about itself. This lesson is about placing an instrument inside the two contexts that usually explain most of what it did — its peer group and its market — and about the intermarket claims that get made when the same technique is pointed at unrelated asset classes.

Comparing a stock with the broad market answers one question. Comparing it with its sector answers a second. Doing both at once answers a third that neither can reach alone.

Three ratios, three different questions

  1. Stock / marketDid this holding beat the obvious alternative?
  2. Stock / sectorDid this company do better than its competitors?
  3. Sector / marketWas the sector where the movement came from?
Read together, they separate a company's own performance from its sector's.

The combination is what makes it worth the extra Foreign() call. A stock strongly ahead of the market and level with its sector was carried; the interesting object is the sector, not the company. A stock level with the market but well ahead of its sector did something its peers did not, in a sector that went nowhere. Those two situations look identical on a stock-versus-market chart and call for completely different follow-up questions.

To compute “stock against sector” you need a number for the sector. Almost everyone uses a sector ETF or a published sector index, and almost no one states what that substitution costs.

A fund is an instrument, not a measurement. A sector ETF has its own supply and demand, its own liquidity, its own creation and redemption mechanics, and a price that can drift from the value of its holdings. On a quiet day the difference is negligible; on a disorderly one it is not, and disorderly days are usually the ones being studied.

Weighting decides what you are measuring. A capitalisation-weighted sector proxy is, in many sectors, mostly a chart of its two or three largest members. If your stock is one of them, “stock versus sector” is partly the stock versus itself, and the ratio is damped toward flat by construction. An equal-weighted proxy of the same sector answers a genuinely different question, and the two frequently disagree.

Constituents change. Index and fund providers add and remove members, and the proxy’s history reflects whoever was in it at the time — not whoever is in it now. That is the correct behaviour for a tradable fund and it is a survivorship consideration when you use its history as a research series.

Classification is a judgement. Whether a large retailer that sells most of its goods online belongs to retail or to technology is a decision someone made, and different providers made it differently. A stock’s sector is a property of the classification scheme, not of the company.

AmiBroker classifies every symbol along four independent axes — market, group, sector and industry — plus any number of watch lists. Each axis has one reading function, and each takes the same mode argument:

Fragment — not a complete formula

SectorID(); // numeric sector id
SectorID( 1 ); // sector NAME, as a string
IndustryID( 1 ); // industry name
MarketID( 1 ); // market name
GroupID( 1 ); // group name

mode = 0, the default, returns the numeric identifier. mode = 1 returns the name as a string. The return type changes with the argument, which is a small trap worth naming: compare names to strings and identifiers to numbers, never the two mixed.

Three further facts decide how you should use these in a course formula or a shared library.

The identifiers are ordinal positions, and positions move. The Categories window has gained Move Up and Move Down buttons since version 6.10, and the documentation notes that reordering forces symbols to be re-indexed because symbols refer to the ordinal position of a category. A formula containing SectorID() == 7 therefore means something different after somebody tidies the list. Prefer names, or resolve a name to an identifier at run time.

The identifier ranges differ per axis. CategoryGetName() documents the valid range as 0–255 for market, group and industry, but only 0–63 for sector. Watch lists have no documented upper limit. A single generic loop written for “all categories” will run off the end of the sector range.

There is no InSector(). Nor InGroup() or InIndustry() — those pages do not exist in the function reference and the names are not in the official index. The membership tests that do exist are InWatchList(), InWatchListName(), InGICS() and InICB(), plus the property tests IsIndex(), IsFavorite() and IsContinuous(). For the four ordinal axes, compare the identifier or the name:

Fragment — not a complete formula

InThisSector = SectorID( 1 ) == "Information Technology";

CategoryGetSymbols( category, index, mode = 0 ) returns a comma-separated string of the symbols in a category. The category argument takes the documented constants categoryMarket, categoryGroup, categorySector, categoryIndustry, categoryWatchlist, categoryFavorite, categoryIndex, categoryGICS, categoryICB and — from version 5.50 — categoryAll, meaning every symbol in the database. mode is also new in 5.50: 0 returns tickers, 1 returns full names.

The result is one long string, parsed with StrExtract() in a loop. That loop, with SetForeign() inside it, is how you would build a sector average yourself rather than trusting a fund — and it is also the pattern that will make a formula slow if you point it at categoryAll from a chart, for the reasons the previous lesson gave.

If your data source supplies them, AmiBroker also carries the GICS and ICB classifications, with GicsID( mode ), IcbID( mode ), InGICS( "code" ) and InICB( "code" ). These behave differently from the ordinal axes in a way that matters: their codes are hierarchical and fixed, so they do not shift when anyone reorders a list, and a request for a parent code includes its children. The CategoryGetName() page gives the example directly: asking for the symbols of GICS code 10 returns everything in the energy sector including sub-codes such as 10101010 and 10102050.

If your database has GICS or ICB codes, prefer them for anything you intend to keep. If it does not, the ordinal axes are what you have, and the advice above about names over numbers is how you keep them from breaking.

Intermarket relationships and their instability

Section titled “Intermarket relationships and their instability”

Intermarket analysis extends the sector idea outward: bonds against equities, currencies against commodities, one country’s index against another’s. The technique is the same Foreign() call. The epistemics are much harder, and the difference is worth being explicit about.

A sector relationship has a mechanism you can state in a sentence: these companies sell similar things to similar customers and are exposed to similar costs, so their revenues move together. You can be wrong about how strong it is; you are not guessing about whether a connection exists.

Intermarket claims — “yields lead equities”, “the metal leads the miners”, “this currency pair leads that index” — usually arrive without a mechanism, and often without a stated window, direction or lag. What they arrive with instead is a chart of two lines that moved together over a period chosen after the fact.

The measurable part of such a claim is the correlation between the two series’ returns. The next formula measures it, and measures whether it stayed put.

Take any two symbols and answer three questions at once: how strongly do their one-bar returns move together right now, how strongly did they move together at various points in the past, and how often has that number changed sign? A single correlation figure is a summary of one window. A picture of the same figure over time is evidence about whether the relationship is a relationship.

Complete runnable AFL

correlation-stability.afl
// ===========================================================================
// Correlation stability audit
//
// Measures how the relationship between this symbol and one other symbol has
// moved over time, and shows two ways of measuring it that give very
// different answers.
//
// HOW TO RUN
// Apply Indicator to a new pane. Ctrl+R sets the partner symbol and the two
// rolling windows.
//
// WHAT IT SHOWS
// - Rolling correlation of ONE-BAR RETURNS, at a short and a long window.
// This is the quantity most intermarket claims are implicitly about.
// - Rolling correlation of PRICE LEVELS, drawn dashed. Two series that both
// drift upwards tend to produce a large level correlation whether or not
// anything connects them, which is why this line is here as a warning and
// not as a measurement.
// - How many times the short-window return correlation has changed sign.
// A relationship that changes sign is not a stable relationship.
//
// WHAT IT DOES NOT SHOW
// Nothing here identifies a mechanism, a direction of causation or a lead
// and lag. A correlation is a summary of two columns of numbers over one
// window. Reading a cause into it is a separate claim that needs separate
// evidence.
//
// ASSUMPTIONS
// - Foreign() aligns the partner to this symbol's bars. Bars the partner did
// not trade are padded flat, which makes its return exactly zero on those
// bars and pulls the measured correlation toward zero. The padded-bar
// count is printed so the size of that effect is visible.
// - Correlation() needs a full window before it returns anything, so the
// left edge of the pane is empty by design.
// ===========================================================================
_SECTION_BEGIN( "Correlation stability" );
PartnerSymbol = ParamStr( "Partner symbol", "^GSPC" );
ShortWindow = Param( "Short window (bars)", 60, 10, 500, 5 );
LongWindow = Param( "Long window (bars)", 250, 20, 2000, 10 );
LookBack = Param( "Compare with the value this many bars ago", 250, 20, 2000, 10 );
PartnerClose = Foreign( PartnerSymbol, "C" ); // fixup 1: holes filled flat
PartnerRaw = Foreign( PartnerSymbol, "C", 0 ); // fixup 0: holes stay Null
PartnerBars = LastValue( Cum( NOT IsNull( PartnerRaw ) ) );
PaddedBars = LastValue( Cum( IsNull( PartnerRaw ) AND NOT IsNull( PartnerClose ) ) );
HomeReturn = ROC( Close, 1 );
PartnerReturn = ROC( PartnerClose, 1 );
CorrShort = Correlation( HomeReturn, PartnerReturn, ShortWindow );
CorrLong = Correlation( HomeReturn, PartnerReturn, LongWindow );
CorrLevels = Correlation( Close, PartnerClose, LongWindow );
// A relationship that keeps crossing zero is not one relationship observed
// repeatedly; it is a number that happens to be computable on every bar.
SignChanges = LastValue( Cum( Cross( CorrShort, 0 ) OR Cross( 0, CorrShort ) ) );
CorrLongNow = LastValue( CorrLong );
CorrLongThen = LastValue( Ref( CorrLong, -LookBack ) );
CorrShortNow = LastValue( CorrShort );
Plot( CorrShort, StrFormat( "Return correlation, %g bars", ShortWindow ), colorBlue, styleLine );
Plot( CorrLong, StrFormat( "Return correlation, %g bars", LongWindow ), colorSeaGreen, styleLine | styleThick );
Plot( CorrLevels, StrFormat( "PRICE LEVEL correlation, %g bars - not a measurement", LongWindow ),
colorGrey40, styleLine | styleDashed );
PlotGrid( 0, colorLightGrey );
PlotGrid( 0.5, colorLightGrey );
PlotGrid( -0.5, colorLightGrey );
_N( Title =
StrFormat( "%s against %s - correlation of one-bar returns\n", Name(), PartnerSymbol )
+ StrFormat( "Now: %.2f over %g bars, %.2f over %g bars\n",
CorrShortNow, ShortWindow, CorrLongNow, LongWindow )
+ StrFormat( "The same %g-bar figure as it stood %g bars ago: %.2f\n",
LongWindow, LookBack, CorrLongThen )
+ StrFormat( "Sign changes in the %g-bar figure over the whole history: %g\n",
ShortWindow, SignChanges )
+ StrFormat( "Partner bars with real quotes: %g padded flat bars: %g\n",
PartnerBars, PaddedBars )
+ "The dashed line is the correlation of price levels. Two rising series "
+ "produce a high value there whether or not anything links them." );
_SECTION_END();

Download correlation-stability.afl86 lines

The partner symbol is read twice, once padded and once raw, for the same reason as in the previous lesson: the difference is the padded-bar count, and padded bars have a specific effect here that the title has to report. A padded bar carries the previous close, so the partner’s one-bar return on that bar is exactly zero, and a run of zeros pulls a measured correlation toward zero. On a pair with different holiday calendars this is not a rounding effect.

Two rolling windows are plotted, short and long, so that the reader can see the trade-off directly: the short window responds quickly and is noisy, the long window is stable and late. Neither is the true value, because there is no true value — there is a series of estimates over windows you chose.

The dashed line is the correlation of price levels rather than returns. It is drawn as a warning. Two series that both drift upwards over a decade will produce a large level correlation whether or not anything connects them, because both are mostly a function of time. Almost every casually quoted “correlation” between two markets is either this quantity or a chart that behaves like it.

The sign-change count is the blunt summary. A relationship whose measured correlation has crossed zero repeatedly is not one relationship being observed repeatedly; it is a number that can be computed on every bar.

  • Correlation( array1, array2, periods ) — the rolling correlation coefficient over the last periods bars. It needs a full window before it returns anything, so the left edge of the pane is empty by design.
  • ROC( array, periods = 1 ) — rate of change in per cent. Used here to turn two price series into two return series before comparing them.
  • Cross( array1, array2 ) — true on the bar where the first crosses above the second. Used twice, in both directions, to count sign changes about zero.

A pane with two solid lines wandering between −1 and +1 and a dashed line that usually sits much closer to +1 or −1 than either of them. The title gives the current short and long readings, the long reading as it stood a year ago, the number of sign changes, and the padded-bar count.

  1. Set the partner symbol to the chart’s own symbol. Both solid lines should read 1.00 everywhere after the warm-up. Anything else means the two series being compared are not identical, which is itself worth investigating.
  2. Compare the current long-window reading with the same reading a year ago, printed on the third title line. Write down the difference. That difference is the quantity most intermarket claims omit.
  3. Halve and double both windows. If your conclusion about the pair changes, the conclusion was about the window.
  4. Note the padded-bar count. Then swap the two symbols — chart the partner and set the original as the partner — and note it again. They will often differ, for reasons the next lesson explains.
Symptom Cause
Both lines empty The window is longer than the available history, or the partner ticker is wrong
Correlation implausibly close to zero A large padded-bar count: the partner’s return is zero on every manufactured bar
Dashed line near 1.00 quoted as “these markets are correlated” That line is the level correlation, and it is in the formula as a counter-example
A confident reading from a 60-bar window on weekly data Sixty weekly bars is over a year; the window length means different things per interval

Replace ROC( Close, 1 ) with a multi-bar return and see how the picture changes. Correlations of overlapping multi-bar returns are inflated by the overlap, which is a well-known effect and easy to reproduce here: compare the same pair using 1-bar returns and 20-bar overlapping returns, and watch the second look far more convincing than the first for no additional reason.

Everything the previous formula produces is a description of two columns of numbers. Four things it cannot supply, and which any intermarket claim needs:

Direction. A correlation is symmetric. It says nothing about which series moved first, and adding a lag to the calculation does not fix that — it just adds another parameter you chose after looking.

Cause. Two series can move together because one drives the other, because a third thing drives both, because they share investors who rebalance on the same schedule, or because both are denominated in the same currency and you are partly measuring that currency.

Stability. Even where a connection is real, its strength moves. A number measured over 2010–2015 is a statement about 2010–2015.

Independence of the search. If you test a hundred pairs of instruments across several windows, some pairs will show strong correlations even when nothing connects them at all. The strength of the strongest result tells you very little unless you also state how many you looked at. This is the same multiplicity problem the course raised about optimisation, arriving in a different costume.

A stock sits inside a sector and a market, and comparing it with both at once separates its own contribution from its peer group’s. The sector proxy you use for that is a fund or an index with its own weighting, its own constituents and its own inception date — a substitution worth naming rather than assuming.

AmiBroker’s four ordinal category axes are read with MarketID(), GroupID(), SectorID() and IndustryID(), all taking mode = 1 for names; their identifiers are ordinal positions that move when categories are reordered, and sectors are limited to 0–63 where the others run to 0–255. GICS and ICB codes, where your data supplies them, are hierarchical and fixed and are the better key for anything durable.

Intermarket relationships are measurable and unstable. Measure them over rolling windows, look at the sign changes, keep the level correlation firmly in the category of warning rather than evidence, and remember that a correlation never contained a mechanism to begin with.

Check your understanding

Question 1. A stock is well ahead of the broad market and exactly level with its sector proxy. What does that combination suggest?
Show the answer and why

Answer: The sector moved, and the stock moved with it

Level with the sector means the company did what its peers did. The gain relative to the market came from the sector, which makes the sector — not the company — the object worth studying next.

Question 2. Why does the course prefer SectorID(1) == "Information Technology" to SectorID() == 7?
Show the answer and why

Answer: Numeric ids are ordinal positions and change when categories are reordered

The Categories window can reorder categories, and the documentation notes that symbols refer to the ordinal position of a category. Hard-coded numbers silently start selecting a different sector. The 0-63 range is a real limit, but it is a separate issue.

Question 3. Which of these is a valid AFL membership test? Select all that apply.
Show the answer and why

Answer: InWatchListName("My Hotlist"), InGICS("10")

InSector and InGroup do not exist in the official function reference. Watch lists and the GICS/ICB classifications have real membership functions; the four ordinal axes are tested by comparing the id or the name.

Question 4. A partner symbol trades on a calendar with thirty more holidays per year than the charted symbol. What happens to the measured return correlation, with the default fixup?
Show the answer and why

Answer: It is pulled toward zero, because the partner's return is exactly zero on every padded bar

fixup = 1 fills the missing bar from the previous close, so the partner's one-bar return there is zero while the charted symbol's is not. Thirty such bars a year is a meaningful dilution, and nothing on screen reports it unless you count it.

Question 5. Two markets show a price-level correlation of 0.95 over ten years. What does that establish?
Show the answer and why

Answer: Very little on its own, because two series that both drift upwards produce a high level correlation regardless of any connection

Level correlation is largely a statement that both series are functions of time. The return correlation of the same pair over the same period is frequently close to zero, which is why the formula draws the level line as a warning rather than a measurement.

Sources for this lesson

8 verified · checked 2026-09-01

  1. 01AFL Function Reference — SectorIDamibroker.com/guide/afl/sectorid.html2026-08-31
  2. 02AFL Function Reference — IndustryIDamibroker.com/guide/afl/industryid.html2026-08-31
  3. 03AFL Function Reference — MarketIDamibroker.com/guide/afl/marketid.html2026-08-31
  4. 04AFL Function Reference — GroupIDamibroker.com/guide/afl/groupid.html2026-08-31
  5. 05AFL Function Reference — CategoryGetNameamibroker.com/guide/afl/categorygetname.html2026-08-31
  6. 06AFL Function Reference — CategoryGetSymbolsamibroker.com/guide/afl/categorygetsymbols.html2026-08-31
  7. 07AFL Function Reference — Correlationamibroker.com/guide/afl/correlation.html2026-08-31
  8. 08AmiBroker User's Guide — Categories window§ Rearranging categoriesamibroker.com/guide/w_categories.html2026-09-01

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.