P
PipsGrowth
Python Trading

Python Forex Trading Tutorial: Connect Python to MetaTrader 5

Learn how to connect Python to MetaTrader 5 for algorithmic forex trading. Complete guide with code examples for data fetching, order execution, and strategy backtesting.

PG
Pips Growth Team
2026-07-01
11 min

Python Forex Trading Tutorial: Connect Python to MetaTrader 5

Python has become the most popular programming language for algorithmic trading — and for good reason. It's easy to learn, has powerful data analysis libraries, and integrates directly with MetaTrader 5 through the official MetaTrader5 Python package.

This tutorial walks you through everything you need to start building automated forex trading systems in Python: connecting to MT5, fetching market data, executing orders, and running backtests.

Why Use Python for Forex Trading?

While MQL5 is the native language for MT5 Expert Advisors, Python offers several advantages:

  • Easier to learn — Python's syntax is far simpler than MQL5 (which is C++-based)
  • Rich ecosystem — pandas, numpy, scikit-learn, matplotlib, and thousands of financial libraries
  • Machine learning — Build predictive models with TensorFlow, PyTorch, or scikit-learn
  • Data analysis — Analyze years of tick data with pandas in seconds
  • Community support — Massive Python community means answers to every question
  • Cross-platform — Run your code on any OS, not just Windows

Python vs MQL5: When to Use Each

Feature Python MQL5
Learning curve Easy Steep (C++ background)
Execution speed Slower (API calls) Faster (native)
Machine learning Excellent Limited
Backtesting libraries Many (vectorbt, backtrader) Built-in Strategy Tester
Live trading Via MT5 Python API Native in MT5
Community Massive Specialized

Best approach: Use Python for research, analysis, and backtesting. Use MQL5 for production EAs that need maximum speed. Or use Python for everything if speed isn't critical (swing trading, daily strategies).


Prerequisites

1. Install MetaTrader 5

Download MT5 from your broker's website. Make sure you have at least a demo account.

2. Install Python 3.10+

Download from python.org. During installation, check "Add Python to PATH".

3. Install Required Packages

pip install MetaTrader5 pandas numpy matplotlib

4. Have Your MT5 Account Ready

You need:

  • MT5 installed and logged in
  • Account number (login)
  • Password
  • Broker server name

Connecting Python to MT5

The MetaTrader5 package connects directly to your running MT5 terminal. MT5 must be open and logged in for the Python connection to work.

import MetaTrader5 as mt5
import pandas as pd
from datetime import datetime

# Initialize connection to MT5
if not mt5.initialize():
    print("Failed to initialize MT5")
    mt5.shutdown()
    quit()

# Connect to your account (optional if already logged in MT5)
account = 12345678  # Your account number
password = "your_password"
server = "YourBroker-Server"  # e.g., "ICMarketsSC-Demo"

if not mt5.login(account, password, server):
    print(f"Login failed: {mt5.last_error()}")
    mt5.shutdown()
    quit()

# Verify connection
account_info = mt5.account_info()
print(f"Connected to account: {account_info.login}")
print(f"Balance: ${account_info.balance}")
print(f"Equity: ${account_info.equity}")
print(f"Leverage: 1:{account_info.leverage}")

Key Connection Notes

  • MT5 must be running and logged in — Python connects to the terminal, not directly to the broker server
  • If you have multiple MT5 terminals open, use mt5.initialize(path="C:\\Path\\To\\terminal64.exe") to specify which one
  • The connection uses inter-process communication (IPC), so Python and MT5 must be on the same machine (or use a VPS)

Fetching Market Data

Getting Historical Bars (OHLCV)

# Get the last 1000 H1 candles for EURUSD
rates = mt5.copy_rates_from("EURUSD", mt5.TIMEFRAME_H1, datetime.now(), 1000)

# Convert to pandas DataFrame
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)

print(df.head())
print(f"\nTotal bars: {len(df)}")
print(f"Date range: {df.index[0]} to {df.index[-1]}")

Getting Data for a Specific Date Range

utc_from = datetime(2024, 1, 1)
utc_to = datetime(2025, 12, 31)

rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_D1, utc_from, utc_to)
df = pd.DataFrame(rates)
df['time'] = pd.to_datetime(df['time'], unit='s')
df.set_index('time', inplace=True)

print(f"Retrieved {len(df)} daily candles")

Available Timeframes

mt5.TIMEFRAME_M1   # 1 minute
mt5.TIMEFRAME_M5   # 5 minutes
mt5.TIMEFRAME_M15  # 15 minutes
mt5.TIMEFRAME_M30  # 30 minutes
mt5.TIMEFRAME_H1   # 1 hour
mt5.TIMEFRAME_H4   # 4 hours
mt5.TIMEFRAME_D1   # 1 day
mt5.TIMEFRAME_W1   # 1 week
mt5.TIMEFRAME_MN1  # 1 month

Getting Tick Data

# Get last 1000 ticks
ticks = mt5.copy_ticks_from("EURUSD", datetime.now(), 1000, mt5.COPY_TICKS_ALL)
tick_df = pd.DataFrame(ticks)
tick_df['time'] = pd.to_datetime(tick_df['time_msc'], unit='ms')
print(tick_df[['time', 'bid', 'ask']].head())

Getting Symbol Information

# Get all available symbols
symbols = mt5.symbols_get()
print(f"Total symbols available: {len(symbols)}")

# Get info for a specific symbol
info = mt5.symbol_info("EURUSD")
print(f"Spread: {info.spread}")
print(f"Digits: {info.digits}")
print(f"Point: {info.point}")
print(f"Min lot: {info.volume_min}")
print(f"Max lot: {info.volume_max}")
print(f"Trade mode: {info.trade_mode}")

Executing Orders from Python

Market Buy Order

def market_buy(symbol, volume, sl_points=0, tp_points=0, magic=1001, deviation=20):
    """Send a market buy order via MT5"""
    
    # Ensure symbol is selected
    if not mt5.symbol_select(symbol, True):
        print(f"Failed to select {symbol}")
        return None
    
    point = mt5.symbol_info(symbol).point
    price = mt5.symbol_info_tick(symbol).ask
    
    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": float(volume),
        "type": mt5.ORDER_TYPE_BUY,
        "price": price,
        "sl": price - sl_points * point if sl_points > 0 else 0.0,
        "tp": price + tp_points * point if tp_points > 0 else 0.0,
        "magic": magic,
        "deviation": deviation,
        "comment": "Python script buy",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    
    result = mt5.order_send(request)
    
    if result.retcode != mt5.TRADE_RETCODE_DONE:
        print(f"Order failed: {result.retcode} - {result.comment}")
        return None
    
    print(f"Buy order filled: {result.volume} lots at {result.price}")
    return result

Market Sell Order

def market_sell(symbol, volume, sl_points=0, tp_points=0, magic=1001, deviation=20):
    """Send a market sell order via MT5"""
    
    if not mt5.symbol_select(symbol, True):
        print(f"Failed to select {symbol}")
        return None
    
    point = mt5.symbol_info(symbol).point
    price = mt5.symbol_info_tick(symbol).bid
    
    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": symbol,
        "volume": float(volume),
        "type": mt5.ORDER_TYPE_SELL,
        "price": price,
        "sl": price + sl_points * point if sl_points > 0 else 0.0,
        "tp": price - tp_points * point if tp_points > 0 else 0.0,
        "magic": magic,
        "deviation": deviation,
        "comment": "Python script sell",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    
    result = mt5.order_send(request)
    
    if result.retcode != mt5.TRADE_RETCODE_DONE:
        print(f"Order failed: {result.retcode} - {result.comment}")
        return None
    
    print(f"Sell order filled: {result.volume} lots at {result.price}")
    return result

Closing a Position

def close_position(ticket, deviation=20):
    """Close an open position by ticket"""
    
    position = mt5.positions_get(ticket=ticket)
    if not position:
        print(f"Position {ticket} not found")
        return None
    
    pos = position[0]
    
    # Determine close order type (opposite of open)
    if pos.type == mt5.POSITION_TYPE_BUY:
        order_type = mt5.ORDER_TYPE_SELL
        price = mt5.symbol_info_tick(pos.symbol).bid
    else:
        order_type = mt5.ORDER_TYPE_BUY
        price = mt5.symbol_info_tick(pos.symbol).ask
    
    request = {
        "action": mt5.TRADE_ACTION_DEAL,
        "symbol": pos.symbol,
        "volume": pos.volume,
        "type": order_type,
        "position": ticket,
        "price": price,
        "deviation": deviation,
        "magic": pos.magic,
        "comment": "Python close position",
        "type_time": mt5.ORDER_TIME_GTC,
        "type_filling": mt5.ORDER_FILLING_IOC,
    }
    
    result = mt5.order_send(request)
    
    if result.retcode != mt5.TRADE_RETCODE_DONE:
        print(f"Close failed: {result.retcode}")
        return None
    
    print(f"Position {ticket} closed at {result.price}")
    return result

Checking Open Positions

# Get all open positions
positions = mt5.positions_get()

if positions:
    pos_df = pd.DataFrame([{
        'ticket': p.ticket,
        'symbol': p.symbol,
        'type': 'BUY' if p.type == mt5.POSITION_TYPE_BUY else 'SELL',
        'volume': p.volume,
        'open_price': p.price_open,
        'current_price': p.price_current,
        'profit': p.profit,
        'magic': p.magic,
    } for p in positions])
    
    print(pos_df)
    print(f"\nTotal floating P/L: ${sum(p.profit for p in positions):.2f}")
else:
    print("No open positions")

Building a Simple SMA Crossover Strategy in Python

Here's a complete working example that fetches data, calculates signals, and could execute trades:

import MetaTrader5 as mt5
import pandas as pd
import numpy as np
from datetime import datetime

def get_sma_signals(symbol, timeframe, fast_period, slow_period, bars=500):
    """Generate SMA crossover signals"""
    
    # Fetch data
    rates = mt5.copy_rates_from(symbol, timeframe, datetime.now(), bars)
    df = pd.DataFrame(rates)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time', inplace=True)
    
    # Calculate SMAs
    df['sma_fast'] = df['close'].rolling(fast_period).mean()
    df['sma_slow'] = df['close'].rolling(slow_period).mean()
    
    # Generate signals
    df['signal'] = 0
    df.loc[df['sma_fast'] > df['sma_slow'], 'signal'] = 1   # Bullish
    df.loc[df['sma_fast'] < df['sma_slow'], 'signal'] = -1  # Bearish
    
    # Detect crossovers (signal change)
    df['crossover'] = df['signal'].diff().fillna(0)
    
    return df

# Run the strategy
mt5.initialize()

df = get_sma_signals("EURUSD", mt5.TIMEFRAME_H1, fast_period=50, slow_period=200)

# Show last 10 signals
signals = df[df['crossover'] != 0].tail(10)
print("Recent SMA crossovers:")
for idx, row in signals.iterrows():
    direction = "BUY" if row['crossover'] > 0 else "SELL"
    print(f"  {idx}: {direction} signal (SMA{50}: {row['sma_fast']:.5f}, SMA{200}: {row['sma_slow']:.5f})")

mt5.shutdown()

Backtesting Python Strategies

While MT5 has its own Strategy Tester, Python traders often use dedicated backtesting libraries for more flexibility.

Using pandas for Simple Backtesting

def backtest_sma_crossover(df, initial_balance=10000, lot_size=0.1):
    """Simple vectorized backtest of SMA crossover"""
    
    balance = initial_balance
    position = 0
    entry_price = 0
    trades = []
    
    for i in range(1, len(df)):
        # Check for crossover signals
        if df['crossover'].iloc[i] > 0 and position <= 0:
            # Close short if open
            if position < 0:
                pnl = (entry_price - df['close'].iloc[i]) * lot_size * 100000
                balance += pnl
                trades.append({'exit': df.index[i], 'pnl': pnl, 'type': 'short'})
            # Open long
            position = 1
            entry_price = df['close'].iloc[i]
            
        elif df['crossover'].iloc[i] < 0 and position >= 0:
            # Close long if open
            if position > 0:
                pnl = (df['close'].iloc[i] - entry_price) * lot_size * 100000
                balance += pnl
                trades.append({'exit': df.index[i], 'pnl': pnl, 'type': 'long'})
            # Open short
            position = -1
            entry_price = df['close'].iloc[i]
    
    # Close final position
    if position > 0:
        pnl = (df['close'].iloc[-1] - entry_price) * lot_size * 100000
        balance += pnl
        trades.append({'exit': df.index[-1], 'pnl': pnl, 'type': 'long'})
    elif position < 0:
        pnl = (entry_price - df['close'].iloc[-1]) * lot_size * 100000
        balance += pnl
        trades.append({'exit': df.index[-1], 'pnl': pnl, 'type': 'short'})
    
    print(f"Initial balance: ${initial_balance:,.2f}")
    print(f"Final balance: ${balance:,.2f}")
    print(f"Total return: {((balance - initial_balance) / initial_balance * 100):.2f}%")
    print(f"Total trades: {len(trades)}")
    print(f"Win rate: {sum(1 for t in trades if t['pnl'] > 0) / len(trades) * 100:.1f}%" if trades else "No trades")
    
    return balance, trades

