> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oddspapi.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 预测市场与交易所 —— 订单簿深度、限额与订单路由

> 在 OddsPapi 上构建做市与套利系统：聚合的交易所价格、back/lay 订单簿深度、用于订单路由的场内原生 ID、投注限额，以及各场馆的实测延迟。

跨预测市场做市意味着同时维护多个场馆的一致视图 —— 价格、深度、将订单送回场馆所需的标识符，以及对「这条报价还作数吗」的诚实回答。OddsPapi 将这一切归一化为单一数据流，因此新增一个场馆只是改一个过滤条件，而不是一个集成项目。

本指南讲的是建立在该数据流之上的交易决策：如何导出可交易视图、如何按真实深度定量、如何判断某条价格已不可执行，以及连接中断时的正确行为。负载结构与线上示例请见 [`odds`](/zh/websocket/channels/odds) 频道页 —— 本页假定您已读过它。

***

## 每条价格包含什么

每个 outcome 都在同一个对象中携带做市所需的要素 —— 无需二次查询：

| 字段                        | 作用                                                |
| ------------------------- | ------------------------------------------------- |
| `price`                   | 最优可得价格（小数），另有 `priceAmerican` / `priceFractional` |
| `meta.back` / `meta.lay`  | 订单簿阶梯 —— `{ price, size }`，最优价在前                  |
| `limit`                   | 该价格上可接受的最大投注额                                     |
| `bookmakerMarketId`       | 场馆自身的盘口标识符                                        |
| `bookmakerOutcomeId`      | 场馆自身的选项标识符                                        |
| `bookmakerChangedAt`      | 场馆的变更时间戳（t0）                                      |
| `changedAt`               | OddsPapi 观察到该变更的时间（t1）                            |
| `active` / `marketActive` | 硬性停止条件 —— 为 false 时不要报价                           |

四段时间戳链（`bookmakerChangedAt` → `changedAt` → 信封 `ts` → 您的接收时间）让您可以自行测量各场馆的延迟，而不必只凭信任。各博彩商已公布的数值参见[延迟与新鲜度](/zh/api-reference/reliability#延迟与新鲜度)。

***

## 导出可交易视图

`meta.back` 和 `meta.lay` 在所有交易所与预测市场中形态一致：`{ price, size }` 数组，最优价在前，且各场馆语义相同。`back` 的首条即盘口顶部，与该 outcome 的 `price` 一致。确切结构参见[高级元数据](/zh/websocket/channels/odds#高级元数据meta)。

正因为形态不随场馆变化，跨场馆比较可以直接进行 —— 无需按场馆分支解析，也无需您自建一层归一化逻辑并随各场馆接口变动不断维护。

**跨场馆的最优买价与最优卖价。** 将所有报价按选项分组，然后在您有权限的场馆中取最高 `back` 价与最低 `lay` 价。即便您只会吃单边，两边也都重要：最窄的价差告诉您共识实际落在哪里，而报在其外的场馆要么携带了信息，要么携带了一条陈旧价格 —— 究竟是哪一种，由时间戳而非价格来判断。

**交叉与锁定盘口是信号，而不是机会。** 若某场馆的最优 `back` 高于另一场馆的最优 `lay`，在把它当作套利之前，先核对双方的 `changedAt`。真实的交叉会在毫秒级内消失；持续存在的交叉几乎总是意味着其中一方已停止更新。先通过下文的状态门控，再考虑定量。

**深度是按场馆计的，不能汇总。** 把各场馆的阶梯加总，得到的是一个您无法据以交易的数字 —— 每个场馆的队列各自独立，您无法用一个场馆的订单去吃另一个场馆的挂单。请按场馆建模可执行规模，再在**选项**层面汇总，而不是在盘口层面。

**成交量以场馆自身的币种计价。** `size` 以场馆的基础货币表示。加密货币场馆请先通过[`currencies`](/zh/websocket/channels/currencies) 频道换算，再去比较深度或设定仓位上限，否则一个场馆上的大数字与另一个场馆上的小数字可能代表同样的敞口。

***

## 定量：`limit` 与深度各司其职

同一条赔率行上传来两个规模信号，它们约束的是不同的东西 —— 混为一谈会让您过度定量：

* **`limit`** 约束单笔订单 —— 场馆在这个价格上最多接受多少。参见[投注限额](/zh/websocket/channels/odds#投注限额)。
* **阶梯**约束成交结果 —— 触及价之后还挂着多少量，以及清掉某个规模要付出多少成本。

请沿阶梯走到目标规模，并以成交量加权均价而非盘口顶部价来给这笔成交定价。限额下调以普通赔率更新的形式、在同一个 `oddsId` 上到达，且往往是更早出现的那个信号。完整论述（包括 `limit` 是唯一规模信号的传统博彩商）参见 [sharp 线路交易与 CLV](/zh/guides/sharp-line-trading#limit-与深度回答的是不同的问题)。

***

## 判断一条报价是否可交易

价格新鲜度与价格价值是两个独立的问题，而对自动化交易而言，判断错的时候真正付出代价的是前者。四道彼此独立的关卡，任何一道都可以否决：

| 关卡             | 来源                                                | 含义                         |
| -------------- | ------------------------------------------------- | -------------------------- |
| `active`       | `odds`                                            | 该选项当前不可投注                  |
| `marketActive` | `odds`                                            | 整个盘口已停止 —— 其下每个选项都不可交易     |
| `suspended`    | [`bookmakers`](/zh/websocket/channels/bookmakers) | 该场馆已暂停此赛事                  |
| `staleOdds`    | [`bookmakers`](/zh/websocket/channels/bookmakers) | **我们与该场馆的连接已丢失。** 新鲜度不再有保证 |

`staleOdds` 是最容易被漏掉的一个，因为您收到的最后一条价格看上去毫无异常。您手上的价格可能已在场馆侧变动，而我们无从告知。在它变回 `false` 之前，请将该场馆的所有数据视为未经验证，并撤回报价而不是重新定价。详见[自动化交易关键字段](/zh/websocket/channels/bookmakers#自动化交易关键字段)。

再加一道属于您自己的关卡：**对照公布上界做时效检查。** 每家博彩商都在 `GET /bookmakers` 上公布 `maxDelayLiveInSec` / `maxDelayPregameInSec`。若 `now − changedAt` 明显超出该博彩商的上界，即便没有任何标志位触发，这条报价也已超出约定 —— 在最初几秒里，一个安静的场馆和一个故障的场馆看起来是一样的。参见[状态信号](/zh/coverage/bookmakers#状态信号)。

把这五项接入同一个「此刻我能否据此报价」的判定函数，并在每次更新时求值，而不是靠定时器。一个判定，一处可改。

对于赛事盘口，`bookmakers` 频道上的 `participantsRotated` 是性质不同的第六道检查：为 true 时，该场馆的主客场分配与我们相反，这会颠倒基于方向的 outcome 含义，并翻转让分的符号。它影响的是正确性而非新鲜度；而如果您按位置而不是按 ID 映射 outcome，它的影响是无声的。

***

## 延迟预算

您的反应时间由四段构成，其中您能掌控的恰好只有一段：

1. 场馆 → OddsPapi 观测（`changedAt − bookmakerChangedAt`）—— 按博彩商公布，在场馆提供可用时间戳时为实测值。
2. 合并 —— 固定约 10 ms 的批处理窗口；最新值一定会送达，因此它约束的是陈旧度上界，而不是丢弃数据。
3. 网关 → 您（`您的接收时间 − ts`）—— 传输跳，也是您唯一能缩短的一段。参见[服务器位置](/zh/api-reference/concepts#服务器位置很重要)。
4. 您自身的决策，以及订单回到场馆的往返时间。

请用自己的时钟持续测量第 3 段；它是最先劣化的一段，也是您的基础设施决策真正能改变的一段。第 1 段与第 4 段则应对照场馆的更新节奏来做预算：若某场馆重新定价的速度快于您闭环的速度，您就不是在该场馆的盘口顶部上竞争，此时应当报在其后或减小规模，而不是去追。

按场馆计的延迟也正是「对所有场馆采用统一报价策略」为什么是错的原因。同一标的上，一个 0.05 秒的场馆与一个约 1 秒的场馆理应对应不同的价差，而用于参数化这一点的数值是公布出来的，不需要您去推测。同样的推理应用在吃线上，参见 [sharp 线路交易与 CLV](/zh/guides/sharp-line-trading)。

***

## 熬过数据空档

做市方最糟糕的状态，是以为某个盘口是最新的、并据此报价，而事实并非如此。因此断连时的正确行为是有顺序的：**先撤报价，再做对账。**

* **服务端发起的重连。** 在发布或重新均衡之前，我们会推送一个 `reconnect` 控制帧，并在一小段宽限期内继续推送数据。请对这个帧作出反应，而不是等待随后的套接字错误 —— 这样得到的是干净的交接，而不是空档。
* **在重放窗口内。** 携带每频道游标（`serverEpoch` + `entryId`）重连，遗漏的更新会被无缝重放。
* **超出窗口。** 网关发出 `snapshot_required`；用一次 REST 快照恢复。这是最坏情况有界的既定路径，而不是错误。
* **当 `serverEpoch` 发生变化时**，旧游标已失去意义 —— 请重新快照，而不是恢复。

完整机制见[恢复和重放](/zh/websocket/resume-replay)。真正重要的纪律是：您账本中按场馆记录的「最后已知良好」时间戳，应当独立于套接字是否连着来门控报价 —— 因为套接字活着、背后场馆却已冻结，才是真正会造成损失的那种故障。

***

## 将订单路由回场馆

`bookmakerMarketId` 与 `bookmakerOutcomeId` **随每条价格在实时数据流上传输**。当策略选定某个选项时，您手中已经握有场馆自身的标识符 —— 无需维护映射表，也无需在下单前再调用一次接口。这一点恰恰在最困难的时刻最有价值：您最想动手的那一刻，也是您最不希望链路中还夹着一次查询的时刻。

在赛事层面，`GET /fixtures/mapping` 支持双向解析 —— 传入 `fixtureIds` 获取场馆 ID，或传入 `bookmakerFixtureIds` 从场馆 ID 反查 OddsPapi 赛事。[`bookmakers`](/zh/websocket/channels/bookmakers) 频道同样按场馆、按赛事携带 `bookmakerFixtureId` 与 `fixturePath`，因此人工交易台也能直接打开场馆自己的页面查看同一盘口。

***

## 跨场馆定价

定价之前，先按 `marketId` 分组。同一 `marketId` 下的所有 outcome 构成完整的概率空间，计算水位、归一化与完整性检查都需要它 —— 而每个线值都是独立的 `marketId`，因此一个盘口绝不会悄悄混入不同线值。

在此之上是常规做法：先在盘口内对各场馆的报价做归一化，再跨场馆合并。有两种权重值得显式保留，因为数据本身支持它们：

* **按规模加权** —— 展示出真实深度、`limit` 也较高的场馆，比在同一价格上只挂象征性数量的场馆构成更强的观测。
* **按延迟加权** —— 实测上界在 100 毫秒以内的场馆与上界为数秒的场馆，其当前程度并不相等，而公布的逐家数值让您可以据此加权，而不是靠猜。

事后工作 —— 成交审计、回测、收盘线 —— 请使用 REST [历史赔率与 CLV](/zh/api-reference/concepts#历史赔率与-clv) 接口，它们与实时数据流共用同一 `oddsId` 键，因此实时决策与其审计轨迹无需任何转换即可关联。

***

## 非体育市场即期货

政治、选举、金融、加密货币与天气被建模为 `sportId` 69+ 的运动，且**仅以期货形式**存在 —— 每个冠军盘口对应一个期货：

```
{futureId}:{bookmaker}:{futureOutcomeId}:{participantId}
```

使用 [`futures`](/zh/websocket/channels/futures) 与 [`oddsFutures`](/zh/websocket/channels/oddsFutures) 频道，二者遵循相同的交付语义并携带相同的订单簿 `meta`。上文关于门控、定量与深度的内容全部原样适用。模型参见[期货如何运作](/zh/api-reference/concepts#期货如何运作预测市场)，sportId 区间参见[覆盖范围](/zh/coverage)。

**某个期货问的是哪个问题**由 `futures` 负载上的 `market.marketId` 给出 —— 即 `futureId` 末尾同样携带的 5 位 `futureMarketId`，按运动分配（`69002` = 政治 Prediction）。

**某条价格对应哪个选择项**由键的最后两段给出，而由哪一段承载则取决于盘口的形态。以参与者为键的盘口（`winner`、`topscorer`、`relegation`、`mvp`）不拥有 outcome 行：选择项就是您要投注的实体，`futureOutcomeId` 取哨兵值 `0`。可分解盘口（是 / 否、大 / 小）则按线值、按方向携带真实的 `futureOutcomeId` —— 单个期货盘口会跨越多条线值，且与赛事盘口不同，让分位于 outcome 上而非盘口上。请从赔率行的显式字段读取二者，而不要拆分 `oddsId`；参见[期货赔率ID](/zh/api-reference/concepts#期货赔率id)。

***

## 接入步骤

<Steps>
  <Step title="快照">
    通过 REST（`/fixtures/odds` 或 `/futures/odds`）拉取当前状态以初始化您的订单簿。
  </Step>

  <Step title="订阅数据流">
    连接 `wss://v5.oddspapi.io/ws`，并以您交易的频道、运动和场馆登录。请在订阅 `odds` 的同时订阅 `bookmakers` —— 没有它，您有价格但没有陈旧度信号。
  </Step>

  <Step title="应用更新">
    以 `oddsId` 为键就地覆盖。每条消息是状态，而非一次跳动。
  </Step>

  <Step title="门控">
    在每次更新时、按场馆求值报价判定函数，再让价格进入下游任何环节。
  </Step>

  <Step title="恢复">
    将 `reconnect` 与 `snapshot_required` 当作既定路径来处理。参见[恢复和重放](/zh/websocket/resume-replay)。
  </Step>
</Steps>

在登录时过滤 —— `sportIds`、`tournamentIds` 和 `bookmakers` 削减消息量的效果远好于客户端过滤 —— 并在规模化时于 `odds` 上优先使用 `receiveType: "zstd"`（[压缩](/zh/websocket/compression)）。登录报文结构与可运行的客户端示例见 [`odds`](/zh/websocket/channels/odds) 频道页与[快速开始](/zh/quickstart)。

***

## 您可以依赖的保证

* **只有一种订单簿结构，而不是每个场馆一种。** `meta.back` / `meta.lay` 在所有交易所与预测市场上都是语义一致的 `{ price, size }` 数组，因此您只需写一个解析器，而不是一整套按场馆定制的适配器。未知的 `meta` 键属于增量扩展；`back` / `lay` 核心不会被改作他用。
* **场馆原生 ID 就挂在价格本身上。** `bookmakerMarketId` 与 `bookmakerOutcomeId` 位于实时赔率行内，因此将订单回送至场馆无需二次查询，也不需要您自建反向映射表。
* **成交量是一等字段。** `limit` 在各家博彩商间已归一化，且几乎每家 sharp 博彩商都携带，您实际能成交的额度与价格在同一条载荷中。
* **失联会被上报，而不是被掩盖。** `staleOdds` 会告诉您我们何时无法再为某场馆的价格背书 —— 这正是「撤回报价」与「在不知情的情况下对着一个冻结的盘口交易」之间的差别。
