5. Oscillators: EMA and stochastic to spot bounces
Level 4: Stock Screening
Minervini's Trend Template looks for trends that are already established. The second screener you are going to build looks for something different: the precise moment at which a share, inside an uptrend, bounces after a small pullback — an entry opportunity, not just a confirmation of the trend.
Two tools are combined for this: three exponential moving averages of different lengths, which define the underlying trend, and the stochastic oscillator, which measures whether the price is in oversold territory in the short term.
def añadir_ema(precio, dias):
return precio.ewm(span=dias).mean()
def añadir_estocastico(cierre, minimo, maximo, periodo, k, d=0):
k_bruto = ((cierre - minimo.rolling(window=periodo).min()) /
(maximo.rolling(window=periodo).max() - minimo.rolling(window=periodo).min())) * 100
stoch_k = k_bruto.rolling(window=k).mean()
if d == 0:
return stoch_k
return stoch_k.rolling(window=d).mean()The stochastic oscillator compares the current closing price with the range (low to high) of the last periodo days, expressed on a scale of 0 to 100. A value close to 0 means the price is near its recent low (possible oversold); a value close to 100, that it is near its recent high (possible overbought). The k and d variables smooth that calculation with an extra moving average, to cut down day-to-day noise.
With the three EMAs (18, 50 and 100 days) and the stochastic already computed, the function that detects the bounce checks three conditions on the last two price candles:
def detectar_rebote(df):
vela_actual = df.iloc[-1]
vela_anterior = df.iloc[-2]
tendencia_alcista = vela_actual["EMA18"] > vela_actual["EMA50"] > vela_actual["EMA100"]
zona_sobreventa = vela_actual["STOCH_K"] <= 30 or vela_actual["STOCH_D"] <= 30
rebote_confirmado = (vela_anterior["Low"] < vela_anterior["EMA50"]
and vela_anterior["Close"] > vela_anterior["EMA50"])
return tendencia_alcista and zona_sobreventa and rebote_confirmadoThe first condition (tendencia_alcista) demands that the three EMAs be stacked from shortest to longest — the same moving average alignment logic you already saw in the Trend Template, applied here with exponential rather than simple averages. The second (zona_sobreventa) demands that the stochastic oscillator be low, a sign that the price has cooled off in the short term inside that underlying uptrend. The third (rebote_confirmado) is the most specific: it looks at the previous candle, not the current one, and checks that the day's low dipped below the 50-day EMA but the close ended above it — that is, that the price "pierced" the average and recovered within the same day, the classic visual pattern of a bounce off a dynamic support.
This screener is more demanding in terms of timing than Minervini's because, by using the previous candle as well as the current one, it only detects the signal on the exact day it happens — if you do not check the screener often, you can miss the very entry window it is looking for.
To go deeper into this lesson
- Momentumin the glossary
- Momentum investing: what it is and its S&P 500 recordguide