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).
This is done by changing Buy array values from “true” to sigScaleIn (this prevents redundant signals from being removed because scale-in marks are kept untouched) and modifying standard procedure to treat scale-in signals as normal buys (no scaling).
Note that there are many ways to achieve the same effect. The technique presented here was choosen because it is easy-to-use (does not require changes in your core trading system code – all it needs is to plug-in the custom backtest part). Longer implementation would be required if you do not want to (ab)use scaling signals.
One thing worth mentioning is the fact that since scaling-in signals do not store position score this example formula does not support ranking of signals according to user-defined scores.
// YOUR TRADING SYSTEM HERE
Buy= H == HHV( H, 10 ); // REPLACE THIS WITH YOUR OWN BUY RULE
Sell = L == LLV( L, 10 ); // REPLACE THIS WITH YOUR OWN SELL RULE
PositionSize = -20;
SetOption("MaxOpenPositions", 5 );
// END OF TRADING SYSTEM HERE
// COMMON CODE PART
// TO BE COPY-PASTED if you want keep redundant signals
// This is long-only version.
// It is easy to extend to handle short trades as well
Buy = IIf( Buy, sigScaleIn, False ); // replace regular buy signals by scale in
// so they do not get filtered
SetOption("UseCustomBacktestProc", True );
if( Status("action") == actionPortfolio )
{
bo = GetBacktesterObject();
bo.PreProcess(); // Initialize backtester
for(bar=0; bar<BarCount; bar++)
{
for ( sig=bo.GetFirstSignal(bar); sig; sig=bo.GetNextSignal(bar) )
{
// first handle exit signals
if (sig.IsExit() AND sig.Price != -1 )
{
// Exit Signal
bo.ExitTrade(bar,sig.symbol,sig.Price);
}
}
// update stats after closing trades
bo.UpdateStats(bar, 1 );
bContinue = True;
for ( sig=bo.GetFirstSignal(bar); sig AND bContinue;
sig=bo.GetNextSignal(bar))
{
// enter new trade when scale-in signal is found
// and we don't have already open position for given symbol
if (sig.IsScale() AND sig.Price != -1 AND
IsNull( bo.FindOpenPos( sig.Symbol ) ) )
{
// Entry Signal
if( bo.EnterTrade(bar, sig.symbol, sig.IsLong(),
sig.Price,sig.PosSize) == 0 )
{
// if certain trade can not be entered due to insufficient funds
// or too small value (less than "MinPositionValue") or
// to few shares (less than "MinShares"
// then do NOT process any further signals
bContinue = False;
}
}
}
bo.UpdateStats(bar,1); // MAE/MFE is updated when timeinbar is set to 1.
bo.UpdateStats(bar,2);
}
bo.PostProcess(); // Finalize backtester
}