Всем привет! Сегодня мы продолжим работать над нашим советником, добавим в него простейшие торговые сигналы по индикатору и модернизируем различные функции советника для удобства его эксплуатации.
Предыдущие уроки
- 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