Skip to main content

Pandas-based data handler for MetaTrader 5

Project description

pdmt5

Pandas-based data handler for MetaTrader 5

CI/CD Python Version License: MIT Platform

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 automatically converts MT5's native data structures into pandas DataFrames, enabling seamless integration with data science workflows.

Key Features

  • 📊 Pandas Integration: All data returned as pandas DataFrames 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
  • 📈 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

From GitHub

git clone https://github.com/dceoy/pdmt5.git
pip install -U --no-cache-dir ./pdmt5

Using uv (recommended for development)

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

# Configure connection
config = Mt5Config(
    login=12345678,
    password="your_password",
    server="YourBroker-Server",
    timeout=60000
)

# Use as context manager
with Mt5DataClient(config=config) as client:
    # Get account information as DataFrame
    account_info = client.account_info_as_df()
    print(account_info)

    # Get OHLCV data as DataFrame
    rates = client.copy_rates_from_as_df(
        symbol="EURUSD",
        timeframe=mt5.TIMEFRAME_H1,
        date_from=datetime(2024, 1, 1),
        count=100
    )
    print(rates.head())

    # Get current positions as DataFrame
    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
  • 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:

  • Trading Configuration:
    • order_filling_mode - Order execution mode: "IOC" (default), "FOK", or "RETURN"
    • dry_run - Test mode flag for simulating trades without execution
  • 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
    • Dry run mode for strategy testing without real trades

Configuration

from pdmt5 import Mt5Config

config = Mt5Config(
    login=12345678,          # MT5 account number
    password="password",     # MT5 password
    server="Broker-Server",  # MT5 server name
    timeout=60000           # Connection timeout in ms
)

Examples

Getting Historical Data

import MetaTrader5 as mt5
from datetime import datetime

with Mt5DataClient(config=config) as client:
    # Get last 1000 H1 bars for EURUSD as DataFrame
    df = client.copy_rates_from_as_df(
        symbol="EURUSD",
        timeframe=mt5.TIMEFRAME_H1,
        date_from=datetime.now(),
        count=1000
    )

    # Data includes: time, open, high, low, close, tick_volume, spread, real_volume
    print(df.columns)
    print(df.describe())

Working with Tick Data

from datetime import datetime, timedelta

with Mt5DataClient(config=config) as client:
    # Get ticks for the last hour as DataFrame
    ticks = client.copy_ticks_from_as_df(
        symbol="EURUSD",
        date_from=datetime.now() - timedelta(hours=1),
        count=10000,
        flags=mt5.COPY_TICKS_ALL
    )

    # Tick data includes: time, bid, ask, last, volume, flags
    print(ticks.head())

Analyzing Positions

with Mt5DataClient(config=config) as client:
    # Get all open positions as DataFrame
    positions = client.positions_get_as_df()

    if not positions.empty:
        # Calculate summary statistics
        summary = positions.groupby('symbol').agg({
            'volume': 'sum',
            'profit': 'sum',
            'price_open': 'mean'
        })
        print(summary)

Trading Operations

from pdmt5 import Mt5TradingClient

# Create trading client with specific order filling mode
with Mt5TradingClient(config=config, order_filling_mode="IOC") as trader:
    # Place a market buy order
    order_result = trader.place_market_order(
        symbol="EURUSD",
        volume=0.1,
        order_side="BUY",
        order_filling_mode="IOC",  # Immediate or Cancel
        order_time_mode="GTC"      # Good Till Cancelled
    )
    print(f"Order placed: {order_result['retcode']}")

    # Update stop loss and take profit for open positions
    update_results = trader.update_sltp_for_open_positions(
        symbol="EURUSD",
        stop_loss=1.0950,   # New stop loss
        take_profit=1.1050  # New take profit
    )
    for result in update_results:
        print(f"Position updated: {result['retcode']}")

    # Calculate margin ratio for a new position
    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%}")

    # Close all EURUSD positions
    results = trader.close_open_positions(symbols="EURUSD")

    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']}")

    # Using dry run mode for testing
    trader_dry = Mt5TradingClient(config=config, dry_run=True)
    with trader_dry:
        # Test placing an order without actual execution
        test_order = trader_dry.place_market_order(
            symbol="GBPUSD",
            volume=0.1,
            order_side="SELL",
            dry_run=True  # Override instance setting
        )
        print(f"Test order validation: {test_order['retcode']}")

Market Analysis with Mt5TradingClient

with Mt5TradingClient(config=config) as trader:
    # Calculate spread ratio for EURUSD
    spread_ratio = trader.calculate_spread_ratio("EURUSD")
    print(f"EURUSD spread ratio: {spread_ratio:.5f}")

    # Get minimum order margin for BUY and SELL
    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']})")
    
    # Calculate volume by margin
    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}")

    # Get recent OHLC data with custom timeframe
    rates_df = trader.fetch_latest_rates_as_df(
        symbol="EURUSD",
        granularity="M15",  # 15-minute bars
        count=100
    )
    print(rates_df.tail())

    # Get tick data for the last 60 seconds
    ticks_df = trader.fetch_latest_ticks_as_df(
        symbol="EURUSD",
        seconds=60
    )
    print(f"Received {len(ticks_df)} ticks")

    # Collect entry deals for the last hour
    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())

    # Get positions with calculated metrics
    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

# Clone repository
git clone https://github.com/dceoy/pdmt5.git
cd pdmt5

# Install with uv
uv sync

# Run tests
uv run pytest test/ -v

# Run type checking
uv run pyright .

# Run linting
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:

  1. Fork the repository
  2. Create a feature branch
  3. Ensure tests pass and coverage is maintained
  4. 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pdmt5-0.1.6.tar.gz (117.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pdmt5-0.1.6-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file pdmt5-0.1.6.tar.gz.

File metadata

  • Download URL: pdmt5-0.1.6.tar.gz
  • Upload date:
  • Size: 117.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pdmt5-0.1.6.tar.gz
Algorithm Hash digest
SHA256 295471ae4159f1c80bbc86ffb6795216eedf419a9d7e0b891b7fb9161f33c3aa
MD5 4ec995150b10d9e2a59fc254cbb32338
BLAKE2b-256 08ecb7c6253e7baf6f8962da5781aa7fb276be4177e4f70eb756bf7a25c0d846

See more details on using hashes here.

File details

Details for the file pdmt5-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: pdmt5-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for pdmt5-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 0d3fd6d443dbdd6cf9409a4b8981dfdacc5cc59d1256953934af456d4dc6b035
MD5 27c507c865ea3cff1fc0c639bcb2ca2c
BLAKE2b-256 e0b76115e2573a8847334766ffa7b51057090bb6d1a41b0e6692870a81dab6fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page