Overall Statistics
Total Trades
179
Average Win
1.33%
Average Loss
-1.17%
Compounding Annual Return
21.810%
Drawdown
20.100%
Expectancy
0.714
Net Profit
114.521%
Sharpe Ratio
1.051
Probabilistic Sharpe Ratio
47.700%
Loss Rate
20%
Win Rate
80%
Profit-Loss Ratio
1.14
Alpha
0.062
Beta
0.729
Annual Standard Deviation
0.151
Annual Variance
0.023
Information Ratio
0.265
Tracking Error
0.095
Treynor Ratio
0.219
Total Fees
$386.54
Estimated Strategy Capacity
$14000000.00
Lowest Capacity Asset
KBE TDP0JIUCTNJ9
using QuantConnect.Algorithm.Framework;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Risk;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
using QuantConnect.Orders;
using QuantConnect.Securities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace QuantConnect.Algorithm.CSharp.Test
{
    public class SectorMomentum : QCAlgorithm
    {
        List<string> _symbols = new List<string>()
        {
            "VNQ",  // Vanguard Real Estate Index Fund
            "XLK",  // Technology Select Sector SPDR Fund
            "XLE",  // Energy Select Sector SPDR Fund
            "XLV",  // Health Care Select Sector SPDR Fund
            "XLF",  // Financial Select Sector SPDR Fund
            "KBE",  // SPDR S&P Bank ETF
            "VAW",  // Vanguard Materials ETF
            "XLY",  // Consumer Discretionary Select Sector SPDR Fund
            "XLP",  // Consumer Staples Select Sector SPDR Fund
            "VGT",  // Vanguard Information Technology ETFm

        };

        //private Dictionary<Symbol, Momentum> _momentums = new Dictionary<Symbol, Momentum>();

        public override void Initialize()
        {
            SetStartDate(2018, 1, 1);
            SetEndDate(DateTime.Now);
            SetCash(100000);
            int period = 3 * 21;

            SetWarmup(period);

            var symbols = new List<Symbol>();
            foreach (var symbol in _symbols)
            {
                var equity = AddEquity(symbol, Resolution.Daily);
                //_momentums.Add(symbol, MOM(symbol, period, Resolution.Daily));
                symbols.Add(equity.Symbol);
            }

            AddUniverseSelection(new ManualUniverseSelectionModel(symbols));
            AddAlpha(new SectorMomentumAlpha(this));
            AddRiskManagement(new MaximumDrawdownPercentPerSecurity(0.1m));
            AddRiskManagement(new MaximumUnrealizedProfitPercentPerSecurity(0.2m));
            SetPortfolioConstruction(new SectorMomentumPortfolioConstruction(this));
            SetExecution(new SectorMomentumExecutionModel());
        }

        #region Framework

        public class SectorMomentumExecutionModel : ImmediateExecutionModel
        {
            public override void Execute(QCAlgorithm algorithm, IPortfolioTarget[] targets)
            {
                if (targets.Length > 0)
                {
                    foreach (var target in targets)
                    {
                        algorithm.SetHoldings(target.Symbol, target.Quantity);
                    }
                }
            }
        }

        public class SectorMomentumPortfolioConstruction : SymbolDataNotifier, IPortfolioConstructionModel
        {
            public SectorMomentumPortfolioConstruction(QCAlgorithm algo) : base(algo){ }

            public IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
            {
                List<IPortfolioTarget> portfolioTargets = new List<IPortfolioTarget>();
                if(insights.Length > 0)
                {
                    var top3 = SymbolData.OrderByDescending(x => x.Momentum.Current.Value).Take(3);
                    foreach (var securityData in algorithm.Portfolio)
                    {
                        var security = securityData.Value;
                        if(security.Invested && !top3.Any(x => security.Symbol.Value == x.Symbol.Value))
                        {
                            portfolioTargets.Add(new PortfolioTarget(security.Symbol, 0));
                        }
                    }
                    foreach(var insight in insights)
                    {
                        PortfolioTarget target = new PortfolioTarget(insight.Symbol, 0.33m);
                        portfolioTargets.Add(target);
                    }
                }
                return portfolioTargets;
            }
        }

        public class SectorMomentumAlpha : SymbolDataNotifier, IAlphaModel
        {
            public SectorMomentumAlpha(QCAlgorithm algo) : base(algo) { }

            public IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
            {
                if (algorithm.IsWarmingUp || SymbolData.Count < 3 || !TimeToRebalance(algorithm)) return new List<Insight>();
                List<Insight> insights = new List<Insight>();
                algorithm.Log("Rebalancing");
                var top3 = SymbolData.OrderByDescending(x => x.Momentum.Current.Value).Take(3);
                foreach(var symbol in top3)
                {
                    decimal percentage = 1m / top3.Count();
                    insights.Add(new Insight(symbol.Symbol, new TimeSpan(30, 0, 0, 0, 0), InsightType.Price, InsightDirection.Up));
                }
                ResetRebalanceTime(algorithm);
                return insights;
            }
        }

        #endregion

        #region Utilities

        public class SymbolDataNotifier : INotifiedSecurityChanges
        {
            private List<SectorMomentumSymbolData> _symbolData = new List<SectorMomentumSymbolData>();
            protected IReadOnlyCollection<SectorMomentumSymbolData> SymbolData { get { return _symbolData; } }

            private DateTime _lastRebalance;

            public SymbolDataNotifier(QCAlgorithm algo) => _lastRebalance = algo.Time;
            protected bool TimeToRebalance(QCAlgorithm algo) => _lastRebalance.AddMonths(1) <= algo.Time;
            protected void ResetRebalanceTime(QCAlgorithm algo) => _lastRebalance = algo.Time;


            public void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
            {
                if (changes == null)
                    return;

                foreach (var added in changes.AddedSecurities)
                {
                    SectorMomentumSymbolData symbolData = new SectorMomentumSymbolData(added.Symbol, algorithm);
                    _symbolData.Add(symbolData);
                }

                foreach (var removed in changes.RemovedSecurities)
                {
                    if (_symbolData.Any(x => x.Symbol.Value == removed.Symbol.Value))
                    {
                        var symbolData = _symbolData.Single(x => x.Symbol.Value == removed.Symbol.Value);
                        _symbolData.Remove(symbolData);
                    }
                }
            }
        }

        public class SectorMomentumSymbolData
        {
            public Symbol Symbol { get; private set; }
            public Momentum Momentum { get; private set; }

            public SectorMomentumSymbolData(Symbol symbol, QCAlgorithm algo)
            {
                Symbol = symbol;
                Momentum = algo.MOM(Symbol, 3 * 21, Resolution.Daily);
            }
        }

        #endregion
    }
}