//+------------------------------------------------------------------+ //| CandlestickPatternEA.mq5 | //| Copyright 2025, ART inc. | //| | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, ARTi" #property link "https://www.abbeyroadtech.com" #property version "1.00" #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 // 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 // Order tracking int totalBuyPositions = 0; // Track current buy positions int totalSellPositions = 0; // Track current sell positions //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { // Initialize symbol info if(!SymbolInfo.Name(_Symbol)) { Print("Failed to initialize symbol info"); return INIT_FAILED; } // Initialize trade object Trade.SetExpertMagicNumber(123456); // Set a magic number for this EA // 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); // Count currently open positions CountOpenPositions(); // Log initialization info string atrModeDesc = InpUseFixedATRValue ? "Fixed value: " + DoubleToString(InpMinATRValue, 5) : "Dynamic: " + DoubleToString(InpATRPercentage, 1) + "% of peak over " + IntegerToString(InpATRLookbackPeriod) + " bars"; Print("EA initialized with volatility filters: ATR = ", (InpUseATRFilter ? "ON (" + atrModeDesc + ")" : "OFF"), ", BBW = ", (InpUseBBWFilter ? "ON" : "OFF")); 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; // Count currently open positions CountOpenPositions(); // Check if maximum positions reached if(totalBuyPositions + totalSellPositions >= InpMaxPositions) 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) { // Close buy positions on a sell pattern signal CloseBuyPositions(); } if(buyPatternSignal && InpCloseOnOppositeSignal) { // Close sell positions on a buy pattern signal CloseSellPositions(); } // 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)) { // 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)) { // 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 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); Print("Dynamic ATR Threshold: Peak ATR = ", DoubleToString(peakATR, 5), ", Required ", DoubleToString(InpATRPercentage, 1), "% = ", DoubleToString(minRequiredATR, 5)); } // 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; } else { Print("ATR Filter PASSED: Current ATR (", DoubleToString(currentATR, 5), ") exceeds minimum threshold (", DoubleToString(minRequiredATR, 5), ")"); } } // 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; } else { Print("BB Width Filter PASSED: Current width (", DoubleToString(bbWidth, 5), ") exceeds minimum threshold (", DoubleToString(InpMinBBWidth, 5), ")"); } } // 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); totalBuyPositions++; } } //+------------------------------------------------------------------+ //| 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); totalSellPositions++; } } //+------------------------------------------------------------------+ //| Close all Buy positions | //+------------------------------------------------------------------+ void CloseBuyPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == Trade.RequestMagic()) { // Check if it's a buy position if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { // Close the position if(!Trade.PositionClose(PositionGetTicket(i))) { Print("Failed to close buy position: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } else { Print("Buy position closed on opposite signal"); } } } } } // Reset counter totalBuyPositions = 0; } //+------------------------------------------------------------------+ //| Close all Sell positions | //+------------------------------------------------------------------+ void CloseSellPositions() { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == Trade.RequestMagic()) { // Check if it's a sell position if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { // Close the position if(!Trade.PositionClose(PositionGetTicket(i))) { Print("Failed to close sell position: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } else { Print("Sell position closed on opposite signal"); } } } } } // Reset counter totalSellPositions = 0; } //+------------------------------------------------------------------+ //| Close only losing positions by type | //+------------------------------------------------------------------+ void CloseLosingPositions(ENUM_POSITION_TYPE posType) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionSelectByTicket(PositionGetTicket(i))) { // Check if it's our EA's position and the correct type if(PositionGetInteger(POSITION_MAGIC) == Trade.RequestMagic() && PositionGetInteger(POSITION_TYPE) == posType) { // Check if the position is losing double posProfit = PositionGetDouble(POSITION_PROFIT); if(posProfit < 0) // Only close if it's losing money { // Close the position if(!Trade.PositionClose(PositionGetTicket(i))) { Print("Failed to close losing position: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } else { string posTypeStr = (posType == POSITION_TYPE_BUY) ? "Buy" : "Sell"; Print("Losing ", posTypeStr, " position closed on opposite signal. Profit: ", posProfit); } } } } } // Reset counters after closing positions CountOpenPositions(); } //+------------------------------------------------------------------+ //| Count open positions | //+------------------------------------------------------------------+ void CountOpenPositions() { totalBuyPositions = 0; totalSellPositions = 0; for(int i = 0; i < PositionsTotal(); i++) { if(PositionSelectByTicket(PositionGetTicket(i))) { // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) == Trade.RequestMagic()) { // Count by position type if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) totalBuyPositions++; else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) totalSellPositions++; } } } } //+------------------------------------------------------------------+ //| 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(!PositionSelectByTicket(ticket)) continue; // Check if it's our EA's position if(PositionGetInteger(POSITION_MAGIC) != Trade.RequestMagic()) continue; // Get position details double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); 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 = bid - InpTrailingStop * point; // 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, PositionGetDouble(POSITION_TP))) { Print("Trailing stop adjusted for BUY position #", ticket, " New stop loss: ", newSL, " Previous stop loss: ", currentSL); } else { Print("Failed to adjust trailing stop: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } } } } // 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 = ask + InpTrailingStop * point; // 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, PositionGetDouble(POSITION_TP))) { Print("Trailing stop adjusted for SELL position #", ticket, " New stop loss: ", newSL, " Previous stop loss: ", currentSL); } else { Print("Failed to adjust trailing stop: Error ", Trade.ResultRetcode(), ", ", Trade.ResultRetcodeDescription()); } } } } } }