Backtesting question

coming back to this thread: Exit ignored because signal predates already processed event - #2 by Tomasz

can the regular backtester process a buy, sell, short, cover signal in 1 bar? Or can the regular backtester not know the sequence of these trades and I would need to use the CBT?

I am testing a situation where the price crosses a trail line and then takes the trade at the trail line price. So then you get 2 trades (going from a short to a long position):

  1. Cover (at trail line price)
  2. Buy (at trail line price)

Then I wait until the end of the bar and see if the price is still above the trail line. If it is not then I return to the short position which results in 2 additional trades:

  1. Sell (at close price of bar)
  2. Short (at close price of bar)

I tested this but the regular backtester will not handle all these trades. I get:

Date	Information
	Entry signals(score):@NQ#=Short(-1), @NQ#=Buy(1), 
	Exit signals:@NQ#=Cover, @NQ#=Sell, 
	Exit Short, @NQ#, Price: 25257, (Avg. exit pr. 25257), Shares: 1, Commission: 2.5, (Total comm.: 5), Profit: 1120 (22.40 %), Entry rank:-1, Equity: 109790, Fx rate: 1
	Enter Short, @NQ#, Price: 25254.75, Shares: 1, Commission: 2.5, Rank: -1, Equity 109630, Margin Loan: 0, Fx rate: 1
	@NQ# Entry ignored because signal predates already processed event
	@NQ# Exit ignored because signal predates already processed event
	1 Open Positions: , @NQ# (-1), Market Value: 4997.50, Equity: 109630.00, Cash: 104632.50, Margin: 0.00, Net Cash Balance: 104632.50, 

The system is in the 5min timeframe and I already calculate it in the 1min timeframe but sometimes the signal occurs in the last 1min bar of the 5min TF. See chart for illustration.

The first signal bar in the chart shows that the trail line is broken to the upside. It then covers the short and goes long. But since this is also the last 1min bar of a 5min bar and the close price of this bar is below the trail line it sells the long and goes back to a short position at the close price of this 1min bar.

The second trade is entered in the 2nd minute of a 5min bar and because it closes below the trail line it reverses back to a short position in the 5th minute of the 5min bar.

1 Like

I never really understood the Custom Backtester. Below I tried something. The original signals are commented out and replaced with Custom backtester code. I do not get any signals. Can somebody point me to what I am doing wrong? Thank you

// TRADING SIGNALS ARE CALCULATED HERE
// this has to be put in the custom backtester
if( Status( "actionex" ) == actionPortfolio )
{
    // back test for futures
    SetBarsRequired( sbrAll, sbrAll );
    SetBacktestMode( backtestRegularRaw2Multi );
    SetTradeDelays( 0, 0, 0, 0 );
    SetOption( "FuturesMode", True );
    SetOption( "PriceBoundChecking", False );
    SetOption( "AllowSameBarExit", True );
    SetOption( "CommissionMode", 3 );
    SetOption( "CommissionAmount", 2.5 );
    SetPositionSize( 1, spsShares );
    SetOption( "UseCustomBacktestProc", True );

    bo = GetBacktesterObject();
    bo.PreProcess();
    sym = Name();
    psize = 1;
    rnd = 1;

    Buy = Sell = Short = Cover = 0;
    BuyPrice = SellPrice = ShortPrice = CoverPrice = 0;
    inlong = inshort = 0;
    buysequence = shortsequence = sellsequence = coversequence = 0;

    for( i = 0; i < BarCount; i++ )
    {
        if( uptrend[i] )
        {
            if( L[i] < trailarrayup[i] AND inshort == 0 )
            {
                if( inlong == 1 )
                {
                    // exit long
                    bo.ExitTrade( i, sym, Min( O[i], trailarrayup[i] ) );
                    //Sell[i] = 1;
                    //SellPrice[i] = Min( O[i], trailarrayup[i] );
                    inlong = 0;
                    sellsequence[i] = 1; // if multiple signals inside one bar track the sequence number
                }

                // enter short
                bo.EnterTrade( i, sym, False, Min( O[i], trailarrayup[i] ), psize, rnd );
                //Short[i] = 1;
                //ShortPrice[i] = Min( O[i], trailarrayup[i] );
                inshort = 1;
                shortsequence[i] = 1 + sellsequence[i]; // if multiple signals inside one bar track the sequence number

                // test if close of bar is below trail line
                if( C[i] > trailarrayup[i] + threshold[i] )
                {
                    // stay in long because trail is not broken to the down side (cover short and go back long)

                    // cover short
                    bo.ExitTrade( i, sym, C[i] );
                    //Cover[i] = 1;
                    //CoverPrice[i] = C[i];
                    inshort = 0;
                    coversequence[i] = 3; // if multiple signals inside one bar track the sequence number

                    // get back long
                    bo.EnterTrade( i, sym, True, C[i], psize, rnd );
                    //Buy[i] = 1;
                    //BuyPrice[i] = C[i];
                    inlong = 1;
                    buysequence[i] = 4; // if multiple signals inside one bar track the sequence number
                }
            }
            else

                // re-enter long when trail line is not crossed by the close price of the bar
                if( C[i] > trailarrayup[i] + threshold[i] AND inshort == 1 )
                {
                    // cover short
                    bo.ExitTrade( i, sym, C[i] );
                    //Cover[i] = 1;
                    //CoverPrice[i] = C[i];
                    inshort = 0;
                    coversequence[i] = 1; // if multiple signals inside one bar track the sequence number

                    // get back long
                    bo.EnterTrade( i, sym, True, C[i], psize, rnd );
                    //Buy[i] = 1;
                    //BuyPrice[i] = C[i];
                    inlong = 1;
                    buysequence[i] = 2; // if multiple signals inside one bar track the sequence number
                }
        }
        else
            if( dntrend[i] )
            {
                if( H[i] > trailarraydn[i] AND inlong == 0 )
                {
                    if( inshort == 1 )
                    {
                        // exit short
                        bo.ExitTrade( i, sym, Max( O[i], trailarraydn[i] ) );
                        //Cover[i] = 1;
                        //CoverPrice[i] = Max( O[i], trailarraydn[i] );
                        inshort = 0;
                        coversequence[i] = 1; // if multiple signals inside one bar track the sequence number
                    }

                    // enter long
                    bo.EnterTrade( i, sym, True, Max( O[i], trailarraydn[i] ), psize, rnd );
                    //Buy[i] = 1;
                    //BuyPrice[i] = Max( O[i], trailarraydn[i] );
                    inlong = 1;
                    buysequence[i] = 1 + coversequence[i]; // if multiple signals inside one bar track the sequence number

                    // test if close of bar is above trail line
                    if( C[i] < trailarraydn[i] - threshold[i] )
                    {
                        // stay in short because trail is not broken to the up side (sell long and go back short)

                        // sell long
                        bo.ExitTrade( i, sym, C[i] );
                        //Sell[i] = 1;
                        //SellPrice[i] = C[i];
                        inlong = 0;
                        sellsequence[i] = 3; // if multiple signals inside one bar track the sequence number

                        // get back short
                        bo.EnterTrade( i, sym, False, C[i], psize, rnd );
                        //Short[i] = 1;
                        //ShortPrice[i] = C[i];
                        inshort = 1;
                        shortsequence[i] = 4; // if multiple signals inside one bar track the sequence number
                    }
                }
                else

                    // re-enter short when trail line is not crossed by the close price of the bar
                    if( C[i] < trailarraydn[i] - threshold[i] AND inlong == 1 )
                    {
                        // sell long
                        bo.ExitTrade( i, sym, C[i] );
                        //Sell[i] = 1;
                        //SellPrice[i] = C[i];
                        inlong = 0;
                        sellsequence[i] = 1; // if multiple signals inside one bar track the sequence number

                        // get back short
                        bo.EnterTrade( i, sym, False, C[i], psize, rnd );
                        //Short[i] = 1;
                        //ShortPrice[i] = C[i];
                        inshort = 1;
                        shortsequence[i] = 2; // if multiple signals inside one bar track the sequence number
                    }
            }

        bo.UpdateStats( i, 1 );
        bo.UpdateStats( i, 2 );
    }

    bo.PostProcess();
}

Status( "actionex" ) == actionPortfolio
The code in this section runs only once in the second phase of backtesting, and therefore this is wrong sym = Name(); if you want to retrieve the actual symbol to trade.
bo.ExitTrade( i, sym, Min( O[i], trailarrayup[i] ) ); sym here is therefore incorrect.
Basically, all arguments to bo.ExitTrade() are incorrect because you need to go through how the CBT works.

In 2nd phase Name() will return ~~~EQUITY

For that, in 2nd phase you need to retrieve either the signal object or trade object.
You iterate over each bar, which will return signal object.

Also, you have mixed the For loop which is supposed to be outside and runs for each symbol and sets the required arrays Buy, Sell, Short, Cover, BuyPrice, SellPrice, ShortPrice, CoverPrice

You create signals in the first phase.

Read the Signal object paragraph here
Porfolio Backtester Interface Reference

1 Like

thanks for your reply.

So if I understand you correctly I first need to calculate and fill the Buy, Sell, Short, Cover etc. arrays which would be the first phase of the backtest. And then I can do the the second phase? I thought I read somewhere that this was not necessary. Let me search for that comment, will get back to you.

That was this comment:

but maybe the context is different. Will have a look at it again

Think of it this way.

When you reach phase 2, you are looking at signals from all the symbols.

logically, you would use the signal object for each symbol on every bar and decide if you want to process the signal by opening or closing a trade.

now if you decide not to use the signals, then you can randomly open and close trades which Low-level CBT will allow but it not realistic, is it?

1 Like

thank you. I will get back to you later. I thought I seen similar code as mine. I see the point for Phase 1 and create the backtest object which you can then access in Phase 2. But at a low level I do not understand why I can not directly create an empty backtest object and then fill it with trades and skipping the first phase.

But I will study it more and hope I will understand it better. Also will check if it will work if I do the Phase 1 first and then loop through the signals as per this redundant example of april 24, 2006

Following statement is a "what-if" and not recommended / wrong.

well, you could use SetForeign() inside Status( "actionex" ) == actionPortfolio code block,
then, you could almost do the phase 1 inside phase 2 but it is the wrong way and not appropriate use of CBT.
bo.PreProcess(); has a purpose, which is affected by SetBacktestMode()

In general, we have to follow AB rules in the sense that we use it how the designer intended it to be used.

1 Like

In addition to all the helpful advice from @nsm51, I would move all the initial Set* commands outside of the CBT, i.e. not inside the block that begins with:

If (Status( "actionex" ) == actionPortfolio)
3 Likes

thanks. I have not solved it yet but probably need to use the example from 2006.

If anyone wants to give it a shot :grinning_face: I put the entire system without CBT below. This is a test version adding a simple system.

The issue I try to solve is that a signal is only valid if the CLOSE price crosses above the trail line (in the case of a long trade). In the chart you see the system is short and then the high price crosses the trail to the upside. Then I cover (number 1), buy (number 2) but the close closes below the trail line so then I sell (number 3) and go back short (number 4). So these are 4 trades in a single signal bar. The regular backtester does not handle all these signals. It only does a cover and goes back short. The buy and sell it does not handle. This is what I am trying to solve with the CBT. Thanks

// https://www.amibroker.com/guide/a_custombacktest.html
// https://www.amibroker.com/kb/category/analysis/backtest-analysis/custom-backtest/

per = Param( "Period", 50, 5, 100, 1 );
threshold_fact = Param( "Threshold_factor", 0, 0, 100, 1 );
showsequencenumbers = ParamToggle( "Show Sequence Numbers", "No|Yes", 1 );
ft = ParamList( "Font Type", "Tahoma Bold|Arial Black|Verdana Bold|Courier New Bold|Comic Sans MS Bold|Arial Bold|Candara Bold|Calibri Bold|Constantia Bold|Georgia Bold|Gadugi Bold|Segoe UI Bold", 1 );
sz = Param( "Font Size", 15, 8, 50, 1 );

bi = BarIndex();
fvb = FirstVisibleValue( bi );
lvb = LastVisibleValue( bi );
trailarrayup = trailarraydn = Null;

trailarraydn = Ref( HHV( H, per ), -1 );
trailarrayup = Ref( LLV( L, per ), -1 );

threshold = TickSize * threshold_fact;

// if ticksize is not set in code or information window use 0.01
if( TickSize == 0 )
{
    TickSize = 0.01;
}

// test system
Buy1 = Cross( C, trailarraydn );
Short1 = Cross( trailarrayup, C );
uptrend = Ref( Flip( Buy1, Short1 ), -1 );
dntrend = Ref( Flip( Short1, Buy1 ), -1 );

// back test for futures
//SetBarsRequired( sbrAll, sbrAll );
SetBacktestMode( backtestRegularRaw2Multi );
SetTradeDelays( 0, 0, 0, 0 );
SetOption( "FuturesMode", True );
SetOption( "PriceBoundChecking", False );
SetOption( "AllowSameBarExit", True );
SetOption( "CommissionMode", 3 );
SetOption( "CommissionAmount", 2.5 );
SetPositionSize( 1, spsShares );
SetOption( "MaxOpenPositions", 1 );

Buy = Sell = Short = Cover = 0;
BuyPrice = SellPrice = ShortPrice = CoverPrice = 0;
inlong = inshort = 0;
buysequence = shortsequence = sellsequence = coversequence = 0;

for( i = 0; i < BarCount; i++ )
{
    if( uptrend[i] )
    {
        if( L[i] < trailarrayup[i] AND inshort == 0 )
        {
            if( inlong == 1 )
            {
                // exit long
                Sell[i] = 1;
                SellPrice[i] = Min( O[i], trailarrayup[i] );
                inlong = 0;
                sellsequence[i] = 1; // if multiple signals inside one bar track the sequence number
            }

            // enter short
            Short[i] = 1;
            ShortPrice[i] = Min( O[i], trailarrayup[i] );
            inshort = 1;
            shortsequence[i] = 1 + sellsequence[i]; // if multiple signals inside one bar track the sequence number

            // test if close of bar is below trail line
            if( C[i] > trailarrayup[i] + threshold[i] )
            {
                // stay in long because trail is not broken to the down side (cover short and go back long)

                // cover short
                Cover[i] = 1;
                CoverPrice[i] = C[i];
                inshort = 0;
                coversequence[i] = 3; // if multiple signals inside one bar track the sequence number

                // get back long
                Buy[i] = 1;
                BuyPrice[i] = C[i];
                inlong = 1;
                buysequence[i] = 4; // if multiple signals inside one bar track the sequence number
            }
        }
        else

            // re-enter long when trail line is not crossed by the close price of the bar
            if( C[i] > trailarrayup[i] + threshold[i] AND inshort == 1 )
            {
                // cover short
                Cover[i] = 1;
                CoverPrice[i] = C[i];
                inshort = 0;
                coversequence[i] = 1; // if multiple signals inside one bar track the sequence number

                // get back long
                Buy[i] = 1;
                BuyPrice[i] = C[i];
                inlong = 1;
                buysequence[i] = 2; // if multiple signals inside one bar track the sequence number
            }
    }
    else
        if( dntrend[i] )
        {
            if( H[i] > trailarraydn[i] AND inlong == 0 )
            {
                if( inshort == 1 )
                {
                    // exit short
                    Cover[i] = 1;
                    CoverPrice[i] = Max( O[i], trailarraydn[i] );
                    inshort = 0;
                    coversequence[i] = 1; // if multiple signals inside one bar track the sequence number
                }

                // enter long
                Buy[i] = 1;
                BuyPrice[i] = Max( O[i], trailarraydn[i] );
                inlong = 1;
                buysequence[i] = 1 + coversequence[i]; // if multiple signals inside one bar track the sequence number

                // test if close of bar is above trail line
                if( C[i] < trailarraydn[i] - threshold[i] )
                {
                    // stay in short because trail is not broken to the up side (sell long and go back short)

                    // sell long
                    Sell[i] = 1;
                    SellPrice[i] = C[i];
                    inlong = 0;
                    sellsequence[i] = 3; // if multiple signals inside one bar track the sequence number

                    // get back short
                    Short[i] = 1;
                    ShortPrice[i] = C[i];
                    inshort = 1;
                    shortsequence[i] = 4; // if multiple signals inside one bar track the sequence number
                }
            }
            else

                // re-enter short when trail line is not crossed by the close price of the bar
                if( C[i] < trailarraydn[i] - threshold[i] AND inlong == 1 )
                {
                    // sell long
                    Sell[i] = 1;
                    SellPrice[i] = C[i];
                    inlong = 0;
                    sellsequence[i] = 1; // if multiple signals inside one bar track the sequence number

                    // get back short
                    Short[i] = 1;
                    ShortPrice[i] = C[i];
                    inshort = 1;
                    shortsequence[i] = 2; // if multiple signals inside one bar track the sequence number
                }
        }
}

SetChartBkColor( ColorRGB( 0, 0, 0 ) );
SetChartOptions( 0, chartShowArrows | chartShowDates );
Plot( C, "C", colorWhite, styleCandle, Null, Null, 0, 0, 1 );
Plot( trailarrayup, "trail up", colorLightBlue, styleLine | styleStaircase | styleNoRescale, Null, Null, 0, 0, 1 );
Plot( trailarraydn, "trail dn", colorLightOrange, styleLine | styleStaircase | styleNoRescale, Null, Null, 0, 0, 1 );

PlotShapes( IIf( Buy, shapeUpArrow, shapeNone ), colorBrightGreen, 0, L, -15 );
PlotShapes( IIf( Sell, shapeDownArrow, shapeNone ), colorRed, 0, H, -15 );
PlotShapes( IIf( Buy, shapeSmallCircle, shapeNone ), colorLightBlue, 0, BuyPrice, 0 );
PlotShapes( IIf( Sell, shapeSmallCircle, shapeNone ), colorLightOrange, 0, SellPrice, 0 );
PlotShapes( IIf( Short, shapeSmallDownTriangle, shapeNone ), colorRed, 0, H, IIf( Short AND Sell, -30, -15 ) );
PlotShapes( IIf( Cover, shapeSmallUpTriangle, shapeNone ), colorBrightGreen, 0, L, IIf( Cover AND Buy, -30, -15 ) );
PlotShapes( IIf( Short, shapeSmallSquare, shapeNone ), colorLightOrange, 0, ShortPrice, 0 );
PlotShapes( IIf( Cover, shapeSmallSquare, shapeNone ), colorLightBlue, 0, CoverPrice, 0 );

Plot( IIf( trailarrayup, trailarrayup + threshold, Null ), "", colorLightBlue, styleDashed | styleStaircase | styleNoRescale, Null, Null, 0, 1, 1 );
Plot( IIf( trailarraydn, trailarraydn - threshold, Null ), "", colorLightOrange, styleDashed | styleStaircase | styleNoRescale, Null, Null, 0, 1, 1 );
Plot( uptrend, "", ColorRGB( 0, 0, 30 ), styleArea | styleOwnScale | styleNoLabel, 0, 1, 0, -100 );
Plot( dntrend, "", ColorRGB( 30, 0, 0 ), styleArea | styleOwnScale | styleNoLabel, 0, 1, 0, -100 );

if( showsequencenumbers )
{
    for( i = fvb + 1; i <= lvb; i++ )
    {
        if( buysequence[i] OR sellsequence[i] OR shortsequence[i] OR coversequence[i] )
        {
            if( buysequence[i] )
            {
                PlotTextSetFont( "" + buysequence[i], ft, sz, i, L[i], colorLightBlue, colorDarkGrey, -4.5 * sz );
            }

            if( sellsequence[i] )
            {
                PlotTextSetFont( "" + sellsequence[i], ft, sz, i, H[i], colorLightOrange, colorDarkGrey, 3.5 * sz );
            }

            if( coversequence[i] )
            {
                PlotTextSetFont( "" + coversequence[i], ft, sz, i, L[i], colorLightBlue, colorDarkGrey, -6 * sz );
            }

            if( shortsequence[i] )
            {
                PlotTextSetFont( "" + shortsequence[i], ft, sz, i, H[i], colorLightOrange, colorDarkGrey, 5 * sz );
            }
        }
    }
}

Title = EncodeColor( colorLightOrange ) + "trailarraydn: " + trailarraydn + " | " + EncodeColor( colorLightBlue ) + "trailarrayup: " + trailarrayup + " | "  + EncodeColor( colorGold ) + "Close: " + C;

should be

SetBacktestMode( backtestRegularRawMulti );

1 Like