mql5/Indicators/DexterrFX - SMC Bible/External.mq5
younglubah-gmail 9c54121a6b new files added
2026-03-04 16:35:00 +02:00

420 lines
13 KiB
MQL5

//+------------------------------------------------------------------+
//| 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;
}
//+------------------------------------------------------------------+