
# Session Levels (MetaTrader 5 and MetaTrader 4)
A free indicator that draws the **Asia, London and New York session boxes**, the **previous day and previous week high and
low** and **today's open**, with session times entered in **New York time** and converted from your broker's clock, so
daylight saving is right on any broker. It alerts when a closed bar breaks the previous day or the Asia range. Source for both
editions, a Python reference of the rules, and the tests that were used to check that MT5, MT4 and Python all agree.
**Website:** [allanninal.dev/mql5 (indicators)](https://www.allanninal.dev/mql5/#indicators) · [MT4 editions](https://www.allanninal.dev/mql5/#mt4) · [allanninal.dev](https://www.allanninal.dev)
· **Contact:** message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).

> It shows levels, not entries. It places no trades and gives no buy or sell advice. Nothing here is a profit claim
> (see [Honest limits](#honest-limits)).
## Get it on the MQL5 Market
| Edition | Platform | Price | Link |
|---|---|---|---|
| Session Levels | MT5 | free | |
| Session Levels MT4 | MT4 | free | |
The Market builds are the same calculation as the source in this repository.
## Contents
1. [What it is and who it is for](#what-it-is-and-who-it-is-for)
2. [Why it was built](#why-it-was-built)
3. [How it works](#how-it-works)
4. [Non-repainting: the proof and how it was tested](#non-repainting-the-proof-and-how-it-was-tested)
5. [Architecture and buffers](#architecture-and-buffers)
6. [Inputs, alerts, install and compile](#inputs-alerts-install-and-compile)
7. [Run the Python reference and the tests](#run-the-python-reference-and-the-tests)
8. [Honest limits](#honest-limits)
9. [Licence](#licence)
## What it is and who it is for
**What:**
- Three **session boxes** (Asia 19:00 to 04:00, London 03:00 to 12:00, New York 08:00 to 17:00, New York time). The box edges
are the session high and low; the current session grows live.
- **Previous day high and low** (solid), **previous week high and low** (dashed) and **today's open** (dotted), as lines per
day; on the current day they run to the right edge with price labels.
- **Alerts** on closed bars: a close above the previous day high, below the previous day low, and, after the Asia session,
above its high or below its low. Each kind at most once per trading day.
- Sessions and the open on M1 to H4; above H4 only the previous day and week levels.
**For:** traders who mark session ranges and daily or weekly levels by hand and want them drawn automatically, correctly
shifted for their broker and for daylight saving, on any symbol. Also for developers: seven documented buffers for `iCustom`.
**Not for:** anyone who wants buy and sell arrows or an automatic system. This is a level map.
| | |
|---|---|
|  |  |
|  |  |
Image notes: charts are drawn by the Python reference on broker history (the same numbers the indicator shows); the alert text
is the indicator's real text but the pop-up and phone card around it are mock-ups; the Data Window panel is a mock-up of the
buffer map. They are illustrations, not terminal captures. More images, including the MT4 versions, are in
[`docs/images/`](docs/images).
## Why it was built
Session boxes and previous-day levels are among the most hand-drawn things on a chart. The reasons, with the evidence we have:
- **Brokers differ in clock and daylight saving.** A box that says "London 03:00 to 12:00" is only right if the indicator
knows whether your server is on a New York close, EET or a fixed offset, and which weeks each side has shifted. The point
of this indicator is to get that right and to say how.
- **Demand is steady but small-ticket.** On the MQL5 Freelance board, 72 of 2,014 scraped jobs (Nov 2025 to Oct 2026) were
about session logic (Asian range, killzones, session breakouts); typical budgets are 30 to 100 USD. Free session tools
already sit in the Market's top lists, so this is a free reference, not a claim of a gap.
- **Provable behaviour.** Alerts use closed bars only and every number here comes from an executed check.
```mermaid
mindmap
root((Session Levels))
Problem
Hand-drawn session boxes
Broker clocks and daylight saving differ
Previous day and week levels drift
Design rules
Times entered in New York time
Clock detected or chosen
Alerts on closed bars, once per day per kind
Proof
Python reference
7 buffers compared bar by bar
Tester repaint probe
Market validation
```
## How it works
### The rules, step by step
1. **Broker clock to New York time.** A trading day runs from 17:00 to 17:00 New York time. Each bar's broker time is
converted with one of four clocks: *Auto* (compares the live server time with UTC and picks one of the others; in the
Strategy Tester it cannot tell and assumes New York close), *New York close* (UTC+2 / +3 with US daylight saving),
*EET* (UTC+2 / +3 with European daylight saving) or a *fixed offset*. US daylight saving runs from the second Sunday of
March to the first Sunday of November; European from the last Sunday of March to the last Sunday of October. In the
weeks when only one side has shifted, an EET broker's chart shows the sessions an hour earlier.
2. **Sessions.** A bar belongs to a session by its **open time**. A session that crosses midnight (Asia) belongs to the
trading day it ends on. Box edges are the highest high and lowest low so far.
3. **Previous day and week.** The previous trading day's high and low, and the previous Monday-based week's. The first day and
week in the data have no "previous". On D1 and above the day and week are built from the chart's own bars.
4. **Today's open** is the first price of the trading day (17:00 New York).
5. **Signals** (buffer 6, closed bars only, each kind at most once per trading day): `+1` the first close above the previous
day high, `-1` the first close below the previous day low, `+2` the first close above the Asia high after Asia ended, `-2`
the first close below the Asia low. If two kinds fire on one bar the priority is: previous day high, previous day low,
Asia high, Asia low.
### Per-bar algorithm
```mermaid
flowchart TD
A[Bar i] --> B[Broker time to New York time
by the chosen clock]
B --> C[Trading day and week of the bar]
C --> D[Update day and week high, low, open]
D --> E[Which session is the bar in? by open time]
E --> F[Update box high and low,
Asia range so far]
F --> G{Bar is closed?}
G -- no, live bar --> H[Draw only, no signal]
G -- yes --> I{Close beyond a level
not yet signalled today?}
I -- no --> Z[Nothing]
I -- yes --> J[Signal in buffer 6]
J --> K[Alert, once per bar, only when live]
```
### Trading-day states
```mermaid
stateDiagram-v2
[*] --> DayStart: 17:00 New York, today's flags cleared
DayStart --> AsiaRange: Asia (from 19:00) builds its high and low
AsiaRange --> RangeFixed: Asia ends (04:00)
DayStart --> PrevDayBreak: first close beyond the previous day high or low (+1 or -1, once each)
AsiaRange --> PrevDayBreak: same rule, any time of the day
RangeFixed --> AsiaBreak: first close beyond the Asia high or low (+2 or -2, once each)
PrevDayBreak --> DayEnd
AsiaBreak --> DayEnd
RangeFixed --> DayEnd: 17:00
DayEnd --> DayStart: levels roll to the next day
```
### Alert flow
```mermaid
sequenceDiagram
participant T as Terminal
participant I as Session Levels indicator
participant H as Alert helper
participant U as You
T->>I: OnCalculate (history load, prev_calculated = 0)
I->>I: recompute the levels and signals of every bar
I-->>H: nothing (never alert during history load)
T->>I: OnCalculate (a bar closes)
I->>I: test the bar that just closed
I->>H: SignalAlert(text, bar time, live = true)
alt newer than the last alert and not in the tester
H->>U: pop-up (optional sound, push, e-mail)
else same bar again or tester run
H-->>I: nothing sent
end
```
### How many signals each timeframe gives (measured)
Signal values (buffer 6) over the same XAUUSD history with default inputs. Source:
[`tests/results/parity-mt5.txt`](tests/results/parity-mt5.txt). This counts signals only; it says nothing about whether
breaking a level is worth trading.
```mermaid
xychart-beta
title "Signal values on XAUUSD, default inputs"
x-axis ["M15", "H1", "H4"]
y-axis "signal values" 0 --> 1400
bar [1165, 1038, 840]
```
Coarser bars give fewer signals because several breaks can share one bar and each kind fires once per day.

## Non-repainting: the proof and how it was tested
"Non-repainting" is easy to claim. Here is what was actually run. Raw result files are in [`tests/results/`](tests/results).
**1. Three implementations, compared bar by bar.** The Python reference ([`tests/reference.py`](tests/reference.py)) was written
from the rules above, not from the MQL code. The MT5 and MT4 indicators were dumped bar by bar (7 buffers) in the MetaTrader
Strategy Tester and [`tests/check_parity.py`](tests/check_parity.py) compared each dump with the reference. Pass means: on
every closed bar all seven buffers match (levels within 1e-9 relative, signals exactly, blank where the reference is blank).
| | MT5 | MT4 |
|---|---|---|
| dumps compared | 24 (4 symbols x M15/H1/H4 x 2 input sets) | 25 (the same 24 + one every-tick EURUSD H1) |
| bars compared | 652,332 | 509,573 |
| signal values compared | 24,804 | 19,342 |
| result | ALL PASS | ALL PASS |
Symbols: EURUSD, XAUUSD, GBPJPY, BTCUSD. Input sets: defaults; London 02:00 to 05:00 and New York 07:00 to 10:00 ("kill
zones"). The test server uses a New York close clock, which the reference assumes (see [Honest limits](#honest-limits)).
Sources: [`tests/results/parity-mt5.txt`](tests/results/parity-mt5.txt), [`tests/results/parity-mt4.txt`](tests/results/parity-mt4.txt).
The MT4 test history is shorter on the FX pairs, which is why the bar counts differ.
```mermaid
xychart-beta
title "Signal values compared, Python vs indicator (all 0 differences)"
x-axis ["MT5", "MT4"]
y-axis "signal values" 0 --> 28000
bar [24804, 19342]
```
**2. Repaint probe.** A probe records the signal buffer on each bar *at the moment it closes*, then reads the same bars again at
the end and counts any value that changed. It also counts any signal ever shown on the still-forming bar.
| Run | Closed bars checked | Signals seen at close | Changed afterwards | Signals on the live bar |
|---|---|---|---|---|
| MT5, EURUSD H1, every tick, 10,411,052 ticks | 4,644 | 345 | **0** | **0** |
| MT4, 25 runs combined | 506,172 | 19,020 | **0** | **0** |
Source: [`tests/results/repaint-summary.md`](tests/results/repaint-summary.md). The MT5 probe ran once (EURUSD H1, default
inputs); the MT4 probe ran on every dump run.
**3. Market validation.** Both Market builds passed the MQL5 Market's automated product validation before publication (the
Market's own check, not part of this repository).
```mermaid
flowchart LR
R[reference.py
Python, rules as code] --> P
S5[MT5 indicator] --> T5[Tester script
DumpIndicator
every bar to CSV]
S4[MT4 indicator] --> T4[Tester EA
SessionLevelsDump + Dump4.mqh
CSV + repaint log]
T5 --> P[check_parity.py
7 buffers, 1e-9]
T4 --> P
S5 --> RP[RepaintProbe EA
values at close vs final]
T4 --> RL[repaint log
per run]
P --> V{all pass?}
RP --> V
RL --> V
V -- yes --> M[Market validation]
V -- no --> F[fix, rerun everything]
```
### Why the design cannot repaint
- Signals are written only for closed bars; the forming bar's signal buffer is empty.
- A level of the previous day or week never changes once that day or week is over. A box edge of a session in progress does
move as price moves; that is the drawing of the live session, not a signal.
- Each signal kind fires at most once per trading day, and alerts fire only for the bar that just closed, after the history
load, once per bar, and never in the tester.
## Architecture and buffers
```mermaid
flowchart TB
subgraph MT5["mql5/Indicators/SessionLevels"]
A5[SessionLevels.mq5
inputs, clock, sessions, levels, drawing]
B5[SignalAlerts.mqh
alert inputs, TfName, SignalAlert]
A5 -- include --> B5
end
subgraph MT4["mql4/Indicators/SessionLevels"]
A4[SessionLevels.mq4]
B4[SignalAlerts.mqh
MT4 edition]
A4 -- include --> B4
end
subgraph TESTS[tests]
R[reference.py]
C[check_parity.py]
D5[mql5: DumpIndicator, RepaintProbe, Params.mqh]
D4[mql4: SessionLevelsDump.mq4, Dump4.mqh]
C --> R
end
D5 -. IndicatorCreate .-> A5
D4 -. iCustom .-> A4
```
```mermaid
classDiagram
class SessionLevels {
+InpDays int
+InpClock enum
+InpAsiaStart string
+InpShowPD bool
+OnInit()
+OnCalculate()
}
class SignalAlerts {
+InpAlertPopup bool
+InpAlertSound bool
+InpAlertPush bool
+InpAlertEmail bool
+SignalAlert()
}
class reference {
+session_levels()
}
SessionLevels --> SignalAlerts : alerts
reference ..> SessionLevels : same rules, compared by check_parity
```
### `iCustom` buffer table (for EA developers)
| Buffer | Content |
|---|---|
| 0 | previous day high |
| 1 | previous day low |
| 2 | previous week high |
| 3 | previous week low |
| 4 | Asia high so far (the current trading day's Asia session; empty before it starts) |
| 5 | Asia low so far |
| 6 | signal: +1 close above previous day high, -1 below previous day low, +2 above the Asia high, -2 below the Asia low |
| 7 to 19 | internal calculation buffers |
Empty value is `EMPTY_VALUE`. Read **closed bars** (shift 1 and higher). `iCustom` parameters follow the input order; in MT5
the `input group` headings also count as string parameters when you build a handle with `IndicatorCreate` (see
`tests/mql5/Include/IndTest/Params.mqh`). In MT4 the same seven buffers are available; a filled box is the session colour
blended into the chart background (MT4 has no translucency).
## Inputs, alerts, install and compile
| Input | Default | Meaning |
|---|---|---|
| Days to draw | 5 | Days that get boxes and lines |
| Draw Asia / London / New York | true | Per-session switch, each with its own start, end (HH:MM New York time) and colour |
| Session times | Asia 19:00-04:00, London 03:00-12:00, New York 08:00-17:00 | New York time |
| Label each box / Fill the boxes | true / true | Box text and fill |
| Previous day / previous week / today's open | true | Levels, each with a colour |
| Price labels on the levels / Line width | true / 1 | Appearance |
| Broker server clock | Auto | Auto, New York close, EET or Fixed offset |
| Broker offset from UTC, hours | 0 | Fixed offset only |
| Alert kinds | all four on | Previous day high / low, Asia high / low |
| Fakeout marker: on, markers | true, true | Marks a break of the overnight range that closes back inside |
| Fakeout range / window (New York time) | 17:00-02:00 / 02:00-11:00 | The overnight range and the window in which fakeouts are marked |
| Bars to close back inside | 2 | 1 = the break bar only, 2 = the break bar or the next |
| Daily-structure filter, fakeout colours | false, red / blue | Bearish only after lower highs and lows (bullish after higher); marker colours |
| Alert on a fakeout | true | Adds fakeouts to the alert kinds |
| Pop-up / sound / push / e-mail | true / false / false / false | Delivery (push needs your MetaQuotes ID) |
| Debug log for bug reports | false | Writes extra lines to the Experts tab, to attach when you report a problem |
An alert reads, for example, `Session Levels EURUSD M15: closed above previous day high 1.0850`.
**Install from source.** Copy `mql5/Indicators/SessionLevels/` into `/MQL5/Indicators/` (MT5) or
`mql4/Indicators/SessionLevels/` into `/MQL4/Indicators/` (MT4), open `SessionLevels.mq5` or `.mq4` in MetaEditor
and press Compile (F7). Both compile with 0 errors (checked with the MetaEditor of a MetaTrader 5 and a MetaTrader 4 build on
a Windows machine; the compiled files are not in the repository). Keep the two files of each edition together. The debug log needs the Chart Kit file as well: copy `mql5/Include/ChartKit/` (MT5) or `mql4/Include/ChartKit/` (MT4) into the `Include` folder of the data folder.
## Run the Python reference and the tests
```bash
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
```
```python
import sys; sys.path.insert(0, "tests")
from reference import session_levels
# t (epoch seconds, server time), o, h, l, c: arrays of bars, oldest first; the last bar is the live bar.
# Assumes a New York close server clock (server = New York + 7 h). Returns an (n, 7) array: the seven buffers.
out = session_levels(t, o, h, l, c, asia_start="19:00", asia_end="04:00")
```
To repeat the parity check on your own data:
1. Compile the indicator and the test tool for your platform (`tests/mql5/Scripts/DumpIndicator`, or
`tests/mt4/SessionLevelsDump.mq4` with `tests/mql4/Include/IndTest4/Dump4.mqh` in your `MQL4/Include/IndTest4/`; MT5 also
needs `tests/mql5/Include/IndTest/Params.mqh`).
2. Run it for each symbol and timeframe with the input sets in `tests/configs.tsv` (MT5) or `tests/mt4/configs.tsv` (MT4), on
a server whose clock is New York close. Each run writes one CSV per symbol and timeframe.
3. `python3 tests/check_parity.py ` (add `--mt4` for MT4 dumps). It prints PASS or FAIL per file.
4. Repaint: run `tests/mql5/Experts/RepaintProbe` in the MT5 Strategy Tester (every tick); MT4 writes its repaint log with the dump.
The price dumps used for the published numbers are not in this repository (they are broker price data).
## Honest limits
- **Levels, not entries.** A previous-day break is a fact about a close, not a prediction. No profit or accuracy claim is made;
the numbers are about *consistency* and *frequency*.
- **The parity tests cover one clock.** The Python reference assumes a New York close server clock, and the test server had
one. The other clocks (EET, fixed offset, Auto) follow the stated daylight-saving rules but were not compared against the
reference. In the Strategy Tester, Auto cannot detect the clock and assumes New York close, so choose it yourself there.
- **Sessions are conventions.** Session hours are the defaults above; change them if you use others.
- **A session box in progress moves** as price moves; only alerts and the signal buffer are closed-bar facts.
- **Above H4** only the previous day and week levels are drawn; on D1 and above they are built from the chart's own bars.
- **Scope of the tests.** Four symbols, three timeframes, two input sets, one MT5 repaint run. Other symbols, timeframes and
settings are not covered by the published numbers. The MT5 build was not run on a live chart as part of these numbers.
## Licence
Everything is © 2026 Allan Niñal under the
[PolyForm Noncommercial License 1.0.0](LICENSES/PolyForm-Noncommercial-1.0.0.md): source-available, free for personal and
other noncommercial use, not for commercial use. Each source file has an `SPDX-License-Identifier` line; the text is in
[`LICENSE`](LICENSE). The licence applies to this source, not to anything you build or trade with the indicator. Trading
involves risk; the software is provided as is.
---
**Website:** [allanninal.dev/mql5](https://www.allanninal.dev/mql5/#indicators) · [MT4](https://www.allanninal.dev/mql5/#mt4) · [allanninal.dev](https://www.allanninal.dev)
· Questions: message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).