Go Back   NinjaTrader Support Forum > NinjaScript Development Support > Strategy Development

Strategy Development Support for the development of custom automated trading strategies using NinjaScript.

Reply
 
Thread Tools Display Modes
Old 02-10-2010, 02:39 PM   #1
MidnightRider228
Junior Member
 
Join Date: Feb 2010
Location: Pennsylvania
Posts: 9
Thanks: 0
Thanked 0 times in 0 posts
Default Relative Strength Sector Rotation Strategy

Attached is my first ever Ninjascript that implements my sector rotation strategy using the Select Sector ETFs

http://www.sectorspdr.com/

The strategy is based mainly on the work done by Mike Carr in his book "Smarter Investing in Any Economy: The Definitive Guide to Relative Strength Investing"

The strategy is as follows:

1.) Each week, calculate the relative strength rank of each ETF. This can be done in any number of ways. I chose to use a ratio of moving averages as done by Carr. The attached file contains the custom indicator used to calculate the RS rank.

2.) Hold the top X-number of ETFs based on relative strength rank.

3.) Sell these ETFs if they fall below a programmed sell-rank and replace with the highest ranked ETF given by that week's calculation.

I've also added a hedge factor to reduce drawdowns. I do not buy ETFs if the S&P is below a programmed moving average. 200-day SMA works well. When backtesting, call it on the "SPY" ETF which tracks the S&P. This is used as the hedging instrument.

I've been backtesting with Yahoo data and I'm showing cumulative profit of close to 400% since 1/1/1999--I'm still trying to optimize.

Also note that I had to manually add the SPDR ETFs via the Instrument manager.

Before it gets picked apart, please know it's my first time developing on the NT platform and this is also free I'm still trying to optimize the input parameters
Attached Files
File Type: zip SectorSPDRRotation.zip (5.9 KB, 349 views)
Last edited by MidnightRider228; 02-10-2010 at 03:01 PM. Reason: Added some notes
MidnightRider228 is offline  
Reply With Quote
Old 02-10-2010, 03:17 PM   #2
NinjaTrader_Josh
NinjaTrader Product Manager
 
NinjaTrader_Josh's Avatar
 
Join Date: May 2007
Location: Denver, CO
Posts: 17,460
Thanks: 1
Thanked 161 times in 82 posts
Default

MidnightRider228,

Thanks for the contribution!

One thing to note is that I don't think commissions are on a per execution basis? Probably would make more sense to do that off of a per entry order basis.
NinjaTrader_Josh is offline  
Reply With Quote
Old 02-10-2010, 08:57 PM   #3
MidnightRider228
Junior Member
 
Join Date: Feb 2010
Location: Pennsylvania
Posts: 9
Thanks: 0
Thanked 0 times in 0 posts
Default Date of Execution

Josh,

One thing I noticed while backtesting is that if I print the date in OnExecution it's the day I submitted the order as if its filled instantly, but it shows up as executing on the next trading day in the Strategy Analyzer window... why is that?
MidnightRider228 is offline  
Reply With Quote
Old 02-11-2010, 08:07 AM   #4
NinjaTrader_Bertrand
NinjaTrader Customer Service
 
NinjaTrader_Bertrand's Avatar
 
Join Date: Sep 2008
Location: Germany
Posts: 25,298
Thanks: 414
Thanked 1,562 times in 1,528 posts
Default

MidnightRider228, this is unfortunately expected - the order will be executed on the next possible trade location after your bar has closed, which for your daily chart approach is the open of the next day then.
NinjaTrader_Bertrand is offline  
Reply With Quote
Old 02-12-2010, 07:57 AM   #5
MidnightRider228
Junior Member
 
Join Date: Feb 2010
Location: Pennsylvania
Posts: 9
Thanks: 0
Thanked 0 times in 0 posts
Default

That still does explain why OnExecution() prints the wrong time the order was executed. I would think that the routine would be ran after execution, not before.
MidnightRider228 is offline  
Reply With Quote
Old 02-12-2010, 09:07 AM   #6
NinjaTrader_Josh
NinjaTrader Product Manager
 
NinjaTrader_Josh's Avatar
 
Join Date: May 2007
Location: Denver, CO
Posts: 17,460
Thanks: 1
Thanked 161 times in 82 posts
Default

What are you using to print? You likely are not printing the execution time, but just a Time[] time.

To get execution time you need to access the execution.Time property.
NinjaTrader_Josh is offline  
Reply With Quote
Old 10-23-2010, 04:37 PM   #7
CCVirginia
Junior Member
 
Join Date: Oct 2010
Posts: 3
Thanks: 0
Thanked 0 times in 0 posts
Default

MidnightRider,

Very interesting that you are doing rotation (and seem to be the first, which is surprising). I am trying to backtest various strategies such as rotation, candle patterns, trend following and stacked timeframes - I have been evaluating tradestation & metastock but these strategies seem very hard to do in TS and the backtesting is MS seems weak.

So, like you, I am experimenting with ninja - but I am not as far along so I really appriciate the work you have already put into this. I have a lot of software background but none with NT or C# (But it is a lot like Java and C++). I hope to be able to contribute to this investigation.

First try it didn't work, but as a NT new guy it is probably my fault - no trades happen.

You may also want to check out this: http://www.copstrat.com/ - this person also seems to have done a lot of work on validating a rotation strategy that returns about 20% with infrequent trading.

One suggestion - it would be great if the list of stocks to rotate could be in an instrument list, not that I know how to access one from a script {perhaps support could lend a hand here!}
CCVirginia is offline  
Reply With Quote
Old 03-14-2011, 01:42 AM   #8
sf631
Junior Member
 
Join Date: Oct 2010
Posts: 19
Thanks: 0
Thanked 0 times in 0 posts
Default

@Midnight, thanks for sharing. This is actually exactly what I'm looking to do (not RSI but rotation).

I'm trying a slightly different approach that I had used successfully in my previous backtesting environment (WealthLab), also C# based. Let me preface by saying that I'm not a good programmer, or even mediocre. The below is something that I got to work from various reference code bits, I don't completely understand why they work.

The approach uses List.Sort() methods on lists of holders, each of which contains an index i, a symbol name (not needed just for debugging, and a ranking score. The holder objects are bin sorted much like you're doing except using the Compare() method available in the System.Collections.General library.

The benefit of this approach is that you don't have to maintain your own sorting method. The downside is that I'm not able to get it to work in NT. I keep getting the following error message:
Code:
**NT** Error on calling 'OnBarUpdate' method for strategy 'FirstRotationPDStrategy/f13b47a4902142b6ae99d89a39e4fdd1': Failed to compare two elements in the array.
within the output window.

If anyone is familiar with these methods and can spot something that I'm missing in the below skeleton code, I'd greatly appreciate the knowledge. @Midnight, maybe there are parts of this reference code that you'd want to borrow too.

Code:
// This namespace holds all strategies and is required. Do not change it.
namespace NinjaTrader.Strategy
{
    public class FirstRotationPDStrategy : Strategy, IComparer<PDHolder> //Creates IComparer called PDHolder
    {
        #region Variables
               int number = 3;
        List<PDHolder> list = new List <PDHolder>(); // Unsure if this is necessary too????
        #endregion

        protected override void Initialize()
        {
            CalculateOnBarClose = true;

             //Add DailyBars of the investment universe
            Add("ABC", PeriodType.Day, 1);    //1 
            Add("DEF", PeriodType.Day, 1);    //2
            Add("GHI", PeriodType.Day, 1);  //3
            Add("JKL", PeriodType.Day, 1);    //4
            Add("MNO", PeriodType.Day, 1);  //5
            Add("PQR", PeriodType.Day, 1);  //6
            Add("STU", PeriodType.Day, 1);  //7
            Add("VWX", PeriodType.Day, 1);  //8
            Add("YZZ", PeriodType.Day, 1);  //9
            Add("ZYX", PeriodType.Day, 1);  //10
    
    }


        protected override void OnBarUpdate()
        {
        
                    
            
            for (int i = 0; i < 9; i++)  //Loop to go thru each symbol

            { 
                PDHolder holder = new PDHolder();
                holder.index = i;
                holder.score = ScoringIndicator(BarsArray[i]).ScorePane[0]; // a Call to indicator that generates score
                holder.symbol = BarsArray[i].Instrument.FullName;
                
                list.Add(holder);
                
            } 
            
            List <PDHolder> listShort = new List<PDHolder>(list);
            listShort.Sort();
            
            List <PDHolder> listLong = new List<PDHolder>(list);
            listLong.Sort();
            listLong.Reverse();

            int c = list.Count;

            for(int r = 1; r<= c - number; r++)
            {
                listShort.RemoveAt(0);
                listLong.RemoveAt(0);
            }
   
            ///  TRADE EXITS HERE
            ///  for each current position, check to see ifin the listShort, 
            ///  if not then exit short position - if still in the list, leave in place    
            
            ///  for each current position, check to see if in the listLong, 
            ///  if not then exit long position - if still in the list, leave in place    


            ///  TRADE ENTRIES HERE
            ///  for each holder in listShort, check to see if already short
            ///  if not, enter a short position.  If so, do nothing

            ///  for each holder in listLong, check to see if already long
            ///  if not, enter a long position.  If so, do nothing

        }
 
    public int Compare(PDHolder item1, PDHolder item2)
    {
        return item1.score.CompareTo(item2.score);
    } 
        
    }

    public class PDHolder // defines a holder object
    {
        internal string symbol;
        internal double PD;
        internal double score;
        internal double index;
    } 
}
sf631 is offline  
Reply With Quote
Old 06-07-2011, 05:57 AM   #9
madhatter
Junior Member
 
Join Date: Dec 2010
Posts: 1
Thanks: 0
Thanked 0 times in 0 posts
Default

@sf631, thanks a million for your code, I found it most helpful. I'm also getting the same sort error and can't figure out a way around it... did you ever resolve that error?
madhatter is offline  
Reply With Quote
Old 10-26-2012, 08:16 PM   #10
oyz79
Member
 
Join Date: Aug 2009
Posts: 35
Thanks: 0
Thanked 0 times in 0 posts
Default

Thanks for the code.

How would one calculate based on historical returns? For example, purchase the top 2 ETFs based on the strongest returns over the past xx bars?
oyz79 is offline  
Reply With Quote
Old 11-13-2012, 08:38 PM   #11
fx_pete
Member
 
Join Date: Jan 2010
Posts: 36
Thanks: 2
Thanked 2 times in 2 posts
Default My ETF rotation script

Hi I am doing something very similar based on http://www.etfreplay.com

They use a relative strength + volatility based rating. I am sorting by returns for two different periods (A, B) and volatility over period V.

I had the code working and for some reason it stopped. The BarsInProgress var stays at 0 even though BarsArray has 10 elements in it. I can't understand why. The only thing I did is redefined session templates under instrument manager and downloaded more data from kinetic.

I would appreciate if someone could look at this prelim code and see if I am doing something stupid that I have overlooked.

Since I was only playing around I coded it as an indicator where you would be able to selects ETFs defined by an ENUM that would then be added to the primary data series. One would then be able to select the dates and the calculations would be done. For now the dates are hard coded and the script looks for the bar# of the selected date and does the computation. Print() statements are used to debug the code ... I have included a screen shot from yesterday when it was working ,,, today it only runs BarsInProgress = 0 and does't even end up doing the computation as it is called only once BarsInProgress == BarsArray.Length - 1

Please any input would be appreciated!


Code:
       protected override void Initialize()
       {		
                       // instantiate the etf collections		
			etf = new List<EtfData>();
			
			// make sure this charts series is added to the list
			if (!etf.Exists(delegate(EtfData item) { return item.Label == Instrument.FullName; }))
				etf.Add(new EtfData(Instrument.FullName));							

			// fill the collections with the selected ETFs	
			if (etfsel != EtfType.ALL)
			{		
				if (!etf.Exists(delegate(EtfData item) { return item.Label == etfsel.ToString(); }))
					etf.Add(new EtfData(etfsel.ToString()));							
			}
			else
			{// add all the ETFs ...
				
				// ... by itterating over the values in the enum
				foreach (EtfType value in Enum.GetValues(typeof(EtfType)))
				{
					if (value == EtfType.ALL) 
						continue;	// with the exception of EtfType.ALL ...
					
					if (!etf.Exists(delegate(EtfData item) { return item.Label == value.ToString(); }))
						etf.Add(new EtfData(value.ToString()));							
				}
			}
			
			// debug: added etfs
			
				Print("added etf(s) :");
				foreach (EtfData item in etf)
					Print(item.Label);
				Print("");
				ClearOutputWindow();
						

			
		    // go through the collection of ETFs and add them as dataseries to BarsArray
			// note: the indicator's chart series is already in BarsArray on index 0, 
			// with each addition the corresponding value of BarsInProgress increases by 1 
			foreach (EtfData item in etf)
			{
				if (item.Label == Instrument.FullName) 
					continue; // dont add it (already in by default)
				else
					Add(item.Label, PeriodType.Day, ndays);
			}
			
			// temp code ...
			endDate = new DateTime(2012, 11, 05);
			startDate = new DateTime(2012, 08, 05);
			// .............
			
			// create the DateTimeSeries for getting the date of current bar
			date = new DateTimeSeries(this);
			
            Overlay	= false;	
			
		}
				
        /// <summary>
        /// Called on each bar update event (incoming tick)
        /// </summary>
        protected override void OnBarUpdate()
        {					
			// make sure we have the right bars period type
			if (BarsPeriod.Id != PeriodType.Day && BarsPeriod.Value < 1)
				return;	
	
			if (CurrentBar == 0)
			{
				Print("");
				Print("all data series in BarsArray :");
				for (int i = 0; i < BarsArray.Length; i++)
					Print(BarsArray[i].Instrument.FullName);
				Print("");				
			
				Print("");
				Print("start date: " + startDate.ToString());		
				Print("end date: " + endDate.ToString());
				Print("");			
				
			}
				
			// make sure there are enough bars
			if (CurrentBar <= GetMaxPeriod()) // in this case <= because [barsago+1] below
				return;

			/// Bars are already loaded and OnBarUpdate() will run only on a new bar !
			/// need to use GetBar() to get data for bars with the correct dates
			
			int endbar   = Bars.GetBar(endDate);
			int barsago  = CurrentBar - endbar;
			int barsagoA = barsago + periodA;
			int barsagoB = barsago + periodB;
					
			if (CurrentBar == endbar)
			{// perform computation once CurrentBar # and endDate bar # match
				
				// debug: flow control
					
					Print("Processing ETF << " + etf[BarsInProgress].Label + " >>");
					Print("BarsInProgress = " + BarsInProgress.ToString());
					Print("Current bar is # " + CurrentBar.ToString()); 
					Print("Current end bar is # " + CurrentBar.ToString()); 
					Print("GetMaxPeriod() = " + GetMaxPeriod().ToString());
					Print("");
				
				// degug: start/end bar prices
				/*
					Print("start bar close : " + Close[barsagoA].ToString());		
					Print("end bar close   : " + Close[barsagoB].ToString());
				*/
								
				/// use BarsInProgress to make sure it is saved to the correct data series
				
				// get the 1st return
				double rA = (Close[barsago] - Close[barsagoA]) / Close[barsagoA];
				
				// get the 2nd return
				double rB = (Close[barsago] - Close[barsagoB]) / Close[barsagoB];
			
				// volatility calculation ...
				// a) get the individual returns for the defined period and 
				// b) store them in the data set
				double[] vreturns = new double[periodV];
				for (int i = barsago + (periodV-1); i >= 0; i--) // itterate down from 19 to 0 values ago from barsago (20 values)
					vreturns[barsago] = ((Close[barsago] - Close[barsago+1]) / Close[barsago+1]);
				
				// compute  volatility
				// a) using standard deviation and 
				// b) anualize it by multipling it by 252 
				//    where 252 = trading days in a year 
				double v = StandardDeviation(vreturns, periodV) * Math.Sqrt(252);				
				
				// save results to etf data
				etf[BarsInProgress].ReturnA = rA;		
				etf[BarsInProgress].ReturnB = rB;		
				etf[BarsInProgress].Volatility = v;			
				
				if (BarsInProgress == etf.Count-1) // with all series saved
				{ // go ahead computing the rank by ...
				
					// make a copy of the original data
					List<List<EtfData>> data = new List<List<EtfData>>();

					// add 3 copies of etf data (one for each sort)
					data.Add(etf);
					data.Add(etf);
					data.Add(etf);
					
					// sort the data by returns (A, B) and Volatility (V)
					// ... need to reverse order for sorting by returns because we need high to low (default sort is low to high)
					foreach(List<EtfData> container in data)
					{
						switch(data.IndexOf(container))
						{
							case A: 
								container.Sort( delegate (EtfData item1, EtfData item2) 
									{ return item1.ReturnA.CompareTo(item2.ReturnA); });
								container.Reverse();
							break;
							case B: container.Sort( delegate (EtfData item1, EtfData item2) 
									{ return item1.ReturnB.CompareTo(item2.ReturnB); });
								container.Reverse();
							break;
							case V: container.Sort( delegate (EtfData item1, EtfData item2) 
									{ return item1.Volatility.CompareTo(item2.Volatility); });
							break;
						}
					}
					
					
					// compute rankby order of all data
					foreach(List<EtfData> container in data)
					{
						foreach (EtfData item in container)
						{		
							switch(data.IndexOf(container))
							{
								case A: item.RankByA = container.IndexOf(item); break;
								case B: item.RankByB = container.IndexOf(item); break;
								case V: item.RankByV = container.IndexOf(item); break;
							}
						}
					}
				
					// compute overall rank	using weights & save the results
					foreach(EtfData item in etf)
					{
						item.RankByA = data[0].Find(delegate (EtfData d) { return d.Label == item.Label; }).RankByA;
						item.RankByB = data[0].Find(delegate (EtfData d) { return d.Label == item.Label; }).RankByB;
						item.RankByV = data[0].Find(delegate (EtfData d) { return d.Label == item.Label; }).RankByV;
						item.FinalRank = item.RankByA*weightA + item.RankByB*weightB + item.RankByV*weightV;  
					}
				
				
					// sort the final etf results
					etf.Sort( delegate (EtfData item1, EtfData item2) 
						{
							return item1.FinalRank.CompareTo(item2.FinalRank);
						}
					);
								
				
					// etf should now contain the final result order !
						
					// debug: contents of data
					
						Print("");
						Print("*********************");
						Print("       RESULTS       ");
						Print("*********************");		
						foreach (EtfData item in etf)
						{
							Print("# " + (etf.IndexOf(item) + 1).ToString()+ " Etf << " + item.Label + " >>");
							Print("RA = " + (item.ReturnA * 100).ToString("0.0"));
							Print("RB = " + (item.ReturnB * 100).ToString("0.0"));
							Print("V  = " + (item.Volatility * 100).ToString("0.0"));
							Print("");
						}
						Print("");		
				}
			
			}		
        }
Attached Images
File Type: png EtfRank - results.png (857.1 KB, 76 views)
Attached Files
File Type: cs MyEtfRank.cs (23.7 KB, 36 views)
fx_pete is offline  
Reply With Quote
Old 05-06-2013, 07:53 AM   #12
kalkanis
Junior Member
 
Join Date: Feb 2013
Posts: 21
Thanks: 0
Thanked 1 time in 1 post
Default

Hi fx_pete. I am very interested in your work too. Did you find a way to fix your code?
kalkanis is offline  
Reply With Quote
Old 05-07-2013, 02:58 PM   #13
nvtrader
Junior Member
 
Join Date: Mar 2013
Posts: 3
Thanks: 0
Thanked 0 times in 0 posts
Default Fildelity Select Trend System

I took midnightrader's code and modified it to implement a trend following system with the Fildelity select sector funds.
My contribution back to the community

I'm new to NinjaTrader and have a few questions:
1) When you backtest with multiple instruments the percentage numbers (drawdown, cumulative profit etc.) in the summary of the strategy tester seems to be messed up. So I had to print the peak to valley drawdown on the output screen. You can see that in the attached code. Is there a way to fix that so that it's doesn't have to be manually calculated?

