Проект - "Газонокосилка". Советник "gazonkos".

Механическая торговая система «Газонокосилка» названа, так потому что советник, торгующий по этой системе, должен будет «косить капусту» понемногу, но часто. Поэтому такие параметры как стоплосс и тейкпрофит выбраны небольшими, чтобы сделки могли закрываться при небольших движениях цены. Однако, советник не должен пипсовать и оптимальное для него – это одна, две сделки в день.

Торговые правила системы «Газонокосилка»:

1. Входим на откате ценового движения
2. Выходим по Стоплосс и Тейкпрофит

Таким образом, сначала необходимо определить ценовое движение или импульс (тут может быть множество вариантов). Затем дожидаемся отката цены (так же множество вариантов) и входим в рынок.

Ценовое движение

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

ценовое движение

Однако, для первоначальной версии советника упростим процедуру определения движения (импульса) цены. Как и в советнике «20/200_pips» будем определять изменение цены за некоторый промежуток времени. А, если еще точнее, то будем смотреть разницу между ценами закрытия баров в момент времени t1 и t2 (см. рисунок). Если цена Close за промежуток времени от t1 до t2 увеличилась на величину большую чем delta, то это будем воспринимать, как наличие ценового движения вверх, а если уменьшилась на delta и более – то вниз.

Откат

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

откат цены

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

При работе советника на часовом графике, одного бара будет достаточно для определения отката. Конечно, можно и нужно использовать более продвинутые способы определения отката, но об этом будет в последующих статьях по проекту «Газонокосилка».

Вход в позицию осуществляется сразу после того, как советник обнаружит откат ценового движения. Входим по направлению ценового движения, в надежде на то, что цена, после отката, продолжит свое движение.

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

Советник «gazonkos»

Всю работу торговой системы «Газонокосилка», можно разбить на несколько состояний:

1. Ждем движения цены (импульса)
2. Следим за ценой и ждем отката
3. Входим в позицию

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

Структурная схема советника gazonkos

Советник следует запускать на графиках с периодом H1 (это связано лишь с выбранными способами определения движения цены и ее отката). А тестирование проводить только по модели «все тики», т.к. в данной системе требуется определять откат цены внутри часового бара, что возможно только в модели «все тики» при наличии минутной истории.

Итак, подытожим правила работы системы заложенные в первой версии советника «gazonkos»:
Советник с приходом нового часового бара проверяет, не было ли движения цены (импульса). Если был, то в течение этого часового бара проверяем, не было ли отката цены, если был, то тут же входим в рынок, если отката не было, то переходим в режим ожидания движения цены и все повторяется.

//+------------------------------------------------------------------+
//|                                             gazonkos expert.mq4  |
//|                                                    1H   EUR/USD  |
//|                                                    Smirnov Pavel |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+

#property copyright "Smirnov Pavel"
#property link      "www.autoforex.ru"

extern int magic = 12345;
extern int TakeProfit = 16; // Уровень тейкпрофит в пунктах
extern int Otkat = 16;// Величина отката в пунктах
extern int StopLoss = 40; // уровень стоплосс в пунктах

extern int t1=3;
extern int t2=2;
extern int delta=40;

extern double lot = 0.1;// Размер позиции
extern int active_trades=1;//Максимальное количество одновременно открытых ордеров

int STATE=0;
int Trade=0;
double maxprice=0.0;
double minprice=10000.0;
int ticket;
bool cantrade=true;
int LastTradeTime=0;
int LastSignalTime=0;

int OpenLong(double volume=0.1)
{
  int slippage=10;
  string comment="gazonkos expert (Long)";
  color arrow_color=Blue;

  ticket=OrderSend(Symbol(),OP_BUY,volume,Ask,slippage,Ask-StopLoss*Point,
                      Ask+TakeProfit*Point,comment,magic,0,arrow_color);
  if(ticket>0)
  {
    if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
    {
      Print("Buy order opened : ",OrderOpenPrice());
      return(0);
    }
  }
  else
  {
    Print("Error opening Buy order : ",GetLastError());
    return(-1);
  }
}

int OpenShort(double volume=0.1)
{
  int slippage=10;
  string comment="gazonkos expert (Short)";
  color arrow_color=Red;

  ticket=OrderSend(Symbol(),OP_SELL,volume,Bid,slippage,Bid+StopLoss*Point,
                      Bid-TakeProfit*Point,comment,magic,0,arrow_color);
  if(ticket>0)
  {
    if(OrderSelect(ticket,SELECT_BY_TICKET,MODE_TRADES))
      {
        Print("Sell order opened : ",OrderOpenPrice());
        return(0);
      }
  }
  else
  {
    Print("Error opening Sell order : ",GetLastError());
    return(-1);
  }
}

