Edgoro
Expert Advisor · MQL5

Draw on the chart in MQL5: lines, arrows and text

Short answer

Call ObjectCreate(0, name, type, 0, time, price) with a unique name and a type such as OBJ_HLINE, OBJ_ARROW_UP or OBJ_TEXT, then style the object with ObjectSetInteger() and ObjectSetString(). Start every name with a common prefix, move existing objects with ObjectMove() instead of re-creating them, and delete them all with ObjectsDeleteAll(0, prefix) in OnDeinit().

UsesObjectCreateObjectSetIntegerObjectSetStringObjectMoveObjectsDeleteAllChartRedraw

The code

An Expert Advisor that trades nothing: it draws the previous day's high and low, and marks with an arrow and a label each bar that closed beyond them.

Save it as MQL5/Experts/draw-objects-on-chart.mq5 and compile it in MetaEditor (F7): 0 errors, 0 warnings.

MQL5
#define PREFIX "exdraw_"   // every object this EA creates starts with it, so it can delete them all

input int   InpHistoryBars = 300;         // Bars to mark when the EA starts
input color InpLevelColor  = clrDodgerBlue; // Color of the previous day's high and low

// Creates a horizontal line, or moves it if it already exists
void HLine(const string name, const double price, const color clr, const string tooltip)
  {
   if(ObjectFind(0, name) < 0)
     {
      if(!ObjectCreate(0, name, OBJ_HLINE, 0, 0, price))
        {
         PrintFormat("ObjectCreate %s failed: error %d", name, GetLastError());
         return;
        }
      ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH);
      ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
      ObjectSetString(0, name, OBJPROP_TOOLTIP, tooltip);
     }
   else
      ObjectMove(0, name, 0, 0, price);
  }

// Arrow under a bar's low (up) or above its high (down), with a short text next to it
void Mark(const datetime time, const double price, const bool up, const string text)
  {
   string name = PREFIX + (up ? "up_" : "dn_") + IntegerToString((long)time);
   if(ObjectFind(0, name) >= 0)
      return;                                     // already drawn (names identify objects)
   if(!ObjectCreate(0, name, up ? OBJ_ARROW_UP : OBJ_ARROW_DOWN, 0, time, price))
      return;
   ObjectSetInteger(0, name, OBJPROP_COLOR, up ? clrSeaGreen : clrTomato);
   ObjectSetInteger(0, name, OBJPROP_ANCHOR, up ? ANCHOR_TOP : ANCHOR_BOTTOM);  // the tip touches the price
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);

   string label = name + "_txt";
   if(ObjectCreate(0, label, OBJ_TEXT, 0, time, price))
     {
      ObjectSetString(0, label, OBJPROP_TEXT, text);
      ObjectSetString(0, label, OBJPROP_FONT, "Arial");
      ObjectSetInteger(0, label, OBJPROP_FONTSIZE, 8);
      ObjectSetInteger(0, label, OBJPROP_COLOR, up ? clrSeaGreen : clrTomato);
      ObjectSetInteger(0, label, OBJPROP_ANCHOR, up ? ANCHOR_LEFT_UPPER : ANCHOR_LEFT_LOWER);
      ObjectSetInteger(0, label, OBJPROP_SELECTABLE, false);
     }
  }

// Marks bar `shift` if it closed beyond the previous day's high or low while the bar before it hadn't
void CheckBar(const int shift)
  {
   datetime t = iTime(_Symbol, PERIOD_CURRENT, shift);
   int day = iBarShift(_Symbol, PERIOD_D1, t);    // the day this bar belongs to
   if(t == 0 || day < 0)
      return;
   double pdh = iHigh(_Symbol, PERIOD_D1, day + 1);
   double pdl = iLow(_Symbol, PERIOD_D1, day + 1);
   double c0  = iClose(_Symbol, PERIOD_CURRENT, shift);
   double c1  = iClose(_Symbol, PERIOD_CURRENT, shift + 1);
   if(pdh <= 0.0 || c1 <= 0.0)
      return;
   if(c0 > pdh && c1 <= pdh)
      Mark(t, iLow(_Symbol, PERIOD_CURRENT, shift), true, "Close above PDH");
   else if(c0 < pdl && c1 >= pdl)
      Mark(t, iHigh(_Symbol, PERIOD_CURRENT, shift), false, "Close below PDL");
  }

void OnTick()
  {
   static datetime lastBar = 0;
   datetime bar = iTime(_Symbol, PERIOD_CURRENT, 0);
   if(bar == 0 || bar == lastBar)
      return;
   int from = (lastBar == 0) ? MathMin(InpHistoryBars, iBars(_Symbol, PERIOD_CURRENT) - 2) : 1;
   lastBar = bar;
   for(int shift = from; shift >= 1; shift--)       // first run: the history; then only the bar that just closed
      CheckBar(shift);
   HLine(PREFIX + "pdh", iHigh(_Symbol, PERIOD_D1, 1), InpLevelColor, "Previous day high");
   HLine(PREFIX + "pdl", iLow(_Symbol, PERIOD_D1, 1), InpLevelColor, "Previous day low");
   ChartRedraw();                                   // object changes show at the next redraw otherwise
  }

void OnDeinit(const int reason)
  {
   ObjectsDeleteAll(0, PREFIX);                     // objects outlive the EA unless it deletes them
   ChartRedraw();
  }

How it works

  • PREFIX. Every object name starts with exdraw_. Names are how MQL5 identifies objects, so a shared prefix keeps them apart from other programs' objects and lets ObjectsDeleteAll(0, PREFIX) remove exactly this EA's drawings.
  • HLine(). ObjectFind() returns -1 when no object has that name. The line is created and styled once, then only moved with ObjectMove() when the level changes.
  • Names from the bar time. "exdraw_up_" + IntegerToString((long)time) is unique per bar, so each mark is drawn once, and a restart doesn't duplicate marks that are still on the chart.
  • OBJPROP_ANCHOR. For arrows, ANCHOR_TOP puts the arrow below the price with its tip on it (under a low), ANCHOR_BOTTOM above it (over a high). Text uses the corner anchors, such as ANCHOR_LEFT_UPPER.
  • iBarShift(_Symbol, PERIOD_D1, t). Finds the day each historical bar belongs to, so the marks drawn at start use the right previous day for every bar, not just yesterday's.
  • ChartRedraw(). Object changes are applied at the next redraw; calling it once after a batch of changes shows them at once.

Pitfalls

  • Objects outlive the program. Remove the EA without deleting its objects and they stay on the chart and are saved with it. OnDeinit() is where they go.
  • ObjectCreate() is asynchronous. It returns true when the command is queued for the chart, and the object appears when the chart processes it, which is why the EA calls ChartRedraw() after a batch of changes.
  • OBJ_TEXT versus OBJ_LABEL. Text is anchored to a time and a price and scrolls with the chart; a label is placed in pixels from a corner and stays put. Use labels for panels.
  • Thousands of objects. Marking every bar of a long history slows the chart down. Cap the history (InpHistoryBars) or draw per-bar marks with indicator buffers instead.
  • Invisible objects. An object on a time or price outside the visible range, in another subwindow, or hidden for the current timeframe by OBJPROP_TIMEFRAMES is there but not seen. In the Strategy Tester, objects appear only in visual mode.

Variations

  • Trend lines and boxes. OBJ_TREND takes two time and price points; OBJ_RECTANGLE draws a box, for example around a session's range.
  • Wingdings arrows. OBJ_ARROW with OBJPROP_ARROWCODE (233 and 234 are the usual up and down arrows) gives more shapes than OBJ_ARROW_UP and OBJ_ARROW_DOWN.
  • Marks as an indicator. For a mark on many bars, a custom indicator with a DRAW_ARROW plot is lighter than objects, and an EA can read it back with iCustom().
  • The levels themselves. The previous day's high and low are the classic session high and low levels; pivot points are another set computed from the previous day.

Questions

Why doesn't my object appear on the chart?

Check that ObjectCreate() returned true, that the time and price fall in the visible part of the chart, that you drew on subwindow 0 if you meant the main chart, and call ChartRedraw(). An object with OBJPROP_TIMEFRAMES limited to other timeframes is also hidden.

How do I delete only the objects my EA created?

Give all of them a common prefix and call ObjectsDeleteAll(0, prefix) in OnDeinit(). Objects drawn by hand or by other programs, without that prefix, stay.

Should an EA draw signals with objects or with an indicator?

Objects suit a few marks and lines that the EA manages itself. For a mark on many bars, an indicator buffer draws faster and repaints correctly when you scroll or change timeframe.

This program is a minimal example written to show one technique, not a trading system, and none of it is a recommendation to trade. Trading involves substantial risk of loss and is not suitable for every investor. Edgoro is a software tool, not a broker or financial adviser: nothing it produces is investment advice, and past or backtested performance does not guarantee future results.

Want it inside a complete EA?

Ask Edgoro's MT5 EA generator to mark entries and exits on the chart and it adds the drawing code, with a prefix and cleanup, to the EA it writes. A clean build proves the code compiles, not that a strategy works: test it on a demo account.