четверг, 10 мая 2012 г.

Пишем советник для Forex. Написания советника для рынка Форекс в MQL4


В предыдущем посте мы рассмотрели как написать индикатор для рынка Форекс. Хоть он и не был полезен нам как трейдерам, он был весьма полезен нам как новичкам - программистам.
Индикаторы, в общем, очень важны для технического анализа рынков и попыток предсказания будущее двежение цены.
Но для того, чтобы использовать индикаторы необходимо сидеть перед монитором с широко открытыми глазами и вручную совершать сделки.

Если Вы вдруг устанете, решите выпить чашечку чая или взять небольшой отпуск, то Вам придётся рассмотреть одну из следующих опций:
Можно кого-нибудь нанять, чтобы он следил за терминалом и звонил Вам каждые 5 минут, чтобы рассказать, что происходит. Если этот человек профессионал, то Вы будете тратить на него всю свою прибыль. А если он новичок, то Вы потратете на него весь свой каптал.
Вторая опция — автоматизировать свою торговлю.
Именно для этого нужны советники - они же торговые эксперты - они же механические торговые системы (МТС).

Примечание: не путать с роботами - далее я объясню почему...

Можно сказать, что Советник — это программа, написанная на MQL4 (ведь мы изучаем MQL4, верно?), которая использует Ваши любимые индикаторы, торговые методы для автоматизации Ваших торгов.
Он покупает, продаёт и модифицирует ордера за Вас.

Сегодня  я попробую подробно описать процесс написания советника для рынка Форекс в MQL4.

Первые два шага:

Шаг 1:

Если Вы ещё не открыли свой MetaEditor, то пора бы это сделать. Затем выберите Файл->Создать (или просто нажмите CTRL+N).
Появится помощник (как при создании индикатора).
На этот раз надо выбрать пункт «Советник».

Шаг 2:

После нажатия кнопки «Next», Вы окажетесь в окошке общих параметров советника.
Помощник позволяет Вам назначить свойства Вашего советника и задать внешние переменные, которые будут использоваться в нём.
На этом шаге можно задать следующие свойства:

1 — Имя. Название Вашего советника. Мы назовём его My_first_EA.
2 — Автор. Имя автора. Введите какое либо имя.
3 — Ссылка. Ссылка на Ваш сайт или e-mail.
4 — Параметры:
Это список внешних (extern) параметров. Это те параметры, которые пользователь сможет менять из окошка настроек советника.
Для добавления нового параметра нажмите кнопку «Добавить». Нажатие этой конпки приведёт к появлению новой записи в списке внешних переменных. Каждая переменная имеет три поля:
Имя: двойной щелчок по этому полю позволит Вам задать имя (идентификатор) переменной.
Тип: двойной щелчок по этому полю позволит Вам задать тип переменной.
Начальное значение: двойной щелчок по этому полю позволит Вам задать начальное значение переменной.
Последнее поле является опциональным, что означает, что его можно оставить пустым.

В нашем случае, мы добавим три переменные:
Рис. 2 — Помощник. 2й шаг.
Теперь нажмите кнопку «Finish». MetaEditor перенесёт Вас к коду, подготовленному помощником и сохранит его в файл My_first_EA.mq4 в папке MetaTrader4experts.

Замечание: торговых советников надо класть в папку MetaTrader4 - experts, а индикаторы — в папку MetaTrader4 - experts - indicators, иначе они не будут работать.

Помощник подготовил следующий код:

Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"

//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+
int start()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
Теперь добавим свой код:

Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"

//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
 {
//----

//----
 return(0);
 }

int Crossed (double line1 , double line2)
{
 static int last_direction = 0;
 static int current_dirction = 0;

 if(line1>line2)current_dirction = 1; //up
 if(line1<line2)current_dirction = 2; //down

 if(current_dirction != last_direction) //changed
 {
 last_direction = current_dirction;
 return (last_direction);
 }

 else
 {
 return (0);
 }

}

//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+

int start()
{
//----
 int cnt, ticket, total;
 double shortEma, longEma;

 if(Bars<100)
 {
 Print("bars less than 100");
 return(0);
 }

 if(TakeProfit<10)
 {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
 }

 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);

 int isCrossed = Crossed (shortEma,longEma);

 total = OrdersTotal();
 if(total < 1)
 {
 if(isCrossed == 1)
 {
 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("BUY order opened : ",OrderOpenPrice());
 }
 else Print("Error opening BUY order : ",GetLastError());
 return(0);
 }

 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
 Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }

 return(0);
 }

 for(cnt=0;cnt<total;cnt++)
 {
 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
 if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
 {
 if(OrderType()==OP_BUY) // long position is opened
 {
 // should it be closed?
 if(isCrossed == 2)
 {
 OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if(Bid-OrderOpenPrice()>Point*TrailingStop)
 {
 if(OrderStopLoss()<Bid-Point*TrailingStop)
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
 return(0);
 }
 }
 }
 }

 else // go to short position
 {
 // should it be closed?
 if(isCrossed == 1)
 {
 OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
 {
 if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
 return(0);
 }
 }
 }
 }
 }
 }

 return(0);
}

//+------------------------------------------------------------------+
Можно скопировать код прямо отсюда.
Скомпилируйте эксперта, например, нажав F5.

Напуганы?

Не бойтесь этих 170 строчек кода, что Вы видите выше. Скоро мы их разберём строчка-за-строчкой. Это очень просто.

Протестируйте МТС:

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

Замечание: эта МТС предназначена для работы с EUR/USD на ТФ H4.

Протестировать советника можно двумя способами:

1 — Online-торговля

При online-торговле результаты теста точнее, но надо потратить дни или даже месяцы, чтобы выяснить прибыльна или нет данная МТС.
Для начала, необходимо разрешить советнику автоматизировать Вашу торговлю.
В MetaTrader4 выберите Сервис -> Настройки (или просто нажмите CTRL+O).
Во вкладке Советники разрешите следующие опции:

