feat(maea): migrate from MQL4 to MQL5 and simplify strategy
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.
This commit is contained in:
509
MAEA.mq5
Normal file
509
MAEA.mq5
Normal file
@@ -0,0 +1,509 @@
|
||||
//+------------------------------------------------------------------+
|
||||
//| 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;
|
||||
}
|
||||
//+------------------------------------------------------------------+
|
||||
Reference in New Issue
Block a user