---
name: ninequant-backtest
description: |
  用九章量化 NineQuantAI 的公开 API 回测美股与美股 ETF 的技术策略（吊灯止损趋势跟踪、
  双均线交叉），并获取历史日线、收盘报价、标的静态信息与标的搜索结果。

  在以下情况使用：用户想知道某套交易规则在历史上的表现；提到「回测 / backtest」；
  想验证或对比 SMA 周期、ATR 止损倍数、ADX 过滤等参数；询问某只美股或 ETF 的
  历史 K 线、收盘价、最大回撤、胜率、盈亏比、夏普、超额收益；要求做参数扫描或
  参数优化；或点名 QQQ / SPY / AAPL / NVDA 等美股代码并想看某组规则的历史结果。

  在以下情况不要使用：用户要实时或盘中行情（本平台当前只分发 T+1 及更早的收盘数据）；
  要期货、外汇、加密货币（任何部署都不支持）；要港股或 A 股而 `GET /api/meta` 的
  `markets` 里没有 `HK` / `CN`（公开部署当前只有 `US`）；要基本面、财报、
  新闻或分析师评级（不提供）；要下单、交易、转账或连接券商（平台没有任何下单能力）；
  要投资建议或「该买什么」（不提供，也不应由本技能代答）。
---

# 九章量化 NineQuantAI 回测技能

让 AI 助手直接调用九章量化的公开 API，把用户口头描述的交易规则跑成真实的历史回测，
并如实解读结果。**所有数字由服务端引擎计算，你只负责发起请求与解读结果——绝不要
自己编造或估算任何指标。**

---

## 0. 先读这一节：数据边界（不可回避，必须如实转达）

这些是平台的真实能力边界。**用户问到相关问题时如实说明，不要含糊，更不要为了
显得能力更强而暗示我们有其实没有的数据。**

| 维度 | 实际情况 |
|---|---|
| 市场 | **以 `GET /api/meta` 的 `markets` 为准。** 公开部署当前是 `["US"]`——只有美股与美股 ETF。港股 / A 股在 `planned_markets` 里：代码与接口已就绪，但数据要另配 **Tushare Pro** 令牌（一笔要人去买的订阅），未配之前一根 K 线都没有。 |
| 历史深度 | **以 `GET /api/meta` 的 `data_range` 为准，别背日期。** 美股的合并口径日线自 **2024-07-01**；更早的区间（最早 2018-05-01）若该部署回补过，是**重建口径**（见下一行）。请求超出范围会被自动截断并在 `notes` 里说明。 |
| 数据口径 | 三种：`consolidated`（合并，含场外成交）、`primary_session`（主上市交易所 + 常规时段重建，**成交量只有合并量的 21%~34%**）、`single_venue`（**回测拒绝使用**）。响应的 `venue_mode` 会标明。 |
| 时效 | 当前分发姿态 `eod_only`：**只提供 T+1 及更早的收盘数据**，没有实时、没有延迟盘中。 |
| 分时数据 | `GET /v1/intraday` 在默认部署下返回 **403**。这是授权约束，不是故障。 |
| 品种 | 只有股票与 ETF 的日/周/月线。没有期权、期货、外汇、加密货币。 |
| 基本面 | 没有财报、估值、新闻、评级。只有价格与成交量。 |
| 交易 | 平台**没有任何下单能力**，不对接券商。 |

### 关于市场覆盖：只信 `/api/meta`，不要背文档里的日期

这份技能会被用在不同的部署上，而**市场覆盖与历史深度是配置决定的，不是写死的**：
港股 / A 股需要 Tushare Pro 的令牌（A 股与港股是两笔分开的权限），配了就有，没配就没有；
美股 2024-07 之前的历史也要该部署主动回补过才有。

所以会话开始时先调一次 `GET /api/meta`（免费、不计限流），然后：

- `markets` 里有的市场，才可以说「支持」；
- `planned_markets` 里的市场，说法是**「规划中，当前这个部署没有数据」**，
  不要说「马上就有」「即将上线」，你不知道那个令牌什么时候会被配上；
- 用户问「你们支持港股吗」，答案来自 `markets` 而不是来自你的记忆。

**绝不要因为这份文档提到了 `0700.HK` 的格式，就以为港股可用。** 格式就绪
与数据就绪是两回事，前者只是意味着令牌配上那天不用改代码。

### 关于「5 年回测」这类请求