Включить советники
Разрешить советнику торговать
И нажмите кнопку «OK».
Рис.3 — Разрешаем автоторговлю.
Теперь найдите своего эксперта в Навигаторе в разделе Советники и перетащите его на график EUR/USD H4.
Рис.4 — Советник включён.
2 — тестер стратегий

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

Выберете параметры как на рисунке:
Рис.5 — Настройки тестера.
Нажмите кнопку Старт.
Далее Вас интересует вкладка отчёт.

Вникайте!

Изучаем основной материал:
Ваш первый советник.

Теперь мы разберем полученную программу строчку за строчкой.
Если Вы готовы? Начнём!

Замечание: Наш советник предназначен для обучения и не будет (не нацелен на) извлекать прибыль.

Код, который у нас уже есть:

Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"

//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
 {
//----

//----
 return(0);
 }

int Crossed (double line1 , double line2)
{
 static int last_direction = 0;
 static int current_dirction = 0;

 if(line1>line2)current_dirction = 1; //up
 if(line1<line2)current_dirction = 2; //down

 if(current_dirction != last_direction) //changed
 {
 last_direction = current_dirction;
 return (last_direction);
 }

 else
 {
 return (0);
 }

}

//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+

int start()
{
//----
 int cnt, ticket, total;
 double shortEma, longEma;

 if(Bars<100)
 {
 Print("bars less than 100");
 return(0);
 }

 if(TakeProfit<10)
 {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
 }

 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);

 int isCrossed = Crossed (shortEma,longEma);

 total = OrdersTotal();
 if(total < 1)
 {
 if(isCrossed == 1)
 {
 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("BUY order opened : ",OrderOpenPrice());
 }
 else Print("Error opening BUY order : ",GetLastError());
 return(0);
 }

 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
 Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }

 return(0);
 }

 for(cnt=0;cnt<total;cnt++)
 {
 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
 if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
 {
 if(OrderType()==OP_BUY) // long position is opened
 {
 // should it be closed?
 if(isCrossed == 2)
 {
 OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if(Bid-OrderOpenPrice()>Point*TrailingStop)
 {
 if(OrderStopLoss()<Bid-Point*TrailingStop)
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
 return(0);
 }
 }
 }
 }

 else // go to short position
 {
 // should it be closed?
 if(isCrossed == 1)
 {
 OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
 {
 if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
 return(0);
 }
 }
 }
 }
 }
 }

 return(0);
}

//+------------------------------------------------------------------+
Идея ТС нашего эксперта.

Прежде чем, разбирать код, рассмотрим, как устроена ТС, на которой основан наш эксперт. Любой советник должен решать, когда входить в рынок, и когда из него выходить. Основная идея при написании советников — определить каковы условия входов и выходов.
Наш советник прост и проста его идея. Рассмотрим её.
Используются две средние типа EMA (Exponential Moving Average): EMA 8 (быстрая) и EMA 13 (медленная).

Открытие позиций:

Наш советник будет открывать позиции при пересечении средних. Причём направление пересечения определяет в какую сторону будет открыта позиция.
Если быстрая EMA после пересечения окажется выше медленной EMA, то откроется сделка BUY (long).
Если быстрая EMA после пересечения окажется ниже медленной EMA, то откроется сделка SELL (short).

Одновременно может быть открыта только одна сделка.

Закрытие позиций:

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

Модификация позиций:

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

Код:
//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
В приведённых строчках помощник объявил три внешние переменные, как мы его и попросили. Эти переменные пользователь может изменять в окошке настроек эксперта. Также они проинициализированы значениями по умолчанию.

Код:
int Crossed (double line1 , double line2)
{
 static int last_direction = 0;
 static int current_dirction = 0;

 if(line1>line2)current_dirction = 1; //up
 if(line1<line2)current_dirction = 2; //down

 if(current_dirction != last_direction) //changed
 {
 last_direction = current_dirction;
 return (last_direction);
 }

 else
 {
 return (0);
 }

}
Как я уже говорил, идея советника заключается в слежении за двумя средними и их пересечениями. Для достижения этой цели мы создаем функцию Crossed.
Функция Crossed принимает на вход две переменные типа double и возвращает переменную типа integer. Первый параметр — это тек. значение первой линии (в нашем случае — быстрой EMA). Второй параметр — это тек. значение второй линии (в нашем случае — медленной EMA).
При каждом вызове функция сохраняет информацию о взаиморасположении этих линий в статических переменных (cтатические переменные хранятся в постоянной области памяти программы, их значения не теряются при выходе из функции). При этом функция сравнивает тек. взаиморасположение линий с их взаиморасположением при предыдущем вызове.
- Функция возвращает 0, если взаиморасположение линий не изменилось.
- Функция возвращает 1, если взаиморасположение линий изменилось, и первая линия оказалась над второй.
- Функция возвращает 2, если месторасположение линий изменилось, и первая линия оказалась под второй.

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

Посмотрим — как мы написали эту функцию?

Код:
int Crossed (double line1 , double line2)
Это объявление функции. Оно означает, что мы хотим создать функцию с именем Crossed, которая принимает на вход два параметра типа double и возвращает integer. Когда Вы будете вызывать эту функцию, ей надо быдет передавать два параметра типа double, а возвращать Вам она будет integer. Функцию необходимо объявлять перед её использованием (вызовом).
Расположение кода функции значения не имеет. Я его поставил гад функцией start(), Вы можете поставить его где угодно.

Код:
 static int last_direction = 0;
 static int current_dirction = 0;
