382 lines
12 KiB
Plaintext
382 lines
12 KiB
Plaintext
//+------------------------------------------------------------------+
|
||
//| rsi-scalping.mq5 |
|
||
//| Lab EA: EMA 9/21 + Stochastic RSI — M1 scalping rules (tutorial) |
|
||
//+------------------------------------------------------------------+
|
||
#property copyright "Lab"
|
||
#property version "1.00"
|
||
|
||
#include <Trade\Trade.mqh>
|
||
|
||
//--- inputs: indicator tuning (video defaults)
|
||
input ENUM_TIMEFRAMES InpTf = PERIOD_M1; // Chart / signal timeframe
|
||
input int InpEmaFast = 9; // EMA fast (short-term)
|
||
input int InpEmaSlow = 21; // EMA slow (trend)
|
||
input int InpRsiLen = 14; // RSI length (Stoch RSI core)
|
||
input int InpStochLen = 14; // Stochastic lookback on RSI
|
||
input int InpStochK = 4; // Stoch RSI %K smoothing
|
||
input int InpStochD = 7; // Stoch RSI %D smoothing
|
||
input double InpObLevel = 80.0; // Overbought line
|
||
input double InpOsLevel = 20.0; // Oversold line
|
||
//--- filters
|
||
input bool InpUseMidZoneFilter = true; // Skip if K,D in 40–60 (indecision)
|
||
input int InpMinBarsSinceCross = 10; // Min bars between EMA crosses
|
||
input bool InpUseHtfFilter = false; // Align with higher TF EMAs
|
||
input ENUM_TIMEFRAMES InpHtf = PERIOD_M5; // Higher timeframe
|
||
input double InpMinEmaSepPts = 0.0; // Min |EMA9-EMA21| in points (0=off)
|
||
//--- risk
|
||
input double InpLots = 0.01;
|
||
input int InpSlBufferPts = 20; // Extra SL beyond last 2-bar extreme
|
||
input double InpTpRiskMultiple = 1.75; // TP = risk * this (1.5–2.0 typical)
|
||
input bool InpExitOnEma9Break = true; // Close long if close < EMA9 (vice versa shorts)
|
||
input bool InpExitOnStochZone = true; // Close long at Stoch RSI ≥ OB; short at ≤ OS
|
||
//--- session
|
||
input ulong InpMagic = 20260412;
|
||
input int InpSlippagePts = 30;
|
||
|
||
CTrade g_trade;
|
||
|
||
int g_hEmaFast = INVALID_HANDLE;
|
||
int g_hEmaSlow = INVALID_HANDLE;
|
||
int g_hRsi = INVALID_HANDLE;
|
||
int g_hEmaFastHtf = INVALID_HANDLE;
|
||
int g_hEmaSlowHtf = INVALID_HANDLE;
|
||
|
||
double g_emaFast[];
|
||
double g_emaSlow[];
|
||
double g_rsi[];
|
||
double g_stochK[];
|
||
double g_stochD[];
|
||
double g_emaFastHtf[];
|
||
double g_emaSlowHtf[];
|
||
|
||
//+------------------------------------------------------------------+
|
||
int OnInit()
|
||
{
|
||
g_trade.SetExpertMagicNumber(InpMagic);
|
||
g_trade.SetDeviationInPoints(InpSlippagePts);
|
||
SetTradeFillingBySymbol();
|
||
|
||
g_hEmaFast = iMA(_Symbol, InpTf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||
g_hEmaSlow = iMA(_Symbol, InpTf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||
g_hRsi = iRSI(_Symbol, InpTf, InpRsiLen, PRICE_CLOSE);
|
||
if(InpUseHtfFilter)
|
||
{
|
||
g_hEmaFastHtf = iMA(_Symbol, InpHtf, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
|
||
g_hEmaSlowHtf = iMA(_Symbol, InpHtf, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
|
||
}
|
||
|
||
if(g_hEmaFast == INVALID_HANDLE || g_hEmaSlow == INVALID_HANDLE || g_hRsi == INVALID_HANDLE)
|
||
return INIT_FAILED;
|
||
if(InpUseHtfFilter && (g_hEmaFastHtf == INVALID_HANDLE || g_hEmaSlowHtf == INVALID_HANDLE))
|
||
return INIT_FAILED;
|
||
|
||
ArraySetAsSeries(g_emaFast, true);
|
||
ArraySetAsSeries(g_emaSlow, true);
|
||
ArraySetAsSeries(g_rsi, true);
|
||
ArraySetAsSeries(g_stochK, true);
|
||
ArraySetAsSeries(g_stochD, true);
|
||
ArraySetAsSeries(g_emaFastHtf, true);
|
||
ArraySetAsSeries(g_emaSlowHtf, true);
|
||
|
||
return INIT_SUCCEEDED;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnDeinit(const int reason)
|
||
{
|
||
if(g_hEmaFast != INVALID_HANDLE) IndicatorRelease(g_hEmaFast);
|
||
if(g_hEmaSlow != INVALID_HANDLE) IndicatorRelease(g_hEmaSlow);
|
||
if(g_hRsi != INVALID_HANDLE) IndicatorRelease(g_hRsi);
|
||
if(g_hEmaFastHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaFastHtf);
|
||
if(g_hEmaSlowHtf != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowHtf);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void OnTick()
|
||
{
|
||
static datetime last_bar = 0;
|
||
datetime t = iTime(_Symbol, InpTf, 0);
|
||
if(t == last_bar)
|
||
{
|
||
// Still manage exits on tick if you use break-even / trailing — here bar-based only
|
||
return;
|
||
}
|
||
last_bar = t;
|
||
|
||
const int need = 400;
|
||
if(CopyBuffer(g_hEmaFast, 0, 0, need, g_emaFast) < need) return;
|
||
if(CopyBuffer(g_hEmaSlow, 0, 0, need, g_emaSlow) < need) return;
|
||
if(CopyBuffer(g_hRsi, 0, 0, need + InpStochLen + InpStochK + InpStochD + 5, g_rsi) < need) return;
|
||
|
||
if(!ComputeStochRsi(g_rsi, InpStochLen, InpStochK, InpStochD, g_stochK, g_stochD, need))
|
||
return;
|
||
|
||
if(InpUseHtfFilter)
|
||
{
|
||
if(CopyBuffer(g_hEmaFastHtf, 0, 0, 3, g_emaFastHtf) < 3) return;
|
||
if(CopyBuffer(g_hEmaSlowHtf, 0, 0, 3, g_emaSlowHtf) < 3) return;
|
||
}
|
||
|
||
// bar 1 = last closed candle (tutorial: trade after confirmation candle closes)
|
||
const int c = 1;
|
||
const int p = 2;
|
||
|
||
if(PositionExistsForMagic())
|
||
{
|
||
ManageOpenPosition(c, p);
|
||
return;
|
||
}
|
||
|
||
if(!PassesFlatEmaFilter(c))
|
||
return;
|
||
|
||
// Long: EMA9 crosses EMA21 up at bar 1 close; Stoch RSI K,D leave oversold with bullish K/D cross
|
||
const bool bull_cross = (g_emaFast[p] < g_emaSlow[p] && g_emaFast[c] > g_emaSlow[c]);
|
||
const bool bear_cross = (g_emaFast[p] > g_emaSlow[p] && g_emaFast[c] < g_emaSlow[c]);
|
||
|
||
if(!bull_cross && !bear_cross)
|
||
return;
|
||
|
||
if(InpUseHtfFilter)
|
||
{
|
||
if(bull_cross && !(g_emaFastHtf[c] > g_emaSlowHtf[c]))
|
||
return;
|
||
if(bear_cross && !(g_emaFastHtf[c] < g_emaSlowHtf[c]))
|
||
return;
|
||
}
|
||
|
||
if(!MinBarsSincePreviousCrossOk())
|
||
return;
|
||
|
||
const bool stoch_long_ok =
|
||
(g_stochK[p] < InpOsLevel && g_stochD[p] < InpOsLevel) &&
|
||
(g_stochK[c] > g_stochD[c] && g_stochK[p] <= g_stochD[p]) &&
|
||
(g_stochK[c] > InpOsLevel * 0.9); // "left" oversold — allow ~18 if OS=20
|
||
|
||
const bool stoch_short_ok =
|
||
(g_stochK[p] > InpObLevel && g_stochD[p] > InpObLevel) &&
|
||
(g_stochK[c] < g_stochD[c] && g_stochK[p] >= g_stochD[p]) &&
|
||
(g_stochK[c] < InpObLevel * 1.05);
|
||
|
||
if(InpUseMidZoneFilter)
|
||
{
|
||
if(g_stochK[c] > 40.0 && g_stochK[c] < 60.0 && g_stochD[c] > 40.0 && g_stochD[c] < 60.0)
|
||
return;
|
||
}
|
||
|
||
if(bull_cross && stoch_long_ok)
|
||
{
|
||
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||
int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
double low12 = MathMin(iLow(_Symbol, InpTf, c), iLow(_Symbol, InpTf, p));
|
||
double sl = low12 - InpSlBufferPts * pt;
|
||
sl = NormalizeDouble(sl, dg);
|
||
if(sl >= ask - pt)
|
||
sl = ask - 10 * pt;
|
||
double risk = ask - sl;
|
||
if(risk <= 0) return;
|
||
double tp = ask + risk * InpTpRiskMultiple;
|
||
tp = NormalizeDouble(tp, dg);
|
||
g_trade.Buy(InpLots, _Symbol, ask, sl, tp, "EMA+StochRSI long");
|
||
return;
|
||
}
|
||
|
||
if(bear_cross && stoch_short_ok)
|
||
{
|
||
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||
int dg = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
|
||
double hi12 = MathMax(iHigh(_Symbol, InpTf, c), iHigh(_Symbol, InpTf, p));
|
||
double sl = hi12 + InpSlBufferPts * pt;
|
||
sl = NormalizeDouble(sl, dg);
|
||
if(sl <= bid + pt)
|
||
sl = bid + 10 * pt;
|
||
double risk = sl - bid;
|
||
if(risk <= 0) return;
|
||
double tp = bid - risk * InpTpRiskMultiple;
|
||
tp = NormalizeDouble(tp, dg);
|
||
g_trade.Sell(InpLots, _Symbol, bid, sl, tp, "EMA+StochRSI short");
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool ComputeStochRsi(const double &rsi[], const int stoch_len, const int k_len, const int d_len,
|
||
double &out_k[], double &out_d[], const int out_count)
|
||
{
|
||
int rsi_count = ArraySize(rsi);
|
||
static double raw[];
|
||
ArrayResize(raw, rsi_count);
|
||
ArraySetAsSeries(raw, true);
|
||
|
||
for(int i = 0; i < rsi_count; i++)
|
||
{
|
||
if(i + stoch_len > rsi_count)
|
||
{
|
||
raw[i] = 50.0;
|
||
continue;
|
||
}
|
||
double lo = rsi[i];
|
||
double hi = rsi[i];
|
||
for(int j = 0; j < stoch_len; j++)
|
||
{
|
||
double v = rsi[i + j];
|
||
if(v < lo) lo = v;
|
||
if(v > hi) hi = v;
|
||
}
|
||
if(hi == lo)
|
||
raw[i] = 50.0;
|
||
else
|
||
raw[i] = (rsi[i] - lo) / (hi - lo) * 100.0;
|
||
}
|
||
|
||
ArrayResize(out_k, out_count);
|
||
ArrayResize(out_d, out_count);
|
||
ArraySetAsSeries(out_k, true);
|
||
ArraySetAsSeries(out_d, true);
|
||
|
||
static double k_unsm[];
|
||
ArrayResize(k_unsm, rsi_count);
|
||
ArraySetAsSeries(k_unsm, true);
|
||
|
||
for(int i = 0; i < rsi_count; i++)
|
||
{
|
||
if(i + k_len > rsi_count)
|
||
{
|
||
k_unsm[i] = raw[i];
|
||
continue;
|
||
}
|
||
double s = 0.0;
|
||
for(int j = 0; j < k_len; j++)
|
||
s += raw[i + j];
|
||
k_unsm[i] = s / (double)k_len;
|
||
}
|
||
|
||
for(int i = 0; i < out_count; i++)
|
||
{
|
||
if(i + d_len > rsi_count)
|
||
{
|
||
out_k[i] = k_unsm[i];
|
||
out_d[i] = k_unsm[i];
|
||
continue;
|
||
}
|
||
double sk = 0.0;
|
||
for(int j = 0; j < d_len; j++)
|
||
sk += k_unsm[i + j];
|
||
out_d[i] = sk / (double)d_len;
|
||
out_k[i] = k_unsm[i];
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool PassesFlatEmaFilter(const int c)
|
||
{
|
||
if(InpMinEmaSepPts <= 0.0)
|
||
return true;
|
||
double pt = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||
double sep = MathAbs(g_emaFast[c] - g_emaSlow[c]) / pt;
|
||
return (sep >= InpMinEmaSepPts);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool MinBarsSincePreviousCrossOk()
|
||
{
|
||
if(InpMinBarsSinceCross <= 0)
|
||
return true;
|
||
// Cross under test completed on bar 1 (index c=1): between shift 2 and 1.
|
||
// Earliest earlier cross: between i+1 and i for i >= 3.
|
||
for(int i = 3; i < 300; i++)
|
||
{
|
||
const bool cu = (g_emaFast[i + 1] < g_emaSlow[i + 1] && g_emaFast[i] > g_emaSlow[i]);
|
||
const bool cd = (g_emaFast[i + 1] > g_emaSlow[i + 1] && g_emaFast[i] < g_emaSlow[i]);
|
||
if(cu || cd)
|
||
return (i - 1 >= InpMinBarsSinceCross);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool PositionExistsForMagic()
|
||
{
|
||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||
{
|
||
ulong ticket = PositionGetTicket(i);
|
||
if(ticket == 0) continue;
|
||
if(!PositionSelectByTicket(ticket)) continue;
|
||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void SetTradeFillingBySymbol()
|
||
{
|
||
long mask = SymbolInfoInteger(_Symbol, SYMBOL_FILLING_MODE);
|
||
if((mask & SYMBOL_FILLING_IOC) != 0)
|
||
g_trade.SetTypeFilling(ORDER_FILLING_IOC);
|
||
else if((mask & SYMBOL_FILLING_FOK) != 0)
|
||
g_trade.SetTypeFilling(ORDER_FILLING_FOK);
|
||
else
|
||
g_trade.SetTypeFilling(ORDER_FILLING_RETURN);
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
void ManageOpenPosition(const int c, const int p)
|
||
{
|
||
if(!PositionSelectBySymbolForMagic())
|
||
return;
|
||
ulong ticket = (ulong)PositionGetInteger(POSITION_TICKET);
|
||
long type = PositionGetInteger(POSITION_TYPE);
|
||
double k1 = g_stochK[c];
|
||
double d1 = g_stochD[c];
|
||
|
||
if(InpExitOnEma9Break)
|
||
{
|
||
double close1 = iClose(_Symbol, InpTf, c);
|
||
if(type == POSITION_TYPE_BUY && close1 < g_emaFast[c])
|
||
{
|
||
g_trade.PositionClose(ticket);
|
||
return;
|
||
}
|
||
if(type == POSITION_TYPE_SELL && close1 > g_emaFast[c])
|
||
{
|
||
g_trade.PositionClose(ticket);
|
||
return;
|
||
}
|
||
}
|
||
|
||
if(InpExitOnStochZone)
|
||
{
|
||
if(type == POSITION_TYPE_BUY && k1 >= InpObLevel && d1 >= InpObLevel * 0.95)
|
||
{
|
||
g_trade.PositionClose(ticket);
|
||
return;
|
||
}
|
||
if(type == POSITION_TYPE_SELL && k1 <= InpOsLevel && d1 <= InpOsLevel * 1.05)
|
||
{
|
||
g_trade.PositionClose(ticket);
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|
||
bool PositionSelectBySymbolForMagic()
|
||
{
|
||
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
||
{
|
||
ulong t = PositionGetTicket(i);
|
||
if(t == 0) continue;
|
||
if(!PositionSelectByTicket(t)) continue;
|
||
if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
|
||
if((ulong)PositionGetInteger(POSITION_MAGIC) == InpMagic)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
//+------------------------------------------------------------------+
|