用户说「回测 5 年」时，你**必须**这样处理：照常发请求（`time_range: "5Y"`），
然后读响应里的 `data_range`。它会告诉你实际用到的区间。如果 `truncated` 为 `true`
且 `reason_codes` 含 `start_before_data_start`，就明确告诉用户：

> 数据只能回溯到 YYYY-MM-DD（`data_range` 里写着），所以这次实际回测的是
> XXXX-XX-XX 至 XXXX-XX-XX，约 N 个月而不是 5 年。样本长度直接影响结论可信度。

日期从响应里读，**不要从这份文档里抄**——不同部署的起点不一样。

**绝不要把截断后的结果说成 5 年回测。** 这是这份技能最重要的一条纪律。

### 关于口径：三个取值，两条告警

日线有三种口径，响应里的 `venue_mode` 会标明本次用的是哪一种：

| `venue_mode` | 是什么 | 你该怎么说 |
|---|---|---|
| `consolidated` | 合并口径，汇总全部交易场所（含场外成交） | 正常，不用特别说明 |
| `primary_session` | 主上市交易所 + 常规时段（09:30~16:00 ET）重建。**收盘价与官方收盘一致**（实测 547/547 个交易日完全相等），开高低偏差均值 ≤0.03%，但**成交量只有合并量的 21%~34%** | 要说明这一段的**成交量不是全市场成交量** |
| `single_venue` | 单交易所全时段原始日线 | **回测会直接拒绝**。若出现 `non_consolidated_venue_mode` 告警，说明结果不可信，如实告知，不要照常汇报指标 |

回测区间横跨两种口径时，`notes` 里会出现结构化标记，**必须转达**：

- **`mixed_session_source`** —— 这次回测的 2024-07 之前那段是主场常规时段重建，
  成交量为单场口径。说法是「这段区间的价格可信，成交量只是主交易所的部分」。
- **`volume_filter_on_primary_session`** —— 上一条成立、**且用户开了成交量放大过滤**。
  这条更严重：实测单场占比在相邻两个交易日能从 26.77% 跳到 32.74%，
  **即使真实成交量持平也会看到 22% 的「放量」**。要明确告诉用户
  **这段区间的放量信号本身不可信**，不是"略有偏差"。这个噪声调参消不掉。

---

## 1. 何时使用 / 何时不使用

### 该用

- 「QQQ 用吊灯止损策略跑一下，SMA 100 天」→ 直接回测
- 「我的想法是价格站上 200 日均线才买，跌破 3 倍 ATR 就走，历史上行吗」→ 翻译成参数后回测
- 「SMA 用 50 和 100 差别大吗」→ 跑两次对比
- 「帮我找 exit_mult 的最优值」→ 参数扫描（见第 8 节）
- 「AAPL 最近一年最大回撤多少」→ 回测或取 K 线后由服务端指标回答
- 「NVDA 昨天收盘多少」→ `GET /v1/quote/NVDA.US`
- 「有哪些以 SEMI 开头的标的」→ `GET /v1/symbols?q=SEMI`

### 不该用（要么拒绝，要么明确说明做不到）

- 「现在 TSLA 多少钱」→ 说明只有 T+1 收盘价，给出最近一个可分发交易日的收盘价并标注日期
- 「腾讯 0700.HK 回测一下」→ 先看 `/api/meta` 的 `markets`。没有 `HK` 就说明
  「这个部署目前不覆盖港股」，**不要说「即将上线」**——那取决于一笔还没买的数据订阅
- 「帮我买 100 股 SPY」→ 说明平台没有下单能力
- 「我该买哪只股票」→ 拒绝给出投资建议；可以改为「我可以帮你回测你自己的规则」
- 「明天会涨吗」→ 拒绝预测；回测说明的是历史，不是未来
- 「帮我做一个月线级别的均线策略」→ 可以：`period` 支持 `week` / `month`，但注意
  月线的样本数取决于该部署实际有多少历史（读 `data_range`），只有两年数据时月线
  只有 20 余根，样本太少，要提前说明

---

## 2. 快速开始

无需 API Key 即可试用（按 IP 走游客额度）。第一次调用建议先探测部署状态：

```bash
BASE="https://us2.ninequantai.com"      # 本地开发用 http://127.0.0.1:8765

curl -s "$BASE/api/meta"
```

返回里最重要的三项：

- `data.distribution_mode` —— 当前分发姿态（默认 `eod_only`）
- `data.data_range.earliest_bar` / `latest_bar` —— 本部署真实可用的数据区间
- `data.markets` —— 当前支持的市场（当前只有 `["US"]`）

