0
# Custom Screening
1
2
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.
3
4
## Capabilities
5
6
### Equity Query Builder
7
8
Build custom screening queries for stocks and equities with flexible operators and criteria combinations.
9
10
```python { .api }
11
class EquityQuery:
12
def __init__(self, operator: str, operand: list):
13
"""
14
Create a custom equity screening query.
15
16
Parameters:
17
- operator: str, query operator ('EQ', 'GT', 'LT', 'GTE', 'LTE', 'BTWN', 'IS-IN', 'AND', 'OR')
18
- operand: list, operand values or nested query objects
19
20
Operators:
21
- 'EQ': Equal to
22
- 'GT': Greater than
23
- 'LT': Less than
24
- 'GTE': Greater than or equal to
25
- 'LTE': Less than or equal to
26
- 'BTWN': Between (requires 2-element list)
27
- 'IS-IN': In list of values
28
- 'AND': Logical AND (combines multiple queries)
29
- 'OR': Logical OR (combines multiple queries)
30
"""
31
32
# Properties for query validation
33
valid_fields: Dict # Valid field names organized by category
34
valid_values: Dict # Valid values for specific fields
35
```
36
37
#### Available Screening Fields
38
39
Common equity screening fields include:
40
41
**Price & Performance**:
42
- `percentchange`: Percent change from previous close
43
- `price`: Current stock price
44
- `dayrange`: Day's trading range
45
- `52weekrange`: 52-week price range
46
47
**Valuation Metrics**:
48
- `pe`: Price-to-earnings ratio
49
- `peg`: Price/earnings to growth ratio
50
- `pb`: Price-to-book ratio
51
- `ps`: Price-to-sales ratio
52
- `marketcap`: Market capitalization
53
54
**Financial Metrics**:
55
- `roe`: Return on equity
56
- `roa`: Return on assets
57
- `debttoequity`: Debt-to-equity ratio
58
- `currentratio`: Current ratio
59
60
**Market Data**:
61
- `volume`: Trading volume
62
- `avgvolume`: Average volume
63
- `sector`: Business sector
64
- `industry`: Industry classification
65
- `region`: Geographic region
66
67
#### Usage Examples
68
69
```python
70
import yfinance as yf
71
from yfinance import EquityQuery, screen
72
73
# Simple percentage change filter
74
gainers_query = EquityQuery('gt', ['percentchange', 5])
75
76
# Price range filter
77
price_range_query = EquityQuery('btwn', ['price', [50, 200]])
78
79
# Market cap filter
80
large_cap_query = EquityQuery('gte', ['marketcap', 10000000000]) # >= $10B
81
82
# Sector filter
83
tech_query = EquityQuery('eq', ['sector', 'Technology'])
84
85
# Complex combined query
86
complex_query = EquityQuery('and', [
87
EquityQuery('gt', ['percentchange', 3]), # Up more than 3%
88
EquityQuery('gte', ['marketcap', 1000000000]), # Market cap >= $1B
89
EquityQuery('eq', ['region', 'us']), # US stocks only
90
EquityQuery('lt', ['pe', 25]) # P/E ratio < 25
91
])
92
93
# Alternative OR combination
94
alternative_query = EquityQuery('or', [
95
EquityQuery('gt', ['percentchange', 10]), # Up more than 10%
96
EquityQuery('gt', ['volume', 5000000]) # High volume
97
])
98
```
99
100
### Fund Query Builder
101
102
Build custom screening queries for mutual funds and ETFs with fund-specific criteria.
103
104
```python { .api }
105
class FundQuery:
106
def __init__(self, operator: str, operand: list):
107
"""
108
Create a custom fund screening query.
109
110
Parameters:
111
- operator: str, query operator (same as EquityQuery operators)
112
- operand: list, operand values or nested query objects
113
114
Fund-specific fields include expense ratios, fund categories,
115
performance metrics, and asset allocation criteria.
116
"""
117
118
# Properties for query validation (fund-specific)
119
valid_fields: Dict # Valid fund field names
120
valid_values: Dict # Valid values for fund-specific fields
121
```
122
123
#### Fund-Specific Screening Fields
124
125
**Fund Characteristics**:
126
- `fundtype`: Fund type (ETF, mutual fund, index fund)
127
- `category`: Fund category (large cap, small cap, bond, etc.)
128
- `expenserate`: Annual expense ratio
129
- `nav`: Net asset value
130
131
**Performance Metrics**:
132
- `ytdreturn`: Year-to-date return
133
- `1yearreturn`: 1-year return
134
- `3yearreturn`: 3-year annualized return
135
- `5yearreturn`: 5-year annualized return
136
137
**Fund Size & Activity**:
138
- `totalassets`: Total fund assets under management
139
- `avgvolume`: Average trading volume (for ETFs)
140
141
#### Usage Examples
142
143
```python
144
from yfinance import FundQuery
145
146
# Low-cost funds filter
147
low_cost_query = FundQuery('lt', ['expenserate', 0.5]) # Expense ratio < 0.5%
148
149
# High-performing funds
150
performance_query = FundQuery('gt', ['1yearreturn', 15]) # > 15% 1-year return
151
152
# Large fund filter
153
large_fund_query = FundQuery('gte', ['totalassets', 1000000000]) # >= $1B AUM
154
155
# Combined fund criteria
156
quality_fund_query = FundQuery('and', [
157
FundQuery('lt', ['expenserate', 0.75]), # Low expense ratio
158
FundQuery('gt', ['3yearreturn', 10]), # Good 3-year performance
159
FundQuery('eq', ['fundtype', 'ETF']), # ETFs only
160
FundQuery('gte', ['totalassets', 500000000]) # Adequate size
161
])
162
```
163
164
### Screen Execution
165
166
Execute screening queries against the financial database with sorting and pagination options.
167
168
```python { .api }
169
def screen(query: Union[str, EquityQuery, FundQuery],
170
offset: int = None, size: int = None, count: int = None,
171
sortField: str = None, sortAsc: bool = None,
172
userId: str = None, userIdType: str = None,
173
session = None) -> dict:
174
"""
175
Execute a screening query to find matching financial instruments.
176
177
Parameters:
178
- query: str (predefined query name) or EquityQuery/FundQuery object
179
- offset: int, starting position for pagination
180
- size: int, number of results to return (max 250)
181
- count: int, alias for size parameter
182
- sortField: str, field to sort results by (default: 'ticker')
183
- sortAsc: bool, sort ascending (True) or descending (False, default)
184
- userId: str, user identifier for personalized results
185
- userIdType: str, type of user identifier
186
- session: requests.Session, optional session for HTTP requests
187
188
Returns:
189
dict with screening results including matched instruments and metadata
190
"""
191
```
192
193
#### Screen Result Structure
194
195
```python
196
{
197
'finance': {
198
'result': [{
199
'id': 'screener_result_id',
200
'title': 'Screening Results',
201
'description': 'Custom equity screen',
202
'canonicalName': 'custom_screen',
203
'count': 45,
204
'quotes': [
205
{
206
'symbol': 'AAPL',
207
'shortName': 'Apple Inc.',
208
'longName': 'Apple Inc.',
209
'sector': 'Technology',
210
'industry': 'Consumer Electronics',
211
'marketCap': 2800000000000,
212
'regularMarketPrice': 175.43,
213
'regularMarketChange': 2.15,
214
'regularMarketChangePercent': 1.24,
215
'volume': 45672100
216
},
217
# ... more results
218
],
219
'start': 0,
220
'total': 45
221
}]
222
},
223
'error': None
224
}
225
```
226
227
#### Usage Examples
228
229
```python
230
# Execute predefined screen
231
day_gainers = screen("day_gainers")
232
results = day_gainers['finance']['result'][0]['quotes']
233
234
# Execute custom query with sorting
235
custom_query = EquityQuery('and', [
236
EquityQuery('gt', ['percentchange', 5]),
237
EquityQuery('eq', ['region', 'us'])
238
])
239
240
custom_results = screen(custom_query,
241
size=50,
242
sortField='percentchange',
243
sortAsc=False)
244
245
# Pagination example
246
page_1 = screen("most_actives", size=25, offset=0)
247
page_2 = screen("most_actives", size=25, offset=25)
248
249
# Fund screening
250
fund_query = FundQuery('lt', ['expenserate', 0.5])
251
low_cost_funds = screen(fund_query, size=30, sortField='expenserate')
252
```
253
254
### Predefined Screening Queries
255
256
Access a comprehensive set of predefined screening queries for common investment research scenarios.
257
258
```python { .api }
259
PREDEFINED_SCREENER_QUERIES: Dict[str, str]
260
# Dictionary containing predefined query names and descriptions
261
```
262
263
#### Available Predefined Screens
264
265
**Equity Screens**:
266
267
*Growth & Performance*:
268
- `'aggressive_small_caps'`: High-growth potential small cap stocks
269
- `'day_gainers'`: Top daily percentage gainers
270
- `'day_losers'`: Top daily percentage losers
271
- `'growth_technology_stocks'`: Technology stocks with strong growth metrics
272
- `'small_cap_gainers'`: Best performing small cap stocks
273
- `'undervalued_growth_stocks'`: Growth stocks trading at reasonable valuations
274
- `'undervalued_large_caps'`: Large cap value opportunities
275
276
*Activity & Interest*:
277
- `'most_actives'`: Most actively traded stocks by volume
278
- `'most_shorted_stocks'`: Stocks with highest short interest
279
280
**Fund Screens**:
281
282
*Conservative Options*:
283
- `'conservative_foreign_funds'`: Conservative international funds
284
- `'high_yield_bond'`: High-yield bond funds
285
- `'portfolio_anchors'`: Stable, core holding funds
286
287
*Growth Oriented*:
288
- `'solid_large_growth_funds'`: Reliable large cap growth funds
289
- `'solid_midcap_growth_funds'`: Quality mid cap growth funds
290
- `'top_mutual_funds'`: Top-rated mutual funds across categories
291
292
#### Usage Examples
293
294
```python
295
# View all available predefined screens
296
from yfinance import PREDEFINED_SCREENER_QUERIES
297
298
print("Available Predefined Screens:")
299
for query_name, description in PREDEFINED_SCREENER_QUERIES.items():
300
print(f" {query_name}: {description}")
301
302
# Execute predefined screens
303
day_gainers = screen("day_gainers", size=20)
304
most_active = screen("most_actives", size=15)
305
growth_tech = screen("growth_technology_stocks", size=25)
306
307
# Get results from predefined screens
308
gainers_list = day_gainers['finance']['result'][0]['quotes']
309
for stock in gainers_list[:5]: # Top 5
310
print(f"{stock['symbol']}: {stock['regularMarketChangePercent']:+.2f}%")
311
```
312
313
## Advanced Screening Patterns
314
315
### Multi-Criteria Screening Strategy
316
317
```python
318
def comprehensive_stock_screen(min_market_cap=1000000000, min_change=2.0,
319
max_pe=20, sectors=None):
320
"""Create a comprehensive stock screening strategy."""
321
322
# Build base criteria
323
criteria = [
324
EquityQuery('gte', ['marketcap', min_market_cap]), # Market cap filter
325
EquityQuery('gt', ['percentchange', min_change]), # Performance filter
326
EquityQuery('lt', ['pe', max_pe]), # Valuation filter
327
EquityQuery('gt', ['volume', 500000]) # Liquidity filter
328
]
329
330
# Add sector filter if specified
331
if sectors:
332
if len(sectors) == 1:
333
criteria.append(EquityQuery('eq', ['sector', sectors[0]]))
334
else:
335
sector_queries = [EquityQuery('eq', ['sector', sector]) for sector in sectors]
336
criteria.append(EquityQuery('or', sector_queries))
337
338
# Combine all criteria
339
comprehensive_query = EquityQuery('and', criteria)
340
341
# Execute screen
342
results = screen(comprehensive_query, size=100, sortField='percentchange', sortAsc=False)
343
344
return results
345
346
# Usage
347
tech_healthcare_screen = comprehensive_stock_screen(
348
min_market_cap=5000000000, # $5B minimum
349
min_change=3.0, # 3%+ gain
350
max_pe=25, # P/E < 25
351
sectors=['Technology', 'Healthcare']
352
)
353
```
354
355
### Momentum Strategy Screening
356
357
```python
358
def momentum_screen_strategy(timeframes=['1day', '1week', '1month']):
359
"""Screen for momentum stocks across multiple timeframes."""
360
361
momentum_results = {}
362
363
# Daily momentum
364
daily_momentum = EquityQuery('and', [
365
EquityQuery('gt', ['percentchange', 5]), # Up 5%+ today
366
EquityQuery('gt', ['volume', 1000000]), # High volume
367
EquityQuery('gte', ['marketcap', 1000000000]) # Large enough
368
])
369
370
# Execute momentum screens
371
if '1day' in timeframes:
372
momentum_results['daily'] = screen(daily_momentum,
373
size=50,
374
sortField='percentchange')
375
376
# Note: Weekly and monthly momentum would require additional data
377
# This demonstrates the pattern for extending to multiple timeframes
378
379
return momentum_results
380
381
# Usage
382
momentum_stocks = momentum_screen_strategy(['1day'])
383
daily_momentum = momentum_stocks['daily']['finance']['result'][0]['quotes']
384
385
print("Top Daily Momentum Stocks:")
386
for stock in daily_momentum[:10]:
387
print(f"{stock['symbol']}: {stock['regularMarketChangePercent']:+.2f}% "
388
f"(Vol: {stock['volume']:,})")
389
```
390
391
### Value Investing Screen
392
393
```python
394
def value_investing_screen(max_pe=15, max_pb=2.0, min_roe=10):
395
"""Screen for value investment opportunities."""
396
397
value_criteria = EquityQuery('and', [
398
EquityQuery('lt', ['pe', max_pe]), # Low P/E ratio
399
EquityQuery('lt', ['pb', max_pb]), # Low price-to-book
400
EquityQuery('gt', ['roe', min_roe]), # Good return on equity
401
EquityQuery('gte', ['marketcap', 1000000000]), # Minimum size
402
EquityQuery('gt', ['volume', 200000]) # Adequate liquidity
403
])
404
405
value_results = screen(value_criteria,
406
size=75,
407
sortField='pe', # Sort by P/E ratio
408
sortAsc=True) # Lowest P/E first
409
410
return value_results
411
412
# Usage
413
value_opportunities = value_investing_screen(max_pe=12, max_pb=1.5, min_roe=15)
414
value_stocks = value_opportunities['finance']['result'][0]['quotes']
415
416
print("Value Investment Opportunities:")
417
for stock in value_stocks[:10]:
418
pe_ratio = stock.get('trailingPE', 'N/A')
419
print(f"{stock['symbol']}: P/E={pe_ratio}, Sector={stock.get('sector', 'N/A')}")
420
```
421
422
### Fund Screening Strategies
423
424
```python
425
def low_cost_index_fund_screen(max_expense=0.5, min_assets=1000000000):
426
"""Screen for low-cost index funds."""
427
428
index_fund_criteria = FundQuery('and', [
429
FundQuery('lt', ['expenserate', max_expense]), # Low expense ratio
430
FundQuery('gte', ['totalassets', min_assets]), # Adequate size
431
FundQuery('eq', ['fundtype', 'ETF']), # ETFs preferred
432
FundQuery('gt', ['avgvolume', 100000]) # Good liquidity
433
])
434
435
results = screen(index_fund_criteria,
436
size=50,
437
sortField='expenserate',
438
sortAsc=True) # Lowest expenses first
439
440
return results
441
442
def high_yield_fund_screen(min_yield=4.0, max_expense=1.0):
443
"""Screen for high-yield income funds."""
444
445
yield_criteria = FundQuery('and', [
446
FundQuery('gt', ['yield', min_yield]), # High yield
447
FundQuery('lt', ['expenserate', max_expense]), # Reasonable expenses
448
FundQuery('gte', ['totalassets', 500000000]) # Minimum fund size
449
])
450
451
results = screen(yield_criteria,
452
size=30,
453
sortField='yield',
454
sortAsc=False) # Highest yield first
455
456
return results
457
458
# Usage
459
low_cost_etfs = low_cost_index_fund_screen(max_expense=0.25, min_assets=5000000000)
460
high_yield_funds = high_yield_fund_screen(min_yield=5.0, max_expense=0.75)
461
```
462
463
### Sector-Specific Screening
464
465
```python
466
def sector_leader_screen(sector, performance_threshold=10):
467
"""Screen for leaders within a specific sector."""
468
469
sector_leaders = EquityQuery('and', [
470
EquityQuery('eq', ['sector', sector]), # Specific sector
471
EquityQuery('gt', ['percentchange', performance_threshold]), # Strong performance
472
EquityQuery('gt', ['marketcap', 1000000000]), # Large cap preferred
473
EquityQuery('gt', ['volume', 500000]), # Good liquidity
474
EquityQuery('lt', ['pe', 30]) # Not overvalued
475
])
476
477
results = screen(sector_leaders,
478
size=25,
479
sortField='marketcap',
480
sortAsc=False) # Largest companies first
481
482
return results
483
484
def emerging_sector_screen(sectors, min_growth=20):
485
"""Screen for emerging opportunities across multiple sectors."""
486
487
# Create sector filter
488
if len(sectors) == 1:
489
sector_filter = EquityQuery('eq', ['sector', sectors[0]])
490
else:
491
sector_queries = [EquityQuery('eq', ['sector', s]) for s in sectors]
492
sector_filter = EquityQuery('or', sector_queries)
493
494
emerging_criteria = EquityQuery('and', [
495
sector_filter, # Target sectors
496
EquityQuery('gt', ['percentchange', min_growth]), # High growth
497
EquityQuery('btwn', ['marketcap', [100000000, 10000000000]]), # Mid-cap range
498
EquityQuery('gt', ['volume', 250000]) # Adequate volume
499
])
500
501
results = screen(emerging_criteria,
502
size=40,
503
sortField='percentchange',
504
sortAsc=False)
505
506
return results
507
508
# Usage
509
tech_leaders = sector_leader_screen('Technology', performance_threshold=5)
510
clean_energy_emerging = emerging_sector_screen(['Utilities', 'Materials'], min_growth=15)
511
```
512
513
## Screen Result Processing and Analysis
514
515
### Result Analysis and Ranking
516
517
```python
518
def analyze_screen_results(screen_results, additional_metrics=True):
519
"""Analyze and enhance screening results with additional metrics."""
520
521
quotes = screen_results['finance']['result'][0]['quotes']
522
enhanced_results = []
523
524
for quote in quotes:
525
symbol = quote['symbol']
526
527
# Basic data from screen
528
result = {
529
'symbol': symbol,
530
'name': quote.get('shortName', ''),
531
'sector': quote.get('sector', ''),
532
'price': quote.get('regularMarketPrice', 0),
533
'change_percent': quote.get('regularMarketChangePercent', 0),
534
'volume': quote.get('volume', 0),
535
'market_cap': quote.get('marketCap', 0)
536
}
537
538
# Add additional metrics if requested
539
if additional_metrics:
540
try:
541
ticker = yf.Ticker(symbol)
542
info = ticker.info
543
544
result.update({
545
'pe_ratio': info.get('trailingPE', None),
546
'pb_ratio': info.get('priceToBook', None),
547
'dividend_yield': info.get('dividendYield', 0) * 100 if info.get('dividendYield') else 0,
548
'profit_margin': info.get('profitMargins', 0) * 100 if info.get('profitMargins') else 0,
549
'debt_to_equity': info.get('debtToEquity', None)
550
})
551
except:
552
# Handle cases where additional data isn't available
553
pass
554
555
enhanced_results.append(result)
556
557
return enhanced_results
558
559
# Usage
560
screen_result = screen("day_gainers", size=20)
561
analyzed_results = analyze_screen_results(screen_result, additional_metrics=True)
562
563
for stock in analyzed_results[:5]:
564
print(f"{stock['symbol']}: {stock['change_percent']:+.2f}% "
565
f"P/E={stock.get('pe_ratio', 'N/A')} "
566
f"Yield={stock.get('dividend_yield', 0):.1f}%")
567
```
568
569
### Portfolio Construction from Screens
570
571
```python
572
def build_portfolio_from_screens(screen_configs, max_positions=20,
573
max_sector_weight=0.3):
574
"""Build a diversified portfolio from multiple screening strategies."""
575
576
all_candidates = []
577
578
# Execute multiple screens
579
for config in screen_configs:
580
screen_name = config['name']
581
query = config['query']
582
weight = config.get('weight', 1.0)
583
584
results = screen(query, size=config.get('size', 25))
585
quotes = results['finance']['result'][0]['quotes']
586
587
for quote in quotes:
588
all_candidates.append({
589
'symbol': quote['symbol'],
590
'name': quote.get('shortName', ''),
591
'sector': quote.get('sector', ''),
592
'screen_source': screen_name,
593
'weight': weight,
594
'change_percent': quote.get('regularMarketChangePercent', 0),
595
'market_cap': quote.get('marketCap', 0)
596
})
597
598
# Remove duplicates (keep highest weighted occurrence)
599
unique_candidates = {}
600
for candidate in all_candidates:
601
symbol = candidate['symbol']
602
if symbol not in unique_candidates or candidate['weight'] > unique_candidates[symbol]['weight']:
603
unique_candidates[symbol] = candidate
604
605
# Sort by weighted score
606
candidates = list(unique_candidates.values())
607
candidates.sort(key=lambda x: x['change_percent'] * x['weight'], reverse=True)
608
609
# Build diversified portfolio
610
portfolio = []
611
sector_weights = {}
612
613
for candidate in candidates:
614
if len(portfolio) >= max_positions:
615
break
616
617
sector = candidate['sector']
618
current_sector_weight = sector_weights.get(sector, 0)
619
620
# Check sector concentration limits
621
if current_sector_weight < max_sector_weight:
622
portfolio.append(candidate)
623
sector_weights[sector] = current_sector_weight + (1.0 / max_positions)
624
625
return portfolio
626
627
# Usage
628
portfolio_screens = [
629
{
630
'name': 'momentum',
631
'query': EquityQuery('and', [
632
EquityQuery('gt', ['percentchange', 3]),
633
EquityQuery('gt', ['volume', 1000000])
634
]),
635
'weight': 1.5,
636
'size': 30
637
},
638
{
639
'name': 'value',
640
'query': EquityQuery('and', [
641
EquityQuery('lt', ['pe', 20]),
642
EquityQuery('gt', ['roe', 10])
643
]),
644
'weight': 1.0,
645
'size': 25
646
}
647
]
648
649
diversified_portfolio = build_portfolio_from_screens(portfolio_screens,
650
max_positions=15,
651
max_sector_weight=0.25)
652
653
print("Constructed Portfolio:")
654
for i, holding in enumerate(diversified_portfolio, 1):
655
print(f"{i:2d}. {holding['symbol']} ({holding['sector']}) - "
656
f"Source: {holding['screen_source']}")
657
```