Overall Statistics
Total Trades
205
Average Win
11.52%
Average Loss
-1.82%
Compounding Annual Return
37.488%
Drawdown
40.600%
Expectancy
2.627
Net Profit
3314.240%
Sharpe Ratio
1.047
Probabilistic Sharpe Ratio
37.126%
Loss Rate
51%
Win Rate
49%
Profit-Loss Ratio
6.33
Alpha
0.247
Beta
0.517
Annual Standard Deviation
0.284
Annual Variance
0.081
Information Ratio
0.707
Tracking Error
0.283
Treynor Ratio
0.575
Total Fees
$1673.14
Estimated Strategy Capacity
$370000.00
Lowest Capacity Asset
UGL U85WJOCE24BP
#region imports
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Globalization;
    using System.Drawing;
    using QuantConnect;
    using QuantConnect.Algorithm.Framework;
    using QuantConnect.Algorithm.Framework.Selection;
    using QuantConnect.Algorithm.Framework.Alphas;
    using QuantConnect.Algorithm.Framework.Portfolio;
    using QuantConnect.Algorithm.Framework.Execution;
    using QuantConnect.Algorithm.Framework.Risk;
    using QuantConnect.Parameters;
    using QuantConnect.Benchmarks;
    using QuantConnect.Brokerages;
    using QuantConnect.Util;
    using QuantConnect.Interfaces;
    using QuantConnect.Algorithm;
    using QuantConnect.Indicators;
    using QuantConnect.Data;
    using QuantConnect.Data.Consolidators;
    using QuantConnect.Data.Custom;
    using QuantConnect.DataSource;
    using QuantConnect.Data.Fundamental;
    using QuantConnect.Data.Market;
    using QuantConnect.Data.UniverseSelection;
    using QuantConnect.Notifications;
    using QuantConnect.Orders;
    using QuantConnect.Orders.Fees;
    using QuantConnect.Orders.Fills;
    using QuantConnect.Orders.Slippage;
    using QuantConnect.Scheduling;
    using QuantConnect.Securities;
    using QuantConnect.Securities.Equity;
    using QuantConnect.Securities.Future;
    using QuantConnect.Securities.Option;
    using QuantConnect.Securities.Forex;
    using QuantConnect.Securities.Crypto;   
    using QuantConnect.Securities.Interfaces;
    using QuantConnect.Storage;
    using QuantConnect.Data.Custom.AlphaStreams;
    using QCAlgorithmFramework = QuantConnect.Algorithm.QCAlgorithm;
    using QCAlgorithmFrameworkBridge = QuantConnect.Algorithm.QCAlgorithm;
#endregion
namespace QuantConnect.Algorithm.CSharp
{
    public static class HelperMethods
    {
        public static void SetupRsi(this Dictionary<string,RelativeStrengthIndex> collection, QCAlgorithm algorithm, string ticker, int periods)
        {
            var indicator = new RelativeStrengthIndex(periods);
            algorithm.RegisterIndicator(ticker, indicator, TimeSpan.FromDays(1));

            collection.Add(ticker, indicator);
        }
    }

    public class CasualBrownFish : QCAlgorithm
    {
        private enum PortfolioDesign
        {
            RiskOn = 1,
            RiskOffRisingRates,
            RiskOffFallingRates
        }

        private readonly List<string> _tickers = new() {"BND", "BIL", "TECL", "TQQQ", "UPRO", "TMF", "TLT", "UUP", "QID", "TBF", "UGL", "BTAL", "XLP", "SPY"};
        private readonly List<string> _cumulativeReturnTickers = new() { "BND", "BIL", "TLT" };
        private readonly Dictionary<string,Equity> _equities = new();
        
        private DateTime _lastEvaluation;
        private DateTime _nextEvaluation;

        private PortfolioDesign _lastPortfolioDesign = 0;

        // RSI fund surfers
        private readonly Dictionary<string,RelativeStrengthIndex> _riskOnEquities = new();
        private readonly Dictionary<string,RelativeStrengthIndex> _riskOffEquities = new();

        // Rolling windows of close prices
        private readonly Dictionary<string,RollingWindow<decimal>> _closePrices = new();

        public override void Initialize()
        {
            var startDate = new DateTime(2011, 9, 13, 13, 0, 0);

            SetStartDate(startDate);
            SetCash(10000);
            SetBenchmark("SPY");

            // Load ticker data feeds
            foreach (var ticker in _tickers)
            {
                _equities.Add(ticker, AddEquity(ticker, Resolution.Hour));

                var consolidator = new TradeBarConsolidator(TimeSpan.FromDays(1));
                consolidator.DataConsolidated += ConsolidationHandler;
                SubscriptionManager.AddConsolidator(_equities[ticker].Symbol, consolidator);
            }

            // Warm up price history
            WarmUpPriceData("BND", 60);
            WarmUpPriceData("BIL", 60);
            WarmUpPriceData("TLT", 20);

            // Set up RSI indicators
            _riskOnEquities.SetupRsi(this, "TECL", 10);
            _riskOnEquities.SetupRsi(this, "TQQQ", 10);
            _riskOnEquities.SetupRsi(this, "UPRO", 10);
            _riskOnEquities.SetupRsi(this, "TMF", 10);

            _riskOffEquities.SetupRsi(this, "QID", 20);
            _riskOffEquities.SetupRsi(this, "TBF", 20);

            // Schedule symphony rule evaluations
            Schedule.On(DateRules.EveryDay("SPY"),
                        TimeRules.At(15, 30, 0), // 3:30 PM
                        EvaluateSymphony);
        }

        /// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
        /// Slice object keyed by symbol containing the stock data
        public override void OnData(Slice data)
        {
        }

        private DateTime CalculateNextEvaluationDate()
        {
            var rebalanceDates = new List<DateTime>
            {
                new DateTime(this.Time.Year, 4, 1),
                new DateTime(this.Time.Year, 7, 1),
                new DateTime(this.Time.Year, 10, 1),
                new DateTime(this.Time.Year + 1, 1, 1),
            }.OrderBy(d => d);

            var thisDate = this.Time;

            var nextEvaluationDate = thisDate >= rebalanceDates.Last()
                ? rebalanceDates.Last()
                : thisDate <= rebalanceDates.First()
                    ? rebalanceDates.First()
                    : rebalanceDates.First(d => d >= thisDate);

            return nextEvaluationDate;
        }

        private bool ShouldEvaluateSymphony()
        {
            if (_lastEvaluation == default)
            {
                return true;
            }

            return this.Time.Date > _nextEvaluation;
        }

        private void EvaluateSymphony()
        {
            // Check all indicators are ready
            if (_closePrices.Values.All(x => x.IsReady == true) &&
                _riskOnEquities.Values.All(x => x.IsReady == true) &&
                _riskOffEquities.Values.All(x => x.IsReady == true) &&
                ShouldEvaluateSymphony()) // Trade according to the date rules
            {
                // Symphony algorithm
                if (CumulativeReturn("BND", 60) > CumulativeReturn("BIL", 60))
                {
                    // Risk ON - select lowest 3 RSI
                    var equities = _riskOnEquities
                        .OrderByDescending(x => x.Value.Current.Value)
                        .TakeLast(3)
                        .Select(x => x.Key)
                        .ToList();
                    
                    SetHoldings(equities.Select(x => 
                        new PortfolioTarget(x, 1m / equities.Count)
                    ).ToList(), liquidateExistingHoldings: _lastPortfolioDesign != PortfolioDesign.RiskOn);

                    _lastPortfolioDesign = PortfolioDesign.RiskOn;
                }
                else
                {
                    if (CumulativeReturn("TLT", 20) < CumulativeReturn("BIL", 20))
                    {
                        // Risk OFF, Rising Rates - UUP at 50%, select lowest RSI
                        var equity = _riskOffEquities
                            .OrderByDescending(x => x.Value.Current.Value)
                            .TakeLast(1)
                            .Select(x => x.Key)
                            .First();

                        SetHoldings(new List<PortfolioTarget>() {
                            new PortfolioTarget("UUP", 0.5m),
                            new PortfolioTarget(equity, 0.5m)
                        }, liquidateExistingHoldings: _lastPortfolioDesign != PortfolioDesign.RiskOffRisingRates);

                        _lastPortfolioDesign = PortfolioDesign.RiskOffRisingRates;
                    }
                    else
                    {
                        // Risk OFF, Falling Rates
                        SetHoldings(new List<PortfolioTarget>() {
                            new PortfolioTarget("UGL", 0.25m),
                            new PortfolioTarget("TMF", 0.25m),
                            new PortfolioTarget("BTAL", 0.25m),
                            new PortfolioTarget("XLP", 0.25m)
                        }, liquidateExistingHoldings: _lastPortfolioDesign != PortfolioDesign.RiskOffFallingRates);

                        _lastPortfolioDesign = PortfolioDesign.RiskOffFallingRates;
                    }
                }

                // Update last evaluation
                _lastEvaluation = this.Time.Date;
                _nextEvaluation = CalculateNextEvaluationDate();
            }
        }

        private decimal CumulativeReturn(string ticker, int periods)
        {
            var returns = _closePrices[ticker][0] / _closePrices[ticker][1];

            for (int i = 1; i < periods - 2; i++)
            {
                returns *= _closePrices[ticker][i] / _closePrices[ticker][i+1];
            }

            return returns - 1;
        }

        private void ConsolidationHandler(object sender, TradeBar data)
        {
            if (_cumulativeReturnTickers.Contains(data.Symbol.Value))
            {
                _closePrices[data.Symbol.Value].Add(data.Close);
            }
        }

        private void WarmUpPriceData(string ticker, int periods)
        {
            _closePrices.Add(ticker, new RollingWindow<decimal>(periods));
            var history = History<TradeBar>(ticker, periods, Resolution.Daily);
            
            foreach (var tradeBar in history)
            {
                _closePrices[ticker].Add(tradeBar.Close);
            }
        }
    }
}