tradingkit-mt5 0.1.0
Broker-agnostic trading library for Python
Requires Python: >=3.10
Keywords: algorithmic-trading, finance, metatrader, mt5, trading

# Trading Lib

A broker-agnostic Python trading library that abstracts broker APIs for platform-independent algorithmic trading strategies.

[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)

## Features

- **Broker Agnostic**: Write strategies once, run on any supported broker
- **Async-First**: Built with `asyncio` for non-blocking I/O operations
- **Type-Safe**: Full type hints with Pydantic models for runtime validation
- **Extensible**: Easy to add new broker implementations via Protocol interfaces
- **Production Ready**: Rate limiting, error handling, and connection management built-in

## Supported Brokers

| Broker | Status | Notes |
|--------|--------|-------|
| MetaTrader 5 | ✅ Implemented | Full API coverage |
| Interactive Brokers | 🔜 Planned | Coming soon |
| Binance | 🔜 Planned | Coming soon |

## Installation

```bash
# Using pip
pip install tradingkit-mt5

# Using uv
uv add tradingkit-mt5

# From source
git clone https://github.com/LucianP984/py-trading-lib.git
cd py-trading-lib
pip install -e .
```

### Requirements

- Python 3.10+
- MetaTrader 5 terminal installed (Windows only for MT5)
- Pydantic 2.0+

## Quick Start

```python
import asyncio
from decimal import Decimal
from trading_lib import create_broker, BrokerType, OrderRequest, TradeAction, OrderType

async def main():
    # Create broker instance
    broker = create_broker(BrokerType.MT5)

    # Connect to MT5 terminal
    await broker.connect(
        login=12345678,
        password="your_password",
        server="YourBroker-Demo"
    )

    # Get account info
    account = await broker.get_account_info()
    print(f"Balance: {account.balance} {account.currency}")
    print(f"Equity: {account.equity}")
    print(f"Free Margin: {account.margin_free}")

    # Get symbol info
    symbol = await broker.get_symbol_info("EURUSD")
    print(f"EURUSD Spread: {symbol.spread} points")

    # Get latest tick
    tick = await broker.get_symbol_tick("EURUSD")
    print(f"Bid: {tick.bid}, Ask: {tick.ask}")

    # Get historical bars
    from trading_lib import Timeframe
    bars = await broker.get_bars("EURUSD", Timeframe.H1, start=0, count=100)
    print(f"Retrieved {len(bars)} bars")

    # Place a market order
    request = OrderRequest(
        action=TradeAction.DEAL,
        symbol="EURUSD",
        volume=Decimal("0.1"),
        type=OrderType.BUY,
        deviation=10,
        magic=12345,
        comment="Trading Lib"
    )

    # Check order before sending
    check = await broker.check_order(request)
    if check.valid:
        result = await broker.send_order(request)
        if result.success:
            print(f"Order placed! Ticket: {result.order}")

    # Get open positions
    positions = await broker.get_positions()
    for pos in positions:
        print(f"Position {pos.ticket}: {pos.symbol} {pos.volume} lots, P/L: {pos.profit}")

    # Disconnect
    await broker.disconnect()

asyncio.run(main())
```

## Architecture

The library uses Protocol-based abstractions for broker-agnostic design:

```
┌─────────────────────────────────────────────────────────────┐
│                     Your Trading Strategy                    │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      BrokerProtocol                          │
│  (ConnectionProtocol, AccountProtocol, OrderProtocol, ...)  │
└─────────────────────────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
        ┌──────────┐   ┌──────────┐   ┌──────────┐
        │ MT5Client│   │ IBClient │   │ Binance  │
        │          │   │ (planned)│   │ (planned)│
        └──────────┘   └──────────┘   └──────────┘
```

## API Reference

### Factory

```python
from trading_lib import create_broker, BrokerType

# Create MT5 broker
broker = create_broker(BrokerType.MT5)

# With custom path
broker = create_broker(BrokerType.MT5, path="C:/Program Files/MT5/terminal64.exe")
```

### Connection

```python
# Connect with login
await broker.connect(login=12345, password="pass", server="Demo-Server")

# Check connection
is_connected = await broker.is_connected()

# Disconnect
await broker.disconnect()

# Context manager
async with create_broker(BrokerType.MT5) as broker:
    await broker.connect(...)
    # Auto-disconnects on exit
```

### Account Information

```python
account = await broker.get_account_info()
# AccountInfo(login, balance, equity, margin, margin_free, leverage, ...)

terminal = await broker.get_terminal_info()
# TerminalInfo(name, company, connected, trade_allowed, ...)
```

### Symbols

```python
# Get all symbols
symbols = await broker.get_symbols()

# Filter by group
forex_symbols = await broker.get_symbols(group="*USD*")

# Get specific symbol info
eurusd = await broker.get_symbol_info("EURUSD")

# Get latest tick
tick = await broker.get_symbol_tick("EURUSD")

# Select symbol in Market Watch
await broker.select_symbol("EURUSD", enable=True)
```

### Market Data

```python
from trading_lib import Timeframe

# Get bars by position (0 = current bar)
bars = await broker.get_bars("EURUSD", Timeframe.H1, start=0, count=100)

# Get bars by date range
from datetime import datetime
bars = await broker.get_bars(
    "EURUSD",
    Timeframe.M15,
    start=datetime(2024, 1, 1),
    end=datetime(2024, 1, 31)
)

# Get ticks
ticks = await broker.get_ticks("EURUSD", start=datetime.now(), count=1000)
```

### Orders

```python
from trading_lib import OrderRequest, TradeAction, OrderType, OrderFilling

# Market order
request = OrderRequest(
    action=TradeAction.DEAL,
    symbol="EURUSD",
    volume=Decimal("0.1"),
    type=OrderType.BUY,
    deviation=10,
)

# Pending order
request = OrderRequest(
    action=TradeAction.PENDING,
    symbol="EURUSD",
    volume=Decimal("0.1"),
    type=OrderType.BUY_LIMIT,
    price=Decimal("1.0800"),
    sl=Decimal("1.0750"),
    tp=Decimal("1.0900"),
)

# Check and send
check = await broker.check_order(request)
if check.valid:
    result = await broker.send_order(request)

# Get active orders
orders = await broker.get_orders()
orders = await broker.get_orders(symbol="EURUSD")
```

### Positions

```python
# Get all positions
positions = await broker.get_positions()

# Filter by symbol
eurusd_positions = await broker.get_positions(symbol="EURUSD")

# Filter by ticket
position = await broker.get_positions(ticket=123456)
```

### Trade History

```python
from datetime import datetime, timedelta

start = datetime.now() - timedelta(days=30)
end = datetime.now()

# Get historical orders
orders = await broker.get_history_orders(start=start, end=end)

# Get deals
deals = await broker.get_history_deals(start=start, end=end)
```

### Market Depth

```python
# Subscribe to market depth
await broker.subscribe_book("EURUSD")

# Get current book
book = await broker.get_book("EURUSD")
for entry in book:
    print(f"{entry.type}: {entry.price} x {entry.volume}")

# Unsubscribe
await broker.unsubscribe_book("EURUSD")
```

## Error Handling

```python
from trading_lib import (
    TradingError,
    ConnectionError,
    AuthenticationError,
    OrderRejectedError,
    InsufficientMarginError,
)

try:
    await broker.connect(login=12345, password="wrong")
except AuthenticationError as e:
    print(f"Login failed: {e.message} (code: {e.code})")

try:
    result = await broker.send_order(request)
except OrderRejectedError as e:
    print(f"Order rejected: {e.message}")
    print(f"Original request: {e.request}")
except InsufficientMarginError:
    print("Not enough margin")
```

## Adding Custom Brokers

Implement the `BrokerProtocol` to add new broker support:

```python
from trading_lib import BrokerProtocol, register_broker, BrokerType

class MyBrokerClient(BrokerProtocol):
    @property
    def broker_name(self) -> str:
        return "MyBroker"

    async def connect(self, ...) -> bool:
        # Implementation
        ...

    async def get_account_info(self) -> AccountInfo:
        # Implementation
        ...

    # Implement all protocol methods...

# Register with factory
register_broker(BrokerType.MY_BROKER, MyBrokerClient)
```

## Development

```bash
# Clone repository
git clone https://github.com/LucianP984/py-trading-lib.git
cd py-trading-lib

# Install with dev dependencies
pip install -e ".[dev]"

# Run tests
pytest

# Type checking
mypy trading_lib

# Linting
ruff check trading_lib
```

## License

MIT License - see [LICENSE](LICENSE) for details.

## Disclaimer

This software is for educational and research purposes only. Trading financial instruments carries significant risk. Always test thoroughly with demo accounts before using real money. The authors are not responsible for any financial losses incurred through the use of this software.