Передача торговых сигналов в универсальном советнике.

Igor Kim | 6 ноября, 2006


Введение


Однажды я предпринимал попытку универсализировать и унифицировать процесс разработки советников в теме "Разработка универсального советника". Предполагалась деятельность по выработке некоего стандарта разработки основных модулей-кирпичиков, из которых в будущем можно было бы собирать советники, как из деталей конструктора. Частично я эту работу выполнил, предложив структуру универсального советника и решение по универсальному использованию сигналов различных индикаторов. В данной статье я продолжу свою работу, попытавшись универсализировать формирование и передачу торговых сигналов и управление позициями в советниках. Сразу оговорюсь, что позициями я называю торговые операции BUY и SELL и все нижесказанное имеет отношение только к ним. Отложенные ордера BUYLIMIT, BUYSTOP, SELLLIMIT и SELLSTOP пока не рассматриваются, но в конце статьи я покажу, что предлагаемый мной подход легко переносится и на них.

Классификация торговых сигналов


Торговые сигналы могут быть следующие:
  1. Купить;
  2. Продать;
  3. Докупить (доливка, усреднение);
  4. Допродать (доливка, усреднение);
  5. Закрыть покупку полностью;
  6. Закрыть продажу полностью;
  7. Закрыть покупку частично;
  8. Закрыть продажу частично.

Если решения о доливках, усреднениях и частичных закрытиях вынести из модуля формирования торговых сигналов в модуль управления позициями, то данный перечень легко можно сократить до:

  1. Купить;
  2. Продать;
  3. Закрыть покупку;
  4. Закрыть продажу;
  5. Ничего не делать (для нормальной работы советника понадобится еще и такой сигнал).

Исходя из вышесказанного предполагается следующая схема работы советника:

  1. Сигнальный модуль формирует торговый сигнал;
  2. Торговый сигнал поступает в модуль управления позициями, который принимает решение о новых открытиях, о доливках/усреднениях, о частичных или полных закрытиях и передает сокращенный торговый сигнал в модуль исполнения торговых сигналов;
  3. Модуль исполнения торговых сигналов занимается непосредственным выполнением торговых операций.
В данной статье я хочу рассмотреть различные способы формирования торговых сигналов и способы их передачи в модуль управления позициями, то есть интерфейс между вышеуказанными модулями из пунктов 1 и 2.

Свинговая торговля одной позицией


Суть такой торговли в том, что перед открытием новой позиции предыдущая закрывается. И новая позиция противоположна предыдущей. Если была покупка, то открывается продажа и наоборот. Так как моменты открытия и закрытия позиций совмещены во времени, сигналы закрытия могут быть сокращены. Таким образом, для реализации свинговой торговли становится достаточным передавать только три торговых сигнала: купить, продать и ничего не делать. А передать эти сигналы можно посредством только одной переменной целого типа. Например:

    1 - покупай;
    0 - ничего не делай;
   -1 - продавай.

Тогда блок анализа рыночной ситуации и формирования торгового сигнала легко выносится в отдельную функцию, например, GetTradeSignal(),
//+----------------------------------------------------------------------------+
//|  Возвращает торговый сигнал:                                               |
//|     1 - покупай                                                            |
//|     0 - ничего не делай                                                    |
//|    -1 - продавай                                                           |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
int GetTradeSignal(string sym="", int tf=0) 
  {
   int bs=0;
   if (sym=="") sym=Symbol();
 
   // Блок анализа с присвоением значения переменной bs
 
   return(bs);
  }
//+----------------------------------------------------------------------------+

которая и возвращает вышеперечисленные целые значения. Вызов этой функции удобно делать непосредственно в модуле управления позициями.
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
   double sl=0, tp=0;
   int    bs=GetTradeSignal();
 
   if(bs>0) 
     {
      if(ExistPositions("", OP_SELL)) 
          ClosePositions("", OP_SELL);
      if(!ExistPositions("", OP_BUY)) 
        {
          if(StopLoss!=0) 
              sl=Ask-StopLoss*Point;
          if(TakeProfit!=0) 
              tp=Ask+TakeProfit*Point;
          OpenPosition("", OP_BUY, sl, tp);
        }
     }
   if(bs<0) 
     {
       if(ExistPositions("", OP_BUY)) 
           ClosePositions("", OP_BUY);
       if(!ExistPositions("", OP_SELL)) 
         {
           if(StopLoss!=0) 
               sl=Bid+StopLoss*Point;
           if(TakeProfit!=0) 
               tp=Bid-TakeProfit*Point;
           OpenPosition("", OP_SELL, sl, tp);
         }
     }
  }
//+----------------------------------------------------------------------------+

В данном случае одна единственная локальная переменная bs целого типа выполняет роль связующего звена между двумя модулями. Полный текст примера кода советника для свинговой торговли одной позицией можно посмотреть в файле e-SampleSwing.mq4.

Простая торговля одной позицией


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

     2 - закрывай продажу;
     1 - покупай;
     0 - ничего не делай;
   -1 - продавай;
   -2 - закрывай покупку.

Модуль управления позициями уместится в функции:
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    double sl=0, tp=0;
    int    bs=GetTradeSignal();
 
    if(ExistPositions()) 
      {
        if(bs==2) 
            ClosePositions("", OP_SELL);
        if(bs==-2) 
            ClosePositions("", OP_BUY);
      } 
    else 
      {
        if(bs==1) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(bs==-1) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
//+----------------------------------------------------------------------------+
Полный текст примера кода советника для простой торговли одной позицией приведeн в файле e-SampleSimple.mq4.

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


Существует три способа разделения:

  1. Открытия в одной переменной, закрытия в другой. Можно будет совмещать открытия с закрытиями, то есть организовывать свинговую торговлю, но нельзя будет открываться в обе стороны и закрывать разносторонние позиции;
  2. Покупки (открытия и закрытия) в одной переменной, а продажи (открытия и закрытия) в другой. Можно будет открываться в обе стороны и закрывать сразу  все позиции, но нельзя будет одновременно закрыть и открыть, например, покупку или продажу, то есть переоткрыться;
  3. Открытие покупки и закрытие продажи в одной переменной, а открытие продажи и закрытие покупки в другой. Также можно будет открываться в обе стороны и закрывать все позиции, но нельзя будет организовать свинговую торговлю.
Возьмем для реализации второй вариант, так как переоткрытие всё-таки востребовано редко. Никто не хочет лишний раз терять деньги на спрэде. А тем временем второй вариант может быть реализован несколькими способами.

     1. В две локальные переменные возвращают значения две функции. Одна функция формирует сигналы для покупок, вторая - для продаж.
//+----------------------------------------------------------------------------+
//|  Возвращает торговый сигнал для длинных позиций:                           |
//|     1 - покупай                                                            |
//|     0 - ничего не делай                                                  |
//|    -1 - закрывай покупку                                                   |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
int GetTradeSignalBuy(string sym="", int tf=0) 
  {
    int bs=0;
    if(sym=="") 
        sym=Symbol();
 
    // Блок анализа с присвоением значения переменной bs
 
    return(bs);
  }
 
//+----------------------------------------------------------------------------+
//|  Возвращает торговый сигнал для коротких позиций:                          |
//|     1 - продавай                                                           |
//|     0 - ничего не делай                                                  |
//|    -1 - закрывай продажу                                                   |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
int GetTradeSignalSell(string sym="", int tf=0) 
  {
    int bs=0;
    if(sym=="") 
        sym=Symbol();
 
    // Блок анализа с присвоением значения переменной bs
 
    return(bs);
  }
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    double sl=0, tp=0;
    int    bs=GetTradeSignalBuy();
    int    ss=GetTradeSignalSell();
 
    if(ExistPositions()) 
      {
        if(bs<0) 
            ClosePositions("", OP_BUY);
        if(ss<0) 
            ClosePositions("", OP_SELL);
      }
    if(!ExistPositions()) 
      {
        if(bs>0) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(ss>0) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
//+----------------------------------------------------------------------------+
Это простой и удобный способ формирования и передачи торгового сигнала, но сигнальный модуль разделяется на две функции, что, скорее всего, замедлит  его работу.

     2. Две глобальные переменные получают значения в одной функции.

//+----------------------------------------------------------------------------+
BuySignal=0;
SellSignal=0;
 
//+----------------------------------------------------------------------------+
//|  Формирует торговые сигналы.                                               |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
void FormTradeSignals(string sym="", int tf=0) 
  {
    if(sym=="") sym=Symbol();
 
    // Блок анализа с присвоением значений переменным BuySignal и SellSignal
  }
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    double sl=0, tp=0;
 
    FormTradeSignals();
    if(ExistPositions()) 
      {
        if(BuySignal<0) 
            ClosePositions("", OP_BUY);
        if(SellSignal<0) 
            ClosePositions("", OP_SELL);
      }
    if(!ExistPositions()) 
      {
        if(BuySignal>0) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(SellSignal>0) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
//+----------------------------------------------------------------------------+

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

     3. Две локальные переменные передаются в одну функцию по ссылке.

//+----------------------------------------------------------------------------+
//|  Возвращает торговые сигналы.                                              |
//|  Параметры:                                                                |
//|    bs  - сигнал для покупок                                                |
//|    ss  - сигнал для продаж                                                 |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
void GetTradeSignals(int& bs, int& ss, string sym="", int tf=0) 
  {
    if(sym=="") 
        sym=Symbol();
 
    // Блок анализа с присвоением значений переменным bs и ss
  }
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    double sl=0, tp=0;
    int    bs=0, ss=0;
 
    GetTradeSignals(bs, ss);
    if(ExistPositions()) 
      {
        if(bs<0) 
            ClosePositions("", OP_BUY);
        if(ss<0) 
            ClosePositions("", OP_SELL);
      }
    if(!ExistPositions()) 
      {
        if(bs>0) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(ss>0) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
//+----------------------------------------------------------------------------+

     4. Массив из двух элементов. Если глобальный, то инициализируется внутри функции. Если локальный, то передаётся по ссылке. Здесь всё просто. Вместо двух переменных один массив. Глобальные переменные рассмотрены, локальные по ссылке тоже. Ничего нового.


И наконец, можно организовать полноценный параллельный интерфейс передачи торговых сигналов, разбросав их по четырём переменным. Так как для случая управления позициями каждый сигнал имеет два состояния: есть или нет, то и тип переменных удобнее выбрать логический. Комбинировать сигналы, передавая их посредством четырёх переменных, можно без ограничений. Приведу пример кода для способа с массивом из четырёх элементов логического типа.
//+----------------------------------------------------------------------------+
//|  Возвращает торговые сигналы.                                              |
//|  Параметры:                                                                |
//|    ms  - массив сигналов                                                   |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
void GetTradeSignals(bool& ms[], string sym="", int tf=0) 
  {
    if(sym=="") 
        sym=Symbol();
 
    // Блок анализа с заполнением массива ms:
    // ms[0]=True;   // Buy
    // ms[1]=True;   // Sell
    // ms[2]=True;   // Close Buy
    // ms[3]=True;   // Close Sell
  }
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    bool   ms[4]={False, False, False, False};
    double sl=0, tp=0;
 
    GetTradeSignals(ms);
    if(ExistPositions()) 
      {
        if(ms[2]) 
            ClosePositions("", OP_BUY);
        if(ms[3]) 
            ClosePositions("", OP_SELL);
      }
    if(!ExistPositions()) 
      {
        if(ms[0]) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(ms[1]) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
//+----------------------------------------------------------------------------+

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

Поддержка основной позиции


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


Реализовать передачу торговых сигналов для такой тактики можно как посредством одной переменной, так и двух и четырёх. Это уже рассмотрено, повторяться не буду. Трудность, с которой придётся столкнуться, это обеспечение связки один сигнал - одна позиция. В случае с торговой тактикой с одной позицией этот вопрос решался простой проверкой наличия позиции функцией ExistPositions(). Есть позиция, сигнал на вход пропускается, нет позиции - выполняется.

Итак, способы решения:

  1. Организация паузы между входами. Самая простая реализация;
  2. Один бар - один вход. Разновидность первого способа. Реализация тоже простая;
  3. Нумерация сигналов и ведение массивов сигналов и тикетов ордеров. Это самая сложная реализация с сомнительной надёжностью.
В качестве примера приведу код для первого способа.
//+----------------------------------------------------------------------------+
extern int PauseBetweenEntrys=3600;    // Пауза между входами в секундах
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    bool   ms[4]={False, False, False, False};
    double sl=0, tp=0;
 
    GetTradeSignals(ms);
    if(ExistPositions()) 
      {
        if(ms[2]) 
            ClosePositions("", OP_BUY);
        if(ms[3]) 
            ClosePositions("", OP_SELL);
      }
    if(SecondsAfterOpenLastPos()>=PauseBetweenEntrys) 
      {
        if(ms[0]) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
        if(ms[1]) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
 
//+----------------------------------------------------------------------------+
//|  Возвращает количество секунд после открытия последней позиций.            |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    op  - операция                 (-1 - любая позиция)                     |
//|    mn  - MagicNumber              (-1 - любой магик)                       |
//+----------------------------------------------------------------------------+
datetime SecondsAfterOpenLastPos(string sym="", int op=-1, int mn=-1) 
  {
    datetime oot;
    int      i, k=OrdersTotal();
 
    if(sym=="") sym=Symbol();
        for(i=0; ik; i++) 
          {
            if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
              {
                if(OrderSymbol()==sym) 
                  {
                    if(OrderType()==OP_BUY || OrderType()==OP_SELL) 
                      {
                        if(op0 || OrderType()==op) 
                          {
                            if(mn0 || OrderMagicNumber()==mn) 
                              {
                                if(ootOrderOpenTime()) 
                                    oot=OrderOpenTime();
                              }
                          }
                      }
                 }
             }
        }
      return(CurTime()-oot);
  }
//+----------------------------------------------------------------------------+

Полный текст примера кода советника для тактики с поддержкой основной позиции находится в файле e-SampleMain.mq4.

Свинговая торговля с доливками


При поступлении сигнала на вход открывается одна позиция. На основании всех последующих сигналов в сторону первой позиции открываются новые дополнительные позиции. При поступлении сигнала против существующих позиций закрываются все позиции и открывается одна в сторону сигнала. Торговый цикл повторяется.

Передача и исполнение торговых сигналов для свинговой торговли одной позицией уже были рассмотрены. Переделаем этот пример для реализации возможности открытия дополнительных позиций. А для обеспечения связки один сигнал - одна позиция используем ограничение возможности входа временным интервалом одного бара. Один бар - один вход.
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    double sl=0, tp=0;
    int    bs=GetTradeSignal();
 
    if(bs>0) 
      {
        if(ExistPositions("", OP_SELL)) 
            ClosePositions("", OP_SELL);
        if(NumberOfBarLastPos()>0) 
          {
            if(StopLoss!=0) 
                sl=Ask-StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Ask+TakeProfit*Point;
            OpenPosition("", OP_BUY, sl, tp);
          }
      }
    if(bs<0) 
      {
        if(ExistPositions("", OP_BUY)) 
            ClosePositions("", OP_BUY);
        if(NumberOfBarLastPos()>0) 
          {
            if(StopLoss!=0) 
                sl=Bid+StopLoss*Point;
            if(TakeProfit!=0) 
                tp=Bid-TakeProfit*Point;
            OpenPosition("", OP_SELL, sl, tp);
          }
      }
  }
 
//+----------------------------------------------------------------------------+
//|  Возвращает номер бара открытия последней позиции или -1.                  |
//|  Параметры:                                                                |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//|    op  - операция                 (-1 - любая позиция)                     |
//|    mn  - MagicNumber              (-1 - любой магик)                       |
//+----------------------------------------------------------------------------+
int NumberOfBarLastPos(string sym="", int tf=0, int op=-1, int mn=-1) 
  {
    datetime oot;
    int      i, k=OrdersTotal();
 
    if(sym=="") 
        sym=Symbol();
    for(i=0; ik; i++) 
      {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) 
          {
            if(OrderSymbol()==sym) 
              {
                if(OrderType()==OP_BUY || OrderType()==OP_SELL) 
                  {
                    if(op0 || OrderType()==op) 
                      {
                        if(mn0 || OrderMagicNumber()==mn) 
                          {
                            if(ootOrderOpenTime()) oot=OrderOpenTime();
                          }
                      }
                  }
             }
         }
     }
   return(iBarShift(sym, tf, oot, True));
  }
//+----------------------------------------------------------------------------+

Полный текст примера кода советника для свинговой торговли с доливками приведён в файле e-SampleSwingAdd.mq4.

Портфельная тактика


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


На первый взгляд это самый сложный вариант исполнения торговых сигналов в плане программной реализации. Здесь кроме передачи торговых сигналов нужно передавать принадлежность сигнала тому или иному портфелю. Но если сигналы разделить на группы, составляющие портфель, то будет видно, что каждая группа в отдельности - это простая торговля одной позицией. Способ объединения групп в портфель напрашивается сам собой: каждой группе присвоить уникальный номер и в цикле выполнять перебор всех групп. Вот пример для четырёх групп сигналов.
//+----------------------------------------------------------------------------+
//|  Возвращает торговые сигналы.                                              |
//|  Параметры:                                                                |
//|    ms  - массив сигналов                                                   |
//|    ns  - номер сигнала                                                     |
//|    sym - наименование инструмента ("" - текущий символ)                    |
//|    tf  - таймфрейм                ( 0 - текущий таймфрейм)                 |
//+----------------------------------------------------------------------------+
void GetTradeSignals(bool& ms[], int ns, string sym="", int tf=0) 
  {
    if (sym=="") sym=Symbol();
 
    // Коммутация групп сигналов операторами switch или if
    // Блок анализа с заполнением массива ms:
    // ms[0]=True;   // Buy
    // ms[1]=True;   // Sell
    // ms[2]=True;   // Close Buy
    // ms[3]=True;   // Close Sell
  }
 
//+----------------------------------------------------------------------------+
//|  Управление позициями                                                      |
//+----------------------------------------------------------------------------+
void ManagePositions() 
  {
    bool   ms[4];
    double sl=0, tp=0;
    int    i;
 
    for(i=0; i4; i++) 
      {
        ArrayInitialize(ms, False);
        GetTradeSignals(ms, i);
        if(ExistPositions("", -1, MAGIC+i)) 
          {
            if(ms[2]) ClosePositions("", OP_BUY, MAGIC+i);
            if(ms[3]) ClosePositions("", OP_SELL, MAGIC+i);
          }
        if(!ExistPositions("", -1, MAGIC+i)) 
          {
            if(ms[0]) 
              {
                if(StopLoss!=0) 
                    sl=Ask-StopLoss*Point;
                if(TakeProfit!=0) 
                    tp=Ask+TakeProfit*Point;
                OpenPosition("", OP_BUY, sl, tp, MAGIC+i);
              }
            if(ms[1]) 
              {
                if(StopLoss!=0) 
                    sl=Bid+StopLoss*Point;
                if(TakeProfit!=0) 
                    tp=Bid-TakeProfit*Point;
                OpenPosition("", OP_SELL, sl, tp, MAGIC+i);
              }
          }
      }
  }
//+----------------------------------------------------------------------------+

Полный текст примера кода советника для портфельной тактики находится в файле e-SampleCase.mq4.

Торговые сигналы для ордеров


Рассмотрим перечень торговых сигналов, необходимых для работы с отложенными ордерами.

  1. Установить BuyLimit;
  2. Установить SellLimit;
  3. Установить BuyStop;
  4. Установить SellStop;
  5. Удалить BuyLimit;
  6. Удалить SellLimit;
  7. Удалить BuyStop;
  8. Удалить SellStop;
  9. Модифицировать BuyLimit;
  10. Модифицировать SellLimit;
  11. Модифицировать BuyStop;
  12. Модифицировать SellStop.
Даже сильно напрягать фантазию не нужно, чтобы увидеть, что с отложенными ордерами может возникнуть необходимость передать одновременно четыре и более торговых сигналов. А если логика советника потребует работы как с позициями, так и с ордерами, тогда что?

Вот уточненный полный перечень всех торговых сигналов.
  1. Открыть Buy;
  2. Открыть Sell;
  3. Установить BuyLimit;
  4. Установить SellLimit;
  5. Установить BuyStop;
  6. Установить SellStop;
  7. Закрыть Buy;
  8. Закрыть Sell;
  9. Удалить BuyLimit;
  10. Удалить SellLimit;
  11. Удалить BuyStop;
  12. Удалить SellStop;
  13. Модифицировать BuyLimit;
  14. Модифицировать SellLimit;
  15. Модифицировать BuyStop;
  16. Модифицировать SellStop.
Шестнадцать торговых сигналов! Два байта! Была ещё мысль передавать сигналы, как двоичное число, но в строке. Например, 00101..., в котором позиция нуля или единички отвечала бы за определённый сигнал. Мысль-то была, но возня с подстроками для дешифрации сигналов... Таким образом, по всему выходит, что самое удобное решение - это массив с количестом элементов, равным количеству сигналов. Кроме того для повышения удобства обращения к элементам массива индексы можно определить, как константы. В MQL4 уже есть OP_BUY, OP_SELL и так далее. Что нам мешает доопределить:
#define OP_CLOSEBUY         6
#define OP_CLOSESELL        7
#define OP_DELETEBUYLIMIT   8
#define OP_DELETESELLLIMIT  9
#define OP_DELETEBUYSTOP   10
#define OP_DELETESELLSTOP  11
#define OP_MODIFYBUYLIMIT  12
#define OP_MODIFYSELLLIMIT 13
#define OP_MODIFYBUYSTOP   14
#define OP_MODIFYSELLSTOP  15
и обращаться к элементам массива  ms[OP_BUY] или ms[OP_MODIFYSELLLIMIT] вместо ms[0] и ms[13]? Но это уже, как говорится, дело вкуса. Я за компактный и простой код, поэтому предпочитаю числа.

Идем дальше. Все, вроде бы, просто и красиво! Но не хватает ценовых уровней установки ордеров, а также стопов и тейкпрофитов для позиций. Ведь каждый торговый сигнал может нести с собой еще и определенный размер стопа и тейк-профита и определенный ценовой уровень установки ордера. Что делать? Как передать эту информацию вместе с торговым сигналом? Я предлагаю следующее решение.

  1. Объявлять двумерный массив с количеством строк, равным количеству сигналов, а столбцов - три;
  2. Первый столбец нулевым значением будет сигнализировать отсутствие сигнала, а ненулевым значением показывать наличие сигнала для позиций. Для ордеров это будет ценовой уровень установки ордера;
  3. Второй столбец - стопы;
  4. Третий столбец - тейк-профиты.

Полный текст примера кода советника для торговли с ордерами приведён в файле e-SampleOrders.mq4.


А если нам понадобится устанавливать несколько BuyLimit или несколько SellStop? Да ещё и позиции открывать? Тогда мы поступим так, как описано в разделе "Портфельная тактика" настоящей статьи, то есть организуем портфельную тактику с опознаванием позиций и ордеров по магик-номерам.

Заключение


Наступило время подвести итоги:

  1. Передаче торговых сигналов посредством одной переменной присущи недостатки последовательного интерфейса - в каждый конкретный момент времени может быть передан только один сигнал;
  2. Организация параллельного интерфейса приводит к увеличению количества переменных и немного усложняет их опрос, но позволяет одновременно передавать множество сигналов.

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