Zero values in Strategy Tester result page / Global Variables as trade signal between indicator & EA

 

Hi guys,

I get zero values in the Strategy Tester result page:

Did anyone ever face such a "funny" result page?

What I did: 

1. Select ranges for the parameters of the EA so I get 20 combinations.
2. Press "Start test" and watch the progress bar working slowly through all 20 combinations. Checking the agents tab, I see all CPU cores working slowly from 0% to 100%.
3. After I received the above results in that table, I double clicked the first one and got that parameter selection, then I went back to settings and deactivated optimization + clicked "visual mode".
4. The Strategy Tester opens in a new window and I can see my EA working just fine from start to end.
   -> I actually waited until the EA completed the very last bar, so I could stop at the breakpoints where all de-initialization is done. I stepped through all of these lines, no exceptions, no interruptions, all finishes and cleans up properly. The completed Strategy Tester window shows the final result.
5. I made sure the compilation is not a "debug version" (that is, I closed the MetaTrader5 platform and compiled the EA + all including indicators in MetaEditor).
6. Running the EA in live chart and debug mode with the current data works just fine; no errors on the expert or journal tab.
7. EDIT: A signal indicator places a signal into a global variable which my EA is reading (<-- Problem already fixed! this was the cause).

This error is driving me nuts... Anyone got some similar experiences and has some general ideas to share? (If a general approach doesn't help, I will ofc go deeper into detail by dissecting the EA into pieces)

Cheers,
Marcel

 

First idea: does the code contain any if-statements related to MQLInfoInteger(MQL_OPTIMIZATION) / MQLInfoInteger(MQL_TESTER) / MQLInfoInteger(MQL_VISUAL_MODE) that might have affect the order execution?

 

I make use of these here:

bool IsTester()
{
        return (bool)MQL5InfoInteger(MQL5_TESTER);
}

bool IsOptimization()
{
        return (bool)MQL5InfoInteger(MQL5_OPTIMIZATION);
}

bool IsVisualMode()
{
        return (bool)MQL5InfoInteger(MQL5_VISUAL_MODE);
}

bool IsRealtime()
{
        if(!IsTester() && !IsOptimization() && !IsVisualMode())
                return true;
        else
                return false;
}
 

Okay then.. it may be a completely different issue, but is definitely something to check. We don't know your code, so you should check where and how exactly these functions are used, e.g. something like "if (!IsOptimization()) {}" or "if (IsVisualMode()){}" preceding the part where you send your orders or maybe such statements that lead to invalid stops or positions sizes in the optimizer... hard to say without the code. So I would just hit ctrl-f and systematically search for all occurances of the above mentioned functions and check for what exactly their consequence is.


Btw: just to confirm that I understand the problem correctly: I guess the sentence

4. The Strategy Tester opens in a new window and I can see my EA working just fine from start to end.

means that in the single test your orders are executed correctly so that you observe a change in your resulting balance, right? Because if "working just fine" only means that you get no errors, there may be a thousand reasons why no trades are executed. My answer above was for the assumed situation that you only don't see order execution in the optimizer.

 

Sorry for this kind-of timewaster thread! However, thanks for looking into here.

The basic "problem" is that my EA depends on an indicator which drops signals (from ongoing calculations) as GlobalVariables.

[On a side note: When I implemented that indicator 5-6 years ago, I spent countless hours to figure out which is the actually correct index I have to access when I let the a) strategy tester run with live data / b) ST with historic data / and c) the whole EA run in a live chart.
At some point I figured out that using global variables in combination with templates worked as a nice communication layer between indicator and EA. It also worked correctly for all 3 scenarios, so I went along with that. At some point, I should probably revisit this code and see if I can avoid GVars at all..]

It's difficult to reveal the complete picture on a public forum, showing code details of my indicator/EA  that is having low DD and high profits with all simulations. Fate of the lonely coder, I guess...

I'm checking more stuff, maybe I can something suspicious, or I'll reveal parts of the stuff which could require some overhaul/fixing.

 
Marcel Fitzner:

Sorry for this kind-of timewaster thread! However, thanks for looking into here.

The basic "problem" is that my EA depends on an indicator which drops signals (from ongoing calculations) as GlobalVariables.

[On a side note: When I implemented that indicator 5-6 years ago, I spent countless hours to figure out which is the actually correct index I have to access when I let the a) strategy tester run with live data / b) ST with historic data / and c) the whole EA run in a live chart.
At some point I figured out that using global variables in combination with templates worked as a nice communication layer between indicator and EA. It also worked correctly for all 3 scenarios, so I went along with that. At some point, I should probably revisit this code and see if I can avoid GVars at all..]

It's difficult to reveal the complete picture on a public forum, showing code details of my indicator/EA  that is having low DD and high profits with all simulations. Fate of the lonely coder, I guess...

I'm checking more stuff, maybe I can something suspicious, or I'll reveal parts of the stuff which could require some overhaul/fixing.

Something to check :

You may get such problem when the Strategy Tester can't make the conversion from the profit currency to the account currency.

So for example if your profit currency is USD, your account is EUR but EURUSD is not available you will get such results.

 
In optimizing mode my experience is that globals sharing same names produce irrational results, while on single run there is no interference. I am not sure why.
 

Thank you guys for checking in! I could resolve my issue and the ST simulation shows trading profits again! It was a misconfigured indicator parameter in a chart template that caused the trading signals to not be loaded into a global variable properly.


If you like to know how I constructed my EA/indicator coupling - OR - you have similar experiences / better options / more efficient solutions, you're welcome to add your comments.


1. Usually, with a method like this (with a previously created indicator handler) one should be able to pull the data from an indicator buffer (e.g. a tradiging signal):

// Returns the indicator value based on 'index'

double CIndicatorWrapper::operator[](int index)
{
   double values[];
   if (m_handle == INVALID_HANDLE)
      return EMPTY_VALUE;
   if (CopyBuffer(m_handle, m_current_buffer, index, 1, values) == 0)
   {
      string text = "Failed copy buffer of indicator. Last error: " + (string)GetLastError();
      Print(text);
      return EMPTY_VALUE;
   }
   return values[0];
}

Unfortunately, I had trouble with it when I was accessing my trading signals in a timely fashion. If I call that method in my EA, it does not copy the signal buffer properly from the last bar in the aforementioned 3 scenarios:  a) EA runs with live data; b) Strategy Tester of the EA running with historic data; c) debugging / ST of the EA on a live data.

The indices [0] and [1] are tricky in these 3 scenarios and back then I was probably fed up or unable to write code that detects in which scenario the Strategy Tester is currently running.


2. So I found an article here on MQL5.com about creating a class that manages an indicator handler. Additionally to managing that indicator handle, it allows to [define /writes to/ reads from] a global variable (GV), through an initializer, a setter and a getter method, respectively.

Then I created a chart template with the name of that EA (e.g. MySpecialEA), I dropped my signal indicator + made sure all parameters are configured correctly -> then saved that chart template file.

When a single Strategy Tester or a set of simulations is running, that chart template makes sure the signal indicator is loaded. When the simulation starts, the indicator will automatically write its signals into the GV, and the EA can read this GV to perform trades based on each signal.


Here's some skeleton code which might be of use for you:

//+------------------------------------------------------------------+
//|                                                 SignalReader.mqh |
//|                                                   Marcel Fitzner |
//|                                          <opensource>@nomail.com |
//+------------------------------------------------------------------+
#property copyright "Open Source"
#property version       "1.00"

#include <GlobalVariables/cglobalvar.mqh>
#include <Strings/ToString.mqh>

/*
        This SignalReader include file allows reading from an indicator and creates a global variable (GVars)
        if it doesn't exist yet.
        
        Setter and getter methods allow to access the GVar.
        
        Setter methods should only be used in the actual indicator which eventually sets the value into that GVar, while 
        other Indicators or an Expert Advisors can read that GVars through the getter method.
*/

