Page 1 of 2

EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 6:09 pm
by FXS London Scalper
Somewhere around 15:00–16:00 UK, the London–New York overlap starts to fade. On some days, EURUSD doesn't fade so much as stop entirely. Spreads stay tight, but the chart flattens and every small move reverses.

Ways to recognise a dead overlap early:

- The 5-minute ATR drops below half its morning value.
- Price returns to the same level repeatedly within a few pips.
- US equity markets are flat and bond yields are steady.
- No scheduled events until the next day.

Why traders get caught:

- After a profitable morning, there's a temptation to add more.
- After a losing morning, there's a temptation to recover.
- Tight spreads make it look cheap to trade.

But cheap isn't the same as profitable. If the expected move is 3 pips and the all-in cost is 0.6, the cost is 20% of the target, and the probability of hitting the target drops significantly in a dead market.

A simple rule: if the 5-minute ATR at 15:00 UK is below a certain threshold (e.g. 3 pips on EURUSD), the session is over.

Do you have an exit rule for the afternoon, or do you trade until your fixed end time?

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:05 pm
by FTtrader
FXS London Scalper wrote: Thu Oct 22, 2026 6:09 pm Somewhere around 15:00–16:00 UK, the London–New York overlap starts to fade. On some days, EURUSD doesn't fade so much as stop entirely. Spreads stay tight, but the chart flattens and every small move reverses.

Ways to recognise a dead overlap early:

- The 5-minute ATR drops below half its morning value.
- Price returns to the same level repeatedly within a few pips.
- US equity markets are flat and bond yields are steady.
- No scheduled events until the next day.

Why traders get caught:

- After a profitable morning, there's a temptation to add more.
- After a losing morning, there's a temptation to recover.
- Tight spreads make it look cheap to trade.

But cheap isn't the same as profitable. If the expected move is 3 pips and the all-in cost is 0.6, the cost is 20% of the target, and the probability of hitting the target drops significantly in a dead market.

A simple rule: if the 5-minute ATR at 15:00 UK is below a certain threshold (e.g. 3 pips on EURUSD), the session is over.

Do you have an exit rule for the afternoon, or do you trade until your fixed end time?
Hi FXS London Scalper,

Interesting thread btw.

I run a hybrid exit rule: a hard time stop at 16:00 UK regardless of market conditions, coupled with a dynamic volatility kill-switch that arms at 14:30 UK. If the market dies before the hard stop, the volatility filter takes me out early.

The Mathematical Trap

Your point about the 20% cost is spot on, but execution friction makes it worse. In a dead 3-pip range, the spread is just the entry fee. Add round-turn commissions and 0.2 pips of slippage, and your friction cost easily eats 50% of the potential reward. The risk-to-reward ratio physically breaks down when structural volume disappears.

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:06 pm
by FTtrader
Validating the Dead Zone

Relying solely on ATR can trigger false signals if the market is just pausing. I pair the 5-minute ATR drop with two other checks:

Tick Volume Divergence: If the ATR drops below 3 pips and tick volume falls by 40% relative to the 13:00–14:00 window, algorithmic participants have officially parked their flow.

M15 Doji Clusters: Two consecutive 15-minute candles closing within a pip of their open confirms the market is just hunting retail stops in a tight band.

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:06 pm
by FTtrader
The Psychology of the Fade

Boredom trading in this window is as toxic as revenge trading. Tight spreads give the illusion of opportunity, but attempting to force a win from a momentum-less market usually turns a solid morning into a frustrating breakeven afternoon.

How do you handle positions that are already open and in profit when this 15:00 drop-off triggers your rule?

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:08 pm
by FTtrader
Implementing a dynamic volatility kill-switch requires decoupling the market state evaluation from your main entry logic. By running this check on the closure of each lower-timeframe candle during your vulnerability window, the system can automatically block new orders and flatten existing exposure if the threshold is breached.

This C# implementation for a cBot targets a 14:30 UK activation window and a 3-pip ATR threshold:

Code: Select all

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class VolatilityKillSwitch : Robot
    {
        [Parameter("ATR Period", DefaultValue = 14)]
        public int AtrPeriod { get; set; }

        [Parameter("Kill Switch Threshold (Pips)", DefaultValue = 3.0)]
        public double MinAtrPips { get; set; }

        private AverageTrueRange _atr;
        private bool _tradingSuspended;

        protected override void OnStart()
        {
            // Bind ATR to the 5-minute timeframe regardless of the active execution chart
            var m5Bars = MarketData.GetBars(TimeFrame.Minute5);
            _atr = Indicators.AverageTrueRange(m5Bars, AtrPeriod, MovingAverageType.Simple);
        }

        protected override void OnBar()
        {
            EvaluateVolatilityState();
        }

        private void EvaluateVolatilityState()
        {
            TimeSpan currentUtcTime = Server.Time.TimeOfDay;
            TimeSpan killWindowStart = new TimeSpan(13, 30, 0); // 13:30 UTC / 14:30 UK (Winter)
            TimeSpan hardStop = new TimeSpan(15, 0, 0);       // 15:00 UTC / 16:00 UK

            if (currentUtcTime >= hardStop)
            {
                SuspendTrading("Hard time stop reached.");
                return;
            }

            if (currentUtcTime >= killWindowStart)
            {
                // Normalize raw ATR value to pips
                double currentAtrPips = _atr.Result.Last(1) / Symbol.PipSize;

                if (currentAtrPips < MinAtrPips)
                {
                    SuspendTrading($"Market dead. M5 ATR dropped to {Math.Round(currentAtrPips, 1)} pips.");
                }
            }
        }

        private void SuspendTrading(string reason)
        {
            if (_tradingSuspended) return;

            _tradingSuspended = true;
            Print($"Kill-switch armed: {reason}");

            // Flatten open positions asynchronously to prevent thread blocking
            var activePositions = Positions.Where(p => p.SymbolName == SymbolName).ToArray();
            foreach (var position in activePositions)
            {
                ClosePositionAsync(position);
            }

            // Cancel pending limit/stop orders
            var pendingOrders = PendingOrders.Where(o => o.SymbolName == SymbolName).ToArray();
            foreach (var order in pendingOrders)
            {
                CancelPendingOrderAsync(order);
            }
        }

        private void ExecuteTradeStrategy()
        {
            if (_tradingSuspended) return;
            // Standard execution logic goes here
        }
    }
}

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:08 pm
by FTtrader
Timeframe Isolation: The ATR calculation explicitly requests TimeFrame.Minute5 data. This ensures the 3-pip threshold remains mathematically valid even if your strategy is executing scalps on an M1 chart.

Asynchronous Liquidation: ClosePositionAsync prevents execution thread blocking during the teardown phase. This is critical in dead markets where tight spreads can widen instantly, requiring the broker to make multiple routing attempts to fill the exit order.

State Locking: The _tradingSuspended boolean acts as a global lock, instantly breaking the entry cycle until the instance is reset or the next session begins.

To integrate the tick volume divergence check, you can add a secondary evaluation inside EvaluateVolatilityState that compares the sum of m5Bars.TickVolumes over the last 3 bars against a rolling baseline recorded during the 13:00–14:00 window.

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:12 pm
by FTtrader
In MQL5, implementing this requires handling two framework-specific behaviors: translating the target time into the broker’s server time (IC Markets runs on GMT+2 / GMT+3 DST) and iterating through positions in reverse to prevent index shifting during liquidation.

Here is the direct equivalent in MQL5, utilizing the standard <Trade\Trade.mqh> library for execution:

Code: Select all

#property strict
#include <Trade\Trade.mqh>

input int    AtrPeriod         = 14;
input double MinAtrPips        = 3.0;

// Note: Time inputs must reflect Broker Server Time (TimeCurrent)
// For IC Markets (GMT+2/GMT+3), 14:30 UK might be 16:30 Server Time depending on the time of year.
input int    KillWindowHour    = 16;
input int    KillWindowMinute  = 30; 
input int    HardStopHour      = 18;
input int    HardStopMinute    = 0;  

CTrade trade;
int    atrHandle;
bool   tradingSuspended = false;
double pipSize;

int OnInit()
{
    // Initialize M5 ATR Handle regardless of the chart timeframe the EA is attached to
    atrHandle = iATR(_Symbol, PERIOD_M5, AtrPeriod);
    if (atrHandle == INVALID_HANDLE)
    {
        Print("Failed to create ATR handle");
        return INIT_FAILED;
    }

    // Normalize point to pip size (e.g., IC Markets 5-digit pricing)
    pipSize = (_Digits == 5 || _Digits == 3) ? _Point * 10.0 : _Point;
    
    // Configure CTrade for asynchronous execution (slippage tolerance, magic number, etc. can be added here)
    trade.SetAsyncMode(true);

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    IndicatorRelease(atrHandle);
}

void OnTick()
{
    EvaluateVolatilityState();

    if (tradingSuspended) return;

    // Standard execution logic goes here
}

