trading-platform/docs/02-definicion-modulos/OQI-006-ml-signals/requerimientos/RF-ML-005-notificaciones.md
rckrdmrd a7cca885f0 feat: Major platform documentation and architecture updates
Changes include:
- Updated architecture documentation
- Enhanced module definitions (OQI-001 to OQI-008)
- ML integration documentation updates
- Trading strategies documentation
- Orchestration and inventory updates
- Docker configuration updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 05:33:35 -06:00

449 lines
12 KiB
Markdown

---
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