Skip to main content

Building a Telegram-to-MT4 Trade Copier from Scratch

894 words·5 mins
Apex Team
Table of Contents

Sending a single message from your phone and watching it execute as a live trade across multiple MetaTrader 4 terminals simultaneously — that’s exactly what ApexCopier does. This post walks through how we built it, the decisions behind the architecture, and the key code that makes it work.


The Problem with Off-the-Shelf Solutions
#

Most Telegram-to-MT4 copiers on the market come as compiled black boxes — a .exe connector and a .ex4 EA with no source code, no flexibility, and no real understanding of what’s happening under the hood. If something breaks or you want to customize the signal format, you’re stuck.

We wanted something different: a fully transparent, minimal system built around a clear signal format that we control end to end. No unnecessary dependencies, no mystery files running in the background — just a Python script, a CSV file, and an MQL4 Expert Advisor doing exactly what we tell them to do.


Part 1 — The Connector: From Telegram to a File
#

The connector is a Python script that runs alongside MT4 and acts as the bridge between your Telegram channel and the trading terminal. It uses the Telegram Bot API — a bot is created via @BotFather, added as an Administrator to a private channel, and from that point it receives every message posted to that channel in real time.

@bot.channel_post_handler(func=lambda msg: True)
def on_channel_post(message):
    text   = message.text or message.caption or ""
    signal = sig_parser.parse(text)

    if signal is None:
        print(f"[skip] Not a valid signal: {repr(text)}")
        return

    sig_id = writer.append(signal, MT4_FILES_PATHS, COUNTER_FILE)

When a message arrives, it’s passed to the parser. Valid signals get written as a row in signals.csv directly inside MT4’s sandboxed MQL4/Files/ folder. Invalid messages are silently skipped.

The signal format was designed to be human-friendly and fast to type, and you can customize it for various needs:

Message Action
L Long position
S Short position
C Close all positions
EURUSD BUY MARKET TP:1.0950 SL:1.0800 Full market order
EURUSD SELL LIMIT 1.0820 TP:1.0750 SL:1.0870 Pending order

Our parser handles both simple one-letter signals and full structured signals with symbol, direction, entry type, and price levels — all in a single function with no external dependencies beyond the message text itself.

One important architectural note: each machine running MT4 gets its own dedicated bot token. The Telegram Bot API only allows one active polling connection per token, so running the connector on two machines with the same token causes a 409 conflict. The solution is simple — create a second bot, add it as admin to the same channel, and assign it to the second machine. Both bots receive every signal independently.


Part 2 — The IPC Bridge: Signals as a CSV File
#

MT4 is a sandboxed environment. Expert Advisors can only read and write files within their own MQL4/Files/ folder — there’s no direct way for an external process to push data into a running EA. The standard solution is file-based inter-process communication, and CSV is the most readable format for it.

Every signal written by the connector becomes a row:

id, timestamp, type, symbol, direction, entry_type, entry_price, sl, tp
1, 2026-04-20 23:15:00, SIMPLE, , BUY, MARKET, 0.00000, 0.00000, 0.00000
2, 2026-04-20 23:31:00, FULL, EURUSD, SELL, MARKET, 0.00000, 1.08700, 1.07500

The connector appends rows and never modifies existing ones. The EA tracks the last signal ID it processed in a separate last_processed.txt file — on each timer tick it reads only the rows it hasn’t seen yet, executes them, and updates the pointer. This means restarts on either side are safe: the EA will never double-execute a signal, and the connector will never overwrite history.

For multiple MT4 instances on the same machine, the connector simply writes the same row to multiple paths simultaneously:

for path in MT4_FILES_PATHS:
    signals_file = os.path.join(path, "signals.csv")
    try:
        _write_row(signals_file, row, header)
    except OSError as e:
        print(f"[warn] Could not write to {signals_file}: {e}")

Each terminal reads from its own copy of the file, completely independently.


Part 3 — The Expert Advisor: Execution Inside MT4
#

The EA is intentionally lean. It has no trading logic of its own — it only reads signals and executes them. A one-second timer replaces OnTick() to keep CPU usage minimal and avoid unnecessary processing on every price update.

void OnTimer() { ProcessSignals(); }

Lot sizing for full signals supports three modes controlled by EA inputs: fixed lot, percentage of account equity, or a fixed dollar risk amount. When a risk-based mode is active, the lot is calculated so that a stop loss hit costs exactly the specified amount:

double slPoints  = MathAbs(entryPrice - sl) / MarketInfo(symbol, MODE_POINT);
double tickValue = MarketInfo(symbol, MODE_TICKVALUE);
double lot       = NormalizeDouble(riskAmount / (slPoints * tickValue), lotDecimals);

The EA also applies a custom chart theme on initialization — black background, white bull candles, and Telegram’s signature blue (#2AABEE) for bear candles and foreground elements — so every chart it touches gets a consistent dark look without manual configuration.

Signal types are individually switchable via EA inputs, meaning the same channel can serve terminals configured differently: one instance trading full signals with percentage-based risk, another only responding to the simple L/S/C commands with a fixed lot. Each terminal is independent, with its own magic number, lot settings, and enable/disable switches.


ApexCopier ended up being roughly 200 lines of Python and 150 lines of MQL4 — small enough to understand completely, flexible enough to grow.

Author
Apex Team
Algorithmic Trading Solutions