My First Indicator... and it isn't working reliably ... can't understand why ...

 

Firstly hello to everyone, my first post after much reading of what seems a great community!

I'm trying to put a price marker 'gauge' with two halves after the end of the chart (I use candles).
I want it to illustrate the necessary price movement to cover my spread (on the left half) and on the right the price movement needed for a 5% return.
I'm making it so I don't place orders on price movements that are so small they are pointless!

I am unsure about several aspects of my code, and I have had it working but it seems to be very unpredicatable - works when it feels like it...
e.g. Right now it works perfectly on a Pepperstone Bitcoin.p M15 chart - appearing immediately and clicking from one period to another give immediate updates.
Changing to GBPUSD I get nothing! unless I wait... then it appears.... then it vanishes again...

Here is the current code... Also file attached to post.

//+------------------------------------------------------------------+
//| Illustrator of Price Movements Needed to cover                   |
//|     1. Spread and 2. 5% Profit                                   |
//|                                                   Copyright 2021 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_plots   0

#define Diag(A) Comment(#A + " = " + (string)A);Print(#A + " = " + (string)A);  // add Diag(var); anywhere to Comment and Print the var name and value!

//+------------------------------------------------------------------+
int OnCalculate(const int 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[])
  {   PlotBox();
   return(rates_total);  }

//+------------------------------------------------------------------+
void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam)
  {   PlotBox();  }

//+------------------------------------------------------------------+
void PlotBox()
  {
   MqlRates priceData[]; ArraySetAsSeries(priceData,true);
   CopyRates(_Symbol,_Period,0,1,priceData);

   datetime boxLeft = priceData[0].time+7*PeriodSeconds(PERIOD_CURRENT);
   datetime boxMiddle = priceData[0].time+(datetime)(7.25*PeriodSeconds(PERIOD_CURRENT));
   datetime boxRight = priceData[0].time+(datetime)(7.5*PeriodSeconds(PERIOD_CURRENT));

   double buyPrice=priceData[0].close+(priceData[0].spread/MathPow(10,_Digits));
   double sellMargin=priceData[0].close-(priceData[0].spread/MathPow(10,_Digits));

   ObjectDelete(0,"SpreadBox");
   bool outcome = ObjectCreate(
                     0,                                      // chart identifier
                     "SpreadBox",                            // object name
                     OBJ_RECTANGLE,                          // object type
                     0,                                      // window index
                     boxLeft,                                // time of the first anchor point
                     buyPrice,                               // price of the first anchor point
                     boxMiddle,                              // time of the second anchor point
                     sellMargin);                            // price of the second anchor point
   ObjectSetInteger(0,"SpreadBox",OBJPROP_COLOR,clrOrange);   ObjectSetInteger(0,"SpreadBox",OBJPROP_FILL,true);

// define a margin of specific profit ?5% and use that to illustrate margins on chart.
// NEED TO FIGURE OUT LEVERAGE AND COMMISSION MATHS ETC YET!! - Currently just a bigger box!
   double profitPrice=priceData[0].close+2*(priceData[0].spread/MathPow(10,_Digits));
   double lossPrice=priceData[0].close-2*(priceData[0].spread/MathPow(10,_Digits));

   ObjectDelete(0,"ProfitBox");
   bool outcome2 = ObjectCreate(
                      0,                                      // chart identifier
                      "ProfitBox",                            // object name
                      OBJ_RECTANGLE,                          // object type
                      0,                                      // window index
                      boxMiddle,                              // time of the first anchor point
                      profitPrice,                            // price of the first anchor point
                      boxRight,                               // time of the second anchor point
                      lossPrice);                             // price of the second anchor point
   ObjectSetInteger(0,"ProfitBox",OBJPROP_COLOR,clrGreen);     ObjectSetInteger(0,"ProfitBox",OBJPROP_FILL,true);
  }
  


I think I have several issues you could all help me with, so I shall break it down...

