Overall Statistics
Total Trades
954
Average Win
0.73%
Average Loss
-0.55%
Compounding Annual Return
7.609%
Drawdown
14.200%
Expectancy
0.391
Net Profit
229.019%
Sharpe Ratio
0.644
Probabilistic Sharpe Ratio
4.949%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
1.31
Alpha
0.067
Beta
0.011
Annual Standard Deviation
0.106
Annual Variance
0.011
Information Ratio
-0.131
Tracking Error
0.203
Treynor Ratio
6.42
Total Fees
$1011.39
Estimated Strategy Capacity
$62000.00
namespace QuantConnect 
{   
using System;
using System.Net;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Parameters;
using QuantConnect.Data.Custom;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Market;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Parameters;


    /// <summary>
    /// ETF Global Rotation Strategy
    /// </summary>
    public class ETFReplay: QCAlgorithm
    {

        // we'll hold some computed data in these guys
        List<SymbolData> SymbolData = new List<SymbolData>();
		
		[Parameter("Keeps")]
        public int Keeps = 3;
        
        [Parameter("CashReservePct")]
        public decimal CashReservePct = 0.010m;

		[Parameter("WeightRa")]
		public decimal WeightRa = 0.20m;
		
		[Parameter("WeightRb")]
		public decimal WeightRb = 0.50m;
		
		[Parameter("WeightV")]
		public decimal WeightV = 0.30m;    // vol weight (decimal percentage)
		
		[Parameter("FilterSMA")]
		public int FilterSMA = 8;		// MA filter, monthly length
		
		[Parameter("LookbackRa")]
		public string LookbackRa = "20D";    // return "a" lookback
		
		[Parameter("LookbackRb")]
		public string LookbackRb = "3M";     // return "b" lookback
		
		[Parameter("LookbackV")]
		public string LookbackV = "20D";    // volatility lookback          
		
		[Parameter("SymbolsUrl")]		
		public string Url = "";
		
		[Parameter("RebalancePeriod")]
		public string RebalancePeriod = "Daily";
		
		List<string> GrowthSymbols = new List<String>{
			// safety symbol
			"SHY"
		};
		
		private bool first = true;
		
		private DateTime LastRotationTime = DateTime.Now;

        /// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
        /// </summary>
        public override void Initialize()
        {
            SetCash(10000);
            SetStartDate(2005, 1, 1);
			
			
			SetGrowthSymbols();
			
			Debug("WeightRa: " + WeightRa);
			Debug("WeightRb: " + WeightRb);
			Debug("WeightV: " + WeightV);
			Debug("SymbolUrl: " + Url);
			Debug("RebalancePeriod: " + RebalancePeriod);
			
			int periodADays = DaysFromLookback(LookbackRa);
			int periodBDays = DaysFromLookback(LookbackRb);
			int periodVolDays = DaysFromLookback(LookbackV);
			Debug(String.Format("LookbackRa: {0} ({1} days)", LookbackRa, periodADays));
			Debug(String.Format("LookbackRb: {0} ({1} days)", LookbackRb, periodBDays));
			Debug(String.Format("LookbackV: {0} ({1} days)", LookbackV, periodVolDays));
			
            foreach (var symbol in GrowthSymbols)
            {
            	Debug("adding symbol to universe: " + symbol);
            	
				AddSecurity(SecurityType.Equity, symbol, Resolution.Daily);
                
                // TODO - add parameters for lookback periods
                var periodAPerf = MOMP(symbol, periodADays, Resolution.Daily);
                var periodBPerf = MOMP(symbol, periodBDays, Resolution.Daily);  // (252/12) * x months
                
                // FIXME -The moving average is calculated using the closing price from the last day of each month(e.g. a 10 month moving average has 10 datapoints)
                var filterSMA = SMA(symbol, 21*FilterSMA, Resolution.Daily);

				// we use log returns instead of definition in replay
                var std = new StandardDeviation(periodVolDays);
                decimal annualizationFactor = (decimal)Math.Sqrt(252);
                var logReturns = LOGR(symbol, periodVolDays, Resolution.Daily);
                CompositeIndicator<IndicatorDataPoint> volatility = std.Of(logReturns).Times(annualizationFactor);
				
                SymbolData.Add(new SymbolData
                {
                    Symbol = symbol,
                    ReturnA = periodAPerf,
                    ReturnB = periodBPerf,
                    Volatility = volatility,
                    FilterSMA = filterSMA
                });
            }

			// assumption we don't look back more than a year
            SetWarmup(TimeSpan.FromDays(365));
        }
		
		// Convert the lookback strings to number of days (e.g. 20D->20, 2M->42)
		public int DaysFromLookback (string period) {
			string strAmount = period.Substring(0, period.Length - 1);
			int amount = Int32.Parse(strAmount);
			
			int multiplier = period.EndsWith("M") ? 21 : 1;
			return multiplier * amount;
		}
		
		
		// Downloads the symbols from dropbox, file should contain each symbol on a new line and nothing else
		private void SetGrowthSymbols() {
		 //	using (var client = new WebClient())
   //         {
   //           	// fetch the file from dropbox
   //             var file = client.DownloadString(Url);
   //             if (file.Length > 0) {
   //             	string[] contents = file.Split(new string[] {"\n", "\r\n"}, StringSplitOptions.RemoveEmptyEntries);
			// 		foreach(var symbol in contents){
			// 			var cleaned = symbol.Trim();
			// 			// skip comments
			// 			if (cleaned.StartsWith("#")) continue;
						
			// 			Debug("Adding to universe: " + cleaned);
   //             		GrowthSymbols.Add(cleaned);
			// 		}
   //             }
			
			// }
			
			// Alpha symbols
			GrowthSymbols.Add("SPY");
			GrowthSymbols.Add("DVY");
			GrowthSymbols.Add("PFF");
			GrowthSymbols.Add("MDY");
			GrowthSymbols.Add("AGG");
			GrowthSymbols.Add("IWM");
			GrowthSymbols.Add("TBF");
			GrowthSymbols.Add("IEF");
			GrowthSymbols.Add("TIP");
			GrowthSymbols.Add("EUM");
			GrowthSymbols.Add("DBA");
			GrowthSymbols.Add("EFA");
			GrowthSymbols.Add("FXA");
			GrowthSymbols.Add("EEM");
			GrowthSymbols.Add("IDV");
			GrowthSymbols.Add("FXE");
			GrowthSymbols.Add("EFZ");
			GrowthSymbols.Add("SH");
			GrowthSymbols.Add("DBC");

			
		}
		
        private Dictionary<String, decimal> GetEtfReplayRankings()
        {
            // Rank according to ETF Replay rules (http://www.etfreplay.com/members/how_the_screener_works.aspx)
            var orderedReturnA = SymbolData.OrderByDescending(x => x.ReturnA);
            var orderedReturnB = SymbolData.OrderByDescending(x => x.ReturnB);
            var orderedVol = SymbolData.OrderBy(x => x.Volatility);
            var rankings = new Dictionary<String, decimal>();


            // add all the symbols
            foreach(var sd in SymbolData)
            {
                rankings[sd.Symbol] = 0.0m;
            }

            for(int i = 0; i < orderedReturnA.Count(); ++i)
            {
                var current = orderedReturnA.ElementAt(i);
                rankings[current.Symbol] += i * WeightRa;
            }
            for(int i = 0; i < orderedReturnB.Count(); ++i)
            {
                var current = orderedReturnB.ElementAt(i);
                rankings[current.Symbol] += i * WeightRb;
            }

            for(int i = 0; i < orderedVol.Count(); ++i)
            {
                var current = orderedVol.ElementAt(i);
                rankings[current.Symbol] += i * WeightV;
            }

            return rankings;            
        }

        public override void OnData(Slice data)
        {
            if (IsWarmingUp) return;

            Log("OnData slice: date=" + data.Time + ", cnt=" + data.Count);

            try
            {
                bool rebalance = false;
                if (first)
                {
                    rebalance = true;
                    first = false;
                }else
                {
                    // var delta = Time.Subtract(LastRotationTime);
                    // rebalance = delta > RotationInterval;
                    
                    rebalance = LastRotationTime.Month != Time.Month;
                    if (RebalancePeriod == "Quarterly") {
                        // January - 1
                        // April - 4
                        // July - 7
                        // Oct - 10
                        rebalance = (rebalance && 
                        		((Time.Month == 1) ||
                        		(Time.Month == 4) ||
                        		(Time.Month == 7) ||
                        		(Time.Month == 10)));
                    }
                    
                }

                if (rebalance)
                {
                    LastRotationTime = Time;

                    // pick which one is best from growth and safety symbols
                    //var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList();
                    var rankings = GetEtfReplayRankings();
                    var orderedObjScores = SymbolData.OrderBy(x => rankings[x.Symbol]).ToList();


                    List<String> nextHoldings = new List<string>();
                    
                    Debug("OrderedObjScores Count: " + orderedObjScores.Count());
                    for (int i = 0; i < Keeps; ++i)
                    {
                        var currentSymbolData = orderedObjScores[i];
                        var lastClose = Securities[currentSymbolData.Symbol].Close;
                        bool maFilter = lastClose > currentSymbolData.FilterSMA;
                        if (maFilter)
                        {
                            // this meets our investment criteria
                            nextHoldings.Add(currentSymbolData.Symbol);
                        }
                        
                        // FIXME - allocate to "Safety" symbol, right now saftey symbol is part of the growth symbols so it should bubble up but that doesn't match replay
                    }

                    Log(">>NextPositions<<");
                    foreach(var position in nextHoldings)
                    {
                        Log("\t>>" + position);
                    }

                    if (nextHoldings.Count == 0)
                    {
                        // goto 100% cash
                        Log("LIQUIDATE>>CASH");
                        Liquidate();

                    }else {
                        foreach(var kvp in Portfolio.Securities.Values)
                        {
                            // liquidate anything we are currently invested in but not part of our next portfolio state
                            if (kvp.Invested && !nextHoldings.Contains(kvp.Symbol))
                            {
                                Log("LIQUIDATE>>" + kvp.Symbol);
                                Liquidate(kvp.Symbol);
                            }
                        }

                        decimal allocationPercentage = (1.0m - CashReservePct) / Keeps;
                        foreach(var symbol in nextHoldings)
                        {
                            Log("BUY>>" + symbol);
                            SetHoldings(symbol, allocationPercentage);
                        }

                    }
                }
            }
            catch (Exception ex)
            {
                Error("OnData: " + ex.Message + "\r\n\r\n" + ex.StackTrace);
            }

        }
    }

    class SymbolData
    {
        public string Symbol;

        public MomentumPercent ReturnA { get; set; }
        public MomentumPercent ReturnB { get; set; }
        public CompositeIndicator<IndicatorDataPoint> Volatility { get; set; }
        public SimpleMovingAverage FilterSMA { get; set; }

    }

}