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