CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-yfinance

Download market data from Yahoo! Finance API

Overview
Eval results
Files

screening.mddocs/

Custom Screening

Build custom queries to screen and filter financial instruments based on various criteria with predefined screening options. These capabilities enable systematic investment research and opportunity discovery across equity and fund markets.

Capabilities

Equity Query Builder

Build custom screening queries for stocks and equities with flexible operators and criteria combinations.

class EquityQuery:
    def __init__(self, operator: str, operand: list):
        """
        Create a custom equity screening query.
        
        Parameters:
        - operator: str, query operator ('EQ', 'GT', 'LT', 'GTE', 'LTE', 'BTWN', 'IS-IN', 'AND', 'OR')
        - operand: list, operand values or nested query objects
        
        Operators:
        - 'EQ': Equal to
        - 'GT': Greater than
        - 'LT': Less than  
        - 'GTE': Greater than or equal to
        - 'LTE': Less than or equal to
        - 'BTWN': Between (requires 2-element list)
        - 'IS-IN': In list of values
        - 'AND': Logical AND (combines multiple queries)
        - 'OR': Logical OR (combines multiple queries)
        """
    
    # Properties for query validation
    valid_fields: Dict  # Valid field names organized by category
    valid_values: Dict  # Valid values for specific fields

Available Screening Fields

Common equity screening fields include:

Price & Performance:

  • percentchange: Percent change from previous close
  • price: Current stock price
  • dayrange: Day's trading range
  • 52weekrange: 52-week price range

Valuation Metrics:

  • pe: Price-to-earnings ratio
  • peg: Price/earnings to growth ratio
  • pb: Price-to-book ratio
  • ps: Price-to-sales ratio
  • marketcap: Market capitalization

Financial Metrics:

  • roe: Return on equity
  • roa: Return on assets
  • debttoequity: Debt-to-equity ratio
  • currentratio: Current ratio

Market Data:

  • volume: Trading volume
  • avgvolume: Average volume
  • sector: Business sector
  • industry: Industry classification
  • region: Geographic region

Usage Examples

import yfinance as yf
from yfinance import EquityQuery, screen

# Simple percentage change filter
gainers_query = EquityQuery('gt', ['percentchange', 5])

# Price range filter
price_range_query = EquityQuery('btwn', ['price', [50, 200]])

# Market cap filter
large_cap_query = EquityQuery('gte', ['marketcap', 10000000000])  # >= $10B

# Sector filter
tech_query = EquityQuery('eq', ['sector', 'Technology'])

# Complex combined query
complex_query = EquityQuery('and', [
    EquityQuery('gt', ['percentchange', 3]),      # Up more than 3%
    EquityQuery('gte', ['marketcap', 1000000000]), # Market cap >= $1B
    EquityQuery('eq', ['region', 'us']),          # US stocks only
    EquityQuery('lt', ['pe', 25])                 # P/E ratio < 25
])

# Alternative OR combination
alternative_query = EquityQuery('or', [
    EquityQuery('gt', ['percentchange', 10]),     # Up more than 10%
    EquityQuery('gt', ['volume', 5000000])        # High volume
])

Fund Query Builder

Build custom screening queries for mutual funds and ETFs with fund-specific criteria.

class FundQuery:
    def __init__(self, operator: str, operand: list):
        """
        Create a custom fund screening query.
        
        Parameters:
        - operator: str, query operator (same as EquityQuery operators) 
        - operand: list, operand values or nested query objects
        
        Fund-specific fields include expense ratios, fund categories, 
        performance metrics, and asset allocation criteria.
        """
    
    # Properties for query validation (fund-specific)
    valid_fields: Dict  # Valid fund field names
    valid_values: Dict  # Valid values for fund-specific fields

Fund-Specific Screening Fields

Fund Characteristics:

  • fundtype: Fund type (ETF, mutual fund, index fund)
  • category: Fund category (large cap, small cap, bond, etc.)
  • expenserate: Annual expense ratio
  • nav: Net asset value

Performance Metrics:

  • ytdreturn: Year-to-date return
  • 1yearreturn: 1-year return
  • 3yearreturn: 3-year annualized return
  • 5yearreturn: 5-year annualized return

Fund Size & Activity:

  • totalassets: Total fund assets under management
  • avgvolume: Average trading volume (for ETFs)

