diff --git a/src/SniperEA.mq5 b/src/SniperEA.mq5 index feb6ed1..a463adc 100644 --- a/src/SniperEA.mq5 +++ b/src/SniperEA.mq5 @@ -72,6 +72,21 @@ input bool EnableDebugMode = true; / input bool LogPatternDetection = true; // Log pattern detection events input bool LogTradeExecution = true; // Log trade execution details +input group "=== Visualization Settings ===" input bool EnableVisualization = true; // Enable chart visualization +input bool ShowInfoPanel = true; // Show information panel +input int MaxObjectsPerPattern = 50; // Maximum objects per pattern type + +input group "=== Visualization Colors ===" input color BullishOBColor = clrDodgerBlue; // Bullish Order Block color +input color BearishOBColor = clrCrimson; // Bearish Order Block color +input color BullishFVGColor = clrLimeGreen; // Bullish Fair Value Gap color +input color BearishFVGColor = clrOrange; // Bearish Fair Value Gap color +input color BullishBOSColor = clrGreen; // Bullish Break of Structure color +input color BearishBOSColor = clrRed; // Bearish Break of Structure color +input color SweepColor = clrYellow; // Liquidity Sweep color +input color EntryLevelColor = clrWhite; // Entry level color +input color StopLossColor = clrRed; // Stop Loss level color +input color TakeProfitColor = clrGreen; // Take Profit level color + //--- Global variables string SymbolsToTrade[]; int TotalSymbols = 0; @@ -387,14 +402,391 @@ void CleanupChartObjects() Print("Cleaned up ", total_objects, " chart objects"); } +//+------------------------------------------------------------------+ +//| Phase 3: Visualization Helper Functions | +//+------------------------------------------------------------------+ +string GenerateObjectName(string pattern_type, string symbol, datetime time, int index = 0) +{ + return StringFormat("SniperEA_%s_%s_%d_%d", pattern_type, symbol, (int)time, index); +} + +void CleanupPatternObjects(string pattern_type, string symbol = "") +{ + string prefix = (symbol == "") ? StringFormat("SniperEA_%s_", pattern_type) : StringFormat("SniperEA_%s_%s_", pattern_type, symbol); + + int deleted = ObjectsDeleteAll(0, prefix); + if (deleted > 0 && EnableDebugMode) + { + LogDebug(StringFormat("Cleaned up %d %s objects for %s", deleted, pattern_type, + (symbol == "") ? "all symbols" : symbol)); + } +} + +bool IsVisualizationEnabled() +{ + return EnableVisualization; +} + +int CountPatternObjects(string pattern_type, string symbol = "") +{ + string prefix = (symbol == "") ? StringFormat("SniperEA_%s_", pattern_type) : StringFormat("SniperEA_%s_%s_", pattern_type, symbol); + + int count = 0; + int total_objects = ObjectsTotal(0); + + for (int i = 0; i < total_objects; i++) + { + string obj_name = ObjectName(0, i); + if (StringFind(obj_name, prefix) == 0) + count++; + } + + return count; +} + +//+------------------------------------------------------------------+ +//| Phase 3: Pattern Drawing Functions | +//+------------------------------------------------------------------+ +void DrawOrderBlock(string symbol, OrderBlock &ob, color block_color = clrNONE) +{ + if (!IsVisualizationEnabled() || !ShowOrderBlocks) + return; + + // Check object limit + if (CountPatternObjects("OB", symbol) >= MaxObjectsPerPattern) + { + CleanupPatternObjects("OB", symbol); + } + + // Generate unique object name + string obj_name = GenerateObjectName("OB", symbol, ob.time); + + // Delete existing object if it exists + ObjectDelete(0, obj_name); + + // Determine color based on bullish/bearish and user settings + color draw_color = block_color; + if (draw_color == clrNONE) + { + draw_color = ob.is_bullish ? BullishOBColor : BearishOBColor; + } + + // Create rectangle object + if (ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, ob.time, ob.high, ob.time + PeriodSeconds(PERIOD_H1) * 24, ob.low)) + { + // Set rectangle properties + ObjectSetInteger(0, obj_name, OBJPROP_COLOR, draw_color); + ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, obj_name, OBJPROP_FILL, true); + ObjectSetInteger(0, obj_name, OBJPROP_BACK, true); + ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, obj_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, obj_name, OBJPROP_HIDDEN, true); + + // Add transparency + ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, draw_color); + ObjectSetInteger(0, obj_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); + + // Set description + string description = StringFormat("Order Block %s - Strength: %.2f - %s", + ob.is_bullish ? "Bullish" : "Bearish", + ob.strength, + ob.is_fresh ? "Fresh" : "Used"); + ObjectSetString(0, obj_name, OBJPROP_TOOLTIP, description); + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew Order Block: %s at %.5f-%.5f", + ob.is_bullish ? "Bullish" : "Bearish", ob.low, ob.high)); + } + } + else + { + LogWarning(StringFormat("Failed to create Order Block object: %s", obj_name)); + } +} + +void DrawFairValueGap(string symbol, FairValueGap &fvg, color gap_color = clrNONE) +{ + if (!IsVisualizationEnabled() || !ShowFVG) + return; + + // Check object limit + if (CountPatternObjects("FVG", symbol) >= MaxObjectsPerPattern) + { + CleanupPatternObjects("FVG", symbol); + } + + // Generate unique object name + string obj_name = GenerateObjectName("FVG", symbol, fvg.time); + + // Delete existing object if it exists + ObjectDelete(0, obj_name); + + // Determine color based on bullish/bearish and user settings + color draw_color = gap_color; + if (draw_color == clrNONE) + { + draw_color = fvg.is_bullish ? BullishFVGColor : BearishFVGColor; + } + + // Create rectangle object for FVG + if (ObjectCreate(0, obj_name, OBJ_RECTANGLE, 0, fvg.time, fvg.top, fvg.time + PeriodSeconds(PERIOD_H1) * 12, fvg.bottom)) + { + // Set rectangle properties with transparency + ObjectSetInteger(0, obj_name, OBJPROP_COLOR, draw_color); + ObjectSetInteger(0, obj_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, obj_name, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, obj_name, OBJPROP_FILL, true); + ObjectSetInteger(0, obj_name, OBJPROP_BACK, true); + ObjectSetInteger(0, obj_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, obj_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, obj_name, OBJPROP_HIDDEN, true); + + // Add transparency and background color + ObjectSetInteger(0, obj_name, OBJPROP_BGCOLOR, draw_color); + ObjectSetInteger(0, obj_name, OBJPROP_BORDER_TYPE, BORDER_FLAT); + + // Set description + string description = StringFormat("Fair Value Gap %s - Size: %.1f pips - %s", + fvg.is_bullish ? "Bullish" : "Bearish", + MathAbs(fvg.top - fvg.bottom) / SymbolInfoDouble(symbol, SYMBOL_POINT) / 10, + fvg.is_filled ? "Filled" : "Open"); + ObjectSetString(0, obj_name, OBJPROP_TOOLTIP, description); + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew Fair Value Gap: %s at %.5f-%.5f", + fvg.is_bullish ? "Bullish" : "Bearish", fvg.bottom, fvg.top)); + } + } + else + { + LogWarning(StringFormat("Failed to create Fair Value Gap object: %s", obj_name)); + } +} + +void DrawBreakOfStructure(string symbol, BreakOfStructure &bos) +{ + if (!IsVisualizationEnabled() || !ShowBOS) + return; + + // Check object limit + if (CountPatternObjects("BOS", symbol) >= MaxObjectsPerPattern) + { + CleanupPatternObjects("BOS", symbol); + } + + // Generate unique object names for arrow and label + string arrow_name = GenerateObjectName("BOS_Arrow", symbol, bos.time); + string label_name = GenerateObjectName("BOS_Label", symbol, bos.time); + + // Delete existing objects if they exist + ObjectDelete(0, arrow_name); + ObjectDelete(0, label_name); + + // Determine color and arrow code based on direction + color draw_color = bos.is_bullish ? BullishBOSColor : BearishBOSColor; + int arrow_code = bos.is_bullish ? 233 : 234; // Up arrow for bullish, down arrow for bearish + + // Create arrow object + if (ObjectCreate(0, arrow_name, OBJ_ARROW, 0, bos.time, bos.level)) + { + // Set arrow properties + ObjectSetInteger(0, arrow_name, OBJPROP_COLOR, draw_color); + ObjectSetInteger(0, arrow_name, OBJPROP_ARROWCODE, arrow_code); + ObjectSetInteger(0, arrow_name, OBJPROP_WIDTH, 3); + ObjectSetInteger(0, arrow_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, arrow_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, arrow_name, OBJPROP_HIDDEN, true); + + // Set description + string description = StringFormat("Break of Structure %s at %.5f", + bos.is_bullish ? "Bullish" : "Bearish", + bos.level); + ObjectSetString(0, arrow_name, OBJPROP_TOOLTIP, description); + } + + // Create label object + if (ObjectCreate(0, label_name, OBJ_TEXT, 0, bos.time, bos.level)) + { + // Set label properties + ObjectSetInteger(0, label_name, OBJPROP_COLOR, draw_color); + ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, label_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, label_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, label_name, OBJPROP_HIDDEN, true); + ObjectSetString(0, label_name, OBJPROP_FONT, "Arial Bold"); + ObjectSetString(0, label_name, OBJPROP_TEXT, bos.is_bullish ? "BOS↑" : "BOS↓"); + + // Position label slightly offset from the level + double offset = bos.is_bullish ? SymbolInfoDouble(symbol, SYMBOL_POINT) * 50 : -SymbolInfoDouble(symbol, SYMBOL_POINT) * 50; + ObjectSetDouble(0, label_name, OBJPROP_PRICE, bos.level + offset); + } + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew Break of Structure: %s at %.5f", + bos.is_bullish ? "Bullish" : "Bearish", bos.level)); + } +} + +void DrawLiquiditySweep(string symbol, LiquiditySweep &sweep) +{ + if (!IsVisualizationEnabled() || !ShowSweeps) + return; + + // Check object limit + if (CountPatternObjects("SWEEP", symbol) >= MaxObjectsPerPattern) + { + CleanupPatternObjects("SWEEP", symbol); + } + + // Generate unique object names for icon and label + string icon_name = GenerateObjectName("SWEEP_Icon", symbol, sweep.time); + string label_name = GenerateObjectName("SWEEP_Label", symbol, sweep.time); + string line_name = GenerateObjectName("SWEEP_Line", symbol, sweep.time); + + // Delete existing objects if they exist + ObjectDelete(0, icon_name); + ObjectDelete(0, label_name); + ObjectDelete(0, line_name); + + // Create horizontal line at sweep level + if (ObjectCreate(0, line_name, OBJ_HLINE, 0, sweep.time, sweep.level)) + { + ObjectSetInteger(0, line_name, OBJPROP_COLOR, SweepColor); + ObjectSetInteger(0, line_name, OBJPROP_STYLE, STYLE_DOT); + ObjectSetInteger(0, line_name, OBJPROP_WIDTH, 1); + ObjectSetInteger(0, line_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, line_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, line_name, OBJPROP_HIDDEN, true); + } + + // Create sweep icon (triangle) + if (ObjectCreate(0, icon_name, OBJ_ARROW, 0, sweep.time, sweep.level)) + { + // Set icon properties - use different arrows for high/low sweeps + int arrow_code = sweep.is_high_sweep ? 242 : 241; // Triangle up for high sweep, down for low sweep + ObjectSetInteger(0, icon_name, OBJPROP_COLOR, SweepColor); + ObjectSetInteger(0, icon_name, OBJPROP_ARROWCODE, arrow_code); + ObjectSetInteger(0, icon_name, OBJPROP_WIDTH, 4); + ObjectSetInteger(0, icon_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, icon_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, icon_name, OBJPROP_HIDDEN, true); + + // Set description + string description = StringFormat("Liquidity Sweep %s at %.5f - %s", + sweep.is_high_sweep ? "High" : "Low", + sweep.level, + sweep.confirmed ? "Confirmed" : "Pending"); + ObjectSetString(0, icon_name, OBJPROP_TOOLTIP, description); + } + + // Create label + if (ObjectCreate(0, label_name, OBJ_TEXT, 0, sweep.time, sweep.level)) + { + // Set label properties + ObjectSetInteger(0, label_name, OBJPROP_COLOR, SweepColor); + ObjectSetInteger(0, label_name, OBJPROP_FONTSIZE, 8); + ObjectSetInteger(0, label_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, label_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, label_name, OBJPROP_HIDDEN, true); + ObjectSetString(0, label_name, OBJPROP_FONT, "Arial Bold"); + ObjectSetString(0, label_name, OBJPROP_TEXT, sweep.is_high_sweep ? "🔺" : "🔻"); + + // Position label slightly offset from the level + double offset = sweep.is_high_sweep ? SymbolInfoDouble(symbol, SYMBOL_POINT) * 30 : -SymbolInfoDouble(symbol, SYMBOL_POINT) * 30; + ObjectSetDouble(0, label_name, OBJPROP_PRICE, sweep.level + offset); + } + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew Liquidity Sweep: %s at %.5f", + sweep.is_high_sweep ? "High" : "Low", sweep.level)); + } +} + +void DrawTradeLevels(string symbol, double entry, double sl, double tp, string trade_type = "") +{ + if (!IsVisualizationEnabled() || !ShowTradeLevels) + return; + + // Generate unique object names + datetime current_time = TimeCurrent(); + string entry_name = GenerateObjectName("ENTRY", symbol, current_time); + string sl_name = GenerateObjectName("SL", symbol, current_time); + string tp_name = GenerateObjectName("TP", symbol, current_time); + + // Clean up old trade level objects for this symbol + CleanupPatternObjects("ENTRY", symbol); + CleanupPatternObjects("SL", symbol); + CleanupPatternObjects("TP", symbol); + + // Create Entry Level Line + if (entry > 0 && ObjectCreate(0, entry_name, OBJ_HLINE, 0, current_time, entry)) + { + ObjectSetInteger(0, entry_name, OBJPROP_COLOR, EntryLevelColor); + ObjectSetInteger(0, entry_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, entry_name, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, entry_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, entry_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, entry_name, OBJPROP_HIDDEN, true); + + string entry_desc = StringFormat("Entry Level: %.5f %s", entry, trade_type); + ObjectSetString(0, entry_name, OBJPROP_TOOLTIP, entry_desc); + } + + // Create Stop Loss Line + if (sl > 0 && ObjectCreate(0, sl_name, OBJ_HLINE, 0, current_time, sl)) + { + ObjectSetInteger(0, sl_name, OBJPROP_COLOR, StopLossColor); + ObjectSetInteger(0, sl_name, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, sl_name, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, sl_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, sl_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, sl_name, OBJPROP_HIDDEN, true); + + string sl_desc = StringFormat("Stop Loss: %.5f", sl); + ObjectSetString(0, sl_name, OBJPROP_TOOLTIP, sl_desc); + } + + // Create Take Profit Line + if (tp > 0 && ObjectCreate(0, tp_name, OBJ_HLINE, 0, current_time, tp)) + { + ObjectSetInteger(0, tp_name, OBJPROP_COLOR, TakeProfitColor); + ObjectSetInteger(0, tp_name, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, tp_name, OBJPROP_WIDTH, 2); + ObjectSetInteger(0, tp_name, OBJPROP_SELECTABLE, false); + ObjectSetInteger(0, tp_name, OBJPROP_SELECTED, false); + ObjectSetInteger(0, tp_name, OBJPROP_HIDDEN, true); + + string tp_desc = StringFormat("Take Profit: %.5f", tp); + ObjectSetString(0, tp_name, OBJPROP_TOOLTIP, tp_desc); + } + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew Trade Levels for %s: Entry=%.5f, SL=%.5f, TP=%.5f", + symbol, entry, sl, tp)); + } +} + //+------------------------------------------------------------------+ //| Update information panel | //+------------------------------------------------------------------+ void UpdateInfoPanel() { + if (!IsVisualizationEnabled() || !ShowInfoPanel) + return; + // Get current session string current_session = GetCurrentSession(); + // Update EA status + ObjectSetString(0, "SniperEA_Status", OBJPROP_TEXT, "Sniper EA - " + current_session); + // Get account information double account_balance = AccountInfoDouble(ACCOUNT_BALANCE); double account_equity = AccountInfoDouble(ACCOUNT_EQUITY); @@ -403,19 +795,42 @@ void UpdateInfoPanel() // Count current positions int total_positions = PositionsTotal(); - // Create info text + // Get pattern counts for current symbol + MarketStructureData m1_data; + int ob_count = 0, fvg_count = 0, bos_count = 0, sweep_count = 0; + + if (GetTimeframeData(PERIOD_M1, m1_data) && m1_data.is_valid) + { + ob_count = ArraySize(m1_data.order_blocks); + fvg_count = ArraySize(m1_data.fair_value_gaps); + bos_count = ArraySize(m1_data.bos_events); + sweep_count = ArraySize(m1_data.liquidity_sweeps); + } + + // Get market bias + string market_bias = GetMarketBias(_Symbol); + + // Create comprehensive info text string info_text = StringFormat( - "Session: %s\n" + - "Balance: %.2f\n" + - "Equity: %.2f\n" + - "Margin: %.2f\n" + - "Positions: %d/%d", + "=== SNIPER EA DASHBOARD ===\n" + + "Status: ACTIVE | Session: %s\n" + + "Balance: $%.2f | Equity: $%.2f\n" + + "Positions: %d/%d | Risk: %.1f%%\n" + + "\n" + + "=== MARKET ANALYSIS ===\n" + + "Symbol: %s | Bias: %s\n" + + "Order Blocks: %d | FVGs: %d\n" + + "BOS Events: %d | Sweeps: %d\n" + + "\n" + + "=== CAMBODIA TIME ===\n" + + "Current Time: %s", current_session, - account_balance, - account_equity, - account_margin, - total_positions, - MaxPositions); + account_balance, account_equity, + total_positions, MaxPositions, RiskPercent, + _Symbol, market_bias, + ob_count, fvg_count, + bos_count, sweep_count, + TimeToString(TimeGMT() + 7 * 3600, TIME_MINUTES)); // Update info label if (ObjectFind(0, "SniperEA_Info") < 0) @@ -435,6 +850,70 @@ void UpdateInfoPanel() ObjectSetString(0, "SniperEA_Info", OBJPROP_TEXT, info_text); } +void DrawPatternsOnChart(string symbol, MarketStructureData &mtf_data) +{ + if (!IsVisualizationEnabled()) + return; + + // Draw Order Blocks + if (ShowOrderBlocks) + { + 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) + { + DrawOrderBlock(symbol, mtf_data.order_blocks[i]); + } + } + } + + // Draw Fair Value Gaps + if (ShowFVG) + { + for (int i = 0; i < ArraySize(mtf_data.fair_value_gaps); i++) + { + if (!mtf_data.fair_value_gaps[i].is_filled) + { + DrawFairValueGap(symbol, mtf_data.fair_value_gaps[i]); + } + } + } + + // Draw Break of Structure events + if (ShowBOS) + { + for (int i = 0; i < ArraySize(mtf_data.bos_events); i++) + { + if (mtf_data.bos_events[i].confirmed) + { + DrawBreakOfStructure(symbol, mtf_data.bos_events[i]); + } + } + } + + // Draw Liquidity Sweeps + if (ShowSweeps) + { + for (int i = 0; i < ArraySize(mtf_data.liquidity_sweeps); i++) + { + if (mtf_data.liquidity_sweeps[i].confirmed) + { + DrawLiquiditySweep(symbol, mtf_data.liquidity_sweeps[i]); + } + } + } + + if (EnableDebugMode) + { + LogDebug(StringFormat("Drew patterns for %s %s: OB=%d, FVG=%d, BOS=%d, Sweeps=%d", + symbol, 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))); + } +} + //+------------------------------------------------------------------+ //| Get current trading session | //+------------------------------------------------------------------+ @@ -501,6 +980,13 @@ bool ExecuteBuyTrade(string symbol, double entry, double sl, double tp, double l if (result) { LogTrade("BUY EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size)); + + // Draw trade levels on chart + if (symbol == _Symbol) + { + DrawTradeLevels(symbol, entry, sl, tp, "BUY"); + } + return true; } else @@ -531,6 +1017,13 @@ bool ExecuteSellTrade(string symbol, double entry, double sl, double tp, double if (result) { LogTrade("SELL EXECUTED", symbol, StringFormat("Entry: %.5f, SL: %.5f, TP: %.5f, Lot: %.2f", entry, sl, tp, lot_size)); + + // Draw trade levels on chart + if (symbol == _Symbol) + { + DrawTradeLevels(symbol, entry, sl, tp, "SELL"); + } + return true; } else @@ -2832,6 +3325,12 @@ bool UpdateTimeframeData(string symbol, MarketStructureData &mtf_data) mtf_data.current_bias.strength, mtf_data.current_bias.direction, mtf_data.market_phase)); + + // Phase 3: Draw patterns on chart (only for current symbol and M15/H4 timeframes for clarity) + if (symbol == _Symbol && (mtf_data.timeframe == PERIOD_M15 || mtf_data.timeframe == PERIOD_H4)) + { + DrawPatternsOnChart(symbol, mtf_data); + } } return success;