⚠ El trading implica un riesgo significativo de pérdida. Todo el contenido de este sitio es únicamente educativo y no constituye asesoramiento financiero.
Lección 6 de 13 · Gratis

Indicadores en código: SMA, EMA, RSI, ATR y bandas de Bollinger vectorizados, verificados frente a una plataforma de gráficos

Escribe funciones vectorizadas de pandas para los cinco indicadores más usados y aprende por qué sus valores pueden diferir de los de una plataforma de gráficos y cómo conciliarlos.

Autor: Moustafa HijaziContenido revisado: 2026-10-306 min de lecturaCurso de autoaprendizaje

Qué aprenderás

  • Cómo implementan rolling y ewm la SMA, la EMA, el suavizado de Wilder, el ATR y las bandas de Bollinger en unas pocas líneas cada uno.
  • Qué parámetros (adjust, alpha, ddof) cambian los números en silencio.
  • Por qué tu RSI o tu EMA difiere de la plataforma de gráficos en las primeras barras y cómo comparar ambos correctamente.
  • Cómo organizar los indicadores como funciones puras en src/indicators.py para reutilizarlos y probarlos.

Explicación

El curso Análisis técnico en profundidad explicó qué significan estos indicadores; aquí el objetivo es un código correcto, rápido y comprobable. Cada función recibe una Series y devuelve una Series con el mismo índice.

SMA (simple moving average, media móvil simple): def sma(s, n): return s.rolling(n).mean().

EMA (exponential moving average, media móvil exponencial): def ema(s, n): return s.ewm(span=n, adjust=False).mean(). Con span=n, la constante de suavizado es α = 2 ÷ (n + 1); para n = 20, α = 0.0952. adjust=False da la fórmula recursiva EMAt = α × Pt + (1 − α) × EMAt−1 que usan las plataformas; el valor por defecto adjust=True pondera de forma distinta en las primeras decenas de barras. pandas inicializa con el primer precio; la mayoría de las plataformas inicializan con la SMA de las primeras n barras. Ambas semillas se desvanecen; tras unas 3n barras las series coinciden estrechamente.

RSI (relative strength index, índice de fuerza relativa), versión de Wilder:

delta = s.diff()

gain = delta.clip(lower=0); loss = -delta.clip(upper=0)

avg_gain = gain.ewm(alpha=1/n, adjust=False).mean()

avg_loss = loss.ewm(alpha=1/n, adjust=False).mean()

rsi = 100 - 100 / (1 + avg_gain / avg_loss)

El suavizado de Wilder es una EMA con α = 1 ÷ n, no 2 ÷ (n + 1); usar aquí span=14 es el error de RSI más común. Cuando avg_loss es cero, el RSI pasa correctamente a 100.

ATR (average true range, rango verdadero medio): el rango verdadero es el mayor de máximo − mínimo, |máximo − cierre anterior| y |mínimo − cierre anterior|:

pc = close.shift(1)

tr = pd.concat([high - low, (high - pc).abs(), (low - pc).abs()], axis=1).max(axis=1)

atr = tr.ewm(alpha=1/n, adjust=False).mean()

Algunas plataformas muestran en su lugar un ATR basado en una SMA; compruébalo antes de comparar. La lección 8 usa el ATR para los stops y el dimensionamiento.

Bandas de Bollinger: banda central = SMA(n), bandas superior e inferior = central ± k × desviación típica sobre las mismas n barras, normalmente n = 20 y k = 2. La trampa es ddof: rolling(n).std() de pandas divide entre n − 1 (muestral), mientras que las plataformas suelen dividir entre n (poblacional). Escribe std(ddof=0) para que coincidan. El valor muestral es mayor, lo suficiente para convertir un «toque» de banda en un «no toque».

Verificar frente a una plataforma. Usa la misma serie ajustada que la plataforma, calcula tu indicador y compara los últimos 100 valores, porque las diferencias de semilla se atenúan. Usa np.allclose con una tolerancia pequeña. Las diferencias persistentes proceden de cinco fuentes habituales: datos ajustados frente a no ajustados, la inicialización, span frente a alpha, ddof o un precio de entrada distinto. Corrige o documenta la discrepancia; nunca «ajustes» un parámetro hasta que los números coincidan.

Ejemplo práctico

Ejemplo hipotético: diez cierres diarios en euros: 20.00, 20.50, 21.00, 20.80, 21.40, 21.00, 21.60, 22.00, 21.80, 22.40.

SMA(5) en la última barra: (21.00 + 21.60 + 22.00 + 21.80 + 22.40) ÷ 5 = 108.80 ÷ 5 = €21.76.

Bollinger(5, 2) en la última barra. Desviaciones respecto a 21.76: −0.76, −0.16, 0.24, 0.04, 0.64; suma de cuadrados 1.0720. Varianza poblacional = 1.0720 ÷ 5 = 0.2144, desviación típica 0.4630, superior = 21.76 + 2 × 0.4630 = €22.69, inferior €20.83. Fórmula muestral: 1.0720 ÷ 4 = 0.2680, desviación típica 0.5177, superior €22.80, inferior €20.72.

EMA(5) con α = 2 ÷ 6 = 0.3333 y adjust=False: EMA1 = 20.00 (semilla), EMA2 = 0.3333 × 20.50 + 0.6667 × 20.00 = 20.1667, EMA3 = 0.3333 × 21.00 + 0.6667 × 20.1667 = 20.4444, y así sucesivamente hasta 21.8310 en la última barra.

Rango verdadero de una barra con máximo de €21.20, mínimo de €20.60 y cierre anterior de €20.80: max(0.60, |21.20 − 20.80| = 0.40, |20.60 − 20.80| = 0.20) = €0.60.

Error frecuente

Confundir span y alpha en ewmewm(span=14) usa α = 2 ÷ 15 = 0.133, mientras que el RSI y el ATR de Wilder necesitan ewm(alpha=1/14), α = 0.0714. La versión con span reacciona casi el doble de rápido y no coincidirá con ninguna plataforma estándar. Escribe explícitamente el α que pretendes usar y anótalo en el docstring de la función.

Actividad práctica

En papel, usando un RSI de Wilder de 3 periodos solo a modo de ilustración (en la práctica se usa 14): cierres 50.00, 50.50, 51.00, 50.80. (a) Enumera las ganancias y pérdidas de cada barra. (b) ewm(alpha=1/3, adjust=False) de pandas inicializa con la primera observación; calcula avg_gain y avg_loss en la última barra y el RSI. (c) Explica en una frase por qué una plataforma que inicializa con la media simple de las tres primeras variaciones mostraría aquí un valor distinto, y qué harías antes de concluir que tu código está mal.

Mostrar solución

(a) Variaciones: +0.50, +0.50, −0.20. Ganancias: 0.50, 0.50, 0.00. Pérdidas: 0.00, 0.00, 0.20.

(b) α = 1/3. avg_gain: 0.50 (semilla) → (1/3)(0.50) + (2/3)(0.50) = 0.50 → (1/3)(0) + (2/3)(0.50) = 0.3333. avg_loss: 0 → 0 → (1/3)(0.20) + (2/3)(0) = 0.0667. RS = 0.3333 ÷ 0.0667 = 5.0; RSI = 100 − 100 ÷ (1 + 5) = 83.33.

(c) Aquí la semilla de media simple (ganancias 0.3333, pérdidas 0.0667) da casualmente el mismo 83.33, pero en general las dos semillas difieren durante las primeras decenas de barras, así que compara los 100 valores más recientes de una serie larga antes de tocar el código.

Limitaciones: un RSI de 3 periodos es un recurso didáctico; cuatro barras no pueden mostrar el comportamiento de convergencia que importa en la práctica.

Ideas clave

  • La SMA es rolling(n).mean(); la EMA es ewm(span=n, adjust=False); el suavizado de Wilder para el RSI y el ATR es ewm(alpha=1/n, adjust=False).
  • Las bandas de Bollinger de la mayoría de las plataformas usan la desviación típica poblacional: pasa ddof=0.
  • Las diferencias de semilla se desvanecen; compara las últimas 100 barras con una tolerancia y busca las cinco causas habituales de discrepancia antes de cambiar el código.
  • Mantén los indicadores como funciones puras documentadas para poder someterlos a pruebas unitarias y reutilizarlos en cada backtest.

Siguiente paso

La lección 7 une las piezas en un backtest vectorizado: señales, posiciones, el desplazamiento que evita el sesgo de anticipación, los costes de transacción y la curva de capital.

Autoevaluación

Responde a las preguntas y luego comprueba tus respuestas. Puedes volver a intentarlo tantas veces como quieras.

1. ¿Qué llamada de pandas implementa el suavizado de Wilder para un RSI o un ATR de 14 periodos?

2. Cinco cierres tienen una media de €40.00 y una suma de desviaciones al cuadrado de 2.00. ¿Dónde está la banda de Bollinger superior (k = 2) con la fórmula poblacional y con la fórmula muestral?

3. Tu EMA(20) coincide con la plataforma con una diferencia de 0.01 en las últimas 200 barras, pero difiere hasta €0.40 en las primeras 30 barras. ¿Cuál es la conclusión correcta?

Autoevaluación automática para tu propio repaso. Tus respuestas se comprueban en tu navegador y no las vemos ni evaluamos. No es un examen ni una titulación.

Tu progreso

«Completada» solo significa que marcaste la lección como terminada. No es una prueba de tu comprensión. El progreso y las notas se guardan solo en este navegador y en este dispositivo, no en una cuenta.