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;
| |||
}
| |||
//+------------------------------------------------------------------+
|