<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>OKX WebSocket 频道连接</title>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns"></script>
|
<style>
|
body {
|
font-family: Arial, sans-serif;
|
margin: 20px;
|
}
|
.channel {
|
margin-bottom: 20px;
|
padding: 15px;
|
border: 1px solid #ccc;
|
border-radius: 5px;
|
}
|
.channel h3 {
|
margin-top: 0;
|
}
|
.status {
|
font-weight: bold;
|
}
|
.connected {
|
color: green;
|
}
|
.disconnected {
|
color: red;
|
}
|
.messages {
|
height: 200px;
|
overflow-y: scroll;
|
border: 1px solid #eee;
|
padding: 10px;
|
margin-top: 10px;
|
background-color: #f9f9f9;
|
}
|
.chart-container {
|
width: 100%;
|
height: 300px;
|
margin-top: 20px;
|
}
|
.mode-toggle {
|
margin: 10px 0;
|
padding: 10px;
|
background-color: #f0f0f0;
|
border-radius: 5px;
|
}
|
.mode-toggle button {
|
margin: 0 5px;
|
padding: 5px 10px;
|
border: 1px solid #ccc;
|
background-color: #fff;
|
cursor: pointer;
|
}
|
.mode-toggle button.active {
|
background-color: #007bff;
|
color: white;
|
}
|
</style>
|
</head>
|
<body>
|
<h1>OKX WebSocket 频道连接</h1>
|
|
<div class="mode-toggle">
|
<span>当前模式:</span>
|
<button id="demoModeBtn" class="active" onclick="switchMode('demo')">模拟盘 (wspap)</button>
|
<button id="liveModeBtn" onclick="switchMode('live')">实盘 (ws)</button>
|
</div>
|
|
<div class="channel" id="publicChannel">
|
<h3>公共频道</h3>
|
<p>URL: <input type="text" id="publicUrl" value="wss://wspap.okx.com:8443/ws/v5/public" style="width: 300px;"></p>
|
<p>状态: <span class="status disconnected" id="publicStatus">未连接</span></p>
|
<button onclick="toggleConnection('public')">连接/断开</button>
|
<button onclick="clearMessages('public')">清除消息</button>
|
<div class="chart-container">
|
<canvas id="ethUsdtChart"></canvas>
|
</div>
|
<div class="messages" id="publicMessages"></div>
|
</div>
|
|
<div class="channel" id="privateChannel">
|
<h3>私有频道</h3>
|
<p>URL: <input type="text" id="privateUrl" value="wss://wspap.okx.com:8443/ws/v5/private" style="width: 300px;"></p>
|
<p>状态: <span class="status disconnected" id="privateStatus">未连接</span></p>
|
<button onclick="toggleConnection('private')">连接/断开</button>
|
<div class="messages" id="privateMessages"></div>
|
</div>
|
|
<div class="channel" id="businessChannel">
|
<h3>业务频道</h3>
|
<p>URL: <input type="text" id="businessUrl" value="wss://wspap.okx.com:8443/ws/v5/business" style="width: 300px;"></p>
|
<p>状态: <span class="status disconnected" id="businessStatus">未连接</span></p>
|
<button onclick="toggleConnection('business')">连接/断开</button>
|
<div class="messages" id="businessMessages"></div>
|
</div>
|
|
<div class="channel" id="markPriceCandleChannel">
|
<h3>标记价格K线频道</h3>
|
<p>URL: <input type="text" id="markPriceCandleUrl" value="wss://wspap.okx.com:8443/ws/v5/business" style="width: 300px;"></p>
|
<p>状态: <span class="status disconnected" id="markPriceCandleStatus">未连接</span></p>
|
<button onclick="toggleConnection('markPriceCandle')">连接/断开</button>
|
<div class="messages" id="markPriceCandleMessages"></div>
|
</div>
|
|
<script>
|
// 存储WebSocket连接对象
|
const connections = {
|
public: null,
|
private: null,
|
business: null,
|
markPriceCandle: null
|
};
|
|
// 存储各频道的URL
|
const urls = {
|
demo: {
|
public: 'wss://wspap.okx.com:8443/ws/v5/public',
|
private: 'wss://wspap.okx.com:8443/ws/v5/private',
|
business: 'wss://wspap.okx.com:8443/ws/v5/business'
|
},
|
live: {
|
public: 'wss://ws.okx.com:8443/ws/v5/public',
|
private: 'wss://ws.okx.com:8443/ws/v5/private',
|
business: 'wss://ws.okx.com:8443/ws/v5/business'
|
}
|
};
|
|
// 为markPriceCandle频道添加URL配置
|
urls.demo.markPriceCandle = urls.demo.business;
|
urls.live.markPriceCandle = urls.live.business;
|
|
// 当前模式,默认为模拟盘
|
let currentMode = 'demo';
|
|
// ETH-USDT 价格数据
|
const ethUsdtPrices = [];
|
const ethUsdtTimes = [];
|
let ethUsdtChart = null;
|
|
// 初始化图表
|
function initChart() {
|
const ctx = document.getElementById('ethUsdtChart').getContext('2d');
|
ethUsdtChart = new Chart(ctx, {
|
type: 'line',
|
data: {
|
labels: ethUsdtTimes,
|
datasets: [{
|
label: 'ETH-USDT Price',
|
data: ethUsdtPrices,
|
borderColor: 'rgb(75, 192, 192)',
|
backgroundColor: 'rgba(75, 192, 192, 0.2)',
|
tension: 0.1,
|
fill: false
|
}]
|
},
|
options: {
|
responsive: true,
|
maintainAspectRatio: false,
|
scales: {
|
x: {
|
type: 'time',
|
time: {
|
unit: 'minute'
|
}
|
},
|
y: {
|
beginAtZero: false
|
}
|
}
|
}
|
});
|
}
|
|
// 更新图表
|
function updateChart() {
|
if (ethUsdtChart) {
|
// 对于折线图,我们需要重新设置labels和data并调用update
|
ethUsdtChart.data.labels = ethUsdtTimes;
|
ethUsdtChart.data.datasets[0].data = ethUsdtPrices;
|
ethUsdtChart.update();
|
}
|
}
|
|
// 连接/断开WebSocket连接
|
function toggleConnection(channel) {
|
if (connections[channel]) {
|
// 如果已经连接,则断开连接
|
connections[channel].close();
|
connections[channel] = null;
|
document.getElementById(`${channel}Status`).textContent = '未连接';
|
document.getElementById(`${channel}Status`).className = 'status disconnected';
|
} else {
|
// 如果未连接,则建立连接
|
connectToChannel(channel);
|
}
|
}
|
|
// 清除指定频道的消息
|
function clearMessages(channel) {
|
const messagesDiv = document.getElementById(`${channel}Messages`);
|
messagesDiv.innerHTML = '';
|
|
// 清除图表数据
|
if (channel === 'public') {
|
ethUsdtPrices.length = 0;
|
ethUsdtTimes.length = 0;
|
updateChart();
|
}
|
}
|
|
// 切换模式
|
function switchMode(mode) {
|
// 更新当前模式
|
currentMode = mode;
|
|
// 更新按钮状态
|
document.getElementById('demoModeBtn').classList.toggle('active', mode === 'demo');
|
document.getElementById('liveModeBtn').classList.toggle('active', mode === 'live');
|
|
// 更新所有频道的URL输入框的值
|
document.querySelectorAll('.channel').forEach(channelDiv => {
|
const channelId = channelDiv.id.replace('Channel', '');
|
// 更新输入框的值而不是替换整个元素
|
const urlInput = document.getElementById(`${channelId}Url`);
|
if (urlInput && urls[mode] && urls[mode][channelId]) {
|
urlInput.value = urls[mode][channelId];
|
}
|
});
|
}
|
|
// 连接到指定频道
|
function connectToChannel(channel) {
|
// 获取用户输入的URL
|
let url;
|
switch(channel) {
|
case 'public':
|
url = document.getElementById('publicUrl').value;
|
break;
|
case 'private':
|
url = document.getElementById('privateUrl').value;
|
break;
|
case 'business':
|
url = document.getElementById('businessUrl').value;
|
break;
|
case 'markPriceCandle':
|
url = document.getElementById('markPriceCandleUrl').value;
|
break;
|
default:
|
url = urls[currentMode][channel];
|
}
|
|
const ws = new WebSocket(url);
|
|
ws.onopen = function() {
|
console.log(`${channel}频道连接成功`);
|
document.getElementById(`${channel}Status`).textContent = '已连接';
|
document.getElementById(`${channel}Status`).className = 'status connected';
|
|
// 发送订阅消息
|
if (channel === 'public') {
|
const subscribeMsg = {
|
op: 'subscribe',
|
args: [
|
{
|
channel: 'tickers',
|
instId: 'ETH-USDT'
|
}
|
]
|
};
|
ws.send(JSON.stringify(subscribeMsg));
|
|
// 初始化图表
|
if (!ethUsdtChart) {
|
initChart();
|
}
|
} else if (channel === 'markPriceCandle') {
|
// 订阅标记价格K线数据 (BTC-USD-190628, 1分钟K线)
|
const subscribeMsg = {
|
op: 'subscribe',
|
args: [
|
{
|
channel: 'mark-price-candle1m',
|
instId: 'ETH-USDT'
|
}
|
]
|
};
|
ws.send(JSON.stringify(subscribeMsg));
|
}
|
};
|
|
ws.onmessage = function(event) {
|
const message = event.data;
|
console.log(`${channel}频道收到消息:`, message);
|
|
// 在页面上显示消息
|
const messagesDiv = document.getElementById(`${channel}Messages`);
|
const messageElement = document.createElement('div');
|
|
// 尝试解析JSON消息并格式化显示
|
try {
|
const jsonData = JSON.parse(message);
|
|
// 检查是否为ETH-USDT的ticker数据
|
if (jsonData.arg && jsonData.arg.instId === 'ETH-USDT' && jsonData.data && jsonData.data.length > 0) {
|
const price = parseFloat(jsonData.data[0].last);
|
const time = new Date();
|
|
// 添加价格和时间到数组
|
ethUsdtPrices.push(price);
|
ethUsdtTimes.push(time);
|
|
// 限制数据点数量,只保留最近的1000个
|
// 为了保持图表连续性,我们采用滑动窗口的方式
|
if (ethUsdtPrices.length > 1000) {
|
// 移除最旧的数据点
|
ethUsdtPrices.shift();
|
ethUsdtTimes.shift();
|
|
// 为了保持图表连续性,我们需要保留最后一个数据点作为下一个数据段的起始点
|
// 这样可以避免图表出现断裂
|
// 当数据点超过1000个时,我们重新构建数据集,确保连续性
|
if (ethUsdtPrices.length > 1) {
|
// 保留最后一个数据点作为新数据段的起始点
|
const lastPrice = ethUsdtPrices[ethUsdtPrices.length - 1];
|
const lastTime = ethUsdtTimes[ethUsdtTimes.length - 1];
|
|
// 清空数组,但保留最后一个数据点
|
ethUsdtPrices.length = 0;
|
ethUsdtTimes.length = 0;
|
|
// 将最后一个数据点作为新数据段的起始点
|
ethUsdtPrices.push(lastPrice);
|
ethUsdtTimes.push(lastTime);
|
}
|
}
|
|
// 更新图表
|
updateChart();
|
}
|
|
// 检查是否为标记价格K线数据
|
if (jsonData.arg && jsonData.arg.channel && jsonData.arg.channel.startsWith('mark-price-candle') &&
|
jsonData.arg.instId === 'BTC-USD-190628' && jsonData.data && jsonData.data.length > 0) {
|
// 这里可以处理标记价格K线数据
|
// 标记价格K线数据格式: [ts, o, h, l, c]
|
// ts: 开始时间, o: 开盘价, h: 最高价, l: 最低价, c: 收盘价
|
console.log('标记价格K线数据:', jsonData.data[0]);
|
}
|
|
messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${JSON.stringify(jsonData, null, 2)}`;
|
} catch (e) {
|
// 如果不是JSON格式,则直接显示
|
messageElement.innerHTML = `<strong>[${new Date().toLocaleTimeString()}]</strong> ${message}`;
|
}
|
|
messagesDiv.appendChild(messageElement);
|
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
};
|
|
ws.onerror = function(error) {
|
console.error(`${channel}频道连接错误:`, error);
|
document.getElementById(`${channel}Status`).textContent = '连接错误';
|
document.getElementById(`${channel}Status`).className = 'status disconnected';
|
};
|
|
ws.onclose = function() {
|
console.log(`${channel}频道连接已关闭`);
|
document.getElementById(`${channel}Status`).textContent = '连接已关闭';
|
document.getElementById(`${channel}Status`).className = 'status disconnected';
|
connections[channel] = null;
|
};
|
|
connections[channel] = ws;
|
}
|
</script>
|
</body>
|
</html>
|