//+------------------------------------------------------------------+ //| TrendPullbackPro.mq5| //| Trend-following pullback EA with risk & management | //+------------------------------------------------------------------+ #property copyright "Hadi Strategy" #property link "" #property version "1.10" #property strict //--- Inputs: risk & money management input double InpRiskPerTradePercent = 1.0; // Total risk per trade (% of balance) input int InpMagicNumber = 123456; // Magic number input double InpStopBufferPoints = 10; // Extra buffer beyond swing high/low (points) //--- Inputs: indicators input ENUM_TIMEFRAMES InpTF = PERIOD_CURRENT; // Signal timeframe input int InpFastEMAPeriod = 20; input int InpSlowEMAPeriod = 50; input int InpRSIPeriod = 14; input double InpRSILowZoneMin = 40; // Uptrend pullback zone min input double InpRSILowZoneMax = 50; // Uptrend pullback zone max input double InpRSIHighZoneMin = 50; // Downtrend pullback zone min input double InpRSIHighZoneMax = 60; // Downtrend pullback zone max //--- Inputs: partial TP and R multiples input bool InpUsePartialTP = true; input double InpPartialCloseAtR = 1.0; // R at which to take partial profit input double InpPartialClosePercent = 50.0; // % of volume to close at partial TP input double InpFinalTargetR = 3.0; // Final target R for runner input bool InpUseBreakEven = true; input double InpBreakEvenRMultiple = 1.0; // Move SL to BE at this R (runner) //--- Inputs: trailing stop input bool InpUseEMATrailing = true; input double InpTrailStartR = 1.0; // Start trailing after this R input double InpTrailOffsetPoints = 5; // Extra buffer beyond EMA (points) //--- Inputs: spread & time filters input bool InpUseSpreadFilter = true; input double InpMaxSpreadPoints = 30; // Max allowed spread (in points) input bool InpUseTimeFilter = false; input int InpSessionStartHour = 8; // Trading session start (server time) input int InpSessionEndHour = 22; // Trading session end (server time) input bool InpAvoidRolloverHour = true; input int InpRolloverHour = 23; // Avoid new entries at this hour //--- constants #define NO_TREND 0 #define UPTREND 1 #define DOWNTREND -1 #define PARTIAL_COMMENT "TrendPullbackEA_Partial" #define RUNNER_COMMENT "TrendPullbackEA_Runner" //--- Global indicator handles int rsi_handle; int ema_fast_handle; int ema_slow_handle; //--- Tick storage MqlTick last_tick; //+------------------------------------------------------------------+ //| OnInit | //+------------------------------------------------------------------+ int OnInit() { rsi_handle = iRSI(_Symbol, InpTF, InpRSIPeriod, PRICE_CLOSE); if(rsi_handle == INVALID_HANDLE) { Print("Error creating RSI handle. Error: ", GetLastError()); return(INIT_FAILED); } ema_fast_handle = iMA(_Symbol, InpTF, InpFastEMAPeriod, 0, MODE_EMA, PRICE_CLOSE); if(ema_fast_handle == INVALID_HANDLE) { Print("Error creating fast EMA handle. Error: ", GetLastError()); return(INIT_FAILED); } ema_slow_handle = iMA(_Symbol, InpTF, InpSlowEMAPeriod, 0, MODE_EMA, PRICE_CLOSE); if(ema_slow_handle == INVALID_HANDLE) { Print("Error creating slow EMA handle. Error: ", GetLastError()); return(INIT_FAILED); } SymbolInfoTick(_Symbol, last_tick); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| OnDeinit | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { IndicatorRelease(rsi_handle); IndicatorRelease(ema_fast_handle); IndicatorRelease(ema_slow_handle); } //+------------------------------------------------------------------+ //| OnTick | //+------------------------------------------------------------------+ void OnTick() { if(!SymbolInfoTick(_Symbol, last_tick)) return; // Run main logic once per bar on signal timeframe static datetime last_bar_time = 0; datetime cur_bar_time = iTime(_Symbol, InpTF, 0); if(cur_bar_time == last_bar_time) return; last_bar_time = cur_bar_time; ManageOpenPositions(); // Avoid new entries if no time/spread conditions if(!CanOpenNewTrades()) return; CheckOpenSignals(); } //+------------------------------------------------------------------+ //| Check if we can open new trades (time & spread) | //+------------------------------------------------------------------+ bool CanOpenNewTrades() { // Spread filter if(InpUseSpreadFilter) { double spread_points = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / SymbolInfoDouble(_Symbol, SYMBOL_POINT); if(spread_points > InpMaxSpreadPoints) { //Print("Spread too high: ", spread_points, " > ", InpMaxSpreadPoints); return(false); } } // Time filter if(InpUseTimeFilter) { datetime now = TimeCurrent(); MqlDateTime dt; TimeToStruct(now, dt); // avoid rollover hour if(InpAvoidRolloverHour && dt.hour == InpRolloverHour) return(false); // session window bool inSession = (dt.hour >= InpSessionStartHour && dt.hour < InpSessionEndHour); if(!inSession) return(false); } return(true); } //+------------------------------------------------------------------+ //| Check for new entry signals | //+------------------------------------------------------------------+ void CheckOpenSignals() { if(HasOpenPosition()) return; int trend = GetTrendDirection(); if(trend == NO_TREND) return; double rsi_value; if(!GetRSI(1, rsi_value)) return; MqlRates rates[]; if(CopyRates(_Symbol, InpTF, 0, 3, rates) < 3) return; // rates[0] current, [1] closed 1, [2] closed 2 MqlRates candle = rates[1]; MqlRates prev = rates[2]; bool longSignal = false; bool shortSignal = false; if(trend == UPTREND) { if(rsi_value >= InpRSILowZoneMin && rsi_value <= InpRSILowZoneMax) { if(IsBullishPattern(candle, prev)) longSignal = true; } } else if(trend == DOWNTREND) { if(rsi_value >= InpRSIHighZoneMin && rsi_value <= InpRSIHighZoneMax) { if(IsBearishPattern(candle, prev)) shortSignal = true; } } if(longSignal) OpenTrade(ORDER_TYPE_BUY, candle); else if(shortSignal) OpenTrade(ORDER_TYPE_SELL, candle); } //+------------------------------------------------------------------+ //| Manage open positions (partial TP, BE, trailing) | //+------------------------------------------------------------------+ void ManageOpenPositions() { double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue; long type = PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); double sl = PositionGetDouble(POSITION_SL); double tp = PositionGetDouble(POSITION_TP); double price_open = PositionGetDouble(POSITION_PRICE_OPEN); string comment = PositionGetString(POSITION_COMMENT); MqlTick tick; SymbolInfoTick(_Symbol, tick); double current_price = (type == POSITION_TYPE_BUY ? tick.bid : tick.ask); double riskPoints; if(type == POSITION_TYPE_BUY) riskPoints = (price_open - sl) / point; else riskPoints = (sl - price_open) / point; if(riskPoints <= 0) continue; double profitPoints; if(type == POSITION_TYPE_BUY) profitPoints = (current_price - price_open) / point; else profitPoints = (price_open - current_price) / point; double rMultiple = profitPoints / riskPoints; // 1) Partial TP logic for "partial" leg if(InpUsePartialTP && comment == PARTIAL_COMMENT) { if(rMultiple >= InpPartialCloseAtR) { // Close this position fully (it's the partial leg) ClosePosition(ticket); continue; } } // 2) Break-even & trailing for runner leg if(comment == RUNNER_COMMENT) { // Break-even if(InpUseBreakEven && rMultiple >= InpBreakEvenRMultiple) { if(type == POSITION_TYPE_BUY && sl < price_open) ModifyPositionSL(ticket, price_open); else if(type == POSITION_TYPE_SELL && sl > price_open) ModifyPositionSL(ticket, price_open); } // EMA trailing after a certain R if(InpUseEMATrailing && rMultiple >= InpTrailStartR) { double emaFast; if(!GetEMA(ema_fast_handle, 1, emaFast)) continue; double newSL; double offset = InpTrailOffsetPoints * point; if(type == POSITION_TYPE_BUY) { newSL = emaFast - offset; if(newSL > sl && newSL < current_price) // only tighten SL ModifyPositionSL(ticket, newSL); } else { newSL = emaFast + offset; if(newSL < sl && newSL > current_price) ModifyPositionSL(ticket, newSL); } } // Optionally: ensure TP aligns with final target R // If user wants to rely only on trailing, they can disable or adjust final R. // We keep TP as set at entry. } } } //+------------------------------------------------------------------+ //| Get trend direction from EMAs | //+------------------------------------------------------------------+ int GetTrendDirection() { double ema_fast[3], ema_slow[3]; if(CopyBuffer(ema_fast_handle, 0, 0, 3, ema_fast) < 3) return NO_TREND; if(CopyBuffer(ema_slow_handle, 0, 0, 3, ema_slow) < 3) return NO_TREND; double fast = ema_fast[1]; double slow = ema_slow[1]; if(fast > slow) return UPTREND; else if(fast < slow) return DOWNTREND; return NO_TREND; } //+------------------------------------------------------------------+ //| Get RSI value at bar index | //+------------------------------------------------------------------+ bool GetRSI(int index, double &value) { double buffer[1]; int copied = CopyBuffer(rsi_handle, 0, index, 1, buffer); if(copied != 1) return false; value = buffer[0]; return true; } //+------------------------------------------------------------------+ //| Get EMA value from handle | //+------------------------------------------------------------------+ bool GetEMA(int handle, int index, double &value) { double buffer[1]; int copied = CopyBuffer(handle, 0, index, 1, buffer); if(copied != 1) return false; value = buffer[0]; return true; } //+------------------------------------------------------------------+ //| Bullish pattern: engulfing + pin-bar-ish | //+------------------------------------------------------------------+ bool IsBullishPattern(const MqlRates &c, const MqlRates &prev) { double bodyC = MathAbs(c.close - c.open); double bodyP = MathAbs(prev.close - prev.open); double rangeC = c.high - c.low; if(rangeC <= 0) return false; // Basic bullish: close > open if(c.close <= c.open) return false; // Engulfing body: current body >= previous body, and covers its open/close bool engulfing = (bodyC >= bodyP && c.open <= prev.close && c.close >= prev.open); // Pin-bar-ish: long lower wick double lowerWick = MathMin(c.open, c.close) - c.low; bool longLowerWick = (lowerWick >= rangeC * 0.4); // tweak if(engulfing || longLowerWick) return true; return false; } //+------------------------------------------------------------------+ //| Bearish pattern: engulfing + pin-bar-ish | //+------------------------------------------------------------------+ bool IsBearishPattern(const MqlRates &c, const MqlRates &prev) { double bodyC = MathAbs(c.close - c.open); double bodyP = MathAbs(prev.close - prev.open); double rangeC = c.high - c.low; if(rangeC <= 0) return false; // Basic bearish: close < open if(c.close >= c.open) return false; // Engulfing body bool engulfing = (bodyC >= bodyP && c.open >= prev.close && c.close <= prev.open); // Pin-bar-ish: long upper wick double upperWick = c.high - MathMax(c.open, c.close); bool longUpperWick = (upperWick >= rangeC * 0.4); // tweak if(engulfing || longUpperWick) return true; return false; } //+------------------------------------------------------------------+ //| Check if there is already an open position | //+------------------------------------------------------------------+ bool HasOpenPosition() { for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) != InpMagicNumber) continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol) return true; } return false; } //+------------------------------------------------------------------+ //| Calculate lot size based on total risk % | //+------------------------------------------------------------------+ double CalculateTotalVolume(double stopLossPrice, ENUM_ORDER_TYPE type) { double balance = AccountInfoDouble(ACCOUNT_BALANCE); double riskMoney = balance * InpRiskPerTradePercent / 100.0; double entryPrice = (type == ORDER_TYPE_BUY ? last_tick.ask : last_tick.bid); double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tick_size <= 0 || tick_value <= 0) return(0.0); double slDistance = (type == ORDER_TYPE_BUY ? entryPrice - stopLossPrice : stopLossPrice - entryPrice); if(slDistance <= 0) return(0.0); double moneyPerPointPerLot = tick_value / tick_size * point; double stopPoints = slDistance / point; double volume = riskMoney / (stopPoints * moneyPerPointPerLot); double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); double lotStep= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); volume = MathFloor(volume / lotStep) * lotStep; if(volume < minLot) volume = 0.0; if(volume > maxLot) volume = maxLot; return(volume); } //+------------------------------------------------------------------+ //| Open trade: split into partial + runner | //+------------------------------------------------------------------+ void OpenTrade(ENUM_ORDER_TYPE type, const MqlRates &signalCandle) { MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return; double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double entryPrice = (type == ORDER_TYPE_BUY ? tick.ask : tick.bid); double slPrice; if(type == ORDER_TYPE_BUY) slPrice = signalCandle.low - InpStopBufferPoints * point; else slPrice = signalCandle.high + InpStopBufferPoints * point; if(slPrice <= 0) return; double totalVolume = CalculateTotalVolume(slPrice, type); if(totalVolume <= 0) { Print("Calculated total volume <= 0, skipping trade."); return; } double slDistance = (type == ORDER_TYPE_BUY ? entryPrice - slPrice : slPrice - entryPrice); // final TP at FinalTargetR double finalTPDistance = slDistance * InpFinalTargetR; double runnerTP; if(type == ORDER_TYPE_BUY) runnerTP = entryPrice + finalTPDistance; else runnerTP = entryPrice - finalTPDistance; // partial volume double partialVolume = totalVolume; double runnerVolume = 0.0; if(InpUsePartialTP && InpPartialClosePercent > 0 && InpPartialClosePercent < 100) { partialVolume = totalVolume * (InpPartialClosePercent / 100.0); runnerVolume = totalVolume - partialVolume; } else { // no partial: everything is runner partialVolume = 0.0; runnerVolume = totalVolume; } // normalized volumes double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); partialVolume = MathFloor(partialVolume / lotStep) * lotStep; runnerVolume = MathFloor(runnerVolume / lotStep) * lotStep; if(partialVolume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) partialVolume = 0.0; if(runnerVolume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) runnerVolume = 0.0; // Partial TP price at PartialCloseAtR double partialTPPrice = 0.0; if(InpUsePartialTP && partialVolume > 0 && InpPartialCloseAtR > 0) { double partialTPDist = slDistance * InpPartialCloseAtR; if(type == ORDER_TYPE_BUY) partialTPPrice = entryPrice + partialTPDist; else partialTPPrice = entryPrice - partialTPDist; } // 1) Open partial leg if(partialVolume > 0.0) { SendOrder(type, partialVolume, slPrice, partialTPPrice, PARTIAL_COMMENT); } // 2) Open runner leg if(runnerVolume > 0.0) { SendOrder(type, runnerVolume, slPrice, runnerTP, RUNNER_COMMENT); } } //+------------------------------------------------------------------+ //| Send market order | //+------------------------------------------------------------------+ bool SendOrder(ENUM_ORDER_TYPE type, double volume, double sl, double tp, string comment) { if(volume <= 0.0) return(false); MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) return(false); MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.magic = InpMagicNumber; request.symbol = _Symbol; request.volume = volume; request.type = type; request.deviation = 20; // points request.type_filling= ORDER_FILLING_FOK; request.price = (type == ORDER_TYPE_BUY ? tick.ask : tick.bid); request.sl = sl; request.tp = tp; request.comment = comment; if(!OrderSend(request, result)) { Print("OrderSend failed. Error: ", GetLastError()); return(false); } if(result.retcode != TRADE_RETCODE_DONE) { Print("OrderSend retcode: ", result.retcode); return(false); } Print("Opened ", (type == ORDER_TYPE_BUY ? "BUY" : "SELL"), " ticket #", result.order, " vol=", DoubleToString(volume,2), " SL=", DoubleToString(sl,_Digits)," TP=", DoubleToString(tp,_Digits), " comment=", comment); return(true); } //+------------------------------------------------------------------+ //| Modify SL for position | //+------------------------------------------------------------------+ bool ModifyPositionSL(ulong ticket, double newSL) { if(!PositionSelectByTicket(ticket)) return(false); string sym = PositionGetString(POSITION_SYMBOL); long type = PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); double price = PositionGetDouble(POSITION_PRICE_OPEN); double tp = PositionGetDouble(POSITION_TP); MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_SLTP; request.magic = InpMagicNumber; request.symbol = sym; request.volume = volume; request.type = (ENUM_ORDER_TYPE)type; request.price = price; request.sl = newSL; request.tp = tp; if(!OrderSend(request, result)) { Print("ModifyPositionSL OrderSend failed. Error: ", GetLastError()); return(false); } if(result.retcode != TRADE_RETCODE_DONE) { Print("ModifyPositionSL retcode: ", result.retcode); return(false); } Print("Modified SL for ticket #", ticket, " to ", DoubleToString(newSL,_Digits)); return(true); } //+------------------------------------------------------------------+ //| Close position | //+------------------------------------------------------------------+ bool ClosePosition(ulong ticket) { if(!PositionSelectByTicket(ticket)) return(false); string sym = PositionGetString(POSITION_SYMBOL); long type = PositionGetInteger(POSITION_TYPE); double volume = PositionGetDouble(POSITION_VOLUME); double price = 0.0; MqlTick tick; if(!SymbolInfoTick(sym, tick)) return(false); if(type == POSITION_TYPE_BUY) price = tick.bid; else price = tick.ask; MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.magic = InpMagicNumber; request.symbol = sym; request.volume = volume; request.type = (type == POSITION_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY); request.price = price; request.deviation = 20; request.type_filling= ORDER_FILLING_FOK; request.comment = "Close"; if(!OrderSend(request, result)) { Print("ClosePosition OrderSend failed. Error: ", GetLastError()); return(false); } if(result.retcode != TRADE_RETCODE_DONE) { Print("ClosePosition retcode: ", result.retcode); return(false); } Print("Closed ticket #", ticket); return(true); } //+------------------------------------------------------------------+