English Русский 中文 Deutsch 日本語 Português
preview
Integración de MQL5 con paquetes de procesamiento de datos (Parte 3): Visualización mejorada de datos

Integración de MQL5 con paquetes de procesamiento de datos (Parte 3): Visualización mejorada de datos

MetaTrader 5Ejemplos |
255 4
Hlomohang John Borotho
Hlomohang John Borotho

Introducción

Los operadores de los mercados financieros a menudo se enfrentan al reto de dar sentido a grandes cantidades de datos, desde las fluctuaciones de precios y los volúmenes de negociación hasta los indicadores técnicos y las noticias económicas. Con la velocidad y complejidad de los mercados modernos, resulta abrumador interpretar estos flujos de datos de manera eficaz utilizando métodos tradicionales. Los gráficos por sí solos pueden no proporcionar suficiente información, lo que puede llevar a perder oportunidades o a tomar decisiones inoportunas. La necesidad de identificar rápidamente las tendencias, los cambios de tendencia y los riesgos potenciales aumenta la dificultad. Para los operadores que buscan tomar decisiones informadas y basadas en datos, la incapacidad de extraer información clave de los datos es un problema crítico que puede dar lugar a pérdidas de beneficios o a un aumento de los riesgos.

La visualización mejorada de datos aborda este reto transformando los datos financieros sin procesar en representaciones visuales más intuitivas e interactivas. Herramientas como los gráficos dinámicos de velas japonesas, las superposiciones de indicadores técnicos y los mapas de calor de los rendimientos proporcionan a los operadores una comprensión más profunda y práctica de las condiciones del mercado. Al integrar elementos visuales que resaltan tendencias, correlaciones y anomalías, los operadores pueden detectar rápidamente oportunidades y tomar decisiones mejor informadas. Este enfoque mejorado ayuda a reducir la complejidad de la interpretación de los datos, lo que permite a los operadores actuar con mayor confianza y eficiencia en los mercados financieros, que evolucionan rápidamente.


Recopilar datos históricos

from datetime import datetime
import MetaTrader5 as mt5
import pandas as pd
import pytz

# Display data on the MetaTrader 5 package
print("MetaTrader5 package author: ", mt5.__author__)
print("MetaTrader5 package version: ", mt5.__version__)

# Configure pandas display options
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1500)

# Establish connection to MetaTrader 5 terminal
if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

# Set time zone to UTC
timezone = pytz.timezone("Etc/UTC")
# Create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2024, 1, 2, tzinfo=timezone)
utc_to = datetime.now(timezone)  # Set to the current date and time

# Get bars from XAUUSD H1 (hourly timeframe) within the specified interval
rates = mt5.copy_rates_range("XAUUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)

# Shut down connection to the MetaTrader 5 terminal
mt5.shutdown()

# Check if data was retrieved
if rates is None or len(rates) == 0:
    print("No data retrieved. Please check the symbol or date range.")
else:
    # Display each element of obtained data in a new line (for the first 10 entries)
    print("Display obtained data 'as is'")
    for rate in rates[:10]:
        print(rate)

    # Create DataFrame out of the obtained data
    rates_frame = pd.DataFrame(rates)
    # Convert time in seconds into the 'datetime' format
    rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')

    # Save the data to a CSV file
    filename = "XAUUSD_H1_2nd.csv"
    rates_frame.to_csv(filename, index=False)
    print(f"\nData saved to file: {filename}")

Para recuperar datos históricos, primero establecemos una conexión con el terminal MetaTrader 5 utilizando la función `mt5.initialize()`. Esto es esencial porque el paquete Python se comunica directamente con la plataforma MetaTrader 5 en ejecución. Configuramos el código para establecer el intervalo de tiempo deseado para la extracción de datos especificando las fechas de inicio y finalización. Los objetos `datetime` se crean en la zona horaria UTC para garantizar la coherencia entre las diferentes zonas horarias. A continuación, el script utiliza la función `mt5.copy-rates-range()` para solicitar datos históricos por hora para el símbolo XAUUSD, desde el 2 de enero de 2024 hasta la fecha y hora actuales.

Después de obtener los datos históricos, nos desconectamos de forma segura del terminal MetaTrader 5 utilizando `mt5.shutdown()` para evitar cualquier conexión innecesaria adicional.. Los datos recuperados se muestran inicialmente en su formato sin procesar para confirmar que la extracción de datos se ha realizado correctamente. Convertimos estos datos en un DataFrame de pandas para facilitar su manipulación y análisis. Además, el código convierte las marcas de tiempo Unix en un formato de fecha y hora legible, lo que garantiza que los datos estén bien estructurados y listos para su posterior procesamiento o análisis. Este enfoque permite a los operadores analizar los movimientos históricos del mercado y tomar decisiones de trading informadas basadas en el rendimiento pasado.

filename = "XAUUSD_H1_2nd.csv"
rates_frame.to_csv(filename, index=False)
print(f"\nData saved to file: {filename}")

Como mi sistema operativo es Linux, tengo que guardar los datos recibidos en un archivo. Pero para aquellos que utilizan Windows, pueden recuperar los datos fácilmente con el siguiente script:

from datetime import datetime
import MetaTrader5 as mt5
import pandas as pd
import pytz

# Display data on the MetaTrader 5 package
print("MetaTrader5 package author: ", mt5.__author__)
print("MetaTrader5 package version: ", mt5.__version__)

# Configure pandas display options
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1500)

# Establish connection to MetaTrader 5 terminal
if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

# Set time zone to UTC
timezone = pytz.timezone("Etc/UTC")
# Create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2024, 1, 2, tzinfo=timezone)
utc_to = datetime.now(timezone)  # Set to the current date and time

# Get bars from XAUUSD H1 (hourly timeframe) within the specified interval
rates = mt5.copy_rates_range("XAUUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)

# Shut down connection to the MetaTrader 5 terminal
mt5.shutdown()

# Check if data was retrieved
if rates is None or len(rates) == 0:
    print("No data retrieved. Please check the symbol or date range.")
else:
    # Display each element of obtained data in a new line (for the first 10 entries)
    print("Display obtained data 'as is'")
    for rate in rates[:10]:
        print(rate)

    # Create DataFrame out of the obtained data
    rates_frame = pd.DataFrame(rates)
    # Convert time in seconds into the 'datetime' format
    rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')

    # Display data directly
    print("\nDisplay dataframe with data")
    print(rates_frame.head(10))

Y si por alguna razón no puede obtener los datos históricos, puede recuperarlos manualmente en su plataforma MetTrader 5 siguiendo los siguientes pasos. Inicie su plataforma MetaTrader y, en la parte superior del panel de MetaTrader 5, vaya a > Herramientas y, a continuación, a > Opciones y accederá a las opciones de Gráficos. A continuación, deberá seleccionar el número de barras del gráfico que desea descargar. Es mejor elegir la opción de barras ilimitadas, ya que trabajaremos con fechas y no sabremos cuántas barras hay en un periodo de tiempo determinado.

Después de eso, ahora tendrás que descargar los datos reales. Para ello, deberá ir a > Ver y, a continuación, a > Símbolos, y accederá a la pestaña Especificaciones. Simplemente navega a Barras o Ticks dependiendo del tipo de datos que quieras descargar. Continúe e introduzca el periodo de fechas de inicio y finalización de los datos históricos que desea descargar. A continuación, haga clic en el botón de solicitud para descargar los datos y guardarlos en formato CSV.


Visualización de datos de MetaTrader 5 en Jupyter Lab

Para cargar los datos históricos de MetaTrader 5 en Jupyter Lab, primero debe localizar la carpeta donde se descargaron los datos. Una vez en Jupyter Lab, navega hasta esa carpeta para acceder a los archivos. El siguiente paso es cargar los datos y revisar los nombres de las columnas. Es importante revisar los nombres de las columnas para asegurarse de que los datos se gestionan correctamente y evitar errores que podrían producirse por utilizar nombres de columna incorrectos.

Código Python:

import pandas as pd

# assign variable to the historical data
file_path = '/home/int_junkie/Documents/ML/predi/XAUUSD.m_H1_2nd.csv'

data = pd.read_csv(file_path, delimiter='\t')

# Display the first few rows and column names
print(data.head())
print(data.columns)



Nuestros datos históricos comienzan el 2 de enero de 2024 y llegan hasta la actualidad.

Código Python:

# Convert the <DATE> and <TIME> columns into a single datetime column
data['<DATETIME>'] = pd.to_datetime(data['<DATE>'] + ' ' + data['<TIME>'], format='%Y.%m.%d %H:%M:%S')

# Drop the original <DATE> and <TIME> columns
data = data.drop(columns=['<DATE>', '<TIME>'])

# Convert numeric columns from strings to appropriate float types
numeric_columns = ['<OPEN>', '<HIGH>', '<LOW>', '<CLOSE>', '<TICKVOL>', '<VOL>', '<SPREAD>']
data[numeric_columns] = data[numeric_columns].apply(pd.to_numeric)

# Set datetime as index for easier plotting
data.set_index('<DATETIME>', inplace=True)

# Let's plot the close price and tick volume to visualize the trend
import matplotlib.pyplot as plt

# Plot closing price and tick volume
fig, ax1 = plt.subplots(figsize=(12, 6))

# Close price on primary y-axis
ax1.set_xlabel('Date')
ax1.set_ylabel('Close Price', color='tab:blue')
ax1.plot(data.index, data['<CLOSE>'], color='tab:blue', label='Close Price')
ax1.tick_params(axis='y', labelcolor='tab:blue')

# Tick volume on secondary y-axis
ax2 = ax1.twinx()  
ax2.set_ylabel('Tick Volume', color='tab:green')  
ax2.plot(data.index, data['<TICKVOL>'], color='tab:green', label='Tick Volume')
ax2.tick_params(axis='y', labelcolor='tab:green')

# Show the plot
plt.title('Close Price and Tick Volume Over Time')
fig.tight_layout()
plt.show()

Precio de cierre y volumen de ticks

El gráfico anterior muestra dos métricas clave a lo largo del tiempo:

  1. Precio de cierre (en azul): Representa el precio de cierre de cada hora en el gráfico. Podemos observar fluctuaciones a lo largo del tiempo, lo que indica períodos de tendencias tanto al alza como a la baja en el precio del oro (XAU/USD).
  2. Volumen de ticks (en verde): Indica la cantidad de cambios de precio dentro de cada hora. Los picos en el volumen de ticks a menudo corresponden a una mayor actividad o volatilidad del mercado. Por ejemplo, períodos de alto volumen pueden coincidir con movimientos de precios significativos, lo que podría indicar eventos importantes o cambios en el sentimiento del mercado.

Ahora profundicemos en nuestros datos:

1. Análisis de tendencias.

# Calculating moving averages: 50-period and 200-period for trend analysis
data['MA50'] = data['<CLOSE>'].rolling(window=50).mean()
data['MA200'] = data['<CLOSE>'].rolling(window=200).mean()

# Plot close price along with the moving averages
plt.figure(figsize=(12, 6))

# Plot close price
plt.plot(data.index, data['<CLOSE>'], label='Close Price', color='blue')

# Plot moving averages
plt.plot(data.index, data['MA50'], label='50-Period Moving Average', color='orange')
plt.plot(data.index, data['MA200'], label='200-Period Moving Average', color='red')

plt.title('Close Price with 50 & 200 Period Moving Averages')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend(loc='best')
plt.grid(True)
plt.show()


El gráfico muestra el precio de cierre junto con las medias móviles de 50 y 200 períodos:

  1. Precio de cierre (línea azul): Representa el precio real al final de cada período de tiempo.
  2. Media móvil de 50 períodos (línea naranja): una media móvil a corto plazo que suaviza los datos de precios a lo largo de 50 períodos. Cuando el precio de cierre cruza por encima de esta línea, puede indicar una posible tendencia alcista, y cuando cruza por debajo, puede indicar una tendencia bajista.
  3. Media móvil de 200 períodos (línea roja): una media móvil a más largo plazo, que proporciona información sobre la tendencia general. Tiende a reaccionar más lentamente a los cambios de precios, por lo que los cruces con la media móvil de 50 períodos pueden indicar importantes cambios de tendencia a largo plazo.

El punto clave que buscamos es la cruz dorada, cuando la media móvil de 50 períodos cruza por encima de la media móvil de 200 períodos, lo que puede indicar una posible tendencia alcista fuerte. El último es la «cruz de la muerte», que se produce cuando la media móvil de 50 períodos cruza por debajo de la media móvil de 200 períodos, lo que puede indicar una posible tendencia bajista.

A continuación, analizamos la volatilidad calculando el rango de precios (diferencia entre el máximo y el mínimo) y lo visualizamos.

2. Análisis de volatilidad.

# Calculate the price range (High - Low)
data['Price_Range'] = data['<HIGH>'] - data['<LOW>']

# Calculate Bollinger Bands
# Use a 20-period moving average and 2 standard deviations
data['MA20'] = data['<CLOSE>'].rolling(window=20).mean()
data['BB_upper'] = data['MA20'] + 2 * data['<CLOSE>'].rolling(window=20).std()
data['BB_lower'] = data['MA20'] - 2 * data['<CLOSE>'].rolling(window=20).std()

# Plot the price range and Bollinger Bands along with the close price
plt.figure(figsize=(12, 8))

# Plot the close price
plt.plot(data.index, data['<CLOSE>'], label='Close Price', color='blue')

# Plot Bollinger Bands
plt.plot(data.index, data['BB_upper'], label='Upper Bollinger Band', color='red', linestyle='--')
plt.plot(data.index, data['BB_lower'], label='Lower Bollinger Band', color='green', linestyle='--')

# Fill the area between Bollinger Bands for better visualization
plt.fill_between(data.index, data['BB_upper'], data['BB_lower'], color='gray', alpha=0.3)

# Plot the price range on a separate axis
plt.figure(figsize=(12, 6))
plt.plot(data.index, data['Price_Range'], label='Price Range (High-Low)', color='purple')

plt.title('Bollinger Bands and Price Range (Volatility Analysis)')
plt.xlabel('Date')
plt.ylabel('Price')
plt.legend(loc='best')
plt.grid(True)
plt.show()



A partir de los resultados anteriores, hemos realizado lo siguiente:

  1. Cálculo del rango de precios: Calculamos el rango de precios para cada periodo de tiempo restando el precio más bajo (`<LOW>`) del precio más alto ('<HIGH>`). Esto indica el alcance de la variación del precio durante cada hora, lo que ayuda a medir la volatilidad de ese periodo.
  2. `data['Price-Range'] = data['<HIGH>'] - data['<LOW>'] : The resulting `Price-Range` column shows how much the price fluctuated within each hour.
  3. Cálculo de las Bandas de Bollinger: El indicador de Bandas de Bollinger se calcula para obtener información sobre la volatilidad de los precios. `MA-20` Es la media móvil de 20 períodos de los precios de cierre. Sirve como línea media de las Bandas de Bollinger. `BB-upper` y `BB-lower` representan las bandas superior e inferior, respectivamente. Se calculan como dos desviaciones estándar por encima y por debajo de la media móvil de 20 períodos. Cuando los precios se mueven hacia la banda superior, esto indica que el mercado podría estar sobrecomprado; del mismo modo, los movimientos hacia la banda inferior sugieren que el mercado podría estar sobrevendido.
  4. Visualización del precio y las bandas de Bollinger: Precio de cierre (línea azul), representa el precio de cierre real para cada período de tiempo. Bandas de Bollinger superior (línea discontinua roja) y Bandas de Bollinger inferior (línea discontinua verde), estas líneas muestran las bandas de volatilidad que forman las Bandas de Bollinger alrededor de la media móvil. El área sombreada, el área entre las Bandas de Bollinger superior e inferior, está sombreada en gris, lo que representa visualmente el rango de volatilidad dentro del cual se espera que se mueva el precio.
  5. Gráfico de rango de precios separado: El segundo gráfico muestra el rango de precios como una línea morada separada, que muestra cómo fluctúa la volatilidad a lo largo del tiempo. Los picos más grandes en este gráfico indican períodos de mayor volatilidad.
Creación de un modelo de aprendizaje por refuerzo:
    import pandas as pd
    import talib
    from stable_baselines3 import DQN
    from stable_baselines3.common.env_checker import check_env
    
    # Verify the environment
    check_env(env)
    
    # Initialize and train the DQN model
    model = DQN('MlpPolicy', env, verbose=1)
    model.learn(total_timesteps=10000)
    
    # Save the trained model
    model.save("trading_dqn_model")
    
    # Load and preprocess the data as before
    data = pd.read_csv('XAUUSD_H1_Data-V.csv', delimiter='\t')
    data['<DATETIME>'] = pd.to_datetime(data['<DATE>'] + ' ' + data['<TIME>'], format='%Y.%m.%d %H:%M:%S')
    data = data.drop(columns=['<DATE>', '<TIME>'])
    numeric_columns = ['<OPEN>', '<HIGH>', '<LOW>', '<CLOSE>', '<TICKVOL>', '<VOL>', '<SPREAD>']
    data[numeric_columns] = data[numeric_columns].apply(pd.to_numeric)
    data.set_index('<DATETIME>', inplace=True)
    
    # Calculate Bollinger Bands (20-period moving average with 2 standard deviations)
    data['MA20'] = data['<CLOSE>'].rolling(window=20).mean()
    data['BB_upper'] = data['MA20'] + 2 * data['<CLOSE>'].rolling(window=20).std()
    data['BB_lower'] = data['MA20'] - 2 * data['<CLOSE>'].rolling(window=20).std()
    
    # Calculate percentage price change and volume change as additional features
    data['Pct_Change'] = data['<CLOSE>'].pct_change()
    data['Volume_Change'] = data['<VOL>'].pct_change()
    
    # Fill missing values
    data.fillna(0, inplace=True)

    En el código anterior, preparamos datos financieros para un modelo de aprendizaje por refuerzo (Reinforcement Learning, RL) con el fin de tomar decisiones de trading en el mercado XAU/USD (oro) utilizando un algoritmo DQN (Deep Q-Network) de `stable-baseline3`. Primero carga y procesa datos históricos, incluyendo el cálculo de las bandas de Bollinger (un indicador técnico basado en medias móviles y volatilidad de precios) y añade características como los cambios porcentuales en el precio y el volumen. Se valida el entorno y se entrena un modelo DQN durante 100.000 pasos temporales, tras lo cual se guarda el modelo para su uso futuro. Por último, los datos que faltan se rellenan con ceros para garantizar un entrenamiento fluido del modelo.

    import gym
    from gym import spaces
    import numpy as np
    
    class TradingEnv(gym.Env):
        def __init__(self, data):
            super(TradingEnv, self).__init__()
            
            # Market data and feature columns
            self.data = data
            self.current_step = 0
            
            # Define action and observation space
            # Actions: 0 = Hold, 1 = Buy, 2 = Sell
            self.action_space = spaces.Discrete(3)
            
            # Observations (features: Bollinger Bands, Price Change, Volume Change)
            self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(5,), dtype=np.float32)
            
            # Initial balance and positions
            self.balance = 10000  # Starting balance
            self.position = 0  # No position at the start (0 = no trade, 1 = buy, -1 = sell)
        
        def reset(self):
            self.current_step = 0
            self.balance = 10000
            self.position = 0
            return self._next_observation()
        
        def _next_observation(self):
            # Get the current market data (Bollinger Bands, Price Change, Volume Change)
            obs = np.array([
                self.data['BB_upper'].iloc[self.current_step],
                self.data['BB_lower'].iloc[self.current_step],
                self.data['Pct_Change'].iloc[self.current_step],
                self.data['Volume_Change'].iloc[self.current_step],
                self.position
            ])
            return obs
        
        def step(self, action):
            # Execute the trade based on action and update balance and position
            self.current_step += 1
            
            # Get current price
            current_price = self.data['<CLOSE>'].iloc[self.current_step]
            
            reward = 0  # Reward initialization
            done = self.current_step == len(self.data) - 1  # Check if we're done
            
            # Buy action
            if action == 1 and self.position == 0:
                self.position = 1
                self.entry_price = current_price
            
            # Sell action
            elif action == 2 and self.position == 1:
                reward = current_price - self.entry_price
                self.balance += reward
                self.position = 0
            
            # Hold action
            else:
                reward = 0
            
            return self._next_observation(), reward, done, {}
        
        def render(self, mode='human', close=False):
            # Optional: Print the current balance and position
            print(f"Step: {self.current_step}, Balance: {self.balance}, Position: {self.position}")
    
    # Create the trading environment
    env = TradingEnv(data)

    A partir del código anterior, definimos una clase de entorno comercial `TradingEnv` usando la biblioteca `gym` para simular un entorno comercial basado en datos históricos del mercado. El entorno permite tres acciones posibles: retener, comprar o vender. Incluye un espacio de observación con cinco características (Bandas de Bollinger, cambio porcentual de precio, cambio de volumen y posición comercial actual). El agente comienza con un saldo de 10.000 unidades y sin posición. En cada paso, en función de la acción seleccionada, el entorno actualiza la posición y el saldo del agente, calcula las recompensas por las operaciones rentables y avanza al siguiente paso en los datos. El entorno puede reiniciarse para iniciar un nuevo episodio o mostrar el estado actual del proceso comercial. Este entorno se utilizará para entrenar modelos de aprendizaje de refuerzo.

    import gymnasium as gym
    from gymnasium import spaces
    import numpy as np
    from stable_baselines3 import DQN
    from stable_baselines3.common.env_checker import check_env
    
    # Define the custom Trading Environment
    class TradingEnv(gym.Env):
        def __init__(self, data):
            super(TradingEnv, self).__init__()
            
            # Market data and feature columns
            self.data = data
            self.current_step = 0
            
            # Define action and observation space
            # Actions: 0 = Hold, 1 = Buy, 2 = Sell
            self.action_space = spaces.Discrete(3)
            
            # Observations (features: Bollinger Bands, Price Change, Volume Change)
            self.observation_space = spaces.Box(low=-np.inf, high=np.inf, shape=(5,), dtype=np.float32)
            
            # Initial balance and positions
            self.balance = 10000  # Starting balance
            self.position = 0  # No position at the start (0 = no trade, 1 = buy, -1 = sell)
    
        def reset(self, seed=None, options=None):
            # Initialize the random seed
            self.np_random, seed = self.seed(seed)
            
            self.current_step = 0
            self.balance = 10000
            self.position = 0
            
            # Return initial observation and an empty info dictionary
            return self._next_observation(), {}
    
        def _next_observation(self):
            # Get the current market data (Bollinger Bands, Price Change, Volume Change)
            obs = np.array([
                self.data['BB_upper'].iloc[self.current_step],
                self.data['BB_lower'].iloc[self.current_step],
                self.data['Pct_Change'].iloc[self.current_step],
                self.data['Volume_Change'].iloc[self.current_step],
                self.position
            ], dtype=np.float32)  # Explicitly cast to float32
            return obs
        
        def step(self, action):
            self.current_step += 1
            current_price = self.data['<CLOSE>'].iloc[self.current_step]
            
            reward = 0
            done = self.current_step == len(self.data) - 1
            truncated = False  # Set to False unless there's an external condition to end the episode early
            
            # Execute the action
            if action == 1 and self.position == 0:
                self.position = 1
                self.entry_price = current_price
            
            elif action == 2 and self.position == 1:
                reward = current_price - self.entry_price
                self.balance += reward
                self.position = 0
            
            # Return next observation, reward, terminated, truncated, and an empty info dict
            return self._next_observation(), reward, done, truncated, {}
    
        def render(self, mode='human', close=False):
            print(f"Step: {self.current_step}, Balance: {self.balance}, Position: {self.position}")
        
        def seed(self, seed=None):
            self.np_random, seed = gym.utils.seeding.np_random(seed)
            return self.np_random, seed
    
    # Assuming your data is already prepared (as a DataFrame) and includes Bollinger Bands and other necessary features
    # Create the environment
    env = TradingEnv(data)
    
    # Verify the environment
    check_env(env)
    
    # Train the model using DQN
    model = DQN('MlpPolicy', env, verbose=1)
    model.learn(total_timesteps=10000)
    
    # Save the trained model
    model.save("trading_dqn_model")
    

    Salida:

    Modelo

    Luego definimos un entorno comercial personalizado utilizando "gymnasium" para el aprendizaje de refuerzo, donde un agente aprende a tomar decisiones comerciales basadas en datos históricos del mercado. El entorno permite tres acciones: mantener, comprar o vender, y presenta cinco observaciones, incluidas las bandas de Bollinger, el cambio porcentual de precio, el cambio de volumen y la posición actual. El agente comienza con un saldo de 10.000 y sin posiciones abiertas. Cada paso en el entorno hace avanzar al agente, actualizando su posición, saldo y calculando recompensas por operaciones exitosas. El entorno se valida utilizando la función `check-Env()` de `stable-baseline3`, y se entrena un modelo DQN (Deep Q-Network) durante 10.000 pasos de tiempo para aprender estrategias comerciales óptimas. El modelo entrenado se guarda para uso futuro en sistemas de comercio automatizado.

    # Unpack the observation from the reset() method
    obs, _ = env.reset()
    
    # Loop through the environment steps
    for step in range(len(data)):
        # Predict the action based on the observation
        action, _states = model.predict(obs)
        
        # Step the environment
        obs, rewards, done, truncated, info = env.step(action)
        
        # Render the environment (print the current state)
        env.render()
    
        # Check if the episode is done
        if done or truncated:
            print("Testing completed!")
            break
    

    Capital inicial

    Equidad final

    El resultado indica que la estrategia de negociación implementada por el modelo entrenado generó una pequeña ganancia durante el período de negociación, con un aumento en el saldo de 10.000$ a 10.108$. Si bien esto sugiere que el modelo fue capaz de identificar operaciones rentables, el margen de beneficio de 108 dólares (una ganancia del 1,08 %) es relativamente modesto.

    Los resultados implican que el modelo está realizando operaciones cautelosas o de baja frecuencia, o podrían indicar que la estrategia no está totalmente optimizada para obtener mayores retornos. Una evaluación adicional, que incluya un período de prueba más largo o ajustes a los parámetros del modelo (como la función de recompensa, la selección de características o la lógica de ejecución comercial), podría ayudar a mejorar el rendimiento del modelo. También es importante considerar factores como los costos de transacción y la gestión de riesgos para garantizar que la estrategia siga siendo rentable a lo largo del tiempo.

    Juntándolo todo en MQL5

    Vamos a conectar MQL5 al script de Python que ejecutará nuestro modelo entrenado, tendremos que configurar un canal de comunicación entre MQL5 y Python. En nuestro caso utilizaremos un servidor de socket, que es el de uso común.
    //+------------------------------------------------------------------+
    //|                                                   EnhancedML.mq5 |
    //|                                  Copyright 2024, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2024, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    //+------------------------------------------------------------------+
    //|                          Includes                                |
    //+------------------------------------------------------------------+
    #include <WinAPI\winapi.mqh>
    #include <Trade\Trade.mqh>
    CTrade              trade;

    En primer lugar, incluimos la biblioteca API de Windows (`winapi.mqh`) para operaciones a nivel de sistema y la biblioteca comercial (`trade.mqh`) para la gestión comercial. Luego declaramos una instancia de la clase (`CTrade`), llamada trade.

    //+------------------------------------------------------------------+
    //|                          Global Vars                             |
    //+------------------------------------------------------------------+
    int stopLoss = 350;
    int takeProfit = 500;
    
    string Address = "127.0.0.1";
    int port = 9999;
    int socket = SocketCreate();
    
    double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

    Establecemos la `Address` en `"127.0.0.1"` (host local) y el `port` en "9999", que usaremos para la comunicación del socket. La función `SocketCreate()` inicializa un socket y almacena el controlador del socket en la variable `socket`, lo que permite la comunicación con el servidor Python.

    //+------------------------------------------------------------------+
    //| Expert initialization function                                   |
    //+------------------------------------------------------------------+
    int OnInit(){
    
        if (!SocketConnect(socket, Address, port, 1000)){  
            Print("Successfully connected to ", Address, ":", port);
        } else {
            Print("Connection to ", Address, ":", port, " failed, error ", GetLastError());
            return INIT_FAILED;
        }
    
       return(INIT_SUCCEEDED);
    }

    La función `SocketConnect()` intenta conectar el socket creado al servidor Python en la dirección dada (host local) y el puerto `9999`. Nuestro tiempo de espera en milisegundos es `1000`.

    //+------------------------------------------------------------------+
    //| Expert tick function                                             |
    //+------------------------------------------------------------------+
    void OnTick(){
       
       
       uint len=SocketIsReadable(socket);
    
       char buffer[16];
       int bytes = SocketRead(socket, buffer, len, 23);
       
       if (bytes > 0){
       
          string action_str = CharArrayToString(buffer);
          
          int action = StringToInteger(action_str);
          //int action = atoi(buffer);
          
          // Execute a trade based on action
          if(action == 1){
             //buy trade
             MBuy();
             Print("Buy action received...");
          } else if(action == 2){
             //sell trade
             MSell();
             Print("Sell action received...");
          }
       }
       
    }

    La función `OnTick()` de nuestro MQL5 está diseñada para procesar las instrucciones de trading entrantes en cada tick del mercado a través de una conexión de socket. Comienza comprobando si hay datos disponibles en el socket utilizando `SocketIsReadable()`, que devuelve la longitud de los datos. Si hay datos disponibles, la función `SocketRead()` lee los datos en un búfer y el número de bytes leídos correctamente se almacena en `bytes`. Si se han recibido datos, el búfer se convierte en una cadena y, a continuación, en un entero (acción). En función del valor de «action», la función ejecuta la operación correspondiente: si «action == 1», se ejecuta una operación de compra llamando a «MBuy()», y si «action == 2», se activa una operación de venta llamando a «MSell()». Después de cada operación, una instrucción de impresión registra la acción recibida (compra o venta). En esencia, la función escucha las órdenes de compra o venta a través del socket y ejecuta automáticamente las operaciones correspondientes.

    //+------------------------------------------------------------------+
    //|                        Buy Function                              |
    //+------------------------------------------------------------------+
    void MBuy(){
    
       static int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    
       double Lots = 0.02;
       double sl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID) - stopLoss, digits);
       double tp = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK) + takeProfit * _Point, digits);
       trade.PositionOpen(_Symbol, ORDER_TYPE_BUY, Lots, Ask, sl, tp);
    }

    Función para abrir operaciones de compra.

    //+------------------------------------------------------------------+
    //|                         Sell Function                            |
    //+------------------------------------------------------------------+
    void MSell(){
       static int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    
       double Lots = 0.02;
       double sl = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK) + stopLoss, digits);
       double tp = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID) - takeProfit * _Point, digits);
       trade.PositionOpen(_Symbol, ORDER_TYPE_SELL, Lots, Bid, sl, tp);
    }

    Función para abrir operaciones de venta.


    Script de servidor de socket Python (servidor de modelo de negociación)

    Nuestro script Python cargará el modelo entrenado y configurará un servidor socket para escuchar las conexiones desde MQL5. Cuando recibe datos, hará una predicción y enviará la acción comercial.

    import socket
    import numpy as np
    from stable_baselines3 import DQN
    
    # Load the trained model
    model = DQN.load("trading_dqn_model")
    
    # Set up the server
    HOST = '127.0.0.1'  # Localhost (you can replace this with your IP)
    PORT = 9999         # Port to listen on
    
    # Create a TCP/IP socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.bind((HOST, PORT))
    server_socket.listen(1)
    
    print(f"Server listening on {HOST}:{PORT}...")
    
    while True:
        # Wait for a connection
        client_socket, client_address = server_socket.accept()
        print(f"Connection from {client_address}")
        
        # Receive data from MQL5 (price data sent by EA)
        data = client_socket.recv(1024).decode('utf-8')
        
        if data:
            print(f"Received data: {data}")
            
            # Convert received data to a numpy array
            observation = np.fromstring(data, sep=',')  # Assumes comma-separated price data
    
            # Make prediction using the model
            action, _ = model.predict(observation)
            
            # Send the predicted action back to MQL5
            client_socket.send(str(action).encode('utf-8'))
        
        # Close the client connection
        client_socket.close()
    

    Guarde el script de Python como `trading-model-server.py` o cualquier nombre de su elección. Abra su terminal o símbolo del sistema, navegue hasta el directorio en el que guarda su modelo y `trading-model-server.py` y ejecute lo siguiente para establecer una conexión.

    python trading_model_server.py
    


    Conclusión

    En resumen, desarrollamos un sistema comercial integral que integra el aprendizaje automático con MQL5 para automatizar las decisiones comerciales basadas en datos históricos. Comenzamos cargando y preprocesando datos históricos de XAU/USD, calculando las Bandas de Bollinger e implementando otras funciones clave como cambios de precio y volumen. Mediante el aprendizaje por refuerzo, concretamente una red Q profunda (Deep Q-Network, DQN), entrenamos un modelo para predecir acciones de compra y venta basándonos en patrones en los datos. A continuación, el modelo entrenado se conectó a MQL5 a través de un sistema de comunicación por socket, lo que permitió la interacción en tiempo real entre la plataforma de negociación y nuestro modelo de toma de decisiones basado en Python. Esto nos permitió ejecutar operaciones automáticamente en función de las predicciones del modelo, convirtiendo a todo el sistema en una poderosa herramienta para el trading algorítmico.

    Esta visualización de datos mejorada y la integración del aprendizaje automático pueden beneficiar significativamente a los comerciantes al proporcionarles conocimientos más profundos y una toma de decisiones más informada. Al analizar las tendencias, la volatilidad y los patrones clave en el mercado, el sistema puede identificar puntos de entrada y salida óptimos para las operaciones. La automatización de la ejecución comercial basada en modelos basados en datos reduce el error humano y el sesgo emocional, lo que conduce a operaciones comerciales más consistentes y estratégicas. En general, este enfoque proporciona a los operadores una herramienta sofisticada que aprovecha los datos históricos para mejorar el rendimiento, al mismo tiempo que ahorra tiempo al automatizar tareas comerciales repetitivas.

    Traducción del inglés realizada por MetaQuotes Ltd.
    Artículo original: https://www.mql5.com/en/articles/16083

    Archivos adjuntos |
    Enhanced.ipynb (2101.04 KB)
    EnhancedML.mq5 (3.68 KB)
    XAUUSD_H1_2nd.csv (281.65 KB)
    amrhamed83
    amrhamed83 | 25 oct 2024 en 13:58

    ¿dónde enviar los datos de mt5 a python?

    No he ejecutado el código, pero parece que es missing.....

    Stanislav Korotky
    Stanislav Korotky | 25 oct 2024 en 22:28
    No veo en absoluto una "Visualización de datos mejorada" en este artículo. El título es engañoso.
    Hlomohang John Borotho
    Hlomohang John Borotho | 28 oct 2024 en 21:46
    amrhamed83 #:

    ¿dónde enviar los datos de mt5 a python?

    No he ejecutado el código, pero parece que es missing.....

    Si te refieres a los datos para las señales, cómo funciona es que tenemos un servidor python con el modelo entrenado que está conectado a la MetaTrader5, en el artículo el servidor python se ejecuta en el host local
    HOST = '127.0.0.1'  # Localhost (you can replace this with your IP)
    PORT = 9999         # Port to listen on
    Too Chee Ng
    Too Chee Ng | 18 dic 2024 en 12:33
    Gracias por este artículo. Buen marco.
    Kit de herramientas de negociación MQL5 (Parte 3): Desarrollo de una biblioteca EX5 para la gestión de órdenes pendientes Kit de herramientas de negociación MQL5 (Parte 3): Desarrollo de una biblioteca EX5 para la gestión de órdenes pendientes
    Aprenda a desarrollar e implementar una biblioteca EX5 integral de órdenes pendientes en su código o proyectos MQL5. Este artículo le mostrará cómo crear una extensa biblioteca EX5 de gestión de órdenes pendientes y lo guiará en el proceso de importarla e implementarla mediante la creación de un panel de negociación o una interfaz gráfica de usuario (GUI). El panel de órdenes del asesor experto permitirá a los usuarios abrir, monitorear y eliminar órdenes pendientes asociadas con un número mágico específico directamente desde la interfaz gráfica en la ventana del gráfico.
    Cómo crear un panel interactivo MQL5 utilizando la clase Controls (Parte 1): Configuración del panel Cómo crear un panel interactivo MQL5 utilizando la clase Controls (Parte 1): Configuración del panel
    En este artículo, creamos un panel de control interactivo para operaciones bursátiles utilizando la clase Controls en MQL5, diseñada para optimizar las operaciones bursátiles. El panel incluye un título, botones de navegación para Operar, Cerrar e Información, y botones de acción especializados para ejecutar operaciones y gestionar posiciones. Al final del artículo, tendrás un panel base listo para futuras mejoras en futuras entregas.
    Creación de un Panel de administración de operaciones en MQL5 (Parte IV): Capa de seguridad de inicio de sesión Creación de un Panel de administración de operaciones en MQL5 (Parte IV): Capa de seguridad de inicio de sesión
    Imagine un actor malicioso infiltrándose en la sala del administrador comercial y obteniendo acceso a las computadoras y al panel de administración que se utilizan para comunicar información valiosa a millones de comerciantes en todo el mundo. Una intrusión de este tipo podría tener consecuencias desastrosas, como el envío no autorizado de mensajes engañosos o clics aleatorios en botones que desencadenan acciones no deseadas. En esta discusión, exploraremos las medidas de seguridad en MQL5 y las nuevas características de seguridad que hemos implementado en nuestro Panel de administración para protegernos contra estas amenazas. Al mejorar nuestros protocolos de seguridad, nuestro objetivo es proteger nuestros canales de comunicación y mantener la confianza de nuestra comunidad comercial global. Encuentre más información en la discusión de este artículo.
    Cuerpo en Connexus (Parte 4): Añadiendo compatibilidad con cuerpos HTTP Cuerpo en Connexus (Parte 4): Añadiendo compatibilidad con cuerpos HTTP
    En este artículo, exploramos el concepto de cuerpo en las solicitudes HTTP, que es esencial para enviar datos como JSON y texto sin formato. Discutimos y explicamos cómo usarlo correctamente con los encabezados adecuados. También presentamos la clase ChttpBody, parte de la biblioteca Connexus, que simplificará el trabajo con el cuerpo de las solicitudes.