# RF-ML-003: Indicadores Técnicos del ML

**Versión:** 1.0.0
**Fecha:** 2025-12-05
**Épica:** OQI-006 - Señales ML y Predicciones
**Prioridad:** P1
**Story Points:** 5

---

## Descripción

El sistema debe calcular y proporcionar indicadores técnicos avanzados utilizados como features del modelo ML. Estos indicadores deben estar disponibles tanto para el entrenamiento del modelo como para visualización en la interfaz de usuario.

---

## Requisitos Funcionales

### RF-ML-003.1: Indicadores de Volatilidad

El sistema debe calcular:

**Volatilidad Estándar:**
```python
volatility_N = std(returns[-N:])

Períodos: 5, 10, 20, 50
```

**Average True Range (ATR):**
```python
true_range = max(
    high - low,
    abs(high - close_prev),
    abs(low - close_prev)
)
atr_N = sma(true_range, N)

Períodos: 5, 10, 20, 50
```

**Bollinger Bands Position:**
```python
bb_middle = sma(close, 20)
bb_std = std(close, 20)
bb_upper = bb_middle + (2 * bb_std)
bb_lower = bb_middle - (2 * bb_std)

bb_position = (close - bb_lower) / (bb_upper - bb_lower)
// Rango: 0 (en banda inferior) a 1 (en banda superior)
```

### RF-ML-003.2: Indicadores de Momentum

El sistema debe calcular:

**Momentum Simple:**
```python
momentum_N = (close / close[-N]) - 1

Períodos: 5, 10, 20
```

**Rate of Change (ROC):**
```python
roc_N = ((close - close[-N]) / close[-N]) * 100

Períodos: 5, 10, 20
```

**Relative Strength Index (RSI):**
```python
# RSI de 14 períodos
gains = max(0, close - close_prev)
losses = max(0, close_prev - close)

avg_gain = ema(gains, 14)
avg_loss = ema(losses, 14)

rs = avg_gain / avg_loss
rsi_14 = 100 - (100 / (1 + rs))

// Rango: 0 (sobreventa extrema) a 100 (sobrecompra extrema)
// Zonas: < 30 sobreventa, > 70 sobrecompra
```

### RF-ML-003.3: Medias Móviles

El sistema debe calcular:

**Simple Moving Average (SMA):**
```python
sma_N = mean(close[-N:])

Períodos: 5, 10, 20, 50
```

**Exponential Moving Average (EMA):**
```python
multiplier = 2 / (N + 1)
ema_N = (close * multiplier) + (ema_prev * (1 - multiplier))

Períodos: 5, 10, 20, 50
```

**SMA Ratios:**
```python
sma_ratio_N = close / sma_N - 1

// Indica distancia del precio actual respecto a la SMA
// Positivo: precio sobre SMA (alcista)
// Negativo: precio bajo SMA (bajista)

Períodos: 5, 10, 20, 50
```

### RF-ML-003.4: MACD (Moving Average Convergence Divergence)

El sistema debe calcular:

```python
# MACD estándar (12, 26, 9)
ema_12 = ema(close, 12)
ema_26 = ema(close, 26)

macd_line = ema_12 - ema_26
macd_signal = ema(macd_line, 9)
macd_histogram = macd_line - macd_signal

// Interpretación:
// macd > signal: momento alcista
// macd < signal: momento bajista
// histogram > 0: aceleración alcista
// histogram < 0: aceleración bajista
```

### RF-ML-003.5: Indicadores de Volumen

El sistema debe calcular:

**Volume Ratio:**
```python
volume_sma = sma(volume, 20)
volume_ratio = volume / volume_sma

// Rango típico: 0.5 - 2.0
// > 1.5: volumen alto (confirmación de movimiento)
// < 0.7: volumen bajo (falta de interés)
```

### RF-ML-003.6: Indicadores de High/Low

El sistema debe calcular:

**High-Low Range Percentage:**
```python
hl_range_pct = (high - low) / close * 100

// Mide la volatilidad intravela
```

**Distance to High/Low:**
```python
high_distance = (high - close) / high
low_distance = (close - low) / low

// Indica posición del cierre dentro de la vela
```

**Historical Max/Min Ratios:**
```python
hist_max_N = max(high[-N:])
hist_min_N = min(low[-N:])

hist_max_ratio_N = close / hist_max_N - 1
hist_min_ratio_N = close / hist_min_N - 1

Períodos: 10, 20, 50, 100
```

---

## Datos de Entrada

| Campo | Tipo | Descripción | Requerido |
|-------|------|-------------|-----------|
| symbol | string | Par de trading | Sí |
| limit | number | Número de velas históricas | No (default: 100) |

---

## Datos de Salida

```typescript
interface TechnicalIndicators {
  symbol: string;
  timestamp: string;
  price: number;

  // Volatilidad (9 indicators)
  volatility: {
    vol_5: number;
    vol_10: number;
    vol_20: number;
    vol_50: number;
    atr_5: number;
    atr_10: number;
    atr_20: number;
    atr_50: number;
    bb_position: number;    // 0-1
  };

  // Momentum (7 indicators)
  momentum: {
    mom_5: number;
    mom_10: number;
    mom_20: number;
    roc_5: number;
    roc_10: number;
    roc_20: number;
    rsi_14: number;         // 0-100
  };

  // Medias Móviles (12 indicators)
  moving_averages: {
    sma_5: number;
    sma_10: number;
    sma_20: number;
    sma_50: number;
    ema_5: number;
    ema_10: number;
    ema_20: number;
    ema_50: number;
    sma_ratio_5: number;
    sma_ratio_10: number;
    sma_ratio_20: number;
    sma_ratio_50: number;
  };

  // MACD (3 indicators)
  macd: {
    macd_line: number;
    macd_signal: number;
    macd_histogram: number;
  };

  // Volumen (1 indicator)
  volume: {
    volume_ratio: number;
  };

  // High/Low (7 indicators)
  high_low: {
    hl_range_pct: number;
    high_distance: number;
    low_distance: number;
    hist_max_ratio_10: number;
    hist_max_ratio_20: number;
    hist_min_ratio_10: number;
    hist_min_ratio_20: number;
  };
}
```

**Ejemplo:**
```json
{
  "symbol": "BTCUSDT",
  "timestamp": "2025-12-05T18:35:00.000Z",
  "price": 89450.00,
  "volatility": {
    "vol_5": 0.0012,
    "vol_10": 0.0015,
    "vol_20": 0.0018,
    "vol_50": 0.0022,
    "atr_5": 250.5,
    "atr_10": 280.3,
    "atr_20": 310.8,
    "atr_50": 345.2,
    "bb_position": 0.65
  },
  "momentum": {
    "mom_5": 0.0025,
    "mom_10": 0.0045,
    "mom_20": 0.0082,
    "roc_5": 0.25,
    "roc_10": 0.45,
    "roc_20": 0.82,
    "rsi_14": 58.3
  },
  "moving_averages": {
    "sma_5": 89380.5,
    "sma_10": 89320.2,
    "sma_20": 89250.8,
    "sma_50": 89100.4,
    "ema_5": 89400.1,
    "ema_10": 89350.6,
    "ema_20": 89280.3,
    "ema_50": 89150.7,
    "sma_ratio_5": 0.0008,
    "sma_ratio_10": 0.0014,
    "sma_ratio_20": 0.0022,
    "sma_ratio_50": 0.0039
  },
  "macd": {
    "macd_line": 45.2,
    "macd_signal": 38.7,
    "macd_histogram": 6.5
  },
  "volume": {
    "volume_ratio": 1.25
  },
  "high_low": {
    "hl_range_pct": 0.35,
    "high_distance": 0.0002,
    "low_distance": 0.0032,
    "hist_max_ratio_10": -0.0015,
    "hist_max_ratio_20": -0.0028,
    "hist_min_ratio_10": 0.0042,
    "hist_min_ratio_20": 0.0078
  }
}
```

---

## Reglas de Negocio

1. **Datos Mínimos:** Requiere al menos 100 velas para indicadores de período 50
2. **Actualización:** Los indicadores se recalculan cada 5 minutos (nueva vela)
3. **Caché:** Resultados se cachean por 1 minuto
4. **Precisión:** Cálculos con precisión de 8 decimales
5. **NaN Handling:** Valores NaN/Inf se reemplazan con 0 o último valor válido

---

## Criterios de Aceptación

```gherkin
Escenario: Calcular indicadores para BTCUSDT
  DADO que existen al menos 100 velas históricas
  CUANDO hace GET /api/indicators/BTCUSDT
  ENTONCES recibe 39 indicadores técnicos
  Y todos los valores son números válidos (no NaN)
  Y rsi_14 está entre 0 y 100
  Y bb_position está entre 0 y 1

Escenario: Indicadores en tiempo real vía WebSocket
  DADO que el usuario está conectado a /ws/BTCUSDT
  CUANDO se cierra una nueva vela de 5 minutos
  ENTONCES recibe indicadores actualizados
  Y el timestamp coincide con el cierre de la vela

Escenario: Datos insuficientes
  DADO que solo existen 30 velas históricas
  CUANDO solicita indicadores
  ENTONCES recibe error 400 Bad Request
  Y el mensaje indica "Insufficient data: need 100+ candles"
```

---

## Dependencias

### Técnicas:
- **NumPy:** Cálculos vectorizados
- **Pandas:** Series de tiempo
- **TA-Lib (opcional):** Librería de indicadores técnicos
- **Binance API:** Datos OHLCV

### Funcionales:
- Usado por RF-ML-001 (como features del modelo)
- Usado por RF-ML-002 (para generar señales)
- Usado por RF-TRD-002 (visualización en charts)

---

## Notas Técnicas

### Implementación - Feature Calculator

```python
# apps/ml-services/src/models/indicators.py

import numpy as np
import pandas as pd

class TechnicalIndicators:
    """
    Calcula indicadores técnicos para ML features
    """

    @staticmethod
    def calculate_all(ohlcv: pd.DataFrame) -> dict:
        """
        Calcula todos los indicadores

        Args:
            ohlcv: DataFrame con columnas [timestamp, open, high, low, close, volume]

        Returns:
            dict con 39 indicadores
        """
        close = ohlcv['close'].values
        high = ohlcv['high'].values
        low = ohlcv['low'].values
        volume = ohlcv['volume'].values

        indicators = {}

        # Volatilidad (9)
        indicators.update(calculate_volatility(close, high, low))

        # Momentum (7)
        indicators.update(calculate_momentum(close))

        # Medias Móviles (12)
        indicators.update(calculate_moving_averages(close))

        # MACD (3)
        indicators.update(calculate_macd(close))

        # Volumen (1)
        indicators.update(calculate_volume(volume))

        # High/Low (7)
        indicators.update(calculate_high_low(close, high, low))

        return indicators
```

### Optimizaciones:
- Uso de NumPy vectorizado (100x más rápido que loops)
- Cálculo incremental para nuevas velas
- Caché de medias móviles intermedias

### Performance:
- Cálculo de 39 indicadores: < 10ms para 500 velas
- Memoria: ~50KB por símbolo en caché

---

## Referencias

- [TA-Lib Documentation](https://ta-lib.org/)
- [Investopedia - Technical Indicators](https://www.investopedia.com/terms/t/technicalindicator.asp)
- [NumPy Performance Tips](https://numpy.org/doc/stable/user/c-info.html)

---

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