How to code for double ranking process?

Hi everyone,
I am vert new to the amibroker coding.
I am trading in Thailand stock market which have about 700 stocks.
Please anyone here suggest me how to code my trading model like this.

Long only, in Thai stocks of about 700 stocks (with “SET” as its index )
Keep invest in the best 15 stocks only when market timing is right (SET index EMA 20 golden cross EMA 100), otherwise no new entry for the remaining cash. The existing position will stay for 20 days( or 1 month holding period)
Upon the market is right …rebalance the portfolio once per month.

My criterias to select all 15 stocks is that

Ranking all stocks in the universe with one indicator (let say ADX) and choose the top best 10% (first 10 percentile) from all stocks in the universe or that is around 70 stocks.
*I don’t know how to code this part !

Then, ranking these selected stocks with another indicator (let say RSI) and select the best 15 stocks (among approximatedly 70 stocks from the first ranking)
*for the second ranking, i know that i can use positionscore function.

Then Rebalance once per month. Any stocks that rank below 15th is sold and replaced with the new one whose rank is 15th or better in order for the portfolio keep having 15 stocks all the time.

Below is my rough coding, just to show you what i am trying to do as a very afl programming novice. Its absolutely need to be corrected a lot.

So could anyone here please suggest me correct my code or show me the right coding for my trading rules.

Thank you in advance.

{//Option
	SetOption("InitialEquity" , 1000000);
	SetOption("MaxOpenPositions", 15);
	SetOption("MinShares", 100);
	RoundLotSize = 100;
	SetOption("CommissionMode", 1);
	SetOption("CommissionAmount", 0.16);
	
	SetTradeDelays(1, 1, 0, 0);
	BuyPrice = Open; 
	SellPrice = Open;
}

{//Signal
	index = Foreign( "SET","C", 1);
	buyCon1 = MA(index, 20) > MA(index, 100);//Broad index Market Timing
	buyCon2 = ???//ranking with ADX, then select the top 70 stocks or the top 10 percentile among all stocks in the universe with highest ADX
	Buy = buyCon1// AND buyCon2 ;
	Sell = 0; //no sell condition, sell only with applystop function of N bars representing N days.
}

{//Position
	SetPositionSize(6.5, spsPercentOfEquity);
	PositionScore = RSI();
}

{//Stop
	//ApplyStop(3, 1, 20);
	ApplyStop(stopTypeNBar, stopModeBars, 20);//20 means 20 trading day is 1 month holding period.
}

Moderator comment: Your post included incorrectly formatted code and I had to fix it. Next time follow the forum rules and make sure your code is properly formatted as instructed in “How to use this site”

Read the information in the user manual on the StaticVarGenerateRanks function. There is example code there which can be used.

You can generate the first-pass ranks, and then do a second pass and rank only the symbols that had a high enough rank from the first pass.

I have some code that does this, but it’s part of a larger formula, and I would have to extract it which would take a bit of time. Try first with just one pass to make sure you see how it works. Then see if you can add the second.

3 Likes

Hello @golfpathawat,

As you are new to AmiBroker coding (AFL), then I would suggest that you break your items down even further and work on them individually. Trying to start off with a System, before leaning the Exploration, is a bit like wanting to learn how to high jump before being able to walk.

So let’s start with doing an exploration to get the list in order. Then we can SEARCH to find out how to create a WatchList from the Exploration, to use in the next ranking.

Hopefully this step by step approach will help you learn some of the functionality of AFL/AmiBroker and get you a system the way you want it.

Snoopy.

Edit: I posted just after @derfahrer, and like the idea of StaticVarGenerateRanks function better than my idea of building a watchlist. Check out the manual and then show us your code as you work your way through.

1 Like

OK, I have extracted some code to do 2-pass ranking. You can rank based on RSI14 or Volume (simple options – substitute your own filters of choice). The default is to rank first on RSI then take the top 10 and rank by Volume. If you do Explore on a single day you can then see the rankings.

I think that this works properly, but you will want to test more extensively to make sure. (I did only a short test). Note that you may need to check the “pad and align to reference symbol” option for it to work properly.

/* 
	Two-pass ranking
		Ranks symbols in pass 1, then in pass 2 ranks the top 'x' from pass 1 according to second filter 

	Rotate and optionally rebalance positions on specified timeframe:
		weekly
		monthly
*/

NumberHeld = Param("# of Positions", 2, 1, 100, 1);

// Trading frequency
TradeFreq = ParamList("Trade Frequency", "Week|Month|Hold", 1);

FilterMode = ParamList("Filter Mode", "1 Pass|2 Pass",1);
	
FilterName1 = ParamList("Pass1 Filter", "RSI|Volume");

NumPasses = 1;
if (FilterMode=="2 Pass") {
	FilterName2 = ParamList("Pass2 Filter", "RSI|Volume");
	Pass1Max = Param("Pass 1 top X", 5, 2, 100, 1);
	NumPasses++;
}

// set trade / rebalance signal based on frequency
switch(TradeFreq) {
	case "Week":
		Rebalance = DayOfWeek() < Ref(DayOfWeek(), -1);
		break;
	
	case "Month":
		Rebalance = Month() != Ref(Month(),-1);
		break;

	case "Hold":
		SetOption("HoldMinDays", 30);
		Rebalance = 1;
		break;
}
 
// The following block gets all the filter values and does the pass 1 and pass 2 ranking
// only need to do the calculations once -- so do for the first stock

// read in all symbols
Mainlistnum = GetOption( "FilterIncludeWatchlist" ); 
Mainlist = GetCategorySymbols( categoryWatchlist, Mainlistnum );

NumSymbols = 0;
if (Status("stocknum") == 0) {

	// delete static variables 
	StaticVarRemove("*ValuesToSort*"); 

	for (i = 0; (sym = StrExtract(Mainlist, i)) != ""; i++ ){

		NumSymbols++;
		// make the symbol be the 'current' symbol (so the OHLC arrays can be used directly)
		SetForeign(sym);
	
		for (j = 1; j <= NumPasses; j++) {
		 
			switch (VarGet("FilterName"+j)) {
			
				case "RSI":
					VarSet("filterScore"+j, RSI(14));
					break;
				case "Volume":
					// give lower price higher score
					VarSet("filterScore"+j, V);
					break;
			}

			// note: it's being done this way, because this code was extracted from a larger formula that was using the filter scores
			// store filter value in filterScore'j''Sym'
			// and will be put in the static var set ValuesToSort_filter'j''sym'
			StaticVarSet("ValuesToSort_filter" + j + sym, IIf(IsEmpty(VarGet("filterScore"+j)), -100, VarGet("filterScore"+j))); 
		}
		// restore price arrays to the active symbol
		RestorePriceArrays();
	}
	
	// For each filter, generate the ranks 
	// Generate pass 1 ranks. resulting ranks will be held in the static var set "RankValuesToSort_filter1'sym'"
	StaticVarGenerateRanks("Rank", "ValuesToSort_filter1", 0, 1224); // normal rank mode 

	// now generate the Pass 2 (final) ranking, if so configured  
	if (filterMode == "2 Pass") {
	
		for (i = 0; (sym = StrExtract(Mainlist, i)) != ""; i++ ){
	
			// get pass 1 rank
			Pass1Rank = StaticVarGet("RankValuesToSort_filter1" + sym);

			Pass2FilterValue = StaticVarGet("ValuesToSort_filter2"+sym);
			
			// if not in the 'top x' then set the pass 2 filter value to -100
			StaticVarSet("ValuesToSort_filter2"+ sym, IIf(Pass1Rank <= Pass1Max, Pass2FilterValue, -100)); 
		}
		// generate the Paas 2 ranks.  Rank values will be stored in the RankSumValuesToSort_'symbol' arrays
		StaticVarGenerateRanks("Rank", "ValuesToSort_filter2", 0, 1224); // normal rank mode
	}
}

// get this symbol's rank from the Rank array  (depending on whether 1 pass or 2 pass)

if (filterMode == "1 Pass") {
	SymRank = StaticVarGet("RankValuesToSort_filter1" + Name());
}
else {
	SymRank = StaticVarGet("RankValuesToSort_filter2" + Name());	
}

// Exploration for debugging
filterIndex = 1;
AddColumn(C, "Close", 2.1);
AddColumn(SymRank, "Sym Rank", 2.0);
for(j = 1; j <= NumPasses; j++) {
	AddColumn(StaticVarGet("RankValuesToSort_filter" + j + Name()), "RankF-"+j, 2.0);
	AddColumn(StaticVarGet("ValuesToSort_filter" + j + Name()), "ValF-"+j, 2.2);
}
		
SetOption ("MaxOpenPositions", NumberHeld);
SetOption ("AllowPositionShrinking", True);

PositionSize = -100/NumberHeld;	// Equally divide capital among the positions held

// Buy / rebalance rule: first bar, or rebalance day, or force rotation selected AND the symbol's rank is high enough
Firstbar = Status("firstbarintest") OR Status("firstbarinrange");

if (TradeFreq == "Hold") {
	Buy = SymRank <= NumberHeld;
}
else {
	Buy = (Firstbar OR Rebalance) AND SymRank <= NumberHeld;
}
Sell = Rebalance AND SymRank > NumberHeld;

// Exploration output.  Could be expanded to make more useful
Filter = 1;

AddColumn(Buy, "Buy", 1.0);
AddColumn(Rebalance, "Rebal", 1.0);
AddColumn(Firstbar, "Firstbar", 1.0);


15 Likes

Thank you and feel appreciated for all the comments especially the example code from Derfahrer.
I will study how the code work so I can understand what modification I can do on it.

My purpose is to backtest my present strategy. :slight_smile:

I am working on a similar ranking procedure.
I have an inconsistency problem, do you encounter something similar?

Basically, when I click backtest multiple times in a row, without changing any code, my results differ. I wonder where this comes from. Did you encounter a similar problem?

Dio

I figured out why my results kept changing.
There were some symbols in my database with too little quotes.
That messed up the ranking.

Dio

@derfahrer It's really neat, the code.

I just some food for thought, logic-wise:
Ranking by volume makes me uncomfortable, I don't know if it's only an example though.
I would be better to use either "unadjusted volume", or "turnover" (Close*Volume) due to the splits in stock prices. Using just volume will falsify the results of long backtests.

1 Like

thanks. Yes, Volume was definitely only an example, just to illustrate. Not suggesting at all that people use that. Maybe could have picked a better example. On the other hand maybe not a bad idea for people to look closer and select something better :slight_smile: