StaticVarGenerateRanks() and Top-N Selection
You now have a way to leave one number per symbol somewhere every execution can see it.
StaticVarGenerateRanks() is the function that reads that whole family of numbers, sorts
them across symbols bar by bar, and writes the resulting positions back as a new family of
static variables.
It is a well-behaved function with an unusually high number of ways to use it wrongly and get plausible output. This lesson goes through the signature, the naming rules, the direction question, and — most importantly — a procedure that will tell you whether the ranking in front of you is correct.
The signature, and the typo in it
Section titled “The signature, and the typo in it”The AFL Function Reference page prints its own SYNTAX line as:
StaticVarGenarateRanks( "outputprefix", "inputprefix", topranks, tiemode )The real call, then, is:
Fragment — not a complete formula
StaticVarGenerateRanks( "outputprefix", "inputprefix", topranks, tiemode );| Argument | Meaning |
|---|---|
outputprefix |
String prepended to the generated rank variables |
inputprefix |
String identifying the score variables to read |
topranks |
0 for normal mode; positive for top-N; negative for bottom-N |
tiemode |
How ties are numbered. Documented values: 1234 and 1224 |
It returns nothing. It was introduced in AmiBroker 5.70.
What it actually does
Section titled “What it actually does”inputprefix is not a list of variables — it is a pattern. AmiBroker searches every
static variable whose name begins with that prefix and assumes the remaining part of
the name is a stock symbol. So with an input prefix of "P13Score", a variable named
P13ScoreMSFT contributes MSFT’s score, and a stray variable named P13ScoreTemp would
contribute the score of a symbol called “Temp”.
One ranking call, from scores to positions
- P13ScoreAAAarray of scores
- P13ScoreBBBarray of scores
- Sort across symbols, bar by barranks start at ONE
- P13RankP13ScoreAAAarray of ranks
- P13RankP13ScoreBBBarray of ranks
Output naming in normal mode
Section titled “Output naming in normal mode”With topranks set to 0, the output variable for each symbol is named
outputprefix + inputprefix + symbol. This is the single detail people get wrong most
often, because the name is longer than you expect. After
Fragment — not a complete formula
StaticVarGenerateRanks( "Rank", "P13Score", 0, 1224 );MSFT’s rank array is in RankP13ScoreMSFT, not in RankMSFT. Read it back with the same
concatenation you used to build it.
Choose the two prefixes so the output family cannot be mistaken for an input family.
"Rank" plus "P13Score" gives outputs beginning RankP13Score, which does not begin
with P13Score, so a second call is safe. Had the input prefix been "P13", the outputs
would have begun RankP13... — still safe — but an input prefix of "Rank" with an
output prefix of "Rank" would feed the function its own results.
Output naming in top and bottom mode
Section titled “Output naming in top and bottom mode”With topranks greater than zero you get top-N mode; with topranks less than zero,
bottom-N mode. The outputs are named outputprefix + inputprefix + N where N is 1, 2,
3 and so on, and — this is the trap — they hold indexes, not scores and not tickers.
With an output prefix of Top and an input prefix of ROC, TopROC1 holds the index of
the top-rated value.
To turn an index into a ticker, the function also writes a companion variable named
outputprefix + inputprefix + "Symbols" holding a comma-separated list, so an index of 1
means the first entry in TopROCSymbols. StaticVarGetRankedSymbols( "outputprefix", "inputprefix", datetime ) does that lookup for you and returns a comma-separated string
for one date and time. The Ranking chapter describes what comes back as ranked symbols
while the underlying companion variable is described as holding variable names; check
what your build returns on the first run and strip the prefix if you see one.
Ranks count from one. The documentation states this twice on the same page, which is usually a sign that people get it wrong.
Ranking direction
Section titled “Ranking direction”There is no direction argument. The documentation states that ranks start at one and
describes positive topranks as selecting top-ranked values; taken with the chapter’s
opening description of a sorted list whose first entry is the best performer, the
straightforward reading is that in normal mode rank 1 holds the highest score. That is
what this course assumes.
It is also, precisely, an inference. Confirming it in your own build takes two minutes and the procedure is below, so do that rather than take it on trust. If your check disagrees, the fix is not an argument you have missed — there isn’t one. You reverse the order by negating the score before you store it:
Fragment — not a complete formula
// rank smallest-first instead of largest-firstStaticVarSet( InputPrefix + Sym, -SymbolScore );That is worth knowing for its own sake. Ranking by lowest volatility, or by smallest spread, is a perfectly ordinary requirement, and negation is how you express it.
Reading ranks back per symbol
Section titled “Reading ranks back per symbol”An Exploration that ranks every symbol of the Analysis window’s universe on a momentum score, reports each symbol’s own rank, and reports enough extra information to show whether the ranking is sound.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// universe-ranks.afl// Part 13 - StaticVarGenerateRanks() and Top-N Selection//// Turns one score per symbol into one rank per symbol per bar across the whole// universe, and reports enough extra information to prove the ranking is sane// rather than merely plausible.//// How to run it: Analysis window, Apply to = Filter (a watch list) or All,// Range = 1 recent bar, then Explore.//// Assumptions declared up front:// - Daily bars. The ranking is computed on the bar grid of whichever symbol// AmiBroker happens to process first, so a first symbol with a short or// gappy history limits the whole ranking. The lesson explains the fix.// - StaticVarGenerateRanks is expensive. It is called ONCE, inside the// Status("stocknum") == 0 block, exactly as the documentation insists.// - The output family is named outputprefix + inputprefix + symbol, so with// the prefixes below MSFT's rank lives in "P13RankP13ScoreMSFT".// - Note the official function-reference page misspells the name in its// SYNTAX line ("StaticVarGenarateRanks"). The callable name is// StaticVarGenerateRanks, spelled as it is below.
InputPrefix = "P13Score";OutputPrefix = "P13Rank";MaxRankName = "P13HighestRank";ScoredName = "P13ScoredCount";
ScorePeriod = Param( "Momentum lookback (bars)", 126, 20, 500, 1 );TopCount = Param( "Report top N", 20, 1, 200, 1 );TieToggle = ParamToggle( "Tie mode", "1224 shared ranks|1234 consecutive ranks", 0 );DirToggle = ParamToggle( "Rank direction", "Highest score first|Lowest score first", 0 );
if( TieToggle ) TieMode = 1234; else TieMode = 1224;
ApplySetting = GetOption( "ApplyTo" );
if( ApplySetting == 2 ){ WatchListNum = GetOption( "FilterIncludeWatchlist" ); SymbolList = CategoryGetSymbols( categoryWatchlist, WatchListNum );}else if( ApplySetting == 0 ){ SymbolList = CategoryGetSymbols( categoryAll, 0 );}else{ Error( "Set Apply to = All symbols, or Filter with an Include watch list." );}
// ------------------------------------------------------------------- PASS 1if( Status( "stocknum" ) == 0 ){ // Remove the inputs AND the previous run's outputs. The asterisk is not // decoration: without it nothing is removed at all. StaticVarRemove( InputPrefix + "*" ); StaticVarRemove( OutputPrefix + InputPrefix + "*" ); StaticVarRemove( MaxRankName ); StaticVarRemove( ScoredName );
for( i = 0; ( Sym = StrExtract( SymbolList, i ) ) != ""; i++ ) { if( SetForeign( Sym ) ) { SymbolScore = ROC( Close, ScorePeriod ); RestorePriceArrays();
// Ranking direction is not an argument of the ranking function. // Negating the score is how you turn "highest first" into // "lowest first" - there is no third option. if( DirToggle ) SymbolScore = -SymbolScore;
StaticVarSet( InputPrefix + Sym, SymbolScore ); } }
// Called exactly once. topranks = 0 selects normal ranking mode, in which // every ranked symbol gets its own output array. StaticVarGenerateRanks( OutputPrefix, InputPrefix, 0, TieMode );
// -------------------------------------------------------- self-check // Read every rank back and record two numbers the reader can compare: // how many symbols had a real score on each bar, and the highest rank // the function actually issued. If the second is larger than the first, // something that was not a score has been ranked. HighestRank = 0; ScoredSymbols = 0;
for( i = 0; ( Sym = StrExtract( SymbolList, i ) ) != ""; i++ ) { ThisScore = StaticVarGet( InputPrefix + Sym ); ThisRank = StaticVarGet( OutputPrefix + InputPrefix + Sym );
HighestRank = Max( HighestRank, Nz( ThisRank ) ); ScoredSymbols = ScoredSymbols + ( NOT IsNull( ThisScore ) ); }
StaticVarSet( MaxRankName, HighestRank ); StaticVarSet( ScoredName, ScoredSymbols );}
// ------------------------------------------------------------------- PASS 2OwnScore = StaticVarGet( InputPrefix + Name() );OwnRank = StaticVarGet( OutputPrefix + InputPrefix + Name() );RankIssued = StaticVarGet( MaxRankName );Scored = StaticVarGet( ScoredName );
// Rank 1 is the first rank AmiBroker issues; ranks count from ONE, not zero.RankPercent = 100 * OwnRank / Max( Scored, 1 );
Filter = NOT IsNull( OwnRank );
AddColumn( OwnScore, "Score", 1.2 );AddColumn( OwnRank, "Rank", 1.0 );AddColumn( RankPercent, "Rank as % of field", 1.1 );AddColumn( Scored, "Symbols scored", 1.0 );AddColumn( RankIssued, "Highest rank issued", 1.0 );AddTextColumn( WriteIf( OwnRank <= TopCount, "selected", "" ), "Top " + NumToStr( TopCount, 1.0 ) );
SetSortColumns( 4 );How it works
Section titled “How it works”The shape is the two-pass pattern from the previous lesson with the ranking call inserted
between the passes. Pass one removes both the input family and the previous run’s output
family, loops the universe writing one score per symbol, and then calls
StaticVarGenerateRanks() exactly once. The direction toggle negates the score before
storing, which is the only mechanism there is for reversing the order.
After the ranking call, a second loop reads every rank straight back out of the store.
That loop is the self-check: it records the highest rank the function issued and how many
symbols had a non-Null score, and stores both. Pass two then reports each symbol’s
score, its rank, its rank as a percentage of the field, and those two diagnostic numbers.
If the highest rank issued is larger than the number of symbols scored, something that was
not a score has been given a position. That is a real possibility, because the
documentation does not state how Null input values are treated, and it is exactly the
kind of thing that should be visible in the output rather than assumed away.
Key functions
Section titled “Key functions”StaticVarGenerateRanks( "outputprefix", "inputprefix", topranks, tiemode )— the ranking call;topranks = 0selects normal mode.StaticVarGet( "varname", align = True )— reads back both the scores and the ranks.Max( ARRAY1, ARRAY2 )— used to track the highest rank issued across the loop, and to keep the percentage division defined when nothing has been scored yet.ParamToggle( "name", "values", defaultval = 0 )— supplies the tie mode and the direction without editing the formula.
Expected result
Section titled “Expected result”One row per ranked symbol, sorted by rank ascending, so rank 1 is the top row. The
“Symbols scored” and “Highest rank issued” columns hold the same value on every row. On a
healthy run those two numbers are equal, or the highest rank is slightly smaller when the
tie mode is 1224 and ties have compressed the top of the range.
Test it
Section titled “Test it”This is the procedure worth doing once properly, because it settles the direction question and the correctness question together.
- Build a watch list of six symbols and nothing else.
- Set the Analysis range to one recent bar and run the Exploration.
- Copy the Score and Rank columns into a spreadsheet.
- Sort your copy by Score, descending. Number the rows 1 to 6.
- Compare your numbering with the Rank column.
If they match, rank 1 is the highest score in your build and the function is doing what
you think. If they are reversed, negate the score. If they match for five rows and not the
sixth, look at that symbol’s score: it is almost certainly Null, and you have just
discovered how your build ranks missing values.
Common errors
Section titled “Common errors”- Every rank is
Null. The read-back name is wrong. It must beoutputprefix + inputprefix + symbol, both prefixes, in that order. - Ranks go far higher than the number of symbols. A previous run’s scores are still in memory. Check that the removal call ends with an asterisk, and check it in the inspector formula from the previous lesson.
- The ranking is stale after you change the score period. Pass one only runs on the first symbol; if you changed a parameter but the Analysis did not re-run from the start, you are reading the old ranks.
- Two symbols hold rank 1 unexpectedly. Tie mode
1224numbers ties with an equal rank. Switch to1234if you need a strict order.
Extension
Section titled “Extension”Add a column for the symbol’s rank twenty bars ago — Ref( OwnRank, -20 ) — and a column
for the change. A ranking that reshuffles completely every month is telling you something
about the score you chose.
Top-N selection, two ways
Section titled “Top-N selection, two ways”From normal mode
Section titled “From normal mode”If you already have ranks as arrays, the top N is a comparison:
Fragment — not a complete formula
IsTopN = OwnRank <= 20;This is usually what you want inside an Exploration, because it keeps the score, the rank
and the selection in one table where they can be checked against each other. Remember that
in tie mode 1224 this can return more than twenty rows.
From top and bottom mode
Section titled “From top and bottom mode”The dedicated mode is for the other question: not “where does this symbol stand?” but
“who is at the top right now?”. It builds the tables of indexes described above and the
companion symbol list, and StaticVarGetRankedSymbols() reads them for one bar.
A chart pane that names the strongest and weakest symbols of a small universe at the bar you have selected, and plots the current symbol’s own rank.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// top-ranked-symbols.afl// Part 13 - StaticVarGenerateRanks() and Top-N Selection//// A chart pane that names the strongest and weakest symbols of a small// universe at the bar you click on. It follows the documented one-pass// indicator pattern: clear, score, generate, read - all in one execution.//// Assumptions declared up front:// - This re-ranks the entire list on EVERY chart redraw. The documentation// measures the ranking function at roughly 20 ms per 15,000 bars and 7// symbols, so keep the list short. Twenty symbols is comfortable; five// hundred is not, and belongs in an Analysis run instead.// - Top and bottom mode store INDEXES into a companion list, not scores and// not tickers. StaticVarGetRankedSymbols does that lookup for you.// - Ranks count from ONE.// - The list is taken from a watch list unless you type symbols yourself.
_SECTION_BEGIN( "Top ranked symbols" );
InputPrefix = "P13ChartScore";NormalPrefix = "P13ChartRank";TopPrefix = "P13ChartTop";BottomPrefix = "P13ChartBottom";
TypedList = ParamStr( "Symbols (comma separated, blank = watch list)", "" );WatchListNum = Param( "Watch list number", 0, 0, 200, 1 );ScorePeriod = Param( "Momentum lookback (bars)", 126, 20, 500, 1 );HowMany = Param( "How many at each end", 3, 1, 20, 1 );
if( TypedList == "" ) SymbolList = CategoryGetSymbols( categoryWatchlist, WatchListNum );else SymbolList = TypedList;
// Clear first. Every one of these names is shared with every other formula in// this AmiBroker, so a stale entry is not a local problem.StaticVarRemove( InputPrefix + "*" );StaticVarRemove( NormalPrefix + InputPrefix + "*" );StaticVarRemove( TopPrefix + InputPrefix + "*" );StaticVarRemove( BottomPrefix + InputPrefix + "*" );
for( i = 0; ( Sym = StrExtract( SymbolList, i ) ) != ""; i++ ){ if( SetForeign( Sym ) ) { SymbolScore = ROC( Close, ScorePeriod ); RestorePriceArrays(); StaticVarSet( InputPrefix + Sym, SymbolScore ); }}
// Three calls, three modes. Normal mode gives every symbol its own rank array;// positive topranks builds the top-N table; negative topranks builds the// bottom-N table.StaticVarGenerateRanks( NormalPrefix, InputPrefix, 0, 1224 );StaticVarGenerateRanks( TopPrefix, InputPrefix, HowMany, 1224 );StaticVarGenerateRanks( BottomPrefix, InputPrefix, -HowMany, 1224 );
OwnRank = StaticVarGet( NormalPrefix + InputPrefix + Name() );
Plot( OwnRank, "Rank of " + Name(), colorBlue, styleLine | styleThick );
// The ranked-symbol lookup is per date/time, not per array, so it answers for// the bar you have selected on the chart and no other.SelectedDate = SelectedValue( DateTime() );
Title = "Universe: " + NumToStr( StrCount( SymbolList, "," ) + 1, 1.0 ) + " symbols " + "lookback " + NumToStr( ScorePeriod, 1.0 ) + " bars" + "\n" + Name() + " rank: " + WriteVal( OwnRank, 1.0 ) + "\n" + "Strongest: " + StaticVarGetRankedSymbols( TopPrefix, InputPrefix, SelectedDate ) + "\n" + "Weakest: " + StaticVarGetRankedSymbols( BottomPrefix, InputPrefix, SelectedDate );
_SECTION_END();How it works
Section titled “How it works”Everything happens in one execution, which is the documented indicator pattern: clear,
score, generate, read. Three ranking calls are made — normal mode for the plotted rank
line, positive topranks for the strongest, negative for the weakest — and each writes
its own family of variables, which is why all three families are removed at the start.
The lookup is per date and time rather than per bar index, so the formula takes
SelectedValue( DateTime() ) and asks for the ranked list at that instant. Click a
different bar and the title changes.
Key functions
Section titled “Key functions”StaticVarGetRankedSymbols( "outputprefix", "inputprefix", datetime )— returns the comma-separated ranked list for one date and time. The prefixes must match the pair passed to the ranking call exactly.SelectedValue( ARRAY )— the value of an array at the selected bar.DateTime()— the date and time array for the current symbol.StrCount( "string", "substring" )— counts separators, used here to report how many symbols are in the list.
Expected result
Section titled “Expected result”A line showing this symbol’s rank within the list, and a title naming the three strongest and three weakest at the selected bar. Clicking through bars moves both.
Test it
Section titled “Test it”Set the list to five symbols you can chart individually. Note the strongest name in the title, then chart that symbol and check that its rank line reads 1 on the same bar. Repeat for the weakest.
Common errors
Section titled “Common errors”- The title is empty. The ranked-symbol lookup only has anything to return after a
ranking call with a non-zero
topranks, and the two prefixes must match that call. - The chart is slow. This re-ranks the entire list on every redraw. The documentation measures the ranking call at roughly 20 ms per 15,000 bars and 7 symbols; scale that up and the cost is obvious. Keep chart-based ranking to a short list.
- The rank line is flat at 1. The watch list number points at a list with one symbol in it.
Extension
Section titled “Extension”Add a second pane driven by the same prefixes but a different score — turnover, say — and compare who is at the top of each. Two rankings that agree are telling you the two scores are measuring the same thing.
Failure modes when the first pass is incomplete
Section titled “Failure modes when the first pass is incomplete”The ranking is only ever as good as the family of scores it was handed, and a first pass can be incomplete in several ways that produce no error at all.
- Stale variables from an earlier run. The wildcard was missing, or the prefix changed. Symbols that are no longer in your universe keep their old scores and keep taking up positions in the ranking.
- Symbols that were skipped.
SetForeign()returns 0 for a ticker that is not in the database and leaves the price arrays untouched. If the return value is not checked, the loop stores the previous symbol’s score under the missing symbol’s name. Nullscores from insufficient history. A 252-bar rate of change is undefined until there are 252 prior bars. How the ranking function treatsNullinput is not documented, so do not depend on it: either exclude the symbol by not writing a variable for it, or make the emptiness visible with a self-check column, as the formula above does.- A short bar range. A static array stores only the bars currently in use, so a narrow Analysis range produces a short ranking. The ranks will still be correct for the bars covered, and simply absent before them.
- The first symbol’s calendar. Everything computed inside one execution is aligned to that execution’s own symbol. Pass one runs on whichever symbol is first, so a first symbol with a short history or data holes limits the ranking for every symbol. Where this matters, use the Analysis setting “Pad and align all data to reference symbol”, which the manual notes is off by default, and pick a reference symbol with complete history.
- The universe and the run disagreeing. If the loop reads a hard-coded list while the
Analysis runs on a different watch list, half your rows will have no rank and you will
spend an afternoon looking at the ranking function. Read the universe from
GetOption(), as both formulas here do.
Cost, and when not to use this at all
Section titled “Cost, and when not to use this at all”The Ranking chapter is unusually blunt on both points, and it is worth repeating in full because it saves people from two different mistakes.
On cost: the function is computationally and memory intensive, at roughly 20 ms per 15,000
bars and 7 symbols. Call it once per run, guarded by Status( "stocknum" ) == 0, or
better still pre-compute the ranks in a separate Scan and use them later. Calling it per
symbol collapses performance, because as well as being slow it must lock the shared static
variable memory, so every other thread that touches a static variable waits for it.
On scope: the documentation states that this function is not intended to replace the
backtester’s built-in ranking through PositionScore, and that whenever you can, you
should use PositionScore because it is far faster and less memory-consuming for
backtests with ranking. StaticVarGenerateRanks() is intended for explorations and
indicators, and for cases PositionScore alone cannot express. Part 28 covers
PositionScore where it belongs.
StaticVarGenerateRanks() reads a family of static variables matched by prefix, sorts
their values across symbols bar by bar, and writes a new family whose names concatenate
the output prefix, the input prefix and the symbol. Ranks start at one, ties are numbered
by the documented modes 1234 or 1224, and direction is controlled by negating the
score rather than by an argument. Top and bottom mode store indexes into a companion
symbol list, which StaticVarGetRankedSymbols() will resolve for one bar.
None of that is hard. What is hard is knowing whether the numbers on screen are right, and the answer is the six-symbol hand check: sort a spreadsheet, compare, and only then build on top of it. The project applies all of this to a real relative strength tool.
Check your understanding
Sources for this lesson
5 verified · checked 2026-08-31
- 01AFL Function Reference - StaticVarGenerateRanksamibroker.com/guide/afl/staticvargenerateranks.html2026-08-31
- 02AFL Function Reference - StaticVarGetRankedSymbolsamibroker.com/guide/afl/staticvargetrankedsymbols.html2026-08-31
- 03AmiBroker User's Guide - Ranking functionalityamibroker.com/guide/h_ranking.html2026-08-31
- 04AFL Function Reference - StaticVarSetamibroker.com/guide/afl/staticvarset.html2026-08-31
- 05AFL Function Reference - StaticVarRemoveamibroker.com/guide/afl/staticvarremove.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.