TheGreatEA/StrategyFrameworkEA.mq5
2026-10-08 10:02:24 +00:00

532 lines
16 KiB
MQL5

//+------------------------------------------------------------------+
//| StrategyFrameworkEA.mq5 |
//| Reusable MT5 Expert Advisor framework |
//| Strategy logic is intentionally separated from trade management |
//+------------------------------------------------------------------+
#property strict
#property version "1.00"
#property description "Reusable EA framework. Add your strategy in CheckBuySetup/CheckSellSetup."
#include <Trade/Trade.mqh>
CTrade trade;
//=========================== INPUTS =================================
//--- General
input ulong InpMagicNumber = 26081001;
input bool InpAllowBuy = true;
input bool InpAllowSell = true;
input bool InpOneTradePerBar = true;
input int InpMaxOpenTrades = 1;
//--- Risk
input double InpRiskPercent = 1.0; // % of equity risked per trade
input double InpMaxDailyLossPercent = 3.0; // EA stops opening new trades
input double InpMaxSpreadPoints = 30.0;
//--- Stop/Target
input double InpStopLossPoints = 300.0;
input double InpTakeProfitPoints = 600.0;
//--- Trading session (server time)
input bool InpUseSessionFilter = true;
input int InpSessionStartHour = 8;
input int InpSessionStartMinute = 0;
input int InpSessionEndHour = 18;
input int InpSessionEndMinute = 0;
//--- Break-even
input bool InpUseBreakEven = false;
input double InpBreakEvenTriggerPts = 300.0;
input double InpBreakEvenLockPts = 20.0;
//--- Trailing stop
input bool InpUseTrailingStop = false;
input double InpTrailingStartPts = 400.0;
input double InpTrailingDistancePts = 250.0;
//=========================== GLOBALS ================================
datetime g_lastBarTime = 0;
string g_dayGVName = "";
//+------------------------------------------------------------------+
//| Expert initialization |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetTypeFillingBySymbol(_Symbol);
g_dayGVName = "EA_DailyStart_" +
IntegerToString((long)AccountInfoInteger(ACCOUNT_LOGIN)) +
"_" + IntegerToString((long)InpMagicNumber);
ResetDailyStartIfNeeded();
Print("StrategyFrameworkEA initialized on ", _Symbol);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick |
//+------------------------------------------------------------------+
void OnTick()
{
ResetDailyStartIfNeeded();
// Existing trades are managed even when new entries are blocked.
ManageOpenPositions();
if(!CanOpenNewTrade())
return;
if(InpOneTradePerBar && !IsNewBar())
return;
// ==============================================================
// STRATEGY MODULE
// Put your strategy rules inside these two functions.
// ==============================================================
bool buySignal = InpAllowBuy && CheckBuySetup();
bool sellSignal = InpAllowSell && CheckSellSetup();
// Prevent both directions from firing on the same tick.
if(buySignal && !sellSignal)
OpenBuy();
if(sellSignal && !buySignal)
OpenSell();
}
//+------------------------------------------------------------------+
//| BUY strategy placeholder |
//| Return TRUE only when ALL your BUY conditions are satisfied. |
//+------------------------------------------------------------------+
bool CheckBuySetup()
{
// EXAMPLE:
// if(condition1 && condition2 && condition3)
// return true;
return(false);
}
//+------------------------------------------------------------------+
//| SELL strategy placeholder |
//| Return TRUE only when ALL your SELL conditions are satisfied. |
//+------------------------------------------------------------------+
bool CheckSellSetup()
{
// EXAMPLE:
// if(condition1 && condition2 && condition3)
// return true;
return(false);
}
//+------------------------------------------------------------------+
//| Can a new trade be opened? |
//+------------------------------------------------------------------+
bool CanOpenNewTrade()
{
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED))
return(false);
if(!MQLInfoInteger(MQL_TRADE_ALLOWED))
return(false);
if(!IsWithinTradingSession())
return(false);
if(CurrentSpreadPoints() > InpMaxSpreadPoints)
return(false);
if(DailyLossLimitReached())
return(false);
if(CountOurOpenPositions() >= InpMaxOpenTrades)
return(false);
return(true);
}
//+------------------------------------------------------------------+
//| Open BUY |
//+------------------------------------------------------------------+
void OpenBuy()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(ask <= 0.0)
return;
double sl = 0.0;
double tp = 0.0;
if(InpStopLossPoints > 0)
sl = NormalizePrice(ask - InpStopLossPoints * _Point);
if(InpTakeProfitPoints > 0)
tp = NormalizePrice(ask + InpTakeProfitPoints * _Point);
double volume = CalculateRiskVolume(InpStopLossPoints);
if(volume <= 0.0)
return;
if(!trade.Buy(volume, _Symbol, 0.0, sl, tp, "Strategy BUY"))
Print("BUY failed. Retcode=", trade.ResultRetcode(),
" ", trade.ResultRetcodeDescription());
}
//+------------------------------------------------------------------+
//| Open SELL |
//+------------------------------------------------------------------+
void OpenSell()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(bid <= 0.0)
return;
double sl = 0.0;
double tp = 0.0;
if(InpStopLossPoints > 0)
sl = NormalizePrice(bid + InpStopLossPoints * _Point);
if(InpTakeProfitPoints > 0)
tp = NormalizePrice(bid - InpTakeProfitPoints * _Point);
double volume = CalculateRiskVolume(InpStopLossPoints);
if(volume <= 0.0)
return;
if(!trade.Sell(volume, _Symbol, 0.0, sl, tp, "Strategy SELL"))
Print("SELL failed. Retcode=", trade.ResultRetcode(),
" ", trade.ResultRetcodeDescription());
}
//+------------------------------------------------------------------+
//| Risk-based position sizing |
//+------------------------------------------------------------------+
double CalculateRiskVolume(double stopPoints)
{
if(stopPoints <= 0.0)
{
// If no SL is configured, use minimum volume rather than
// pretending a risk-based calculation is possible.
return(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN));
}
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskMoney = equity * (InpRiskPercent / 100.0);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0.0 || tickValue <= 0.0)
return(0.0);
double priceDistance = stopPoints * _Point;
double moneyPerLot = (priceDistance / tickSize) * tickValue;
if(moneyPerLot <= 0.0)
return(0.0);
double volume = riskMoney / moneyPerLot;
return(NormalizeVolume(volume));
}
//+------------------------------------------------------------------+
//| Manage open positions |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
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) != InpMagicNumber)
continue;
if(InpUseBreakEven)
ApplyBreakEven(ticket);
if(InpUseTrailingStop)
ApplyTrailingStop(ticket);
}
}
//+------------------------------------------------------------------+
//| Break-even |
//+------------------------------------------------------------------+
void ApplyBreakEven(ulong ticket)
{
if(!PositionSelectByTicket(ticket))
return;
long type = PositionGetInteger(POSITION_TYPE);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double sl = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double currentPrice = (type == POSITION_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double profitPoints = (type == POSITION_TYPE_BUY)
? (currentPrice - openPrice) / _Point
: (openPrice - currentPrice) / _Point;
if(profitPoints < InpBreakEvenTriggerPts)
return;
double newSL = (type == POSITION_TYPE_BUY)
? openPrice + InpBreakEvenLockPts * _Point
: openPrice - InpBreakEvenLockPts * _Point;
newSL = NormalizePrice(newSL);
bool improve = false;
if(type == POSITION_TYPE_BUY)
improve = (sl == 0.0 || newSL > sl);
else
improve = (sl == 0.0 || newSL < sl);
if(improve)
trade.PositionModify(ticket, newSL, tp);
}
//+------------------------------------------------------------------+
//| Trailing stop |
//+------------------------------------------------------------------+
void ApplyTrailingStop(ulong ticket)
{
if(!PositionSelectByTicket(ticket))
return;
long type = PositionGetInteger(POSITION_TYPE);
double currentSL = PositionGetDouble(POSITION_SL);
double tp = PositionGetDouble(POSITION_TP);
double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
double currentPrice = (type == POSITION_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_BID)
: SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double profitPoints = (type == POSITION_TYPE_BUY)
? (currentPrice - openPrice) / _Point
: (openPrice - currentPrice) / _Point;
if(profitPoints < InpTrailingStartPts)
return;
double newSL = (type == POSITION_TYPE_BUY)
? currentPrice - InpTrailingDistancePts * _Point
: currentPrice + InpTrailingDistancePts * _Point;
newSL = NormalizePrice(newSL);
bool improve = false;
if(type == POSITION_TYPE_BUY)
improve = (currentSL == 0.0 || newSL > currentSL);
else
improve = (currentSL == 0.0 || newSL < currentSL);
if(improve)
trade.PositionModify(ticket, newSL, tp);
}
//+------------------------------------------------------------------+
//| Count this EA's open positions on this symbol |
//+------------------------------------------------------------------+
int CountOurOpenPositions()
{
int count = 0;
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 &&
(ulong)PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
count++;
}
}
return(count);
}
//+------------------------------------------------------------------+
//| Current spread in points |
//+------------------------------------------------------------------+
double CurrentSpreadPoints()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(ask <= 0.0 || bid <= 0.0)
return(999999.0);
return((ask - bid) / _Point);
}
//+------------------------------------------------------------------+
//| Trading session |
//+------------------------------------------------------------------+
bool IsWithinTradingSession()
{
if(!InpUseSessionFilter)
return(true);
MqlDateTime tm;
TimeToStruct(TimeCurrent(), tm);
int nowMinutes = tm.hour * 60 + tm.min;
int startMinutes = InpSessionStartHour * 60 + InpSessionStartMinute;
int endMinutes = InpSessionEndHour * 60 + InpSessionEndMinute;
// Same start/end means session is treated as 24 hours.
if(startMinutes == endMinutes)
return(true);
// Normal session.
if(startMinutes < endMinutes)
return(nowMinutes >= startMinutes && nowMinutes < endMinutes);
// Overnight session, e.g. 22:00 -> 06:00.
return(nowMinutes >= startMinutes || nowMinutes < endMinutes);
}
//+------------------------------------------------------------------+
//| Daily loss protection |
//+------------------------------------------------------------------+
bool DailyLossLimitReached()
{
if(InpMaxDailyLossPercent <= 0.0)
return(false);
double startBalance = GetDailyStartBalance();
if(startBalance <= 0.0)
return(false);
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double lossPercent = ((startBalance - equity) / startBalance) * 100.0;
return(lossPercent >= InpMaxDailyLossPercent);
}
//+------------------------------------------------------------------+
//| Get/create today's starting balance |
//+------------------------------------------------------------------+
double GetDailyStartBalance()
{
if(g_dayGVName == "")
return(0.0);
if(!GlobalVariableCheck(g_dayGVName))
GlobalVariableSet(g_dayGVName, AccountInfoDouble(ACCOUNT_BALANCE));
return(GlobalVariableGet(g_dayGVName));
}
//+------------------------------------------------------------------+
//| Reset daily balance when server date changes |
//+------------------------------------------------------------------+
void ResetDailyStartIfNeeded()
{
if(g_dayGVName == "")
return;
string dateName = g_dayGVName + "_DATE";
string today = TimeToString(TimeCurrent(), TIME_DATE);
if(!GlobalVariableCheck(g_dayGVName) ||
!GlobalVariableCheck(dateName))
{
GlobalVariableSet(g_dayGVName, AccountInfoDouble(ACCOUNT_BALANCE));
GlobalVariableSet(dateName, (double)StringToTime(today));
return;
}
datetime storedDate = (datetime)GlobalVariableGet(dateName);
datetime todayDate = StringToTime(today);
if(storedDate != todayDate)
{
GlobalVariableSet(g_dayGVName, AccountInfoDouble(ACCOUNT_BALANCE));
GlobalVariableSet(dateName, (double)todayDate);
}
}
//+------------------------------------------------------------------+
//| Detect a new bar |
//+------------------------------------------------------------------+
bool IsNewBar()
{
datetime currentBar = iTime(_Symbol, PERIOD_CURRENT, 0);
if(currentBar == 0)
return(false);
if(currentBar != g_lastBarTime)
{
g_lastBarTime = currentBar;
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Normalize price |
//+------------------------------------------------------------------+
double NormalizePrice(double price)
{
int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
return(NormalizeDouble(price, digits));
}
//+------------------------------------------------------------------+
//| Normalize volume to broker constraints |
//+------------------------------------------------------------------+
double NormalizeVolume(double volume)
{
double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(stepVol <= 0.0)
return(0.0);
volume = MathMax(minVol, MathMin(maxVol, volume));
volume = MathFloor(volume / stepVol) * stepVol;
if(volume < minVol)
volume = minVol;
int volumeDigits = 2;
if(stepVol == 1.0) volumeDigits = 0;
else if(stepVol == 0.1) volumeDigits = 1;
else if(stepVol == 0.01) volumeDigits = 2;
else if(stepVol == 0.001)volumeDigits = 3;
return(NormalizeDouble(volume, volumeDigits));
}
//+------------------------------------------------------------------+