Advertisement IC Markets

Continuation only after a higher-low reclaim — my simple filter

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

cTrader cAlgo Implementation (.NET C#)

Code: Select all

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class StructuralReclaimPro : Robot
    {
        // =========================================================================
        // 1. PARAMETERS
        // =========================================================================
        [Parameter("Risk per Trade (%)", DefaultValue = 1.0, MinValue = 0.1, Step = 0.1, Group = "1. Risk Management")]
        public double RiskPct { get; set; }

        [Parameter("Risk/Reward Ratio", DefaultValue = 2.0, MinValue = 0.5, Step = 0.5, Group = "1. Risk Management")]
        public double RrRatio { get; set; }

        [Parameter("Pivot Left (Impulse Leg)", DefaultValue = 5, MinValue = 1, Group = "2. Microstructure")]
        public int LeftBars { get; set; }

        [Parameter("Pivot Right (Confirmation)", DefaultValue = 2, MinValue = 1, Group = "2. Microstructure")]
        public int RightBars { get; set; }

        [Parameter("HTF Resolution", DefaultValue = "Hour4", Group = "3. HTF Context")]
        public TimeFrame HtfResolution { get; set; }

        [Parameter("Start Hour (UTC)", DefaultValue = 8, Group = "4. Session")]
        public int SessionStart { get; set; }

        [Parameter("End Hour (UTC)", DefaultValue = 17, Group = "4. Session")]
        public int SessionEnd { get; set; }

        // =========================================================================
        // STATE & INDICATORS
        // =========================================================================
        private Bars _htfBars;
        private ExponentialMovingAverage _htfEma;
        
        private SetupState _longSetup = new SetupState();
        private SetupState _shortSetup = new SetupState();

        private double _lastPl = double.NaN;
        private double _prevPl = double.NaN;
        private double _lastPh = double.NaN;
        private double _prevPh = double.NaN;

        private const string TriggerLineName = "ReclaimTriggerLine";
        private const string SlLineName = "ReclaimSlLine";

        // =========================================================================
        // INITIALIZATION
        // =========================================================================
        protected override void OnStart()
        {
            // Initialize HTF Data and EMA
            _htfBars = MarketData.GetBars(HtfResolution);
            _htfEma = Indicators.ExponentialMovingAverage(_htfBars.ClosePrices, 20);
        }

        // =========================================================================
        // CORE LOGIC (Evaluated on every closed M15 bar)
        // =========================================================================
        protected override void OnBar()
        {
            UpdateStructureAndState();
            CheckExecutionOrInvalidation();
            UpdateVisuals();
        }

        private void UpdateStructureAndState()
        {
            int checkIndex = Bars.Count - 1 - RightBars;
            if (checkIndex - LeftBars < 0) return;

            bool isPivotLow = true;
            bool isPivotHigh = true;
            double highestInRight = double.MinValue;
            double lowestInRight = double.MaxValue;

            // 1. Determine if the checkIndex is a valid pivot
            for (int i = checkIndex - LeftBars; i <= checkIndex + RightBars; i++)
            {
                if (i != checkIndex)
                {
                    if (Bars.LowPrices[i] <= Bars.LowPrices[checkIndex]) isPivotLow = false;
                    if (Bars.HighPrices[i] >= Bars.HighPrices[checkIndex]) isPivotHigh = false;
                }
                
                // Track the micro-extremes during the RightBars confirmation period
                if (i > checkIndex)
                {
                    if (Bars.HighPrices[i] > highestInRight) highestInRight = Bars.HighPrices[i];
                    if (Bars.LowPrices[i] < lowestInRight) lowestInRight = Bars.LowPrices[i];
                }
            }

            // 2. Get HTF Bias (synced to current time to avoid lookahead bias)
            int htfIndex = _htfBars.OpenTimes.GetIndexByTime(Bars.OpenTimes.LastValue);
            double currentHtfEma = _htfEma.Result[htfIndex];
            bool isBullishHtf = Bars.ClosePrices.Last(1) > currentHtfEma;
            bool isBearishHtf = Bars.ClosePrices.Last(1) < currentHtfEma;

            // 3. Update State Machines
            if (isPivotLow)
            {
                double currentPl = Bars.LowPrices[checkIndex];
                _prevPl = double.IsNaN(_lastPl) ? currentPl : _lastPl;
                _lastPl = currentPl;

                if (_lastPl > _prevPl && isBullishHtf)
                {
                    _longSetup.Activate(currentPl, highestInRight, Bars.OpenTimes[checkIndex]);
                    _shortSetup.Deactivate();
                }
            }

            if (isPivotHigh)
            {
                double currentPh = Bars.HighPrices[checkIndex];
                _prevPh = double.IsNaN(_lastPh) ? currentPh : _lastPh;
                _lastPh = currentPh;

                if (_lastPh < _prevPh && isBearishHtf)
                {
                    _shortSetup.Activate(currentPh, lowestInRight, Bars.OpenTimes[checkIndex]);
                    _longSetup.Deactivate();
                }
            }
        }

        private void CheckExecutionOrInvalidation()
        {
            double close = Bars.ClosePrices.Last(1);
            double high = Bars.HighPrices.Last(1);
            double low = Bars.LowPrices.Last(1);
            bool inSession = Server.Time.Hour >= SessionStart && Server.Time.Hour < SessionEnd;

            // KILL SWITCH: Setup swept before execution
            if (_longSetup.IsActive && low <= _longSetup.InvalidationLevel)
            {
                _longSetup.Deactivate();
                Chart.DrawText("VoidLong", "X", Bars.OpenTimes.Last(1), low, Color.Gray);
            }

            if (_shortSetup.IsActive && high >= _shortSetup.InvalidationLevel)
            {
                _shortSetup.Deactivate();
                Chart.DrawText("VoidShort", "X", Bars.OpenTimes.Last(1), high, Color.Gray);
            }

            // EXECUTION
            if (Positions.Count == 0 && inSession)
            {
                if (_longSetup.IsActive && close > _longSetup.TriggerLevel)
                {
                    ExecuteTrade(TradeType.Buy, _longSetup.InvalidationLevel);
                    _longSetup.Deactivate();
                }
                else if (_shortSetup.IsActive && close < _shortSetup.TriggerLevel)
                {
                    ExecuteTrade(TradeType.Sell, _shortSetup.InvalidationLevel);
                    _shortSetup.Deactivate();
                }
            }
        }

        private void ExecuteTrade(TradeType tradeType, double slPrice)
        {
            double entryPrice = Symbol.Ask;
            if (tradeType == TradeType.Sell) entryPrice = Symbol.Bid;

            double slPips = Math.Abs(entryPrice - slPrice) / Symbol.PipSize;
            if (slPips <= 0) return;

            // Fixed Fractional Position Sizing
            double riskAmount = Account.Equity * (RiskPct / 100);
            double volume = Symbol.NormalizeVolumeInUnits(riskAmount / (slPips * Symbol.PipValue), RoundingMode.Down);

            double tpPips = slPips * RrRatio;

            ExecuteMarketOrder(tradeType, SymbolName, volume, "ReclaimPA", slPips, tpPips);
        }

        private void UpdateVisuals()
        {
            DateTime currentTime = Bars.OpenTimes.LastValue;

            if (_longSetup.IsActive)
            {
                Chart.DrawTrendLine(TriggerLineName, _longSetup.StartTime, _longSetup.TriggerLevel, currentTime, _longSetup.TriggerLevel, Color.Teal, 2, LineStyle.Lines);
                Chart.DrawTrendLine(SlLineName, _longSetup.StartTime, _longSetup.InvalidationLevel, currentTime, _longSetup.InvalidationLevel, Color.Gray, 1, LineStyle.Solid);
            }
            else if (_shortSetup.IsActive)
            {
                Chart.DrawTrendLine(TriggerLineName, _shortSetup.StartTime, _shortSetup.TriggerLevel, currentTime, _shortSetup.TriggerLevel, Color.Red, 2, LineStyle.Lines);
                Chart.DrawTrendLine(SlLineName, _shortSetup.StartTime, _shortSetup.InvalidationLevel, currentTime, _shortSetup.InvalidationLevel, Color.Gray, 1, LineStyle.Solid);
            }
            else
            {
                Chart.RemoveObject(TriggerLineName);
                Chart.RemoveObject(SlLineName);
            }
        }

        // =========================================================================
        // HELPER CLASS
        // =========================================================================
        private class SetupState
        {
            public bool IsActive { get; private set; }
            public double InvalidationLevel { get; private set; }
            public double TriggerLevel { get; private set; }
            public DateTime StartTime { get; private set; }

            public void Activate(double invalidation, double trigger, DateTime start)
            {
                IsActive = true;
                InvalidationLevel = invalidation;
                TriggerLevel = trigger;
                StartTime = start;
            }

            public void Deactivate()
            {
                IsActive = false;
            }
        }
    }
}
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

Architectural Notes for cTrader

Data Synchronization: To prevent lookahead bias with the HTF EMA, the script fetches the HTF bar index that corresponds strictly to the timestamp of the currently closing M15 bar using _htfBars.OpenTimes.GetIndexByTime().

Structural Arrays: Instead of relying on Pine Script's built-in ta.pivotlow, this bot manually iterates over the LeftBars and RightBars arrays natively on OnBar(). This guarantees exact parity with how the structures were defined.

Dynamic Sizing (ExecuteTrade): Calculates standard normalized units Symbol.NormalizeVolumeInUnits() dividing exact equity risk by the PipValue times the pip distance, handling different asset class denominators cleanly (whether applied to major pairs, spot metals, or indices).

Garbage Collected Visuals: The UpdateVisuals() method redraws the lines continuously using fixed object names (TriggerLineName). When _longSetup.IsActive drops to false, it calls Chart.RemoveObject() immediately to prevent chart clutter.
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

Porting this architecture to MQL4 requires handling MetaTrader's native reverse-indexed series arrays (Time[], High[], Low[]) and utilizing iBarShift to prevent lookahead bias when checking the higher timeframe structure.

Because MQL4 Expert Advisors are tick-driven, the PA structure is evaluated strictly upon bar closure, while the execution and kill-switch are monitored tick-by-tick within OnTick().

The MQL4 Expert Advisor (.mq4)

Code: Select all

//+------------------------------------------------------------------+
//|                                         StructuralReclaimPro.mq4 |
//|                                       Strict PA Scalping Network |
//+------------------------------------------------------------------+
#property copyright "Strict PA Implementation"
#property strict

// --- 1. PARAMETERS ---
input double RiskPct = 1.0;               // Risk per Trade (%)
input double RrRatio = 2.0;               // Risk/Reward Ratio
input int LeftBars = 5;                   // Pivot Left (Impulse Leg)
input int RightBars = 2;                  // Pivot Right (Confirmation)
input ENUM_TIMEFRAMES HtfRes = PERIOD_H4; // HTF Resolution
input int SessionStart = 8;               // Session Start (Broker Time)
input int SessionEnd = 17;                // Session End (Broker Time)
input int MagicNum = 777123;              // EA Magic Number

// --- STATE VARIABLES ---
bool isLongSetupActive = false;
bool isShortSetupActive = false;
double triggerLvl = 0.0;
double invalLvl = 0.0;
datetime setupStart = 0;
datetime lastBarTime = 0;

string triggerLineName = "ReclaimTriggerLine";
string slLineName = "ReclaimSlLine";

//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION                                            |
//+------------------------------------------------------------------+
int OnInit() {
    lastBarTime = Time[0];
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| EXPERT DEINITIALIZATION                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason) {
    ObjectDelete(0, triggerLineName);
    ObjectDelete(0, slLineName);
}

//+------------------------------------------------------------------+
//| EXPERT TICK FUNCTION                                             |
//+------------------------------------------------------------------+
void OnTick() {
    bool isNewBar = false;
    if (Time[0] != lastBarTime) {
        isNewBar = true;
        lastBarTime = Time[0];
    }
    
    // Evaluate structure only on M15 bar close
    if (isNewBar) {
        UpdateStructure();
    }
    
    // Check execution/invalidation on every tick
    CheckExecutionOrInvalidation();
    UpdateVisuals();
}

//+------------------------------------------------------------------+
//| STATE MACHINE & MICROSTRUCTURE (Evaluated on Bar Close)          |
//+------------------------------------------------------------------+
void UpdateStructure() {
    // 0 is the current forming bar. The pivot check is shifted by RightBars + 1
    int checkIndex = RightBars + 1; 
    
    if (checkIndex + LeftBars >= Bars) return;
    
    bool isPivotLow = true;
    bool isPivotHigh = true;
    
    double plValue = Low[checkIndex];
    double phValue = High[checkIndex];
    
    double highestInRight = 0;
    double lowestInRight = 999999;
    
    // Scan Right side & capture micro-extremes for trigger level
    for (int i = 1; i <= RightBars; i++) {
        if (High[i] > highestInRight) highestInRight = High[i];
        if (Low[i] < lowestInRight) lowestInRight = Low[i];
        
        if (Low[i] <= plValue) isPivotLow = false;
        if (High[i] >= phValue) isPivotHigh = false;
    }
    
    // Scan Left side
    for (int i = checkIndex + 1; i <= checkIndex + LeftBars; i++) {
        if (Low[i] <= plValue) isPivotLow = false;
        if (High[i] >= phValue) isPivotHigh = false;
    }
    
    // HTF Bias via EMA (iBarShift ensures strict historical alignment)
    int htfIndex = iBarShift(NULL, HtfRes, Time[1]);
    double htfEma = iMA(NULL, HtfRes, 20, 0, MODE_EMA, PRICE_CLOSE, htfIndex);
    
    bool isBullishHtf = Close[1] > htfEma;
    bool isBearishHtf = Close[1] < htfEma;
    
    static double lastPl = EMPTY_VALUE;
    static double prevPl = EMPTY_VALUE;
    static double lastPh = EMPTY_VALUE;
    static double prevPh = EMPTY_VALUE;
    
    // Long Setup Detection
    if (isPivotLow) {
        prevPl = (lastPl == EMPTY_VALUE) ? plValue : lastPl;
        lastPl = plValue;
        
        if (lastPl > prevPl && isBullishHtf) {
            isLongSetupActive = true;
            isShortSetupActive = false;
            invalLvl = lastPl;
            triggerLvl = highestInRight;
            setupStart = Time[checkIndex];
        }
    }
    
    // Short Setup Detection
    if (isPivotHigh) {
        prevPh = (lastPh == EMPTY_VALUE) ? phValue : lastPh;
        lastPh = phValue;
        
        if (lastPh < prevPh && isBearishHtf) {
            isShortSetupActive = true;
            isLongSetupActive = false;
            invalLvl = lastPh;
            triggerLvl = lowestInRight;
            setupStart = Time[checkIndex];
        }
    }
}

//+------------------------------------------------------------------+
//| TICK-BY-TICK EXECUTION & INVALIDATION                            |
//+------------------------------------------------------------------+
void CheckExecutionOrInvalidation() {
    int currentHour = TimeHour(TimeCurrent());
    bool inSession = (currentHour >= SessionStart && currentHour < SessionEnd);
    
    // KILL SWITCH: Invalidated before reclaim execution occurs
    if (isLongSetupActive && Bid <= invalLvl) isLongSetupActive = false;
    if (isShortSetupActive && Ask >= invalLvl) isShortSetupActive = false;
    
    // EXECUTION
    if (CountOpenPositions() == 0 && inSession) {
        if (isLongSetupActive && Ask > triggerLvl) {
            ExecuteTrade(OP_BUY, invalLvl);
            isLongSetupActive = false;
        } 
        else if (isShortSetupActive && Bid < triggerLvl) {
            ExecuteTrade(OP_SELL, invalLvl);
            isShortSetupActive = false;
        }
    }
}

//+------------------------------------------------------------------+
//| TRADE EXECUTION & FIXED FRACTIONAL SIZING                        |
//+------------------------------------------------------------------+
void ExecuteTrade(int type, double slPrice) {
    double entryPrice = (type == OP_BUY) ? Ask : Bid;
    double slDist = MathAbs(entryPrice - slPrice);
    if (slDist <= 0) return;
    
    double riskAmount = AccountEquity() * (RiskPct / 100.0);
    double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
    
    double ticks = slDist / tickSize;
    double rawLot = riskAmount / (ticks * tickValue);
    
    double minLot = MarketInfo(Symbol(), MODE_MINLOT);
    double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
    double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
    
    double lotSize = MathFloor(rawLot / lotStep) * lotStep;
    if (lotSize < minLot) lotSize = minLot;
    if (lotSize > maxLot) lotSize = maxLot;
    
    double tpPrice = (type == OP_BUY) ? entryPrice + (slDist * RrRatio) : entryPrice - (slDist * RrRatio);
    
    int digits = (int)MarketInfo(Symbol(), MODE_DIGITS);
    slPrice = NormalizeDouble(slPrice, digits);
    tpPrice = NormalizeDouble(tpPrice, digits);
    
    int ticket = OrderSend(Symbol(), type, lotSize, entryPrice, 3, slPrice, tpPrice, "PA_Reclaim", MagicNum, 0, (type == OP_BUY) ? clrTeal : clrRed);
}

//+------------------------------------------------------------------+
//| UTILS                                                            |
//+------------------------------------------------------------------+
int CountOpenPositions() {
    int count = 0;
    for (int i = OrdersTotal() - 1; i >= 0; i--) {
        if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) {
            if (OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNum) count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| GARBAGE COLLECTED VISUALS                                        |
//+------------------------------------------------------------------+
void UpdateVisuals() {
    if (isLongSetupActive || isShortSetupActive) {
        color clr = isLongSetupActive ? clrTeal : clrRed;
        
        // Trigger Line (Dashed)
        if (ObjectFind(0, triggerLineName) < 0) {
            ObjectCreate(0, triggerLineName, OBJ_TREND, 0, setupStart, triggerLvl, Time[0], triggerLvl);
            ObjectSetInteger(0, triggerLineName, OBJPROP_COLOR, clr);
            ObjectSetInteger(0, triggerLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, triggerLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, triggerLineName, OBJPROP_WIDTH, 2);
        } else {
            ObjectSetDouble(0, triggerLineName, OBJPROP_PRICE, 0, triggerLvl);
            ObjectSetDouble(0, triggerLineName, OBJPROP_PRICE, 1, triggerLvl);
            ObjectSetInteger(0, triggerLineName, OBJPROP_TIME, 0, setupStart);
            ObjectSetInteger(0, triggerLineName, OBJPROP_TIME, 1, Time[0]);
            ObjectSetInteger(0, triggerLineName, OBJPROP_COLOR, clr);
        }
        
        // Invalidation Line (Solid)
        if (ObjectFind(0, slLineName) < 0) {
            ObjectCreate(0, slLineName, OBJ_TREND, 0, setupStart, invalLvl, Time[0], invalLvl);
            ObjectSetInteger(0, slLineName, OBJPROP_COLOR, clrGray);
            ObjectSetInteger(0, slLineName, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(0, slLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, slLineName, OBJPROP_WIDTH, 1);
        } else {
            ObjectSetDouble(0, slLineName, OBJPROP_PRICE, 0, invalLvl);
            ObjectSetDouble(0, slLineName, OBJPROP_PRICE, 1, invalLvl);
            ObjectSetInteger(0, slLineName, OBJPROP_TIME, 0, setupStart);
            ObjectSetInteger(0, slLineName, OBJPROP_TIME, 1, Time[0]);
        }
    } else {
        ObjectDelete(0, triggerLineName);
        ObjectDelete(0, slLineName);
    }
}
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

Architectural Notes for MQL4

Reverse Indexing (checkIndex): MT4 arrays run backward (0 is the current forming bar). A pivot is validated precisely when the RightBars finish forming, meaning the candidate pivot is located at index RightBars + 1. The loop scans indices 1 to RightBars exclusively to map the micro-reclaim barrier height.

HTF Syncing (iBarShift): To perfectly align the overarching bias with the M15 chart, iBarShift(NULL, HtfRes, Time[1]) fetches the precise HTF index that was running at the close of the previous M15 bar. This guarantees the EA doesn't inherit lookahead bias during Strategy Tester backtests.

Tick-Level Execution: While the bias and microstructure only recalculate sequentially at the open of a new bar, CheckExecutionOrInvalidation() continuously monitors the current Bid and Ask prices. This strictly applies your PA logic to execute the exact microsecond the structural reclaim is breached or the higher-low is violated.
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

Here is the complete, professional-grade port of your price action scalping logic for MetaTrader 5 (MQL5).

Moving from MQL4 to MQL5 requires adapting to the native CTrade object-oriented library for execution, and utilizing CopyRates or native array access to pull M15 and H4 chart data securely without lookahead bias.

This implementation evaluates structural pivots exactly at bar close, maps the micro-barriers for the reclaim, dynamically draws the visual tracking lines, and calculates precise fixed-fractional risk sizing tick-for-tick before pushing market orders.
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

The MQL5 Expert Advisor (.mq5)

Code: Select all

//+------------------------------------------------------------------+
//|                                         StructuralReclaimPro.mq5 |
//|                                       Strict PA Scalping Network |
//+------------------------------------------------------------------+
#property copyright "Strict PA Implementation"
#property version   "1.00"

#include <Trade\Trade.mqh>

// --- 1. PARAMETERS ---
input double RiskPct = 1.0;                           // Risk per Trade (%)
input double RrRatio = 2.0;                           // Risk/Reward Ratio
input int LeftBars = 5;                               // Pivot Left (Impulse Leg)
input int RightBars = 2;                              // Pivot Right (Confirmation)
input ENUM_TIMEFRAMES HtfRes = PERIOD_H4;             // HTF Resolution
input int SessionStart = 8;                           // Session Start (Broker Time)
input int SessionEnd = 17;                            // Session End (Broker Time)
input ulong MagicNum = 777125;                        // EA Magic Number

// --- STATE VARIABLES ---
CTrade trade;
int htfEmaHandle = INVALID_HANDLE;
datetime lastBarTime = 0;

bool isLongSetupActive = false;
bool isShortSetupActive = false;
double triggerLvl = 0.0;
double invalLvl = 0.0;
datetime setupStart = 0;

string triggerLineName = "ReclaimTriggerLine";
string slLineName = "ReclaimSlLine";

//+------------------------------------------------------------------+
//| EXPERT INITIALIZATION                                            |
//+------------------------------------------------------------------+
int OnInit()
{
    trade.SetExpertMagicNumber(MagicNum);
    lastBarTime = iTime(_Symbol, PERIOD_CURRENT, 0);
    
    // Initialize the HTF EMA Handle
    htfEmaHandle = iMA(_Symbol, HtfRes, 20, 0, MODE_EMA, PRICE_CLOSE);
    if(htfEmaHandle == INVALID_HANDLE)
    {
        Print("Failed to create HTF EMA handle!");
        return(INIT_FAILED);
    }
    
    return(INIT_SUCCEEDED);
}

//+------------------------------------------------------------------+
//| EXPERT DEINITIALIZATION                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    IndicatorRelease(htfEmaHandle);
    ObjectDelete(0, triggerLineName);
    ObjectDelete(0, slLineName);
}

//+------------------------------------------------------------------+
//| EXPERT TICK FUNCTION                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    datetime currentTime = iTime(_Symbol, PERIOD_CURRENT, 0);
    bool isNewBar = false;
    
    if (currentTime != lastBarTime)
    {
        isNewBar = true;
        lastBarTime = currentTime;
    }
    
    // Evaluate structure only on M15 bar close
    if (isNewBar)
    {
        UpdateStructure();
    }
    
    // Check execution/invalidation on every tick
    CheckExecutionOrInvalidation();
    UpdateVisuals();
}

//+------------------------------------------------------------------+
//| STATE MACHINE & MICROSTRUCTURE (Evaluated on Bar Close)          |
//+------------------------------------------------------------------+
void UpdateStructure()
{
    int checkIndex = RightBars + 1;
    
    // Fetch recent rates (timeseries style)
    int barsToCopy = checkIndex + LeftBars + 1;
    MqlRates rates[];
    ArraySetAsSeries(rates, true);
    
    if(CopyRates(_Symbol, PERIOD_CURRENT, 0, barsToCopy, rates) < barsToCopy)
        return;
        
    bool isPivotLow = true;
    bool isPivotHigh = true;
    
    double plValue = rates[checkIndex].low;
    double phValue = rates[checkIndex].high;
    
    double highestInRight = 0.0;
    double lowestInRight = DBL_MAX;
    
    // Scan Right side & capture micro-extremes for trigger level
    for (int i = 1; i <= RightBars; i++)
    {
        if (rates[i].high > highestInRight) highestInRight = rates[i].high;
        if (rates[i].low < lowestInRight) lowestInRight = rates[i].low;
        
        if (rates[i].low <= plValue) isPivotLow = false;
        if (rates[i].high >= phValue) isPivotHigh = false;
    }
    
    // Scan Left side
    for (int i = checkIndex + 1; i <= checkIndex + LeftBars; i++)
    {
        if (rates[i].low <= plValue) isPivotLow = false;
        if (rates[i].high >= phValue) isPivotHigh = false;
    }
    
    // Fetch HTF Bias via EMA (iBarShift ensures strict historical alignment)
    int htfIndex = iBarShift(_Symbol, HtfRes, rates[1].time, false);
    double htfEmaData[];
    ArraySetAsSeries(htfEmaData, true);
    
    if(CopyBuffer(htfEmaHandle, 0, htfIndex, 1, htfEmaData) <= 0)
        return;
        
    double htfEma = htfEmaData[0];
    bool isBullishHtf = rates[1].close > htfEma;
    bool isBearishHtf = rates[1].close < htfEma;
    
    static double lastPl = EMPTY_VALUE;
    static double prevPl = EMPTY_VALUE;
    static double lastPh = EMPTY_VALUE;
    static double prevPh = EMPTY_VALUE;
    
    // Long Setup Detection
    if (isPivotLow)
    {
        prevPl = (lastPl == EMPTY_VALUE) ? plValue : lastPl;
        lastPl = plValue;
        
        if (lastPl > prevPl && isBullishHtf)
        {
            isLongSetupActive = true;
            isShortSetupActive = false;
            invalLvl = lastPl;
            triggerLvl = highestInRight;
            setupStart = rates[checkIndex].time;
        }
    }
    
    // Short Setup Detection
    if (isPivotHigh)
    {
        prevPh = (lastPh == EMPTY_VALUE) ? phValue : lastPh;
        lastPh = phValue;
        
        if (lastPh < prevPh && isBearishHtf)
        {
            isShortSetupActive = true;
            isLongSetupActive = false;
            invalLvl = lastPh;
            triggerLvl = lowestInRight;
            setupStart = rates[checkIndex].time;
        }
    }
}

//+------------------------------------------------------------------+
//| TICK-BY-TICK EXECUTION & INVALIDATION                            |
//+------------------------------------------------------------------+
void CheckExecutionOrInvalidation()
{
    MqlTick currentTick;
    if(!SymbolInfoTick(_Symbol, currentTick)) return;
    
    MqlDateTime dt;
    TimeCurrent(dt);
    bool inSession = (dt.hour >= SessionStart && dt.hour < SessionEnd);
    
    // KILL SWITCH: Invalidated before reclaim execution occurs
    if (isLongSetupActive && currentTick.bid <= invalLvl) isLongSetupActive = false;
    if (isShortSetupActive && currentTick.ask >= invalLvl) isShortSetupActive = false;
    
    // EXECUTION
    if (PositionsTotalByMagic() == 0 && inSession)
    {
        if (isLongSetupActive && currentTick.ask > triggerLvl)
        {
            ExecuteTrade(ORDER_TYPE_BUY, invalLvl, currentTick.ask);
            isLongSetupActive = false;
        } 
        else if (isShortSetupActive && currentTick.bid < triggerLvl)
        {
            ExecuteTrade(ORDER_TYPE_SELL, invalLvl, currentTick.bid);
            isShortSetupActive = false;
        }
    }
}

//+------------------------------------------------------------------+
//| TRADE EXECUTION & FIXED FRACTIONAL SIZING                        |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE type, double slPrice, double entryPrice)
{
    double slDist = MathAbs(entryPrice - slPrice);
    if (slDist <= 0) return;
    
    double riskAmount = AccountInfoDouble(ACCOUNT_EQUITY) * (RiskPct / 100.0);
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    
    double ticks = slDist / tickSize;
    if(ticks * tickValue <= 0) return;
    
    double rawLot = riskAmount / (ticks * tickValue);
    
    double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
    double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    
    double lotSize = MathFloor(rawLot / lotStep) * lotStep;
    if (lotSize < minLot) lotSize = minLot;
    if (lotSize > maxLot) lotSize = maxLot;
    
    double tpPrice = (type == ORDER_TYPE_BUY) ? entryPrice + (slDist * RrRatio) : entryPrice - (slDist * RrRatio);
    
    int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS);
    slPrice = NormalizeDouble(slPrice, digits);
    tpPrice = NormalizeDouble(tpPrice, digits);
    
    if (type == ORDER_TYPE_BUY)
        trade.Buy(lotSize, _Symbol, entryPrice, slPrice, tpPrice, "PA_Reclaim_Long");
    else
        trade.Sell(lotSize, _Symbol, entryPrice, slPrice, tpPrice, "PA_Reclaim_Short");
}

//+------------------------------------------------------------------+
//| UTILS                                                            |
//+------------------------------------------------------------------+
int PositionsTotalByMagic()
{
    int count = 0;
    for(int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if(ticket > 0)
        {
            if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNum)
                count++;
        }
    }
    return count;
}

//+------------------------------------------------------------------+
//| GARBAGE COLLECTED VISUALS                                        |
//+------------------------------------------------------------------+
void UpdateVisuals()
{
    datetime currentTime = TimeCurrent();
    
    if (isLongSetupActive || isShortSetupActive)
    {
        color clr = isLongSetupActive ? clrTeal : clrRed;
        
        // Trigger Line (Dashed)
        if (ObjectFind(0, triggerLineName) < 0)
        {
            ObjectCreate(0, triggerLineName, OBJ_TREND, 0, setupStart, triggerLvl, currentTime, triggerLvl);
            ObjectSetInteger(0, triggerLineName, OBJPROP_COLOR, clr);
            ObjectSetInteger(0, triggerLineName, OBJPROP_STYLE, STYLE_DASH);
            ObjectSetInteger(0, triggerLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, triggerLineName, OBJPROP_WIDTH, 2);
        }
        else
        {
            ObjectSetDouble(0, triggerLineName, OBJPROP_PRICE, 0, triggerLvl);
            ObjectSetDouble(0, triggerLineName, OBJPROP_PRICE, 1, triggerLvl);
            ObjectSetInteger(0, triggerLineName, OBJPROP_TIME, 0, setupStart);
            ObjectSetInteger(0, triggerLineName, OBJPROP_TIME, 1, currentTime);
            ObjectSetInteger(0, triggerLineName, OBJPROP_COLOR, clr);
        }
        
        // Invalidation Line (Solid)
        if (ObjectFind(0, slLineName) < 0)
        {
            ObjectCreate(0, slLineName, OBJ_TREND, 0, setupStart, invalLvl, currentTime, invalLvl);
            ObjectSetInteger(0, slLineName, OBJPROP_COLOR, clrGray);
            ObjectSetInteger(0, slLineName, OBJPROP_STYLE, STYLE_SOLID);
            ObjectSetInteger(0, slLineName, OBJPROP_RAY_RIGHT, false);
            ObjectSetInteger(0, slLineName, OBJPROP_WIDTH, 1);
        }
        else
        {
            ObjectSetDouble(0, slLineName, OBJPROP_PRICE, 0, invalLvl);
            ObjectSetDouble(0, slLineName, OBJPROP_PRICE, 1, invalLvl);
            ObjectSetInteger(0, slLineName, OBJPROP_TIME, 0, setupStart);
            ObjectSetInteger(0, slLineName, OBJPROP_TIME, 1, currentTime);
        }
    }
    else
    {
        ObjectDelete(0, triggerLineName);
        ObjectDelete(0, slLineName);
    }
}
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: Continuation only after a higher-low reclaim — my simple filter

