English 中文 Español Deutsch 日本語 Português
preview
Введение в MQL5 (Часть 8): Руководство для начинающих по созданию советников (II)

Введение в MQL5 (Часть 8): Руководство для начинающих по созданию советников (II)

MetaTrader 5Трейдинг | 21 января 2025, 14:21
713 0
Israel Pelumi Abioye
Israel Pelumi Abioye

Введение

Ранее мы с вами изучили самые основы MQL5. Теперь вы готовы перейти к одной из важнейших задач в алгоритмической торговле: созданию рабочего советника. Как я упоминал в предыдущей статье, в этой серии мы будем использовать проектный подход. Он позволит лучше понять сами идеи, а также того, как эти идеи применяются на практике. К моменту завершения изучения вы будете уверенно понимать, как автоматизировать торговые решения на основе свечных паттернов и предопределенных условий. 

Многие начинающие часто задают вопросы на форумах, и хотя участники сообщества с радостью дают ценные советы, некоторым "самоучкам" бывает сложно реализовать эти ответы в своих программах. Хотя конкретный вопрос может быть решен, часто невозможно дать детальное объяснение всего кода. Даже если к советам прилагаются полезные фрагменты кода, не всегда получается соединить все это воедино. Проектный подход, который представлен в этой статье, поможет найти ответы на такие часто задаваемые вопросы, а также предложит решения, которые можно будет применять для любого советника.

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

В ходе проекта мы обсудим нескольких часто возникающих вопросов, с которыми сталкиваются начинающие трейдеры, особенно те, что часто задаются на форумах MQL5. Вот некоторые из них:

  • Как покупать и продавать в MQL5?
  • Как получить цены открытия и закрытия свечи?
  • Как запретить торговлю на каждом тике?
  • Как ограничить советника так, чтобы он открывал только одну сделку за раз?
  • Как задать торговый период для советника?
  • Как указать дни недели, в которые советник может торговать?
  • Как установить лимиты прибыли и убытка для сделок?

Используя проектный подход, я постараюсь предоставить четкие и практичные ответы на эти вопросы. Это поможет вам реализовать решения непосредственно в вашем эксперте. Такой практический метод помогает не только лучше понять теоретические концепции, но и увидеть их применение в реальных ситуациях. Давайте перейдем не посредственно к делу.


1. Создание проекта

1.1. Псевдокод

Перед тем как начать писать код, важно использовать псевдокод для описания логики нашего советника. О значимости псевдокода мы говорили в предыдущей статье. Так, он помогает четко спланировать и организовать ваши идеи, что ускоряет процесс написания кода. Что касается изучения языка MQL5, проектный подход более эффективен, чем изучение всего сразу. Чем больше проектов вы выполните, тем лучше будете разбираться во всем этом. Вот базовый псевдокод для нашего советника: 

1. Инициализация советника:

  • Установка магического номер для идентификации сделок.
  • Определение времени начала и окончания торговли.
  • Инициализация переменных для хранения цен.

2. На каждом тике:

  • Проверяем текущее время, чтобы определить, находимся ли внутри торгового промежутка.
  • Получение цен открытия и закрытия предыдущего дня.
  • Совершение сделки на продажу, если цена закрытия предыдущего дня была ниже цены открытия (медвежья).
  • Совершение сделки на покупку, если закрытие предыдущего дня было выше открытия (бычий тренд).
  • Проверка, что одновременно открыта только одна сделка.
  • Торговля ведется только с понедельника по четверг.
  • Только одна позиция должна быть открытой.
  • Дневной лимит — две сделки в день.
  • Дневной лимит прибыли.
  • Закрытие сделок в конце торгового периода.

1.2. Импорт необходимых библиотек

Библиотеки — это наборы заранее написанных классов и функций на языке MQL5, которые упрощают написание кода. Они позволяют сосредоточиться на уникальных особенностях вашего советника, а не переписывать стандартные функции. В одной из наших предыдущих статей мы обсуждали концепцию использования Include-файлов. Не переживайте, если вы не до конца понимаете, что они работают. Вы все равно сможете работать по этой статье. Не обязательно знать все сразу, чтобы использовать MQL5. Обучение на основе проектов позволяет двигаться постепенно. Даже если просто следовать инструкциям, вы сможете эффективно использовать приведенные примеры кода и объяснения в своих программах.

Аналогия

Представьте себе работу программиста как огромную, волшебную библиотеку. Эта библиотека наполнена множеством книг. Каждая книга содержит уникальные инструкции и идеи для достижения определенных целей. Тогда библиотеки MQL5 — это редкие книги на полках вашей волшебной библиотеки. В них записаны заранее написанные истории (или код), которые вы можете использовать для создания своего торгового робота. Вместо того чтобы писать все с нуля, вы просто берете нужную книгу и используете ее содержимое, чтобы сэкономить время. 

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

1.2.1. Подключение библиотеки Trade

Для управления торговыми операциями нужно включить библиотеку Trade в советник. Эта библиотека предоставляет набор классов и функций для эффективного управления сделками.

Аналогия

Представьте себе, что у вас есть огромный книжный шкаф, заполненный книгами. Каждая книга — это своеобразный набор инструментов, которые помогают решать определенные задачи. Чтобы создать что-то уникальное, например, классного торгового робота, вы берете с полки нужную книгу. В данном случае вы берете специальную книгу, в которой записаны все инструменты для управления торговыми операциями. В MQL5 такие инструменты хранятся в библиотеке Trade. Вам больше не нужно писать эти инструкции самостоятельно — все уже готово для использования!

Такие готовые инструменты можно сразу включить в свой проект с помощью следующей строки кода:

#include <Trade/Trade.mqh> // Include the trade library for trading functions

Из этой сроки робот поймет, что нужно взять с полки книгу под названием Trade.mqh. Эта книга содержит всевозможные полезные инструменты, которые позволят роботу открывать, модифицировать и закрывать сделки. При этом использование готовых инструментов и функций сэкономит вам кучу времени.

Пример:

#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;
//magic number
int MagicNumber = 103432;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// Set the magic number for the EA's trades
   trade.SetExpertMagicNumber(MagicNumber);

// Return initialization success
   return(INIT_SUCCEEDED);
  }

1.2.2.1. Создание экземпляра класса CTrade

Класс CTrade, входящий в библиотеку Trade, содержит в себе методы для выполнения торговых операций. Создание экземпляра этого класса позволит вам использовать его функции для управления вашими сделками.

Аналогия

Снова представьте себе библиотеку. Вы уже выбрали нужную книгу (Trade.mqh), в которой есть все инструкции по торговле. Класс CTrade — особый персонаже в этой книге, некий супергерое, который знает все о торговле. Чтобы использовать этого супергероя, вам нужно ввести его в свою историю. Это можно сделать, создав экземпляр класса CTrade. Таким образом вы приглашаете супергероя в ваш проект.

Пригласить супергероя можно с помощью простой строки кода:

CTrade trade;

В этом коде CTrade — супергерой, а trade — ваше приглашение. Создавая этот экземпляр под названием trade, вы сообщаете: «Супергерой CTrade, пожалуйста, приди в мой проект и помоги мне управлять сделками!» 

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

1.2.2.2. Установка магического числа

Магическое число — уникальный идентификатор, присваиваемый каждой сделке, которую разметил ваш советник. Этот идентификатор позволяет отслеживать и управлять сделками, позволяя советнику свои собственные сделки от тех, которые размещены вручную или другими экспертами. Представим, что каждая книга на полке — это сделка. Иногда у вас может быть несколько книг (сделок), которые выглядят одинаково, но на каждой из них есть специальная наклейка с уникальным номером. Эта наклейка поможет вам быстро находить и упорядочивать книги.

Такая уникальная наклейка — магическое число для вашего советника. Именно по ней можно безошибочно идентифицировать каждую сделку, которую размещает ваш советник.

Пример:

int MagicNumber = 103432;
В контексте класса CTrade магическое число задается следующим образом:
trade.SetExpertMagicNumber(MagicNumber);

Эта строка гарантирует, что каждая сделка, размещенная экземпляром сделки, будет иметь указанный магический номер.

Чтобы использовать готовый код, реализующие сложные задачи, необходимо импортировать нужные библиотеки в MQL5. Библиотеки похожи на наборы инструментов, которые экономят время и силы. Использование этих библиотек позволит вам сосредоточиться на отличительных особенностях вашего советника, а не писать весь код с нуля.


2. Получение и анализ свечных данных

Для успешной торговли важно уметь получать и анализировать цены открытия и закрытия свечей. Эти данные позволяют понимать рыночные тенденции и принимать решение — покупать или продавать. Чтобы понимать картину движения рынка, необходимо иметь доступ к ценам открытия и закрытия свечей. Используя функции CopyOpen и CopyClose, можно получать данные о свечах. С их помощью можно определить, является ли свеча бычьей (цена закрытия выше открытия) или медвежьей (цена закрытия ниже открытия).

2.1. Функции CopyClose и CopyOpen в MQL5

Для получения цен закрытия и открытия свечей для заданного символа и таймфрейма используются функции CopyClose и CopyOpen. Эти функции позволяют выбирать данные в зависимости от ваших потребностей.

2.1.1. Вызов по начальному индексу и количеству требуемых элементов

Аналогия

Представьте себе книжную полку, где каждая книга — это свеча, содержащая данные о цене открытия и закрытия. Если вы хотите начать копировать книги с определенного места, нужно считать их слева направо. Например, можно просто отсчитать третью книгу от начала.

Далее вы выбираете количество книг, которые хотите скопировать начиная с этой позиции. Например, если вы решите взять пять книг, начиная с третьей, у вас на руках окажется в общей сложности пять книг. Так же, как мы выбираем первую книгу на полке и количество книг, которые нужно взять начиная с этой точки, этот метод использует функцию CopyClose, чтобы вы могли указать начальную позицию в ценовых данных и количество элементов (свечей), которые нужно скопировать.

Синтаксис:

CopyClose(symbol_name, timeframe,  start_pos, count, close_array);
CopyOpen(symbol_name, timeframe,  start_pos, count, open_array);

Пример:

double close_prices[];
double open_prices[]; 
CopyClose(_Symbol, PERIOD_D1, 2, 5, close_prices); // Copy the close prices of 5 daily candlesticks starting from the 3rd candlestick
CopyOpen(_Symbol, PERIOD_D1, 2, 5, open_prices); // Copy the open prices of 5 daily candlesticks starting from the 3rd candlestick

В этой аналогии ценовые данные для конкретного символа и таймфрейма представлены книжной полкой, которая обозначена именем символа и таймфреймом. Решение взять пять книг из исходной позиции указывается значением count. Начальная позиция (start_pos) — указание третьей книги на полке. Вы держите выбранные книги в руках и сохраняете скопированные данные в целевом массиве (close_array).


2.1.2. Вызов по начальной дате и количеству элементов

Аналогия

Снова представим ценовые данные как книжную полку, где каждая книга — это свеча, содержащая дневные данные свечей. Чтобы начать копировать цены закрытия с определенной даты, находим на полке книгу, соответствующую этой дате. Например, можно выбрать дату «1 июня» — это будет начальная дата, с которой будут скопированы данные.

Далее вы выбираем количество книг, которые нужно взять, то есть количество цен закрытия, которые нужно скопировать начиная с этой даты. Если выбрать, например, пять книг начиная с «1 июня», вы скопируете пять книг, т.е. цен закрытия. Метод похож на выбор начальной книги на полке и указанию количества книг, которые нужно взять с этой точки. Здесь функция CopyClose используется, чтобы указать начальную дату в ценовых данных и количество элементов (свечей), которые нужно скопировать.

Точно ак же вы можете использовать функцию CopyOpen, чтобы получить цены открытия. Выбираете нужную начальную дату и указываете количество значений цен открытия, которые нужно скопировать.

 Синтаксис:

CopyClose(symbol_name, timeframe,  timeframe, count, close_array[]);
CopyOpen(symbol_name, timeframe,  timeframe, count, open_array[]);

Пример:

close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00';  // Starting from June 1st, 2023
// Copy the close prices of 5 daily candlesticks starting from June 1st, 2023
CopyClose(_Symbol, PERIOD_D1, start_date, 5, close_prices);
// Copy the open prices of 5 daily candlesticks starting from June 1st, 2023 CopyOpen(_Symbol, PERIOD_D1, start_date, 5, open_prices);

В этой аналогии ценовые данные для конкретного символа и таймфрейма представлены книжной полкой, которая обозначена именем символа и таймфреймом. Выбор книги на полке, которая соответствует «1 июня», — это указание начальной даты (start_time), а выбор пяти книг с этой начальной даты — указание количества элементов (count). Так же, как вы держите выбранные книги в руках, скопированные данные сохраняются в целевых массивах (close_array и open_array).

Таким образом, функции CopyClose и CopyOpen позволяют удобно и интуитивно понятно выбирать определенные ценовые данные начиная с выбранной даты.

2.1.3. Обращение по начальной и конечной датам требуемого интервала

Аналогия

Представьте, что каждая книга на вашей книжной полке — это свеча, показывающая цены открытия и закрытия за определенный период времени. Если вы хотите получить цены закрытия за определенный период времени, нужно найти книги из требуемого периода.

В качестве иллюстрации предположим, что вы хотите скопировать цены закрытия с 1 по 5 июня. Вам нужно найти книгу, которая соответствует начальной точке 1 июня и конечной точке 5 июня. Чтобы получить цены закрытия за этот период времени, нужно выбрать все книги, опубликованные между этими двумя датами.

Для книг мы выбираем первую и последнюю книгу из диапазона, а здесь - начальную и конечную дату для запроса данных. Аналогичным образом вы можете использовать функцию CopyOpen для получения цен открытия.

Синтаксис:
CopyClose( symbol_name, timeframe, start_time, stop_time, close_array[]);
CopyOpen(symbol_name, timeframe, start_time, stop_time, open_array[]);
Пример:
double close_prices[];
double open_prices[];
datetime start_date = D'2023.06.01 00:00'; // Starting from June 1st, 2023
datetime end_date = D'2023.06.05 00:00'; // Ending on June 5th, 2023
// Copy the close prices from June 1st to June 5th
CopyClose(_Symbol, PERIOD_D1, start_date, end_date, close_prices);
// Copy the open prices from June 1st to June 5th
CopyOpen(_Symbol, PERIOD_D1, start_date, end_date, open_prices);

В этой аналогии ценовые данные для конкретного символа и таймфрейма представлены книжной полкой, которая обозначена именем символа и таймфреймом. В нашем примере начальная дата (start_time) — это 1 июня, а конечная дата (stop_time) — 5 июня. Так же, как вы держите выбранные книги в руках, скопированные данные сохраняются в целевых массивах (close_array и open_array).

Благодаря этой аналогии становится понятно, как функции CopyClose и CopyOpen помогают упорядоченно и интуитивно получать данные с вашей "книжной полки" цен в диапазоне с указанной начальной и конечной датой.

Преобразование строки во время

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

Аналогия

Представьте библиотеку, в которой полки расположены в соответствии с датой и временем суток. Чтобы прочитать нужные книги (или получить данные) в определенное время суток, нужно знать день и время. Ежедневно обновлять даты для получения нужных книг — мягко говоря, не самый эффективный вариант. Чтобы упростить этот процесс (извлечение данных), представьте, что строковые значения времени — это метки, которые вы прикрепляете к нужным часам.

Пример:

// Declaring time strings
string start_time_str = "00:00";  // Start time
string end_time_str = "20:00";    // End time

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {

// Converting time strings to datetime values
   datetime start_time = StringToTime(start_time_str);
   datetime end_time = StringToTime(end_time_str);

   Print("start_time: ", start_time,"\nend_time: ",end_time);

  }

Объяснение:

// Объявление строковых значений времени string start_time_str = "00:00"; // Время начала string end_time_str = "20:00"; // Время окончания

  • Здесь метки присвоены конкретным часам, которые вы хотите прочитать. Это упрощает процесс извлечения данных.

// Преобразование строк времени в значения datetime start_time = StringToTime(start_time_str); datetime end_time = StringToTime(end_time_str);

  • При использовании функции StringToTime вы получаете указание на конкретную точку во времени. Это верно независимо от даты. Таким образом, не придется беспокоиться о смене даты каждый день. Благодаря тому, что start_time и end_time автоматически ссылаются на текущее врем, легко получить нужные данные без необходимости ручного обновления.

Выводимая информация:

Рисунок 1. Результат работы кода


Реализация в советнике

Пример:

#include <Trade/Trade.mqh>

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day


// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {

         Comment("Its a sell");

        }

     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         Comment("Its a buy");
        }
     }

  }

Объяснение:

Для начала с помощью #include <Trade/Trade.mqh> мы подключаем библиотеку Trade для работы с торговыми функциями. Затем создаtv экземпляр класса CTrade — CTrade trade;, который обеспечивает выполнение торговых операций. Также мы определяем уникальный идентификатор для сделок нашего советника — int MagicNumber = 103432;.

Далее объявляем массивы для хранения цен открытия и закрытия предыдущего дня, цен открытия и закрытия первого бара на таймфрейме H1 текущего дня, а также данных о ценах открытия и закрытия баров на H1. Эти массивы будут содержать полученные данные о ценах, которые мы будем анализировать для принятия торговых решений. Параметры времени начала и окончания торговли, а также время первой сделки задаются в виде строковых параметров (string), которые посднее будут преобразованы в формат datetime. В функции OnInit мы задаем магический номер эксперта для идентификации наших сделок. Также в OnTick мы преобразуем значение времени из формата sting в формат datetime и получаем текущее время. Затем с помощью функций CopyClose и CopyOpen копируем цены закрытия и открытия дневных и H1-свечей в соответствующие массивы. Функция ArraySetAsSeries устанавливает порядок копирования массивов справа налево, чтобы самые последние данные находились по индексу 0.

Наконец, анализируем полученные данные о свечах, чтобы определить настроение рынка. Если последняя дневная свеча медвежья, проверяются определенные условия для открытия сделки на продажу. Аналогично, если последняя дневная свеча бычья, проверяются условия для открытия сделки на покупку. Функция Comment выводит принятое торговое решение в виде комментария на графике. Этот пример кода создает структуру для извлечения и анализа цен открытия и закрытия дневных и часовых свечей, а затем использует этот анализ для определения направления сделки.


3. Реализация выполнения торговых операций

3.1 Как совершать покупки и продажи в MQL5

В этом разделе рассматриваются основные шаги для выполнения ордеров на покупку и продажу в MQL5, включая определение уровней стоп-лосса (SL) и тейк-профита (TP). Для этого используется класс CTrade из библиотеки Trade.mqh, который предоставляет базовые методы для выполнения торговых операций.

Размещение ордеров на покупку и продажу:

Для открытия ордеров на покупку и продажу используем методы Buy и Sell класса CTrade. Благодаря этим методам мы можем выполнять сделки с минимальным количеством кода. Представьте класс CTrade как специальную полку в библиотеке, посвященную торговым функциям. Когда нужно выполнить сделку, достаточно просто взять нужную "книгу" (метод) с полки и применить ее.

Установка стоп-лосса и тейк-профита:

Уровни TP и SL являются ключевыми для управления рисками. Тейк-профит (TP) определяет желаемую прибыль, а стоп-лосс (SL) — максимально допустимый убыток. Оба уровня указываются в виде ценовых точек относительно текущей рыночной цены. Можно представить уровни TP и SL как закладки в книге: SL указывает, где вы остановитесь в случае, если что-то пойдат не так, а TP — где вы заберете свою награду, если все пойдет по плану.

Пример:
#include <Trade/Trade.mqh> // Include the trade library for trading functions

// Create an instance of the CTrade class for trading operations
CTrade trade;

// Unique identifier for the EA's trades
int MagicNumber = 103432;

// Arrays to store the previous day's open and close prices
double daily_close[];
double daily_open[];

// Arrays to store the first H1 bar's open and close prices of the day
double first_h1_price_close[];

// Arrays to store H1 bars' open and close prices
double H1_price_close[];
double H1_price_open[];

// Strings to define the trading start and end times and the first trade time
string start = "00:00";
string end = "20:00";
string firsttrade  = "02:00";

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
// Convert time strings to datetime format
   datetime start_time = StringToTime(start); // Convert start time string to datetime
   datetime end_time = StringToTime(end); // Convert end time string to datetime
   datetime current_time = TimeCurrent(); // Get the current time
   datetime first_tradetime = StringToTime(firsttrade); // Convert first trade time string to datetime

// Copy daily close and open prices
   CopyClose(_Symbol, PERIOD_D1, 1, 1, daily_close); // Copy the close price of the previous day
   CopyOpen(_Symbol, PERIOD_D1, 1, 1, daily_open); // Copy the open price of the previous day

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(daily_close, true); // Set daily_close array as series
   ArraySetAsSeries(daily_open, true); // Set daily_open array as series

// Copy close and open prices for the first H1 bar of the day
   CopyClose(_Symbol, PERIOD_H1, start_time, 1, first_h1_price_close); // Copy the close price of the first H1 bar

// Copy close prices for the latest 5 H1 bars
   CopyClose(_Symbol, PERIOD_H1, 0, 5, H1_price_close); // Copy the close prices of the latest 5 H1 bars
   CopyOpen(_Symbol, PERIOD_H1, 0, 5, H1_price_open); // Copy the open prices of the latest 5 H1 bars

// Set the arrays to be copied from right to left (latest to oldest)
   ArraySetAsSeries(H1_price_close, true); // Set H1_price_close array as series
   ArraySetAsSeries(H1_price_open, true); // Set H1_price_open array as series

// Get the symbol point size
   double symbol_point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);

// Get the current Bid price
   double Bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Calculate the stop loss and take profit prices for sell
   double tp_sell = Bid - 400 * symbol_point;
   double sl_sell = Bid + 100 * symbol_point;

// Get the current Ask price
   double Ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);

// Calculate the stop loss and take profit prices for buy
   double tp_buy = Ask + 400 * symbol_point;
   double sl_buy = Ask - 100 * symbol_point;

// If the last daily bar is bearish
   if(daily_close[0] < daily_open[0])
     {
      // Check specific conditions for a sell trade
      if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the sell trade
         trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
         Comment("It's a sell");
        }
     }

// If the last daily bar is bullish
   if(daily_close[0] > daily_open[0])
     {
      // Check specific conditions for a buy trade
      if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
        {
         // Execute the buy trade
         trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
         Comment("It's a buy");
        }
     }
  }

Объяснение:

  • С помощью #include подключаем торговую библиотеку.
  • Создаем экземпляр класса CTrade.
  • На основе текущих цен Bid и Ask, а также размера пункта символа, рассчитываем уровни стоп-лосса (SL) и тейк-профита (TP).
  • Используем методы Buy и Sell для размещения ордеров с заданными уровнями SL и TP.

Параметры методов trade.Buy и trade.Sell:

  • lotsize — объем сдделки.
  • _Symbol — символ, по которому будет выполнена операция (например, EURUSD).
  • Ask/Bid — текущая цена Ask для сделок на покупку или Bid для сделок на продажу.
  • sl — уровень стоп-лосс.
  • tp — уровень тейк-профит.

В этой статье мы обсудили, как получить определенные данные о свечах, а именно, цены открытия и закрытия, и как размещать ордера на покупку и продажу. Это основы интеграции торговых операций в собственную программу. Однако из-за обработки событий в функции OnTick ордера могут отправляться с каждым новым тиком.

Поэтому следующий важный шаг — ограничить возможности советника, чтобы он совершал только одну сделку за раз, чтобы контролировать риск и не допускать чрезмерно активной торговли. Для этого необходимо реализовать логику, позволяющую определить, есть ли уже какие-либо открытые позиции, прежде чем совершать новую сделку. Эта стратегия гарантирует, что сделки совершаются только при необходимости.


Как запретить торговлю на каждом тике:

Обработка событий в OnTick может привести к открытию новых позиций на каждом тике, что увеличивает риск избыточной торговли. Нужно убедиться, что сделки совершаются только при необходимости — для этого проверяем новые бары и открытые позиции. Представьте, что каждый новый бар — это новая книга, добавленная в нашу библиотеку. Мы читаем (торгуем) только тогда, когда добавляется новая книга (бар), и при этом убеждаемся, что никакая другая книга (сделка) не читается (не активна) в данный момент.

Пример:

// Flag to indicate a new bar has formed
bool newBar;

// Variable to store the time of the last bar
datetime lastBarTime;

// Array to store bar data (OHLC)
MqlRates bar[];

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

// Check for a new bar
   CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // Copy the latest 3 H1 bars
   if(bar[0].time > lastBarTime)  // Check if the latest bar time is greater than the last recorded bar time
     {
      newBar = true; // Set the newBar flag to true
      lastBarTime = bar[0].time; // Update the last bar time
     }
   else
     {
      newBar = false; // Set the newBar flag to false
     }

