orderly-websocket-streaming
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOrderly Network: WebSocket Streaming
Orderly Network:WebSocket 数据流
This skill covers establishing WebSocket connections, authentication, and subscribing to real-time data streams on Orderly Network.
本技能介绍如何在Orderly Network上建立WebSocket连接、完成身份验证并订阅实时数据流。
When to Use
适用场景
- Building real-time trading interfaces
- Streaming orderbook data
- Receiving execution notifications
- Monitoring position updates
- 构建实时交易界面
- 流式传输订单簿数据
- 接收执行通知
- 监控仓位更新
Prerequisites
前置条件
- Orderly account ID
- Ed25519 private key for signing
- WebSocket client library (native WebSocket, socket.io, etc.)
- Orderly 账户ID
- 用于签名的Ed25519私钥
- WebSocket客户端库(原生WebSocket、socket.io等)
WebSocket Endpoints
WebSocket端点
| Environment | URL |
|---|---|
| Mainnet Public | |
| Mainnet Private | |
| Testnet Public | |
| Testnet Private | |
| 环境 | URL |
|---|---|
| 主网公开环境 | |
| 主网私有环境 | |
| 测试网公开环境 | |
| 测试网私有环境 | |
Connection Flow
连接流程
1. Connect to WebSocket endpoint
2. Authenticate (for private streams)
3. Subscribe to topics
4. Receive real-time messages
5. Handle reconnection/heartbeat1. 连接到WebSocket端点
2. 完成身份验证(私有数据流)
3. 订阅主题
4. 接收实时消息
5. 处理重连/心跳机制Public Streams (No Authentication)
公开数据流(无需身份验证)
Connect and Subscribe
连接与订阅
typescript
const ws = new WebSocket('wss://ws.orderly.org/ws/stream');
ws.onopen = () => {
// Subscribe to orderbook
ws.send(
JSON.stringify({
id: 'sub_ob_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@orderbook',
})
);
// Subscribe to trades
ws.send(
JSON.stringify({
id: 'sub_trades_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@trade',
})
);
// Subscribe to 24h ticker
ws.send(
JSON.stringify({
id: 'sub_ticker_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@24hrTicker',
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};typescript
const ws = new WebSocket('wss://ws.orderly.org/ws/stream');
ws.onopen = () => {
// 订阅订单簿
ws.send(
JSON.stringify({
id: 'sub_ob_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@orderbook',
})
);
// 订阅交易数据
ws.send(
JSON.stringify({
id: 'sub_trades_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@trade',
})
);
// 订阅24小时行情
ws.send(
JSON.stringify({
id: 'sub_ticker_1',
event: 'subscribe',
topic: 'PERP_ETH_USDC@24hrTicker',
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};Available Public Topics
可用的公开主题
| Topic | Description |
|---|---|
| Full orderbook snapshot |
| Incremental updates |
| Trade executions |
| Kline/candlestick data |
| Mark price updates |
| Index price updates |
| 24h statistics |
| All symbols mark prices |
| All symbols index prices |
| 主题 | 描述 |
|---|---|
| 完整订单簿快照 |
| 增量更新 |
| 交易执行数据 |
| K线/蜡烛图数据 |
| 标记价格更新 |
| 指数价格更新 |
| 24小时统计数据 |
| 所有交易对的标记价格 |
| 所有交易对的指数价格 |
Orderbook Stream
订单簿数据流
typescript
interface OrderbookMessage {
topic: string;
ts: number; // Timestamp
data: {
asks: [string, string][]; // [price, quantity]
bids: [string, string][];
prevTs: number; // Previous timestamp for sequencing
};
}
// Subscribe
{ "event": "subscribe", "topic": "PERP_ETH_USDC@orderbookupdate" }
// Maintain local orderbook
let orderbook = { asks: [], bids: [] };
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.topic?.includes('orderbookupdate')) {
// Apply incremental updates
for (const [price, qty] of msg.data.asks) {
updateLevel(orderbook.asks, price, qty);
}
for (const [price, qty] of msg.data.bids) {
updateLevel(orderbook.bids, price, qty);
}
}
};
function updateLevel(levels: [string, string][], price: string, qty: string) {
const idx = levels.findIndex(l => l[0] === price);
if (qty === '0') {
// Remove level
if (idx !== -1) levels.splice(idx, 1);
} else {
// Update or insert
if (idx !== -1) {
levels[idx] = [price, qty];
} else {
levels.push([price, qty]);
// Sort (asks ascending, bids descending)
levels.sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
}
}
}typescript
interface OrderbookMessage {
topic: string;
ts: number; // 时间戳
data: {
asks: [string, string][]; // [价格, 数量]
bids: [string, string][];
prevTs: number; // 用于排序的上一个时间戳
};
}
// 订阅
{ "event": "subscribe", "topic": "PERP_ETH_USDC@orderbookupdate" }
// 维护本地订单簿
let orderbook = { asks: [], bids: [] };
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.topic?.includes('orderbookupdate')) {
// 应用增量更新
for (const [price, qty] of msg.data.asks) {
updateLevel(orderbook.asks, price, qty);
}
for (const [price, qty] of msg.data.bids) {
updateLevel(orderbook.bids, price, qty);
}
}
};
function updateLevel(levels: [string, string][], price: string, qty: string) {
const idx = levels.findIndex(l => l[0] === price);
if (qty === '0') {
// 删除该档位
if (idx !== -1) levels.splice(idx, 1);
} else {
// 更新或插入档位
if (idx !== -1) {
levels[idx] = [price, qty];
} else {
levels.push([price, qty]);
// 排序(卖单升序,买单降序)
levels.sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
}
}
}Kline Stream
K线数据流
typescript
// Available intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
ws.send(JSON.stringify({
id: 'sub_kline',
event: 'subscribe',
topic: 'PERP_ETH_USDC@kline_1m',
}));
// Message format
{
"topic": "PERP_ETH_USDC@kline_1m",
"ts": 1699123456789,
"data": {
"t": 1699123440000, // Kline start time
"o": "3000.00", // Open
"h": "3010.00", // High
"l": "2995.00", // Low
"c": "3005.00", // Close
"v": "123.45", // Volume
"qv": "370350.00" // Quote volume
}
}typescript
// 可用时间间隔:1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
ws.send(JSON.stringify({
id: 'sub_kline',
event: 'subscribe',
topic: 'PERP_ETH_USDC@kline_1m',
}));
// 消息格式
{
"topic": "PERP_ETH_USDC@kline_1m",
"ts": 1699123456789,
"data": {
"t": 1699123440000, // K线开始时间
"o": "3000.00", // 开盘价
"h": "3010.00", // 最高价
"l": "2995.00", // 最低价
"c": "3005.00", // 收盘价
"v": "123.45", // 成交量
"qv": "370350.00" // 成交额
}
}Private Streams (Authentication Required)
私有数据流(需身份验证)
Authentication
身份验证
typescript
import { signAsync } from '@noble/ed25519';
const accountId = 'your_account_id';
const privateKey = new Uint8Array(32); // Your Ed25519 private key
const ws = new WebSocket(`wss://ws-private-evm.orderly.org/v2/ws/private/stream/${accountId}`);
ws.onopen = async () => {
const timestamp = Date.now();
const message = timestamp.toString();
const signature = await signAsync(new TextEncoder().encode(message), privateKey);
// Encode as base64url (browser & Node.js compatible)
const base64 = btoa(String.fromCharCode(...signature));
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
// Send authentication
ws.send(
JSON.stringify({
id: 'auth_1',
event: 'auth',
params: {
orderly_key: `ed25519:${publicKeyBase58}`,
sign: base64url,
timestamp: timestamp,
},
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'auth' && data.success) {
console.log('Authenticated successfully');
// Now subscribe to private topics
subscribePrivateTopics();
}
};typescript
import { signAsync } from '@noble/ed25519';
const accountId = 'your_account_id';
const privateKey = new Uint8Array(32); // 你的Ed25519私钥
const ws = new WebSocket(`wss://ws-private-evm.orderly.org/v2/ws/private/stream/${accountId}`);
ws.onopen = async () => {
const timestamp = Date.now();
const message = timestamp.toString();
const signature = await signAsync(new TextEncoder().encode(message), privateKey);
// 编码为base64url(兼容浏览器和Node.js)
const base64 = btoa(String.fromCharCode(...signature));
const base64url = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
// 发送身份验证请求
ws.send(
JSON.stringify({
id: 'auth_1',
event: 'auth',
params: {
orderly_key: `ed25519:${publicKeyBase58}`,
sign: base64url,
timestamp: timestamp,
},
})
);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'auth' && data.success) {
console.log('身份验证成功');
// 现在订阅私有主题
subscribePrivateTopics();
}
};Private Topics
私有主题
| Topic | Description |
|---|---|
| Order status updates |
| Position changes |
| Balance updates |
| Liquidation warnings |
| Wallet/deposit updates |
| Account updates |
| Settlement updates |
| General notifications |
| 主题 | 描述 |
|---|---|
| 订单状态更新 |
| 仓位变更 |
| 余额更新 |
| 强平预警 |
| 钱包/存款更新 |
| 账户信息更新 |
| 结算更新 |
| 通用通知 |
Execution Report Stream
执行报告数据流
typescript
function subscribePrivateTopics() {
// Subscribe to order updates
ws.send(
JSON.stringify({
id: 'sub_exec',
event: 'subscribe',
topic: 'executionreport',
// Optional: filter by symbol
params: { symbol: 'PERP_ETH_USDC' },
})
);
}
// Message format
interface ExecutionReport {
topic: 'executionreport';
ts: number;
data: {
orderId: string;
clientOrderId?: string;
symbol: string;
side: 'BUY' | 'SELL';
orderType: string;
status: 'NEW' | 'PARTIAL_FILLED' | 'FILLED' | 'CANCELLED' | 'REJECTED';
price: string;
quantity: string;
executedQty: string;
avgPrice?: string;
leavesQty: string;
lastExecutedPrice?: string;
lastExecutedQty?: string;
fee?: string;
feeToken?: string;
reason?: string;
};
}typescript
function subscribePrivateTopics() {
// 订阅订单更新
ws.send(
JSON.stringify({
id: 'sub_exec',
event: 'subscribe',
topic: 'executionreport',
// 可选:按交易对过滤
params: { symbol: 'PERP_ETH_USDC' },
})
);
}
// 消息格式
interface ExecutionReport {
topic: 'executionreport';
ts: number;
data: {
orderId: string;
clientOrderId?: string;
symbol: string;
side: 'BUY' | 'SELL';
orderType: string;
status: 'NEW' | 'PARTIAL_FILLED' | 'FILLED' | 'CANCELLED' | 'REJECTED';
price: string;
quantity: string;
executedQty: string;
avgPrice?: string;
leavesQty: string;
lastExecutedPrice?: string;
lastExecutedQty?: string;
fee?: string;
feeToken?: string;
reason?: string;
};
}Position Stream
仓位数据流
typescript
ws.send(JSON.stringify({
id: 'sub_position',
event: 'subscribe',
topic: 'position',
}));
// Message format
{
"topic": "position",
"ts": 1699123456789,
"data": {
"symbol": "PERP_ETH_USDC",
"positionQty": 0.5,
"averageOpenPrice": 3000,
"markPrice": 3100,
"unrealizedPnl": 50,
"leverage": 10,
"liqPrice": 2700,
"mmr": 0.01,
"imr": 0.02
}
}typescript
ws.send(JSON.stringify({
id: 'sub_position',
event: 'subscribe',
topic: 'position',
}));
// 消息格式
{
"topic": "position",
"ts": 1699123456789,
"data": {
"symbol": "PERP_ETH_USDC",
"positionQty": 0.5,
"averageOpenPrice": 3000,
"markPrice": 3100,
"unrealizedPnl": 50,
"leverage": 10,
"liqPrice": 2700,
"mmr": 0.01,
"imr": 0.02
}
}Balance Stream
余额数据流
typescript
ws.send(JSON.stringify({
id: 'sub_balance',
event: 'subscribe',
topic: 'balance',
}));
// Message format
{
"topic": "balance",
"ts": 1699123456789,
"data": {
"token": "USDC",
"total": "10000.00",
"free": "8000.00",
"locked": "2000.00",
"unsettledPnl": "500.00"
}
}typescript
ws.send(JSON.stringify({
id: 'sub_balance',
event: 'subscribe',
topic: 'balance',
}));
// 消息格式
{
"topic": "balance",
"ts": 1699123456789,
"data": {
"token": "USDC",
"total": "10000.00",
"free": "8000.00",
"locked": "2000.00",
"unsettledPnl": "500.00"
}
}React SDK: Built-in Streaming
React SDK:内置数据流支持
The Orderly SDK handles WebSocket connections automatically:
typescript
import {
useOrderbookStream,
usePositionStream,
useOrderStream,
useBalance
} from '@orderly.network/hooks';
function TradingInterface({ symbol }: { symbol: string }) {
// Orderbook - automatically connects to WebSocket
const { asks, bids } = useOrderbookStream(symbol);
// Positions - real-time updates
const { rows: positions } = usePositionStream();
// Orders - real-time updates
const [orders] = useOrderStream({ status: OrderStatus.INCOMPLETE });
// Balance
const balance = useBalance();
return (
<div>
<OrderbookWidget asks={asks} bids={bids} />
<PositionsTable positions={positions} />
<OrdersTable orders={orders} />
<BalanceDisplay balance={balance} />
</div>
);
}Orderly SDK会自动处理WebSocket连接:
typescript
import {
useOrderbookStream,
usePositionStream,
useOrderStream,
useBalance
} from '@orderly.network/hooks';
function TradingInterface({ symbol }: { symbol: string }) {
// 订单簿 - 自动连接到WebSocket
const { asks, bids } = useOrderbookStream(symbol);
// 仓位 - 实时更新
const { rows: positions } = usePositionStream();
// 订单 - 实时更新
const [orders] = useOrderStream({ status: OrderStatus.INCOMPLETE });
// 余额
const balance = useBalance();
return (
<div>
<OrderbookWidget asks={asks} bids={bids} />
<PositionsTable positions={positions} />
<OrdersTable orders={orders} />
<BalanceDisplay balance={balance} />
</div>
);
}Connection Management
连接管理
Heartbeat
心跳机制
typescript
// Orderly sends ping, you respond with pong
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ping') {
ws.send(JSON.stringify({ event: 'pong' }));
}
};
// Or use native WebSocket ping/pong
ws.onopen = () => {
// Some clients need explicit ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ event: 'ping' }));
}
}, 30000);
};typescript
// Orderly发送ping,你需要回复pong
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event === 'ping') {
ws.send(JSON.stringify({ event: 'pong' }));
}
};
// 或使用原生WebSocket的ping/pong
ws.onopen = () => {
// 部分客户端需要显式发送ping
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ event: 'ping' }));
}
}, 30000);
};Reconnection
重连逻辑
typescript
class OrderlyWebSocket {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private subscriptions: string[] = [];
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
this.authenticate();
this.resubscribe();
};
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(
() => {
this.reconnectAttempts++;
this.connect();
},
Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
);
}
};
}
private resubscribe() {
for (const topic of this.subscriptions) {
this.ws?.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
subscribe(topic: string) {
this.subscriptions.push(topic);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
}typescript
class OrderlyWebSocket {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private subscriptions: string[] = [];
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
this.authenticate();
this.resubscribe();
};
this.ws.onclose = () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(
() => {
this.reconnectAttempts++;
this.connect();
},
Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000)
);
}
};
}
private resubscribe() {
for (const topic of this.subscriptions) {
this.ws?.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
subscribe(topic: string) {
this.subscriptions.push(topic);
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(
JSON.stringify({
event: 'subscribe',
topic,
})
);
}
}
}Error Handling
错误处理
typescript
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.success === false) {
console.error('WebSocket error:', data.errorMsg);
switch (data.errorMsg) {
case 'invalid symbol':
// Handle invalid symbol
break;
case 'authentication failed':
// Re-authenticate
break;
case 'rate limit exceeded':
// Slow down subscription requests
break;
}
}
};
// Error response format
{
"id": "sub_ob_1",
"event": "subscribe",
"success": false,
"errorMsg": "invalid symbol"
}typescript
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.success === false) {
console.error('WebSocket错误:', data.errorMsg);
switch (data.errorMsg) {
case 'invalid symbol':
// 处理无效交易对
break;
case 'authentication failed':
// 重新进行身份验证
break;
case 'rate limit exceeded':
// 降低订阅请求频率
break;
}
}
};
// 错误响应格式
{
"id": "sub_ob_1",
"event": "subscribe",
"success": false,
"errorMsg": "invalid symbol"
}Rate Limits
速率限制
| Action | Limit |
|---|---|
| Subscriptions per connection | 100 |
| Messages per second | 50 |
| Connection per IP | 10 |
| 操作 | 限制 |
|---|---|
| 单连接订阅数 | 100 |
| 每秒消息数 | 50 |
| 单IP连接数 | 10 |
Common Issues
常见问题
"Authentication failed" error
"Authentication failed" 错误
- Check timestamp is current (not future/past)
- Verify signature encoding (base64url)
- Ensure orderly_key format is
ed25519:{publicKey}
- 检查时间戳是否为当前时间(不能是未来或过去太久)
- 验证签名编码格式(base64url)
- 确保orderly_key格式为
ed25519:{publicKey}
Connection drops frequently
连接频繁断开
- Implement heartbeat/pong
- Check network stability
- Use reconnection logic
- 实现心跳/pong机制
- 检查网络稳定性
- 使用重连逻辑
Missing messages
消息丢失
- Check if subscription was successful
- Verify topic name format
- Monitor for error messages
- 检查订阅是否成功
- 验证主题名称格式
- 监控错误消息
Orderbook out of sync
订单簿不同步
- Use to detect missed messages
prevTs - Re-subscribe to get fresh snapshot
- Implement sequence validation
- 使用检测丢失的消息
prevTs - 重新订阅以获取最新快照
- 实现序列验证
Related Skills
相关技能
- orderly-api-authentication - Ed25519 signing details
- orderly-trading-orders - Order management
- orderly-positions-tpsl - Position monitoring
- orderly-sdk-react-hooks - SDK streaming hooks
- orderly-api-authentication - Ed25519签名详细说明
- orderly-trading-orders - 订单管理
- orderly-positions-tpsl - 仓位监控
- orderly-sdk-react-hooks - SDK数据流钩子