Skip to content
Level 5 · Real-Time AmiBroker UserLessonPart 23 · page 1 of 430 min Professional edition Live feed
30Minutes
6AFL functions
8Sources
Professional + live feedRequires

This page needs the Professional edition and a real-time data feed. Every gated part of this course ships a Level A path that uses Bar Replay and historical data instead — look for it below.

AFL functions taught here6

GetRTData() and the Live Quote Fields

Every AFL variable you have written so far has been a series: Close has one value per bar, MA( Close, 50 ) has one value per bar, and a comparison between them produces one Boolean per bar. GetRTData() does not work like that. It hands you a single number describing this instant, it has no history whatsoever, and on most installations it hands you nothing at all.

By the end of this lesson you should be able to name every field the function documents, write the guard that distinguishes “the feed says zero” from “there is no feed”, read a quote for a symbol other than the one on screen, and convert the feed’s own timestamp into something you can compare with a bar timestamp.

The reference page gives the signature as GetRTData( "fieldname" ), filed under Miscellaneous functions, added in AmiBroker 4.60, returning a NUMBER. It retrieves the last — that is, the most recent — value of a named field reported by the streaming real-time data source for the currently selected symbol.

The word to underline in that sentence is last. Not the value at each bar. There is exactly one, and it belongs to the moment your formula ran.

An array function and a scalar function on the same five bars

One scalar, broadcast across every bar. The letter b is the same single value in all five cells — there is no bar-by-bar bid history to show.
Bar12345
Close10.011.012.011.013.0
MA(Close, 3)NullNull11.011.312.0
GetRTData("Bid")bbbbb
One scalar, broadcast across every bar. The letter b is the same single value in all five cells — there is no bar-by-bar bid history to show. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

Three consequences follow, and they are the reason this function surprises people:

  • You cannot backtest against it. A backtest walks history; GetRTData() has none. If you write Buy = Close > GetRTData( "Ask" ); and run a backtest over five years, the one value read at the moment the backtest started is compared against every bar of 2019. The result is arithmetic, not a simulation.
  • You cannot recover a past bid. Nothing in AFL exposes yesterday’s spread. If you want a history of quote fields you have to sample them yourself on each refresh and store them, which is what static variables are for.
  • Arithmetic with an array silently broadcasts. Close - GetRTData( "Last" ) compiles, runs, and produces an array — every bar’s close minus one number. That is occasionally what you want and frequently a bug.

Field names are strings. The reference page prints exactly these, in this order, with these descriptions. GetRTDataForeign() reprints the same list verbatim on its own page.

Field name Documented meaning
"Ask" current best ask price
"AskSize " current ask size
"Bid" current best bid price
"BidSize " current bid size
"52WeekHigh" 52 week high value
"52WeekHighDate" 52 week high date (in datenum format)
"52WeekLow" 52 week low value
"52WeekLowDate" 52 week low date (in datenum format)
"Change" change since yesterdays close
"Dividend" last dividend value
"DivYield" dividend yield
"EPS" earnings per share
"High" current day’s high price
"Low" current day’s low price
"Open" current day’s open price
"Last" last trade price
"OpenInt" current open interest
"Prev" previous day close
"TotalVolume" total today’s volume
"TradeVolume" last trade volume
"ChangeDate" datenum (YYYMMDD) of last data change
"ChangeTime" timenum (HHMMSS) of last data change
"UpdateDate" datenum (YYYMMDD) of last data update
"UpdateTime" timenum (HHMMSS) of last data update
"Shares" total number of shares

Twenty-five names. Four details about that table are worth having in your head before you start typing.

Two names are printed with a trailing space. The page shows "AskSize " and "BidSize " with a space inside the closing quote, while every other entry has none. That is a typesetting artefact of the reference page, not a field whose name contains a space. The names to write are "AskSize" and "BidSize".

The date fields are described as YYYMMDD. Three Ys. The other date fields on the same page are described as “in datenum format”, and datenum is a four-digit-year encoding everywhere else in AFL. This is a typo in the documentation. The point of mentioning it is not pedantry: when a reference page has visible slips in it, treat the shape of a value as something to verify empirically rather than as something you have been told.

Field names are matched without regard to case. The list prints "52WeekHigh", and the page’s own example then calls GetRTData( "52weekhigh" ). Both work. Pick one convention and keep it — the list’s own capitalisation is the sensible choice.