1. How should I be launching this 'indicator'

I was thinking I could code the whole thing in OnCalculate, but this doesn't update unless the price changes. Does it? Certainly doesn't 'just work' anyway...
I DO want it to appear pref. immediately even if there is no Tick Event (e.g. out of hours), and I DO want it to update with any Chart change (Period, Symbol)...
So I shifted the code to its own function and have tried launching it from OnTimer (albeit with inefficient launches every 5 seconds for life)...
I tried OnChart, thinking this was launched every time anything changed on the chart, but I get very unpredictable results.
I even tried OnTick, but that wouldn't work out of hours and made it an EA. I want to run it alongside my next project - an EA and you can't run two!
I DO also want (ideally) the data from the indicator to be accessible to my EA.

2. Am I initialising it (as an indicator) correctly?

It doesn't really need 'buffers' just to hold four price values calculated on each update does it? - close+spread, close-spread, close+5%profitmargin, close-5%profitmargin.
(the profit margin I still need to do the maths for so my current code just doubles the spread bar to show the plan).

I suspect I will need to use buffers to make it accessible to my EA though?
Should I use one buffer for the four values buffer[4]?    or buffer1[0], buffer2[0], buffer3[0], buffer4[0]?
I suspect the latter makes more sense, just seems inefficient!

Anyway, I couldn't find much on exactly what needs #property defining for an indicator so I did i by trial and error!
(#property indicator_chart_window & #property indicator_plots   0} seems the minimum to avoid compiler warnings?
Does that cover everything? Did I do it right? Is something here explaining why it doesn't work reliably??


3. It seems to differ (slightly) from the ask price line on the chart, maybe just lagging a tick or two. Why?
The buy price+spread (top of green bar) should be the ask price.. and it is fairly close, often spot on, but not for a tick or two.
Does anyone know why?? - best seen on a volatile high volume M1 chart.

4. Any feedback on my coding always appreciated! :-)
Can I drop all those unused variables in the code line for OnCalculate?? maybe a role for NUL?? - just unsightly...

P.S. I have spent a lot of time searching for my own answers before I posted here, just maybe not in the right places!
I'm doing this to learn how to code indicators, learn OOP etc, so please don't just reply - "Use this one that someone else already made!"
That said, let me know anyway if you know a good one - maybe I will learn something more.

P.P.S. Much credit to Mr Verleyen! - If you happen on this post, I've read your posts regularly and credit you with my understanding of the #define code and ternary operators I have used elsewhere, thanks!
Anyone here helping is much appreciated.

Documentation on MQL5: Constants, Enumerations and Structures / Codes of Errors and Warnings / Trade Server Return Codes
Documentation on MQL5: Constants, Enumerations and Structures / Codes of Errors and Warnings / Trade Server Return Codes
  • www.mql5.com
Trade Server Return Codes - Codes of Errors and Warnings - Constants, Enumerations and Structures - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5
 

One quick added note...
My log shows (only one!) a single error (in all the time I've been using / testing this) - stating array out of range!?

My only array is MqlRates priceData[], it is only used in the PlotBox function and initialised and filled at that start.
So I am assuming that at some point, the code to fill it...

CopyRates(_Symbol,_Period,0,1,priceData);

... returned no data at all? So didn't even fill priceData[] as far as [0]?? - I only use priceData.xxx[0] in the code.

Even more baffled now! Maybe an unrelated issue to the original post though?
Apart for reporting the error, it caused no additional problems...

Should I make an error handler for this, or can I fix the error??

The checks a trading robot must pass before publication in the Market
The checks a trading robot must pass before publication in the Market
  • www.mql5.com
Before any product is published in the Market, it must undergo compulsory preliminary checks in order to ensure a uniform quality standard. This article considers the most frequent errors made by developers in their technical indicators and trading robots. An also shows how to self-test a product before sending it to the Market.
 

Ok, so no replies as yet, but if anyone else has similar problems and finds my post, here's my learning so far...
Basically, to simplify my whole long (sorry) story above - I needed to know which program type and functions to use to enable me to run what I am thinking of as an indicator...
I need it to run alongside an EA, and to update with user chart changes, new tick data, and to run some analyses (and add chart elements) the moment it is launched - not just if new price data arrives...

The obvious answer would be to write this as an indicator, and using the above code and adding an OnInit launch for my PlotBox function seems to be mostly working...
Adding OnTick turns it into an EA, so that wasn't an option...

I think the most useful MQL Reference pages for this are those relating to the Event Handling Functions.

I also need to delete it by adding a DeInit - otherwise removing the indicator does not remove the plots it made on the chart!

I still have some work to do on the spread plot itself - it isn't lining up with the buy and sell prices, so I'm going to do the maths myself (using Ask-Bid from CopyRates)...
...not using CopyRates.spread or CopySpread data (none of which actually show the current spread consistently - maybe they relate to the whole candle / other recent data but I'm not even convinced of that).

Still a work in progress!...


Documentation on MQL5: Event Handling / OnStart
Documentation on MQL5: Event Handling / OnStart
  • www.mql5.com
OnStart - Event Handling - MQL5 Reference - Reference on algorithmic/automated trading language for MetaTrader 5
 

OnChartEvent() runs before data is available, so CopyRates() value should be checked.

   MqlRates priceData[]; ArraySetAsSeries(priceData,true);
   if(CopyRates(_Symbol,_Period,0,1,priceData)!=1)
      return;
 
Ernst Van Der Merwe:

OnChartEvent() runs before data is available, so CopyRates() value should be checked.

Ah, that might well explain some of my problems!

I obviously don't fully understand when these price data values become available - any idea where this sort of info is detailed?

I shall have a bit more of a read of the MQL5 reference...
Sounds like CopyRates is generally a better (more immediate and up to date) way to get price data in general...?

 
mlbarnes: Sounds like CopyRates is generally a better (more immediate and up to date) way to get price data in general...?

No. Data is up to date when you get a new tick. Use the events as designed. OnChartEvent is for events on the chart.

Don't try to use any price or server related functions in OnInit (or on load), as there may be no connection/chart yet:

  1. Terminal starts.
  2. Indicators/EAs are loaded. Static and globally declared variables are initialized. (Do not depend on a specific order.)
  3. OnInit is called.
  4. For indicators OnCalculate is called with any existing history.
  5. Human may have to enter password, connection to server begins.
  6. New history is received, OnCalculate called again.
  7. New tick is received, OnCalculate/OnTick is called. Now TickValue, TimeCurrent, account information and prices are valid.
 

Just an update for anyone watching / finding this later ...

I think most (possibly all) of the problems I had with my 'Spread Box' not appearing were related to....

1. Failing to use ArraySetAsSeries for the arrays provided by OnCalculate before using them.
2. Errors in my code that gave price or time values for the box that were off the chart - so the box was drawn, but not actually on the chart!
3. Spread values of zero - so the height of the box was zero...

My one on-going dilemma is with the OnCalculate array .spread which often returns zero (although almost never with my real broker!)...

It does this when the spread is very small, but not zero! ...and OnCalculate doesn't offer an ask array for me to calculate spread more precisely...
So I am thinking I will retrieve Spread as below instead of from the function... (Due credit to Wiliam for making this bit of code as tidy as it is!...)

double mySpread = SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)*_Point;

This certainly seems to work better - my spread bar now touches the sell price exactly and continuously...

Full (considerably changed) code is now....

//+------------------------------------------------------------------+
//| SpreadGauge - Illustrator of Price Movements Needed to cover     |
//|     1. Spread and 2. 2% Profit beyond spread                     |
//|                                        Copyright M L Barnes 2021 |
//+------------------------------------------------------------------+
#property indicator_chart_window
#property indicator_buffers 1
#property indicator_plots   1
#property indicator_label1  "Spread"      // Name of a plot for the Data Window 
#property indicator_type1   DRAW_LINE     // Type of plotting is line 
#property indicator_color1  clrRed        // Line color 
#property indicator_style1  STYLE_SOLID   // Line style 
#property indicator_width1  1             // Line Width 
double spreadBuffer[];                    // Indicator buffer for the plot

//+------------------------------------------------------------------+
int OnInit(void)
  {
   SetIndexBuffer(0,spreadBuffer,INDICATOR_DATA);
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
int OnCalculate(const int 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[])
  {
   ArraySetAsSeries(high,true);
   ArraySetAsSeries(spread,true);
   ArraySetAsSeries(spreadBuffer,true);
   for(int i=(rates_total-prev_calculated-1); i>=0; i--)
     {
      spreadBuffer[i]=high[i]+spread[i]*_Point;
     }
   PlotBox();
   return(rates_total);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
void PlotBox()
  {
   MqlRates priceData[];
   ArraySetAsSeries(priceData,true);
   CopyRates(_Symbol,_Period,0,1,priceData);
   
   double mySpread = SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)*_Point;

   datetime boxLeft = priceData[0].time+3*PeriodSeconds(PERIOD_CURRENT);
   datetime boxMiddle = priceData[0].time+(datetime)(3.25*PeriodSeconds(PERIOD_CURRENT));
   datetime boxRight = priceData[0].time+(datetime)(3.5*PeriodSeconds(PERIOD_CURRENT));

   double buyPrice=priceData[0].close+(mySpread);
   double sellMargin=priceData[0].close-(mySpread);

   ObjectDelete(0,"SpreadBox");
   bool outcome = ObjectCreate(
                     0,                                      // chart identifier
                     "SpreadBox",                            // object name
                     OBJ_RECTANGLE,                          // object type
                     0,                                      // window index
                     boxLeft,                                // time of the first anchor point
                     buyPrice,                               // price of the first anchor point
                     boxMiddle,                              // time of the second anchor point
                     sellMargin);                            // price of the second anchor point
   ObjectSetInteger(0,"SpreadBox",OBJPROP_COLOR,clrOrange);
   ObjectSetInteger(0,"SpreadBox",OBJPROP_FILL,true);

   double profitPrice=priceData[0].close+2*(mySpread);       // Yet to be updated with calcs for profit margins after spread e.g. 2%.
   double lossPrice=priceData[0].close-2*(mySpread);         // Yet to be updated with calcs for profit margins after spread e.g. 2%.

   ObjectDelete(0,"ProfitBox");
   bool outcome2 = ObjectCreate(
                      0,                                      // chart identifier
                      "ProfitBox",                            // object name
                      OBJ_RECTANGLE,                          // object type
                      0,                                      // window index
                      boxMiddle,                              // time of the first anchor point
                      profitPrice,                            // price of the first anchor point
                      boxRight,                               // time of the second anchor point
                      lossPrice);                             // price of the second anchor point
   ObjectSetInteger(0,"ProfitBox",OBJPROP_COLOR,clrGreen);
   ObjectSetInteger(0,"ProfitBox",OBJPROP_FILL,true);
   ChartRedraw();
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectDelete(0,"SpreadBox");
   ObjectDelete(0,"ProfitBox");
  }
//+------------------------------------------------------------------+

Here's what it looks like...

SpreadGauge

Pretty happy with it now! - but do let me know if you see issues or refinements... :-)

Now just testing it...

P.S. I know that having the buffer and the plotted line is largely pointless / redundant to other chart features - the SpreadGuage itself is what I was after...
I just want these to experiment with... plus I wasn't sure if you could have an indicator without any 'output' (buffer)? - will have to test that next!

P.P.S. Just added some OnDeinit code to remove the plot more immediately when the indicator is removed.