Edgoro
Expert Advisor · MQL5

MQL5 on-chart dashboard with labels (or Comment)

Short answer

For a quick display, Comment(text) writes lines in the chart's top-left corner. For a panel, create an OBJ_RECTANGLE_LABEL background and one OBJ_LABEL per value, placed in pixels with OBJPROP_XDISTANCE and OBJPROP_YDISTANCE, change their text with ObjectSetString(0, name, OBJPROP_TEXT, text) from OnTimer(), and delete them in OnDeinit().

UsesOBJ_LABELOBJ_RECTANGLE_LABELObjectSetStringEventSetTimerHistorySelectTERMINAL_SCREEN_DPI

The code

An Expert Advisor that trades nothing: it draws a panel with the account's balance, equity, floating and closed results, open positions, spread and server time.

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

MQL5
#define PFX "exdash_"

input long  InpMagic   = -1;          // Positions and deals to count (-1 = all on this account)
input int   InpX       = 12;          // Distance from the left edge, pixels
input int   InpY       = 24;          // Distance from the top edge, pixels
input color InpBg      = C'24,24,28'; // Background
input color InpText    = clrWhite;    // Text

string ROWS[] = {"Balance", "Equity", "Floating P/L", "Closed today", "Open positions", "Spread", "Server time"};
double scale  = 1.0;                  // screen DPI / 96, so the panel isn't tiny on a 4K screen

int Px(const int v) { return (int)MathRound(v * scale); }

void CreateLabel(const string name, const int x, const int y, const string text)
  {
   ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
   ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
   ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(0, name, OBJPROP_COLOR, InpText);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);    // not listed in the chart's object list
   ObjectSetString(0, name, OBJPROP_TEXT, text);
  }

void CreatePanel()
  {
   int rows = ArraySize(ROWS);
   string bg = PFX + "bg";
   ObjectCreate(0, bg, OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, bg, OBJPROP_CORNER, CORNER_LEFT_UPPER);
   ObjectSetInteger(0, bg, OBJPROP_XDISTANCE, Px(InpX));
   ObjectSetInteger(0, bg, OBJPROP_YDISTANCE, Px(InpY));
   ObjectSetInteger(0, bg, OBJPROP_XSIZE, Px(250));
   ObjectSetInteger(0, bg, OBJPROP_YSIZE, Px(16 + rows * 18));
   ObjectSetInteger(0, bg, OBJPROP_BGCOLOR, InpBg);
   ObjectSetInteger(0, bg, OBJPROP_BORDER_TYPE, BORDER_FLAT);
   ObjectSetInteger(0, bg, OBJPROP_COLOR, InpBg);
   ObjectSetInteger(0, bg, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, bg, OBJPROP_HIDDEN, true);
   for(int i = 0; i < rows; i++)
     {
      int y = Px(InpY + 8 + i * 18);
      CreateLabel(PFX + "k" + IntegerToString(i), Px(InpX + 10), y, ROWS[i]);
      CreateLabel(PFX + "v" + IntegerToString(i), Px(InpX + 130), y, "-");
     }
  }

bool Counted(const long magic) { return InpMagic < 0 || magic == InpMagic; }

// Net result of today's deals (profit, swap, commission, fees), deposits and withdrawals excluded
double ClosedToday()
  {
   datetime now = TimeTradeServer();
   if(!HistorySelect((datetime)((long)now / 86400 * 86400), now + 60))
      return 0.0;
   double sum = 0.0;
   for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
     {
      ulong deal = HistoryDealGetTicket(i);
      if(deal == 0 || !Counted(HistoryDealGetInteger(deal, DEAL_MAGIC)))
         continue;
      long type = HistoryDealGetInteger(deal, DEAL_TYPE);
      if(type != DEAL_TYPE_BUY && type != DEAL_TYPE_SELL)
         continue;                                     // balance, credit, bonus... aren't trading results
      sum += HistoryDealGetDouble(deal, DEAL_PROFIT) + HistoryDealGetDouble(deal, DEAL_SWAP)
             + HistoryDealGetDouble(deal, DEAL_COMMISSION) + HistoryDealGetDouble(deal, DEAL_FEE);
     }
   return sum;
  }

void SetValue(const int row, const string text)
  {
   ObjectSetString(0, PFX + "v" + IntegerToString(row), OBJPROP_TEXT, text);
  }

void Update()
  {
   string ccy = AccountInfoString(ACCOUNT_CURRENCY);
   double floating = 0.0;
   int    count    = 0;
   for(int i = PositionsTotal() - 1; i >= 0; i--)
     {
      if(PositionGetTicket(i) == 0 || !Counted(PositionGetInteger(POSITION_MAGIC)))
         continue;
      floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
      count++;
     }
   SetValue(0, StringFormat("%.2f %s", AccountInfoDouble(ACCOUNT_BALANCE), ccy));
   SetValue(1, StringFormat("%.2f %s", AccountInfoDouble(ACCOUNT_EQUITY), ccy));
   SetValue(2, StringFormat("%+.2f %s", floating, ccy));
   SetValue(3, StringFormat("%+.2f %s", ClosedToday(), ccy));
   SetValue(4, IntegerToString(count));
   SetValue(5, StringFormat("%d points", (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD)));
   SetValue(6, TimeToString(TimeTradeServer(), TIME_DATE | TIME_SECONDS));
   ChartRedraw();
  }

int OnInit()
  {
   scale = TerminalInfoInteger(TERMINAL_SCREEN_DPI) / 96.0;
   CreatePanel();
   Update();
   EventSetTimer(1);   // refresh once a second, ticks or not
   return INIT_SUCCEEDED;
  }

void OnTimer()
  {
   Update();
  }

void OnDeinit(const int reason)
  {
   EventKillTimer();
   ObjectsDeleteAll(0, PFX);
   ChartRedraw();
  }

How it works

  • scale. TERMINAL_SCREEN_DPI is 96 on a standard screen and more on high-resolution ones. Multiplying pixel sizes by DPI / 96 keeps the panel in proportion with the text, which Windows already scales.
  • Created once, updated often. CreatePanel() runs in OnInit(); Update() only changes texts. Re-creating objects on every refresh makes the panel flicker.
  • A one-second timer. A busy symbol can tick many times a second, and nobody reads a panel that fast. The timer also keeps the clock and spread moving when ticks stop.
  • ClosedToday(). Today's deals since server midnight, adding profit, swap, commission and fees. Deposits and withdrawals are deals too (DEAL_TYPE_BALANCE), so only buy and sell deals are counted.
  • Counted(). With InpMagic at -1 the panel shows the whole account; with an EA's magic number it shows only that EA's trades.
  • OBJPROP_HIDDEN. Keeps the labels out of the chart's object list, so a user cleaning up objects by hand doesn't delete the panel by accident.

Pitfalls

  • Comment() is shared. Each chart has one comment: another EA or script calling Comment() replaces yours. Clear it with Comment("") in OnDeinit(), or it stays after the EA is removed.
  • Heavy work on every tick. HistorySelect() and loops over positions on every tick of a fast market waste time the EA needs for trading. A timer refresh is enough.
  • The top-left corner is busy. The One Click Trading panel and the chart's own information sit there. Start the panel lower (InpY) or use another corner.
  • Other corners. With CORNER_RIGHT_UPPER, OBJPROP_XDISTANCE is measured from the right edge, and the label's anchor must be set to a right anchor (ANCHOR_RIGHT_UPPER), or the text runs off the screen.
  • Fonts that aren't there. A font missing on the machine, common on a VPS or under Wine, falls back to another one with different widths, and the columns no longer line up.

Variations

  • The one-line version. Comment(StringFormat("Equity %.2f\nSpread %d", AccountInfoDouble(ACCOUNT_EQUITY), (int)SymbolInfoInteger(_Symbol, SYMBOL_SPREAD))); from OnTimer(), and Comment("") in OnDeinit().
  • Colors that mean something. Set OBJPROP_COLOR of the P/L labels to red or green from their sign in Update().
  • Buttons. OBJ_BUTTON objects and OnChartEvent() with CHARTEVENT_OBJECT_CLICK turn the panel into a control: a button that runs the close-all loop, for example.
  • A daily loss line. Add today's result as a share of the day's starting balance, the number prop firms watch: the daily loss limit guide computes it.

Questions

Should I use Comment() or labels for an MT5 dashboard?

Comment() is fine for a few lines while developing. Labels give you position, colors, fonts and several blocks of information, and they don't clash with other programs that use Comment().

Why does my panel stay on the chart after I remove the EA?

Because objects and the chart comment outlive the program that created them. Delete the objects (ObjectsDeleteAll with your prefix) and clear the comment in OnDeinit().

Why is the text on my panel tiny on a 4K screen?

Pixel distances and sizes don't scale by themselves. Multiply them by TerminalInfoInteger(TERMINAL_SCREEN_DPI) / 96, as this example does, so the layout follows the screen.

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 for an EA with a status panel and it writes one like this, alongside the trading logic. A clean build proves the code compiles, not that a strategy works: test it on a demo account.