Issue with Position Sizing When Using SetTradeDelays in GTAA Backtest

Hi everyone!

I’m using the GTAA system shared by TrendXplorer at this post

And I’m noticing something that isn’t working as expected. If I trade the signal at the close of the same candle, I get the expected results. However, to make it more realistic, I prefer to parameterize buys and sells at the open of the next candle. When I do this, some trades don’t execute due to a lack of capital, as you can see in the image.

But when I don’t use the SetTradeDelay, the same trade executes without any issues.

I’ve identified that the problem is with the position sizing calculation. However, what I can’t understand is why it calculates the position size correctly without SetTradeDelays, but miscalculates it when SetTradeDelays is enabled.

Could anyone guide me on how to run the backtest using the SetTradeDelay with the correct trades?

Thank you very much!

That is so because probably the formula does not account for delays that you might have set and calculates PositionSize variable incorrectly (without accounting for delays).

1 Like

Good morning, Tomasz. Thank you for your quick response.

Yes, that’s the conclusion I’ve reached as well: the positionsize variable doesn’t calculate correctly when delays are applied.

However, the Explorer shows no difference in the variable with or without the delay, so I’m not sure why the delay affects the position size. This is the first time I’ve encountered this behavior in my systems.

I’ve attached the code I got from the forum.

/* GTAA - Global Tactical Asset Allocation with Out-Of-Market universe
**
** from: 
** A Quantitative Approach to Tactical Asset Allocation
** by Mebane T. Faber 2007, 2013
**
** source: http://papers.ssrn.com/sol3/papers.cfm?abstract_id=962461
**
** AmiBroker implementation by TrendXplorer
** www.trendxplorer.info
** trendxplorer@gmail.com
**
** latest version: March 5, 2017
**
** NB! Designed for monthly data 
*/

// --- begin of code ---
PosScore=0;
// --- inputs --- 
wln_bonds  = Param( "WatchListNumber for Bonds:" , 95, 0, 150, 1 );
wln_stocks = Param( "WatchListNumber for Stocks:", 96, 0, 150, 1 );
lookback   = Param( "MA lookback (m):", 10, 1, 24, 1 );

// --- detect tradelist ---
wlnumber        = GetOption( "FilterIncludeWatchlist" );
watchlist       = GetCategorySymbols( categoryWatchlist, wlnumber );

// --- detect stocks universe ---
stockslist      = GetCategorySymbols( categoryWatchlist, wln_stocks );
NumberOfStocks  = StrCount( stockslist, "," ) + 1;
NoS             = NumberOfStocks;

// --- detect bonds universe ---
bondslist       = GetCategorySymbols( categoryWatchlist, wln_bonds );
NumberOfBonds   = StrCount( bondslist, "," ) + 1;
NoB             = NumberOfBonds;

// --- top selection ---
PqS        = Param( "Number of Stock Positions:", 3, 1, NoS, 1 );
PqB        = Param( "Number of Bond Positions:" , 1, 1, NoB, 1 );

// --- inputs for return mix ---
w1m        = ParamToggle( "Include  1 month return?", "No|Yes", 1 );
w3m        = ParamToggle( "Include  3 month return?", "No|Yes", 1 );
w6m        = ParamToggle( "Include  6 month return?", "No|Yes", 1 );
w12m       = ParamToggle( "Include 12 month return?", "No|Yes", 1 );
   
// --- backtester settings ---
SetOption( "CommissionAmount", 0.00 );
SetOption( "InitialEquity", 100000 );
SetTradeDelays( 0, 0, 0, 0 ); 
SetOption( "MaxOpenLong", NoS );
SetOption( "MaxOpenPositions", NoS );
SetOption( "AllowPositionShrinking", True );
SetOption( "AllowSameBarExit", True ); 
SetOption( "ReverseSignalForcesExit", False ); 
SetOption( "HoldMinBars", 1 );
SetBacktestMode( backtestRegular );
SetOption("ExtraColumnsLocation", 1 ); 
RoundLotSize = 0.0001;

// --- init values ---
cashCount = cashScore = 0;

// --- asset selection and capital allocation routine ---
// based on https://groups.yahoo.com/neo/groups/amibroker/conversations/topics/178791
if ( Status( "stocknum" ) == 0 )
{
    StaticVarRemove( "SMA*"  );
    StaticVarRemove( "avg*"  );
    StaticVarRemove( "Pos*"  );
    StaticVarRemove( "Rank*" );
    StaticVarRemove( "cash*" );


	// --- stocks universe --- //

    for ( i = 0; ( symbol = StrExtract( stockslist, i ) )  != "";  i++ )
    {
        SetForeign( symbol );
        
			// --- load quotes ---
			Data     = Close;

			// ---  calculate SMA and trend filter ---
			SMA       = Sum( Data, lookback ) / lookback;
			SMAfilter = Data > SMA;
									
			// --- 1m, 3m, 6m and 12m and avg total return ---			
			stocksRet1m  = Nz( -1 + Data / Ref( Data, -1  ) );
			stocksRet3m  = Nz( -1 + Data / Ref( Data, -3  ) );
			stocksRet6m  = Nz( -1 + Data / Ref( Data, -6  ) );
			stocksRet12m = Nz( -1 + Data / Ref( Data, -12 ) );
			avgStocksRet = ( w1m * stocksRet1m + w3m * stocksRet3m + w6m * stocksRet6m + w12m * stocksRet12m ) / ( w1m + w3m + w6m + w12m );
						
		RestorePriceArrays();
		
		// --- store values ---
        StaticVarSet( "SMAfilter"    + symbol, SMAfilter    );
        StaticVarSet( "avgStocksRet" + symbol, avgStocksRet );
    }
    
    // --- generate ranks for stocks ---
    StaticVarGenerateRanks( "Rank_", "avgStocksRet", 0, 1224 );

    for ( i = 0; ( symbol = StrExtract( stockslist, i ) )  != "";  i++ )
    {
    	// --- retrieve stored values ---
    	Rank_avgStocksRet = StaticVarGet( "Rank_avgStocksRet" + symbol );
    	SMAfilter         = StaticVarGet( "SMAfilter"         + symbol ); 
    	
    	// --- count number of top ranked symbols below SMA filter ---
    	cashCount         = IIf( Rank_avgStocksRet > PqS, cashCount, IIf( SMAfilter, cashCount, cashCount + 1 ) );    	
        
        // --- allocate capital to top ranked symbols above SMA filter ---
		PosScore          = IIf( Rank_avgStocksRet <= PqS AND SMAfilter, Nz( 100 / PqS ), 0 );
		
		// --- store values ---		
		StaticVarSet( "PosScore" + symbol, PosScore );
	}
       
    // --- calculate allocation for out-of-market "cash" fund ---
    PosCash = cashCount * 100 / PqS; 
    
    // --- store value ---
    StaticVarSet( "PosCash"  , PosCash   );
    
            
	// --- bonds universe --- //

    for ( i = 0; ( symbol = StrExtract( bondslist, i ) )  != "";  i++ )
    {
        SetForeign ( symbol );
        
			// --- load quotes ---
			Data = Close;
			
			// --- 1m, 3m, 6m and 12m and avg total return ---			
			bondsRet1m  = Nz( -1 + Data / Ref( Data, -1  ) );
			bondsRet3m  = Nz( -1 + Data / Ref( Data, -3  ) );
			bondsRet6m  = Nz( -1 + Data / Ref( Data, -6  ) );
			bondsRet12m = Nz( -1 + Data / Ref( Data, -12 ) );
			avgBondsRet = ( w1m * bondsRet1m + w3m * bondsRet3m + w6m * bondsRet6m + w12m * bondsRet12m ) / ( w1m + w3m + w6m + w12m );
			
        RestorePriceArrays();
        
        // --- store values ---
        StaticVarSet( "avgBondsRet" + symbol, avgBondsRet );
    }

    // --- generate ranks for bonds ---    
    StaticVarGenerateRanks( "Rank_", "avgBondsRet", 0, 1224 );
    
    for ( i = 0; ( symbol = StrExtract( bondslist, i ) )  != "";  i++ )
    {		
		// --- retrieve values ---
		Rank_avgBondsRet = StaticVarGet( "Rank_avgBondsRet" + symbol );
		PosCash          = StaticVarGet( "PosCash"                   );
		
		// --- check if bond symbol is part of stockslist too ---
		_PosScore    = StaticVarGet( "PosScore" + symbol );
		PosScore     = IIf( Rank_avgBondsRet <= PqB, Nz( PosCash / PqB ), 0 );
		PosScore     = IIf( !IsNull( _PosScore ), PosScore + _PosScore, PosScore );
		
		// --- store values ---
		StaticVarSet( "PosScore" + symbol, PosScore );	
    }
}

// --- retrieve values ---
symbol    = Name();

if( StrFind( stockslist, Name() ) )
{
	avgReturn = StaticVarGet( "avgStocksRet"      + symbol ) * 100;
	PosScore  = StaticVarGet( "PosScore"          + symbol );
	Rank      = StaticVarGet( "Rank_avgStocksRet" + symbol );
}

if( StrFind( bondslist, Name() ) )
{
	avgReturn = StaticVarGet( "avgBondsRet"       + symbol ) * 100;
	PosScore  = StaticVarGet( "PosScore"          + symbol );
	Rank      = StaticVarGet( "Rank_avgBondsRet"  + symbol ) + 100;
}

// --- set position sizes ---
PositionSize = -PosScore; // "-" sign for spsPercentOfEquity

// --- re-balance at the end/close of every month ---
Buy          = PosScore > 0;
Sell         = 0;
Short        = Cover = 0;
BuyPrice     = Close;
SellPrice    = Close;

// --- exit at the end of the month for rebalancing ---
ApplyStop( stopTypeNBar, stopModeBars, numbars = 1, exitatstop = 0 );

// --- exploration filter ---
ExploreFilter = ParamToggle( "ExploreFilter", "LastBarInTest|All", 1 );
if ( ExploreFilter )
	Filter = 1;
else
	Filter = Status( "LastBarInTest" );

// --- sort for exploration only (not on backtest) ---
if ( Status( "actionex" ) == actionExplore ) 
{
	SetSortColumns( 2, -5, 4 );

	// --- columns for exploration ---
	ColorRet = IIf( avgReturn > 0, colorBrightGreen, colorRed );
	
	if( StrFind( stockslist, Name() ) ) PosColor = IIf( PosScore > 0, colorGold  , colorWhite );
	if( StrFind( bondslist , Name() ) ) PosColor = IIf( PosScore > 0, colorYellow, colorWhite );

	AddColumn( avgReturn, "Performance (%)", 3.3, 1, ColorRet );
	AddColumn( Rank     , "Rank"           , 1.0              );
	AddColumn( PosScore , "PosScore (%)"   , 3.3, 1, PosColor );
}


// --- end of code ---

To replicate the issue I’m describing, just modify line 52 to:

SetTradeDelays( 0, 0, 0, 0 );

And lines 195 and 196 to:

BuyPrice     = Open;
SellPrice    = Open;

Thanks for your help

You should just use Ref() on PositionSize variable to shift it one bar so it is in sync with delayed signals.

2 Likes

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.