class CSignalReader
{
        public:
                                                CSignalReader(const string symbol, const ENUM_TIMEFRAMES period, string indicatorHandleName, string labelOfSignal);
                                                                                                                
                                                ~CSignalReader();
        
                int                             Init(bool initIndicatorHandle = false);
        
                double                          GetSignal()              const { //Print("Fetching GV signal: ", DoubleToString(GetGV(m_GV_Signal_name), 1));
                                                                                 return GetGV(m_GV_Signal_name); }
                datetime                        GetSignalTimestamp()     const { return m_GV_Signal_lastUpdate;  }
        
                void                		SetSignal(double value, datetime updateTime) { //PrintFormat("Setting GV signal: %s for time: %s", DoubleToString(value, 1), TimeToString(updateTime));
                                                                                               SetGV(m_GV_Signal_name, value);
                                                                                               m_GV_Signal_lastUpdate = updateTime;
                                                                                             }

        private:
                int                             InitIndicator();

                double                          GetGV(string name) const;
                void                            SetGV(string name, double value, bool verify = false);
        
                string                          m_IndicatorHandleName;
                int                                     m_handle;
                
                string                          m_LabelOfSignal;
        
                string                          m_Symbol;
                ENUM_TIMEFRAMES         m_Chart_Period;


                CGlobalVar*                     m_GV_Signal;
                string                          m_GV_Signal_name;
                
                datetime            m_GV_Signal_lastUpdate;
                                
                // Parameters for the indicator handle          
                int                             m_ParamInt1;		// exemplary indicator parameters
                double                          m_ParamDouble1;
                double                          m_ParamDouble2;
                int                             m_ParamInt2;
                int                             m_ParamInt3;
                bool                            m_ParamBool1;
};

CSignalReader::CSignalReader(const string symbol, const ENUM_TIMEFRAMES period, string indicatorHandleName, string labelOfSignal)
        :       m_Symbol(symbol),
                m_Chart_Period(period),
                m_IndicatorHandleName(indicatorHandleName),
                m_LabelOfSignal(labelOfSignal)
{ }

CSignalReader::~CSignalReader()
{
        delete m_GV_Signal;
        IndicatorRelease(m_handle);
}

int CSignalReader::Init(bool initIndicatorHandle = false)
{
        Print("Initializing Global Variable helper ...");
        if(initIndicatorHandle)
        {
                if (INIT_FAILED == InitIndicator())
                        return INIT_FAILED;
        } else
                Print("... without indicator handle.");

        m_GV_Signal_name = m_Symbol + "_" + TimeframeToString(m_Chart_Period) + "_" + m_LabelOfSignal; //"MY_SPECIAL_INDICATOR_SIGNAL"

        m_GV_Signal = new CGlobalVar(m_GV_Signal_name, 0.0, TimeCurrent());

        if(!GlobalVariableCheck(m_GV_Signal_name))
        {
                if(!m_GV_Signal.Create(m_GV_Signal_name, 0.0))
                        if(!m_GV_Signal.IsGlobalVar(m_GV_Signal_name)) {
                                return INIT_FAILED;                     
                        }
        }
        
        return INIT_SUCCEEDED;
}

int CSignalReader::InitIndicator()
{
        m_handle = iCustom(m_Symbol, m_Chart_Period, "FolderToMySpecialSignalIndicator\\MySpecialSignalIndicator",
                                                m_ParamInt1, m_ParamDouble1, m_ParamDouble2,
                                                m_ParamInt2, m_ParamInt3, m_ParamBool1);

        if(m_handle == INVALID_HANDLE) {
                PrintFormat("Error obtaining OptimisticPessimisticAreas indicator handle! Aborting initialization.");
                return INIT_FAILED;
        }
        Print("... with indicator handle!");
        
        return INIT_SUCCEEDED;
}

