Files
profitable-expert-advisor/lab/EAs/rsiDivergence.mq5
T
2026-04-24 14:14:09 +02:00

385 lines
11 KiB
Plaintext

//+------------------------------------------------------------------+
//| rsiDivergence.mq5 |
//| Lab EA: RSI divergence + EMA distance filter |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
input group "=== Market ==="
input string InpSymbol = "";
input ENUM_TIMEFRAMES InpTf = PERIOD_CURRENT;
input double InpLots = 0.01;
input ulong InpMagic = 202604231;
input int InpSlippagePts = 30;
input int InpMaxPositions = 1;
input bool InpRequireFlat = true; // no new entry while any position with this magic exists
input group "=== Indicators ==="
input int InpRsiPeriod = 14;
input int InpEmaPeriod = 200;
input group "=== Swing / divergence ==="
input int InpPivotRadius = 2; // bars each side; pivot confirms after radius closes
input int InpSwingLookback = 80; // search swings within [radius+1 .. lookback]
input int InpMinPivotGap = 3; // min bars between the two swings used for a pair
input group "=== EMA distance (entry filter) ==="
input double InpMinEmaDistPtsBuy = 80.0; // buy: (EMA - close) / _Point >= this on signal bar
input double InpMinEmaDistPtsSell = 80.0; // sell: (close - EMA) / _Point >= this
input group "=== Risk ==="
input bool InpUseSLTP = true; // off = naked positions (can run years until margin stop)
input double InpSLPts = 500.0; // points; tune per symbol (_Point)
input double InpTPPts = 1000.0;
input int InpMaxHoldBars = 0; // 0=off; else close position after this many bars open (signal TF)
CTrade g_trade;
int g_hRsi = INVALID_HANDLE;
int g_hEma = INVALID_HANDLE;
datetime g_lastBar = 0;
datetime g_lastBuyPivotNew = 0;
datetime g_lastBuyPivotOld = 0;
datetime g_lastSellPivotNew = 0;
datetime g_lastSellPivotOld = 0;
string WorkSymbol()
{
if(StringLen(InpSymbol) > 0)
return InpSymbol;
return _Symbol;
}
ENUM_TIMEFRAMES WorkTf()
{
if(InpTf == PERIOD_CURRENT)
return (ENUM_TIMEFRAMES)_Period;
return InpTf;
}
void SetFilling()
{
const long fill = SymbolInfoInteger(WorkSymbol(), SYMBOL_FILLING_MODE);
if((fill & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
else if((fill & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
}
bool CopyRsiEma(const int bars, double &rsi[], double &ema[])
{
ArrayResize(rsi, bars);
ArrayResize(ema, bars);
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(ema, true);
if(CopyBuffer(g_hRsi, 0, 0, bars, rsi) < bars)
return false;
if(CopyBuffer(g_hEma, 0, 0, bars, ema) < bars)
return false;
return true;
}
bool IsSwingLow(const int s, const int r)
{
if(s < r + 1)
return false;
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
double lv = iLow(sym, tf, s);
for(int k = -r; k <= r; k++)
{
if(k == 0)
continue;
if(iLow(sym, tf, s + k) <= lv)
return false;
}
return true;
}
bool IsSwingHigh(const int s, const int r)
{
if(s < r + 1)
return false;
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
double hv = iHigh(sym, tf, s);
for(int k = -r; k <= r; k++)
{
if(k == 0)
continue;
if(iHigh(sym, tf, s + k) >= hv)
return false;
}
return true;
}
bool CollectSwingLows(int &outSwings[], const int r, const int lookback)
{
ArrayResize(outSwings, 0);
const int from = r + 1;
if(lookback <= from)
return false;
for(int s = from; s <= lookback; s++)
{
if(!IsSwingLow(s, r))
continue;
int n = ArraySize(outSwings);
ArrayResize(outSwings, n + 1);
outSwings[n] = s;
}
return ArraySize(outSwings) >= 2;
}
bool CollectSwingHighs(int &outSwings[], const int r, const int lookback)
{
ArrayResize(outSwings, 0);
const int from = r + 1;
if(lookback <= from)
return false;
for(int s = from; s <= lookback; s++)
{
if(!IsSwingHigh(s, r))
continue;
int n = ArraySize(outSwings);
ArrayResize(outSwings, n + 1);
outSwings[n] = s;
}
return ArraySize(outSwings) >= 2;
}
void SortSwingsAscending(int &sw[])
{
int n = ArraySize(sw);
for(int i = 0; i < n - 1; i++)
for(int j = i + 1; j < n; j++)
if(sw[i] > sw[j])
{
int t = sw[i];
sw[i] = sw[j];
sw[j] = t;
}
}
bool BullishDivergence(const double &rsi[], const int r, const int lookback, int &sNew, int &sOld)
{
int swings[];
if(!CollectSwingLows(swings, r, lookback))
return false;
SortSwingsAscending(swings);
const int n = ArraySize(swings);
sNew = swings[0];
sOld = swings[1];
if(sOld - sNew < InpMinPivotGap)
return false;
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
const double lowNew = iLow(sym, tf, sNew);
const double lowOld = iLow(sym, tf, sOld);
if(lowNew >= lowOld)
return false;
if(rsi[sNew] <= rsi[sOld])
return false;
return true;
}
bool BearishDivergence(const double &rsi[], const int r, const int lookback, int &sNew, int &sOld)
{
int swings[];
if(!CollectSwingHighs(swings, r, lookback))
return false;
SortSwingsAscending(swings);
sNew = swings[0];
sOld = swings[1];
if(sOld - sNew < InpMinPivotGap)
return false;
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
const double hiNew = iHigh(sym, tf, sNew);
const double hiOld = iHigh(sym, tf, sOld);
if(hiNew <= hiOld)
return false;
if(rsi[sNew] >= rsi[sOld])
return false;
return true;
}
bool EmaDistanceBuyOk(const double &ema[], const int barShift)
{
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
const double c = iClose(sym, tf, barShift);
if(c <= 0.0 || ema[barShift] <= 0.0)
return false;
const double pts = (ema[barShift] - c) / _Point;
return (pts >= InpMinEmaDistPtsBuy);
}
bool EmaDistanceSellOk(const double &ema[], const int barShift)
{
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
const double c = iClose(sym, tf, barShift);
if(c <= 0.0 || ema[barShift] <= 0.0)
return false;
const double pts = (c - ema[barShift]) / _Point;
return (pts >= InpMinEmaDistPtsSell);
}
int CountOurPositions()
{
const string sym = WorkSymbol();
int c = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != sym)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
c++;
}
return c;
}
void ManageMaxHoldBars()
{
if(InpMaxHoldBars <= 0)
return;
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) != sym)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
const datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
const int sh = iBarShift(sym, tf, openTime);
if(sh < 0)
continue;
if(sh >= InpMaxHoldBars)
g_trade.PositionClose(ticket);
}
}
void BuildSLTP(const bool isBuy, const double price, double &sl, double &tp)
{
sl = tp = 0.0;
if(!InpUseSLTP)
return;
if(isBuy)
{
sl = price - InpSLPts * _Point;
tp = price + InpTPPts * _Point;
}
else
{
sl = price + InpSLPts * _Point;
tp = price - InpTPPts * _Point;
}
}
int OnInit()
{
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
if(!SymbolSelect(sym, true))
Print("rsiDivergence: SymbolSelect note for ", sym);
g_hRsi = iRSI(sym, tf, InpRsiPeriod, PRICE_CLOSE);
g_hEma = iMA(sym, tf, InpEmaPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(g_hRsi == INVALID_HANDLE || g_hEma == INVALID_HANDLE)
return INIT_FAILED;
g_trade.SetExpertMagicNumber(InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePts);
SetFilling();
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
if(g_hRsi != INVALID_HANDLE) IndicatorRelease(g_hRsi);
if(g_hEma != INVALID_HANDLE) IndicatorRelease(g_hEma);
}
void OnTick()
{
const string sym = WorkSymbol();
const ENUM_TIMEFRAMES tf = WorkTf();
ManageMaxHoldBars();
datetime t = iTime(sym, tf, 0);
if(t == 0 || t == g_lastBar)
return;
g_lastBar = t;
const int need = InpSwingLookback + InpPivotRadius + 5;
double rsi[], ema[];
if(!CopyRsiEma(need, rsi, ema))
return;
const int openN = CountOurPositions();
if(openN >= InpMaxPositions)
return;
if(InpRequireFlat && openN > 0)
return;
const int r = MathMax(1, InpPivotRadius);
const int lb = MathMax(r + 3, InpSwingLookback);
int sNew = 0, sOld = 0;
MqlTick tick;
if(!SymbolInfoTick(sym, tick))
return;
if(BullishDivergence(rsi, r, lb, sNew, sOld) && EmaDistanceBuyOk(ema, 1))
{
const datetime tPivotNew = iTime(sym, tf, sNew);
const datetime tPivotOld = iTime(sym, tf, sOld);
if(tPivotNew == 0 || tPivotOld == 0)
return;
if(tPivotNew == g_lastBuyPivotNew && tPivotOld == g_lastBuyPivotOld)
return;
double sl, tp;
BuildSLTP(true, tick.ask, sl, tp);
if(g_trade.Buy(InpLots, sym, tick.ask, sl, tp, "RSI div+EMA buy"))
{
g_lastBuyPivotNew = tPivotNew;
g_lastBuyPivotOld = tPivotOld;
Print("Buy RSI div: swings ", sOld, "->", sNew, " pivots ", TimeToString(tPivotOld), " -> ", TimeToString(tPivotNew));
}
return;
}
if(BearishDivergence(rsi, r, lb, sNew, sOld) && EmaDistanceSellOk(ema, 1))
{
const datetime tPivotNew = iTime(sym, tf, sNew);
const datetime tPivotOld = iTime(sym, tf, sOld);
if(tPivotNew == 0 || tPivotOld == 0)
return;
if(tPivotNew == g_lastSellPivotNew && tPivotOld == g_lastSellPivotOld)
return;
double sl, tp;
BuildSLTP(false, tick.bid, sl, tp);
if(g_trade.Sell(InpLots, sym, tick.bid, sl, tp, "RSI div+EMA sell"))
{
g_lastSellPivotNew = tPivotNew;
g_lastSellPivotOld = tPivotOld;
Print("Sell RSI div: swings ", sOld, "->", sNew, " pivots ", TimeToString(tPivotOld), " -> ", TimeToString(tPivotNew));
}
}
}