427 lines
20 KiB
Markdown
<p align="center"><img src="docs/images/logo.png" alt="Signal Trader logo" width="120"></p>
| |||
| |||
# Signal Trader (MetaTrader 5)
| |||
| |||
A free Expert Advisor that automates the **closed-bar signals** of UT Bot, SuperTrend or Range Filter: one source at a time, an
| |||
ATR stop, a fixed 0.01 lot. It trades live on **one validated combination only**: XAUUSD H4 with SuperTrend. Source, a Python
| |||
statement of the three engines and of the trade rule, tests, and the honest result of the screen it came from: **one pass in 444
| |||
tests, which is about what chance alone gives**.
| |||
| |||
**MetaTrader 5 only.** The MT4 port reproduced the EA exactly but **failed its publish gate** (t 1.74 against a required 2.0), so
| |||
there is no MT4 edition and no MT4 code here. See [The MT4 gate](#the-mt4-gate-failed).
| |||
| |||
**Website:** [allanninal.dev/mql5 (Expert Advisors)](https://www.allanninal.dev/mql5/#eas) · [allanninal.dev](https://www.allanninal.dev)
| |||
· **Contact:** message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).
| |||
| |||

| |||
| |||
> This EA promises no profit. It automates signals and is not a strategy with a proven edge. Every number here is a backtest.
| |||
| |||
## Get it on the MQL5 Market
| |||
| |||
| Edition | Platform | Price | Link |
| |||
|---|---|---|---|
| |||
| Signal Trader | MT5 | free | <https://www.mql5.com/en/market/product/201967> |
| |||
| Signal Trader Pro | MT5 | paid | <https://www.mql5.com/en/market/product/201969> |
| |||
| MT4 editions | MT4 | n/a | none: the MT4 port failed its publish gate |
| |||
| |||
The Market build lists a fourth signal source whose calculation is not part of this repository; the validated preset uses
| |||
SuperTrend. The Pro edition is a separate, paid product that is **not** part of this repository (see [Free vs Pro](#free-vs-pro)).
| |||
| |||
## 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. [What the screen does and does not show](#what-the-screen-does-and-does-not-show)
| |||
5. [The MT4 gate failed](#the-mt4-gate-failed)
| |||
6. [How it was tested](#how-it-was-tested)
| |||
7. [Architecture](#architecture)
| |||
8. [Inputs, the preset, install and compile](#inputs-the-preset-install-and-compile)
| |||
9. [Run the Python reference and the tests](#run-the-python-reference-and-the-tests)
| |||
10. [Honest limits](#honest-limits)
| |||
11. [Free vs Pro](#free-vs-pro)
| |||
12. [Licence and credits](#licence-and-credits)
| |||
| |||
## What it is and who it is for
| |||
| |||
**What:** the EA reads one signal per closed bar from an embedded engine (UT Bot, SuperTrend or Range Filter), and acts on it at
| |||
the next bar's open. An opposite position is closed and reversed, a same-side signal is ignored, otherwise a market order goes out
| |||
with a stop of `stop x ATR(14)` of the signal bar and no target by default. The lot is 0.01, or the broker's minimum when that is
| |||
larger. One position per symbol and magic number; no grid, martingale or averaging. A panel shows the source, the chart, the preset
| |||
state, the last signal and the position.
| |||
| |||
**Live safety gate:** live, the EA opens orders only on the one **validated preset**: XAUUSD, H4, SuperTrend. On any other symbol,
| |||
timeframe or source the panel says "not validated", the EA raises one alert and sends no new orders. The Strategy Tester accepts
| |||
any combination so the MQL5 Market validator and you can try it; a result outside the preset is not validated.
| |||
| |||
**For:** traders who want these standard signals executed by rule on gold H4 and who read the evidence first; developers who want a
| |||
small, tested EA with Python statements of its engines.
| |||
| |||
**Not for:** anyone expecting a proven edge.
| |||
| |||

| |||
| |||
Image notes: both images are drawn by a script from the Python simulation of the Strategy Tester run on broker history; they are
| |||
illustrations, not terminal captures.
| |||
| |||
## Why it was built
| |||
| |||
The motivations, stated with the evidence we actually have:
| |||
| |||
- **A screen, run honestly.** Instead of picking a favourite indicator, 444 combinations of symbol, timeframe and signal source were
| |||
tested out of sample with settings frozen in-sample, and the EA ships only for what passed both the Python backtest and the
| |||
MetaTrader 5 Strategy Tester. One combination did.
| |||
- **Weak demand, stated plainly.** Our October 2026 market research found that named classic indicators are requested only rarely
| |||
by name, and signal-execution EAs are plentiful. This is a free reference implementation of three standard signals with an
| |||
unusually strict "trade only what passed" rule, not a claim of an unmet need.
| |||
- **Provable behaviour.** The three engines and the trade rule are stated in [`tests/reference.py`](tests/reference.py) and compared
| |||
with the research simulation, trade for trade.
| |||
| |||
```mermaid
| |||
mindmap
| |||
root((Signal Trader))
| |||
Origin
| |||
444 tests of symbol, timeframe and source
| |||
Settings frozen in-sample first
| |||
One pass, chance expects 2.55
| |||
Design rules
| |||
Closed bars only, no repaint
| |||
Next open, ATR stop, fixed lot
| |||
Live only on the validated preset
| |||
Proof
| |||
Python statement of the engines and the rule
| |||
Same trades as the research simulation
| |||
MT4 gate failed and said so
| |||
```
| |||
| |||
## How it works
| |||
| |||
### The three signal engines
| |||
| |||
Each engine is fed one **closed** bar at a time and reports a signal for that bar; the last bar of an array is never signalled.
| |||
`ATR_n` is Wilder's RMA of the true range, seeded with the simple average of the first `n` values.
| |||
| |||
1. **UT Bot** (key `k`, ATR period `n`): `nLoss = k x ATR_n`; the line `T` on the close rises with price and falls with price, never
| |||
against it; buy when `C > T` and `C[1] <= T[1]`, sell on the mirror. Optional: only with the EMA(200) trend.
| |||
2. **SuperTrend** (ATR period `n`, multiplier `m`): bands `hl2 -/+ m x ATR_n` that only tighten while the close respects them; the
| |||
trend flips when the close passes the opposite band; a signal is a flip. Optional: only with the EMA(200) trend.
| |||
3. **Range Filter** (period `p`, multiplier `m`): `range = EMA(EMA(|C - C[1]|, p), 2p - 1) x m`; the filter moves only when price travels
| |||
more than `range` from it; a signal is the first buy setup after a sell setup (and the reverse).
| |||
| |||
### The trade rule
| |||
| |||
1. **Signal** on closed bar `i`. It is acted on at the open of bar `i+1` (or the first tradable tick if the market is closed).
| |||
2. **Position.** Opposite: close it at that open and reverse. Same side: ignore. None: open.
| |||
3. **Order.** A buy at the ask, a sell at the bid; stop `= fill -/+ stop x ATR(14)` of bar `i` (never closer than the broker's stop and
| |||
freeze levels plus the spread); optional target `= x times the stop distance` (off by default).
| |||
4. **Checks.** Refused if the stop would lose more than 50% of equity at 0.01 lot, if free margin is short, if Algo Trading is off,
| |||
or if the market is closed or the preset is not validated (live).
| |||
| |||
### Per-bar algorithm
| |||
| |||
```mermaid
| |||
flowchart TD
| |||
A[New bar] --> B[Feed the bars that closed since the last call to the engine]
| |||
B --> C{Signal on the newest closed bar?}
| |||
C -- no --> Z[Nothing]
| |||
C -- yes --> D{Market open?}
| |||
D -- no --> P[Keep the signal for this bar and retry on the next ticks]
| |||
D -- yes --> E{Position open?}
| |||
E -- same side --> Z
| |||
E -- opposite --> F[Close it at the open, then continue as flat]
| |||
E -- none --> G
| |||
F --> G{Algo Trading on, preset validated, margin ok?}
| |||
G -- no --> Y[Skipped, reason on the panel]
| |||
G -- yes --> H[Market order, stop = stop x ATR 14 of the signal bar]
| |||
```
| |||
| |||
### Position states
| |||
| |||
```mermaid
| |||
stateDiagram-v2
| |||
[*] --> Flat
| |||
Flat --> Long: buy signal, order at the next open
| |||
Flat --> Short: sell signal, order at the next open
| |||
Long --> Short: sell signal, close and reverse at the open
| |||
Short --> Long: buy signal, close and reverse at the open
| |||
Long --> Long: same-side signal ignored
| |||
Short --> Short: same-side signal ignored
| |||
Long --> Flat: stop hit
| |||
Short --> Flat: stop hit
| |||
```
| |||
| |||
### From signal to order
| |||
| |||
```mermaid
| |||
sequenceDiagram
| |||
participant T as Terminal
| |||
participant E as EA
| |||
participant G as Engine
| |||
participant B as Broker
| |||
T->>E: first tick of a new bar
| |||
E->>T: closed bars since the last call
| |||
E->>G: Feed(open, high, low, close) per bar
| |||
G-->>E: signal for the newest closed bar
| |||
alt validated preset and checks pass
| |||
E->>B: market order with the ATR stop
| |||
B-->>E: filled
| |||
else not validated or blocked
| |||
E->>E: no order, reason on the panel
| |||
end
| |||
```
| |||
| |||
## What the screen does and does not show
| |||
| |||
Source: [`tests/results/screen-and-tester.md`](tests/results/screen-and-tester.md). Protocol written before any out-of-sample number was
| |||
looked at; settings frozen from in-sample data (2020-01 to 2023-06) per test; judged out of sample (2023-07 to 2026-09) on pass
| |||
criteria: at least 40 trades, mean R > 0 and t >= 2.0, profit factor >= 1.20, both halves positive, news-pause run still positive; the
| |||
Strategy Tester must meet them too.
| |||
| |||
| Stage | Tests | Passes in Python | Expected by chance |
| |||
|---|---:|---:|---:|
| |||
| First screen: 8 symbols x H1, H4, D1 x 4 sources | 96 | 1 | 0.76 |
| |||
| Extension: 29 more symbols, same rules | 348 | 0 | 1.78 |
| |||
| All | 444 | 1 | 2.55 |
| |||
| |||
```mermaid
| |||
xychart-beta
| |||
title "Passes observed vs expected by chance (backtest screen)"
| |||
x-axis ["First 96 tests", "All 444 tests"]
| |||
y-axis "passes" 0 --> 3
| |||
bar [1, 1]
| |||
bar [0.76, 2.55]
| |||
```
| |||
| |||
(First series observed, second expected by chance.) The Holm-adjusted p of the best result is 1.0.
| |||
| |||
The one pass: **XAUUSD H4, SuperTrend (ATR 10, multiplier 2.0), stop 3 ATR, no target.**
| |||
| |||
| | trades | mean R | t | PF |
| |||
|---|---:|---:|---:|---:|
| |||
| In-sample 2020-01 to 2023-06 | | | 0.51 | 1.11 |
| |||
| Python, out of sample | 212 | +0.179 | 2.05 | 1.52 |
| |||
| MetaTrader 5 Strategy Tester, out of sample | 211 | +0.180 | 2.06 | 1.52 |
| |||
| |||
Read it with care:
| |||
| |||
- **About what chance gives.** 444 tests make about 2.55 chance passes; one was observed.
| |||
- **Weak in-sample.** The same rule had t 0.51 and PF 1.11 in the setting period.
| |||
- **All buys, in a gold rally.** Out of sample: 106 buys earned +0.40 R each, 106 sells -0.04 R each.
| |||
- **Fragile neighbours.** ATR 7 gives t 1.57; doubling the spread gives t 2.00.
| |||
- **Not evidence of a lasting edge.** It passes the gate that was written down; the listing claims none.
| |||
| |||
```mermaid
| |||
xychart-beta
| |||
title "Mean R per trade by side, out of sample (106 trades each, backtest)"
| |||
x-axis ["Buys", "Sells"]
| |||
y-axis "R per trade" -0.1 --> 0.5
| |||
bar [0.40, -0.04]
| |||
```
| |||
| |||
## The MT4 gate failed
| |||
| |||
The MT4 port reproduced the EA exactly in the MT4 tester (XAUUSD H4, every tick): 212 of 212 signals and 211 of 211 trades identical
| |||
to Python, entries 100%, exits 98.6%. But the publish gate judges the MT4 trade list, and it missed criterion 2:
| |||
| |||
| Run | trades | mean R | t | PF |
| |||
|---|---:|---:|---:|---:|
| |||
| Python | 211 | +0.184 | 2.09 | 1.53 |
| |||
| MetaTrader 5 tester (real per-bar spreads) | 211 | +0.180 | 2.06 | 1.52 |
| |||
| MetaTrader 4 tester, two spread chunks (170 then 20 points) | 211 | +0.152 | 1.74 | 1.42 |
| |||
| MetaTrader 4 tester, one fixed 170-point spread | 211 | +0.149 | 1.70 | 1.41 |
| |||
| |||
```mermaid
| |||
xychart-beta
| |||
title "t-statistic of the mean R against the gate of 2.0"
| |||
x-axis ["Python", "MT5 tester", "MT4 two chunks", "MT4 fixed spread"]
| |||
y-axis "t" 0 --> 2.5
| |||
bar [2.09, 2.06, 1.74, 1.70]
| |||
```
| |||
| |||
MT4 cannot model real per-bar spreads. By the rule that an EA is published only if it passes both backtests, there is no MT4
| |||
edition, and this repository holds no MT4 code.
| |||
| |||
## How it was tested
| |||
| |||
| Check | Result |
| |||
|---|---|
| |||
| Rule invariants on synthetic data, 20 seeds, 3 sources, 11,851 trades ([`tests/check_reference.py`](tests/check_reference.py)) | PASS, 0 violations |
| |||
| Reference vs the research simulation, XAUUSD H4 SuperTrend preset, 2023-07 to 2026-10 | 213 of 213 entries identical (bar and side); same exit bar 212 of 213 |
| |||
| EA in the MT5 Strategy Tester vs the simulation, preset | 212 / 212 signals, 211 / 211 trades identical, entries 100%, exit bar 100% |
| |||
| Same comparison on default settings (EURUSD H1, 2025-26) | UT Bot 548 / 548 signals, SuperTrend 270 / 270, Range Filter 330 / 330; exit bar 98.9 to 99.4% |
| |||
| MT5 release check (memory, speed, non-repainting, validator-style runs) | PASS |
| |||
| MT5 edition compiled with MetaEditor (clean scratch folder, only this repository's files) | 0 errors, 0 warnings |
| |||
| MT5 Market build | passed the MQL5 Market's automated product validation before publication (the Market's own check) |
| |||
| |||
Sources: [`tests/results/`](tests/results). The EA-in-tester comparisons come from scripts that need the author's private research
| |||
environment and broker history, so they are not published; the numbers are as recorded there. Only the reference-vs-simulation
| |||
comparison and the synthetic check are reproducible from this repository's code (with your own data for the first).
| |||
| |||
```mermaid
| |||
flowchart LR
| |||
M[Bars: open, high, low, close, spread] --> R[reference.py<br/>engines and trade rule in Python]
| |||
SIM[Research simulation<br/>not published] --> C{Same entries and exits?}
| |||
R --> C
| |||
C -- 213 of 213 entries --> P[PASS]
| |||
SY[Synthetic random walks] --> K[check_reference.py<br/>rule invariants]
| |||
R --> K
| |||
K --> P2[PASS]
| |||
EA[SignalTrader.mq5] --> MC[MetaEditor 0 errors, 0 warnings]
| |||
EA --> TST[MT5 Strategy Tester<br/>debug log with signals and trades]
| |||
TST --> PAR[Parity with the simulation]
| |||
```
| |||
| |||
**Not covered:** forward (live) trading, commission and slippage, tick-level fills (the tests use 1-minute OHLC); the three engines
| |||
other than SuperTrend have only the default-settings parity runs above and no validated preset.
| |||
| |||
## Architecture
| |||
| |||
```mermaid
| |||
flowchart TB
| |||
subgraph MT5["mql5/Experts/SignalTrader"]
| |||
E[SignalTrader.mq5<br/>the program]
| |||
C[SignalTrader.mqh<br/>inputs, rule, orders, panel, events]
| |||
G[StEngines.mqh<br/>CEng, CUtBot, CSuperTrend, CRangeFilter]
| |||
RF[RF.mqh<br/>Range Filter step]
| |||
P[StPanel.mqh<br/>cached labels, boxes, rows]
| |||
PR[StPresets.mqh<br/>the validated preset]
| |||
E --> C
| |||
C --> G
| |||
G --> RF
| |||
C --> P
| |||
C --> PR
| |||
end
| |||
subgraph TESTS[tests]
| |||
R[reference.py]
| |||
K[check_reference.py]
| |||
K --> R
| |||
end
| |||
R -. same engines and rule .-> G
| |||
```
| |||
| |||
```mermaid
| |||
classDiagram
| |||
class SignalTrader {
| |||
+InpSource ENUM_ST_SOURCE
| |||
+InpStopATR double
| |||
+InpTpR double
| |||
+InpMagic long
| |||
+OnInit()
| |||
+OnTick()
| |||
+HandleSignal()
| |||
+OpenTrade()
| |||
}
| |||
class CEng {
| |||
+Feed()
| |||
+Step()
| |||
+Describe()
| |||
+atr double[]
| |||
}
| |||
class CUtBot
| |||
class CSuperTrend
| |||
class CRangeFilter
| |||
class StPresets {
| |||
+StPresetValidated()
| |||
}
| |||
class reference {
| |||
+signals()
| |||
+run()
| |||
}
| |||
CEng <|-- CUtBot
| |||
CEng <|-- CSuperTrend
| |||
CEng <|-- CRangeFilter
| |||
SignalTrader --> CEng
| |||
SignalTrader --> StPresets
| |||
reference ..> CEng : same engines, compared trade by trade
| |||
```
| |||
| |||
## Inputs, the preset, install and compile
| |||
| |||
| Input | Default | Meaning |
| |||
|---|---|---|
| |||
| Signal source | UT Bot | UT Bot, SuperTrend or Range Filter |
| |||
| UT Bot: key value, ATR period, EMA(200) trend | 2.0, 10, off | engine settings |
| |||
| SuperTrend: ATR period, multiplier, EMA(200) trend | 10, 3.0, off | engine settings |
| |||
| Range Filter: sampling period, range multiplier | 100, 3.0 | engine settings |
| |||
| Stop loss, x ATR(14) of the signal bar | 2.0 | the stop distance |
| |||
| Take profit, x the stop distance | 0 (none) | optional target |
| |||
| Magic number; maximum slippage, points | 26101701; 30 | order identity and deviation |
| |||
| Show the panel; log every signal and trade | true; false | display and debug (`TRD` and `DBG` lines) |
| |||
| |||
**The defaults are not the preset.** Load [`presets/SignalTrader_SuperTrend_XAUUSD_H4.set`](presets/SignalTrader_SuperTrend_XAUUSD_H4.set) on an
| |||
XAUUSD H4 chart: source SuperTrend, ATR period 10, multiplier 2.0, stop loss 3.0 x ATR. The Preset row on the panel then reads
| |||
"validated".
| |||
| |||
**Install from source.** Copy the `mql5/Experts/SignalTrader` folder into `<terminal data folder>/MQL5/Experts/`, open
| |||
`SignalTrader.mq5` in MetaEditor and press Compile (F7): 0 errors, 0 warnings. Keep the files together.
| |||
| |||
**Account.** Minimum balance suggested by the Market listing: 1,500 USD at 0.01 lot (recommended 3,000 USD). In the out-of-sample
| |||
backtest at 0.01 lot the largest drawdown was 638 USD, the largest single loss 272 USD and the widest stop about 450 USD. These are
| |||
backtest figures, not a limit on future losses. Test on a demo account first.
| |||
| |||
## Run the Python reference and the tests
| |||
| |||
```bash
| |||
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
| |||
python3 tests/check_reference.py # synthetic data, no broker history needed; prints PASS or FAIL
| |||
```
| |||
| |||
```python
| |||
import sys; sys.path.insert(0, "tests")
| |||
from reference import signals, run
| |||
# bars: DataFrame indexed by bar open time with open, high, low, close and spread (price units)
| |||
sig = signals("st", dict(atr=10, mult=2.0), bars.open, bars.high, bars.low, bars.close) # +1 / -1 / 0 per closed bar
| |||
trades = run(bars, sig, stop_atr=3.0) # one row per closed trade, gross of swap
| |||
```
| |||
| |||
## Honest limits
| |||
| |||
- **No edge is shown.** One pass in 444 tests, where about 2.55 were expected by chance, with a weak in-sample result and all of
| |||
the profit from buys in a gold rally. The EA is unproven automation.
| |||
- **One validated combination.** Live, nothing else trades; the other sources and symbols have not passed anything.
| |||
- **Costs and fills:** the backtests include spread and swap, not commission or slippage; the tester used 1-minute OHLC.
| |||
- **MetaTrader 5 only.** The MT4 port exists but failed its gate and is not published.
| |||
- **Server clock and symbols:** the validated name may carry a broker prefix or suffix; check that your broker lists XAUUSD.
| |||
- No profit or accuracy claim is made.
| |||
| |||
## Free vs Pro
| |||
| |||
| | Free: Signal Trader | Paid: Signal Trader Pro |
| |||
|---|---|---|
| |||
| Platforms | MT5 [201967](https://www.mql5.com/en/market/product/201967) | MT5 [201969](https://www.mql5.com/en/market/product/201969) |
| |||
| Price | free | paid (see the Market page) |
| |||
| What you get | the signals above, fixed 0.01 lot, ATR stop, the validated preset | the same engines and preset with sizing, confirmation, position management, filters and a news filter, as described on its Market listing |
| |||
| Source | this repository | not published |
| |||
| |||
No need to buy the paid product to use the free one: the free EA is complete on its own.
| |||
Website: [allanninal.dev/mql5](https://www.allanninal.dev/mql5/#eas).
| |||
| |||
## Licence and credits
| |||
| |||
**Origin of the maths.** The UT Bot idea, an ATR trailing stop on the close, is by **Yo_adriiiiaan** ("UT Bot Strategy" on TradingView),
| |||
from an original idea by **HPotter**; the TradingView script **"UT Bot Alerts"** (8 February 2020) is by **QuantNomad**. The SuperTrend
| |||
engine follows the widely published TradingView ATR-band script. Range Filter is an own implementation of the published
| |||
range-filter method. Thank you to all of them.
| |||
| |||
**What this means for the licence.** TradingView publishes open-source scripts under the Mozilla Public License 2.0 unless the author
| |||
states another licence. I could not retrieve the original scripts' headers to confirm that, so I took the conservative route: the
| |||
files that implement the signal maths are licensed **MPL-2.0**:
| |||
| |||
- `mql5/Experts/SignalTrader/StEngines.mqh`
| |||
- `mql5/Experts/SignalTrader/RF.mqh`
| |||
- `tests/reference.py`
| |||
| |||
They are independent implementations in other languages plus additions, but treating them as derived from an MPL-2.0 work is the safe
| |||
reading. MPL-2.0 is file-level: changes to those files must stay MPL-2.0 and be shared.
| |||
| |||
Everything else (the program, the rule and order code, the panel, the preset, tests, docs, images) 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. Texts: [`LICENSE`](LICENSE),
| |||
[`LICENSES/MPL-2.0.txt`](LICENSES/MPL-2.0.txt). The licence applies to this source, not to anything you build or trade with the EA.
| |||
Trading involves risk; the software is provided as is.
| |||
| |||
---
| |||
| |||
**Website:** [allanninal.dev/mql5](https://www.allanninal.dev/mql5/#eas) · [allanninal.dev](https://www.allanninal.dev)
| |||
· Questions: message me on [LinkedIn](https://www.linkedin.com/in/allanninal/).
|