Initial commit: MAEA Expert Advisor v1.00

- Implemented complete trend-following strategy with EMA-based signals
- Added 5 visual indicator lines (3 EMAs + 2 Border Lines)
- Implemented breakthrough and pullback signal detection
- Added dynamic lot sizing (0.01/0.02 based on pullback)
- Implemented comprehensive filters:
  * Volume filter (20-bar average)
  * Spread filter (max 30 points)
  * Multiple Timeframe filter (H1 trading, D1 trend confirmation)
  * News filter (Thailand timezone)
- Added advanced risk management:
  * Take Profit in USD
  * Stop Loss at Border Lines
  * Breakeven (100 points)
  * Trailing Stop (100 points)
  * Max drawdown protection (10%)
- Created comprehensive documentation
- Connected to Gitea repository
This commit is contained in:
Kunthawat Greethong
2026-01-02 11:13:52 +07:00
commit 7c7c118c5f
4 changed files with 1658 additions and 0 deletions

775
MAEA.mq4 Normal file
View File

@@ -0,0 +1,775 @@
//+------------------------------------------------------------------+
//| MAEA.mq4 |
//| Moving Average Expert Advisor |
//| Advanced Trend-Following Strategy |
//+------------------------------------------------------------------+
#property copyright "MAEA"
#property link "https://git.moreminimore.com/kunthawat/MAEA"
#property version "1.00"
#property strict
#property indicator_chart_window
//--- Indicator buffers
#property indicator_buffers 5
#property indicator_plots 5
//--- Plot settings for EMA High
#property indicator_label1 "EMA High"
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrLightBlue
#property indicator_style1 STYLE_SOLID
#property indicator_width1 2
//--- Plot settings for EMA Medium
#property indicator_label2 "EMA Medium"
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrYellow
#property indicator_style2 STYLE_SOLID
#property indicator_width2 2
//--- Plot settings for EMA Low
#property indicator_label3 "EMA Low"
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrOrange
#property indicator_style3 STYLE_SOLID
#property indicator_width3 2
//--- Plot settings for High Border
#property indicator_label4 "High Border"
#property indicator_type4 DRAW_LINE
#property indicator_color4 clrPurple
#property indicator_style4 STYLE_DASH
#property indicator_width4 1
//--- Plot settings for Low Border
#property indicator_label5 "Low Border"
#property indicator_type5 DRAW_LINE
#property indicator_color5 clrPurple
#property indicator_style5 STYLE_DASH
#property indicator_width5 1
//+------------------------------------------------------------------+
//| Input Parameters |
//+------------------------------------------------------------------+
input string Section1 = "=== EMA Settings ===";
input int EMAPeriod = 30; // EMA Period for all lines
input string Section2 = "=== Lot Settings ===";
input double LotSizeNormal = 0.01; // Lot size without pullback
input double LotSizePullback = 0.02; // Lot size with pullback
input string Section3 = "=== Risk Management ===";
input double TakeProfitUSD = 5.0; // Take Profit target in USD
input int BreakevenPoints = 100; // Points to trigger breakeven
input int TrailingStopPoints = 100; // Trailing stop distance
input double MaxDrawdownPercent = 10.0;// Max drawdown percentage
input string Section4 = "=== Filters ===";
input int MaxSpread = 30; // Maximum allowed spread in points
input int VolumePeriod = 20; // Period for volume average
input bool UseMTFFilter = true; // Enable MTF filter
input bool UseNewsFilter = true; // Enable news filter
input string NewsAvoidHours = "14,15,20,21"; // Hours to avoid (Thailand time)
input string NewsAvoidDays = "1,5"; // Days to avoid (Monday=1, Friday=5)
input string Section5 = "=== General ===";
input int MagicNumber = 12345; // EA unique identifier
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
// Indicator buffers
double EMAHighBuffer[];
double EMAMediumBuffer[];
double EMALowBuffer[];
double HighBorderBuffer[];
double LowBorderBuffer[];
// EMA handles
int EMAHighHandle;
int EMAMediumHandle;
int EMALowHandle;
// D1 EMA handles for MTF filter
int D1_EMAHighHandle;
int D1_EMAMediumHandle;
int D1_EMALowHandle;
// State tracking
bool PullbackBuy = false;
bool PullbackSell = false;
bool BreakevenTriggered = false;
double OrderOpenPrice = 0;
double OrderStopLoss = 0;
double OrderTakeProfit = 0;
int OrderTicket = 0;
// Drawdown tracking
double AccountEquityPeak = 0;
// Previous bar values for signal detection
double PrevClose = 0;
double PrevEMAHigh = 0;
double PrevEMAMedium = 0;
double PrevEMALow = 0;
// News filter arrays
int AvoidHoursArray[];
int AvoidDaysArray[];
//+------------------------------------------------------------------+
//| Custom indicator initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
// Set indicator buffers
SetIndexBuffer(0, EMAHighBuffer);
SetIndexBuffer(1, EMAMediumBuffer);
SetIndexBuffer(2, EMALowBuffer);
SetIndexBuffer(3, HighBorderBuffer);
SetIndexBuffer(4, LowBorderBuffer);
// Set indicator labels
PlotIndexSetString(0, PLOT_LABEL, "EMA High");
PlotIndexSetString(1, PLOT_LABEL, "EMA Medium");
PlotIndexSetString(2, PLOT_LABEL, "EMA Low");
PlotIndexSetString(3, PLOT_LABEL, "High Border");
PlotIndexSetString(4, PLOT_LABEL, "Low Border");
// Initialize EMA handles
EMAHighHandle = iMA(Symbol(), PERIOD_CURRENT, EMAPeriod, 0, MODE_EMA, PRICE_HIGH);
EMAMediumHandle = iMA(Symbol(), PERIOD_CURRENT, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
EMALowHandle = iMA(Symbol(), PERIOD_CURRENT, EMAPeriod, 0, MODE_EMA, PRICE_LOW);
// Initialize D1 EMA handles for MTF filter
D1_EMAHighHandle = iMA(Symbol(), PERIOD_D1, EMAPeriod, 0, MODE_EMA, PRICE_HIGH);
D1_EMAMediumHandle = iMA(Symbol(), PERIOD_D1, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
D1_EMALowHandle = iMA(Symbol(), PERIOD_D1, EMAPeriod, 0, MODE_EMA, PRICE_LOW);
// Check handles
if(EMAHighHandle == INVALID_HANDLE || EMAMediumHandle == INVALID_HANDLE || EMALowHandle == INVALID_HANDLE)
{
Print("Error creating EMA handles");
return(INIT_FAILED);
}
if(UseMTFFilter && (D1_EMAHighHandle == INVALID_HANDLE || D1_EMAMediumHandle == INVALID_HANDLE || D1_EMALowHandle == INVALID_HANDLE))
{
Print("Error creating D1 EMA handles");
return(INIT_FAILED);
}
// Parse news filter arrays
ParseNewsFilterArrays();
// Initialize account equity peak
AccountEquityPeak = AccountEquity();
Print("MAEA initialized successfully");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
// Release indicator handles
if(EMAHighHandle != INVALID_HANDLE) IndicatorRelease(EMAHighHandle);
if(EMAMediumHandle != INVALID_HANDLE) IndicatorRelease(EMAMediumHandle);
if(EMALowHandle != INVALID_HANDLE) IndicatorRelease(EMALowHandle);
if(D1_EMAHighHandle != INVALID_HANDLE) IndicatorRelease(D1_EMAHighHandle);
if(D1_EMAMediumHandle != INVALID_HANDLE) IndicatorRelease(D1_EMAMediumHandle);
if(D1_EMALowHandle != INVALID_HANDLE) IndicatorRelease(D1_EMALowHandle);
Print("MAEA deinitialized");
}
//+------------------------------------------------------------------+
//| Custom indicator iteration function |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// Check for minimum bars
if(rates_total < EMAPeriod + 1) return(0);
// Set arrays as series
ArraySetAsSeries(EMAHighBuffer, false);
ArraySetAsSeries(EMAMediumBuffer, false);
ArraySetAsSeries(EMALowBuffer, false);
ArraySetAsSeries(HighBorderBuffer, false);
ArraySetAsSeries(LowBorderBuffer, false);
// Calculate start position
int start;
if(prev_calculated == 0)
{
start = EMAPeriod;
// Initialize buffers
for(int i = 0; i < start; i++)
{
EMAHighBuffer[i] = 0;
EMAMediumBuffer[i] = 0;
EMALowBuffer[i] = 0;
HighBorderBuffer[i] = 0;
LowBorderBuffer[i] = 0;
}
}
else
{
start = prev_calculated - 1;
}
// Calculate EMA values and border lines
for(int i = start; i < rates_total; i++)
{
// Get EMA values
double emaHigh = GetEMAValue(EMAHighHandle, i);
double emaMedium = GetEMAValue(EMAMediumHandle, i);
double emaLow = GetEMAValue(EMALowHandle, i);
// Calculate range
double range = emaMedium - emaLow;
// Calculate border lines
double highBorder = emaHigh + range;
double lowBorder = emaLow - range;
// Store values in buffers
EMAHighBuffer[i] = emaHigh;
EMAMediumBuffer[i] = emaMedium;
EMALowBuffer[i] = emaLow;
HighBorderBuffer[i] = highBorder;
LowBorderBuffer[i] = lowBorder;
}
// Only process trading logic on new bar
static datetime lastBarTime = 0;
if(time[rates_total - 1] != lastBarTime)
{
lastBarTime = time[rates_total - 1];
// Update previous bar values
int lastBar = rates_total - 1;
int prevBar = rates_total - 2;
if(prevBar >= EMAPeriod)
{
PrevClose = close[prevBar];
PrevEMAHigh = EMAHighBuffer[prevBar];
PrevEMAMedium = EMAMediumBuffer[prevBar];
PrevEMALow = EMALowBuffer[prevBar];
// Process trading logic
ProcessTradingLogic(rates_total, time, open, high, low, close, tick_volume, spread);
}
}
// Manage open orders on every tick
ManageOpenOrders();
return(rates_total);
}
//+------------------------------------------------------------------+
//| Get EMA value from handle |
//+------------------------------------------------------------------+
double GetEMAValue(int handle, int shift)
{
double value[];
ArraySetAsSeries(value, true);
CopyBuffer(handle, 0, shift, 1, value);
return(value[0]);
}
//+------------------------------------------------------------------+
//| Parse news filter arrays |
//+------------------------------------------------------------------+
void ParseNewsFilterArrays()
{
// Parse hours
string hoursStr = NewsAvoidHours;
ArrayResize(AvoidHoursArray, 0);
while(hoursStr != "")
{
int pos = StringFind(hoursStr, ",");
string hourStr;
if(pos >= 0)
{
hourStr = StringSubstr(hoursStr, 0, pos);
hoursStr = StringSubstr(hoursStr, pos + 1);
}
else
{
hourStr = hoursStr;
hoursStr = "";
}
StringTrimLeft(hourStr);
StringTrimRight(hourStr);
if(hourStr != "")
{
int hour = (int)StringToInteger(hourStr);
int size = ArraySize(AvoidHoursArray);
ArrayResize(AvoidHoursArray, size + 1);
AvoidHoursArray[size] = hour;
}
}
// Parse days
string daysStr = NewsAvoidDays;
ArrayResize(AvoidDaysArray, 0);
while(daysStr != "")
{
int pos = StringFind(daysStr, ",");
string dayStr;
if(pos >= 0)
{
dayStr = StringSubstr(daysStr, 0, pos);
daysStr = StringSubstr(daysStr, pos + 1);
}
else
{
dayStr = daysStr;
daysStr = "";
}
StringTrimLeft(dayStr);
StringTrimRight(dayStr);
if(dayStr != "")
{
int day = (int)StringToInteger(dayStr);
int size = ArraySize(AvoidDaysArray);
ArrayResize(AvoidDaysArray, size + 1);
AvoidDaysArray[size] = day;
}
}
}
//+------------------------------------------------------------------+
//| Process trading logic |
//+------------------------------------------------------------------+
void ProcessTradingLogic(const int rates_total,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const int &spread[])
{
// Check if we have an open order
if(HasOpenOrder()) return;
// Check drawdown
if(!CheckDrawdown()) return;
// Check news filter
if(UseNewsFilter && !CheckNewsFilter(time[rates_total - 1])) return;
// Check spread filter
if(spread[rates_total - 1] > MaxSpread) return;
// Check volume filter
if(!CheckVolumeFilter(tick_volume, rates_total)) return;
// Check MTF filter
if(UseMTFFilter && !CheckMTFFilter()) return;
// Get current values
int lastBar = rates_total - 1;
double currentClose = close[lastBar];
double currentEMAHigh = EMAHighBuffer[lastBar];
double currentEMALow = EMALowBuffer[lastBar];
// Detect pullback signals
DetectPullbackSignals(currentClose, currentEMAHigh, PrevEMAHigh, currentEMALow, PrevEMALow, PrevEMAMedium);
// Detect breakthrough signals
if(DetectBreakthroughBuy(currentClose, currentEMAHigh, PrevClose, PrevEMAHigh))
{
OpenBuyOrder();
}
else if(DetectBreakthroughSell(currentClose, currentEMALow, PrevClose, PrevEMALow))
{
OpenSellOrder();
}
}
//+------------------------------------------------------------------+
//| Detect pullback signals |
//+------------------------------------------------------------------+
void DetectPullbackSignals(double currentClose, double currentEMAHigh, double prevEMAHigh,
double currentEMALow, double prevEMALow, double prevEMAMedium)
{
// Buy pullback: price moved down to hit a line, then closed above it
if(PrevClose < prevEMAMedium && currentClose > currentEMALow)
{
PullbackBuy = true;
}
else if(PrevClose < prevEMALow && currentClose > currentEMALow)
{
PullbackBuy = true;
}
else if(PrevClose < prevEMAHigh && currentClose > currentEMAHigh)
{
PullbackBuy = true;
}
// Sell pullback: price moved up to hit a line, then closed below it
if(PrevClose > prevEMAMedium && currentClose < currentEMAHigh)
{
PullbackSell = true;
}
else if(PrevClose > prevEMAHigh && currentClose < currentEMAHigh)
{
PullbackSell = true;
}
else if(PrevClose > prevEMALow && currentClose < currentEMALow)
{
PullbackSell = true;
}
}
//+------------------------------------------------------------------+
//| Detect breakthrough buy signal |
//+------------------------------------------------------------------+
bool DetectBreakthroughBuy(double currentClose, double currentEMAHigh, double prevClose, double prevEMAHigh)
{
return (prevClose <= prevEMAHigh && currentClose > currentEMAHigh);
}
//+------------------------------------------------------------------+
//| Detect breakthrough sell signal |
//+------------------------------------------------------------------+
bool DetectBreakthroughSell(double currentClose, double currentEMALow, double prevClose, double prevEMALow)
{
return (prevClose >= prevEMALow && currentClose < currentEMALow);
}
//+------------------------------------------------------------------+
//| Check volume filter |
//+------------------------------------------------------------------+
bool CheckVolumeFilter(const long &tick_volume[], int rates_total)
{
if(rates_total < VolumePeriod + 1) return false;
// Calculate average volume
double avgVolume = 0;
for(int i = rates_total - VolumePeriod - 1; i < rates_total - 1; i++)
{
avgVolume += tick_volume[i];
}
avgVolume /= VolumePeriod;
// Check if current volume is above average
return (tick_volume[rates_total - 1] > avgVolume);
}
//+------------------------------------------------------------------+
//| Check MTF filter |
//+------------------------------------------------------------------+
bool CheckMTFFilter()
{
double d1Close = iClose(Symbol(), PERIOD_D1, 0);
double d1EMAHigh = GetEMAValue(D1_EMAHighHandle, 0);
double d1EMALow = GetEMAValue(D1_EMALowHandle, 0);
if(d1Close > d1EMAHigh)
{
// Only buy orders allowed
return true;
}
else if(d1Close < d1EMALow)
{
// Only sell orders allowed
return true;
}
else
{
// Price between lines - no orders allowed
return false;
}
}
//+------------------------------------------------------------------+
//| Check news filter |
//+------------------------------------------------------------------+
bool CheckNewsFilter(datetime currentTime)
{
MqlDateTime timeStruct;
TimeToStruct(currentTime, timeStruct);
// Convert to Thailand timezone (UTC+7)
// MT4 server time is usually UTC, so add 7 hours
int thailandHour = (timeStruct.hour + 7) % 24;
int thailandDay = timeStruct.day_of_week;
// Check if current hour is in avoid list
for(int i = 0; i < ArraySize(AvoidHoursArray); i++)
{
if(thailandHour == AvoidHoursArray[i])
{
return false;
}
}
// Check if current day is in avoid list
for(int i = 0; i < ArraySize(AvoidDaysArray); i++)
{
if(thailandDay == AvoidDaysArray[i])
{
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Check drawdown |
//+------------------------------------------------------------------+
bool CheckDrawdown()
{
double currentEquity = AccountEquity();
// Update peak equity
if(currentEquity > AccountEquityPeak)
{
AccountEquityPeak = currentEquity;
}
// Calculate drawdown percentage
double drawdownPercent = ((AccountEquityPeak - currentEquity) / AccountEquityPeak) * 100;
// Check if drawdown exceeds limit
if(drawdownPercent >= MaxDrawdownPercent)
{
return false;
}
return true;
}
//+------------------------------------------------------------------+
//| Open buy order |
//+------------------------------------------------------------------+
void OpenBuyOrder()
{
double lotSize = PullbackBuy ? LotSizePullback : LotSizeNormal;
double stopLoss = LowBorderBuffer[ArraySize(LowBorderBuffer) - 1];
double takeProfit = CalculateTakeProfit(lotSize, OP_BUY);
OrderTicket = OrderSend(Symbol(), OP_BUY, lotSize, Ask, 3, stopLoss, takeProfit, "MAEA Buy", MagicNumber, 0, clrBlue);
if(OrderTicket > 0)
{
OrderOpenPrice = Ask;
OrderStopLoss = stopLoss;
OrderTakeProfit = takeProfit;
BreakevenTriggered = false;
// Reset pullback flags
PullbackBuy = false;
PullbackSell = false;
Print("Buy order opened: Ticket=", OrderTicket, " Price=", Ask, " SL=", stopLoss, " TP=", takeProfit);
}
else
{
Print("Error opening buy order: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Open sell order |
//+------------------------------------------------------------------+
void OpenSellOrder()
{
double lotSize = PullbackSell ? LotSizePullback : LotSizeNormal;
double stopLoss = HighBorderBuffer[ArraySize(HighBorderBuffer) - 1];
double takeProfit = CalculateTakeProfit(lotSize, OP_SELL);
OrderTicket = OrderSend(Symbol(), OP_SELL, lotSize, Bid, 3, stopLoss, takeProfit, "MAEA Sell", MagicNumber, 0, clrRed);
if(OrderTicket > 0)
{
OrderOpenPrice = Bid;
OrderStopLoss = stopLoss;
OrderTakeProfit = takeProfit;
BreakevenTriggered = false;
// Reset pullback flags
PullbackBuy = false;
PullbackSell = false;
Print("Sell order opened: Ticket=", OrderTicket, " Price=", Bid, " SL=", stopLoss, " TP=", takeProfit);
}
else
{
Print("Error opening sell order: ", GetLastError());
}
}
//+------------------------------------------------------------------+
//| Calculate take profit in pips from USD target |
//+------------------------------------------------------------------+
double CalculateTakeProfit(double lotSize, int orderType)
{
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
double pipValue = tickValue / tickSize * Point;
double profitInPips = TakeProfitUSD / (lotSize * pipValue);
double profitInPrice = profitInPips * Point;
if(orderType == OP_BUY)
{
return Ask + profitInPrice;
}
else
{
return Bid - profitInPrice;
}
}
//+------------------------------------------------------------------+
//| Check if we have an open order |
//+------------------------------------------------------------------+
bool HasOpenOrder()
{
if(OrderTicket == 0) return false;
if(OrderSelect(OrderTicket, SELECT_BY_TICKET))
{
if(OrderCloseTime() == 0)
{
return true;
}
else
{
OrderTicket = 0;
return false;
}
}
else
{
OrderTicket = 0;
return false;
}
}
//+------------------------------------------------------------------+
//| Manage open orders |
//+------------------------------------------------------------------+
void ManageOpenOrders()
{
if(!HasOpenOrder()) return;
if(!OrderSelect(OrderTicket, SELECT_BY_TICKET)) return;
double currentPrice = OrderType() == OP_BUY ? Bid : Ask;
double currentProfit = OrderProfit();
double openPrice = OrderOpenPrice();
// Check take profit
if(currentProfit >= TakeProfitUSD)
{
CloseOrder();
return;
}
// Check stop loss
if(OrderType() == OP_BUY && currentPrice <= OrderStopLoss())
{
CloseOrder();
return;
}
else if(OrderType() == OP_SELL && currentPrice >= OrderStopLoss())
{
CloseOrder();
return;
}
// Calculate profit in points
double profitPoints = 0;
if(OrderType() == OP_BUY)
{
profitPoints = (currentPrice - openPrice) / Point;
}
else
{
profitPoints = (openPrice - currentPrice) / Point;
}
// Check breakeven
if(!BreakevenTriggered && profitPoints >= BreakevenPoints)
{
double newSL = openPrice;
if(OrderModify(OrderTicket, openPrice, newSL, OrderTakeProfit(), 0, clrNONE))
{
BreakevenTriggered = true;
OrderStopLoss = newSL;
Print("Breakeven triggered: SL moved to ", newSL);
}
}
// Check trailing stop
if(BreakevenTriggered && profitPoints >= BreakevenPoints + TrailingStopPoints)
{
double newSL;
if(OrderType() == OP_BUY)
{
newSL = currentPrice - TrailingStopPoints * Point;
if(newSL > OrderStopLoss())
{
if(OrderModify(OrderTicket, openPrice, newSL, OrderTakeProfit(), 0, clrNONE))
{
OrderStopLoss = newSL;
Print("Trailing stop: SL moved to ", newSL);
}
}
}
else
{
newSL = currentPrice + TrailingStopPoints * Point;
if(newSL < OrderStopLoss())
{
if(OrderModify(OrderTicket, openPrice, newSL, OrderTakeProfit(), 0, clrNONE))
{
OrderStopLoss = newSL;
Print("Trailing stop: SL moved to ", newSL);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Close order |
//+------------------------------------------------------------------+
void CloseOrder()
{
if(OrderSelect(OrderTicket, SELECT_BY_TICKET))
{
double closePrice = OrderType() == OP_BUY ? Bid : Ask;
if(OrderClose(OrderTicket, OrderLots(), closePrice, 3, clrNONE))
{
Print("Order closed: Ticket=", OrderTicket, " Profit=", OrderProfit());
OrderTicket = 0;
BreakevenTriggered = false;
}
else
{
Print("Error closing order: ", GetLastError());
}
}
}
//+------------------------------------------------------------------+