Overall Statistics
Total Orders
3553
Average Win
0.11%
Average Loss
-0.09%
Compounding Annual Return
14.373%
Drawdown
24.200%
Expectancy
0.766
Start Equity
1000000000
End Equity
3832974979.31
Net Profit
283.297%
Sharpe Ratio
0.722
Sortino Ratio
0.785
Probabilistic Sharpe Ratio
19.774%
Loss Rate
23%
Win Rate
77%
Profit-Loss Ratio
1.29
Alpha
0.009
Beta
0.98
Annual Standard Deviation
0.131
Annual Variance
0.017
Information Ratio
0.143
Tracking Error
0.052
Treynor Ratio
0.096
Total Fees
$10505589.32
Estimated Strategy Capacity
$42000000.00
Lowest Capacity Asset
JNJ R735QTJ8XC9X
Portfolio Turnover
1.07%
from AlgorithmImports import *
from QuantConnect.DataSource import *

class ETFConstituentUniverseAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2010, 1, 1)
        self.set_end_date(2020, 1, 1)
        self.set_cash(1_000_000_000)
        self.universe_settings.asynchronous = True
        self.universe_settings.resolution = Resolution.MINUTE

        # Requesting data
        self.spy = self.add_equity("SPY").symbol 
        self._universe = self.add_universe(self.universe.etf(self.spy, self.universe_settings, self.etf_constituents_filter))
        
        # Example of Historical Universe Data
        history = self.history(self._universe, 30, Resolution.DAILY)
        for (universe_symbol, time), constituents in history.items():
            for constituent in constituents:
                self.debug(f'{constituent.symbol}: {constituent.weight}')

        self.weight_by_symbol = {}
        
        # Set up daily rebalance scheduled event
        self.schedule.on(
            self.date_rules.every_day(self.spy),
            self.time_rules.after_market_open(self.spy, 1),
            self.rebalance)

    def etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]:
        # Get the 10 securities with the largest weight in the index, save the weights
        selected = sorted([c for c in constituents if c.weight],
            key=lambda c: c.weight, reverse=True)[:10]
        self.weight_by_symbol = {c.symbol: c.weight for c in selected}
        
        return list(self.weight_by_symbol.keys())

    def rebalance(self) -> None:
        spy_weight = sum(self.weight_by_symbol.values())

        # Liquidate the ones not in top 10 weights
        if spy_weight > 0:
            for symbol in self.portfolio.Keys:
                if symbol not in self.weight_by_symbol:
                    self.liquidate(symbol)

            # Invest portfolio by normalized weights of the top 10 constituents
            for symbol, weight in self.weight_by_symbol.items():
                self.set_holdings(symbol, 1 * weight / spy_weight) 

    def on_securities_changed(self, changes: SecurityChanges) -> None:
        for security in changes.removed_securities:
            if security.invested:
                self.liquidate(security.symbol, 'Removed From Universe')

        for security in changes.added_securities:
            # Historical data for newly added constituents
            history = self.history(security.symbol, 7, Resolution.DAILY)
            self.debug(f'We got {len(history)} prices for asset from our history request for {security.symbol}')