book
Checkout our new book! Hands on AI Trading with Python, QuantConnect, and AWS Learn More arrow

Research Environment

Universes

Introduction

Universe selection is the process of selecting a basket of assets to research. Dynamic universe selection increases diversification and decreases selection bias in your analysis.

Get Universe Data

Universes are data types. To get historical data for a universe, pass the universe data type to the UniverseHistory method. The object that returns contains a universe data collection for each day. With this object, you can iterate through each day and then iterate through the universe data objects of each day to analyze the universe constituents.

For example, follow these steps to get the US Equity Fundamental data for a specific universe:

  1. Create a QuantBook.
  2. Select Language:
    qb = QuantBook()
  3. Define a universe.
  4. The following example defines a dynamic universe that contains the 10 Equities with the lowest PE ratios in the market. To see all the Fundamental attributes you can use to define a filter function for a Fundamental universe, see Data Point Attributes. To create the universe, call the add_universe method with the filter function.

    Select Language:
    def filter_function(fundamentals):
        sorted_by_pe_ratio = sorted(
            [f for f in fundamentals if not np.isnan(f.valuation_ratios.pe_ratio)], 
            key=lambda fundamental: fundamental.valuation_ratios.pe_ratio
        )
        return [fundamental.symbol for fundamental in sorted_by_pe_ratio[:10]]
    
    universe = qb.add_universe(filter_function)
  5. Call the universe_history method with the universe, a start date, and an end date.
  6. Select Language:
    universe_history = qb.universe_history(universe, datetime(2023, 11, 6), datetime(2023, 11, 13))

    The end date arguments is optional. If you omit it, the method returns Fundamental data between the start date and the current day.

    The universe_history method returns a Series where the multi-index is the universe Symbol and the time when universe selection would occur in a backtest. Each row in the data column contains a list of Fundamental objects. The following image shows the first 5 rows of an example Series:

    Historical fundamental universe dataframe

    To get a flat DataFrame instead of a Series, set the flatten argument to True.

  7. Iterate through the Series to access the universe data.
  8. Select Language:
    universe_history = universe_history.droplevel('symbol', axis=0)
    for date, fundamentals in universe_history.items():
        for fundamental in fundamentals:
            symbol = fundamental.symbol
            price = fundamental.price
            if fundamental.has_fundamental_data:
                pe_ratio = fundamental.valuation_ratios.pe_ratio

Available Universes

To get universe data for other types of universes, you usually just need to replace Fundamental in the preceding code snippets with the universe data type. The following table shows the datasets that support universe selection and their respective data type. For more information, about universe selection with these datasets and the data points you can use in the filter function, see the dataset's documentation.

Dataset NameUniverse Type(s)Documentation
US Fundamental DataFundamentalLearn more
US ETF ConstituentsETFConstituentUniverseLearn more
Crypto Price DataCryptoUniverseLearn more
US Congress TradingQuiverQuantCongresssUniverseLearn more
WallStreetBetsQuiverWallStreetBetsUniverseLearn more
Corporate Buybacks
  • SmartInsiderIntentionUniverse
  • SmartInsiderTransactionUniverse
Learn more
Brain Sentiment IndicatorBrainSentimentIndicatorUniverseLearn more
Brain ML Stock RankingBrainStockRankingUniverseLearn more
Brain Language Metrics on Company Filings
  • BrainCompanyFilingLanguageMetricsUniverseAll
  • BrainCompanyFilingLanguageMetricsUniverse10K
Learn more
CNBC TradingQuiverCNBCsUniverseLearn more
US Government ContractsQuiverGovernmentContractUniverseLearn more
Corporate LobbyingQuiverLobbyingUniverseLearn more
Insider TradingQuiverInsiderTradingUniverseLearn more

To get universe data for Futures and Options, use the future_history and option_history methods, respectively.

Examples

The following examples demonstrate some common practices for universe research.

Example 1: Top-Minus-Bottom PE Ratio

The below example studies the top-minus-bottom PE Ratio universe, in which the top 10 PE Ratio stocks are brought, and the bottom 10 are sold in equal weighting daily. We carry out a mini-backtest to analyze its performance.

Select Language:
# Instantiate the QuantBook instance for researching.
qb = QuantBook()

# Set start and end dates of the research to avoid look-ahead bias.
start = datetime(2021, 1, 1)
end = datetime(2021, 4, 1)
qb.set_start_date(end)

# Request data for research purposes.
# We are interested in the most liquid primary stocks.
universe = qb.add_universe(
    lambda datum: [x.symbol for x in sorted(
        [f for f in datum], 
        key=lambda f: f.dollar_volume
    )[-100:]]
)

# Historical data call for the data to be compared and tested.
universe_history = qb.universe_history(universe, start, end).droplevel([0])
# All symbols' daily prices are for return comparison.
history = qb.history(
    [x.symbol for x in set(
        e for datum in universe_history.values.flatten() for e in datum
    )], 
    start,
    end,
    Resolution.DAILY
).close.unstack(0)

# Change the index format for matching.
universe_history.index = universe_history.index.date
history.index = history.index.date
# Process the historical data to generate a signal and return it for research.
bottom_liquid_universe_history = universe_history.apply(
    lambda datum: [x.symbol for x in sorted(
        datum,
        key=lambda d: d.dollar_volume
    )[:10]]
)
top_liquid_universe_history = universe_history.apply(
    lambda datum: [x.symbol for x in sorted(
        datum,
        key=lambda d: d.dollar_volume,
        reverse=True
    )[:10]]
)
ret = history.pct_change().dropna()

# Keep the entries that have common indices for a fair comparison.
common_index = list(set(ret.index).intersection(universe_history.index))
ret = ret.loc[common_index].apply(
    lambda x: x[top_liquid_universe_history.loc[x.name]].sum()*0.1\
        - x[bottom_liquid_universe_history.loc[x.name]].sum()*0.1,
    axis=1
)

# Plot the data for visualization. It is easier to study the pattern.
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot()

# Use line plot to present time series.
ax.plot(ret.sort_index())

# Set axis and titles to explain the plot.
ax.set_xlabel("Time")
ax.set_ylabel("Return")
ax.set_title("Top Minus Bottom Liquidity Return")

plt.show()

You can also see our Videos. You can also get in touch with us via Discord.

Did you find this page helpful?

Contribute to the documentation: