Files
MQL5Data/Documents/BKP/XPullbackTradeer.mq5
T
2025-12-30 04:31:39 +03:30

709 lines
22 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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);
}
//+------------------------------------------------------------------+