Multiple Time Frame support in AFL

IMPORTANT: TimeFrame functions are NOT intended to replace Periodicity setting. To switch periodicity/interval you should only use Periodicity setting. TimeFrame functions are ONLY for formulas that MIX many different intervals at once in single formula.

Release 4.41 brings ability to use multiple time frames (bar intervals) in single formula. The time frame functions can be divided into 3 functional groups:

  1. switching time frame of build-in O, H, L, C, V, OI, Avg arrays: TimeFrameSet, TimeFrameRestore
  2. compressing/expanding single arrays to/from specified interval: TimeFrameCompress, TimeFrameExpand
  3. immediate access to price/volume arrays in different time frame: TimeFrameGetPrice

First group is used when your formula needs to perform some calculations on indicators in different time frame than currently selected one. For example if you need to calculate 13-bar moving average on 5 minute data and 9 bar exponential avarage from hourly data while current interval is 1 minute you would write:

TimeFrameSet( in5Minute ); // switch to 5 minute frame

/* MA now operates on 5 minute data, ma5_13 holds time-compressed 13 bar MA of 5min bars */

ma5_13 =
MA( C, 13 );

TimeFrameRestore(); // restore time frame to original

TimeFrameSet( inHourly ); // switch now to hourly

mah_9 =
EMA( C, 9 ); // 9 bar moving average from hourly data

TimeFrameRestore(); // restore time frame to original

Plot( Close, "Price", colorWhite, styleCandle );

// plot expanded average

Plot( TimeFrameExpand( ma5_13, in5Minute), "13 bar moving average from 5 min bars", colorRed );
Plot( TimeFrameExpand( mah_9, inHourly), "9 bar moving average from hourly bars", colorRed );

TimeFrameSet( interval ) - replaces current built-in price/volume arrays: open, high, low, close, volume, openint, avg with time-compressed bars of specified interval once you switched to a different time frame all calculations and built-in indicators operate on selected time frame. To get back to original interval call TimeFrameRestore() funciton. If you want to call TimeFrameSet again with different interval you have to restore original time frame first using TimeFrameRestore(). Interval is time frame interval in seconds. For example: 60 is one minute bar. You should use convenient constants for common intervals: in1Minute, in5Minute, in15Minute, inHourly, inDaily, inWeekly, inMonthly.

With version 4.70 you can also specify N-tick intervals. This is done by passing NEGATIVE value as interval. For example -5 will give 5-tick bar compression, and -133 will give 133-tick compression. Please note that using N-tick intervals works only if your database uses Tick base time interval set in File -> Database Settings dialog.

TimeFrameSet( -133 ); // switch to 133-tick interval

IMPORTANT: TimeFrameSet() is NOT an equivalent of the periodicity setting in Analysis Settings. The only use for time-frame functions is when you want to have the trading rules based on MULTIPLE time frames at once. See details in "How it works internally" below.

 

TimeFrameRestore() - restores price arrays replaced by SetTimeFrame.Note that only OHLC, V, OI and Avg built-in variables are restored to original time frame when you call TimeFrameRestore(). All other variables created when being in different time frame remain compressed. To de-compress them to original interval you have to use TimeFrameExpand.

Once you switch the time frame using TimeFrameSet, all AFL functions operate on this time frame until you switch back the time frame to original interval using TimeFrameRestore or set to different interval again using TimeFrameSet. It is good idea to ALWAYS call TimeFrameRestore when you are done with processing in other time frames.

When time frame is switched to other than original interval the results of all functions called since TimeFrameSet are time-compressed too. If you want to display them in original time frame you would need to 'expand' them as described later. Variables created and assigned before call to TimeFrameSet() remain in the time frame they were created. This behaviour allows mixing unlimited different time frames in single formula.

PLEASE NOTE that you can only compress data from shorter interval to longer interval. So when working with 1-minute data you can compress to 2, 3, 4, 5, 6, ....N-minute data. But when working with 15 minute data you can not get 1-minute data bars. In a similar way if you have only EOD data you can not access intraday time frames.

Second group: TimeFrameCompress/TimeFrameExpand allow to compress and expand single arrays to / from different time frames. Especially worth mentioning is TimeFrameExpand that is used to decompress array variables that were created in different time frame. Decompressing is required to properly display the array created in different time frame. For example if you want to display weekly moving average it must be 'expanded' so the data of one weekly bar covers five daily bars (Monday-Friday) of corresponding week.

TimeFrameExpand( array, interval, mode = expandLast ) - expands time-compressed array from 'interval' time frame to base time frame ('interval' must match the value used in TimeFrameCompress or TimeFrameSet)
Available modes:
expandLast - the compressed value is expanded starting from last bar within given period (so for example weekly close/high/low is available on Friday's bar)
expandFirst - the compressed value is expanded starting from first bar within given period (so for example weekly open is available from Monday's bar)
expandPoint - the resulting array gets not empty values only for the last bar within given period (all remaining bars are Null (empty)).

Caveat: expandFirst used on price different than open may look into the future. For example if you create weekly HIGH series, expanding it to daily interval using expandFirst will enable you to know on MONDAY what was the high for entire week.

IMPORTANT: TimeFrameExpand IS REQUIRED for any formula that uses TimeFrame* functions. If you don't expand time compressed data you will have incorrect timestamps (see description below in "How it works").

TimeFrameCompress is provided for completeness and it can be used when you want to compress single array without affecting built-in OHLC,V arrays. If you call TimeFrameCompress it does not affect results of other functions.

wc = TimeFrameCompress( Close, inWeekly );

/* now the time frame is still unchanged (say daily) and our MA will operate on daily data */

dailyma =
MA( C, 14 );

/* but if we call MA on compressed array, it will give MA from other time frame */

weeklyma =
MA( wc, 14 ); // note that argument is time-compressed array

Plot( dailyma, "DailyMA", colorRed );

weeklyma =
TimeFrameExpand( weeklyma, inWeekly ); // expand for display

Plot( weeklyma, "WeeklyMA", colorBlue );

During this formula the time frame remained at original setting we only compressed single array.

TimeFrameCompress( array, interval, mode = compressLast )
- compresses single array to given interval using given compression mode available modes:
compressLast - last (close) value of the array within interval
compressOpen - open value of the array within interval
compressHigh - highest value of the array within interval
compressLow - lowest value of the array within interval
compressVolume - sum of values of the array within interval

Graph0 = TimeFrameExpand( TimeFrameCompress( Close, inWeekly, compressLast ), inWeekly, expandLast );
Graph1 = TimeFrameExpand( TimeFrameCompress( Open, inWeekly, compressOpen ), inWeekly, expandFirst );

Third group consist of just one useful function: TimeFrameGetPrice which allows to reference price and volume from other time frames without switching /compressing/expanding time frames. Just one function call to retrieve price from higher time frame. It allows also to reference not only current but past bars from different time frames.

TimeFrameGetPrice( pricefield, interval, shift = 0, mode = expandFirst );
- references OHLCV fields from other time frames. This works immediatelly without need to call TimeFrameSet at all.
Price field is one of the following: "O", "H", "L", "C", "V", "I" (open interest). Interval is bar interval in seconds. shift allows to reference past (negative values) and future (positive values) data in higher time frame. For example -1 gives previous bar's data (like in Ref function but this works in higher time frame).

Examples:

TimeFrameGetPrice( "O", inWeekly, -1 ) // gives you previous week Open price
TimeFrameGetPrice( "C", inWeekly, -3 ) // gives you weekly Close price 3 weeks ago
TimeFrameGetPrice( "H", inWeekly, -2 ) // gives you weekly High price 2 weeks ago
TimeFrameGetPrice( "O", inWeekly, 0 ) // gives you this week Open price.
TimeFrameGetPrice( "H", inDaily, -1 ) // gives previous Day High when working on intraday data

Shift works as in Ref() function but it is applied to compressed time frame.

Note these functions work like these 3 nested functions
TimeFrameExpand( Ref( TimeFrameCompress( array, interval, compress(depending on field used) ), shift ), interval, expandFirst )
therefore if shift = 0 compressed data may look into the future ( weekly high can be known on monday ). If you want to write a trading system using this function please make sure to reference PAST data by using negative shift value.

The only difference is that TimeFrameGetPrice is 2x faster than nested Expand/Compress.

Note on performance of TimeFrame functions:

a) Measurements done on Athlon 1.46GHz, 18500 daily bars compressed to weekly time frame

TimeFrameGetPrice( "C", inWeekly, 0 ) - 0.0098 sec (9.8 milliseconds)
TimeFrameSet( inWeekly ) - 0.012 sec (12 milliseconds)
TimeFrameRestore( ) - 0.006 sec (6 milliseconds)
TimeFrameCompress( Close, inWeekly, compressLast ); - 0.0097 sec (9.7 milliseconds)
TimeFrameExpand( array, inWeekly, expandLast ); - 0.0098 sec (9.8 milliseconds)

b) Measurements done on Athlon 1.46GHz, 1000 daily bars compressed to weekly time frameall functions below 0.0007 sec (0.7 millisecond)

How does it work internally ?

Time-frame functions do not change the BarCount - they just squeeze the arrays so you have first N-bars filled with NULL values and then - last part of the array contains the actual time-compressed values.

This is why it is essential to expand the data back to the original frame with TimeFrameExpand.

The following simple exploration shows what happens after you switch to a higher timeframe. Run Exploration on current symbol, all quotations, periodicity set to daily and you will see how "weekly close compressed" column contains empty values at the beginning and weekly compressed data at the end of array.

 

Filter = 1;
AddColumn(Close, "Daily close");

TimeFrameSet(inWeekly);
AddColumn(wc = Close, "weekly close compressed");
TimeFrameRestore();

AddColumn( TimeFrameExpand(wc, inWeekly), "weekly close expanded");

EXAMPLES

EXAMPLE 1: Plotting weekly MACD and cross arrows from daily data

TimeFrameSet( inWeekly );
m =
MACD(12, 26 ); // MACD from WEEKLY data
TimeFrameRestore();

m1 =
TimeFrameExpand( m, inWeekly );

Plot( m1, "Weekly MACD", colorRed );
PlotShapes( Cross( m1, 0 ) * shapeUpArrow, colorGreen );
PlotShapes( Cross( 0, m1 ) * shapeDownArrow, colorGreen );

EXAMPLE 2: weekly candlestick chart overlaid on line daily price chart

wo = TimeFrameGetPrice( "O", inWeekly, 0, expandPoint );
wh =
TimeFrameGetPrice( "H", inWeekly, 0, expandPoint );
wl =
TimeFrameGetPrice( "L", inWeekly, 0, expandPoint );
wc =
TimeFrameGetPrice( "C", inWeekly, 0, expandPoint );

PlotOHLC( wo, wh, wl, wc, "Weekly Close", colorWhite, styleCandle );
Plot( Close, "Daily Close", colorBlue );

EXAMPLE 3: Simplified Triple screen system

/* switch to weekly time frame */
TimeFrameSet( inWeekly );
whist =
MACD( 12, 26 ) - Signal( 12, 26, 9 );
wtrend =
ROC( whist, 1 ); // weekly trend - one week change of weekly macd histogram
TimeFrameRestore();

/* expand calculated MACD to daily so we can use it with daily signals */
wtrend =
TimeFrameExpand( wtrend, inWeekly );


/* elder ray */
bullpower=
High - EMA(Close,13);
bearpower=
Low - EMA(Close,13);

Buy = wtrend > 0 /* 1st screen: positive weekly trend */
AND
bearpower <
0 AND bearpower > Ref( bearpower, -1 ) /* 2nd screen bear power negative but rising */
AND
H > Ref( H, -1 ); /* 3rd screen, if prices make a new high */

BuyPrice = Ref( H, -1 ); // buy stop level;

Sell = 0 ; // exit only by stops
ApplyStop( stopTypeProfit, stopModePercent, 30, True );
ApplyStop( stopTypeTrailing, stopModePercent, 20, True );