**用这个返回值来判断能做什么，不要照搬本文档里写死的日期。** 部署不同、数据同步
进度不同，实际区间会变。

然后跑第一次回测：

```bash
curl -s -X POST "$BASE/v1/backtest" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "QQQ.US",
    "strategy": "chandelier",
    "time_range": "1Y",
    "params": {"sma_period": 100, "entry_mult": 2.0, "exit_mult": 3.0},
    "include_klines": false,
    "include_equity": false
  }'
```

### 带 API Key（额度更高）

```bash
curl -s -X POST "$BASE/v1/backtest" \
  -H "Authorization: Bearer nq_xxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{...}'
```

Key 在 https://us2.ninequantai.com 注册后于「账户 → API Key」创建，以 `nq_` 开头。

> **Key 的处置纪律**：不要让用户把 Key 明文贴进聊天框。引导他们放进环境变量
> `NQ_API_KEY`、放进 `optimize.py` 同目录的 `api_key.txt`，或放进 MCP 配置文件。
> 如果用户已经贴出来了，提醒他去控制台重置。

---

## 3. 执行工作流

**每次做回测都走这五步，不要跳步。**

### 第 1 步：确认标的存在且有数据

```
GET /v1/static/{symbol}
```

看 `data.history_start` / `data.history_end`——这是该标的**本地实际入库的区间**，
比全局区间更准。同时看 `data.is_delisted`：已退市标的可以回测（我们刻意保留它们
以避免幸存者偏差），但要提醒用户这只票已经退市，退市日期在 `note` 里。

代码写错时这一步会返回 404，比在回测那一步失败更容易解释。

### 第 2 步：确认策略与参数（只在需要非默认参数时做）

```
GET /v1/strategies
```

返回每个策略的 `params_schema`（标准 JSON Schema，含 `minimum` / `maximum` / `default`
与三语标签）。**参数取值范围以这个返回为准，不要照抄本文档第 5 节的表格**——
表格是给你建立直觉的，接口返回的才是当前生效的约束。

### 第 3 步：跑回测

```
POST /v1/backtest
```

给 AI 使用时**务必**设 `"include_klines": false, "include_equity": false`。
一次 1 年回测的 `klines` 有 250 行、每行十几个字段，`equity_curve` 同样长，
默认全带上会白白吃掉几万 token，而你要的只是那十几个指标。

只有用户明确要求画图或要逐日数据时才打开。`include_trades` 可以保持 `true`——
逐笔交易通常只有几十行，且是解释结论时最有用的材料。

### 第 4 步：读 `notes` 与 `summary.warnings`（**不可跳过**）

这两个字段是平台刻意做出来的诚实机制，**在汇报任何指标之前先读它们**：

- `notes[]` —— 每条有 `code` / `level` / `message`。`level: "warning"` 的必须转达。
- `summary.warnings[]` —— 引擎级告警码，含义见第 6 节。

如果有告警而你只报了收益率，你就是在误导用户。

### 第 5 步：解读

汇报时至少覆盖这五项，缺一不可：

1. **实际区间与 K 线根数**（来自 `data_range`），以及是否被截断
2. **交易次数**——放在收益率之前说。3 笔交易的 80% 收益毫无统计意义
3. **策略收益 vs 基准收益**（`total_pnl_pct` vs `buy_hold_pnl_pct`）
4. **两者的最大回撤**（`max_drawdown_pct` vs `buy_hold_max_dd_pct`）。注意这两个字段返回的是**正数幅度**，9.8 的意思是「回撤 9.8%」，转述时不要写成 +9.8% —— 那会被读成上涨
5. **所有 warning 级的提示**

一句可用的模板：

> 区间 2024-07-01 至 2025-08-29，共 292 根日线。策略做了 14 笔交易，胜率 35.7%，
> 收益 +18.2%，最大回撤 9.8%；同期买入持有 +21.4%、最大回撤 16.3%。
> 也就是说这套参数少赚 3.2 个百分点，换来回撤减半。
> ⚠️ 交易次数只有 14 笔，样本偏小，这个结论还不稳定。

---

## 4. API 快速参考

**Base URL**：`https://us2.ninequantai.com`（本地开发 `http://127.0.0.1:8765`）
**认证**：`Authorization: Bearer nq_xxx`（可选，不带则走游客额度）
**统一响应外壳**：

```json
{"code": 0, "message": "ok", "data": { }}
```

失败时 `code` 为 HTTP 状态码、`data` 为 `null`。每个响应都带 `X-Request-Id`，
用户报障时让他附上这个编号。

### 回测

| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/v1/backtest` | 跑回测。游客可用 |
| GET | `/v1/strategies` | 策略列表与 `params_schema`。游客可用 |
| GET | `/v1/backtests` | 我保存的回测记录（需登录/Key） |
| GET | `/v1/backtests/{id}` | 单条回测详情 |

### 行情

| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/v1/quote/{symbol}` | 报价。`eod_only` 下是最近可分发交易日的收盘快照 |
| GET | `/v1/kline/{symbol}` | 历史 K 线。参数 `period` / `start` / `end` / `days` / `adjust` |
| GET | `/v1/static/{symbol}` | 静态信息 + 本地已入库区间 |
| GET | `/v1/symbols` | 标的搜索。参数 `q` / `market` / `limit` |
| POST | `/v1/batch` | 批量查询，单次最多 50 个标的 |
| GET | `/v1/intraday/{symbol}` | 分时。**`eod_only` 下返回 403，属预期行为** |

### 公共

| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/meta` | 分发姿态、可用区间、引擎版本。**每次会话开头调一次** |
| GET | `/api/plans` | 各套餐的限流额度与权益，无需认证 |
| GET | `/health` | 依赖连通性 |

### `POST /v1/backtest` 请求体

```jsonc
{
  "symbol": "QQQ.US",              // 必填
  "strategy": "chandelier",        // chandelier（默认）/ dual_ma
  "time_range": "1Y",              // YTD 1M 3M 6M 1Y 2Y 3Y 4Y 5Y 6Y 7Y 8Y 10Y MAX；给了 start/end 时被忽略
  "start": "2024-07-01",           // 可选，YYYY-MM-DD
  "end":   "2025-06-30",           // 可选
  "params": { "sma_period": 100, "entry_mult": 2.0, "exit_mult": 3.0 },
  "cost": {                        // 可选。**默认全零**，不给就是不计成本
    "commission_rate": 0.0003,     // 万三；不填则 0
    "min_commission": 0,
    "slippage_bps": 5              // 万五；不填则 0
  },
  "initial_capital": 100000,       // 可选
  "execute_on": "close",           // close（默认，信号日收盘成交）/ next_open（次日开盘成交）
  "locale": "zh-CN",               // zh-CN / zh-TW / en，影响 notes 文案语言
  "include_klines": false,         // ← 给 AI 用时务必置 false
  "include_trades": true,
  "include_equity": false          // ← 给 AI 用时务必置 false
}
```

顶层扁平写法同样被接受（`{"symbol":"QQQ.US","sma_period":100,"entry_mult":2.0}`），
未知的顶层键会自动并入 `params`。两种写法同时出现时以 `params` 为准。
**推荐用嵌套的 `params` 写法**，意图更明确，也不会因为拼错键名而被静默当成策略参数。

### `POST /v1/backtest` 响应关键字段

```jsonc
{
  "code": 0, "message": "ok",
  "data": {
    "symbol": "QQQ.US", "strategy": "chandelier",
    "params": { },                 // 实际生效的参数（含未显式给出的默认值）
    "engine_version": "1.1.0",     // 同参数同版本必然复现同结果
    "venue_mode": "consolidated",  // 数据口径
    "start_date": "2024-07-01", "end_date": "2025-08-29",

    // 指标同时以两种形态给出，取任一即可
    "num_trades": 14, "win_rate": 35.7,
    "total_pnl_pct": 18.2, "annualized_pct": 15.8,
    "max_drawdown_pct": 9.8, "sharpe": 0.92,   // 回撤是正数幅度，不是负数
    "profit_factor": 1.84, "avg_hold_bars": 12.4,
    "buy_hold_pnl_pct": 21.4, "buy_hold_max_dd_pct": 16.3,
    "excess_return_pct": -3.2,
    "summary": { /* 同上，外加 num_bars / venue_mode / warnings */ },

    "cost": { "commission_rate": 0.0, "slippage_bps": 0.0,
              "initial_capital": 100000, "execute_on": "close" },
    "data_range": {
      "requested_start": "2020-01-01", "actual_start": "2024-07-01",
      "available_start": "2024-07-01", "available_end": "2025-08-29",
      "bars": 292, "truncated": true,
      "reason_codes": ["start_before_data_start"]
    },
    "data_freshness": { "distribution_mode": "eod_only", "as_of": "2025-08-29",
                        "delayed": true, "venue_mode": "consolidated" },
    "notes": [ {"code": "...", "level": "warning", "message": "..."} ],
    "trades": [ ],                 // include_trades=true 时
    "klines": null, "equity_curve": null
  }
}
```

---

## 5. 策略与参数

**权威来源是 `GET /v1/strategies` 的 `params_schema`。** 下表用于建立直觉。

### `chandelier` —— 吊灯止损趋势跟踪（默认策略）

规则（默认档）：
- **入场**：收盘价 > SMA **且** 收盘价 > 入场线，入场线 = N 日最高价 − `entry_mult` × ATR。
  即「站上均线，并且已经从近期高点的 ATR 缓冲区里站回来」。
- **出场**：最低价击穿止损线，止损线 = **N 日滚动最高价** − `exit_mult` × ATR。
  锚点是滚动窗口内的最高价，不是持仓期内的最高价 —— 旧高点滚出窗口时这条线会
  **跟着下移**。这一点常被误解，但它正是主流工具的口径。

| 参数 | 类型 | 默认 | 范围 | 含义 |
|---|---|---|---|---|
| `sma_period` | int | 100 | 5–500 | SMA 周期，决定大方向过滤的松紧 |
| `breakout_period` | int | 22 | 2–250 | N 日窗口，入场线与出场线的锚点都用它 |
| `atr_period` | int | 14 | 2–100 | ATR 计算周期 |
| `entry_mult` | float | 2.0 | 0.5–10.0 | 入场线宽度（×ATR），真正参与入场判定 |
| `exit_mult` | float | 3.0 | 0.5–10.0 | 出场止损宽度（×ATR） |
| `entry_mode` | string | `chandelier` | `chandelier` / `breakout` | 入场口径。`breakout` 是旧口径：必须收盘创 N 日新高才买，苛刻得多 |
| `trail_mode` | string | `rolling` | `rolling` / `ratchet` | 止损锚点。`ratchet` 是经典吊灯：锚在持仓期内最高价、只上移不下移，出场更晚、笔数更少 |
| `adx_filter` | bool | false | — | 是否启用 ADX 趋势强度过滤 |
| `adx_threshold` | float | 25.0 | 0–100 | ADX 阈值，仅 `adx_filter=true` 时生效 |
| `vol_filter` | bool | false | — | 是否启用成交量放大过滤 |
| `vol_mult` | float | 1.0 | 0.1–5.0 | 放量倍数（相对 20 日均量），仅 `vol_filter=true` 时生效 |

直觉：`exit_mult` 越大，止损越宽、持仓越久、交易次数越少、单笔波动越大。
`sma_period` 越大越迟钝，趋势市里少挨打、震荡市里错过更多。
`entry_mode` 与 `trail_mode` 是「换一套规则」而不是「调一个数」，
**扫参数时不要把它们混进网格**——两档结果不可比，排名出来是假的。

### `dual_ma` —— 双均线交叉

快线上穿慢线买入，下穿卖出。作为对照基准存在——用来判断更复杂的策略是否真的
配得上它的复杂度。

| 参数 | 类型 | 默认 | 范围 | 约束 |
|---|---|---|---|---|
| `fast_period` | int | 50 | 2–250 | 必须小于 `slow_period` |
| `slow_period` | int | 200 | 3–500 | — |

`fast_period >= slow_period` 会返回 400。

---

## 6. 结果解读

### 指标含义（用中文向用户解释时用这些说法）

| 字段 | 中文 | 一句话解释 |
|---|---|---|
| `num_trades` | 交易次数 | 已平仓交易笔数。**少于 20 笔时任何结论都只是巧合的候选** |
| `win_rate` | 胜率 | 盈利交易占比（%）。趋势策略胜率 35% 很正常，不是缺陷 |
| `total_pnl_pct` | 策略收益 | 区间总收益率（%），已扣手续费与滑点 |
| `annualized_pct` | 年化收益 | 按实际区间长度折算。**区间不足一年时这个数会被放大，慎用** |
| `max_drawdown_pct` | 最大回撤 | 净值从峰值跌到谷底的最大幅度。**引擎已取绝对值，返回的是正数**（9.8 表示回撤 9.8%），别当成收益 |
| `sharpe` | 夏普比率 | 单位波动换来多少超额收益。>1 尚可，>2 在少样本下多半是噪声 |
| `profit_factor` | 盈亏比 | 总盈利 ÷ 总亏损。>1 才赚钱；>3 且交易少通常是过拟合 |
| `avg_hold_bars` | 平均持仓 | 平均持仓交易日数 |
| `buy_hold_pnl_pct` | 基准收益 | 同期买入持有的收益（%）。**跑不赢它的策略没有存在意义** |
| `buy_hold_max_dd_pct` | 基准最大回撤 | 同期买入持有的最大回撤，同样是正数幅度 |
| `excess_return_pct` | 超额收益 | 策略收益 − 基准收益。负值说明这套规则不如躺平 |

### 告警码（`summary.warnings[]`）

| 码 | 含义 | 你必须怎么说 |
|---|---|---|
| `closed_trades_lt_5` | 已平仓交易少于 5 笔 | 「样本太少，这个结果不能作为判断依据」 |
| `high_return_few_trades` | 收益 >200% 但交易 <8 笔 | 「高收益来自极少数几笔，大概率不可复制」 |
| `non_consolidated_venue_mode` | 用的不是合并口径 | 「本次数据口径非合并口径，指标不可信」 |

### 提示码（`notes[].code`，`level: "warning"` 的必须转达）

| 码 | 含义 |
|---|---|
| `start_before_data_start` | 请求起点早于数据起点，已从数据起点开始。**这条几乎必然出现在 5Y/10Y/MAX 请求里** |
| `end_after_data_end` | 请求终点晚于数据终点 |
| `end_capped_by_distribution_mode` | 终点被分发姿态收敛到 T+1 |
| `max_bars_capped` | K 线数超过单次上限 5000 根，只保留了最近 5000 根 |
| `insufficient_warmup` | 区间太短，指标预热不足，前段信号失真 |
| `delisted_symbol` | 该标的已退市 |
| `no_cost_model` | 本次未计交易成本，收益偏乐观 |
| `cost_applied` | 已计入的手续费率与滑点（信息级） |
| `not_saved_guest` | 游客模式不保存记录（信息级） |

---

## 7. 股票代码格式

统一格式是 `代码.后缀`。**能用哪些后缀取决于 `/api/meta` 的 `markets`**，
不是取决于这张表——表里列全了格式，是为了令牌配上那天你不用改写法。

| 后缀 | 市场 | 例子 | 币种 | 可用性 |
|---|---|---|---|---|
| `.US` | 美股（含 ETF） | `QQQ.US`、`AAPL.US`、`BRK.B.US` | USD | 恒可用 |
| `.HK` | 港股 | `0700.HK`、`9988.HK` | HKD | **看 `markets` 有没有 `HK`** |
| `.SH` | 沪市 | `510300.SH`、`601985.SH` | CNY | **看 `markets` 有没有 `CN`** |
| `.SZ` | 深市 | `002463.SZ`、`300913.SZ` | CNY | 同上 |

沪深在 `markets` 里合并为一个市场码 `CN`，但**代码后缀仍分 `.SH` 与 `.SZ`**，
不要写 `600519.CN`。港股对外**统一四位**（`0700.HK`），即使上游数据源用的是五位
（`00700.HK`）——转换在服务端完成，你按四位传就对了。

| 用户说 | 你传 | 说明 |
|---|---|---|
| `QQQ` | `QQQ.US` | 不带后缀会按规则推断，但**显式带上更稳妥** |
| `qqq` | `QQQ.US` | 大小写不敏感，服务端会转大写 |
| `苹果` / `Apple` | `AAPL.US` | 中文名或公司名先用 `GET /v1/symbols?q=Apple` 搜 |
| `BRK.B` | `BRK.B.US` | 代码本身带点是合法的，只有已知市场后缀才会被剥离 |
| `腾讯` / `700` / `00700` | `0700.HK` | 港股统一补到**四位**（万位以上的保持五位），前导零不能丢 |
| `600519` | `600519.SH` | 六位数字按首位分交易所：5/6/9 → `.SH`，0/2/3 → `.SZ` |
| `600519.SZ` | ✗ | **代码与后缀冲突会直接报错**，服务端不做静默纠正。先搞清楚是哪个交易所 |
| `BTC` / `BTCUSD` | ✗ | 不支持加密货币，任何市场都不支持 |

无后缀输入的推断规则：纯字母 → 美股；4~5 位数字 → 港股；6 位数字按上表分沪深。
代码只允许大写字母、数字、`.`、`-`、`+`，最长 24 字符；`.` 后面是已知市场后缀时
才当作后缀剥离，否则算代码的一部分（所以 `BRK.B` 不会被截成 `BRK`）。

**传了一个当前部署没开通的市场，会返回 400 并明确说明。** 遇到这种 400，
如实告诉用户「这个部署目前不覆盖该市场」，不要改成别的标的替用户做决定。

**代码不确定时先搜，不要猜。** `GET /v1/symbols?q=关键词&limit=10` 会返回
代码精确命中优先、其次代码前缀、最后名称命中的结果，并包含已退市标的
（`is_delisted: true`）。

---

## 8. 参数优化

用户说「帮我找最优参数」「哪个 SMA 周期最好」「扫一遍 exit_mult」时：

### 少量对比（≤ 6 组）：直接多次调用

顺序调 `POST /v1/backtest`，每次只改一个参数，然后做成表格对比。
**注意游客回测额度只有 5 次/分钟**，超过就要在调用之间加间隔。

### 系统性搜索（> 6 组）：用 `optimize.py`

同目录下的 `optimize.py` 是一个零依赖的 Python 脚本，做两阶段搜索：
先粗搜核心参数（SMA 周期 × 入场/出场 ATR 倍数），再对 TOP 结果微调
ADX 与成交量过滤。它自己处理限流节流、429 指数退避、结果落盘与过拟合预警。

```bash
python3 optimize.py QQQ.US --time-range 1Y
python3 optimize.py SPY.US --strategy chandelier --quick      # 缩小网格，约 12 次调用
python3 optimize.py AAPL.US --base-url http://127.0.0.1:8765  # 指向本地部署
```

API Key 放在脚本同目录的 `api_key.txt`（单行，`nq_` 开头），或设环境变量
`NQ_API_KEY`。没有 Key 也能跑，只是按游客额度节流，耗时更长。

结果写入 `optimize_<标的>_<时间戳>.json`，同时在终端打印排行榜与预警。

### 解读优化结果的纪律（**这一节比脚本本身重要**）

1. **绝不要只报最高分那一组。** 把 TOP 5 一起给用户看。
2. **看邻域，不看单点。** 如果 `exit_mult=3.0` 得分 8.5，而 2.5 和 3.5 都只有 2.1，
   这是个尖峰，几乎肯定是噪声。要找的是一片平台——相邻取值表现接近的那个区域。
   脚本会在 `neighborhood` 字段里给出这个诊断，直接引用它。
3. **搜索次数越多，最高分越可能是运气。** 扫了 40 组参数，最高的那组「看起来很好」
   本来就是统计上的必然。这一点要主动告诉用户，不要等他问。
4. **一定要转达脚本的 `warnings`。** 交易数过少、胜率 100%、收益异常高，
   这些全是过拟合的典型信号。
5. **建议做跨标的验证。** 在 QQQ 上最优的参数，去 SPY、IWM 上跑一遍看方向是否一致。
   不一致就说明它只是拟合了 QQQ 这一段特定的历史。

---

## 9. 错误处理

| 状态码 | 含义 | 你该怎么做 |
|---|---|---|
| **400** | 参数不合法 | `message` 里已经写明是哪个参数、错在哪、该填什么。照着改，别重试原样请求 |
| **401** | API Key 无效或已停用 | 告诉用户去控制台检查/重置 Key。**不要降级成游客偷偷重试**——用户以为在用自己的额度 |
| **403** | 该姿态或该套餐不开放 | 调 `/v1/intraday` 时最常见，说明当前是 `eod_only`，属预期。不要重试 |
| **404** | 标的不存在 | 用 `GET /v1/symbols?q=...` 搜正确代码，再重试一次 |
| **422** | 请求体结构有误 | `message` 里带了出错字段路径。修正 JSON 结构 |
| **429** | 超过限流 | 读响应体的 `retry_after`（秒）或 `Retry-After` 头，**等满再重试**，指数退避 |
| **502** | 上游数据源故障 | 不是你的问题。等几秒重试一次；连续失败就如实告诉用户上游有故障 |
| **503** | 数据源未配置/不可用 | 该部署没配好数据源。告诉用户联系管理员，不要反复重试 |

### 429 的正确处理

响应带这些头：`Retry-After`、`X-RateLimit-Limit`、`X-RateLimit-Remaining`、
`X-RateLimit-Reset`。**成功的响应也带 `X-RateLimit-*`**——用它主动控制节奏，
比撞上 429 再退避高明得多。

限流额度（写进 `plans` 表，以 `GET /api/plans` 返回的为准）：

| 层级 | 行情单端点 | 批量 | 回测 |
|---|---|---|---|
| guest（按 IP） | 30/分 | 10/分 | 5/分 |
| free（按 Key） | 60/分 | 20/分 | 20/分 |
| pro | 300/分 | 100/分 | 60/分 |

行情类每个端点一个独立的桶：quote 和 kline 各自 30/分，不共享。

### 不要做的重试

- 400/403/404 原样重试——错误不会自己好，只是浪费额度
- 429 立刻重试——会被继续拒绝，且计入用量
- 用户 Key 401 后改用游客身份——他会以为自己的 Key 在工作

---

## 10. Agent 决策树

```
用户提到股票 / 策略 / 回测
│
├─ 这个市场在 /api/meta 的 markets 里吗？
│  ├─ 期货/外汇/加密 → 任何部署都不支持，到此为止
│  ├─ 港股/A股且 markets 里没有 → 说明这个部署不覆盖该市场，到此为止
│  └─ 是 ↓
│
├─ 他想要什么？
│  │
│  ├─ 实时价格 / 盘中数据
│  │   → 说明只有 T+1 及更早的收盘数据
│  │   → GET /v1/quote/{symbol}，汇报时明确标注 as_of 日期
│  │
│  ├─ 历史 K 线 / 某段时间的价格
│  │   → GET /v1/kline/{symbol}?days=N（或 start/end）
│  │   → 检查 data 里的区间是否覆盖用户要求，被截断就说明
│  │
│  ├─ 找代码 / 不确定代码
│  │   → GET /v1/symbols?q=关键词
│  │
│  ├─ 单次回测（说了标的与规则）
│  │   → 1. GET /v1/static/{symbol} 确认存在与区间
│  │     2. POST /v1/backtest（include_klines=false, include_equity=false）
│  │     3. 读 notes 与 summary.warnings
│  │     4. 按第 3 节第 5 步的模板解读
│  │
│  ├─ 对比几组参数（2~6 组）
│  │   → 顺序调 POST /v1/backtest，每次只改一个参数
│  │   → 做成表格：参数 / 交易数 / 收益 / 回撤 / 盈亏比
│  │   → 重点说「相邻取值的差异」，不是「哪个最高」
│  │
│  ├─ 系统性找最优参数（> 6 组）
│  │   → 用 optimize.py（第 8 节）
│  │   → 报 TOP 5 + 邻域稳健性 + 全部 warnings
│  │
│  ├─ 「我这个想法行不行」（自然语言描述的规则）
│  │   → 先把它翻译成 chandelier 或 dual_ma 的参数
│  │   → 翻译不了（比如涉及基本面、涉及做空、涉及多标的轮动）就直说做不到
│  │   → 能翻译就先复述一遍翻译结果让用户确认，再跑
│  │
│  └─ 「该买什么」/「明天会涨吗」/「帮我下单」
│      → 拒绝。可以改为「我可以帮你回测你自己的规则」
│
└─ 每次汇报结果时必须包含：
   实际区间与 K 线根数 · 交易次数 · 策略 vs 基准（收益与回撤）· 全部 warning
```

---

## 11. 绝不要做的事

1. **绝不要自己算指标。** 收益率、回撤、夏普全部来自服务端。你算的数和平台页面
   对不上，用户会同时失去对你和对平台的信任。
2. **绝不要把截断后的结果说成用户请求的区间长度。** 见第 0 节。
3. **绝不要跳过 `notes` 和 `warnings` 直接报收益率。**
4. **绝不要说「这个策略很好」「建议使用」。** 回测说明的是历史，不是未来。
   可以说「在这段历史上它的回撤比买入持有浅」，不能说「它更好」。
5. **绝不要给投资建议、选股建议、买卖时点建议。**
6. **绝不要宣称 `/api/meta` 的 `markets` 里没有的市场。** 港股、A 股在多数部署里
   属于 `planned_markets`——代码就绪不等于有数据。实时数据、期权、基本面则是
   任何部署都没有的，直接说没有。
7. **绝不要把用户的 API Key 写进回复、日志或提交到版本库。**
8. **绝不要在扫描了几十组参数后，只报最高分那一组。**

---

## 12. 更多资料

- 完整 API 文档、术语解释、各 AI 工具安装指南：同目录的 `reference.md`
- 参数优化脚本：同目录的 `optimize.py`
- 在线接口文档：`https://us2.ninequantai.com/docs`（Swagger）
- MCP Server（把行情与回测注册成 AI 工具）：仓库 `mcp/README.md`

---

**免责声明**：本平台提供历史数据与回测计算，不构成任何投资建议。回测结果基于历史
数据，不代表未来表现。平台不提供任何下单能力，也不对接券商实盘交易。
