Multiple indicators on one chart (Part 04): Advancing to an Expert Advisor

Daniel Jose | 9 May, 2022

Introduction

In my previous articles, I have explained how to create an indicator with multiple subwindows, which becomes interesting when using custom indicators. This was pretty easy to do. But when we try to implement the same functionality in an Expert Advisor, things become a little more complicated since we do not have the tools that we used in a custom indicator. At this point, programming becomes essential: being able to write the correct code to create a subwindow is paramount. Even though this task is not that easy, knowing how to put a subwindow in an EA doesn't involve a lot of coding, just some knowledge of how MQL5 works.


Planning

We already have our custom indicator working, that is, our object class is already functioning, and since this is an object class, we can easily transfer it to other models. However, simply declaring and trying to use the class in our EA won't make things work the same as in our custom indicator, and the reason is that we don't have subwindow capability in our EA. But then the idea came: "What if we use an already compiled and working custom indicator and call it from the EA using the iCustom command? Well, that might actually work since the subwindow is not needed and the command would look like this:

#property copyright "Daniel Jose"
//+------------------------------------------------------------------+
input string user01 = "";                //Used indicators
input string user02 = "";                //Assets to follow
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//... Expert Advisor code ...

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), 0, m_handleSub)) return INIT_FAILED;
//... Expert Advisor code ...

        ChartRedraw();
        
        return(INIT_SUCCEEDED);
}
//...The rest of the Expert Advisor code ...

This simple code snippet is able to load our custom indicator, although it won't work properly because we don't have a subwindow. In this case, when the code is executed in the EA, the EA will apply our indicator directly in the main window, which means that our chart will be hidden by the templates loaded by the indicator, which is definitely not what we are looking for.

Therefore, our real and main problem is to create a subwindow that can be used so that we can use our already functioning indicator. But why create a subwindow for the subsequent launch of our indicator? It doesn't make sense; it's better to add functionality directly to our EA and thus overcome any limitations that may arise.

Based on this, we need to perform several tasks:

Task Purpose
1 => Create a general-purpose indicator. It allows to create and use the iCustom command without polluting the chart.
2 => Include this indicator into the EA in some way.  This will allow you to transfer the Expert Advisor with full functionality without any problems.
3 => Generate a general object class for the subwindow  Allows adding subwindows via the EA
4 => Get our C_TemplateChart class bound to the window class. This will allow us to manage the contents of subwindows without changing anything in the already running code.

Although this may seem hard, the difficulties are solved quite simply. So, let's deal with each of the points.


Implementation: Creating a general-purpose indicator

This part can be solved by creating a completely clean but functional custom indicator code. The code in this case will look like this:

#property copyright "Daniel Jose"
#property version   "1.00"
#property description "This file only enables support of indicators in SubWin."
#property indicator_chart_window
#property indicator_plots 0
//+------------------------------------------------------------------+
int OnInit()
{
        return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[])
{
        return rates_total;
}
//+------------------------------------------------------------------+

Only this and nothing more. Let's save this file as SubSupport.mq5. But it won't be located together with other indicators — instead let's move it to the RESOURCE directory of our Expert Advisor. So, the file structure will look like in the picture below:


There is a good reason for this but let's put it aside for now. Now let's move on to the next task.


Implementation: Including the general indicator in the EA

To do this, we need to add the following code to the top of our EA.

//+------------------------------------------------------------------+
#define def_Resource "Resources\\SubSupport.ex5"
//+------------------------------------------------------------------+
#resource def_Resource
//+------------------------------------------------------------------+

This will include the compiled code of the general indicator into our EA. Once this is done, the general indicator will have the .ex5 file deleted as it is no longer needed. Now you should pay attention to the fact that if the SubSupport.ex5 file is not found at the time the EA code is being compiled, the compiler will automatically compile the code of the SubSupport. mq5 general indicator and will add this newly compiled executable file to our Expert Advisor. So, if you ever edit the SubSupport.mq5 file and need to add the changes into the Expert Advisor, you should delete SubSupport.ex5; otherwise, the changes will not be added.

This detail is important: sometimes you really need to know how to add newly implemented changes to the resource.

Well, the general indicator is now part of the Expert Advisor, so let's move on to the next task.


Implementation : Creating a subwindow object class

This part is also simple. Here we need to define some points before coding, namely: what features do we really need in this class? Initially, I decided to use the following:

Function Description
Init Allows adding subwindows via the EA
Close Allows adding subwindows via the EA

These functions will not be tested, so I assume that they will only be called once during the lifetime of the EA. But as our EA grows, it's good to think about making it even more practical in the future. Therefore, let's create a new object class called C_Terminal - this class will support a few things related to the graphical terminal. We will learn more about it later. Let's look at the last task, since there is no way to implement the solution partially.


Implementation: C_TemplateChart class inheritance

When I decided to create something new using OOP (Object Oriented Programming), I did this because I already knew that there are big advantages to using this approach, including security and inheritance. There is also polymorphism, but we will use it later when creating a cross-order system. In this particular case we will use one of the advantages of OOP - inheritance. C_TemplateChart is already a fully functional class. Seeing this, you wouldn't want to have the trouble of reprogramming everything again or run the risk of adding code to the class while this would prevent the class from being used in other places. The solution is to use inheritance which allows the addition of new code or function without changing the original code at all.

Using inheritance has a number of advantages, including the following: already tested code remains tested; complexity grows without an equal increase in code size; only new features really need to be tested; what does not change is simply inherited, providing stability. In other words, things improve with minimal effort, but with maximum security. To understand this, let's look at the diagram below.

The grandparent class is the most basic class where we have the lowest level of data manipulation, but when the parent class inherits something from the grandparent, all things declared as public in the grandparent class can be seen and used by the parent class. And we can also add new things to the parent class, and this does not affect what is inherited and supported by inheritance. If the parent class is already finished and working, and we want to extend it without changing anything in the classes below, then we create a child class, and it will have all the features of the previous classes. We can also change the way the things work, and that's the interesting thing about inheritance, because these changes will not affect other classes. However, there is a limitation here, unlike C++ which allows multiple inheritance. If a child can inherit functions from both the Father and the Mother side, this is not possible in MQL5. But you still get benefit from the inheritance. An example of multiple inheritance can be seen below:

Okay, but how to do it in MQL5? How to declare an inheritance so that we can take the advantage of it? The most accurate way to understand this is to read the object-oriented programming (OOP) content but here we'll get straight to the point. Inheritance will be done using the following lines:

#include "C_TemplateChart.mqh"
//+------------------------------------------------------------------+
class C_SubWindow : public C_TemplateChart
{
// ... Class code
};

See that the C_SubWindow class will publicly inherit the C_TemplateChart class, so now we can use the C_SubWindow class to access the C_TemplateChart class functionality.

In the above code snippet, I highlighted one thing. Note that it is in quotes ( " ) and not in angle brackets ( < > ) as usual. So why did I do this? Like the C++ language, MQL5 also has some very interesting things, but some things confuse those who are just starting to learn the art of programming. When we place a header file between angle brackets ( < > ), we mean an absolute path — in this case the compiler will follow exactly the path we specified. But when we use quotes (as we did this time), the compiler will use a relative path, or, to make it clearer, it will first start from the current directory where the working file is located. It may seem strange, but there are times when we have the same name for files that have different contents and they are in different directories, but we still want to refer to the current directory, so we use quotes for that.

The two functions that we plan to use earlier, INIT and CLOSE, are shown below:

//+------------------------------------------------------------------+
bool Init(void)
{
        if (m_handleSub != INVALID_HANDLE) return true;
        if ((m_handleSub = iCustom(NULL, 0, "::" + def_Resource)) == INVALID_HANDLE) return false;
        m_IdSub = (int) ChartGetInteger(Terminal.Get_ID(), CHART_WINDOWS_TOTAL);
        if (!ChartIndicatorAdd(Terminal.Get_ID(), m_IdSub, m_handleSub)) return false;
                
        return true;
}
//+------------------------------------------------------------------+
void Close(void)
{
        ClearTemplateChart();
        if (m_handleSub == INVALID_HANDLE) return;
        IndicatorRelease(m_IdSub);
        ChartIndicatorDelete(Terminal.Get_ID(), m_IdSub, ChartIndicatorName(Terminal.Get_ID(), m_IdSub, 0));
        ChartRedraw();
        m_handleSub = INVALID_HANDLE;
}
//+------------------------------------------------------------------+

See, the code is very simple and short. But there is something we have to be careful about, pay attention to the highlighted part. You must be careful not to make a mistake while adding this part, because if you don't leave it as it is, the SubSupport.ex5 executable file that we asked to be added to the EA will not be visible inside the EA — instead it will be visible outside the EA. For more details, you can read about Resources. But basically, if you use ( :: ), this will indicate that the EA should use the internal resource available inside it. But if we just indicate the name of the resource, the EA will search for it in inside the MQL5 directory, and if the file does not exist at the specified location, the function will fail even if the file was added as an EA resource.

Then, once the resource is loaded, we check the number of subwindows present and add an indicator to that subwindow.

What the code is actually doing can be seen below:

input string user01 = "";               //Used indicators
input string user02 = "";               //Assets to follows
//+------------------------------------------------------------------+
int OnInit()
{
        int m_handleSub;

//...   

        if ((m_handleSub = iCustom(NULL, 0, "Chart In SubWindows\\Chart In SubWindow.ex5", user01, user02)) == INVALID_HANDLE) return INIT_FAILED;
        if (!ChartIndicatorAdd(ChartID(), (int) ChartGetInteger(ChartID(), CHART_WINDOWS_TOTAL), m_handleSub)) return INIT_FAILED;

//...

        ChartRedraw();
        
   return(INIT_SUCCEEDED);
}
//...The rest of the Expert Advisor code ...

Both codes will work the same, but the object class version will allow us to add more things over time, since the version shown above is the consolidated version and won't change. Both versions do the same thing: they create a subwindow from the EA and put all previously created custom indicators in this subwindow. Pay attention to the changes made to the code compared to the code at the beginning of the article - the changes are highlighted in color.


Conclusion

It is very interesting how we decide to follow the path of achieving our goals. Sometimes we can encounter difficulties and think that it is difficult to achieve our goals, but with a little patience and dedication we can overcome obstacles that at first seemed insurmountable. In this article, I demonstrate how you can extend the functionality of a class without having to modify it — through inheritance. At the same time, I show how you can add indicators to charts so that they work as already tested. We add the ex5 program inside our EA and use it without having to port the original ex5 by simply loading the EA.

The attached file contains all the improvements developed so far, but there will be even more interesting things in this code soon. 😁👍