Quant500 Academy

4. Programar el filtro Minervini completo: de 500 acciones a una lista corta

Nivel 4: Screening de Acciones

Con el RS Rating ya calculado para el 30% superior de acciones, el siguiente paso es comprobar, una por una, si cada una de esas acciones cumple también las condiciones de precio y medias móviles del Trend Template. Primero, calculamos las tres medias móviles que hacen falta:

for periodo in [50, 150, 200]:
    df[f"SMA_{periodo}"] = df["Adj Close"].rolling(window=periodo).mean()

Con las medias calculadas, se extraen los valores más recientes de cada una, junto con el mínimo y el máximo de las últimas 52 semanas (aproximadamente 260 días de cotización):

precio_actual = df["Adj Close"].iloc[-1]
media_50 = df["SMA_50"].iloc[-1]
media_150 = df["SMA_150"].iloc[-1]
media_200 = df["SMA_200"].iloc[-1]
minimo_52sem = round(df["Low"].iloc[-260:].min(), 2)
maximo_52sem = round(df["High"].iloc[-260:].max(), 2)
media_200_hace_un_mes = df["SMA_200"].iloc[-20]

Y ahora, las siete condiciones del Trend Template se traducen literalmente, una a una, en comparaciones booleanas:

condicion_1 = precio_actual > media_150 > media_200
condicion_2 = media_150 > media_200
condicion_3 = media_200 > media_200_hace_un_mes
condicion_4 = media_50 > media_150 > media_200
condicion_5 = precio_actual > media_50
condicion_6 = precio_actual >= 1.3 * minimo_52sem
condicion_7 = precio_actual >= 0.75 * maximo_52sem

cumple_todas = all([condicion_1, condicion_2, condicion_3, condicion_4,
                     condicion_5, condicion_6, condicion_7])

Un detalle de Python que aparece aquí y merece explicación: precio_actual > media_150 > media_200 es una comparación encadenada, equivalente a escribir (precio_actual > media_150) and (media_150 > media_200), pero más legible. Solo es True si ambas comparaciones se cumplen a la vez, en el orden exacto en que se escriben.

all() comprueba que todos los elementos de la lista sean True; basta con que una sola condición falle para que la acción quede descartada del screener — es un filtro estricto por diseño: el método de Minervini no promedia condiciones, las exige todas a la vez.

Un matiz práctico que no está en la teoría pero sí en el código real: al recorrer cientos de tickers, algunos van a fallar — por datos faltantes, por un ticker deslistado, por un error temporal de la fuente. Envolver cada iteración en un try/except (visto en el Nivel 2) evita que un solo ticker problemático interrumpa el cribado de las 500 empresas restantes.

resultados = []
for ticker in top_30["Ticker"]:
    try:
        # ... calcular condiciones para este ticker ...
        if cumple_todas:
            resultados.append(ticker)
    except Exception as error:
        print(f"No se pudo procesar {ticker}: {error}")
        continue

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.