4. Programming the full Minervini filter: from 500 shares to a short list
Level 4: Stock Screening
With the RS Rating already computed for the top 30% of shares, the next step is to check, one by one, whether each of those shares also meets the price and moving average conditions of the Trend Template. First, we compute the three moving averages we need:
for periodo in [50, 150, 200]:
df[f"SMA_{periodo}"] = df["Adj Close"].rolling(window=periodo).mean()With the averages computed, we pull out the most recent value of each one, along with the 52-week low and high (roughly 260 trading days):
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]And now the seven Trend Template conditions translate literally, one by one, into boolean comparisons:
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])One Python detail shows up here and deserves an explanation: precio_actual > media_150 > media_200 is a chained comparison, equivalent to writing (precio_actual > media_150) and (media_150 > media_200), but easier to read. It is only True if both comparisons hold at once, in the exact order in which they are written.
all() checks that every element of the list is True; a single failed condition is enough for the share to be dropped from the screener — it is a strict filter by design: Minervini's method does not average conditions, it demands all of them at once.
A practical detail that is not in the theory but is very much in real code: when you loop over hundreds of tickers, some will fail — missing data, a delisted ticker, a temporary error at the source. Wrapping each iteration in a try/except (seen in Level 2) stops a single problem ticker from interrupting the screening of the remaining 500 companies.
resultados = []
for ticker in top_30["Ticker"]:
try:
# ... compute the conditions for this ticker ...
if cumple_todas:
resultados.append(ticker)
except Exception as error:
print(f"No se pudo procesar {ticker}: {error}")
continueTo go deeper into this lesson
- Momentumin the glossary
- Momentum investing: what it is and its S&P 500 recordguide