Build Your Own Stock Scanner in Quantconnect
Filtering stocks to create a daily watchlist

There are thousands of stocks in the US equity market, and it may be too difficult to keep track of all the stocks. As a trader, it is good to filter out stocks that meet certain conditions than to randomly select stocks each day to day
But how do you know which stocks to select?
We can use a filtering mechanism to see which stocks are good candidates for a potential breakout. Note that we still need to have an entry setup in place to take a position in the stock. Here, the main objective is to create a watchlist that may provide a good setup, where we can further carry out our analysis
Before getting into the code, I am gonna explain some concepts in QuantConnect that help us carry out the filtering
STATIC AND DYNAMIC UNIVERSE
A “static universe” refers to a fixed set of assets that are defined at the beginning of the algorithm and remain unchanged throughout its execution. These assets can be manually specified or selected based on predefined criteria. Once the static universe is defined, the algorithm operates only on those assets and does not dynamically add or remove assets during runtime.
On the other hand, a “dynamic universe” refers to a set of assets that can change over time. In this case, the algorithm continuously evaluates and updates the universe based on certain criteria or conditions. New assets can be added, and existing assets can be removed from the universe as per the defined rules.
UNIVERSE FILTERING
QuantConnect provides a mechanism for universe selection and filtering to manage dynamic universes effectively. This mechanism allows you to define rules or filters to select assets based on various criteria, such as price, volume, fundamental data, technical indicators, etc.
Here’s a general overview of how universe filtering works in QuantConnect:
- Universe Creation: At the beginning of the algorithm, you define a universe based on certain criteria using the
AddUniversemethod. This method takes a filter or a function that determines the initial universe of assets. - Coarse/Fine Filtering: QuantConnect uses a two-step filtering process called coarse and fine filtering. The coarse filter is applied first and operates on a broader set of assets. Its purpose is to quickly eliminate assets that don’t meet basic criteria, such as minimum volume or price. The coarse filter returns a smaller subset of assets, which then undergo fine filtering.
- Fine Filtering: After the coarse filter, the remaining assets go through fine filtering, where more detailed criteria can be applied, such as fundamental data analysis or complex technical indicators. Fine filtering is typically performed on a daily basis to update the universe and ensure it reflects the desired criteria.
- Universe Selection: Once the filtering process is complete, the resulting universe of assets is selected for trading. The algorithm will only consider assets that are part of the selected universe for making trading decisions.
- Monitoring and Updates: During the algorithm’s execution, the universe is continuously monitored and updated based on the defined criteria. New assets meeting the criteria can be added, and existing assets that no longer meet the criteria can be removed from the universe.
Ok let’s code our scanner in QuantConnect and then I am gonna explain how the filtering is carried out in the code
# region imports
from AlgorithmImports import *
from collections import deque
# endregion
class Watchlist(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2023, 5, 17)
self.SetEndDate(2023, 5, 19)
self.SetCash(100000)
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseFilter)
def CoarseFilter(self, coarse):
universe = [asset for asset in coarse if asset.Volume > 1000000 and asset.Price > 1 and asset.HasFundamentalData]
SortedByDollarVolume = sorted(universe, key=lambda x: x.DollarVolume, reverse=True)
topSortedByDollarVolume = SortedByDollarVolume[:500]
symbolObjects = [asset.Symbol for asset in topSortedByDollarVolume]
return symbolObjects
def OnData(self,data):
for security in self.ActiveSecurities.Values:
symbol = security.Symbol
if symbol not in data:
continue
#current price
current_price = self.Securities[symbol].Close
#current_volume
volume = self.Securities[symbol].Volume
history = self.History(symbol, 20, Resolution.Daily)
# Get the previous price
previous_price = history.loc[symbol].iloc[-1].close
#Average volume
average_volume = history.groupby('symbol').mean()['volume'].iloc[0]
# Calculate average daily range
average_range = history.groupby('symbol').mean()['high'].subtract(history.groupby('symbol').mean()['low']).iloc[0]
if current_price / previous_price > 2 * average_range and volume > 2 * average_volume:
self.Debug(f"{symbol.Value}:Earning list winners on {self.Time.date()}.")
CODE COMMENTARY
Sure! Let’s go through the code step by step:
- The code starts with the import statement, importing the
dequeclass from thecollectionsmodule. - Next, a class named
Watchlistis defined, which inherits from theQCAlgorithmclass. - Inside the class, there is an
Initializemethod. This method is called when the algorithm is initialized. Here's what it does:
- Sets the start and end dates for the algorithm using
SetStartDateandSetEndDate. - Sets the initial cash amount for the algorithm using
SetCash. - Sets the resolution for the universe using
UniverseSettings.Resolution. - Adds a universe by calling the
AddUniversemethod and passing it theCoarseFiltermethod.
4. The CoarseFilter method is defined next. In the code, we have not implemented the FineFilter.This method takes a coarse parameter, which represents a collection of assets at a coarse resolution. Here's what it does:
- Filters the
coarseassets based on specific criteria: volume greater than 1,000,000, price greater than 1, and having fundamental data. - Sorts the filtered assets by their dollar volume in descending order.
- Selects the top 500 assets with the highest dollar volume.
- Extracts the symbols from the selected assets and returns them as a list.
5 The OnData method is defined next. This method is called for each new piece of data received by the algorithm. Here's what it does:
- Iterates over the active securities in the algorithm’s portfolio.
- Checks if the current security’s symbol is present in the received data. If not, it skips the iteration.
- Retrieves the closing price and volume for the security from the
Securitiesproperty. - Retrieves the historical data for the symbol, specifically the last 20 days, using the
Historymethod. - Calculates the average volume and average daily range from the historical data.
- Compares the current price to the previous price based on a condition: if the current price is more than twice the average range and the volume is more than twice the average volume.
- If the condition is met, it prints a debug message indicating an “Earning list winner” for the symbol and the current date.
The winner's list provided by the code is listed below. You can further add entry rules, exit rules, and position sizing for each symbol on the list to initiate a trade

A Message from QuantFactory:

Thank you for being part of our community! Before you go:
- Master Quantitative Research with Python👉 here.
- Join our Premium Discord Server👉 here.
- Subscribe to our Exclusive Stocks Newsletter 👉 here.
- If you liked the story feel free to clap 👏 and follow the author
- For more stories from the Algorithmic Trading and Quantitative Analysis world you should follow the Quant Factory Publication
*Note that this article does not provide personal investment advice and I am not a qualified licensed investment advisor. All information found here is for entertainment or educational purposes only and should not be construed as personal investment advice.






