导航
English
HTTP Python

概念介绍

本节介绍接入 Outcomes API 前需要理解的核心业务概念,包括 xp(基础资产)、事件、市场、结果、价格、Split / Merge、镜像订单簿、订单簿、持仓、结算以及关键 ID。

理解这些概念后,开发者可以更清楚地判断:

1. 预测市场

预测市场是一种围绕未来事件结果进行交易的市场。

每个市场通常对应一个明确的问题,例如:

Germany 在某场比赛会赢吗?

用户可以根据自己的判断交易该问题下的不同结果。最常见的结果是 YESNO

示例:

对象 示例
Market Germany 会赢吗?
YES Germany 会赢
NO Germany 不会赢

可以简单理解为:

交易方向 含义
买入 YES 用户认为该事件结果会发生
买入 NO 用户认为该事件结果不会发生

2. 核心对象模型

Outcomes API 中的核心对象关系如下:

可以简单理解为:

对象 含义
Event 真实世界事件
Market Event 下的具体交易问题
Outcome Market 下可以交易的结果,例如 YES / NO
assetId 某个 outcome 的可交易资产 ID

开发者接入时,通常遵循下面的链路:

  1. 查询 Event
  2. 获取该 Event 下的 Market
  3. 选择某个 Market 下的 YESNO outcome
  4. 使用该 outcome 对应的 assetId 进行交易

3. Event 与 Market

事件(Event)是预测市场的上层组织单位,表示一个真实世界事件。

示例:

对象 示例
Event Germany vs. Curacao

一个 Event 下可以包含多个 Market:

Market 问题
Market 1 Germany 会赢吗?
Market 2 两队会平局吗?
Market 3 Curacao 会赢吗?

用户实际交易的不是 Event 本身,而是某个 Market 下的 YES 或 NO outcome。

4. 结果 Outcome

结果(Outcome)是 Market 下的可交易对象。

在二元市场中,每个 Market 通常有两个 outcome:

Outcome 含义
YES 该 Market 的结果会发生
NO 该 Market 的结果不会发生

每个 outcome 都会有自己的 assetId。开发者下单时,最终使用的是目标 outcome 对应的 assetId

示例:

如果用户想买入 YES,则使用 yesOutcome.assetId。 如果用户想买入 NO,则使用 noOutcome.assetId

5. 价格与概率

预测市场中的价格是 01 之间的小数。价格可以理解为市场对某个结果发生概率的实时估计。

例如:

Outcome 当前价格 可以理解为
YES 0.65 市场当前认为 YES 发生的概率约为 65%
NO 0.35 市场当前认为 NO 发生的概率约为 35%

为什么价格≈概率:胜出的 outcome 结算为 1 xp、未胜出结算为 0 xp,因此持有一个 outcome 的期望价值 = 1 xp × 该结果发生的概率。在有效市场中,价格会趋近这一期望值,所以价格可以近似看作市场对该结果发生概率的估计。

价格由市场交易形成,不代表最终结果。最终结果以市场结算为准。

6. YES / NO 的互补关系

YES 和 NO 是同一个 Binary Market 的两面。一个结果发生时,另一个结果就不会发生。

在理想情况下,二者价格之和接近 1

Outcome 价格
YES 0.65
NO 0.35
YES + NO 1.00

这种关系可以概括为:

YES + NO ≈ 1

开发者不需要手动处理 YES / NO 的价格换算。下单时只需要选择目标 outcome,并使用该 outcome 对应的 assetId

7. Split / Merge 机制

Split / Merge 是 YES / NO 互补关系背后的基础机制。

对于一个 Binary Market,YES 和 NO 可以看作一组成对的条件结果。系统可以通过 Split 和 Merge 在 xp 与 YES / NO outcome 之间进行转换。

7.1 Split

Split 是将一份 xp 拆分为等量的 YES 和 NO outcome。

1 xp → 1 YES + 1 NO

可以理解为:用户把一份完整的市场权益拆成了该市场下的两个互补结果。

操作前 操作后
1 xp 1 YES + 1 NO

7.2 Merge

Merge 是 Split 的逆操作。用户可以将等量的 YES 和 NO outcome 合并回 xp。

1 YES + 1 NO → 1 xp

只有当 YES 和 NO 数量相等时,才可以进行 Merge。

操作前 操作后
1 YES + 1 NO 1 xp

这个机制保证了 YES 和 NO 的互补关系:

YES + NO ≈ 1

也就是说,在同一个 Binary Market 中,YES 和 NO 不是完全独立的资产,而是同一组条件结果的两面。

8. 镜像订单簿

基于 YES / NO 的互补关系,系统可以使用镜像订单簿统一处理 YES 和 NO 的交易。

对于同一个 Binary Market:

用户操作 经济效果等价于
买入 YES @ 0.60 卖出 NO @ 0.40
买入 NO @ 0.30 卖出 YES @ 0.70

原因是:

YES 价格 + NO 价格 ≈ 1

因此,系统可以在底层用一套统一订单簿同时支持 YES 和 NO 的交易。

开发者不需要手动拆分或换算镜像订单。下单时只需要选择用户希望交易的 outcome,并使用该 outcome 对应的 assetId。系统会在订单簿层处理 YES / NO 的等价关系。

9. 订单簿

Outcomes 使用中央限价订单簿,即 CLOB。

CLOB 是 Central Limit Order Book 的缩写,表示中央限价订单簿。市场价格不是平台直接设定的,而是由用户之间的挂单和成交形成。

订单簿包含买卖两侧:

方向 含义
Bid 买方愿意买入的价格
Ask 卖方愿意卖出的价格

示例:

Side Price Size
Ask 0.67 100
Ask 0.66 80
Bid 0.65 120
Bid 0.64 200

当买卖双方价格匹配时,订单会发生撮合成交。

10. Maker 与 Taker

订单成交时,用户可能是 Maker,也可能是 Taker。

角色 含义
Maker 挂单进入订单簿,为市场提供流动性
Taker 主动吃掉订单簿中已有挂单,消耗流动性

示例:

当前最优卖价 Ask = 0.66

用户操作 结果 角色
0.66 买入 立即成交 Taker
0.60 挂买单 当前无法立即成交,进入订单簿等待成交 Maker

订单撮合通常遵循:

优先级 说明
价格优先 更好的价格优先成交
时间优先 相同价格下,更早进入订单簿的订单优先成交

11. 持仓

未成交的挂单会占用可用余额。订单成交后,用户会获得对应 outcome 的持仓。

示例:

操作 结果
用户买入 10 个 YES 成交后,用户持有 10 个 YES outcome

持仓会随着以下操作发生变化:

操作 对持仓的影响
成交 增加或减少对应 outcome 持仓
平仓 减少已有 outcome 持仓
Split 生成 YES / NO outcome
Merge 合并 YES / NO outcome
结算 根据最终结果更新持仓和余额

12. 平仓

用户不一定要等到市场结算后才退出持仓。如果市场仍可交易,用户可以通过卖出已有 outcome 来平仓。

示例:

步骤 操作
第 1 步 用户以 0.40 买入 YES
第 2 步 之后 YES 价格上涨到 0.70
第 3 步 用户卖出 YES 平仓
第 4 步 用户持仓和余额根据成交结果更新

平仓本质上是通过反向交易退出已有持仓。

13. 结算

当事件结果确定后,Market 会进入结算流程。

结算后:

Outcome 结算结果
胜出的 outcome 结算为 1 xp
未胜出的 outcome 结算为 0 xp

示例:

Market:Germany 会赢吗?

如果 Germany 最终赢了:

Outcome 结果
YES 胜出
NO 未胜出

如果 Germany 没有赢:

Outcome 结果
YES 未胜出
NO 胜出

市场结算后,未成交挂单会结束,用户持仓和余额会根据最终结果更新。

14. 二元市场(Binary Market)

二元市场(Binary Market)是最常见的市场结构,表示一个是 / 否问题。

示例:

Market Germany 会赢吗?
YES Germany 会赢
NO Germany 不会赢

Binary Market 中通常只有两个 outcome:

开发者只需要选择 YES 或 NO 对应的 assetId 进行交易。

15. 多元互斥市场(NegRisk Market)

多元互斥市场(NegRisk Market)用于多个互斥结果的场景。一个 Event 下可以包含多个相关 Market,但最终通常只有一个结果胜出。

示例:

Event:谁会赢得冠军?

Market 问题
Market 1 Germany 会夺冠吗?
Market 2 France 会夺冠吗?
Market 3 Brazil 会夺冠吗?
Market 4 其他球队会夺冠吗?

如果 Germany 最终夺冠:

Market YES 结果
Germany 会夺冠吗? 胜出
France 会夺冠吗? 未胜出
Brazil 会夺冠吗? 未胜出
其他球队会夺冠吗? 未胜出

对开发者来说,NegRisk Market 不改变基础交易方式。开发者仍然选择具体 Market 下的 YES 或 NO outcome,并使用对应的 assetId 交易。

市场类型 特点 开发者交易方式
Binary Market 单个是 / 否问题 选择 YES 或 NO 的 assetId
NegRisk Market 多个互斥结果 仍然选择具体 Market 下的 YES 或 NO 的 assetId

16. 市场状态

Market 在生命周期中会经历不同状态。

常见状态如下:

状态 含义 是否通常可交易
active 市场正常交易中
paused 市场暂停交易
settling 市场结算中
resolved 市场已结算

开发者下单前应检查 Market 状态。如果 Market 不是 active,订单可能会被拒绝。

17. 关键 ID

接入 API 时,最容易混淆的是各类 ID。

可以先记住:

ID 含义 常见用途
eventId 事件 ID 查询 Event 详情及其下属 Market
marketId 市场 ID 标识具体交易问题
assetId outcome 的资产 ID 下单、撤单、行情、订单簿、持仓等交易相关操作
orderId 订单 ID 查询订单、撤单、追踪订单状态

最重要的规则是:

下单、撤单、行情、订单簿相关操作通常使用 assetId

如果接口返回的 assetIdnull,说明该 outcome 暂时不可交易。开发者需要等待 assetId 返回有效值后,再进行交易相关操作。

18. 小结

开发者可以用下面这条链路理解 Outcomes 的核心模型:

Event → Market → Outcome → assetId → Order / Position

对应关系如下:

阶段 说明
Event 找到真实世界事件
Market 找到该事件下的具体问题
Outcome 选择 YES 或 NO
assetId 获取可交易资产 ID
Order 使用 assetId 下单
Position 成交后形成对应持仓

对于 API 接入方来说,最关键的是:

关键点 说明
不交易 Event Event 只是组织单位
不直接交易 Market Market 是具体问题
实际交易 Outcome Outcome 通过 assetId 表示
YES / NO 有互补关系 YES + NO ≈ 1
镜像订单簿由系统处理 开发者不需要手动换算
下单前检查状态 Market 需要处于可交易状态
assetId 不能为空 assetId = null 表示暂不可交易

快速开始

本指南将帮助你完成 Outcomes API 的最小接入流程,并提交第一笔订单。

前置条件

开始前,请确保你已经具备以下条件:

条件 说明
OKX 账户 已开通预测市场相关能力
可用余额 用于提交订单
OKX App 用于创建 API Key 和授权 Agent
开发环境 本文示例使用 Rust SDK
Agent 私钥 用于对下单、撤单等写操作进行签名

第 1 步:设置 API Key

在对任何请求进行签名之前,您需先通过 OKX API 管理页面 或 APP 创建 API Key。创建成功后,您将获得以下 3 项必须妥善保管的凭证:

凭证 说明
APIKey 公开标识
SecretKey 用于请求签名
Passphrase 您自行设置的额外安全凭证

其中,APIKey 与 SecretKey 由平台随机生成并提供;Passphrase 由您自行设置,用于增强 API 访问的安全性。

APIKey 权限

对于预测市场,需要选中以下所有权限:

权限 说明
读取(Read) 查询账单、历史记录等只读操作
交易(Trade) 下单、撤单、转账、调整配置等写入操作

创建步骤

步骤 1:APP 预测市场页面点选 "View" 步骤 2:点击右上角菜单
APP 预测市场页面点选 View 点击右上角菜单
步骤 3:进入生成 API Key 页面 步骤 4:生成 API Key
进入生成 API Key 页面 生成 API Key
步骤 5:生成后可查看详情 步骤 6:详情页面
生成后可查看详情 详情页面

所有 REST 接口都需要 OKX API 凭证。使用 with_credentials 构造客户端(详见第 5 步)。

第 2 步:设置 Agent

您的主密钥保持安全——创建一个代表您签署交易的 Agent 密钥。

  1. 生成一个新的以太坊密钥对(任何密钥生成工具)。保存 Agent 私钥Agent 地址
  2. OKX App 中授权该 Agent——Agent 授权必须通过 App 完成,不能通过 API。

APP 授权 Agent 步骤

步骤 1:输入待授权的 Agent 地址 步骤 2:授权成功
输入待授权的 Agent 地址 授权成功

授权完成后,后续所有写 API 调用使用 Agent 私钥,读 API 调用不需要私钥。

第 3 步:添加 SDK

安装

Cargo.toml 中加入本 SDK 与 Tokio 运行时:

Cargo.toml 依赖配置

[dependencies]
okx-outcomes-sdk = { git = "https://github.com/okx/outcomes-sdk.git", features = ["signing", "websocket"] }
tokio = { version = "1", features = ["full"] }

这就是完整的依赖列表。tokio-tungstenitek256sha3futures-util 等都会通过 SDK 的 feature 间接引入。不要 自行添加这些依赖,否则可能与 SDK 构建时使用的版本不一致。

Feature 开关

Feature 启用内容 何时启用
default 仅 REST 客户端(事件、市场、订单、持仓、余额、成交、价格、体育数据)。 只读集成。
signing EIP-712 + ECDSA 动作签名辅助函数。 任何写操作:place_ordercancel_ordercancel_allsplitmergeredeemheartbeat
websocket 基于 tokio-tungstenite 的 PredictionsWsClient,以及类型化的 WsMessage 解析器。 实时价格、成交、订单簿,以及用户订单 / 持仓 / 余额流。

第 4 步:配置环境变量

建议使用环境变量保存凭证:

环境变量配置

export PREDICTIONS_API_KEY="your-api-key"
export PREDICTIONS_API_SECRET="your-secret-key"
export PREDICTIONS_API_PASSPHRASE="your-passphrase"
export PREDICTIONS_AGENT_PRIVATE_KEY="0x..."

第 5 步:初始化 Client

使用 API Key、Secret Key 和 Passphrase 初始化 SDK Client:

初始化 Client 示例

use okx_outcomes_sdk::{ApiCredentials, OutcomesSdkClient};

let creds = ApiCredentials {
    api_key:    std::env::var("PREDICTIONS_API_KEY")?,
    secret_key: std::env::var("PREDICTIONS_API_SECRET")?,
    passphrase: std::env::var("PREDICTIONS_API_PASSPHRASE")?,
};

let client = OutcomesSdkClient::with_credentials(creds);

SDK 会在本地完成 REST 请求签名,并自动写入所需的鉴权请求头。

第 6 步:查询可交易市场

get_events 返回分页的事件列表。所有筛选参数都是可选的:

查询事件列表示例

let page = client
    .get_events(
        Some("active"),      // status: "active" | "resolved"
        None,                // tag
        None,                // league_id
        Some("volume_24h"),  // sort: "volume" | "volume_24h" | "ending_soon" | "newest"
        None,                // cursor(来自上一页)
        Some(20),            // page_size(最大 50)
    )
    .await?;

for event in &page.events {
    println!("{} - {} markets ({} points volume)",
        event.event_title, event.total_markets_count, event.volume);
}

// 分页:把 `page.pagination.next_cursor` 作为下一次调用的 `cursor` 传回去。

其他读取方法

方法 用途
client.search(keyword, cursor, page_size) 按关键字搜索事件与市场。
client.get_event(event_id) 单个事件及其完整市场列表。
client.get_event_markets(event_id) 单个事件下的全部市场(不分页)。
client.get_market(market_id) 通过市场 ID 获取单个市场。
client.get_ticker(inst_id) 单个市场标的的最新行情。
client.get_candles(...) K 线历史。
client.get_trades(...) 最近的公开成交历史。

找到 status: "active" 的市场,记录:

第 7 步:下限价单

以 0.55 xp 的价格在市场 100049000 上买入 10 个 YES 代币。需要 两层认证:API Key 请求头 + SDK 签名请求体。

下限价单完整示例

use okx_outcomes_sdk::{ApiCredentials, OutcomesSdkClient};
use okx_outcomes_sdk::models::order::{
    LimitOrderType, OrderItem, PlaceOrderAction, PlaceOrderRequest, SigningOrderSide, SizeType,
};
use okx_outcomes_sdk::signing::{
    action_place_order, generate_client_order_id_default, now_millis, parse_private_key,
    sign_to_wrapper, LimitTif, OrderRequest, OrderType,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let creds = ApiCredentials {
        api_key:    std::env::var("PREDICTIONS_API_KEY")?,
        secret_key: std::env::var("PREDICTIONS_API_SECRET")?,
        passphrase: std::env::var("PREDICTIONS_API_PASSPHRASE")?,
    };
    let client = OutcomesSdkClient::with_credentials(creds);

    // 1. Load the on-chain signing key (hex, with or without 0x prefix).
    let key = parse_private_key(&std::env::var("PREDICTIONS_AGENT_PRIVATE_KEY")?)?;

    // 2. Build the typed order. `asset_id` is the outcome asset ID from Step 6.
    let order_request = OrderRequest {
        asset_id:         "100049000".into(),
        side:             SigningOrderSide::Buy,
        market_type:      "prediction".into(),
        client_order_id:  Some(generate_client_order_id_default()?),
        price:            "0.55".into(),
        reduce_only:      false,
        size:             "10".into(),
        size_type:        SizeType::Base, // omitted on the wire (default)
        order_type:       OrderType::Limit(LimitOrderType { tif: LimitTif::Gtc }),
    };

    // 3. Derive the wire-format OrderItem from the same OrderRequest so the
    //    JSON body cannot drift from the signed msgpack bytes.
    let order_item = OrderItem::try_from(&order_request)?;
    let action = action_place_order(vec![order_request]);

    // 4. Sign and assemble the SignatureWrapper expected by the wire format.
    let nonce = now_millis();
    let signature = sign_to_wrapper(&action, nonce, None, &key)?;

    // 5. Submit.
    let req = PlaceOrderRequest {
        action: PlaceOrderAction {
            action_type: "placeOrder".into(),
            grouping:    "na".into(),
            orders:      vec![order_item],
        },
        nonce: nonce as i64,
        signature,
    };
    let resp = client.place_order(&req).await?;
    println!("tx_hash: {}", resp.tx_hash);
    Ok(())
}

下单响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b"
  }
}

第 8 步:查看订单

list_ordersstatus = Some("open") 时按页返回账户上所有非终态订单。把上一次响应中的 next_cursor 通过 cursor 参数传回,直到 has_nextfalse

查询订单列表示例

use okx_outcomes_sdk::models::order::OrderRecord;

let mut cursor: Option<String> = None;
let mut all_open: Vec<OrderRecord> = Vec::new();

loop {
    let page = client
        .list_orders(
            None,             // market_id: None = 所有市场,Some(123) = 限定某个市场
            Some("open"),     // "open" = PENDING_PLACE / ACTIVE / PENDING_CANCEL;"closed" = FILLED / PARTIALLY_FILLED / CANCELLED / EXPIRED / FAILED
            cursor.as_deref(),
            Some(50),         // 每页最大条数
        )
        .await?;
    all_open.extend(page.list);
    if !page.has_next {
        break;
    }
    cursor = page.next_cursor;
}

println!("{} open orders", all_open.len());
for o in &all_open {
    println!(
        "#{} [{}] {} {}/{} @ {}  market={} asset={} cloid={:?}",
        o.id, o.status, o.side, o.filled_size, o.size, o.price,
        o.market_id, o.asset_id, o.client_order_id,
    );
}

每个 OrderRecord 暴露下单参数(sidesizepriceorder_typesize_typeexpiration)、下单元数据(market_idasset_idclient_order_idtx_hash),以及当前生命周期状态(statusfilled_sizefilled_amount)。

提示:

第 9 步:撤销订单

撤单流程与 place_order 一致,同样分三步:构造类型化的 CancelRequest,从中派生对应的 wire 格式 CancelItem,对动作签名,最后提交 wire 请求。可以通过订单的服务端 oidOrderRecord.id 的返回值)或 cloid(下单时传入的 client_order_id)来定位目标订单。

下面的片段假设 clientkey 已按下单一节构造完成。

撤销订单示例

use okx_outcomes_sdk::models::order::{CancelItem, CancelOrderAction, CancelOrderRequest};
use okx_outcomes_sdk::signing::{
    action_cancel, now_millis, sign_to_wrapper, CancelRequest, CancelTarget,
};

// 1. CancelTarget 必须选其一:
//    Oid   = 服务端分配的订单 ID(OrderRecord.id),十进制字符串。
//    Cloid = 客户端分配的订单 ID,带 0x 前缀的 hex 字符串。
let cancel_request = CancelRequest {
    asset_id:    "100049000".into(),
    market_type: "prediction".into(),
    target:      CancelTarget::Oid("578840".into()),
    // target:   CancelTarget::Cloid("0xabc...".into()),
};

// 2. 从同一份请求派生 wire 项,确保签名字节与 JSON 请求体不会发散。
let cancel_item = CancelItem::from(&cancel_request);
let action      = action_cancel(vec![cancel_request]);

// 3. 签名并提交。
let nonce     = now_millis();
let signature = sign_to_wrapper(&action, nonce, None, &key)?;

let resp = client
    .cancel_order(&CancelOrderRequest {
        action: CancelOrderAction {
            action_type: "cancel".into(),
            cancels:     vec![cancel_item],
        },
        nonce:     nonce as i64,
        signature,
    })
    .await?;
println!("cancel tx_hash: {}", resp.tx_hash);

第 10 步:查看余额、订单与持仓

查询余额与持仓示例

// 余额
let entries = client.get_balance().await?; // Vec<BalanceEntry>
for entry in &entries {
    println!("[{}] available={} total={}", entry.odds_type, entry.available, entry.balance);
}

// 持仓
let positions = client.get_positions(
    Some("open"), // "open" | "closed" | "settled"
    None,         // market_id
    None,         // cursor
    Some(50),     // limit
).await?;
println!("{} positions", positions.list.len());

订单查询方式见第 8 步。get_positions 同样通过 cursor + limit 分页。

完整流程回顾

最小接入流程如下:

  1. 在 App / 网页 中创建 API Key
  2. 在 App 中授权 Agent
  3. 安装 SDK
  4. 配置环境变量
  5. 初始化 Client
  6. 查询 Event 和 Market
  7. 获取 YES / NO outcome 的 assetId,使用 assetId 提交订单
  8. 查询订单状态
  9. 如有需要,撤销订单
  10. 成交后查询持仓和余额

注意事项

主题 说明
心跳 如果运行机器人,设置心跳以在机器人断线时自动撤销所有订单。每 < 5 分钟发送一次预签名的全部撤单,详情见 SDK 文档。
价格刻度 见下表。
Nonce 使用当前毫秒时间戳。每个 Nonce 只能使用一次。
时间戳偏差 OK-ACCESS-TIMESTAMP 与服务器时间相差不能超过 30 秒,否则返回错误码 50102。

价格刻度规则

价格区间 最小精度 示例
0.04 - 0.96 0.01 0.04, 0.55, 0.96
< 0.04 或 > 0.96 0.001 0.039, 0.962

常见问题

问题 可能原因 处理方式
assetIdnull 市场尚未完成注册 等待 assetId 返回有效值后再下单
下单失败 市场不可交易、余额不足、签名错误或参数不合法 查看错误码和订单失败原因
签名失败 Agent 私钥不正确,或签名内容与请求体不一致 确认 Agent 已授权,并使用 SDK 构造签名
查询不到订单 订单 ID 错误,或订单已经结束 使用订单列表接口确认订单状态
撤单后订单仍未取消 撤单异步处理中 等待状态从 PENDING_CANCEL 更新为 CANCELLED

REST API

REST API 用于查询事件、市场、行情、订单簿、订单、成交、持仓、余额,以及提交下单、撤单、Split、Merge、Redeem 等写操作。开发者可以通过 REST API 完成完整的交易闭环:发现市场、获取 assetId、提交订单、查询订单状态、管理持仓并处理结算相关操作。所有接口的基础地址为 https://www.okx.com

1. 事件与市场

数据结构定义

结果选项 OutcomeResp

字段 类型 说明
tokenId string / null 条件代币合约地址;未上链前为 null
assetId string / null TradeZone 资产 ID(下单时使用);未上链前为 null
name string 结果名称,如 "Yes""No"
price string 当前价格(0–1 之间的字符串,如 "0.82")
bgColor string 按钮背景色(Hex);体育 moneyline Yes outcome 取主队主题色;其余为 null

市场对象 MarketResp

字段 类型 说明
id string 市场中心化唯一 ID
marketId string 市场在 TradeZone 中的唯一 ID(下单时使用)
negRisk boolean 是否互斥市场(negRisk)
status string 市场状态:active / paused / settling / resolved
settleStage int 结算阶段:0=未开始 1=第一轮公示 2=第一轮 dispute 3=第二轮公示 4=第二轮 dispute 5=已结算
question string 完整市场问题
shortQuestion string / null 缩略市场问题
description string 市场描述
marketIcon string / null 市场图标 URL
bestBid string / null 最优买价(0–1);无买盘时为 null
bestAsk string / null 最优卖价(0–1);无卖盘时为 null
lastTradePrice string / null 最新成交价(0–1);从未成交时为 null
volume string 总交易量(xp)
probability string / null 市场 Yes 概率(0–1 小数);未上链时为 null
resolutionSources string[] 结算数据来源 URL 列表
yesOutcome OutcomeObject Yes 方结果选项
noOutcome OutcomeObject No 方结果选项
startTime string market 预计开始时间(毫秒时间戳)
endTime string market 预计结束时间(毫秒时间戳)
resolveStartAt string 首次进入 start_resolve 状态的时间(毫秒时间戳)
resolveAt string 首次进入 resolved 状态的时间(毫秒时间戳)

事件对象 EventResp

字段 类型 说明
id string 事件中心化唯一 ID
eventId string 事件在 TradeZone 中的唯一 ID
negRisk boolean 是否负风险事件
status string 事件状态:active / paused / resolved
eventTitle string 事件标题
description string 事件描述
eventIcon string / null 事件图标 URL
volume string 事件内所有市场交易量之和(xp)
startTime string / null 开始交易时间戳(ms)
endTime string / null 停止交易时间戳(ms)
createdAt string 事件创建时间戳(ms)
totalMarketsCount int 事件下市场总数
finalOutcomesMarketId string / null 结算后胜出市场 ID;未结算时为 null
markets array<MarketResp> 市场列表(列表接口最多返回前 2 个;完整列表通过 GET /api/v5/predictions/events/{eventId}/markets 获取)

游标分页 PaginationResp

字段 类型 说明
nextCursor string / null 下一页游标;null 表示已是最后一页
hasMore boolean 是否有更多数据
pageSize int 本次返回数量

1.1 获取事件列表

获取预测市场事件列表,支持状态等多维过滤与排序。

HTTP请求

GET /api/v5/predictions/events

请求参数(Query)

参数 类型 必填 默认值 说明
status string active 事件状态过滤:active / resolved
sort string volume_24h 排序方式:volume / volume_24h / ending_soon / newest
tag string - 体育标签 ID 过滤(来自 GET /api/v5/predictions/sports/tags)
leagueId string - 联赛 ID 过滤(来自 GET /api/v5/predictions/sports/tags/{tagId}/leagues)
cursor string - 分页游标,首次请求不传
pageSize int 10 每页条数(最大 50)

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "events": [
      {
        "id": "100",
        "eventId": "100",
        "negRisk": false,
        "status": "active",
        "eventTitle": "Will BTC exceed $100k by end of 2026?",
        "description": "This event tracks whether Bitcoin will cross $100,000.",
        "eventIcon": "https://cdn.example.com/events/100.png",
        "volume": "1250000.00",
        "startTime": 1700000000000,
        "endTime": 1798761600000,
        "createdAt": 1710000000000,
        "totalMarketsCount": 1,
        "finalOutcomesMarketId": null,
        "markets": [
          {
            "id": "1",
            "marketId": "1",
            "oddsType": "points",
            "negRisk": false,
            "status": "active",
            "settleStage": 0,
            "question": "Will BTC exceed $100k by end of 2026?",
            "shortQuestion": "BTC > $100k?",
            "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
            "marketIcon": null,
            "bestBid": "0.64",
            "bestAsk": "0.66",
            "lastTradePrice": "0.65",
            "volume": "1250000.00",
            "probability": "0.65",
            "resolutionSources": ["https://coinmarketcap.com"],
            "yesOutcome": {
              "tokenId": "0xabc...001",
              "assetId": "1",
              "name": "Yes",
              "price": "0.65",
              "finalResult": null
            },
            "noOutcome": {
              "tokenId": "0xabc...002",
              "assetId": "2",
              "name": "No",
              "price": "0.35",
              "finalResult": null
            }
          }
        ]
      }
    ],
    "pagination": {
      "nextCursor": "eyJpZCI6MTAwfQ",
      "hasMore": true,
      "pageSize": 10
    }
  }
}
字段 类型 说明
events array<EventResp> 事件列表;每个事件的 markets 最多返回前 2 个市场
pagination PaginationResp 游标分页信息

1.2 获取单个事件

获取指定事件的完整信息,markets 字段返回该事件下所有市场(不截断)。

HTTP请求

GET /api/v5/predictions/events/{eventId}

路径参数

参数 类型 必填 说明
eventId string 事件 ID

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "100",
    "eventId": "100",
    "negRisk": false,
    "status": "active",
    "eventTitle": "NBA Finals 2026 - Lakers vs Celtics",
    "description": "Who will win the 2026 NBA Championship?",
    "eventIcon": "https://cdn.example.com/events/100.png",
    "volume": "3200000.00",
    "startTime": 1748000000000,
    "endTime": 1750000000000,
    "createdAt": 1710000000000,
    "totalMarketsCount": 3,
    "finalOutcomesMarketId": null,
    "markets": [ ]
  }
}

返回完整 EventResp,markets 包含该事件下的全部市场。

1.3 获取市场列表

获取指定事件下的全部市场,不分页。

HTTP请求

GET /api/v5/predictions/events/{eventId}/markets

路径参数

参数 类型 必填 说明
eventId string 事件 ID

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "markets": [
      {
        "id": "1",
        "marketId": "1",
        "negRisk": false,
        "status": "active",
        "settleStage": 0,
        "question": "Will BTC exceed $100k by end of 2026?",
        "shortQuestion": "BTC > $100k?",
        "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
        "marketIcon": null,
        "bestBid": "0.64",
        "bestAsk": "0.66",
        "lastTradePrice": "0.65",
        "volume": "1250000.00",
        "probability": "0.65",
        "resolutionSources": ["https://coinmarketcap.com"],
        "yesOutcome": {
          "tokenId": "0xabc...001",
          "assetId": "1",
          "name": "Yes",
          "price": "0.65",
          "finalResult": null
        },
        "noOutcome": {
          "tokenId": "0xabc...002",
          "assetId": "2",
          "name": "No",
          "price": "0.35",
          "finalResult": null
        }
      }
    ]
  }
}
字段 类型 说明
markets array<MarketResp> 该事件下的全部市场

1.4 获取单个市场

获取指定市场的详细信息。

HTTP请求

GET /api/v5/predictions/markets/{marketId}

路径参数

参数 类型 必填 说明
marketId string 市场 TradeZone ID

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "1",
    "marketId": "1",
    "negRisk": false,
    "status": "resolved",
    "settleStage": 5,
    "question": "Will BTC exceed $100k by end of 2026?",
    "shortQuestion": "BTC > $100k?",
    "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
    "marketIcon": null,
    "bestBid": null,
    "bestAsk": null,
    "lastTradePrice": "0.98",
    "volume": "1250000.00",
    "probability": null,
    "resolutionSources": ["https://coinmarketcap.com"],
    "yesOutcome": {
      "tokenId": "0xabc...001",
      "assetId": "1",
      "name": "Yes",
      "price": "1.00",
      "finalResult": true
    },
    "noOutcome": {
      "tokenId": "0xabc...002",
      "assetId": "2",
      "name": "No",
      "price": "0.00",
      "finalResult": false
    }
  }
}

返回完整 MarketResp。

1.5 搜索事件

按关键字全文搜索事件,返回匹配的事件列表,支持游标分页。

HTTP请求

GET /api/v5/predictions/events/search

请求参数(Query)

参数 类型 必填 默认值 说明
keyword string - 搜索关键字,匹配事件标题和描述
cursor string - 分页游标,首次请求不传
pageSize int 10 每页条数(最大 50)

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "events": [
      {
        "id": "100",
        "eventId": "100",
        "negRisk": false,
        "status": "active",
        "eventTitle": "Will BTC exceed $100k by end of 2026?",
        "description": "This event tracks whether Bitcoin will cross $100,000.",
        "eventIcon": "https://cdn.example.com/events/100.png",
        "volume": "1250000.00",
        "startTime": 1700000000000,
        "endTime": 1798761600000,
        "createdAt": 1710000000000,
        "totalMarketsCount": 1,
        "finalOutcomesMarketId": null,
        "markets": [
          {
            "id": "1",
            "marketId": "1",
            "oddsType": "points",
            "negRisk": false,
            "status": "active",
            "settleStage": 0,
            "question": "Will BTC exceed $100k by end of 2026?",
            "shortQuestion": "BTC > $100k?",
            "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
            "marketIcon": null,
            "bestBid": "0.64",
            "bestAsk": "0.66",
            "lastTradePrice": "0.65",
            "volume": "1250000.00",
            "probability": "0.65",
            "resolutionSources": ["https://coinmarketcap.com"],
            "yesOutcome": {
              "tokenId": "0xabc...001",
              "assetId": "1",
              "name": "Yes",
              "price": "0.65",
              "finalResult": null
            },
            "noOutcome": {
              "tokenId": "0xabc...002",
              "assetId": "2",
              "name": "No",
              "price": "0.35",
              "finalResult": null
            }
          }
        ]
      }
    ],
    "pagination": {
      "nextCursor": "eyJpZCI6MTAwfQ",
      "hasMore": true,
      "pageSize": 10
    }
  }
}
字段 类型 说明
events array<EventResp> 事件列表
pagination PaginationResp 游标分页信息

2. 价格

2.1 获取行情 Ticker

获取单个预测市场产品的最新行情信息,用于订单簿中的最新成交价展示

请求参数

参数 类型 必须 说明
instId String 预测市场 yesAssetId

HTTP请求

GET /api/v5/market/ticker?instId={yesAssetId}

返回参数

返回示例

{
  "code": "0",
  "msg": "",
  "data": [{
    "instType": "SPOT",
    "instId": "{yesAssetId}",
    "last": "0.65",
    "lastSz": "100",
    "askPx": "0.66",
    "askSz": "500",
    "bidPx": "0.64",
    "bidSz": "300",
    "open24h": "0.60",
    "high24h": "0.70",
    "low24h": "0.55",
    "vol24h": "10000",
    "volCcy24h": "6500",
    "sodUtc0": "0.62",
    "sodUtc8": "0.61",
    "ts": "1711900800000"
  }]
}
字段 类型 说明
instType String 产品类型
instId String 产品ID
last String 最新成交价
lastSz String 最新成交数量
askPx String 卖一价
askSz String 卖一价对应数量
bidPx String 买一价
bidSz String 买一价对应数量
open24h String 24小时开盘价
high24h String 24小时最高价
low24h String 24小时最低价
vol24h String 24小时成交量(以张计)
volCcy24h String 24小时成交量(以计价货币计)
sodUtc0 String UTC 0 时开盘价
sodUtc8 String UTC+8 时开盘价
ts String 数据更新时间(Unix 毫秒时间戳)

2.2 获取 K 线数据

查询预测市场产品的历史 K 线数据,K线数据按请求的粒度分组返回,K线数据每个粒度最多可获取最近1,440条

请求参数

参数 类型 必须 说明
instId String 预测市场 yesAssetId
bar String K 线周期,默认 1m。可选值:1m 3m 5m 15m 30m 1H 2H 4H 6H 12H 1D 1W 1M
after String 分页游标,返回该时间戳之前的数据(Unix 毫秒)
before String 分页游标,返回该时间戳之后的数据(Unix 毫秒)
limit String 每页数量,最大 100,默认 100

HTTP请求

GET /api/v5/market/candles?instId={yesAssetId}&bar=1m&limit=100

返回数据

(data 为二维数组,每条记录字段顺序如下)

返回示例

{
  "code": "0",
  "msg": "",
  "data": [
    ["1711900800000", "0.65", "0.67", "0.63", "0.66", "2000", "1300", "1300", "1"],
    ["1711900740000", "0.63", "0.66", "0.62", "0.65", "1800", "1170", "1170", "1"]
  ]
}
索引 字段 类型 说明
0 ts String K 线开始时间(Unix 毫秒时间戳)
1 o String 开盘价
2 h String 最高价
3 l String 最低价
4 c String 收盘价
5 vol String 成交量(以张计)
6 volCcy String 成交量(以计价货币计)
7 volCcyQuote String 成交量(以报价货币计)
8 confirm String K 线状态:0 未确认,1 已确认

2.3 获取深度数据

查询预测市场产品的买卖盘口深度快照

请求参数

参数 类型 必须 说明
instId String 预测市场 yesAssetId
sz String 深度档位数量,最大值可传400,即买卖深度共800条 不填写此参数,默认返回1档深度数据

HTTP请求

GET /api/v5/market/pm-books?instId={yesAssetId}&sz=400

返回参数

返回示例

{
  "code": "0",
  "msg": "",
  "data": [
    {
      "asks": [
        ["67364.1","0.45478048","5"]
      ],
      "bids": [
        ["67364","1.72315936", "17"]
      ],
      "ts": "1774943488756",
      "seqId": 74487243135
    }
  ]
}

字段 类型 说明
asks Array 卖盘数据,按价格从低到高排列
bids Array 买盘数据,按价格从高到低排列
ts String 深度快照时间(Unix 毫秒时间戳)
seqId Number 订单簿的版本号,预测市场无需关心

asks / bids 数组中每条记录格式:[价格, 数量, 订单数量]

3. 订单

3.1 下单

提交签名后的下单请求。开发者自行构造 calldata 并用自己的钱包私钥(ECDSA)签名,服务端校验后写入数据库并直接转发至 TradeZone(不经过 AA Wallet)。

HTTP请求

POST /api/v5/predictions/orders

请求体

请求示例

{
  "action": {
    "type": "placeOrder",
    "grouping": "na",
    "orders": [{
      "assetId": "1",
      "marketType": "prediction",
      "side": "buy",
      "price": "0.65",
      "size": "100",
      "reduceOnly": false,
      "clientOrderId": "0x0197a98c91312671ca83f15ccbd5186f",
      "orderType": { "limit": { "tif": "gtc" } }
    }]
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object 下单动作
action.type string 固定 "placeOrder"
action.grouping string 固定 "na"
action.orders array 订单列表(每次一笔)
action.orders[].assetId string TradeZone 资产 ID(如 "1"
action.orders[].marketType string 固定 "prediction"
action.orders[].side string "buy" / "sell"
action.orders[].price string 挂单价格(如 "0.65"
action.orders[].size string 下单数量(如 "100"
action.orders[].clientOrderId string 下单clientOrderId 区分地区
action.orders[].reduceOnly boolean 是否只减仓,默认 false
action.orders[].sizeType string "base"(默认)/ "quote"
action.orders[].orderType object 订单类型
action.orders[].orderType.limit.tif string "gtc" / "gtd" / "ioc" / "fok" / "alo"
nonce long 请求时间戳(毫秒),防重放
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

TIF(Time In Force)

JSON 值 交易结构 说明
"gtc" "tif": "gtc" Good-Til-Cancel,挂单直到成交或取消
"gtd" "tif": { "gtd": { "expiresAfter": 1700000005000 } }, Good-Til-Date,挂单直到 expiresAfter 指定的时间过期或成交/取消
"ioc" "tif": "ioc" Immediate-Or-Cancel(即 FAK),立即尽可能成交后取消剩余。仅用于模拟市价单,需配合价格保护上/下限(price
"fok" "tif": "fok" Fill-Or-Kill,全部成交或全部取消。仅用于模拟市价单,需配合价格保护上/下限(price
"alo" "tif": "alo" Add-Liquidity-Only (Post-Only),只挂 maker 单,若会立即成交则拒绝

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...789"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

3.2 撤销单个订单

撤销一个活跃订单。开发者自行构造撤单 calldata 并签名,服务端校验订单归属和状态后提交至 TradeZone。

HTTP请求

POST /api/v5/predictions/orders/cancel

请求体

请求示例

{
  "action": {
    "type": "cancel",
    "cancels": [{
      "assetId": "1",
      "marketType": "prediction",
      "oid": "12345",
      "clientOrderId":"0x"
    }]
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object 撤单动作
action.type string 固定 "cancel"
action.cancels array 撤单列表(每次一笔)
action.cancels[].assetId string TradeZone 资产 ID(如 "1"
action.cancels[].marketType string 固定 "prediction"
action.cancels[].oid string 订单 ID与客户端ID 两个中必传一个
action.cancels[].clientOrderId string 订单 ID与客户端ID 两个中必传一个
nonce long 请求时间戳(毫秒)
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

3.3 查询单个订单

查询指定订单的完整详情(活跃或历史订单均可)

HTTP请求

GET /api/v5/predictions/orders/{orderId}

路径参数

参数 类型 必填 说明
orderId string 订单 ID

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "1",
    "oid": "1",
    "clientOrderId": "1",
    "marketId": "1",
    "assetId": "1",
    "side": "BUY",
    "orderType": "GTC",
    "sizeType": "BASE",
    "size": "100",
    "price": "0.65",
    "expiration": null,
    "txHash": "0xdef...789",
    "status": "ACTIVE",
    "filledSize": "40",
    "filledAmount": "26",
    "failReason": null,
    "cancelReason": null,
    "oddsType": "points",
    "createdAt": "1710000000000",
    "updatedAt": "1710000000000"
  }
}
字段 类型 说明
id string 订单 ID
oid string 订单 oid
clientOrderId string 订单 clientOrderId
marketId string 市场 ID
tokenId string YES/NO 代币地址
assetId string TradeZone 资产 ID
side string BUY / SELL
orderType string GTC / GTD / FOK / IOC / POST_ONLY
sizeType string BASE / QUOTE
size string 原始下单数量
price string 挂单价格
expiration string GTD 过期时间戳(毫秒);非 GTD 为 null
txHash string 当前阶段的交易哈希
status string 订单状态(见下表)
filledSize string 已成交代币数量
filledAmount string 已成交 xp 金额
failReason string 失败原因(仅 status = FAILED 时有值)
cancelReason string 取消原因(系统触发的取消)
oddsType string 盘口类型:points=积分盘
createdAt string 订单创建时间戳(毫秒)
updatedAt string 最后更新时间戳(毫秒)

订单状态

Status Description
PENDING_PLACE Submitted, waiting for on-chain confirmation
ACTIVE Active open order
PENDING_CANCEL Cancellation submitted, waiting for on-chain confirmation
FILLED Fully filled
PARTIALLY_FILLED Partially filled then cancelled or expired
FAILED On-chain transaction failed
CANCELLED Cancelled by user or system
EXPIRED GTD order expired

3.4 查询用户订单列表

查询当前认证用户的订单列表,支持过滤和分页。

HTTP请求

GET /api/v5/predictions/orders

请求参数(Query)

参数 类型 必填 说明
marketId string 按市场 ID 过滤
status string open(PENDING_PLACE / ACTIVE / PENDING_CANCEL)、closed(FILLED / PARTIALLY_FILLED / CANCELLED / EXPIRED / FAILED),不传默认open
cursor string 分页游标
limit int 每页条数(默认 20,最大 50)

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "id": "1",
        "oid": "1",
        "clientOrderId": "1",
        "marketId": "1",
        "assetId": "1",
        "side": "BUY",
        "orderType": "GTC",
        "sizeType": "BASE",
        "size": "100",
        "price": "0.65",
        "expiration": null,
        "txHash": "0xdef...789",
        "status": "ACTIVE",
        "filledSize": "40",
        "filledAmount": "26",
        "failReason": null,
        "cancelReason": null,
        "oddsType": "points",
        "createdAt": "1710000000000",
        "updatedAt": "1710000000000"
      }
    ],
    "nextCursor": "eyJpZCI6MTIzfQ",
    "hasNext": false
  }
}
字段 类型 说明
list[].id string 订单 ID
list[].marketId string 市场 ID
list[].oid string 订单 oid
list[].clientOrderId string 订单 clientOrderId
list[].tokenId string YES/NO 代币地址
list[].assetId string TradeZone 资产 ID
list[].side string BUY / SELL
list[].orderType string GTC / GTD / FOK / IOC / POST_ONLY
list[].sizeType string BASE / QUOTE
list[].size string 原始下单数量
list[].price string 挂单价格
list[].expiration string GTD 过期时间戳(毫秒);非 GTD 为 null
list[].txHash string 当前阶段的交易哈希
list[].status string 订单状态:PENDING_PLACE / ACTIVE / PENDING_CANCEL / FILLED / PARTIALLY_FILLED / FAILED / CANCELLED / EXPIRED
list[].filledSize string 已成交代币数量
list[].filledAmount string 已成交 pts金额
list[].failReason string 失败原因(仅 status = FAILED 时有值)
list[].cancelReason string 取消原因(系统触发的取消)
list[].oddsType string 盘口类型:points=积分盘
list[].createdAt String 订单创建时间戳(毫秒)
list[].updatedAt string 最后更新时间戳(毫秒)
nextCursor string 下一页游标
hasNext boolean 是否有更多数据

3.5 撤销全部 / 指定市场订单

撤销当前认证用户的活跃订单。assetIds 为空列表时撤销全部市场订单;传入具体值时仅撤销对应资产所在市场的订单。

HTTP请求

POST /api/v5/predictions/orders/cancel-all

请求体

请求示例(按市场撤单)

{
  "action": {
    "type": "cancelAll",
    "assetIds": [1, 2, 5],
    "marketType": "prediction"
  },
  "nonce": 1708929600000,
  "expiresAfter": 1708929660000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}

请求示例(撤销全部)

{
  "action": {
    "type": "cancelAll",
    "assetIds": [],
    "marketType": "prediction"
  },
  "nonce": 1708929600000,
  "expiresAfter": 1708929660000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object 撤单动作
action.type string 固定 "cancelAll"
action.assetIds array 资产 ID 列表;传空列表撤销全部订单,传具体值按对应市场撤单
action.marketType string 市场类型,固定 "prediction"
nonce long 请求时间戳(毫秒)
expiresAfter long 过期时间戳(毫秒)
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

3.6 发送心跳

心跳机制,用于保护开发者的活跃订单。请求体携带一个预签名的 cancelAll action,nonce 设置为当前时间 + 5 分钟。服务端暂存该签名;若开发者在 5 分钟内未续期心跳,系统将自动使用该签名执行全部撤单。

开发者需持续调用此接口(建议间隔 < 5 分钟),每次用新的 nonce(当前时间 + 5 分钟)覆盖上一次的预签名。

HTTP请求

POST /api/v5/predictions/heartbeat

请求体

请求示例

{
  "action": {
    "type": "cancelAll",
    "assetIds": [],
    "marketType": "prediction"
  },
  "nonce": 1708929900000,
  "expiresAfter": 1708929960000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object 撤单动作,结构与 POST /api/v5/predictions/orders/cancel-all 一致
action.type string 固定 "cancelAll"
action.assetIds array 固定传空列表 [],心跳触发时撤销全部订单
action.marketType string 市场类型,固定 "prediction"
nonce long 当前时间 + 5 分钟的时间戳(毫秒)
expiresAfter long 过期时间戳(毫秒)
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "serverTimestamp": 1708929600000,
    "expireAt": 1708929900000
  }
}
字段 类型 说明
serverTimestamp long 服务端当前时间戳(毫秒)
expireAt long 本次心跳过期时间戳(毫秒)

订单语义说明

订单模式支持矩阵

支持矩阵

业务场景 sizeType tif size price 语义 支持
限价买入 base gtc / gtd / ioc / fok / alo 用户输入 shares 用户输入限价
限价卖出 base gtc / gtd / ioc / fok / alo 用户输入 shares 用户输入限价
市价买入(按数量) base ioc / fok 用户输入 shares 系统按盘口模拟生成的最差成交价(保护价)
市价买入(按金额) quote ioc 用户输入名义金额(pts) 系统按盘口模拟生成的最差成交价(保护价)
市价卖出(按数量) base ioc / fok 用户输入 shares 系统按盘口模拟生成的最差成交价(保护价)
市价买入(按金额)+ FOK quote fok 不支持

关键规则

  1. ioc 在预测市场中等价于业界常用的 fak:能成交多少成交多少,剩余取消,不会留单。

  2. fok 仅支持按 shares 下单sizeType=quotetif=fok 组合会被服务端拒绝。

  3. alo(Post Only)仅适用于限价单:与 sizeType=quote 或市价单组合会被拒绝。

  4. 市价单不是"无保护价单":在下单时根据盘口模拟出 worstPrice,并以此作为 price 提交至 TradeZone。实际成交价格不会差于 worstPrice

  5. tif=gtd 必须传 expiration:字段路径为 action.orders[].orderType.limit.expiration,类型为 String(Unix 毫秒时间戳,13 位),绝对时间。未传或非法时当前抛 10001 PARAM_ERROR

不支持组合的错误返回

触发场景 当前错误码
sizeType=quotetif=fok 10001 PARAM_ERROR
sizeType=quotetif ∈ {gtc, gtd, alo} 10001 PARAM_ERROR
tif=gtd 但未传 expiration 10001 PARAM_ERROR

最小下单金额与清仓例外

最小下单金额规则

清仓例外(Sell-All Exception)

价格单位与精度规则

合法范围

条件 是否允许
price ≤ 0 拒绝
price ≥ 1 拒绝
0 < price < 1 允许,进入精度校验

分段精度规则

精度按 [0.04, 0.96] 与区间外分两段:

区间 最大小数位数 等价 cent 区间 示例(允许) 示例(拒绝)
0 < price < 0.04 3 位 (0, 4) cent 0.001 / 0.025 / 0.039 0.0125(4 位)
0.04 ≤ price ≤ 0.96 2 位(tick=0.01) [4, 96] cent 0.04 / 0.25 / 0.96 0.045 / 0.123 / 0.961
0.96 < price < 1 3 位 (96, 100) cent 0.962 / 0.9875 / 0.999 0.99875(4 位)

ClientOrderId 生成规范

clientOrderId(简称 cloid)是订单在链上的唯一标识。链上事件全球广播,各区域消费方通过 cloid 中的 region/env 前缀筛选出属于自己的订单。任何接入方生成的 cloid 都必须遵循本规范,否则订单会被误判归属。

格式

格式为 0x{region}{env}{random}

长度(字符) 内容 说明
前缀 2 0x 固定字面量
region 1 1 个 hex 字符 区域编码
env 1 1 个 hex 字符 环境编码
random 30 30 个 hex 字符 随机数(小写)

总长固定 34 字符,全部为小写 hex(0-9, a-f)。

region / env 编码表

region 含义
0 HK
1 US
2 EU
env 含义
1 线上

取值都是 1 个 hex 字符,目前用到 0/1/2,后续扩展不超过 f(15)。

random 生成要求

参考实现

Java(UUID 派生)

UUID u = UUID.randomUUID();
long hi = u.getMostSignificantBits();
long lo = u.getLeastSignificantBits();
StringBuilder sb = new StringBuilder("0x");
sb.append(Integer.toHexString(region & 0xF));
sb.append(Integer.toHexString(env & 0xF));
for (int i = 14; i >= 0; i--) {
    sb.append(Character.forDigit((int)((hi >>> (i * 4)) & 0xF), 16));
}
for (int i = 14; i >= 0; i--) {
    sb.append(Character.forDigit((int)((lo >>> (i * 4)) & 0xF), 16));
}
return sb.toString();

Node.js

const crypto = require('crypto');
function generate(region, env) {
  const rand = crypto.randomBytes(15).toString('hex'); // 30 hex
  return `0x${region.toString(16)}${env.toString(16)}${rand}`;
}
import secrets
def generate(region: int, env: int) -> str:
    return f"0x{region:x}{env:x}{secrets.token_hex(15)}"

Go

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

func Generate(region, env int) (string, error) {
    b := make([]byte, 15)
    if _, err := rand.Read(b); err != nil {
        return "", err
    }
    return fmt.Sprintf("0x%x%x%s", region, env, hex.EncodeToString(b)), nil
}

服务端解析与归属判断

接收方按以下规则判断 cloid 是否属于当前环境:

if cloid is null/empty
  or length < 4
  or 不以 "0x" 开头
  or cloid[2], cloid[3] 不是合法 hex 字符:
    视为 HK-预发(region=0, env=0)  ← 兜底规则
else:
    region = parse_hex(cloid[2])
    env    = parse_hex(cloid[3])
    判断是否等于当前环境的 (region, env)

常见问题

4. 仓位操作

4.1 Split(xp → YES + NO)

将 xp 分割为等量的 YES 和 NO 条件代币对。开发者自行构造 calldata 并签名,服务端校验后直接提交至 TradeZone。

HTTP请求

POST /api/v5/predictions/positions/split

请求体

请求示例

{
  "action": {
    "type": "predictionSplit",
    "marketId": "1",
    "size": "100000000"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object Split 动作
action.type string 固定 "predictionSplit"
action.marketId string 市场 ID(如 "1"
action.size string pts 数量(最小单位字符串,如 "100000000"
nonce long 请求时间戳(毫秒),防重放
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

4.2 Merge(YES + NO → xp)

将等量的 YES 和 NO 条件代币合并回 xp(Split 的逆操作)。开发者自行构造 calldata 并签名,服务端校验后直接提交至 TradeZone。

HTTP请求

POST /api/v5/predictions/positions/merge

请求体

请求示例

{
  "action": {
    "type": "predictionMerge",
    "marketId": "1",
    "size": "100000000"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object Merge 动作
action.type string 固定 "predictionMerge"
action.marketId string 市场 ID(如 "1"
action.size string 合并数量(最小单位字符串,如 "100000000"
nonce long 请求时间戳(毫秒),防重放
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

4.3 Redeem(结算后兑换 xp)

市场结算后,用胜出的条件代币按 1:1 兑换 xp。兑换数量无需传入,默认兑换用户持有的全部胜出代币。

HTTP请求

POST /api/v5/predictions/positions/redeem

请求体

请求示例

{
  "action": {
    "type": "predictionRedeem",
    "marketId": "1"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
字段 类型 必填 说明
action object Redeem 动作
action.type string 固定 "predictionRedeem"
action.marketId string 市场 ID(如 "1"
nonce long 请求时间戳(毫秒),防重放
signature object 签名对象
signature.Ecdsa object ECDSA 签名分量
signature.Ecdsa.r string 签名 r 值("0x..."
signature.Ecdsa.s string 签名 s 值("0x..."
signature.Ecdsa.v int 签名 v 值(01

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
字段 类型 说明
txHash string TradeZone 交易哈希

5. 成交历史

5.1 查询成交记录

查询当前认证用户的成交(Fill)记录。服务端从登录态解析 userId 并内部转换为 address 查询。

HTTP请求

GET /api/v5/predictions/trades

请求参数(Query)

参数 类型 必填 说明
marketId string 按市场 ID 过滤
side string 按方向过滤:BUY / SELL
startTime long 起始时间戳(毫秒),包含
endTime long 结束时间戳(毫秒),不包含
cursor string 分页游标
limit int 每页条数(默认 20,最大 100)

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "tradeId": "1",
        "orderId": "1",
        "marketId": "1",
        "tokenId": "0xabc...001",
        "side": "BUY",
        "size": "50",
        "amount": "32.5",
        "price": "0.65",
        "fee": "0.065",
        "role": "TAKER",
        "txHash": "0xdef...789",
        "createdAt": 1710000030000
      }
    ],
    "nextCursor": "eyJpZCI6MTAwMDF9",
    "hasNext": false
  }
}
字段 类型 说明
list[].tradeId string 成交记录 ID
list[].orderId string 关联订单 ID
list[].marketId string 市场 ID
list[].tokenId string 代币地址(YES 或 NO)
list[].side string BUY / SELL
list[].size string 成交代币数量
list[].amount string 成交 xp 金额
list[].price string 成交价格
list[].fee string 手续费(xp)
list[].role string MAKER / TAKER
list[].txHash string 链上交易哈希
list[].createdAt string 成交时间戳(毫秒)
nextCursor string 下一页游标
hasNext boolean 是否有更多数据

6. 仓位查询

6.1 查询当前持仓

查询当前认证用户的活跃持仓。

HTTP请求

GET /api/v5/predictions/positions?status=open

6.2 查询已平仓仓位

查询当前认证用户已退出或已结算的仓位。

HTTP请求

GET /api/v5/predictions/positions?status=closed

6.3 查询指定市场仓位

查询当前认证用户在指定市场的仓位。

HTTP请求

GET /api/v5/predictions/positions?marketId={marketId}

6.4 统一端点

以上三种场景均通过同一端点 + 查询参数组合实现:

HTTP请求

GET /api/v5/predictions/positions

请求参数(Query)

参数 类型 必填 说明
status string open(活跃持仓)、closed(已清仓),不传返回全部
marketId long 按市场 ID 过滤
cursor string 分页游标
limit int 每页条数(默认 20,最大 100)

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "id": "100001",
        "tokenId": "0xabc...001",
        "marketId": "1",
        "tokenIndex": "1",
        "tokenName": "Yes",
        "size": "500",
        "availableSize": "480",
        "value": "325",
        "avgPrice": "0.62",
        "unRealizedPnl": "15",
        "unRealizedPnlPercentage": "0.048",
        "title": "Will BTC exceed $100k by end of 2026?",
        "icon": "https://cdn.example.com/markets/1.png",
        "eventId": "100",
        "winningToken": null,
        "positionStatus": 1,
        "oddsType": "points",
        "curPrice": "0.65",
        "realizedPnl": "12.5",
        "realizedPnlPercentage": "0.04"
      }
    ],
    "nextCursor": "eyJpZCI6MTAwMDAxfQ",
    "hasNext": false
  }
}
字段 类型 说明
list[].id string 仓位 ID
list[].tokenId string 代币 ID
list[].marketId string 市场 ID
list[].tokenIndex string 代币方向("1" = YES,"2" = NO)
list[].tokenName string 代币名称("Yes" / "No"
list[].size string 持仓数量(= remain)
list[].availableSize string 可用持仓量(= remain − frozen,未被 SELL 挂单锁定的数量)
list[].value string 当前市值(curPrice × size)
list[].avgPrice string 加权平均持仓成本
list[].unRealizedPnl string 未实现盈亏
list[].unRealizedPnlPercentage string 未实现盈亏百分比
list[].title string 市场问题文本
list[].icon string 市场图标 URL
list[].eventId string 父事件 ID
list[].winningToken string 结算后获胜方代币 ID;未结算时为 null
list[].positionStatus integer 仓位状态码(见 PositionStatusEnum)
list[].oddsType string 盘口类型:points=积分盘
list[].curPrice string 当前代币实时价格
list[].realizedPnl string 已实现盈亏
list[].realizedPnlPercentage string 已实现盈亏百分比
nextCursor string 下一页游标
hasNext boolean 是否有更多数据

7. 账户余额

7.1 查询账户余额

查询当前认证用户的积分余额。

HTTP请求

GET /api/v5/predictions/balance

请求参数:无

响应data):

响应示例

{
  "code": 0,
  "message": "OK",
  "data": [
    { "oddsType": "points", "balance": "2.2",  "available": "2.2"  }
  ]
}

data 为数组,每个元素对应一种盘口类型的余额:

字段 类型 说明
oddsType string 盘口类型:points=积分盘
balance string 总余额
available string 可用余额(总余额 - 冻结中的金额)

8. 限流(Rate Limits)

事件与市场 API

端点 限额
GET /api/v5/predictions/events 20 次 / 1s
GET /api/v5/predictions/events/{eventId} 20 次 / 1s
GET /api/v5/predictions/events/{eventId}/markets 20 次 / 1s
GET /api/v5/predictions/markets/{marketId} 20 次 / 1s
GET /api/v5/predictions/events/search 10 次 / 1s

价格 API(行情数据)

端点 限额
GET /api/v5/market/ticker 10 次 / 1s
GET /api/v5/market/candles 20 次 / 1s
GET /api/v5/market/pm-books 20 次 / 1s

订单 API

查询类

端点 限额
GET /api/v5/predictions/orders/{orderId} 20 次 / 1s
GET /api/v5/predictions/orders 20 次 / 1s

写操作类

端点 限额
POST /api/v5/predictions/orders 50 次 / 1s
POST /api/v5/predictions/orders/cancel 20 次 / 1s
POST /api/v5/predictions/orders/cancel-all 1 次 / 1h
POST /api/v5/predictions/heartbeat 1 次 / 1s

仓位操作 API

端点 限额
POST /api/v5/predictions/positions/split 5 次 / 1s
POST /api/v5/predictions/positions/merge 5 次 / 1s
POST /api/v5/predictions/positions/redeem 5 次 / 1s

成交历史 / 仓位查询 / 余额

端点 限额
GET /api/v5/predictions/trades 10 次 / 1s
GET /api/v5/predictions/positions 10 次 / 1s
GET /api/v5/predictions/balance 10 次 / 1s

触发限流的响应

SDK API 参考

okx-outcomes-sdk Rust crate 公开的每一个公共方法、请求体、响应结构、错误变体和 WebSocket 频道的完整参考。README 是快速入门;本文档是详细版本。

通篇约定:

1. 安装

Cargo.toml 中通过 Git 添加依赖:

Cargo.toml 依赖配置

[dependencies]
okx-outcomes-sdk = { git = "https://github.com/okx/outcomes-sdk.git", features = ["signing", "websocket"] }

2. 客户端构造

模块: okx_outcomes_sdk::{OutcomesSdkClient, ApiCredentials}

pub struct ApiCredentials {
    pub api_key:    String, // OK-ACCESS-KEY 请求头的值
    pub secret_key: String, // HMAC-SHA256 签名密钥;永不传输
    pub passphrase: String, // OK-ACCESS-PASSPHRASE 请求头的值
}

impl OutcomesSdkClient {
    pub fn with_credentials(creds: ApiCredentials) -> Self;
    pub fn with_credentials_and_url(creds: ApiCredentials, base_url: impl Into<String>) -> Self;
}

Base URL 解析顺序:传给 with_credentials_and_url 的显式参数 > PREDICTIONS_API_BASE 环境变量 > https://www.okx.com。Endpoint 常量是完整的绝对路径(/api/v5/predictions/.../api/v5/market/...),与 base URL 拼接,因此一个主机配置同时覆盖预测与市场数据两类调用。

3. 错误

模块: okx_outcomes_sdk::SdkError

每一个可失败的调用都返回 Result<T, SdkError>。该枚举有七个变体:

pub enum SdkError {
    /// 网络故障:连接被拒绝、DNS、超时、TLS 握手失败。
    /// 传输层;通常重试是安全的。
    Http(reqwest::Error),

    /// 服务端在响应信封中返回了非零业务错误码。
    /// `code` 是上游 OKX 的业务码;根据它决定重试 / 退避 / 中止。
    Api { code: i64, message: String },

    /// 响应体无法按预期 schema 反序列化。
    /// 通常意味着 SDK 与服务端版本不匹配。
    Deserialize(serde_json::Error),

    /// WS 连接、发送、登录或关闭失败。
    /// 包括登录被拒(`60xxx` 错误码)和登录过程中的超时。
    WebSocket { message: String },

    /// 保留给绕过公共构造函数、最终没有附带凭据的调用方。
    /// 通过 `with_credentials` / `with_credentials_and_url` 构造的客户端不会
    /// 返回此变体——它们始终携带凭据。
    NotAuthenticated { hint: String },

    /// URL 不合法、状态缺失或意料之外的内部前置条件。
    /// 几乎总是调用方的编程错误。
    Internal { message: String },

    /// 序列化请求体或 WS 载荷失败。
    /// 实际很少见(仅在出现非有限浮点数等情况下才触发)。
    Serialization { message: String },
}

各变体通过 thiserror 实现了 Display,因此 format!("{e}") 会生成可读的一行日志。

4. 事件与市场

模块: okx_outcomes_sdk::models::event::*。API 实现位于 okx_outcomes_sdk::api::events

4.1 获取事件列表 get_events

获取预测市场事件的分页列表。

pub async fn get_events(
    &self,
    status:    Option<&str>,   // "active"(默认)| "resolved"
    tag:       Option<&str>,   // 体育标签 ID
    league_id: Option<&str>,   // 体育联赛 ID
    sort:      Option<&str>,   // "volume" | "volume_24h"(默认) | "ending_soon" | "newest"
    cursor:    Option<&str>,   // 上一次 `EventsResponse.pagination.next_cursor` 返回的分页游标
    page_size: Option<i32>,    // 每页条目数,最大 50(默认 10)
) -> Result<EventsResponse, SdkError>;

pub struct EventsResponse {
    events:     Vec<EventObject>,
    pagination: Pagination,    // 见通用类型
}

pub struct EventObject {
    id:                       String,         // 全局唯一事件 ID
    event_id:                 String,         // 事件 ID
    neg_risk:                 bool,           // 互斥(negRisk)事件
    status:                   EventStatus,    // Active / Paused / Resolved / Unknown
    event_title:              String,         // 展示标题
    description:              String,         // 长描述
    event_icon:               Option<String>, // 图标 URL
    volume:                   String,         // 所有市场的总交易量
    start_time:               Option<i64>,    // 开始交易时间(ms)
    end_time:                 Option<i64>,    // 结束交易时间(ms)
    created_at:               i64,            // 创建时间戳(ms)
    total_markets_count:      i32,            // 该事件下的市场数量
    final_outcomes_market_id: Option<String>, // 结算后的胜出市场 ID
    markets:                  Vec<MarketObject>,      // 列表端点最多返回 2 个市场;
                                                      // 需要完整列表请调用 `get_event_markets`
}

通过关键字搜索事件和市场。

pub async fn search(
    &self,
    keyword:   &str,           // 自由文本查询(必需)
    cursor:    Option<&str>,   // 分页游标
    page_size: Option<i32>,    // 默认 10
) -> Result<EventsResponse, SdkError>;

响应: EventsResponse(与 get_events 形状相同)。

4.3 获取单个事件 get_event

获取单个事件,并内联其完整市场列表。

pub async fn get_event(&self, event_id: &str) -> Result<EventObject, SdkError>;

事件 ID 不存在时返回 Api { code: 40404, ... }

4.4 获取事件下市场 get_event_markets

获取事件的全部市场(无分页、无列表上限)。

pub async fn get_event_markets(&self, event_id: &str) -> Result<MarketsResponse, SdkError>;

pub struct MarketsResponse {
    markets: Vec<MarketObject>,
}

pub struct MarketObject {
    id:                        String,         // 全局唯一市场 ID
    market_id:                 String,         // 市场 ID
    neg_risk:                  bool,           // negRisk 市场标志
    status:                    MarketStatus,   // Active / Paused / Settling / Resolved / Unknown
    settle_stage:              i32,            // 0 = 未开始,5 = 已结算
    question:                  String,         // 完整市场问题
    short_question:            Option<String>, // 简短问题
    description:               String,         // 长描述
    market_icon:               Option<String>, // 图标 URL
    start_time:                String,         // 交易开始时间(ms,字符串)
    end_time:                  String,         // 交易结束时间(ms,字符串)
    resolve_start_at:          String,         // 结算窗口开始时间(ms,字符串)
    resolve_at:                String,         // 结算时间(ms,字符串)
    best_bid:                  Option<String>, // [0, 1] 区间内的十进制;没有买单时为 None
    best_ask:                  Option<String>, // [0, 1] 区间内的十进制;没有卖单时为 None
    last_trade_price:          Option<String>, // 第一笔成交前为 None
    volume:                    String,         // 市场交易量
    probability:               Option<String>, // [0, 1] 区间内的 YES 结果概率
    resolution_sources:        Vec<String>,    // 用于裁定的来源 URL
    yes_outcome:               OutcomeObject,
    no_outcome:                OutcomeObject,
}

pub struct OutcomeObject {
    token_id:     Option<String>, // 条件代币地址;部署前为 None
    asset_id:     Option<String>, // 资产 ID;在下单 / 行情中作为 `inst_id` 使用
    name:         String,         // "Yes" 或 "No"
    price:        String,         // [0, 1] 区间内的十进制
    final_result: Option<bool>,   // Some(true) = 胜出, Some(false) = 落败, None = 未结算
}

4.5 获取单个市场 get_market

获取单个市场。

pub async fn get_market(&self, market_id: &str) -> Result<MarketObject, SdkError>;

5. 账户:余额

模块: okx_outcomes_sdk::models::balance::*。API 位于 okx_outcomes_sdk::api::balance

5.1 查询余额 get_balance

返回已认证用户按赔率类型分组的可用余额。

pub async fn get_balance(&self) -> Result<BalanceResponse, SdkError>;

pub type BalanceResponse = Vec<BalanceEntry>;

pub struct BalanceEntry {
    odds_type: OddsType, // Spots / Points / Unknown(spots = 实盘,points = 积分盘)
    balance:   String, // 总余额(单位由 odds_type 决定)
    available: String, // 可用余额(总余额 - 被未成交订单冻结的金额)
}

6. 账户:订单

模块: okx_outcomes_sdk::models::order::*。API 位于 okx_outcomes_sdk::api::orders

6.1 下单 place_order

提交一个已签名的限价(或触发)订单。

pub async fn place_order(&self, req: &PlaceOrderRequest) -> Result<TxHashResponse, SdkError>;

struct PlaceOrderRequest {
    action:    PlaceOrderAction,
    nonce:     i64,                // 毫秒时间戳,防重放
    signature: SignatureWrapper,   // { Ecdsa: { r, s, v } }
}

struct PlaceOrderAction {
    action_type: String,           // 始终为 "placeOrder"
    grouping:    String,           // 始终为 "na"
    orders:      Vec<OrderItem>,
}

struct OrderItem {
    asset_id:        String,           // 结果 assetId
    side:            SigningOrderSide, // Buy / Sell(下单侧小写 wire,字节用于 EIP-712 哈希)
    market_type:     String,           // 始终为 "prediction"
    client_order_id: Option<String>,   // 34 字符客户端订单 ID;见签名 > 客户端订单 ID
    price:           String,           // [0, 1] 区间内的十进制
    reduce_only:     bool,
    size:            String,           // 十进制
    size_type:       SizeType,         // Base(默认,wire 上省略)/ Quote
    order_type:      OrderTypeSpec,    // { limit: { tif } }
}

struct OrderTypeSpec   { limit: LimitOrderType }
struct LimitOrderType  { tif: LimitTif /* Gtc | Gtd { expires_after } | Ioc | Fok | Alo */ }

响应: TxHashResponse { tx_hash: String }

构造类型化的 signing::types::OrderRequest,用 signing::sign_to_wrapper 签名,再通过 OrderItem::from(&OrderRequest) 推导出通信侧的 OrderItem,以保证签名字节和 JSON 请求体不会发生漂移。参见签名

6.2 撤单 cancel_order

撤销一个活跃订单(按服务端 ID 或客户端订单 ID)。

pub async fn cancel_order(&self, req: &CancelOrderRequest) -> Result<TxHashResponse, SdkError>;

struct CancelOrderRequest {
    action:    CancelOrderAction,
    nonce:     i64,
    signature: SignatureWrapper,
}

struct CancelOrderAction {
    action_type: String,           // 始终为 "cancel"
    cancels:     Vec<CancelItem>,
}

struct CancelItem {
    asset_id:     String,
    market_type:  String,           // "prediction"
    // 二者必选其一:
    by: CancelBy,                   // 扁平化序列化为 { "oid": ... } 或 { "clientOrderId": ... }
}

enum CancelBy {
    Oid           { oid: String },          // 服务端分配,十进制字符串
    ClientOrderId { client_order_id: String }, // 34 字符客户端订单 ID,带 0x 前缀的十六进制
}

响应: TxHashResponse { tx_hash: String }

6.3 全部撤单 cancel_all

撤销所有活跃订单,或者撤销指定 asset ID 集合下的所有活跃订单。

pub async fn cancel_all(&self, req: &CancelAllRequest) -> Result<TxHashResponse, SdkError>;

struct CancelAllRequest {
    action:        CancelAllAction,
    nonce:         i64,
    expires_after: i64,            // 过期时间戳(ms),必填
    signature:     SignatureWrapper,
}

struct CancelAllAction {
    action_type: String,            // 始终为 "cancelAll"
    asset_ids:   Vec<String>,       // 空 = 所有市场;非空 = 过滤
    market_type: String,            // "prediction"
}

响应: TxHashResponse

6.4 心跳 heartbeat

刷新用于保护活跃订单的断线自动撤单开关(dead-man's switch)。

pub async fn heartbeat(&self, req: &CancelAllRequest) -> Result<HeartbeatResponse, SdkError>;

struct HeartbeatResponse {
    server_timestamp: i64, // 服务端当前时间(ms)
    expire_at:        i64, // 本次心跳过期的时间(ms)
}

请求体使用同样的 CancelAllRequest 形状:签名好的载荷就是预授权的 cancel-all——心跳超时后服务端会代为执行它。将 nonce 设为 now_msexpires_after 设为 now_ms + 300_000(5 分钟)。心跳调用频率应高于每 5 分钟一次。

6.5 查询单个订单 get_order

通过服务端分配的 ID 查询单个订单。

pub async fn get_order(&self, order_id: &str) -> Result<OrderRecord, SdkError>;

pub struct OrderRecord {
    id:              String,         // 服务端分配的订单 ID
    oid:             String,         // 订单 oid(与 `id` 不同)
    market_id:       String,
    token_id:        String,         // YES/NO 代币合约地址
    asset_id:        String,         // YES 或 NO 结果资产 ID
    client_order_id: Option<String>, // 下单时提供的客户端订单 ID(如果有)
    side:            OrderSide,        // Buy / Sell / Unknown
    order_type:      TimeInForce,      // Gtc / Gtd / Ioc / Fok / PostOnly / Unknown
    size_type:       OrderSizeType,    // Base / Quote / Unknown
    size:            String,         // 十进制
    price:           String,         // 十进制
    expiration:      Option<String>, // GTD 过期时间(ms,字符串);非 GTD 时为 None
    tx_hash:         String,         // 提交交易哈希
    status:          RestOrderStatus, // PendingPlace / Active / PendingCancel / Filled /
                                      // PartiallyFilled / Failed / Cancelled / Expired / Unknown
    filled_size:     String,          // 十进制
    filled_amount:   String,          // 十进制
    fail_reason:     Option<String>,  // 仅当 status == RestOrderStatus::Failed 时出现
    cancel_reason:   Option<String>,  // 服务端发起撤单时设置(心跳超时、市场结算等)
    odds_type:       OddsType,        // Spots / Points / Unknown
    created_at:      String,         // Unix ms(字符串)
    updated_at:      String,         // Unix ms(字符串)
}

6.6 查询订单列表 list_orders

列出已认证用户的订单。

pub async fn list_orders(
    &self,
    market_id: Option<&str>,   // 按市场 ID 过滤
    status:    Option<&str>,   // "open"(待处理 + 活跃)| "closed"(成交 / 已撤 / 已过期 / 失败)
    cursor:    Option<&str>,   // 分页游标
    limit:     Option<i32>,    // 最大 50,默认 20
) -> Result<OrdersResponse, SdkError>;

// 共享分页响应外壳的类型别名。
pub type OrdersResponse = PagedListResponse<OrderRecord>;
// pub struct PagedListResponse<T> { list: Vec<T>, next_cursor: Option<String>, has_next: bool }

7. 账户:持仓

模块: okx_outcomes_sdk::models::position::*。API 位于 okx_outcomes_sdk::api::positions

7.1 查询持仓 get_positions

查询已认证用户的持仓。

pub async fn get_positions(
    &self,
    status:    Option<&str>,   // "open" | "closed";不传则全部
    market_id: Option<&str>,
    cursor:    Option<&str>,   // 分页游标
    limit:     Option<i32>,    // 最大 100,默认 20
) -> Result<PositionsResponse, SdkError>;

pub type PositionsResponse = PagedListResponse<PositionRecord>;

pub struct PositionRecord {
    id:                         String,         // 标识符
    token_id:                   String,
    market_id:                  String,
    token_index:                String,         // "1" = YES, "2" = NO
    token_name:                 String,         // "Yes" 或 "No"
    size:                       String,         // 当前剩余数量
    available_size:             String,         // 可用数量(= size − 被卖单冻结的部分)
    value:                      String,         // cur_price * size
    avg_price:                  String,         // 加权平均建仓成本
    un_realized_pnl:            String,         // 未实现盈亏
    un_realized_pnl_percentage: String,
    title:                      String,         // 展示字符串
    icon:                       String,         // 展示字符串
    event_id:                   String,
    winning_token:              Option<String>, // 结算后的胜出代币 ID;结算前为 None
    position_status:            i32,            // 持仓状态码(完整枚举见 API 参考)
    cur_price:                  String,         // 当前代币价格
    realized_pnl:               String,         // 已实现盈亏
    realized_pnl_percentage:    String,
    odds_type:                  OddsType,       // Spots / Points / Unknown
                                                 //(已经线上验证:wire 值为 "points" 或 "spots",不是 "real")
}

8. 账户:成交

模块: okx_outcomes_sdk::models::trade::*。API 位于 okx_outcomes_sdk::api::trades

8.1 查询成交 get_trades

查询已认证用户的成交历史。

pub async fn get_trades(
    &self,
    market_id:  Option<&str>,   // 按市场 ID 过滤
    side:       Option<&str>,   // "BUY" | "SELL"
    start_time: Option<i64>,    // 开始时间(含,ms)
    end_time:   Option<i64>,    // 结束时间(不含,ms)
    cursor:     Option<&str>,   // 分页游标
    limit:      Option<i32>,    // 最大 100,默认 20
) -> Result<TradesResponse, SdkError>;

type TradesResponse = PagedListResponse<TradeRecord>;

struct TradeRecord {
    trade_id:   String,    // TAKER 行和链上分配前的 MAKER 行返回空串
    order_id:   String,
    market_id:  String,
    token_id:   String,
    side:       OrderSide, // Buy / Sell / Unknown
    size:       String,    // 成交代币数
    amount:     String,    // 成交金额
    price:      String,
    fee:        String,
    role:       Role,      // Maker / Taker / Unknown
    tx_hash:    String,
    created_at: String,      // Unix ms(字符串)
}

trade_id 在 TAKER 行,以及在链上 trade-id 分配机制之前的历史 MAKER 行中为 None

9. 条件代币

模块: okx_outcomes_sdk::models::position::*(与 positions 共用)。API 位于 okx_outcomes_sdk::api::positions

三者均为需要 EIP-712 签名的写操作。每个请求体的外层形状一致: { action, nonce, signature },只是 action 不同。每个均返回 TxHashResponse { tx_hash: String }

9.1 拆分 split

将一个市场的等额 xp 拆分为等量的 YES + NO 代币(merge 的逆操作)。

pub async fn split(&self, req: &SplitRequest) -> Result<TxHashResponse, SdkError>;

struct SplitRequest { action: SplitAction, nonce: i64, signature: SignatureWrapper }
struct SplitAction {
    action_type: String, // "predictionSplit"
    market_id:   String,
    size:        String, // 最小单位数量
}

9.2 合并 merge

将等量的 YES + NO 代币合并(回到 xp)。

pub async fn merge(&self, req: &MergeRequest) -> Result<TxHashResponse, SdkError>;

struct MergeRequest { action: MergeAction, nonce: i64, signature: SignatureWrapper }
struct MergeAction {
    action_type: String, // "predictionMerge"
    market_id:   String,
    size:        String,
}

9.3 赎回 redeem

在市场结算后,赎回调用方的全部胜出代币余额。没有 size 字段;服务端会赎回调用方持有的全部数量。

pub async fn redeem(&self, req: &RedeemRequest) -> Result<TxHashResponse, SdkError>;

struct RedeemRequest { action: RedeemAction, nonce: i64, signature: SignatureWrapper }
struct RedeemAction {
    action_type: String, // "predictionRedeem"
    market_id:   String,
}

市场尚未裁定时返回 Api { code: 51020, ... }

10. 市场数据

模块: okx_outcomes_sdk::models::price::*。API 位于 okx_outcomes_sdk::api::prices

这些调用访问 OKX 的市场数据 API https://www.okx.com/api/v5/market/* —— 与预测市场 API 同主机但路径前缀和响应信封都不同。市场数据信封把 code 包成 JSON 字符串,因此 SdkError::Api { code } 中的 code 是解析后的整数值。

10.1 获取行情 get_ticker

单个 instrument 的最新报价。inst_id 是市场的 yes_outcome.asset_id

pub async fn get_ticker(&self, inst_id: &str) -> Result<Ticker, SdkError>;

pub struct Ticker {
    inst_type:  String,
    inst_id:    String,
    last:       String, // 最新成交价
    last_sz:    String, // 最新成交数量
    ask_px:     String, // 盘口最优卖价
    ask_sz:     String, // 盘口最优卖单数量
    bid_px:     String, // 盘口最优买价
    bid_sz:     String, // 盘口最优买单数量
    open24h:    String, // 24 小时开盘价
    high24h:    String, // 24 小时最高价
    low24h:     String, // 24 小时最低价
    vol24h:     String, // 24 小时成交量(基础币)
    vol_ccy24h: String, // 24 小时成交量(报价币)
    sod_utc0:   String, // UTC 0  开盘价
    sod_utc8:   String, // UTC+8 开盘价
    ts:         String, // 更新时间戳(Unix ms 十进制字符串)
}

服务端返回 1 元素数组;SDK 会拆包。当 inst ID 未知时返回 Api { code: -1, message: "ticker not found" }

10.2 获取K线 get_candles

K 线历史。

pub async fn get_candles(
    &self,
    inst_id: &str,
    bar:     Option<&str>,   // "1m" / "5m" / "15m" / "30m" / "1H" / "4H" / "1D" / ... ;默认 "1m"
    after:   Option<&str>,   // 返回时间戳在该值**之前**的 K 线(ms)
    before:  Option<&str>,   // 返回时间戳在该值**之后**的 K 线(ms)
    limit:   Option<i32>,    // 最大 300,默认 100
) -> Result<Vec<Candle>, SdkError>;

pub struct Candle(pub Vec<String>);

impl Candle {
    pub fn ts(&self)        -> &str;   // 索引 0:开盘时间(Unix ms 字符串)
    pub fn open(&self)      -> &str;   // 索引 1:开盘价
    pub fn high(&self)      -> &str;   // 索引 2:最高价
    pub fn low(&self)       -> &str;   // 索引 3:最低价
    pub fn close(&self)     -> &str;   // 索引 4:收盘价
    pub fn vol(&self)       -> &str;   // 索引 5:成交量(合约张数)
    // 索引 6:以计价币计的成交量(无 helper)
    // 索引 7:以报价币计的成交量(无 helper)
    pub fn confirmed(&self) -> bool;   // 索引 8:为 "1" 时返回 true(K 线已收盘)
}

10.3 获取深度 get_pm_books

预测市场盘口深度快照。

pub async fn get_pm_books(
    &self,
    inst_id: &str,             // YES 结果资产 ID
    sz:      Option<i32>,      // 单边深度档位数;最大 400(双边合计最多 800)。
                               // 不传时默认 1(仅 BBO)。
) -> Result<PmBookDepth, SdkError>;

pub struct PmBookDepth {
    asks:   Vec<Vec<String>>,  // 卖盘档位,按价格升序。每条为 [price, size, order_count]。
    bids:   Vec<Vec<String>>,  // 买盘档位,按价格降序。每条为 [price, size, order_count]。
    ts:     String,            // 快照时间戳(Unix ms 十进制字符串)
    seq_id: i64,               // 盘口版本序列;对多数调用方不透明,
                               // 仅为了与 API 响应对齐而暴露
}

服务端返回 1 元素的 data 数组;SDK 会拆包。响应为空时返回 Api { code: -1, message: "pm-books snapshot not found" }

11. WebSocket

模块: okx_outcomes_sdk::ws::*。需要 websocket Cargo feature。

连接模型

Open API 对公共频道和私有频道使用同一个端点: wss://<host>/ws/v5/business。公共频道可匿名使用。私有频道在 WS 握手之后需要一次性的 op: "login"

主机:

pub mod ws::endpoints {
    pub const DEFAULT_WS_HOST: &str = "wss://ws.okx.com:8443";
    pub const EU_WS_HOST:      &str = "wss://wseea.okx.com";
    pub const US_WS_HOST:      &str = "wss://wsus.okx.com";
    pub const BUSINESS_PATH:   &str = "/ws/v5/business";
}

PredictionsWsClient 默认使用 DEFAULT_WS_HOST;可通过 PredictionsWsClient::with_host(...)PREDICTIONS_WS_HOST 环境变量覆盖。

生命周期与韧性:

公共 API

pub struct PredictionsWsClient { /* ... */ }

impl PredictionsWsClient {
    pub fn new() -> Self;
    pub fn with_host(host: &str) -> Self;

    pub async fn connect(&self, path: &str) -> Result<(), SdkError>;
    pub async fn login(&self, creds: &ApiCredentials) -> Result<(), SdkError>;
    pub async fn subscribe(&self, channel: &str, params: Vec<HashMap<String, String>>) -> Result<(), SdkError>;
    pub async fn unsubscribe(&self, channel: &str, params: Vec<HashMap<String, String>>) -> Result<(), SdkError>;
    pub async fn disconnect(&self);

    pub fn set_on_data(&self, callback: WsDataCallback);
    pub fn set_on_connection_state(&self, callback: WsConnectionStateCallback);
}

pub type WsDataCallback             = Arc<dyn Fn(&WsMessage) + Send + Sync>;
pub type WsConnectionStateCallback  = Arc<dyn Fn(&str, bool) + Send + Sync>;

subscribe 是幂等的:使用相同的 (channel, params) 对调用两次不会重复订阅,也不会在重放列表中产生重复条目。

登录签名(由 login 内部处理):SDK 计算 sign = Base64(HMAC-SHA256(secret_key, timestamp + "GET" + "/users/self/verify")),并发送:

{"op": "login", "args": [{"apiKey": "...", "passphrase": "...", "timestamp": "...", "sign": "..."}]}

login 返回的 future 只有在服务端响应后才会 resolve:

消息分发

每个传入的 JSON 帧都会被解析一次成 WsMessage 枚举,然后交给 on_data。消费方永远看不到原始 JSON。

pub enum WsMessage {
    Event {
        event:   String,
        channel: Option<String>,
        inst_id: Option<String>,
        msg:     Option<String>,
    },
    Prices(Vec<WsPriceTick>),
    Books { data: Vec<WsPmBookData>, action: String }, // action = "snapshot" | "update"
    Trades(Vec<WsPmTrade>),
    Tickers(Vec<WsPmTicker>),
    Game(Vec<WsGameStatus>),
    EventStatus(Vec<WsEventStatus>),
    Candle(Vec<Candle>),                   // 类型化包装,9 列 OHLCV 数组
    Orders(Vec<WsOrder>),
    Positions(Vec<WsPosition>),
    UserTrades(Vec<WsUserTrade>),
    Balance(Vec<WsBalance>),
    Pnl(Vec<WsPnl>),
    Unknown { channel: String, raw: serde_json::Value },
}

WsMessage::Event 承载 event:"subscribe"|"unsubscribe"|"login"|"error" 等确认消息,与任何数据频道无关。

公共频道

11.1 prediction-market-prices

按市场的价格 tick。

struct WsPriceTick {
    yes_asset_id:     String,
    last_trade_price: Option<String>, // 首次成交前为 None
    best_bid:         Option<String>, // 没有买单时为 None
    best_ask:         Option<String>, // 没有卖单时为 None
    timestamp:        String,         // Unix ms 十进制字符串
    probability:      String,         // 基点 * 100,例如 "6500" = 65.00%
    market_volume:    String,
    event_volume:     String,
    event_id:         String,
}

11.2 pm-books

盘口快照和增量更新。

struct WsPmBookData {
    asks:        Vec<Vec<String>>, // [[price, size, ...], ...]
    bids:        Vec<Vec<String>>, // [[price, size, ...], ...]
    ts:          String,
    checksum:    Option<i64>,      // 规范化盘口的 CRC32 完整性校验
    seq_id:      Option<i64>,      // 单调序列;出现间隙 = 丢失,应重置
    prev_seq_id: Option<i64>,      // 首个快照为 -1
}

prev_seq_id 与上一帧的 seq_id 不匹配时,丢弃本地盘口,等待下一个 snapshot

11.3 pm-trades

公开成交回报。

struct WsPmTrade {
    inst_id:  String,
    trade_id: Option<String>, // 单笔推送:Some;聚合推送:None
    f_id:     Option<String>, // 聚合推送:首个 trade id;单笔:None
    l_id:     Option<String>, // 聚合推送:最后一个 trade id;单笔:None
    px:       String,         // 价格
    sz:       String,         // 数量
    side:     String,         // "buy" / "sell"(taker 方)
    ts:       String,
}

通过 trade_id 与 (f_idl_id) 中哪一对为 Some 来区分单笔推送和窗口聚合推送。

11.4 pm-tickers

OKX 风格的按 instrument ticker 推送。

struct WsPmTicker {
    inst_type: String, inst_id: String,
    last:       String, last_sz: String,
    ask_px:     String, ask_sz:  String,
    bid_px:     String, bid_sz:  String,
    open24h:    String, high24h: String, low24h: String,
    vol24h:     String, vol_ccy24h: String,
    sod_utc0:   String, sod_utc8: String,
    ts:         String,
}

11.5 pm-event-status

事件结算推送。

struct WsEventStatus {
    event_id:       String,
    status:         String,  // 例如 "resolved"
    market_id:      String,  // 胜出市场 ID
    outcome_option: String,  // "yes" / "no" / "others" / 球队名 / "draw"
    timestamp:      String,
}

11.6 pm-candle*

K 线流。频道名编码了 bar: pm-candle1mpm-candle5mpm-candle1Hpm-candle1D 等等。

私有频道(需要 login

订阅时传空参数(服务端将订阅范围限定到已登录账户)。

11.7 pm-order

订单状态变更。

struct WsOrder {
    order_id:        String,
    market_id:       String,
    status:          OrderStatus,       // Active / Filled / PartiallyFilled / PlaceFailed /
                                        // CancelFailed / Cancelled / Expired / Unknown
    side:            OrderSide,         // Buy / Sell / Unknown
    // 以下字段都依赖 status —— 见 spec 的 status → 必备字段表。
    // 用 Option 建模,缺失键反序列化为 None。
    client_order_id: Option<String>,
    asset_id:        Option<String>,    // YES 或 NO 的 assetId
    direction:       Option<Direction>, // Yes / No / Unknown —— 该订单走的结果方向
    filled_size:     Option<String>,
    order_size:      Option<String>,    // serde alias = "size"
    avg_price:       Option<String>,
    amount:          Option<String>,    // BUY = 花费, SELL = 收到(xp)
    limit_price:     Option<String>,    // serde alias = "price"
    fail_message:    Option<String>,    // 仅 PLACE_FAILED / CANCEL_FAILED 出现
    odds_type:       Option<OddsType>,
    tx_hash:         Option<String>,    // serde rename = "txHash"
    trade_id:        Option<String>,
}

11.8 pm-position

持仓更新。

该频道有两种 payload 变体;WsPosition 用单一扁平 struct 表示, 变体相关字段都放在 Option 里。基于 status 分支判断(可用 PositionStatus::is_position_snapshot() / is_failed())以决定 哪些字段是有意义的。

struct WsPosition {
    // 两个变体共有
    market_id: String,
    status:    PositionStatus,    // Fill / FillFailed / Redeem / RedeemFailed /
                                  // Split / SplitFailed / Merge / MergeFailed /
                                  // Deposit / DepositFailed / Withdraw / WithdrawFailed / Unknown
    amount:    String,            // 变体 1: 持仓 `remain`(REDEEM 时为 "0")
                                  // 变体 2: split/merge/deposit/withdraw 数量
    odds_type: Option<OddsType>,

    // 变体 1(FILL / REDEEM / *_FAILED)—— 完整持仓快照
    id:                          Option<String>,
    token_id:                    Option<String>,
    asset_id:                    Option<String>,
    timestamp:                   Option<String>,
    un_realized_pnl:             Option<String>,
    un_realized_pnl_percentage:  Option<String>,
    value:                       Option<String>,
    avg_price:                   Option<String>,
    trade_id:                    Option<String>,

    // 变体 2(SPLIT / MERGE / DEPOSIT / WITHDRAW / *_FAILED)
    tx_hash: Option<String>,           // serde rename = "txHash"
    ext:     Option<WsPositionExt>,    // 仅 DEPOSIT 时填充
}

struct WsPositionExt {
    to_tx_hash: String,                // serde rename = "toTxHash"
}

11.9 pm-user-trade

用户自身的成交流。

struct WsUserTrade {
    order_id:        String,
    client_order_id: String,    // 容错默认空串;通常都有值
    market_id:       String,
    token_id:        String,
    asset_id:        String,    // yesAssetId 或 noAssetId
    side:            OrderSide, // Buy / Sell / Unknown
    size:            String,
    price:           String,
    txhash:          String,
    timestamp:       String,
    trade_id:        String,    // 交易 ID
}

11.10 pm-balance

余额变更。

struct WsBalance {
    wallet_address: String,
    available:      String,
    total:          String,
    frozen:         String,
    token_id:       String,                  // 链上 Point token id
    change_type:    BalanceChangeType,       // Place / Cancel / Fill / Split / Merge /
                                              // Redeem / Deposit / Withdraw / Unknown
    change_amount:  Option<String>,          // spec:可能为 null
    update_time:    String,
    odds_type:      Option<OddsType>,
}

11.11 pm-pnl

浮动盈亏流 —— 推送两种 payload;用 serde untagged enum 建模, 根据存在的字段自动选择正确变体。

enum WsPnl {
    Overview(WsPnlOverview),     // portfolioValue + 各周期汇总
    Timeseries(WsPnlTimeseries), // 包含 high/low/current 的图表点
}

struct WsPnlOverview {
    portfolio_value: String,                 // xp 余额 + 持仓市值
    periods:         Vec<WsPnlPeriodSummary>,
}

struct WsPnlPeriodSummary {
    period:      String, // "1D" / "1W" / "1M" / "6M" / "1Y"
    period_pnl:  String,
    pnl_percent: String,
}

struct WsPnlTimeseries {
    period:      String,         // "0"=1D / "1"=1W / "2"=1M / "3"=6M / "4"=1Y
    interval:    String,         // ms: 600000 / 1800000 / 3600000 / 86400000
    points:      Vec<WsPnlPoint>,
    current_pnl: String,
    high:        String,
    low:         String,
}

WS 错误码

WS 层错误通过 WsMessage::Event { event: "error", msg, .. } 暴露;登录相关的错误则通过 login() 返回的 Err 暴露。常见错误码:

错误码 含义
60004 登录时间戳无效(时钟漂移、已过期)。
60005 API key 无效。
60006 时间戳已过期(30 秒窗口)。
60007 签名无效。
60009 登录失败(通用)。
60011 该私有频道需要登录。
60012 无效的 op 值。
60018 订阅失败(频道名或参数错误)。

12. 签名

模块: okx_outcomes_sdk::signing::*。需要 signing Cargo feature。

任何写操作的完整流水线:构造一个类型化的 Action,用你的 k256::ecdsa::SigningKey 通过 sign_to_wrapper 签名,然后把得到的 SignatureWrapper 放进请求体。

pub fn parse_private_key(hex_key: &str) -> Result<SigningKey, String>;
pub fn now_millis() -> u64;

pub fn sign_to_wrapper(
    action:        &Action,
    nonce:         u64,
    expires_after: Option<u64>,
    key:           &SigningKey,
) -> Result<SignatureWrapper, String>;

Action 构造函数:

pub fn action_place_order(orders: Vec<OrderRequest>) -> Action;
pub fn action_cancel(cancels: Vec<CancelRequest>) -> Action;
pub fn action_cancel_all(asset_ids: Vec<String>, market_type: &str) -> Action;
pub fn action_prediction_split (market_id: &str, size: &str) -> Action;
pub fn action_prediction_merge (market_id: &str, size: &str) -> Action;
pub fn action_prediction_redeem(market_id: &str) -> Action;

类型化输入:

struct OrderRequest {
    asset_id:        String,
    side:            SigningOrderSide,  // Buy / Sell(下单侧小写 wire)
    market_type:     String,            // "prediction"
    client_order_id: Option<String>,    // 34 字符客户端订单 ID
    price:           String,
    reduce_only:     bool,
    size:            String,
    size_type:       SizeType,          // Base(默认)/ Quote
    order_type:      OrderType,
}

enum OrderType {
    Limit(LimitTif),
}
enum LimitTif {
    Plain(String),                                  // "gtc" | "ioc" | "fok" | "alo"
    Gtd { expiry_time: u64 },                       // good-til-date,ms
}

struct CancelRequest {
    asset_id:    String,
    market_type: String,                            // "prediction"
    target:      CancelTarget,
}
enum CancelTarget { Oid(String), ClientOrderId(String) }

通信侧的对应类型(OrderItemCancelItem)分别实现 TryFrom<&OrderRequest>From<&CancelRequest>,以保证 JSON 请求体和签名字节都由同一份源 struct 构造。

客户端订单 ID:

pub fn generate_client_order_id_default() -> Result<String, String>;
pub fn generate_client_order_id(region: Region, env: Env) -> Result<String, String>;
pub fn validate_client_order_id(s: &str) -> bool;
pub fn parse_client_order_id_prefix(client_order_id: Option<&str>) -> ClientOrderIdPrefix;
pub fn register_client_order_id_context(region: Region, env: Env);

客户端订单 ID 是 34 字符的十六进制字符串,形如 0x{region}{env}{30 位十六进制随机数}generate_client_order_id_default() 读取已注册的全局上下文(默认是 HK / PROD)。若需要不同的上下文,在启动时注册一次即可覆盖。

低阶 helper:

pub fn signer_address(key: &SigningKey) -> String;
pub fn ecrecover(signing_hash: &str, signature: &str) -> Result<String, String>;
pub fn sign_action(...) -> Result<String, String>;        // 返回 "0x..." 十六进制
pub fn sign_action_full(...) -> Result<(String, String, String, u8), String>; // (txhash, r, s, v)
pub fn sign_action_debug(...) -> Result<SigningDebug, String>; // 返回所有中间哈希值

正常流程请使用 sign_to_wrapper。低阶函数仅用于调试,或用于需要访问 txhash 的调用方(例如显示"在浏览器中查看"链接)。

13. 通用类型

模块: okx_outcomes_sdk::models::common::*

struct Pagination {
    next_cursor: Option<String>, // 最后一页时为 None
    has_more:    bool,
    page_size:   i32,            // 当前页条目数
}

struct EcdsaSignature {
    r: String, // 十六进制,带 0x 前缀
    s: String, // 十六进制,带 0x 前缀
    v: u8,     // recovery id:0 或 1
}

struct SignatureWrapper {
    // 序列化为 { "Ecdsa": { r, s, v } }
    ecdsa: EcdsaSignature,
}

SDK 透明地包装了两种 API 信封:

WebSocket

1. WebSocket 登录认证

仅订阅私有频道前需先进行 WebSocket 登录认证。公共频道无需登录。

详见 WebSocket 登录认证文档

通用订阅格式

{
  "op": "subscribe",
  "args": [
     { "channel": "<频道名1>", "instId": "<yesAssetId1>" },
     { "channel": "<频道名2>", "instId": "<yesAssetId2>" }
  ]
}

通用取消订阅格式

{
  "op": "unsubscribe",
  "args": [
      { "channel": "<频道名1>", "instId": "<yesAssetId1>" },
      { "channel": "<频道名2>", "instId": "<yesAssetId2>" }
   ]
}

2. WebSocket 私有频道

频道名称 订阅参数 是否需要授权 说明
pm-order channelName 用户订单数据推送
pm-position channelName 仓位变更
pm-user-trade channelName 用户交易历史推送
pm-balance channelName 余额变更
pm-pnl channelName 当前仓位的浮盈/浮亏数值

2.1 订单状态推送

推送频率: 事件触发,订单状态变更时推送

订阅示例

{
  "op": "subscribe",
  "args": [{ "channel": "pm-order" }]
}

推送数据格式

{
  "arg": { "channel": "pm-order", "uid": "{cexUserId}" },
  "data": [{
    "orderId": "307173036051017730",
    "clientOrderId": "cli-abc-123",
    "marketId": "100001",
    "status": "FILLED",
    "assetId": "71",
    "side": "BUY",
    "direction": "YES",
    "filledSize": "10",
    "orderSize": "10",
    "avgPrice": "0.57",
    "amount": "5.7",
    "limitPrice": "0.45",
    "failMessage": null,
    "oddsType": "points",
    "txHash": "0xdef...",
    "tradeId": "9876543210"
  }]
}

推送字段说明

字段 类型 说明
orderId string 订单 ID
clientOrderId string / null 客户端订单 ID(cloid);客户端未传时为 null
marketId string 市场 ID
status string 推送事件类型(见下表枚举)
assetId string 币对资产 ID(yesAssetId 或 noAssetId)
side string 交易方向:BUY / SELL
direction string 持仓方向:YES / NO
filledSize string / null 本次累计成交份额;无成交时为 null
orderSize string 下单份额
avgPrice string / null 累计成交均价(= amount / filledSize);无成交时为 null
amount string / null 累计成交金额(xp),BUY=花费 / SELL=收入;无成交时为 null
limitPrice string / null 限价单挂单价格(仅限价场景);市价单为 null
failMessage string / null 失败提示文案;仅 PLACE_FAILED / CANCEL_FAILED 场景填值
oddsType string 盘口类型:points
txHash string / null 链上交易哈希;未上链事件为 null(如 PLACE_FAILED
tradeId string / null TradeZone 成交 ID;仅限价单部分成交(status=ACTIVE)时填值

status 枚举

code 说明 必填字段
ACTIVE 活跃(限价单上链 / 部分成交且剩余有效) orderSize, limitPrice(部分成交时另含 filledSize, avgPrice, amount, tradeId
FILLED 完全成交 filledSize, avgPrice, amount, txHash
PARTIALLY_FILLED 部分成交后撤单 filledSize, orderSize, avgPrice, amount, txHash
PLACE_FAILED 下单失败 orderSize, failMessage
CANCEL_FAILED 撤单失败(非预期) orderId, failMessage
CANCELLED 用户主动撤单 / 系统批量撤 orderSize, limitPrice
EXPIRED 订单到期 orderSize, limitPrice

2.2 仓位变更推送

推送频率: 事件触发

订阅示例

{
  "op": "subscribe",
  "args": [{ "channel": "pm-position" }]
}

2.3 仓位

(status = FILL / REDEEM/FILL_FAILED/REDEEM_FAILED)

推送数据格式

{
  "arg": { "channel": "pm-position", "uid": "{cexUserId}" },
  "data": [{
    "id": "307173036051017730",
    "marketId": "100001",
    "tokenId": "20000",
    "assetId": "71",
    "amount": "10",
    "timestamp": "1712736000000",
    "unRealizedPnl": "5.20",
    "unRealizedPnlPercentage": "1.05",
    "value": "100.00",
    "avgPrice": "0.57",
    "status": "FILL",
    "tradeId": "9876543210",
    "oddsType": "points"
  }]
}

字段说明

字段 类型 说明
id String 仓位 ID
marketId String 市场 ID
tokenId String YES/NO token 链上 ID
assetId String 币对资产 ID(yesAssetId 或 noAssetId)
amount String 当前持仓数量(remain 快照),REDEEM 场景为 "0"
timestamp String 事件时间戳(毫秒)
unRealizedPnl String 未实现盈亏 = (currentPrice − avgCost) × remain;REDEEM 为 "0"
unRealizedPnlPercentage String 未实现盈亏百分比(8 位精度);REDEEM 为 "0"
value String 仓位市值 = currentPrice × remain;REDEEM 为 "0"
avgPrice String 加权平均持仓成本;REDEEM 为 "0"
status String FILL / REDEEM
tradeId String / null TradeZone 成交 ID;仅 FILL 场景填值(REDEEM 为 null)
oddsType String 盘口类型:points

2.4 status 适用场景

code 说明 单次推送条数
FILL 订单成交导致仓位变更 1(单 tokenId 仓位)
REDEEM 结算赎回,该市场仓位清零 N(该市场原有的每个 tokenId 各一条,amount=0

2.5 仓位

(status = SPLIT / MERGE / DEPOSIT / WITHDRAW/SPLIT_FAILED/MERGE_FAILED/DEPOSIT_FAILED/WITHDRAW_FAILED)

推送数据格式

{
  "arg": { "channel": "pm-position", "uid": "{cexUserId}" },
  "data": [{
    "marketId": "100001",
    "status": "DEPOSIT",
    "amount": "100",
    "txHash": "0xdef...",
    "oddsType": "points",
    "ext": {
      "toTxHash": "0xabc..."
    }
  }]
}

字段说明

字段 类型 说明
marketId String 市场 ID
status String SPLIT / MERGE / DEPOSIT / WITHDRAW
amount String 金额
txHash String 链上交易哈希。DEPOSIT 场景填 XLayer 原始交易哈希;其余场景填该业务对应链上哈希
oddsType String 盘口类型:points
ext Object / null 扩展信息;仅 DEPOSIT 场景填值,其余场景为 null
ext.toTxHash String / null TZ 侧入账交易哈希;仅 DEPOSIT 场景填值

status 枚举

code 说明
FILL 订单成交导致仓位变更
SPLIT Split 拆分成功
MERGE Merge 合并成功
REDEEM 结算赎回成功
DEPOSIT 充值成功
WITHDRAW 提现成功
FILL_FAILED 成交失败
SPLIT_FAILED Split 失败
MERGE_FAILED Merge 失败
REDEEM_FAILED 赎回失败
DEPOSIT_FAILED 充值失败
WITHDRAW_FAILED 提现失败

2.6 撮合成交推送

每笔 FILL 事件推送一条成交流水,前端用于实时展示成交记录列表。

推送频率: 事件触发,每笔成交一条

订阅示例

{
  "op": "subscribe",
  "args": [{ "channel": "pm-user-trade" }]
}

推送数据格式

{
  "arg": { "channel": "pm-user-trade", "uid": "{cexUserId}" },
  "data": [{
    "orderId": "307173036051017730",
    "clientOrderId": "cli-abc-123",
    "marketId": "100001",
    "tokenId": "20000",
    "assetId": "71",
    "side": "BUY",
    "size": "10",
    "price": "0.57",
    "txhash": "0xdef...",
    "timestamp": "1712736000000",
    "tradeId": "9876543210"
  }]
}

推送字段说明

字段 类型 说明
orderId string TradeZone 订单 ID
clientOrderId string / null 客户端订单 ID;客户端未传时为 null
marketId string 市场 ID
tokenId string YES/NO token 链上 ID
assetId string 币对资产 ID(yesAssetId 或 noAssetId)
side string 成交方向:BUY / SELL
size string 本次成交数量
price string 本次成交价格
txhash string 链上交易哈希
timestamp string 事件时间戳(毫秒)
tradeId string TradeZone 成交 ID

2.7 余额变更推送

余额同步成功后推送,前端用于实时更新余额显示。

推送频率: 事件触发,余额变更(挂单 / 撤单 / 成交 / Split / Merge / 赎回 / 充值 / 提现)时推送

订阅示例

{
  "op": "subscribe",
  "args": [{ "channel": "pm-balance" }]
}

推送数据格式

{
  "arg": { "channel": "pm-balance", "uid": "{cexUserId}" },
  "data": [{
    "walletAddress": "0x1234abcd5678ef901234abcd5678ef901234abcd",
    "available": "950.5",
    "total": "1000",
    "frozen": "49.5",
    "tokenId": "0",
    "changeType": "FILL",
    "changeAmount": "100",
    "updateTime": "1712736000000",
    "oddsType": "points"
  }]
}

推送字段说明

字段 类型 说明
walletAddress String 用户 AA 钱包地址
available String 可用余额
total String 总余额(含冻结)
frozen String 冻结金额(= total − available)
tokenId String xp token 链上 ID(与 oddsType 对应)
changeType String 触发原因(见下表枚举)
changeAmount String / null 本次变动金额;不适用场景为 null
updateTime String 事件时间戳(毫秒)
oddsType String 盘口类型:points

2.8 changeType 枚举

code 触发场景
PLACE 挂单冻结
CANCEL 撤单解冻
FILL 成交导致余额变更
SPLIT Split 扣减 xp
MERGE Merge 增加 xp
REDEEM 结算赎回
DEPOSIT 充值
WITHDRAW 提现

2.9 Pnl变更推送

同时推送Pnl曲线图及概览

推送数据格式

{
  "arg": { "channel": "pm-pnl", "uid": "{cexUserId}" },
  "data": [{
    "portfolioValue": "1234.56",
    "periods": [
      { "period": "1D", "periodPnl": "12.34",  "pnlPercent": "1.01" },
      { "period": "1W", "periodPnl": "56.78",  "pnlPercent": "4.83" },
      { "period": "1M", "periodPnl": "120.45", "pnlPercent": "10.81" },
      { "period": "6M", "periodPnl": "320.10", "pnlPercent": "35.02" },
      { "period": "1Y", "periodPnl": "500.00", "pnlPercent": "67.93" }
    ]
  }]
}

字段说明

字段 类型 说明
portfolioValue String 当前组合总价值(xp 可用余额 + 仓位市值)
periods Array 多周期 PnL 汇总数组
periods[].period String 周期:1D / 1W / 1M / 6M / 1Y
periods[].periodPnl String 该周期内 PnL 绝对值
periods[].pnlPercent String 该周期内 PnL 百分比

推送数据格式

{
  "arg": { "channel": "pm-pnl", "uid": "{cexUserId}" },
  "data": [{
    "period": "0",
    "interval": "600000",
    "points": [
      { "time": "1712707200000", "pnl": "1000.00" },
      { "time": "1712728800000", "pnl": "1010.50" },
      { "time": "1712750400000", "pnl": "1023.75" }
    ],
    "currentPnl": "1023.75",
    "high": "1030.00",
    "low": "995.00"
  }]
}

字段说明

字段 类型 说明
period String 周期编码:0=1D / 1=1W / 2=1M / 3=6M / 4=1Y
interval String 数据点间隔(毫秒),与周期对应(见下表)
points Array 时间序列数据点
points[].time String 数据点时间戳(毫秒)
points[].pnl String 该时刻总资产(= xp 余额 + 仓位市值)
currentPnl String 当前总资产(兼容字段)
high String 周期内最高总资产
low String 周期内最低总资产

2.10 周期编码与 interval 对照

period 编码 周期 interval(毫秒) 含义
0 1D 600000 10 分钟
1 1W 1800000 30 分钟
2 1M 3600000 1 小时
3 6M 86400000 1 天
4 1Y 86400000 1 天

3. WebSocket 公共频道

3.1 深度推送

3.2 推送频率

订阅示例

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-books",
    "instId": "{yesAssetId}"
  }]
}

订阅成功响应

{
  "event": "subscribe",
  "arg": { "channel": "pm-books", "instId": "{yesAssetId}" }
}

推送数据格式

{                    
      "arg": {"channel": "pm-books", "instId": "{yesAssetId}"},
      "action": "snapshot",                   
      "data": [                               
          {                                                                                                                                                                                                                                                                                                                 
              "asks": [                                                                                                                                                                                                                                                                                                     
                  ["67300.1", "201.18", "25"],                                                                                                                                                                                                                                                                         
                  ["67300.2", "421.45", "5"]                                                                                                                                                                                                                                                                           
              ],                                                                                                                                                                                                                                                                                                            
              "bids": [                                                                                                                                                                                                                                                                                                     
                  ["67300", "525.41", "34"],                                                                                                                                                                                                                                                                           
                  ["67299.9", "0.17", "7"]                                                                                                                                                                                                                                                                             
              ],                                                                                                                                                                                                                                                                                                            
              "ts": "1774944028506",                                                                                                                                                                                                                                                                                        
              "checksum": -702280706,                                                                                                                                                                                                                                                                                       
              "seqId": 308306650401,                                                                                                                                                                                                                                                                                        
              "prevSeqId": -1                                                                                                                                                                                                                                                                                               
          }                                                                                                                                                                                                                                                                                                                 
      ]                                                                                                                                                                                                                                                                                                                     
  }  

3.3 成交推送

实时推送每笔成交记录

推送频率:推送间隔 200ms,每次推送最多 20 条成交,编码类型为聚合成交

订阅示例

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-trades",
    "instId": "{yesAssetId}"
  }]
}

推送数据格式

{
  "arg": { "channel": "pm-trades", "instId": "{yesAssetId}" },
  "data": [{
    "instId": "{yesAssetId}",
    "tradeId": "123456789",
    "px": "0.65",
    "sz": "100",
    "side": "buy",
    "ts": "1711900800123"
  }]
}

推送字段说明

字段 类型 说明
instId String yesAssetId
fId String 聚合周期内首笔成交 ID
lId String 聚合周期内末笔成交 ID
px String 成交价格
sz String 成交数量
side String 成交方向:buy 买入,sell 卖出
ts String 成交时间(Unix 毫秒时间戳)

3.4 K 线推送

实时推送 K 线数据,当前 K 线未收盘时会持续更新推送。端上可基于K线的收盘价绘制价格趋势图,具体订阅的颗粒度由端上自行评估

频道命名规则: pm-candle + 周期,例如 pm-candle1mpm-candle15m

订阅示例(以 1 分钟和 5 分钟为例)

{
  "op": "subscribe",
  "args": [
    { "channel": "pm-candle1m", "instId": "{yesAssetId}" },
    { "channel": "pm-candle15m", "instId": "{yesAssetId}" }
  ]
}

推送数据格式

{
  "arg": { "channel": "pm-candle15m", "instId": "{yesAssetId}" },
  "data": [
    ["1711900800000", "0.65", "0.67", "0.63", "0.66", "2000", "1300", "1300", "0"]
  ]
}

3.5 推送字段说明

(data 数组中每条记录按索引顺序)

索引 字段 说明
0 ts K 线开始时间(Unix 毫秒时间戳)
1 o 开盘价
2 h 最高价
3 l 最低价
4 c 收盘价
5 vol 成交量(以张计)
6 volCcy 成交量(以计价货币计)
7 volCcyQuote 成交量(以报价货币计)
8 confirm K 线状态:0 未确认(当前 K 线),1 已确认(已收盘)

3.6 行情 Ticker 推送

实时推送最新成交价、买一价、卖一价和24小时交易量等信息。

推送频率:最快100ms推送一次,触发推送的事件有:成交、买一卖一发生变动,没有触发事件时不推送

订阅示例

{
  "op": "subscribe",
  "args": [{ "channel": "pm-tickers", "instId": "{yesAssetId}" }]
}

订阅成功响应

{
  "event": "subscribe",
  "arg": { "channel": "pm-tickers", "instId": "{yesAssetId}" },
  "connId": "accb8e21"
}

推送数据格式

{
  "arg": { "channel": "pm-tickers", "instId": "{yesAssetId}" },
  "data": [{
    "instType": "PREDICTIONS",
    "instId": "{yesAssetId}",
    "last": "0.4999",
    "lastSz": "1",
    "askPx": "0.6",
    "askSz": "30",
    "bidPx": "0.4",
    "bidSz": "30",
    "open24h": "0.5001",
    "high24h": "0.5001",
    "low24h": "0.4999",
    "vol24h": "372",
    "volCcy24h": "186",
    "sodUtc0": "0.4999",
    "sodUtc8": "0.4999",
    "ts": "1774948808119"
  }]
}

推送字段说明

字段 类型 说明
instType String 产品类型
instId String yesAssetId
last String 最新成交价
lastSz String 最新成交数量
askPx String 卖一价
askSz String 卖一价对应数量
bidPx String 买一价
bidSz String 买一价对应数量
open24h String 24小时开盘价
high24h String 24小时最高价
low24h String 24小时最低价
vol24h String 24小时成交量(以张计)
volCcy24h String 24小时成交量(以计价货币计)
sodUtc0 String UTC 0 时开盘价
sodUtc8 String UTC+8 时开盘价
ts String 数据推送时间(Unix 毫秒时间戳)

3.7 概率价格推送

预测市场概率价格推送,包含市场概率、累计成交额、买一卖一、最新成交价等信息

推送频率:定时3s推送一次

订阅示例

{
  "op": "subscribe",
  "args": [{
    "channel": "prediction-market-prices",
    "instId": "{yesAssetId}"
  }]
}

推送数据格式

{
    "arg": {
        "channel": "prediction-market-prices",
        "instId": "{yesAssetId}"
    },
    "data": [
        {
            "yesAssetId": "71",
            "eventId": "1774875348987717503",
            "bestBid": "0.4896",
            "bestAsk": "0.6134",
            "lastTradePrice": "0.5848",
            "probability": "5515",
            "marketVolume": "17.1463",
            "eventVolume": "773.6524",
            "timestamp": "1775036647300"
        }
    ]
}

推送字段说明

字段 类型 说明
yesAssetId String 预测市场 yesAssetId
eventId String 市场所属的event
bestBid String 买一价(最优买入价)
bestAsk String 卖一价(最优卖出价)
lastTradePrice String 最新成交价
probability String Yes 方向的市场概率,推送万分比整数(如 0.6500 → 6500)
marketVolume String 当前市场累计成交额
eventVolume String 当前事件累计成交额
timestamp String 事件时间戳(毫秒)

3.8 事件状态推送

事件状态变更,用于展示事件最终结算结果

推送频率:事件触发,事件得到最终结果时推送,世界杯期间一天2~3场比赛;多元互斥、单一二元事件拿到最终结果时进行推送

订阅示例

注意: 订阅此频道时,instId 取值为 event-{eventId}

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-event-status",
    "instId": "event-{eventId}"
  }]
}

推送数据格式

{
  "arg": {
    "channel": "pm-event-status",
    "instId": "event-{eventId}"
  },
  "data": [{
    "eventId": "{eventId}",
    "status": "resolved",
    "marketId":"marketId",
    "outcomeOption":"yes | no | others | 球队名称 | draw",
    "timestamp": "1672290687"
  }]
}

推送字段说明

字段 类型 说明
eventId string 比赛ID
status string 事件状态
marketId string 胜出市场的marketId
outcomeOption string 最终胜出的展示
timestamp string 事件时间戳(毫秒)

常见错误码

1. 响应格式

所有异常统一返回以下 JSON 结构:

{
  "code": 100015,
  "msg": "calldata 无效或字段不合法"
}

1.1 通用

所有 OpenAPI v5 接口都可能产生。 更多通用错误码详见:OKX 公共错误码文档

Code Meaning
10000 User not logged in
10001 Parameter validation failed
10002 Authentication failed
206004 Request oddsType does not match the account

1.2 事件/市场类

Code Meaning
201001 Event does not exist
201002 Market does not exist

适用接口:

2. 下单 / 写操作类

涵盖:下单、撤单、全部撤单、心跳、Split、Merge、Redeem。

Code Meaning
100001 Market is not tradable
100002 Insufficient balance
100006 Account is frozen
100010 Insufficient token balance
100011 Order does not exist
100012 Order status does not allow cancellation
100013 Request address does not match the user address
100015 Invalid calldata or malformed fields
100016 Nonce already used
100017 Nonce expired
100018 Order amount is below the minimum notional
100101 TradeZone SDK signature exception
120007 User does not exist
120022 Account is in escape (exit) process
201002 Market does not exist
213003 Signature verified but TradeZone submission failed

适用接口:

3. 查询订单 / 仓位类

涵盖:查询单订单、订单列表、成交历史、仓位查询。

Code Meaning
100011 Order does not exist
400 Path parameter parsing failed

适用接口: