mirror of
https://github.com/Ichinga-Samuel/aiomql.git
synced 2026-07-28 12:47:43 +00:00
1586 lines
41 KiB
Markdown
1586 lines
41 KiB
Markdown
# aiomql — The Complete Guide to Building Algorithmic Trading Bots with Python & MetaTrader 5
|
||
|
||
> **An in-depth tutorial covering every feature of the aiomql framework — from your first async connection to production-grade, multi-instrument trading bots.**
|
||
|
||
## Table of Contents
|
||
|
||
1. [What is aiomql?](#what-is-aiomql)
|
||
2. [Installation & Requirements](#installation--requirements)
|
||
3. [Configuration](#configuration)
|
||
4. [Connecting to MetaTrader 5](#connecting-to-metatrader-5)
|
||
5. [Working with Symbols](#working-with-symbols)
|
||
6. [Market Data: Candles & Ticks](#market-data-candles--ticks)
|
||
7. [Technical Analysis](#technical-analysis)
|
||
8. [Placing Orders](#placing-orders)
|
||
9. [The Trader Abstraction](#the-trader-abstraction)
|
||
10. [Risk & Money Management (RAM)](#risk--money-management-ram)
|
||
11. [Building a Strategy](#building-a-strategy)
|
||
12. [Trading Sessions](#trading-sessions)
|
||
13. [The Bot Orchestrator](#the-bot-orchestrator)
|
||
14. [Position Tracking](#position-tracking)
|
||
15. [Trade Result Recording](#trade-result-recording)
|
||
16. [Trade History](#trade-history)
|
||
17. [Contributed Extensions](#contributed-extensions)
|
||
18. [Multi-Process Execution](#multi-process-execution)
|
||
19. [Synchronous API](#synchronous-api)
|
||
20. [Full Example: EMA Crossover Bot](#full-example-ema-crossover-bot)
|
||
21. [Architecture Overview](#architecture-overview)
|
||
22. [Summary](#summary)
|
||
|
||
---
|
||
|
||
## What is aiomql?
|
||
|
||
**aiomql** is a Python framework for building algorithmic trading bots on top of
|
||
[MetaTrader 5](https://www.metatrader5.com/). Rather than writing raw MT5 API calls
|
||
and managing connection boilerplate, aiomql gives you:
|
||
|
||
- **An async-first MetaTrader 5 interface** — every MT5 function wrapped with
|
||
`asyncio.to_thread` and automatic reconnection on transient errors.
|
||
- **High-level abstractions** — Strategy, Bot, Trader, Order, Symbol, Sessions, RAM,
|
||
and more — so you can focus on trading logic.
|
||
- **A full synchronous API** — every async class has a sync counterpart for scripts,
|
||
notebooks, and quick prototyping.
|
||
- **Built-in technical analysis** — pandas-ta integration with optional TA-Lib support.
|
||
- **Trade recording** — persist results to CSV, JSON, or SQLite automatically.
|
||
- **Position tracking** — monitor open positions with trailing stops, extending
|
||
take-profits, hedging, and stacking.
|
||
- **Multi-process execution** — run independent bots in parallel with a single call.
|
||
|
||
aiomql is designed for traders who want to build, test, and deploy algorithmic
|
||
strategies in Python without reinventing the wheel.
|
||
|
||
---
|
||
|
||
## Installation & Requirements
|
||
|
||
### Prerequisites
|
||
|
||
| Requirement | Details |
|
||
|---|---|
|
||
| **Python** | ≥ 3.13 |
|
||
| **OS** | Windows (MetaTrader 5 terminal requirement) |
|
||
| **Account** | A MetaTrader 5 trading account (demo or live) |
|
||
|
||
### Install via pip
|
||
|
||
```bash
|
||
pip install aiomql
|
||
```
|
||
|
||
### Optional Extras
|
||
|
||
```bash
|
||
# TA-Lib technical indicators
|
||
pip install aiomql[talib]
|
||
|
||
# Optional accelerators (Cython, Numba, tqdm)
|
||
pip install aiomql[optional]
|
||
|
||
# Everything
|
||
pip install aiomql[all]
|
||
```
|
||
|
||
---
|
||
|
||
## Configuration
|
||
|
||
aiomql uses a **singleton `Config` class** that can load settings from a JSON file
|
||
or be configured programmatically. This single configuration object is shared across
|
||
all components.
|
||
|
||
### Option 1: JSON Configuration File
|
||
|
||
Create an `aiomql.json` file in your project root:
|
||
|
||
```json
|
||
{
|
||
"login": 12345678,
|
||
"password": "your_password",
|
||
"server": "YourBroker-Demo",
|
||
"trade_record_mode": "csv"
|
||
}
|
||
```
|
||
|
||
The `Config` class automatically searches for `aiomql.json` in your project directory
|
||
tree, starting from the current working directory and walking up to the root.
|
||
|
||
### Option 2: Programmatic Configuration
|
||
|
||
```python
|
||
from aiomql import Config
|
||
|
||
config = Config(
|
||
login=12345678,
|
||
password="your_password",
|
||
server="YourBroker-Demo",
|
||
trade_record_mode="csv"
|
||
)
|
||
```
|
||
|
||
### Key Configuration Options
|
||
|
||
| Setting | Type | Description |
|
||
|---|---|---|
|
||
| `login` | `int` | MetaTrader account number |
|
||
| `password` | `str` | Account password |
|
||
| `server` | `str` | Broker server name |
|
||
| `path` | `str` | Path to the MT5 terminal executable |
|
||
| `trade_record_mode` | `str` | Trade logging format: `"csv"`, `"json"`, or `"sql"` |
|
||
| `root` | `str` | Project root directory |
|
||
|
||
Because `Config` is a **singleton**, any component in your application that creates
|
||
a `Config()` instance receives the same shared configuration.
|
||
|
||
### State & Store
|
||
|
||
The `Config` class also provides access to two persistent storage mechanisms:
|
||
|
||
- **`config.state`** — A `State` object for persistent key-value storage (backed by SQLite).
|
||
- **`config.store`** — A `Store` object for in-memory shared state accessible across components.
|
||
|
||
These are useful for sharing data between strategies, trackers, and other components at runtime.
|
||
|
||
---
|
||
|
||
## Connecting to MetaTrader 5
|
||
|
||
The `MetaTrader` class wraps every MT5 API function with async execution and
|
||
automatic retry logic. It supports both async context manager usage and direct calls.
|
||
|
||
### Basic Connection
|
||
|
||
```python
|
||
import asyncio
|
||
from aiomql import MetaTrader
|
||
|
||
|
||
async def main():
|
||
async with MetaTrader() as mt5:
|
||
# Get account information
|
||
account = await mt5.account_info()
|
||
print(f"Balance: {account.balance}")
|
||
print(f"Equity: {account.equity}")
|
||
|
||
# Get terminal info
|
||
terminal = await mt5.terminal_info()
|
||
print(f"Terminal: {terminal.name}")
|
||
|
||
# Get available symbols
|
||
symbols = await mt5.symbols_get()
|
||
print(f"{len(symbols)} symbols available")
|
||
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
### How It Works
|
||
|
||
Under the hood, `MetaTrader` runs every MT5 call through `asyncio.to_thread`, which
|
||
means your event loop stays responsive even during blocking I/O. If a connection
|
||
error occurs, the `_handler` method automatically retries initialization and login up
|
||
to 3 times.
|
||
|
||
```python
|
||
# The MetaTrader class is also a singleton — every instance shares the same state
|
||
mt5_a = MetaTrader()
|
||
mt5_b = MetaTrader()
|
||
assert mt5_a is mt5_b # True
|
||
```
|
||
|
||
### Key Methods
|
||
|
||
| Method | Description |
|
||
|---|---|
|
||
| `initialize()` | Connect to the MT5 terminal |
|
||
| `login()` | Log in with credentials |
|
||
| `shutdown()` | Close the connection |
|
||
| `account_info()` | Get account details |
|
||
| `terminal_info()` | Get terminal details |
|
||
| `symbols_get()` | List available symbols |
|
||
| `symbol_info()` | Get info for a specific symbol |
|
||
| `copy_rates_from()` | Fetch OHLC bar data |
|
||
| `copy_rates_from_pos()` | Fetch recent bars by count |
|
||
| `copy_ticks_from()` | Fetch tick data |
|
||
| `order_check()` | Check if an order can be executed |
|
||
| `order_send()` | Send a trade order |
|
||
| `positions_get()` | Get open positions |
|
||
| `history_deals_get()` | Get historical deals |
|
||
| `history_orders_get()` | Get historical orders |
|
||
|
||
---
|
||
|
||
## Working with Symbols
|
||
|
||
The `Symbol` class is a high-level wrapper around a financial instrument. It pulls
|
||
properties from MT5 (spread, lot sizes, contract size, etc.) and provides methods
|
||
to fetch market data.
|
||
|
||
### Initializing a Symbol
|
||
|
||
```python
|
||
from aiomql import Symbol
|
||
|
||
|
||
async def main():
|
||
symbol = Symbol(name="EURUSD")
|
||
await symbol.initialize()
|
||
|
||
# Now you can access all symbol properties
|
||
print(f"Spread: {symbol.spread}")
|
||
print(f"Point: {symbol.point}")
|
||
print(f"Min volume: {symbol.volume_min}")
|
||
print(f"Max volume: {symbol.volume_max}")
|
||
print(f"Volume step: {symbol.volume_step}")
|
||
print(f"Contract size: {symbol.trade_contract_size}")
|
||
```
|
||
|
||
### Getting Price Data
|
||
|
||
```python
|
||
# Get the current tick
|
||
tick = await symbol.info_tick()
|
||
print(f"Bid: {tick.bid}, Ask: {tick.ask}")
|
||
|
||
# Get the last 500 H1 candles
|
||
from aiomql import TimeFrame
|
||
|
||
candles = await symbol.copy_rates_from_pos(
|
||
timeframe=TimeFrame.H1, count=500
|
||
)
|
||
```
|
||
|
||
### Volume Utilities
|
||
|
||
The `Symbol` class includes helpers for working with lot sizes:
|
||
|
||
```python
|
||
# Check if a volume is valid
|
||
is_valid, adjusted = symbol.check_volume(volume=0.15)
|
||
|
||
# Round volume to the nearest step
|
||
rounded = symbol.round_off_volume(volume=0.153, round_down=True)
|
||
```
|
||
|
||
### Currency Conversion
|
||
|
||
```python
|
||
# Convert between currencies using live rates
|
||
amount_in_usd = await symbol.convert_currency(
|
||
amount=100.0,
|
||
from_currency="EUR",
|
||
to_currency="USD"
|
||
)
|
||
```
|
||
|
||
### ForexSymbol — Specialized for Forex
|
||
|
||
The `ForexSymbol` class extends `Symbol` with forex-specific calculations:
|
||
|
||
```python
|
||
from aiomql import ForexSymbol
|
||
|
||
eurusd = ForexSymbol(name="EURUSD")
|
||
await eurusd.initialize()
|
||
|
||
# Pip value (point * 10)
|
||
print(f"Pip: {eurusd.pip}") # 0.0001 for most pairs
|
||
|
||
# Compute volume based on risk amount and stop loss distance
|
||
volume = await eurusd.compute_volume_sl(
|
||
amount=100.0, # risk $100
|
||
price=1.1000, # entry price
|
||
sl=1.0950, # stop loss
|
||
round_down=True
|
||
)
|
||
|
||
# Compute volume based on risk amount and points
|
||
volume = await eurusd.compute_volume_points(
|
||
amount=100.0,
|
||
points=500,
|
||
round_down=True
|
||
)
|
||
|
||
# Compute points needed for a given profit at a given volume
|
||
points = eurusd.compute_points(amount=50.0, volume=0.1)
|
||
```
|
||
|
||
---
|
||
|
||
## Market Data: Candles & Ticks
|
||
|
||
### Candles
|
||
|
||
The `Candles` class wraps pandas `DataFrame` with trading-specific functionality.
|
||
When you fetch rates from a `Symbol`, you receive a `Candles` object:
|
||
|
||
```python
|
||
from aiomql import TimeFrame
|
||
|
||
candles = await symbol.copy_rates_from_pos(
|
||
timeframe=TimeFrame.H1, count=500
|
||
)
|
||
|
||
# It's a DataFrame under the hood
|
||
print(candles.data.head())
|
||
|
||
# Access individual candles by index
|
||
last_candle = candles[-1]
|
||
print(f"Open: {last_candle.open}, Close: {last_candle.close}")
|
||
|
||
# Candle properties
|
||
print(f"Body: {last_candle.body}")
|
||
print(f"Range: {last_candle.range}")
|
||
print(f"Is Bullish: {last_candle.is_bullish()}")
|
||
print(f"Is Bearish: {last_candle.is_bearish()}")
|
||
print(f"Is Doji: {last_candle.is_doji()}")
|
||
|
||
# DataFrame columns are accessible directly
|
||
print(candles.open) # Series of open prices
|
||
print(candles.close) # Series of close prices
|
||
print(candles.high) # Series of high prices
|
||
print(candles.low) # Series of low prices
|
||
```
|
||
|
||
### Renaming Columns
|
||
|
||
```python
|
||
# Rename DataFrame columns (useful after adding indicators)
|
||
candles.rename(
|
||
EMA_34="fast_ema",
|
||
EMA_55="slow_ema",
|
||
inplace=True
|
||
)
|
||
```
|
||
|
||
### Ticks
|
||
|
||
The `Tick` class represents a single price tick, and `Ticks` is a collection:
|
||
|
||
```python
|
||
# Get recent ticks
|
||
ticks = await symbol.copy_ticks_from(
|
||
date_from=datetime.now() - timedelta(minutes=5),
|
||
count=1000
|
||
)
|
||
|
||
# Access tick data
|
||
for tick in ticks:
|
||
print(f"Time: {tick.time}, Bid: {tick.bid}, Ask: {tick.ask}")
|
||
|
||
# The Ticks object also supports technical analysis
|
||
ticks.ta.sma(length=20, append=True)
|
||
```
|
||
|
||
---
|
||
|
||
## Technical Analysis
|
||
|
||
aiomql integrates **pandas-ta** (classic) out of the box, available through the
|
||
`.ta` accessor on `Candles` and `Ticks` objects. Every pandas-ta indicator is available
|
||
directly.
|
||
|
||
### Using pandas-ta Indicators
|
||
|
||
```python
|
||
candles = await symbol.copy_rates_from_pos(
|
||
timeframe=TimeFrame.H1, count=500
|
||
)
|
||
|
||
# Moving averages
|
||
candles.ta.sma(length=20, append=True)
|
||
candles.ta.ema(length=50, append=True)
|
||
|
||
# RSI
|
||
candles.ta.rsi(length=14, append=True)
|
||
|
||
# MACD
|
||
candles.ta.macd(fast=12, slow=26, signal=9, append=True)
|
||
|
||
# Bollinger Bands
|
||
candles.ta.bbands(length=20, std=2, append=True)
|
||
|
||
# ATR
|
||
candles.ta.atr(length=14, append=True)
|
||
|
||
# Stochastic
|
||
candles.ta.stoch(k=14, d=3, append=True)
|
||
|
||
# Access the results as DataFrame columns
|
||
print(candles.data.columns.tolist())
|
||
```
|
||
|
||
### The ta_lib Helper
|
||
|
||
The `Candles` object also includes a `ta_lib` helper with convenience functions:
|
||
|
||
```python
|
||
# Check if series A is above series B
|
||
above = candles.ta_lib.above(candles.fast_ema, candles.slow_ema)
|
||
|
||
# Check if series A is below series B
|
||
below = candles.ta_lib.below(candles.fast_ema, candles.slow_ema)
|
||
```
|
||
|
||
### Optional: TA-Lib Integration
|
||
|
||
If you install `aiomql[talib]`, you get access to the full C-based TA-Lib library
|
||
for high-performance indicator computation.
|
||
|
||
---
|
||
|
||
## Placing Orders
|
||
|
||
The `Order` class handles all trade order operations. It's a subclass of
|
||
`TradeRequest` and provides methods for checking, sending, and managing orders.
|
||
|
||
### Creating and Sending a Market Order
|
||
|
||
```python
|
||
from aiomql import Order, OrderType, TradeAction
|
||
|
||
# Create a buy order
|
||
order = Order(
|
||
symbol="EURUSD",
|
||
type=OrderType.BUY,
|
||
volume=0.1,
|
||
price=1.1000,
|
||
sl=1.0950,
|
||
tp=1.1100
|
||
)
|
||
|
||
# Check if the order can be executed
|
||
check_result = await order.check()
|
||
print(f"Margin required: {check_result.margin}")
|
||
|
||
# Send the order
|
||
result = await order.send()
|
||
print(f"Order ticket: {result.order}")
|
||
print(f"Return code: {result.retcode}")
|
||
```
|
||
|
||
### Modifying an Order
|
||
|
||
```python
|
||
order.modify(
|
||
sl=1.0960,
|
||
tp=1.1120
|
||
)
|
||
```
|
||
|
||
### Margin and Profit Calculations
|
||
|
||
```python
|
||
# Calculate required margin
|
||
margin = await order.calc_margin()
|
||
print(f"Margin needed: {margin}")
|
||
|
||
# Calculate expected profit (at take-profit level)
|
||
profit = await order.calc_profit()
|
||
print(f"Expected profit: {profit}")
|
||
|
||
# Calculate expected loss (at stop-loss level)
|
||
loss = await order.calc_loss()
|
||
print(f"Expected loss: {loss}")
|
||
```
|
||
|
||
### Pending Order Management
|
||
|
||
```python
|
||
# Get total pending orders
|
||
total = await Order.orders_total()
|
||
|
||
# Get all pending orders
|
||
pending = await Order.get_pending_orders()
|
||
|
||
# Get a specific pending order by ticket
|
||
order = await Order.get_pending_order(ticket=123456)
|
||
|
||
# Cancel a pending order
|
||
await Order.cancel_order(order=123456, symbol="EURUSD")
|
||
```
|
||
|
||
### Connection Retry Logic
|
||
|
||
The `send_order` method automatically retries on connection errors (retcode 10031)
|
||
with exponential backoff, up to 3 retries.
|
||
|
||
---
|
||
|
||
## The Trader Abstraction
|
||
|
||
While `Order` handles the low-level mechanics, the `Trader` class provides a
|
||
higher-level interface for placing trades with proper risk management and trade
|
||
recording.
|
||
|
||
### Why Use Trader?
|
||
|
||
`Trader` ties together the `Order`, `Symbol`, and `RAM` (risk manager) classes
|
||
into a cohesive workflow:
|
||
|
||
1. **Calculate position size** based on your risk parameters
|
||
2. **Set stop-loss and take-profit** levels
|
||
3. **Check the order** before sending
|
||
4. **Send the order** and handle errors
|
||
5. **Record the trade** to your chosen storage format
|
||
|
||
### Creating Orders with Stops
|
||
|
||
```python
|
||
from aiomql import Trader, ForexSymbol, RAM, OrderType
|
||
|
||
symbol = ForexSymbol(name="EURUSD")
|
||
await symbol.initialize()
|
||
|
||
ram = RAM(risk=2, risk_to_reward=3) # 2% risk, 1:3 R:R
|
||
trader = Trader(symbol=symbol, ram=ram)
|
||
|
||
# Create order with stop-loss and take-profit using absolute prices
|
||
await trader.create_order_with_stops(
|
||
order_type=OrderType.BUY,
|
||
sl=1.0950,
|
||
tp=1.1150
|
||
)
|
||
|
||
# Create order with just a stop-loss (TP calculated from R:R ratio)
|
||
await trader.create_order_with_sl(
|
||
order_type=OrderType.BUY,
|
||
sl=1.0950
|
||
)
|
||
|
||
# Create order using points distance
|
||
await trader.create_order_with_points(
|
||
order_type=OrderType.BUY,
|
||
points=500
|
||
)
|
||
|
||
# Create a simple order with no stops
|
||
await trader.create_order_no_stops(
|
||
order_type=OrderType.BUY,
|
||
volume=0.01
|
||
)
|
||
```
|
||
|
||
### Checking and Sending
|
||
|
||
```python
|
||
# Check if the order passes broker validation
|
||
can_trade = await trader.check_order()
|
||
|
||
if can_trade:
|
||
result = await trader.send_order()
|
||
```
|
||
|
||
### Recording Trades
|
||
|
||
After sending, trades are automatically recorded via the `record_trade` method:
|
||
|
||
```python
|
||
await trader.record_trade(
|
||
result=result,
|
||
parameters={"strategy": "EMA Crossover"},
|
||
name="EMAXOver",
|
||
expected_profit=25.50
|
||
)
|
||
```
|
||
|
||
### Pre-Built Traders
|
||
|
||
aiomql ships with two ready-to-use `Trader` subclasses in the `contrib` package:
|
||
|
||
#### SimpleTrader
|
||
|
||
Places trades with a specified stop-loss. Volume is calculated based on the RAM
|
||
risk amount and the distance to the stop-loss.
|
||
|
||
```python
|
||
from aiomql import SimpleTrader, ForexSymbol, OrderType
|
||
|
||
symbol = ForexSymbol(name="EURUSD")
|
||
trader = SimpleTrader(symbol=symbol)
|
||
|
||
await trader.place_trade(
|
||
order_type=OrderType.BUY,
|
||
sl=1.0950,
|
||
parameters={"name": "MyStrategy"}
|
||
)
|
||
```
|
||
|
||
#### ScalpTrader
|
||
|
||
Places trades using minimum volume and no stop-loss/take-profit levels.
|
||
Ideal for scalping strategies where positions are managed manually.
|
||
|
||
```python
|
||
from aiomql import ScalpTrader, ForexSymbol, OrderType
|
||
|
||
symbol = ForexSymbol(name="EURUSD")
|
||
trader = ScalpTrader(symbol=symbol)
|
||
|
||
await trader.place_trade(
|
||
order_type=OrderType.BUY,
|
||
parameters={"name": "ScalpBot"}
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Risk & Money Management (RAM)
|
||
|
||
The `RAM` (Risk Assessment & Money) class is at the heart of aiomql's risk
|
||
management. It calculates position sizes, enforces trade limits, and manages
|
||
your exposure.
|
||
|
||
### Configuration
|
||
|
||
```python
|
||
from aiomql import RAM
|
||
|
||
ram = RAM(
|
||
risk=2, # Risk 2% of free margin per trade
|
||
risk_to_reward=3, # 1:3 risk-to-reward ratio
|
||
min_amount=5, # Minimum $5 risk per trade
|
||
max_amount=500, # Maximum $500 risk per trade
|
||
loss_limit=3, # Max 3 losing positions at once
|
||
open_limit=5, # Max 5 total open positions
|
||
fixed_amount=None # Set to override percentage-based calculation
|
||
)
|
||
```
|
||
|
||
### Calculating Risk Amount
|
||
|
||
```python
|
||
# Get the amount to risk per trade (based on free margin)
|
||
amount = await ram.get_amount()
|
||
print(f"Amount to risk: ${amount:.2f}")
|
||
|
||
# If fixed_amount is set, it always returns that
|
||
ram.modify_ram(fixed_amount=100.0)
|
||
amount = await ram.get_amount() # Always $100
|
||
```
|
||
|
||
### Enforcing Position Limits
|
||
|
||
```python
|
||
# Check if we can open a new position
|
||
can_open = await ram.check_open_positions()
|
||
if not can_open:
|
||
print("Too many open positions!")
|
||
|
||
# Check if we've hit the loss limit
|
||
can_trade = await ram.check_losing_positions()
|
||
if not can_trade:
|
||
print("Too many losing positions!")
|
||
```
|
||
|
||
### How the Amount is Calculated
|
||
|
||
The calculation flow is:
|
||
|
||
1. If `fixed_amount` is set → return `fixed_amount`
|
||
2. Otherwise → `margin_free × (risk / 100)`
|
||
3. If `min_amount` and `max_amount` are set → clamp the result
|
||
|
||
---
|
||
|
||
## Building a Strategy
|
||
|
||
The `Strategy` class is the cornerstone of aiomql. You subclass it, define your
|
||
parameters, and implement the `trade()` method. The framework handles everything
|
||
else — initialization, the execution loop, session management, and error handling.
|
||
|
||
### Anatomy of a Strategy
|
||
|
||
```python
|
||
from aiomql import Strategy, ForexSymbol, TimeFrame, Tracker, OrderType, Sessions, ScalpTrader
|
||
|
||
|
||
class MyStrategy(Strategy):
|
||
# Type annotations for strategy-specific parameters
|
||
ttf: TimeFrame
|
||
fast_period: int
|
||
slow_period: int
|
||
tracker: Tracker
|
||
|
||
# Default parameter values — these become instance attributes
|
||
parameters = {
|
||
"ttf": TimeFrame.H1,
|
||
"fast_period": 20,
|
||
"slow_period": 50,
|
||
}
|
||
|
||
def __init__(self, *, symbol: ForexSymbol, params: dict = None,
|
||
sessions: Sessions = None, name: str = "MyStrategy"):
|
||
super().__init__(symbol=symbol, params=params, sessions=sessions, name=name)
|
||
self.tracker = Tracker(snooze=self.ttf.seconds)
|
||
self.trader = ScalpTrader(symbol=self.symbol)
|
||
|
||
async def trade(self):
|
||
"""This method is called repeatedly by the execution loop."""
|
||
# 1. Fetch market data
|
||
candles = await self.symbol.copy_rates_from_pos(
|
||
timeframe=self.ttf, count=200
|
||
)
|
||
|
||
# 2. Apply indicators
|
||
candles.ta.sma(length=self.fast_period, append=True)
|
||
candles.ta.sma(length=self.slow_period, append=True)
|
||
candles.rename(
|
||
**{f"SMA_{self.fast_period}": "fast",
|
||
f"SMA_{self.slow_period}": "slow"},
|
||
inplace=True
|
||
)
|
||
|
||
# 3. Check for signals
|
||
if candles.ta_lib.above(candles.fast, candles.slow).iloc[-1]:
|
||
self.tracker.update(order_type=OrderType.BUY, snooze=3600)
|
||
elif candles.ta_lib.below(candles.fast, candles.slow).iloc[-1]:
|
||
self.tracker.update(order_type=OrderType.SELL, snooze=3600)
|
||
else:
|
||
self.tracker.update(order_type=None, snooze=self.ttf.seconds)
|
||
|
||
# 4. Execute or sleep
|
||
if self.tracker.order_type is not None:
|
||
await self.trader.place_trade(
|
||
order_type=self.tracker.order_type,
|
||
parameters=self.parameters
|
||
)
|
||
await self.delay(secs=self.tracker.snooze)
|
||
else:
|
||
await self.sleep(secs=self.tracker.snooze)
|
||
```
|
||
|
||
### Key Concepts
|
||
|
||
#### Parameters Dictionary
|
||
|
||
The `parameters` class attribute defines default values for your strategy. When you
|
||
instantiate the strategy, you can override any parameter via the `params` argument:
|
||
|
||
```python
|
||
strategy = MyStrategy(
|
||
symbol=ForexSymbol(name="EURUSD"),
|
||
params={"fast_period": 10, "slow_period": 30}
|
||
)
|
||
```
|
||
|
||
Parameters are also accessible as attributes thanks to `__getattr__`:
|
||
|
||
```python
|
||
print(strategy.fast_period) # 10
|
||
```
|
||
|
||
#### The Tracker
|
||
|
||
The `Tracker` (from `aiomql.contrib.utils`) is a lightweight state holder for
|
||
strategies. It stores the current `order_type`, a `snooze` duration, and any extra
|
||
state you want to track:
|
||
|
||
```python
|
||
from aiomql import Tracker
|
||
|
||
tracker = Tracker(snooze=300) # default snooze of 5 minutes
|
||
|
||
# Update tracker state
|
||
tracker.update(
|
||
order_type=OrderType.BUY,
|
||
snooze=3600,
|
||
trend="bullish",
|
||
last_price=1.1050
|
||
)
|
||
|
||
# Access state
|
||
print(tracker.order_type) # OrderType.BUY
|
||
print(tracker.trend) # "bullish"
|
||
```
|
||
|
||
#### Sleep vs Delay
|
||
|
||
- **`self.sleep(secs=...)`** — Computes the exact time until the next bar opens,
|
||
ensuring your strategy wakes up right at the start of a new candle. This is
|
||
critical for cooperative multitasking.
|
||
- **`self.delay(secs=...)`** — A simple `asyncio.sleep` for the given duration.
|
||
|
||
#### Initialization
|
||
|
||
Override `initialize()` to run one-time setup. By default, it initializes the
|
||
symbol:
|
||
|
||
```python
|
||
async def initialize(self):
|
||
"""Called once before the strategy starts trading."""
|
||
result = await self.symbol.initialize()
|
||
# Load historical data, train models, etc.
|
||
return result
|
||
```
|
||
|
||
#### Stopping a Strategy
|
||
|
||
Raise `StopTrading` from within `trade()` to gracefully stop the strategy:
|
||
|
||
```python
|
||
from aiomql.core.exceptions import StopTrading
|
||
|
||
async def trade(self):
|
||
if some_condition:
|
||
raise StopTrading("Reached daily profit target")
|
||
```
|
||
|
||
---
|
||
|
||
## Trading Sessions
|
||
|
||
Sessions let you restrict when a strategy can trade. This is essential for forex
|
||
markets where different sessions (London, New York, Tokyo) have different
|
||
characteristics.
|
||
|
||
### Defining Sessions
|
||
|
||
```python
|
||
from datetime import time
|
||
from aiomql import Session, Sessions
|
||
|
||
# Define individual sessions (times are in UTC)
|
||
london = Session(
|
||
name="London",
|
||
start=time(8, 0),
|
||
end=time(16, 0)
|
||
)
|
||
|
||
new_york = Session(
|
||
name="New York",
|
||
start=time(13, 0),
|
||
end=time(21, 0)
|
||
)
|
||
|
||
tokyo = Session(
|
||
name="Tokyo",
|
||
start=time(0, 0),
|
||
end=time(9, 0)
|
||
)
|
||
```
|
||
|
||
### Session Actions
|
||
|
||
Sessions can automatically execute actions at their start and end:
|
||
|
||
```python
|
||
# Close all positions when the London session ends
|
||
london = Session(
|
||
name="London",
|
||
start=time(8, 0),
|
||
end=time(16, 0),
|
||
on_end="close_all"
|
||
)
|
||
|
||
# Close only losing positions at session end
|
||
new_york = Session(
|
||
name="New York",
|
||
start=time(13, 0),
|
||
end=time(21, 0),
|
||
on_end="close_loss"
|
||
)
|
||
|
||
# Close only winning positions
|
||
tokyo = Session(
|
||
name="Tokyo",
|
||
start=time(0, 0),
|
||
end=time(9, 0),
|
||
on_end="close_win"
|
||
)
|
||
|
||
# Custom actions
|
||
async def my_start_action(session):
|
||
print(f"{session.name} started!")
|
||
|
||
async def my_end_action(session):
|
||
print(f"{session.name} ended!")
|
||
|
||
custom = Session(
|
||
name="Custom",
|
||
start=time(10, 0),
|
||
end=time(18, 0),
|
||
on_start="custom_start",
|
||
on_end="custom_end",
|
||
custom_start=my_start_action,
|
||
custom_end=my_end_action
|
||
)
|
||
```
|
||
|
||
### Combining Sessions
|
||
|
||
Group sessions together with the `Sessions` class. When a strategy uses
|
||
`Sessions`, the framework automatically:
|
||
|
||
1. Checks if the current time falls within any session
|
||
2. Waits (sleeps) if outside all sessions
|
||
3. Handles transitions between sessions (closing the previous, opening the next)
|
||
|
||
```python
|
||
sessions = Sessions(sessions=[london, new_york])
|
||
|
||
strategy = MyStrategy(
|
||
symbol=ForexSymbol(name="EURUSD"),
|
||
sessions=sessions
|
||
)
|
||
```
|
||
|
||
### Session Utilities
|
||
|
||
```python
|
||
# Check if currently in session
|
||
print(london.in_session())
|
||
|
||
# Get session duration
|
||
duration = london.duration()
|
||
print(f"Duration: {duration.hours}h {duration.minutes}m")
|
||
|
||
# Seconds until session starts
|
||
secs = london.until()
|
||
print(f"Session starts in {secs} seconds")
|
||
```
|
||
|
||
---
|
||
|
||
## The Bot Orchestrator
|
||
|
||
The `Bot` class is the top-level orchestrator that brings everything together.
|
||
It manages the MT5 terminal connection, initializes strategies, and coordinates
|
||
their execution.
|
||
|
||
### Basic Usage
|
||
|
||
```python
|
||
from aiomql import Bot, ForexSymbol
|
||
from my_strategies import EMAXOver, RSIStrategy
|
||
|
||
def main():
|
||
# Create symbols
|
||
symbols = [ForexSymbol(name=s) for s in ["EURUSD", "GBPUSD", "USDJPY"]]
|
||
|
||
# Create strategies
|
||
strategies = [EMAXOver(symbol=sym) for sym in symbols]
|
||
|
||
# Create bot and add strategies
|
||
bot = Bot()
|
||
bot.add_strategies(strategies)
|
||
|
||
# Run (blocks until shutdown)
|
||
bot.execute()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
### Adding Coroutines
|
||
|
||
You can add background coroutines that run alongside your strategies:
|
||
|
||
```python
|
||
async def log_account_status():
|
||
"""Periodically log account balances."""
|
||
from aiomql import Account
|
||
account = Account()
|
||
while True:
|
||
await account.refresh()
|
||
print(f"Balance: {account.balance}, Equity: {account.equity}")
|
||
await asyncio.sleep(60)
|
||
|
||
|
||
bot = Bot()
|
||
bot.add_strategies(strategies)
|
||
|
||
# Runs in the same event loop
|
||
bot.add_coroutine(coroutine=log_account_status)
|
||
|
||
# Runs on a separate thread (for CPU-intensive or blocking tasks)
|
||
bot.add_coroutine(
|
||
coroutine=some_blocking_task,
|
||
on_separate_thread=True
|
||
)
|
||
```
|
||
|
||
### Adding Functions
|
||
|
||
For synchronous functions that should run in a thread pool:
|
||
|
||
```python
|
||
import time
|
||
|
||
def heartbeat(interval=30):
|
||
while True:
|
||
print("Bot is alive...")
|
||
time.sleep(interval)
|
||
|
||
|
||
bot.add_function(function=heartbeat, interval=30)
|
||
```
|
||
|
||
### Async Entry Point
|
||
|
||
If you're already inside an async context, use `start()` instead of `execute()`:
|
||
|
||
```python
|
||
async def main():
|
||
bot = Bot()
|
||
bot.add_strategies(strategies)
|
||
await bot.start()
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
### What Happens Inside
|
||
|
||
When you call `bot.execute()`:
|
||
|
||
1. **Terminal Start** — connects to the MT5 terminal and logs in
|
||
2. **Strategy Initialization** — each strategy's `initialize()` is called;
|
||
strategies that fail are silently skipped
|
||
3. **Executor Start** — the `Executor` runs all strategies, coroutines, and
|
||
functions concurrently using asyncio tasks and thread pools
|
||
|
||
---
|
||
|
||
## Position Tracking
|
||
|
||
aiomql provides a sophisticated position tracking system through the `contrib.trackers`
|
||
package. This allows you to monitor open positions and apply automated management rules.
|
||
|
||
### OpenPosition
|
||
|
||
The `OpenPosition` class wraps a trade position with tracking, hedging, and stacking
|
||
capabilities:
|
||
|
||
```python
|
||
from aiomql import OpenPosition, PositionTracker
|
||
|
||
# Create an OpenPosition from a trade result
|
||
pos = OpenPosition(
|
||
ticket=123456,
|
||
symbol="EURUSD",
|
||
# ... other position details
|
||
)
|
||
|
||
# Modify stop-loss and take-profit
|
||
await pos.modify_stops(sl=1.0960, tp=1.1120)
|
||
|
||
# Close the position
|
||
success, result = await pos.close_position()
|
||
|
||
# Add custom trackers to the position
|
||
pos.add_tracker(tracker=my_tracker, name="trailing_stop")
|
||
```
|
||
|
||
### Position Tracking Functions
|
||
|
||
aiomql includes pre-built tracking functions:
|
||
|
||
- **`exit_at_profit`** — Close a position when profit reaches a target price
|
||
- **`extend_take_profit`** — Dynamically extend the take-profit as price moves favorably
|
||
|
||
### OpenPositionsTracker
|
||
|
||
The `OpenPositionsTracker` manages all open positions and runs their trackers
|
||
automatically:
|
||
|
||
```python
|
||
from aiomql import Bot, OpenPositionsTracker
|
||
|
||
bot = Bot()
|
||
bot.add_strategies(strategies)
|
||
|
||
# Track all open positions on a separate thread
|
||
bot.add_coroutine(
|
||
coroutine=OpenPositionsTracker(autocommit=True).track,
|
||
on_separate_thread=True
|
||
)
|
||
|
||
bot.execute()
|
||
```
|
||
|
||
This automatically:
|
||
- Discovers all open positions
|
||
- Runs any registered trackers on each position
|
||
- Handles position closure and cleanup
|
||
- Manages hedges, stacks, and pending orders
|
||
|
||
---
|
||
|
||
## Trade Result Recording
|
||
|
||
Every trade can be automatically recorded for analysis. The `Result` class supports
|
||
three storage formats:
|
||
|
||
### CSV
|
||
|
||
```python
|
||
# In your Config or aiomql.json
|
||
config = Config(trade_record_mode="csv")
|
||
|
||
# Trades are saved to: <root>/trade_records/<strategy_name>.csv
|
||
```
|
||
|
||
### JSON
|
||
|
||
```python
|
||
config = Config(trade_record_mode="json")
|
||
|
||
# Trades are saved to: <root>/trade_records/<strategy_name>.json
|
||
```
|
||
|
||
### SQLite
|
||
|
||
```python
|
||
config = Config(trade_record_mode="sql")
|
||
|
||
# Trades are saved to a SQLite database in your project root
|
||
```
|
||
|
||
### What Gets Recorded?
|
||
|
||
Each trade record includes:
|
||
- **Order result** — ticket, deal, volume, price, retcode
|
||
- **Strategy parameters** — all parameters from the strategy's `parameters` dict
|
||
- **Trade request** — the actual request sent to the broker
|
||
- **Extras** — timestamp, expected profit, strategy name
|
||
|
||
### Manual Recording
|
||
|
||
```python
|
||
from aiomql import Result
|
||
from aiomql.core.models import OrderSendResult
|
||
|
||
result = Result(
|
||
result=order_send_result,
|
||
parameters={"strategy": "EMA", "timeframe": "H1"},
|
||
name="EMAXOver",
|
||
expected_profit=25.0
|
||
)
|
||
|
||
await result.save(trade_record_mode="csv")
|
||
```
|
||
|
||
---
|
||
|
||
## Trade History
|
||
|
||
The `History` class lets you retrieve completed deals and orders from your account
|
||
history:
|
||
|
||
```python
|
||
from datetime import datetime, timedelta
|
||
from aiomql import History
|
||
|
||
# Get the last 7 days of history
|
||
history = History(
|
||
date_from=datetime.now() - timedelta(days=7),
|
||
date_to=datetime.now()
|
||
)
|
||
|
||
# Fetch deals and orders
|
||
await history.initialize()
|
||
|
||
# Access deals
|
||
print(f"Total deals: {history.total_deals}")
|
||
for deal in history.deals:
|
||
print(f" {deal.symbol}: {deal.profit}")
|
||
|
||
# Access orders
|
||
print(f"Total orders: {history.total_orders}")
|
||
|
||
# Filter by ticket
|
||
deals = history.filter_deals_by_ticket(ticket=123456)
|
||
|
||
# Filter by position ID
|
||
deals = history.filter_deals_by_position(position=789012)
|
||
|
||
# Filter by symbol group (e.g., only USD pairs)
|
||
history = History(
|
||
date_from=datetime.now() - timedelta(days=30),
|
||
date_to=datetime.now(),
|
||
group="*USD*"
|
||
)
|
||
```
|
||
|
||
---
|
||
|
||
## Contributed Extensions
|
||
|
||
The `contrib` package contains community extensions that build on the core framework.
|
||
|
||
### Strategies
|
||
|
||
#### Chaos
|
||
|
||
A demo strategy that randomly buys or sells. Useful for testing your infrastructure:
|
||
|
||
```python
|
||
from aiomql import Chaos, ForexSymbol
|
||
|
||
strategy = Chaos(symbol=ForexSymbol(name="EURUSD"))
|
||
```
|
||
|
||
### Symbols
|
||
|
||
#### ForexSymbol
|
||
|
||
Specialized `Symbol` subclass with pip calculations and volume computation based on
|
||
stop-loss distance. [See the Symbols section above for details.](#forexsymbol--specialized-for-forex)
|
||
|
||
### Traders
|
||
|
||
#### SimpleTrader
|
||
|
||
Trades with a stop-loss, calculating volume from risk.
|
||
[See above.](#simpletrader)
|
||
|
||
#### ScalpTrader
|
||
|
||
Trades with minimum volume and no stops.
|
||
[See above.](#scalptrader)
|
||
|
||
### Trackers
|
||
|
||
#### Tracker (Strategy Tracker)
|
||
|
||
A lightweight state holder for tracking strategy signals and snooze timers.
|
||
[See the Strategy section.](#the-tracker)
|
||
|
||
#### PositionTracker
|
||
|
||
Wraps a tracking function to execute on an open position.
|
||
|
||
#### OpenPositionsTracker
|
||
|
||
Automatically discovers and manages all open positions.
|
||
[See the Position Tracking section.](#openpositionstracker)
|
||
|
||
---
|
||
|
||
## Multi-Process Execution
|
||
|
||
For running completely independent bots in parallel, use `Bot.process_pool()`:
|
||
|
||
```python
|
||
from aiomql import Bot, ForexSymbol
|
||
from strategies import ForexStrategy, CryptoStrategy
|
||
|
||
|
||
def run_forex():
|
||
bot = Bot()
|
||
symbols = [ForexSymbol(name=s) for s in ["EURUSD", "GBPUSD"]]
|
||
strategies = [ForexStrategy(symbol=s) for s in symbols]
|
||
bot.add_strategies(strategies)
|
||
bot.execute()
|
||
|
||
|
||
def run_crypto():
|
||
bot = Bot()
|
||
symbols = [ForexSymbol(name=s) for s in ["BTCUSD", "ETHUSD"]]
|
||
strategies = [CryptoStrategy(symbol=s) for s in symbols]
|
||
bot.add_strategies(strategies)
|
||
bot.execute()
|
||
|
||
|
||
# Run both bots in separate processes
|
||
Bot.process_pool(
|
||
processes={
|
||
run_forex: {},
|
||
run_crypto: {}
|
||
},
|
||
num_workers=2
|
||
)
|
||
```
|
||
|
||
Each process gets its own event loop, MT5 connection, and strategy set. This is
|
||
useful when you want complete isolation between different trading systems.
|
||
|
||
---
|
||
|
||
## Synchronous API
|
||
|
||
Every async class in aiomql has a synchronous counterpart. This makes the library
|
||
usable in Jupyter notebooks, simple scripts, or anywhere you don't want to deal with
|
||
`asyncio`.
|
||
|
||
### Synchronous MetaTrader
|
||
|
||
```python
|
||
from aiomql.core.sync import MetaTrader as SyncMetaTrader
|
||
|
||
mt5 = SyncMetaTrader()
|
||
mt5.initialize_sync()
|
||
mt5.login_sync(login=12345678, password="your_password", server="YourBroker-Demo")
|
||
|
||
account = mt5._account_info()
|
||
print(f"Balance: {account.balance}")
|
||
|
||
mt5.shutdown()
|
||
```
|
||
|
||
### Synchronous Strategy Initialization
|
||
|
||
```python
|
||
# The Bot class handles sync initialization internally
|
||
bot = Bot()
|
||
bot.execute() # Uses sync initialization under the hood
|
||
```
|
||
|
||
### Synchronous RAM
|
||
|
||
```python
|
||
ram = RAM(risk=2)
|
||
amount = ram.get_amount_sync()
|
||
can_trade = ram.check_open_positions_sync()
|
||
```
|
||
|
||
---
|
||
|
||
## Full Example: EMA Crossover Bot
|
||
|
||
Here's a complete, production-ready example that puts everything together:
|
||
|
||
### Project Structure
|
||
|
||
```
|
||
my_trading_bot/
|
||
├── aiomql.json
|
||
├── strategies/
|
||
│ ├── __init__.py
|
||
│ └── ema_crossover.py
|
||
└── bot.py
|
||
```
|
||
|
||
### aiomql.json
|
||
|
||
```json
|
||
{
|
||
"login": 12345678,
|
||
"password": "your_password",
|
||
"server": "YourBroker-Demo",
|
||
"trade_record_mode": "csv"
|
||
}
|
||
```
|
||
|
||
### strategies/ema_crossover.py
|
||
|
||
```python
|
||
from aiomql import (
|
||
Strategy, ForexSymbol, TimeFrame, Tracker,
|
||
OrderType, Sessions, ScalpTrader
|
||
)
|
||
|
||
|
||
class EMAXOver(Strategy):
|
||
"""EMA Crossover strategy.
|
||
|
||
Buys when the fast EMA crosses above the slow EMA.
|
||
Sells when the fast EMA crosses below the slow EMA.
|
||
"""
|
||
|
||
ttf: TimeFrame
|
||
tcc: int
|
||
fast_ema: int
|
||
slow_ema: int
|
||
tracker: Tracker
|
||
interval: TimeFrame
|
||
timeout: int
|
||
|
||
parameters = {
|
||
"ttf": TimeFrame.H1,
|
||
"tcc": 3000,
|
||
"fast_ema": 34,
|
||
"slow_ema": 55,
|
||
"interval": TimeFrame.M15,
|
||
"timeout": 3 * 60 * 60, # 3 hours cooldown after a trade
|
||
}
|
||
|
||
def __init__(self, *, symbol: ForexSymbol, params: dict = None,
|
||
sessions: Sessions = None, name: str = "EMAXOver"):
|
||
super().__init__(
|
||
symbol=symbol, params=params,
|
||
sessions=sessions, name=name
|
||
)
|
||
self.tracker = Tracker(snooze=self.interval.seconds)
|
||
self.trader = ScalpTrader(symbol=self.symbol)
|
||
|
||
async def find_entry(self):
|
||
# Fetch candle data
|
||
candles = await self.symbol.copy_rates_from_pos(
|
||
timeframe=self.ttf, count=self.tcc
|
||
)
|
||
|
||
# Calculate EMAs
|
||
candles.ta.ema(length=self.fast_ema, append=True)
|
||
candles.ta.ema(length=self.slow_ema, append=True)
|
||
candles.rename(
|
||
**{f"EMA_{self.fast_ema}": "fast_ema",
|
||
f"EMA_{self.slow_ema}": "slow_ema"},
|
||
inplace=True,
|
||
)
|
||
|
||
# Check for crossover signals
|
||
fast_above_slow = candles.ta_lib.above(
|
||
candles.fast_ema, candles.slow_ema
|
||
)
|
||
fast_below_slow = candles.ta_lib.below(
|
||
candles.fast_ema, candles.slow_ema
|
||
)
|
||
|
||
if fast_above_slow.iloc[-1]:
|
||
self.tracker.update(
|
||
order_type=OrderType.BUY,
|
||
snooze=self.timeout
|
||
)
|
||
elif fast_below_slow.iloc[-1]:
|
||
self.tracker.update(
|
||
order_type=OrderType.SELL,
|
||
snooze=self.timeout
|
||
)
|
||
else:
|
||
self.tracker.update(
|
||
order_type=None,
|
||
snooze=self.interval.seconds
|
||
)
|
||
|
||
async def trade(self):
|
||
await self.find_entry()
|
||
|
||
if self.tracker.order_type is None:
|
||
# No signal — sleep until next bar
|
||
await self.sleep(secs=self.tracker.snooze)
|
||
else:
|
||
# Signal found — place trade and cooldown
|
||
await self.trader.place_trade(
|
||
order_type=self.tracker.order_type,
|
||
parameters=self.parameters,
|
||
)
|
||
await self.delay(secs=self.tracker.snooze)
|
||
```
|
||
|
||
### bot.py
|
||
|
||
```python
|
||
import logging
|
||
from aiomql import Bot, ForexSymbol, OpenPositionsTracker
|
||
from strategies.ema_crossover import EMAXOver
|
||
|
||
logging.basicConfig(level=logging.INFO)
|
||
|
||
|
||
def main():
|
||
# Define symbols to trade
|
||
symbols = [
|
||
ForexSymbol(name=s)
|
||
for s in ["EURUSD", "GBPUSD", "USDJPY"]
|
||
]
|
||
|
||
# Create a strategy instance for each symbol
|
||
strategies = [EMAXOver(symbol=sym) for sym in symbols]
|
||
|
||
# Create the bot
|
||
bot = Bot()
|
||
bot.add_strategies(strategies)
|
||
|
||
# Track open positions on a separate thread
|
||
bot.add_coroutine(
|
||
coroutine=OpenPositionsTracker(autocommit=True).track,
|
||
on_separate_thread=True
|
||
)
|
||
|
||
# Start trading
|
||
bot.execute()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────┐
|
||
│ Bot (Orchestrator) │
|
||
│ ┌──────────────────────────────────────────────┐ │
|
||
│ │ Executor │ │
|
||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────────────┐│ │
|
||
│ │ │Strategy 1│ │Strategy 2│ │ Coroutines/ ││ │
|
||
│ │ │(EURUSD) │ │(GBPUSD) │ │ Functions ││ │
|
||
│ │ └────┬────┘ └────┬────┘ └────────┬────────┘│ │
|
||
│ └───────┼───────────┼───────────────┼──────────┘ │
|
||
│ │ │ │ │
|
||
│ ┌───────▼───────────▼───────────────▼──────────┐ │
|
||
│ │ Shared Services │ │
|
||
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐│ │
|
||
│ │ │ Config │ │MetaTrader│ │ RAM │ │ Result ││ │
|
||
│ │ │(single)│ │(single) │ │ │ │ ││ │
|
||
│ │ └────────┘ └────────┘ └────────┘ └────────┘│ │
|
||
│ └──────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────┐
|
||
│ MetaTrader 5 │
|
||
│ Terminal │
|
||
│ (asyncio.to_thread)│
|
||
└─────────────────────┘
|
||
```
|
||
|
||
### Design Principles
|
||
|
||
1. **Singletons** — `Config` and `MetaTrader` in some cases are singletons, ensuring consistent
|
||
state across all components.
|
||
2. **Async-first** — Every MT5 call is wrapped with `asyncio.to_thread`, keeping
|
||
the event loop responsive.
|
||
3. **Composition** — Components are designed to be composed. A `Strategy` uses a
|
||
`Symbol`, `Trader`, `RAM`, and `Sessions`.
|
||
4. **Separation of concerns** — Market data (`Symbol`, `Candles`, `Ticks`), trade
|
||
execution (`Order`, `Trader`), risk management (`RAM`), and orchestration (`Bot`,
|
||
`Executor`) are all separate modules.
|
||
|
||
---
|
||
|
||
## Summary
|
||
|
||
| Feature | Module | Description |
|
||
|---|---|---|
|
||
| MT5 Connection | `MetaTrader` | Async MT5 interface with retry |
|
||
| Configuration | `Config` | Singleton JSON/programmatic config |
|
||
| Market Data | `Symbol`, `Candles`, `Ticks` | Symbols, OHLC bars, tick data |
|
||
| Technical Analysis | `ta_libs` | pandas-ta + optional TA-Lib |
|
||
| Orders | `Order` | Check, send, margin/profit calc |
|
||
| Trade Execution | `Trader`, `SimpleTrader`, `ScalpTrader` | High-level trade placement |
|
||
| Risk Management | `RAM` | Position sizing, trade limits |
|
||
| Strategy | `Strategy` | Base class for trading logic |
|
||
| Sessions | `Session`, `Sessions` | Time-window trading restrictions |
|
||
| Orchestration | `Bot`, `Executor` | Multi-strategy concurrent execution |
|
||
| Position Tracking | `OpenPositionsTracker`, `OpenPosition` | Trailing stops, hedging, stacking |
|
||
| Trade Recording | `Result` | CSV, JSON, SQLite persistence |
|
||
| History | `History` | Deal and order history queries |
|
||
| Forex Helpers | `ForexSymbol` | Pip calc, volume from SL |
|
||
| Positions | `Positions` | Open position management |
|
||
| Multi-Process | `Bot.process_pool()` | Run bots in parallel processes |
|
||
| Sync API | `core.sync`, `lib.sync` | Synchronous mirrors |
|
||
|
||
aiomql takes care of the infrastructure, the connection handling, the execution
|
||
loops, and the bookkeeping — so you can focus on what matters: **your trading
|
||
strategy**.
|
||
|
||
---
|
||
|
||
*For the full API reference, see the [documentation](docs/toc.md).*
|
||
|
||
*aiomql is MIT-licensed and maintained by [Ichinga Samuel](https://github.com/Ichinga-Samuel/aiomql).*
|
||
|
||
---
|
||
|
||
### Support This Project
|
||
|
||
If you found this tutorial helpful, consider supporting the development of aiomql:
|
||
|
||
- [❤️ Sponsor on GitHub](https://github.com/sponsors/Ichinga-Samuel)
|
||
- [☕ Buy Me a Coffee](https://buymeacoffee.com/ichingasamuel)
|