September 29, 2023

Опасные баги MT5

МТ5 срочно нуждается в подорожнике

Дисклеймер

Статья предназначена исключительно для информационных целей и не является советом по инвестициям или рекомендацией к действию.

Срочно несите подорожник

По моему опыту работы с МТ5, иногда возникают проблемы, которые связаны с самим торговым терминалом. Попытки найти причину в коде советника или индикатора обычно бесполезны, так как просто рушится всю логика кода и начинается непредсказуемое поведение советника или индикатора.

Никто от этого не застрахован. Между тем, такие глюки очень опасны для торговли. Приведу простой пример:

Здесь сделка по EURGBP с лотом 0.08 является лишней. С точки зрения работы кода, такой сделки не должно было быть, и это не отражено в логах советника. В логах присутствует только сделка на EURGBP с объемом 0.01 лота. Откуда взялась данная сделка с объемом 0.08? Кроме того, объем 0.08 встречается только в логах сделки EURUSD, которая, однако, появилась спустя минуту после этой сделки на EURGBP, и, следовательно, не могла повлиять на нее.

Так как я уже был знаком с таким поведением у МТ5, то делаю запись логов везде, где можно и осмысленно, чтобы при анализе логов можно было отличить баги собственного кода от багов самого терминала.

Находим сделку по EURGBP в логах с лотом 0.01, вот она:

В моем советнике используется конечный автомат. Он имеет несколько состояний:

enum TradeStatus {
	TS_INITIAL,
	TS_OPENING,
	TS_ALL_OPENED,
	TS_CLOSING,
	TS_REPEATED_CLOSING,
	TS_ALL_CLOSED
};

После того, когда хотя-бы одна сделка открылась, состояние автомата меняется с TS_INITIAL на TS_OPENING. Это происходит в конце функции открытия сделок:

if (is_send_singal) {   
	if (second_of_day <= close_second_day) {
		open_trades_expiration = server_ts + close_second_day - second_of_day;
	} else {
		open_trades_expiration = server_ts + (homura::SEC_PER_DAY - second_of_day) + close_second_day;
	}
	status = TradeStatus::TS_OPENING;
	on_status(magic, status);
	if (logger) logger.log(__LINE__,"TradeStatus: TS_OPENING; magic: " + IntegerToString(magic));
} else {
	status = TradeStatus::TS_INITIAL;
	on_status(magic, status);
	if (logger) logger.log(__LINE__,"TradeStatus: TS_INITIAL; magic: " + IntegerToString(magic));
} // if else (is_send_singal)

Срабатывает код, где происходит запись лога line: 723; TradeStatus: TS_OPENING; magic: 5.

Далее происходит повторный вызов функции. Функция начинается с проверки статуса сделок:

switch(status) {
case TradeStatus::TS_INITIAL:
	break;
case TradeStatus::TS_OPENING:
	// Проверяем наличие сделок для открытия
	if (trade_manager.check_all_trades_opened()) {
		status = TradeStatus::TS_ALL_OPENED;
		on_status(magic, status);
		if (logger) logger.log(__LINE__,"TradeStatus: TS_ALL_OPENED; magic: " + IntegerToString(magic));
	}
	return;
case TradeStatus::TS_CLOSING:
	// Ничего не делаем, ждем
	if (trade_manager.check_all_trades_closed()) {
		status = TradeStatus::TS_ALL_CLOSED;
		on_status(magic, status);
		if (logger) logger.log(__LINE__,"TradeStatus: TS_ALL_CLOSED; magic: " + IntegerToString(magic));
	}
	return;
case TradeStatus::TS_ALL_OPENED:
case TradeStatus::TS_REPEATED_CLOSING:
	// Проверяем время экспирации
	if (open_trades_expiration == 0 || 
		server_ts >= open_trades_expiration) {
		// Обнуляем время экспирации
		open_trades_expiration = 0;
		// Закрываем все сделки по времеи
		if (close_all_positions()) {
			status = TradeStatus::TS_CLOSING;
			on_status(magic, status);
			if (logger) logger.log(__LINE__,"TradeStatus: TS_CLOSING; magic: " + IntegerToString(magic));
		} else {
			status = TradeStatus::TS_REPEATED_CLOSING;
			on_status(magic, status);
			if (logger) logger.log(__LINE__,"TradeStatus: TS_REPEATED_CLOSING; magic: " + IntegerToString(magic));
		}
	}
	// Ничего не делаем, эспирация сделок еще не истекла
	return;
case TradeStatus::TS_ALL_CLOSED:
	// Для теста!!!
	//if (enable_test_mode) return;
	status = TradeStatus::TS_INITIAL;
	on_status(magic, status);
	if (logger) logger.log(__LINE__,"TradeStatus: TS_INITIAL; magic: " + IntegerToString(magic));
	break;
};

Код должен был попасть в секцию case TradeStatus::TS_OPENING, после чего функция должна была завершиться, так как в конце происходит return.

Однако, по логам мы видим запись line: 727; TradeStatus: TS_INITIAL; magic: 5. Это сработал код в конце функции:

} else {
	status = TradeStatus::TS_INITIAL;
	on_status(magic, status);
	if (logger) logger.log(__LINE__,"TradeStatus: TS_INITIAL; magic: " + IntegerToString(magic));
} // if else (is_send_singal)

Возникает вопрос, как такое возможно? Но это еще не все. Вторая сделка на паре EURGBP с лотом 0.08 отсутствует в логах. Если первую сделку мы видим, как запись open position: DONE; magic: 5; symbol: EURGBP; signal: BUY; order: 339505028; volume: 0.01; comment: Request executed, то вот второй сделки нигде нет.

Конечно, я не первый раз встречаюсь с подобным странным поведением кода в терминале МТ5. Ранее подобные баги у меня отняли целый месяц перед запуском торговли на intrade.bar. Тогда я выяснил, что подобная проблема бывала не только у меня: несколько человек с ТГ канала тоже подтвердили, что встречали подобное.

Как лечить баг

Помогает отключение оптимизации при компиляции советника (убрать флажок "Максимальная оптимизация"). После отключения оптимизации обязательно нужно перекомпилировать советник или индикатор.

Может помочь обновление терминала, для этого рекомендую скачивать и устанавливать оригинальный МТ5, потому что терминалы от брокеров не всегда обновляются и могут иметь подобные баги.

Иногда очередное обновление МТ5 может быть причиной появления бага. В этом случае он может исчезнуть со следующим обновлением. Поэтому я рекомендую после очередной компиляции советника или индикатора на новой версии МТ5 сначала проверять его работу логики кода перед использованием на реальном счете.