Advertisement IC Markets

Revenge Trading Is the #1 Account Killer

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

2. MT5 / MQL5: Pro Risk Engine

MQL5's history architecture requires evaluating "Deals" rather than "Orders." We strictly filter for DEAL_ENTRY_OUT to ensure we are only counting closed trades, preventing partial fills or deposits/withdrawals from distorting your consecutive loss streak.

Code: Select all

//+------------------------------------------------------------------+
//|                                       RevengeController_Pro.mq5  |
//+------------------------------------------------------------------+
#property strict

// ==========================================
// ⚙️ INPUTS: RISK & TILT MECHANICS
// ==========================================
input group "--- Risk Limits ---";
input double   SoftStopPct        = 2.0;       // Daily Soft Stop (%)
input int      MaxConsecLosses    = 3;         // Max Consecutive Losses Limit

input group "--- Tilt & Cooldown ---";
input int      BaseCooldownMin    = 5;         // Base Cooldown (Min)
input int      EscalationMult     = 3;         // Cooldown Escalation Multiplier

input group "--- Trading Session ---";
input int      StartHour          = 9;         // Session Start Hour (Broker Time)
input int      StartMinute        = 30;        // Session Start Minute
input int      EndHour            = 16;        // Session End Hour
input int      EndMinute          = 0;         // Session End Minute

input ulong    MagicNumber        = 12345;     // EA Magic Number (0 for Manual Trades)

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Calculate Daily PnL
    double dailyPnLPct = GetDailyPnLPct();
    bool hitSoftStop = (dailyPnLPct <= -SoftStopPct);

    // 2. Fetch Tilt State
    int consecLosses = 0;
    datetime lastLossTime = 0;
    GetTiltMetrics(consecLosses, lastLossTime);
    bool hitConsecLossLimit = (consecLosses >= MaxConsecLosses);

    // 3. Calculate Escalating Cooldown
    int currentCooldownSecs = 0;
    int secondsSinceLoss = (int)(TimeCurrent() - lastLossTime);
    bool inCooldown = false;
    
    if (consecLosses > 0)
    {
        // Math: Base * (Multiplier ^ (Losses - 1))
        double multPower = MathPow(EscalationMult, MathMax(0, consecLosses - 1));
        currentCooldownSecs = (int)(BaseCooldownMin * multPower * 60);
        if (secondsSinceLoss < currentCooldownSecs) inCooldown = true;
    }

    // 4. Session Fencing
    bool inSession = IsInSession();

    // 5. Master Failsafe
    bool isLockedOut = (hitSoftStop || hitConsecLossLimit);
    
    // 6. Update HUD
    UpdateHUD(isLockedOut, inCooldown, inSession, dailyPnLPct, consecLosses, currentCooldownSecs - secondsSinceLoss);

    // 7. Execution Block
    if (isLockedOut || inCooldown || !inSession) 
    {
        return; // Engine locked. Abort tick.
    }

    // ==========================================
    // 📉 YOUR A+ SETUP LOGIC GOES HERE
    // ==========================================
}

//+------------------------------------------------------------------+
//| Engine Logic Functions                                           |
//+------------------------------------------------------------------+
double GetDailyPnLPct()
{
    double todayClosedProfit = 0.0;
    datetime startOfDay = iTime(_Symbol, PERIOD_D1, 0);

    HistorySelect(startOfDay, TimeCurrent());
    int dealsTotal = HistoryDealsTotal();

    for(int i = 0; i < dealsTotal; i++)
    {
        ulong ticket = HistoryDealGetTicket(i);
        if(ticket > 0)
        {
            // Only count closing deals to calculate realized profit
            long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
            if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT)
            {
                todayClosedProfit += HistoryDealGetDouble(ticket, DEAL_PROFIT) + 
                                     HistoryDealGetDouble(ticket, DEAL_COMMISSION) + 
                                     HistoryDealGetDouble(ticket, DEAL_SWAP);
            }
        }
    }

    double startOfDayBalance = AccountInfoDouble(ACCOUNT_BALANCE) - todayClosedProfit;
    return ((AccountInfoDouble(ACCOUNT_EQUITY) - startOfDayBalance) / startOfDayBalance) * 100.0;
}

void GetTiltMetrics(int &consecLossesCount, datetime &lastTime)
{
    consecLossesCount = 0;
    lastTime = 0;

    HistorySelect(0, TimeCurrent());
    int dealsTotal = HistoryDealsTotal();

    // Loop backwards from the most recent deal
    for(int i = dealsTotal - 1; i >= 0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        if(ticket > 0)
        {
            if(HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && 
              (MagicNumber == 0 || HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber))
            {
                long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
                
                // We only care about exits
                if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT)
                {
                    double netProfit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + 
                                       HistoryDealGetDouble(ticket, DEAL_COMMISSION) + 
                                       HistoryDealGetDouble(ticket, DEAL_SWAP);
                                       
                    if(netProfit < 0)
                    {
                        consecLossesCount++;
                        if(lastTime == 0) lastTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
                    }
                    else if(netProfit > 0)
                    {
                        break; // Streak broken by a win
                    }
                }
            }
        }
    }
}

