diff --git a/Experts/XPullbackTradeer.mq5 b/Experts/XPullbackTradeer.mq5 new file mode 100644 index 00000000..9692e8b5 --- /dev/null +++ b/Experts/XPullbackTradeer.mq5 @@ -0,0 +1,708 @@ +//+------------------------------------------------------------------+ +//| 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); +} +//+------------------------------------------------------------------+ diff --git a/Libraries/x-saherelm.common.extensions.lib.mq5 b/Libraries/x-saherelm.common.extensions.lib.mq5 index 097bcff8..cc2fcf48 100644 --- a/Libraries/x-saherelm.common.extensions.lib.mq5 +++ b/Libraries/x-saherelm.common.extensions.lib.mq5 @@ -7126,36 +7126,21 @@ double GetTrueRange( _barIndex = NormalizeInt(_barIndex, 0); // - XOHCL bar; - XOHCL pBar; + double _low = iLow(_symbol, _period, _barIndex); + double _high = iHigh(_symbol, _period, _barIndex); + double _pLow = iLow(_symbol, _period, _barIndex + 1); + double _pHigh = iHigh(_symbol, _period, _barIndex + 1); + double _pClose = iClose(_symbol, _period, _barIndex + 1); // - bool has = - bar.Init( - _symbol, - _period, - _barIndex // - ); - if (!ignorePC) - { - // - has = has && - bar.GetPreviousBar(pBar); - } - if (!has) - { - return result; - } - - // - double hlDiff = bar.high - bar.low; + double hlDiff = _high - _low; // if (!ignorePC) { // - double hpcDiff = MathAbs(bar.high - pBar.close); - double lpcDiff = MathAbs(bar.high - pBar.close); + double hpcDiff = MathAbs(_high - _pClose); + double lpcDiff = MathAbs(_high - _pClose); // result = MathMax(hlDiff, hpcDiff); @@ -7166,11 +7151,6 @@ double GetTrueRange( result = hlDiff; } - // - // Cleanup ... - bar.Clean(); - pBar.Clean(); - // return result; }