PreviousDay_HL/PreviousDay_HL.mq5
digitalrogue 5a652b3a68
2026-04-17 15:24:10 -05:00

656 righe
Nessun fine linea
20 KiB
MQL5

//+------------------------------------------------------------------+
//| 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<rates_total; i++)
{
TriggerLongBuffer[i] = g_trigger_long;
TriggerShortBuffer[i] = g_trigger_short;
PDHBuffer[i] = g_pdh;
PDLBuffer[i] = g_pdl;
MidBuffer[i] = g_mid;
DiscountBuffer[i] = g_discount;
PremiumBuffer[i] = g_premium;
DOpenBuffer[i] = g_dopen;
WOpenBuffer[i] = g_wopen;
}
if(EnableAlerts)
CheckAlerts(time);
return rates_total;
}
//+------------------------------------------------------------------+
void BuildHistoricalLevels(int daysBack)
{
int maxBars = Bars(_Symbol, PERIOD_D1);
if(maxBars < 3) return;
int limit = MathMin(daysBack, maxBars - 2);
for(int shift = limit; shift >= 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);
}
//+------------------------------------------------------------------+