Thị trường dự đoán là một trong những lĩnh vực đòi hỏi kỹ thuật cao nhất để xây dựng API. Bạn phải xử lý các công cụ tài chính có thời hạn, xác suất được định giá theo thời gian thực, sự kiện đa kết quả với các mối quan hệ vốn phức tạp, cùng một nhóm người dùng gồm cả người dùng giao diện và bot giao dịch tự động. Mọi quyết định thiết kế đều nhanh chóng được kiểm tra dưới tải thực tế.
Polymarket, nền tảng thị trường dự đoán lớn nhất thế giới xét theo khối lượng, đã xây dựng một hệ sinh thái API đáng để nghiên cứu. Đây không chỉ là API CRUD trên cơ sở dữ liệu, mà là kiến trúc phân lớp để cân bằng giữa tính mở và bảo mật, dữ liệu thời gian thực và lịch sử, cũng như các mô hình tài chính truyền thống với nguyên thủy crypto.
Dưới đây là tám mẫu thiết kế có thể áp dụng khi xây dựng API cho hệ thống giao dịch hoặc dữ liệu thời gian thực.
Mẫu 1: Phân tách API theo miền nghiệp vụ
Polymarket cung cấp ba API, mỗi API phục vụ một miền rõ ràng:
-
Gamma API (
gamma-api.polymarket.com) — khám phá thị trường, sự kiện, thẻ và tìm kiếm. -
CLOB API (
clob.polymarket.com) — sổ lệnh, giá và đặt lệnh. -
Data API (
data-api.polymarket.com) — vị thế người dùng, giao dịch, phân tích và bảng xếp hạng.
Đây không chỉ là cách đặt tên. Mỗi API có yêu cầu xác thực, tần suất cập nhật và nhóm người dùng khác nhau:
| API | Mục đích chính | Xác thực |
|---|---|---|
| Gamma | Duyệt và khám phá thị trường | Công khai |
| CLOB | Đọc sổ lệnh và giao dịch | Công khai khi đọc, xác thực khi ghi |
| Data | Truy vấn dữ liệu theo ví | Công khai, định danh bằng địa chỉ ví |
Thay vì gom tất cả vào một API như /markets, /orders, /users, hãy bắt đầu bằng câu hỏi:
API này được dùng để làm gì?
Ví dụ:
- Khám phá cần truy vấn linh hoạt, cache tốt và độ trễ vừa phải.
- Giao dịch cần độ trễ thấp, xác thực chặt chẽ và xử lý lỗi chính xác.
- Phân tích cần dữ liệu lịch sử, phân trang và truy vấn theo người dùng.
Việc tách base URL cho từng miền cho phép từng API mở rộng, triển khai và áp dụng chính sách xác thực độc lập.
Mẫu 2: Ưu tiên truy cập dữ liệu công khai
Dữ liệu thị trường như giá, sổ lệnh, siêu dữ liệu sự kiện và lịch sử giao dịch đều có thể truy cập công khai:
curl "https://gamma-api.polymarket.com/events?limit=5"
Không cần API key, OAuth hay bước đăng nhập trước khi đọc dữ liệu.
Thiết kế này phù hợp khi giá trị của nền tảng tăng theo số lượng người có thể:
- theo dõi thị trường;
- xây dashboard;
- làm bot phân tích;
- tích hợp dữ liệu vào sản phẩm khác.
Khi thiết kế API, hãy tách rõ quyền đọc và quyền ghi:
GET /markets → công khai
GET /markets/{id}/book → công khai
POST /orders → yêu cầu xác thực
DELETE /orders/{id} → yêu cầu xác thực
Nguyên tắc thực tế:
- Nếu dữ liệu không nhạy cảm, đừng tạo rào cản không cần thiết.
- Chỉ yêu cầu xác thực tại điểm người dùng thực hiện hành động có rủi ro, chẳng hạn đặt hoặc hủy lệnh.
- Thiết kế rate limit riêng cho endpoint đọc và ghi thay vì áp dụng cùng một chính sách cho tất cả.
Mẫu 3: Xác thực hai cấp độ theo mức độ tin cậy
Các endpoint giao dịch yêu cầu xác thực, nhưng Polymarket sử dụng hai cấp độ với mục đích khác nhau.
Xác thực cấp 1 (L1) sử dụng chữ ký EIP-712 từ khóa riêng của người dùng. Mục tiêu là chứng minh quyền sở hữu ví và tạo thông tin xác thực API:
// L1: Dùng khóa riêng để tạo thông tin xác thực API
const credentials = await client.createOrDeriveApiKey();
// → { key: "...", secret: "...", passphrase: "..." }
Xác thực cấp 2 (L2) dùng HMAC-SHA256 với thông tin xác thực đã tạo. Đây là lớp xác thực được gửi trong mọi yêu cầu giao dịch:
// Header L2 cho mọi yêu cầu giao dịch
{
"POLY_ADDRESS": "0x...",
"POLY_SIGNATURE": "<hmac-sha256>",
"POLY_TIMESTAMP": "1716000000",
"POLY_API_KEY": "550e8400-...",
"POLY_PASSPHRASE": "..."
}
Có thể áp dụng tư duy này ngoài crypto:
| Cấp độ | Câu hỏi cần trả lời | Tần suất |
|---|---|---|
| L1 | “Bạn thực sự là ai?” | Không thường xuyên |
| L2 | “Yêu cầu này có thực sự đến từ bạn?” | Mọi request |
Trong một hệ thống thông thường, L1 có thể là đăng nhập bằng hardware key, chữ ký số hoặc MFA mạnh. L2 có thể là access token ngắn hạn hoặc request signature.
Điểm quan trọng: không nên yêu cầu người dùng thực hiện thao tác xác thực mạnh nhất cho mọi request. Hãy dùng nó để thiết lập lòng tin, sau đó dùng thông tin xác thực phiên cho luồng tần suất cao.
Mẫu 4: Xem lệnh là tin nhắn đã ký, không chỉ là API request
Khi đặt lệnh trên Polymarket, client không đơn thuần gửi dữ liệu đến server. Nó tạo một tin nhắn được ký mật mã, đại diện cho một cam kết tài chính có thể thực thi:
const response = await client.createAndPostOrder(
{
tokenID: "71321045679...",
price: 0.65,
size: 100,
side: Side.BUY,
},
{
tickSize: "0.01",
negRisk: false,
},
OrderType.GTC
);
Bên dưới, SDK xây dựng dữ liệu dạng EIP-712, ký bằng khóa riêng rồi gửi chữ ký kèm lệnh. Công cụ khớp lệnh hoạt động ngoài chuỗi, nhưng giao dịch khớp được thanh toán trên Polygon bằng các chữ ký đó.
Điều này thay đổi ngữ nghĩa của API request:
POST /orders
Thông thường:
"Vui lòng thực hiện hành động này thay tôi."
Với payload được ký:
"Đây là ủy quyền mật mã cho hành động này."
Mẫu này hữu ích khi payload cần tự mang bằng chứng ủy quyền, ví dụ:
- lệnh tài chính;
- phê duyệt tài liệu pháp lý;
- yêu cầu chuyển tài sản;
- thao tác quản trị có rủi ro cao.
Lợi ích là khả năng xác minh và chống chối bỏ không phụ thuộc hoàn toàn vào lớp transport.
Mẫu 5: Mô hình dữ liệu phải thể hiện quan hệ khái niệm
Polymarket tổ chức dữ liệu quanh hai đối tượng: Sự kiện (Event) và Thị trường (Market).
- Sự kiện là một câu hỏi tổng quát, ví dụ: “Ai sẽ thắng cuộc đua Thượng viện Hoa Kỳ năm 2026 tại Pennsylvania?”
- Thị trường là một kết quả nhị phân có thể giao dịch trong sự kiện đó, ví dụ: “Liệu Bob Casey có thắng không?”
Một sự kiện có thể chứa nhiều thị trường:
{
"id": "501",
"title": "Cuộc đua Thượng viện Pennsylvania 2026",
"negRisk": true,
"markets": [
{
"id": "2301",
"question": "Liệu Bob Casey có thắng không?",
"outcomePrices": "[\"0.42\", \"0.58\"]"
},
{
"id": "2302",
"question": "Liệu Dave McCormick có thắng không?",
"outcomePrices": "[\"0.35\", \"0.65\"]"
},
{
"id": "2303",
"question": "Liệu một ứng cử viên thứ ba có thắng không?",
"outcomePrices": "[\"0.23\", \"0.77\"]"
}
]
}
Khi tích hợp, đừng chỉ lấy giá mà bỏ qua cấu trúc dữ liệu:
const yesPrice = outcomePrices[0];
const noPrice = outcomePrices[1];
Ở đây, thứ tự chỉ mục là một phần của hợp đồng API: outcomes[0] tương ứng với outcomePrices[0].
Bài học là không làm phẳng các quan hệ có ảnh hưởng đến hành vi nghiệp vụ. Nếu một thuộc tính như negRisk thay đổi cách tính vị thế hoặc đặt lệnh, nó phải xuất hiện trực tiếp trong response và type của SDK.
Mẫu 6: Đưa bất biến nghiệp vụ thành trường API có kiểu
Cờ negRisk đánh dấu một loại sự kiện đa kết quả, trong đó chỉ có đúng một kết quả thắng.
Trong sự kiện này tồn tại quan hệ vốn:
1 mã thông báo “Không” cho kết quả A ≡ 1 mã thông báo “Có” cho mọi kết quả còn lại
Ví dụ:
| Trước | Sau |
|---|---|
| 1× Không (Khác) | 1× Có (Casey) + 1× Có (McCormick) |
API thể hiện quy tắc này trực tiếp bằng negRisk: true trong dữ liệu thị trường và trong tùy chọn tạo lệnh.
Khi xây bot, hãy kiểm tra cờ này trước khi tính toán vị thế hoặc tạo order:
function buildOrderOptions(market: { negRisk: boolean; tickSize: string }) {
return {
tickSize: market.tickSize,
negRisk: market.negRisk,
};
}
Đừng để các ràng buộc quan trọng chỉ nằm trong tài liệu. Nếu bỏ qua một quy tắc có thể tạo ra hành vi sai, API nên buộc client phải xử lý quy tắc đó qua schema, enum, field hoặc validation rõ ràng.
Mẫu 7: Xem tick size động là trạng thái thị trường
Trong nhiều API tài chính, kích thước bước giá là cấu hình tĩnh. Polymarket xử lý nó như trạng thái động của thị trường.
Khi giá tiến gần cực trị — trên 0.96 hoặc dưới 0.04 — tick size tối thiểu giảm từ 0.01 xuống 0.001:
{
"event_type": "tick_size_change",
"asset_id": "65818619657...",
"old_tick_size": "0.01",
"new_tick_size": "0.001",
"timestamp": "100000000"
}
Điều này quan trọng vì ở mức giá cực thấp, thay đổi từ 0.04 sang 0.03 tương đương thay đổi 25%. Tick size nhỏ hơn giúp biểu diễn xác suất chính xác hơn, chẳng hạn 0.973 thay vì làm tròn thành 0.97.
Nếu bot hard-code tick size, lệnh có thể bị từ chối. Thay vào đó, hãy lưu tick size theo asset_id và cập nhật từ WebSocket:
const tickSizes = new Map<string, string>();
function onTickSizeChange(event: {
asset_id: string;
new_tick_size: string;
}) {
tickSizes.set(event.asset_id, event.new_tick_size);
}
function getTickSize(assetId: string) {
const tickSize = tickSizes.get(assetId);
if (!tickSize) {
throw new Error(`Chưa có tick size cho asset ${assetId}`);
}
return tickSize;
}
Nguyên tắc áp dụng rộng hơn:
- Quy tắc thị trường không phải lúc nào cũng tĩnh.
- Client cần nhận được thay đổi trạng thái qua event, không chỉ phát hiện sau khi request thất bại.
- Những giá trị ảnh hưởng đến validation lệnh phải được cập nhật theo thời gian thực.
Mẫu 8: Tách WebSocket theo nhóm người dùng và yêu cầu độ trễ
Polymarket vận hành hai hệ thống WebSocket riêng.
Kênh Thị trường (wss://ws-subscriptions-clob.polymarket.com/ws/market) phục vụ người dùng giao dịch. Client đăng ký theo token ID để nhận ảnh chụp sổ lệnh, thay đổi giá, giao dịch khớp và thay đổi tick size:
{
"assets_ids": [
"65818619657568813474341868652308942079804919287380422192892211131408793125422"
],
"type": "market"
}
Socket Dữ liệu Thời gian thực (wss://ws-live-data.polymarket.com) phục vụ nhu cầu khác: bình luận, giá crypto từ Binance và Chainlink, giá cổ phiếu và tín hiệu hoạt động xã hội.
{
"action": "subscribe",
"subscriptions": [
{
"topic": "crypto_prices",
"type": "update",
"filters": "btcusdt,ethusd"
}
]
}
Hai luồng này có đặc tính vận hành khác nhau:
| Đặc tính | Market Channel | Real-Time Data Socket |
|---|---|---|
| Người dùng chính | Trader, market maker, bot | UI, dashboard, feed |
| Dữ liệu | Sổ lệnh, giá, giao dịch | Bình luận, giá tham chiếu, hoạt động |
| Độ trễ | Rất thấp | Thấp nhưng không cần cấp giao dịch |
| Mô hình đăng ký | Theo asset ID | Theo topic |
Nếu gộp hai nhu cầu này vào một WebSocket, bạn thường phải đánh đổi một trong hai phía:
- hoặc thiết kế feed xã hội phức tạp như hệ thống giao dịch;
- hoặc làm luồng sổ lệnh thiếu độ tin cậy và độ trễ cần thiết.
Khi thiết kế realtime API, hãy phân loại consumer trước:
- Họ cần độ trễ đến mức nào?
- Họ cần xử lý bao nhiêu message mỗi giây?
- Nếu mất một event, hậu quả là gì?
- Họ subscribe theo entity, topic hay luồng dữ liệu tổng hợp?
Nếu câu trả lời khác nhau đáng kể, nên tách hạ tầng hoặc ít nhất tách protocol và channel.
Những điểm chung của các mẫu này
Thiết kế API của Polymarket cho thấy một nguyên tắc nhất quán:
API nên làm lộ cấu trúc thật của miền nghiệp vụ, thay vì che giấu nó bằng một abstraction quá đơn giản.
Các quyết định thiết kế đều phản ánh điều đó:
- Ba lớp API ánh xạ tới ba miền nghiệp vụ khác nhau.
- Endpoint đọc công khai phản ánh nhu cầu tiêu thụ dữ liệu thị trường ở quy mô lớn.
- Xác thực hai cấp độ tách việc chứng minh danh tính khỏi xác thực request thường xuyên.
- Lệnh được ký biến payload thành một cam kết có thể xác minh.
- Quan hệ Event/Market và
negRiskthể hiện trực tiếp mô hình vốn. - Tick size động được phát như trạng thái thời gian thực.
- WebSocket được phân tách theo nhu cầu về độ trễ và dữ liệu.
Khi xây API cho một miền phức tạp, đừng chỉ tối ưu cho tên endpoint nhất quán hoặc payload dễ gọi. Hãy xác định:
- bất biến nghiệp vụ nào phải được thực thi;
- trạng thái nào có thể thay đổi theo thời gian;
- quan hệ nào client không được phép bỏ qua;
- nhóm người dùng nào có yêu cầu vận hành khác nhau.
Một API tốt không chỉ giúp consumer gọi được endpoint. Nó giúp consumer xây đúng hệ thống trên nền tảng đó.
Top comments (0)