Page 4 of 5

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:14 am
by FTtrader
Architectural Upgrades for Pine Script

One-and-Done Garbage Collection: In the MT5/cTrader versions, the lines were drawn statically. In this script, once the M15 candle sweeps the exact level and closes back inside, the array.remove() function deletes that specific EQH/EQL price from active memory. It marks the chart with a colored triangle and will never alert you for that specific liquidity pool again, preventing duplicate webhook executions on choppy consolidations.

barmerge.lookahead_off: This guarantees zero repainting. If you use this script on a live chart, the H4/D1 structures will only lock in and draw the line after the higher timeframe candle has fully closed and confirmed the right shoulder of the swing.

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:15 am
by FTtrader
Transitioning this logic to MQL4 for MetaTrader 4 actually simplifies the data retrieval process compared to MQL5. Because MT4 treats historical price data natively as timeseries arrays (newest bar is index 0), we do not need to use CopyHigh, CopyLow, or ArraySetAsSeries. We can query the higher timeframes directly using iHigh() and iLow().

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:15 am
by FTtrader
This is built as a Custom Indicator (.mq4). Drop it into your MQL4\Indicators folder and compile it in MetaEditor.

Code: Select all

//+------------------------------------------------------------------+
//|                               Liquidity_Sweeps_EQH_EQL_MT4.mq4   |
//|                               Mechanical MT4 Indicator           |
//+------------------------------------------------------------------+
#property copyright "Forex, Stocks & Me"
#property link      "https://forex-scalping.com"
#property version   "1.00"
#property strict
#property indicator_chart_window
#property indicator_plots 0

input int    InpSwingBars      = 5;             // Fractal Swing Length (Bars left & right)
input int    InpHistory        = 500;           // Number of HTF bars to scan
input int    InpPointTolerance = 150;           // Tolerance in Points (150 = 15 pips)
input color  InpColorD1        = clrCrimson;    // D1 EQH/EQL Color
input color  InpColorH4        = clrDarkOrange; // H4 EQH/EQL Color

// Structure to store swing data in memory
struct SwingPoint {
    datetime time;
    double   price;
    bool     used;
};

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit() {
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, "EQH_EQL_");
}

//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[]) {
    
    // Only recalculate when a new M15 bar opens to save CPU cycles
    if(rates_total == prev_calculated) return rates_total;
    
    ObjectsDeleteAll(0, "EQH_EQL_");
    
    MapEqualLiquidity(PERIOD_D1, "D1", InpColorD1, STYLE_SOLID, 2);
    MapEqualLiquidity(PERIOD_H4, "H4", InpColorH4, STYLE_DASHDOT, 1);
    
    ChartRedraw(0);
    return(rates_total);
}

//+------------------------------------------------------------------+
//| Core logic to find and pair EQH / EQL pools                      |
//+------------------------------------------------------------------+
void MapEqualLiquidity(int tf, string prefix, color clr, int style, int width) {
    SwingPoint highs[];
    SwingPoint lows[];
    
    // Step 1: Scan for strict structural extremes using MT4 native iHigh/iLow
    // Index 0 is current forming bar, we scan backwards into history
    for(int i = InpSwingBars; i < InpHistory - InpSwingBars; i++) {
        bool isRes = true;
        bool isSup = true;
        
        for(int j = 1; j <= InpSwingBars; j++) {
            if(iHigh(Symbol(), tf, i) <= iHigh(Symbol(), tf, i-j) || iHigh(Symbol(), tf, i) <= iHigh(Symbol(), tf, i+j)) isRes = false;
            if(iLow(Symbol(), tf, i) >= iLow(Symbol(), tf, i-j) || iLow(Symbol(), tf, i) >= iLow(Symbol(), tf, i+j)) isSup = false;
        }
        
        if(isRes) {
            int s = ArraySize(highs);
            ArrayResize(highs, s + 1);
            highs[s].time = iTime(Symbol(), tf, i);
            highs[s].price = iHigh(Symbol(), tf, i);
            highs[s].used = false;
        }
        if(isSup) {
            int s = ArraySize(lows);
            ArrayResize(lows, s + 1);
            lows[s].time = iTime(Symbol(), tf, i);
            lows[s].price = iLow(Symbol(), tf, i);
            lows[s].used = false;
        }
    }
    
    // MT4 natively provides the Point variable based on broker digits
    double tolerance = InpPointTolerance * Point;
    
    // Step 2 & 3: Find EQH pairs (Double/Triple Tops)
    for(int i = 0; i < ArraySize(highs); i++) {
        if(highs[i].used) continue;
        
        for(int j = i + 1; j < ArraySize(highs); j++) {
            if(!highs[j].used && MathAbs(highs[i].price - highs[j].price) <= tolerance) {
                // Connect older touch (j) to recent touch (i)
                DrawLine("EQH_EQL_" + prefix + "_Res_" + TimeToString(highs[i].time), highs[j].time, highs[j].price, highs[i].time, highs[i].price, clr, style, width);
                highs[i].used = true;
                highs[j].used = true;
                break;
            }
        }
    }
    
    // Step 4: Find EQL pairs (Double/Triple Bottoms)
    for(int i = 0; i < ArraySize(lows); i++) {
        if(lows[i].used) continue;
        
        for(int j = i + 1; j < ArraySize(lows); j++) {
            if(!lows[j].used && MathAbs(lows[i].price - lows[j].price) <= tolerance) {
                DrawLine("EQH_EQL_" + prefix + "_Sup_" + TimeToString(lows[i].time), lows[j].time, lows[j].price, lows[i].time, lows[i].price, clr, style, width);
                lows[i].used = true;
                lows[j].used = true;
                break;
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Renders a trendline connecting the touches, extended right       |
//+------------------------------------------------------------------+
void DrawLine(string name, datetime t1, double p1, datetime t2, double p2, color clr, int style, int width) {
    if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {
        ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
        ObjectSetInteger(0, name, OBJPROP_BACK, true);         
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);  
        ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
    }
}
//+------------------------------------------------------------------+

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:16 am
by FTtrader
MT4-Specific Mechanics

The Point Variable: Unlike MQL5 which requires calling SymbolInfoDouble(), MQL4 automatically assigns the Point variable per chart. On IC Markets MT4 (5-digit broker), Point equals 0.00001. Inputting 150 into InpPointTolerance cleanly translates to 15 pips.

Time Series Indexing: iHigh(), iLow(), and iTime() automatically pull data backward in time. i = 0 is the active candle, and i = 10 is ten candles ago. The loop logic handles this natively without needing ArraySetAsSeries().

Execution Limitations: This is purely a visual indicator. If you want this to auto-execute your exponential risk management model on MT4 the way the cBot does, you must compile it as an Expert Advisor (.mq4 placed in the Experts folder) and use MT4's OrderSend() function instead of drawing lines.

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:17 am
by FTtrader
Since we already built the MQL5 Indicator earlier in this thread, bringing "that" back to MT5 means upgrading from a simple visual indicator to a full-blown Expert Advisor (EA).

This will take all the advanced logic we just engineered for the cBot—the Exponential Risk Engine, Partial Profit Closures, Breakeven Stop, Trailing Stop, and Auto-Execution—and translate it directly into MQL5 object-oriented code.

Because MT5 handles partial closures and tick-events differently than cTrader, I have implemented an array-based state tracker (_partiallyClosedTickets) to ensure the EA doesn't get caught in an infinite loop when closing 50% of your position at the 1:1 target.

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:17 am
by FTtrader
Save this in your MQL5\Experts folder (not the Indicators folder) and compile it as LiquiditySweeps_Master_EA.mq5.

Code: Select all

//+------------------------------------------------------------------+
//|                                     LiquiditySweeps_Master_EA.mq5|
//|                                     Mechanical MT5 Auto-Exec EA  |
//+------------------------------------------------------------------+
#property copyright "Forex, Stocks & Me"
#property link      "https://forex-scalping.com"
#property version   "1.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

// === INPUTS ===
sinput string grp1 = "--- Structural Mapping ---";
input int    InpSwingBars      = 5;             // Fractal Swing (Bars left & right)
input int    InpHistory        = 500;           // HTF Bars to scan
input double InpTolerancePips  = 15.0;          // EQH/EQL Tolerance (Pips)

sinput string grp2 = "--- Exponential Risk Engine ---";
input bool   InpUseExponential = true;          // Use Accrued Profit Scaling?
input double InpStartingBalance= 1000.0;        // Series Starting Balance
input double InpBaseRisk       = 20.0;          // Base Risk (Currency)
input double InpProfitScalePct = 50.0;          // Profit Reinvestment (%)
input bool   InpLockPrincipal  = true;          // Lock Principal (Risk ONLY Profits)
input double InpStaticLots     = 1.0;           // Static Fallback Volume

sinput string grp3 = "--- Execution Rules ---";
input double InpStopLossPips   = 10.0;          // Stop Loss (Pips)
input double InpTakeProfitPips = 50.0;          // Take Profit (Pips)
input double InpMaxSpreadPips  = 1.5;           // Max Spread (Pips)

sinput string grp4 = "--- Trade Management (BE, Trail, Partial) ---";
input double InpManagementRR   = 1.0;           // Trigger Management at R:R
input bool   InpUsePartial     = true;          // Take Partial Profit?
input double InpPartialPct     = 50.0;          // Partial Profit (%)
input bool   InpUseBreakeven   = true;          // Move to Breakeven?
input double InpCommBufferPips = 0.6;           // Commission Buffer (Pips)
input bool   InpUseTrailing    = true;          // Use Trailing Stop after BE?
input double InpTrailDistPips  = 15.0;          // Trailing Distance (Pips)
input double InpTrailStepPips  = 2.0;           // Trailing Step (Pips)

input ulong  InpMagicNumber    = 888888;        // EA Magic Number

// === GLOBALS & CLASSES ===
CTrade         trade;
CPositionInfo  pos;
ulong          partiallyClosedTickets[];        // State tracker to prevent infinite loops

struct SwingPoint {
    datetime time;
    double   price;
    bool     used;
};

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(InpMagicNumber);
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, "EQH_EQL_");
}

//+------------------------------------------------------------------+
//| Helper: Dynamic Pip Sizing for 5-Digit Brokers (IC Markets)      |
//+------------------------------------------------------------------+
double GetPipSize() {
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    return (digits == 5 || digits == 3) ? _Point * 10.0 : _Point;
}

//+------------------------------------------------------------------+
//| Helper: State Tracking for Partial Closes                        |
//+------------------------------------------------------------------+
bool IsPartiallyClosed(ulong ticket) {
    for(int i = 0; i < ArraySize(partiallyClosedTickets); i++) {
        if(partiallyClosedTickets[i] == ticket) return true;
    }
    return false;
}
void AddPartiallyClosed(ulong ticket) {
    int s = ArraySize(partiallyClosedTickets);
    ArrayResize(partiallyClosedTickets, s + 1);
    partiallyClosedTickets[s] = ticket;
}

//+------------------------------------------------------------------+
//| Core Tick Loop (Trade Management)                                |
//+------------------------------------------------------------------+
void OnTick() {
    ManageActivePositions();
    
    // Check for new M15 Bar for mapping and entry logic
    static datetime last_bar = 0;
    datetime curr_bar = (datetime)SeriesInfoInteger(_Symbol, PERIOD_M15, SERIES_LASTBAR_DATE);
    if(curr_bar != last_bar) {
        last_bar = curr_bar;
        ProcessM15BarClose();
    }
}

//+------------------------------------------------------------------+
//| Trade Management Module (Tick by Tick)                           |
//+------------------------------------------------------------------+
void ManageActivePositions() {
    double pip = GetPipSize();
    double trigger_dist = InpStopLossPips * InpManagementRR * pip;
    
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        if(pos.SelectByIndex(i) && pos.Symbol() == _Symbol && pos.Magic() == InpMagicNumber) {
            
            double open_price = pos.PriceOpen();
            double current_sl = pos.StopLoss();
            ulong ticket = pos.Ticket();
            
            double profit_dist = 0;
            if(pos.PositionType() == POSITION_TYPE_BUY)  profit_dist = SymbolInfoDouble(_Symbol, SYMBOL_BID) - open_price;
            if(pos.PositionType() == POSITION_TYPE_SELL) profit_dist = open_price - SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            
            // Activate management when R:R target is hit
            if(profit_dist >= trigger_dist) {
                
                // 1. Partial Close
                if(InpUsePartial && !IsPartiallyClosed(ticket)) {
                    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                    double min_vol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
                    double vol_to_close = MathFloor((pos.Volume() * (InpPartialPct / 100.0)) / step) * step;
                    
                    if(vol_to_close >= min_vol && pos.Volume() > vol_to_close) {
                        if(trade.PositionClosePartial(ticket, vol_to_close)) {
                            AddPartiallyClosed(ticket); // Lock to prevent loop
                            Print("Partial 50% closed for ticket: ", ticket);
                        }
                    }
                }
                
                // 2. Breakeven & Trailing Stop
                if(InpUseBreakeven) {
                    double comm_buffer = InpCommBufferPips * pip;
                    double be_sl = (pos.PositionType() == POSITION_TYPE_BUY) ? open_price + comm_buffer : open_price - comm_buffer;
                    double target_sl = be_sl;
                    
                    if(InpUseTrailing) {
                        double trail_sl = (pos.PositionType() == POSITION_TYPE_BUY) 
                                          ? SymbolInfoDouble(_Symbol, SYMBOL_BID) - (InpTrailDistPips * pip)
                                          : SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (InpTrailDistPips * pip);
                                          
                        target_sl = (pos.PositionType() == POSITION_TYPE_BUY) ? MathMax(target_sl, trail_sl) : MathMin(target_sl, trail_sl);
                    }
                    
                    double step_buffer = InpTrailStepPips * pip;
                    bool requires_update = false;
                    
                    if(pos.PositionType() == POSITION_TYPE_BUY && (current_sl == 0 || target_sl > current_sl + step_buffer)) requires_update = true;
                    if(pos.PositionType() == POSITION_TYPE_SELL && (current_sl == 0 || target_sl < current_sl - step_buffer)) requires_update = true;
                    
                    if(requires_update) trade.PositionModify(ticket, NormalizeDouble(target_sl, _Digits), pos.TakeProfit());
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Structural Mapping & Entry Module (M15 Bar Close)                |
//+------------------------------------------------------------------+
void ProcessM15BarClose() {
    ObjectsDeleteAll(0, "EQH_EQL_");
    
    double activeEQH[], activeEQL[];
    MapEqualLiquidity(PERIOD_D1, "D1", clrCrimson, STYLE_SOLID, 2, activeEQH, activeEQL);
    MapEqualLiquidity(PERIOD_H4, "H4", clrDarkOrange, STYLE_DASHDOT, 1, activeEQH, activeEQL);
    ChartRedraw(0);
    
    // Spread Check Circuit Breaker
    double spread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / GetPipSize();
    if(spread > InpMaxSpreadPips) return;
    
    // Ensure we don't open multiple trades for the same setup
    if(PositionsTotal() > 0) return;
    
    // M15 execution trigger (Index 1 is the recently closed bar)
    double o = iOpen(_Symbol, PERIOD_M15, 1);
    double h = iHigh(_Symbol, PERIOD_M15, 1);
    double l = iLow(_Symbol, PERIOD_M15, 1);
    double c = iClose(_Symbol, PERIOD_M15, 1);
    
    double volume = GetDynamicVolume();
    if(volume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) return;
    
    double pip = GetPipSize();
    
    // Check Short Sweeps
    for(int i = 0; i < ArraySize(activeEQH); i++) {
        if(o < activeEQH[i] && h > activeEQH[i] && c < activeEQH[i]) {
            double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            trade.Sell(volume, _Symbol, bid, bid + (InpStopLossPips * pip), bid - (InpTakeProfitPips * pip), "LiqSweep");
            return;
        }
    }
    
    // Check Long Sweeps
    for(int i = 0; i < ArraySize(activeEQL); i++) {
        if(o > activeEQL[i] && l < activeEQL[i] && c > activeEQL[i]) {
            double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            trade.Buy(volume, _Symbol, ask, ask - (InpStopLossPips * pip), ask + (InpTakeProfitPips * pip), "LiqSweep");
            return;
        }
    }
}

//+------------------------------------------------------------------+
//| Exponential Risk Engine Calculation                              |
//+------------------------------------------------------------------+
double GetDynamicVolume() {
    double risk_currency = InpBaseRisk;
    
    if(InpUseExponential) {
        double accrued_profit = AccountInfoDouble(ACCOUNT_EQUITY) - InpStartingBalance;
        if(accrued_profit > 0) {
            risk_currency = InpLockPrincipal 
                            ? (accrued_profit * (InpProfitScalePct / 100.0))
                            : InpBaseRisk + (accrued_profit * (InpProfitScalePct / 100.0));
        }
    } else {
        return InpStaticLots;
    }
    
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tick_size  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double pip_val_per_lot = tick_value * (GetPipSize() / tick_size);
    
    double calculated_vol = risk_currency / (InpStopLossPips * pip_val_per_lot);
    
    // Floor to nearest lot step (e.g. 0.01)
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    return MathFloor(calculated_vol / step) * step;
}

//+------------------------------------------------------------------+
//| HTF Liquidity Mapper                                             |
//+------------------------------------------------------------------+
void MapEqualLiquidity(ENUM_TIMEFRAMES tf, string prefix, color clr, int style, int width, double &outEQH[], double &outEQL[]) {
    double h[], l[];
    datetime t[];
    
    ArraySetAsSeries(h, true); ArraySetAsSeries(l, true); ArraySetAsSeries(t, true);
    
    if(CopyHigh(_Symbol, tf, 0, InpHistory, h) < InpHistory) return;
    if(CopyLow(_Symbol, tf, 0, InpHistory, l) < InpHistory) return;
    if(CopyTime(_Symbol, tf, 0, InpHistory, t) < InpHistory) return;
    
    SwingPoint highs[], lows[];
    
    for(int i = InpSwingBars; i < InpHistory - InpSwingBars; i++) {
        bool isRes = true, isSup = true;
        for(int j = 1; j <= InpSwingBars; j++) {
            if(h[i] <= h[i-j] || h[i] <= h[i+j]) isRes = false;
            if(l[i] >= l[i-j] || l[i] >= l[i+j]) isSup = false;
        }
        
        if(isRes) { int s = ArraySize(highs); ArrayResize(highs, s+1); highs[s].time = t[i]; highs[s].price = h[i]; highs[s].used = false; }
        if(isSup) { int s = ArraySize(lows);  ArrayResize(lows, s+1);  lows[s].time  = t[i]; lows[s].price = l[i]; lows[s].used = false; }
    }
    
    double tolerance = InpTolerancePips * GetPipSize();
    
    for(int i = 0; i < ArraySize(highs); i++) {
        if(highs[i].used) continue;
        for(int j = i + 1; j < ArraySize(highs); j++) {
            if(!highs[j].used && MathAbs(highs[i].price - highs[j].price) <= tolerance) {
                DrawLine("EQH_" + prefix + "_" + TimeToString(highs[i].time), highs[j].time, highs[j].price, highs[i].time, highs[i].price, clr, style, width);
                int s = ArraySize(outEQH); ArrayResize(outEQH, s+1); outEQH[s] = highs[i].price;
                highs[i].used = true; highs[j].used = true;
                break;
            }
        }
    }
    
    for(int i = 0; i < ArraySize(lows); i++) {
        if(lows[i].used) continue;
        for(int j = i + 1; j < ArraySize(lows); j++) {
            if(!lows[j].used && MathAbs(lows[i].price - lows[j].price) <= tolerance) {
                DrawLine("EQL_" + prefix + "_" + TimeToString(lows[i].time), lows[j].time, lows[j].price, lows[i].time, lows[i].price, clr, style, width);
                int s = ArraySize(outEQL); ArrayResize(outEQL, s+1); outEQL[s] = lows[i].price;
                lows[i].used = true; lows[j].used = true;
                break;
            }
        }
    }
}

void DrawLine(string name, datetime t1, double p1, datetime t2, double p2, color clr, int style, int width) {
    if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {
        ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true);
        ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
        ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
        ObjectSetInteger(0, name, OBJPROP_BACK, true);         
        ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);  
        ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
    }
}
//+------------------------------------------------------------------+

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:18 am
by FTtrader
To upgrade this into an institutional-grade, "Pro" Expert Advisor, we need to add the three protective layers that separate retail traders from funded professionals and algorithmic desks:

Prop-Firm Style Daily Drawdown Limit (Circuit Breaker): If market chop chips away at your capital, the EA calculates your total realized + floating PnL for the current day. If it hits your maximum daily loss percentage, the EA hard-stops all new entries until midnight.

Session Filters: Liquidity sweeps only work when volume is present. The Pro version includes strict broker-time trading windows so you only execute during the London or New York overlap, ignoring the dead Asian session.

On-Chart Telemetry HUD: A real-time dashboard printed directly on your chart so you can instantly monitor your Daily PnL, active Risk Mode, Spread, and whether the Session/Drawdown filters are blocking trades.

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:18 am
by FTtrader
Here is the upgraded LiquiditySweeps_PRO_EA.mq5.

Code: Select all

//+------------------------------------------------------------------+
//|                                     LiquiditySweeps_PRO_EA.mq5   |
//|                                     Institutional Auto-Exec EA   |
//+------------------------------------------------------------------+
#property copyright "Forex, Stocks & Me"
#property link      "https://forex-scalping.com"
#property version   "2.00"

#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\HistoryOrderInfo.mqh>

// === INPUTS ===
sinput string grp1 = "--- Structural Mapping ---";
input int    InpSwingBars      = 5;             // Fractal Swing (Bars left & right)
input int    InpHistory        = 500;           // HTF Bars to scan
input double InpTolerancePips  = 15.0;          // EQH/EQL Tolerance (Pips)

sinput string grp2 = "--- Exponential Risk Engine ---";
input bool   InpUseExponential = true;          // Use Accrued Profit Scaling?
input double InpStartingBalance= 1000.0;        // Series Starting Balance
input double InpBaseRisk       = 20.0;          // Base Risk (Currency)
input double InpProfitScalePct = 50.0;          // Profit Reinvestment (%)
input bool   InpLockPrincipal  = true;          // Lock Principal?
input double InpStaticLots     = 1.0;           // Static Fallback Volume

sinput string grp3 = "--- Execution Rules ---";
input double InpStopLossPips   = 10.0;          // Stop Loss (Pips)
input double InpTakeProfitPips = 50.0;          // Take Profit (Pips)
input double InpMaxSpreadPips  = 1.5;           // Max Spread (Pips)
input ulong  InpMaxSlippagePts = 30;            // Max Slippage (Points)

sinput string grp4 = "--- Pro Circuit Breakers & Sessions ---";
input double InpMaxDailyLossPct= 2.0;           // Max Daily Loss (%) - Halts EA
input string InpSessionStart   = "14:00";       // Trading Start (Broker Time)
input string InpSessionEnd     = "18:00";       // Trading End (Broker Time)

sinput string grp5 = "--- Trade Management (BE, Trail, Partial) ---";
input double InpManagementRR   = 1.0;           // Trigger Management at R:R
input bool   InpUsePartial     = true;          // Take Partial Profit?
input double InpPartialPct     = 50.0;          // Partial Profit (%)
input bool   InpUseBreakeven   = true;          // Move to Breakeven?
input double InpCommBufferPips = 0.6;           // Commission Buffer (Pips)
input bool   InpUseTrailing    = true;          // Use Trailing Stop after BE?
input double InpTrailDistPips  = 15.0;          // Trailing Distance (Pips)
input double InpTrailStepPips  = 2.0;           // Trailing Step (Pips)

input ulong  InpMagicNumber    = 888899;        // EA Magic Number

// === GLOBALS & CLASSES ===
CTrade         trade;
CPositionInfo  pos;
ulong          partiallyClosedTickets[];        // State tracker
double         startOfDayBalance = 0;           // For Daily Drawdown Tracker

struct SwingPoint {
    datetime time;
    double   price;
    bool     used;
};

//+------------------------------------------------------------------+
//| Initialization                                                   |
//+------------------------------------------------------------------+
int OnInit() {
    trade.SetExpertMagicNumber(InpMagicNumber);
    trade.SetDeviationInPoints(InpMaxSlippagePts); // Pro slippage control
    startOfDayBalance = AccountInfoDouble(ACCOUNT_BALANCE);
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) {
    ObjectsDeleteAll(0, "EQH_EQL_");
    Comment(""); // Clear Telemetry HUD
}

//+------------------------------------------------------------------+
//| Helper: Dynamic Pip Sizing                                       |
//+------------------------------------------------------------------+
double GetPipSize() {
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    return (digits == 5 || digits == 3) ? _Point * 10.0 : _Point;
}

//+------------------------------------------------------------------+
//| Core Tick Loop                                                   |
//+------------------------------------------------------------------+
void OnTick() {
    // 1. Check if a new day started to reset the Daily Drawdown balance
    static int current_day = -1;
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    if(dt.day_of_year != current_day) {
        current_day = dt.day_of_year;
        startOfDayBalance = AccountInfoDouble(ACCOUNT_BALANCE);
        ArrayResize(partiallyClosedTickets, 0); // Clear partial state tracker
    }
    
    // 2. Active Trade Management
    ManageActivePositions();
    
    // 3. Update HUD
    UpdateTelemetryHUD();
    
    // 4. M15 Bar Close Mapping & Entry
    static datetime last_bar = 0;
    datetime curr_bar = (datetime)SeriesInfoInteger(_Symbol, PERIOD_M15, SERIES_LASTBAR_DATE);
    if(curr_bar != last_bar) {
        last_bar = curr_bar;
        ProcessM15BarClose();
    }
}

//+------------------------------------------------------------------+
//| Pro Circuit Breaker: Max Daily Loss Tracker                      |
//+------------------------------------------------------------------+
bool IsDailyDrawdownHit() {
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    double dailyPnL = currentEquity - startOfDayBalance;
    double maxLossAllowed = startOfDayBalance * (InpMaxDailyLossPct / 100.0);
    
    // If daily PnL is negative and exceeds our max loss threshold
    if(dailyPnL < 0 && MathAbs(dailyPnL) >= maxLossAllowed) return true;
    return false;
}

//+------------------------------------------------------------------+
//| Pro Circuit Breaker: Session Window                              |
//+------------------------------------------------------------------+
bool IsWithinTradingSession() {
    MqlDateTime dt;
    TimeToStruct(TimeCurrent(), dt);
    
    int currentMins = dt.hour * 60 + dt.min;
    
    string startArr[], endArr[];
    StringSplit(InpSessionStart, ':', startArr);
    StringSplit(InpSessionEnd, ':', endArr);
    
    int startMins = (int)StringToInteger(startArr[0]) * 60 + (int)StringToInteger(startArr[1]);
    int endMins   = (int)StringToInteger(endArr[0]) * 60 + (int)StringToInteger(endArr[1]);
    
    return (currentMins >= startMins && currentMins <= endMins);
}

//+------------------------------------------------------------------+
//| Telemetry HUD                                                    |
//+------------------------------------------------------------------+
void UpdateTelemetryHUD() {
    double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
    double dailyPnL = currentEquity - startOfDayBalance;
    double spread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / GetPipSize();
    
    string ddStatus = IsDailyDrawdownHit() ? "[HALTED: Daily Limit Hit]" : "[OK]";
    string sessionStatus = IsWithinTradingSession() ? "ACTIVE" : "WAITING";
    string riskMode = InpUseExponential ? (InpLockPrincipal ? "EXPONENTIAL (Locked)" : "EXPONENTIAL (Compounding)") : "STATIC";
    
    string hud = "\n=== PRO TELEMETRY ===\n" +
                 "Risk Mode: " + riskMode + "\n" +
                 "Session: " + sessionStatus + "\n" +
                 "Spread: " + DoubleToString(spread, 1) + " pips\n" +
                 "Daily PnL: " + DoubleToString(dailyPnL, 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + " " + ddStatus + "\n" +
                 "Active Target R:R: 1:" + DoubleToString(InpTakeProfitPips/InpStopLossPips, 1);
                 
    Comment(hud);
}

//+------------------------------------------------------------------+
//| Trade Management Module (BE, Trail, Partial)                     |
//+------------------------------------------------------------------+
void ManageActivePositions() {
    double pip = GetPipSize();
    double trigger_dist = InpStopLossPips * InpManagementRR * pip;
    
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        if(pos.SelectByIndex(i) && pos.Symbol() == _Symbol && pos.Magic() == InpMagicNumber) {
            
            double open_price = pos.PriceOpen();
            double current_sl = pos.StopLoss();
            ulong ticket = pos.Ticket();
            
            double profit_dist = (pos.PositionType() == POSITION_TYPE_BUY)  
                ? SymbolInfoDouble(_Symbol, SYMBOL_BID) - open_price
                : open_price - SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            
            if(profit_dist >= trigger_dist) {
                // 1. Partial Close
                bool isPartiallyClosed = false;
                for(int j=0; j<ArraySize(partiallyClosedTickets); j++) { if(partiallyClosedTickets[j]==ticket) isPartiallyClosed = true; }
                
                if(InpUsePartial && !isPartiallyClosed) {
                    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                    double vol_to_close = MathFloor((pos.Volume() * (InpPartialPct / 100.0)) / step) * step;
                    
                    if(vol_to_close >= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN) && pos.Volume() > vol_to_close) {
                        if(trade.PositionClosePartial(ticket, vol_to_close)) {
                            int s = ArraySize(partiallyClosedTickets);
                            ArrayResize(partiallyClosedTickets, s + 1);
                            partiallyClosedTickets[s] = ticket;
                            Print("PRO EXECUTION: Partial 50% closed at Target R:R.");
                        }
                    }
                }
                
                // 2. Breakeven & Trailing Stop
                if(InpUseBreakeven) {
                    double comm_buffer = InpCommBufferPips * pip;
                    double be_sl = (pos.PositionType() == POSITION_TYPE_BUY) ? open_price + comm_buffer : open_price - comm_buffer;
                    double target_sl = be_sl;
                    
                    if(InpUseTrailing) {
                        double trail_sl = (pos.PositionType() == POSITION_TYPE_BUY) 
                                          ? SymbolInfoDouble(_Symbol, SYMBOL_BID) - (InpTrailDistPips * pip)
                                          : SymbolInfoDouble(_Symbol, SYMBOL_ASK) + (InpTrailDistPips * pip);
                        target_sl = (pos.PositionType() == POSITION_TYPE_BUY) ? MathMax(target_sl, trail_sl) : MathMin(target_sl, trail_sl);
                    }
                    
                    double step_buffer = InpTrailStepPips * pip;
                    bool requires_update = false;
                    
                    if(pos.PositionType() == POSITION_TYPE_BUY && (current_sl == 0 || target_sl > current_sl + step_buffer)) requires_update = true;
                    if(pos.PositionType() == POSITION_TYPE_SELL && (current_sl == 0 || target_sl < current_sl - step_buffer)) requires_update = true;
                    
                    if(requires_update) trade.PositionModify(ticket, NormalizeDouble(target_sl, _Digits), pos.TakeProfit());
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Structural Mapping & Entry Module (M15 Bar Close)                |
//+------------------------------------------------------------------+
void ProcessM15BarClose() {
    ObjectsDeleteAll(0, "EQH_EQL_");
    
    double activeEQH[], activeEQL[];
    MapEqualLiquidity(PERIOD_D1, "D1", clrCrimson, STYLE_SOLID, 2, activeEQH, activeEQL);
    MapEqualLiquidity(PERIOD_H4, "H4", clrDarkOrange, STYLE_DASHDOT, 1, activeEQH, activeEQL);
    ChartRedraw(0);
    
    // --- PRO CIRCUIT BREAKERS ---
    if(IsDailyDrawdownHit()) return;
    if(!IsWithinTradingSession()) return;
    
    double spread = (SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID)) / GetPipSize();
    if(spread > InpMaxSpreadPips) return;
    if(PositionsTotal() > 0) return;
    
    double o = iOpen(_Symbol, PERIOD_M15, 1);
    double h = iHigh(_Symbol, PERIOD_M15, 1);
    double l = iLow(_Symbol, PERIOD_M15, 1);
    double c = iClose(_Symbol, PERIOD_M15, 1);
    
    double volume = GetDynamicVolume();
    if(volume < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) return;
    
    double pip = GetPipSize();
    
    for(int i = 0; i < ArraySize(activeEQH); i++) {
        if(o < activeEQH[i] && h > activeEQH[i] && c < activeEQH[i]) {
            double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            trade.Sell(volume, _Symbol, bid, bid + (InpStopLossPips * pip), bid - (InpTakeProfitPips * pip), "PRO_Sweep");
            return;
        }
    }
    
    for(int i = 0; i < ArraySize(activeEQL); i++) {
        if(o > activeEQL[i] && l < activeEQL[i] && c > activeEQL[i]) {
            double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            trade.Buy(volume, _Symbol, ask, ask - (InpStopLossPips * pip), ask + (InpTakeProfitPips * pip), "PRO_Sweep");
            return;
        }
    }
}

//+------------------------------------------------------------------+
//| Exponential Risk Engine Calculation                              |
//+------------------------------------------------------------------+
double GetDynamicVolume() {
    double risk_currency = InpBaseRisk;
    
    if(InpUseExponential) {
        double accrued_profit = AccountInfoDouble(ACCOUNT_EQUITY) - InpStartingBalance;
        if(accrued_profit > 0) {
            risk_currency = InpLockPrincipal 
                            ? (accrued_profit * (InpProfitScalePct / 100.0))
                            : InpBaseRisk + (accrued_profit * (InpProfitScalePct / 100.0));
        }
    } else return InpStaticLots;
    
    double tick_value = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tick_size  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double pip_val_per_lot = tick_value * (GetPipSize() / tick_size);
    
    double calculated_vol = risk_currency / (InpStopLossPips * pip_val_per_lot);
    double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    return MathFloor(calculated_vol / step) * step;
}

//+------------------------------------------------------------------+
//| HTF Liquidity Mapper                                             |
//+------------------------------------------------------------------+
void MapEqualLiquidity(ENUM_TIMEFRAMES tf, string prefix, color clr, int style, int width, double &outEQH[], double &outEQL[]) {
    double h[], l[];
    datetime t[];
    ArraySetAsSeries(h, true); ArraySetAsSeries(l, true); ArraySetAsSeries(t, true);
    
    if(CopyHigh(_Symbol, tf, 0, InpHistory, h) < InpHistory || CopyLow(_Symbol, tf, 0, InpHistory, l) < InpHistory || CopyTime(_Symbol, tf, 0, InpHistory, t) < InpHistory) return;
    
    SwingPoint highs[], lows[];
    for(int i = InpSwingBars; i < InpHistory - InpSwingBars; i++) {
        bool isRes = true, isSup = true;
        for(int j = 1; j <= InpSwingBars; j++) {
            if(h[i] <= h[i-j] || h[i] <= h[i+j]) isRes = false;
            if(l[i] >= l[i-j] || l[i] >= l[i+j]) isSup = false;
        }
        if(isRes) { int s = ArraySize(highs); ArrayResize(highs, s+1); highs[s].time = t[i]; highs[s].price = h[i]; highs[s].used = false; }
        if(isSup) { int s = ArraySize(lows);  ArrayResize(lows, s+1);  lows[s].time  = t[i]; lows[s].price = l[i]; lows[s].used = false; }
    }
    
    double tolerance = InpTolerancePips * GetPipSize();
    for(int i = 0; i < ArraySize(highs); i++) {
        if(highs[i].used) continue;
        for(int j = i + 1; j < ArraySize(highs); j++) {
            if(!highs[j].used && MathAbs(highs[i].price - highs[j].price) <= tolerance) {
                DrawLine("EQH_" + prefix + "_" + TimeToString(highs[i].time), highs[j].time, highs[j].price, highs[i].time, highs[i].price, clr, style, width);
                int s = ArraySize(outEQH); ArrayResize(outEQH, s+1); outEQH[s] = highs[i].price;
                highs[i].used = true; highs[j].used = true; break;
            }
        }
    }
    for(int i = 0; i < ArraySize(lows); i++) {
        if(lows[i].used) continue;
        for(int j = i + 1; j < ArraySize(lows); j++) {
            if(!lows[j].used && MathAbs(lows[i].price - lows[j].price) <= tolerance) {
                DrawLine("EQL_" + prefix + "_" + TimeToString(lows[i].time), lows[j].time, lows[j].price, lows[i].time, lows[i].price, clr, style, width);
                int s = ArraySize(outEQL); ArrayResize(outEQL, s+1); outEQL[s] = lows[i].price;
                lows[i].used = true; lows[j].used = true; break;
            }
        }
    }
}
void DrawLine(string name, datetime t1, double p1, datetime t2, double p2, color clr, int style, int width) {
    if(ObjectCreate(0, name, OBJ_TREND, 0, t1, p1, t2, p2)) {
        ObjectSetInteger(0, name, OBJPROP_RAY_RIGHT, true); ObjectSetInteger(0, name, OBJPROP_COLOR, clr); ObjectSetInteger(0, name, OBJPROP_STYLE, style);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_BACK, true); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);  
        ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
    }
}
//+------------------------------------------------------------------+

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:19 am
by FTtrader
Pro Upgrades Explained

Drawdown Halt Mechanism: IsDailyDrawdownHit() tracks the ACCOUNT_BALANCE at midnight and compares it against your live equity throughout the day. If your floating/closed losses cross the InpMaxDailyLossPct threshold (e.g., -2.0%), it shuts off the execution engine until the next day starts, protecting your capital from tilt or a chopping market.

Session Boundary Control: The IsWithinTradingSession() function enforces a strict time window (default 14:00 to 18:00 Broker Time, which aligns with the New York overlap). Sweeps that trigger outside of high-liquidity windows are completely ignored.

Telemetry HUD: Using the Comment() function, the EA now actively paints a clean readout on the top left of your M15 chart showing your exact active risk state, spread, daily PnL, and whether your session is active or halted by a circuit breaker.

Re: Checklist fatigue: how I kept my pre-trade list to five lines

Posted: Wed Oct 28, 2026 9:25 am
by FTtrader
To elevate this Expert Advisor from a retail script to a quantitative, institutional-grade execution engine (v3.00 Quant), six architectural flaws common to basic EAs have been re-engineered:

Unmitigated (Virgin) Liquidity Validation: Previous versions paired historical swings without checking what happened afterward. The engine now validates that price never broke the level between the two touches and never swept the level in the historical bars following the second touch. Once an M15 bar sweeps a pool, that level is locked as mitigated so it can never trigger duplicate trades.

Native MQL5 Economic Calendar Filter (MqlCalendarValue): Directly enforces Item #1 of the checklist ("News: nothing red in the next 15 minutes?"). The EA queries the native MetaTrader 5 economic calendar database for high-impact events affecting the active pair's base or quote currency and halts execution before and after the release.