Static Variables in Depth
Part 13 used static variables as plumbing: write a score per symbol, generate ranks, read them back. This lesson is about what you were actually handling. A static variable is a slot in a single, flat, application-wide store that outlives every formula, that no window owns, that nothing clears for you, and that several threads may be reading while another writes. Each of those properties is a way to get a wrong answer quietly, and each has a documented pattern that avoids it.
Lifetime: the program run, not the formula
Section titled “Lifetime: the program run, not the formula”The sentence repeated on all four core pages is precise, and every word in it is load bearing:
Static variable — the variable has static duration (it is allocated when the program begins and deallocated when the program ends) and initializes it to Null unless another value is specified. Static variables allow to share values between various formulas.
“The program” is the running AmiBroker process. Not the formula. Not the chart pane. Not the Analysis run. A static variable written by an exploration at nine in the morning is still there, unchanged, when a chart formula reads it at four in the afternoon, unless something explicitly removed it or AmiBroker was closed in between.
Who can see a static variable
- Chart pane AIndicator formulareads and writes
- Chart pane B, on another symbolDifferent formula entirelysame names
- Analysis window — ExplorationOne thread per symbol
- Analysis window — BacktestPhase I threads, then Phase II
- The static variable storeOne flat name → value map for the whole processno scoping of any kind
There is no scoping. Two unrelated formulas that both use the name "values" are using
the same slot, and the second one to run wins. This is the single most common way static
state goes wrong, and it is not a bug — it is what “share values between various
formulas” means.
The persistent argument
Section titled “The persistent argument”By default nothing survives a restart. StaticVarSet( "name", value, persistent = False, compressionMode = cmDefault ) takes persistence as its third positional argument;
StaticVarSetText( "name", "value", persist = False ) takes it as its third argument too,
and has no fourth. Setting it to True — available since 5.80 — means the variable is
written to a file called PersistVars.bin when AmiBroker closes, and reloaded on the
next startup.
Fragment — not a complete formula
// Survives a restart. Note the third argument.StaticVarSet( "RS_universe_asof", DateNum(), True );
// StaticVarSetText has no compressionMode argument - do not copy the 4-argument form.StaticVarSetText( "RS_universe_name", "ASX200", True );SetOption( "StaticVarAutoSave", interval ) adds periodic saving on top of the save on
exit, which the documentation says “is always done”. The interval is in seconds; zero
disables it; and from version 6.90 an interval of -1 performs a single one-shot save.
The compressionMode argument (6.10+) has three documented values: cmDefault compresses
persistent variables only, cmNever never compresses, cmAlways compresses persistent and
non-persistent alike. Compression works by removing repeated values from the sequence and
restoring them on read — which is why it is incompatible with align = False on
StaticVarGet, where the repeated values are simply not retrieved.
Static arrays
Section titled “Static arrays”Arrays became storable in 5.30 and are where the memory goes. The StaticVarSet page is
blunt about the cost: a static array variable “will consume 8 * (number_of_bars) bytes of
memory and it won’t be released until program is closed or variable is removed using
StaticVarRemove()”. Five hundred symbols at twenty thousand bars is roughly eighty
megabytes that nothing will ever reclaim on its own.
Three behaviours around arrays cause more confusion than the memory does.
Only the bars currently in use are stored. StaticVarSet stores “only as many bars as
there are currently in use by given chart”. Under QuickAFL that is not the whole history,
so a static array written from a zoomed chart is shorter than you expect. This is the one
place where AddToComposite behaves differently — it forces all bars — and the difference
is documented explicitly.
Reads are aligned by timestamp unless you say otherwise. StaticVarGet( "varname", align = True ) synchronises stored values against the current symbol’s date and time
stamps. With align = False there are no checks at all: values are filled in consecutively
regardless of their timestamps, and if the stored array is shorter than the current one,
“the last value of static array will be propagated till BarCount - 1”. The documentation
advises against align = False unless you know exactly what you are doing, and adds that
speed is not a reason to reach for it, because alignment “has similar complexity as plain
memory copy”.
Intervals must match. Read a static back in the same interval you wrote it in.
Cross-interval reads do padding and compression automatically, but — quoting the page —
“Foreign compresses data always from base-time interval, while static variables operate
on previously stored interval, hence result may differ”. Reading a daily-written static on
an intraday chart gives “essentially flat lines for each day”. Tick, n-volume and
n-tick intervals do not work reliably at all, because timestamps can repeat.
Threads, atomicity and races
Section titled “Threads, atomicity and races”AmiBroker’s threading model is stated as one rule: one operation on one symbol is one thread. Three chart panes on one symbol are three threads. A scan over four hundred symbols is up to four hundred pieces of work spread over the available threads, capped at 2 per Analysis window in the Standard edition and 32 in Professional, and in both cases capped by the number of logical processors Windows reports.
What the multi-threading chapter says about static variables specifically is narrow and
worth quoting rather than paraphrasing: access is “fast, thread-safe, and atomic at the
single StaticVarSet/StaticVarGet call level. This means that it reads/writes an entire
array in an atomic way, so no other thread will read/write that array in the middle of
another thread updating it.”
Read that as: one call is indivisible. Two calls are not. A read-modify-write done as
StaticVarGet, then arithmetic, then StaticVarSet is three operations, and another
thread can slip between them. That is the classic lost-update race, and it is the reason
the following pattern is wrong in an Analysis run:
Fragment — not a complete formula
// WRONG in a multi-symbol Analysis run: three operations, not one.Total = Nz( StaticVarGet( "TotalVolume" ) );Total = Total + Volume;StaticVarSet( "TotalVolume", Total );There are three documented ways out, in increasing order of cost.
One writer, many readers
Section titled “One writer, many readers”The documented rule of thumb: as long as only one thread writes and many threads only read, no synchronisation is needed. In practice that means doing all the writing in a block that AmiBroker runs on its own:
Fragment — not a complete formula
if( Status( "stocknum" ) == 0 ){ StaticVarRemove( "score*" ); // note the asterisk // ... build every score here, single threaded ...}
// Every symbol, in parallel, reads only.MyScore = StaticVarGet( "score" + Name() );AmiBroker detects that Status("stocknum") == 0 statement, runs the very first symbol in
one thread only, waits for it to complete, and only then launches the other threads.
StaticVarAdd for accumulation
Section titled “StaticVarAdd for accumulation”StaticVarAdd( "name", value, keepAll = True, persistent = False ), added in 6.10, does the
read-add-write as one interlocked operation. It is the documented multi-threading-safe
replacement for AddToComposite. Two details catch people:
- It converts all Nulls to zeros, as
AddToCompositedoes. AddToCompositeskips group 253 by default so composites are not added to themselves.StaticVarAdddoes not, so guard it yourself:if( GroupID() != 253 ) StaticVarAdd( ... );- It does not raise the “required bars” estimate the way
AddToCompositedoes, so under QuickAFL you may be accumulating a truncated array. The 7.00 release notes recommend addingSetBarsRequired( sbrAll, sbrAll )when you need every bar.
Its documented speed advantage over AddToComposite is roughly twofold single-threaded and
around fourfold with eight threads — and the docs are candid that lock contention means “it
does not scale as much as naive person may think”.
A real lock, when nothing else will do
Section titled “A real lock, when nothing else will do”StaticVarCompareExchange( "varname", exchange, comperand ) is an atomic compare-and-swap on
a scalar numeric static variable. It compares the variable with comperand, stores
exchange if they are equal, and returns the value the variable had before the call —
not a success flag. Zero comes back if the variable did not exist. The idiom is:
Fragment — not a complete formula
// Acquire: we own the lock only if the previous value was 0.if( StaticVarCompareExchange( "MySemaphore", 1, 0 ) == 0 ){ // ... critical section ... StaticVarSet( "MySemaphore", 0 ); // release - you must do this yourself}Naming schemes
Section titled “Naming schemes”One flat namespace means collisions are your problem. Two conventions are worth adopting.
For the function-based API there is no automatic namespacing at all, so use an explicit
module prefix — "MOM_score" + Sym, "RS_rank" + Sym — and always remove with that same
prefix. Choose prefixes that are long and distinctive, because AmiBroker’s matching is
“starts with”: a prefix of "val" will also swallow every "values*" variable belonging to
something else and treat the wrong remainder as a symbol name.
For declared statics there is a documented mechanism. #pragma enable_static_decl "prefix"
enables the static keyword, and the prefix is prepended automatically to the real static
variable name “to avoid name clashes/conflicts”:
Fragment — not a complete formula
#pragma enable_static_decl "MOMPANE{chartid}"
static _lastAlertBar; // real name is MOMPANE<chartid>_lastAlertBar
if( IsNull( _lastAlertBar ) ) _lastAlertBar = 0;The tokens {chartid}, {interval} and {symbol} are substituted at run time, which gives
you per-chart, per-interval or per-symbol namespaces for free. The documented convention is
to prefix declared statics with _ or s_ so they stand out in the code.
Two properties of declared statics differ from the function calls, and both matter. A declared static is read once at the declaration and saved once when execution completes, so it costs no more than a normal variable — but it is therefore not a live channel between threads during a run. And the underlying variable is still an ordinary static reachable under its full prefixed name from anywhere, so the prefix is namespacing, not privacy.
Cleanup
Section titled “Cleanup”Nothing is removed automatically. Not at the end of a formula, not at the end of an
Analysis run. StaticVarRemove( "variablename" ) is the only documented way to free static
array memory short of closing AmiBroker, and since 5.30 it accepts wildcards: * matches
any number of characters including zero, ? matches exactly one.
AmiBroker’s own ranking examples carry this comment, shouting included:
Fragment — not a complete formula
// delete static variables - DO NOT forget the asterisk (wildcard) at the endStaticVarRemove( "ValuesToSort*" );Forget the asterisk and last run’s scores stay resident, and the ranking silently uses a
mixture of old and new. Put the removal inside the Status("stocknum") == 0 block so it
runs once, before the writes, rather than once per symbol.
Debugging static state
Section titled “Debugging static state”When a formula “mysteriously” sees stale data, look at the store before you look at the formula.
A read-only pane that answers three questions: how many static variables exist, how much memory they occupy, and how that splits across the prefixes you care about.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// static-var-inspector.afl// Part 36 - Static Variables in Depth//// Reports what is currently resident in AmiBroker's application-wide static// variable namespace: how many variables, how much memory, and how that// breaks down across the name prefixes you care about.//// WHY THIS EXISTS// Static variables outlive every formula run. Nothing clears them for you.// When a formula "mysteriously" sees stale data, the first question is what// is actually in the namespace - and this pane answers it.//// ASSUMPTIONS// - AmiBroker 7.00.1. StaticVarInfo(..., "count") needs 6.90 or later;// StaticVarInfo itself needs 5.60; StaticVarCount needs 5.30.// - Read-only. This formula never writes and never removes a static// variable, so it cannot damage state that another formula depends on.// - Memory figures are bytes as reported by AmiBroker, not an estimate.
_SECTION_BEGIN("Static Variable Inspector");
// Prefixes you want broken out, separated by commas. Everything not matched// by one of these still appears in the totals.PrefixList = "rank,score,~,MOM_,RS_";
TotalCount = StaticVarCount();TotalMemory = StaticVarInfo( "*", "totalmemory" );
Report = "Static variable namespace\n" + "Variables resident: " + NumToStr( TotalCount, 1.0 ) + "\n" + "Total memory: " + NumToStr( TotalMemory / 1024, 1.1 ) + " KB\n" + "-----------------------------\n";
for( i = 0; ( Prefix = StrExtract( PrefixList, i ) ) != ""; i++ ){ // The wildcard matters. "*" matches any number of characters INCLUDING // zero, so "score*" also matches a variable named exactly "score". Pattern = Prefix + "*";
Count = StaticVarInfo( Pattern, "count" ); Memory = StaticVarInfo( Pattern, "totalmemory" );
Report = Report + Pattern + " " + NumToStr( Nz( Count ), 1.0 ) + " var(s), " + NumToStr( Nz( Memory ) / 1024, 1.1 ) + " KB\n";}
// The full list goes to the Interpretation window rather than the chart title,// because on a busy installation it is far too long for a title bar.// Pass the list as an ARGUMENT, never as the format string itself: a stray// "%" inside the data would be read as a formatting specifier and raise// Error 61. The %s specifier needs AmiBroker 6.20 or later.printf( "Static variables currently resident:\n%s\n", StaticVarInfo( "*", "list" ) );
Plot( Close, "Close", colorDefault, styleCandle );
Title = Report;
_SECTION_END();How it works
Section titled “How it works”StaticVarCount() gives the global total — it has no filtering argument, which is why the
per-prefix breakdown uses StaticVarInfo instead. StaticVarInfo( pattern, field ) accepts
the same wildcards as StaticVarRemove, and its four documented fields are "list",
"memory", "totalmemory" and "count". The loop asks for a count and a memory figure per
prefix; the full name list goes to the Interpretation window via printf, because on a busy
installation it is far too long for a chart title.
Key functions
Section titled “Key functions”StaticVarInfo( "*", "totalmemory" ) reports bytes including the memory used by the
variable names themselves; "memory" excludes them. With thousands of long per-symbol names
the difference is not negligible.
StaticVarInfo( pattern, "count" ) requires AmiBroker 6.90 or later. StaticVarInfo
itself requires 5.60 and StaticVarCount requires 5.30.
Expected result
Section titled “Expected result”A title reading something like Variables resident: 412 with a memory figure and one line
per prefix. On a freshly started AmiBroker with no formulas run, expect zero.
Test it
Section titled “Test it”Run a Part 13 ranking exploration, then apply this pane. The rank* and score* counts
should be non-zero and roughly equal to the number of symbols in your watch list. Now close
AmiBroker, reopen it, and apply the pane again before running anything: everything that was
not written with persistent = True must be gone. If something survived, you have a
persistent variable you did not intend.
Common errors
Section titled “Common errors”Using StaticVarGet’s align argument as though it were StaticVarGetText’s — the text
function has no align argument, and StaticVarSetText has no compressionMode. Copying a
signature across the family is a reliable way to produce an argument-count error.
Extension
Section titled “Extension”Add a fourth column: for each prefix, the count of variables whose names end in a ticker that is not in the current watch list. Those are the leftovers from a different universe, and they are exactly what corrupts a ranking that looks fine.
What changed
Section titled “What changed”You now hold the right model. Static variables are a process-wide key/value store; they
outlive every execution; persistence to disk is opt-in per variable and happens on exit or
on a timer; a single Get or Set is atomic but a group of them is not; in multi-symbol
Analysis you write once under Status("stocknum") == 0 and read freely afterwards; and
nothing cleans up unless you do, with the asterisk in the right place and the pattern narrow
enough not to take out someone else’s state.
Check your understanding
Sources for this lesson
10 verified · checked 2026-08-31
- 01AFL Function Reference — StaticVarSetamibroker.com/guide/afl/staticvarset.html2026-08-31
- 02AFL Function Reference — StaticVarGetamibroker.com/guide/afl/staticvarget.html2026-08-31
- 03AFL Function Reference — StaticVarGetTextamibroker.com/guide/afl/staticvargettext.html2026-08-31
- 04AFL Function Reference — StaticVarRemoveamibroker.com/guide/afl/staticvarremove.html2026-08-31
- 05AFL Function Reference — StaticVarInfoamibroker.com/guide/afl/staticvarinfo.html2026-08-31
- 06AFL Function Reference — StaticVarAddamibroker.com/guide/afl/staticvaradd.html2026-08-31
- 07AFL Function Reference — StaticVarCompareExchangeamibroker.com/guide/afl/staticvarcompareexchange.html2026-08-31
- 08AmiBroker User's Guide — Efficient use of multi-threadingamibroker.com/guide/h_multithreading.html2026-08-31
- 09AmiBroker User's Guide — static keywordamibroker.com/guide/keyword/static.html2026-08-31
- 10AFL Function Reference — SetOption§ StaticVarAutoSaveamibroker.com/guide/afl/setoption.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.