English 中文 Español Deutsch 日本語 Português
preview
Интеграция MQL5 с пакетами обработки данных (Часть 3): Улучшенная визуализация данных

Интеграция MQL5 с пакетами обработки данных (Часть 3): Улучшенная визуализация данных

MetaTrader 5Примеры |
420 4
Hlomohang John Borotho
Hlomohang John Borotho

Введение

Трейдеры на финансовых рынках часто сталкиваются с проблемой осмысления огромных объемов данных: от колебаний цен и объемов торгов до технических индикаторов и экономических новостей. Учитывая скорость и сложность современных рынков, становится все сложнее эффективно интерпретировать эти потоки данных с помощью традиционных методов. Графики сами по себе не могут обеспечить достаточной информации, что приводит к упущенным возможностям или несвоевременным решениям. Необходимость быстро определять тренды, развороты и потенциальные риски усугубляет ситуацию. Для трейдеров, стремящихся принимать обоснованные решения на основе данных, неспособность извлечь ключевую информацию является серьезной проблемой, которая может привести к потере прибыли или повышению рисков.

Улучшенная визуализация данных решает эту проблему, преобразуя необработанные финансовые данные в более интуитивную и интерактивную визуальную форму. Такие инструменты, как динамические свечные графики, наложения технических индикаторов и тепловые карты доходности, предоставляют трейдерам более глубокое и практическое понимание рыночных условий. Интегрируя визуальные элементы, которые подчеркивают тренды, корреляции и аномалии, трейдеры могут быстро выявлять возможности и принимать более обоснованные решения. Этот усовершенствованный подход помогает снизить сложность интерпретации данных, позволяя трейдерам действовать более уверенно и эффективно на быстро меняющихся финансовых рынках.


Сбор исторических данных

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}")

Для извлечения исторических данных сначала устанавливаем соединение с терминалом MetaTrader 5 с помощью функции mt5.initialize(). Это важно, поскольку пакет Python напрямую взаимодействует с работающей платформой MetaTrader 5. Мы настраиваем код, чтобы задать желаемый временной диапазон для извлечения данных, указав начальную и конечную даты. Объекты datetime создаются в часовом поясе UTC для обеспечения согласованности в разных часовых поясах. Затем скрипт использует функцию mt5.copy-rates-range() для запроса исторических почасовых данных для символа XAUUSD, начиная со 2 января 2024 года до текущей даты и времени.

После получения исторических данных мы безопасно отключаемся от терминала MetaTrader 5 с помощью mt5.shutdown(), чтобы избежать дальнейших ненужных подключений. Извлеченные данные изначально отображаются в необработанном формате для подтверждения успешного извлечения данных. Мы преобразуем эти данные в pandas DataFrame для более легкой обработки и анализа. Кроме того, код преобразует временные метки Unix в читаемый формат даты и времени, гарантируя, что данные хорошо структурированы и готовы к дальнейшей обработке или анализу. Такой подход позволяет трейдерам анализировать исторические движения рынка и принимать обоснованные торговые решения на основе прошлых результатов.

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

Поскольку моя операционная система — Linux, мне приходится сохранять полученные данные в файл. Пользователи Windows могут получить данные с помощью следующего скрипта:

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))

Если по какой-то причине получить исторические данные не удается, вы можете извлечь их вручную на платформе MetTrader 5, выполнив следующие действия. Запустите платформу MetaTrader и в верхней части панели выберите "Сервис" > "Настройки" > вкладка "Графики". Затем вам нужно будет выбрать количество баров на графике, которые вы хотите загрузить. Лучше всего выбрать вариант с неограниченным количеством баров, поскольку мы будем работать с датой и не будем знать, сколько баров в определенном периоде времени.

После этого загрузим реальные данные. Перейдите "Вид" > "Символы" > вкладка "Спецификация". Перейдите на вкладку "Бары" или "Тики" в зависимости от того, какие данные вы хотите загрузить. Введите начальную и конечную даты периода исторических данных, которые вы хотите загрузить, после чего нажмите "Запрос", чтобы загрузить данные и сохранить их в формате .csv.


Визуализация данных MetaTrader 5 в Jupyter Lab

Чтобы загрузить исторические данные MetaTrader 5 в Jupyter Lab, вам сначала необходимо найти папку, в которую они были загружены данные. Будучи в Jupyter Lab, перейдите в эту папку, чтобы получить доступ к файлам. Следующим шагом будет загрузка данных и просмотр названий столбцов. Проверка имен столбцов важна для обеспечения правильного управления данными и предотвращения ошибок, которые могут возникнуть из-за использования неправильных имен столбцов.

Код 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)



Наши исторические данные охватывают период с 2 января 2024 года по текущий момент.

Код 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()

цена закрытия и тиковый объем

На графике выше показана динамика двух ключевых показателей с течением времени:

  1. Close Price (синия линия) - цена закрытия для каждого часа на графике. Мы можем наблюдать колебания с течением времени, указывающие на периоды как восходящих, так и нисходящих трендов в цене золота (XAU/USD).
  2. Tick Volume (зеленые столбцы) - количество изменений цены в течение каждого часа. Скачки тикового объема часто соответствуют повышенной активности или волатильности рынка. Например, периоды высокого объема могут совпадать со значительными колебаниями цен, что может сигнализировать о важных событиях или изменениях в настроениях рынка.

Теперь давайте более подробно рассмотрим наши данные:

1. Анализ тренда.

# 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()


На графике показана цена закрытия, а также 50-периодная и 200-периодная скользящие средние:

  1. Close Price (синяя линия) - фактическая цена в конце каждого периода времени.
  2. 50-period Moving Average (оранжевая линия) - краткосрочная скользящая средняя, которая сглаживает ценовые данные за 50 периодов. Когда цена закрытия пересекает эту линию вверх, это может быть сигналом о потенциальном восходящем тренде, а когда она пересекает ее вниз, - о нисходящего.
  3. 200-period Moving Average (красная линия) - более долгосрочная 200-периодная скользящая средняя, которая дает представление об общем тренде. Она имеет тенденцию медленнее реагировать на изменения цен, поэтому пересечения со скользящей средней за 50 периодов могут сигнализировать о значительных разворотах долгосрочного тренда.

Ключевой элемент, который мы ищем, — это золотой крест: когда 50-периодная скользящая средняя пересекает 200-периодную скользящую среднюю вверх, это может быть сигналом о потенциально сильном бычьем тренде. Также есть крест смерти, когда 50-периодная скользящая средняя опускается ниже 200-периодной. Это может быть сигналом о потенциальном медвежьем тренде.

Далее мы анализируем волатильность, рассчитывая ценовой диапазон (разницу между максимумом и минимумом) и визуализируя его.

2. Анализ волатильности.

# 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()



На основе вышеприведенного вывода мы сделали следующее:

  1. Расчет диапазона цен: Мы рассчитываем диапазон цен для каждого периода путем вычитания минимальной цены (`<LOW>`) от максимальной ('<High>`). Это дает представление о степени изменения цены в течение каждого часа, помогая измерить волатильность за этот период.
  2. `data['Price-Range'] = data['<HIGH>'] - data['<LOW>']: Полученный столбец Price-Range (диапазон цен) показывает, насколько сильно колебалась цена в течение каждого часа.
  3. Расчет полос Боллинджера: Индикатор полос Боллинджера рассчитывается для анализа волатильности цен. MA-20 - 20-периодная скользящая средняя цен закрытия. Она служит средней линией полос Боллинджера. BB-upper и BB-lower представляют верхнюю и нижнюю полосы соответственно. Они рассчитываются как два стандартных отклонения выше и ниже 20-периодной скользящей средней. Когда цены движутся к верхней полосе, это указывает на то, что рынок может быть перекуплен; аналогично, движение к нижней полосе предполагает, что рынок может быть перепродан.
  4. Визуализация цены и полос Боллинджера: цена закрытия (синяя линия) представляет собой фактическую цену закрытия для каждого периода времени. Верхние полосы Боллинджера (красная пунктирная линия) и нижние полосы Боллинджера (зеленая пунктирная линия), эти линии показывают полосы волатильности, которые формируют полосы Боллинджера вокруг скользящей средней. Затененная область - область между верхней и нижней полосами Боллинджера закрашена серым цветом, что визуально отображает диапазон волатильности, в пределах которого, как ожидается, будет двигаться цена.
  5. Отдельный график ценового диапазона (Price Range): Второй график отображает ценовой диапазон в виде отдельной фиолетовой линии, показывающей, как волатильность колеблется с течением времени as a separate purple line, showing how the volatility fluctuates over time. Более крупные пики на этом графике указывают на периоды повышенной волатильности.
Создание модели обучения с подкреплением:
    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)

    В приведенном выше коде мы подготавливаем финансовые данные для модели обучения с подкреплением (reinforcement learning, RL) для принятия торговых решений на рынке XAUUSD (золото) с использованием алгоритма глубокой Q-сети (Deep Q-Network, DQN) из stable-baseline3. Сначала он загружает и обрабатывает исторические данные, включая расчет полос Боллинджера (технического индикатора, основанного на скользящих средних и волатильности цен), а также добавляет такие функции, как процентные изменения цен и объема. Среда проверяется, и модель DQN обучается на 10 000 временных шагов, после чего модель сохраняется для будущего использования. Наконец, недостающие данные заполняются нулями, чтобы обеспечить плавное обучение модели.

    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)

    В приведенном выше коде мы определяем класс торговой среды TradingEnv, используя библиотеку gym для моделирования торговой среды на основе исторических рыночных данных. Среда допускает три возможных действия: удержание, покупка или продажа. Она включает в себя пространство наблюдения с пятью функциями (полосы Боллинджера, процентное изменение цены, изменение объема и текущая торговая позиция). Агент начинает с баланса 10 000 единиц и без позиции. На каждом этапе, в зависимости от выбранного действия, среда обновляет позицию и баланс агента, рассчитывает вознаграждения за прибыльные сделки и переходит к следующему этапу обработки данных. Среда может быть сброшена, чтобы начать новый эпизод или отобразить текущее состояние торгового процесса. Эта среда будет использоваться для обучения моделей обучения с подкреплением.

    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")
    

    Результат:

    Модель

    Затем мы определяем пользовательскую торговую среду, используя gymnasium для обучения с подкреплением, где агент учится принимать торговые решения на основе исторических рыночных данных. Среда допускает три действия: удержание, покупка или продажа, и содержит пять наблюдений, включая полосы Боллинджера, процентное изменение цены, изменение объема и текущую позицию. Агент начинает с баланса 10 000 и без открытых позиций. Каждый шаг в среде продвигает агента вперед, обновляя его позицию, баланс и рассчитывая вознаграждения за успешные сделки. Среда проверяется с помощью функции check-Env() из stable-baseline3, а модель DQN (Deep Q-Network) обучается на протяжении 10 000 временных шагов для изучения оптимальных торговых стратегий. Обученная модель сохраняется для будущего использования в автоматизированных торговых системах.

    # 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
    

    Начальный эквити

    Конечный эквити

    Результат показывает, что торговая стратегия, реализованная обученной моделью, привела к небольшой прибыли за торговый период, при этом баланс увеличился с USD 10 000 до USD 10 108. Хотя это говорит о том, что модель смогла выявить прибыльные сделки, маржа прибыли в размере USD 108 (прирост 1,08%) относительно невелика.

    Результаты указывают на то, что модель совершает осторожные или низкочастотные сделки, или это может указывать на то, что стратегия не полностью оптимизирована для получения более высокой прибыли. Дальнейшая оценка, включая более длительный период тестирования или корректировку параметров модели (таких как функция вознаграждения, выбор характеристик или логика исполнения сделок), может помочь улучшить производительность модели. Также важно учитывать такие факторы, как расходы на совершение сделок и управление рисками, чтобы гарантировать, что стратегия останется прибыльной с течением времени.

    Собираем всё вместе в MQL5

    Мы собираемся подключить MQL5 к скрипту Python, который будет запускать нашу обученную модель. Для этого нам нужно будет настроить канал связи между MQL5 и Python. В нашем случае мы будем использовать широко применяемый сервер на сокетах.
    //+------------------------------------------------------------------+
    //|                                                   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;

    Во-первых, мы подключаем библиотеку Windows API (winapi.mqh) для операций на системном уровне и торговую библиотеку (trade.mqh) для управления сделками. Затем мы объявляем экземпляр класса (CTrade) с именем 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);

    Устанавливаем Address на "127.0.0.1" (локальный хост), а port - на "9999" для связи между сокетами. Функция SocketCreate() инициализирует сокет и сохраняет хэндл сокета в переменной socket, обеспечивая связь с сервером 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);
    }

    Функция SocketConnect() пытается подключить созданный сокет к серверу Python по указанному адресу (локальный хост) и порту 9999. Наше время ожидания в миллисекундах равно 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...");
          }
       }
       
    }

    Функция OnTick() в MQL5 предназначена для обработки входящих торговых инструкций на каждом тике рынка через связь между сокетами. Она начинается с проверки наличия каких-либо данных на сокете с помощью функции SocketIsReadable(), которая возвращает длину данных. Если данные присутствуют, функция SocketRead() считывает данные в буфер, а количество успешно считанных байтов сохраняется в bytes. Если данные получены, буфер преобразуется в строку, затем в целое число (действие). На основе значения action функция выполняет соответствующую сделку: если action == 1, выполняется сделка на покупку путем вызова MBuy(), если же action == 2, выполняется сделка на продажу путем вызова MSell(). После каждого торгового действия оператор print регистрирует полученное действие (покупку или продажу). По сути, функция прослушивает команды на покупку или продажу через сокет и автоматически выполняет соответствующие сделки.

    //+------------------------------------------------------------------+
    //|                        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);
    }

    Функция открытия сделок на покупку.

    //+------------------------------------------------------------------+
    //|                         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);
    }

    Функция открытия сделок на продажу.


    Скрипт сервера сокетов Python (сервер торговой модели)

    Наш скрипт Python загрузит обученную модель и настроит сервер на сокетах для прослушивания подключений из MQL5. Получив данные, он сделает прогноз и отправит обратно торговое действие.

    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()
    

    Сохраните скрипт Python как trading-model-server.py или под любым другим именем. Откройте терминал или командную строку, перейдите в каталог, в котором вы сохранили свою модель и файл trading-model-server.py, и выполните следующую команду, чтобы установить соединение.

    python trading_model_server.py
    


    Заключение

    Мы разработали комплексную торговую систему, которая интегрирует машинное обучение с MQL5 для автоматизации принятия торговых решений на основе исторических данных. Мы начали с загрузки и предварительной обработки исторических данных XAUUSD, расчета полос Боллинджера и внедрения других ключевых функций, таких как изменения цен и объемов. Используя обучение с подкреплением, в частности Deep Q-Network (DQN), мы обучили модель прогнозировать действия по покупке и продаже на основе закономерностей в данных. Затем обученная модель была подключена к MQL5 через систему связи через сокеты, что позволило обеспечить взаимодействие в режиме реального времени между торговой платформой и нашей моделью принятия решений на основе Python. Это позволило нам автоматически совершать сделки на основе прогнозов модели, что сделало всю систему мощным инструментом для алгоритмической торговли.

    Такая улучшенная визуализация данных и интеграция машинного обучения могут принести трейдерам значительную пользу, предоставляя более глубокие сведения и позволяя принимать более обоснованные решения. Анализируя тренды, волатильность и ключевые закономерности на рынке, система может определять оптимальные точки входа и выхода для сделок. Автоматизация исполнения сделок на основе моделей, управляемых данными, снижает человеческий фактор и эмоциональную предвзятость, что приводит к более последовательной и стратегической торговле. Подход предоставляет трейдерам сложный инструмент, который использует исторические данные для повышения производительности, а также экономит время за счет автоматизации повторяющихся торговых задач.

    Перевод с английского произведен MetaQuotes Ltd.
    Оригинальная статья: https://www.mql5.com/en/articles/16083

    Прикрепленные файлы |
    Enhanced.ipynb (2101.04 KB)
    EnhancedML.mq5 (3.68 KB)
    XAUUSD_H1_2nd.csv (281.65 KB)
    Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
    amrhamed83
    amrhamed83 | 25 окт. 2024 в 13:58

    куда вы отправляете данные из mt5 в python?

    Я не запускал код, но кажется, что он отсутствует.....

    Stanislav Korotky
    Stanislav Korotky | 25 окт. 2024 в 22:28
    Я вообще не вижу в этой статье "Улучшенной визуализации данных". Название вводит в заблуждение.
    Hlomohang John Borotho
    Hlomohang John Borotho | 28 окт. 2024 в 21:46
    amrhamed83 #:

    куда вы отправляете данные из mt5 в python?

    Я не запускал код, но кажется, что он отсутствует.....

    Если вы имеете в виду данные для сигналов, то это работает так: у нас есть python-сервер с обученной моделью, который подключен к MetaTrader5, в статье python-сервер запущен на локальном хосте
    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 дек. 2024 в 12:33
    Спасибо за эту статью. Хорошая основа.
    Матричная модель прогнозирования на марковской цепи Матричная модель прогнозирования на марковской цепи
    Создаем матричную модель прогнозирования на марковской цепи. Что такое марковские цепи, и как можно использовать марковскую цепь для трейдинга на Форекс.
    Переосмысливаем классические стратегии (Часть IX): Анализ на нескольких таймфреймах  (II) Переосмысливаем классические стратегии (Часть IX): Анализ на нескольких таймфреймах (II)
    В сегодняшнем обсуждении мы рассмотрим стратегию анализа на нескольких таймфреймах, чтобы узнать, на каком таймфрейме наша модель искусственного интеллекта работает лучше всего. Наш анализ приводит нас к выводу, что месячный и часовой таймфреймы дают модели с относительно низким уровнем ошибок по паре EURUSD. Мы использовали это в своих интересах и создали торговый алгоритм, который делает прогнозы с помощью искусственного интеллекта на месячном таймфрейме и совершает сделки на часовом таймфрейме.
    Заголовок в Connexus (Часть 3): Освоение использования HTTP-заголовков для запросов Заголовок в Connexus (Часть 3): Освоение использования HTTP-заголовков для запросов
    Продолжаем разработку библиотеки Connexus. В этой главе мы исследуем концепцию заголовков в протоколе HTTP, объясняя, что это такое, для чего они предназначены и как их использовать в запросах. Мы рассмотрим основные заголовки, используемые при взаимодействии с API, а также покажем практические примеры того, как настроить их в библиотеке.
    Создаем интерактивную MQL5-панель с использованием класса Controls (Часть 1): Настройка панели Создаем интерактивную MQL5-панель с использованием класса Controls (Часть 1): Настройка панели
    В этой статье мы создадим интерактивную торговую панель с использованием класса Controls в MQL5, предназначенную для оптимизации торговых операций. Панель содержит заголовок, кнопки навигации для торговли, закрытия и информации, а также специализированные кнопки для заключения сделок и управления позициями. К концу статьи у нас будет базовая панель, готовая к дальнейшим улучшениям.