Author:
Rembrant Oyangoren Albeos
ORCID
License
Repo Size
Last Commit
Rust
MQL5
Title:
SUM3API: Using Rust, ZeroMQ, and MetaQuotes
Language (MQL5) API Combination to Extract,
Communicate, and Externally Project Financial
Data from MetaTrader 5 (MT5)
Abstract :
MetaTrader 5 (MT5), when connected to preferred exchanges or brokers, supports automated algorithmic trading via Expert Advisors (EAs) written in MetaQuotes Language (MQL5). While MetaQuotes Ltd. provides an official Python integration package, publicly documented methods for internally extracting and externally projecting MT5 financial data remain limited. To address this gap, we implemented a novel approach that bridges MQL5 and Rust via ZeroMQ publisher– subscriber and request–reply bindings. This benchmark-based methodology enables quantitative researchers, feature engineers, and algorithmic traders to develop trading systems leveraging MT5 data feeds using Rust, thereby bypassing the limitations inherent to pure MQL5 Expert Advisors. The methodology was validated through integration within a functional trad- ing terminal application demonstrating low-latency capabilities including: real-time account information monitoring (balance, equity, free and used margin), downloadable historical data requests (OHLC bars and raw tick data), downloadable forward data streaming (live tick recording), trade execution controls (market, limit, and stop orders with lot sizing and cancellation), messaging and notifications for debugging & recent calls, and a live microsecond-resolution raw tick-level bid/ask price formation chart.
Keywords:
MetaTrader 5, ZeroMQ, Rust, MetaQuotes Language 5, algorithmic trading, inter-process communication, financial data extraction, low-latency systems
Note: My accounts got banned and I'm lazy to rewrite shits. But anyway, you may read the documentation/research paper of SUM3API
here . Stay safe.
Simple SUM3API System Framework
image
SUM3API Framework
Using Rust (programming language), ZeroMQ (networking library), and MQL5 (programming language) API combination to conduct Algorithmic Trading and Programmatically Communicate (ATPC) to MetaTrader 5 (MT5).
To prove this framework, a simple Trading Terminal Software (TTS) is made that only includes fundamental features to make a fully functioning trading terminal.
SUM3API TTS features:
SUM3API doesn't need credentials to conduct ATPC on MT5. Unlike python where we need to specify sensitive variables in our code, such as account ID, password, and server.
Fetch account information: Balance, Equity, free & used margin. Without a credential specification/initialization.
Live milli/micro second raw bid/ask formation.
Historical data request TOCHLV (time, open, close, high, low, volume) and tick-level data request.
Live-data recording to download ongoing data streams.
Trade controls: min-max Lotsizing, Buy & Sell positions (market, limit, and stoporders)
Message logs where all actions are audited (active positions, pending orders)
A proof of the SUM3API System Framework demonstration through a Software (Trading Terminal)
622566837_1124349333017733_1244258009559375965_n (1)
This is what it looked like from the perspective of a ‘stress-Following-this-GUIDE-tester.’ Following this GUIDE from the very start to the finish.
image
The following pages will cover Complete End-to-End System Architecture , MQL5-ZMQ Wrapper library , and RUST-ZMQ Wrapper library for the SUM3API System.
The Complete End-to-End System Architecture: MQL5 ↔ ZeroMQ ↔ Rust for SUM3API
Version : 2.0.0
Last Updated : 2026-01-28
Purpose : Comprehensive technical documentation covering all micro-level implementation details
Table of Contents
System Overview
Complete Architecture Diagram
Security Architecture
Component Deep Dive
Data Flow & Communication Patterns
Account Information Fetching
Complete Data Structures
ZeroMQ Layer Details
Async Task Management
File Structure & Dependencies
System Overview
This system implements a secure, real-time bidirectional trading bridge between MetaTrader 5 and a Rust-based GUI application using ZeroMQ as the transport layer.
Core Design Principles
Security First : No credentials in code or transmitted over network
Real-time Performance : Tick-level granularity with minimal latency
Separation of Concerns : Authentication vs. Trading logic
Async Architecture : Non-blocking I/O for maximum throughput
Type Safety : Strong typing in both MQL5 and Rust
Complete Architecture Diagram
High-Level System Architecture
1 flowchart TB
2 subgraph USER_SPACE ["User Space"]
3 USER [("User")]
4 end
5
6 subgraph MT5_PLATFORM ["MetaTrader 5 Platform (Authenticated Process)"]
7 direction TB
8
9 subgraph AUTH ["Authentication Layer"]
10 MT5_GUI [MT5 Terminal GUI]
11 SESSION ["Authenticated Session<br/>[+] Account ID<br/>[+] Server Connection<br/>[+] Trading Permissions"]
12 end
13
14 subgraph DATA_SOURCES ["MT5 Data Sources"]
15 direction TB
16 MARKET [("Market Data Feed<br/>Tick Stream")]
17 ACCOUNT_DB [("Account Database<br/>ACCOUNT_BALANCE<br/>ACCOUNT_EQUITY<br/>ACCOUNT_MARGIN<br/>ACCOUNT_MARGIN_FREE")]
18 POSITIONS_DB [("Positions Database<br/>Active Trades")]
19 ORDERS_DB [("Orders Database<br/>Pending Orders")]
20 HISTORY_DB [("Historical Database<br/>OHLC & Tick Data")]
21 end
22
23 subgraph EA_LAYER ["Expert Advisor Layer"]
24 EA [ZmqPublisher.mq5<br/>Expert Advisor]
25 TRADE_ENGINE [CTrade Engine<br/>Order Execution]
26 end
27
28 USER --> |1. Manual Login<br/>account + password + server| MT5_GUI
29 MT5_GUI --> SESSION
30 SESSION -.-> |Inherits Session| EA
31
32 MARKET --> EA
33 ACCOUNT_DB --> EA
34 POSITIONS_DB --> EA
35 ORDERS_DB --> EA
36 HISTORY_DB --> EA
37 EA --> TRADE_ENGINE
38 end
39
40 subgraph ZMQ_LAYER ["ZeroMQ Transport Layer (localhost)"]
41 direction TB
42 PUB_SOCKET [["[PUB] PUB Socket<br/>tcp://0.0.0.0:5555<br/>Broadcast Mode"]]
43 REP_SOCKET [["[REP] REP Socket<br/>tcp://0.0.0.0:5556<br/>Request-Reply Mode"]]
44 end
45
46 subgraph RUST_APP ["Rust Application (mt5-chart)"]
47 direction TB
48
49 subgraph ASYNC_RUNTIME ["Tokio Async Runtime"]
50 TICK_TASK [["[Task] Tick Subscriber Task<br/>SubSocket<br/>Port 5555"]]
51 ORDER_TASK [["[Task] Order Handler Task<br/>ReqSocket<br/>Port 5556"]]
52 end
53
54 subgraph CHANNELS ["MPSC Channels"]
55 direction TB
56 TICK_CHAN [Tick Channel<br/>capacity: 100]
57 ORDER_CHAN [Order Request Channel<br/>capacity: 10]
58 RESPONSE_CHAN [Order Response Channel<br/>capacity: 10]
59 end
60
61 subgraph APP_STATE ["Application State"]
62 STATE [Mt5ChartApp<br/>• data: Vec<TickData><br/>• balance, equity, margin<br/>• positions, orders<br/>• UI state]
63 end
64
65 subgraph GUI ["egui GUI Components"]
66 direction TB
67 CHART [["[Chart] Price Chart<br/>Bid/Ask Lines<br/>Position Lines<br/>Order Breaklines"]]
68 ACCOUNT_PANEL [["[Account] Account Info Panel<br/>Balance, Equity<br/>Margin, Free Margin"]]
69 TRADE_PANEL [["[Trade] Trade Controls<br/>Market Orders<br/>Pending Orders"]]
70 HISTORY_PANEL [["[History] History Download<br/>OHLC/Tick CSV Export"]]
71 RECORD_PANEL [["[REC] Live Recording<br/>Real-time CSV Capture"]]
72 POSITIONS_PANEL [["[Pos] Active Positions<br/>Close Management"]]
73 ORDERS_PANEL [["[Orders] Pending Orders<br/>Cancel Management"]]
74 end
75
76 TICK_TASK --> TICK_CHAN
77 ORDER_TASK <--> ORDER_CHAN
78 ORDER_TASK <--> RESPONSE_CHAN
79
80 TICK_CHAN --> STATE
81 STATE <--> ORDER_CHAN
82 RESPONSE_CHAN --> STATE
83
84 STATE --> CHART
85 STATE --> ACCOUNT_PANEL
86 STATE --> TRADE_PANEL
87 STATE --> HISTORY_PANEL
88 STATE --> RECORD_PANEL
89 STATE --> POSITIONS_PANEL
90 STATE --> ORDERS_PANEL
91 end
92
93 EA --> PUB_SOCKET
94 EA <--> REP_SOCKET
95
96 PUB_SOCKET -.-> |JSON Tick Stream<br/>Non-blocking| TICK_TASK
97 ORDER_TASK -.-> |JSON Request<br/>Blocking| REP_SOCKET
98 REP_SOCKET -.-> |JSON Response<br/>Blocking| ORDER_TASK
99
100 style USER_SPACE fill : #f0f0f0 , stroke : #666 , stroke-width : 2px
101 style MT5_PLATFORM fill : #e6f3ff , stroke : #0066cc , stroke-width : 3px
102 style AUTH fill : #fff9e6 , stroke : #ffcc00 , stroke-width : 2px
103 style ZMQ_LAYER fill : #f0fff0 , stroke : #00cc00 , stroke-width : 3px
104 style RUST_APP fill : #ffe6f0 , stroke : #cc0066 , stroke-width : 3px
105 style SESSION fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
Security Architecture
Authentication Flow & Credential Isolation
1 sequenceDiagram
2 participant User
3 participant MT5_GUI as MT5 Terminal GUI
4 participant Broker as Broker Server
5 participant Session as Authenticated Session
6 participant EA as MQL5 Expert Advisor
7 participant ZMQ as ZeroMQ Sockets
8 participant Rust as Rust Application
9
10 rect rgb (255, 240, 200)
11 Note over User,Session : Phase 1 : One-Time Authentication (Manual)
12 User ->> MT5_GUI : Enter credentials<br/>• Account ID : 12345678<br/>• Password : ********<br/>• Server : MetaQuotes-Demo
13 MT5_GUI ->> Broker : Authenticate
14 Broker -->> MT5_GUI : [+] Authentication Success
15 MT5_GUI ->> Session : Create Authenticated Session
16 Note over Session : Session stores : <br/> [+] Account credentials<br/> [+] Server connection<br/> [+] Trading permissions<br/> [+] Account state
17 end
18
19 rect rgb (230, 255, 230)
20 Note over Session,EA : Phase 2 : EA Initialization (Session Inheritance)
21 User ->> MT5_GUI : Attach EA to chart
22 MT5_GUI ->> EA : OnInit ( )
23 EA ->> Session : Request session access
24 Session -->> EA : [+] Grant access (no credentials needed)
25 Note over EA : EA now has : <br/> [+] Authenticated session<br/> [+] Account info access<br/> [+] Trading permissions<br/> [-] NO credentials stored
26 end
27
28 rect rgb (230, 240, 255)
29 Note over EA,Rust : Phase 3 : External Communication (Credential-Free)
30 EA ->> ZMQ : Bind PUB socket (port 5555)
31 EA ->> ZMQ : Bind REP socket (port 5556)
32 Rust ->> ZMQ : Connect SUB socket (127.0.0.1:5555)
33 Rust ->> ZMQ : Connect REQ socket (127.0.0.1:5556)
34 Note over ZMQ,Rust : [+] Only localhost TCP addresses<br/> [-] NO credentials transmitted<br/> [-] NO authentication required
35 end
36
37 rect rgb (255, 230, 230)
38 Note over EA,Rust : Phase 4 : Runtime Operations (Secure)
39 loop Every Tick
40 EA ->> Session : AccountInfoDouble (ACCOUNT_BALANCE)
41 Session -->> EA : balance value
42 EA ->> Session : AccountInfoDouble (ACCOUNT_EQUITY)
43 Session -->> EA : equity value
44 EA ->> ZMQ : Publish JSON {balance, equity, ...}
45 ZMQ -->> Rust : Receive data (no auth needed)
46 end
47
48 Rust ->> ZMQ : Send order request {type: "market_buy", ...}
49 ZMQ -->> EA : Receive request
50 EA ->> Session : Execute trade via CTrade
51 Session -->> EA : Trade result
52 EA ->> ZMQ : Send response {success: true, ticket: ...}
53 ZMQ -->> Rust : Receive response
54 end
Security Comparison: MT5 Python API vs. MQL5+ZMQ+Rust
Security Aspect MT5 Python API MQL5 + ZeroMQ + Rust Credentials in Code Required (account, password, server) Not Required Credential Storage Must store in config/env vars No storage needed Credential Transmission Transmitted via Python API Never transmitted Authentication Method Programmatic (code-based) Manual (GUI-based) Session Model Python creates new session EA inherits existing session Attack Surface High (credentials exposed) Low (no credentials) Version Control Risk High (accidental commits) None Network Exposure Depends on configuration Localhost only (default) Credential Interception Possible during transmission Not applicable Separation of Concerns Mixed (auth + trading) Clear (auth separate)
Account Information Access Pattern
1 flowchart LR
2 subgraph MT5 ["MT5 Authenticated Session"]
3 ACC_API ["Account Info API<br/>AccountInfoDouble()"]
4 ACC_DATA [(Account Data<br/>ACCOUNT_BALANCE<br/>ACCOUNT_EQUITY<br/>ACCOUNT_MARGIN<br/>ACCOUNT_MARGIN_FREE)]
5 end
6
7 subgraph EA ["Expert Advisor"]
8 FETCH [Fetch Account Info<br/>Lines 366-369]
9 JSON_BUILD [Build JSON Payload<br/>Lines 428-443]
10 end
11
12 subgraph ZMQ ["ZeroMQ"]
13 PUB [PUB Socket<br/>Port 5555]
14 end
15
16 subgraph RUST ["Rust App"]
17 PARSE [Parse JSON<br/>Lines 745-753]
18 UPDATE [Update State<br/>Lines 338-348]
19 DISPLAY [Display in GUI<br/>Lines 449-466]
20 end
21
22 ACC_API --> ACC_DATA
23 ACC_DATA --> |No credentials needed| FETCH
24 FETCH --> JSON_BUILD
25 JSON_BUILD --> PUB
26 PUB -.-> |JSON over TCP| PARSE
27 PARSE --> UPDATE
28 UPDATE --> DISPLAY
29
30 style ACC_DATA fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
31 style FETCH fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
32 style PUB fill : #fff9e6 , stroke : #ffcc00 , stroke-width : 2px
33 style DISPLAY fill : #ffe6f0 , stroke : #cc0066 , stroke-width : 2px
Component Deep Dive
1. MQL5 Expert Advisor: ZmqPublisher.mq5
File Structure
Location : MQL5/Experts/ZmqPublisher.mq5
Lines : 451
Size : 19,014 bytes
Dependencies : Zmq.mqh, Trade.mqh
Input Parameters
1 input string InpPubAddress = "tcp://0.0.0.0:5555"; // Tick Publisher Address
2 input string InpRepAddress = "tcp://0.0.0.0:5556"; // Order Handler Address
3 input double InpDefaultSlippage = 10; // Default Slippage (points)
Global Variables
1 CZmq *g_publisher; // PUB socket for tick data broadcasting
2 CZmq *g_responder; // REP socket for order request handling
3 CTrade g_trade; // MT5 trading helper class
Initialization Sequence (OnInit)
1 flowchart TD
2 START ([OnInit Called]) --> INIT_PUB [Create CZmq Publisher]
3 INIT_PUB --> PUB_INIT {Init ZMQ_PUB?}
4 PUB_INIT --> |Failed| FAIL1 [Return INIT_FAILED]
5 PUB_INIT --> |Success| PUB_BIND {Bind to Port 5555?}
6 PUB_BIND --> |Failed| FAIL2 [Return INIT_FAILED]
7 PUB_BIND --> |Success| INIT_REP [Create CZmq Responder]
8
9 INIT_REP --> REP_INIT {Init ZMQ_REP?}
10 REP_INIT --> |Failed| FAIL3 [Return INIT_FAILED]
11 REP_INIT --> |Success| REP_BIND {Bind to Port 5556?}
12 REP_BIND --> |Failed| FAIL4 [Return INIT_FAILED]
13 REP_BIND --> |Success| CONFIG_TRADE [Configure CTrade]
14
15 CONFIG_TRADE --> SET_SLIP [SetDeviationInPoints]
16 SET_SLIP --> SET_FILL [SetTypeFilling IOC]
17 SET_FILL --> SUCCESS [Return INIT_SUCCEEDED]
18
19 style START fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
20 style SUCCESS fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
21 style FAIL1 fill : #ffcccc , stroke : #cc0000 , stroke-width : 2px
22 style FAIL2 fill : #ffcccc , stroke : #cc0000 , stroke-width : 2px
23 style FAIL3 fill : #ffcccc , stroke : #cc0000 , stroke-width : 2px
24 style FAIL4 fill : #ffcccc , stroke : #cc0000 , stroke-width : 2px
OnTick() Processing Flow
1 flowchart TB
2 TICK ([OnTick Event]) --> CHECK_REQ {Check REP Socket<br/>Non-blocking}
3
4 CHECK_REQ --> |Request Available| RECV_REQ [Receive Request JSON]
5 RECV_REQ --> PROCESS [ProcessOrderRequest]
6 PROCESS --> SEND_RESP [Send Response JSON<br/>Blocking]
7 SEND_RESP --> CHECK_PUB
8
9 CHECK_REQ --> |No Request| CHECK_PUB {Check Publisher}
10
11 CHECK_PUB --> |NULL| END ([Return])
12 CHECK_PUB --> |Valid| GET_TICK [SymbolInfoTick]
13
14 GET_TICK --> GET_ACCOUNT [Get Account Info<br/>Lines 366-369]
15 GET_ACCOUNT --> GET_CONSTRAINTS [Get Symbol Constraints<br/>Lines 372-374]
16 GET_CONSTRAINTS --> GET_POSITIONS [Get Active Positions<br/>Lines 377-397]
17 GET_POSITIONS --> GET_ORDERS [Get Pending Orders<br/>Lines 400-425]
18 GET_ORDERS --> BUILD_JSON [Build Complete JSON<br/>Lines 428-443]
19 BUILD_JSON --> PUBLISH [Publish to PUB Socket<br/>Line 445]
20 PUBLISH --> END
21
22 style TICK fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
23 style GET_ACCOUNT fill : #fff9e6 , stroke : #ffcc00 , stroke-width : 2px
24 style PUBLISH fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
Account Information Fetching (Detailed)
Lines 366-369: Account Info Retrieval
1 // Get account info
2 double balance = AccountInfoDouble(ACCOUNT_BALANCE);
3 double equity = AccountInfoDouble(ACCOUNT_EQUITY);
4 double margin = AccountInfoDouble(ACCOUNT_MARGIN);
5 double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
MQL5 Account Info Functions :
AccountInfoDouble(ACCOUNT_BALANCE) - Current account balance
AccountInfoDouble(ACCOUNT_EQUITY) - Current equity (balance + floating P/L)
AccountInfoDouble(ACCOUNT_MARGIN) - Margin currently used
AccountInfoDouble(ACCOUNT_MARGIN_FREE) - Free margin available
Security Note : These functions access the authenticated session's account data without requiring credentials . The EA inherits the session from the MT5 terminal.
Symbol Trading Constraints (Lines 372-374)
1 // Get symbol trading constraints
2 double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
3 double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
4 double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
Position Fetching Loop (Lines 377-397)
1 // Get Active Positions (Only for current symbol to simplify)
2 string positionsJson = "[";
3 int posCount = PositionsTotal();
4 bool firstPos = true;
5 for(int i = 0; i < posCount; i++) {
6 ulong ticket = PositionGetTicket(i);
7 if(PositionSelectByTicket(ticket)) {
8 if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
9 if(!firstPos) StringAdd(positionsJson, ",");
10
11 string posType = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? "BUY" : "SELL";
12 StringAdd(positionsJson, "{\"ticket\":" + IntegerToString(ticket) +
13 ",\"type\":\"" + posType + "\"" +
14 ",\"volume\":" + DoubleToString(PositionGetDouble(POSITION_VOLUME), 2) +
15 ",\"price\":" + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) +
16 ",\"profit\":" + DoubleToString(PositionGetDouble(POSITION_PROFIT), 2) +
17 "}");
18 firstPos = false;
19 }
20 }
21 }
22 StringAdd(positionsJson, "]");
Order Request Processing (Lines 87-188)
1 flowchart TD
2 START ([ProcessOrderRequest]) --> PARSE [Parse JSON Request<br/>Extract: type, symbol, volume, price, ticket]
3
4 PARSE --> ROUTE {Route by Type}
5
6 ROUTE --> |market_buy| MB [Get ASK price<br/>g_trade.Buy]
7 ROUTE --> |market_sell| MS [Get BID price<br/>g_trade.Sell]
8 ROUTE --> |limit_buy| LB [g_trade.BuyLimit]
9 ROUTE --> |limit_sell| LS [g_trade.SellLimit]
10 ROUTE --> |stop_buy| SB [g_trade.BuyStop]
11 ROUTE --> |stop_sell| SS [g_trade.SellStop]
12 ROUTE --> |close_position| CP [g_trade.PositionClose]
13 ROUTE --> |cancel_order| CO [g_trade.OrderDelete]
14 ROUTE --> |download_history| DH [DownloadHistory]
15 ROUTE --> |unknown| ERR [Unknown order type]
16
17 MB --> CHECK {Success?}
18 MS --> CHECK
19 LB --> CHECK
20 LS --> CHECK
21 SB --> CHECK
22 SS --> CHECK
23 CP --> CHECK
24 CO --> CHECK
25 DH --> CHECK
26 ERR --> BUILD_FAIL
27
28 CHECK --> |Yes| BUILD_SUCCESS ["Build Success JSON<br/>{success: true, ticket: ...}"]
29 CHECK --> |No| BUILD_FAIL ["Build Failure JSON<br/>{success: false, error: ...}"]
30
31 BUILD_SUCCESS --> RETURN [Return JSON Response]
32 BUILD_FAIL --> RETURN
33
34 style START fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
35 style BUILD_SUCCESS fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
36 style BUILD_FAIL fill : #ffcccc , stroke : #cc0000 , stroke-width : 2px
2. ZMQ Wrapper: Zmq.mqh
File Structure
Location : MQL5/Include/Zmq/Zmq.mqh
Lines : 145
Size : 4,100 bytes
Purpose : MQL5 wrapper around libzmq.dll
Class Structure
1 classDiagram
2 class CZmq {
3 -long m_context
4 -long m_socket
5 -bool m_initialized
6 +CZmq ( )
7 +~CZmq ( )
8 +bool Init (int type)
9 +bool Bind (string endpoint)
10 +bool Connect (string endpoint)
11 +int Send (string message, bool nonBlocking)
12 +string Receive (bool nonBlocking)
13 +void Shutdown ( )
14 }
15
16 class libzmq_dll {
17 <<external>>
18 +long zmq_ctx_new ( )
19 +int zmq_ctx_term (long context)
20 +long zmq_socket (long context, int type)
21 +int zmq_close (long socket)
22 +int zmq_bind ( long socket, uchar endpoint[] )
23 +int zmq_connect ( long socket, uchar endpoint[] )
24 +int zmq_send ( long socket, uchar buf[], int len, int flags )
25 +int zmq_recv ( long socket, uchar buf[], int len, int flags )
26 +int zmq_errno ( )
27 }
28
29 CZmq --> libzmq_dll : imports
Socket Type Constants
1 #define ZMQ_PUB 1 // Publisher socket (one-to-many)
2 #define ZMQ_SUB 2 // Subscriber socket (many-to-one)
3 #define ZMQ_REQ 3 // Request socket (synchronous client)
4 #define ZMQ_REP 4 // Reply socket (synchronous server)
5 #define ZMQ_NOBLOCK 1 // Non-blocking flag
Method Details
Init(int type) - Lines 51-68
1 bool Init(int type) {
2 if(m_initialized) return true;
3
4 m_context = zmq_ctx_new(); // Create ZMQ context
5 if(m_context == 0) {
6 Print("ZMQ Init failed: Context creation error");
7 return false;
8 }
9
10 m_socket = zmq_socket(m_context, type); // Create socket of specified type
11 if(m_socket == 0) {
12 Print("ZMQ Init failed: Socket creation error");
13 return false;
14 }
15
16 m_initialized = true;
17 return true;
18 }
Send(string message, bool nonBlocking) - Lines 98-114
1 int Send(string message, bool nonBlocking = true) {
2 if(!m_initialized) return -1;
3
4 uchar data[];
5 StringToCharArray(message, data, 0, WHOLE_ARRAY, CP_UTF8);
6 int len = ArraySize(data) - 1; // Exclude null terminator
7 if (len < 0) len = 0;
8
9 int flags = 0;
10 if(nonBlocking) flags = ZMQ_NOBLOCK;
11
12 int bytesSent = zmq_send(m_socket, data, len, flags);
13 return bytesSent;
14 }
Receive(bool nonBlocking) - Lines 117-131
1 string Receive(bool nonBlocking = true) {
2 if(!m_initialized) return "";
3
4 uchar buffer[4096];
5 ArrayInitialize(buffer, 0);
6
7 int flags = 0;
8 if(nonBlocking) flags = ZMQ_NOBLOCK;
9
10 int bytesReceived = zmq_recv(m_socket, buffer, ArraySize(buffer) - 1, flags);
11
12 if(bytesReceived <= 0) return "";
13
14 return CharArrayToString(buffer, 0, bytesReceived, CP_UTF8);
15 }
3. Rust Application: main.rs
File Structure
Location : Rustmt5-chart/src/main.rs
Lines : 853
Size : 35,504 bytes
Language : Rust 2021 Edition
Dependencies (Cargo.toml)
1 [ dependencies ]
2 eframe = "0.27" # egui framework
3 egui = "0.27" # Immediate mode GUI
4 egui_plot = "0.27" # Plotting library
5 serde = { version = "1.0" , features = [ "derive" ] }
6 serde_json = "1.0" # JSON serialization
7 tokio = { version = "1" , features = [ "full" ] }
8 zeromq = "0.3" # ZeroMQ bindings
9 chrono = "0.4" # Date/time handling
Data Structure Hierarchy
1 classDiagram
2 class TickData {
3 +String symbol
4 +f64 bid
5 +f64 ask
6 +i64 time
7 +u64 volume
8 +f64 balance
9 +f64 equity
10 +f64 margin
11 +f64 free_margin
12 +f64 min_lot
13 +f64 max_lot
14 +f64 lot_step
15 +Vec~PositionData~ positions
16 +Vec~PendingOrderData~ orders
17 }
18
19 class PositionData {
20 +u64 ticket
21 +String pos_type
22 +f64 volume
23 +f64 price
24 +f64 profit
25 }
26
27 class PendingOrderData {
28 +u64 ticket
29 +String order_type
30 +f64 volume
31 +f64 price
32 }
33
34 class OrderRequest {
35 +String order_type
36 +String symbol
37 +f64 volume
38 +f64 price
39 +u64 ticket
40 +Option~String~ timeframe
41 +Option~String~ start
42 +Option~String~ end
43 +Option~String~ mode
44 +Option~u64~ request_id
45 }
46
47 class OrderResponse {
48 +bool success
49 +Option~i64~ ticket
50 +Option~String~ error
51 +Option~String~ message
52 }
53
54 class OrderBreakline {
55 +usize index
56 +String order_type
57 +i64 ticket
58 }
59
60 class Mt5ChartApp {
61 +Receiver~TickData~ tick_receiver
62 +Vec~TickData~ data
63 +String symbol
64 +f64 balance
65 +f64 equity
66 +f64 margin
67 +f64 free_margin
68 +Sender~OrderRequest~ order_sender
69 +Receiver~OrderResponse~ response_receiver
70 +Vec~PositionData~ positions
71 +Vec~PendingOrderData~ pending_orders
72 +Vec~OrderBreakline~ order_breaklines
73 +update ( )
74 +send_order ( )
75 +send_download_request ( )
76 }
77
78 TickData "1" *-- "*" PositionData
79 TickData "1" *-- "*" PendingOrderData
80 Mt5ChartApp "1" *-- "*" TickData
81 Mt5ChartApp "1" *-- "*" OrderBreakline
Data Flow & Communication Patterns
Complete Tick Data Flow
1 sequenceDiagram
2 participant MT5 as MT5 Market
3 participant EA as ZmqPublisher.mq5
4 participant PUB as PUB Socket : 5555
5 participant SUB as SUB Socket (Rust)
6 participant CHAN as Tick Channel
7 participant APP as Mt5ChartApp
8 participant GUI as egui GUI
9
10 rect rgb (230, 255, 230)
11 Note over MT5,EA : Every Tick Event
12 MT5 ->> EA : OnTick ( )
13
14 EA ->> EA : SymbolInfoTick (_Symbol, tick)
15 EA ->> EA : AccountInfoDouble (ACCOUNT_BALANCE)
16 EA ->> EA : AccountInfoDouble (ACCOUNT_EQUITY)
17 EA ->> EA : AccountInfoDouble (ACCOUNT_MARGIN)
18 EA ->> EA : AccountInfoDouble (ACCOUNT_MARGIN_FREE)
19 EA ->> EA : SymbolInfoDouble (SYMBOL_VOLUME_MIN/MAX/STEP)
20
21 loop For each position
22 EA ->> EA : PositionGetTicket (i)
23 EA ->> EA : Build position JSON
24 end
25
26 loop For each order
27 EA ->> EA : OrderGetTicket (i)
28 EA ->> EA : Build order JSON
29 end
30
31 EA ->> EA : StringConcatenate (json, ...)
32 EA ->> PUB : Send (json, non-blocking)
33 end
34
35 rect rgb (230, 240, 255)
36 Note over PUB,APP : Async Rust Processing
37 PUB -->> SUB : TCP transmission
38 SUB ->> SUB : recv ( ) .await
39 SUB ->> SUB : serde_json : : from_str : : <TickData> ( )
40 SUB ->> CHAN : tick_tx.send (tick) .await
41
42 CHAN -->> APP : tick_receiver.try_recv ( )
43 APP ->> APP : Update balance, equity, margin
44 APP ->> APP : Update positions, orders
45 APP ->> APP : data.push (tick)
46 APP ->> APP : Record to CSV if recording
47 end
48
49 rect rgb (255, 240, 230)
50 Note over APP,GUI : GUI Update (60 FPS)
51 APP ->> GUI : update (&mut self, ctx, frame)
52 GUI ->> GUI : Draw price chart
53 GUI ->> GUI : Draw account panel
54 GUI ->> GUI : Draw positions/orders
55 GUI ->> GUI : ctx.request_repaint ( )
56 end
Complete Order Execution Flow
1 sequenceDiagram
2 participant GUI as egui GUI
3 participant APP as Mt5ChartApp
4 participant CHAN as Order Channel
5 participant REQ as REQ Socket (Rust)
6 participant REP as REP Socket : 5556
7 participant EA as ZmqPublisher.mq5
8 participant TRADE as CTrade Engine
9 participant MT5 as MT5 Terminal
10
11 rect rgb (255, 240, 230)
12 Note over GUI,APP : User Interaction
13 GUI ->> APP : Button clicked : "BUY"
14 APP ->> APP : send_order ("market_buy", None, None)
15 APP ->> APP : Build OrderRequest struct
16 APP ->> APP : serde_json : : to_string (&request)
17 APP ->> CHAN : order_sender.try_send (request)
18 end
19
20 rect rgb (230, 240, 255)
21 Note over CHAN,EA : Async Order Task
22 CHAN -->> REQ : order_rx.recv ( ) .await
23 REQ ->> REQ : Serialize to JSON
24 REQ ->> REP : socket.send (json) .await (blocking)
25
26 REP -->> EA : Receive (non-blocking) in OnTick
27 EA ->> EA : ProcessOrderRequest (request)
28 EA ->> EA : ExtractJsonString (request, "type")
29 EA ->> EA : ExtractJsonDouble (request, "volume")
30 end
31
32 rect rgb (230, 255, 230)
33 Note over EA,MT5 : Trade Execution
34 EA ->> EA : if (orderType == "market_buy")
35 EA ->> EA : askPrice = SymbolInfoDouble (SYMBOL_ASK)
36 EA ->> TRADE : g_trade.Buy (volume, symbol, askPrice, 0, 0, "Rust GUI Order")
37 TRADE ->> MT5 : Execute market order
38 MT5 -->> TRADE : Trade result
39 TRADE -->> EA : success = true, resultTicket = 12345678
40
41 EA ->> EA : Build response JSON
42 EA ->> EA : {"success":true,"ticket":12345678}
43 EA ->> REP : Send (response, blocking)
44 end
45
46 rect rgb (240, 230, 255)
47 Note over REP,APP : Response Processing
48 REP -->> REQ : socket.recv ( ) .await (blocking)
49 REQ ->> REQ : serde_json : : from_str : : <OrderResponse> ( )
50 REQ ->> CHAN : response_tx.send (response) .await
51
52 CHAN -->> APP : response_receiver.try_recv ( )
53 APP ->> APP : if response.success
54 APP ->> APP : Create OrderBreakline
55 APP ->> APP : order_breaklines.push (breakline)
56 APP ->> APP : last_order_result = "✓ Order executed!"
57 end
58
59 rect rgb (255, 240, 230)
60 Note over APP,GUI : GUI Feedback
61 APP ->> GUI : Update chart with breakline
62 GUI ->> GUI : Draw vertical line at execution point
63 GUI ->> GUI : Display success message
64 end
Account Information Fetching
MQL5 Account Info API
1 flowchart LR
2 subgraph MT5_SESSION ["MT5 Authenticated Session"]
3 AUTH [Authenticated User Session]
4 ACC_STATE [(Account State<br/>• Balance<br/>• Equity<br/>• Margin<br/>• Free Margin<br/>• Leverage<br/>• Currency)]
5 end
6
7 subgraph MQL5_API ["MQL5 Account API"]
8 API1 [AccountInfoDouble<br/>ACCOUNT_BALANCE]
9 API2 [AccountInfoDouble<br/>ACCOUNT_EQUITY]
10 API3 [AccountInfoDouble<br/>ACCOUNT_MARGIN]
11 API4 [AccountInfoDouble<br/>ACCOUNT_MARGIN_FREE]
12 end
13
14 subgraph EA_CODE ["Expert Advisor Code"]
15 FETCH ["Lines 366-369:<br/>double balance = AccountInfoDouble(ACCOUNT_BALANCE);<br/>double equity = AccountInfoDouble(ACCOUNT_EQUITY);<br/>double margin = AccountInfoDouble(ACCOUNT_MARGIN);<br/>double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);"]
16 end
17
18 AUTH --> ACC_STATE
19 ACC_STATE --> API1
20 ACC_STATE --> API2
21 ACC_STATE --> API3
22 ACC_STATE --> API4
23
24 API1 --> FETCH
25 API2 --> FETCH
26 API3 --> FETCH
27 API4 --> FETCH
28
29 style AUTH fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
30 style ACC_STATE fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
31 style FETCH fill : #fff9e6 , stroke : #ffcc00 , stroke-width : 2px
Account Info Constants (MQL5)
Constant Type Description ACCOUNT_BALANCEdouble Account balance in deposit currency ACCOUNT_EQUITYdouble Account equity (balance + floating P/L) ACCOUNT_MARGINdouble Margin currently used ACCOUNT_MARGIN_FREEdouble Free margin available for trading ACCOUNT_MARGIN_LEVELdouble Margin level percentage ACCOUNT_PROFITdouble Current profit on all positions ACCOUNT_CREDITdouble Credit amount ACCOUNT_LEVERAGElong Account leverage (e.g., 100 for 1:100) ACCOUNT_CURRENCYstring Account currency (e.g., "USD")
Rust Account Info Reception
Lines 338-348: Account Info Update
1 // Update account info from latest tick
2 if tick . balance > 0.0 {
3 self . balance = tick . balance ;
4 self . equity = tick . equity ;
5 self . margin = tick . margin ;
6 self . free_margin = tick . free_margin ;
7 self . min_lot = tick . min_lot ;
8 self . max_lot = tick . max_lot ;
9 if tick . lot_step > 0.0 {
10 self . lot_step = tick . lot_step ;
11 }
12 }
Lines 449-466: Account Info Display
1 ui . collapsing ( "Account Info" , | ui | {
2 egui :: Grid :: new ( "account_grid" )
3 . num_columns ( 2 )
4 . spacing ( [ 10.0 , 4.0 ] )
5 . show ( ui , | ui | {
6 ui . label ( "Balance:" ) ;
7 ui . colored_label ( egui :: Color32 :: from_rgb ( 100 , 200 , 100 ) , format! ( "${:.2}" , self . balance ) ) ;
8 ui . end_row ( ) ;
9 ui . label ( "Equity:" ) ;
10 ui . colored_label ( egui :: Color32 :: from_rgb ( 100 , 180 , 255 ) , format! ( "${:.2}" , self . equity ) ) ;
11 ui . end_row ( ) ;
12 ui . label ( "Margin Used:" ) ;
13 ui . colored_label ( egui :: Color32 :: from_rgb ( 255 , 200 , 100 ) , format! ( "${:.2}" , self . margin ) ) ;
14 ui . end_row ( ) ;
15 ui . label ( "Free Margin:" ) ;
16 ui . colored_label ( egui :: Color32 :: from_rgb ( 100 , 255 , 200 ) , format! ( "${:.2}" , self . free_margin ) ) ;
17 ui . end_row ( ) ;
18 } ) ;
19 } ) ;
Complete Data Structures
JSON Tick Data Format (PUB/SUB Port 5555)
1 {
2 "symbol" : "XAUUSDc" ,
3 "bid" : 2650.55 ,
4 "ask" : 2650.75 ,
5 "time" : 1706284800 ,
6 "volume" : 100 ,
7 "balance" : 10000.00 ,
8 "equity" : 10150.25 ,
9 "margin" : 500.00 ,
10 "free_margin" : 9650.25 ,
11 "min_lot" : 0.01 ,
12 "max_lot" : 100.00 ,
13 "lot_step" : 0.01 ,
14 "positions" : [
15 {
16 "ticket" : 12345678 ,
17 "type" : "BUY" ,
18 "volume" : 0.10 ,
19 "price" : 2645.50 ,
20 "profit" : 50.50
21 } ,
22 {
23 "ticket" : 12345679 ,
24 "type" : "SELL" ,
25 "volume" : 0.05 ,
26 "price" : 2655.00 ,
27 "profit" : -25.00
28 }
29 ] ,
30 "orders" : [
31 {
32 "ticket" : 87654321 ,
33 "type" : "BUY LIMIT" ,
34 "volume" : 0.05 ,
35 "price" : 2600.00
36 } ,
37 {
38 "ticket" : 87654322 ,
39 "type" : "SELL STOP" ,
40 "volume" : 0.10 ,
41 "price" : 2700.00
42 }
43 ]
44 }
JSON Order Request Format (REQ/REP Port 5556)
Market Order Request :
1 {
2 "type" : "market_buy" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.01 ,
5 "price" : 0.0 ,
6 "ticket" : 0
7 }
Pending Order Request :
1 {
2 "type" : "limit_buy" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.05 ,
5 "price" : 2600.00 ,
6 "ticket" : 0
7 }
Close Position Request :
1 {
2 "type" : "close_position" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.0 ,
5 "price" : 0.0 ,
6 "ticket" : 12345678
7 }
History Download Request :
1 {
2 "type" : "download_history" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.0 ,
5 "price" : 0.0 ,
6 "ticket" : 0 ,
7 "timeframe" : "M1" ,
8 "start" : "2024.01.01" ,
9 "end" : "2024.01.31" ,
10 "mode" : "OHLC" ,
11 "request_id" : 1
12 }
JSON Order Response Format
Success Response :
1 {
2 "success" : true ,
3 "ticket" : 12345678
4 }
Failure Response :
1 {
2 "success" : false ,
3 "error" : "Error 10019: Not enough money"
4 }
History Download Success Response :
1 {
2 "success" : true ,
3 "message" : "1000 records||CSV_DATA||Time,Open,High,Low,Close,TickVol,Spread|NL|2024.01.01 00:00,2650.50,2651.00,2650.00,2650.75,100,3|NL|..."
4 }
ZeroMQ Layer Details
Socket Patterns
1 flowchart TB
2 subgraph PUB_SUB ["PUB/SUB Pattern (Port 5555)"]
3 direction LR
4 PUB [Publisher<br/>ZmqPublisher.mq5]
5 SUB1 [Subscriber 1<br/>Rust App]
6 SUB2 [Subscriber 2<br/>Other Apps]
7
8 PUB --> |Broadcast| SUB1
9 PUB --> |Broadcast| SUB2
10 end
11
12 subgraph REQ_REP ["REQ/REP Pattern (Port 5556)"]
13 direction LR
14 REQ [Request<br/>Rust App]
15 REP [Reply<br/>ZmqPublisher.mq5]
16
17 REQ <--> |Synchronous| REP
18 end
19
20 style PUB fill : #ccffcc , stroke : #00cc00 , stroke-width : 2px
21 style REP fill : #ffe6cc , stroke : #ff9900 , stroke-width : 2px
Socket Configuration
PUB Socket (EA Side) :
1 g_publisher = new CZmq();
2 g_publisher.Init(ZMQ_PUB);
3 g_publisher.Bind("tcp://0.0.0.0:5555"); // Bind to all interfaces
4 g_publisher.Send(json, true); // Non-blocking send
SUB Socket (Rust Side) :
1 let mut socket = zeromq :: SubSocket :: new ( ) ;
2 socket . connect ( "tcp://127.0.0.1:5555" ) . await ; // Connect to localhost
3 socket . subscribe ( "" ) . await ; // Subscribe to all messages
4 let msg = socket . recv ( ) . await ; // Blocking receive
REP Socket (EA Side) :
1 g_responder = new CZmq();
2 g_responder.Init(ZMQ_REP);
3 g_responder.Bind("tcp://0.0.0.0:5556"); // Bind to all interfaces
4 string request = g_responder.Receive(true); // Non-blocking receive
5 g_responder.Send(response, false); // Blocking send (REP pattern)
REQ Socket (Rust Side) :
1 let mut socket = zeromq :: ReqSocket :: new ( ) ;
2 socket . connect ( "tcp://127.0.0.1:5556" ) . await ; // Connect to localhost
3 socket . send ( json_request . into ( ) ) . await ; // Blocking send
4 let msg = socket . recv ( ) . await ; // Blocking receive
Async Task Management
Tokio Runtime Architecture
1 flowchart TB
2 subgraph TOKIO ["Tokio Async Runtime"]
3 MAIN [tokio::main]
4
5 subgraph TASKS ["Spawned Tasks"]
6 TICK_TASK [Tick Subscriber Task<br/>Lines 731-763]
7 ORDER_TASK [Order Handler Task<br/>Lines 768-835]
8 end
9
10 subgraph CHANNELS ["MPSC Channels"]
11 TICK_CH [Tick Channel<br/>capacity: 100]
12 ORDER_CH [Order Channel<br/>capacity: 10]
13 RESP_CH [Response Channel<br/>capacity: 10]
14 end
15 end
16
17 subgraph EGUI ["eframe GUI (Blocking)"]
18 APP [Mt5ChartApp::update]
19 end
20
21 MAIN --> TICK_TASK
22 MAIN --> ORDER_TASK
23 MAIN --> EGUI
24
25 TICK_TASK --> TICK_CH
26 ORDER_TASK <--> ORDER_CH
27 ORDER_TASK <--> RESP_CH
28
29 TICK_CH --> APP
30 APP --> ORDER_CH
31 RESP_CH --> APP
32
33 style TOKIO fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
34 style EGUI fill : #ffe6f0 , stroke : #cc0066 , stroke-width : 2px
Tick Subscriber Task (Lines 731-763)
1 tokio :: spawn ( async move {
2 let mut socket = zeromq :: SubSocket :: new ( ) ;
3 match socket . connect ( "tcp://127.0.0.1:5555" ) . await {
4 Ok ( _ ) => println! ( "Connected to ZMQ Tick Publisher on port 5555" ) ,
5 Err ( e ) => eprintln! ( "Failed to connect to ZMQ tick publisher: {}" , e ) ,
6 }
7
8 let _ = socket . subscribe ( "" ) . await ;
9
10 loop {
11 match socket . recv ( ) . await {
12 Ok ( msg ) => {
13 if let Some ( payload_bytes ) = msg . get ( 0 ) {
14 if let Ok ( json_str ) = std :: str :: from_utf8 ( payload_bytes ) {
15 match serde_json :: from_str :: < TickData > ( json_str ) {
16 Ok ( tick ) => {
17 if let Err ( e ) = tick_tx . send ( tick ) . await {
18 eprintln! ( "Tick channel error: {}" , e ) ;
19 break ;
20 }
21 }
22 Err ( e ) => eprintln! ( "JSON Parse Error: {}. Msg: {}" , e , json_str ) ,
23 }
24 }
25 }
26 }
27 Err ( e ) => {
28 eprintln! ( "ZMQ Tick Recv Error: {}" , e ) ;
29 tokio :: time :: sleep ( tokio :: time :: Duration :: from_millis ( 1000 ) ) . await ;
30 }
31 }
32 }
33 } ) ;
Order Handler Task (Lines 768-835)
1 tokio :: spawn ( async move {
2 let mut socket = zeromq :: ReqSocket :: new ( ) ;
3 match socket . connect ( "tcp://127.0.0.1:5556" ) . await {
4 Ok ( _ ) => println! ( "Connected to ZMQ Order Handler on port 5556" ) ,
5 Err ( e ) => {
6 eprintln! ( "Failed to connect to ZMQ order handler: {}" , e ) ;
7 return ;
8 }
9 }
10
11 while let Some ( order_request ) = order_rx . recv ( ) . await {
12 // Serialize order request to JSON
13 let json_request = match serde_json :: to_string ( & order_request ) {
14 Ok ( json ) => json ,
15 Err ( e ) => {
16 eprintln! ( "Failed to serialize order request: {}" , e ) ;
17 continue ;
18 }
19 } ;
20
21 println! ( "Sending request: {}" , json_request ) ;
22
23 // Send request (blocking in REQ/REP pattern)
24 if let Err ( e ) = socket . send ( json_request . into ( ) ) . await {
25 eprintln! ( "Failed to send: {}" , e ) ;
26 let _ = response_tx . send ( OrderResponse {
27 success : false ,
28 ticket : None ,
29 error : Some ( format! ( "Send failed: {}" , e ) ) ,
30 message : None ,
31 } ) . await ;
32 continue ;
33 }
34
35 // Wait for response (blocking in REQ/REP pattern)
36 match socket . recv ( ) . await {
37 Ok ( msg ) => {
38 if let Some ( payload_bytes ) = msg . get ( 0 ) {
39 if let Ok ( json_str ) = std :: str :: from_utf8 ( payload_bytes ) {
40 println! ( "Received response: {}" , json_str ) ;
41 match serde_json :: from_str :: < OrderResponse > ( json_str ) {
42 Ok ( response ) => {
43 let _ = response_tx . send ( response ) . await ;
44 }
45 Err ( e ) => {
46 let _ = response_tx . send ( OrderResponse {
47 success : false ,
48 ticket : None ,
49 error : Some ( format! ( "Parse error: {}" , e ) ) ,
50 message : None ,
51 } ) . await ;
52 }
53 }
54 }
55 }
56 }
57 Err ( e ) => {
58 eprintln! ( "Response recv error: {}" , e ) ;
59 let _ = response_tx . send ( OrderResponse {
60 success : false ,
61 ticket : None ,
62 error : Some ( format! ( "Recv failed: {}" , e ) ) ,
63 message : None ,
64 } ) . await ;
65 }
66 }
67 }
68 } ) ;
File Structure & Dependencies
Complete Directory Structure
SUM3API/
├── MQL5/
│ ├── Experts/
│ │ └── ZmqPublisher.mq5 # Main EA (451 lines, 19 KB)
│ ├── Include/
│ │ └── Zmq/
│ │ └── Zmq.mqh # ZMQ wrapper (145 lines, 4 KB)
│ └── Libraries/
│ ├── libzmq.dll # ZeroMQ native library
│ └── libsodium.dll # Crypto library (ZMQ dependency)
│
└── Rustmt5-chart/
├── Cargo.toml # Rust dependencies
├── Cargo.lock # Dependency lock file (117 KB)
├── src/
│ └── main.rs # Main application (853 lines, 35 KB)
├── output/ # CSV output directory
│ ├── History_*.csv # Downloaded historical data
│ └── Live_*.csv # Live recorded tick data
└── target/ # Build artifacts
├── debug/ # Debug build
└── release/ # Release build
Dependency Graph
1 flowchart TB
2 subgraph MQL5_DEPS ["MQL5 Dependencies"]
3 EA [ZmqPublisher.mq5]
4 ZMQ_MQH [Zmq.mqh]
5 TRADE_MQH [Trade.mqh<br/>MT5 Built-in]
6 LIBZMQ [libzmq.dll]
7 LIBSODIUM [libsodium.dll]
8 end
9
10 subgraph RUST_DEPS ["Rust Dependencies"]
11 MAIN [main.rs]
12 EFRAME [eframe 0.27]
13 EGUI [egui 0.27]
14 EGUI_PLOT [egui_plot 0.27]
15 SERDE [serde 1.0]
16 SERDE_JSON [serde_json 1.0]
17 TOKIO [tokio 1.x]
18 ZEROMQ [zeromq 0.3]
19 CHRONO [chrono 0.4]
20 end
21
22 EA --> ZMQ_MQH
23 EA --> TRADE_MQH
24 ZMQ_MQH --> LIBZMQ
25 LIBZMQ --> LIBSODIUM
26
27 MAIN --> EFRAME
28 MAIN --> EGUI_PLOT
29 MAIN --> SERDE
30 MAIN --> SERDE_JSON
31 MAIN --> TOKIO
32 MAIN --> ZEROMQ
33 MAIN --> CHRONO
34 EFRAME --> EGUI
35
36 style EA fill : #e6f3ff , stroke : #0066cc , stroke-width : 2px
37 style MAIN fill : #ffe6f0 , stroke : #cc0066 , stroke-width : 2px
Summary
This document provides a complete end-to-end technical specification of the MQL5 ↔ ZeroMQ ↔ Rust trading system, including:
Security Architecture : Credential-free design with session inheritance
Account Information Flow : From MT5 API to Rust GUI
Complete Data Structures : JSON formats and Rust/MQL5 types
Communication Patterns : PUB/SUB and REQ/REP with sequence diagrams
Async Task Management : Tokio runtime and channel architecture
Micro-level Implementation : Line-by-line code references
File Structure : Complete dependency graph
Key Security Advantage : Unlike MT5's Python API which requires explicit credentials in code, this system leverages MT5's authenticated session, eliminating credential exposure entirely.
MQL5 ZeroMQ Wrapper Library
A comprehensive reusable MQL5 wrapper library for ZeroMQ socket operations, designed for real-time communication between MetaTrader 5 and external applications.
Table of Contents
Overview
Architecture
Prerequisites and Installation
API Reference
Usage Guide
Socket Patterns
Message Protocol
Complete Examples
Error Handling
Best Practices
Troubleshooting
Overview
This library provides a high-level MQL5 wrapper around the native ZeroMQ (libzmq) library, enabling MetaTrader 5 Expert Advisors and indicators to communicate with external applications via TCP sockets.
[!NOTE]
For the companion Rust client library, see
Rust-ZMQ Library for SUM3API .
Key Features
Simple API : Object-oriented wrapper class with intuitive methods
Multiple Socket Types : Support for PUB, SUB, REQ, and REP patterns
Non-blocking Operations : Configurable blocking/non-blocking send and receive
UTF-8 Support : Automatic string encoding/decoding
Resource Management : Automatic cleanup on destruction
Supported Socket Types
Constant Value Description ZMQ_PUB1 Publisher socket for broadcasting messages ZMQ_SUB2 Subscriber socket for receiving broadcasts ZMQ_REQ3 Request socket for request/reply pattern (client) ZMQ_REP4 Reply socket for request/reply pattern (server)
Architecture
System Overview
1 flowchart LR
2 subgraph MT5 ["MetaTrader 5"]
3 EA ["ZmqPublisher EA"]
4 CZmq ["CZmq Wrapper"]
5 DLL ["libzmq.dll"]
6 EA --> CZmq
7 CZmq --> DLL
8 end
9
10 subgraph Network ["ZeroMQ TCP/IP"]
11 PUB ["PUB Socket<br/>tcp://0.0.0.0:5555"]
12 REP ["REP Socket<br/>tcp://0.0.0.0:5556"]
13 end
14
15 subgraph Client ["External Client"]
16 SUB ["SUB Socket"]
17 REQ ["REQ Socket"]
18 APP ["Application<br/>(Rust/Go/Java/C++)"]
19 SUB --> APP
20 REQ --> APP
21 end
22
23 DLL --> PUB
24 DLL --> REP
25 PUB --> |"Tick Data (JSON)"| SUB
26 REQ <--> |"Order Request/Response"| REP
Communication Flow
1 sequenceDiagram
2 participant MT5 as MetaTrader 5
3 participant PUB as PUB Socket : 5555
4 participant SUB as SUB Socket
5 participant Client as External Client
6 participant REQ as REQ Socket
7 participant REP as REP Socket : 5556
8
9 Note over MT5,Client : Tick Data Publishing (PUB/SUB)
10 MT5 ->> PUB : OnTick ( ) - Create JSON
11 PUB ->> SUB : Broadcast tick data
12 SUB ->> Client : Parse and display
13
14 Note over MT5,Client : Order Handling (REQ/REP)
15 Client ->> REQ : Create order request
16 REQ ->> REP : Send JSON request
17 REP ->> MT5 : Receive and parse
18 MT5 ->> MT5 : Execute order
19 MT5 ->> REP : Create response
20 REP ->> REQ : Send JSON response
21 REQ ->> Client : Parse result
Pattern Details
Tick Data Publishing (PUB/SUB Pattern)
EA binds PUB socket to tcp://0.0.0.0:5555
External client subscribes via SUB socket
EA publishes tick data as JSON on every tick
Order Handling (REQ/REP Pattern)
EA binds REP socket to tcp://0.0.0.0:5556
External client sends order requests via REQ socket
EA processes orders and sends responses
Prerequisites and Installation
Required Files
Place the following files in your MetaTrader 5 installation directory:
MQL5/
|-- Libraries/
| |-- libzmq.dll # ZeroMQ core library
| |-- libsodium.dll # Cryptographic dependency for libzmq
|
|-- Include/
| |-- Zmq/
| |-- Zmq.mqh # MQL5 wrapper class
|
|-- Experts/
|-- ZmqPublisher.mq5 # Example Expert Advisor
Installation Steps
Download ZeroMQ Libraries
Download libzmq.dll (v4.3.x or later) from ZeroMQ releases
Download libsodium.dll from libsodium releases
Both DLLs must be the same architecture (x64 for 64-bit MT5)
Copy Files
Copy libzmq.dll --> MQL5/Libraries/
Copy libsodium.dll --> MQL5/Libraries/
Copy Zmq.mqh --> MQL5/Include/Zmq/
Enable DLL Imports in MetaTrader 5
Go to Tools > Options > Expert Advisors
Enable "Allow DLL imports"
Disable "Allow DLL imports only for signed DLLs" (or sign the DLLs)
Compile Your EA
Open MetaEditor
Include the wrapper: #include <Zmq/Zmq.mqh>
Compile your Expert Advisor
API Reference
Class: CZmq
The main wrapper class for ZeroMQ operations.
Constructor and Destructor
Creates a new CZmq instance. Does not initialize any ZMQ resources.
Destructor. Automatically calls Shutdown() to clean up resources.
Init
Initializes the ZeroMQ context and creates a socket of the specified type.
Parameters:
Name Type Description typeintSocket type: ZMQ_PUB, ZMQ_SUB, ZMQ_REQ, or ZMQ_REP
Returns:
true if initialization succeeded
false if context or socket creation failed
Example:
1 CZmq * publisher = new CZmq ( ) ;
2 if ( ! publisher . Init ( ZMQ_PUB ) ) {
3 Print ( "Failed to initialize ZMQ publisher" ) ;
4 return INIT_FAILED ;
5 }
Bind
bool Bind(string endpoint)
Binds the socket to a local endpoint. Typically used by server-side sockets (PUB, REP).
Parameters:
Name Type Description endpointstringZMQ endpoint URL (e.g., "tcp://0.0.0.0:5555")
Returns:
true if binding succeeded
false if binding failed (check logs for error code)
Endpoint Formats:
Format Description tcp://*:5555Bind to all interfaces on port 5555 tcp://0.0.0.0:5555Same as above tcp://127.0.0.1:5555Bind to localhost only ipc:///tmp/socketInter-process communication (Unix only)
Example:
1 if ( ! publisher . Bind ( "tcp://0.0.0.0:5555" ) ) {
2 Print ( "Failed to bind to port 5555" ) ;
3 return INIT_FAILED ;
4 }
Connect
bool Connect(string endpoint)
Connects the socket to a remote endpoint. Typically used by client-side sockets (SUB, REQ).
Parameters:
Name Type Description endpointstringZMQ endpoint URL (e.g., "tcp://127.0.0.1:5555")
Returns:
true if connection initiated successfully
false if connection failed
Example:
1 CZmq * subscriber = new CZmq ( ) ;
2 subscriber . Init ( ZMQ_SUB ) ;
3 if ( ! subscriber . Connect ( "tcp://127.0.0.1:5555" ) ) {
4 Print ( "Failed to connect to publisher" ) ;
5 }
Send
int Send(string message, bool nonBlocking = true)
Sends a string message through the socket.
Parameters:
Name Type Description messagestringThe message to send (UTF-8 encoded) nonBlockingboolIf true, returns immediately. If false, blocks until sent. Default: true
Returns:
Number of bytes sent on success
-1 on failure
Example:
1 string json = "{\"symbol\":\"EURUSD\",\"bid\":1.1234}" ;
2 int bytes = publisher . Send ( json , false ) ; // Blocking send
3 if ( bytes < 0 ) {
4 Print ( "Send failed" ) ;
5 }
Receive
string Receive(bool nonBlocking = true)
Receives a message from the socket.
Parameters:
Name Type Description nonBlockingboolIf true, returns immediately with empty string if no message. If false, blocks until message received. Default: true
Returns:
Received message as string on success
Empty string "" if no message available (non-blocking) or on error
Buffer Size:
Maximum receive buffer is 4096 bytes
For larger messages, modify the buffer[4096] in Zmq.mqh
Example:
1 // Non-blocking receive (polling)
2 string msg = responder . Receive ( true ) ;
3 if ( msg != "" ) {
4 Print ( "Received: " , msg ) ;
5 }
6
7 // Blocking receive (waits for message)
8 string msg = requester . Receive ( false ) ;
Shutdown
Closes the socket and terminates the ZMQ context. Should be called during cleanup.
Example:
1 void OnDeinit ( const int reason ) {
2 if ( g_publisher != NULL ) {
3 g_publisher . Shutdown ( ) ;
4 delete g_publisher ;
5 g_publisher = NULL ;
6 }
7 }
Usage Guide
Step 1: Include the Library
Step 2: Declare Global Instance
CZmq *g_publisher; // Declare as pointer for proper lifecycle management
Step 3: Initialize in OnInit()
1 int OnInit ( ) {
2 g_publisher = new CZmq ( ) ;
3
4 if ( ! g_publisher . Init ( ZMQ_PUB ) ) {
5 Print ( "ZMQ initialization failed" ) ;
6 return INIT_FAILED ;
7 }
8
9 if ( ! g_publisher . Bind ( "tcp://0.0.0.0:5555" ) ) {
10 Print ( "ZMQ bind failed" ) ;
11 return INIT_FAILED ;
12 }
13
14 Print ( "ZMQ Publisher ready on port 5555" ) ;
15 return INIT_SUCCEEDED ;
16 }
Step 4: Use in OnTick()
1 void OnTick ( ) {
2 MqlTick tick ;
3 if ( SymbolInfoTick ( _Symbol , tick ) ) {
4 string json ;
5 StringConcatenate ( json ,
6 "{\"symbol\":\"" , _Symbol ,
7 "\",\"bid\":" , DoubleToString ( tick . bid , _Digits ) ,
8 ",\"ask\":" , DoubleToString ( tick . ask , _Digits ) ,
9 "}" ) ;
10
11 g_publisher . Send ( json ) ;
12 }
13 }
Step 5: Cleanup in OnDeinit()
1 void OnDeinit ( const int reason ) {
2 if ( g_publisher != NULL ) {
3 g_publisher . Shutdown ( ) ;
4 delete g_publisher ;
5 g_publisher = NULL ;
6 }
7 }
Socket Patterns
PUB/SUB Pattern (One-to-Many Broadcasting)
1 flowchart LR
2 PUB ["Publisher\n(MT5 EA)"]
3 SUB1 ["Subscriber 1\n(Rust App)"]
4 SUB2 ["Subscriber 2\n(Go Service)"]
5 SUB3 ["Subscriber 3\n(Java Dashboard)"]
6
7 PUB --> |"Tick JSON"| SUB1
8 PUB --> |"Tick JSON"| SUB2
9 PUB --> |"Tick JSON"| SUB3
Used for real-time data streaming where the publisher broadcasts to all connected subscribers.
MQL5 Side (Publisher):
1 CZmq * publisher = new CZmq ( ) ;
2 publisher . Init ( ZMQ_PUB ) ;
3 publisher . Bind ( "tcp://0.0.0.0:5555" ) ;
4
5 // In OnTick
6 publisher . Send ( "{\"bid\": 1.1234}" ) ;
Rust Client Side (Subscriber):
1 use zeromq :: { Socket , SubSocket } ;
2
3 let mut socket = SubSocket :: new ( ) ;
4 socket . connect ( "tcp://127.0.0.1:5555" ) . await ? ;
5 socket . subscribe ( "" ) . await ? ; // Subscribe to all messages
6
7 loop {
8 let msg = socket . recv ( ) . await ? ;
9 println! ( "Received: {:?}" , msg ) ;
10 }
Go Client Side (Subscriber):
1 package main
2
3 import (
4 "fmt"
5 zmq "github.com/pebbe/zmq4"
6 )
7
8 func main ( ) {
9 subscriber , _ := zmq . NewSocket ( zmq . SUB )
10 defer subscriber . Close ( )
11
12 subscriber . Connect ( "tcp://127.0.0.1:5555" )
13 subscriber . SetSubscribe ( "" ) // Subscribe to all messages
14
15 for {
16 msg , _ := subscriber . Recv ( 0 )
17 fmt . Printf ( "Received: %s\n" , msg )
18 }
19 }
REQ/REP Pattern (Request-Reply)
1 sequenceDiagram
2 participant Client
3 participant REQ as REQ Socket
4 participant REP as REP Socket
5 participant MT5 as MT5 EA
6
7 Client ->> REQ : market_buy request
8 REQ ->> REP : Send JSON
9 REP ->> MT5 : Receive ( )
10 MT5 ->> MT5 : g_trade.Buy ( )
11 MT5 ->> REP : Send response
12 REP ->> REQ : JSON response
13 REQ ->> Client : {success: true, ticket: 12345}
Used for command-response communication, such as order execution.
MQL5 Side (Responder):
1 CZmq * responder = new CZmq ( ) ;
2 responder . Init ( ZMQ_REP ) ;
3 responder . Bind ( "tcp://0.0.0.0:5556" ) ;
4
5 // In OnTick (non-blocking poll)
6 string request = responder . Receive ( true ) ;
7 if ( request != "" ) {
8 // Process request
9 string response = ProcessOrderRequest ( request ) ;
10 responder . Send ( response , false ) ; // Blocking send required for REP
11 }
Rust Client Side (Requester):
1 use zeromq :: { Socket , ReqSocket } ;
2
3 let mut socket = ReqSocket :: new ( ) ;
4 socket . connect ( "tcp://127.0.0.1:5556" ) . await ? ;
5
6 // Send order request
7 let request = r#"{"type":"market_buy","symbol":"EURUSD","volume":0.01}"# ;
8 socket . send ( request . into ( ) ) . await ? ;
9
10 // Wait for response
11 let response = socket . recv ( ) . await ? ;
12 println! ( "Response: {:?}" , response ) ;
Go Client Side (Requester):
1 package main
2
3 import (
4 "fmt"
5 zmq "github.com/pebbe/zmq4"
6 )
7
8 func main ( ) {
9 requester , _ := zmq . NewSocket ( zmq . REQ )
10 defer requester . Close ( )
11
12 requester . Connect ( "tcp://127.0.0.1:5556" )
13
14 // Send order request
15 request := `{"type":"market_buy","symbol":"EURUSD","volume":0.01}`
16 requester . Send ( request , 0 )
17
18 // Wait for response
19 response , _ := requester . Recv ( 0 )
20 fmt . Printf ( "Response: %s\n" , response )
21 }
Message Protocol
Tick Data Message (PUB Socket)
Published on every tick from MQL5 to connected subscribers.
1 {
2 "symbol" : "XAUUSDc" ,
3 "bid" : 2345.67 ,
4 "ask" : 2345.89 ,
5 "time" : 1706400000 ,
6 "volume" : 100 ,
7 "balance" : 10000.00 ,
8 "equity" : 10150.50 ,
9 "margin" : 500.00 ,
10 "free_margin" : 9650.50 ,
11 "min_lot" : 0.01 ,
12 "max_lot" : 100.00 ,
13 "lot_step" : 0.01 ,
14 "positions" : [
15 {
16 "ticket" : 12345 ,
17 "type" : "BUY" ,
18 "volume" : 0.10 ,
19 "price" : 2340.50 ,
20 "profit" : 15.25
21 }
22 ] ,
23 "orders" : [
24 {
25 "ticket" : 12346 ,
26 "type" : "BUY LIMIT" ,
27 "volume" : 0.05 ,
28 "price" : 2330.00
29 }
30 ]
31 }
Order Request Message (REQ Socket)
Sent from external client to MQL5 for order execution.
Market Order:
1 {
2 "type" : "market_buy" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.01 ,
5 "price" : 0
6 }
Limit Order:
1 {
2 "type" : "limit_buy" ,
3 "symbol" : "XAUUSDc" ,
4 "volume" : 0.01 ,
5 "price" : 2340.00
6 }
Close Position:
1 {
2 "type" : "close_position" ,
3 "ticket" : 12345
4 }
Cancel Order:
1 {
2 "type" : "cancel_order" ,
3 "ticket" : 12346
4 }
Download History:
1 {
2 "type" : "download_history" ,
3 "symbol" : "XAUUSDc" ,
4 "timeframe" : "M1" ,
5 "start" : "2024.01.01" ,
6 "end" : "2024.01.31" ,
7 "mode" : "OHLC"
8 }
Order Response Message (REP Socket)
Sent from MQL5 back to the client.
Success:
1 {
2 "success" : true ,
3 "ticket" : 12347
4 }
Failure:
1 {
2 "success" : false ,
3 "error" : "Error 10019: Not enough money"
4 }
Supported Order Types
Type String Description market_buyExecute market buy order market_sellExecute market sell order limit_buyPlace buy limit pending order limit_sellPlace sell limit pending order stop_buyPlace buy stop pending order stop_sellPlace sell stop pending order close_positionClose existing position by ticket cancel_orderDelete pending order by ticket download_historyRequest historical data download
Complete Examples
Example 1: Simple Tick Publisher
1 //+------------------------------------------------------------------+
2 //| SimpleTickPublisher.mq5 |
3 //+------------------------------------------------------------------+
4 # include <Zmq/Zmq.mqh>
5
6 input string InpAddress = "tcp://0.0.0.0:5555" ;
7
8 CZmq * g_publisher ;
9
10 int OnInit ( ) {
11 g_publisher = new CZmq ( ) ;
12
13 if ( ! g_publisher . Init ( ZMQ_PUB ) ) {
14 Print ( "Failed to init ZMQ" ) ;
15 return INIT_FAILED ;
16 }
17
18 if ( ! g_publisher . Bind ( InpAddress ) ) {
19 Print ( "Failed to bind" ) ;
20 return INIT_FAILED ;
21 }
22
23 Print ( "Publisher ready on " , InpAddress ) ;
24 return INIT_SUCCEEDED ;
25 }
26
27 void OnDeinit ( const int reason ) {
28 if ( g_publisher != NULL ) {
29 g_publisher . Shutdown ( ) ;
30 delete g_publisher ;
31 }
32 }
33
34 void OnTick ( ) {
35 MqlTick tick ;
36 if ( SymbolInfoTick ( _Symbol , tick ) ) {
37 string json ;
38 StringConcatenate ( json ,
39 "{\"symbol\":\"" , _Symbol ,
40 "\",\"bid\":" , DoubleToString ( tick . bid , _Digits ) ,
41 ",\"ask\":" , DoubleToString ( tick . ask , _Digits ) ,
42 ",\"time\":" , IntegerToString ( tick . time ) ,
43 "}" ) ;
44
45 g_publisher . Send ( json ) ;
46 }
47 }
Example 2: Order Executor Service
1 //+------------------------------------------------------------------+
2 //| OrderExecutor.mq5 |
3 //+------------------------------------------------------------------+
4 # include <Zmq/Zmq.mqh>
5 # include <Trade/Trade.mqh>
6
7 input string InpAddress = "tcp://0.0.0.0:5556" ;
8
9 CZmq * g_responder ;
10 CTrade g_trade ;
11
12 int OnInit ( ) {
13 g_responder = new CZmq ( ) ;
14
15 if ( ! g_responder . Init ( ZMQ_REP ) )
16 return INIT_FAILED ;
17
18 if ( ! g_responder . Bind ( InpAddress ) )
19 return INIT_FAILED ;
20
21 g_trade . SetDeviationInPoints ( 10 ) ;
22
23 Print ( "Order executor ready on " , InpAddress ) ;
24 return INIT_SUCCEEDED ;
25 }
26
27 void OnDeinit ( const int reason ) {
28 if ( g_responder != NULL ) {
29 g_responder . Shutdown ( ) ;
30 delete g_responder ;
31 }
32 }
33
34 void OnTick ( ) {
35 // Non-blocking receive
36 string request = g_responder . Receive ( true ) ;
37
38 if ( request == "" ) return ;
39
40 Print ( "Request: " , request ) ;
41
42 // Parse and execute (simplified)
43 string response ;
44 if ( StringFind ( request , "market_buy" ) >= 0 ) {
45 double ask = SymbolInfoDouble ( _Symbol , SYMBOL_ASK ) ;
46 if ( g_trade . Buy ( 0.01 , _Symbol , ask ) ) {
47 StringConcatenate ( response ,
48 "{\"success\":true,\"ticket\":" ,
49 IntegerToString ( g_trade . ResultOrder ( ) ) , "}" ) ;
50 } else {
51 response = "{\"success\":false,\"error\":\"Buy failed\"}" ;
52 }
53 } else {
54 response = "{\"success\":false,\"error\":\"Unknown command\"}" ;
55 }
56
57 g_responder . Send ( response , false ) ; // Blocking send for REP
58 Print ( "Response: " , response ) ;
59 }
Example 3: Rust Client (Complete)
1 // Cargo.toml dependencies:
2 // zeromq = "0.3"
3 // tokio = { version = "1", features = ["full"] }
4 // serde = { version = "1", features = ["derive"] }
5 // serde_json = "1"
6
7 use serde :: { Deserialize , Serialize } ;
8 use tokio :: sync :: mpsc ;
9 use zeromq :: { Socket , SocketRecv , SocketSend } ;
10
11 #[derive(Debug, Deserialize)]
12 struct TickData {
13 symbol : String ,
14 bid : f64 ,
15 ask : f64 ,
16 time : i64 ,
17 }
18
19 #[derive(Debug, Serialize)]
20 struct OrderRequest {
21 #[serde(rename = "type" )]
22 order_type : String ,
23 symbol : String ,
24 volume : f64 ,
25 }
26
27 #[tokio::main]
28 async fn main ( ) -> Result < ( ) , Box < dyn std :: error :: Error >> {
29 // Subscribe to tick data
30 let ( tx , mut rx ) = mpsc :: channel :: < TickData > ( 100 ) ;
31
32 tokio :: spawn ( async move {
33 let mut socket = zeromq :: SubSocket :: new ( ) ;
34 socket . connect ( "tcp://127.0.0.1:5555" ) . await . unwrap ( ) ;
35 socket . subscribe ( "" ) . await . unwrap ( ) ;
36
37 loop {
38 if let Ok ( msg ) = socket . recv ( ) . await {
39 if let Some ( bytes ) = msg . get ( 0 ) {
40 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
41 if let Ok ( tick ) = serde_json :: from_str :: < TickData > ( json ) {
42 let _ = tx . send ( tick ) . await ;
43 }
44 }
45 }
46 }
47 }
48 } ) ;
49
50 // Process ticks
51 while let Some ( tick ) = rx . recv ( ) . await {
52 println! ( "{}: Bid={}, Ask={}" , tick . symbol , tick . bid , tick . ask ) ;
53 }
54
55 Ok ( ( ) )
56 }
Example 4: Java Client (Complete)
1 // Maven dependency: org.zeromq:jeromq:0.5.3
2 import org . zeromq . SocketType ;
3 import org . zeromq . ZContext ;
4 import org . zeromq . ZMQ ;
5 import com . google . gson . Gson ;
6
7 public class MT5Client {
8 private ZContext context ;
9 private ZMQ . Socket subscriber ;
10 private ZMQ . Socket requester ;
11 private Gson gson = new Gson ( ) ;
12
13 public MT5Client ( int tickPort , int orderPort ) {
14 context = new ZContext ( ) ;
15
16 // Subscriber for tick data
17 subscriber = context . createSocket ( SocketType . SUB ) ;
18 subscriber . connect ( "tcp://127.0.0.1:" + tickPort ) ;
19 subscriber . subscribe ( "" . getBytes ( ) ) ;
20
21 // Requester for orders
22 requester = context . createSocket ( SocketType . REQ ) ;
23 requester . connect ( "tcp://127.0.0.1:" + orderPort ) ;
24 }
25
26 public void startTickStream ( ) {
27 new Thread ( ( ) -> {
28 while ( ! Thread . currentThread ( ) . isInterrupted ( ) ) {
29 String msg = subscriber . recvStr ( ZMQ . DONTWAIT ) ;
30 if ( msg != null ) {
31 TickData tick = gson . fromJson ( msg , TickData . class ) ;
32 System . out . printf ( "%s: Bid=%.5f, Ask=%.5f%n" ,
33 tick . symbol , tick . bid , tick . ask ) ;
34 }
35 try { Thread . sleep ( 1 ) ; } catch ( InterruptedException e ) { break ; }
36 }
37 } ) . start ( ) ;
38 }
39
40 public OrderResponse sendOrder ( String type , String symbol , double volume ) {
41 OrderRequest request = new OrderRequest ( type , symbol , volume ) ;
42 requester . send ( gson . toJson ( request ) ) ;
43 String response = requester . recvStr ( ) ;
44 return gson . fromJson ( response , OrderResponse . class ) ;
45 }
46
47 public void close ( ) {
48 context . close ( ) ;
49 }
50
51 // Data classes
52 static class TickData {
53 String symbol ;
54 double bid , ask ;
55 long time ;
56 }
57
58 static class OrderRequest {
59 String type , symbol ;
60 double volume ;
61 OrderRequest ( String t , String s , double v ) { type = t ; symbol = s ; volume = v ; }
62 }
63
64 static class OrderResponse {
65 boolean success ;
66 Long ticket ;
67 String error ;
68 }
69
70 public static void main ( String [ ] args ) {
71 MT5Client client = new MT5Client ( 5555 , 5556 ) ;
72 client . startTickStream ( ) ;
73
74 // Execute a buy order
75 OrderResponse response = client . sendOrder ( "market_buy" , "EURUSD" , 0.01 ) ;
76 System . out . println ( "Order result: " + response . success ) ;
77 }
78 }
Example 5: C++ Client (Complete)
1 // Requires: libzmq, cppzmq, nlohmann/json
2 // Compile: g++ -std=c++17 -o mt5_client mt5_client.cpp -lzmq -lpthread
3
4 # include <zmq.hpp>
5 # include <nlohmann/json.hpp>
6 # include <iostream>
7 # include <thread>
8 # include <atomic>
9
10 using json = nlohmann :: json ;
11
12 class MT5Client {
13 private :
14 zmq :: context_t context ;
15 zmq :: socket_t subscriber ;
16 zmq :: socket_t requester ;
17 std :: atomic < bool > running { false } ;
18 std :: thread tick_thread ;
19
20 public :
21 MT5Client ( int tick_port = 5555 , int order_port = 5556 )
22 : context ( 1 ) , subscriber ( context , zmq :: socket_type :: sub ) ,
23 requester ( context , zmq :: socket_type :: req ) {
24
25 subscriber . connect ( "tcp://127.0.0.1:" + std :: to_string ( tick_port ) ) ;
26 subscriber . set ( zmq :: sockopt :: subscribe , "" ) ;
27
28 requester . connect ( "tcp://127.0.0.1:" + std :: to_string ( order_port ) ) ;
29 }
30
31 void start_tick_stream ( ) {
32 running = true ;
33 tick_thread = std :: thread ( [ this ] ( ) {
34 while ( running ) {
35 zmq :: message_t message ;
36 auto result = subscriber . recv ( message , zmq :: recv_flags :: dontwait ) ;
37 if ( result ) {
38 std :: string msg ( static_cast < char * > ( message . data ( ) ) , message . size ( ) ) ;
39 json tick = json :: parse ( msg ) ;
40 std :: cout << tick [ "symbol" ] . get < std :: string > ( )
41 << ": Bid=" << tick [ "bid" ] . get < double > ( )
42 << ", Ask=" << tick [ "ask" ] . get < double > ( ) << std :: endl ;
43 }
44 std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 1 ) ) ;
45 }
46 } ) ;
47 }
48
49 json send_order ( const std :: string & type , const std :: string & symbol , double volume ) {
50 json request = { { "type" , type } , { "symbol" , symbol } , { "volume" , volume } } ;
51 std :: string req_str = request . dump ( ) ;
52
53 zmq :: message_t req_msg ( req_str . begin ( ) , req_str . end ( ) ) ;
54 requester . send ( req_msg , zmq :: send_flags :: none ) ;
55
56 zmq :: message_t reply ;
57 requester . recv ( reply ) ;
58
59 std :: string reply_str ( static_cast < char * > ( reply . data ( ) ) , reply . size ( ) ) ;
60 return json :: parse ( reply_str ) ;
61 }
62
63 json market_buy ( const std :: string & symbol , double volume ) {
64 return send_order ( "market_buy" , symbol , volume ) ;
65 }
66
67 json market_sell ( const std :: string & symbol , double volume ) {
68 return send_order ( "market_sell" , symbol , volume ) ;
69 }
70
71 void stop ( ) {
72 running = false ;
73 if ( tick_thread . joinable ( ) ) tick_thread . join ( ) ;
74 }
75
76 ~ MT5Client ( ) { stop ( ) ; }
77 } ;
78
79 int main ( ) {
80 MT5Client client ;
81 client . start_tick_stream ( ) ;
82
83 // Execute a buy order
84 json response = client . market_buy ( "EURUSD" , 0.01 ) ;
85 std :: cout << "Order result: " << response . dump ( ) << std :: endl ;
86
87 // Keep running
88 std :: this_thread :: sleep_for ( std :: chrono :: seconds ( 60 ) ) ;
89 return 0 ;
90 }
Error Handling
ZMQ Error Codes
The library uses zmq_errno() to retrieve error codes. Common errors:
Error Code Description Solution 11 EAGAIN (resource unavailable) Normal for non-blocking ops when no data 48 EADDRINUSE (address in use) Port already bound, use different port 111 ECONNREFUSED Remote endpoint not available 156384713 ETERM (context terminated) ZMQ context was terminated
Defensive Programming
1 // Always check initialization
2 if ( ! g_publisher . Init ( ZMQ_PUB ) ) {
3 Print ( "ZMQ Init failed" ) ;
4 return INIT_FAILED ;
5 }
6
7 // Always check bind/connect
8 if ( ! g_publisher . Bind ( "tcp://0.0.0.0:5555" ) ) {
9 Print ( "ZMQ Bind failed, errno: " , zmq_errno ( ) ) ;
10 g_publisher . Shutdown ( ) ;
11 return INIT_FAILED ;
12 }
13
14 // Handle empty receive gracefully
15 string msg = g_responder . Receive ( true ) ;
16 if ( msg == "" ) {
17 // No message available, continue
18 return ;
19 }
Best Practices
1. Resource Management
Always use pointers and proper cleanup:
1 CZmq * g_socket = NULL ; // Initialize to NULL
2
3 int OnInit ( ) {
4 g_socket = new CZmq ( ) ;
5 // ... init and bind
6 }
7
8 void OnDeinit ( const int reason ) {
9 if ( g_socket != NULL ) {
10 g_socket . Shutdown ( ) ;
11 delete g_socket ;
12 g_socket = NULL ;
13 }
14 }
2. Non-Blocking in OnTick()
Never use blocking operations in OnTick() - they will freeze the terminal:
1 void OnTick ( ) {
2 // GOOD: Non-blocking receive
3 string msg = g_responder . Receive ( true ) ;
4
5 // BAD: This would freeze the terminal
6 // string msg = g_responder.Receive(false);
7 }
3. REQ/REP Pattern Compliance
The REP socket must always send a reply after receiving a request:
1 void OnTick ( ) {
2 string request = g_responder . Receive ( true ) ;
3 if ( request != "" ) {
4 // MUST send response for every request
5 string response = ProcessRequest ( request ) ;
6 g_responder . Send ( response , false ) ; // Use blocking send
7 }
8 }
4. Buffer Size Considerations
The default receive buffer is 4096 bytes. For larger messages:
1 // In Zmq.mqh, modify:
2 uchar buffer [ 16384 ] ; // Increase to 16KB
5. JSON Message Construction
Use StringConcatenate for efficient string building:
1 string json ;
2 StringConcatenate ( json ,
3 "{\"symbol\":\"" , _Symbol ,
4 "\",\"value\":" , DoubleToString ( value , 5 ) ,
5 "}" ) ;
Troubleshooting
Common Issues
Issue: "dll imports are not allowed"
Solution: Enable Allow DLL imports in Tools > Options > Expert Advisors
Issue: "Cannot load library 'libzmq.dll'"
Solution: Ensure libzmq.dll is in MQL5/Libraries/ folder
Solution: Ensure libsodium.dll is also present (dependency)
Solution: Verify DLLs are 64-bit if using 64-bit MT5
Issue: "ZMQ Bind failed"
Solution: Check if port is already in use
Solution: Try a different port number
Solution: Ensure firewall allows the port
Issue: No data received on subscriber
Solution: Ensure subscriber connects AFTER publisher binds
Solution: Add a small delay after connect before expecting data
Solution: Verify network connectivity
Issue: "Request not answered" on REQ socket
Solution: Ensure REP socket always sends a response for every receive
Solution: Check for crashes in request processing logic
Debug Logging
Add print statements to trace execution:
1 void OnTick ( ) {
2 string request = g_responder . Receive ( true ) ;
3 if ( request != "" ) {
4 Print ( "Received request: " , request ) ;
5
6 string response = ProcessRequest ( request ) ;
7 Print ( "Sending response: " , response ) ;
8
9 int sent = g_responder . Send ( response , false ) ;
10 Print ( "Bytes sent: " , sent ) ;
11 }
12 }
Version History
Version Date Changes 2.00 2026-01-27 Added REP socket support, order handling, account info streaming 1.00 2026-01-20 Initial release with PUB socket support
References
//end of documentattion
Rust ZeroMQ Wrapper Library for MT5 Communication
A comprehensive reusable Rust library for ZeroMQ socket operations, designed for real-time communication with MetaTrader 5 via the MQL5-ZMQ bridge.
Table of Contents
Overview
Architecture
Prerequisites and Installation
API Reference
Usage Guide
Data Structures
Complete Examples
Error Handling
Best Practices
Integration with Other Languages
Overview
This library provides a high-level Rust wrapper for ZeroMQ socket operations, specifically designed to communicate with MetaTrader 5 Expert Advisors running the MQL5-ZMQ bridge.
[!NOTE]
For the companion MQL5 server library, see
MQL5-ZMQ Library for SUM3API .
Key Features
Async/Await Support : Built on Tokio for non-blocking operations
Type-Safe Messages : Serde-based JSON serialization with strongly typed structs
Dual Socket Pattern : SUB socket for tick streaming, REQ socket for order execution
Channel-Based Architecture : Uses MPSC channels for thread-safe message passing
Automatic Reconnection : Resilient connection handling
Supported Socket Types
Pattern Rust Socket MQL5 Socket Purpose PUB/SUB SubSocketZMQ_PUBReal-time tick data streaming REQ/REP ReqSocketZMQ_REPOrder execution and commands
Architecture
System Integration
1 flowchart TB
2 subgraph MT5 ["MetaTrader 5"]
3 EA ["ZmqPublisher EA"]
4 MQL ["CZmq Wrapper"]
5 EA --> MQL
6 end
7
8 subgraph ZMQ ["ZeroMQ Layer"]
9 PUB ["PUB :5555"]
10 REP ["REP :5556"]
11 end
12
13 subgraph Rust ["Rust Application"]
14 SUB ["SubSocket"]
15 REQ ["ReqSocket"]
16 TICK_CH ["Tick Channel"]
17 ORDER_CH ["Order Channel"]
18 APP ["Application Logic"]
19
20 SUB --> TICK_CH
21 TICK_CH --> APP
22 APP --> ORDER_CH
23 ORDER_CH --> REQ
24 end
25
26 MQL --> PUB
27 MQL --> REP
28 PUB --> |"JSON Tick Data"| SUB
29 REQ <--> |"JSON Orders"| REP
Data Flow
1 sequenceDiagram
2 participant MT5 as MT5 EA
3 participant PUB as PUB Socket
4 participant SUB as Rust SubSocket
5 participant CH as MPSC Channel
6 participant APP as Rust App
7 participant REQ as Rust ReqSocket
8 participant REP as REP Socket
9
10 Note over MT5,APP : Tick Data Flow
11 loop Every Tick
12 MT5 ->> PUB : Publish JSON
13 PUB ->> SUB : Broadcast
14 SUB ->> CH : tx.send (tick)
15 CH ->> APP : rx.recv ( )
16 end
17
18 Note over APP,MT5 : Order Execution Flow
19 APP ->> REQ : Order Request
20 REQ ->> REP : Send JSON
21 REP ->> MT5 : Parse Order
22 MT5 ->> MT5 : Execute Trade
23 MT5 ->> REP : Response
24 REP ->> REQ : JSON Response
25 REQ ->> APP : OrderResponse
Prerequisites and Installation
Cargo.toml Dependencies
1 [ dependencies ]
2 zeromq = "0.3"
3 tokio = { version = "1" , features = [ "full" ] }
4 serde = { version = "1" , features = [ "derive" ] }
5 serde_json = "1"
6 chrono = "0.4"
System Requirements
Rust 1.70 or later
ZeroMQ library installed on system (for zeromq crate)
MetaTrader 5 with MQL5-ZMQ EA running
Installation Steps
Add dependencies to Cargo.toml (see above)
Build the project
Verify MT5 EA is running
Ensure ZmqPublisher.mq5 is attached to a chart
Verify ports 5555 (tick data) and 5556 (orders) are accessible
API Reference
Data Structures
TickData
Represents real-time market data received from MT5.
1 #[derive(Clone, Debug, Deserialize)]
2 pub struct TickData {
3 pub symbol : String ,
4 pub bid : f64 ,
5 pub ask : f64 ,
6 pub time : i64 ,
7 #[serde(default)]
8 pub volume : u64 ,
9 #[serde(default)]
10 pub balance : f64 ,
11 #[serde(default)]
12 pub equity : f64 ,
13 #[serde(default)]
14 pub margin : f64 ,
15 #[serde(default)]
16 pub free_margin : f64 ,
17 #[serde(default)]
18 pub min_lot : f64 ,
19 #[serde(default)]
20 pub max_lot : f64 ,
21 #[serde(default)]
22 pub lot_step : f64 ,
23 #[serde(default)]
24 pub positions : Vec < PositionData > ,
25 #[serde(default)]
26 pub orders : Vec < PendingOrderData > ,
27 }
Field Type Description symbolStringTrading symbol (e.g., "EURUSD") bidf64Current bid price askf64Current ask price timei64Unix timestamp volumeu64Tick volume balancef64Account balance equityf64Account equity marginf64Used margin free_marginf64Available margin min_lotf64Minimum lot size max_lotf64Maximum lot size lot_stepf64Lot size increment positionsVec<PositionData>Active positions ordersVec<PendingOrderData>Pending orders
PositionData
Represents an active trading position.
1 #[derive(Clone, Debug, Deserialize)]
2 pub struct PositionData {
3 pub ticket : u64 ,
4 #[serde(rename = "type" )]
5 pub pos_type : String , // "BUY" or "SELL"
6 pub volume : f64 ,
7 pub price : f64 ,
8 pub profit : f64 ,
9 }
PendingOrderData
Represents a pending order.
1 #[derive(Clone, Debug, Deserialize)]
2 pub struct PendingOrderData {
3 pub ticket : u64 ,
4 #[serde(rename = "type" )]
5 pub order_type : String , // "BUY LIMIT", "SELL STOP", etc.
6 pub volume : f64 ,
7 pub price : f64 ,
8 }
OrderRequest
Request structure for sending orders to MT5.
1 #[derive(Clone, Debug, Serialize)]
2 pub struct OrderRequest {
3 #[serde(rename = "type" )]
4 pub order_type : String ,
5 pub symbol : String ,
6 pub volume : f64 ,
7 pub price : f64 ,
8 #[serde(default)]
9 pub ticket : u64 ,
10 #[serde(skip_serializing_if = "Option::is_none" )]
11 pub timeframe : Option < String > ,
12 #[serde(skip_serializing_if = "Option::is_none" )]
13 pub start : Option < String > ,
14 #[serde(skip_serializing_if = "Option::is_none" )]
15 pub end : Option < String > ,
16 #[serde(skip_serializing_if = "Option::is_none" )]
17 pub mode : Option < String > ,
18 }
Supported Order Types:
Type Description market_buyExecute market buy order market_sellExecute market sell order limit_buyPlace buy limit pending order limit_sellPlace sell limit pending order stop_buyPlace buy stop pending order stop_sellPlace sell stop pending order close_positionClose position by ticket cancel_orderCancel pending order by ticket download_historyRequest historical data
OrderResponse
Response structure from MT5 order execution.
1 #[derive(Clone, Debug, Deserialize)]
2 pub struct OrderResponse {
3 pub success : bool ,
4 pub ticket : Option < i64 > ,
5 pub error : Option < String > ,
6 pub message : Option < String > ,
7 }
Usage Guide
Step 1: Create Channels
1 use tokio :: sync :: mpsc ;
2
3 // Channel for tick data (MT5 -> App)
4 let ( tick_tx , tick_rx ) = mpsc :: channel :: < TickData > ( 100 ) ;
5
6 // Channel for order requests (App -> MT5)
7 let ( order_tx , order_rx ) = mpsc :: channel :: < OrderRequest > ( 10 ) ;
8
9 // Channel for order responses (MT5 -> App)
10 let ( response_tx , response_rx ) = mpsc :: channel :: < OrderResponse > ( 10 ) ;
Step 2: Spawn Tick Subscriber Task
1 tokio :: spawn ( async move {
2 let mut socket = zeromq :: SubSocket :: new ( ) ;
3 socket . connect ( "tcp://127.0.0.1:5555" ) . await . unwrap ( ) ;
4 socket . subscribe ( "" ) . await . unwrap ( ) ;
5
6 loop {
7 match socket . recv ( ) . await {
8 Ok ( msg ) => {
9 if let Some ( bytes ) = msg . get ( 0 ) {
10 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
11 if let Ok ( tick ) = serde_json :: from_str :: < TickData > ( json ) {
12 let _ = tick_tx . send ( tick ) . await ;
13 }
14 }
15 }
16 }
17 Err ( e ) => {
18 eprintln! ( "Tick recv error: {}" , e ) ;
19 tokio :: time :: sleep ( Duration :: from_secs ( 1 ) ) . await ;
20 }
21 }
22 }
23 } ) ;
Step 3: Spawn Order Handler Task
1 tokio :: spawn ( async move {
2 let mut socket = zeromq :: ReqSocket :: new ( ) ;
3 socket . connect ( "tcp://127.0.0.1:5556" ) . await . unwrap ( ) ;
4
5 while let Some ( request ) = order_rx . recv ( ) . await {
6 let json = serde_json :: to_string ( & request ) . unwrap ( ) ;
7
8 if let Err ( e ) = socket . send ( json . into ( ) ) . await {
9 let _ = response_tx . send ( OrderResponse {
10 success : false ,
11 ticket : None ,
12 error : Some ( format! ( "Send failed: {}" , e ) ) ,
13 message : None ,
14 } ) . await ;
15 continue ;
16 }
17
18 match socket . recv ( ) . await {
19 Ok ( msg ) => {
20 if let Some ( bytes ) = msg . get ( 0 ) {
21 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
22 if let Ok ( response ) = serde_json :: from_str :: < OrderResponse > ( json ) {
23 let _ = response_tx . send ( response ) . await ;
24 }
25 }
26 }
27 }
28 Err ( e ) => {
29 let _ = response_tx . send ( OrderResponse {
30 success : false ,
31 ticket : None ,
32 error : Some ( format! ( "Recv failed: {}" , e ) ) ,
33 message : None ,
34 } ) . await ;
35 }
36 }
37 }
38 } ) ;
Step 4: Process Ticks and Send Orders
1 // Process incoming ticks
2 while let Some ( tick ) = tick_rx . recv ( ) . await {
3 println! ( "{}: Bid={}, Ask={}" , tick . symbol , tick . bid , tick . ask ) ;
4
5 // Example: Send a buy order when certain condition is met
6 if some_trading_condition ( & tick ) {
7 let order = OrderRequest {
8 order_type : "market_buy" . to_string ( ) ,
9 symbol : tick . symbol . clone ( ) ,
10 volume : 0.01 ,
11 price : 0.0 ,
12 ticket : 0 ,
13 timeframe : None ,
14 start : None ,
15 end : None ,
16 mode : None ,
17 } ;
18 let _ = order_tx . send ( order ) . await ;
19 }
20 }
Complete Examples
Example 1: Basic Tick Subscriber
1 use serde :: Deserialize ;
2 use zeromq :: { Socket , SocketRecv } ;
3
4 #[derive(Debug, Deserialize)]
5 struct TickData {
6 symbol : String ,
7 bid : f64 ,
8 ask : f64 ,
9 time : i64 ,
10 }
11
12 #[tokio::main]
13 async fn main ( ) -> Result < ( ) , Box < dyn std :: error :: Error >> {
14 let mut socket = zeromq :: SubSocket :: new ( ) ;
15 socket . connect ( "tcp://127.0.0.1:5555" ) . await ? ;
16 socket . subscribe ( "" ) . await ? ;
17
18 println! ( "Connected to MT5 tick publisher" ) ;
19
20 loop {
21 let msg = socket . recv ( ) . await ? ;
22 if let Some ( bytes ) = msg . get ( 0 ) {
23 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
24 if let Ok ( tick ) = serde_json :: from_str :: < TickData > ( json ) {
25 println! ( "{}: {:.5} / {:.5}" , tick . symbol , tick . bid , tick . ask ) ;
26 }
27 }
28 }
29 }
30 }
Example 2: Order Execution Client
1 use serde :: { Deserialize , Serialize } ;
2 use zeromq :: { Socket , SocketRecv , SocketSend } ;
3
4 #[derive(Serialize)]
5 struct OrderRequest {
6 #[serde(rename = "type" )]
7 order_type : String ,
8 symbol : String ,
9 volume : f64 ,
10 price : f64 ,
11 }
12
13 #[derive(Debug, Deserialize)]
14 struct OrderResponse {
15 success : bool ,
16 ticket : Option < i64 > ,
17 error : Option < String > ,
18 }
19
20 #[tokio::main]
21 async fn main ( ) -> Result < ( ) , Box < dyn std :: error :: Error >> {
22 let mut socket = zeromq :: ReqSocket :: new ( ) ;
23 socket . connect ( "tcp://127.0.0.1:5556" ) . await ? ;
24
25 println! ( "Connected to MT5 order handler" ) ;
26
27 // Send a market buy order
28 let order = OrderRequest {
29 order_type : "market_buy" . to_string ( ) ,
30 symbol : "EURUSD" . to_string ( ) ,
31 volume : 0.01 ,
32 price : 0.0 ,
33 } ;
34
35 let json = serde_json :: to_string ( & order ) ? ;
36 println! ( "Sending: {}" , json ) ;
37
38 socket . send ( json . into ( ) ) . await ? ;
39
40 let response = socket . recv ( ) . await ? ;
41 if let Some ( bytes ) = response . get ( 0 ) {
42 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
43 let resp : OrderResponse = serde_json :: from_str ( json ) ? ;
44 if resp . success {
45 println! ( "Order executed! Ticket: {:?}" , resp . ticket ) ;
46 } else {
47 println! ( "Order failed: {:?}" , resp . error ) ;
48 }
49 }
50 }
51
52 Ok ( ( ) )
53 }
Example 3: Full Trading Application
1 use serde :: { Deserialize , Serialize } ;
2 use tokio :: sync :: mpsc ;
3 use zeromq :: { Socket , SocketRecv , SocketSend } ;
4 use std :: time :: Duration ;
5
6 // ============================================================================
7 // Data Structures
8 // ============================================================================
9
10 #[derive(Clone, Debug, Deserialize)]
11 struct PositionData {
12 ticket : u64 ,
13 #[serde(rename = "type" )]
14 pos_type : String ,
15 volume : f64 ,
16 price : f64 ,
17 profit : f64 ,
18 }
19
20 #[derive(Clone, Debug, Deserialize)]
21 struct TickData {
22 symbol : String ,
23 bid : f64 ,
24 ask : f64 ,
25 time : i64 ,
26 #[serde(default)]
27 balance : f64 ,
28 #[serde(default)]
29 equity : f64 ,
30 #[serde(default)]
31 positions : Vec < PositionData > ,
32 }
33
34 #[derive(Clone, Debug, Serialize)]
35 struct OrderRequest {
36 #[serde(rename = "type" )]
37 order_type : String ,
38 symbol : String ,
39 volume : f64 ,
40 #[serde(default)]
41 price : f64 ,
42 #[serde(default)]
43 ticket : u64 ,
44 }
45
46 #[derive(Clone, Debug, Deserialize)]
47 struct OrderResponse {
48 success : bool ,
49 ticket : Option < i64 > ,
50 error : Option < String > ,
51 }
52
53 // ============================================================================
54 // Main Application
55 // ============================================================================
56
57 #[tokio::main]
58 async fn main ( ) -> Result < ( ) , Box < dyn std :: error :: Error >> {
59 // Create channels
60 let ( tick_tx , mut tick_rx ) = mpsc :: channel :: < TickData > ( 100 ) ;
61 let ( order_tx , mut order_rx ) = mpsc :: channel :: < OrderRequest > ( 10 ) ;
62 let ( response_tx , mut response_rx ) = mpsc :: channel :: < OrderResponse > ( 10 ) ;
63
64 // Spawn tick subscriber
65 tokio :: spawn ( async move {
66 let mut socket = zeromq :: SubSocket :: new ( ) ;
67 if let Err ( e ) = socket . connect ( "tcp://127.0.0.1:5555" ) . await {
68 eprintln! ( "Failed to connect to tick publisher: {}" , e ) ;
69 return ;
70 }
71 let _ = socket . subscribe ( "" ) . await ;
72 println! ( "Tick subscriber connected" ) ;
73
74 loop {
75 match socket . recv ( ) . await {
76 Ok ( msg ) => {
77 if let Some ( bytes ) = msg . get ( 0 ) {
78 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
79 if let Ok ( tick ) = serde_json :: from_str :: < TickData > ( json ) {
80 if tick_tx . send ( tick ) . await . is_err ( ) {
81 break ;
82 }
83 }
84 }
85 }
86 }
87 Err ( e ) => {
88 eprintln! ( "Tick error: {}" , e ) ;
89 tokio :: time :: sleep ( Duration :: from_secs ( 1 ) ) . await ;
90 }
91 }
92 }
93 } ) ;
94
95 // Spawn order handler
96 let resp_tx = response_tx . clone ( ) ;
97 tokio :: spawn ( async move {
98 let mut socket = zeromq :: ReqSocket :: new ( ) ;
99 if let Err ( e ) = socket . connect ( "tcp://127.0.0.1:5556" ) . await {
100 eprintln! ( "Failed to connect to order handler: {}" , e ) ;
101 return ;
102 }
103 println! ( "Order handler connected" ) ;
104
105 while let Some ( request ) = order_rx . recv ( ) . await {
106 let json = match serde_json :: to_string ( & request ) {
107 Ok ( j ) => j ,
108 Err ( e ) => {
109 let _ = resp_tx . send ( OrderResponse {
110 success : false ,
111 ticket : None ,
112 error : Some ( format! ( "Serialize error: {}" , e ) ) ,
113 } ) . await ;
114 continue ;
115 }
116 } ;
117
118 println! ( "Sending order: {}" , json ) ;
119
120 if let Err ( e ) = socket . send ( json . into ( ) ) . await {
121 let _ = resp_tx . send ( OrderResponse {
122 success : false ,
123 ticket : None ,
124 error : Some ( format! ( "Send error: {}" , e ) ) ,
125 } ) . await ;
126 continue ;
127 }
128
129 match socket . recv ( ) . await {
130 Ok ( msg ) => {
131 if let Some ( bytes ) = msg . get ( 0 ) {
132 if let Ok ( json ) = std :: str :: from_utf8 ( bytes ) {
133 if let Ok ( resp ) = serde_json :: from_str :: < OrderResponse > ( json ) {
134 let _ = resp_tx . send ( resp ) . await ;
135 }
136 }
137 }
138 }
139 Err ( e ) => {
140 let _ = resp_tx . send ( OrderResponse {
141 success : false ,
142 ticket : None ,
143 error : Some ( format! ( "Recv error: {}" , e ) ) ,
144 } ) . await ;
145 }
146 }
147 }
148 } ) ;
149
150 // Spawn response handler
151 tokio :: spawn ( async move {
152 while let Some ( response ) = response_rx . recv ( ) . await {
153 if response . success {
154 println! ( "Order SUCCESS: Ticket {:?}" , response . ticket ) ;
155 } else {
156 println! ( "Order FAILED: {:?}" , response . error ) ;
157 }
158 }
159 } ) ;
160
161 // Main loop - process ticks
162 println! ( "Starting main loop..." ) ;
163 let mut tick_count = 0u64 ;
164
165 while let Some ( tick ) = tick_rx . recv ( ) . await {
166 tick_count += 1 ;
167
168 // Print every 100th tick to avoid spam
169 if tick_count % 100 == 0 {
170 println! (
171 "[{}] {}: Bid={:.5}, Ask={:.5}, Balance={:.2}, Positions={}" ,
172 tick_count ,
173 tick . symbol ,
174 tick . bid ,
175 tick . ask ,
176 tick . balance ,
177 tick . positions . len ( )
178 ) ;
179 }
180
181 // Example trading logic: buy when no positions exist
182 if tick . positions . is_empty ( ) && tick_count == 500 {
183 let order = OrderRequest {
184 order_type : "market_buy" . to_string ( ) ,
185 symbol : tick . symbol . clone ( ) ,
186 volume : 0.01 ,
187 price : 0.0 ,
188 ticket : 0 ,
189 } ;
190 let _ = order_tx . send ( order ) . await ;
191 }
192 }
193
194 Ok ( ( ) )
195 }
Error Handling
Common Error Patterns
1 // Connection error handling
2 match socket . connect ( "tcp://127.0.0.1:5555" ) . await {
3 Ok ( _ ) => println! ( "Connected" ) ,
4 Err ( e ) => {
5 eprintln! ( "Connection failed: {}" , e ) ;
6 // Implement retry logic
7 tokio :: time :: sleep ( Duration :: from_secs ( 5 ) ) . await ;
8 }
9 }
10
11 // Receive error handling with retry
12 loop {
13 match socket . recv ( ) . await {
14 Ok ( msg ) => process_message ( msg ) ,
15 Err ( e ) => {
16 eprintln! ( "Recv error: {}" , e ) ;
17 tokio :: time :: sleep ( Duration :: from_millis ( 100 ) ) . await ;
18 continue ;
19 }
20 }
21 }
22
23 // JSON parsing error handling
24 match serde_json :: from_str :: < TickData > ( json ) {
25 Ok ( tick ) => handle_tick ( tick ) ,
26 Err ( e ) => eprintln! ( "JSON parse error: {} - Data: {}" , e , json ) ,
27 }
Error Response Structure
Always check OrderResponse.success before using other fields:
1 if response . success {
2 let ticket = response . ticket . unwrap_or ( 0 ) ;
3 println! ( "Order executed with ticket: {}" , ticket ) ;
4 } else {
5 let error = response . error . unwrap_or_else ( | | "Unknown error" . to_string ( ) ) ;
6 eprintln! ( "Order failed: {}" , error ) ;
7 }
Best Practices
1. Use Bounded Channels
Prevent memory issues with bounded channels:
1 // Good: Bounded channel with reasonable capacity
2 let ( tx , rx ) = mpsc :: channel :: < TickData > ( 100 ) ;
3
4 // Avoid: Unbounded channels can grow infinitely
5 // let (tx, rx) = mpsc::unbounded_channel();
2. Handle Channel Errors
Check for send/receive errors:
1 // Check if receiver is dropped
2 if tx . send ( tick ) . await . is_err ( ) {
3 eprintln! ( "Receiver dropped, exiting" ) ;
4 break ;
5 }
6
7 // Use try_send for non-blocking with backpressure
8 match tx . try_send ( tick ) {
9 Ok ( _ ) => { } ,
10 Err ( mpsc :: error :: TrySendError :: Full ( _ ) ) => {
11 eprintln! ( "Channel full, dropping tick" ) ;
12 }
13 Err ( mpsc :: error :: TrySendError :: Closed ( _ ) ) => break ,
14 }
3. Graceful Shutdown
Implement proper shutdown handling:
1 use tokio :: signal ;
2
3 tokio :: select! {
4 _ = process_ticks ( & mut tick_rx ) => { } ,
5 _ = signal :: ctrl_c ( ) => {
6 println! ( "Shutting down..." ) ;
7 }
8 }
4. Connection Resilience
Implement reconnection logic:
1 async fn connect_with_retry ( addr : & str , max_retries : u32 ) -> Result < SubSocket , Error > {
2 for attempt in 1 ..= max_retries {
3 let mut socket = zeromq :: SubSocket :: new ( ) ;
4 match socket . connect ( addr ) . await {
5 Ok ( _ ) => return Ok ( socket ) ,
6 Err ( e ) => {
7 eprintln! ( "Attempt {}/{} failed: {}" , attempt , max_retries , e ) ;
8 tokio :: time :: sleep ( Duration :: from_secs ( attempt as u64 ) ) . await ;
9 }
10 }
11 }
12 Err ( Error :: ConnectionFailed )
13 }
Integration with Other Languages
This Rust library is designed to work alongside the MQL5-ZMQ bridge. The same protocol can be implemented in other languages:
Go Integration
1 // See MQL5-ZMQ Library documentation for Go examples
2 import zmq "github.com/pebbe/zmq4"
Java Integration
1 // See MQL5-ZMQ Library documentation for Java examples
2 import org . zeromq . ZMQ ;
C++ Integration
1 // See MQL5-ZMQ Library documentation for C++ examples
2 # include <zmq.hpp>
All clients use the same JSON message protocol defined in the
MQL5-ZMQ Library .
Version History
Version Date Changes 2.00 2026-01-27 Added order handling, position tracking, full async support 1.00 2026-01-20 Initial release with tick subscription
License
MIT License
Copyright (c) 2026 Albeos Rembrant
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
References
Citation
If you use this library in your research or project, please cite:
1 @software{rembrant2026sum3api,
2 author = {Rembrant Oyangoren Albeos},
3 title = {{SUM3API}: Using Rust, ZeroMQ, and MetaQuotes Language (MQL5) API Combination to Extract, Communicate, and Externally Project Financial Data from MetaTrader 5 (MT5)},
4 year = {2026},
5 publisher = {Hugging Face},
6 url = {https://huggingface.co/ContinualQuasars/SUM3API},
7 version = {2.0.0}
8 }