//+------------------------------------------------------------------+ //| CandlestickPatternEA_Fixed.mq5 | //| Copyright 2025, ART inc. | //| CRITICAL FIX: Position counting | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, ARTi" #property link "https://www.abbeyroadtech.com" #property version "1.01" #property strict // Include necessary libraries #include #include // Input Parameters for Trading input double InpLotSize = 0.01; // Lot size input int InpStopLoss = 100; // Stop Loss in points input int InpTakeProfit = 200; // Take Profit in points input bool InpUseHammerSignals = true; // Trade Hammer signals input bool InpUsePinBarSignals = true; // Trade Pin Bar signals input bool InpUseWickRejection = true; // Trade Wick Rejection signals input bool InpUseTweezerTop = true; // Trade Tweezer Top signals input bool InpUseShootingStar = true; // Trade Shooting Star signals input int InpMaxPositions = 5; // Maximum open positions input bool InpCloseOnOppositeSignal = true; // Close position on opposite signal input ulong InpMagicNumber = 123456; // Magic number for this EA // Trailing Stop Parameters input bool InpUseTrailingStop = true; // Use trailing stop input int InpTrailingStart = 50; // Points of profit before trailing begins input int InpTrailingStep = 10; // Trailing step in points input int InpTrailingStop = 30; // Trailing stop distance in points // Input Parameters for Moving Average Filter input int InpFastMA = 50; // Fast MA period input int InpSlowMA = 200; // Slow MA period input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA method input ENUM_APPLIED_PRICE InpMAPrice = PRICE_CLOSE; // Applied price // Input Parameters for Indicator input int InpCandlesToAnalyze = 300; // Number of candles to analyze input double InpHammerRatio = 0.3; // Hammer body to wick ratio input double InpPinBarRatio = 0.25; // Pin bar body to wick ratio input double InpWickRejectionRatio = 0.4; // Wick rejection ratio input double InpTweezerMaxDiff = 0.1; // Tweezer top max difference % input double InpShootingStarRatio = 0.3; // Shooting star body to wick ratio input int InpConfirmationCandles = 1; // Confirmation candles // ATR Filter Parameters input bool InpUseATRFilter = true; // Use ATR filter input int InpATRPeriod = 14; // ATR period input group "ATR Filter Mode" input bool InpUseFixedATRValue = true; // Use fixed ATR value (vs percentage of peak) input double InpMinATRValue = 0.0010; // Fixed: Minimum ATR value to trade (adjust for your pair) input int InpATRLookbackPeriod = 50; // Period to find peak ATR value input double InpATRPercentage = 30.0; // Percentage of peak ATR (30% = 0.3 * max ATR) // Bollinger Band Width Filter Parameters input bool InpUseBBWFilter = true; // Use Bollinger Band Width filter input int InpBBPeriod = 20; // Bollinger Bands period input double InpBBDeviation = 2.0; // Bollinger Bands deviation input double InpMinBBWidth = 0.0020; // Minimum BB width to trade (as ratio) // Global Variables CTrade Trade; // Trading object CSymbolInfo SymbolInfo; // Symbol info object int FastMAHandle; // Fast MA indicator handle int SlowMAHandle; // Slow MA indicator handle int ATRHandle; // ATR indicator handle int BBHandle; // Bollinger Bands indicator handle int PatternIndicatorHandle; // Candlestick pattern indicator handle bool isTradingAllowed = true; // Flag to control trading datetime lastBarTime = 0; // Last processed bar time // CRITICAL FIX: Removed unreliable position counters // Now using CountOpenPositions() directly every time //+------------------------------------------------------------------+ //| Input Validation | //+------------------------------------------------------------------+ bool ValidateInputs() { // Validate lot size if(InpLotSize <= 0) { Alert("ERROR: Lot size must be positive"); return false; } double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); if(InpLotSize < minLot || InpLotSize > maxLot) { Alert("ERROR: Lot size must be between ", minLot, " and ", maxLot); return false; } // Validate SL/TP if(InpStopLoss <= 0) { Alert("ERROR: Stop Loss must be positive"); return false; } if(InpTakeProfit <= 0) { Alert("ERROR: Take Profit must be positive"); return false; } if(InpStopLoss >= InpTakeProfit) { Alert("WARNING: Stop Loss (", InpStopLoss, ") >= Take Profit (", InpTakeProfit, "). Unfavorable R:R ratio!"); // Don't return false - just warn, trader might want this } // Validate MA periods if(InpFastMA <= 0 || InpSlowMA <= 0) { Alert("ERROR: MA periods must be positive"); return false; } if(InpFastMA >= InpSlowMA) { Alert("ERROR: Fast MA (", InpFastMA, ") should be less than Slow MA (", InpSlowMA, ")"); return false; } // Validate max positions if(InpMaxPositions <= 0 || InpMaxPositions > 100) { Alert("ERROR: Max positions must be between 1 and 100"); return false; } // Validate trailing stop parameters if(InpUseTrailingStop) { if(InpTrailingStart <= 0 || InpTrailingStop <= 0 || InpTrailingStep <= 0) { Alert("ERROR: Trailing stop parameters must be positive"); return false; } if(InpTrailingStop >= InpTrailingStart) { Alert("WARNING: Trailing stop distance (", InpTrailingStop, ") >= start level (", InpTrailingStart, ")"); } } // Validate ATR parameters if(InpUseATRFilter) { if(InpATRPeriod <= 0) { Alert("ERROR: ATR period must be positive"); return false; } if(InpUseFixedATRValue) { if(InpMinATRValue <= 0) { Alert("ERROR: Minimum ATR value must be positive"); return false; } } else { if(InpATRPercentage <= 0 || InpATRPercentage > 100) { Alert("ERROR: ATR percentage must be between 1 and 100"); return false; } if(InpATRLookbackPeriod <= 0) { Alert("ERROR: ATR lookback period must be positive"); return false; } } } // Validate BB parameters if(InpUseBBWFilter) { if(InpBBPeriod <= 0 || InpBBDeviation <= 0) { Alert("ERROR: BB period and deviation must be positive"); return false; } if(InpMinBBWidth <= 0) { Alert("ERROR: Minimum BB width must be positive"); return false; } } // Validate pattern ratios if(InpHammerRatio <= 0 || InpPinBarRatio <= 0 || InpWickRejectionRatio <= 0 || InpTweezerMaxDiff < 0 || InpShootingStarRatio <= 0) { Alert("ERROR: Pattern ratios must be positive"); return false; } return true; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // CRITICAL FIX: Validate all inputs first if(!ValidateInputs()) return INIT_FAILED; // Initialize symbol info if(!SymbolInfo.Name(_Symbol)) { Print("Failed to initialize symbol info"); return INIT_FAILED; } // CRITICAL FIX: Use input magic number instead of hardcoded Trade.SetExpertMagicNumber(InpMagicNumber); // Initialize indicator handles FastMAHandle = iMA(_Symbol, _Period, InpFastMA, 0, InpMAMethod, InpMAPrice); SlowMAHandle = iMA(_Symbol, _Period, InpSlowMA, 0, InpMAMethod, InpMAPrice); // Initialize volatility filter indicators if(InpUseATRFilter) ATRHandle = iATR(_Symbol, _Period, InpATRPeriod); if(InpUseBBWFilter) BBHandle = iBands(_Symbol, _Period, InpBBPeriod, InpBBDeviation, 0, PRICE_CLOSE); // Initialize the custom candlestick pattern indicator PatternIndicatorHandle = iCustom(_Symbol, _Period, "CandlePatternConfirmation", InpCandlesToAnalyze, InpHammerRatio, InpPinBarRatio, InpWickRejectionRatio, InpTweezerMaxDiff, InpShootingStarRatio, InpConfirmationCandles); // Check if indicators were created successfully if(FastMAHandle == INVALID_HANDLE || SlowMAHandle == INVALID_HANDLE || PatternIndicatorHandle == INVALID_HANDLE) { Print("Failed to create primary indicators: Error ", GetLastError()); return INIT_FAILED; } if((InpUseATRFilter && ATRHandle == INVALID_HANDLE) || (InpUseBBWFilter && BBHandle == INVALID_HANDLE)) { Print("Failed to create volatility filter indicators: Error ", GetLastError()); return INIT_FAILED; } // Set lastBarTime to avoid immediate trading on EA start lastBarTime = iTime(_Symbol, _Period, 0); // CRITICAL FIX: Log initialization with actual position count int buyCount, sellCount; CountOpenPositions(buyCount, sellCount); string atrModeDesc = InpUseFixedATRValue ? "Fixed value: " + DoubleToString(InpMinATRValue, 5) : "Dynamic: " + DoubleToString(InpATRPercentage, 1) + "% of peak over " + IntegerToString(InpATRLookbackPeriod) + " bars"; Print("=== CandlestickPatternEA Fixed v1.01 Initialized ==="); Print("Magic Number: ", InpMagicNumber); Print("Current Positions - Buy: ", buyCount, ", Sell: ", sellCount); Print("ATR Filter: ", (InpUseATRFilter ? "ON (" + atrModeDesc + ")" : "OFF")); Print("BBW Filter: ", (InpUseBBWFilter ? "ON" : "OFF")); Print("Max Positions: ", InpMaxPositions); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Release indicator handles if(FastMAHandle != INVALID_HANDLE) IndicatorRelease(FastMAHandle); if(SlowMAHandle != INVALID_HANDLE) IndicatorRelease(SlowMAHandle); if(PatternIndicatorHandle != INVALID_HANDLE) IndicatorRelease(PatternIndicatorHandle); if(InpUseATRFilter && ATRHandle != INVALID_HANDLE) IndicatorRelease(ATRHandle); if(InpUseBBWFilter && BBHandle != INVALID_HANDLE) IndicatorRelease(BBHandle); Print("Expert removed. Reason: ", reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { // Check if there's a new bar datetime currentBarTime = iTime(_Symbol, _Period, 0); bool isNewBar = (currentBarTime != lastBarTime); // Process trailing stops on every tick (not just new bars) if(InpUseTrailingStop) { ManageTrailingStops(); } // Only process signals on new bar if(!isNewBar) return; // Update lastBarTime lastBarTime = currentBarTime; // Check if trading is allowed if(!isTradingAllowed) return; // CRITICAL FIX: Get actual position counts directly int totalBuyPositions, totalSellPositions; CountOpenPositions(totalBuyPositions, totalSellPositions); int totalPositions = totalBuyPositions + totalSellPositions; // Check if maximum positions reached if(totalPositions >= InpMaxPositions) { Print("Max positions reached (", totalPositions, "/", InpMaxPositions, "). Skipping trade check."); return; } // Check volatility filters first if(!CheckVolatilityFilters()) { Print("Trade skipped due to low volatility or market indecision"); return; } // Check for MA filter bool fastAboveSlow = false; bool fastBelowSlow = false; CheckMAFilter(fastAboveSlow, fastBelowSlow); // Get candlestick pattern signals bool hammerSignal = false; bool pinBarSignal = false; bool wickRejectionBullSignal = false; bool wickRejectionBearSignal = false; bool tweezerTopSignal = false; bool shootingStarSignal = false; CheckCandlestickPatterns(hammerSignal, pinBarSignal, wickRejectionBullSignal, wickRejectionBearSignal, tweezerTopSignal, shootingStarSignal); // Define candlestick pattern signals (without MA filter) bool buyPatternSignal = (InpUseHammerSignals && hammerSignal) || (InpUseWickRejection && wickRejectionBullSignal); bool sellPatternSignal = (InpUsePinBarSignals && pinBarSignal) || (InpUseWickRejection && wickRejectionBearSignal) || (InpUseTweezerTop && tweezerTopSignal) || (InpUseShootingStar && shootingStarSignal); // Check for opposite candlestick pattern signals and close positions if needed // This happens regardless of MA filter - based ONLY on candlestick patterns if(sellPatternSignal && InpCloseOnOppositeSignal && totalBuyPositions > 0) { Print("Sell pattern detected - closing ", totalBuyPositions, " buy position(s)"); CloseBuyPositions(); } if(buyPatternSignal && InpCloseOnOppositeSignal && totalSellPositions > 0) { Print("Buy pattern detected - closing ", totalSellPositions, " sell position(s)"); CloseSellPositions(); } // CRITICAL FIX: Recount positions after potential closes CountOpenPositions(totalBuyPositions, totalSellPositions); totalPositions = totalBuyPositions + totalSellPositions; // For opening new positions, use both candlestick pattern AND MA filter bool validBuySignal = buyPatternSignal && fastAboveSlow; bool validSellSignal = sellPatternSignal && fastBelowSlow; // Process buy signals if(validBuySignal && (totalBuyPositions < InpMaxPositions) && (totalPositions < InpMaxPositions)) { // Check if we can place order (spread, stop level) if(!CanPlaceOrder(ORDER_TYPE_BUY)) { Print("Cannot place buy order - spread or stop level issue"); return; } // Open buy positions based on specific signals if(InpUseHammerSignals && hammerSignal) { OpenBuyPosition("Hammer"); } else if(InpUseWickRejection && wickRejectionBullSignal) { OpenBuyPosition("Wick Rejection Bullish"); } } // Process sell signals if(validSellSignal && (totalSellPositions < InpMaxPositions) && (totalPositions < InpMaxPositions)) { // Check if we can place order (spread, stop level) if(!CanPlaceOrder(ORDER_TYPE_SELL)) { Print("Cannot place sell order - spread or stop level issue"); return; } // Open sell positions based on specific signals if(InpUsePinBarSignals && pinBarSignal) { OpenSellPosition("Pin Bar"); } else if(InpUseWickRejection && wickRejectionBearSignal) { OpenSellPosition("Wick Rejection Bearish"); } else if(InpUseTweezerTop && tweezerTopSignal) { OpenSellPosition("Tweezer Top"); } else if(InpUseShootingStar && shootingStarSignal) { OpenSellPosition("Shooting Star"); } } } //+------------------------------------------------------------------+ //| Check if order can be placed (spread and stop level check) | //+------------------------------------------------------------------+ bool CanPlaceOrder(ENUM_ORDER_TYPE orderType) { SymbolInfo.Refresh(); SymbolInfo.RefreshRates(); double stopLevel = (double)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; double spread = SymbolInfo.Ask() - SymbolInfo.Bid(); double minStopDistance = stopLevel + spread; if(orderType == ORDER_TYPE_BUY) { double slDistance = (SymbolInfo.Ask() - (SymbolInfo.Ask() - InpStopLoss * _Point)) / _Point; if(slDistance < minStopDistance) { Print("Buy SL distance (", slDistance, ") < minimum required (", minStopDistance, ")"); return false; } } else if(orderType == ORDER_TYPE_SELL) { double slDistance = ((SymbolInfo.Bid() + InpStopLoss * _Point) - SymbolInfo.Bid()) / _Point; if(slDistance < minStopDistance) { Print("Sell SL distance (", slDistance, ") < minimum required (", minStopDistance, ")"); return false; } } return true; } //+------------------------------------------------------------------+ //| Check volatility filters to avoid trades during indecision | //+------------------------------------------------------------------+ bool CheckVolatilityFilters() { // Check ATR Filter if(InpUseATRFilter) { double atrValues[]; double minRequiredATR = 0; // Get current ATR value if(CopyBuffer(ATRHandle, 0, 0, 1, atrValues) <= 0) { Print("Failed to copy current ATR value: Error ", GetLastError()); return false; } double currentATR = atrValues[0]; // Determine which ATR threshold to use if(InpUseFixedATRValue) { // Use the fixed value directly minRequiredATR = InpMinATRValue; } else { // Get historical ATR values for the lookback period if(CopyBuffer(ATRHandle, 0, 0, InpATRLookbackPeriod, atrValues) <= 0) { Print("Failed to copy historical ATR values: Error ", GetLastError()); return false; } // Find the peak ATR value in the lookback period double peakATR = 0; for(int i = 0; i < InpATRLookbackPeriod; i++) { if(atrValues[i] > peakATR) peakATR = atrValues[i]; } // Calculate minimum required ATR as percentage of peak minRequiredATR = peakATR * (InpATRPercentage / 100.0); } // If current ATR is below threshold, market volatility is too low if(currentATR < minRequiredATR) { Print("ATR Filter: Current ATR (", DoubleToString(currentATR, 5), ") is below minimum threshold (", DoubleToString(minRequiredATR, 5), ")"); return false; } } // Check Bollinger Band Width Filter if(InpUseBBWFilter) { double upperBand[1], lowerBand[1], middleBand[1]; if(CopyBuffer(BBHandle, 1, 0, 1, upperBand) <= 0 || CopyBuffer(BBHandle, 2, 0, 1, lowerBand) <= 0 || CopyBuffer(BBHandle, 0, 0, 1, middleBand) <= 0) { Print("Failed to copy Bollinger Bands values: Error ", GetLastError()); return false; } // Calculate width as a ratio rather than raw points double bbWidth = (upperBand[0] - lowerBand[0]) / middleBand[0]; // If BB width is below minimum threshold, market is in consolidation if(bbWidth < InpMinBBWidth) { Print("BB Width Filter: Current BB width (", DoubleToString(bbWidth, 5), ") is below minimum threshold (", DoubleToString(InpMinBBWidth, 5), ")"); return false; } } // All filters passed or are disabled return true; } //+------------------------------------------------------------------+ //| Check Moving Average Filter | //+------------------------------------------------------------------+ void CheckMAFilter(bool &fastAboveSlow, bool &fastBelowSlow) { // Get MA values double fastMAValue[1] = {0}; double slowMAValue[1] = {0}; // Copy MA values if(CopyBuffer(FastMAHandle, 0, 0, 1, fastMAValue) <= 0 || CopyBuffer(SlowMAHandle, 0, 0, 1, slowMAValue) <= 0) { Print("Failed to copy MA values: Error ", GetLastError()); return; } // Set filter flags fastAboveSlow = (fastMAValue[0] > slowMAValue[0]); fastBelowSlow = (fastMAValue[0] < slowMAValue[0]); } //+------------------------------------------------------------------+ //| Check Candlestick Patterns | //+------------------------------------------------------------------+ void CheckCandlestickPatterns(bool &hammerSignal, bool &pinBarSignal, bool &wickRejectionBullSignal, bool &wickRejectionBearSignal, bool &tweezerTopSignal, bool &shootingStarSignal) { // Arrays to store indicator values for each pattern double hammerValues[1] = {0}; double pinBarValues[1] = {0}; double wickRejectionValues[1] = {0}; double tweezerTopValues[1] = {0}; double shootingStarValues[1] = {0}; // Get values from the pattern indicator if(CopyBuffer(PatternIndicatorHandle, 0, 1, 1, hammerValues) <= 0 || CopyBuffer(PatternIndicatorHandle, 1, 1, 1, pinBarValues) <= 0 || CopyBuffer(PatternIndicatorHandle, 2, 1, 1, wickRejectionValues) <= 0 || CopyBuffer(PatternIndicatorHandle, 3, 1, 1, tweezerTopValues) <= 0 || CopyBuffer(PatternIndicatorHandle, 4, 1, 1, shootingStarValues) <= 0) { Print("Failed to copy pattern values: Error ", GetLastError()); return; } // Set signal flags hammerSignal = (hammerValues[0] != EMPTY_VALUE); pinBarSignal = (pinBarValues[0] != EMPTY_VALUE); // For wick rejection, we need to determine if it's bullish or bearish if(wickRejectionValues[0] != EMPTY_VALUE) { // Determine if bullish or bearish based on the position relative to the candle double candleHigh = iHigh(_Symbol, _Period, 1); wickRejectionBullSignal = (wickRejectionValues[0] < candleHigh); wickRejectionBearSignal = (wickRejectionValues[0] > candleHigh); } else { wickRejectionBullSignal = false; wickRejectionBearSignal = false; } tweezerTopSignal = (tweezerTopValues[0] != EMPTY_VALUE); shootingStarSignal = (shootingStarValues[0] != EMPTY_VALUE); } //+------------------------------------------------------------------+ //| Open a Buy position | //+------------------------------------------------------------------+ void OpenBuyPosition(string signalType) { // Update symbol info SymbolInfo.Refresh(); SymbolInfo.RefreshRates(); // Calculate position size double lotSize = InpLotSize; // Calculate stop loss and take profit levels double stopLoss = SymbolInfo.Ask() - InpStopLoss * SymbolInfo.Point(); double takeProfit = SymbolInfo.Ask() + InpTakeProfit * SymbolInfo.Point(); // Execute buy order if(!Trade.Buy(lotSize, _Symbol, SymbolInfo.Ask(), stopLoss, takeProfit, signalType)) { Print("Buy order failed: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } else { Print("Buy order placed successfully: Signal = ", signalType, ", Lots = ", lotSize); } } //+------------------------------------------------------------------+ //| Open a Sell position | //+------------------------------------------------------------------+ void OpenSellPosition(string signalType) { // Update symbol info SymbolInfo.Refresh(); SymbolInfo.RefreshRates(); // Calculate position size double lotSize = InpLotSize; // Calculate stop loss and take profit levels double stopLoss = SymbolInfo.Bid() + InpStopLoss * SymbolInfo.Point(); double takeProfit = SymbolInfo.Bid() - InpTakeProfit * SymbolInfo.Point(); // Execute sell order if(!Trade.Sell(lotSize, _Symbol, SymbolInfo.Bid(), stopLoss, takeProfit, signalType)) { Print("Sell order failed: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } else { Print("Sell order placed successfully: Signal = ", signalType, ", Lots = ", lotSize); } } //+------------------------------------------------------------------+ //| Close all Buy positions | //+------------------------------------------------------------------+ void CloseBuyPositions() { int closedCount = 0; int failedCount = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionSelectByTicket(ticket)) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { // Check if it's a buy position if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { // Close the position if(!Trade.PositionClose(ticket)) { Print("Failed to close buy position #", ticket, ": Error ", Trade.ResultRetcode()); failedCount++; } else { Print("Buy position #", ticket, " closed on opposite signal"); closedCount++; } } } } } Print("CloseBuyPositions: ", closedCount, " closed, ", failedCount, " failed"); } //+------------------------------------------------------------------+ //| Close all Sell positions | //+------------------------------------------------------------------+ void CloseSellPositions() { int closedCount = 0; int failedCount = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionSelectByTicket(ticket)) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { // Check if it's a sell position if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { // Close the position if(!Trade.PositionClose(ticket)) { Print("Failed to close sell position #", ticket, ": Error ", Trade.ResultRetcode()); failedCount++; } else { Print("Sell position #", ticket, " closed on opposite signal"); closedCount++; } } } } } Print("CloseSellPositions: ", closedCount, " closed, ", failedCount, " failed"); } //+------------------------------------------------------------------+ //| CRITICAL FIX: Count open positions properly | //+------------------------------------------------------------------+ void CountOpenPositions(int &buyCount, int &sellCount) { buyCount = 0; sellCount = 0; for(int i = 0; i < PositionsTotal(); i++) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(PositionSelectByTicket(ticket)) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) { // Count by position type ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); if(posType == POSITION_TYPE_BUY) buyCount++; else if(posType == POSITION_TYPE_SELL) sellCount++; } } } } //+------------------------------------------------------------------+ //| Manage trailing stops for all open positions | //+------------------------------------------------------------------+ void ManageTrailingStops() { // Update symbol info SymbolInfo.Refresh(); SymbolInfo.RefreshRates(); double ask = SymbolInfo.Ask(); double bid = SymbolInfo.Bid(); double point = SymbolInfo.Point(); // Process all open positions for(int i = 0; i < PositionsTotal(); i++) { // Select position by ticket ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; // Get position details double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // Trailing logic for BUY positions if(posType == POSITION_TYPE_BUY) { // Calculate profit in points double profitPoints = (bid - openPrice) / point; // Only trail if minimum profit is reached if(profitPoints >= InpTrailingStart) { // Calculate new stop loss level double newSL = NormalizeDouble(bid - InpTrailingStop * point, _Digits); // Only modify if new SL is higher (better) than current one // and at least one trailing step away from current SL if(newSL > currentSL + InpTrailingStep * point) { if(Trade.PositionModify(ticket, newSL, currentTP)) { Print("Trailing stop adjusted for BUY position #", ticket, " New SL: ", newSL, " Previous SL: ", currentSL); } else { Print("Failed to adjust trailing stop for #", ticket, ": Error ", Trade.ResultRetcode()); } } } } // Trailing logic for SELL positions else if(posType == POSITION_TYPE_SELL) { // Calculate profit in points double profitPoints = (openPrice - ask) / point; // Only trail if minimum profit is reached if(profitPoints >= InpTrailingStart) { // Calculate new stop loss level double newSL = NormalizeDouble(ask + InpTrailingStop * point, _Digits); // Only modify if new SL is lower (better) than current one // and at least one trailing step away from current SL if(currentSL == 0 || newSL < currentSL - InpTrailingStep * point) { if(Trade.PositionModify(ticket, newSL, currentTP)) { Print("Trailing stop adjusted for SELL position #", ticket, " New SL: ", newSL, " Previous SL: ", currentSL); } else { Print("Failed to adjust trailing stop for #", ticket, ": Error ", Trade.ResultRetcode()); } } } } } } //+------------------------------------------------------------------+