Foreign(), SetForeign() and RestorePriceArrays()
By the end of this lesson you will be able to pull any other symbol’s price and volume series into a formula, say exactly which of your own variables that changes, and prove on screen that you put them back. Three functions do the work, and the difference between using them correctly and using them plausibly is not visible in the output — which is why the last third of this lesson is about verification rather than syntax.
One formula, two symbols
Section titled “One formula, two symbols”When the Analysis window or a chart runs your formula, one symbol is current.
Close, High, Volume and the rest are that symbol’s arrays. Foreign() reaches
past that boundary and fetches an array belonging to a different symbol, then hands
it back to you already lined up with the current symbol’s bars.
That last clause is the whole design. If the two series were returned on their own
calendars you could not subtract one from the other, because bar 400 would mean a
different date in each. So AmiBroker guarantees that the array you receive is bar
for bar the foreign symbol’s data on the current symbol’s dates. The function’s
author states this explicitly in the notes on the Foreign page: if DateNum()
returns a given date for a bar, then Foreign("SYMBOL","C") at that bar is that
symbol’s close on the same date.
Synchronising two calendars requires decisions, and AmiBroker makes them for you. Understanding what it decided is the difference between a comparison and a coincidence — Lesson 4 of this part is devoted to it. For now, hold onto the shape of the problem:
What Foreign() has to do before you get an array
- Read the foreign symbolIts own bars, on its own trading days
- Drop bars the current symbol does not haveA day the index traded and your stock did not simply disappears
- Fill bars the foreign symbol does not haveControlled by the fixup argument
- Return an array of exactly BarCount valuesAligned to the current symbol
Foreign(): read one array
Section titled “Foreign(): read one array”The documented signature is:
Fragment — not a complete formula
Foreign( TICKER, DATAFIELD, fixup = 1 )It returns an array.
TICKER is a string holding the symbol, spelled exactly as your database
spells it. There is no universal ticker for “the S&P 500”: what you type depends on
where your data came from, and getting it wrong is the most common failure in this
whole part.
DATAFIELD is a string selecting which array to read. The documented codes are
"O", "H", "L", "C", "V" and "I" for open interest, plus "1" and "2"
for the Aux1 and Aux2 fields from version 5.29 onwards. The official example on the
Foreign page also uses the long form "Close", so both spellings appear in real
code.
fixup decides what happens on bars where the foreign symbol has no data. It
takes three documented values, and they are not interchangeable:
fixup |
What happens on a bar the foreign symbol did not trade |
|---|---|
0 |
Nothing. The hole stays Null, and you handle it yourself |
1 (default) |
O, H, L and C are all set to the previous bar’s Close; volume is set to zero |
2 |
Pre-4.90 behaviour: the previous bar’s O, H, L, C and V are repeated |
The reference is blunt about the choice: unless you know exactly what you are doing,
leave fixup at its default of 1, because with fixup = 0 you inherit Null
values that you must deal with yourself.
That advice is right for drawing a chart and wrong for auditing your data, which is why this part uses both. Look at what the three modes actually produce on a Wednesday when the foreign symbol did not print:
One missing foreign bar, three fixup modes
| Bar | Mon | Tue | Wed | Thu | Fri |
|---|---|---|---|---|---|
Current symbol Close | 10.00 | 10.20 | 10.10 | 10.40 | 10.60 |
Foreign C, fixup = 0 | 50.00 | 50.50 | Null | 51.00 | 51.40 |
Foreign C, fixup = 1 | 50.00 | 50.50 | 50.50 | 51.00 | 51.40 |
Foreign H, fixup = 1 | 50.30 | 50.80 | 50.50 | 51.20 | 51.60 |
Foreign V, fixup = 1 | 1.2m | 0.9m | 0 | 1.1m | 1.3m |
Foreign V, fixup = 2 | 1.2m | 0.9m | 0.9m | 1.1m | 1.3m |
The reference does not document what Foreign() returns for a ticker that is not in
the database, so do not assume it. The official Example 2 on the Foreign page tests
the data instead, with IsNull( fc[0] ). A slightly stronger test that works in any
fixup mode is to count the bars that came back with something in them:
Fragment — not a complete formula
BenchmarkClose = Foreign( "^GSPC", "C" );BenchmarkBars = LastValue( Cum( NOT IsNull( BenchmarkClose ) ) );HaveBenchmark = BenchmarkBars > 0;SetForeign(): swap the whole price group
Section titled “SetForeign(): swap the whole price group”Six Foreign() calls to get O, H, L, C, V and OI is verbose, and it is also slow.
SetForeign() does the same job in one call:
Fragment — not a complete formula
SetForeign( ticker, fixup = True, tradeprices = False )It returns a number: 1 if the ticker exists, 0 if it does not. The reference
documents the equivalence explicitly — a single SetForeign() call does what six
Foreign() calls do, and takes about the same time as one of them, which the page
describes as roughly six times faster.
After a successful call, Open, High, Low, Close, Volume, OpenInt and
Avg belong to the foreign symbol. Avg is documented as (C+H+L)/3 of the
foreign symbol — note the three components, not four. Every function that reads
those arrays follows automatically, which is the point: RSI(), MA(), ATR(),
Cross() and your own library functions all now describe the other symbol without
being told.
The tradeprices argument
Section titled “The tradeprices argument”tradeprices defaults to False. When set to True, the substitution extends
beyond the price arrays to BuyPrice, SellPrice, ShortPrice, CoverPrice,
PointValue, TickSize, RoundLotSize and MarginDeposit. The reference gives
the reason plainly: this is what allows Equity() to work correctly against foreign
data, because the backtest machinery needs the foreign symbol’s contract details as
well as its prices. The official Example 2 uses exactly that combination.
For chart and exploration work you almost always want the default. Turn it on only when you are simulating trading in the foreign symbol, and remember the matching requirement in the next section.
What SetForeign() does not change
Section titled “What SetForeign() does not change”This is where formulas silently go wrong. The documented substitution covers the price group and, optionally, the trade-price group. It does not cover anything that describes the symbol’s identity:
Name()still returns the original symbol.FullName(),MarketID(),GroupID(),SectorID()andIndustryID()still describe the original symbol.
That is entirely reasonable — the formula is still running on the original symbol
— but it means a loop that switches to each symbol in a watch list and labels its
output with Name() will label every row with the same ticker. Use your own loop
variable for identity, and reserve the price arrays for prices.
RestorePriceArrays(): when it is mandatory
Section titled “RestorePriceArrays(): when it is mandatory”Fragment — not a complete formula
RestorePriceArrays( tradeprices = False )It returns nothing. It takes no ticker, because there is nothing to choose: it puts back whatever the original symbol of this execution was.
Call it after every SetForeign(). Not after the last one in a formula — after
every one, including inside a loop body before the next iteration. A SetForeign()
without a matching restore leaves every later line of the formula operating on
foreign data while looking exactly like code about the current symbol. If the
formula ends there, the damage is limited to that pane. If the formula continues
into Buy and Sell assignments, an entire backtest is computed on the wrong
instrument and reported under the right one’s name.
Three details of the restore are easy to miss:
tradepricesmust match the value you passed toSetForeign(). If you switched withSetForeign( sym, True, True )and restore with a bareRestorePriceArrays(), the price arrays come back butBuyPrice,TickSize,PointValueand the rest stay pointed at the foreign symbol. The backtester then sizes and fills trades using another instrument’s contract specification.- It is the same underlying function as
TimeFrameRestore(). The author states this in the notes on the RestorePriceArrays page. CallingRestorePriceArrays()therefore also cancels a timeframe previously set withTimeFrameSet(). A formula that opens a weekly timeframe, then callsSetForeign()and restores inside it, has silently returned to the base interval as well. - There is no stack. Nothing in the documentation describes nested
SetForeign()calls unwinding in last-in-first-out order. Do not write code that depends on it. Switch, use, restore; then switch again.
There is one further documented use, added in version 5.90: RestorePriceArrays()
can also restore OHLC arrays that you overwrote directly, with no TimeFrameSet()
or SetForeign() involved at all. If you have ever assigned Close = something;
during an experiment, this is how you get the real data back.
Formula: proving the scope instead of assuming it
Section titled “Formula: proving the scope instead of assuming it”Everything above is a claim about what happens inside your formula. None of it is
visible in a chart. This formula makes it visible: it plots the current symbol’s RSI
against a benchmark’s RSI, and prints, in the title, whether the switch succeeded,
what Name() returned inside the substituted block, and whether the arrays actually
came back afterwards.
The value is not the indicator. The value is a template you can paste the verification lines out of, into any cross-symbol formula you write later.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// ===========================================================================// Foreign and SetForeign - what changes, and for how long//// Draws the RSI of the chart's own symbol against the RSI of a benchmark,// and - more importantly - proves on screen which variables the substitution// touched and which it left alone.//// HOW TO RUN// Paste into Formula Editor, Apply Indicator to a new pane. Press Ctrl+R to// set the benchmark symbol and the fixup mode.//// WHAT IT DEMONSTRATES// 1. Foreign( ticker, field, fixup ) reads ONE array and changes nothing.// 2. SetForeign( ticker, fixup, tradeprices ) swaps the whole price group,// and its second argument is a fixup code, NOT a data field.// 3. Name() is not part of the price group, so it still reports the chart's// own symbol from inside the substituted block.// 4. RestorePriceArrays() puts the originals back. The title prints the// total absolute difference between Close after the restore and the copy// taken before the substitution. Anything other than 0 means the restore// did not happen.//// ASSUMPTIONS, STATED SO THEY CAN BE CHECKED// - The benchmark ticker is spelled exactly as your database spells it.// Ticker conventions differ between data sources; there is no universal// symbol for "the S&P 500".// - SetForeign returns 0 and leaves the arrays untouched when the ticker is// missing, so a typo produces a chart of the WRONG symbol's RSI drawn// under the RIGHT symbol's label unless the return value is tested.// - With the default fixup of 1, bars on which the benchmark did not trade// are filled from its previous Close, which makes them flat bars with zero// volume. Any indicator that uses range or volume sees those as real.// ===========================================================================
_SECTION_BEGIN( "Foreign scope demonstration" );
BenchmarkSymbol = ParamStr( "Benchmark symbol", "^GSPC" );RsiPeriod = Param( "RSI period", 14, 2, 100, 1 );FixupMode = Param( "Fixup: 0 = leave Null, 1 = fill from Close, 2 = repeat bar", 1, 0, 2, 1 );
// A copy of the home symbol's Close, taken before anything is substituted.// It is the only way to make the restore verifiable rather than assumed.HomeClose = Close;HomeName = Name();
// --- 1. Foreign(): one field, nothing else -------------------------------// Argument order is ( ticker, datafield, fixup ). The second argument here IS// a field code.BenchmarkClose = Foreign( BenchmarkSymbol, "C", FixupMode );
// The reference does not document what Foreign returns for a ticker that is// not in the database, so test the data rather than trusting the call. Counting// the non-Null bars works whatever the fixup mode.BenchmarkBars = LastValue( Cum( NOT IsNull( BenchmarkClose ) ) );HaveBenchmark = BenchmarkBars > 0;
// --- 2. SetForeign(): the whole price group ------------------------------// Argument order is ( ticker, fixup, tradeprices ). The second argument here is// NOT a field code. SetForeign( "^GSPC", "C" ) compiles and does the wrong thing.Switched = SetForeign( BenchmarkSymbol, FixupMode );
// Declared before the branch so that both paths leave them defined.BenchmarkRsi = Null;NameInside = HomeName;
if ( Switched ){ // Close, Open, High, Low, Volume, OpenInt and Avg now belong to the // benchmark, so every function that reads them silently follows. BenchmarkRsi = RSI( RsiPeriod );
// Name() is not part of the price group. It still returns the home symbol. NameInside = Name();}
// --- 3. Restore, unconditionally -----------------------------------------// Called outside the if() on purpose: a restore that only runs on the success// path is the bug this lesson exists to prevent.RestorePriceArrays();
HomeRsi = RSI( RsiPeriod );
// Zero if and only if every bar of Close matches the pre-substitution copy.RestoreError = LastValue( Cum( abs( Close - HomeClose ) ) );
// --- Plot -----------------------------------------------------------------Plot( HomeRsi, "RSI " + HomeName, colorBlue, styleLine | styleThick );Plot( BenchmarkRsi, "RSI " + BenchmarkSymbol, colorOrange, styleLine );PlotGrid( 30, colorLightGrey );PlotGrid( 70, colorLightGrey );
// Everything the reader needs to judge the chart is stated in words, so the// pane is readable without relying on the two line colours._N( Title = StrFormat( "%s - RSI(%g) against %s\n", HomeName, RsiPeriod, BenchmarkSymbol ) + WriteIf( Switched, "SetForeign succeeded. ", "SetForeign FAILED - the ticker was not found, the arrays were never " + "switched, and the second line below is the home symbol again. " ) + StrFormat( "Benchmark bars with data: %g\n", BenchmarkBars ) + "Name() inside the substituted block returned: " + NameInside + " (the home symbol, not the benchmark)\n" + StrFormat( "Restore check - total |Close - HomeClose| after restore: %g\n", RestoreError ) + WriteIf( RestoreError == 0, "Arrays restored correctly.", "ARRAYS NOT RESTORED - everything below this line is benchmark data." ) );
_SECTION_END();How it works
Section titled “How it works”The formula takes a copy of Close into HomeClose before touching anything. That
copy is the only thing in the file that makes the restore checkable rather than
assumed, and it costs one array.
The Foreign() section demonstrates the read-one-array form and, in the same
breath, the existence test: counting non-Null bars works in every fixup mode,
where testing a single bar does not.
The SetForeign() section is guarded by its own return value. BenchmarkRsi and
NameInside are given defaults before the if, so that both paths through the
formula leave every variable defined — a habit worth keeping, because AFL will
happily let you read a variable that only one branch assigned and the error message
you get is about something else entirely.
RestorePriceArrays() sits outside the if. That placement is deliberate: a
restore that only runs on the success path is precisely the bug the lesson is
warning about, and putting it on the failure path too costs nothing.
Finally, RestoreError sums the absolute difference between Close and the
pre-substitution copy across every bar. There is exactly one correct value.
Key functions
Section titled “Key functions”Cum( array )— running total.Cum( NOT IsNull( x ) )counts how many bars ofxcarried a value;LastValue()of that is the total.LastValue( array )— the value at the last bar, turned into a number so it can be used in a comparison or printed withStrFormat().WriteIf( condition, "true text", "false text" )— chooses a string. Used here to state the verdict in words rather than expecting the reader to interpret a number.
Expected result
Section titled “Expected result”A pane with two RSI lines and a title block of five lines. On a healthy run the
title says the switch succeeded, reports a benchmark bar count in the same order of
magnitude as the chart’s own bar count, reports that Name() inside the block
returned the chart’s own symbol, and ends with total |Close - HomeClose| after restore: 0 followed by Arrays restored correctly.
Test it
Section titled “Test it”- Misspell the benchmark ticker in the Parameters dialog. The title should switch to the SetForeign FAILED message and the benchmark bar count should read 0. Notice that the chart still draws, and that without the guard you would have no way to tell.
- Comment out the
RestorePriceArrays()line and refresh.RestoreErrorbecomes non-zero and the title says so. This is the failure the lesson is about, made visible for once. - Set
fixupto 0 in the Parameters dialog and compare the benchmark bar count with the count atfixup = 1. The difference is the number of bars AmiBroker manufactured on your behalf.
Common errors
Section titled “Common errors”| What you did | What you see |
|---|---|
SetForeign( sym, "C" ) |
No error. The string is taken as a fixup value, and the chart looks plausible |
Restore inside the if only |
Works until the ticker is wrong, then fails silently |
Used Name() to label the foreign line |
Both lines carry the same symbol name |
Restored with the wrong tradeprices |
Prices look right; a later backtest sizes positions from another instrument |
| Compared bar counts of two symbols and expected equality | They are rarely equal, and inequality alone is not the fault |
Extension
Section titled “Extension”Add a second benchmark and a second SetForeign()/RestorePriceArrays() pair, then
extend the restore check so that it verifies Close, High, Low and Volume
rather than Close alone. If you would rather not write four checks, sum them:
Cum( abs(Close-HomeClose) + abs(High-HomeHigh) + abs(Low-HomeLow) ).
The performance cost
Section titled “The performance cost”Cross-symbol access is not free, and the User’s Guide is specific about why. Any
access to a symbol other than the current one involves a global lock — a critical
section — and therefore may affect performance. The multithreading page recommends
reducing the use of Foreign() and AddToComposite() in favour of static
variables wherever possible.
Three practical consequences:
- On a chart, the cost is irrelevant. Four
Foreign()calls on one pane will never be what makes your screen slow. - In a scan or exploration across thousands of symbols, it matters a great deal.
A
Foreign()call in a formula applied to 3,000 symbols is 3,000 acquisitions of a lock that every thread shares, which is exactly the shape of workload that refuses to speed up when you add threads. - Where you need the same foreign series for every symbol in a run — a market index used as a regime filter, for instance — compute it once and put it in a static variable, as Part 13 describes. That converts 3,000 locked reads into one.
Three more functions in the same family
Section titled “Three more functions in the same family”PlotForeign() draws another symbol’s price chart without pulling it into
variables first:
Fragment — not a complete formula
PlotForeign( tickersymbol, name, color, style = styleCandle | styleOwnScale, minvalue, maxvalue, XShift = 0, ZOrder = 0, width = 1 );Note the default style: it already includes styleOwnScale, so the plot does not
share the price pane’s scale unless you say otherwise. Passing your own style
without styleOwnScale changes that silently, and minvalue/maxvalue do nothing
unless styleOwnScale is in effect.
RelStrength( "tickername", fixup = 1 ) returns comparative relative strength
against another symbol. Two things about it are worth knowing before you use it in
research. First, passing an empty string makes it use the base security
configured in Symbol → Categories for that symbol’s market, which means the same
formula silently measures against a different benchmark when a symbol is filed
under a different market. Second, the function reference describes what it does but
does not publish the arithmetic; the interpretation paragraph on that page is a user
comment, not vendor documentation. When you need a quantity you can define and
defend in a write-up, compute the ratio yourself from Foreign() — which is what
the next lesson does.
GetBaseIndex() returns, as a string, the relative-strength base index
configured for the current symbol’s market. It is the honest way to find out what
RelStrength("") would compare against, and it is useful in an exploration column
when you are auditing a database you did not build.
Foreign() fetches one array and changes nothing else. SetForeign() replaces the
whole price group and optionally the trade-price group, returns 0 without changing
anything when the ticker is missing, and leaves every identity function untouched.
RestorePriceArrays() ends the substitution, must match the tradeprices flag you
switched with, and — because it is the same function as TimeFrameRestore() — also
ends any timeframe you had set.
The two fixup arguments are the ones to slow down for. They sit in different
positions in the two functions, and their default of 1 quietly manufactures flat,
zero-volume bars wherever the foreign symbol did not trade. The next three lessons
are, in one way or another, about the consequences of that sentence.
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AFL Function Reference — Foreignamibroker.com/guide/afl/foreign.html2026-08-31
- 02AFL Function Reference — SetForeignamibroker.com/guide/afl/setforeign.html2026-08-31
- 03AFL Function Reference — RestorePriceArraysamibroker.com/guide/afl/restorepricearrays.html2026-08-31
- 04AFL Function Reference — PlotForeignamibroker.com/guide/afl/plotforeign.html2026-08-31
- 05AFL Function Reference — RelStrengthamibroker.com/guide/afl/relstrength.html2026-08-31
- 06AFL Function Reference — GetBaseIndexamibroker.com/guide/afl/getbaseindex.html2026-08-31
- 07AmiBroker User's Guide — Multithreading§ Reducing the use of AddToComposite / Foreign to a minimumamibroker.com/guide/h_multithreading.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.