Overall Statistics
Total Trades
366
Average Win
1.55%
Average Loss
-1.49%
Compounding Annual Return
16.111%
Drawdown
27.000%
Expectancy
0.307
Net Profit
139.193%
Sharpe Ratio
0.697
Probabilistic Sharpe Ratio
16.184%
Loss Rate
36%
Win Rate
64%
Profit-Loss Ratio
1.04
Alpha
0.05
Beta
0.822
Annual Standard Deviation
0.183
Annual Variance
0.033
Information Ratio
0.266
Tracking Error
0.127
Treynor Ratio
0.155
Total Fees
$1382.59
Estimated Strategy Capacity
$6000000.00
Lowest Capacity Asset
IYE RVLEALAHHC2T
#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<ETFConstituentData>> _etfConstituentsDataBySymbol = new();
        private Dictionary<Symbol, decimal?> _scoreBySector = new();
        private bool _rebalance = false;
        private int _numEtfs;
        private double _pctConstituents;
        private Dictionary<DateTime, decimal> _portfolioValues = new();

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

            Settings.FreePortfolioValuePercentage = 0.025m;

            _numEtfs = GetParameter("numEtfs", 3);
            _pctConstituents = GetParameter("pctConstituents", 0.5);

            AddUniverse<BrainSentimentIndicatorUniverse>(
                "BrainSentimentIndicatorUniverse", Resolution.Daily,
                altCoarse =>
                {
                    _brainUniverseData = altCoarse.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)
            {
                // Select constituents with largest weight
                var etfSymbol = kvp.Key;
                var constituentsData = kvp.Value;
                var symbols = constituentsData
                    .OrderByDescending(x => x.Weight)
                    .Take((int)(_pctConstituents * (double)constituentsData.Count()))
                    .Select(x => x.Symbol);

                // Calculate average sentiment of sector subset
                _scoreBySector[etfSymbol] = _brainUniverseData
                    .Where(brainSentiment => symbols.Contains(brainSentiment.Symbol))
                    .Select(brainSentiment => brainSentiment.Sentiment30Days)
                    .Average();
            }

            // 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);
        }
    }
}