420 строки
13 КиБ
MQL5
420 строки
13 КиБ
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;
| |||
}
| |||
//+------------------------------------------------------------------+
|