Jose-bot/bot
2026-05-05 15:04:31 +00:00

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