HomeKnowledge Base

Separate ranks for categories that can be used in backtesting

When we want to develop a trading system, which enters only N top-scored symbols from each of the sectors, industries or other sub-groups of symbols ranked separately, we should build appropriate ranks for each of such categories. This can be done with ranking functionalities provided by StaticVarGenerateRanks function.

The formula presented below iterates though the list of symbols included in the test, then calculates the scores used for ranking and writes them into static variables. The static variables names are based on category number (sectors in this example) and that allows to create separate ranks for each sector.

// watchlist should contain all symbols included in the test
wlnum GetOption"FilterIncludeWatchlist" );
List = 
CategoryGetSymbolscategoryWatchlistwlnum ) ;

Status"stocknum" ) == )
// cleanup variables created in previous runs (if any)
StaticVarRemove"rank*" );
StaticVarRemove"values*" );
categoryList ",";

0; ( Symbol StrExtract( List, ) )  != "";  n++ )
SetForeignsymbol );

// use sectors for ranking
category sectorID();

// add sector to the list
if( ! StrFindcategoryList"," category "," ) ) categoryList += NumToStrcategory1) + ",";

// write our ranking criteria to a variable
        // in this example we will use 10-bar rate-of-change
values RocClose10 );

// write ranked values to a static variable
StaticVarSet"values" category "_" symbolvalues );


// generate separate ranks for each category from the list
for( 1; ( category StrExtractcategoryList) ) != ""i++ )
StaticVarGenerateRanks"rank""values" category "_"01224 );

category sectorID();
symbol Name();

values StaticVarGet"values" category "_" symbol );
rank StaticVarGet"rank" "values" category "_" symbol );

// exploration code for verification
AddColumnvalues"values" );
AddColumnrank"rank" );
AddTextColumnSectorID), "Sector" );
AddColumnSectorID(), "Sector No");
Filter rank <= 2;

Status"Action" ) == actionExplore SetSortColumns25);

// sample backtesting rules
SetBacktestModebacktestRotational );
score IIfrank <= 2values);
// switch symbols at the beginning of the month only
PositionScore IIf!= Refm, -), scorescoreNoRotate );
SetPositionSize1spsPercentOfEquity );

Our test should be applied to a watchlist, which contains all symbols we want to include in our ranking code:

Watch list selection

Running the exploration will show two top-ranked symbols for each of the sectors:


We can also change Filter variable definition to

Filter 1;

and show all ranked symbols instead.

Such ranking information can be used in backtest and sample rules included at the end of the code use rank information to allow only two top-scored symbols to be traded.

Ruin stop or mysterious Short(6) in the trade list

When you back-test a trading system, you may sometimes encounter trades marked with (6) exit reason, showing e.g.: Short (6) or Short (ruin) in the trade list as in the picture below:

Ruin stop in trade list

As explained in the this Knowledge Base article: such identifier tells us that the trade was closed because of the ruin stop activation.

A ruin-stop is a built-in, fixed percentage stop set at -99.96%, so it gets activated if your position is losing almost all (99.96%) of its entry value. It almost never occurs in long trades, but it may be quite common if your trading system places short trades without any kind of maximum loss stop. Imagine that you short a stock when its price is $10, then it’s price rises to $20 (twice the entry price). When you buy to cover the position you must pay $20 per share, which means that your loss on this trade is $10 per share ($20-$10). This means 100% loss (as per entry value). If you placed such a trade with all your capital you would be bankrupt. That is why this stop is called “ruin stop”. Unfortunately, by the nature of short selling, the gains are limited to 100% (when stock price goes down to zero) but losses are virtually unlimited.

So what to do to prevent exits by ruin stop?

The best idea is to just place proper max. loss stop at much smaller percentage (such as 10% or 20%) depending on what your risk tolerance is, to limit drawdowns and decrease the chance of wiping your account down to zero.

If, for some weird reason, you want to turn OFF this built-in stop, you can do so using this code:

SetOption"DisableRuinStop"True );

but it is highly discouraged, because when you wipe your account down to zero (or even below zero) it makes no point to run back-test any further. Instead of disabling this feature you should place proper, tighter maximum loss stop.

How to show price ratio between two symbols

Charting ratios between the prices of two symbols can easily be done with AmiBroker. For this purpose we can use Spread built-in formula available in Charts window:

Inserting Spread formula

Then select the style as Ratio:

Parameter window

The primary symbol (Symbol1) is the one selected in the chart window, the other symbol (Symbol2) is defined in the Parameters window, as presented in the above screenshot.

It is also possible to create such ratio chart programmatically in AFL using the following code:

ratio Foreign("Symbol1""C") / Foreign("Symbol2""C");
Plotratio"ratio"colorRed );

Symbol1 and symbol2 names in the above code need to be replaced with the actual symbol names from our database we want to use.

How to read highest high value of future bars

Built in HHV and LLV functions allow to read highest high or lowest low of n-past bars. If we want to refer to future values, there is an easy way to do it using simple Ref function and just shift HHV or LLV reading from N-bars ahead. A ready to use function showing such approach is presented below:

// function definitions
function futureHHV( array, periods )
RefHHV( array, periods ), periods );
futureLLV( array, periods )
RefLLV( array, periods ), periods );

// sample use
PlotClose"Close"colorDefaultstyleBar );
PlotHHVH20 ), "HHV"colorGreenstyleDashed );
PlotfutureHHVH20 ), "Future HHV"colorGreenstyleThick );
PlotLLVL20 ), "LLV"colorRedstyleDashed );
PlotfutureLLVL20 ), "Future LLV"colorRedstyleThick );

And here is the chart produced by the formula above:

Future HHV

How to count symbols in given category

When we want to find out how many symbols belong to given category (such as watchlist) then for manual inspection, it is enough to hover the mouse cursor over the particular category name in the Symbols window and the information will be shown in a tooltip:

Category symbol count

If we want to check such information using AFL code, we could read the list of symbols returned with CategoryGetSymbols and by counting commas (which separate symbol names) find out the number of tickers.

A reusable function is presented below:

function CategoryCountSymbolscategorynumber )
count StrCount( list = CategoryGetSymbolscategorynumber ), ",");  
IIf( list == ""0count );

Title "Symbols in watchlist 0: " CategoryCountSymbolscategoryWatchlist);

How to fill background between hand-drawn trend lines

Among built-in drawing tools, the Triangle, Rectangle and Ellipse allow to fill the background with custom color. However, if we wanted to fill the space between manually drawn trend lines, then we could use AFL formula with Study function that allows to detect the position of the line. Then – knowing the arrays of top and bottom lines we could fill the area between with a cloud plot.

A sample formula, which shows such implementation is presented below. The code fills the space between L1 and L2 trendlines (red color) and between upper and lower bands of the regression channel (RU and RL study id’s respectively).

// regular price plot
PlotClose"Close"colorDefaultstyleBar );

// custom function definition
function FillSpaceID1ID2color )
// get current chart ID
chartID GetChartID();
// read the positions of the lines
l1 Study(ID1chartID );
l2 Study(ID2chartID );
// draw cloud chart
PlotOHLCl1l1l2l2""ColorBlend(colorGetChartBkColor() ), styleCloud|styleNoRescale|styleNoLabelNullNull0, -);

// call function and refer to the assigned study ID's
FillSpace"L1","L2"colorRed );
FillSpace"RU","RL"colorBlue );

The chart produced by the formula looks as follows:

Chart with background fill

We need to remember that each line needs to have unique Study ID assigned in the Properties window.

Properties window

In case of regression channel the ID’s of the upper and lower lines are defined in Regression Channel tab:

Properties window

If we wanted to handle more lines, then it may be more practical to process the list of study ID’s defined in a custom string instead of individual function calls.

// regular price plot
PlotClose"Close"colorDefaultstyleBar );

// custom function definition
function FillSpaceID1ID2color )
// get current chart ID
chartID GetChartID();
// read the positions of the lines
l1 Study(ID1chartID );
l2 Study(ID2chartID );
// draw cloud chart
PlotOHLCl1l1l2l2""ColorBlend(colorGetChartBkColor() ), styleCloud|styleNoRescale|styleNoLabelNullNull0, -);

BulkFillIDlistcolor )
0; ( ID1 StrExtractIDlist) ) != ""+= )
ID2 StrExtractIDlisti+);
FillSpaceID1,ID2color );

// call function and refer to the assigned study ID's
BulkFill"L1,L2,RU,RL,R1,R2"colorRed );

Number of stopped-out trades as a custom metric

For the purpose of counting trades closed by particular stop we can refer to ExitReason property of the trade object in the custom backtester. The custom backtest formula presented below iterates through the list of closed trades, then counts the trades, which indicate exit reason = 2, that is stop-loss.

The following values are used for indication of the particular exit reason:

  1. normal exit
  2. maximum loss stop
  3. profit target stop
  4. trailing stop
  5. n-bar stop
  6. ruin stop (losing 99.96% of entry value)
SetCustomBacktestProc"" );

/* Now custom-backtest procedure follows */
if( Status"action" ) == actionPortfolio )
bo GetBacktesterObject();

bo.Backtest(); // run default backtest procedure

    // initialize counter
stoplossCountLong stoplossCountShort 0;

// iterate through closed trades
for( trade bo.GetFirstTrade(); tradetrade bo.GetNextTrade() )
// check for stop-loss exit reason
if( trade.ExitReason == )
// increase long or short counter respectively
if( trade.IsLong() )

// add the custom metric
bo.AddCustomMetric"Stoploss trades"stoplossCountLong stoplossCountShort


Buy CrossMACD(), Signal() );
Sell CrossSignal(), MACD() );
Short Sell;
Cover Buy;

How to fix Error 61 in printf/StrFormat calls

AmiBroker version 6.07 has introduced a strict check for correct string formatting in printf and StrFormat functions. These functions allow to specify the string followed by the list of arguments that will be inserted into the string in places, where %f, %g or %e specifiers are entered.

This works the following way:

StrFormat example

It is important for the list of subsequent arguments to match the number of % specifiers in the string. In cases, where there is no match – AmiBroker will display Error 61 message. Strict check is required because Microsoft C runtime crashes if wrong parameters are passed. Passing on earlier version was dangerous because it would lead to random crashes now and then depending on machine configuration.

There may be the following typical problems in the code:

Example 1. Four % specifiers, five value arguments

In this example formatting string contains four % specifiers so AmiBroker expects four arguments coming later, but five are given instead (too many).

// WRONG - too many value arguments
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g"OpenHighLowCloseVolume );

Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g"OpenHighLowClose );

Example 2. Five % specifiers, four value arguments

In this example formatting string contains five % specifiers so AmiBroker expects five arguments coming later, but four are given instead (too few).

// WRONG - %.0f specifier does not have a matching argument
Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g, Volume: %.0f"OpenHighLowClose );

Title StrFormat"Open: %g, High: %g, Low: %g, Close: %g, Volume: %.0f"OpenHighLowCloseVolume );

Example 3. Incorrectly coded percent (%) sign

In this example user wanted to print just % (percent sign), but used % (wrong) instead of %% (correct specifier of literal percent sign).

// WRONG - to show the % sign in the output we need to use %%
Title StrFormat"Close: %g (%.1f%)"CloseSelectedValueROCClose1) ) );

// CORRECT - you should use %% to print actual percent sign
Title StrFormat"Close: %g (%.1f%%)"CloseSelectedValueROCClose1) ) );

The example 3 requires special attention, as it is a common mistake. Due to the fact that % sign is a special character, we need to use %% in our string if we want to print % sign in the output string.

How to increase maximum periods of built-in indicators

Built-in indicators and averages which are shipped with AmiBroker use Param() function calls to provide the ability to adjust parameter values through Parameters window. Param function in the code specifies default, minimum, maximum values for the input arguments.

The order of arguments in Param function is the following:

Param"name"defaultvalminmaxstepsincr )

In certain situations, we may however want to use larger period settings than the pre-defined maximum. There is an easy way to adjust the code to achieve such task. Let us consider using built-in Price (all in one) indicator and setting e.g. 200 or 300 periods for Bollinger Bands (default maximum is 100).

To modify the underlying code, we need to:

  1. Click on the chart with right mouse button and choose Edit Formula from the context menu to bring up the AFL code editor
  2. In the code identify Bollinger Band section and the Param function call responsible for setting number of periods and change it from 200 to 300 as shown in the picture below.

    Param call

  3. Approve the changes, by selecting Tools->Apply from the editor’s menu

Now we can go back to Parameters dialog and we will be able to set Bollinger Bands Periods setting up to 300 periods.

How to run certain piece of code only once

There are situations where we may need to run certain code components just once, e.g. to initialize some static variables before auto-trading execution or perform some tasks (such as ranking) at the very beginning of backtest or exploration. The following techniques may be useful in such cases:

When we want to execute certain part of code just once after starting AmiBroker, we may use a flag written to a static variable that would indicate if our initialization has been triggered or not.

if( NzStaticVarGet("InitializationDone") ) == )
// code for first execution

If we want to run certain part of code at the beginning of the test run in Analysis window, we can use:

if ( Status("stocknum") == )
// our code here

When Status(“stocknum”) is detected in the code, then execution is performed in a single thread for the very first symbol. Only after processing of this first symbol has finished the other threads will start.

A practical example showing use of this feature is presented in the following tutorial:

Next Page »