# MT5 Multi-Symbol Bar Timing Probe — Results Report

**Study:** Cross-symbol bar synchronisation in the MT5 Strategy Tester  
**Primary symbol:** EURUSD M15  
**Secondary symbols:** GBPUSD, USDCHF, USDCAD, GBPJPY, EURJPY, EURAUD  
**Period:** 2025-05-08 to 2026-05-08  

---

## Background

A portfolio EA running multiple symbols inside a single MT5 backtest showed
systematic +15-minute entry delays on GBPUSD, USDCHF, USDCAD and GBPJPY,
while EURUSD, EURJPY and EURAUD entered on time. When each symbol was run in
its own independent backtest the timing was correct.

This probe quantifies whether the MT5 tester delivers a stale `iTime()` value
for off-chart symbols at the moment the primary-symbol `OnTick` fires.

---

## Measurement method

A minimal EA (`BarTimingProbe.mq5`) runs on EURUSD M15 as the primary symbol.
On each primary new-bar event it opens a **tracking window** (up to 50 ticks).
Each tick it polls `iTime(secondary, PERIOD_M15, 0)` for all six secondary
symbols and records on which tick — if any — each one reports the new bar time.

| Lag value | Meaning |
|-----------|---------|
| `0` | Secondary bar visible on the **same tick** as primary (no lag) |
| `N > 0` | Secondary bar appeared **N ticks** after the primary new-bar tick |
| `-1` (window-exceeded) | Secondary bar did **not** advance within 50 ticks |

In **Open Prices Only** mode (1 tick per bar), `-1` means the secondary bar
will only advance on the **next** primary bar tick — a full **+1 bar = +15 min** delay.

## Key metric — Lagged (%)

**Lagged** = % of primary new-bar events where the secondary bar had NOT yet
advanced. This combines within-window lag (`lag > 0`) and window-exceeded (`lag = -1`),
giving a single number that answers: *how often does signal logic see stale bar data?*

---

## Summary table — Lagged (%) per symbol per model

| Symbol | Every Tick (Model=0) | Real Ticks (Model=4) | 1-Min OHLC (Model=1) | Open Prices Only (Model=2) |
|---------|---------|---------|---------|---------|
| GBPUSD | **99.7%** | **54.6%** | **99.7%** | **100.0%** |
| USDCHF | **99.8%** | **60.4%** | **99.8%** | **100.0%** |
| USDCAD | **99.7%** | **62.5%** | **99.7%** | **100.0%** |
| GBPJPY | **99.6%** | **42.7%** | **99.6%** | **100.0%** |
| EURJPY | **0.6%** | **35.8%** | **0.6%** | **0.4%** |
| EURAUD | **0.7%** | **33.0%** | **0.7%** | **0.4%** |

*Lagged = secondary bar had not yet advanced (lag > 0, or window-exceeded).*  
*In Open Prices Only, window-exceeded ≡ 1 full bar (15 min) behind the primary.*

---

## Per-model detail

### Every Tick (Model=0)  (`et`)

| Symbol | Bars | Immediate (lag=0) | **Lagged total** | ↳ within-window (lag>0) | ↳ window-exceeded (lag=−1) | Mean ticks¹ | Max ticks¹ |
|--------|------|-------------------|------------------|------------------------|---------------------------|-------------|------------|
| GBPUSD | 24,751 | 0.3% | **99.7%** | 99.7% | 0.0% | 1.04 | 41 |
| USDCHF | 24,751 | 0.2% | **99.8%** | 99.8% | 0.0% | 1.06 | 49 |
| USDCAD | 24,751 | 0.3% | **99.7%** | 99.7% | 0.0% | 1.05 | 40 |
| GBPJPY | 24,751 | 0.4% | **99.6%** | 99.6% | 0.0% | 1.02 | 37 |
| EURJPY | 24,751 | 99.4% | **0.6%** | 0.6% | 0.0% | 2.46 | 15 |
| EURAUD | 24,751 | 99.3% | **0.7%** | 0.7% | 0.0% | 3.15 | 48 |

*¹ Mean/max ticks counted only among bars where lag > 0 (within-window). Window-exceeded bars are excluded from tick statistics.*

### Real Ticks (Model=4)  (`rt`)

| Symbol | Bars | Immediate (lag=0) | **Lagged total** | ↳ within-window (lag>0) | ↳ window-exceeded (lag=−1) | Mean ticks¹ | Max ticks¹ |
|--------|------|-------------------|------------------|------------------------|---------------------------|-------------|------------|
| GBPUSD | 24,791 | 45.4% | **54.6%** | 54.5% | 0.1% | 1.73 | 39 |
| USDCHF | 24,791 | 39.6% | **60.4%** | 60.4% | 0.0% | 2.12 | 48 |
| USDCAD | 24,791 | 37.5% | **62.5%** | 62.4% | 0.0% | 2.29 | 47 |
| GBPJPY | 24,791 | 57.3% | **42.7%** | 42.6% | 0.1% | 1.46 | 39 |
| EURJPY | 24,791 | 64.2% | **35.8%** | 35.8% | 0.0% | 1.45 | 45 |
| EURAUD | 24,791 | 67.0% | **33.0%** | 32.9% | 0.1% | 1.50 | 46 |

*¹ Mean/max ticks counted only among bars where lag > 0 (within-window). Window-exceeded bars are excluded from tick statistics.*

### 1-Min OHLC (Model=1)  (`m1`)

