Discussion of article "Cross-Platform Expert Advisor: Signals"

 

New article Cross-Platform Expert Advisor: Signals has been published:

This article discusses the CSignal and CSignals classes which will be used in cross-platform expert advisors. It examines the differences between MQL4 and MQL5 on how particular data needed for evaluation of trade signals are accessed to ensure that the code written will be compatible with both compilers.

All the other calculations needed for the generation of the current signal were moved to the CSignal and CSignals objects. Thus, all we need to do is to have CSignals perform a check, and then gets its output by invoking its methods CheckOpenLong and CheckOpenShort. The following screen shots show the results of testing the expert on MetaTrader 4 and MetaTrader 5:

(MT4)

signal_ordermanager (MT4)

Author: Enrico Lambino

 
MetaQuotes Software Corp.:

New article Cross-Platform Expert Advisor: Signals has been published:

Author: Enrico Lambino


Hi Enrico. I have just been looking at your work in search of a solution to my problem. On compiling the StopBase class, I get a list of errors, please see below

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

Ignoring the warnings, the errors seem to come from order type issues. Also, compiling your example signal_ma yields the following errors

'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

Where are these errors coming from please and how can I resolve them?  On related issue, from my understanding, the Liskov Substitution stipulates that we should program to an interface as the super class can be substituted with its base class. Why have you chosen to pass objects into the functions(which are in themselves interfaces), instead of using their base classes? I might be wrong but I believe that would be best practice would it not?

Thanks for your help in advance and for the great work you did.
 

Hi Shephard, thank you for your comment. Regarding your questions:

  1. Unfortunately, there is no way at this point to compile StopBase.mqh on its own. While the MQL4 and MQL5 compilers accept forward declarations, they give an error if you try to access a method or member of the class that was forwardly declared. You will need to compile StopBase.mqh with a much larger class, such as COrderManager or CExpertAdvisor (CStop and CStops is a component of these two classes).
  2. Regarding the type mismatch errors, I did not find them on the original files (were the source modified on your end?).
  3. I think in most cases, the base classes can be passed to class methods with no problems. When coding experts using the library, it would be easier to assume that the base class do not exist at all. But some objects have new virtual methods as well as non-virtual methods. These two sets of methods cannot be accessed by the base class alone. If you use the derived classes instead of the base, the compiler is able to select the correct version of the derived classes, so using the derived classes is more comprehensive than using the base classes alone. 
 
Enrico Lambino:

Hi Shephard, thank you for your comment. Regarding your questions:

  1. Unfortunately, there is no way at this point to compile StopBase.mqh on its own. While the MQL4 and MQL5 compilers accept forward declarations, they give an error if you try to access a method or member of the class that was forwardly declared. You will need to compile StopBase.mqh with a much larger class, such as COrderManager or CExpertAdvisor (CStop and CStops is a component of these two classes).
  2. Regarding the type mismatch errors, I did not find them on the original files (were the source modified on your end?).
  3. I think in most cases, the base classes can be passed to class methods with no problems. When coding experts using the library, it would be easier to assume that the base class do not exist at all. But some objects have new virtual methods as well as non-virtual methods. These two sets of methods cannot be accessed by the base class alone. If you use the derived classes instead of the base, the compiler is able to select the correct version of the derived classes, so using the derived classes is more comprehensive than using the base classes alone. 

Hi Enrico,

Thanks very much for your quick response. I understand your reasoning behind passing concrete classes instead of their base classes. Certain implementations that are straightforward in C++ are just not possible in MQL, but that is the way it is.

With regards to type mismatches, no, I did not change anything. I simply downloaded and compiled. I was looking at your work in search of an annoying problem I had in my own system.  Your approach is really great.  

I developed my system like this. I considered a signal outputting system as a strategy.  Say I have an MA crossover system, if I create an object of this with say EURUSD, this is a strategy.  I add this strategy into a list of strategies. I can create another strategy, and add it to the list.  But the strategies are called not by OnTick, rather, they are part of an Observer pattern.  They are updated, or called when specific events occur, say a new bar on 5 Minute, a new 10 Pip Renko bar etc.  I had problems with the Stops, which triggered my research.

Thanks once again for taking time to share your knowledge and skill, and your prompt response.

 

Hi Shephard,

Thanks for sharing your suggestions and insights.

I think what you are working on is a great idea. In am currently working on the last few articles for this series (order stops and stops included).  I am not sure if the Stops classes are a good fit (it depends on your implementation). If you can make them work independently of the order manager, that would be good because it would help simplify your class objects. But I hope that you may find them to be useful in your work.

Regarding the compilation:

  1. Only main source/header files and base class files can be compiled without errors (with the exception of those that use forward declarations). This is the reason why on the examples, I used the #include directive on the header files for the base files (not the language-specific files).
  2. You should use the right compiler (compiling an mq5 file with an MQL4 compiler will lead to compiler errors).
 

Hi Enrico,

I'm testing your signals module. I've got one signal SHORT and one signal NEUTRAL. How come I finally got CMD_VOID? When CMD_VOID appears?

Actually this appears in 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;
              }
           }
        }

There are only 2 signals. Previous signal was CMD_SHORT. Current signal is CMD_NEUTRAL. Can you confirm that CMD_SHORT and CMD_NEUTRAL give as a result CMD_VOID?

If my first signal was CMD_NEUTRAL and the second signal CMD_SHORT the total signal would be CMD_SHORT. But if the first signal CMD_SHORT and the second CMD_NEUTRAL it gives CMD_VOID.

I guess it must be like this:

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;
     }

This is not fully correct in terms of close signals. Why there cannot be two consequent same direction close signals?

For example I have entry signal Short, next exit signal Long (closing short), next entry signal Long, next entry signal Short, next exit signal Long. So the last signal for exit short position will not work since it's the same direction as previous exit signal.

 
mbjen:

This is not fully correct in terms of close signals. Why there cannot be two consequent same direction close signals?

For example I have entry signal Short, next exit signal Long (closing short), next entry signal Long, next entry signal Short, next exit signal Long. So the last signal for exit short position will not work since it's the same direction as previous exit signal.

It depends on what you wanted to achieve on how the signals should be evaluated.

The last code you posted is only executed when the class member m_new_signal is set to true. This is for trading on new signals only. You can set this protected class member by using the method NewSignal available in the class.

 
Enrico Lambino:

It depends on what you wanted to achieve on how the signals should be evaluated.

The last code you posted is only executed when the class member m_new_signal is set to true. This is for trading on new signals only. You can set this protected class member by using the method NewSignal available in the class.


I know that. But if I set it to false it will also affect my entry signals. It's ok for entry signal but not for exit since exit rules can be different. It can be reversion or exit signal so 2 same direction exit signals is a normal thing.

 

It looks like in m_signal_close is saved previous signal value in SignalBase.mqh. For example I have some exit signal. If it checks it and Calculate() method returns false it gives last signal value which was at previous exit.

 

Hi mbjen,

mbjen:

Hi Enrico,

I'm testing your signals module. I've got one signal SHORT and one signal NEUTRAL. How come I finally got CMD_VOID? When CMD_VOID appears?

Actually this appears in CSignalsBase::Check

There are only 2 signals. Previous signal was CMD_SHORT. Current signal is CMD_NEUTRAL. Can you confirm that CMD_SHORT and CMD_NEUTRAL give as a result CMD_VOID?

If my first signal was CMD_NEUTRAL and the second signal CMD_SHORT the total signal would be CMD_SHORT. But if the first signal CMD_SHORT and the second CMD_NEUTRAL it gives CMD_VOID.

I guess it must be like this:

I am sorry for overlooking your first question. I had not noticed it until now. It was a very good feedback.

Thank you for pointing this out. Yes, you are right. I will update the code and revise the article.

mbjen:

I know that. But if I set it to false it will also affect my entry signals. It's ok for entry signal but not for exit since exit rules can be different. It can be reversion or exit signal so 2 same direction exit signals is a normal thing.

You have a point here. But for example, if I only want to give an exit signal at an MA crossover, this would be needed. I think separating them would be a better option than the current code:

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:

It looks like in m_signal_close is saved previous signal value in SignalBase.mqh. For example I have some exit signal. If it checks it and Calculate() method returns false it gives last signal value which was at previous exit.

The Calculate method is a virtual method. If the method is to return false, you should reset those class members to neutral from within the method itself.

Reason: