--- id: "RF-ML-005" title: "Notificaciones y Alertas de Senales" type: "Requirement" status: "Done" priority: "Alta" epic: "OQI-006" project: "trading-platform" version: "1.0.0" created_date: "2025-12-05" updated_date: "2026-01-04" --- # RF-ML-005: Notificaciones y Alertas de Señales **Versión:** 1.0.0 **Fecha:** 2025-12-05 **Épica:** OQI-006 - Señales ML y Predicciones **Prioridad:** P2 **Story Points:** 7 --- ## Descripción El sistema debe enviar notificaciones en tiempo real a los usuarios cuando se generen nuevas señales de trading de alta prioridad, cuando se alcancen niveles de TP/SL, o cuando ocurran eventos importantes en el modelo ML. --- ## Requisitos Funcionales ### RF-ML-005.1: Tipos de Notificaciones El sistema debe soportar los siguientes tipos de notificaciones: | Tipo | Prioridad | Descripción | |------|-----------|-------------| | **NEW_SIGNAL** | Alta | Nueva señal BUY/SELL generada | | **TP_HIT** | Media | Take Profit alcanzado | | **SL_HIT** | Alta | Stop Loss alcanzado | | **SIGNAL_EXPIRED** | Baja | Señal expiró sin ejecutarse | | **MODEL_RETRAINED** | Media | Modelo re-entrenado con nuevas métricas | | **LOW_CONFIDENCE** | Baja | Confianza del modelo cayó bajo umbral | ### RF-ML-005.2: Canales de Notificación El sistema debe soportar múltiples canales: **In-App (Prioridad 1):** - Notificaciones en la plataforma web - Badge counter en navbar - Panel de notificaciones con historial **Push Notifications (Prioridad 2):** - Web Push API para navegadores - Notificaciones desktop **Email (Prioridad 3):** - Resumen diario de señales - Alertas críticas (SL hit) **Webhook (Prioridad 4):** - Integración con Telegram/Discord - Webhooks personalizados ### RF-ML-005.3: Configuración de Alertas por Usuario Cada usuario debe poder configurar: ```typescript interface AlertPreferences { user_id: string; // Canales activos channels: { in_app: boolean; push: boolean; email: boolean; webhook?: string; // URL del webhook }; // Filtros de señales filters: { min_priority: 'HIGH' | 'MEDIUM' | 'LOW'; symbols: string[]; // Vacío = todos horizons: string[]; // Vacío = todos actions: ('BUY' | 'SELL')[]; // Vacío = ambos min_confidence: number; // 0-1 min_score: number; // 0-10 }; // Configuración de horarios quiet_hours?: { enabled: boolean; start_hour: number; // 0-23 end_hour: number; timezone: string; }; // Rate limiting max_notifications_per_hour?: number; } ``` ### RF-ML-005.4: Contenido de Notificaciones **NEW_SIGNAL - Nueva Señal:** ```json { "type": "NEW_SIGNAL", "timestamp": "2025-12-05T19:00:00.000Z", "priority": "HIGH", "title": "Nueva señal BUY para BTCUSDT", "message": "Señal de compra con confianza 75% - TP: $89,900 | SL: $89,150", "data": { "signal_id": "550e8400-e29b-41d4-a716-446655440000", "symbol": "BTCUSDT", "action": "BUY", "horizon": "scalping", "entry_price": 89400.00, "tp1": 89650.00, "tp2": 89775.00, "tp3": 89900.00, "stop_loss": 89150.00, "confidence": 0.75, "score": 8.5 }, "actions": [ { "label": "Ver Detalles", "url": "/signals/550e8400-e29b-41d4-a716-446655440000" }, { "label": "Ver Chart", "url": "/trading/BTCUSDT" } ] } ``` **TP_HIT - Take Profit Alcanzado:** ```json { "type": "TP_HIT", "timestamp": "2025-12-05T19:25:00.000Z", "priority": "MEDIUM", "title": "TP1 alcanzado - BTCUSDT", "message": "Tu señal alcanzó TP1 ($89,650) con ganancia de +0.28%", "data": { "signal_id": "550e8400-e29b-41d4-a716-446655440000", "symbol": "BTCUSDT", "tp_level": "TP1", "tp_price": 89650.00, "entry_price": 89400.00, "current_price": 89655.00, "gain_pct": 0.28 } } ``` **SL_HIT - Stop Loss Alcanzado:** ```json { "type": "SL_HIT", "timestamp": "2025-12-05T19:15:00.000Z", "priority": "HIGH", "title": "Stop Loss activado - BTCUSDT", "message": "Tu señal alcanzó SL ($89,150) con pérdida de -0.28%", "data": { "signal_id": "550e8400-e29b-41d4-a716-446655440000", "symbol": "BTCUSDT", "stop_loss": 89150.00, "entry_price": 89400.00, "current_price": 89145.00, "loss_pct": -0.28 } } ``` ### RF-ML-005.5: Notificaciones In-App El sistema debe: - Mostrar badge counter en el navbar con número de notificaciones no leídas - Panel de notificaciones accesible con click en icono campana - Listar notificaciones ordenadas por fecha (más recientes primero) - Marcar como leída al hacer click - Opción "Marcar todas como leídas" - Eliminar notificaciones antiguas (>30 días) **UI del Panel:** ``` ┌─────────────────────────────────────────┐ │ Notificaciones [x] │ ├─────────────────────────────────────────┤ │ [•] Nueva señal BUY - BTCUSDT │ │ Confianza 75% | Hace 5 min │ │ │ │ [•] TP1 alcanzado - ETHUSDT │ │ Ganancia +0.5% | Hace 15 min │ │ │ │ [ ] Modelo re-entrenado │ │ Nuevo MAE: 0.0012 | Hace 2h │ │ │ │ [ ] Señal expirada - BTCUSDT │ │ No ejecutada | Hace 5h │ ├─────────────────────────────────────────┤ │ [Marcar todas como leídas] │ │ [Ver todas las notificaciones] │ └─────────────────────────────────────────┘ ``` ### RF-ML-005.6: Rate Limiting y Anti-Spam El sistema debe: - Limitar a máximo 20 notificaciones por hora por usuario (configurable) - Agrupar notificaciones similares (ej: múltiples señales del mismo símbolo) - Respetar horarios de silencio (quiet hours) configurados por el usuario - No enviar notificaciones duplicadas **Agrupación de Notificaciones:** ```json { "type": "NEW_SIGNAL_BATCH", "timestamp": "2025-12-05T19:00:00.000Z", "title": "3 nuevas señales generadas", "message": "BTCUSDT (BUY), ETHUSDT (BUY), BNBUSDT (SELL)", "data": { "count": 3, "signals": [ { "symbol": "BTCUSDT", "action": "BUY" }, { "symbol": "ETHUSDT", "action": "BUY" }, { "symbol": "BNBUSDT", "action": "SELL" } ] } } ``` --- ## Datos de Entrada ### Crear/Actualizar Preferencias | Campo | Tipo | Descripción | Requerido | |-------|------|-------------|-----------| | channels | object | Canales activos | Sí | | filters | object | Filtros de señales | Sí | | quiet_hours | object | Horarios de silencio | No | | max_notifications_per_hour | number | Rate limit | No | --- ## Datos de Salida ### Obtener Notificaciones ```typescript interface Notification { id: string; user_id: string; type: NotificationType; priority: 'HIGH' | 'MEDIUM' | 'LOW'; title: string; message: string; data: any; actions?: NotificationAction[]; read: boolean; created_at: string; } interface NotificationList { notifications: Notification[]; unread_count: number; total_count: number; page: number; page_size: number; } ``` --- ## Reglas de Negocio 1. **Notificaciones Críticas:** SL_HIT siempre se envía, ignorando quiet hours 2. **Deduplicación:** Una señal genera solo 1 notificación NEW_SIGNAL 3. **Expiración:** Notificaciones mayores a 30 días se eliminan automáticamente 4. **Batch Processing:** Señales generadas en batch (cada 5 min) se agrupan 5. **Prioridad de Canales:** In-app > Push > Email > Webhook 6. **Límite de Webhook:** Máximo 3 webhooks por usuario --- ## Criterios de Aceptación ```gherkin Escenario: Recibir notificación de nueva señal HIGH DADO que el usuario tiene alertas activas Y min_priority = "HIGH" CUANDO se genera una señal BUY de prioridad HIGH ENTONCES recibe notificación in-app Y el badge counter incrementa en 1 Y la notificación incluye detalles de la señal Escenario: Respetar quiet hours DADO que el usuario configuró quiet_hours de 22:00 a 8:00 Y son las 23:30 CUANDO se genera una señal MEDIUM ENTONCES NO recibe notificación Y la notificación se almacena para verla después Escenario: Notificación de Stop Loss (crítica) DADO que el usuario configuró quiet_hours Y son las 2:00 AM CUANDO una señal alcanza el Stop Loss ENTONCES recibe notificación SL_HIT (ignora quiet hours) Y la prioridad es HIGH Escenario: Marcar notificación como leída DADO que el usuario tiene 5 notificaciones no leídas CUANDO hace click en una notificación ENTONCES se marca como leída (read = true) Y el unread_count disminuye a 4 Y navega a la página de detalles ``` --- ## Dependencias ### Técnicas: - **WebSocket:** Para notificaciones in-app en tiempo real - **Web Push API:** Para notificaciones push de navegador - **Email Service (SendGrid/AWS SES):** Para emails - **PostgreSQL:** Almacenamiento de notificaciones - **Redis:** Caché de preferencias y rate limiting ### Funcionales: - **RF-ML-002:** Generación de señales (trigger de notificaciones) - **RF-AUTH-005:** Sessions (identificar usuario activo) --- ## Notas Técnicas ### Arquitectura de Notificaciones ```python # apps/ml-services/src/notifications/notification_service.py class NotificationService: """ Servicio de notificaciones multi-canal """ async def send_notification( self, user_id: str, notification: Notification ): # 1. Obtener preferencias del usuario prefs = await self.get_user_preferences(user_id) # 2. Filtrar según preferencias if not self.should_send(notification, prefs): return # 3. Verificar quiet hours if self.is_quiet_hours(prefs) and notification.priority != 'HIGH': await self.queue_for_later(user_id, notification) return # 4. Verificar rate limit if await self.is_rate_limited(user_id): await self.queue_for_later(user_id, notification) return # 5. Enviar a canales activos tasks = [] if prefs.channels.in_app: tasks.append(self.send_in_app(user_id, notification)) if prefs.channels.push: tasks.append(self.send_push(user_id, notification)) if prefs.channels.email: tasks.append(self.send_email(user_id, notification)) if prefs.channels.webhook: tasks.append(self.send_webhook(prefs.channels.webhook, notification)) await asyncio.gather(*tasks) # 6. Guardar en base de datos await self.save_notification(user_id, notification) ``` ### Base de Datos - Tabla `notifications` ```sql CREATE TABLE notifications ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id), type VARCHAR(50) NOT NULL, priority VARCHAR(10) NOT NULL, title VARCHAR(200) NOT NULL, message TEXT NOT NULL, data JSONB, read BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT NOW(), INDEX idx_user_id (user_id), INDEX idx_created_at (created_at), INDEX idx_read (read) ); CREATE TABLE user_alert_preferences ( user_id UUID PRIMARY KEY REFERENCES users(id), channels JSONB NOT NULL, filters JSONB NOT NULL, quiet_hours JSONB, max_notifications_per_hour INTEGER DEFAULT 20, updated_at TIMESTAMP DEFAULT NOW() ); ``` ### WebSocket Event ```typescript // Cliente se suscribe a notificaciones socket.on('subscribe_notifications', (userId) => { // Join room personal socket.join(`notifications:${userId}`); }); // Servidor envía notificación io.to(`notifications:${userId}`).emit('new_notification', notification); ``` --- ## Referencias - [Web Push API](https://developer.mozilla.org/en-US/docs/Web/API/Push_API) - [SendGrid Email API](https://docs.sendgrid.com/api-reference/mail-send/mail-send) - [Socket.io Rooms](https://socket.io/docs/v4/rooms/) --- **Creado por:** Requirements-Analyst **Fecha:** 2025-12-05 **Última actualización:** 2025-12-05