Overall Statistics
Total Trades
472
Average Win
3.40%
Average Loss
-0.61%
Compounding Annual Return
29.766%
Drawdown
19.400%
Expectancy
2.376
Net Profit
3580.875%
Sharpe Ratio
1.4
Probabilistic Sharpe Ratio
88.975%
Loss Rate
49%
Win Rate
51%
Profit-Loss Ratio
5.58
Alpha
0.196
Beta
0.152
Annual Standard Deviation
0.15
Annual Variance
0.023
Information Ratio
0.564
Tracking Error
0.205
Treynor Ratio
1.386
Total Fees
$16765.09
Estimated Strategy Capacity
$380000.00
Lowest Capacity Asset
TLH TP8J6Z7L419H
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum
{
    using System;
    using QuantConnect.Algorithm.CSharp;

    /// <summary>
    /// Date utilities for the InOutMultiMomentum algorithm.
    /// </summary>
    public static class DateUtilities
    {
        /// <summary>
        /// Adds the warmup time plus padding to a given <see cref="DateTime"/>.
        /// </summary>
        /// <param name="initialDate"><see cref="DateTime"/> to add warmup to.</param>
        /// <returns><see cref="DateTime"/> with warm up time and padding added.</returns>
        public static DateTime AddWarmUp(this DateTime initialDate) => initialDate.AddDays(InOutMultiMomentumAlgorithm.WarmupDays + 5);
    }
}
using InOutMultiMomentum.UniverseSelection;
using QuantConnect;
using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace InOutMultiMomentum
{
    public class InOutAlphaModel : AlphaModel
    {
        public const int VolatilityPeriods = 126; // "VOLA"
        private const int BaseReturn = 83; // "BASE_RET"

        /// <summary>
        /// Earliest possible date the algorithm can begin backtesting at.
        /// </summary>
        /// <remarks>
        /// UUP began trading on 2-20-2017; we need to pad out to obtain sufficient data.
        /// </remarks>
        public readonly DateTime EarliestStartDate = new DateTime(2007, 2, 20).AddWarmUp();

        /// <summary>
        /// Time span after which alpha model state is considered invalid and discarded.
        /// </summary>
        private readonly TimeSpan OldestAlphaModelState = TimeSpan.FromDays(5);

        private readonly string AlphaModelStateKeyName = $"{nameof(InOutAlphaModel)}-State";

        private const int PredictionInterval = 30;
        private const int GenerateAlphaAfterMarketOpenMinutes = 100; // 11:10 AM
        private const string MarketTicker = "SPY";
        private const string SilverTicker = "SLV";
        private const string GoldTicker = "GLD";
        private const string IndustrialsTicker = "XLI";
        private const string UtilitiesTicker = "XLU";
        private const string BaseMetalsTicker = "DBB";
        private const string USDollarTicker = "UUP";

        private readonly QCAlgorithm _algorithm;
        private readonly Dictionary<Symbol, TradeBarConsolidator> _consolidators = new();
        private readonly Dictionary<string, Symbol> _indicatorPairs = new();
        private readonly PriceHistoryCollection _priceHistory;
        private readonly Resolution _resolution;
        private readonly TimeSpan _predictionInterval;

        private readonly Dictionary<Symbol, SymbolData> _riskOnSymbolData = new();
        private readonly Dictionary<Symbol, SymbolData> _riskOffSymbolData = new();

        //private int _outDayCount = 0;
        private bool _generateInsights = false;

        public InOutAlphaModel(QCAlgorithm algorithm, Resolution resolution = Resolution.Daily)
        {
            _algorithm = algorithm;
            _resolution = resolution;

            var indicatorTickers = new[]
            {
                MarketTicker,
                SilverTicker,
                GoldTicker,
                IndustrialsTicker,
                UtilitiesTicker,
                BaseMetalsTicker,
                USDollarTicker
            };

            // Set up indicator pairs
            foreach (var ticker in indicatorTickers)
            {
                var symbol = _algorithm.AddEquity(ticker, _resolution, Market.USA).Symbol;
                _indicatorPairs.Add(ticker, symbol);

                var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
                consolidator.DataConsolidated += ConsolidationHandler;
                _algorithm.SubscriptionManager.AddConsolidator(symbol, consolidator);
                _consolidators.Add(symbol, consolidator);
            }

            // Load in history for indicator pairs
            var history = _algorithm.History(_indicatorPairs.Values, VolatilityPeriods + 1, _resolution);
            _priceHistory = new PriceHistoryCollection(history);

            // Schedule this alpha to only produce insights at 11:10 AM.
            _algorithm.Schedule.On(
                _algorithm.DateRules.EveryDay(), 
                _algorithm.TimeRules.AfterMarketOpen(MarketTicker, GenerateAlphaAfterMarketOpenMinutes),
                () => _generateInsights = true);

            // Remaining setup
            _predictionInterval = _resolution.ToTimeSpan().Multiply(PredictionInterval);
            Name = $"{nameof(InOutAlphaModel)}({_resolution})";
        }

        ~InOutAlphaModel()
        {
            foreach (var (symbol, consolidator) in _consolidators)
            {
                _algorithm.SubscriptionManager.RemoveConsolidator(symbol, consolidator);
            }
        }

        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                var symbol = added.Symbol;
                var ticker = symbol.Value;

                if (!_riskOnSymbolData.ContainsKey(symbol) && !_riskOffSymbolData.ContainsKey(symbol))
                {
                    var selectionModel = (InOutUniverseSelectionModel)algorithm.UniverseSelection;
                    var symbolData = new SymbolData(algorithm, symbol);

                    if (selectionModel.RiskOnTickers.Contains(ticker))
                    {
                        _riskOnSymbolData.Add(symbol, symbolData);
                    }
                    else if (selectionModel.RiskOffTickers.Contains(ticker))
                    {
                        _riskOffSymbolData.Add(symbol, symbolData);
                    }
                }
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                var symbol = removed.Symbol;

                if (_riskOnSymbolData.ContainsKey(symbol))
                {
                    _riskOnSymbolData.Remove(symbol);
                }

                if (_riskOffSymbolData.ContainsKey(symbol))
                {
                    _riskOffSymbolData.Remove(symbol);
                }
            }
        }

        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            if (_algorithm.IsWarmingUp || !_generateInsights || _riskOnSymbolData.Count == 0 || _riskOffSymbolData.Count == 0)
            {
                yield break;
            }

            _generateInsights = false;

            // Load alpha model state from object store
            var state = LoadState(algorithm);

            // Calculate alpha model signal
            var signal = CalculateRiskOffSignal();
            var selectRiskOnAssets = false;

            if (signal.RiskOff)
            {
                state.OutDayCount = 0;
                //_outDayCount = 0;
            }
            else
            {
                state.OutDayCount += 1;
                //_outDayCount += 1;

                //if (_outDayCount > signal.WaitDays)
                if (state.OutDayCount > signal.WaitDays)
                {
                    selectRiskOnAssets = true;
                }
            }

            var selected = selectRiskOnAssets
                ? SelectAsset(_riskOnSymbolData.Values.ToList())
                : SelectAsset(_riskOffSymbolData.Values.ToList());

            foreach (var sd in _riskOnSymbolData.Concat(_riskOffSymbolData).ToDictionary(kvp => kvp.Key, kvp => kvp.Value).Values)
            {
                if (sd.Symbol == selected)
                {
                    // Upwards-trending symbol
                    yield return Insight.Price(sd.Symbol, _predictionInterval, InsightDirection.Up);
                }
                else if ((selectRiskOnAssets && _riskOnSymbolData.ContainsKey(sd.Symbol)) ||
                        (!selectRiskOnAssets && _riskOffSymbolData.ContainsKey(sd.Symbol)))
                {
                    // Flat-trending symbols
                    yield return Insight.Price(sd.Symbol, _predictionInterval, InsightDirection.Flat);
                }
                else
                {
                    // Down-trending symbols
                    yield return Insight.Price(sd.Symbol, _predictionInterval, InsightDirection.Down);
                }
            }

            // Save model state
            state.Updated = algorithm.Time.Date;
            SaveState(algorithm, state);
        }

        private IndicatorSignal CalculateRiskOffSignal()
        {
            var marketStandardDeviation = _priceHistory.StandardDeviation(_indicatorPairs[MarketTicker]);
            var marketVolatility = marketStandardDeviation * Math.Sqrt(252);

            var waitDays = Convert.ToInt32(Math.Floor(marketVolatility * BaseReturn));
            var period = Convert.ToInt32(Math.Floor((1.0 - marketVolatility) * BaseReturn));

            var returns = new Dictionary<string, double>();

            foreach (var (ticker, symbol) in _indicatorPairs)
            {
                var percentChange = _priceHistory.PercentChange(symbol, period);
                returns[ticker] = percentChange.Last();
            }

            var riskOff =
                (returns[SilverTicker] < returns[GoldTicker]) &&
                (returns[IndustrialsTicker] < returns[UtilitiesTicker]) &&
                (returns[BaseMetalsTicker] < returns[USDollarTicker]);

            return new IndicatorSignal
            {
                RiskOff = riskOff,
                WaitDays = waitDays
            };
        }

        private void ConsolidationHandler(object sender, TradeBar consolidated)
        {
            // Update daily close price and trim by volatility
            _priceHistory.Update(consolidated);
            _priceHistory.Trim(consolidated.Symbol, VolatilityPeriods + 1);
        }

        private AlphaModelState LoadState(QCAlgorithm algorithm)
        {
            if (algorithm.ObjectStore.ContainsKey(AlphaModelStateKeyName))
            {
                var state = algorithm.ObjectStore.ReadJson<AlphaModelState>(AlphaModelStateKeyName);

                if (state != null)
                {
                    if (algorithm.Time.Date <= (state.Updated + OldestAlphaModelState).Date)
                    {
                        algorithm.Debug($"Loaded alpha model state from {state.Updated:yyyy-MM-dd} (OutDays={state.OutDayCount})");
                        return state;
                    }
                }
            }

            algorithm.Debug("Created new alpha model state.");
            return new AlphaModelState();
        }

        private void SaveState(QCAlgorithm algorithm, AlphaModelState state)
        {
            algorithm.Debug($"Saved alpha model state from {state.Updated:yyyy-MM-dd} to object store");
            algorithm.ObjectStore.SaveJson(AlphaModelStateKeyName, state);
        }

        private Symbol SelectAsset(List<SymbolData> assets)
        {
            var returns = new Dictionary<Symbol, decimal>();
            //_algorithm.Debug($"Assets: {string.Join(", ", _riskOnSymbolData.Concat(_riskOffSymbolData).Select(kvp => kvp.Key).ToList())}");

            foreach (var sd in assets)
            {
                //_algorithm.Debug($"Return: {sd.Symbol} = {sd.Returns}");
                returns.Add(sd.Symbol, sd.Returns);
            }

            return returns.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value).First().Key;
        }

        public class SymbolData
        {
            private const int ReturnLookbackPeriods = 252; // "RET"
            private const int ExclusionPeriods = 21; // "EXCL"

            private readonly QCAlgorithm _algorithm;

            public decimal Returns
            {
                get
                {
                    var priceHistory = _algorithm.History<TradeBar>(Symbol, TimeSpan.FromDays(ReturnLookbackPeriods + ExclusionPeriods), Resolution.Daily);
                    var closePrices = priceHistory.Select(x => x.Close).ToList();

                    return closePrices[^ExclusionPeriods] / closePrices[0];
                }
            }

            public Symbol Symbol { get; private set; }

            public SymbolData(QCAlgorithm algorithm, Symbol symbol)
            {
                _algorithm = algorithm;
                Symbol = symbol;
            }
        }

        public class AlphaModelState
        {
            public DateTime Updated;
            public int OutDayCount;
        }

        public class IndicatorSignal
        {
            public bool RiskOff;
            public int WaitDays;
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using InOutMultiMomentum.UniverseSelection;
    using QuantConnect;
    using QuantConnect.Algorithm;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Securities;

    /// <summary>
    /// Portfolio construction model for the InOutMultiMomentum algorithm.
    /// </summary>
    public class InOutPortfolioConstructionModel : PortfolioConstructionModel
    {
        private readonly decimal riskOnLeverage;
        private readonly decimal riskOffLeverage;

        private readonly Dictionary<Symbol, SymbolData> riskOnSymbolData = new();
        private readonly Dictionary<Symbol, SymbolData> riskOffSymbolData = new();

        /// <summary>
        /// Initializes a new instance of the <see cref="InOutPortfolioConstructionModel"/> class.
        /// </summary>
        /// <param name="riskOnLeverage">Leverage to use when the algorithm is in risk-on mode.</param>
        /// <param name="riskOffLeverage">Leverage to use when the algorithm is in risk-off mode.</param>
        public InOutPortfolioConstructionModel(decimal riskOnLeverage, decimal riskOffLeverage)
        {
            this.riskOnLeverage = riskOnLeverage;
            this.riskOffLeverage = riskOffLeverage;
        }

        /// <inheritdoc/>
        public override IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
        {
            if (algorithm.IsWarmingUp || this.riskOnSymbolData.Count == 0 || this.riskOffSymbolData.Count == 0)
            {
                yield break;
            }

            foreach (var insight in insights)
            {
                if (insight.Direction == InsightDirection.Up)
                {
                    var leverage = this.riskOnSymbolData.ContainsKey(insight.Symbol)
                        ? this.riskOnLeverage
                        : this.riskOffLeverage;

                    yield return PortfolioTarget.Percent(algorithm, insight.Symbol, leverage);
                }
                else
                {
                    yield return PortfolioTarget.Percent(algorithm, insight.Symbol, 0.0m);
                }
            }
        }

        /// <inheritdoc/>
        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                var symbol = added.Symbol;
                var ticker = symbol.Value;

                if (!this.riskOnSymbolData.ContainsKey(symbol) && !this.riskOffSymbolData.ContainsKey(symbol))
                {
                    var selectionModel = (InOutUniverseSelectionModel)algorithm.UniverseSelection;
                    var symbolData = new SymbolData(symbol);

                    if (selectionModel.RiskOnTickers.Contains(ticker))
                    {
                        this.riskOnSymbolData.Add(symbol, symbolData);
                    }
                    else if (selectionModel.RiskOffTickers.Contains(ticker))
                    {
                        this.riskOffSymbolData.Add(symbol, symbolData);
                    }
                }
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                var symbol = removed.Symbol;

                if (this.riskOnSymbolData.ContainsKey(symbol))
                {
                    _ = this.riskOnSymbolData.Remove(symbol);
                }

                if (this.riskOffSymbolData.ContainsKey(symbol))
                {
                    _ = this.riskOffSymbolData.Remove(symbol);
                }
            }
        }

        /// <summary>
        /// Symbol data for securities used by the algorithm.
        /// </summary>
        public class SymbolData
        {
            /// <summary>
            /// Initializes a new instance of the <see cref="SymbolData"/> class.
            /// </summary>
            /// <param name="symbol"><see cref="Symbol"/> of this data object.</param>
            public SymbolData(Symbol symbol) => this.Symbol = symbol;

            /// <summary>
            /// Gets the <see cref="Symbol"/> for this data object.
            /// </summary>
            public Symbol Symbol { get; }
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using MathNet.Numerics.Statistics;
    using QuantConnect;
    using QuantConnect.Data;
    using QuantConnect.Data.Market;

    /// <summary>
    /// Collection of price history used by the <see cref="InOutMultiMomentumAlphaModel"/>.
    /// </summary>
    public class PriceHistoryCollection
    {
        private readonly Dictionary<Symbol, SortedDictionary<DateTime, decimal>> priceHistory = new();

        /// <summary>
        /// Initializes a new instance of the <see cref="PriceHistoryCollection"/> class.
        /// </summary>
        /// <param name="initialHistory">Initial history retrieved for indicator pairs.</param>
        public PriceHistoryCollection(IEnumerable<Slice> initialHistory)
        {
            var symbols = initialHistory.SelectMany(slice => slice.Keys)
                .Distinct()
                .ToList();

            foreach (var symbol in symbols)
            {
                var symbolHistory = initialHistory.Get<TradeBar>(symbol);

                foreach (var bar in symbolHistory)
                {
                    this.Update(bar);
                }
            }
        }

        /// <summary>
        /// Calculates the percentage of change between elements of the given period.
        /// </summary>
        /// <param name="symbol"><see cref="Symbol"/> for which to calculate from.</param>
        /// <param name="period">Period of the comparison.</param>
        /// <returns>Array of percentage value changes for the <paramref name="symbol"/> over the given <paramref name="period"/>.</returns>
        public double[] PercentChange(Symbol symbol, int period = 1)
            => PercentChange(this.priceHistory[symbol].Values.ToDoubleArray(), period);

        /// <summary>
        /// Calculates the standard deviation of the percentage change for the given <paramref name="symbol"/>.
        /// </summary>
        /// <param name="symbol"><see cref="Symbol"/> for which to calculate.</param>
        /// <returns>Standard deviation over the percentage change of the <paramref name="symbol"/>.</returns>
        public double StandardDeviation(Symbol symbol)
        {
            var percentChangeSeries = this.PercentChange(symbol);
            return percentChangeSeries.StandardDeviation();
        }

        /// <summary>
        /// Trims price data for the indicated number of <paramref name="periods"/> of the given <paramref name="symbol"/>.
        /// </summary>
        /// <param name="symbol"><see cref="Symbol"/> to trim price data from.</param>
        /// <param name="periods">Number of periods of data to trim.</param>
        public void Trim(Symbol symbol, int periods)
        {
            var toKeep = this.priceHistory[symbol].Keys
                .OrderBy(k => k)
                .Skip(Math.Max(0, this.priceHistory[symbol].Count - periods))
                .ToList();

            var toRemove = this.priceHistory[symbol].Keys.Where(x => !toKeep.Contains(x)).ToList();

            foreach (var key in toRemove)
            {
                _ = this.priceHistory[symbol].Remove(key);
            }
        }

        /// <summary>
        /// Updates price data.
        /// </summary>
        /// <param name="bar"><see cref="TradeBar"/> of data to collect price information from.</param>
        public void Update(TradeBar bar)
        {
            var closePrice = bar.Close;
            var endTime = bar.EndTime;
            var symbol = bar.Symbol;

            if (!this.priceHistory.ContainsKey(symbol))
            {
                this.priceHistory[symbol] = new SortedDictionary<DateTime, decimal>();
            }

            if (endTime != default && closePrice != default)
            {
                this.priceHistory[symbol][endTime] = closePrice;
            }
        }

        /// <summary>
        /// Calculates the percentage of change between elements of the given period.
        /// </summary>
        /// <param name="series">Price data series to calculate from.</param>
        /// <param name="period">Period of the comparison.</param>
        /// <returns>Array of percentage value changes over the given <paramref name="period"/>.</returns>
        private static double[] PercentChange(double[] series, int period)
        {
            var returnSeries = new List<double>();

            for (var i = period; i < series.Length; i++)
            {
                var b = series[i];
                var a = series[i - period];

                returnSeries.Add((b - a) / Math.Abs(a));
            }

            return returnSeries.ToArray();
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using QuantConnect;
    using QuantConnect.Algorithm.CSharp;
    using QuantConnect.Algorithm.Framework.Selection;

    /// <summary>
    /// Base selection model for any InOutMultiMomentum algorithm instance.
    /// </summary>
    public abstract class InOutUniverseSelectionModel : ManualUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="InOutUniverseSelectionModel"/> class.
        /// </summary>
        /// <param name="name">Name of this selection model.</param>
        /// <param name="riskOnTickers">List of tickers to use when the algorithm is in risk-on mode.</param>
        /// <param name="riskOffTickers">List of tickers ot use when the algorithm is in risk-off mode.</param>
        public InOutUniverseSelectionModel(string name, Dictionary<string, DateTime> riskOnTickers, Dictionary<string, DateTime> riskOffTickers)
            : base(riskOnTickers.Concat(riskOffTickers).Select(kvp => Symbol.Create(kvp.Key, SecurityType.Equity, Market.USA)).ToList())
        {
            this.Name = name;
            this.RiskOnTickers = riskOnTickers.Keys.ToList();
            this.RiskOffTickers = riskOffTickers.Keys.ToList();

            this.StartDate = riskOnTickers.Concat(riskOffTickers)
                .Select(kvp => kvp.Value).ToList()
                .OrderByDescending(x => x.Date).First().AddWarmUp();
        }

        /// <summary>
        /// Gets the name of this selection model.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets the earliest date this selection model may be backtested from.
        /// </summary>
        public DateTime StartDate { get; private set; }

        /// <summary>
        /// Gets the list of tickers to use when the algorithm is in risk-on mode.
        /// </summary>
        public List<string> RiskOnTickers { get; private set; }

        /// <summary>
        /// Gets the list of tickers to use when the algorithm is in risk-off mode.
        /// </summary>
        public List<string> RiskOffTickers { get; private set; }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF3xMixedIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF3xMixedIndexUniverse"/> class.
        /// </summary>
        public LETF3xMixedIndexUniverse()
            : base(
                nameof(LETF3xMixedIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "TQQQ", new DateTime(2010, 2,  9) },
                    { "UPRO", new DateTime(2009, 6, 25) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TMF", new DateTime(2009, 4, 16) },
                    { "TYD", new DateTime(2009, 4, 16) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class UnleveredMixedIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UnleveredMixedIndexUniverse"/> class.
        /// </summary>
        public UnleveredMixedIndexUniverse()
            : base(
                nameof(UnleveredMixedIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "QQQ", new DateTime(1999, 3, 10) },
                    { "SPY", new DateTime(1993, 1, 22) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TLT", new DateTime(2002, 7, 22) },
                    { "TLH", new DateTime(2007, 1,  5) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class UnleveredSP500Universe : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UnleveredSP500Universe"/> class.
        /// </summary>
        public UnleveredSP500Universe()
            : base(
                nameof(UnleveredSP500Universe),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "SPY", new DateTime(1993, 1, 22) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TLT", new DateTime(2002, 7, 22) },
                    { "TLH", new DateTime(2007, 1,  5) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF2xMixedIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF2xMixedIndexUniverse"/> class.
        /// </summary>
        public LETF2xMixedIndexUniverse()
            : base(
                nameof(LETF2xMixedIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "QLD", new DateTime(2006, 6, 19) },
                    { "SSO", new DateTime(2006, 6, 19) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "UBT", new DateTime(2010, 1, 19) },
                    { "UST", new DateTime(2010, 1, 19) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF2xSP500Universe : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF2xSP500Universe"/> class.
        /// </summary>
        public LETF2xSP500Universe()
            : base(
                nameof(LETF2xSP500Universe),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "SSO", new DateTime(2006, 6, 19) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "UBT", new DateTime(2010, 1, 19) },
                    { "UST", new DateTime(2010, 1, 19) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF2xTechIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF2xTechIndexUniverse"/> class.
        /// </summary>
        public LETF2xTechIndexUniverse()
            : base(
                nameof(LETF2xTechIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "QLD", new DateTime(2006, 6, 19) },
                    { "ROM", new DateTime(2007, 1, 30) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "UBT", new DateTime(2010, 1, 19) },
                    { "UST", new DateTime(2010, 1, 19) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF3xTechIndex1xBondUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF3xTechIndex1xBondUniverse"/> class.
        /// </summary>
        public LETF3xTechIndex1xBondUniverse()
            : base(
                nameof(LETF3xTechIndex1xBondUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "TECL", new DateTime(2008, 12, 17) },
                    { "TQQQ", new DateTime(2010,  2,  9) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "EDV",  new DateTime(2007,  1,  5) },
                    { "VGSH", new DateTime(2009, 11, 19) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF3xSP500Universe : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF3xSP500Universe"/> class.
        /// </summary>
        public LETF3xSP500Universe()
            : base(
                nameof(LETF3xSP500Universe),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "UPRO", new DateTime(2009, 6, 25) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TMF", new DateTime(2009, 4, 16) },
                    { "TYD", new DateTime(2009, 4, 16) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class LETF3xTechIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="LETF3xTechIndexUniverse"/> class.
        /// </summary>
        public LETF3xTechIndexUniverse()
            : base(
                nameof(LETF3xTechIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "TECL", new DateTime(2008, 12, 17) },
                    { "TQQQ", new DateTime(2010,  2,  9) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TMF", new DateTime(2009, 4, 16) },
                    { "TYD", new DateTime(2009, 4, 16) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class UnleveredTechIndexUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UnleveredTechIndexUniverse"/> class.
        /// </summary>
        public UnleveredTechIndexUniverse()
            : base(
                nameof(UnleveredTechIndexUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "QQQ", new DateTime(1999, 3, 10) },
                    { "FDN", new DateTime(2006, 6, 19) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TLT", new DateTime(2002, 7, 22) },
                    { "TLH", new DateTime(2007, 1,  5) },
                })
        {
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace QuantConnect.Algorithm.CSharp
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using InOutMultiMomentum;
    using InOutMultiMomentum.UniverseSelection;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Brokerages;
    using QuantConnect.Parameters;

    /// <inheritdoc/>
#pragma warning disable SA1649
    public class InOutMultiMomentumAlgorithm : QCAlgorithm
#pragma warning restore SA1649
    {
        /// <summary>
        /// Indicates the number of days worth of data required to warm up the algorithm.
        /// </summary>
        public const int WarmupDays = 350;

        /// <summary>
        /// All available universe selection models.
        /// </summary>
        private readonly List<InOutUniverseSelectionModel> universeSelectionModels = new()
        {
            new LETF2xMixedIndexUniverse(),
            new LETF2xSP500Universe(),
            new LETF2xTechIndexUniverse(),
            new LETF3xMixedIndexUniverse(),
            new LETF3xSP500Universe(),
            new LETF3xTechIndex1xBondUniverse(),
            new LETF3xTechIndexUniverse(),
            new NTSXUniverse(),
            new UnleveredMixedIndexUniverse(),
            new UnleveredSP500Universe(),
            new UnleveredTechIndexUniverse(),
        };

        /// <summary>
        /// Defines the desired starting date for backtesting.
        /// </summary>
        /// <remarks>
        /// Note that if the backtest cannot start due to an indicator ETF not having data, this value is ignored.
        /// </remarks>
        public DateTime BacktestStartDate { get; set; } = new DateTime(2008, 1, 1);

        /// <summary>
        /// Defines the desired leverage when the algorithm is in "risk on" mode.
        /// </summary>
        [Parameter("RiskOnLeverage")]
        public decimal RiskOnLeverage { get; set; }

        /// <summary>
        /// Defines the desired leverage when the algorithm is in "risk off" mode.
        /// </summary>
        [Parameter("RiskOffLeverage")]
        public decimal RiskOffLeverage { get; set; }

        /// <summary>
        /// If set, defines which risk management model to use.
        /// </summary>
        [Parameter("RiskManagementModel")]
        public string RiskManagementModel { get; set; }

        /// <summary>
        /// Defines the percentage parameter for the risk management model.
        /// </summary>
        [Parameter("RiskManagementModelPercent")]
        public decimal RiskManagementModelPercent { get; set; }

        /// <summary>
        /// Defines which universe selection model to use.
        /// </summary>
        [Parameter("UniverseSelectionModel")]
        public string UniverseSelectionModel { get; set; }

        /// <inheritdoc/>
        public override void Initialize()
        {
            var universeSelection = this.universeSelectionModels
                .FirstOrDefault(x => x.Name == this.UniverseSelectionModel);

            if (universeSelection == null)
            {
                throw new ArgumentOutOfRangeException($"Unrecognized universe selection model: {this.UniverseSelectionModel}");
            }

            var alpha = new InOutMultiMomentumAlphaModel(this);

            var startDate = new List<DateTime>
            {
                universeSelection.StartDate,
                alpha.EarliestStartDate,
                this.BacktestStartDate,
            }.OrderByDescending(x => x).First();

            this.Debug($"Universe: {universeSelection.StartDate:yyyy-MM-dd}, Alpha: {alpha.EarliestStartDate:yyyy-MM-dd}, Backtest: {this.BacktestStartDate:yyyy-MM-dd}");
            this.Debug($"Start date: {startDate:yyyy-MM-dd}");

            this.SetCash(100000);

            // this.SetEndDate(2020, 12, 31);
            this.SetStartDate(startDate);

            this.SetBenchmark("SPY");
            this.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin);
            this.SetWarmUp(TimeSpan.FromDays(WarmupDays));

            this.UniverseSettings.Resolution = Resolution.Minute;

            this.SetAlpha(new InOutMultiMomentumAlphaModel(this));
            this.SetPortfolioConstruction(new InOutPortfolioConstructionModel(this.RiskOnLeverage, this.RiskOffLeverage));
            this.SetUniverseSelection(universeSelection);

            switch (this.RiskManagementModel)
            {
                case nameof(MaximumDrawdownPercentPerSecurity):
                    this.SetRiskManagement(new MaximumDrawdownPercentPerSecurity(this.RiskManagementModelPercent));
                    break;

                case nameof(MaximumDrawdownPercentPortfolio):
                    this.SetRiskManagement(new MaximumDrawdownPercentPortfolio(this.RiskManagementModelPercent));
                    break;

                case nameof(MaximumUnrealizedProfitPercentPerSecurity):
                    this.SetRiskManagement(new MaximumUnrealizedProfitPercentPerSecurity(this.RiskManagementModelPercent));
                    break;

                case nameof(TrailingStopRiskManagementModel):
                    this.SetRiskManagement(new TrailingStopRiskManagementModel(this.RiskManagementModelPercent));
                    break;

                default:
                    if (!string.IsNullOrWhiteSpace(this.RiskManagementModel))
                    {
                        throw new ArgumentOutOfRangeException($"Invalid risk management model: {this.RiskManagementModel}");
                    }

                    break;
            }
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using InOutMultiMomentum.UniverseSelection;
    using QuantConnect;
    using QuantConnect.Algorithm;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Data;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;

    /// <summary>
    /// InOutMultiMomentum alpha model.
    /// </summary>
    public class InOutMultiMomentumAlphaModel : AlphaModel
    {
        /// <summary>
        /// Number of volatility periods.
        /// </summary>
        public const int VolatilityPeriods = 126; // "VOLA"

        private const int BaseReturn = 83; // "BASE_RET"
        private const int PredictionInterval = 30;
        private const int GenerateAlphaAfterMarketOpenMinutes = 100; // 11:10 AM
        private const string MarketTicker = "SPY";
        private const string SilverTicker = "SLV";
        private const string GoldTicker = "GLD";
        private const string IndustrialsTicker = "XLI";
        private const string UtilitiesTicker = "XLU";
        private const string BaseMetalsTicker = "DBB";
        private const string USDollarTicker = "UUP";

        private readonly QCAlgorithm algorithm;
        private readonly string alphaModelStateKeyName = $"{nameof(InOutMultiMomentumAlphaModel)}-State";
        private readonly Dictionary<Symbol, TradeBarConsolidator> consolidators = new();
        private readonly Dictionary<string, Symbol> indicatorPairs = new();
        private readonly TimeSpan oldestAlphaModelState = TimeSpan.FromDays(5);
        private readonly TimeSpan predictionInterval;
        private readonly PriceHistoryCollection priceHistory;
        private readonly Resolution resolution;
        private readonly Dictionary<Symbol, SymbolData> riskOnSymbolData = new();
        private readonly Dictionary<Symbol, SymbolData> riskOffSymbolData = new();

        private bool generateInsights;

        /// <summary>
        /// Initializes a new instance of the <see cref="InOutMultiMomentumAlphaModel"/> class.
        /// </summary>
        /// <param name="algorithm">Algorithm executing this alpha model.</param>
        /// <param name="resolution">Resolution at which to fetch historical data.</param>
        public InOutMultiMomentumAlphaModel(QCAlgorithm algorithm, Resolution resolution = Resolution.Daily)
        {
            this.algorithm = algorithm;
            this.resolution = resolution;

            var indicatorTickers = new[]
            {
                MarketTicker,
                SilverTicker,
                GoldTicker,
                IndustrialsTicker,
                UtilitiesTicker,
                BaseMetalsTicker,
                USDollarTicker,
            };

            // Set up indicator pairs
            foreach (var ticker in indicatorTickers)
            {
                var symbol = this.algorithm.AddEquity(ticker, this.resolution, Market.USA).Symbol;
                this.indicatorPairs.Add(ticker, symbol);

                var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
                consolidator.DataConsolidated += this.ConsolidationHandler;
                this.algorithm.SubscriptionManager.AddConsolidator(symbol, consolidator);
                this.consolidators.Add(symbol, consolidator);
            }

            // Load in history for indicator pairs
            var history = this.algorithm.History(this.indicatorPairs.Values, VolatilityPeriods + 1, this.resolution);
            this.priceHistory = new PriceHistoryCollection(history);

            // Schedule this alpha to only produce insights at 11:10 AM.
            _ = this.algorithm.Schedule.On(
                this.algorithm.DateRules.EveryDay(),
                this.algorithm.TimeRules.AfterMarketOpen(MarketTicker, GenerateAlphaAfterMarketOpenMinutes),
                () => this.generateInsights = true);

            // Remaining setup
            this.predictionInterval = this.resolution.ToTimeSpan().Multiply(PredictionInterval);
            this.Name = $"{nameof(InOutMultiMomentumAlphaModel)}({this.resolution})";
        }

        /// <summary>
        /// Finalizes an instance of the <see cref="InOutMultiMomentumAlphaModel"/> class.
        /// </summary>
        ~InOutMultiMomentumAlphaModel()
        {
            foreach (var (symbol, consolidator) in this.consolidators)
            {
                this.algorithm.SubscriptionManager.RemoveConsolidator(symbol, consolidator);
            }
        }

        /// <summary>
        /// Earliest possible date the algorithm can begin backtesting at.
        /// </summary>
        /// <remarks>
        /// UUP began trading on 2-20-2017; we need to pad out to obtain sufficient data.
        /// </remarks>
        public DateTime EarliestStartDate { get; } = new DateTime(2007, 2, 20).AddWarmUp();

        /// <inheritdoc/>
        public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
        {
            foreach (var added in changes.AddedSecurities)
            {
                var symbol = added.Symbol;
                var ticker = symbol.Value;

                if (!this.riskOnSymbolData.ContainsKey(symbol) && !this.riskOffSymbolData.ContainsKey(symbol))
                {
                    var selectionModel = (InOutUniverseSelectionModel)algorithm.UniverseSelection;
                    var symbolData = new SymbolData(algorithm, symbol);

                    if (selectionModel.RiskOnTickers.Contains(ticker))
                    {
                        this.riskOnSymbolData.Add(symbol, symbolData);
                    }
                    else if (selectionModel.RiskOffTickers.Contains(ticker))
                    {
                        this.riskOffSymbolData.Add(symbol, symbolData);
                    }
                }
            }

            foreach (var removed in changes.RemovedSecurities)
            {
                var symbol = removed.Symbol;

                if (this.riskOnSymbolData.ContainsKey(symbol))
                {
                    _ = this.riskOnSymbolData.Remove(symbol);
                }

                if (this.riskOffSymbolData.ContainsKey(symbol))
                {
                    _ = this.riskOffSymbolData.Remove(symbol);
                }
            }
        }

        /// <inheritdoc/>
        public override IEnumerable<Insight> Update(QCAlgorithm algorithm, Slice data)
        {
            if (algorithm.IsWarmingUp || !this.generateInsights || this.riskOnSymbolData.Count == 0 || this.riskOffSymbolData.Count == 0)
            {
                yield break;
            }

            this.generateInsights = false;

            // Load alpha model state from object store
            var state = this.LoadState();

            // Calculate alpha model signal
            var signal = this.CalculateRiskOffSignal();
            var selectRiskOnAssets = false;

            if (signal.RiskOff)
            {
                state.OutDayCount = 0;
            }
            else
            {
                state.OutDayCount += 1;

                if (state.OutDayCount > signal.WaitDays)
                {
                    selectRiskOnAssets = true;
                }
            }

            var selected = selectRiskOnAssets
                ? SelectAsset(this.riskOnSymbolData.Values.ToList())
                : SelectAsset(this.riskOffSymbolData.Values.ToList());

            foreach (var sd in this.riskOnSymbolData.Concat(this.riskOffSymbolData).ToDictionary(kvp => kvp.Key, kvp => kvp.Value).Values)
            {
                if (sd.Symbol == selected)
                {
                    // Upwards-trending symbol
                    yield return Insight.Price(sd.Symbol, this.predictionInterval, InsightDirection.Up);
                }
                else if ((selectRiskOnAssets && this.riskOnSymbolData.ContainsKey(sd.Symbol)) ||
                        (!selectRiskOnAssets && this.riskOffSymbolData.ContainsKey(sd.Symbol)))
                {
                    // Flat-trending symbols
                    yield return Insight.Price(sd.Symbol, this.predictionInterval, InsightDirection.Flat);
                }
                else
                {
                    // Down-trending symbols
                    yield return Insight.Price(sd.Symbol, this.predictionInterval, InsightDirection.Down);
                }
            }

            // Save model state
            state.Updated = algorithm.Time.Date;
            this.SaveState(state);
        }

        /// <summary>
        /// Selects the asset with the highest momentum from a list of provided assets.
        /// </summary>
        /// <param name="assets">List of <see cref="SymbolData"/> to select an asset from.</param>
        /// <returns><see cref="Symbol"/> with the highest momentum.</returns>
        private static Symbol SelectAsset(List<SymbolData> assets)
        {
            if (assets.Count == 1)
            {
                return assets[0].Symbol;
            }

            var returns = new Dictionary<Symbol, decimal>();

            foreach (var sd in assets)
            {
                returns.Add(sd.Symbol, sd.Returns);
            }

            return returns.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value).First().Key;
        }

        /// <summary>
        /// Calculates if the algorithm should be risk-off.
        /// </summary>
        /// <returns><see cref="IndicatorSignal"/> data.</returns>
        private IndicatorSignal CalculateRiskOffSignal()
        {
            var marketStandardDeviation = this.priceHistory.StandardDeviation(this.indicatorPairs[MarketTicker]);
            var marketVolatility = marketStandardDeviation * Math.Sqrt(252);

            var waitDays = Convert.ToInt32(Math.Floor(marketVolatility * BaseReturn));
            var period = Convert.ToInt32(Math.Floor((1.0 - marketVolatility) * BaseReturn));

            var returns = new Dictionary<string, double>();

            foreach (var (ticker, symbol) in this.indicatorPairs)
            {
                var percentChange = this.priceHistory.PercentChange(symbol, period);
                returns[ticker] = percentChange.Last();
            }

            var riskOff =
                (returns[SilverTicker] < returns[GoldTicker]) &&
                (returns[IndustrialsTicker] < returns[UtilitiesTicker]) &&
                (returns[BaseMetalsTicker] < returns[USDollarTicker]);

            return new IndicatorSignal
            {
                RiskOff = riskOff,
                WaitDays = waitDays,
            };
        }

        /// <summary>
        /// Data consolidation event handler.
        /// </summary>
        /// <param name="sender">Source object.</param>
        /// <param name="consolidated">Consolidated data.</param>
        private void ConsolidationHandler(object sender, TradeBar consolidated)
        {
            // Update daily close price and trim by volatility
            this.priceHistory.Update(consolidated);
            this.priceHistory.Trim(consolidated.Symbol, VolatilityPeriods + 1);
        }

        /// <summary>
        /// Loads the alpha model's state from the object store.
        /// </summary>
        /// <returns><see cref="AlphaModelState"/></returns>
        private AlphaModelState LoadState()
        {
            if (this.algorithm.ObjectStore.ContainsKey(this.alphaModelStateKeyName))
            {
                var state = this.algorithm.ObjectStore.ReadJson<AlphaModelState>(this.alphaModelStateKeyName);

                if (state != null)
                {
                    if (this.algorithm.Time.Date <= (state.Updated + this.oldestAlphaModelState).Date)
                    {
                        return state;
                    }
                }
            }

            return new AlphaModelState();
        }

        /// <summary>
        /// Saves the indicated alpha model state to the object store.
        /// </summary>
        /// <param name="state"><see cref="AlphaModelState"/> to save to the object store.</param>
        private void SaveState(AlphaModelState state) =>
            _ = this.algorithm.ObjectStore.SaveJson(this.alphaModelStateKeyName, state);

        /// <summary>
        /// Symbol data for the securities used by the alpha model.
        /// </summary>
        public class SymbolData
        {
            private const int ReturnLookbackPeriods = 252; // "RET"
            private const int ExclusionPeriods = 21; // "EXCL"

            private readonly QCAlgorithm algorithm;

            /// <summary>
            /// Initializes a new instance of the <see cref="SymbolData"/> class.
            /// </summary>
            /// <param name="algorithm">Algorithm executing this alpha model.</param>
            /// <param name="symbol"><see cref="Symbol"/> of this data object.</param>
            public SymbolData(QCAlgorithm algorithm, Symbol symbol)
            {
                this.algorithm = algorithm;
                this.Symbol = symbol;
            }

            /// <summary>
            /// Gets the returns for this symbol.
            /// </summary>
            public decimal Returns
            {
                get
                {
                    var priceHistory = this.algorithm.History<TradeBar>(this.Symbol, TimeSpan.FromDays(ReturnLookbackPeriods + ExclusionPeriods), Resolution.Daily);
                    var closePrices = priceHistory.Select(x => x.Close).ToList();

                    return closePrices[^ExclusionPeriods] / closePrices[0];
                }
            }

            /// <summary>
            /// Gets the <see cref="Symbol"/> for this data object.
            /// </summary>
            public Symbol Symbol { get; private set; }
        }

        /// <summary>
        /// Alpha model state data.
        /// </summary>
        public class AlphaModelState
        {
            /// <summary>
            /// Gets or sets the number of days the alpha model has been in risk-off mode.
            /// </summary>
            public int OutDayCount { get; set; }

            /// <summary>
            /// Gets or sets the last time the alpha model's state was updated.
            /// </summary>
            public DateTime Updated { get; set; }
        }

        /// <summary>
        /// Indicator signal data.
        /// </summary>
        public class IndicatorSignal
        {
            /// <summary>
            /// Gets or sets if the algorithm has calculated it should be in risk-off mode.
            /// </summary>
            public bool RiskOff { get; set; }

            /// <summary>
            /// Gets or sets the number of days the algorithm should be in risk-off mode.
            /// </summary>
            public int WaitDays { get; set; }
        }
    }
}
// --------------------------------------------------
// Copyright (c) John Cardenas. All rights reserved.
// --------------------------------------------------

namespace InOutMultiMomentum.UniverseSelection
{
    using System;
    using System.Collections.Generic;

    /// <inheritdoc/>
    public class NTSXUniverse : InOutUniverseSelectionModel
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="NTSXUniverse"/> class.
        /// </summary>
        public NTSXUniverse()
            : base(
                nameof(NTSXUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "NTSX", new DateTime(2018, 8, 02) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TLT", new DateTime(2002, 7, 22) },
                    { "TLH", new DateTime(2007, 1,  5) },
                })
        {
        }
    }
}
using System;
using System.Collections.Generic;

namespace InOutMultiMomentum.UniverseSelection
{
    public class NTSXBondUniverse : InOutUniverseSelectionModel
    {
        public NTSXBondUniverse() :
            base(
                nameof(NTSXBondUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "NTSX", new DateTime(2018,  8,  2) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "TMF", new DateTime(2009, 4, 16) },
                    { "TYD", new DateTime(2009, 4, 16) },
                }
            )
        {
        }
    }
}
using System;
using System.Collections.Generic;

namespace InOutMultiMomentum.UniverseSelection
{
    public class NTSXSWANUniverse : InOutUniverseSelectionModel
    {
        public NTSXSWANUniverse() :
            base(
                nameof(NTSXSWANUniverse),
                new Dictionary<string, DateTime>() // Risk on
                {
                    { "NTSX", new DateTime(2018,  8,  2) },
                },
                new Dictionary<string, DateTime>() // Risk off
                {
                    { "SWAN", new DateTime(2018, 11, 16) },
                }
            )
        {
        }
    }
}