Моделирование рынка: Первые шаги на SQL в MQL5 (IV)
Введение
Приветствую всех в очередной статье из серии о том, как создать систему повторения/моделирования.
В предыдущей статье Моделирование рынка: Первые шаги на SQL в MQL5 (III) мы показали, как можно встроить SQL-скрипт в исполняемый файл, созданный в MQL5. Однако мы не ограничивались этим. Мы также внесли некоторые изменения в код класса, чтобы сделать его гораздо проще в использовании, и, конечно же, немного скорректировали способ реализации кода.
Хотя код для класса, представленный в предыдущей статье, очень полезен для создания базы данных с использованием SQLite, интегрированной в MetaTrader 5 (о чем мы расскажем позже), этот код никак не позволяет нам выполнять запросы из MQL5.
Поэтому главная цель этой статьи — максимально ясно объяснить, как можно изменить этот класс, чтобы добавить код, необходимый для выполнения запросов. Иными словами, здесь мы реализуем систему, с помощью которой сможем обращаться с запросами к базе данных SQLite и получать результаты.
Прежде чем мы увидим, как это сделать, нам нужно понять кое-что очень важное, когда мы будем использовать то, что мы показываем в этой серии. Именно из-за подобных нюансов мы пока не используем SQLite в сочетании с кодом системы репликации/моделирования, так как хотим объяснить и предельно четко показать, как на самом деле работает встроенная в MetaTrader 5 поддержка SQLite.
Многие могут подумать, что SQLite, входящий в состав MetaTrader 5, работает так же, как и версия, доступ к которой осуществляется через DLL-файл. Хотя оба варианта по сути работают одинаково, существуют некоторые особенности в использовании SQLite, интегрированного в MetaTrader 5. Разработчики могут изменить это в будущем, но ничего не следует принимать как должное. Если сомневаетесь, сначала попробуйте, а затем изучите, как решить любые проблемы, которые могут возникнуть при использовании SQLite, встроенного в конкретное приложение. Я не утверждаю, что проблема заключается в каком-то конкретном приложении. Я лишь говорю, что сначала стоит всё проверить, а уже потом делать выводы о том, что используется не та версия SQLite, которую вы ожидали.
Понимание проблемы
На момент написания данной статьи проблема наблюдается в версии 5.00 Build 3815 от 22 июня 2023 года. Да, этот код был создан довольно давно. К тому времени, как вы дочитаете эти строки, уверяю вас, что MetaTrader 5 уже будет обновлен, и всё это устареет. Тем не менее, не игнорируйте содержание этих статей, ведь они могут научить вас тому, что вы, возможно, даже не представляли возможным.
Давайте теперь перейдем к проблеме. Ознакомьтесь с приведенным ниже SQL-скриптом.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. DROP TABLE IF EXISTS tb_Symbols; 04. DROP TABLE IF EXISTS tb_Quotes; 05. 06. CREATE TABLE IF NOT EXISTS tb_Symbols 07. ( 08. id PRIMARY KEY, 09. symbol NOT NULL UNIQUE 10. ); 11. 12. CREATE TABLE IF NOT EXISTS tb_Quotes 13. ( 14. of_day NOT NULL, 15. price NOT NULL, 16. fk_id NOT NULL, 17. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 18. );
Код в SQLite
Этот SQL-скрипт создает набор таблиц, в которых существует связь между таблицами tb_Symbols и tb_Quotes. Это гарантируется внешним ключом, определенным в строке 17. Если вам непонятно, о чем мы говорим, ознакомьтесь с предыдущими статьями этой серии.
Теперь взгляните на строки 3 и 4 того же SQL-скрипта. Они удаляют таблицы из файла базы данных SQL. Независимо от того, запускаете ли вы его в MetaTrader 5 или используете DLL-библиотеку SQLite, мы получим одинаковый результат. Однако так происходит не всегда. Причина кроется именно в строке 17, которая устанавливает связь между значениями, присутствующими в tb_Symbols и tb_Quotes.
"Подождите, что вы имеете в виду, когда говорите, что мы не всегда будем получать одинаковый результат? Разве SQLite в MetaTrader 5 не работает так же, как и при использовании через DLL-библиотеку? Почему вы утверждаете, что запуск данного скрипта дает разные результаты?" Возможно, я не очень хорошо выразился, или, по крайней мере, не объяснил как следует подробностей. На самом деле, выполнение приводит к тому же результату; однако при наличии какого-либо значения, устанавливающего связь между tb_Symbols и tb_Quotes, встроенный в MetaTrader 5 SQLite не может удалить таблицы, и возникает ошибка, в чём можно убедиться с помощью следующего теста.
Запустите приведенный ниже скрипт дважды подряд. Первый запуск завершится без ошибок. Но при повторном запуске мы получим изображение, сопровождающее код.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. DROP TABLE IF EXISTS tb_Symbols; 04. DROP TABLE IF EXISTS tb_Quotes; 05. 06. CREATE TABLE IF NOT EXISTS tb_Symbols 07. ( 08. id PRIMARY KEY, 09. symbol NOT NULL UNIQUE 10. ); 11. 12. CREATE TABLE IF NOT EXISTS tb_Quotes 13. ( 14. of_day NOT NULL, 15. price NOT NULL, 16. fk_id NOT NULL, 17. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 18. ); 19. 20. INSERT INTO tb_Symbols (id, symbol) VALUES (1, 'PETR4'); 21. 22. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES ('2023-07-10', '22.00', 1);
Код в SQLite

Обратите внимание, что при интервале менее трех секунд между выполнениями результаты оказались совершенно разными. При первой попытке SQL-скрипт выполнился успешно; сразу после этого, при повторном запуске того же скрипта, операция завершилась ошибкой. Сообщенный код ошибки ясно указывает на то, что проблема связана со взаимосвязью между таблицами, а данная взаимосвязь возникает именно в 17-й строке скрипта.
Однако ошибка не была вызвана именно строкой 17. Обратите внимание на изображение выше: код нашего класса, предназначенный для выполнения SQL-скриптов, сообщает, что проблема возникла при отправке строки 3 на выполнение в SQLite.
Тем не менее, нужно подчеркнуть кое-что. Если мы отправим этот же SQL-скрипт в SQLite для независимого выполнения, код выполнится без ошибок. Мы продемонстрировали это, когда рассказывали о программировании на SQL; если у вас возникнут сомнения, ознакомьтесь с предыдущими статьями, где я это наглядно показал.
Можно подумать, что проблема заключается в SQLite, интегрированном в MetaTrader 5, но я не собираюсь разжигать споры. Возможно, эта встроенная версия содержит механизм безопасности, который предотвращает удаление таблиц с активными внешними ключами. Не знаю точной причины этой кажущейся асимметрии между автономным SQLite и тем, который поставляется с MetaTrader 5. И это не единственное различие: вы также заметите несоответствия, например, при попытке создать триггер с помощью встроенной библиотеки SQLite.
Поэтому я не буду вас винить, если вы решите использовать SQLite через DLL. Тем не менее, судя по моим тестам, встроенный SQLite позволяет решать множество задач без использования DLL. Если же различия в поведении станут слишком критичными, мы найдем другой путь. На данный момент мы можем продолжить работу с интегрированной версией.
Таким образом, если мы используем встроенный в MetaTrader 5 SQLite и нам необходимо удалить таблицы базы данных, самый простой способ — удалить файл базы данных. При повторном запуске скрипта, встроенного в исполняемый файл, никаких конфликтов не возникнет.
Хорошо. Если всё изложенное выше понятно, перейдём к следующему шагу — самое интересное начинается прямо сейчас. Рекомендую вам максимально сосредоточиться на объяснении.
В статье Моделирование рынка (Часть 25): Первые шаги на SQL в MQL5 (I) мы упоминали, что для работы с SQL нам достаточно шести функций из тех, что доступны в MQL5. В предыдущих статьях мы уже рассмотрели три из них, и они уже реализованы в классе C_DB_SQL. Сейчас самое время узнать и понять, почему нам нужно только три другие функции. Чтобы должным образом разделить объяснения, мы рассмотрим новую тему дальше.
Последние три необходимые функции
То, что я объясню далее, ни в коем случае не должно заставить вас чувствовать себя ущемлённым из-за того, что вы не поняли этого раньше, независимо от того, используете ли вы уже базы данных в MQL5 или только начинаете. Я просто покажу, как это делаю я: это и не самый правильный, и не самый неподходящий способ, а лишь мой подход к работе.
Утверждая, что для доступа к SQL достаточно всего шести функций MQL5, я вовсе не говорю, что вы ошибаетесь, если используете больше функций или процедур. Напротив, я восхищаюсь теми, кто досконально изучает язык или инструмент. Мой подход заключается в использовании самого необходимого минимума.
Я обычно не запоминаю каждую деталь всех функций каждого языка или инструмента, которые использую; для этого потребовалось бы время, а доступные сроки зачастую ограничены. Я предпочитаю выполнять задачу в срок, независимо от уровня сложности.
Чтобы проверить правильность моего подхода, мы снова внесем изменения в скрипт базы данных. Я предлагаю сделать это иначе, чтобы избежать негибких приемов: создай новый скрипт и назови его script 02.sql. Если хотите, добавьте его содержимое в конец файла script 01.sql; вы получите точно такой же результат.
Единственное отличие, если вы последуете моему совету, заключается в том, что вам потребуется немного изменить основной файл. Поскольку не все знают, где именно нужно внести изменения, мы подробно остановимся на этом решении и покажем, как его реализовать для получения правильного результата.
Цель здесь — вставить данные в базу данных, чтобы впоследствии можно было выполнять запросы. Посмотрите на следующие SQL-скрипты.
01. PRAGMA FOREIGN_KEYS = ON; 02. 03. CREATE TABLE IF NOT EXISTS tb_Symbols 04. ( 05. id PRIMARY KEY, 06. symbol NOT NULL UNIQUE 07. ); 08. 09. CREATE TABLE IF NOT EXISTS tb_Quotes 10. ( 11. of_day NOT NULL, 12. price NOT NULL, 13. fk_id NOT NULL, 14. FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id) 15. );
Код в SQLite
01. INSERT INTO tb_Symbols (id, symbol) VALUES
02. (2, 'PETR4'),
03. (1, 'ITUB3'),
04. (3, 'VALE3');
05.
06. INSERT INTO tb_Quotes (of_day, price, fk_id) VALUES
07. ('2023-07-10', '22.00', 1),
08. ('2023-07-11', '22.20', 1),
09. ('2023-07-12', '22.40', 1),
10. ('2023-07-13', '22.30', 1),
11. ('2023-07-14', '22.60', 1),
12. ('2023-07-10', '26.00', 2),
13. ('2023-07-11', '26.20', 2),
14. ('2023-07-12', '26.40', 2),
15. ('2023-07-13', '26.30', 2),
16. ('2023-07-14', '26.60', 2),
17. ('2023-07-10', '62.00', 3),
18. ('2023-07-11', '62.20', 3),
19. ('2023-07-12', '62.40', 3),
20. ('2023-07-13', '62.30', 3),
21. ('2023-07-14', '62.60', 3); Код в SQLite
Хотя их два, можно объединить их содержимое и запустить программу с уже показанным основным файлом 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_Create 08. #resource "\\Files\\Script 02.sql" as string SQL_Insert 09. //+------------------------------------------------------------------+ 10. #include <Market Replay\SQL\C_DB_SQL.mqh> 11. //+------------------------------------------------------------------+ 12. input string user01 = "DataBase01"; //Database File Name 13. //+------------------------------------------------------------------+ 14. C_DB_SQL *SQL; 15. //+------------------------------------------------------------------+ 16. const string ExecScripts(void) 17. { 18. string szMsg = (*SQL).ExecResourceSQL(SQL_Create); 19. if (szMsg != NULL) return szMsg; 20. return (*SQL).ExecResourceSQL(SQL_Insert); 21. }; 22. //+------------------------------------------------------------------+ 23. void OnStart() 24. { 25. string szMsg; 26. 27. SQL = new C_DB_SQL(user01); 28. 29. szMsg = ExecScripts(); 30. Print(szMsg == NULL ? "Result of executing the SQL script: Success" : szMsg); 31. 32. delete SQL; 33. } 34. //+------------------------------------------------------------------+
Код на языке MQL5
Видно, что всё очень просто, практично и понятно. Первый SQL-скрипт создает необходимые таблицы; второй вставляет данные в базу данных, добавляя некоторые значения. Поскольку эти же скрипты уже встречались ранее, вы наверняка знаете, что делает каждый из них и как они взаимодействуют для создания записей, которые мы собираемся сформировать.
Хорошо, теперь посмотрите на код на языке MQL5. Прошу заметить, что мы добавили строку 8 и изменили название существующего ресурса на более подходящее. В связи с некоторыми особенностями строки 29, мы вызываем функцию, расположенную в строке 16. Её цель — выполнить два SQL-скрипта, которые на самом деле будут внутренними ресурсами финального исполняемого файла, созданного в MQL5. Во-первых, в строке 18 мы запрашиваем выполнение SQL-скрипта, который создает таблицы. Затем, в строке 19, мы проверили результат. Если первый SQL-скрипт выполняется успешно, мы запускаем второй в строке 20.
Задача второго SQL-скрипта — вставить данные в базу данных. Это простая задача и не требует больших усилий для выполнения. С этим у нас есть всё необходимое для следующего этапа реализации: разработки части, которая будет выполнять запросы к базе данных SQL из кода MQL5. Но сначала давайте рассмотрим содержимое файла базы данных; для этого мы можем удобно использовать MetaEditor, как показано ниже:

Можно увидеть, что ожидаемый контент действительно был добавлен в базу данных. С этой уверенностью перейдём к следующему шагу. Вернемся к заголовочному файлу C_DB_SQL.mqh. Помните, что в своем исходном состоянии определённый там класс не может возвращать никаких результатов запроса. Хотя запускать команды SELECT возможно, как мы уже объясняли перед началом работы с кодом MQL5, в этом мало толку, если мы не можем использовать результаты напрямую в самом MQL5.
Разработчики MQL5 предусмотрели несколько функций для получения результатов SQL-запросов. Ещё раз подчеркнём, что всё, что мы рассмотрим далее, предполагает использование SQLite, интегрированного в MetaTrader 5. Если вы используете SQLite через DLL, процедура немного меняется; возможно, позже мы объясним, как это сделать. На данный момент мы будем работать с интегрированной базой данных SQLite. Аналогичным образом, при доступе к базе данных через сокеты требуется другой подход. Не путайте их: каждая ситуация требует своего решения, даже если конечный результат может показаться одинаковым.
С учетом вышесказанного, мы можем сосредоточиться на самом коде. Нам необходимо добиться того, чтобы запросы выполнялись, а их результаты при необходимости можно было легко интерпретировать. Для этого нам потребуется использовать две новые функции. Первая из них — DatabasePrepare, предназначенная для выполнения запросов к базе данных. Обратите внимание, что нам не следует использовать DatabaseExecute, как многие могут предположить или подумать. Причина в том, что DatabaseExecute сообщает нам только о том, была ли команда SQL успешно выполнена или нет. Она не предоставляет нам никакой возможности увидеть, что возвращает SQL при выполнении запроса.
Именно поэтому мы используем DatabasePrepare, поскольку данная функция позволяет нам увидеть, какие данные SQL вернет нашему приложению MQL5. Это первая часть и первая необходимая нам функция. Кроме того, нам понадобится ещё одна функция, предназначенная для чтения данных, возвращаемых SQL-запросом. Но сначала давайте посмотрим, как изменился код класса для включения DatabasePrepare. Код класса указан ниже.
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. m_Request; 012. //+------------------------------------------------------------------+ 013. void Convert(const char &buff[], const int size) 014. { 015. string sz0 = ""; 016. bool b0, b1, bs1, bs2, bc0, bc1, bc; 017. int nLine = 1; 018. 019. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 020. for (int count = 0, nC0 = nLine; count < size; count++) 021. { 022. switch (buff[count]) 023. { 024. case '\t': 025. sz0 += (bs1 || bs2 ? "\t" : ""); 026. break; 027. case '\n': 028. nC0++; 029. case '\r': 030. bc0 = false; 031. break; 032. case ';': 033. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 034. default: 035. switch (buff[count]) 036. { 037. case '"': 038. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 039. break; 040. case '\'': 041. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 042. break; 043. } 044. if (((count + 1) < size) && (!bs1) && (!bs2)) 045. { 046. if (bc = ((buff[count] == '-') && (buff[count + 1] == '-'))) bc0 = true; 047. if (bc = ((buff[count] == '/') && (buff[count + 1] == '*'))) bc1 = true; 048. if (bc = ((buff[count] == '*') && (buff[count + 1] == '/'))) bc1 = false; 049. if (bc) 050. { 051. count += 1; 052. bc = false; 053. continue; 054. } 055. } 056. if (!(bc0 || bc1)) 057. { 058. if ((!b1) && (buff[count] > ' ')) 059. { 060. b1 = true; 061. nLine = nC0; 062. } 063. sz0 += (b1 ? StringFormat("%c", buff[count]) : ""); 064. } 065. } 066. if (b0) 067. { 068. m_Arr.Add(sz0, nLine); 069. sz0 = ""; 070. b0 = b1 = false; 071. } 072. } 073. } 074. //+------------------------------------------------------------------+ 075. const string ExecSQL(void) 076. { 077. string szCmd; 078. 079. for (int count = 0, nLine; count >= 0; count++) 080. { 081. szCmd = m_Arr.At(count, nLine); 082. if (nLine < 0) break; 083. if (!ExecCommandSQL(szCmd)) 084. return StringFormat("Execution of line %d of the SQL script failed...", nLine); 085. } 086. 087. return NULL; 088. } 089. //+------------------------------------------------------------------+ 090. public : 091. //+------------------------------------------------------------------+ 092. C_DB_SQL(const string szFileName = ":memory:") 093. { 094. m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE); 095. m_Request = INVALID_HANDLE; 096. } 097. //+------------------------------------------------------------------+ 098. ~C_DB_SQL() 099. { 100. DatabaseClose(m_handleDB); 101. } 102. //+------------------------------------------------------------------+ 103. const string ExecResourceSQL(const string szResource) 104. { 105. char buff[]; 106. int size; 107. 108. ArrayResize(buff, size = StringLen(szResource)); 109. StringToCharArray(szResource, buff); 110. Convert(buff, size); 111. ArrayFree(buff); 112. 113. return ExecSQL(); 114. } 115. //+------------------------------------------------------------------+ 116. const string ExecScriptSQL(const string szFileName) 117. { 118. int file, size; 119. char buff[]; 120. 121. if ((file = FileOpen(szFileName, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 122. return StringFormat("Unable to open script file: %s", szFileName); 123. ArrayResize(buff, size = (int) FileSize(file)); 124. FileReadArray(file, buff); 125. FileClose(file); 126. Convert(buff, size); 127. ArrayFree(buff); 128. 129. return ExecSQL(); 130. } 131. //+------------------------------------------------------------------+ 132. bool ExecCommandSQL(const string szCmd) 133. { 134. return (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szCmd)); 135. } 136. //+------------------------------------------------------------------+ 137. bool ExecRequestOfData(const string szCmd) 138. { 139. if (m_Request != INVALID_HANDLE) DatabaseFinalize(m_Request); 140. return ((m_Request = DatabasePrepare(m_handleDB, szCmd)) != INVALID_HANDLE); 141. } 142. //+------------------------------------------------------------------+ 143. }; 144. //+------------------------------------------------------------------+
Код C_DB_SQL.mqh
Прошу заметить, что в строке 11 мы добавили новую переменную. Данная переменная будет использоваться для чтения данных, которые SQL возвращает нам в запросе. Как и все переменные, она будет инициализирована в строке 95. Обратите внимание на значение, которое мы используем. Теперь перейдём к строке 137, где реализована функция ExecRequestOfData. Это функция, которую мы будем использовать для отправки команды запроса в SQL. Обычно эта команда выполняется с использованием SELECT FROM, но, как увидим позже, здесь это может быть довольно специфично.
В любом случае, прошу заметить, что в строке 139 мы проверяем, имеет ли переменная m_Request значение, отличное от INVALID_HANDLE. Если это подтвердится, значит, у нас есть предыдущий запрос в кэше. Иными словами, мы хотим удалить данные из предыдущего запроса, чтобы выполнить новый. И для этого мы используем другую функцию: DatabaseFinalize. Ее цель — удалить данные из предыдущего запроса, и таким образом она представляет собой третью функцию, которая нам понадобится на данном этапе расширения класса C_DB_SQL.
После завершения предыдущего запроса, если он был, в строке 140 можно отправить новый запрос. Обратите внимание, что не обязательно указывать имя базы данных. Это было сделано в момент создания класса. Все, что нам нужно, — это указать, какую команду использовать, а сама команда поступит от вызывающей стороны, которую мы рассмотрим позже. При успешном выполнении запроса функция DatabasePrepare вернет числовое значение. Эти данные будут сохранены в переменной m_Request для последующего использования. Если запрос завершится ошибкой, будет возвращено значение INVALID_HANDLE. И, следовательно, функция ExecRequestOfData вернет false, сообщая вызывающей стороне, что выполнение запроса завершилось ошибкой.
Хорошо, но здесь есть один нюанс: почему бы нам не вернуть данные напрямую из самой функции ExecRequestOfData? Почему мы предоставляем обратную связь только в случае успеха или неудачи запроса? Причина очень проста. Возможно, по той или иной причине мы захотим сделать запрос, который вернет определенные значения или поля таблицы. И в зависимости от специфики той или иной области, мы можем выбрать разные направления или принять разные решения. Это вынудило бы нас выполнить новый запрос к базе данных. Хотя это и не является ошибкой, такой подход приводит к избыточным запросам к базе данных, что особенно критично в случае клиент-сервера.
Все необходимые поля можно получить одним запросом. Это, помимо оптимизации SQL-запросов к базе данных, также помогает нам немного ускорить работу основного кода. По этой причине чтение данных вынесено в отдельную функцию, предназначенную исключительно для этой цели.
Это может показаться абсолютной глупостью и даже чем-то непрактичным. Но учтите, что можно очень легко адаптировать основной код для чтения конкретного поля именно потому, что выполнение запроса отделено от чтения полученного результата. Это может показаться очень сложным для объяснения, так как при выполнении SQL-запроса мы можем получить несколько возвращаемых значений, используя всего одну из колонок одной из таблиц. Это показано на предыдущей анимации в этой же статье.
Но если запрос составлен грамотно и правильно ориентирован, зачастую мы будем получать один-единственный результат. Хотя можно и не получить ни одного. Это происходит так, потому что критерии, использованные в запросе, не были найдены в базе данных.
Именно здесь возникает нюанс, который мало кто понимает по-настоящему. И прежде чем показать функцию чтения данных, мне хотелось бы воспользоваться возможностью и объяснить этот момент, так как данная тема может быть вам интересна.
Многие люди склонны недооценивать SQL или даже вообще не использовать его, потому что не до конца понимают, как он на самом деле работает. Когда мы обращаемся с запросом к базе данных SQL, мы не всегда ищем общий ответ. В некоторых случаях нам может потребоваться очень объективный и практичный ответ. Если создать базу данных с надлежащей структурой и моделью данных, в неё можно будет интегрировать практически любые типы информации. Это довольно интересно, поскольку люди часто думают о базе данных лишь как о наборе чисел и буквенно-цифровых символов для хранения данных о продажах, клиентах, товарах или тому подобном.
Но если правильно понимать, как работает SQL и как с ним работать, можно создать систему или, точнее, небольшого робота, способного научиться работать с заданным символом. И всё это без необходимости создавать операционную модель. Мы просто вводим ряд котировок в базу данных и, используя SQL для запросов в сочетании с самим MQL5 для выполнения вычислений, которые SQL сделать не может, создаем механизм, способный в полностью автоматическом режиме обучаться торговле на рынке. Данный механизм самостоятельно научится предсказывать возможные будущие движения данного символа.
Я уже упоминал об этом некоторое время назад, в той же серии статей, посвященных системе репликации/моделирования. Возможно, вы ещё не видели или не заметили, сколько можно достичь с помощью этой простой системы, процесс создания которой я сейчас демонстрирую. Но если изучать этот вопрос должным образом, то можно создать тот самый механизм, о котором мы говорили. Конечно, для получения нужного результата нам понадобится использовать определенные математические методы и навыки программирования. Именно поэтому мы не возвращаем результат запроса непосредственно в базу данных SQL при его выполнении из MQL5, как показано в приведенном выше коде.
Может случиться так, что этот запрос был составлен таким образом, что мы сможем использовать результаты совершенно иначе, чем многие ожидали бы. Поскольку разработчики MQL5 разрешили нам использовать этот механизм, я не думаю, что разумно менять способ его работы. А поскольку объяснить, как именно будет производиться чтение, довольно сложно в нескольких словах, мы оставим и это объяснение, и изменения, которые необходимо будет внести в основной код, для следующей статьи.
Заключительные идеи
В этой статье мы рассказали о том, как мы можем сделать некоторые вещи. Мы упомянули тот факт, что можно создать робота, способного обучиться торговле без необходимости проектировать для него операционную модель. Мы обсудили проблему, возникающую при попытке использовать некоторые функции SQL непосредственно в SQLite, встроенном в MetaTrader 5, и показали, как её можно обойти. Но, несмотря на всё это, мы так и не показали, как на самом деле можно получить результаты выполнения запроса, используя сам MQL5. Причина в том, что мы можем создать функцию, которая автоматически адаптируется к ожидаемому результату запроса. Это для того, чтобы вы могли понять, почему мы сказали, что нам нужны всего шесть функций, которые MQL5 позволяет использовать при работе с SQL.
И ещё одна последняя деталь: В MQL5 уже есть механизмы для использования моделирования ONNX. Моделирование ONNX гораздо лучше подходит для создания робота, способного самостоятельно обучиться торговле на рынке, чем SQL. Но ничто не мешает вам использовать для этой цели моделирование на основе SQL. Главное — это обучение и правильное применение полученных знаний.
| Файл | Описание |
|---|---|
| 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/13110
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Разработка инструментария для анализа Price Action (Часть 62): Создание адаптивной системы обнаружения параллельных каналов и пробоев на языке MQL5
Повышение эффективности торговли с использованием Smart Money Concepts (SMC): OB, BOS и FVG
Знакомство с языком MQL5 (Часть 42): Руководство для начинающих по работе с файлами в MQL5 (IV)
MetaTrader и Google Таблицы через PythonAnywhere: Руководство по безопасному потоку данных
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования