前端交易架构设计
详细的交易页面架构设计,涵盖 WebSocket 实时数据流、订单簿渲染、K 线图集成、下单面板以及高性能前端优化方案。
1. 前端整体架构
组件化
每个功能区域独立组件,通过全局 Store 共享数据。组件间松耦合,可独立测试和替换。
双通道
REST API 用于下单/撤单/查询等操作;WebSocket 用于接收实时行情、订单状态、成交回报。
离线缓存
K 线历史数据缓存到 IndexedDB,避免切换周期时重复请求。订单簿增量更新减少带宽。
响应式
桌面端多面板布局;移动端 Tab 切换。使用 CSS Grid 实现自适应,支持面板拖拽调整。
2. WebSocket 连接管理
class WebSocketManager {
private ws: WebSocket | null = null;
private subscriptions: Map<string, Set<Function>> = new Map();
private reconnectAttempts = 0;
private maxReconnectAttempts = 10;
private heartbeatInterval: NodeJS.Timer;
connect(url: string) {
this.ws = new WebSocket(url);
this.ws.onopen = () => {
this.reconnectAttempts = 0;
this.resubscribeAll(); // Resubscribe after reconnect
this.startHeartbeat();
};
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
this.dispatch(msg.channel, msg.data);
};
this.ws.onclose = () => this.reconnect();
}
subscribe(channel: string, callback: Function) {
if (!this.subscriptions.has(channel)) {
this.subscriptions.set(channel, new Set());
this.send({ action: "subscribe", channel });
}
this.subscriptions.get(channel)!.add(callback);
}
unsubscribe(channel: string, callback: Function) {
this.subscriptions.get(channel)?.delete(callback);
if (this.subscriptions.get(channel)?.size === 0) {
this.send({ action: "unsubscribe", channel });
this.subscriptions.delete(channel);
}
}
private reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
const delay = Math.min(1000 * 2 ** this.reconnectAttempts, 30000);
setTimeout(() => {
this.reconnectAttempts++;
this.connect(this.url);
}, delay);
}
private startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
this.send({ action: "ping" });
}, 15000);
}
}3. WebSocket 协议设计
订阅频道列表
订单簿深度。首次推送全量快照,之后增量更新。频道: depth@BTCUSDT。包含 bids/asks 二维数组 [price, qty]。
逐笔成交。实时推送每一笔成交记录。频道: trade@BTCUSDT。包含 price, qty, side, time。
K 线更新。按订阅周期推送当前蜡烛的 OHLCV 更新。频道: kline@BTCUSDT:1m。窗口关闭时 isFinal=true。
24h 行情摘要。包含最新价、涨跌幅、24h 高低价、成交量。频道: ticker@BTCUSDT。约 1 秒推送一次。
用户订单更新(需鉴权)。推送订单状态变更、成交回报。频道: order@{userId}。包含完整订单对象。
用户账户更新(需鉴权)。余额变更、持仓变更推送。频道: account@{userId}。
消息格式
// 订阅请求
{ "action": "subscribe", "channel": "depth@BTCUSDT" }
// 订单簿全量快照(首次)
{
"channel": "depth@BTCUSDT",
"type": "snapshot",
"data": {
"lastUpdateId": 100,
"bids": [["41950.00","1.500"],["41900.00","2.300"]],
"asks": [["42000.00","0.800"],["42050.00","1.200"]]
}
}
// 订单簿增量更新
{
"channel": "depth@BTCUSDT",
"type": "update",
"data": {
"firstUpdateId": 101,
"lastUpdateId": 103,
"bids": [["41950.00","1.200"]], // qty 变更
"asks": [["42000.00","0.000"]] // qty=0 表示删除该价位
}
}
// 逐笔成交
{
"channel": "trade@BTCUSDT",
"type": "trade",
"data": { "tradeId": 1001, "price": "42000.00", "qty": "0.5",
"buyerIsMaker": false, "time": 1700000000000 }
}
// K 线更新
{
"channel": "kline@BTCUSDT:1m",
"type": "kline",
"data": { "openTime": 1700000000000, "open": "42000", "high": "42100",
"low": "41950", "close": "42050", "volume": "123.5",
"isFinal": false }
}WebSocket 消息流模拟
点击按钮模拟 WebSocket 连接、订阅、推送的完整消息流:
4. 订单簿可视化
// Local order-book maintenance logic
class LocalOrderBook {
bids: Map<string, string> = new Map(); // price → qty
asks: Map<string, string> = new Map();
lastUpdateId = 0;
applySnapshot(snapshot) {
this.bids.clear();
this.asks.clear();
snapshot.bids.forEach(([p, q]) => this.bids.set(p, q));
snapshot.asks.forEach(([p, q]) => this.asks.set(p, q));
this.lastUpdateId = snapshot.lastUpdateId;
}
applyUpdate(update) {
// Drop stale updates
if (update.lastUpdateId <= this.lastUpdateId) return;
// Detect sequence gap
if (update.firstUpdateId > this.lastUpdateId + 1) {
this.requestSnapshot(); // Request fresh full snapshot
return;
}
update.bids.forEach(([p, q]) => {
if (q === "0" || q === "0.000") this.bids.delete(p);
else this.bids.set(p, q);
});
update.asks.forEach(([p, q]) => {
if (q === "0" || q === "0.000") this.asks.delete(p);
else this.asks.set(p, q);
});
this.lastUpdateId = update.lastUpdateId;
}
// Group levels by display precision
getGrouped(precision: number, levels: number) {
const groupedBids = this.groupByPrecision(this.bids, precision, 'desc');
const groupedAsks = this.groupByPrecision(this.asks, precision, 'asc');
return {
bids: groupedBids.slice(0, levels),
asks: groupedAsks.slice(0, levels),
spread: groupedAsks[0]?.[0] - groupedBids[0]?.[0]
};
}
}盘口显示样式
┌─────────────────────────────────────┐ │ 卖盘 (Asks) - 红色 │ │ 价格 数量 累计 │ │ ████████░░ 42200.00 1.200 8.500 │ │ ███████░░░ 42150.00 2.800 7.300 │ │ █████░░░░░ 42100.00 0.500 4.500 │ │ ████░░░░░░ 42050.00 1.200 4.000 │ │ ██░░░░░░░░ 42000.00 2.800 2.800 │ ├──── Spread: 50.00 (0.12%) ──────────│ │ ██░░░░░░░░ 41950.00 1.500 1.500 │ │ ████░░░░░░ 41900.00 2.300 3.800 │ │ ██████░░░░ 41850.00 1.800 5.600 │ │ ████████░░ 41800.00 3.200 8.800 │ │ █████████░ 41750.00 2.000 10.800 │ │ 买盘 (Bids) - 绿色 │ └─────────────────────────────────────┘ 背景条: 累计量占总量的比例,直观展示流动性分布。 点击价格: 自动填入下单面板的限价字段。 点击数量: 自动填入委托数量。
5. 深度图
X 轴为价格,Y 轴为累计数量。买盘从右往左递增(绿色),卖盘从左往右递增(红色)。
默认显示 bestPrice +/- 2% 范围;支持缩放和拖拽查看更大范围的流动性分布。
订阅 depth 频道,每次增量更新后重新计算累计量并重绘。使用 Canvas 绘制确保帧率。
鼠标悬停显示对应价格的累计量和预计成交均价;点击直接填入下单面板价格。
深度图示例
下方 Canvas 渲染的深度图展示买卖盘的累计流动性分布:
6. K 线图集成
使用 TradingView Charting Library 或 Lightweight Charts。支持技术指标(MA/EMA/MACD/RSI/BOLL)、画线工具。
后端返回 [openTime, open, high, low, close, volume] 数组;前端转换为图表库需要的格式。
支持 1m/5m/15m/30m/1h/4h/1d/1w 等周期。切换时先查本地缓存,缺失部分通过 REST API 补充。
在 K 线上叠加标记层:用户的历史成交点、挂单价格线、强平价格线、MA 均线等。
K 线蜡烛图示例
下方 Canvas 渲染的 K 线图支持切换周期和悬停查看 OHLCV 详情:
7. 下单面板
┌───────────────────────────────┐ │ [限价] [市价] [止损] │ ├───────────────────────────────┤ │ ┌─ 买入 BTC ─┐ ┌─ 卖出 BTC ─┐│ │ │ (选中) │ │ ││ │ └────────────┘ └────────────┘│ ├───────────────────────────────┤ │ 价格 [ 42000.00 ] USDT │ │ 数量 [ 1.00 ] BTC │ │ │ │ [25%] [50%] [75%] [100%] │ │ │ │ 杠杆 [ 1x ▾ ] │ │ ☐ Post-Only ☐ IOC │ ├───────────────────────────────┤ │ 可用 12,500.00 USDT │ │ 总额 42,000.00 USDT │ │ 手续费 ~25.20 USDT (0.06%) │ ├───────────────────────────────┤ │ ┌─────────────────────────┐ │ │ │ 买入 BTC (绿色) │ │ │ └─────────────────────────┘ │ └───────────────────────────────┘
百分比按钮
25%/50%/75%/100% 按可用余额计算最大可买/可卖数量。考虑手续费预留,100% 时扣除预估费用。
价格联动
点击订单簿的价格行自动填入价格字段;市价单隐藏价格输入。
实时预估
输入价格和数量后实时计算:总额、预估手续费、预估滑点(市价单参考当前盘口)。
确认弹窗
大额订单或市价单可选择显示确认弹窗,展示预估成交明细,防止误操作。
8. 订单管理
展示 NEW / PARTIALLY_FILLED 状态的活跃订单。显示价格、数量、已成交量、进度条。支持单个撤单和一键全撤。
展示所有已结束订单(FILLED/CANCELED/EXPIRED)。分页加载,支持按交易对、时间范围、方向筛选。
展示个人的成交明细。每笔 Trade 显示成交价、成交量、手续费、角色(Maker/Taker)。
通过 order@{userId} 频道实时接收订单状态变更。新订单插入列表顶部,状态变更原地刷新。
┌──────────────────────────────────────────────────────────┐ │ [当前委托(3)] [历史委托] [成交记录] [持仓] │ ├──────────────────────────────────────────────────────────┤ │ 交易对 方向 价格 数量 已成交 状态 操作 │ │ BTCUSDT 买入 42000.00 2.000 0.800 进行中 [撤单]│ │ ETHUSDT 卖出 2800.00 5.000 5.000 已成交 - │ │ BTCUSDT 买入 41500.00 1.000 0.000 挂单中 [撤单]│ ├──────────────────────────────────────────────────────────┤ │ [撤销 BTCUSDT] [全部撤销] │ └──────────────────────────────────────────────────────────┘
9. 状态管理
// Zustand store example
const useMarketStore = create<MarketState>((set, get) => ({
symbol: 'BTCUSDT',
ticker: null,
orderBook: { bids: [], asks: [], spread: 0 },
precision: 2,
setSymbol: (symbol) => {
set({ symbol });
wsManager.unsubscribe(`depth@${get().symbol}`);
wsManager.subscribe(`depth@${symbol}`, (data) => {
set({ orderBook: data });
});
},
updateOrderBook: (update) => {
const book = get().localBook;
book.applyUpdate(update);
set({ orderBook: book.getGrouped(get().precision, 20) });
},
setPrecision: (precision) => {
set({ precision });
const book = get().localBook;
set({ orderBook: book.getGrouped(precision, 20) });
},
}));10. 前端性能优化
高频数据(盘口、行情)使用 requestAnimationFrame 节流。每帧最多渲染一次,避免过度重绘。WebSocket 消息先缓冲到队列,每帧批量处理。
成交列表、历史订单使用虚拟滚动(react-virtualized/react-window),只渲染可视区域的 DOM 节点,降低内存和渲染开销。
K 线数据聚合、技术指标计算放入 Web Worker,避免阻塞主线程。Worker 计算完毕后 postMessage 回主线程渲染。
订单簿只传输变化的价位(增量更新),不是每次全量。本地维护完整簿,增量 patch 后计算显示数据。
深度图和 K 线图使用 Canvas/WebGL 渲染,避免大量 DOM 操作。仅在数据变更时重绘,使用双缓冲防闪烁。
交易页面按需加载。K 线图库(TradingView ~500KB)懒加载;未使用的指标库 dynamic import。
11. 页面布局
┌─────────────────────────────────────────────────────────────────┐ │ [Header] BTCUSDT ▾ | 42,050.00 | +2.5% | H:43,000 L:41,000 │ ├──────────────────────────────┬──────────────┬────────────────────┤ │ │ │ │ │ │ 订单簿 │ 下单面板 │ │ K 线图区域 │ (盘口) │ │ │ TradingView │ │ [限价][市价][止损] │ │ 支持全屏 │ 42200 1.20 │ 价格 [42000] │ │ │ 42150 2.80 │ 数量 [1.00] │ │ │ 42100 0.50 │ [25%][50%][100%] │ │ │── 50.00 ──── │ │ │ │ 42050 1.50 │ [ 买入 BTC ] │ │ │ 42000 2.30 │ │ │ │ 41950 1.80 │ 可用: 12,500 │ │ │ │ 手续费: ~25 │ ├──────────────────────────────┴──────────────┴────────────────────┤ │ 最新成交列表 | 深度图 │ ├─────────────────────────────────────────────────────────────────┤ │ [当前委托] [历史委托] [成交记录] [持仓] │ │ BTCUSDT BUY 42000 2.00 0.80 进行中 [撤单] │ └─────────────────────────────────────────────────────────────────┘ 桌面端: CSS Grid 多列布局,面板可拖拽调整大小 移动端: Tab 切换(K线 / 盘口 / 下单 / 订单),底部固定下单按钮