Fundamental Stock Selection by Dollar Volume and P/E Ratio
Summary
This example demonstrates a daily equity universe selection process using fundamental data. It filters for securities with available fundamental data and a price above $1, sorts the remaining names by daily dollar volume, then ranks them by P/E ratio and selects the top two. The algorithm processes changes to the selected universe by liquidating invested securities that are removed and assigning holdings to newly added securities.
The document is an implementation example rather than a tested investment thesis. It provides a short historical date range and illustrates how to connect a fundamental universe to portfolio actions, but reports no returns, benchmarks, or risk analysis. The ranking favors higher P/E ratios among liquid, eligible stocks, a choice that may not suit every strategy and whose implications are not discussed. The sample also contains a separate selection function with a configurable count, while the active selector returns two names.
Key ideas
- The universe filters for stocks with fundamental data and prices above $1.
- Eligible securities are first sorted by daily dollar volume and then by P/E ratio.
- The active selection function returns the two highest-ranked names.
- Removed invested securities are liquidated, and newly added securities receive holdings.
- The example gives no evidence that this selection rule produces superior returns.
Tags
Full text
# FundamentalUniverseSelectionAlgorithm
# FundamentalUniverseSelectionAlgorithm
Demonstration of how to define a universe using the fundamental data
## 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 how to define a universe using the fundamental data
### </summary>
### <meta name="tag" content="using data" />
### <meta name="tag" content="universes" />
### <meta name="tag" content="coarse universes" />
### <meta name="tag" content="regression test" />
class FundamentalUniverseSelectionAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2014, 3, 25)
self.set_end_date(2014, 4, 7)
self.universe_settings.resolution = Resolution.DAILY
self.add_equity("SPY")
self.add_equity("AAPL")
self.set_universe_selection(FundamentalUniverseSelectionModel(self.select))
self.changes = None
self.number_of_symbols_fundamental = 10
# return a list of three fixed symbol objects
def selection_function(self, fundamental):
# sort descending by daily dollar volume
sorted_by_dollar_volume = sorted([x for x in fundamental if x.price > 1],
key=lambda x: x.dollar_volume, reverse=True)
# sort descending by P/E ratio
sorted_by_pe_ratio = sorted(sorted_by_dollar_volume, 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_fundamental] ]
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)
self.debug("Liquidated Stock: " + str(security.symbol.value))
# we want 50% allocation in each security in our universe
for security in self.changes.added_securities:
self.set_holdings(security.symbol, 0.02)
self.changes = None
# this event fires whenever we have changes to our universe
def on_securities_changed(self, changes):
self.changes = changes
def select(self, fundamental):
# sort descending by daily dollar volume
sorted_by_dollar_volume = sorted([x for x in fundamental if x.has_fundamental_data and x.price > 1],
key=lambda x: x.dollar_volume, reverse=True)
# sort descending by P/E ratio
sorted_by_pe_ratio = sorted(sorted_by_dollar_volume, 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[:2] ]
```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.