English 中文 Español Deutsch 日本語 Português
preview
Многослойный перцептрон и алгоритм обратного распространения ошибки (Часть 3): Интеграция с тестером стратегии - Обзор (I)

Многослойный перцептрон и алгоритм обратного распространения ошибки (Часть 3): Интеграция с тестером стратегии - Обзор (I)

MetaTrader 5Тестер | 25 мая 2023, 10:16
637 0
Jonathan Pereira
Jonathan Pereira

Введение

В последних статьях мы обсуждали построение и использование моделей машинного обучения в упрощенном виде, с использованием соединения клиент-сервер. Однако эти модели работают только в "производственных" средах, поскольку тестер не выполняет сетевые функции. Поэтому, начиная данное исследование, я проверял модели в среде Python, что неплохо, но подразумевает придание модели (или моделям) детерминированности при принятии решения о покупке или продаже конкретного актива, или при реализации технических индикаторов на Python, что не всегда применимо из-за пользовательского и закрытого исходного кода. Однако при использовании стратегии с применением пользовательских индикаторов в качестве фильтра, или при тестировании стратегии с помощью тейк и стопа, трейлинг-стопа или безубытка, отсутствие тестера дает о себе знать. Создание собственного тестера, даже на более доступных языках, таких как Python, является сложной задачей.


Обзор

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



Организация разработки

В поисках способов интеграции систем я изначально думал о возможности использования REST API из-за его простоты в плане построения и управления. Однако после анализа документации по функции WebRequest я понял, что этот вариант неосуществим, поскольку в нем прямо говорится о невозможности его использования.  

"WebRequest() нельзя выполнить в Тестере Стратегий".

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

Продвигаясь дальше в своих исследованиях, я наткнулся на тексты, которые подсказали мне новое решение:

"Многие разработчики сталкиваются с одной и той же проблемой: как получить доступ к песочнице терминала без использования «опасных» DLL.

Одним из простейших и безопаснейших методов является использование стандартных именованных каналов, которые работают как обычные файловые операции. Они позволяют организовать межпроцессное взаимодействие клиент-сервер между программами ".

"Система защиты торговой платформы MetaTrader 5 не позволяет MQL5-программам работать вне песочницы, чтобы защищать трейдеров от угроз при использовании непроверенных советников. Используя именованные каналы, можно создавать простые интеграции со сторонним программным обеспечением и безопасно управлять советниками извне".


При усовершенствовании своего подхода, я понял, что могу использовать сообщения из CSV-файлов. Это связано с тем, что на Python не будет никаких проблем с обработкой данных CSV, в то время как стандартные классы MQL5, работающие с файлами (CFile, CFileTxt и т.д.), позволяют записывать данные всех типов и матрицы, но не содержат опции записи заголовка CSV-файла. Однако это ограничение довольно легко обойти.

Поэтому я решил разработать архитектуру, которая позволит обмениваться файлами, прежде чем разрабатывать решение на стороне MQL5. Межпроцессная коммуникация (Inter-Process-Communication, IPC) - это набор механизмов, обеспечивающих передачу информации между процессами.

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

Используя инструмент Figma, я разработал документацию и справочные материалы, которые использовал в дальнейшем.




Чтобы лучше понять контекст предыдущей темы, я оставлю объяснение потока сообщений, который мы установим для создания и обеспечения стабильной и безопасной связи. Моя идея заключается в том, чтобы изначально не углубляться в некоторые технические вопросы для того, чтобы облегчить понимание архитектуры, которой будем использоваться.

Каждый раз, когда сервер (Python) инициализируется, он будет ждать, пока ему будет отправлено сообщение инициализации, как видно из потока "1 - Waiting for initialization". Только после прикрепления советника к графику начнется процесс обмена сообщениями. Задача MetaTrader - отправить Python сообщение о том, на каком хосте, порту и в какой среде он работает.


Следующие макросы отвечают за построение заголовка сообщения инициализации.

#define HEADER_FILE_INIT {"host","port","typerun"}
#define LINES_FILE_INT(HOST, PORT, TYPE) {{string(HOST), string(PORT), string(TYPE)}}


Когда мы говорим о среде, мы имеем в виду место, где работает советник: либо в тестере стратегии, либо на реальном счете. Мы будем называть тестовую среду "Test", а живую среду - "Live".

Ниже можно увидеть, что советник получает параметры "Host" и "Port".

sinput group   "General Configuration"
sinput string            InpHost           = "127.0.0.1";
sinput int               InpPort           = 8081;


Во время запуска записи о среде, хосте и порте собираются и сохраняются сервером для последующего чтения.
static EtypeRun typerun= (MQLInfoInteger(MQL_TESTER) || MQLInfoInteger(MQL_VISUAL_MODE))?TEST:LIVE;
if(!monitor.OnInit(typerun, InpHost, InpPort))
   return(INIT_FAILED);
bool CMonitor::OnInit(EtypeRun type_run, string host, int port)
  {
   ...
   File.SetCommon(true);
   File.Open("TransferML/init.csv", FILE_WRITE|FILE_SHARE_READ|FILE_ANSI);
   string header[3]   = HEADER_FILE_INIT;
   string lines[1][3] = LINES_FILE_INT(host,port,type_run);

   if((File.WriteHeader(header)<1&File.WriteLine(lines)<1&!Strategy.Config(m_params))!=0)
      res=false;

   File.Close();

...
  }

В приведенном выше фрагменте мы читаем файл "init" и передаем информацию об окружении и хосте. На данном этапе выполняются шаги "1- Startup Initialization" и "2- Send Initialization".

Ниже приведен фрагмент Python, который получит инициализацию, обработает данные, установит используемую среду и подтвердит инициализацию клиенту. На этом этапе выполняются шаги "2- Collect data input", "3 -Startup process data input", "4 - Set Env" и "5 - Confirm initialization".

host, port, typerun = file.check_init_param(PATH_COMMON.format(INIT_ARCHIVE))
file.save_file_csv(PATH_COMMON.format(INIT_OK_ARCHIVE))


После всех этих шагов MetaTrader должен ждать подтверждения для запуска сервера. Этот шаг называется "3 - Waiting for confirmation".

bool CMonitor::OnInit(EtypeRun type_run, string host, int port)
  {
   ...
   while(!File.IsExist("TransferML/init_checked.csv", FILE_COMMON))
     {
      //waiting for startup
      Comment("waiting for startup");
     }
...

  }


После этого шага Python выбирает, какой поток использовать. Если это производственный поток, то мы будем использовать сокетное соединение, взяв за основу то, что было сделано в предыдущей статье. Если это тестовый поток, тогда мы будем использовать обмен сообщениями с файлами CSV. Серверная сторона ожидает инструкций от клиента, который посылает такие команды, как "START", "STOP" и "BREAK" (названия выбирались случайно), чтобы начать процесс отправки значения, сгенерированного некоторой моделью.


Плюсы и минусы

Наша взаимосвязь четкая и эффективная, поскольку стандартизация данных на каждом этапе гарантирует стабильность системы. Кроме того, понимание данных упрощается как на стороне Python с использованием библиотеки pandas, так и на стороне Mql5, с помощью матриц и векторов.



Обмен сообщениями является сутью проблемы, поэтому я сделал стандартными отправку и получение в формате CSV. Чтобы облегчить эту задачу, я разработал класс, который абстрагирует усилия по созданию строк и заголовков. Он используется в качестве основы для обмена данными между средами. Ниже показываем заголовок класса с основными методами и атрибутами.

class CFileCSV : public CFile
  {
private:
   template<typename T>
   string            ToString(const int, const T &[][]);
   template<typename T>
   string            ToString(const T &[]);
   short             m_delimiter;

public:
                     CFileCSV(void);
                    ~CFileCSV(void);
   //--- methods for working with files
   int               Open(const string,const int, const short);
   template<typename T>
   uint              WriteHeader(const T &values[]);
   template<typename T>
   uint              WriteLine(const T &values[][]);
   string            Read(void);
  };  

Видно, что методы сохранения строк и заголовков принимают динамические векторы и матрицы, что позволяет строить файл во время выполнения, без необходимости конкатенации текста с помощью функций "StringAdd()" или "StringConcatenate()" в основной части кода. Эту работу выполняют функции "ToString", которые принимают вектор или матрицу и преобразуют их в текст, соответствующий стандарту CSV.


Например:

Представим, что у нас есть модель, которая получает значение последних 4 свечей, и есть информация, которую посчитали нужным передать, примерно так:

data;close;val_ma

10202022;10.55;10.49

10212022;10.95;11.09

10222022;11.55;11.29

10232022;11.15;11.29


Этот пример иллюстрирует использование статических данных, хранящихся в глобальных переменных, однако в реальной системе эти данные будут собираться в соответствии с потребностями каждой стратегии, как видно из изображения, которое показывает архитектуру интеграции. Важно отметить, что стратегия является основным элементом системы, так как она определяет, какая информация необходима для правильной работы модели. Например, если нам нужно добавить информацию о ценах или индикаторах, это будет одним из вариантов. Однако необходимо помнить, что изменения формата отправляемых или получаемых данных потребует соответствующей поддержки кода. Хотя это легко устранимая проблема, важно планировать разработки системы. Как упоминалось выше, это всего лишь пример проверки концепции (POC), и, если она окажется чем-то многообещающим, то можно будет провести ее усовершенствование в будущем.

Чтобы создать приведенный выше пример вручную, нам нужна матрица с тремя значениями, представляющими заголовок, и матрица [4][3], которая будет содержать данные. Как можно видеть, запись и чтение этого CSV-файла очень просты.

#include "FileCSV.mqh"

#define PATH(path) "Test/"+path+".csv"

string H[3] = { "data", "close", "val_ma" };
string L[4][3]  = {{"10202022", "10.55", "10.49"},{"10212022", "10.95", "11.09"},{"10222022", "11.55", "11.29"},{"10232022", "11.15", "11.29"}};

CFileCSV              File;
ulong start=0,time=0;
void OnStart()
  {
   start=0;
   time=0;
   start=GetTickCount();
   for(int i=0; i<100; i++)
     {
      File.Open(PATH("init"), FILE_WRITE|FILE_SHARE_READ|FILE_ANSI);
      ResetLastError();
      if((File.WriteHeader(H)<1&File.WriteLine(L)<1)!=0)
         Print("Error : ", GetLastError());
      File.Close();

      while(!File.IsExist(PATH("init_checked")))
        {
         //waiting for startup
         Comment("waiting for startup");
        }
      File.Delete(PATH("init"));
      File.Delete(PATH("init_checked"));
     }
   time=GetTickCount()-start;
   Print("Time send 100 archives with transfer message [ms]: ",time);
  }


Минусом такого подхода является то, что хранение данных происходит на диске, что может повлиять на среднюю скорость обработки. Однако при сравнении со скоростью обработки данных в системе, использующей сокеты, наблюдается приемлемая производительность.


Проведение тестовой отправки:

Мы отправим 100 файлов, содержащих 3 колонки и 4 строки данных, и потом измерим скорость передачи данных.

from Services import File

PATH_COMMON     = r'C:\Users\letha\AppData\Roaming\MetaQuotes\Terminal\B8C209507DCA35B09B2C3483BD67B706\MQL5\Files\Test\{}.csv'
INIT_ARCHIVE    = 'init'
INIT_OK_ARCHIVE = 'init_checked'

if __name__ == "__main__":
    file = File()
    file.delete_file(PATH_COMMON.format(INIT_ARCHIVE))
    file.delete_file(PATH_COMMON.format(INIT_OK_ARCHIVE))
  
    while True:
        
        receive = file.check_open_file(PATH_COMMON.format(INIT_ARCHIVE))
        file.delete_file(PATH_COMMON.format(INIT_ARCHIVE))
        file.save_file_csv(PATH_COMMON.format(INIT_OK_ARCHIVE))
void OnStart()
  {

   start=0;
   time=0;

   start=GetTickCount();

   for(int i=0; i<100; i++)
     {
      File.Open(PATH("init"), FILE_WRITE|FILE_SHARE_READ|FILE_ANSI);

      ResetLastError();
      if((File.WriteHeader(H)<1&File.WriteLine(L)<1)!=0)
         Print("Error : ", GetLastError());
      File.Close();

      while(!File.IsExist(PATH("init_checked")))
        {
         //waiting for startup
         Comment("waiting for startup");
        }
        
      File.Delete(PATH("init"));
      File.Delete(PATH("init_checked"));

     }
     
   time=GetTickCount()-start;
   Print("Time send 100 archives with transfer message [ms]: ",time);

  }

Мы видим результат:

testeCSV (EURUSD,M1) Time to send 100 files, transfer message [ms]: 5578

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

Кроме того, процесс ограничен одной моделью/стратегией, но это можно усовершенствовать, чтобы сделать систему более масштабируемой в будущем.


Использование линейной регрессии:

Что такое линейная регрессия?

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

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

После сбора данных необходимо выбрать, какой зависимой и независимой переменной мы будем пользоваться в анализе. Зависимая переменная - это переменная, которую необходимо предсказать, а независимые переменные - это те, которые используются при объяснении поведения зависимой переменной. Например, если целью является прогнозирование цены акции, зависимой переменной будет цена акции, в то время как независимыми переменными могут быть объем торгов, прибыльность и другие экономические показатели.

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

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

Линейная регрессия - это статистический метод, широко используемый в финансовом анализе для прогнозирования поведения финансовых активов, таких как акции, облигации и валюты. Она позволяет финансовым аналитикам обнаружить взаимосвязь между различными переменными и, таким образом, предсказать будущие показатели актива. Реализация линейной регрессии в Python легко проводится с помощью библиотеки scikit-learn и может стать ценным инструментом для прогнозирования цен финансовых активов. Однако важно помнить, что линейная регрессия является базовым методом и может не подходить для всех видов финансовых активов или конкретных ситуаций. Всегда важно оценить качество прогноза и проанализировать другие методы финансового анализа.

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


Реализация на языке Python:

import random
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
from sklearn.preprocessing import OneHotEncoder

random.seed(42)

encoder = OneHotEncoder()

# Create an empty dataframe
data = pd.DataFrame(columns=['ticker', 'price', 'volume', 'economic_indicator'])

# Fill the dataframe with random values
for i in range(500):
    row = {
        'ticker': "FAKE3",
        'price': round(random.uniform(100, 200), 2),
        'volume': round(random.uniform(10000, 100000), 2),
        'economic_indicator': round(random.uniform(1, 100), 2)
    }
    data = data.append(row, ignore_index=True)

print(data)

# aplica o one-hot encoding na coluna "ticker"
onehot_encoded = encoder.fit_transform(data[['ticker']])

# adiciona as novas colunas one-hot encoded ao dataframe original
data['tiker_encoder'] = onehot_encoded.toarray()

# Selecionando as variáveis independentes e dependente
X = data[['tiker_encoder', 'volume', 'economic_indicator']]
y = data['price']

# Criando o modelo de regressão linear
model = LinearRegression()

# Treinando o modelo com os dados históricos
model.fit(X, y)

# Fazendo previsões com o modelo treinado
y_pred = model.predict(X)

# Avaliando a qualidade da previsão
r2 = r2_score(y, y_pred)
print("Coeficiente de determinação:", r2)

# Fazendo previsões para novos dados
new_data = [[1, 23228.17, 61.21]]
new_price_pred = model.predict(new_data)
print("Previsão de preço para novos dados:", new_price_pred)

Этот код использует библиотеку scikit-learn для создания линейной регрессионной модели на основе исторических ценовых данных, торгового объема и одного экономического индикатора. Модель обучается на исторических данных и используется для прогнозирования цен. Коэффициент детерминации (R²) тоже рассчитывается как мера качества прогноза. Кроме того, модель также используется для прогнозирования с учетом новых предоставленных данных.

Стоит отметить, что данный код является статическим и служит только в качестве примера. Его можно легко адаптировать для работы с текущими и динамическими данными и использовать в производственной среде. Кроме того, необходимо получить рыночные данные и экономические показатели для правильного обучения модели.

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

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


Заключение

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

Важно отметить, что реализация класса CFileCSV будет иметь фундаментальное значение для обмена данными между MQL5 и Python, позволяя использовать передовые функции анализа данных и моделирования на обеих платформах, и будет ключевым компонентом для использования всех преимуществ этой архитектуры.

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

Прикрепленные файлы |
Как построить советник, работающий автоматически (Часть 13): Автоматизация (V) Как построить советник, работающий автоматически (Часть 13): Автоматизация (V)
Знаете ли вы, что такое блок-схема? Умеете ли вы ее использовать? Думаете ли вы, что блок-схемы - это дело начинающих программистов? Тогда я вам предлагаю ознакомиться с этой статьей и узнать, как работать с блок-схемами.
Теория категорий в MQL5 (Часть 6): Мономорфные расслоенные произведения и эпиморфные кодекартовы квадраты Теория категорий в MQL5 (Часть 6): Мономорфные расслоенные произведения и эпиморфные кодекартовы квадраты
Теория категорий представляет собой разнообразный и расширяющийся раздел математики, который лишь недавно начал освещаться в MQL5-сообществе. Эта серия статей призвана рассмотреть некоторые из ее концепций для создания открытой библиотеки и дальнейшему использованию этого замечательного раздела в создании торговых стратегий.
Как создать советник, который торгует автоматически (Часть 14): Автоматизация (VI) Как создать советник, который торгует автоматически (Часть 14): Автоматизация (VI)
Здесь мы действительно применим на практике все знания этой серии статей. Наконец мы построим 100% автоматическую и функциональную систему, но для этого нам придется научиться одной последней детали.
Как подключить MetaTrader 5 к PostgreSQL Как подключить MetaTrader 5 к PostgreSQL
В статье описываются четыре метода подключения кода MQL5 к базе данных Postgres и предоставляется пошаговое руководство по настройке среды разработки для одного из них, REST API, с использованием подсистемы Windows для Linux (WSL). Показано демонстрационное приложение для API с соответствующим кодом MQL5 для вставки данных и запросов к соответствующим таблицам, а также демонстрационный советник для использования этих данных.