2026-02-28 18:16:28 +01:00
# 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).*
2026-02-28 22:22:30 +01:00
---
### 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 )