| Symbol | Bars | Immediate (lag=0) | **Lagged total** | ↳ within-window (lag>0) | ↳ window-exceeded (lag=−1) | Mean ticks¹ | Max ticks¹ |
|--------|------|-------------------|------------------|------------------------|---------------------------|-------------|------------|
| GBPUSD | 24,751 | 0.3% | **99.7%** | 99.7% | 0.0% | 1.01 | 37 |
| USDCHF | 24,751 | 0.2% | **99.8%** | 99.8% | 0.0% | 1.02 | 13 |
| USDCAD | 24,751 | 0.3% | **99.7%** | 99.7% | 0.0% | 1.01 | 6 |
| GBPJPY | 24,751 | 0.4% | **99.6%** | 99.6% | 0.0% | 1.01 | 14 |
| EURJPY | 24,751 | 99.4% | **0.6%** | 0.6% | 0.0% | 1.40 | 8 |
| EURAUD | 24,751 | 99.3% | **0.7%** | 0.7% | 0.0% | 2.15 | 34 |

*¹ Mean/max ticks counted only among bars where lag > 0 (within-window). Window-exceeded bars are excluded from tick statistics.*

### Open Prices Only (Model=2)  (`op`)

> **Open Prices Only note:** MT5 generates exactly 1 tick per bar. `window-exceeded` (lag = -1) means the secondary bar did **not** advance on the same tick as the primary. Since there are no further ticks in this bar period the secondary will only update on the next M15 bar — **+15 minutes of signal delay**.

| Symbol | Bars | Immediate (lag=0) | **Lagged total** | ↳ within-window (lag>0) | ↳ window-exceeded (lag=−1) | Mean ticks¹ | Max ticks¹ |
|--------|------|-------------------|------------------|------------------------|---------------------------|-------------|------------|
| GBPUSD | 24,751 | 0.0% | **100.0%** | 0.0% | 100.0% | 1.00 | 1 |
| USDCHF | 24,751 | 0.0% | **100.0%** | 0.0% | 100.0% | 1.00 | 1 |
| USDCAD | 24,751 | 0.0% | **100.0%** | 0.0% | 100.0% | 1.00 | 1 |
| GBPJPY | 24,751 | 0.0% | **100.0%** | 0.0% | 100.0% | 1.00 | 1 |
| EURJPY | 24,751 | 99.6% | **0.4%** | 0.0% | 0.4% | 0.00 | 0 |
| EURAUD | 24,751 | 99.6% | **0.4%** | 0.0% | 0.4% | 0.00 | 0 |

*¹ Mean/max ticks counted only among bars where lag > 0 (within-window). Window-exceeded bars are excluded from tick statistics.*

---

## Verdict

**Confirmed:** non-EUR off-chart symbols (GBPUSD, USDCHF, USDCAD, GBPJPY) show bar sync lag on virtually every M15 bar — up to **100.0%** of bars across all tested modelling modes.

In **Every Tick, Real Ticks, and 1-Min OHLC** modes the lag is almost always **exactly 1 tick**. The secondary bar becomes visible one tick after the primary fires. Signal logic reading `iTime(secondary, tf, 0)` inside `OnTick` will see the previous bar's timestamp on that one tick, causing a brief 1-tick data misalignment. In high-resolution models this resolves within the same M15 bar and has a minor practical impact.

In **Open Prices Only** mode the impact is fundamentally different. MT5 generates exactly **1 tick per M15 bar**. When the primary (EURUSD) bar fires, non-EUR secondary bars have not yet advanced — and since there are no further ticks in this bar period, they **never** catch up within the tracking window (100% of bars show window-exceeded). The secondary bar only becomes current on the **next** primary tick — i.e. the next M15 bar, 15 minutes later. Any signal that reads off-chart `iTime()` or `iOpen/iClose` on the primary new-bar event will silently operate on stale data, producing a systematic **+1-bar (15-minute) entry delay** for all non-EUR pairs.

**Recommended architecture:** run each symbol in its own independent MT5 backtest — where it is the primary symbol — and merge the resulting trade schedules externally. This eliminates the off-chart bar sync dependency entirely. A gate of the form `if (iTime(sym, tf, 0) == expected_bar) { ... }` reduces stale reads in tick modes but does not help in Open Prices Only mode where the secondary bar never advances within the bar period.

---

## Implications for portfolio EA design

| Approach | Assessment |
|----------|------------|
| Multi-symbol live signals in one EA, reading `iTime()` off-chart | **Use with caution** — secondary bars lag by 1 tick (~99.7% of M15 bars for non-EUR pairs); in Open Prices Only this becomes a full +15-minute delay |
| Gate off-chart reads on `iTime(sym, tf, 0) == expected_bar` | **Partial mitigation** — eliminates stale reads in tick models, but unhelpful in Open Prices Only where the secondary bar never advances mid-bar |
| Independent per-symbol MT5 runs + externally merged schedule | **Recommended** — each backtest uses its own symbol as primary; zero off-chart dependency |

---

## Charts

| File | Description |
|------|-------------|
| `lag_heatmap.png` | Colour-coded grid: lagged % at a glance (symbols × models) |
| `lag_overview.png` | Grouped bar chart comparing all symbols and models |
| `lag_distributions.png` | Tick-level histogram for tick-based models (et / rt / m1) |

---

## Data files

- `btp_EURUSD_PERIOD_M15_et.csv` — 53,214 rows — Every Tick (Model=0)
- `btp_EURUSD_PERIOD_M15_rt.csv` — 82,435 rows — Real Ticks (Model=4)
- `btp_EURUSD_PERIOD_M15_m1.csv` — 50,722 rows — 1-Min OHLC (Model=1)
- `btp_EURUSD_PERIOD_M15_op.csv` — 24,758 rows — Open Prices Only (Model=2)