AB Portfolio Rotation CBT Level 1

I confirm, after some experiment, that @fxshrat code (with CBT) seems do exactly what it has to do, with great precision.
While Positionscore solution doesn't work.

Sorry if i always tank you all for the precious contribute.
I do not have a particular need, my questions are just for improve my understanding and for the pleasure of studying ALF programming.
I'm feeling a passion that is growing inside for this language.

Sorry guys, I missed this statement in the Help documentation:

Blockquote
Exits are generated automatically when security's rank drops below "worst rank held" . There is no real control over when exits happen except of setting low score to force exits. You can also set the score on any (at least one) security to value of scoreNoRotate to prevent rotation (so already open positions are kept). But this is global and does not give you individual control.

So in fact you cannot exit individual positions using only the PositionScore.

1 Like

I haven't worked with Intraday data in Amibroker. So I can only comment without being able to test.

I edited your code and made some comments. And added some _Trace() statements.

Perhaps your DayExit and EntryTime parameters do not get passed to the CBT code without adding StaticVarSet()? It seems that dt, dNum, and dTime do get passed without adding StaticVarSet().

Also your code is only example (not actual code with actual symbols) and seems to only buy designed symbols on one date. Perhaps only one symbols has sufficient rank for buy on that date?

Hope below code helps. Comment and I am happy to help.

SetBacktestMode(backtestRotational);   

maxpos = 2 ;   
SetOption("InitialEquity",10000000);   
SetOption("AccountMargin",100);   
SetOption("MaxOpenPositions",maxpos);   
SetPositionSize(1,spsPercentOfEquity);   
Llb = 15;
Lti = (ROC(EMA(Close,5),Llb) + ROC(EMA(Close,10),Llb));
PositionScore = Max(Lti,0) ;
SetOption("WorstRankHeld",maxpos+2);   
dNum = DateNum(); //JG: dNum, dTime, and dt do get passed to CBT without StaticVarSet()???
dTime = TimeNum();
dt = DateTime();
// DayExit = TimeNum()==151500;  //JG: Do DayExit & EntryTime get passed to CBT? May not without StaticVarSet()?
// EntryTime = TimeNum()<151500; //JG: 151500 is 3:15pm local time

SetCustomBacktestProc("");

if(Status("action") == actionPortfolio )
{
  bo = GetBacktesterObject();
  bo.PreProcess();
  maxOpenPoss = GetOption("MaxOpenPositions");
  worstRankHeld = GetOption("WorstRankHeld");
  for(bar = 0 ; bar < BarCount; bar++)
  {
	   DayExit = dTime[bar]==151500;
	   for ( opPos = bo.GetFirstOpenPos(); opPos; opPos = bo.GetNextOpenPos() )
		{	// sell 
			thisOpenPos = bo.FindSignal(bar, opPos.Symbol, 0);
			if( IsEmpty( bo.FindSignal(bar, opPos.Symbol, 0) ) )  //JG: if this symbol not in signals, sell, lower than Worst Rank Held rank
			{ 	bo.ExitTrade(bar, opPos.Symbol, opPos.GetPrice(bar,"C"), 1); 
				_TRACE("WRH Sell " + opPos.Symbol + " Date: " + dt[bar] );
			}
			if( DayExit )  //JG: DayExit not passed to CBT?
			{ 	bo.ExitTrade(bar, opPos.Symbol, opPos.GetPrice(bar,"C"), 1);
				_TRACE("DayExit Sell " + opPos.Symbol + " Date: " + dt[bar] );
			}
		}
        EntryTime = dTime[bar]<151500;
        for ( sig = bo.getFirstSignal(bar); sig; sig = bo.GetNextSignal(bar) )
 	    {	// buy
 		    includeok = 0; //JG: in CBT includeok is scalar, not array?
			IF(sig.Symbol == "StockA"){includeok = dNum[bar] == 1110406;} // This DateNum() is 04/06/2011
			IF(sig.Symbol == "StockB"){includeok = dNum[bar] == 1110406;}
			IF(sig.Symbol == "StockC"){includeok = dNum[bar] == 1110406;}
	        if(bo.GetOpenPosQty() < maxOpenPoss   ) // buy if positions open less than maxOpenPoss
		    { 	inOpen = bo.FindOpenPos(sig.Symbol) ; // if already open, don't buy 
				// if (IsNull(inOpen) AND EntryTime[bar] AND includeok[bar]  ) bo.EnterTrade(bar, sig.Symbol, true, sig.Price, sig.PosSize) ; 
				if (IsNull(inOpen) AND EntryTime AND includeok  ) {
					bo.EnterTrade(bar, sig.Symbol, true, sig.Price, sig.PosSize) ;
					_TRACE("EntryTime includeok in " + sig.Symbol + " Date: " + dt[bar] ); 
				}
		    }
	    }       
        bo.UpdateStats( bar, 1 ); //JG: should be here, probably not earlier also?
        bo.UpdateStats( bar, 2 );
    }	
    bo.PostProcess();	
}

