//+------------------------------------------------------------------+ //| SniperEA.mq5 | //| MT5 Sniper Strategy Expert Advisor | //| OB + BOS + Liquidity Sweep + FVG | //+------------------------------------------------------------------+ #property copyright "Sniper Strategy EA" #property link "" #property version "1.00" //--- Include files #include #include #include //--- Global objects CTrade trade; CPositionInfo position; COrderInfo order; //--- Input parameters input group "=== Core Settings ===" input int MaxTradesPerDay = 3; // Maximum trades per symbol per day input double RiskPercent = 1.0; // Risk percentage per trade input double MinRR = 2.0; // Minimum risk-reward ratio input bool UseTimeFilter = true; // Enable session filtering input group "=== Session Settings ===" input string AsiaStart = "00:00"; // Asia session start (GMT) input string AsiaEnd = "09:00"; // Asia session end (GMT) input string LondonStart = "08:00"; // London session start (GMT) input string LondonEnd = "17:00"; // London session end (GMT) input string NYStart = "13:00"; // New York session start (GMT) input string NYEnd = "22:00"; // New York session end (GMT) input group "=== Risk Management ===" input int MaxSL = 50; // Maximum stop loss in pips input int MinSL = 10; // Minimum stop loss in pips input double MaxSlippage = 2.0; // Maximum slippage in pips input int MaxPositions = 10; // Maximum total positions input int MaxPositionsPerSymbol = 3; // Maximum positions per symbol input group "=== Pattern Detection ===" input int OBLookback = 20; // Order Block lookback candles input double MinFVGSize = 3.0; // Minimum FVG size in pips input double MinSweepDistance = 5.0; // Minimum sweep distance in pips input int BOSConfirmationCandles = 3; // BOS confirmation within candles input int SwingLookback = 10; // Swing high/low lookback period input double OBStrengthFilter = 0.5; // Order Block strength filter (0-1) input bool RequireMultiTFConfirmation = true; // Require multi-timeframe confirmation input group "=== Visualization ===" input bool ShowOrderBlocks = true; // Show Order Block zones input bool ShowFVG = true; // Show Fair Value Gaps input bool ShowBOS = true; // Show Break of Structure input bool ShowSweeps = true; // Show Liquidity Sweeps input bool ShowTradeLevels = true; // Show Entry/SL/TP levels input group "=== Symbols to Trade ===" input string Symbol1 = "EURUSD"; // Symbol 1 input string Symbol2 = "GBPUSD"; // Symbol 2 input string Symbol3 = "USDJPY"; // Symbol 3 input string Symbol4 = "USDCHF"; // Symbol 4 input string Symbol5 = "AUDUSD"; // Symbol 5 input string Symbol6 = "USDCAD"; // Symbol 6 input string Symbol7 = "NZDUSD"; // Symbol 7 input string Symbol8 = "XAUUSD"; // Symbol 8 (Gold) input group "=== Logging & Debug ===" input bool EnableDetailedLogging = true; // Enable detailed logging input bool EnableDebugMode = true; // Enable debug mode input bool LogPatternDetection = true; // Log pattern detection events input bool LogTradeExecution = true; // Log trade execution details //--- Global variables string SymbolsToTrade[]; int TotalSymbols = 0; datetime LastBarTime = 0; bool IsInitialized = false; string LogPrefix = "SniperEA"; int LogLevel = 0; // 0=Info, 1=Warning, 2=Error, 3=Debug //--- Structure definitions struct OrderBlock { double high; double low; datetime time; bool is_bullish; bool is_fresh; int strength; }; struct FairValueGap { double top; double bottom; datetime time; bool is_bullish; bool is_filled; }; struct LiquiditySweep { double level; datetime time; bool is_high_sweep; bool confirmed; }; struct BreakOfStructure { double level; datetime time; bool is_bullish; bool confirmed; }; //--- Function declarations bool ConfirmBOS(string symbol, ENUM_TIMEFRAMES timeframe, int break_bar, bool is_bullish, double level); //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("=== Sniper EA Initialization Started ==="); // Initialize trade object trade.SetExpertMagicNumber(123456); trade.SetDeviationInPoints((int)(MaxSlippage * 10)); trade.SetTypeFilling(ORDER_FILLING_FOK); // Setup symbols array if (!SetupSymbolsArray()) { Print("ERROR: Failed to setup symbols array"); return INIT_FAILED; } // Validate input parameters if (!ValidateInputs()) { Print("ERROR: Invalid input parameters"); return INIT_FAILED; } // Initialize chart objects if (!InitializeChartObjects()) { Print("ERROR: Failed to initialize chart objects"); return INIT_FAILED; } // Initialize multi-timeframe analysis if (!InitializeMultiTimeframeAnalysis()) { Print("ERROR: Failed to initialize multi-timeframe analysis"); return INIT_FAILED; } IsInitialized = true; LastBarTime = iTime(_Symbol, PERIOD_M1, 0); Print("=== Sniper EA Initialization Completed Successfully ==="); Print("Trading Symbols: ", TotalSymbols); Print("Risk per Trade: ", RiskPercent, "%"); Print("Minimum R:R Ratio: ", MinRR, ":1"); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Print("=== Sniper EA Deinitialization Started ==="); // Clean up chart objects CleanupChartObjects(); // Print deinitialization reason string deinit_reason = ""; switch (reason) { case REASON_PROGRAM: deinit_reason = "Expert Advisor terminated"; break; case REASON_REMOVE: deinit_reason = "Expert Advisor removed from chart"; break; case REASON_RECOMPILE: deinit_reason = "Expert Advisor recompiled"; break; case REASON_CHARTCHANGE: deinit_reason = "Chart symbol or period changed"; break; case REASON_CHARTCLOSE: deinit_reason = "Chart closed"; break; case REASON_PARAMETERS: deinit_reason = "Input parameters changed"; break; case REASON_ACCOUNT: deinit_reason = "Account changed"; break; default: deinit_reason = "Unknown reason"; break; } Print("Deinitialization Reason: ", deinit_reason); Print("=== Sniper EA Deinitialization Completed ==="); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if (!IsInitialized) return; // Check for new bar datetime current_bar_time = iTime(_Symbol, PERIOD_M1, 0); if (current_bar_time == LastBarTime) return; LastBarTime = current_bar_time; // Main trading logic will be implemented here ProcessTradingLogic(); } //+------------------------------------------------------------------+ //| Setup symbols array from input parameters | //+------------------------------------------------------------------+ bool SetupSymbolsArray() { ArrayResize(SymbolsToTrade, 0); TotalSymbols = 0; string symbols[8] = {Symbol1, Symbol2, Symbol3, Symbol4, Symbol5, Symbol6, Symbol7, Symbol8}; for (int i = 0; i < 8; i++) { if (symbols[i] != "" && symbols[i] != "NONE") { ArrayResize(SymbolsToTrade, TotalSymbols + 1); SymbolsToTrade[TotalSymbols] = symbols[i]; TotalSymbols++; } } return TotalSymbols > 0; } //+------------------------------------------------------------------+ //| Validate input parameters | //+------------------------------------------------------------------+ bool ValidateInputs() { if (RiskPercent <= 0 || RiskPercent > 10) { Print("ERROR: Risk percent must be between 0 and 10"); return false; } if (MinRR < 1.0) { Print("ERROR: Minimum R:R ratio must be at least 1.0"); return false; } if (MaxSL <= MinSL) { Print("ERROR: Maximum SL must be greater than Minimum SL"); return false; } if (MaxTradesPerDay <= 0) { Print("ERROR: Max trades per day must be positive"); return false; } return true; } //+------------------------------------------------------------------+ //| Initialize chart objects | //+------------------------------------------------------------------+ bool InitializeChartObjects() { // Set chart properties for better visualization ChartSetInteger(0, CHART_SHOW_GRID, false); ChartSetInteger(0, CHART_SHOW_VOLUMES, false); ChartSetInteger(0, CHART_SHOW_OHLC, true); // Create information panel background if (ObjectCreate(0, "SniperEA_InfoPanel", OBJ_RECTANGLE_LABEL, 0, 0, 0)) { ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_XDISTANCE, 10); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_YDISTANCE, 30); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_XSIZE, 250); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_YSIZE, 150); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BGCOLOR, clrDarkSlateGray); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BORDER_TYPE, BORDER_FLAT); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_WIDTH, 1); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_BACK, false); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_SELECTABLE, false); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_SELECTED, false); ObjectSetInteger(0, "SniperEA_InfoPanel", OBJPROP_HIDDEN, true); } // Create EA status label if (ObjectCreate(0, "SniperEA_Status", OBJ_LABEL, 0, 0, 0)) { ObjectSetInteger(0, "SniperEA_Status", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_XDISTANCE, 20); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_YDISTANCE, 40); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_COLOR, clrLime); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_FONTSIZE, 10); ObjectSetString(0, "SniperEA_Status", OBJPROP_FONT, "Arial Bold"); ObjectSetString(0, "SniperEA_Status", OBJPROP_TEXT, "Sniper EA - ACTIVE"); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_SELECTABLE, false); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_SELECTED, false); ObjectSetInteger(0, "SniperEA_Status", OBJPROP_HIDDEN, true); } Print("Chart objects initialized successfully"); return true; } //+------------------------------------------------------------------+ //| Clean up chart objects | //+------------------------------------------------------------------+ void CleanupChartObjects() { // Clean up all chart objects created by the EA int total_objects = ObjectsDeleteAll(0, "SniperEA_"); Print("Cleaned up ", total_objects, " chart objects"); } //+------------------------------------------------------------------+ //| Update information panel | //+------------------------------------------------------------------+ void UpdateInfoPanel() { // Get current session string current_session = GetCurrentSession(); // Get account information double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); double account_margin = AccountInfoDouble(ACCOUNT_MARGIN); // Count current positions int total_positions = PositionsTotal(); // Create info text string info_text = StringFormat( "Session: %s\n" + "Balance: %.2f\n" + "Equity: %.2f\n" + "Margin: %.2f\n" + "Positions: %d/%d", current_session, account_balance, account_equity, account_margin, total_positions, MaxPositions); // Update info label if (ObjectFind(0, "SniperEA_Info") < 0) { ObjectCreate(0, "SniperEA_Info", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_XDISTANCE, 20); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_YDISTANCE, 60); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_COLOR, clrWhite); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_FONTSIZE, 8); ObjectSetString(0, "SniperEA_Info", OBJPROP_FONT, "Courier New"); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_SELECTABLE, false); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_SELECTED, false); ObjectSetInteger(0, "SniperEA_Info", OBJPROP_HIDDEN, true); } ObjectSetString(0, "SniperEA_Info", OBJPROP_TEXT, info_text); } //+------------------------------------------------------------------+ //| Get current trading session | //+------------------------------------------------------------------+ string GetCurrentSession() { MqlDateTime dt; TimeToStruct(TimeGMT(), dt); int current_hour = dt.hour; int current_minute = dt.min; int current_time_minutes = current_hour * 60 + current_minute; // Convert session times to minutes int asia_start = (int)(StringToTime("1970.01.01 " + AsiaStart) % 86400 / 60); int asia_end = (int)(StringToTime("1970.01.01 " + AsiaEnd) % 86400 / 60); int london_start = (int)(StringToTime("1970.01.01 " + LondonStart) % 86400 / 60); int london_end = (int)(StringToTime("1970.01.01 " + LondonEnd) % 86400 / 60); int ny_start = (int)(StringToTime("1970.01.01 " + NYStart) % 86400 / 60); int ny_end = (int)(StringToTime("1970.01.01 " + NYEnd) % 86400 / 60); // Check which session we're in if ((current_time_minutes >= asia_start && current_time_minutes < asia_end) || (asia_start > asia_end && (current_time_minutes >= asia_start || current_time_minutes < asia_end))) return "ASIA"; if ((current_time_minutes >= london_start && current_time_minutes < london_end) || (london_start > london_end && (current_time_minutes >= london_start || current_time_minutes < london_end))) return "LONDON"; if ((current_time_minutes >= ny_start && current_time_minutes < ny_end) || (ny_start > ny_end && (current_time_minutes >= ny_start || current_time_minutes < ny_end))) return "NEW YORK"; return "OFF HOURS"; } //+------------------------------------------------------------------+ //| Trade Execution Functions | //+------------------------------------------------------------------+ bool ExecuteBuyTrade(string symbol, double entry, double sl, double tp, double lot_size) { // Validate trade parameters if (!ValidateTradeParameters(symbol, true, entry, sl, tp, lot_size)) { LogError(StringFormat("Invalid buy trade parameters for %s", symbol)); return false; } // Normalize prices entry = NormalizePrice(symbol, entry); sl = NormalizePrice(symbol, sl); tp = NormalizePrice(symbol, tp); // Execute buy trade bool result = trade.Buy(lot_size, symbol, entry, sl, tp, "Sniper EA Buy"); if (result) { LogTrade("BUY EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size)); return true; } else { int error_code = trade.ResultRetcode(); HandleTradeError(error_code, "Buy Trade Execution"); return false; } } bool ExecuteSellTrade(string symbol, double entry, double sl, double tp, double lot_size) { // Validate trade parameters if (!ValidateTradeParameters(symbol, false, entry, sl, tp, lot_size)) { LogError(StringFormat("Invalid sell trade parameters for %s", symbol)); return false; } // Normalize prices entry = NormalizePrice(symbol, entry); sl = NormalizePrice(symbol, sl); tp = NormalizePrice(symbol, tp); // Execute sell trade bool result = trade.Sell(lot_size, symbol, entry, sl, tp, "Sniper EA Sell"); if (result) { LogTrade("SELL EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size)); return true; } else { int error_code = trade.ResultRetcode(); HandleTradeError(error_code, "Sell Trade Execution"); return false; } } bool ValidateTradeParameters(string symbol, bool is_buy, double entry, double sl, double tp, double lot_size) { // Check symbol validity if (!SymbolSelect(symbol, true)) { LogError(StringFormat("Symbol %s not available", symbol)); return false; } // Check lot size double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if (lot_size < min_lot || lot_size > max_lot) { LogError(StringFormat("Invalid lot size %.2f for %s (min: %.2f, max: %.2f)", lot_size, symbol, min_lot, max_lot)); return false; } // Check price validity if (entry <= 0 || sl <= 0 || tp <= 0) { LogError("Invalid price levels - all prices must be positive"); return false; } // Check stop loss and take profit logic if (is_buy) { if (sl >= entry) { LogError("Buy trade: Stop loss must be below entry price"); return false; } if (tp <= entry) { LogError("Buy trade: Take profit must be above entry price"); return false; } } else { if (sl <= entry) { LogError("Sell trade: Stop loss must be above entry price"); return false; } if (tp >= entry) { LogError("Sell trade: Take profit must be below entry price"); return false; } } // Check minimum distance requirements int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double min_distance = stops_level * point; if (is_buy) { if ((entry - sl) < min_distance || (tp - entry) < min_distance) { LogError(StringFormat("Insufficient distance to stops level (%d points)", stops_level)); return false; } } else { if ((sl - entry) < min_distance || (entry - tp) < min_distance) { LogError(StringFormat("Insufficient distance to stops level (%d points)", stops_level)); return false; } } return true; } //+------------------------------------------------------------------+ //| Position Sizing and Risk Calculation Functions | //+------------------------------------------------------------------+ double CalculatePositionSize(string symbol, double risk_amount, double sl_distance) { if (sl_distance <= 0) { LogError("Invalid stop loss distance for position sizing"); return 0.0; } // Get symbol specifications double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); double min_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max_lot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if (tick_value == 0 || tick_size == 0) { LogError(StringFormat("Invalid symbol specifications for %s", symbol)); return 0.0; } // Calculate position size based on risk double value_per_pip = tick_value / tick_size; double position_size = risk_amount / (sl_distance * value_per_pip); // Normalize to lot step position_size = MathFloor(position_size / lot_step) * lot_step; // Apply limits position_size = MathMax(position_size, min_lot); position_size = MathMin(position_size, max_lot); LogDebug(StringFormat("Position size calculated for %s: %.2f lots (Risk: %.2f, SL Distance: %.5f)", symbol, position_size, risk_amount, sl_distance)); return position_size; } double CalculateRiskAmount(double account_balance, double risk_percent) { if (risk_percent <= 0 || risk_percent > 10) { LogError(StringFormat("Invalid risk percentage: %.2f%%", risk_percent)); return 0.0; } double risk_amount = account_balance * (risk_percent / 100.0); LogDebug(StringFormat("Risk amount calculated: %.2f (%.2f%% of %.2f)", risk_amount, risk_percent, account_balance)); return risk_amount; } bool ValidateTradeConditions(string symbol, bool is_buy) { // Check if symbol is tradeable if (!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE)) { LogWarning(StringFormat("Trading disabled for %s", symbol)); return false; } // Check market hours if (!SymbolInfoInteger(symbol, SYMBOL_TRADE_CALC_MODE)) { LogWarning(StringFormat("Market closed for %s", symbol)); return false; } // Check position limits int current_positions = CountPositionsForSymbol(symbol); if (current_positions >= MaxPositionsPerSymbol) { LogWarning(StringFormat("Maximum positions reached for %s (%d/%d)", symbol, current_positions, MaxPositionsPerSymbol)); return false; } // Check total position limit int total_positions = PositionsTotal(); if (total_positions >= MaxPositions) { LogWarning(StringFormat("Maximum total positions reached (%d/%d)", total_positions, MaxPositions)); return false; } // Check account free margin double required_margin = CalculateRequiredMargin(symbol, 0.01); // Minimum lot for estimation double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if (free_margin < required_margin * 10) // Require 10x minimum margin as buffer { LogWarning(StringFormat("Insufficient free margin: %.2f (required: %.2f)", free_margin, required_margin * 10)); return false; } return true; } int CountPositionsForSymbol(string symbol) { int count = 0; for (int i = 0; i < PositionsTotal(); i++) { if (position.SelectByIndex(i)) { if (position.Symbol() == symbol && position.Magic() == trade.RequestMagic()) { count++; } } } return count; } double CalculateRequiredMargin(string symbol, double lot_size) { double margin_required = 0; // Use OrderCalcMargin for accurate calculation if (!OrderCalcMargin(ORDER_TYPE_BUY, symbol, lot_size, SymbolInfoDouble(symbol, SYMBOL_ASK), margin_required)) { // Fallback calculation double contract_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_CONTRACT_SIZE); double margin_rate = SymbolInfoDouble(symbol, SYMBOL_MARGIN_INITIAL); double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); margin_required = (lot_size * contract_size * current_price * margin_rate) / AccountInfoInteger(ACCOUNT_LEVERAGE); } return margin_required; } //+------------------------------------------------------------------+ //| Stop Loss and Take Profit Calculation Functions | //+------------------------------------------------------------------+ double CalculateStopLoss(string symbol, bool is_buy, OrderBlock &ob, LiquiditySweep &sweep) { double sl_price = 0.0; double pip_value = CalculatePipValue(symbol); double buffer = 5.0 * pip_value; // 5 pip buffer beyond the level if (is_buy) { // For buy trades, SL should be below the entry level if (sweep.level > 0 && !sweep.is_high_sweep) { // Use liquidity sweep level for SL (low sweep for buy setup) sl_price = sweep.level - buffer; LogDebug(StringFormat("Buy SL based on liquidity sweep: %.5f", sl_price)); } else if (ob.is_bullish && ob.low > 0) { // Use Order Block low for SL sl_price = ob.low - buffer; LogDebug(StringFormat("Buy SL based on Order Block: %.5f", sl_price)); } else { // Fallback: use current price with minimum SL double current_price = SymbolInfoDouble(symbol, SYMBOL_BID); sl_price = current_price - (MinSL * pip_value); LogDebug(StringFormat("Buy SL fallback: %.5f", sl_price)); } } else { // For sell trades, SL should be above the entry level if (sweep.level > 0 && sweep.is_high_sweep) { // Use liquidity sweep level for SL (high sweep for sell setup) sl_price = sweep.level + buffer; LogDebug(StringFormat("Sell SL based on liquidity sweep: %.5f", sl_price)); } else if (!ob.is_bullish && ob.high > 0) { // Use Order Block high for SL sl_price = ob.high + buffer; LogDebug(StringFormat("Sell SL based on Order Block: %.5f", sl_price)); } else { // Fallback: use current price with minimum SL double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); sl_price = current_price + (MinSL * pip_value); LogDebug(StringFormat("Sell SL fallback: %.5f", sl_price)); } } // Validate SL distance double current_price = is_buy ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); double sl_distance = MathAbs(current_price - sl_price); double min_sl_distance = MinSL * pip_value; double max_sl_distance = MaxSL * pip_value; if (sl_distance < min_sl_distance) { LogWarning(StringFormat("SL distance too small (%.1f pips), adjusting to minimum", sl_distance / pip_value)); sl_price = is_buy ? current_price - min_sl_distance : current_price + min_sl_distance; } else if (sl_distance > max_sl_distance) { LogWarning(StringFormat("SL distance too large (%.1f pips), adjusting to maximum", sl_distance / pip_value)); sl_price = is_buy ? current_price - max_sl_distance : current_price + max_sl_distance; } return NormalizePrice(symbol, sl_price); } double CalculateTakeProfit(string symbol, bool is_buy, double entry, double sl, double rr_ratio) { if (rr_ratio < MinRR) { LogWarning(StringFormat("RR ratio %.2f below minimum %.2f, adjusting", rr_ratio, MinRR)); rr_ratio = MinRR; } double sl_distance = MathAbs(entry - sl); double tp_distance = sl_distance * rr_ratio; double tp_price = 0.0; if (is_buy) { tp_price = entry + tp_distance; } else { tp_price = entry - tp_distance; } LogDebug(StringFormat("TP calculated for %s: %.5f (RR: %.2f:1, Distance: %.1f pips)", symbol, tp_price, rr_ratio, tp_distance / CalculatePipValue(symbol))); return NormalizePrice(symbol, tp_price); } double CalculateOptimalRR(string symbol, bool is_buy, double entry, FairValueGap &fvg) { double base_rr = MinRR; // Start with minimum RR // Adjust RR based on FVG size (larger gaps = higher potential) if (fvg.top > 0 && fvg.bottom > 0) { double fvg_size = fvg.top - fvg.bottom; double pip_value = CalculatePipValue(symbol); double fvg_pips = fvg_size / pip_value; if (fvg_pips > 10) { base_rr = 3.0; // Higher RR for larger FVGs } else if (fvg_pips > 5) { base_rr = 2.5; } } // Adjust based on session (higher volatility = higher RR potential) string current_session = GetCurrentSession(); if (current_session == "LONDON" || current_session == "NEW YORK") { base_rr += 0.5; // Add 0.5 to RR during high volatility sessions } // Cap the maximum RR base_rr = MathMin(base_rr, 4.0); LogDebug(StringFormat("Optimal RR calculated: %.2f:1 for %s", base_rr, symbol)); return base_rr; } //+------------------------------------------------------------------+ //| Entry Opportunity Analysis Functions | //+------------------------------------------------------------------+ bool AnalyzeEntryOpportunity(string symbol, ENUM_TIMEFRAMES tf = PERIOD_M1) { LogDebug(StringFormat("Analyzing entry opportunity for %s on %s", symbol, EnumToString(tf))); // Update multi-timeframe analysis for this symbol if (!UpdateMultiTimeframeAnalysis(symbol)) { LogWarning(StringFormat("Failed to update multi-timeframe analysis for %s", symbol)); return false; } // Get M1 timeframe data for entry signals MarketStructureData m1_data; if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid) { LogDebug(StringFormat("M1 data not available or invalid for %s", symbol)); return false; } // Check multi-timeframe bias if required if (RequireMultiTFConfirmation) { string market_bias = GetMarketBias(symbol); if (market_bias == "NEUTRAL") { LogDebug(StringFormat("Neutral market bias for %s, skipping", symbol)); return false; } } // Analyze bullish setups if (AnalyzeBullishSetup(symbol)) { LogPattern("Entry Opportunity", symbol, "Bullish setup detected"); return true; } // Analyze bearish setups if (AnalyzeBearishSetup(symbol)) { LogPattern("Entry Opportunity", symbol, "Bearish setup detected"); return true; } return false; } bool AnalyzeBullishSetup(string symbol) { // Get M1 timeframe data MarketStructureData m1_data; if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid) { return false; } // Step 1: Find valid liquidity sweep (low sweep for bullish setup) LiquiditySweep valid_sweep; bool sweep_found = false; for (int i = 0; i < ArraySize(m1_data.liquidity_sweeps); i++) { if (!m1_data.liquidity_sweeps[i].is_high_sweep && IsLiquiditySweepValid(symbol, PERIOD_M1, m1_data.liquidity_sweeps[i])) { valid_sweep = m1_data.liquidity_sweeps[i]; sweep_found = true; break; } } if (!sweep_found) { LogDebug(StringFormat("No valid low sweep found for bullish setup on %s", symbol)); return false; } // Step 2: Find opposite direction BOS (bullish BOS after low sweep) BreakOfStructure valid_bos; bool bos_found = false; for (int i = 0; i < ArraySize(m1_data.bos_events); i++) { if (m1_data.bos_events[i].is_bullish && m1_data.bos_events[i].confirmed && m1_data.bos_events[i].time > valid_sweep.time) // BOS must be after sweep { valid_bos = m1_data.bos_events[i]; bos_found = true; break; } } if (!bos_found) { LogDebug(StringFormat("No valid bullish BOS found after low sweep on %s", symbol)); return false; } // Step 3: Find valid FVG between BOS and current price FairValueGap valid_fvg; bool fvg_found = false; for (int i = 0; i < ArraySize(m1_data.fair_value_gaps); i++) { if (m1_data.fair_value_gaps[i].is_bullish && IsFVGValid(symbol, PERIOD_M1, m1_data.fair_value_gaps[i]) && m1_data.fair_value_gaps[i].time > valid_bos.time) // FVG must be after BOS { valid_fvg = m1_data.fair_value_gaps[i]; fvg_found = true; break; } } if (!fvg_found) { LogDebug(StringFormat("No valid bullish FVG found after BOS on %s", symbol)); return false; } // Step 4: Find fresh bullish Order Block OrderBlock valid_ob; bool ob_found = false; for (int i = 0; i < ArraySize(m1_data.order_blocks); i++) { if (m1_data.order_blocks[i].is_bullish && m1_data.order_blocks[i].is_fresh && m1_data.order_blocks[i].strength >= OBStrengthFilter && m1_data.order_blocks[i].time > valid_fvg.time) // OB must be after FVG { valid_ob = m1_data.order_blocks[i]; ob_found = true; break; } } if (!ob_found) { LogDebug(StringFormat("No valid fresh bullish OB found after FVG on %s", symbol)); return false; } // Step 5: Check multi-timeframe alignment if (RequireMultiTFConfirmation) { if (!IsMultiTimeframeAligned(symbol, true)) { LogDebug(StringFormat("Multi-timeframe not aligned for bullish setup on %s", symbol)); return false; } } // Step 6: Execute bullish trade return ExecuteBullishTrade(symbol, valid_ob, valid_fvg, valid_sweep); } bool AnalyzeBearishSetup(string symbol) { // Get M1 timeframe data MarketStructureData m1_data; if (!GetTimeframeData(PERIOD_M1, m1_data) || !m1_data.is_valid) { return false; } // Step 1: Find valid liquidity sweep (high sweep for bearish setup) LiquiditySweep valid_sweep; bool sweep_found = false; for (int i = 0; i < ArraySize(m1_data.liquidity_sweeps); i++) { if (m1_data.liquidity_sweeps[i].is_high_sweep && IsLiquiditySweepValid(symbol, PERIOD_M1, m1_data.liquidity_sweeps[i])) { valid_sweep = m1_data.liquidity_sweeps[i]; sweep_found = true; break; } } if (!sweep_found) { LogDebug(StringFormat("No valid high sweep found for bearish setup on %s", symbol)); return false; } // Step 2: Find opposite direction BOS (bearish BOS after high sweep) BreakOfStructure valid_bos; bool bos_found = false; for (int i = 0; i < ArraySize(m1_data.bos_events); i++) { if (!m1_data.bos_events[i].is_bullish && m1_data.bos_events[i].confirmed && m1_data.bos_events[i].time > valid_sweep.time) // BOS must be after sweep { valid_bos = m1_data.bos_events[i]; bos_found = true; break; } } if (!bos_found) { LogDebug(StringFormat("No valid bearish BOS found after high sweep on %s", symbol)); return false; } // Step 3: Find valid FVG between BOS and current price FairValueGap valid_fvg; bool fvg_found = false; for (int i = 0; i < ArraySize(m1_data.fair_value_gaps); i++) { if (!m1_data.fair_value_gaps[i].is_bullish && IsFVGValid(symbol, PERIOD_M1, m1_data.fair_value_gaps[i]) && m1_data.fair_value_gaps[i].time > valid_bos.time) // FVG must be after BOS { valid_fvg = m1_data.fair_value_gaps[i]; fvg_found = true; break; } } if (!fvg_found) { LogDebug(StringFormat("No valid bearish FVG found after BOS on %s", symbol)); return false; } // Step 4: Find fresh bearish Order Block OrderBlock valid_ob; bool ob_found = false; for (int i = 0; i < ArraySize(m1_data.order_blocks); i++) { if (!m1_data.order_blocks[i].is_bullish && m1_data.order_blocks[i].is_fresh && m1_data.order_blocks[i].strength >= OBStrengthFilter && m1_data.order_blocks[i].time > valid_fvg.time) // OB must be after FVG { valid_ob = m1_data.order_blocks[i]; ob_found = true; break; } } if (!ob_found) { LogDebug(StringFormat("No valid fresh bearish OB found after FVG on %s", symbol)); return false; } // Step 5: Check multi-timeframe alignment if (RequireMultiTFConfirmation) { if (!IsMultiTimeframeAligned(symbol, false)) { LogDebug(StringFormat("Multi-timeframe not aligned for bearish setup on %s", symbol)); return false; } } // Step 6: Execute bearish trade return ExecuteBearishTrade(symbol, valid_ob, valid_fvg, valid_sweep); } //+------------------------------------------------------------------+ //| Trade Execution Logic Functions | //+------------------------------------------------------------------+ bool ExecuteBullishTrade(string symbol, OrderBlock &ob, FairValueGap &fvg, LiquiditySweep &sweep) { LogInfo(StringFormat("Executing bullish trade for %s", symbol)); // Validate trade conditions if (!ValidateTradeConditions(symbol, true)) { LogWarning(StringFormat("Trade conditions not met for bullish trade on %s", symbol)); return false; } // Calculate entry price (prefer FVG midpoint, fallback to OB zone) double entry_price = 0.0; if (fvg.top > 0 && fvg.bottom > 0) { entry_price = GetFVGMidpoint(fvg); LogDebug(StringFormat("Using FVG midpoint for entry: %.5f", entry_price)); } else { entry_price = (ob.high + ob.low) / 2.0; // OB midpoint LogDebug(StringFormat("Using OB midpoint for entry: %.5f", entry_price)); } // Calculate stop loss double sl_price = CalculateStopLoss(symbol, true, ob, sweep); if (sl_price <= 0) { LogError(StringFormat("Invalid stop loss calculated for %s", symbol)); return false; } // Calculate optimal risk-reward ratio double rr_ratio = CalculateOptimalRR(symbol, true, entry_price, fvg); // Calculate take profit double tp_price = CalculateTakeProfit(symbol, true, entry_price, sl_price, rr_ratio); if (tp_price <= entry_price) { LogError(StringFormat("Invalid take profit calculated for %s", symbol)); return false; } // Calculate position size double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk_amount = CalculateRiskAmount(account_balance, RiskPercent); double sl_distance = MathAbs(entry_price - sl_price); double lot_size = CalculatePositionSize(symbol, risk_amount, sl_distance); if (lot_size <= 0) { LogError(StringFormat("Invalid lot size calculated for %s", symbol)); return false; } // Execute the trade bool trade_result = ExecuteBuyTrade(symbol, entry_price, sl_price, tp_price, lot_size); if (trade_result) { LogTrade("BULLISH SETUP EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f (%.1f pips), TP: %.5f (%.2f:1 RR), Lot: %.2f", entry_price, sl_price, sl_distance / CalculatePipValue(symbol), tp_price, rr_ratio, lot_size)); } return trade_result; } bool ExecuteBearishTrade(string symbol, OrderBlock &ob, FairValueGap &fvg, LiquiditySweep &sweep) { LogInfo(StringFormat("Executing bearish trade for %s", symbol)); // Validate trade conditions if (!ValidateTradeConditions(symbol, false)) { LogWarning(StringFormat("Trade conditions not met for bearish trade on %s", symbol)); return false; } // Calculate entry price (prefer FVG midpoint, fallback to OB zone) double entry_price = 0.0; if (fvg.top > 0 && fvg.bottom > 0) { entry_price = GetFVGMidpoint(fvg); LogDebug(StringFormat("Using FVG midpoint for entry: %.5f", entry_price)); } else { entry_price = (ob.high + ob.low) / 2.0; // OB midpoint LogDebug(StringFormat("Using OB midpoint for entry: %.5f", entry_price)); } // Calculate stop loss double sl_price = CalculateStopLoss(symbol, false, ob, sweep); if (sl_price <= 0) { LogError(StringFormat("Invalid stop loss calculated for %s", symbol)); return false; } // Calculate optimal risk-reward ratio double rr_ratio = CalculateOptimalRR(symbol, false, entry_price, fvg); // Calculate take profit double tp_price = CalculateTakeProfit(symbol, false, entry_price, sl_price, rr_ratio); if (tp_price >= entry_price) { LogError(StringFormat("Invalid take profit calculated for %s", symbol)); return false; } // Calculate position size double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); double risk_amount = CalculateRiskAmount(account_balance, RiskPercent); double sl_distance = MathAbs(entry_price - sl_price); double lot_size = CalculatePositionSize(symbol, risk_amount, sl_distance); if (lot_size <= 0) { LogError(StringFormat("Invalid lot size calculated for %s", symbol)); return false; } // Execute the trade bool trade_result = ExecuteSellTrade(symbol, entry_price, sl_price, tp_price, lot_size); if (trade_result) { LogTrade("BEARISH SETUP EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f (%.1f pips), TP: %.5f (%.2f:1 RR), Lot: %.2f", entry_price, sl_price, sl_distance / CalculatePipValue(symbol), tp_price, rr_ratio, lot_size)); } return trade_result; } //+------------------------------------------------------------------+ //| Main trading logic processor | //+------------------------------------------------------------------+ void ProcessTradingLogic() { // Update information panel UpdateInfoPanel(); // Check if trading is allowed in current session if (UseTimeFilter && GetCurrentSession() == "OFF HOURS") { LogDebug("Trading outside allowed session hours"); return; } // Check account status if (!IsAccountTradingAllowed()) { LogWarning("Account trading not allowed"); return; } // Manage existing positions first ManageOpenPositions(); // Check if we can open new positions if (PositionsTotal() >= MaxPositions) { LogDebug(StringFormat("Maximum positions reached (%d/%d)", PositionsTotal(), MaxPositions)); return; } // Process each symbol for trading opportunities for (int i = 0; i < TotalSymbols; i++) { string symbol = SymbolsToTrade[i]; // Skip if symbol has reached maximum positions if (CountPositionsForSymbol(symbol) >= MaxPositionsPerSymbol) { LogDebug(StringFormat("Maximum positions reached for %s (%d/%d)", symbol, CountPositionsForSymbol(symbol), MaxPositionsPerSymbol)); continue; } // Analyze entry opportunities for this symbol if (AnalyzeEntryOpportunity(symbol, PERIOD_M1)) { LogInfo(StringFormat("Entry opportunity processed for %s", symbol)); } } // Update multi-timeframe status for debugging if (EnableDebugMode) { for (int i = 0; i < TotalSymbols; i++) { PrintMultiTimeframeStatus(SymbolsToTrade[i]); } } } bool IsAccountTradingAllowed() { // Check if trading is allowed on the account if (!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) { LogError("Trading not allowed on this account"); return false; } // Check if Expert Advisors are allowed if (!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { LogError("Expert Advisor trading not allowed in terminal"); return false; } // Check account balance double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); if (account_balance <= 0) { LogError("Invalid account balance"); return false; } // Check free margin double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); if (free_margin <= 0) { LogError("No free margin available"); return false; } return true; } //+------------------------------------------------------------------+ //| Position Management Functions | //+------------------------------------------------------------------+ void ManageOpenPositions() { for (int i = PositionsTotal() - 1; i >= 0; i--) { if (position.SelectByIndex(i)) { // Only manage positions opened by this EA if (position.Magic() != trade.RequestMagic()) continue; string symbol = position.Symbol(); ulong ticket = position.Ticket(); // Check for position management opportunities if (ShouldUpdatePosition(ticket)) { UpdatePositionManagement(ticket); } } } } bool ShouldUpdatePosition(ulong ticket) { if (!position.SelectByTicket(ticket)) return false; // Check if position is in profit for trailing stop double current_profit = position.Profit(); double position_open_price = position.PriceOpen(); double current_price = position.Type() == POSITION_TYPE_BUY ? SymbolInfoDouble(position.Symbol(), SYMBOL_BID) : SymbolInfoDouble(position.Symbol(), SYMBOL_ASK); // Simple break-even logic double pip_value = CalculatePipValue(position.Symbol()); double profit_pips = MathAbs(current_price - position_open_price) / pip_value; // Move to break-even when in 20+ pips profit if (profit_pips >= 20.0) { double current_sl = position.StopLoss(); double break_even_price = position_open_price; if (position.Type() == POSITION_TYPE_BUY) { if (current_sl < break_even_price) { LogInfo(StringFormat("Moving position %llu to break-even", ticket)); return true; } } else { if (current_sl > break_even_price) { LogInfo(StringFormat("Moving position %llu to break-even", ticket)); return true; } } } return false; } void UpdatePositionManagement(ulong ticket) { if (!position.SelectByTicket(ticket)) return; double new_sl = position.PriceOpen(); // Break-even double current_tp = position.TakeProfit(); // Modify position to break-even if (trade.PositionModify(ticket, new_sl, current_tp)) { LogTrade("POSITION MODIFIED", position.Symbol(), StringFormat("Ticket: %llu moved to break-even at %.5f", ticket, new_sl)); } else { int error_code = trade.ResultRetcode(); HandleTradeError(error_code, "Position Modification"); } } //+------------------------------------------------------------------+ //| Logging Functions | //+------------------------------------------------------------------+ void LogInfo(string message) { if (EnableDetailedLogging) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [INFO] ", LogPrefix, ": ", message); } } void LogWarning(string message) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [WARNING] ", LogPrefix, ": ", message); } void LogError(string message) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [ERROR] ", LogPrefix, ": ", message); } void LogDebug(string message) { if (EnableDebugMode) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [DEBUG] ", LogPrefix, ": ", message); } } void LogPattern(string pattern_type, string symbol, string details) { if (LogPatternDetection) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [PATTERN] ", LogPrefix, ": ", pattern_type, " detected on ", symbol, " - ", details); } } void LogTrade(string action, string symbol, string details) { if (LogTradeExecution) { string timestamp = TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS); Print("[", timestamp, "] [TRADE] ", LogPrefix, ": ", action, " on ", symbol, " - ", details); } } //+------------------------------------------------------------------+ //| Error Handling Functions | //+------------------------------------------------------------------+ bool HandleTradeError(int error_code, string operation) { string error_description = ""; bool is_critical = false; switch (error_code) { case TRADE_RETCODE_REQUOTE: error_description = "Requote"; break; case TRADE_RETCODE_REJECT: error_description = "Request rejected"; is_critical = true; break; case TRADE_RETCODE_CANCEL: error_description = "Request canceled by trader"; break; case TRADE_RETCODE_PLACED: error_description = "Order placed"; return true; // Success case TRADE_RETCODE_DONE: error_description = "Request completed"; return true; // Success case TRADE_RETCODE_DONE_PARTIAL: error_description = "Request partially completed"; return true; // Partial success case TRADE_RETCODE_ERROR: error_description = "Request processing error"; is_critical = true; break; case TRADE_RETCODE_TIMEOUT: error_description = "Request timeout"; break; case TRADE_RETCODE_INVALID: error_description = "Invalid request"; is_critical = true; break; case TRADE_RETCODE_INVALID_VOLUME: error_description = "Invalid volume"; is_critical = true; break; case TRADE_RETCODE_INVALID_PRICE: error_description = "Invalid price"; break; case TRADE_RETCODE_INVALID_STOPS: error_description = "Invalid stops"; break; case TRADE_RETCODE_TRADE_DISABLED: error_description = "Trade disabled"; is_critical = true; break; case TRADE_RETCODE_MARKET_CLOSED: error_description = "Market closed"; break; case TRADE_RETCODE_NO_MONEY: error_description = "No money"; is_critical = true; break; case TRADE_RETCODE_PRICE_CHANGED: error_description = "Price changed"; break; case TRADE_RETCODE_PRICE_OFF: error_description = "Off quotes"; break; case TRADE_RETCODE_INVALID_EXPIRATION: error_description = "Invalid expiration"; break; case TRADE_RETCODE_ORDER_CHANGED: error_description = "Order state changed"; break; case TRADE_RETCODE_TOO_MANY_REQUESTS: error_description = "Too many requests"; break; case TRADE_RETCODE_NO_CHANGES: error_description = "No changes"; break; case TRADE_RETCODE_SERVER_DISABLES_AT: error_description = "Autotrading disabled by server"; is_critical = true; break; case TRADE_RETCODE_CLIENT_DISABLES_AT: error_description = "Autotrading disabled by client"; is_critical = true; break; case TRADE_RETCODE_LOCKED: error_description = "Request locked"; break; case TRADE_RETCODE_FROZEN: error_description = "Order or position frozen"; break; case TRADE_RETCODE_INVALID_FILL: error_description = "Invalid fill"; break; case TRADE_RETCODE_CONNECTION: error_description = "No connection"; is_critical = true; break; case TRADE_RETCODE_ONLY_REAL: error_description = "Only real accounts allowed"; is_critical = true; break; case TRADE_RETCODE_LIMIT_ORDERS: error_description = "Limit orders limit reached"; break; case TRADE_RETCODE_LIMIT_VOLUME: error_description = "Volume limit reached"; break; case TRADE_RETCODE_INVALID_ORDER: error_description = "Invalid order"; is_critical = true; break; case TRADE_RETCODE_POSITION_CLOSED: error_description = "Position already closed"; break; default: error_description = "Unknown error"; is_critical = true; break; } if (is_critical) { LogError(StringFormat("%s failed with critical error %d: %s", operation, error_code, error_description)); } else { LogWarning(StringFormat("%s failed with error %d: %s", operation, error_code, error_description)); } return false; } //+------------------------------------------------------------------+ //| Utility Functions | //+------------------------------------------------------------------+ double NormalizePrice(string symbol, double price) { return NormalizeDouble(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); } double CalculatePipValue(string symbol) { double pip_size = SymbolInfoDouble(symbol, SYMBOL_POINT); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); if (digits == 5 || digits == 3) pip_size *= 10; return pip_size; } bool IsNewBar(string symbol, ENUM_TIMEFRAMES timeframe) { static datetime last_bar_time = 0; datetime current_bar_time = iTime(symbol, timeframe, 0); if (current_bar_time != last_bar_time) { last_bar_time = current_bar_time; return true; } return false; } //+------------------------------------------------------------------+ //| Order Block Detection Functions | //+------------------------------------------------------------------+ bool DetectOrderBlocks(string symbol, ENUM_TIMEFRAMES timeframe, OrderBlock &order_blocks[]) { ArrayResize(order_blocks, 0); int bars_to_analyze = MathMin(OBLookback * 2, iBars(symbol, timeframe) - 10); if (bars_to_analyze < 10) return false; LogDebug(StringFormat("Analyzing %d bars for Order Blocks on %s %s", bars_to_analyze, symbol, EnumToString(timeframe))); // Look for potential Order Blocks for (int i = 5; i < bars_to_analyze; i++) { // Get candle data double high = iHigh(symbol, timeframe, i); double low = iLow(symbol, timeframe, i); double open = iOpen(symbol, timeframe, i); double close = iClose(symbol, timeframe, i); datetime time = iTime(symbol, timeframe, i); // Check for bullish Order Block (demand zone) if (IsBullishOrderBlock(symbol, timeframe, i)) { OrderBlock ob; ob.high = high; ob.low = low; ob.time = time; ob.is_bullish = true; ob.is_fresh = IsOrderBlockFresh(symbol, timeframe, i, true); ob.strength = CalculateOrderBlockStrength(symbol, timeframe, i, true); if (ob.strength >= OBStrengthFilter) { ArrayResize(order_blocks, ArraySize(order_blocks) + 1); order_blocks[ArraySize(order_blocks) - 1] = ob; LogPattern("Order Block", symbol, StringFormat("Bullish OB at %.5f-%.5f, Strength: %.2f", ob.low, ob.high, ob.strength)); } } // Check for bearish Order Block (supply zone) if (IsBearishOrderBlock(symbol, timeframe, i)) { OrderBlock ob; ob.high = high; ob.low = low; ob.time = time; ob.is_bullish = false; ob.is_fresh = IsOrderBlockFresh(symbol, timeframe, i, false); ob.strength = CalculateOrderBlockStrength(symbol, timeframe, i, false); if (ob.strength >= OBStrengthFilter) { ArrayResize(order_blocks, ArraySize(order_blocks) + 1); order_blocks[ArraySize(order_blocks) - 1] = ob; LogPattern("Order Block", symbol, StringFormat("Bearish OB at %.5f-%.5f, Strength: %.2f", ob.low, ob.high, ob.strength)); } } } LogDebug(StringFormat("Found %d Order Blocks on %s %s", ArraySize(order_blocks), symbol, EnumToString(timeframe))); return ArraySize(order_blocks) > 0; } bool IsBullishOrderBlock(string symbol, ENUM_TIMEFRAMES timeframe, int index) { // Get current candle data double open = iOpen(symbol, timeframe, index); double close = iClose(symbol, timeframe, index); double high = iHigh(symbol, timeframe, index); double low = iLow(symbol, timeframe, index); // Must be a bullish candle if (close <= open) return false; // Check for strong bullish momentum (body > 60% of total range) double body_size = close - open; double total_range = high - low; if (total_range == 0) return false; double body_ratio = body_size / total_range; if (body_ratio < 0.6) return false; // Check for significant volume increase (if available) long current_volume = iVolume(symbol, timeframe, index); long avg_volume = 0; for (int i = 1; i <= 5; i++) { avg_volume += iVolume(symbol, timeframe, index + i); } avg_volume /= 5; if (current_volume < avg_volume * 1.2) return false; // Check for price rejection from this level in subsequent candles bool has_rejection = false; for (int i = 1; i <= 5; i++) { if (index - i < 0) break; double test_low = iLow(symbol, timeframe, index - i); double test_close = iClose(symbol, timeframe, index - i); // Price came back to test the OB zone and bounced if (test_low <= high && test_low >= low && test_close > high) { has_rejection = true; break; } } return has_rejection; } bool IsBearishOrderBlock(string symbol, ENUM_TIMEFRAMES timeframe, int index) { // Get current candle data double open = iOpen(symbol, timeframe, index); double close = iClose(symbol, timeframe, index); double high = iHigh(symbol, timeframe, index); double low = iLow(symbol, timeframe, index); // Must be a bearish candle if (close >= open) return false; // Check for strong bearish momentum (body > 60% of total range) double body_size = open - close; double total_range = high - low; if (total_range == 0) return false; double body_ratio = body_size / total_range; if (body_ratio < 0.6) return false; // Check for significant volume increase (if available) long current_volume = iVolume(symbol, timeframe, index); long avg_volume = 0; for (int i = 1; i <= 5; i++) { avg_volume += iVolume(symbol, timeframe, index + i); } avg_volume /= 5; if (current_volume < avg_volume * 1.2) return false; // Check for price rejection from this level in subsequent candles bool has_rejection = false; for (int i = 1; i <= 5; i++) { if (index - i < 0) break; double test_high = iHigh(symbol, timeframe, index - i); double test_close = iClose(symbol, timeframe, index - i); // Price came back to test the OB zone and bounced if (test_high >= low && test_high <= high && test_close < low) { has_rejection = true; break; } } return has_rejection; } bool IsOrderBlockFresh(string symbol, ENUM_TIMEFRAMES timeframe, int ob_index, bool is_bullish) { double ob_high = iHigh(symbol, timeframe, ob_index); double ob_low = iLow(symbol, timeframe, ob_index); // Check if price has significantly broken through the OB zone for (int i = 0; i < ob_index; i++) { double test_high = iHigh(symbol, timeframe, i); double test_low = iLow(symbol, timeframe, i); if (is_bullish) { // For bullish OB, check if price broke significantly below if (test_low < ob_low - (ob_high - ob_low) * 0.5) return false; } else { // For bearish OB, check if price broke significantly above if (test_high > ob_high + (ob_high - ob_low) * 0.5) return false; } } return true; } double CalculateOrderBlockStrength(string symbol, ENUM_TIMEFRAMES timeframe, int index, bool is_bullish) { double strength = 0.0; // Factor 1: Candle body size relative to average double body_size = MathAbs(iClose(symbol, timeframe, index) - iOpen(symbol, timeframe, index)); double avg_body = 0; for (int i = 1; i <= 10; i++) { avg_body += MathAbs(iClose(symbol, timeframe, index + i) - iOpen(symbol, timeframe, index + i)); } avg_body /= 10; if (avg_body > 0) strength += (body_size / avg_body) * 0.3; // 30% weight // Factor 2: Volume relative to average long current_volume = iVolume(symbol, timeframe, index); long avg_volume = 0; for (int i = 1; i <= 10; i++) { avg_volume += iVolume(symbol, timeframe, index + i); } avg_volume /= 10; if (avg_volume > 0) strength += ((double)current_volume / avg_volume) * 0.2; // 20% weight // Factor 3: Number of times price respected the level int respect_count = 0; double ob_high = iHigh(symbol, timeframe, index); double ob_low = iLow(symbol, timeframe, index); for (int i = 1; i < index && i <= 20; i++) { double test_high = iHigh(symbol, timeframe, index - i); double test_low = iLow(symbol, timeframe, index - i); double test_close = iClose(symbol, timeframe, index - i); if (is_bullish) { if (test_low <= ob_high && test_low >= ob_low && test_close > ob_high) respect_count++; } else { if (test_high >= ob_low && test_high <= ob_high && test_close < ob_low) respect_count++; } } strength += respect_count * 0.1; // 10% weight per respect // Factor 4: Time since formation (fresher = stronger) double time_factor = 1.0 - (index / (double)OBLookback); strength += time_factor * 0.3; // 30% weight return MathMin(strength, 2.0); // Cap at 2.0 } //+------------------------------------------------------------------+ //| Break of Structure Detection Functions | //+------------------------------------------------------------------+ bool DetectBreakOfStructure(string symbol, ENUM_TIMEFRAMES timeframe, BreakOfStructure &bos_events[]) { ArrayResize(bos_events, 0); int bars_to_analyze = MathMin(SwingLookback * 3, iBars(symbol, timeframe) - 10); if (bars_to_analyze < 20) return false; LogDebug(StringFormat("Analyzing %d bars for Break of Structure on %s %s", bars_to_analyze, symbol, EnumToString(timeframe))); // Find swing highs and lows first double swing_highs[]; double swing_lows[]; datetime swing_high_times[]; datetime swing_low_times[]; FindSwingPoints(symbol, timeframe, bars_to_analyze, swing_highs, swing_lows, swing_high_times, swing_low_times); // Analyze for BOS patterns AnalyzeBOSPatterns(symbol, timeframe, swing_highs, swing_lows, swing_high_times, swing_low_times, bos_events); LogDebug(StringFormat("Found %d BOS events on %s %s", ArraySize(bos_events), symbol, EnumToString(timeframe))); return ArraySize(bos_events) > 0; } void FindSwingPoints(string symbol, ENUM_TIMEFRAMES timeframe, int bars_to_analyze, double &swing_highs[], double &swing_lows[], datetime &swing_high_times[], datetime &swing_low_times[]) { ArrayResize(swing_highs, 0); ArrayResize(swing_lows, 0); ArrayResize(swing_high_times, 0); ArrayResize(swing_low_times, 0); for (int i = SwingLookback; i < bars_to_analyze - SwingLookback; i++) { double current_high = iHigh(symbol, timeframe, i); double current_low = iLow(symbol, timeframe, i); datetime current_time = iTime(symbol, timeframe, i); // Check for swing high bool is_swing_high = true; for (int j = 1; j <= SwingLookback; j++) { if (iHigh(symbol, timeframe, i - j) >= current_high || iHigh(symbol, timeframe, i + j) >= current_high) { is_swing_high = false; break; } } if (is_swing_high) { ArrayResize(swing_highs, ArraySize(swing_highs) + 1); ArrayResize(swing_high_times, ArraySize(swing_high_times) + 1); swing_highs[ArraySize(swing_highs) - 1] = current_high; swing_high_times[ArraySize(swing_high_times) - 1] = current_time; } // Check for swing low bool is_swing_low = true; for (int j = 1; j <= SwingLookback; j++) { if (iLow(symbol, timeframe, i - j) <= current_low || iLow(symbol, timeframe, i + j) <= current_low) { is_swing_low = false; break; } } if (is_swing_low) { ArrayResize(swing_lows, ArraySize(swing_lows) + 1); ArrayResize(swing_low_times, ArraySize(swing_low_times) + 1); swing_lows[ArraySize(swing_lows) - 1] = current_low; swing_low_times[ArraySize(swing_low_times) - 1] = current_time; } } } void AnalyzeBOSPatterns(string symbol, ENUM_TIMEFRAMES timeframe, double &swing_highs[], double &swing_lows[], datetime &swing_high_times[], datetime &swing_low_times[], BreakOfStructure &bos_events[]) { // Analyze bullish BOS (breaking above previous swing high) for (int i = 1; i < ArraySize(swing_highs); i++) { double previous_high = swing_highs[i]; datetime previous_time = swing_high_times[i]; // Look for price breaking above this high int start_bar = iBarShift(symbol, timeframe, previous_time); if (start_bar < 0) continue; for (int j = 0; j < start_bar && j < BOSConfirmationCandles * 2; j++) { double current_high = iHigh(symbol, timeframe, j); double current_close = iClose(symbol, timeframe, j); datetime current_time = iTime(symbol, timeframe, j); if (current_high > previous_high && current_close > previous_high) { // Confirm the break with subsequent candles bool confirmed = ConfirmBOS(symbol, timeframe, j, true, previous_high); if (confirmed) { BreakOfStructure bos; bos.level = previous_high; bos.time = current_time; bos.is_bullish = true; bos.confirmed = true; ArrayResize(bos_events, ArraySize(bos_events) + 1); bos_events[ArraySize(bos_events) - 1] = bos; LogPattern("Break of Structure", symbol, StringFormat("Bullish BOS at %.5f", previous_high)); break; } } } } // Analyze bearish BOS (breaking below previous swing low) for (int i = 1; i < ArraySize(swing_lows); i++) { double previous_low = swing_lows[i]; datetime previous_time = swing_low_times[i]; // Look for price breaking below this low int start_bar = iBarShift(symbol, timeframe, previous_time); if (start_bar < 0) continue; for (int j = 0; j < start_bar && j < BOSConfirmationCandles * 2; j++) { double current_low = iLow(symbol, timeframe, j); double current_close = iClose(symbol, timeframe, j); datetime current_time = iTime(symbol, timeframe, j); if (current_low < previous_low && current_close < previous_low) { // Confirm the break with subsequent candles bool confirmed = ConfirmBOS(symbol, timeframe, j, false, previous_low); if (confirmed) { BreakOfStructure bos; bos.level = previous_low; bos.time = current_time; bos.is_bullish = false; bos.confirmed = true; ArrayResize(bos_events, ArraySize(bos_events) + 1); bos_events[ArraySize(bos_events) - 1] = bos; LogPattern("Break of Structure", symbol, StringFormat("Bearish BOS at %.5f", previous_low)); break; } } } } } bool ConfirmBOS(string symbol, ENUM_TIMEFRAMES timeframe, int break_bar, bool is_bullish, double level) { int confirmation_count = 0; // Check subsequent candles for confirmation for (int i = 0; i < BOSConfirmationCandles && break_bar - i >= 0; i++) { double close_price = iClose(symbol, timeframe, break_bar - i); if (is_bullish) { if (close_price > level) confirmation_count++; } else { if (close_price < level) confirmation_count++; } } // Require at least 2 out of 3 confirmation candles return confirmation_count >= MathMax(2, BOSConfirmationCandles / 2); } bool IsBOSValid(string symbol, ENUM_TIMEFRAMES timeframe, BreakOfStructure &bos) { // Check if BOS is recent enough datetime current_time = iTime(symbol, timeframe, 0); int time_diff = (int)((current_time - bos.time) / PeriodSeconds(timeframe)); if (time_diff > BOSConfirmationCandles * 3) return false; // Check if price is still respecting the BOS level double current_price = iClose(symbol, timeframe, 0); if (bos.is_bullish) { return current_price > bos.level; } else { return current_price < bos.level; } } //+------------------------------------------------------------------+ //| Fair Value Gap Detection Functions | //+------------------------------------------------------------------+ bool DetectFairValueGaps(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg_array[]) { ArrayResize(fvg_array, 0); int bars_to_analyze = MathMin(50, iBars(symbol, timeframe) - 5); if (bars_to_analyze < 10) return false; LogDebug(StringFormat("Analyzing %d bars for Fair Value Gaps on %s %s", bars_to_analyze, symbol, EnumToString(timeframe))); double pip_value = CalculatePipValue(symbol); double min_gap_size = MinFVGSize * pip_value; // Look for FVG patterns (3-candle pattern) for (int i = 2; i < bars_to_analyze; i++) { // Get three consecutive candles double high1 = iHigh(symbol, timeframe, i); // First candle double low1 = iLow(symbol, timeframe, i); double high2 = iHigh(symbol, timeframe, i - 1); // Middle candle (impulse) double low2 = iLow(symbol, timeframe, i - 1); double high3 = iHigh(symbol, timeframe, i - 2); // Third candle double low3 = iLow(symbol, timeframe, i - 2); datetime gap_time = iTime(symbol, timeframe, i - 1); // Check for bullish FVG (gap between candle 1 high and candle 3 low) if (low3 > high1) { double gap_size = low3 - high1; if (gap_size >= min_gap_size) { FairValueGap fvg; fvg.top = low3; fvg.bottom = high1; fvg.time = gap_time; fvg.is_bullish = true; fvg.is_filled = IsFVGFilled(symbol, timeframe, i - 2, fvg.top, fvg.bottom, true); if (!fvg.is_filled) { ArrayResize(fvg_array, ArraySize(fvg_array) + 1); fvg_array[ArraySize(fvg_array) - 1] = fvg; LogPattern("Fair Value Gap", symbol, StringFormat("Bullish FVG at %.5f-%.5f, Size: %.1f pips", fvg.bottom, fvg.top, gap_size / pip_value)); } } } // Check for bearish FVG (gap between candle 1 low and candle 3 high) if (high3 < low1) { double gap_size = low1 - high3; if (gap_size >= min_gap_size) { FairValueGap fvg; fvg.top = low1; fvg.bottom = high3; fvg.time = gap_time; fvg.is_bullish = false; fvg.is_filled = IsFVGFilled(symbol, timeframe, i - 2, fvg.top, fvg.bottom, false); if (!fvg.is_filled) { ArrayResize(fvg_array, ArraySize(fvg_array) + 1); fvg_array[ArraySize(fvg_array) - 1] = fvg; LogPattern("Fair Value Gap", symbol, StringFormat("Bearish FVG at %.5f-%.5f, Size: %.1f pips", fvg.bottom, fvg.top, gap_size / pip_value)); } } } } LogDebug(StringFormat("Found %d unfilled FVGs on %s %s", ArraySize(fvg_array), symbol, EnumToString(timeframe))); return ArraySize(fvg_array) > 0; } bool IsFVGFilled(string symbol, ENUM_TIMEFRAMES timeframe, int start_bar, double top, double bottom, bool is_bullish) { // Check if price has filled the FVG since its formation for (int i = 0; i < start_bar; i++) { double high = iHigh(symbol, timeframe, i); double low = iLow(symbol, timeframe, i); if (is_bullish) { // For bullish FVG, check if price came back down to fill the gap if (low <= bottom) return true; } else { // For bearish FVG, check if price came back up to fill the gap if (high >= top) return true; } } return false; } bool IsFVGValid(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg) { // Check if FVG is still unfilled if (fvg.is_filled) return false; // Check current price position relative to FVG double current_price = iClose(symbol, timeframe, 0); if (fvg.is_bullish) { // For bullish FVG, price should be above the gap return current_price > fvg.top; } else { // For bearish FVG, price should be below the gap return current_price < fvg.bottom; } } double GetFVGMidpoint(FairValueGap &fvg) { return (fvg.top + fvg.bottom) / 2.0; } bool IsPriceInFVG(double price, FairValueGap &fvg) { return price >= fvg.bottom && price <= fvg.top; } void UpdateFVGStatus(string symbol, ENUM_TIMEFRAMES timeframe, FairValueGap &fvg_array[]) { // Update the filled status of existing FVGs for (int i = 0; i < ArraySize(fvg_array); i++) { if (!fvg_array[i].is_filled) { double current_high = iHigh(symbol, timeframe, 0); double current_low = iLow(symbol, timeframe, 0); if (fvg_array[i].is_bullish) { if (current_low <= fvg_array[i].bottom) { fvg_array[i].is_filled = true; LogPattern("Fair Value Gap", symbol, "Bullish FVG filled"); } } else { if (current_high >= fvg_array[i].top) { fvg_array[i].is_filled = true; LogPattern("Fair Value Gap", symbol, "Bearish FVG filled"); } } } } } //+------------------------------------------------------------------+ //| Liquidity Sweep Detection Functions | //+------------------------------------------------------------------+ bool DetectLiquiditySweeps(string symbol, ENUM_TIMEFRAMES timeframe, LiquiditySweep &sweep_array[]) { ArrayResize(sweep_array, 0); int bars_to_analyze = MathMin(100, iBars(symbol, timeframe) - 10); if (bars_to_analyze < 20) return false; LogDebug(StringFormat("Analyzing %d bars for Liquidity Sweeps on %s %s", bars_to_analyze, symbol, EnumToString(timeframe))); double pip_value = CalculatePipValue(symbol); double min_sweep_distance = MinSweepDistance * pip_value; // Find equal highs and lows first double equal_highs[]; double equal_lows[]; datetime equal_high_times[]; datetime equal_low_times[]; FindEqualHighsLows(symbol, timeframe, bars_to_analyze, equal_highs, equal_lows, equal_high_times, equal_low_times); // Look for liquidity sweeps above equal highs for (int i = 0; i < ArraySize(equal_highs); i++) { double equal_high = equal_highs[i]; datetime equal_time = equal_high_times[i]; int equal_bar = iBarShift(symbol, timeframe, equal_time); if (equal_bar < 0) continue; // Look for sweep above this equal high for (int j = 0; j < equal_bar && j < 20; j++) { double current_high = iHigh(symbol, timeframe, j); double current_close = iClose(symbol, timeframe, j); datetime current_time = iTime(symbol, timeframe, j); // Check if price swept above equal high if (current_high > equal_high + min_sweep_distance) { // Check for rejection (close back below equal high) if (current_close < equal_high) { LiquiditySweep sweep; sweep.level = equal_high; sweep.time = current_time; sweep.is_high_sweep = true; sweep.confirmed = ConfirmLiquiditySweep(symbol, timeframe, j, true, equal_high); if (sweep.confirmed) { ArrayResize(sweep_array, ArraySize(sweep_array) + 1); sweep_array[ArraySize(sweep_array) - 1] = sweep; LogPattern("Liquidity Sweep", symbol, StringFormat("High sweep at %.5f, Distance: %.1f pips", equal_high, (current_high - equal_high) / pip_value)); } break; } } } } // Look for liquidity sweeps below equal lows for (int i = 0; i < ArraySize(equal_lows); i++) { double equal_low = equal_lows[i]; datetime equal_time = equal_low_times[i]; int equal_bar = iBarShift(symbol, timeframe, equal_time); if (equal_bar < 0) continue; // Look for sweep below this equal low for (int j = 0; j < equal_bar && j < 20; j++) { double current_low = iLow(symbol, timeframe, j); double current_close = iClose(symbol, timeframe, j); datetime current_time = iTime(symbol, timeframe, j); // Check if price swept below equal low if (current_low < equal_low - min_sweep_distance) { // Check for rejection (close back above equal low) if (current_close > equal_low) { LiquiditySweep sweep; sweep.level = equal_low; sweep.time = current_time; sweep.is_high_sweep = false; sweep.confirmed = ConfirmLiquiditySweep(symbol, timeframe, j, false, equal_low); if (sweep.confirmed) { ArrayResize(sweep_array, ArraySize(sweep_array) + 1); sweep_array[ArraySize(sweep_array) - 1] = sweep; LogPattern("Liquidity Sweep", symbol, StringFormat("Low sweep at %.5f, Distance: %.1f pips", equal_low, (equal_low - current_low) / pip_value)); } break; } } } } LogDebug(StringFormat("Found %d Liquidity Sweeps on %s %s", ArraySize(sweep_array), symbol, EnumToString(timeframe))); return ArraySize(sweep_array) > 0; } void FindEqualHighsLows(string symbol, ENUM_TIMEFRAMES timeframe, int bars_to_analyze, double &equal_highs[], double &equal_lows[], datetime &equal_high_times[], datetime &equal_low_times[]) { ArrayResize(equal_highs, 0); ArrayResize(equal_lows, 0); ArrayResize(equal_high_times, 0); ArrayResize(equal_low_times, 0); double pip_value = CalculatePipValue(symbol); double tolerance = 2.0 * pip_value; // 2 pip tolerance for "equal" levels // Find swing points first double swing_highs[]; double swing_lows[]; datetime swing_high_times[]; datetime swing_low_times[]; FindSwingPoints(symbol, timeframe, bars_to_analyze, swing_highs, swing_lows, swing_high_times, swing_low_times); // Find equal highs for (int i = 0; i < ArraySize(swing_highs); i++) { double current_high = swing_highs[i]; datetime current_time = swing_high_times[i]; int equal_count = 1; // Count how many swing highs are at similar level for (int j = i + 1; j < ArraySize(swing_highs); j++) { if (MathAbs(swing_highs[j] - current_high) <= tolerance) { equal_count++; } } // If we have at least 2 equal highs, add to array if (equal_count >= 2) { // Check if this level is already in the array bool already_exists = false; for (int k = 0; k < ArraySize(equal_highs); k++) { if (MathAbs(equal_highs[k] - current_high) <= tolerance) { already_exists = true; break; } } if (!already_exists) { ArrayResize(equal_highs, ArraySize(equal_highs) + 1); ArrayResize(equal_high_times, ArraySize(equal_high_times) + 1); equal_highs[ArraySize(equal_highs) - 1] = current_high; equal_high_times[ArraySize(equal_high_times) - 1] = current_time; } } } // Find equal lows for (int i = 0; i < ArraySize(swing_lows); i++) { double current_low = swing_lows[i]; datetime current_time = swing_low_times[i]; int equal_count = 1; // Count how many swing lows are at similar level for (int j = i + 1; j < ArraySize(swing_lows); j++) { if (MathAbs(swing_lows[j] - current_low) <= tolerance) { equal_count++; } } // If we have at least 2 equal lows, add to array if (equal_count >= 2) { // Check if this level is already in the array bool already_exists = false; for (int k = 0; k < ArraySize(equal_lows); k++) { if (MathAbs(equal_lows[k] - current_low) <= tolerance) { already_exists = true; break; } } if (!already_exists) { ArrayResize(equal_lows, ArraySize(equal_lows) + 1); ArrayResize(equal_low_times, ArraySize(equal_low_times) + 1); equal_lows[ArraySize(equal_lows) - 1] = current_low; equal_low_times[ArraySize(equal_low_times) - 1] = current_time; } } } } bool ConfirmLiquiditySweep(string symbol, ENUM_TIMEFRAMES timeframe, int sweep_bar, bool is_high_sweep, double level) { // Check for strong rejection after the sweep double sweep_high = iHigh(symbol, timeframe, sweep_bar); double sweep_low = iLow(symbol, timeframe, sweep_bar); double sweep_close = iClose(symbol, timeframe, sweep_bar); if (is_high_sweep) { // For high sweep, look for bearish rejection double wick_size = sweep_high - sweep_close; double body_size = MathAbs(iClose(symbol, timeframe, sweep_bar) - iOpen(symbol, timeframe, sweep_bar)); // Wick should be at least 2x the body size if (wick_size < body_size * 2) return false; // Close should be below the swept level if (sweep_close >= level) return false; } else { // For low sweep, look for bullish rejection double wick_size = sweep_close - sweep_low; double body_size = MathAbs(iClose(symbol, timeframe, sweep_bar) - iOpen(symbol, timeframe, sweep_bar)); // Wick should be at least 2x the body size if (wick_size < body_size * 2) return false; // Close should be above the swept level if (sweep_close <= level) return false; } return true; } bool IsLiquiditySweepValid(string symbol, ENUM_TIMEFRAMES timeframe, LiquiditySweep &sweep) { if (!sweep.confirmed) return false; // Check if sweep is recent enough datetime current_time = iTime(symbol, timeframe, 0); int time_diff = (int)((current_time - sweep.time) / PeriodSeconds(timeframe)); if (time_diff > 10) return false; // Must be within last 10 candles // Check current price position double current_price = iClose(symbol, timeframe, 0); if (sweep.is_high_sweep) { // For high sweep, price should be below the swept level return current_price < sweep.level; } else { // For low sweep, price should be above the swept level return current_price > sweep.level; } } //+------------------------------------------------------------------+ //| Multi-Timeframe Analysis Engine | //+------------------------------------------------------------------+ struct MarketStructureData { OrderBlock order_blocks[]; FairValueGap fair_value_gaps[]; BreakOfStructure bos_events[]; LiquiditySweep liquidity_sweeps[]; ENUM_TIMEFRAMES timeframe; datetime last_update; bool is_valid; }; // Global market structure data for different timeframes MarketStructureData MTF_Data_M1; MarketStructureData MTF_Data_M15; MarketStructureData MTF_Data_H4; MarketStructureData MTF_Data_D1; MarketStructureData MTF_Data_W1; bool InitializeMultiTimeframeAnalysis() { LogInfo("Initializing Multi-Timeframe Analysis Engine"); // Initialize timeframe data structures MTF_Data_M1.timeframe = PERIOD_M1; MTF_Data_M1.is_valid = false; MTF_Data_M1.last_update = 0; MTF_Data_M15.timeframe = PERIOD_M15; MTF_Data_M15.is_valid = false; MTF_Data_M15.last_update = 0; MTF_Data_H4.timeframe = PERIOD_H4; MTF_Data_H4.is_valid = false; MTF_Data_H4.last_update = 0; MTF_Data_D1.timeframe = PERIOD_D1; MTF_Data_D1.is_valid = false; MTF_Data_D1.last_update = 0; MTF_Data_W1.timeframe = PERIOD_W1; MTF_Data_W1.is_valid = false; MTF_Data_W1.last_update = 0; LogInfo("Multi-Timeframe Analysis Engine initialized successfully"); return true; } bool UpdateMultiTimeframeAnalysis(string symbol) { LogDebug("Updating Multi-Timeframe Analysis for " + symbol); bool updated = false; // Update M1 analysis (most frequent) if (IsNewBar(symbol, PERIOD_M1) || !MTF_Data_M1.is_valid) { updated |= UpdateTimeframeData(symbol, MTF_Data_M1); } // Update M15 analysis if (IsTimeframeUpdateNeeded(symbol, MTF_Data_M15) || !MTF_Data_M15.is_valid) { updated |= UpdateTimeframeData(symbol, MTF_Data_M15); } // Update H4 analysis if (IsTimeframeUpdateNeeded(symbol, MTF_Data_H4) || !MTF_Data_H4.is_valid) { updated |= UpdateTimeframeData(symbol, MTF_Data_H4); } // Update D1 analysis if (IsTimeframeUpdateNeeded(symbol, MTF_Data_D1) || !MTF_Data_D1.is_valid) { updated |= UpdateTimeframeData(symbol, MTF_Data_D1); } // Update W1 analysis (least frequent) if (IsTimeframeUpdateNeeded(symbol, MTF_Data_W1) || !MTF_Data_W1.is_valid) { updated |= UpdateTimeframeData(symbol, MTF_Data_W1); } if (updated) { LogDebug("Multi-Timeframe Analysis updated for " + symbol); } return updated; } bool IsTimeframeUpdateNeeded(string symbol, MarketStructureData &mtf_data) { datetime current_bar_time = iTime(symbol, mtf_data.timeframe, 0); return current_bar_time != mtf_data.last_update; } bool UpdateTimeframeData(string symbol, MarketStructureData &mtf_data) { LogDebug(StringFormat("Updating %s analysis for %s", EnumToString(mtf_data.timeframe), symbol)); bool success = true; // Update Order Blocks success &= DetectOrderBlocks(symbol, mtf_data.timeframe, mtf_data.order_blocks); // Update Fair Value Gaps success &= DetectFairValueGaps(symbol, mtf_data.timeframe, mtf_data.fair_value_gaps); // Update Break of Structure events success &= DetectBreakOfStructure(symbol, mtf_data.timeframe, mtf_data.bos_events); // Update Liquidity Sweeps success &= DetectLiquiditySweeps(symbol, mtf_data.timeframe, mtf_data.liquidity_sweeps); // Update metadata mtf_data.last_update = iTime(symbol, mtf_data.timeframe, 0); mtf_data.is_valid = success; if (success) { LogDebug(StringFormat("%s analysis completed: OB=%d, FVG=%d, BOS=%d, Sweeps=%d", EnumToString(mtf_data.timeframe), ArraySize(mtf_data.order_blocks), ArraySize(mtf_data.fair_value_gaps), ArraySize(mtf_data.bos_events), ArraySize(mtf_data.liquidity_sweeps))); } return success; } string GetMarketBias(string symbol) { // Analyze higher timeframes for overall market bias string h4_bias = GetTimeframeBias(symbol, MTF_Data_H4); string d1_bias = GetTimeframeBias(symbol, MTF_Data_D1); string w1_bias = GetTimeframeBias(symbol, MTF_Data_W1); // Weight the biases (Weekly > Daily > H4) if (w1_bias == d1_bias && d1_bias == h4_bias) { return w1_bias; // All timeframes agree } else if (w1_bias == d1_bias) { return w1_bias; // Higher timeframes agree } else if (d1_bias == h4_bias) { return d1_bias; // Lower timeframes agree } else { return w1_bias; // Default to highest timeframe } } string GetTimeframeBias(string symbol, MarketStructureData &mtf_data) { if (!mtf_data.is_valid) return "NEUTRAL"; int bullish_signals = 0; int bearish_signals = 0; // Analyze BOS events for (int i = 0; i < ArraySize(mtf_data.bos_events); i++) { if (IsBOSValid(symbol, mtf_data.timeframe, mtf_data.bos_events[i])) { if (mtf_data.bos_events[i].is_bullish) bullish_signals++; else bearish_signals++; } } // Analyze Order Blocks for (int i = 0; i < ArraySize(mtf_data.order_blocks); i++) { if (mtf_data.order_blocks[i].is_fresh && mtf_data.order_blocks[i].strength > OBStrengthFilter) { if (mtf_data.order_blocks[i].is_bullish) bullish_signals++; else bearish_signals++; } } // Analyze Liquidity Sweeps for (int i = 0; i < ArraySize(mtf_data.liquidity_sweeps); i++) { if (IsLiquiditySweepValid(symbol, mtf_data.timeframe, mtf_data.liquidity_sweeps[i])) { if (mtf_data.liquidity_sweeps[i].is_high_sweep) bearish_signals++; // High sweep typically leads to bearish move else bullish_signals++; // Low sweep typically leads to bullish move } } // Determine bias if (bullish_signals > bearish_signals + 1) return "BULLISH"; else if (bearish_signals > bullish_signals + 1) return "BEARISH"; else return "NEUTRAL"; } bool IsMultiTimeframeAligned(string symbol, bool is_bullish_setup) { if (!RequireMultiTFConfirmation) return true; string market_bias = GetMarketBias(symbol); if (is_bullish_setup) { return market_bias == "BULLISH" || market_bias == "NEUTRAL"; } else { return market_bias == "BEARISH" || market_bias == "NEUTRAL"; } } void CopyMarketStructureData(const MarketStructureData &source, MarketStructureData &dest) { // Copy arrays ArrayResize(dest.order_blocks, ArraySize(source.order_blocks)); ArrayCopy(dest.order_blocks, source.order_blocks); ArrayResize(dest.fair_value_gaps, ArraySize(source.fair_value_gaps)); ArrayCopy(dest.fair_value_gaps, source.fair_value_gaps); ArrayResize(dest.bos_events, ArraySize(source.bos_events)); ArrayCopy(dest.bos_events, source.bos_events); ArrayResize(dest.liquidity_sweeps, ArraySize(source.liquidity_sweeps)); ArrayCopy(dest.liquidity_sweeps, source.liquidity_sweeps); // Copy simple fields dest.timeframe = source.timeframe; dest.last_update = source.last_update; dest.is_valid = source.is_valid; } bool GetTimeframeData(ENUM_TIMEFRAMES timeframe, MarketStructureData &mtf_data) { switch (timeframe) { case PERIOD_M1: CopyMarketStructureData(MTF_Data_M1, mtf_data); return true; case PERIOD_M15: CopyMarketStructureData(MTF_Data_M15, mtf_data); return true; case PERIOD_H4: CopyMarketStructureData(MTF_Data_H4, mtf_data); return true; case PERIOD_D1: CopyMarketStructureData(MTF_Data_D1, mtf_data); return true; case PERIOD_W1: CopyMarketStructureData(MTF_Data_W1, mtf_data); return true; default: return false; } } void PrintMultiTimeframeStatus(string symbol) { if (!EnableDebugMode) return; string status = StringFormat( "=== Multi-Timeframe Status for %s ===\n" + "Market Bias: %s\n" + "M1 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" + "M15 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" + "H4 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" + "D1 - OB:%d FVG:%d BOS:%d Sweeps:%d\n" + "W1 - OB:%d FVG:%d BOS:%d Sweeps:%d", symbol, GetMarketBias(symbol), ArraySize(MTF_Data_M1.order_blocks), ArraySize(MTF_Data_M1.fair_value_gaps), ArraySize(MTF_Data_M1.bos_events), ArraySize(MTF_Data_M1.liquidity_sweeps), ArraySize(MTF_Data_M15.order_blocks), ArraySize(MTF_Data_M15.fair_value_gaps), ArraySize(MTF_Data_M15.bos_events), ArraySize(MTF_Data_M15.liquidity_sweeps), ArraySize(MTF_Data_H4.order_blocks), ArraySize(MTF_Data_H4.fair_value_gaps), ArraySize(MTF_Data_H4.bos_events), ArraySize(MTF_Data_H4.liquidity_sweeps), ArraySize(MTF_Data_D1.order_blocks), ArraySize(MTF_Data_D1.fair_value_gaps), ArraySize(MTF_Data_D1.bos_events), ArraySize(MTF_Data_D1.liquidity_sweeps), ArraySize(MTF_Data_W1.order_blocks), ArraySize(MTF_Data_W1.fair_value_gaps), ArraySize(MTF_Data_W1.bos_events), ArraySize(MTF_Data_W1.liquidity_sweeps)); LogDebug(status); }