内容
# mt5-httpapi
在真实的Windows虚拟机(Docker + QEMU/KVM)中运行MetaTrader 5,并添加REST API以支持程序化交易。没有Wine的兼容层,也没有临时解决方案——一个合法的Windows环境运行完整的MT5终端,以可移植模式运行。
支持多个经纪商和多个账户同时在同一个虚拟机上运行。每个终端都有自己的Python API进程在虚拟机内运行,并且有一个始终在线的nginx sidecar通过单个主机端口`http://localhost:8888/<broker>/<account>/...`来管理它们。您可以同时运行两个FTMO挑战,或混合经纪商——无论您需要什么。
**内置技术分析。** 一个`POST /symbols/<symbol>/rates/ta`返回OHLC条形数据*和*您请求的指标——RSI、MACD、Bollinger、ADX、VWAP、Ichimoku、Order Blocks、Fair Value Gaps、BOS/CHoCH、swing结构、S/R级别、数十种更多指标——在一个往返中。[wickworks](https://github.com/psyb0t/docker-wickworks) sidecar运行在mt5容器的网络命名空间中,因此API外部的任何内容都无法访问它。只有基本要素——原始指标系列和结构事实,没有解释性信号(在消费者中构建)。无需客户端TA堆栈,无需pandas管道,无需`pip install`舞蹈——只需请求您需要的指标并在获取条形数据后返回它们。参见[技术分析](#technical-analysis)。
## 目录
- [免责声明](#-免责声明)
- [推荐经纪商](#推荐经纪商)
- [需求](#需求)
- [当512M不够用](#当512m不够用)
- [实际使用](#实际使用-4个终端运行在2个vCPU和512M内存上-轻负载)
- [快速入门](#快速入门)
- [配置](#配置)
- [`config/config.yaml`](#configconfigyaml)
- [`config/setup.bat`](#configsetupbat)
- [`mt5installers/`](#mt5installers)
- [API](#api)
- [回测](#回测)
- [健康检查](#健康检查)
- [终端](#终端)
- [账户](#账户)
- [经纪商时间与真实UTC](#经纪商时间与真实UTC)
- [符号](#符号) — 列表、信息、ticks、**费率**、**费率 + 技术分析**
- [仓位](#仓位)
- [订单](#订单)
- [交易结果](#交易结果)
- [历史数据](#历史数据)
- [MCP接口](#mcp接口)
- [示例](#示例)
- [优化指南](#优化指南)
- [Go客户端](#go客户端)
- [技术分析](#技术分析) — 通过wickworks在服务器端,或通过pandas-ta在客户端
- [生成目标](#生成目标)
- [端口](#端口)
- [Tailscale(可选)](#tailscale可选)
- [Cloudflare Tunnel(可选)](#cloudflare-tunnel可选)
- [项目结构](#项目结构)
- [并发性和背压](#并发性和背压)
- [日志](#日志)
- [许可证](#许可证)
## ⚠️ 免责声明
这是一个用于自动化交易的工具。如果您亏空了账户,责任在于您。请先使用模拟账户,测试您的策略,并在您的算法购买顶部时不要哭诉。
## 推荐经纪商
- [RoboForex](https://my.roboforex.com/en/?a=zswg)
- [TeleTrade](https://my.teletrade-dj.com/agent_pp.html?agent_pp=26834897)
## 需求
- 启用KVM的Linux主机(`/dev/kvm`)
- Docker + Docker Compose
- ~20 GB磁盘(4 GB ISO + 11 GB虚拟机 + MT5安装)
- 5 GB内存(用于Windows虚拟机)
容器自带`512M`内存限制和`5G`内存交换限制——所以虚拟机主要运行在主机交换空间上。tiny11 + debloat脚本空载占用约1.4 GB,MT5 + Python API在此基础上增加一点。对于低容量使用(下单、查询仓位、拉取小窗口的最近蜡烛)来说,这足够了——它对交换空间的敏感度不高,延迟不是问题。noVNC用于观察安装进度;之后,请忽略UI并直接使用REST API。
### 当512M不够用
MT5终端缓存每个加载的图表的进程内存,并且永远不会释放它。当您开始回填深度历史数据(例如,抓取所有符号×多个时间范围×数年的数据)时,每个终端的内存占用都会增加到多GB。容器限制为512M,所有内容都会被分页到主机交换空间中。Windows虚拟机管理器不知道页面在磁盘上,进程似乎无响应,Windows开始静默修剪/杀死它们。cmd.exe包装器保持打开但空白,Python API进程已死,隧道挂起。
如果您正在进行**大量历史数据抓取**,请至少执行以下操作之一:
- 将容器内存限制提高到实际需求(4-8 GB用于多终端重度抓取)。
- 每次只抓取一个经纪商(停止其他经纪商),以便只有一个终端累积缓存。
- 通过`POST /terminal/restart`重启终端,以刷新图表缓存。
- 将时间范围分段,而不是一次性拉取10年的M1数据。
或者,如果您正在大量使用MT5,请在专用机器上运行它。
### 实际使用:4个终端运行在2个vCPU + 512M内存上(仅轻负载)

4个MT5终端(RoboForex,2x TeleTrade,FTMO)同时运行在2个vCPU上,仅有512M真实内存。CPU在启动时飙升到100%,空载时降至约15%。总内存占用:2.1 GB,舒适地使用交换空间。**只要不进行深度历史数据抓取**,在空载/轻度轮询时,您可以容纳10+个终端。
## 快速入门
```bash
# 1. 从模板创建您的配置文件
cp config/config.yaml.example config/config.yaml
# 编辑config/config.yaml:api_token、accounts、terminals(见下文)
# 2. 将经纪商的MT5安装程序放入mt5installers/
# 命名为:mt5setup-<broker>.exe
cp ~/Downloads/mt5setup.exe mt5installers/mt5setup-roboforex.exe
# 3. 启动它
make up
```
首次运行时,下载[tiny11](https://archive.org/details/tiny-11-NTDEV)(精简版Windows 11,约4 GB),安装它(约10分钟),然后自动设置Python + MT5。之后,启动时间约为1分钟。去买杯咖啡吧。
## 配置
### `config/config.yaml`
单一真实来源——gitignored。复制`config/config.yaml.example`并编辑。结构:
```yaml
# API身份验证的Bearer令牌。为空=无身份验证。
api_token: "在此处粘贴openssl rand -hex 32的输出"
# 虚拟机每N分钟自动重启一次(刷新DWM/VirtIO-GPU状态)。0=禁用。
reboot_interval: 30
# 默认策略测试器超时时间。POST /backtest可以按作业覆盖。
backtest_timeout: "6h"
tailscale:
auth_key: "" # tskey-auth-... - 空值禁用tailscale sidecar
login_server: "" # Headscale URL;空值=Tailscale云
# VM的额外pip包。MetaTrader5/flask/waitress/flask-compress始终已安装。
requirements: []
# 经纪商凭证,按经纪商→账户名称组织。
accounts:
roboforex:
main:
login: 12345678
server: "RoboForex-Pro"
password: "your_password"
demo:
login: 87654321
server: "RoboForex-Demo"
password: "demo_password"
# 终端实例 - 每个条目一个MT5进程 + 一个API进程。
terminals:
- broker: roboforex
account: main
port: 6542
utc_offset: "3h"
symbol_suffix: ""
- broker: roboforex
account: demo
port: 6543
utc_offset: "3h"
symbol_suffix: ""
- broker: roboforex
account: tester
port: 6544
utc_offset: "3h"
mode: backtest # 不要自动启动terminal64.exe;为/backtest作业保留
symbol_suffix: ".r" # 可选的显式后缀,用于测试仪符号重映射
```
每个字段的注释:
- **`api_token`** - 如果设置,每个端点都需要`Authorization: Bearer <token>`。为空=开放。使用`openssl rand -hex 32`生成。
- **`reboot_interval`** - 调度重启之间的分钟数。`0`禁用。
- **`backtest_timeout`** - `POST /backtest`的默认策略测试器超时时间。接受与`utc_offset`相同的持续时间语法:`"6h"`、`"30m"`、`"3h30m"`、`"90m"`,或裸数字解释为小时。每个请求的表单字段`timeout`覆盖它。
- **`tailscale.auth_key`** / **`tailscale.login_server`** - 参见[Tailscale](#tailscale可选)。空`auth_key`跳过sidecar。
- **`requirements`** - VM中安装的额外pip包。
- **`accounts.<broker>.<account>`** - `broker`必须与安装程序名称(`mt5setup-<broker>.exe`)和`terminals[]`中的`broker`字段匹配。`account`必须与`terminals[]`中的`account`字段匹配。
- **`terminals[].port`** - 此终端的容器内部HTTP API端口。只有nginx和mt5容器与之通信;不暴露给主机。
- **`terminals[].instance`** - 可选的终端克隆名称。当同一个`broker` / `account`多次出现时使用此功能。没有`queue`字段在`config.yaml`中;调用者通过将请求路由到`/ <broker> / <account> / <instance> / ...`来选择特定的克隆。缺失/空=`default`,并且默认实例还保留旧的`/<broker>/<account>/...`别名。
- **`terminals[].utc_offset`** - 经纪商服务器的UTC偏移量,用于将所有时间戳归一化为真实的UTC(参见下面的[经纪商时间与真实UTC](#经纪商时间与真实UTC))。可选 - 默认值为`0`。接受`"3h"`、`"3h30m"`、`"-2h"`、`"90m"`,或裸数字解释为小时。常见值:RoboForex/FTMO`"3h"`,TeleTrade`"2h"`。
- **`terminals[].mode`** - `live`(默认)或`backtest`。`live`保持`terminal64.exe`运行,以便MT5 SDK保持初始化状态,适用于实时交易端点。`backtest`准备相同的可移植目录,但**不**启动`terminal64.exe`,使数据目录可供Strategy Tester子进程使用 - 参见[Backtest](#backtest)。MT5每个可移植数据目录都是单实例,因此测试不能与`live`终端运行。
- **`terminals[].symbol_suffix`** - Strategy Tester重映射的符号后缀的可选显式值。如果设置,mt5-httpapi在`[Tester].Symbol`已经以该后缀结尾时追加它。示例:`"p"`、`".p"`、`"-mini"`。使用`""`表示无后缀。
每个终端安装到`<broker>/base/`并在启动时复制到`<broker>/<account>/`,因此同一个经纪商的多个账户不会相互冲突。
### `config/setup.bat`
自定义命令在每次VM启动时运行,在MT5启动之前。将您需要的Windows设置放在这里。
### `mt5installers/`
在此处放置经纪商的MT5安装程序。命名它们`mt5setup-<broker>.exe`,每个安装程序都会自动获得其自己的可移植安装。
## API
所有终端都通过单个主机端口上的nginx提供服务。默认入口点:`http://localhost:8888`(仅限回环)。每个终端都位于其自己的路径前缀:
```
http://localhost:8888/<broker>/<account>/...
```
当您配置显式终端实例时,请按以下方式路由:
```
http://localhost:8888/<broker>/<account>/<instance>/...
```
示例:在`config.yaml`的`terminals:`列表中配置`roboforex/main`终端后,访问`http://localhost:8888/roboforex/main/ping`。前缀`/ <broker> / <account>`由nginx剥离,剩余部分代理到VM内的终端API进程。
如果在`config.yaml`中设置了`api_token`,请在每个请求中包含令牌:
```bash
export MT5_API_TOKEN=$(grep ^api_token config/config.yaml | awk -F'"' '{print $2}')
curl -H "Authorization: Bearer $MT5_API_TOKEN" http://localhost:8888/roboforex/main/ping
```
### 健康检查
| 方法 | 端点 | 描述 |
| ------ | -------- | ----------------- |
| GET | `/ping` | 这个东西还活着吗? |
| GET | `/error` | 最后一个MT5错误 |
**GET `/ping`**:
```json
{ "status": "ok" }
```
**GET `/error`**:
```json
{ "code": 1, "message": "Success" }
```
### 终端
| 方法 | 端点 | 描述 |
| ------ | -------------------- | ------------------------- |
| GET | `/terminal` | 终端信息 |
| POST | `/terminal/init` | 初始化MT5连接 |
| POST | `/terminal/shutdown` | 杀死MT5 |
**GET `/terminal`**:
```json
{
"build": 5602,
"codepage": 0,
"commondata_path": "C:\\Users\\Docker\\AppData\\Roaming\\MetaQuotes\\Terminal\\Common",
"community_account": false,
"community_balance": 0.0,
"community_connection": false,
"company": "Your Broker Inc.",
"connected": true,
"data_path": "C:\\Users\\Docker\\Desktop\\Shared\\mybroker",
"dlls_allowed": true,
"email_enabled": false,
"ftp_enabled": false,
"language": "English",
"maxbars": 100000,
"mqid": false,
"name": "MyBroker MetaTrader 5",
"notifications_enabled": false,
"path": "C:\\Users\\Docker\\Desktop\\Shared\\mybroker",
"ping_last": 0,
"retransmission": 0.003,
"trade_allowed": true,
"tradeapi_disabled": false,
"broker_utc_offset_hours": 3,
"broker_utc_offset_seconds": 10800
}
```
**POST `/terminal/init`** 和 **POST `/terminal/shutdown`**:
```json
{ "success": true }
```
API在第一个请求时自动初始化。您几乎不需要手动调用这些。
### 账户
| 方法 | 端点 | 描述 |
| ------ | ---------- | -------------------- |
| GET | `/account` | 当前账户信息 |
**GET `/account`**:
```json
{
"login": 12345678,
"name": "Your Name",
"server": "MyBroker-Server",
"company": "Your Broker Inc.",
"currency": "USD",
"currency_digits": 2,
"balance": 10000.0,
"credit": 0.0,
"profit": 0.0,
"equity": 10000.0,
"margin": 0.0,
"margin_free": 10000.0,
"margin_level": 0.0,
"margin_initial": 0.0,
"margin_maintenance": 0.0,
"margin_so_call": 70.0,
"margin_so_so": 20.0,
"margin_so_mode": 0,
"assets": 0.0,
"liabilities": 0.0,
"commission_blocked": 0.0,
"leverage": 500,
"limit_orders": 0,
"trade_allowed": true,
"trade_expert": true,
"trade_mode": 0,
"fifo_close": false
}
```
### 经纪商时间与真实UTC
MT5有一个臭名昭的时区陷阱:它返回的每个时间戳(tick `time`,rate `time`,position `time`,deal `time_msc`等)是**经纪商服务器的壁钟时间**,编码为unix整数。看起来像UTC,但不是。RoboForex/FTMO运行UTC+3,TeleTrade运行UTC+2 - 所以在RoboForex上捕获的tick(`22:57`)报告为unix `01:57`(3小时提前),在TeleTrade上报告为`00:57`(2小时提前)。
MT5 Python SDK不公开`TimeCurrent()` / `TimeGMT()`, 所以API不能自动检测这一点。相反,在`config.yaml`中为每个终端设置`utc_offset`:
```yaml
terminals:
- broker: roboforex
account: main
port: 6542
utc_offset: "3h"
```
设置后,API:
- 从每个输出时间戳(tick time, rate time, position/order/deal times, `_msc`字段)中减去偏移量,因此响应为真实的UTC unix。
- 将偏移量添加到传入的`from`/`to`查询参数中,因此调用者始终传递真实的UTC unix并返回真实的UTC unix。
通过`GET /terminal`检查 - 字段`broker_utc_offset_hours`和`broker_utc_offset_seconds`显示当前生效的偏移量。
如果省略`utc_offset`(或`0`),API会传递原始经纪商时间戳(pre-1.8行为)。
### 符号
| 方法 | 端点 | 描述 |
| ------ | ------------------------ | ----------------------------------------- |
| GET | `/symbols` | 列出符号 (`?group=*USD*`) |
| GET | `/symbols/:symbol` | 符号详情 |
| GET | `/symbols/:symbol/tick` | 最新报价 |
| GET | `/symbols/:symbol/rates` | OHLCV 蜡烛图 (`?timeframe=H1&count=100`, `?timeframe=H1&from=<unix>&count=-100`, 或 `?timeframe=H1&from=<unix>&to=<unix>`) |
| POST | `/symbols/:symbol/rates/ta` | 与 `/rates` 相同的查询参数;JSON 正文 `{indicators: {...}, recentBars?: N}`. 返回蜡烛图 + wickworks 技术分析。 |
| GET | `/symbols/:symbol/ticks` | 报价数据 (`?count=100`, `?from=<unix>&count=-100`, 或 `?from=<unix>&to=<unix>`) |
**GET `/symbols`** — 符号名称数组:
```json
["EURUSD", "GBPUSD", "ADAUSD", "BTCUSD", "..."]
```
**GET `/symbols/:symbol`** — 完整符号信息:
```json
{
"name": "EURUSD",
"description": "欧元 vs 美元",
"path": "Markets\\Forex\\Major\\EURUSD",
"currency_base": "EUR",
"currency_profit": "USD",
"currency_margin": "EUR",
"digits": 5,
"point": 1e-05,
"spread": 30,
"spread_float": true,
"trade_contract_size": 100000.0,
"trade_tick_size": 1e-05,
"trade_tick_value": 1.0,
"trade_tick_value_profit": 1.0,
"trade_tick_value_loss": 1.0,
"volume_min": 0.01,
"volume_max": 100.0,
"volume_step": 0.01,
"volume_limit": 0.0,
"trade_mode": 4,
"trade_calc_mode": 0,
"trade_exemode": 2,
"trade_stops_level": 1,
"trade_freeze_level": 0,
"swap_long": -11.0,
"swap_short": 1.14064,
"swap_mode": 1,
"swap_rollover3days": 3,
"margin_initial": 0.0,
"margin_maintenance": 0.0,
"margin_hedged": 50000.0,
"filling_mode": 3,
"expiration_mode": 15,
"order_gtc_mode": 0,
"order_mode": 127,
"bid": 1.18672,
"ask": 1.18702,
"bidhigh": 1.18845,
"bidlow": 1.1847,
"askhigh": 1.1885,
"asklow": 1.18475,
"last": 0.0,
"time": 1771027139,
"select": true,
"visible": true,
"custom": false,
"session_deals": 0,
"session_buy_orders": 0,
"session_sell_orders": 0,
"session_open": 1.1869,
"session_close": 1.18698,
"price_change": -0.0219,
"bank": "",
"basis": "",
"category": "",
"exchange": "",
"isin": "",
"..."
}
```
以下是您实际会使用的字段:
| 字段 | 描述 |
| ----------------------------------------- | ------------------------------------------ |
| `bid`, `ask` | 当前价格 |
| `digits` | 价格小数位 |
| `point` | 最小价格变动 |
| `trade_tick_size` | 最小价格变动单位 |
| `trade_tick_value` | 每 tick 利润/亏损 |
| `trade_contract_size` | 合同大小(外汇 100000) |
| `volume_min`, `volume_max`, `volume_step` | 手数限制 |
| `spread` | 当前点差 |
| `swap_long`, `swap_short` | 过夜掉期利率 |
| `trade_stops_level` | SL/TP 距离价格的最小距离(点) |
**GET `/symbols/:symbol/tick`**:
```json
{
"time": 1771150549,
"bid": 0.3001,
"ask": 0.3004,
"last": 0.0,
"volume": 0,
"time_msc": 1771150549145,
"flags": 1030,
"volume_real": 0.0
}
```
**GET `/symbols/:symbol/rates`** — OHLCV 蜡烛图数组:
时间范围:`M1` `M2` `M3` `M4` `M5` `M6` `M10` `M12` `M15` `M20` `M30` `H1` `H2` `H3` `H4` `H6` `H8` `H12` `D1` `W1` `MN1`
```json
{
"time": 1771128000,
"open": 0.2962,
"high": 0.3006,
"low": 0.2922,
"close": 0.2979,
"tick_volume": 4755,
"spread": 30,
"real_volume": 0
}
```
`time` 是蜡烛图的开始时间,unix 时间戳(秒)。
查询参数(利率):
| 参数 | 行为 |
| --- | --- |
| `timeframe` | 默认 `M1` |
| `count` | 有符号整数(默认 `100`)。正数 = 从 `from` 开始的 N 条(或最后 N 条,如果没有 `from`)。负数 = `\|N\|` 条,结束于 `from`。零 = 空结果。与 `to` 互斥。 |
| `from` | 锚点(真实 UTC)。省略 = 现在。接受 unix 秒,`YYYY_MM_DD_HH_MM_SS`,或 `YYYY_MM_DD`(UTC 时间 midnight)。 |
| `to` | 范围结束(真实 UTC,与 `from` 相同格式)。需要 `from`。返回 `[from, to]` 中的所有蜡烛图,超出 `terminal_info().maxbars` 的限制。 与 `count` 互斥。 |
示例:
- `?timeframe=H1&count=100` — 当前蜡烛图前 100 条 H1 蜡烛图
- `?timeframe=H1&from=1700000000&count=100` — 从锚点开始的 100 条蜡烛图
- `?timeframe=H1&from=2024_01_15&count=-100` — 截至 UTC 时间 2024-01-15 午夜的 100 条蜡烛图
- `?timeframe=H1&from=2024_01_15_09_30_00&to=2024_01_15_16_00_00` — 窗口内的每条 H1 蜡烛图
选择适合的模式:`count` 当您想要确切的 N 条蜡烛图并且不关心结束时间时使用;`to` 当您有明确的窗口并想要窗口内的所有内容时使用。`count` 模式在内部计算范围并填充周末/节假日;而 `to` 模式直接传递给 `copy_rates_range`。
**最大蜡烛图限制:** MT5 每次请求最多返回 `terminal_info().maxbars` 行(默认 100,000 — 在 `GET /terminal` 处可见)。对于长时间的回补(例如 1 年的 M1 ≈ 525k 条蜡烛图),请分段客户端时间范围并拼接结果。
符号自动选择 MarketWatch — 首次访问时自动选择。无需手动选择即可补丁交易不频繁的工具。
**POST `/symbols/:symbol/rates/ta`** — 获取与 `GET /symbols/:symbol/rates` 相同的蜡烛图(相同的查询参数:`timeframe`, `count`, `from`, `to`),然后将它们转发给 [wickworks](https://github.com/psyb0t/docker-wickworks) 技术分析侧车进行指标分析。返回两者作为兄弟节点:
```json
{
"symbol": "EURUSD",
"timeframe": "H1",
"bars": [ { "time": 1771146000, "open": 1.0832, "high": 1.0840, "low": 1.0828, "close": 1.0835, "tick_volume": 1234, "spread": 1, "real_volume": 0 } ],
"ta": { "indicators": { "rsi": [ ... ], "macd": { ... } }, "...": "wickworks response" }
}
```
JSON 正文:
| 字段 | 必需 | 含义 |
| --- | --- | --- |
| `indicators` | 是 | 非空对象 — wickworks 指标规格。每个条目将输出键映射到 `true`(使用默认值运行)或平面参数对象(例如 `{"length": 20, "std": 2}`);添加 `"type": "<name>"` 仅当输出键与指标名称不同时(例如 `{"rsi21": {"type": "rsi", "length": 21}}` 运行第二个 RSI 并使用自定义键)。请参阅 [wickworks 指标目录](https://github.com/psyb0t/docker-wickworks#available-indicators) 获取完整的类型、参数和输出形状列表。 |
| `recentBars` | 否 | 目前 **无效** 在 wickworks v0.3.0 中 — 请求架构接受但未使用(保留用于未来信号标记输出)。要获取较少的回补条目,请降低 `count` 查询字符串或在客户端切片。 |
侧车在 mt5 容器的 net 命名空间内运行,无需发布端口 — 只有 mt5 进程(通过此 API 扩展)可以访问它。通过 `wickworks:` 在 `config.yaml` 中配置(默认为 `http://20.20.20.1:8000/`,从 Windows VM 内部看到的 dockurr 网关 IP)。
示例:
```bash
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"indicators":{"rsi":true,"macd":true},"recentBars":50}' \
"$MT5_API_URL/symbols/EURUSD/rates/ta?timeframe=H1&count=200"
```
**GET `/symbols/:symbol/ticks`** — 报价数组:
```json
{
"time": 1771146325,
"bid": 0.2973,
"ask": 0.2976,
"last": 0.0,
"volume": 0,
"time_msc": 1771146325123,
"flags": 6,
"volume_real": 0.0
}
```
查询参数(报价):与速率相同的 `count` / `from` / `to` 模型 — 正数 `count` = 从 `from` 开始,负数 = 向后,`from+to` = 范围,`count` 和 `to` 互斥,`to` 需要 `from`。另外:
| 参数 | 值 | 默认值 | 含义 |
| --- | --- | --- | --- |
| `flags` | `ALL`, `INFO`, `TRADE` | `ALL` | `INFO` = 仅 bid/ask 变化(~10× 更小的数据包),`TRADE` = 仅交易,`ALL` = 所有内容 |
示例:
- `?count=100` — 当前报价前 100 条
- `?from=2024_01_15_14_30_00&count=500` — 从该时间戳开始的 500 条报价
- `?from=1700000000&count=-500` — 截至锚点的 500 条报价
- `?from=2024_01_15_09_00_00&to=2024_01_15_10_00_00` — 该 1 小时窗口内的每条报价
报价密度注意事项:流动性对(NY 时段 EURUSD)发出 10-100 条报价/秒。1 小时 `from+to` 窗口可返回数百万行。优先使用 `count` 模式,除非您真的需要窗口内的每条报价 — 即使如此,请保持窗口小或分页。
当客户端发送 `Accept-Encoding: gzip` 时,响应会进行 gzip 压缩 — 通常可以减少 5-10 倍的大型速率/报价获取的带宽。`curl` 如果您传递 `--compressed`,则会遵循此约定。
### 头寸
| 方法 | 端点 | 描述 |
| ------ | -------------------- | -------------------------------- |
| GET | `/positions` | 列出开仓头寸 (`?symbol=`) |
| GET | `/positions/:ticket` | 获取头寸 |
| PUT | `/positions/:ticket` | 更新 SL/TP |
| DELETE | `/positions/:ticket` | 关闭头寸 |
**GET `/positions`** — 头寸对象数组:
```json
{
"ticket": 42094820,
"time": 1771150554,
"time_msc": 1771150554509,
"time_update": 1771150554,
"time_update_msc": 1771150554509,
"type": 0,
"magic": 0,
"identifier": 42094820,
"reason": 3,
"volume": 100.0,
"price_open": 0.3005,
"sl": 0.28,
"tp": 0.32,
"price_current": 0.3003,
"swap": 0.0,
"profit": -0.02,
"symbol": "ADAUSD",
"comment": "",
"external_id": ""
}
```
`type` 0 = 买入,1 = 卖出。`profit` 是未实现的盈亏。
**PUT `/positions/:ticket`** — 移动止损 / 止盈:
```json
{
"sl": 0.27,
"tp": 0.36
}
```
**DELETE `/positions/:ticket`** — 关闭它:
```json
{
"volume": 500,
"deviation": 20
}
```
所有字段可选。`volume` 默认使用全仓,`deviation` 默认值为 20。
### 单
| 方法 | 端点 | 描述 |
| ------ | ----------------- | -------------------------------- |
| GET | `/orders` | 列出待订单 (`?symbol=`) |
| POST | `/orders` | 发送订单 |
| GET | `/orders/:ticket` | 获取订单 |
| PUT | `/orders/:ticket` | 修改订单 |
| DELETE | `/orders/:ticket` | 取消订单 |
| "ticket": 42094812,
"time_setup": 1771147800,
"time_setup_msc": 1771147800123,
"time_done": 0,
"time_done_msc": 0,
"time_expiration": 0,
"type": 2,
"type_time": 0,
"type_filling": 1,
"state": 1,
"magic": 0,
"position_id": 0,
"position_by_id": 0,
"reason": 3,
"volume_initial": 1000.0,
"volume_current": 1000.0,
"price_open": 0.28,
"sl": 0.25,
"tp": 0.35,
"price_current": 0.2989,
"price_stoplimit": 0.0,
"symbol": "ADAUSD",
"comment": "",
"external_id": ""
}
`type`: 0=买入,1=卖出,2=买入限价,3=卖出限价,4=买入止损,5=卖出止损。`state`: 1=已挂单,2=已取消,3=部分成交,4=已成交,5=被拒绝,6=过期。
**POST `/orders`** — 发送订单:
```json
{
"symbol": "ADAUSD",
"type": "BUY",
"volume": 1000,
"price": 0.28,
"sl": 0.25,
"tp": 0.35,
"deviation": 20,
"magic": 0,
"comment": "",
"type_filling": "IOC",
"type_time": "GTC"
}
```
必需:`symbol`, `type`, `volume`. 其他均为可选。`price` 自动填充市场订单。
订单类型:
- 市场:`BUY`, `SELL`
- 待订单: `BUY_LIMIT`, `SELL_LIMIT`, `BUY_STOP`, `SELL_STOP`, `BUY_STOP_LIMIT`, `SELL_STOP_LIMIT`
填充策略:`FOK`, `IOC` (默认), `RETURN`
过期类型:`GTC` (默认), `DAY`, `SPECIFIED`, `SPECIFIED_DAY`
**PUT `/orders/:ticket`** — 改变主意:
```json
{
"price": 0.29,
"sl": 0.26,
"tp": 0.36,
"type_time": "GTC"
}
```
所有字段可选。
### 交易结果
订单和头寸的 POST/PUT/DELETE 返回内容:
```json
{
"retcode": 10009,
"deal": 40536203,
"order": 42094820,
"volume": 100.0,
"price": 0.3005,
"bid": 0.3002,
"ask": 0.3005,
"comment": "请求执行",
"request_id": 1549268253,
"retcode_external": 0
}
```
`retcode` 10009 = 成功。其他 = 出问题了。
### 历史
| 方法 | 端点 | 描述 |
| ------ | ----------------- | -------------------------------- |
| GET | `/history/orders` | 订单历史 (`?from=TS&to=TS`) |
| GET | `/history/deals` | 成交历史 (`?from=TS&to=TS`) |
`from` 和 `to` 均为必填,unix 时间戳(秒)。
**历史订单对象** (已完成/取消的订单):
```json
{
"ticket": 42094820,
"time_setup": 1771150554,
"time_setup_msc": 1771150554509,
"time_done": 1771150554,
"time_done_msc": 1771150554509,
"time_expiration": 0,
"type": 0,
"type_time": 0,
"type_filling": 1,
"state": 4,
"magic": 0,
"position_id": 42094820,
"position_by_id": 0,
"reason": 3,
"volume_initial": 100.0,
"volume_current": 0.0,
"price_open": 0.3005,
"sl": 0.28,
"tp": 0.32,
"price_current": 0.3005,
"price_stoplimit": 0.0,
"symbol": "ADAUSD",
"comment": "请求执行",
"external_id": ""
}
```
`state` 4 = 已成交,2 = 已取消,5 = 被拒绝,6 = 过期。`volume_current` 0 = 已完全成交。
**成交对象** (实际成交交易):
```json
{
"ticket": 40536203,
"order": 42094820,
"time": 1771150554,
"time_msc": 1771150554509,
"type": 0,
"entry": 0,
"position_id": 42094820,
"symbol": "ADAUSD",
"volume": 100.0,
"price": 0.3005,
"commission": 0.0,
"swap": 0.0,
"profit": 0.0,
"fee": 0.0,
"magic": 0,
"reason": 3,
"comment": "",
"external_id": ""
}
```
`type`: 0 = 买入,1 = 卖出。`entry`: 0 = 开仓,1 = 平仓。`profit` 为 0 为开仓,实际盈亏为平仓。
### 回测
通过HTTP API运行MT5策略测试器回测和优化。这些端点由每个终端提供,但它们只在配置文件的`config.yaml`条目中设置为`mode: backtest`的终端上成功运行。原因是结构性的:MT5在每个可移植数据目录中都是单实例的,因此如果`terminal64.exe`已经运行以支持实时SDK,则针对同一目录的策略测试器子进程会静默退出,返回代码`0`,并且不会生成报告。`mode: backtest`跳过了自动启动和实时模式SDK初始化,留下了数据目录,以便测试程序使用。请选择您要将凭证注入到运行的`[Common]`部分的经纪商/账户命名空间 - 例如,专用的`darwinex/tester`条目位于您实时`darwinex/main`旁边。
如果您的经纪商使用后缀符号,如`EURUSDp`或`EURUSD.p`,请在该回测终端上设置`terminals[]symbol_suffix`。如果经纪商使用普通符号,请明确设置`symbol_suffix: ""`。
两阶段流程:
1. `POST /backtest/build-ini` — 将小型JSON规格转换为完全形成的`tester.ini`(无凭证,无专家路径解析)。无状态助手;您也可以自己编写INI文件。
2. `POST /backtest` — 上传INI文件和`.ex5`专家文件以及可选的`.set`参数文件。返回`jobId`;轮询状态;当完成后,获取报告和终端日志。
对于普通回测,设置`[Tester].Optimization=0`或省略它。对于优化,将`[Tester].Optimization`设置为以下之一:
- `1` — 慢速完整算法
- `2` — 快速遗传算法
- `3` — 市场观察中选择的所有符号
优化运行需要一个`.set`文件,其输入参数已经包含优化范围。mt5-httpapi现在可以直接分阶段保存的MT5 `.set`文件,也可以从结构化JSON通过`POST /backtest/build-set`生成。
优化模式并不都会产生相同的MT5产物:
| 模式 | MT5设置 | 搜索范围 | 主要解析产物 | MT5写入的报告名称 |
| ---- | ----------- | ------------ | ----------------------- | -------------------------- |
| `1` | 慢速完整 | 单个符号在`[Tester].Symbol`中 | MT5 XML电子表格报告 | `<report>.xml` |
| `2` | 遗传 | 单个符号在`[Tester].Symbol`中 | MT5 XML电子表格报告 | `<report>.xml` |
| `3` | 所有市场观察符号 | 市场观察中当前选定的符号 | `Tester/cache/*.opt`缓存文件 | `<report>.symbols.xml` |
模式`3`是其中的例外。MT5仍然会写入报告文件,但它是仅限于表头的`.symbols.xml`变体,而实际的传递行则位于测试器缓存中。mt5-httpapi会解析该缓存,从代理日志中恢复传递到符号的映射,并将发现的缓存产物公开在`optimizationCache`中。如果您想要完整的模式请求/响应示例,请参阅[`docs/backtest-optimization.md`](docs/backtest-optimization.md)。
MT5 `.set`文件是普通的参量文件,通常是UTF-16文本。普通的保存设置如下所示:
```ini
Properties_=------
Magic_Number=1615044595
Entry_Amount=0.01
Stop_Loss=0
Take_Profit=92
___0______=------
Ind0Param0=3
Ind0Param1=1
Ind0Param2=1
Ind0Param3=8.0
___1______=------
Ind1Param0=20
Ind1Param1=31
Ind1Param2=5
```
该示例与`assets/sets/`下的捆绑预设结构匹配:简单的`name=value`行加上分隔符键。
对于优化,MT5 UI会以以下形式导出每个可优化的输入:
```ini
property=value||start||step||stop||Y|N
```
含义:
| 位置 | 含义 | 备注 |
| -------- | ------- | ----- |
| 1 | 当前值 | 默认或起始值 |
| 2 | 开始 | 优化范围开始 |
| 3 | 步长 | 每次传递的增量 |
| 4 | 停止 | 优化范围结束 |
| 5 | 优化 | `Y` = 启用,`N` = 禁用 |
示例:
```ini
TakeProfit=50||10||5||100||Y
StopLoss=30||10||5||80||Y
LotSize=0.1||0||0||0||N
```
- `TakeProfit`和`StopLoss`被优化,因为优化字段为`Y`。
- `LotSize`保持固定,因为优化字段为`N`。
因此,您要优化的参数,范围已知,看起来像这样:
```ini
MyParam=50||10||5||200||Y
```
而您要固定的参数,看起来像这样:
```ini
MyParam=50||0||0||0||N
```
从MT5策略测试器的“输入”选项卡创建设置文件,启用您要变化的参数的优化范围,然后保存。如果您已经有结构化的参数元数据,`POST /backtest/build-set`接受JSON并使用相同的`Y` / `N`标记返回MT5本地`.set`文本。
`POST /backtest/build-set`的JSON示例:
```json
{
"comments": [
"saved on 2026.05.15 08:30:02",
"this file contains input parameters for testing/optimizing MyEA"
],
"parameters": [
{"name": "_Properties_", "value": "------"},
{
"name": "Take_Profit",
"value": 92,
"start": 80,
"step": 4,
"stop": 92,
"optimize": true
},
{
"name": "Stop_Loss",
"value": 0,
"start": 0,
"step": 1,
"stop": 10,
"optimize": false
}
]
}
```
响应是`text/plain` `.set`内容,准备保存或上传。
每个API进程每时间只有一个测试程序运行(由内部锁序列化);额外提交的请求会排队。
#### 资产来源
专家文件和设置文件可以内联发送(优先用于临时运行)或通过托管在主机上的池按名称引用:
```
assets/
experts/ # *.ex5 — 主机托管的专家顾问(只读挂载)
sets/ # *.set — 主机托管的参数文件
```
`docker-compose.yml`挂载`./assets:/shared/assets:ro`在VM内公开它们,以便API可以读取它们。`expert_name` / `set_name`中的路径遍历被拒绝。
存储库还提供了一个专用的预热对:
```text
assets/experts/MT5SystemWarmup.mq5
assets/experts/MT5SystemWarmup.ex5
```
`MT5SystemWarmup.ex5`是回测管理器历史预热流程使用的默认专家。`.mq5`文件存在,以便在需要时,您可以在Windows虚拟机中重新生成编译的`.ex5`。
#### 重新生成`MT5SystemWarmup.ex5`
当mt5-httpapi虚拟机运行时,并且`run.sh`已将脚本同步到`data/shared/scripts/`,请从Windows客户机内部重新生成编译的预热EA:
1. 通过noVNC打开虚拟机。
2. 在虚拟机内启动`cmd.exe`。
3. 运行:
```bat
C:\Users\Docker\Desktop\Shared\scripts\compile-warmup-ea.bat
```
脚本的作用:
- 在`C:\Users\Docker\Desktop\Shared\terminals\*\base`下找到第一个已安装的经纪商`base`终端
- 将`C:\Users\Docker\Desktop\Assets\experts\MT5SystemWarmup.mq5`复制到该终端的`MQL5\Experts\Advisors\`
- 运行`MetaEditor64.exe /compile:... /log:...`
- 将生成的`MT5SystemWarmup.ex5`复制回`C:\Users\Docker\Desktop\Assets\experts\`
编译日志位于:
```text
C:\Users\Docker\Desktop\Shared\logs\compile-warmup-ea.log
```
如果`Assets`文件夹未在`C:\Users\Docker\Desktop\Assets`处公开,则脚本会退回到`C:\Users\Docker\Desktop\Shared\assets`。
#### `POST /backtest/build-ini`
正文(JSON):
| 字段 | 必需 | 备注 |
| ------------------ | -------- | -------------------------------------------------- |
| `symbol` | 是 | 例如`NZDJPY` |
| `timeframe` | 是 | `M1` `M5` `M15` `H1` `D1` … (21个标准值) |
| `expert` | 是 | 文件名以`.ex5`结尾 |
| `fromDate`+`toDate`| 一个 | `YYYY-MM-DD` |
| `lastYears` | 一个 | 整数;窗口结束于今日UTC |
| `lastDays` | 一个 | 整数 |
| `modelling` | 否 | `every-tick` `1m-ohlc` `open-prices` `real-ticks` |
| `latencyMs` | 否 | 整数毫秒 → `ExecutionMode` |
| `deposit` | 否 | 默认`10000` |
| `currency` | 否 | 默认`USD` |
| `leverage` | 否 | 默认`100`,以`1:N`形式写入 |
| `expertParameters` | 否 | `.set`文件名 |
| `optimization` | 否 | `0` 关闭,`1` 慢速完整,`2` 遗传,`3` 市场观察符号 |
| `optimizationCriterion` | 否 | `0..7`;默认`0`(最大余额) |
| `reportName` | 否 | 默认`backtest-report.htm`用于回测,`optimization-report.xml`用于优化 |
返回生成的INI的`text/plain`。
优化INI的JSON示例:
```json
{
"symbol": "GBPUSD",
"timeframe": "M15",
"expert": "MyEA.ex5",
"lastYears": 3,
"modelling": "open-prices",
"expertParameters": "myea-optimizer.set",
"optimization": 2,
"optimizationCriterion": 5,
"reportName": "gbpusd-m15-sharpe-search"
}
```
#### `POST /backtest`
多部分表单字段:
| 字段 | 必需 | 备注 |
| -------------- | -------- | -------------------------------------------------------------- |
| `ini` | 是 | INI文件(文件上传) |
| `expert` | 一个 | `.ex5`上传 |
| `expert_name` | 一个 | `assets/experts/`中的文件名 |
| `set` | 否 | `.set`上传 |
| `set_name` | 否 | `assets/sets/`中的文件名 |
| `topPasses` | 否 | 对于优化作业,保持前`1..500`个解析的XML传递。默认`50`。 |
| `timeout` | 否 | 时长字符串覆盖(`"30m"`,`"6h"`,`"3h30m"`)。默认为`backtest_timeout`,然后是硬编码`6h`。 |
响应`202 Accepted`,带有`Retry-After`头部和排队的作业有效载体:
```json
{
"jobId": "b3f7…",
"status": "queued",
"broker": "darwinex",
"account": "live",
"submittedAt": "2026-05-12T10:00:00Z",
"statusUrl": "/backtest/b3f7…",
"reportUrl": "/backtest/b3f7…/report",
"logUrl": "/backtest/b3f7…/log",
"pollAfterSeconds": 60,
"optimizationType": 0,
"optimizationResults": null,
"optimizationCache": null,
"queuePosition": 1
}
```
上传的INI中的`[Common]` `Login` / `Password` / `Server`始终被请求的经纪商/账户的`config.yaml`凭证覆盖。专家路径被重写为`Uploaded\<basename>`,并且设置文件被命名为每个作业,以避免冲突。
#### `GET /backtest/<jobId>`
状态有效载体。`status` ∈ `queued` `running` `completed` `failed`。完成后,包括从HTML报告解析的`summary`对象(`netProfit`,`profitFactor`,`recoveryFactor`,`expectedPayoff`,`sharpeRatio`,`maxDrawdown`,`totalTrades`,`profitTrades`,`lossTrades`,...)。
对于优化作业,载体还包括:
- `optimizationType` — 提交的MT5优化模式(`1`,`2`或`3`)
- `optimizationResults` — 按`Result`降序排列的解析后的前N个列表
- `optimizationCache` — 当结果来自MT5 `.opt`缓存文件时,缓存产物元数据
结果源取决于提交的模式:
- 模式`1`和`2`首先解析MT5 XML电子表格报告,只有当相同作业的`.opt`缓存可用时,才使用缓存解析。
- 模式`3`首先解析MT5测试器缓存,因为`.symbols.xml`报告不包含优化行。
API保留MT5列名不变。如果XML导出包含诸如`Profit`,`Profit Factor`,`Expected Payoff`,`Drawdown`,`Recovery Factor`,`Sharpe Ratio`或优化的输入名称等字段,则这些相同的字段将出现在每个`optimizationResults`行中。
优化状态有效载体的示例:
```json
{
"jobId": "8c2a…",
"status": "completed",
"broker": "darwinex",
"account": "tester",
"reportName": "gbpusd-m15-sharpe-search.xml",
"reportUrl": "/backtest/8c2a…/report",
"logUrl": "/backtest/8c2a…/log",
"optimizationType": 2,
"optimizationCache": null,
"optimizationResults": [
{
"Pass": 184,
"Result": 2.41,
"Profit": 1263.5,
"Profit Factor": 1.48,
"Expected Payoff": 13.02,
"Recovery Factor": 3.11,
"Total trades": 97,
"Sharpe Ratio": 2.41,
"FastPeriod": 12,
"SlowPeriod": 34
}
]
}
```
模式3优化有效载体示例:
```json
{
"jobId": "b05643…",
"status": "completed",
"broker": "darwinex",
"account": "live",
"reportName": "mode3-gbpcad-m15-last5y-rerun5.symbols.xml",
"reportUrl": "/backtest/b05643…/report",
"logUrl": "/backtest/b05643…/log",
"optimizationType": 3,
"optimizationCache": {
"name": "EA Studio GBPCAD M15 1615044595.all_symbols.M15.20210525.20260525.22.788ECDD113BA3097A58EF888EBEFF9CA.opt",
"pattern": "EA Studio GBPCAD M15 1615044595.all_symbols.M15.20210525.20260525.*.opt",
"build": "22",
"cacheHash": "788ECDD113BA3097A58EF888EBEFF9CA",
"rowCount": 28,
"symbolComponent": "all_symbols",
"period": "M15"
},
"optimizationResults": [
{
"Pass": 21,
"Symbol": "GBPJPY",
"Result": 1657.54,
"Profit": 657.54,
"Profit Factor": 1.9,
"Expected Payoff": 2.57,
"Recovery Factor": 3.89,
"Sharpe Ratio": 0.75,
"Equity DD %": 11.22,
"Trades": 256,
"Custom": ""
}
]
}
```
#### `GET /backtest/<jobId>/report` 和 `/log`
流式传输原始报告和终端日志文件。回测返回MT5 HTML报告。优化返回MT5 XML电子表格导出。`404`直到作业完成。
#### 工作示例
```bash
export URL=http://127.0.0.1:8888/darwinex/live
export TOK=changeme-mt5-httpapi-token
# 1. 为5年NZDJPY M15开仓价运行构建INI。
curl -sS -X POST "$URL/backtest/build-ini" \
-H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
-d '{"symbol":"NZDJPY","timeframe":"M15","expert":"EA Studio NZDJPY M15 1615044595.ex5","lastYears":5,"modelling":"open-prices","latencyMs":5,"expertParameters":"ea studio nzdjpy m15 1615044595.set"}' \
> tester.ini
# 2. 使用托管在主机上的专家+设置提交。
JOB=$(curl -sS -X POST "$URL/backtest" \
-H "Authorization: Bearer $TOK" \
-F "ini=@tester.ini" \
-F "expert_name=EA Studio NZDJPY M15 1615044595.ex5" \
-F "set_name=ea studio nzdjpy m15 1615044595.set" \
| jq -r .jobId)
# 3. 等待完成。
while :; do
STATUS=$(curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB" | jq -r .status)
echo "$STATUS"; [[ "$STATUS" == completed || "$STATUS" == failed ]] && break
sleep 30
done
# 4. 获取报告。
curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB/report" -o report.htm
```
#### 优化示例
假设您已经在MT5中创建了一个具有优化范围的`.set`文件,并将其放在`assets/sets/`中,或计划内联上传。
```bash
export URL=http://127.0.0.1:8888/darwinex/tester
export TOK=changeme-mt5-httpapi-token
# 构建INI,提交多部分作业,轮询完成,然后打印API摘要和原始MT5 XML报告的第一行。
tmp_ini=$(mktemp) && \
job_json=$(mktemp) && \
trap 'rm -f "$tmp_ini" "$job_json"' EXIT && \
curl -sS -X POST "$URL/backtest/build-ini" \
-H "Authorization: Bearer $TOK" \
-H "Content-Type: application/json" \
-d '{"symbol":"GBPCAD","timeframe":"M15","expert":"EA Studio GBPCAD M15 1615044595.ex5","lastYears":1,"modelling":"open-prices","expertParameters":"ea studio gbpcad m15 1615044595.take-profit-opt-80-92-step4.set","optimization":1,"optimizationCriterion":0,"reportName":"gbpcad-m15-last1y-openprices-opt"}' \
> "$tmp_ini" && \
curl -sS -X POST "$URL/backtest" \
-H "Authorization: Bearer $TOK" \
-F "ini=@$tmp_ini;filename=tester.ini" \
-F "expert_name=EA Studio GBPCAD M15 1615044595.ex5" \
-F "set_name=ea studio gbpcad m15 1615044595.take-profit-opt-80-92-step4.set" \
-F "topPasses=20" \
> "$job_json" && \
JOB=$(jq -r '.jobId' "$job_json") && \
echo "Submitted job: $JOB" && \
while :; do \
STATUS_JSON=$(curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB") && \
STATUS=$(printf '%s' "$STATUS_JSON" | jq -r '.status') && \
echo "Status: $STATUS" && \
[[ "$STATUS" == completed || "$STATUS" == failed ]] && break; \
sleep 10; \
done && \
echo && echo "Final API summary:" && \
printf '%s\n' "$STATUS_JSON" | jq '{jobId,status,exitCode,durationSeconds,optimizationResults}' && \
echo && echo "Report preview:" && \
curl -sS -H "Authorization: Bearer $TOK" "$URL/backtest/$JOB/report" \
| grep -E '(<Row>|<Cell><Data ss:Type="String">|<Cell><Data ss:Type="Number">|<Cell ss:StyleID="[^"]+"><Data ss:Type="Number">)' \
| head -n 60
```
注意:
- 使用配置为`mode: backtest`的终端,而不是实时终端命名空间。
- 优化结果取决于`.set`文件中的范围。如果在MT5中未启用范围,则优化没有意义。
- `optimizationResults`是一个方便的摘要。对于模式`1`和`2`,原始XML在`/report`中仍然是真相的全部来源。对于模式`3`,解析的`.opt`缓存和`optimizationCache`元数据是最佳调试源,因为`/report`是MT5 `.symbols.xml`头部导出。
- 如果您期望的指标在`optimizationResults`中缺失,请首先检查原始XML报告。API保留MT5导出的列名,而不是将它们映射到固定模式。
## MCP 接口
每个终端还会在 `/mcp` 处安装一个 [模型上下文协议](https://modelcontextprotocol.io) 服务器(可流式传输的 HTTP),与 REST API 一起,在同一个进程中。它公开了 **专用的、类型化的工具**,按家族分组 — 每个工具的名称、类型化的参数和描述都是代理读取的内容(无需猜测原始路径)。每个工具都运行完全相同的处理程序、身份验证和 MT5 锁定,如同真正的 HTTP 请求。
- **市场数据** — `list_symbols`、`get_symbol`、`get_tick`、`get_rates(symbol, timeframe, count?)`、`get_ticks`、`get_rates_ta`
- **账户 / 持仓** — `get_account`、`list_positions`、`get_position`、`modify_position`、`close_position`
- **订单** — `list_orders`、`get_order`、`create_order(symbol, type, volume, price?, sl?, tp?)`、`modify_order`、`cancel_order`
- **历史 / 终端 / 回测** — `get_history_orders`、`get_history_deals`、`get_terminal`、`terminal_control`、`get_backtest`、`ping`
- **紧急出口** — `request(method, path, query?, body?)` + `endpoints`(路由目录)用于没有专用工具的任何操作
订单/持仓工具(`create_order`、`cancel_order`、`close_position` 等)是不可逆的实时账户操作,并在其工具描述中注明。
与 REST 相同的 bearer 身份验证:空的 `api_token` 也会禁用 `/mcp` 上的身份验证;配置的令牌需要在每次 MCP 调用时使用 `Authorization: Bearer <token>`。
可达的 URL 是终端的正常基准加上 `/mcp/` — nginx 剥离 `/<broker>/<account>/` 并直接将剩余部分代理到目标,所以:
```
$MT5_API_URL/mcp/
# 例如 http://localhost:8888/roboforex/main/mcp/
```
```bash
# 原始 JSON-RPC — 直接调用请求工具
curl -sS -H "Authorization: Bearer $MT5_API_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
"$MT5_API_URL/mcp/" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ping","arguments":{}}}'
```
通过 `request` 访问的订单/持仓变更与直接调用这些 REST 路由是相同的实时、不可逆的交易操作 — 在调用它们之前请确认参数。
对于仅支持本地 stdio 服务器的 MCP 客户端,[`@psyb0t/mt5-httpapi`](.agents/plugins/mt5-httpapi) OpenClaw 插件是一个薄的 stdio↔HTTP 桥接到此端点。
## 优化指南
参见 [`docs/backtest-optimization.md`](docs/backtest-optimization.md) 以获取专门的指南,涵盖:
- 模式 `1`、`2` 和 `3` 在操作上的不同
- 如何为每种模式构建 INIs 和 `.set` 文件
- 所有优化模式的完整 `curl` 示例
- 如何解释 `optimizationResults`、`optimizationCache` 和生成的报告工件
如果在回测运行时重启 API,则孤立的作业在下次启动时标记为
`failed`(`API 在完成前重启`)。
## 示例
```bash
export MT5_API_URL=http://localhost:8888/roboforex/main
export MT5_API_TOKEN=$(grep ^api_token config/config.yaml | awk -F'"' '{print $2}') # 如果未配置身份验证,则省略
# 检查余额
curl -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/account
# 获取一些 EURUSD H4 蜡烛
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/symbols/EURUSD/rates?timeframe=H4&count=100"
# YOLO 1000 ADAUSD,止损和止盈
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders \
-H "Content-Type: application/json" \
-d '{"symbol": "ADAUSD", "type": "BUY", "volume": 1000, "sl": 0.25, "tp": 0.35}'
# 下达一个挂单买入限价
curl -X POST -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/orders \
-H "Content-Type: application/json" \
-d '{"symbol": "ADAUSD", "type": "BUY_LIMIT", "volume": 1000, "price": 0.28, "sl": 0.25, "tp": 0.35}'
# 修改止损和止盈
curl -X PUT -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/12345 \
-H "Content-Type: application/json" \
-d '{"sl": 0.27, "tp": 0.36}'
# 关闭一半
curl -X DELETE -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/12345 \
-H "Content-Type: application/json" \
-d '{"volume": 500}'
# 关闭所有
curl -X DELETE -H "Authorization: Bearer $MT5_API_TOKEN" $MT5_API_URL/positions/12345
# 在运行多终端时访问不同的终端
curl -H "Authorization: Bearer $MT5_API_TOKEN" http://localhost:8888/roboforex/main/account
curl -H "Authorization: Bearer $MT5_API_TOKEN" http://localhost:8888/ftmo/challenge1/account
# 获取最近 24 小时的成交历史
curl -H "Authorization: Bearer $MT5_API_TOKEN" "$MT5_API_URL/history/deals?from=$(date -d '1 day ago' +%s)&to=$(date +%s)"
```
## Go 客户端
一个类型化的 Go 客户端位于 [`clients/go/`](clients/go/)。所有端点都已覆盖,错误映射到类型化的哨兵,结构体已针对实时响应进行验证。
```bash
go get github.com/psyb0t/mt5-httpapi/clients/go
```
```go
package main
import (
"context"
"errors"
"log"
"os"
"time"
mt5 "github.com/psyb0t/mt5-httpapi/clients/go"
"github.com/psyb0t/aichteeteapee"
)
func main() {
c, err := mt5.New(mt5.Config{
BaseURL: os.Getenv("MT5_API_URL"),
Token: os.Getenv("MT5_API_TOKEN"), // 如果服务器没有身份验证,则为空字符串
Timeout: 30 * time.Second,
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
acc, err := c.GetAccount(ctx)
if errors.Is(err, mt5.ErrNotInitialized) {
log.Fatal("MT5 仍在启动中,请在一秒后重试")
}
if errors.Is(err, aichteeteapee.ErrUnauthorized) {
log.Fatal("令牌错误")
}
if err != nil {
log.Fatal(err)
}
log.Printf("balance=%.2f %s leverage=1:%d", acc.Balance, acc.Currency, acc.Leverage)
// 下达一个市价买入订单
res, err := c.CreateOrder(ctx, &mt5.CreateOrderRequest{
Symbol: "EURUSD",
Type: "BUY",
Volume: 0.1,
SL: 1.08,
TP: 1.10,
})
if err != nil {
log.Fatal(err)
}
log.Printf("retcode=%d order=%d deal=%d price=%.5f", res.Retcode, res.Order, res.Deal, res.Price)
// 拉取 H4 蜡烛
rates, err := c.GetRates(ctx, "EURUSD", mt5.RatesQuery{Timeframe: "H4", Count: 100})
if err != nil {
log.Fatal(err)
}
log.Printf("获取 %d 根蜡烛,最后一根收盘价=%.5f", len(rates), rates[len(rates)-1].Close)
}
```
### 可用方法
| 方法 | 端点 |
| --- | --- |
| `Ping` | `GET /ping` |
| `LastError` | `GET /error` |
| `GetTerminal` / `InitTerminal` / `ShutdownTerminal` / `RestartTerminal` | `/terminal[/...]` |
| `GetAccount` | `GET /account` |
| `ListSymbols` / `GetSymbol` / `GetTick` / `GetRates` / `GetRatesTA` / `GetTicks` | `/symbols[/...]` |
| `ListOrders` / `CreateOrder` / `GetOrder` / `UpdateOrder` / `CancelOrder` | `/orders[/...]` |
| `ListPositions` / `GetPosition` / `UpdatePosition` / `ClosePosition` | `/positions[/...]` |
| `HistoryOrders` / `HistoryDeals` | `/history/...` |
### 错误映射
HTTP 状态码映射到类型化错误,您可以 `errors.Is()` 以下:
| 状态码 | 错误 |
| --- | --- |
| 400 | `aichteeteapee.ErrBadRequest` |
| 401 | `aichteeteapee.ErrUnauthorized` |
| 403 | `aichteeteapee.ErrForbidden` |
| 404 | `aichteeteapee.ErrNotFound` |
| 409 | `aichteeteapee.ErrConflict` |
| 422 | `aichteeteapee.ErrUnprocessableEntity` |
| 429 | `aichteeteapee.ErrTooManyRequests` |
| 500 | `aichteeteapee.ErrInternalServer` |
| 502 | `aichteeteapee.ErrBadGateway` |
| 503 | `mt5httpapi.ErrNotInitialized`(MT5 仍在启动中) |
| 504 | `aichteeteapee.ErrGatewayTimeout` |
助手:`mt5httpapi.IsNotInitialized(err)` 简化了常见的 503 重试情况。
## 技术分析
两种选项:
**服务器端通过 wickworks 侧车(`POST /symbols/:symbol/rates/ta`)** — 带有指标的蜡烛已经丰富(RSI,MACD,布林带,ADX,VWAP,Ichimoku,订单块/FVG/BOS/CHoCH,摆动结构,支撑/阻力水平等)。原始指标仅 — wickworks 发出原始指标序列和 SMC 结构事实;解释信号(背离,交叉事件等)应在您的消费者中。wickworks 容器与 docker-compose 一起提供,并锁定到 mt5 网络命名空间 — 无外部流量,无单独部署。参见 [端点文档](#symbols) 和 [github.com/psyb0t/docker-wickworks](https://github.com/psyb0t/docker-wickworks) 上的完整指标目录以及参数 + 输出形状。
**客户端** — 使用 `GET /rates` 获取原始蜡烛并自行分析。`examples/python/` 中有一个使用 [pandas-ta](https://github.com/twopirllc/pandas-ta) 的完整工作示例,包含 ATR、RSI、MACD、布林带、MFI、随机指标、ADX、VWAP 和移动平均线。
```bash
cd examples/python
pip install -r requirements.txt
# 默认:EURUSD H4 200 根蜡烛
python ta.py
# 自定义符号/时间范围/数量
python ta.py BTCUSD H1 100
python ta.py ADAUSD D1 200
# 自定义 API URL
MT5_API_URL=http://10.0.0.5:8888/roboforex/main python ta.py EURUSD D1
# 带 TA 叠加的烛图(1920x1080 PNG)
python chart.py ADAUSD
python chart.py BTCUSD H1 100
python chart.py EURUSD D1 200 -o eurusd.png
```
查看 `indicators.py` 获取单个指标函数,`signals.py` 获取信号检测。将它们用作构建您自己的构件。
## 制作目标
```
make up 启动虚拟机(如果需要,下载 ISO)
make down 关闭虚拟机
make logs 尾随日志
make status 检查虚拟机和 API 状态
make lint 在一个临时 Docker 镜像中检查所有 .ps1/.sh 脚本
make format 在原位应用 shfmt 格式化到 .sh 文件
make test 在一个临时 Docker 镜像中运行单元测试
make clean 删除虚拟机磁盘和状态(保留 ISO)
make distclean 删除包括 ISO 在内的所有内容
```
`make lint` 涵盖在虚拟机中运行的脚本以及主机端的 shell:`.ps1` 获取纯 ASCII 检查,解析检查和 PSScriptAnalyzer;`.sh` 获取 shellcheck 和 shfmt。ASCII 规则并非美观 — Windows PowerShell 5.1 读取 `.ps1` 为 ANSI,除非文件具有 UTF-8 BOM,否则多字节字符在字符串字面量中会损坏并导致解析错误,您不会看到,直到虚拟机启动。`make format` 修复 shfmt 标志的任何内容。
## 端口
| 端口 | 服务 | 覆盖 |
| ----- | ----------------------- | --------------------------------- |
| 8006 | noVNC(虚拟机桌面) | `NOVNC_PORT=9006 make up` |
| 8888 | HTTP API(nginx,所有终端) | `API_HOST_PORT=9999 make up` |
只有两个端口离开 docker 网络。每个终端的端口来自 `config.yaml` 的 `terminals:` 列表在容器内部保持 - nginx(始终开启,根据相同的列表生成)通过 docker DNS 将 `/<broker>/<account>/...` 路由到正确的终端,mt5 容器的 iptables DNAT 从那里转发到 Windows 虚拟机。主机绑定默认仅限回环(`127.0.0.1:8888`);如果您想暴露在局域网上,请更改 `docker-compose.yml` 中的绑定,或者使用下面的 Tailscale 侧车进行 tailnet 暴露。
## Tailscale(可选)
使用裸 MagicDNS 主机名通过您的 tailnet 暴露 API — `http://mt5-httpapi/<broker>/<account>/...` — 适用于 stock Tailscale 和自托管 Headscale。纯 HTTP(无 TLS)设计:裸主机名没有匹配的证书,wireguard 层已经在 tailnet 内加密所有内容。
工作原理:`tailscale` 侧车在 **自己的网络命名空间**(桥接模式,非主机网络)中加入 tailnet,因此它拥有自己的 tailnet 身份 — ACL 仅限于侧车的节点,并且主机的 tailscale(如果有)不会出现在侧车的入站路径中。Tailscale Serve 在该网络命名空间内监听端口 80,并通过 docker 内部网络将请求代理到始终开启的 `nginx` 侧车(`http://nginx:80`)。nginx 然后剥离 `/<broker>/<account>/` 并通过 docker DNS 将请求代理到正确的终端。`nginx.conf` 根据 `config.yaml` 的 `terminals:` 列表在每次 `make up` 时自动生成;Tailscale Serve 配置通过 `tailscale serve` CLI 在侧车内连接(它需要实时 FQDN,只有 CLI 知道)并在 tailscaled 状态中持久化。
侧车在 TUN 模式下运行(`TS_USERSPACE=false`),因此在它的网络命名空间内存在一个真实的 `tailscale0` 接口。这意味着从侧车的网络命名空间发出到 `100.64.0.0/10` 的任何出站流量都通过侧车自己的隧道路由,使用其 tailnet 身份 — 而不是通过主机的 `tailscale0`(如果主机在不同的账户上有一个)。注意范围:这仅适用于从侧车的网络命名空间发出的流量。其他容器(`nginx`、`mt5`)位于 docker 桥上,不在侧车的网络命名空间中,因此从它们发出的任何 tailnet 绑定流量仍将通过主机的隧道。我们在默认设置中不会从这些容器中启动 tailnet 出站流量,因此不会产生影响 — 但如果您添加一个服务来执行此操作,请将其放在 `network_mode: service:tailscale` 上。
**设置**:
1. 在 `config/config.yaml` 中设置您的身份验证密钥:
```yaml
tailscale:
auth_key: "tskey-auth-..."
login_server: "" # 对于 Headscale,请设置为 "https://headscale.your.domain"
```
2. 在 `docker-compose.yml` 中取消注释 `tailscale` 块。(nginx 始终开启 - 无需为此取消注释任何内容。)
3. `make up`。`run.sh` 读取 `config.yaml`,写入 `.env` 中的 `TS_AUTHKEY` (和 `TS_EXTRA_ARGS=--login-server=...`,如果使用 Headscale),启动堆栈,等待 tailscaled 身份验证,然后在侧车内运行 `tailscale serve --bg --http=80 http://nginx:80`,以将 tailnet :80 侦听器连接到 nginx。服务配置在 `.data/tailscale/state` 中持续存在,因此后续的 `make up` 调用不需要重新执行此操作。
**状态持久性**: tailnet 身份存在于 `.data/tailscale/state/`。`make down`/`make up` 重用现有的登录 — `TS_AUTHKEY` 仅在首次身份验证时使用(或在删除 `rm -rf .data/tailscale/state` 后)。如果您希望经常重置状态,请使用可重用的身份验证密钥。
**多终端 URL 方案**:
```
http://mt5-httpapi/roboforex/main/account
http://mt5-httpapi/roboforex/main/symbols/EURUSD/rates?count=100
http://mt5-httpapi/ftmo/challenge1/positions
```
API 令牌(如果在 `config.yaml` 中设置)仍然适用 — Tailscale 处理网络级访问,令牌处理应用级身份验证。请像对待受限主机名一样,使用公共主机名 **并始终在 `config.yaml` 中设置 `api_token`**。
## Cloudflare 隧道(可选)
在不打开防火墙端口的情况下公开 API。cloudflared 与 Cloudflare 的边缘对接并代理到始终开启的 `nginx` 侧车 — 一个隧道,一个主机名,每个终端都可以通过 `/<broker>/<account>/` 访问。
**设置**:
1. 在主机上安装 cloudflared(一次, 只需创建隧道):
```bash
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /tmp/cloudflared
sudo install /tmp/cloudflared /usr/local/bin/cloudflared
```
2. 身份验证,创建隧道,并将单个主机名路由到它(必须是在 Cloudflare 上您控制的区域):
```bash
cloudflared tunnel login
cloudflared tunnel create mt5-httpapi
cloudflared tunnel route dns mt5-httpapi mt5-api.yourdomain.com
```
3. 将凭证和配置放入 `.data/cloudflared/`:
```bash
mkdir -p .data/cloudflared
cp ~/.cloudflared/<tunnel-id>.json .data/cloudflared/creds.json
```
创建 `.data/cloudflared/config.yml`:
```yaml
tunnel: <tunnel-id>
credentials-file: /etc/cloudflared/creds.json
ingress:
- hostname: mt5-api.yourdomain.com
service: http://nginx:80
- service: http_status:404
```
4. 在 `docker-compose.yml` 中取消注释 `cloudflared` 块并执行 `make up`。
**公共 URL 方案**:
```
https://mt5-api.yourdomain.com/roboforex/main/account
https://mt5-api.yourdomain.com/ftmo/challenge1/positions
```
Cloudflare 在边缘终止 TLS — 您免费获得 HTTPS,无需管理证书。从 cloudflared 到 `nginx:80` 的连接是 docker 桥上的纯 HTTP,但它从不离开主机。
**子域深度**: Cloudflare 的免费通用 SSL 涵盖 `*.yourdomain.com` 但不涵盖更深的级别,如 `*.mt5.yourdomain.com`。使用根域名下的单个子域。
API 令牌在公开可访问性方面仍然适用 — Cloudflare 控制公开可达性,承载令牌控制应用程序。请像对待受限主机名一样,使用公共主机名 **并始终在 `config.yaml` 中设置 `api_token`**。
项目结构
```
config/ 您的配置文件目录
config.yaml 唯一的真实来源(gitignored)
config.yaml.example 提交的模板 — 复制到 config.yaml
setup.bat 自定义启动命令(可选)
hosts VM 的 hosts 文件中的额外条目(可选)
scripts/ 在 Windows VM 中运行的脚本
oem-install.bat 首次启动 OEM 脚本(创建启动项)
install.bat 设置(Python、MT5、防火墙)— 每次启动都运行
start(安装 + 启动终端 + API)
reboot.bat 唯一的重启路径 — 写入 rebooting.flag 并
在关闭之前释放两个锁定目录
acquire_lock.ps1 启动时间锁定的锁定获取,用于 start.bat 和
install.bat;自动清除重启孤立的锁定
debloat.bat Windows 精简脚本
defender-remover/ Windows Defender 移除工具
mt5api/ Python HTTP API 服务器
handlers/ 路由处理程序
config.py 配置(--broker、--account、--port CLI 参数)
mt5client.py MT5 包装器
server.py Flask 路由
examples/ 使用示例
python/ TA、图表和 API 客户端模块
mt5installers/ 券商 MT5 设置可执行文件(gitignored)
data/ 生成的/易失性数据(gitignored)
win.iso Windows ISO
storage/ VM 磁盘
shared/ 与 VM 共享的文件夹
scripts/ 从 scripts/ 同步的批处理脚本
config/ 从 config/ 同步的配置文件
terminals/ 按券商/账户划分的 MT5 安装
logs/ 所有日志文件
mt5api/ Python API 包
oem/ 首次启动脚本
```
## 并发与背压
MT5 Python SDK 是单连接/进程且非线程安全的 — 并发调用 `mt5.*` 会破坏内部状态(特别是 `last_error`,多个流程会隐式读取)。API 在所有 SDK 调用周围强制实施进程范围的互斥锁,锁定持续整个请求处理过程,因此多调用处理程序(`POST /orders`、`get_rates` 重试循环等)与其他操作是原子的。
您会观察到的连:
- **`/ping`** 是唯一不获取锁定的处理程序。使用它进行活动探测 — 即使 SDK 出现问题,它也能保持响应。
- 每个 `mt5.*` 调用都有 30 秒的硬超时限制。出现问题时调用返回 **`504 mt5 call timed out`** 并释放锁定。孤立的 C 线程仍在 SDK 中运行;健康监测器会检测到终端死亡并运行 `restart_terminal` 以释放它。
- 当太多请求在锁定队列中堆积时,新请求会返回 **`503 queue depth N exceeds max M`** 而不是等待。默认上限为 20;可通过环境变量 `MT5_MAX_QUEUE_DEPTH=...` 调整。
- 每个调用的计时日志(`<req_id> mt5.<fn> dur_ms=...`)都会在每次 SDK 调用时发出,以便问题排查时有数据可供分析。
如果您看到来自单个终端的持续 503/504 错误,请在 `data/shared/logs/api-<broker>-<account>.log` 中查找 `mt5.* TIMEOUT` 行 — 这就是出现问题的 SDK 调用。
## 日志
在 VM 的共享文件夹中(`data/shared/logs/`):
- `install.log` - MT5 安装进度(install.bat)
- `start.log` - 启动序列日志(start.bat)
- `pip.log` - Python 包安装日志
- `api-<broker>-<account>.log` - 每个终端的 API 日志
- `windows-events.log` - 截取的 Windows 系统 + 应用程序事件日志(仅警告/严重级别)。捕获 OOM 杀死(`MicrosoftResource-Exhaustion-Detector`)、terminal64.exe (`Application)、蓝屏(`Bug服务失败等。
- `full.log` - 上日志的单一叙述,带有 `[start]` / `[install]`api:<broker>/<` / `[winevt。`tail -f full 是唯一诊断视图。
志轮转
一个侧车(`ator`)每天轮转/shared/logs/` 中的所有`,并删除 天前的存档。:`full.log` →.log.YYYYMMDD昨天日期)在午夜一次唤醒时。幂小时检查循环,无 守护进程。
可docker-compose.yml` 重写默认值:
- `REAYS`(默认为 `7`)- 要保留的存数
- `INTERVAL`(默认为 `3600`)- 以秒为单位的检查边界间隔
截断是原地进行的(存档是副本,然后原始文件被 `:>` 截断),因此 Python API 的打开日志句柄继续写入,无需重新打开。
当问题出现时,请先检查这些日志。
## 许可证
WTFPL
连接信息
猜你喜欢
暂无推荐服务器