Forextrade

Диверсификация рисков. Часть 2

Советник Phoenix_Direct

Советник Phoenix_Reverse

Советник PhoenixDouble

Файлы стратегий для Autograf 4.0

Развернутые результаты тестирования эксперта

       

        Продолжая тему диверсификации рисков при помощи сведения двух противоположных систем в одну, рассмотрим немногим более сложную систему. В качестве такой системы возьмем один из вариантов известного торгового робота Phoenix (Феникс), который был создан Хендриком Стамом (Hendrick Stam). Стратегия стала популярной в результате успешной работы в рамках Чемпионата Автоматических Торговых Систем 2006 года, в котором эксперт Phoenix занял пятое место.

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

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

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

       

                      Рис. 1 - Первый тип сигнала оперирует данными дневного графика цены.

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

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

           В данном случае две средние используются не в стиле CrossOver (пересечение), а в стиле расхождения средних, на чем основан индикатор MACD (см. рис. 2).

                Рис. 2. - Расхождение средних в качестве сигнала открытия сделок.

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

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

           Описанные условия торговли реализованы в эксперте Phoenix_Direct. За его основу взят эксперт SimpleMACross из предыдущей статьи, в котором полностью изменено тело функции GetSignal и добавлено несколько строк в блоке открытия позиций функции start. Сигнальную функцию, как обычно, приведем полностью:

 
//+-------------------------------------------------------------------------------------+
  //| Расчет значений средних с формированием сигналов открытия позиций                   |
  //+-------------------------------------------------------------------------------------+
  void GetSignal()
  {
   Signal = 0;
  // - 1 - ====================== Расчет сигнала №1 =======================================
   bool PierceUp = False; bool PierceDown = False;
   for (int i = 1; i < iBars(Symbol(), PERIOD_D1); i++)
     {
      if (iClose(Symbol(), PERIOD_D1, i) > iHigh(Symbol(), PERIOD_D1, i+1))
        {
         PierceUp = True;
         break;
        }
      if (iClose(Symbol(), PERIOD_D1, i) < iLow(Symbol(), PERIOD_D1, i+1))
        {
         PierceDown = True;
         break;
        }
     }   
   if (!PierceUp && !PierceDown) return;    
  // - 1 - ======================= Окончание блока ========================================
  
  // - 2 - ====================== Расчет сигнала №2 =======================================
   double Div1 = iMA(Symbol(), 0, FastMAPeriod, FastMAShift, FastMAMethod, FastMAPrice, 1)-
                 iMA(Symbol(), 0, SlowMAPeriod, SlowMAShift, SlowMAMethod, SlowMAPrice, 1);
   double Div2 = iMA(Symbol(), 0, FastMAPeriod, FastMAShift, FastMAMethod, FastMAPrice, 2)-
                 iMA(Symbol(), 0, SlowMAPeriod, SlowMAShift, SlowMAMethod, SlowMAPrice, 2);
   double Div3 = iMA(Symbol(), 0, FastMAPeriod, FastMAShift, FastMAMethod, FastMAPrice, 3)-
                 iMA(Symbol(), 0, SlowMAPeriod, SlowMAShift, SlowMAMethod, SlowMAPrice, 3);
  // - 2 - ======================= Окончание блока ========================================
  
  // - 3 - == Генерация сигнала ===========================================================
   if (PierceUp)
     if (Div3 >= Div2 && Div1 > Div2 && Div1 < 0)
       Signal = 1;                                        // Открытие BUY
   
   if (PierceDown)
     if (Div3 <= Div2 && Div1 < Div2 && Div1 > 0)
      Signal = -1;                                        // Открытие SELL
  // - 3 - == Окончание блока =============================================================
  }

        Блок 1 находит последнее пробитие дня ценой закрытия. Направление пробития становится направлением текущего тренда.

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

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

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

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

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

           Рис. 3. - Расположение уровней стоп-приказов и профитов при существовании разнонаправленных позиций.

    Допустим, позиция Sell, открытая по реверсной системе, существует уже некоторое время. Позицию Buy необходимо открыть по сигналу прямой системы. Так как объемы позиций равны, достаточно закрыть Sell. Но следует учесть несовпадение уровней стоп-приказов и профитов позиций. При существовании двух разнонаправленных позиций, если цена будет расти, сначала сработает уровень профита позиции Buy. Это эквивалентно открытию короткой позиции, так как Sell все еще будет активен, а Buy уже закрыт. То есть для полного соответствия при закрытии позиций необходимо на уровень профита длинной позиции установить ордер Sell Limit с такими же уровнями стопа и профита, которые были у предшествующей короткой позиции. Такое решение справедливо лишь для случая, когда профит новой длинной позиции находится ниже уровня стопа короткой. Если же профит длинной позиции располагается выше стопа короткой, то на уровень стопа Sell нужно установить ордер Buy Stop c параметрами, взятыми от позиции Buy.

    Очень похожим является алгоритм действий на случай падения цены. Исходя из рис. 3, сначала сработает уровень профита короткой позиции, в результате чего Sell будет закрыт и в рынке останется лишь длинная позиция. Значит, для полного соответствия, в случае отсутствия позиций, необходимо на уровень профита короткой позиции установить ордер Buy Limit с параметрами, которые были у длинной позиции. Такой алгоритм действий справедлив, если уровень профита позиции Sell находится выше стоп-приказа позиции Buy. В случае, когда профит короткой позиции находится ниже стоп-приказа длинной позиции, нужно по цене стоп-приказа Buy установить ордер Sell Stop с параметрами первоначальной позиции Sell.

    Решением описанной задачи в советнике PhoenixDouble занимается функция SetPendings:

 