1 Like

I forgot to convert DateTime to string as shown in below _Trace() statement.

_TRACE("DayExit Sell " + opPos.Symbol + " Date: " + DateTimeToStr(dt[bar]) );

1 Like

Turn on "Pad and align" option in the settings. It is REQUIRED for rotational trading if you have data holes.

1 Like

Thanks for your reply. Still no change in the result. I think I've found what the issue is. When I run this code on stock data it runs properly. I was initially running it on futures data where there are a few data holes (not on the day its being back tested though ) in it because of the different expiries I intend to trade. Could that be a reason why its not taking trades properly?

Ranking sometimes not working (when you have holes in data) AFL Programming

Hi all, I have some ranking code that I intend to use to downsize the stock selection universe. In general it works, but sometimes it's not producing any trade. For example, this code works as expected: if (Status( "stocknum" ) == 0) { for (i = 0; ( symbol = StrExtract( watchlist, i ) ) != ""; i++) { SetForeign(symbol); rank = ROC(C, 20); RestorePriceArrays(); StaticVarSet("values" + symbol, rank); } StaticVarGenerateRanks("rank", "values", 0, 1224 ); } // ... and later, …

Came across this thread that suggests that any kind of ranking requires data to be aligned.
In that case, how does the code work fine when "EnableRotationalTrading()" irrespective of the data holes?

I had an AB "pad and align" problem a while back. But I had to disable "pad and align" to get it to work.

And AB documentation was not very helpful in finding problem. But I finally got it working.

So I don't know what to suggest. Your data is probably the problem. You will just have to continue trail and error.

Good Luck.

Here is my code,
i am face issue "market value zero" so no open position.
i was try last 2yr data for back test.
please help me to solve it.

SetBacktestMode( backtestRotational ); 
  
BuyPrice = Open; 
SetTradeDelays( 1, 1, 1, 1); 
  
Totalpositions = 20; 
SetOption("WorstRankHeld", 22); 
SetOption("MaxOpenPositions", Totalpositions ); 
setPositionSize (50000 , spsValue); 
//PositionSize = -100 / Totalpositions ; 
  
Score = ROC(EMA(Close,200), 15); 
Score = IIf(Score < 0, 0, Score ); 
stockDisqualified = (C < EMA(Close, 200)) OR (C > 9999) OR (score >22); 
PositionScore = IIf(stockDisqualified, Score , scoreNoRotate );  
StaticVarSet( "negROC_" + Name(), ROC(EMA(Close,200), 15) < 0 );
//StaticVarSet( "negROC_" + Name(), ROC(C, 20) < 0 );

SetCustomBacktestProc("");
if ( Status( "action" ) == actionPortfolio ) {
	bo = GetBacktesterObject();
	bo.PreProcess();    
	dt = DateTime();
	
	for ( i = 1; i < BarCount; i++ ) {  
		/// Exit open position of single symbol if ROC  
		/// of open position being negative one 
		/// @link https://forum.amibroker.com/t/ab-portfolio-rotation-cbt-level-1/8859/49 
		/// by fxshrat@gmail.com
		/// Commercial use prohibited!		
		for ( openpos = bo.GetFirstOpenPos(); openpos; openpos = bo.GetNextOpenPos() ) {	
			rc_neg = StaticVarGet( "negROC_" + openpos.Symbol );
			if ( rc_neg[ i/*-1*/ ] ) { // if negative ROC
				//_TRACEF("%s, i:%g", DateTimeToStr(dt[i]), i);
				sym = openpos.Symbol;
				price = Foreign(sym, "C");
				bo.ExitTrade( i, sym, price[i] ); // exit open pos. of neg. ROC symbol  					
			}
		} 
		bo.ProcessTradeSignals( i ); 		   
    }  
    
    bo.PostProcess();
}