double CSignalReader::GetGV(string name) const
{
        double value = 0.0;

        if (!GlobalVariableGet(name, value)) {
                PrintFormat("Error! Could not get global variable %s with value %0.3f. Error Code: %d", name, value,  GetLastError());
                ResetLastError();
        }
        
        return value;
}
 

Dang, I just realize you also need the cglobalvar.mqh:

//+------------------------------------------------------------------+
//|                                                   CGlobalVar.mqh |
//|                                                   <OpenSource>   |
//+------------------------------------------------------------------+
#property copyright "OpenSource"
#property version       "1.00"

#include <Object.mqh>

class CGlobalVar : public CObject
{

        private:
                string            m_name;
                double            m_value;
                
                datetime          m_create_time;
                datetime          m_last_time;
                
                bool              m_is_temp;    // flag for temporary var
        
        public:
        
                void                            CGlobalVar();
                void                            CGlobalVar(const string _var_name, const double _var_val, const datetime _create_time);
                void                            ~CGlobalVar() {};
                
                bool                            Create(const string _var_name, const double _var_val = 0.0, const bool plain_name = true, const bool _is_temp = false);
                bool                            Delete();
                
                bool                            IsGlobalVar(const string _var_name, bool _to_print = true, bool plain_name = false);
                
                double                          GetDoubleValue() const;
                
                bool                            SetValue(const double _var_val);
                bool                            SetValueOnCondition(const double _var_new_val, const double _var_check_val);
                
                string                          GetName() const;
                datetime                        GetCreateTime() const;
                datetime                        GetLastTime();
                template<typename T>
                T                                       GetValue(T _type) const;
                bool                            IsTemporary() const;
        
        private:
           string                               FormName(const string _base_name, bool plain_name = true, const bool _is_temp = false);
};

void CGlobalVar::CGlobalVar()
{
        this.m_name = NULL;
        this.m_create_time = this.m_last_time = 0;
        this.m_value = WRONG_VALUE;
        this.m_is_temp = false;
}

void CGlobalVar::CGlobalVar(const string _var_name, const double _var_val, const datetime _create_time)
        :       m_name(_var_name),
                m_value(_var_val),
                m_create_time(_create_time)
{
        this.m_last_time = TimeCurrent();
        this.m_is_temp = this.IsTemporary();
}

bool CGlobalVar::Create(const string _var_name,
                                                const double _var_val = 0.0,
                                                const bool plain_name = true,
                                                const bool _is_temp = false)
{
        string curr_var_name = this.FormName(_var_name, plain_name, _is_temp);
        
        if (!GlobalVariableCheck(curr_var_name)) //--- check the existence of a global variable
        {
                if (_is_temp)
                        if (!GlobalVariableTemp(curr_var_name)) //--- create a temporary global variable
                        {
                                Print("Failed to create a temporary global variable!");
                                return false;
                        }
                
                datetime creation_time = GlobalVariableSet(curr_var_name,_var_val);
                
                if (creation_time > 0) //--- check the creation time
                {
                        this.m_create_time = this.m_last_time=creation_time;
                        this.m_name = curr_var_name;
                        this.m_value = _var_val;
                        this.m_is_temp = _is_temp;
                        return true;
                }
                else
                {
                        Print("Failed to set a new value for the global variable'" + curr_var_name + "'!");
                        //--- in case if it does exist
                        if (GlobalVariableCheck(curr_var_name) && GlobalVariableDel(curr_var_name))
                                Alert("Deleted the global variable '" + curr_var_name + "'!");
                        return false;
                }
        }
        return false;
}

bool CGlobalVar::Delete()
{
        if (this.m_name != NULL &&
                GlobalVariableCheck(this.m_name) &&
                GlobalVariableDel(this.m_name))
                        return true;
        
        return false;
}

bool CGlobalVar::IsGlobalVar(const string _var_name, bool plain_name = true, bool print_debug_msg = false)
{
        string gvar_name = this.FormName(_var_name, plain_name);
        
        if (GlobalVariableCheck(gvar_name))
        {
                this.m_name = gvar_name;
                this.m_create_time = this.m_last_time=GlobalVariableTime(gvar_name);
                this.m_value = GlobalVariableGet(gvar_name);
                this.m_is_temp = this.IsTemporary();

                if (print_debug_msg)
                        Print("The gvar '" + _var_name + "' does exist.");
                        
                return true;
        }

        if (print_debug_msg)
                Print("The gvar '" + _var_name + "' doesn't exist.");
        
        return false;
}

bool CGlobalVar::SetValue(const double _var_val)
{
        if (this.m_name != NULL)
                if (GlobalVariableCheck(this.m_name))
                {
                        datetime new_date = GlobalVariableSet(this.m_name, _var_val);
                
                        if (new_date > 0)
                        {
                                this.m_last_time = new_date;
                                this.m_value = _var_val;
                                return true;
                        }
                        else
                        {
                                Print("Failed to set a new value for the global variable'" + this.m_name + "'!");
                                return false;
                        }
                }
        
        return false;
}

bool CGlobalVar::SetValueOnCondition(const double _var_new_val, const double _var_check_val)
{
        if (this.m_name != NULL &&
                GlobalVariableCheck(this.m_name))
                {
                        if (GlobalVariableSetOnCondition(this.m_name, _var_new_val, _var_check_val))
                        {
                                this.m_last_time = GlobalVariableTime(this.m_name);
                                this.m_value = _var_new_val;
                                return true;
                        }
                        else
                        {
                                Print("Failed to set a new value for the global variable'" + this.m_name + "' on condition!");
                                return false;
                        }
                }
        
        return false;
}

double CGlobalVar::GetDoubleValue() const
{
        if (this.m_name != NULL && GlobalVariableCheck(this.m_name))
                return (double)this.m_value;
        
        return 0.0;
}

string CGlobalVar::GetName() const
{
        if (this.m_name!=NULL && GlobalVariableCheck(this.m_name))
                return this.m_name;
        
        return NULL;
}

datetime CGlobalVar::GetCreateTime() const
{
        if (this.m_name != NULL && GlobalVariableCheck(this.m_name))
                return this.m_create_time;
        
        return 0;
}

datetime CGlobalVar::GetLastTime()
{
        if (this.m_name!=NULL && GlobalVariableCheck(this.m_name))
        {
                this.m_last_time = GlobalVariableTime(this.m_name);
                return this.m_last_time;
        }
        
        return 0;
}

template<typename T>
T CGlobalVar::GetValue(T _type) const
{
        if (this.m_name!=NULL && GlobalVariableCheck(this.m_name))
                return (T)this.m_value;
        
        return (T)0;
}

bool CGlobalVar::IsTemporary() const
{
        string gvar_name = this.m_name;
        
        if (gvar_name != NULL)
        {
                int str_pos = StringFind(gvar_name,"_temp_");
                if (str_pos > -1)
                        return true;
        }
        
        return false;
}

string CGlobalVar::FormName(const string _base_name, bool plain_name = true, const bool _is_temp = false)
{
        string gvar_name = _base_name;

        if (plain_name)
                return gvar_name;
        
        if (_is_temp)
          gvar_name += "_temp";
        
        gvar_name += "_prog_" + MQLInfoString(MQL_PROGRAM_NAME);
        
        string curr_prog_type_str = NULL;
        ENUM_PROGRAM_TYPE curr_prog_type = (ENUM_PROGRAM_TYPE)MQLInfoInteger(MQL_PROGRAM_TYPE);
        curr_prog_type_str = EnumToString(curr_prog_type);
        curr_prog_type_str = StringSubstr(curr_prog_type_str, 8, 3);
        StringToLower(curr_prog_type_str);
        gvar_name += "_" + curr_prog_type_str;
        
        return gvar_name;
}

The ToString.mqh contains (just some snippet that arefor me enough for now):

