2026-03-04 16:35:19 +02:00 | | | //+------------------------------------------------------------------+
|
| | | //| 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;
|
| | | }
|
| | |
|
2026-03-04 16:41:59 +02:00 | | |
|
2026-03-04 16:35:19 +02:00 | | | //--- 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;
|
| | | }
|
| | | //+------------------------------------------------------------------+
|