Алготрейдинг без рутины: быстрый анализ сделок в MetaTrader 5 с SQLite
Проблема обратной связи в алгоритмической торговле
Алгоритмическая торговля требует не только разработки конкурентных стратегий, но и постоянного контроля результатов. Без обратной связи торговая система может стать неуправляемой. Пока вы не видите, как ведёт себя стратегия в реальном времени, вы управляете не системой, а набором догадок.
На современных биржевых рынках и на FOREX частота сделок выросла многократно. Данные устаревают быстрее, чем вы успеваете сделать выводы. А вместе с ними устаревает и актуальность ваших выводов. Вдобавок к этому, повторяющиеся рутинные ручные действия неизбежно порождают задержки и ошибки. Они появляются просто потому, что человек устал, отвлёкся или поспешил.
Именно здесь особенно важны возможности MetaTrader 5. Встроенная поддержка SQLite — локальной базы данных — меняет правила игры. База данных становится не внешним хранилищем, а органической частью торгового процесса. Запросы выполняются практически мгновенно, статистика обновляется автоматически, анализ торговли занимает секунды вместо часов. И результат анализа оказывается там, где он действительно нужен — рядом с торговлей, а не где-то за пределами терминала.
В статье разобран "минимальный рабочий набор" алготрейдера: структура базы данных торгового журнала, безопасная быстрая запись данных, аналитические SQL-запросы и вывод ключевой статистики на интерактивную панель в MetaTrader 5. Это позволит убрать большую часть рутины и сосредоточиться на творческом процессе: анализе и развитии торговой системы.
Зачем алгоритмическому трейдеру локальная база данных
Разработка и тестирование торговых стратегий порождают колоссальные объёмы данных. Результаты оптимизаций, истории сделок, сигналы индикаторов, макроэкономические события — всё это требует не просто хранения, но и структурирования, и быстрого доступа. Локальная база данных решает эти задачи, с которыми сталкивается любой серьёзный алготрейдер.
Сохранение результатов тестирования и оптимизацииОптимизация стратегии с перебором параметров генерирует сотни, а то и тысячи вариантов параметров. Каждый проход — это набор метрик, значений параметров, результатов. Как сравнить их между собой? Как отследить эволюцию стратегии во времени? База данных позволяет хранить полную историю оптимизаций и анализировать её в любых разрезах — от простых таблиц до сложных корреляций.
Автоматический торговый журналВместо рутинного ведения журнала в электронной таблице запустите эксперт в терминале и получите автоматическую фиксацию каждой сделки с полным набором атрибутов: символ, «магик», объём, цена, время, результат, комментарий. Данные попадают в базу в реальном времени и становятся доступны для анализа мгновенно — без дополнительных действий со стороны трейдера.
Анализ торговых сигналовСохранение и анализ торговых сигналов открывает возможности, недоступные при работе с обычными логами. Какова эффективность сигналов? Какие индикаторы коррелируют между собой? В каких условиях входы и выходы оптимальны? Все трейдеры хотят знать ответы на эти вопросы. SQL-запросы предоставляют мощнейший инструмент анализа без необходимости писать дополнительный код.
Для начала работы с SQLite нужны всего три функции:
- DatabaseOpen — открыть базу данных
- DatabaseExecute — выполнить запрос к таблице
- DatabaseClose — закрыть базу данных
И этого достаточно для решения 90% задач, стоящих перед алготрейдером.
Пример открытия и закрытия базы данных:
int db = DatabaseOpen("trading.sqlite", DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE); if(db == INVALID_HANDLE) { Print("Error opening Database: ", GetLastError()); return; } // ... работа с базой данных ... DatabaseClose(db); // закрытие базы данных
Главное: SQLite встроен в систему MetaTrader 5, что называется, «из коробки» и работает напрямую с ядром терминала. В то время как Excel и другие электронные таблицы — это внешние программы, к которым эксперт на MQL5 обращается либо через устаревшие «костыли» вроде OLE-автоматизации, либо через внешний CSV-файл. Разница в пользу SQLite в MetaTrader 5 колоссальная — и по скорости, и по удобству, и по надёжности.
Схема таблиц: архитектура торгового журнала
Проектирование базы данных начинается с вопроса: что именно нужно хранить? Для торгового журнала ответ очевиден — историю сделок, сигналы и события. Но почему именно три таблицы, а не одна «большая и универсальная»? Разделение на DEALS, SIGNALS и EVENTS — это не прихоть, а необходимость. Каждая сущность живёт своей жизнью и связывается с другими при запросе. Хотите узнать «все сделки после сигнала с уверенностью выше 80%»? SQLite справится с этим легко и элегантно и при этом не выходя за пределы торгового терминала.
Таблица DEALS — хронология торговлиСодержит полную информацию о каждой сделке: идентификатор, тикеты, символ, «магический» номер (идентификатор стратегии), объём, цены открытия и закрытия, время, финансовый результат и произвольный комментарий. Это «скелет» торгового журнала — основа для любого анализа.
Таблица SIGNALS — история сигналовХранит сигналы от индикаторов и торговых систем: символ, таймфрейм, тип сигнала (покупка/продажа), цену, время возникновения и дополнительные параметры. Позволяет анализировать качество сигналов и их связь с реальными сделками — ключ к пониманию, работает ли ваша стратегия.
Таблица EVENTS — новостной контекстСодержит информацию о важных экономических событиях: название, валюту, уровень важности, прогноз, фактическое значение и время публикации. Позволяет находить взаимосвязи между торговыми результатами и новостным фоном — ведь рынок не существует в вакууме.
На рисунке ниже представлена ER-диаграмма (Entity-Relationship Diagram) — визуальное отображение структуры базы данных и взаимосвязей между таблицами:

Рис. 1: ER-диаграмма таблиц торгового журнала
Код создания таблиц:
// Таблица истории сделок - DEALS: string createDeals = "CREATE TABLE IF NOT EXISTS DEALS (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "deal_ticket INTEGER UNIQUE, " "order_ticket INTEGER, " "symbol TEXT NOT NULL, " "type INTEGER, " // 0=BUY, 1=SELL "direction INTEGER, " // 0=IN, 1=OUT, 2=IN/OUT "volume REAL, " "price_open REAL, " "price_close REAL, " "profit REAL, " "swap REAL, " "commission REAL, " "sl REAL, " "tp REAL, " "magic INTEGER, " "comment TEXT, " "time INTEGER, " // Unix timestamp "time_msc INTEGER, " "reason INTEGER" ");"; // Таблица торговых сигналов - SIGNALS: string createSignals = "CREATE TABLE IF NOT EXISTS SIGNALS (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "symbol TEXT NOT NULL, " "signal_type TEXT, " // 'BUY', 'SELL', 'CLOSE' "price REAL, " "stop_loss REAL, " "take_profit REAL, " "strength REAL, " // 0.0 - 1.0 "source TEXT, " // Strategy name "notes TEXT, " "time INTEGER" ");"; // Таблица событий - EVENTS: string createEvents = "CREATE TABLE IF NOT EXISTS EVENTS (" "id INTEGER PRIMARY KEY AUTOINCREMENT, " "event_type TEXT, " // 'NEWS', 'ERROR', 'NOTE', etc. "symbol TEXT, " "description TEXT, " "importance INTEGER, " // 1=Low, 2=Medium, 3=High "time INTEGER," "actual REAL, " "previous REAL, " "forecast REAL " ");"; // Индексы для ускорения поисковых запросов: string createIndexes[] = { "CREATE INDEX IF NOT EXISTS idx_deals_symbol ON DEALS(symbol);", "CREATE INDEX IF NOT EXISTS idx_deals_magic ON DEALS(magic);", "CREATE INDEX IF NOT EXISTS idx_deals_time ON DEALS(time);", "CREATE INDEX IF NOT EXISTS idx_signals_symbol ON SIGNALS(symbol);", "CREATE INDEX IF NOT EXISTS idx_signals_time ON SIGNALS(time);" }; // Создание таблиц в БД: if(!DatabaseExecute(database, createDeals)) { Print("Error creating DEALS table: ", GetLastError()); return(false); } if(!DatabaseExecute(database, createSignals)) { Print("Error creating SIGNALS table: ", GetLastError()); return(false); } if(!DatabaseExecute(database, createEvents)) { Print("Error creating EVENTS table: ", GetLastError()); return(false); } // Создание индексов: for(int i = 0; i < ArraySize(createIndexes); i++) { DatabaseExecute(database, createIndexes[i]); }
Обратите внимание на индексы: idx_deals_symbol, idx_deals_magic, idx_deals_time, idx_signals_symbol, idx_signals_time. Они ускоряют запросы по ключевым полям: symbol, magic, time в таблице DEALS и symbol, time в SIGNALS. Индексы — это инвестиция в производительность, которая окупается при каждом аналитическом запросе.
Вставка данных и транзакции: скорость имеет значение
MQL5 предоставляет два способа вставки данных в таблицы: прямое выполнение SQL-запроса и использование подготовленных запросов. Какой выбрать? Ответ однозначен: подготовленные запросы предпочтительнее и с точки зрения безопасности, и с точки зрения производительности.
Прямая вставка через DatabaseExecute:
string sql = StringFormat( "INSERT INTO DEALS (ticket, symbol, magic, volume, price, time, profit) " "VALUES (%d, '%s', %d, %.2f, %.5f, %I64d, %.2f)", ticket, symbol, magic, volume, price, TimeCurrent(), profit); DatabaseExecute(db, sql);
Параметризованный запрос через DatabasePrepare:
// Создадим параметризованный запрос: int request = DatabasePrepare(db, "INSERT INTO DEALS (ticket, symbol, magic, volume, price, time, profit) " "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"); // Установим значение первого параметра запроса - в функции DatabaseBind индексация полей в записи начинается с нуля: DatabaseBind(request, 0, ticket); DatabaseTransactionBegin(db); // Устанавливаем значения остальных параметров перед добавлением записи: DatabaseBind(request, 1, symbol); DatabaseBind(request, 2, magic); DatabaseBind(request, 3, volume); DatabaseBind(request, 4, price); DatabaseBind(request, 5, TimeCurrent()); DatabaseBind(request, 6, profit); // Выполним запрос на вставку записи: DatabaseRead(request); DatabaseFinalize(request); DatabaseTransactionCommit(db);
Использование транзакций — это не опция, а необходимость. При массовых операциях вставки и обновления транзакции ускоряют работу в сотни и даже тысячи раз. Почему? Без транзакции каждая операция фиксируется на диск отдельно. С транзакцией — все операции группируются и записываются одним блоком.
Результаты наших тестов впечатляют: производительность SQLite в MQL5 сопоставима с нативным C++ кодом (LLVM 9.0). В большинстве тестов разница составляет менее 5%, а в некоторых сценариях MQL5 даже превосходит C++. На диаграмме ниже показано сравнение времени вставки 1000 записей без транзакций против 25000 записей с транзакциями — разница более чем в 3500 раз в пересчёте на одну запись!