Здесь мы объявляем две статические переменные типа static для хранения информации о тек. и предыдущем расположении линий (ещё раз: они статические, потому не теряют своего значения при выходе из функции). Они нам нужны, чтобы проверить изменилось ли взаиморасположение линий. Мы их проинициализировали нулями, т.к. не хотим, чтобы они сработали при самом первом вызове функции (в противном случае советник сразу бы открыл ордера

Код:
if(current_dirction != last_direction) //changed
В этой строчке мы сравниваем две статические переменные. Если current_dirction не равен last_direction, это значит, что взаиморасположение претерпело изменение.

Код:
 last_direction = current_dirction;
 return (last_direction);
В случае, если направление изменилось, нам надо изменить значение переменной last_direction для будущего использования. После чего мы возвращаем значение last_direction, которое равно 1, если первая линия выше второй, и 2— если наоборот.

Код:
else
 {
 return (0);
 }
В противном случае (взаиморасположение линий не изменилось) необходимо вернуть 0.

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

Торговые функции

Рассмотрим 25 торговых функций, часть из которых необходимы нам для продолжения написания советника для рынка Форекс в MQL4.
Для начала мы разберём самую главную функцию — SenOrderd(). А затем — все остальные в алфавитном порядке.
Готовы? Поехали!

1 — OrderSend()

Синтаксис:
int OrderSend( string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment=NULL, int magic=0, datetime expiration=0, color arrow_color=CLR_NONE)

Описание:
Основная функция, используемая для открытия позиции или установки отложенного ордера.
Возвращает номер тикета, который назначен ордеру торговым сервером или -1 в случае неудачи. Чтобы получить дополнительную информацию об ошибке, необходимо вызвать функцию GetLastError().
Замечания.
При открытии рыночного ордера (OP_SELL или OP_BUY) в качестве цены открытия могут использоваться только самые последние цены Bid (для продажи) или Ask (для покупки). Если операция проводится по финансовому инструменту, отличному от текущего, то для получения последних котировок по этому инструменту необходимо воспользоваться функцией MarketInfo() с параметром MODE_BID или MODE_ASK. Нельзя использовать расчетную либо ненормализованную цену. Если запрашиваемой цены открытия не было в ценовом потоке либо запрашиваемая цена не нормализована в соответствии с количеством знаков после десятичной точки, то будет сгенерирована ошибка 129 (ERR_INVALID_PRICE). Если запрашиваемая цена открытия сильно устарела, то независимо от значения параметра slippage будет сгенерирована ошибка 138 (ERR_REQUOTE). Если же запрашиваемая цена устарела, но ещё присутствует в ценовом потоке, то позиция открывается по текущей цене и только в том случае, если текущая цена попадает в диапазон price+-slippage.

Цены StopLoss и TakeProfit не могут располагаться слишком близко к рынку. Минимальное расстояние стопов в пунктах можно получить, используя функцию MarketInfo() с параметром MODE_STOPLEVEL. В случае ошибочных, а также ненормализованных стопов генерируется ошибка 130 (ERR_INVALID_STOPS).

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

На некоторых торговых серверах может быть установлен запрет на применение срока истечения отложенных ордеров. В этом случае при попытке задать ненулевое значение в параметре expiration будет сгенерирована ошибка 147 (ERR_TRADE_EXPIRATION_DENIED).

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

Параметры:
symbol - Наименование финансового инструмента, с которым проводится торговая операция.
cmd - Торговая операция. Может быть любым из значений торговых операций.
volume - Количество лотов.
price - Цена открытия.
slippage - Максимально допустимое отклонение цены для рыночных ордеров (ордеров на покупку или продажу).
stoploss - Цена закрытия позиции при достижении уровня убыточности (0 в случае отсутствия уровня убыточности).
takeprofit - Цена закрытия позиции при достижении уровня прибыльности (0 в случае отсутствия уровня прибыльности).
comment - Текст комментария ордера. Последняя часть комментария может быть изменена торговым сервером.
magic - Магическое число ордера. Может использоваться как определяемый пользователем идентификатор.
expiration - Срок истечения отложенного ордера.
arrow_color - Цвет открывающей стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то открывающая стрелка не отображается на графике.

Торговые операции:


2 — OrderClose()

Синтаксис:
bool OrderClose( int ticket, double lots, double price, int slippage, color Color=CLR_NONE)

Описание:
Закрытие позиции. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().

Параметры:
ticket — Уникальный порядковый номер ордера.
lots — Количество лотов для закрытия.
price — Цена закрытия.
slippage — Значение максимального проскальзывания в пунктах.
Color — Цвет стрелки закрытия на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображается.

3 — OrderCloseBy()

Синтаксис:
bool OrderCloseBy( int ticket, int opposite, color Color=CLR_NONE)

Описание:
Закрытие одной открытой позиции другой позицией, открытой по тому же самому инструменту, но в противоположном направлении. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().

Параметры:
ticket — Уникальный порядковый номер закрываемого ордера.
opposite — Уникальный порядковый номер противоположного ордера.
Color — Цвет стрелки закрытия на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображается.

4 — OrderClosePrice()

Синтаксис:
double OrderClosePrice( )

Описание:
Возвращает цену закрытия выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

5 — OrderCloseTime()

Синтаксис:
datetime OrderCloseTime( )

Описание:
Возвращает время закрытия для выбранного ордера. Только закрытые ордера имеют время закрытия, не равное 0. Открытые или отложенные ордера имеют время закрытия, равное 0.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

6 — OrderComment()

Синтаксис:
string OrderComment( )

Описание:
Возвращает комментарий для выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

7 — OrderCommission()

Синтаксис:
double OrderCommission( )

Описание:
Возвращает значение рассчитанной комиссии для выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

8 — OrderDelete()

Синтаксис:
bool OrderDelete( int ticket, color arrow_color=CLR_NONE)

Описание:
Удаляет ранее установленный отложенный ордер. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().

Параметры:
ticket — Уникальный порядковый номер ордера.
arrow_color — Цвет стрелки на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелка на графике не отображаются.

9 — OrderExpiration()

Синтаксис:
datetime OrderExpiration( )

Описание:
Возвращает дату истечения для выбранного отложенного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

10 — OrderLots()

Синтаксис:
double OrderLots( )

Описание:
Возвращает количество лотов для выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

11 — OrderMagicNumber()

Синтаксис:
int OrderMagicNumber( )

Описание:
Возвращает идентификационное («магическое») число для выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

12 — OrderModify()

Синтаксис:
bool OrderModify( int ticket, double price, double stoploss, double takeprofit, datetime expiration, color arrow_color=CLR_NONE)

Описание:
Изменяет параметры ранее открытых позиций или отложенных ордеров. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().
Замечания: цену открытия и время истечения можно изменять только у отложенных ордеров.
Если в качестве параметров функции передать неизмененные значения, то в этом случае будет сгенерирована ошибка 1 (ERR_NO_RESULT).
На некоторых торговых серверах может быть установлен запрет на применение срока истечения отложенных ордеров. В этом случае при попытке задать ненулевое значение в параметре expiration будет сгенерирована ошибка 147 (ERR_TRADE_EXPIRATION_DENIED).

Параметры:
ticket — Уникальный порядковый номер ордера.
price — Новая цена открытия отложенного ордера.
stoploss — Новое значение StopLoss.
takeprofit — Новое значение TakeProfit.
expirationВремя истечения отложенного ордера.
arrow_color — Цвет стрелок модификации StopLoss и/или TakeProfit на графике. Если параметр отсутствует или его значение равно CLR_NONE, то стрелки на графике не отображаются.

13 — OrderOpenPrice()

Синтаксис:
double OrderOpenPrice( )

Описание:
Возвращает цену открытия для выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

14 — OrderOpenTime()

Синтаксис:
datetime OrderOpenTime( )

Описание:
Возвращает время открытия выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

15 — OrderPrint()

Синтаксис:
void OrderPrint( )

Описание:
Выводит данные ордера в журнал в виде строки следующего формата:
номер тикета; время открытия; торговая операция; количество лотов; цена открытия; стоп лосс; тейк профит; время закрытия; цена закрытия; комиссия; своп; прибыль; комментарий; магическое число; дата истечения отложенного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

16 — OrderProfit()

Синтаксис:
double OrderProfit( )

Описание:
Возвращает значение чистой прибыли (без учёта свопов и комиссий) для выбранного ордера. Для открытых позиций это — текущая нереализованная прибыль. Для закрытых ордеровзафиксированная прибыль.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

17 — OrderSelect()

Синтаксис:
bool OrderSelect( int index, int select, int pool=MODE_TRADES)

Описание:
Функция выбирает ордер для дальнейшей работы с ним. Возвращает TRUE при успешном завершении функции. Возвращает FALSE при неудачном завершении функции. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError().
Параметр pool игнорируется, если ордер выбирается по номеру тикета. Номер тикета является уникальным идентификатором ордера. Чтобы определить, из какого списка выбран ордер, необходимо проанализировать его время закрытия. Если время закрытия ордера равно 0, то ордер является открытым или отложенным и взят из списка открытых позиций терминала. Отличить открытую позицию от отложенного ордера можно по типу ордера. Если время закрытия ордера не равно 0, то ордер является закрытым или удаленным отложенным и был выбран из истории терминала. Отличить закрытый ордер от удаленного отложенного также можно по типу ордера.

Параметры:
indexПозиция ордера или номер ордера в зависимости от второго параметра.
select — Флаг способа выбора. Mожет быть одним из следующих величин:
SELECT_BY_POS — в параметре index передается порядковый номер позиции в списке,
SELECT_BY_TICKET — в параметре index передается номер тикета.
pool — Источник данных для выбора. Используется, когда параметр select равен SELECT_BY_POS. Mожет быть одной из следующих величин:
MODE_TRADES (по умолчанию) — ордер выбирается среди открытых и отложенных ордеров,
MODE_HISTORY — ордер выбирается среди закрытых и удаленных ордеров.

18 — OrderHistoryTotal()

Синтаксис:
int OrdersHistoryTotal( )

Описание:
Возвращает количество закрытых позиций и удаленных ордеров в истории текущего счета, загруженной в клиентском терминале. Размер списка истории зависит от текущих настроек вкладки «История счета» терминала.

Параметры:
- отсутствуют -

19 — OrderStopLoss()

Синтаксис:
double OrderStopLoss( )

Описание:
Возвращает значение цены закрытия позиции при достижении уровня убыточности (stop loss) для текущего выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

20 — OrdersTotal()

Синтаксис:
int OrdersTotal( )

Описание:
Возвращает общее количество открытых и отложенных ордеров.

Параметры:
- отсутствуют -

21 — OrderSwap()

Синтаксис:
double OrderSwap( )

Описание:
Возвращает значение свопа для текущего выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

22 — OrderSymbol()

Синтаксис:
string OrderSymbol( )

Описание:
Возвращает наименование финансового инструмента для текущего выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

23 — OrderTakeProfit()

Синтаксис:
double OrderTakeProfit( )

Описание:
Возвращает значение цены закрытия позиции при достижении уровня прибыльности (take profit) для текущего выбранного ордера
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

24 — OrderTicket()

Синтаксис:
int OrderTicket( )

Описание:
Возвращает номер тикета для текущего выбранного ордера.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

25 — OrderType()

Синтаксис:
int OrderType( )

Описание:
Возвращает тип операции текущего выбранного ордера. Mожет быть одной из следующих величин:
OP_BUYпозиция на покупку,
OP_SELLпозиция на продажу,
OP_BUYLIMIT отложенный ордер на покупку по достижении заданного уровня, текущая цена выше уровня,
OP_BUYSTOPотложенный ордер на покупку по достижении заданного уровня, текущая цена ниже уровня,
OP_SELLLIMITотложенный ордер на продажу по достижении заданного уровня, текущая цена ниже уровня,
OP_SELLSTOPотложенный ордер на продажу по достижении заданного уровня, текущая цена выше уровня.
Ордер должен быть предварительно выбран с помощью функции OrderSelect().

Параметры:
- отсутствуют -

Замечания
Хотел бы, чтобы Вы обратили внимание на наиболее важные торговые функции:
OrderSend()
OrderModify()
OrderSelect()
OrderClose()
OrderDelete()

А на закуску мы рассмотрим ошибки исполнения:

Любая торговая операция (функции OrderSend(), OrderClose, OrderCloseBy, OrderDelete или OrderModify) по ряду причин может завершиться неудачей и вернуть либо отрицательный номер тикета, либо FALSE. Причину неудачи можно выяснить, вызвав функцию GetLastError(). Каждая ошибка должна быть обработана по-своему. Ниже в таблице приведены общие рекомендации.

Коды ошибок, возвращаемые торговым сервером:

Надеюсь, Вам хватило времени познакомиться с торговыми функциями MQL4, потому что сегодня они нам пригодятся.
В предыдущем описании данного поста мы познакомились с нашим первым советником и поняли, какая идея за ним скрывается. Теперь мы продолжим попытку разобрать код советника.
Итак, начнём.

Код, который мы уже имеем:

Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"

//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
 {
//----

//----
 return(0);
 }

int Crossed (double line1 , double line2)
{
 static int last_direction = 0;
 static int current_dirction = 0;

 if(line1>line2)current_dirction = 1; //up
 if(line1<line2)current_dirction = 2; //down

 if(current_dirction != last_direction) //changed
 {
 last_direction = current_dirction;
 return (last_direction);
 }

 else
 {
 return (0);
 }

}

//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+

int start()
{
//----
 int cnt, ticket, total;
 double shortEma, longEma;

 if(Bars<100)
 {
 Print("bars less than 100");
 return(0);
 }

 if(TakeProfit<10)
 {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
 }

 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);

 int isCrossed = Crossed (shortEma,longEma);

 total = OrdersTotal();
 if(total < 1)
 {
 if(isCrossed == 1)
 {
 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("BUY order opened : ",OrderOpenPrice());
 }
 else Print("Error opening BUY order : ",GetLastError());
 return(0);
 }

 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
 Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }

 return(0);
 }

 for(cnt=0;cnt<total;cnt++)
 {
 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
 if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
 {
 if(OrderType()==OP_BUY) // long position is opened
 {
 // should it be closed?
 if(isCrossed == 2)
 {
 OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if(Bid-OrderOpenPrice()>Point*TrailingStop)
 {
 if(OrderStopLoss()<Bid-Point*TrailingStop)
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
 return(0);
 }
 }
 }
 }

 else // go to short position
 {
 // should it be closed?
 if(isCrossed == 1)
 {
 OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
 {
 if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
 return(0);
 }
 }
 }
 }
 }
 }

 return(0);
}

