mql5_trade_poc/indicators/SwingPoints_H4/SwingPoints_H4.mq5
388 lines
No EOL
14 KiB
MQL5
//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+