Free SMC Zones indicator for MetaTrader 5 and 4: order blocks, fair value gaps, BOS/CHoCH, closed-bar zones, Python reference and parity tests.
  • MQL5 68.4%
  • MQL4 19.8%
  • Python 11.8%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Allan Niñal Allan Niñal 0b449de25e Free source regenerated: pop-up and push on by default
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 14:28:57 +08:00
docs/images SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00
LICENSES SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00
mql4 Free source regenerated: pop-up and push on by default 2026-10-31 14:28:57 +08:00
mql5 Free source regenerated: pop-up and push on by default 2026-10-31 14:28:57 +08:00
tests SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00
.gitignore SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00
LICENSE SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00
README.md Free source regenerated: pop-up and push on by default 2026-10-31 14:28:57 +08:00
requirements.txt SMC Zones: free MT5 and MT4 indicator source, Python reference, tests and README 2026-10-21 18:04:52 +08:00

SMC Zones logo

SMC Zones (MetaTrader 5 and MetaTrader 4)

A free smart-money-concepts indicator that draws order blocks, fair value gaps and BOS / CHoCH lines, with optional liquidity sweeps, a session filter and a higher-timeframe layer, and sends an alert when a zone is created, touched or mitigated. Every zone is decided on closed bars only and never changes afterwards. 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) · MT4 editions · allanninal.dev · Contact: message me on LinkedIn.

SMC Zones on XAUUSD H1

It is an indicator, not a strategy. It draws zones and lines and places no trades. Nothing here is a profit claim (see Honest limits).

Get it on the MQL5 Market

Edition Platform Price Link
SMC Zones MT5 free https://www.mql5.com/en/market/product/201880
SMC Zones MT4 MT4 free https://www.mql5.com/en/market/product/202375

The Market builds are the same calculation as the source in this repository.

Contents

  1. What it is and who it is for
  2. Why it was built
  3. How it works
  4. Non-repainting: the proof and how it was tested
  5. Architecture and buffers
  6. Inputs, alerts, install and compile
  7. Run the Python reference and the tests
  8. Honest limits
  9. Licence

What it is and who it is for

What: the usual smart-money vocabulary, with the rule for each word written down and tested:

  • BOS / CHoCH lines: a break of structure when a close goes beyond the last unbroken swing in the direction of the trend, a change of character when it reverses the previous break.
  • Order blocks: the zone left at the start of the leg that broke the swing.
  • Fair value gaps: a three-candle gap of at least k x ATR.
  • Optional liquidity sweeps (a wick beyond a swing that closes back inside), a session filter (only create zones and sweeps inside chosen server hours) and zones of a higher timeframe drawn as outlines.
  • A zone is mitigated by the rule you choose: touch, midpoint, far-edge wick, or a close beyond the far edge.

For: traders who use these concepts and want to know exactly when a zone is created and when it ends, and who want it to stay that way tomorrow. Also for developers who want 16 documented buffers to read with iCustom.

Not for: anyone who expects a zone to predict a reaction. A zone marks where a rule fired on closed bars.

mitigation rules alerts
higher timeframe off vs on inputs

Image notes: the 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/.

Why it was built

Smart-money indicators are everywhere, so this is not a gap-filling product. The reasons, stated with the evidence we actually have:

  • Crowded, and the claims cannot be checked. A scan of the MQL5 Market in October 2026 found many free and paid SMC tools (the best-known free one has over 50 reviews). Almost all say "non-repainting"; none we read published a test or stated when a zone is created. Several did not say what counts as "mitigated".
  • Demand exists but is spread thin. On the MQL5 Freelance board, 116 of 2,014 scraped jobs (Nov 2025 to Oct 2026) were in the SMC / order block / FVG / liquidity group, median budget 50 USD, about 30 applicants per job. People want it; the supply side is saturated.
  • So the offer is trust and tidiness, not features. Exact rules, a test that compares MT5, MT4 and an independent Python version bar by bar, and a probe that re-reads every buffer after the bar closed.
  • Free on purpose. Our own comparison said a new tool cannot be clearly better than the free leader on features, so it is free and its value is the evidence.
mindmap
  root((SMC Zones))
    Problem
      Many SMC tools, few stated rules
      Non-repainting claimed, rarely tested
      Unclear when a zone ends
    Design rules
      Decide on closed bars only
      Creation bar and mitigation bar set once
      You choose the mitigation rule
    Proof
      Python reference
      16 buffers compared bar by bar
      Tester repaint probe
      Market validation

How it works

