# AGENTS.md — Mobius Quant Engine API

> 面向消费此 API 的 AI agent / 程序客户端 / 集成方的操作手册。多数失败来自字
> 段名 / 大小写 / 端点猜错;本文档就是为了消除这种猜测。
>
> 想看能力全集 / 数据源 / 指标列表? 看 [`README.md`](./README.md)。

---

## 1. 这是什么

Mobius Quant Engine 是一个**多市场行情 + 技术分析 HTTP API**。

- **Crypto**(常驻 store 模式):Binance / Bybit / OKX spot+perp + Hyperliquid perp,
  共 7 venue,~2520 streams 实时同步,1m / 5m / 15m / 1h / 4h / 1d 全档
- **Stock + Forex**(转发模式):A 股 + 港股 + 美股 + 外汇,**任意标的代码都可查**
  (引擎实时从腾讯财经免费接口拉)
- **59 个技术指标**（含自定义模型）,按需计算；实现与一致性边界见各指标说明
- 提供原始 OHLCV、指标值、声明式 chart 绘制协议三种粒度
- **黄金现货**(可选,默认关闭):`commodity:spot` / `XAUUSD`,
  每 5 分钟更新缓存,六个周期(最小 5m),无真实成交量。接入限制见 §12。

它**不是**:下单平台、历史档案(缓存有界)、tick 流(最小 bar = 1m)、基本面 / 新闻
源、投资建议引擎。

详细数据源 + 指标罗列见 [`README.md`](./README.md)。

---

## 2. 接入参数

| | |
|---|---|
| Base URL | `https://api.mobiusquant.ai` |
| Auth | **可选** `Authorization: Bearer mq_xxx` —— 不带也能调,带 token 提速 |
| TLS | 必需(HTTP 自动跳 HTTPS) |
| 限速 | 匿名 **10 req/min per IP** · token **60 req/min**(详见 §11) |
| POST 请求 | `Content-Type: application/json` |

**数据准备期间**:先 probe `GET /api/health`。`status: "starting"` 表示行情仍在
准备；部分市场未就绪时也可能为 `degraded`。尚未就绪的行情查询返回 `503`，
按 `Retry-After` 等待后重试，不要紧密轮询。指标目录等发现接口无需等待行情。

---

## 3. 端点决策树

| 目标 | 方法 | 路径 |
|---|---|---|
| **数据 / 行情** | | |
| 列出所有 venue + 健康状态 | GET | `/api/markets` |
| 列出 venue 上支持的 symbol(crypto 用) | GET | `/api/markets/{exchange}/{market}/symbols` |
| 列出支持的 interval + 缓存深度 | GET | `/api/intervals` |
| 拉原始 OHLCV | GET | `/api/klines` |
| 计算指标(用于交易逻辑) | **POST** | `/api/indicators` |
| 构建图表(klines + 指标渲染协议) | **POST** | `/api/chart` |
| 用自带数据算指标(BYO klines,不查 store) | **POST** | `/api/indicators/compute` |
| **发现 / 元数据** | | |
| 列出所有指标 + 参数 + 输出列 | GET | `/api/indicators/registry` |
| 不读取行情，按实际参数解析字段与约束 | POST | `/api/indicators/registry/resolve` |
| 按指标、类型或 ID 查询特征定义、规则与含义 | GET | `/api/indicators/signals` |
| 列出可绘图的指标子集 + 样式元数据 | GET | `/api/indicators/catalog` |
| **运营层(playbook + builtin symbols)** | | |
| 列出 builtin symbol(natural-name → canonical) | GET | `/api/symbols/builtin` |
| 搜索标的 | GET | `/api/symbols/search?q=` |
| 列出所有 playbook | GET | `/api/playbooks` |
| 单 playbook 详情 | GET | `/api/playbooks/{name}` |
| 一键跑 playbook(多周期分析) | **POST** | `/api/analyze` |
| **服务自描述** | | |
| 服务健康 | GET | `/api/health` |
| 本文档 | GET | `/agents.md` |
| OpenAPI schema | GET | `/openapi.json` |
| Swagger UI | GET | `/docs` |

**常见误用**:`/api/indicators` 和 `/api/chart` 都是 **POST**,GET 会返回 `405
Method Not Allowed`。请求体 schema 一致(见 §6),返回格式不同。

---

## 4. 概念

### 4.1 Venue 是 (exchange, market) 对,不是单字符串

两个字段分开传:

```jsonc
// ✅ 正确
{"exchange": "binance", "market": "perp", ...}

// ❌ 错误
{"venue": "binance:perp", ...}
{"exchange": "binance:perp", ...}
{"exchange": "binance-perp", ...}
```

**基础 venue 表**(黄金现货等可选 venue 以 `/api/markets` 为准):

| Exchange | Market | 模式 | 说明 |
|---|---|---|---|
| `binance` | `spot` / `perp` | store(WS) | USDT-M 期货叫 perp,不叫 futures |
| `bybit` | `spot` / `perp` | store(WS) | perp 含 USDT-M + USDC-M(都用 USDC 后缀作 canonical) |
| `okx` | `spot` / `perp` | store(WS) | perp 仅 USDT-M(无 USDC) |
| `hyperliquid` | `perp` | store(WS) | 仅 USDC perp,无 spot |
| `stock` | `cn` / `hk` / `us` | forwarded(HTTP) | A 股 / 港股 / 美股 |
| `forex` | `spot` | forwarded(HTTP) | 外汇即期 |

`market` 合法值:`spot` `perp` `cn` `hk` `us`。**不接受** `futures` / `future`
/ `swap` / `margin` / `usdt-m`。

**覆盖缺口**:不存在的 venue 返回 400。
- `okx:perp` 无 USDC 对(只有 USDT)
- `hyperliquid:perp` 无 USDT 对(只有 USDC)

### 4.2 Symbol — canonical 格式按 venue 不同

#### Crypto:`{BASE}{QUOTE}` 全大写连写

```jsonc
✅ "BTCUSDT" "ETHUSDC" "1000PEPEUSDT" "BNBUSDC"
❌ "btcusdt"           // 大小写
   "BTC/USDT"          // 分隔符
   "BTC-USDT"          // 连字符
   "BTC"               // Hyperliquid 原生形态 — 不接受
   "BTCPERP"           // Bybit USDC perp 原生 — 不接受
   "BTC-USDT-SWAP"     // OKX 原生 — 不接受
```

引擎内部翻译 canonical → exchange-native。**永远发 canonical**,native 形态
返回 404。

#### Stock A 股 (`stock:cn`):**6 位裸代码**(无 sh/sz/bj 前缀)

```jsonc
✅ "600519"  // 茅台,沪市主板
✅ "300750"  // 宁德时代,创业板
✅ "688981"  // 中芯国际,科创板
✅ "430047"  // 北交所(自动识别 4/8 开头)
✅ "510500"  // 沪市 ETF
✅ "159915"  // 深市 ETF
❌ "sh600519"   // 不要带前缀
❌ "600519.SH" // 不要带后缀
```

板块由代码前缀自动判别:`6/9/5/11/20` 沪市,`0/3/12/15/16` 深市,`43/83/87/88/92`
北交所。

#### Stock 港股 (`stock:hk`):**5 位带前导零**

```jsonc
✅ "00700"  // 腾讯
✅ "09988"  // 阿里
✅ "00388"  // 港交所
✅ "08083"  // GEM 板
❌ "700"     // 不要去前导零
❌ "hk00700" // 不要带 hk 前缀
```

#### Stock 美股 (`stock:us`):**裸 ticker**(无后缀)

```jsonc
✅ "AAPL"    // NASDAQ
✅ "JPM"     // NYSE(引擎内置 NYSE/NASDAQ 后缀映射表)
✅ "NVDA"    // 任意主流 ticker
✅ "TSM"     // ADR
❌ "AAPL.OQ"   // 不要带 .OQ
❌ "JPM.N"     // 不要带 .N
❌ "BRK.B"     // 含点 ticker 暂不支持
```

NASDAQ / NYSE 区分由引擎内置映射处理;不在表中的 ticker 默认按 NASDAQ 走,
未命中可能空数据。

#### Forex (`forex:spot`):**6 字母连写**

```jsonc
✅ "EURUSD" "USDJPY" "GBPUSD" "USDCHF" "USDCNY"
❌ "EUR/USD"   // 不要分隔符
❌ "EURUSD=X"  // 不要后缀
```

### 4.3 Symbol 前缀约定(crypto memecoin)

- 价格极小的 memecoin 带 `1000` 前缀:`1000PEPEUSDT` `1000LUNCUSDT` `1000BONKUSDT`
- Hyperliquid 原生 `kPEPE` canonical 化为 `1000PEPEUSDC`
- 在 `GET /api/markets/{ex}/{mk}/symbols` 确认精确名,**不要靠猜**

### 4.4 Interval — 严格小写

完整集合:`1m` `5m` `15m` `30m` `1h` `4h` `1d`

**每个 venue 实际支持的子集不同**:

| Venue | 支持的 intervals |
|---|---|
| crypto(7 个 venue 都一样) | `1m` `5m` `15m` `1h` `4h` `1d` |
| `stock:cn` | `1m` `5m` `15m` `30m` `1h` `1d` |
| `stock:hk` / `stock:us` / `forex:spot` | **仅 `1d`**(腾讯无分钟 K) |

```
✅ "1m" "5m" "15m" "30m" "1h" "4h" "1d"
❌ "1M" "1H" "1D"   // 大写
❌ "60" "60m"       // 数字 / 错误后缀
❌ "1hr" "240m"     // 拼写错误
❌ "1w" "1M"        // 周线月线暂未暴露
```

打错的 interval 返回 400 with `valid: [...]`。

### 4.5 时间格式

**所有时间一律毫秒** Unix epoch。

```jsonc
✅ "startTime": 1735689600000   // 2025-01-01T00:00:00Z
❌ "startTime": 1735689600      // 秒 — 解释为 1970-era
❌ "startTime": "2025-01-01"    // 字符串 — Pydantic 422
```

响应 `open_time` 也是 ms。Chart payload 的 `time` 是**秒**(lightweight-charts
惯例)。**两者故意不一致**,不要做归一化。

### 4.6 指标请求格式 — 命名参数字典

`calc` 字段是**对象列表**,不是字符串。

```jsonc
✅ 正确
"calc": [
  {"name": "ema",  "params": {"period": 20}},
  {"name": "rsi",  "params": {"period": 14}},
  {"name": "macd", "params": {"fast": 12, "slow": 26, "signal": 9}}
]

❌ 错误(旧 compact 格式已下线)
"calc": "ema:20,rsi:14,macd:12:26:9"
```

**参数名必须精确匹配指标签名**。EMA 用 `period`(不是 `length` / `n` /
`window`)。查准确参数名:`GET /api/indicators/registry`。

### 4.7 `id` 字段 — 何时设置

每个 `calc[]` 条目在响应里对应一个 key。默认 key = `"name:k1=v1,k2=v2"`
(params 按字母排序)。两条目产生同 key → 400;用 `id` 消歧。

```jsonc
// ✅ 同指标不同参数 → 自动不同 key,无需 id
"calc": [
  {"name": "ema", "params": {"period": 20}},   // key: "ema:period=20"
  {"name": "ema", "params": {"period": 50}}    // key: "ema:period=50"
]

// ✅ 同指标同参数 → 必须给 id
"calc": [
  {"name": "rsi", "params": {"period": 14}, "id": "rsi_close"},
  {"name": "rsi", "params": {"period": 14}, "id": "rsi_hl2"}
]

// ❌ 完全重复无 id → 400 Duplicate calc key
```

### 4.8 `limit` 字段 — 每个端点上限不同

| 端点 | 上限 |
|---|---|
| `GET /api/klines` | 每 (venue, interval),查 `/api/intervals` 的 `max_klines` |
| `POST /api/indicators` | `1000`(常量) |
| `POST /api/chart` | 同 `/api/klines`,**静默截断** |
| `POST /api/analyze` | 由 playbook 决定,典型 50-200 |

`/api/indicators` 超出返回 400;`/api/chart` 静默截断到上限。

---

## 5. 响应结构

所有响应都是 JSON。

### 5.1 `GET /api/klines`

```jsonc
{
  "exchange": "binance", "market": "perp", "symbol": "BTCUSDT", "interval": "1h",
  "data": [
    [1735689600000, 95000.0, 95500.0, 94800.0, 95200.0, 1234.5, 117500000.0],
    // [open_time_ms, open, high, low, close, volume, quote_volume]
    ...
  ]
}
```

每行 7 列:`[open_time_ms, open, high, low, close, volume, quote_volume]`。
注意是标准 OHLC 顺序(不是 OCHL)。

### 5.2 `POST /api/indicators`

```jsonc
{
  "exchange":"binance", "market":"perp", "symbol":"BTCUSDT", "interval":"1h",
  "current_price": 95200.0,
  "count": 100,
  "klines": [[1735689600000, 95000.0, ...], ...],  // 与指标行 1:1 对齐
  "indicators": {
    "ema:period=20": {
      "columns": ["open_time", "ema_20"],
      "data": [[1735689600000, 94890.5], ...]
    },
    "rsi:period=14": {
      "columns": ["open_time", "rsi"],
      "data": [[1735689600000, 67.4], ...]
    },
    "macd:fast=12,signal=9,slow=26": {
      "columns": ["open_time", "macd", "signal", "hist"],
      "data": [[1735689600000, 120.5, 110.2, 10.3], ...]
    }
  }
}
```

`indicators` 的 key 由 name+params(或 `id`)确定。**按 key 读,不要按位置**。

### 5.3 `POST /api/chart`

```jsonc
{
  "exchange":"binance", "market":"perp", "symbol":"BTCUSDT", "interval":"1h",
  "klines": [{"time":1735689600, "open":95000.0, "high":95500.0,
              "low":94800.0, "close":95200.0, "volume":1234.5}, ...],
  "panels": [
    {"id":"main", "overlay":true, "height_ratio":3.0, "items":[
      {"type":"line", "label":"EMA(20)", "style":{"role":"primary","width":2},
       "data":[{"time":1735689600,"value":94890.5}, ...]}
    ]},
    {"id":"rsi:period=14", "overlay":false, "height_ratio":1.0,
     "value_range":[0,100], "items":[
      {"type":"line", "label":"RSI(14)", "style":{...}, "data":[...]},
      {"type":"hline", "value":30, "style":{"role":"oversold","dash":"dashed"}},
      {"type":"hline", "value":70, "style":{"role":"overbought","dash":"dashed"}}
    ]}
  ]
}
```

`time` 这里是**秒**(不是 ms)。`style.role` 是语义标签(frontend 按主题映射到
颜色)。每个 panel 渲染为独立子图垂直堆叠,`height_ratio` 控制相对高度。

### 5.4 `POST /api/analyze`(playbook)

```jsonc
{
  "playbook": "market-pulse",
  "asset_class": "crypto",
  "intervals": ["5m", "15m"],
  "data": {
    "5m": {
      "current_price": 95200.0,
      "klines": [[...], ...],
      "indicators": {
        "mobius_trend:...": {"data":[...], "explain": { /* knowledge */ }},
        "cvd:...":          {"data":[...], "explain": { /* knowledge */ }}
      }
    },
    "15m": { /* 同结构 */ }
  }
}
```

每个 indicator 内嵌 `explain`(knowledge 的 `summary_focus`),LLM 可直接据此
按 playbook 的 `synthesis` / `output_template` / `constraints` 综合分析。

### 5.5 `POST /api/indicators/compute`(BYO klines)

跟 `/api/indicators` 几乎同形态,区别:**用户自带 OHLCV**,引擎不查 store。
请求顶层可传 `last_bar_closed=false` 表示末根尚未收盘（默认 true，调用方保证输入已收盘）；
CVD 不会用形成中的确认柱触发正式背离。提供 `interval` 可计算 CVD 确认柱的收盘时刻，
不提供时 `confirmed_at_ms=null`，但仍返回确认柱的 `confirmation_bar_time_ms`。
适用场景:回测、自定义数据源、引擎没订阅的市场、离线分析。

```jsonc
// Request
{
  "klines": [
    [1735689600000, 95000.0, 95500.0, 94800.0, 95200.0, 1234.5],
    [1735693200000, 95200.0, 95800.0, 95100.0, 95650.0, 1500.2],
    // ... ≤ 2000 根
  ],
  // 或对象形式(自动识别):
  // [{"open_time":1735689600000, "open":95000, "high":95500, "low":94800,
  //   "close":95200, "volume":1234.5, "quote_volume":117526200}, ...]
  "interval": "1h",         // 可选,纯回显;某些 MTF 指标用不到
  "calc": [{"name":"smc", "params":{"show_premium_discount_zones":true}, "id":"smc"}],
  "echo_klines": true       // 默认 true;false 时响应不回 klines 数组
}

// Response
{
  "count": 500,
  "interval": "1h",         // 仅当 request 传了
  "klines": [[...], ...],   // echo,除非 echo_klines=false
  "indicators": {
    "smc": {
      "columns": [...],
      "data":    [...],
      "objects": {...},     // SMC 等非数值指标自动透传 sidecar
      "explain": {...}      // explain=true 时附带 knowledge 解读
    }
  }
}
```

**约束**:
- `klines` 通常上限 **2000 根**；仅含 HMM 的请求可到 5000 根（详见 §14）
- `open_time` 必须**严格单调递增的毫秒**;OHLCV 必须 finite
- **无 warmup buffer** —— 用户自带数据全权负责:ATR(200)/EMA(200) 至少 200 根、SMC swing ≥ 50、FVG ≥ 3
- `calc` schema 跟 `/api/indicators` 完全一致;auth + rate limit 同套

---

## 6. Recipes

### 6.1 拉 BTC perp 最近 100 根 1h K 线

```bash
curl 'https://api.mobiusquant.ai/api/klines?exchange=binance&market=perp&symbol=BTCUSDT&interval=1h&limit=100'
```

### 6.2 看 BTC 是不是超买(RSI > 70)

```bash
curl -X POST https://api.mobiusquant.ai/api/indicators \
  -H 'content-type: application/json' \
  -d '{
    "exchange":"binance","market":"perp","symbol":"BTCUSDT","interval":"1h",
    "limit": 1,
    "calc":[{"name":"rsi","params":{"period":14}}]
  }'
```

读 `indicators["rsi:period=14"].data[-1][1]`,大于 70 即超买。

### 6.3 EMA 金叉死叉(短 vs 长)

```bash
curl -X POST https://api.mobiusquant.ai/api/indicators \
  -H 'content-type: application/json' \
  -d '{
    "exchange":"binance","market":"perp","symbol":"ETHUSDT","interval":"4h",
    "limit": 2,
    "calc":[
      {"name":"ema","params":{"period":20},"id":"ema_short"},
      {"name":"ema","params":{"period":50},"id":"ema_long"}
    ]
  }'
```

对比 `indicators.ema_short.data[-1][1]` 和 `indicators.ema_long.data[-1][1]`
最近两根的相对位置,判断金叉 / 死叉方向。

### 6.4 拉 A 股茅台日 K + RSI

```bash
curl -X POST https://api.mobiusquant.ai/api/indicators \
  -H 'content-type: application/json' \
  -d '{
    "exchange":"stock","market":"cn","symbol":"600519","interval":"1d",
    "limit": 30,
    "calc":[{"name":"rsi","params":{"period":14}}]
  }'
```

### 6.5 拉 A 股 5 分钟 K 线 + EMA

```bash
curl -X POST https://api.mobiusquant.ai/api/indicators \
  -H 'content-type: application/json' \
  -d '{
    "exchange":"stock","market":"cn","symbol":"300750","interval":"5m",
    "limit": 200,
    "calc":[{"name":"ema","params":{"period":20}}]
  }'
```

注意:港股 / 美股 / 外汇 **没有分钟 K**,interval 必须传 `1d`。

### 6.6 拉港股腾讯日 K

```bash
curl 'https://api.mobiusquant.ai/api/klines?exchange=stock&market=hk&symbol=00700&interval=1d&limit=60'
```

### 6.7 拉美股 NVDA 日 K(任意 ticker 都可,无需预注册)

```bash
curl 'https://api.mobiusquant.ai/api/klines?exchange=stock&market=us&symbol=NVDA&interval=1d&limit=60'
```

### 6.8 拉外汇 EURUSD 日 K

```bash
curl 'https://api.mobiusquant.ai/api/klines?exchange=forex&market=spot&symbol=EURUSD&interval=1d&limit=30'
```

### 6.9 渲染图表 — K 线 + EMA + RSI + MACD

```bash
curl -X POST https://api.mobiusquant.ai/api/chart \
  -H 'content-type: application/json' \
  -d '{
    "exchange":"binance","market":"perp","symbol":"BTCUSDT","interval":"1h",
    "limit": 200,
    "calc":[
      {"name":"ema","params":{"period":50}},
      {"name":"rsi","params":{"period":14}},
      {"name":"macd","params":{"fast":12,"slow":26,"signal":9}}
    ]
  }'
```

遍历 `panels[]`,每个 panel 渲染为独立子图。

### 6.10 用自带数据算指标(BYO klines)

回测、自定义数据源、引擎没订阅的市场场景:

```bash
curl -X POST 'https://api.mobiusquant.ai/api/indicators/compute?explain=true' \
  -H 'authorization: Bearer mq_xxx' \
  -H 'content-type: application/json' \
  -d '{
    "klines": [
      [1735689600000, 95000.0, 95500.0, 94800.0, 95200.0, 1234.5],
      [1735693200000, 95200.0, 95800.0, 95100.0, 95650.0, 1500.2]
    ],
    "interval": "1h",
    "calc": [{"name": "rsi", "params": {"period": 14}}]
  }'
```

注意:
- klines 自带,**无 warmup buffer** —— 需要够 history。ATR/EMA(200) ≥ 200 根、SMC swing ≥ 50、FVG ≥ 3。
- 通常上限 **2000 根**；仅含 HMM 的请求可到 5000 根（详见 §14）
- `open_time` 必须严格单调递增的毫秒,OHLCV 必须 finite
- auth / rate limit 跟 `/api/indicators` 同套

适合场景:
- 给历史数据跑指标(数据库导出的 OHLCV、CSV 上传)
- 自定义合约 / 跨交易所聚合的虚拟 K 线
- 模拟数据回测策略

不适合:
- 实时行情(走 `/api/indicators` 让引擎从 store 取,延迟更低)
- MTF 跨周期需求(`fvg_timeframe` 等参数走 store 路径才支持)

### 6.11 跑 playbook(自动多周期分析)

```bash
curl -X POST https://api.mobiusquant.ai/api/analyze \
  -H 'content-type: application/json' \
  -d '{
    "playbook":"market-pulse",
    "exchange":"binance","market":"perp","symbol":"BTCUSDT",
    "limit": 100
  }'
```

引擎按 playbook 定义的 intervals + indicators 跑完一套,返回每个周期的 K 线 +
指标 + explain。LLM 按 playbook 的 `synthesis` 模板生成最终回答。

### 6.12 发现哪些 symbol / 指标 / playbook 可用

```bash
# Crypto symbol 列表(per venue)
curl 'https://api.mobiusquant.ai/api/markets/binance/perp/symbols'

# Stock / forex 转发模式 — 任意标的都可查,无需先列表;若想看预热示例:
curl 'https://api.mobiusquant.ai/api/symbols/builtin'

# 所有指标
curl 'https://api.mobiusquant.ai/api/indicators/registry'

# 所有 playbook
curl 'https://api.mobiusquant.ai/api/playbooks'
```

### 6.13 探测服务健康

```bash
curl 'https://api.mobiusquant.ai/api/health'
```

| `status` | 含义 | 行动 |
|---|---|---|
| `ok` | 所有 venue 健康 | 继续 |
| `degraded` | 部分流过期(数据仍服务) | 继续 |
| `dead` | 所有 WS batch 失败 | 任务失败 |
| `starting` | crypto init 中 | 等 60s 重试 |

---

## 7. 错误 — 状态码语义

| 码 | 原因 | 行动 |
|---|---|---|
| **400** | 未知 exchange/market/interval、未知指标、calc 参数与签名不符、calc key 重复(无 id)、limit 超 cap、不支持的 venue+interval 组合(如 `stock:hk`+`5m`) | 读 `detail` 修正后重发 |
| **404** | symbol 在该 venue 上不存在,或缓存空(crypto init 中);**stock/forex 假代码也是 404 + 空 data** | 先看 `/api/health`,`starting` 等;否则查 `/api/markets/.../symbols` |
| **405** | GET 调了 POST 端点 | 换 POST + JSON body |
| **422** | Pydantic 校验:必填缺失、类型错、未知字段、JSON 错乱 | 读 validation 数组修请求结构 |
| **401** | 缺 token / token 错误 / 失效 / 撤销 | 不要重试,先修 token(§11) |
| **429** | 速率限制(匿名 10/min per IP 或 token 60/min) | 看 `Retry-After` 头退避;匿名可申 token 升档 |
| **502** | 上游容器重启 | 30 秒后重试 |
| **503** | 所请求行情或知识库尚未就绪，或行情暂不可用 | 检查 `/api/health`，优先按 `Retry-After` 退避 |
| **504** | 上游(腾讯)超时,仅 stock/forex | 短时重试,可能命中 stale cache |

**错误体一律 `{"detail": "..."}`,重试前必须读 `detail`** — 400 盲目重试会
死循环。

---

## 8. 常见坑 — 集成方踩过的

1. **`GET /api/indicators` → 405**。是 POST。`/api/chart` `/api/analyze` 同
2. **旧 compact-list `"calc":"ema:20"`** 已下线,用 object 列表
3. **`interval:"1H"` → 400**,小写
4. **`symbol:"BTC/USDT"` → 404**,canonical 无分隔符
5. **`symbol:"BTC"` 给 Hyperliquid → 404**,用 `BTCUSDC`(canonical 已翻译)
6. **`startTime` 用秒**,默默拉到 1970 — 务必毫秒
7. **`exchange:"binance:perp"` → 400**,分两字段
8. **`market:"futures"` → 400**,用 `perp`
9. **OKX 求 USDC perp → 404**,OKX 只有 USDT perp
10. **Hyperliquid 求 USDT pair → 404**,HL 只有 USDC
11. **A 股代码带前缀 `"sh600519"` → 404**,canonical 是裸代码 `600519`
12. **港股代码 `"700"` → 404**,canonical 是 5 位带前导零 `00700`
13. **美股代码 `"AAPL.OQ"` → 404 或 0 行**,canonical 是裸 `AAPL`
14. **`stock:hk` + `interval:5m` → 400** "Interval '5m' not supported on
    stock:hk; valid: ['1d']";港股 / 美股 / 外汇腾讯只有日级
15. **`limit:9999` 给 `/api/indicators` → 400**,常量 1000 上限
16. **同名同参数 calc 无 id → 400** Duplicate calc key
17. **按下标读 `indicators` → 错** — 是 dict,按 key 取,顺序保留但不要靠它
18. **行情准备中 → 503** — probe `/api/health`，按 `Retry-After` 等待；已就绪市场可独立查询。

---

## 9. 运营层 — playbook 和 symbol search

引擎不仅给"原料"(K 线 / 指标),也给"成品配方"(playbook)。

```
basics layer    /api/indicators       单指标
                /api/chart            可视化
                /api/klines           原始 K 线

operations layer /api/symbols/...     natural-name → canonical
                 /api/playbooks       预定义指标组合 + 周期
                 /api/analyze         按 playbook 自动跑完整分析
```

### 当前 playbook 目录

| Playbook | 适用 | 指标组合 | 周期 |
|---|---|---|---|
| `market-pulse` | crypto / stock / forex 通用 | `mobius_trend` + `cvd` | crypto 5m/15m · stock 30m/1h · forex 15m/1h |

调 `/api/playbooks` 拿最新列表(admin 后台改 → engine 60s 自动拉)。

### Symbol 搜索

`GET /api/symbols/search?q=` 接受中英 ticker / 名称 / 别名,返回 canonical。
适合"用户说 AAPL 想看苹果"这种自然语言场景。

---

## 10. 参考

| 端点 | 用途 |
|---|---|
| `GET /openapi.json` | 完整机器可读 schema(所有端点 / 字段类型 / 示例) |
| `GET /docs` | Swagger UI(人 / 抽查) |
| `GET /api/indicators/registry` | 每个指标的参数 + 输出列 |
| `GET /api/indicators/signals` | 按指标分组的特征定义；可按 `indicator`、`kind`、`id` 筛选 |
| `GET /api/indicators/catalog` | 有 chart spec 的指标子集 |
| `GET /api/markets` | 每 venue symbol 数 + WS 状态 |
| `GET /api/intervals` | interval 列表 + 缓存深度 + staleness budget |

**字段名或值有疑问时,先调发现端点,再发 POST**。一次额外 GET 远比一次失败
POST 的代价小。

---

## 11. 鉴权与速率限制

分档限速:

| 档 | 触发条件 | 限速 | key |
|---|---|---|---|
| 匿名 | 不带 `Authorization` 头 | **10 req/min per IP** | `ip:<CF-Connecting-IP>` |
| Token | 带合法 `Authorization: Bearer mq_xxx` | **60 req/min** | `tok:<user_id>` 或 `atok:<token_id>` |

带了 Authorization 但 token **不合法**(拼错 / 过期 / 已撤销)→ `401`,不降级匿名。

burst = 配额本身 —— 可一秒打完,然后等满整一分钟从空桶补满。

**豁免路径**(既不要求 token 也不限速):`/api/health` `/agents.md` `/docs`
`/redoc` `/openapi.json`。

### 申请 token(可选,匿名也能直接用)

- **匿名试用**(7 天):https://www.mobiusquant.ai/zh/apply-token,过人机验证即可
- **登录永久**:https://www.mobiusquant.ai/zh/account → API Tokens → 新建
- 明文 `mq_<43 chars>` **只显示一次**,立刻复制保存
- 调用时 `Authorization: Bearer mq_xxx`
- 登录用户最多 10 个 active token;撤销/到期 ≤60 秒传播到引擎,新建 token cache miss
  会触发单查回源,几乎 0 延迟生效

### 响应头

每个非豁免调用响应都会带:

```
X-RateLimit-Limit: 10    (匿名档) 或 60 (token 档)
```

429 时:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 7
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0

# 匿名超限:
{"detail": "rate limit exceeded (10/min); apply for a free token at https://www.mobiusquant.ai/zh/apply-token to upgrade to 60/min."}

# token 超限:
{"detail": "rate limit exceeded (60/min); consider batching requests."}
```

**尊重 `Retry-After`**。死循环重试只会让桶永远空。

### 401 vs 429

- **401** = 带了 token 但 token 错(拼错 / 过期 / 撤销)。停止重试,先修 token
- **429** = 桶被打空,按 `Retry-After` 退避;匿名档可申 token 升 6x

---

## 12. 黄金现货(可选)

启用后使用 `exchange="commodity"`, `market="spot"`, `symbol="XAUUSD"`。
不要使用 `XAU/USD`、`forex:spot` 或期货标的 `XAUF`。
`commodity:futures` 是另一个既有 venue,不受这项新增功能影响；本文前面
关于 crypto 不接受 `futures` 的规则不适用于商品期货。

支持 `5m / 15m / 30m / 1h / 4h / 1d`。所有周期每 5 分钟更新,
**最小周期为 5m,黄金现货请求 1m 返回 400**。查询实例实际是否启用、就绪:

```bash
curl 'https://api.mobiusquant.ai/api/health'
curl 'https://api.mobiusquant.ai/api/intervals?exchange=commodity&market=spot'
curl 'https://api.mobiusquant.ai/api/klines?exchange=commodity&market=spot&symbol=XAUUSD&interval=5m&limit=100'
```

黄金继续复用 `/api/klines`、`/api/indicators`、`/api/chart` 和价格指标。
响应额外包含 `data_meta`(analyze 为每个周期单独附带):

- `price_type=mid`, `volume_type=unavailable`。
- `sync_interval_secs=300`, `last_sync_ms` 为分钟采集最近成功时间,
  `data_as_of_ms` 为该周期数据覆盖时间；不是本次 API 查询时间。
- `last_bar_complete` 表示缓存最新 bar 是否已收盘且数据完整；未完成 bar
  会随后续采集修订。历史窗口查询时,此字段仍描述缓存的最新 bar。
- `ready` / `alignment_verified` / `stale` / `last_error` 描述当前数据质量。
- `session_timezone=Australia/Sydney`, `session_start=07:00` 是数据分桶边界,
  **不是可交易时段承诺**。日线开盘时间按该边界转为 epoch ms；4h 遵循
  悉尼墙上时间,跨夏令时的 bar 实际可能是 3/5 小时。

限制:

1. 两个 volume 字段的 0 仅为协议占位,不是零成交。CVD、VWAP、VWMA、OBV、
   CMF、KVO、PVO、Chaikin oscillator、Fibonacci Bollinger Bands 返回 400。
   `/api/indicators/registry` 的 `requires_volume` 可用于前端禁用。
2. 现有含 CVD 的 `market-pulse` 不能直接用于黄金；需在管理后台配置仅用
   价格指标的 playbook,并提供 commodity 默认周期或在请求中明确 `intervals`。
3. 初始化/时间边界校验失败返回 503。15 分钟 stale 仍可读缓存并带标记；
   超过 1 小时,最新数据查询返回 503。历史有界查询可继续读旧缓存。
4. 六个公开周期的返回 cap 均为 1000,内部额外保留指标预热空间。
   `extend_history=true` 不会触发上游拉取；超出已缓存范围返回 400。
5. 调用方只使用 Mobius token(如需),**不要把上游 key 发给本 API**。
   上游 key 仅由服务端调度器使用；API 缓存读取不消耗上游 credits。
6. 仅在部署方具有对应展示/再分发授权时对外开放。

---

## 13. CVD 柱状数据与背离事件

`cvd_hist` 是柱高，`cvd_delta` 是单根 OHLCV 估算量差，非逐笔主动买卖成交量。
原有 `bull_div/bear_div` 和强度列保留；每个 CVD 结果新增 `objects.divergences`。
每个事件包含 `start/end`（各含 `time_ms/price/cvd`）、`direction=bullish|bearish`、
`kind=regular`、`status=confirmed`、`id`、`label=+RD|-RD`、`strength`，以及
`confirmation_bar_time_ms`（确认柱开盘）与 `confirmed_at_ms`（按周期计算的收盘）。
所有事件时间均为毫秒；图表 `panels.items[].data[].time` 仍为秒。

默认需要右侧 2 根 K 线且确认柱到达周期收盘时刻才有正式信号。连线画回历史端点
不意味着当时已经知道信号。强度 1–3 是连续计数而非胜率；区间不是信号有效期。
查询窗口按确认柱过滤事件，保留完整原始端点；只裁剪绘图坐标。
Chart 的 `cvd-divergence` 声明 `rendered_in_panels=true`，主图/副图线与标记
已在 panels 中，消费方不要再从 objects 重复画一次。多个 CVD 实例按响应 key 分组。

## 14. HMM Mobius 双尺度方向

指标名 `hmm_regime`；实时 crypto `5m/15m/1h/4h/1d`，BYO 另支持 `12h`。
HMM 必须明确 interval，不支持 1m/30m；12h 仅支持自带 K 线计算。
通过 indicators / compute / chart / analyze 端点调用。

```json
{"name":"hmm_regime","id":"hmm","params":{"horizon":20,"fast_weight":0.35,"stick":0.97,"confirm":3,"dominance":0.55,"margin":0.15,"normalization":"auto"}}
```

- 8 个业务字段：`hmm_state, hmm_status,
  hmm_p_up, hmm_p_range, hmm_p_down, hmm_context_state, hmm_context_status, hmm_supported`，
  另加公共 open_time（毫秒）。按 columns 字段名读取。
- 方向编码 `-1=无有效方向, 0=震荡, 1=上涨, 2=下跌`。
  status 为 unavailable（预热/缺口）、uncertain（待确认/证据不足）、
  confirmed（已确认且证据支持）。不要把 -1 当下跌。
- hmm_supported：true 当前已收盘证据支持局部保留方向；false 已有保留方向但证据不支持；
  null 没有有效保留方向，含预热、缺口和首次确认等待。false 不等于反向已确认。
  证据恢复支持原方向不必重新等待 confirm 根；建立/切换方向才需要连续确认。
- 三类局部权重 0～1、合计约 1，不是校准概率或胜率。不可用阶段初始 1/3 不构成结论。
  保留方向不一定与最大权重类别相同；背景为同周期 horizon×3，不是跨周期。
- normalization 为 auto/d1/legacy，默认 auto：5m/15m/1h→D1，4h/12h/1d→Legacy。
  显式模式覆盖自动选择。
- 基础 meta 六个键：schema_version=3、model_version=mobius-direction-v2、effective_normalization、
  history_start_ms、last_bar_closed、last_confirmed_time_ms。
  末项是截至窗口末尾最后已收盘K线的开盘时间，不是方向切换时刻；空窗口后两项为null。
- 未收盘末根沿用方向、权重和证据支持；BYO末根形成中传顶层last_bar_closed=false。
  horizon20输入预热D1局部20/背景60，Legacy153/461；horizon100分别100/300与769/2309，
  之后仍需方向确认；是否可用以 status 为准。
- Registry 的 supported_intervals 包括 BYO12h；supported_live_intervals 不包括12h。
  normalization_by_interval 和 default_warmup_bars_by_normalization 描述默认模式及分模式预热。
- 仅计算 HMM 时自带 K 线上限5000，混合指标时2000；
  超业务上限413，超5000硬上限422；每次最多8个HMM实例；limit只裁剪输出。
- 行情接口的 meta 包含 `history_status=warming|ready|partial` 和 `history_bars`；
  前者分别表示准备中、完整、不完整，后者是本次计算输入根数。`ready` 不代表方向已确认。
  Chart 对应信息在 `indicator_meta[请求key]`。自带 K 线计算不返回这两个字段。
  需要完整历史的调用方等待 ready，不要把 warming/partial 当作完整结果；
  新上市标的可能长期 partial。按正常刷新节奏复查，不要紧密循环请求。
- 默认Chart只有Up/Range/Down三条标准line，取相同0～1权重，time为秒。
  无HMM objects、renderer、背景、仪表盘或着色；可选特征与事件绘图见 §15。
  paint_bars/show_dashboard/show_details不是有效参数，传入返回400。
- 比较必须对齐OHLC、周期、完整起点、有效模式、参数和收盘标记。
  输入历史不同，结果可能不同。

## 15. 指标原生特征（可选）

`POST /api/indicators` 和 `POST /api/indicators/compute` 请求顶层可传
`"signals": true`，默认 false。59 个指标已逐项确认，35 个提供106个特征
（101个事件、5个状态），24个没有独立信号；具体以注册表和各指标文档为准。
特征不是买卖指令。

每个指标在原有结果旁新增 `signals: {columns, data}`，与该指标输出逐行对齐。
前两列为 `open_time`、`confirmed_at_ms`（毫秒），后者为该已收盘柱按周期计算的
收盘时刻，不表示特征一定触发。其余列使用完整特征名：

- MACD：`macd.cross_up/cross_down/zero_cross_up/zero_cross_down/above_signal/below_signal`。
- RSI：`rsi.enter_overbought/exit_overbought/enter_oversold/exit_oversold/overbought/oversold`；阈值固定70、30。
- CVD：`cvd.bullish_divergence_confirmed`、`cvd.bearish_divergence_confirmed`；区间及强度仍读 `objects.divergences`。

特征值 `true` 表示发生/成立，`false` 表示可判断但不成立，`null` 表示不可判断。
事件仅在发生柱为 true，状态可连续多根为 true。无独立特征的指标返回整个 `signals: null`；
未请求时没有 `signals` 字段，不向原数值 columns 中添加特征。

完整定义用 `GET /api/indicators/signals` 查询（不返回行情或计算结果）：

```http
GET /api/indicators/signals?indicator=macd
GET /api/indicators/signals?indicator=macd&kind=event
GET /api/indicators/signals?id=macd.cross_up
```

返回 `{count, signal_count, indicators}`，`indicators` 以指标名分组；组内包含
`has_signals`、`signal_status`、`signal_reason` 和 `signals` 定义数组。
每条定义含 `id/label/kind/description/meaning/required_history/version/details`。
`signal_status` 区分 `supported`、`none`、`unreviewed`，不代表当前数据可判断性。
不带 `kind/id` 时保留无特征指标及原因；带 `kind/id` 时只保留匹配的非空分组。
计数为筛选后的数量。合法筛选无匹配返回200和空分组；未知名称/ID或归属冲突返回400，
非法类型或空参数返回422。名称区分大小写，组合条件为AND。
特征ID与计算列名及图表 `signal_ids` 一致，不是自定义 `calc[].id`；不同参数实例仍按响应key区分。
完整用法：https://docs.mobiusquant.ai/zh/signals 。鉴权与限流沿用普通API。

`GET /api/indicators/registry?include_signals=true` 仍可按需追加 `has_signals` 与
定义列表 `signals`，含 `id/kind/description/required_history/version`。

实时特征目前仅支持 crypto；其他市场开启返回400。BYO必须明确 `interval` 且为固定时长
K线，遵守 `last_bar_closed`。未收盘行时间/特征为null。预热或数值无效时特征为null；
从首个周期缺口起，本次输入后续特征均为null，不跨缺口判断。MACD默认状态/事件分别
至少34/35根；RSI默认至少15/16根且对应RSI有效；CVD默认至少50根且已有正成交量。
RSI平均跌幅为零时当前数值可能为null，对应特征不会被解释为false。
信号沿用数值查询窗口，不保证监控补偿或去重；历史修订、输入起点变化可能改变结果。
`/api/chart` 也接受 `signals: true`，追加 `indicator_signals[请求key]`（相同特征表）
以及主图中的 `type: markers, source: signals` 确认事件标记。
标记 `time` 为确认柱开盘秒时间，`confirmed_at_ms` 为收盘毫秒时间；
`indicator_key` 区分实例，`signal_ids` 包含同柱事件。只绘制 event=true，不绘制持续状态。
CVD保留背离区间与端点，开启本选项时统一标记替代原确认箭头。
默认图表响应不变，不含新增特征字段。规则见 https://docs.mobiusquant.ai/zh/compute#feature-chart 。

## 16. 单次计算的可选信息

`/api/indicators`、`/api/indicators/compute`、`/api/chart` 可传顶层：

```json
{"signals":true,"include":["signal_reasons","signal_details","calculation","definition"]}
```

省略 `include` 或传 `[]` 保持原响应。每项可独立选择，前两项必须开启 `signals`；
未知项、重复项或未开启信号返回422。

- `signal_reasons`：在 signals 下增加 `invalid_reasons: {columns,data}`，按特征说明 null。
  仅保留有 null 特征的行，第一列 open_time 为毫秒。不可判断单元格为原因码数组，
  可判断单元格为 null（表示没有原因，不代表特征值）。原因可并存：
  `bar_not_closed/insufficient_history/input_gap/invalid_input/invalid_dependency/state_unavailable/not_applicable`。
- `signal_details`：在 signals 下增加 `details.bindings` 和 `details.context`。
  MACD/RSI/CVD/HMM 提供原生属性关联；无关联定义的其他指标返回空 bindings，特征能力不受影响。
  数值绑定 `field/offset` 中，0=当前柱、-1=前一输入柱。context 只补同一计算中必要但未返回的数值行。
  CVD 以同实例、确认柱时间和方向关联原对象，不复制强度。Chart 的实际对象 key 由绑定给出。
- `calculation`：每个结果增加同名对象，包含实际参数、算法和特征版本、自动解析选项、
  实际 input/output 历史范围、周期与收盘上下文。Chart 为 `indicator_calculations[key]`。
  范围包含 `start_open_time_ms/end_open_time_ms/bars`，不是连续性或可复现保证。
- `definition`：每个结果增加同名对象，包含参数约束、实际输出列类型/单位/枚举、市场/周期限制。
  Chart 为 `indicator_definitions[key]`。

结构化定义也可独立查询，不计算指标、不加载行情：

```http
GET /api/indicators/registry?include_schema=true
POST /api/indicators/registry/resolve
```

POST 请求：`{"calc":[{"name":"ema","params":{"period":50},"id":"e"}],"interval":"5m"}`。
返回 `{count,indicators}`，按实例 key 分组，动态列为 ema_50。interval 可选；calc 最多100项。
`parameter_constraints_complete=false` 表示参数约束尚未完整声明，不能把空约束当作无限制。
所有选项只描述单次计算；组合规则、轮询补偿、去重、通知和版本迁移由消费端处理。
完整说明：https://docs.mobiusquant.ai/zh/signals#single-calculation 。

## 附:语言

知识库(指标 `desc` / `summary_focus` / playbook `synthesis`)**只有中文**,
LLM 翻译给其他语言用户。这是设计决策(见 VISION §8 决策 7),不是 bug。
