| # 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 |
|
|
| 1. [System Overview](#system-overview) |
| 2. [Complete Architecture Diagram](#complete-architecture-diagram) |
| 3. [Security Architecture](#security-architecture) |
| 4. [Component Deep Dive](#component-deep-dive) |
| 5. [Data Flow & Communication Patterns](#data-flow--communication-patterns) |
| 6. [Account Information Fetching](#account-information-fetching) |
| 7. [Complete Data Structures](#complete-data-structures) |
| 8. [ZeroMQ Layer Details](#zeromq-layer-details) |
| 9. [Async Task Management](#async-task-management) |
| 10. [File Structure & Dependencies](#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 |
|
|
| 1. **Security First**: No credentials in code or transmitted over network |
| 2. **Real-time Performance**: Tick-level granularity with minimal latency |
| 3. **Separation of Concerns**: Authentication vs. Trading logic |
| 4. **Async Architecture**: Non-blocking I/O for maximum throughput |
| 5. **Type Safety**: Strong typing in both MQL5 and Rust |
|
|
| --- |
|
|
| ## Complete Architecture Diagram |
|
|
| ### High-Level System Architecture |
|
|
| ```mermaid |
| flowchart TB |
| subgraph USER_SPACE["User Space"] |
| USER[("User")] |
| end |
| |
| subgraph MT5_PLATFORM["MetaTrader 5 Platform (Authenticated Process)"] |
| direction TB |
| |
| subgraph AUTH["Authentication Layer"] |
| MT5_GUI[MT5 Terminal GUI] |
| SESSION["Authenticated Session<br/>[+] Account ID<br/>[+] Server Connection<br/>[+] Trading Permissions"] |
| end |
| |
| subgraph DATA_SOURCES["MT5 Data Sources"] |
| direction TB |
| MARKET[("Market Data Feed<br/>Tick Stream")] |
| ACCOUNT_DB[("Account Database<br/>ACCOUNT_BALANCE<br/>ACCOUNT_EQUITY<br/>ACCOUNT_MARGIN<br/>ACCOUNT_MARGIN_FREE")] |
| POSITIONS_DB[("Positions Database<br/>Active Trades")] |
| ORDERS_DB[("Orders Database<br/>Pending Orders")] |
| HISTORY_DB[("Historical Database<br/>OHLC & Tick Data")] |
| end |
| |
| subgraph EA_LAYER["Expert Advisor Layer"] |
| EA[ZmqPublisher.mq5<br/>Expert Advisor] |
| TRADE_ENGINE[CTrade Engine<br/>Order Execution] |
| end |
| |
| USER -->|1. Manual Login<br/>account + password + server| MT5_GUI |
| MT5_GUI --> SESSION |
| SESSION -.->|Inherits Session| EA |
| |
| MARKET --> EA |
| ACCOUNT_DB --> EA |
| POSITIONS_DB --> EA |
| ORDERS_DB --> EA |
| HISTORY_DB --> EA |
| EA --> TRADE_ENGINE |
| end |
| |
| subgraph ZMQ_LAYER["ZeroMQ Transport Layer (localhost)"] |
| direction TB |
| PUB_SOCKET[["[PUB] PUB Socket<br/>tcp://0.0.0.0:5555<br/>Broadcast Mode"]] |
| REP_SOCKET[["[REP] REP Socket<br/>tcp://0.0.0.0:5556<br/>Request-Reply Mode"]] |
| end |
| |
| subgraph RUST_APP["Rust Application (mt5-chart)"] |
| direction TB |
| |
| subgraph ASYNC_RUNTIME["Tokio Async Runtime"] |
| TICK_TASK[["[Task] Tick Subscriber Task<br/>SubSocket<br/>Port 5555"]] |
| ORDER_TASK[["[Task] Order Handler Task<br/>ReqSocket<br/>Port 5556"]] |
| end |
| |
| subgraph CHANNELS["MPSC Channels"] |
| direction TB |
| TICK_CHAN[Tick Channel<br/>capacity: 100] |
| ORDER_CHAN[Order Request Channel<br/>capacity: 10] |
| RESPONSE_CHAN[Order Response Channel<br/>capacity: 10] |
| end |
| |
| subgraph APP_STATE["Application State"] |
| STATE[Mt5ChartApp<br/>β’ data: Vec<TickData><br/>β’ balance, equity, margin<br/>β’ positions, orders<br/>β’ UI state] |
| end |
| |
| subgraph GUI["egui GUI Components"] |
| direction TB |
| CHART[["[Chart] Price Chart<br/>Bid/Ask Lines<br/>Position Lines<br/>Order Breaklines"]] |
| ACCOUNT_PANEL[["[Account] Account Info Panel<br/>Balance, Equity<br/>Margin, Free Margin"]] |
| TRADE_PANEL[["[Trade] Trade Controls<br/>Market Orders<br/>Pending Orders"]] |
| HISTORY_PANEL[["[History] History Download<br/>OHLC/Tick CSV Export"]] |
| RECORD_PANEL[["[REC] Live Recording<br/>Real-time CSV Capture"]] |
| POSITIONS_PANEL[["[Pos] Active Positions<br/>Close Management"]] |
| ORDERS_PANEL[["[Orders] Pending Orders<br/>Cancel Management"]] |
| end |
| |
| TICK_TASK --> TICK_CHAN |
| ORDER_TASK <--> ORDER_CHAN |
| ORDER_TASK <--> RESPONSE_CHAN |
| |
| TICK_CHAN --> STATE |
| STATE <--> ORDER_CHAN |
| RESPONSE_CHAN --> STATE |
| |
| STATE --> CHART |
| STATE --> ACCOUNT_PANEL |
| STATE --> TRADE_PANEL |
| STATE --> HISTORY_PANEL |
| STATE --> RECORD_PANEL |
| STATE --> POSITIONS_PANEL |
| STATE --> ORDERS_PANEL |
| end |
| |
| EA --> PUB_SOCKET |
| EA <--> REP_SOCKET |
| |
| PUB_SOCKET -.->|JSON Tick Stream<br/>Non-blocking| TICK_TASK |
| ORDER_TASK -.->|JSON Request<br/>Blocking| REP_SOCKET |
| REP_SOCKET -.->|JSON Response<br/>Blocking| ORDER_TASK |
| |
| style USER_SPACE fill:#f0f0f0,stroke:#666,stroke-width:2px |
| style MT5_PLATFORM fill:#e6f3ff,stroke:#0066cc,stroke-width:3px |
| style AUTH fill:#fff9e6,stroke:#ffcc00,stroke-width:2px |
| style ZMQ_LAYER fill:#f0fff0,stroke:#00cc00,stroke-width:3px |
| style RUST_APP fill:#ffe6f0,stroke:#cc0066,stroke-width:3px |
| style SESSION fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| ``` |
|
|
| --- |
|
|
| ## Security Architecture |
|
|
| ### Authentication Flow & Credential Isolation |
|
|
| ```mermaid |
| sequenceDiagram |
| participant User |
| participant MT5_GUI as MT5 Terminal GUI |
| participant Broker as Broker Server |
| participant Session as Authenticated Session |
| participant EA as MQL5 Expert Advisor |
| participant ZMQ as ZeroMQ Sockets |
| participant Rust as Rust Application |
| |
| rect rgb(255, 240, 200) |
| Note over User,Session: Phase 1: One-Time Authentication (Manual) |
| User->>MT5_GUI: Enter credentials<br/>β’ Account ID: 12345678<br/>β’ Password: ********<br/>β’ Server: MetaQuotes-Demo |
| MT5_GUI->>Broker: Authenticate |
| Broker-->>MT5_GUI: [+] Authentication Success |
| MT5_GUI->>Session: Create Authenticated Session |
| Note over Session: Session stores:<br/>[+] Account credentials<br/>[+] Server connection<br/>[+] Trading permissions<br/>[+] Account state |
| end |
| |
| rect rgb(230, 255, 230) |
| Note over Session,EA: Phase 2: EA Initialization (Session Inheritance) |
| User->>MT5_GUI: Attach EA to chart |
| MT5_GUI->>EA: OnInit() |
| EA->>Session: Request session access |
| Session-->>EA: [+] Grant access (no credentials needed) |
| Note over EA: EA now has:<br/>[+] Authenticated session<br/>[+] Account info access<br/>[+] Trading permissions<br/>[-] NO credentials stored |
| end |
| |
| rect rgb(230, 240, 255) |
| Note over EA,Rust: Phase 3: External Communication (Credential-Free) |
| EA->>ZMQ: Bind PUB socket (port 5555) |
| EA->>ZMQ: Bind REP socket (port 5556) |
| Rust->>ZMQ: Connect SUB socket (127.0.0.1:5555) |
| Rust->>ZMQ: Connect REQ socket (127.0.0.1:5556) |
| Note over ZMQ,Rust: [+] Only localhost TCP addresses<br/>[-] NO credentials transmitted<br/>[-] NO authentication required |
| end |
| |
| rect rgb(255, 230, 230) |
| Note over EA,Rust: Phase 4: Runtime Operations (Secure) |
| loop Every Tick |
| EA->>Session: AccountInfoDouble(ACCOUNT_BALANCE) |
| Session-->>EA: balance value |
| EA->>Session: AccountInfoDouble(ACCOUNT_EQUITY) |
| Session-->>EA: equity value |
| EA->>ZMQ: Publish JSON {balance, equity, ...} |
| ZMQ-->>Rust: Receive data (no auth needed) |
| end |
| |
| Rust->>ZMQ: Send order request {type: "market_buy", ...} |
| ZMQ-->>EA: Receive request |
| EA->>Session: Execute trade via CTrade |
| Session-->>EA: Trade result |
| EA->>ZMQ: Send response {success: true, ticket: ...} |
| ZMQ-->>Rust: Receive response |
| 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 |
|
|
| ```mermaid |
| flowchart LR |
| subgraph MT5["MT5 Authenticated Session"] |
| ACC_API["Account Info API<br/>AccountInfoDouble()"] |
| ACC_DATA[(Account Data<br/>ACCOUNT_BALANCE<br/>ACCOUNT_EQUITY<br/>ACCOUNT_MARGIN<br/>ACCOUNT_MARGIN_FREE)] |
| end |
| |
| subgraph EA["Expert Advisor"] |
| FETCH[Fetch Account Info<br/>Lines 366-369] |
| JSON_BUILD[Build JSON Payload<br/>Lines 428-443] |
| end |
| |
| subgraph ZMQ["ZeroMQ"] |
| PUB[PUB Socket<br/>Port 5555] |
| end |
| |
| subgraph RUST["Rust App"] |
| PARSE[Parse JSON<br/>Lines 745-753] |
| UPDATE[Update State<br/>Lines 338-348] |
| DISPLAY[Display in GUI<br/>Lines 449-466] |
| end |
| |
| ACC_API --> ACC_DATA |
| ACC_DATA -->|No credentials needed| FETCH |
| FETCH --> JSON_BUILD |
| JSON_BUILD --> PUB |
| PUB -.->|JSON over TCP| PARSE |
| PARSE --> UPDATE |
| UPDATE --> DISPLAY |
| |
| style ACC_DATA fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| style FETCH fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style PUB fill:#fff9e6,stroke:#ffcc00,stroke-width:2px |
| 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 |
|
|
| ```mql5 |
| input string InpPubAddress = "tcp://0.0.0.0:5555"; // Tick Publisher Address |
| input string InpRepAddress = "tcp://0.0.0.0:5556"; // Order Handler Address |
| input double InpDefaultSlippage = 10; // Default Slippage (points) |
| ``` |
|
|
| #### Global Variables |
|
|
| ```mql5 |
| CZmq *g_publisher; // PUB socket for tick data broadcasting |
| CZmq *g_responder; // REP socket for order request handling |
| CTrade g_trade; // MT5 trading helper class |
| ``` |
|
|
| #### Initialization Sequence (OnInit) |
|
|
| ```mermaid |
| flowchart TD |
| START([OnInit Called]) --> INIT_PUB[Create CZmq Publisher] |
| INIT_PUB --> PUB_INIT{Init ZMQ_PUB?} |
| PUB_INIT -->|Failed| FAIL1[Return INIT_FAILED] |
| PUB_INIT -->|Success| PUB_BIND{Bind to Port 5555?} |
| PUB_BIND -->|Failed| FAIL2[Return INIT_FAILED] |
| PUB_BIND -->|Success| INIT_REP[Create CZmq Responder] |
| |
| INIT_REP --> REP_INIT{Init ZMQ_REP?} |
| REP_INIT -->|Failed| FAIL3[Return INIT_FAILED] |
| REP_INIT -->|Success| REP_BIND{Bind to Port 5556?} |
| REP_BIND -->|Failed| FAIL4[Return INIT_FAILED] |
| REP_BIND -->|Success| CONFIG_TRADE[Configure CTrade] |
| |
| CONFIG_TRADE --> SET_SLIP[SetDeviationInPoints] |
| SET_SLIP --> SET_FILL[SetTypeFilling IOC] |
| SET_FILL --> SUCCESS[Return INIT_SUCCEEDED] |
| |
| style START fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style SUCCESS fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| style FAIL1 fill:#ffcccc,stroke:#cc0000,stroke-width:2px |
| style FAIL2 fill:#ffcccc,stroke:#cc0000,stroke-width:2px |
| style FAIL3 fill:#ffcccc,stroke:#cc0000,stroke-width:2px |
| style FAIL4 fill:#ffcccc,stroke:#cc0000,stroke-width:2px |
| ``` |
|
|
| #### OnTick() Processing Flow |
|
|
| ```mermaid |
| flowchart TB |
| TICK([OnTick Event]) --> CHECK_REQ{Check REP Socket<br/>Non-blocking} |
| |
| CHECK_REQ -->|Request Available| RECV_REQ[Receive Request JSON] |
| RECV_REQ --> PROCESS[ProcessOrderRequest] |
| PROCESS --> SEND_RESP[Send Response JSON<br/>Blocking] |
| SEND_RESP --> CHECK_PUB |
| |
| CHECK_REQ -->|No Request| CHECK_PUB{Check Publisher} |
| |
| CHECK_PUB -->|NULL| END([Return]) |
| CHECK_PUB -->|Valid| GET_TICK[SymbolInfoTick] |
| |
| GET_TICK --> GET_ACCOUNT[Get Account Info<br/>Lines 366-369] |
| GET_ACCOUNT --> GET_CONSTRAINTS[Get Symbol Constraints<br/>Lines 372-374] |
| GET_CONSTRAINTS --> GET_POSITIONS[Get Active Positions<br/>Lines 377-397] |
| GET_POSITIONS --> GET_ORDERS[Get Pending Orders<br/>Lines 400-425] |
| GET_ORDERS --> BUILD_JSON[Build Complete JSON<br/>Lines 428-443] |
| BUILD_JSON --> PUBLISH[Publish to PUB Socket<br/>Line 445] |
| PUBLISH --> END |
| |
| style TICK fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style GET_ACCOUNT fill:#fff9e6,stroke:#ffcc00,stroke-width:2px |
| style PUBLISH fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| ``` |
|
|
| #### Account Information Fetching (Detailed) |
|
|
| **Lines 366-369: Account Info Retrieval** |
|
|
| ```mql5 |
| // Get account info |
| double balance = AccountInfoDouble(ACCOUNT_BALANCE); |
| double equity = AccountInfoDouble(ACCOUNT_EQUITY); |
| double margin = AccountInfoDouble(ACCOUNT_MARGIN); |
| 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) |
|
|
| ```mql5 |
| // Get symbol trading constraints |
| double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); |
| double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); |
| double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); |
| ``` |
|
|
| #### Position Fetching Loop (Lines 377-397) |
|
|
| ```mql5 |
| // Get Active Positions (Only for current symbol to simplify) |
| string positionsJson = "["; |
| int posCount = PositionsTotal(); |
| bool firstPos = true; |
| for(int i = 0; i < posCount; i++) { |
| ulong ticket = PositionGetTicket(i); |
| if(PositionSelectByTicket(ticket)) { |
| if(PositionGetString(POSITION_SYMBOL) == _Symbol) { |
| if(!firstPos) StringAdd(positionsJson, ","); |
| |
| string posType = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) ? "BUY" : "SELL"; |
| StringAdd(positionsJson, "{\"ticket\":" + IntegerToString(ticket) + |
| ",\"type\":\"" + posType + "\"" + |
| ",\"volume\":" + DoubleToString(PositionGetDouble(POSITION_VOLUME), 2) + |
| ",\"price\":" + DoubleToString(PositionGetDouble(POSITION_PRICE_OPEN), _Digits) + |
| ",\"profit\":" + DoubleToString(PositionGetDouble(POSITION_PROFIT), 2) + |
| "}"); |
| firstPos = false; |
| } |
| } |
| } |
| StringAdd(positionsJson, "]"); |
| ``` |
|
|
| #### Order Request Processing (Lines 87-188) |
|
|
| ```mermaid |
| flowchart TD |
| START([ProcessOrderRequest]) --> PARSE[Parse JSON Request<br/>Extract: type, symbol, volume, price, ticket] |
| |
| PARSE --> ROUTE{Route by Type} |
| |
| ROUTE -->|market_buy| MB[Get ASK price<br/>g_trade.Buy] |
| ROUTE -->|market_sell| MS[Get BID price<br/>g_trade.Sell] |
| ROUTE -->|limit_buy| LB[g_trade.BuyLimit] |
| ROUTE -->|limit_sell| LS[g_trade.SellLimit] |
| ROUTE -->|stop_buy| SB[g_trade.BuyStop] |
| ROUTE -->|stop_sell| SS[g_trade.SellStop] |
| ROUTE -->|close_position| CP[g_trade.PositionClose] |
| ROUTE -->|cancel_order| CO[g_trade.OrderDelete] |
| ROUTE -->|download_history| DH[DownloadHistory] |
| ROUTE -->|unknown| ERR[Unknown order type] |
| |
| MB --> CHECK{Success?} |
| MS --> CHECK |
| LB --> CHECK |
| LS --> CHECK |
| SB --> CHECK |
| SS --> CHECK |
| CP --> CHECK |
| CO --> CHECK |
| DH --> CHECK |
| ERR --> BUILD_FAIL |
| |
| CHECK -->|Yes| BUILD_SUCCESS["Build Success JSON<br/>{success: true, ticket: ...}"] |
| CHECK -->|No| BUILD_FAIL["Build Failure JSON<br/>{success: false, error: ...}"] |
| |
| BUILD_SUCCESS --> RETURN[Return JSON Response] |
| BUILD_FAIL --> RETURN |
| |
| style START fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style BUILD_SUCCESS fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| 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 |
|
|
| ```mermaid |
| classDiagram |
| class CZmq { |
| -long m_context |
| -long m_socket |
| -bool m_initialized |
| +CZmq() |
| +~CZmq() |
| +bool Init(int type) |
| +bool Bind(string endpoint) |
| +bool Connect(string endpoint) |
| +int Send(string message, bool nonBlocking) |
| +string Receive(bool nonBlocking) |
| +void Shutdown() |
| } |
| |
| class libzmq_dll { |
| <<external>> |
| +long zmq_ctx_new() |
| +int zmq_ctx_term(long context) |
| +long zmq_socket(long context, int type) |
| +int zmq_close(long socket) |
| +int zmq_bind(long socket, uchar endpoint[]) |
| +int zmq_connect(long socket, uchar endpoint[]) |
| +int zmq_send(long socket, uchar buf[], int len, int flags) |
| +int zmq_recv(long socket, uchar buf[], int len, int flags) |
| +int zmq_errno() |
| } |
| |
| CZmq --> libzmq_dll : imports |
| ``` |
|
|
| #### Socket Type Constants |
|
|
| ```mql5 |
| #define ZMQ_PUB 1 // Publisher socket (one-to-many) |
| #define ZMQ_SUB 2 // Subscriber socket (many-to-one) |
| #define ZMQ_REQ 3 // Request socket (synchronous client) |
| #define ZMQ_REP 4 // Reply socket (synchronous server) |
| #define ZMQ_NOBLOCK 1 // Non-blocking flag |
| ``` |
|
|
| #### Method Details |
|
|
| **Init(int type)** - Lines 51-68 |
| ```mql5 |
| bool Init(int type) { |
| if(m_initialized) return true; |
| |
| m_context = zmq_ctx_new(); // Create ZMQ context |
| if(m_context == 0) { |
| Print("ZMQ Init failed: Context creation error"); |
| return false; |
| } |
| |
| m_socket = zmq_socket(m_context, type); // Create socket of specified type |
| if(m_socket == 0) { |
| Print("ZMQ Init failed: Socket creation error"); |
| return false; |
| } |
| |
| m_initialized = true; |
| return true; |
| } |
| ``` |
|
|
| **Send(string message, bool nonBlocking)** - Lines 98-114 |
| ```mql5 |
| int Send(string message, bool nonBlocking = true) { |
| if(!m_initialized) return -1; |
| |
| uchar data[]; |
| StringToCharArray(message, data, 0, WHOLE_ARRAY, CP_UTF8); |
| int len = ArraySize(data) - 1; // Exclude null terminator |
| if (len < 0) len = 0; |
| |
| int flags = 0; |
| if(nonBlocking) flags = ZMQ_NOBLOCK; |
| |
| int bytesSent = zmq_send(m_socket, data, len, flags); |
| return bytesSent; |
| } |
| ``` |
|
|
| **Receive(bool nonBlocking)** - Lines 117-131 |
| ```mql5 |
| string Receive(bool nonBlocking = true) { |
| if(!m_initialized) return ""; |
| |
| uchar buffer[4096]; |
| ArrayInitialize(buffer, 0); |
| |
| int flags = 0; |
| if(nonBlocking) flags = ZMQ_NOBLOCK; |
| |
| int bytesReceived = zmq_recv(m_socket, buffer, ArraySize(buffer) - 1, flags); |
| |
| if(bytesReceived <= 0) return ""; |
| |
| return CharArrayToString(buffer, 0, bytesReceived, CP_UTF8); |
| } |
| ``` |
|
|
| ### 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) |
|
|
| ```toml |
| [dependencies] |
| eframe = "0.27" # egui framework |
| egui = "0.27" # Immediate mode GUI |
| egui_plot = "0.27" # Plotting library |
| serde = { version = "1.0", features = ["derive"] } |
| serde_json = "1.0" # JSON serialization |
| tokio = { version = "1", features = ["full"] } |
| zeromq = "0.3" # ZeroMQ bindings |
| chrono = "0.4" # Date/time handling |
| ``` |
|
|
| #### Data Structure Hierarchy |
|
|
| ```mermaid |
| classDiagram |
| class TickData { |
| +String symbol |
| +f64 bid |
| +f64 ask |
| +i64 time |
| +u64 volume |
| +f64 balance |
| +f64 equity |
| +f64 margin |
| +f64 free_margin |
| +f64 min_lot |
| +f64 max_lot |
| +f64 lot_step |
| +Vec~PositionData~ positions |
| +Vec~PendingOrderData~ orders |
| } |
| |
| class PositionData { |
| +u64 ticket |
| +String pos_type |
| +f64 volume |
| +f64 price |
| +f64 profit |
| } |
| |
| class PendingOrderData { |
| +u64 ticket |
| +String order_type |
| +f64 volume |
| +f64 price |
| } |
| |
| class OrderRequest { |
| +String order_type |
| +String symbol |
| +f64 volume |
| +f64 price |
| +u64 ticket |
| +Option~String~ timeframe |
| +Option~String~ start |
| +Option~String~ end |
| +Option~String~ mode |
| +Option~u64~ request_id |
| } |
| |
| class OrderResponse { |
| +bool success |
| +Option~i64~ ticket |
| +Option~String~ error |
| +Option~String~ message |
| } |
| |
| class OrderBreakline { |
| +usize index |
| +String order_type |
| +i64 ticket |
| } |
| |
| class Mt5ChartApp { |
| +Receiver~TickData~ tick_receiver |
| +Vec~TickData~ data |
| +String symbol |
| +f64 balance |
| +f64 equity |
| +f64 margin |
| +f64 free_margin |
| +Sender~OrderRequest~ order_sender |
| +Receiver~OrderResponse~ response_receiver |
| +Vec~PositionData~ positions |
| +Vec~PendingOrderData~ pending_orders |
| +Vec~OrderBreakline~ order_breaklines |
| +update() |
| +send_order() |
| +send_download_request() |
| } |
| |
| TickData "1" *-- "*" PositionData |
| TickData "1" *-- "*" PendingOrderData |
| Mt5ChartApp "1" *-- "*" TickData |
| Mt5ChartApp "1" *-- "*" OrderBreakline |
| ``` |
|
|
| --- |
|
|
| ## Data Flow & Communication Patterns |
|
|
| ### Complete Tick Data Flow |
|
|
| ```mermaid |
| sequenceDiagram |
| participant MT5 as MT5 Market |
| participant EA as ZmqPublisher.mq5 |
| participant PUB as PUB Socket :5555 |
| participant SUB as SUB Socket (Rust) |
| participant CHAN as Tick Channel |
| participant APP as Mt5ChartApp |
| participant GUI as egui GUI |
| |
| rect rgb(230, 255, 230) |
| Note over MT5,EA: Every Tick Event |
| MT5->>EA: OnTick() |
| |
| EA->>EA: SymbolInfoTick(_Symbol, tick) |
| EA->>EA: AccountInfoDouble(ACCOUNT_BALANCE) |
| EA->>EA: AccountInfoDouble(ACCOUNT_EQUITY) |
| EA->>EA: AccountInfoDouble(ACCOUNT_MARGIN) |
| EA->>EA: AccountInfoDouble(ACCOUNT_MARGIN_FREE) |
| EA->>EA: SymbolInfoDouble(SYMBOL_VOLUME_MIN/MAX/STEP) |
| |
| loop For each position |
| EA->>EA: PositionGetTicket(i) |
| EA->>EA: Build position JSON |
| end |
| |
| loop For each order |
| EA->>EA: OrderGetTicket(i) |
| EA->>EA: Build order JSON |
| end |
| |
| EA->>EA: StringConcatenate(json, ...) |
| EA->>PUB: Send(json, non-blocking) |
| end |
| |
| rect rgb(230, 240, 255) |
| Note over PUB,APP: Async Rust Processing |
| PUB-->>SUB: TCP transmission |
| SUB->>SUB: recv().await |
| SUB->>SUB: serde_json::from_str::<TickData>() |
| SUB->>CHAN: tick_tx.send(tick).await |
| |
| CHAN-->>APP: tick_receiver.try_recv() |
| APP->>APP: Update balance, equity, margin |
| APP->>APP: Update positions, orders |
| APP->>APP: data.push(tick) |
| APP->>APP: Record to CSV if recording |
| end |
| |
| rect rgb(255, 240, 230) |
| Note over APP,GUI: GUI Update (60 FPS) |
| APP->>GUI: update(&mut self, ctx, frame) |
| GUI->>GUI: Draw price chart |
| GUI->>GUI: Draw account panel |
| GUI->>GUI: Draw positions/orders |
| GUI->>GUI: ctx.request_repaint() |
| end |
| ``` |
|
|
| ### Complete Order Execution Flow |
|
|
| ```mermaid |
| sequenceDiagram |
| participant GUI as egui GUI |
| participant APP as Mt5ChartApp |
| participant CHAN as Order Channel |
| participant REQ as REQ Socket (Rust) |
| participant REP as REP Socket :5556 |
| participant EA as ZmqPublisher.mq5 |
| participant TRADE as CTrade Engine |
| participant MT5 as MT5 Terminal |
| |
| rect rgb(255, 240, 230) |
| Note over GUI,APP: User Interaction |
| GUI->>APP: Button clicked: "BUY" |
| APP->>APP: send_order("market_buy", None, None) |
| APP->>APP: Build OrderRequest struct |
| APP->>APP: serde_json::to_string(&request) |
| APP->>CHAN: order_sender.try_send(request) |
| end |
| |
| rect rgb(230, 240, 255) |
| Note over CHAN,EA: Async Order Task |
| CHAN-->>REQ: order_rx.recv().await |
| REQ->>REQ: Serialize to JSON |
| REQ->>REP: socket.send(json).await (blocking) |
| |
| REP-->>EA: Receive(non-blocking) in OnTick |
| EA->>EA: ProcessOrderRequest(request) |
| EA->>EA: ExtractJsonString(request, "type") |
| EA->>EA: ExtractJsonDouble(request, "volume") |
| end |
| |
| rect rgb(230, 255, 230) |
| Note over EA,MT5: Trade Execution |
| EA->>EA: if(orderType == "market_buy") |
| EA->>EA: askPrice = SymbolInfoDouble(SYMBOL_ASK) |
| EA->>TRADE: g_trade.Buy(volume, symbol, askPrice, 0, 0, "Rust GUI Order") |
| TRADE->>MT5: Execute market order |
| MT5-->>TRADE: Trade result |
| TRADE-->>EA: success = true, resultTicket = 12345678 |
| |
| EA->>EA: Build response JSON |
| EA->>EA: {"success":true,"ticket":12345678} |
| EA->>REP: Send(response, blocking) |
| end |
| |
| rect rgb(240, 230, 255) |
| Note over REP,APP: Response Processing |
| REP-->>REQ: socket.recv().await (blocking) |
| REQ->>REQ: serde_json::from_str::<OrderResponse>() |
| REQ->>CHAN: response_tx.send(response).await |
| |
| CHAN-->>APP: response_receiver.try_recv() |
| APP->>APP: if response.success |
| APP->>APP: Create OrderBreakline |
| APP->>APP: order_breaklines.push(breakline) |
| APP->>APP: last_order_result = "β Order executed!" |
| end |
| |
| rect rgb(255, 240, 230) |
| Note over APP,GUI: GUI Feedback |
| APP->>GUI: Update chart with breakline |
| GUI->>GUI: Draw vertical line at execution point |
| GUI->>GUI: Display success message |
| end |
| ``` |
|
|
| --- |
|
|
| ## Account Information Fetching |
|
|
| ### MQL5 Account Info API |
|
|
| ```mermaid |
| flowchart LR |
| subgraph MT5_SESSION["MT5 Authenticated Session"] |
| AUTH[Authenticated User Session] |
| ACC_STATE[(Account State<br/>β’ Balance<br/>β’ Equity<br/>β’ Margin<br/>β’ Free Margin<br/>β’ Leverage<br/>β’ Currency)] |
| end |
| |
| subgraph MQL5_API["MQL5 Account API"] |
| API1[AccountInfoDouble<br/>ACCOUNT_BALANCE] |
| API2[AccountInfoDouble<br/>ACCOUNT_EQUITY] |
| API3[AccountInfoDouble<br/>ACCOUNT_MARGIN] |
| API4[AccountInfoDouble<br/>ACCOUNT_MARGIN_FREE] |
| end |
| |
| subgraph EA_CODE["Expert Advisor Code"] |
| 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);"] |
| end |
| |
| AUTH --> ACC_STATE |
| ACC_STATE --> API1 |
| ACC_STATE --> API2 |
| ACC_STATE --> API3 |
| ACC_STATE --> API4 |
| |
| API1 --> FETCH |
| API2 --> FETCH |
| API3 --> FETCH |
| API4 --> FETCH |
| |
| style AUTH fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| style ACC_STATE fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style FETCH fill:#fff9e6,stroke:#ffcc00,stroke-width:2px |
| ``` |
|
|
| ### Account Info Constants (MQL5) |
|
|
| | Constant | Type | Description | |
| |----------|------|-------------| |
| | `ACCOUNT_BALANCE` | double | Account balance in deposit currency | |
| | `ACCOUNT_EQUITY` | double | Account equity (balance + floating P/L) | |
| | `ACCOUNT_MARGIN` | double | Margin currently used | |
| | `ACCOUNT_MARGIN_FREE` | double | Free margin available for trading | |
| | `ACCOUNT_MARGIN_LEVEL` | double | Margin level percentage | |
| | `ACCOUNT_PROFIT` | double | Current profit on all positions | |
| | `ACCOUNT_CREDIT` | double | Credit amount | |
| | `ACCOUNT_LEVERAGE` | long | Account leverage (e.g., 100 for 1:100) | |
| | `ACCOUNT_CURRENCY` | string | Account currency (e.g., "USD") | |
|
|
| ### Rust Account Info Reception |
|
|
| **Lines 338-348: Account Info Update** |
|
|
| ```rust |
| // Update account info from latest tick |
| if tick.balance > 0.0 { |
| self.balance = tick.balance; |
| self.equity = tick.equity; |
| self.margin = tick.margin; |
| self.free_margin = tick.free_margin; |
| self.min_lot = tick.min_lot; |
| self.max_lot = tick.max_lot; |
| if tick.lot_step > 0.0 { |
| self.lot_step = tick.lot_step; |
| } |
| } |
| ``` |
|
|
| **Lines 449-466: Account Info Display** |
|
|
| ```rust |
| ui.collapsing("Account Info", |ui| { |
| egui::Grid::new("account_grid") |
| .num_columns(2) |
| .spacing([10.0, 4.0]) |
| .show(ui, |ui| { |
| ui.label("Balance:"); |
| ui.colored_label(egui::Color32::from_rgb(100, 200, 100), format!("${:.2}", self.balance)); |
| ui.end_row(); |
| ui.label("Equity:"); |
| ui.colored_label(egui::Color32::from_rgb(100, 180, 255), format!("${:.2}", self.equity)); |
| ui.end_row(); |
| ui.label("Margin Used:"); |
| ui.colored_label(egui::Color32::from_rgb(255, 200, 100), format!("${:.2}", self.margin)); |
| ui.end_row(); |
| ui.label("Free Margin:"); |
| ui.colored_label(egui::Color32::from_rgb(100, 255, 200), format!("${:.2}", self.free_margin)); |
| ui.end_row(); |
| }); |
| }); |
| ``` |
|
|
| --- |
|
|
| ## Complete Data Structures |
|
|
| ### JSON Tick Data Format (PUB/SUB Port 5555) |
|
|
| ```json |
| { |
| "symbol": "XAUUSDc", |
| "bid": 2650.55, |
| "ask": 2650.75, |
| "time": 1706284800, |
| "volume": 100, |
| "balance": 10000.00, |
| "equity": 10150.25, |
| "margin": 500.00, |
| "free_margin": 9650.25, |
| "min_lot": 0.01, |
| "max_lot": 100.00, |
| "lot_step": 0.01, |
| "positions": [ |
| { |
| "ticket": 12345678, |
| "type": "BUY", |
| "volume": 0.10, |
| "price": 2645.50, |
| "profit": 50.50 |
| }, |
| { |
| "ticket": 12345679, |
| "type": "SELL", |
| "volume": 0.05, |
| "price": 2655.00, |
| "profit": -25.00 |
| } |
| ], |
| "orders": [ |
| { |
| "ticket": 87654321, |
| "type": "BUY LIMIT", |
| "volume": 0.05, |
| "price": 2600.00 |
| }, |
| { |
| "ticket": 87654322, |
| "type": "SELL STOP", |
| "volume": 0.10, |
| "price": 2700.00 |
| } |
| ] |
| } |
| ``` |
|
|
| ### JSON Order Request Format (REQ/REP Port 5556) |
|
|
| **Market Order Request**: |
| ```json |
| { |
| "type": "market_buy", |
| "symbol": "XAUUSDc", |
| "volume": 0.01, |
| "price": 0.0, |
| "ticket": 0 |
| } |
| ``` |
|
|
| **Pending Order Request**: |
| ```json |
| { |
| "type": "limit_buy", |
| "symbol": "XAUUSDc", |
| "volume": 0.05, |
| "price": 2600.00, |
| "ticket": 0 |
| } |
| ``` |
|
|
| **Close Position Request**: |
| ```json |
| { |
| "type": "close_position", |
| "symbol": "XAUUSDc", |
| "volume": 0.0, |
| "price": 0.0, |
| "ticket": 12345678 |
| } |
| ``` |
|
|
| **History Download Request**: |
| ```json |
| { |
| "type": "download_history", |
| "symbol": "XAUUSDc", |
| "volume": 0.0, |
| "price": 0.0, |
| "ticket": 0, |
| "timeframe": "M1", |
| "start": "2024.01.01", |
| "end": "2024.01.31", |
| "mode": "OHLC", |
| "request_id": 1 |
| } |
| ``` |
|
|
| ### JSON Order Response Format |
|
|
| **Success Response**: |
| ```json |
| { |
| "success": true, |
| "ticket": 12345678 |
| } |
| ``` |
|
|
| **Failure Response**: |
| ```json |
| { |
| "success": false, |
| "error": "Error 10019: Not enough money" |
| } |
| ``` |
|
|
| **History Download Success Response**: |
| ```json |
| { |
| "success": true, |
| "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|..." |
| } |
| ``` |
|
|
| --- |
|
|
| ## ZeroMQ Layer Details |
|
|
| ### Socket Patterns |
|
|
| ```mermaid |
| flowchart TB |
| subgraph PUB_SUB["PUB/SUB Pattern (Port 5555)"] |
| direction LR |
| PUB[Publisher<br/>ZmqPublisher.mq5] |
| SUB1[Subscriber 1<br/>Rust App] |
| SUB2[Subscriber 2<br/>Other Apps] |
| |
| PUB -->|Broadcast| SUB1 |
| PUB -->|Broadcast| SUB2 |
| end |
| |
| subgraph REQ_REP["REQ/REP Pattern (Port 5556)"] |
| direction LR |
| REQ[Request<br/>Rust App] |
| REP[Reply<br/>ZmqPublisher.mq5] |
| |
| REQ <-->|Synchronous| REP |
| end |
| |
| style PUB fill:#ccffcc,stroke:#00cc00,stroke-width:2px |
| style REP fill:#ffe6cc,stroke:#ff9900,stroke-width:2px |
| ``` |
|
|
| ### Socket Configuration |
|
|
| **PUB Socket (EA Side)**: |
| ```mql5 |
| g_publisher = new CZmq(); |
| g_publisher.Init(ZMQ_PUB); |
| g_publisher.Bind("tcp://0.0.0.0:5555"); // Bind to all interfaces |
| g_publisher.Send(json, true); // Non-blocking send |
| ``` |
|
|
| **SUB Socket (Rust Side)**: |
| ```rust |
| let mut socket = zeromq::SubSocket::new(); |
| socket.connect("tcp://127.0.0.1:5555").await; // Connect to localhost |
| socket.subscribe("").await; // Subscribe to all messages |
| let msg = socket.recv().await; // Blocking receive |
| ``` |
|
|
| **REP Socket (EA Side)**: |
| ```mql5 |
| g_responder = new CZmq(); |
| g_responder.Init(ZMQ_REP); |
| g_responder.Bind("tcp://0.0.0.0:5556"); // Bind to all interfaces |
| string request = g_responder.Receive(true); // Non-blocking receive |
| g_responder.Send(response, false); // Blocking send (REP pattern) |
| ``` |
|
|
| **REQ Socket (Rust Side)**: |
| ```rust |
| let mut socket = zeromq::ReqSocket::new(); |
| socket.connect("tcp://127.0.0.1:5556").await; // Connect to localhost |
| socket.send(json_request.into()).await; // Blocking send |
| let msg = socket.recv().await; // Blocking receive |
| ``` |
|
|
| --- |
|
|
| ## Async Task Management |
|
|
| ### Tokio Runtime Architecture |
|
|
| ```mermaid |
| flowchart TB |
| subgraph TOKIO["Tokio Async Runtime"] |
| MAIN[tokio::main] |
| |
| subgraph TASKS["Spawned Tasks"] |
| TICK_TASK[Tick Subscriber Task<br/>Lines 731-763] |
| ORDER_TASK[Order Handler Task<br/>Lines 768-835] |
| end |
| |
| subgraph CHANNELS["MPSC Channels"] |
| TICK_CH[Tick Channel<br/>capacity: 100] |
| ORDER_CH[Order Channel<br/>capacity: 10] |
| RESP_CH[Response Channel<br/>capacity: 10] |
| end |
| end |
| |
| subgraph EGUI["eframe GUI (Blocking)"] |
| APP[Mt5ChartApp::update] |
| end |
| |
| MAIN --> TICK_TASK |
| MAIN --> ORDER_TASK |
| MAIN --> EGUI |
| |
| TICK_TASK --> TICK_CH |
| ORDER_TASK <--> ORDER_CH |
| ORDER_TASK <--> RESP_CH |
| |
| TICK_CH --> APP |
| APP --> ORDER_CH |
| RESP_CH --> APP |
| |
| style TOKIO fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| style EGUI fill:#ffe6f0,stroke:#cc0066,stroke-width:2px |
| ``` |
|
|
| ### Tick Subscriber Task (Lines 731-763) |
|
|
| ```rust |
| tokio::spawn(async move { |
| let mut socket = zeromq::SubSocket::new(); |
| match socket.connect("tcp://127.0.0.1:5555").await { |
| Ok(_) => println!("Connected to ZMQ Tick Publisher on port 5555"), |
| Err(e) => eprintln!("Failed to connect to ZMQ tick publisher: {}", e), |
| } |
| |
| let _ = socket.subscribe("").await; |
| |
| loop { |
| match socket.recv().await { |
| Ok(msg) => { |
| if let Some(payload_bytes) = msg.get(0) { |
| if let Ok(json_str) = std::str::from_utf8(payload_bytes) { |
| match serde_json::from_str::<TickData>(json_str) { |
| Ok(tick) => { |
| if let Err(e) = tick_tx.send(tick).await { |
| eprintln!("Tick channel error: {}", e); |
| break; |
| } |
| } |
| Err(e) => eprintln!("JSON Parse Error: {}. Msg: {}", e, json_str), |
| } |
| } |
| } |
| } |
| Err(e) => { |
| eprintln!("ZMQ Tick Recv Error: {}", e); |
| tokio::time::sleep(tokio::time::Duration::from_millis(1000)).await; |
| } |
| } |
| } |
| }); |
| ``` |
|
|
| ### Order Handler Task (Lines 768-835) |
|
|
| ```rust |
| tokio::spawn(async move { |
| let mut socket = zeromq::ReqSocket::new(); |
| match socket.connect("tcp://127.0.0.1:5556").await { |
| Ok(_) => println!("Connected to ZMQ Order Handler on port 5556"), |
| Err(e) => { |
| eprintln!("Failed to connect to ZMQ order handler: {}", e); |
| return; |
| } |
| } |
| |
| while let Some(order_request) = order_rx.recv().await { |
| // Serialize order request to JSON |
| let json_request = match serde_json::to_string(&order_request) { |
| Ok(json) => json, |
| Err(e) => { |
| eprintln!("Failed to serialize order request: {}", e); |
| continue; |
| } |
| }; |
| |
| println!("Sending request: {}", json_request); |
| |
| // Send request (blocking in REQ/REP pattern) |
| if let Err(e) = socket.send(json_request.into()).await { |
| eprintln!("Failed to send: {}", e); |
| let _ = response_tx.send(OrderResponse { |
| success: false, |
| ticket: None, |
| error: Some(format!("Send failed: {}", e)), |
| message: None, |
| }).await; |
| continue; |
| } |
| |
| // Wait for response (blocking in REQ/REP pattern) |
| match socket.recv().await { |
| Ok(msg) => { |
| if let Some(payload_bytes) = msg.get(0) { |
| if let Ok(json_str) = std::str::from_utf8(payload_bytes) { |
| println!("Received response: {}", json_str); |
| match serde_json::from_str::<OrderResponse>(json_str) { |
| Ok(response) => { |
| let _ = response_tx.send(response).await; |
| } |
| Err(e) => { |
| let _ = response_tx.send(OrderResponse { |
| success: false, |
| ticket: None, |
| error: Some(format!("Parse error: {}", e)), |
| message: None, |
| }).await; |
| } |
| } |
| } |
| } |
| } |
| Err(e) => { |
| eprintln!("Response recv error: {}", e); |
| let _ = response_tx.send(OrderResponse { |
| success: false, |
| ticket: None, |
| error: Some(format!("Recv failed: {}", e)), |
| message: None, |
| }).await; |
| } |
| } |
| } |
| }); |
| ``` |
|
|
| --- |
|
|
| ## 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 |
|
|
| ```mermaid |
| flowchart TB |
| subgraph MQL5_DEPS["MQL5 Dependencies"] |
| EA[ZmqPublisher.mq5] |
| ZMQ_MQH[Zmq.mqh] |
| TRADE_MQH[Trade.mqh<br/>MT5 Built-in] |
| LIBZMQ[libzmq.dll] |
| LIBSODIUM[libsodium.dll] |
| end |
| |
| subgraph RUST_DEPS["Rust Dependencies"] |
| MAIN[main.rs] |
| EFRAME[eframe 0.27] |
| EGUI[egui 0.27] |
| EGUI_PLOT[egui_plot 0.27] |
| SERDE[serde 1.0] |
| SERDE_JSON[serde_json 1.0] |
| TOKIO[tokio 1.x] |
| ZEROMQ[zeromq 0.3] |
| CHRONO[chrono 0.4] |
| end |
| |
| EA --> ZMQ_MQH |
| EA --> TRADE_MQH |
| ZMQ_MQH --> LIBZMQ |
| LIBZMQ --> LIBSODIUM |
| |
| MAIN --> EFRAME |
| MAIN --> EGUI_PLOT |
| MAIN --> SERDE |
| MAIN --> SERDE_JSON |
| MAIN --> TOKIO |
| MAIN --> ZEROMQ |
| MAIN --> CHRONO |
| EFRAME --> EGUI |
| |
| style EA fill:#e6f3ff,stroke:#0066cc,stroke-width:2px |
| 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. |
|
|
| --- |
|
|
|
|