Skip to content
Level 5 · Real-Time AmiBroker UserLessonPart 23 · page 3 of 428 min
28Minutes
8AFL functions
6Sources
StandardRequires
AFL functions taught here8

RequestTimedRefresh() and Real-Time Performance

A chart pane runs your formula when you apply it, when you change a parameter, when you scroll, and when new data arrives. RequestTimedRefresh() adds a fifth trigger: a clock. One line turns a static pane into one that re-executes on a schedule, and the schedule is yours to choose.

The line is easy. Choosing the number is the lesson. Every refresh re-runs the whole formula, every pane runs on its own thread, and a layout with a dozen panes all asking for one-second refreshes is a straightforward way to saturate a processor with work nobody looks at.

The reference page gives the signature as RequestTimedRefresh( interval, onlyvisible = True ), filed under Indicators, added in AmiBroker 4.90, returning nothing. It causes the indicator window containing it to refresh automatically every interval seconds — and the wording of the next clause is the important one — “regardless of data source used or connection state”.

That clause is why this lesson carries no Professional badge and needs no subscription. Timed refresh is not a real-time feature that happens to work offline. It is a charting feature that works everywhere, and real-time work happens to depend on it.

AmiBroker aligns refreshes to second boundaries. RequestTimedRefresh( 5 ) fires at second 0, 5, 10 and so on through 55 of each minute, rather than five seconds after whenever the pane happened to load.

Since version 5.30.3 the interval can go below one second, down to 0.1 s — but only when enabled through a registry setting, HKCU/Software/TJP/Broker/Settings/EnableHiresRTR, as a DWORD value of 1. Treat that as a documented capability rather than a recommendation. Sub-second refresh multiplies every cost on this page by up to ten, and the accuracy caveat above does not improve to match.

The second argument defaults to True, meaning refreshes are triggered only for visible, non-minimised windows. This applies to the main AmiBroker window as well: minimise AmiBroker and, by default, charts stop refreshing. Passing False keeps them refreshing while minimised.

Fragment — not a complete formula

RequestTimedRefresh( 5 ); // stops when the window is minimised
RequestTimedRefresh( 5, False ); // keeps running when minimised

The visibility rule is narrower than it first appears. It covers the minimised state, and the case where a chart has been dragged beyond the edge of the physical screen so it is open but not visible to an eye.

Every timed refresh re-executes the entire formula for that pane. Not the changed part: all of it, from the first line.

The multi-threading chapter supplies the other half of the arithmetic. Its core rule is that one operation on one symbol is one thread, where an operation is displaying a single chart pane, or running a scan, exploration, backtest or optimisation. A chart window with three panes therefore uses three threads on a single symbol. Thread limits are edition-dependent: the Standard edition is limited to two threads per Analysis window and the Professional edition allows thirty-two, and AmiBroker additionally caps threads at the number of logical processors Windows reports.

What sits between the timer and the number on screen

  1. Timer firesAligned to the second boundary, accurate to about ±55 ms
  2. Pane thread wakesOne chart pane is one thread2 per Analysis window on Standard, 32 on Professional
  3. Quotations loadedAs many bars as the pane is configured to use
  4. Your whole formula executesEvery line, from the top
  5. Pane repaintsPlot output and low-level graphics

The consequence is easy to work out and easy to forget:

Panes asking for refresh Interval Whole-formula executions per minute
1 5 s 12
4 5 s 48
4 1 s 240
12 1 s 720
12 0.5 s 1,440

Multiply the right-hand column by the milliseconds one execution takes, and you have the processor time your layout consumes doing nothing but redrawing itself. Twelve panes at one second, each taking 40 ms, is 28.8 seconds of formula execution per minute — spread across threads, but real work all the same, before AmiBroker has painted anything.

There is a second refresh source underneath all of this. Preferences, on the Intraday tab, sets a real-time chart refresh interval whose documented default is three seconds. Setting that field to zero requests a refresh on every arriving trade — and the manual states plainly that the Standard edition will not allow it, making that a Professional-only capability. With zero set, AmiBroker refreshes on every new trade provided the formulas execute fast enough, and if they do not it dynamically adjusts the rate to hold average processor use at no more than fifty per cent. Part 21 covers how that interacts with the feed itself. RequestTimedRefresh() is an additional, formula-driven trigger on top of it, not a replacement for it.

GetPerformanceCounter( bReset = False ) reads the Windows high-resolution performance counter and returns milliseconds, with a resolution documented as up to 0.001 ms. Added in 4.90, available in both editions.

Three documented facts shape how you use it.

Reset before you measure. Without a reset the value is milliseconds since the machine booted, which is a very large number, and AFL carries about seven significant digits. The page recommends resetting at the start of the block being measured so that floating-point resolution does not eat the answer.

A call costs about 0.015 ms. The page’s own example says so. Two calls bracket every measurement, so anything under roughly 0.05 ms is mostly the measurement apparatus.

Counters are per formula. Resetting in one formula does not affect the counters in another, so two panes can measure themselves independently without interfering.

There is also a hardware caveat printed on the page: the function relies on the Windows QueryPerformanceCounter API and the processor’s timestamp counter, and may give inaccurate results on multi-core processors with clock-stepping technologies enabled in BIOS. If your readings swing wildly with no change in workload, suspect that before suspecting your code.

A chart pane that reports, in its own title, how long it took to execute, its worst case and mean since you last reset it, how many times it has run, and roughly what share of one processor core that implies at the interval you chose — plus a dial for making it deliberately more expensive so you can watch the numbers move.

Complete runnable AFL

refresh-cost-meter.afl
// refresh-cost-meter.afl
// Part 23 - RequestTimedRefresh() and Real-Time Performance
//
// Measures what this chart pane costs every time it runs, and reports the
// answer inside the pane, so that a refresh interval can be chosen from
// evidence instead of from optimism.
//
// It reports:
// - milliseconds taken by one execution of the measured block;
// - a running worst case and a running mean since the counters were reset;
// - how many times the pane has executed;
// - roughly what share of one CPU core that implies at the chosen interval;
// - whether this particular execution came from the timer or from you.
//
// Assumptions and honest limits:
// - No data feed of any kind is required. RequestTimedRefresh() is
// documented to refresh "regardless of data source used or connection
// state", so this runs perfectly well on an end-of-day database.
// - GetPerformanceCounter() is documented to cost about 0.015 ms per call,
// so readings below roughly 0.05 ms are mostly measurement noise.
// - Its reference page also warns that the underlying counter can be
// inaccurate on machines with CPU clock-stepping enabled in BIOS. If your
// numbers jump around wildly with no change in workload, suspect that
// before suspecting AmiBroker.
// - The duty-cycle figure is your formula's own cost only. It excludes
// everything AmiBroker does around the formula - loading quotations,
// painting the pane, the data plugin's own work.
// - Statistics live in static variables keyed by GetChartID(), so two copies
// of this pane keep separate counters instead of corrupting each other's.
_SECTION_BEGIN("Refresh cost meter");
RefreshSeconds = Param( "Refresh interval (seconds)", 1, 1, 60, 1 );
ExtraPasses = Param( "Extra passes over the data", 0, 0, 200, 1 );
ResetPressed = ParamTrigger( "Statistics", "Reset counters" );
RequestTimedRefresh( RefreshSeconds );
//----------------------------------------------------------------------------
// The measured block. Everything between the reset and the read is timed.
//----------------------------------------------------------------------------
// Reset first. The counter otherwise reports milliseconds since the machine
// booted, which is a very large number, and AFL carries about seven
// significant digits - so an unreset measurement of a short block loses most
// of its precision. The reference page recommends this explicitly.
GetPerformanceCounter( True );
FastLine = MA( Close, 20 );
SlowLine = MA( Close, 50 );
AtrValue = Max( ATR( 14 ), 0.000001 );
Score = ( FastLine - SlowLine ) / AtrValue;
// A dial for making the formula genuinely more expensive. Each extra pass is
// real array work over the whole loaded history, which is how real formulas
// get slow: more indicators, more lookbacks, more bars.
for( i = 1; i <= ExtraPasses; i++ )
{
Score = Score + ( MA( Close, 20 + i ) - SlowLine ) / AtrValue;
}
ElapsedMs = GetPerformanceCounter();
//----------------------------------------------------------------------------
// Running statistics, kept per chart pane.
//----------------------------------------------------------------------------
StatKey = "rtcost" + NumToStr( GetChartID(), 1.0 ) + "_";
// Static variables start as Null, so the first read of each one has to be
// guarded. Nz() does that without pretending a missing value was ever zero
// anywhere else.
RunCount = Nz( StaticVarGet( StatKey + "runs" ) );
TotalMs = Nz( StaticVarGet( StatKey + "total" ) );
WorstMs = Nz( StaticVarGet( StatKey + "worst" ) );
if( ResetPressed )
{
RunCount = 0;
TotalMs = 0;
WorstMs = 0;
}
RunCount = RunCount + 1;
TotalMs = TotalMs + ElapsedMs;
WorstMs = Max( WorstMs, ElapsedMs );
StaticVarSet( StatKey + "runs", RunCount );
StaticVarSet( StatKey + "total", TotalMs );
StaticVarSet( StatKey + "worst", WorstMs );
MeanMs = TotalMs / Max( RunCount, 1 );
// One pane executing for MeanMs every RefreshSeconds occupies this fraction of
// one core. Multiply by the number of panes doing the same thing to get the
// number that actually matters.
DutyPercent = 100 * ( MeanMs / 1000 ) / RefreshSeconds;
if( Status( "redrawaction" ) == 1 ) RedrawSource = "timer";
else RedrawSource = "user action or new data";
//----------------------------------------------------------------------------
// Report.
//----------------------------------------------------------------------------
Plot( Score, "Fast-slow distance in ATR units", colorBlueGrey, styleLine | styleThick );
PlotGrid( 0, colorLightGrey );
_N( Title =
Name() + " - " + Interval( 2 ) + " - refresh cost meter\n" +
StrFormat( "This execution: %.3f ms (redraw from ", ElapsedMs ) + RedrawSource + ")\n" +
StrFormat( "Mean %.3f ms, worst %.3f ms, over %g executions\n", MeanMs, WorstMs, RunCount ) +
StrFormat( "Refresh every %g s implies about %.2f%% of one core for this pane alone\n",
RefreshSeconds, DutyPercent ) +
StrFormat( "Bars given to this formula: %g Extra passes: %g\n", BarCount, ExtraPasses ) +
"Thread " + NumToStr( Status( "ThreadID" ), 1.0 ) );
_SECTION_END();

Download refresh-cost-meter.afl116 lines

The measured block is bracketed by two calls: GetPerformanceCounter( True ) to reset, and GetPerformanceCounter() to read. Everything between them is timed, and nothing else is. The block contains ordinary array work — two moving averages and an ATR — plus a loop whose count comes from a parameter. Each extra pass computes another moving average over the whole loaded history, which is how real formulas actually become slow: more indicators, longer lookbacks, more bars, not clever tricks.

The statistics survive between executions in static variables, which is the only mechanism that does. Their names are prefixed with GetChartID(), so two copies of the pane on the same chart keep separate counters instead of trampling each other. Every read is wrapped in Nz(), because a static variable that has never been set reads as Null.

The duty-cycle figure is the mean execution time divided by the refresh interval, expressed as a percentage. It is deliberately understated: it counts your formula only, not the quotation loading or the repaint around it. Treat it as a floor.

Status( "redrawaction" ) reports whether this particular execution came from the timer or from you, and Status( "ThreadID" ) names the thread it ran on — useful when several panes are running and you want to confirm they really are separate.

GetPerformanceCounter( True ) resets the counter; the same call without an argument reads it. GetChartID() returns the chart’s identifier, which is what makes the static-variable names unique per pane. ParamTrigger() puts a button in the Parameters dialog: it returns 1 for the single execution following a press and 0 afterwards, which is exactly the semantics a reset needs. StaticVarSet() and StaticVarGet() carry the running totals across executions.

Apply the pane to any chart on any database. The title updates on the interval you set, the execution time reads as a fraction of a millisecond with the extra-pass dial at zero, and the run count climbs steadily. Turn the dial up and both the per-execution time and the duty percentage rise with it. Press the reset button in the Parameters dialog and the run count returns to one.

Set the extra passes to zero, note the mean over a minute, then set them to fifty and note it again. The difference is the cost of fifty additional moving averages over your loaded history, and it should scale roughly linearly with the dial. If it does not, either your bar count is small enough that overhead dominates, or the counter caveat above applies to your machine.

Then open a second copy of the pane on the same chart. Both should keep independent counts, and both should report different thread identifiers.

Reading GetPerformanceCounter() without resetting first produces a number in the millions with no useful precision in its low digits. Measuring a block that takes less than about 0.05 ms measures the two calls rather than the block. Reusing one static-variable name across panes produces statistics that jump between panes. And forgetting Nz() on the first read gives Null totals that propagate silently through every subsequent calculation.

Add a second measured block around only the loop, so the title reports the fixed cost and the variable cost separately. Then reduce the pane’s bar count in the chart settings and watch how much of the cost was the number of bars rather than the number of indicators — which is usually the answer nobody expects.

Distinguishing a timer refresh from any other

Section titled “Distinguishing a timer refresh from any other”

Status( "redrawaction" ) returns 0 for a regular refresh and 1 for one triggered by RequestTimedRefresh(). The reference page offers this specifically as the way to tell them apart, and it is the guard that makes side effects survivable in a self-refreshing pane.

Fragment — not a complete formula

RequestTimedRefresh( 1 );
// Only act on the timer's schedule. A mouse-driven repaint, a scroll or a
// parameter change must not be allowed to look like the passage of time.
if( Status( "redrawaction" ) == 1 )
{
_TRACE( "timer refresh at " + Now() );
}

A related function, RequestMouseMoveRefresh(), added in 6.30, re-executes the formula when the cursor moves over the chart area. It carries no example and no performance note of its own, but the cost model is identical: every mouse movement re-runs the whole formula. It belongs in hover readouts and crosshair panels, not in anything that polls for data.

  • On a chart of end-of-day data that changes once a day. The refresh will find nothing new and cost the same as one that does.
  • On a heavy formula, at a short interval. Measure first. A formula taking 200 ms cannot usefully be asked to run every 100 ms.
  • On a pane that lives on a sheet you do not display. It will not run at all, and the silence looks like working code.
  • As a substitute for repeat scanning. If the job is to watch a universe rather than a symbol, the Analysis window’s repeat facility is the right tool, and Part 24 covers its costs.
  • To drive anything that must happen at a precise moment. The timer is documented as approximate. Build on completed bars, not on wall-clock precision.
  • On more panes than you actually read. The cheapest optimisation available is deleting a pane.

There is nothing to work around here. RequestTimedRefresh() is documented to work irrespective of the data source and the connection state, GetPerformanceCounter() and Status() are available in both editions, and the cost meter above measures a formula, not a feed.

That makes this the best place in the whole real-time track to do your tuning. Build the pane on your end-of-day database, measure it honestly, choose an interval you can defend, and only then point it at live data. Doing it the other way round means tuning a formula while the market is moving, which is the worst possible time to learn that your layout needs 30 seconds of processor time per minute.

If you want the pane to update against changing data without a subscription, run Bar Replay underneath it: replay advances the visible data for every symbol, and the pane will redraw against a moving series exactly as it would against a feed. Part 26 covers what that simulation does and does not reproduce.

RequestTimedRefresh( interval, onlyvisible ) re-executes an indicator pane on a schedule, independently of any data source, aligned to second boundaries and accurate to roughly a twentieth of a second. The onlyvisible default stops refreshes for minimised windows and cannot rescue panes on inactive sheets, which do not exist until shown. Each refresh runs the entire formula on its own thread, so the cost of a layout is panes multiplied by frequency multiplied by execution time. GetPerformanceCounter() measures that execution time in milliseconds, provided you reset it first and do not try to time anything shorter than its own overhead. Status( "redrawaction" ) separates timer refreshes from every other kind, which is what lets a self-refreshing pane hold a side effect safely.

Next, the three functions come together into a panel that has to behave correctly whether or not anything is streaming.

Check your understanding

Question 1. A layout has eight panes, each calling `RequestTimedRefresh( 1 )`, and each formula takes 25 ms. How much formula execution is that per minute?
Show the answer and why

Answer: 12 seconds

Eight panes at one execution per second is 480 executions per minute, and 480 × 25 ms is 12,000 ms. Spread over threads it is still 12 seconds of processor work per minute for a layout that has not been asked to do anything else. This arithmetic is the whole reason to measure execution time before choosing an interval.

Question 2. Your alerting pane sits on a sheet you never open, and it has `RequestTimedRefresh( 1, False )`. Why does it never fire?
RequestTimedRefresh( 1, False );
Show the answer and why

Answer: Panes on inactive sheets do not exist until shown, so they cannot be refreshed; onlyvisible does not change that

The documentation states that the visibility rule mainly covers minimised windows and windows moved off the physical screen, and that panes on inactive sheets do not really exist until they are shown — so they cannot be refreshed at all. Setting onlyvisible to False keeps a minimised window refreshing; it cannot create a pane that has never been displayed.

Question 3. Which of these are documented facts about GetPerformanceCounter()? Select all that apply.
Show the answer and why

Answer: It returns milliseconds, Calling it costs about 0.015 ms, It should be reset before timing a short block, because AFL carries about seven significant digits

The page states all three of those. It also states the opposite of the third option: resetting counters inside one formula does not affect counters in other formulas, which is what allows two panes to measure themselves independently.

Question 4. You want a self-refreshing pane to write one debug line per timer tick, and not one per scroll or mouse-over. What does the documentation offer?
Show the answer and why

Answer: Status("redrawaction") == 1

The RequestTimedRefresh() page gives this as an explicit hint: Status("redrawaction") returns 0 for a regular refresh caused by a user action and 1 for a timer refresh. Status("action") is actionIndicator for every chart repaint, whatever caused it, so it cannot distinguish them.

Sources for this lesson

6 verified · checked 2026-08-31

  1. 01AmiBroker AFL Function Reference — RequestTimedRefreshamibroker.com/guide/afl/requesttimedrefresh.html2026-08-31
  2. 02AmiBroker AFL Function Reference — RequestMouseMoveRefreshamibroker.com/guide/afl/requestmousemoverefresh.html2026-08-31
  3. 03AmiBroker AFL Function Reference — GetPerformanceCounteramibroker.com/guide/afl/getperformancecounter.html2026-08-31
  4. 04AmiBroker AFL Function Reference — Statusamibroker.com/guide/afl/status.html2026-08-31
  5. 05AmiBroker User's Guide — Multi-threadingamibroker.com/guide/h_multithreading.html2026-08-31
  6. 06AmiBroker User's Guide — Preferences§ Intradayamibroker.com/guide/w_preferences.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.