An EA running on a VPS chart tells you very little at a glance. Is trading allowed right now? How many positions does it hold? What was the last error? You can find all of it in the Experts tab and the Toolbox, but when you open the terminal for ten seconds to check, a few lines of text on the chart are much faster.
This tutorial builds a panel of plain text labels in the top left corner. It updates once a second, works when no ticks arrive, stays out of the way of optimization, and cleans up after itself.
Why labels and not Comment()
Comment() is the quickest option and is fine for debugging. It has two limits: everything is one block in one colour, and any other program on the same chart that calls Comment() overwrites it. OBJ_LABEL objects are positioned in pixels from a chart corner, so they do not move when the chart scrolls, and each line can have its own colour.
Step 1: inputs and setup
input long MagicNumber = 20261105; // only count this EA's positions
input bool ShowPanel = true; // draw the status panel
#define PANEL_PREFIX "CTBPANEL_"
#define PANEL_LINES 5
bool g_panelOn = false;
string g_lastError = "none";
int OnInit()
{
bool tester = MQLInfoInteger(MQL_TESTER) != 0;
bool visual = MQLInfoInteger(MQL_VISUAL_MODE) != 0;
// Draw on live charts and in visual tests only, never during optimization
g_panelOn = ShowPanel && (!tester || visual);
if(g_panelOn)
{
CreatePanel();
EventSetTimer(1);
UpdatePanel();
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
ObjectsDeleteAll(0, PANEL_PREFIX);
ChartRedraw();
}
All object names start with one prefix, so ObjectsDeleteAll(0, PANEL_PREFIX) removes the panel and nothing else on the chart. Chart objects in a non visual test or an optimization pass only cost time, so the panel switches itself off there.
Step 2: creating the labels
void CreatePanel()
{
int y = 20;
for(int i = 0; i < PANEL_LINES; i++)
{
string name = PANEL_PREFIX + IntegerToString(i);
if(ObjectFind(0, name) < 0)
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9);
ObjectSetInteger(0, name, OBJPROP_COLOR, clrSilver);
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
ObjectSetString(0, name, OBJPROP_TEXT, " ");
y += 16; // next line 16 pixels lower
}
}
void SetLine(int i, string text, color clr = clrSilver)
{
string name = PANEL_PREFIX + IntegerToString(i);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
}
A fixed width font such as Consolas keeps numbers aligned. OBJPROP_SELECTABLE false stops a stray click from dragging a label, and OBJPROP_HIDDEN true keeps the labels out of the object list so nobody deletes them by accident. If the labels already exist, for example after a timeframe change, ObjectFind skips the create and the properties are simply set again.
Step 3: filling it in
void UpdatePanel()
{
if(!g_panelOn)
return;
int count = 0;
double floating = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0)
continue;
if(PositionGetInteger(POSITION_MAGIC) != MagicNumber)
continue;
if(PositionGetString(POSITION_SYMBOL) != _Symbol)
continue;
count++;
floating += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
bool canTrade = TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) != 0
&& MQLInfoInteger(MQL_TRADE_ALLOWED) != 0;
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
string ccy = AccountInfoString(ACCOUNT_CURRENCY);
SetLine(0, "EA on " + _Symbol + ", magic " + IntegerToString(MagicNumber));
SetLine(1, canTrade ? "Trading allowed" : "Trading OFF (Algo Trading button or EA settings)",
canTrade ? clrLimeGreen : clrOrangeRed);
SetLine(2, "Open positions: " + IntegerToString(count));
SetLine(3, "Floating: " + DoubleToString(floating, 2) + " " + ccy,
floating < 0.0 ? clrOrangeRed : clrSilver);
SetLine(4, "Spread: " + IntegerToString(spread) + " points, last error: " + g_lastError);
ChartRedraw();
}
void OnTimer()
{
UpdatePanel();
}
The update runs from OnTimer, not OnTick. On a quiet symbol, or over the weekend, there may be no ticks for a long time, and the panel should still show that trading was switched off. One ChartRedraw at the end repaints all five lines together.
Floating profit here adds POSITION_PROFIT and POSITION_SWAP. Commission is charged on deals, not on the open position, so this number is slightly better than what you would get if you closed now.
Step 4: recording the last error
Wherever the EA sends an order, store a short description when it fails:
#include <Trade\Trade.mqh>
CTrade trade;
void TryBuy(double lots)
{
if(!trade.Buy(lots, _Symbol))
{
g_lastError = IntegerToString(trade.ResultRetcode()) + " " + trade.ResultRetcodeDescription();
UpdatePanel();
}
}
Keep it short. The full details still belong in the log; the panel only has to tell you that something needs a look.
Things to keep in mind
- The panel is a view, not state. Do not read values back from the labels to make trading decisions.
- Labels sit on top of price bars. If they cover something important, move them with OBJPROP_XDISTANCE or anchor them to another corner with OBJPROP_CORNER.
- If an indicator or another tool on the same chart also draws labels, give each one its own prefix, or removing one program will delete the other's objects.
- Keep each line short. Long error descriptions are hard to read on a small VPS screen; the full text belongs in the log.
Five lines of text will not make an EA better at trading. They do make it much quicker to notice when it has stopped trading.
Top comments (0)