//+------------------------------------------------------------------+ //| DinexSpace | Round Number Levels | //| Horizontal lines at round prices around the current price, with | //| stronger "major" lines every N steps. The step is chosen from | //| the price size or set by hand. Draws levels only, no signals. | //+------------------------------------------------------------------+ #property copyright "(c) 2026 DinexSpace" #property version "1.00" #property description "Round-number price levels around the current price, with major lines every N steps." #property description "Automatic or manual step. Draws levels only, no signals." #property strict #property indicator_chart_window #property indicator_buffers 0 #property indicator_plots 0 input group "Step" input bool InpAuto = true; // Automatic minor step input double InpStep = 10.0; // Manual minor step (price) input int InpMajorK = 5; // Major line every N minor steps input int InpCount = 6; // Lines above and below price input group "Display" input color InpColMinor = C'70,70,70'; // Minor lines input color InpColMajor = clrSpringGreen; // Major lines input ENUM_LINE_STYLE InpMinorSty = STYLE_DOT; // Minor line style input bool InpLabels = true; // Price labels input bool InpLblMajor = true; // Labels on major lines only const string PFX = "DNXRN_"; double g_lastBase = -1, g_lastStep = -1; // About 1/100 of price rounded to 1, 2 or 5 x a power of ten (gold ~4000 -> 50, EURUSD ~1.10 -> 0.01). double AutoStep(double px) { double raw = px / 100.0; double p10 = MathPow(10, MathFloor(MathLog10(raw))); double m = raw / p10; double nice = (m < 1.5) ? 1 : (m < 3.5) ? 2 : (m < 7.5) ? 5 : 10; return nice * p10; } int OnInit() { if(InpMajorK < 1 || InpCount < 1 || InpCount > 50 || (!InpAuto && InpStep <= 0)) return INIT_PARAMETERS_INCORRECT; g_lastBase = -1; g_lastStep = -1; return INIT_SUCCEEDED; } void OnDeinit(const int reason) { ObjectsDeleteAll(0, PFX); } 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[]) { if(rates_total < 1) return 0; ArraySetAsSeries(time, true); ArraySetAsSeries(close, true); double px = close[0]; if(px <= 0) return rates_total; double step = InpAuto ? AutoStep(px) : InpStep; if(step <= 0) return rates_total; double base = MathFloor(px / step) * step; datetime tLbl = time[0] + 3 * PeriodSeconds(_Period); // Rebuild only when the set of levels changes; otherwise just keep the labels next to the last bar. bool rebuild = (base != g_lastBase || step != g_lastStep || prev_calculated == 0); if(rebuild) { ObjectsDeleteAll(0, PFX); g_lastBase = base; g_lastStep = step; } int dg = (int)MathMax(0, MathMin(_Digits, MathCeil(-MathLog10(step)))); for(int k = -InpCount + 1; k <= InpCount; k++) { double y = base + k * step; if(y <= 0) continue; long idx = (long)MathRound(y / step); bool major = (idx % InpMajorK) == 0; string nm = PFX + IntegerToString(k + InpCount); if(rebuild) { ObjectCreate(0, nm, OBJ_HLINE, 0, 0, y); ObjectSetInteger(0, nm, OBJPROP_COLOR, major ? InpColMajor : InpColMinor); ObjectSetInteger(0, nm, OBJPROP_STYLE, major ? STYLE_SOLID : InpMinorSty); ObjectSetInteger(0, nm, OBJPROP_WIDTH, major ? 2 : 1); ObjectSetInteger(0, nm, OBJPROP_BACK, true); ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true); } if(InpLabels && (major || !InpLblMajor)) { string lb = nm + "_lbl"; if(ObjectFind(0, lb) < 0) { ObjectCreate(0, lb, OBJ_TEXT, 0, tLbl, y); ObjectSetInteger(0, lb, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER); ObjectSetInteger(0, lb, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, lb, OBJPROP_COLOR, major ? InpColMajor : InpColMinor); ObjectSetInteger(0, lb, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, lb, OBJPROP_HIDDEN, true); ObjectSetString(0, lb, OBJPROP_TEXT, DoubleToString(y, dg)); } ObjectMove(0, lb, 0, tLbl, y); } } return rates_total; } //+------------------------------------------------------------------+