//+-------------------------------------------------------------------------------------+
  //| Подготовка данных для установки отложенных ордеров                                  |
  //+-------------------------------------------------------------------------------------+
  void SetPendings(int ID, int Rev, int Type, double SLD, double TPD, double SLR, 
                   double TPR)
  {
  // - 1 - ==================== Если по текущей системе необходимо открыть длинную ========
   if (Type == OP_BUY)
     {
      if (SLR - Spread < TPD) 
        {// Установить BuyStop на уровне SLR с ИД = ID
         PenType[] = OP_BUYSTOP;
         OpPrice[] = SLR;
         SLPrice[] = SLD;               // Уровни стопа и профита берем от текущей системы
         TPPrice[] = TPD;
         OrdID[] = ID;
        }
       else
        {// Установить SellLimit на уровне TPD с ИД = Rev
         PenType[] = OP_SELLLIMIT;
         OpPrice[] = TPD;
         SLPrice[] = SLR;       // Уровни стопа и профита берем от противоположной системы
         TPPrice[] = TPR;       
         OrdID[] = Rev;
        } 
      if (TPR - Spread < SLD) 
        {// Установить SellStop на уровне SLD с ИД = Rev
         PenType[1] = OP_SELLSTOP;
         OpPrice[1] = SLD;
         SLPrice[1] = SLR;       // Уровни стопа и профита берем от противоположной системы
         TPPrice[1] = TPR;       
         OrdID[1] = Rev;
        }
       else
        {// Установить BuyLimit на уровне TPR с ИД = ID
         PenType[1] = OP_BUYLIMIT;
         OpPrice[1] = TPR;
         SLPrice[1] = SLD;               // Уровни стопа и профита берем от текущей системы
         TPPrice[1] = TPD;       
         OrdID[1] = ID;
        } 
     }
  // - 1 - ===================================== Окончание блока ==========================
    else
  // - 2 - ==================== Если по текущей системе необходимо открыть короткую =======
     {
      if (SLR + Spread > TPD) 
        {// Установить SellStop на уровне SLR с ИД = ID
         PenType[] = OP_SELLSTOP;
         OpPrice[] = SLR;
         SLPrice[] = SLD;               // Уровни стопа и профита берем от текущей системы
         TPPrice[] = TPD;       
         OrdID[] = ID;
        }
       else
        {// Установить BuyLimit на уровне TPD с ИД = Rev
         PenType[] = OP_BUYLIMIT;
         OpPrice[] = TPD;
         SLPrice[] = SLR;       // Уровни стопа и профита берем от противоположной системы
         TPPrice[] = TPR;       
         OrdID[] = Rev;
        } 
      if (TPR + Spread > SLD) 
        {// Установить BuyStop на уровне SLD с ИД = Rev
         PenType[1] = OP_BUYSTOP;
         OpPrice[1] = SLD;
         SLPrice[1] = SLR;       // Уровни стопа и профита берем от противоположной системы
         TPPrice[1] = TPR;       
         OrdID[1] = Rev;
        }
       else
        {// Установить SellLimit на уровне TPR с ИД = ID
         PenType[1] = OP_SELLLIMIT;
         OpPrice[1] = TPR;
         SLPrice[1] = SLD;               // Уровни стопа и профита берем от текущей системы
         TPPrice[1] = TPD;       
         OrdID[1] = ID;
        } 
     } 
  // - 2 - ===================================== Окончание блока ==========================
  }

        При вызове функции, ей передаются такие параметры:

  • ID, Rev - идентификаторы текущей и противоположной ей системы соответственно. Прямой системе соответствует значение 0, а реверсной 1.

  • Type - тип позиции, которую нужно открыть. Хотя на самом деле позиция открываться не будет.

  • SLD, TPD - уровни стоп-приказа и профита устанавливаемой позиции.

  • SLR, TPR - уровни стоп-приказа и профита существующей позиции.

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

        В элементах массива OpPrice сохраняются цены открытия ордеров, в TPPrice - уровни профита, а в SLPrice - уровни стоп-приказов. Для последующей идентификации позиций, которые возникнут при срабатывании ордеров, также указываем принадлежность к одной из систем, заполняя элементы массива OrdID идентификаторами 0 или 1.

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

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

 
//+-------------------------------------------------------------------------------------+
  //| Установка отложенных ордеров                                                        |
  //+-------------------------------------------------------------------------------------+
  void SetOrders()
  {
   for (int i = 1; i >= 0; i--)
     if (PenType[i] > 0)
       if (OpenOrderCorrect(PenType[i], OpPrice[i], SLPrice[i], TPPrice[i], OrdID[i]) == 0)
         PenType[i] = 0;
        else
         return; 
  }

        Так как ордеров всего два, то итераций цикла for тоже две. Установка выполняется, если текущий элемент PenType больше нуля. После успешной установки ордера элемент PenType заполняется нулем, сигнализируя, что данный ордер в дальнейшем устанавливать не нужно.

         Увеличение количества типов ордеров, которыми оперирует эксперт PhoenixDouble, по сравнению с SimpleMACrossDouble, требует дополнения кода функции нахождения своих ордеров OrdersFind. В новой версии функция должна следить не только за позициями (Buy или Sell), но и за ордерами (Buy Stop, Sell Stop, Buy Limit, Sell Limit). Также у функции появляются новые "обязанности". Так как отложенные ордера должны быть связанными (срабатывание одного ордера должно отменять противоположный), функция следит за количеством отложенных ордеров. Если остался лишь один ордер, то его необходимо удалить. Решаются все эти задачи при помощи такого кода:

 
//+-------------------------------------------------------------------------------------+
  //| Поиск позиций с заполнением массивов тикетов BuyTicket и SellTicket                 |
  //+-------------------------------------------------------------------------------------+
  bool OrdersFind()
  {
  // - 1 - ======================== Инициализация массивов тикетов ======================== 
   ArrayInitialize(BuyTicket, -1);
   ArrayInitialize(SellTicket, -1);
   ArrayInitialize(BSTicket, -1);
   ArrayInitialize(SSTicket, -1);
   ArrayInitialize(BLTicket, -1);
   ArrayInitialize(SLTicket, -1);
   int Pendings = 0;
   IsPendings = False;
  // - 1 - ========================= Окончание блока ====================================== 
  
  // - 2 - ========================= Подсчет своих позиций ================================ 
   for (int i = OrdersTotal()-1; i >= 0; i--)
     if (OrderSelect(i, SELECT_BY_POS))
       if (OrderSymbol() == Symbol() && MathFloor(OrderMagicNumber()/10) == MagicNumber)
         {
          int ID = MathMod(OrderMagicNumber(), 10);
          switch (OrderType())
            {
             case OP_BUY: 
                 BuyTicket[ID] = OrderTicket(); 
                 BuySL[ID] = OrderStopLoss(); 
                 BuyTP[ID] = OrderTakeProfit();
             break;
             case OP_SELL: 
                 SellTicket[ID] = OrderTicket(); 
                 SellSL[ID] = OrderStopLoss();
                 SellTP[ID] = OrderTakeProfit();
             break;
             case OP_BUYSTOP: BSTicket[ID] = OrderTicket(); Pendings++; break;
             case OP_SELLSTOP: SSTicket[ID] = OrderTicket(); Pendings++; break;
             case OP_BUYLIMIT: BLTicket[ID] = OrderTicket(); Pendings++; break;
             case OP_SELLLIMIT: SLTicket[ID] = OrderTicket(); Pendings++; break;
            }
        } 
  // - 2 - ========================= Окончание блока ======================================
  
  // - 3 - ============== Ликвидация лишнего ордера из пары связанных ордеров =============
   if (Pendings == 1)    // Существует только один отложенный ордер, необходимо удалить его
     if (!DeletePendings())
       return(False);
  // - 3 - ========================= Окончание блока ======================================
   if (Pendings == 2) IsPendings = True;
   return(True);
  }

    В первом блоке к массивам тикетов BuyTicket и SellTicket добавлено четыре массива BSTicket (для ордеров Buy Stop), SSTicket (для ордеров Sell Stop), BLTicket (для ордеров Buy Limit) и SLTicket (для ордеров Sell Limit).  Флаг IsPending сигнализирует о наличии отложенных ордеров, а переменная Pendings - об их  количестве.

    Второй блок немного расширен, так как необходимо рассортировывать шесть типов ордеров. Для позиций, в дополнение к тикетам, в массивах BuySL, SellSL, BuyTP и SellTP сохраняются уровни стопов и профитов.

    Обязанностью третьего блока является слежение за количеством ордеров. Если остался лишь один ордер, то следует передача управления функции удаления отложенного ордера DeletePendings:  

 
