Overall Statistics
Total Orders
353
Average Win
1.70%
Average Loss
-1.48%
Compounding Annual Return
19.096%
Drawdown
31.900%
Expectancy
0.394
Start Equity
100000
End Equity
277439.35
Net Profit
177.439%
Sharpe Ratio
0.745
Sortino Ratio
0.755
Probabilistic Sharpe Ratio
26.329%
Loss Rate
35%
Win Rate
65%
Profit-Loss Ratio
1.14
Alpha
0.062
Beta
0.875
Annual Standard Deviation
0.175
Annual Variance
0.031
Information Ratio
0.508
Tracking Error
0.103
Treynor Ratio
0.149
Total Fees
$1359.21
Estimated Strategy Capacity
$11000000.00
Lowest Capacity Asset
KBE TDP0JIUCTNJ9
Portfolio Turnover
4.73%
#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 class WellDressedVioletChinchilla : QCAlgorithm
    {
        private List<BrainSentimentIndicatorUniverse> _brainUniverseData;
        private Dictionary<Symbol, IEnumerable<ETFConstituentUniverse>> _etfConstituentsDataBySymbol = new();
        private Dictionary<Symbol, decimal?> _scoreBySector = new();
        private int _numEtfs = 3;
        private Dictionary<DateTime, decimal> _portfolioValues = new();
        private bool _rebalance = false;

        public override void Initialize()
        {
            SetStartDate(2017, 1, 1);
            SetEndDate(2022, 11, 1);
            SetCash(100000);

            AddUniverse<BrainSentimentIndicatorUniverse>(
                "BrainSentimentIndicatorUniverse", Resolution.Daily,
                altCoarse =>
                {
                    _brainUniverseData = altCoarse.Select(x => (BrainSentimentIndicatorUniverse)x).ToList();
                    return Enumerable.Empty<Symbol>(); 
                });

            // Sector ETFs from https://www.cnbc.com/sector-etfs/
            var tickers = new[]
            {
                "XLE", // Energy Select Sector SPDR Fund
                "XLF", // Financial Select Sector SPDR Fund
                "XLU", // Utilities Select Sector SPDR Fund
                "XLI", // Industrial Select Sector SPDR Fund
                "GDX", // VanEck Gold Miners ETF
                "XLK", // Technology Select Sector SPDR Fund
                "XLV", // Health Care Select Sector SPDR Fund
                "XLY", // Consumer Discretionary Select Sector SPDR Fund
                "XLP", // Consumer Staples Select Sector SPDR Fund
                "XLB", // Materials Select Sector SPDR Fund
                "XOP", // Spdr S&P Oil & Gas Exploration & Production Etf
                "IYR", // iShares U.S. Real Estate ETF
                "XHB", // Spdr S&P Homebuilders Etf
                "ITB", // iShares U.S. Home Construction ETF
                "VNQ", // Vanguard Real Estate Index Fund ETF Shares
                "GDXJ",// VanEck Junior Gold Miners ETF
                "IYE", // iShares U.S. Energy ETF
                "OIH", // VanEck Oil Services ETF
                "XME", // SPDR S&P Metals & Mining ETF
                "XRT", // Spdr S&P Retail Etf
                "SMH", // VanEck Semiconductor ETF
                "IBB", // iShares Biotechnology ETF
                "KBE", // SPDR S&P Bank ETF
                "KRE", // SPDR S&P Regional Banking ETF
                "XTL"  // SPDR S&P Telecom ETF
            };

            foreach (var ticker in tickers)
            {
                var etfSymbol = AddEquity(ticker, Resolution.Daily).Symbol;
                AddUniverse(Universe.ETF(etfSymbol, Market.USA, UniverseSettings,
                    constituents =>
                    {
                        _etfConstituentsDataBySymbol[etfSymbol] = constituents;
                        return Enumerable.Empty<Symbol>();
                    })
                );
            }

            // Schedule the rebalance
            Schedule.On(DateRules.MonthStart(GetParameter("rebalance-day", 0)),
                TimeRules.Midnight,
                () =>
                {
                    _rebalance = true;
                });
        }

        public override void OnData(Slice data)
        {
            // Record net portfolio value
            _portfolioValues[Time] = Portfolio.TotalPortfolioValue;

            // Rebalance?
            if (!_rebalance)
            {
                return;
            }
            _rebalance = false;

            // Calculate sector sentiment
            foreach (var kvp in _etfConstituentsDataBySymbol)
            {
                var etfSymbol = kvp.Key;
                var etfConstituentsData = kvp.Value;
                var sectorSymbols = etfConstituentsData.Select(etfConstituentsData => etfConstituentsData.Symbol).ToHashSet();
                var etfWeightBySymbol = sectorSymbols.ToDictionary(
                    sectorSymbol => sectorSymbol, 
                    sectorSymbol => etfConstituentsData.Where(etfConstituentsData => etfConstituentsData.Symbol == sectorSymbol).FirstOrDefault().Weight
                );

                _scoreBySector[etfSymbol] = _brainUniverseData
                    .Where(brainSentiment => sectorSymbols.Contains(brainSentiment.Symbol))
                    .Select(brainSentiment => brainSentiment.Sentiment30Days * etfWeightBySymbol[brainSentiment.Symbol])
                    .Sum();
            }

            // Select target ETFs
            var targetSymbols = _scoreBySector.OrderByDescending(x => x.Value).Take(_numEtfs).Select(kvp => kvp.Key);;

            // Liquidate ETFs that are no longer targeted
            SetHoldings(Portfolio.Where(kvp => kvp.Value.Invested && !targetSymbols.Contains(kvp.Key)).Select(kvp => new PortfolioTarget(kvp.Key, 0)).ToList());

            // Rebalance targeted ETFs
            var weight = 1.0m / _numEtfs;
            SetHoldings(targetSymbols.Select(symbol => new PortfolioTarget(symbol, weight)).ToList());
        }

        public override void OnEndOfAlgorithm()
        {
            // Save daily portfolio values to ObjectStore
            ObjectStore.SaveJson<Dictionary<DateTime, decimal>>($"{ProjectId}/portfolioValues", _portfolioValues);
        }
    }
}