Советник на MQL5 с нуля. Часть 1: скелет робота и риск без мартингейла
Разбираем устройство советника на MQL5 на примере нашего собственного робота: три обязательные функции, расчёт объёма от расстояния до стопа и почему высокий процент побед обычно означает спрятанный убыток.

Советник на MQL5 с нуля. Часть 1: скелет робота и риск без мартингейла
Мы держим на живых демо-счетах полтора десятка бесплатных советников из каталога и публикуем их результаты. Когда набралось 449 сделок, выяснилась неприятная закономерность: роботы, которые сливали счёт, почти все имели высокий процент прибыльных сделок. Один закрыл в плюс 68% сделок и обнулил депозит.
Поэтому мы написали свой — не чтобы обогнать рынок, а чтобы показать на коде, чем устроен робот, который не может уничтожить счёт за один день. Он лежит в каталоге вместе с исходником и торгует на нашем демо-счёте. В этой части разберём скелет и работу с риском, во второй — сборку и прогон в тестере из командной строки.
Из чего состоит советник
Любой советник на MQL5 — это три функции, которые вызывает терминал.
int OnInit() // один раз при запуске на графике
void OnTick() // на каждом изменении цены
void OnDeinit(const int reason) // при снятии с графика
OnInit — место для настройки: здесь мы задаём магический номер, по которому робот отличает свои позиции от чужих, и режим исполнения ордеров.
#include <Trade\Trade.mqh>
CTrade trade;
int OnInit()
{
trade.SetExpertMagicNumber(MagicNumber);
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetDeviationInPoints(20);
return(INIT_SUCCEEDED);
}
Класс CTrade из стандартной библиотеки берёт на себя формирование торговых запросов — писать структуру MqlTradeRequest вручную в 2026 году незачем.
OnTick вызывается на каждом тике, то есть десятки раз в минуту. Если стратегия работает по закрытию свечей, первым делом надо отсечь лишние вызовы, иначе робот будет принимать одно и то же решение сотни раз:
static datetime lastBar = 0;
datetime barTime = iTime(_Symbol, PERIOD_M15, 0);
if(barTime == lastBar) return; // эта свеча уже обработана
lastBar = barTime;
Параметры: почему не в пунктах
Первая версия нашего советника не совершила ни одной сделки за четыре года истории. Причина оказалась в фильтре, записанном в пунктах: у золота разрядность котировок у разных брокеров отличается в десять раз, и «диапазон не шире 3000 пунктов» означало у одного брокера три доллара, а у другого тридцать. Фильтр отсекал вообще всё.
Поэтому пороги задаются в процентах от цены — такая настройка переносится между брокерами и инструментами без правок:
input double MinRangePct = 0.15; // тихий день, ловить нечего
input double MaxRangePct = 2.0; // новостной разрыв, пробой ложный
double rangePct = (rangeHigh - rangeLow) / price * 100.0;
if(rangePct < MinRangePct || rangePct > MaxRangePct) return;
То же касается спреда. Абсолютный лимит бесполезен: на золоте спред бывает 90–190 пунктов, и порог в 60 отсекает всё подряд. Значение имеет не сам спред, а какую долю хода он съедает:
double spreadShare = spreadPrice / (rangeHigh - rangeLow) * 100.0;
if(spreadShare > MaxSpreadRangePct) return; // издержки съедят движение
Главное: объём считается от стопа, а не наоборот
Это и есть разница между роботом, который переживает плохую неделю, и роботом, который обнуляет счёт.
Неправильный подход, встречающийся в большинстве бесплатных советников: объём фиксирован или растёт после убытка, а стоп ставится «где-нибудь» или не ставится вовсе. Правильный — обратный: сначала решаем, где сделка признаётся ошибочной, и только потом считаем объём так, чтобы эта ошибка стоила заранее известную долю депозита.
double RiskLots(double stopDistance)
{
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
if(tickValue <= 0 || tickSize <= 0) return(0);
// Сколько денег готовы потерять на этой сделке
double moneyRisk = AccountInfoDouble(ACCOUNT_BALANCE) * RiskPercent / 100.0;
// Во сколько обойдётся стоп при объёме в один лот
double lossPerLot = (stopDistance / tickSize) * tickValue;
double lots = moneyRisk / lossPerLot;
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
lots = MathFloor(lots / lotStep) * lotStep;
// Депозита не хватает на такой стоп — сделку пропускаем,
// а не рискуем больше заявленного
if(lots < minLot) return(0);
return(lots);
}
Обратите внимание на последнюю строку. Соблазн велик: округлить до минимального лота и всё-таки войти. Именно так робот незаметно начинает рисковать втрое больше, чем написано в настройках. Пропустить сделку дешевле.
Важная деталь: SYMBOL_TRADE_TICK_VALUE и SYMBOL_TRADE_TICK_SIZE нужно спрашивать у терминала, а не зашивать в код. Для золота, валютной пары и индекса эти числа разные, и робот, написанный «под EURUSD», на золоте посчитает объём с ошибкой в сто раз.
Безубыток: страховка, а не панацея
Когда сделка ушла в плюс на заранее заданное число «рисков», стоп переносится в точку входа. Дальше сделка либо заработает, либо закроется в ноль, но убыточной уже не станет.
double risk = MathAbs(entry - sl);
double profit = (type == POSITION_TYPE_BUY) ? (price - entry) : (entry - price);
if(BreakevenAtR > 0 && profit >= risk * BreakevenAtR)
trade.PositionModify(ticket, entry, tp); // стоп на цену входа
Момент переноса — параметр, и это не мелочь. В нашем тестировании перенос на однократном риске давал заметно худший результат, чем на полуторакратном: слишком ранний безубыток выбивает сделки, которым нужно «продышаться» перед движением. Разница между этими двумя вариантами на четырёхлетней истории — около десяти процентов итога.
Что получилось
Стратегия простая: измеряем диапазон тихой азиатской сессии, входим на первом закрытии свечи за его границей, стоп за противоположной границей, цель — три расстояния до стопа, одна сделка в день.
Параметры подбирались на 2022–2024 годах и проверялись на 2025–2026, которых подбор не видел:
| Выборка | Результат | Сделок | Прибыльных | Профит-фактор | Просадка |
|---|---|---|---|---|---|
| Обучение 2022–2024 | +33,0% | 327 | 43,7% | 1,35 | 7,5% |
| Проверка 2025–2026 | +11,6% | 145 | 46,2% | 1,33 | 3,9% |
| Другой брокер, 2022–2025 | +41,5% | 335 | 30,4% | 1,41 | 9,2% |
Третья строка — тот же советник и те же настройки, но на котировках другого брокера. Разница в результате берётся не из стратегии, а из спреда и разрядности котировок: у одного брокера золото называется GOLD#, у другого XAUUSD. Если стратегия рассыпается при смене брокера — она держалась не на рынке, а на конкретном прайс-фиде.
Прибыльных сделок меньше половины — и это нормально при цели втрое больше риска. Робот из нашего теста с 68% побед выглядел кратно лучше по этой колонке и при этом уничтожил депозит. Процент побед не говорит о советнике почти ничего; смотреть надо на просадку и профит-фактор.
Честно и про ограничения: на EURUSD с теми же настройками профит-фактор около единицы, то есть преимущество именно на золоте. И backtest остаётся backtest — робот уже стоит на нашем демо-счёте, результат будет виден в мониторинге без возможности что-либо подкрутить.
Дальше
Во второй части — то, чего почти нет в русскоязычных руководствах: как компилировать советник из командной строки через metaeditor64.exe и гонять его в тестере стратегий без единого клика в интерфейсе, через ini-конфиг. Это превращает проверку гипотезы из десятиминутного ручного ритуала в десятисекундную команду, и только благодаря этому мы смогли перебрать сотню конфигураций за вечер.
Исходник советника целиком лежит в карточке в каталоге — забирайте, читайте, меняйте под себя.
Ссылка на брокера в тексте партнёрская: по ней мы получаем комиссию, а вы — те же условия, что и по прямой. Мы упоминаем только то, чем пользуемся сами.
Поделиться статьёй:
Комментарии (0)
Чтобы оставить комментарий, необходимо войти в систему
Пока нет комментариев
Будьте первым, кто оставит комментарий!
Хотите больше полезных статей?
Подпишитесь на наш блог и получайте новые статьи прямо на почту
Читать все статьи