Overall Statistics
Total Trades
43
Average Win
5.67%
Average Loss
-3.88%
Compounding Annual Return
25.393%
Drawdown
14.700%
Expectancy
0.716
Net Profit
203.658%
Sharpe Ratio
1.033
Loss Rate
30%
Win Rate
70%
Profit-Loss Ratio
1.46
Alpha
0.261
Beta
-0.021
Annual Standard Deviation
0.249
Annual Variance
0.062
Information Ratio
0.344
Tracking Error
0.297
Treynor Ratio
-12.42
using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Models;

namespace QuantConnect.Rotation
{
    public class GlobalRotation : QCAlgorithm
    {
        // we'll use this to tell us when the month has ended
        DateTime LastRotationTime = DateTime.MinValue;
        TimeSpan RotationInterval = TimeSpan.FromDays(30);
        
        // these are the growth symbols we'll rotate through
        List<string> GrowthSymbols = new List<string>
        {
            "MDY", // US S&P mid cap 400
            "IEV", // iShares S&P europe 350
            "EEM", // iShared MSCI emerging markets
            "ILF", // iShares S&P latin america
            "EPP"  // iShared MSCI Pacific ex-Japan
        };

        // these are the safety symbols we go to when things are looking bad for growth
        List<string> SafetySymbols = new List<string>
        {
            "EDV", // Vangaurd TSY 25yr+
            "SHY"  // Barclays Low Duration TSY
        };

        // we'll hold some computed data in these guys
        List<SymbolData> SymbolData = new List<SymbolData>();

        public override void Initialize()
        {
            SetCash(25000);
            SetStartDate(2008, 1, 1);
            SetStartDate(2010, 1, 2);

            foreach (var symbol in GrowthSymbols.Union(SafetySymbols))
            {
                // ideally we would use daily data
                AddSecurity(SecurityType.Equity, symbol, Resolution.Minute);
                SymbolData.Add(new SymbolData
                {
                    Symbol = symbol
                });
            }
        }

        private bool first = true;
        public void OnData(TradeBars data)
        {
            try
            {
                TradeBar bar = data.First().Value;

                // the first time we come through here we'll need to do some things such as allocation
                // and initializing our symbol data
                if (first)
                {
                    first = false;
                    LastRotationTime = bar.Time;
                    InitializeSymbolDataAndHoldings(data);
                    return;
                }

                var delta = bar.Time.Subtract(LastRotationTime);
                if (delta > RotationInterval)
                {
                    LastRotationTime = bar.Time;

                    // compute performance for each symbol
                    UpdateSymbolData(data);

                    // pick which one is best from growth and safety symbols
                    var orderedObjScores = SymbolData.OrderByDescending(x => x.ObjectiveScore).ToList();
                    foreach (var orderedObjScore in orderedObjScores)
                    {
                        Log(">>SCORE>>" + orderedObjScore.Symbol + ">>" + orderedObjScore.ObjectiveScore);
                    }
                    var bestGrowth = orderedObjScores.First();

                    if (bestGrowth.ObjectiveScore > 0)
                    {
                        if (Portfolio[bestGrowth.Symbol].Quantity == 0)
                        {
                            Log("PREBUY>>LIQUIDATE>>");
                            Liquidate();
                        }
                        Log(">>BUY>>" + bestGrowth.Symbol + "@" + (100*bestGrowth.LastNMonthPerformance(1)).ToString("00.00"));
                        decimal qty = Portfolio.Cash/Securities[bestGrowth.Symbol].Close;
                        Order(bestGrowth.Symbol, qty);
                    }
                    else
                    {
                        // if no one has a good objective score then let's hold cash this month to be safe
                        Log(">>LIQUIDATE>>CASH");
                        Liquidate();
                    }
                }
            }
            catch (Exception ex)
            {
                Error("OnTradeBar: " + ex.Message + "\r\n\r\n" + ex.StackTrace);
            }
        }

        private void UpdateSymbolData(TradeBars data)
        {
            foreach (var symbolData in SymbolData)
            {
                TradeBar bar;
                if (data.TryGetValue(symbolData.Symbol, out bar))
                {
                    symbolData.AddClosingPrice(bar.Close);
                }
            }
        }

        private void InitializeSymbolDataAndHoldings(TradeBars data)
        {
            var symbolsToAllocate = new List<string>();
            foreach (SymbolData symbolData in SymbolData)
            {
                TradeBar bar;
                if (data.TryGetValue(symbolData.Symbol, out bar))
                {
                    Log(">>SEEDED>>"+symbolData.Symbol);
                    symbolData.InitializeWith(bar.Close);
                    symbolsToAllocate.Add(symbolData.Symbol);
                }
            }

            return;

            // we could optionally decide an intelligent way to start, but with no back
            // data its hard to make good guesses, we can just let it run for an interval
            // before initial seeding

            double percent = 1/(double) symbolsToAllocate.Count;
            foreach (var symbol in symbolsToAllocate)
            {
                SetHoldings(symbol, percent);
            }
        }
    }

    class SymbolData
    {
        private const int maxMonths = 6;

        public string Symbol;
        private List<decimal> PreviousMonths = new List<decimal>();


        public void AddClosingPrice(decimal value)
        {
            PreviousMonths.Insert(0, value);
            if (PreviousMonths.Count > maxMonths)
            {
                PreviousMonths.RemoveAt(PreviousMonths.Count - 1);
            }
        }

        public void InitializeWith(decimal value)
        {
            for (int i = 0; i < maxMonths; i++)
            {
                AddClosingPrice(value);
            }
        }

        public decimal LastNMonthPerformance(int n)
        {
            decimal delta = PreviousMonths[n] - PreviousMonths[0];
            return delta/PreviousMonths[n];
        }

        public decimal ObjectiveScore
        {
            get
            {
                // there's a few categories for scoring with different weights

                // first we look at the 1 month performance
                decimal weight1 = 100;
                decimal value1 = LastNMonthPerformance(1)*100;

                // look at the sum of the last three months performance, that is, SUM (1mp, 2mp, 3mp)
                decimal weight2 = 75;
                decimal value2 = Enumerable.Range(0, 3).Select(LastNMonthPerformance).Sum()*33;

                return (weight1*value1 + weight2*value2)/(weight1 + weight2);
            }
        }
    }
}