Migrate MAEA Expert Advisor from MetaTrader 4 to MetaTrader 5 platform with significant strategy simplification and architectural improvements. Key changes: - Platform: MQL4 → MQL5 - Indicators: Reduced from 5 lines (3 EMAs + 2 borders) to 2 EMAs with zone fill - Strategy: Removed pullback signals and dynamic lot sizing - Lot sizing: Changed from dynamic (0.01/0.02) to fixed 0.01 lot - Stop loss: Changed from Border Lines to opposite EMA (Buy: EMA Low, Sell: EMA High) - Signal logic: Added opposite signal close for reversal protection - New filter: Added minimum zone width filter (100 points) - Documentation: Updated implementation plans and project summary - New files: Added AGENTS.md, MAEA_Indicator.mq5, and opencode.jsonc BREAKING CHANGE: Complete rewrite from MQL4 to MQL5 with simplified strategy. Previous MAEA.mq4 and README.md deleted. New implementation requires MetaTrader 5.
509 lines
18 KiB
Plaintext
509 lines
18 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| MAEA.mq5 |
|
|
//| Moving Average EA |
|
|
//| Trend-following EA with EMA zone breakout |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "MAEA"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Indicators\Indicators.mqh>
|
|
|
|
// === EMA SETTINGS ===
|
|
input int EMAPeriod = 30;
|
|
input int EMAShift = 0;
|
|
input int MinZoneWidthPoints = 100;
|
|
|
|
// === RISK MANAGEMENT ===
|
|
input double LotSize = 0.01;
|
|
input double TakeProfitUSD = 5.0;
|
|
input int BreakevenPoints = 100;
|
|
input int TrailingStopPoints = 100;
|
|
|
|
// === FILTERS ===
|
|
input bool UseMTFFilter = true;
|
|
input bool UseNewsFilter = true;
|
|
input int MaxSpread = 30;
|
|
input int VolumePeriod = 20;
|
|
|
|
// === NEWS FILTER ===
|
|
input string NewsAvoidHours = "14,15,20,21";
|
|
input string NewsAvoidDays = "1,5";
|
|
|
|
// === RISK PROTECTION ===
|
|
input double MaxDrawdownPercent = 10.0;
|
|
|
|
// === EA SETTINGS ===
|
|
input int MagicNumber = 12345;
|
|
|
|
// === GLOBAL VARIABLES ===
|
|
CTrade trade;
|
|
int handleEMAHigh = INVALID_HANDLE;
|
|
int handleEMALow = INVALID_HANDLE;
|
|
int handleD1EMAHigh = INVALID_HANDLE;
|
|
int handleD1EMALow = INVALID_HANDLE;
|
|
|
|
double g_EMAHighBuffer[];
|
|
double g_EMALowBuffer[];
|
|
double g_D1EMAHighBuffer[];
|
|
double g_D1EMALowBuffer[];
|
|
|
|
datetime g_LastBarTime = 0;
|
|
double g_EntryPrice = 0;
|
|
double g_BreakevenPrice = 0;
|
|
bool g_BreakevenTriggered = false;
|
|
bool g_TradingAllowed = true;
|
|
double g_AccountHighBalance = 0;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert initialization function |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit() {
|
|
Print("[MAEA] Initializing EA...");
|
|
|
|
trade.SetExpertMagicNumber(MagicNumber);
|
|
|
|
handleEMAHigh = iMA(_Symbol, PERIOD_CURRENT, EMAPeriod, EMAShift, MODE_EMA, PRICE_HIGH);
|
|
handleEMALow = iMA(_Symbol, PERIOD_CURRENT, EMAPeriod, EMAShift, MODE_EMA, PRICE_LOW);
|
|
handleD1EMAHigh = iMA(_Symbol, PERIOD_D1, EMAPeriod, EMAShift, MODE_EMA, PRICE_HIGH);
|
|
handleD1EMALow = iMA(_Symbol, PERIOD_D1, EMAPeriod, EMAShift, MODE_EMA, PRICE_LOW);
|
|
|
|
if(handleEMAHigh == INVALID_HANDLE || handleEMALow == INVALID_HANDLE ||
|
|
handleD1EMAHigh == INVALID_HANDLE || handleD1EMALow == INVALID_HANDLE) {
|
|
Print("[MAEA] Failed to create EMA handles");
|
|
return INIT_FAILED;
|
|
}
|
|
|
|
Print("[MAEA] Handles - EMAHigh: ", handleEMAHigh, ", EMALow: ", handleEMALow);
|
|
|
|
ArraySetAsSeries(g_EMAHighBuffer, true);
|
|
ArraySetAsSeries(g_EMALowBuffer, true);
|
|
ArraySetAsSeries(g_D1EMAHighBuffer, true);
|
|
ArraySetAsSeries(g_D1EMALowBuffer, true);
|
|
|
|
g_AccountHighBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
g_LastBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
Print("[MAEA] Initialization successful");
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert deinitialization function |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason) {
|
|
Print("[MAEA] Deinitializing. Reason: ", reason);
|
|
|
|
if(handleEMAHigh != INVALID_HANDLE) IndicatorRelease(handleEMAHigh);
|
|
if(handleEMALow != INVALID_HANDLE) IndicatorRelease(handleEMALow);
|
|
if(handleD1EMAHigh != INVALID_HANDLE) IndicatorRelease(handleD1EMAHigh);
|
|
if(handleD1EMALow != INVALID_HANDLE) IndicatorRelease(handleD1EMALow);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Expert tick function |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick() {
|
|
datetime currentBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
if(currentBarTime != g_LastBarTime) {
|
|
g_LastBarTime = currentBarTime;
|
|
CheckDrawdown();
|
|
OnNewBar();
|
|
}
|
|
|
|
ManageOpenPosition();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| New bar processing |
|
|
//+------------------------------------------------------------------+
|
|
void OnNewBar() {
|
|
if(HasOpenPosition()) {
|
|
CheckOppositeSignal();
|
|
return;
|
|
}
|
|
|
|
CheckBreakthroughSignal();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check breakthrough signal and open position |
|
|
//+------------------------------------------------------------------+
|
|
void CheckBreakthroughSignal() {
|
|
if(!CopyIndicatorData()) return;
|
|
|
|
double prevClose = iClose(_Symbol, PERIOD_CURRENT, 1);
|
|
double currClose = iClose(_Symbol, PERIOD_CURRENT, 0);
|
|
double emaHigh = g_EMAHighBuffer[0];
|
|
double emaLow = g_EMALowBuffer[0];
|
|
double zoneWidth = MathAbs(emaHigh - emaLow);
|
|
|
|
if(zoneWidth < MinZoneWidthPoints * _Point) return;
|
|
|
|
bool prevInsideZone = (prevClose <= emaHigh && prevClose >= emaLow);
|
|
|
|
if(!prevInsideZone) return;
|
|
|
|
bool buySignal = (currClose > emaHigh);
|
|
bool sellSignal = (currClose < emaLow);
|
|
|
|
if(!buySignal && !sellSignal) return;
|
|
|
|
if(buySignal && !AllFiltersPass(true)) return;
|
|
if(sellSignal && !AllFiltersPass(false)) return;
|
|
|
|
if(buySignal) {
|
|
OpenBuyPosition();
|
|
}
|
|
else if(sellSignal) {
|
|
OpenSellPosition();
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check opposite signal and close position |
|
|
//+------------------------------------------------------------------+
|
|
void CheckOppositeSignal() {
|
|
if(!CopyIndicatorData()) return;
|
|
|
|
if(!HasOpenPosition()) return;
|
|
|
|
double currClose = iClose(_Symbol, PERIOD_CURRENT, 0);
|
|
double prevClose = iClose(_Symbol, PERIOD_CURRENT, 1);
|
|
double emaHigh = g_EMAHighBuffer[0];
|
|
double emaLow = g_EMALowBuffer[0];
|
|
|
|
bool buySignal = (prevClose <= emaHigh && prevClose >= emaLow && currClose > emaHigh);
|
|
bool sellSignal = (prevClose <= emaHigh && prevClose >= emaLow && currClose < emaLow);
|
|
|
|
if(!buySignal && !sellSignal) return;
|
|
|
|
ulong positionTicket = GetPositionTicket();
|
|
if(positionTicket == 0) return;
|
|
|
|
ENUM_POSITION_TYPE posType = GetPositionType(positionTicket);
|
|
|
|
if(posType == POSITION_TYPE_BUY && sellSignal) {
|
|
trade.PositionClose(positionTicket);
|
|
ResetPositionVariables();
|
|
Print("Buy position closed due to opposite sell signal");
|
|
}
|
|
else if(posType == POSITION_TYPE_SELL && buySignal) {
|
|
trade.PositionClose(positionTicket);
|
|
ResetPositionVariables();
|
|
Print("Sell position closed due to opposite buy signal");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage open position (TP, SL, breakeven, trailing stop) |
|
|
//+------------------------------------------------------------------+
|
|
void ManageOpenPosition() {
|
|
if(!HasOpenPosition()) return;
|
|
|
|
ulong positionTicket = GetPositionTicket();
|
|
if(positionTicket == 0) return;
|
|
|
|
if(!PositionSelectByTicket(positionTicket)) return;
|
|
|
|
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
double currentSL = PositionGetDouble(POSITION_SL);
|
|
double currentTP = PositionGetDouble(POSITION_TP);
|
|
double profit = PositionGetDouble(POSITION_PROFIT);
|
|
ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
if(currentTP > 0 && profit >= TakeProfitUSD) {
|
|
trade.PositionClose(positionTicket);
|
|
ResetPositionVariables();
|
|
Print("Position closed by Take Profit");
|
|
return;
|
|
}
|
|
|
|
double pointsProfit = 0;
|
|
double breakevenLevel = 0;
|
|
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
pointsProfit = (currentPrice - openPrice) / _Point;
|
|
breakevenLevel = openPrice + BreakevenPoints * _Point;
|
|
}
|
|
else {
|
|
pointsProfit = (openPrice - currentPrice) / _Point;
|
|
breakevenLevel = openPrice - BreakevenPoints * _Point;
|
|
}
|
|
|
|
if(!g_BreakevenTriggered && pointsProfit >= BreakevenPoints) {
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
trade.PositionModify(positionTicket, openPrice, currentTP);
|
|
}
|
|
else {
|
|
trade.PositionModify(positionTicket, openPrice, currentTP);
|
|
}
|
|
g_BreakevenTriggered = true;
|
|
Print("Breakeven triggered for position ", positionTicket);
|
|
}
|
|
|
|
if(g_BreakevenTriggered && TrailingStopPoints > 0) {
|
|
double trailDistance = TrailingStopPoints * _Point;
|
|
double newSL;
|
|
|
|
if(posType == POSITION_TYPE_BUY) {
|
|
newSL = currentPrice - trailDistance;
|
|
if(newSL > currentSL + _Point && newSL > openPrice) {
|
|
trade.PositionModify(positionTicket, newSL, currentTP);
|
|
}
|
|
}
|
|
else {
|
|
newSL = currentPrice + trailDistance;
|
|
if(newSL < currentSL - _Point && newSL < openPrice) {
|
|
trade.PositionModify(positionTicket, newSL, currentTP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open buy position |
|
|
//+------------------------------------------------------------------+
|
|
void OpenBuyPosition() {
|
|
if(!CopyIndicatorData()) return;
|
|
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double sl = g_EMALowBuffer[0];
|
|
double tp = CalculateTakeProfit(ask, sl, true);
|
|
|
|
if(!trade.Buy(LotSize, _Symbol, ask, sl, tp, "MAEA Buy")) {
|
|
int err = GetLastError();
|
|
Print("Buy order failed. Error: ", err);
|
|
}
|
|
else {
|
|
g_EntryPrice = ask;
|
|
g_BreakevenTriggered = false;
|
|
Print("Buy position opened at ", ask);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open sell position |
|
|
//+------------------------------------------------------------------+
|
|
void OpenSellPosition() {
|
|
if(!CopyIndicatorData()) return;
|
|
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double sl = g_EMAHighBuffer[0];
|
|
double tp = CalculateTakeProfit(bid, sl, false);
|
|
|
|
if(!trade.Sell(LotSize, _Symbol, bid, sl, tp, "MAEA Sell")) {
|
|
int err = GetLastError();
|
|
Print("Sell order failed. Error: ", err);
|
|
}
|
|
else {
|
|
g_EntryPrice = bid;
|
|
g_BreakevenTriggered = false;
|
|
Print("Sell position opened at ", bid);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate take profit in price |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateTakeProfit(double entryPrice, double sl, bool isBuy) {
|
|
double pointValue = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
|
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
|
|
double profitPoints = TakeProfitUSD / (tickValue * LotSize / tickSize);
|
|
double profitPrice = profitPoints * pointValue;
|
|
|
|
if(isBuy) {
|
|
return entryPrice + profitPrice;
|
|
}
|
|
else {
|
|
return entryPrice - profitPrice;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Copy indicator data |
|
|
//+------------------------------------------------------------------+
|
|
bool CopyIndicatorData() {
|
|
if(CopyBuffer(handleEMAHigh, 0, 0, 10, g_EMAHighBuffer) <= 0) return false;
|
|
if(CopyBuffer(handleEMALow, 0, 0, 10, g_EMALowBuffer) <= 0) return false;
|
|
if(UseMTFFilter) {
|
|
if(CopyBuffer(handleD1EMAHigh, 0, 0, 2, g_D1EMAHighBuffer) <= 0) return false;
|
|
if(CopyBuffer(handleD1EMALow, 0, 0, 2, g_D1EMALowBuffer) <= 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check all filters |
|
|
//+------------------------------------------------------------------+
|
|
bool AllFiltersPass(bool isBuyDirection) {
|
|
if(!CheckVolumeFilter()) return false;
|
|
if(!CheckSpreadFilter()) return false;
|
|
if(!CheckDrawdownAllowed()) return false;
|
|
if(UseMTFFilter && !CheckMTFFilter(isBuyDirection)) return false;
|
|
if(UseNewsFilter && !CheckNewsFilter()) return false;
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Volume filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckVolumeFilter() {
|
|
long currentVolume = iVolume(_Symbol, PERIOD_CURRENT, 0);
|
|
long avgVolume = 0;
|
|
|
|
for(int i = 1; i <= VolumePeriod; i++) {
|
|
avgVolume += iVolume(_Symbol, PERIOD_CURRENT, i);
|
|
}
|
|
avgVolume = avgVolume / VolumePeriod;
|
|
|
|
return (currentVolume > avgVolume);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Spread filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckSpreadFilter() {
|
|
long spread;
|
|
SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spread);
|
|
return (spread <= MaxSpread);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| MTF filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckMTFFilter(bool isBuyDirection) {
|
|
double d1Close = iClose(_Symbol, PERIOD_D1, 0);
|
|
double d1EMAHigh = g_D1EMAHighBuffer[0];
|
|
double d1EMALow = g_D1EMALowBuffer[0];
|
|
|
|
if(d1Close > d1EMAHigh) {
|
|
return isBuyDirection;
|
|
}
|
|
else if(d1Close < d1EMALow) {
|
|
return !isBuyDirection;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| News filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckNewsFilter() {
|
|
datetime gmtTime = TimeGMT();
|
|
datetime thaiTime = gmtTime + 25200;
|
|
MqlDateTime timeStruct;
|
|
TimeToStruct(thaiTime, timeStruct);
|
|
|
|
string hoursArr[];
|
|
StringSplit(NewsAvoidHours, ',', hoursArr);
|
|
for(int i = 0; i < ArraySize(hoursArr); i++) {
|
|
string h = hoursArr[i];
|
|
StringTrimRight(h);
|
|
StringTrimLeft(h);
|
|
if(h == IntegerToString(timeStruct.hour)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
string daysArr[];
|
|
StringSplit(NewsAvoidDays, ',', daysArr);
|
|
for(int i = 0; i < ArraySize(daysArr); i++) {
|
|
string d = daysArr[i];
|
|
StringTrimRight(d);
|
|
StringTrimLeft(d);
|
|
if(d == IntegerToString(timeStruct.day_of_week)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check drawdown |
|
|
//+------------------------------------------------------------------+
|
|
void CheckDrawdown() {
|
|
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
if(currentBalance > g_AccountHighBalance) {
|
|
g_AccountHighBalance = currentBalance;
|
|
}
|
|
|
|
if(currentBalance > 0 && g_AccountHighBalance > 0) {
|
|
double drawdownPercent = ((g_AccountHighBalance - currentBalance) / g_AccountHighBalance) * 100;
|
|
|
|
if(drawdownPercent >= MaxDrawdownPercent) {
|
|
g_TradingAllowed = false;
|
|
Print("Drawdown limit reached. Trading stopped.");
|
|
}
|
|
else {
|
|
g_TradingAllowed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if trading is allowed based on drawdown |
|
|
//+------------------------------------------------------------------+
|
|
bool CheckDrawdownAllowed() {
|
|
return g_TradingAllowed;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if has open position |
|
|
//+------------------------------------------------------------------+
|
|
bool HasOpenPosition() {
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(PositionGetSymbol(i) == _Symbol) {
|
|
ulong magic = PositionGetInteger(POSITION_MAGIC);
|
|
if(magic == MagicNumber) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position ticket |
|
|
//+------------------------------------------------------------------+
|
|
ulong GetPositionTicket() {
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--) {
|
|
if(PositionGetSymbol(i) == _Symbol) {
|
|
ulong ticket = PositionGetInteger(POSITION_TICKET);
|
|
ulong magic = PositionGetInteger(POSITION_MAGIC);
|
|
if(magic == MagicNumber) {
|
|
return ticket;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get position type |
|
|
//+------------------------------------------------------------------+
|
|
ENUM_POSITION_TYPE GetPositionType(ulong ticket) {
|
|
if(PositionSelectByTicket(ticket)) {
|
|
return (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
}
|
|
return POSITION_TYPE_BUY;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset position variables |
|
|
//+------------------------------------------------------------------+
|
|
void ResetPositionVariables() {
|
|
g_EntryPrice = 0;
|
|
g_BreakevenTriggered = false;
|
|
}
|
|
//+------------------------------------------------------------------+ |