It does not seem that you need CBT for this strategy. And the CBT in your code appears to combine mid-level and low-level CBT. I am not sure if it will work without recoding.

Below is non-CBT backtest code of your (with a few edits) code that I back tested with SP500 over 1/1/16 to 3/22/19, achieved ~ 11% CAR. As my understanding of AB documentation positionScore = 0 for a symbol on a date causes symbol (not portfolio) sell on that date. And negative positionscore for symbol initiates short (if abs(positionscore) has sufficient rank). Thus, add 1000 to position score, although probably not needed in this code.

Hope this helps.

typeSetBacktestMode( backtestRotational ); 
  
BuyPrice = Open; 
SetTradeDelays( 1, 1, 1, 1); 
  
Totalpositions = 20; 
SetOption("WorstRankHeld", 22); 
SetOption("MaxOpenPositions", Totalpositions ); 
//setPositionSize (50000 , spsValue); 
PositionSize = -100 / Totalpositions ; 
Score = ROC(EMA(Close,200), 15); 
Score = IIf(Score < 0, 0, Score ); 
stockDisqualified = (C < EMA(Close, 200)) OR (C > 9999) OR (score >22); 
PositionScore = IIf(stockDisqualified, Score + 1000 , 0 );  
// StaticVarSet( "negROC_" + Name(), ROC(EMA(Close,200), 15) < 0 );
//StaticVarSet( "negROC_" + Name(), ROC(C, 20) < 0 ); or paste code here
1 Like

Thanks JGunn sir,
ya it's working but i want to add CBT b'coz in falling market if ROC show negative then exit from that stock.
so in falling market we exit early and not add new position.

change as below (i.e., remove "+ 1000") in non-CBT. This exits or does not enter a symbol if score is <= 0. Don't have to worry about short since you have IIf(Score < 0, 0, Score ) .

If you insist, you can use my CBT code in earlier post and add a few lines of code.

PositionScore = IIf(stockDisqualified, Score , 0 ); 
1 Like

@mradtke, Can you please comment on your above " COMPLETE control ....... use Buy and Sell arrays as usual and force the "rotational" aspect from within the CBT."

Wouldn't we still need phase 1 buy & sell signals which can only be made with rankings, which we could do in phase 1. But using EnableRotationalTrading() AB does this for us and passes as signals.

I have been wondering about this, but keep dismissing, perhaps wrongly.

Thanks

Thank you sir.
and need your permission for use it.

Thanks Sir for give me permission to use your code.
and thanks for instant reply of my query.
Have a nice time Sir.

Yeah. Thats exactly what even I want to know. In almost all the examples I've seen, they all use EnableRotationalTrading(). And as @JGunn said, it passes on the signals to us.
@JGunn, How does one remove the "Pad and Align" issue? Because when I use EnableRotationalTrading(), it ranks and takes the trades properly on the same data set but when I use CBT, in takes different trades.

@JGunn: yes, you would still need to generate Buy and Sell signals and PositionScore values from Phase 1. That's what I meant by "use Buy and Sell arrays as usual". Once you have those signals in the CBT, you can do whatever you want including "rotation" which means forcing exits for stocks that no longer meet the ranking criteria but also processing other exit signals that you may have generated in Phase 1, for example to exit a stock with negative ROC on a non-rotation day, or rebalancing your positions at some regular interval (usually but not always the rebalance interval is the same as the rotation interval).

These latter tasks are not supported by EnableRotationalTrading(), so you have to decide if you want to take the easy route and accept the functionality that's offered or if you want to take the more difficult route so that you can implement the functionality that you want.

So can you use PositionScore variable without using EnableRotationalTrading()? And then control the rotational aspect from within the CBT?

Yes, PositionScore is used for all types of backtests, not just rotational mode tests. See the documentation here: https://www.amibroker.com/guide/h_ranking.html

What about "WorstRankHeld"? Does that have to be a part of your Sell signal in Phase 1 or should that be incorporated in the CBT?

WorstRankHeld is rotational mode only as documented http://www.amibroker.com/f?enablerotationaltrading . It would not make sense in regular mode as sells in regular mode are solely triggered by Sell variable.