64 lines
2.5 KiB
Python
64 lines
2.5 KiB
Python
from __future__ import annotations
|
|
from PyQt6.QtCore import QThread, pyqtSignal
|
|
from core.market_data import get_quote
|
|
from utils.notifications import send_toast
|
|
|
|
|
|
class AlertWorker(QThread):
|
|
alert_triggered = pyqtSignal(str, str, str) # symbol, type, message
|
|
check_complete = pyqtSignal()
|
|
|
|
def __init__(self, interval_seconds: int = 300):
|
|
super().__init__()
|
|
self._interval = interval_seconds
|
|
self._running = False
|
|
|
|
def run(self):
|
|
self._running = True
|
|
while self._running:
|
|
self._check_alerts()
|
|
self.check_complete.emit()
|
|
for _ in range(self._interval * 10):
|
|
if not self._running:
|
|
return
|
|
self.msleep(100)
|
|
|
|
def stop(self):
|
|
self._running = False
|
|
|
|
def _check_alerts(self):
|
|
from db.database import get_session
|
|
from db.models import Alert
|
|
from datetime import datetime
|
|
|
|
with get_session() as session:
|
|
active = session.query(Alert).filter_by(is_active=True).all()
|
|
for alert in active:
|
|
try:
|
|
quote = get_quote(alert.symbol, force=True)
|
|
price = quote.get("price", 0.0)
|
|
volume = quote.get("volume", 0)
|
|
triggered = False
|
|
message = ""
|
|
|
|
if alert.alert_type == "price_above" and price >= alert.target_value:
|
|
triggered = True
|
|
message = f"{alert.symbol} hit ${price:.2f} (above ${alert.target_value:.2f})"
|
|
elif alert.alert_type == "price_below" and price <= alert.target_value:
|
|
triggered = True
|
|
message = f"{alert.symbol} hit ${price:.2f} (below ${alert.target_value:.2f})"
|
|
elif alert.alert_type == "volume_spike":
|
|
avg_vol = quote.get("avg_volume") or (volume / 1.5)
|
|
if avg_vol and volume >= avg_vol * alert.target_value:
|
|
triggered = True
|
|
message = f"{alert.symbol} volume spike: {volume:,} ({alert.target_value:.1f}x avg)"
|
|
|
|
if triggered:
|
|
alert.is_active = False
|
|
alert.triggered_at = datetime.utcnow()
|
|
session.commit()
|
|
send_toast("StockMind Alert", message)
|
|
self.alert_triggered.emit(alert.symbol, alert.alert_type, message)
|
|
except Exception:
|
|
continue
|