0
# Alerts & Screening
1
2
Price alert management and stock screening capabilities for market discovery and automated monitoring of price movements and market conditions.
3
4
## Capabilities
5
6
### Price Alerts Management
7
8
Create, manage, and remove price-based alerts for securities.
9
10
```python { .api }
11
def alerts_list(self):
12
"""
13
Get list of all active alerts.
14
15
Returns:
16
list: List of alert objects containing:
17
- alertId: Unique alert identifier
18
- tickerId: Security ticker ID
19
- tickerSymbol: Stock symbol
20
- alertType: Type of alert (price, smart, etc.)
21
- triggerValue: Alert trigger value
22
- isActive: Alert status
23
- createTime: Alert creation timestamp
24
25
Returns None if no alerts or request fails.
26
"""
27
28
def alerts_add(self, stock=None, frequency=1, interval=1, priceRules=[], smartRules=[]):
29
"""
30
Add price or smart alerts for a security.
31
32
Parameters:
33
- stock (str, optional): Stock symbol to set alert for
34
- frequency (int): Alert frequency setting
35
- interval (int): Alert interval setting
36
- priceRules (list): List of price rule objects containing:
37
- type: Rule type ('PRICE_UP', 'PRICE_DOWN', 'PRICE_CROSS_UP', 'PRICE_CROSS_DOWN')
38
- value: Trigger price or percentage
39
- operator: Comparison operator ('>=', '<=', '>', '<')
40
- smartRules (list): List of smart rule objects for advanced alerts
41
42
Returns:
43
dict: Alert creation result with alert ID
44
"""
45
46
def alerts_remove(self, alert=None, priceAlert=True, smartAlert=True):
47
"""
48
Remove existing alerts.
49
50
Parameters:
51
- alert (dict, optional): Specific alert object to remove
52
- priceAlert (bool): Remove price alerts
53
- smartAlert (bool): Remove smart alerts
54
55
Returns:
56
dict: Removal result
57
"""
58
```
59
60
Usage examples:
61
62
```python
63
# Get current alerts
64
alerts = wb.alerts_list()
65
if alerts:
66
print(f"Active alerts: {len(alerts)}")
67
for alert in alerts:
68
print(f"Alert for {alert['tickerSymbol']}: {alert['alertType']}")
69
else:
70
print("No active alerts")
71
72
# Add price alert for Apple above $160
73
price_rules = [{
74
'type': 'PRICE_CROSS_UP',
75
'value': 160.0,
76
'operator': '>='
77
}]
78
79
alert_result = wb.alerts_add(
80
stock='AAPL',
81
frequency=1,
82
interval=1,
83
priceRules=price_rules
84
)
85
86
print(f"Price alert created: {alert_result}")
87
88
# Add percentage change alert
89
pct_rules = [{
90
'type': 'PRICE_UP',
91
'value': 5.0, # 5% increase
92
'operator': '>='
93
}]
94
95
wb.alerts_add(
96
stock='TSLA',
97
priceRules=pct_rules
98
)
99
100
# Remove specific alert
101
if alerts and len(alerts) > 0:
102
wb.alerts_remove(alert=alerts[0])
103
104
# Remove all price alerts for cleanup
105
wb.alerts_remove(priceAlert=True, smartAlert=False)
106
```
107
108
### Stock Screening
109
110
Discover stocks using built-in screening tools with customizable filters.
111
112
```python { .api }
113
def run_screener(self, region=None, price_lte=None, price_gte=None, pct_chg_gte=None, pct_chg_lte=None, sort=None, volume_gte=None, market_cap_gte=None, market_cap_lte=None, pe_ratio_lte=None, dividend_yield_gte=None, ...):
114
"""
115
Run stock screener with multiple filter criteria.
116
117
Parameters:
118
- region (str, optional): Market region ('US', 'HK', etc.)
119
- price_lte (float, optional): Maximum stock price
120
- price_gte (float, optional): Minimum stock price
121
- pct_chg_gte (float, optional): Minimum percentage change
122
- pct_chg_lte (float, optional): Maximum percentage change
123
- sort (str, optional): Sort criteria ('price_asc', 'price_desc', 'pct_chg_desc', 'volume_desc')
124
- volume_gte (int, optional): Minimum trading volume
125
- market_cap_gte (float, optional): Minimum market capitalization
126
- market_cap_lte (float, optional): Maximum market capitalization
127
- pe_ratio_lte (float, optional): Maximum P/E ratio
128
- dividend_yield_gte (float, optional): Minimum dividend yield
129
- ... (additional screening parameters available)
130
131
Returns:
132
list: List of stocks matching criteria containing:
133
- symbol: Stock symbol
134
- name: Company name
135
- price: Current price
136
- change: Price change
137
- changeRatio: Percentage change
138
- volume: Trading volume
139
- marketCap: Market capitalization
140
- peRatio: Price-to-earnings ratio
141
- dividendYield: Dividend yield
142
"""
143
```
144
145
Usage examples:
146
147
```python
148
# Screen for stocks under $50 with >5% gain today
149
growth_stocks = wb.run_screener(
150
region='US',
151
price_lte=50.0,
152
pct_chg_gte=5.0,
153
volume_gte=1000000, # Minimum 1M volume
154
sort='pct_chg_desc'
155
)
156
157
print(f"Found {len(growth_stocks)} growth stocks:")
158
for stock in growth_stocks[:10]: # Top 10
159
print(f"{stock['symbol']}: ${stock['price']} ({stock['changeRatio']:+.2f}%)")
160
161
# Screen for dividend stocks
162
dividend_stocks = wb.run_screener(
163
region='US',
164
dividend_yield_gte=3.0, # >3% dividend yield
165
market_cap_gte=1000000000, # >$1B market cap
166
price_gte=10.0, # >$10 price
167
sort='dividend_yield_desc'
168
)
169
170
# Screen for value stocks
171
value_stocks = wb.run_screener(
172
region='US',
173
pe_ratio_lte=15.0, # P/E ratio under 15
174
market_cap_gte=500000000, # >$500M market cap
175
pct_chg_gte=-2.0, # Not down more than 2%
176
sort='pe_ratio_asc'
177
)
178
179
# Screen for high volume breakouts
180
breakout_stocks = wb.run_screener(
181
region='US',
182
pct_chg_gte=10.0, # >10% gain
183
volume_gte=5000000, # >5M volume
184
price_gte=5.0, # >$5 price
185
sort='volume_desc'
186
)
187
```
188
189
### Market Discovery
190
191
Find active market movers and trending stocks.
192
193
```python { .api }
194
def active_gainer_loser(self, direction='gainer', rank_type='afterMarket', count=50):
195
"""
196
Get lists of active gainers, losers, or most active stocks.
197
198
Parameters:
199
- direction (str): List type - 'gainer', 'loser', or 'active'
200
- rank_type (str): Market session - 'afterMarket', 'preMarket', 'regular'
201
- count (int): Number of results to return (max 100)
202
203
Returns:
204
list: List of stocks with performance data containing:
205
- symbol: Stock symbol
206
- name: Company name
207
- price: Current price
208
- change: Price change
209
- changeRatio: Percentage change
210
- volume: Trading volume
211
- marketCap: Market capitalization
212
"""
213
```
214
215
Usage examples:
216
217
```python
218
# Get top gainers in regular trading
219
gainers = wb.active_gainer_loser(
220
direction='gainer',
221
rank_type='regular',
222
count=20
223
)
224
225
print("Top Gainers:")
226
for stock in gainers:
227
print(f"{stock['symbol']}: ${stock['price']} ({stock['changeRatio']:+.2f}%)")
228
229
# Get biggest losers
230
losers = wb.active_gainer_loser(
231
direction='loser',
232
rank_type='regular',
233
count=15
234
)
235
236
print("\nBiggest Losers:")
237
for stock in losers:
238
print(f"{stock['symbol']}: ${stock['price']} ({stock['changeRatio']:+.2f}%)")
239
240
# Get most active stocks by volume
241
most_active = wb.active_gainer_loser(
242
direction='active',
243
rank_type='regular',
244
count=25
245
)
246
247
print("\nMost Active:")
248
for stock in most_active:
249
print(f"{stock['symbol']}: Volume {stock['volume']:,}")
250
251
# Get pre-market movers
252
premarket_gainers = wb.active_gainer_loser(
253
direction='gainer',
254
rank_type='preMarket',
255
count=10
256
)
257
258
# Get after-hours movers
259
afterhours_gainers = wb.active_gainer_loser(
260
direction='gainer',
261
rank_type='afterMarket',
262
count=10
263
)
264
```
265
266
## Advanced Screening Strategies
267
268
### Multi-Criteria Stock Scanner
269
270
```python
271
class StockScanner:
272
def __init__(self, webull_client):
273
self.wb = webull_client
274
275
def momentum_scanner(self):
276
"""Scan for momentum stocks with strong price and volume action."""
277
278
results = self.wb.run_screener(
279
region='US',
280
pct_chg_gte=5.0, # >5% gain
281
volume_gte=2000000, # >2M volume
282
price_gte=10.0, # >$10 price
283
market_cap_gte=100000000, # >$100M market cap
284
sort='pct_chg_desc'
285
)
286
287
print(f"Momentum Scanner Results ({len(results)} stocks):")
288
for stock in results[:10]:
289
print(f"{stock['symbol']}: ${stock['price']} ({stock['changeRatio']:+.2f}%) Vol: {stock['volume']:,}")
290
291
return results
292
293
def value_scanner(self):
294
"""Scan for undervalued stocks with good fundamentals."""
295
296
results = self.wb.run_screener(
297
region='US',
298
pe_ratio_lte=20.0, # P/E under 20
299
market_cap_gte=1000000000, # >$1B market cap
300
price_gte=5.0, # >$5 price
301
dividend_yield_gte=1.0, # >1% dividend
302
pct_chg_gte=-5.0, # Not down more than 5%
303
sort='pe_ratio_asc'
304
)
305
306
print(f"Value Scanner Results ({len(results)} stocks):")
307
for stock in results[:10]:
308
pe = stock.get('peRatio', 'N/A')
309
div_yield = stock.get('dividendYield', 'N/A')
310
print(f"{stock['symbol']}: ${stock['price']} P/E: {pe} Div: {div_yield}%")
311
312
return results
313
314
def breakout_scanner(self):
315
"""Scan for stocks breaking out with high volume."""
316
317
results = self.wb.run_screener(
318
region='US',
319
pct_chg_gte=8.0, # >8% gain
320
volume_gte=3000000, # >3M volume
321
price_gte=15.0, # >$15 price
322
sort='volume_desc'
323
)
324
325
# Filter for stocks near day high
326
breakout_candidates = []
327
for stock in results:
328
try:
329
# Get more detailed quote data
330
quote = self.wb.get_quote(stock=stock['symbol'])
331
high = float(quote.get('high', 0))
332
current = float(quote.get('close', 0))
333
334
# Check if current price is within 2% of day high
335
if high > 0 and (current / high) >= 0.98:
336
breakout_candidates.append({
337
**stock,
338
'nearHigh': (current / high) * 100
339
})
340
341
except Exception as e:
342
continue
343
344
print(f"Breakout Scanner Results ({len(breakout_candidates)} stocks):")
345
for stock in breakout_candidates[:10]:
346
print(f"{stock['symbol']}: ${stock['price']} ({stock['changeRatio']:+.2f}%) Near High: {stock['nearHigh']:.1f}%")
347
348
return breakout_candidates
349
350
def earnings_scanner(self):
351
"""Scan for stocks with upcoming earnings."""
352
353
# Get stocks with recent high volume (potential earnings plays)
354
results = self.wb.run_screener(
355
region='US',
356
volume_gte=5000000, # >5M volume (unusual activity)
357
price_gte=20.0, # >$20 price
358
market_cap_gte=1000000000, # >$1B market cap
359
sort='volume_desc'
360
)
361
362
print(f"Potential Earnings Plays ({len(results)} stocks):")
363
for stock in results[:15]:
364
print(f"{stock['symbol']}: Vol {stock['volume']:,} ({stock['changeRatio']:+.2f}%)")
365
366
return results
367
368
# Usage
369
scanner = StockScanner(wb)
370
371
# Run different scans
372
momentum_stocks = scanner.momentum_scanner()
373
value_stocks = scanner.value_scanner()
374
breakout_stocks = scanner.breakout_scanner()
375
earnings_plays = scanner.earnings_scanner()
376
```
377
378
### Alert Management System
379
380
```python
381
class AlertManager:
382
def __init__(self, webull_client):
383
self.wb = webull_client
384
self.active_alerts = {}
385
386
def setup_watchlist_alerts(self, symbols, alert_type='percentage', threshold=5.0):
387
"""Set up alerts for a list of symbols."""
388
389
for symbol in symbols:
390
try:
391
if alert_type == 'percentage':
392
# Set up percentage move alerts
393
up_rules = [{
394
'type': 'PRICE_UP',
395
'value': threshold,
396
'operator': '>='
397
}]
398
399
down_rules = [{
400
'type': 'PRICE_DOWN',
401
'value': -threshold,
402
'operator': '<='
403
}]
404
405
# Create upward move alert
406
up_alert = self.wb.alerts_add(
407
stock=symbol,
408
priceRules=up_rules
409
)
410
411
# Create downward move alert
412
down_alert = self.wb.alerts_add(
413
stock=symbol,
414
priceRules=down_rules
415
)
416
417
self.active_alerts[symbol] = {
418
'up_alert': up_alert,
419
'down_alert': down_alert,
420
'threshold': threshold
421
}
422
423
print(f"Alerts set for {symbol}: ±{threshold}%")
424
425
elif alert_type == 'price':
426
# Set price-based alerts
427
quote = self.wb.get_quote(stock=symbol)
428
current_price = float(quote['close'])
429
430
upper_price = current_price * (1 + threshold/100)
431
lower_price = current_price * (1 - threshold/100)
432
433
up_rules = [{
434
'type': 'PRICE_CROSS_UP',
435
'value': upper_price,
436
'operator': '>='
437
}]
438
439
down_rules = [{
440
'type': 'PRICE_CROSS_DOWN',
441
'value': lower_price,
442
'operator': '<='
443
}]
444
445
up_alert = self.wb.alerts_add(stock=symbol, priceRules=up_rules)
446
down_alert = self.wb.alerts_add(stock=symbol, priceRules=down_rules)
447
448
self.active_alerts[symbol] = {
449
'up_alert': up_alert,
450
'down_alert': down_alert,
451
'upper_price': upper_price,
452
'lower_price': lower_price
453
}
454
455
print(f"Price alerts set for {symbol}: ${lower_price:.2f} - ${upper_price:.2f}")
456
457
except Exception as e:
458
print(f"Failed to set alerts for {symbol}: {e}")
459
460
def check_alert_status(self):
461
"""Check status of all active alerts."""
462
463
all_alerts = self.wb.alerts_list()
464
if not all_alerts:
465
print("No active alerts")
466
return
467
468
print(f"Active Alerts ({len(all_alerts)}):")
469
for alert in all_alerts:
470
symbol = alert.get('tickerSymbol', 'Unknown')
471
alert_type = alert.get('alertType', 'Unknown')
472
is_active = alert.get('isActive', False)
473
status = "🟢 Active" if is_active else "⚪ Inactive"
474
475
print(f" {symbol}: {alert_type} - {status}")
476
477
def cleanup_alerts(self, symbols=None):
478
"""Remove alerts for specific symbols or all alerts."""
479
480
if symbols:
481
# Remove alerts for specific symbols
482
all_alerts = self.wb.alerts_list()
483
if all_alerts:
484
for alert in all_alerts:
485
if alert.get('tickerSymbol') in symbols:
486
self.wb.alerts_remove(alert=alert)
487
print(f"Removed alert for {alert.get('tickerSymbol')}")
488
else:
489
# Remove all alerts
490
self.wb.alerts_remove(priceAlert=True, smartAlert=True)
491
print("All alerts removed")
492
493
# Clear tracking
494
if symbols:
495
for symbol in symbols:
496
self.active_alerts.pop(symbol, None)
497
else:
498
self.active_alerts.clear()
499
500
# Usage
501
alert_mgr = AlertManager(wb)
502
503
# Set up alerts for portfolio stocks
504
portfolio_symbols = ['AAPL', 'TSLA', 'MSFT', 'NVDA', 'AMD']
505
alert_mgr.setup_watchlist_alerts(portfolio_symbols, alert_type='percentage', threshold=3.0)
506
507
# Check alert status
508
alert_mgr.check_alert_status()
509
510
# Later, clean up alerts
511
# alert_mgr.cleanup_alerts(symbols=['AAPL', 'TSLA'])
512
```
513
514
## Complete Screening & Alert Example
515
516
```python
517
from webull import webull
518
import time
519
520
def market_discovery_system():
521
"""Complete market discovery and alert system."""
522
523
wb = webull()
524
wb.login('your_email@example.com', 'your_password')
525
526
# Initialize scanner and alert manager
527
scanner = StockScanner(wb)
528
alert_mgr = AlertManager(wb)
529
530
print("=== MARKET DISCOVERY SYSTEM ===")
531
532
# 1. Run multiple scans
533
print("\n1. Running Market Scans...")
534
535
momentum_stocks = scanner.momentum_scanner()
536
value_stocks = scanner.value_scanner()
537
breakout_stocks = scanner.breakout_scanner()
538
539
# 2. Combine results and find interesting opportunities
540
print("\n2. Analyzing Opportunities...")
541
542
all_candidates = []
543
544
# Add momentum stocks with score
545
for stock in momentum_stocks[:5]:
546
all_candidates.append({
547
**stock,
548
'strategy': 'momentum',
549
'score': stock['changeRatio'] # Use % change as score
550
})
551
552
# Add value stocks with score
553
for stock in value_stocks[:5]:
554
pe_ratio = stock.get('peRatio', 999)
555
score = 100 / pe_ratio if pe_ratio > 0 else 0 # Inverse P/E as score
556
all_candidates.append({
557
**stock,
558
'strategy': 'value',
559
'score': score
560
})
561
562
# Add breakout stocks with score
563
for stock in breakout_stocks[:3]:
564
all_candidates.append({
565
**stock,
566
'strategy': 'breakout',
567
'score': stock['changeRatio'] + (stock['volume'] / 1000000) # Change + volume score
568
})
569
570
# 3. Rank all opportunities
571
all_candidates.sort(key=lambda x: x['score'], reverse=True)
572
573
print("\nTop Opportunities:")
574
for i, stock in enumerate(all_candidates[:10]):
575
print(f"{i+1}. {stock['symbol']} ({stock['strategy']}): Score {stock['score']:.2f}")
576
print(f" ${stock['price']} ({stock['changeRatio']:+.2f}%) Vol: {stock['volume']:,}")
577
578
# 4. Set up alerts for top candidates
579
print("\n3. Setting Up Alerts...")
580
581
top_symbols = [stock['symbol'] for stock in all_candidates[:5]]
582
alert_mgr.setup_watchlist_alerts(top_symbols, alert_type='percentage', threshold=2.0)
583
584
# 5. Monitor for a period
585
print("\n4. Monitoring Market Activity...")
586
587
for minute in range(5): # Monitor for 5 minutes
588
print(f"\nMinute {minute + 1}:")
589
590
# Check current gainers/losers
591
current_gainers = wb.active_gainer_loser('gainer', 'regular', 5)
592
current_losers = wb.active_gainer_loser('loser', 'regular', 5)
593
594
print("Current Top Gainers:")
595
for stock in current_gainers:
596
print(f" {stock['symbol']}: {stock['changeRatio']:+.2f}%")
597
598
print("Current Top Losers:")
599
for stock in current_losers:
600
print(f" {stock['symbol']}: {stock['changeRatio']:+.2f}%")
601
602
time.sleep(60) # Wait 1 minute
603
604
# 6. Final report
605
print("\n5. Final Alert Status:")
606
alert_mgr.check_alert_status()
607
608
# Cleanup
609
print("\nCleaning up alerts...")
610
alert_mgr.cleanup_alerts()
611
612
# Run the system
613
if __name__ == "__main__":
614
market_discovery_system()
615
```
616
617
## Best Practices
618
619
### Alert Management
620
- Use reasonable thresholds to avoid alert fatigue
621
- Review and clean up alerts regularly
622
- Combine price and volume criteria for better signals
623
- Test alert triggers with paper trading first
624
625
### Stock Screening
626
- Use multiple criteria to filter results effectively
627
- Combine fundamental and technical filters
628
- Validate screener results with additional analysis
629
- Save and backtest successful screening strategies
630
631
### Risk Management
632
- Set up alerts for portfolio positions to monitor risk
633
- Use screening to find diversification opportunities
634
- Monitor sector concentration through screening
635
- Set up alerts for major market moves