pdmt5
Pandas-based data handler for MetaTrader 5

Overview
pdmt5 is a Python package that provides a pandas-based interface for MetaTrader 5 (MT5), making it easier to work with financial market data in Python. It provides helpers to convert MT5's native data structures into pandas DataFrames and dictionaries, enabling seamless integration with data science workflows.
Key Features
- 📊 Pandas Integration: DataFrame and dictionary helpers for easy analysis
- 🔧 Type Safety: Full type hints with strict pyright checking and pydantic validation
- 🏦 Comprehensive MT5 Coverage: Account info, market data, tick data, orders, positions, and more
- 🚀 Context Manager Support: Clean initialization and cleanup with
with statements (initialize only)
- 📈 Time Series Ready: OHLCV data with proper datetime indexing
- 🛡️ Robust Error Handling: Custom exceptions with detailed MT5 error information
- 💰 Advanced Trading Operations: Position management, margin calculations, and risk analysis tools
- 🧪 Dry Run Mode: Test trading strategies without executing real trades
Requirements
- Operating System: Windows (required by MetaTrader5 API)
- Python: 3.11 or higher
- MetaTrader 5: Terminal must be installed
Installation
Using pip
pip install -U pdmt5 MetaTrader5
Using uv
git clone https://github.com/dceoy/pdmt5.git
cd pdmt5
uv sync
Quick Start
import MetaTrader5 as mt5
from datetime import datetime
from pdmt5 import Mt5DataClient, Mt5Config
config = Mt5Config(
login=12345678,
password="your_password",
server="YourBroker-Server",
timeout=60000
)
with Mt5DataClient(config=config) as client:
client.login(config.login, config.password, config.server)
account_info = client.account_info_as_df()
print(account_info)
rates = client.copy_rates_from_as_df(
symbol="EURUSD",
timeframe=mt5.TIMEFRAME_H1,
date_from=datetime(2024, 1, 1),
count=100
)
print(rates.head())
positions = client.positions_get_as_df()
print(positions)
Core Components
Mt5Client
The base client wrapper for all MetaTrader5 operations with context manager support:
- Connection Management:
initialize() - Establish connection with MT5 terminal (with optional path, login, password, server, timeout)
login() - Connect to trading account with credentials
shutdown() - Close MT5 terminal connection
- Context manager support (
with statement) for automatic initialization/cleanup (initialize only)
- Terminal Information:
version() - Get MT5 terminal version, build, and release date
last_error() - Get last error code and description
account_info() - Get current trading account information
terminal_info() - Get terminal status and settings
- Symbol Operations:
symbols_total() - Get total number of financial instruments
symbols_get() - Get all symbols or filter by group
symbol_info() - Get detailed data on specific symbol
symbol_info_tick() - Get last tick for symbol
symbol_select() - Show/hide symbol in MarketWatch
- Market Depth:
market_book_add() - Subscribe to Market Depth events
market_book_get() - Get current Market Depth data
market_book_release() - Unsubscribe from Market Depth
- Market Data:
copy_rates_from() - Get bars from specified date
copy_rates_from_pos() - Get bars from specified position
copy_rates_range() - Get bars for date range
copy_ticks_from() - Get ticks from specified date
copy_ticks_range() - Get ticks for date range
- Order Operations:
orders_total() - Get number of active orders
orders_get() - Get active orders with optional filters
order_calc_margin() - Calculate required margin
order_calc_profit() - Calculate potential profit
order_check() - Check if order can be placed
order_send() - Send order to trade server
- Position Operations:
positions_total() - Get number of open positions
positions_get() - Get open positions with optional filters
- Trading History:
history_orders_total() - Get number of historical orders
history_orders_get() - Get historical orders with filters
history_deals_total() - Get number of historical deals
history_deals_get() - Get historical deals with filters
Mt5DataClient
Extends Mt5Client with pandas DataFrame and dictionary conversions:
- Enhanced Connection:
initialize_and_login_mt5() - Combined initialization and login with retry logic
- Configurable retry attempts via
retry_count parameter
- DataFrame/Dictionary Conversions: All methods have both
_as_df and _as_dict variants:
version_as_dict/df() - MT5 version information
last_error_as_dict/df() - Last error details
account_info_as_dict/df() - Account information
terminal_info_as_dict/df() - Terminal information
symbols_get_as_dicts/df() - Symbol list with optional group filter
symbol_info_as_dict/df() - Single symbol information
symbol_info_tick_as_dict/df() - Last tick data
market_book_get_as_dicts/df() - Market depth data
- OHLCV Data Methods:
copy_rates_from_as_dicts/df() - Historical bars from date
copy_rates_from_pos_as_dicts/df() - Historical bars from position
copy_rates_range_as_dicts/df() - Historical bars for date range
- Tick Data Methods:
copy_ticks_from_as_dicts/df() - Historical ticks from date
copy_ticks_range_as_dicts/df() - Historical ticks for date range
- Trading Data Methods:
orders_get_as_dicts/df() - Active orders with filters
order_check_as_dict/df() - Order validation results
order_send_as_dict/df() - Order execution results
positions_get_as_dicts/df() - Open positions with filters
history_orders_get_as_dicts/df() - Historical orders with date/ticket/position filters
history_deals_get_as_dicts/df() - Historical deals with date/ticket/position filters
- Features:
- Automatic time conversion to datetime objects
- Optional DataFrame indexing with
index_keys parameter
- Input validation for dates, counts, and positions
- Pydantic-based configuration via
Mt5Config
Mt5TradingClient
Advanced trading operations client that extends Mt5DataClient:
- Position Management:
close_open_positions() - Close all positions for specified symbol(s)
place_market_order() - Place market orders with configurable side, volume, and execution modes
update_sltp_for_open_positions() - Modify stop loss and take profit levels for open positions
- Margin Calculations:
calculate_minimum_order_margin() - Calculate minimum required margin for a specific order side
calculate_volume_by_margin() - Calculate maximum volume for given margin amount
calculate_spread_ratio() - Calculate normalized bid-ask spread ratio
calculate_new_position_margin_ratio() - Calculate margin ratio for potential new positions
- Simplified Data Access:
fetch_latest_rates_as_df() - Get recent OHLC data with timeframe strings (e.g., "M1", "H1", "D1")
fetch_latest_ticks_as_df() - Get tick data for specified seconds around last tick
collect_entry_deals_as_df() - Filter and collect entry deals (BUY/SELL) from history
fetch_positions_with_metrics_as_df() - Get open positions with calculated metrics (elapsed time, margin, profit ratios)
- Features:
- Smart order routing with configurable filling modes
- Comprehensive error handling with
Mt5TradingError
- Support for batch operations on multiple symbols
- Automatic position closing with proper order type reversal
Configuration
from pdmt5 import Mt5Config
config = Mt5Config(
login=12345678,
password="password",
server="Broker-Server",
timeout=60000
)
Examples
Getting Historical Data
import MetaTrader5 as mt5
from datetime import datetime
with Mt5DataClient(config=config) as client:
df = client.copy_rates_from_as_df(
symbol="EURUSD",
timeframe=mt5.TIMEFRAME_H1,
date_from=datetime.now(),
count=1000
)
print(df.columns)
print(df.describe())
Working with Tick Data
from datetime import datetime, timedelta
with Mt5DataClient(config=config) as client:
ticks = client.copy_ticks_from_as_df(
symbol="EURUSD",
date_from=datetime.now() - timedelta(hours=1),
count=10000,
flags=mt5.COPY_TICKS_ALL
)
print(ticks.head())
Analyzing Positions
with Mt5DataClient(config=config) as client:
positions = client.positions_get_as_df()
if not positions.empty:
summary = positions.groupby('symbol').agg({
'volume': 'sum',
'profit': 'sum',
'price_open': 'mean'
})
print(summary)
Trading Operations
from pdmt5 import Mt5TradingClient
with Mt5TradingClient(config=config) as trader:
order_result = trader.place_market_order(
symbol="EURUSD",
volume=0.1,
order_side="BUY",
order_filling_mode="IOC",
order_time_mode="GTC"
)
print(f"Order placed: {order_result['retcode']}")
update_results = trader.update_sltp_for_open_positions(
symbol="EURUSD",
stop_loss=1.0950,
take_profit=1.1050
)
for result in update_results:
print(f"Position updated: {result['retcode']}")
margin_ratio = trader.calculate_new_position_margin_ratio(
symbol="EURUSD",
new_position_side="SELL",
new_position_volume=0.2
)
print(f"New position margin ratio: {margin_ratio:.2%}")
results = trader.close_open_positions(
symbols="EURUSD",
order_filling_mode="FOK"
)
if results:
for symbol, close_results in results.items():
for result in close_results:
print(f"Closed position {result.get('position')} with result: {result['retcode']}")
Market Analysis with Mt5TradingClient
with Mt5TradingClient(config=config) as trader:
spread_ratio = trader.calculate_spread_ratio("EURUSD")
print(f"EURUSD spread ratio: {spread_ratio:.5f}")
buy_margin = trader.calculate_minimum_order_margin("EURUSD", "BUY")
sell_margin = trader.calculate_minimum_order_margin("EURUSD", "SELL")
print(f"Minimum BUY margin: {buy_margin['margin']} (volume: {buy_margin['volume']})")
print(f"Minimum SELL margin: {sell_margin['margin']} (volume: {sell_margin['volume']})")
available_margin = 1000.0
max_buy_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "BUY")
max_sell_volume = trader.calculate_volume_by_margin("EURUSD", available_margin, "SELL")
print(f"Max BUY volume for ${available_margin}: {max_buy_volume}")
print(f"Max SELL volume for ${available_margin}: {max_sell_volume}")
rates_df = trader.fetch_latest_rates_as_df(
symbol="EURUSD",
granularity="M15",
count=100
)
print(rates_df.tail())
ticks_df = trader.fetch_latest_ticks_as_df(
symbol="EURUSD",
seconds=60
)
print(f"Received {len(ticks_df)} ticks")
deals_df = trader.collect_entry_deals_as_df(
symbol="EURUSD",
history_seconds=3600
)
if not deals_df.empty:
print(f"Found {len(deals_df)} entry deals")
print(deals_df[['time', 'type', 'volume', 'price']].head())
positions_df = trader.fetch_positions_with_metrics_as_df("EURUSD")
if not positions_df.empty:
print(f"Open positions with metrics:")
print(positions_df[['ticket', 'volume', 'profit', 'elapsed_seconds', 'underlier_profit_ratio']].head())
Development
Setup Development Environment
git clone https://github.com/dceoy/pdmt5.git
cd pdmt5
uv sync
uv run pytest tests/ -v
uv run pyright .
uv run ruff check --fix .
uv run ruff format .
Code Quality
This project maintains high code quality standards:
- Type Checking: Strict mode with pyright
- Linting: Comprehensive ruff configuration with 40+ rule categories
- Testing: pytest with coverage tracking (minimum 90%)
- Documentation: Google-style docstrings
Error Handling
The package provides detailed error information:
from pdmt5 import Mt5RuntimeError
try:
with Mt5DataClient(config=config) as client:
data = client.copy_rates_from("INVALID", mt5.TIMEFRAME_H1, datetime.now(), 100)
except Mt5RuntimeError as e:
print(f"MT5 Error: {e}")
print(f"Error code: {e.error_code}")
print(f"Description: {e.description}")
Limitations
- Windows Only: Due to MetaTrader5 API requirements
- MT5 Terminal Required: The MetaTrader 5 terminal must be installed
- Single Thread: MT5 API is not thread-safe
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Ensure tests pass and coverage is maintained
- Submit a pull request
See CLAUDE.md for development guidelines.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Daichi Narushima, Ph.D.
Acknowledgments
- MetaTrader 5 for providing the Python API
- The pandas community for the excellent data manipulation tools