Обсуждение статьи "Кроссплатформенный торговый советник: Сигналы"

 

Опубликована статья Кроссплатформенный торговый советник: Сигналы:

В статье обсуждаются классы CSignal и CSignals, которые будут использоваться в кроссплатформенных торговых советниках. Рассмотрены различия между MQL4 и MQL5 в организации данных, необходимых для оценки полученных торговых сигналов. Итог — код, совместимый с компиляторами обеих версий.

Наш последний пример — комбинация индикаторов MA и HA, включенная в советник. Отличий в этом примере немного. Мы просто добавляем определения классов, находящиеся во втором и третьем примерах, и затем добавляем указатели на экземпляры CSignalMA и CSignalHA в экземпляр CSignals. Ниже показан результат пробного использования эксперта.

(MT4)

signal_ha_ma (MT4)

Автор: Enrico Lambino

 
MetaQuotes Software Corp.:

Новая статья Кроссплатформенный советник: Сигналы опубликована:

Автор: Энрико Ламбино


Привет, Энрико. Я только что просмотрел вашу работу в поисках решения моей проблемы. При компиляции класса StopBase я получаю список ошибок, см. ниже.

implicit conversion from 'number' to 'string'   OrderStopBase.mqh       395     25
implicit conversion from 'number' to 'string'   OrderStopBase.mqh       467     26
implicit enum conversion        OrderStopBase.mqh       468     33
implicit enum conversion        OrderStop.mqh   37      33
implicit conversion from 'number' to 'string'   OrderStop.mqh   44      43
'COrderBase' - import not defined       OrderStop.mqh   54      7
'else' - semicolon expected     OrderStop.mqh   81      4
')' - unexpected token  OrderStop.mqh   81      46
implicit conversion from 'number' to 'string'   StopBase.mqh    767     22
implicit conversion from 'number' to 'string'   StopBase.mqh    774     28
implicit conversion from 'number' to 'string'   StopBase.mqh    796     22
implicit conversion from 'number' to 'string'   StopBase.mqh    803     28
implicit conversion from 'number' to 'string'   StopBase.mqh    698     36
'OrderType' - cannot convert enum       Stop.mqh        227     52
'StopLossCustom' - no one of the overloads can be applied to the function call  Stop.mqh        228     61
could be one of 2 function(s)   Stop.mqh        228     61
   double CStopBase::StopLossCustom(const string,const ENUM_ORDER_TYPE,const double)    StopBase.mqh    136     22
   bool CStopBase::StopLossCustom()     StopBase.mqh    99      22
'OrderType' - cannot convert enum       Stop.mqh        215     71
'TakeProfitCustom' - no one of the overloads can be applied to the function call        Stop.mqh        215     98
could be one of 2 function(s)   Stop.mqh        215     98
   double CStopBase::TakeProfitCustom(const string,const ENUM_ORDER_TYPE,const double)  StopBase.mqh    140     22
   bool CStopBase::TakeProfitCustom()   StopBase.mqh    111     22
implicit conversion from 'number' to 'string'   Stop.mqh        250     39
implicit enum conversion        Stop.mqh        291     31
implicit conversion from 'number' to 'string'   Stop.mqh        292     36

Не обращая внимания на предупреждения, ошибки, похоже, возникают из-за проблем с типом заказа. Кроме того, компиляция вашего примера signal_ma приводит к следующим ошибкам

'GetPointer' - parameter conversion not allowed OrderStopVirtualBase.mqh        51      39
'GetPointer' - parameter conversion not allowed OrderStopVirtualBase.mqh        58      41
'=' - type mismatch     OrderStopsBase.mqh      106     23
'=' - type mismatch     OrderStopsBase.mqh      108     23
'=' - type mismatch     OrderStopsBase.mqh      110     23
'=' - type mismatch     OrderStopsBase.mqh      180     20
'=' - type mismatch     OrderStopsBase.mqh      182     20
'=' - type mismatch     OrderStopsBase.mqh      184     20

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

Заранее спасибо за помощь и за отличную работу.
 

Здравствуйте, Шепард, спасибо за ваш комментарий. Что касается ваших вопросов:

  1. К сожалению, на данный момент нет возможности скомпилировать StopBase.mqh самостоятельно. Хотя компиляторы MQL4 и MQL5 принимают прямое объявление, они выдают ошибку, если вы пытаетесь получить доступ к методу или члену класса, который был объявлен прямо. Вам придется скомпилировать StopBase.mqh с более крупным классом, таким как COrderManager или CExpertAdvisor (CStop и CStops являются компонентами этих двух классов).
  2. Что касается ошибок несоответствия типов, то я не обнаружил их в исходных файлах (были ли изменены исходники с вашей стороны?).
  3. Думаю, в большинстве случаев базовые классы можно без проблем передавать в методы классов. При кодировании экспертов, использующих библиотеку, проще считать, что базовых классов вообще не существует. Но у некоторых объектов есть новые виртуальные методы, а также невиртуальные методы. Эти два набора методов не могут быть доступны только базовому классу. Если вы используете производные классы вместо базовых, компилятор может выбрать правильную версию производных классов, поэтому использование производных классов является более полным, чем использование одних только базовых классов.
 
Enrico Lambino:

Здравствуйте, Шепард, спасибо за ваш комментарий. Что касается ваших вопросов:

  1. К сожалению, на данный момент нет возможности скомпилировать StopBase.mqh самостоятельно. Хотя компиляторы MQL4 и MQL5 принимают прямое объявление, они выдают ошибку, если вы пытаетесь получить доступ к методу или члену класса, который был объявлен прямо. Вам придется скомпилировать StopBase.mqh с более крупным классом, таким как COrderManager или CExpertAdvisor (CStop и CStops являются компонентами этих двух классов).
  2. Что касается ошибок несоответствия типов, то я не обнаружил их в исходных файлах (были ли изменены исходники с вашей стороны?).
  3. Думаю, в большинстве случаев базовые классы можно без проблем передавать в методы классов. При кодировании экспертов, использующих библиотеку, проще считать, что базовых классов вообще не существует. Но у некоторых объектов есть новые виртуальные методы, а также невиртуальные методы. Эти два набора методов не могут быть доступны только базовому классу. Если вы используете производные классы вместо базовых, компилятор может выбрать правильную версию производных классов, поэтому использование производных классов является более полным, чем использование только базовых классов.

Привет, Энрико,

Большое спасибо за быстрый ответ. Я понимаю, почему вы передаете конкретные классы вместо их базовых классов. Некоторые реализации, которые просты в C++, просто невозможны в MQL, но так уж сложилось.

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

Я разработал свою систему следующим образом. Я рассматривал систему вывода сигналов как стратегию. Скажем, у меня есть система пересечения МА, если я создаю объект, скажем, EURUSD, то это уже стратегия. Я добавляю эту стратегию в список стратегий. Я могу создать еще одну стратегию и добавить ее в список. Но стратегии вызываются не по OnTick, скорее, они являются частью паттерна Observer. Они обновляются или вызываются, когда происходят определенные события, например, новый бар на 5 Minute, новый бар 10 Pip Renko и т.д. У меня были проблемы со стопами, что и послужило толчком для моего исследования.

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

 

Привет, Шепард,

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

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

Что касается компиляции:

  1. Без ошибок могут быть скомпилированы только основные файлы исходных текстов/заголовков и файлы базовых классов (за исключением тех, в которых используются прямые объявления). Именно поэтому в примерах я использовал директиву #include в заголовочных файлах для базовых файлов (а не в файлах, относящихся к конкретному языку).
  2. Необходимо использовать правильный компилятор (компиляция файла mq5 компилятором MQL4 приведет к ошибкам компилятора).
 

Привет, Энрико,

Я тестирую ваш модуль сигналов. У меня есть один сигнал SHORT и один сигнал NEUTRAL. Как получилось, что я получил CMD_VOID? Когда появляется CMD_VOID?

На самом деле это появляется в CSignalsBase::Check

if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }

Есть только 2 сигнала. Предыдущим сигналом был CMD_SHORT. Текущий сигнал - CMD_NEUTRAL. Можете ли вы подтвердить, что CMD_SHORT и CMD_NEUTRAL дают в результате CMD_VOID?

Если первый сигнал был CMD_NEUTRAL, а второй CMD_SHORT, то общий сигнал будет CMD_SHORT. Но если первый сигнал CMD_SHORT, а второй CMD_NEUTRAL, то это дает CMD_VOID.

Я полагаю, что это должно быть так:

if(signal.Entry())
        {
         if(m_signal_open>CMD_VOID)
           {
            ENUM_CMD signal_open=signal.SignalOpen();
            if(m_signal_open==CMD_NEUTRAL)
              {    
               m_signal_open=signal_open;
              }
            else if(m_signal_open!=signal_open && signal_open!=CMD_NEUTRAL)
              {               
               m_signal_open=CMD_VOID;
              }
           }
        }
 
if(m_new_signal)
     {
      if(m_signal_open==m_signal_open_last)
         m_signal_open = CMD_NEUTRAL;
      if(m_signal_close==m_signal_close_last)
         m_signal_close= CMD_NEUTRAL;
     }

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

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

 
mbjen:

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

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

Это зависит от того, чего вы хотите добиться, как должны оцениваться сигналы.

Последний код, который вы опубликовали, выполняется только тогда, когда член класса m_new_signal установлен в true. Это нужно только для торговли по новым сигналам. Вы можете установить этот защищенный член класса с помощью метода NewSignal, доступного в классе.

 
Enrico Lambino:

Это зависит от того, чего вы хотите добиться, как должны оцениваться сигналы.

Последний код, который вы опубликовали, выполняется только тогда, когда член класса m_new_signal установлен в true. Это нужно только для торговли по новым сигналам. Вы можете установить этот защищенный член класса с помощью метода NewSignal, доступного в классе.


Я знаю это. Но если я установлю false, это также повлияет на мои сигналы входа. Это нормально для сигнала входа, но не для выхода, поскольку правила выхода могут быть разными. Это может быть реверс или сигнал выхода, так что 2 сигнала выхода в одном направлении - это нормальное явление.

 

Похоже, что в m_signal_close сохраняется предыдущее значение сигнала в SignalBase.mqh. Например, у меня есть сигнал выхода. Если он проверяет его и метод Calculate() возвращает false, то он выдает последнее значение сигнала, которое было при предыдущем выходе.

 

Привет, mbjen,

mbjen:

Привет, Энрико,

Я тестирую ваш модуль сигналов. У меня есть один сигнал SHORT и один сигнал NEUTRAL. Как получилось, что я получил CMD_VOID? Когда появляется CMD_VOID?

На самом деле это появляется в CSignalsBase::Check

Есть только 2 сигнала. Предыдущим сигналом был CMD_SHORT. Текущий сигнал - CMD_NEUTRAL. Можете ли вы подтвердить, что CMD_SHORT и CMD_NEUTRAL дают в результате CMD_VOID?

Если первый сигнал был CMD_NEUTRAL, а второй CMD_SHORT, то общий сигнал будет CMD_SHORT. Но если первый сигнал CMD_SHORT, а второй CMD_NEUTRAL, то это дает CMD_VOID.

Наверное, так и должно быть:

Извините, что пропустил ваш первый вопрос. Я не замечал его до сих пор. Это был очень хороший отзыв.

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

mbjen:

Я знаю это. Но если я установлю false, это также повлияет на мои сигналы входа. Это нормально для сигнала входа, но не для выхода, поскольку правила выхода могут быть разными. Это может быть реверсия или сигнал выхода, так что 2 сигнала выхода в одном направлении - это нормальное явление.

Здесь вы правы. Но, например, если я хочу подавать сигнал выхода только при пересечении МА, это будет необходимо. Я думаю, что их разделение было бы лучшим вариантом, чем текущий код:

if(m_new_signal)
 {
  if(m_signal_open==m_signal_open_last)
  m_signal_open = CMD_NEUTRAL;
 }
if(m_new_signal_close)
 {
  if(m_signal_close==m_signal_close_last)
  m_signal_close= CMD_NEUTRAL;
 }
mbjen:

Похоже, что в m_signal_close сохраняется предыдущее значение сигнала в SignalBase.mqh. Например, у меня есть сигнал выхода. Если он проверяется и метод Calculate() возвращает false, то он выдает последнее значение сигнала, которое было при предыдущем выходе.

Метод Calculate является виртуальным методом. Если метод должен возвращать false, вы должны сбросить эти члены класса в нейтральное состояние из самого метода.