//+------------------------------------------------------------------+ //| DigitalRogue | //| Algorithmic Trading Systems for MetaTrader 5 | //| | //| Previous Day High/Low + Equilibrium + Discount/Premium + Opens | //| Historical daily segments + EA-readable buffers | //| Trigger lines now stay on their relevant day | //| | //| Copyright 2026, DigitalRogue Trading | //| All rights reserved. | //| https://www.mql5.com/en/users/digitalrogue | //+------------------------------------------------------------------+ #property strict #property indicator_chart_window #property indicator_plots 9 #property indicator_buffers 9 #property indicator_type1 DRAW_NONE #property indicator_type2 DRAW_NONE #property indicator_type3 DRAW_NONE #property indicator_type4 DRAW_NONE #property indicator_type5 DRAW_NONE #property indicator_type6 DRAW_NONE #property indicator_type7 DRAW_NONE #property indicator_type8 DRAW_NONE #property indicator_type9 DRAW_NONE #property indicator_label1 "TriggerLong" #property indicator_label2 "TriggerShort" #property indicator_label3 "PDH" #property indicator_label4 "PDL" #property indicator_label5 "Equilibrium" #property indicator_label6 "Discount" #property indicator_label7 "Premium" #property indicator_label8 "DailyOpen" #property indicator_label9 "WeeklyOpen" double TriggerLongBuffer[]; double TriggerShortBuffer[]; double PDHBuffer[]; double PDLBuffer[]; double MidBuffer[]; double DiscountBuffer[]; double PremiumBuffer[]; double DOpenBuffer[]; double WOpenBuffer[]; // --- cached current-day values for EA reading double g_pdh = EMPTY_VALUE; double g_pdl = EMPTY_VALUE; double g_mid = EMPTY_VALUE; double g_discount = EMPTY_VALUE; double g_premium = EMPTY_VALUE; double g_dopen = EMPTY_VALUE; double g_wopen = EMPTY_VALUE; double g_trigger_long = EMPTY_VALUE; double g_trigger_short = EMPTY_VALUE; // --- Visual inputs input color PDH_Color = clrRed; input color PDL_Color = clrDodgerBlue; input int LineWidth = 2; input bool ShowLabels = false; input bool ShowMidLine = true; input bool ShowQLines = true; input int Q_Percent = 25; input color MidQ_Color = clrSilver; input int MidQ_Width = 1; input ENUM_LINE_STYLE MidQ_Style = STYLE_DASH; input bool ShowTriggerLines = true; input color Trigger_Color = clrYellow; input int Trigger_Width = 1; input ENUM_LINE_STYLE Trigger_Style = STYLE_DASH; input bool UseEquilibriumLabel = true; // --- Open lines input bool ShowDailyOpenLine = true; input bool ShowWeeklyOpenLine = false; input color Open_Color = clrSilver; input int Open_Width = 1; input ENUM_LINE_STYLE Open_Style = STYLE_DOT; // --- Alerts input bool EnableAlerts = true; input bool AlertPDH_PDL = true; input bool AlertMid_Q = false; input bool AlertOpens = true; input bool AlertOnTouch = false; input int TouchTolerancePts = 5; input bool LimitAlertsToOnePerCandle = true; // --- History input int MaxHistoricalDays = 10; // --- current-day active names string curPDH_Line = ""; string curPDL_Line = ""; string curMID_Line = ""; string curQLO_Line = ""; string curQHI_Line = ""; string curDOPEN_Line = ""; string curWOPEN_Line = ""; string curTrigLong_Line = ""; string curTrigShort_Line = ""; string curPDH_Label = ""; string curPDL_Label = ""; string curMID_Label = ""; string curQLO_Label = ""; string curQHI_Label = ""; string curDOPEN_Label = ""; string curWOPEN_Label = ""; string curTrigLong_Label = ""; string curTrigShort_Label = ""; // --- State datetime last_day = 0; datetime lastAlertBarTime = 0; int lastSide_PDH = 0; int lastSide_PDL = 0; int lastSide_MID = 0; int lastSide_QLO = 0; int lastSide_QHI = 0; int lastSide_DOPEN = 0; int lastSide_WOPEN = 0; //+------------------------------------------------------------------+ int OnInit() { IndicatorSetString(INDICATOR_SHORTNAME, "Prev Day Levels History + Trigger Segments + EA Buffers"); SetIndexBuffer(0, TriggerLongBuffer, INDICATOR_DATA); SetIndexBuffer(1, TriggerShortBuffer, INDICATOR_DATA); SetIndexBuffer(2, PDHBuffer, INDICATOR_DATA); SetIndexBuffer(3, PDLBuffer, INDICATOR_DATA); SetIndexBuffer(4, MidBuffer, INDICATOR_DATA); SetIndexBuffer(5, DiscountBuffer, INDICATOR_DATA); SetIndexBuffer(6, PremiumBuffer, INDICATOR_DATA); SetIndexBuffer(7, DOpenBuffer, INDICATOR_DATA); SetIndexBuffer(8, WOpenBuffer, INDICATOR_DATA); ArraySetAsSeries(TriggerLongBuffer, true); ArraySetAsSeries(TriggerShortBuffer, true); ArraySetAsSeries(PDHBuffer, true); ArraySetAsSeries(PDLBuffer, true); ArraySetAsSeries(MidBuffer, true); ArraySetAsSeries(DiscountBuffer, true); ArraySetAsSeries(PremiumBuffer, true); ArraySetAsSeries(DOpenBuffer, true); ArraySetAsSeries(WOpenBuffer, true); for(int i=0; i<9; i++) PlotIndexSetDouble(i, PLOT_EMPTY_VALUE, EMPTY_VALUE); BuildHistoricalLevels(MaxHistoricalDays); datetime d0 = iTime(_Symbol, PERIOD_D1, 0); if(d0 != 0) last_day = d0; ResetAlertSides(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // keep history on chart by default } //+------------------------------------------------------------------+ 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 <= 0) return 0; datetime d0 = iTime(_Symbol, PERIOD_D1, 0); if(d0 == 0) return rates_total; if(d0 != last_day) { last_day = d0; BuildTodayLevels(); ResetAlertSides(); lastAlertBarTime = 0; CleanupOldHistory(MaxHistoricalDays); } else { if(ShowLabels) AlignLabels(); } // keep buffers valid on every call for EA access for(int i=0; i= 0; shift--) BuildDaySet(shift, shift == 0); } //+------------------------------------------------------------------+ void BuildTodayLevels() { BuildDaySet(0, true); } //+------------------------------------------------------------------+ void BuildDaySet(int shift, bool makeCurrent) { datetime dayStart = iTime(_Symbol, PERIOD_D1, shift); if(dayStart == 0) return; datetime nextDay = dayStart + 86400; double pdh = iHigh(_Symbol, PERIOD_D1, shift + 1); double pdl = iLow(_Symbol, PERIOD_D1, shift + 1); if(pdh == 0.0 || pdl == 0.0) return; double range = pdh - pdl; if(range <= 0.0) return; int q = (int)MathMax(1, MathMin(49, Q_Percent)); double mid = (pdh + pdl) * 0.5; double qlo = pdl + range * (q / 100.0); double qhi = pdl + range * (1.0 - (q / 100.0)); double trigger_long = (qlo + pdl) * 0.5; double trigger_short = (qhi + pdh) * 0.5; double dopen = EMPTY_VALUE; double wopen = EMPTY_VALUE; if(ShowDailyOpenLine) { double tmp = iOpen(_Symbol, PERIOD_D1, shift); if(tmp > 0.0) dopen = tmp; } if(ShowWeeklyOpenLine) { int wShift = iBarShift(_Symbol, PERIOD_W1, dayStart, false); if(wShift >= 0) { double tmp = iOpen(_Symbol, PERIOD_W1, wShift); if(tmp > 0.0) wopen = tmp; } } string tag = DayTag(dayStart); string pdhLine = "PDH_" + tag; string pdlLine = "PDL_" + tag; string midLine = "MID_" + tag; string qloLine = "QLO_" + tag; string qhiLine = "QHI_" + tag; string dopenLine = "DOPEN_" + tag; string wopenLine = "WOPEN_" + tag; string trigLongLine = "TRIGGER_LONG_" + tag; string trigShortLine = "TRIGGER_SHORT_" + tag; string pdhLabel = "LBL_PDH_" + tag; string pdlLabel = "LBL_PDL_" + tag; string midLabel = "LBL_MID_" + tag; string qloLabel = "LBL_QLO_" + tag; string qhiLabel = "LBL_QHI_" + tag; string dopenLabel = "LBL_DOPEN_" + tag; string wopenLabel = "LBL_WOPEN_" + tag; string trigLongLabel = "LBL_TRIGGER_LONG_" + tag; string trigShortLabel = "LBL_TRIGGER_SHORT_" + tag; // draw dated segments CreateSegment(pdhLine, dayStart, nextDay, pdh, PDH_Color, LineWidth, STYLE_SOLID); CreateSegment(pdlLine, dayStart, nextDay, pdl, PDL_Color, LineWidth, STYLE_SOLID); if(ShowMidLine) CreateSegment(midLine, dayStart, nextDay, mid, MidQ_Color, MidQ_Width, MidQ_Style); if(ShowQLines) { CreateSegment(qloLine, dayStart, nextDay, qlo, MidQ_Color, MidQ_Width, MidQ_Style); CreateSegment(qhiLine, dayStart, nextDay, qhi, MidQ_Color, MidQ_Width, MidQ_Style); } if(ShowTriggerLines) { CreateSegment(trigLongLine, dayStart, nextDay, trigger_long, Trigger_Color, Trigger_Width, Trigger_Style); CreateSegment(trigShortLine, dayStart, nextDay, trigger_short, Trigger_Color, Trigger_Width, Trigger_Style); } if(ShowDailyOpenLine && dopen != EMPTY_VALUE) CreateSegment(dopenLine, dayStart, nextDay, dopen, Open_Color, Open_Width, Open_Style); if(ShowWeeklyOpenLine && wopen != EMPTY_VALUE) CreateSegment(wopenLine, dayStart, nextDay, wopen, Open_Color, Open_Width, STYLE_DASH); if(ShowLabels) { CreateLabel(pdhLabel, "PDH", pdh, PDH_Color, nextDay); CreateLabel(pdlLabel, "PDL", pdl, PDL_Color, nextDay); if(ShowMidLine) CreateLabel(midLabel, (UseEquilibriumLabel ? "EQUILIBRIUM" : "MID"), mid, MidQ_Color, nextDay); if(ShowQLines) { CreateLabel(qloLabel, "DISCOUNT", qlo, MidQ_Color, nextDay); CreateLabel(qhiLabel, "PREMIUM", qhi, MidQ_Color, nextDay); } if(ShowTriggerLines) { CreateLabel(trigLongLabel, "LONG TRIG", trigger_long, Trigger_Color, nextDay); CreateLabel(trigShortLabel, "SHORT TRIG", trigger_short, Trigger_Color, nextDay); } if(ShowDailyOpenLine && dopen != EMPTY_VALUE) CreateLabel(dopenLabel, "D-OPEN", dopen, Open_Color, nextDay); if(ShowWeeklyOpenLine && wopen != EMPTY_VALUE) CreateLabel(wopenLabel, "W-OPEN", wopen, Open_Color, nextDay); } if(makeCurrent) { curPDH_Line = pdhLine; curPDL_Line = pdlLine; curMID_Line = midLine; curQLO_Line = qloLine; curQHI_Line = qhiLine; curDOPEN_Line = dopenLine; curWOPEN_Line = wopenLine; curTrigLong_Line = trigLongLine; curTrigShort_Line = trigShortLine; curPDH_Label = pdhLabel; curPDL_Label = pdlLabel; curMID_Label = midLabel; curQLO_Label = qloLabel; curQHI_Label = qhiLabel; curDOPEN_Label = dopenLabel; curWOPEN_Label = wopenLabel; curTrigLong_Label = trigLongLabel; curTrigShort_Label = trigShortLabel; // cache current-day values for EA buffers g_pdh = pdh; g_pdl = pdl; g_mid = mid; g_discount = qlo; g_premium = qhi; g_dopen = dopen; g_wopen = wopen; g_trigger_long = trigger_long; g_trigger_short = trigger_short; } } //+------------------------------------------------------------------+ void ResetAlertSides() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid <= 0) return; lastSide_PDH = Side(bid, g_pdh, 0); lastSide_PDL = Side(bid, g_pdl, 0); lastSide_MID = Side(bid, g_mid, 0); lastSide_QLO = Side(bid, g_discount, 0); lastSide_QHI = Side(bid, g_premium, 0); lastSide_DOPEN = Side(bid, g_dopen, 0); lastSide_WOPEN = Side(bid, g_wopen, 0); } //+------------------------------------------------------------------+ void CheckAlerts(const datetime &time[]) { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid <= 0) return; datetime barTime = time[0]; int tolPts = (AlertOnTouch ? TouchTolerancePts : 0); if(AlertPDH_PDL) { if(g_pdh > 0) CheckCrossOrTouch(barTime, "PDH", bid, g_pdh, tolPts, lastSide_PDH); if(g_pdl > 0) CheckCrossOrTouch(barTime, "PDL", bid, g_pdl, tolPts, lastSide_PDL); } if(AlertMid_Q) { if(ShowMidLine && g_mid > 0) { string midTag = (UseEquilibriumLabel ? "EQUILIBRIUM" : "MID"); CheckCrossOrTouch(barTime, midTag, bid, g_mid, tolPts, lastSide_MID); } if(ShowQLines) { if(g_discount > 0) CheckCrossOrTouch(barTime, "DISCOUNT", bid, g_discount, tolPts, lastSide_QLO); if(g_premium > 0) CheckCrossOrTouch(barTime, "PREMIUM", bid, g_premium, tolPts, lastSide_QHI); } } if(AlertOpens) { if(ShowDailyOpenLine && g_dopen > 0) CheckCrossOrTouch(barTime, "D-OPEN", bid, g_dopen, tolPts, lastSide_DOPEN); if(ShowWeeklyOpenLine && g_wopen > 0) CheckCrossOrTouch(barTime, "W-OPEN", bid, g_wopen, tolPts, lastSide_WOPEN); } } //+------------------------------------------------------------------+ int Side(double price, double level, int tolPoints) { if(level <= 0 || level == EMPTY_VALUE) return 0; double tol = tolPoints * _Point; if(price > level + tol) return +1; if(price < level - tol) return -1; return 0; } //+------------------------------------------------------------------+ bool CanFireThisCandle(datetime barTime) { if(!LimitAlertsToOnePerCandle) return true; return (lastAlertBarTime != barTime); } //+------------------------------------------------------------------+ void MarkAlertFired(datetime barTime) { if(LimitAlertsToOnePerCandle) lastAlertBarTime = barTime; } //+------------------------------------------------------------------+ void FireAlert(datetime barTime, const string msg) { if(!CanFireThisCandle(barTime)) return; Alert(msg); Print(msg); MarkAlertFired(barTime); } //+------------------------------------------------------------------+ void CheckCrossOrTouch(datetime barTime, const string tag, double bid, double level, int tolPts, int &lastSide) { if(AlertOnTouch && tolPts > 0) { double tol = tolPts * _Point; if(MathAbs(bid - level) <= tol) { FireAlert(barTime, StringFormat("%s: TOUCH %s @ %s (Bid=%s)", _Symbol, tag, DoubleToString(level, _Digits), DoubleToString(bid, _Digits))); } } int sideNow = Side(bid, level, tolPts); if(sideNow == 0) return; if(lastSide == 0) { lastSide = sideNow; return; } if(sideNow != lastSide) { string dir = (sideNow > lastSide ? "UP" : "DOWN"); FireAlert(barTime, StringFormat("%s: CROSS %s %s @ %s (Bid=%s)", _Symbol, dir, tag, DoubleToString(level, _Digits), DoubleToString(bid, _Digits))); lastSide = sideNow; } } //+------------------------------------------------------------------+ string DayTag(datetime t) { MqlDateTime dt; TimeToStruct(t, dt); return StringFormat("%04d%02d%02d", dt.year, dt.mon, dt.day); } //+------------------------------------------------------------------+ void CreateSegment(const string name, datetime t1, datetime t2, double price, color clr, int width, ENUM_LINE_STYLE style) { if(price == EMPTY_VALUE || price <= 0.0) return; if(ObjectFind(0, name) == -1) ObjectCreate(0, name, OBJ_TREND, 0, t1, price, t2, price); ObjectMove(0, name, 0, t1, price); ObjectMove(0, name, 1, t2, price); ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, false); ObjectSetInteger(0, name, OBJPROP_RAY_LEFT, false); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, style); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); } //+------------------------------------------------------------------+ void CreateLabel(const string name, const string text, double price, color clr, datetime t) { if(price == EMPTY_VALUE || price <= 0.0) return; if(ObjectFind(0, name) == -1) { ObjectCreate(0, name, OBJ_TEXT, 0, t, price); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, true); } ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectMove(0, name, 0, t, price); } //+------------------------------------------------------------------+ void AlignLabels() { datetime t = iTime(_Symbol, PERIOD_D1, 0) + 86400; AlignOne(curPDH_Label, curPDH_Line, t); AlignOne(curPDL_Label, curPDL_Line, t); AlignOne(curMID_Label, curMID_Line, t); AlignOne(curQLO_Label, curQLO_Line, t); AlignOne(curQHI_Label, curQHI_Line, t); AlignOne(curDOPEN_Label, curDOPEN_Line, t); AlignOne(curWOPEN_Label, curWOPEN_Line, t); AlignOne(curTrigLong_Label, curTrigLong_Line, t); AlignOne(curTrigShort_Label, curTrigShort_Line, t); } //+------------------------------------------------------------------+ void AlignOne(const string labelName, const string lineName, datetime t) { if(ObjectFind(0, labelName) == -1) return; if(ObjectFind(0, lineName) == -1) return; double p = ObjectGetDouble(0, lineName, OBJPROP_PRICE, 0); if(p > 0.0 && p != EMPTY_VALUE) ObjectMove(0, labelName, 0, t, p); } //+------------------------------------------------------------------+ void CleanupOldHistory(int keepDays) { if(keepDays <= 0) return; datetime cutoff = iTime(_Symbol, PERIOD_D1, keepDays); if(cutoff <= 0) return; int total = ObjectsTotal(0); for(int i = total - 1; i >= 0; i--) { string name = ObjectName(0, i); if(StringLen(name) < 8) continue; string suffix = StringSubstr(name, StringLen(name) - 8, 8); if(!IsDigitsOnly(suffix)) continue; datetime objDay = TagToDate(suffix); if(objDay > 0 && objDay < cutoff) ObjectDelete(0, name); } } //+------------------------------------------------------------------+ bool IsDigitsOnly(const string s) { for(int i = 0; i < StringLen(s); i++) { ushort c = StringGetCharacter(s, i); if(c < '0' || c > '9') return false; } return true; } //+------------------------------------------------------------------+ datetime TagToDate(const string tag) { if(StringLen(tag) != 8) return 0; int y = (int)StringToInteger(StringSubstr(tag, 0, 4)); int m = (int)StringToInteger(StringSubstr(tag, 4, 2)); int d = (int)StringToInteger(StringSubstr(tag, 6, 2)); MqlDateTime dt; dt.year = y; dt.mon = m; dt.day = d; dt.hour = 0; dt.min = 0; dt.sec = 0; return StructToTime(dt); } //+------------------------------------------------------------------+