Seleccionar página

Cómo obtener los precios de MetaTrader 5 desde Python – Parte 2

por

Apreciado trader,

En este segundo artículo de la serie nos centraremos en cómo obtener los precios de MetaTrader 5 desde python. Estos precios serán tanto en forma de datos OHLC, así como en resolución de tick.

Este punto va a ser clave para el diseño de tus estrategias en python, ya que necesitarás acceder a los precios de los distintos instrumentos (símbolos en MT5) para poder tomar tus decisiones. Si no has leído el primer artículo donde te enseño cómo conetar python y MetaTrader 5, te recomiendo comenzar por allí antes de seguir con los puntos que veremos en esta entrega.

Si eres de los que prefiere seguir este tipo de tutoriales en formato de vídeo, también hemos preparado una lista de reproducción con todos los pasos en nuestro canal de YouTube.

Por otro lado, puedes obtener también el Jupyter Notebook que hemos preparado con todo el código y sus explicaciones por si quieres ir directamente al grano.

¡Vamos a ello!

Obtención de precios de MetaTrader 5 desde python

Como bien sabes, obtener las cotizaciones de los instrumentos con los que trabaja es imprescindible para cualquier estrategia de trading automatizada. Pues esto es precisamente lo que vamos a tratar durante todo este artículo.

Nota: este proceso está únicamente dirigido a la obtención de los datos que va a necesitar una estrategia para poder operar a tiempo real y no para la obtención de series históricas para backtesting.

Es importante que recuerdes que durante toda esta sección utilizaremos la variable symbol que definiremos a continuación. Es decir, durante todo este ejemplo vamos a usar el instrumento GBPUSD. Para ello, lo definimos a continuación:

# Definimos el símbolo del que queremos obtener los datos
symbol = "GBPUSD"

Datos OHLC

Primero vamos a obtener los datos en formato OHLC (Open, High, Low, Close). Lo primero que deberemos hacer será definir la temporalidad de los datos que queramos obtener.

En este ejemplo, usaremos una frecuencia de una hora:

# Definimos la temporalidad de los datos OHLC. En este caso, usaremos velas de 1 hora.
timeframe = mt5.TIMEFRAME_H1

Seguidamente, definiremos una variable formato en la que especificaremos el formato de fecha que vamos a utilizar. También utilizaremos esta variable durante todo el artículo.

# Definimos el formato de las fechas que entraremos en forma de string: YYYY-MM-DD HH:MM:SS
formato = "%Y-%m-%d %H:%M:%S"   # Para fechas como "2023-07-20 15:30:00"

Obtener X velas desde una fecha concreta

En caso de que necesites obtener un número determinado de velas desde una fecha concreta, deberás usar la función copy_rates_from().

Nota: Para hacernos la vida más fácil, convertimos todos los datos obtenidos a un DataFrame.

El primer paso será definir la fecha de inicio y el número de velas que quieres obtener desde esa fecha hacia atrás. Es decir, si queremos 15 velas y la fecha es el 13-07-2023 a las 15:00, obtendremos las velas desde la 01:00 hasta las 15:00 del día 13: un total de 15 velas.

Veámoslo con el ejemplo en código:

# Primero definimos la fecha y el número de velas que queremos
date_from = datetime.strptime("2023-07-13 15:00:00", formato).replace(tzinfo=pytz.utc)
num_velas = 15

Seguidamente, obtendremos los datos utilizando la función copy_rates_from(), pasándole como argumentos el símbolo, la temporalidad, la fecha y la cantidad de velas. A su vez, convertiremos lo que nos devuelva la función a un DataFrame.

# Obtenemos los datos y los convertimos a un DataFrame
ohlc_data_from = pd.DataFrame(mt5.copy_rates_from(symbol, timeframe, date_from, num_velas))

Para acabar de formatear correctamente nuestro DataFrame, convertiremos la columna time, que nos será devuelta en formato timestamp con una resolución de segundos, a un objeto de tipo DateTime. También convertiremos la columna time en el índice del DataFrame.

# Convertimos la columna 'time' a datetime (ya que está en unix timestamp en segundos)
ohlc_data_from['time'] = pd.to_datetime(ohlc_data_from['time'], unit='s')

# Finalmente, convertimos la columna 'time' al índice del DataFrame
ohlc_data_from.set_index('time', inplace=True)

Para poder visualizar el resultado, podremos ejecutar el siguiente código en la celda del Notebook para que nos muestra las 5 primeras filas del DataFrame:

# Visualizamos las primeras 5 filas del DataFrame
ohlc_data_from.head()

Con el siguiente resultado:

Obtener X velas desde un índice determinado

Probablemente, la opción que más utilices sea la de obtener X velas desde un índice determinado. Puesto en un ejemplo más fácil de entender, sería el equivalente a decir que quieres obtener las últimas X velas desde la vela actual (o la vela anterior, ya cerrada).

Para poder comprender correctamente este punto, es necesario entender cómo se contabilizan los índices de las velas en la plataforma MetaTrader 5.

En MT5, los índices comienzan en 0 y van de derecha a izquierda. Es decir, la vela actual, que aún se está formando, es la vela con índice 0. Entonces, la vela anterior, que sí ha cerrado, es la 1, la anterior es la 2, y así hacia la izquierda del gráfico. Con la siguiente imagen será mucho más fácil entenderlo.

Nota: esto significa que el índice es dinámico. Cada nueva vela pasará a ser la 0, por lo que la vela que antes era la 0 ahora será la 1, la que era la 1 será la 2, etcétera.

Aunque pueda parecer que este detalle nos complica la vida, realmente nos viene muy bien. ¿Por qué? Debido a que sabemos que la última vela formada será siempre la 1, por lo que será muy fácil acceder a los datos de la vela inmediatamente anterior.

Definiremos primero el número de velas hacia atrás que queremos obtener y el índice desde el que queremos comenzar:

# Debemos recordar que el índice 0 es la vela actual
# y el índice 1 es la última vela ya formada.
desde_indice = 1
num_velas = 15

Con esta información clara, estamos ya en disposición de seguir con la obtención de los datos. Para ello, usaremos la función copy_rates_from_pos():

# Obtenemos los datos y los convertimos a un DataFrame
ohlc_data_pos = pd.DataFrame(mt5.copy_rates_from_pos(symbol, timeframe, desde_indice, num_velas))
ohlc_data_pos['time'] = pd.to_datetime(ohlc_data_pos['time'], unit='s')
ohlc_data_pos.set_index('time', inplace=True)

# Visualizamos los datos
ohlc_data_pos.head()

Nota: Será recomendable acceder a los datos a partir del índice 1, ya que si accedemos a partir del índice 0 (la vela actual), los datos de esta vela irán cambiando mientras se va formando.

Obtener las velas comprendidas entre dos fechas

En caso de que quisieras poder definir un rango de velas que quisieras obtener utilizando fechas, lo podrías hacer con la función copy_rates_range(). En este caso, deberías definir una fecha de inicio y otra de final:

# Definimos las dos fechas de inicio y de fin
date_ini = datetime.strptime("2023-07-13 15:00:00", formato).replace(tzinfo=pytz.utc)
date_end = datetime.strptime("2023-07-17 15:00:00", formato).replace(tzinfo=pytz.utc)

Fíjate en que seguimos utilizando la variable formato que habíamos definido en la sección anterior.

Seguidamente, estaremos en disposición de obtener los datos y convertirlos directamente a un DataFrame, formateando la columna time como en el caso anterior y finalmente visualizando los datos:

# Obtenemos los datos y los convertimos a un DataFrame
ohlc_data_range = pd.DataFrame(mt5.copy_rates_range(symbol, timeframe, date_ini, date_end))
ohlc_data_range['time'] = pd.to_datetime(ohlc_data_range['time'], unit='s')
ohlc_data_range.set_index('time', inplace=True)

# Visualizamos los datos
ohlc_data_range.head()

Datos Tick

Llega el momento de obtener también los datos en resolución de tick desde la plataforma MetaTrader 5 a nuestro programa en python. Como en el caso anterior, vamos a convertir también todos los datos recibidos a un pandas DataFrame.

Obtener X ticks desde una fecha concreta

Como ocurría en el caso de las velas en formato OHLC, también podemos recuperar un número determinado de ticks desde una fecha y una hora concretas.

Para ello, deberemos definir primero el número de ticks que queremos, y después usaremos la función copy_ticks_from() para conseguirlos.

# Definimos el número de ticks que queremos
num_ticks = 5000

# Obtenemos los datos y los convertimos a un DataFrame
tick_data_from = pd.DataFrame(mt5.copy_ticks_from(symbol, date_ini, num_ticks, mt5.COPY_TICKS_ALL))

Fíjate en que siempre que trabajes con datos tick, deberás pasarle como último argumento a las funciones la constante mt5.COPY_TICKS_ALL.

Seguidamente, convertimos de nuevo la información sobre la hora a la que ese tick se generó a un formato que podamos utilizar. La diferencia en este caso es que deberemos utilizar la columna time_msc.

Esto es así debido a que la frecuencia en la que recibimos los ticks es muy alta, siendo habitual recibir más de un tick por segundo. Por esta razón, usando una resolución de milisegundos obtendremos una representación de la información correcta.

Para ello, deberemos tener en cuenta que las unidades a la hora de convertir los datos a un objeto datetime ya no serán segundos (‘s’), sino milisegundos (‘ms’).

# Convertimos el unix timestamp a datetime. A tener en cuenta que ahora viene dado en milisegundos
tick_data_from['time_msc'] = pd.to_datetime(tick_data_from['time_msc'], unit='ms')
tick_data_from.set_index('time_msc', inplace=True)

Finalmente, podemos visualizar los datos:

# Visualizamos los datos
tick_data_from.head()

Obtener los ticks comprendidos entre dos fechas

En este caso, vamos a recopilar todos los ticks ocurridos en un instrumento entre dos fechas concretas. Para ello, podemos reutilizar el código donde habíamos definido las variables date_ini y date_end. Sin embargo, te lo copio aquí de nuevo:

# Definimos las dos fechas de inicio y de fin
date_ini = datetime.strptime("2023-07-13 15:00:00", formato).replace(tzinfo=pytz.utc)
date_end = datetime.strptime("2023-07-17 15:00:00", formato).replace(tzinfo=pytz.utc)

Ahora, podremos utilizar la función copy_ticks_range() para poder recuperar estos datos:

# Obtenemos los datos y los convertimos a un DataFrame
tick_data_range = pd.DataFrame(mt5.copy_ticks_range(symbol, date_ini, date_end, mt5.COPY_TICKS_ALL))
tick_data_range['time_msc'] = pd.to_datetime(tick_data_range['time_msc'], unit='ms')
tick_data_range.set_index('time_msc', inplace=True)

Calcular el spread de manera vectorizada

Otra cosa interesante que podríamos hacer con los datos tick es calcular el spread (la diferencia entre el precio Ask y el Bid).

Al tener todos los datos en un DataFrame, podemos realizar esta operación de manera muy rápida y eficiente en todos los ticks de los que dispongamos. Esto lo conseguimos creando la operación de manera vectorizada, lo que nos permite recurrir al intérprete CPython para multiplicar la velocidad de nuestros cálculos.

# Calculamos el spread de los datos tick
tick_data_range['spread'] = tick_data_range['ask'] - tick_data_range['bid']

# Visualizamos los datos
tick_data_range.head()

Conclusión

¡Y hasta aquí esta segunda entrega! Ahora ya sabes cómo obtener los datos OHLC y tick desde python a través de la plataforma MT5, cómo guardarlos en un DataFrame bien formateado y cómo calcular el spread.

¿Consideras este proceso largo o difícil? Hemos automatizado todo en nuestra herramienta Quantdle. Descarga los datos que deseas en un click y ahórrate tiempo y posibles fallos.

En el siguiente y último artículo de la serie, veremos todo lo relevante con el envío y ejecución de órdenes, deals y gestión de posiciones. También veremos cómo cerrar las posiciones abiertas, cancelar cualquier órden pendiente y gestionar posibles errores de ejecución.

Como siempre, si tienes cualquier duda, puedes ponerte en contacto con nostros a través de nuestro formulario o de la comunidad de Discord.

Gracias por tu lectura.

Un saludo,

Martí

Escrito por Marti Castany

Martí Castany es Quant Researcher, Portfolio Manager y Co-Fundador de KomaLogic, una firma de trading algorítmico. Con más de una década de experiencia en el sector de la inversión sistemática, Martí se ha destacado en el desarrollo de modelos estadísticos, análisis y limpieza de datos y gestión cuantitativa del riesgo.

25 Jul, 2023

También puede interesarte…