Skip to content
Level 3 · AFL DeveloperLessonPart 14 · page 2 of 530 min
30Minutes
7AFL functions
8Sources
StandardRequires
AFL functions taught here7

TimeFrameSet() and TimeFrameRestore()

TimeFrameSet( inWeekly ) is one line long and it changes the meaning of every line after it. By the end of this lesson you will know precisely which arrays it replaces, what happens to everything it does not replace, why the numbers it produces are attached to the wrong bars until you do something about it, and which restrictions the official documentation places on what you may do inside the block.

Fragment — not a complete formula

TimeFrameSet( inWeekly ); // returns NOTHING; every price array is now weekly
WeeklyAverage = MA( Close, 10 );
TimeFrameRestore();

The documented syntax is TimeFrameSet( interval ), and the argument is an interval in seconds. Sixty means one minute. Everything else follows from that: the named constants are just readable spellings of second counts.

Constant Documented value Notes
in1Minute 60
in5Minute 5 × 60
in15Minute 15 × 60
inHourly 3600
inDaily 24 × 3600 86,400
inWeekly 5 × 24 × 3600 + 1 432,001 — note the +1
inMonthly 25 × 24 × 3600 + 1 2,160,001 — note the +1
inQuarterly not published constant exists; the guide does not print its value
inYearly not published constant exists; the guide does not print its value

There is no in30Minute and no in4Hour in the documented constant list. For anything not named, the documented idiom is an integer multiple: 3 * in1Minute for three-minute bars, 4 * inHourly for four-hour bars, 3 * inDaily for three-day bars.

Two other forms are documented. A negative interval means N-tick bars — TimeFrameSet( -133 ) is 133-tick compression — and works correctly only when the database’s base time interval is set to tick in File → Database Settings. Positive values can also be reinterpreted as tick, volume or range bars by a prior call to TimeFrameMode(); that belongs with intraday work and is covered where intraday databases are, in Part 19.

What changes inside the block, and what does not

Section titled “What changes inside the block, and what does not”

This is where most multi-timeframe bugs are born, so it is worth being pedantic.

TimeFrameSet() replaces exactly seven built-in arrays: Open, High, Low, Close, Volume, OpenInt and Avg. That is the documented list, and it is the complete list. Nothing else in your formula is touched by the call itself.

But because those seven arrays are the input to almost everything, the consequence is sweeping: every function you call after the switch operates on weekly bars, so every result you compute after the switch is a weekly result. MA( Close, 10 ) inside the block is a ten-week average of weekly closes. It is not a two-week-long average of daily closes, and it is not the same series as MA( Close, 50 ) on daily bars. They answer different questions and they turn on different days.

Variables you created before the call keep the frame they were made in. The reference page states this directly, and it is the mechanism that lets one formula mix any number of intervals: compute your daily things first, switch, compute your weekly things, restore.

Fragment — not a complete formula

DailyAverage = MA( Close, 50 ); // daily: made before the switch, stays daily
TimeFrameSet( inWeekly );
WeeklyAverage = MA( Close, 10 ); // weekly AND time-compressed
TimeFrameRestore();

The User’s Guide is unusually explicit about the implementation, and understanding it removes most of the mystery from the rest of this part.

Timeframe functions do not change BarCount. The array you get back has exactly as many elements as the base-interval array had. AmiBroker squeezes the compressed values into it by filling the first N elements with Null and packing the real values at the end.

Ten daily bars, compressed to two weekly bars

Illustrative numbers. The compressed row holds two real values in a ten-element array, and they are not on the bars they belong to until the array is expanded.
BarMonTueWedThuFriMonTueWedThuFri
Close (daily)10.010.410.210.610.510.811.211.011.411.3
Close inside the blockcompressed: packed at the endNullNullNullNullNullNullNullNull10.511.3
TimeFrameExpand(…, inWeekly)back on the right barsNullNullNullNull10.510.510.510.510.511.3
Illustrative numbers. The compressed row holds two real values in a ten-element array, and they are not on the bars they belong to until the array is expanded.

Look at the middle row. The two weekly closes are sitting on the last two daily bars, which are Thursday and Friday of week 2. If you plotted that row, or put it in an Exploration column, or compared it with Close, you would be reading week 1’s close against Thursday of week 2. Nothing would warn you. This is the whole reason the next lesson exists, and why the guide says flatly that expanding is required for any formula that uses the TimeFrame functions.

The documented syntax is TimeFrameRestore( tradeprices = False ), and the reference page says plainly that the tradeprices argument should be set to false. Leave it alone.

The critical sentence is what it restores: only OHLC, Volume, OpenInt and Avg. Every other variable created while the block was open stays compressed. Your weekly average, your weekly Boolean condition, your weekly high — none of them come back on their own. They remain compressed arrays with the layout in the diagram above, and they must be expanded before use.

The documented restrictions inside a block

Section titled “The documented restrictions inside a block”

Five rules, all of them from the official pages rather than from folklore.

One block at a time. “Before calling TimeFrameSet again in the same formula with different interval you have to restore original timeframe first using TimeFrameRestore.” There is no stack and no nesting. Treat set and restore as a strictly flat pair.

Fragment — not a complete formula

// Wrong: a second switch without restoring the first
TimeFrameSet( inWeekly );
TimeFrameSet( inMonthly );
// Right
TimeFrameSet( inWeekly );
WeeklyValue = MA( Close, 10 );
TimeFrameRestore();
TimeFrameSet( inMonthly );
MonthlyValue = MA( Close, 6 );
TimeFrameRestore();

Compression goes up only. From one-minute data you can build 2, 3, 5 or N-minute bars. From 15-minute data you cannot get one-minute bars, and from an end-of-day database you cannot reach any intraday interval at all. The data to build the shorter bars is not there, and no function will invent it.

Loops need help; array functions do not. AmiBroker’s built-in array functions check for Nulls at the beginning of a series and skip them. A for or while loop does not: starting at bar 0 inside a compressed block reads Null and poisons everything downstream. The documented pattern uses NullCount(), which returns the number of consecutive Nulls at the start of an array.

Fragment — not a complete formula

TimeFrameSet( inWeekly );
Start = NullCount( Close ); // where the real weekly data begins
for( i = Start + 1; i < BarCount; i++ )
{
// seed element Start with a real value before the loop, then work forward
}
TimeFrameRestore();

Note that BarCount is unchanged inside the block, so only the start index moves.

Everything leaving the block must be expanded. Skipping it raises Warning 509, whose official wording is that TimeFrameExpand() is absolutely required for any formula using the TimeFrame functions, and that without it your timestamps will be wrong. The Common Coding Mistakes chapter gives the canonical pair:

Fragment — not a complete formula

TimeFrameSet( inWeekly );
MA14_Weekly = MA( Close, 14 );
TimeFrameRestore();
Buy = Cross( Close, MA14_Weekly ); // WRONG
Buy = Cross( Close, TimeFrameExpand( MA14_Weekly, inWeekly ) ); // CORRECT

Mind QuickAFL. The official QuickAFL article lists cases where its estimate of how many bars a formula needs may not produce identical results, and names “TimeFrame functions with much higher intervals than base interval” among them. A weekly indicator built from daily bars is exactly that case. The practical symptom is a formula that gives different numbers zoomed in than zoomed out, or different backtest results over a sub-range than over all quotations. The documented fix is SetBarsRequired() at the top of the formula — a specific figure such as SetBarsRequired( 1000, 0 ), or SetBarsRequired( sbrAll, sbrAll ) to switch QuickAFL off entirely.

TimeFrameGetPrice(): the one-call alternative

Section titled “TimeFrameGetPrice(): the one-call alternative”

If all you want is another interval’s open, high, low, close, volume or open interest, the whole set-compute-restore-expand sequence is unnecessary. TimeFrameGetPrice() does it in one call and the guide records it as about twice as fast as the equivalent nested calls.

Fragment — not a complete formula

PreviousWeekHigh = TimeFrameGetPrice( "H", inWeekly, -1 );
PreviousDayHigh = TimeFrameGetPrice( "H", inDaily, -1 ); // on intraday bars

The documented signature is TimeFrameGetPrice( pricefield, interval, shift = 0, mode = expandFirst ). Three things about it catch people out:

  • pricefield is a quoted string, one of "O", "H", "L", "C", "V" or "I". Passing the bare array High is not the same thing.
  • shift counts higher-timeframe bars, not base bars. On daily data with inWeekly, -3 means three weeks ago. It behaves like Ref() applied to the compressed series.
  • Its defaults point the wrong way for trading rules. Both shift = 0 and mode = expandFirst are the risky choices, and the reference page says so: with shift = 0, “compressed data may look into the future ( weekly high can be known on monday )”, and it instructs you to reference past data with a negative shift when writing a trading system.

That last point is the subject of the next lesson. For now, take the documented advice at face value: in a decision, use a negative shift.

Reading about a leading-Null prefix is not the same as watching one appear in a table against real dates. This Exploration puts the daily close, the raw compressed weekly close and the expanded weekly close in neighbouring columns, so the layout stops being an abstraction.

Produce direct evidence, on your own data, of three claims made above: that compression fills the front of the array with Nulls and packs values at the end; that expansion puts them back on the right bars; and that a variable created before the switch is untouched by it.

Complete runnable AFL

timeframe-anatomy.afl
// ===========================================================================
// Time frame anatomy
// Makes visible what TimeFrameSet() actually does to an array. The official
// User's Guide describes it plainly: time-frame functions do not change
// BarCount, they squeeze the array so the first N slots hold Null and the
// compressed values sit at the END of the array. Until you expand them back,
// those values are attached to the wrong bars.
//
// This Exploration puts the raw compressed array and the expanded array in
// neighbouring columns so you can see the difference rather than believe it.
//
// HOW TO RUN
// Analysis window -> Apply to: Current symbol (any liquid share with a few
// years of history). Range: All quotations.
// Analysis -> Settings -> Periodicity: Daily.
// Read the table from the FIRST row downwards. The interesting damage is at
// the top of the table, not at the bottom.
//
// WHAT TO LOOK FOR
// 1. "Weekly close (compressed)" is empty for roughly the first four fifths
// of the rows, then prints numbers that have nothing to do with the date
// on the same row. That is the unexpanded array.
// 2. "Weekly close (expanded)" carries a plausible weekly value on every
// row, and changes only on the last trading day of each week.
// 3. "Daily MA(20) built before the switch" is untouched by TimeFrameSet:
// variables assigned before the call stay in the frame they were made in.
//
// ASSUMPTIONS, STATED SO THEY CAN BE CHECKED
// - The database base time interval is daily or shorter. Compression only
// goes upwards; from end-of-day data no intraday frame is reachable.
// - QuickAFL is switched off inside the formula with SetBarsRequired(),
// because time-frame functions with an interval much higher than the base
// interval are one of the documented cases where the QuickAFL estimate
// can change the numbers you see.
// ===========================================================================
SetBarsRequired( sbrAll, sbrAll );
// ---------------------------------------------------------------------------
// Built BEFORE the switch. These stay in the daily frame.
// ---------------------------------------------------------------------------
DailyClose = Close;
DailyMA20 = MA( Close, 20 );
// ---------------------------------------------------------------------------
// Inside the block every built-in price array is weekly, so every result
// computed here is weekly AND time-compressed.
// ---------------------------------------------------------------------------
TimeFrameSet( inWeekly );
WeeklyCloseRaw = Close;
WeeklyMA10Raw = MA( Close, 10 );
TimeFrameRestore();
// TimeFrameRestore() puts back Open, High, Low, Close, Volume, OpenInt and Avg,
// and nothing else. The two variables above are still compressed, so they must
// be expanded before they can be displayed or compared with daily data.
// The interval argument names the frame the data CAME FROM, not the frame we
// are going to.
WeeklyClose = TimeFrameExpand( WeeklyCloseRaw, inWeekly );
WeeklyMA10 = TimeFrameExpand( WeeklyMA10Raw, inWeekly );
// Where the real values begin inside the compressed array.
LeadingNulls = NullCount( WeeklyCloseRaw );
// Day names, so the alignment can be read without counting rows.
DayName = WriteIf( DayOfWeek() == 1, "Mon",
WriteIf( DayOfWeek() == 2, "Tue",
WriteIf( DayOfWeek() == 3, "Wed",
WriteIf( DayOfWeek() == 4, "Thu",
WriteIf( DayOfWeek() == 5, "Fri", "other" ) ) ) ) );
Filter = 1;
SetOption( "NoDefaultColumns", True );
AddColumn( DateTime(), "Date", formatDateTimeISO, colorDefault, colorDefault, 110 );
AddTextColumn( DayName, "Day", 1.0, colorDefault, colorDefault, 50 );
AddColumn( Interval(), "Base interval (seconds)", 1.0 );
AddColumn( LeadingNulls, "Leading Nulls in compressed array", 1.0 );
AddColumn( DailyClose, "Daily close", 1.2 );
AddColumn( WeeklyCloseRaw, "Weekly close COMPRESSED (unusable)", 1.2 );
AddColumn( WeeklyClose, "Weekly close expanded", 1.2 );
AddColumn( Close, "Close after restore", 1.2 );
AddColumn( DailyMA20, "Daily MA(20) built before the switch", 1.2 );
AddColumn( WeeklyMA10, "Weekly MA(10) expanded", 1.2 );

Download timeframe-anatomy.afl86 lines

The formula has four movements. First it builds two daily series — the close itself and a 20-bar average — before any switch, which is what makes them stay daily. Second it opens the weekly block and computes two weekly series. Third it restores, which brings back only the seven built-in arrays, and expands the two weekly series explicitly. Fourth it prints everything side by side, including the raw compressed array that you would normally never display, plus NullCount() so you can see how many leading Nulls there are without counting rows yourself.

SetBarsRequired( sbrAll, sbrAll ) sits at the top for the reason given above: without it, the numbers can depend on the analysis range, and an example whose output changes with the range teaches the wrong lesson.

  • NullCount( array, mode = 1 ) — counts consecutive Nulls. Mode 1, the default, counts them at the beginning; 2 at the end; 3 from both ends; 0 counts every Null in the array including non-consecutive ones.
  • Interval( format = 0 ) — returns the interval the formula is currently running at, in seconds by default. It is here as a self-check: if that column does not read 86,400 you are not running daily and the rest of the table means something else. Compare it numerically, never against the text from Interval( 2 ), because those names are translated in localised builds.
  • DayOfWeek() — 0 for Sunday through 6 for Saturday. Used to label the rows so that weekly alignment can be read at a glance.

Change inWeekly to inMonthly in the TimeFrameSet() call and in both TimeFrameExpand() calls, then re-run. The leading-Null count should grow by roughly a factor of four, because there are about four times fewer monthly bars than weekly ones, and the expanded column should now change once a month. If you change only the TimeFrameSet() call and leave the expansions on inWeekly, the expanded column becomes nonsense with no error message — which is a useful failure to have seen once, deliberately, rather than for the first time in a live formula.

  • Weekly columns entirely empty. The Analysis Periodicity is already weekly or longer. You cannot compress weekly data to weekly bars in a way that leaves anything to show; check the Interval() column.
  • “Weekly close expanded” changes on a Monday rather than a Friday. Someone has passed an explicit expandFirst to TimeFrameExpand(), or the database’s first-day-of-week setting has been changed. The next lesson is about exactly this.
  • The numbers differ between two runs over different ranges. SetBarsRequired() has been removed or QuickAFL is intervening. Put it back before drawing any conclusion.

Add a column that shows TimeFrameGetPrice( "C", inWeekly, -1 ) next to the expanded weekly close. That is the previous completed week’s close, and comparing the two columns row by row is the fastest way to build an intuition for how far apart “the most recent completed weekly value” and “the value from one week before that” really are.

You can now read any multi-timeframe formula and answer three questions about it: which arrays it switched, which of its variables are still compressed, and whether it obeyed the documented restrictions on nesting, direction and loops. You also know that the compressed form is not a display format but a genuinely different arrangement of the array, which is why the next step is not optional.

What remains is the argument the whole part is built around: once you have a compressed weekly value, which base bar should it appear on? Get that wrong and everything above still runs, still produces plausible numbers, and quietly reads the future.

Check your understanding

Question 1. Which arrays does TimeFrameSet() replace?
Show the answer and why

Answer: Open, High, Low, Close, Volume, OpenInt and Avg only

The reference page lists exactly those seven built-in arrays. Everything else is affected only indirectly, because functions called after the switch read those arrays.

Question 2. After TimeFrameRestore(), what is the state of a variable you computed inside the block?
TimeFrameSet( inWeekly );
w = MA( Close, 10 );
TimeFrameRestore();
Show the answer and why

Answer: Still time-compressed, and unusable against daily data until expanded

Restore returns only the seven built-in price arrays. Everything else keeps the compressed layout, with Nulls at the front and values packed at the end.

Question 3. You are working on 15-minute bars and want 1-minute bars. What does the documentation say?
Show the answer and why

Answer: False

False. Compression goes only from a shorter interval to a longer one. From 15-minute data you cannot obtain 1-minute bars, and from end-of-day data you cannot reach any intraday interval.

Question 4. Which statements about a for-loop inside a TimeFrameSet block are correct? Select all that apply.
Show the answer and why

Answer: Starting the loop at bar 0 can read Null values and corrupt the result, NullCount() gives the index where the compressed data actually begins, Built-in array functions already skip the leading Nulls

BarCount is unchanged — only the start index moves. Array functions tolerate the leading Nulls; loops do not, which is what NullCount() is for.

Question 5. Which call reads the high of the week before last on daily data?
Show the answer and why

Answer: TimeFrameGetPrice( "H", inWeekly, -2 )

The price field is a quoted string, and shift counts higher-timeframe bars, so -2 means two weekly bars back rather than two days.

Sources for this lesson

8 verified · checked 2026-08-31

  1. 01AFL Function Reference — TimeFrameSetamibroker.com/guide/afl/timeframeset.html2026-08-31
  2. 02AFL Function Reference — TimeFrameRestoreamibroker.com/guide/afl/timeframerestore.html2026-08-31
  3. 03AFL Function Reference — TimeFrameGetPriceamibroker.com/guide/afl/timeframegetprice.html2026-08-31
  4. 04AFL Function Reference — NullCountamibroker.com/guide/afl/nullcount.html2026-08-31
  5. 05AFL Function Reference — SetBarsRequiredamibroker.com/guide/afl/setbarsrequired.html2026-08-31
  6. 06AmiBroker User's Guide — Multiple Time Frame Support in AFL§ How does it work internallyamibroker.com/guide/h_timeframe.html2026-08-31
  7. 07AmiBroker User's Guide — Common coding mistakes§ TimeFrameExpand() is required to match data with original time frameamibroker.com/guide/a_mistakes.html2026-08-31
  8. 08AmiBroker Knowledge Base — QuickAFL factsamibroker.com/kb/2008/07/03/quickafl2026-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.