//+-------------------------------------------------------------------------------------+
  //| Удаление всех отложенных ордеров                                                    |
  //+-------------------------------------------------------------------------------------+
  bool DeletePendings()
  {
   for (int i = 0; i < 2; i++)
     {                                                       
      int Ticket = 0;
      if (BSTicket[i] > 0) Ticket = BSTicket[i];
      if (BLTicket[i] > 0) Ticket = BLTicket[i];
      if (SSTicket[i] > 0) Ticket = SSTicket[i];
      if (SLTicket[i] > 0) Ticket = SLTicket[i];
      if (Ticket > 0)
        {
         if (WaitForTradeContext())
           if (OrderDelete(Ticket))
             continue;
         return(False);  
        }   
     }
   return(True);
  }

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

    Перед тем, как связать все функции воедино при помощи функции start, рассмотрим последнюю вспомогательную функцию Trades:

 
//+-------------------------------------------------------------------------------------+
  //| Открытие и закрытие позиций                                                         |
  //+-------------------------------------------------------------------------------------+
  bool Trades()
  {
  // - 1 - ============================ Инициализация "рабочих" переменных ================
   int i = MathAbs(Signal);                         // Количество итераций цикла 0, 1 или 2
   while (i > 0)
     {
      int ID = MathFloor(i/2);            // Идентификатор системы 0 - прямая, 1 - обратная
      int Rev = MathAbs(ID-1);                     // Идентификатор противоположной системы
  // - 1 - ===================================== Окончание блока ==========================
    
  // - 2 - ==================== Действия при сигнале открытия длинных позиций =============
      if (Signal > 0)                                                  
        {
         double SL = NP(MathMin(MathMin(iLow(Symbol(), PERIOD_D1, 1), 
                                        iLow(Symbol(), PERIOD_D1, 0)),
                                iLow(Symbol(), PERIOD_D1, 2)) - Tick);
         double TP = NP(Ask + 4*iATR(Symbol(), 0, 24, 1));                                
         if (SellTicket[ID] > 0)                             // найден SELL текущей системы
           {                                             // нужно закрыть его и открыть BUY
            if (!ClosePos(SellTicket[ID]))                         // Попытка закрытия SELL
              return(False);
            if (SellTicket[Rev] > 0)                 // найден SELL противоположной системы
              {                            // закрываем его, и ставим два отложенных ордера
               if (!ClosePos(SellTicket[Rev]))
                 return(False);
               // Команда установки отложенных ордеров
               SetPendings(ID, Rev, OP_BUY, SL, TP, SellSL[Rev], SellTP[Rev]);
              }
             else                  // не найден SELL противоположной системы, открываем BUY
              if (OpenOrderCorrect(OP_BUY, NP(Ask), SL, TP, ID) != 0)
                return(False); 
           }
          else 
           if (BuyTicket[ID] < 0)// не найден BUY текущей системы, т. е. вообще нет позиций
                                                                        // текущей системы. 
                                    // Проверяем существование SELL противоположной системы
             if (SellTicket[Rev] > 0)          // Если найден SELL, то закрываем его, но не
               {                                                           // открываем BUY
                if (!ClosePos(SellTicket[Rev]))
                  return(False);
               // Команда установки отложенных ордеров
                SetPendings(ID, Rev, OP_BUY, SL, TP, SellSL[Rev], SellTP[Rev]);
               } 
              else
               // Если не было закрытия SELL на текущей свече или существует BUY 
               // противоположной системы, то открываем новый BUY
               if (BuyTicket[Rev] > 0 || !IsCloseOnThisCandle(OP_SELL, Rev))
                 if (OpenOrderCorrect(OP_BUY, NP(Ask), SL, TP, ID) != 0)
                   return(False);
        }           
  // - 2 - ===================================== Окончание блока ==========================
         
  // - 3 - ==================== Действия при сигнале открытия коротких позиций ============
      if (Signal < 0)
        {
         SL = NP(MathMax(MathMax(iHigh(Symbol(), PERIOD_D1, 0), 
                                 iHigh(Symbol(), PERIOD_D1, 1)), 
                         iHigh(Symbol(), PERIOD_D1, 2)) + Spread + Tick);
         TP = NP(Bid - 4*iATR(Symbol(), 0, 24, 1));                                
         if (BuyTicket[ID] > 0)                               // найден BUY текущей системы
           {                                            // нужно закрыть его и открыть SELL
            if (!ClosePos(BuyTicket[ID]))           // Попытка закрытия BUY текущей системы
              return(False);
            if (BuyTicket[Rev] > 0)                   // найден BUY противоположной системы
              {                                // закрываем его, но новый SELL не открываем
               if (!ClosePos(BuyTicket[Rev]))//Попытка закрытия BUY противоположной системы
                 return(False);
               // Команда установки отложенных ордеров
               SetPendings(ID, Rev, OP_SELL, SL, TP, BuySL[Rev], BuyTP[Rev]);
              }
             else          // Нет позиции BUY противоположной системы, можно открывать SELL
              if (OpenOrderCorrect(OP_SELL, NP(Bid), SL, TP, ID) != 0)
                return(False);  
           }
          else                                           // Нет позиции BUY текущей системы
           if (SellTicket[ID] < 0)      // не найден SELL текущей системы, т. е. вообще нет
                   // текущей системы. Проверяем существование SELL противоположной системы
             if (BuyTicket[Rev] > 0)            // Если найден BUY противоположной системы,
               {                                  // то закрываем его, но SELL не открываем
                if (!ClosePos(BuyTicket[Rev]))
                  return(False);
               // Команда установки отложенных ордеров
                SetPendings(ID, Rev, OP_SELL, SL, TP, BuySL[Rev], BuyTP[Rev]);
               }
              else
               // Если не было закрытия BUY на текущей свече или существует SELL 
               // противоположной системы, то открываем новый SELL
               if (SellTicket[Rev] > 0 || !IsCloseOnThisCandle(OP_BUY, Rev))
                 if (OpenOrderCorrect(OP_SELL, NP(Bid), SL, TP, ID) != 0)
                   return(False);  
        }           
  // - 3 - ===================================== Окончание блока ==========================
             
      i = i - 2;            // Учитываем, что сигнал мог быть по обратной системе с весом 2
     } 
   return(True);
  }

По сравнению с этой же функцией эксперта SimpleMACrossDouble, произошли следующие изменения:

  • В начале блоков 2 и 3 добавлен расчет уровней стопов и профитов

  • После закрытия позиции противоположной системы следует вызов функции SetPendings с тем, чтобы впоследствии были установлены отложенные ордера

  • При вызове функции OpenOrderCorrect, открывающей соответствующие позиции, передаются рассчитанные уровни стоп-приказа и профита

Благодаря введению новых функций, код функции Trades не очень увеличился, все основные команды остались на своих местах.

Связующее звено эксперта - функция start - выглядит следующим образом:

 
//+-------------------------------------------------------------------------------------+
  //| Функция START эксперта                                                              |
  //+-------------------------------------------------------------------------------------+
  int start()
    {
  // - 1 -  == Разрешено ли советнику работать? ===========================================
     if (!Activate || FatalError)             // Отключается работа советника, если функция
      return();           //  init завершилась с ошибкой  или имела место фатальная ошибка
  // - 1 -  == Окончание блока ============================================================
       
  // - 2 - == Сбор информации об условиях торговли ========================================
     Spread = ND(MarketInfo(Symbol(), MODE_SPREAD)*Point);                  // текщий спрэд
     StopLevel = ND(MarketInfo(Symbol(), MODE_STOPLEVEL)*Point);  // текущий уровень стопов 
     FreezeLevel = ND(MarketInfo(Symbol(), MODE_FREEZELEVEL)*Point);   // уровень заморозки
  // - 2 -  == Окончание блока ============================================================
  
  // - 3 - ======== Установка отложенных ордеров и подсчет своих позиций ==================
     if (PenType[] > 0)         // Если необходимо установить отложенные ордера, то больше
       {                                                   // ничего на этом тике не делаем
        SetOrders();
        return();
       } 
     if (!OrdersFind()) return();                                 // Подсчет своих позиций
  // - 3 -  == Окончание блока ============================================================
  
  // - 4 - ======== Обработка существующей позиции и контроль открытия нового бара ========
     if (LastBar == Time[])
       return();
  // - 4 -  == Окончание блока ============================================================
  
  // - 5 - ======================== Расчет сигнала ========================================
     GetSignal();
  // - 5 -  == Окончание блока ============================================================
  
  // - 6 - == Открытие позиций ============================================================
     if (Signal != 0)
       {
        if (IsPendings)                          // Если присутствуют отложенные ордера, то
          if (!DeletePendings())                                     // пытаемся удалить их
            return();
        if (!Trades())                                          // Открытие/закрытие сделок
          return();
       }   
  // - 6 -  == Окончание блока ============================================================
  
     LastBar = Time[];
  
     return();
    }

     Первый и второй блоки во всех наших экспертах стандартные.

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

    Четвертый блок следит за тем, чтобы эксперт "не перетрудился", а именно: не выполнял действия, которые уже были успешно выполнены за время текущей свечи.

    Пятый блок содержит вызов одной единственной функции, которая генерирует сигналы открытия позиции. Результат ее работы отображается в переменной Signal.

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

   Код эксперта PhoenixDouble готов и теперь можно переходить к проверке его работоспособности, а также проверке работоспобности самой стратегии. Перед тем, как подвергнуть тестированию сводную систему, подберем оптимальные периоды быстрых и длинных средних для прямой и реверсной системы каждой валютной пары. Для этого потребуется произвести оптимизацию экспертов Phoenix_Direct и Phoenix_Reverse за 2008 год. Тестирование сводной системы будем производить на историческом периоде 01.01.2008 - 13.03.2010, то есть в два раза большем, чем период оптимизации. Результаты оптимизации приведены в таблице 1:

 
