AFL Help: Multi-timeframe RS Ranking System - All Ranks return 0

Hi everyone,

I’m a trader from Vietnam. I’ve been studying the IBD 12-month RS Rating ideas from this forum and I'm now trying to expand it into a multi-timeframe version (12M, 6M, 3M).

The logic: Calculate Raw RS -> Rank them using StaticVarGenerateRanks -> Convert Rank to a 1-99 Score -> Auto-add to Watchlists.

The Bug: When running the exploration on "1 recent bar", my Raw RS calculates correctly, but Rank returns 0 for all tickers. Because Rank is 0, my final scoring formula 100 - (100 * Rank / ListQty) gives every stock a perfect 100.

My Troubleshooting (Are these the culprits?):

  1. The Naming Trap: I originally wrote StaticVarGenerateRanks("RankRSraw_", "RSraw_", ...). Did AmiBroker concatenate this into "RankRSraw_RSraw_", causing my later StaticVarGet("RankRSraw_") to return Null?
  2. Array Misalignment: Inside my for loop, extracting the Rank array and directly applying math (100 - 100 * Rank...) seems to collapse to 0. Is this because of differing symbol lifespans? Would converting the Array to a Scalar via LastValue() before the math fix this?

Since I’m still mastering AFL, especially memory management and array alignment, could the veterans here confirm if my conclusions are correct? Please excuse any errors in my English.

Deeply appreciate your time and expertise!

// ============================================================================
// COMPREHENSIVE RS RATING SYSTEM (IBD STYLE)
// Description: Calculates Raw RS, generates rankings, converts to 1-99 Score,
// and automatically categorizes symbols into Watchlists based on their Scores.
// ============================================================================

// Sync timeframe across all symbols to avoid Array Alignment errors (Error 0/Null)
SetBarsRequired(-1, -1);
SetForeign("VNINDEX"); 
RestorePriceArrays();

// ================== 1. DEFINE UNIVERSE ==================
listHSX   = CategoryGetSymbols(categoryMarket, 2);
listHNX   = CategoryGetSymbols(categoryMarket, 3);
listUPCOM = CategoryGetSymbols(categoryMarket, 1);
List      = listHSX + "," + listHNX + "," + listUPCOM;
ListQty   = StrCount(List, ",") + 1;

// Run heavy operations ONLY ONCE for the entire market to prevent crashing
if (Status("stocknum") == 0) 
{
    // ================== 2. RESET STATIC VARIABLES ==================
    StaticVarRemove("RSraw_*");  StaticVarRemove("RSR1_*");  StaticVarRemove("RSR2_*");
    StaticVarRemove("Rank_*");   
    StaticVarRemove("RS_*");     StaticVarRemove("RS1_*");   StaticVarRemove("RS2_*");

    // ================== 3. CALCULATE RAW RS ==================
    for (n = 0; (Symbol = StrExtract(List, n)) != ""; n++)
    {
        SetForeign(Symbol);

        // Check data availability for newly listed stocks
        Has252 = NOT IsNull(Ref(C, -252));
        Has126 = NOT IsNull(Ref(C, -126));
        Has84  = NOT IsNull(Ref(C, -84));
        Has63  = NOT IsNull(Ref(C, -63));
        Has42  = NOT IsNull(Ref(C, -42));
        Has21  = NOT IsNull(Ref(C, -21));

        // 12-Month RS
        RSraw = IIf(Has63 == False, Null, 
                    IIf(Has252, 
                        0.4*(C/Ref(C,-63)) + 0.2*(C/Ref(C,-126)) + 0.2*(C/Ref(C,-189)) + 0.2*(C/Ref(C,-252)), 
                        0.6*Nz(C/Ref(C,-63), 1) + 0.4*Nz(C/Ref(C,-126), 1)));

        // 3-Month RS
        RSR1 = IIf(Has21 == False, Null, 
                   IIf(Has63, 
                       0.5*(C/Ref(C,-21)) + 0.3*(C/Ref(C,-42)) + 0.2*(C/Ref(C,-63)), 
                       0.6*Nz(C/Ref(C,-21), 1) + 0.4*Nz(C/Ref(C,-42), 1)));

        // 6-Month RS
        RSR2 = IIf(Has42 == False, Null, 
                   IIf(Has126, 
                       0.5*(C/Ref(C,-42)) + 0.3*(C/Ref(C,-84)) + 0.2*(C/Ref(C,-126)), 
                       0.6*Nz(C/Ref(C,-42), 1) + 0.4*Nz(C/Ref(C,-84), 1)));

        RestorePriceArrays(); 

        // Store into RAM. 'True' ensures array length matches the VNINDEX reference
        StaticVarSet("RSraw_" + Symbol, RSraw, True);
        StaticVarSet("RSR1_"  + Symbol, RSR1,  True);
        StaticVarSet("RSR2_"  + Symbol, RSR2,  True);
    }

    // ================== 4. GENERATE RANKS ==================
    // Generates ranking arrays. Target variables will be named "RankRSraw_", etc.
    StaticVarGenerateRanks("Rank", "RSraw_", 0, 1224);
    StaticVarGenerateRanks("Rank", "RSR1_",  0, 1224);
    StaticVarGenerateRanks("Rank", "RSR2_",  0, 1224);

    // ================== 5. RESET WATCHLISTS ==================
    for (n = 0; (Symbol = StrExtract(List, n)) != ""; n++)
    {
        for (wl = 0; wl <= 8; wl++) 
            CategoryRemoveSymbol(Symbol, categoryWatchlist, wl);
    }

    // ================== 6. SCORING & CLASSIFICATION ==================
    for (n = 0; (Symbol = StrExtract(List, n)) != ""; n++)
    {
        // Extract Rank array and convert to a Scalar value using LastValue() 
        // Nz() handles nulls by assigning the lowest rank (ListQty)
        Rraw = LastValue(Nz(StaticVarGet("RankRSraw_" + Symbol), ListQty));
        R1   = LastValue(Nz(StaticVarGet("RankRSR1_"  + Symbol), ListQty));
        R2   = LastValue(Nz(StaticVarGet("RankRSR2_"  + Symbol), ListQty));

        // Convert Rank (1...n) to Percentile Rating (1-99)
        Score  = 100 - (100 * Rraw / ListQty);
        Score1 = 100 - (100 * R1 / ListQty);
        Score2 = 100 - (100 * R2 / ListQty);

        // Save final scores to RAM
        StaticVarSet("RS_"  + Symbol, Score,  True);
        StaticVarSet("RS1_" + Symbol, Score1, True);
        StaticVarSet("RS2_" + Symbol, Score2, True);

        // Distribute to Watchlists based on Scores
        // --- 12M RS (IBD) ---
        if (Score >= 70)      CategoryAddSymbol(Symbol, categoryWatchlist, 0);
        else if (Score >= 50) CategoryAddSymbol(Symbol, categoryWatchlist, 1);
        else                  CategoryAddSymbol(Symbol, categoryWatchlist, 2);

        // --- 3M RS ---
        if (Score1 >= 70)      CategoryAddSymbol(Symbol, categoryWatchlist, 3);
        else if (Score1 >= 50) CategoryAddSymbol(Symbol, categoryWatchlist, 4);
        else                   CategoryAddSymbol(Symbol, categoryWatchlist, 5);

        // --- 6M RS ---
        if (Score2 >= 70)      CategoryAddSymbol(Symbol, categoryWatchlist, 6);
        else if (Score2 >= 50) CategoryAddSymbol(Symbol, categoryWatchlist, 7);
        else                   CategoryAddSymbol(Symbol, categoryWatchlist, 8);
    }
}

// ================== 7. EXPLORATION DISPLAY ==================
Filter = 1;

// Retrieve values for the specific ticker currently being processed by the Explore engine
RS_IBD = StaticVarGet("RS_"  + Name());
RS_3M  = StaticVarGet("RS1_" + Name());
RS_6M  = StaticVarGet("RS2_" + Name());

AddColumn(RS_IBD, "RS IBD",   1.2, colorDefault, IIf(RS_IBD >= 80, colorGreen, colorDefault));
AddColumn(RS_3M,  "RS1 (3M)", 1.2);
AddColumn(RS_6M,  "RS2 (6M)", 1.2);

SetSortColumns(-3); // Sort descending by RS IBD column

A few things to think about:

  1. Are you using Pad & Align to a well-behaved symbol? This is the correct way to make sure all symbols have the same set of bars. SetBarsRequired() and StaticVarSet() are not the right way.

  2. The third parameter of StaticVarSet() determines whether your static variable is persistent, i.e. saved to a file and later restored. It does not "ensures array length matches the VNINDEX reference" as stated in your comments.

  3. You should expand your Exploration to include your intermediate static variables to see if they all contain what you think they do. This and other debugging tips can be found in this post: How do I debug my formula?

3 Likes