Рождение эксперта. Стохастическая Система.

Опубликовано: 01.01.2007

В данной статье я покажу, шаг за шагом, как был написан эксперт для Стохастической системы, с описанием которой можно познакомится в разделе МТС, на моем сайте www.autoforex.ru. Данная Механическая Торговая Система была опубликована в журнале FOREX MAGAZINE №173.

Напомню правила торговли по этой системе:

Общие правила:
1. Торговые сигналы рассматриваются с 7.00 до 15.00 EST. (с 13.00 по 21.00 CET(используется в MetaTrader 4))
2. Безусловный выход из позиции в 16.00 EST (22.00 CET) (позиция не переносится на следующий день).
3. Закрытие первой части позиции производится с целью по прибыли в 10 пунктов.

Правила для длинной позиции:
1. Покупаем, когда линия %К, находившаяся ниже уровня 30, пересекает уровень 50.
2. Закрываем позицию на 2-ом снижении линии %К. Местоположение снижения не имеет значения.
3. Закрываем позицию, если линия %К пересекает уровень 40 (защита от движения в противоположную сторону).

Правила для короткой позиции:
1. Продаем, когда линия %К, находившаяся выше уровня 70, пересекает уровень 50.
2. Закрываем позицию на 2-ом повышении линии %К. Местоположение снижения не имеет значения.
3. Закрываем позицию, если линия %К пересекает уровень 60 (защита от движения в противоположную сторону).

Замечание: в эксперте, который мы будем писать, одновременно будут открываться два одинаковых ордера BUY или SELL. Это сделано для того, чтобы упростить алгоритм работы эксперта. Все дело в том, что при достижении профита в 10 пунктов предполагается, по правилам системы, закрыть часть позиции, а это проще сделать, если у нас открыто 2 одинаковых ордера. В этом случае достаточно закрыть один из ордеров, хотя, даже и этого нам не нужно делать, т.к. он закроется автоматически при достижении профита в 10 пунктов, потому что при открытии этого ордера мы выставим значение TakeProfit равным 10 пунктов.

Итак, приступаем к написанию эксперта на MQL4 для MetaTrader 4. Я предполагаю, что Вы имеете начальные представления об MQL4. Если нет, то прочитайте справку по MQL4. И вообще, в любом случае начинать изучать язык лучше с прочтения справки по данному языку. Это же касается и языка программирования MQL4.

Открываем MetaEditor и создаем нового эксперта. Для этого идем в меню "Файл" -> "Создать…"-> выбираем "Советник". Далее вводим необходимую информацию, при этом поле "параметры" оставляем пустым. Параметры мы добавим по ходу написания эксперта. Жмем кнопку "Готово", и вот, что мы в итоге получим:

//+------------------------------------------------------------------+
//|                                             stochastic_system.mq4|
//|                                                    Павел Смирнов |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+
#property copyright "Павел Смирнов"
#property link      "www.autoforex.ru"

int init()
  {
   return(0);
  }

int deinit()
  {
   return(0);
  }

int start()
  {
   return(0);
  }

Я убрал некоторые закомментированные строки, которые автоматически вставляются редактором, исключительно для экономии места.

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

int start()
  {
    double stoch_1=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,1);
    double stoch_2=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,2);
    double stoch_3=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,3);

    return(0);
  }

Так же объявляем внешние переменные, которые задают параметры Стохастика. Это позволит нам менять их во время тестирования:

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

Стохастик

Для вычислений нам потребуется именно три значения Стохастика т.к. нам нужно будет определять изменение направления в его движении. Причем заметьте, что мы вычисляем значения Стохастика только на 3-х уже сформировавшихся свечах предшествующих текущему, нулевому бару. Это важно, так как текущий бар при реальной торговле будет постоянно меняться, пока не примет окончательную форму с приходом нового бара.
На рисунке наглядно показано, какие значения принимают переменные stoch_1, stoch_2, и stoch_3. Текущее значение Стохастика, которое соответствует текущему, или нулевому бару, постоянно меняется с приходом новых тиков, т.к. сам нулевой бар изменяется. Все остальные свечи уже сформированы, и их можно использовать для расчетов, или же при принятии решений.

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

int Hour_curr=TimeHour(TimeCurrent());

Все приготовления сделаны, и наш эксперт принимает вид:

//+------------------------------------------------------------------+
//|                                             stochastic_system.mq4|
//|                                                    Павел Смирнов |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+
#property copyright "Павел Смирнов"
#property link      "www.autoforex.ru"

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

int init()
  {
   return(0);
  }

int deinit()
  {
   return(0);
  }

int start()
  {
    double stoch_1=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,1);
    double stoch_2=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,2);
    double stoch_3=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,3);
    int Hour_curr=TimeHour(TimeCurrent());

    return(0);
  }

Теперь займемся правилами торговли. Реализуем следующее правило, вход в длинную позицию:

1. Покупаем, когда линия %К, находившаяся ниже уровня 30, пересекает уровень 50.

Эту проверку нужно производить только в том случае, если еще не открыто ни одной позиции, т.е. OrdersTotal()<1, и не забываем, что торговля ведется строго с 13.00 по 21.00 CET:

if(OrdersTotal()<1)
      {
        if((Hour_curr>=13)&&(Hour_curr<21))//проверка сигналов только в этот промежуток времени
          {
            // здесь будем проверять условия для входа в рынок
          }
      }

Ну а теперь, внутри этой конструкции, будем проводить проверку на выполнение условия для длинной позиции. Для этого нам потребуется переменная, которая говорила бы нам, что Стохастик находился ниже уровня 30, до того как пересек уровень 50. Добавляем строку, в которой переменная K_level будет приравнена к 30, когда Стохастик опустится ниже уровня 30. Добавим ее в начало функции start(), например, сразу после объявления переменной Hour_curr:

if (stoch_1<30) K_level=30;

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

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

int K_level=0;

int init()
  {
   return(0);
  }

Теперь, для входа в длинную позицию, достаточно проверить, что K_level = 30 и Стохастик имеет значение больше 50. Это и будет считаться приказом на покупку. На MQL4 это выглядит так:

if((K_level==30)&&(stoch_1>50.0))//сигнал 
на покупку
  {
    RefreshRates();
    ticket=OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,0,"buy_order1",1,0,Red);
    ticket=OrderSend(Symbol(),OP_BUY,0.1,Ask,10,0,Ask+TakeProfit*Point,"buy_order2",2,0,Red);
    K_level=50;
  }

Функция RefreshRates() в данном случае используется для того, чтобы обновить значение переменной Ask, которая используется в функции OrderSend(…). Этого можно было и не делать, так как в нашем случае, ввиду примитивных вычислений, переменная Ask вряд ли изменится за время проведения вычислений. Все же, я рекомендую использовать функцию RefreshRates() даже в этом случае, потому как вероятность того, что переменная Ask изменится, все же есть.

Далее идут подряд две функции OrderSend(…). Первая открывает ту часть совокупной позиции, которая будет закрыта на втором снижении линии . Поэтому значения стоплосс и тейкпрофит в этой функции равны нулю. Вторая открывает часть позиции, которая будет закрываться при достижении профита равного 10 (это значение задается внешней переменной TakeProfit).

Последней строчкой в этом блоке мы "обнуляем" пременную K_level, присвоив ей значение равное 50. Стохастик должен, теперь, снова опуститься ниже уровня 30, чтобы эксперт получил возможность в будущем совершить сделку.

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

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

На данный момент мы имеем следующий код:

//+------------------------------------------------------------------+
//|                                             stochastic_system.mq4|
//|                                                    Павел Смирнов |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+
#property copyright "Павел Смирнов"
#property link      "www.autoforex.ru"

extern int TakeProfit=10;

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

int K_level=0;

int init()
  {
   return(0);
  }

int deinit()
  {
   return(0);
  }

int start()
  {
    int ticket=0;
    double stoch_1=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,1);
    double stoch_2=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,2);
    double stoch_3=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,3);
    int Hour_curr=TimeHour(TimeCurrent());

    if (stoch_1>70) K_level=70;
    if (stoch_1<30) K_level=30;
    if(OrdersTotal()<1)
      {
        if((Hour_curr>=13)&&(Hour_curr<21))//проверка сигналов только в этот промежуток времени
          {
            if((K_level==30)&&(stoch_1>50.0))//сигнал на покупку
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,0,"buy_order1"
,1,0,Red);
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,Ask+TakeProfit*Point,"buy_order2",2,0,Red);
                K_level=50;
              }
            if((K_level==70)&&(stoch_1<50.0))//сигнал на продажу
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,0,"sell_order1"
,1,0,Blue);
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,Bid-TakeProfit*Point,"sell_order2",2,0,Red);
                K_level=50;
              }
          }
      }

    return(0);
  }

Теперь давайте добавим в эксперта блок закрытия всех сделок в 22.00 CET, т.к. это одно из правил торговой системы, и его проще реализовать на MQL4, нежели другие правила для закрытия сделок:

if (Hour_curr>=22)//безусловное закрытие всех позиций
  {
  while(OrdersTotal()>0)
    {
      CloseDirect(0,"Принудительное закрытие сделки в 22.00, ticket=");
    }
  }

С условием - Hour_curr>=22 все понятно - проверяем, что текущий час больше или равен 22, т.е. наступило время для закрытия всех сделок. Можно было проверять на строгое равенство 22 часам, но это хуже, так как возможна ситуация, когда ни одной котировки с таким значением времени не окажется. А так, мы и в 22, и в 23 часа сможем закрыть сделки.

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

Далее идет цикл, в котором происходит вызов функции CloseDirect(…), которая закрывает первую позицию в списке открытых ордеров. Закрытие первого в списке ордера мы вынесли в отдельную функцию с той целью, что данная функция нам потребуется еще несколько раз, когда будет реализован алгоритм закрытия ордеров на 2-ом снижении (повышении) линии и при обратном движении Стохастика.

Итак, функция CloseDirect(…):

void CloseDirect(int cntr, string comm)
  {
    double closeprice;
    if(OrderSelect(cntr,SELECT_BY_POS,MODE_TRADES))
      {
        RefreshRates();
        if (OrderType()==OP_BUY)
          closeprice=Bid;
        else
          closeprice=Ask;
        if (OrderClose(OrderTicket(),OrderLots
(),closeprice,10,Green))
          {
            Print(comm, OrderTicket());
          }
        else
          {
            Print("ОШИБКА в CloseDirect():OrderClose() - ",GetLastError());
          }
      }
    else
      {
        Print("ОШИБКА в CloseDirect():OrderSelect() - ",GetLastError());
      }
  }

Функция имеет два входных параметра, cntr – порядковый номер открытого ордера, который нужно закрыть и comm. – комментарий, который выводится в журнал для того, чтобы иметь возможность идентифицировать причину закрытия ордера.
Алгоритм работы этой функции простой: сначала выбираем ордер с порядковым номером cntr из списка открытых ордеров при помощи функции OrderSelect(…):

if(OrderSelect(cntr,SELECT_BY_POS,MODE_TRADES))
  {
    //Ордер выбран и здесь можно делать с ним все, что душе угодно
  }
else
  {
    Print("ОШИБКА в CloseDirect():OrderSelect() - ",GetLastError());
  }

Затем, в зависимости от того, ордер какого типа мы будем закрывать, выбираем цену закрытия - Bid для длинной позиции, Ask – для короткой:

RefreshRates();
if (OrderType()==OP_BUY)
  closeprice=Bid;
else
  closeprice=Ask;

И, наконец, закрываем позицию:

if (OrderClose(OrderTicket(),OrderLots(),closeprice,
10,Green))
  {
    Print(comm, OrderTicket());
  }
else
  {
    Print("ОШИБКА в CloseDirect():OrderClose() - ",GetLastError());
  }

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

Давайте вернемся к блоку принудительного закрытия сделок в 22.00 CET и посмотрим, как он работает. Допустим у нас, на данный момент, открыто 2 ордера. У каждого из них есть порядковый номер позиции в списке открытых ордеров. У первого это значение равно нулю, а у второго – единице. OrdersTotal() при этом равен 2. При выполнении функции CloseDirect(0,"Принудительное закрытие сделки в 22.00, ticket=") будет закрыт первый ордер, тогда наш второй ордер автоматически становится первым с порядковым номером равным нулю, а OrdersTotal() равным 1. На следующем витке цикла будет удален оставшийся ордер, при этом OrdersTotal() станет равным 0, и цикл закончится.

Соберем куски кода вместе и наш эксперт примет вид:

//+------------------------------------------------------------------+
//|                                             stochastic_system.mq4|
//|                                                    Павел Смирнов |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+
#property copyright "Павел Смирнов"
#property link      "www.autoforex.ru"

extern int TakeProfit=10;

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

int K_level=0;

void CloseDirect(int cntr, string comm)
  {
    double closeprice;
    if(OrderSelect(cntr,SELECT_BY_POS,MODE_TRADES))
      {
        RefreshRates();
        if (OrderType()==OP_BUY)
          closeprice=Bid;
        else
          closeprice=Ask;
        if (OrderClose(OrderTicket(),OrderLots
(),closeprice,10,Green))
        {
          Print(comm, OrderTicket());
        }
        else
          {
            Print("ОШИБКА в CloseDirect():OrderClose() - ",GetLastError());
          }
      }
    else
      {
        Print("ОШИБКА в CloseDirect():OrderSelect() - ",GetLastError());
      }
  }

int init()
  {
   return(0);
  }

int deinit()
  {
   return(0);
  }

int start()
  {
    int ticket=0;
    double stoch_1=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,1);
    double stoch_2=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,2);
    double stoch_3=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,3);
    int Hour_curr=TimeHour(TimeCurrent());

    if (stoch_1>70) K_level=70;
    if (stoch_1<30) K_level=30;
    if(OrdersTotal()<1)
      {
        if((Hour_curr>=13)&&(Hour_curr<21))//проверка сигналов только в этот промежуток времени
          {
            if((K_level==30)&&(stoch_1>50.0))//сигнал на покупку
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,0,"buy_order1"
,1,0,Red);
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,Ask+TakeProfit*Point,"buy_order2",2,0,Red);
                K_level=50;
              }
            if((K_level==70)&&(stoch_1<50.0))//сигнал на продажу
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,0,"sell_order1"
,1,0,Blue);
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,Bid-TakeProfit*Point,"sell_order2",2,0,Red);
                K_level=50;
              }
          }
      }
    if (Hour_curr>=22)//безусловное закрытие всех позиций
      {
        while(OrdersTotal()>0)
          {
            CloseDirect(0,"Принудительное закрытие сделки в 22.00, ticket=");
          }
      }
    return(0);
  }

В таком состоянии эксперта можно откомпилировать и запустить на выполнение, чтобы получить возможность проанализировать его работу. По крайней мере, тех функций и правил, которые в него уже заложены. Делается это очень просто. Берем график EUR/USD M5, накладываем на него индикатор Stochastic с теми же параметрами, что и индикатор, используемый в эксперте. Этот индикатор будет использоваться для определения значений Стохастика в определенные моменты времени. Затем запускаем эксперта на небольшом участке истории, например 3 дня и смотрим, какие сделки совершал эксперт и когда. При этом проверяем, используя график Стохастика, были ли на самом деле сигналы на открытие ордеров. Так же проверяем выполнение условий для закрытия ордеров. Все должно соответствовать тем правилам, которые были заложены в эксперта. Таким образом, вручную, проверяется работоспособность любого эксперта.

Если все хорошо, то следуем дальше. Нам еще надо реализовать выход с рынка при 2-ом снижении (повышении) Стохастика, ну и защитить от обратного движения.
Сначала поставим защиту от обратного движения Стохастика:

// блок закрытия всех открытых ордеров в случае, если рынок пошел не в нашу сторону
    if (OrdersTotal()>=1)
      {
        if(OrderSelect(0,SELECT_BY_POS,MODE_TRADES))
          {
            if(OrderType()==OP_BUY)
              {
                if(stoch_1<=40)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Принудительное закрытие сделки при обратном движении рынка, ticket="
);
                    }
              }
            if(OrderType()==OP_SELL)
              {
                if(stoch_1>=60)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Принудительное закрытие сделки при обратном движении рынка, ticket="
);
                    }
              }
          }
        else
          {
            Print("ОШИБКА в Start()(блок закрытия при обратном движении) :OrderSelect() - ",GetLastError());
            return(-1);
          }
      }

Здесь все просто: в этот блок заходим только, если OrdersTotal()>0, т.е. есть открытые ордера, иначе нам просто незачем туда заходить. Далее, с помощью функции OrderSelect(), выбираем первый ордер (порядковый номер 0), затем, если тип выбранного ордера - BUY, то проверяем значение Стохастика и, если оно меньше 40, то закрываем все открытые позиции:

if(OrderSelect(0,SELECT_BY_POS,MODE_TRADES))
  {
    if(OrderType()==OP_BUY)
      {
        if(stoch_1<=40)
          while(OrdersTotal()>0)
            {
              CloseDirect(0,"Принудительное закрытие сделки при обратном движении рынка, ticket=");
            }

Аналогично работает блок для закрытия позиции SELL.

На данный момент у нас имеется эксперт, который открывает сделки в период с 13.00 по 21.00 CET, закрывает часть позиции (один из двух ордеров) при достижении профита в 10 пунктов и закрывает все позиции, если Стохастик идет в противоположную сторону.

Нам осталось реализовать закрытие BUY-позиции на 2-ом снижении линии , а так же закрытие SELL-позиции на 2-ом повышении линии . На MQL4 это выглядит следующим образом:

// блок закрытия на 2-ом снижении (повышении) Стохастика.
    if (OrdersTotal()>=1)
      {
        if ((stoch_1<stoch_2)&&(stoch_3<stoch_2)) down++;
        if ((stoch_1>stoch_2)&&(stoch_3>stoch_2)) up++;

        if(OrderSelect(0,SELECT_BY_POS,MODE_TRADES))
          {
            if(OrderType()==OP_BUY)
              {
                if(down==NWave)
                   while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Закрытие всех BUY-сделок после второго подъема, ticket=");
                    }
              }
            if(OrderType()==OP_SELL)
              {
                if(up==NWave)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Закрытие всех SELL-сделок после второго подъема, ticket=");
                    }
              }
          }
        else
          {
            Print("ОШИБКА в Start():OrderSelect() - ",GetLastError());
            return(-1);
          }
      }

Как видите здесь тоже все просто. В блоке используются две глобальные переменные – down и up. Переменная down это счетчик понижений, который считает, сколько понижений Стохастика прошло после открытия сделки. Переменная up считает, соответственно, количество повышений Стохастика. Эти переменные объявляются вместе с переменной K_level и должны обнуляться во время открытия сделки, поэтому в блоки открытия сделок добавляем строчки обнуления переменных down и up.

Идем дальше. В строке:

if ((stoch_1<stoch_2)&&(stoch_3<stoch_2)) down++;

происходит проверка на наличие очередного понижения Стохастика, если таковое было, то счетчик увеличивается на 1 (оператор down++). Наличие понижения определяется очень просто – сравниваются значения Стохастика один бар назад, два бара назад и три бара назад, переменные - stoch_1, stoch_2 и stoch_3 соответственно. Понижение было, если stoch_3<stoch_2 и stoch_2>stoch_1. Аналогично проверяется наличие повышения.

Переменная NWave содержит в себе значение равное количеству понижений (повышений) после которого необходимо закрывать сделку. Изначально, по правилам системы, это значение равно 2, но я решил иметь возможность изменять этот параметр во время тестирования эксперта. Поэтому переменная NWave объявлена как внешняя.

Далее, если число повышений (понижений) Стохастика равно NWave, то происходит закрытие ордеров с помощью функции CloseDirect(…). Эту функцию мы уже разбирали.

Вот и все. Но, забегая вперед, хочу отметить, что при тестировании эксперта выявилось следующее: при значениях переменных stoch_2 >50, stoch_1<30, K_level=70, т.е. когда Стохастик находившийся выше линии 70 пересекает сверху вниз линию 50 при этом, достигая уровня ниже 30, по правилам системы имеем сигнал на продажу, но сделка не совершается. Причина, как выяснилось, в том, что значение Стохастика (переменная stoch_1) меньшее тридцати переключает значение переменной K_level в 30 вот в этой строчке:

if (stoch_1<30) K_level=30;

Но нас это не устраивает, поэтому внесем изменения в код и заменим строки:

if (stoch_1>70) K_level=70;
if (stoch_1<30) K_level=30;

на код:

if ((stoch_1>70)&&(stoch_2>50)) K_level=70;
if ((stoch_1<30)&&(stoch_2<50)) K_level=30;

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

Теперь все, и наш эксперт принял окончательный вид:

//+------------------------------------------------------------------+
//|                                             stochastic_system.mq4|
//|                                                    Павел Смирнов |
//|                                                 www.autoforex.ru |
//+------------------------------------------------------------------+
#property copyright "Павел Смирнов"
#property link      "www.autoforex.ru"

extern int TakeProfit=10;
extern int NWave=2;

extern int K=9;
extern int D=3;
extern int slowing=5;
extern int Average_method=2;
extern int price_field=0;

int K_level=0;
int down=0;
int up=0;

void CloseDirect(int cntr, string comm)
  {
    double closeprice;
    if(OrderSelect(cntr,SELECT_BY_POS,MODE_TRADES))
      {
        RefreshRates();
        if (OrderType()==OP_BUY)
          closeprice=Bid;
        else
          closeprice=Ask;
        if (OrderClose(OrderTicket(),OrderLots
(),closeprice,10,Green))
        {
          Print(comm, OrderTicket());
        }
        else
          {
            Print("ОШИБКА в CloseDirect():OrderClose() - ",GetLastError());
          }
      }
    else
      {
        Print("ОШИБКА в CloseDirect():OrderSelect() - ",GetLastError());
      }
  }

int init()
  {
   return(0);
  }

int deinit()
  {
   return(0);
  }

int start()
  {
    int ticket=0;
    double stoch_1=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,1);
    double stoch_2=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,2);
    double stoch_3=iStochastic(NULL,0,K,D,slowing,Average_method,price_field,MODE_MAIN,3);
    int Hour_curr=TimeHour(TimeCurrent());

    if ((stoch_1>70)&&(stoch_2>50)) K_level=70;
    if ((stoch_1<30)&&(stoch_2<50)) K_level=30;
    if(OrdersTotal()<1)
      {
        if((Hour_curr>=13)&&(Hour_curr<21))//проверка сигналов только в этот промежуток времени
          {
            if((K_level==30)&&(stoch_1>50.0))//сигнал на покупку
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,0,"buy_order1"
,1,0,Red);
                ticket=OrderSend(Symbol(),OP_BUY,0.1,
Ask,10,0,Ask+TakeProfit*Point,"buy_order2",2,0,Red);
                K_level=50;
                down=0;
              }
            if((K_level==70)&&(stoch_1<50.0))//сигнал на продажу
              {
                RefreshRates();
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,0,"sell_order1"
,1,0,Blue);
                ticket=OrderSend(Symbol(),OP_SELL,0.1,
Bid,10,0,Bid-TakeProfit*Point,"sell_order2",2,0,Red);
                K_level=50;
                up=0;
              }
          }
      }

    // блок закрытия всех открытых ордеров в случае, если рынок пошел не в нашу сторону
    if (OrdersTotal()>=1)
      {
        if(OrderSelect(0,SELECT_BY_POS,MODE_TRADES))
          {
            if(OrderType()==OP_BUY)
              {
                if(stoch_1<=40)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Принудительное закрытие сделки при обратном движении рынка, ticket="
);
                    }
              }
            if(OrderType()==OP_SELL)
              {
                if(stoch_1>=60)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Принудительное закрытие сделки при обратном движении рынка, ticket="
);
                    }
              }
          }
        else
          {
            Print("ОШИБКА в Start()(блок закрытия при обратном движении) :OrderSelect() - ",GetLastError());
            return(-1);
          }
      }

    // блок закрытия на 2-ом снижении (повышении) Стохастика.
    if (OrdersTotal()>=1)
      {
        if ((stoch_1<stoch_2)&&(stoch_3<stoch_2)) down++;
        if ((stoch_1>stoch_2)&&(stoch_3>stoch_2)) up++;

        if(OrderSelect(0,SELECT_BY_POS,MODE_TRADES))
          {
            if(OrderType()==OP_BUY)
              {
                if(down==NWave)
                   while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Закрытие всех BUY-сделок после второго подьема, ticket=");
                    }
              }
            if(OrderType()==OP_SELL)
              {
                if(up==NWave)
                  while(OrdersTotal()>0)
                    {
                      CloseDirect(0,"Закрытие всех SELL-сделок после второго подьема, ticket=");
                    }
              }
          }
        else
          {
            Print("ОШИБКА в Start():OrderSelect() - ",GetLastError());
            return(-1);
          }
      }

    if (Hour_curr>=22)//безусловное закрытие всех позиций
      {
        while(OrdersTotal()>0)
          {
            CloseDirect(0,"Принудительное закрытие сделки в 22.00, ticket=");
          }
      }

    return(0);
  }

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

Весь текст эксперта в отдельном окне можно посмотреть здесь.

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

На этом я заканчиваю первую статью из цикла статей «Рождение эксперта».

С Уважением
Павел Смирнов.