Разработка самовосстанавливающегося советника в MQL5 (Часть 1): Архитектура постоянного хранения состояния сделки
Введение
От современных советников все чаще ожидается непрерывная работа в нестабильных средах, таких как VPS-серверы, удаленные терминалы и инфраструктура реальной торговли. Однако, несмотря на растущую сложность автоматизированных торговых систем, многие советники по-прежнему почти полностью полагаются на оперативную память во время выполнения.
Это становится серьезной проблемой в момент неожиданного отключения торгового терминала. Перезагрузка VPS, сбой в работе интернета, сбой MetaTrader, отключение электроэнергии или перезагрузка терминала могут мгновенно стереть внутреннее рабочее состояние советника. Переменные, хранящиеся в памяти, исчезают, временные вычисления теряются, а контекст восстановления становится недоступным при перезапуске советника. Для простых систем это может не создавать серьезных проблем. Однако ситуация становится гораздо опаснее, когда советник внутренне отслеживает важные торговые параметры, такие как виртуальные уровни стоп-лосса и тейк-профита, состояние безубытка, прогрессия трейлинг-стопа, статус синхронизации или другая логика, критичная для восстановления состояния.
Рассмотрим простой пример. Советник открывает позицию BUY и внутренне отслеживает виртуальный стоп-лосс на определенном ценовом уровне, не отправляя стоп-лосс напрямую брокеру. Спустя несколько минут VPS, на котором размещен терминал, неожиданно перезагружается. После повторного запуска MetaTrader советник больше не помнит уровень виртуального стоп-лосса, который он отслеживал ранее. Если цена значительно изменилась во время простоя, сделка теперь может оказаться незащищенной, поскольку советник потерял свое рабочее состояние в памяти во время перезапуска.
Основная проблема заключается не столько в самой сделке, сколько в потере непрерывности между торговыми сессиями. В этой серии статей рассматривается, как можно уменьшить подобные проблемы, внедрив архитектуру с постоянным сохранением состояния сделки в советник на MQL5. Вместо того чтобы полностью полагаться на временные переменные среды выполнения, советник постоянно сохраняет важную рабочую информацию в локальной базе данных SQLite, чтобы впоследствии восстановить свое внутреннее состояние после перезапуска.
В этой первой части серии мы сосредоточимся на проектировании самой основы для хранения данных. Мы создадим слой базы данных, создадим структурированную модель состояния сделки, реализуем процедуры инициализации базы данных и разработаем функции, способные сохранять и восстанавливать рабочую информацию о сделках между торговыми сессиями.
К концу этой статьи читатели создадут слой хранения данных, который поддерживает непрерывность работы после перезагрузки терминала и предотвращает запуск советника с пустой оперативной памятью.
Понимание проблемы сохранения состояния в советниках
Большинство советников рассчитаны на непрерывное выполнение действий, сохраняя свое рабочее состояние в памяти по мере обработки рыночных данных. "Проблема сохранения состояния" возникает, когда терминал неожиданно закрывается.
Причина сбоев, связанных с перезагрузкой, кроется в различии между оперативной памятью и постоянным хранилищем. Оперативная память хранит временные данные, которые существуют только во время работы советника. Поскольку эта память является энергозависимой, она стирается в момент завершения работы MetaTrader.
При перезагрузке терминала советник запускается с "чистым" состоянием памяти. Он не может "запомнить" данные предыдущей торговой сессии, если разработчик явно не запрограммировал систему на их извлечение из постоянного хранилища. Это создает существенную архитектурную уязвимость.
Рассмотрим следующий сценарий:
- Советник открывает позицию BUY по паре XAUUSD.
- Советник внутренне отслеживает: виртуальный стоп-лосс; виртуальный тейк-профит; состояние активации безубытка и прогрессию трейлинг-стопов.
- VPS, на котором размещен MetaTrader, неожиданно перезапускается.
- MetaTrader прекращает работу. Вся временная оперативная память очищается.
- MetaTrader снова запускается.
- Советник запускается, не имея информации о предыдущем состоянии сделки.
Сама позиция у брокера может по-прежнему существовать, но память советника — нет. На этом этапе советник больше не помнит: где находился виртуальный стоп-лосс; был ли уже активирован безубыток; насколько далеко продвинулся трейлинг-стоп; или в каком рабочем состоянии он находился до выключения.
Проблема становится особенно опасной, когда советник полагается на виртуальное управление сделками вместо защиты со стороны брокера. Поскольку брокер не знает внутренних уровней стоп-лосса, ответственность за запоминание и обеспечение соблюдения этих уровней полностью лежит на самом советнике.
Если советник теряет свое рабочее состояние в памяти во время перезапуска, виртуальная защита фактически исчезает до тех пор, пока не будет восстановлено внутреннее состояние. Таким образом, проблему хранения данных можно очень просто сформулировать:
Перезапуск советника → сброс оперативной памяти → потеря рабочего состояния сделки
Эта проблема не вызвана сбоями в работе MetaTrader. Терминал работает точно так, как и ожидалось. Реальная проблема исходит из архитектурных решений, принятых внутри самого советника.
Многие традиционные советники предполагают непрерывное выполнение и поэтому хранят критически важную оперативную информацию только во временной памяти. В результате советник становится уязвимым для перезапусков, сбоев, выключений и прерываний связи, поскольку его внутреннее состояние не сохраняется после текущей торговой сессии.
Для решения этой проблемы важную оперативную информацию необходимо переместить из временной памяти во время выполнения в постоянное хранилище, устойчивое к выключениям и перезапускам терминала.
В следующем разделе мы начнем проектировать модель постоянно сохраняемого состояния сделки, которая послужит основой для восстановления данных в оставшейся части серии.
Подготовка основы советника и модели состояния восстановления
Прежде чем приступить к реализации самой архитектуры восстановления, нам нужно подготовить базовый проект советника, в котором будет разворачиваться остальная часть системы на протяжении всей серии. Читателям рекомендуется открыть MetaEditor и следовать пошаговой реализации вместе со статьей.
Создайте новый пустой исходный файл советника с именем "selfHealingExpert.mq5." После создания файла удалите сгенерированный по умолчанию код и вставьте следующий базовый шаблон кода:
//+------------------------------------------------------------------+ //| SelfHealingExpert.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ru/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ru/users/chachaian" #property version "1.00" #include <Trade\Trade.mqh> //+------------------------------------------------------------------+ //| Database configuration | //+------------------------------------------------------------------+ #define DATABASE_FILE_NAME "self_healing_trade_manager.sqlite" //+------------------------------------------------------------------+ //| Enumeration: Defines the direction of the test trade. | //+------------------------------------------------------------------+ enum ENUM_TEST_TRADE_DIRECTION { TEST_TRADE_BUY, // Open a BUY position TEST_TRADE_SELL // Open a SELL position }; //+------------------------------------------------------------------+ //| Enumeration: Defines the current operational state of the EA. | //+------------------------------------------------------------------+ enum ENUM_EA_STATE { EA_STATE_STARTING, EA_STATE_RECOVERING, EA_STATE_RUNNING, EA_STATE_SAFE_MODE, EA_STATE_ERROR }; //+------------------------------------------------------------------+ //| User Input Parameters | //+------------------------------------------------------------------+ input long InpMagicNumber = 20260509; input int InpTimerSeconds = 5; input double InpVirtualSLPoints = 500; input double InpVirtualTPPoints = 1000; input double InpLots = 0.01; input bool InpAllowTestTrade = true; input bool InpOpenTestTradeOnStartup = true; input ENUM_TEST_TRADE_DIRECTION InpTestTradeDirection = TEST_TRADE_BUY; input bool InpUseBreakeven = true; input double InpBreakevenTriggerPoints = 300; input double InpBreakevenLockPoints = 20; input bool InpUseTrailingStop = true; input double InpTrailStartPoints = 500; input double InpTrailStepPoints = 100; input double InpTrailDistancePoints = 300; //+------------------------------------------------------------------+ //| Structure: Stores the persistent operational state and risk | //| parameters of an active trade for recovery after a system crash. | //+------------------------------------------------------------------+ struct STradeState { ulong ticket; string symbol; long magic; int direction; double volume; double entryPrice; double virtualSL; double virtualTP; double lastTrailPrice; bool breakevenActivated; bool trailingActivated; datetime openTime; datetime lastHeartbeat; datetime lastUpdateTime; }; //+------------------------------------------------------------------+ //| Global variables: Maintain the EA's runtime state, active trade | //| information, and SQLite database connection during execution. | //+------------------------------------------------------------------+ ENUM_EA_STATE g_eaState = EA_STATE_STARTING; // Current operational state of the Expert Advisor. STradeState g_tradeState; // Active trade's virtual management and recovery state. bool g_hasTradeState = false; // Indicates whether a valid trade state is currently loaded. int g_database = INVALID_HANDLE; // Active SQLite database connection handle. CTrade g_trade; // Trading object used to send test orders. //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(InpTimerSeconds); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+
Этот шаблон задает исходную архитектуру проекта до введения логики хранения состояния. Раздел #property определяет метаданные советника, отображаемые MetaTrader.
//+------------------------------------------------------------------+ //| SelfHealingExpert.mq5 | //| Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian | //| https://www.mql5.com/ru/users/chachaian | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd. Developer is Chacha Ian" #property link "https://www.mql5.com/ru/users/chachaian" #property version "1.00"
Следующая инструкция #include:
#include <Trade\Trade.mqh> импортирует стандартную торговую библиотеку, используемую позже в серии для открытия и закрытия демонстрационных сделок. Далее проект определяет конфигурацию имени файла базы данных:
//+------------------------------------------------------------------+ //| Database configuration | //+------------------------------------------------------------------+ #define DATABASE_FILE_NAME "self_healing_trade_manager.sqlite"
Эта константа централизует имя файла базы данных SQLite, используемое во всей системе восстановления.
Перечисление ENUM_TEST_TRADE_DIRECTION определяет направление демонстрационных сделок, используемых позже во время тестирования восстановления.
//+------------------------------------------------------------------+ //| Enumeration: Defines the direction of the test trade. | //+------------------------------------------------------------------+ enum ENUM_TEST_TRADE_DIRECTION { TEST_TRADE_BUY, // Open a BUY position TEST_TRADE_SELL // Open a SELL position };
Перечисление ENUM_EA_STATE определяет рабочее состояние советника. Это важно, поскольку советник впоследствии переходит между состоянием запуска, восстановления, нормального выполнения, безопасного режима и критическими сбоями.
//+------------------------------------------------------------------+ //| Enumeration: Defines the current operational state of the EA. | //+------------------------------------------------------------------+ enum ENUM_EA_STATE { EA_STATE_STARTING, EA_STATE_RECOVERING, EA_STATE_RUNNING, EA_STATE_SAFE_MODE, EA_STATE_ERROR };
Раздел входных параметров определяет настраиваемые параметры, используемые во всей системе восстановления. Хотя некоторые из этих параметров будут использоваться только в последующих частях серии, их определение на раннем этапе помогает установить общую архитектуру советника.
//+------------------------------------------------------------------+ //| User Input Parameters | //+------------------------------------------------------------------+ input long InpMagicNumber = 20260509; input int InpTimerSeconds = 5; input double InpVirtualSLPoints = 500; input double InpVirtualTPPoints = 1000; input double InpLots = 0.01; input bool InpAllowTestTrade = true; input bool InpOpenTestTradeOnStartup = true; input ENUM_TEST_TRADE_DIRECTION InpTestTradeDirection = TEST_TRADE_BUY; input bool InpUseBreakeven = true; input double InpBreakevenTriggerPoints = 300; input double InpBreakevenLockPoints = 20; input bool InpUseTrailingStop = true; input double InpTrailStartPoints = 500; input double InpTrailStepPoints = 100; input double InpTrailDistancePoints = 300;
Далее мы определяем структуру STradeState.
//+------------------------------------------------------------------+ //| Structure: Stores the persistent operational state and risk | //| parameters of an active trade for recovery after a system crash. | //+------------------------------------------------------------------+ struct STradeState { ulong ticket; string symbol; long magic; int direction; double volume; double entryPrice; double virtualSL; double virtualTP; double lastTrailPrice; bool breakevenActivated; bool trailingActivated; datetime openTime; datetime lastHeartbeat; datetime lastUpdateTime; };
Эта структура действует как центральная модель оперативной памяти системы восстановления. Она хранит всю критически важную информацию, необходимую для восстановления состояния управляемой сделки после перезапуска или завершения работы.
Непосредственно под структурой мы определяем глобальные переменные среды выполнения, используемые во всем проекте.
//+------------------------------------------------------------------+ //| Global variables: Maintain the EA's runtime state, active trade | //| information, and SQLite database connection during execution. | //+------------------------------------------------------------------+ ENUM_EA_STATE g_eaState = EA_STATE_STARTING; // Current operational state of the Expert Advisor. STradeState g_tradeState; // Active trade's virtual management and recovery state. bool g_hasTradeState = false; // Indicates whether a valid trade state is currently loaded. int g_database = INVALID_HANDLE; // Active SQLite database connection handle. CTrade g_trade; // Trading object used to send test orders.
Первая переменная:
ENUM_EA_STATE g_eaState = EA_STATE_STARTING;
сохраняет текущее рабочее состояние советника. Эта переменная позволяет советнику понимать, в какой фазе выполнения он находится в данный момент. Например, при запуске советник переходит в состояние EA_STATE_STARTING; при восстановлении — в состояние EA_STATE_RECOVERING; при нормальной работе — в состояние EA_STATE_RUNNING; а при возникновении опасных проблем синхронизации — может войти в режим EA_STATE_SAFE_MODE.
В дальнейшем различные части советника будут использовать эту переменную для определения того, разрешено ли безопасное выполнение определенных операций. Следующая переменная:
STradeState g_tradeState;
хранит активное состояние восстановления управляемой сделки во время выполнения. Эта структура становится центральной рабочей памятью советника во время его работы. Она содержит: тикет позиции; виртуальный стоп-лосс, виртуальный тейк-профит; информацию о трейлинге; временные метки heartbeat; и всю другую информацию о сделке, чувствительную к восстановлению.
Всякий раз, когда советник загружает сохраненные данные из SQLite, информация восстанавливается в g_tradeState. Впоследствии советник постоянно обновляет эту структуру во время выполнения, прежде чем снова сохранить ее в базу данных.
Следующий флаг:
bool g_hasTradeState = false;
сообщает советнику, загружено ли в память в данный момент допустимое состояние сделки. Это важно, потому что советник никогда не должен пытаться управлять сделкой, если надлежащее рабочее состояние еще не установлено.
Например, прежде чем проверять: виртуальный стоп-лосс; виртуальный тейк-профит; логику безубытока; или логику трейлинга, советник сначала проверяет следующее:
g_hasTradeState == true Это предотвращает попытки системы восстановления управлять пустыми или неинициализированными данными сделки.
Далее мы определяем:
int g_database = INVALID_HANDLE;
Эта переменная хранит активный хэндл подключения к базе данных SQLite. Позже, когда советник успешно откроет базу данных SQLite с помощью DatabaseOpen, MetaTrader вернет хэндл базы данных, который хранится в g_database.
Все последующие операции с базой данных в проекте используют этот хэндл, включая: сохранение состояния сделки; загрузку состояния сделки; обновление heartbeat-меток; и пометку сделок как закрытых. Если база данных не открывается успешно, g_database остается равным INVALID_HANDLE, что позволяет советнику безопасно обнаруживать ошибки инициализации базы данных.
Наконец, мы определяем:
CTrade g_trade;
Это объект исполнения сделки, предоставляемый стандартной торговой библиотекой MetaTrader. Позже в этой серии этот объект будет использоваться для: открытия демонстрационных сделок; закрытия позиций; и управления операциями, связанными с восстановлением.
Вместо того чтобы вручную создавать торговые запросы с нуля, класс CTrade предоставляет более чистый и безопасный интерфейс для исполнения сделок брокером.
На данном этапе советник содержит модель рабочего состояния, структуру постоянного хранения состояния сделки, глобальные переменные среды выполнения и основные обработчики событий, необходимые для архитектуры восстановления.
Наконец, шаблон определяет четыре основных обработчика событий, используемых жизненным циклом советника. Функция OnInit выполняется при запуске советника.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(InpTimerSeconds); return(INIT_SUCCEEDED); }
Механизм таймеров инициализируется с помощью:
EventSetTimer(InpTimerSeconds); Это указывает MetaTrader начать генерацию событий таймера с фиксированными интервалами, определяемыми входным параметром InpTimerSeconds. Например, если:
InpTimerSeconds == 5 Затем MetaTrader автоматически вызывает функцию OnTimer каждые пять секунд, пока советник остается активным. Хотя таймер еще не выполняет операции восстановления в этом разделе, он уже создает инфраструктуру тайминга, на которую будут опираться последующие части системы восстановления.
В следующих разделах механизм таймеров поможет советнику выполнять периодические фоновые задачи, такие как обновления меток heartbeat, проверки синхронизации, мониторинг восстановления и обслуживание состояния базы данных. Это важно, потому что часть процедур восстановления должна работать даже при низкой рыночной активности, когда тики приходят редко или временно перестают поступать.
Например, если рынок становится неактивным в течение ночи, событие OnTick может перестать часто выполняться. Однако системе восстановления по-прежнему необходим надежный механизм мониторинга непрерывности работы независимо от поступающих ценовых тиков. По этой причине механизм таймеров инициализируется на раннем этапе проекта.
Функция OnDeinit выполняется при завершении работы советника.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); }
Внутри мы вызываем:
EventKillTimer(); для безопасной остановки системы таймеров, которая была запущена ранее внутри OnInit. Это гарантирует, что MetaTrader больше не будет генерировать события таймера после выгрузки советника. Впоследствии эта функция безопасно закроет соединение с базой данных и зафиксирует окончательную информацию о состоянии во время выполнения перед завершением работы.
Функция OnTick выполняется всякий раз, когда поступает новый тик.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- }
Будущая логика управления виртуальными сделками будет работать на основе этого обработчика событий.
Функция OnTimer выполняется с фиксированными интервалами, определяемыми количеством секунд, установленным с помощью EventSetTimer во время инициализации советника.
//+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- }
Этот таймер становится важным, поскольку некоторые задачи восстановления должны продолжать выполняться независимо от тиков. В дальнейшем в этой серии статей таймер поможет выполнять проверки синхронизации, обновления сигналов активности и мониторинг восстановления даже в периоды низкой активности рынка.
На данном этапе фундамент проекта полностью готов. Советник должен успешно компилироваться, содержать основные определения архитектуры восстановления и быть готовым к созданию слоя хранения данных SQLite, который мы разработаем в следующем разделе.
Настройка постоянного хранения данных SQLite в MQL5
На этом этапе советник уже имеет базовую структуру среды выполнения, необходимую для хранения информации о восстановлении во время работы программы. Однако одной оперативной памяти недостаточно. Если терминал выключается, значения, хранящиеся в памяти, исчезают. Для решения этой проблемы нам необходимо создать постоянный слой хранения данных для проекта. Этот слой будет хранить важную информацию о состоянии сделок советника в базе данных SQLite, чтобы информация оставалась доступной после перезапуска MetaTrader.
Для этого проекта имя файла базы данных определяется следующим образом:
//+------------------------------------------------------------------+ //| Database configuration | //+------------------------------------------------------------------+ #define DATABASE_FILE_NAME "self_healing_trade_manager.sqlite"
Эта константа хранит имя файла базы данных в одном месте. Позже, при открытии базы данных, мы будем использовать DATABASE_FILE_NAME вместо того, чтобы вводить имя файла непосредственно в функции базы данных. Это упрощает сопровождение кода, поскольку изменение имени базы данных требует редактирования всего одной строки.
Поскольку мы не используем общий каталог файлов, база данных будет создана в локальном каталоге файлов текущего терминала:
MQL5/Files/self_healing_trade_manager.sqlite
Так база данных остается локальной для текущего терминала MetaTrader и при этом переживает его перезапуски. Далее мы создаем функцию, отвечающую за открытие базы данных SQLite.
//+------------------------------------------------------------------+ //| Opens the SQLite database used for persistent trade state. | //+------------------------------------------------------------------+ bool OpenDatabase() { //--- open or create the SQLite database file g_database = DatabaseOpen(DATABASE_FILE_NAME, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); //--- validate database handle if(g_database == INVALID_HANDLE) { //--- print database initialization error PrintFormat("Failed to open database. Error: %d", GetLastError()); return(false); } //--- database opened successfully return(true); }
Функция OpenDatabase устанавливает связь между советником и файлом базы данных SQLite. Результат DatabaseOpen хранится g_database. Это значение становится активным хэндлом базы данных, который будет использоваться всеми последующими операциями с базой данных.
Флаги, используемые в функции DatabaseOpen, имеют важное значение. DATABASE_OPEN_READWRITE позволяет советнику читать и записывать данные в базу данных. DATABASE_OPEN_CREATE позволяет MetaTrader автоматически создать файл базы данных, если он еще не существует.
Если база данных открывается успешно, g_database получает действительный хэндл, и функция возвращает значение true. Если база данных не открывается, g_database остается недействительным, выводится ошибка и функция возвращает значение false. После открытия базы данных следующим шагом является проверка существования необходимой таблицы.
//+------------------------------------------------------------------+ //| Ensures that all required SQLite tables exist. | //+------------------------------------------------------------------+ bool EnsureDatabaseTables() { //--- validate database connection if(g_database == INVALID_HANDLE) { //--- database is not available Print("Cannot ensure database tables because the database is not open."); return(false); } //--- SQL query used to create the trade state table string query = "CREATE TABLE IF NOT EXISTS trade_states (" "ticket INTEGER PRIMARY KEY," "symbol TEXT," "magic INTEGER," "direction INTEGER," "volume REAL," "entry_price REAL," "virtual_sl REAL," "virtual_tp REAL," "last_trail_price REAL," "breakeven_activated INTEGER," "trailing_activated INTEGER," "open_time INTEGER," "last_heartbeat INTEGER," "last_update_time INTEGER," "state TEXT" ");"; //--- execute table creation query if(!DatabaseExecute(g_database, query)) { //--- print SQL execution error PrintFormat("Failed to ensure database tables. Error: %d", GetLastError()); return(false); } //--- required database tables verified successfully return(true); }
Функция EnsureDatabaseTables подготавливает структуру базы данных, необходимую советнику. Перед выполнением SQL-запроса функция сначала проверяет, действителен ли хэндл базы данных:
if(g_database == INVALID_HANDLE)
Это предотвращает попытки советника создать таблицу до открытия базы данных.
Оператор SQL использует:
CREATE TABLE IF NOT EXISTS
Это чрезвычайно важно, поскольку советник может быть подключен к графику много раз. Если таблица уже существует, SQLite оставляет её без изменений. Если таблица не существует, SQLite создаёт её.
В нашем случае мы определяем только одну таблицу базы данных под названием:
trade_states
Эта таблица будет хранить восстанавливаемое состояние сделки. Ее столбцы соответствуют важным полям, уже определенным в STradeState, таким как тикет, символ, магическое число, направление позиции, цена входа, виртуальный стоп-лосс, виртуальный тейк-профит, информация о трейлинге, время меток heartbeat, время обновления и состояние. Это создает структуру постоянного хранения, которая впоследствии позволит советнику сохранять и восстанавливать информацию о состоянии сделки.
Наконец, нам нужна функция для безопасного закрытия базы данных.
//+------------------------------------------------------------------+ //| Closes the active SQLite database connection. | //+------------------------------------------------------------------+ void CloseDatabase() { //--- validate database handle if(g_database == INVALID_HANDLE) return; //--- close SQLite database connection DatabaseClose(g_database); //--- reset database handle g_database = INVALID_HANDLE; }
Функция CloseDatabase отвечает за освобождение активного соединения SQLite, когда советник удаляется с графика или когда терминал закрывается. Функция сначала проверяет, не является ли хэндл базы данных уже недействительным. Если активного соединения с базой данных нет, она немедленно завершает работу. Если база данных открыта, функция вызывает:
DatabaseClose(g_database); После закрытия базы данных хэндл возвращается к значению INVALID_HANDLE. Это предотвращает случайное использование советником старого хэндла базы данных после закрытия соединения. На этом этапе советник выполняет три важные функции жизненного цикла базы данных:
OpenDatabase() // opens or creates the SQLite database file EnsureDatabaseTables() // creates the trade_states table if missing CloseDatabase() // closes the SQLite database connection safely
Однако эти функции не выполняются автоматически. На следующем шаге мы должны связать их с жизненным циклом советника, вызывая их из OnInit и OnDeinit.
Подключение SQLite к жизненному циклу советника
База данных должна быть открыта при запуске советника, поскольку все будущие операции сохранения и восстановления зависят от действительного соединения с SQLite. Необходимая таблица также должна быть проверена сразу после открытия базы данных. Если таблица не существует, советник создаёт её. Если она уже существует, советник продолжает работу без изменения существующих данных.
Обновим функцию OnInit следующим образом:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create a connection to the SQLite database if(!OpenDatabase()) { g_eaState = EA_STATE_ERROR; return(INIT_FAILED); } //--- Create the database table or make sure that it exists if(!EnsureDatabaseTables()) { CloseDatabase(); g_eaState = EA_STATE_ERROR; return(INIT_FAILED); } //--- create timer EventSetTimer(InpTimerSeconds); //--- Sets the EA to the RUNNING state g_eaState = EA_STATE_RUNNING; return(INIT_SUCCEEDED); }
Теперь последовательность запуска понятна. Сначала советник пытается открыть базу данных SQLite. Если это не удается, инициализация останавливается, поскольку слой хранения данных недоступен. Далее советник проверяет наличие таблицы trade_states. Если этот шаг не удается, соединение с базой данных закрывается до остановки инициализации.
Только после того, как база данных будет успешно открыта и проверена, советник запускает таймер. Наконец, советник переходит в состояние EA_STATE_RUNNING, что означает, что основа готова к следующим операциям восстановления, которые будут добавлены позже.
Базе данных также необходим корректный путь завершения работы. При удалении советника с графика, перекомпиляции или закрытии терминала активное соединение с SQLite должно быть закрыто до завершения работы программы. Обновим функцию OnDeinit следующим образом:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Close the database connection CloseDatabase(); //--- destroy timer EventKillTimer(); }
Это гарантирует безопасное освобождение хэндла базы данных при остановке работы советника. На этом этапе слой постоянного хранения состояния — уже не просто набор неиспользуемых функций. На этом завершается первая рабочая версия слоя хранения данных SQLite.
Тестирование слоя хранения данных
На этом этапе советник должен успешно компилироваться и инициализироваться без ошибок. Прежде чем перейти к следующему разделу, следует убедиться, что слой хранения данных SQLite действительно работает корректно.
Нажмите кнопку Compile в MetaEditor и подтвердите успешную компиляцию проекта. Если все было реализовано корректно, в журнале компиляции должно отображаться:
0 error(s), 0 warning(s)
Далее вернитесь в MetaTrader 5 и найдите советник на панели Навигатор. Советник должен появиться под:
Navigator → Expert Advisors
Прикрепите советник к любому графику и разрешите автоматическую торговлю, когда появится соответствующий запрос. На данный момент советник по-прежнему не открывает сделки и не выполняет операции по восстановлению. Однако в процессе инициализации теперь автоматически происходит следующая последовательность действий:
1. Открывается база данных SQLite.
2. Проверяется наличие таблицы trade_states.
3. Запускается таймер.
4. Переход в состояние RUNNING.
Если инициализация базы данных проходит успешно, MetaTrader автоматически создает файл базы данных SQLite. Чтобы убедиться в этом, откройте каталог данных MetaTrader. Внутри MetaTrader перейдите по следующему пути:
File → Open Data Folder
Откроется окно проводника Windows. Оттуда откройте следующие папки:
MQL5 → Files
В папке Files теперь должен находиться файл базы данных SQLite:
self_healing_trade_manager.sqlite

Наличие этого файла подтверждает, что база данных была успешно открыта, слой хранения данных корректно инициализирован, и советник теперь способен постоянно хранить информацию для восстановления между сеансами терминала.
На данном этапе база данных все еще в основном пуста, поскольку советник еще не начал сохранять информацию о состоянии сделок. Однако сама инфраструктура хранения данных теперь полностью работоспособна и готова к логике восстановления, которую мы реализуем позже.
Сохранение восстанавливаемого состояния сделки в SQLite
На этом этапе советник уже может: открыть базу данных SQLite; создать таблицу trade_states; и поддерживать слой постоянного хранения состояния. Однако система восстановления по-прежнему не может обеспечить непрерывность работы, пока советник не станет способен сохранять информацию о состоянии сделки во время выполнения в SQLite. Цель постоянного хранения состояния заключается не просто в создании файла базы данных. Цель состоит в непрерывном сохранении внутреннего рабочего состояния советника, чтобы информацию можно было восстановить после перезапуска.
Для этого мы создадим функцию, отвечающую за сохранение текущего состояния сделок во время выполнения в базу данных SQLite. Добавим следующую функцию ниже CloseDatabase:
//+------------------------------------------------------------------+ //| Saves or updates the active trade state in the SQLite database. | //+------------------------------------------------------------------+ bool SaveTradeState(const STradeState &state) { //--- validate database connection if(g_database == INVALID_HANDLE) { //--- database connection unavailable Print("Cannot save trade state because the database is not open."); return(false); } //--- prepare SQL query used to save the trade state string query = StringFormat( "INSERT OR REPLACE INTO trade_states " "(ticket,symbol,magic,direction,volume,entry_price,virtual_sl,virtual_tp," "last_trail_price,breakeven_activated,trailing_activated,open_time," "last_heartbeat,last_update_time,state) " "VALUES (%I64u,'%s',%I64d,%d,%.8f,%.8f,%.8f,%.8f,%.8f,%d,%d,%I64d,%I64d,%I64d,'ACTIVE');", state.ticket, state.symbol, state.magic, state.direction, state.volume, state.entryPrice, state.virtualSL, state.virtualTP, state.lastTrailPrice, state.breakevenActivated ? 1 : 0, state.trailingActivated ? 1 : 0, (long)state.openTime, (long)state.lastHeartbeat, (long)state.lastUpdateTime ); //--- execute SQL save query if(!DatabaseExecute(g_database, query)) { //--- print SQL execution error PrintFormat("Failed to save trade state. Error: %d", GetLastError()); return(false); } //--- trade state saved successfully return(true); }
Функция SaveTradeState отвечает за передачу текущего состояния восстановления во время выполнения из памяти в постоянное хранилище SQLite. Функция принимает:
const STradeState &state которая представляет собой структуру активного состояния сделок во время выполнения, загруженную в память. Перед попыткой сохранения данных функция сначала проверяет соединение с базой данных:
if(g_database == INVALID_HANDLE)
Это предотвращает попытки советника выполнить обращение к базе данных до успешной инициализации SQLite. Затем функция подготавливает SQL-запрос, отвечающий за хранение информации о состоянии сделки. Оператор SQL использует:
INSERT OR REPLACE
Это важно, потому что одно и то же состояние сделки может обновляться много раз во время выполнения. Если записи с таким тикетом еще нет в таблице, SQLite создает новую строку. Если операция уже существует, SQLite заменяет предыдущую строку, используя обновленную информацию о восстановлении. Это позволяет советнику непрерывно поддерживать актуальное рабочее состояние сделки в постоянном хранилище, вместо создания дубликатов записей для одной и той же позиции.
Запрос хранит всю основную информацию, чувствительную для восстановления, включая: тикет сделки; символ; магическое число; направление; объем; цену входа; виртуальный стоп-лосс; виртуальный тейк-профит; ход трейлинг-стопа; состояние безубытка; временные метки heartbeat; и статус рабочего состояния.
Обратите внимание, что в последнем столбце хранится:
ACTIVE
Это указывает на то, что сохраненная запись о состоянии сделки по-прежнему представляет собой активно управляемую позицию. В дальнейшем в серии закрытые сделки будут помечаться по-другому, чтобы система восстановления случайно не восстановила неактивные позиции при перезапуске. Наконец, функция выполняет SQL-запрос с помощью DatabaseExecute. Если запрос выполняется успешно, функция возвращает true. В противном случае выводится ошибка SQLite и функция возвращает значение false.
На этом этапе советник может сохранять рабочее состояние во время выполнения в хранилище SQLite. Это становится основой, на которой система восстановления будет впоследствии восстанавливать информацию о состоянии сделки после перезапуска.
Загрузка восстанавливаемого состояния сделки из SQLite
На этом этапе советник уже может: создать базу данных SQLite; инициализировать таблицу trade_states; и поддерживать слой постоянного хранения состояния. Однако, одного хранения данных недостаточно. Самовосстанавливающийся советник также должен быть способен восстанавливать ранее сохраненную информацию обратно в оперативную память после перезапуска. Это основная идея восстановления.
Когда терминал выключается, все переменные среды выполнения исчезают из памяти. После перезапуска советнику необходимо восстановить свое рабочее состояние, используя информацию, ранее хранившуюся в SQLite. Для этого мы создадим две важные функции восстановления:
- TradeStateExists
- LoadTradeState
Функция TradeStateExists проверяет, существует ли уже сохраненная запись о состоянии сделки в базе данных для конкретного тикета. Функция LoadTradeState восстанавливает сохраненную информацию SQLite обратно в структуру среды выполнения STradeState. Мы начинаем с TradeStateExists. Добавим следующую функцию ниже SaveTradeState:
//+------------------------------------------------------------------+ //| Checks whether a trade state record exists for a ticket. | //+------------------------------------------------------------------+ bool TradeStateExists(const ulong ticket) { //--- validate database connection if(g_database == INVALID_HANDLE) return(false); //--- prepare SQL query used to search for the trade ticket string query = StringFormat("SELECT ticket FROM trade_states WHERE ticket=%I64u;", ticket); //--- prepare SQLite query request int request = DatabasePrepare(g_database, query); //--- validate SQLite request handle if(request == INVALID_HANDLE) { //--- print SQLite preparation error PrintFormat("Failed to prepare trade state existence query. Error: %d", GetLastError()); return(false); } //--- read SQLite query result bool exists = DatabaseRead(request); //--- release SQLite request resources DatabaseFinalize(request); //--- return query result return(exists); }
Эта функция выполняет быструю проверку наличия записи в базе данных, используя тикет сделки.
SQL-запрос:
SELECT ticket FROM trade_states WHERE ticket=...
Этот запрос ищет в базе данных существующую запись о состоянии сделки с указанным номером тикета. Запрос подготавливается с помощью DatabasePrepare, который создает хэндл запроса SQLite, используемый во время извлечения данных. Далее функция вызывает DatabaseRead. Если SQLite успешно считывает соответствующую строку, функция возвращает значение true. В противном случае — возвращает false.
Наконец, хэндл запроса освобождается с помощью DatabaseFinalize. Это важно, поскольку хэндлы запросов SQLite всегда должны освобождаться после использования, чтобы избежать ненужного накопления ресурсов во время выполнения. Далее мы создаем функцию, отвечающую за восстановление рабочего состояния во время выполнения из SQLite.
Добавим следующую функцию непосредственно под TradeStateExists:
//+------------------------------------------------------------------+ //| Loads a saved trade state from the SQLite database by ticket. | //+------------------------------------------------------------------+ bool LoadTradeState(const ulong ticket, STradeState &state) { //--- validate database connection if(g_database == INVALID_HANDLE) { //--- database connection unavailable Print("Cannot load trade state because the database is not open."); return(false); } //--- prepare SQL query used to load the saved trade state string query = StringFormat( "SELECT ticket, symbol, magic, direction, volume,entry_price,virtual_sl,virtual_tp," "last_trail_price,breakeven_activated,trailing_activated,open_time," "last_heartbeat,last_update_time " "FROM trade_states WHERE ticket=%I64u AND state='ACTIVE';", ticket ); //--- prepare SQLite query request int request = DatabasePrepare(g_database, query); //--- validate SQLite request handle if(request == INVALID_HANDLE) { //--- print SQLite preparation error PrintFormat("Failed to prepare load trade state query. Error: %d", GetLastError()); return(false); } //--- validate query result if(!DatabaseRead(request)) { //--- release SQLite request resources DatabaseFinalize(request); return(false); } //--- temporary database values long ticketValue; long magicValue; long openTimeValue; long heartbeatValue; long updateTimeValue; int breakevenValue; int trailingValue; //--- read database column values DatabaseColumnLong(request, 0, ticketValue); DatabaseColumnText(request, 1, state.symbol); DatabaseColumnLong(request, 2, magicValue); DatabaseColumnInteger(request, 3, state.direction); DatabaseColumnDouble(request, 4, state.volume); DatabaseColumnDouble(request, 5, state.entryPrice); DatabaseColumnDouble(request, 6, state.virtualSL); DatabaseColumnDouble(request, 7, state.virtualTP); DatabaseColumnDouble(request, 8, state.lastTrailPrice); DatabaseColumnInteger(request, 9, breakevenValue); DatabaseColumnInteger(request, 10, trailingValue); DatabaseColumnLong(request, 11, openTimeValue); DatabaseColumnLong(request, 12, heartbeatValue); DatabaseColumnLong(request, 13, updateTimeValue); //--- rebuild runtime trade state state.ticket = (ulong)ticketValue; state.magic = magicValue; state.breakevenActivated = (breakevenValue > 0); state.trailingActivated = (trailingValue > 0); state.openTime = (datetime)openTimeValue; state.lastHeartbeat = (datetime)heartbeatValue; state.lastUpdateTime = (datetime)updateTimeValue; //--- release SQLite request resources DatabaseFinalize(request); //--- trade state restored successfully return(true); }
Эта функция восстанавливает состояние среды выполнения. Сначала она извлекает сохраненную строку о состоянии сделки из SQLite по тикету.
Только записи, отмеченные как:
state='ACTIVE'
загружаются. Это не позволяет советнику восстановить сделки, которые уже были отмечены как закрытые. После подготовки запроса и проверки результата функция начинает чтение столбцов базы данных по отдельности, используя такие функции, как DatabaseColumnLong, DatabaseColumnDouble, DatabaseColumnInteger и DatabaseColumnText.
Далее каждое значение из базы данных копируется обратно в структуру STradeState среды выполнения. Именно в этот момент постоянное хранилище становится оперативной памятью восстановления. Теперь советник способен восстанавливать ранее сохраненную информацию о сделках в состояние среды выполнения после перезапуска. На этом этапе слой хранения данных может: сохранять состояние среды выполнения в SQLite; проверять наличие сохраненного состояния; и восстанавливать сохраненную информацию о состоянии сделки обратно в память.
На этом завершается создание базовой архитектуры хранения данных и восстановления, необходимой перед тем, как мы начнем реализовывать логику восстановления сделок в реальном времени в следующих разделах.
Заключение
В этой части серии мы приступили к решению одной из важнейших архитектурных проблем традиционных советников: потери оперативной памяти после перезапуска или выключения терминала. Для решения этой проблемы мы создали основу для самовосстанавливающейся системы с использованием SQLite внутри MetaTrader 5. В ходе статьи мы успешно реализовали следующие компоненты:
- Чистая основа проекта советника.
- Управление рабочим состоянием с помощью ENUM_EA_STATE.
- Централизованная структура восстановления STradeState.
- Глобальное управление состоянием среды выполнения.
- Интеграция базы данных SQLite в MetaTrader 5.
- Автоматическое создание базы данных в каталоге MQL5/Files.
- Управление жизненным циклом базы данных.
- Постоянное хранение состояния сделок с помощью SaveTradeState.
- Проверка существования состояния сделок с помощью TradeStateExists.
- Восстановление рабочего состояния с помощью LoadTradeState.
На данном этапе советник может: автоматически создавать свою базу данных SQLite; поддерживать слой постоянного хранения состояния; сохранять восстанавливаемую информацию о состоянии сделки; и восстанавливать ранее сохраненное состояние среды выполнения обратно в память. Эта архитектура постоянного хранения данных теперь служит основой, на которой будет строиться остальная часть системы восстановления в течение всей этой серии из пяти частей.
Прежде чем продолжить, сравните свою реализацию с прилагаемым исходным файлом.
SelfHealingExpert.mq5
Убедитесь, что ваш проект: успешно компилируется; корректно создает базу данных SQLite; воспроизводит описанное в статье поведение слоя хранения состояния. В следующей части серии мы начнем создавать слой восстановления системы в реальном времени. Это включает в себя: обнаружение управляемых позиций; управление виртуальными стоп-лоссами и тейк-профитами; процедуры восстановления при запуске; и немедленную проверку защиты после перезапуска.
К концу следующей статьи советник сможет восстанавливать и продолжать управление активными сделками после перезапуска, используя слой сохранения данных, разработанный в этой части.
Репозиторий проекта
Проект поддерживается в Algo Forge на MQL5 и будет развиваться на протяжении всей серии.
Репозиторий:
Самовосстанавливающийся советник
Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22532
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Особенности написания Пользовательских Индикаторов
Алгоритм бактериальной эволюции Нумаоки для быстрой адаптации (NBE)
Автоматизация торговых стратегий в MQL5 (Часть 32): Создание системы распознавания гармонического паттерна "5 Drives" с использованием Price Action
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования