v0.1
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+230
@@ -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
@@ -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"
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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>;
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user