Skip to content
All library documents

Coarse and Fine Fundamental Filters for Stock Universe Selection

Article Strategy library · Author: QuantConnect

Summary

This algorithm example demonstrates a two-stage method for narrowing a stock universe. Its coarse filter ranks available stocks by daily dollar volume and passes the five highest-volume names onward. A fine filter then ranks those candidates by price-to-earnings ratio and retains two. Universe data is set to daily resolution, and the example starts with $50,000 over a 2014 calendar-year backtest period.

When the selected universe changes, the algorithm liquidates invested securities that were removed and assigns 20% portfolio weight to each newly added security. The example teaches universe construction and handling membership changes, rather than offering evidence for a profitable factor strategy. In particular, the fine ranking selects the highest P/E ratios, a choice whose investment rationale and treatment of missing or extreme values are not explained. No returns, benchmark comparison, or risk statistics are reported, so the selection rules should be evaluated independently before practical use.

Key ideas

  • Coarse selection first ranks stocks by daily dollar volume.
  • Fine selection applies a P/E ranking to the coarse-filtered candidates.
  • The example selects five coarse candidates and retains two after fine filtering.
  • Removed holdings are liquidated, while new constituents receive equal 20% target weights.
  • The sample shows universe mechanics but provides no performance or risk evaluation.

Tags

Full text
# CoarseFineFundamentalComboAlgorithm


# CoarseFineFundamentalComboAlgorithm









Demonstration of using coarse and fine universe selection together to filter down a smaller universe of stocks.

## Source (Apache-2.0)

```python
# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
# Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from AlgorithmImports import *

### <summary>
### Demonstration of using coarse and fine universe selection together to filter down a smaller universe of stocks.
### </summary>
### <meta name="tag" content="using data" />
### <meta name="tag" content="universes" />
### <meta name="tag" content="coarse universes" />
### <meta name="tag" content="fine universes" />
class CoarseFineFundamentalComboAlgorithm(QCAlgorithm):

    def initialize(self):
        '''Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

        self.set_start_date(2014,1,1)  #Set Start Date
        self.set_end_date(2015,1,1)    #Set End Date
        self.set_cash(50000)            #Set Strategy Cash

        # what resolution should the data *added* to the universe be?
        self.universe_settings.resolution = Resolution.DAILY

        # this add universe method accepts two parameters:
        # - coarse selection function: accepts an IEnumerable<CoarseFundamental> and returns an IEnumerable<Symbol>
        # - fine selection function: accepts an IEnumerable<FineFundamental> and returns an IEnumerable<Symbol>
        self.add_universe(self.coarse_selection_function, self.fine_selection_function)

        self.__number_of_symbols = 5
        self.__number_of_symbols_fine = 2
        self._changes = None


    # sort the data by daily dollar volume and take the top 'NumberOfSymbols'
    def coarse_selection_function(self, coarse):
        # sort descending by daily dollar volume
        sorted_by_dollar_volume = sorted(coarse, key=lambda x: x.dollar_volume, reverse=True)

        # return the symbol objects of the top entries from our sorted collection
        return [ x.symbol for x in sorted_by_dollar_volume[:self.__number_of_symbols] ]

    # sort the data by P/E ratio and take the top 'NumberOfSymbolsFine'
    def fine_selection_function(self, fine):
        # sort descending by P/E ratio
        sorted_by_pe_ratio = sorted(fine, key=lambda x: x.valuation_ratios.pe_ratio, reverse=True)

        # take the top entries from our sorted collection
        return [ x.symbol for x in sorted_by_pe_ratio[:self.__number_of_symbols_fine] ]

    def on_data(self, data):
        # if we have no changes, do nothing
        if self._changes is None: return

        # liquidate removed securities
        for security in self._changes.removed_securities:
            if security.invested:
                self.liquidate(security.symbol)

        # we want 20% allocation in each security in our universe
        for security in self._changes.added_securities:
            self.set_holdings(security.symbol, 0.2)

        self._changes = None


    # this event fires whenever we have changes to our universe
    def on_securities_changed(self, changes):
        self._changes = changes

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.