dinexspace-free-indicators/Indicators/DinexRoundLevels.mq5
119 lines
4.7 KiB
MQL5
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+