last ...
This commit is contained in:
@@ -0,0 +1,839 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| CompleteStructure_MinorZones_EA.mq5 |
|
||||
//| Major Zones (4H) + Minor Zones (15m) with Full Validation |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Professional Trader & AI Assistant"
|
||||
#property version "4.00"
|
||||
#property strict
|
||||
|
||||
//--- Input Parameters - Major Structure (4H)
|
||||
input int MajorPivotLookback = 3;
|
||||
input int MaxBarsToScan = 500;
|
||||
input color BullishZoneColor = clrLime;
|
||||
input color BearishZoneColor = clrRed;
|
||||
input color InvalidZoneColor = clrGray;
|
||||
|
||||
//--- Input Parameters - Minor Structure (15m)
|
||||
input int MinorWickLookback = 2;
|
||||
input double WickToBodyRatio = 2.0;
|
||||
input color MinorHighZoneColor = clrOrange;
|
||||
input color MinorLowZoneColor = clrDodgerBlue;
|
||||
input color MinorInvalidColor = clrSilver;
|
||||
input color EquilibriumColor = clrYellow;
|
||||
|
||||
//--- ساختار داده برای ماژور
|
||||
struct MajorZone
|
||||
{
|
||||
double zoneHigh;
|
||||
double zoneLow;
|
||||
datetime zoneTime;
|
||||
int barIndex;
|
||||
bool isValid;
|
||||
};
|
||||
|
||||
//--- ساختار داده برای مینور
|
||||
struct MinorZone
|
||||
{
|
||||
double zoneHigh;
|
||||
double zoneLow;
|
||||
datetime zoneTime;
|
||||
int barIndex;
|
||||
bool isValid;
|
||||
bool isLongSignal; // true = سیگنال لانگ (کف مینور), false = سیگنال شورت (سقف مینور)
|
||||
};
|
||||
|
||||
//--- آرایههای ماژور
|
||||
MajorZone majorLows[];
|
||||
MajorZone majorHighs[];
|
||||
int currentLowIndex = -1;
|
||||
int currentHighIndex = -1;
|
||||
|
||||
//--- آرایههای مینور
|
||||
MinorZone minorLows[];
|
||||
MinorZone minorHighs[];
|
||||
int currentMinorLowIndex = -1;
|
||||
int currentMinorHighIndex = -1;
|
||||
|
||||
//--- متغیرهای بایاس و نواحی
|
||||
string biasDirection = "NEUTRAL";
|
||||
double equilibriumPrice = 0;
|
||||
double premiumZone = 0;
|
||||
double discountZone = 0;
|
||||
bool structureValid = false;
|
||||
|
||||
//--- پیشوند آبجکتها
|
||||
string mzPrefix = "MZ_";
|
||||
string qzPrefix = "QZ_";
|
||||
string mnPrefix = "MN_";
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
ArrayResize(majorLows, 0);
|
||||
ArrayResize(majorHighs, 0);
|
||||
ArrayResize(minorLows, 0);
|
||||
ArrayResize(minorHighs, 0);
|
||||
Print("EA v4.0 Initialized. Major + Minor Zones with Full Validation.");
|
||||
return (INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
ObjectsDeleteAll(0, mzPrefix);
|
||||
ObjectsDeleteAll(0, qzPrefix);
|
||||
ObjectsDeleteAll(0, mnPrefix);
|
||||
Comment("");
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// 1. دریافت دادههای 4H
|
||||
double high4H[], low4H[], open4H[], close4H[];
|
||||
datetime time4H[];
|
||||
|
||||
ArraySetAsSeries(high4H, true);
|
||||
ArraySetAsSeries(low4H, true);
|
||||
ArraySetAsSeries(open4H, true);
|
||||
ArraySetAsSeries(close4H, true);
|
||||
ArraySetAsSeries(time4H, true);
|
||||
|
||||
int copied4H = CopyHigh(_Symbol, PERIOD_H4, 0, MaxBarsToScan, high4H);
|
||||
CopyLow(_Symbol, PERIOD_H4, 0, MaxBarsToScan, low4H);
|
||||
CopyOpen(_Symbol, PERIOD_H4, 0, MaxBarsToScan, open4H);
|
||||
CopyClose(_Symbol, PERIOD_H4, 0, MaxBarsToScan, close4H);
|
||||
CopyTime(_Symbol, PERIOD_H4, 0, MaxBarsToScan, time4H);
|
||||
|
||||
if (copied4H <= 0)
|
||||
return;
|
||||
|
||||
// 2. بررسی بیاعتباری ماژورهای جاری
|
||||
CheckCurrentZonesInvalidation(close4H);
|
||||
|
||||
// 3. بهروزرسانی ماژورها (فقط در کندل جدید 4H)
|
||||
static datetime lastBarTime4H = 0;
|
||||
datetime currentBarTime4H = time4H[0];
|
||||
|
||||
if (lastBarTime4H != currentBarTime4H)
|
||||
{
|
||||
lastBarTime4H = currentBarTime4H;
|
||||
UpdateCurrentZones(high4H, low4H, open4H, close4H, time4H, copied4H);
|
||||
}
|
||||
|
||||
// 4. اعتبارسنجی ساختاری و بایاس
|
||||
ValidateStructure();
|
||||
DetermineBias();
|
||||
|
||||
// 5. رسم ماژورها
|
||||
DrawAllMajorZones();
|
||||
|
||||
// 6. اگر ساختار معتبر است، مینورها را پردازش کن
|
||||
if (structureValid)
|
||||
{
|
||||
CalculateQuartileZones();
|
||||
DrawQuartileZones();
|
||||
ProcessMinorZones();
|
||||
}
|
||||
|
||||
// 7. داشبورد
|
||||
DisplayDashboard();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| بررسی بیاعتباری ماژورهای جاری |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckCurrentZonesInvalidation(double &close4H[])
|
||||
{
|
||||
if (currentLowIndex >= 0 && currentLowIndex < ArraySize(majorLows))
|
||||
{
|
||||
if (majorLows[currentLowIndex].isValid)
|
||||
{
|
||||
for (int i = 0; i <= majorLows[currentLowIndex].barIndex; i++)
|
||||
{
|
||||
if (close4H[i] < majorLows[currentLowIndex].zoneLow)
|
||||
{
|
||||
majorLows[currentLowIndex].isValid = false;
|
||||
Print("Major Low INVALIDATED at index ", currentLowIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentHighIndex >= 0 && currentHighIndex < ArraySize(majorHighs))
|
||||
{
|
||||
if (majorHighs[currentHighIndex].isValid)
|
||||
{
|
||||
for (int i = 0; i <= majorHighs[currentHighIndex].barIndex; i++)
|
||||
{
|
||||
if (close4H[i] > majorHighs[currentHighIndex].zoneHigh)
|
||||
{
|
||||
majorHighs[currentHighIndex].isValid = false;
|
||||
Print("Major High INVALIDATED at index ", currentHighIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| بهروزرسانی ماژورها |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdateCurrentZones(double &high4H[], double &low4H[], double &open4H[], double &close4H[], datetime &time4H[], int total)
|
||||
{
|
||||
// مدیریت کفها
|
||||
bool needNewLow = (currentLowIndex == -1 || !majorLows[currentLowIndex].isValid);
|
||||
if (needNewLow)
|
||||
{
|
||||
bool foundPrevious = false;
|
||||
for (int i = ArraySize(majorLows) - 1; i >= 0; i--)
|
||||
{
|
||||
if (majorLows[i].isValid)
|
||||
{
|
||||
currentLowIndex = i;
|
||||
foundPrevious = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPrevious)
|
||||
{
|
||||
MajorZone newLow;
|
||||
if (FindNewMajorLow(high4H, low4H, open4H, close4H, time4H, total, newLow))
|
||||
{
|
||||
int newSize = ArraySize(majorLows);
|
||||
ArrayResize(majorLows, newSize + 1);
|
||||
majorLows[newSize] = newLow;
|
||||
currentLowIndex = newSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentLowIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// مدیریت سقفها
|
||||
bool needNewHigh = (currentHighIndex == -1 || !majorHighs[currentHighIndex].isValid);
|
||||
if (needNewHigh)
|
||||
{
|
||||
bool foundPrevious = false;
|
||||
for (int i = ArraySize(majorHighs) - 1; i >= 0; i--)
|
||||
{
|
||||
if (majorHighs[i].isValid)
|
||||
{
|
||||
currentHighIndex = i;
|
||||
foundPrevious = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPrevious)
|
||||
{
|
||||
MajorZone newHigh;
|
||||
if (FindNewMajorHigh(high4H, low4H, open4H, close4H, time4H, total, newHigh))
|
||||
{
|
||||
int newSize = ArraySize(majorHighs);
|
||||
ArrayResize(majorHighs, newSize + 1);
|
||||
majorHighs[newSize] = newHigh;
|
||||
currentHighIndex = newSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentHighIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool FindNewMajorLow(double &high[], double &low[], double &open[], double &close[], datetime &time[], int total, MajorZone &outZone)
|
||||
{
|
||||
int lastMinorHighIdx = -1;
|
||||
int lastMinorLowIdx = -1;
|
||||
|
||||
for (int i = total - MajorPivotLookback - 1; i >= MajorPivotLookback; i--)
|
||||
{
|
||||
if (IsPivot(high, i, true, total, MajorPivotLookback))
|
||||
lastMinorHighIdx = i;
|
||||
if (IsPivot(low, i, false, total, MajorPivotLookback))
|
||||
lastMinorLowIdx = i;
|
||||
|
||||
if (lastMinorHighIdx != -1 && i < lastMinorHighIdx && close[i] > high[lastMinorHighIdx])
|
||||
{
|
||||
if (lastMinorLowIdx != -1 && lastMinorLowIdx > lastMinorHighIdx)
|
||||
{
|
||||
bool stillValid = true;
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
if (close[j] < low[lastMinorLowIdx])
|
||||
{
|
||||
stillValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid)
|
||||
{
|
||||
outZone.zoneLow = low[lastMinorLowIdx];
|
||||
outZone.zoneHigh = open[lastMinorLowIdx];
|
||||
outZone.zoneTime = time[lastMinorLowIdx];
|
||||
outZone.barIndex = lastMinorLowIdx;
|
||||
outZone.isValid = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool FindNewMajorHigh(double &high[], double &low[], double &open[], double &close[], datetime &time[], int total, MajorZone &outZone)
|
||||
{
|
||||
int lastMinorHighIdx = -1;
|
||||
int lastMinorLowIdx = -1;
|
||||
|
||||
for (int i = total - MajorPivotLookback - 1; i >= MajorPivotLookback; i--)
|
||||
{
|
||||
if (IsPivot(high, i, true, total, MajorPivotLookback))
|
||||
lastMinorHighIdx = i;
|
||||
if (IsPivot(low, i, false, total, MajorPivotLookback))
|
||||
lastMinorLowIdx = i;
|
||||
|
||||
if (lastMinorLowIdx != -1 && i < lastMinorLowIdx && close[i] < low[lastMinorLowIdx])
|
||||
{
|
||||
if (lastMinorHighIdx != -1 && lastMinorHighIdx > lastMinorLowIdx)
|
||||
{
|
||||
bool stillValid = true;
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
if (close[j] > high[lastMinorHighIdx])
|
||||
{
|
||||
stillValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid)
|
||||
{
|
||||
outZone.zoneHigh = high[lastMinorHighIdx];
|
||||
outZone.zoneLow = open[lastMinorHighIdx];
|
||||
outZone.zoneTime = time[lastMinorHighIdx];
|
||||
outZone.barIndex = lastMinorHighIdx;
|
||||
outZone.isValid = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsPivot(double &data[], int idx, bool isHigh, int total, int lookback)
|
||||
{
|
||||
for (int j = 1; j <= lookback; j++)
|
||||
{
|
||||
if (idx - j < 0 || idx + j >= total)
|
||||
continue;
|
||||
if (isHigh)
|
||||
{
|
||||
if (data[idx] <= data[idx - j] || data[idx] <= data[idx + j])
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (data[idx] >= data[idx - j] || data[idx] >= data[idx + j])
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void ValidateStructure()
|
||||
{
|
||||
structureValid = false;
|
||||
|
||||
if (currentLowIndex >= 0 && currentHighIndex >= 0)
|
||||
{
|
||||
if (majorLows[currentLowIndex].isValid && majorHighs[currentHighIndex].isValid)
|
||||
{
|
||||
if (majorHighs[currentHighIndex].zoneHigh > majorLows[currentLowIndex].zoneHigh)
|
||||
{
|
||||
structureValid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
majorLows[currentLowIndex].isValid = false;
|
||||
majorHighs[currentHighIndex].isValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void DetermineBias()
|
||||
{
|
||||
if (!structureValid)
|
||||
{
|
||||
biasDirection = "NEUTRAL";
|
||||
return;
|
||||
}
|
||||
|
||||
if (majorHighs[currentHighIndex].barIndex < majorLows[currentLowIndex].barIndex)
|
||||
{
|
||||
biasDirection = "BEARISH";
|
||||
}
|
||||
else if (majorLows[currentLowIndex].barIndex < majorHighs[currentHighIndex].barIndex)
|
||||
{
|
||||
biasDirection = "BULLISH";
|
||||
}
|
||||
else
|
||||
{
|
||||
biasDirection = "NEUTRAL";
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void DrawAllMajorZones()
|
||||
{
|
||||
for (int i = 0; i < ArraySize(majorLows); i++)
|
||||
{
|
||||
string objName = mzPrefix + "Low_" + IntegerToString(i);
|
||||
|
||||
if (ObjectFind(0, objName) < 0)
|
||||
{
|
||||
ObjectCreate(0, objName, OBJ_RECTANGLE, 0, 0, 0, 0, 0);
|
||||
ObjectSetInteger(0, objName, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
color zoneColor = majorLows[i].isValid ? BullishZoneColor : InvalidZoneColor;
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, majorLows[i].zoneHigh);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, majorLows[i].zoneLow);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 0, majorLows[i].zoneTime);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 1, TimeCurrent());
|
||||
}
|
||||
|
||||
for (int i = 0; i < ArraySize(majorHighs); i++)
|
||||
{
|
||||
string objName = mzPrefix + "High_" + IntegerToString(i);
|
||||
|
||||
if (ObjectFind(0, objName) < 0)
|
||||
{
|
||||
ObjectCreate(0, objName, OBJ_RECTANGLE, 0, 0, 0, 0, 0);
|
||||
ObjectSetInteger(0, objName, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
color zoneColor = majorHighs[i].isValid ? BearishZoneColor : InvalidZoneColor;
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, majorHighs[i].zoneHigh);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, majorHighs[i].zoneLow);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 0, majorHighs[i].zoneTime);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 1, TimeCurrent());
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void CalculateQuartileZones()
|
||||
{
|
||||
double majorHigh = majorHighs[currentHighIndex].zoneHigh;
|
||||
double majorLow = majorLows[currentLowIndex].zoneLow;
|
||||
double range = majorHigh - majorLow;
|
||||
|
||||
equilibriumPrice = majorLow + (range * 0.50);
|
||||
premiumZone = majorLow + (range * 0.75);
|
||||
discountZone = majorLow + (range * 0.25);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void DrawQuartileZones()
|
||||
{
|
||||
string eqName = qzPrefix + "Equilibrium";
|
||||
if (ObjectFind(0, eqName) < 0)
|
||||
{
|
||||
ObjectCreate(0, eqName, OBJ_HLINE, 0, 0, equilibriumPrice);
|
||||
ObjectSetInteger(0, eqName, OBJPROP_COLOR, EquilibriumColor);
|
||||
ObjectSetInteger(0, eqName, OBJPROP_STYLE, STYLE_DASHDOT);
|
||||
ObjectSetInteger(0, eqName, OBJPROP_WIDTH, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectSetDouble(0, eqName, OBJPROP_PRICE, 0, equilibriumPrice);
|
||||
}
|
||||
|
||||
string topName = qzPrefix + "Top25";
|
||||
if (ObjectFind(0, topName) < 0)
|
||||
{
|
||||
ObjectCreate(0, topName, OBJ_HLINE, 0, 0, premiumZone);
|
||||
ObjectSetInteger(0, topName, OBJPROP_COLOR, clrDarkRed);
|
||||
ObjectSetInteger(0, topName, OBJPROP_STYLE, STYLE_DOT);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectSetDouble(0, topName, OBJPROP_PRICE, 0, premiumZone);
|
||||
}
|
||||
|
||||
string botName = qzPrefix + "Bottom25";
|
||||
if (ObjectFind(0, botName) < 0)
|
||||
{
|
||||
ObjectCreate(0, botName, OBJ_HLINE, 0, 0, discountZone);
|
||||
ObjectSetInteger(0, botName, OBJPROP_COLOR, clrDarkGreen);
|
||||
ObjectSetInteger(0, botName, OBJPROP_STYLE, STYLE_DOT);
|
||||
}
|
||||
else
|
||||
{
|
||||
ObjectSetDouble(0, botName, OBJPROP_PRICE, 0, discountZone);
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| پردازش مینورها: بررسی اعتبار + جستجوی جدید |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessMinorZones()
|
||||
{
|
||||
// 1. دریافت دادههای 15 دقیقه
|
||||
double high15m[], low15m[], open15m[], close15m[];
|
||||
datetime time15m[];
|
||||
|
||||
ArraySetAsSeries(high15m, true);
|
||||
ArraySetAsSeries(low15m, true);
|
||||
ArraySetAsSeries(open15m, true);
|
||||
ArraySetAsSeries(close15m, true);
|
||||
ArraySetAsSeries(time15m, true);
|
||||
|
||||
int copied15m = CopyHigh(_Symbol, PERIOD_M15, 0, 500, high15m);
|
||||
CopyLow(_Symbol, PERIOD_M15, 0, 500, low15m);
|
||||
CopyOpen(_Symbol, PERIOD_M15, 0, 500, open15m);
|
||||
CopyClose(_Symbol, PERIOD_M15, 0, 500, close15m);
|
||||
CopyTime(_Symbol, PERIOD_M15, 0, 500, time15m);
|
||||
|
||||
if (copied15m <= 0)
|
||||
return;
|
||||
|
||||
// 2. بررسی بیاعتباری مینورهای جاری
|
||||
CheckMinorZonesInvalidation(close15m);
|
||||
|
||||
// 3. بهروزرسانی مینورها (در هر تیک، چون 15 دقیقه سریعتر است)
|
||||
UpdateCurrentMinorZones(high15m, low15m, open15m, close15m, time15m, copied15m);
|
||||
|
||||
// 4. رسم تمام مینورها
|
||||
DrawAllMinorZones();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| بررسی بیاعتباری مینورهای جاری با Close کندل 15 دقیقه |
|
||||
//+------------------------------------------------------------------+
|
||||
void CheckMinorZonesInvalidation(double &close15m[])
|
||||
{
|
||||
// بررسی کف مینور جاری
|
||||
if (currentMinorLowIndex >= 0 && currentMinorLowIndex < ArraySize(minorLows))
|
||||
{
|
||||
if (minorLows[currentMinorLowIndex].isValid)
|
||||
{
|
||||
for (int i = 0; i <= minorLows[currentMinorLowIndex].barIndex; i++)
|
||||
{
|
||||
if (close15m[i] < minorLows[currentMinorLowIndex].zoneLow)
|
||||
{
|
||||
minorLows[currentMinorLowIndex].isValid = false;
|
||||
Print("Minor Low INVALIDATED at index ", currentMinorLowIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// بررسی سقف مینور جاری
|
||||
if (currentMinorHighIndex >= 0 && currentMinorHighIndex < ArraySize(minorHighs))
|
||||
{
|
||||
if (minorHighs[currentMinorHighIndex].isValid)
|
||||
{
|
||||
for (int i = 0; i <= minorHighs[currentMinorHighIndex].barIndex; i++)
|
||||
{
|
||||
if (close15m[i] > minorHighs[currentMinorHighIndex].zoneHigh)
|
||||
{
|
||||
minorHighs[currentMinorHighIndex].isValid = false;
|
||||
Print("Minor High INVALIDATED at index ", currentMinorHighIndex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| بهروزرسانی مینورها |
|
||||
//+------------------------------------------------------------------+
|
||||
void UpdateCurrentMinorZones(double &high15m[], double &low15m[], double &open15m[], double &close15m[], datetime &time15m[], int total)
|
||||
{
|
||||
// مدیریت کفهای مینور (فقط در بایاس صعودی)
|
||||
if (biasDirection == "BULLISH")
|
||||
{
|
||||
bool needNewMinorLow = (currentMinorLowIndex == -1 || !minorLows[currentMinorLowIndex].isValid);
|
||||
if (needNewMinorLow)
|
||||
{
|
||||
bool foundPrevious = false;
|
||||
for (int i = ArraySize(minorLows) - 1; i >= 0; i--)
|
||||
{
|
||||
if (minorLows[i].isValid)
|
||||
{
|
||||
currentMinorLowIndex = i;
|
||||
foundPrevious = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPrevious)
|
||||
{
|
||||
MinorZone newMinorLow;
|
||||
if (FindNewMinorLow(high15m, low15m, open15m, close15m, time15m, total, newMinorLow))
|
||||
{
|
||||
int newSize = ArraySize(minorLows);
|
||||
ArrayResize(minorLows, newSize + 1);
|
||||
minorLows[newSize] = newMinorLow;
|
||||
currentMinorLowIndex = newSize;
|
||||
Print("New Minor Low Zone found at index: ", newSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMinorLowIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// مدیریت سقفهای مینور (فقط در بایاس نزولی)
|
||||
if (biasDirection == "BEARISH")
|
||||
{
|
||||
bool needNewMinorHigh = (currentMinorHighIndex == -1 || !minorHighs[currentMinorHighIndex].isValid);
|
||||
if (needNewMinorHigh)
|
||||
{
|
||||
bool foundPrevious = false;
|
||||
for (int i = ArraySize(minorHighs) - 1; i >= 0; i--)
|
||||
{
|
||||
if (minorHighs[i].isValid)
|
||||
{
|
||||
currentMinorHighIndex = i;
|
||||
foundPrevious = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPrevious)
|
||||
{
|
||||
MinorZone newMinorHigh;
|
||||
if (FindNewMinorHigh(high15m, low15m, open15m, close15m, time15m, total, newMinorHigh))
|
||||
{
|
||||
int newSize = ArraySize(minorHighs);
|
||||
ArrayResize(minorHighs, newSize + 1);
|
||||
minorHighs[newSize] = newMinorHigh;
|
||||
currentMinorHighIndex = newSize;
|
||||
Print("New Minor High Zone found at index: ", newSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentMinorHighIndex = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| پیدا کردن کف مینور جدید (ناحیه Wick-to-Body در Discount) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool FindNewMinorLow(double &high[], double &low[], double &open[], double &close[], datetime &time[], int total, MinorZone &outZone)
|
||||
{
|
||||
for (int i = 1; i < total - MinorWickLookback; i++)
|
||||
{
|
||||
double bodyTop = MathMax(open[i], close[i]);
|
||||
double bodyBot = MathMin(open[i], close[i]);
|
||||
double bodySize = bodyTop - bodyBot;
|
||||
|
||||
double lowerWick = bodyBot - low[i];
|
||||
|
||||
// شرط 1: شدوی پایینی بلند (حداقل 2 برابر بدنه)
|
||||
// شرط 2: نوک شدو در ناحیه Discount (زیر 50%)
|
||||
// شرط 3: بدنه بالای 50% بسته شده
|
||||
if (lowerWick > 0 && bodySize > 0 && (lowerWick / bodySize) >= WickToBodyRatio)
|
||||
{
|
||||
if (low[i] < equilibriumPrice && bodyBot > equilibriumPrice)
|
||||
{
|
||||
// بررسی اعتبار: آیا از زمان تشکیل تا الان شکسته شده؟
|
||||
bool stillValid = true;
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
if (close[j] < low[i])
|
||||
{
|
||||
stillValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid)
|
||||
{
|
||||
outZone.zoneLow = low[i]; // نوک شدو
|
||||
outZone.zoneHigh = open[i]; // ابتدای بدنه (Wick-to-Body)
|
||||
outZone.zoneTime = time[i];
|
||||
outZone.barIndex = i;
|
||||
outZone.isValid = true;
|
||||
outZone.isLongSignal = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| پیدا کردن سقف مینور جدید (ناحیه Wick-to-Body در Premium) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool FindNewMinorHigh(double &high[], double &low[], double &open[], double &close[], datetime &time[], int total, MinorZone &outZone)
|
||||
{
|
||||
for (int i = 1; i < total - MinorWickLookback; i++)
|
||||
{
|
||||
double bodyTop = MathMax(open[i], close[i]);
|
||||
double bodyBot = MathMin(open[i], close[i]);
|
||||
double bodySize = bodyTop - bodyBot;
|
||||
|
||||
double upperWick = high[i] - bodyTop;
|
||||
|
||||
// شرط 1: شدوی بالایی بلند
|
||||
// شرط 2: نوک شدو در ناحیه Premium (بالای 50%)
|
||||
// شرط 3: بدنه زیر 50% بسته شده
|
||||
if (upperWick > 0 && bodySize > 0 && (upperWick / bodySize) >= WickToBodyRatio)
|
||||
{
|
||||
if (high[i] > equilibriumPrice && bodyTop < equilibriumPrice)
|
||||
{
|
||||
bool stillValid = true;
|
||||
for (int j = 0; j <= i; j++)
|
||||
{
|
||||
if (close[j] > high[i])
|
||||
{
|
||||
stillValid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (stillValid)
|
||||
{
|
||||
outZone.zoneHigh = high[i]; // نوک شدو
|
||||
outZone.zoneLow = open[i]; // ابتدای بدنه (Wick-to-Body)
|
||||
outZone.zoneTime = time[i];
|
||||
outZone.barIndex = i;
|
||||
outZone.isValid = true;
|
||||
outZone.isLongSignal = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| رسم تمام مینورها (معتبر و نامعتبر) |
|
||||
//+------------------------------------------------------------------+
|
||||
void DrawAllMinorZones()
|
||||
{
|
||||
// رسم کفهای مینور
|
||||
for (int i = 0; i < ArraySize(minorLows); i++)
|
||||
{
|
||||
string objName = mnPrefix + "Low_" + IntegerToString(i);
|
||||
|
||||
if (ObjectFind(0, objName) < 0)
|
||||
{
|
||||
ObjectCreate(0, objName, OBJ_RECTANGLE, 0, 0, 0, 0, 0);
|
||||
ObjectSetInteger(0, objName, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
color zoneColor = minorLows[i].isValid ? MinorLowZoneColor : MinorInvalidColor;
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, minorLows[i].zoneHigh);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, minorLows[i].zoneLow);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 0, minorLows[i].zoneTime);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 1, TimeCurrent());
|
||||
}
|
||||
|
||||
// رسم سقفهای مینور
|
||||
for (int i = 0; i < ArraySize(minorHighs); i++)
|
||||
{
|
||||
string objName = mnPrefix + "High_" + IntegerToString(i);
|
||||
|
||||
if (ObjectFind(0, objName) < 0)
|
||||
{
|
||||
ObjectCreate(0, objName, OBJ_RECTANGLE, 0, 0, 0, 0, 0);
|
||||
ObjectSetInteger(0, objName, OBJPROP_FILL, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
|
||||
ObjectSetInteger(0, objName, OBJPROP_SELECTABLE, false);
|
||||
}
|
||||
|
||||
color zoneColor = minorHighs[i].isValid ? MinorHighZoneColor : MinorInvalidColor;
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, zoneColor);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 0, minorHighs[i].zoneHigh);
|
||||
ObjectSetDouble(0, objName, OBJPROP_PRICE, 1, minorHighs[i].zoneLow);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 0, minorHighs[i].zoneTime);
|
||||
ObjectSetInteger(0, objName, OBJPROP_TIME, 1, TimeCurrent());
|
||||
}
|
||||
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
void DisplayDashboard()
|
||||
{
|
||||
string dash = "\n========== COMPLETE STRUCTURE (v4.0) ==========\n";
|
||||
dash += "Symbol: " + _Symbol + "\n";
|
||||
dash += "------------------------------------------------------\n";
|
||||
|
||||
if (structureValid)
|
||||
{
|
||||
dash += "MAJOR HIGH: " + DoubleToString(majorHighs[currentHighIndex].zoneHigh, _Digits) + "\n";
|
||||
dash += "MAJOR LOW: " + DoubleToString(majorLows[currentLowIndex].zoneLow, _Digits) + "\n";
|
||||
dash += "------------------------------------------------------\n";
|
||||
dash += "Equilibrium (50%): " + DoubleToString(equilibriumPrice, _Digits) + "\n";
|
||||
dash += "Premium (75%): " + DoubleToString(premiumZone, _Digits) + "\n";
|
||||
dash += "Discount (25%): " + DoubleToString(discountZone, _Digits) + "\n";
|
||||
dash += "------------------------------------------------------\n";
|
||||
dash += "BIAS: " + biasDirection + "\n";
|
||||
dash += "------------------------------------------------------\n";
|
||||
|
||||
if (currentMinorLowIndex >= 0)
|
||||
{
|
||||
dash += "Current Minor Low: " + (minorLows[currentMinorLowIndex].isValid ? "✓ VALID" : " INVALID") + "\n";
|
||||
dash += " Zone: " + DoubleToString(minorLows[currentMinorLowIndex].zoneLow, _Digits) +
|
||||
" - " + DoubleToString(minorLows[currentMinorLowIndex].zoneHigh, _Digits) + "\n";
|
||||
}
|
||||
|
||||
if (currentMinorHighIndex >= 0)
|
||||
{
|
||||
dash += "Current Minor High: " + (minorHighs[currentMinorHighIndex].isValid ? "✓ VALID" : "✗ INVALID") + "\n";
|
||||
dash += " Zone: " + DoubleToString(minorHighs[currentMinorHighIndex].zoneLow, _Digits) +
|
||||
" - " + DoubleToString(minorHighs[currentMinorHighIndex].zoneHigh, _Digits) + "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dash += "Structure: INVALID - Waiting for valid Major Zones\n";
|
||||
}
|
||||
|
||||
dash += "======================================================\n";
|
||||
|
||||
Comment(dash);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
@@ -1,360 +0,0 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| SmartShadowScanner.mq5 |
|
||||
//| Advanced Shadow Scanner with 3-Step Checklist Filter |
|
||||
//+------------------------------------------------------------------+
|
||||
#property copyright "Professional Trader Setup 2026"
|
||||
#property version "3.00"
|
||||
#property description "Scans for large rejection shadows filtered by Sweep, POI, and Session"
|
||||
|
||||
//--- User Inputs
|
||||
input group "=== Base Shadow Settings ==="
|
||||
input double ShadowMultiplier = 2.0; // Shadow to Body Multiplier (e.g., 2.0 means shadow is 2x body)
|
||||
input double MinRangeATR_Multi = 0.5; // Min Candle Range as ATR Multiplier (0.5 = 50% of ATR)
|
||||
input int MinCandleSizePoints = 30; // Min Absolute Candle Size in Points (Filters micro-candles)
|
||||
input ENUM_TIMEFRAMES Timeframe = PERIOD_H4; // Timeframe to Analyze
|
||||
|
||||
input group "=== Filter 1: Liquidity Sweep ==="
|
||||
input bool EnableSweepFilter = true; // Enable Liquidity Sweep Filter
|
||||
input int SwingLookback = 10; // Swing Lookback Period (Left & Right candles)
|
||||
|
||||
input group "=== Filter 2: Point of Interest (POI) ==="
|
||||
input bool EnablePOIFilter = true; // Enable POI Filter
|
||||
input int POI_ProximityPoints = 50; // Allowed Distance to POI in Points
|
||||
input bool CheckPDH_PDL = true; // Check Previous Day High/Low
|
||||
input bool CheckOrderBlock = true; // Check Recent Order Blocks
|
||||
|
||||
input group "=== Filter 3: Trading Session ==="
|
||||
input bool EnableSessionFilter = true; // Enable Session Time Filter
|
||||
input int LondonStartHour = 8; // London Start Hour (Server Time)
|
||||
input int LondonEndHour = 12; // London End Hour (Server Time)
|
||||
input int NYStartHour = 13; // New York Start Hour (Server Time)
|
||||
input int NYEndHour = 18; // New York End Hour (Server Time)
|
||||
|
||||
input group "=== Display & Alert Settings ==="
|
||||
input bool DrawArrows = true; // Draw Arrows on Chart
|
||||
input bool ShowAlerts = true; // Show Popup Alerts
|
||||
input bool SendPush = false; // Send Push Notifications
|
||||
input color BullishArrowColor = clrLime; // Bullish Arrow Color (Lower Shadow)
|
||||
input color BearishArrowColor = clrRed; // Bearish Arrow Color (Upper Shadow)
|
||||
input int ArrowDistancePoints = 10; // Arrow Distance from Shadow Tip (Points)
|
||||
|
||||
//--- Global Variables
|
||||
datetime lastBarTime = 0;
|
||||
int atrHandle = INVALID_HANDLE;
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert initialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
int OnInit()
|
||||
{
|
||||
// Initialize ATR indicator handle for volatility filtering
|
||||
atrHandle = iATR(_Symbol, Timeframe, 14);
|
||||
if(atrHandle == INVALID_HANDLE)
|
||||
{
|
||||
Print("Error creating ATR indicator handle!");
|
||||
return(INIT_FAILED);
|
||||
}
|
||||
|
||||
Print("=== Smart Shadow Scanner v3.0 Initialized ===");
|
||||
Print("Sweep Filter: ", EnableSweepFilter ? "Enabled" : "Disabled");
|
||||
Print("POI Filter: ", EnablePOIFilter ? "Enabled" : "Disabled");
|
||||
Print("Session Filter: ", EnableSessionFilter ? "Enabled" : "Disabled");
|
||||
return(INIT_SUCCEEDED);
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert deinitialization function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnDeinit(const int reason)
|
||||
{
|
||||
// Clean up chart objects and release indicator memory
|
||||
ObjectsDeleteAll(0, "ShadowArrow_");
|
||||
IndicatorRelease(atrHandle);
|
||||
ChartRedraw();
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Expert tick function |
|
||||
//+------------------------------------------------------------------+
|
||||
void OnTick()
|
||||
{
|
||||
// Process only on new bar close to avoid redundant calculations
|
||||
datetime currentBarTime = iTime(_Symbol, Timeframe, 0);
|
||||
if(currentBarTime == lastBarTime) return;
|
||||
lastBarTime = currentBarTime;
|
||||
|
||||
// Copy required bars (Need enough history for Swing Lookback)
|
||||
int barsNeeded = SwingLookback * 2 + 10;
|
||||
MqlRates rates[];
|
||||
ArraySetAsSeries(rates, true);
|
||||
if(CopyRates(_Symbol, Timeframe, 0, barsNeeded, rates) < barsNeeded) return;
|
||||
|
||||
// Copy ATR value for the closed candle (index 1)
|
||||
double atrBuffer[];
|
||||
ArraySetAsSeries(atrBuffer, true);
|
||||
if(CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) < 1) return;
|
||||
double currentATR = atrBuffer[0];
|
||||
|
||||
// Extract OHLC data for the target candle (index 1)
|
||||
double openPrice = rates[1].open;
|
||||
double highPrice = rates[1].high;
|
||||
double lowPrice = rates[1].low;
|
||||
double closePrice = rates[1].close;
|
||||
datetime barTime = rates[1].time;
|
||||
|
||||
// Calculate candle metrics
|
||||
double body = MathAbs(openPrice - closePrice);
|
||||
double upperShadow = highPrice - MathMax(openPrice, closePrice);
|
||||
double lowerShadow = MathMin(openPrice, closePrice) - lowPrice;
|
||||
double totalRange = highPrice - lowPrice;
|
||||
|
||||
// ==========================================
|
||||
// Base Filters (Volatility & Size)
|
||||
// ==========================================
|
||||
// Filter 1: Absolute minimum size (ignores dead-market micro candles)
|
||||
if(totalRange < MinCandleSizePoints * _Point) return;
|
||||
|
||||
// Filter 2: Dynamic ATR filter (candle must be at least X% of average range)
|
||||
if(totalRange < (currentATR * MinRangeATR_Multi)) return;
|
||||
|
||||
// Identify shadow type
|
||||
bool isBullishShadow = (lowerShadow >= (ShadowMultiplier * body) && lowerShadow > upperShadow);
|
||||
bool isBearishShadow = (upperShadow >= (ShadowMultiplier * body) && upperShadow > lowerShadow);
|
||||
|
||||
if(!isBullishShadow && !isBearishShadow) return;
|
||||
|
||||
// ==========================================
|
||||
// Advanced 3-Step Checklist
|
||||
// ==========================================
|
||||
bool passedSweep = !EnableSweepFilter || CheckLiquiditySweep(rates, isBullishShadow);
|
||||
bool passedPOI = !EnablePOIFilter || CheckNearPOI(rates, isBullishShadow, barTime);
|
||||
bool passedSession = !EnableSessionFilter || CheckSession(barTime);
|
||||
|
||||
// Trigger final signal only if ALL enabled filters are passed
|
||||
if(passedSweep && passedPOI && passedSession)
|
||||
{
|
||||
ProcessSignal(isBullishShadow, barTime, isBullishShadow ? lowPrice : highPrice);
|
||||
}
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 1: Check for Liquidity Sweep |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckLiquiditySweep(const MqlRates &rates[], bool isBullish)
|
||||
{
|
||||
int targetIndex = 1; // Target candle is at index 1
|
||||
|
||||
if(isBullish) // For lower shadow, it must have swept a previous Swing Low
|
||||
{
|
||||
for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++)
|
||||
{
|
||||
if(IsSwingLow(rates, i, SwingLookback))
|
||||
{
|
||||
double swingLow = rates[i].low;
|
||||
// Sweep condition: Wick pierces the swing low, but body closes above it
|
||||
if(rates[targetIndex].low < swingLow && rates[targetIndex].close > swingLow)
|
||||
{
|
||||
return true; // Valid liquidity sweep detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else // For upper shadow, it must have swept a previous Swing High
|
||||
{
|
||||
for(int i = targetIndex + 2; i < ArraySize(rates) - SwingLookback; i++)
|
||||
{
|
||||
if(IsSwingHigh(rates, i, SwingLookback))
|
||||
{
|
||||
double swingHigh = rates[i].high;
|
||||
// Sweep condition: Wick pierces the swing high, but body closes below it
|
||||
if(rates[targetIndex].high > swingHigh && rates[targetIndex].close < swingHigh)
|
||||
{
|
||||
return true; // Valid liquidity sweep detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // No valid sweep found
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Swing High |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSwingHigh(const MqlRates &rates[], int index, int lookback)
|
||||
{
|
||||
if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false;
|
||||
|
||||
double high = rates[index].high;
|
||||
for(int i = 1; i <= lookback; i++)
|
||||
{
|
||||
if(rates[index - i].high >= high) return false;
|
||||
if(rates[index + i].high >= high) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Swing Low |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsSwingLow(const MqlRates &rates[], int index, int lookback)
|
||||
{
|
||||
if(index - lookback < 0 || index + lookback >= ArraySize(rates)) return false;
|
||||
|
||||
double low = rates[index].low;
|
||||
for(int i = 1; i <= lookback; i++)
|
||||
{
|
||||
if(rates[index - i].low <= low) return false;
|
||||
if(rates[index + i].low <= low) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 2: Check Proximity to Point of Interest (POI) |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckNearPOI(const MqlRates &rates[], bool isBullish, datetime barTime)
|
||||
{
|
||||
double currentPrice = isBullish ? rates[1].low : rates[1].high;
|
||||
double proximity = POI_ProximityPoints * _Point;
|
||||
|
||||
// POI Type 1: Previous Day High / Previous Day Low (PDH/PDL)
|
||||
if(CheckPDH_PDL)
|
||||
{
|
||||
MqlRates dayRates[];
|
||||
ArraySetAsSeries(dayRates, true);
|
||||
|
||||
// Fetch previous daily candle data
|
||||
if(CopyRates(_Symbol, PERIOD_D1, 1, 1, dayRates) >= 1)
|
||||
{
|
||||
double pdh = dayRates[0].high;
|
||||
double pdl = dayRates[0].low;
|
||||
|
||||
if(isBullish && MathAbs(currentPrice - pdl) <= proximity) return true; // Near PDL
|
||||
if(!isBullish && MathAbs(currentPrice - pdh) <= proximity) return true; // Near PDH
|
||||
}
|
||||
}
|
||||
|
||||
// POI Type 2: Recent Order Blocks (OB)
|
||||
if(CheckOrderBlock)
|
||||
{
|
||||
// Scan the last 20 candles for Order Blocks
|
||||
for(int i = 2; i < MathMin(20, ArraySize(rates) - 1); i++)
|
||||
{
|
||||
// Bullish OB: Bearish candle followed by a strong bullish displacement
|
||||
if(isBullish && IsBullishOrderBlock(rates, i))
|
||||
{
|
||||
double obLow = rates[i].low;
|
||||
double obHigh = rates[i].high;
|
||||
if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Bearish OB: Bullish candle followed by a strong bearish displacement
|
||||
if(!isBullish && IsBearishOrderBlock(rates, i))
|
||||
{
|
||||
double obLow = rates[i].low;
|
||||
double obHigh = rates[i].high;
|
||||
if(currentPrice >= (obLow - proximity) && currentPrice <= (obHigh + proximity))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Not near any valid POI
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Bullish Order Block |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsBullishOrderBlock(const MqlRates &rates[], int index)
|
||||
{
|
||||
// Current candle must be bearish
|
||||
if(rates[index].close >= rates[index].open) return false;
|
||||
|
||||
if(index - 1 < 0) return false;
|
||||
|
||||
// Next candle (index-1) must be a strong bullish displacement
|
||||
double nextBody = MathAbs(rates[index-1].close - rates[index-1].open);
|
||||
double nextRange = rates[index-1].high - rates[index-1].low;
|
||||
|
||||
if(rates[index-1].close <= rates[index-1].open) return false; // Must be bullish
|
||||
if(nextBody < (nextRange * 0.6)) return false; // Body must be at least 60% of total range
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Identify Bearish Order Block |
|
||||
//+------------------------------------------------------------------+
|
||||
bool IsBearishOrderBlock(const MqlRates &rates[], int index)
|
||||
{
|
||||
// Current candle must be bullish
|
||||
if(rates[index].close <= rates[index].open) return false;
|
||||
|
||||
if(index - 1 < 0) return false;
|
||||
|
||||
// Next candle (index-1) must be a strong bearish displacement
|
||||
double nextBody = MathAbs(rates[index-1].close - rates[index-1].open);
|
||||
double nextRange = rates[index-1].high - rates[index-1].low;
|
||||
|
||||
if(rates[index-1].close >= rates[index-1].open) return false; // Must be bearish
|
||||
if(nextBody < (nextRange * 0.6)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Filter 3: Check Trading Session Time |
|
||||
//+------------------------------------------------------------------+
|
||||
bool CheckSession(datetime barTime)
|
||||
{
|
||||
MqlDateTime dt;
|
||||
TimeToStruct(barTime, dt);
|
||||
int hour = dt.hour;
|
||||
|
||||
// London Session
|
||||
if(hour >= LondonStartHour && hour < LondonEndHour) return true;
|
||||
|
||||
// New York Session
|
||||
if(hour >= NYStartHour && hour < NYEndHour) return true;
|
||||
|
||||
return false; // Outside active sessions (e.g., Asian session)
|
||||
}
|
||||
|
||||
//+------------------------------------------------------------------+
|
||||
//| Process Final Signal (Draw, Alert, Push) |
|
||||
//+------------------------------------------------------------------+
|
||||
void ProcessSignal(bool isBullish, datetime barTime, double extremePrice)
|
||||
{
|
||||
string direction = isBullish ? "BULLISH" : "BEARISH";
|
||||
string message = StringFormat("✅ Golden %s Signal | %s | Price: %s",
|
||||
direction,
|
||||
TimeToString(barTime, TIME_DATE|TIME_MINUTES),
|
||||
DoubleToString(extremePrice, _Digits));
|
||||
|
||||
// 1. Draw Arrow on Chart
|
||||
if(DrawArrows)
|
||||
{
|
||||
string objName = "ShadowArrow_" + IntegerToString(barTime);
|
||||
int arrowCode = isBullish ? 233 : 234; // 233: Up arrow, 234: Down arrow
|
||||
color arrowColor = isBullish ? BullishArrowColor : BearishArrowColor;
|
||||
double arrowPrice = isBullish ?
|
||||
(extremePrice - ArrowDistancePoints * _Point) :
|
||||
(extremePrice + ArrowDistancePoints * _Point);
|
||||
|
||||
ObjectCreate(0, objName, OBJ_ARROW, 0, barTime, arrowPrice);
|
||||
ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, arrowCode);
|
||||
ObjectSetInteger(0, objName, OBJPROP_COLOR, arrowColor);
|
||||
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 3);
|
||||
ObjectSetInteger(0, objName, OBJPROP_ANCHOR, isBullish ? ANCHOR_TOP : ANCHOR_BOTTOM);
|
||||
}
|
||||
|
||||
// 2. Trigger Alerts
|
||||
if(ShowAlerts) Alert(message);
|
||||
if(SendPush) SendNotification(message);
|
||||
|
||||
// 3. Print to Experts Log
|
||||
Print(message);
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user