How do I automatically create a Text Object on the Chart with MQL Code?

 

I am trying to automatically create a TextObject on the chart as a specific Bar/Time and Price, but I can't get it working. I tried the examples given in the MQL Help, but they don't show any text, they don't seem to work at all.

What is the minimum amount of code needed to draw a Text Object with:

> String = "my text string"

> at Time[time_x]

> at price [price_y]

?

 
   string oName="Name";
   string text= "my text string";
   datetime tim=iTime(_Symbol,PERIOD_CURRENT,1);
   double price=iHigh(_Symbol,PERIOD_CURRENT,1);
   ObjectCreate(0,oName,OBJ_TEXT,0,tim,price);
   ObjectSetString(0,oName,OBJPROP_TEXT,text);
 
Keith Watford:

Thank you.

Putting that into the context of the built-in MT4 [Bollinger] Bands Indicator, the below is creating Text Objects. However, all the Text Objects seem to be being created at the same time/price position - so I have numerous objects on top of each other. I discovered this by double clicking an object, selecting it then dragging it to another bar/price on the chart. What am I missing? Can you tell me why they are not being added to the bars where the if conditions are true as the for loop is iterating from the start of the chart to the current bar?

//+------------------------------------------------------------------+
//|                                                        Bands.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Bollinger Bands"
#property strict

#include <MovingAverages.mqh>

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 LightSeaGreen
#property indicator_color2 LightSeaGreen
#property indicator_color3 LightSeaGreen
//--- indicator parameters
input int    InpBandsPeriod=20;      // Bands Period
input int    InpBandsShift=0;        // Bands Shift
input double InpBandsDeviations=2.0; // Bands Deviations
//--- buffers
double ExtMovingBuffer[];
double ExtUpperBuffer[];
double ExtLowerBuffer[];
double ExtStdDevBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- 1 additional buffer used for counting.
   IndicatorBuffers(4);
   IndicatorDigits(Digits);
//--- middle line
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMovingBuffer);
   SetIndexShift(0,InpBandsShift);
   SetIndexLabel(0,"Bands SMA");
//--- upper band
   SetIndexStyle(1,DRAW_LINE);
   SetIndexBuffer(1,ExtUpperBuffer);
   SetIndexShift(1,InpBandsShift);
   SetIndexLabel(1,"Bands Upper");
//--- lower band
   SetIndexStyle(2,DRAW_LINE);
   SetIndexBuffer(2,ExtLowerBuffer);
   SetIndexShift(2,InpBandsShift);
   SetIndexLabel(2,"Bands Lower");
//--- work buffer
   SetIndexBuffer(3,ExtStdDevBuffer);
//--- check for input parameter
   if(InpBandsPeriod<=0)
     {
      Print("Wrong input parameter Bands Period=",InpBandsPeriod);
      return(INIT_FAILED);
     }
   SetIndexDrawBegin(2,InpBandsPeriod+InpBandsShift);
//--- initialization done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Bollinger Bands                                                  |
//+------------------------------------------------------------------+
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[])
  {
   int i,pos;
//---
   if(rates_total<=InpBandsPeriod || InpBandsPeriod<=0)
      return(0);
//--- counting from 0 to rates_total
   ArraySetAsSeries(ExtMovingBuffer,false);
   ArraySetAsSeries(ExtUpperBuffer,false);
   ArraySetAsSeries(ExtLowerBuffer,false);
   ArraySetAsSeries(ExtStdDevBuffer,false);
   ArraySetAsSeries(close,false);
//--- initial zero
   if(prev_calculated<1)
     {
      for(i=0; i<InpBandsPeriod; i++)
        {
         ExtMovingBuffer[i]=EMPTY_VALUE;
         ExtUpperBuffer[i]=EMPTY_VALUE;
         ExtLowerBuffer[i]=EMPTY_VALUE;
        }
     }
//--- starting calculation
   if(prev_calculated>1)
      pos=prev_calculated-1;
   else
      pos=0;
//--- main cycle
   for(i=pos; i<rates_total && !IsStopped(); i++)
     {
      //--- middle line
      ExtMovingBuffer[i]=SimpleMA(i,InpBandsPeriod,close);
      //--- calculate and write down StdDev
      ExtStdDevBuffer[i]=StdDev_Func(i,close,ExtMovingBuffer,InpBandsPeriod);
      //--- upper line
      ExtUpperBuffer[i]=ExtMovingBuffer[i]+InpBandsDeviations*ExtStdDevBuffer[i];
      //--- lower line
      ExtLowerBuffer[i]=ExtMovingBuffer[i]-InpBandsDeviations*ExtStdDevBuffer[i];
      //---
//--------------- creating text object (with time/price coordinates) -----------------//
      if (i>0)
        {
         if (Close[i] < ExtUpperBuffer[i] && Close[i-1] > ExtUpperBuffer[i-1])
           {
            string objName="object_"+(string)i; // unique object name to prevent Error #4200
            long current_chart_id=ChartID();
            string l_sLabel= "Sell";
            datetime tim=iTime(_Symbol,PERIOD_CURRENT,1);
            double price=iHigh(_Symbol,PERIOD_CURRENT,1);
            ObjectCreate(current_chart_id,objName,OBJ_TEXT,0,tim,price);
            ObjectSetString(current_chart_id,objName,OBJPROP_TEXT,l_sLabel);
            //Print("Close crossed from above to below Upper BB at: ",Time[i]);
           }
         else if (Close[i] > ExtLowerBuffer[i] && Close[i-1] < ExtLowerBuffer[i-1])
           {
            string objName="object_"+(string)i; // unique object name to prevent Error #4200
            long current_chart_id=ChartID();
            string l_sLabel= "Buy";
            datetime tim=iTime(_Symbol,PERIOD_CURRENT,1);
            double price=iLow(_Symbol,PERIOD_CURRENT,1);
            ObjectCreate(current_chart_id,objName,OBJ_TEXT,0,tim,price);
            ObjectSetString(current_chart_id,objName,OBJPROP_TEXT,l_sLabel);
            //Print("Close crossed from below to above Lower BB at: ",Time[i]);
           }
        }
//------------------------------------------------------------------------------------//
     }
//---- OnCalculate done. Return new prev_calculated.
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Calculate Standard Deviation                                     |
//+------------------------------------------------------------------+
double StdDev_Func(int position,const double &price[],const double &MAprice[],int period)
  {
//--- variables
   double StdDev_dTmp=0.0;
//--- check for position
   if(position>=period)
     {
      //--- calcualte StdDev
      for(int i=0; i<period; i++)
         StdDev_dTmp+=MathPow(price[position-i]-MAprice[position],2);
      StdDev_dTmp=MathSqrt(StdDev_dTmp/period);
     }
//--- return calculated value
   return(StdDev_dTmp);
  }
//+------------------------------------------------------------------+

I need to experiment with font attributes and price levels to get it looking right, but how do I ensure the Text Objects are removed when I remove the Indicator from the chart?

Thanks again.
 
string objName="object_"+(string)i; // unique object name to prevent Error #4200

The above will not necessarily create a unique name

            datetime tim=iTime(_Symbol,PERIOD_CURRENT,i);
            string objName="object_"+TimeToString(tim); // unique object name to prevent Error #4200
            long current_chart_id=ChartID();
            string l_sLabel= "Sell";
            double price=iHigh(_Symbol,PERIOD_CURRENT,i);
            ObjectCreate(current_chart_id,objName,OBJ_TEXT,0,tim,price);
            ObjectSetString(current_chart_id,objName,OBJPROP_TEXT,l_sLabel);

how do I ensure the Text Objects are removed when I remove the Indicator from the chart?

In OnDeinit()

 ObjectsDeleteAll(0,"object_");   
 
Keith Watford:

The above will not necessarily create a unique name

how do I ensure the Text Objects are removed when I remove the Indicator from the chart?

In OnDeinit()

Thanks Keith.

I changed the code according to your feedback to the below, but now it seems to only print the last object and it is still in the wrong place:

//+------------------------------------------------------------------+
//|                                                        Bands.mq4 |
//|                   Copyright 2005-2014, MetaQuotes Software Corp. |
//|                                              http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright   "2005-2014, MetaQuotes Software Corp."
#property link        "http://www.mql4.com"
#property description "Bollinger Bands"
#property strict

#include <MovingAverages.mqh>

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 LightSeaGreen
#property indicator_color2 LightSeaGreen
#property indicator_color3 LightSeaGreen
//--- indicator parameters
input int    InpBandsPeriod=20;      // Bands Period
input int    InpBandsShift=0;        // Bands Shift
input double InpBandsDeviations=2.0; // Bands Deviations
//--- buffers
double ExtMovingBuffer[];
double ExtUpperBuffer[];
double ExtLowerBuffer[];
double ExtStdDevBuffer[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//--- 1 additional buffer used for counting.
   IndicatorBuffers(4);
   IndicatorDigits(Digits);
//--- middle line
   SetIndexStyle(0,DRAW_LINE);
   SetIndexBuffer(0,ExtMovingBuffer);
   SetIndexShift(0,InpBandsShift);
   SetIndexLabel(0,"Bands SMA");
//--- upper band
   SetIndexStyle(1,DRAW_LINE);
   SetIndexBuffer(1,ExtUpperBuffer);
   SetIndexShift(1,InpBandsShift);
   SetIndexLabel(1,"Bands Upper");
//--- lower band
   SetIndexStyle(2,DRAW_LINE);
   SetIndexBuffer(2,ExtLowerBuffer);
   SetIndexShift(2,InpBandsShift);
   SetIndexLabel(2,"Bands Lower");
//--- work buffer
   SetIndexBuffer(3,ExtStdDevBuffer);
//--- check for input parameter
   if(InpBandsPeriod<=0)
     {
      Print("Wrong input parameter Bands Period=",InpBandsPeriod);
      return(INIT_FAILED);
     }
   SetIndexDrawBegin(2,InpBandsPeriod+InpBandsShift);
//--- initialization done
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Bollinger Bands                                                  |
//+------------------------------------------------------------------+
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[])
  {
   int i,pos;
//---
   if(rates_total<=InpBandsPeriod || InpBandsPeriod<=0)
      return(0);
//--- counting from 0 to rates_total
   ArraySetAsSeries(ExtMovingBuffer,false);
   ArraySetAsSeries(ExtUpperBuffer,false);
   ArraySetAsSeries(ExtLowerBuffer,false);
   ArraySetAsSeries(ExtStdDevBuffer,false);
   ArraySetAsSeries(close,false);
//--- initial zero
   if(prev_calculated<1)
     {
      for(i=0; i<InpBandsPeriod; i++)
        {
         ExtMovingBuffer[i]=EMPTY_VALUE;
         ExtUpperBuffer[i]=EMPTY_VALUE;
         ExtLowerBuffer[i]=EMPTY_VALUE;
        }
     }
//--- starting calculation
   if(prev_calculated>1)
      pos=prev_calculated-1;
   else
      pos=0;
//--- main cycle
   for(i=pos; i<rates_total && !IsStopped(); i++)
     {
      //--- middle line
      ExtMovingBuffer[i]=SimpleMA(i,InpBandsPeriod,close);
      //--- calculate and write down StdDev
      ExtStdDevBuffer[i]=StdDev_Func(i,close,ExtMovingBuffer,InpBandsPeriod);
      //--- upper line
      ExtUpperBuffer[i]=ExtMovingBuffer[i]+InpBandsDeviations*ExtStdDevBuffer[i];
      //--- lower line
      ExtLowerBuffer[i]=ExtMovingBuffer[i]-InpBandsDeviations*ExtStdDevBuffer[i];
      //---
//--------------- creating text object (with time/price coordinates) -----------------//
      if (i>0)
        {
         if (Close[i] < ExtUpperBuffer[i] && Close[i-1] > ExtUpperBuffer[i-1])
           {
            datetime tim=iTime(_Symbol,PERIOD_CURRENT,i);
            string objName="object_"+TimeToString(tim); // unique object name to prevent Error #4200
            long current_chart_id=ChartID();
            string l_sLabel= "Sell";
            double price=iHigh(_Symbol,PERIOD_CURRENT,i);
            ObjectCreate(current_chart_id,objName,OBJ_TEXT,0,tim,price);
            ObjectSetString(current_chart_id,objName,OBJPROP_TEXT,l_sLabel);
            //Print("Close crossed from above to below Upper BB at: ",Time[i]);
           }
         else if (Close[i] > ExtLowerBuffer[i] && Close[i-1] < ExtLowerBuffer[i-1])
           {
            datetime tim=iTime(_Symbol,PERIOD_CURRENT,i);
            string objName="object_"+TimeToString(tim); // unique object name to prevent Error #4200
            long current_chart_id=ChartID();
            string l_sLabel= "Buy";
            double price=iLow(_Symbol,PERIOD_CURRENT,i);
            ObjectCreate(current_chart_id,objName,OBJ_TEXT,0,tim,price);
            ObjectSetString(current_chart_id,objName,OBJPROP_TEXT,l_sLabel);
            //Print("Close crossed from below to above Lower BB at: ",Time[i]);
           }
        }
//------------------------------------------------------------------------------------//
     }
//---- OnCalculate done. Return new prev_calculated.
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Indicator Deinitialisation Function                              |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0,"object_");
//--- deinitialization done
  }
//+------------------------------------------------------------------+
//| Calculate Standard Deviation                                     |
//+------------------------------------------------------------------+
double StdDev_Func(int position,const double &price[],const double &MAprice[],int period)
  {
//--- variables
   double StdDev_dTmp=0.0;
//--- check for position
   if(position>=period)
     {
      //--- calcualte StdDev
      for(int i=0; i<period; i++)
         StdDev_dTmp+=MathPow(price[position-i]-MAprice[position],2);
      StdDev_dTmp=MathSqrt(StdDev_dTmp/period);
     }
//--- return calculated value
   return(StdDev_dTmp);
  }
//+------------------------------------------------------------------+
 
Steve:

Thanks Keith.

I changed the code according to your feedback to the below, but now it seems to only print the last object and it is still in the wrong place:

Sorry, but you will have to check through your code and see what it is not doing that you think it should. I don't have time.

 
         if (Close[i] < ExtUpperBuffer[i] && Close[i-1] > ExtUpperBuffer[i-1])
           {
            datetime tim=iTime(_Symbol,PERIOD_CURRENT,i);
You set your buffers to non-series, and then use the as-series predefined Close and as-series iTime with your non-series index.
 
Keith Watford:

Sorry, but you will have to check through your code and see what it is not doing that you think it should. I don't have time.

No worries - appreciate your help so far.

Thanks.

 
William Roeder:
You set your buffers to non-series, and then use the as-series predefined Close and as-series iTime with your non-series index.

Thanks so much. I see what you're getting at now...I was wondering whether I had this backwards.

I'll change it and see how it goes. Much appreciated.

Reason: