//+------------------------------------------------------------------+ //| External.mq5 | //| External swings from internal Swing iCustom (structure-based) | //| 1) External High: highest among N swing-highs on BOTH sides | //| 2) External Low : lowest among N swing-lows on BOTH sides | //| 3) If consecutive external highs -> insert lowest low between | //| If consecutive external lows -> insert highest high between | //| NO ArraySetAsSeries() | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.40" #property indicator_chart_window #property indicator_plots 2 #property indicator_buffers 5 //--- Plot 0: External Swing High #property indicator_label1 "SwingHigh" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrCrimson #property indicator_width1 1 //--- Plot 1: External Swing Low #property indicator_label2 "SwingLow" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrMediumSeaGreen #property indicator_width2 1 input int InpLeftRightBars = 3; // passed to internal Swing indicator input int InpSwingWindow = 2; // N swings on EACH side (2 => compare 2 left + 2 right) input ushort InpArrowCodeHigh = 172; input ushort InpArrowCodeLow = 172; //--- plotted (external) buffers double SwingHighBuffer[]; double SwingLowBuffer[]; double MergedSwingBuffer[]; //--- internal swing buffers copied from iCustom Swing double HighBuffer[]; double LowBuffer[]; //--- iCustom handle int handle = INVALID_HANDLE; datetime last_bar_time = 0; //----------------------------- Helpers ------------------------------// bool HasValue(const double v) { if(v == EMPTY_VALUE) return false; if(v <= 0.0) return false; // protects against zero fillers return true; } // series indices: 0 newest, rates_total-1 oldest // return min low index between two bar indices (inclusive or exclusive pivot bars) int FindLowestLowIndexBetween(const double &low[], const int idxA, const int idxB) { int start = (idxA > idxB) ? idxA : idxB; // older (larger index) int end = (idxA > idxB) ? idxB : idxA; // newer (smaller index) // Prefer excluding the pivot bars themselves; keep at least one bar scan if possible int s = start - 1; int e = end + 1; if(s < e) { s = start; // fallback if too tight e = end; } double best = DBL_MAX; int bestIdx = -1; for(int i = s; i >= e; i--) { if(low[i] < best) { best = low[i]; bestIdx = i; } } return bestIdx; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindHighestHighIndexBetween(const double &high[], const int idxA, const int idxB) { int start = (idxA > idxB) ? idxA : idxB; int end = (idxA > idxB) ? idxB : idxA; int s = start - 1; int e = end + 1; if(s < e) { s = start; e = end; } double best = -DBL_MAX; int bestIdx = -1; for(int i = s; i >= e; i--) { if(high[i] > best) { best = high[i]; bestIdx = i; } } return bestIdx; } // Swing point list (only highs list or lows list) struct SwingPoint { int index; // bar index double price; }; // External point (mixed highs/lows) enum ExtType { EXT_HIGH = 1, EXT_LOW = -1 }; struct ExtPoint { int index; double price; ExtType type; }; // Insert in chronological order (oldest->newest) into ext[] by index (descending index) void ExtInsertChrono(ExtPoint &ext[], const ExtPoint &p) { int n = ArraySize(ext); ArrayResize(ext, n + 1); // find insert position: ext sorted by index descending (older bigger index first) int pos = n; for(int i = 0; i < n; i++) { if(p.index > ext[i].index) { pos = i; break; } } // shift right for(int i = n; i > pos; i--) ext[i] = ext[i - 1]; ext[pos] = p; } // If an ext point already exists at index with same type, keep more extreme. // If exists with opposite type (rare), keep both by shifting (we avoid that in logic). void ExtUpsert(ExtPoint &ext[], const ExtPoint &p) { int n = ArraySize(ext); for(int i = 0; i < n; i++) { if(ext[i].index == p.index && ext[i].type == p.type) { if(p.type == EXT_HIGH) { if(p.price > ext[i].price) ext[i].price = p.price; } else { if(p.price < ext[i].price) ext[i].price = p.price; } return; } } ExtInsertChrono(ext, p); } //+------------------------------------------------------------------+ //| Init | //+------------------------------------------------------------------+ int OnInit() { // map plotted buffers SetIndexBuffer(0, SwingHighBuffer, INDICATOR_DATA); SetIndexBuffer(1, SwingLowBuffer, INDICATOR_DATA); // map internal copy buffers (calculation only) SetIndexBuffer(2, HighBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer(3, LowBuffer, INDICATOR_CALCULATIONS); SetIndexBuffer(4, MergedSwingBuffer, INDICATOR_CALCULATIONS); PlotIndexSetInteger(0, PLOT_ARROW, InpArrowCodeHigh); PlotIndexSetInteger(1, PLOT_ARROW, InpArrowCodeLow); PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); // iCustom handle to your internal swing indicator handle = iCustom(_Symbol, _Period, "Arjo Indicators\\Swing", InpLeftRightBars); if(handle == INVALID_HANDLE) { Print("Error creating Swing iCustom handle. LastError=", GetLastError()); return(INIT_FAILED); } if(InpSwingWindow < 1) { Print("InpSwingWindow must be >= 1. Using 1."); } return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Deinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(handle != INVALID_HANDLE) { IndicatorRelease(handle); handle = INVALID_HANDLE; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void MergeSwings(const double &swingHigh[], const double &swingLow[], double &merged[], int rates_total) { ArrayInitialize(merged, 0.0); for(int i = 0; i < rates_total; i++) { if(swingHigh[i] != 0.0) { merged[i] = swingHigh[i]; Print("high"); } else if(swingLow[i] != 0.0) { merged[i] = swingLow[i]; Print("low"); } } } //+------------------------------------------------------------------+ //| Calculate | //+------------------------------------------------------------------+ 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; // new-bar gate (time[0] is newest) if(time[rates_total - 1] == last_bar_time) return rates_total; // copy internal swing 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; } // clear plotted buffers for(int i = 0; i < rates_total; i++) { SwingHighBuffer[i] = EMPTY_VALUE; SwingLowBuffer[i] = EMPTY_VALUE; } // Build chronological lists of swing highs and swing lows (oldest -> newest) SwingPoint highs[]; SwingPoint lows[]; ArrayResize(highs, 0); ArrayResize(lows, 0); for(int i = rates_total - 1; i >= 0; i--) { // oldest -> newest if(HasValue(HighBuffer[i])) { int n = ArraySize(highs); ArrayResize(highs, n + 1); highs[n].index = i; highs[n].price = HighBuffer[i]; } if(HasValue(LowBuffer[i])) { int n = ArraySize(lows); ArrayResize(lows, n + 1); lows[n].index = i; lows[n].price = LowBuffer[i]; } } int win = (InpSwingWindow < 1) ? 1 : InpSwingWindow; // Collect externals (mixed list) ExtPoint ext[]; ArrayResize(ext, 0); // External highs: cur > all highs in [j-win..j-1] and > all highs in [j+1..j+win] int hc = ArraySize(highs); if(hc >= (2 * win + 1)) { for(int j = win; j <= hc - 1 - win; j++) { double cur = highs[j].price; bool ok = true; for(int k = j - win; k <= j - 1; k++) { if(highs[k].price >= cur) { ok = false; break; } } if(ok) { for(int k = j + 1; k <= j + win; k++) { if(highs[k].price >= cur) { ok = false; break; } } } if(ok) { ExtPoint p; p.index = highs[j].index; p.price = cur; p.type = EXT_HIGH; ExtInsertChrono(ext, p); } } } // External lows: cur < all lows in [j-win..j-1] and < all lows in [j+1..j+win] int lc = ArraySize(lows); if(lc >= (2 * win + 1)) { for(int j = win; j <= lc - 1 - win; j++) { double cur = lows[j].price; bool ok = true; for(int k = j - win; k <= j - 1; k++) { if(lows[k].price <= cur) { ok = false; break; } } if(ok) { for(int k = j + 1; k <= j + win; k++) { if(lows[k].price <= cur) { ok = false; break; } } } if(ok) { ExtPoint p; p.index = lows[j].index; p.price = cur; p.type = EXT_LOW; ExtInsertChrono(ext, p); } } } // Post-process: // If consecutive external points have the same type, insert opposite extreme between them: // - HIGH then HIGH -> insert LOW at lowest low between their indices // - LOW then LOW -> insert HIGH at highest high between their indices int ec = ArraySize(ext); if(ec >= 2) { // We'll build a new list with insertions ExtPoint fixed[]; ArrayResize(fixed, 0); // ext is chronological: older first (bigger index first) ExtInsertChrono(fixed, ext[0]); for(int i = 1; i < ec; i++) { // find last in fixed (chronologically newest element is at the end because we always insert chrono; // but easier: pick the previous ext in chrono order by scanning last inserted with same index logic) // We'll simply get the most recent point in fixed by choosing the smallest index in fixed. // However because fixed is kept chrono, newest is last element. int fn = ArraySize(fixed); ExtPoint prev = fixed[fn - 1]; ExtPoint cur = ext[i]; if(prev.type == cur.type) { if(cur.type == EXT_HIGH) { int lowIdx = FindLowestLowIndexBetween(low, prev.index, cur.index); if(lowIdx >= 0) { ExtPoint mid; mid.index = lowIdx; mid.price = low[lowIdx]; mid.type = EXT_LOW; ExtUpsert(fixed, mid); } } else { // EXT_LOW int highIdx = FindHighestHighIndexBetween(high, prev.index, cur.index); if(highIdx >= 0) { ExtPoint mid; mid.index = highIdx; mid.price = high[highIdx]; mid.type = EXT_HIGH; ExtUpsert(fixed, mid); } } } // always add current ExtUpsert(fixed, cur); } // Replace ext with fixed (already chrono) //ext = fixed; ArrayCopy(ext, fixed); ec = ArraySize(ext); } // Plot results for(int i = 0; i < ec; i++) { if(ext[i].index < 0 || ext[i].index >= rates_total) continue; if(ext[i].type == EXT_HIGH) { SwingHighBuffer[ext[i].index] = ext[i].price; MergedSwingBuffer[ext[i].index] = ext[i].price; } else { SwingLowBuffer[ext[i].index] = ext[i].price; MergedSwingBuffer[ext[i].index] = ext[i].price; } } // MergeSwings(SwingHighBuffer, SwingLowBuffer, MergedSwingBuffer, rates_total); last_bar_time = time[rates_total - 1]; return rates_total; } //+------------------------------------------------------------------+