3. Tu primera estrategia completa: el cruce de una media móvil
Nivel 5: Estrategias de Momentum, Mean-Reversion y Pairs Trading
Es hora de programar tu primera estrategia completa de principio a fin: la más simple que existe, y por eso mismo el punto de partida obligado de cualquiera que empiece en trading algorítmico. La lógica es minimalista: entra largo cuando el precio cierra por encima de su media móvil simple; sal (o ponte corto) cuando cierra por debajo.
import yfinance as yf
datos = yf.download("GS", start="2022-01-01", progress=False)
datos["SMA100"] = datos["Close"].rolling(window=100).mean()
datos["Señal"] = 0
datos.loc[datos["Close"] > datos["SMA100"], "Señal"] = 1
datos.loc[datos["Close"] < datos["SMA100"], "Señal"] = -1
datos["Posicion"] = datos["Señal"] - datos["Señal"].shift(1)La columna Posicion es el truco que convierte una señal continua (1 o -1 cada día) en un evento puntual: al restar la señal de ayer a la de hoy, solo aparece un valor distinto de cero el día exacto en que la señal cambia. Un salto de -1 a 1 da como resultado 2 (señal de compra); un salto de 1 a -1 da -2 (señal de venta). El resto de los días, sin cambio de señal, el resultado es 0.
señales_compra = datos[datos["Posicion"] > 0]
señales_venta = datos[datos["Posicion"] == -2]Con las señales identificadas, el siguiente paso natural es preguntarte: ¿habría funcionado esto en el pasado? Aquí es donde entra un primer backtest — deliberadamente ingenuo, y el propio material fuente insiste en llamarlo así — que recorre distintos periodos de media móvil (de 2 a 100 días) y calcula qué retorno habría dado cada uno:
resultados = []
for periodo in range(2, 101):
datos["SMA"] = datos["Close"].rolling(window=periodo).mean()
datos["Señal"] = 0
datos.loc[datos["Close"] > datos["SMA"], "Señal"] = 1
datos.loc[datos["Close"] < datos["SMA"], "Señal"] = -1
datos["Retorno_diario"] = datos["Close"].pct_change() * datos["Señal"].shift(1)
retorno_acumulado = (datos["Retorno_diario"] + 1).prod() - 1
resultados.append((periodo, retorno_acumulado * 100))datos['Señal'].shift(1) es un detalle que no puedes saltarte: el retorno de hoy solo se puede capturar si ya tenías la señal de ayer, nunca la de hoy mismo — de lo contrario estarías usando información que en el momento real de operar todavía no existía. Es una versión temprana del look-ahead bias que verás formalizado más adelante en el curso.
Y aquí llega la advertencia que el propio material original subraya de forma explícita, y que conviene tomarse en serio: este backtest asume que puedes comprar y vender exactamente al precio de cierre, sin comisiones, sin slippage, sin restricciones de liquidez y sin impuestos. Ninguna de esas condiciones existe en el mercado real. El resultado de este ejercicio sirve para explorar la idea, no para decidir con qué media móvil vas a operar mañana con dinero de verdad — eso exige un backtest mucho más riguroso, que verás en niveles posteriores.
Para ampliar esta lección
- Momentum (momento)en el glosario
- Z-score (puntuación típica)en el glosario
- Momentum en bolsa: qué es y cuánto ha rentado en el S&P 500guía