141 строка
5,6 КиБ
MQL5
//+------------------------------------------------------------------+
| |||
//| DinexSpace | Day/Week Separators |
| |||
//| Vertical lines at the start of each day, week and month, with |
| |||
//| optional day-of-week labels. The day can start at the broker's |
| |||
//| midnight or at a custom server hour (for example a 17:00 New |
| |||
//| York forex day). It only marks time; it gives no signals. |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "(c) 2026 DinexSpace"
| |||
#property version "1.00"
| |||
#property description "Vertical separators for days, weeks and months, with day-of-week labels."
| |||
#property description "Day start at server midnight or a custom server hour. Marks time only, no signals."
| |||
#property strict
| |||
#property indicator_chart_window
| |||
#property indicator_buffers 0
| |||
#property indicator_plots 0
| |||
| |||
input group "Separators"
| |||
input bool InpDay = true; // Day
| |||
input color InpColDay = C'70,70,70'; // Day colour
| |||
input ENUM_LINE_STYLE InpStyDay = STYLE_DOT; // Day style
| |||
input bool InpWeek = true; // Week
| |||
input color InpColWeek = C'0,140,140'; // Week colour
| |||
input ENUM_LINE_STYLE InpStyWeek = STYLE_DASH; // Week style
| |||
input bool InpMonth = true; // Month
| |||
input color InpColMon = clrSpringGreen; // Month colour
| |||
input ENUM_LINE_STYLE InpStyMon = STYLE_SOLID; // Month style
| |||
| |||
input group "Day start"
| |||
input int InpDayHour = 0; // Day starts at server hour (0 = server midnight)
| |||
| |||
input group "Display"
| |||
input bool InpLabels = true; // Day-of-week labels
| |||
input color InpColLbl = clrDimGray; // Label colour
| |||
input int InpKeep = 200; // Separators to keep
| |||
input int InpMaxBars = 5000; // Bars to scan
| |||
| |||
const string PFX = "DNXSEP_";
| |||
string g_names[]; // separator object names, oldest first
| |||
| |||
string Dow(int d)
| |||
{
| |||
string n[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
| |||
return (d >= 0 && d < 7) ? n[d] : "";
| |||
}
| |||
| |||
// Trading day key of a server time, with the day starting at InpDayHour.
| |||
datetime DayKey(datetime t)
| |||
{
| |||
datetime s = t - InpDayHour * 3600;
| |||
return s - s % 86400;
| |||
}
| |||
| |||
void AddSeparator(datetime t, double y, color clr, ENUM_LINE_STYLE st, string text)
| |||
{
| |||
string nm = PFX + IntegerToString((long)t);
| |||
if(ObjectFind(0, nm) < 0)
| |||
ObjectCreate(0, nm, OBJ_VLINE, 0, t, 0);
| |||
ObjectSetInteger(0, nm, OBJPROP_COLOR, clr);
| |||
ObjectSetInteger(0, nm, OBJPROP_STYLE, st);
| |||
ObjectSetInteger(0, nm, OBJPROP_BACK, true);
| |||
ObjectSetInteger(0, nm, OBJPROP_SELECTABLE, false);
| |||
ObjectSetInteger(0, nm, OBJPROP_HIDDEN, true);
| |||
if(InpLabels)
| |||
{
| |||
string lb = nm + "_lbl";
| |||
if(ObjectFind(0, lb) < 0)
| |||
ObjectCreate(0, lb, OBJ_TEXT, 0, t, y);
| |||
ObjectSetInteger(0, lb, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER);
| |||
ObjectSetInteger(0, lb, OBJPROP_FONTSIZE, 7);
| |||
ObjectSetInteger(0, lb, OBJPROP_COLOR, InpColLbl);
| |||
ObjectSetInteger(0, lb, OBJPROP_SELECTABLE, false);
| |||
ObjectSetInteger(0, lb, OBJPROP_HIDDEN, true);
| |||
ObjectMove(0, lb, 0, t, y);
| |||
ObjectSetString(0, lb, OBJPROP_TEXT, text == "" ? " " : text); // an empty label text shows "Label"
| |||
}
| |||
int n = ArraySize(g_names);
| |||
if(n > 0 && g_names[n - 1] == nm)
| |||
return; // same bar processed again
| |||
ArrayResize(g_names, n + 1);
| |||
g_names[n] = nm;
| |||
if(n + 1 > InpKeep)
| |||
{
| |||
ObjectDelete(0, g_names[0]);
| |||
ObjectDelete(0, g_names[0] + "_lbl");
| |||
ArrayRemove(g_names, 0, 1);
| |||
}
| |||
}
| |||
| |||
int OnInit()
| |||
{
| |||
if(InpDayHour < 0 || InpDayHour > 23 || InpKeep < 10 || InpMaxBars < 100)
| |||
return INIT_PARAMETERS_INCORRECT;
| |||
ArrayResize(g_names, 0);
| |||
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 < 2)
| |||
return 0;
| |||
if(PeriodSeconds(_Period) >= 86400)
| |||
return rates_total; // separators only make sense on intraday charts
| |||
ArraySetAsSeries(time, false);
| |||
ArraySetAsSeries(low, false);
| |||
int from;
| |||
if(prev_calculated == 0)
| |||
{
| |||
ObjectsDeleteAll(0, PFX);
| |||
ArrayResize(g_names, 0);
| |||
from = MathMax(1, rates_total - InpMaxBars);
| |||
}
| |||
else
| |||
from = MathMax(1, prev_calculated - 1);
| |||
| |||
for(int i = from; i < rates_total; i++)
| |||
{
| |||
MqlDateTime a, b;
| |||
TimeToStruct(DayKey(time[i]), a);
| |||
TimeToStruct(DayKey(time[i - 1]), b);
| |||
bool newDay = DayKey(time[i]) != DayKey(time[i - 1]);
| |||
bool newMonth = newDay && a.mon != b.mon;
| |||
// a new week starts when the trading day's weekday goes backwards (e.g. Fri -> Mon) or 7+ days pass
| |||
bool newWeek = newDay && (a.day_of_week < b.day_of_week || DayKey(time[i]) - DayKey(time[i - 1]) >= 7 * 86400);
| |||
if(InpMonth && newMonth)
| |||
AddSeparator(time[i], low[i], InpColMon, InpStyMon, Dow(a.day_of_week));
| |||
else if(InpWeek && newWeek)
| |||
AddSeparator(time[i], low[i], InpColWeek, InpStyWeek, Dow(a.day_of_week));
| |||
else if(InpDay && newDay)
| |||
AddSeparator(time[i], low[i], InpColDay, InpStyDay, Dow(a.day_of_week));
| |||
}
| |||
return rates_total;
| |||
}
| |||
//+------------------------------------------------------------------+
|