//+------------------------------------------------------------------+ //| SMC_TrendBreakout_MTF.mq5 | //| Visual indicator: BOS/CHoCH + Donchian breakout + MTF filter | //+------------------------------------------------------------------+ #property strict #property indicator_chart_window #property indicator_buffers 2 #property indicator_plots 2 // --- plot 0: buy arrows #property indicator_label1 "Long" #property indicator_type1 DRAW_ARROW #property indicator_color1 clrLimeGreen #property indicator_style1 STYLE_SOLID #property indicator_width1 2 // --- plot 1: sell arrows #property indicator_label2 "Short" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrTomato #property indicator_style2 STYLE_SOLID #property indicator_width2 2 input group "SMC (structure)" input bool UseSMC = true; input bool UseCHoCH = true; // label opposite break as CHoCH input group "Trend Breakout" input bool UseDonchianBreakout = true; input int DonchianLookback = 20; // bars (completed bars) input group "Lower timeframe confirmation" input bool RequireMTFConfirm = true; input ENUM_TIMEFRAMES LowerTF = PERIOD_M5; input int EMAFast = 20; input int EMASlow = 50; input group "Signals" input bool FireOnClose = true; // use last closed bar (recommended) input int ArrowCodeLong = 233; // wingdings: up arrow input int ArrowCodeShort = 234; // wingdings: down arrow input int ArrowOffsetPoints = 10; // vertical offset in points input group "Drawing" input bool DrawStructureLines = true; input bool DrawBreakoutLines = false; input int MaxObjects = 200; // prevent clutter input group "Notifications" input bool PopupAlerts = false; input bool PushNotifications = false; double gLongBuf[]; double gShortBuf[]; int gFractalsHandle = INVALID_HANDLE; int gEmaFastHandle = INVALID_HANDLE; int gEmaSlowHandle = INVALID_HANDLE; // Tracks last trend direction on main TF: 1 bullish, -1 bearish, 0 unknown int gTrendDir = 0; string gObjPrefix; datetime gLastBarTime = 0; // OPTIMIZATION: Static arrays to avoid repeated allocation/deallocation in OnCalculate static double gUpFractalCache[600]; static double gDnFractalCache[600]; static int ClampInt(const int value, const int lowerBound, const int upperBound) { return (valueupperBound?upperBound:value)); } static bool IsNewBar(const datetime t0) { if(t0==gLastBarTime) return false; gLastBarTime=t0; return true; } static void Notify(const string msg) { if(PopupAlerts) Alert(msg); if(PushNotifications) SendNotification(msg); } static void SafeDeleteOldObjects() { // keep last MaxObjects objects with prefix, delete oldest (by time suffix) // OPTIMIZATION: Single-pass algorithm instead of double-loop int total = ObjectsTotal(0, 0, -1); int objectCount = 0; // Single pass: count and store object names string objectNames[]; ArrayResize(objectNames, 0); for(int objectIndex=total-1;objectIndex>=0;objectIndex--) { string name = ObjectName(0, objectIndex, 0, -1); if(StringFind(name, gObjPrefix) == 0) { // OPTIMIZATION: Store size to avoid repeated ArraySize() calls int currentSize = ArraySize(objectNames); ArrayResize(objectNames, currentSize + 1); objectNames[currentSize] = name; objectCount++; } } // If within limit, no deletion needed if(objectCount <= MaxObjects) return; // Delete all objects with prefix (fast & safe for indicator) for(int i = 0; i < ArraySize(objectNames); i++) { ObjectDelete(0, objectNames[i]); } } static void DrawHLine(const string name, const double price, const color lineColor, const ENUM_LINE_STYLE lineStyle, const int lineWidth) { if(!DrawStructureLines && !DrawBreakoutLines) return; if(ObjectFind(0, name) >= 0) return; ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, name, OBJPROP_STYLE, lineStyle); ObjectSetInteger(0, name, OBJPROP_WIDTH, lineWidth); ObjectSetInteger(0, name, OBJPROP_BACK, true); } static void DrawText(const string name, const datetime timeValue, const double price, const string text, const color textColor) { if(ObjectFind(0, name) >= 0) return; ObjectCreate(0, name, OBJ_TEXT, 0, timeValue, price); ObjectSetString(0, name, OBJPROP_TEXT, text); ObjectSetInteger(0, name, OBJPROP_COLOR, textColor); ObjectSetInteger(0, name, OBJPROP_ANCHOR, ANCHOR_LEFT); ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 9); ObjectSetInteger(0, name, OBJPROP_BACK, true); } static double HighestHigh(const double &high[], const int start, const int count) { int maxIndex = ArrayMaximum(high, start, count); if(maxIndex == -1) return -DBL_MAX; return high[maxIndex]; } static double LowestLow(const double &low[], const int start, const int count) { int minIndex = ArrayMinimum(low, start, count); if(minIndex == -1) return DBL_MAX; return low[minIndex]; } // --- Cached MTF direction (performance) static datetime g_mtfDir_lastCheckTime = 0; static int g_mtfDir_cachedValue = 0; static int GetMTFDir() { if(!RequireMTFConfirm) return 0; if(gEmaFastHandle==INVALID_HANDLE || gEmaSlowHandle==INVALID_HANDLE) return 0; // PERF: Only check for new MTF direction on a new bar of the LowerTF. datetime mtf_time[1]; if(CopyTime(_Symbol, LowerTF, 0, 1, mtf_time) != 1) return 0; if(mtf_time[0] == g_mtfDir_lastCheckTime) return g_mtfDir_cachedValue; g_mtfDir_lastCheckTime = mtf_time[0]; double fast[2], slow[2]; ArraySetAsSeries(fast, true); ArraySetAsSeries(slow, true); if(CopyBuffer(gEmaFastHandle, 0, 1, 1, fast) != 1) { g_mtfDir_cachedValue=0; return 0; } if(CopyBuffer(gEmaSlowHandle, 0, 1, 1, slow) != 1) { g_mtfDir_cachedValue=0; return 0; } if(fast[0] > slow[0]) g_mtfDir_cachedValue = 1; else if(fast[0] < slow[0]) g_mtfDir_cachedValue = -1; else g_mtfDir_cachedValue = 0; return g_mtfDir_cachedValue; } int OnInit() { SetIndexBuffer(0, gLongBuf, INDICATOR_DATA); SetIndexBuffer(1, gShortBuf, INDICATOR_DATA); ArraySetAsSeries(gLongBuf, true); ArraySetAsSeries(gShortBuf, true); PlotIndexSetInteger(0, PLOT_ARROW, ArrowCodeLong); PlotIndexSetInteger(1, PLOT_ARROW, ArrowCodeShort); gObjPrefix = StringFormat("SMC_TB_MTF_%I64u_", (long)ChartID()); // fractals on main TF gFractalsHandle = iFractals(_Symbol, _Period); if(gFractalsHandle == INVALID_HANDLE) return INIT_FAILED; // EMA handles on lower TF (for confirmation) gEmaFastHandle = iMA(_Symbol, LowerTF, EMAFast, 0, MODE_EMA, PRICE_CLOSE); gEmaSlowHandle = iMA(_Symbol, LowerTF, EMASlow, 0, MODE_EMA, PRICE_CLOSE); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(gFractalsHandle != INVALID_HANDLE) IndicatorRelease(gFractalsHandle); if(gEmaFastHandle != INVALID_HANDLE) IndicatorRelease(gEmaFastHandle); if(gEmaSlowHandle != INVALID_HANDLE) IndicatorRelease(gEmaSlowHandle); } 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[]) { // OPTIMIZATION: Early exit if no new bars to calculate if(prev_calculated > 0 && prev_calculated == rates_total) return rates_total; if(rates_total < 100) return 0; int donLookback = (DonchianLookback < 2 ? 2 : DonchianLookback); ArraySetAsSeries(time, true); ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(close, true); // Clear only newly calculated area; indicator buffers are series arrays (0 = current bar) int start = (prev_calculated==0 ? rates_total-1 : rates_total - prev_calculated); start = ClampInt(start, 0, rates_total-1); for(int i=start;i>=0;i--) { gLongBuf[i] = EMPTY_VALUE; gShortBuf[i] = EMPTY_VALUE; } // Only emit signals once per new bar (less spam); use bar 1 when FireOnClose. if(!IsNewBar(time[0])) return rates_total; SafeDeleteOldObjects(); const int sigBar = (FireOnClose ? 1 : 0); if(sigBar >= rates_total-1) return rates_total; // --- OPTIMIZATION: Use static arrays to avoid repeated allocation int need = MathMin(600, rates_total); ArraySetAsSeries(gUpFractalCache, true); ArraySetAsSeries(gDnFractalCache, true); int gotUp = CopyBuffer(gFractalsHandle, 0, 0, need, gUpFractalCache); int gotDn = CopyBuffer(gFractalsHandle, 1, 0, need, gDnFractalCache); if(gotUp <= 0 || gotDn <= 0) return rates_total; // Find most recent confirmed swing high/low (fractal appears 2 bars after formation) double lastSwingHigh = 0.0; datetime lastSwingHighT = 0; double lastSwingLow = 0.0; datetime lastSwingLowT = 0; for(int i=sigBar+2; i= rates_total) return rates_total; double donHigh = HighestHigh(high, donStart, donCount); double donLow = LowestLow(low, donStart, donCount); // --- Lower TF confirmation int mtfDir = GetMTFDir(); // 1 up, -1 down, 0 neutral/unknown bool mtfOkLong = (!RequireMTFConfirm) || (mtfDir == 1); bool mtfOkShort = (!RequireMTFConfirm) || (mtfDir == -1); // --- Signals bool smcLong=false, smcShort=false, donLong=false, donShort=false; if(UseSMC) { if(lastSwingHighT!=0 && close[sigBar] > lastSwingHigh) smcLong = true; if(lastSwingLowT!=0 && close[sigBar] < lastSwingLow) smcShort = true; } if(UseDonchianBreakout) { if(close[sigBar] > donHigh) donLong = true; if(close[sigBar] < donLow) donShort = true; } bool finalLong = (smcLong || donLong) && mtfOkLong; bool finalShort = (smcShort || donShort) && mtfOkShort; // --- Plot & draw double pointValue = _Point; if(finalLong) { gLongBuf[sigBar] = low[sigBar] - ArrowOffsetPoints * pointValue; color lineColor = clrLimeGreen; if(smcLong && lastSwingHighT!=0 && DrawStructureLines) { int breakDir = 1; bool choch = (UseCHoCH && gTrendDir!=0 && breakDir != gTrendDir); string kind = (choch ? "CHoCH↑" : "BOS↑"); string structureObjectName = gObjPrefix + StringFormat("SMC_%s_%I64d", kind, (long)time[sigBar]); DrawHLine(structureObjectName+"_L", lastSwingHigh, lineColor, STYLE_DOT, 1); DrawText(structureObjectName+"_T", time[sigBar], lastSwingHigh, kind, lineColor); gTrendDir = breakDir; } if(donLong && DrawBreakoutLines) { string breakoutObjectName = gObjPrefix + StringFormat("DON_H_%I64d", (long)time[sigBar]); DrawHLine(breakoutObjectName, donHigh, clrDeepSkyBlue, STYLE_DASH, 1); } Notify(StringFormat("%s LONG | TF=%s | MTF=%s", _Symbol, EnumToString(_Period), EnumToString(LowerTF))); } if(finalShort) { gShortBuf[sigBar] = high[sigBar] + ArrowOffsetPoints * pointValue; color lineColor = clrTomato; if(smcShort && lastSwingLowT!=0 && DrawStructureLines) { int breakDir = -1; bool choch = (UseCHoCH && gTrendDir!=0 && breakDir != gTrendDir); string kind = (choch ? "CHoCH↓" : "BOS↓"); string structureObjectName = gObjPrefix + StringFormat("SMC_%s_%I64d", kind, (long)time[sigBar]); DrawHLine(structureObjectName+"_L", lastSwingLow, lineColor, STYLE_DOT, 1); DrawText(structureObjectName+"_T", time[sigBar], lastSwingLow, kind, lineColor); gTrendDir = breakDir; } if(donShort && DrawBreakoutLines) { string breakoutObjectName = gObjPrefix + StringFormat("DON_L_%I64d", (long)time[sigBar]); DrawHLine(breakoutObjectName, donLow, clrDeepSkyBlue, STYLE_DASH, 1); } Notify(StringFormat("%s SHORT | TF=%s | MTF=%s", _Symbol, EnumToString(_Period), EnumToString(LowerTF))); } return rates_total; }