Overall Statistics
Total Orders
353
Average Win
1.70%
Average Loss
-1.48%
Compounding Annual Return
19.095%
Drawdown
31.900%
Expectancy
0.394
Start Equity
100000
End Equity
277424.27
Net Profit
177.424%
Sharpe Ratio
0.745
Sortino Ratio
0.754
Probabilistic Sharpe Ratio
26.323%
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.80
Estimated Strategy Capacity
$11000000.00
Lowest Capacity Asset
KBE TDP0JIUCTNJ9
Portfolio Turnover
4.73%
from AlgorithmImports import *
from collections import defaultdict
import json

class WellDressedVioletChinchilla(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2017, 1, 1)
        self.SetEndDate(2022, 11, 1)
        self.SetCash(100000)

        self._brainUniverseData = []
        self._etfConstituentsDataBySymbol = defaultdict(list)
        self._scoreBySector = {}
        self._numEtfs = 3
        self._portfolioValues = {}
        self._rebalance = False

        self.AddUniverse(BrainSentimentIndicatorUniverse, "BrainSentimentIndicatorUniverse", Resolution.Daily, self.BrainsentimentSelection)

        tickers = [
            "XLE", "XLF", "XLU", "XLI", "GDX", "XLK", "XLV", "XLY",
            "XLP", "XLB", "XOP", "IYR", "XHB", "ITB", "VNQ", "GDXJ",
            "IYE", "OIH", "XME", "XRT", "SMH", "IBB", "KBE", "KRE", "XTL"
        ]

        for ticker in tickers:
            etf_symbol = self.AddEquity(ticker, Resolution.Daily).Symbol
            self.add_universe(self.universe.etf(etf_symbol, Market.USA, self.UniverseSettings, self.ETFConstituentSelection(etf_symbol)))

        self.Schedule.On(self.DateRules.MonthStart(self.GetParameter("rebalance-day", 0)), self.TimeRules.Midnight, self.Rebalance)

    def BrainsentimentSelection(self, altCoarse):
        self._brainUniverseData = [x for x in altCoarse]
        return []

    def ETFConstituentSelection(self, etf_symbol):
        def _ETFConstituentSelection(constituents):
            self._etfConstituentsDataBySymbol[etf_symbol] = constituents
            return []
        return _ETFConstituentSelection

    def OnData(self, data):
        self._portfolioValues[self.Time] = self.Portfolio.TotalPortfolioValue

        if not self._rebalance:
            return
        self._rebalance = False

        for etf_symbol, etfConstituentsData in self._etfConstituentsDataBySymbol.items():
            sector_symbols = {c.Symbol for c in etfConstituentsData}
            etf_weight_by_symbol = {c.Symbol: c.Weight for c in etfConstituentsData}

            self._scoreBySector[etf_symbol] = sum(
                brain.Sentiment30Days * etf_weight_by_symbol[brain.Symbol]
                for brain in self._brainUniverseData if brain.Symbol in sector_symbols
            )

        target_symbols = sorted(self._scoreBySector, key=self._scoreBySector.get, reverse=True)[:self._numEtfs]

        self.SetHoldings(
            [PortfolioTarget(kvp.Key, 0) for kvp in self.Portfolio if kvp.Value.Invested and kvp.Key not in target_symbols]
        )

        weight = 1.0 / self._numEtfs
        self.SetHoldings([PortfolioTarget(symbol, weight) for symbol in target_symbols])

    def Rebalance(self):
        self._rebalance = True