Моделирование рынка: Первые шаги на SQL в MQL5 (II)
Введение
Приветствую всех в очередной статье из серии о том, как создать систему репликации/моделирования.
В предыдущей статье Моделирование рынка: Первые шаги на SQL в MQL5 (I) мы начали использовать SQL вместе с кодом на MQL5. Хотя многие считают, что мы можем без проблем встраивать SQL-код в другой код, обычно это не так. Причина заключается в том, что SQL-код включается в исполняемый файл в виде строки. И тот факт, что SQL-код внедряется в виде строки, хотя и не вызывает проблем в небольших фрагментах, в итоге это может создать нам немало головной боли.
Так происходит, потому что если при переносе SQL-кода в строку мы допустим опечатку, мы, скорее всего, этого не заметим. Не существует простого и эффективного способа визуально проверить правильность строки — то есть SQL-кода —, так как синтаксис при попадании внутрь строки не подсвечивается разными цветами.
Этот тип проблем может быть очень неприятным для нас. Я сам сталкивался с трудностями, пытаясь понять, почему SQL-код не работает правильно внутри исполняемого файла. После спокойного разбора кода я наткнулся на небольшую опечатку, которая мешала SQL установить связи между таблицами как положено. Я потерял довольно много времени только на то, чтобы выяснить причину, и, когда, наконец, её нашёл, почувствовал, что мог бы использовать это время с большей пользой. С тех пор, при разработке более сложного кода, я работаю совершенно по-другому.
Именно в этом и заключается цель данной статьи: показать вам, уважаемый читатель, альтернативу использованию SQL-кода с исполняемым файлом, созданным на языке MQL5. Если вы действительно планируете использовать SQL в MetaTrader 5, я настоятельно рекомендую вам серьезно рассмотреть метод, который покажем сегодня; вы даже сможете адаптировать его под другие задачи. Если вы собираетесь выполнять действительно сложные операции с SQL, избегайте встраивания кода непосредственно в строки внутри исполняемого файла. Велика вероятность того, что где-то произойдет ошибка и вы потратите много времени на выяснение причин. Если же, напротив, код короткий и простой, я не вижу причин для беспокойства.
Начало работы с SQL-скриптами
Для начала использования SQL-скриптов внутри исполняемого файла, написанного на MQL5, первым делом необходимо понять синтаксис SQL. Если изучить, как строятся команды, то вы увидите, что все они заканчиваются точкой с запятой (;). Это уже подсказывает, какой разделитель нам следует использовать чтобы выделять каждую завершённую SQL-команду.
Здесь происходит нечто интересное. Хотя мы обычно пишем команды SQL в несколько строк, на самом деле это одна длинная строка. Команда начинается в начале строки и заканчивается именно точкой с запятой (;). Однако читать ее так может быть затруднительно, потому что в некоторых случаях она очень длинная. Для SQL это не имеет значения: он всегда будет считать, что команда представляет собой одну строку. Это уже указывает на то, что нам следует делать.
Следующий вопрос, который нам необходимо решить, — это комментарии. Не является редкостью, когда SQL-скрипт включает в себя множество комментариев, что крайне полезно, если файл имеет большой размер или содержит разделы, требующие повышенного внимания. Поскольку мы не хотим тратить время на отправку интерпретатору частей, которые он не будет обрабатывать, нам придется удалить эти комментарии перед отправкой команды в SQL.
Однострочный комментарий, обозначаемый двумя дефисами (--), легко обработать. Многострочные комментарии, напротив, могут усложнить нам задачу, так как внутри блока могут появиться слова, которые SQL интерпретирует как команды. Именно этого мы и хотим избежать, поэтому нам придётся обрабатывать этот случай отдельно.
Есть ещё один минус. В SQL также существуют строки, и если внутри строки появляются индикаторы комментария, они не должны интерпретироваться как начало или конец комментария. Такая ситуация требует отдельной обработки и может запутать того, кто только начинает программировать. Чтобы упростить обучение, мы оставим обработку комментариев на потом; на данный момент мы будем исходить из того, что SQL-скрипты не содержат комментариев, чтобы избежать усложнения работы, которую мы будем выполнять в MQL5.
Давайте начнем со следующего: в предыдущей статье мы создали класс для работы с SQL. Этот класс всё ещё находится на ранней стадии разработки, но мы можем использовать его для создания другого класса, отвечающего за обработку скрипта. Как мы это сделаем? Это кажется сложным, но, если вы следили за моими статьями, то уже знаете, что я всегда стараюсь предложить максимально простое решение, и этот раз не станет исключением. Первым делом необходимо отделить SQL-класс от основного файла, что легко достигается путем, показанным ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. class C_DB_SQLite 05. { 06. private : 07. int m_handleDB; 08. public : 09. //+------------------------------------------------------------------+ 10. C_DB_SQLite(const string szFileName) 11. :m_handleDB(INVALID_HANDLE) 12. { 13. if ((m_handleDB = DatabaseOpen(szFileName, DATABASE_OPEN_CREATE | DATABASE_OPEN_READWRITE)) == INVALID_HANDLE) 14. { 15. Print("Unable to create or open the file ", szFileName); 16. return; 17. } 18. } 19. //+------------------------------------------------------------------+ 20. ~C_DB_SQLite() 21. { 22. DatabaseClose(m_handleDB); 23. Print("Closing Database..."); 24. } 25. //+------------------------------------------------------------------+ 26. bool Command(const string szRequestSQL) 27. { 28. bool ret = (m_handleDB == INVALID_HANDLE ? false : DatabaseExecute(m_handleDB, szRequestSQL)); 29. Print("Request execution: ", (ret ? "Success" : "Failed"), "..."); 30. return ret; 31. } 32. //+------------------------------------------------------------------+ 33. }; 34. //+------------------------------------------------------------------+
Исходный код для C_DB_SQLite.mqh
Код, который вы только что видели, находился в основном файле. То есть мы удаляем из скрипта на MQL5 часть, соответствующую классу, и создаем заголовочный файл с именем C_DB_SQLite.mqh, который сохраняем в папке Include\Market Replay\SQL. Таким образом, любой будущий скрипт, которому потребуется применить те же принципы работы с 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_SQLite.mqh> 08. //+------------------------------------------------------------------+ 09. input string user01 = "DataBase01"; //FileName 10. //+------------------------------------------------------------------+ 11. void OnStart() 12. { 13. C_DB_SQLite *DB; 14. 15. DB = new C_DB_SQLite(user01 + ".sqlite"); 16. 17. (*DB).Command("PRAGMA FOREIGN_KEYS = ON;"); 18. (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Symbols" 19. "(" 20. "id PRIMARY KEY," 21. "symbol NOT NULL UNIQUE" 22. ");"); 23. (*DB).Command("CREATE TABLE IF NOT EXISTS tb_Quotes" 24. "(" 25. "of_day NOT NULL," 26. "price NOT NULL," 27. "fk_id NOT NULL," 28. "FOREIGN KEY (fk_id) REFERENCES tb_Symbols(id)" 29. ");"); 30. delete DB; 31. } 32. //+------------------------------------------------------------------+
Исходный код скрипта MQL5
Обратите внимание на строку 07: там мы добавили #include, который позволяет скрипту получать доступ к коду заголовочного файла так, как если бы он был написан непосредственно в основном файле. Именно так мы и будем организовывать проект с использованием нескольких заголовочных файлов. Обратите внимание также, что в этой же строке я указываю точно такое же расположение файла, как я упомянул чуть ранее. Нам не потребовалось писать имя папки Include, потому что MQL5 по умолчанию ищет заголовочные файлы в этом каталоге.
Файл не обязан находиться именно там, но мы не будем вдаваться в подробности, чтобы избежать путаницы; лучше оставить всё на своих местах. Это может показаться тривиальным, тем не менее некоторые люди не могли добиться компиляции кода, и мне приходилось объяснять им это не один раз, не зная, действительно ли они всё поняли.
Поскольку данный пример очень простой, я использую его для иллюстрации работы заголовочных файлов. Прошу прощения, если это кажется очевидным: я хочу, чтобы все понимали этот процесс. Поскольку мы решили не прикреплять файлы к статьям, энтузиасты должны знать, как получить полный код и разместить каждый файл по правильному пути для генерации финального исполняемого файла.
У нас уже есть базовая структура, необходимая для работы с SQL-скриптами, хранящаяся в отдельном файле. Стоит упомянуть кое-что важное: хотя первоначальная идея заключается в сохранении SQL-скрипта в независимом файле, можно с помощью директив компиляции встроить этот файл внутрь исполняемого файла, сгенерированного в MQL5. Я думаю, это гораздо более чистое решение, чем заполнение кода MQL5 строками SQL. Но прежде чем мы увидим, как этого добиться, мы подготовим обработку данного SQL-скрипта, чтобы код MQL5 мог его использовать. Для начала создадим новый класс, как показано ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_DB_SQLite.mqh" 05. //+------------------------------------------------------------------+ 06. class C_ScriptSQL : private C_DB_SQLite 07. { 08. private : 09. public : 10. //+------------------------------------------------------------------+ 11. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 12. :C_DB_SQLite(szFileDataBase) 13. { 14. } 15. //+------------------------------------------------------------------+ 16. ~C_ScriptSQL() 17. { 18. } 19. //+------------------------------------------------------------------+ 20. bool Execute(void) 21. { 22. return false; 23. } 24. //+------------------------------------------------------------------+ 25. }; 26. //+------------------------------------------------------------------+
Начальный код файла C_ScriptSQL.mqh
Хорошо. Давайте посмотрим, что у нас здесь в начале. Обратите внимание, что в строке 04 объявлена директива #include. Это означает, что мы включаем файл C_DB_SQLite.mqh в этот заголовочный файл, который мы назовем C_ScriptSQL. Теперь обратите внимание на следующий момент. Это тонкий момент, но очень важный. Прошу заметить, что имя C_DB_SQLite.mqh заключено в двойные кавычки. Это означает, что создаваемый нами файл C_ScriptSQL должен находиться в том же каталоге, что и файл C_DB_SQLite.mqh.
Каждый раз, когда мы встречаем в коде MQL5 или C/C++ то же самое, что видим в строке 04, нам следует понимать, что текущий файл нужно искать или размещать относительно пути, указанного в #include. Другими словами, мы можем перемещаться между различными папками, просто указав это в директиве `#include`. Следовательно, этот файл C_ScriptSQL.mqh должен быть сохранен в папке Include\Market Replay\SQL, которая является той же самой папкой, где находится файл C_DB_SQLite.mqh. Если сделать так, код скомпилируется идеально.
Теперь давайте посмотрим на некоторые вещи, которые могут показаться немного сложными. Обратите внимание, что в строке 06 мы объявляем класс C_ScriptSQL, который приватно наследуется от класса C_DB_SQLite. Причины такого подхода станут понятны позже. Из-за этого наследования нам необходимо определить конструктор для инициализации класса C_DB_SQLite. Поэтому у нас есть строка 11, где мы объявляем конструктор класса C_ScriptSQL. Обратите внимание, что здесь мы получим два параметра. Итак, для инициализации конструктора C_DB_SQLite мы используем строку 12 и передаем один из параметров конструктору класса C_DB_SQLite, чтобы класс был инициализирован. Другой параметр мы используем позже.
Поскольку я хочу оставить класс уже подготовленным, в строке 16 я объявляю его деструктор. И, в строке 20, процедура, которую мы будем использовать в основном коде. Но так как эта процедура должна возвращать какое-то значение, мы уже объявляем в строке 22, что возвращаемое значение — false. Иными словами, если протестировать код ещё до его завершения, я гарантирую, что любая процедура, которая должна возвращать значение, всегда возвращает false.
Хорошо, но что именно будет делать этот класс C_ScriptSQL? Идея заключается в том, что этот класс должен получать имя файла SQL-скрипта и имя базы данных, а затем выполнять скрипт над базой данных, возможно, даже позволяя нам возвращать какое-то значение, если скрипт выполняет какой-либо запрос. На данный момент идея заключается в том, чтобы удалить код, который вы можете видеть в основном коде MQL5. Этот SQL-код можно увидеть, просмотрев скрипт в формате MQL5.
Поскольку предстоит сделать ещё очень много, нам нужно разделить проблемы на задачи и внедрять решение постепенно, задача за задачей, пока мы не получим то, что нам действительно необходимо. Таким образом, первая задача — удалить SQL-скрипт из основного кода, написанного на MQL5, и сохранить данный скрипт в файл на диске. Поскольку MQL5 по соображениям безопасности не позволяет получать доступ к файлам за пределами своего каталога, нам необходимо сохранить файл в доступном месте и легком для использования. Для простоты мы оставим его в каталоге MQL5\Files. То есть, в той же директории, которую мы будем использовать для хранения нашей базы данных по умолчанию. Таким образом, мы сохраним файл под конкретным именем и, чтобы отличать его, будем использовать расширение .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. );
Исходный код SQL-скрипта
На следующем изображении можно увидеть выделенный объект вместе с указанием его местоположения.

Идеально. У нас уже есть всё необходимое для начала работы. Обратите внимание на имя, которое мы дали файлу SQL-скрипта, потому что мы будем использовать его в ближайшее время. Прошу заметить то, о чем мы говорили ранее: каждая команда в SQL-скрипте заканчивается точкой с запятой ( ; ). Поэтому мы внедрим систему таким образом, чтобы можно было в точности выполнить показанный выше SQL-скрипт. На данном этапе мы не будем беспокоиться о комментариях, поскольку, как можно видеть, в SQL-скрипте их нет. Таким образом, код, который должен быть помещен в файл C_ScriptSQL, можно увидеть ниже.
01. //+------------------------------------------------------------------+ 02. #property copyright "Daniel Jose" 03. //+------------------------------------------------------------------+ 04. #include "C_DB_SQLite.mqh" 05. #include "..\Service Graphics\Support\C_Array.mqh" 06. //+------------------------------------------------------------------+ 07. class C_ScriptSQL : private C_DB_SQLite 08. { 09. private : 10. C_Array m_Arr; 11. char m_Buff[]; 12. int m_Size; 13. //+------------------------------------------------------------------+ 14. void Convert(void) 15. { 16. string sz0 = ""; 17. bool b0 = false, b1 = false; 18. int nLine = 1; 19. 20. for (int count = 0, nC0 = nLine; count < m_Size; count++) 21. { 22. switch (m_Buff[count]) 23. { 24. case '\t': 25. break; 26. case '\n': 27. nC0++; 28. case '\r': 29. break; 30. case ';': 31. b0 = true; 32. default: 33. if ((!b1) && (m_Buff[count] > ' ')) 34. { 35. b1 = true; 36. nLine = nC0; 37. } 38. sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : ""); 39. } 40. if (b0) 41. { 42. m_Arr.Add(sz0, nLine); 43. sz0 = ""; 44. b0 = b1 = false; 45. } 46. } 47. } 48. //+------------------------------------------------------------------+ 49. public : 50. //+------------------------------------------------------------------+ 51. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 52. :C_DB_SQLite(szFileDataBase), 53. m_Size(-1) 54. { 55. int handle; 56. 57. if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 58. { 59. Print("Unable to open script file: ", szFileScript); 60. return; 61. } 62. ArrayResize(m_Buff, m_Size = (int) FileSize(handle)); 63. FileReadArray(handle, m_Buff); 64. FileClose(handle); 65. Convert(); 66. } 67. //+------------------------------------------------------------------+ 68. ~C_ScriptSQL() 69. { 70. ArrayFree(m_Buff); 71. } 72. //+------------------------------------------------------------------+ 73. bool Execute(void) 74. { 75. int nLine; 76. string szInfo; 77. 78. for (int c = 0; c >= 0; c++) 79. { 80. szInfo = m_Arr.At(c, nLine); 81. if (nLine > 0) 82. Print(nLine, ">>", szInfo); 83. else break; 84. } 85. return false; 86. } 87. //+------------------------------------------------------------------+ 88. }; 89. //+------------------------------------------------------------------+
Код C_ScriptSQL.mqh
Прошу заметить, что код уже значительно разросся. Но здесь у нас уже реализовано почти всё, что нам нужно. Тем не менее, давайте быстро проведем обзор, чтобы понять, что мы здесь имеем. В строке 05 мы указали, что нам необходимо использовать заголовочный файл. Этот файл уже существует в исходном коде системы репликации/моделирования. Этот заголовочный файл следует искать в предыдущих статьях этой серии. Если вы следили за статьями и обновляли код так, как я показывал, вам не о чем беспокоиться, потому что у вас уже будет правильный файл, который нам нужен.
Хорошо, в строках с 10 по 12 мы видим несколько приватных переменных этого класса. В строке 14, напротив, у нас есть процедура, целью которой является разобрать содержимое m_Buff и разложить его по массиву строк. Эта процедура довольно интересна, так как она выполнит за нас всю тяжелую работу. По сути, здесь мы будем посимвольно проходить по буферу, чтобы получить строку SQL-команды.
Обратите внимание, что в каждом случае будет проверяться наличие определенного символа. В строке 24 мы проверяем символ табуляции. Пока что этот символ просто не будут учитывать. В строке 26 мы проверили наличие символа новой строки. При обнаружении данного символа счетчик строк увеличивается, что и происходит в строке 27. Обратите внимание, что в строке 28 мы анализируем наличие символа возврата каретки.
Оба символа, как возврата каретки, так и новой строки, получают общую обработку в строке 29. Однако только символ новой строки получает другую обработку, поскольку он увеличивает счетчик строк. Если редактор, который мы используем для создания SQL-скрипта, не использует символ новой строки, что довольно необычно, нам придется использовать символ возврата каретки для увеличения счетчика строк. В любом случае, я не думаю, что есть необходимость менять этот момент.
В строке 30 мы уже имеем именно тот символ, который, как мы и ожидали, должен был обозначать конец команды SQL. Обратите внимание, что там мы используем переменную, чтобы указать, что команда была найдена. Но поскольку этот же символ должен присутствовать и при отправке команды в SQL, мы не закрываем блок символом break. Таким образом, она попадет в блок default. Этот блок будет отвечать за любой символ, который не был обработан ранее. Посмотрим, что из этого выйдет.
В строке 33 мы проверяем, был ли найден какой-либо символ, отличающийся от пробела. Это связано с тем, что в начале команды SQL пробелы не нужны. Когда появится символ, отличающийся от пробела, мы отметим это в строке 35 и сразу после этого запишем номер строки, в которой начинается SQL-команда.
В строке 38 мы выделяем символ буфера. Но здесь есть один важный момент: Строка команды получит какой-либо символ только в том случае, если в команде уже присутствует хотя бы один символ. В противном случае она будет ждать, пока не появится какой-то символ. Чтобы закончить, в строке 40, когда у нас будет указание на то, что команда была полностью захвачена, она будет сохранена, а процесс перезапустится для поиска следующей SQL-команды.
Что касается конструктора и деструктора, на мой взгляд, они имеют довольно простой код, поэтому нет необходимости комментировать их каким-то особым образом. Однако в коде на строке 73 есть несколько моментов, заслуживающих пояснения. Давайте посмотрим на них. Обратите внимание, что в строке 78 мы видим довольно странный цикл for. Это связано с тем, что его отсчет начинается с нуля и постепенно увеличивается. Однако он не бесконечен, как многим может показаться. Процесс завершается, когда значение переменной становится отрицательным. И это произойдет в какой-то момент, так как тип переменной — целое число со знаком.
Но прежде чем это произойдет, в строке 80 у нас есть возможность получить команды, которые мы сохранили ранее. Обратите внимание, что в строке 81 мы проверяем, является ли указанная строка отрицательной. В этом случае будет выполнена строка 83, что завершит цикл в строке 78. Если у нас есть что-то сохраненное, мы выведем это в журнал в строке 82. Вот так это и работает.
Обратите внимание, что если вместо вывода команды в терминал мы отправим ее в класс C_DB_SQLite, то в этой точке мы сможем выполнить команду, помещенную в файл 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_ScriptSQL.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_ScriptSQL *SQL; 15. 16. SQL = new C_ScriptSQL(user02, user01); 17. 18. (*SQL).Execute(); 19. 20. delete SQL; 21. } 22. //+------------------------------------------------------------------+
Код скрипта MQL5
Запустив данный файл в терминале MetaTrader 5, мы получим такой результат:

Обратите внимание, что сообщается именно номер строки, в которой начинается SQL-команда, а также команда, которую выделила система. То есть мы уже имеем то, что хотим, и, следовательно, можем отправлять команды в класс C_DB_SQLite вместо того, чтобы выводить их в терминал. Но сначала давайте разберемся, как обрабатывать комментарии в файле SQL-скрипта. Для того, чтобы иметь возможность обрабатывать разрешенные в SQL комментарии, нам необходимо вернуться к процедуре Convert, включенной в класс C_ScriptSQL, и внести туда некоторые изменения и дополнения.
По сути, наша задача состоит в том, чтобы определить, находимся ли мы внутри строки, а не вне её. Кажется, всё довольно просто. Однако, когда дело доходит до SQL, всё становится немного сложнее. Причина в том, что SQL допускает использование как одинарных кавычек (' ), так и двойных кавычек ( " ). И нет никакого способа узнать наверняка, будет ли скрипт использовать одинарные или двойные кавычки для выделения строк. Следовательно, нам необходимо рассмотреть оба случая. Но это не самая сложная часть, на самом деле, главная проблема заключается в другом. Тем не менее давайте посмотрим, как стал выглядеть код, чтобы мы могли достичь желаемой цели. Данный момент можно увидеть ниже:
001. //+------------------------------------------------------------------+ 002. #property copyright "Daniel Jose" 003. //+------------------------------------------------------------------+ 004. #include "C_DB_SQLite.mqh" 005. #include "..\Service Graphics\Support\C_Array.mqh" 006. //+------------------------------------------------------------------+ 007. class C_ScriptSQL : private C_DB_SQLite 008. { 009. private : 010. C_Array m_Arr; 011. char m_Buff[]; 012. int m_Size; 013. //+------------------------------------------------------------------+ 014. void Convert(void) 015. { 016. string sz0 = ""; 017. bool b0, b1, bs1, bs2, bc0, bc1, bc; 018. int nLine = 1; 019. 020. b0 = b1 = bs1 = bs2 = bc0 = bc1 = bc = false; 021. for (int count = 0, nC0 = nLine; count < m_Size; count++) 022. { 023. switch (m_Buff[count]) 024. { 025. case '\t': 026. sz0 += (bs1 || bs2 ? "\t" : ""); 027. break; 028. case '\n': 029. nC0++; 030. case '\r': 031. bc0 = false; 032. break; 033. case ';': 034. b0 = (bs1 || bs2 || bc0 || bc1 ? b0 : true); 035. default: 036. switch (m_Buff[count]) 037. { 038. case '"': 039. bs1 = (bs2 || bc0 || bc1 ? bs1 : !bs1); 040. break; 041. case '\'': 042. bs2 = (bs1 || bc0 || bc1 ? bs2 : !bs2); 043. break; 044. } 045. if (((count + 1) < m_Size) && (!bs1) && (!bs2)) 046. { 047. if (bc = ((m_Buff[count] == '-') && (m_Buff[count + 1] == '-'))) bc0 = true; 048. if (bc = ((m_Buff[count] == '/') && (m_Buff[count + 1] == '*'))) bc1 = true; 049. if (bc = ((m_Buff[count] == '*') && (m_Buff[count + 1] == '/'))) bc1 = false; 050. if (bc) 051. { 052. count += 1; 053. bc = false; 054. continue; 055. } 056. } 057. if (!(bc0 || bc1)) 058. { 059. if ((!b1) && (m_Buff[count] > ' ')) 060. { 061. b1 = true; 062. nLine = nC0; 063. } 064. sz0 += (b1 ? StringFormat("%c", m_Buff[count]) : ""); 065. } 066. } 067. if (b0) 068. { 069. m_Arr.Add(sz0, nLine); 070. sz0 = ""; 071. b0 = b1 = false; 072. } 073. } 074. } 075. //+------------------------------------------------------------------+ 076. public : 077. //+------------------------------------------------------------------+ 078. C_ScriptSQL(const string szFileScript, const string szFileDataBase) 079. :C_DB_SQLite(szFileDataBase), 080. m_Size(-1) 081. { 082. int handle; 083. 084. if ((handle = FileOpen(szFileScript, FILE_READ | FILE_BIN)) == INVALID_HANDLE) 085. { 086. Print("Unable to open script file: ", szFileScript); 087. return; 088. } 089. ArrayResize(m_Buff, m_Size = (int) FileSize(handle)); 090. FileReadArray(handle, m_Buff); 091. FileClose(handle); 092. Convert(); 093. } 094. //+------------------------------------------------------------------+ 095. ~C_ScriptSQL() 096. { 097. ArrayFree(m_Buff); 098. } 099. //+------------------------------------------------------------------+ 100. bool Execute(void) 101. { 102. int nLine; 103. string szInfo; 104. 105. for (int c = 0; c >= 0; c++) 106. { 107. szInfo = m_Arr.At(c, nLine); 108. if (nLine > 0) 109. Print(nLine, ">>", szInfo); 110. else break; 111. } 112. return false; 113. } 114. //+------------------------------------------------------------------+ 115. }; 116. //+------------------------------------------------------------------+
Исходный код для C_ScriptSQL.mqh
Обратите внимание, что код гораздо сложнее. Именно поэтому ранее я показал, как всё устроено, так как понимание этого кода для обработки комментариев окажется куда более запутанным, если мы не разберемся в предыдущем варианте кода. Будьте внимательны, теперь добавлено гораздо больше переменных. И то, что символ табуляции, который раньше игнорировался, теперь уже не будет игнорироваться во всех случаях. Он будет игнорироваться только в конкретных случаях, то есть когда мы находимся вне строки. Когда мы находимся внутри неё, символ табуляции будет регистрироваться.
Также потребовалось добавить кое-что, что вы можете увидеть в строке 31. Там, когда обнаруживается новая строка, мы сбрасываем значение, используемое для указания того, что мы находимся внутри комментария. Обратите внимание, что при обнаружении символа точки с запятой ( ; ) мы проверяем, находимся ли мы внутри комментария или строки. Это необходимо для предотвращения ложных завершений команд SQL. Обратите также внимание, что в строке 36 мы добавили оператор switch. Его функция — обработка кавычек. Но почему мы не оставили это в основном тексте? Причина в том, что если бы данная обработка выполнялась в основном коде, нам пришлось бы проделать дополнительную работу по сохранению целостности SQL-скрипта.
Но на что я действительно хочу обратить ваше внимание, уважаемый читатель, так это на код в строке 45. Потому что именно с этого момента мы начинаем анализ наличия комментариев в SQL-коде. Каждая строка проверяет условие для комментария. И у всех них есть нечто общее: переменная bc. Когда эта переменная будет true, она будет указывать на то, что у нас есть комментарий и что любое содержимое должно игнорироваться. Давайте посмотрим, как это работает. В строке 47 мы проверяем наличие двойного дефиса (--), который в SQL обозначает комментарий до конца текущей строки. В строке 48 мы проверяем начало многострочного комментария. А в строке 49 мы проверяем, появляется ли конец многострочного комментария.
Все эти проверки очень просты. Однако стоит заметить, что мы всегда будем игнорировать их, находясь внутри строки. Мы также будем игнорировать определение строки, если находимся внутри комментария. По сути, это заставляет скрипт считывать и очищать всё, что не является командой. Попробуйте создать SQL-скрипты с комментариями и строками. Запустите код скрипта MQL5 в MetaTrader 5, чтобы проверить, всё ли работает как ожидалось. Внимательно изучите каждую деталь, чтобы убедиться, что код корректно обрабатывается подпрограммой класса C_ScriptSQL. Это связано с тем, что, как только этот анализ будет выполнен успешно, можно будет заменить код в строке 100 на тот, который представлен ниже. В результате скрипт будет не выводиться в терминал MetaTrader 5, а действительно выполняться. Таким образом, мы достигли своей цели.
099. //+------------------------------------------------------------------+ 100. bool Execute(void) 101. { 102. int nLine; 103. string szInfo; 104. 105. for (int c = 0; c >= 0; c++) 106. { 107. szInfo = m_Arr.At(c, nLine); 108. if (nLine < 0) return c > 0; 109. if (!this.Command(szInfo)) return false; 110. } 111. return false; 112. } 113. //+------------------------------------------------------------------+
Фрагмент из файла C_ScriptSQL.mqh
Не забудьте также обновить основной файл, добавив в него код, показанный ниже:
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_ScriptSQL.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_ScriptSQL *SQL; 15. 16. SQL = new C_ScriptSQL(user02, user01); 17. 18. Print("Result of executing the SQL script: ", (*SQL).Execute() ? "Success" : "Failed", "..."); 19. 20. delete SQL; 21. } 22. //+------------------------------------------------------------------+
Исходный код скрипта MQL5
А если вы хотите провести много тестов с помощью 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
Заключительные идеи
В этой статье мы показали, как, имея небольшие знания MQL5 и SQL, можно запускать SQL-код с помощью скрипта. Или, лучше сказать, вы можете использовать файл, содержащий SQL-код, и, предоставив соответствующую информацию, выполнить именно тот код, который содержится в этом файле SQL-скрипта.
Действительно, многие могут посчитать использование внешнего файла проблемой. Это связано с тем, что вам всегда придётся предоставлять клиенту SQL-файл. И вам придётся полагаться на то, что клиент не будет трогать или изменять код, содержащийся в SQL-файле. Это действительно проблема, если вы точно не знаете, как поступить в такой ситуации. Но даже если вам понравилось то, что вы здесь увидели, и вы опасаетесь, что кто-то может изменить или модифицировать код, который будет выполнять SQL, не стоит об этом беспокоиться.
Если вы не знаете, как встроить этот файл, содержащий SQL-код, в исполняемый файл, созданный на MQL5, не пропустите следующую статью. Я покажу вам, как это сделать там. Кроме того, мы увидим еще больше вещей, касающихся работы с SQL из MQL5.
| Файл | Описание |
|---|---|
| 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/13078
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Торговые инструменты MQL5 (Часть 26): Интеграция частотного разбиения, энтропии и критерия хи-квадрат в визуальный анализатор
Алгоритм оптимизации на основе коронавируса — Corona Virus Optimization (CVO)
Архитектура машинного обучения для MetaTrader 5 (Часть 16): Вложенная кросс-валидация для несмещённой оценки
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования