add Telegram alerts: qualified setups, S/R proximity, score drops, daily digest
Closes the action loop — instead of polling the dashboard, the platform pushes actionable signals to Telegram. New hourly 'alerts' job dispatches four toggleable triggers, deduped via a new alert_log table (cooldown-based for qualified/S-R/digest, watermark-based for score deterioration). Admin → Settings gains a Telegram panel (write-only bot token, chat ID, per-trigger toggles, Send Test). Credentials follow DB > env precedence (TELEGRAM_BOT_TOKEN / _CHAT_ID). Backend: alert_service + AlertLog model + migration 005, scheduler job, admin endpoints/schema. Frontend: AlertSettings panel, hooks, api, types. Deploy: run alembic upgrade (new alert_log table). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ from app.dependencies import get_db, require_admin
|
||||
from app.models.user import User
|
||||
from app.schemas.admin import (
|
||||
ActivationConfigUpdate,
|
||||
AlertConfigUpdate,
|
||||
CreateUserRequest,
|
||||
DataCleanupRequest,
|
||||
JobToggle,
|
||||
@@ -24,6 +25,7 @@ from app.schemas.admin import (
|
||||
)
|
||||
from app.schemas.common import APIEnvelope
|
||||
from app.services import admin_service
|
||||
from app.services import alert_service
|
||||
from app.services import sentiment_provider_service
|
||||
from app.services import ticker_universe_service
|
||||
|
||||
@@ -210,6 +212,37 @@ async def test_sentiment_settings(
|
||||
return APIEnvelope(status="success", data=result)
|
||||
|
||||
|
||||
@router.get("/admin/settings/alerts", response_model=APIEnvelope)
|
||||
async def get_alert_settings(
|
||||
_admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
config = await alert_service.get_alert_config(db)
|
||||
return APIEnvelope(status="success", data=config)
|
||||
|
||||
|
||||
@router.put("/admin/settings/alerts", response_model=APIEnvelope)
|
||||
async def update_alert_settings(
|
||||
body: AlertConfigUpdate,
|
||||
_admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
config = await alert_service.update_alert_config(
|
||||
db, **body.model_dump(exclude_unset=True)
|
||||
)
|
||||
return APIEnvelope(status="success", data=config)
|
||||
|
||||
|
||||
@router.post("/admin/settings/alerts/test", response_model=APIEnvelope)
|
||||
async def test_alert_settings(
|
||||
_admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Send a test Telegram message with the current config."""
|
||||
result = await alert_service.send_test_alert(db)
|
||||
return APIEnvelope(status="success", data=result)
|
||||
|
||||
|
||||
@router.get("/admin/settings/ticker-universe", response_model=APIEnvelope)
|
||||
async def get_ticker_universe_setting(
|
||||
_admin: User = Depends(require_admin),
|
||||
|
||||
Reference in New Issue
Block a user