Usage Examples

from yfinance import FundQuery

# Low-cost funds filter
low_cost_query = FundQuery('lt', ['expenserate', 0.5])  # Expense ratio < 0.5%

# High-performing funds
performance_query = FundQuery('gt', ['1yearreturn', 15])  # > 15% 1-year return

# Large fund filter
large_fund_query = FundQuery('gte', ['totalassets', 1000000000])  # >= $1B AUM

# Combined fund criteria
quality_fund_query = FundQuery('and', [
    FundQuery('lt', ['expenserate', 0.75]),       # Low expense ratio
    FundQuery('gt', ['3yearreturn', 10]),         # Good 3-year performance
    FundQuery('eq', ['fundtype', 'ETF']),         # ETFs only
    FundQuery('gte', ['totalassets', 500000000])  # Adequate size
])

Screen Execution

Execute screening queries against the financial database with sorting and pagination options.

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) -> dict:
    """
    Execute a screening query to find matching financial instruments.
    
    Parameters:
    - query: str (predefined query name) or EquityQuery/FundQuery object
    - offset: int, starting position for pagination
    - size: int, number of results to return (max 250)
    - count: int, alias for size parameter
    - sortField: str, field to sort results by (default: 'ticker')
    - sortAsc: bool, sort ascending (True) or descending (False, default)
    - userId: str, user identifier for personalized results
    - userIdType: str, type of user identifier
    - session: requests.Session, optional session for HTTP requests
    
    Returns:
    dict with screening results including matched instruments and metadata
    """

Screen Result Structure

{
    'finance': {
        'result': [{
            'id': 'screener_result_id',
            'title': 'Screening Results',
            'description': 'Custom equity screen',
            'canonicalName': 'custom_screen',
            'count': 45,
            'quotes': [
                {
                    'symbol': 'AAPL',
                    'shortName': 'Apple Inc.',
                    'longName': 'Apple Inc.',
                    'sector': 'Technology',
                    'industry': 'Consumer Electronics',
                    'marketCap': 2800000000000,
                    'regularMarketPrice': 175.43,
                    'regularMarketChange': 2.15,
                    'regularMarketChangePercent': 1.24,
                    'volume': 45672100
                },
                # ... more results
            ],
            'start': 0,
            'total': 45
        }]
    },
    'error': None
}

Usage Examples

# Execute predefined screen
day_gainers = screen("day_gainers")
results = day_gainers['finance']['result'][0]['quotes']

# Execute custom query with sorting
custom_query = EquityQuery('and', [
    EquityQuery('gt', ['percentchange', 5]),
    EquityQuery('eq', ['region', 'us'])
])

custom_results = screen(custom_query, 
                       size=50, 
                       sortField='percentchange', 
                       sortAsc=False)

# Pagination example
page_1 = screen("most_actives", size=25, offset=0)
page_2 = screen("most_actives", size=25, offset=25)

# Fund screening
fund_query = FundQuery('lt', ['expenserate', 0.5])
low_cost_funds = screen(fund_query, size=30, sortField='expenserate')

Predefined Screening Queries

Access a comprehensive set of predefined screening queries for common investment research scenarios.

PREDEFINED_SCREENER_QUERIES: Dict[str, str]
# Dictionary containing predefined query names and descriptions

Available Predefined Screens

Equity Screens:

Growth & Performance:

  • 'aggressive_small_caps': High-growth potential small cap stocks
  • 'day_gainers': Top daily percentage gainers
  • 'day_losers': Top daily percentage losers
  • 'growth_technology_stocks': Technology stocks with strong growth metrics
  • 'small_cap_gainers': Best performing small cap stocks
  • 'undervalued_growth_stocks': Growth stocks trading at reasonable valuations
  • 'undervalued_large_caps': Large cap value opportunities

Activity & Interest:

  • 'most_actives': Most actively traded stocks by volume
  • 'most_shorted_stocks': Stocks with highest short interest

Fund Screens:

Conservative Options:

  • 'conservative_foreign_funds': Conservative international funds
  • 'high_yield_bond': High-yield bond funds
  • 'portfolio_anchors': Stable, core holding funds

