CustomMenu

Wednesday, March 18, 2015

Momentum Rotation Strategies and Data - Part 3

In this post we will look at the results for a Momentum Rotation strategy that ranks funds based on the sum of two rate-of-change (ROC) values.  We will use the same portfolio of ETFs discussed in Part 1 and Part 2 of this series.

So far this series has started a number of good conversations, trying to find an explanation for my results.  I appreciate all of your comments, and would be delighted if you could find an alternate explanation for my results!  At the end of this post I will include the AFL code I used in AmiBroker to generate the results in Part 2.  The primary AFL indicator that I am using for rotation ranking is ROC.

The strategy discussed in this post works as follows.  On the second to last trading day of the month, the strategy calculates the 60 day ROC and 120 day ROC for each fund in the portfolio based on closing prices on that day.  It adds these two values together, and if the sum is negative it reassigns the sum a value of 0.  It then ranks all 10 funds, selecting the fund with the highest rank.  If all funds have a rank of 0, the system will move to cash.  On the last trading day of the month, it executes the buy and sell orders at the close - "market on close" orders in live trading.

In the diagram below, three equity curves are displayed for a 60 day / 120 day dual momentum Rotation System.  

60 Day / 120 Day Momentum Rotation Strategy Equity Curves
(click to enlarge)
The blue curve labeled "Adjusted" is the equity curve generated from our momentum system using dividend and split adjusted data.  The signals and P&L are derived only from the adjusted data time series.  The red curve labeled "Actual" is the equity curve generated from our momentum system using only split adjusted data, that has not been adjusted for dividends.  It is lower, as expected, since dividends are not included.  The green curve labeled "Hybrid" is the equity curve generated from our momentum system using the "Actual" time series for signal generation and the "Adjusted" time series for the P&L calculation.  This is a two step process...first the system is run across the "Actual" time series data to derive the trade dates and ETFs selected.  The second step uses the dates and ETFs selected in step 1 to to calculate P&L from the "Adjusted" time series data.

What we can see in the next two images are the signals, or ETFs selected, by the strategy from the "Actual" data set and the "Adjusted" data set.  The chart below shows the ETFs selected (and held) by the strategy by date using the "Actual" time series data.

(click to enlarge)

The chart below shows the ETFs selected (and held) by the strategy by date using the "Adjusted" time series data.  Similar to the 60 day momentum strategy, the signals do not match.

(click to enlarge)

As I mentioned to some readers today, I am using Yahoo adjusted data in my "Adjusted" data database, and Yahoo non-adjusted data in my "Actual" data database.  Here are two links from Yahoo that discuss their data source for historical data (it's CSI), and their approach for dividend adjusting data:


Also, here is the AmiBroker AFL code that was used in Part 2 of this series:

SetBacktestMode( backtestRotational );

// 1 ###### BACKTESTER SETTINGS - 1. GENERAL TAB 
SetOption("InitialEquity", 100000);
SetOption("MinShares", 1);
SetOption("MinPosValue", 0);
SetOption("FuturesMode", False);
SetOption("AllowPositionShrinking", False);
SetOption("ActivateStopsImmediately", False);
SetOption("ReverseSignalForcesExit", False);
SetOption("AllowSameBarExit", False);
RoundLotSize = 0;
TickSize = 0;
MarginDeposit = 0;
PointValue = 1;
SetOption("CommissionMode", 2);
SetOption("CommissionAmount", 7.95);
SetOption("InterestRate", 0);
SetOption("AccountMargin", 100);
SetOption("MarginRequirement", 100);

// 2 ###### BACKTESTER SETTINGS - 2. TRADES TAB 
BuyPrice = SellPrice = ShortPrice = CoverPrice = Close;
SetTradeDelays( 1, 1, 1, 1);

// 5 ###### BACKTESTER SETTINGS - 5. PORTFOLIO TAB 
//SetOption("MaxOpenPositions",   1);
// check the box to "Add artificial future bar..."
// Limit trade size as % - use 10 for live trading
// check the box to "Disable trade size limit..."
SetOption("UsePrevBarEquityForPosSizing", False);
SetOption("UseCustomBacktestProc",  False);

// 6 ###### BACKTESTER SETTINGS - 6. WALK FORWARD TAB
//SetOption("WorstRankHeld",    1);


Totalpositions = 1;
SetOption("WorstRankHeld", 1);
SetOption("MaxOpenPositions", Totalpositions );
PositionSize = -100 / Totalpositions ;

LastDayOfMonth = IIf( (Month() == Ref( Month(), 1) AND (Month() != Ref( Month(), 2)) ), 1, 0);
TradeDay = LastDayOfMonth ;

Score = ROC(Close, 60);
PositionScore = IIf(Score < 0, 0, Score ); // Long only
PositionScore = IIf(TradeDay , PositionScore , scoreNoRotate);

//Exploration
Filter = 1;
AddColumn(Score ,"Score",1.1);
AddColumn(PositionScore ,"PositionScore ",1.1);
AddColumn(PositionSize ,"Position Size",1.1);

You can download the AFL code above from my Google Drive: 00_60DayMomentum.afl

If you don't want to miss my new blog posts, follow my blog either by email or by RSS feed.  Both options are free, and are available on the top of the right hand navigation column under the headings "Follow By Email" and "Subscribe To RSS Feed".  I follow blogs by RSS using Feedly, but any RSS reader will work.

6 comments:

DR said...
This comment has been removed by the author.
DR said...
This comment has been removed by the author.
Neha said...

Many thanks for the article.
However Line no 43 and 44 which are responsible for giving us the last trading day of the month are not working for me. Can you please help. I would like to stress on the point that I am using the above two lines as a stand alone code and not as a part of the whole program, as my requirement is only to findout the last trading day of the month. Some help from your side would be highly appreciated.

Thanks & Regards,
Neha

Neha said...

Sir,

This is in response to my previous post some time back. I think I figured out the answer to my own question.
Check the following code:

SecondLastDayOfMonth = IIf( (Month() == Ref( Month(), 1) AND (Month() != Ref( Month(), 2)) ), 1, 0);
LastDayOfMonth = BarsSince(SecondLastDayOfMonth) + 1;
LastDayOfMonth= ExRem(LastDayOfMonth, SecondLastDayOfMonth);
PlotShapes(IIf(LastDayOfMonth, shapeUpArrow, shapeNone), colorYellow, 0, yposition = Low);

Let me know and sorry for the trouble 🙂

Regards,
Neha

Dave R. said...

Hi Neha,

You are correct, the formula is checking for the second to last day of the month.

The AFL script has trade delays set to 1, which means that the actual trade occurs on the last day of the month, using the signal from the second to last day of the month. This is how I actually use the system, and is why the backtest AFL is structured in this way.

For example, let's look at July, 2016. The last day of the month was Friday, July 29. I would calculate the signals after the close on Thursday, July 28. These signals would determine which product I would trade on Friday, July 29. On the morning of July, 29 I would enter a market-on-close (MOC) order using the signals from Thursday.

Thanks,
Dave

Neha said...

Sir,

Thank you so much for the explanation. I get one more way of attending to my problem. Thanks once again.

Regards,
Neha

Post a Comment