void EvaluateVolatilityState()
{
    MqlDateTime serverTime;
    TimeCurrent(serverTime);

    int currentMinutes   = serverTime.hour * 60 + serverTime.min;
    int killStartMinutes = KillWindowHour * 60 + KillWindowMinute;
    int hardStopMinutes  = HardStopHour * 60 + HardStopMinute;

    if (currentMinutes >= hardStopMinutes)
    {
        SuspendTrading("Hard time stop reached.");
        return;
    }

    if (currentMinutes >= killStartMinutes)
    {
        double atrBuffer[];
        ArraySetAsSeries(atrBuffer, true);
        
        // Copy index 1 to read the last fully closed M5 candle, avoiding intra-bar fluttering
        if (CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) > 0) 
        {
            double currentAtrPips = atrBuffer[0] / pipSize;

            if (currentAtrPips < MinAtrPips)
            {
                SuspendTrading(StringFormat("Market dead. M5 ATR dropped to %.1f pips.", currentAtrPips));
            }
        }
    }
}

void SuspendTrading(string reason)
{
    if (tradingSuspended) return;

    tradingSuspended = true;
    Print("Kill-switch armed: ", reason);

    // Flatten open positions (must loop backwards to safely delete by index)
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionGetString(POSITION_SYMBOL) == _Symbol)
        {
            trade.PositionClose(ticket); // Executes async because SetAsyncMode(true) was called
        }
    }

    // Cancel pending orders (must loop backwards)
    for (int i = OrdersTotal() - 1; i >= 0; i--)
    {
        ulong ticket = OrderGetTicket(i);
        if (OrderGetString(ORDER_SYMBOL) == _Symbol)
        {
            trade.OrderDelete(ticket);
        }
    }
}

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:12 pm
by FTtrader
Key Differences from cAlgo

Broker Time vs. UTC: MetaTrader operates purely on TimeCurrent() (Server Time). The inputs for the arming windows are exposed as server hours/minutes. You will need to calibrate these based on the IC Markets server offset compared to UK time.

Pointer Iteration: When liquidating arrays of positions or orders in MQL5, you must loop decrementally (PositionsTotal() - 1 down to 0). Closing an order at index 0 shifts all subsequent orders down an index, which causes skipping if iterated forwards.

Buffer Polling: CopyBuffer is pulling index 1 instead of 0. This evaluates the ATR based strictly on the last closed M5 candle, preventing the kill-switch from triggering prematurely on a low-volatility opening minute of a new candle.

Asynchronous Execution: Setting trade.SetAsyncMode(true); mirrors the ClosePositionAsync behavior in cTrader. It fires the liquidation requests to the broker without halting the EA thread waiting for execution confirmation.

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:15 pm
by FTtrader
To implement tick volume divergence, you must capture a daily baseline average during the active market hours (e.g., 13:00–14:00 UK) and compare it dynamically against the rolling average of the most recent closed candles during your vulnerability window.

Here is the updated MQL5 logic. It introduces a daily state tracker that calculates the baseline once per day as soon as the baseline window closes, preventing redundant array copying on every tick.

Code: Select all

#property strict
#include <Trade\Trade.mqh>

input int    AtrPeriod           = 14;
input double MinAtrPips          = 3.0;

// Time parameters require Broker Server Time (e.g., IC Markets GMT+2/GMT+3)
input int    BaselineStartHour   = 15;   // 13:00 UK -> 15:00 Server Time
input int    BaselineStartMinute = 0;
input int    BaselineEndHour     = 16;   // 14:00 UK -> 16:00 Server Time
input int    BaselineEndMinute   = 0;

input int    KillWindowHour      = 16;   // 14:30 UK -> 16:30 Server Time
input int    KillWindowMinute    = 30; 
input int    HardStopHour        = 18;   // 16:00 UK -> 18:00 Server Time
input int    HardStopMinute      = 0;  

input double VolumeDropThreshold = 0.40; // 40% drop in volume required
input int    VolumeCheckBars     = 3;    // Check average of last 3 M5 candles

CTrade trade;
int    atrHandle;
bool   tradingSuspended = false;
double pipSize;

double dailyBaselineVolume = 0;
int    lastBaselineDay     = -1;

int OnInit()
{
    atrHandle = iATR(_Symbol, PERIOD_M5, AtrPeriod);
    if (atrHandle == INVALID_HANDLE) return INIT_FAILED;

    pipSize = (_Digits == 5 || _Digits == 3) ? _Point * 10.0 : _Point;
    trade.SetAsyncMode(true);

    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason)
{
    IndicatorRelease(atrHandle);
}

void OnTick()
{
    EvaluateVolatilityState();
    if (tradingSuspended) return;
    
    // Standard execution logic goes here
}

void EvaluateVolatilityState()
{
    MqlDateTime dt;
    TimeCurrent(dt);

    int currentMinutes   = dt.hour * 60 + dt.min;
    int killStartMinutes = KillWindowHour * 60 + KillWindowMinute;
    int hardStopMinutes  = HardStopHour * 60 + HardStopMinute;

    // 1. Hard Stop Check
    if (currentMinutes >= hardStopMinutes)
    {
        SuspendTrading("Hard time stop reached.");
        return;
    }

    // 2. Establish Daily Baseline
    int baselineEndMinutes = BaselineEndHour * 60 + BaselineEndMinute;
    
    if (currentMinutes >= baselineEndMinutes && dt.day_of_year != lastBaselineDay)
    {
        MqlDateTime startDt = dt;
        startDt.hour = BaselineStartHour;
        startDt.min = BaselineStartMinute;
        startDt.sec = 0;
        
        MqlDateTime endDt = dt;
        endDt.hour = BaselineEndHour;
        endDt.min = BaselineEndMinute;
        endDt.sec = 0;

        datetime startTime = StructToTime(startDt);
        datetime endTime = StructToTime(endDt);
        
        long baseVolBuffer[];
        int copied = CopyTickVolume(_Symbol, PERIOD_M5, startTime, endTime, baseVolBuffer);
        
        if (copied > 0)
        {
            long totalBaseVol = 0;
            for(int i = 0; i < copied; i++) totalBaseVol += baseVolBuffer[i];
            dailyBaselineVolume = (double)totalBaseVol / copied;
            lastBaselineDay = dt.day_of_year; // Lock calculation for today
            PrintFormat("Today's Baseline Volume set to: %.1f ticks/bar", dailyBaselineVolume);
        }
    }

    // 3. Kill-Switch Window Evaluation
    if (currentMinutes >= killStartMinutes && dailyBaselineVolume > 0)
    {
        // ATR Check
        double atrBuffer[];
        ArraySetAsSeries(atrBuffer, true);
        double currentAtrPips = 0;
        bool isAtrDead = false;

        if (CopyBuffer(atrHandle, 0, 1, 1, atrBuffer) > 0) 
        {
            currentAtrPips = atrBuffer[0] / pipSize;
            if (currentAtrPips < MinAtrPips) isAtrDead = true;
        }

        // Tick Volume Divergence Check
        long currentVolBuffer[];
        double currentAvgVol = 0;
        bool isVolumeDead = false;

        if (CopyTickVolume(_Symbol, PERIOD_M5, 1, VolumeCheckBars, currentVolBuffer) == VolumeCheckBars)
        {
            long currentTotal = 0;
            for(int i = 0; i < VolumeCheckBars; i++) currentTotal += currentVolBuffer[i];
            currentAvgVol = (double)currentTotal / VolumeCheckBars;
            
            // Check if volume fell by the threshold (e.g. current < baseline * 0.60)
            if (currentAvgVol < dailyBaselineVolume * (1.0 - VolumeDropThreshold))
            {
                isVolumeDead = true;
            }
        }

        // Dual Confirmation
        if (isAtrDead && isVolumeDead)
        {
            SuspendTrading(StringFormat("Dead Zone confirmed. ATR: %.1f pips | Vol: %.0f (Baseline: %.0f)", 
                           currentAtrPips, currentAvgVol, dailyBaselineVolume));
        }
    }
}

void SuspendTrading(string reason)
{
    if (tradingSuspended) return;
    tradingSuspended = true;
    Print("Kill-switch armed: ", reason);

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

    for (int i = OrdersTotal() - 1; i >= 0; i--)
    {
        ulong ticket = OrderGetTicket(i);
        if (OrderGetString(ORDER_SYMBOL) == _Symbol)
        {
            trade.OrderDelete(ticket);
        }
    }
}

Re: EURUSD mid-Atlantic drift: recognising when the overlap is dead

Posted: Thu Oct 22, 2026 7:16 pm
by FTtrader
Execution Mechanics

Daily Reset Cache (lastBaselineDay): Extracting time-range history on every tick is a massive computational drain in MT5. By checking dt.day_of_year != lastBaselineDay, the EA copies the baseline array exactly once immediately after BaselineEndMinute passes.

Synchronized Time Structs: The logic constructs exact datetime boundaries using MqlDateTime to feed CopyTickVolume(..., startTime, endTime, ...). This isolates the exact 13:00–14:00 UK volume structure, regardless of intra-hour fluctuations.

Dual Condition Lock: The system now demands both low price displacement (isAtrDead) and structural participant withdrawal (isVolumeDead) to trigger the kill-switch, aggressively filtering out temporary mid-afternoon pauses that still have underlying algorithmic flow.