Рис. 2: Сравнение скорости вставки: без транзакций/с транзакциями
Пример использования транзакции при массовой вставке:
DatabaseTransactionBegin(db); for(int i = 0; i < ArraySize(deals); i++) { InsertDeal(db, deals[i]); // вставка очередной сделки - элемента массива структур deals[] } DatabaseTransactionCommit(db);
Все тесты можно изучить (и самим проверить!) в разделе MQL5-форума: SQLite в MQL5: новые функции и тестирование производительности.
SQL-запросы: аналитика без программирования
Одно из главных преимуществ SQL — возможность получения сложной аналитики одним запросом. В MetaTrader 5 это доступно напрямую. Никаких циклов, никаких условий, никаких временных переменных. Один запрос — и готовый результат. Рассмотрим несколько типичных сценариев.
Статистика по торговым символамКакие инструменты приносят прибыль, а какие — тихо «съедают» депозит? Этот вопрос, а за ним и SQL-запрос должен быть первым в арсенале любого трейдера. Он показывает сводную информацию по каждому символу: количество сделок, суммарную прибыль и средний результат.
string sql = "SELECT symbol, COUNT(*) as deals, " "SUM(profit) as total_profit, " "AVG(profit) as avg_profit " "FROM DEALS GROUP BY symbol " "ORDER BY total_profit DESC"; DatabasePrint(db, sql);
Разбор элементов запроса:
| Элемент запроса | Описание |
|---|---|
| SELECT symbol | Выбирает колонку с названием инструмента |
| COUNT(*) | Подсчитывает количество сделок для инструмента |
| SUM(profit) | Суммирует прибыль по всем сделкам инструмента |
| GROUP BY symbol | Группирует результаты по инструменту |
| ORDER BY total_profit DESC | Сортирует по убыванию общей прибыли |
Анализ по стратегиям («магическим» номерам)
«Магический» номер — это уникальный идентификатор эксперта, добавляемый к каждой сделке. Если на одном счёте работает несколько стратегий, этот запрос покажет, какая из них эффективнее. Особенность — использование условного выражения CASE для подсчёта прибыльных сделок.
string sql = "SELECT magic, COUNT(*) as trades, " "SUM(CASE WHEN profit > 0 THEN 1 ELSE 0 END) as wins, " "SUM(profit) as net_profit " "FROM DEALS GROUP BY magic"; DatabasePrint(db, sql);
Разбор элементов запроса:
| Элемент запроса | Описание |
|---|---|
| SELECT magic | Выбирает колонку с «магическим» номером |
| COUNT(*) | Общее количество сделок по выбранной стратегии |
| CASE WHEN profit > 0 | Условие: если прибыль положительна |
| THEN 1 ELSE 0 END | Возвращает 1 для прибыльных сделок и 0 для убыточных сделок |
| SUM(wins) | Суммирует единицы — получаем количество прибыльных сделок wins |
Разделив число прибыльных сделок (wins) на общее число (trades), получаем процент прибыльных сделок — ключевую метрику качества стратегии. Вы получили высокий процент при отрицательной суммарной прибыли? Это сигнал о проблеме с управлением рисками — убыточные сделки крупнее прибыльных.
Распределение торговли по часам
Когда стратегия работает лучше всего? Во время европейской сессии? Американской? Или, может быть, в «тихие часы»? Функция strftime извлекает час из времени открытия сделки (формат 00--23), позволяя выявить наиболее и наименее прибыльные временные интервалы.
string sql = "SELECT strftime('%H', time, 'unixepoch') as hour, " "COUNT(*) as trades, SUM(profit) as profit " "FROM DEALS GROUP BY hour"; DatabasePrint(db, sql);
Важно: в MQL5 и SQLite время хранится как Unix timestamp (секунды с 1970-01-01). Поэтому добавлен модификатор 'unixepoch'. Аналогично можно анализировать распределение по дню недели strftime('%w', ...), месяцу strftime('%m', ...) или комбинациям.
Разбор элементов запроса:
| Элемент запроса | Описание |
|---|---|
| strftime('%H', ...) | Извлекает час из времени открытия сделки (00-23) |
| time | Колонка с временем открытия сделки |
| GROUP BY hour | Группирует сделки по часу открытия |
Панель статистики: визуализация внутри терминала
Сухие цифры — это хорошо, но наглядность лучше. База данных легко интегрируется с графическими панелями внутри терминала, превращая данные в понятную визуальную информацию. Интерактивная панель статистики работает в реальном времени, показывая именно то, что нужно трейдеру.
Показанная ниже панель отображает суммарный результат за заданное число дней (SUMMARY), раздельный результат по инструментам (BY SYMBOL) и лучшие часы для торговли (BEST HOURS). Для чтения данных в структуру используется функция DatabaseReadBind:

Рис. 3: Интерактивная панель статистики в терминале
struct DealStats { string symbol; int count; double total_profit; double avg_profit; }; int request = DatabasePrepare(db, "SELECT symbol, COUNT(*) as count, SUM(profit) as total_profit, " "AVG(profit) as avg_profit FROM DEALS GROUP BY symbol"); DealStats stats; while(DatabaseReadBind(request, stats)) { // Вывод в таблицу на панели: AddRowToTable(stats); } DatabaseFinalize(request);
Функция DatabaseReadBind автоматически сопоставляет поля структуры с колонками запроса по именам. Достаточно объявить структуру с полями, соответствующими именам колонок в SQL-запросе.
Управление панельюКнопка [Refresh] обновляет статистику с полным пересчетом. Кнопка [Export] выводит рассчитанные данные в CSV-файлы из таблиц базы данных: общий результат работы, сделки, события и сигналы. Пример экспорта:
1,XAGUSD,196,322.25,1.64,14,181
2,AUDUSD,196,-12.0,-0.06,82,99
3,EURUSD,58,-38.5,-0.66,23,35
4,GBPUSD,57,-43.2,-0.75,17,40
«Горячие» клавиши для быстрого управления:
- 'A' или 'a' — обновить аналитику,
- 'E' или 'e' — вывести данные в CSV-файл,
- 'R' или 'r' — считать историю сделок,
- 'P' или 'p' — включить/выключить показ панели.
Настройки эксперта:
- «Database file name» — название файла базы данных (по умолчанию: «TradingJournal.db»),
- «Auto-import trade history» — автоматическая загрузка истории (по умолчанию: true),
- «History depth in days» — глубина истории (по умолчанию: 30 дней),
- «Export path» — путь к CSV-файлам (по умолчанию: «MQL5/Files»),
- «Export to CSV format» — экспорт в CSV (по умолчанию: true),
- «Show statistics panel on chart» — показ панели (по умолчанию: true).
MetaEditor: инструмент работы с базой данных
Редактор MetaEditor, входящий в состав MetaTrader 5, имеет встроенный инструмент для работы с SQLite: открытие и редактирование таблиц, выполнение SQL-запросов, откат изменений. Поскольку база данных SQLite хранится в единственном файле на компьютере пользователя, к ней можно получить доступ в любой момент через навигатор MetaEditor — проконтролировать структуру и содержимое таблиц, выполнить отладочные запросы.
Это особенно удобно при отладке MQL5-приложений, работающих с базой данных. Попробуйте в реальном времени увидеть, какие данные записываются, и скорректировать логику эксперта.
Посмотрим, как легко это сделать на примере базы данных, которая создаётся тестовым экспертом «TradingJournalSQLite-EA» из данной статьи. Откроем базу данных и таблицы в ней, запустим несколько SQL-запросов и закроем. Можно долго объяснять все это словами, но лучше один раз увидеть...
1. Начните с самого простого — откройте базу данных и выберите нужную таблицу: 
Рис. 4: Открыть/Выбрать таблицу/Закрыть
Таблица 'DEALS' выбирается двойным кликом левой кнопки мыши на её названии. Это эквивалентно выполнению SQL-запроса в навигаторе:
SELECT * FROM 'DEALS';2. Настройте формат времени:

Рис. 5: Формат времени
3. Основной вопрос: Кто сколько зарабатывает? Узнайте ответ за миллисекунды:SELECT symbol, COUNT(*) as count, SUM(profit) as total_profit, AVG(profit) as avg_profit FROM DEALS GROUP BY symbol;
Для тех, кто не любит длинные строки:
SELECT symbol, COUNT(*) as count, SUM(profit) as total_profit, AVG(profit) as avg_profit FROM DEALS GROUP BY symbol;

Рис. 6: Кто сколько зарабатывает
4. Отсортируйте по убыванию заработка:SELECT symbol, COUNT(*) as count, SUM(profit) as total_profit, AVG(profit) as avg_profit FROM DEALS GROUP BY symbol ORDER BY total_profit DESC;

Рис. 7: По убыванию заработка
SELECT SUM(CASE WHEN direction IN (1, 2, 3) THEN profit ELSE 0 END) as net_profit FROM DEALS;

Рис. 8: Итого
Хотите отладить сложный запрос? Не проблема:
SELECT COUNT(*) as total_deals, SUM(CASE WHEN direction IN (1, 2, 3) THEN 1 ELSE 0 END) as closed_trades, SUM(CASE WHEN (profit + swap + commission) > 0 AND direction IN (1, 2, 3) THEN 1 ELSE 0 END) as total_wins, SUM(CASE WHEN (profit + swap + commission) < 0 AND direction IN (1, 2, 3) THEN 1 ELSE 0 END) as total_losses, ROUND(100.0 * SUM(CASE WHEN (profit + swap + commission) > 0 AND direction IN (1, 2, 3) THEN 1 ELSE 0 END) / NULLIF(SUM(CASE WHEN direction IN (1, 2, 3) THEN 1 ELSE 0 END), 0), 2) as overall_win_rate, SUM(CASE WHEN direction IN (1, 2, 3) THEN profit ELSE 0 END) as net_profit, SUM(CASE WHEN direction IN (1, 2, 3) THEN swap ELSE 0 END) as total_swap, SUM(CASE WHEN direction IN (1, 2, 3) THEN commission ELSE 0 END) as total_commission, SUM(CASE WHEN direction IN (1, 2, 3) THEN profit + swap + commission ELSE 0 END) as net_result, ROUND(AVG(CASE WHEN direction IN (1, 2, 3) THEN profit + swap + commission ELSE NULL END), 2) as avg_profit_per_trade, SUM(CASE WHEN (profit + swap + commission) > 0 AND direction IN (1, 2, 3) THEN (profit + swap + commission) ELSE 0 END) as gross_profit, SUM(CASE WHEN (profit + swap + commission) < 0 AND direction IN (1, 2, 3) THEN ABS(profit + swap + commission) ELSE 0 END) as gross_loss, CASE WHEN SUM(CASE WHEN (profit + swap + commission) < 0 AND direction IN (1, 2, 3) THEN ABS(profit + swap + commission) ELSE 0 END) > 0 THEN ROUND(SUM(CASE WHEN (profit + swap + commission) > 0 AND direction IN (1, 2, 3) THEN (profit + swap + commission) ELSE 0 END) / SUM(CASE WHEN (profit + swap + commission) < 0 AND direction IN (1, 2, 3) THEN ABS(profit + swap + commission) ELSE 0 END), 2) ELSE 0 END as profit_factor, COUNT(DISTINCT symbol) as symbols_traded, COUNT(DISTINCT magic) as strategies_used FROM DEALS;

Рис. 9: Сложный SQL-запрос
Данный SQL-запрос взят без изменений из тестового эксперта «TradingJournalSQLite-EA», приложенного к статье.
Заключение: торгово-аналитическая система прямо в MetaTrader 5
Торговый журнал перестал быть статическим файлом, который нужно экспортировать, открывать в сторонней программе и вручную приводить к удобному виду. В MetaTrader 5 он стал частью торгово-аналитической системы — точной, надёжной, реагирующей на изменения в реальном времени. Это сделано нативно — SQLite интегрирован в терминал MetaTrader 5 на уровне ядра.
Это позволяет использовать MetaTrader 5 как самодостаточную торгово-аналитическую систему и применять всю мощь SQL‑запросов. С возможностью расширения и изменения под новые идеи. Вот что получает трейдер:
- данные сохраняются автоматически и доступны для анализа сразу после появления;
- аналитические SQL-запросы можно менять быстро и гибко, без усложнения кода эксперта;
- панель с аналитикой доступна прямо на графике в терминале — там, где идёт торговля;
- система легко расширяется под новые идеи, метрики и срезы анализа — без обращения к сторонним приложениям.
Именно поэтому встроенная поддержка SQLite в MetaTrader 5 — это не просто удобное хранилище данных, а крепкий фундамент для полноценной торгово‑аналитической инфраструктуры, где торговля и анализ работают в одной среде, на одном языке и с нативной поддержкой SQL.
Рекомендуемые материалы для углублённого изучения SQLite в MQL5:
- Работа с базами данных
- SQLite в MQL5: новые функции и тестирование производительности
- SQLite: нативная работа с базами данных на SQL в MQL5
- Рецепты MQL5 — База данных макроэкономических событий
- Возможности SQLite в MQL5: Пример интерактивной панели с торговой статистикой в разрезе символов и магиков
Список файлов, приложенных к статье:
| Название файла | Описание |
|---|---|
| TradingJournalSQLite-EA.mq5 | Файл, содержащий код тестового эксперта, создающего панель со статистикой торговли |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Статистический арбитраж на основе коинтегрированных акций (Часть 5): Отбор активов
Моделирование рынка (Часть 24): Первые шаги на SQL (VII)
Создание самооптимизирующихся советников на MQL5 (Часть 15): Идентификация линейных систем
Упрощение работы с базами данных в MQL5 (Часть 1): Введение в базы данных и SQL
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования