Page 3 of 7

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:54 am
by FTtrader
The solution is utilizing MetaTrader's Terminal Global Variables (GlobalVariableSet and GlobalVariableGet) to write the highest equity value directly to the terminal's physical memory footprint.

Code: Select all

#property copyright "Dynamic Equity Guardian"
#property version   "2.00"
#property strict

#include <Trade\Trade.mqh>

//--- Input parameters
input double InpTrailingDrawdown = 2500.0; // Trailing Drawdown Limit (Account Currency)
input bool   InpSendPush         = true;   // Send mobile push notification
input int    InpSlippage         = 3;      // Allowed slippage in points

CTrade trade;
double highWaterMark;
string gvName;

int OnInit() {
    trade.SetExpertMagicNumber(99999);
    trade.SetDeviationInPoints(InpSlippage);
    
    // IOC is preferred over FOK in raw spread environments to prevent rejection during flash volatility
    trade.SetTypeFilling(ORDER_FILLING_IOC); 
    
    // Create an account-specific key for the Global Variable
    gvName = "MaxEquity_" + IntegerToString(AccountInfoInteger(ACCOUNT_LOGIN));
    
    // Recover previous state in case of a VPS or terminal restart
    if (GlobalVariableCheck(gvName)) {
        highWaterMark = GlobalVariableGet(gvName);
    } else {
        highWaterMark = AccountInfoDouble(ACCOUNT_EQUITY);
        GlobalVariableSet(gvName, highWaterMark);
    }
    
    PrintFormat("Guardian Initialized. Watermark: %.2f | Dynamic Floor: %.2f", highWaterMark, highWaterMark - InpTrailingDrawdown);
    return(INIT_SUCCEEDED);
}

void OnTick() {
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    
    // 1. Dynamically track and update the high-water mark
    if (currentEquity > highWaterMark) {
        highWaterMark = currentEquity;
        GlobalVariableSet(gvName, highWaterMark);
    }
    
    // 2. Calculate the live physical floor
    double dynamicFloor = highWaterMark - InpTrailingDrawdown;
    
    // 3. Breach detection
    if (currentEquity <= dynamicFloor && PositionsTotal() > 0) {
        string alertMsg = StringFormat("FATAL ALERT: Trailing limit breached! Equity: %.2f, Floor: %.2f", currentEquity, dynamicFloor);
        Print(alertMsg);
        
        if (InpSendPush) {
            SendNotification(alertMsg);
        }
        
        FlattenAllPositions();
        
        // Wipe the saved watermark so the next trading series starts with a clean slate
        GlobalVariableDel(gvName);
        ExpertRemove(); 
    }
}

void FlattenAllPositions() {
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0) {
            if (!trade.PositionClose(ticket)) {
                PrintFormat("Failed to close position #%I64u. Error: %d", ticket, GetLastError());
            } else {
                PrintFormat("Closed position #%I64u", ticket);
            }
        }
    }
}

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:54 am
by FTtrader
State Persistence and Lifecycle Management

The gvName string dynamically appends your account login number. If you run multiple accounts on the same VPS, this prevents cross-contamination of high-water marks. When a breach occurs, GlobalVariableDel(gvName) clears the memory file before ExpertRemove() unloads the EA, ensuring your next 2-to-6 week trading series starts fresh without anchoring to a previous evaluation's peak.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:55 am
by FTtrader
Order Execution Realities

The shift from ORDER_FILLING_FOK (Fill-or-Kill) to ORDER_FILLING_IOC (Immediate-or-Cancel) is critical when operating near failure thresholds. In highly liquid raw spread environments, FOK can result in total order rejection if the requested volume isn't instantly available at the precise microsecond. IOC guarantees the broker will close whatever liquidity is immediately available, incrementally flattening your exposure rather than rejecting the command entirely while equity continues to drop.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:55 am
by FTtrader
Integrating a daily drawdown limit requires the EA to track broker server time for midnight rollovers and calculate two independent failure floors simultaneously. When managing your own deposited capital across a structured 2 to 6-week trading series, the script must evaluate both the series-long trailing floor and the strictly intraday floor, dynamically flattening your exposure if either threshold is breached.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:55 am
by FTtrader
Here is the updated MQL5 implementation utilizing Terminal Global Variables to persist both the daily start-of-day (SOD) equity and the maximum series watermark.

Code: Select all

#property copyright "Dual-Floor Risk Guardian"
#property version   "3.00"
#property strict

#include <Trade\Trade.mqh>

//--- Input parameters
input double InpTrailingDrawdown = 2500.0; // Series Trailing Limit (Account Currency)
input double InpDailyDrawdown    = 1000.0; // Daily Hard Limit (Account Currency)
input bool   InpSendPush         = true;   // Send mobile push notification
input int    InpSlippage         = 3;      // Allowed slippage in points

CTrade trade;
double highWaterMark;
double startOfDayEquity;
int    currentDayOfYear;

string gvMaxEqName, gvSODEqName, gvDayTrackerName;

int OnInit() {
    trade.SetExpertMagicNumber(99999);
    trade.SetDeviationInPoints(InpSlippage);
    trade.SetTypeFilling(ORDER_FILLING_IOC); 
    
    long login = AccountInfoInteger(ACCOUNT_LOGIN);
    gvMaxEqName      = "MaxEq_" + IntegerToString(login);
    gvSODEqName      = "SODEq_" + IntegerToString(login);
    gvDayTrackerName = "DayTrack_" + IntegerToString(login);
    
    MqlDateTime dt;
    TimeCurrent(dt);
    int today = dt.day_of_year;
    
    // 1. Recover or Initialize Trailing Watermark
    if (GlobalVariableCheck(gvMaxEqName)) {
        highWaterMark = GlobalVariableGet(gvMaxEqName);
    } else {
        highWaterMark = AccountInfoDouble(ACCOUNT_EQUITY);
        GlobalVariableSet(gvMaxEqName, highWaterMark);
    }
    
    // 2. Recover or Initialize Daily Watermark
    if (GlobalVariableCheck(gvDayTrackerName) && GlobalVariableGet(gvDayTrackerName) == today) {
        startOfDayEquity = GlobalVariableGet(gvSODEqName);
        currentDayOfYear = today;
    } else {
        startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
        currentDayOfYear = today;
        GlobalVariableSet(gvSODEqName, startOfDayEquity);
        GlobalVariableSet(gvDayTrackerName, currentDayOfYear);
    }
    
    PrintFormat("Guardian initialized. Trailing Watermark: %.2f | SOD Equity: %.2f", highWaterMark, startOfDayEquity);
    return(INIT_SUCCEEDED);
}

void OnTick() {
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    MqlDateTime dt;
    TimeCurrent(dt);
    
    // 1. Broker Midnight Rollover Check
    if (dt.day_of_year != currentDayOfYear) {
        currentDayOfYear = dt.day_of_year;
        startOfDayEquity = currentEquity; // Reset daily baseline
        GlobalVariableSet(gvSODEqName, startOfDayEquity);
        GlobalVariableSet(gvDayTrackerName, currentDayOfYear);
        Print("Midnight Rollover: New Daily Baseline set to ", startOfDayEquity);
    }
    
    // 2. Dynamically track and update the high-water mark
    if (currentEquity > highWaterMark) {
        highWaterMark = currentEquity;
        GlobalVariableSet(gvMaxEqName, highWaterMark);
    }
    
    // 3. Calculate both physical floors
    double dynamicTrailingFloor = highWaterMark - InpTrailingDrawdown;
    double dynamicDailyFloor    = startOfDayEquity - InpDailyDrawdown;
    
    // The active floor is always whichever limit is HIGHER (closer to current price)
    double activeFloor = MathMax(dynamicTrailingFloor, dynamicDailyFloor);
    
    // 4. Breach detection
    if (currentEquity <= activeFloor && PositionsTotal() > 0) {
        string alertMsg = StringFormat("FATAL ALERT: Floor breached! Equity: %.2f, Active Floor: %.2f", currentEquity, activeFloor);
        Print(alertMsg);
        
        if (InpSendPush) SendNotification(alertMsg);
        
        FlattenAllPositions();
        
        // Wipe the saved variables so the terminal starts fresh after the breach
        GlobalVariableDel(gvMaxEqName);
        GlobalVariableDel(gvSODEqName);
        GlobalVariableDel(gvDayTrackerName);
        ExpertRemove(); 
    }
}

void FlattenAllPositions() {
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0) {
            trade.PositionClose(ticket);
        }
    }
}

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:56 am
by FTtrader
Key Architectural Details

Server Time Rollover (TimeCurrent): The script uses TimeCurrent() rather than TimeLocal() to query the broker's server time. This ensures the daily drawdown baseline resets precisely at broker midnight (typically 00:00 EET on IC Markets) rather than the local system time of your VPS. Using dt.day_of_year cleanly handles month-end and year-end rollovers without complex date math.

The Dual-Floor Logic (MathMax): By calculating MathMax(dynamicTrailingFloor, dynamicDailyFloor), the EA instantly identifies which risk parameter is currently more restrictive. If your account is significantly in profit for the day, the trailing series floor might be the active danger zone. If you take a loss early in the day, the daily floor will sit higher than the trailing floor, triggering the closure first.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:56 am
by FTtrader
To add a take-profit target, you introduce a new input variable for your series equity goal and evaluate it before the drawdown logic inside the OnTick() loop. When your equity crosses this threshold, the script secures the goal, flattens all exposure, wipes the state variables, and unloads itself from the chart so it remains dormant during your rest period between series.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:56 am
by FTtrader
Here is the complete implementation with the integrated equity target:

Code: Select all

#property copyright "Complete Series Guardian"
#property version   "4.00"
#property strict

#include <Trade\Trade.mqh>

//--- Input parameters
input double InpSeriesTarget     = 12000.0; // Series Equity Goal (0 to disable)
input double InpTrailingDrawdown = 2500.0;  // Series Trailing Limit (Account Currency)
input double InpDailyDrawdown    = 1000.0;  // Daily Hard Limit (Account Currency)
input bool   InpSendPush         = true;    // Send mobile push notification
input int    InpSlippage         = 3;       // Allowed slippage in points

CTrade trade;
double highWaterMark;
double startOfDayEquity;
int    currentDayOfYear;

string gvMaxEqName, gvSODEqName, gvDayTrackerName;

int OnInit() {
    trade.SetExpertMagicNumber(99999);
    trade.SetDeviationInPoints(InpSlippage);
    trade.SetTypeFilling(ORDER_FILLING_IOC); 
    
    long login = AccountInfoInteger(ACCOUNT_LOGIN);
    gvMaxEqName      = "MaxEq_" + IntegerToString(login);
    gvSODEqName      = "SODEq_" + IntegerToString(login);
    gvDayTrackerName = "DayTrack_" + IntegerToString(login);
    
    MqlDateTime dt;
    TimeCurrent(dt);
    int today = dt.day_of_year;
    
    if (GlobalVariableCheck(gvMaxEqName)) {
        highWaterMark = GlobalVariableGet(gvMaxEqName);
    } else {
        highWaterMark = AccountInfoDouble(ACCOUNT_EQUITY);
        GlobalVariableSet(gvMaxEqName, highWaterMark);
    }
    
    if (GlobalVariableCheck(gvDayTrackerName) && GlobalVariableGet(gvDayTrackerName) == today) {
        startOfDayEquity = GlobalVariableGet(gvSODEqName);
        currentDayOfYear = today;
    } else {
        startOfDayEquity = AccountInfoDouble(ACCOUNT_EQUITY);
        currentDayOfYear = today;
        GlobalVariableSet(gvSODEqName, startOfDayEquity);
        GlobalVariableSet(gvDayTrackerName, currentDayOfYear);
    }
    
    PrintFormat("Guardian active. Target: %.2f | SOD: %.2f", InpSeriesTarget, startOfDayEquity);
    return(INIT_SUCCEEDED);
}

void OnTick() {
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    MqlDateTime dt;
    TimeCurrent(dt);
    
    // 1. Broker Midnight Rollover Check
    if (dt.day_of_year != currentDayOfYear) {
        currentDayOfYear = dt.day_of_year;
        startOfDayEquity = currentEquity; 
        GlobalVariableSet(gvSODEqName, startOfDayEquity);
        GlobalVariableSet(gvDayTrackerName, currentDayOfYear);
    }
    
    // 2. Series Target Detection (Checked BEFORE drawdown floors)
    if (InpSeriesTarget > 0 && currentEquity >= InpSeriesTarget) {
        string targetMsg = StringFormat("SERIES COMPLETE: Goal reached! Equity: %.2f.", currentEquity);
        Print(targetMsg);
        
        if (InpSendPush) SendNotification(targetMsg);
        
        if (PositionsTotal() > 0) FlattenAllPositions();
        
        // Wipe variables to prepare for a clean slate after the weeks off
        GlobalVariableDel(gvMaxEqName);
        GlobalVariableDel(gvSODEqName);
        GlobalVariableDel(gvDayTrackerName);
        ExpertRemove(); 
        return; // Immediately exit OnTick to skip floor evaluation
    }
    
    // 3. Dynamically track and update the high-water mark
    if (currentEquity > highWaterMark) {
        highWaterMark = currentEquity;
        GlobalVariableSet(gvMaxEqName, highWaterMark);
    }
    
    // 4. Calculate both physical floors
    double dynamicTrailingFloor = highWaterMark - InpTrailingDrawdown;
    double dynamicDailyFloor    = startOfDayEquity - InpDailyDrawdown;
    double activeFloor = MathMax(dynamicTrailingFloor, dynamicDailyFloor);
    
    // 5. Breach detection
    if (currentEquity <= activeFloor && PositionsTotal() > 0) {
        string alertMsg = StringFormat("FATAL ALERT: Floor breached! Equity: %.2f, Active Floor: %.2f", currentEquity, activeFloor);
        Print(alertMsg);
        
        if (InpSendPush) SendNotification(alertMsg);
        
        FlattenAllPositions();
        
        GlobalVariableDel(gvMaxEqName);
        GlobalVariableDel(gvSODEqName);
        GlobalVariableDel(gvDayTrackerName);
        ExpertRemove(); 
    }
}

void FlattenAllPositions() {
    for (int i = PositionsTotal() - 1; i >= 0; i--) {
        ulong ticket = PositionGetTicket(i);
        if (ticket > 0) {
            trade.PositionClose(ticket);
        }
    }
}

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:57 am
by FTtrader
Key Architectural Additions

Logic Hierarchy (return; statement): The target goal is intentionally evaluated in step 2, strictly before the drawdown floors are calculated. If the target is hit, the return; command halts the rest of the OnTick() loop. This is critical because flattening multiple positions sequentially takes a few milliseconds; if the code evaluated the drawdown limit simultaneously while spread widens during position closure, it could falsely trigger the failure alert immediately after hitting the win target.

Variable Eradication: By executing GlobalVariableDel() for all three state trackers upon reaching the target, the script ensures that the terminal's memory is totally sanitized. When you initiate a new trading series after your 2 to 4-week break, attaching the EA to the chart will securely initialize fresh baselines without inheriting the previous cycle's data.

Re: Hitting most of the profit target in the first few days: what now?

Posted: Sat Oct 24, 2026 9:57 am
by FTtrader
Unlike MQL5, which reads the live broker account equity natively, Pine Script operates in a sandboxed environment. A Pine Script strategy() can only calculate equity, drawdowns, and targets based on the trades the script itself executes. It cannot read the floating equity of trades you place manually on your mobile phone via IC Markets.

To implement this logic in TradingView, you must use strategy.equity, enable calc_on_every_tick = true so the floors are monitored intrabar, and use a state variable (isHalted) to permanently disable the script once your 2 to 6-week series goal or failure threshold is reached.