API 参考 / Market Maker API
面向程序化做市的接口:批量下单与撤单、按市场撤单、跨市场撤全部、心跳保护,以及下单与撤单分开的限流桶。凭证为 pxk_ API key。
| Base URL | http://127.0.0.1:3100 |
|---|---|
| 路径前缀 | /v1/px |
| 鉴权 | Authorization: Bearer pxk_<keyId>.<secret>,在 设置 → API key 创建;也接受会话 JWT。 |
| 作用域 | read → GET · place → 下单 · cancel → 全部撤单端点与心跳(心跳的效果是撤单)。资金动作(入金 / 借贷)只接受会话,API key 一律 403。 |
| 金额与时间 | 一律 E6 定点 + 十进制字符串(quantityE6、limitPriceE6、expireAtMs)。不用浮点。 |
| 错误体 | { "code", "message", "requestId" };带上 requestId 联系我们可直接定位。 |
以上数值与服务端强制值来自同一份常量(@fathom/exchange-sdk)。
总览页的 Quickstart 走的是零售下单闭环。做市商的闭环是另一条:拿 key → 确认档位 → 登记心跳 → 批量双边报价 → 撤旧换新 → 收工前 kill switch。下面是这条闭环的最小可跑骨架——替换两个环境变量即可运行。
#!/usr/bin/env python3
"""做市闭环最小骨架。运行前:export PX_BASE_URL=... PX_API_KEY=pxk_..."""
import os, time, requests
BASE = os.environ["PX_BASE_URL"]
H = {"X-PX-Key": os.environ["PX_API_KEY"], "Content-Type": "application/json"}
MARKET = "px-btc-120k"
def call(method, path, body=None):
r = requests.request(method, BASE + path, headers=H, json=body, timeout=5)
if r.status_code == 429: # 桶内代币不足:整批被拒,退避后重发整批
time.sleep(1.0)
return None
r.raise_for_status()
return r.json()
# 1. 登记心跳。超过窗口未续期,服务端撤掉你名下全部挂单(cancel-on-disconnect)
call("POST", "/v1/px/heartbeats")
open_ids = []
try:
while True:
# 2. 撤掉上一轮的报价。单市场撤单比逐张撤便宜,也避免撤单频率打满
if open_ids:
call("DELETE", "/v1/px/orders/by-market", {"marketId": MARKET})
open_ids = []
# 3. 双边批量报价。逐张成败,results 按请求顺序返回
res = call("POST", "/v1/px/orders/batch", {"orders": [
{"marketId": MARKET, "outcome": "A", "side": "BUY", "orderType": "LIMIT",
"limitPriceE6": "520000", "quantityE6": "100000000", "tif": "GTC",
"clientOrderId": f"bid-{int(time.time())}"},
{"marketId": MARKET, "outcome": "A", "side": "SELL", "orderType": "LIMIT",
"limitPriceE6": "540000", "quantityE6": "100000000", "tif": "GTC",
"clientOrderId": f"ask-{int(time.time())}"},
]})
if res:
open_ids = [r["order"]["orderId"] for r in res["results"] if r["ok"]]
for r in res["results"]:
if not r["ok"]:
print("rejected:", r["code"], r["message"])
# 4. 续心跳,频率取窗口的一半以内,给网络抖动留余量
call("POST", "/v1/px/heartbeats")
time.sleep(5)
finally:
# 5. 收工:kill switch 撤掉全部挂单,再注销心跳登记(注销本身不触发全撤)
call("DELETE", "/v1/px/orders/all")
call("DELETE", "/v1/px/heartbeats")
刻意不做的事:没有重连退避、没有盘口订阅、没有风险敞口检查。这三样与你的策略耦合,抄一份通用实现进去只会误导。
每个端点有独立页面,含行为规则、鉴权、请求与响应示例。
| 端点 | 路径 | 作用域 | 说明 |
|---|---|---|---|
| 批量下单 | POST /v1/px/orders/batch | place | 一次提交多张订单。 |
| 批量撤单 | DELETE /v1/px/orders | cancel | 按订单 id 撤单。 |
| 按市场撤单 | DELETE /v1/px/orders/by-market | cancel | 撤掉本人在指定市场的全部挂单。 |
| 撤全部(kill switch) | DELETE /v1/px/orders/all | cancel | 撤掉本人在全部市场的全部挂单。 |
| 心跳(cancel-on-disconnect) | POST /v1/px/heartbeats | cancel | 定期发送心跳。超时未收到时,服务端撤掉本人全部挂单。 |
| 注销心跳 | DELETE /v1/px/heartbeats | cancel | 取消心跳登记。不撤挂单。 |
| 单笔下单 | POST /v1/px/orders | place | 提交一张订单。 |
| 单笔撤单 | DELETE /v1/px/orders/:orderId | cancel | 撤掉一张订单。 |
| 我的挂单 | GET /v1/px/orders | read | 返回本人当前全部挂单。 |
GET /v1/px/markets/:marketId。GET /v1/px/orders 同步挂单,再消费新事件。POST /v1/px/orders/batch 挂价格档,每批不超过 15 张。DELETE /v1/px/orders/all 撤全部,再 DELETE /v1/px/heartbeats 注销心跳。| 状态 | code | 何时返回 |
|---|---|---|
| 400 | BATCH_TOO_LARGE | 批量条数超过上限 |
| 400 | INVALID_PRICE | 价格不整除市场 tick |
| 400 | INVALID_QUANTITY | 数量低于下限或非法 |
| 400 | INSUFFICIENT_BALANCE | 可用余额不足以冻结 |
| 400 | MARKET_NOT_OPEN | 市场未开放或已结束 |
| 404 | ORDER_NOT_FOUND | 订单不存在或不属于本人(不区分) |
| 409 | ORDER_NOT_LIVE | 订单已终态,不可再撤 |
| 409 | HEARTBEAT_ID_MISMATCH | 心跳 ID 不符;响应附服务端期望的 ID |
| 429 | RATE_LIMITED | 桶内代币不足;Retry-After 给出等待秒数 |
| 503 | MARKET_DEGRADED | 镜像盘口陈旧,拒绝成交而不是按旧价成交 |
| 401 | API_KEY_INVALID / API_KEY_REVOKED | key 无效或已吊销 |
| 403 | FORBIDDEN | key 作用域不足(如用 read key 下单) |
批量下单里单张订单的拒绝原因用同一套 code,出现在 results[i].code;批量撤单的逐 id 原因出现在 notCanceled。