Multiple criteria sorting

Consider the classic Amibroker method to rank stocks. In particular this one is based on three criteria.
The final rank is a weighted combination of the ranks of the three criteria.

My question is: how can i modify the final array "rank" so that it will have integers starting from 1?

I need this because in my buy condition i want introduce "rank <=10", that is i want not buy more then 10 stocks.

wlnum = GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categoryWatchlist, wlnum );

if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
    StaticVarRemove( "values*" );

    for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
    {
        SetForeign ( symbol );
       
        // first criteria used for scoring
        values1 = Crit1  ;
       
        values2 =  Crit2;
        
        values3 = Crit3;
        
        
         inIndex = NorgateIndexConstituentTimeSeries(tkindex);
         RestorePriceArrays();
         
         // here we write static variables holding the values
        StaticVarSet (  "values1"  +  symbol, IIf(inIndex,values1,-1e9));
        StaticVarSet (  "values2"  +  symbol, IIf(inIndex,values2,-1e9));
        StaticVarSet (  "values3"  +  symbol, IIf(inIndex,values3,-1e9));
    }

    StaticVarGenerateRanks( "rank", "values1", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values2", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values3", 0, 1224 );
    
}


// retrieve rank positions
symbol = Name();
rank1 = StaticVarGet ( "rankvalues1" +  symbol );
rank2 = StaticVarGet ( "rankvalues2" +  symbol );
rank3 = StaticVarGet ( "rankvalues3" +  symbol );


rank = 0.33 * rank1 + 0.33 * rank2 + 0.33 * rank3;

You skipped some of the steps that I described here: Separate ranks for categories that can be used in backtesting

Inside your stocknum == 0 block, you need to add another loop that goes through all the symbols. Inside that loop, retrieve rank1, rank2, rank3, and combine into a new value called rankValue4. Put it in a static variable.

After that second symbol-by-symbol loop, call StaticVarGenerateRanks() again.

4 Likes

Here the code i've produced based on your instructions:

Copy

wlnum = GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categoryWatchlist, wlnum );

if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
    StaticVarRemove( "values*" );

    for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
    {
        SetForeign ( symbol );
       
        // first criteria used for scoring
        values1 = Crit1  ;  //higher is better
       
        values2 =  Crit2;   //lower is better
        
        values3 = Crit3;    //lower is better
        
        
         inIndex = NorgateIndexConstituentTimeSeries(tkindex);
         RestorePriceArrays();
         
         // here we write static variables holding the values
        StaticVarSet (  "values1"  +  symbol, IIf(inIndex,values1,-1e9));
        StaticVarSet (  "values2"  +  symbol, IIf(inIndex,values2,-1e9));
        StaticVarSet (  "values3"  +  symbol, IIf(inIndex,values3,-1e9));
    }

    StaticVarGenerateRanks( "rank", "values1", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values2", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values3", 0, 1224 );
	
	 for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
    {
         SetForeign ( symbol );        
         // 
         rank1_inside = StaticVarGet ( "rankvalues1" +  symbol );
         rank2_inside = StaticVarGet ( "rankvalues2" +  symbol );
         rank3_inside = StaticVarGet ( "rankvalues3" +  symbol );
         StaticVarSet("value_comb" + symbol, IIf(inIndex, 0.33 * rank1_inside + 0.33 * rank2_inside + 0.33 * rank3_inside,-1e9))   ;
    }
   StaticVarGenerateRanks( "rank", "value_comb", 0, 1224 );
    
}

PositionScore = IIf(NorgateIndexConstituentTimeSeries(tkindex) , 100/rank_comb ,0);



1 Like

That mostly looks OK, but there are still a few problems.

  1. Recall that StaticVarGenerateRanks gives the best rank of 1 to the highest value being ranked. In your comments for Criteria 1, 2, and 3 you have "higher is better" and "lower is better", but you ranked them all the same way so that's probably not producing the result you want in the first round of ranking.

  2. In the second round of ranking, the best symbols (those that had desirable values in the first round of ranking) will have three low values (rankvalues1, rankvalues2, rankvalues3). Combining those three values will produce a new low value (value_comb). But we want the best symbols to have a high value_comb, not a low value_comb, so that it will get a good rank (near 1) after the second round. The easiest way to achieve this is to rank by 1/(weighted sum) instead of ranking by (weighted sum).

  3. You're not retrieving your final ranking before putting it into the PositionScore. You need something like:

    rank_comb = StaticVarGet ( "rankvalue_comb" +  symbol );
    PositionScore = IIf(NorgateIndexConstituentTimeSeries(tkindex) , 1/rank_comb ,0);
4 Likes

In particular the point 2 is correct?

wlnum = GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categoryWatchlist, wlnum );

if ( Status("stocknum") == 0 ) // Generate ranking when we are on the very first symbol
{
    StaticVarRemove( "values*" );

    for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
    {
        SetForeign ( symbol );
       
        // first criteria used for scoring
        values1 = Crit1  ;  //higher is better
       
        values2 =  1000 - Crit2;   //lower is better
        
        values3 = 1000 - Crit3;    //lower is better
        
        
         inIndex = NorgateIndexConstituentTimeSeries(tkindex);
         RestorePriceArrays();
         
         // here we write static variables holding the values
        StaticVarSet (  "values1"  +  symbol, IIf(inIndex,values1,-1e9));
        StaticVarSet (  "values2"  +  symbol, IIf(inIndex,values2,-1e9));
        StaticVarSet (  "values3"  +  symbol, IIf(inIndex,values3,-1e9));
    }

    StaticVarGenerateRanks( "rank", "values1", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values2", 0, 1224 );
    StaticVarGenerateRanks( "rank", "values3", 0, 1224 );
	
	 for ( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++    )
    {
         SetForeign ( symbol );        
         // 
         rank1_inside = StaticVarGet ( "rankvalues1" +  symbol );
         rank2_inside = StaticVarGet ( "rankvalues2" +  symbol );
         rank3_inside = StaticVarGet ( "rankvalues3" +  symbol );
         StaticVarSet("value_comb" + symbol, IIf(inIndex, 100 / (0.33 * rank1_inside + 0.33 * rank2_inside + 0.33 * rank3_inside,-1e9)))   ;
    }
   StaticVarGenerateRanks( "rank", "value_comb", 0, 1224 );
    
}

rank_comb = StaticVarGet ( "rankvalue_comb" +  symbol );
PositionScore = IIf(NorgateIndexConstituentTimeSeries(tkindex) , 100/rank_comb ,0);


With regard to Norgate historical constituents i have used two instructions:

StaticVarSet (  "values1"  +  symbol, IIf(inIndex,values1,-1e9));
StaticVarSet (  "values2"  +  symbol, IIf(inIndex,values2,-1e9));
 StaticVarSet (  "values3"  +  symbol, IIf(inIndex,values3,-1e9));
PositionScore = IIf(NorgateIndexConstituentTimeSeries(tkindex) , 1/rank_comb ,0);

Are they correct?
Is the second (or the first) an unnecessary duplication or both are necessary?

You probably don't need to check index constituencies again when assigning position score, because any symbol not in the index is already going to have a very large rank_comb value (i.e. it was ranked poorly). However, if there's a chance your entry logic could allow you to look very far down the position score list, then you might want to keep the Norgate call either in the PositionScore as you have it or directly in your Buy logic.

1 Like