bool IsInSession()
{
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    int currentMins = dt.hour * 60 + dt.min;
    int startMins = StartHour * 60 + StartMinute;
    int endMins = EndHour * 60 + EndMinute;
    return (currentMins >= startMins && currentMins <= endMins);
}

void UpdateHUD(bool lockedOut, bool cooldown, bool inSession, double pnl, int consecLosses, int secsRemaining)
{
    string status = "ARMED & CLEAR";
    if (lockedOut) status = "FATAL LOCKOUT";
    else if (cooldown) status = StringFormat("COOLDOWN (%d min left)", (secsRemaining / 60) + 1);
    else if (!inSession) status = "OUT OF SESSION";

    string hud = StringFormat(
        "====== PRO RISK ENGINE ======\n" +
        "Status: %s\n" +
        "Daily PnL: %.2f%% / Limit: -%.2f%%\n" +
        "Consec Losses: %d / Limit: %d", 
        status, pnl, SoftStopPct, consecLosses, MaxConsecLosses
    );
    Comment(hud);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

Key Upgrades for MQL Architecture

True Equity Drawdown Calculations: Pine Script natively includes open PnL in strategy.equity. To mirror this accurately in MetaTrader without complex order looping, we calculate the startOfDayBalance by subtracting today's closed profit from the current balance. We then use AccountEquity() (which natively tracks all floating positions) against that starting balance. This perfectly mimics prop-firm daily limit rules.

Stateless Tracking (GetTiltMetrics): Rather than keeping a consecLosses += 1 counter in memory that wipes if the EA restarts, the code scans your trade history backwards in real-time. It identifies the last exit, checks if it was a loss, and continues counting backward until it hits a winner. Your exact penalty state is mathematically reconstructed on every tick.

Broker-Time Fencing: Instead of string formatting which is fragile in MQL, the session window (IsInSession()) uses simple integers (e.g., Start 09:30, End 16:00). Remember that TimeCurrent() relies on broker server time, not your local PC time, so set these inputs to match your broker's timezone mapping.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

This "Apex" tier translation moves beyond basic terminal tracking. To achieve true institutional reliability in MetaTrader, this version utilizes Terminal Global Variables to track your High-Water Mark (HWM). This ensures that if MetaTrader crashes, reboots, or loses connection, your HWM is not wiped from memory, preventing you from accidentally bypassing your trailing drawdown limit.

It also fully implements the Microstructure (Liquidity Sweep) execution, dynamic ATR-based fractional position sizing, and the Time-in-Trade abort logic.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

1. MT4 / MQL4: Apex Risk Engine & Microstructure

Code: Select all

//+------------------------------------------------------------------+
//|                                        RevengeController_Apex.mq4|
//+------------------------------------------------------------------+
#property strict

// ==========================================
// ⚙️ INPUTS: PROP FIRM LIMITS & TILT
// ==========================================
input string   ___FirmLimits___   = "--- Prop Firm Constraints ---";
input double   DailyHardStopPct   = 4.0;       // Daily Hard Stop (%)
input double   TrailStopPct       = 5.0;       // Trailing Max DD (%)
input double   MaxRiskPerTrade    = 0.5;       // Max Risk Per Trade (%)

input string   ___TiltMechanics___= "--- Tilt & Cooldown ---";
input int      BaseCooldownMin    = 5;         // Base Cooldown (Min)
input int      EscalationMult     = 3;         // Loss Multiplier
input int      MaxConsecLosses    = 3;         // Max Consecutive Losses
input int      MaxBarsInTrade     = 10;        // Time-In-Trade Abort (Bars)

input string   ___Session___      = "--- Execution Window ---";
input int      StartHour          = 9, StartMinute = 30;
input int      EndHour            = 16, EndMinute  = 0;
input int      MagicNumber        = 99999;

// Global State
string hwmGlobalName;

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Unique Global Variable for today's HWM (survives EA restarts/crashes)
    hwmGlobalName = StringFormat("HWM_%d_%d", AccountNumber(), DayOfYear());
    if(!GlobalVariableCheck(hwmGlobalName))
        GlobalVariableSet(hwmGlobalName, AccountEquity());
        
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Manage High-Water Mark
    double currentEquity = AccountEquity();
    double hwm = GlobalVariableGet(hwmGlobalName);
    if(currentEquity > hwm)
    {
        hwm = currentEquity;
        GlobalVariableSet(hwmGlobalName, hwm);
    }

    // 2. State & Drawdown Math
    double startOfDayBalance = GetStartOfDayBalance();
    double dailyPnLPct = ((currentEquity - startOfDayBalance) / startOfDayBalance) * 100.0;
    double trailDDPct  = ((currentEquity - hwm) / hwm) * 100.0;

    int consecLosses = 0;
    datetime lastLossTime = 0;
    GetTiltMetrics(consecLosses, lastLossTime);

    // 3. Failsafe Triggers
    bool hitDailyStop = (dailyPnLPct <= -DailyHardStopPct);
    bool hitTrailStop = (trailDDPct <= -TrailStopPct);
    bool hitConsecLoss = (consecLosses >= MaxConsecLosses);
    bool inSession = IsInSession();

    int currentCooldownSecs = 0;
    int secondsSinceLoss = (int)(TimeCurrent() - lastLossTime);
    bool inCooldown = false;
    
    if (consecLosses > 0)
    {
        double multPower = MathPow(EscalationMult, MathMax(0, consecLosses - 1));
        currentCooldownSecs = (int)(BaseCooldownMin * multPower * 60);
        if (secondsSinceLoss < currentCooldownSecs) inCooldown = true;
    }

    bool isLockedOut = (hitDailyStop || hitTrailStop || hitConsecLoss);
    
    UpdateHUD(isLockedOut, inCooldown, inSession, dailyPnLPct, trailDDPct, consecLosses, currentCooldownSecs - secondsSinceLoss);

    // 4. Trade Management (Time-in-Trade Abort)
    ManageOpenTrades();

    // 5. Execution Block
    if (isLockedOut || inCooldown || !inSession || OpenPositionsCount() > 0) return;

    // ==========================================
    // 📉 LIQUIDITY SWEEP MICROSTRUCTURE
    // ==========================================
    // Check structure using Bar 1 (closed candle) vs past 15 bars (Bar 2 to 16)
    double swingLow  = Low[iLowest(Symbol(), 0, MODE_LOW, 15, 2)];
    double swingHigh = High[iHighest(Symbol(), 0, MODE_HIGH, 15, 2)];

    bool bullishSweep = (Low[1] < swingLow && Close[1] > swingLow && Close[1] > Open[1]);
    bool bearishSweep = (High[1] > swingHigh && Close[1] < swingHigh && Close[1] < Open[1]);

    if(bullishSweep)
    {
        double atr = iATR(Symbol(), 0, 14, 1);
        double sl = Low[1] - atr;
        double tp = Ask + ((Ask - sl) * 2.0); // 2R Target
        double lots = CalculatePositionSize(sl);
        if(lots > 0)
            int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, 3, sl, tp, "Sweep Long", MagicNumber, 0, clrGreen);
    }
    else if(bearishSweep)
    {
        double atr = iATR(Symbol(), 0, 14, 1);
        double sl = High[1] + atr;
        double tp = Bid - ((sl - Bid) * 2.0); // 2R Target
        double lots = CalculatePositionSize(sl);
        if(lots > 0)
            int ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, 3, sl, tp, "Sweep Short", MagicNumber, 0, clrRed);
    }
}

//+------------------------------------------------------------------+
//| Engine Logic Functions                                           |
//+------------------------------------------------------------------+
double GetStartOfDayBalance()
{
    double todayClosedProfit = 0.0;
    datetime startOfDay = iTime(Symbol(), PERIOD_D1, 0);

    for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderCloseTime() >= startOfDay)
            todayClosedProfit += (OrderProfit() + OrderSwap() + OrderCommission());
    }
    return AccountBalance() - todayClosedProfit;
}

void GetTiltMetrics(int &consecLossesCount, datetime &lastTime)
{
    consecLossesCount = 0; lastTime = 0;
    for(int i = OrdersHistoryTotal() - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
        {
            double netProfit = OrderProfit() + OrderSwap() + OrderCommission();
            if(netProfit < 0) 
            {
                consecLossesCount++;
                if(lastTime == 0) lastTime = OrderCloseTime();
            }
            else if(netProfit > 0) break; 
        }
    }
}

void ManageOpenTrades()
{
    for(int i = OrdersTotal() - 1; i >= 0; i--)
    {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
        {
            int barsInTrade = iBarShift(Symbol(), 0, OrderOpenTime());
            if(barsInTrade >= MaxBarsInTrade)
            {
                if(OrderType() == OP_BUY) OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrGray);
                if(OrderType() == OP_SELL) OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrGray);
            }
        }
    }
}

int OpenPositionsCount()
{
    int count = 0;
    for(int i = OrdersTotal() - 1; i >= 0; i--)
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber) count++;
    return count;
}

double CalculatePositionSize(double stopLossPrice)
{
    double riskAmount = AccountEquity() * (MaxRiskPerTrade / 100.0);
    double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
    
    double slDistance = MathAbs(Ask - stopLossPrice);
    if(slDistance == 0 || tickSize == 0 || tickValue == 0) return 0;
    
    double lossPerLot = (slDistance / tickSize) * tickValue;
    double lots = riskAmount / lossPerLot;
    
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
    
    lots = MathRound(lots / lotStep) * lotStep;
    if(lots < minLot) lots = 0; // Skip trade if risk doesn't allow minimum size
    if(lots > maxLot) lots = maxLot;
    
    return lots;
}

bool IsInSession()
{
    int currentMins = Hour() * 60 + Minute();
    return (currentMins >= (StartHour * 60 + StartMinute) && currentMins <= (EndHour * 60 + EndMinute));
}

void UpdateHUD(bool lockedOut, bool cooldown, bool inSession, double dailyPnL, double trailDD, int consecLosses, int secsRemaining)
{
    string status = lockedOut ? "FATAL LOCKOUT" : cooldown ? StringFormat("COOLDOWN (%dm)", (secsRemaining/60)+1) : inSession ? "ARMED & CLEAR" : "OUT OF SESSION";
    string hud = StringFormat("== APEX RISK ENGINE ==\nStatus: %s\nDaily PnL: %.2f%% / Limit: -%.2f%%\nTrail DD: %.2f%% / Limit: -%.2f%%\nLosses: %d / Limit: %d", 
        status, dailyPnL, DailyHardStopPct, trailDD, TrailStopPct, consecLosses, MaxConsecLosses);
    Comment(hud);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

2. MT5 / MQL5: Apex Risk Engine & Microstructure

MQL5 utilizes the <Trade\Trade.mqh> library for execution. Price action data (swing highs/lows) is retrieved via CopyHigh and CopyLow to replicate TradingView's array structure accurately.

Code: Select all

//+------------------------------------------------------------------+
//|                                        RevengeController_Apex.mq5|
//+------------------------------------------------------------------+
#property strict
#include <Trade\Trade.mqh>

CTrade trade;

// ==========================================
// ⚙️ INPUTS: PROP FIRM LIMITS & TILT
// ==========================================
input group "--- Prop Firm Constraints ---";
input double   DailyHardStopPct   = 4.0;       
input double   TrailStopPct       = 5.0;       
input double   MaxRiskPerTrade    = 0.5;       

input group "--- Tilt & Cooldown ---";
input int      BaseCooldownMin    = 5;         
input int      EscalationMult     = 3;         
input int      MaxConsecLosses    = 3;         
input int      MaxBarsInTrade     = 10;        

input group "--- Execution Window ---";
input int      StartHour          = 9, StartMinute = 30;
input int      EndHour            = 16, EndMinute  = 0;
input ulong    MagicNumber        = 99999;

// Global State & Handles
string hwmGlobalName;
int atrHandle;

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(MagicNumber);
    
    hwmGlobalName = StringFormat("HWM_%d_%d", AccountInfoInteger(ACCOUNT_LOGIN), DayOfYear());
    if(!GlobalVariableCheck(hwmGlobalName))
        GlobalVariableSet(hwmGlobalName, AccountInfoDouble(ACCOUNT_EQUITY));
        
    atrHandle = iATR(_Symbol, _Period, 14);
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // 1. Manage High-Water Mark
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    double hwm = GlobalVariableGet(hwmGlobalName);
    if(currentEquity > hwm)
    {
        hwm = currentEquity;
        GlobalVariableSet(hwmGlobalName, hwm);
    }

    // 2. State & Drawdown Math
    double startOfDayBalance = GetStartOfDayBalance();
    double dailyPnLPct = ((currentEquity - startOfDayBalance) / startOfDayBalance) * 100.0;
    double trailDDPct  = ((currentEquity - hwm) / hwm) * 100.0;

    int consecLosses = 0;
    datetime lastLossTime = 0;
    GetTiltMetrics(consecLosses, lastLossTime);

    // 3. Failsafe Triggers
    bool hitDailyStop = (dailyPnLPct <= -DailyHardStopPct);
    bool hitTrailStop = (trailDDPct <= -TrailStopPct);
    bool hitConsecLoss = (consecLosses >= MaxConsecLosses);
    bool inSession = IsInSession();

    int currentCooldownSecs = 0;
    int secondsSinceLoss = (int)(TimeCurrent() - lastLossTime);
    bool inCooldown = false;
    
    if (consecLosses > 0)
    {
        double multPower = MathPow(EscalationMult, MathMax(0, consecLosses - 1));
        currentCooldownSecs = (int)(BaseCooldownMin * multPower * 60);
        if (secondsSinceLoss < currentCooldownSecs) inCooldown = true;
    }

    bool isLockedOut = (hitDailyStop || hitTrailStop || hitConsecLoss);
    
    UpdateHUD(isLockedOut, inCooldown, inSession, dailyPnLPct, trailDDPct, consecLosses, currentCooldownSecs - secondsSinceLoss);

    // 4. Trade Management (Time-in-Trade Abort)
    ManageOpenTrades();

    // 5. Execution Block
    if (isLockedOut || inCooldown || !inSession || PositionsTotal() > 0) return;

    // ==========================================
    // 📉 LIQUIDITY SWEEP MICROSTRUCTURE
    // ==========================================
    double high[], low[], close[], open[];
    double atr[];
    
    // Copy array data (0 is current forming candle, 1 is last closed candle)
    if(CopyHigh(_Symbol, _Period, 0, 17, high) <= 0 || CopyLow(_Symbol, _Period, 0, 17, low) <= 0 || 
       CopyClose(_Symbol, _Period, 0, 2, close) <= 0 || CopyOpen(_Symbol, _Period, 0, 2, open) <= 0 ||
       CopyBuffer(atrHandle, 0, 1, 1, atr) <= 0) return;

    // Find swing high/low from past 15 closed bars (excluding candle 1)
    double swingLow = low[2]; double swingHigh = high[2];
    for(int i = 2; i <= 16; i++) 
    {
        if(low[i] < swingLow) swingLow = low[i];
        if(high[i] > swingHigh) swingHigh = high[i];
    }

    // Candle 1 logic
    bool bullishSweep = (low[1] < swingLow && close[1] > swingLow && close[1] > open[1]);
    bool bearishSweep = (high[1] > swingHigh && close[1] < swingHigh && close[1] < open[1]);

    double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
    double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

    if(bullishSweep)
    {
        double sl = low[1] - atr[0];
        double tp = currentAsk + ((currentAsk - sl) * 2.0); // 2R Target
        double lots = CalculatePositionSize(sl, currentAsk);
        if(lots > 0) trade.Buy(lots, _Symbol, currentAsk, sl, tp, "Sweep Long");
    }
    else if(bearishSweep)
    {
        double sl = high[1] + atr[0];
        double tp = currentBid - ((sl - currentBid) * 2.0); // 2R Target
        double lots = CalculatePositionSize(sl, currentBid);
        if(lots > 0) trade.Sell(lots, _Symbol, currentBid, sl, tp, "Sweep Short");
    }
}

//+------------------------------------------------------------------+
//| Engine Logic Functions                                           |
//+------------------------------------------------------------------+
double GetStartOfDayBalance()
{
    double todayClosedProfit = 0.0;
    datetime startOfDay = iTime(_Symbol, PERIOD_D1, 0);

    HistorySelect(startOfDay, TimeCurrent());
    for(int i = 0; i < HistoryDealsTotal(); i++)
    {
        ulong ticket = HistoryDealGetTicket(i);
        if(ticket > 0)
        {
            long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
            if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT)
                todayClosedProfit += (HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_COMMISSION) + HistoryDealGetDouble(ticket, DEAL_SWAP));
        }
    }
    return AccountInfoDouble(ACCOUNT_BALANCE) - todayClosedProfit;
}

void GetTiltMetrics(int &consecLossesCount, datetime &lastTime)
{
    consecLossesCount = 0; lastTime = 0;
    HistorySelect(0, TimeCurrent());
    for(int i = HistoryDealsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = HistoryDealGetTicket(i);
        if(ticket > 0 && HistoryDealGetString(ticket, DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket, DEAL_MAGIC) == MagicNumber)
        {
            long entryType = HistoryDealGetInteger(ticket, DEAL_ENTRY);
            if(entryType == DEAL_ENTRY_OUT || entryType == DEAL_ENTRY_INOUT)
            {
                double netProfit = HistoryDealGetDouble(ticket, DEAL_PROFIT) + HistoryDealGetDouble(ticket, DEAL_COMMISSION) + HistoryDealGetDouble(ticket, DEAL_SWAP);
                if(netProfit < 0)
                {
                    consecLossesCount++;
                    if(lastTime == 0) lastTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
                }
                else if(netProfit > 0) break;
            }
        }
    }
}

void ManageOpenTrades()
{
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
        {
            datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
            int barsInTrade = Bars(_Symbol, _Period, openTime, TimeCurrent());
            if(barsInTrade >= MaxBarsInTrade)
                trade.PositionClose(ticket);
        }
    }
}

double CalculatePositionSize(double stopLossPrice, double entryPrice)
{
    double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * (MaxRiskPerTrade / 100.0);
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    
    double slDistance = MathAbs(entryPrice - stopLossPrice);
    if(slDistance == 0 || tickSize == 0 || tickValue == 0) return 0;
    
    double lossPerLot = (slDistance / tickSize) * tickValue;
    double lots = riskAmount / lossPerLot;
    
    double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    lots = MathRound(lots / lotStep) * lotStep;
    if(lots < minLot) lots = 0;
    if(lots > maxLot) lots = maxLot;
    
    return lots;
}

bool IsInSession()
{
    MqlDateTime dt; TimeToStruct(TimeCurrent(), dt);
    int currentMins = dt.hour * 60 + dt.min;
    return (currentMins >= (StartHour * 60 + StartMinute) && currentMins <= (EndHour * 60 + EndMinute));
}

void UpdateHUD(bool lockedOut, bool cooldown, bool inSession, double dailyPnL, double trailDD, int consecLosses, int secsRemaining)
{
    string status = lockedOut ? "FATAL LOCKOUT" : cooldown ? StringFormat("COOLDOWN (%dm)", (secsRemaining/60)+1) : inSession ? "ARMED & CLEAR" : "OUT OF SESSION";
    string hud = StringFormat("== APEX RISK ENGINE ==\nStatus: %s\nDaily PnL: %.2f%% / Limit: -%.2f%%\nTrail DD: %.2f%% / Limit: -%.2f%%\nLosses: %d / Limit: %d", 
        status, dailyPnL, DailyHardStopPct, trailDD, TrailStopPct, consecLosses, MaxConsecLosses);
    Comment(hud);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

Translating the "first level" logic into cTrader (C# cAlgo) is much cleaner than MetaTrader because cAlgo is a modern, object-oriented framework. Since you are an enterprise C# software engineer, you will immediately notice how much easier it is to use LINQ to query the History collection rather than manually looping through clunky history indices like in MQL.

In this cBot, we separate the concerns:

OnTick() constantly monitors your floating equity for the daily soft stop and updates the HUD in real-time.

OnBar() handles the actual trading logic (the SMA crossover in this basic version) to ensure signals are evaluated only on closed candles, but it must pass the master filter first.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

cTrader / cAlgo: Revenge Controller Basic (C#)

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class RevengeController_Basic : Robot
    {
        // ==========================================
        // ⚙️ INPUTS: RISK & REVENGE PARAMETERS
        // ==========================================
        [Parameter("Daily Soft Stop (%)", Group = "Risk Limits", DefaultValue = 2.0, MinValue = 0.1, Step = 0.25)]
        public double SoftStopPct { get; set; }

        [Parameter("Post-Loss Cooldown (Min)", Group = "Tilt Mechanics", DefaultValue = 5, MinValue = 1)]
        public int CooldownMinutes { get; set; }

        [Parameter("Trade Volume (Units)", Group = "Execution", DefaultValue = 100000, MinValue = 1000)]
        public double TradeVolume { get; set; }

        private SimpleMovingAverage _smaFast;
        private SimpleMovingAverage _smaSlow;
        private string _botLabel = "RevengeControllerBasic";

        protected override void OnStart()
        {
            // Initialize indicators for the example logic
            _smaFast = Indicators.SimpleMovingAverage(Bars.ClosePrices, 9);
            _smaSlow = Indicators.SimpleMovingAverage(Bars.ClosePrices, 21);
        }

        protected override void OnTick()
        {
            // 1. Calculate Daily PnL
            double startOfDayBalance = GetStartOfDayBalance();
            double dailyPnLPct = ((Account.Equity - startOfDayBalance) / startOfDayBalance) * 100.0;
            bool hitSoftStop = dailyPnLPct <= -SoftStopPct;

            // 2. Check Cooldown Status
            DateTime lastLossTime = GetLastLossTime();
            double secondsSinceLoss = (Server.Time - lastLossTime).TotalSeconds;
            bool inCooldown = (lastLossTime != DateTime.MinValue) && (secondsSinceLoss < (CooldownMinutes * 60));

            // 3. Update HUD
            UpdateHUD(hitSoftStop, inCooldown, dailyPnLPct, (int)((CooldownMinutes * 60) - secondsSinceLoss));

            // Optional: You could also forcefully close open positions here if hitSoftStop becomes true mid-trade
            // if (hitSoftStop && Positions.Count(p => p.Label == _botLabel) > 0) 
            //     CloseAllPositions();
        }

        protected override void OnBar()
        {
            // Re-evaluate State for execution (Blocks new trades)
            double startOfDayBalance = GetStartOfDayBalance();
            double dailyPnLPct = ((Account.Equity - startOfDayBalance) / startOfDayBalance) * 100.0;
            bool hitSoftStop = dailyPnLPct <= -SoftStopPct;

            DateTime lastLossTime = GetLastLossTime();
            bool inCooldown = (lastLossTime != DateTime.MinValue) && ((Server.Time - lastLossTime).TotalMinutes < CooldownMinutes);

            // 4. Master Filter
            if (hitSoftStop || inCooldown)
                return;

            // ==========================================
            // 📉 EXAMPLE ENTRY LOGIC (PROTECTED)
            // ==========================================
            bool isLongSetup = _smaFast.Result.Last(1) > _smaSlow.Result.Last(1) && _smaFast.Result.Last(2) <= _smaSlow.Result.Last(2);
            bool isShortSetup = _smaFast.Result.Last(1) < _smaSlow.Result.Last(1) && _smaFast.Result.Last(2) >= _smaSlow.Result.Last(2);

            if (isLongSetup && Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName) == 0)
            {
                CloseAllPositions(); // Simple reversal logic
                ExecuteMarketOrder(TradeType.Buy, SymbolName, TradeVolume, _botLabel);
            }
            else if (isShortSetup && Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName) == 0)
            {
                CloseAllPositions();
                ExecuteMarketOrder(TradeType.Sell, SymbolName, TradeVolume, _botLabel);
            }
        }

        // ==========================================
        // 🧠 ENGINE LOGIC FUNCTIONS
        // ==========================================
        private double GetStartOfDayBalance()
        {
            // LINQ query to sum all net profit for trades closed today
            double todayClosedProfit = History
                .Where(t => t.ClosingTime.Date == Server.Time.Date)
                .Sum(t => t.NetProfit);

            return Account.Balance - todayClosedProfit;
        }

        private DateTime GetLastLossTime()
        {
            // LINQ query to grab the absolute most recent closed trade for this symbol/bot
            var lastTrade = History
                .Where(t => t.SymbolName == SymbolName && t.Label == _botLabel)
                .OrderByDescending(t => t.ClosingTime)
                .FirstOrDefault();

            if (lastTrade != null && lastTrade.NetProfit < 0)
            {
                return lastTrade.ClosingTime;
            }

            return DateTime.MinValue; // Last trade was a win, or no history exists
        }

        private void CloseAllPositions()
        {
            foreach (var position in Positions.Where(p => p.Label == _botLabel && p.SymbolName == SymbolName))
            {
                ClosePosition(position);
            }
        }

        // ==========================================
        // 🖥️ TELEMETRY HUD
        // ==========================================
        private void UpdateHUD(bool softStop, bool cooldown, double pnl, int secsRemaining)
        {
            string status = "SYSTEM CLEAR";
            Color statusColor = Color.LimeGreen;

            if (softStop)
            {
                status = "SOFT STOP HIT: SESSION OVER";
                statusColor = Color.Red;
            }
            else if (cooldown)
            {
                status = $"COOLDOWN: {secsRemaining} seconds remaining";
                statusColor = Color.Orange;
            }

            string hudText = $"--- REVENGE CONTROLLER ---\nStatus: {status}\nDaily PnL: {Math.Round(pnl, 2)}% / -{SoftStopPct}%";
            
            // Draw static text anchored to the bottom right of the chart
            Chart.DrawStaticText("HUD", hudText, VerticalAlignment.Bottom, HorizontalAlignment.Right, statusColor);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

Why this architecture shines in C# / cTrader:

LINQ Power: Finding the last losing trade (GetLastLossTime) is a simple OrderByDescending().FirstOrDefault() paired with a lambda expression checking NetProfit. No messy for loops counting backward through abstract history indices.

True Equity Sync: Because OnTick evaluates Account.Equity directly in real-time, the soft-stop acts as a hard ceiling. It instantly captures floating drawdown, not just closed drawdown.

Chart Object Model: Rather than dropping basic text in the top-left corner via a terminal Comment() function, Chart.DrawStaticText allows proper UI anchoring directly over the candlestick data in the color of the current state.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

Building the "Pro" level risk engine in cTrader is highly efficient. Because cAlgo is a modern C# environment, we do not need to rely on the brittle for loop iterations required by MetaTrader to reconstruct state. Instead, we use LINQ to query the terminal's History collection dynamically, ensuring the consecLosses count and escalating cooldown timers instantly rebuild themselves even if the cBot is stopped, recompiled, or the terminal reboots.

This implementation introduces escalating penalties, the consecutive loss hard-stop, and strict server-time session fencing.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Revenge Trading Is the #1 Account Killer

Post by PTScalper »

cTrader / cAlgo: Pro Risk Engine (C#)

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class RevengeController_Pro : Robot
    {
        // ==========================================
        // ⚙️ INPUTS: RISK LIMITS
        // ==========================================
        [Parameter("Daily Soft Stop (%)", Group = "Risk Limits", DefaultValue = 2.0, MinValue = 0.1, Step = 0.25)]
        public double SoftStopPct { get; set; }

        [Parameter("Max Consecutive Losses", Group = "Risk Limits", DefaultValue = 3, MinValue = 1)]
        public int MaxConsecLosses { get; set; }

        // ==========================================
        // ⚙️ INPUTS: TILT & COOLDOWN
        // ==========================================
        [Parameter("Base Cooldown (Min)", Group = "Tilt Mechanics", DefaultValue = 5, MinValue = 1)]
        public int BaseCooldownMin { get; set; }

        [Parameter("Escalation Multiplier", Group = "Tilt Mechanics", DefaultValue = 3, MinValue = 1)]
        public int EscalationMult { get; set; }

        // ==========================================
        // ⚙️ INPUTS: SESSION FENCING
        // ==========================================
        [Parameter("Session Start (Server Time)", Group = "Execution Window", DefaultValue = "09:30:00")]
        public string SessionStart { get; set; }

        [Parameter("Session End (Server Time)", Group = "Execution Window", DefaultValue = "16:00:00")]
        public string SessionEnd { get; set; }

        [Parameter("Trade Volume (Units)", Group = "Execution", DefaultValue = 100000, MinValue = 1000)]
        public double TradeVolume { get; set; }

        private SimpleMovingAverage _smaFast;
        private SimpleMovingAverage _smaSlow;
        private string _botLabel = "RevengeControllerPro";

        private TimeSpan _startTime;
        private TimeSpan _endTime;

        protected override void OnStart()
        {
            // Parse session boundaries
            TimeSpan.TryParse(SessionStart, out _startTime);
            TimeSpan.TryParse(SessionEnd, out _endTime);

            // Initialize example indicators
            _smaFast = Indicators.SimpleMovingAverage(Bars.ClosePrices, 9);
            _smaSlow = Indicators.SimpleMovingAverage(Bars.ClosePrices, 21);
        }

        protected override void OnTick()
        {
            // 1. Core State Calculations
            double dailyPnL = GetDailyPnLPct();
            bool hitSoftStop = dailyPnL <= -SoftStopPct;

            GetTiltMetrics(out int consecLosses, out DateTime lastLossTime);
            bool hitConsecLimit = consecLosses >= MaxConsecLosses;

            bool inSession = IsInSession();

            // 2. Escalating Cooldown Math (Base * Multiplier ^ (Losses - 1))
            double currentCooldownSecs = 0;
            double secondsSinceLoss = (Server.Time - lastLossTime).TotalSeconds;
            bool inCooldown = false;

            if (consecLosses > 0)
            {
                double multPower = Math.Pow(EscalationMult, Math.Max(0, consecLosses - 1));
                currentCooldownSecs = BaseCooldownMin * multPower * 60;
                
                if (secondsSinceLoss < currentCooldownSecs) 
                    inCooldown = true;
            }

            bool isLockedOut = hitSoftStop || hitConsecLimit;

            // 3. Update Visual Dashboard
            UpdateHUD(isLockedOut, inCooldown, inSession, dailyPnL, consecLosses, (int)(currentCooldownSecs - secondsSinceLoss));

            // Optional: Hard abort open trades if soft stop is breached via floating drawdown
            // if (isLockedOut && Positions.Count(p => p.Label == _botLabel) > 0) CloseAllPositions();
        }

        protected override void OnBar()
        {
            // Re-fetch state for bar execution to prevent race conditions
            double dailyPnL = GetDailyPnLPct();
            GetTiltMetrics(out int consecLosses, out DateTime lastLossTime);

            bool isLockedOut = (dailyPnL <= -SoftStopPct) || (consecLosses >= MaxConsecLosses);
            bool inSession = IsInSession();
            bool inCooldown = false;

            if (consecLosses > 0)
            {
                double multPower = Math.Pow(EscalationMult, Math.Max(0, consecLosses - 1));
                if ((Server.Time - lastLossTime).TotalSeconds < (BaseCooldownMin * multPower * 60))
                    inCooldown = true;
            }

            // 4. Master Engine Filter
            if (isLockedOut || inCooldown || !inSession) return;

            // ==========================================
            // 📉 PROTECTED EXECUTION LOGIC
            // ==========================================
            bool isLongSetup = _smaFast.Result.Last(1) > _smaSlow.Result.Last(1) && _smaFast.Result.Last(2) <= _smaSlow.Result.Last(2);
            bool isShortSetup = _smaFast.Result.Last(1) < _smaSlow.Result.Last(1) && _smaFast.Result.Last(2) >= _smaSlow.Result.Last(2);

            if (isLongSetup && Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName) == 0)
            {
                CloseAllPositions();
                ExecuteMarketOrder(TradeType.Buy, SymbolName, TradeVolume, _botLabel);
            }
            else if (isShortSetup && Positions.Count(p => p.Label == _botLabel && p.SymbolName == SymbolName) == 0)
            {
                CloseAllPositions();
                ExecuteMarketOrder(TradeType.Sell, SymbolName, TradeVolume, _botLabel);
            }
        }

        // ==========================================
        // 🧠 ENGINE LOGIC FUNCTIONS
        // ==========================================
        private double GetDailyPnLPct()
        {
            // Sum realized profit for the current day
            double todayClosedProfit = History
                .Where(t => t.ClosingTime.Date == Server.Time.Date)
                .Sum(t => t.NetProfit);

            // Establish the balance at the start of the day
            double startOfDayBalance = Account.Balance - todayClosedProfit;
            
            // Return True Floating Drawdown vs Start of Day Balance
            return ((Account.Equity - startOfDayBalance) / startOfDayBalance) * 100.0;
        }

        private void GetTiltMetrics(out int consecLosses, out DateTime lastLossTime)
        {
            consecLosses = 0;
            lastLossTime = DateTime.MinValue;

            // Fetch history in reverse chronological order
            var botHistory = History
                .Where(t => t.SymbolName == SymbolName && t.Label == _botLabel)
                .OrderByDescending(t => t.ClosingTime)
                .ToList();

            foreach (var trade in botHistory)
            {
                if (trade.NetProfit < 0)
                {
                    consecLosses++;
                    if (lastLossTime == DateTime.MinValue) lastLossTime = trade.ClosingTime;
                }
                else if (trade.NetProfit > 0)
                {
                    break; // Streak broken by a winner
                }
            }
        }

        private bool IsInSession()
        {
            TimeSpan currentServerTime = Server.Time.TimeOfDay;
            return currentServerTime >= _startTime && currentServerTime <= _endTime;
        }

        private void CloseAllPositions()
        {
            foreach (var position in Positions.Where(p => p.Label == _botLabel && p.SymbolName == SymbolName))
            {
                ClosePosition(position);
            }
        }

        // ==========================================
        // 🖥️ TELEMETRY HUD
        // ==========================================
        private void UpdateHUD(bool lockedOut, bool cooldown, bool inSession, double pnl, int losses, int secsRemaining)
        {
            string status = "ARMED & CLEAR";
            Color statusColor = Color.LimeGreen;

            if (lockedOut)
            {
                status = "FATAL LOCKOUT";
                statusColor = Color.Red;
            }
            else if (cooldown)
            {
                status = $"COOLDOWN ({(secsRemaining / 60) + 1}m left)";
                statusColor = Color.Orange;
            }
            else if (!inSession)
            {
                status = "OUT OF SESSION";
                statusColor = Color.Gray;
            }

            string hudText = 
                $"====== PRO RISK ENGINE ======\n" +
                $"Status: {status}\n" +
                $"Daily PnL: {Math.Round(pnl, 2)}% / Limit: -{SoftStopPct}%\n" +
                $"Consec Losses: {losses} / Limit: {MaxConsecLosses}";

            Chart.DrawStaticText("HUD", hudText, VerticalAlignment.Bottom, HorizontalAlignment.Right, statusColor);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply