Build a Stock Screener with Python
Filter and rank live stock market data using Python query composition.
Introduction
30 Second Summary
Every day you search for things. Whether you are filtering products on Amazon by price and rating, or searching for apartments by location and rent, you are composing search queries with multiple criteria.
In this project, you will build a stock screener in Python that filters the entire US stock market by financial criteria like P/E ratio, earnings growth, and sector.
What You'll Build
A Python command-line tool that screens live stock market data using multiple strategies, each built from composable search query filters.
By the end of this project, you'll have:
- A working stock screener that queries live market data and returns filtered results.
- Hands-on experience composing search queries with boolean logic (AND/OR operators).
- A mental model for how all search systems work: filter, combine, sort, display.
- Secret Mission: Add an interactive custom query builder that lets you define your own screening criteria on the fly.
Are there any prerequisites?
You need basic Python familiarity (variables, functions, lists, dictionaries). No prior API experience is required.
Before We Start
Stock screeners sift through thousands of stocks and return only the ones matching specific criteria. Every search system works this way: take a large dataset, apply filters, rank the results, and display them.
Before building your screener, you need Python and the yfinance library installed. This step gets your environment ready so you can focus on writing search queries in the next steps.
In this step, get ready to:
- Verify that Python 3.9 or higher is installed on your machine.
- Create a project folder and install the yfinance library.
- Confirm your setup works with a test script.
Verify Python is installed
This project requires Python 3.9 or higher. Let's check what version you have.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
- Check your Python version by running this command:
macOS
python3 --version
Windows
python --version
✔️ I see version 3.9 or higher
Your Python version is ready. Move on to the next substep.
ⓧ I see an older version
You have Python installed, but it is below version 3.9. You need to install a newer version.
- Go to python.org/downloads and download the latest Python installer.
- Run the installer. On Windows, check the Add Python to PATH checkbox at the bottom of the first screen before clicking Install Now.
- On macOS, the new version installs alongside the old one. After installing, use python3 to invoke it (the system python may still point to the old version).
- Close and reopen your terminal, then run the version check again to confirm.
ⓧ Command not found
Python is not installed yet. Let's fix that.
- Go to python.org/downloads and download the latest Python installer for your operating system.
- Run the installer. On Windows, check the Add Python to PATH checkbox at the bottom of the first screen before clicking Install Now.
- On macOS, the installer adds python3 to your system. Use python3 --version to verify after install.
- Close and reopen your terminal, then run the version check command again.
Create your project folder and install yfinance
Now that Python is ready, you need a dedicated folder for your project and the yfinance library, which gives you free access to live stock market data.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir stock-screener
cd stock-screener
- Confirm the folder was created by running:
ls
The folder should be empty for now. That is expected.
- Install the yfinance library by running this command:
macOS
pip3 install yfinance==1.4.1
Windows
pip install yfinance==1.4.1
You should see output scrolling as pip downloads and installs yfinance and its dependencies. The final line should say Successfully installed followed by a list of package names.
What is yfinance?
yfinance is a free Python library that pulls live stock market data from Yahoo Finance. It requires no API key and has no rate limits for the screening use case in this project.
Seeing a permission error or command not found?
- If you see a permissions error on macOS, try adding --user to the end of the command: pip3 install yfinance==1.4.1 --user.
- If pip is not found on Windows, close and reopen PowerShell. The Python installer needs a fresh terminal to register the PATH change.
Help me fix my pip installation error.
Verify the installation works
A quick test script confirms that yfinance imported correctly and is the version you expect.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code (or the name of your code editor) and press Enter to open it.
- Click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the stock-screener folder, and click Open.
- Create a new file called test_install.py by clicking the new file icon in the Explorer sidebar.
- Paste the following code into test_install.py:
import yfinance as yf
print(yf.__version__)
print("yfinance installed successfully!")
What does this code do?
- import yfinance as yf loads the yfinance library and gives it the short name yf for convenience.
- yf.__version__ prints the installed version number so you can confirm it matches 1.4.1.
- The second print confirms the import succeeded without errors.
- Save test_install.py.
- Run the script in your terminal by running this command:
macOS
python3 test_install.py
Windows
python test_install.py
You should see 1.4.1 on the first line and yfinance installed successfully! on the second line.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file.
ⓧ I'd like to double check the full code
import yfinance as yf
print(yf.__version__)
print("yfinance installed successfully!")
Seeing ModuleNotFoundError?
- This means the Python running your script is different from the one where you installed yfinance. On macOS, make sure you run with python3 (not python).
- On Windows, close and reopen PowerShell, then try the command again.
- Make sure your terminal is inside the stock-screener folder. Run pwd (macOS) or cd (Windows) to check your current location.
Help me fix this ModuleNotFoundError for yfinance.
Your environment is ready. Next up, you will run your first stock screen and see live market data flowing into your terminal.
Run Your First Stock Screen
Your Python environment is ready and yfinance is installed. Now it's time to run your first stock screen and see live market data.
yfinance includes predefined screens that work like saved searches. You'll run two of them to understand what data comes back, then explore the raw response structure to see what fields are available for building custom queries.
In this step, get ready to:
- Run a predefined stock screen and see live market data in your terminal.
- Try a second predefined screen to compare results.
- Explore the full response data structure to understand available fields.
Create your screener and run a predefined screen
yfinance's yf.screen() function runs stock screens against live market data. Predefined screens are named queries that Yahoo Finance has already built. You just pass the name and get results back.
- Create a new file called screener.py inside your stock-screener folder and add the following code:
import yfinance as yf
response = yf.screen("day_gainers")
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
for stock in quotes[:5]:
print(f" {stock['symbol']} - {stock.get('shortName', 'N/A')}")
What does this code do?
- yf.screen("day_gainers") runs a predefined screen that finds all stocks up more than 3% today with high volume. Yahoo Finance maintains this query on their end.
- response['quotes'] extracts the list of matching stocks from the response. Each item in this list is a dictionary containing data about one stock.
- The loop prints the first 5 results, showing the stock symbol and company name. stock.get('shortName', 'N/A') safely handles any stock that's missing a name field.
- Save screener.py.
- Run the script by entering this command in your terminal:
python screener.py
- You should see output like Found 25 stocks followed by 5 stock symbols and company names. The exact stocks will vary depending on the current market day.
Not seeing any output?
- Make sure you are running the command from inside the stock-screener folder. If not, run cd stock-screener first.
- If you see a ModuleNotFoundError for yfinance, your installation may not have completed. Run pip install yfinance==1.4.1 again.
- If you see 0 stocks, the market may be closed. Try running it during US trading hours, or continue to the next substep since the structure still works.
Help me debug my screener.py script
Try a second screen and explore the response data
Each predefined screen is just a named query with specific filters and a sort order. By running a different one, you'll see how the same function returns completely different results based on the query behind the name.
- In screener.py, find this line at the top of your file:
import yfinance as yf
- Add import json on the line directly below it, so the top of your file looks like this:
import yfinance as yf
import json
- Now add the following code at the very bottom of your file, below the existing for loop:
response = yf.screen("most_actives")
quotes = response['quotes']
print(json.dumps(quotes[0], indent=2, default=str))
What does this code do?
- yf.screen("most_actives") runs a different predefined screen that returns the most heavily traded stocks by volume. Same function, different query.
- json.dumps(quotes[0], indent=2, default=str) prints the complete data for one stock in a readable, indented JSON format. This lets you see every field available in the response.
You'll see fields like symbol, shortName, regularMarketPrice, regularMarketChangePercent, marketCap, and many more. These are the fields you can filter and sort by when building custom queries.
- Save screener.py and run it again:
python screener.py
- You should see the day_gainers output from before, followed by a large block of JSON showing all the data fields for one of the most actively traded stocks.
Seeing a NameError for json?
- Make sure import json appears at the top of your file, on the line directly below import yfinance as yf.
- If you see an IndexError on quotes[0], the screen returned zero results. This can happen outside market hours. Try running the script again during US trading hours.
Help me fix my screener.py errors
✔️ Awesome, I've got everything!
Great. Double check you've saved your file, then move on to the next step.
ⓧ I'd like to double check the full code
Here's the complete screener.py file at this point:
import yfinance as yf
import json
response = yf.screen("day_gainers")
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
for stock in quotes[:5]:
print(f" {stock['symbol']} - {stock.get('shortName', 'N/A')}")
response = yf.screen("most_actives")
quotes = response['quotes']
print(json.dumps(quotes[0], indent=2, default=str))
You've seen what predefined screens return and explored the raw data structure. Next, you'll compose your own custom queries to search for exactly the stocks you want.
Build Custom Search Queries
In the previous step, you ran predefined screens like day_gainers and most_actives. These are named shortcuts that Yahoo already defined for you.
Now you will build your own search queries from scratch using EquityQuery. This is where the real power of search composition begins. You pick the operators, choose the fields, and combine them to create any filter you can imagine.
In this step, get ready to:
- Build your first custom filter using a single EquityQuery operator.
- Learn the full vocabulary of filter operators (greater than, less than, between, equals, and more).
- Combine multiple filters with AND logic to create a multi-criteria search.
Import EquityQuery and build your first custom filter
Predefined screens are convenient, but they limit you to queries someone else designed. EquityQuery is the building block for custom searches. Each EquityQuery takes an operator (like "greater than") and a field/value pair to filter on.
- Replace the entire contents of screener.py with this code:
import yfinance as yf
from yfinance import EquityQuery
import json
# Single filter example
q = EquityQuery('gt', ['percentchange', 3])
response = yf.screen(q, sortField='percentchange', sortAsc=False, size=10)
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
What does this code do?
from yfinance import EquityQuery brings in the query builder class that lets you construct custom filters.
EquityQuery('gt', ['percentchange', 3]) creates a single filter: find stocks where percent change is greater than 3%. The first argument is the operator ('gt' means "greater than"), and the second is a list containing the field name and threshold value.
yf.screen(q, sortField='percentchange', sortAsc=False, size=10) executes your custom query. It sorts results by percent change (highest first) and returns up to 10 matches.
- Save screener.py and run it:
python screener.py
You should see output like Found 10 stocks (the exact number depends on current market conditions).
Seeing zero results or an error?
If you see Found 0 stocks, the market may be closed or no stocks are up more than 3% today. Try changing 3 to 1 temporarily to broaden the filter.
If you see an import error, make sure your first line says from yfinance import EquityQuery exactly.
help me debug my EquityQuery import or screen results
Learn the operator vocabulary
EquityQuery supports six comparison operators. Each one filters stocks differently, just like comparison operators in a database query.
- Here are the operators you can use. Try replacing the q = ... line in screener.py with each one and running the script to see different results:
# Equals (exact match)
EquityQuery('eq', ['sector', 'Technology'])
# Greater than
EquityQuery('gt', ['percentchange', 3])
# Less than
EquityQuery('lt', ['peratio.lasttwelvemonths', 20])
# Greater than or equal
EquityQuery('gte', ['intradaymarketcap', 2000000000])
# Between two values
EquityQuery('btwn', ['peratio.lasttwelvemonths', 0, 20])
# Value is in a set
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ'])
What do these operators mean?
- 'eq' finds exact matches. Use it for categories like sector or region.
- 'gt' and 'lt' are "greater than" and "less than" for numeric thresholds.
- 'gte' means "greater than or equal to."
- 'btwn' filters for values within a range (inclusive of both bounds).
- 'is-in' checks if a value belongs to a set of options. Use it for exchange codes or other multi-value fields.
These operators exist everywhere
These six operators are the same patterns you will find in SQL WHERE clauses, search engine filters, and REST API query parameters. Learning them here means you can compose queries in almost any system.
- After experimenting, set your q = ... line back to the original single filter:
q = EquityQuery('gt', ['percentchange', 3])
Combine filters with AND logic
A single filter is useful, but real searches need multiple criteria at once. The 'and' operator combines filters so that every condition must be true for a stock to pass through.
- Add this multi-criteria AND query below the existing print statement in screener.py:
# Multi-criteria AND query
q = EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us']),
EquityQuery('gte', ['intradaymarketcap', 2000000000])
])
response = yf.screen(q, sortField='percentchange', sortAsc=False, size=15)
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
for stock in quotes[:5]:
print(f" {stock['symbol']} - {stock.get('shortName', 'N/A')}")
What does this AND query do?
EquityQuery('and', [...]) wraps a list of conditions into a single combined query. A stock must satisfy ALL three conditions to appear in the results.
- percentchange > 3 filters for stocks up more than 3% today.
- region == 'us' restricts to US-listed stocks.
- intradaymarketcap >= 2000000000 ensures only large companies (over $2 billion market cap) appear.
This AND composition is the core pattern behind every search system. Database WHERE clauses, search engine filters, and API queries all combine conditions the same way.
- Save screener.py and run it:
python screener.py
You should see two sets of output. First, the single filter results. Then the AND query results showing fewer stocks (since more conditions must be met).
Second query returns zero results?
If the AND query prints Found 0 stocks, it likely means no stocks currently satisfy all three conditions simultaneously. This is normal outside of US market hours (9:30 AM to 4:00 PM Eastern).
Try lowering the percentchange threshold from 3 to 1, or remove the percentchange filter entirely to verify the other conditions work.
help me troubleshoot why my AND query returns no results
✔️ Awesome, I've got everything!
Great. Double check you have saved screener.py with both the single filter and the AND query.
ⓧ I'd like to double check the full code
import yfinance as yf
from yfinance import EquityQuery
import json
# Single filter example
q = EquityQuery('gt', ['percentchange', 3])
response = yf.screen(q, sortField='percentchange', sortAsc=False, size=10)
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
# Multi-criteria AND query
q = EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us']),
EquityQuery('gte', ['intradaymarketcap', 2000000000])
])
response = yf.screen(q, sortField='percentchange', sortAsc=False, size=15)
quotes = response['quotes']
print(f"Found {len(quotes)} stocks")
for stock in quotes[:5]:
print(f" {stock['symbol']} - {stock.get('shortName', 'N/A')}")
You can now compose custom queries with any combination of operators. Next up, you will package these queries into reusable screening strategies.
Build Multiple Screening Strategies
You've proven that AND queries can filter the stock market by multiple criteria at once. However, the code in your file is a one-off experiment with no structure.
Professional tools organize their search logic into named strategies that users can pick from a menu. In this step, you'll refactor your exploratory code into reusable functions and a registry that connects user choices to queries.
In this step, get ready to:
- Create three screening strategy functions using different filter combinations.
- Build a strategy registry mapping user choices to query functions.
- Add a helper function that executes any query and returns results.
Create three screening strategy functions
Each strategy encapsulates a different investing philosophy as a reusable query. Instead of scattered one-off experiments, you will have named functions that anyone can call.
- Select all the code in screener.py and delete it.
- Replace the entire file with the following imports and three strategy functions:
import yfinance as yf
from yfinance import EquityQuery
def value_stocks():
"""Low P/E, positive earnings growth, large cap."""
return EquityQuery('and', [
EquityQuery('btwn', ['peratio.lasttwelvemonths', 0, 20]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 10]),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ']),
EquityQuery('gte', ['intradaymarketcap', 2000000000])
])
def growth_tech():
"""High revenue and earnings growth in tech."""
return EquityQuery('and', [
EquityQuery('gte', ['quarterlyrevenuegrowth.quarterly', 25]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 25]),
EquityQuery('eq', ['sector', 'Technology']),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ'])
])
def day_movers():
"""Big price moves today, liquid stocks only."""
return EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us']),
EquityQuery('gte', ['intradaymarketcap', 2000000000]),
EquityQuery('gte', ['intradayprice', 5]),
EquityQuery('gt', ['dayvolume', 15000])
])
What do these functions do?
Each function returns an EquityQuery built with AND logic. Every condition inside the list must be true for a stock to pass the filter.
- value_stocks() finds large-cap stocks with low P/E ratios (0-20) and positive earnings growth (10%+). These are stocks the market may be undervaluing.
- growth_tech() targets tech companies growing revenue (25%+) and earnings (25%+) aggressively.
- day_movers() catches stocks making big moves today (3%+ change) with enough trading volume to be liquid.
You won't see output yet. The next chunks connect these functions to a registry and execution helper.
Create a strategy registry
A registry maps user-friendly menu choices to the functions that generate queries. This pattern decouples the menu from the query logic, so adding a new strategy later is just one line.
- Add the following dictionary below your three functions in screener.py:
strategies = {
'1': ('Value Stocks', value_stocks, 'eodvolume'),
'2': ('Growth Tech', growth_tech, 'eodvolume'),
'3': ('Day Movers', day_movers, 'percentchange'),
}
What does this dictionary hold?
Each key ('1', '2', '3') maps to a tuple with three elements: a display name, the query-building function, and the field to sort results by.
When the user picks option '2', the program unpacks ('Growth Tech', growth_tech, 'eodvolume') and knows exactly which function to call and how to sort the output.
This dictionary becomes useful in the next chunk when you add the execution helper.
Add a run_screen() helper function
You need a single function that accepts any query object, executes it against the market, and returns the matching stocks. This keeps execution logic in one place.
- Add the following function below the strategies dictionary in screener.py:
def run_screen(query, sort_field='percentchange', sort_asc=False):
"""Execute a screen query and return the list of matching quotes."""
response = yf.screen(query, sortField=sort_field, sortAsc=sort_asc, size=25)
return response['quotes']
What does run_screen() do?
It wraps yf.screen() with sensible defaults. The sort_field parameter controls how results are ranked. The size=25 parameter limits results to 25 stocks.
Because run_screen() accepts any EquityQuery object, it works with all three strategies and any future ones you add.
- Save screener.py.
- Verify your strategies work by running this command in your terminal:
python -c "from screener import run_screen, value_stocks; quotes = run_screen(value_stocks()); print(f'Found {len(quotes)} value stocks')"
You should see a line like Found 25 value stocks (the exact number depends on current market conditions).
Seeing an import error?
- Make sure you are running the command from the stock-screener folder where screener.py lives.
- Check that your file starts with import yfinance as yf and from yfinance import EquityQuery on separate lines. A missing import causes a NameError.
- If you see a network error or empty results, your internet connection may have dropped or the market data endpoint is temporarily unavailable. Wait a moment and try again.
Help me debug my screener import or execution error.
✔️ Awesome, I've got everything!
Great. Double check you've saved screener.py and the verify command returned results.
ⓧ I'd like to double check the full code
import yfinance as yf
from yfinance import EquityQuery
def value_stocks():
"""Low P/E, positive earnings growth, large cap."""
return EquityQuery('and', [
EquityQuery('btwn', ['peratio.lasttwelvemonths', 0, 20]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 10]),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ']),
EquityQuery('gte', ['intradaymarketcap', 2000000000])
])
def growth_tech():
"""High revenue and earnings growth in tech."""
return EquityQuery('and', [
EquityQuery('gte', ['quarterlyrevenuegrowth.quarterly', 25]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 25]),
EquityQuery('eq', ['sector', 'Technology']),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ'])
])
def day_movers():
"""Big price moves today, liquid stocks only."""
return EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us']),
EquityQuery('gte', ['intradaymarketcap', 2000000000]),
EquityQuery('gte', ['intradayprice', 5]),
EquityQuery('gt', ['dayvolume', 15000])
])
strategies = {
'1': ('Value Stocks', value_stocks, 'eodvolume'),
'2': ('Growth Tech', growth_tech, 'eodvolume'),
'3': ('Day Movers', day_movers, 'percentchange'),
}
def run_screen(query, sort_field='percentchange', sort_asc=False):
"""Execute a screen query and return the list of matching quotes."""
response = yf.screen(query, sortField=sort_field, sortAsc=sort_asc, size=25)
return response['quotes']
Your screener now has three reusable strategies and a clean execution helper. Next up, you'll format the raw results into a readable table and build the interactive CLI menu that ties everything together.
Format Results and Build the CLI
Your screening strategies are ready to go. Each one encapsulates a different search pattern as a reusable function.
Right now, the raw data from yfinance comes back as a list of dictionaries. In this step, you will format that data into a readable table and wrap everything in a menu-driven CLI that lets you pick a strategy and see results instantly.
In this step, get ready to:
- Build a display function that formats stock data into an aligned table.
- Add a main menu that lets you choose a screening strategy.
- Run the complete stock screener with live market data.
Build the display function
The raw quote data from yfinance contains numbers like 2890000000000 for market cap. You need a function that converts these into a human-readable table with aligned columns and friendly suffixes like $2.9T.
- In screener.py, add the following display_results() function below your run_screen() function:
def display_results(quotes):
"""Display screening results in a formatted table."""
if not quotes:
print("\nNo stocks matched your criteria.")
return
# Print column headers with fixed widths
print(f"\n{'Symbol':<10}{'Name':<30}{'Price':>10}{'Change %':>10}{'Market Cap':>15}")
print("-" * 75)
# Show up to 15 results
for stock in quotes[:15]:
symbol = stock.get('symbol', 'N/A')
name = stock.get('shortName', 'N/A')[:28]
price = stock.get('regularMarketPrice', 0)
change = stock.get('regularMarketChangePercent', 0)
market_cap = stock.get('marketCap', 0)
# Format market cap with T/B/M suffixes
if market_cap >= 1_000_000_000_000:
cap_str = f"${market_cap / 1_000_000_000_000:.1f}T"
elif market_cap >= 1_000_000_000:
cap_str = f"${market_cap / 1_000_000_000:.1f}B"
elif market_cap >= 1_000_000:
cap_str = f"${market_cap / 1_000_000:.1f}M"
else:
cap_str = f"${market_cap:,.0f}"
print(f"{symbol:<10}{name:<30}{price:>10.2f}{change:>9.2f}%{cap_str:>15}")
What does this code do?
This function takes the list of quote dictionaries returned by run_screen() and prints them as a formatted table.
- The f-string format specifiers (<10, >10) align text left or right within fixed-width columns.
- The market cap formatting converts raw numbers into readable values like $2.9T or $450.3B.
- The stock.get('field', default) pattern safely handles missing data by returning a fallback value.
- Save screener.py.
You won't see a change yet. The next chunk builds the menu that calls this function.
Add the main menu
The main() function ties everything together. It prints a menu, reads the user's choice, looks up the matching strategy, runs the query, and displays the results.
- Add the following main() function and entry-point guard below display_results():
def main():
print("=" * 75)
print("STOCK SCREENER")
print("=" * 75)
print("\nChoose a screening strategy:")
# List all available strategies from the registry
for key, (name, _, _) in strategies.items():
print(f" {key}. {name}")
choice = input("\nEnter choice (1-3): ").strip()
# Validate the user's input
if choice not in strategies:
print("Invalid choice.")
return
# Look up the strategy and run it
name, query_fn, sort_field = strategies[choice]
print(f"\nRunning screen: {name}...")
query = query_fn()
quotes = run_screen(query, sort_field=sort_field)
display_results(quotes)
if __name__ == "__main__":
main()
What does this code do?
- The for key, (name, _, _) in strategies.items() loop prints the menu by reading from the strategy registry you built in Step 4.
- The input() call pauses the script and waits for the user to type a number.
- The if __name__ == "__main__": guard ensures main() only runs when you execute the file directly, not when it is imported.
- Save screener.py.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before running it.
ⓧ I'd like to double check the full code
Here is the complete screener.py file. Compare yours against it:
import yfinance as yf
from yfinance import EquityQuery
def value_stocks():
"""Low P/E, positive earnings growth, large cap."""
return EquityQuery('and', [
EquityQuery('btwn', ['peratio.lasttwelvemonths', 0, 20]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 10]),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ']),
EquityQuery('gte', ['intradaymarketcap', 2000000000])
])
def growth_tech():
"""High revenue and earnings growth in tech."""
return EquityQuery('and', [
EquityQuery('gte', ['quarterlyrevenuegrowth.quarterly', 25]),
EquityQuery('gte', ['epsgrowth.lasttwelvemonths', 25]),
EquityQuery('eq', ['sector', 'Technology']),
EquityQuery('is-in', ['exchange', 'NMS', 'NYQ'])
])
def day_movers():
"""Big price moves today, liquid stocks only."""
return EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us']),
EquityQuery('gte', ['intradaymarketcap', 2000000000]),
EquityQuery('gte', ['intradayprice', 5]),
EquityQuery('gt', ['dayvolume', 15000])
])
strategies = {
'1': ('Value Stocks', value_stocks, 'eodvolume'),
'2': ('Growth Tech', growth_tech, 'eodvolume'),
'3': ('Day Movers', day_movers, 'percentchange'),
}
def run_screen(query, sort_field='percentchange', sort_asc=False):
"""Execute a screen query and return the list of matching quotes."""
response = yf.screen(query, sortField=sort_field, sortAsc=sort_asc, size=25)
return response['quotes']
def display_results(quotes):
"""Display screening results in a formatted table."""
if not quotes:
print("\nNo stocks matched your criteria.")
return
print(f"\n{'Symbol':<10}{'Name':<30}{'Price':>10}{'Change %':>10}{'Market Cap':>15}")
print("-" * 75)
for stock in quotes[:15]:
symbol = stock.get('symbol', 'N/A')
name = stock.get('shortName', 'N/A')[:28]
price = stock.get('regularMarketPrice', 0)
change = stock.get('regularMarketChangePercent', 0)
market_cap = stock.get('marketCap', 0)
if market_cap >= 1_000_000_000_000:
cap_str = f"${market_cap / 1_000_000_000_000:.1f}T"
elif market_cap >= 1_000_000_000:
cap_str = f"${market_cap / 1_000_000_000:.1f}B"
elif market_cap >= 1_000_000:
cap_str = f"${market_cap / 1_000_000:.1f}M"
else:
cap_str = f"${market_cap:,.0f}"
print(f"{symbol:<10}{name:<30}{price:>10.2f}{change:>9.2f}%{cap_str:>15}")
def main():
print("=" * 75)
print("STOCK SCREENER")
print("=" * 75)
print("\nChoose a screening strategy:")
for key, (name, _, _) in strategies.items():
print(f" {key}. {name}")
choice = input("\nEnter choice (1-3): ").strip()
if choice not in strategies:
print("Invalid choice.")
return
name, query_fn, sort_field = strategies[choice]
print(f"\nRunning screen: {name}...")
query = query_fn()
quotes = run_screen(query, sort_field=sort_field)
display_results(quotes)
if __name__ == "__main__":
main()
Run your stock screener
- Run your finished screener by executing this command:
python screener.py
- You should see a menu with three strategies listed. Enter 1, 2, or 3 and press Enter.
- You should see a formatted table of live stock data with columns for Symbol, Name, Price, Change %, and Market Cap.
- Try running the script again and choosing a different strategy to see different results.
Why do the results change each time?
Your screener queries live market data from Yahoo Finance. The stocks that match your criteria change throughout the trading day as prices move, volumes shift, and earnings reports come in.
Not seeing any results?
- If you see an error about input or KeyError, make sure you are running the file with python screener.py from the terminal, not running it in an interactive Python session.
- If you get "No stocks matched your criteria", that is normal for some strategies outside US market hours. Try the Day Movers strategy during trading hours (9:30 AM to 4:00 PM ET).
- If you see a connection error, check your internet connection. yfinance needs network access to reach Yahoo Finance.
Help me debug my stock screener output.
Secret mission
Add an Interactive Custom Query Builder
Your screener works, but every strategy is hardcoded. What if you want to search for healthcare stocks with a P/E under 15 that moved more than 2% today? Right now you would have to edit the code. Build an interactive mode that lets you compose custom queries on the fly, without touching a single line of Python.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your files for future use or delete them entirely.
Resources you used:
- The stock-screener folder on your Desktop (contains screener.py and test_install.py).
- The yfinance Python package installed via pip.
Keep everything running
No action needed. Choose this if you want to keep experimenting with your stock screener or add new strategies.
- Your stock-screener folder stays on your Desktop.
- The yfinance package remains installed in your Python environment.
Pause - I'll come back to this later
Since this project is entirely local with no running processes, there is nothing to pause. Your files and installed packages will be waiting for you when you return.
- No action needed. Everything is already idle.
Delete - I don't want to use this again
Remove all project files and the installed package to start fresh.
- Delete the project folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/stock-screener
Windows
rmdir /s /q %USERPROFILE%\Desktop\stock-screener
- Uninstall the yfinance package by running:
pip uninstall yfinance
- Type y and press Enter when prompted to confirm the uninstall.
That's a Wrap!
That's a Wrap!
Nice work! You just built a fully functional stock screener that queries live market data and filters it using the same search patterns found in databases, search engines, and APIs.
You've learned how to:
- Built a Python stock screener that queries live market data using the yfinance API.
- Composed multi-criteria search queries using boolean operators (AND, GT, LT, BTWN, EQ, IS-IN) to filter stocks by price, sector, market cap, and earnings growth.
- Built multiple screening strategies as reusable functions and formatted raw API data into human-readable tables.
- Secret Mission: Added an interactive custom query builder that lets users define their own screening criteria on the fly.
Ready to quiz yourself?