This library contains a series of tools to create a trading bot for Forex trading. It uses the DWX Connect (modified) to send commands and receive information of MetaTrader. This library is created based on a linux installation of MetaTrader.
pip install tradeopoetry add tradeoOr you can add manually in pyproject.toml file if you want download it from a specific branch:
tradeo = { git = "git@github.com:sorul/tradeo.git", branch = "develop" }Tradeo is built around three small pieces:
- A strategy decides whether a candle snapshot should become an order. See basic_strategy.py.
- An event handler receives MetaTrader responses and calls your strategy. See basic_event_handler.py.
- An executable wires everything together: starts the client, asks MetaTrader for data, handles open trades and waits for new candles. See basic_forex.py.
The main object is MT_Client. It talks to MetaTrader through files written in
the MQL5/Files/AgentFiles folder. You can let it poll those files in
background threads, or you can request data and wait for it explicitly.
Note
The configuration of Metatrader is necessary for the functioning of Tradeo. There is an example of both the configuration and the use of the library in a real project: sorul_tradingbot
- tradeo.utils.logger module: It contains a logger that can be used in your project. It can be configured to log in a file, in the console or in a syslog server. It also has the possibility of sending logs to a Telegram chat.
- tradeo.trading_methods module: It contains some trading methods that can be used in your strategies, such as calculating the pivots, calculating POH, VAL, VAH, calculating the EMA, RSI, etc.
MT_Client.start() can launch small background pollers. Each poller watches one
MetaTrader file and updates the in-memory client state.
For interval-based bots, the clearest setup is usually:
mt_client = MT_Client(
event_handler=BasicEventHandler(),
pollers={
'messages': True,
'market_data': True,
'bar_data': False,
'open_orders': True,
'historical_data': False,
'historical_trades': False,
},
)That means:
| Poller | Reads | Most useful for |
|---|---|---|
messages |
Messages.json |
Almost every live bot. It keeps MetaTrader errors and info messages available so the bot can report failed commands, wrong order formats or broker-side rejections. |
market_data |
Market_Data.json |
Bots that need current bid/ask while managing orders: break-even logic, trailing stops, pending-order type detection, spread checks, or lot/risk calculations. Enable it after subscribe_symbols(...). |
bar_data |
Bar_Data.json |
Always-on bots that want live candle updates from subscribe_symbols_bar_data(...), for example a dashboard or a process that reacts as soon as a new M1/M5 bar is published. Leave it off for cron-style bots that request candles explicitly. |
open_orders |
Orders.json |
Bots that manage existing positions or account state: closing unknown orders, moving stop loss, reading balance/equity, avoiding duplicate open orders, or reporting current exposure. |
historical_data |
Historical_Data_<symbol>.json |
Long-running, event-driven bots that send get_historical_data(...) requests and want on_historical_data(...) to fire in the background. For interval-based bots, request_historical_data(...) plus wait_historical_data(...) is usually clearer. |
historical_trades |
Historical_Trades.json |
Always-on bots that need closed trades refreshed periodically, for example daily PnL tracking or duplicate-trade prevention across the whole session. For interval-based bots, ensure_historical_trades_current(...) before making decisions is usually enough. |
If pollers is not provided, Tradeo reads the TB_CHECK_*_THREAD environment
variables instead. This is useful when you prefer to configure the runtime from
a .env file rather than making the Python call explicit.
For a bot that runs every few minutes, prefer this flow:
mt_client.start()
mt_client.request_historical_data(Config.symbols, Config.timeframe)
mt_client.subscribe_symbols(Config.symbols)
# handle existing trades first
orders = mt_client.check_open_orders()
# then process the candles requested above
remaining_symbols = mt_client.wait_historical_data(
Config.symbols,
timeout_seconds=240,
)wait_historical_data(...) calls check_historical_data(...) internally, so
your event_handler.on_historical_data(...) still receives the candle snapshot.
The example executable basic_forex.py uses
this style.
For critical workflows where stale closed-trade history could cause a wrong decision, such as opening a duplicate order after a position has just closed, use:
mt_client.ensure_historical_trades_current(
timeout_seconds=5,
max_age_seconds=120,
lookback_days=2,
)This helper loads a recent Historical_Trades.json snapshot and, when the file
is missing or stale, sends GET_HISTORICAL_TRADES once before waiting for a
fresh file.
This is intentionally different from enabling the historical_trades poller:
- Use
historical_trades=Truewhen your application is always running and you want closed trades to refresh periodically in the background. - Use
historical_trades=Falseplusensure_historical_trades_current(...)when your bot runs by intervals and only needs a fresh snapshot before making a decision.
Using both is usually redundant for interval-based bots. It can make sense for an always-on application that wants background refreshes most of the time, but still needs to force a freshness check before a critical decision.
You usually need environment variables for timezones, symbols, MetaTrader paths
and logging. Pollers can also be configured from the environment when you do not
want to pass pollers={...} in Python.
# Timezone configuration
export TB_LOCAL_TIMEZONE=Europe/Madrid
export TB_BROKER_TIMEZONE=Etc/GMT-2
# Trading configuration
export TB_SYMBOLS=EURUSD,USDCAD,USDCHF
export TB_ACCOUNT_CURRENCY=EUR
export TB_TIMEFRAME=M5
export TB_LOOKBACK_DAYS=10
# Optional Forex-Client poller configuration
export TB_CHECK_MESSAGES_THREAD=true
export TB_CHECK_MARKET_DATA_THREAD=true
export TB_CHECK_BAR_DATA_THREAD=false
export TB_CHECK_OPEN_ORDERS_THREAD=true
export TB_CHECK_HISTORICAL_DATA_THREAD=false
export TB_CHECK_HISTORICAL_TRADES_THREAD=false
# Metatrader configuration
export TB_WINE_HOME="${HOME}/.wine"
export TB_MT_FILES_PATH="${TB_WINE_HOME}/drive_c/.../MQL5/Files"
# Logging configuration
export TB_ACTIVATE_SYSLOG=false
export TB_LOG_LEVEL=INFO
export TB_SYSLOG_ADDRESS=logs2.papertrailapp.com
export TB_SYSLOG_PORT=12345
# Telegram configuration
export TB_ACTIVATE_TELEGRAM=false
export TB_TG_LOG_LEVEL=INFO
export TB_TG_FOREX_TOKEN=0000000000:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
export TB_TG_FOREX_CHAT_ID=-999999999The different possibilities for exporting environment variables depend on the user's preference. For example, we can place all the variables in a ".env" file and then execute the command using a Makefile and poetry:
Makefile
run_forex:
source .env && ~/.local/bin/poetry run run_forexEdit the crontab (crontab -e):
@reboot cd <path_to_your_project> && make start_metatrader
*/5 * * * 0-5 cd <path_to_your_project> && make run_forex