TUSHAR.
Article20 min read

MT5 MCP Server: Giving Claude Autonomous Control Over MetaTrader 5

How I built an MCP server that lets Claude AI autonomously write MQL5 Expert Advisors, compile them, run backtests, scan markets, and analyze trade history — collapsing the entire algo-trading research loop into a single conversation.

Algorithmic trading research is painfully manual. You get an idea, open MetaEditor, write MQL5 code, compile it, switch to Strategy Tester, configure the parameters, run the backtest, wait, read the report, adjust, repeat. Each iteration is 10–15 minutes of friction. If the idea is bad — and most ideas are — you've wasted an hour.

I wanted to close that loop. The goal: let Claude AI drive the entire research cycle autonomously. Write the EA → compile it → run the backtest → read the results → iterate. No switching windows, no manual configuration, no copy-pasting output.

The result is the MT5 MCP Server — a production-grade MCP server that exposes 16 tools giving Claude full, autonomous control over MetaTrader 5.


What Is MCP and Why Does It Matter Here

MCP (Model Context Protocol) is the emerging standard for connecting AI models to external systems via tools. Instead of building one-off integrations, you expose capabilities as typed tool definitions and the model decides when and how to use them.

Think of it like this: traditionally Claude can reason but can't act — it can't open MetaEditor, can't run a backtest, can't read a tester log. MCP changes that. Each tool is a capability Claude can invoke with structured arguments, and the result comes back as text. Chain enough tools together and the model can execute an entire research workflow without human intervention.

The architecture looks like this:

Claude (MCP client)
        │
        ▼
  server.py  ← stdio transport
        │
   ┌────┴───────────────────────────────┐
   │                                    │
tools/ea_manager.py           tools/market_data.py
tools/backtester.py           tools/indicators.py
                              tools/trade_manager.py
        │
        ▼
MetaTrader 5 (terminal + MetaEditor)

The server runs as a Python process communicating over stdio (the MCP standard). Each tool call arrives as JSON, executes against the live MT5 terminal, and returns results as text that Claude can reason over.


The 16 Tools

I organized the tools into five modules. Here's every tool, what it does, and its key parameters.

EA Manager

These four tools handle the code creation and file system operations.

write_ea — Writes MQL5 code to the MT5 Experts folder. Claude generates the code, calls this tool, and the file lands in the right place ready to compile.

write_ea(
    filename="ema_crossover.mq5",
    code="...",  # full MQL5 source
    folder="experts"  # or "scripts" / "indicators"
)

compile_mql5 — Invokes MetaEditor silently, compiles the file, and returns success/fail plus all errors and warnings. This is what tells Claude whether its MQL5 code was syntactically correct.

compile_mql5(filepath="ema_crossover.mq5")
# Returns: {"success": true, "errors": [], "warnings": []}

write_file / read_file / list_files — General file I/O. Claude uses these to write helper scripts, read tester logs, list what EAs exist in the Experts folder.


Backtester

run_backtest — Launches MT5 Strategy Tester unattended with full parameter control. The key innovation here is completion detection: instead of waiting a fixed timeout, it polls the tester log file watching for the completion signature. Works for simple EAs in seconds and complex tick-based tests in minutes.

run_backtest(
    ea_name="AI_Generated\\ema_crossover",
    symbol="EURUSD",
    period="H1",
    from_date="2024.01.01",
    to_date="2025.01.01",
    deposit=10000,
    leverage=100,
    model="1min_ohlc",  # every_tick | 1min_ohlc | open_prices | real_ticks
    ea_inputs={"FastMA": 25, "SlowMA": 100, "RiskPct": 1.0},
    timeout_seconds=300
)

get_backtest_results — Parses the tester log and extracts net profit, win rate, profit factor, max drawdown, average trade, best and worst trade, and the complete trade list. Claude gets structured JSON it can analyze immediately.

{
  "net_profit": 1891.40,
  "win_rate": 58.3,
  "profit_factor": 2.80,
  "max_drawdown": 230.50,
  "max_drawdown_pct": 2.3,
  "total_trades": 24,
  "trades": [...]
}

Market Data

get_price — Live bid/ask and spread for any symbol. Useful for Claude to sanity-check current market conditions before recommending anything.

get_price_data — Historical OHLCV candles. Up to 5000 bars on any timeframe (M1 through W1). Returns a list of candles that Claude can pass to Python analysis code.

get_price_data(symbol="EURUSD", timeframe="H1", count=500)
# Returns list of {time, open, high, low, close, tick_volume} dicts

get_symbols — All available symbols on the connected broker, with an optional search filter. Claude uses this before scanning so it knows what to scan.

get_account_info — Balance, equity, margin level, free margin, leverage, and floating P&L. A quick health check on the account.

run_python — The escape hatch. Execute arbitrary Python code with the mt5 library available. Claude uses this for custom analysis that doesn't fit a structured tool — Sharpe ratio calculations, custom indicator implementations, data exports.

run_python(code="""
import MetaTrader5 as mt5
deals = mt5.history_deals_get(from_date, to_date)
profits = [d.profit for d in deals if d.profit != 0]
sharpe = sum(profits) / (len(profits) ** 0.5 * __import__('statistics').stdev(profits))
print(f"Sharpe ratio: {sharpe:.2f}")
""")

Indicators

calculate_indicators — Fetches OHLCV data from MT5 and computes technical indicators using pandas/numpy. Supports EMA, SMA, RSI, MACD, ATR, and Bollinger Bands in a single call. Returns the last N candles with all indicator columns, plus a "latest snapshot" dict.

calculate_indicators(
    symbol="EURUSD",
    timeframe="H1",
    count=50,
    indicators=["EMA_25", "EMA_100", "EMA_200", "RSI_14", "ATR_14", "MACD"]
)

The response includes a latest key with the most recent values — great for Claude to quickly check whether a setup condition is active right now.

{
  "latest": {
    "close": 1.08412,
    "EMA_25": 1.08380,
    "EMA_100": 1.08210,
    "EMA_200": 1.07950,
    "RSI_14": 54.3,
    "ATR_14": 0.00082
  },
  "candles": [...]
}

scan_symbols — The market scanner. Give it a condition and it evaluates every symbol on the broker (or a filtered subset) and returns all matches with signal details.

scan_symbols(
    condition="ema_crossover",   # ema_crossover | ema_crossunder | ema_above | ema_below | rsi_oversold | rsi_overbought
    timeframe="H4",
    search_filter="EUR",         # only scan EUR pairs
    fast_period=25,
    slow_period=100,
    max_symbols=50
)

This is the tool I use most for idea generation. "Scan all majors on H4 for EMA crossovers" is a single tool call. Claude gets back a ranked list of matches, then decides which ones are worth investigating further.


Trade Manager

get_open_positions — All currently open trades: ticket, symbol, direction, volume, entry price, current price, floating P&L, swap, commission.

get_trade_history — Closed trades with full statistics: net P&L, win rate, profit factor, average win/loss, best and worst trade, and per-symbol breakdown. Accepts a date range and optional symbol filter.

get_trade_history(
    from_date="2026-01-01",
    to_date="2026-06-07",
    symbol="EURUSD"
)

Implementation Details

The MT5 Python Bridge

MetaTrader 5 exposes a COM interface that the official MetaTrader5 Python library wraps. The library connects to the running terminal process — so the terminal must already be open and logged in. All market data, order operations, and history queries go through this bridge.

One important detail: the bridge needs to be initialized once per process, not once per tool call. Every tool module has a shared _ensure_mt5() helper:

def _ensure_mt5() -> bool:
    import MetaTrader5 as mt5
    if not mt5.initialize():
        return False
    return True

This avoids the overhead of re-initializing the connection on every call while keeping each module independent.

Compiling MQL5 From Python

MetaEditor doesn't have a command-line API, but it accepts a /compile flag:

import subprocess

result = subprocess.run([
    META_EDITOR_PATH,
    "/compile", filepath,
    "/log"
], capture_output=True, timeout=60)

The /log flag writes a .log file next to the source. I parse that file for error and warning lines — MetaEditor uses a consistent format:

; 1 error(s), 0 warning(s)
myea.mq5(42,5): error 006: 'OrderSend' - wrong parameters count

Backtest Completion Detection

This was the trickiest part. MT5 Strategy Tester is a GUI process — there's no programmatic way to know when a backtest finishes. My approach:

  1. Write an .ini config file that MT5's Strategy Tester reads on launch
  2. Launch the terminal with the config: terminal64.exe /config:backtest.ini
  3. Poll the tester log file watching for the completion marker

The tester log lives at a predictable path under the MT5 data directory. When a backtest finishes, the log always ends with one of a few known patterns ("optimization finished", "testing finished", "backtest finished"). I poll every 2 seconds until I see it or the timeout expires.

def _wait_for_completion(log_path: str, timeout: int) -> bool:
    start = time.time()
    completion_markers = ["optimization finished", "testing finished", "backtest finished"]
    while time.time() - start < timeout:
        if log_path.exists():
            tail = _read_log_tail(log_path, lines=20)
            if any(m in tail.lower() for m in completion_markers):
                return True
        time.sleep(2)
    return False

Parsing Results From the Tester Log

MT5 writes full trade statistics and the trade list into the tester log. The format is consistent across MT5 versions. I extract the key stats with regex and parse the trade table line by line:

import re

STAT_PATTERNS = {
    "net_profit": r"Net profit\s+([\d\.\-]+)",
    "profit_factor": r"Profit factor\s+([\d\.]+)",
    "win_rate": r"Percent profitable\s+([\d\.]+)%",
    "max_drawdown": r"Equity Drawdown Maximal\s+([\d\.]+)",
}

def _parse_tester_log(log_content: str) -> dict:
    stats = {}
    for key, pattern in STAT_PATTERNS.items():
        match = re.search(pattern, log_content)
        if match:
            stats[key] = float(match.group(1))
    return stats

The Research: EMA(25/100) + Regime Filter

Once the server was built, I used it to research a real strategy. Claude drove the entire process — from baseline to final optimized version — across a multi-turn conversation spanning several sessions.

The Research Loop

The research followed a structured iteration:

v1: Raw EMA crossover (baseline)
  ↓ poor results in ranging markets
v2: Add EMA 200 trend filter
  ↓ improved, still choppy in sideways conditions
v3: Add Choppiness Index + ADX filter (regime detection)
  ↓ significant improvement, fewer low-quality trades
v4: Optimize SL/TP ratios (ATR-based)
  ↓ best results: 1.5x ATR stop, 3.0x ATR target
v5: Test break-even and trailing stop
  ↓ conclusion: hurts performance — don't add them
FINAL: EMA(25/100) + Regime Filter, ATR sizing, no BE/trail

Strategy Parameters

ParameterValue
Fast EMA25
Slow EMA100
Trend EMA200 (H4 timeframe)
Stop loss1.5× ATR(14)
Take profit3.0× ATR(14)
Risk per trade1% of equity (compounding)
Choppiness Index< 61.8 (skip choppy markets)
ADX> 15 (skip weak trends)
ATR entry filter< 0.6× average ATR (avoid overextended entries)
H4 alignmentRequired — H1 signal must match H4 direction

Backtest Results (EURUSD H1, after all fees)

Broker conditions: MetaQuotes demo, 3-point spread, $7 commission round-trip, swap -0.70 pips long / -1.00 pips short per night.

PeriodNet P&LReturnProfit FactorMax Drawdown
2024+$1,891+18.9%2.802.3%
2025+$10.0%1.006.3%
2026 Jan–Jun+$120+1.2%1.184.6%
3yr combined+$2,042+20.4%1.596.3%

Annualized: ~7.9%/year after all fees. ~22 trades per year — the regime filters are aggressive, which is the point. The strategy only enters when multiple conditions align, so it rarely trades but trades well when it does.

Why No Break-Even or Trailing Stop?

This was a counterintuitive finding that took several backtest iterations to confirm. Intuitively, adding a break-even stop "should" protect profits. In practice, it kicked the strategy out of trades too early — right before price resumed the trend and hit the 3× ATR target.

The ATR-based target is already calibrated to the typical move size for this setup. Adding BE just introduced noise. The be_optimize.py script tested 15 combinations of BE trigger levels and trail distances. None improved performance vs the fixed SL/TP setup.

If your TP is correctly sized for the setup, a trailing stop is usually interference, not protection.


A Real Claude Session

Here's what a research iteration looked like in practice. I'd give Claude a prompt like:

"Backtest the EMA(25/100) strategy on EURUSD H1 from 2024-2026 
with 1.5x ATR stop and 3x ATR target. Then break down the results 
year by year and tell me which year had the worst drawdown and why."

Claude would:

  1. Call run_backtest with the parameters
  2. Wait for completion
  3. Call get_backtest_results
  4. Call run_python to split trades by year
  5. Call calculate_indicators to check market conditions in the drawdown period
  6. Return a structured analysis with the diagnosis

What would have taken me 30–45 minutes of manual work — run the backtest, export the report, write Python to slice by year, cross-reference with the price chart — took 60–90 seconds of Claude execution time.


Setting Up

Requirements: Windows, MetaTrader 5 terminal running and logged in, Python 3.10+.

git clone https://github.com/tusharrayamajhi/mt5-mcp-server.git
cd mt5-mcp-server
pip install mcp MetaTrader5 pandas lxml

Edit config.py to point at your MT5 installation:

META_EDITOR_PATH = r"C:\Program Files\MetaTrader 5\MetaEditor64.exe"
TERMINAL_PATH    = r"C:\Program Files\MetaTrader 5\terminal64.exe"
EXPERTS_DIR      = r"C:\Users\<you>\AppData\Roaming\MetaQuotes\Terminal\<ID>\MQL5\Experts"

Register with Claude Desktop in ~/.claude/settings.json:

{
  "mcpServers": {
    "mt5": {
      "command": "python",
      "args": ["C:\\path\\to\\mt5-mcp-server\\server.py"]
    }
  }
}

Restart Claude Desktop. The MT5 tools appear in Claude's tool list. Open MetaTrader 5, log in, and you're ready.


What I Learned

MCP is the right abstraction for trading tools. The alternative would have been a REST API or a CLI tool. MCP is better here because Claude can call multiple tools in sequence within a single reasoning step — write EA, compile, run backtest, parse results, all in one chain without human intervention. A REST API would require orchestration logic outside the model.

Log-based completion detection is surprisingly robust. I expected to need a more sophisticated IPC mechanism. Polling a log file every 2 seconds is crude, but it works reliably because the tester log is always written to the same path and always ends with the same marker. Simple beats clever.

Regime filters are underrated. The biggest performance jump came not from optimizing EMA periods or SL/TP ratios but from adding the Choppiness Index filter. Cutting out trades in ranging markets (CI > 61.8) reduced trade count by ~40% while improving the profit factor significantly. Quality over quantity.

Don't over-engineer the exit. The break-even stop experiment was a good lesson. More complexity ≠ better results. The original 1.5×/3× ATR setup outperformed every BE and trailing stop variant. If your entry and target sizing are correct, you don't need a complex exit.

AI-driven research is genuinely faster. The research process that produced these results — baseline → 5 iterations → final strategy — took roughly 6 hours of actual work. Without the MCP server I estimate it would have taken 2–3 days of manual iteration. The speedup comes from eliminating the friction between having an idea and seeing its results.


What's Next

The MQL5 EA itself — the live trading version of this strategy — hasn't been built yet. The server is built, the strategy is researched, but the live EA is the remaining piece. When I build it, Claude will write the MQL5 code, compile it via the server, and do a final validation backtest, all in one session.

Other planned additions:

  • Live order execution toolsplace_order, close_position, modify_sl_tp
  • Multi-symbol portfolio backtest — run the strategy across a basket of pairs simultaneously
  • Walk-forward optimization support — divide the test period into in-sample/out-of-sample windows automatically
  • Risk dashboard tool — aggregate open exposure by currency, correlation-adjusted position sizing

The server is open source: github.com/tusharrayamajhi/mt5-mcp-server.

If you're doing algo-trading research and you want Claude to drive the iteration instead of you, this is the setup.


Related Reading

Tushar Rayamajhi | AI Engineer & Backend Developer