The rules, step by step

Let O, H, L, C be a bar's open, high, low, close and i the bar index (oldest = 0). Direction d = +1 is bullish, -1 bearish. Everything at bar i is decided from bars 0..i only; the last bar of the chart (the forming one) is never used.

  1. ATR: Wilder's smoothing of the true range, seeded with the simple average of the first n values (default n = 14).
  2. Swing: bar p is a swing high if each of the s bars to its left has a strictly lower high and each of the s bars to its right a high not above it (a swing low the mirror). It is known at bar p + s, never earlier (default s = 3).
  3. BOS / CHoCH: a close beyond the newest unbroken swing high (bullish) or low (bearish). It is a CHoCH when the previous break went the other way, otherwise a BOS. The first break ever is a BOS.
  4. Order block: take the bar from the broken swing to the break bar with the lowest low (bullish) or highest high (bearish), call it org. If org is a candle against the move, the block candle is the last of the run of against-candles starting at org; otherwise the nearest against-candle among the 3 bars before org; otherwise org. Bullish zone: top = high of the block candle, bottom = lower of the two lows; bearish is the mirror.
  5. Fair value gap at bar i: bullish if L[i] - H[i-2] >= k x ATR[i], bearish if L[i-2] - H[i] >= k x ATR[i] (default k = 0.2). The zone is the gap.
  6. Liquidity sweep: a bar wicks beyond an untaken swing (at most 60 bars old) by at least 0.1 x ATR and closes back inside. Every swing wicked through is marked taken, whether or not the bar closed back inside.
  7. Mitigation (checked on bars after the zone's creation bar), your choice: touch (a wick reaches the near edge), midpoint, far-edge wick, or far-edge close (a bar closes beyond the far edge). A midpoint exactly on the wick counts, within a relative 1e-10, because (top + bottom) / 2 can be one unit in the last place off.
  8. Touch: a bar whose wick reaches the near edge of a live zone that was not already inside on the bar before.
  9. Session filter (optional): zones and sweeps are only created on bars whose open hour (server time) is inside [start, end).

Per bar the engine always runs in this order: mitigation and touch of older zones, sweeps, fair value gap, break and order block, and then it looks for the swing that became known s bars back.

Per-bar algorithm

flowchart TD
    A[Closed bar i] --> B[ATR update]
    B --> C{ATR ready?}
    C -- no --> S[Look for the swing s bars back]
    C -- yes --> D[1 Older live zones: mitigated? touched?]
    D --> E[2 Liquidity sweep of an untaken swing?]
    E --> F[3 Fair value gap ending at bar i?]
    F --> G[4 Close beyond the newest unbroken swing?]
    G -- yes --> H[BOS or CHoCH, build the order block]
    G -- no --> S
    H --> S
    S --> W[Write the 16 buffers for bar i]
    W --> X{Last closed bar and not history load?}
    X -- yes --> Y[Alert for the events of bar i]
    X -- no --> Z[Done]

Zone life cycle

stateDiagram-v2
    [*] --> Live: created on a closed bar (never earlier)
    Live --> Live: price outside the zone
    Live --> Touched: a wick reaches the near edge for the first time
    Touched --> Live: price leaves the zone
    Touched --> Touched: re-enters, counted again
    Live --> Mitigated: the chosen rule fires on a closed bar
    Touched --> Mitigated: the chosen rule fires on a closed bar
    Mitigated --> [*]: end bar is fixed, kept only if you ask

Alert flow

sequenceDiagram
    participant T as Terminal
    participant I as SMC Zones indicator
    participant H as Alert helper
    participant U as You
    T->>I: OnCalculate (history load, prev_calculated = 0)
    I->>I: run the engine over every closed bar
    I-->>H: nothing (never alert during history load)
    T->>I: OnCalculate (a new bar closes)
    I->>I: step the engine on the bar that just closed
    I->>H: SignalAlert(events of that bar, bar time, live = true)
    alt newer than the last alert and not in the tester
        H->>U: one message with all events (pop-up, sound, push, e-mail)
    else same bar again or tester run
        H-->>I: nothing sent
    end

How many zones the settings make (measured)

Zones created per 1,000 bars on XAUUSD H1 broker history, for three of the input sets used in the tests (order blocks plus fair value gaps). Source: tests/results/parity-mt5.txt (68,410 bars each). This counts zones only. It says nothing about whether they work.

xychart-beta
    title "Zones created per 1,000 bars, XAUUSD H1"
    x-axis ["Defaults", "Swing 5 + session 7-16", "Swing 2, gap 0.5 ATR"]
    y-axis "zones per 1,000 bars" 0 --> 220
    bar [186.2, 64.7, 149.2]

Defaults: swing 3, ATR 14, gap 0.2 x ATR, order block mitigated by a close, gap by a wick. Second: swing 5, touch rules, session filter 07:00-16:00. Third: swing 2, ATR 10, gap 0.5 x ATR, far-edge wick and close rules.

swing strength

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/.

1. Three implementations, compared bar by bar. The Python reference (tests/reference.py) states each rule above in code. The MT5 and MT4 indicators were dumped bar by bar (all 16 buffers) in the MetaTrader Strategy Tester and tests/check_parity.py compared each dump with the reference. Pass means: on every closed bar every buffer is equal (prices within 1e-9 relative, counts exactly, empty where the reference has nothing).

MT5 MT4
dumps compared 36 (4 symbols x M15/H1/H4 x 3 input sets) 37 (the same 36 + one every-tick EURUSD H1)
bars compared 1,122,912 761,453
order blocks 70,816 47,570
fair value gaps 78,770 61,946
zones mitigated 144,950 105,899
BOS / CHoCH 80,616 54,733
liquidity sweeps 41,441 26,013
result ALL PASS ALL PASS

Symbols: EURUSD, XAUUSD, GBPJPY, BTCUSD. Input sets: defaults; swing 5 with touch rules and a session filter; swing 2 with a 0.5 x ATR gap and far-edge rules. Sources: tests/results/parity-mt5.txt, tests/results/parity-mt4.txt. The MT5 and MT4 histories differ in length on the FX pairs, which is why the bar counts differ.

xychart-beta
    title "Items compared, Python vs indicator (all 0 differences)"
    x-axis ["Order blocks", "Gaps", "Mitigated", "BOS/CHoCH", "Sweeps"]
    y-axis "items compared (thousands)" 0 --> 160
    bar [70.8, 78.8, 145.0, 80.6, 41.4]
    line [47.6, 61.9, 105.9, 54.7, 26.0]

Bars: MT5. Line: MT4.

2. Repaint probe. A probe records which signal buffers are set 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 13,988 0 0
MT4, 37 runs combined 756,852 129,285 0 0

Source: tests/results/repaint-summary.md. The MT5 probe ran once (EURUSD H1, default inputs); the MT4 probe ran on every dump run. "Signals" counts every set value among the probed buffers (new zones, structure breaks, sweeps).

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).

flowchart LR
    R[reference.py<br/>Python, rules as code] --> P
    S5[MT5 indicator] --> T5[Tester script<br/>DumpIndicator<br/>every bar to CSV]
    S4[MT4 indicator] --> T4[Tester EA<br/>SMCZonesDump + Dump4.mqh<br/>CSV + repaint log]
    T5 --> P[check_parity.py<br/>16 buffers, 1e-9]
    T4 --> P
    S5 --> RP[RepaintProbe EA<br/>values at close vs final]
    T4 --> RL[repaint log<br/>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

  • The engine is a state machine: Step(i) reads bars 0..i only, so what it decided about bar i cannot change later.
  • A zone's creation bar and its mitigation bar are written once. A swing is only used s bars after it formed.
  • The forming bar is never stepped; its buffers are always empty.
  • Alerts fire only for the bar that just closed, only after the history load, once per bar, and never in the tester.
  • What does move: the right edge of the drawing (live zones are extended a few bars past the last bar). That is the drawing, not a signal.

Architecture and buffers

flowchart TB
    subgraph MT5["mql5/Indicators/SMCZones"]
        A5[SMCZones.mq5<br/>inputs, buffers, drawing, alerts]
        E5[SmcEngine.mqh<br/>CSmcEngine state machine]
        B5[SignalAlerts.mqh<br/>alert inputs, TfName, SignalAlert]
        A5 -- include --> E5
        A5 -- include --> B5
    end
    subgraph MT4["mql4/Indicators/SMCZones"]
        A4[SMCZones.mq4]
        E4[SmcEngine.mqh<br/>exact copy]
        B4[SignalAlerts.mqh<br/>MT4 edition]
        A4 -- include --> E4
        A4 -- include --> B4
    end
    subgraph TESTS[tests]
        R[reference.py]
        C[check_parity.py]
        D5[mql5: DumpIndicator, RepaintProbe, Params.mqh]
        D4[mql4: SMCZonesDump.mq4, Dump4.mqh]
        C --> R
    end
    D5 -. IndicatorCreate .-> A5
    D4 -. iCustom .-> A4
classDiagram
    class SMCZones {
        +InpSwing int
        +InpATR int
        +InpOBMit enum
        +InpFVGMin double
        +InpFVGMit enum
        +InpHTF timeframe
        +OnInit()
        +OnCalculate()
        +Redraw()
        +Events()
    }
    class CSmcEngine {
        +zones SSmcZone[]
        +breaks SSmcBreak[]
        +sweeps SSmcSweep[]
        +Reset()
        +Step(i)
    }
    class SignalAlerts {
        +InpAlertPopup bool
        +InpAlertSound bool
        +InpAlertPush bool
        +InpAlertEmail bool
        +SignalAlert()
    }
    class reference {
        +smc_zones()
    }
    SMCZones --> CSmcEngine : one for the chart, one for the higher timeframe
    SMCZones --> SignalAlerts : alerts
    reference ..> CSmcEngine : same rules, compared by check_parity

iCustom buffer table (for EA developers)

All 16 buffers are data only (nothing is plotted); the zones are chart objects. Empty value is EMPTY_VALUE. Read closed bars (shift 1 and higher). A buffer is set on the bar where the event was decided.

Buffer Content
0 / 1 bullish order block created on this bar: top / bottom
2 / 3 bearish order block created on this bar: top / bottom
4 / 5 bullish fair value gap created on this bar: top / bottom
6 / 7 bearish fair value gap created on this bar: top / bottom
8 structure: +1 BOS up, -1 BOS down, +2 CHoCH up, -2 CHoCH down
9 the broken swing's price
10 sweep of a low (the swing's price), bar closed back above
11 sweep of a high (the swing's price), bar closed back below
12 number of zones mitigated on this bar
13 number of live zones touched for the first time on this bar
14 / 15 live bullish / bearish zones after this bar

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). MT4 has no input groups, and its buffers are read with the usual series shift.

Inputs, alerts, install and compile

Input Default Meaning
Swing strength 3 Bars on each side that make a swing
ATR period 14 Wilder ATR length
Draw order blocks / Order block is mitigated when true / far edge, close Zone rule
Draw fair value gaps / Minimum gap, x ATR true / 0.20 Gap size
Gap is mitigated when far edge, wick Zone rule
Mark liquidity sweeps / Swing looked back / Wick beyond the swing false / 60 bars / 0.10 x ATR Sweeps
Only create zones and sweeps in the session false (07:00 to 17:00 server hour) Session filter
Also draw zones of this timeframe current (off) Higher-timeframe outlines, completed bars only
BOS / CHoCH lines, keep mitigated zones true / false Display
Draw zones created within / at most / extend live zones 1000 bars / 80 zones / 12 bars Display limits
Fill the zones, colours true Appearance
Alerts: new zone / touch / mitigated / BOS-CHoCH / sweep true / true / false / true / false Which events alert
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

Install from source. Copy mql5/Indicators/SMCZones/ into <terminal data folder>/MQL5/Indicators/ (MT5) or mql4/Indicators/SMCZones/ into <data folder>/MQL4/Indicators/ (MT4), open SMCZones.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 three 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

python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
import sys; sys.path.insert(0, "tests")
from reference import smc_zones
# t, o, h, l, c are numpy arrays of bar times (epoch seconds) and OHLC, oldest first; the last bar is the forming bar
buffers, zones, breaks, sweeps = smc_zones(t, o, h, l, c, swing=3, atr=14)

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/SMCZonesDump.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). Each run writes one CSV per symbol and timeframe, tagged with the config name.
  3. python3 tests/check_parity.py <folder with the CSVs> (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

  • Zones are not predictions. Nothing here says a zone holds, that a CHoCH reverses the market, or that a sweep marks a turn. The numbers above are about consistency and frequency, never profit.
  • One reading of smart-money concepts. There is no standard definition. These are written-down, testable rules; another tool's order block can be a different candle and both can be called correct.
  • Most zones end. In the MT5 test dumps 144,950 of the 149,586 order blocks and gaps created (about 97%) were later mitigated. That is how the rules work, not a promise about any one zone.
  • Lag is built in. A swing is only known s bars after it formed, so BOS / CHoCH and the order block appear on the bar that breaks it, not when the turn started.
  • Higher-timeframe zones use completed bars of that timeframe only, so they appear when that bar closes.
  • Scope of the tests. Four symbols, three timeframes, three 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: 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. 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 · MT4 · allanninal.dev · Questions: message me on LinkedIn.