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