//+------------------------------------------------------------------+ //| VolatilityRoundPriceScale.mq5 | //+------------------------------------------------------------------+ #property copyright "2026" #property link "" #property version "1.25" #property description "Volatility-based round-number price scale + local time scale + Sessions" #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 //--- Inputs input int InpAtrPeriod = 14; // ATR period input ENUM_TIMEFRAMES InpAtrTimeframe = PERIOD_CURRENT; // ATR timeframe input double InpAtrMultiplier = 1.0; // ATR multiplier input int InpMaxLabels = 10; // Maximum price labels visible input double InpMinStepPips = 5.0; // Minimum step in pips input double InpMaxStepPips = 10000.0; // Maximum step in pips input double InpCustomPipSize = 0.0; // Custom pip size, 0 = auto input bool InpHideDefaultScale = true; // Hide default price scale input bool InpShowLines = true; // Show horizontal lines input int InpRightOffsetBars = 0; // Label offset from right edge, bars (0 = flush) input string InpFontName = "Segoe UI"; // Font name input int InpFontSize = 8; // Font size input int InpLabelPadding = 1; // Padding around label text in box, px input color InpTextColor = clrWhite; // Normal price label color input color InpPriceLineColor = clrWhite; // Price grid line color input int InpRefreshSeconds = 5; // Redraw interval, seconds //--- Chart margin inputs input bool InpCreateRightMargin = true; // Shift chart to free right margin input double InpRightMarginPercent = 8.0; // MAX right margin, % of width (auto-fits to labels) input bool InpBottomMargin = true; // Opaque bottom strip for time labels input bool InpBottomPadding = true; // Dynamically shift candles up to avoid bottom strip //--- Scale separator inputs input bool InpShowScaleBorders = true; // 1px separator lines plot/scales input color InpScaleBorderColor = clrWhite; // Separator line color //--- Line style inputs input ENUM_LINE_STYLE InpNormalLineStyle = STYLE_DOT; // Normal grid line style input ENUM_LINE_STYLE InpThickLineStyle = STYLE_DASHDOTDOT; // Thick (round number / day) line style //--- Highlight inputs (major round numbers) input bool InpEnableHighlight = true; // Enable round-number highlight input int InpRoundMode = 0; // 0=auto, 1=fixed pips, 2=fixed price input double InpRoundNumberPips = 100.0; // Mode 1: round size in pips input double InpRoundNumberPrice = 100.0; // Mode 2: round size in price input bool InpUseHighlightBackground= true; // Use pink background input color InpHighlightTextColor = clrBlack; // Round-number text color input color InpHighlightBackColor = clrLightPink; // Box + round line color input int InpRoundLineWidth = 2; // Round-number line thickness, px //--- Local time scale inputs input bool InpHideDefaultTimeScale = true; // Hide built-in date/time scale input int InpMaxTimeLabels = 16; // Maximum time labels visible input bool InpShowDateOnNewDay = true; // Special label at midnight input int InpMidnightLabelMode = 0; // 0="Fri", 1="Fri 05.09", 2="05.09" input bool InpTimeShowMinutes = false; // false = "13", true = "13:00" input bool InpForceEveryHour = false; // true = mark every single hour input color InpHourLineColor = clrWhite; // Hour vertical line (grid) color input color InpHourTextColor = clrWhite; // Hour label text color //--- Day (midnight) highlight inputs input color InpDayLineColor = clrSkyBlue; // Midnight vertical line color input int InpDayLineWidth = 2; // Midnight line thickness, px input bool InpUseDayBackground = true; // Sky-blue box behind day label input color InpDayBackColor = clrSkyBlue; // Day label background color input color InpDayTextColor = clrBlack; // Day label text color //--- Session inputs (SYD, TYO, LDN, NYC) input bool InpShowSessions = true; // Draw session open lines input color InpSessionColor = clrLime; // Session line color input int InpSessionLineWidth = 1; // Session line thickness, px input ENUM_LINE_STYLE InpSessionLineStyle = STYLE_DASH; // Session line style //--- Globals const string PREFIX = "VolRoundScale_"; //--- English short day names, index = MqlDateTime.day_of_week (0=Sunday) const string DAY_NAMES[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; int g_atrHandle = INVALID_HANDLE; double g_pip = 0.0; bool g_scaleHidden = false; bool g_originalScale = true; bool g_dateScaleHidden = false; bool g_originalDateScale = true; bool g_shiftChanged = false; bool g_originalShift = false; double g_originalShiftSize = 10.0; double g_appliedShiftPct = -1.0; int g_plotRightX = 0; int g_borderX = 0; //--- Vertical Scale Management bool g_isManagingScale = false; bool g_origScaleFix = false; double g_origFixedMin = 0.0; double g_origFixedMax = 0.0; ulong g_lastRedrawMs = 0; bool g_needRedraw = true; //--- Layout signature (skip rebuild when nothing visible changed) bool g_sigValid = false; int g_sigStart = 0; int g_sigEnd = 0; int g_sigW = 0; int g_sigH = 0; long g_sigStepPts = 0; long g_sigRoundPts= 0; long g_sigMinPts = 0; long g_sigMaxPts = 0; long g_sigStepH = 0; long g_sigFirstB = 0; long g_sigOff = 0; long g_sigHour = 0; datetime g_sigRight = 0; datetime g_sigTL = 0; datetime g_sigTR = 0; bool g_sigTime = false; //--- Function prototypes int RedrawScale(bool force); void ApplyVerticalPadding(); bool ComputeTimeScale(int width, int height, datetime &tLeft, datetime &tRight, long &off, long &stepH, long &firstB, long &lRight); void BuildTimeScale(int width, int height, long off, long stepH, long firstB, long lRight); void DrawSessionLines(int width, int height, datetime tLeft, datetime tRight); void TimeScaleGeometry(int height, int &stripH, int &labelY, bool &useStrip); string MidnightLabel(const long localSeconds); void TextBoxSize(const string text, int &w, int &h); void CreatePixelBackground(const string name, const int x, const int y, const int w, const int h, const color bg); datetime RightEdgeTime(); void CreateHighlightBackground(const string key, const datetime rightTime, const double price, const string label); double ChooseStepPrice(double min, double max, double atrValue); double NiceCeil(double value); double PipSize(); bool IsDST_Sydney(datetime utcTime); bool IsDST_London(datetime utcTime); bool IsDST_NYC(datetime utcTime); //+------------------------------------------------------------------+ //| Refresh interval in ms | //+------------------------------------------------------------------+ ulong RefreshMs() { int sec = InpRefreshSeconds; if(sec < 1) sec = 1; return (ulong)sec * 1000; } //+------------------------------------------------------------------+ //| Throttled redraw | //+------------------------------------------------------------------+ void TryRedraw(bool force = false, bool bypassThrottle = false) { ulong now = GetTickCount64(); if(bypassThrottle) { static ulong lastEventMs = 0; if(now - lastEventMs < 150) return; lastEventMs = now; } else if(!force && now - g_lastRedrawMs < RefreshMs()) return; if(!force && !g_needRedraw) return; int r = RedrawScale(force); if(r > 0) { g_needRedraw = false; g_lastRedrawMs = now; } } //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { g_pip = PipSize(); if(g_pip <= 0.0) return INIT_FAILED; g_atrHandle = iATR(_Symbol, InpAtrTimeframe, InpAtrPeriod); if(g_atrHandle == INVALID_HANDLE) return INIT_FAILED; if(InpHideDefaultScale) { g_originalScale = (bool)ChartGetInteger(0, CHART_SHOW_PRICE_SCALE); if(ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, false)) g_scaleHidden = true; } if(InpHideDefaultTimeScale) { g_originalDateScale = (bool)ChartGetInteger(0, CHART_SHOW_DATE_SCALE); if(ChartSetInteger(0, CHART_SHOW_DATE_SCALE, false)) g_dateScaleHidden = true; } if(InpCreateRightMargin) { g_originalShift = (bool)ChartGetInteger(0, CHART_SHIFT); g_originalShiftSize = ChartGetDouble(0, CHART_SHIFT_SIZE); if(ChartSetInteger(0, CHART_SHIFT, true)) { g_shiftChanged = true; g_appliedShiftPct = -1.0; } } //--- Save original vertical scale state g_origScaleFix = (bool)ChartGetInteger(0, CHART_SCALEFIX); g_origFixedMin = ChartGetDouble(0, CHART_FIXED_MIN); g_origFixedMax = ChartGetDouble(0, CHART_FIXED_MAX); g_sigValid = false; EventSetTimer(MathMax(1, InpRefreshSeconds)); TryRedraw(true); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); ObjectsDeleteAll(0, PREFIX); if(g_scaleHidden) ChartSetInteger(0, CHART_SHOW_PRICE_SCALE, g_originalScale); if(g_dateScaleHidden) ChartSetInteger(0, CHART_SHOW_DATE_SCALE, g_originalDateScale); if(g_shiftChanged) { ChartSetInteger(0, CHART_SHIFT, g_originalShift); ChartSetDouble(0, CHART_SHIFT_SIZE, g_originalShiftSize); } //--- Restore original vertical scale state if(g_isManagingScale) { ChartSetInteger(0, CHART_SCALEFIX, false); // Turn off to restore native autoscale if(g_origScaleFix) { ChartSetDouble(0, CHART_FIXED_MIN, g_origFixedMin); ChartSetDouble(0, CHART_FIXED_MAX, g_origFixedMax); ChartSetInteger(0, CHART_SCALEFIX, true); } } if(g_atrHandle != INVALID_HANDLE) IndicatorRelease(g_atrHandle); } //+------------------------------------------------------------------+ //| OnCalculate : NO drawing here, only a dirty flag | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { g_needRedraw = true; return rates_total; } //+------------------------------------------------------------------+ //| OnTimer | //+------------------------------------------------------------------+ void OnTimer() { TryRedraw(false); } //+------------------------------------------------------------------+ //| OnChartEvent | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_CHART_CHANGE) { g_needRedraw = true; TryRedraw(false, true); } } //+------------------------------------------------------------------+ //| Dynamically calculate visible high/low and pad the bottom | //+------------------------------------------------------------------+ void ApplyVerticalPadding() { if(!InpBottomPadding) { if(g_isManagingScale) { ChartSetInteger(0, CHART_SCALEFIX, false); if(g_origScaleFix) { ChartSetDouble(0, CHART_FIXED_MIN, g_origFixedMin); ChartSetDouble(0, CHART_FIXED_MAX, g_origFixedMax); ChartSetInteger(0, CHART_SCALEFIX, true); } g_isManagingScale = false; } return; } int height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(height <= 0) return; int stripH = 0, labelY = 0; bool useStrip = false; TimeScaleGeometry(height, stripH, labelY, useStrip); int bottomPadPx = useStrip ? stripH + 2 : 0; int topPadPx = 5; if(bottomPadPx <= 0 && topPadPx <= 0) return; long firstBar = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); long visibleBars = ChartGetInteger(0, CHART_VISIBLE_BARS); if(firstBar <= 0 || visibleBars <= 0) return; int rightIdx = (int)(firstBar - visibleBars + 1); if(rightIdx < 0) rightIdx = 0; int count = (int)(firstBar - rightIdx + 1); if(count <= 0 || count > 10000) return; double highs[], lows[]; ArraySetAsSeries(highs, true); ArraySetAsSeries(lows, true); if(CopyHigh(_Symbol, _Period, rightIdx, count, highs) != count) return; if(CopyLow(_Symbol, _Period, rightIdx, count, lows) != count) return; int iMax = ArrayMaximum(highs); int iMin = ArrayMinimum(lows); if(iMax < 0 || iMin < 0) return; double maxH = highs[iMax]; double minL = lows[iMin]; double range = maxH - minL; if(range <= 0) range = 100 * _Point; int usableHeight = height - bottomPadPx - topPadPx; if(usableHeight <= 0) usableHeight = 1; double pricePerPx = range / (double)usableHeight; double newMin = minL - (pricePerPx * bottomPadPx); double newMax = maxH + (pricePerPx * topPadPx); double currentMin = ChartGetDouble(0, CHART_FIXED_MIN); double currentMax = ChartGetDouble(0, CHART_FIXED_MAX); // Hysteresis threshold to prevent infinite recalculation feedback loops double threshold = pricePerPx * 3.0; if(!g_isManagingScale || MathAbs(currentMin - newMin) > threshold || MathAbs(currentMax - newMax) > threshold) { ChartSetInteger(0, CHART_SCALEFIX, true); ChartSetDouble(0, CHART_FIXED_MIN, newMin); ChartSetDouble(0, CHART_FIXED_MAX, newMax); g_isManagingScale = true; } } //+------------------------------------------------------------------+ //| Main redraw. Returns: 0=failed, 1=skipped(same), 2=redrawn | //+------------------------------------------------------------------+ int RedrawScale(bool force) { //--- Apply vertical padding first so the chart scale is correct //--- before we read CHART_PRICE_MIN/MAX for the grid lines. ApplyVerticalPadding(); double min = ChartGetDouble(0, CHART_PRICE_MIN); double max = ChartGetDouble(0, CHART_PRICE_MAX); if(max <= min) { double mid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(mid <= 0.0) mid = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(mid <= 0.0) mid = SymbolInfoDouble(_Symbol, SYMBOL_LAST); if(mid <= 0.0) return 0; min = mid - 100.0 * g_pip; max = mid + 100.0 * g_pip; } double atr[]; ArraySetAsSeries(atr, true); if(CopyBuffer(g_atrHandle, 0, 0, 1, atr) <= 0) return 0; double step = ChooseStepPrice(min, max, atr[0]); if(step <= 0.0) return 0; int start = (int)MathFloor(min / step); int end = (int)MathCeil(max / step); if(end < start) return 0; while(end - start > 500 && step > 0.0) { step *= 2.0; start = (int)MathFloor(min / step); end = (int)MathCeil(max / step); } int w = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int h = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(w <= 0 || h <= 0) return 0; int twMax = 0, thMax = 0; TextBoxSize(DoubleToString(max, _Digits), twMax, thMax); int neededPx = twMax + 8; g_plotRightX = w - neededPx; g_borderX = g_plotRightX + 2; if(g_plotRightX < 0) g_plotRightX = 0; if(g_borderX < 0) g_borderX = 0; if(g_borderX > w - 1) g_borderX = w - 1; if(InpCreateRightMargin) { double neededPct = (double)neededPx / (double)w * 100.0; if(neededPct > InpRightMarginPercent) neededPct = InpRightMarginPercent; if(neededPct < 1.0) neededPct = 1.0; if(neededPct > 50.0) neededPct = 50.0; if(MathAbs(g_appliedShiftPct - neededPct) > 0.05) { ChartSetDouble(0, CHART_SHIFT_SIZE, neededPct); g_appliedShiftPct = neededPct; } } int stripH = 0; int labelY = 0; bool useStrip = false; TimeScaleGeometry(h, stripH, labelY, useStrip); datetime tLineLeft = 0; datetime tLineRight = 0; int subL = 0; double pL = 0.0; ChartXYToTimePrice(0, 0, h / 2, subL, tLineLeft, pL); ChartXYToTimePrice(0, g_plotRightX, h / 2, subL, tLineRight, pL); bool drawLines = InpShowLines; if(tLineLeft == 0 || tLineRight == 0 || tLineRight <= tLineLeft) { long firstBar2 = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); tLineLeft = iTime(_Symbol, _Period, (int)firstBar2); tLineRight = iTime(_Symbol, _Period, 0); } if(tLineLeft == 0 || tLineRight == 0 || tLineRight <= tLineLeft) drawLines = false; datetime rightTime = RightEdgeTime(); double roundStep = 0.0; if(InpEnableHighlight) { if(InpRoundMode == 0) roundStep = MathPow(10.0, MathFloor(MathLog(step) / MathLog(10.0)) + 1.0); else if(InpRoundMode == 1) roundStep = InpRoundNumberPips * g_pip; else roundStep = InpRoundNumberPrice; while(roundStep > 0.0 && roundStep <= step) roundStep *= 10.0; } long roundPoints = 0; if(roundStep > 0.0 && _Point > 0.0) roundPoints = (long)MathRound(roundStep / _Point); datetime tL = 0, tR = 0; long off = 0, stepH = 0, firstB = 0, lR = 0; bool haveTime = ComputeTimeScale(w, h, tL, tR, off, stepH, firstB, lR); long stepPts = (long)MathRound(step / _Point); long minPts = (long)MathRound(min / _Point); long maxPts = (long)MathRound(max / _Point); long hourNow = (long)TimeLocal() / 3600; if(!force && g_sigValid && g_sigStart == start && g_sigEnd == end && g_sigStepPts == stepPts && g_sigRoundPts == roundPoints && g_sigMinPts == minPts && g_sigMaxPts == maxPts && g_sigRight == rightTime && g_sigW == w && g_sigH == h && g_sigHour == hourNow && g_sigTime == haveTime && (!haveTime || (g_sigTL == tL && g_sigTR == tR && g_sigOff == off && g_sigStepH == stepH && g_sigFirstB == firstB))) return 1; ObjectsDeleteAll(0, PREFIX); //--- Draw the opaque bottom strip (safe now because padding guarantees no candles overlap it) if(haveTime && useStrip) CreatePixelBackground(PREFIX + "STRIP", 0, h - stripH, w, stripH, (color)ChartGetInteger(0, CHART_COLOR_BACKGROUND)); if(InpShowScaleBorders) { int borderH = (haveTime && useStrip) ? h - stripH : h; CreatePixelBackground(PREFIX + "BR_", g_borderX, 0, 1, borderH, InpScaleBorderColor); if(haveTime && useStrip) CreatePixelBackground(PREFIX + "BB_", 0, h - stripH - 1, w, 1, InpScaleBorderColor); } int keepAboveY = (haveTime && useStrip) ? h - stripH - 2 : labelY - 2; for(int i = start; i <= end; i++) { double price = (double)i * step; if(price < min || price > max) continue; int pxs = 0, pys = 0; ChartTimePriceToXY(0, 0, rightTime, price, pxs, pys); if(pys < 0 || pys >= keepAboveY) continue; string key = IntegerToString((long)MathRound(price / _Point)); string label = DoubleToString(price, _Digits); bool highlight = false; if(roundPoints > 0) { long levelPoints = (long)MathRound(price / _Point); highlight = (levelPoints % roundPoints == 0); } color textColor = highlight ? InpHighlightTextColor : InpTextColor; if(drawLines) { string lineName = PREFIX + "L_" + key; if(ObjectCreate(0, lineName, OBJ_TREND, 0, tLineLeft, price, tLineRight, price)) { color lineColor = highlight ? InpHighlightBackColor : InpPriceLineColor; ENUM_LINE_STYLE lineStyle = highlight ? InpThickLineStyle : InpNormalLineStyle; int lineWidth = highlight ? InpRoundLineWidth : 1; ObjectSetInteger(0, lineName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, lineName, OBJPROP_STYLE, lineStyle); ObjectSetInteger(0, lineName, OBJPROP_WIDTH, lineWidth); ObjectSetInteger(0, lineName, OBJPROP_RAY_LEFT, false); ObjectSetInteger(0, lineName, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, lineName, OBJPROP_BACK, true); ObjectSetInteger(0, lineName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); } } if(highlight && InpUseHighlightBackground) CreateHighlightBackground(key, rightTime, price, label); string textName = PREFIX + "T_" + key; if(ObjectCreate(0, textName, OBJ_TEXT, 0, rightTime, price)) { ObjectSetString(0, textName, OBJPROP_TEXT, label); ObjectSetString(0, textName, OBJPROP_FONT, InpFontName); ObjectSetInteger(0, textName, OBJPROP_FONTSIZE, InpFontSize); ObjectSetInteger(0, textName, OBJPROP_COLOR, textColor); ObjectSetInteger(0, textName, OBJPROP_ANCHOR, ANCHOR_RIGHT); ObjectSetInteger(0, textName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, textName, OBJPROP_HIDDEN, true); ObjectSetInteger(0, textName, OBJPROP_BACK, false); ObjectSetInteger(0, textName, OBJPROP_ZORDER, 2); } } if(haveTime) BuildTimeScale(w, h, off, stepH, firstB, lR); if(haveTime && InpShowSessions) DrawSessionLines(w, h, tL, tR); ChartRedraw(); g_sigValid = true; g_sigStart = start; g_sigEnd = end; g_sigStepPts = stepPts; g_sigRoundPts = roundPoints; g_sigMinPts = minPts; g_sigMaxPts = maxPts; g_sigRight = rightTime; g_sigW = w; g_sigH = h; g_sigHour = hourNow; g_sigTime = haveTime; g_sigTL = tL; g_sigTR = tR; g_sigOff = off; g_sigStepH = stepH; g_sigFirstB = firstB; return 2; } //+------------------------------------------------------------------+ //| Compute local-time scale state (exact whole-hour boundaries) | //+------------------------------------------------------------------+ bool ComputeTimeScale(int width, int height, datetime &tLeft, datetime &tRight, long &off, long &stepH, long &firstB, long &lRight) { int sub = 0; double ptmp = 0.0; tLeft = 0; tRight = 0; ChartXYToTimePrice(0, 2, height / 2, sub, tLeft, ptmp); ChartXYToTimePrice(0, width - 2, height / 2, sub, tRight, ptmp); if(tLeft == 0 || tRight == 0 || tRight <= tLeft) { long first = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); tLeft = iTime(_Symbol, _Period, (int)first); tRight = iTime(_Symbol, _Period, 0); if(tLeft == 0 || tRight == 0 || tRight <= tLeft) return false; } off = (long)TimeLocal() - (long)TimeCurrent(); long lLeft = (long)tLeft + off; lRight = (long)tRight + off; long visibleHours = (lRight - lLeft) / 3600; if(visibleHours <= 0) return false; const long stepsH[8] = {1, 2, 3, 6, 12, 24, 48, 168}; stepH = stepsH[7]; if(InpForceEveryHour) stepH = 1; else { for(int s = 0; s < 8; s++) { if(visibleHours / stepsH[s] <= InpMaxTimeLabels) { stepH = stepsH[s]; break; } } } while(visibleHours / stepH > 500) stepH *= 2; long hourIdx = lLeft / 3600; long firstHourIdx = hourIdx - (hourIdx % stepH); firstB = firstHourIdx * 3600; return true; } //+------------------------------------------------------------------+ //| Bottom strip / label geometry | //+------------------------------------------------------------------+ void TimeScaleGeometry(int height, int &stripH, int &labelY, bool &useStrip) { int tw = 0, th = 0; TextBoxSize("00", tw, th); stripH = th + 8; useStrip = (InpBottomMargin && InpHideDefaultTimeScale); labelY = useStrip ? height - stripH + 4 : height - th - 3; if(labelY < 0) labelY = 0; } //+------------------------------------------------------------------+ //| Build local-time vertical lines + 24h hour labels | //+------------------------------------------------------------------+ void BuildTimeScale(int width, int height, long off, long stepH, long firstB, long lRight) { long stepSec = stepH * 3600; int pad = MathMax(0, InpLabelPadding); int stripH = 0; int labelY = 0; bool useStrip = false; TimeScaleGeometry(height, stripH, labelY, useStrip); double anyPrice = ChartGetDouble(0, CHART_PRICE_MIN); for(long l = firstB; l <= lRight; l += stepSec) { datetime ts = (datetime)(l - off); int hour = (int)((l % 86400) / 3600); bool isDay = (hour == 0); int x = 0; int y = 0; if(!ChartTimePriceToXY(0, 0, ts, anyPrice, x, y)) continue; if(x < -30 || x >= g_plotRightX) continue; color lineColor = isDay ? InpDayLineColor : InpHourLineColor; ENUM_LINE_STYLE lineStyle = isDay ? InpThickLineStyle : InpNormalLineStyle; int lineWidth = isDay ? InpDayLineWidth : 1; string lineName = PREFIX + "V_" + IntegerToString(l); if(ObjectCreate(0, lineName, OBJ_VLINE, 0, ts, 0)) { ObjectSetInteger(0, lineName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, lineName, OBJPROP_STYLE, lineStyle); ObjectSetInteger(0, lineName, OBJPROP_WIDTH, lineWidth); ObjectSetInteger(0, lineName, OBJPROP_BACK, true); ObjectSetInteger(0, lineName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); } string text; if(isDay && InpShowDateOnNewDay) text = MidnightLabel(l); else if(InpTimeShowMinutes) text = StringFormat("%02d:00", hour); else text = StringFormat("%02d", hour); color txtColor = isDay ? InpDayTextColor : InpHourTextColor; int tw = 0; int th = 0; TextBoxSize(text, tw, th); int xLab = x - tw / 2; if(xLab < 0) xLab = 0; if(xLab + tw > width) xLab = width - tw; if(xLab < 0) xLab = 0; if(isDay && InpUseDayBackground) { int bgW = tw + 2 * pad; int bgH = th + 2 * pad; int bgX = xLab - pad; int bgY = labelY - pad; if(useStrip && bgY < height - stripH) bgY = height - stripH; if(bgX + bgW > width) bgX = width - bgW; if(bgX < 0) bgX = 0; if(bgY < 0) bgY = 0; CreatePixelBackground(PREFIX + "DB_" + IntegerToString(l), bgX, bgY, bgW, bgH, InpDayBackColor); } string tlName = PREFIX + "TL_" + IntegerToString(l); if(ObjectCreate(0, tlName, OBJ_LABEL, 0, 0, 0)) { ObjectSetInteger(0, tlName, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, tlName, OBJPROP_XDISTANCE, xLab); ObjectSetInteger(0, tlName, OBJPROP_YDISTANCE, labelY); ObjectSetString(0, tlName, OBJPROP_TEXT, text); ObjectSetString(0, tlName, OBJPROP_FONT, InpFontName); ObjectSetInteger(0, tlName, OBJPROP_FONTSIZE, InpFontSize); ObjectSetInteger(0, tlName, OBJPROP_COLOR, txtColor); ObjectSetInteger(0, tlName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, tlName, OBJPROP_HIDDEN, true); ObjectSetInteger(0, tlName, OBJPROP_BACK, false); ObjectSetInteger(0, tlName, OBJPROP_ZORDER, 2); } } } //+------------------------------------------------------------------+ //| Draw Session Open Lines (SYD, TYO, LDN, NYC) with DST | //+------------------------------------------------------------------+ void DrawSessionLines(int width, int height, datetime tLeft, datetime tRight) { if(!InpShowSessions) return; long serverOffset = (long)TimeCurrent() - (long)TimeGMT(); MqlDateTime dtLeft; TimeToStruct(tLeft - serverOffset, dtLeft); dtLeft.hour = 0; dtLeft.min = 0; dtLeft.sec = 0; datetime utcDayStart = StructToTime(dtLeft); double anyPrice = ChartGetDouble(0, CHART_PRICE_MIN); for(long d = utcDayStart; d <= tRight - serverOffset + 86400; d += 86400) { int sydHour = (IsDST_Sydney(d) ? 21 : 22); int tyoHour = 0; int ldnHour = (IsDST_London(d) ? 7 : 8); int nycHour = (IsDST_NYC(d) ? 12 : 13); int hours[4] = {sydHour, tyoHour, ldnHour, nycHour}; string names[4] = {"SYD", "TYO", "LDN", "NYC"}; for(int i = 0; i < 4; i++) { datetime utcOpen = d + hours[i] * 3600; datetime serverOpen = utcOpen + serverOffset; if(serverOpen < tLeft || serverOpen > tRight) continue; int x = 0, y = 0; if(!ChartTimePriceToXY(0, 0, serverOpen, anyPrice, x, y)) continue; if(x < -30 || x >= g_plotRightX) continue; string lineName = PREFIX + "S_" + names[i] + "_" + IntegerToString(d); if(ObjectCreate(0, lineName, OBJ_VLINE, 0, serverOpen, 0)) { ObjectSetInteger(0, lineName, OBJPROP_COLOR, InpSessionColor); ObjectSetInteger(0, lineName, OBJPROP_STYLE, InpSessionLineStyle); ObjectSetInteger(0, lineName, OBJPROP_WIDTH, InpSessionLineWidth); ObjectSetInteger(0, lineName, OBJPROP_BACK, true); ObjectSetInteger(0, lineName, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, lineName, OBJPROP_HIDDEN, true); } } } } //+------------------------------------------------------------------+ //| DST Approximations (Month-based, accurate enough for charts) | //+------------------------------------------------------------------+ bool IsDST_Sydney(datetime utcTime) { MqlDateTime dt; TimeToStruct(utcTime, dt); return (dt.mon >= 10 || dt.mon <= 3); } bool IsDST_London(datetime utcTime) { MqlDateTime dt; TimeToStruct(utcTime, dt); return (dt.mon >= 4 && dt.mon <= 10); } bool IsDST_NYC(datetime utcTime) { MqlDateTime dt; TimeToStruct(utcTime, dt); return (dt.mon >= 3 && dt.mon <= 11); } //+------------------------------------------------------------------+ //| Midnight label: English day name, optional date | //+------------------------------------------------------------------+ string MidnightLabel(const long localSeconds) { MqlDateTime st; TimeToStruct((datetime)localSeconds, st); string dayName = ""; if(st.day_of_week >= 0 && st.day_of_week <= 6) dayName = DAY_NAMES[st.day_of_week]; string dateStr = StringFormat("%02d.%02d", st.day, st.mon); if(InpMidnightLabelMode == 1) return dayName + " " + dateStr; if(InpMidnightLabelMode == 2) return dateStr; return dayName; } //+------------------------------------------------------------------+ //| Estimate rendered text size in pixels for current font settings | //+------------------------------------------------------------------+ void TextBoxSize(const string text, int &w, int &h) { double emPx = (double)MathMax(1, InpFontSize) * 96.0 / 72.0; w = (int)MathCeil(StringLen(text) * emPx * 0.62); h = (int)MathCeil(emPx * 1.15); if(w < 4) w = 4; if(h < 4) h = 4; } //+------------------------------------------------------------------+ //| Generic pixel-based background rectangle | //+------------------------------------------------------------------+ void CreatePixelBackground(const string name, const int x, const int y, const int w, const int h, const color bg) { if(ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0)) { ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(0, name, OBJPROP_XSIZE, w); ObjectSetInteger(0, name, OBJPROP_YSIZE, h); ObjectSetInteger(0, name, OBJPROP_BGCOLOR, bg); ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, name, OBJPROP_COLOR, bg); ObjectSetInteger(0, name, OBJPROP_BACK, false); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); ObjectSetInteger(0, name, OBJPROP_ZORDER, 1); } } //+------------------------------------------------------------------+ //| Tight pink background behind highlighted price label | //+------------------------------------------------------------------+ void CreateHighlightBackground(const string key, const datetime rightTime, const double price, const string label) { int x = 0; int y = 0; if(!ChartTimePriceToXY(0, 0, rightTime, price, x, y)) return; int chartWidth = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int chartHeight = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(y < 0 || (chartHeight > 0 && y > chartHeight)) return; int tw = 0; int th = 0; TextBoxSize(label, tw, th); int pad = MathMax(0, InpLabelPadding); int bgWidth = tw + 2 * pad; int bgHeight = th + 2 * pad; int xRight = chartWidth - 1; int xLeft = xRight - bgWidth; int yTop = y - th / 2 - pad; if(xLeft < 0) xLeft = 0; CreatePixelBackground(PREFIX + "B_" + key, xLeft, yTop, bgWidth, bgHeight, InpHighlightBackColor); } //+------------------------------------------------------------------+ //| Get time at the exact right pixel edge of the chart | //+------------------------------------------------------------------+ datetime RightEdgeTime() { datetime t = 0; int width = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int height = (int)ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS); if(width > 0 && height > 0) { datetime tmpTime = 0; double tmpPrice = 0.0; int sub_window = 0; if(ChartXYToTimePrice(0, width - 1, height / 2, sub_window, tmpTime, tmpPrice)) t = tmpTime; } if(t == 0) { long first = ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); long visible = ChartGetInteger(0, CHART_VISIBLE_BARS); long rightIndex = 0; if(first > 0 && visible > 0) { rightIndex = first - visible + 1; if(rightIndex < 0) rightIndex = 0; } t = iTime(_Symbol, _Period, (int)rightIndex); if(t == 0) t = TimeCurrent(); } int offset = InpRightOffsetBars; if(offset < 0) offset = 0; if(offset > 0) t = t - (datetime)(PeriodSeconds(_Period) * offset); return t; } //+------------------------------------------------------------------+ //| Choose volatility-based round step | //+------------------------------------------------------------------+ double ChooseStepPrice(double min, double max, double atrValue) { double range = MathMax(max - min, 0.0); double volatilityStep = atrValue * InpAtrMultiplier; double densityStep = range / (double)MathMax(1, InpMaxLabels); double rawStep = MathMax(volatilityStep, densityStep); if(rawStep <= 0.0) rawStep = g_pip; double rawPips = rawStep / g_pip; double nicePips = NiceCeil(rawPips); if(nicePips < InpMinStepPips) nicePips = InpMinStepPips; if(nicePips > InpMaxStepPips) nicePips = InpMaxStepPips; double step = nicePips * g_pip; double maxCount = MathMax(2.0, (double)InpMaxLabels); if(range > 0.0 && step > 0.0) { while(range / step > maxCount) { double requiredPips = (range / maxCount) / g_pip; double nextPips = NiceCeil(requiredPips); double nextStep = nextPips * g_pip; if(nextStep <= step) step *= 2.0; else step = nextStep; } } return step; } //+------------------------------------------------------------------+ //| Round up to 1 / 2 / 5 style numbers | //+------------------------------------------------------------------+ double NiceCeil(double value) { if(value <= 0.0) return 1.0; double exponent = MathFloor(MathLog(value) / MathLog(10.0)); double base = MathPow(10.0, exponent); double fraction = value / base; if(fraction <= 1.0) return base; if(fraction <= 2.0) return 2.0 * base; if(fraction <= 5.0) return 5.0 * base; return 10.0 * base; } //+------------------------------------------------------------------+ //| Pip size | //+------------------------------------------------------------------+ double PipSize() { if(InpCustomPipSize > 0.0) return InpCustomPipSize; if(_Digits == 3 || _Digits == 5) return 10.0 * _Point; return _Point; } //+------------------------------------------------------------------+