Rotation Rebalance with Variable # of Positions

Objective: Create Buy/Hold with Rebalance Performance Benchmark of basket of tickers, where # active of tickers time varies.
Current Approach: StaticVarAdd # of tickers with filter on active. Rotation Trade with CBT example of rebalance.
Error: I believe error is sourced by the current approach in CBT expects a static value not an array based # of holdings (EachPosPercent). Any guidance on how to work around this problem to achieve this objective is greatly appreciated.

Background: In evaluating a quant strategy on a basket of securities, a best practice technique is to the benchmark the performance of this strategy against simply holding all the securities and periodically rebalancing. This version of the code does that (current using annual rebalance, but can easily be done with monthly, etc.). The challenge is that makes this harder than “standard” example solutions is that many baskets (watchlists) of tickers do not exist of the entire length of the backtest, so simply counting # of tickers as a fixed answer creates a process that does not keep yield fully exposure nor rebalanced.
Code Below.

_SECTION_BEGIN("Setup Options");

formn = ParamStr( "set name", "Eq Wtg Rebal" );
SetFormulaName(formn);

SetBacktestMode( backtestRotational );
SetOption("InitialEquity",1000000);
SetOption( "CommissionMode", 3 ); /* 3 set commissions $ per share; 1=% of trade */
	comamt  =Param("comamt shr", .01,.0,.1,.01);  // sw more conservative ver
	SetOption( "CommissionAmount", comamt ); /* per share  + SLIPPAGE */
SetOption("AccountMargin",100); 
SetOption("AllowPositionShrinking", True);  
RoundLotSize = 1;

RModeN = Param("0=Trade,1=det,2=sum,3=no", 1,0,3,1);   
	SetOption("PortfolioReportMode", RModeN );
_SECTION_END();

DBVOn = ParamToggle("DebugView","OFF|ON", 0);

// --- detect watchlist ---
wlnumber        = GetOption( "FilterIncludeWatchlist" );
watchlist       = GetCategorySymbols( categoryWatchlist, wlnumber );
numberOfSymbols = StrCount( watchlist, "," ) + 1;
NoS             = numberOfSymbols;

SetOption("MaxOpenPositions", NoS);

 ActiveTicka =ActiveTicka2=SmpleadjCt=SmpleadjCtx =0;
 
if ( Status( "stocknum" ) == 0 )    
	{ 
	
		{ 		 
			wlist = GetOption("FilterIncludeWatchlist"); 
			 
			symlist = CategoryGetSymbols( categoryWatchlist, wlist ) ; 
			 
			StaticVarRemove("*");  // delete static variables 		
		} 
		 
	for( i = 0; ( symbol= StrExtract( symlist, i ) ) != ""; i++ ) 
		{ 
		SetForeign(symbol); 
		
		Symctact = iif(isNull(c),0,iif(c>0.01,1,0)); // Filter on active ticker
		if( DBVOn ) _TRACE("Symctact p1= " + Symctact);
		
		StaticVarAdd( "~Symctact", Symctact );		
		if( DBVOn ) _TRACE("Symctact vadd= " + Symctact);
		
		StaticVarAdd( "~SymbolCount", 1 );
		
		RestorePriceArrays(); 
		} 			
	} 
	
	SmpleadjCtx = staticvarget("~Symctact");  // Using active tickers
	
Symbol = Name();

PositionSize = nz(-100/SmpleadjCtx);	// Equally divide capital among the positions held  - works
EachPosPercent = 100/SmpleadjCtx;

BuyPrice = C;
SellPrice = C;

		Firstdayoftheyear = year()!= ref(year(),-1);
		Lastdayoftheyear = year()!= ref(year(),1);
									
		dt = DateTime();
		Start = dt[ 0 ] ;
		buyok = (Firstdayoftheyear OR Start) and c>0;
		Rebal = (Lastdayoftheyear OR Start) and c>0;
  
_SECTION_BEGIN("trade"); 
	PScore = ROC( C, 1 ); 
	PositionScore = IIf(Rebal, PScore, scoreNoRotate) ; 
  
_SECTION_END();   
//

Filter = buyok OR Rebal;

AddColumn( C  , "C"          , 3.2 );
AddColumn( NoS  , "numberOfSymbols"          , 3.0 );
Addcolumn(StaticVarGet ("~SymbolCount"), "~SymbolCount", 3.0);
Addcolumn(StaticVarGet ("~Symctact"), "~Symctact"); // works
AddColumn( SmpleadjCtx  , "SmpleadjCtx"          , 3.0 ); // works
AddColumn( PositionSize  , "PositionSize"          , 3.0 );

SetOption("UseCustomBacktestProc", True ); 

if( Status("action") == actionPortfolio )
{
  bo = GetBacktesterObject();
 
  bo.PreProcess(); // Initialize backtester
 
  for(bar=0; bar < BarCount; bar++)
  {
   bo.ProcessTradeSignals( bar );
  
   CurEquity = bo.Equity;
  
   for( pos = bo.GetFirstOpenPos(); pos; pos = bo.GetNextOpenPos() )
   {
    posval = pos.GetPositionValue();
   
    diff = posval - 0.01 * EachPosPercent * CurEquity;
    price = pos.GetPrice( bar, "O" );
   
    if( diff != 0 AND
        abs( diff ) > 0.005 * CurEquity AND
        abs( diff ) > price )
    {
     bo.ScaleTrade( bar, pos.Symbol, diff < 0, price, abs( diff ) );
    }
   }
  }
  bo.PostProcess(); // Finalize backtester
}
1 Like

Have your read Tom Cover's book/paper on Universal Portfolio? Constant Rebalance, but with optimized symbol weighting.

Thanks for the thought; I actually have a pretty deep and wide concept and structural knowledge of a large array of portfolio algorithms (MinCorr, Maxsharpe, risk parity, eq weight risk, target return, target risk, min co-variance, hierarchical cluster, momentum, factor regime, GTAA, Eff frontier, etc). However, my ability to write advanced custom code (multi-layer loops etc) from scratch is quite weak. I am available to chat about it live at some point if you are interested.

Would like to chat live. Don't see email or contact info for you?

I kindly ask if anyone in the community can point me towards a solutions to my problem/error, as I am stuck?

Please let me know if I can provide any clarification.
Thanks!

It is simple - use Knowledge Base example here http://www.amibroker.com/kb/2014/10/23/how-to-exclude-top-ranked-symbols-in-rotational-backtest/ that shows how to SKIP signals (by skipping some of signals you can adjust number of positions open)
The number of excluded symbols may vary on bar-by-bar basis.

Much appreciated Tomasz
I will read it carefully to see how I can apply to position sizing formula.