Recommended Python Backtesting Libraries

Library Best For Learning Curve
vectorbt Fast vectorized backtesting Medium
backtrader Feature-rich event-driven backtesting Medium
zipline Quantopian-style pipeline Steep
bt Strategy composition and testing Easy
pandas-ta Technical analysis indicators Easy

Running Python EAs on a VPS

Since Python connects to MT5 via IPC, both must run on the same machine. For 24/5 trading:

  1. Rent a Windows VPS — Python + MT5 require Windows (or Wine on Linux, but it's unreliable)
  2. Install MT5 and Python on the VPS
  3. Log in to MT5 and keep it running
  4. Run your Python script as a background process or scheduled task

VPS Recommendations

Provider Price Location Notes
ForexVPS $20/mo NY4, LD4 Optimized for MT5
Beeks VPS $25/mo NY4, LD4 Low latency to brokers
AWS EC2 (Windows) $15-30/mo Multiple Flexible but requires setup

Tip: Choose a VPS in the same data center as your broker's servers. For IC Markets and Pepperstone, that's NY4 (New York) or LD4 (London). See our Best Broker for MT5 EAs guide for server locations.


Complete Example: Python Trading Bot Template

Here's a minimal but functional trading bot template you can adapt:

import MetaTrader5 as mt5
import pandas as pd
import time
from datetime import datetime

class SimpleBot:
    def __init__(self, symbol, magic=1001):
        self.symbol = symbol
        self.magic = magic
        self.running = False
        
    def initialize(self):
        if not mt5.initialize():
            raise Exception("Failed to initialize MT5")
        if not mt5.symbol_select(self.symbol, True):
            raise Exception(f"Failed to select {self.symbol}")
        print(f"Bot initialized for {self.symbol}")
        
    def get_signal(self):
        """Override this method with your strategy"""
        rates = mt5.copy_rates_from(self.symbol, mt5.TIMEFRAME_H1, datetime.now(), 250)
        df = pd.DataFrame(rates)
        
        df['sma_fast'] = df['close'].rolling(50).mean()
        df['sma_slow'] = df['close'].rolling(200).mean()
        
        if df['sma_fast'].iloc[-1] > df['sma_slow'].iloc[-1]:
            return "BUY"
        elif df['sma_fast'].iloc[-1] < df['sma_slow'].iloc[-1]:
            return "SELL"
        return None
    
    def has_open_position(self):
        positions = mt5.positions_get(symbol=self.symbol, magic=self.magic)
        return len(positions) > 0 if positions else False
    
    def execute_signal(self, signal):
        if signal == "BUY":
            self.market_buy()
        elif signal == "SELL":
            self.market_sell()
    
    def market_buy(self, volume=0.1, sl_points=200, tp_points=400):
        point = mt5.symbol_info(self.symbol).point
        price = mt5.symbol_info_tick(self.symbol).ask
        
        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": self.symbol,
            "volume": float(volume),
            "type": mt5.ORDER_TYPE_BUY,
            "price": price,
            "sl": price - sl_points * point,
            "tp": price + tp_points * point,
            "magic": self.magic,
            "deviation": 20,
            "comment": "Python bot buy",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_IOC,
        }
        
        result = mt5.order_send(request)
        if result.retcode == mt5.TRADE_RETCODE_DONE:
            print(f"BUY filled at {result.price}")
        else:
            print(f"BUY failed: {result.comment}")
    
    def market_sell(self, volume=0.1, sl_points=200, tp_points=400):
        point = mt5.symbol_info(self.symbol).point
        price = mt5.symbol_info_tick(self.symbol).bid
        
        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": self.symbol,
            "volume": float(volume),
            "type": mt5.ORDER_TYPE_SELL,
            "price": price,
            "sl": price + sl_points * point,
            "tp": price - tp_points * point,
            "magic": self.magic,
            "deviation": 20,
            "comment": "Python bot sell",
            "type_time": mt5.ORDER_TIME_GTC,
            "type_filling": mt5.ORDER_FILLING_IOC,
        }
        
        result = mt5.order_send(request)
        if result.retcode == mt5.TRADE_RETCODE_DONE:
            print(f"SELL filled at {result.price}")
        else:
            print(f"SELL failed: {result.comment}")
    
    def run(self, check_interval=60):
        self.running = True
        print(f"Bot started. Checking every {check_interval}s")
        
        while self.running:
            try:
                if not self.has_open_position():
                    signal = self.get_signal()
                    if signal:
                        print(f"[{datetime.now()}] Signal: {signal}")
                        self.execute_signal(signal)
                else:
                    pass  # Wait for position to close
                    
            except Exception as e:
                print(f"Error: {e}")
            
            time.sleep(check_interval)
    
    def stop(self):
        self.running = False
        mt5.shutdown()
        print("Bot stopped")

# Usage
if __name__ == "__main__":
    bot = SimpleBot("EURUSD", magic=1001)
    bot.initialize()
    
    try:
        bot.run(check_interval=300)  # Check every 5 minutes
    except KeyboardInterrupt:
        bot.stop()

Frequently Asked Questions

Does Python work with MT4?

No. The MetaTrader5 Python package only works with MT5. MT4 doesn't have an official Python API. If you need Python with MT4, you'd need third-party bridges or libraries, which are less reliable.

Can I run Python on a different machine than MT5?

No. The Python MT5 API uses inter-process communication (IPC), which requires both to be on the same machine. For remote trading, use a VPS with both MT5 and Python installed.

Is Python fast enough for scalping?

For high-frequency scalping (sub-second), Python may be too slow due to IPC overhead. For strategies that trade on bar close (M5, H1, D1), Python is perfectly fine. If you need microsecond execution, use MQL5 EAs instead.

Can I use machine learning with MT5 in Python?

Yes! This is one of Python's biggest advantages. Fetch historical data from MT5, train a model with scikit-learn or TensorFlow, and use the model's predictions to generate trading signals. The MetaTrader5 package handles data fetching and order execution, while ML libraries handle prediction.

How do I handle errors and reconnections?

Always wrap your MT5 calls in try/except blocks. If the connection drops, call mt5.initialize() again. For production bots, implement a heartbeat check every few minutes and reconnect if needed.


Disclaimer: Algorithmic trading involves significant risk. The code examples in this tutorial are for educational purposes only and are not financial advice. Always test strategies on a demo account before trading with real money.

Affiliate Disclosure: This page contains affiliate links. We may earn a commission when you click on certain links or sign up with brokers featured on this site, at no additional cost to you. Our reviews and recommendations are based on thorough research and remain unbiased.Learn more

Risk Warning: Trading forex and CFDs involves significant risk of loss. Past performance is not indicative of future results. CFDs are complex instruments and come with a high risk of losing money rapidly due to leverage. Ensure you understand the risks before trading. This content is for educational purposes only and does not constitute financial advice.

Written by

Pips Growth Team

Trading Education & Research Team

The Pips Growth Team is a group of experienced traders, financial analysts, and trading educators dedicated to providing accurate, actionable forex education. Our team combines decades of hands-on market experience with deep technical knowledge to create comprehensive guides, honest broker reviews, and proven trading strategies. Every article is thoroughly researched, fact-checked, and reviewed by multiple team members to ensure the highest quality and accuracy.

15+ Years Experience53+ Articles Published
Forex & CFD TradingTechnical AnalysisRisk ManagementBroker Reviews & Evaluation
View Full Profile
Share

Get Trading Tips

Python Forex Trading Tutorial: Connect Python to MetaTrader 5 | PipsGrowth