I’m playing with rotational strategies. Right now I am using the S&P500 sector etfs such as XLY, XLF etc in my backtesting. i have the complete code at the bottom of this post.
I wanted to see how a rotational strategy would perform rotating sectors on a monthly basis, or an x * monthly basis. The problem is that I am unable to get a backtest result. I have isolated the problem to the folloiwng code:
NumSymbols = CategoryCountSymbols( categoryWatchlist, SectorETFs );
MaxNumberSymbols = Param( "Displayed Symbols", 5, 0, NumSymbols, 1 );
freq = Param("Holding Period in Months", 3, 1,12, 1 ) * 22;
lookback = Param("Momentum Lookback Period in Days", 5, 1, 20, 1 );
CountMonths = Cum(Month() != Ref(Month(), -1 ));
//Plot(Cum(Month()),"counting months",colorOrange);
nMonth = CountMonths % freq == 0; // % brings back the remainder of CountMonths/freq. If equal to zero, then that is the month to rotate sectors/stocks
nMonthPulse = ExRem(nMonth, ! nMonth) ;
For some reason, the pulses generated from these lines do not work for PositionScore, even though I am able to plot the pulses. if I use the pulses from the following code, the backtester works fine:
UpCondST = MACD()>0;
UpPulseST = ExRem(UpCondST, ! UpCondST);
So, if I substitute nMonthPulse with UpPulse in the PositionScore function I get a backtest result. I’ve played around with the nMonthPulse code and it appears to be in the Cum() function. So, I am stumped to why pulses from nMonthPulse don’t work and they do with UpPulseST.
I used another way of achieving a somewhat monthly pulse by the following code:
pi = 3.1415926;
LPV[0]=-1; //initializing the first lunar phase value
LPV90 = 0;
MonthDays=30 * freq; // this is one complete 2 pi radian lunar phase cycle
//First New Moon: Jan 1 1900 7:52 AM. 7:52 AM is 0.3278 of a day.
TD = DaysSince1900(); // today's date as a serial number of days since first new moon. Jan 1 1900 is serial number 2, so I have to compensate for that.
// I also have to subtract 0.3278 from the day count to compensate for the 7:52 AM.
LPV = sin((TD-(2-0.3278))/MonthDays * 2 * pi); // Gives position in Lunar cycle for each day of data
LPV90 = cos((TD-(2-0.3278))/MonthDays * 2 * pi);
MoonSquare= LPV > 0;
MoonPulse = ExRem(MoonSquare, ! MoonSquare);
The pulses from this code work well also.
Any help to further my understanding will be greatly appreciated. Thank you!
I set the watchlist ( I used watchlist number 63) for the following symbols:
XLY, XLK, XLI, XLB, XLE, XLP, XLV, XLU, XLF, SPY
SetBarsRequired( sbrAll, sbrAll ); // Ensure that when I use the zoom feature on the charts
// that my indicators do not change.
// counting symbols in a watchlist
function CategoryCountSymbols( category, number )
{
NumCommas = StrCount( list = CategoryGetSymbols( category, number ), "," );
return IIf( list == "", 0, NumCommas + 1 );
}
SetForeign( "SPY" );
UpCondST = MACD()>0;
UpPulseST = ExRem(UpCondST, ! UpCondST);
DownCondST = ! UpCondST;
DownCondLT = MACD(12,50);
Plot( UpCondST, "UpPulse", colorblue );
SectorETFs = 63; // Associating the Watchlist Number to the Watchlist Name
NumSymbols = CategoryCountSymbols( categoryWatchlist, SectorETFs );
MaxNumberSymbols = Param( "Displayed Symbols", 5, 0, NumSymbols, 1 );
freq = Param("Holding Period in Months", 3, 1,12, 1 ) * 22;
lookback = Param("Momentum Lookback Period in Days", 5, 1, 20, 1 );
CountMonths = Cum(Month() != Ref(Month(), -1 ));
//Plot(Cum(Month()),"counting months",colorOrange);
nMonth = CountMonths % freq == 0; // % brings back the remainder of CountMonths/freq. If equal to zero, then that is the month to rotate sectors/stocks
nMonthPulse = ExRem(nMonth, ! nMonth) ;
RestorePriceArrays();
SymbolList = CategoryGetSymbols( categorywatchlist, SectorETFs );
if( Status( "stocknum" ) == 0 )
{
StaticVarRemove( "Performance*" );
for( i = 1; ( CurrentSymbol = StrExtract( SymbolList, i - 1 ) ) != "SPY"; i++ ) // First symbol is at position zero in SymbolList
{
SetForeign( CurrentSymbol ); // Goes back to UpCondST to find out how many bars to Spy low from that date
Performance = C / Ref( C, -1 * lookback ) ; // The formula for momentum is actually C / Ref( C, -1 * lookback) -1. I removed the "-1" to keep the result a positive number and proportionate to the rank of each stock.
//Performance = Ref( C, -1 * lookback ) / C; // Inverse momentum. This puts the slowest momentum stock as the top rank.
RestorePriceArrays();
StaticVarSet( "Performance" + CurrentSymbol, Performance );
}
}
Symbol = Name();
Performance = StaticVarGet( "Performance" + Symbol );
SetBacktestMode( backtestRotational );
SetOption( "MaxOpenPositions", MaxNumberSymbols );
SetOption( "WorstRankHeld", MaxNumberSymbols );
PositionScore = IIf( DownCondLT AND DownCondST, scoreExitAll,
IIf( nMonthPulse, Performance, scoreNoRotate ) );
/*PositionScore = IIf( DownCondLT AND DownCondST, scoreExitAll,
IIf( UpPulseST, Performance, scoreNoRotate ) ); */
SetPositionSize( 100 / MaxNumberSymbols, spsPercentOfEquity );
Plot( nMonthPulse+1,"Month",colorGreen);