string prependZeroForSingleDigit(const string digit)
{
        if (StringLen(digit) == 1)
                return "0" + digit;
                
        return digit;
}

string EnumOrderTypeToString(ENUM_ORDER_TYPE type)
{
        string str_type="unknown operation";
        switch(type)
        {
                case (ORDER_TYPE_BUY):                          return("buy");
                case (ORDER_TYPE_SELL):                         return("sell");
                case (ORDER_TYPE_BUY_LIMIT):            return("buy limit");
                case (ORDER_TYPE_SELL_LIMIT):           return("sell limit");
                case (ORDER_TYPE_BUY_STOP):                     return("buy stop");
                case (ORDER_TYPE_SELL_STOP):            return("sell stop");
                case (ORDER_TYPE_BUY_STOP_LIMIT):       return("buy stop limit");
                case (ORDER_TYPE_SELL_STOP_LIMIT):      return("sell stop limit");
        }
        return(str_type);
}

string TimeToDateString(ulong time)
{
        ulong days=0;
        ulong hours=0;
        ulong minutes=0;
        ulong seconds=0;

        seconds = time%60;
        time/=60;

        minutes=time%60;
        time/=60;

        hours=time%24;
        time/=24;

        days=time;

        return StringFormat("%02ud : %02uh : %02um : %02us", days, hours, minutes, seconds);
}

string PositionTypeToString(ENUM_POSITION_TYPE type)
{
        string str="";

        if(type==POSITION_TYPE_BUY)
                str="buy";
        else if(type==POSITION_TYPE_SELL)
                str="sell";
        else
                str="wrong value";

        return(str);
}

string TimeframeToString(ENUM_TIMEFRAMES timeframe)
{
        string str="";

        if(timeframe==WRONG_VALUE|| timeframe== NULL)
                timeframe= Period(); //take the time frame of the current chart
        switch(timeframe)
        {
                case PERIOD_M1  : str="M1";  break;
                case PERIOD_M2  : str="M2";  break;
                case PERIOD_M3  : str="M3";  break;
                case PERIOD_M4  : str="M4";  break;
                case PERIOD_M5  : str="M5";  break;
                case PERIOD_M6  : str="M6";  break;
                case PERIOD_M10 : str="M10"; break;
                case PERIOD_M12 : str="M12"; break;
                case PERIOD_M15 : str="M15"; break;
                case PERIOD_M20 : str="M20"; break;
                case PERIOD_M30 : str="M30"; break;
                case PERIOD_H1  : str="H1";  break;
                case PERIOD_H2  : str="H2";  break;
                case PERIOD_H3  : str="H3";  break;
                case PERIOD_H4  : str="H4";  break;
                case PERIOD_H6  : str="H6";  break;
                case PERIOD_H8  : str="H8";  break;
                case PERIOD_H12 : str="H12"; break;
                case PERIOD_D1  : str="D1";  break;
                case PERIOD_W1  : str="W1";  break;
                case PERIOD_MN1 : str="MN1"; break;
        }

        return(str);
}
 

To wrap this up, here the essential parts of my signal indicator:

input bool GlobalVarsUpdate     = true;                 // GlobalVarsUpdate: false = Visualize signals only;
sinput bool LogDebug            = false;


class MySpecialSignalIndicator
{
   private:
        // ...
        double          signalBuffer[];

        SignalReader*   m_GVs_OPA;

   public:
                        MySpecialSignalIndicator(void);
                        ~MySpecialSignalIndicator(void);
        
        int             ComputeBuffers(const int rates_total, const int prev_calculated, const datetime &time[], const double& price[]);
        int             DoSomeMagiCalculations(const int rates_total, const int prev_calculated, const datetime &time[], const double& price[]);
        
        bool            Init();
        void            Deinit();

        protected:
        bool            InitBuffers();
        void            InitializeArrays();
        void            ResizeArrays(int rates_total);
        void            UpdateGlobalVarSignal(int index, datetime time);
};

MySpecialSignalIndicator::MySpecialSignalIndicator()
{
        // ...
}

MySpecialSignalIndicator::~MySpecialSignalIndicator()
{ 
        // ...
        delete m_GVs_Signal;
}

bool MySpecialSignalIndicator::Init()
{
        // ...


        // only to use the setter method later on
        m_GVs_Signal = new SignalReader(Symbol(), Period());

        // Let's make sure this helper class does not create a handler to this indicator here which again would create a helper class which would create a handler to this indicator, and so on..
        m_GVs_Signal.Init(false); // Init(true) would start a recursive initialization.
        ResizeArrays(0); 
        InitializeArrays();

        if (!InitBuffers())
           returnfalse; 
         

        return true;
}

void MySpecialSignalIndicator::ResizeArrays(int rates)
{
        //PrintFormat("Resizing arrays to new capacity (%d)", rates);          // ... 
         
        ArrayResize(signalBuffer,                             rates, 1);
}

void MySpecialSignalIndicator::InitializeArrays() {          

        // ...         ArrayInitialize(signalBuffer,                           0.0); 
} 

bool MySpecialSignalIndicator::InitBuffers()
{ 
        SetIndexBuffer(0,       signalBuffer,                 INDICATOR_DATA); // I have more buffers ofc, but for sake of simplicity let index be 0

        //--- set indexing for the numeration like in time series 
        ArraySetAsSeries(signalBuffer,                  false); 
         

        PlotIndexSetInteger(0, PLOT_COLOR_INDEXES,      0); // last parameter = #index of the buffer which we want colorized
        PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0, clrAqua); 
         
        PlotIndexSetInteger(0, PLOT_LINE_COLOR, 1, clrBrown);
        PlotIndexSetDouble(0,  PLOT_EMPTY_VALUE, 0.0); 
         
        IndicatorSetInteger(INDICATOR_DIGITS, 5); 

        StringConcatenate(shortname, shortname, " (", TimeframeToString(Period()), ")"); 
        IndicatorSetString(INDICATOR_SHORTNAME, shortname);

        PlotIndexSetString(0, PLOT_LABEL, "Signal"); 
         
        return true;
}

int MySpecialSignalIndicator::ComputeBuffers(const int rates_total, const int prev_calculated, const datetime &time[], constdouble& price[])
{
        // does some calculations, manipulates the signal buffer and calls UpdateGlobalVarSignal(..)
        return DoSomeMagiCalculations(rates_total, prev_calculated, time, price);
}

// The GVar update method:void 
MySpecialSignalIndicator::UpdateGlobalVarSignal(int index, datetime time)
{ 
        if (!GlobalVarsUpdate) return; // could be that the user wants this indicator for visualization only, not to create/update the global variable          

        if (signalBuffer[index] != 0)
        {
                if (LogDebug) PrintFormat("%sSignal at %s at bar (%d) : %f.", LogPrefix, TimeToString(time), index, signalBuffer[index]); 
                m_GVs_Signal.SetSignal(signalBuffer[index], time);
        }
        else 
                m_GVs_Signal.SetSignal(0.0, time);
}

// then the global event methods that run the indicator:
MySpecialSignalIndicator* SignalIndicator;

int OnInit()
{ 
        SignalIndicator = new MySpecialSignalIndicator();

        if(!SignalIndicator.Init())
        {
                return INIT_FAILED;                  
                delete SignalIndicator;
        } 

        return INIT_SUCCEEDED;
}

void OnDeinit(constint reason)
{
        Print("SignalIndicator deinitializing. Reason: ", reason);
        delete SignalIndicator;
}

int OnCalculate(constint rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[], 
                const double &low[], 
                const double &close[], 
                const long &tick_volume[], 
                const long &volume[], 
                const int &spread[])
{ 
        return SignalIndicator.ComputeBuffers(rates_total, prev_calculated, time, close);
} 


Maybe this helps @Enrique Dangeroux.

Again, If anyone else has suggestions for improvements, let me know.

Reason: