Grid Trading con Python: Una Estrategia Algorítmica Simple y Rentable

En el artículo de hoy, exploraremos la estrategia de grid trading — un enfoque sencillo pero efectivo que no depende de indicadores técnicos para determinar tendencias – aplicada mediante el lenguaje Python. Esta estrategia es una excelente candidata para el trading algorítmico, y la prueba retroactiva utilizando Python demuestra un aumento constante en el capital durante dos meses de datos en un marco de tiempo de 5 minutos.
Los resultados son curiosamente buenos, con un Ratio de Sharpe de casi 5.7.
¿Qué es el grid trading?
El grid trading implica crear una rejilla de valores en un gráfico de precios. Cada vez que el precio cruza una línea de la rejilla, abrimos simultáneamente una posición larga y una posición corta. El objetivo de beneficio para cada posición abierta se establece en la siguiente línea de la rejilla. Esta estrategia prospera en mercados con oscilaciones frecuentes de precios, lo que la hace particularmente adecuada para marcos de tiempo más cortos.
Ahora implementaremos una estrategia de grid trading en Python y la probaremos con datos históricos.
Paso 1: Configuración del Entorno en Python
Para comenzar, necesitamos configurar nuestro entorno de Python importando las bibliotecas necesarias y descargando el conjunto de datos. Utilizaremos yfinance para obtener los datos del tipo de cambio EUR/USD, pandas para la manipulación de datos, numpy para operaciones numéricas y pandas_ta para indicadores de análisis técnico.
# Importación de bibliotecas de Python
import yfinance as yf
import pandas as pd
import numpy as np
import pandas_ta as ta
# Descargar los datos del EUR/USD para los últimos 59 días con un intervalo de 5 minutos
dataF = yf.download("EURUSD=X", start=pd.Timestamp.today() - pd.DateOffset(days=59), end=pd.Timestamp.today(), interval='5m')Importación de Bibliotecas
- yfinance (yf): Una biblioteca de Python para descargar datos históricos del mercado desde Yahoo Finance.
- pandas (pd): Una poderosa biblioteca para la manipulación y análisis de datos.
- numpy (np): Una biblioteca para operaciones numéricas.
- pandas_ta (ta): Una biblioteca de análisis técnico para conjuntos de datos financieros.
Descarga de Datos
Utilizamos yfinance para descargar los datos del tipo de cambio EUR/USD desde el 19 de noviembre de 2022 hasta el 16 de enero de 2023, con un intervalo de 5 minutos. Esto nos proporciona un conjunto de datos detallado y de alta frecuencia adecuado para nuestra estrategia de trading en rejilla.
Paso 2: Creación de la Rejilla
En este paso, creamos una rejilla de valores que determinará nuestros puntos de entrada y salida. La rejilla es una serie de líneas horizontales espaciadas a intervalos regulares en el gráfico de precios. Cada vez que el precio cruce una línea de la rejilla, abriremos una nueva posición.
grid_distancia = 0.005 precio_medio = 1.065 def generar_grid(precio_medio, grid_distancia, grid_rango): return np.arange(precio_medio - grid_rango, precio_medio + grid_rango, grid_distancia) grid = generar_grid(precio_medio=precio_medio, grid_distancia=grid_distancia, grid_rango=0.1)
Definición de parámetros:
- grid_distancia = 0.005: Esto define la distancia entre cada línea de la rejilla. Representa el incremento de precio en el cual abriremos nuevas posiciones.
- precio_medio = 1.065: Este es el precio central alrededor del cual se crea la rejilla. Puede establecerse al precio de mercado actual o con base en cualquier otro precio de referencia.
IMPORTANTE: Estos valores deben ser modificados dependiendo del mercado, el activo negociado y el análisis actual. Por lo tanto, cada vez que consultes datos de Yfinance, se actualizarán los datos y estos parámetros, entre otros en esta estrategia, deben actualizarse en consecuencia.
Generación de la Rejilla:
- La función generate_grid crea un rango de valores desde precio_medio – grid_rango hasta precio_medio + grid_rango, espaciados por grid_distancia.
- grid_rango = 0.1: Esto determina el rango total de la rejilla por encima y por debajo del precio_medio.
Creación de la Rejilla:
El array de la rejilla contiene todos los niveles de precios en los que consideraremos abrir posiciones. Esto forma la columna vertebral de nuestra estrategia de trading en rejilla, permitiéndonos entrar y salir de operaciones de manera sistemática a medida que el precio se mueve.
Paso 3: Generación de Señales de Trading
En este paso, generamos señales de trading basadas en la rejilla que creamos. Se genera una señal de trading cada vez que el precio cruza una línea de la rejilla. Esto se realiza iterando sobre cada fila en nuestro conjunto de datos y verificando si el precio máximo o mínimo para ese intervalo cruza alguna de las líneas de la rejilla.
signal = [0] * len(dataF)
i = 0
for index, row in dataF.iterrows():
for p in grid:
if min(row.Low, row.High) < p and max(row.Low, row.High) > p:
signal[i] = 1
i += 1
dataF["signal"] = signal
dataF[dataF["signal"] == 1]
Inicialización de Señales:
- signal = [0] * len(dataF): Creamos una lista de ceros con la misma longitud que nuestro conjunto de datos. Esta lista almacenará nuestras señales de trading.
Iteración Sobre los Datos:
Iteramos sobre cada fila en el conjunto de datos utilizando dataF.iterrows().
Para cada fila, verificamos si el precio cruza alguna de las líneas de la rejilla. Esto se hace comparando el mínimo y máximo de los precios altos y bajos de la fila con cada línea de la rejilla.
Generación de Señales:
Si se cruza una línea de la rejilla (es decir, el precio mínimo está por debajo de la línea de la rejilla y el precio máximo está por encima de ella), establecemos el valor correspondiente en la lista de señales a 1.
Esto indica que se ha generado una señal de trading en ese intervalo de tiempo.
Adición de Señales al DataFrame:
- dataF[“signal”] = signal: Agregamos la lista de señales como una nueva columna a nuestro DataFrame.
- dataF[dataF[“signal”] == 1]: Esto filtra el DataFrame para mostrar solo las filas donde se ha generado una señal de trading.
Al generar estas señales de trading, identificamos los puntos exactos en el tiempo cuando el precio cruza una línea de la rejilla, indicando posibles entradas en el mercado. Este enfoque sistemático asegura que capturamos todos los movimientos de precios relevantes dentro de nuestra rejilla definida.
Paso 4: Preparación para la Prueba Retroactiva
En este paso, preparamos nuestro conjunto de datos para la prueba retroactiva calculando el Rango Verdadero Promedio (ATR) y definiendo la función de señal. El ATR es un indicador de volatilidad que nos ayuda a establecer niveles dinámicos de stop-loss y take-profit.
dfpl = dataF[:].copy() def SIGNAL(): return dfpl.signal dfpl['ATR'] = ta.atr(high=dfpl.High, low=dfpl.Low, close=dfpl.Close, length=16) dfpl.dropna(inplace=True)
Copiado del DataFrame:
- dfpl = dataF[:].copy(): Creamos una copia de nuestro conjunto de datos original para evitar modificarlo directamente durante el proceso de prueba retroactiva. Esto asegura que nuestros datos originales permanezcan intactos.
Definición de la Función de Señal:
- def SIGNAL(): Esta función devuelve la columna de señales de nuestro DataFrame. Se utilizará durante la prueba retroactiva para acceder a las señales de trading generadas.
Calculando el Rango Verdadero Promedio (ATR):
- dfpl[‘ATR’] = ta.atr(high=dfpl.High, low=dfpl.Low, close=dfpl.Close, length=16): Calculamos el ATR utilizando un período de 16 intervalos. El ATR es una medida de la volatilidad del mercado y ayuda a establecer niveles adecuados de stop-loss y take-profit.
- high=dfpl.High, low=dfpl.Low, close=dfpl.Close: Estos parámetros especifican los precios máximos, mínimos y de cierre utilizados para calcular el ATR.
Limpieza de los Datos:
- dfpl.dropna(inplace=True): Eliminamos cualquier fila con valores faltantes para garantizar la integridad de nuestro proceso de prueba retroactiva. Este paso es crucial para obtener resultados precisos y fiables en la prueba retroactiva.
El ATR proporciona una medida dinámica de la volatilidad. Al calcular el ATR y definir la función de señal, equipamos nuestro marco de prueba retroactiva con las herramientas necesarias para evaluar el desempeño de la estrategia de trading en rejilla.
Paso 5: Implementación y Ejecución de la Prueba Retroactiva
En este paso, implementamos nuestra estrategia de trading utilizando la biblioteca backtesting y ejecutamos una prueba retroactiva para evaluar su desempeño. Definimos una clase de estrategia personalizada y configuramos los parámetros de la prueba retroactiva.
from backtesting import Strategy
from backtesting import Backtest
import backtesting
class MyStrat(Strategy):
mysize = 50
def init(self):
super().init()
self.signal1 = self.I(SIGNAL)
def next(self):
super().next()
slatr = 1.5 * grid_distance # Distancia del stop loss
TPSLRatio = 0.5 # Relación entre toma de ganancias y stop loss
if self.signal1 == 1 and len(self.trades) <= 10000:
# Posición de venta
sl1 = self.data.Close[-1] + slatr
tp1 = self.data.Close[-1] - slatr * TPSLRatio
self.sell(sl=sl1, tp=tp1, size=self.mysize)
# Posición de compra
sl1 = self.data.Close[-1] - slatr
tp1 = self.data.Close[-1] + slatr * TPSLRatio
self.buy(sl=sl1, tp=tp1, size=self.mysize)
# Ejecución de la prueba de backtesting
bt = Backtest(dfpl, MyStrat, cash=50, margin=1/100, hedging=True, exclusive_orders=False)
stat = bt.run()
stat
Importación de Bibliotecas:
- from backtesting import Strategy, Backtest: Importa las clases necesarias de la biblioteca backtesting para definir y ejecutar nuestra estrategia.
- import backtesting: Importa la biblioteca de prueba retroactiva.
Definiendo la Clase de Estrategia:
- class MyStrat(Strategy): Creamos una clase de estrategia personalizada heredando de la clase Strategy.
- mysize = 50: Define el tamaño de la posición para cada operación.
Inicialización:
- def init(self): Inicializa la estrategia.
- self.signal1 = self.I(SIGNAL): Accede a las señales de trading utilizando la función SIGNAL definida anteriormente.
Definiendo la Lógica de Trading:
- def next(self): Define la lógica para ejecutar operaciones.
- slatr = 1.5 * grid_distance: Calcula la distancia del stop loss basada en la distancia de la rejilla.
- TPSLRatio = 0.5: Establece la proporción de take profit a stop loss.
Ejecución de Operaciones:
- Si se genera una señal (self.signal1 == 1) y el número de operaciones es menor o igual a 10,000:
Posición de Venta:
- sl1 = self.data.Close[-1] + slatr: Establece el stop loss por encima del precio de cierre actual.
- tp1 = self.data.Close[-1] – slatr * TPSLRatio: Establece el take profit por debajo del precio de cierre actual.
- self.sell(sl=sl1, tp=tp1, size=self.mysize): Ejecuta la orden de venta.
Posición de Compra:
- sl1 = self.data.Close[-1] – slatr: Establece el stop loss por debajo del precio de cierre actual.
- tp1 = self.data.Close[-1] + slatr * TPSLRatio: Establece el take profit por encima del precio de cierre actual.
- self.buy(sl=sl1, tp=tp1, size=self.mysize): Ejecuta la orden de compra.
Ejecutando la Prueba Retroactiva:
- bt = Backtest(dfpl, MyStrat, cash=50, margin=1/100, hedging=True, exclusive_orders=False): Configura los parámetros de la prueba retroactiva.
- cash=50: Saldo inicial en efectivo.
- margin=1/100: Ratio de apalancamiento.
- hedging=True: Permite la cobertura.
- exclusive_orders=False: Permite órdenes superpuestas.
- stat = bt.run(): Ejecuta la prueba retroactiva y almacena las estadísticas.
Con esta ejecución de la prueba de backtesting, podemos evaluar el desempeño de nuestra estrategia de grid trading. El objeto stat contiene métricas detalladas e información que nos ayuda a comprender la efectividad de la estrategia.
Paso 6: Análisis de los Resultados de la Prueba Retroactiva

Resumen:
Duración: La estrategia se probó durante 57 días y 21 horas, proporcionando un período de evaluación exhaustivo.
Tiempo de Exposición: 99.14%, indicando que la estrategia estuvo casi siempre en el mercado.
Métricas de Desempeño:
- Equidad Final: $136.02, partiendo de $50.
- Pico de Equidad: $156.59, mostrando potencial para mayores ganancias.
- Retorno: 172.04%, reflejando una rentabilidad sustancial.
- Retorno de Compra y Mantener: 4.23%, demostrando el rendimiento superior de la estrategia.
- Retorno Anualizado: 37364.62%, extremadamente alto debido a la corta duración de la prueba retroactiva.
- Volatilidad Anualizada: 46309.60%, indicando alto riesgo y operaciones frecuentes.
Métricas Ajustadas por Riesgo:
- Ratio de Sharpe: 0.81, sugiriendo retornos moderados ajustados por riesgo.
- Ratio de Sortino: 1089.28, indicando una excelente gestión del riesgo a la baja.
- Ratio de Calmar: 2194.58, reflejando altos retornos en relación con la máxima caída.
Caída:
- Máxima Caída: -17.03%, una disminución significativa pero manejable.
- Caída Promedio: -1.16%, relativamente baja.
- Duración Máxima de la Caída: Casi 10 días, mostrando el período de recuperación más largo.
- Duración Promedio de la Caída: Menos de 7 horas, indicando recuperaciones rápidas.
Actividad de Trading:
- Número de Operaciones: 1698, alta frecuencia debido a la estrategia de trading en rejilla.
- Tasa de Éxito: 73.03%, una tasa de éxito muy alta.
- Mejor Operación: 0.87%, ganancias pequeñas pero consistentes.
- Peor Operación: -0.85%, pérdidas controladas.
- Operación Promedio: 0.10%, reflejando el efecto acumulativo de muchas pequeñas operaciones.