Some obvious names are not there. There is no "Volume": today’s cumulative figure is "TotalVolume" and the size of the most recent trade is "TradeVolume". There is no "Close": the current price is "Last" and yesterday’s is "Prev". And there is nothing at all for market depth. Level 2 order-book data has no AFL access point, and neither does the Time and Sales tape — Part 22 covers both windows, and the absence of an AFL route to them is documented by omission rather than by any statement.

This is where formulas go wrong, so read the two notes on the reference page as carefully as they were written.

Note 1: the function is available only in the Professional edition, and calling it from the Standard edition “will give you NULL values for all fields”. Note 2: it “works only if data source uses real time data source (plugin)”.

Between them those sentences cover almost everyone reading this. An end-of-day database, an ASCII import, an AmiQuote database, a Norgate database, a real-time database whose plugin has disconnected, or any database at all under the Standard edition: in every one of those cases there is no streaming source to report a field, and there is nothing for the function to return.

The correct shape is to test for absence explicitly and to keep the absent case absent:

Fragment — not a complete formula

BidValue = GetRTData( "Bid" );
// Wrong: turns "no feed" into a price of zero, and every calculation
// downstream now works on a number that was never quoted by anybody.
BidValue = Nz( GetRTData( "Bid" ) );
// Right: absence stays absence, and the display says so.
if( IsNull( BidValue ) )
BidText = "n/a";
else
BidText = NumToStr( BidValue, 1.4 );

Nz() is a fine function and this course uses it constantly. It is the wrong function here, because the whole information content of a missing quote is that it is missing.

There is a second trap hiding behind the first. Zero is a legitimate value for several of these fields — "OpenInt" on an equity, "TradeVolume" before the first print of the day, "Dividend" on a company that pays none. A guard written as if( BidValue == 0 ) conflates “the feed told me zero” with “the feed told me nothing”, and those need different handling. IsNull() is the test that distinguishes them.

Editions, and a documentation inconsistency worth knowing about

Section titled “Editions, and a documentation inconsistency worth knowing about”

The edition requirement for these two functions is unambiguous and appears on both function pages: Professional only, Null for every field otherwise. Nothing in this lesson works around that.

The wider claim is less tidy, and you will meet the untidiness. The User’s Guide chapter on getting quotes heads its real-time table “REAL-TIME DATA (Professional Edition only)” — and that same page carries the line “THIS PAGE IS CURRENT AS OF March 2, 2023”, along with entries for data vendors that have since changed hands or shut down. AmiBroker’s own ordering page, meanwhile, describes real-time capability in the Standard edition with a restricted symbol count in the Real-Time Quote window.

Both statements are official. They are not obviously reconcilable, and this course is not going to pretend one of them away.

GetRTDataForeign( "fieldname", "symbol" ) does the same job for a ticker other than the selected one. It was added in AmiBroker 4.80, takes exactly the same field-name list, and carries the same two notes about editions and plugins.

Its reference page makes one performance claim explicitly: it is “much faster than SetForeign/GetRTData combo”. That sentence is the whole design rule for multi-symbol real-time panels. Switching the evaluation context with SetForeign() in a loop, purely so that GetRTData() reads a different symbol, is the slow way to build a heat map. Pass the ticker as an argument instead.

Fragment — not a complete formula

// A three-symbol quote strip, without changing the evaluation context.
IndexBid = GetRTDataForeign( "Bid", "^GSPC" );
IndexAsk = GetRTDataForeign( "Ask", "^GSPC" );
// The same guard applies to every one of these calls. A foreign symbol that
// is not currently streaming is exactly as absent as no feed at all.
if( IsNull( IndexBid ) OR IsNull( IndexAsk ) )
StripText = "index quote unavailable";
else
StripText = "index spread " + NumToStr( IndexAsk - IndexBid, 1.4 );

There is a hard practical ceiling on this too. The real-time data chapter states that AmiBroker will rotate active symbols in the Data Manager when you list more tickers than your subscription allows, and that this rotation does not apply to the Real-Time Quote window, which cannot hold more symbols than the subscription limit. A dashboard that calls GetRTDataForeign() across five hundred tickers on a fifty-symbol subscription is not reading five hundred live quotes. Part 18 has the documented symbol ceilings by vendor.

Four of the fields are timestamps, and they are not in the format the rest of AFL uses. "ChangeDate" and "UpdateDate" are DATENUMs; "ChangeTime" and "UpdateTime" are TIMENUMs. Neither encoding is a DateTime, and a DateTime is what DateTime(), Now(5) and DateTimeDiff() expect.

DateTimeConvert() is the bridge. Format 2 converts a datenum plus an optional timenum into a DateTime:

Fragment — not a complete formula

// Two encodings in, one DateTime out.
FeedStamp = DateTimeConvert( 2, GetRTData( "UpdateDate" ), GetRTData( "UpdateTime" ) );
// DateTime values are a bitset. Only == and != are reliable on them, so elapsed
// time comes from DateTimeDiff(), never from subtraction or a > comparison.
AgeSeconds = DateTimeDiff( Now( 5 ), FeedStamp );

Two warnings come attached to that second line, both documented. Now() reads your PC clock, which has no relationship to the timestamps in your database unless the database timeshift happens to align them; Status( "timeshift" ) reports that shift in seconds so you can see whether the comparison means anything. And Status( "lastrtupdate" ) gives the DateTime of the last update sent by the plugin, which is often the better reference for “how stale is my feed” — with the caveat, stated on the Status() page, that it depends on the plugin sending correct update stamps, that most sources send odd timestamps at weekends, and that the IQFeed plugin sends them only inside regular trading hours.

Note 4 on the reference page is the answer to the most common complaint about this function. The value it returns is the current value at the moment of the call — and your formula is only called when the chart or commentary is re-executed, at the refresh interval set in Preferences. The built-in Real-Time Quote window refreshes far more often: the page puts it at “at least 10 times per second”.

So the number in the quote window and the number in your panel will disagree, routinely, and neither is wrong. Part 21 separates feed updates from chart refreshes properly; the next lesson but one shows how to take control of the refresh side with RequestTimedRefresh().

Finding out what your feed actually supplies

Section titled “Finding out what your feed actually supplies”

Note 3 says that availability of data depends on the underlying data source, and directs you to the Real-Time Quote window to see whether a given field is available. That is the right first check, and it is a Part 22 subject. The second check is to ask from AFL, symbol by symbol, and record the answer.

Complete runnable AFL

