Sector Rotation Strategy Using StaticVarSet and StaticVarGenerateRanks

howto
Tags: #<Tag:0x00007fb3da434050>

#1

The rotational strategy that I have posted below ranks nine sector ETFs (contained in “symlist”) based on their rate of change and uses StaticVarSet and StaticVarGenerateRanks.

It then rotationally invests in three stocks (those with the highest rate of change) that are in the same sector as the highest ranked ETF.

What I would like to do but can’t figure out is how to allow it to invest in the top three or four ranked sectors based on the ETF rate of change ranking.

I would really appreciate any input and help doing this. It took me quite a while to get it to this point and I’m struggling to find any references similar to what I want to do.

symlist = "XLB,XLP,XLY,XLK,XLV,XLI,XLU,XLE,XLF";
indexLookBack = 80;
stockLookBack = 50; //Optimize("stockLookBack", 90, 20, 150, 15);

// reset static variables
StaticVarRemove( "ValuesToSort*" );
StaticVarRemove( "RankValuesToSort*" );
StaticVarRemove( "Sectors*" );
StaticVarRemove( "RankSectors*" );
_TRACE("!CLEAR!");

// generate static variables
for( i = 0; ( sym = StrExtract( symlist, i) ) != ""; i++ ) 
{	 
	SetForeign(sym ); 
	Value = ROC( Close, indexLookback ); 
	StaticVarSet( "ValuesToSort" + sym, Value ); // save ROC values to static variable
	Sectors = SectorID(0); //0 returns the integer sector ID, 1 returns its name as a string
	StaticVarSet( "Sectors" + sym, Sectors); // save sector IDs to static variable
	RestorePriceArrays();
}

// generate ranking 
StaticVarGenerateRanks( "Rank", "ValuesToSort", 0, 1224 ); // normal rank mode 

targetRank = 1; //Optimize("targetRank", 1, 1, 9, 1); 
targetSector = 0; //initialize this to zero so that it doesn't get overwritten in the IIF below

// read ranking 
for ( j = 0; ( sym = StrExtract( symlist, j ) ) != ""; j++ ) 
{ 
    //_TRACE(sym + " Value: " + StaticVarGet("ValuesToSort" + sym) + "   Rank: " + StaticVarGet("RankValuesToSort" + sym)); 
	//_TRACE(sym + " Sector: " + StaticVarGet("Sectors" + sym));
	stockRank = StaticVarGet("RankValuesToSort" + sym); 
	targetSector = IIF(StaticVarGet("RankValuesToSort" + sym) == targetRank, StaticVarGet("Sectors" + sym), Max(targetSector, 0)); 
}

//_TRACE("" + targetSector);

///////////////////// Rotational Strategy Parameters & Settings ///////////////////////////////////////////////////
numPositions = 3; //Optimize("numPositions", 3, 3, 8, 1);
numExtras = 0; //Optimize("numExtras", 0, 0, 10, 1);
capital = 20000;

// Small Calcs required for backtest settings
worstHeld = numPositions + numExtras;

// Backtest Settings
SetBacktestMode( backtestRotational );
SetOption("InitialEquity", capital); 
SetOption("MaxOpenPositions", numPositions);
SetOption("WorstRankHeld", worstHeld);

// Fixed fraction position sizing  
SetPositionSize( 100/numPositions, spsPercentOfEquity );

////////////////////// Position Scoring ////////////////////////////////////////////////////////////////////////////
sectorFlag = SectorID() == targetSector;
adjSlope = ROC(Close, stocklookBack);
compScore = IIf(sectorFlag == 1, Max(adjSlope, 0), 0); //apply zero score to stocks not in the target industry. exclude short trades. 
PositionScore = compScore;


Assigning preferred parameter value to each security
#2

@porcupine Since you have been a member of this forum for less than 24 hours I would suggest you begin to review the many resources on how to properly code your system.

You do not need to use StaticVarGenerateRanks if doing a rotational strategy if not using the Custom Backtest Interface. Read this, code it, experiment with it. (it is from the Official Knowledge Base, where I would suggest all AmiBroker related research should begin)
http://www.amibroker.com/kb/index.php?s=rotation

Then look on this forum for some of the many previous discussions on “rotational" trading, like this thread with many posts.

And if that isn’t interesting or educational enough just “search” the forum and look at some of these rotational discussions and codes.

image

If after that research you have improved your code and still have a problem don’t hesitate to come back to this thread and ask for help. If successful please feel free to share your successful code and results with the forum. I (and almost everyone else on the forum) would always offer a warm welcome to new contributors.

Good luck!

PS I am pretty sure Howard Bandy has coded a version of your system (and of course many other systems) in his 2nd book “Quantitative Trading Systems” which is also a great place for a newbie to grow his AmiBroker skills.


Third party services, blogs, courses, books, add-ons
#3

@porcupine After finishing your homework as assigned by @portfoliobuilder :wink: you might want to take a look at this thread in the old forum: https://groups.yahoo.com/neo/groups/amibroker/conversations/messages/197308


#4

@TrendXplorer I think new members have trouble logging in to the old forum and since it is your code why not post it here on the new forum.

And for @porcupine I would add to his homework by suggesting in visit the great rotational blog at indexswingtrader.blogspot.com :+1:


#5

@porcupine Cross posting from the old Yahoo forum: GTAA Timing Model


#6

@TrendXplorer Thanks for the link to the GTAA strategy and for re-posting it in the new forum. Very nice code. It performs quite well when backtested with monthly data. I'm a big fan of tactical asset allocation. I've found that it's one of the best ways of minimizing drawdown.

@portfoliobuilder I appreciate your response and am still working on this. I have Bandy's book (actually, all of them) and he does provide a solution in "Quantitative Trading Systems." Having said that, it's a clumsy solution that relies on exporting a .CSV file of signals and then re-importing them and using that data as if it's a price series. I don't see how I could accomplish what I'm trying to do without using StaticVarGenerateRanks unless I script my own ranking function (which I might end up doing). If you have a solution, please post it. I scoured this forum as well as other sources before deciding to post my question. The most similar thing that I found is this post by Tomasz: http://www.amibroker.com/kb/2016/01/30/separate-ranks-for-categories-that-can-be-used-in-backtesting/ but it doesn't do what I asked about here. You mentioned the Clenow rotation thread. I have posted my own version of Clenow's momentum strategy there in the hopes of keeping the conversation going on it.

A general note, I have noticed that most of the responses to newbie's questions/posts in this forum have the same format: 1. Dismissive comment referencing their lack of experience, 2. Derogatory remark about their code and how it demonstrates their misunderstanding of how Amibroker processes arrays, 3. One or more links to resources that they've likely already pored over for days or weeks. I may be a newbie when it comes to this forum but I've been using Amibroker for a while, and other platforms for many years. I haven't posted here before because I dreaded doing so, knowing that I'd get the typical response described above. The tone here is much different than it is at Quantopian for example, where there's a definite sense that people are collaborating, improving, and growing together. Anyways, that's my two cents. Once again, thank you for taking the time to respond in the first place.


#7

Maybe you don't get comments on code on Quantopian simply because on Quantopian forum you rarely see lots of actual code. They rather discuss about math and/or ideas. And math does not trigger much emotion :slight_smile:


#8

@porcupine It is unfortunate if you found my post

None of those were my intent and frankly I don't think I was the least bit dismissive or derogatory. I saw that you had just joined the forum, and you had made no mention of reviewing all of the resources I listed in my response. I honestly felt they were very worthwhile resources that would help a new user get a good start on coding rotational systems.

Perhaps I like to answer too many questions on this forum and I do post codes often but I like the old adage , "instead of just giving a hungry man a fish to eat, teach a man to fish and you have fed him for life".

That is my intent when I take the time to type out a long response with numerous helpful links. And I felt that the answer to your question was in those links in several places.

Also I am happy to hear that you

and I look forward to learning much from you and your future posts.


#9

@porcupine your post has obviously gotten under my skin. I re-read your original post and lo-and-behold I had not read it carefully enough the first time. Mea culpa.
You are looking for a 2 pass ranking, Sector then Stock. Possible useful links

https://groups.yahoo.com/neo/groups/amibroker/conversations/topics/187601

https://groups.yahoo.com/neo/groups/amibroker/conversations/messages/197096

I have an Exploration based on code from @HelixTrader one of those posts on the old forum and hopefully it might help point you in the right direction. I use a different data feed and my Sector symbols are based on my data feed. Just substitute your own proper symbols.

// derived from Alan (@HelixTrader) on old forum
// https://groups.yahoo.com/neo/groups/amibroker/conversations/messages/197113

#include_once "Formulas\Norgate Data\Norgate Data Functions.afl"

sectorlist = "$SPXE,$SPXM,$SPXI,$SPXD,$SPXS,$SPXA,$SPXF,$SPXT,$SPXL,$SPXU,$SPXR";
wlnum = GetOption( "FilterIncludeWatchlist" );
List = CategoryGetSymbols( categoryWatchlist, wlnum ) ;

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

    StaticVarRemove( "sector*" );
    StaticVarRemove( "ticker*" );

    for( i = 0; ( sec = StrExtract( sectorlist, i ) ) != ""; i++ )
    {
        SetForeign( sec );
        sectormom = ROC( C, 90 );
        RestorePriceArrays();
        StaticVarSet( "sectormom" + sec, sectormom );

    }

    StaticVarGenerateRanks( "sectorrank", "sectormom", 0, 1224 );

    // ticker ranking
    for( n = 0; ( sym = StrExtract( list, n ) ) != ""; n++ )
    {
        SetForeign( sym );
        mom = ROC( C, 90 );
        RestorePriceArrays();
        StaticVarSet( "mom" + sym, mom );
    }

    StaticVarGenerateRanks( "tickerrank", "mom", 0 , 1224 );
}

StockSector = StrLeft( GicsID( 0 ), 2 );
sec = "";

if( stocksector == "10" )
    sec = "$SPXE";

if( stocksector == "15" )
    sec = "$SPXM";

if( stocksector == "20" )
    sec = "$SPXI";

if( stocksector == "25" )
    sec = "$SPXD";

if( stocksector == "30" )
    sec = "$SPXS";

if( stocksector == "35" )
    sec = "$SPXA";

if( stocksector == "40" )
    sec = "$SPXF";

if( stocksector == "45" )
    sec = "$SPXT";

if( stocksector == "50" )
    sec = "$SPXL";

if( stocksector == "55" )
    sec = "$SPXU";

if( stocksector == "60" )
    sec = "$SPXR";

sym = Name();

sectorrank = StaticVarGet( "sectorranksectormom" + sec );
sectormom = StaticVarGet( "sectormom" + sec );
tickerrank = StaticVarGet( "tickerrankmom" + sym );
tickerMom = StaticVarGet( "mom" + sym );

dynamic_colorS = IIf( sectormom > 0, colorLime, colorRose );
dynamic_colorT = IIf( tickerMom > 0, colorLime, colorRose );

Filter = 1;
AddTextColumn( StockSector, "StockSector" );
AddTextColumn( SectorID( 1 ), "Sector name", 1.2, colorDefault, colorLightYellow, 180 );
AddTextColumn( GicsID( 1 ), "GICS name", 1.2, colorDefault, colorDefault, 270 );
AddTextColumn( sec, "sec" );
AddColumn( sectorrank, "Sector Rank", 1 );
AddColumn( sectormom, "Sector Mom", 1.2, colorDefault, dynamic_colorS );
AddColumn( tickerrank, "ticker rank", 1 );
AddColumn( tickermom, "Ticker Momentum", 1.2, colorDefault, dynamic_colorT );
SetSortColumns( 7, 9 );

Produces and Exploration that looks like this,
image


#10

@portfoliobuilder you should not feel 'guilty'. You did nothing wrong. Just the opposite. You helped. Your response made perfect sense. And I hope that @porcupine would at least click 'like' on your post to thank you. I did.

That is precisely the reason why newcomers may get dismissive comment. If somebody joins and within 5 minutes in his very first post starts to a) demand; b) complain about the forum, then frankly and reasonably what kind of response one can expect?
(Just to be sure that @porcupine does not take it personally - I am not saying that about him/her, but about some pattern that we see from time to time).

This forum is much more diverse than Quantopian. Quantopian has very narrow scope of interest and narrower audience. On AmiBroker forum we've got huge diversity. Starting from absolute beginners learning how to plot their first chart to super-advanced coders, from chartists to quants, from 20 to 90 years old, doctors, engineers, students, financial advisors, architects, anybody, people from USA, Australia, Indonesia, India, all Europe, Brazil, Argentina, New Zealand, literally everywhere. With that kind of diversity some tensions are inevitable.

For what it is worth: newcomers should realize that others can see their forum "read time" in their profile. And they should not be surprised if somebody points to existing material when we see something like "5 minute total forum read time". There is nothing dismissive about that, it is is just common sense and most logical course of action. Again it is not about any particular person, just a pattern observed from time to time.

BTW: linking to existing material is very welcome as it makes it easier to find related posts.


#11

I have posted some example code to do a 2-pass ranking:

That's not quite what you're doing here though (rank all symbols by factor 1, then top x by factor 2)

I think I would try something like this:

  • rank your sector ETFs
  • get the sector id of the top ETF (like you're doing)
  • then go through your symbols then to rank the ROC
  • for any symbol not in the sector of interest, make the ROC negative (so that it would be ranked below those of the chosen sector)
  • Buy signal would be for a symbol with rank in the top 3

#12

@portfoliobuilder The second link to the old Yahoo group and the code that you posted are exactly what I was looking for. Thank you for the help, it's much appreciated. I'm sorry for my cantankerous response yesterday. I felt pretty bad about it last night after I re-read it. I think I was frustrated that I couldn't figure it out myself. It was a poor way to get started on the forum and I'm ashamed. I hope that we can start fresh. Once again, thanks a lot for your help and for taking the time to find the helpful resources.

@derfahrer Exactly, that's what I'm looking to do. I think the code that @portfoliobuilder provided will help me get it done.

@Tomasz Thanks for the thoughts. Just wanted to say that even though someone's "read time" says 5 minutes, that might not be true. In my case, I've spent a lot of time on this forum -and greatly appreciate that we have it since it's so much easier to use than the old Yahoo group -but that's not reflected in my "read time' since I only registered in order to finally ask for help on something that had been dogging me for weeks.

Best,


#13

Just to add more context to the conversation... I'm working on this strategy because I want to add a small-cap equities rotation strategy to my portfolio in 2018; hopefully sooner rather than later.

Once I get the sector and symbol rankings working, I'm going to replace the simple ROC with Clenow's momentum method. Here's a simple function for it:

//function to get exponential slopes (Clenow method)
function EXSLOPE(p, len){
	slopeArray = ln(p);
	linSlope = LinRegSlope(slopeArray, len);
	eSlope = exp(linSlope);
	annualSlope = (eSlope^250)-1;
	bi = BarIndex();
	R2 = (Correlation(slopeArray, bi, len))^2;
	adjSlope = R2 * annualSlope;
return (adjSlope);
}

Then, the plan is to see which combination of sectors perform the best (e.g. exclude the two lowest ranking sectors). From some preliminary analysis, I've found that, counterintuitively (to me at least), it's best to invest in all sectors EXCEPT the highest ranking sector. This could be because it is overheated (for the lookback period).

I've also found that it's best to use a longer lookback period for the ranking of the individual stocks than for the sectors; i.e. find stocks that have been out-performing for a while and then enter when the sector that they are in starts to heat up.

Running this strategy on small-caps results in a large system drawdown as well as an unacceptably large risk of ruin (RoR) due to their volatility. The best way that I've found of reducing drawdown (for this strategy) is to adjust position size based on a market regime filter and the best way that I've found of reducing the RoR is to include tactical asset allocation. I'm doing that via exiting open equity positions (based off the market regime filter) and entering TLT and SHY.

I'm also trying to come up with a better ranking method than what Clenow uses. I want to penalize more for recent price and volume volatility and penalize for excess returns that are so severe that they might indicate an impending violent retracement. While his ranking algo is decent for large-cap equities, it tends to capture small-caps in an overheated state right before they're about to experience a severe correction. Hence, the large DD and RoR when applied to small-caps.

Once I get some of these ideas working better and integrated into the strategy, I'll post it here.


#14

@porcupine, since you cited Quantopian, I wonder if you already did some tests using some of the ideas that Clenow posted there in a revised "Equity Momentum" strategy (April 8, 2017).
In particular, I refer to the use a double adjusted slope (for instance 125/250d or 60/90d) and to the feature that will exclude recent x days from the slope calculations.
In such a case what your experience?


#15

I haven't yet but I'll let you know if I do. Yourself?


#16

Yes I am aware of it, but there is no way to track anonymous (not logged in) users.


#17

@porcupine, no. I only looked at that code and considered the idea to port it to AB since I'm interested in testing stocks outside of USA market but unfortunately I have not done anything yet.


#18

@beppe @porcupine I too am interested in developing a good ranking method. It is also possible to use different weights for different measures, and of course "optimization" of the weights and/or lookback periods for measures of momentum may be worth a look. The optimization is not to overly curve fit results but to find out how robust those weights or look back periods are.

On the old forum a couple of posters (credit to "pinreader" and "jimguniii") asked about the Stock Charts "SCTR" ranking methodology developed by a well known (and probably very old) technical analyst John Murphy.

The discusssion,
https://groups.yahoo.com/neo/groups/amibroker/conversations/messages/197207

The underlying SCTR method,
http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:sctr

I made an attempt to help them code it but never totally finished as I was unfamiliar with the method (until months later when I looked it up).

I'll post my preliminary version which I think needs fine-tuning in case either of you want to follow-up on this. I would help but am currently overwhelmed by other projects.

// a work in progress

// forum posts, https://groups.yahoo.com/neo/groups/amibroker/conversations/messages/197245
// BUT not complete as all contributors need to be turned into a % percentage
///@link http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:sctr


// watchlist should contain all symbols included in the test
PPOShort = Param( "PPO Short Period", 12, 1, 150, 1 );
PPOLong = Param( "PPO Long Period", 26, 1, 150, 1 );
PPOsignal = Param( "PPOsignal", 9, 1, 150, 1 );

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

NumberOfTickers = StrCount( List, "," ) + 1;

if( Status( "stocknum" ) == 0 )
{
    // cleanup variables created in previous runs (if any)
    StaticVarRemove( "values*" );
    StaticVarRemove( "rank*" );


    for( n = 0; ( Symbol = StrExtract( List, n ) )  != "";  n++ )
    {

        SetForeign( symbol );

        // value used for scoring
		// BUT not complete as all contributors need to be turned into a % percentage
        PPO = ( EMA( Close, PPOShort ) - EMA( Close, PPOLong ) ) / EMA( Close, PPOLong );
        PPOS = ( EMA( ppo, PPOsignal ) );
        Val = PPO - PPOS;

        LT_EMA = 100*( Close - EMA( Close, 200 ) ) / EMA( Close, 200 ) * 0.3;
        LT_ROC = ROC( Close, 125 ) * 0.3;

        MT_EMA = 100*( Close - EMA( Close, 50 ) ) / EMA( Close, 50 ) * 0.15;
        MT_ROC = ROC( Close, 20 ) * 0.15;

        ST_PPO = ( Val - Ref( Val, -3 ) ) / 3 * 0.05;
        ST_RSI = RSI( 14 ) * 0.05;

        values = LT_EMA + LT_ROC + MT_EMA + MT_ROC + ST_PPO + ST_RSI;

        RestorePriceArrays();

        // write ranked values to a static variable
        StaticVarSet( "values" + symbol, values );
    }

    StaticVarGenerateRanks( "rank", "values", 0, 1224 );
}


symbol = Name();
values = StaticVarGet( "values" +  symbol );
rank = StaticVarGet( "rankvalues" +  symbol );

PercentileRank = 100 * ( NumberOfTickers - rank ) / ( NumberOfTickers - 1 );

// exploration code for verification
Filter = 1;
SetSortColumns( 4 );
AddColumn( values, "SCTR" );
AddColumn( rank, "Rank", 1.0, colorBlack, colorLightYellow );
AddColumn( PercentileRank, "PercentileRank" );
AddColumn( NumberOfTickers, "NumberOfTickers", 1.0 );

And on the current SP500 produces this kind of result,
image


#19

@portfoliobuilder your memory to recall interesting stuff in the old Yahoo group is quite remarkable! :clap::clap::clap:

I will for sure take a look. Thanks


#20

Thank you very much. I am still figuring out. Thanks.