Внедрение в MQL5 практических модулей из других языков (Часть 1): Создание библиотеки SQLite3 как в Python
Разделы
- Введение
- Что такое модуль sqlite3 в Python
- Подключение к базе данных SQLite
- Выполнение SQL-операторов
- Работа с текстовыми (строковыми) значениями из базы данных
- Управление транзакциями базы данных
- Другие методы в модуле sqlite3
- Регистрация сделок в базе данных
- Заключение
Введение
Случалось ли вам когда-нибудь хотеть, чтобы один или два ваших любимых модуля, библиотеки, фреймворка и т. д., присутствующие в другом языке программирования, были встроены в MQL5? Со мной такое часто случается.
В сообществе MQL5 огромное количество разработчиков с различным опытом программирования; некоторые, как и я, пришли из веб-разработки, другие — из разработки под Android, и из многих других профессий ,связанных с программированием. Это означает, что большинство программистов знакомы с различными языками программирования, такими как JavaScript, Java, Python, C++, C#, и это лишь некоторые из них.
В этих разных языках программирования программисты сталкиваются с различными инструментами (модулями) кодирования — полезными модулями, которые мы просто хотим использовать везде, где это возможно. Например, мне до такой степени нравится использовать для математических вычислений модуль NumPy, предлагаемый в Python, что однажды мне пришлось реализовать аналогичную библиотеку на языке MQL5, описав реализацию в этой статье.
Хотя попытка внедрить модуль, инструмент, фреймворк и т. д. из одного языка в другой — в данном случае в MQL5 — может привести к несколько иным функциональным возможностям и результатам из-за различий в природе языков программирования, наличия схожего синтаксиса или опыта может быть достаточно, чтобы разработка продукта на MQL5 стала легкой и увлекательной для разработчиков, знакомых с разными языками. Не говоря уже о том, что в процессе мы можем получить ценную информацию, которая укрепит наши навыки программирования в целом.
В этой новой серии статей мы будем реализовывать не все модули из других языков, а все модули из другого языка, которые имеют практическое значение в языке MQL5. Например, это могут быть модули для математических вычислений, хранения данных, анализа данных и т. д.
Начиная с модуля sqlite3, который входит в комплект поставки языка программирования Python.
Что такое модуль sqlite3 в Python
Давайте сначала разберемся в базе данных SQLite:
База данных SQLite — это упрощенная, автономная, не требующая серверов система управления базами данных SQL. Она широко используется в приложениях, требующих простого, встроенного, локального решения для хранения данных. Это файловая база данных, которая хранит (схему, таблицы, индексы и данные) всё в одном файле .sqlite или.db.
В отличие от баз данных MySQL илиPostgreSQL, требующих определенной настройки, наличия сервера и административных параметров, SQLite работает сразу же без всего этого, поскольку читает и записывает данные непосредственно на диск.
Язык программирования MQL5 поставляется со встроенными функциями для работы с базой данных SQLite; этих встроенных функций достаточно. Однако, по сравнению с использованием модуля sqlite3 в Python, они не так просты в использовании.
Например, при попытке создать простой «пример» базы данных и вставить некоторую информацию в таблицу с именем users, таблица создается автоматически, если она еще не существует.
Использование модуля Python sqlite
import sqlite3 conn = sqlite3.connect("example.db") cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, age INTEGER, email TEXT UNIQUE ) """) # Execute a query try: cursor.execute("INSERT INTO users (name, age, email) VALUES (?, ?, ?)", ("Bruh", 30, "bruh@example.com")) conn.commit() # commit the transaction | save the new information to a database except sqlite3.DatabaseError as e: print("Insert failed:", e) conn.close() # closing the database
Использование встроенных функций MQL5
void OnStart() { //--- int db_handle = DatabaseOpen("example.db", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if (db_handle == INVALID_HANDLE) { printf("Failed to open a database. Error = %s",ErrorDescription(GetLastError())); return; } string sql = " CREATE TABLE IF NOT EXISTS users (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " name TEXT NOT NULL," " age INTEGER," " email TEXT UNIQUE" ")"; if (!DatabaseExecute(db_handle, sql)) //Execute a sql query { printf("Failed to execute a query to the database. Error = %s",ErrorDescription(GetLastError())); return; } if (!DatabaseTransactionBegin(db_handle)) //Begin the transaction { printf("Failed to begin a transaction. Error = %s",ErrorDescription(GetLastError())); return; } sql = "INSERT INTO users (name, age, email) VALUES ('Bruh', 30, 'bruh@example.com')"; if (!DatabaseExecute(db_handle, sql)) //Execute a query { printf("Failed to execute a query to the database. Error = %s",ErrorDescription(GetLastError())); return; } if (!DatabaseTransactionCommit(db_handle)) //Commit the transaction | push the changes to a database { printf("Failed to commit a transaction. Error = %s",ErrorDescription(GetLastError())); return; } DatabaseClose(db_handle); //Close the database }
Все мы можем согласиться с тем, что использование sqlite3 в языке Python сделало наш код чище, чем использование встроенных функций языка MQL5, которые требуют от нас обработки ошибок, возвращаемой или добавляемой в базу данных информации.
Модуль sqlite3 обрабатывает множество ненужных шагов при выполнении команды и управлении транзакциями, что значительно упрощает пользователям получение и вставку некоторых данных в базу данных SQLite и извлечение их из нее без особых проблем.
Итак, попробуем реализовать аналогичный модуль на языке MQL5.
Подключение к базе данных SQLite
Метод connect в sqlite3 языка Python создает новую базу данных, если заданное имя для базы данных не существует, и возвращает объект соединения, представляющий собой подключение к базе данных на диске.
import sqlite3 con = sqlite3.connect("example.db")
В MQL5 это соединение аналогично элементу handle базы данных, поэтому технически нам не нужно возвращать хэндл в нашей библиотеке MQL5, поскольку мы будем использовать его во всех функциях внутри нашего класса.
class CSqlite3 { protected: int m_request; public: int m_db_handle; //... Остальные функции }
bool CSqlite3::connect(const string db_name, const bool common=false, const bool database_in_memory=false) { int flags = DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE; if (common) //Open the database from the common folder flags |= DATABASE_OPEN_COMMON; if (database_in_memory) //Open the database in memory flags |= DATABASE_OPEN_MEMORY; m_db_handle = DatabaseOpen(db_name, flags); //--- if (m_db_handle == INVALID_HANDLE) { printf("func=%s line=%d, Failed to open a database. Error = %s",__FUNCTION__, __LINE__, ErrorDescription(GetLastError())); return false; } return true; }
MQL5 предоставляет нам возможность сохранять данные из любого из двух источников: datapath (папка) или common datapath (папка). Когда аргумент common установлен как true, информация из базы данных будет считываться из источника common datapath, а не из обычного источника datapath.
Кроме того, мы можем предоставить пользователю возможность выбора, открывать базу данных в оперативной памяти (RAM) или на диске, как в случае установки переменной database_in_memory на true. База данных будет создана в оперативной памяти вместо создания ее на диске, и наоборот.
Мы игнорируем один флаг, а именно DATABASE_OPEN_READONLY, при открытии базы данных в режиме ReadOnly. Причина проста: если вы не хотите записывать данные в базу данных, вы вообще не будете выполнять запросы на вставку (INSERT). Логично?
Выполнение SQL-операторов
Это одна из важнейших функций, которую мы часто используем при работе с базами данных SQLite. Эта функция позволяет получать информацию, вставлять, обновлять, удалять и изменять значения в базе данных и т. д.
Эта функция выполняет SQL-запросы и команды непосредственно в нашей базе данных.
В sqlite3 на языке Python функция execute() работает безупречно и без усилий. Она может принимать любые команды как для вставки, так и для получения информации, и автоматически знает, когда и что возвращать, а что нет.
В MQL5 есть встроенная функция с именем DatabaseExecute, которая похожа на execute() в sqlite3 на языке Python. Оба процесса исполняют запрос к указанной базе данных или таблице. Однако этот метод MQL5 подходит для выполнения всех запросов, кроме тех, которые содержат ключевое слово "SELECT" для чтения информации из базы данных.
Для эффективного чтения информации из базы данных используем функцию Database Prepare, поскольку это создает хэндл запроса, который затем может быть выполнен с помощью DatabaseRead.
Для создания аналогичной гибкой функции в MQL5, позволяющей исполнять функции независимо от типа запроса, необходимо различать типы SQL-запросов и возвращать правильную информацию с корректным запросом.
execute_res_structure CSqlite3::execute(const string sql) { execute_res_structure res; string trimmed_sql = sql; StringTrimLeft(trimmed_sql); //Trim the leading white space string first_word = trimmed_sql; // Find the index of the first space (to isolate the first SQL keyword) int space_index = StringFind(trimmed_sql, " "); if (space_index != -1) first_word = StringSubstr(trimmed_sql, 0, space_index); // Extract the first word from the query StringToUpper(first_word); //Convert the first word in the query to uppercase for comparison if (first_word == "SELECT") { // SELECT query – prepare and expect data m_request = DatabasePrepare(m_db_handle, sql); res.request = m_request; return res; } else { // INSERT/UPDATE/DELETE – execute directly if (!m_transaction_active) { if (!this.begin()) return res; else m_transaction_active_auto = true; //The transaction was started automatically by the execute function } ResetLastError(); if (!DatabaseExecute(m_db_handle, sql)) { printf("func=%s line=%d, Failed to execute a query to the database. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError())); } } return res; }
Аналогичным образом, функция execute в Python, все операторы, кроме SELECT, неявно открывают транзакцию с помощью функции begin, и перед сохранением изменений в базе данных эту транзакцию необходимо подтвердить.
После вызова функции execute и вставки некоторой информации в базу данных необходимо вызвать метод commit для сохранения изменений в базе данных. Мы обсудим данную функцию позже в этой публикации..
Когда функция вызывается при помощи любого оператора или запроса, кроме SELECT, она не возвращает никакого значения, поскольку, вероятно, обновляет, изменяет и вставляет информацию в базу данных. Однако, если функция вызывается с запросом типа SELECT, она возвращает структуру данных. Обсудим эту структуру подробно.
1. Метод fetchone
Модуль sqlite3, предлагаемый в языке Python, способен динамически возвращать всю полученную из SQL-оператора информацию или часть ее.
Начинается все с возможности получать одну строку информации из базы данных.
import sqlite3 conn = sqlite3.connect("example.db") cursor = conn.cursor() print(conn.execute("SELECT * FROM users").fetchone())
Несмотря на запрос всей доступной в базе данных SQLite информации из SQL-оператора, функция fetchone ограничивает функцию execute в возможности возврата более чем одной строки данных из базы данных.
База данных.

Результаты.
(mcm-env) C:\Users\Omega Joctan\OneDrive\Desktop\MCM>C:/Anaconda/envs/mcm-env/python.exe "c:/Users/Omega Joctan/OneDrive/Desktop/MCM/sqlite_test.py" (1, 'Alice', 30, 'alice@example.com')
Нам нужна аналогичная функция в MQL5-классе, внутри структуры с именем execute_res_structure, которую возвращает функция execute.
struct execute_res_structure { int request; CLabelEncoder le; vector fetchone() { int cols = DatabaseColumnsCount(request); vector row = vector::Zeros(cols); while (DatabaseRead(request)) // Essential, read the entire database { string row_string = "("; for (int i = 0; i < cols; i++) { int int_val; //Integer variable double dbl_val; //double string str_val; //string variable ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i); string col_name; if (!DatabaseColumnName(request, i, col_name)) { printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); continue; } switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable { case DATABASE_FIELD_TYPE_INTEGER: if (!DatabaseColumnInteger(request, i, int_val)) printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%d", int_val); row[i] = int_val; } break; case DATABASE_FIELD_TYPE_FLOAT: if (!DatabaseColumnDouble(request, i, dbl_val)) printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%.5f", dbl_val); row[i] = dbl_val; } break; case DATABASE_FIELD_TYPE_TEXT: if (!DatabaseColumnText(request, i, str_val)) printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += "'" + str_val + "'"; row[i] = (double)str_val; } break; default: if (MQLInfoInteger(MQL_DEBUG)) PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name); break; } // Add comma if not last element if (i < cols - 1) row_string += ", "; } row_string += ")"; if (MQLInfoInteger(MQL_DEBUG)) Print(row_string); // Print the full row once break; } DatabaseFinalize(request); return row; // Replace with actual parsed return if needed } //... Остальные функции
Из аналогичной базы данных в MetaEditor.
Ниже показано, как мы отправляем запрос, который запрашивает всю доступную информацию в таблице базы данных, а затем ограничивает объем возвращаемой информации одной строкой данных.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("example.db"); Print(sqlite3.execute("SELECT * FROM users").fetchone()); sqlite3.close(); }
Результаты.
RJ 0 13:25:04.402 sqlite3 test (XAUUSD,H1) (1, 'Alice', 30, 'zero@example.com') DQ 0 13:25:04.402 sqlite3 test (XAUUSD,H1) [1,0,30,0]
Выглядит отлично! Теперь вы можете без особых усилий извлечь из базы данных всего одну строку значений. Однако текущая функция игнорирует все двоичные значения и не обрабатывает и не кодирует строки, поэтому всем данным типа "TEXT" или строковым данным в возвращаемом векторе присваивается значение ноль.
Кроме того, в отличие от массивов Python, которые могут хранить значения разных типов данных, векторы и массивы MQL5 этого не могут; позже мы увидим, как с этим справиться.
2. Метод fetchall
В отличие от fetchone, этот метод получает всю информацию, запрошенную в SQL-операторе метода execute.
struct execute_res_structure { int request; //... Other functions matrix fetchall() { int cols = DatabaseColumnsCount(request); vector row = vector::Zeros(cols); int CHUNK_SIZE = 1000; //For optimized matrix handling matrix results_matrix = matrix::Zeros(CHUNK_SIZE, cols); int rows_found = 0; //for counting the number of rows seen in the database while (DatabaseRead(request)) // Essential, read the entire database { string row_string = "("; //for printing purposes only. Similar to how Python prints for (int i = 0; i < cols; i++) { int int_val; //Integer variable double dbl_val; //double variable string str_val; //string variable ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i); string col_name; if (!DatabaseColumnName(request, i, col_name)) { printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); continue; } switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable { case DATABASE_FIELD_TYPE_INTEGER: if (!DatabaseColumnInteger(request, i, int_val)) printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%d", int_val); //For printing purposes only row[i] = int_val; } break; case DATABASE_FIELD_TYPE_FLOAT: if (!DatabaseColumnDouble(request, i, dbl_val)) printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%.5f",dbl_val); //For printing purposes only row[i] = dbl_val; } break; case DATABASE_FIELD_TYPE_TEXT: if (!DatabaseColumnText(request, i, str_val)) printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += "'" + str_val + "'"; row[i] = (double)str_val; } break; default: if (MQLInfoInteger(MQL_DEBUG)) PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name); break; } // Add comma if not last element if (i < cols - 1) row_string += ", "; } //--- row_string += ")"; if (MQLInfoInteger(MQL_DEBUG)) Print(row_string); // Print the full row once //--- rows_found++; //Increment the rows counter if (rows_found > (int)results_matrix.Rows()) results_matrix.Resize(results_matrix.Rows()+CHUNK_SIZE, cols); //Resizing the array after 1000 rows results_matrix.Row(row, rows_found-1); //Insert a row into the matrix } results_matrix.Resize(rows_found, cols); //Resize the matrix according to the number of unknown rows found in the database | Final trim DatabaseFinalize(request); //Removes a request created in DatabasePrepare(). return results_matrix; // return the final matrix } //... Other lines of code }
На этот раз мы возвращаем матрицу вместо вектора, чтобы обеспечить возможность размещения целой таблицы, которая является двумерной.
Самая сложная часть в этой функции — обработка процесса динамического изменения размера результирующей матрицы. Таблицы в базе данных иногда могут быть огромными (содержащими более 100 000 строк), и определить размер базы данных или таблиц с точки зрения рядов бывает непросто. Таким образом, изменение размера результирующей матрицы на каждой итерации может значительно замедлить работу этой функции, особенно при многократном переборе строк, поскольку функция Resize является одной из наиболее ресурсоемких в MQL5.
Именно поэтому я решил изменять размер матрицы после каждых 1000 итераций в приведенной выше функции, чтобы уменьшить количество вызовов метода Resize.
Использование функций.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("example.db"); Print(sqlite3.execute("SELECT * FROM users").fetchall()); sqlite3.close(); }
Результаты отображаются на вкладке «Эксперты» в MetaTrader 5.
IF 0 13:30:33.649 sqlite3 test (XAUUSD,H1) (1, 'Alice', 30, 'zero@example.com') FR 0 13:30:33.649 sqlite3 test (XAUUSD,H1) (2, 'Alice', 30, 'alice@example.com') FD 0 13:30:33.649 sqlite3 test (XAUUSD,H1) (3, 'Alice', 30, 'bro@example.com') QQ 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (4, 'Alice', 30, 'ishowspeed@example.com') MO 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (5, 'Alice', 30, 'damn@example.com') MD 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (6, 'Alice', 30, 'wth@example.com') QN 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (7, 'Bruh', 30, 'stubborn@example.com') NO 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (8, 'Bruh', 30, 'whathehelly@example.com') ED 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (9, 'Bruh', 30, 'huh@example.com') PO 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (10, 'Bruh', 30, 'whatsgoingon@example.com') FS 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (11, 'Bruh', 30, 'bruh@example.com') FF 0 13:30:33.650 sqlite3 test (XAUUSD,H1) (12, 'Bruh', 30, 'how@example.com') JO 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [[1,0,30,0] RG 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [2,0,30,0] QN 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [3,0,30,0] LE 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [4,0,30,0] KL 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [5,0,30,0] NS 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [6,0,30,0] MJ 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [7,0,30,0] HQ 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [8,0,30,0] GH 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [9,0,30,0] OL 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [10,0,30,0] RE 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [11,0,30,0] JS 0 13:30:33.650 sqlite3 test (XAUUSD,H1) [12,0,30,0]]
Функция execute вернула все двенадцать строк, имеющихся в базе данных.
Повторюсь, массивы MQL5 не могут содержать неоднородные переменные в одном массиве, поэтому в итоге каждый ряд, содержащий строки из таблицы базы данных, оказывается нулевым. По крайней мере, пока.
3. Метод fetchmany
Аналогично методу fetchall, эта функция возвращает матрицу, содержащую значения из таблицы, но этот метод позволяет вам контролировать количество рядов, которые вы хотите вернуть.
struct execute_res_structure { int request; //... other lines of code matrix fetchmany(uint size) { int cols = DatabaseColumnsCount(request); vector row = vector::Zeros(cols); matrix results_matrix = matrix::Zeros(size, cols); int rows_found = 0; while (DatabaseRead(request)) // Essential, read the entire database { string row_string = "("; for (int i = 0; i < cols; i++) { int int_val; //Integer variable double dbl_val; //double variable string str_val; //string variable ENUM_DATABASE_FIELD_TYPE col_type = DatabaseColumnType(request, i); string col_name; if (!DatabaseColumnName(request, i, col_name)) { printf("func=%s line=%d, Failed to read database column name. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); continue; } switch (col_type) //Detect a column datatype and assign the value read from every row of that column to the suitable variable { case DATABASE_FIELD_TYPE_INTEGER: if (!DatabaseColumnInteger(request, i, int_val)) printf("func=%s line=%d, Failed to read Integer. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%d", int_val); row[i] = int_val; } break; case DATABASE_FIELD_TYPE_FLOAT: if (!DatabaseColumnDouble(request, i, dbl_val)) printf("func=%s line=%d, Failed to read Double. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += StringFormat("%.5f", dbl_val); row[i] = dbl_val; } break; case DATABASE_FIELD_TYPE_TEXT: if (!DatabaseColumnText(request, i, str_val)) printf("func=%s line=%d, Failed to read Text. Error = %s", __FUNCTION__, __LINE__, ErrorDescription(GetLastError())); else { row_string += "'" + str_val + "'"; row[i] = (double)str_val; } break; default: if (MQLInfoInteger(MQL_DEBUG)) PrintFormat("%s = <Unknown or Unsupported column Type by this Class>", col_name); break; } } results_matrix.Row(row, rows_found); rows_found++; if (rows_found >= (int)size) break; row_string += ")"; if (MQLInfoInteger(MQL_DEBUG)) Print(row_string); // Print the full row once } results_matrix.Resize(rows_found, cols); //Resize the matrix according to the number of unknown rows found in the database | Final trim DatabaseFinalize(request); //Removes a request created in DatabasePrepare(). return results_matrix; // return the final matrix } //... other functions }
Использование функций.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("example.db"); Print(sqlite3.execute("SELECT * FROM users").fetchmany(5)); sqlite3.close(); }
Результаты.
EG 0 13:45:25.480 sqlite3 test (XAUUSD,H1) (1'Alice'30'zero@example.com') IR 0 13:45:25.481 sqlite3 test (XAUUSD,H1) (2'Alice'30'alice@example.com') EJ 0 13:45:25.481 sqlite3 test (XAUUSD,H1) (3'Alice'30'bro@example.com') NS 0 13:45:25.481 sqlite3 test (XAUUSD,H1) (4'Alice'30'ishowspeed@example.com') CD 0 13:45:25.481 sqlite3 test (XAUUSD,H1) [[1,0,30,0] KR 0 13:45:25.481 sqlite3 test (XAUUSD,H1) [2,0,30,0] LI 0 13:45:25.481 sqlite3 test (XAUUSD,H1) [3,0,30,0] QP 0 13:45:25.481 sqlite3 test (XAUUSD,H1) [4,0,30,0] EJ 0 13:45:25.481 sqlite3 test (XAUUSD,H1) [5,0,30,0]]
Несмотря на то, что эти три функции способны контролировать объем данных, хранящихся в матрице, они по-прежнему зависят от вашего SQL-оператора; именно он определяет, какую информацию следует вернуть. Три описанные выше функции являются лишь средствами для получения данных, запрашиваемых SQL-оператором.
4. Дополнительный метод: проверка состояния функции execution
По умолчанию эта функция возвращает структуру, содержащую множество полезных функций для возврата данных, полученных после выполнения SQL-запроса типа "SELECT". Однако мы можем часто вызывать метод execute для SQL-оператора, не относящегося к типу SELECT, что заставляет эту функцию воздерживаться от возврата каких-либо данных, которые можно было бы использовать для проверки успешности или неуспешности запроса.
Для этого нам необходимо использовать переменную с именем boolean в структуре, возвращаемой функцией execute.
{
sqlite3.connect("indicators.db");
sqlite3.execute(
" CREATE TABLE IF NOT EXISTS EURUSD ("
" id INTEGER PRIMARY KEY AUTOINCREMENT,"
" example_indicator FLOAT,"
")"
);
if (!sqlite3.execute(StringFormat("INSERT INTO USDJPY (example_indicator) VALUES (%.5f)",(double)rand())).boolean) //A SQL query with a purposefully placed error
printf("The execute function failed!");
} Логическая переменная — это переменная типа bool; она становится истинной, если исполнение функции было успешным, или ложной, если ее исполнение не удалось.
Результаты.
QK 2 18:27:00.575 sqlite3 test (XAUUSD,H1) database error, near ")": syntax error DN 0 18:27:00.576 sqlite3 test (XAUUSD,H1) func=CSqlite3::execute line=402, Failed to execute a query to the database. Error = Generic database error GE 2 18:27:00.578 sqlite3 test (XAUUSD,H1) database error, no such table: USDJPY PS 0 18:27:00.578 sqlite3 test (XAUUSD,H1) func=CSqlite3::execute line=402, Failed to execute a query to the database. Error = Generic database error IS 0 18:27:00.578 sqlite3 test (XAUUSD,H1) The execute function failed!
Чтобы проверить, успешно ли прошло исполнение функции, если ей передан запрос типа "SELECT", который возвращает некоторые данные, вам необходимо оценить размер возвращаемой матрицы или вектора.
Если они пусты (rows==0 для матрицы и size==0 для вектора), это означает, что функция execute завершила исполнение с ошибкой.
Работа с текстовыми (строковыми) значениями из базы данных
Как обсуждалось в предыдущем разделе, упрощенная функция execute не способна возвращать строковые значения в матрице или векторе, и нам это известно; строки или строковые значения могут быть столь же полезны, как и другие переменные, поэтому после получения всех значений из базы данных, хранящейся в матрице, необходимо отдельно извлечь все столбцы, содержащие строковые значения.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("example.db"); Print("database matrix:\n",sqlite3.execute("SELECT * FROM users").fetchall()); string name_col[]; sqlite3.execute("SELECT name FROM users").fetch_column("name", name_col); ArrayPrint(name_col); }
При наличии такого массива строковых значений можно найти способы закодировать их в переменные, принимаемые матрицей (типы double, float и т. д.), а затем подставить обратно в матрицу.
Результаты.
LS 0 12:48:12.456 sqlite3 test (XAUUSD,H1) (1, 'Alice', 30, 'zero@example.com')
KG 0 12:48:12.456 sqlite3 test (XAUUSD,H1) (2, 'Alice', 30, 'alice@example.com')
KH 0 12:48:12.456 sqlite3 test (XAUUSD,H1) (3, 'Alice', 30, 'bro@example.com')
OM 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (4, 'Alice', 30, 'ishowspeed@example.com')
GH 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (5, 'Alice', 30, 'damn@example.com')
KH 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (6, 'Alice', 30, 'wth@example.com')
OR 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (7, 'Bruh', 30, 'stubborn@example.com')
LJ 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (8, 'Bruh', 30, 'whathehelly@example.com')
OG 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (9, 'Bruh', 30, 'huh@example.com')
RS 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (10, 'Bruh', 30, 'whatsgoingon@example.com')
PF 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (11, 'Bruh', 30, 'bruh@example.com')
DS 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (12, 'Bruh', 30, 'how@example.com')
NL 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (13, 'John', 83, 'johndoe@example.com')
IE 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (14, 'John', 83, 'johndoe2@example.com')
KP 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (15, 'John', 83, 'johndoe3@example.com')
IN 0 12:48:12.457 sqlite3 test (XAUUSD,H1) (16, 'John', 83, 'johndoe4@example.com')
KD 0 12:48:12.457 sqlite3 test (XAUUSD,H1) database matrix:
HP 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [[1,0,30,0]
PF 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [2,0,30,0]
OM 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [3,0,30,0]
ND 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [4,0,30,0]
MK 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [5,0,30,0]
LR 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [6,0,30,0]
KI 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [7,0,30,0]
JP 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [8,0,30,0]
IG 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [9,0,30,0]
QM 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [10,0,30,0]
LF 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [11,0,30,0]
KO 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [12,0,30,0]
RP 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [13,0,83,0]
MI 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [14,0,83,0]
PR 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [15,0,83,0]
DF 0 12:48:12.457 sqlite3 test (XAUUSD,H1) [16,0,83,0]]
LQ 0 12:48:12.458 sqlite3 test (XAUUSD,H1) "Alice" "Alice" "Alice" "Alice" "Alice" "Alice" "Bruh" "Bruh" "Bruh" "Bruh" "Bruh" "Bruh" "John" "John" "John" "John" Управление транзакциями базы данных
Модуль sqlite3 в Python предоставляет множество методов для управления открытием и закрытием транзакций базы данных, включая время и способ осуществления таких открытий и закрытий. Мы можем адаптировать тот же самый формат и в нашем классе, что упростит обработку транзакций при работе с базами данных SQLite.
| Функция | Описание | Примечание |
|---|---|---|
bool CSqlite3::commit(void) | Подтверждает текущую транзакцию, делая все изменения в базе данных постоянными. | Она требуется после SQL-запросов типа INSERT, UPDATE или DELETE, если параметр autocommit установлен в значение false. |
bool CSqlite3::rollback(void) | Отменяет текущую транзакцию, аннулируя все неподтвержденные изменения. | Полезно при обработке ошибок. |
bool CSqlite3::begin(void) | Начинает транзакцию, которая впоследствии будет зафиксирована в базе данных. | Перед внесением каких-либо изменений в базу данных используется более явный синтаксис. Функция execute() вызывает ее автоматически, когда параметр autocommit установлен в значение true. |
bool CSqlite3::in_transaction() | Логическая функция для проверки активности транзакции. | Функция возвращает true, если транзакция в данный момент активна. |
CSqlite3(bool autocommit=false) | При желании — хотя это и не рекомендуется, — вы можете изменить значение autocommit внутри конструктора класса позволяет включить или отключить автоматическое подтверждение транзакции внутри функции execute. |
Три функции (begin, rollback и commit) построены на основе встроенных функций MQL5 для обработки транзакций базы данных.
bool CSqlite3::commit(void) { if (!DatabaseTransactionCommit(m_db_handle)) { printf("func=%s line=%d, Failed to commit a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError())); return false; } m_transaction_active = false; //Reset the transaction after commit m_transaction_active_auto = false; return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSqlite3::begin(void) { if (m_transaction_active) { if (!m_transaction_active_auto) //print only if the user started the transaction not when it was started automatically by the execute() function printf("Can not begin, already in a transaction. Call the function rollback() to disregard it, or commit() to save the changes"); return false; } //--- if (!DatabaseTransactionBegin(m_db_handle)) { printf("func=%s line=%d, Failed to begin a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError())); return false; } m_transaction_active = true; m_transaction_active_auto = false; return m_transaction_active; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CSqlite3::rollback(void) { if (!DatabaseTransactionRollback(m_db_handle)) { printf("func=%s line=%d, Failed to rollback a transaction. Error = %s",__FUNCTION__,__LINE__,ErrorDescription(GetLastError())); return false; } m_transaction_active = false; //Reset the transaction after rollback m_transaction_active_auto = false; return true; }
Другие методы в модуле sqlite3
Это менее часто используемые функции данного модуля, но они, тем не менее, полезны для решения различных задач.
1. Метод executemany
Эта функция позволяет вставлять множество строк в таблицу базы данных за один вызов.
Предположим, у вас есть матрица, содержащая несколько строк, и каждый столбец в ней содержит значения индикаторов определенного типа, которые вы хотите одновременно вставить в базу данных, — это функция типа goto.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("indicators.db"); sqlite3.execute( " CREATE TABLE IF NOT EXISTS EURUSD (" " id INTEGER PRIMARY KEY AUTOINCREMENT," " INDICATOR01 FLOAT," " INDICATOR02 FLOAT," " INDICATOR03 FLOAT" ")" ); matrix data = {{101, 25, 001}, {102, 32, 002}, {103, 29, 003}}; sqlite3.executemany("INSERT INTO EURUSD (INDICATOR01, INDICATOR02, INDICATOR03) VALUES (?,?,?)", data); sqlite3.commit(); sqlite3.close(); }
Результаты.

Количество вопросительных знаков после ключевого слова VALUES должно быть равно количеству столбцов в матрице параметров, чтобы эта функция не выдавала ошибок.
Имейте в виду, что из-за ограничений матрицы на однородные переменные внутри одной матрицы, вы не можете с помощью этой функции одновременно добавлять в базу данных различные типы данных, например: вы не сможете вставить в базу данных одновременно строку и значение из столбца типа double.
2. Метод executescript
Эта функция удобна, когда вам нужно выполнить несколько SQL-операторов за один прием. Например, с помощью одного оператора можно создать таблицы, вставить строки и т. д. Ниже перечислены основные особенности этой функции.
void OnStart() { //--- sqlite3.connect("indicators.db"); // Use executescript to log actions sqlite3.executescript( "CREATE TABLE IF NOT EXISTS logs (" "id INTEGER PRIMARY KEY AUTOINCREMENT," "event TEXT NOT NULL" ");" "INSERT INTO logs (event) VALUES ('Users batch inserted');" ); sqlite3.close(); }
- Эта функция принимает строку из нескольких SQL-операторов, разделенных точкой с запятой ";".
- Она исполняет их все одновременно, без параметров и без разбора, как функции execute() или executemany().
- Она не возвращает результатов, а просто выполняет пакет команд.
Результаты.

Функция executescript автоматически фиксирует любые открытые транзакции, поэтому обычно нет необходимости в явном виде вызывать функцию commit, если это не требуется для управления.
3. Дополнительный метод: print_table
Для получения функционала, аналогичного атрибуту cursor.description, который предоставляется модулем sqlite3 в языке Python и возвращает структуру SQL-запроса, базы данных или таблицы.
Для получения аналогичного результата мы можем использовать встроенную функцию MQL5, названную DatabasePrint.
void CSqlite3::print_table(const string table_name_or_sql, const int flags=0) // Prints a table or an SQL request execution result in the Experts journal. { if (DatabasePrint(m_db_handle, table_name_or_sql, flags)<0) printf("func=%s line=%d, Failed to print the table or query result. Error = %s",__FUNCTION__,__LINE__, ErrorDescription(GetLastError())); }
Применение.
sqlite3.print_table("SELECT * FROM users"); Результаты.
CM 0 13:17:19.028 sqlite3 test (XAUUSD,H1) #| id name age email PJ 0 13:17:19.028 sqlite3 test (XAUUSD,H1) --+-------------------------------------- MH 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 1| 1 Alice 30 zero@example.com KS 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 2| 2 Alice 30 alice@example.com NO 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 3| 3 Alice 30 bro@example.com NI 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 4| 4 Alice 30 ishowspeed@example.com IL 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 5| 5 Alice 30 damn@example.com LO 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 6| 6 Alice 30 wth@example.com EE 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 7| 7 Bruh 30 stubborn@example.com EM 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 8| 8 Bruh 30 whathehelly@example.com ER 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 9| 9 Bruh 30 huh@example.com HK 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 10| 10 Bruh 30 whatsgoingon@example.com IQ 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 11| 11 Bruh 30 bruh@example.com LQ 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 12| 12 Bruh 30 how@example.com GG 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 13| 13 John 83 johndoe@example.com GK 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 14| 14 John 83 johndoe2@example.com NQ 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 15| 15 John 83 johndoe3@example.com QF 0 13:17:19.028 sqlite3 test (XAUUSD,H1) 16| 16 John 83 johndoe4@example.com
Регистрация сделок в базе данных
Мы рассмотрели несколько простых примеров того, как можно выполнять SQL-операторы, добавлять значения в базу данных и совершать многие другие действия. Давайте наконец сделаем что-нибудь полезное: вставим все сделки из истории MetaTrader 5 в базу данных SQLite.
Этот пример взят из этой статьи.
Без sqlite3.
//--- auxiliary variables ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source //--- go through all deals and add them to the database bool failed=false; int deals=HistoryDealsTotal(); // --- lock the database before executing transactions DatabaseTransactionBegin(database); for(int i=0; i<deals; i++) { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason); if(!DatabaseExecute(database, request_text)) { PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError()); PrintFormat("i=%d: deal #%d %s", i, deal_ticket, symbol); failed=true; break; } } //--- check for transaction execution errors if(failed) { //--- roll back all transactions and unlock the database DatabaseTransactionRollback(database); PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError()); return(false); } //--- all transactions have been performed successfully - record changes and unlock the database DatabaseTransactionCommit(database);
С использованием sqlite3.
#include <sqlite3.mqh> CSqlite3 sqlite3; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- sqlite3.connect("Trades_database.db"); //--- auxiliary variables ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source //--- go through all deals and add them to the database HistorySelect(0, TimeCurrent()); int deals=HistoryDealsTotal(); sqlite3.execute("CREATE TABLE IF NOT EXISTS DEALS (" "ID INT KEY NOT NULL," "ORDER_ID INT NOT NULL," "POSITION_ID INT NOT NULL," "TIME INT NOT NULL," "TYPE INT NOT NULL," "ENTRY INT NOT NULL," "SYMBOL CHAR(10)," "VOLUME REAL," "PRICE REAL," "PROFIT REAL," "SWAP REAL," "COMMISSION REAL," "MAGIC INT," "REASON INT );" ); //Creates a table if it doesn't exist sqlite3.begin(); //Start the transaction // --- lock the database before executing transactions for(int i=0; i<deals; i++) //loop through all deals { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason); sqlite3.execute(request_text); } sqlite3.commit(); //Commit all deals to the database at once sqlite3.close(); //close the database }
Результат.

Заключительные мысли
Воссоздание модуля sqlite3 из языка Python на языке MQL5 стало интересным и полезным опытом, который демонстрирует как гибкость, так и ограничения MQL5 по сравнению с Python. Хотя MQL5 не имеет встроенной поддержки таких расширенных функций, как инструменты авторизации (авторизаторы) или менеджеры контекста для базы данных SQLite, при тщательном абстрагировании и продуманном проектировании методов разработчики могут добиться очень похожего опыта.
Этот пользовательский класс CSqlite3 теперь позволяет разработчикам MQL5 взаимодействовать с базами данных SQLite структурированным, «питоноподобным» способом — с поддержкой запросов, транзакций, управления фиксацией/отменой и операций выборки, таких как fetchone(), fetchmany(), fetchall() и т. д.
Если вы раньше работали с Python, этот модуль покажется вам знакомым и, будем надеяться, приятным в использовании.
Всем добра.
Таблица вложений
| Имя файла | Описание и использование |
|---|---|
| Include\errordescription.mqh | Содержит описания всех кодов ошибок, генерируемых MQL5 и MetaTrader 5 |
| Include\sqlite3.mqh | Содержит класс CSqlite3 для работы с базами данных SQLite таким же образом, как в Python. |
| — скрипты; | Скрипт для тестирования класса CSqlite3. |
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18640
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
От начального до среднего уровня: Объекты (I)
Возможности Мастера MQL5, которые вам нужно знать (Часть 70): Использование паттернов SAR и RVI с сетью экспоненциального ядра
Нейросети в трейдинге: Единая архитектура взаимодействия рыночных признаков и торгового контекста (Окончание)
Знакомство с языком MQL5 (Часть 37): Освоение API и функции WebRequest в языке MQL5 (XI)
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования

