amibroker

HomeKnowledge Base

Debugging techniques – Part 1 – Exploration

From time to time people send us their formulas asking what happens in their own code. Or they do not know why given trade is taken or not. These questions are usually caused by the fact that people lack the insight what is happening inside and what values values their variables hold.

The first general-purpose debugging technique is using Exploration.

You need to add several AddColumn statements and run your code as Exploration, so you can actually see the values of all variables. This will reveal whenever you really have values that you expect and would make it easier for you to understand what is happening inside your code.

In simplest form add this code to your system formula:

Filter 1// show all bars
AddColumnBuy"Buy" )

and it will show you if you are getting expected values in Buy array. You can use the same technique to track the content of any variable. Add as many columns as you want. You would be surprised how much insight into your own code you will get.

You can use Exploration to learn how particular function works, for example, if you don’t understand how ValueWhen works, you can display its results this way:

Filter 1// show all bars
//
MAC10 );
cond CrossC);
bi BarIndex();
//
AddColumnC"Close" );
AddColumnm"Mov Avg" );
AddColumncond"Condition");
AddColumnbi"BarIndex" );
AddColumnValueWhencondbi ), "ValueWhen( cond, BarIndex() )" );
AddColumnValueWhencondClose), "ValueWhen( cond, Close )" )

If you run above code you will clearly see how ValueWhen picks the value when condition is true and “holds” it for all other bars (when condition is false).

Debug using Exploration

Once you get this level of insight into your code you will be better equipped to fix any errors.
Exploration is number one choice in getting detailed view on what is happening inside your code.

For more information about Exploration see http://www.amibroker.com/guide/h_exploration.html

How to create your own code snippet

AmiBroker 5.84 (released today) offers users an easy way to create their own code snippets. Code snippet is a small piece of re-usable AFL code. AmiBroker comes with lots of pre-defined snippets. You can learn more about built-in snippets here.

But now you can add your own! And it is fairly easy using new Code Snippet window. Code Snippets window is available in new AFL editor (in floating frame mode). It can be shown/hidden using Window menu.

To create your own snippet, do the following:

  1. type the code you want
  2. select (mark) the code you want to place in a snippet
  3. press Save selection as snippet button in the Code Snippets window

Code Snippets 1

If you do the steps above the following dialog will appear:

Code Snippets 2

Now you need to enter the Name of the snippet, the Description and Category. Category can be selected from already existing items (using drop down box), or new category name can be typed in the category field. Key trigger field is optional and contains snippet auto-complete trigger (to be implemented later). Once you enter all fields and press OK, your new snippet will appear in the list.

Code Snippets 3

From then on you can use your own snippet the same way as existing snippets. Perhaps most convenient method is using drag-drop from the list to AFL editor.

As you may have noticed user-defined snippets are marked with red color box in the Code Snippets list. Only user-defined snippets can be overwritten and/or deleted. To overwrite existing user-defined snippet, simply follow the steps above and give existing name. AmiBroker will ask then if you want to overwrite existing snippet. To delete a snippet, select the snippet you want to delete from the list and press Delete (X) button in the Code Snippet window.

Closing trades in delisted symbols

When we perform historical tests on databases that contain delisted symbols – we may encounter a situation, where there are open positions in those tickers remaining till the very end of the backtest, distorting the results (as these open positions will reduce remaining maximum open positions limit for the other symbols).

Here is an easy technique which allows to force closing positions in those symbols on the very last bar traded for given symbol. The code below just adds an additional Sell signal on the last available bar in the database for this symbol:

bi BarIndex();
exitLastBar bi == LastValuebi );
Sell /*your regular sell rules*/ OR exitLastBar

If we are using 1-bar trade delays in our backtesting settings, then the exit signal would need to be triggered one bar in advance (so the delayed signal could still be traded on the last bar) and the code would look like this:

SetTradeDelays(1,1,1,1);
bi BarIndex();
exitLastBar bi == LastValuebi );
Sell /*your regular sell rules*/ OR exitLastBar

There is also a dedicated field in Symbol->Information window which allows to store the delisting date directly in the database. AmiBroker allows to read that field from AFL code using GetFnData() function. If we have this field populated for delisted symbols for our symbols, then the code forcing exits on delisting date would be:

exitLastBar datetime() >= GetFnData("DelistingDate");
Sell /*your regular sell rules*/ OR exitLastBar

What is important, this approach would work also, when Pad and Align to reference symbol feature is used in Analysis window settings.

In order to populate Delisting Date field in the database, we can enter the dates manually through Symbol->Information window or use ASCII importer to import the information from the input text files. More details about ASCII importing can be found at:
http://www.amibroker.com/guide/d_ascii.html

Can I encrypt my formula so no-one can decipher it?

Currently the only way to protect your code from other peoples’ eyes is to translate part of the formula (such as few functions) or entire formula to C/C++ language and compile as AFL plugin DLL. Doing so requires some C/C++ programming knowledge, a C/C++ compiler (free Visual Studio Express or GNU tools can be used) and AmiBroker Development Kit (ADK).

ADK contains instructions how to write your own AFL plugin along with code samples that are ready-to-compile. Some C/C++ knowledge is required though.

ADK is freely downloadable from
http://www.amibroker.com/bin/ADK.exe (self-extracing exe)
or
http://www.amibroker.com/bin/ADK.zip (zip archive)

NOTE: ADK is not for the beginners. It is for programmers (i.e. for people who already wrote some C/C++ code in their life). We are working on providing alternative methods for non-programmers.

How to identify signal that triggered entry/exit if multiple signals are used

When designing a trading system we often need to quickly identify which of the rules used in the code triggered the particular Buy or Sell signal. Here are some techniques that may be useful in such identification.

For the purpose of this demonstration let us use a sample formula, where the Buy signal may be triggered by one of three independent rules:

Buy1 CrossMACD(), Signal() );
Buy2 CrossCloseMA(Close50) );
Buy3 CrossRSI(), 30 );
//
Buy buy1 OR Buy2 OR Buy3

To determine which of those three rules generates the entry signal, we can either visualize signals in the chart or use Exploration feature of the Analysis window.

In case a custom chart is used, we can do the following:

  1. display the signal in custom chart title
  2. use PlotShapes function to indicate certain buy rule
  3. use PlotText to add pre-defined text labels.

The formula below shows sample implementations of these three techniques. This is actually one of many ways that can be used for coding such custom output:

Buy1 CrossMACD(), Signal() );
Buy2 CrossCloseMA(Close,50) );
Buy3 CrossRSI(), 30 );
//
Buy buy1 OR Buy2 OR Buy3;
//
// Standard price plot
PlotClose"Close"colorBlackstyleCandle);
//
// Custom title definition
BuyReason EncodeColor(colorGreen ) + WriteIf(Buy,"Buy signals: ","")
           + 
WriteIf(buy1"Buy1 """) +WriteIf(buy2"Buy2""")
           + 
WriteIf(buy3"Buy3""");
Title StrFormat"{{NAME}} - {{INTERVAL}} {{DATE}} Close %g ",Close ) +BuyReason;
//
// Plotshapes function calls
PlotShapes(Buy*shapeUpArrowcolorGreen0Low);
PlotShapes(Buy1*shapedigit1colorGreen0Low,-30);
PlotShapes(Buy2*shapedigit2colorGreen0Low,-45);
PlotShapes(Buy3*shapedigit3colorGreen0Low,-60);
//
//
// Custom text labels displayed with PlotText
if( SelectedValue(Buy) )
{
   
SelectedValueBarIndex() );
   
maxy Status("axismaxy");
   
miny Status("axisminy");
   
0.15 * (maxy miny) + miny;
   
text WriteIf(buy1], "\nBuy1 """)
          +  
WriteIf(buy2], "\nBuy2 """)
          +  
WriteIf(buy3], "\nBuy3 """);
   
PlotTexttexti,  ycolorWhitecolorGreen );

The chart below shows how to use signal visualization technique implemented in the formula.

Chart Example 1

The other method is to use the Exploration feature of Analysis window that allows to generate tabular output, where we can display the values of selected variables. The detailed tutorial explaining this feature is available at:
http://www.amibroker.com/guide/h_exploration.html

For the discussed purpose of tracking the signals that triggered entry or exit, we can add the following code to our trading system to show the values of each Buy1, Buy2, Buy3 variables:

Filter Buy;
AddColumnBuy1"Buy1"1colorDefaultIIfBuy1colorGreencolorDefault ) );
AddColumnBuy2"Buy2"1colorDefaultIIfBuy2colorGreencolorDefault ) );
AddColumnBuy3"Buy3"1colorDefaultIIfBuy3colorGreencolorDefault ) )

Exploration Signal tracking

With regard to exit signals they can be visualized in a similar way as shown above, but there is also an additional functionality in the backtester, which allows to indicate the exit condition directly in the trade list. This can be done by assigning values higher than 1 (but not more than 127) to Sell variable.

Sell1 CrossSignal(), MACD() );
sell2 CrossMA(Close50), Close );
Sell Sell1 10 Sell2 20

The above expression will result in assigning value of 10 to Sell variable for the bars where Sell1 is true, 20 for the bars where Sell2 is true and 30 for the bars where both conditions are true.

These values will be indicated in the trade list:

Backtest exit signal tracking

It is worth to mention that values 1 to 9 are reserved for built-in stops and used internally by the backtester, and have special meaning:

  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)
  7. reserved
  8. reserved
  9. reserved

Note also that you must not assign value greater than 127 to Sell or Cover variable. If you assign bigger value it will be truncated.

This is further discussed here: http://www.amibroker.com/guide/afl/equity.html

Do NOT make assumptions on number of bars

From time to time some users face “Error 10. Subscript out of range” in their formulas. The error itself is described in the manual, but still a few words of explanation why it happens may be useful.

The error usually occurs when formula uses hard-coded number of bars in the loop statements like this:

for( 0300i++ ) // MISTAKE: FIXED number of bars
{
  
Close]; // ERROR 10. because 'i' becomes greater than BarCount

or like this:

for( BarCount 1BarCount 300i-- ) // MISTAKE: FIXED number of bars
{
  
Close]; // ERROR 10. because 'i' becomes LESS than zero

In both cases the code will FAIL if it is run on symbol that has LESS than 300 bars (BarCount < 300). In fact it will even fail on symbol that has more than 300 bars because of two facts:

  1. during AFL Editor’s Verify Syntax not more than 200 most recent bars are used
  2. a chart may be zoomed in so number of visible bars may be much lower and QuickAFL kicks in (so your AFL is executed with visible bars only).

This all means that one should never make any assumptions on number of bars your formula would get, because if you do, the formula will fail.

The formula should be written so it is able to execute without errors with BarCount as small as 1 (ONE).

This is normally done by writing a ‘for’ loop in a way recommended in the manual:

for( 0BarCounti++ )
{
   
Close]; // this will never produce Error 10 because i is in the range 0..BarCount-1

If your formula references past data, say 10-bars earlier, you should start your loop with index 10, as below:

for( 10BarCounti++ )
{
  
x] = Close] - Close10 ]; // both subscripts will be OK

What to do if your formula, for some reason, really requires fixed number of bars? Well, the answer is that you should check if you really get as many bars as you think:

if( BarCount 300 // check first if you have enough bars
{
   
// here we know that we have more than 300 bars
   
for( 0300i++ )
   {
      
Close]; 
   }

A function with multiple return values

A typical AFL function returns one value. For example sin( x ) returns sine value of argument x. Sometimes however it is useful and/or required to return more than one value from the function.

Returning multiple values is possible only via arguments passed by reference, but trouble is that in AFL all arguments are passed by value (as in C language). Passing by value means that only value of variable is passed, not the variable itself, so original variable is not modified as shown in the example below:

function Dummy)
{
    
7// x is treated as function-local
}
//
val 10;
Dummyval );
printf"%g\\n"val ); // will print 10 because 'val' is unaffected by function cal

The behaviour shown above is desirable because we usually want the function to be opaque and do not interfere with what is defined outside of the function except for returning the result of the function.

But what if we actually wanted to write to variables passed as arguments? Well that is possible if we pass the names of the variables as arguments.

// This example shows how to return multiple values
// the idea is to pass the name of the variable instead of
// value
//
function fun_multiple_resultsresult1nameresult2name )
{
  
VarSetresult1name); // setting variable using passed name
  
VarSetresult2name);
  return;
}
//
// to get multiple values from a function
// we call the function passing NAMES of variables
//
10;
20;
printf("a = %g\\n");
printf("b = %g\\n");
//
fun_multiple_results"a""b" ); // pass the names of variables
//
printf("a = %g\\n"); // see new values assigned to variables
printf("b = %g\\n")

Of course we can use arguments passed by name for two-way communication – we can use them as both inputs and outputs as shown in the following example that swaps the values of arguments

function Swapvar1namevar2name )
{
    
temp1 VarGetvar1name ); // read the value from variable
    
temp2 VarGetvar2name );
    
VarSetvar1nametemp2 ); // write the value to variable
    
VarSetvar2nametemp1 );
}
// Initial values
5;
37;
//
printf("Before swap x = %g, y = %g\\n"x);
//
Swap"x""y" ); // pass names of variables
//
printf("After swap x = %g, y = %g\\n"x)

The code above will produce output like this:

Before swap x = 5, y = 37
After swap x = 37, y = 5

So it is clear that variables were passed to the function, swapped and returned successfully.

Broad market timing in system formulas

Some trading systems may benefit from attempt to time the broad market. A market-wide valuation, such as moving average, sentiment or some other mechanism may be used to tell if we should be in the market or not.

Flexibility of AFL language allows to create rules or indicators, which are based on more than just one symbol. This enables us to introduce additional filters based on wide-market index performance.

For the purpose of reading quotes of another symbol one can use Foreign or SetForeign functions.

The following formula shows how to generate entry signals in individual stocks when S&P500 index is above its 200-period moving average and exit signals when S&P500 is equal or below 200-period average (^GSPC is a ticker for Yahoo Finance data for S&P500)

//
// read S&P 500 values from ^GSPC ticker
//
sp500 Foreign"^GSPC""C" );
//
// market-wide filter should be in "state" form
// (so it is True all the time when market is up)
//
marketup sp500 MAsp500200 );
marketdown NOT marketup;
//
// sample trading rules (MACD crossovers)
//
BuySignal CrossMACD(), Signal() );
SellSignal CrossSignal(), MACD() );
//
// combine per-symbo signals with broad-market timing
//
Buy BuySignal AND marketup// enter trade only when buy signal AND market is in up trend
Sell SellSignal OR marketdown// exit position if sell signal OR market turns dow

A more complex broad-market timing that requires not only closing price of market index can be implemented using SetForeign function. SetForeign replaces all OHLCV data series with that of the “other” security and allows to calculate all kind of indicators that would normally use current security. Broad market timing does not need to be just “all-in” or “all-out” switch. For example one can switch the trading method depending on whenever broad market is trending or sideways.

//
// Switch to S&P symbol to calculate broad-market timing
//
SetForeign"^GSPC" );
//
// now we can calculate any indicator based on SP500
//
MarketIsTrending ADX40 ) > 20// ADX (40 days) from SP500
//
// now go back to original data (current symbol)
//
RestorePriceArrays();
//
// you can have different rules that are switched
// depending on what broad market is doing
//
TrendingBuy CrossCMAC30 ) );
TrendingSell CrossMAC30 ), );
//
SidewaysBuy CrossMACD(), Signal() );
SidewaysSell CrossSignal(), MACD() );
//
// switch methods using broad-market timing
//
Buy IIfMarketIsTrendingTrendingBuySidewaysBuy );
Sell IIfMarketIsTrendingTrendingSellSidewaysSell )

In this simple example we assume that market timing signals change very infrequently so they change much less often than Trending/Sideways Buy/Sell signals are generated. If that is not the case the switching logic would need to be more complex to decide what to do when we are in the “trending” trade and market switches to sideways mode. In such situation, the code above uses SidewaysSell signal to sell the position, which may or may not be what you are after.

Another example is changing position sizing depending on broad market conditions. We can choose to be fully invested when broead market is up and only 30% invested in down market.

//
// Switch to S&P symbol to calculate broad-market timing
//
SetForeign"^GSPC" );
//
// now we can calculate any indicator based on
// SP500
//
MarketIsUp MAC200 ); // here C represents closing price of SP500
//
// now go back to origiginal data (current symbol)
//
RestorePriceArrays();
//
// normal rules (in this example they do not chang)
//
Buy CrossMACD(), Signal() );
Sell CrossSignal(), MACD() );
//
// no more than 10 positions open at a time
//
SetOption("MaxOpenPositions"10 );
//
// change position sizing depending on broad market conditions
// in this example we will allocate:
// 10% per position
// if broad market is up (so we can be allocated upto 100% of funds)
// 3% per position
// if broad market is down (so we can be allocated upto 30% of funds)
//
SetPositionSizeIIfMarketIsUp10), spsPercentOfEquity )

How to display Bond and Bill prices in fractions

Treasury Bond and Bill futures are traded in fractions, not decimals. A typical bond quote may be 124’21 which means 124 and 21/32nds, so one needs special method to display prices in non-decimal format.

To achieve desired result we need to do two things.

1. Switch Y axis grid to desired fraction format. To make a change, right click on the chart and select Parameters, then switch to Axes & Grid tab and change Grid Format as shown below:

Parameter Window

2. Create a price chart with custom title showing fractions instead of decimals. Go to Analysis->Formula Editor, enter the following formula and press Apply Indicator toolbar button.

function NumToFracnum )
{
  return 
StrFormat("%.0f'%0.f"floornum ), fracnum ) * 320 );
}
PlotClose"Close"colorDefaultstyleCandle);
Title "{{NAME}} - {{INTERVAL}} {{DATE}} Close: " NumToFracClose )

The NumToFrac function formats decimal value into full points and 1/320nds fraction of the full point.

Using Zig-Zag in trading systems

Zig-zag indicator, as well as other functions using it (Peak/Trough, PeakBars, Troughbars), inherently look into the future. As such they should not be used in trading system formulas without taking precautions. The only way to fix the ‘problem’ is to delay the signal as long as it takes for zig/zag to stabilise last ‘leg’. The delay is variable and depends how much time it takes for defined percentage change to occur in the price series since last peak/trough.

Ready-to-use solution is presented in the Traders’ Tips section of the AmiBroker members area:
http://www.amibroker.com/members/traders/11-2003.html

(NOTE: access to members’ area is limited to licensed users only, if you forgot your password use reminder at http://www.amibroker.com/login.html)

« Previous PageNext Page »