Español Português
preview
Моделирование рынка: Первые шаги на SQL в MQL5 (III)

Моделирование рынка: Первые шаги на SQL в MQL5 (III)

MetaTrader 5Тестер |
141 0
Daniel Jose
Daniel Jose

Введение

Приветствую всех в новой статье из серии системы репликации/моделирования.

В предыдущей статье Моделирование рынка: Первые шаги на SQL в MQL5 (II) мы рассмотрели пример реализации класса на MQL5 для обеспечения базовой поддержки. Его цель заключается именно в том, чтобы позволить хранить SQL-код в отдельном файле скрипта. Таким образом, нам не потребуется писать тот же SQL-код в виде строки внутри кода MQL5. Хотя данное решение функционально, в нём есть некоторые детали, которые мы можем и должны улучшить, чтобы сделать работу с SQL более приятной или, по крайней мере, более удобной при более интенсивном использовании данного языка.

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

Кроме того, если мы интегрируем файл, который в данном случае будет представлять собой SQL-скрипт, в исполняемый файл, у нас будет на одну проблему меньше при передаче программы другому пользователю. Это связано с тем, что сам исполняемый файл уже будет содержать ожидаемые и необходимые данные для фактического выполнения поставленной задачи. Я не стану говорить вам не делать этого, и я не буду тем, кто запретит вам это делать. Решение о включении файла, который в данном случае будет представлять собой SQL-скрипт, в исполняемый файл зависит исключительно от вас, как от программиста. Потому что каждый случай уникален и требует особого подхода для правильного решения.

Умение интегрировать и использовать SQL-скрипты в исполняемом файле, созданном на языке MQL5, не является сложной задачей. Но для того, чтобы всё работало идеально, необходимо знать некоторые моменты. Отлично, тогда мы не будем слишком долго задерживаться на объяснениях и подготовке. Перейдем сразу к делу, потому что в этой статье нам предстоит проделать большую работу, чтобы подготовить всё необходимое для следующих публикаций.


Изменение структуры классов

Хотя, на первый взгляд, схема, созданная в предыдущей статье, выглядит интересно, она содержит ряд проблем, которые затрудняют более интенсивное использование файлов SQL-скриптов. Даже если вы не будете использовать файлы скриптов, а ограничитесь только отдельными SQL-командами. То, как всё было структурировано в предыдущей статье, затрудняет выполнение определенных задач. Одна из таких задач — возможность простым образом менять направление, выбираемое при реализации основного кода. Чтобы прояснить этот момент, подумайте вот о чем: схема классов и то, как они работают не позволяют нам использовать некоторые возможности программирования. Это связано с тем, что все будет ориентировано на терминал или использование с SQLite, которая является частью MetaTrader 5.

Однако, если мы захотим создать собственную сборку SQLite, используя для этого доступный в сети исходный код, и использовать эту кастомную версию SQLite совместно с MetaTrader 5, мы столкнемся с определенными трудностями при текущей структуре представленного кода. Но у нас есть и другая проблема. Если хотим использовать окно сообщения MessageBox, доступное в Windows, для оповещения пользователя, у нас возникнет множество проблем при использовании схемы, представленной в предыдущей статье. Но решить эту задачу на данный момент довольно просто, поскольку на данном этапе мы всё ещё разрабатываем структуру того, что будет фактически использоваться при применении SQL в системе репликации/моделирования, нам необходимо внести изменения на этом этапе разработки, чтобы в дальнейшем избежать лишних проблем при переносе в систему репликации/моделирования.

Итак, первым делом нужно удалить заголовочный файл C_DB_SQLite.mqh. Дело в том, что мы собираемся структурировать код таким образом, чтобы в будущем иметь возможность использовать другую реализацию SQL. Такую, которая относится к клиент-серверную реализацию. Но это будет сделано позже. На данный момент мы продолжим работать с SQLite.

Хорошо. Учитывая это, имя класса C_ScriptSQL, как и имя самого файла, было изменено на C_DB_SQL. Имя файла всегда будет соответствовать имени класса, поэтому файл был переименован в C_DB_SQL.mqh и будет находиться в том же каталоге, который мы рассматривали в предыдущей статье. Однако код претерпел некоторые изменения, как можно видеть ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Service Graphics\Support\C_Array.mqh"
005. //+------------------------------------------------------------------+
006. class C_DB_SQL
007. {
008.     private    :
009.         C_Array    m_Arr;
010.         int        m_handleDB;
011. //+------------------------------------------------------------------+
012.         void Convert(const char &buff[], const int size)
013.         {
014.             string sz0 = "";
015.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
016.             int nLine = 1;
017.             
018.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
019.             for (int count = 0, nC0 = nLine; count < size; count++)
020.             {
021.                 switch (buff[count])
022.                 {
023.                     case '\t':
024.                         sz0 += (bs1 || bs2 ? "\t" : "");
025.                         break;
026.                     case '\n':
027.                         nC0++;
028.                     case '\r':
029.                         bc0 = false;
030.                         break;
031.                     case ';':
032.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
033.                     default:
034.                         switch (buff[count])
035.                         {
036.                             case '"':
037.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
038.                                 break;
039.                             case '\'':
040.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
041.                                 break;
042.                         }
043.                         if (((count + 1) < size) && (!bs1) && (!bs2))
044.                         {
045.                             if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true;
046.                             if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true;
047.                             if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false;
048.                             if (bc)
049.                             {
050.                                 count += 1;
051.                                 bc = false;
052.                                 continue;
053.                             }
054.                         }
055.                         if (!(bc0 || bc1))
056.                         {
057.                             if ((!b1) && (buff[count] > ' '))
058.                             {
059.                                 b1 = true;
060.                                 nLine = nC0;
061.                             }
062.                             sz0 += (b1 ? StringFormat("%c", buff[count]) : "");
063.                         }
064.                 }
065.                 if (b0)
066.                 {
067.                     m_Arr.Add(sz0, nLine);
068.                     sz0 = "";
069.                     b0 = b1 = false;
070.                 }
071.             }
072.         }
073. //+------------------------------------------------------------------+
074.     public    :
075. //+------------------------------------------------------------------+
076.         C_DB_SQL(const string szFileName = ":memory:")
077.         {
078.             m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE);
079.         }
080. //+------------------------------------------------------------------+
081.         ~C_DB_SQL()
082.         {
083.             DatabaseClose(m_handleDB);
084.         }
085. //+------------------------------------------------------------------+
086.         const string ExecScriptSQL(const string szFileName)
087.         {
088.             int file, size;
089.             char buff[];
090.             string szCmd;
091.             
092.             if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
093.                 return StringFormat("Unable to open script file: %s", szFileName);
094.             ArrayResize(buff, size = (int) FileSize(file));
095.             FileReadArray(file, buff);                
096.             FileClose(file);
097.             Convert(buff, size);
098.             ArrayFree(buff);
099.             for (int count = 0, nLine; count >= 0; count++)
100.             {
101.                 szCmd = m_Arr.At(count, nLine);
102.                 if (nLine < 0) break;
103.                 if (!ExecCommandSQL(szCmd))
104.                     return StringFormat("Execution of line %d of the SQL script failed...", nLine);
105.             }
106.             
107.             return NULL;
108.         }
109. //+------------------------------------------------------------------+
110.         bool ExecCommandSQL(const string szCmd)
111.         {
112.             return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd));
113.         }
114. //+------------------------------------------------------------------+
115. };
116. //+------------------------------------------------------------------+

Исходный код для C_DB_SQL.mqh

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

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

Это подводит нас к деструктору класса, который идет сразу за ним и который можно увидеть в строке 81. Обратите внимание, что здесь мы будем просто закрывать базу данных. Таким образом, пока экземпляр класса используется, база данных будет оставаться открытой. Помните об этом, если попытаетесь получить доступ к базе данных в тот момент, когда ее использует какое-либо приложение внутри MetaTrader 5, так как это может привести к ошибкам доступа.

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

Обратите внимание, что мы изменили здесь лишь несколько деталей. Мы откроем, прочитаем, разберем и передадим команды из скрипта на выполнение. Это делается в строке 103, которая вызывает именно ту функцию, которая находится в строке 110. Хотя эта функция состоит всего из одной строки, а именно строки 112, здесь мы подготавливаем класс, а также остальную часть реализации, чтобы в будущем мы могли использовать реализацию клиент-сервер для SQL. Если это произойдет в будущем, нам потребуется внести как можно меньше изменений в код. Однако любые изменения будут вноситься исключительно в код этого класса.

Возвращаемся к строке 103. В случае сбоя выполнения SQL-команды, как можно видеть, в строке 104 мы форматируем строку. Ее цель заключается в том, чтобы вернуть вызывающей стороне информацию, указывающую, на какой именно строке SQL-скрипта произошел сбой выполнения. Однако, если всё пройдет идеально, мы получим в качестве возвращаемого значения NULL, как это можно увидеть в строке 107. Таким образом, на основе SQL-скрипта, который мы рассматривали в предыдущей статье, мы можем протестировать весь класс сразу. Для этого мы воспользуемся новой версией скрипта на языке MQL5, которую можно увидеть ниже.

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #include <Market Replay\SQL\C_DB_SQL.mqh>
08. //+------------------------------------------------------------------+
09. input string user01 = "DataBase01";       //Database File Name
10. input string user02 = "Script 01.sql";    //SQL Script File Name
11. //+------------------------------------------------------------------+
12. void OnStart()
13. {
14.     C_DB_SQL *SQL;
15.     string szMsg;
16.     
17.     SQL = new C_DB_SQL(user01);
18.     
19.     szMsg = (*SQL).ExecScriptSQL(user02);
20.     Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg);
21.     
22.     delete SQL;
23. }
24. //+------------------------------------------------------------------+

Исходный код скрипта MQL5

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

Если обнаружена ошибка, можно проверить её, изменив SQL-скрипт таким образом, чтобы он вызывал ошибку. В результате мы получим изображение, показанное ниже:

Обратите внимание на то, как всё работает. Используя всего один SQL-скрипт, мы полностью протестировали весь класс C_DB_SQL, чтобы гарантировать то, что мы сможем как отправлять команды с помощью вызова ExecCommandSQL, так и выполнять SQL-скрипт с помощью вызова ExecScriptSQL. И всё это протестировано за один раз. Подобный подход — то, к чему стремится каждый программист. То есть мы создаем набор небольших процедур, каждая из которых по отдельности относительно коротка и очень конкретна. И мы пытаемся сделать так, чтобы они выполняли очень конкретную задачу, но при этом каким-то образом заставляли все остальные процедуры запускаться и одновременно тестироваться. Когда нам действительно удается сделать что-то подобное, мы экспоненциально увеличиваем скорость программирования, потому что нам потребуется тестировать меньше процедур или тестировать их реже, так как все они будут выполняться довольно часто, что позволит устранять возможные сбои гораздо быстрее.

Хорошо. Мы завершили начальную часть. Но многие, возможно, думают, что использование внешнего файла — это настоящее мучение, поскольку нам всегда придётся следить за тем, чтобы пользователь не менял каталог, или, по крайней мере, чтобы пользователь не редактировал и не изменял SQL-скрипт, который необходимо будет выполнить.

Должен признать, в теории, я с вами согласен, уважаемый читатель. Однако я не согласен с одним пунктом. Использовать внешний файл необязательно. Данный файл можно интегрировать в исполняемый файл, созданный в MQL5. Таким образом, вы получите гарантию, что пользователь не будет изменять или подменять SQL-скрипт, который должен выполняться. Кроме того, нам будет гарантирована корректность синтаксиса SQL-скрипта. Как я уже говорил в предыдущей статье, размещать SQL-команды в виде строки внутри исполняемого файла очень рискованно. Потому что мы можем, сами того не осознавая, написать что-то неправильное. А заметить сбой мы по-настоящему сможем гораздо позже, когда база данных, как правило, уже будет интенсивно использоваться.

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

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


Интеграция SQL-скрипта в исполняемый файл

Первым делом нужно убедиться, что SQL-скрипт, который будет интегрирован в исполняемый файл, действительно работает. Попробуем сделать это, используя сам SQL. Не используйте код, который будет выполнять скрипт, внутри конечного исполняемого файла. Как бы банально это ни звучало, мы должны гарантированно знать, что скрипт действительно работает. Для этого необходимо сначала выполнить сам скрипт в SQL-среде. На этом этапе следует обратить внимание на один момент. Не знаю, читали ли вы предыдущие статьи серии о создании системы репликации/моделирования. Это связано с тем, что на протяжении нескольких статей этой серии мы уже немного рассказывали о том, как работать с SQL, чтобы вы, если у вас не было необходимых знаний по этой теме, могли немного изучить эту среду.

Как мы уже говорили тогда, вам действительно не следует думать, что использование SQLite будет чем-то отличаться от использования другой реализации SQL. Да, есть несколько отличий, но в целом, если вы понимаете SQL, вы сможете разработать то, что надо будет сделать с использованием SQLite в MetaTrader 5. Это относится даже к коду, протестированному, например, в MySQL или SQL Server. Существенных различий нет, при условии, конечно, что вы действительно понимаете, что делается.

После того, как мы убедимся в работоспособности скрипта, нужно будет сохранить его и поместить его куда-нибудь в каталог MQL5. Для примера мы будем использовать тот скрипт, который применяем начиная с предыдущей статьи. Поэтому он будет находиться в том же месте, которое было указано в предыдущей статье. Тем не менее, сначала мы изменим заголовочный файл C_DB_SQL.mqh, чтобы можно было запустить скрипт. Это связано с тем, что, в отличие от чтения внешнего файла, при использовании скрипта в качестве ресурса внутри исполняемого файла всё обычно делается иначе. Таким образом, код нового заголовочного файла можно увидеть ниже.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Service Graphics\Support\C_Array.mqh"
005. //+------------------------------------------------------------------+
006. class C_DB_SQL
007. {
008.     private    :
009.         C_Array    m_Arr;
010.         int        m_handleDB;
011. //+------------------------------------------------------------------+
012.         void Convert(const char &buff[], const int size)
013.         {
014.             string sz0 = "";
015.             bool b0, b1, bs1, bs2, bc0, bc1, bc;
016.             int nLine = 1;
017.             
018.             b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false;
019.             for (int count = 0, nC0 = nLine; count < size; count++)
020.             {
021.                 switch (buff[count])
022.                 {
023.                     case '\t':
024.                         sz0 += (bs1 || bs2 ? "\t" : "");
025.                         break;
026.                     case '\n':
027.                         nC0++;
028.                     case '\r':
029.                         bc0 = false;
030.                         break;
031.                     case ';':
032.                         b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true);
033.                     default:
034.                         switch (buff[count])
035.                         {
036.                             case '"':
037.                                 bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1);
038.                                 break;
039.                             case '\'':
040.                                 bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2);
041.                                 break;
042.                         }
043.                         if (((count + 1) < size) && (!bs1) && (!bs2))
044.                         {
045.                             if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true;
046.                             if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true;
047.                             if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false;
048.                             if (bc)
049.                             {
050.                                 count += 1;
051.                                 bc = false;
052.                                 continue;
053.                             }
054.                         }
055.                         if (!(bc0 || bc1))
056.                         {
057.                             if ((!b1) && (buff[count] > ' '))
058.                             {
059.                                 b1 = true;
060.                                 nLine = nC0;
061.                             }
062.                             sz0 += (b1 ? StringFormat("%c", buff[count]) : "");
063.                         }
064.                 }
065.                 if (b0)
066.                 {
067.                     m_Arr.Add(sz0, nLine);
068.                     sz0 = "";
069.                     b0 = b1 = false;
070.                 }
071.             }
072.         }
073. //+------------------------------------------------------------------+
074.         const string ExecSQL(void)
075.         {
076.             string szCmd;
077.             
078.             for (int count = 0, nLine; count >= 0; count++)
079.             {
080.                 szCmd = m_Arr.At(count, nLine);
081.                 if (nLine < 0) break;
082.                 if (!ExecCommandSQL(szCmd))
083.                     return StringFormat("Execution of line %d of the SQL script failed...", nLine);
084.             }
085.             
086.             return NULL;
087.         }
088. //+------------------------------------------------------------------+
089.     public    :
090. //+------------------------------------------------------------------+
091.         C_DB_SQL(const string szFileName = ":memory:")
092.         {
093.             m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE);
094.         }
095. //+------------------------------------------------------------------+
096.         ~C_DB_SQL()
097.         {
098.             DatabaseClose(m_handleDB);
099.         }
100. //+------------------------------------------------------------------+
101.         const string ExecResourceSQL(const string szResource)
102.         {
103.             char buff[];
104.             int size;
105.             
106.             ArrayResize(buff, size = StringLen(szResource));
107.             StringToCharArray(szResource, buff);
108.             Convert(buff, size);
109.             ArrayFree(buff);
110.             
111.             return ExecSQL();
112.         }
113. //+------------------------------------------------------------------+
114.         const string ExecScriptSQL(const string szFileName)
115.         {
116.             int file, size;
117.             char buff[];
118.             
119.             if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE)
120.                 return StringFormat("Unable to open script file: %s", szFileName);
121.             ArrayResize(buff, size = (int) FileSize(file));
122.             FileReadArray(file, buff);                
123.             FileClose(file);
124.             Convert(buff, size);
125.             ArrayFree(buff);
126.             
127.             return ExecSQL();
128.         }
129. //+------------------------------------------------------------------+
130.         bool ExecCommandSQL(const string szCmd)
131.         {
132.             return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd));
133.         }
134. //+------------------------------------------------------------------+
135. };
136. //+------------------------------------------------------------------+

Исходный код для C_DB_SQL.mqh

Обратите внимание, что мы внесли небольшое изменение. Первое изменение находится в функции ExecScriptSQL, где на строке 127 мы удаляем код, который был там ранее выполнен. Мы перенесли его в другое место. Можно убедиться в этом, посмотрев предыдущую версию, которая упоминается в предыдущей теме. Прошу заметить, что тот же код, который ранее находился здесь, в функции ExecScriptSQL, теперь находится в функции ExecSQL. Данная функция находится в строке 74 и является закрытой функцией класса. Иными словами, мы не можем вызвать данную процедуру вне тела класса. Это связано с тем, что вне тела класса C_DB_SQL эта функция ExecSQL не имеет смысла. Но здесь это имеет полный смысл.

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

Теперь самое интересное: поскольку мы отделили часть, отвечающую за фактическое выполнение SQL-команд, как если бы они поступали из внешнего файла, содержащего SQL-скрипт, мы можем использовать ту же самую процедуру ExecSQL для выполнения чего-то, помещенного непосредственно внутрь финального исполняемого файла. В данном случае это будет файл SQL-скрипта. Но на этом всё не заканчивается, мы можем сделать гораздо больше.

Однако, чтобы не усложнять объяснение, поскольку, если вы немного подумаете, то в итоге поймете, что мы можем сделать гораздо больше того, что я собираюсь объяснить, я не вижу необходимости вдаваться в подробности и детали относительно масштабов того, что мы только что сделали. Однако, взгляните на строку 101. Там мы видим функцию, которая очень похожа на функцию ExecScriptSQL, но здесь мы решили назвать её ExecResourceSQL, чтобы избежать конфликта с предыдущей версией ExecScriptSQL.

Теперь давайте заглянем внутрь функции в строке 101. Прошу заметить, что мы начинаем с объявления двух переменных и что в строке 106 мы резервируем место в памяти. Размер данного пространства будет соответствовать размеру строки, которую мы получим. Обратите внимание на это. Код будет находиться в строке, точно так же, как это было в случае с файлом. Только в данном случае передаваемая строка будет поступать не из файла, по крайней мере, не напрямую. Позже вы поймете это лучше. То есть, поскольку строка поступает не из файла в бинарном виде, нам необходимо преобразовать ее в бинарный массив. Таким образом, мы используем строку 107, которая преобразует строку в ее представление в виде большого массива символов.

На данном этапе мы имеем дело с чем-то эквивалентным тому, что произошло бы, если бы мы выполняли чтение с помощью файла. Это в точности отражает то, что происходит в строке 122. Итак, получив массив символов, следующим шагом будет преобразование данного массива в инструкции SQL. Это делается в строке 108. После этого мы возвращаем память операционной системе в строке 109, а в строке 111 делаем точно то же самое, что и в случае, когда информация SQL-скрипта поступала из внешнего файла. То есть мы вызываем функцию ExecSQL, которая находится в строке 74.

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

"Хорошо. Но как же мы будем использовать функцию ExecResourceSQL? Придётся ли нам копировать и вставлять всё содержимое SQL-скрипта?" На самом деле, это вполне возможно, но это было бы утомительным и совершенно ненужным делом. Мы можем сделать лучше. Мы попросим MQL5 скопировать файл для нас. Таким образом, если нам нужно будет что-то изменить, мы внесем эти изменения, и в момент повторной компиляции финального исполняемого файла сам MQL5 гарантирует, что всё будет работать должным образом. Итак, нужный файл будет интегрирован в исполняемый файл наилучшим образом и всегда с самым актуальным содержимым, независимо от того, сколько времени прошло. MQL5 возьмет на себя основную работу.

Чтобы получить эту возможность при использовании MQL5, нам нужно будет изменить основной файл следующим образом:

01. //+------------------------------------------------------------------+
02. #property copyright "Daniel Jose"
03. #property description "Basic script for SQL database written in MQL5"
04. #property version   "1.00"
05. #property script_show_inputs
06. //+------------------------------------------------------------------+
07. #resource "\\Files\\Script 01.sql" as string SQL_01
08. //+------------------------------------------------------------------+
09. #include <Market Replay\SQL\C_DB_SQL.mqh>
10. //+------------------------------------------------------------------+
11. input string user01 = "DataBase01";        //Database File Name
12. //+------------------------------------------------------------------+
13. void OnStart()
14. {
15.     C_DB_SQL *SQL;
16.     string szMsg;
17.     
18.     SQL = new C_DB_SQL(user01);
19.     
20.     szMsg = (*SQL).ExecResourceSQL(SQL_01);
21.     Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg);
22.     
23.     delete SQL;
24. }
25. //+------------------------------------------------------------------+

Исходный код скрипта MQL5

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

Теперь обратите внимание на то, что следует за именем в двойных кавычках ("). Именно здесь кроется один из приемов или способов использования ресурсов в MQL5, а также выполнения других действий.

Вы даже можете подумать, что строка 07 — это пустяком. Однако для компилятора и, следовательно, для всего кода, строка 07 определяет строковую константу с именем SQL_01, содержимое которой в точности совпадает с содержимым файла, указанного в двойных кавычках. В итоге: это было бы равносильно тому, как если бы вы вручную взяли весь код, содержащийся в файле, в данном случае Script 01.sql, и поместили его внутрь этого кода. Но вы бы поместили его не в переменную, а в строковую константу, имя которой было бы SQL_01.

Не знаю, действительно ли вы смогли понять то, что я только что сказал. Но именно здесь компилятор MQL5 нам действительно поможет, если вы измените содержимое файла Script 01.sql и сохраните его в том же каталоге, который ожидается этим основным кодом: в момент повторной компиляции кода компилятор MQL5 найдет текущее содержимое файла Script 01.sql и сделает его доступным в виде константной строки с именем SQL_01.

Подобные вещи значительно облегчают нам жизнь. В случае, если файл Script 01.sql не существует в указанном месте, компилятор MQL5 выдаст ошибку во время компиляции, указывая на то, что файл Script 01.sql отсутствует по указанному пути. Однако после того, как код будет скомпилирован, вам больше не придется беспокоиться о файле Script 01.sql. Это связано с тем, что он будет интегрирован в исполняемый файл.

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

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

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


Заключительные идеи

Глядя на код класса C_DB_SQL, можно задаться вопросом: зачем нам нужны три разные, и на первый взгляд, идентичные, функции для выполнения SQL-кода? Разве не было бы логичнее иметь всего одну функцию? Я даже сам об этом задумывался. Неужели нет способа немного упростить класс C_DB_SQL? Но когда мы отдаём код на анализ другим разработчикам, в итоге мы кое-что видим. Хотя у нас есть три идентичные с виду или очень похожие функции, которые предназначены для выполнения SQL-кода, в действительности не такие уж они одинаковые.

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

Однако в этой статье мы по-прежнему не рассматриваем другую часть, связанную с SQL. Речь идёт именно о получении ответов, которые возвращает SQL в ходе выполнения запроса нашим кодом, написанным на MQL5. Эта часть будет действительно очень интересной, так как у нас есть разные способы решения одного и того же вопроса. Тем не менее, способ доступа к результатам запросов к базе данных будет рассмотрен в следующей статье, поскольку в данный момент мы сосредоточены на использовании SQLite, с учетом того, что она интегрирована в MetaTrader 5.

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

Файл Описание
Experts\Expert Advisor.mq5
Демонстрирует взаимодействие между Chart Trade и советником (для взаимодействия требуется Mouse Study).
 Indicators\Chart Trade.mq5 Создает окно для настройки отправляемого ордера (для взаимодействия требуется Mouse Study).
 Indicators\Market Replay.mq5 Создает элементы управления для взаимодействия с сервисом репликации/моделирования (для взаимодействия требуется Mouse Study).
 Indicators\Mouse Study.mq5 Обеспечивает взаимодействие между графическими элементами управления и пользователем (необходимо как для работы системы репликации/моделирования, так и на реальном рынке).
 Services\Market Replay.mq5 Создает и поддерживает сервис репликации/моделирования рынка (главный файл всей системы)
 Code VS C++\Servidor.cpp Создает и поддерживает серверный сокет, разработанный на C++ (версия MiniChat)
 Code in Python\Server.py Создает и поддерживает сокет в Python для связи между MetaTrader 5 и Excel
 Indicators\Mini Chat.mq5 Позволяет реализовать мини-чат через индикатор (для работы требуется использование сервера)
 Experts\Mini Chat.mq5 Позволяет реализовать мини-чат с помощью советника (для работы требуется сервер).
 Scripts\SQLite.mq5 Демонстрирует использование скрипта SQL с помощью MQL5
 Files\Script 01.sql Демонстрирует создание простой таблицы с внешним ключом.
 Files\Script 02.sql Показывает добавление значений в таблицу


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

Прикрепленные файлы |
Anexo.zip (571.71 KB)
Архитектура машинного обучения для MetaTrader 5 (Часть 16): Вложенная кросс-валидация для несмещённой оценки Архитектура машинного обучения для MetaTrader 5 (Часть 16): Вложенная кросс-валидация для несмещённой оценки
В статье представлен конвейер вложенной кросс-валидации V-in-V для финансовых данных, который устраняет утечку информации в трех точках принятия решений: подбор гиперпараметров, калибровка и итоговая оценка. Временное разделение на три зоны изолирует внутренний walk-forward поиск с правилом 1-SE от внешней walk-forward или CPCV-оценки, а изотоническая OOF (out-of-fold) калибровка обучается независимо. Итоговый UnifiedValidationCalibrator дает несмещенные оценки на вневыборочных данных и хорошо откалиброванные вероятности для продакшена.
Создаем объемные 3D бары на MQL5 Создаем объемные 3D бары на MQL5
Переносим 3D-бары из Python в нативный MQL5: вместо plotly и моста к терминалу — сцена на CCanvas3D и DirectX 11 прямо на графике. Цена, время и тиковый объём раскладываются по трём осям, геометрия собирается вручную из вершин и треугольников, а орбитальная камера на событиях мыши даёт интерактивный осмотр без внешних зависимостей.
Алгоритм оптимизации на основе коронавируса — Corona Virus Optimization (CVO) Алгоритм оптимизации на основе коронавируса — Corona Virus Optimization (CVO)
Описываем и реализуем CVO: заражение как генерация кандидатов, покоординатное нормальное возмущение, динамическая популяция. Алгоритм интегрирован в C_AO и проверен на стандартном бенчмарке. Разбор выявляет масштабную причину стагнации и даёт прикладное решение — переход к относительному шагу по ширине диапазона; код готов к использованию.
Возможности Мастера MQL5, которые вам нужно знать (Часть 76): Использование паттернов Awesome Oscillator и каналов конвертов с обучением с учителем Возможности Мастера MQL5, которые вам нужно знать (Часть 76): Использование паттернов Awesome Oscillator и каналов конвертов с обучением с учителем
В продолжение нашей предыдущей статьи о паре индикаторов Awesome Oscillator и каналов конвертов (Envelope Channels), мы рассмотрим, как эту пару можно улучшить с помощью обучения с учителем. Awesome Oscillator и канал конвертов — это взаимодополняющее сочетание инструментов, позволяющих выявлять тренды и создавать уровни поддержки/сопротивления. Наш подход к обучению с учителем представляет собой сверточную нейронную сеть (CNN), которая использует ядро скалярного произведения (Dot Product Kernel) с механизмом внимания во времени (Cross-Time-Attention) для определения размеров своих ядер и каналов. Как обычно, это делается в пользовательском файле класса сигналов (signal class), который взаимодействует с Мастером MQL5 для сборки советника.