//+------------------------------------------------------------------+
//| ReversalSignalsEA |
//| by ChatGPT (GPT-5 Thinking) |
//| Reversal candlestick pattern + volume price position signal EA |
//+------------------------------------------------------------------+
#property copyright "Open-source for education"
#property version "1.00"
#property strict
#include <Trade/Trade.mqh>
input double InpLots = 0.10; // Fixed lot size
input int InpSlAtrMult = 2; // SL = ATR multiple
input int InpTpAtrMult = 4; // TP = ATR multiple
input int InpMinRangePoints = 200; // Minimum candle range required for pattern recognition (points)
input double InpBodySmallRatio = 0.30; // Small body threshold (body/full length)
input double InpLongShadowRatio = 0.60; // Long shadow threshold (shadow/full length)
input double InpDojiBodyRatio = 0.10; // Doji threshold (body/full length)
input int InpVolLookback = 20; // Volume mean window
input double InpVolSpikeRatio = 1.5; // Volume expansion threshold (relative mean multiple)
input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Running period
input bool InpOnePosition = true; // At most 1 position at a time
input uint InpMagic = 95279527; // Magic number
// Trend filtering
input int InpFastEMA = 20; // Fast EMA
input int InpSlowEMA = 50; // Slow EMA
// Only signal prompt without trading (for debugging)
input bool InpSignalOnly = false;
CTrade trade;
// Indicator handle
int hATR = INVALID_HANDLE;
int hEMAfast = INVALID_HANDLE;
int hEMAslow = INVALID_HANDLE;
datetime lastBarTime = 0;
//--------------------------- Utility Functions -------------------------------//
struct Candle {
double open, high, low, close;
long volume;
double range, body, upper, lower;
};
bool GetCandle(int shift, Candle &c) {
MqlRates r[];
if(CopyRates(_Symbol, InpTimeframe, shift, 2, r) != 2) return false;
// r[0] is the more recent, r[1] is the earlier (in ascending order by time? In CopyRates it is from oldest to newest)
// To avoid confusion, we directly use the time point of iBarShift to grab a single one
datetime t = iTime(_Symbol, InpTimeframe, shift);
MqlRates rr[];
if(CopyRates(_Symbol, InpTimeframe, t, 1, rr) != 1) return false;
c.open = rr[0].open;
c.high = rr[0].high;
c.low = rr[0].low;
c.close = rr[0].close;
c.volume= rr[0].tick_volume;
c.range = MathMax(1e-8, c.high - c.low);
c.body = MathAbs(c.close - c.open);
c.upper = c.high - MathMax(c.open, c.close);
c.lower = MathMin(c.open, c.close) - c.low;
return true;
}
double PointsFromPrice(double priceDiff){
return priceDiff / _Point;
}
bool NewBar(){
datetime t = iTime(_Symbol, InpTimeframe, 0);
if(t != lastBarTime){
lastBarTime = t;
return true;
}
return false;
}
bool GetATR(int shift, double &atr){
double buf[];
if(CopyBuffer(hATR,0,shift,1,buf)!=1) return false;
atr = buf[0];
return true;
}
bool EMATrendUp(int shift){
double f[], s[];
if(CopyBuffer(hEMAfast,0,shift,1,f)!=1) return false;
if(CopyBuffer(hEMAslow,0,shift,1,s)!=1) return false;
return (f[0] > s[0]);
}
bool EMATrendDown(int shift){
double f[], s[];
if(CopyBuffer(hEMAfast,0,shift,1,f)!=1) return false;
if(CopyBuffer(hEMAslow,0,shift,1,s)!=1) return false;
return (f[0] < s[0]);
}
double AvgVol(int fromShift, int bars){
long vols = 0;
int got = 0;
for(int i=fromShift; i<fromShift+bars; i++){
Candle c;
if(!GetCandle(i,c)) break;
vols += c.volume;
got++;
}
if(got==0) return 0.0;
return (double)vols/got;
}
//------------------------- Pattern Recognition --------------------------------//
bool IsSmallBody(const Candle &c){
return (c.body/c.range) <= InpBodySmallRatio && PointsFromPrice(c.range) >= InpMinRangePoints;
}
bool IsDoji(const Candle &c){
return (c.body/c.range) <= InpDojiBodyRatio && PointsFromPrice(c.range) >= InpMinRangePoints;
}
bool LongLowerShadow(const Candle &c){
return (c.lower/c.range) >= InpLongShadowRatio && IsSmallBody(c);
}
bool LongUpperShadow(const Candle &c){
return (c.upper/c.range) >= InpLongShadowRatio && IsSmallBody(c);
}
bool IsBullHammer(const Candle &c){ // Hammer: long lower shadow, small body, closing near the top
if(!LongLowerShadow(c)) return false;
return (c.close > c.open); // Closing bullish is better
}
bool IsInvertedHammer(const Candle &c){ // Inverted hammer: long upper shadow, small body, commonly found at the bottom
return LongUpperShadow(c);
}
bool IsShootingStar(const Candle &c){ // Shooting star: long upper shadow, small body
return LongUpperShadow(c);
}
bool IsBullEngulf(const Candle &curr, const Candle &prev){
bool prevBear = (prev.close < prev.open);
bool currBull = (curr.close > curr.open);
bool engulf = (curr.open <= prev.close && curr.close >= prev.open);
return prevBear && currBull && engulf && PointsFromPrice(curr.range) >= InpMinRangePoints;
}
bool IsBearEngulf(const Candle &curr, const Candle &prev){
bool prevBull = (prev.close > prev.open);
bool currBear = (curr.close < curr.open);
bool engulf = (curr.open >= prev.close && curr.close <= prev.open);
return prevBull && currBear && engulf && PointsFromPrice(curr.range) >= InpMinRangePoints;
}
bool VolumeSpike(int shift){
Candle c;
if(!GetCandle(shift,c)) return false;
double avg = AvgVol(shift+1, MathMax(5, InpVolLookback));
if(avg <= 0) return false;
return (c.volume >= avg * InpVolSpikeRatio);
}
//------------------------- Trading Logic --------------------------------//
bool HasOpenPosition(){
if(!InpOnePosition) return false;
for(int i=0;i<PositionsTotal();i++){
ulong ticket = PositionGetTicket(i);
if(PositionGetInteger(POSITION_MAGIC) == (long)InpMagic &&
PositionGetString(POSITION_SYMBOL) == _Symbol){
return true;
}
}
return false;
}
void PlaceOrder(ENUM_ORDER_TYPE type, double sl_price, double tp_price, string reason){
trade.SetExpertMagicNumber(InpMagic);
trade.SetDeviationInPoints(20);
if(InpSignalOnly){
PrintFormat("[SignalOnly] %s -> %s SL=%.2f TP=%.2f", Symbol, EnumToString(type), slprice, tp_price);
return;
}
bool ok=false;
if(type==ORDER_TYPE_BUY){
ok = trade.Buy(InpLots, Symbol, SymbolInfoDouble(Symbol, SYMBOL_ASK), 0, sl_price, tp_price, reason);
}else if(type==ORDER_TYPE_SELL){
ok = trade.Sell(InpLots, Symbol, SymbolInfoDouble(Symbol, SYMBOL_BID), 0, sl_price, tp_price, reason);
}
if(!ok){
PrintFormat("Order failed: %s, err=%d", reason, GetLastError());
}else{
PrintFormat("Order placed: %s", reason);
}
}
void EvaluateSignals(){
// Use the last completed candlestick (shift=1) as a signal
Candle c1, c2;
if(!GetCandle(1,c1) || !GetCandle(2,c2)) return;
// Trend filtering
bool up = EMATrendUp(1);
bool down = EMATrendDown(1);
// Volume
bool volSpike = VolumeSpike(1);
// --- Bullish signal (bottom reversal) ---
bool bullSignal =
(IsBullHammer(c1) || IsInvertedHammer(c1) || IsBullEngulf(c1,c2) || (IsDoji(c1) && down))
|| (LongLowerShadow(c1) && volSpike);
// --- Bearish signal (top reversal) ---
bool bearSignal =
(IsShootingStar(c1) || IsBearEngulf(c1,c2) || (IsDoji(c1) && up))
|| (LongUpperShadow(c1) && volSpike);
// Combining trend position: bottom signals prioritize in down/trending; top signals prioritize in up/trending
bullSignal = bullSignal && (down || !up);
bearSignal = bearSignal && (up || !down);
if(!bullSignal && !bearSignal) return;
if(HasOpenPosition()) return;
// Risk: Set SL/TP based on ATR
double atr; if(!GetATR(1, atr)) return;
double slPoints = MathMax(InpMinRangePoints, (int)PointsFromPrice(atr*InpSlAtrMult));
double tpPoints = MathMax(InpMinRangePoints, (int)PointsFromPrice(atr*InpTpAtrMult));
double priceBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double priceAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(bullSignal){
double sl = priceBid - slPoints*_Point;
double tp = priceBid + tpPoints*_Point;
PlaceOrder(ORDER_TYPE_BUY, sl, tp, "Bullish Reversal");
}else if(bearSignal){
double sl = priceAsk + slPoints*_Point;
double tp = priceAsk - tpPoints*_Point;
PlaceOrder(ORDER_TYPE_SELL, sl, tp, "Bearish Reversal");
}
}
//--------------------------- MT5 Events --------------------------------//
int OnInit()
{
hATR = iATR(_Symbol, InpTimeframe, 14);
hEMAfast = iMA(_Symbol, InpTimeframe, InpFastEMA, 0, MODE_EMA, PRICE_CLOSE);
hEMAslow = iMA(_Symbol, InpTimeframe, InpSlowEMA, 0, MODE_EMA, PRICE_CLOSE);
if(hATR==INVALID_HANDLE || hEMAfast==INVALID_HANDLE || hEMAslow==INVALID_HANDLE){
Print("Indicator handle creation failed");
return(INIT_FAILED);
}
lastBarTime = iTime(_Symbol, InpTimeframe, 0);
return(INIT_SUCCEEDED);
}
void OnDeinit(const int reason)
{
}
void OnTick()
{
if(!NewBar()) return;
EvaluateSignals();
}