Files

875 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mt5Bridge — MT5 HTTP API 网关
通过 HTTP REST API 操控 MetaTrader 5,可部署在 Windows 云服务器上 7×24 运行。
---
## 架构
```
任意语言 (Python / Rust / Node / Go / ...)
▼ HTTP REST API (带 API Key 认证)
Mt5Bridge (C# + MtApi5) ← 通过端口映射暴露到公网
▼ localhost:8228
MtApi5 EA (MT5 图表上运行)
MetaTrader 5 客户端
```
---
## 前置条件
| 依赖 | 说明 |
|------|------|
| .NET 8 SDK | 本地编译(只需编译一次) |
| .NET 8 ASP.NET Core Runtime | 部署目标主机上安装 |
| MetaTrader 5 客户端 | 已登录任意账户 |
| MtApi5 EA | 挂载在 MT5 任意图表上,默认监听端口 8228 |
| MtApi5 安装包 | 安装后生成 `MT5Connector.dll` 等依赖 |
---
## 一、本地编译
```powershell
cd Mt5Bridge
dotnet build -c Release
```
编译产物在 `bin\Release\net8.0\`,包含以下文件:
| 文件 | 说明 |
|------|------|
| `Mt5Bridge.exe` | 可执行入口 |
| `Mt5Bridge.dll` | 编译后的主程序 |
| `Mt5Bridge.runtimeconfig.json` | .NET 运行时配置 |
| `Mt5Bridge.deps.json` | 依赖描述 |
| `MtApi5.dll` | C# 端 MT5 通信库 |
| `MtClient.dll` | MT5 客户端通信库 |
| `Newtonsoft.Json.dll` | JSON 序列化 |
---
## 二、部署到目标主机
### 1. 安装 .NET 8 Runtime
下载 .NET 8 ASP.NET Core RuntimeWindows x64):https://dotnet.microsoft.com/en-us/download/dotnet/8.0
如果是下载 SDK 压缩包,解压后将内容复制到 `C:\Program Files\dotnet\`
```powershell
Copy-Item -Path "解压目录\*" -Destination "C:\Program Files\dotnet\" -Recurse -Force
```
验证安装:
```powershell
dotnet --list-runtimes
```
应看到 `Microsoft.NETCore.App 8.0.x``Microsoft.AspNetCore.App 8.0.x`
### 2. 部署文件
`bin\Release\net8.0\` 整个文件夹复制到目标主机,例如 `C:\Mt5Bridge\`
### 3. 安装 MtApi5
在目标主机上运行 MtApi5 安装包(MSI),这会安装 `MT5Connector.dll` 等系统级依赖。
### 4. 挂载 EA
`MtApi5.ex5` 复制到 MT5 的 `MQL5\Experts\` 目录,在 MT5 任意图表上挂载运行。
### 5. 启动验证
**前台运行(调试用,关掉窗口程序就停)**
```powershell
cd C:\Mt5Bridge
dotnet Mt5Bridge.dll
```
看到以下输出即成功:
```
CommandTimeout set to 120000ms
Connecting to MT5 via MtApi5 on port 8228...
MT5 Connection: Connected
MT5 Bridge ready on http://localhost:8080
```
**后台运行(7×24 守护,推荐)**
```powershell
cd C:\Mt5Bridge
Start-Process -FilePath "dotnet" -ArgumentList "Mt5Bridge.dll" -WindowStyle Hidden
```
启动后不弹任何窗口,关掉 PowerShell 也不会停。
**关闭后台进程**
```powershell
# 先找 PID
Get-Process -Name dotnet
# 用 PID 杀掉
Stop-Process -Id <PID号> -Force
# 或者一行全杀
Get-Process dotnet -ErrorAction SilentlyContinue | Stop-Process -Force
```
---
## 三、配置 API Key
编辑 [Program.cs](Program.cs),修改第 34 行的 API Key
```csharp
const string API_KEY = "你的随机密码";
```
生成随机密码:
```powershell
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | %{[char]$_})
```
修改后重新编译,将新的 `Mt5Bridge.dll` 覆盖到目标主机。
### 更新 DLL(服务运行时)
Bridge 作为后台服务运行时,文件被锁定无法直接覆盖。需要先停止再覆盖:
```powershell
# 阻止任务计划再次触发
Stop-ScheduledTask -TaskName "Mt5Bridge"
# 仅结束当前 Bridge 进程
Get-CimInstance Win32_Process |
Where-Object { $_.Name -eq "dotnet.exe" -and $_.CommandLine -like "*Mt5Bridge.dll*" } |
Invoke-CimMethod -MethodName Terminate
# 现在可以覆盖 Mt5Bridge.dll 了
# 重新启动
Start-ScheduledTask -TaskName "Mt5Bridge"
```
说明:
- 仅更新 `Mt5Bridge.dll` / `Mt5Bridge.exe` 这一侧,就会影响 HTTP API、健康检查、参数校验、错误处理、重连逻辑等桥接服务行为。
- `Alpha Trend.ex5` 不属于桥接服务本体,不替换也不会影响 `/health``/account``/symbols``/rates/from-pos``/positions``/orders``/history/deals``/gvar``/order/check``/order/send``/stream/ticks/{symbol}``/stream/ticks-sse/{symbol}` 这些 API 的正常工作。
- 只有在你需要新版指标导出逻辑时,才需要同步替换 `Alpha Trend.ex5`。新版指标变化包括:只输出已收盘 K 线信号,以及使用带周期/参数作用域的 GlobalVariable 名称。
---
## 四、端口映射(公网访问)
### 云服务商端口映射
在云服务商控制台配置端口映射,例如:
| 字段 | 值 |
|------|-----|
| 类型 | TCP |
| 公网端口 | 13485 |
| 内网端口 | 8080 |
| 说明 | Mt5Bridge |
### Windows 防火墙
如果云服务商不自动放通,手动添加防火墙规则:
```powershell
New-NetFirewallRule -Name "Mt5Bridge" -DisplayName "Mt5Bridge 13485" -Direction Inbound -Protocol TCP -LocalPort 13485 -Action Allow
```
---
## 五、注册为后台服务(开机自启)
使用 Windows 任务计划程序,让 Bridge 开机自动启动、崩溃自动重启。
### 安装
```powershell
$action = New-ScheduledTaskAction -Execute "C:\Program Files\dotnet\dotnet.exe" -Argument "Mt5Bridge.dll" -WorkingDirectory "C:\Mt5Bridge"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName "Mt5Bridge" -Action $action -Trigger $trigger -Settings $settings
Start-ScheduledTask -TaskName "Mt5Bridge"
```
### 管理命令
```powershell
# 查看状态
Get-ScheduledTask -TaskName "Mt5Bridge" | Get-ScheduledTaskInfo
# 停止
Stop-ScheduledTask -TaskName "Mt5Bridge"
# 重启
Start-ScheduledTask -TaskName "Mt5Bridge"
# 删除
Unregister-ScheduledTask -TaskName "Mt5Bridge" -Confirm:$false
```
---
## 六、API 接口
所有接口均需认证,支持两种方式:
**方式一:Header(推荐,适用于代码调用)**
```
X-API-Key: 你的密码
```
**方式二:URL 参数(适用于浏览器直接访问)**
```
?key=你的密码
```
示例:
```
http://IP:端口/health?key=你的密码
```
### 健康检查
```
GET /health
```
**响应示例:**
```json
{
"status": "healthy",
"mt5_connected": true,
"mt5_version": "unknown",
"api_version": "1.0.0"
}
```
当 MT5 断开时,接口会返回 `503 Service Unavailable`
### 账户信息
```
GET /account
```
**响应字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| login | number | 账户号 |
| leverage | number | 杠杆 |
| balance | number | 余额 |
| equity | number | 净值 |
| profit | number | 浮动盈亏 |
| margin | number | 已用保证金 |
| margin_free | number | 可用保证金 |
| margin_level | number | 保证金比例 |
| currency | string | 账户币种 |
| server | string | 服务器名 |
| name | string | 账户名称 |
| company | string | 经纪商 |
| trade_allowed | bool | 是否允许交易 |
| trade_expert | bool | 是否允许 EA 交易 |
实际响应结构:
```json
{
"data": [
{
"login": 12345678,
"balance": 10000.0
}
],
"count": 1,
"format": "json"
}
```
### 品种信息
```
GET /symbols/{symbol}
```
bid/ask 从实时 tick 数据获取(自动等待最多 3 秒),避免品种刚加入 Market Watch 时返回 0 的问题。
**示例:** `/symbols/XAUUSDc`
**响应字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| name | string | 品种名称 |
| description | string | 描述 |
| digits | number | 小数位数 |
| point | number | 点值 |
| bid | number | 卖价 |
| ask | number | 买价 |
| spread | number | 点差 |
| spread_float | bool | 是否浮动点差 |
| volume_min | number | 最小手数 |
| volume_max | number | 最大手数 |
| volume_step | number | 手数步长 |
| trade_contract_size | number | 合约大小 |
| currency_base | string | 基础货币 |
| currency_profit | string | 利润货币 |
| category | string | 分类路径 |
### 实时 Tick
```
GET /symbols/{symbol}/tick
```
自动将品种加入 MT5 Market Watch,并等待最多 3 秒获取真实 tick 数据(解决品种未订阅时返回空值的问题)。
**示例:** `/symbols/XAUUSDc/tick`
**响应字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| time | string | 时间 |
| bid | number | 卖价 |
| ask | number | 买价 |
| last | number | 最新成交价 |
| volume | number | 成交量 |
| time_msc | string | 毫秒级时间 |
| volume_real | number | 真实成交量 |
### 实时 Tick 推送(WebSocket
```
WS /stream/ticks/{symbol}
```
MT5 每收到一条 tick 就立即推给所有订阅者,避免轮询开销。
-`X-API-Key` 认证(握手 Header 或 `?key=` query 参数)
- 连接时自动把品种加入 MT5 Market Watch;最后一个订阅者断开时自动移除
- 同一品种可被多个客户端同时订阅,互不影响
- 每个品种最多 50 个订阅者(WS + SSE 共用),超出返回 `429`
- 每 30 秒发一条心跳包,无 tick 时也发,用于穿透 NAT 保持连接
**Python 示例(需 `pip install websocket-client`):**
```python
import websocket, json
ws = websocket.WebSocketApp(
"ws://你的IP:13485/stream/ticks/XAUUSDc",
header=["X-API-Key: 你的密码"],
on_message=lambda ws, msg: print(json.loads(msg)),
)
ws.run_forever()
```
**浏览器示例(用 query 参数传 key):**
```js
const ws = new WebSocket("ws://你的IP:13485/stream/ticks/XAUUSDc?key=你的密码");
ws.onmessage = (e) => console.log(JSON.parse(e.data));
```
**推送格式(每条 tick 一帧 JSON):**
```json
{
"type": "tick",
"symbol": "XAUUSDc",
"time": "2026-07-08T10:30:45",
"bid": 4180.0,
"ask": 4180.5,
"last": 4180.2,
"volume": 100,
"time_msc": "2026-07-08T10:30:45.123000",
"flags": 6
}
```
心跳包:`{"type":"heartbeat","time":"2026-07-08T10:31:15"}`
### 实时 Tick 推送(SSE,不能用 WebSocket 时)
```
GET /stream/ticks-sse/{symbol} → text/event-stream
```
面向无法建 WebSocket 的客户端(部分老浏览器、内网代理、curl 等)。
```bash
curl -N -H "X-API-Key: 你的密码" http://你的IP:13485/stream/ticks-sse/XAUUSDc
```
每条 tick 一帧 SSE
```
data: {"type":"tick","symbol":"XAUUSDc","bid":4180.0,...}
data: {"type":"heartbeat","time":"2026-07-08T10:31:15"}
```
### 历史 K 线
```
GET /rates/from-pos?symbol={symbol}&timeframe={timeframe}&start_pos={start}&count={count}
```
**参数说明:**
| 参数 | 说明 |
|------|------|
| symbol | 品种名称 |
| timeframe | 周期:`TIMEFRAME_M1` / `M5` / `M15` / `M30` / `H1` / `H4` / `D1` |
| start_pos | 起始位置(0=最新) |
| count | 获取数量 |
**示例:** `/rates/from-pos?symbol=XAUUSDc&timeframe=TIMEFRAME_H1&start_pos=0&count=100`
**响应字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| time | string | 开盘时间 |
| open | number | 开盘价 |
| high | number | 最高价 |
| low | number | 最低价 |
| close | number | 收盘价 |
| tick_volume | number | Tick 成交量 |
| spread | number | 点差 |
| real_volume | number | 真实成交量 |
### 当前持仓
```
GET /positions[?symbol={symbol}]
```
symbol 为可选过滤参数。
**响应字段:**
| 字段 | 类型 | 说明 |
|------|------|------|
| ticket | number | 持仓编号 |
| symbol | string | 品种 |
| type | number | 类型(0=买, 1=卖) |
| volume | number | 手数 |
| price_open | number | 开仓价 |
| sl | number | 止损价 |
| tp | number | 止盈价 |
| price_current | number | 当前价 |
| swap | number | 隔夜息 |
| profit | number | 盈亏 |
| comment | string | 注释 |
| magic | number | Magic Number |
### 平仓
```
POST /position/close
```
```json
{ "ticket": 12345678 }
{ "ticket": 12345678, "volume": 0.05 } // 部分平仓
```
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| ticket | number | ✅ | 持仓编号 |
| volume | number | ❌ | 平仓手数;不传/0=全平,>0=部分平仓 |
| deviation | number | ❌ | 允许滑点(默认 10) |
### 改持仓 SL/TP
```
POST /position/modify
```
```json
{ "ticket": 12345678, "sl": 4170.0, "tp": 4190.0 }
```
`sl`/`tp` 设为 `0` 表示清除。
### 对冲平仓(节省点差)
```
POST /position/close-by
```
```json
{ "position": 111, "position_by": 222 }
```
两张持仓必须 **同品种 + 反向**。MT5 用净额结算,只收一次点差。
### 批量平仓
```
POST /positions/close-batch
```
```json
{ "magic": 123456 }
{ "symbol": "XAUUSDc", "magic": 123456 }
```
`symbol``magic` **至少传一个**。返回 `{closed, failed, data:[...]}`
### 移动止损
服务端不做轮询,由客户端结合 `/positions` 拉取和 `/position/modify` 实现:
```python
while True:
for p in bridge.positions():
cur = p["price_current"]
new_sl = cur - 50 if p["type"] == 0 else cur + 50
if abs(new_sl - p["sl"]) >= 10:
bridge.modify_position(p["ticket"], sl=new_sl)
time.sleep(1)
```
也可订阅 WebSocket tick 流(`/stream/ticks/{symbol}`),按 tick 回调触发,更实时。
### 挂单
```
GET /orders?symbol={symbol}
```
symbol 为可选参数,不传则返回所有挂单。
### 撤单
```
POST /order/cancel
```
```json
{ "ticket": 87654321 }
```
### 改挂单
```
POST /order/modify
```
```json
{ "ticket": 87654321, "price": 4180.0, "sl": 4170.0, "tp": 4190.0 }
```
### 订单预检
```
POST /order/check
```
**填充模式自动适配**:服务端会根据品种的 `SYMBOL_FILLING_MODE` 自动选择经纪商支持的填充模式。如果请求的 `type_filling` 不被支持,会按 IOC(1) → FOK(0) → RETURN(2) 顺序降级,无需客户端手动判断。
**请求体:**
```json
{
"action": 1,
"symbol": "XAUUSDc",
"volume": 0.01,
"order_type": 0,
"price": 4180.0,
"sl": 4170.0,
"tp": 4190.0,
"magic": 123456,
"comment": "test",
"deviation": 10,
"type_filling": 0
}
```
| 字段 | 类型 | 说明 |
|------|------|------|
| action | number | 交易操作类型 |
| symbol | string | 品种 |
| volume | number | 手数 |
| order_type | number | 订单类型 |
| price | number | 价格 |
| sl | number | 止损 |
| tp | number | 止盈 |
| magic | number | Magic Number |
| comment | string | 注释 |
| deviation | number | 偏差 |
| type_filling | number | 填充模式:0=FOK, 1=IOC, 2=RETURN(自动适配,不传也行) |
**type_filling 说明:**
| 值 | 含义 | 说明 |
|------|------|------|
| 0 | FOK (Fill or Kill) | 必须全部成交,否则取消 |
| 1 | IOC (Immediate or Cancel) | 能成交多少成交多少 |
| 2 | RETURN | 剩余部分留在订单簿 |
> 不同经纪商支持的填充模式不同(如 ICMarkets 只支持 IOC),服务端会自动降级,客户端无需关心。
### 下单
```
POST /order/send
```
`/order/check` 相同,`type_filling` 会自动适配经纪商支持的填充模式。
**请求体:**
```json
{
"request": {
"action": 1,
"symbol": "XAUUSDc",
"volume": 0.01,
"order_type": 0,
"price": 4180.0,
"sl": 4170.0,
"tp": 4190.0,
"magic": 123456,
"comment": "test",
"deviation": 10,
"type_filling": 0
}
}
```
### 历史成交
```
GET /history/deals?date_from={from}&date_to={to}&symbol={symbol}
```
**示例:** `/history/deals?date_from=2026-07-01&date_to=2026-07-03&symbol=XAUUSDc`
### 全局变量(MQL5 信号桥)
让 MQL5 指标/EA 将信号写入 GlobalVariablePython 通过 Bridge 读取。无需翻译 MQL5 代码。
```
GET /gvar
GET /gvar/{name}
POST /gvar/{name}
DELETE /gvar/{name}
```
**列出所有全局变量:**
```
GET /gvar
```
**读取指定变量:**
```
GET /gvar/{name}
```
**写入变量:**
```
POST /gvar/{name}
Body: {"value": 75.5}
```
**删除变量:**
```
DELETE /gvar/{name}
```
**MQL5 侧(仅在你要升级指标信号导出逻辑时才需要修改):**
```mql5
int last_closed = rates_total - 2;
string key = StringFormat("MY_SIGNAL_%s_%s", _Symbol, EnumToString(_Period));
GlobalVariableSet(key, Alpha[last_closed]);
```
如果你没有替换新版 `Alpha Trend.ex5`,远程 `/gvar` 中仍会看到旧键名,例如:
```text
AT_Alpha_XAUUSDc
AT_Buy_XAUUSDc
AT_Offset_XAUUSDc
AT_Sell_XAUUSDc
AT_Trend_XAUUSDc
```
这不影响桥接 API 本身,只表示 MT5 端仍在运行旧版指标。
**Python 侧读取:**
```python
resp = requests.get(f"{BRIDGE}/gvar/MY_SIGNAL_XAUUSDc", headers=HEADERS)
signal = resp.json()["value"]
```
---
## 七、调用示例
### 浏览器
直接在地址栏输入(带 `?key=` 参数):
```
http://IP:端口/health?key=你的密码
http://IP:端口/account?key=你的密码
http://IP:端口/symbols/XAUUSDc/tick?key=你的密码
```
### PowerShell
```powershell
# 健康检查
Invoke-RestMethod -Uri "http://IP:端口/health" -Headers @{"X-API-Key"="你的密码"}
# 账户信息
Invoke-RestMethod -Uri "http://IP:端口/account" -Headers @{"X-API-Key"="你的密码"}
# 实时行情
Invoke-RestMethod -Uri "http://IP:端口/symbols/XAUUSDc/tick" -Headers @{"X-API-Key"="你的密码"}
# 获取 100 根 H1 K 线
Invoke-RestMethod -Uri "http://IP:端口/rates/from-pos?symbol=XAUUSDc&timeframe=TIMEFRAME_H1&start_pos=0&count=100" -Headers @{"X-API-Key"="你的密码"}
```
### Python
```python
import requests
BRIDGE = "http://IP:端口"
HEADERS = {"X-API-Key": "你的密码"}
# 健康检查
resp = requests.get(f"{BRIDGE}/health", headers=HEADERS)
print(resp.json())
# 账户信息
resp = requests.get(f"{BRIDGE}/account", headers=HEADERS)
print(resp.json())
# 实时行情
resp = requests.get(f"{BRIDGE}/symbols/XAUUSDc/tick", headers=HEADERS)
tick = resp.json()
print(f"Bid: {tick['data'][0]['bid']}, Ask: {tick['data'][0]['ask']}")
# 获取 K 线
resp = requests.get(f"{BRIDGE}/rates/from-pos", headers=HEADERS, params={
"symbol": "XAUUSDc",
"timeframe": "TIMEFRAME_H1",
"start_pos": 0,
"count": 100
})
print(resp.json())
# 下单
resp = requests.post(f"{BRIDGE}/order/send", headers=HEADERS, json={
"request": {
"action": 1,
"symbol": "XAUUSDc",
"volume": 0.01,
"order_type": 0,
"price": 4180.0,
"sl": 4170.0,
"tp": 4190.0,
"magic": 123456,
"comment": "test",
"deviation": 10,
"type_filling": 0
}
})
print(resp.json())
```
### curl
```bash
# 健康检查
curl -H "X-API-Key: 你的密码" http://IP:端口/health
# 账户信息
curl -H "X-API-Key: 你的密码" http://IP:端口/account
# 下单
curl -X POST http://IP:端口/order/send \
-H "Content-Type: application/json" \
-H "X-API-Key: 你的密码" \
-d '{"request":{"action":1,"symbol":"XAUUSDc","volume":0.01,"order_type":0,"price":4180.0,"sl":4170.0,"tp":4190.0,"magic":123456,"comment":"test","deviation":10,"type_filling":0}}'
```
---
## 八、迁移到新主机
### 需要复制的内容
| 从哪里 | 复制什么 | 放到哪里 |
|--------|---------|---------|
| 本地 `bin\Release\net8.0\` | 整个文件夹 | 新主机 `C:\Mt5Bridge\` |
| MtApi5 安装包 | 运行 MSI 安装 | 新主机 |
| MtApi5 项目 | `MtApi5.ex5` | 新主机 MT5 的 `MQL5\Experts\` |
### 新主机操作步骤
1. 安装 .NET 8 ASP.NET Core Runtime
2. 运行 MtApi5 MSI 安装包
3. 复制 `bin\Release\net8.0\``C:\Mt5Bridge\`
4. 复制 `MtApi5.ex5` 到 MT5 Experts 目录
5. 在 MT5 图表上挂载 MtApi5 EA
6. 配置云服务商端口映射(如需要外网访问)
7. 配置 Windows 防火墙
8. 注册任务计划程序实现开机自启
9. 验证:`Invoke-RestMethod -Uri "http://localhost:8080/health" -Headers @{"X-API-Key"="密码"}`
---
## 九、常用维护命令
```powershell
# 查看 Bridge 是否在运行
Get-ScheduledTask -TaskName "Mt5Bridge" | Get-ScheduledTaskInfo
# 查看是否在监听端口
netstat -ano | findstr 8080
# 查看 .NET 运行时版本
dotnet --list-runtimes
# 手动启动 Bridge(前台调试)
cd C:\Mt5Bridge
dotnet Mt5Bridge.dll
# 杀死 Bridge 进程
Stop-ScheduledTask -TaskName "Mt5Bridge"
```
---
## 十、安全建议
1. **API Key 必须修改**:部署前把默认的 `mt5bridge-2024-secret-key` 改成随机字符串
2. **端口号不要用默认的**:当前使用 8080,如要更换建议用 10000 以上的随机端口
3. **防火墙白名单**:如果只有固定 IP 访问,在云服务商安全组里限制来源 IP
4. **不要用 HTTP 明文传输敏感数据**:可配合 Nginx Proxy Manager 加 HTTPS
5. **定期更换 API Key**:修改 `Program.cs` 中的 Key 后重新编译部署