Files
2026-07-11 02:42:55 +08:00

598 lines
23 KiB
Plaintext

//+------------------------------------------------------------------+
//| BB_RSI_MeanReversion.mq5 |
//| Version: 1.0 |
//| Description: Mean reversion EA using Bollinger Bands + RSI |
//| on M1 with optional M15 higher-timeframe context. |
//| |
//| RISK WARNING: This EA is for educational purposes only. |
//| Live trading requires proper risk assessment, forward testing, |
//| and understanding of all risks involved in Forex trading. |
//| Past performance does not guarantee future results. |
//+------------------------------------------------------------------+
#property copyright "GENESIS Strategy B — Ares"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
//+------------------------------------------------------------------+
//| INPUT GROUPS |
//+------------------------------------------------------------------+
// --- 1. Trade Filters ---
input string Inp_TradeComment = "BB_RSI_M1"; // EA comment
input bool Inp_AllowLong = true; // Allow long trades
input bool Inp_AllowShort = true; // Allow short trades
input int Inp_MagicNumber = 20250514; // EA magic number
// --- 2. Bollinger Bands ---
input int Inp_BB_Period = 20; // BB period
input double Inp_BB_Deviation = 2.0; // BB deviation
input int Inp_BB_Shift = 0; // BB shift
input ENUM_MA_METHOD Inp_BB_MA_Method = MODE_SMA; // BB MA method
input ENUM_APPLIED_PRICE Inp_BB_Price = PRICE_CLOSE; // BB applied price
// --- 3. RSI ---
input int Inp_RSI_Period = 14; // RSI period
input double Inp_RSI_Oversold = 30.0; // RSI oversold level
input double Inp_RSI_Overbought = 70.0; // RSI overbought level
input ENUM_APPLIED_PRICE Inp_RSI_Price = PRICE_CLOSE; // RSI applied price
// --- 4. Higher Timeframe Context (M15) ---
input bool Inp_UseM15Context = true; // Use M15 context
input ENUM_TIMEFRAMES Inp_ContextTF = PERIOD_M15; // Context timeframe
input int Inp_ContextMAPeriod = 50; // Context MA period
input double Inp_ContextMATol = 0.0002; // Distance tolerance from MA
// --- 5. Entry Logic ---
input bool Inp_RequireOutsideBand = true; // Price must close outside BB
input bool Inp_RequireRSIFilter = true; // Require RSI filter
input int Inp_CandlesSinceSignal = 1; // Candle index (1=last closed)
// --- 6. Risk & Money Management ---
input double Inp_RiskPercent = 1.0; // % account risked per trade
input bool Inp_UseFixedLot = false; // Use fixed lot
input double Inp_FixedLot = 0.01; // Fixed lot size
input int Inp_StopLossPips = 20; // SL in pips
input int Inp_TakeProfitPips = 40; // TP in pips
input bool Inp_UseTrailingStop = false; // Enable trailing stop
input int Inp_TrailingStartPips = 15; // Profit pips to start trailing
input int Inp_TrailingStepPips = 5; // Trailing step in pips
// --- 7. Time & Session Filters ---
input bool Inp_UseTimeFilter = true; // Restrict trading hours
input int Inp_StartHour = 5; // Start hour (GMT)
input int Inp_StartMinute = 0; // Start minute
input int Inp_EndHour = 17; // End hour (GMT)
input int Inp_EndMinute = 0; // End minute
input bool Inp_UseNewsFilter = true; // Avoid news events
input string Inp_NewsFile = "news.txt"; // News timestamps file
// --- 8. Spread & Slippage ---
input double Inp_MaxSpreadPips = 1.0; // Max allowed spread (pips)
input int Inp_Slippage = 10; // Slippage tolerance (points)
input int Inp_MaxRetries = 3; // Max order send retries
// --- 9. Drawdown Protection ---
input bool Inp_UseDailyLossLimit = true; // Stop after daily loss
input double Inp_DailyLossPercent = 6.0; // Max daily loss %
input bool Inp_UseGlobalDDLimit = true; // Global drawdown halt
input double Inp_GlobalDDPercent = 25.0; // Max total DD %
input bool Inp_CloseAllOnDD = true; // Close all on DD breach
// --- 10. Execution ---
input bool Inp_UseOnePositionPerDir = true; // One position per direction
input int Inp_MinSecondsBetweenTrades = 30; // Cooldown seconds
//+------------------------------------------------------------------+
//| GLOBAL VARIABLES |
//+------------------------------------------------------------------+
CTrade g_Trade;
CPositionInfo g_Position;
int g_BB_Handle = INVALID_HANDLE;
int g_RSI_Handle = INVALID_HANDLE;
int g_MA_Handle = INVALID_HANDLE;
double g_PipSize = 0.0;
double g_PeakEquity = 0.0;
double g_DayStartBal = 0.0;
datetime g_LastBarTime = 0;
datetime g_LastTradeCloseTime = 0;
bool g_TradingDisabled = false;
datetime g_CurrentDayStart = 0;
datetime g_NewsTimes[];
int g_NewsCount = 0;
int g_NewsMinutes = 15; // minutes before/after to block
//+------------------------------------------------------------------+
//| OnInit |
//+------------------------------------------------------------------+
int OnInit()
{
// Determine pip size (4-digit vs 5-digit broker)
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
g_PipSize = (digits == 3 || digits == 5) ? _Point * 10.0 : _Point;
// Create indicator handles
g_BB_Handle = iBands(_Symbol, PERIOD_M1, Inp_BB_Period, Inp_BB_Shift,
Inp_BB_Deviation, Inp_BB_Price);
g_RSI_Handle = iRSI(_Symbol, PERIOD_M1, Inp_RSI_Period, Inp_RSI_Price);
g_MA_Handle = iMA(_Symbol, Inp_ContextTF, Inp_ContextMAPeriod, 0,
MODE_SMA, PRICE_CLOSE);
if(g_BB_Handle == INVALID_HANDLE ||
g_RSI_Handle == INVALID_HANDLE ||
g_MA_Handle == INVALID_HANDLE)
{
Print("ERROR: Failed to create indicator handles. EA stopping.");
return INIT_FAILED;
}
// Configure trade object
g_Trade.SetExpertMagicNumber(Inp_MagicNumber);
g_Trade.SetDeviationInPoints(Inp_Slippage);
g_Trade.SetTypeFilling(ORDER_FILLING_FOK);
// Initialise equity tracking
g_PeakEquity = AccountInfoDouble(ACCOUNT_EQUITY);
g_DayStartBal = AccountInfoDouble(ACCOUNT_BALANCE);
g_CurrentDayStart = GetDayStart(TimeCurrent());
// Load news filter file
if(Inp_UseNewsFilter) LoadNewsFile();
Print("BB_RSI_MeanReversion EA initialised. PipSize=", g_PipSize,
" | Magic=", Inp_MagicNumber);
return INIT_SUCCEEDED;
}
//+------------------------------------------------------------------+
//| OnDeinit |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_BB_Handle != INVALID_HANDLE) IndicatorRelease(g_BB_Handle);
if(g_RSI_Handle != INVALID_HANDLE) IndicatorRelease(g_RSI_Handle);
if(g_MA_Handle != INVALID_HANDLE) IndicatorRelease(g_MA_Handle);
}
//+------------------------------------------------------------------+
//| OnTick |
//+------------------------------------------------------------------+
void OnTick()
{
// 0. If globally disabled, just manage trailing on existing positions
if(g_TradingDisabled)
{
if(Inp_UseTrailingStop) ManageTrailingStop();
return;
}
// 1. Only act on new bar
if(!IsNewBar()) return;
// 2. Update peak equity
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
if(equity > g_PeakEquity) g_PeakEquity = equity;
// 3. Reset day tracking if new day
datetime today = GetDayStart(TimeCurrent());
if(today != g_CurrentDayStart)
{
g_CurrentDayStart = today;
g_DayStartBal = AccountInfoDouble(ACCOUNT_BALANCE);
Print("New trading day. Starting balance: ", g_DayStartBal);
}
// 4. Global drawdown check
if(Inp_UseGlobalDDLimit && g_PeakEquity > 0)
{
double ddPct = (g_PeakEquity - equity) / g_PeakEquity * 100.0;
if(ddPct >= Inp_GlobalDDPercent)
{
Print("GLOBAL DRAWDOWN LIMIT HIT: ", DoubleToString(ddPct, 2),
"% >= ", Inp_GlobalDDPercent, "%. Halting EA.");
if(Inp_CloseAllOnDD) CloseAllPositions();
g_TradingDisabled = true;
return;
}
}
// 5. Daily loss check
if(Inp_UseDailyLossLimit && g_DayStartBal > 0)
{
double dayLossPct = (g_DayStartBal - AccountInfoDouble(ACCOUNT_BALANCE))
/ g_DayStartBal * 100.0;
if(dayLossPct >= Inp_DailyLossPercent)
{
Print("DAILY LOSS LIMIT HIT: ", DoubleToString(dayLossPct, 2),
"% >= ", Inp_DailyLossPercent, "%. Skipping until tomorrow.");
return;
}
}
// 6. Time filter
if(Inp_UseTimeFilter && !IsTradeTime()) return;
// 7. Spread filter
double spreadPips = GetCurrentSpreadPips();
if(spreadPips > Inp_MaxSpreadPips)
{
Print("Spread too high: ", DoubleToString(spreadPips, 2),
" pips > max ", Inp_MaxSpreadPips);
return;
}
// 8. News filter
if(Inp_UseNewsFilter && IsNewsTime()) return;
// 9. Cooldown check
if((int)(TimeCurrent() - g_LastTradeCloseTime) < Inp_MinSecondsBetweenTrades)
return;
// 10. Get indicator values
double bbUpper[], bbLower[], bbMiddle[];
double rsiVal[];
ArraySetAsSeries(bbUpper, true);
ArraySetAsSeries(bbLower, true);
ArraySetAsSeries(bbMiddle, true);
ArraySetAsSeries(rsiVal, true);
int idx = Inp_CandlesSinceSignal; // 1 = last closed candle
int need = idx + 2;
if(CopyBuffer(g_BB_Handle, 1, 0, need, bbUpper) < need) return; // Upper
if(CopyBuffer(g_BB_Handle, 2, 0, need, bbLower) < need) return; // Lower
if(CopyBuffer(g_BB_Handle, 0, 0, need, bbMiddle) < need) return; // Middle
if(CopyBuffer(g_RSI_Handle, 0, 0, need, rsiVal) < need) return;
double closePrice = iClose(_Symbol, PERIOD_M1, idx);
double rsi = rsiVal[idx];
double bbUp = bbUpper[idx];
double bbLow = bbLower[idx];
// 11. M15 context
double contextMA = 0.0;
if(Inp_UseM15Context)
{
double maArr[];
ArraySetAsSeries(maArr, true);
if(CopyBuffer(g_MA_Handle, 0, 0, 2, maArr) < 2) return;
contextMA = maArr[0];
}
// 12. Signal generation
bool longSignal = false;
bool shortSignal = false;
// Long
if(Inp_AllowLong)
{
bool bbOk = !Inp_RequireOutsideBand || (closePrice < bbLow);
bool rsiOk = !Inp_RequireRSIFilter || (rsi < Inp_RSI_Oversold);
bool ctxOk = !Inp_UseM15Context || (closePrice > contextMA - Inp_ContextMATol);
longSignal = bbOk && rsiOk && ctxOk;
}
// Short
if(Inp_AllowShort)
{
bool bbOk = !Inp_RequireOutsideBand || (closePrice > bbUp);
bool rsiOk = !Inp_RequireRSIFilter || (rsi > Inp_RSI_Overbought);
bool ctxOk = !Inp_UseM15Context || (closePrice < contextMA + Inp_ContextMATol);
shortSignal = bbOk && rsiOk && ctxOk;
}
// 13. Position check
if(longSignal && Inp_UseOnePositionPerDir && HasPositionInDirection(POSITION_TYPE_BUY))
longSignal = false;
if(shortSignal && Inp_UseOnePositionPerDir && HasPositionInDirection(POSITION_TYPE_SELL))
shortSignal = false;
// 14. Execute
if(longSignal)
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl = ask - Inp_StopLossPips * g_PipSize;
double tp = ask + Inp_TakeProfitPips * g_PipSize;
sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
double lot = CalculateLot(Inp_StopLossPips);
OpenOrder(ORDER_TYPE_BUY, lot, ask, sl, tp);
}
else if(shortSignal)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl = bid + Inp_StopLossPips * g_PipSize;
double tp = bid - Inp_TakeProfitPips * g_PipSize;
sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS));
double lot = CalculateLot(Inp_StopLossPips);
OpenOrder(ORDER_TYPE_SELL, lot, bid, sl, tp);
}
// 15. Trailing stop management
if(Inp_UseTrailingStop) ManageTrailingStop();
}
//+------------------------------------------------------------------+
//| IsNewBar — returns true only once per M1 candle |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime barTime = iTime(_Symbol, PERIOD_M1, 0);
if(barTime == g_LastBarTime) return false;
g_LastBarTime = barTime;
return true;
}
//+------------------------------------------------------------------+
//| IsTradeTime — returns true if current time is in session |
//+------------------------------------------------------------------+
bool IsTradeTime()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
int nowMins = dt.hour * 60 + dt.min;
int startMin = Inp_StartHour * 60 + Inp_StartMinute;
int endMin = Inp_EndHour * 60 + Inp_EndMinute;
return (nowMins >= startMin && nowMins < endMin);
}
//+------------------------------------------------------------------+
//| GetCurrentSpreadPips |
//+------------------------------------------------------------------+
double GetCurrentSpreadPips()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
return (ask - bid) / g_PipSize;
}
//+------------------------------------------------------------------+
//| LoadNewsFile — parse news.txt (format: "YYYY.MM.DD HH:MM") |
//+------------------------------------------------------------------+
void LoadNewsFile()
{
int fh = FileOpen(Inp_NewsFile, FILE_READ | FILE_TXT | FILE_COMMON);
if(fh == INVALID_HANDLE)
{
Print("News file '", Inp_NewsFile, "' not found — news filter skipped.");
return;
}
g_NewsCount = 0;
ArrayResize(g_NewsTimes, 0);
while(!FileIsEnding(fh))
{
string line = FileReadString(fh);
StringTrimRight(line);
StringTrimLeft(line);
if(StringLen(line) < 16) continue;
datetime t = StringToTime(line);
if(t > 0)
{
ArrayResize(g_NewsTimes, g_NewsCount + 1);
g_NewsTimes[g_NewsCount++] = t;
}
}
FileClose(fh);
Print("News filter loaded: ", g_NewsCount, " events from ", Inp_NewsFile);
}
//+------------------------------------------------------------------+
//| IsNewsTime — returns true if within news window |
//+------------------------------------------------------------------+
bool IsNewsTime()
{
if(g_NewsCount == 0) return false;
datetime now = TimeCurrent();
int windowSec = g_NewsMinutes * 60;
for(int i = 0; i < g_NewsCount; i++)
{
if(MathAbs((double)(now - g_NewsTimes[i])) <= windowSec)
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| CalculateLot — risk-based or fixed |
//+------------------------------------------------------------------+
double CalculateLot(int slPips)
{
if(Inp_UseFixedLot) return NormaliseLot(Inp_FixedLot);
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskAmt = balance * Inp_RiskPercent / 100.0;
double tickVal = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
// pip value per lot in account currency
double pipValuePerLot = (g_PipSize / tickSize) * tickVal;
if(pipValuePerLot <= 0) return NormaliseLot(Inp_FixedLot);
double rawLot = riskAmt / ((double)slPips * pipValuePerLot);
return NormaliseLot(rawLot);
}
//+------------------------------------------------------------------+
//| NormaliseLot — round to lot step, clamp to min/max |
//+------------------------------------------------------------------+
double NormaliseLot(double lot)
{
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double lotMin = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double lotMax = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
lot = MathFloor(lot / lotStep) * lotStep;
lot = MathMax(lot, lotMin);
lot = MathMin(lot, lotMax);
return NormalizeDouble(lot, 2);
}
//+------------------------------------------------------------------+
//| OpenOrder — send with retry loop |
//+------------------------------------------------------------------+
void OpenOrder(ENUM_ORDER_TYPE type, double lot, double price,
double sl, double tp)
{
for(int attempt = 1; attempt <= Inp_MaxRetries; attempt++)
{
bool sent = false;
if(type == ORDER_TYPE_BUY)
sent = g_Trade.Buy(lot, _Symbol, price, sl, tp, Inp_TradeComment);
else
sent = g_Trade.Sell(lot, _Symbol, price, sl, tp, Inp_TradeComment);
if(sent)
{
ulong ticket = g_Trade.ResultOrder();
string dir = (type == ORDER_TYPE_BUY) ? "BUY" : "SELL";
Print(TimeToString(TimeCurrent()), " | ORDER OPENED | ", dir,
" | Ticket=", ticket,
" | Lot=", DoubleToString(lot, 2),
" | Price=", DoubleToString(price, _Digits),
" | SL=", DoubleToString(sl, _Digits),
" | TP=", DoubleToString(tp, _Digits));
return;
}
int err = GetLastError();
Print("Order attempt ", attempt, " failed. Error=", err,
" | Retcode=", g_Trade.ResultRetcode());
// Don't retry on hard errors
if(err == ERR_MARKET_CLOSED || err == ERR_TRADE_DISABLED) break;
Sleep(500);
}
Print("Order FAILED after ", Inp_MaxRetries, " retries.");
}
//+------------------------------------------------------------------+
//| HasPositionInDirection |
//+------------------------------------------------------------------+
bool HasPositionInDirection(ENUM_POSITION_TYPE dir)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(g_Position.SelectByIndex(i))
{
if(g_Position.Magic() == Inp_MagicNumber &&
g_Position.Symbol() == _Symbol &&
g_Position.PositionType() == dir)
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| ManageTrailingStop |
//+------------------------------------------------------------------+
void ManageTrailingStop()
{
double trailStart = Inp_TrailingStartPips * g_PipSize;
double trailStep = Inp_TrailingStepPips * g_PipSize;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!g_Position.SelectByIndex(i)) continue;
if(g_Position.Magic() != Inp_MagicNumber) continue;
if(g_Position.Symbol() != _Symbol) continue;
double sl = g_Position.StopLoss();
double openPx = g_Position.PriceOpen();
double digits = (double)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
if(g_Position.PositionType() == POSITION_TYPE_BUY)
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double profit = bid - openPx;
if(profit >= trailStart)
{
double newSL = NormalizeDouble(bid - trailStep, (int)digits);
if(newSL > sl + _Point)
g_Trade.PositionModify(g_Position.Ticket(), newSL,
g_Position.TakeProfit());
}
}
else // SELL
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double profit = openPx - ask;
if(profit >= trailStart)
{
double newSL = NormalizeDouble(ask + trailStep, (int)digits);
if(newSL < sl - _Point || sl == 0)
g_Trade.PositionModify(g_Position.Ticket(), newSL,
g_Position.TakeProfit());
}
}
}
}
//+------------------------------------------------------------------+
//| CloseAllPositions |
//+------------------------------------------------------------------+
void CloseAllPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(g_Position.SelectByIndex(i))
{
if(g_Position.Magic() == Inp_MagicNumber &&
g_Position.Symbol() == _Symbol)
{
g_Trade.PositionClose(g_Position.Ticket());
Print(TimeToString(TimeCurrent()),
" | EMERGENCY CLOSE | Ticket=", g_Position.Ticket(),
" | Reason: Drawdown limit");
g_LastTradeCloseTime = TimeCurrent();
}
}
}
}
//+------------------------------------------------------------------+
//| GetDayStart — midnight of given datetime |
//+------------------------------------------------------------------+
datetime GetDayStart(datetime t)
{
MqlDateTime dt;
TimeToStruct(t, dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
return StructToTime(dt);
}
//+------------------------------------------------------------------+
//| OnTradeTransaction — track close time for cooldown |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
if(trans.deal_type == DEAL_TYPE_BUY || trans.deal_type == DEAL_TYPE_SELL)
{
// Check if this deal closes a position
if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(trans.deal, DEAL_ENTRY)
== DEAL_ENTRY_OUT)
{
if((long)HistoryDealGetInteger(trans.deal, DEAL_MAGIC)
== Inp_MagicNumber)
{
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
Print(TimeToString(TimeCurrent()),
" | POSITION CLOSED | Deal=", trans.deal,
" | Profit=", DoubleToString(profit, 2));
g_LastTradeCloseTime = TimeCurrent();
}
}
}
}
}
//+------------------------------------------------------------------+