Post by PTScalper »

Architectural Notes for MQL5

Modern Order Execution: Uses the #include <Trade\Trade.mqh> library rather than raw OrderSend commands. The .Buy() and .Sell() methods handle the server requests asynchronously and automatically format order strings, keeping execution logic much cleaner.

Indicator Handle Binding (htfEmaHandle): Unlike MT4's iMA() which computes on demand, MT5 creates an indicator pointer buffer in OnInit() and efficiently pulls precisely one value with CopyBuffer() mapped identically to the previous bar close using iBarShift.

MqlRates Structure Mapping: Pivot logic leverages CopyRates combined with ArraySetAsSeries(rates, true) to natively duplicate MT4's reverse-indexing ([0] is current, [1] is previous), which makes translating micro-structure array loops flawless while maintaining native access to .high, .low, and .time properties.

Tick Value Normalization (SYMBOL_TRADE_TICK_VALUE): Dynamically scales fractional lots across distinct asset classes exactly like the cTrader version, evaluating the cost per tick relative to absolute price distance rather than a raw points/pips estimation.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
LondonScalper
Posts: 770
Joined: Sat Sep 05, 2026 7:54 am

Re: Continuation only after a higher-low reclaim — my simple filter

Post by LondonScalper »

That stack makes sense for your hold times. An M15 higher-low under a D1 bias is a proper swing-structure filter, and it throws out most of the M1 storytelling on its own.

The trade-off is the stop. An M15 higher-low on GBPUSD in London is often 15–25 pips below the entry, while my M5 version usually sits within 6–10. Same logic, different risk unit, so the two are not interchangeable. With my size on your reclaim, one loser would cost two or three of my normal stops.

Where we agree completely is the hierarchy: the higher chart owns the definition and the lower chart is only allowed to execute it. For me that pair is M5/M1, for you D1/M15. The failure mode is identical in both — letting the execution chart draw its own higher-low because the bigger one is taking too long to form.

I ran the M15 version on EURUSD for a few weeks last year. Fewer tickets and cleaner invalidation, but it rarely completed inside a single London morning, which is the only window I trade seriously.
Post Reply