//+------------------------------------------------------------------+ //| SwingPoints_H4.mq5 | //| Swing High / Low Detection with Labels | //| Structural classification: HH / HL / LH / LL | //+------------------------------------------------------------------+ //| | //| Detects swing highs and swing lows using configurable left/ | //| right bar strength, plots them as arrows, and labels each one | //| with its structural classification. | //| | //| FEATURES | //| -------- | //| - Configurable left/right strength for pivot detection | //| - Minimum price move filter to ignore trivial pivots | //| - HH / HL / LH / LL labels for reading market structure | //| - Object lifecycle management: old labels are cleaned up | //| - Buffer-based output, readable via CopyBuffer() from EAs | //| - Public accessors: GetLastSwingHigh(), GetLastSwingLow() | //| | //| INPUTS | //| ------ | //| InpLeftBars - bars required to the left (default 3) | //| InpRightBars - bars required to the right (default 2) | //| InpMinMovePoints - minimum move in points (default 10) | //| InpShowLabels - show HH/HL/LH/LL labels (default true) | //| InpMaxBars - bars to calculate (default 500) | //| | //| NOTE | //| ---- | //| This is a reference implementation published as part of the | //| mql5-projects library. It makes no performance claims and is | //| not a standalone trading system. | //| | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, C.J. Weekes" #property copyright "Copyright 2026, Alfonso Golden Trader #property version "1.00" #property description "Swing high/low detection with HH/HL/LH/LL labels." #property description "Configurable strength, minimum move filter, object cleanup." #property description "Buffer output, EA-accessible. Reference implementation." //--- Plot Swing Highs #property indicator_label1 "SwingHigh" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrRed #property indicator_width1 2 #property indicator_style1 STYLE_SOLID //--- Plot Swing Lows #property indicator_label2 "SwingLow" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrBlue #property indicator_width2 2 #property indicator_style2 STYLE_SOLID //--- Input Parameters input int InpLeftBars = 3; // Left Strength (bars before) input int InpRightBars = 2; // Right Strength (bars after) input double InpMinMovePoints = 10; // Min price move in points input bool InpShowLabels = true; // Show HH/HL/LH/LL labels input int InpMaxBars = 500; // Bars to calculate //--- Indicator buffers double SwingHighBuffer[]; double SwingLowBuffer[]; double HighPriceBuffer[]; double LowPriceBuffer[]; //--- Global variables datetime lastAlertTime = 0; int swingHighIndex, swingLowIndex; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { // Set index mapping SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA); SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA); SetIndexBuffer(2, HighPriceBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer(3, LowPriceBuffer, INDICATOR_CALCULATIONS); // Set empty value PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0); // Set arrow codes (MQL5 uses Wingdings character codes) PlotIndexSetInteger(0, PLOT_ARROW, 217); // Down arrow PlotIndexSetInteger(1, PLOT_ARROW, 218); // Up arrow // Set arrow shifts PlotIndexSetInteger(0, PLOT_ARROW_SHIFT, 5); PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, -5); IndicatorSetString(INDICATOR_SHORTNAME, "SwingPoints_H4 (" + IntegerToString(InpLeftBars) + "," + IntegerToString(InpRightBars) + ")"); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { // Ensure arrays are properly sized if(rates_total <= InpLeftBars + InpRightBars) return(0); // Limit calculation range for performance int start = prev_calculated - InpRightBars - 1; if(start < InpRightBars + 1) start = InpRightBars + 1; // Limit to MaxBars for speed int end = rates_total - 1; if(end - start > InpMaxBars && prev_calculated == 0) start = end - InpMaxBars; if(start < InpRightBars + 1) start = InpRightBars + 1; // Clear buffers at start if(prev_calculated == 0) { ArrayInitialize(SwingHighBuffer, 0); ArrayInitialize(SwingLowBuffer, 0); ArrayInitialize(HighPriceBuffer, 0); ArrayInitialize(LowPriceBuffer, 0); } // Detect swing points for(int i = start; i <= end - InpRightBars && !IsStopped(); i++) { // Skip if no price movement if(high[i] == low[i]) continue; // Check for Swing High if(IsSwingHigh(high, i, rates_total)) { SwingHighBuffer[i] = high[i]; HighPriceBuffer[i] = high[i]; // Draw HH/HL label if enabled if(InpShowLabels) DrawSwingLabel(time, high, low, i, true); } else SwingHighBuffer[i] = 0; // Check for Swing Low if(IsSwingLow(low, i, rates_total)) { SwingLowBuffer[i] = low[i]; LowPriceBuffer[i] = low[i]; // Draw LH/LL label if enabled if(InpShowLabels) DrawSwingLabel(time, high, low, i, false); } else SwingLowBuffer[i] = 0; } // Clean up old trend labels if needed if(InpShowLabels && prev_calculated > 0) CleanupOldLabels(time, rates_total); return(rates_total); } //+------------------------------------------------------------------+ //| Check if bar at index is a swing high | //+------------------------------------------------------------------+ bool IsSwingHigh(const double &high[], int index, int rates_total) { // Need enough bars on both sides if(index < InpLeftBars || index + InpRightBars >= rates_total) return false; double candidateHigh = high[index]; double minMove = InpMinMovePoints * _Point; // Check left side - all must be lower for(int l = 1; l <= InpLeftBars; l++) { if(high[index - l] >= candidateHigh - minMove) return false; } // Check right side - all must be lower for(int r = 1; r <= InpRightBars; r++) { if(high[index + r] >= candidateHigh - minMove) return false; } return true; } //+------------------------------------------------------------------+ //| Check if bar at index is a swing low | //+------------------------------------------------------------------+ bool IsSwingLow(const double &low[], int index, int rates_total) { // Need enough bars on both sides if(index < InpLeftBars || index + InpRightBars >= rates_total) return false; double candidateLow = low[index]; double minMove = InpMinMovePoints * _Point; // Check left side - all must be higher for(int l = 1; l <= InpLeftBars; l++) { if(low[index - l] <= candidateLow + minMove) return false; } // Check right side - all must be higher for(int r = 1; r <= InpRightBars; r++) { if(low[index + r] <= candidateLow + minMove) return false; } return true; } //+------------------------------------------------------------------+ //| Draw HH/HL/LH/LL labels | //+------------------------------------------------------------------+ void DrawSwingLabel(const datetime &time[], const double &high[], const double &low[], int index, bool isHigh) { string labelName = "SwingLabel_" + IntegerToString(index) + "_" + (isHigh ? "H" : "L"); // Delete existing label if it exists ObjectDelete(0, labelName); // Determine if this is HH/HL or LH/LL by comparing to previous swing bool isHigher = false; bool isLower = false; // Find previous swing of same type int prevIndex = FindPreviousSwing(index - 1, isHigh); if(prevIndex >= 0) { if(isHigh) { if(high[index] > high[prevIndex]) isHigher = true; // HH else isLower = true; // LH } else { if(low[index] < low[prevIndex]) isLower = true; // LL else isHigher = true; // HL } } // Create label string labelText = ""; color labelColor = clrGray; double labelPrice; if(isHigh) { labelPrice = high[index] + (20 * _Point); if(isHigher) { labelText = "HH"; labelColor = clrLimeGreen; } else if(isLower) { labelText = "LH"; labelColor = clrOrange; } else labelText = "H"; } else { labelPrice = low[index] - (20 * _Point); if(isLower) { labelText = "LL"; labelColor = clrRed; } else if(isHigher) { labelText = "HL"; labelColor = clrGreen; } else labelText = "L"; } if(StringLen(labelText) > 0) { ObjectCreate(0, labelName, OBJ_TEXT, 0, time[index], labelPrice); ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(0, labelName, OBJPROP_COLOR, labelColor); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8); ObjectSetInteger(0, labelName, OBJPROP_BACK, true); } } //+------------------------------------------------------------------+ //| Find previous swing point | //+------------------------------------------------------------------+ int FindPreviousSwing(int startIndex, bool isHigh) { if(isHigh) { for(int i = startIndex; i >= 0; i--) { if(i < ArraySize(SwingHighBuffer) && SwingHighBuffer[i] > 0) return i; } } else { for(int i = startIndex; i >= 0; i--) { if(i < ArraySize(SwingLowBuffer) && SwingLowBuffer[i] > 0) return i; } } return -1; } //+------------------------------------------------------------------+ //| Clean up old labels | //+------------------------------------------------------------------+ void CleanupOldLabels(const datetime &time[], int total) { // Keep only last 100 labels for performance string prefix = "SwingLabel_"; int keepCount = 100; int found = 0; for(int i = total - 1; i >= 0 && found < keepCount; i--) { string nameH = prefix + IntegerToString(i) + "_H"; string nameL = prefix + IntegerToString(i) + "_L"; if(ObjectFind(0, nameH) >= 0) found++; if(ObjectFind(0, nameL) >= 0) found++; } // Delete older ones for(int i = 0; i < total - keepCount * 2; i++) { string nameH = prefix + IntegerToString(i) + "_H"; string nameL = prefix + IntegerToString(i) + "_L"; ObjectDelete(0, nameH); ObjectDelete(0, nameL); } } //+------------------------------------------------------------------+ //| Get latest swing high (for EA use) | //+------------------------------------------------------------------+ double GetLastSwingHigh(int shift = 1) { int total = ArraySize(SwingHighBuffer); for(int i = total - shift; i >= 0; i--) { if(i < total && SwingHighBuffer[i] > 0) return SwingHighBuffer[i]; } return 0; } //+------------------------------------------------------------------+ //| Get latest swing low (for EA use) | //+------------------------------------------------------------------+ double GetLastSwingLow(int shift = 1) { int total = ArraySize(SwingLowBuffer); for(int i = total - shift; i >= 0; i--) { if(i < total && SwingLowBuffer[i] > 0) return SwingLowBuffer[i]; } return 0; } //+------------------------------------------------------------------+