Учимся писать советник. Часть 1.

В качестве примера для обучения решил начать с нуля создание собственного торгового советника. Для пущего привлечения читателей к своему детищу назову его Форекс-Грааль (Forex-Grail), слово «святой» (holy) от греха подальше опущу. Буду стараться делать код удобочитаемым, чтобы в дальнейшем было проще его расширять и искать ошибки. Итак, вперед.

Запускаем редактор Метаедитор (MetaEditor)  из поставки Метатрейдера 4 (Metatrader), до 5 версии думаю я еще не скоро дойду, а в нем «Мастер MQL», где выбираем из предлагаемого списка шаблон советника.

wizard-grail
Откроется окно среды разработки, приступим к наполнению исходника.
Первым делом назначим нашему советнику имя, номер версии, не забудем и копирайт и ссылочку на данный блог.

//+------------------------------------------------------------------+
//|                                                  Forex-Grail.mq4 |
//|                                     Copyright 2016, HomeTrade.ru |
//|                                             https://hometrade.ru |
//+------------------------------------------------------------------+
/*
Учимся писать советник.
Версия 1 - основные функции
*/
#define VERSION "1.00"
#property version VERSION
#property copyright "Copyright 2016, HomeTrade.ru"
#property link      "https://hometrade.ru"
#property strict
string ExpertName="Forex-Grail v."+VERSION;
Затем идут внешние переменные, отвечающие за настройку и оптимизацию советника
//внешние переменные extern int SlipPage = 3; //Проскальзывание extern int StopLoss = 20; //Стоп-лосс extern int TakeProfit = 20; //Тейк-профит extern int MagicNumber = 333; //Magic номер extern double StartLot = 0.1; // Размер лота для начала торгов extern double MaxLot = 1; //Максимально допустимый лот

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

//внутренние переменные
double MINLOT,MAXLOT;
bool expertStopped=false; //признак остановки эксперта
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
//Если наши значения не попадают в пределы устанавливаемые брокером
MAXLOT = MarketInfo(Symbol(),MODE_MAXLOT);
MINLOT = MarketInfo(Symbol(),MODE_MINLOT);
if (MaxLot>MAXLOT) MaxLot=MAXLOT;
if (StartLot<MINLOT) StartLot=MINLOT;

//корректируем некоторые значения для 5 или 3 значных котировок
if (Digits == 5 || Digits == 3)
{
TakeProfit *= 10;
StopLoss *= 10;

}

return(INIT_SUCCEEDED);
}

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

void OnDeinit(const int reason)
{

}

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

void OnTick()
{
//если нет денег для торговли, сообщим об этом однократно
double margin= MarketInfo(NULL, MODE_MARGINREQUIRED);
if ( AccountFreeMargin() < margin)
{
if (expertStopped) return; //уже сообщали что не хватает денег. Чтобы не тревожить брокера
Print ("AccountFreeMargin= ",AccountFreeMargin()," margin= ",margin);
expertStopped=true;
return;
}

expertStopped=false;
Traiding(); //торговые операции
// ТОДО другие операции, например отслеживание - трейлинг - убыточных/профитных сделок, проверка условий на закрытие ордеров и т.п.

}

Далее идет функция Traiding(), в которой мы проверяем условия входа в сделку и при необходимости открываем их.

//Проверяем условия на открытие позиций, открываем позиции
void Traiding()
{
int direction,ticket;

if (TradesCount()>0) return; //уже есть открытые ордера

// куда будем открываться при отсутствии ордеров
if (Open[1]<Close[1])
direction=OP_BUY;
else direction=OP_SELL;

if (direction==OP_BUY)
{
ticket= MyOrderSend(Symbol(),OP_BUY,StartLot,NormalizeDouble(Ask,Digits),SlipPage,NormalizeDouble(Bid - StopLoss*Point,Digits),NormalizeDouble(Bid + TakeProfit*Point,Digits),ExpertName,MagicNumber,0,Blue);
}
else if (direction==OP_SELL)
{
ticket= MyOrderSend(Symbol(),OP_SELL, StartLot,NormalizeDouble(Bid,Digits), SlipPage, NormalizeDouble(Ask + StopLoss*Point,Digits),NormalizeDouble(Ask - TakeProfit*Point,Digits),ExpertName,MagicNumber,0,Green);
}

}

В нашем простейшем примере мы будем торговать одним ордером. Для этого в функции TradesCount() вычисляется число рыночных ордеров, и если хотя бы один присутствует, то ничего не делаем.

//Подсчитаем число открытых рыночных ордеров
int TradesCount() {
int count = 0;
for (int trade = OrdersTotal() - 1; trade >= 0; trade--) //пройдем по всем ордерам
{
if (OrderSelect(trade, SELECT_BY_POS, MODE_TRADES)==true) //смотрим только те что в рынке
{
if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) //если мэджик номер и пара совпадают - это наш ордер
count++;
}
}
return (count);
}

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

//Впоследствии можно улучшить обработку ошибок с попытками их исправления, а пока просто аналог OrderSend
int MyOrderSend(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=clrNONE)
{
int ticket=-1;
ticket=OrderSend(symbol,cmd,volume,price,slippage,stoploss,takeprofit,comment,magic,expiration,arrow_color);
if(ticket<0)
{
Print("Ошибка OrderSend:",GetLastError()," cmd=",cmd);
}

return ticket;
}

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

OrderSend(Symbol(),OP_BUY,0.1,Ask,Slippage,Ask-lStop,Ask+dTake," коммент",0,0,clOpenBuy);

И это будет работать в большинстве случаев. Но иногда можете нарваться на ошибку 130 — неправильные стопы. Особенно если вы скальпер и несколько пипсов в сделке вам ох как важны. А все потому что стопы и тейки в длинной позиции нужно устанавливать не от Ask, а от Bid.
Запомним несколько простых, пусть и печальных для трейдера правил:
1) Брокер работает против вас, любая сделка открывается не в вашу пользу.
2) Продаем по заниженной против нас цене, а покупаем по завышенной.
3) ASK — цена по которой вы покупаете (для вас завышают цену), она выше цены, по которой вы же можете продать (А идет выше буквы B).
4) BID — цена по которой вы продаете (для вас занижают цену), она ниже цены, чем продают вам (буква B стоит ниже А)
5) Длинная позиция, покупка, открывается по невыгодной для вас цене ASK , соответственно закрытие длинной позиции, будь то стоп или профит, будет проходить как продажа вами по невыгодной для вас цене BID.
6) Короткая позиция, продажа, открывается по невыгодной для вас цене BID, соответственно её закрытие будет проводиться брокером как покупка вами по грабительской цене ASK.

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

Итак, вот в общем-то наш первый советник и готов. Пора приступить к испытаниям и открывать реальный торговый счет для загребания денег :).

strategy-tester-grail-1
К сожалению, график не внушает оптимизма, с Канарами придется повременить и продолжить работать головой. Постойте, раз на рынке только 25% времени существуют выраженные тренды, а оставшееся время валюты и сырье болтаются во флете, то может нам нужно не идти в направлении предыдущей свечи, а развернуться? Поменяем знак сравнения в if (Open[1]<Close[1]) чтобы получилось if (Open[1]>Close[1]) — в лонг идем при черной свече. И заново протестируем «грааль».

strategy-tester-grail-2

Стало значительно лучше, даже вышли в плюс. Кстати, почаще заглядывайте в журнал тестера на проверку наличия там ошибок. Лучше выявить и исправить их сразу при тестировании, чем после непонятных реальных финансовых потерь.
journal-grail

А если еще и провести оптимизацию значений
grail-input-opt
то можно получить вполне себе симпатичный график.
strategy-tester-grail-opt-2

На этой оптимистической ноте и закончим создание первой самостоятельной версии автоматического торгового советника Форекс-Грааль. И постоянного профита всем нам!

Продолжение находится здесь: вводим трейлинг-стоп.

Вы можете оставить комментарий, или ссылку на Ваш сайт.

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

Вы должны быть авторизованы, чтобы разместить комментарий.