Amibroker is one of the most versatile tools for Trading system development and testing. It has a very robust backtest and optimization engine out of the box. Additionally, it also provide custom backtester interface using which you can play around the default backtest rules and metrics. It allows customizing the operation of the backtester’s second phase which processes the trading signals. In this post, we’ll try to explore Amibroker custom backtester features and examples. You should be ready to write your own custom backtest AFL after reading this post.
Amibroker Custom Backtester model
Amibroker uses object oriented model for custom backtesting. Amibroker provides a single Backtester object to perform backtests. To use the Backtester object, you first have to get a copy of it and assign that to your own variable:
bo = GetBacktesterObject();
The variable “bo” is your own variable, and you can call it whatever you like within the naming rules of AFL.
The interface also exposes the signal object, stats object and trade object, but only object directly accessible from AFL is Backtester object, all other objects are accessible by calling Backtester object methods as shown in the picture below.
Each of these objects have multiple methods which can be accesses from within the AFL code. The detailed documentation of these methods can be found here.
The Amibroker custom backtester interface provides three levels of user customization, simply called high-level, mid-level, and low-level. The high-level approach requires the least programming knowledge, and the low-level approach the most.
- high-level approach (the easiest)
– using Backtest() method and it runs default backtest procedure (as in old versions) – allows simple implementation of custom metrics - mid-level approach
– using PreProcess()/ProcessTradeSignal()/PostProcess() methods – allows to modify signals, query open positions (good for advanced position sizing) - low-level approach (the most complex)
– using PreProcess()/EnterTrade()/ExitTrade()/ScaleTrade()/UpdateStats()/HandleStops()/PostProcess() methods – provides full control over entire backtest process for hard-code programmers only
Using the Amibroker custom backtester interface
To use your own custom backtest procedure, you first need to tell Amibroker that you will be doing so. There are a few ways of doing this:
- By setting a path to the file holding the procedure in the Automatic Analysis Settings Portfolio page. This procedure will then be used with all backtests, if the “Enable custom backtest procedure” checkbox is checked.
- By specifying these same two settings in your AFL code using the functions SetOption(“UseCustomBacktestProc”, True) and SetCustomBacktestProc(“<path to procedure AFL file>”). Note that path separators inside strings need to use two backslashes, for example “c:\\AmiBroker\\Formulas\\Custom\\Backtests\\MyProc.afl”.
- By putting the procedure in the same file as the other AFL code and using the statement SetCustomBacktestProc(“”). This tells AmiBroker that there is a custom backtest procedure but there’s no path for it, because it’s in the current file. This option will be used in the examples going forward in this post.
The next thing that’s required in all backtest procedures is to ensure the procedure only runs during the second phase of the backtest. That’s achieved with the following
conditional statement:
if (Status(“action”) == actionPortfolio)
{
. . . .
}
And finally, before anything else can be done, a copy of the Backtester object is needed:
bo = GetBacktesterObject();
So all custom backtest procedures, where they’re in the same file as the other AFL code, will have a template like this:
SetCustomBacktestProc(“”);
if (Status(“action”) == actionPortfolio)
{
bo = GetBacktesterObject();
// Rest of procedure goes here
}
Amibroker Custom Backtester Examples
Example 1: Profit/Loss percentage for individual symbols in portfolio backtest
This AFL will calculate Profit/Loss % for each individual scrip in Portfolio Backtesting. This can help to determine on which securities the trading system works well, and on which securities it doesn’t.
function ProcessTrade( trade ) { global tradedSymbols; symbol = trade.Symbol; // if( ! StrFind( tradedSymbols, "," + symbol + "," ) ) { tradedSymbols += symbol + ","; } // // HINT: you may replace it with GetPercentProfit if you wish profit = trade.GetPercentProfit (); // if( trade.IsLong() ) { varname = "long_" + symbol; VarSet( varname, Nz( VarGet( varname ) ) + profit ); } else { varname = "short_" + symbol; VarSet( varname, Nz( VarGet( varname ) ) + profit ); } } // SetCustomBacktestProc( "" ); // /* Now custom-backtest procedure follows */ // if ( Status( "action" ) == actionPortfolio ) { bo = GetBacktesterObject(); // bo.Backtest(); // run default backtest procedure // tradedSymbols = ","; // //iterate through closed trades for ( trade = bo.GetFirstTrade( ); trade; trade = bo.GetNextTrade( ) ) { ProcessTrade( trade ); } // //iterate through open positions for ( trade = bo.GetFirstOpenPos( ); trade; trade = bo.GetNextOpenPos( ) ) { ProcessTrade( trade ); } // //iterate through the list of traded symbols and generate custom metrics for ( i = 1; ( sym = StrExtract( tradedSymbols, i ) ) != ""; i++ ) { longprofit = VarGet( "long_" + sym ); shortprofit = VarGet( "short_" + sym ); allprofit = Nz( longprofit ) + Nz( shortprofit ); // metric uses 2 decimal points and // 3 (calculate sum) as a "combine method" for walk forward out-of-sample bo.AddCustomMetric( "Profit for " + sym, allprofit, longprofit, shortprofit, 2, 3 ); } } // SetOption( "MaxOpenPositions", 10 ); // Buy = Cross( MACD(), Signal() ); Sell = Cross( Signal(), MACD() ); Short = Sell; Cover = Buy; SetPositionSize( 10, spsPercentOfEquity ) ; SetOption("InitialEquity",1000000);
Example 2: Relative Average Profit/Loss for each trade
This AFL will add a metric in the trade log which will specify for each winning trade how far above or below the average winning profit it was as a percentage, and similarly for each losing trade, how far above or below the average loss it was as a percentage. For this we need the “WinnersAvgProfit” and “LosersAvgLoss” values from the Stats object, and the profit from the Trade objects for each closed trade (for this example we’ll ignore open positions). Relative loss percentages are displayed as negative numbers.
SetCustomBacktestProc( "" ); if( Status( "action" ) == actionPortfolio ) { bo = GetBacktesterObject(); // Get backtester object bo.Backtest( True ); // Run backtests with no trade listing stat = bo.GetPerformanceStats( 0 ); // Get Stats object for all trades winAvgProfit = stat.GetValue( "WinnersAvgProfit" ); loseAvgLoss = stat.GetValue( "LosersAvgLoss" ); for( trade = bo.GetFirstTrade(); trade; trade = bo.GetNextTrade() ) { // Loop through all closed trades prof = trade.GetProfit(); // Get trade profit in dollars relProf = 0; // This will be profit/avgProfit as % if( prof > 0 ) // If a winner (profit > 0) relProf = prof / winAvgProfit * 100; // Profit relative to average else // Else if a loser (profit <= 0) relProf = -prof / loseAvgLoss * 100; // Loss relative to average trade.AddCustomMetric( "Rel Avg Profit%", relProf ); // Add metric } // End of for loop over all trades bo.ListTrades(); // Generate list of trades } SetOption( "MaxOpenPositions", 10 ); // Buy = Cross( MACD(), Signal() ); Sell = Cross( Signal(), MACD() ); Short = Sell; Cover = Buy; SetPositionSize( 10, spsPercentOfEquity ) ; SetOption( "InitialEquity", 1000000 );
Need to install in my pc ..please give a call on 9987638501
I’m glad I found your site. I have Amibroker but have been using MetaStock since DOS. I’m seeking tutorials that will aid me in transitioning from MFL to AFL.
Thanks for your kind words Raymond. We have noted your request. Will get back to you soon.