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.
#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 withexdraw_. Names are how MQL5 identifies objects, so a shared prefix keeps them apart from other programs' objects and letsObjectsDeleteAll(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 withObjectMove()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_TOPputs the arrow below the price with its tip on it (under a low),ANCHOR_BOTTOMabove it (over a high). Text uses the corner anchors, such asANCHOR_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 returnstruewhen the command is queued for the chart, and the object appears when the chart processes it, which is why the EA callsChartRedraw()after a batch of changes.OBJ_TEXTversusOBJ_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_TIMEFRAMESis there but not seen. In the Strategy Tester, objects appear only in visual mode.
Variations
- Trend lines and boxes.
OBJ_TRENDtakes two time and price points;OBJ_RECTANGLEdraws a box, for example around a session's range. - Wingdings arrows.
OBJ_ARROWwithOBJPROP_ARROWCODE(233 and 234 are the usual up and down arrows) gives more shapes thanOBJ_ARROW_UPandOBJ_ARROW_DOWN. - Marks as an indicator. For a mark on many bars, a custom indicator with a
DRAW_ARROWplot 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.