Skip to content
Level 3 · AFL DeveloperLessonPart 36 · page 1 of 628 min
28Minutes
11AFL functions
7Sources
StandardRequires
AFL functions taught here11

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.

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.

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 );
}

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.

When a generated name is the right tool

  1. 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.
  2. 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.
  3. 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.
  4. Then a dynamic variable is the shortest correct answer.Otherwise a plain array, indexed by position, is shorter, faster and easier to read.
The third question is the one that decides it. If you only ever walk the collection in order, you do not need a keyed lookup.

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.

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.

Complete runnable AFL

dynamic-variable-panel.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();

Download dynamic-variable-panel.afl97 lines

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.

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.

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.

Two checks, in this order.

  1. Put the current chart symbol into SymbolList. The number printed for it must match what you get from a one-line pane containing Plot( ROC( Close, 63 ), "ROC", colorRed ); read at the last bar. If it does not, the SetForeign / RestorePriceArrays pairing is wrong somewhere.
  2. Add a ticker you know is not in the database — ZZZZNOTREAL will do. It must appear as no data, not as 0.00 %. This is the check that matters, because a silent zero is indistinguishable from a real flat result.

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.

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.

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_AAPL if you know that name exists, but you cannot watch “whatever the loop just wrote”, and you cannot call VarGet from the Watch window because functions are not allowed there.
  • Find-in-files stops working. Searching for score_AAPL finds 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.

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.

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

Question 1. A formula runs `VarSet( "score_AAPL", 12.5 );` in a chart pane. Which statement is correct?
Show the answer and why

Answer: It exists only for the rest of this formula execution

Dynamic variables live inside the current formula execution only. Sharing between formulas, panes or windows — and any persistence at all — is what the StaticVar family is for.

Question 2. Why does the worked example test `IsNull( Score[ LastBar ] )` instead of `IsNull( LastValue( Score ) )`?
ScoreNow = Score[ LastBar ];
if( IsNull( ScoreNow ) ) VarSetText( "state_" + Sym, "no data" );
Show the answer and why

Answer: LastValue returns zero when the last bar is Null, so the test would never fire

The official LastValue page states it returns zero if the last bar of the array is Null. A missing symbol would then score 0.00% and look like a real flat result rather than an absent one.

Question 3. Which of these are true of dynamic variables? Select all that apply.
Show the answer and why

Answer: They are always global, even when set inside a user-defined function, VarGetText returns an empty string for a name that was never set, VarSet can fail if the constructed name matches a function identifier

The first three are documented on the official pages. The fourth is false: the Watch window accepts variable identifiers and expressions over them, but it cannot call functions.

Question 4. You loop a 40-symbol watch list, compute one number per symbol, and at the end print them in list order. Nothing else looks a value up by ticker. What should you use?
Show the answer and why

Answer: An ordinary array indexed by loop position

You never retrieve by key, only iterate in order. An array indexed by position is one variable instead of forty, visible in the debugger, and usable with AFL array operations.

Sources for this lesson

7 verified · checked 2026-08-31

  1. 01AFL Function Reference — VarSetamibroker.com/guide/afl/varset.html2026-08-31
  2. 02AFL Function Reference — VarGetamibroker.com/guide/afl/varget.html2026-08-31
  3. 03AFL Function Reference — VarSetTextamibroker.com/guide/afl/varsettext.html2026-08-31
  4. 04AFL Function Reference — VarGetTextamibroker.com/guide/afl/vargettext.html2026-08-31
  5. 05AmiBroker User's Guide — AFL language reference§ typeof operatoramibroker.com/guide/a_language.html2026-08-31
  6. 06AmiBroker User's Guide — User-defined functions and scopeamibroker.com/guide/a_userfunctions.html2026-08-31
  7. 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.