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?):
- The Naming Trap: I originally wrote StaticVarGenerateRanks("RankRSraw_", "RSraw_", ...). Did AmiBroker concatenate this into "RankRSraw_RSraw_", causing my later StaticVarGet("RankRSraw_") to return Null?
- 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
