736 lines
25 KiB
Text
736 lines
25 KiB
Text
//+------------------------------------------------------------------+
| |||
//| SMC_OrderBlocks_TrendDiv.mq5 |
| |||
//| Order Blocks + 3-Touch Trendlines + Divergence |
| |||
//+------------------------------------------------------------------+
| |||
#property copyright "HorizonAI"
| |||
#property link ""
| |||
#property version "1.00"
| |||
#property indicator_chart_window
| |||
#property indicator_buffers 0
| |||
#property indicator_plots 0
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| INPUTS |
| |||
//+------------------------------------------------------------------+
| |||
input group "=== Order Blocks ==="
| |||
input int OB_ImpulseBars = 3; // Min impulse candles for OB
| |||
input int OB_MaxDisplay = 10; // Max OBs displayed
| |||
input bool OB_BodyOnly = false; // Body-only zones
| |||
input color OB_BullColor = clrDodgerBlue;
| |||
input color OB_BearColor = clrTomato;
| |||
input color OB_MitigatedColor = clrGray;
| |||
| |||
input group "=== Trendlines (3-Touch) ==="
| |||
input int TL_LeftBars = 5; // Pivot left bars
| |||
input int TL_RightBars = 3; // Pivot right bars
| |||
input int TL_MinTouches = 3; // Minimum touches to draw
| |||
input int TL_MaxLines = 5; // Max trendlines per TF
| |||
input color TL_UptrendColor = clrLime;
| |||
input color TL_DowntrendColor = clrRed;
| |||
input int TL_LineWidth = 2;
| |||
| |||
input group "=== RSI Divergence ==="
| |||
input int RSI_Period = 14; // RSI period
| |||
input int RSI_LeftBars = 5; // Pivot left bars for divergence
| |||
input int RSI_RightBars = 3; // Pivot right bars for divergence
| |||
input int RSI_MaxSignals = 10; // Max divergence signals
| |||
input color RSI_BullColor = clrLime;
| |||
input color RSI_BearColor = clrRed;
| |||
| |||
input group "=== Multi-Timeframe ==="
| |||
input bool Show_M15 = true; // Show M15 trendlines
| |||
input bool Show_H1 = true; // Show H1 trendlines
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| STRUCTS |
| |||
//+------------------------------------------------------------------+
| |||
struct OrderBlock
| |||
{
| |||
double top;
| |||
double bottom;
| |||
datetime time;
| |||
bool isBullish;
| |||
bool mitigated;
| |||
string objName;
| |||
};
| |||
| |||
struct PivotPoint
| |||
{
| |||
double price;
| |||
int barIndex;
| |||
datetime time;
| |||
};
| |||
| |||
struct Trendline
| |||
{
| |||
datetime time1;
| |||
double price1;
| |||
datetime time2;
| |||
double price2;
| |||
bool isUptrend;
| |||
int touches;
| |||
string objName;
| |||
};
| |||
| |||
struct DivergenceSignal
| |||
{
| |||
datetime time1;
| |||
double price1;
| |||
datetime time2;
| |||
double price2;
| |||
bool isBullish;
| |||
string lineName;
| |||
string arrowName;
| |||
};
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| GLOBALS |
| |||
//+------------------------------------------------------------------+
| |||
OrderBlock g_OBs[];
| |||
Trendline g_TL_M15[];
| |||
Trendline g_TL_H1[];
| |||
DivergenceSignal g_DivSignals[];
| |||
| |||
int g_rsiHandle;
| |||
int g_lastProcessedBar = 0;
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| INIT |
| |||
//+------------------------------------------------------------------+
| |||
int OnInit()
| |||
{
| |||
// Create RSI handle
| |||
g_rsiHandle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
| |||
if(g_rsiHandle == INVALID_HANDLE)
| |||
{
| |||
Print("Failed to create RSI handle");
| |||
return INIT_FAILED;
| |||
}
| |||
| |||
// Clean up any leftover objects
| |||
ObjectsDeleteAll(0, "OB_");
| |||
ObjectsDeleteAll(0, "TL_M15_");
| |||
ObjectsDeleteAll(0, "TL_H1_");
| |||
ObjectsDeleteAll(0, "DIV_");
| |||
| |||
return INIT_SUCCEEDED;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| DEINIT |
| |||
//+------------------------------------------------------------------+
| |||
void OnDeinit(const int reason)
| |||
{
| |||
ObjectsDeleteAll(0, "OB_");
| |||
ObjectsDeleteAll(0, "TL_M15_");
| |||
ObjectsDeleteAll(0, "TL_H1_");
| |||
ObjectsDeleteAll(0, "DIV_");
| |||
if(g_rsiHandle != INVALID_HANDLE)
| |||
IndicatorRelease(g_rsiHandle);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ON 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 < 50) return rates_total;
| |||
| |||
int start = (prev_calculated < 50) ? 50 : prev_calculated - 1;
| |||
| |||
//--- Order Blocks Detection
| |||
DetectOrderBlocks(rates_total, start, time, open, high, low, close);
| |||
UpdateOrderBlocks(rates_total, high, low, close, time);
| |||
| |||
//--- Trendlines on M15
| |||
if(Show_M15)
| |||
ProcessTrendlines(PERIOD_M15, "M15", g_TL_M15, TL_MaxLines);
| |||
| |||
//--- Trendlines on H1
| |||
if(Show_H1)
| |||
ProcessTrendlines(PERIOD_H1, "H1", g_TL_H1, TL_MaxLines);
| |||
| |||
//--- RSI Divergence
| |||
DetectRSIDivergence(rates_total, start, time, high, low, close);
| |||
| |||
return rates_total;
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ORDER BLOCK DETECTION |
| |||
//+------------------------------------------------------------------+
| |||
void DetectOrderBlocks(const int rates_total, int start,
| |||
const datetime &time[],
| |||
const double &open[],
| |||
const double &high[],
| |||
const double &low[],
| |||
const double &close[])
| |||
{
| |||
for(int i = start; i < rates_total - OB_ImpulseBars; i++)
| |||
{
| |||
//--- Check for bullish impulse (3+ consecutive bullish candles)
| |||
bool bullImpulse = true;
| |||
for(int k = 0; k < OB_ImpulseBars; k++)
| |||
{
| |||
if(close[i + k] <= open[i + k])
| |||
{
| |||
bullImpulse = false;
| |||
break;
| |||
}
| |||
}
| |||
| |||
if(bullImpulse)
| |||
{
| |||
// Find last bearish candle before impulse
| |||
for(int j = i - 1; j >= MathMax(0, i - 10); j--)
| |||
{
| |||
if(close[j] < open[j]) // Bearish candle = Bullish OB
| |||
{
| |||
AddOrderBlock(high[j], low[j], time[j], true);
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
| |||
//--- Check for bearish impulse
| |||
bool bearImpulse = true;
| |||
for(int k = 0; k < OB_ImpulseBars; k++)
| |||
{
| |||
if(close[i + k] >= open[i + k])
| |||
{
| |||
bearImpulse = false;
| |||
break;
| |||
}
| |||
}
| |||
| |||
if(bearImpulse)
| |||
{
| |||
// Find last bullish candle before impulse
| |||
for(int j = i - 1; j >= MathMax(0, i - 10); j--)
| |||
{
| |||
if(close[j] > open[j]) // Bullish candle = Bearish OB
| |||
{
| |||
AddOrderBlock(high[j], low[j], time[j], false);
| |||
break;
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ADD ORDER BLOCK |
| |||
//+------------------------------------------------------------------+
| |||
void AddOrderBlock(double top, double bottom, datetime time, bool isBullish)
| |||
{
| |||
// Check if OB already exists at this time
| |||
for(int i = 0; i < ArraySize(g_OBs); i++)
| |||
{
| |||
if(g_OBs[i].time == time && g_OBs[i].isBullish == isBullish)
| |||
return;
| |||
}
| |||
| |||
int idx = ArraySize(g_OBs);
| |||
ArrayResize(g_OBs, idx + 1);
| |||
| |||
g_OBs[idx].top = top;
| |||
g_OBs[idx].bottom = bottom;
| |||
g_OBs[idx].time = time;
| |||
g_OBs[idx].isBullish = isBullish;
| |||
g_OBs[idx].mitigated = false;
| |||
g_OBs[idx].objName = "OB_" + (isBullish ? "Bull_" : "Bear_") + IntegerToString(idx);
| |||
| |||
// Draw rectangle
| |||
datetime endTime = TimeCurrent() + PeriodSeconds() * 100;
| |||
color obColor = isBullish ? OB_BullColor : OB_BearColor;
| |||
| |||
if(ObjectFind(0, g_OBs[idx].objName) < 0)
| |||
{
| |||
ObjectCreate(0, g_OBs[idx].objName, OBJ_RECTANGLE, 0, time, top, endTime, bottom);
| |||
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_COLOR, obColor);
| |||
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_FILL, true);
| |||
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_BACK, true);
| |||
ObjectSetInteger(0, g_OBs[idx].objName, OBJPROP_WIDTH, 1);
| |||
}
| |||
| |||
// Enforce max display
| |||
EnforceMaxOBs();
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| UPDATE ORDER BLOCKS (mitigation check) |
| |||
//+------------------------------------------------------------------+
| |||
void UpdateOrderBlocks(const int rates_total,
| |||
const double &high[],
| |||
const double &low[],
| |||
const double &close[],
| |||
const datetime &time[])
| |||
{
| |||
for(int i = 0; i < ArraySize(g_OBs); i++)
| |||
{
| |||
if(g_OBs[i].mitigated) continue;
| |||
| |||
int lastBar = rates_total - 1;
| |||
| |||
// Bullish OB mitigated when price closes below bottom
| |||
if(g_OBs[i].isBullish && close[lastBar] < g_OBs[i].bottom)
| |||
{
| |||
g_OBs[i].mitigated = true;
| |||
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_COLOR, OB_MitigatedColor);
| |||
}
| |||
// Bearish OB mitigated when price closes above top
| |||
else if(!g_OBs[i].isBullish && close[lastBar] > g_OBs[i].top)
| |||
{
| |||
g_OBs[i].mitigated = true;
| |||
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_COLOR, OB_MitigatedColor);
| |||
}
| |||
| |||
// Extend rectangle right
| |||
if(!g_OBs[i].mitigated)
| |||
{
| |||
ObjectSetInteger(0, g_OBs[i].objName, OBJPROP_TIME, 1, TimeCurrent() + PeriodSeconds() * 100);
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ENFORCE MAX OBs |
| |||
//+------------------------------------------------------------------+
| |||
void EnforceMaxOBs()
| |||
{
| |||
int size = ArraySize(g_OBs);
| |||
if(size <= OB_MaxDisplay) return;
| |||
| |||
// Remove oldest mitigated OBs first
| |||
for(int i = 0; i < size && ArraySize(g_OBs) > OB_MaxDisplay; i++)
| |||
{
| |||
if(g_OBs[i].mitigated)
| |||
{
| |||
ObjectDelete(0, g_OBs[i].objName);
| |||
ArrayRemove(g_OBs, i, 1);
| |||
i--;
| |||
size--;
| |||
}
| |||
}
| |||
| |||
// If still over limit, remove oldest
| |||
while(ArraySize(g_OBs) > OB_MaxDisplay)
| |||
{
| |||
ObjectDelete(0, g_OBs[0].objName);
| |||
ArrayRemove(g_OBs, 0, 1);
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| PROCESS TRENDLINES FOR A TIMEFRAME |
| |||
//+------------------------------------------------------------------+
| |||
void ProcessTrendlines(ENUM_TIMEFRAMES tf, string tfPrefix,
| |||
Trendline &tlArray[], int maxLines)
| |||
{
| |||
// Get data from the specified timeframe
| |||
int bars = iBars(_Symbol, tf);
| |||
if(bars < 50) return;
| |||
| |||
double high[], low[], close[];
| |||
datetime time[];
| |||
| |||
ArrayResize(high, bars);
| |||
ArrayResize(low, bars);
| |||
ArrayResize(close, bars);
| |||
ArrayResize(time, bars);
| |||
| |||
if(CopyHigh(_Symbol, tf, 0, bars, high) != bars) return;
| |||
if(CopyLow(_Symbol, tf, 0, bars, low) != bars) return;
| |||
if(CopyClose(_Symbol, tf, 0, bars, close) != bars) return;
| |||
if(CopyTime(_Symbol, tf, 0, bars, time) != bars) return;
| |||
| |||
// Find pivot highs and lows
| |||
PivotPoint pivotHighs[];
| |||
PivotPoint pivotLows[];
| |||
| |||
FindPivots(high, low, time, bars, pivotHighs, pivotLows);
| |||
| |||
// Clear old trendlines for this TF
| |||
string prefix = "TL_" + tfPrefix + "_";
| |||
ObjectsDeleteAll(0, prefix);
| |||
| |||
// Find uptrend lines (connecting higher lows with 3+ touches)
| |||
FindTrendlines(pivotLows, ArraySize(pivotLows), true, tlArray, prefix, maxLines, time, bars);
| |||
| |||
// Find downtrend lines (connecting lower highs with 3+ touches)
| |||
FindTrendlines(pivotHighs, ArraySize(pivotHighs), false, tlArray, prefix, maxLines, time, bars);
| |||
| |||
// Draw valid trendlines
| |||
for(int i = 0; i < ArraySize(tlArray); i++)
| |||
{
| |||
if(tlArray[i].touches >= TL_MinTouches && StringFind(tlArray[i].objName, tfPrefix) >= 0)
| |||
{
| |||
DrawTrendline(tlArray[i], tfPrefix);
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| FIND PIVOTS |
| |||
//+------------------------------------------------------------------+
| |||
void FindPivots(const double &high[], const double &low[],
| |||
const datetime &time[], int bars,
| |||
PivotPoint &pivotHighs[], PivotPoint &pivotLows[])
| |||
{
| |||
int phCount = 0, plCount = 0;
| |||
| |||
for(int i = TL_RightBars; i < bars - TL_RightBars; i++)
| |||
{
| |||
// Check pivot high
| |||
bool isPH = true;
| |||
for(int k = 1; k <= TL_LeftBars; k++)
| |||
if(high[i] < high[i - k]) { isPH = false; break; }
| |||
if(isPH)
| |||
for(int k = 1; k <= TL_RightBars; k++)
| |||
if(high[i] < high[i + k]) { isPH = false; break; }
| |||
| |||
if(isPH)
| |||
{
| |||
ArrayResize(pivotHighs, phCount + 1);
| |||
pivotHighs[phCount].price = high[i];
| |||
pivotHighs[phCount].barIndex = i;
| |||
pivotHighs[phCount].time = time[i];
| |||
phCount++;
| |||
}
| |||
| |||
// Check pivot low
| |||
bool isPL = true;
| |||
for(int k = 1; k <= TL_LeftBars; k++)
| |||
if(low[i] > low[i - k]) { isPL = false; break; }
| |||
if(isPL)
| |||
for(int k = 1; k <= TL_RightBars; k++)
| |||
if(low[i] > low[i + k]) { isPL = false; break; }
| |||
| |||
if(isPL)
| |||
{
| |||
ArrayResize(pivotLows, plCount + 1);
| |||
pivotLows[plCount].price = low[i];
| |||
pivotLows[plCount].barIndex = i;
| |||
pivotLows[plCount].time = time[i];
| |||
plCount++;
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| FIND TRENDLINES WITH 3+ TOUCHES |
| |||
//+------------------------------------------------------------------+
| |||
void FindTrendlines(PivotPoint &pivots[], int pivotCount, bool isUptrend,
| |||
Trendline &tlArray[], string prefix, int maxLines,
| |||
const datetime &time[], int bars)
| |||
{
| |||
if(pivotCount < 3) return;
| |||
| |||
// Try each pair of pivots as the base line
| |||
for(int i = 0; i < pivotCount - 1; i++)
| |||
{
| |||
for(int j = i + 1; j < pivotCount; j++)
| |||
{
| |||
// Calculate slope
| |||
double slope = (pivots[j].price - pivots[i].price) /
| |||
(double)(pivots[j].barIndex - pivots[i].barIndex);
| |||
| |||
// For uptrend, slope should be positive (higher lows)
| |||
// For downtrend, slope should be negative (lower highs)
| |||
if(isUptrend && slope <= 0) continue;
| |||
if(!isUptrend && slope >= 0) continue;
| |||
| |||
// Count touches
| |||
int touches = 0;
| |||
double tolerance = 0.0005 * pivots[i].price; // 0.05% tolerance
| |||
| |||
for(int k = 0; k < pivotCount; k++)
| |||
{
| |||
double linePrice = pivots[i].price + slope * (pivots[k].barIndex - pivots[i].barIndex);
| |||
if(MathAbs(pivots[k].price - linePrice) <= tolerance)
| |||
touches++;
| |||
}
| |||
| |||
if(touches >= TL_MinTouches)
| |||
{
| |||
// Check if we already have a similar line
| |||
bool duplicate = false;
| |||
for(int t = 0; t < ArraySize(tlArray); t++)
| |||
{
| |||
if(MathAbs(tlArray[t].price1 - pivots[i].price) < tolerance &&
| |||
MathAbs(tlArray[t].price2 - pivots[j].price) < tolerance)
| |||
{
| |||
duplicate = true;
| |||
break;
| |||
}
| |||
}
| |||
| |||
if(!duplicate)
| |||
{
| |||
int idx = ArraySize(tlArray);
| |||
ArrayResize(tlArray, idx + 1);
| |||
| |||
tlArray[idx].time1 = pivots[i].time;
| |||
tlArray[idx].price1 = pivots[i].price;
| |||
tlArray[idx].time2 = time[bars - 1];
| |||
tlArray[idx].price2 = pivots[i].price + slope * (bars - 1 - pivots[i].barIndex);
| |||
tlArray[idx].isUptrend = isUptrend;
| |||
tlArray[idx].touches = touches;
| |||
tlArray[idx].objName = prefix + IntegerToString(idx);
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| DRAW TRENDLINE |
| |||
//+------------------------------------------------------------------+
| |||
void DrawTrendline(Trendline &tl, string tfPrefix)
| |||
{
| |||
if(ObjectFind(0, tl.objName) >= 0) return;
| |||
| |||
ObjectCreate(0, tl.objName, OBJ_TREND, 0, tl.time1, tl.price1, tl.time2, tl.price2);
| |||
ObjectSetInteger(0, tl.objName, OBJPROP_COLOR, tl.isUptrend ? TL_UptrendColor : TL_DowntrendColor);
| |||
ObjectSetInteger(0, tl.objName, OBJPROP_WIDTH, TL_LineWidth);
| |||
ObjectSetInteger(0, tl.objName, OBJPROP_RAY_RIGHT, true);
| |||
ObjectSetInteger(0, tl.objName, OBJPROP_STYLE, STYLE_SOLID);
| |||
| |||
// Add label
| |||
string labelName = tl.objName + "_LBL";
| |||
ObjectCreate(0, labelName, OBJ_TEXT, 0, tl.time2, tl.price2);
| |||
ObjectSetString(0, labelName, OBJPROP_TEXT, " " + tfPrefix + " (" + IntegerToString(tl.touches) + "T)");
| |||
ObjectSetInteger(0, labelName, OBJPROP_COLOR, tl.isUptrend ? TL_UptrendColor : TL_DowntrendColor);
| |||
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| RSI DIVERGENCE DETECTION |
| |||
//+------------------------------------------------------------------+
| |||
void DetectRSIDivergence(const int rates_total, int start,
| |||
const datetime &time[],
| |||
const double &high[],
| |||
const double &low[],
| |||
const double &close[])
| |||
{
| |||
// Get RSI values
| |||
double rsi[];
| |||
ArrayResize(rsi, rates_total);
| |||
if(CopyBuffer(g_rsiHandle, 0, 0, rates_total, rsi) != rates_total) return;
| |||
| |||
// Find price pivot highs and lows
| |||
PivotPoint priceHighs[], priceLows[];
| |||
int phCount = 0, plCount = 0;
| |||
| |||
for(int i = RSI_RightBars; i < rates_total - RSI_RightBars; i++)
| |||
{
| |||
// Price pivot high
| |||
bool isPH = true;
| |||
for(int k = 1; k <= RSI_LeftBars; k++)
| |||
if(high[i] < high[i - k]) { isPH = false; break; }
| |||
if(isPH)
| |||
for(int k = 1; k <= RSI_RightBars; k++)
| |||
if(high[i] < high[i + k]) { isPH = false; break; }
| |||
| |||
if(isPH)
| |||
{
| |||
ArrayResize(priceHighs, phCount + 1);
| |||
priceHighs[phCount].price = high[i];
| |||
priceHighs[phCount].barIndex = i;
| |||
priceHighs[phCount].time = time[i];
| |||
phCount++;
| |||
}
| |||
| |||
// Price pivot low
| |||
bool isPL = true;
| |||
for(int k = 1; k <= RSI_LeftBars; k++)
| |||
if(low[i] > low[i - k]) { isPL = false; break; }
| |||
if(isPL)
| |||
for(int k = 1; k <= RSI_RightBars; k++)
| |||
if(low[i] > low[i + k]) { isPL = false; break; }
| |||
| |||
if(isPL)
| |||
{
| |||
ArrayResize(priceLows, plCount + 1);
| |||
priceLows[plCount].price = low[i];
| |||
priceLows[plCount].barIndex = i;
| |||
priceLows[plCount].time = time[i];
| |||
plCount++;
| |||
}
| |||
}
| |||
| |||
// Find RSI pivot highs and lows
| |||
PivotPoint rsiHighs[], rsiLows[];
| |||
int rhCount = 0, rlCount = 0;
| |||
| |||
for(int i = RSI_RightBars; i < rates_total - RSI_RightBars; i++)
| |||
{
| |||
// RSI pivot high
| |||
bool isRH = true;
| |||
for(int k = 1; k <= RSI_LeftBars; k++)
| |||
if(rsi[i] < rsi[i - k]) { isRH = false; break; }
| |||
if(isRH)
| |||
for(int k = 1; k <= RSI_RightBars; k++)
| |||
if(rsi[i] < rsi[i + k]) { isRH = false; break; }
| |||
| |||
if(isRH)
| |||
{
| |||
ArrayResize(rsiHighs, rhCount + 1);
| |||
rsiHighs[rhCount].price = rsi[i];
| |||
rsiHighs[rhCount].barIndex = i;
| |||
rsiHighs[rhCount].time = time[i];
| |||
rhCount++;
| |||
}
| |||
| |||
// RSI pivot low
| |||
bool isRL = true;
| |||
for(int k = 1; k <= RSI_LeftBars; k++)
| |||
if(rsi[i] > rsi[i - k]) { isRL = false; break; }
| |||
if(isRL)
| |||
for(int k = 1; k <= RSI_RightBars; k++)
| |||
if(rsi[i] > rsi[i + k]) { isRL = false; break; }
| |||
| |||
if(isRL)
| |||
{
| |||
ArrayResize(rsiLows, rlCount + 1);
| |||
rsiLows[rlCount].price = rsi[i];
| |||
rsiLows[rlCount].barIndex = i;
| |||
rsiLows[rlCount].time = time[i];
| |||
rlCount++;
| |||
}
| |||
}
| |||
| |||
// Clear old signals
| |||
ObjectsDeleteAll(0, "DIV_");
| |||
ArrayResize(g_DivSignals, 0);
| |||
| |||
//--- Bearish Divergence: Price higher high + RSI lower high
| |||
if(phCount >= 2 && rhCount >= 2)
| |||
{
| |||
for(int i = 0; i < phCount - 1; i++)
| |||
{
| |||
for(int j = 0; j < rhCount - 1; j++)
| |||
{
| |||
// Check if pivots are close in time (within 5 bars)
| |||
if(MathAbs(priceHighs[i].barIndex - rsiHighs[j].barIndex) <= 5)
| |||
{
| |||
// Check next pivots
| |||
for(int ii = i + 1; ii < phCount; ii++)
| |||
{
| |||
for(int jj = j + 1; jj < rhCount; jj++)
| |||
{
| |||
if(MathAbs(priceHighs[ii].barIndex - rsiHighs[jj].barIndex) <= 5)
| |||
{
| |||
// Price higher high, RSI lower high = bearish divergence
| |||
if(priceHighs[ii].price > priceHighs[i].price &&
| |||
rsiHighs[jj].price < rsiHighs[j].price)
| |||
{
| |||
AddDivergenceSignal(priceHighs[i].time, priceHighs[i].price,
| |||
priceHighs[ii].time, priceHighs[ii].price,
| |||
false);
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//--- Bullish Divergence: Price lower low + RSI higher low
| |||
if(plCount >= 2 && rlCount >= 2)
| |||
{
| |||
for(int i = 0; i < plCount - 1; i++)
| |||
{
| |||
for(int j = 0; j < rlCount - 1; j++)
| |||
{
| |||
if(MathAbs(priceLows[i].barIndex - rsiLows[j].barIndex) <= 5)
| |||
{
| |||
for(int ii = i + 1; ii < plCount; ii++)
| |||
{
| |||
for(int jj = j + 1; jj < rlCount; jj++)
| |||
{
| |||
if(MathAbs(priceLows[ii].barIndex - rsiLows[jj].barIndex) <= 5)
| |||
{
| |||
// Price lower low, RSI higher low = bullish divergence
| |||
if(priceLows[ii].price < priceLows[i].price &&
| |||
rsiLows[jj].price > rsiLows[j].price)
| |||
{
| |||
AddDivergenceSignal(priceLows[i].time, priceLows[i].price,
| |||
priceLows[ii].time, priceLows[ii].price,
| |||
true);
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
}
| |||
| |||
//+------------------------------------------------------------------+
| |||
//| ADD DIVERGENCE SIGNAL |
| |||
//+------------------------------------------------------------------+
| |||
void AddDivergenceSignal(datetime t1, double p1, datetime t2, double p2, bool isBullish)
| |||
{
| |||
// Check for duplicate
| |||
for(int i = 0; i < ArraySize(g_DivSignals); i++)
| |||
{
| |||
if(g_DivSignals[i].time1 == t1 && g_DivSignals[i].time2 == t2)
| |||
return;
| |||
}
| |||
| |||
int idx = ArraySize(g_DivSignals);
| |||
ArrayResize(g_DivSignals, idx + 1);
| |||
| |||
g_DivSignals[idx].time1 = t1;
| |||
g_DivSignals[idx].price1 = p1;
| |||
g_DivSignals[idx].time2 = t2;
| |||
g_DivSignals[idx].price2 = p2;
| |||
g_DivSignals[idx].isBullish = isBullish;
| |||
g_DivSignals[idx].lineName = "DIV_LINE_" + IntegerToString(idx);
| |||
g_DivSignals[idx].arrowName = "DIV_ARR_" + IntegerToString(idx);
| |||
| |||
// Draw divergence line on chart
| |||
color divColor = isBullish ? RSI_BullColor : RSI_BearColor;
| |||
| |||
ObjectCreate(0, g_DivSignals[idx].lineName, OBJ_TREND, 0, t1, p1, t2, p2);
| |||
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_COLOR, divColor);
| |||
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_WIDTH, 2);
| |||
ObjectSetInteger(0, g_DivSignals[idx].lineName, OBJPROP_STYLE, STYLE_DASH);
| |||
| |||
// Draw arrow at signal point
| |||
ENUM_OBJECT arrowType = isBullish ? OBJ_ARROW_UP : OBJ_ARROW_DOWN;
| |||
double arrowPrice = isBullish ? p2 - 10 * _Point : p2 + 10 * _Point;
| |||
| |||
ObjectCreate(0, g_DivSignals[idx].arrowName, arrowType, 0, t2, arrowPrice);
| |||
ObjectSetInteger(0, g_DivSignals[idx].arrowName, OBJPROP_COLOR, divColor);
| |||
ObjectSetInteger(0, g_DivSignals[idx].arrowName, OBJPROP_WIDTH, 3);
| |||
| |||
// Add label
| |||
string labelName = "DIV_LBL_" + IntegerToString(idx);
| |||
ObjectCreate(0, labelName, OBJ_TEXT, 0, t2, arrowPrice);
| |||
ObjectSetString(0, labelName, OBJPROP_TEXT, isBullish ? " Bull Div" : " Bear Div");
| |||
ObjectSetInteger(0, labelName, OBJPROP_COLOR, divColor);
| |||
ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 8);
| |||
| |||
// Enforce max signals
| |||
while(ArraySize(g_DivSignals) > RSI_MaxSignals)
| |||
{
| |||
ObjectDelete(0, g_DivSignals[0].lineName);
| |||
ObjectDelete(0, g_DivSignals[0].arrowName);
| |||
ObjectDelete(0, "DIV_LBL_0");
| |||
ArrayRemove(g_DivSignals, 0, 1);
| |||
}
| |||
}
| |||
//+------------------------------------------------------------------+
|