更新常见问题列表
This commit is contained in:
+222
-81
@@ -6,13 +6,13 @@
|
||||
|
||||
## 连接信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 地址 | `http://61.164.252.86:13485` |
|
||||
| 认证 | `X-API-Key` Header 或 `?key=` URL 参数 |
|
||||
| 格式 | 所有返回均为 JSON |
|
||||
| WebSocket | `ws://61.164.252.86:13485`,握手时用 Header `X-API-Key` |
|
||||
| SSE | `http://.../stream/ticks-sse/{symbol}`,Header 或 `?key=` 均可 |
|
||||
| 项目 | 值 |
|
||||
| --------- | ------------------------------------------------------------ |
|
||||
| 地址 | `http://61.164.252.86:13485` |
|
||||
| 认证 | `X-API-Key` Header 或 `?key=` URL 参数 |
|
||||
| 格式 | 所有返回均为 JSON |
|
||||
| WebSocket | `ws://61.164.252.86:13485`,握手时用 Header `X-API-Key` |
|
||||
| SSE | `http://.../stream/ticks-sse/{symbol}`,Header 或 `?key=` 均可 |
|
||||
|
||||
> **浏览器注意**:原生 `WebSocket` 不支持自定义 Header,须用 `?key=` URL 参数;SSE 用 `new EventSource(url + '?key=...')` 同理。Node/Python/Java 客户端用 Header 更干净。
|
||||
|
||||
@@ -46,33 +46,156 @@ print(api("/health"))
|
||||
|
||||
## API 接口速查
|
||||
|
||||
| 分类 | 方法 | 端点 | 用途 |
|
||||
|------|------|------|------|
|
||||
| **系统** | `GET` | `/health` | 健康检查 + MT5 连接状态 |
|
||||
| **账户** | `GET` | `/account` | 余额/净值/保证金/杠杆 |
|
||||
| **行情** | `GET` | `/symbols/{symbol}` | 品种信息(点值/手数/合约) |
|
||||
| | `GET` | `/symbols/{symbol}/tick` | 拉取一次 tick |
|
||||
| | `WS` | `/stream/ticks/{symbol}` | **实时 tick 推送**(推荐) |
|
||||
| | `GET` | `/stream/ticks-sse/{symbol}` | SSE 推送(浏览器/内网代理友好) |
|
||||
| **K 线** | `GET` | `/rates/from-pos` | K 线(按偏移量) |
|
||||
| | `GET` | `/rates/from-date` | **K 线(按时间范围)⭐** |
|
||||
| **持仓** | `GET` | `/positions[?symbol=]` | 当前持仓列表 |
|
||||
| | `POST` | `/position/close` | 平仓(全/部分) |
|
||||
| | `POST` | `/position/modify` | 改 SL/TP |
|
||||
| | `POST` | `/position/close-by` | 对冲平仓(节省点差) |
|
||||
| | `POST` | `/positions/close-batch` | 批量平仓(按 magic/symbol) |
|
||||
| **挂单** | `GET` | `/orders[?symbol=]` | 挂单列表 |
|
||||
| | `POST` | `/order/cancel` | 撤单 |
|
||||
| | `POST` | `/order/modify` | 改挂单价/SL/TP |
|
||||
| **下单** | `POST` | `/order/check` | 预检(不成交) |
|
||||
| | `POST` | `/order/send` | 实际下单 |
|
||||
| **历史** | `GET` | `/history/deals` | 历史成交 |
|
||||
| **信号** | `GET/POST/DELETE` | `/gvar[/{name}]` | MQL5 全局变量(指标信号桥) |
|
||||
| 分类 | 方法 | 端点 | 用途 |
|
||||
| -------- | ----------------- | ---------------------------- | ------------------------------- |
|
||||
| **系统** | `GET` | `/health` | 健康检查 + MT5 连接状态 |
|
||||
| **账户** | `GET` | `/account` | 余额/净值/保证金/杠杆 |
|
||||
| **行情** | `GET` | `/symbols/{symbol}` | 品种信息(点值/手数/合约) |
|
||||
| | `GET` | `/symbols/{symbol}/tick` | 拉取一次 tick |
|
||||
| | `WS` | `/stream/ticks/{symbol}` | **实时 tick 推送**(推荐) |
|
||||
| | `GET` | `/stream/ticks-sse/{symbol}` | SSE 推送(浏览器/内网代理友好) |
|
||||
| **K 线** | `GET` | `/rates/from-pos` | K 线(按偏移量) |
|
||||
| | `GET` | `/rates/from-date` | **K 线(按时间范围)⭐** |
|
||||
| **持仓** | `GET` | `/positions[?symbol=]` | 当前持仓列表 |
|
||||
| | `POST` | `/position/close` | 平仓(全/部分) |
|
||||
| | `POST` | `/position/modify` | 改 SL/TP |
|
||||
| | `POST` | `/position/close-by` | 对冲平仓(节省点差) |
|
||||
| | `POST` | `/positions/close-batch` | 批量平仓(按 magic/symbol) |
|
||||
| **挂单** | `GET` | `/orders[?symbol=]` | 挂单列表 |
|
||||
| | `POST` | `/order/cancel` | 撤单 |
|
||||
| | `POST` | `/order/modify` | 改挂单价/SL/TP |
|
||||
| **下单** | `POST` | `/order/check` | 预检(不成交) |
|
||||
| | `POST` | `/order/send` | 实际下单 |
|
||||
| **历史** | `GET` | `/history/deals` | 历史成交 |
|
||||
| **信号** | `GET/POST/DELETE` | `/gvar[/{name}]` | MQL5 全局变量(指标信号桥) |
|
||||
|
||||
> 推送类端点(WS/SSE):每品种最多 50 个订阅者,超出返回 `429`;每 30 秒发一条心跳包用于穿透 NAT 保持连接。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 隐含约定与已知陷阱(先读)
|
||||
|
||||
下面这些坑都是踩过的,**不读这节直接调接口几乎必踩**:
|
||||
|
||||
### P1. `/history/deals` 的 `date_to` 是 **EXCLUSIVE**(不含当天)
|
||||
|
||||
```bash
|
||||
# ❌ 0 deals — 07-08 当天全部丢失
|
||||
GET /history/deals?date_from=2026-07-06&date_to=2026-07-08
|
||||
|
||||
# ✅ 42 deals — date_to 设成"明天"才能取到 07-08 当天
|
||||
GET /history/deals?date_from=2026-07-08&date_to=2026-07-09
|
||||
```
|
||||
|
||||
**规则**:永远把 `date_to` 设为"目标日期的下一天"。
|
||||
|
||||
### P2. `/history/deals` 的 `entry` 字段语义跟 MT5 标准 **相反**
|
||||
|
||||
```
|
||||
bridge entry = 1 ⇒ OUT(关仓)—— profit 字段是已实现 P&L(USD)
|
||||
bridge entry = 0 ⇒ IN (开仓)—— profit 固定为 0
|
||||
```
|
||||
|
||||
MT5 MQL5 原生约定是 `DEAL_ENTRY_IN=0 / DEAL_ENTRY_OUT=1`,这个 bridge 的 C# 实现把语义反过来了。
|
||||
**如果不验证就用 close 路径过滤 deal,会一个都匹配不到**(结果 P&L 永远是 0)。
|
||||
|
||||
```python
|
||||
# ✅ 正确:找关仓 deal
|
||||
exits = [d for d in deals if d.get('entry') == 1 and d.get('magic') == 88001]
|
||||
```
|
||||
|
||||
### P3. `/order/send` 只返回 `{retcode, order, comment}`,**没有成交价、没有 deal ticket**
|
||||
|
||||
```json
|
||||
// 实际响应(成功)
|
||||
{"data": {"retcode": 10009, "order": 1797395084, "comment": "Request executed"}}
|
||||
```
|
||||
|
||||
bridge 不返回 `price` 也不返回 `deal` 字段。**所以拿真实 fill 价格和实现 P&L,唯一办法是 close 之后查 `/history/deals`**。
|
||||
|
||||
```python
|
||||
# ❌ 永远拿不到正确价格
|
||||
result = api_post('/order/send', {...})['data']
|
||||
result.get('price') # None
|
||||
|
||||
# ✅ 正确:成交后从 history/deals 拿
|
||||
deals = api('/history/deals', params={'date_from': today, 'date_to': tomorrow})['data']
|
||||
exit_deal = next(d for d in deals if d['entry'] == 1 and d['symbol'] == sym)
|
||||
realized_pnl_usd = exit_deal['profit'] # broker 已经换算成 deposit currency
|
||||
actual_fill_price = exit_deal['price']
|
||||
```
|
||||
|
||||
### P4. `/positions.profit` **是 deposit currency(USD),不是 quote currency**
|
||||
|
||||
```python
|
||||
# USDCAD SELL 当前浮动 -0.17
|
||||
# 这是 USD 真实值 (-0.24 CAD ÷ 1.417 USD/CAD = -0.169 USD ≈ -0.17)
|
||||
# 不是 CAD!
|
||||
```
|
||||
|
||||
MT5 `POSITION_PROFIT` 的官方语义就是 in deposit currency,bridge 严格遵循。
|
||||
**如果手动算 USDCAD / USDJPY P&L 时按 quote currency 处理,会差一个汇率倍数**(USDCAD 大约 1.4×)。
|
||||
|
||||
### P5. bridge 拿到的 tick 跟 broker 实际 fill 差 1-4 ticks
|
||||
|
||||
`/positions` 的 `price_open` / `price_current` 和 `/order/send` 时看到的 tick 跟 broker 服务器**真实成交价**有几毫秒级的时间差,导致值差 0.0001-0.0004(约 0.1-0.4 pip)。
|
||||
|
||||
```
|
||||
broker 实际 fill: 1.41715
|
||||
/positions.price_open: 1.41711 ← 差 0.00004 (0.4 pip)
|
||||
```
|
||||
|
||||
**永远以 `/history/deals` 里的 `price` 为准做对账,不要用 `/positions` 的 price_open**。
|
||||
|
||||
### P6. `/position/close` 与 `/order/send` 的成功 retcode 含义不同
|
||||
|
||||
| 端点 | 成功 retcode | 失败 retcode | 来源 |
|
||||
| ----------------- | ------------------------ | ---------------- | ------------------------------ |
|
||||
| `/order/send` | MT5 原生(通常 `10009`) | MT5 原生 | 直接透传 `result.Retcode` |
|
||||
| `/position/close` | **合成** `10009` | **合成** `10004` | C# 代码 `ok ? 10009u : 10004u` |
|
||||
|
||||
两者都是 `10009 = success`,但**别假设 0 是 success**。建议统一判 `retcode == 10009`。
|
||||
|
||||
### P7. `data: []` 与"默认值数据"的语义混淆
|
||||
|
||||
Bridge 的所有 list 端点(`/account`, `/positions`, `/orders`, `/history/deals`, `/symbols` 等)都遵循这个统一约定:
|
||||
|
||||
- **"有数据"** ⇒ `{"data": [{...}], "count": 1, ...}`
|
||||
- **"无数据"** ⇒ `{"data": [], "count": 0, ...}`(**HTTP 仍 200**,不是错误)
|
||||
|
||||
客户端很容易把"无数据"误判成"默认值数据"。例:
|
||||
|
||||
```python
|
||||
# ❌ 错:data:[] 时 fallback 到 {},默认零值看着像合法数据
|
||||
info = api('/account')
|
||||
acc = (info.get('data') or [{}])[0]
|
||||
if not acc.get('trade_allowed'):
|
||||
raise PermissionError('trading disabled')
|
||||
# ↑ 实际可能是"账户未登录",不是"Algo Trading 真关闭"
|
||||
|
||||
# ❌ 错:bool({})==True,让健康检查通过
|
||||
if api('/account'):
|
||||
mark_healthy()
|
||||
```
|
||||
|
||||
**正确做法**:用 `data` 长度 + 身份字段(如 `login`、`ticket`)作为"真实数据就绪"的标志:
|
||||
|
||||
```python
|
||||
def data_ready(info, key='login'):
|
||||
"""data 非空 + 身份字段 > 0 ⇒ 真实数据"""
|
||||
items = info.get('data') or []
|
||||
return bool(items) and (items[0].get(key, 0) if items else 0) not in (0, None, '')
|
||||
|
||||
# ✅
|
||||
info = api('/account')
|
||||
if not data_ready(info, 'login'):
|
||||
raise ConnectionError('account not loaded yet')
|
||||
```
|
||||
|
||||
**适用所有 list 端点**。判 `positions`、`orders`、`deals` 同理。
|
||||
|
||||
---
|
||||
|
||||
## 1. 健康检查
|
||||
|
||||
```
|
||||
@@ -101,19 +224,19 @@ print(f"杠杆: 1:{acc['leverage']}, 币种: {acc['currency']}")
|
||||
|
||||
**返回字段:**
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| login | 账户号 |
|
||||
| balance | 余额 |
|
||||
| equity | 净值 |
|
||||
| profit | 浮动盈亏 |
|
||||
| margin | 已用保证金 |
|
||||
| margin_free | 可用保证金 |
|
||||
| margin_level | 保证金比例 |
|
||||
| leverage | 杠杆 |
|
||||
| currency | 账户币种 |
|
||||
| trade_allowed | 是否允许交易 |
|
||||
| trade_expert | 是否允许 EA 交易 |
|
||||
| 字段 | 含义 |
|
||||
| ------------- | ---------------- |
|
||||
| login | 账户号 |
|
||||
| balance | 余额 |
|
||||
| equity | 净值 |
|
||||
| profit | 浮动盈亏 |
|
||||
| margin | 已用保证金 |
|
||||
| margin_free | 可用保证金 |
|
||||
| margin_level | 保证金比例 |
|
||||
| leverage | 杠杆 |
|
||||
| currency | 账户币种 |
|
||||
| trade_allowed | 是否允许交易 |
|
||||
| trade_expert | 是否允许 EA 交易 |
|
||||
|
||||
---
|
||||
|
||||
@@ -138,13 +261,13 @@ print(f"XAUUSD Bid: {bid} Ask: {ask} Spread: {ask - bid}")
|
||||
|
||||
**返回字段:**
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| bid | 卖价 |
|
||||
| ask | 买价 |
|
||||
| last | 最新成交价 |
|
||||
| volume | 成交量 |
|
||||
| time | 时间 |
|
||||
| 字段 | 含义 |
|
||||
| ------ | ---------- |
|
||||
| bid | 卖价 |
|
||||
| ask | 买价 |
|
||||
| last | 最新成交价 |
|
||||
| volume | 成交量 |
|
||||
| time | 时间 |
|
||||
|
||||
#### 3-2. 订阅推送(WebSocket 流式)⭐ 推荐
|
||||
|
||||
@@ -303,11 +426,11 @@ def get_symbol_info(symbol):
|
||||
GET /rates/from-pos?symbol={symbol}&timeframe={timeframe}&start_pos={start}&count={count}
|
||||
```
|
||||
|
||||
| 参数 | 可选值 |
|
||||
|------|--------|
|
||||
| 参数 | 可选值 |
|
||||
| --------- | ---------------------------------------------------------- |
|
||||
| timeframe | `TIMEFRAME_M1` / `M5` / `M15` / `M30` / `H1` / `H4` / `D1` |
|
||||
| start_pos | 0 = 最新,1 = 前一根,以此类推 |
|
||||
| count | 获取数量,最大 10000 |
|
||||
| start_pos | 0 = 最新,1 = 前一根,以此类推 |
|
||||
| count | 获取数量,最大 10000 |
|
||||
|
||||
```python
|
||||
import pandas as pd
|
||||
@@ -340,11 +463,11 @@ print(df.head())
|
||||
GET /rates/from-date?symbol={symbol}&timeframe={timeframe}&date_from={date_from}&date_to={date_to}
|
||||
```
|
||||
|
||||
| 参数 | 可选值 |
|
||||
|------|--------|
|
||||
| 参数 | 可选值 |
|
||||
| --------- | ---------------------------------------------------------- |
|
||||
| timeframe | `TIMEFRAME_M1` / `M5` / `M15` / `M30` / `H1` / `H4` / `D1` |
|
||||
| date_from | 起始日期,ISO-8601 或 `yyyy-MM-dd` |
|
||||
| date_to | 结束日期,ISO-8601 或 `yyyy-MM-dd` |
|
||||
| date_from | 起始日期,ISO-8601 或 `yyyy-MM-dd` |
|
||||
| date_to | 结束日期,ISO-8601 或 `yyyy-MM-dd` |
|
||||
|
||||
```python
|
||||
def get_rates_by_date(symbol, timeframe, date_from, date_to):
|
||||
@@ -403,11 +526,11 @@ POST /position/close
|
||||
{ "ticket": 12345678, "volume": 0.05 }
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| ticket | ✅ | 持仓编号 |
|
||||
| volume | ❌ | 平仓手数;不传/0 = 全平;>0 = 部分平仓 |
|
||||
| deviation | ❌ | 允许滑点(默认 10) |
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --------- | ---- | -------------------------------------- |
|
||||
| ticket | ✅ | 持仓编号 |
|
||||
| volume | ❌ | 平仓手数;不传/0 = 全平;>0 = 部分平仓 |
|
||||
| deviation | ❌ | 允许滑点(默认 10) |
|
||||
|
||||
```python
|
||||
def close_position(ticket, volume=None):
|
||||
@@ -498,11 +621,11 @@ POST /positions/close-batch
|
||||
{ "symbol": "XAUUSDc" }
|
||||
```
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| symbol | 一 | 品种过滤 |
|
||||
| magic | 一 | Magic Number 过滤 |
|
||||
| deviation | ❌ | 允许滑点(默认 10) |
|
||||
| 字段 | 必填 | 说明 |
|
||||
| --------- | ---- | ------------------- |
|
||||
| symbol | 一 | 品种过滤 |
|
||||
| magic | 一 | Magic Number 过滤 |
|
||||
| deviation | ❌ | 允许滑点(默认 10) |
|
||||
|
||||
```python
|
||||
def close_by_magic(magic):
|
||||
@@ -604,11 +727,11 @@ check_order("XAUUSD", 0.01, 0, 4180.0, sl=4170.0, tp=4190.0)
|
||||
|
||||
**type_filling 说明:**
|
||||
|
||||
| 值 | 含义 | 说明 |
|
||||
|------|------|------|
|
||||
| 0 | FOK (Fill or Kill) | 必须全部成交,否则取消 |
|
||||
| 1 | IOC (Immediate or Cancel) | 能成交多少成交多少 |
|
||||
| 2 | RETURN | 剩余部分留在订单簿 |
|
||||
| 值 | 含义 | 说明 |
|
||||
| ---- | ------------------------- | ---------------------- |
|
||||
| 0 | FOK (Fill or Kill) | 必须全部成交,否则取消 |
|
||||
| 1 | IOC (Immediate or Cancel) | 能成交多少成交多少 |
|
||||
| 2 | RETURN | 剩余部分留在订单簿 |
|
||||
|
||||
> 不同经纪商支持的填充模式不同(如 ICMarkets 只支持 IOC),服务端会自动降级,客户端无需关心。
|
||||
|
||||
@@ -650,14 +773,14 @@ result = send_order("XAUUSD", 0.01, 0, 4180.0, sl=4170.0, tp=4190.0)
|
||||
|
||||
**order_type 说明:**
|
||||
|
||||
| 值 | 含义 |
|
||||
|------|------|
|
||||
| 0 | 市价买入 |
|
||||
| 1 | 市价卖出 |
|
||||
| 2 | 限价买入 |
|
||||
| 3 | 限价卖出 |
|
||||
| 4 | 止损买入 |
|
||||
| 5 | 止损卖出 |
|
||||
| 值 | 含义 |
|
||||
| ---- | -------- |
|
||||
| 0 | 市价买入 |
|
||||
| 1 | 市价卖出 |
|
||||
| 2 | 限价买入 |
|
||||
| 3 | 限价卖出 |
|
||||
| 4 | 止损买入 |
|
||||
| 5 | 止损卖出 |
|
||||
|
||||
---
|
||||
|
||||
@@ -995,14 +1118,32 @@ Invoke-RestMethod "http://61.164.252.86:13485/rates/from-date?symbol=XAUUSD&time
|
||||
|
||||
## 常见问题
|
||||
|
||||
> **第一次调这个 bridge?先去读 [§「⚠️ 隐含约定与已知陷阱」](#-隐含约定与已知陷阱先读)** —— 7 个非显而易见的约定(date_to 排他、`entry` 字段语义反、`/order/send` 不返回 fill 价、`/account` 空 data 误判 等),不读这一节直接调接口几乎必踩。
|
||||
|
||||
### 返回 "Unauthorized"
|
||||
|
||||
API Key 错误或没带。检查 Header 中的 `X-API-Key` 或 URL 中的 `?key=`。
|
||||
|
||||
### 返回 "对于该符号,不支持市场执行"
|
||||
|
||||
`order_type` 填错了,MT5 中有些品种不支持市价单,有些不支持挂单。先调用 `/order/check` 预检。
|
||||
|
||||
### 返回 "没有足够的资金"
|
||||
|
||||
保证金不足,减小手数或检查 `account.margin_free`。
|
||||
|
||||
### 闭仓 P&L 永远是 0 / 跟 MT5 terminal 对不上
|
||||
|
||||
大概率是过滤了 `entry==0` 的 deal(以为 OUT),实际这个 bridge 是 `entry==1` 才是 OUT(关仓)。详见 §隐含约定 P2。
|
||||
|
||||
### 算出来的 P&L 跟 broker 对差 1.4× / 1.5×
|
||||
|
||||
多半是手动把 `positions.profit` 当 quote currency 处理。`positions.profit` 是 deposit currency(USD),不是 quote currency。详见 §隐含约定 P4。
|
||||
|
||||
### 返回 "无法连接到远程服务器"
|
||||
Bridge 未运行或网络不通,先检查 `/health`。
|
||||
|
||||
Bridge 未运行或网络不通,先检查 `/health`。
|
||||
|
||||
### 业务字段返回默认值 → 怎么区分"无数据"和"真状态"
|
||||
|
||||
`/account` 在账户未加载完成时返回 `data:[]` + 全零字段(HTTP 200)。客户端若只看 `bool(response)` 或 `field == 0`,会把"无数据"误判成"零值正常状态"。详见 §隐含约定 P7。
|
||||
Reference in New Issue
Block a user