Benchmarks and Relative Performance
“Did this stock outperform?” sounds like a question with an answer. It is not. It is a question with three hidden parameters — outperform what, measured from when, and measured how — and changing any one of them changes the answer. This lesson builds the tool that answers it, and then spends as much effort demonstrating how much of the answer you chose as it spends on the arithmetic.
What a benchmark is for
Section titled “What a benchmark is for”A benchmark is the alternative you are being compared with. Its job is to absorb everything you were not trying to measure, so that what remains is attributable to the thing you were.
That makes the choice a research decision, not a formatting decision. Four questions decide it:
Could you actually have held it? A broad index is not investable; a fund tracking it is. If the comparison is meant to answer “was owning this share better than the obvious alternative”, the obvious alternative has to be something a person could have bought, with its costs included. If the comparison is meant to answer “did this share move differently from its market”, an index is the cleaner instrument because it carries no fund-level tracking error.
Is it in the same currency? A share priced in one currency compared with an index priced in another produces a ratio that is partly a currency chart. Nothing in AmiBroker will tell you this has happened.
Does it trade on the same calendar? This is the subject of Lesson 4, and it is
not a minor caveat. A domestic share against a domestic index is usually fine. A
share against an index from another country, or against a futures contract that
trades on days the equity market is closed, is a comparison across two different
sets of dates that Foreign() has already quietly reconciled for you.
Does it include income? Most equity indices quoted in retail data are price indices: they exclude dividends. Individual share prices in most databases are also price series, though some vendors supply back-adjusted totals. Comparing a dividend-adjusted share against a price-only index tilts the whole comparison, and the tilt grows with the length of the window. State which you have; if you do not know, that is itself worth writing down.
The ratio chart
Section titled “The ratio chart”The mechanics are trivial. Divide one series by the other:
Fragment — not a complete formula
BenchmarkClose = Foreign( "^GSPC", "C" );Ratio = IIf( BenchmarkClose > 0, Close / BenchmarkClose, Null );The IIf() guard is not decoration. A benchmark value of zero — which is what you
can get from a badly imported bar — turns the whole array into infinities, and the
chart auto-scales to accommodate them, leaving you with a flat line at the bottom of
an empty pane.
A ratio line rises whenever the numerator gained relative to the denominator. That is a smaller claim than it looks, and it is the claim people routinely overstate:
Both series fall. The ratio rises.
| Bar | Day 1 | Day 2 | Day 3 | Day 4 |
|---|---|---|---|---|
Stock Close | 100.0 | 96.0 | 92.0 | 90.0 |
Benchmark Close | 100.0 | 94.0 | 88.0 | 84.0 |
Ratio | 1.000 | 1.021 | 1.045 | 1.071 |
Rebasing to a start date
Section titled “Rebasing to a start date”A raw ratio is hard to read because its level is arbitrary — it depends on the two symbols’ price scales, so 0.043 and 17.2 are equally meaningless. Rebasing fixes that. Pick an anchor bar, divide the whole series by its value at that bar, and multiply by 100:
Fragment — not a complete formula
AnchorValue = ValueWhen( AnchorBar, Series );Rebased = IIf( AnchorValue > 0, 100 * Series / AnchorValue, Null );Now every series starts at 100 on the anchor bar and its value reads directly as a percentage of where it began. Two rebased price series plotted together compare cleanly regardless of whether one trades at 4 and the other at 40,000.
The interesting part is AnchorBar, which has to be an array that is true on
exactly one bar. There are two useful ways to build it.
Anchor at a fixed date. ParamDate() returns a DateNum by default, and this
expression is true only on the first bar dated on or after it:
Fragment — not a complete formula
AnchorDateNum = ParamDate( "Anchor date", "2020-01-02", 0 );AnchorBar = Cum( DateNum() >= AnchorDateNum ) == 1;Cum() counts qualifying bars from the beginning of the array, so the running count
equals 1 on the first one and never again. If no bar qualifies — the date is beyond
your data — the condition is never true, ValueWhen() returns Null throughout,
and nothing is drawn. That is the right behaviour: an empty pane is a better answer
than a confident wrong one.
Anchor at the left edge of the visible chart. This is what the built-in relative performance example in AmiBroker’s own documentation does, and it makes the chart interactive: pan or zoom and every line re-anchors to whatever is now on the left.
Fragment — not a complete formula
AnchorBar = BarIndex() == Status( "firstvisiblebar" );Formula: rebased comparison with a sensitivity read-out
Section titled “Formula: rebased comparison with a sensitivity read-out”Draw the current symbol, a benchmark, and the ratio between them, all set to 100 at a chosen anchor — and, in the same pane, report what the same comparison would have concluded from two other anchors. The second half is the part that changes how people use the first half.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// ===========================================================================// Rebased comparison and relative strength line//// Puts the chart's symbol and a benchmark on the same scale by setting both// to 100 at a chosen anchor bar, and draws the ratio between them - also// rebased to 100 - as a third line.//// Reading it: the ratio line rises on bars where the symbol gained more (or// lost less) than the benchmark SINCE THE ANCHOR. It is a description of two// price series. It carries no claim about what either series does next.//// HOW TO RUN// Apply Indicator to a new pane. Ctrl+R sets the benchmark, the anchor mode// and the two alternative anchor dates used by the sensitivity read-out.//// WHY THE ANCHOR IS A PARAMETER AND NOT A CONSTANT// Every rebased comparison is relative to one bar. Move that bar and the// whole picture moves with it. The title therefore prints the same// comparison measured from three different anchors, so the reader can see// the size of that effect instead of being asked to take it on trust.//// ASSUMPTIONS// - Foreign() synchronises the benchmark to THIS symbol's bars. Bars the// benchmark did not trade are filled from its previous Close (fixup 1) and// are counted and reported below rather than hidden.// - Rebasing needs a strictly positive value at the anchor bar. A zero or// Null anchor produces an empty line, which is the honest outcome.// ===========================================================================
_SECTION_BEGIN( "Rebased comparison" );
BenchmarkSymbol = ParamStr( "Benchmark symbol", "^GSPC" );UseVisibleAnchor = ParamToggle( "Anchor at", "Fixed date|First visible bar", 1 );PrimaryAnchor = ParamDate( "Anchor date", "2018-01-02", 0 );AlternateAnchorA = ParamDate( "Sensitivity anchor A", "2020-03-23", 0 );AlternateAnchorB = ParamDate( "Sensitivity anchor B", "2022-01-03", 0 );
// -- helpers ---------------------------------------------------------------
// True on exactly one bar: the first bar whose date is on or after the target.// Cum() counts qualifying bars, so the count equals 1 only on the first of them.function FirstBarAtOrAfter( TargetDateNum ){ return Cum( DateNum() >= TargetDateNum ) == 1;}
// ValueWhen() holds the value of the most recent bar where the condition was// true, and Null before it has ever been true - which is exactly the behaviour// a rebased series needs on the left of its anchor.function RebaseTo100( Series, AnchorBar ){ AnchorValue = ValueWhen( AnchorBar, Series ); return IIf( AnchorValue > 0, 100 * Series / AnchorValue, Null );}
// -- data ------------------------------------------------------------------
BenchmarkClose = Foreign( BenchmarkSymbol, "C" ); // fixup defaults to 1BenchmarkRaw = Foreign( BenchmarkSymbol, "C", 0 ); // holes left as Null
BenchmarkBars = LastValue( Cum( NOT IsNull( BenchmarkRaw ) ) );PaddedBars = LastValue( Cum( IsNull( BenchmarkRaw ) AND NOT IsNull( BenchmarkClose ) ) );HomeBars = LastValue( Cum( 1 ) );
// -- anchor ----------------------------------------------------------------
// Status("firstvisiblebar") is documented as available in indicator mode only,// so the fixed-date anchor is used everywhere else. BarIndex() is documented to// count from zero even when QuickAFL trims the array, which makes this// comparison safer than subscripting the array directly.UseVisible = UseVisibleAnchor AND Status( "action" ) == actionIndicator;
if ( UseVisible ) AnchorBar = BarIndex() == Status( "firstvisiblebar" );else AnchorBar = FirstBarAtOrAfter( PrimaryAnchor );
AnchorFound = LastValue( Cum( AnchorBar ) ) > 0;
HomeIndexed = RebaseTo100( Close, AnchorBar );BenchIndexed = RebaseTo100( BenchmarkClose, AnchorBar );
// The relative strength line. Rebasing the ratio as well puts all three lines// on one scale, so the pane needs no second axis to be read.Ratio = IIf( BenchmarkClose > 0, Close / BenchmarkClose, Null );RatioIndexed = RebaseTo100( Ratio, AnchorBar );
// -- start-date sensitivity ------------------------------------------------// The same question, asked from three different anchors.
RatioFromPrimary = LastValue( RatioIndexed );RatioFromA = LastValue( RebaseTo100( Ratio, FirstBarAtOrAfter( AlternateAnchorA ) ) );RatioFromB = LastValue( RebaseTo100( Ratio, FirstBarAtOrAfter( AlternateAnchorB ) ) );
// -- plot ------------------------------------------------------------------
Plot( HomeIndexed, Name() + " (=100)", colorBlue, styleLine | styleThick );Plot( BenchIndexed, BenchmarkSymbol + " (=100)", colorOrange, styleLine );Plot( RatioIndexed, "Ratio " + Name() + "/" + BenchmarkSymbol, colorSeaGreen, styleLine | styleThick );PlotGrid( 100, colorLightGrey );
_N( Title = StrFormat( "%s against %s, both set to 100 at the anchor bar\n", Name(), BenchmarkSymbol ) + WriteIf( UseVisible, "Anchor: first visible bar (pan or zoom the chart and every line moves).\n", "Anchor: the fixed date set in Parameters.\n" ) + WriteIf( AnchorFound, "", "NO ANCHOR BAR FOUND - the anchor date is outside this symbol's history, so nothing is drawn.\n" ) + StrFormat( "Symbol bars: %g benchmark bars with real quotes: %g padded bars: %g\n", HomeBars, BenchmarkBars, PaddedBars ) + StrFormat( "Ratio today, anchored at the chosen anchor: %.1f\n", RatioFromPrimary ) + StrFormat( "Same ratio, anchored at sensitivity date A: %.1f\n", RatioFromA ) + StrFormat( "Same ratio, anchored at sensitivity date B: %.1f\n", RatioFromB ) + "Three numbers, one pair of symbols. The anchor is a choice, not a fact." );
_SECTION_END();How it works
Section titled “How it works”Two small user-defined functions carry the logic. FirstBarAtOrAfter() turns a date
into a one-bar-true array. RebaseTo100() turns any series plus such an array into
a rebased series, relying on ValueWhen()’s documented behaviour of returning
Null before its condition has ever been true — which is exactly what a rebased
series should show to the left of its anchor.
The benchmark is read twice, with fixup = 1 for the series that gets plotted and
fixup = 0 for a diagnostic copy in which holes remain Null. The difference
between the two counts gives the number of padded bars, printed in the title. It
costs one extra Foreign() call and it is the difference between a chart and a
chart you can defend.
The ratio line is rebased as well as the two price lines. That is what lets all three share one axis: each starts at 100, so the pane needs no second scale and no explanation of which line belongs to which side.
The last section computes the final ratio three times, from three different anchors, and prints all three.
Key functions
Section titled “Key functions”ParamDate( "name", "default", format )— a date in the Parameters dialog.format = 0returns aDateNum, which is whatDateNum()produces, so the two compare directly. The reference warns that the default must be a constant, because parameter defaults are cached and not re-read on later evaluations.ValueWhen( condition, array, n = 1 )— the value ofarrayat the most recent bar whereconditionwas true. With a condition true on exactly one bar it becomes “hold this one value forward”.Status( "firstvisiblebar" )— the bar number at the left edge of the chart, in indicator mode only.
Expected result
Section titled “Expected result”Three lines meeting at 100 on the anchor bar. The blue line is the symbol, orange is the benchmark, and the thick green line — the ratio — sits above 100 wherever the symbol has gained more than the benchmark since the anchor, and below it wherever it has not. The title reports the bar counts, the padded-bar count, and three versions of today’s ratio.
Test it
Section titled “Test it”- Set the benchmark to the symbol you are already charting. The ratio line should sit flat at exactly 100 across the whole history. Any deviation means the two series are not what you think they are.
- Set the anchor date to something before your symbol’s first bar. Every line should start at 100 on the first available bar, because that is the first bar at or after the date.
- Set the anchor date beyond the last bar. Nothing should be drawn, and the title should say the anchor was not found.
- Read the three sensitivity numbers. Then change the two sensitivity dates to anything else and read them again.
Common errors
Section titled “Common errors”| Symptom | Cause |
|---|---|
| Pane is empty, title says no anchor bar found | Anchor date is outside this symbol’s history |
| Ratio line is drawn but the price lines are not | The benchmark exists but the symbol’s own data starts later than the anchor |
| One flat line at 100 and one wild line | Benchmark ticker is wrong; Foreign() returned nothing usable |
| Ratio jumps vertically on one bar | A zero or corrupt benchmark bar. The IIf() guard blanks division by zero but not division by 0.0001 |
| Numbers disagree with a website’s “year-to-date” figure | Different anchor, different dividend treatment, or both |
Extension
Section titled “Extension”Add a fourth line: the ratio’s own moving average, so that “is the outperformance still going” becomes a question about a line rather than about a slope you eyeballed. Then ask yourself what the moving-average length is doing to the answer, and whether you would have chosen the same length if you had not already seen the chart.
Start-date sensitivity is not a footnote
Section titled “Start-date sensitivity is not a footnote”Run the formula on any share against any broad index and read the three numbers in the title. On most instruments over most periods they will not merely differ in magnitude; they will differ in sign. The same pair of symbols, the same data, the same arithmetic, and the sentence “this stock has outperformed” is true from one anchor and false from another.
This is not a defect in the tool. It is a property of the question. A ratio between two series has no absolute level, so every statement about it is a statement about a change between two points, and both points are choices.
Two habits follow.
Choose the anchor for a reason you can state. “The start of the current market regime”, “the date the company changed its business”, “three years back, because that is my holding period” are all defensible. “Wherever the chart happened to be scrolled to” is not a reason, even though it is what an interactive anchor gives you by default — which is why the interactive mode is a convenience for looking, and the fixed date is the one you use when you are writing something down.
Report the sensitivity, not just the number. If a conclusion survives being measured from three anchors, say so; that is genuine evidence about its robustness. If it does not survive, the honest report is that the answer depends on the start date, which is a real finding about the pair and often more useful than the number you were hoping for.
Relative strength lines and RelStrength()
Section titled “Relative strength lines and RelStrength()”AmiBroker ships a function for this: RelStrength( "tickername", fixup = 1 ). It
returns the comparative relative strength of the current symbol against another. It
is genuinely convenient, and there are two reasons this course computes ratios by
hand instead.
The first is definitional. The function reference describes what RelStrength() is
for, but the arithmetic it performs is not published on that page; the paragraph
that explains how to interpret it is a user comment rather than vendor
documentation. A number whose formula you cannot state is a number you cannot
defend in a write-up, and it is not one you can reproduce in a spreadsheet when
someone asks you to.
The second is the empty-string form. RelStrength("") uses the standard base
security taken from Symbol → Categories. The Categories window documents Base
indexes as a per-market setting that controls the index used for the Relative
Strength indicator, for composites built through the Composite calculation option,
and for Beta. That is a useful feature and a trap in equal measure: the same formula
compares against different things for symbols filed under different markets, and
moving a symbol between markets changes its meaning without touching a line of code.
If you want to know what that setting currently is for a symbol, GetBaseIndex()
returns it as a string — a good column to add to any exploration that audits a
database you inherited.
Use RelStrength() when you want a quick look. Use an explicit ratio when the
number is going into a decision or a document.
What a rising ratio does and does not imply
Section titled “What a rising ratio does and does not imply”It does establish that, over the window between the anchor and now, the numerator gained more or lost less than the denominator. That is arithmetic and it is not in dispute.
It does not establish that the stock went up. It does not establish that the relationship will continue — the ratio is a description of bars that have already printed, and nothing in it constrains the next one. It does not establish that the stock is a better holding, because that comparison needs costs, position size, and what else you might have done with the money. And it does not establish that whatever caused the outperformance is still operating, which is the assumption almost every use of a relative strength line quietly makes.
Part 13 showed the cross-sectional version of the same caution: “strongest” is period-dependent, and a ranking is not a strategy. The pairwise version is the same sentence with fewer symbols in it.
A benchmark is a choice about what counts as ordinary, and it needs to match your
question in investability, currency, calendar and income treatment. A ratio chart is
one division and one IIf() guard. Rebasing makes ratios readable by fixing them at
100 on an anchor bar, which you build either from a date with
Cum( DateNum() >= d ) == 1 or from the left edge of the chart with
BarIndex() == Status("firstvisiblebar").
The measurement is easy. The anchor is the research. Any relative-performance claim that does not come with its start date attached is incomplete, and one that changes sign when the start date moves is a finding in its own right.
Check your understanding
Sources for this lesson
7 verified · checked 2026-09-01
- 01AFL Function Reference — Foreignamibroker.com/guide/afl/foreign.html2026-08-31
- 02AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
- 03AFL Function Reference — ParamDateamibroker.com/guide/afl/paramdate.html2026-08-31
- 04AFL Function Reference — Status§ firstvisiblebaramibroker.com/guide/afl/status.html2026-08-31
- 05AFL Function Reference — BarIndexamibroker.com/guide/afl/barindex.html2026-08-31
- 06AFL Function Reference — RelStrengthamibroker.com/guide/afl/relstrength.html2026-08-31
- 07AmiBroker User's Guide — Categories window§ Base indexesamibroker.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.