2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-10 18:31:08 +02:00 | | | //| ZoneLevel.mqh |
|
| | | //| Copyright 2025, MetaQuotes Ltd. |
|
| | | //+------------------------------------------------------------------+
|
| | | #property copyright "Copyright 2025, MetaQuotes Ltd."
|
| | | #property link "https://www.mql5.com"
|
| | | #property version "1.00"
|
| | |
|
| | | #include <ABCD/chartObjects.mqh>
|
| | | #include "../Utils/TimePrice.mqh"
|
| | |
|
| | | class VisualSystem; // Forward declaration
|
| | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | class ZoneLevel
|
| | | {
|
| | | protected:
|
| | | long chartId;
|
| | | string name;
|
| | | string levelText;
|
| | | string levelObjName;
|
| | | string levelTextObjName;
|
| | | string levelExtText;
|
| | | string levelExtObjName;
|
| | | string levelExtTextObjName;
|
| | | string suffix;
|
| | |
|
| | | color levelColor;
|
| | | color textColor;
|
| | | color mutedColor;
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | int width, pxDiff;
|
2026-06-10 18:31:08 +02:00 | | | TimePrice a;
|
| | | TimePrice b;
|
| | | TimePrice c;
|
2026-06-23 21:44:37 +02:00 | | | TimePrice d;
|
| | |
|
2026-06-10 18:31:08 +02:00 | | | bool isBull;
|
2026-06-23 21:44:37 +02:00 | | | bool isDrawExt;
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | // --- Dirty tracking ---
|
| | | bool m_dirty; // true if level needs redraw
|
| | | bool m_lastVisible; // previous visibility state
|
2026-06-23 21:44:37 +02:00 | | | bool m_lastExtVisible; // previous visibility state
|
| | |
|
| | | datetime m_from_time, m_to_time;
|
| | | double m_min_price, m_max_price;
|
| | |
|
| | |
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | public:
|
2026-06-23 21:44:37 +02:00 | | | ZoneLevel() : m_dirty(true), m_lastVisible(false), m_lastExtVisible(false) {}
|
2026-06-10 23:03:10 +02:00 | | | ZoneLevel(string _name, string text = "", string _levelExtText = "", int _chartId = 0, color clr = clrWhite, int _width = 1);
|
| | | ~ZoneLevel() { Delete(); }
|
| | |
|
| | | // Core methods
|
2026-06-23 21:44:37 +02:00 | | | 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;
|
2026-06-10 23:03:10 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | | return false;
|
| | | }
|
| | | virtual bool IsExtVisible(datetime fromTime, datetime toTime, double minPrice, double maxPrice);
|
| | | virtual void Draw(long chart_id, VisualSystem* vs) {}
|
| | | virtual void DrawExt(long chart_id, VisualSystem* vs);
|
2026-06-10 23:03:10 +02:00 | | | // --- Dirty-aware render ---
|
| | | void UpdateDraw(VisualSystem* vs, datetime fromTime, datetime toTime, double minPrice, double maxPrice);
|
| | |
|
| | | // Mark dirty when any property changes
|
| | | void MarkDirty() { m_dirty = true; }
|
| | |
|
| | | // Property setters (call MarkDirty when changed)
|
| | | void SetText(string txt) { if(levelText != txt) { levelText = txt; MarkDirty(); } }
|
| | | void SetExtText(string txt) { if(levelExtText != txt) { levelExtText = txt; MarkDirty(); } }
|
| | | void SetColor(color clr) { if(levelColor != clr) { levelColor = clr; MarkDirty(); } }
|
2026-06-23 21:44:37 +02:00 | | | void SetBothColor(color clr) { if(levelColor != clr) { levelColor = clr; textColor = clr; MarkDirty(); } }
|
| | | void SetExtEnd(TimePrice &_a) {c = _a; MarkDirty();}
|
2026-06-10 23:03:10 +02:00 | | | void SetBull(bool _isBull, TimePrice &_a);
|
2026-06-23 21:44:37 +02:00 | | | void SetDrawExt(bool _isDrawExt);
|
2026-06-10 23:03:10 +02:00 | | | void SetFontSize(int fontSize);
|
| | | void SetAtBack(bool back = true);
|
| | | void SetFill(bool fill = true);
|
| | |
|
| | | // Getter
|
2026-06-10 18:31:08 +02:00 | | | string GetName() const { return name; }
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | // Object deletion
|
| | | void Delete();
|
| | | void DeleteText();
|
| | | void DeleteExtender();
|
| | | void DeleteExtenderText();
|
| | | void DeleteLevel();
|
| | | void DeleteLevelText();
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | |
|
| | | datetime GetFromTime() const { return m_from_time; } // store as member
|
| | | datetime GetToTime() const { return m_to_time; }
|
| | | double GetMinPrice() const { return m_min_price; }
|
| | | double GetMaxPrice() const { return m_max_price; }
|
| | |
|
2026-06-10 23:03:10 +02:00 | | | protected:
|
2026-06-23 21:44:37 +02:00 | | | void DrawText(string name, string text, TimePrice &_a, double angle = 0);
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | double RadToDeg(double rad)
|
| | | {
|
| | | return rad * 180.0 / M_PI;
|
| | | }//+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | |
|
| | | bool CalcLineAngleDeg(datetime t1, double p1, datetime t2, double p2, double &angleDeg)
|
| | | {
|
| | | int x1, y1, x2, y2;
|
| | | if(!ChartTimePriceToXY(chartId, 0, t1, p1, x1, y1))
|
| | | return false;
|
| | | if(!ChartTimePriceToXY(chartId, 0, t2, p2, x2, y2))
|
| | | return false;
|
| | |
|
| | | double dx = (double)(x2 - x1);
|
| | | double dy = (double)(y1 - y2);
|
| | |
|
| | | if(MathAbs(dx) < 0.000001 && MathAbs(dy) < 0.000001)
|
| | | {
|
| | | angleDeg = 0.0;
|
| | | return true;
|
| | | }
|
| | |
|
| | | angleDeg = RadToDeg(MathArctan2(dy, dx));
|
| | | return true;
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | | };
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Constructor |
|
| | | //+------------------------------------------------------------------+
|
| | | ZoneLevel::ZoneLevel(string _name, string text = "", string _levelExtText = "", int _chartId = 0, color clr = clrWhite, int _width = 1)
|
| | | : name(_name), levelText(text), levelExtText(_levelExtText), width(_width), chartId(_chartId),
|
2026-06-23 21:44:37 +02:00 | | | levelColor(clr), textColor(clr), isBull(false), isDrawExt(false), pxDiff(6), m_dirty(true), m_lastVisible(false), m_lastExtVisible(false)
|
2026-06-10 23:03:10 +02:00 | | | {
|
| | | suffix = "VIS_";
|
| | | levelObjName = suffix + _name;
|
| | | levelTextObjName = suffix + "txt_" + _name;
|
| | | levelExtTextObjName = suffix + "txt_ext_" + _name;
|
| | | levelExtObjName = suffix + "ext_" + _name;
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Dirty-aware update – called by VisualSystem::Render() |
|
| | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | void ZoneLevel::UpdateDraw(VisualSystem* vs,
|
| | | datetime fromTime, datetime toTime,
|
| | | double minPrice, double maxPrice)
|
2026-06-10 23:03:10 +02:00 | | | {
|
2026-06-23 21:44:37 +02:00 | | | // 1. Check visibility for main level
|
2026-06-10 23:03:10 +02:00 | | | bool isVisible = IsVisible(fromTime, toTime, minPrice, maxPrice);
|
| | | bool visibilityChanged = (isVisible != m_lastVisible);
|
| | | m_lastVisible = isVisible;
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | | // 2. Check visibility for extender
|
| | | bool isExtVisible = IsExtVisible(fromTime, toTime, minPrice, maxPrice);
|
| | | bool visibilityExtChanged = (isExtVisible != m_lastExtVisible);
|
| | | m_lastExtVisible = isExtVisible;
|
| | |
|
| | | // 3. Handle MAIN level
|
2026-06-10 23:03:10 +02:00 | | | if(!isVisible)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | if(visibilityChanged)
|
2026-06-23 21:44:37 +02:00 | | | DeleteLevel(); // main left visible area → delete objects
|
| | | }
|
| | | else
|
| | | {
|
| | | // main is visible – redraw if dirty OR just became visible
|
| | | if(visibilityChanged || m_dirty)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-23 21:44:37 +02:00 | | | Draw(chartId, vs);
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | | }
|
| | |
|
2026-06-23 21:44:37 +02:00 | | | // 4. Handle EXTENDER level (same dirty flag)
|
| | | if(!isExtVisible)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-23 21:44:37 +02:00 | | | if(visibilityExtChanged)
|
| | | DeleteExtender(); // ext left visible area → delete objects
|
| | | }
|
| | | else
|
| | | {
|
| | | // ext is visible – redraw if dirty OR just became visible
|
| | | if(visibilityExtChanged || m_dirty)
|
| | | {
|
| | | DrawExt(chartId, vs);
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | | }
|
| | |
|
2026-06-23 21:44:37 +02:00 | | | // 5. Clear the shared dirty flag only after both components have been processed
|
| | | if(m_dirty)
|
| | | m_dirty = false;
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Set Bull direction (moves text anchor) |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::SetBull(bool _isBull, TimePrice &_a)
|
| | | {
|
| | | if(isBull == _isBull && a.x == _a.x && a.y == _a.y)
|
| | | return; // no change
|
| | | isBull = _isBull;
|
| | | a = _a;
|
| | | MarkDirty();
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | // Convert original to screen X,Y, adjust, then update object position
|
| | | int x = 0, y = 0;
|
| | | ChartTimePriceToXY(chartId, 0, a.x, a.y, x, y);
|
| | | if(!isBull)
|
| | | y += pxDiff;
|
| | | else
|
| | | y -= pxDiff;
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | int sub_window = 0;
|
| | | datetime newTime = 0;
|
| | | double newPrice = 0.0;
|
| | | ChartXYToTimePrice(chartId, x, y, sub_window, newTime, newPrice);
|
| | | ObjectSetDouble(chartId, name, OBJPROP_PRICE, newPrice);
|
| | | }
|
2026-06-23 21:44:37 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | bool ZoneLevel:: 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 ZoneLevel::SetDrawExt(bool _isDrawExt)
|
| | | {
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | | isDrawExt = _isDrawExt;
|
| | |
|
| | | MarkDirty();
|
| | | }
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Set font size for both text objects |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::SetFontSize(int fontSize)
|
| | | {
|
| | | ObjectSetInteger(chartId, levelTextObjName, OBJPROP_FONTSIZE, fontSize);
|
| | | ObjectSetInteger(chartId, levelExtTextObjName, OBJPROP_FONTSIZE, fontSize);
|
| | | // Font change doesn't require redraw of geometry, but we mark dirty anyway
|
| | | MarkDirty();
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | void ZoneLevel::SetAtBack(bool back = true)
|
2026-06-10 18:31:08 +02:00 | | | {
|
2026-06-10 23:03:10 +02:00 | | | ObjectSetInteger(chartId, name, OBJPROP_BACK, back);
|
| | | MarkDirty();
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::SetFill(bool fill = true)
|
| | | {
|
| | | ObjectSetInteger(chartId, name, OBJPROP_FILL, fill);
|
| | | MarkDirty();
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| Draw text helper |
|
| | | //+------------------------------------------------------------------+
|
2026-06-23 21:44:37 +02:00 | | | void ZoneLevel::DrawText(string name, string text, TimePrice &_a, double angle = 0)
|
2026-06-10 23:03:10 +02:00 | | | {
|
2026-06-23 21:44:37 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | int x = 0, y = 0;
|
| | | ChartTimePriceToXY(chartId, 0, _a.x, _a.y, x, y);
|
| | | if(!isBull)
|
| | | y += pxDiff;
|
| | | else
|
| | | y -= pxDiff;
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | int sub_window = 0;
|
| | | datetime newTime = 0;
|
| | | double newPrice = 0.0;
|
| | | ChartXYToTimePrice(chartId, x, y, sub_window, newTime, newPrice);
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | ObjectCreate(chartId, name, OBJ_TEXT, 0, _a.x, newPrice);
|
| | | ObjectSetString(chartId, name, OBJPROP_TEXT, text);
|
| | | ObjectSetInteger(chartId, name, OBJPROP_COLOR, textColor);
|
2026-06-23 21:44:37 +02:00 | | | ObjectSetDouble(chartId, name, OBJPROP_ANGLE, angle);
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-10 23:03:10 +02:00 | | | if(!isBull)
|
| | | ObjectSetInteger(chartId, name, OBJPROP_ANCHOR, ANCHOR_UPPER);
|
| | | else
|
| | | ObjectSetInteger(chartId, name, OBJPROP_ANCHOR, ANCHOR_LOWER);
|
2026-06-10 18:31:08 +02:00 | | | }
|
2026-06-23 21:44:37 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DrawExt(long chart_id, VisualSystem* vs)
|
| | | {
|
| | | if(a.Ok() && b.Ok())
|
| | | d = b;
|
| | | else
|
| | | if(a.Ok() && !b.Ok())
|
| | | {
|
| | | d = a;
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | |
|
2026-06-23 21:44:37 +02:00 | | | if(ObjectFind(chartId, levelExtObjName) < 0)
|
| | | {
|
| | | ObjectCreate(chartId, levelExtObjName, OBJ_TREND, 0, d.x, d.y, c.x, c.y);
|
| | | }
|
| | | ObjectSetInteger(chartId, levelExtObjName, OBJPROP_WIDTH, 1);
|
| | | ObjectSetInteger(chartId, levelExtObjName, OBJPROP_COLOR, levelColor);
|
| | | ObjectSetInteger(chartId, levelExtObjName, OBJPROP_STYLE, STYLE_DOT);
|
| | | ObjectSetInteger(chartId, levelExtObjName, OBJPROP_BACK, false);
|
| | |
|
| | |
|
| | | if(levelExtText != "" && vs.Zoom() >= 2)
|
| | | {
|
| | | double angle;
|
| | | CalcLineAngleDeg(d.x, d.y, c.x, c.y, angle);
|
| | | DrawText(levelExtTextObjName, levelExtText, d.CenterCoord(c), angle);
|
| | | // SetFontSize(vs.GetScaledFontSize(12));
|
| | | }
|
| | | }
|
2026-06-10 18:31:08 +02:00 | | | //+------------------------------------------------------------------+
|
2026-06-10 23:03:10 +02:00 | | | //| Delete all objects |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::Delete()
|
| | | {
|
| | | // Print("Delete");
|
| | | DeleteExtender();
|
| | | DeleteLevel();
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DeleteText()
|
| | | {
|
| | | DeleteLevelText();
|
| | | DeleteExtenderText();
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DeleteExtender()
|
| | | {
|
| | | DeleteExtenderText();
|
| | | ObjectDelete(chartId, levelExtObjName);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DeleteExtenderText()
|
| | | {
|
| | | ObjectDelete(chartId, levelExtTextObjName);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DeleteLevel()
|
| | | {
|
| | | ObjectDelete(chartId, levelObjName);
|
| | | DeleteLevelText();
|
| | | }
|
| | | //+------------------------------------------------------------------+
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | void ZoneLevel::DeleteLevelText()
|
| | | {
|
| | | ObjectDelete(chartId, levelTextObjName);
|
| | | }
|
| | | //+------------------------------------------------------------------+
|