This utilizes CTrade for execution and OnTradeTransaction for asynchronous, millisecond-accurate loss detection—identical to the C# event-driven model.
Code: Select all
//+------------------------------------------------------------------+
//| ProDisciplineFramework_MT5.mq5|
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>
input string grpRisk = "--- Risk Parameters ---";
input double MaxDailyDrawdownPct = 2.0; // Max Daily Drawdown (%)
input int MaxConsecutiveLosses = 3; // Max Consecutive Losses
input int RevengeCooldownMins = 10; // Post-Loss Cooldown (Mins)
input string grpStress = "--- Stress Protocol ---";
input bool StressMode = false; // Engage Stress Protocol
input double StressExposurePct = 25.0; // Stress Sizing Exposure (%)
input double BaseRiskPct = 1.0; // Base Risk Per Trade (%)
// State variables
double startOfDayEquity;
datetime currentSessionDate;
bool isDailyLockout;
int consecutiveLosses;
datetime lastLossTime;
CTrade trade;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
currentSessionDate = GetCurrentDay();
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
isDailyLockout = false;
consecutiveLosses = 0;
InitializeUI();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
CheckSessionRollover();
EnforceDailyDrawdown();
UpdateUI();
if(CanTrade())
{
// Placeholder PA setup (e.g., engulfing)
double close1 = iClose(_Symbol, PERIOD_CURRENT, 1);
double open1 = iOpen(_Symbol, PERIOD_CURRENT, 1);
double close2 = iClose(_Symbol, PERIOD_CURRENT, 2);
double open2 = iOpen(_Symbol, PERIOD_CURRENT, 2);
bool goLong = close1 > open1 && close2 < open2;
bool goShort = close1 < open1 && close2 > open2;
if(goLong && !PositionSelect(_Symbol))
trade.Buy(GetCalculatedVolume(), _Symbol);
else if(goShort && !PositionSelect(_Symbol))
trade.Sell(GetCalculatedVolume(), _Symbol);
}
}
//+------------------------------------------------------------------+
//| Trade Transaction Event (Replaces MT4 History Polling) |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result)
{
// Catch deals exactly when they hit the history pool
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
HistorySelect(0, TimeCurrent());
ulong ticket = trans.deal;
if(HistoryDealSelect(ticket))
{
if(HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol)
{
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_COMMISSION) + HistoryDealGetDouble(ticket, DEAL_SWAP);
if(profit < 0)
{
lastLossTime = TimeCurrent();
consecutiveLosses++;
}
else if(profit > 0)
{
consecutiveLosses = 0; // Reset streak on win
}
}
}
}
}
//+------------------------------------------------------------------+
//| Core Risk Logic |
//+------------------------------------------------------------------+
void CheckSessionRollover()
{
datetime today = GetCurrentDay();
if(today != currentSessionDate)
{
currentSessionDate = today;
startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
isDailyLockout = false;
consecutiveLosses = 0;
Print("New trading session started. Risk parameters reset.");
}
}
void EnforceDailyDrawdown()
{
if(isDailyLockout) return;
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
double drawdownPct = ((startOfDayEquity - currentEquity) / startOfDayEquity) * 100.0;
if(drawdownPct >= MaxDailyDrawdownPct)
{
isDailyLockout = true;
FlattenAllPositions();
Print("CIRCUIT BREAKER: Max Daily Drawdown breached. Flattening.");
}
}
void FlattenAllPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if(PositionGetString(POSITION_SYMBOL) == _Symbol)
{
trade.PositionClose(ticket);
}
}
}
bool CanTrade()
{
if(isDailyLockout) return false;
if(consecutiveLosses >= MaxConsecutiveLosses) return false;
if(lastLossTime > 0 && (TimeCurrent() - lastLossTime) < (RevengeCooldownMins * 60)) return false;
return true;
}
double GetCalculatedVolume()
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double riskAmount = equity * (BaseRiskPct / 100.0);
if(StressMode) riskAmount *= (StressExposurePct / 100.0);
// Convert risk amount to lots (standardizing against contract size)
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double lots = riskAmount / (100 * tickValue); // Example math for generic 100 pip SL
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
return MathMax(minLot, MathRound(lots / stepLot) * stepLot);
}
datetime GetCurrentDay()
{
MqlDateTime dt;
TimeCurrent(dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
return StructToTime(dt);
}
//+------------------------------------------------------------------+
//| UI Generation |
//+------------------------------------------------------------------+
void InitializeUI()
{
ObjectCreate(0, "HUD_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
ObjectSetInteger(0, "HUD_BG", OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, "HUD_BG", OBJPROP_XDISTANCE, 10);
ObjectSetInteger(0, "HUD_BG", OBJPROP_YDISTANCE, 10);
ObjectSetInteger(0, "HUD_BG", OBJPROP_XSIZE, 200);
ObjectSetInteger(0, "HUD_BG", OBJPROP_YSIZE, 100);
ObjectSetInteger(0, "HUD_BG", OBJPROP_BGCOLOR, clrBlack);
CreateLabel("HUD_TITLE", "SYSTEM STATUS:", 20, 80, clrGray);
CreateLabel("HUD_STATUS", "ACTIVE", 120, 80, clrTeal);
CreateLabel("HUD_DD_TXT", "DD %:", 20, 60, clrGray);
CreateLabel("HUD_DD_VAL", "0.00%", 120, 60, clrSilver);
CreateLabel("HUD_STREAK_TXT", "STREAK:", 20, 40, clrGray);
CreateLabel("HUD_STREAK_VAL", "0 Loss", 120, 40, clrSilver);
CreateLabel("HUD_STRESS_TXT", "STRESS MODE:", 20, 20, clrGray);
CreateLabel("HUD_STRESS_VAL", StressMode ? "ENGAGED" : "STANDBY", 120, 20, StressMode ? clrMediumPurple : clrGray);
}
void CreateLabel(string name, string text, int x, int y, color clr)
{
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_RIGHT_LOWER);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetString(0, name, OBJPROP_FONT, "Arial");
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, 8);
}
void UpdateUI()
{
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double ddPct = MathMax(0.0, ((startOfDayEquity - equity) / startOfDayEquity) * 100.0);
ObjectSetString(0, "HUD_DD_VAL", OBJPROP_TEXT, DoubleToString(ddPct, 2) + "% / " + DoubleToString(MaxDailyDrawdownPct, 1) + "%");
ObjectSetInteger(0, "HUD_DD_VAL", OBJPROP_COLOR, ddPct > (MaxDailyDrawdownPct * 0.8) ? clrOrange : clrSilver);
ObjectSetString(0, "HUD_STREAK_VAL", OBJPROP_TEXT, IntegerToString(consecutiveLosses) + " Loss");
ObjectSetInteger(0, "HUD_STREAK_VAL", OBJPROP_COLOR, consecutiveLosses > 0 ? clrOrange : clrSilver);
if(isDailyLockout)
{
ObjectSetString(0, "HUD_STATUS", OBJPROP_TEXT, "MAX DD LOCK");
ObjectSetInteger(0, "HUD_STATUS", OBJPROP_COLOR, clrRed);
}
else if(consecutiveLosses >= MaxConsecutiveLosses)
{
ObjectSetString(0, "HUD_STATUS", OBJPROP_TEXT, "STREAK LOCK");
ObjectSetInteger(0, "HUD_STATUS", OBJPROP_COLOR, clrRed);
}
else if(lastLossTime > 0 && (TimeCurrent() - lastLossTime) < (RevengeCooldownMins * 60))
{
int remainingMins = RevengeCooldownMins - (int)((TimeCurrent() - lastLossTime) / 60);
ObjectSetString(0, "HUD_STATUS", OBJPROP_TEXT, "COOLDOWN (" + IntegerToString(remainingMins) + "m)");
ObjectSetInteger(0, "HUD_STATUS", OBJPROP_COLOR, clrOrange);
}
else
{
ObjectSetString(0, "HUD_STATUS", OBJPROP_TEXT, "ACTIVE");
ObjectSetInteger(0, "HUD_STATUS", OBJPROP_COLOR, clrTeal);
}
}