rt-field-probe.afl
// rt-field-probe.afl
// Part 23 - GetRTData() and the Live Quote Fields
//
// An Exploration that asks your data source, field by field, which of the
// documented GetRTData() fields it actually supplies for each symbol.
//
// Why it exists: the AFL Function Reference lists the field names but states
// that "availability of data depends on underlying data source". It does not
// say which vendor supplies which field, because it cannot. The only reliable
// answer is empirical, and this is how you get it.
//
// How to run it:
// Send to Analysis, set "Apply to" to a small watch list, set Range to
// 1 recent bar, press Explore.
//
// Assumptions and honest limits:
// - GetRTData() is documented as PROFESSIONAL edition only. In the Standard
// edition every field is documented to return Null.
// - It is also documented to work "only if data source uses real time data
// source (plugin)". On an end-of-day, ASCII or AmiQuote database there is
// no feed to ask, so every quote column will be empty. That is the correct
// output of this formula, not a fault in it.
// - Empty is not the same as zero. The documentation promises no numeric
// sentinel for the no-feed case, so nothing here converts an absent field
// into a number.
// - The field names below are copied from the GetRTData() reference page in
// the order that page prints them. Two of them are printed on that page
// with a stray trailing space inside the quotes ("AskSize " and
// "BidSize "); the names used here are the ones without it.
_SECTION_BEGIN("Real-time field probe");
// One row per symbol rather than one row per bar: these are scalar snapshots
// of "now", so repeating them down a column of bars would be misleading.
Filter = Status( "lastbarinrange" );
// Probes five core fields and counts how many answered. Any one of them
// answering is enough to prove a live source is attached; none answering is
// the expected result on a database with no real-time plugin.
function FieldAnswered( FieldName )
{
return IIf( IsNull( GetRTData( FieldName ) ), 0, 1 );
}
AnsweredCount = FieldAnswered( "Last" ) +
FieldAnswered( "Bid" ) +
FieldAnswered( "Ask" ) +
FieldAnswered( "Prev" ) +
FieldAnswered( "TotalVolume" );
if( AnsweredCount == 0 )
FeedVerdict = "no real-time field answered - offline database, Standard edition, or this vendor supplies none of the probed fields";
else
FeedVerdict = NumToStr( AnsweredCount, 1.0 ) + " of 5 probed core fields answered";
AddTextColumn( FeedVerdict, "Feed status", 1.0, colorDefault, colorDefault, 380 );
// The complete documented field list, in the order the reference page gives it.
// An empty cell means the field returned Null: either no feed, or this vendor
// does not supply that field.
AddColumn( GetRTData( "Ask" ), "Ask", 1.4 );
AddColumn( GetRTData( "AskSize" ), "AskSize", 1.0 );
AddColumn( GetRTData( "Bid" ), "Bid", 1.4 );
AddColumn( GetRTData( "BidSize" ), "BidSize", 1.0 );
AddColumn( GetRTData( "52WeekHigh" ), "52WeekHigh", 1.4 );
AddColumn( GetRTData( "52WeekHighDate" ), "52WeekHighDate", 1.0 );
AddColumn( GetRTData( "52WeekLow" ), "52WeekLow", 1.4 );
AddColumn( GetRTData( "52WeekLowDate" ), "52WeekLowDate", 1.0 );
AddColumn( GetRTData( "Change" ), "Change", 1.4 );
AddColumn( GetRTData( "Dividend" ), "Dividend", 1.4 );
AddColumn( GetRTData( "DivYield" ), "DivYield", 1.4 );
AddColumn( GetRTData( "EPS" ), "EPS", 1.4 );
AddColumn( GetRTData( "High" ), "High", 1.4 );
AddColumn( GetRTData( "Low" ), "Low", 1.4 );
AddColumn( GetRTData( "Open" ), "Open", 1.4 );
AddColumn( GetRTData( "Last" ), "Last", 1.4 );
AddColumn( GetRTData( "OpenInt" ), "OpenInt", 1.0 );
AddColumn( GetRTData( "Prev" ), "Prev", 1.4 );
AddColumn( GetRTData( "TotalVolume" ), "TotalVolume", 1.0 );
AddColumn( GetRTData( "TradeVolume" ), "TradeVolume", 1.0 );
AddColumn( GetRTData( "ChangeDate" ), "ChangeDate", 1.0 );
AddColumn( GetRTData( "ChangeTime" ), "ChangeTime", 1.0 );
AddColumn( GetRTData( "UpdateDate" ), "UpdateDate", 1.0 );
AddColumn( GetRTData( "UpdateTime" ), "UpdateTime", 1.0 );
AddColumn( GetRTData( "Shares" ), "Shares", 1.0 );
// UpdateDate is a DATENUM and UpdateTime is a TIMENUM. They are two different
// encodings, and neither of them is a DateTime, so they have to be converted
// before they can be displayed or compared as one value.
FeedStamp = DateTimeConvert( 2, GetRTData( "UpdateDate" ), GetRTData( "UpdateTime" ) );
AddColumn( FeedStamp, "Feed update stamp", formatDateTime );
// The bar timestamp comes from the database, which is a different clock from
// the feed's own update stamp. Showing both makes the difference visible.
AddColumn( DateTime(), "Last bar in range", formatDateTime );
_SECTION_END();

Download rt-field-probe.afl97 lines

Send it to the Analysis window, set Apply to to a small watch list, set Range to one recent bar, and press Explore. You get one row per symbol, a verdict column, and one column per documented field.

Nothing makes GetRTData() return a bid on a database with no streaming plugin. What can be done — and what this part is actually assessing — is everything around that call.

Run the probe on the database you already have. The empty result is not a failure to work around; it is the contract. Most published real-time AFL fails on exactly this case, and having seen it once you will recognise the failure mode in other people’s code.

Write and test the absence handling now. IsNull() guards, “n/a” display strings, and derived values that stay absent when their inputs are absent are all testable on an end-of-day database, because that database produces the absent case on every run. The project at the end of this part is built that way on purpose.

Use historical intraday bars for the array-shaped work. Session high, session low, session range, cumulative volume and the position of the last price inside the day’s range are all computable from stored bars. Only bid, ask and their sizes genuinely require a feed. Part 18 covers the free and low-cost intraday sources; Part 19 covers building the database.

Use Bar Replay for the time-ordered practice. Replay truncates every symbol at a playback position, which reproduces the one thing an end-of-day chart cannot: not knowing what happens next. GetPlaybackDateTime() returns that position, or zero when replay is not running, so a formula can tell the difference and label itself accordingly. Part 26 is the full treatment; the project in this part uses it as a display mode.

GetRTData() returns one number, for one field, for the currently selected symbol, at the moment the formula ran. Twenty-five field names are documented, two of them printed with a stray trailing space, none of them exposing depth or the tape. The function is Professional-only and needs a streaming plugin, and where either condition fails the documented result is Null — not zero, and not any other number you might be tempted to substitute. GetRTDataForeign() reads another ticker and is documented as much faster than switching context with SetForeign(). The four timestamp fields arrive as DATENUM and TIMENUM and need DateTimeConvert() before they can be compared with anything.

The next lesson answers a different question about the same formula: not what the data is, but who is asking for it.

Check your understanding

Question 1. A formula on an end-of-day database contains `Spread = GetRTData( "Ask" ) - GetRTData( "Bid" );`. What has the reference page actually promised about the value of `Spread`?
Spread = GetRTData( "Ask" ) - GetRTData( "Bid" );
Show the answer and why

Answer: Nothing useful: the function is documented to work only with a real-time plugin, so the result must be treated as absent and guarded

The page states the function works only when the data source is a real-time plugin, and that the Standard edition yields Null for every field. It promises no numeric substitute for the missing case, so the only defensible code path tests with IsNull() and reports absence rather than computing on it.

Question 2. Which of these are documented GetRTData() field names? Select all that apply.
Show the answer and why

Answer: "TotalVolume", "Prev", "TradeVolume"

The list has "TotalVolume" for today’s cumulative figure and "TradeVolume" for the size of the last trade, but no "Volume". Yesterday’s close is "Prev" and the current price is "Last"; there is no "Close" field. Guessing a plausible name gets you a Null that looks exactly like a missing feed.

Question 3. Your panel prints the feed timestamp as a meaningless number. The code is `Stamp = GetRTData( "UpdateTime" );` followed by `DateTimeToStr( Stamp )`. What is wrong?
Stamp = GetRTData( "UpdateTime" );
Text  = DateTimeToStr( Stamp );
Show the answer and why

Answer: "UpdateTime" is a TIMENUM, not a DateTime; it needs DateTimeConvert( 2, datenum, timenum ) first

AFL uses three different encodings for time. DATENUM and TIMENUM are decimal-packed numbers; DateTime is a separate bitset encoding. Format 2 of DateTimeConvert() combines a datenum and a timenum into a DateTime, which is what the string and difference functions expect.

Question 4. You want a live quote strip for eight index symbols on a chart showing a ninth. Which approach does the documentation point to?
Show the answer and why

Answer: GetRTDataForeign() with the ticker as its second argument

The GetRTDataForeign() page states it is much faster than the SetForeign/GetRTData combination, which is the documented reason to prefer it for multi-symbol panels. Foreign() reads price arrays, not quote fields, and has no field-name argument.

Question 5. The Real-Time Quote window shows a bid that differs from the one your chart panel is displaying. What is the most likely explanation?
Show the answer and why

Answer: The quote window refreshes at least ten times a second while the formula only sees a new value when the chart re-executes

This asymmetry is documented on the GetRTData() page itself. The function returns the value at the moment of formula execution, and execution happens at the chart refresh interval, so the two displays disagree by design rather than by fault.

Sources for this lesson

8 verified · checked 2026-08-31

  1. 01AmiBroker AFL Function Reference — GetRTDataamibroker.com/guide/afl/getrtdata.html2026-08-31
  2. 02AmiBroker AFL Function Reference — GetRTDataForeignamibroker.com/guide/afl/getrtdataforeign.html2026-08-31
  3. 03AmiBroker AFL Function Reference — DateTimeConvertamibroker.com/guide/afl/datetimeconvert.html2026-08-31
  4. 04AmiBroker AFL Function Reference — DateTimeDiffamibroker.com/guide/afl/datetimediff.html2026-08-31
  5. 05AmiBroker AFL Function Reference — Statusamibroker.com/guide/afl/status.html2026-08-31
  6. 06AmiBroker User's Guide — Real-time data sourcesamibroker.com/guide/h_rtsource.html2026-08-31
  7. 07AmiBroker User's Guide — Real-Time Quote windowamibroker.com/guide/w_rtquote.html2026-08-31
  8. 08AmiBroker User's Guide — How to get quotes from various markets§ Real-time dataamibroker.com/guide/h_quotes.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.