#property strict #property version "1.00" #include #include #include #property description "High/Low Detection Bot: Detects Highs (Bull->Bear) and Lows (Bear->Bull) with Visuals" //==================== INPUTS ==================== // Symbol input string InpSymbol = ""; // Symbol (empty = current) // Visuals input bool InpDrawHighLow = true; // Draw High/Low Points input bool InpDrawBOS = true; // Draw BOS Points input int InpBOSThresholdPoints = 5; // BOS Break Threshold in Points input bool InpDrawFVG = true; // Draw FVG (Fair Value Gap) input int InpFVGThresholdPoints = 5; // FVG Threshold in Points input bool InpDrawiFVG = true; // Draw iFVG (FVG Mitigation) input bool InpDrawOrderBlock = true; // Draw Order Blocks input int InpOrderBlockCandles = 5; // Number of candles to check for Order Block // Detection Parameters input int InpLookback = 500; // Bars to Scan (History on Init) input int InpHistoryLimit = 100; // Max Swing Points to Keep in Memory // Session Settings input bool InpUseSessions = false; // Master Session Switch input bool InpTradeLondon = true; // Trade London Session input bool InpTradeNY = true; // Trade NY Session input bool InpTradeAsia = true; // Trade Asia Session input int InpLondonStart = 8; // London Start Hour input int InpLondonEnd = 16; // London End Hour input int InpNYStart = 13; // NY Start Hour input int InpNYStartMin = 0; // NY Start Minute input int InpNYEnd = 21; // NY End Hour input int InpAsiaStart = 3; // Asia Start Hour input int InpAsiaEnd = 8; // Asia End Hour // Trading Settings input double InpLotSize = 0.1; // Lot Size input int InpStopLoss = 200; // Stop Loss (Points) - Backup/Safety input int InpSLBufferPoints = 10; // Stop Loss Buffer (Points) from OB/FVG input int InpMaxSLPoints = 1000; // Max Stop Loss Distance (Points) input double InpRiskRewardRatio = 2.0; // Risk:Reward Ratio input int InpMaxTradesPerSetup = 1; // Max Trades Per M5 Setup input int InpMagicNumber = 123456; // Magic Number //==================== STRUCTURES ==================== struct TradeSetup { bool active; // Is there a valid M5 setup waiting? int direction; // 1 = Buy, -1 = Sell double slPrice; // The Stop Loss price determined by M5 OB/FVG double initialSL; datetime setupTime; // When the M5 BOS/iFVG happened string setupDesc; // Description of the M5 Setup (Trigger) string confDesc; // Description of the M5 Confirmation string initialDesc; datetime lastCheckedTime; datetime m5FvgTime; // Time of the original M5 FVG (for iFVG setups) double m5FvgTop; // Top of the original M5 FVG (for iFVG setups) double m5FvgBottom; // Bottom of the original M5 FVG (for iFVG setups) }; struct SwingPoint { datetime time; double price; int type; // 1=High, -1=Low }; struct BOSPoint { datetime time; double price; int type; // 1=Bullish BOS (break above high), -1=Bearish BOS (break below low) datetime swingTime; // Time of the swing point that was broken double swingPrice; // Price of the swing point that was broken }; struct FVGPoint { datetime time; // Time of the middle candle (candle 2) double top; // Top of the FVG gap double bottom; // Bottom of the FVG gap int type; // 1=Bullish FVG (gap up), -1=Bearish FVG (gap down) double size; // Size of the gap in points bool mitigated; // True if FVG has been mitigated (iFVG) }; struct iFVGPoint { datetime time; // Time when FVG was mitigated datetime fvgTime; // Time of the original FVG double price; // Price where mitigation occurred int type; // 1=Bullish FVG mitigated (close below), -1=Bearish FVG mitigated (close above) double fvgTop; // Original FVG top double fvgBottom; // Original FVG bottom }; struct OrderBlock { datetime time; // Time of the order block (first candle in the sequence) double top; // Top of the order block double bottom; // Bottom of the order block int type; // 1=Bullish Order Block (before bearish BOS), -1=Bearish Order Block (before bullish BOS) int candleCount; // Number of consecutive candles in the order block }; //==================== GLOBALS ==================== string SYM = ""; CTrade m_trade; double point = 0.0; int g_dayOfYear = -1; TradeSetup g_pendingSetup; datetime g_lastTradedSetupTime = 0; // Track the last M5 setup that was traded int g_tradesExecutedForCurrentSetup = 0; // Count trades executed for current M5 setup datetime g_lastLoggedSetupTime = 0; // Track the last M5 setup that was logged to prevent duplicate prints datetime g_lastCheckedBarTime = 0; datetime g_lastCheckedBarTimeM1 = 0; datetime g_lastCheckedBarTimeM5 = 0; // Dynamic Arrays to store detected levels "on the run" // Arrays for specific timeframes only SwingPoint g_swingHighsM1[]; SwingPoint g_swingLowsM1[]; BOSPoint g_bosPointsM1[]; FVGPoint g_fvgPointsM1[]; iFVGPoint g_ifvgPointsM1[]; OrderBlock g_orderBlocksM1[]; SwingPoint g_swingHighsM5[]; SwingPoint g_swingLowsM5[]; BOSPoint g_bosPointsM5[]; FVGPoint g_fvgPointsM5[]; iFVGPoint g_ifvgPointsM5[]; OrderBlock g_orderBlocksM5[]; // Latest Swing Variables (Always Available) double g_LatestHighPrice = 0; datetime g_LatestHighTime = 0; double g_LatestLowPrice = 0; datetime g_LatestLowTime = 0; //==================== TRADING STRATEGY ==================== //==================== TIME HELPERS ==================== // Helper function to find chart by symbol and timeframe long FindChartBySymbolAndTimeframe(string symbol, ENUM_TIMEFRAMES timeframe) { long currChart = ChartFirst(); int limit = 100; // Safety limit to prevent infinite loop for(int i = 0; i < limit; i++) { if(currChart < 0) break; // End of chart list string chartSymbol = ChartSymbol(currChart); ENUM_TIMEFRAMES chartPeriod = ChartPeriod(currChart); if(chartSymbol == symbol && chartPeriod == timeframe) { return currChart; // Found the chart } currChart = ChartNext(currChart); // Get next chart } return 0; // Chart not found } // Helper function to get or create chart for specific timeframe long GetOrCreateChart(string symbol, ENUM_TIMEFRAMES timeframe) { // First try to find existing chart long chartId = FindChartBySymbolAndTimeframe(symbol, timeframe); if(chartId > 0) { return chartId; } // If not found, try to create new chart with retry logic for(int attempt = 0; attempt < 3; attempt++) { chartId = ChartOpen(symbol, timeframe); if(chartId > 0) { // Apply "hadi" template to the newly created chart ChartApplyTemplate(chartId, "hadi"); // Give chart time to initialize Sleep(100); return chartId; } Sleep(200); // Wait before retry } return 0; // Return 0 if all attempts fail } int DayOfYearServer() { MqlDateTime tm; TimeToStruct(TimeCurrent(), tm); return tm.day_of_year; } // Function to check if we are in allowed sessions bool InTradingSession() { if(!InpUseSessions) return true; // Master Switch OFF = 24/7 trading datetime now = TimeCurrent(); MqlDateTime tm; TimeToStruct(now, tm); int h = tm.hour; int m = tm.min; // Asia (03:00 - 08:00) bool asia = false; if(InpTradeAsia) { if(h >= InpAsiaStart && h < InpAsiaEnd) asia = true; } // London (08:00 - 16:00) bool london = false; if(InpTradeLondon) { if(h >= InpLondonStart && h < InpLondonEnd) london = true; } // New York (13:30? - 21:00) bool ny = false; if(InpTradeNY) { if(h > InpNYStart && h < InpNYEnd) ny = true; else if(h == InpNYStart && m >= InpNYStartMin) ny = true; } return (asia || london || ny); } // Check if a position is already open for this symbol and magic number bool IsPositionOpen() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == SYM && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { return true; } } } return false; } void ResetDailyCountersIfNeeded() { int d = DayOfYearServer(); if(d != g_dayOfYear) { g_dayOfYear = d; } } //==================== HELPER FUNCTIONS ==================== // Find the newest M5 FVG that formed AFTER the setup time // Returns the SL level (Bottom for Buy, Top for Sell) or 0.0 if not found double FindNewerM5FVG(int direction, datetime setupTime, string &outDescription) { double bestSL = 0.0; datetime bestTime = 0; // Earliest FVG time after setupTime string foundType = ""; int fvgCount = ArraySize(g_fvgPointsM5); for(int i = fvgCount - 1; i >= 0; i--) { // FVG can be AT or slightly before setup time (e.g. formed by the BOS/Confirmation candle sequence) // FVG time is usually middle candle, BOS time is closing candle (3rd). So FVG is 1 bar "older" than BOS. if(g_fvgPointsM5[i].time < setupTime - PeriodSeconds(PERIOD_M5)) { continue; } // Check direction: Buy (1) needs Bullish FVG (1), Sell (-1) needs Bearish FVG (-1) if(g_fvgPointsM5[i].type == direction) { // Find the FIRST one after setupTime (earliest time) if(bestTime == 0 || g_fvgPointsM5[i].time < bestTime) { bestTime = g_fvgPointsM5[i].time; // For Buy (1), SL is Bottom. For Sell (-1), SL is Top. bestSL = (direction == 1) ? g_fvgPointsM5[i].bottom : g_fvgPointsM5[i].top; foundType = (direction == 1) ? "Bullish FVG" : "Bearish FVG"; } } } if(bestSL > 0) { outDescription = foundType + " (New) at " + TimeToString(bestTime); } return bestSL; } // Find the initial confirmation (OB or FVG) at the moment of setup creation // For BOS: Priority is OB. // For iFVG: Priority is older FVG (unlikely for initial, but we might just use iFVG itself or look for support) double FindInitialConfirmation(int type, datetime triggerTime, string &outDescription, datetime minTime = 0) { double bestSL = 0.0; datetime bestTime = 0; string foundType = ""; // 1. Check Order Blocks (Priority for BOS) int obCount = ArraySize(g_orderBlocksM5); for(int i = obCount - 1; i >= 0; i--) { if(g_orderBlocksM5[i].time > triggerTime) continue; // Future relative to trigger if(minTime > 0 && g_orderBlocksM5[i].time < minTime) continue; // Logic: Bullish BOS (1) needs Bearish OB (-1) support int targetOBType = (type == 1) ? -1 : 1; if(g_orderBlocksM5[i].type == targetOBType) { if(g_orderBlocksM5[i].time > bestTime) { bestTime = g_orderBlocksM5[i].time; bestSL = (type == 1) ? g_orderBlocksM5[i].bottom : g_orderBlocksM5[i].top; foundType = (g_orderBlocksM5[i].type == 1) ? "Bullish OB" : "Bearish OB"; } break; // Found recent OB } } // 2. If no OB found, check for FVG (Fallback) if(bestSL == 0.0) { int fvgCount = ArraySize(g_fvgPointsM5); for(int i = fvgCount - 1; i >= 0; i--) { if(g_fvgPointsM5[i].time > triggerTime) continue; if(minTime > 0 && g_fvgPointsM5[i].time < minTime) continue; if(g_fvgPointsM5[i].type == type) { if(g_fvgPointsM5[i].time > bestTime) { bestTime = g_fvgPointsM5[i].time; bestSL = (type == 1) ? g_fvgPointsM5[i].bottom : g_fvgPointsM5[i].top; foundType = (type == 1) ? "Bullish FVG" : "Bearish FVG"; } break; } } } if(bestSL > 0) { outDescription = foundType + " at " + TimeToString(bestTime); } return bestSL; } // Execute Trade on M1 Confirmation void ExecuteM1Entry(int direction, double entryPrice, datetime time, string m1TriggerDesc, datetime fvgTime = 0, double fvgTop = 0, double fvgBottom = 0) { if(!InTradingSession()) return; // Respect Session Times if(IsPositionOpen()) return; // Only one trade at a time if(!g_pendingSetup.active) return; // Must have valid M5 Setup if(g_pendingSetup.direction != direction) return; // Must match direction if(time <= g_pendingSetup.setupTime) return; // Must be AFTER the M5 setup // Check if max trades per setup has been reached if(g_tradesExecutedForCurrentSetup >= InpMaxTradesPerSetup) { Print("Max trades (", InpMaxTradesPerSetup, ") already executed for current M5 setup. Skipping entry."); return; } // Validate M1 iFVG: Original FVG must be formed AFTER the M5 setup // M5 Setup Time is the open time of the M5 bar. Confirmation happens at Close (Time + 5min). // We want the M1 FVG to form strictly AFTER the M5 setup is confirmed. if(StringFind(m1TriggerDesc, "iFVG") >= 0 && fvgTime > 0) { datetime m5ConfirmationTime = g_pendingSetup.setupTime + PeriodSeconds(PERIOD_M5); if(fvgTime <= m5ConfirmationTime) { Print("Ignored M1 iFVG trigger: Original FVG (", TimeToString(fvgTime), ") formed before/at M5 setup confirmation (", TimeToString(m5ConfirmationTime), ")"); return; } } double sl = g_pendingSetup.slPrice; // Already includes buffer from CheckM5Handover string effectiveConfDesc = g_pendingSetup.confDesc; // Use initial desc by default // Check for a NEWER M5 FVG (formed after setup) to use as a tighter/better SL string newFVGDesc = ""; double newSL = FindNewerM5FVG(direction, g_pendingSetup.setupTime, newFVGDesc); // Strict rule: if the M5 setup is iFVG, we do NOT allow an entry unless a newer M5 FVG exists if(StringFind(g_pendingSetup.setupDesc, "iFVG") >= 0 && newSL <= 0) { Print("Entry blocked: M5 setup is iFVG but no newer M5 FVG formed yet for SL. Waiting..."); return; } if(newSL > 0) { // Apply Buffer to the new SL double slBuffer = InpSLBufferPoints * point; if(direction == 1) newSL -= slBuffer; else newSL += slBuffer; sl = newSL; effectiveConfDesc = newFVGDesc; // Update description for comments/logs // Update global state for record keeping (optional, but good for logs) g_pendingSetup.slPrice = newSL; g_pendingSetup.confDesc = newFVGDesc; } double tp = 0; double risk = 0; // Construct Short Comment: "5[ConfType]1[TriggerType] MM:SS" string m5ConfType = ""; if(StringFind(effectiveConfDesc, "OB") >= 0) m5ConfType = "ob"; else if(StringFind(effectiveConfDesc, "FVG") >= 0) m5ConfType = "fv"; string m1TrigType = ""; if(StringFind(m1TriggerDesc, "BOS") >= 0) m1TrigType = "bs"; else if(StringFind(m1TriggerDesc, "iFVG") >= 0) m1TrigType = "if"; MqlDateTime dt; TimeToStruct(time, dt); string comment = StringFormat("5%s1%s %02d:%02d", m5ConfType, m1TrigType, dt.hour, dt.min); // Calculate Risk & Reward if(direction == 1) // Buy { // Buffer already applied in CheckM5Handover if(sl >= entryPrice) { Print("Invalid Buy SL: SL ", sl, " >= Price ", entryPrice); return; } risk = entryPrice - sl; // Check Max SL Distance if((risk / point) > InpMaxSLPoints) { Print("Trade Skipped: Buy SL Distance ", DoubleToString(risk/point, 1), " pts > Max ", InpMaxSLPoints, " pts"); return; } tp = entryPrice + (risk * InpRiskRewardRatio); m_trade.Buy(InpLotSize, SYM, entryPrice, sl, tp, comment); } else // Sell { // Buffer already applied in CheckM5Handover if(sl <= entryPrice) { Print("Invalid Sell SL: SL ", sl, " <= Price ", entryPrice); return; } risk = sl - entryPrice; // Check Max SL Distance if((risk / point) > InpMaxSLPoints) { Print("Trade Skipped: Sell SL Distance ", DoubleToString(risk/point, 1), " pts > Max ", InpMaxSLPoints, " pts"); return; } tp = entryPrice - (risk * InpRiskRewardRatio); m_trade.Sell(InpLotSize, SYM, entryPrice, sl, tp, comment); } // Detailed Logging to Terminal Print("========================================"); Print("TRADE EXECUTED: ", comment); Print("========================================"); Print("M5 SETUP:"); Print(" Type: ", g_pendingSetup.setupDesc); Print(" Time: ", TimeToString(g_pendingSetup.setupTime)); Print(" Direction: ", (g_pendingSetup.direction == 1 ? "BULLISH" : "BEARISH")); if(StringFind(g_pendingSetup.setupDesc, "iFVG") >= 0 && g_pendingSetup.m5FvgTime > 0) { Print(" Original FVG (Broken/Mitigated):"); Print(" FVG Time: ", TimeToString(g_pendingSetup.m5FvgTime)); Print(" FVG Top: ", DoubleToString(g_pendingSetup.m5FvgTop, _Digits)); Print(" FVG Bottom: ", DoubleToString(g_pendingSetup.m5FvgBottom, _Digits)); } Print("M5 CONFIRMATION:"); Print(" Type: ", g_pendingSetup.confDesc); Print(" SL Level: ", DoubleToString(g_pendingSetup.slPrice, _Digits)); Print("M1 TRIGGER:"); Print(" Type: ", m1TriggerDesc); Print(" Time: ", TimeToString(time)); Print(" Price: ", DoubleToString(entryPrice, _Digits)); if(StringFind(m1TriggerDesc, "iFVG") >= 0 && fvgTime > 0) { Print(" Original FVG (Broken/Mitigated):"); Print(" FVG Time: ", TimeToString(fvgTime)); Print(" FVG Top: ", DoubleToString(fvgTop, _Digits)); Print(" FVG Bottom: ", DoubleToString(fvgBottom, _Digits)); } Print("TRADE DETAILS:"); Print(" Direction: ", (direction == 1 ? "BUY" : "SELL")); Print(" Entry: ", DoubleToString(entryPrice, _Digits)); Print(" SL: ", DoubleToString(sl, _Digits), " (Buffer: ", InpSLBufferPoints, " pts)"); Print(" TP: ", DoubleToString(tp, _Digits)); Print(" Risk: ", DoubleToString(risk, _Digits), " | Reward: ", DoubleToString(risk * InpRiskRewardRatio, _Digits)); Print(" RR Ratio: 1:", DoubleToString(InpRiskRewardRatio, 1)); Print(" Trade #", g_tradesExecutedForCurrentSetup + 1, " of ", InpMaxTradesPerSetup, " for this setup"); Print("========================================"); // Increment trade counter for current setup g_tradesExecutedForCurrentSetup++; // If max trades reached, deactivate setup if(g_tradesExecutedForCurrentSetup >= InpMaxTradesPerSetup) { g_lastTradedSetupTime = g_pendingSetup.setupTime; // Mark this setup as fully traded g_pendingSetup.active = false; Print("Max trades reached for this M5 setup. Setup deactivated."); } } //==================== BOS DETECTION ==================== // Internal function to register a detected BOS point void RegisterBOSPoint(datetime time, double price, int type, datetime swingTime, double swingPrice, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT) { int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS); double normPrice = NormalizeDouble(price, digits); double normSwingPrice = NormalizeDouble(swingPrice, digits); // Determine chart ID based on timeframe long chartId = 0; // Default to current chart if(timeframe == PERIOD_M1) { // M1 data is analyzed but not drawn (no chart created) chartId = 0; } else if(timeframe == PERIOD_M5) { chartId = GetOrCreateChart(SYM, PERIOD_M5); } else { return; // Only process M1 and M5 timeframes } if(timeframe == PERIOD_M5 && chartId == 0) { return; // Don't proceed if no valid chart for M5 } // Check uniqueness to avoid duplicates int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_bosPointsM1); if(s > 0 && g_bosPointsM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_bosPointsM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_bosPointsM1, s+1); g_bosPointsM1[s].time = time; g_bosPointsM1[s].price = normPrice; g_bosPointsM1[s].type = type; g_bosPointsM1[s].swingTime = swingTime; g_bosPointsM1[s].swingPrice = normSwingPrice; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_bosPointsM5); if(s > 0 && g_bosPointsM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_bosPointsM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_bosPointsM5, s+1); g_bosPointsM5[s].time = time; g_bosPointsM5[s].price = normPrice; g_bosPointsM5[s].type = type; g_bosPointsM5[s].swingTime = swingTime; g_bosPointsM5[s].swingPrice = normSwingPrice; } if(type == 1) // Bullish BOS { // Bullish BOS Detected if(timeframe == PERIOD_M1) { string triggerDesc = "Bullish BOS at " + TimeToString(time); ExecuteM1Entry(1, price, time, triggerDesc); } if(InpDrawBOS && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_BOS_Bull_"; DrawBOSMarker(chartId, prefix + TimeToString(time), time, normPrice, swingTime, normSwingPrice, clrGreen); } } else // Bearish BOS { // Bearish BOS Detected if(timeframe == PERIOD_M1) { string triggerDesc = "Bearish BOS at " + TimeToString(time); ExecuteM1Entry(-1, price, time, triggerDesc); } if(InpDrawBOS && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_BOS_Bear_"; DrawBOSMarker(chartId, prefix + TimeToString(time), time, normPrice, swingTime, normSwingPrice, clrRed); } } } // Function to detect BOS on specific timeframe void DetectBOS(ENUM_TIMEFRAMES timeframe) { // Only process M1 and M5 timeframes if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return; // Get current completed candle data (wait for candle close) MqlRates currentRates[]; ArraySetAsSeries(currentRates, true); if(CopyRates(SYM, timeframe, 1, 2, currentRates) < 2) return; // Need last completed candle and previous one double currentClose = currentRates[0].close; // Close of last completed candle double currentHigh = currentRates[0].high; double currentLow = currentRates[0].low; datetime currentTime = currentRates[0].time; // Get the latest swing high and low only (not all historical) double latestSwingHigh = 0; datetime latestSwingHighTime = 0; double latestSwingLow = 999999; datetime latestSwingLowTime = 0; // Get the last BOS type to prevent consecutive same-direction BOS int lastBOSType = 0; // 0 = none, 1 = bullish, -1 = bearish if(timeframe == PERIOD_M1) { int highCount = ArraySize(g_swingHighsM1); if(highCount > 0) { latestSwingHigh = g_swingHighsM1[highCount-1].price; latestSwingHighTime = g_swingHighsM1[highCount-1].time; } int lowCount = ArraySize(g_swingLowsM1); if(lowCount > 0) { latestSwingLow = g_swingLowsM1[lowCount-1].price; latestSwingLowTime = g_swingLowsM1[lowCount-1].time; } // Get last BOS type for M1 int bosCount = ArraySize(g_bosPointsM1); if(bosCount > 0) { lastBOSType = g_bosPointsM1[bosCount-1].type; } } else if(timeframe == PERIOD_M5) { int highCount = ArraySize(g_swingHighsM5); if(highCount > 0) { latestSwingHigh = g_swingHighsM5[highCount-1].price; latestSwingHighTime = g_swingHighsM5[highCount-1].time; } int lowCount = ArraySize(g_swingLowsM5); if(lowCount > 0) { latestSwingLow = g_swingLowsM5[lowCount-1].price; latestSwingLowTime = g_swingLowsM5[lowCount-1].time; } // Get last BOS type for M5 int bosCount = ArraySize(g_bosPointsM5); if(bosCount > 0) { lastBOSType = g_bosPointsM5[bosCount-1].type; } } // Check for Bullish BOS: Candle BODY closes above latest swing high // ONLY if last BOS was not bullish (prevent consecutive bullish BOS) if(latestSwingHigh > 0 && currentClose > latestSwingHigh && lastBOSType != 1) { // Check if this BOS was already detected bool alreadyDetected = false; if(timeframe == PERIOD_M1) { int bosCount = ArraySize(g_bosPointsM1); for(int j = 0; j < bosCount; j++) { if(g_bosPointsM1[j].type == 1 && g_bosPointsM1[j].swingTime == latestSwingHighTime) { alreadyDetected = true; break; } } } else if(timeframe == PERIOD_M5) { int bosCount = ArraySize(g_bosPointsM5); for(int j = 0; j < bosCount; j++) { if(g_bosPointsM5[j].type == 1 && g_bosPointsM5[j].swingTime == latestSwingHighTime) { alreadyDetected = true; break; } } } if(!alreadyDetected) { RegisterBOSPoint(currentTime, currentClose, 1, latestSwingHighTime, latestSwingHigh, timeframe); } } // Check for Bearish BOS: Candle BODY closes below latest swing low // ONLY if last BOS was not bearish (prevent consecutive bearish BOS) if(latestSwingLow < 999999 && currentClose < latestSwingLow && lastBOSType != -1) { // Check if this BOS was already detected bool alreadyDetected = false; if(timeframe == PERIOD_M1) { int bosCount = ArraySize(g_bosPointsM1); for(int j = 0; j < bosCount; j++) { if(g_bosPointsM1[j].type == -1 && g_bosPointsM1[j].swingTime == latestSwingLowTime) { alreadyDetected = true; break; } } } else if(timeframe == PERIOD_M5) { int bosCount = ArraySize(g_bosPointsM5); for(int j = 0; j < bosCount; j++) { if(g_bosPointsM5[j].type == -1 && g_bosPointsM5[j].swingTime == latestSwingLowTime) { alreadyDetected = true; break; } } } if(!alreadyDetected) { RegisterBOSPoint(currentTime, currentClose, -1, latestSwingLowTime, latestSwingLow, timeframe); } } } // Function to detect FVG (Fair Value Gap) on specific timeframe void DetectFVG(ENUM_TIMEFRAMES timeframe) { // Only process M1 and M5 timeframes if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return; // Get 3 most recent completed candles MqlRates rates[]; ArraySetAsSeries(rates, true); if(CopyRates(SYM, timeframe, 1, 3, rates) < 3) return; // Need 3 completed candles // Candle 1 (oldest), Candle 2 (middle), Candle 3 (newest) double candle1Low = rates[2].low; double candle1High = rates[2].high; double candle2Low = rates[1].low; double candle2High = rates[1].high; double candle3Low = rates[0].low; double candle3High = rates[0].high; datetime candle2Time = rates[1].time; double pointValue = SymbolInfoDouble(SYM, SYMBOL_POINT); double threshold = InpFVGThresholdPoints * pointValue; // Check for Bearish FVG: Candle 1 Low > Candle 3 High + threshold (gap down) if(candle1Low > candle3High + threshold) { double fvgTop = candle1Low; double fvgBottom = candle3High; double fvgSize = (fvgTop - fvgBottom) / pointValue; // Register Bearish FVG (gap down) RegisterFVGPoint(candle2Time, fvgTop, fvgBottom, -1, fvgSize, timeframe); } // Check for Bullish FVG: Candle 1 High < Candle 3 Low - threshold (gap up) if(candle1High < candle3Low - threshold) { double fvgTop = candle3Low; double fvgBottom = candle1High; double fvgSize = (fvgTop - fvgBottom) / pointValue; // Register Bullish FVG (gap up) RegisterFVGPoint(candle2Time, fvgTop, fvgBottom, 1, fvgSize, timeframe); } } // Internal function to register a detected FVG point void RegisterFVGPoint(datetime time, double top, double bottom, int type, double size, ENUM_TIMEFRAMES timeframe) { int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS); double normTop = NormalizeDouble(top, digits); double normBottom = NormalizeDouble(bottom, digits); // Determine chart ID based on timeframe long chartId = 0; // Default to current chart if(timeframe == PERIOD_M1) { // M1 data is analyzed but not drawn (no chart created) chartId = 0; } else if(timeframe == PERIOD_M5) { chartId = GetOrCreateChart(SYM, PERIOD_M5); } else { return; // Only process M1 and M5 timeframes } if(timeframe == PERIOD_M5 && chartId == 0) { return; // Don't proceed if no valid chart for M5 } // Check uniqueness to avoid duplicates int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_fvgPointsM1); if(s > 0 && g_fvgPointsM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_fvgPointsM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_fvgPointsM1, s+1); g_fvgPointsM1[s].time = time; g_fvgPointsM1[s].top = normTop; g_fvgPointsM1[s].bottom = normBottom; g_fvgPointsM1[s].type = type; g_fvgPointsM1[s].size = size; g_fvgPointsM1[s].mitigated = false; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_fvgPointsM5); if(s > 0 && g_fvgPointsM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_fvgPointsM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_fvgPointsM5, s+1); g_fvgPointsM5[s].time = time; g_fvgPointsM5[s].top = normTop; g_fvgPointsM5[s].bottom = normBottom; g_fvgPointsM5[s].type = type; g_fvgPointsM5[s].size = size; g_fvgPointsM5[s].mitigated = false; } if(type == 1) // Bullish FVG { // Bullish FVG Detected if(InpDrawFVG && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_FVG_Bull_"; DrawFVGRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrBlue); } } else // Bearish FVG { // Bearish FVG Detected if(InpDrawFVG && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_FVG_Bear_"; DrawFVGRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrWhite); } } } // Draws FVG rectangle on the specified chart void DrawFVGRectangle(long chartId, string name, datetime time, double top, double bottom, color clr) { if(!InpDrawFVG) return; if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering // Calculate rectangle time range (extend to the right) datetime endTime = time + (PeriodSeconds() * 5); // Extend 5 bars to the right (quarter of previous length) // Create rectangle for FVG if(ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, top, endTime, bottom)) { // Custom Styles based on Color (User Request) // Bullish (Blue) = Solid, Filled // Bearish (White) = Dotted, No Fill (Rectangle Outline) ENUM_LINE_STYLE style = (clr == clrWhite) ? STYLE_DOT : STYLE_SOLID; bool fill = (clr == clrWhite) ? false : true; ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, name, OBJPROP_STYLE, style); ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 1); ObjectSetInteger(chartId, name, OBJPROP_BACK, true); // Draw behind price ObjectSetInteger(chartId, name, OBJPROP_FILL, fill); // Fill the rectangle ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable // Set semi-transparent background for shadow effect (only if filled) if(fill) { color bgColor = clr; if(clr == clrGreen) bgColor = C'0,255,0,50'; // Semi-transparent green else if(clr == clrRed) bgColor = C'255,0,0,50'; // Semi-transparent red else if(clr == clrWhite) bgColor = C'255,255,255,50'; // Semi-transparent white else if(clr == clrBlue) bgColor = C'0,0,255,50'; // Semi-transparent blue ObjectSetInteger(chartId, name, OBJPROP_BGCOLOR, bgColor); } ChartRedraw(chartId); // Force update } } // Function to detect iFVG (FVG Mitigation) on specific timeframe void DetectiFVG(ENUM_TIMEFRAMES timeframe) { // Only process M1 and M5 timeframes if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return; // Get current completed candle data MqlRates currentRates[]; ArraySetAsSeries(currentRates, true); if(CopyRates(SYM, timeframe, 1, 2, currentRates) < 2) return; // Need last completed candle double currentClose = currentRates[0].close; double currentOpen = currentRates[0].open; datetime currentTime = currentRates[0].time; // Determine if current candle is bullish or bearish bool isCurrentBullish = currentClose > currentOpen; bool isCurrentBearish = currentClose < currentOpen; // Check for FVG mitigation in existing FVGs int fvgCount = 0; if(timeframe == PERIOD_M1) { fvgCount = ArraySize(g_fvgPointsM1); } else if(timeframe == PERIOD_M5) { fvgCount = ArraySize(g_fvgPointsM5); } for(int i = 0; i < fvgCount; i++) { FVGPoint fvg; if(timeframe == PERIOD_M1) { fvg = g_fvgPointsM1[i]; } else { fvg = g_fvgPointsM5[i]; } // Skip if already mitigated if(fvg.mitigated) continue; // Check for Bearish FVG mitigation: BULLISH candle body closes ABOVE FVG top if(fvg.type == -1 && isCurrentBullish && currentClose > fvg.top) { // Mark FVG as mitigated if(timeframe == PERIOD_M1) { g_fvgPointsM1[i].mitigated = true; } else { g_fvgPointsM5[i].mitigated = true; } // Register iFVG (FVG mitigation) RegisteriFVGPoint(currentTime, fvg.time, currentClose, -1, fvg.top, fvg.bottom, timeframe); } // Check for Bullish FVG mitigation: BEARISH candle body closes BELOW FVG bottom if(fvg.type == 1 && isCurrentBearish && currentClose < fvg.bottom) { // Mark FVG as mitigated if(timeframe == PERIOD_M1) { g_fvgPointsM1[i].mitigated = true; } else { g_fvgPointsM5[i].mitigated = true; } // Register iFVG (FVG mitigation) RegisteriFVGPoint(currentTime, fvg.time, currentClose, 1, fvg.top, fvg.bottom, timeframe); } } } // Internal function to register a detected iFVG (FVG mitigation) void RegisteriFVGPoint(datetime time, datetime fvgTime, double price, int type, double fvgTop, double fvgBottom, ENUM_TIMEFRAMES timeframe) { int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS); double normPrice = NormalizeDouble(price, digits); // Determine chart ID based on timeframe long chartId = 0; // Default to current chart if(timeframe == PERIOD_M1) { // M1 data is analyzed but not drawn (no chart created) chartId = 0; } else if(timeframe == PERIOD_M5) { chartId = GetOrCreateChart(SYM, PERIOD_M5); } else { return; // Only process M1 and M5 timeframes } if(timeframe == PERIOD_M5 && chartId == 0) { return; // Don't proceed if no valid chart for M5 } // Check uniqueness to avoid duplicates int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_ifvgPointsM1); if(s > 0 && g_ifvgPointsM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_ifvgPointsM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_ifvgPointsM1, s+1); g_ifvgPointsM1[s].time = time; g_ifvgPointsM1[s].fvgTime = fvgTime; g_ifvgPointsM1[s].price = normPrice; g_ifvgPointsM1[s].type = type; g_ifvgPointsM1[s].fvgTop = fvgTop; g_ifvgPointsM1[s].fvgBottom = fvgBottom; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_ifvgPointsM5); if(s > 0 && g_ifvgPointsM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_ifvgPointsM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_ifvgPointsM5, s+1); g_ifvgPointsM5[s].time = time; g_ifvgPointsM5[s].fvgTime = fvgTime; g_ifvgPointsM5[s].price = normPrice; g_ifvgPointsM5[s].type = type; g_ifvgPointsM5[s].fvgTop = fvgTop; g_ifvgPointsM5[s].fvgBottom = fvgBottom; } if(type == -1) // Bearish FVG mitigated (bullish candle closes above) { // iFVG Bearish Mitigated (Actually a Bullish Signal?) // Wait: // iFVG Point Type -1 means "Bearish FVG mitigated". // Original FVG was Bearish (Gap Down). // Mitigation means price closed ABOVE it (Bullish move). // So this is a BULLISH signal (Inversion FVG). if(timeframe == PERIOD_M1) { string triggerDesc = "Bullish iFVG at " + TimeToString(time); ExecuteM1Entry(1, price, time, triggerDesc, fvgTime, fvgTop, fvgBottom); } if(InpDrawiFVG && timeframe == PERIOD_M5) // Only draw for M5 { // Construct the name of the ORIGINAL FVG that was mitigated // Type -1 iFVG means original FVG was Bearish (-1) string originalName = "M5_FVG_Bear_" + TimeToString(fvgTime); string markerName = "M5_iFVG_Bear_" + TimeToString(time); // Use mitigation time for marker DrawiFVGVisuals(chartId, originalName, markerName, time, fvgTop, fvgBottom, clrBlue); // Bullish Signal = Blue } } else // Bullish FVG mitigated (bearish candle closes below) { // iFVG Bullish Mitigated (Actually a Bearish Signal?) // Original FVG was Bullish (Gap Up). // Mitigation means price closed BELOW it (Bearish move). // So this is a BEARISH signal (Inversion FVG). if(timeframe == PERIOD_M1) { string triggerDesc = "Bearish iFVG at " + TimeToString(time); ExecuteM1Entry(-1, price, time, triggerDesc, fvgTime, fvgTop, fvgBottom); } if(InpDrawiFVG && timeframe == PERIOD_M5) // Only draw for M5 { // Construct the name of the ORIGINAL FVG that was mitigated // Type 1 iFVG means original FVG was Bullish (1) string originalName = "M5_FVG_Bull_" + TimeToString(fvgTime); string markerName = "M5_iFVG_Bull_" + TimeToString(time); // Use mitigation time for marker DrawiFVGVisuals(chartId, originalName, markerName, time, fvgTop, fvgBottom, clrWhite); // Bearish Signal = White } } } // Draws iFVG Visuals (Modify Zone + Marker) void DrawiFVGVisuals(long chartId, string originalFVGName, string markerName, datetime time, double top, double bottom, color clr) { if(!InpDrawiFVG) return; // 1. REMOVED: Modification of Original FVG Zone (User request: "ifvg is just the x sign") // 2. Draw the Marker (X) at mitigation time string arrowName = markerName + "_Marker"; if(ObjectFind(chartId, arrowName) < 0) { double midPrice = (top + bottom) / 2.0; if(ObjectCreate(chartId, arrowName, OBJ_ARROW, 0, time, midPrice)) { ObjectSetInteger(chartId, arrowName, OBJPROP_ARROWCODE, 251); // X symbol ObjectSetInteger(chartId, arrowName, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, arrowName, OBJPROP_WIDTH, 2); ObjectSetInteger(chartId, arrowName, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(chartId, arrowName, OBJPROP_HIDDEN, false); ChartRedraw(chartId); } } } // Function to detect Order Blocks on specific timeframe void DetectOrderBlock(ENUM_TIMEFRAMES timeframe) { // Only process M1 and M5 timeframes if(timeframe != PERIOD_M1 && timeframe != PERIOD_M5) return; // Get recent BOS to check for order blocks int bosCount = 0; if(timeframe == PERIOD_M1) { bosCount = ArraySize(g_bosPointsM1); } else if(timeframe == PERIOD_M5) { bosCount = ArraySize(g_bosPointsM5); } // Check the most recent BOS for order block detection if(bosCount == 0) return; BOSPoint lastBOS; if(timeframe == PERIOD_M1) { lastBOS = g_bosPointsM1[bosCount-1]; } else { lastBOS = g_bosPointsM5[bosCount-1]; } // Get candle data for order block analysis MqlRates rates[]; ArraySetAsSeries(rates, true); // Get candles before BOS point (going backwards in time) int barsToGet = InpOrderBlockCandles; // Get extra bars to find order block if(CopyRates(SYM, timeframe, lastBOS.time, barsToGet, rates) < barsToGet) return; // Find the BOS candle in the rates array (should be at index 0 or close to it) int bosCandleIndex = -1; for(int i = 0; i < ArraySize(rates); i++) { if(rates[i].time == lastBOS.time) { bosCandleIndex = i; break; } } if(bosCandleIndex == -1) return; // Look backwards from BOS to find consecutive opposite-direction candles int consecutiveCount = 0; double highestHigh = 0; double lowestLow = 999999; datetime firstCandleTime = 0; bool foundOrderBlock = false; if(lastBOS.type == 1) // Bullish BOS - look for consecutive bearish candles { // Start looking backwards from BOS for first bearish candle int startIdx = bosCandleIndex + 1; // Skip same-direction candles (blue for bullish BOS) to find first opposite (red) while(startIdx < ArraySize(rates) && rates[startIdx].close > rates[startIdx].open) { startIdx++; // Skip bullish candles } // Now collect consecutive bearish candles for(int i = startIdx; i < ArraySize(rates) && consecutiveCount < InpOrderBlockCandles; i++) { if(rates[i].close < rates[i].open) // Bearish candle { if(consecutiveCount == 0) firstCandleTime = rates[i].time; consecutiveCount++; if(rates[i].high > highestHigh) highestHigh = rates[i].high; if(rates[i].low < lowestLow) lowestLow = rates[i].low; foundOrderBlock = true; } else { break; // Stop at first non-bearish candle } } } else if(lastBOS.type == -1) // Bearish BOS - look for consecutive bullish candles { // Start looking backwards from BOS for first bullish candle int startIdx = bosCandleIndex + 1; // Skip same-direction candles (red for bearish BOS) to find first opposite (green) while(startIdx < ArraySize(rates) && rates[startIdx].close < rates[startIdx].open) { startIdx++; // Skip bearish candles } // Now collect consecutive bullish candles for(int i = startIdx; i < ArraySize(rates) && consecutiveCount < InpOrderBlockCandles; i++) { if(rates[i].close > rates[i].open) // Bullish candle { if(consecutiveCount == 0) firstCandleTime = rates[i].time; consecutiveCount++; if(rates[i].high > highestHigh) highestHigh = rates[i].high; if(rates[i].low < lowestLow) lowestLow = rates[i].low; foundOrderBlock = true; } else { break; // Stop at first non-bullish candle } } } // Register order block if found if(foundOrderBlock && consecutiveCount >= 1) { int obType = (lastBOS.type == 1) ? -1 : 1; // Opposite of BOS type RegisterOrderBlock(firstCandleTime, highestHigh, lowestLow, obType, consecutiveCount, timeframe); } } // Internal function to register a detected Order Block void RegisterOrderBlock(datetime time, double top, double bottom, int type, int candleCount, ENUM_TIMEFRAMES timeframe) { int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS); double normTop = NormalizeDouble(top, digits); double normBottom = NormalizeDouble(bottom, digits); // Determine chart ID based on timeframe long chartId = 0; // Default to current chart if(timeframe == PERIOD_M1) { // M1 data is analyzed but not drawn (no chart created) chartId = 0; } else if(timeframe == PERIOD_M5) { chartId = GetOrCreateChart(SYM, PERIOD_M5); } else { return; // Only process M1 and M5 timeframes } if(timeframe == PERIOD_M5 && chartId == 0) { return; // Don't proceed if no valid chart for M5 } // Check uniqueness to avoid duplicates int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_orderBlocksM1); if(s > 0 && g_orderBlocksM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_orderBlocksM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_orderBlocksM1, s+1); g_orderBlocksM1[s].time = time; g_orderBlocksM1[s].top = normTop; g_orderBlocksM1[s].bottom = normBottom; g_orderBlocksM1[s].type = type; g_orderBlocksM1[s].candleCount = candleCount; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_orderBlocksM5); if(s > 0 && g_orderBlocksM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_orderBlocksM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_orderBlocksM5, s+1); g_orderBlocksM5[s].time = time; g_orderBlocksM5[s].top = normTop; g_orderBlocksM5[s].bottom = normBottom; g_orderBlocksM5[s].type = type; g_orderBlocksM5[s].candleCount = candleCount; } if(type == -1) // Bearish Order Block (before bullish BOS) { // Bearish Order Block if(InpDrawOrderBlock && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_OB_Bear_"; DrawOrderBlockRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrRed); } } else // Bullish Order Block (before bearish BOS) { // Bullish Order Block if(InpDrawOrderBlock && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_OB_Bull_"; DrawOrderBlockRectangle(chartId, prefix + TimeToString(time), time, normTop, normBottom, clrGreen); } } } void DrawOrderBlockRectangle(long chartId, string name, datetime time, double top, double bottom, color clr) { if(!InpDrawOrderBlock) return; if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering // Calculate rectangle time range (extend to the right) datetime endTime = time + (PeriodSeconds() * 5); // Extend 30 bars to the right // Create rectangle for Order Block if(ObjectCreate(chartId, name, OBJ_RECTANGLE, 0, time, top, endTime, bottom)) { ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 2); ObjectSetInteger(chartId, name, OBJPROP_BACK, true); // Draw behind price ObjectSetInteger(chartId, name, OBJPROP_FILL, true); // Fill the rectangle ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable // Set semi-transparent background color bgColor = clr; if(clr == clrGreen) bgColor = C'0,255,0,30'; // Very transparent green else if(clr == clrRed) bgColor = C'255,0,0,30'; // Very transparent red ObjectSetInteger(chartId, name, OBJPROP_BGCOLOR, bgColor); ChartRedraw(chartId); // Force update } } //==================== DETECTION & VISUALS ==================== // Draws a short horizontal line from wick to the right for swing points void DrawHighLow(long chartId, string name, datetime time, double price, color clr) { if(!InpDrawHighLow) return; if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering // Calculate end time (extend a few bars to the right) datetime endTime = time + (PeriodSeconds() * 2); // Extend 2 bars to the right (half the previous length) // Create short horizontal trendline instead of arrow if(ObjectCreate(chartId, name, OBJ_TREND, 0, time, price, endTime, price)) { ObjectSetInteger(chartId, name, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, name, OBJPROP_WIDTH, 2); ObjectSetInteger(chartId, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(chartId, name, OBJPROP_HIDDEN, false); // Visible in object list ObjectSetInteger(chartId, name, OBJPROP_SELECTABLE, true); // Selectable ObjectSetInteger(chartId, name, OBJPROP_BACK, false); // Bring to front ObjectSetInteger(chartId, name, OBJPROP_RAY_RIGHT, false); // Don't extend infinitely ChartRedraw(chartId); // Force update } } // Draws BOS marker with line connecting to broken swing point void DrawBOSMarker(long chartId, string name, datetime bosTime, double bosPrice, datetime swingTime, double swingPrice, color clr) { if(!InpDrawBOS) return; if(ObjectFind(chartId, name) >= 0) return; // Avoid flickering // Create BOS arrow string arrowName = name + "_Arrow"; if(ObjectCreate(chartId, arrowName, OBJ_ARROW, 0, bosTime, bosPrice)) { ObjectSetInteger(chartId, arrowName, OBJPROP_ARROWCODE, 236); // Diamond symbol ObjectSetInteger(chartId, arrowName, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, arrowName, OBJPROP_WIDTH, 3); ObjectSetInteger(chartId, arrowName, OBJPROP_ANCHOR, ANCHOR_CENTER); ObjectSetInteger(chartId, arrowName, OBJPROP_HIDDEN, false); ObjectSetInteger(chartId, arrowName, OBJPROP_SELECTABLE, true); } // Create trendline from swing point to BOS point string lineName = name + "_Line"; if(ObjectCreate(chartId, lineName, OBJ_TREND, 0, swingTime, swingPrice, bosTime, bosPrice)) { ObjectSetInteger(chartId, lineName, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, lineName, OBJPROP_WIDTH, 2); ObjectSetInteger(chartId, lineName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(chartId, lineName, OBJPROP_HIDDEN, false); ObjectSetInteger(chartId, lineName, OBJPROP_SELECTABLE, true); ObjectSetInteger(chartId, lineName, OBJPROP_RAY_RIGHT, false); } // Create label at swing point string labelName = name + "_Label"; string labelText = StringFormat("BOS %.5f", swingPrice); if(ObjectCreate(chartId, labelName, OBJ_TEXT, 0, swingTime, swingPrice)) { ObjectSetString(chartId, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(chartId, labelName, OBJPROP_COLOR, clr); ObjectSetInteger(chartId, labelName, OBJPROP_FONTSIZE, 8); ObjectSetInteger(chartId, labelName, OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER); ObjectSetInteger(chartId, labelName, OBJPROP_HIDDEN, false); ObjectSetInteger(chartId, labelName, OBJPROP_SELECTABLE, true); } ChartRedraw(chartId); // Force update } // Internal function to register a detected point void RegisterSwingPoint(datetime time, double price, int type, ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT) { int digits = (int)SymbolInfoInteger(SYM, SYMBOL_DIGITS); double normPrice = NormalizeDouble(price, digits); // Determine chart ID based on timeframe long chartId = 0; // Default to current chart if(timeframe == PERIOD_M1) { // M1 data is analyzed but not drawn (no chart created) chartId = 0; } else if(timeframe == PERIOD_M5) { chartId = GetOrCreateChart(SYM, PERIOD_M5); } else { return; // Only process M1 and M5 timeframes } if(timeframe == PERIOD_M5 && chartId == 0) { return; // Don't proceed if no valid chart for M5 } // Check uniqueness to avoid duplicates (especially during live runs) if(type == 1) // High { int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_swingHighsM1); if(s > 0 && g_swingHighsM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_swingHighsM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_swingHighsM1, s+1); g_swingHighsM1[s].time = time; g_swingHighsM1[s].price = normPrice; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_swingHighsM5); if(s > 0 && g_swingHighsM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_swingHighsM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_swingHighsM5, s+1); g_swingHighsM5[s].time = time; g_swingHighsM5[s].price = normPrice; } // High Detected if(InpDrawHighLow && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_High_"; DrawHighLow(chartId, prefix + TimeToString(time), time, normPrice, clrLime); } } else // Low { int s = 0; if(timeframe == PERIOD_M1) { s = ArraySize(g_swingLowsM1); if(s > 0 && g_swingLowsM1[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_swingLowsM1, 0, 1); s--; // Decrement size after removal } ArrayResize(g_swingLowsM1, s+1); g_swingLowsM1[s].time = time; g_swingLowsM1[s].price = normPrice; } else if(timeframe == PERIOD_M5) { s = ArraySize(g_swingLowsM5); if(s > 0 && g_swingLowsM5[s-1].time == time) return; // Already exists // Enforce History Limit (FIFO) if(s >= InpHistoryLimit){ ArrayRemove(g_swingLowsM5, 0, 1); s--; // Decrement size after removal } ArrayResize(g_swingLowsM5, s+1); g_swingLowsM5[s].time = time; g_swingLowsM5[s].price = normPrice; } // Low Detected if(InpDrawHighLow && timeframe == PERIOD_M5) // Only draw for M5 { string prefix = "M5_Low_"; DrawHighLow(chartId, prefix + TimeToString(time), time, normPrice, clrRed); } } } // Function to scan HISTORY logic for specific timeframe void InitHistoryTimeframe(ENUM_TIMEFRAMES timeframe) { int Lookback = InpLookback; MqlRates rates[]; ArraySetAsSeries(rates, true); // Index 0 is newest int copied = CopyRates(SYM, timeframe, 1, Lookback + 2, rates); // Start from Bar 1 (skip forming) if(copied < Lookback + 2) return; for(int i = Lookback - 1; i >= 0; i--) { bool bullRight = rates[i].close > rates[i].open; bool bearRight = rates[i].close < rates[i].open; bool bullLeft = rates[i+1].close > rates[i+1].open; bool bearLeft = rates[i+1].close < rates[i+1].open; // Low: Bearish (i+1) -> Bullish (i) if(bearLeft && bullRight) { double lowPrice = MathMin(rates[i].low, rates[i+1].low); datetime lowTime = (rates[i].low <= rates[i+1].low) ? rates[i].time : rates[i+1].time; RegisterSwingPoint(lowTime, lowPrice, -1, timeframe); } // High: Bullish (i+1) -> Bearish (i) if(bullLeft && bearRight) { double highPrice = MathMax(rates[i].high, rates[i+1].high); datetime highTime = (rates[i].high >= rates[i+1].high) ? rates[i].time : rates[i+1].time; RegisterSwingPoint(highTime, highPrice, 1, timeframe); } } } // Function to scan HISTORY logic (runs once on Init) void InitHistory() { // Initialize history for M1 and M5 timeframes only InitHistoryTimeframe(PERIOD_M1); InitHistoryTimeframe(PERIOD_M5); } // Function to check LATEST signals for specific timeframe void CheckLivePatternTimeframe(ENUM_TIMEFRAMES timeframe, datetime &lastCheckedBarTime) { MqlRates rates[]; ArraySetAsSeries(rates, true); if(CopyRates(SYM, timeframe, 1, 3, rates) < 3) return; // Need Bar 1 and Bar 2 // Dedup: Only process if we haven't checked this confirmation bar yet if(rates[0].time == lastCheckedBarTime) return; lastCheckedBarTime = rates[0].time; // Mark confirmation bar as processed bool bullRight = rates[0].close > rates[0].open; bool bearRight = rates[0].close < rates[0].open; bool bullLeft = rates[1].close > rates[1].open; bool bearLeft = rates[1].close < rates[1].open; // Low: Bearish (Left/Bar2) -> Bullish (Right/Bar1) if(bearLeft && bullRight) { double lowPrice = MathMin(rates[0].low, rates[1].low); datetime lowTime = (rates[0].low <= rates[1].low) ? rates[0].time : rates[1].time; RegisterSwingPoint(lowTime, lowPrice, -1, timeframe); } // High: Bullish (Left/Bar2) -> Bearish (Right/Bar1) if(bullLeft && bearRight) { double highPrice = MathMax(rates[0].high, rates[1].high); datetime highTime = (rates[0].high >= rates[1].high) ? rates[0].time : rates[1].time; RegisterSwingPoint(highTime, highPrice, 1, timeframe); } } //==================== INIT ==================== int OnInit() { SYM = (StringLen(InpSymbol) == 0 ? _Symbol : InpSymbol); point = SymbolInfoDouble(SYM, SYMBOL_POINT); m_trade.SetExpertMagicNumber(InpMagicNumber); g_dayOfYear = DayOfYearServer(); // Reset Global Setup g_pendingSetup.active = false; g_pendingSetup.direction = 0; g_pendingSetup.slPrice = 0; g_pendingSetup.initialSL = 0; g_pendingSetup.setupTime = 0; g_pendingSetup.initialDesc = ""; g_pendingSetup.lastCheckedTime = 0; // Initialize chart for M5 timeframe only (M1 data will be analyzed but no chart created) long m5Chart = GetOrCreateChart(SYM, PERIOD_M5); // Clear Arrays ArrayResize(g_swingHighsM1, 0); ArrayResize(g_swingLowsM1, 0); ArrayResize(g_bosPointsM1, 0); ArrayResize(g_fvgPointsM1, 0); ArrayResize(g_ifvgPointsM1, 0); ArrayResize(g_orderBlocksM1, 0); ArrayResize(g_swingHighsM5, 0); ArrayResize(g_swingLowsM5, 0); ArrayResize(g_bosPointsM5, 0); ArrayResize(g_fvgPointsM5, 0); ArrayResize(g_ifvgPointsM5, 0); ArrayResize(g_orderBlocksM5, 0); // Populate History Once InitHistory(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { // Clean up M5 chart if it exists (M1 data was analyzed but no chart was created) long m5ChartId = FindChartBySymbolAndTimeframe(SYM, PERIOD_M5); if(m5ChartId > 0) { ObjectsDeleteAll(m5ChartId, "M5_High_"); ObjectsDeleteAll(m5ChartId, "M5_Low_"); ObjectsDeleteAll(m5ChartId, "M5_BOS_Bull_"); ObjectsDeleteAll(m5ChartId, "M5_BOS_Bear_"); ObjectsDeleteAll(m5ChartId, "M5_FVG_"); ObjectsDeleteAll(m5ChartId, "M5_iFVG_"); ObjectsDeleteAll(m5ChartId, "M5_OB_"); } } // Check M5 Handover at the end of the tick // This ensures that Order Blocks detected *after* the BOS are included in the confirmation search void CheckM5Handover() { datetime lastM5Time = iTime(SYM, PERIOD_M5, 1); // Check M5 BOS int bosCount = ArraySize(g_bosPointsM5); if(bosCount > 0) { BOSPoint bos = g_bosPointsM5[bosCount-1]; if(bos.time == lastM5Time) // New BOS { // Prevent re-activating a setup we just traded if(bos.time == g_lastTradedSetupTime) return; int dir = (bos.type == 1) ? 1 : -1; string confDesc = ""; double confSL = FindInitialConfirmation(dir, bos.time, confDesc); if(confSL > 0) { // Apply SL Buffer immediately double slBuffer = InpSLBufferPoints * point; if(dir == 1) confSL -= slBuffer; else confSL += slBuffer; g_pendingSetup.active = true; g_pendingSetup.direction = dir; g_pendingSetup.slPrice = confSL; // Initial Best Price g_pendingSetup.initialSL = confSL; // Fallback g_pendingSetup.setupTime = bos.time; g_pendingSetup.setupDesc = (dir==1?"Bullish":"Bearish") + " BOS at " + TimeToString(bos.time); g_pendingSetup.confDesc = confDesc; g_pendingSetup.initialDesc = confDesc; g_pendingSetup.lastCheckedTime = bos.time; g_tradesExecutedForCurrentSetup = 0; // Reset trade counter for new setup // Only print if this setup hasn't been logged yet if(bos.time != g_lastLoggedSetupTime) { Print("M5 Setup Found: BOS ", (dir==1?"Bull":"Bear"), " at ", TimeToString(bos.time), ". SL: ", confSL); g_lastLoggedSetupTime = bos.time; } } } } // Check M5 iFVG int ifvgCount = ArraySize(g_ifvgPointsM5); if(ifvgCount > 0) { iFVGPoint ifvg = g_ifvgPointsM5[ifvgCount-1]; if(ifvg.time == lastM5Time) // New iFVG { // Prevent re-activating a setup we just traded if(ifvg.time == g_lastTradedSetupTime) return; // iFVG logic: type -1 (Bearish Mitigated) -> Bullish Signal (1) // type 1 (Bullish Mitigated) -> Bearish Signal (-1) int dir = (ifvg.type == -1) ? 1 : -1; string confDesc = ""; // Pass ifvg.time as minTime to ensure confirmation is at or after the iFVG mitigation time double confSL = FindInitialConfirmation(dir, ifvg.time, confDesc, ifvg.time); if(confSL > 0) { // Apply SL Buffer immediately double slBuffer = InpSLBufferPoints * point; if(dir == 1) confSL -= slBuffer; else confSL += slBuffer; g_pendingSetup.active = true; g_pendingSetup.direction = dir; g_pendingSetup.slPrice = confSL; // Initial Best Price g_pendingSetup.initialSL = confSL; // Fallback g_pendingSetup.setupTime = ifvg.time; g_pendingSetup.setupDesc = (dir==1?"Bullish":"Bearish") + " iFVG at " + TimeToString(ifvg.time); g_pendingSetup.confDesc = confDesc; g_pendingSetup.initialDesc = confDesc; g_pendingSetup.lastCheckedTime = ifvg.time; g_pendingSetup.m5FvgTime = ifvg.fvgTime; g_pendingSetup.m5FvgTop = ifvg.fvgTop; g_pendingSetup.m5FvgBottom = ifvg.fvgBottom; g_tradesExecutedForCurrentSetup = 0; // Reset trade counter for new setup // Only print if this setup hasn't been logged yet if(ifvg.time != g_lastLoggedSetupTime) { Print("M5 Setup Found: iFVG ", (dir==1?"Bull":"Bear"), " at ", TimeToString(ifvg.time), ". SL: ", confSL); g_lastLoggedSetupTime = ifvg.time; } } } } } //==================== TICK ==================== void OnTick() { ResetDailyCountersIfNeeded(); // -- Real-Time Check for all timeframes -- CheckLivePatternTimeframe(PERIOD_M1, g_lastCheckedBarTimeM1); CheckLivePatternTimeframe(PERIOD_M5, g_lastCheckedBarTimeM5); // -- BOS Detection on 1min and 5min timeframes -- DetectBOS(PERIOD_M1); // 1-minute timeframe DetectBOS(PERIOD_M5); // 5-minute timeframe // -- FVG Detection on 1min and 5min timeframes -- DetectFVG(PERIOD_M1); // 1-minute timeframe DetectFVG(PERIOD_M5); // 5-minute timeframe // -- iFVG Detection on 1min and 5min timeframes -- DetectiFVG(PERIOD_M1); // 1-minute timeframe DetectiFVG(PERIOD_M5); // 5-minute timeframe // -- Order Block Detection on 1min and 5min timeframes -- DetectOrderBlock(PERIOD_M1); // 1-minute timeframe DetectOrderBlock(PERIOD_M5); // 5-minute timeframe // -- Check for M5 Handover (Late Update to catch Order Blocks) -- CheckM5Handover(); } //==================== END OF EA ====================