Growth Oriented:

  • 'solid_large_growth_funds': Reliable large cap growth funds
  • 'solid_midcap_growth_funds': Quality mid cap growth funds
  • 'top_mutual_funds': Top-rated mutual funds across categories

Usage Examples

# View all available predefined screens
from yfinance import PREDEFINED_SCREENER_QUERIES

print("Available Predefined Screens:")
for query_name, description in PREDEFINED_SCREENER_QUERIES.items():
    print(f"  {query_name}: {description}")

# Execute predefined screens
day_gainers = screen("day_gainers", size=20)
most_active = screen("most_actives", size=15)
growth_tech = screen("growth_technology_stocks", size=25)

# Get results from predefined screens
gainers_list = day_gainers['finance']['result'][0]['quotes']
for stock in gainers_list[:5]:  # Top 5
    print(f"{stock['symbol']}: {stock['regularMarketChangePercent']:+.2f}%")

Advanced Screening Patterns

Multi-Criteria Screening Strategy

def comprehensive_stock_screen(min_market_cap=1000000000, min_change=2.0, 
                              max_pe=20, sectors=None):
    """Create a comprehensive stock screening strategy."""
    
    # Build base criteria
    criteria = [
        EquityQuery('gte', ['marketcap', min_market_cap]),  # Market cap filter
        EquityQuery('gt', ['percentchange', min_change]),    # Performance filter
        EquityQuery('lt', ['pe', max_pe]),                   # Valuation filter
        EquityQuery('gt', ['volume', 500000])                # Liquidity filter
    ]
    
    # Add sector filter if specified
    if sectors:
        if len(sectors) == 1:
            criteria.append(EquityQuery('eq', ['sector', sectors[0]]))
        else:
            sector_queries = [EquityQuery('eq', ['sector', sector]) for sector in sectors]
            criteria.append(EquityQuery('or', sector_queries))
    
    # Combine all criteria
    comprehensive_query = EquityQuery('and', criteria)
    
    # Execute screen
    results = screen(comprehensive_query, size=100, sortField='percentchange', sortAsc=False)
    
    return results

# Usage
tech_healthcare_screen = comprehensive_stock_screen(
    min_market_cap=5000000000,  # $5B minimum
    min_change=3.0,             # 3%+ gain
    max_pe=25,                  # P/E < 25
    sectors=['Technology', 'Healthcare']
)

Momentum Strategy Screening

def momentum_screen_strategy(timeframes=['1day', '1week', '1month']):
    """Screen for momentum stocks across multiple timeframes."""
    
    momentum_results = {}
    
    # Daily momentum
    daily_momentum = EquityQuery('and', [
        EquityQuery('gt', ['percentchange', 5]),      # Up 5%+ today
        EquityQuery('gt', ['volume', 1000000]),       # High volume
        EquityQuery('gte', ['marketcap', 1000000000]) # Large enough
    ])
    
    # Execute momentum screens
    if '1day' in timeframes:
        momentum_results['daily'] = screen(daily_momentum, 
                                         size=50, 
                                         sortField='percentchange')
    
    # Note: Weekly and monthly momentum would require additional data
    # This demonstrates the pattern for extending to multiple timeframes
    
    return momentum_results

# Usage
momentum_stocks = momentum_screen_strategy(['1day'])
daily_momentum = momentum_stocks['daily']['finance']['result'][0]['quotes']

print("Top Daily Momentum Stocks:")
for stock in daily_momentum[:10]:
    print(f"{stock['symbol']}: {stock['regularMarketChangePercent']:+.2f}% "
          f"(Vol: {stock['volume']:,})")

Value Investing Screen

def value_investing_screen(max_pe=15, max_pb=2.0, min_roe=10):
    """Screen for value investment opportunities."""
    
    value_criteria = EquityQuery('and', [
        EquityQuery('lt', ['pe', max_pe]),           # Low P/E ratio
        EquityQuery('lt', ['pb', max_pb]),           # Low price-to-book
        EquityQuery('gt', ['roe', min_roe]),         # Good return on equity
        EquityQuery('gte', ['marketcap', 1000000000]), # Minimum size
        EquityQuery('gt', ['volume', 200000])        # Adequate liquidity
    ])
    
    value_results = screen(value_criteria, 
                          size=75, 
                          sortField='pe',  # Sort by P/E ratio
                          sortAsc=True)    # Lowest P/E first
    
    return value_results

# Usage
value_opportunities = value_investing_screen(max_pe=12, max_pb=1.5, min_roe=15)
value_stocks = value_opportunities['finance']['result'][0]['quotes']

print("Value Investment Opportunities:")
for stock in value_stocks[:10]:
    pe_ratio = stock.get('trailingPE', 'N/A')
    print(f"{stock['symbol']}: P/E={pe_ratio}, Sector={stock.get('sector', 'N/A')}")

Fund Screening Strategies

def low_cost_index_fund_screen(max_expense=0.5, min_assets=1000000000):
    """Screen for low-cost index funds."""
    
    index_fund_criteria = FundQuery('and', [
        FundQuery('lt', ['expenserate', max_expense]),      # Low expense ratio
        FundQuery('gte', ['totalassets', min_assets]),      # Adequate size
        FundQuery('eq', ['fundtype', 'ETF']),               # ETFs preferred
        FundQuery('gt', ['avgvolume', 100000])              # Good liquidity
    ])
    
    results = screen(index_fund_criteria, 
                    size=50, 
                    sortField='expenserate',
                    sortAsc=True)  # Lowest expenses first
    
    return results

def high_yield_fund_screen(min_yield=4.0, max_expense=1.0):
    """Screen for high-yield income funds."""
    
    yield_criteria = FundQuery('and', [
        FundQuery('gt', ['yield', min_yield]),         # High yield
        FundQuery('lt', ['expenserate', max_expense]), # Reasonable expenses
        FundQuery('gte', ['totalassets', 500000000])   # Minimum fund size
    ])
    
    results = screen(yield_criteria, 
                    size=30, 
                    sortField='yield',
                    sortAsc=False)  # Highest yield first
    
    return results

# Usage
low_cost_etfs = low_cost_index_fund_screen(max_expense=0.25, min_assets=5000000000)
high_yield_funds = high_yield_fund_screen(min_yield=5.0, max_expense=0.75)

Sector-Specific Screening

def sector_leader_screen(sector, performance_threshold=10):
    """Screen for leaders within a specific sector."""
    
    sector_leaders = EquityQuery('and', [
        EquityQuery('eq', ['sector', sector]),              # Specific sector
        EquityQuery('gt', ['percentchange', performance_threshold]), # Strong performance
        EquityQuery('gt', ['marketcap', 1000000000]),       # Large cap preferred
        EquityQuery('gt', ['volume', 500000]),              # Good liquidity
        EquityQuery('lt', ['pe', 30])                       # Not overvalued
    ])
    
    results = screen(sector_leaders, 
                    size=25, 
                    sortField='marketcap',
                    sortAsc=False)  # Largest companies first
    
    return results

def emerging_sector_screen(sectors, min_growth=20):
    """Screen for emerging opportunities across multiple sectors."""
    
    # Create sector filter
    if len(sectors) == 1:
        sector_filter = EquityQuery('eq', ['sector', sectors[0]])
    else:
        sector_queries = [EquityQuery('eq', ['sector', s]) for s in sectors]
        sector_filter = EquityQuery('or', sector_queries)
    
    emerging_criteria = EquityQuery('and', [
        sector_filter,                                  # Target sectors
        EquityQuery('gt', ['percentchange', min_growth]), # High growth
        EquityQuery('btwn', ['marketcap', [100000000, 10000000000]]), # Mid-cap range
        EquityQuery('gt', ['volume', 250000])           # Adequate volume
    ])
    
    results = screen(emerging_criteria, 
                    size=40, 
                    sortField='percentchange',
                    sortAsc=False)
    
    return results

# Usage
tech_leaders = sector_leader_screen('Technology', performance_threshold=5)
clean_energy_emerging = emerging_sector_screen(['Utilities', 'Materials'], min_growth=15)

Screen Result Processing and Analysis

Result Analysis and Ranking

def analyze_screen_results(screen_results, additional_metrics=True):
    """Analyze and enhance screening results with additional metrics."""
    
    quotes = screen_results['finance']['result'][0]['quotes']
    enhanced_results = []
    
    for quote in quotes:
        symbol = quote['symbol']
        
        # Basic data from screen
        result = {
            'symbol': symbol,
            'name': quote.get('shortName', ''),
            'sector': quote.get('sector', ''),
            'price': quote.get('regularMarketPrice', 0),
            'change_percent': quote.get('regularMarketChangePercent', 0),
            'volume': quote.get('volume', 0),
            'market_cap': quote.get('marketCap', 0)
        }
        
        # Add additional metrics if requested
        if additional_metrics:
            try:
                ticker = yf.Ticker(symbol)
                info = ticker.info
                
                result.update({
                    'pe_ratio': info.get('trailingPE', None),
                    'pb_ratio': info.get('priceToBook', None),
                    'dividend_yield': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 0,
                    'profit_margin': info.get('profitMargins', 0) * 100 if info.get('profitMargins') else 0,
                    'debt_to_equity': info.get('debtToEquity', None)
                })
            except:
                # Handle cases where additional data isn't available
                pass
        
        enhanced_results.append(result)
    
    return enhanced_results

# Usage
screen_result = screen("day_gainers", size=20)
analyzed_results = analyze_screen_results(screen_result, additional_metrics=True)

for stock in analyzed_results[:5]:
    print(f"{stock['symbol']}: {stock['change_percent']:+.2f}% "
          f"P/E={stock.get('pe_ratio', 'N/A')} "
          f"Yield={stock.get('dividend_yield', 0):.1f}%")

Portfolio Construction from Screens

def build_portfolio_from_screens(screen_configs, max_positions=20, 
                                max_sector_weight=0.3):
    """Build a diversified portfolio from multiple screening strategies."""
    
    all_candidates = []
    
    # Execute multiple screens
    for config in screen_configs:
        screen_name = config['name']
        query = config['query']
        weight = config.get('weight', 1.0)
        
        results = screen(query, size=config.get('size', 25))
        quotes = results['finance']['result'][0]['quotes']
        
        for quote in quotes:
            all_candidates.append({
                'symbol': quote['symbol'],
                'name': quote.get('shortName', ''),
                'sector': quote.get('sector', ''),
                'screen_source': screen_name,
                'weight': weight,
                'change_percent': quote.get('regularMarketChangePercent', 0),
                'market_cap': quote.get('marketCap', 0)
            })
    
    # Remove duplicates (keep highest weighted occurrence)
    unique_candidates = {}
    for candidate in all_candidates:
        symbol = candidate['symbol']
        if symbol not in unique_candidates or candidate['weight'] > unique_candidates[symbol]['weight']:
            unique_candidates[symbol] = candidate
    
    # Sort by weighted score
    candidates = list(unique_candidates.values())
    candidates.sort(key=lambda x: x['change_percent'] * x['weight'], reverse=True)
    
    # Build diversified portfolio
    portfolio = []
    sector_weights = {}
    
    for candidate in candidates:
        if len(portfolio) >= max_positions:
            break
        
        sector = candidate['sector']
        current_sector_weight = sector_weights.get(sector, 0)
        
        # Check sector concentration limits
        if current_sector_weight < max_sector_weight:
            portfolio.append(candidate)
            sector_weights[sector] = current_sector_weight + (1.0 / max_positions)
    
    return portfolio

# Usage
portfolio_screens = [
    {
        'name': 'momentum',
        'query': EquityQuery('and', [
            EquityQuery('gt', ['percentchange', 3]),
            EquityQuery('gt', ['volume', 1000000])
        ]),
        'weight': 1.5,
        'size': 30
    },
    {
        'name': 'value',
        'query': EquityQuery('and', [
            EquityQuery('lt', ['pe', 20]),
            EquityQuery('gt', ['roe', 10])
        ]),
        'weight': 1.0,
        'size': 25
    }
]

diversified_portfolio = build_portfolio_from_screens(portfolio_screens, 
                                                   max_positions=15, 
                                                   max_sector_weight=0.25)

print("Constructed Portfolio:")
for i, holding in enumerate(diversified_portfolio, 1):
    print(f"{i:2d}. {holding['symbol']} ({holding['sector']}) - "
          f"Source: {holding['screen_source']}")

Install with Tessl CLI

npx tessl i tessl/pypi-yfinance

docs

bulk-data.md

config-utils.md

index.md

live-streaming.md

market-sector.md

screening.md

search-lookup.md

ticker-data.md

tile.json