int OrdersTotalMagic(int MagicValue)//функция возвращает количество открытых ордеров с magic = MagicValue
{
   int j=0;
   int i;
   for (i=0;i<OrdersTotal();i++)//Производим просмотр среди всех открытых ордеров
   {
     if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES))//Выбираем по-порядку ордера
     {
        if (OrderMagicNumber()==MagicValue) j++; //Подсчитываем только те у котроых нужный magic   
     }
     else
     {
         Print("gazonkos expert: OrderSelect() в OrdersTotalMagic() вернул ошибку - ",GetLastError());
         return(-1);
     }
   }
   return(j);//Возвращаем количество подсчитанных ордеров с magic = MagicValue.  
}

int init()
{
  return(0);
}

int deinit()
{
  return(0);
}

int start()
{

// STATE = 0  Ждем сигнала к началу работы советника  ------------------------------------------------------------

   if (STATE==0)
   {
      bool cantrade=true;
      if(TimeHour(TimeCurrent())==LastTradeTime) cantrade=false;//запрещаем торговать пока не наступит новый час после последней 
                                                                //открытой сделки (чтобы избежать множественных открываний сделок на одном и том же часовом баре)     
      if(OrdersTotalMagic(magic)>=active_trades) cantrade=false;// проверяем на допустимое количество открытых ордеров
      if(cantrade) // если не было ни одного запрета на открытие сделок, то переходим к ожиданию сигналов системы на открытие ордеров
         STATE=1;
   }

// STATE = 1  Ждем импульса (движения) цены ----------------------------------------------------------------------

   if (STATE==1)
   {
      if((Close[t2]-Close[t1])>delta*Point)// сигнал для входа в длинную позицию
      {
         Trade = 1; //идентификатор позиции, для которой получен сигнал на открытие  "-1" - короткая позиция, "1"-длинная
         maxprice=Bid;// запоминаем текущее положение цены (необходимо для определения отката в STATE=2)
         LastSignalTime=TimeHour(TimeCurrent());//Запоминаем время получения сигнала
         STATE = 2; // перейти в следующее состояние
      }

      if((Close[t1]-Close[t2])>delta*Point)// сигнал для входа в короткую позицию
      {
         Trade = -1; // идентификатор позиции, для которой получен сигнал на открытие  "-1" - короткая позиция, "1"-длинная
         minprice=Bid;// запоминаем текущее положение цены (необходимо для определения отката в STATE=2)
         LastSignalTime=TimeHour(TimeCurrent());//Запоминаем время получения сигнала
         STATE = 2; // перейти в следующее состояние
      }
   }

// STATE = 2 - Ждем отката цены   -------------------------------------------------------------------------------- 

   if (STATE==2)
   {
      if(LastSignalTime!=TimeHour(TimeCurrent()))//Если на баре на котором получен сигнал не произошло отката,то переходим в состояние STATE=0
      {
         STATE=0;
         return(0);
      }
      if(Trade==1)// ожидаем отката для длинной позиции
      {
         if(Bid>maxprice) maxprice=Bid;//если цена пошла еще выше, то меняем значение maxprice на текущее значение цены
         if(Bid<(maxprice-Otkat*Point))// проверяем наличие отката цены после импульса 
            STATE=3;//если произошел откат на величину Otkat, то переходим в состояние открытия длинной позиции
      }

      if(Trade==-1)// ожидаем отката для короткой позиции
      {
         if(Bid<minprice) minprice=Bid;//если цена пошла еще ниже, то меняем значение minprice на текущее значение цены
         if(Bid>(minprice+Otkat*Point))// проверяем наличие отката цены после импульса
            STATE=3;//если произошел откат на величину Otkat, то переходим в состояние открытия короткой позиции
      }
   }

// STATE = 3 - открываем позиции согласно переменной Trade ("-1" - короткую, "1" - длинную)   -------------------- 

   if(STATE==3)
   {
      if(Trade==1)// открываем длинную позицию
      {
         OpenLong(lot);// открываем длинную позицию
         LastTradeTime=TimeHour(TimeCurrent());//запоминаем время совершения последней сделки
         STATE=0; //переходим в состояние ожидания
      }
      if(Trade==-1)// открываем короткую позицию
      {
         OpenShort(lot);// открываем короткую позицию  
         LastTradeTime=TimeHour(TimeCurrent());//запоминаем время совершения последней сделки
         STATE=0; //переходим в состояние ожидания
      }
   }
  return(0);
}

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

Приведу лишь описание входных параметров (внешних переменных) советника по которым можно будет проводить оптимизацию:

magic – магический номер, используя который, советник работает только со своими ордерами.
TakeProfit - Уровень тейкпрофит в пунктах. Он выставляется для всех ордеров при открытии.
Otkat - Величина отката в пунктах. При откате цены на эту величину происходит переход советника в состояние открытия сделки.
StopLoss - уровень стоплосса в пунктах. Как и TakeProfit выставляется при открытии позиции.
t1 – номер бара, который участвует в определении движения цены.
t2 - номер бара, который участвует в определении движения цены.
delta – величина используемая при определении импульса движения цены.
lot - Размер позиции.
active_trades - Максимальное количество одновременно открытых ордеров.

О результатах тестирования советника и, возможно, о его модификациях читайте в следующих статьях.

Продолжение следует ...