---
title: "ETF Constituents Universes"
description: "You can select a universe based on the constituents of an ETF, and then you can further filter your universe down with fundamentals."
canonical: https://www.quantconnect.com/docs/v2/writing-algorithms/universes/equity/etf-constituents-universes
markdown: https://www.quantconnect.com/docs/v2/writing-algorithms/universes/equity/etf-constituents-universes.md
language: python
source: https://github.com/QuantConnect/Documentation/tree/master/03%20Writing%20Algorithms/12%20Universes/03%20Equity/04%20ETF%20Constituents%20Universes
modified: 2026-11-02
index: https://www.quantconnect.com/llms.txt
---

# ETF Constituents Universes

[Documentation](https://www.quantconnect.com/docs/v2.md) > [Writing Algorithms](https://www.quantconnect.com/docs/v2/writing-algorithms.md) > Universes > [Equity](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/equity.md) > ETF Constituents Universes

> Code examples are shown in Python. Append `?lang=csharp` to this URL for the C# version of this page.

## Introduction

An ETF constituents universe lets you select a universe of securities in an exchange traded fund. The [US ETF Constituents dataset](https://www.quantconnect.com/datasets/quantconnect-us-etf-constituents) includes 2,650 US ETFs you can use to create your universe.

## Create Universes

To add an ETF Constituents universe, call the `universe.etf` method.

```python
class ETFConstituentsAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.universe_settings.asynchronous = True
        self.add_universe(self.universe.etf("SPY"))
```

The following table describes the `etf` method arguments:

### Argument: `etf_ticker`

The ETF ticker. To view the supported ETFs in the US ETF Constituents dataset, see [Supported ETFs](https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/quantconnect/us-etf-constituents.md#08-Supported-ETFs).

Data Type: `str` | Default Value: `None`

### Argument: `universe_settings`

The [universe settings](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/settings.md). If you don't provide an argument, it uses the algorithm `universe_settings`.

Data Type: `UniverseSettings` | Default Value: `None`

### Argument: `universe_filter_func`

A function to select some of the ETF constituents for the universe. If you don't provide an argument, it selects all of the constituents.

Data Type: `Callable[[List[ETFConstituentUniverse]], List[Symbol]]` | Default Value: `None`

To select a subset of the ETF constituents, provide a `universe_filter_func` argument. The filter function receives `ETFConstituentUniverse` objects, which represent one of the ETF constituents. `ETFConstituentUniverse` objects have the following attributes:

*Members of `QuantConnect.Data.UniverseSelection.ETFConstituentUniverse` (JSON): https://www.quantconnect.com/services/inspector?type=T:QuantConnect.Data.UniverseSelection.ETFConstituentUniverse&language=python*

```python
class ETFConstituentsAlgorithm(QCAlgorithm):
    def initialize(self) -> None:
        self.universe_settings.asynchronous = True
        self._universe = self.universe.etf("SPY", self.universe_settings, self._etf_constituents_filter)
        self.add_universe(self._universe)

    def _etf_constituents_filter(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]:
        # Get the 10 securities with the largest weight in the index
        selected = sorted(
            constituents, key=lambda c: c.weight or 0.0, reverse=True
        )[:10]
        return [c.symbol for c in selected]
```

## Historical Data

To get historical ETF constituents data, call the `history` method with the `Universe` object and the lookback period.

```python
# DataFrame object:
df_history = self.history(self._universe, 30, Resolution.DAILY, flatten=True)

# Series object:
series_history = self.history(self._universe, 30, Resolution.DAILY)
for (universe_symbol, time), constituents in history.items():
    for constituent in constituents:
        self.log(f'{constituent.symbol} weight at {constituent.end_time}: {constituent.weight}')
```

For more information about ETF Constituents data, see [US ETF Constituents](https://www.quantconnect.com/docs/v2/writing-algorithms/datasets/quantconnect/us-etf-constituents.md).

## Selection Frequency

Equity universes run on a daily basis by default. To adjust the selection schedule, see [Schedule](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/settings.md#10-Schedule).

## Examples

The following examples demonstrate some common practices for ETF constituent universes.

### Example 1: Short the Smallest ETF Constituents

A subset of the SPY constituents outperform the SPY while many of the constituents underperform the overall index. In an attempt to exclude the ETF constituents that underperform the index, the following algorithm buys the SPY and shorts the 100 assets in the index with the smallest weight in the ETF.

```python
class ETFConstituentsUniverseAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)
        self.set_cash(10_000_000)
        self.settings.minimum_order_margin_portfolio_percentage = 0
        # Seed the price of each asset with its last known price to avoid trading errors.
        self.settings.seed_initial_prices = True
        # To avoid over-trading and high transaction costs, refilter and rebalance weekly.
        self.universe_settings.schedule.on(self.date_rules.week_end())
        # Add the SPY ETF.
        self._spy = self.add_equity("SPY").symbol
        # Add a universe of the SPY constituents.
        self._universe = self.add_universe(self.universe.etf(self._spy, universe_filter_func=self._select_assets))
        # Create a Scheduled Event to rebalance the portfolio.
        self.schedule.on(self.date_rules.week_start(), self.time_rules.at(9, 0), self._rebalance)

    def _select_assets(self, constituents: List[ETFConstituentUniverse]) -> List[Symbol]:
        # Cache the constituent weights in a dictionary for filtering and position sizing.
        self._etf_weight_by_symbol = {c.symbol: c.weight for c in constituents if c.weight}
        # Select the 100 consituents with the smallest weight in the ETF.
        # They should have negative excess return.
        return [symbol for symbol, _ in sorted(self._etf_weight_by_symbol.items(), key=lambda x: x[1])[:100]]

    def _rebalance(self) -> None:
        # Get the ETF weight of all the assets currently in the universe.
        weight_by_symbol = {
            symbol: self._etf_weight_by_symbol[symbol] for symbol in self._universe.selected
            # To avoid trading errors, skip assets that have no price yet.
            if self.securities[symbol].price
        }
        # Buy the SPY to eliminate systematic risk.
        targets = [PortfolioTarget(self._spy, 1-sum(weight_by_symbol.values()))]
        # Sell the 100 ETF constituents with the lowest weight due to expected negative excess return.
        targets.extend(
            [PortfolioTarget(symbol, -weight) for symbol, weight in weight_by_symbol.items()]
        )
        # Place orders to rebalance the portfolio.
        self.set_holdings(targets, liquidate_existing_holdings=True)
```

### Example 2: SPY Constituents in an Uptrend

The following example chains an ETF constituents universe and a [fundamental universe](https://www.quantconnect.com/docs/v2/writing-algorithms/universes/equity/fundamental-universes.md) . It first selects all the constituents of the SPY ETF and then filters them down in the fundamental universe filter to select the assets that are trading above their average price over the last 200 days. The output of the fundamental universe selection method is the output of the chained universe. The `Fundamental` objects that the fundamental universe filter function recieves contains the prices of the ETF constituents. By chaining the ETF constituents universe into the fundamental universe, you can update the indicators with the price instead of making a [history request](https://www.quantconnect.com/docs/v2/writing-algorithms/historical-data/history-requests.md) .

```python
class ChainedUniverseAlgorithm(QCAlgorithm):

    _selection_data_by_symbol = {}

    def initialize(self):
        self.set_start_date(2024, 9, 1)
        self.set_end_date(2024, 12, 31)
        # Seed the price of each asset that enters the universe with its last known price so you can trade it
        # on the same morning it enters the universe without getting warnings.
        self.settings.seed_initial_prices = True
        # Add a chained universe that selects the SPY constituents trading above their 200-day SMA.
        spy = Symbol.create("SPY", SecurityType.EQUITY, Market.USA)
        self._universe = self.add_universe(self.universe.etf(spy), self._fundamental_selection)
        # Trade daily at market open since the trading signal is generated on a daily resolution.
        self.schedule.on(self.date_rules.every_day(spy), self.time_rules.after_market_open(spy, 1), self._rebalance)

    def _fundamental_selection(self, fundamental: List[Fundamental]) -> List[Symbol]:
        # Create/Update an SMA indicator for each asset that enters the ETF.
        universe_symbols = []
        for f in fundamental:
            universe_symbols.append(f.symbol)
            if f.symbol not in self._selection_data_by_symbol:
                self._selection_data_by_symbol[f.symbol] = SelectionData(self, f.symbol)
            self._selection_data_by_symbol[f.symbol].update(self.time, f.adjusted_price)

        # Remove indicators for assets that are no longer in the ETF to release the computational resources.
        symbols_to_remove = [s for s in self._selection_data_by_symbol.keys() if s not in universe_symbols]
        for symbol in symbols_to_remove:
            self._selection_data_by_symbol.pop(symbol)

        # Select the Equities trading above their SMA as we estimate them to be in uptrend and still rising.
        selected = [
            symbol for symbol, selection_data in self._selection_data_by_symbol.items() if selection_data.is_above_sma
        ]

        # Plot the results.
        self.plot("Universe", "Possible", len(list(fundamental)))
        self.plot("Universe", "Selected", len(selected))

        return selected

    def _rebalance(self) -> None:
        # Form an equal-weighted portfolio with the Equities that are above their SMA.
        symbols = [symbol for symbol in self._universe.selected if self.securities[symbol].price]
        if not symbols:
            return
        weight = 1 / len(symbols)
        self.set_holdings([PortfolioTarget(symbol, weight) for symbol in symbols], liquidate_existing_holdings=True)

# Define a separate class to contain the SMA indicator.
class SelectionData(object):

    def __init__(self, algorithm, symbol, period=200):
        #  Create the SMA indicator for trend detection and filtering.
        self._sma = SimpleMovingAverage(period)
        # Warm up the SMA so you can immediately use it.
        algorithm.warm_up_indicator(symbol, self._sma, Resolution.DAILY)

    def update(self, time, value):
        self.is_above_sma = self._sma.update(time, value) and value > self._sma.current.value
```

### Other Examples

For more examples, see the following algorithms:

**Demonstration Algorithms**

- [ETFConstituentUniverseRSIAlphaModelAlgorithm.py](https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/ETFConstituentUniverseRSIAlphaModelAlgorithm.py) (Python)
- [UniverseOnlyRegressionAlgorithm.py](https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/UniverseOnlyRegressionAlgorithm.py) (Python)
