//+------------------------------------------------------------------+ //| SMC_Unified_EA_v2.mq5 | //| Smart Money Concepts: Order Blocks + FVG + BOS with Filters | //+------------------------------------------------------------------+ #property copyright "Vignesh Kumaravel, SkyBlueFS 2025" #property version "2.01" #property strict #include #include CTrade trade; CPositionInfo pos; //=========================== ENUMS ===========================// enum ENUM_STRATEGY { STRAT_OB=0, STRAT_FVG=1, STRAT_BOS=2, STRAT_AUTO=3 }; enum ENUM_REGIME { REGIME_ADX=0, REGIME_EFFICIENCY=1 }; //========================== INPUTS ===========================// // Core Strategy input ENUM_STRATEGY Strategy = STRAT_AUTO; input long MagicNumber = 76543; input long HedgeMagic = 76544; // for future hedging input bool OneTradePerBar = true; // Risk & Money Management input double LotPerK = 0.02; // lots per $1000 balance input double BagProfitPercent = 8.0; // close all at +x% equity input double BagLossPercent = 4.0; // close all at -y% equity // Market Context Filters input bool UseSessionFilter = true; // skip Asian session input bool UseATRFilters = true; // displacement + gap sizing input bool UseRegimeFilter = true; // trend filter for BOS input ENUM_REGIME RegimeMethod = REGIME_ADX; // ADX or Efficiency Ratio // Core Parameters (others auto-calculated) input int ATR_Period = 14; input int SwingBars = 5; // swing confirmation period input double FibRetraceLevel = 61.8; // OB entry level // Advanced (auto-adapted if 0) input double DisplacementMin = 0.0; // auto = adaptive input int MaxSpreadPoints = 120; //========================= CONSTANTS ==========================// // Sessions (UTC hours) const int ASIA_START = 1, ASIA_END = 7; const int LONDON_START = 7, LONDON_END = 16; const int NY_START = 12, NY_END = 21; // Colors const color BULL_OB = clrLimeGreen; const color BEAR_OB = clrCrimson; const color BULL_FVG = clrLightBlue; const color BEAR_FVG = clrMistyRose; const color BOS_BULL = clrDodgerBlue; const color BOS_BEAR = clrTomato; //========================== GLOBALS ===========================// double PointValue, currentBalance, profitThreshold, lossThreshold; int DigitsCount; // <-- renamed from 'Digits' datetime lastBarTime = 0, lastTradeBar = 0; // runtime feature flags (mirror inputs; safe to change at runtime) bool gUseATRFilters = true; bool gUseRegimeFilter = true; // Indicators int atr_handle = INVALID_HANDLE; int adx_handle = INVALID_HANDLE; // Order Block State datetime lastOBTime = 0; double obHigh = 0, obLow = 0; int obDirection = 0; // Arrays for bagging system ulong pairs[]; ulong hedges[]; //======================= INITIALIZATION =======================// int OnInit() { PointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT); DigitsCount = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); lastBarTime = iTime(_Symbol, _Period, 0); // mirror inputs into runtime flags gUseATRFilters = UseATRFilters; gUseRegimeFilter = UseRegimeFilter; // Initialize indicators atr_handle = iATR(_Symbol, _Period, ATR_Period); if(gUseATRFilters && atr_handle == INVALID_HANDLE) { Print("ATR initialization failed - disabling ATR filters"); gUseATRFilters = false; // <-- never modify 'input' } if(gUseRegimeFilter && RegimeMethod == REGIME_ADX) { adx_handle = iADX(_Symbol, _Period, 14); if(adx_handle == INVALID_HANDLE) { Print("ADX initialization failed - disabling regime filter"); gUseRegimeFilter = false; // <-- never modify 'input' } } // Initialize bagging thresholds ChangeBaggingThresholds(); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { if(atr_handle != INVALID_HANDLE) IndicatorRelease(atr_handle); if(adx_handle != INVALID_HANDLE) IndicatorRelease(adx_handle); } //====================== UTILITY FUNCTIONS ======================// bool IsNewBar() { datetime current = iTime(_Symbol, _Period, 0); if(current != lastBarTime) { lastBarTime = current; return true; } return false; } double GetATR(int shift = 1) { if(atr_handle == INVALID_HANDLE) return 0; double buffer[1]; return (CopyBuffer(atr_handle, 0, shift, 1, buffer) == 1) ? buffer[0] : 0; } double CalculateAdaptiveThreshold(string type) { if(!gUseATRFilters) return (type == "displacement") ? 1.5 : 0.15; double atr = GetATR(1); if(atr <= 0) return (type == "displacement") ? 1.5 : 0.15; // Adapt thresholds based on recent volatility double bid=SymbolInfoDouble(_Symbol, SYMBOL_BID); if(bid<=0) bid=1.0; double volatility = atr / bid; if(type == "displacement") { return MathMax(1.2, 1.8 - volatility * 100); // Lower threshold in high vol } else if(type == "fvg_gap") { return MathMax(0.10, 0.20 - volatility * 50); // Smaller gaps OK in high vol } return 1.5; // default } //====================== SESSION FILTER ======================// bool IsValidSession() { if(!UseSessionFilter) return true; MqlDateTime dt; TimeToStruct(TimeGMT(), dt); int hour = dt.hour; // Block Asian session if(hour >= ASIA_START && hour < ASIA_END) return false; // Allow London and NY sessions return ((hour >= LONDON_START && hour < LONDON_END) || (hour >= NY_START && hour < NY_END)); } //===================== DISPLACEMENT LOGIC ====================// bool HasDisplacement(int bar) { if(!gUseATRFilters) return true; double atr = GetATR(bar); if(atr <= 0) return true; double high = iHigh(_Symbol, _Period, bar); double low = iLow (_Symbol, _Period, bar); double open = iOpen(_Symbol, _Period, bar); double close= iClose(_Symbol, _Period, bar); double range = high - low; double body = MathAbs(close - open); double displacementThreshold = (DisplacementMin > 0.0) ? DisplacementMin : CalculateAdaptiveThreshold("displacement"); // 2-of-3 score: (range vs ATR), (body strength), (mild continuation) int score = 0; if(range >= displacementThreshold * atr) score++; if(range>0 && (body >= 0.6 * range)) score++; if(bar > 0 && MathAbs(iClose(_Symbol, _Period, bar-1) - iOpen(_Symbol, _Period, bar-1)) >= 0.4 * atr) score++; return score >= 2; } //==================== REGIME DETECTION ====================// double CalculateEfficiencyRatio(int periods) { if(Bars(_Symbol, _Period) < periods + 2) return 0.0; double start = iClose(_Symbol, _Period, periods); double end = iClose(_Symbol, _Period, 1); double netMove = MathAbs(end - start); double totalMove = 0; for(int i = periods; i > 1; i--) { totalMove += MathAbs(iClose(_Symbol, _Period, i-1) - iClose(_Symbol, _Period, i)); } return (totalMove > 0) ? netMove / totalMove : 0.0; } bool IsTrendingMarket() { if(!gUseRegimeFilter) return true; if(RegimeMethod == REGIME_ADX) { if(adx_handle == INVALID_HANDLE) return true; double buffer[1]; if(CopyBuffer(adx_handle, 0, 1, 1, buffer) != 1) return true; return buffer[0] >= 18.0; } else { return CalculateEfficiencyRatio(30) >= 0.30; } } //===================== SWING DETECTION =====================// void FindSwingLevels(double &swingHigh, datetime &highTime, double &swingLow, datetime &lowTime) { swingHigh = 0; highTime = 0; swingLow = 0; lowTime = 0; int lookback = MathMin(20, Bars(_Symbol, _Period) - SwingBars * 2); if(lookback < SwingBars+1) return; for(int i = SwingBars; i <= lookback; i++) { bool isSwingHigh = true, isSwingLow = true; double h = iHigh(_Symbol, _Period, i); double l = iLow (_Symbol, _Period, i); for(int j = 1; j <= SwingBars; j++) { if(h <= iHigh(_Symbol, _Period, i-j) || h <= iHigh(_Symbol, _Period, i+j)) isSwingHigh = false; if(l >= iLow (_Symbol, _Period, i-j) || l >= iLow (_Symbol, _Period, i+j)) isSwingLow = false; if(!isSwingHigh && !isSwingLow) break; } if(isSwingHigh && (highTime == 0 || iTime(_Symbol, _Period, i) > highTime)) { swingHigh = h; highTime = iTime(_Symbol, _Period, i); } if(isSwingLow && (lowTime == 0 || iTime(_Symbol, _Period, i) > lowTime)) { swingLow = l; lowTime = iTime(_Symbol, _Period, i); } } } //==================== POSITION MANAGEMENT ===================// double CalculateLotSize() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double lots = LotPerK * (balance / 1000.0); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double lotStep= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); lots = MathFloor(lots / lotStep) * lotStep; if(lots < minLot) lots = minLot; if(lots > maxLot) lots = maxLot; return lots; } void CalculateStopLoss(ENUM_ORDER_TYPE orderType, double entry, double &sl, double &tp) { double atr = GetATR(0); double slDistance, tpDistance; if(gUseATRFilters && atr > 0) { slDistance = 1.5 * atr; tpDistance = 2.5 * atr; } else { slDistance = 3500 * PointValue; tpDistance = 7500 * PointValue; } if(orderType == ORDER_TYPE_BUY) { sl = entry - slDistance; tp = entry + tpDistance; } else { sl = entry + slDistance; tp = entry - tpDistance; } sl = NormalizeDouble(sl, DigitsCount); tp = NormalizeDouble(tp, DigitsCount); int stopLevel = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); double minDist = stopLevel * PointValue; if(orderType == ORDER_TYPE_BUY) { if((entry - sl) < minDist) sl = NormalizeDouble(entry - minDist, DigitsCount); if((tp - entry) < minDist) tp = NormalizeDouble(entry + minDist, DigitsCount); } else { if((sl - entry) < minDist) sl = NormalizeDouble(entry + minDist, DigitsCount); if((entry - tp) < minDist) tp = NormalizeDouble(entry - minDist, DigitsCount); } } bool ExecuteTrade(ENUM_ORDER_TYPE orderType, string comment) { if(!IsValidSession()) return false; if(OneTradePerBar && lastTradeBar == lastBarTime) return false; if(SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > MaxSpreadPoints) return false; // Check for existing positions for(int i = PositionsTotal()-1; i >= 0; --i) { ulong ticket = PositionGetTicket(i); if(!ticket) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; if((long)PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; return false; // Position already exists } MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return false; double entry = (orderType == ORDER_TYPE_BUY) ? tick.ask : tick.bid; double sl, tp; CalculateStopLoss(orderType, entry, sl, tp); double lots = CalculateLotSize(); trade.SetExpertMagicNumber(MagicNumber); trade.SetDeviationInPoints(20); bool result = trade.PositionOpen(_Symbol, orderType, lots, entry, sl, tp, comment); if(result && OneTradePerBar) lastTradeBar = lastBarTime; return result; } //======================= ORDER BLOCKS =======================// void DetectOrderBlock() { obDirection = 0; obHigh = 0; obLow = 0; lastOBTime = 0; int total = Bars(_Symbol, _Period); if(total < 10) return; int scanBars = MathMin(120, total - 4); for(int i = 1; i < scanBars; i++) { double open_i = iOpen (_Symbol, _Period, i); double close_i = iClose(_Symbol, _Period, i); double open_p = iOpen (_Symbol, _Period, i-1); double close_p = iClose(_Symbol, _Period, i-1); bool bullishOB = (open_i > close_i) && (close_p > open_p) && HasDisplacement(i-1); bool bearishOB = (open_i < close_i) && (close_p < open_p) && HasDisplacement(i-1); if(bullishOB) { obDirection = 1; obHigh = iHigh(_Symbol, _Period, i); obLow = iLow (_Symbol, _Period, i); lastOBTime = iTime(_Symbol, _Period, i); break; } if(bearishOB) { obDirection = -1; obHigh = iHigh(_Symbol, _Period, i); obLow = iLow (_Symbol, _Period, i); lastOBTime = iTime(_Symbol, _Period, i); break; } } } void TradeOrderBlock() { if(obDirection == 0) return; MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; bool inZone = (obDirection > 0) ? (tick.ask >= obLow && tick.ask <= obHigh) : (tick.bid >= obLow && tick.bid <= obHigh); if(!inZone) return; // Fib from most recent swings double swingHigh, swingLow; datetime highTime, lowTime; FindSwingLevels(swingHigh, highTime, swingLow, lowTime); if(swingHigh <= 0 || swingLow <= 0) return; if(obDirection > 0) { double entryLevel = swingHigh - (swingHigh - swingLow) * (FibRetraceLevel / 100.0); if(tick.ask <= entryLevel) ExecuteTrade(ORDER_TYPE_BUY, "SMC_OB_BUY"); } else { double entryLevel = swingLow + (swingHigh - swingLow) * (FibRetraceLevel / 100.0); if(tick.bid >= entryLevel) ExecuteTrade(ORDER_TYPE_SELL, "SMC_OB_SELL"); } } //===================== FAIR VALUE GAPS ====================// void DrawFVG(string name, datetime startTime, double bottom, double top, color clr) { if(ObjectFind(0, name) != -1) ObjectDelete(0, name); ObjectCreate(0, name, OBJ_RECTANGLE, 0, startTime, bottom, lastBarTime, top); ObjectSetInteger(0, name, OBJPROP_FILL, true); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); } void TradeFVGTouch() { MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; int totalObjects = ObjectsTotal(0, 0, -1); for(int i = 0; i < totalObjects; i++) { string objName = ObjectName(0, i); bool isBullFVG = StringFind(objName, "FVG_BULL_") == 0; bool isBearFVG = StringFind(objName, "FVG_BEAR_") == 0; if(!isBullFVG && !isBearFVG) continue; double top = ObjectGetDouble(0, objName, OBJPROP_PRICE, 1); double bottom = ObjectGetDouble(0, objName, OBJPROP_PRICE, 0); if(isBullFVG && tick.ask >= bottom && tick.ask <= top) { if(ExecuteTrade(ORDER_TYPE_BUY, "SMC_FVG_BUY")) break; } if(isBearFVG && tick.bid >= bottom && tick.bid <= top) { if(ExecuteTrade(ORDER_TYPE_SELL, "SMC_FVG_SELL")) break; } } } void DetectAndTradeFVG() { int total = Bars(_Symbol, _Period); if(total < 5) return; int scanBars = MathMin(50, total - 3); double minGapThreshold = CalculateAdaptiveThreshold("fvg_gap"); double atr = GetATR(1); for(int i = 0; i < scanBars; i++) { if(i + 2 >= total) break; double high_A = iHigh(_Symbol, _Period, i + 2); double low_A = iLow (_Symbol, _Period, i + 2); double high_C = iHigh(_Symbol, _Period, i); double low_C = iLow (_Symbol, _Period, i); // Bullish FVG: Low[A] > High[C] if(low_A > high_C) { double gapSize = (low_A - high_C) / PointValue; double minGap = (gUseATRFilters && atr > 0) ? (minGapThreshold * atr / PointValue) : 3.0; if(gapSize >= minGap) { string objName = "FVG_BULL_" + TimeToString(iTime(_Symbol, _Period, i+2), TIME_DATE|TIME_MINUTES); DrawFVG(objName, iTime(_Symbol, _Period, i+2), high_C, low_A, BULL_FVG); } } // Bearish FVG: High[A] < Low[C] if(high_A < low_C) { double gapSize = (low_C - high_A) / PointValue; double minGap = (gUseATRFilters && atr > 0) ? (minGapThreshold * atr / PointValue) : 3.0; if(gapSize >= minGap) { string objName = "FVG_BEAR_" + TimeToString(iTime(_Symbol, _Period, i+2), TIME_DATE|TIME_MINUTES); DrawFVG(objName, iTime(_Symbol, _Period, i+2), high_A, low_C, BEAR_FVG); } } } // touch-to-trade TradeFVGTouch(); } //================== BREAK OF STRUCTURE ===================// void DrawBOS(string name, datetime startTime, double level, datetime endTime, double endLevel, color clr) { if(ObjectFind(0, name) != -1) return; ObjectCreate(0, name, OBJ_TREND, 0, startTime, level, endTime, endLevel); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_WIDTH, 2); string labelName = name + "_LBL"; ObjectCreate(0, labelName, OBJ_TEXT, 0, endTime, endLevel); ObjectSetString(0, labelName, OBJPROP_TEXT, "BOS"); ObjectSetInteger(0, labelName, OBJPROP_COLOR, clr); } void DetectAndTradeBOS() { if(!IsTrendingMarket()) return; // Only trade BOS in trending markets double swingHigh, swingLow; datetime highTime, lowTime; FindSwingLevels(swingHigh, highTime, swingLow, lowTime); if(swingHigh <= 0 && swingLow <= 0) return; MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; // Bullish BOS: Break above swing high with displacement if(swingHigh > 0 && tick.ask > swingHigh && HasDisplacement(1)) { string objName = "BOS_BULL_" + TimeToString(highTime, TIME_DATE|TIME_MINUTES); DrawBOS(objName, highTime, swingHigh, lastBarTime, swingHigh, BOS_BULL); ExecuteTrade(ORDER_TYPE_BUY, "SMC_BOS_BUY"); return; } // Bearish BOS: Break below swing low with displacement if(swingLow > 0 && tick.bid < swingLow && HasDisplacement(1)) { string objName = "BOS_BEAR_" + TimeToString(lowTime, TIME_DATE|TIME_MINUTES); DrawBOS(objName, lowTime, swingLow, lastBarTime, swingLow, BOS_BEAR); ExecuteTrade(ORDER_TYPE_SELL, "SMC_BOS_SELL"); return; } } //==================== BAGGING SYSTEM ====================// void ChangeBaggingThresholds() { currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); profitThreshold = currentBalance * (1.0 + BagProfitPercent/100.0); lossThreshold = currentBalance * (1.0 - BagLossPercent/100.0); PrintFormat("Bagging Targets: Loss=%.2f, Profit=%.2f", lossThreshold, profitThreshold); } void CloseAllTrades() { for(int i = PositionsTotal()-1; i >= 0; --i) { ulong ticket = PositionGetTicket(i); if(!ticket) continue; if(!PositionSelectByTicket(ticket)) continue; string sym = PositionGetString(POSITION_SYMBOL); long mgc = (long)PositionGetInteger(POSITION_MAGIC); if(sym==_Symbol && (mgc==MagicNumber || mgc==HedgeMagic)) { trade.PositionClose(ticket); } } ArrayResize(pairs, 0); ArrayResize(hedges, 0); } void CheckBagging() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); if(equity >= profitThreshold || equity <= lossThreshold) { PrintFormat("Bagging triggered - Equity: %.2f, Targets: %.2f/%.2f", equity, lossThreshold, profitThreshold); CloseAllTrades(); ChangeBaggingThresholds(); } } //======================== MAIN EVENTS ========================// void OnTick() { CheckBagging(); // Always check bagging first if(!IsNewBar()) return; if(Strategy == STRAT_OB || Strategy == STRAT_AUTO){ DetectOrderBlock(); TradeOrderBlock(); } if(Strategy == STRAT_FVG || Strategy == STRAT_AUTO){ DetectAndTradeFVG(); } if(Strategy == STRAT_BOS || Strategy == STRAT_AUTO){ DetectAndTradeBOS(); } }