2) To access multiple instruments, do you have to necessarily add all of them to the chart with Add() method? Is there a way to access the price data of multiple instruments without adding them to the chart? I can't imagine adding 2K instruments say if I wanted to access the price data of all Russell 2K stocks in the strategy.
Attached Files
File Type: cs FidelitySectorTrendSystem.cs (9.3 KB, 73 views)
nvtrader is offline  
Reply With Quote
Old 05-08-2013, 08:00 AM   #14
NinjaTrader_Bertrand
NinjaTrader Customer Service
 
NinjaTrader_Bertrand's Avatar
 
Join Date: Sep 2008
Location: Germany
Posts: 25,298
Thanks: 414
Thanked 1,562 times in 1,528 posts
Default

nvtrader, thanks for the shared script and welcome to our forums.

For basket testing those #'s would be a weighted average in the report, so different from what you've calculated manually - we have this for potential enhancement on our suggestion lists for our next major update.

Correct, all instruments would need to be added if you wanted to have programmatic access to them from the script - they would not be charted through but programmatically added internally only so the script could use them for it's calculations.
NinjaTrader_Bertrand is offline  
Reply With Quote
Old 05-08-2013, 08:54 AM   #15
nvtrader
Junior Member
 
Join Date: Mar 2013
Posts: 3
Thanks: 0
Thanked 0 times in 0 posts
Default

Bertrand,

Thanks for the prompt reply. There's lot of good stuff here.

I have one more question. But it does not belong to this thread. I will create a separate thread and ask the question.
nvtrader is offline  
Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
NT7 Relative Strength/ Spread Charting? pedroprada@bellsouth.net Suggestions And Feedback 9 11-17-2010 03:03 AM
Has anyone developed a framework for testing a sector rotation strategy? mmccanlies Strategy Development 7 02-05-2010 08:01 AM
Charting Relative Strength b/w 2 securities Crumbs Charting 1 01-23-2010 04:45 PM
Index Relative Strength tbone4 Strategy Development 3 02-01-2009 10:32 AM
Relative Strength npperez Indicator Development 6 12-06-2008 05:06 PM


All times are GMT -6. The time now is 01:31 AM.

FULL RISK DISCLOSURE: Futures trading contains substantial risk and is not for every investor. An investor could potentially lose all or more than the initial investment. Risk capital is money that can be lost without jeopardizing ones financial security or lifestyle. Only risk capital should be used for trading and only those with sufficient risk capital should consider trading. Past performance is not necessarily indicative of future results. View Full Risk Disclosure.