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

12 KiB

id title type status priority epic project version created_date updated_date
RF-ML-005 Notificaciones y Alertas de Senales Requirement Done Alta OQI-006 trading-platform 1.0.0 2025-12-05 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:

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:

{
  "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:

{
  "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:

{
  "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:

{
  "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
filters object Filtros de señales
quiet_hours object Horarios de silencio No
max_notifications_per_hour number Rate limit No

Datos de Salida

Obtener Notificaciones

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

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

# 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

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

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


Creado por: Requirements-Analyst Fecha: 2025-12-05 Última actualización: 2025-12-05