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:
- Rent a Windows VPS — Python + MT5 require Windows (or Wine on Linux, but it's unreliable)
- Install MT5 and Python on the VPS
- Log in to MT5 and keep it running
- 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.
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.