6. Z-score: cuándo un precio está muy alto o muy bajo
Nivel 5: Estrategias de Momentum, Mean-Reversion y Pairs Trading
Para convertir "el precio está anormalmente alto o bajo" en una regla programable, necesitas una medida concreta de qué tan lejos está el precio de su media, expresada en unidades comparables entre distintos activos. Esa medida es el z-score: cuántas desviaciones estándar separan un valor de la media de su serie.
z = (x - media) / desviacion_estandarUn z-score de 1 significa que el precio está una desviación estándar por encima de la media; un z-score de -2, dos desviaciones estándar por debajo. La ventaja de trabajar en desviaciones estándar en vez de en precio absoluto es que el mismo umbral (por ejemplo, z > 1) significa lo mismo para una acción de 20€ que para una de 2.000€.
Aplicado a un solo activo, la estrategia calcula primero la media móvil acumulada del precio — el promedio de todos los días hasta la fecha actual, no una ventana fija — y a partir de ahí, el z-score de cada día:
datos = yf.download("AAPL", start="2022-02-02", progress=False)
datos["media_acumulada"] = [datos["Close"][:i+1].mean() for i in range(len(datos))]
zscores = []
for i in range(len(datos)):
desviacion = np.std(datos["Close"][:i+1])
if desviacion == 0:
zscores.append(0)
else:
z = (datos["Close"].iloc[i] - datos["media_acumulada"].iloc[i]) / desviacion
zscores.append(z)Usar la media y la desviación acumuladas hasta cada día, en vez de calculadas sobre todo el histórico de golpe, es importante por la misma razón que el shift(1) del módulo anterior: en el momento real de operar el día i, todavía no conoces los precios de los días posteriores. Calcular la media con datos futuros sería otra forma de look-ahead bias, aunque más sutil que la anterior.
Con el z-score calculado, la regla de la estrategia es simple: vender en corto cuando el z-score supera 1 (el precio está anormalmente caro), comprar en largo cuando cae por debajo de -1 (anormalmente barato), y cerrar toda posición cuando el z-score vuelve a estar cerca de cero — dentro de medio punto — porque en ese momento el precio ya ha revertido a su media y la ventaja de la operación se ha agotado.
dinero = 0
posicion = 0
for i in range(len(datos)):
if zscores[i] > 1:
dinero += datos["Open"].iloc[i] # la condicion debe evaluarse con zscores[i-1] (el z-score que ya existia al cierre de ayer) y ejecutarse a la apertura de hoy
posicion -= 1
elif zscores[i] < -1:
dinero -= datos["Close"].iloc[i]
posicion += 1
elif abs(zscores[i]) < 0.5:
dinero += posicion * datos["Close"].iloc[i]
posicion = 0Este fragmento es, de nuevo, una simulación simplificada, no un backtest completo: dinero acumula el flujo de caja de comprar y vender, sin comisiones ni gestión de tamaño de posición, y posicion puede acumularse en varias operaciones seguidas sin ningún límite. Sirve para entender el mecanismo del z-score aplicado a una estrategia; no está listo para decidir cuánto capital real arriesgar.
Para ampliar esta lección
- Z-score (puntuación típica)en el glosario
- Momentum (momento)en el glosario
- Momentum en bolsa: qué es y cuánto ha rentado en el S&P 500guía