RS Ranking - Validating to Filter out Junk Stocks from Database

I have already read How to imitate IBD Relative Strength Percentile Ranking of Stocks by @rocketPower a great many times.

I have also read @fxshrat post on how to bypass looping for SetForeign(Symbol); function.

I worked out several examples but finally landed in a conundrum .

Method 1. This first method is as simple as described by rocketpower. It scans the entire database of stocks. That's very simple. I somehow dont want to scan the entire database which includes highly illiquid and also junk penny stocks.

///1.  SIMPLE METHOD  SCANS ENTIRE WATCHLIST AND PENNY STOCKS WHICH ARE USELESS

  SetBarsRequired( sbrAll ) ;  /* for all baRS IN CUM FUNC*/ 
  if ( Status( "stocknum" ) == 0 )
    {
        // CLEAR STATIC VARS
        StaticVarRemove( "RS21*" );
        
        StaticVarRemove( "RankRS21*" );
       
        
        ListWL_SP500 = CategoryGetSymbols(categoryWatchlist, 0);
        Sym_Count = 0;

        // SINGLE LOOP: Calculate for all symbols, but mark invalid ones with Null
        for ( i = 0; ( Symbol = StrExtract( ListWL_SP500, i ) ) != ""; i++ )
        {
            // Set context to this symbol and validate data
			if(Status("actionex") != actionExEditVerifyFormula) {
							SetForeign(Symbol);
						}
            RS21Value = ((C/Ref(C,-21))-1)*100;
           
            RestorePriceArrays();
           Raw21RS =	StaticVarSet("RS21" + Symbol, RS21Value); 
         }
         
        StaticVarGenerateRanks( "Rank", "RS21", 0, 1234 );
      
         
     }
         
   // TEST THIS 
   function GetRS21Rank() { return StaticVarGet("RankRS21" + Name()); }

GfxTextOut("GetRS21Rank " + GetRS21Rank() ,20,20);	
  1. I want to eschew penny stocks and low liquid stocks from my ranking process. SO I tried this Method2 using IsTrue(VALIDATION) . I think this is not rational.
///2.  VALIDATION TO ESCHEW PENNY STOCKS USING IsTrue(VALIDATION ) 

SetBarsRequired( sbrAll ) ;  /* for all baRS IN CUM FUNC*/ 
  if ( Status( "stocknum" ) == 0 )
    {
        // CLEAR STATIC VARS
        StaticVarRemove( "RS21*" );
        StaticVarRemove( "RS63*" );
        StaticVarRemove( "RankRS21*" );
        StaticVarRemove( "RankRS63*" );
        
        ListWL_SP500 = CategoryGetSymbols(categoryWatchlist, 0);
        Sym_Count = 0;

        // SINGLE LOOP: Calculate for all symbols, but mark invalid ones with Null
        for ( i = 0; ( Symbol = StrExtract( ListWL_SP500, i ) ) != ""; i++ )
        {
            // Set context to this symbol and validate data
			if(Status("actionex") != actionExEditVerifyFormula) {
							SetForeign(Symbol);
						}
						
						
			 VALIDATION = C >10  AND MA(C,50)* MA(V,50) >= 20000 AND V > 1000;  // VOLUME LIQUIDITY FILTER TO ESCHEW PENNY STOCKS
			  
		
			RS_MOM1M =  ((C/MA(C,21))-1)*100 ;
			RS21Value =  IIf( IsTrue(VALIDATION ) , RS_MOM1M,0);																		
			RestorePriceArrays();
			Raw21RS =	StaticVarSet("RS21" + Symbol, RS21Value); 
         }
         
        StaticVarGenerateRanks( "Rank", "RS21", 0, 1234 );
         
         
     }
     
// TEST THIS 
   function GetRS21Rank() { return StaticVarGet("RankRS21" + Name()); }
   GfxTextOut("GetRS21Rank " + GetRS21Rank() ,20,20);	   
  1. Method 3 - I tried to filter out the penny and low liquid stocks by validating the last bar.
  
 /////3.  VALIDATION TO ESCHEW PENNY STOCKS  LOGICALLY DISCARDS STOCKS CHECKING LAST BARS
 
 SetBarsRequired( sbrAll ) ;  /* for all baRS IN CUM FUNC*/ 
 if ( Status( "stocknum" ) == 0 )
    {
        // CLEAR STATIC VARS
        StaticVarRemove( "RS21*" );
        
        ListWL_SP500 = CategoryGetSymbols(categoryWatchlist, 0);
        Sym_Count = 0;

         
        for ( i = 0; ( Symbol = StrExtract( ListWL_SP500, i ) ) != ""; i++ )
        {
			   // Set context to this symbol and validate data
				if(Status("actionex") != actionExEditVerifyFormula) {
								SetForeign(Symbol);
							}
				
            //VALIDATION 
             IsValidArray = C > 6 AND V > 1000 AND (MA(C,50) * MA(V,50) >= 200000);
            
             RS21Value = ((C/Ref(C,-21))-1)*100;
         
            //APPLY VALIDATION - Set invalid bars to Null
            RS21Value_Validated = IIf( IsValidArray, RS21Value, Null );
             RS21Value_Last = LastValue( RS21Value_Validated );
          
             RestorePriceArrays();
            
					   //If the last value was Null, this will be ignored.
						if ( !IsNull(RS21Value_Last) ) 
						{
							StaticVarSet("RS21" + Symbol, RS21Value_Last);
							 Sym_Count++;
						}
        }

        // GENERATE RANKS on the stored values
        StaticVarGenerateRanks( "Rank", "RS21", 0, 1234 );
    }
    
    
   // TEST THIS 
function GetRS21Rank() { return StaticVarGet("RankRS21" + Name()); }
GfxTextOut("GetRS21Rank " + GetRS21Rank() ,20,20);	
  1. Method - I tried to filter the entire database before proceeding to the calculations.
 /////4.  VALIDATION TO ESCHEW PENNY STOCKS  BY FILERING THE ENTIRE DATABASE BEFORE CALCULATIONS 

SetBarsRequired( sbrAll ) ;  /* for all baRS IN CUM FUNC*/ 
//Only keep symbols from watchlist that have valid data
ListWL_SP500 = CategoryGetSymbols(categoryWatchlist, 0);
filtered_tickerlist = "";
Sym_Count  = 0;

// Process each symbol from the watchlist directly
for(i = 0; (Symbol = StrExtract(ListWL_SP500, i)) != ""; i++)
{
						// Set context to this symbol and validate data
						if(Status("actionex") != actionExEditVerifyFormula) {
							SetForeign(Symbol);
						}
						// Comprehensive data validation
						IsDataValid =  NOT IsNull(SelectedValue(C)) 	AND NOT IsNull(SelectedValue(V)) AND SelectedValue(V) > 0     // Active trading
												// above 3 CONDITIONS will filter out suspended non traded stocks 
												AND SelectedValue(C) > 6   // Minimum price
												AND  SelectedValue(MA(C,50)) * SelectedValue(MA(V,50)) >= 200000   // Liquidity filter
												AND SelectedValue(V) > 1000;      // Minimum volume
												 		
							// Only include symbols with valid data
							if(IsDataValid)
							{
								filtered_tickerlist = filtered_tickerlist + Symbol + ",";
								Sym_Count ++;
							}
							else
							{
								printf("Excluding %s - Invalid data", Symbol);
							}
    
    RestorePriceArrays();
}

 
     if ( Status( "stocknum" ) == 0 ) 
            {
            
					                   
					StaticVarRemove( "RS21*" ); 
				     StaticVarRemove( "RankRS21*" ); 
					 
					 
					for ( n = 0; ( Symbol = StrExtract( filtered_tickerlist, n ) )  != "";  n++ ) 
						{ 	   
							  // Set context to this symbol and validate data
								if(Status("actionex") != actionExEditVerifyFormula) {
								SetForeign(Symbol);
										}
										RS21Value = ((C/Ref(C,-21))-1)*100;        // 21-day return
																	  
                                       RestorePriceArrays();
										Raw21RS =	StaticVarSet("RS21" + Symbol, RS21Value); 
							 }
																	 
																 
							StaticVarGenerateRanks( "Rank", "RS21", 0, 1234 );    /// 1224 suppoeted with equal ranks
	}												        
												        
   // TEST THIS 	 
function GetRS21Rank() { return StaticVarGet("RankRS21" + Name()); }
 GfxTextOut("GetRS21Rank " + GetRS21Rank() ,20,20);										

I have ranking for 63 day period, RVol ranking , VPC Ranking etc etc that I excluded here to shorten the post.

I would then call all these functions elsewhere and add other parameters like distance from 52wk high , highest volumes etc etc and explore the results which would display something like this

Now my dilemma is which Method is appropriate to filter out junk stocks.

And Method 4 takes too long time for exploration.

The profoundly enlightened members reply would be truly appreciated.

Thank you all so much.

Why did you say that approach #2 is not rational? Other than some minor flaws in your code, I think that's the most reasonable approach. If you search the forum, you will also find that there are non-looping methods for achieving the same result.

1 Like

@mradtke Hellow, :heart_suit:

In Approach#2
If I have Stock A(Junk) as Invalid then it would assigned 0 value
And a Stock B which has a negative momentum of value say -2

Then the invalid Stock A in spite of junk would be ranked above Stock B which may be a good stock but has gone down temporarily.

What are your thoughts on this. Or would it be good if I assign null instead of zero while validating. For example

RS21Value =  IIf( IsTrue(VALIDATION ) , RS_MOM1M,Null);
instead of 
RS21Value =  IIf( IsTrue(VALIDATION ) , RS_MOM1M,0);

Yes I am aware of the non-looping method written by the the genius @fxshrat

That was one of the coding flaws I was referring to. Assuming that you're looking for high momentum values, why not assign a large negative number when a stock does not meet your criteria. For example, you could assign -99999:

RS21Value =  IIf( IsTrue(VALIDATION ) , RS_MOM1M, -99999);

Also, you should not think of these as "junk stocks", but rather "stocks that don't meet your criteria on certain days". Take the price filter (C > 10) for example: A stock may normally trade around $11, and therefore it's acceptable to you. But if it dips to $9.50 for one day it doesn't become "junk", and in fact may recover to $11 or more on a subsequent day.

If you're fixing flaws, you could also move this statement:

if(Status("actionex") != actionExEditVerifyFormula)

So that it's part of the outer If statement:

if ( Status( "stocknum" ) == 0 )

Because there's no reason to execute ANY of that logic if you're only verifying the formula. You may also want to avoid executing that code if someone opens the Parameters window.

2 Likes

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