Advertisement IC Markets

Track Emotional State and Rule Adherence Alongside P&L

Document your personal trading journey. Track daily equity curves, review winning and losing streaks, share trade screenshots, and get constructive feedback.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Track Emotional State and Rule Adherence Alongside P&L

Post by PTScalper »

Unlike C# in cTrader, MetaTrader 4 and 5 do not expose asynchronous methods for instantly dropping orders from memory, nor can you block the "New Order" terminal button. The circuit breaker must be deployed as an Expert Advisor (EA) running a high-frequency OnTimer() event. This ensures the bot detects off-plan trades even when the market is slow and no new ticks are arriving.

MetaTrader 5 (MQL5) Implementation

MT5 has a structural advantage for this: the OnTradeTransaction event. If you attempt to enter a manual trade while the daily lockout is active, this event intercepts the order placement instantly in the background, bypassing the need to wait for the next price tick.

Code: Select all

#include <Trade\Trade.mqh>

input double MaxDailyLoss = 500.0; // Max Daily Loss (Account Currency)

CTrade trade;
double startOfDayBalance = 0;
int currentDay = -1;
bool isLockedOut = false;

int OnInit() {
    EventSetTimer(1); // 1-second timer for off-tick monitoring
    UpdateStartOfDayBalance();
    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) {
    EventKillTimer();
}

void OnTick() {
    EvaluateDrawdown();
}

void OnTimer() {
    CheckNewDayReset();
    EvaluateDrawdown();
    
    // Aggressive fallback sweep
    if (isLockedOut && (PositionsTotal() > 0 || OrdersTotal() > 0)) {
        LiquidateAccount();
    }
}

// Instantly intercepts manual revenge trades
void OnTradeTransaction(const MqlTradeTransaction &trans, const MqlTradeRequest &request, const MqlTradeResult &result) {
    if (isLockedOut && (PositionsTotal() > 0 || OrdersTotal() > 0)) {
        LiquidateAccount();
    }
}

void CheckNewDayReset() {
    MqlDateTime dt;
    TimeCurrent(dt);
    if (dt.day_of_year != currentDay) {
        UpdateStartOfDayBalance();
        isLockedOut = false;
        Print("New trading day. Circuit breaker reset.");
    }
}

void UpdateStartOfDayBalance() {
    MqlDateTime dt;
    TimeCurrent(dt);
    currentDay = dt.day_of_year;
    
    // Calculate 00:00:00 of the current broker day
    dt.hour = 0; dt.min = 0; dt.sec = 0;
    datetime startOfDay = StructToTime(dt);
    
    HistorySelect(startOfDay, TimeCurrent());
    double dailyClosedPnL = 0;
    
    for(int i = 0; i < HistoryDealsTotal(); i++) {
        ulong ticket = HistoryDealGetTicket(i);
        dailyClosedPnL += HistoryDealGetDouble(ticket, DEAL_PROFIT) + 
                          HistoryDealGetDouble(ticket, DEAL_COMMISSION) + 
                          HistoryDealGetDouble(ticket, DEAL_SWAP);
    }
    
    startOfDayBalance = AccountInfoDouble(ACCOUNT_BALANCE) - dailyClosedPnL;
}

void EvaluateDrawdown() {
    if (isLockedOut) return;

    double currentLoss = startOfDayBalance - AccountInfoDouble(ACCOUNT_EQUITY);

    if (currentLoss >= MaxDailyLoss) {
        isLockedOut = true;
        Print("CIRCUIT BREAKER TRIGGERED: Daily loss limit reached.");
        LiquidateAccount();
    }
}

void LiquidateAccount() {
    // Loop backward to avoid index shifting during deletions
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if(ticket > 0) trade.PositionClose(ticket);
    }
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        ulong ticket = OrderGetTicket(i);
        if(ticket > 0) trade.OrderDelete(ticket);
    }
}
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: Track Emotional State and Rule Adherence Alongside P&L

Post by PTScalper »

MetaTrader 4 (MQL4) Implementation

MT4 lacks transaction events, so this implementation relies entirely on a fast 1-second loop. It calculates the midnight server time, tracks closed PnL through the history pool, and loops backward to liquidate everything across all charts if the equity drops below the threshold.

Code: Select all

extern double MaxDailyLoss = 500.0; // Max Daily Loss (Account Currency)

double startOfDayBalance = 0;
int currentDay = -1;
bool isLockedOut = false;

int OnInit() {
    EventSetTimer(1); 
    UpdateStartOfDayBalance();
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    EventKillTimer();
}

void OnTick() {
    EvaluateDrawdown();
}

void OnTimer() {
    CheckNewDayReset();
    EvaluateDrawdown();
    
    // Aggressive interception for manual trades
    if (isLockedOut && OrdersTotal() > 0) {
        LiquidateAccount();
    }
}

void CheckNewDayReset() {
    int dayOfYear = TimeDayOfYear(TimeCurrent());
    if (dayOfYear != currentDay) {
        UpdateStartOfDayBalance();
        isLockedOut = false;
        Print("New trading day. Circuit breaker reset.");
    }
}

void UpdateStartOfDayBalance() {
    currentDay = TimeDayOfYear(TimeCurrent());
    
    // Calculate midnight broker time
    datetime startOfDay = TimeCurrent() - (TimeCurrent() % 86400); 
    
    double dailyClosedPnL = 0;
    for(int i = 0; i < OrdersHistoryTotal(); i++) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_HISTORY)) {
            if(OrderCloseTime() >= startOfDay) {
                dailyClosedPnL += OrderProfit() + OrderCommission() + OrderSwap();
            }
        }
    }
    
    startOfDayBalance = AccountBalance() - dailyClosedPnL;
}

void EvaluateDrawdown() {
    if (isLockedOut) return;

    double currentLoss = startOfDayBalance - AccountEquity();

    if (currentLoss >= MaxDailyLoss) {
        isLockedOut = true;
        Print("CIRCUIT BREAKER TRIGGERED: Daily loss limit reached.");
        LiquidateAccount();
    }
}

void LiquidateAccount() {
    // Loop backward to avoid index shifting
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            int type = OrderType();
            string sym = OrderSymbol();
            
            if(type <= OP_SELL) {
                RefreshRates();
                // Dynamically fetch bid/ask for multi-currency handling
                double closePrice = (type == OP_BUY) ? MarketInfo(sym, MODE_BID) : MarketInfo(sym, MODE_ASK);
                OrderClose(OrderTicket(), OrderLots(), closePrice, 3, clrRed);
            } else {
                OrderDelete(OrderTicket());
            }
        }
    }
}
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: Track Emotional State and Rule Adherence Alongside P&L

Post by PTScalper »

Deploy the EA on a single major chart (e.g., EURUSD) on your VPS. Because both scripts parse the entire global terminal order pool (using PositionsTotal() and OrdersTotal()), a single running instance will protect all pairs and open charts simultaneously.
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: Track Emotional State and Rule Adherence Alongside P&L

Post by PTScalper »

Unlike MQL or cTrader, TradingView’s Pine Script does not have direct access to your broker terminal's manual UI buttons or a global account order pool. A Pine Script circuit breaker cannot stop you from clicking "Buy" or "Sell" manually on the TradingView chart interface.

Instead, a Pine Script circuit breaker functions as a risk-management wrapper inside an automated strategy. If you are building a custom indicator or strategy that fires webhook alerts to a live broker execution engine, this code will cut off the strategy's ability to trade and flatten open positions the moment the simulated equity drops past your limit.

Pine Script (v5) Implementation

To make this work in real-time, the strategy must be set to calc_on_every_tick=true. Otherwise, TradingView will wait until the candle closes to calculate the drawdown, which could result in a loss far exceeding your limit on a volatile 15-minute chart.

Code: Select all

//@version=5
strategy("Daily Circuit Breaker [Guardian]", overlay=true, calc_on_every_tick=true, initial_capital=10000)

// --- Inputs ---
maxDailyLoss = input.float(500.0, title="Max Daily Loss (Account Currency)")

// --- State Variables ---
var float startOfDayEquity = na
var bool isLockedOut = false

// --- New Day Detection ---
// Detects when the session rolls over to a new trading day
isNewDay = ta.change(time("D")) != 0

if isNewDay
    startOfDayEquity := strategy.equity
    isLockedOut := false // Reset the circuit breaker

// Initialize the equity baseline on the very first historical bar
if barstate.isfirst
    startOfDayEquity := strategy.initial_capital

// --- Drawdown Calculation ---
// strategy.equity includes both closed trades and open unrealized PnL
currentDailyLoss = startOfDayEquity - strategy.equity

// --- Circuit Breaker Trigger ---
if currentDailyLoss >= maxDailyLoss and not isLockedOut
    isLockedOut := true
    // Instantly flatten all open positions and cancel pending limit/stop orders
    strategy.close_all(comment="CIRCUIT BREAKER: Daily Limit Hit")
    strategy.cancel_all()

// --- Visual Feedback ---
// Paints the chart background red when the account is locked out
bgcolor(isLockedOut ? color.new(color.red, 85) : na, title="Lockout Active")

// =====================================================================
// --- Example Execution Logic ---
// Your price action entry rules go here. They must check 'not isLockedOut'
// =====================================================================

// Example: Simple moving average crossover for demonstration
longCondition = ta.crossover(ta.sma(close, 14), ta.sma(close, 28))
shortCondition = ta.crossunder(ta.sma(close, 14), ta.sma(close, 28))

if longCondition and not isLockedOut
    strategy.entry("Long", strategy.long)

if shortCondition and not isLockedOut
    strategy.entry("Short", strategy.short)
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: Track Emotional State and Rule Adherence Alongside P&L

Post by PTScalper »

Key Differences to Note for TradingView:

strategy.equity Overrides: This built-in variable tracks the strategy's theoretical equity, not your live broker's equity. If you manually intervene in a trade on your broker platform, Pine Script will not know about it. The script must be the sole manager of the positions.

strategy.close_all(): This command is the equivalent of the liquidation loops in MQL/C#. It immediately drops a market order to net your position to zero and removes the strategy from the market.

Alerting the Broker: If you are using this to halt live execution, ensure your TradingView webhook alert message is configured to trigger on the strategy.close_all() comment (e.g., passing "CIRCUIT BREAKER: Daily Limit Hit" in your JSON payload to command your execution engine to flatten the live account).
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply