or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

bulk-data.mdconfig-utils.mdindex.mdlive-streaming.mdmarket-sector.mdscreening.mdsearch-lookup.mdticker-data.md

screening.mddocs/

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

```