Валютная пара Phoenix_Direct Phoenix_Reverse
Период быстрой средней Период медленной средней Период быстрой средней Период медленной средней
EURUSD 8 37 35 99
USDCHF 67 100 37 74
GBPUSD 94 97 4 36
USDJPY 42 97 3  

Табл. 1. - результаты оптимизации эспертов Phoenix_Direct и Phoenix_Double

На основании полученных данных производим тестирование советника на всех четырех валютных парах (см. рис. 4 - 7).

                Рис. 4. - Результаты тестирования эксперта PhoenixDouble на валютной паре EURUSD.

        EURUSD. Вид кривой баланса свидетельствует о низкой стабильности системы. Хотя следует отметить, что общая направленность вверх присутствует. Чистая прибыль достигла значения 3472 доллара, а максимальная просадка смогла подпортить репутацию стратегии, добравшись до 1559 долларов. В итоге фактор восстановления оказался небольшим - 2.22. Но и такой цифры достаточно для занесения стратегии в разряд перспективных.

                                      Рис. 5. - Результаты тестирования эксперта PhoenixDouble на валютной паре USDCHF.

        USDCHF. Окончание тестирование немного подпортило как вид кривой баланса, так и статистику тестирования. Но просто закрыть глаза на последние 40 сделок невозможно. Поэтому учитываем и их. Чистая прибыль 2219 долларов против максимальной просадки 865 долларов дают фактор восстановления 2.56. По сравнению с результатом евро, статистика франка является более предпочтительной. Здесь и фактор восстановления выше, и просадка ниже.

                                    Рис. 6. - Результаты тестирования эксперта PhoenixDouble на валютной паре GBPUSD.

        GBPUSD. Стабильность системы ниже, чем у евро. Единственный показатель, который оказался лучше, это чистая прибыль - 6853 доллара. А вот максимальная просадка самая высокая из всех результатов 3656. Такое огромное значение просадки, вместе с низким фактором восстановления, ставит крест на использовании системы применительно к валютной паре GBPUSD.

                        Рис. 7. - Результаты тестирования эксперта PhoenixDouble на валютной паре USDJPY.

        USDJPY.  Кривая баланса самая "прямая" из всех представленных результатов. Идеальность портят сделки с 90 до 250. По статистическим показателям результаты тоже самые лучшие - чистая прибыль 4860 долларов, а максимальная просадка 982 доллара, что дает фактор восстановления 4.95. Поэтому приходим к выводу, что йена для эксперта PhoenixDouble наиболее перспективная в плане торговли на реальном счете.

 

Доработка стратегии для использования в AutoGraf 4.0

    Доработаем код эксперта PhoenixDouble для использования в качестве присоединяемой стратегии в среде AutoGraf 4.0. Для задания параметров каждой из четырех средних скользящих отведем по четыре входных параметра AutoGraf, начиная с AT_1 и заканчивая AT_16. Очередность параметров соответствует очередности, представленной в PhoenixDouble. Например, AT_1 - FastMAPeriod, AT_2 - FastMAShift, AT_3 - FastMAMethod, AT_4 - FastMAPrice, а AT_16 - SlowMAprice_Rev. Объем позиции устанавливается при помощи панели инструментов AutoGraf под надписью Lots.

 

Для запуска советника из-под AutoGraf 4.0 совершите такие шаги:

  • Воспользуйтесь ссылкой Файлы стратегий для Autograf 4.0 и распакуйте полученный архив в папку MT4\experts\libraries.

  •  Запустите AutoGraf.

  •  Для работы советника в ключе приведенных результатов в окне настроек AutoGraf (закладка "Входные параметры") выставьте правильные значения параметров AT_1 - AT_16 (полное повторение результатов при этом не гарантируется).

  • Выберите стратегию №3. Для этого передвиньте вверх значок So и среди названий стратегий найдите значок S3, который также потяните вверх.

  • Запустите функцию автоматической торговли, передвинув значок AT в верхнее положение.

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

     

 

Игорь Герасько

 

Март 2010

Специально для компании Admiral Markets

3
 
 

Пред.: Диверсификация рисков
След.: Дневные тени

Комментарии

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

Содержание этого поля является приватным и не предназначено к показу.
CAPTCHA
Проверка на СПАМ:
   ____    ___                  _   _    ____   ____  
/ ___| / _ \ __ _ ____ | | | | / ___| | _ \
| | | | | | / _` | |_ / | | | | | | | |_) |
| |___ | |_| | | (_| | / / | |_| | | |___ | _ <
\____| \__\_\ \__, | /___| \___/ \____| |_| \_\
|_|
Enter the code depicted in ASCII art style.
Общие вопросы
info@forextrade.ru
Техническая поддержка
support@fxservice.com
Необходимые файлы и документы
Филиальная сеть
Условия перепечатки материалов с сайта
Уведомление о рисках
Карта сайта
Политика конфиденциальности
Мы принимаем Webmoney Мы принимаем Webmoney
iPhone iTrader Android Trader BlackBerry Trader MetaTrader для мобильных устройств MetaTrader Web Trader iPad iTrader