QuickAFL facts

QuickAFL(tm) is a feature that allows faster AFL calculation under certain conditions. Initially (since 2003) it was available for indicators only, as of version 5.14+ it is available in Automatic Analysis too.

Initially the idea was to allow faster chart redraws through calculating AFL formula only for that part which is visible on the chart. In a similar manner, automatic analysis window can use subset of available quotations to calculate AFL, if selected “range” parameter is less than “All quotations”.

So, in short QuickAFL works so it calculates only part of the array that is currently visible (indicator) or within selected range (Automatic Analysis).

Your formulas, under QuickAFL, may or may NOT use all data bars available, but only visible (or “in-range”) bars (plus some extra to ensure calculation of used indicators), so when you are using Close[ 0 ] it represents not first bar in entire data set but first bar in array currently used (which is just a bit longer than visible, or ‘in-range’ area).

The QuickAFL is designed to be transparent, i.e. do not require any changes to the formulas you are using. To achieve this goal, AmiBroker in the first execution of given formula “learns” how many bars are really needed to calculate it correctly.

To find out the number of bars required to calculate formula AmiBroker internally uses two variables ‘backward ref’ and ‘forward ref’.

‘backward ref’ describes how many PREVIOUS bars are needed to calculate the value for today, and ‘forward ref’ tells how many FUTURE bars are needed to calculate value for today.

If these numbers are known, during execution of given formula AmiBroker takes FIRST visible (or in-range) bar and subtracts ‘backward ref” and takes LAST visible (or in-range) bar and adds ‘forward ref’ to calculate first and last bar needed for calculation of the formula.

Now, how does AmiBroker know a correct “backward ref” and “forward ref” for the entire formula?
Well, every AmiBroker’s built-in function is written so that it knows its own requirements and adds them to global “backward ref” and “forward ref” each time given function is called from your formula.

The whole process starts with setting initial BackwardRef to 30 and ForwardRef to zero. These initial values are used to give “safety margin” for simple loops/scripts.

Next, when parser scans the formula like this:

Buy Ref MAC40 ), -);

it analyses it and “sees” the MA with parameter 40. It knows that simple moving average of period 40 requires 40 past bars and zero future bars to calculate correctly so it does the following (all internally):

BackwardRef BackwardRef 40;
ForwardRef ForwardRef 0;

So now, the value of BackwardRef will be 70 (40+30(initial)), and ForwardRef will be zero.

Next the parser sees Ref( .., -1 );

It knows that Ref with shift of -1 requires 1 past bar and zero future bars so it “accumulates” requirements this way:

BackwardRef BackwardRef 1;
ForwardRef ForwardRef 0;

So it ends up with:

BackwardRef 71;
ForwardRef 0;

The BackwardRef and ForwardRef numbers are displayed by AFL Editor’s Tools->Check and Profile as well as on charts when “Display chart timing” is selected in the preferences.

If you use Check and Profile tool, it will tell you that the formula

Buy Ref MAC40 ), -);

requires 71 past bars and 0 future bars.

You can modify it to

Buy Ref MAC50 ), -);

and it will tell you that it requires 82 past bars (30+50+2) and zero future bars.

If you modify it to

Buy Ref MAC50 ), );

It will tell you that it needs 80 past bars (30+50) and ONE future bar (from Ref).

Thanks to that your formula will use 80 bars prior to first visible (or in-range) bar leading to correct calculation result, while improving the speed of execution by not using bars preceding required ones.

IMPORTANT NOTES

It is very important to understand, that the above estimate requirements while fairly conservative,
and working fine in majority of cases, may NOT give you identical results with QuickAFL enabled, if your formulas use:
a) JScript/VBScript scripting
b) for/while/do loops using more than 30 past bars
c) any functions from external indicator DLLs
d) certain functions that use recursive calculation such as very long exponential averages or TimeFrame functions with much higher intervals than base interval

In these cases, you may need to use SetBarsRequired() function to set initial requirements to value higher than default 30. For example, by placing

SetBarsRequired1000);

at the TOP of your formula you are telling AmiBroker to add 1000 bars PRIOR to first visible (or in-range) bar to ensure more data to stabilise indicators.

You can also effectively turn OFF QuickAFL by adding:

SetBarsRequiredsbrAllsbrAll );

at the top of your formula. It tells AmiBroker to use ALL bars all the time.

It is also worth noting that certain functions like cumulative sum (Cum()) by default request ALL past bars to guarantee the same results when QuickAFL is enabled. But when using such a function, you may or may NOT want to use all bars. So SetBarsRequired() gives you also ability to DECREASE the requirements of formula. This is done by placing SetBarsRequired at the END of your formula, as any call to SetBarsRequired effectively overwrites previously calculated estimate. So
if you write

Cum);
SetBarsRequired1000); // use 1000 past bars DESPITE using Cum()

You may force AmiBroker to use only 1000 bars prior first visible even though Cum() by itself would require all bars.

It is also worth noting that when QuickAFL is used, BarIndex() function does NOT represent elements of the AFL array, but rather the indexes of ENTIRE quotation array. With QuickAFL turned on, an AFL array is usually shorter than quotation array, as illustrated in this picture:

QuickAFL, BarIndex and BarCount

SPECIAL CASE: AddToComposite function

Since AddToComposite creates artificial stock data it is desirable that it works the same regardless of how many ‘visible’ bars there are or how many bars are needed by other parts of the formula.

For this reason internally AddToComposite does this:

SetBarsRequiredsbrAllsbrAll );

which effectivelly means “use all available bars” for the formula. AddToComposite function simply tells the AFL engine to use all available bars (from the very first to the very last) regardless of how formula looks like. This is to ensure that AddToComposite updates ALL bars of the composite

The side-effect is that “Check And Profile” feature will see that it needs to reference future bars and display a warning even though this is false alert because AddToComposite itself has no impact on trading system at all.

Now why this shows only when flag atcFlagEnableInBacktest is on ??
It is simple: this is so because it means that AddToComposite is ACTIVE in BACKTEST.
http://www.amibroker.com/guide/afl/afl_view.php?name=ADDTOCOMPOSITE

Since “Check And Profile” uses “BACKTEST” state you get such result.

If atcFlagEnableInBacktest is not specified AddToComposite is not enabled in Backtest and hence does not affect calculation of BackwardRef and ForwardRef during “Check And Profile”.

BACKWARD COMPATIBILITY NOTES

a) QuickAFL is available in Automatic Analysis in version 5.14.0 or higher
b) sbrAll constant is available in Automatic Analysis in version 5.14.0 or higher. If you are using older versions you should use numeric constant of: 1000000 instead.

Historical portfolio backtest metrics

Recently on the AmiBroker mailing list some users expressed wish to have access to some of portfolio backtest metrics available in “historical” form (i.e. as date series, as opposed to scalars), so they can be plotted as an indicator.

Implementing such functionality is actually easy with existing tools and does not require any OLE scripts. Everything you need is small custom-backtester procedure that just reads built-in stats every bar and puts them into composite ticker.
In the accompanying indicator code all you need to do is simply use Foreign() function to access the historical metrics data generated during backtest.

The code below shows the BACKTEST formula with custom backtester part:

// Replace lines below with YOUR TRADING SYSTEM
EnableRotationalTrading(); 
PositionScore 1/RSI(14); 
PositionSize = -25;
SetOption("WorstRankHeld");
SetOption("MaxOpenPositions"); 

////////////////////////////////////////
// BELOW IS ACTUAL CUSTOM BACKTESTER PART
// that can read any built-in metric (in this example UlcerIndex)
// and store it into composite ticker for further
// retrieval as data series

SetOption("UseCustomBacktestProc"True ); 

if( Status("action") == actionPortfolio )
{
  
bo GetBacktesterObject();
 
  
bo.PreProcess(); // Initialize backtester
 
  // initialize with null 
  // you can have as many historical metrics as you want 
  // (just duplicate line below for many metrics you want)
  
MyHistStat1 Null
  
MyHistStat2 Null// add your own 

  for(bar=0bar BarCountbar++)
  {
   
bo.ProcessTradeSignalsbar );
  
   
// recalculate built-in stats on EACH BAR
   
stats bo.GetPerformanceStats); 
 
   
// the line below reads the metric and stores it as array element
   // you can add many lines for each metric of your choice
   
MyHistStat1bar ] = stats.GetValue("UlcerIndex"); // get ulcer index value calculated this bar
   
MyHistStat2bar ] = stats.GetValue("WinnersPercent"); // add your own

  }

  bo.PostProcess(); // Finalize backtester

  // now STORE the historical data series representing the metric of your choice
  // duplicate the line below for as many metrics as you want
  
AddToCompositeMyHistStat1"~~~UI_HISTORICAL""X"atcFlagEnableInPortfolio atcFlagDefaults );

  // you can add your own as shown below
  
AddToCompositeMyHistStat2"~~~WP_HISTORICAL""X"atcFlagEnableInPortfolio atcFlagDefaults ); 
}

In the code above, for illustration purposes, we are exporting UlcerIndex and Winners Percent metrics as data series. They are stored in composite tickers for easy retrieval from indicator level.
You can easily extend code to include ANY number of metrics you want.

Now in order to Plot metrics as indicators, use this simple formula:

PlotForeign("~~~UI_HISTORICAL""UlcerIndex Historical"colorRedstyleLine );
PlotForeign("~~~WP_HISTORICAL""Winners Percent"colorBluestyleLine styleOwnScale );

As you can see with one Foreign function call you can read the historical value of any metric generated by the backtester.

NOTE: when running backtest please setup a filter in AA that EXCLUDES composites (group 253) from backtest set.

Using redundant signals for entries

NOTE: THIS ARTICLE IS NOW OUTDATED AS AMIBROKER SUPPORTS NEW BACKTEST MODE THAT HANDLES THIS NATIVELY http://www.amibroker.com/f?setbacktestmode

The sample code below shows how to use custom portfolio backtester procedure to change the way backtester works. Normally buy is matched against sell and redundant buy signals between initial buy and matching sell are removed as shown in the picture there:
http://www.amibroker.com/gifs/bt_regular.gif

The procedure below changes this behaviour and allows to use redundant signals (they are not removed).

(more…)

Adding custom metric: Average adverse excursion

Here is a sample that shows how to create custom metric based on per-trade statisitics.
In this example we will calculate the average value of MAE (maximum adverse excursion) from all trades.
(more…)

How to create copy of portfolio equity?

As you know Portfolio backtester creates special ticker “~~~EQUITY” which holds portfolio-level equity of the system under test. Some may find it useful to save this equity into another symbol after backtest for future analysis and/or comparison. Good news is that it is possible to do that automatically using custom backtester procedure and AddToComposite function. The formula below shows how.
(more…)

Re-balancing open positions

Here is an example that shows how to code rotational trading system with rebalancing. The system buys and shorts top 20 securities according to absolute value of positionscore (user definable - in this example we used 20 day rate-of-change) - each at 5% of equity then each day it rebalances existing positions to 5% if only the difference between current position value and “ideal” value is greater than 0.5% and bigger than one share.
(more…)