//+------------------------------------------------------------------+ //| HyperSwings.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_plots 4 #property indicator_buffers 8 //--- Swing High #property indicator_label1 "SwingHigh" #property indicator_type1 DRAW_COLOR_ARROW #property indicator_color1 clrMaroon, clrLightCoral #property indicator_width1 1 #property indicator_label2 "SwingLow" #property indicator_type2 DRAW_COLOR_ARROW #property indicator_color2 clrSeaGreen, clrLightGreen #property indicator_width2 1 #property indicator_label3 "HighPoints" #property indicator_type3 DRAW_ARROW #property indicator_color3 clrCrimson #property indicator_width3 1 #property indicator_label4 "LowPoints" #property indicator_type4 DRAW_ARROW #property indicator_color4 clrMediumSeaGreen #property indicator_width4 1 input int InpLeftRightBars = 3; // Swing detection size input int InpLeftRightBars0 = 1; // Minimum swing size between points input ushort code = 170; // Arrow symbol //--- Indicator buffers double SwingHighBuffer[]; // Swing high prices double SwingLowBuffer[]; // Swing low prices double SwingHighColors[]; // Colors for swing highs (0=initial, 1=broken) double SwingLowColors[]; // Colors for swing lows (0=initial, 1=broken) double HighBufferPoints[]; // Points where swing highs occur double LowBufferPoints[]; // Points where swing lows occur double HighBuffer[]; // Swing high signals from external indicator double LowBuffer[]; // Swing low signals from external indicator //--- Internal tracking int handle = INVALID_HANDLE; datetime last_bar_time = 0; //+------------------------------------------------------------------+ //| Initialize indicator | //+------------------------------------------------------------------+ int OnInit() { //--- Set index buffers SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA); SetIndexBuffer(2, SwingLowBuffer, INDICATOR_DATA); SetIndexBuffer(1, SwingHighColors, INDICATOR_COLOR_INDEX); SetIndexBuffer(3, SwingLowColors, INDICATOR_COLOR_INDEX); SetIndexBuffer(4, HighBufferPoints, INDICATOR_DATA); SetIndexBuffer(5, LowBufferPoints, INDICATOR_DATA); SetIndexBuffer(6, HighBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer(7, LowBuffer, INDICATOR_CALCULATIONS); //--- Set arrow properties PlotIndexSetInteger(0, PLOT_ARROW, 158); PlotIndexSetInteger(1, PLOT_ARROW, 158); PlotIndexSetInteger(2, PLOT_ARROW, code); PlotIndexSetInteger(3, PLOT_ARROW, code); //--- Set empty values PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); //--- Get swing indicator handle handle = iCustom(_Symbol, _Period, "Arjo Indicators\\Swing", InpLeftRightBars); if(handle == INVALID_HANDLE) { Print("Error creating Swing iCustom handle. LastError=", GetLastError()); return INIT_FAILED; } return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Find lowest low between two indices (inclusive) | //+------------------------------------------------------------------+ int FindLowestLowIndex(const double &low[], int start_idx, int end_idx) { int from_idx = MathMin(start_idx, end_idx); int to_idx = MathMax(start_idx, end_idx); int trough_index = -1; double lowest_price = DBL_MAX; for(int i = from_idx + 1; i < to_idx; i++) { // Structural rule: neighbors must be higher if(low[i] < low[i - 1] && low[i] < low[i + 1]) { if(low[i] < lowest_price) { lowest_price = low[i]; trough_index = i; } } } return trough_index; } //+------------------------------------------------------------------+ //| Find highest high between two indices (inclusive) | //+------------------------------------------------------------------+ int FindHighestHighIndex(const double &high[], int start_idx, int end_idx) { int from_idx = MathMin(start_idx, end_idx); int to_idx = MathMax(start_idx, end_idx); int peak_index = -1; double highest_price = -DBL_MAX; for(int i = from_idx + 1; i < to_idx; i++) { // Structural rule: neighbors must be lower if(high[i] > high[i - 1] && high[i] > high[i + 1]) { if(high[i] > highest_price) { highest_price = high[i]; peak_index = i; } } } return peak_index; } //+------------------------------------------------------------------+ //| Fill all bars between two indices with a specific price | //+------------------------------------------------------------------+ void FillRange(double &buffer[], double &colorBuffer[], int start_idx, int end_idx, double price, int color_idx) { int from_idx = MathMin(start_idx, end_idx); int to_idx = MathMax(start_idx, end_idx); for(int i = from_idx; i <= to_idx; i++) { buffer[i] = price; colorBuffer[i] = color_idx; } } //+------------------------------------------------------------------+ //| Process the entire history from oldest to newest | //+------------------------------------------------------------------+ void ProcessAllBars(const int rates_total, const double &high[], const double &low[], const double &close[]) { // Clear buffers for(int i = 0; i < rates_total; i++) { SwingHighBuffer[i] = EMPTY_VALUE; SwingLowBuffer[i] = EMPTY_VALUE; HighBufferPoints[i] = EMPTY_VALUE; LowBufferPoints[i] = EMPTY_VALUE; SwingHighColors[i] = 0; SwingLowColors[i] = 0; } int current_high_index = -1; int current_low_index = -1; double current_high_price = 0; double current_low_price = 0; int last_high_break_index = -1; int last_low_break_index = -1; // Start from oldest (index 0) to newest (rates_total-1) for(int i = 0; i < rates_total; i++) { // Check for new swing high if(HasValue(HighBuffer[i]) && current_high_index == -1) { current_high_index = i; current_high_price = high[i]; SwingHighBuffer[i] = high[i]; HighBufferPoints[i] = high[i]; SwingHighColors[i] = 0; } // Check for new swing low if(HasValue(LowBuffer[i]) && current_low_index == -1) { current_low_index = i; current_low_price = low[i]; SwingLowBuffer[i] = low[i]; LowBufferPoints[i] = low[i]; SwingLowColors[i] = 0; } // If we have a current swing high, check for breakout above it if(current_high_index != -1 && close[i] > current_high_price) { // Find the lowest low between the swing high and breakout int lowest_low_idx = FindLowestLowIndex(low, current_high_index, i); if(lowest_low_idx != -1) { // Record the low point LowBufferPoints[lowest_low_idx] = low[lowest_low_idx]; SwingLowBuffer[lowest_low_idx] = low[lowest_low_idx]; SwingLowColors[lowest_low_idx] = 0; // Fill the swing high line from the swing high to the breakout FillRange(SwingHighBuffer, SwingHighColors, current_high_index, i, current_high_price, 0); // Mark the swing high as broken (color index 1) SwingHighColors[current_high_index] = 1; // Update current low to this new low point current_low_index = lowest_low_idx; current_low_price = low[lowest_low_idx]; last_high_break_index = i; } // Clear current swing high to look for next one current_high_index = -1; current_high_price = 0; } // If we have a current swing low, check for breakdown below it if(current_low_index != -1 && close[i] < current_low_price) { // Find the highest high between the swing low and breakdown int highest_high_idx = FindHighestHighIndex(high, current_low_index, i); if(highest_high_idx != -1) { // Record the high point HighBufferPoints[highest_high_idx] = high[highest_high_idx]; SwingHighBuffer[highest_high_idx] = high[highest_high_idx]; SwingHighColors[highest_high_idx] = 0; // Fill the swing low line from the swing low to the breakdown FillRange(SwingLowBuffer, SwingLowColors, current_low_index, i, current_low_price, 0); // Mark the swing low as broken (color index 1) SwingLowColors[current_low_index] = 1; // Update current high to this new high point current_high_index = highest_high_idx; current_high_price = high[highest_high_idx]; last_low_break_index = i; } // Clear current swing low to look for next one current_low_index = -1; current_low_price = 0; } } // Fill the final swing to the end of chart if(current_high_index != -1) { FillRange(SwingHighBuffer, SwingHighColors, current_high_index, rates_total-1, current_high_price, 0); } if(current_low_index != -1) { FillRange(SwingLowBuffer, SwingLowColors, current_low_index, rates_total-1, current_low_price, 0); } } //+------------------------------------------------------------------+ //| Main calculation 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[]) { if(rates_total <= 0) return 0; if(handle == INVALID_HANDLE) return 0; //--- Copy swing indicator buffers if(CopyBuffer(handle, 0, 0, rates_total, HighBuffer) <= 0) { Print("Error copying Swing HIGH buffer. LastError=", GetLastError()); return 0; } if(CopyBuffer(handle, 1, 0, rates_total, LowBuffer) <= 0) { Print("Error copying Swing LOW buffer. LastError=", GetLastError()); return 0; } //--- Process on each new bar if(time[rates_total - 1] != last_bar_time || prev_calculated == 0) { ProcessAllBars(rates_total, high, low, close); last_bar_time = time[rates_total - 1]; } return rates_total; } //+------------------------------------------------------------------+ //| Deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(handle != INVALID_HANDLE) { IndicatorRelease(handle); handle = INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| Helper function to check if a buffer value is valid | //+------------------------------------------------------------------+ bool HasValue(const double v) { if(v == EMPTY_VALUE) return false; if(v <= 0.0) return false; return true; } //+------------------------------------------------------------------+