//+------------------------------------------------------------------+ //| ShowWeeks.mq5 | //| Copyright (c) 2009-2024, Marketeer | //| https://www.mql5.com/en/users/marketeer | //+------------------------------------------------------------------+ #property copyright "Copyright (c) 2009-2024, Marketeer" #property link "https://www.mql5.com/en/users/marketeer" #property description "Tracks current mouse position to show day of week, week number, day of year, or bar index under the cursor." #property description "Two selected parameters are combined in a single value as a whole part and a fractional part in the Data Window." #property version "1.0" #property indicator_chart_window 0 #property indicator_buffers 1 #property indicator_plots 1 #property indicator_type1 DRAW_NONE #include #include #define PUSH(A,V) (A[ArrayResize(A, ArrayRange(A, 0) + 1, ArrayRange(A, 0) * 2) - 1] = V) #define OBJ_PREFIX "WDi_" #define DAY (60 * 60 * 24) //+------------------------------------------------------------------+ //| I N P U T S | //+------------------------------------------------------------------+ enum InfoType { None = 1, DoW = 10, // Day Of Week Week = 100, // Week Of Year DoY = 1000, // Day Of Year Bar = 1000000000, // Bar Index }; input group "Display In Data Window via Buffer" input InfoType WholePart = Week; input InfoType FractionalPart = DoY; enum Alignment { Top, Middle, Bottom, }; input group "Display Labels on Chart" input bool ShowLabels = false; input string FontName = "Segoe UI"; input int FontSize = 25; input color FontColor = clrNONE; input int Padding = 5; // Padding (% of chart height, for top/bottom alignment) input Alignment AlignTo = Top; input int RotationAngle = 0; // RotationAngle (for middle alignment) //+------------------------------------------------------------------+ //| G L O B A L S | //+------------------------------------------------------------------+ double Buffer1[]; int objCache[]; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { if(_Period > PERIOD_D1) return INIT_PARAMETERS_INCORRECT; SetIndexBuffer(0, Buffer1, INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS, (int)MathLog10(FractionalPart)); ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); // does not work in tester if(WholePart == FractionalPart && WholePart != None) Alert("The same info is selected to display as whole part and fractional part of indicator values!"); if(ShowLabels && _Period < PERIOD_D1) AdjustLabels(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Custom indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int) { ObjectsDeleteAll(0, OBJ_PREFIX); ChartRedraw(); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ 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[]) { int limit = 0; if(prev_calculated <= 0) ArrayInitialize(Buffer1, EMPTY_VALUE); else limit = prev_calculated - 1; for(int i = limit; i < rates_total && !IsStopped(); i++) { if(WholePart * FractionalPart > 1) { const int d = TimeDayOfYear(time[i]); // cache datetime object Buffer1[i] = GetPart(WholePart, i) + GetPart(FractionalPart, i) / (double)FractionalPart; } else { Buffer1[i] = DayOfWeek(time[i]); } } return rates_total; } //+------------------------------------------------------------------+ //| Event handler function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id == CHARTEVENT_MOUSE_MOVE) { int window; datetime time; double price; if(ChartXYToTimePrice(0, (int)lparam, (int)dparam, window, time, price)) { const int b = iBarShift(_Symbol, _Period, time); if(b > -1) time = iTime(_Symbol, _Period, b); else time = (time + PeriodSeconds() / 2) / PeriodSeconds() * PeriodSeconds(); static int day = -1; int now = DayOfWeek(time); // Comment((string)time); if(now != day) { day = now; const bool valid = WholePart * FractionalPart > 1; const string legend = StringFormat("%s%s%s%s", valid ? " | " : "", WholePart > 1 ? EnumToString(WholePart) : "", FractionalPart > 1 ? "." + EnumToString(FractionalPart) : "", valid ? ": ->" : ""); PlotIndexSetString(0, PLOT_LABEL, StringFormat("Day: %s%s", DayName(day), legend)); ChartRedraw(0); } } } else if(id == CHARTEVENT_CHART_CHANGE) { if(ShowLabels && _Period < PERIOD_D1) AdjustLabels(); } } //+------------------------------------------------------------------+ //| Main batch processing of labels | //+------------------------------------------------------------------+ void AdjustLabels() { const int left = (int)ChartGetInteger(0, CHART_FIRST_VISIBLE_BAR); const int width = (int)ChartGetInteger(0, CHART_VISIBLE_BARS); datetime overnights[][2]; // [prev day end] [next day start] int prevday = -1; for(int i = 0; i < width; i++) { if(left - i < 0) continue; const int day = DayOfWeek(iTime(_Symbol, _Period, left - i)); if(day != prevday || left - i == 0) { datetime range[1][2] = {{ (left - i > 0) ? iTime(_Symbol, _Period, left - i + 1) : iTime(_Symbol, _Period, 0) / DAY * DAY - PeriodSeconds(), (left - i > 0) ? iTime(_Symbol, _Period, left - i) : iTime(_Symbol, _Period, 0) / DAY * DAY + DAY }}; ArrayInsert(overnights, range, ArrayRange(overnights, 0)); prevday = day; } } const double add = Padding / 100.0 * (ChartGetDouble(0, CHART_PRICE_MAX) - ChartGetDouble(0, CHART_PRICE_MIN)); const double price = AlignTo == Top ? ChartGetDouble(0, CHART_PRICE_MAX) - add : (AlignTo == Bottom ? ChartGetDouble(0, CHART_PRICE_MIN) + add : (ChartGetDouble(0, CHART_PRICE_MAX) + ChartGetDouble(0, CHART_PRICE_MIN)) / 2); const int realFontSize = (int)(FontSize / (6 - ChartGetInteger(0, CHART_SCALE))); for(int i = 1; i < ArrayRange(overnights, 0); i++) { CreateLabel(overnights[i - 1][1], overnights[i][1] > TimeCurrent() ? overnights[i][1] : overnights[i][0] + PeriodSeconds(), price, realFontSize); } CleanUpLabels(iTime(_Symbol, _Period, left), iTime(_Symbol, _Period, left - width), price, realFontSize); ChartRedraw(); } //+------------------------------------------------------------------+ //| Create or adjust a single label | //+------------------------------------------------------------------+ void CreateLabel(const datetime left, const datetime right, const double price, const int realFontSize) { if(right - left < DAY / 2) return; iClose(_Symbol, _Period, 0); const int d = TimeDayOfYear(left); const string name = OBJ_PREFIX + (string)d; ObjectCreate(0, name, OBJ_TEXT, 0, (left + right) / 2, price); ObjectSetInteger(0, name, OBJPROP_ANCHOR, AlignTo == Top ? ANCHOR_UPPER : (AlignTo == Bottom ? ANCHOR_LOWER : ANCHOR_CENTER)); ObjectSetString(0, name, OBJPROP_TEXT, DayName(DayOfWeek(left)) + (WholePart == Week || FractionalPart == Week ? "|" + (string)WeekNumber() : "") + (WholePart == DoY || FractionalPart == DoY ? "|" + (string)d : "")); ObjectSetString(0, name, OBJPROP_FONT, FontName); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, realFontSize); ObjectSetInteger(0, name, OBJPROP_COLOR, FontColor != clrNONE ? FontColor : ~ChartGetInteger(0, CHART_COLOR_BACKGROUND)); ObjectSetInteger(0, name, OBJPROP_BACK, true); if(AlignTo == Middle) ObjectSetDouble(0, name, OBJPROP_ANGLE, RotationAngle); PUSH(objCache, d); } //+------------------------------------------------------------------+ //| Remove objects outside of visible part of the chart | //+------------------------------------------------------------------+ void CleanUpLabels(const datetime left, const datetime right, const double price, const int realFontSize) { for(int i = 0; i < ArraySize(objCache); i++) { const string name = OBJ_PREFIX + (string)objCache[i]; const datetime t = (datetime)ObjectGetInteger(0, name, OBJPROP_TIME, 0); if(t != 0 && (t < left || t > (right ? right : TimeCurrent() + DAY * 7))) { ObjectDelete(0, name); objCache[i] = -1; } else { ObjectSetDouble(0, name, OBJPROP_PRICE, price); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, realFontSize); } } ArrayPurge(objCache, -1); } //+------------------------------------------------------------------+ //| Helper functions | //+------------------------------------------------------------------+ double GetPart(const InfoType part, const int i) { switch(part) { case DoW: return _TimeDayOfWeek(); case DoY: return _TimeDayOfYear(); case Week: return WeekNumber(); case Bar: return i; } return 0; } string DayName(const int DayNo) { const static string Days[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; return Days[DayNo % 7]; } int DayOfWeek(const datetime time) { return (int)((time / 86400) + 4) % 7; } int WeekNumber(/*const datetime dt*/) // prerequisite: parameter is cached in _macros { const datetime d1 = StringToTime(((string)_TimeYear(/*dt*/) + ".01.01 00:00")); return (_TimeDayOfYear() + DayOfWeek(d1)) / 7 + 1; } //+------------------------------------------------------------------+