//+------------------------------------------------------------------+
Последее, что мы разобрали в прошлый раз, — это функция Crossed(). Таким образом, мы подошли вплотную к функцииstart().

Код:
int cnt, ticket, total;
В этой строчке мы объявили три переменные типа integer. Мы смогли объявить все три переменные в одной строчке, потому что они все одинакого типа.

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

С тем же успехом можно было разбить эту строчку на три части:

Код:
int cnt;

int ticket;

int total;
Переменную cnt мы будем использовать в качестве счётчика в нашем «цикле просмотра открытых позиций».
Переменную ticket мы будем использовать для хранения тикета (идентификационного номера ордера), возвращаемого функцией OrderSend().
Переменную total мы будем использовать для хранения числа уже открытых ордеров.

Код:
double shortEma, longEma;
Опять же, мы объявили две переменные в одной строчке.
Мы будем использовать эти переменные для хранения значений быстрой EMA и медленной EMA.
Я надеюсь, Вы помните, что мы должны следить за их пересечениями и взаиморасположениями. Исходя из этой информации мы будем принимать решения об открытии ордеров на покупку или продажу, а также их закрытии.

Код:
 if(Bars<100)
 {
 Print("bars less than 100");
 return(0);
 }
Мы предполагаем, что график, с которым мы работаем, имеет больше 100 баров. Если это не так, то индикаторы EMA не смогут функционирывать.
Число баров на графике хранится в переменной Bars. С помощью этой переменной мы проверяем количество баров на графике в данный момент и, если оно меньше 100, мы сделаем 2 вещи: во-первых сообщим пользователю, напечатав в лог эксперта сообщение «bars less than 100″, во-вторых мы завершим функцию start() оператором return(0);
Таким образом, мы отказываемся работать, если баров на графике меньше 100.

Код:
 if(TakeProfit<10)
 {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
 }
Также мы не можем работать с неработоспособным значением параметра TakeProfit.
Переменная TakeProfit является внешней (extern), что означает, что пользователь может изменить её в окошке настроек советника.
Мы хотим защитить пользователя нашего советника от его же ошибок. Короче говоря, сделать программу дуракоустойчивой
Мы предполагаем, что любое значение меньше 10 для переменной TakeProfit является плохим выбором. Поэтому мы проверяем значение, заданное пользователем, и сравниваем его с 10. Если окажется, что значение меньше 10, мы сообщим об этом пользователю, распечатав сообщение «TakeProfit less than 10″, и завершим функцию start() оператором return(0);

Код:
 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);
Если всё в порядке: на графике больше 100 баров, и значение TakeProfit больше либо равно 10 — мы можем перейти к расчёту значений EMA на текущем баре.

Мы воспользуемся встроенным в MQL4 индикатором iMA, который рассчитает скользящие среднии.

Здесь нам придётся немного остановиться, чтобы поподробнее узнать о функции iMA.

iMA

Синтаксис:
double iMA( string symbol, int timeframe, int period, int ma_shift, int ma_method, int applied_price, int shift)

Описание:
Расчет скользящего среднего.

Параметры:
symbol — Символьное имя инструмента, на данных которого будет вычисляться индикатор. NULL означает текущий символ.
timeframe — Период. Может быть одним из периодов графика. 0 означает период текущего графика.
period — Период усреднения для вычисления скользящего среднего.
ma_shiftСдвиг индикатора относительно ценового графика.
ma_method Метод усреднения. Может быть любым из значений методов скользящего среднего (Moving Average).
applied_price — Используемая цена. Может быть любой из ценовых констант.
shift — Индекс получаемого значения из индикаторного буфера (сдвиг относительно текущего бара на указанное количество периодов назад).

Методы скользящих:
Ценовые константы:

Всю эту информацию можно получить во встроенном справочнике MQL4. Достаточно лишь в окне Навигатор во вкладке Поиск набрать: iMA.

Код:
 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);
Теперь Вы понимаете, что означают эти строчки.
Переменной shortEma мы присвоили значение, равное:
Усреднённую методом экспоненциального среднего цену Close последних 8 баров, начиная с текущего.
Коротко можно говорить EMA8.
Переменной longEma мы присвоили значение, равное:
Усреднённую методом экспоненциального среднего цену Close последних 13 баров, начиная с текущего.
Коротко можно говорить EMA13.

Код:
int isCrossed = Crossed (shortEma,longEma);
Уверен, что Вы помните, что функция Crossed() принимает две переменные типа double и возвращает integer.
Первый параметр — это тек. значение первой линии, за которой мы хоти наблюдать. Второй — второй линии.
Функция будет следить за двумя линиями при каждом её вызове посредством сохранения их тек. значений в статических переменных, чтобы запоминать их предыдущее состояние.
- Функция возвращает 0, если взаиморасположение линий не изменилось.
- Функция возвращает 1, если взаиморасположение линий изменилось, и первая линия оказалась над второй.
- Функция возвращает 2, если взаиморасположение линий изменилось, и первая линия оказалась под второй.

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

Код:
 total = OrdersTotal();
 if(total < 1)
 {

 ...

 }
Мы присвоили переменной total возвращаемое значение функции OrdersTotal().

Замечание: Функция OrdersTotal() возвращает общее количество открытых и ожидающих ордеров

Следующий за этим блок if сработает, только если нет ни одного открытого или ждущего ордера.

Блок if:

Код:
 {
 if(isCrossed == 1)
 {
 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("BUY order opened : ",OrderOpenPrice());
 }
 else Print("Error opening BUY order : ",GetLastError());
 return(0);
 }

 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }
В случае, когда EMA8 пересекла EMA13 снизу вверх, мы производим покупку. Для этого мы используем функцию OrderSend()

OrderSend()

Синтаксис:
int OrderSend( string symbol, int cmd, double volume, double price, int slippage, double stoploss, double takeprofit, string comment=NULL, int magic=0, datetime expiration=0, color arrow_color=CLR_NONE)

Параметры:
symbol - Мы воспользовались функцией Symbol(), чтобы получить строку с названием валюты, и передали её (строку) функции OrderSend().
cmd - Мы использовали OP_BUY, чтобы открыть сделку на покупку.
volume - Мы использовали переменную Lots, значение которой нам предоставил пользователь.
price - Мы воспользовались переменной (на самом деле функцией) Ask, чтобы получить тек. значение цены ask, и передали его функции OrderSend().
slippage - Мы использовали 3 в качестве значения допустимого проскальзывания.
stoploss - Мы использовали 0 в качествет стоп-лосса, что означает, что у этого ордера нет стоп-лосса.
takeprofit - Мы воспользовались переменной TakeProfit, значение которой нам предоставил пользователь. Умножили её на цену одного пункта, хранящуюся в переменной Point, и прибавили результат к тек. значению Ask (цена открытия ордера). Это Важный момент обратите на него внимание. Аналогичным образом надо рассчитывать стоп-лосс, если он не равен нулю.
comment - Мы использовали строку «My EA» в качестве комментария.
magic - Мы использовали число 12345 в качестве magic-числа. Магическое число нужно только для того, чтобы советник отличал свои ордера от чужих.
expiration - Мы использовали 0, потому что нашему ордеру не нужен срок истечения. Этот параметр применим только к отложенным ордерам.
arrow_color - Мы выбрали зелёный, потому что мы любим деньги, а доллары зелёные

Функция OrderSend() при успешном исполнении возвращает тикет открытого ордера, что мы используем в проверке:

Код:
if(ticket>0)
Затем мы функцией OrderSelect() выбираем открытый ордер по его тикету, и функцией OrderOpenPrice() узнаём цену его открытия.

Если всё прошло успешно, и функция OrderSend() вернула нормальный тикет (больший 0), а функция OrderSelect() благополучно выбрала нужный ордер, то в лог эксперта распечатается сообщение «BUY order opened : » + цена открытия.

Иначе, если функция OrderSend() вернула -1, что означает, что было ошибка, мы должны об этом сообщить пользователю, выдав сообщение: :Error opening BUY order: » + номер ошибки, предоставленный функцией GetLastError(). При этом нам придётся завершить функцию start() опреатором return(0);

Код:
 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
 Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }
Здесь сценарий противоположный — EMA8 пересекает EMA13 сверху вниз. Теперь мы продаём.
Можете найти различия в вызовах функции OrderSend() ?
Правильно!

Следующие параметры не изменились:

symbol
volume
slippage
stoploss
comment
magic
expiration

А эти изменились:

cmd - Мы использовали OP SELL, чтобы открыть сделку на продажу.
price - Мы воспользовались переменной (на самом деле функцией) Bid, чтобы получить тек. значение цены bid, и передали его функции OrderSend().
takeprofit - Мы воспользовались переменной TakeProfit, значение которой нам предоставил пользователь. Умножили её на цену одного пункта, хранящуюся в переменной Point, и вычли результат к тек. значению Bid (цена открытия ордера).
arrow_color - Мы выбрали красный, потому что мы любим деньги, а деньги зелёные, но нам нужен другой цвет для позиций на продажу

Далее в этом куске кода всё аналогично.
Вот и всё… Мы закончили с блоком if(total<1). В следующ…

- Постой!

- Что?

- А я заметил ещё одну строчку в этом блоке, которую мы не разобрали!

- Где?

- Вот она:

Код:
 return(0);
- Отлично!
Посмотрите внимательно на эти строчки:

Код:
 if(total < 1)
 {
 if(isCrossed == 1)
 {

 ...

 }

 if(isCrossed == 2)
 {

 ...

 }

 return(0); // <<-- Вот она
 }
Если EMA8 пересекает EMA13 снизу вверх, то мы покупаем.
Если EMA8 пересекает EMA13 сверху вниз, то мы продаём.
А что если они ещё не пересеклись? За это отвечает данная строчка. Она прервёт функцию start(), если ещё не было пересечения (в случае, если переменная isCrossed равна 1 или 2).
Теперь мы знаем, как наш советник открывает сделки.

Код, который мы уже имеем:

Код:
//+------------------------------------------------------------------+
//| My_First_EA.mq4 |
//| Kirill |
//| StockProgrammer@mail.ru |
//+------------------------------------------------------------------+
#property copyright "Kirill"
#property link "StockProgrammer@mail.ru"

//---- input parameters
extern double TakeProfit=350.0;
extern double Lots=0.1;
extern double TrailingStop=35.0;
//+------------------------------------------------------------------+
//| expert initialization function |
//+------------------------------------------------------------------+
int init()
 {
//----

//----
 return(0);
 }
//+------------------------------------------------------------------+
//| expert deinitialization function |
//+------------------------------------------------------------------+
int deinit()
 {
//----

//----
 return(0);
 }

int Crossed (double line1 , double line2)
{
 static int last_direction = 0;
 static int current_dirction = 0;

 if(line1>line2)current_dirction = 1; //up
 if(line1<line2)current_dirction = 2; //down

 if(current_dirction != last_direction) //changed
 {
 last_direction = current_dirction;
 return (last_direction);
 }

 else
 {
 return (0);
 }

}

//+------------------------------------------------------------------+
//| expert start function |
//+------------------------------------------------------------------+

int start()
{
//----
 int cnt, ticket, total;
 double shortEma, longEma;

 if(Bars<100)
 {
 Print("bars less than 100");
 return(0);
 }

 if(TakeProfit<10)
 {
 Print("TakeProfit less than 10");
 return(0); // check TakeProfit
 }

 shortEma = iMA(NULL,0,8,0,MODE_EMA,PRICE_CLOSE,0);
 longEma = iMA(NULL,0,13,0,MODE_EMA,PRICE_CLOSE,0);

 int isCrossed = Crossed (shortEma,longEma);

 total = OrdersTotal();
 if(total < 1)
 {
 if(isCrossed == 1)
 {
 ticket=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,Ask+TakeProfit*Point, "My EA",12345,0,Green);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("BUY order opened : ",OrderOpenPrice());
 }
 else Print("Error opening BUY order : ",GetLastError());
 return(0);
 }

 if(isCrossed == 2)
 {
 ticket=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,
 Bid-TakeProfit*Point,"My EA",12345,0,Red);
 if(ticket>0)
 {
 if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
 Print("SELL order opened : ",OrderOpenPrice());
 }
 else Print("Error opening SELL order : ",GetLastError());
 return(0);

 }

 return(0);
 }

 for(cnt=0;cnt<total;cnt++)
 {
 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
 if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
 {
 if(OrderType()==OP_BUY) // long position is opened
 {
 // should it be closed?
 if(isCrossed == 2)
 {
 OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if(Bid-OrderOpenPrice()>Point*TrailingStop)
 {
 if(OrderStopLoss()<Bid-Point*TrailingStop)
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
 return(0);
 }
 }
 }
 }

 else // go to short position
 {
 // should it be closed?
 if(isCrossed == 1)
 {
 OrderClose(OrderTicket(),OrderLots(),Ask,3,Violet); // close position
 return(0); // exit
 }

 // check for trailing stop
 if(TrailingStop>0)
 {
 if((OrderOpenPrice()-Ask)>(Point*TrailingStop))
 {
 if((OrderStopLoss()>(Ask+Point*TrailingStop)) || (OrderStopLoss()==0))
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Ask+Point*TrailingStop,OrderTakeProfit(),0,Red);
 return(0);
 }
 }
 }
 }
 }
 }

 return(0);
}

//+------------------------------------------------------------------+
Выше мы разобрали, как происходит проверка того, что OrdersTotal меньше 1, для того, чтобы открывать сделку BUY или SELL только в случае, если до этого не было ни одной уже открытой сделки.

Мы исполльзовали след. код:

Код:
 if(total < 1)
 {
 if(isCrossed == 1)
 {

 ...

 }

 if(isCrossed == 2)
 {

 ...

 }

 return(0);
 }
Это был алгоритм открытия новых сделок. Теперь мы изучим алгоритм закрытия и модификации уже открытых сделок.

Код:
 for(cnt=0;cnt<total;cnt++)
 {

 ...

 }
В вышеуказанном блоке мы используем цикл for для перебора всех открытых ордеров.
Мы начинаем цикл с cnt=0, потому что индексация (не тикеты, а именно их индексы в массиве, где они храняться) ордеров начинается с 0. На каждом щаге цикла мы увеличиваем cnt на 1 (cnt++).
Наша теперешняя задача заключается в изучении тела данного цикла.

Код:
 OrderSelect(cnt, SELECT_BY_POS, MODE_TRADES);
 if(OrderType()<=OP_SELL && OrderSymbol()==Symbol())
 {

 ...

 }
Функция OrderSelect() используется для выбора открытого или отложенного ордера по его тикету или индексу. В этом цикле мы вызвали функцию OrderSelect() перед функциями OrderType() и OrderSymbol(), потому что если бы мы не вызвали её, то вторые две функции просто напросто не сработали бы.

Замечание: Функцию OrderSelect() необходимо вызывать перед следующими функциями, не принимающими никаких параметров:

OrderMagicNumber, OrderClosePrice, OrderCloseTime, OrderOpenPrice,

OrderOpenTime, OrderComment, OrderCommission, OrderExpiration, OrderLots,

OrderPrint, OrderProfit, OrderStopLoss, OrderSwap, OrderSymbol, OrderTakeProfit,

OrderTicket и OrderType

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

Замечание: Индекс первого оредра: 0, второго: 1 и т.д.

Мы использовали режим MODE_TRADES, который означает, что мы будем выбирать ордер из открытых (или отложенных) на данный момент ордеров. Т.е. — не из истории.

Функция OrderType() возвращает тип выбранного ордера. Тип может принимать одно из след. значений:

OP_BUY, OP_SELL, OP_BUYLIMIT, OP_BUYSTOP, OP_SELLLIMIT или OP_SELLSTOP

Причём этими названиями закодированы обычные числа:


Мы проверяем, верно ли то, что тип выбранного ордера меньше либо равен значения OP_SELL. Это означает, что возможно лишь два варианта: OP_SELL и OP_BUY. Эта проверка необходима, т.к. мы не хотим трогать чужие отложенные ордра.
Также мы хотим работать только с ордерами, которые были открыты на валютном инструменте, на график которого мы подгрузили эксперта.
Так что весь последующий код будет работать тоолько, если OrderType() равен OP_SELL или OP_BUY, а OrderSymbol()равен Symbol()

Код:
 if(OrderType()==OP_BUY) // long position is opened
 {

 ...

 }
В этом блоке мы рассматриваем только открытые сделки на покупку.
Посмотрим, что же происходит дальше:

Код:
 // should it be closed?
 if(isCrossed == 2)
 {
 OrderClose(OrderTicket(),OrderLots(),Bid,3,Violet); // close position
 return(0); // exit
 }
Сделку на покупку мы открывали, когда быстрая EMA пересекала медленную EMA снизу вверх.
Логичным было бы закрывать эту сделку при обратном пересечении. Для этого мы вызываем функцию isCrossed(), и если она возвращает 2, то обратное пересечение произошло и необходимо закрыть сделку.

Для закрытия сделки мы используем функцию OrderClose(). Подробнее выше — торговые функции.
Первым параметром она принимает значение тикета закрываемого ордера. Тикет мы предоставляем, используя функцию OrderTicket().
Вторым параметром функция принимает lots — количество лотов для закрытия (ВАЖНО: т.е. можно неполностью закрывать сделку). В нашем случае мы хотим закрыть весь ордер, поэтому в качестве lots предоставляем значение, возвращаемое функцией OrderLots().
Третим параметром функции OrderClose() является предпочитаемая цена закрытия. Ордер на покупку мы закрываем по тек. цене Bid.
Четвёртым параметром является slippage (допустимое проскальзывание цены исполнения). Здесь мы использовали значение 3.
Пятым параметром является цвет закрывающей стрелки. Мы использовали фиолетовый.

Затем мы не забыли завершить функцию start() оператором return(0);

Код:
 // check for trailing stop
 if(TrailingStop>0)
 {
 if(Bid-OrderOpenPrice()>Point*TrailingStop)
 {
 if(OrderStopLoss()<Bid-Point*TrailingStop)
 {
 OrderModify(OrderTicket(),OrderOpenPrice(),Bid-Point*TrailingStop,OrderTakeProfit(),0,Green);
 return(0);
 }
 }
 }
Замечание: мы всё ещё находимся внутри блока if(OrderType()==OP_BUY)

В этом блоке кода мы применим технику треилинг-стопа к нашей открытой Buy позиции.
Для начала, мы проверяем, что предоставленное пользователем значение TrailingStop является пригодным (большим 0).
Затем мы проверяем, что:
1. Разница между тек. ценой Bid и ценой открытия сделки больше, чем величина TrailingStop. Это необходимо для того, чтобы треилинг стоп начинал работать толко при переносе стоп-лосса в безубыток. До этого момента стоп-лосс остаётся фиксированным.
2. Текущий стоп-лосс находится ниже, чем он должен быть. Т.е. ниже той цены, которую мы получим, если из тек. цены Bid вычтем величину TrailingStop.

Для осуществления необходимых модификаций мы применяем функцию OrderModify(). Подробнее выше — торговые функции.
Её параметры следующие:
ticket: Тикет предоставляем с помощью функции OrderTicket().
price: У нас есть цена открытия ордера благодаря функции OrderOpenPrice().
stoploss: Вот то, что нам на самом деле нужно. Т.к. у нас сделка на покупку, то мы выставляем новый стоп-лосс на уровне, получаемом при вычитании из тек. цены Bid величины TrailingStop.

Замечание: Для сделки BUY стоп-лосс должен всегда находится НИЖЕ тек. цены bid. А для сделки SELL - ВЫШЕ тек. цены ask.

takeprofit: Без изменений. Тек. значение тейк-профита мы предоставляем функцией OrderTakeProfit().
expiration: Это не отложенный ордер у него нет срока истесения. Поэтому 0.
arrow_color: Оставляем зелёный цвет.

Наконец-то мы завершаем функцию start().

Код:
 else // go to short position
 {

 ...

 }
Этот блок выполняется в случае, если рассматриваемый ордер не на покупку, а на продажу. Строение и реализация этого блока (блока обработки ордера SELL) абсолютно аналогичны строению и реализации блоку обработки ордера BUY. Попробуйте разобраться с ним самостоятельно.

И в самом конце у нас есть строчка:

Код:
 return(0);
Эта строчка завершает функцию во всех остальных случаях: когда нет условий для открытия новых ордеров, нет условий для их закрытия или модификаций.

Надеюсь, вам понравилось создавать и программировать советники.

0 Responses to “Пишем советник для Forex. Написания советника для рынка Форекс в MQL4”

Отправить комментарий