Files
Buffteks-Website/venv/lib/python3.12/site-packages/yfinance/screener/screener.py
2025-05-08 21:10:14 -05:00

196 lines
10 KiB
Python

from .query import EquityQuery as EqyQy
from .query import FundQuery as FndQy
from .query import QueryBase, EquityQuery, FundQuery
from yfinance.const import _QUERY1_URL_, _SENTINEL_
from yfinance.data import YfData
from ..utils import dynamic_docstring, generate_list_table_from_dict_universal, print_once
from typing import Union
import requests
_SCREENER_URL_ = f"{_QUERY1_URL_}/v1/finance/screener"
_PREDEFINED_URL_ = f"{_SCREENER_URL_}/predefined/saved"
PREDEFINED_SCREENER_BODY_DEFAULTS = {
"offset":0, "count":25, "userId":"","userIdType":"guid"
}
PREDEFINED_SCREENER_QUERIES = {
'aggressive_small_caps': {"sortField":"eodvolume", "sortType":"desc",
"query": EqyQy('and', [EqyQy('is-in', ['exchange', 'NMS', 'NYQ']), EqyQy('lt', ["epsgrowth.lasttwelvemonths", 15])])},
'day_gainers': {"sortField":"percentchange", "sortType":"DESC",
"query": EqyQy('and', [EqyQy('gt', ['percentchange', 3]), EqyQy('eq', ['region', 'us']), EqyQy('gte', ['intradaymarketcap', 2000000000]), EqyQy('gte', ['intradayprice', 5]), EqyQy('gt', ['dayvolume', 15000])])},
'day_losers': {"sortField":"percentchange", "sortType":"ASC",
"query": EqyQy('and', [EqyQy('lt', ['percentchange', -2.5]), EqyQy('eq', ['region', 'us']), EqyQy('gte', ['intradaymarketcap', 2000000000]), EqyQy('gte', ['intradayprice', 5]), EqyQy('gt', ['dayvolume', 20000])])},
'growth_technology_stocks': {"sortField":"eodvolume", "sortType":"desc",
"query": EqyQy('and', [EqyQy('gte', ['quarterlyrevenuegrowth.quarterly', 25]), EqyQy('gte', ['epsgrowth.lasttwelvemonths', 25]), EqyQy('eq', ['sector', 'Technology']), EqyQy('is-in', ['exchange', 'NMS', 'NYQ'])])},
'most_actives': {"sortField":"dayvolume", "sortType":"DESC",
"query": EqyQy('and', [EqyQy('eq', ['region', 'us']), EqyQy('gte', ['intradaymarketcap', 2000000000]), EqyQy('gt', ['dayvolume', 5000000])])},
'most_shorted_stocks': {"count":25, "offset":0, "sortField":"short_percentage_of_shares_outstanding.value", "sortType":"DESC",
"query": EqyQy('and', [EqyQy('eq', ['region', 'us']), EqyQy('gt', ['intradayprice', 1]), EqyQy('gt', ['avgdailyvol3m', 200000])])},
'small_cap_gainers': {"sortField":"eodvolume", "sortType":"desc",
"query": EqyQy("and", [EqyQy("lt", ["intradaymarketcap",2000000000]), EqyQy("is-in", ["exchange", "NMS", "NYQ"])])},
'undervalued_growth_stocks': {"sortType":"DESC", "sortField":"eodvolume",
"query": EqyQy('and', [EqyQy('btwn', ['peratio.lasttwelvemonths', 0, 20]), EqyQy('lt', ['pegratio_5y', 1]), EqyQy('gte', ['epsgrowth.lasttwelvemonths', 25]), EqyQy('is-in', ['exchange', 'NMS', 'NYQ'])])},
'undervalued_large_caps': {"sortField":"eodvolume", "sortType":"desc",
"query": EqyQy('and', [EqyQy('btwn', ['peratio.lasttwelvemonths', 0, 20]), EqyQy('lt', ['pegratio_5y', 1]), EqyQy('btwn', ['intradaymarketcap', 10000000000, 100000000000]), EqyQy('is-in', ['exchange', 'NMS', 'NYQ'])])},
'conservative_foreign_funds': {"sortType":"DESC", "sortField":"fundnetassets",
"query": FndQy('and', [FndQy('is-in', ['categoryname', 'Foreign Large Value', 'Foreign Large Blend', 'Foreign Large Growth', 'Foreign Small/Mid Growth', 'Foreign Small/Mid Blend', 'Foreign Small/Mid Value']), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('is-in', ['riskratingoverall', 1, 2, 3]), FndQy('eq', ['exchange', 'NAS'])])},
'high_yield_bond': {"sortType":"DESC", "sortField":"fundnetassets",
"query": FndQy('and', [FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('is-in', ['riskratingoverall', 1, 2, 3]), FndQy('eq', ['categoryname', 'High Yield Bond']), FndQy('eq', ['exchange', 'NAS'])])},
'portfolio_anchors': {"sortType":"DESC", "sortField":"fundnetassets",
"query": FndQy('and', [FndQy('eq', ['categoryname', 'Large Blend']), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('eq', ['exchange', 'NAS'])])},
'solid_large_growth_funds': {"sortType":"DESC", "sortField":"fundnetassets",
"query": FndQy('and', [FndQy('eq', ['categoryname', 'Large Growth']), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('eq', ['exchange', 'NAS'])])},
'solid_midcap_growth_funds': {"sortType":"DESC", "sortField":"fundnetassets",
"query": FndQy('and', [FndQy('eq', ['categoryname', 'Mid-Cap Growth']), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('lt', ['initialinvestment', 100001]), FndQy('lt', ['annualreturnnavy1categoryrank', 50]), FndQy('eq', ['exchange', 'NAS'])])},
'top_mutual_funds': {"sortType":"DESC", "sortField":"percentchange",
"query": FndQy('and', [FndQy('gt', ['intradayprice', 15]), FndQy('is-in', ['performanceratingoverall', 4, 5]), FndQy('gt', ['initialinvestment', 1000]), FndQy('eq', ['exchange', 'NAS'])])}
}
@dynamic_docstring({"predefined_screeners": generate_list_table_from_dict_universal(PREDEFINED_SCREENER_QUERIES, bullets=True, title='Predefined queries (Dec-2024)')})
def screen(query: Union[str, EquityQuery, FundQuery],
offset: int = None,
size: int = None,
count: int = None,
sortField: str = None,
sortAsc: bool = None,
userId: str = None,
userIdType: str = None,
session = None, proxy = _SENTINEL_):
"""
Run a screen: predefined query, or custom query.
:Parameters:
* Defaults only apply if query = EquityQuery or FundQuery
query : str | Query:
The query to execute, either name of predefined or custom query.
For predefined list run yf.PREDEFINED_SCREENER_QUERIES.keys()
offset : int
The offset for the results. Default 0.
size : int
number of results to return. Default 100, maximum 250 (Yahoo)
Use count instead for predefined queries.
count : int
number of results to return. Default 25, maximum 250 (Yahoo)
Use size instead for custom queries.
sortField : str
field to sort by. Default "ticker"
sortAsc : bool
Sort ascending? Default False
userId : str
The user ID. Default empty.
userIdType : str
Type of user ID (e.g., "guid"). Default "guid".
Example: predefined query
.. code-block:: python
import yfinance as yf
response = yf.screen("aggressive_small_caps")
Example: custom query
.. code-block:: python
import yfinance as yf
from yfinance import EquityQuery
q = EquityQuery('and', [
EquityQuery('gt', ['percentchange', 3]),
EquityQuery('eq', ['region', 'us'])
])
response = yf.screen(q, sortField = 'percentchange', sortAsc = True)
To access predefineds query code
.. code-block:: python
import yfinance as yf
query = yf.PREDEFINED_SCREENER_QUERIES['aggressive_small_caps']
{predefined_screeners}
"""
if proxy is not _SENTINEL_:
print_once("YF deprecation warning: set proxy via new config function: yf.set_config(proxy=proxy)")
_data = YfData(session=session, proxy=proxy)
else:
_data = YfData(session=session)
# Only use defaults when user NOT give a predefined, because
# Yahoo's predefined endpoint auto-applies defaults. Also,
# that endpoint might be ignoring these fields.
defaults = {
'offset': 0,
'count': 25,
'sortField': 'ticker',
'sortAsc': False,
'userId': "",
'userIdType': "guid"
}
if count is not None and count > 250:
raise ValueError("Yahoo limits query count to 250, reduce count.")
if size is not None and size > 250:
raise ValueError("Yahoo limits query size to 250, reduce size.")
fields = {'offset': offset, 'count': count, "size": size, 'sortField': sortField, 'sortAsc': sortAsc, 'userId': userId, 'userIdType': userIdType}
params_dict = {"corsDomain": "finance.yahoo.com", "formatted": "false", "lang": "en-US", "region": "US"}
post_query = None
if isinstance(query, str):
# post_query = PREDEFINED_SCREENER_QUERIES[query]
# Switch to Yahoo's predefined endpoint
if size is not None:
print_once("YF deprecation warning: 'size' argument is deprecated for predefined screens, set 'count' instead.")
count = size
size = None
fields['count'] = fields['size']
del fields['size']
params_dict['scrIds'] = query
for k,v in fields.items():
if v is not None:
params_dict[k] = v
resp = _data.get(url=_PREDEFINED_URL_, params=params_dict)
try:
resp.raise_for_status()
except requests.exceptions.HTTPError:
if query not in PREDEFINED_SCREENER_QUERIES:
print(f"yfinance.screen: '{query}' is probably not a predefined query.")
raise
return resp.json()["finance"]["result"][0]
elif isinstance(query, QueryBase):
# Prepare other fields
for k in defaults:
if k not in fields or fields[k] is None:
fields[k] = defaults[k]
fields['sortType'] = 'ASC' if fields['sortAsc'] else 'DESC'
del fields['sortAsc']
post_query = fields
post_query['query'] = query
else:
raise ValueError(f'Query must be type str or QueryBase, not "{type(query)}"')
if query is None:
raise ValueError('No query provided')
if isinstance(post_query['query'], EqyQy):
post_query['quoteType'] = 'EQUITY'
elif isinstance(post_query['query'], FndQy):
post_query['quoteType'] = 'MUTUALFUND'
post_query['query'] = post_query['query'].to_dict()
# Fetch
response = _data.post(_SCREENER_URL_,
body=post_query,
params=params_dict)
response.raise_for_status()
return response.json()['finance']['result'][0]