| # 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 | |
| 1. [Overview](#overview) | |
| 2. [Architecture](#architecture) | |
| 3. [Prerequisites and Installation](#prerequisites-and-installation) | |
| 4. [API Reference](#api-reference) | |
| 5. [Usage Guide](#usage-guide) | |
| 6. [Data Structures](#data-structures) | |
| 7. [Complete Examples](#complete-examples) | |
| 8. [Error Handling](#error-handling) | |
| 9. [Best Practices](#best-practices) | |
| 10. [Integration with Other Languages](#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](MQL5-ZMQ%20Library%20for%20SUM3API.md). | |
| ### 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 | `SubSocket` | `ZMQ_PUB` | Real-time tick data streaming | | |
| | REQ/REP | `ReqSocket` | `ZMQ_REP` | Order execution and commands | | |
| --- | |
| ## Architecture | |
| ### System Integration | |
| ```mermaid | |
| flowchart TB | |
| subgraph MT5["MetaTrader 5"] | |
| EA["ZmqPublisher EA"] | |
| MQL["CZmq Wrapper"] | |
| EA --> MQL | |
| end | |
| subgraph ZMQ["ZeroMQ Layer"] | |
| PUB["PUB :5555"] | |
| REP["REP :5556"] | |
| end | |
| subgraph Rust["Rust Application"] | |
| SUB["SubSocket"] | |
| REQ["ReqSocket"] | |
| TICK_CH["Tick Channel"] | |
| ORDER_CH["Order Channel"] | |
| APP["Application Logic"] | |
| SUB --> TICK_CH | |
| TICK_CH --> APP | |
| APP --> ORDER_CH | |
| ORDER_CH --> REQ | |
| end | |
| MQL --> PUB | |
| MQL --> REP | |
| PUB -->|"JSON Tick Data"| SUB | |
| REQ <-->|"JSON Orders"| REP | |
| ``` | |
| ### Data Flow | |
| ```mermaid | |
| sequenceDiagram | |
| participant MT5 as MT5 EA | |
| participant PUB as PUB Socket | |
| participant SUB as Rust SubSocket | |
| participant CH as MPSC Channel | |
| participant APP as Rust App | |
| participant REQ as Rust ReqSocket | |
| participant REP as REP Socket | |
| Note over MT5,APP: Tick Data Flow | |
| loop Every Tick | |
| MT5->>PUB: Publish JSON | |
| PUB->>SUB: Broadcast | |
| SUB->>CH: tx.send(tick) | |
| CH->>APP: rx.recv() | |
| end | |
| Note over APP,MT5: Order Execution Flow | |
| APP->>REQ: Order Request | |
| REQ->>REP: Send JSON | |
| REP->>MT5: Parse Order | |
| MT5->>MT5: Execute Trade | |
| MT5->>REP: Response | |
| REP->>REQ: JSON Response | |
| REQ->>APP: OrderResponse | |
| ``` | |
| --- | |
| ## Prerequisites and Installation | |
| ### Cargo.toml Dependencies | |
| ```toml | |
| [dependencies] | |
| zeromq = "0.3" | |
| tokio = { version = "1", features = ["full"] } | |
| serde = { version = "1", features = ["derive"] } | |
| serde_json = "1" | |
| 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 | |
| 1. **Add dependencies to Cargo.toml** (see above) | |
| 2. **Build the project** | |
| ```bash | |
| cargo build --release | |
| ``` | |
| 3. **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. | |
| ```rust | |
| #[derive(Clone, Debug, Deserialize)] | |
| pub struct TickData { | |
| pub symbol: String, | |
| pub bid: f64, | |
| pub ask: f64, | |
| pub time: i64, | |
| #[serde(default)] | |
| pub volume: u64, | |
| #[serde(default)] | |
| pub balance: f64, | |
| #[serde(default)] | |
| pub equity: f64, | |
| #[serde(default)] | |
| pub margin: f64, | |
| #[serde(default)] | |
| pub free_margin: f64, | |
| #[serde(default)] | |
| pub min_lot: f64, | |
| #[serde(default)] | |
| pub max_lot: f64, | |
| #[serde(default)] | |
| pub lot_step: f64, | |
| #[serde(default)] | |
| pub positions: Vec<PositionData>, | |
| #[serde(default)] | |
| pub orders: Vec<PendingOrderData>, | |
| } | |
| ``` | |
| | Field | Type | Description | | |
| |-------|------|-------------| | |
| | `symbol` | `String` | Trading symbol (e.g., "EURUSD") | | |
| | `bid` | `f64` | Current bid price | | |
| | `ask` | `f64` | Current ask price | | |
| | `time` | `i64` | Unix timestamp | | |
| | `volume` | `u64` | Tick volume | | |
| | `balance` | `f64` | Account balance | | |
| | `equity` | `f64` | Account equity | | |
| | `margin` | `f64` | Used margin | | |
| | `free_margin` | `f64` | Available margin | | |
| | `min_lot` | `f64` | Minimum lot size | | |
| | `max_lot` | `f64` | Maximum lot size | | |
| | `lot_step` | `f64` | Lot size increment | | |
| | `positions` | `Vec<PositionData>` | Active positions | | |
| | `orders` | `Vec<PendingOrderData>` | Pending orders | | |
| --- | |
| #### PositionData | |
| Represents an active trading position. | |
| ```rust | |
| #[derive(Clone, Debug, Deserialize)] | |
| pub struct PositionData { | |
| pub ticket: u64, | |
| #[serde(rename = "type")] | |
| pub pos_type: String, // "BUY" or "SELL" | |
| pub volume: f64, | |
| pub price: f64, | |
| pub profit: f64, | |
| } | |
| ``` | |
| --- | |
| #### PendingOrderData | |
| Represents a pending order. | |
| ```rust | |
| #[derive(Clone, Debug, Deserialize)] | |
| pub struct PendingOrderData { | |
| pub ticket: u64, | |
| #[serde(rename = "type")] | |
| pub order_type: String, // "BUY LIMIT", "SELL STOP", etc. | |
| pub volume: f64, | |
| pub price: f64, | |
| } | |
| ``` | |
| --- | |
| #### OrderRequest | |
| Request structure for sending orders to MT5. | |
| ```rust | |
| #[derive(Clone, Debug, Serialize)] | |
| pub struct OrderRequest { | |
| #[serde(rename = "type")] | |
| pub order_type: String, | |
| pub symbol: String, | |
| pub volume: f64, | |
| pub price: f64, | |
| #[serde(default)] | |
| pub ticket: u64, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| pub timeframe: Option<String>, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| pub start: Option<String>, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| pub end: Option<String>, | |
| #[serde(skip_serializing_if = "Option::is_none")] | |
| pub mode: Option<String>, | |
| } | |
| ``` | |
| **Supported Order Types:** | |
| | Type | Description | | |
| |------|-------------| | |
| | `market_buy` | Execute market buy order | | |
| | `market_sell` | Execute market sell order | | |
| | `limit_buy` | Place buy limit pending order | | |
| | `limit_sell` | Place sell limit pending order | | |
| | `stop_buy` | Place buy stop pending order | | |
| | `stop_sell` | Place sell stop pending order | | |
| | `close_position` | Close position by ticket | | |
| | `cancel_order` | Cancel pending order by ticket | | |
| | `download_history` | Request historical data | | |
| --- | |
| #### OrderResponse | |
| Response structure from MT5 order execution. | |
| ```rust | |
| #[derive(Clone, Debug, Deserialize)] | |
| pub struct OrderResponse { | |
| pub success: bool, | |
| pub ticket: Option<i64>, | |
| pub error: Option<String>, | |
| pub message: Option<String>, | |
| } | |
| ``` | |
| --- | |
| ## Usage Guide | |
| ### Step 1: Create Channels | |
| ```rust | |
| use tokio::sync::mpsc; | |
| // Channel for tick data (MT5 -> App) | |
| let (tick_tx, tick_rx) = mpsc::channel::<TickData>(100); | |
| // Channel for order requests (App -> MT5) | |
| let (order_tx, order_rx) = mpsc::channel::<OrderRequest>(10); | |
| // Channel for order responses (MT5 -> App) | |
| let (response_tx, response_rx) = mpsc::channel::<OrderResponse>(10); | |
| ``` | |
| ### Step 2: Spawn Tick Subscriber Task | |
| ```rust | |
| tokio::spawn(async move { | |
| let mut socket = zeromq::SubSocket::new(); | |
| socket.connect("tcp://127.0.0.1:5555").await.unwrap(); | |
| socket.subscribe("").await.unwrap(); | |
| loop { | |
| match socket.recv().await { | |
| Ok(msg) => { | |
| if let Some(bytes) = msg.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| if let Ok(tick) = serde_json::from_str::<TickData>(json) { | |
| let _ = tick_tx.send(tick).await; | |
| } | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| eprintln!("Tick recv error: {}", e); | |
| tokio::time::sleep(Duration::from_secs(1)).await; | |
| } | |
| } | |
| } | |
| }); | |
| ``` | |
| ### Step 3: Spawn Order Handler Task | |
| ```rust | |
| tokio::spawn(async move { | |
| let mut socket = zeromq::ReqSocket::new(); | |
| socket.connect("tcp://127.0.0.1:5556").await.unwrap(); | |
| while let Some(request) = order_rx.recv().await { | |
| let json = serde_json::to_string(&request).unwrap(); | |
| if let Err(e) = socket.send(json.into()).await { | |
| let _ = response_tx.send(OrderResponse { | |
| success: false, | |
| ticket: None, | |
| error: Some(format!("Send failed: {}", e)), | |
| message: None, | |
| }).await; | |
| continue; | |
| } | |
| match socket.recv().await { | |
| Ok(msg) => { | |
| if let Some(bytes) = msg.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| if let Ok(response) = serde_json::from_str::<OrderResponse>(json) { | |
| let _ = response_tx.send(response).await; | |
| } | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| let _ = response_tx.send(OrderResponse { | |
| success: false, | |
| ticket: None, | |
| error: Some(format!("Recv failed: {}", e)), | |
| message: None, | |
| }).await; | |
| } | |
| } | |
| } | |
| }); | |
| ``` | |
| ### Step 4: Process Ticks and Send Orders | |
| ```rust | |
| // Process incoming ticks | |
| while let Some(tick) = tick_rx.recv().await { | |
| println!("{}: Bid={}, Ask={}", tick.symbol, tick.bid, tick.ask); | |
| // Example: Send a buy order when certain condition is met | |
| if some_trading_condition(&tick) { | |
| let order = OrderRequest { | |
| order_type: "market_buy".to_string(), | |
| symbol: tick.symbol.clone(), | |
| volume: 0.01, | |
| price: 0.0, | |
| ticket: 0, | |
| timeframe: None, | |
| start: None, | |
| end: None, | |
| mode: None, | |
| }; | |
| let _ = order_tx.send(order).await; | |
| } | |
| } | |
| ``` | |
| --- | |
| ## Complete Examples | |
| ### Example 1: Basic Tick Subscriber | |
| ```rust | |
| use serde::Deserialize; | |
| use zeromq::{Socket, SocketRecv}; | |
| #[derive(Debug, Deserialize)] | |
| struct TickData { | |
| symbol: String, | |
| bid: f64, | |
| ask: f64, | |
| time: i64, | |
| } | |
| #[tokio::main] | |
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | |
| let mut socket = zeromq::SubSocket::new(); | |
| socket.connect("tcp://127.0.0.1:5555").await?; | |
| socket.subscribe("").await?; | |
| println!("Connected to MT5 tick publisher"); | |
| loop { | |
| let msg = socket.recv().await?; | |
| if let Some(bytes) = msg.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| if let Ok(tick) = serde_json::from_str::<TickData>(json) { | |
| println!("{}: {:.5} / {:.5}", tick.symbol, tick.bid, tick.ask); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| ``` | |
| ### Example 2: Order Execution Client | |
| ```rust | |
| use serde::{Deserialize, Serialize}; | |
| use zeromq::{Socket, SocketRecv, SocketSend}; | |
| #[derive(Serialize)] | |
| struct OrderRequest { | |
| #[serde(rename = "type")] | |
| order_type: String, | |
| symbol: String, | |
| volume: f64, | |
| price: f64, | |
| } | |
| #[derive(Debug, Deserialize)] | |
| struct OrderResponse { | |
| success: bool, | |
| ticket: Option<i64>, | |
| error: Option<String>, | |
| } | |
| #[tokio::main] | |
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | |
| let mut socket = zeromq::ReqSocket::new(); | |
| socket.connect("tcp://127.0.0.1:5556").await?; | |
| println!("Connected to MT5 order handler"); | |
| // Send a market buy order | |
| let order = OrderRequest { | |
| order_type: "market_buy".to_string(), | |
| symbol: "EURUSD".to_string(), | |
| volume: 0.01, | |
| price: 0.0, | |
| }; | |
| let json = serde_json::to_string(&order)?; | |
| println!("Sending: {}", json); | |
| socket.send(json.into()).await?; | |
| let response = socket.recv().await?; | |
| if let Some(bytes) = response.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| let resp: OrderResponse = serde_json::from_str(json)?; | |
| if resp.success { | |
| println!("Order executed! Ticket: {:?}", resp.ticket); | |
| } else { | |
| println!("Order failed: {:?}", resp.error); | |
| } | |
| } | |
| } | |
| Ok(()) | |
| } | |
| ``` | |
| ### Example 3: Full Trading Application | |
| ```rust | |
| use serde::{Deserialize, Serialize}; | |
| use tokio::sync::mpsc; | |
| use zeromq::{Socket, SocketRecv, SocketSend}; | |
| use std::time::Duration; | |
| // ============================================================================ | |
| // Data Structures | |
| // ============================================================================ | |
| #[derive(Clone, Debug, Deserialize)] | |
| struct PositionData { | |
| ticket: u64, | |
| #[serde(rename = "type")] | |
| pos_type: String, | |
| volume: f64, | |
| price: f64, | |
| profit: f64, | |
| } | |
| #[derive(Clone, Debug, Deserialize)] | |
| struct TickData { | |
| symbol: String, | |
| bid: f64, | |
| ask: f64, | |
| time: i64, | |
| #[serde(default)] | |
| balance: f64, | |
| #[serde(default)] | |
| equity: f64, | |
| #[serde(default)] | |
| positions: Vec<PositionData>, | |
| } | |
| #[derive(Clone, Debug, Serialize)] | |
| struct OrderRequest { | |
| #[serde(rename = "type")] | |
| order_type: String, | |
| symbol: String, | |
| volume: f64, | |
| #[serde(default)] | |
| price: f64, | |
| #[serde(default)] | |
| ticket: u64, | |
| } | |
| #[derive(Clone, Debug, Deserialize)] | |
| struct OrderResponse { | |
| success: bool, | |
| ticket: Option<i64>, | |
| error: Option<String>, | |
| } | |
| // ============================================================================ | |
| // Main Application | |
| // ============================================================================ | |
| #[tokio::main] | |
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | |
| // Create channels | |
| let (tick_tx, mut tick_rx) = mpsc::channel::<TickData>(100); | |
| let (order_tx, mut order_rx) = mpsc::channel::<OrderRequest>(10); | |
| let (response_tx, mut response_rx) = mpsc::channel::<OrderResponse>(10); | |
| // Spawn tick subscriber | |
| tokio::spawn(async move { | |
| let mut socket = zeromq::SubSocket::new(); | |
| if let Err(e) = socket.connect("tcp://127.0.0.1:5555").await { | |
| eprintln!("Failed to connect to tick publisher: {}", e); | |
| return; | |
| } | |
| let _ = socket.subscribe("").await; | |
| println!("Tick subscriber connected"); | |
| loop { | |
| match socket.recv().await { | |
| Ok(msg) => { | |
| if let Some(bytes) = msg.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| if let Ok(tick) = serde_json::from_str::<TickData>(json) { | |
| if tick_tx.send(tick).await.is_err() { | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| eprintln!("Tick error: {}", e); | |
| tokio::time::sleep(Duration::from_secs(1)).await; | |
| } | |
| } | |
| } | |
| }); | |
| // Spawn order handler | |
| let resp_tx = response_tx.clone(); | |
| tokio::spawn(async move { | |
| let mut socket = zeromq::ReqSocket::new(); | |
| if let Err(e) = socket.connect("tcp://127.0.0.1:5556").await { | |
| eprintln!("Failed to connect to order handler: {}", e); | |
| return; | |
| } | |
| println!("Order handler connected"); | |
| while let Some(request) = order_rx.recv().await { | |
| let json = match serde_json::to_string(&request) { | |
| Ok(j) => j, | |
| Err(e) => { | |
| let _ = resp_tx.send(OrderResponse { | |
| success: false, | |
| ticket: None, | |
| error: Some(format!("Serialize error: {}", e)), | |
| }).await; | |
| continue; | |
| } | |
| }; | |
| println!("Sending order: {}", json); | |
| if let Err(e) = socket.send(json.into()).await { | |
| let _ = resp_tx.send(OrderResponse { | |
| success: false, | |
| ticket: None, | |
| error: Some(format!("Send error: {}", e)), | |
| }).await; | |
| continue; | |
| } | |
| match socket.recv().await { | |
| Ok(msg) => { | |
| if let Some(bytes) = msg.get(0) { | |
| if let Ok(json) = std::str::from_utf8(bytes) { | |
| if let Ok(resp) = serde_json::from_str::<OrderResponse>(json) { | |
| let _ = resp_tx.send(resp).await; | |
| } | |
| } | |
| } | |
| } | |
| Err(e) => { | |
| let _ = resp_tx.send(OrderResponse { | |
| success: false, | |
| ticket: None, | |
| error: Some(format!("Recv error: {}", e)), | |
| }).await; | |
| } | |
| } | |
| } | |
| }); | |
| // Spawn response handler | |
| tokio::spawn(async move { | |
| while let Some(response) = response_rx.recv().await { | |
| if response.success { | |
| println!("Order SUCCESS: Ticket {:?}", response.ticket); | |
| } else { | |
| println!("Order FAILED: {:?}", response.error); | |
| } | |
| } | |
| }); | |
| // Main loop - process ticks | |
| println!("Starting main loop..."); | |
| let mut tick_count = 0u64; | |
| while let Some(tick) = tick_rx.recv().await { | |
| tick_count += 1; | |
| // Print every 100th tick to avoid spam | |
| if tick_count % 100 == 0 { | |
| println!( | |
| "[{}] {}: Bid={:.5}, Ask={:.5}, Balance={:.2}, Positions={}", | |
| tick_count, | |
| tick.symbol, | |
| tick.bid, | |
| tick.ask, | |
| tick.balance, | |
| tick.positions.len() | |
| ); | |
| } | |
| // Example trading logic: buy when no positions exist | |
| if tick.positions.is_empty() && tick_count == 500 { | |
| let order = OrderRequest { | |
| order_type: "market_buy".to_string(), | |
| symbol: tick.symbol.clone(), | |
| volume: 0.01, | |
| price: 0.0, | |
| ticket: 0, | |
| }; | |
| let _ = order_tx.send(order).await; | |
| } | |
| } | |
| Ok(()) | |
| } | |
| ``` | |
| --- | |
| ## Error Handling | |
| ### Common Error Patterns | |
| ```rust | |
| // Connection error handling | |
| match socket.connect("tcp://127.0.0.1:5555").await { | |
| Ok(_) => println!("Connected"), | |
| Err(e) => { | |
| eprintln!("Connection failed: {}", e); | |
| // Implement retry logic | |
| tokio::time::sleep(Duration::from_secs(5)).await; | |
| } | |
| } | |
| // Receive error handling with retry | |
| loop { | |
| match socket.recv().await { | |
| Ok(msg) => process_message(msg), | |
| Err(e) => { | |
| eprintln!("Recv error: {}", e); | |
| tokio::time::sleep(Duration::from_millis(100)).await; | |
| continue; | |
| } | |
| } | |
| } | |
| // JSON parsing error handling | |
| match serde_json::from_str::<TickData>(json) { | |
| Ok(tick) => handle_tick(tick), | |
| Err(e) => eprintln!("JSON parse error: {} - Data: {}", e, json), | |
| } | |
| ``` | |
| ### Error Response Structure | |
| Always check `OrderResponse.success` before using other fields: | |
| ```rust | |
| if response.success { | |
| let ticket = response.ticket.unwrap_or(0); | |
| println!("Order executed with ticket: {}", ticket); | |
| } else { | |
| let error = response.error.unwrap_or_else(|| "Unknown error".to_string()); | |
| eprintln!("Order failed: {}", error); | |
| } | |
| ``` | |
| --- | |
| ## Best Practices | |
| ### 1. Use Bounded Channels | |
| Prevent memory issues with bounded channels: | |
| ```rust | |
| // Good: Bounded channel with reasonable capacity | |
| let (tx, rx) = mpsc::channel::<TickData>(100); | |
| // Avoid: Unbounded channels can grow infinitely | |
| // let (tx, rx) = mpsc::unbounded_channel(); | |
| ``` | |
| ### 2. Handle Channel Errors | |
| Check for send/receive errors: | |
| ```rust | |
| // Check if receiver is dropped | |
| if tx.send(tick).await.is_err() { | |
| eprintln!("Receiver dropped, exiting"); | |
| break; | |
| } | |
| // Use try_send for non-blocking with backpressure | |
| match tx.try_send(tick) { | |
| Ok(_) => {}, | |
| Err(mpsc::error::TrySendError::Full(_)) => { | |
| eprintln!("Channel full, dropping tick"); | |
| } | |
| Err(mpsc::error::TrySendError::Closed(_)) => break, | |
| } | |
| ``` | |
| ### 3. Graceful Shutdown | |
| Implement proper shutdown handling: | |
| ```rust | |
| use tokio::signal; | |
| tokio::select! { | |
| _ = process_ticks(&mut tick_rx) => {}, | |
| _ = signal::ctrl_c() => { | |
| println!("Shutting down..."); | |
| } | |
| } | |
| ``` | |
| ### 4. Connection Resilience | |
| Implement reconnection logic: | |
| ```rust | |
| async fn connect_with_retry(addr: &str, max_retries: u32) -> Result<SubSocket, Error> { | |
| for attempt in 1..=max_retries { | |
| let mut socket = zeromq::SubSocket::new(); | |
| match socket.connect(addr).await { | |
| Ok(_) => return Ok(socket), | |
| Err(e) => { | |
| eprintln!("Attempt {}/{} failed: {}", attempt, max_retries, e); | |
| tokio::time::sleep(Duration::from_secs(attempt as u64)).await; | |
| } | |
| } | |
| } | |
| Err(Error::ConnectionFailed) | |
| } | |
| ``` | |
| --- | |
| ## 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 | |
| ```go | |
| // See MQL5-ZMQ Library documentation for Go examples | |
| import zmq "github.com/pebbe/zmq4" | |
| ``` | |
| ### Java Integration | |
| ```java | |
| // See MQL5-ZMQ Library documentation for Java examples | |
| import org.zeromq.ZMQ; | |
| ``` | |
| ### C++ Integration | |
| ```cpp | |
| // See MQL5-ZMQ Library documentation for C++ examples | |
| #include <zmq.hpp> | |
| ``` | |
| All clients use the same JSON message protocol defined in the [MQL5-ZMQ Library](MQL5-ZMQ%20Library%20for%20SUM3API.md#message-protocol). | |
| --- | |
| ## 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 | |
| - [ZeroMQ Rust Crate](https://crates.io/crates/zeromq) | |
| - [Tokio Async Runtime](https://tokio.rs/) | |
| - [Serde JSON](https://serde.rs/) | |
| - [MQL5-ZMQ Library](MQL5-ZMQ%20Library%20for%20SUM3API.md) | |
| - [GitHub Repository](https://github.com/algorembrant/Rust-ZMQ-MT5) | |
| --- | |
| ## Citation | |
| If you use this library in your research or project, please cite: | |
| ```bibtex | |
| @software{rembrant2026sum3api, | |
| author = {Rembrant, Albeos}, | |
| title = {{SUM3API}: Using Rust, ZeroMQ, and MetaQuotes Language (MQL5) API Combination to Extract, Communicate, and Externally Project Financial Data from MetaTrader 5 (MT5)}, | |
| year = {2026}, | |
| publisher = {GitHub}, | |
| url = {https://github.com/algorembrant/Rust-ZMQ-MT5}, | |
| version = {2.00} | |
| } | |
| ``` | |
| //end of documentattion |