// If a new bar has formed
   if(newBar == true)
     {
      // If the last daily bar is bearish
      if(daily_close[0] < daily_open[0])
        {
         // Check specific conditions for a sell trade
         if(H1_price_close[2] >= first_h1_price_close[0] && H1_price_close[1] < first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the sell trade
            trade.Sell(1.0, _Symbol, Bid, sl_sell, tp_sell); // Replace with your lot size
            Comment("It's a sell");
           }
        }

      // If the last daily bar is bullish
      if(daily_close[0] > daily_open[0])
        {
         // Check specific conditions for a buy trade
         if(H1_price_close[2] <= first_h1_price_close[0] && H1_price_close[1] > first_h1_price_close[0] && current_time >= first_tradetime)
           {
            // Execute the buy trade
            trade.Buy(1.0, _Symbol, Ask, sl_buy, tp_buy); // Replace with your lot size
            Comment("It's a buy");
           }
        }

     }

  }

Объяснение:

bool newBar;

  • Чтобы определить, сформировался ли новый бар (свеча), в этой строке объявляется логическая переменная с именем newBar.

datetime lastBarTime;

  • Чтобы сохранить время последнего обработанного бара, в этой строке объявляется переменная lastBarTime типа datetime.

MqlRates bar[];

  • В этой строке объявляется массив bar типа MqlRates. Данный массив будет использоваться для хранения данных баров, а именно, цен открытия, максимума, минимума и закрытия (OHLC).

CopyRates(_Symbol, PERIOD_H1, 0, 3, bar); // Copy the latest 3 H1 bars

  • Здесь последние три часовых бара (H1) для текущего символа (_Symbol) копируются в массив баров с помощью функции CopyRates.

if(bar[0].time > lastBarTime)

  • Эта строка определяет, превышает ли время последнего бара (bar[0].time) время предыдущего бара. Если да, то появился новый бар.

newBar = true;

  • Флаг newBar устанавливается в значение true, если сформировался новый бар.

lastBarTime = bar[0].time;

  • Время последнего бара обновляется в переменной lastBarTime.

else {

newBar = false;

        }

  • Если время последнего бара не больше lastBarTime, флаг newBar устанавливается в значение false.

if(newBar == true)

{

 // do this

}

  • Если образовался новый бар, действуем.

Этот код гарантирует, что блок кода внутри оператора if выполняется только тогда, когда формируется новый бар, а не на каждом тике. Для этого проверяется, превышает ли время самого последнего бара время последнего записанного бара. На основании этой проверки устанавливается флаг newBar, который сигнализирует о формировании нового бара. Таким образом, блок кода внутри if(newBar == true) выполняется только один раз для каждого нового бара, что улучшает работу советника и исключает ненужные действия.

3.2 Ограничение для советника: только одна открытая позиция за раз

Теперь, когда выполнение торговых операций инициируется только при формировании нового бара, можно еще улучшить работу советника, ограничив количество открытых позиций. Это гарантирует, что новый ордер будет открыт только в том случае, если у советника нет других активных сделок.

Логика работы:

  • Проверка наличия открытых позиций — прежде чем совершить новую сделку, проверяем, есть ли открытые позиции. Советник разместит сделку, если нет открытых позиций.
  • Запрет сделок на каждом тике — сделки будут совершаться только тогда, когда они действительно необходимы, для этого добавим проверку на наличие новых баров и открытых позиций.

Представьте, каждый новый бар как книгу, добавленную в нашу библиотеку. Мы принимаем решение о чтении (торговле) только тогда, когда добавляется новая книга (бар), и только если в данный момент не читается никакая другая книга (нет сделок).

Пример:

// Initialize the total number of positions being held
int totalPositions = 0;
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)
     {
      // Increment the total positions count
      totalPositions++;
     }
  }

Объяснение:

int totalPositions = 0;

  • В этой строке объявляется целочисленная переменная totalPositions, которая устанавливается равной нулю. Количество открытых позиций, которые в данный момент удерживает советник, будет подсчитываться с использованием этой переменной.

for(int i = 0; i < PositionsTotal(); i++)

  • В цикле идет перебор по всем открытым позициям. Общее количество открытых позиций в торговом терминале возвращает функция PositionsTotal().
  • i — счетчик цикла, начинается с 0 и на каждой итерации увеличивается на единицу. Цикл будкет продолжаться пока i меньше PositionsTotal().

  ulong ticket = PositionGetTicket(i);

  • В этой строке извлекается номер тикета для позиции с индексом i. Номер тикета для позиции по указанному индексу возвращает функция PositionGetTicket(i).
  • ticket — переменная типа ulong, которая хранит номер тикета текущей проверяемой позиции.

if(PositionGetInteger(POSITION_MAGIC) == MagicNumber)

  • Оператор if определяет, совпадают ли магическое число, назначенное советнику (MagicNumber), и магическое число текущей позиции.
  • PositionGetInteger(POSITION_MAGIC) — функция, которая извлекает значение указанного свойства типа integer (в данном случае магического числа) для текущей позиции.
  • Magic Number — предопределенная константа, которая служит уникальным торговым идентификатором данного советника. Гарантирует, что учитываются только позиции, открытые этим советником.

totalPositions++;

  • Эта строка увеличивает счетчик totalPositions на 1, если магическое число текущей позиции совпадает с MagicNumber. По сути, это подсчет количества позиций, открытых данным конкретным советником.

  Этот блок кода используется для подсчета количества позиций, удерживаемых в данный момент советником. Как это работает:

  • Инициализация счетчика (totalPositions) на ноль.
  • Перебор всех открытых позиций с использованием цикла for.
  • Получение тикета для каждой позиции.
  • Проверка соответствия магического числа позиции магическому числу советника.
  • Если есть совпадение, увеличивается счетчик totalPositions.

3.3 Ограничение советника двумя сделками в день

Ограничим количество сделок, совершаемых экспертом за один день, максимум двумя. Это помогает избежать избыточной торговли и уменьшает риск.

Реализация

Здесь мы фильтруем историю торговли за текущий день с помощью функции HistorySelect. Далее используем уникальное магическое число советника, чтобы просмотреть выбранную историю торговли и подсчитать количество сделок, совершенных алгоритмом. Учитываем только сделки на вход, для этого проверяем тип каждой сделки. Советник не будет открывать новые сделки до конца дня, если будет две сделки на вход.

Представьте, что каждый день — это полка в библиотеке. На этой полке можно разместить не более двух новых книг. Перед добавлением книги (открытием сделки) проверяем, сколько книг уже размещено. Если на полке уже есть две книги, новые книги не добавляются, пока не начнется следующий день. Это помогает поддерживать порядок в библиотеке и предотвращает чрезмерную торговлю.

Пример:

// Select the trading history within the specified time range
bool success = HistorySelect(start_time, end_time); // Select the trading history

// Initialize the total number of trades for the day
int totalDeal = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal's magic number matches
      if(HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
        {
         // Check if the deal was an entry
         if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
           {
            // Increment the total deal count
            totalDeal++;
           }
        }
     }
  }

Объяснение:

  • HistorySelect — торговой истории за день.
  • totalDeal — счетчик для учета сделок.
  • Цикл по истории — цикл по истории для подсчета сделок.
  • Проверка магического числа — проверка магика, чтобы определить, принадлежат ли сделки советнику.
  • Если найдена сделка на вход, увеличиваем счетчик.

Благодаря этому коду советник будет совершать не более двух сделок в день, снижая риск чрезмерной торговли.

3.4 Ограничение прибыли или убытка за день

В этом разделе ограничим параметры дневной общей прибыли или убытка советника.

// Initialize the total profit
double totalProfit = 0;
long dealsMagic = 0;
double profit = 0;
if(success)
  {
   for(int i = 0; i < HistoryDealsTotal(); i++)
     {
      // Get the ticket number for the deal
      ulong ticket = HistoryDealGetTicket(i);

      // Check if the deal was an entry
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_IN)
        {
         // Get the magic number of the deal
         dealsMagic = HistoryDealGetInteger(ticket, DEAL_MAGIC);
        }

      // Check if the deal was an exit
      if(HistoryDealGetInteger(ticket, DEAL_ENTRY) == DEAL_ENTRY_OUT)
        {
         // Get the profit of the deal
         profit = HistoryDealGetDouble(ticket, DEAL_PROFIT);

         // Check if the magic number matches
         if(MagicNumber == dealsMagic)
           {
            // Add the profit to the total profit
            totalProfit += profit;
           }
        }
     }
  }

Объяснение:

  • HistorySelect — торговой истории за день.
  • totalProfit — счетчик для отслеживания общей прибыли ли убытка за день.
  • Перебирает все сделки и суммирует их прибыль и убытки.
  • Проверка сделок на вход — определяем магическое число каждой сделки.
  • Проверяем сделки выхода, рассчитываем прибыль по каждой закрытой сделке.
  • Добавление в общий результат — аккумулируем прибыль или убыток для сделок с совпадающим магическим числом.

Этот подход снижает риски, останавливая дополнительные сделки после достижения заранее установленного порога прибыли или убытка за день. Это дает лучший контроль над торговлей и помогает управлять рисками.

3.5 Закрытие всех открытых позиций в указанное время

В этом разделе рассмотрим, как реализовать такую функцию в MQL5. Это снижает риск непредвиденных рыночных колебаний, поскольку все сделки закрываются на ночь или во время низкой активности. Можно сравнить это с закрытием библиотеки в конце рабочего дня. Это позволяет убедиться, что все позиции будут закрыты в назначенное время, так же, как библиотекарь следит за тем, чтобы все книги были возвращены и учтены в конце дня.

// Close trades at the specified end time
for(int i = 0; i < PositionsTotal(); i++)
  {
// Get the ticket number for the position
   ulong ticket = PositionGetTicket(i);

// Check if the position's magic number matches and if it's the end time
   if(PositionGetInteger(POSITION_MAGIC) == MagicNumber && current_time == end_time)
     {
      // Close the position
      trade.PositionClose(ticket);
     }
  }

Объяснение:

Здесь извлекаем номер тикета каждой открытой позиции, перебирая их все. Затем проверяем, что магическое число позиции соответствует магическому числу советника и что текущее время соответствует времени закрытия. Если ребования выполнены, позиции закрываются с помощью функция PositionClose класса CTrade.

3.6 Указание дней недели, в которые может торговать советник

Необходимо получить текущий день недели и сравнить его с правилами торговли. Так можно настроить советник, чтобы он торговал только в определенные дни недели.

Пример:
//getting the day of week and month
MqlDateTime day; //Declare an MqlDateTime structure to hold the current time and date
TimeCurrent(day); // Get the current time and fill the MqlDateTime structure
int week_day = day.day_of_week; //Extract the day of the week (0 = Sunday, 1 = Monday, ..., 6 = Saturday)

//getting the current month
MqlDateTime month; //Declare a structure to hold current month information
TimeCurrent(month); //Get the current date and time
int year_month = month.mon; //Extract the month component (1 for January, 2 for February, ..., 12 for December)

if(week_day == 5)
  {
   Comment("No trades on fridays", "\nday of week: ",week_day);
  }
else
   if(week_day == 4)
     {
      Comment("No trades on Thursdays", "\nday of week: ",week_day);
     }
   else
     {
      Comment(week_day);
     }

Объяснение:

Нужно определить текущий день недели и сравнить его с заданным расписанием, чтобы настроить дни, в которые советник может торговать. Для хранения текущей даты и времени мы сначала объявляем структуру MqlDateTime. Сначала мы заполняем эту структуру текущим месяцем и днем недели с помощью функции TimeCurrent().

Далее проверяем день недели с помощью условных операторов. Если текущий день пятница (значение 5), выведем сообщение, что по пятницам сделки не разрешены. Если четверг (значение 4), по четвергам сделки также запрещены. Торговля разрешена во все остальные дни — в этом случае просто отображается текущий день недели.

Аналогия

Каждый день недели — это как определенный тип книги в библиотеке.

  • Пятница (5) — запрещено торговать в пятницу, так же как библиотека не выдает детективные романы по пятницам.
  • Четверг (4) — торговать нельзя в четверг, подобно тому, как научная фантастика не выдается в этот день.
  • Остальные дни — торговля разрешена, как и выдача всех остальных жанров книг.


Заключение

В данной статье использован проектный подход к изучению основ алгоритмической торговли в MQL5. Такой метод подходит даже для начинающих программистов. Мы изучили, как выполнять фундаментальные задачи: покупка и продажа в MQL5, получение цены открытия и закрытия свечей, применение стратегий, предотвращающих торговлю на каждом тике. Мы также обсудили, как контролировать важные аспекты автоматической торговли, такие как ограничение советника одной сделкой за раз, определение торговых дней и периодов, а также установление лимитов прибыли и убытков.

Если у вас остались вопросы по темам, рассмотренным в статье, не стесняйтесь задавать их. Я помогу разобраться в нюансах реализации кода или связи этих концепций с различными торговыми стратегиями. Мы также сможем вместе обсудить, как лучше понять и использовать алгоритмический трейдинг в MQL5. Помните: освоение любого языка программирования, включая MQL5, — это путь к успеху в алгоритмической торговле. Это естественно, что невозможно понять все сразу. Вы постепенно расширите знания и умения, используя проектное обучение, в ходе которого вы применяете концепции к реальным проектам, подобному тем, которые мы изучали. Каждый проект служит ступенькой, которая постепенно повышает ваши знания и навыки.


Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/15299

Прикрепленные файлы |
MQL5Project2.mq5 (10.02 KB)
Нейросимвольные системы в алготрейдинге: Объединение символьных правил и нейронных сетей Нейросимвольные системы в алготрейдинге: Объединение символьных правил и нейронных сетей
Статья рассказывает об опыте разработки гибридной торговой системы, объединяющей классический технический анализ с нейронными сетями. Автор подробно разбирает архитектуру системы — от базового анализа паттернов и структуры нейросети до механизмов принятия торговых решений, делясь реальным кодом и практическими наблюдениями.
Функции активации нейронов при обучении: ключ к быстрой сходимости? Функции активации нейронов при обучении: ключ к быстрой сходимости?
В данной работе представлено исследование взаимодействия различных функций активации с алгоритмами оптимизации в контексте обучения нейронных сетей. Особое внимание уделяется сравнению классического ADAM и его популяционной версии при работе с широким спектром функций активации, включая осциллирующие функции ACON и Snake. Используя минималистичную архитектуру MLP (1-1-1) и единичный обучающий пример, производится изоляция влияния функций активации на процесс оптимизации от других факторов. Предложен подход к контролю весов сети через границы функций активации и механизма отражения весов, что позволяет избежать проблем с насыщением и застоем в обучении.
От начального до среднего уровня: Переменные (III) От начального до среднего уровня: Переменные (III)
Сегодня мы рассмотрим, как использовать переменные и константы, предопределенные языком MQL5. Кроме того, мы проанализируем еще один особый тип переменных: функции. Умение правильно работать с этими переменными может определить разницу между работающим и неработающим приложением. Для того, чтобы понять представленное здесь, необходимо разобраться с материалом, который был рассмотрен в предыдущих статьях.
Пользовательский индикатор: Отображение сделок входа, выхода и разворота позиции на неттинговых счетах Пользовательский индикатор: Отображение сделок входа, выхода и разворота позиции на неттинговых счетах
В данной статье мы рассмотрим нестандартный способ создания индикатора в MQL5. Вместо того, чтобы фокусироваться на тренде или графическом паттерне, нашей целью будет управление собственными позициями, включая частичные входы и выходы. Мы будем активно использовать динамические матрицы и некоторые торговые функции, связанные с историей сделок и открытыми позициями, чтобы указать на графике, где осуществились данные сделки.