Всем привет! Сегодня мы продолжим работать над нашим советником, добавим в него простейшие торговые сигналы по индикатору и модернизируем различные функции советника для удобства его эксплуатации.
Предыдущие уроки
- MQL5 Урок 1 – Редактор кода, События, Устройство mql-программ
- MQL5 Урок 2 – Типы переменных
- MQL5 Урок 3 – Операции и Выражения
- MQL5 Урок 4 – Операторы
- MQL5 Урок 5 – Математические функции и циклы
- MQL5 Урок 6 – Массивы
- MQL5 Урок 7 – Функции
- MQL5 Урок 8 – Дата и время
- MQL5 Урок 9 – Работа со строками
- MQL5 Урок 10 – Перечисления
- MQL5 Урок 11 – Торговые операции
Логика в обработчике OnInit
Событие Init генерируется сразу после загрузки эксперта или индикатора. Обработчик OnInit имеет два варианта – с возвратом значения и без. С возвратом:
int OnInit(void);
Версия без возврата результата оставлена только для совместимости со старыми кодами:
void OnInit(void);
Функция OnInit() типа void всегда означает удачную инициализацию и не рекомендуется к использованию. Стоит использовать версию с кодом возврата.
Модифицируем нашу функцию OnInit:
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//--- проверим на корректность значение ma_period
if (ma_period <= 0 || lot <= 0 || deviation < 0 || tp < 0 || sl < 0) {
PrintFormat("Недопустимое значение входного параметра");
return (INIT_PARAMETERS_INCORRECT);
}
stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
//--- если уровень минимально допустимого отступа в пунктах от текущей цены закрытия не задан
if (stop_level <= 0) {
stop_level = 50; // зададим отступ в 50 пунктов от текущей цены закрытия
}
price_level = stop_level * SymbolInfoDouble(_Symbol, SYMBOL_POINT);
//--- при оптимизации
if (MQLInfoInteger(MQL_OPTIMIZATION)) {
//--- проверим объем доступной оперативной памяти для агента
int available_memory_mb = TerminalInfoInteger(TERMINAL_MEMORY_TOTAL);
if (available_memory_mb < 2000) {
PrintFormat("Недостаточный объем памяти для агента тестирования: %d MB", available_memory_mb);
return (INIT_AGENT_NOT_SUITABLE);
}
}
//--- проверим подключение к торговому серверу
if (!TerminalInfoInteger(TERMINAL_CONNECTED)) {
Print("Нет подключения к торговому серверу!");
return (INIT_FAILED);
}
//--- проверим разрешение на использование DLL
if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED) || !MQLInfoInteger(MQL_DLLS_ALLOWED)) {
Print("Использование DLL запрещено!");
return (INIT_FAILED);
}
//--- проверим разрешение на торговлю
if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !MQLInfoInteger(MQL_TRADE_ALLOWED)) {
Print("Торговля запрещена!");
return (INIT_FAILED);
}
//--- проверим наличие индикатора
ma_handle = iMA(_Symbol, _Period, ma_period, 0, MODE_SMA, PRICE_CLOSE);
if (ma_handle == INVALID_HANDLE) {
PrintFormat("Не удалось создать хэндл индикатора MA. Код ошибки %d", GetLastError());
return (INIT_FAILED);
}
Print("Номер билда запущенного терминала: ", TerminalInfoInteger(TERMINAL_BUILD));
Print("Наличие подключения к MQL5.community: ", TerminalInfoInteger(TERMINAL_COMMUNITY_CONNECTION));
//--- инициализация эксперта прошла удачно
return(INIT_SUCCEEDED);
}
Модификация функции открытия позиций
Вместо ранее написанных функций MarketBuy и MarketSell мы напишем более универсальную:
//+------------------------------------------------------------------+
//| Покупка или продажа по рынку |
//| int type: |
//| ORDER_TYPE_BUY |
//| ORDER_TYPE_SELL |
//+------------------------------------------------------------------+
void openMarketOrder(ENUM_ORDER_TYPE type)
{
//--- сбросим код последней ошибки в ноль
ResetLastError();
MqlTradeRequest trade_request = {}; // Инициализация структуры торгового запроса
trade_request.action = TRADE_ACTION_DEAL; // Тип - по рынку
trade_request.symbol = _Symbol; // Текущий инструмент
trade_request.volume = lot; // Объем
if (type == ORDER_TYPE_BUY) {
trade_request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); // Цена открытия
} else {
trade_request.price = SymbolInfoDouble(_Symbol, SYMBOL_BID); // Цена открытия
}
trade_request.type = type; // Тип ордера
trade_request.type_filling = ORDER_FILLING_FOK; // Политика исполнения (немедленно или отмена)
trade_request.deviation = deviation; // допустимое отклонение от цены
trade_request.magic = magic_number; // MagicNumber ордера
MqlTradeResult trade_result = {}; // Инициализация структуры результата
MqlTradeCheckResult check_result = {}; // Инициализация структуры проверки запроса
// Проверка структуры торгового запроса
bool res_check = OrderCheck(trade_request, check_result);
if (!res_check) {
Print(__FUNCTION__, " - результат проверки отрицательный, код ответа: ", check_result.retcode);
Print(__FUNCTION__, " - расшифровка кода ответа: ", resultRetcodeText(check_result.retcode));
Print(__FUNCTION__, " - код ошибки: ", GetLastError());
ResetLastError();
}
// Отправка торгового запроса
bool res_send = OrderSendAsync(trade_request, trade_result);
if (!res_send) {
uint answer = trade_result.retcode;
Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
Print(__FUNCTION__, " - код ошибки: ", GetLastError());
ResetLastError();
}
}
Торговая логика в методе OnTick
Так как мы используем для получения торговых сигналов индикатор, его хэндл нужно удалить из памяти программы. Для этого мы модифицируем обработчик OnDeinit:
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
IndicatorRelease(ma_handle);
Print(__FUNCTION__, " - Код причины деинициализации:", reason);
Print(__FUNCTION__, " - Причина деинициализации:", getUninitReasonText(reason));
}
Метод OnTick будет выглядеть следующим образом:
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if (Bars(_Symbol,_Period) < ma_period * 2) { // общее количество баров на графике
Print("На графике меньше ", ma_period * 2, " баров, советник не будет работать!");
return;
}
bool is_new_bar = isNewBar();
//--- советник должен проверять условия совершения новой торговой операции только на новом баре
if (is_new_bar == false) {
return;
}
MqlTick latest_price; // Будет использоваться для текущих котировок
MqlTradeRequest mrequest; // Будет использоваться для отсылки торговых запросов
MqlTradeResult mresult; // Будет использоваться для получения результатов выполнения торговых запросов
MqlRates rates[]; // Будет содержать цены, объемы и спред для каждого бара
// Установим индексацию в массивах котировок и индикаторов, как в таймсериях
ArraySetAsSeries(ma, true);
ArraySetAsSeries(rates, true);
//--- Получить текущее значение котировки в структуру типа MqlTick
if (!SymbolInfoTick(_Symbol, latest_price)) {
Alert("Ошибка получения последних котировок - ошибка: ",GetLastError());
return;
}
//--- Получить исторические данные последних 3-х баров
if (CopyRates(_Symbol,_Period,0,3,rates) < 0) {
Alert("Ошибка копирования исторических данных - ошибка: ", GetLastError());
return;
}
//--- Используя хэндлы индикаторов, копируем новые значения индикаторных буферов в массивы
if (CopyBuffer(ma_handle, 0, 0, 3, ma) < 0) {
Print("Ошибка копирования буферов индикатора MA - номер ошибки:",GetLastError());
return;
}
bool buy_condition = (ma[2] < ma[1] && rates[1].close > ma[1]);
bool sell_condition = (ma[2] > ma[1] && rates[1].close < ma[1]);
if (buy_condition) {
openMarketOrder(ORDER_TYPE_BUY);
}
if (sell_condition) {
openMarketOrder(ORDER_TYPE_SELL);
}
modifySlTp();
}
Как видите, проверку на открытие нового бара мы вынесли в отдельную функцию для удобства:
//+------------------------------------------------------------------+
//| Для сохранения значения времени бара мы используем |
//| static-переменную Old_Time. |
//| При каждом выполнении функции OnTick мы будем сравнивать время |
//| текущего бара с сохраненным временем. |
//| Если они не равны, это означает, что начал строиться новый бар. |
//| Timer function |
//+------------------------------------------------------------------+
bool isNewBar()
{
static datetime old_time;
datetime new_time[1];
bool is_new_bar = false;
// копируем время текущего бара в элемент new_time[0]
int copied = CopyTime(_Symbol, _Period, 0, 1, new_time);
if (copied > 0) { // ok, успешно скопировано
if (old_time != new_time[0]) { // если старое время не равно
is_new_bar = true; // новый бар
if (MQL5InfoInteger(MQL5_DEBUGGING)) {
Print("Новый бар", new_time[0], "старый бар", old_time);
}
old_time = new_time[0]; // сохраняем время бара
}
} else {
Alert("Ошибка копирования времени, номер ошибки =",GetLastError());
ResetLastError();
return is_new_bar;
}
return is_new_bar;
}
Функция модификации стоп-лоссов и тейк-профитов
Как вы заметили, при открытии позиции стоп-лоссы и тейк-профиты не установлены. Поэтому нам нужна функция, которая модифицирует стоп-лоссы и тейк-профиты открытых позиций:
//+------------------------------------------------------------------+
//| Модификация значений Stop Loss и Take Profit у открытой позиции |
//+------------------------------------------------------------------+
void modifySlTp()
{
//--- объявление запроса и результата
MqlTradeRequest request;
MqlTradeResult result;
int total = PositionsTotal(); // количество открытых позиций
//--- перебор всех открытых позиций
for (int i = 0; i < total; i++) {
//--- параметры ордера
ulong position_ticket = PositionGetTicket(i);// тикет позиции
string position_symbol = PositionGetString(POSITION_SYMBOL); // символ
int digits = (int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // количество знаков после запятой
ulong magic = PositionGetInteger(POSITION_MAGIC); // MagicNumber позиции
double volume = PositionGetDouble(POSITION_VOLUME); // объем позиции
double position_sl = PositionGetDouble(POSITION_SL); // Stop Loss позиции
double position_tp = PositionGetDouble(POSITION_TP); // Take Profit позиции
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // тип позиции
//--- если MagicNumber совпадает, Stop Loss и Take Profit не заданы
if(magic == magic_number && position_sl == 0 && position_tp == 0 && position_symbol == _Symbol) {
//--- вычисление текущих ценовых уровней
double price = PositionGetDouble(POSITION_PRICE_OPEN);
//--- вычисление и округление значений Stop Loss и Take Profit
if (type == POSITION_TYPE_BUY) {
position_sl = NormalizeDouble(price - price_level - sl * _Point, digits);
position_tp = NormalizeDouble(price + price_level + tp * _Point, digits);
} else {
position_sl = NormalizeDouble(price + price_level + sl * _Point, digits);
position_tp = NormalizeDouble(price - price_level - tp * _Point, digits);
}
//--- обнуление значений запроса и результата
ZeroMemory(request);
ZeroMemory(result);
//--- установка параметров операции
request.action = TRADE_ACTION_SLTP; // тип торговой операции
request.position = position_ticket; // тикет позиции
request.symbol = position_symbol; // символ
request.sl = position_sl; // Stop Loss позиции
request.tp = position_tp; // Take Profit позиции
request.magic = magic_number; // MagicNumber позиции
//--- вывод информации о модификации
PrintFormat("Modify #%I64d %s %s",position_ticket,position_symbol,EnumToString(type));
//--- отправка запроса
if (!OrderSend(request, result)) {
uint answer = result.retcode;
Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
Print(__FUNCTION__, " - код ошибки: ", GetLastError());
ResetLastError();
}
//--- информация об операции
PrintFormat("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order);
}
}
}
Закрытие открытых позиций
И последняя функция в этом уроке – функция закрытия позиций встречными:
//+------------------------------------------------------------------+
//| Модификация значений Stop Loss и Take Profit у открытой позиции |
//+------------------------------------------------------------------+
void closeAll()
{
//--- объявление запроса и результата
MqlTradeRequest request;
MqlTradeResult result;
int total = PositionsTotal(); // количество открытых позиций
//--- перебор всех открытых позиций
for (int i = total-1; i >= 0; i--) {
//--- параметры ордера
ulong position_ticket = PositionGetTicket(i); // тикет позиции
string position_symbol = PositionGetString(POSITION_SYMBOL); // символ
int digits = (int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // количество знаков после запятой
ulong magic = PositionGetInteger(POSITION_MAGIC); // MagicNumber позиции
double volume = PositionGetDouble(POSITION_VOLUME); // объем позиции
ENUM_POSITION_TYPE type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // тип позиции
//--- если MagicNumber совпадает
if (magic == magic_number && position_symbol == _Symbol) {
//--- обнуление значений запроса и результата
ZeroMemory(request);
ZeroMemory(result);
//--- установка параметров операции
request.action = TRADE_ACTION_DEAL; // тип торговой операции
request.position = position_ticket; // тикет позиции
request.symbol = position_symbol; // символ
request.volume = volume; // объем позиции
request.deviation = deviation; // допустимое отклонение от цены
request.magic = magic_number; // MagicNumber позиции
//--- установка цены и типа ордера в зависимости от типа позиции
if (type == POSITION_TYPE_BUY) {
request.price = SymbolInfoDouble(position_symbol, SYMBOL_BID);
request.type = ORDER_TYPE_SELL;
} else {
request.price = SymbolInfoDouble(position_symbol, SYMBOL_ASK);
request.type = ORDER_TYPE_BUY;
}
//--- вывод информации о закрытии
PrintFormat("Close #%I64d %s %s",position_ticket,position_symbol,EnumToString(type));
//--- отправка запроса
if (!OrderSend(request, result)) {
uint answer = result.retcode;
Print(__FUNCTION__, " - результат отправки отрицательный, код ответа:", answer);
Print(__FUNCTION__, " - расшифровка кода ответа:", resultRetcodeText(answer));
Print(__FUNCTION__, " - код ошибки: ", GetLastError());
ResetLastError();
}
//--- информация об операции
PrintFormat("retcode=%u deal=%I64u order=%I64u",result.retcode,result.deal,result.order);
}
}
}
Заключение
Итак, мы написали простой индикаторный советник, который получает сигналы на покупку и продажу и модифицирует тейк-профиты и стоп-лоссы. Также мы добавили в обработчик OnInit различные проверки, которые повышают безопасность торговли и удобство использования эксперта. Мы добавили в советник индикатор и создали простейшие условия для генерации сигналов на вход. В следующем уроке допишем функции для открытия отложенных ордеров и попробуем написать трейлинг-стоп.
На этом все, всем пока!
Скачать советник из урока
С уважением, Дмитрий аkа Silentspec
Tlap.com
Топ Брокеров 2025 по версии TLAP
Центовые счета
Быстрый ввод и вывод
Платформа CopyFx для копирования сигналов
Отличное исполнение
На рынке с 1998 года
Низкие спреды
Быстрый ввод и вывод
Хорошее исполнение
Множество способов пополнения
С 2007 года на рынке
Счета Zero с нулевыми спредами
Система Копи-трейдинга
Хорошее исполнение
Более 500 торговых инструментов
Комиссия на пополнение 0%
Лицензия ЦБ РФ
Удобный ввод и вывод средств
Подходит для крупных трейдеров
Крупнейший форекс дилер в России
Компания – налоговый агент, выплата налогов без участия клиента
Торговля через MetaTrader 5
Форекс, фондовые индексы и нефть
Низкие спреды
Хорошее исполнение
Подходит для торговли советниками
Торговля криптовалютами
Центовые счета со стартовым лотом 0.01
Система копирования сделок Share4You
Низкие спреды
Подходит для новичков
Лучшие на рынке условия для работы с сеточниками и мартингейлом
Исполнение без вмешательства дилинга
Низкие спреды
Трейдинг Forex, CFD и Crypto
Полная прозрачность работы
Множество представительств компании, в том числе в Великобритании
На рынке с 2006 года
| ||
| ||
| ||
| ||
| ||
| ||
|