This commit is contained in:
yjkt
2026-05-17 11:26:45 +08:00
commit 061cac2d5a
11 changed files with 2491 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/target
Generated
+230
View File
@@ -0,0 +1,230 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "mt5-rs"
version = "0.1.0"
dependencies = [
"hex",
"sha2",
"thiserror",
"windows-sys",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "mt5-rs"
version = "0.1.0"
edition = "2021"
rust-version = "1.70"
description = "A pure Rust library for MetaTrader 5 IPC communication (no Python dependency)"
license = "MIT"
repository = "https://github.com/yjkt/mt5-rs"
documentation = "https://github.com/yjkt/mt5-rs"
readme = "README.md"
keywords = ["metatrader", "mt5", "trading", "forex", "ipc"]
categories = ["api-bindings", "finance"]
authors = ["yjkt <mpbgp@qq.com>"]
[dependencies]
windows-sys = { version = "0.59", features = [
"Win32_System_Pipes",
"Win32_Storage_FileSystem",
"Win32_Foundation",
"Win32_Security",
"Win32_System_IO",
"Win32_System_Threading",
"Win32_System_Diagnostics_ToolHelp",
] }
thiserror = "1.0"
sha2 = "0.10"
hex = "0.4"
[lib]
name = "mt5_rs"
path = "src/lib.rs"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 yjkt
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.
+135
View File
@@ -0,0 +1,135 @@
# mt5-rs
A pure Rust library for MetaTrader 5 IPC communication. No Python dependency.
Compatible with Python `MetaTrader5` library API.
## Features
- Pure Rust implementation, no Python or C++ dependency
- Windows named pipe IPC communication with MT5 terminal
- Full compatibility with Python `MetaTrader5` library (30/32 functions)
- Support MT5 Build 5836+
## Quick Start
```rust
use mt5_rs::{Mt5Client, discover_mt5_pipe};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Auto-discover MT5 pipe
let pipe_name = discover_mt5_pipe();
// Initialize client
let mut client = Mt5Client::new();
client.initialize(Some(&pipe_name))?;
// Get account info
let account = client.account_info()?;
println!("Balance: {}", account.balance);
println!("Equity: {}", account.equity);
println!("Margin Free: {}", account.margin_free);
Ok(())
}
```
## API Reference
### Initialization
| Function | Description |
|----------|-------------|
| `Mt5Client::new()` | Create a new client |
| `initialize(pipe_name)` | Initialize connection to MT5 |
| `shutdown()` | Close connection |
| `login(login, password, server)` | Login to MT5 account |
### Account & Terminal
| Function | Description |
|----------|-------------|
| `account_info()` | Get account information |
| `terminal_info()` | Get terminal information |
| `version()` | Get MT5 version |
| `last_error()` | Get last error code and message |
### Symbols
| Function | Description |
|----------|-------------|
| `symbol_info(symbol)` | Get symbol information |
| `symbol_info_tick(symbol)` | Get current tick for symbol |
| `symbols_get()` | Get all available symbols |
| `symbol_select(symbol, enable)` | Select/deselect symbol in Market Watch |
### Market Data
| Function | Description |
|----------|-------------|
| `copy_rates_from_pos(symbol, timeframe, start_pos, count)` | Copy bars from position |
| `copy_rates_from(symbol, timeframe, date_from, count)` | Copy bars from date |
| `copy_rates_range(symbol, timeframe, date_from, date_to)` | Copy bars in range |
| `copy_ticks_from(symbol, from, count, flags)` | Copy ticks from time |
| `copy_ticks_range(symbol, from, to, flags)` | Copy ticks in range |
### Positions & Orders
| Function | Description |
|----------|-------------|
| `positions_total()` | Get total number of open positions |
| `positions_get(symbol)` | Get open positions |
| `orders_total()` | Get total number of pending orders |
| `orders_get(symbol)` | Get pending orders |
### History
| Function | Description |
|----------|-------------|
| `history_deals_total(from, to)` | Get total number of deals in range |
| `history_deals_get(from, to)` | Get deals in range |
| `history_orders_total(from, to)` | Get total number of orders in range |
| `history_orders_get(from, to)` | Get orders in range |
### Market Depth
| Function | Description |
|----------|-------------|
| `market_book_add(symbol)` | Subscribe to market depth |
| `market_book_get(symbol)` | Get market depth data |
| `market_book_release(symbol)` | Unsubscribe from market depth |
### Trading Calculations
| Function | Description |
|----------|-------------|
| `order_calc_margin(action, symbol, volume, price)` | Calculate required margin (local calculation) |
| `order_calc_profit(action, symbol, volume, price_open, price_close)` | Calculate expected profit (local calculation) |
### Not Implemented
| Function | Status |
|----------|--------|
| `order_check(request)` | TODO - Check trade request validity |
| `order_send(request)` | TODO - Send trade request to MT5 |
## Implementation Notes
### Local Calculation for `order_calc_margin` and `order_calc_profit`
Unlike `go-mt5` which sends commands 202/203 via named pipe, this library uses **local calculation** (same as Python `MetaTrader5` library):
- `order_calc_margin`: `margin = volume × price × margin_initial / 4`
- `order_calc_profit`: `profit = volume × (price_close - price_open) × trade_contract_size`
This approach avoids the "pipe closed" error that occurs with MT5 Build 5836+ when using IPC for these commands.
## Requirements
- Rust 2021 edition
- Windows OS (named pipe IPC is Windows-specific)
- MT5 terminal must be running
## License
MIT
+135
View File
@@ -0,0 +1,135 @@
# mt5-rs
纯 Rust 实现的 MetaTrader 5 IPC 通信库,无需 Python 依赖。
与 Python `MetaTrader5` 库 API 完全兼容。
## 特性
- 纯 Rust 实现,无 Python 或 C++ 依赖
- 通过 Windows 命名管道与 MT5 终端进行 IPC 通信
- 与 Python `MetaTrader5` 库完全兼容(32个函数中的30个)
- 支持 MT5 Build 5836+
## 快速开始
```rust
use mt5_rs::{Mt5Client, discover_mt5_pipe};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 自动发现 MT5 管道
let pipe_name = discover_mt5_pipe();
// 初始化客户端
let mut client = Mt5Client::new();
client.initialize(Some(&pipe_name))?;
// 获取账户信息
let account = client.account_info()?;
println!("Balance: {}", account.balance);
println!("Equity: {}", account.equity);
println!("Margin Free: {}", account.margin_free);
Ok(())
}
```
## API 参考
### 初始化
| 函数 | 描述 |
|------|------|
| `Mt5Client::new()` | 创建新客户端 |
| `initialize(pipe_name)` | 初始化与 MT5 的连接 |
| `shutdown()` | 关闭连接 |
| `login(login, password, server)` | 登录 MT5 账户 |
### 账户与终端
| 函数 | 描述 |
|------|------|
| `account_info()` | 获取账户信息 |
| `terminal_info()` | 获取终端信息 |
| `version()` | 获取 MT5 版本 |
| `last_error()` | 获取最后错误代码和消息 |
### 交易品种
| 函数 | 描述 |
|------|------|
| `symbol_info(symbol)` | 获取品种信息 |
| `symbol_info_tick(symbol)` | 获取品种当前报价 |
| `symbols_get()` | 获取所有可用品种 |
| `symbol_select(symbol, enable)` | 在市场报价中选择/取消选择品种 |
### 行情数据
| 函数 | 描述 |
|------|------|
| `copy_rates_from_pos(symbol, timeframe, start_pos, count)` | 从指定位置复制K线 |
| `copy_rates_from(symbol, timeframe, date_from, count)` | 从指定日期复制K线 |
| `copy_rates_range(symbol, timeframe, date_from, date_to)` | 复制指定范围的K线 |
| `copy_ticks_from(symbol, from, count, flags)` | 从指定时间复制Tick |
| `copy_ticks_range(symbol, from, to, flags)` | 复制指定范围的Tick |
### 持仓与订单
| 函数 | 描述 |
|------|------|
| `positions_total()` | 获取未平仓持仓总数 |
| `positions_get(symbol)` | 获取未平仓持仓 |
| `orders_total()` | 获取挂单总数 |
| `orders_get(symbol)` | 获取挂单 |
### 历史数据
| 函数 | 描述 |
|------|------|
| `history_deals_total(from, to)` | 获取指定范围内成交总数 |
| `history_deals_get(from, to)` | 获取指定范围内成交记录 |
| `history_orders_total(from, to)` | 获取指定范围内订单总数 |
| `history_orders_get(from, to)` | 获取指定范围内订单记录 |
### 市场深度
| 函数 | 描述 |
|------|------|
| `market_book_add(symbol)` | 订阅市场深度 |
| `market_book_get(symbol)` | 获取市场深度数据 |
| `market_book_release(symbol)` | 取消订阅市场深度 |
### 交易计算
| 函数 | 描述 |
|------|------|
| `order_calc_margin(action, symbol, volume, price)` | 计算所需保证金(本地计算) |
| `order_calc_profit(action, symbol, volume, price_open, price_close)` | 计算预期利润(本地计算) |
### 未实现
| 函数 | 状态 |
|------|------|
| `order_check(request)` | TODO - 检查交易请求有效性 |
| `order_send(request)` | TODO - 发送交易请求到 MT5 |
## 实现说明
### `order_calc_margin` 和 `order_calc_profit` 的本地计算
与通过命名管道发送命令 202/203 的 `go-mt5` 不同,本库使用**本地计算**(与 Python `MetaTrader5` 库行为一致):
- `order_calc_margin`: `margin = volume × price × margin_initial / 4`
- `order_calc_profit`: `profit = volume × (price_close - price_open) × trade_contract_size`
此方法避免了 MT5 Build 5836+ 使用 IPC 执行这些命令时的"管道已关闭"错误。
## 系统要求
- Rust 2021 edition
- Windows 操作系统(命名管道 IPC 仅限 Windows
- MT5 终端必须正在运行
## 许可证
MIT
+1345
View File
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Mt5Error {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Connection failed: {0}")]
ConnectionFailed(String),
#[error("Protocol error: {0}")]
ProtocolError(String),
#[error("Invalid response: {0}")]
InvalidResponse(String),
#[error("MT5 not initialized")]
NotInitialized,
#[error("Command failed: cmd={cmd}, error={error}")]
CommandFailed { cmd: u32, error: String },
#[error("Not supported: {0}")]
NotSupported(String),
}
pub type Result<T> = std::result::Result<T, Mt5Error>;
+9
View File
@@ -0,0 +1,9 @@
pub mod client;
pub mod error;
pub mod protocol;
pub mod types;
pub use client::Mt5Client;
pub use error::{Mt5Error, Result};
pub use types::*;
pub use protocol::discover_mt5_pipe;
+288
View File
@@ -0,0 +1,288 @@
use windows_sys::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE};
use windows_sys::Win32::Storage::FileSystem::{
CreateFileW, ReadFile, WriteFile, FILE_ATTRIBUTE_NORMAL,
OPEN_EXISTING,
};
use windows_sys::Win32::System::Pipes::WaitNamedPipeW;
use windows_sys::Win32::System::Threading::{OpenProcess, QueryFullProcessImageNameW};
use windows_sys::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, TH32CS_SNAPPROCESS, PROCESSENTRY32W,
};
use sha2::{Sha256, Digest};
use crate::error::{Mt5Error, Result};
const PROCESS_QUERY_LIMITED_INFORMATION: u32 = 0x1000;
pub struct NamedPipeClient {
handle: HANDLE,
}
impl NamedPipeClient {
pub fn new(pipe_name: Option<&str>) -> Result<Self> {
let name = match pipe_name {
Some(n) => n.to_string(),
None => {
return Err(Mt5Error::ConnectionFailed(
"Pipe name must be provided. Use initialize(Some(\"pipe_name\"))".into(),
))
}
};
let pipe_name_wide: Vec<u16> = name
.encode_utf16()
.chain(std::iter::once(0))
.collect();
unsafe {
WaitNamedPipeW(pipe_name_wide.as_ptr(), 500);
}
let handle = unsafe {
CreateFileW(
pipe_name_wide.as_ptr(),
0x80000000 | 0x40000000,
0,
std::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(Mt5Error::ConnectionFailed(format!(
"Failed to connect to pipe: {}",
name
)));
}
Ok(Self { handle })
}
pub fn send(&self, cmd: u32, data: &[u8]) -> Result<Vec<u8>> {
let total_len = 4 + data.len();
let mut request = Vec::with_capacity(8 + data.len());
request.extend_from_slice(&(total_len as u32).to_le_bytes());
request.extend_from_slice(&cmd.to_le_bytes());
request.extend_from_slice(data);
unsafe {
let mut bytes_written = 0u32;
let result = WriteFile(
self.handle,
request.as_ptr(),
request.len() as u32,
&mut bytes_written,
std::ptr::null_mut(),
);
if result == 0 {
return Err(Mt5Error::IoError(std::io::Error::last_os_error()));
}
}
self.read_response()
}
fn read_response(&self) -> Result<Vec<u8>> {
let mut len_buf = [0u8; 4];
let mut bytes_read = 0u32;
unsafe {
let result = ReadFile(
self.handle,
len_buf.as_mut_ptr(),
len_buf.len() as u32,
&mut bytes_read,
std::ptr::null_mut(),
);
if result == 0 {
return Err(Mt5Error::IoError(std::io::Error::last_os_error()));
}
}
let payload_len = u32::from_le_bytes(len_buf) as usize;
if payload_len < 8 {
return Err(Mt5Error::InvalidResponse(format!(
"Payload too small: {} bytes",
payload_len
)));
}
let mut payload = vec![0u8; payload_len];
let mut total_read = 0usize;
while total_read < payload_len {
let mut bytes_read = 0u32;
unsafe {
let result = ReadFile(
self.handle,
payload[total_read..].as_mut_ptr(),
(payload_len - total_read) as u32,
&mut bytes_read,
std::ptr::null_mut(),
);
if result == 0 {
return Err(Mt5Error::IoError(std::io::Error::last_os_error()));
}
total_read += bytes_read as usize;
}
}
let _cmd_id = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]);
let _success = u32::from_le_bytes([payload[4], payload[5], payload[6], payload[7]]) != 0;
if payload.len() > 8 {
Ok(payload[8..].to_vec())
} else {
Ok(Vec::new())
}
}
}
impl Drop for NamedPipeClient {
fn drop(&mut self) {
if self.handle != INVALID_HANDLE_VALUE {
unsafe {
CloseHandle(self.handle);
}
}
}
}
pub fn compute_pipe_name(terminal_path: &str) -> String {
let input = format!(r"\\?\{}", terminal_path.to_lowercase());
let input_utf16: Vec<u16> = input.encode_utf16().collect();
let mut buf = Vec::with_capacity(input_utf16.len() * 2);
for c in input_utf16 {
buf.push(c as u8);
buf.push((c >> 8) as u8);
}
let mut hasher = Sha256::new();
hasher.update(&buf);
let result = hasher.finalize();
format!(r"\\.\pipe\MT5.Terminal.{}", hex::encode(result).to_uppercase())
}
pub fn discover_mt5_pipe() -> String {
let paths = find_terminal64_paths().unwrap_or_default();
for path in &paths {
let pipe_name = compute_pipe_name(path);
if test_pipe_connection(&pipe_name) {
return pipe_name;
}
}
panic!("No responding MT5 pipe found");
}
fn test_pipe_connection(pipe_name: &str) -> bool {
let pipe_name_wide: Vec<u16> = pipe_name
.encode_utf16()
.chain(std::iter::once(0))
.collect();
unsafe {
WaitNamedPipeW(pipe_name_wide.as_ptr(), 500);
}
let handle = unsafe {
CreateFileW(
pipe_name_wide.as_ptr(),
0x80000000 | 0x40000000,
0,
std::ptr::null(),
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
std::ptr::null_mut(),
)
};
if handle != INVALID_HANDLE_VALUE {
unsafe {
CloseHandle(handle);
}
true
} else {
false
}
}
fn find_terminal64_paths() -> Result<Vec<String>> {
let snapshot = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) };
if snapshot == INVALID_HANDLE_VALUE {
return Err(Mt5Error::ConnectionFailed(
"Failed to create process snapshot".into(),
));
}
let mut paths = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut pe = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..unsafe { std::mem::zeroed() }
};
let mut result = unsafe { Process32FirstW(snapshot, &mut pe) };
while result != 0 {
let exe_name = String::from_utf16_lossy(&pe.szExeFile)
.trim_end_matches('\0')
.to_lowercase();
if exe_name == "terminal64.exe" {
if let Ok(path) = get_process_path(pe.th32ProcessID) {
if seen.insert(path.clone()) {
paths.push(path);
}
}
}
result = unsafe { Process32NextW(snapshot, &mut pe) };
}
unsafe { CloseHandle(snapshot) };
if paths.is_empty() {
return Err(Mt5Error::ConnectionFailed(
"No running terminal64.exe found".into(),
));
}
Ok(paths)
}
fn get_process_path(pid: u32) -> Result<String> {
let handle = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
if handle == std::ptr::null_mut() {
return Err(Mt5Error::ConnectionFailed(format!(
"Failed to open process {}",
pid
)));
}
let mut buf = [0u16; 32768];
let mut size = buf.len() as u32;
let result = unsafe { QueryFullProcessImageNameW(handle, 0, buf.as_mut_ptr(), &mut size) };
unsafe { CloseHandle(handle) };
if result == 0 {
return Err(Mt5Error::ConnectionFailed(format!(
"Failed to get process image name for PID {}",
pid
)));
}
Ok(String::from_utf16_lossy(&buf[..size as usize]))
}
+269
View File
@@ -0,0 +1,269 @@
#[derive(Debug, Clone)]
pub struct AccountInfo {
pub login: i64,
pub trade_mode: i64,
pub leverage: i64,
pub limit_orders: i64,
pub margin_so_mode: i64,
pub trade_allowed: bool,
pub trade_expert: bool,
pub margin_mode: i64,
pub currency_digits: i64,
pub fifo_close: bool,
pub balance: f64,
pub credit: f64,
pub profit: f64,
pub equity: f64,
pub margin: f64,
pub free_margin: f64,
pub margin_level: f64,
pub margin_so_call: f64,
pub margin_so_so: f64,
pub margin_initial: f64,
pub margin_maintenance: f64,
pub assets: f64,
pub liabilities: f64,
pub commission_blocked: f64,
pub name: String,
pub server: String,
pub currency: String,
pub company: String,
}
#[derive(Debug, Clone)]
pub struct TerminalInfo {
pub community_account: bool,
pub community_connection: bool,
pub connected: bool,
pub dlls_allowed: bool,
pub trade_allowed: bool,
pub trade_api_disabled: bool,
pub email_enabled: bool,
pub ftp_enabled: bool,
pub notifications_enabled: bool,
pub mqid: bool,
pub build: i64,
pub max_bars: i64,
pub code_page: i64,
pub ping_last: i64,
pub community_balance: f64,
pub retransmission: f64,
pub company: String,
pub name: String,
pub language: String,
pub path: String,
pub data_path: String,
pub common_data_path: String,
}
#[derive(Debug, Clone)]
pub struct VersionInfo {
pub version: i32,
pub build: i32,
pub build_date: String,
}
#[derive(Debug, Clone)]
pub struct SymbolInfo {
pub custom: bool,
pub chart_mode: i64,
pub select: bool,
pub visible: bool,
pub session_deals: i64,
pub session_buy_orders: i64,
pub session_sell_orders: i64,
pub volume: i64,
pub volume_high: i64,
pub volume_low: i64,
pub time: i64,
pub digits: i64,
pub spread: i64,
pub spread_float: bool,
pub ticks_book_depth: i64,
pub trade_calc_mode: i64,
pub trade_mode: i64,
pub start_time: i64,
pub expiration_time: i64,
pub trade_stops_level: i64,
pub trade_freeze_level: i64,
pub trade_exe_mode: i64,
pub swap_mode: i64,
pub swap_rollover3days: i64,
pub margin_hedged_use_leg: bool,
pub expiration_mode: i64,
pub filling_mode: i64,
pub order_mode: i64,
pub order_gtc_mode: i64,
pub option_mode: i64,
pub option_right: i64,
pub bid: f64,
pub bidhigh: f64,
pub bidlow: f64,
pub ask: f64,
pub askhigh: f64,
pub asklow: f64,
pub last: f64,
pub lasthigh: f64,
pub lastlow: f64,
pub volume_real: f64,
pub volumehigh_real: f64,
pub volumelow_real: f64,
pub option_strike: f64,
pub point: f64,
pub trade_tick_value: f64,
pub trade_tick_value_profit: f64,
pub trade_tick_value_loss: f64,
pub trade_tick_size: f64,
pub trade_contract_size: f64,
pub trade_accrued_interest: f64,
pub trade_face_value: f64,
pub trade_liquidity_rate: f64,
pub volume_min: f64,
pub volume_max: f64,
pub volume_step: f64,
pub volume_limit: f64,
pub swap_long: f64,
pub swap_short: f64,
pub margin_initial: f64,
pub margin_maintenance: f64,
pub session_volume: f64,
pub session_turnover: f64,
pub session_interest: f64,
pub session_buy_orders_volume: f64,
pub session_sell_orders_volume: f64,
pub session_open: f64,
pub session_close: f64,
pub session_aw: f64,
pub session_price_settlement: f64,
pub session_price_limit_min: f64,
pub session_price_limit_max: f64,
pub margin_hedged: f64,
pub price_change: f64,
pub price_volatility: f64,
pub price_theoretical: f64,
pub price_greeks_delta: f64,
pub price_greeks_theta: f64,
pub price_greeks_gamma: f64,
pub price_greeks_vega: f64,
pub price_greeks_rho: f64,
pub price_greeks_omega: f64,
pub price_sensitivity: f64,
pub basis: String,
pub category: String,
pub currency_base: String,
pub currency_profit: String,
pub currency_margin: String,
pub bank: String,
pub description: String,
pub exchange: String,
pub formula: String,
pub isin: String,
pub name: String,
pub page: String,
pub path: String,
}
#[derive(Debug, Clone)]
pub struct Tick {
pub time: i64,
pub bid: f64,
pub ask: f64,
pub last: f64,
pub volume: u64,
pub time_msc: i64,
pub flags: u32,
pub volume_real: f64,
}
#[derive(Debug, Clone)]
pub struct Rate {
pub time: i64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub tick_volume: u64,
pub spread: i32,
pub real_volume: u64,
}
#[derive(Debug, Clone)]
pub struct TradePosition {
pub ticket: i64,
pub time: i64,
pub time_msc: i64,
pub time_update: i64,
pub time_update_msc: i64,
pub r#type: i32,
pub magic: i64,
pub identifier: i64,
pub reason: i32,
pub volume: f64,
pub price_open: f64,
pub price_current: f64,
pub price_sl: f64,
pub price_tp: f64,
pub swap: f64,
pub profit: f64,
pub symbol: String,
pub comment: String,
pub external_id: String,
}
#[derive(Debug, Clone)]
pub struct TradeOrder {
pub ticket: i64,
pub time_setup: i64,
pub time_setup_msc: i64,
pub time_done: i64,
pub time_done_msc: i64,
pub time_expiration: i64,
pub r#type: i32,
pub type_time: i32,
pub type_filling: i32,
pub state: i32,
pub magic: i64,
pub position_id: i64,
pub position_by_id: i64,
pub reason: i32,
pub volume_initial: f64,
pub volume_current: f64,
pub price_open: f64,
pub price_current: f64,
pub price_sl: f64,
pub price_tp: f64,
pub price_stoplimit: f64,
pub symbol: String,
pub comment: String,
pub external_id: String,
}
#[derive(Debug, Clone)]
pub struct TradeDeal {
pub ticket: i64,
pub order: i64,
pub time: i64,
pub time_msc: i64,
pub r#type: i32,
pub entry: i32,
pub magic: i64,
pub position_id: i64,
pub reason: i32,
pub volume: f64,
pub price: f64,
pub commission: f64,
pub swap: f64,
pub profit: f64,
pub fee: f64,
pub symbol: String,
pub comment: String,
pub external_id: String,
}
#[derive(Debug, Clone)]
pub struct BookInfo {
pub r#type: i64,
pub price: f64,
pub volume: i64,
pub volume_real: f64,
}