Codificar estrategias de cruce de medias móviles

Video
25 min~5 min lectura

Reproductor de video

Concepto clave

El cruce de medias móviles es una estrategia de seguimiento de tendencia que utiliza dos medias móviles de diferentes períodos para generar señales de compra y venta. La lógica es simple: cuando la media móvil de corto plazo cruza por encima de la de largo plazo, se interpreta como una señal alcista (cruce dorado). Cuando cruza por debajo, es una señal bajista (cruce de la muerte).

Imagina que eres un surfista observando olas: la media corta (ej. 20 períodos) es como la cresta de la ola actual, mientras que la media larga (ej. 50 períodos) representa la tendencia general del mar. Cuando la cresta supera la tendencia, es momento de subir a la tabla; cuando cae por debajo, es hora de salir del agua. En trading, esto se traduce en entrar en posiciones largas durante cruces alcistas y cerrarlas (o entrar en corto) durante cruces bajistas.

Cómo funciona en la práctica

Vamos a implementar un cruce de medias móviles simple para BTC/USDT en Binance. Primero, necesitamos datos históricos de precios. Usaremos la API de Binance para obtener velas de 1 hora. Luego calcularemos dos medias móviles exponenciales (EMA): una de 20 períodos y otra de 50 períodos. La EMA da más peso a los precios recientes, lo que es útil en mercados volátiles como el crypto.

Paso a paso: 1) Obtener 100 velas de 1h para BTC/USDT. 2) Calcular EMA20 y EMA50. 3) Identificar cruces comparando las medias en cada punto. 4) Generar señales: comprar cuando EMA20 > EMA50 y antes era menor; vender cuando EMA20 < EMA50 y antes era mayor. 5) Backtestear la estrategia con un capital inicial simulado.

Código en acción

Aquí tienes un ejemplo funcional usando Python, pandas y la biblioteca python-binance. Asegúrate de tener instaladas las dependencias: pip install python-binance pandas numpy.

import pandas as pd
from binance.client import Client
from datetime import datetime, timedelta

# Configuración inicial
client = Client(api_key='TU_API_KEY', api_secret='TU_API_SECRET')
symbol = 'BTCUSDT'
interval = Client.KLINE_INTERVAL_1HOUR
limit = 100

# Obtener datos históricos
klines = client.get_klines(symbol=symbol, interval=interval, limit=limit)
df = pd.DataFrame(klines, columns=['timestamp', 'open', 'high', 'low', 'close', 'volume', 'close_time', 'quote_asset_volume', 'number_of_trades', 'taker_buy_base_asset_volume', 'taker_buy_quote_asset_volume', 'ignore'])
df['close'] = df['close'].astype(float)
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')

# Calcular EMAs
def calculate_ema(data, window):
    return data.ewm(span=window, adjust=False).mean()

df['EMA20'] = calculate_ema(df['close'], 20)
df['EMA50'] = calculate_ema(df['close'], 50)

# Identificar cruces
df['signal'] = 0  # 0: sin señal, 1: compra, -1: venta
df['prev_EMA20'] = df['EMA20'].shift(1)
df['prev_EMA50'] = df['EMA50'].shift(1)

for i in range(1, len(df)):
    if df.loc[i, 'EMA20'] > df.loc[i, 'EMA50'] and df.loc[i, 'prev_EMA20'] <= df.loc[i, 'prev_EMA50']:
        df.loc[i, 'signal'] = 1  # Cruce alcista
    elif df.loc[i, 'EMA20'] < df.loc[i, 'EMA50'] and df.loc[i, 'prev_EMA20'] >= df.loc[i, 'prev_EMA50']:
        df.loc[i, 'signal'] = -1  # Cruce bajista

print(df[['timestamp', 'close', 'EMA20', 'EMA50', 'signal']].tail(10))

Antes de optimizar, este código puede generar muchas señales falsas en rangos laterales. Después de agregar un filtro de volumen (solo operar si el volumen está por encima del promedio), reducimos el ruido:

# Mejora: filtrar por volumen
df['volume'] = df['volume'].astype(float)
volume_mean = df['volume'].rolling(20).mean()
df['signal_filtered'] = 0
for i in range(1, len(df)):
    if df.loc[i, 'volume'] > volume_mean.iloc[i]:
        if df.loc[i, 'EMA20'] > df.loc[i, 'EMA50'] and df.loc[i, 'prev_EMA20'] <= df.loc[i, 'prev_EMA50']:
            df.loc[i, 'signal_filtered'] = 1
        elif df.loc[i, 'EMA20'] < df.loc[i, 'EMA50'] and df.loc[i, 'prev_EMA20'] >= df.loc[i, 'prev_EMA50']:
            df.loc[i, 'signal_filtered'] = -1
print(df[['timestamp', 'close', 'EMA20', 'EMA50', 'signal', 'signal_filtered']].tail(10))

Errores comunes

  • Overfitting los períodos: Ajustar las medias (ej. EMA9 y EMA21) sin validación cruzada lleva a estrategias que funcionan solo en datos históricos. Usa walk-forward analysis para probar en múltiples ventanas temporales.
  • Ignorar los costos de transacción: En backtesting, no incluir fees de Binance (0.1% para maker/taker) infla los resultados. Resta un porcentaje por trade en tu simulación.
  • Falta de gestión de riesgo: Operar sin stop-loss o take-profit. Implementa órdenes OCO (One-Cancels-the-Other) en la API para limitar pérdidas.
  • No considerar la latencia: En live trading, los cruces pueden ocurrir entre velas. Usa datos en tiempo real con WebSockets de Binance en lugar de velas cerradas.
  • Uso incorrecto de tipos de medias EMA vs SMA: EMA reacciona más rápido, pero puede generar más señales falsas en mercados laterales. Evalúa cuál se adapta mejor al activo.

Checklist de dominio

  1. Puedo obtener datos históricos de Binance API y calcular EMAs con pandas.
  2. Sé identificar cruces de medias y generar señales de trading automáticamente.
  3. He implementado un filtro de volumen para reducir señales falsas.
  4. Puedo backtestear la estrategia con un capital simulado y calcular el Sharpe Ratio.
  5. Entiendo cómo enviar órdenes de mercado a Binance usando señales de cruce.
  6. Sé optimizar períodos de medias sin overfitting, usando validación out-of-sample.
  7. Puedo integrar la estrategia en un bot que opere en tiempo real con WebSockets.

Implementa y backtestea una estrategia de cruce de medias móviles con gestión de riesgo

En este ejercicio, desarrollarás una estrategia de cruce de medias móviles para ETH/USDT en Binance, incluyendo backtesting y órdenes de gestión de riesgo. Sigue estos pasos:

  1. Configura un entorno Python con las bibliotecas necesarias: python-binance, pandas, numpy.
  2. Obtén 500 velas de 15 minutos para ETH/USDT usando la API de Binance.
  3. Calcula una EMA de 12 períodos y una EMA de 26 períodos.
  4. Genera señales de compra/venta basadas en cruces, similar al ejemplo de la lección.
  5. Implementa un backtest simple: simula un capital inicial de 1000 USDT, comprando/vendiendo con cada señal, y resta un fee de 0.1% por transacción.
  6. Añade órdenes de stop-loss (2% por debajo del precio de entrada) y take-profit (5% por encima) usando lógica condicional en el backtest.
  7. Calcula métricas de performance: retorno total, número de trades, y ratio de aciertos (win rate).
  8. Opcional: prueba diferentes períodos de EMA (ej. 9/21) y compara resultados.

Entrega un script Python funcional que imprima los resultados del backtest y un gráfico de las medias con señales (puedes usar matplotlib si lo deseas).

Pistas
  • Usa df['signal'].shift(1) para evitar look-ahead bias en el backtest.
  • Para stop-loss, calcula el precio de entrada y verifica si el precio mínimo posterior cae un 2%.
  • Considera usar la función .cumprod() de pandas para calcular el equity curve fácilmente.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.