Quant500 Academy

3. Tu primera señal de trading real: el cruce MACD

Nivel 3: Datos de Mercado y APIs

Hasta ahora has descargado y visualizado precios. En este módulo das el paso que convierte un gráfico en una estrategia: generar automáticamente señales de compra y venta a partir de un indicador técnico.

El indicador que vas a usar es el MACD (Moving Average Convergence/Divergence), uno de los más usados en análisis técnico porque combina tendencia y momentum en un solo número. Se construye con dos medias móviles exponenciales (EMA) de distinta longitud: una rápida y una lenta.

ema_rapida = datos["Close"].ewm(span=12, adjust=False).mean()
ema_lenta = datos["Close"].ewm(span=26, adjust=False).mean()

macd = ema_rapida - ema_lenta
señal = macd.ewm(span=9, adjust=False).mean()

Una media exponencial (EMA) da más peso a los precios recientes que a los antiguos, a diferencia de la media móvil simple del módulo anterior, que pesa todos los días de la ventana por igual. Eso la hace reaccionar más rápido a cambios recientes de precio. El MACD es, literalmente, la diferencia entre la EMA rápida (12 periodos) y la EMA lenta (26 periodos); la línea de señal es, a su vez, una EMA de 9 periodos calculada sobre el propio MACD.

La interpretación clásica es: cuando el MACD cruza por encima de la línea de señal, es una señal de compra; cuando cruza por debajo, es una señal de venta. Convertir esa idea en código significa recorrer la serie día a día y comparar las dos líneas en cada punto:

def señales_macd(df):
    precio_compra = []
    precio_venta = []
    posicion_abierta = False

    for i in range(len(df)):
        if df["MACD"].iloc[i] > df["Señal"].iloc[i]:
            if not posicion_abierta:
                precio_compra.append(df["Open"].iloc[i + 1] if i + 1 < len(df) else float("nan"))   # la señal nace con el cierre de i; se ejecuta a la apertura de i+1
                precio_venta.append(float("nan"))
                posicion_abierta = True
            else:
                precio_compra.append(float("nan"))
                precio_venta.append(float("nan"))
        elif df["MACD"].iloc[i] < df["Señal"].iloc[i]:
            if posicion_abierta:
                precio_venta.append(df["Open"].iloc[i + 1] if i + 1 < len(df) else float("nan"))   # se sale igual que se entra: a la apertura del dia siguiente
                precio_compra.append(float("nan"))
                posicion_abierta = False
            else:
                precio_compra.append(float("nan"))
                precio_venta.append(float("nan"))
        else:
            precio_compra.append(float("nan"))
            precio_venta.append(float("nan"))

    return precio_compra, precio_venta

El detalle importante de esta función es la variable posicion_abierta: sin ella, el código generaría una señal de compra en cada día que el MACD esté por encima de la señal, no solo en el momento exacto del cruce — lo que produciría docenas de señales de compra seguidas en vez de una sola. posicion_abierta actúa como memoria: solo compra si no tenías ya una posición abierta, y solo vende si la tenías. Este patrón — una variable de estado que recuerda en qué situación está la estrategia — es la base de prácticamente cualquier generador de señales, por sofisticado que sea el indicador de turno.

Con las dos listas resultantes, puedes marcarlas directamente sobre el precio para ver, visualmente, si la lógica tiene sentido antes de fiarte de ella:

datos["Compra"], datos["Venta"] = señales_macd(datos)

plt.scatter(datos.index, datos["Compra"], color="green", marker="^", label="Compra")
plt.scatter(datos.index, datos["Venta"], color="red", marker="v", label="Venta")
plt.plot(datos["Close"], alpha=0.35, label="Precio")
plt.legend()
plt.show()

Este ejemplo tiene un límite importante que conviene tener claro desde ya: genera señales, no una estrategia completa. No calcula cuánto capital arriesgar, no tiene stop-loss, y no ha pasado por ningún backtest riguroso — eso llega en niveles posteriores. Por ahora, el objetivo es entender el patrón: indicador, comparación, señal, estado.

Para ampliar esta lección

El resto de Quant500

Lo que estás aprendiendo, aplicado al índice todos los días:

La cartera del modelo, cada semana en tu correo

Cada sábado, su semana en un minuto: cómo fue, lo mejor y lo peor, y qué directivos compraron con su dinero. Y el día que el modelo se rehace, qué entra y qué sale. Es una simulación, no un consejo. Como mucho un correo a la semana, sin publicidad y sin cuenta; te das de baja con un clic. Lo hace una persona: Pol Gracia.

Cartera modelo con resultados simulados, no una recomendación personalizada. Tu correo se usa solo para esto: privacidad.

Detrás de la Academia y del resto de Quant500 hay una sola persona: Pol Gracia Serrahima.