Rotational strategy that stays 100% invested in stocks with ROC < 20

I am looking for a sample script that will demonstrate how to remain 100% invested in stocks that have an ROC less than 20. I feel like I'm trying to over complicate it.

This sort of comes close:
http://www.amibroker.com/kb/2006/03/06/re-balancing-open-positions/

and this:

But I really need a sample code accomplishing the subject line here. Could someone please provide?

@portfoliobuilder suggested this to me:
"I once saw someone code without the CBT a "simulated" rebalance. By that I mean on each Rotation day (in the example I think it was Monthly) they sold all positions, and then started from scratch by re-purchasing all the qualifying stocks in their proper size. If you don't count commissions/slippage this simulates the re-balancing of the portfolio."

So, I was able to adjust my code to accomplish this. The entry & exit code is a super simple loop with the exit happening first, setting my marketposition variable to flat, which allows another entry right under it in the loop. It works on a chart, but on the backtest it is only taking entries every other bar. It will not exit first on a bar, then enter again, like it does on the chart. I cannot find the setting that controls this. I thought it might be

SetOption("AllowSameBarExit",false);

but I've tried this both ways, true and false, and it does not help. I've also tried every backtestmode.

What setting in the backtester will make it act like the chart, and take exits, then entries on the same bar?

Not sure I understand why you have a loop. If it's in the Phase 1 (non-CBT) portion of your code, it doesn't matter whether the Buy is "right under" the Sell, because the entire Buy array and entire Sell array will be set before AB executes either its own built-in back test or your CBT.

As always, it would help others to help you if you posted some code.

I have over simplified the actual strategy for the sake of this forum. The loop is needed for a more complex version.

Here is the simplified version's code:


_SECTION_BEGIN("Price");
SetChartOptions(0,chartShowArrows|chartShowDates);
_N(Title = StrFormat("{{NAME}} - {{INTERVAL}} {{DATE}} Open %g, Hi %g, Lo %g, Close %g (%.1f%%) {{VALUES}}", O, H, L, C, SelectedValue( ROC( C, 1 ) ) ));
Plot( C, "Close", ParamColor("Color", colorDefault ), styleNoTitle | ParamStyle("Style") | GetPriceStyle() ); 
_SECTION_END();


InitialCap = Param("InitialCapital",100000,100000,10000000,1000);

DoCounts = Param("DoCounts",0,1,1,1);
ROC_Threshold = Param("ROC_Threshold",0,-100,100,1);


if ( Status("stocknum") == 0) 
{ 
	if (DoCounts)
	{
		wlnum = GetOption( "FilterIncludeWatchlist" ); 
		List = CategoryGetSymbols( categoryWatchlist, wlnum ) ; 

		StaticVarRemove( "score*" ); 
		StaticVarRemove( "~AboveCnt*" ); 
		StaticVarRemove( "~BelowCnt*" ); 
		StaticVarRemove( "~CrxAboveCnt*" ); 
		StaticVarRemove( "~CrxBelowCnt*" ); 
		StaticVarRemove( "~SymbolCt*" ); 
		
		for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    ) 
		{ 
			SetForeign(Symbol);
			myScore = Nz(ROC(Close, 15), -9999);
			StaticVarSet (  "score"  +  symbol, myScore ); 
			
			ActiveSymbol = myScore > -9999; 
			StaticVarAdd( "~SymbolCt", ActiveSymbol );
			
			ScoreAbove = myScore > ROC_Threshold AND ActiveSymbol;
			ScoreBelow = myScore <= ROC_Threshold AND ActiveSymbol;
			
			ScoreCrxAbove = myScore > ROC_Threshold AND Ref(myScore, -1) <= ROC_Threshold AND ActiveSymbol;
			ScoreCrxBelow = myScore <= ROC_Threshold AND Ref(myScore, -1) > ROC_Threshold AND ActiveSymbol;

			StaticVarAdd( "~AboveCnt", ScoreAbove );
			StaticVarAdd( "~BelowCnt", ScoreBelow );
			StaticVarAdd( "~CrxAboveCnt", ScoreCrxAbove );
			StaticVarAdd( "~CrxBelowCnt", ScoreCrxBelow );
			
			RestorePriceArrays();
		} 
	}

} 

myROC = ROC(Close, 15);
ROCscore = Nz(StaticVarGet ( "score" +  Name() ), -9999); 
AboveCnt = StaticVarGet( "~AboveCnt" );
BelowCnt = StaticVarGet( "~BelowCnt" );
CrxAboveCnt = StaticVarGet( "~CrxAboveCnt" );
CrxBelowCnt = StaticVarGet( "~CrxBelowCnt" );
SymbolCt = StaticVarGet( "~SymbolCt" );
Diff = CrxBelowCnt - CrxAboveCnt;

printf("======================" + "\n");
printf("myROC: " + NumToStr(myROC, 1.3) + "\n");
printf("ROCscore: " + NumToStr(ROCscore, 1.3) + "\n");
printf("SymbolCt: " + NumToStr(SymbolCt, 1.0) + "\n");
printf("AboveCnt: " + NumToStr(AboveCnt, 1.0) + "\n");
printf("BelowCnt: " + NumToStr(BelowCnt, 1.0) + "\n");
printf("CrxAboveCnt: " + NumToStr(CrxAboveCnt, 1.0) + "\n");
printf("CrxBelowCnt: " + NumToStr(CrxBelowCnt, 1.0) + "\n");
printf("Diff: " + NumToStr(Diff, 1.0) + "\n");


Buy = 0;
BuyPrice = 0;
Sell = 0;
SellPrice = 0;
Short = 0;
Cover = 0;

mydatetime = DateTimeToStr(GetCursorXPosition());

bi = BarIndex();
dn = DateNum();
tn = TimeNum();
dow = DayOfWeek();
SelectedDN = SelectedValue(dn);
SelectedTN = SelectedValue(tn);


SetBarsRequired( sbrAll, sbrAll );
SetOption("MaxOpenPositions", 9999);
SetOption("AllowPositionShrinking", True);
SetOption("InitialEquity", InitialCap);
SetOption("AllowSameBarExit",false);
SetBacktestMode( backtestRegularRaw );

myPosSize = IIf(BelowCnt > 0, -100/BelowCnt, 0);
PositionSize = myPosSize;


for (i = 0; i < BarCount; i++)
{	
	if (i == 0 )
	{
		mp = 0;
		BarsInTrade = 0;
		MarkEntryPrc = 0;
	}
	else
	if (i > 1 AND i < BarCount - 1)
	{
		// Exit first if we have a position
		if ( mp == 1 )
		{
			BarsInTrade += 1;
			
			if (BarsInTrade >= 1)
			{		
				if ( mp == 1 )
				{
					Sell[i] = 1;
					SellPrice[i] = Close[i];
					
					mp = 0;
					BarsInTrade = 0;
					MarkEntryPrc = 0;
				}
			}
		}	
		
		// And then entry next 
		if (mp == 0)
		{
			if (myROC[i] <= ROC_Threshold)
			{
				Buy[i] = 1;
				BuyPrice[i] = Close[i];
				
				mp = 1;
				MarkEntryPrc = Close[i];
			}	
		}
	}	
}


printf(mydatetime + "\n");
printf("------------------" + "\n" ); 


PlotShapes(IIf(Buy, shapeUpArrow, shapeNone),colorBlue,  0, BuyPrice, Offset=-15); 
PlotShapes(IIf(sell, shapeDownArrow, shapeNone),colorYellow, 0, SellPrice, Offset=-15);

PlotShapes(IIf(Short, shapeDownArrow, shapeNone),colorPink,  0, ShortPrice, Offset=-15); 
PlotShapes(IIf(Cover, shapeUpArrow, shapeNone),colorWhite, 0, CoverPrice, Offset=-15);

see in the graphic below how it is not taking trades as the chart does:
image

After I figured out that you had disabled all the counting (and thus position sizing) using Param variables (thanks for that!), I was able to run your code. Changing this line:

SetOption("AllowSameBarExit",false);

To this:

SetOption("AllowSameBarExit",true);

I see lots of consecutive, one day trades that appear to match the chart. Are you seeing something different when you make that code change?

Yes! Awesome! I could've sworn I did this a bunch of other times and it did not work.... thank you for your time Matt!

If anyone has a more elegant solution to the original post, it would be greatly appreciated

I haven't tested it, but I think your entire bar-bay-bar loop could be replaced with:

Buy = myROC[i] <= ROC_Threshold;
Sell = Ref(Buy,-1);

Yeah, I'm aware of that. As I wrote earlier, this is a simplified version of a more complex project that I stripped for sake of the forum.

Short of writing a CBT so that you wouldn't have to count all the qualifying symbols first, what type of more elegant solution were you looking for?

Remove ALL your code.

Rotational trading that stays 100% invested in socks with ROC< 20 is done in few simple lines without ANY loops, without custom backtester, without static variables, using just EnableRotationalTrading and nothing more. Just read the manual and use the SIMPLEST things. The entire logic is just SINGLE line (PositionScore).
http://www.amibroker.com/guide/h_portfolio.html
and
http://www.amibroker.com/f?enablerotationaltrading

EnableRotationalTrading();
SetOption("WorstRankHeld",10);
SetOption("MaxOpenPositions", 10 );

myROC = ROC( Close, 15 );
myROCThreshold = 20;

PositionSize = -10; // invest 10% of equity in single security

// only enter symbols with ROC below threshold - score of 0 means NO entry
// and prefer lowest ROC symbols
PositionScore = IIF( myROC < myROCThreshold, 1000 - myROC, 0 );
2 Likes

Thank you for weighing in Tomasz. I do not want to cap the number of positions to just 10. I want to invest in ALL stocks in my watchlist that are under an ROC of 20. So I believe I still need the top portion that counts how many stocks qualify on a given day, so that I know how many positions to divide by in order to determine position sizing for that day.

You can count them if you like, but if you do not limit the number of positions you are going to kill all your profits by paying enormous commissions since there are likely to be 4000+ of stocks with ROC < 20 (at least in US). Even super low IB commmissions you will be paying > $4000 in commissions to enter 4000 symbols. Not mentioning that to get low slippage you would need to buy at least 100 shares (or multiplies) per symbol which means you would need to have some really big account. Limiting number of max open positions to realistic number (such as less than 100) is much better idea.

Also even if you insist on doing your way, you don't need to use all the code you wrote. You can just count using single StaticVarAdd and something like 4-5 lines of code.

Understood. But as I wrote earlier in this post, this is a pseudo setup for the sake of simplicity for the forum. The actual rule is much more complex and will not allow as many through. Also, my watchlist may be only a few hundred stocks.

And yes, the extra counts were done because I was experimenting on exactly how to pull this off.....

Question, lets say on bar 1 we enter 100 stocks, and on the next bar only 90 qualify, so it sells 10 of the positions, and enters no more. Does it redistribute the unallocated dollars at this point to the 90 that are still in?

Program only does what it is told to do and nothing more. So it does not "redistribute" (generate new trades without being told precisely to do so). Instead qualifying positions are kept and not qualifying positions are closed. It is controlled by WorstRankHeld setting and it is described in the manual http://www.amibroker.com/f?enablerotationaltrading.