//+------------------------------------------------------------------+ //| ZoneObject.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include "../Market/TimeHelpers.mqh" #include //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class ZoneObject { protected: int chartId; ENUM_TIMEFRAMES tf; string name; string symbol; string suffix; string zoneText; string zoneObjName; string zoneTextObjName; string _zoneText; string _zoneObjName; string _zoneTextObjName; color levelColor; color textColor; int pxDiff; bool isDrawExt; TimePrice a; TimePrice b; TimePrice c, d; bool isBull; bool isBroken; // --- Dirty tracking --- bool m_dirty, txt_dirty, _txt_dirty; bool m_lastVisible; bool m_lastExtVisible; datetime m_from_time, m_to_time; double m_min_price, m_max_price; virtual void Draw(int zoomLevel, int minZoom) = 0; virtual void DrawExt(int zoomLevel, int minZoom); virtual void DrawText(string name, string text, TimePrice &_a, double angle = 0); // Object deletion virtual bool IsExtVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice); virtual bool IsVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { m_from_time = fromTime; m_to_time = toTime; m_min_price = minPrice; m_max_price = maxPrice; return false; } void MarkDirty() { m_dirty = true; } public: ZoneObject(string _name, string _symbol, ENUM_TIMEFRAMES _tf, bool _isBull, string _zoneTxt0, int _chartId = 0) : suffix("visuals_"), m_dirty(true), txt_dirty(true), _txt_dirty(true), name(_name), symbol(_symbol), isBull(_isBull), zoneText(_zoneTxt0), tf(_tf), chartId(_chartId), pxDiff(6), levelColor(clrRosyBrown), textColor(clrRosyBrown), m_lastExtVisible(false), isDrawExt(true) { zoneObjName = suffix + name + "_" + GetTimeframeDescription(tf); zoneTextObjName = suffix + name + "_txt_" + GetTimeframeDescription(tf); _zoneObjName = suffix + name + "_ext_" + GetTimeframeDescription(tf); _zoneTextObjName = suffix + name + "_txt_ext_" + GetTimeframeDescription(tf); } ~ZoneObject() {Delete();} void UpdateDraw(int zoomLevel, datetime fromTime, datetime toTime, double minPrice, double maxPrice, int minZoom = 2); virtual void SetFontSize(int fontSize); // Property setters (call MarkDirty when changed) void SetText(string txt) { if(zoneText != txt) { zoneText = txt; MarkDirty(); } } void SetExtText(string txt) { if(_zoneText != txt) { _zoneText = txt; MarkDirty(); } } void SetColor(color clr) { if(levelColor != clr) { levelColor = clr; MarkDirty(); } } void SetBothColor(color clr) { if(levelColor != clr) { levelColor = clr; textColor = clr; MarkDirty(); } } // void SetExtEnd(TimePrice &_a) {c = _a; MarkDirty();} void SetBull(bool _isBull, TimePrice &_a); void SetDrawExt(bool _isDrawExt); // void SetFontSize(int fontSize); void SetAtBack(bool back = true); void SetFill(bool fill = true); virtual void SetA(TimePrice &_a) {a = _a; MarkDirty();} virtual void SetB(TimePrice &_b) {b = _b; MarkDirty();} void Delete(); void DeleteAllText(); void DeleteLevel(); void DeleteLevelText(); void DeleteExtender(); void DeleteExtenderText(); string GetText() { return zoneText;} string GetName() { return name;} ENUM_TIMEFRAMES GetTimeFrame() { return tf;} string GetSymbol() { return symbol;} TimePrice GetA() { return a;} TimePrice GetB() { return b;} }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ZoneObject:: IsExtVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { // Print("[PointLevel]::IsVisible() ",fromTime," :: ", a.x,"-- ", a.x >= fromTime); return (d.x >= fromTime && c.x <= toTime && d.y >= minPrice && c.y <= maxPrice); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::SetDrawExt(bool _isDrawExt = false) { isDrawExt = _isDrawExt; MarkDirty(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::UpdateDraw(int zoomLevel, datetime fromTime, datetime toTime, double minPrice, double maxPrice, int minZoom = 2) { // 1. Check visibility for main level bool isVisible = IsVisible(fromTime, toTime, minPrice, maxPrice); bool visibilityChanged = (isVisible != m_lastVisible); m_lastVisible = isVisible; // 2. Check visibility for extender bool isExtVisible = IsExtVisible(fromTime, toTime, minPrice, maxPrice); bool visibilityExtChanged = (isExtVisible != m_lastExtVisible); m_lastExtVisible = isExtVisible; // 3. Handle MAIN level if(!isVisible) { if(visibilityChanged) DeleteLevel(); // main left visible area → delete objects } else { // main is visible – redraw if dirty OR just became visible if(visibilityChanged || m_dirty || txt_dirty) Draw(zoomLevel, minZoom); } // 4. Handle EXTENDER level (same dirty flag) if(!isExtVisible) { if(visibilityExtChanged) DeleteExtender(); // ext left visible area → delete objects } else { // ext is visible – redraw if dirty OR just became visible if(visibilityExtChanged || m_dirty || _txt_dirty) { DrawExt(zoomLevel, minZoom); } } // 5. Clear the shared dirty flag only after both components have been processed if(m_dirty) m_dirty = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::DrawExt(int zoomLevel, int minZoom) { if(!isDrawExt) { return; } if(ObjectFind(chartId, _zoneObjName) < 0) { ObjectCreate(chartId, _zoneObjName, OBJ_TREND, 0, c.x, c.y, d.x, d.y); GetVisibilityMaskManual(_zoneObjName, tf); } else { ObjectMove(chartId, _zoneObjName, 0, c.x, c.y); ObjectMove(chartId, _zoneObjName, 1, d.x, d.y); } ObjectSetInteger(chartId, _zoneObjName, OBJPROP_COLOR, levelColor); ObjectSetInteger(chartId, _zoneObjName, OBJPROP_STYLE, STYLE_DOT); if(_zoneText != "" && zoomLevel >= minZoom) { double angle; CalcLineAngleDeg(chartId, c.x, c.y, d.x, d.y, angle); DrawText(_zoneTextObjName, _zoneText, c.CenterCoord(d), angle); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::DeleteExtender() { DeleteExtenderText(); ObjectDelete(chartId, _zoneObjName); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::DeleteExtenderText() { ObjectDelete(chartId, _zoneTextObjName); _txt_dirty = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::SetFontSize(int fontSize) { ObjectSetInteger(chartId, _zoneTextObjName, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(chartId, zoneTextObjName, OBJPROP_FONTSIZE, fontSize); MarkDirty(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::Delete() { DeleteLevel(); DeleteExtender(); MarkDirty(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::DeleteLevel() { bool res1 = ObjectDelete(chartId, zoneObjName); // Print("Deleting main object: ", zoneObjName, " on chart ", chartId, " -> Result: ", res1); txt_dirty = true; DeleteLevelText(); // also modify this one } void ZoneObject::DeleteLevelText() { bool res2 = ObjectDelete(chartId, zoneTextObjName); // Print("Deleting text object: ", zoneTextObjName, " on chart ", chartId, " -> Result: ", res2); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject:: DeleteAllText() { DeleteExtenderText(); DeleteLevelText(); txt_dirty = true; _txt_dirty = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ZoneObject::DrawText(string _name, string text, TimePrice &_a, double angle = 0) { int x = 0, y = 0; ChartTimePriceToXY(chartId, 0, _a.x, _a.y, x, y); if(!isBull) y += pxDiff; else y -= pxDiff; int sub_window = 0; datetime newTime = 0; double newPrice = 0.0; bool result = ChartXYToTimePrice(chartId, x, y, sub_window, newTime, newPrice); if(ObjectFind(chartId, _name) < 0) { ObjectCreate(chartId, _name, OBJ_TEXT, 0, _a.x, newPrice); GetVisibilityMaskManual(_name, tf); } else ObjectMove(chartId, _name, 0, _a.x, _a.y); ObjectSetString(chartId, _name, OBJPROP_TEXT, text); ObjectSetInteger(chartId, _name, OBJPROP_COLOR, textColor); ObjectSetDouble(chartId, _name, OBJPROP_ANGLE, angle); if(!isBull) ObjectSetInteger(chartId, _name, OBJPROP_ANCHOR, ANCHOR_UPPER); else ObjectSetInteger(chartId, _name, OBJPROP_ANCHOR, ANCHOR_LOWER); if(txt_dirty) txt_dirty = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class PointLevel : public ZoneObject { private: public: PointLevel(string _name, string _symbol, ENUM_TIMEFRAMES _tf, bool _isBull, TimePrice &_point, string txt = ""): ZoneObject(_name, _symbol, _tf, _isBull, txt) { a.Set(_point); b.Set(_point); c.Set(_point); d.Set(TimeCurrent(), c.y); } virtual void SetFontSize(int fontSize) { ObjectSetInteger(chartId, _zoneTextObjName, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(chartId, zoneTextObjName, OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(chartId, zoneObjName, OBJPROP_FONTSIZE, (long)MathMax(fontSize * 0.5, 8)); MarkDirty(); } virtual void SetA(TimePrice &_point) { a = _point; b.Set(_point); c.Set(_point); d.Set(TimeCurrent(), c.y); MarkDirty(); } virtual bool IsVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { ZoneObject::IsVisible(fromTime, toTime, minPrice, maxPrice); return (a.x >= fromTime && a.x <= toTime && a.y >= minPrice && a.y <= maxPrice); } virtual void Draw(int zoomLevel, int minZoom) { if(ObjectFind(chartId, zoneObjName) < 0) { ObjectCreate(chartId, zoneObjName, OBJ_TEXT, 0, a.x, a.y, b.x, b.y); GetVisibilityMaskManual(zoneObjName, tf); } else ObjectMove(chartId, zoneObjName, 0, a.x, a.y); ObjectSetInteger(chartId, zoneObjName, OBJPROP_COLOR, levelColor); ObjectSetString(chartId, zoneObjName, OBJPROP_TEXT, "●"); ObjectSetInteger(chartId, zoneObjName, OBJPROP_ANCHOR, ANCHOR_CENTER); if(zoneText != "" && zoomLevel >= minZoom) { double angle; CalcLineAngleDeg(chartId, a.x, a.y, b.x, b.y, angle); DrawText(zoneTextObjName, zoneText, a.CenterCoord(b), angle); } if(isDrawExt) { DrawExt(zoomLevel, minZoom); } } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class TrendLevel : public ZoneObject { private: public: TrendLevel(string _name, string _symbol, ENUM_TIMEFRAMES _tf, bool _isBull, TimePrice &_point, TimePrice &_pointB, string txt = ""): ZoneObject(_name, _symbol, _tf, _isBull, txt) { a.Set(_point); b.Set(_pointB); c.Set(_point.MaxT(_pointB)); d.Set(TimeCurrent(), c.y); } virtual void SetB(TimePrice &_point) { b.Set(_point); c.Set(a.MaxT(b)); d.Set(TimeCurrent(), c.y); MarkDirty(); } virtual bool IsVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { ZoneObject::IsVisible(fromTime, toTime, minPrice, maxPrice); return MathMax(a.x, b.x) >= fromTime && MathMin(a.x, b.x) <= toTime && MathMax(a.y, b.y) >= minPrice && MathMin(a.y, b.y) <= maxPrice; } virtual void Draw(int zoomLevel, int minZoom) { if(ObjectFind(chartId, zoneObjName) < 0) { ObjectCreate(chartId, zoneObjName, OBJ_TREND, 0, a.x, a.y, b.x, b.y); GetVisibilityMaskManual(zoneObjName, tf); } ObjectSetInteger(chartId, zoneObjName, OBJPROP_COLOR, levelColor); if(zoneText != "" && zoomLevel >= minZoom) { double angle; CalcLineAngleDeg(chartId, a.x, a.y, b.x, b.y, angle); DrawText(zoneTextObjName, zoneText, a.CenterCoord(b), angle); if(isDrawExt) { DrawExt(zoomLevel, minZoom); } } } }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ class RectLevel : public ZoneObject { private: public: RectLevel(string _name, string _symbol, ENUM_TIMEFRAMES _tf, bool _isBull, TimePrice &_point, TimePrice &_pointB, string txt = ""): ZoneObject(_name, _symbol, _tf, _isBull, txt) { a.Set(_point); b.Set(_pointB); c.Set(_point.MaxT(_pointB).x, _point.Max(_pointB).y); d.Set(TimeCurrent(), _point.Min(_pointB).y); } virtual bool IsVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice) { ZoneObject::IsVisible(fromTime, toTime, minPrice, maxPrice); return MathMax(a.x, b.x) >= fromTime && MathMin(a.x, b.x) <= toTime && MathMax(a.y, b.y) >= minPrice && MathMin(a.y, b.y) <= maxPrice; } virtual void DrawText(string _name, string text, TimePrice &_a, double angle = 0) { if(ObjectFind(chartId, _name) < 0) { ObjectCreate(chartId, _name, OBJ_TEXT, 0, _a.x, _a.y); GetVisibilityMaskManual(_name, tf); } ObjectSetString(chartId, _name, OBJPROP_TEXT, text); ObjectSetInteger(chartId, _name, OBJPROP_COLOR, textColor); ObjectSetDouble(chartId, _name, OBJPROP_ANGLE, angle); ObjectSetInteger(chartId, _name, OBJPROP_ANCHOR, ANCHOR_CENTER); if(txt_dirty) txt_dirty = false; } virtual void DrawExt(int zoomLevel, int minZoom) { if(ObjectFind(chartId, _zoneObjName) < 0) { ObjectCreate(chartId, _zoneObjName, OBJ_RECTANGLE, 0, c.x, c.y, d.x, d.y); GetVisibilityMaskManual(_zoneObjName, tf); } ObjectSetInteger(chartId, _zoneObjName, OBJPROP_COLOR, levelColor); ObjectSetInteger(chartId, _zoneObjName, OBJPROP_STYLE, STYLE_DOT); if(_zoneText != "" && zoomLevel >= minZoom) { DrawText(_zoneTextObjName, _zoneText, c.CenterCoord(d)); } } virtual void Draw(int zoomLevel, int minZoom) { if(ObjectFind(chartId, zoneObjName) < 0) { ObjectCreate(chartId, zoneObjName, OBJ_RECTANGLE, 0, a.x, a.y, b.x, b.y); GetVisibilityMaskManual(zoneObjName, tf); } ObjectSetInteger(chartId, zoneObjName, OBJPROP_COLOR, levelColor); if(zoneText != "" && zoomLevel >= minZoom) { DrawText(zoneTextObjName, zoneText, a.CenterCoord(b)); if(isDrawExt) { DrawExt(zoomLevel, minZoom); } } } }; //+------------------------------------------------------------------+