How to imitate IBD Relative Strength Percentile Ranking of Stocks

I tried to get it working, but have trouble to get reproducable results.

PART ONE: Trying to run the original code by rocketpower (post #1)

I changed rocketpowers original code only slightly to run it as exploration (refer to first line in this code). I also left the last line as "StaticVarRemove("RankRSraw*_");", altough the * should be placed after the underline.

// Relative Strength ranking of stocks by @rocketpower, modified by @RioL(line 4-6 commented out the market lists, line 11 changed to testing list, changed output watchlist in line 45 and 47, in last line 53 is the * still in front of the "_" - should be afterwards)

// Choose markets to include in the universe (I only use NYSE Market and delisted databases for backtesting hence why they are commented out)
//listNYSE = CategoryGetSymbols(categoryMarket, 1);
//listNSDQ = CategoryGetSymbols(categoryMarket, 2);
//listARCA = CategoryGetSymbols(categoryMarket, 3);
//listNMKT = CategoryGetSymbols(categoryMarket, 4);
//listDLST = CategoryGetSymbols(categoryMarket, 14);

// Create the full list of stocks to be ranked
List    = CategoryGetSymbols(categoryWatchlist, 3); //list with 439 symbols for testing
ListQty = StrCount(List, ",") + 1; 
// Determine the quantity of stocks to rank
StaticVarSet("UniListTotal", ListQty, True);

// Clear the static vars from last run
StaticVarRemove( "RS_*" ); 

// Generate the raw RS score for every stock and store in a static var
for (n = 0; (Symbol = StrExtract(List, n)) != "";  n++) 
{ 
    SetForeign (Symbol); 
    // relative strength IBD style
	ThreeMthRS  = 0.4*(C/Ref(C,-63));
	SixMthRS    = 0.2*(C/Ref(C,-63*2));
	NineMthRS   = 0.2*(C/Ref(C,-63*3));
	TwelveMthRS = 0.2*(C/Ref(C,-63*4));
	RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthRS; 
    RestorePriceArrays(); 
    StaticVarSet("RSraw_"  +  Symbol, RSraw); 
} 

// rank the stocks using this extremely useful function!
StaticVarGenerateRanks("Rank", "RSraw_", 0, 1224); 

// Convert the static var ranks generated into a percentile score.
for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
{
	Rank  = StaticVarGet ("RankRSraw_" +  Symbol); 
	RSpctile = 100 - 100*Rank/ListQty;
	StaticVarSet("RS_" + Symbol, RSpctile, True);
	
	// use this opportunity to store the highest ranking stocks in a watchlist.
	if (LastValue(RSpctile) >= 95)
		CategoryAddSymbol(   Symbol, categoryWatchlist, 62);
	else
		CategoryRemoveSymbol(Symbol, categoryWatchlist, 62);
}

// remove unnecessary static data
// free up old stuff
StaticVarRemove("RSraw_*");
StaticVarRemove("RankRSraw*_");

I did run it with these settings and set as filter the watchlist number 3 as in the code and cleared my output watchlist number 62.
image
After pressing "Explore" I exported the watchlist 62 and cleared it again to run the exploration again, comparing the tickers from run 1 and run 2 it showed both lists do have 21 symbols, but each list has some unique symbols! My expectation was: Both lists have the same symbols, the order might vary due to multithreading.
Correcting the last line to

StaticVarRemove("RankRSraw_*");

does not help (still 21 symbols as result, but some symbols are not reproducible). And neither does it help to activate the "Pad and align ..." option as suggested by the info-tab of the exploration. I tried it with ^DJI (21 symbols as result, some unique) and NEON, a ticker from my test-watchlist, which was also part of the previous result lists (once 17 symbols and one 19 symbols as result, some unique).
image

PART TWO: Trying to debug it with exploration tables to find my mistake

With the help of comment #2 by @beppe I tried some more, but was not able to get my desired result of always getting the same tickers and the same RSpctile-value with the origianl code.

I sort of got an acceptable result after splitting the code into two parts and run them one after the other as a exploration like described earlier. The results are now always the same for the tested date and my testlist. I also tested it for all my 10662 tickers: Adjusted the variable "List" in the following code "part 1" and set the exploration to "All symbols" with a defined date, with "1 recent bar(s)" and with "1 recent day(s)". In all cases the exploration took less than a minute (Intel Core i7 7th Gen, 8 threads used by AmiBroker) and the first and the second run had the same identical results.

Here are the two exploration codes:

// Relative Strength ranking of stocks part 1
// code by rocketpower, modified by RioL
// run in Exploration window, set the same ticker list (see variable "List") as a filter.

Filter = 1; // all quotes and symbols are accepted

// Determine the quantity of stocks to rank
if( Status( "stocknum" ) == 0 )
{
	// Choose markets to include in the universe (I only use NYSE Market and delisted databases for backtesting hence why they are commented out)
	//List = CategoryGetSymbols(categoryAll, 0);
	List = CategoryGetSymbols(categoryWatchlist, 3);
	ListQty = StrCount(List, ",") + 1; 
	StaticVarSet("UniListTotal", ListQty, True);
	
	// Clear the static vars from last run (the second and third are only integrated to ensure a proper start in case the last run was aboarded)
	StaticVarRemove(       "RS_*"); 
}

AddColumn( StaticVarGet("UniListTotal", True), "UniListTotal" );
AddTextColumn( MarketID( 1 ), "Market name" );
AddColumn( Close, "Close" );

// Generate the raw RS score for every stock and store in a static var
// relative strength IBD style
ThreeMthRS  = 0.4*(C/Ref(C,-63));
SixMthRS    = 0.2*(C/Ref(C,-63*2));
NineMthRS   = 0.2*(C/Ref(C,-63*3));
TwelveMthRS = 0.2*(C/Ref(C,-63*4));
RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthRS; 
AddColumn( RSraw, "RSraw", 1.2 );
StaticVarSet("RSraw_"  +  Name(), RSraw); 

SetSortColumns(-6,1); 
// Relative Strength ranking of stocks part 2
// code by rocketpower, modified by RioL
// run in Exploration window, set the same ticker list (see variable "List" in part 1) as a filter.

Filter = 1; // all quotes and symbols are accepted

if( Status( "stocknum" ) == 0 )
{
// rank the stocks using this extremely useful function!
StaticVarGenerateRanks("Rank", "RSraw_", 0, 1224); 
}

AddTextColumn( MarketID( 1 ), "Market name" );
AddColumn( Close, "Close" );
AddColumn(StaticVarGet("RSraw_"  + Name()), "RSraw_StaticVar");
AddColumn(StaticVarGet("RankRSraw_"  + Name()), "RankRSraw_StaticVar");

// Convert the static var ranks generated into a percentile score.
Rank  = StaticVarGet ("RankRSraw_" +  Name()); 
RSpctile = 100 - 100*Rank/StaticVarGet("UniListTotal", True);
AddColumn( RSpctile, "RSpctile" );
StaticVarSet("RS_" + Name(), RSpctile, True);

SetSortColumns(-7,1);

/*// Commented out, because it causes nearly empty columns for "RankRSraw" and "RSpctile" - only one ticker shows results.
// remove unnecessary static data
// free up old stuff
if( Status( "stocknum" ) == 0 )
{
	StaticVarRemove(    "RSraw_*");
	StaticVarRemove("RankRSraw_*");
}
*/

Thanks for taking the time to read this long post, I'll appreciate any input.

@rocketPower Thank you for sharing. I got some question and some sharing on

Question

  1. Where do you get ARCA data from ?
  2. From only 3 market, do you get the same RS Rating score as shown in marketsmith.com ?

Sharing

  1. Marketsmith uses
    -AMEX (247 stocks)
    -ARCA (1,675)
    -BATS (511)
    -NASDQ (3,846)
    -NYSE (2,606)
    -OTC (1)
    -OTCBB (1)

to calculate IBD RS Rating

Also I just wonder don't you use this Rate of Change in the calculation ?

		ThreeMthRS  = 0.4*((C-Ref(C,-3))/Ref(C,-3));
		SixMthRS    = 0.2*((C-Ref(C,-6))/Ref(C,-6));
		NineMthRS   = 0.2*((C-Ref(C,-9))/Ref(C,-9));
		TwelveMthRS = 0.2*((C-Ref(C,-12))/Ref(C,-12));
		RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthRS; 
1 Like

Hello, I use Norgate Data. It works the best..

and use I know I can use ROC, but it was just typed out that way, that day :slight_smile:

1 Like

I have stopped using IBD and their ratings for a while now but I just wanted to drop a quick note here to give my 2 cents just in case it can help anymore...

IMO the IBD method of calculating relative strength rankings is flawed because it used the the rate of change which looks at ending value, beginning value and ignores everything in between. They try to remedy this flaw by adding multiple periods which is a little better but still flawed.

I think a better method would be to use a normalized linear regression slope with a goodness-of-fit measure to ensure all data points in the period get taken into account.

Good trading every one, stay sharp out there :crossed_swords:

2 Likes

@rocketPower
Agreed, Norgate Data is great and a no-brainier, especially with AB.

@shavedlemon Yes I agree with you with respect with the sensitivity to start and stop dates for the ROC calculations. An interesting take on this topic is Ed Seykota's "Trend Lag" calculations...

https://www.seykota.com/tribe/TSP/Trends/index.htm

FYI, you have to fix calculation step that looks like this...
= $1.60 / day / $12
to...
=$1.60 / day / ABS($12)

so that is can play well with negative prices that you can sometimes get in futures.

Ho :santa:

-S

2 Likes

Thank you @Sean, I will look into this... I always enjoy testing new things.

My preferred method, and I am very open to criticism, is to use the built-in Amibroker linear regression tools because they have the benefit of including all data points during the period and not just the beginning and end points.

One way to normalize the LinReg to make to comparable across different securities is:

nSlope1=(LinearReg(Close,Period)/LinRegIntercept(Close,Period));

Which divides the end point of the LinReg by the start point... and then multiply this number by a goodness of fit measure like for example:

gof=Correlation(BarIndex(),Close,Period)^2;

The highest the goodness of fit it is the closer the final number will be to the normalized slope... I added two examples below.

Since our aim in this thread is to compare securities against one another to come up with a rank or rating I believe this is better that using ROC.

Stay sharp out there !

Screenshot 2021-12-10 102157
Screenshot 2021-12-10 102233

5 Likes
  • This is very interesting indeed on using Linear Regression. Would it possible to explain more on your gof and nSlop1 in layman term ? and Also whey nSlope1 must be multiplied by gof ?

  • How about the point of the weight the latest quarter more than others ? Does this LR taken into account on this weight ?

1 Like

My pleasure @shavedlemon, I would always suggest looking at Seykota's Systems Project even though it stopped ages ago and never proceeded further. His examples get an A+ for clarity, simplicity and update-ability (meaning when you get next bars data).

Linear regression has the same start/end implications and lag price action, but perhaps less so when comparing 2 points.

Assuming you wish higher ROC rather than just smooth price action along the best fit line, then you need to have a way to volatility adjust the instruments without "Throwing the baby out with the bathwater". Staying on the take of Seykota, you might want to look at the "Lake Ratio" that he terms and perhaps apply it to the instrument level for some type of adjustment to your ROC ranking etc...
https://www.seykota.com/tribe/risk/index.htm

2 Likes

@sean, thanks for the pointers... I am going to do some digging here. How would you go about ranking stocks by including all data points in a specific period and adjusting for volatility?

@lertsak

I would read up a little bit about that online there are plenty of resources available... if you had to draw a line that would be the "closest" on average to all data points (closes) that would be the linear regression line... after that it's just a matter of dividing the end point by the starting point to get that slope as ratio rather than a dollar number (which is what the built-in function returns).

However not all linear regression lines are created equal (here, for our intents and purposes)... some are "closer" on average to the majority of data points... which indicates smoother price action. That's where multiplying the goodness of fit measure comes into play... it penalizes stocks that are more volatile on average around that best-fit line.... so for two equal normalized slopes the one with the smallest average deviations with rank the highest.

Just FYI for whoever reads this thread, using Linear Regs can be slow depending on how many symbols you have in your database so a faster way would be to calculate the ROC of a MA and rank the results.

2 Likes

I'm not sure I would adjust for volatility explicitly. What you can do is make a kind of a of composite ROC with different "Lag" lengths (or all lengths in a range theoretically). Trick here is not to make it a straight even average, but use a proportion. Remember the "Lag" length is turned into an annualized ROC etc.

1 Like

Thanks for your share!

1 Like

Reproducing a modifed code to get an indicator

// Relative Strength ranking of stocks
// This code must be run in Analysis Engine. 

// watchlist should contain all symbols included in the test
wlnum = GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;
ListQty = StrCount(List, ",") + 1; 
// Determine the quantity of stocks to rank
StaticVarSet("UniListTotal", ListQty, True);
 
// Bars in consideration

Barsno=Param( "Bars", 244, 1, 6100 );
Bars4 = Barsno / 4;
Bars4Num = LastValue(Bars4);

 
if( Status( "stocknum" ) == 0 )
{
	// Clear the static vars from last run
	StaticVarRemove( "RS_*" );
	StaticVarRemove("RSraw_*");
	StaticVarRemove("RankRSraw*_"); 

	// Generate the raw RS score for every stock and store in a static var
	for (n = 0; (Symbol = StrExtract(List, n)) != "";  n++) 
	{ 
		SetForeign (Symbol,0); 
		RSraw = 0;
		
		// relative strength IBD style
		ThreeMthRS  = 0.4*(C/Ref(C,-Bars4Num));
		SixMthRS    = 0.2*(C/Ref(C,-(2*Bars4Num)));
		NineMthRS   = 0.2*(C/Ref(C,-(3*Bars4Num)));
		TwelveMthRS = 0.2*(C/Ref(C,-(4*Bars4Num)));
		RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthRS; 
		RestorePriceArrays(); 
		StaticVarSet("RSraw_"  +  Symbol, RSraw,True); 
		RSraw  = StaticVarGet ("RSraw_" + Name()); 
				
	} 
		//RSraw  = StaticVarGet ("RSraw_" + Name()); 

	// rank the stocks using this extremely useful function!
	StaticVarGenerateRanks("Rank", "RSraw_", 0, 1224); 
    
    /*
	**** If you want to automaticlly store the result in watch list, uncomment the following lines. ***
	Create result watchlist based on market of first symbol on list. Default is US market
	firstSym=StrExtract(List, 0);
	marketSym=StrExtract(firstSym, 1, '.');
	resultwl = -1;

	if ((marketSym == "SZ") OR (marketSym == "SS"))
		resultwl = CategoryCreate( "Top Rank China Stocks", categoryWatchlist ); 
	else if (marketSym == "HK")
		resultwl = CategoryCreate( "Top Rank HK Stocks", categoryWatchlist );
	else
		resultwl = CategoryCreate( "Top Rank US Stocks", categoryWatchlist ); 
    */	 

	// Convert the static var ranks generated into a percentile score.
	for (n = 0; (Symbol = StrExtract(List, n))  != "";  n++) 
	{
		Rank  = StaticVarGet ("RankRSraw_" +  Symbol); 
		RSpctile = 100 - 100*Rank/ListQty;
	
		StaticVarSet("RS_" + Symbol, RSpctile, True);
	
		/**** If you want to automaticlly stores the result in watch list, uncomment the following lines. ***
		 use this opportunity to store the highest ranking stocks in a watchlist.
		if (LastValue(RSpctile) >= 95)
			CategoryAddSymbol(Symbol, categoryWatchlist, resultwl);
		else 
			CategoryRemoveSymbol(Symbol, categoryWatchlist, resultwl);
		*/
	}

}

Rank  = StaticVarGet ("RS_" +  Name()); 

function GetRS(Symbol)
{
	// Retrieve relative strength static var
	return StaticVarGet ( "RS_" +  Symbol ); 
}



// exploration code for verification

		
Filter= 1;
AddColumn( Close, "C",1.2 );
AddColumn(Ref(C,-Barsno),"StartC",1.2);
rankcolor = PercentRank( Close, 100 );
Color = ColorHSB( rankcolor * 64/100, 255, 255 );
AddColumn(GetRS(name()),"IBD Rank",1.2);
AddColumn( rankcolor, "Percent rank", 1.2, colorDefault, Color, -1, rank );

/*
rankcolor = PercentRank( Close, 100 );
Color = ColorHSB( rankcolor * 64/100, 255, 255 );
AddColumn(GetRS(name()),"IBD Score",1.2);
AddColumn(Rank,"Rank",1.2);
AddColumn( rankcolor, "IBD Percentile", 1.2, colorDefault, Color, -1, rank );
*/


if( Status( "Action" ) == actionExplore ) SetSortColumns(-6,-5);

//Plot Rank on Chart
Plot(GetRS(name()),"IBD Rank 1Year",colorRed,styleLine);

BuyCfn=Cross(GetRS(name()),70);
SellCfn=Cross(70,GetRS(name()));

shapebuy=IIf(BuyCfn,shapeUpArrow,shapeNone);
shapesell=IIf(SellCfn,shapeDownArrow,shapeNone);

PlotShapes(shapebuy,colorGreen,0,Graph0,-50);
PlotShapes(shapesell,colorRed,0,Graph0,-50); 


sdt = SelectedValue( DateTime() ); 

/*Title = "{{NAME}} -{{DATE}} - {{VALUES}} TOP: " + RankColoR;
// + " BOT: " + StaticVarGetRankedSymbols( "bot", "ValuesToSort", sdt ) ; 
*/
        
GfxSetOverlayMode(0);
GfxSetZOrder(-4);
GfxSelectFont("Tahoma", Status("pxheight")/5 );
GfxSetTextAlign( 6 );// center alignment
GfxSetTextColor( ColorRGB( 200, 200, 200 ) );
GfxSetBkMode(1); // transparent
GfxTextOut( WriteVal(rank,1,1), Status("pxwidth")/2, Status("pxheight")/12 );

Plot(10,"10 L",colorBlack,styleLine | styleNoTitle);
Plot(30,"30 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(50,"50 L",colorBlack,styleLine | styleNoTitle);
Plot(70,"70 L",colorBlue,styleLine | styleNoTitle | styleDashed);
Plot(90,"90 L",colorBlack,styleLine | styleNoTitle);



/*//Avoid penny stock, and has at least two years worth of data and monthly traded vol at least 1M
Filter = (Rank >= 95) AND (BarCount >= 50) AND (C > 1.0) AND (V > 1000000);

if( Status( "Action" ) == actionExplore ) SetSortColumns(-4,1);*/

Am stuck up with getting columns in exploration that give:

  1. RSRaw - RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthRS;
  2. Rank - Rank = StaticVarGet ("RankRSraw_" + Symbol);
  3. Rank Percentile - RSpctile = 100 - 100*Rank/ListQty;

While I am getting the IBDRank using GetRS(name()
I am mixing up things. Hence needed the above 3 seperately to judge the corectness

1 Like

If only for exploration the crude method would be to calculate :
ThreeMthRS = 0.4*(C/Ref(C,-Bars4Num));
SixMthRS = 0.2*(C/Ref(C,-(2Bars4Num)));
NineMthRS = 0.2
(C/Ref(C,-(3Bars4Num)));
TwelveMthRS = 0.2
(C/Ref(C,-(4*Bars4Num)));
RSraw = ThreeMthRS + SixMthRS + NineMthRS + TwelveMthR

Get the RSRaw in output and get the Ranking Best on same using Ranking features:
AddRankColumn();

For percentile, there will be need to go back again to UnilistTotal

1 Like