Dynamic Variables: VarSet, VarGet and Friends
There is exactly one thing dynamic variables do that nothing else in AFL does: they let
a variable’s name come from the data rather than from the source code. By the end of
this lesson you will be able to recognise the small number of situations where that is
genuinely necessary, write them without the two traps that catch everyone, and — more
often — notice that you are reaching for VarSet when an ordinary array would be
shorter, faster and legible six months from now.
The four functions, exactly as documented
Section titled “The four functions, exactly as documented”| Call | Returns | Since | What it does |
|---|---|---|---|
VarSet( "varname", value ) |
NUMBER — 1 on success, 0 on failure | 4.60 | Stores a number, array or (from 6.10) matrix under a constructed name |
VarGet( "varname" ) |
ARRAY or NUMBER, depending on what was stored | 4.60 | Reads it back |
VarSetText( "varname", "valuetext" ) |
STRING | 4.80 | Stores a string |
VarGetText( "varname" ) |
STRING, always | 4.80 | Reads it back, converting to string if the underlying value is not one |
Two properties are stated on all four official pages and are worth committing to memory before you write a line of this.
Dynamic variables are always global. You cannot make one local to a user-defined
function. Part 11 covered AFL’s scope rules — an identifier first used inside a function
is local, one first used outside is global — and dynamic variables sit outside that
system entirely. A VarSet inside a function writes a name that every other part of the
formula can read.
They are not static variables. They live for the duration of the current formula
execution and no longer. Nothing survives to the next chart refresh, to another pane, or
to the Analysis window. If you want that, you want StaticVarSet, which is the subject
of the next lesson.
Generating names in a loop
Section titled “Generating names in a loop”The idiom the official VarSet page shows is a loop that manufactures a family of
names from a counter:
Fragment — not a complete formula
for( i = 1; i < 10; i++ ){ VarSet( "C" + i, Ref( C, -i ) );}// creates C1, C2, C3 ... C9, holding Ref(C,-1) ... Ref(C,-9)Read that carefully, because it is the whole mechanism. "C" + i is ordinary string
concatenation producing "C1", "C2" and so on; VarSet creates a variable with that
literal name; a later VarGet( "C" + i ) finds it again. There is no magic and no new
data structure — the formula’s variable namespace is being used as a dictionary keyed by
string.
The counterpart loop reads them back:
Fragment — not a complete formula
for( i = 1; i < 10; i++ ){ Plot( VarGet( "C" + i ), "C" + i, colorRed );}Testing whether a name exists
Section titled “Testing whether a name exists”This is the trap. A bare read of a variable that was never assigned is an error, and with generated names you often cannot be sure a particular name was written — a symbol might have been skipped, a branch might not have run.
There are two documented ways to ask.
VarGetText is safe on a name that does not exist. The official page’s own example is
Title = VarGetText( "Title" ) + "something";, which it says “will work correctly
regardless of whenever title was defined earlier or not”. An unset name gives you an
empty string, so if( VarGetText( "state_" + Sym ) == "" ) is a legitimate existence
test.
typeof is the general answer. It is an operator, not a function, and it inspects an
unevaluated operand, returning one of the exact strings "undefined", "number",
"array", "string", "function", "user function", "object", "member",
"handle" or "unknown":
Fragment — not a complete formula
if( typeof( MyVariable ) == "undefined" ){ MyVariable = 0;}Note what typeof cannot do: it takes the operand alone, with no arithmetic and no
extra arguments, and typeof( SomeFunc() ) inspects the identifier rather than the
result. To test the type of a function’s result, assign it first.
Where this genuinely earns its place
Section titled “Where this genuinely earns its place”When a generated name is the right tool
- Is the set of names known when you write the formula?If yes — five indicators, three timeframes, a fixed dashboard — write them out. Named variables are checkable by the syntax checker and visible in the debugger.
- Does the name come from data?A watch list, a category, a Param-driven count, a column of an imported file. The formula cannot know these names in advance.
- Do you need to look a value up by that name later?Not just iterate over them in order — actually retrieve one by key, possibly in a different part of the formula.
- Then a dynamic variable is the shortest correct answer.Otherwise a plain array, indexed by position, is shorter, faster and easier to read.
The honest list of cases is short:
- Per-symbol results inside one formula run. You loop a watch list, compute something for each ticker, and later need that ticker’s value back by name. This is the worked example below.
- A count that is chosen at run time. A
Param()decides how many moving averages the panel draws, so the formula cannot declare them. - Reading a configuration whose keys are data. A comma-separated settings string where each key becomes a variable.
- Appending to a possibly-undefined string, which is the
VarGetText( "Title" )idiom from the docs — a one-liner, but a genuine use.
A worked example: a per-symbol panel
Section titled “A worked example: a per-symbol panel”Show, in one chart pane, the rate of change of every symbol in a list, so a small set of instruments can be compared side by side without opening five charts. The list is data: it is edited at the top of the formula, and neither the formula nor AmiBroker knows what is in it until the formula runs.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// dynamic-variable-panel.afl// Part 36 - Dynamic Variables: VarSet, VarGet and Friends//// A chart-pane panel that reports the rate of change of every symbol in a// hand-written list, keying each result by ticker.//// WHY DYNAMIC VARIABLES ARE USED HERE// The names of the result variables are not known when the formula is// written - they come from SymbolList at run time. That is the situation// VarSet/VarGet exist for. Everything else below is ordinary AFL.//// ASSUMPTIONS// - Daily end-of-day bars. No real-time feed and no Professional edition.// - Every ticker in SymbolList is expected to exist in the database. One// that does not is reported as "no data" rather than silently scoring 0.// - This formula ranks nothing and trades nothing. It reads history and// prints it. No figure it shows is an expectation about the future.
_SECTION_BEGIN("Dynamic Variable Panel");
// The list is data, not code. Edit it to match symbols in your own database.SymbolList = "AAPL,MSFT,KO,XOM,JNJ";LookbackBars = Param( "Lookback bars", 63, 5, 252, 1 );
LastBar = BarCount - 1;
// Never assume how many bars you were given. QuickAFL, a zoomed chart and the// debugger's 200-bar default all shrink BarCount without telling you.EnoughBars = BarCount > LookbackBars;
// ---------------------------------------------------------------------------// Pass 1 - compute one score per symbol, stored under a generated name.// ---------------------------------------------------------------------------if( EnoughBars ){ for( i = 0; ( Sym = StrExtract( SymbolList, i ) ) != ""; i++ ) { SetForeign( Sym );
Score = ROC( Close, LookbackBars );
// Read the last element directly. LastValue() returns ZERO when the // final bar is Null, which would turn a missing symbol into a score // of zero - exactly the silent failure this guard exists to stop. ScoreNow = Score[ LastBar ];
RestorePriceArrays();
VarSet( "score_" + Sym, Score );
if( IsNull( ScoreNow ) ) VarSetText( "state_" + Sym, "no data" ); else VarSetText( "state_" + Sym, "ok" ); }}
// ---------------------------------------------------------------------------// Pass 2 - read the generated names back and build the report.// ---------------------------------------------------------------------------Report = "Rate of change over " + NumToStr( LookbackBars, 1.0 ) + " bars\n";
if( NOT EnoughBars ){ Report = Report + "Not enough bars loaded (BarCount = " + NumToStr( BarCount, 1.0 ) + ").\n";}else{ for( i = 0; ( Sym = StrExtract( SymbolList, i ) ) != ""; i++ ) { // VarGetText returns an empty string for a name that was never set, // so it doubles as the existence test. A bare read of an undefined // variable is an error instead. State = VarGetText( "state_" + Sym );
if( State == "ok" ) { Score = VarGet( "score_" + Sym ); Report = Report + Sym + " " + NumToStr( Score[ LastBar ], 1.2 ) + " %\n"; } else { if( State == "" ) Report = Report + Sym + " not computed\n"; else Report = Report + Sym + " " + State + "\n"; } }}
Plot( Close, "Close", colorDefault, styleCandle );
Title = Report;
_SECTION_END();How it works
Section titled “How it works”There are two passes and they do very different jobs.
The first pass walks the list with StrExtract, switches the price arrays to each
ticker with SetForeign, computes a rate of change, switches back with
RestorePriceArrays, and stores the result under the constructed name "score_" + Sym.
It also stores a status string under "state_" + Sym. That second variable is what
makes the formula honest: it records whether the symbol produced anything, separately
from the value.
The second pass walks the same list again, reads "state_" + Sym with VarGetText
first, and only reads "score_" + Sym when the state says there is something to read.
The whole report is built as a string and handed to Title.
The guard around BarCount matters more than it looks. QuickAFL, a zoomed chart and the
debugger’s 200-bar default all reduce the number of bars a formula receives, so a
63-bar lookback on a 40-bar array has no answer. The formula says so rather than
printing a number derived from nothing.
Key functions you may not have met
Section titled “Key functions you may not have met”StrExtract( list, item, separator = ',' ) pulls the n-th item out of a delimited
string and returns an empty string when you run off the end — which is why the loop
condition is the extraction itself.
ROC( ARRAY, periods = 12, absmode = False ) is percentage rate of change. Note the
documented default period is 12, not 14 and not 10.
NumToStr( NUMBER, format = 1.3, separator = True, roundAndPad = False ) formats a
number for display. The 1.2 used here means two decimal places.
Expected result
Section titled “Expected result”A chart pane showing the current symbol’s candles, with a title listing one line per
ticker: the ticker, its percentage change over the lookback, and a % sign. Symbols
missing from your database appear as no data. If you paste this straight in without
editing SymbolList, expect several no data lines — the five tickers in the file are
stand-ins for whatever your own database contains.
Test it
Section titled “Test it”Two checks, in this order.
- Put the current chart symbol into
SymbolList. The number printed for it must match what you get from a one-line pane containingPlot( ROC( Close, 63 ), "ROC", colorRed );read at the last bar. If it does not, theSetForeign/RestorePriceArrayspairing is wrong somewhere. - Add a ticker you know is not in the database —
ZZZZNOTREALwill do. It must appear asno data, not as0.00 %. This is the check that matters, because a silent zero is indistinguishable from a real flat result.
Common errors
Section titled “Common errors”Forgetting RestorePriceArrays() leaves the price arrays pointing at the last foreign
symbol, so everything after the loop — including Plot( Close, ... ) — silently draws
the wrong instrument. The symptom is a chart whose candles do not match its own title.
Building a name that collides with a function, as described above, raises Error 33 and
points at the VarSet line rather than at the naming scheme that caused it.
Extension
Section titled “Extension”The panel reports in list order. Sort it. Read the scores into an ordinary array in the second pass, sort that array with an explicit loop, and print in rank order — then compare the amount of code you wrote against the alternative in the next section, and decide which version you would rather come back to.
The readability cost, stated plainly
Section titled “The readability cost, stated plainly”Everything you give up is a consequence of the same fact: the name is not in the source code, so nothing that reads source code can help you.
- The syntax checker cannot see a typo in
"scroe_" + Sym. It is a valid string. - The debugger’s Watch window takes variable identifiers only. You can watch
score_AAPLif you know that name exists, but you cannot watch “whatever the loop just wrote”, and you cannot callVarGetfrom the Watch window because functions are not allowed there. - Find-in-files stops working. Searching for
score_AAPLfinds nothing, because the string"score_"and the ticker are in different places. - The reader — including you, later — has to simulate the loop mentally to know what names exist at all.
When a plain array is better
Section titled “When a plain array is better”Most of the time. The test is the third question in the diagram: do you need lookup by name, or only iteration in order?
If it is iteration in order, an array indexed by position is better on every axis. It is one variable rather than n. It is visible in the debugger as a single row. It has no naming scheme to get wrong. And AFL’s array operations work on it, which the generated family cannot do — you cannot add two families of dynamic variables together, but you can add two arrays.
Fragment — not a complete formula
// Keyed by name - needed only if you must look one up by ticker later.VarSet( "score_" + Sym, Value );
// Indexed by position - enough whenever you only ever walk the list in order.Scores[ i ] = LastValue( Value );There is a second alternative worth knowing about. When the two-dimensional shape is the point — every symbol against every other symbol, or every symbol across every bar — a matrix is the right structure, and matrices are the next lesson but one.
And when the value has to outlive the formula run, none of this applies: dynamic variables vanish when execution ends, and you need static variables instead.
What changed
Section titled “What changed”You now know what dynamic variables are for and, more usefully, what they are not for.
The name-from-data test is the whole decision procedure: if the set of names is known
when you write the formula, write them out; if you only ever iterate, use an array; if
you genuinely need to retrieve a value by a key that comes from the data, VarSet and
VarGet are the right tool and the prefix convention keeps them survivable.
You also met the two failure modes that produce wrong numbers rather than error
messages: LastValue returning zero over a Null last bar, and a generated name
colliding with a function identifier.
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AFL Function Reference — VarSetamibroker.com/guide/afl/varset.html2026-08-31
- 02AFL Function Reference — VarGetamibroker.com/guide/afl/varget.html2026-08-31
- 03AFL Function Reference — VarSetTextamibroker.com/guide/afl/varsettext.html2026-08-31
- 04AFL Function Reference — VarGetTextamibroker.com/guide/afl/vargettext.html2026-08-31
- 05AmiBroker User's Guide — AFL language reference§ typeof operatoramibroker.com/guide/a_language.html2026-08-31
- 06AmiBroker User's Guide — User-defined functions and scopeamibroker.com/guide/a_userfunctions.html2026-08-31
- 07AFL Function Reference — LastValueamibroker.com/guide/afl/lastvalue.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.