期货数据实时展示前端实现方案K线图表展示
期货数据实时展示前端实现方案
下面我将为您提供一个完整的期货数据展示前端解决方案,基于StockTV API实现实时行情监控和K线图表展示。
设计思路
本方案采用模块化设计,包含以下核心功能:
- 期货品种列表展示
- 实时价格监控
- K线图表可视化
- WebSocket实时数据推送
- 响应式布局设计
完整HTML实现
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>期货实时行情监控系统 - StockTV数据对接</title><script src="https://cdn.jsdelivr.net/npm/chart.js"></script><style>:root {--primary-color: #1e88e5;--positive-color: #4caf50;--negative-color: #f44336;--bg-color: #f5f5f5;--card-bg: #ffffff;--text-primary: #212121;--text-secondary: #757575;}* {margin: 0;padding: 0;box-sizing: border-box;font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;}body {background-color: var(--bg-color);color: var(--text-primary);line-height: 1.6;}.container {max-width: 1400px;margin: 0 auto;padding: 20px;}header {text-align: center;margin-bottom: 30px;padding: 20px;background: var(--card-bg);border-radius: 10px;box-shadow: 0 2px 10px rgba(0,0,0,0.05);}h1 {color: var(--primary-color);margin-bottom: 10px;}.subtitle {color: var(--text-secondary);font-size: 1.1rem;}.grid-container {display: grid;grid-template-columns: 300px 1fr;gap: 20px;}@media (max-width: 968px) {.grid-container {grid-template-columns: 1fr;}}.card {background: var(--card-bg);border-radius: 10px;padding: 20px;box-shadow: 0 2px 10px rgba(0,0,0,0.05);margin-bottom: 20px;}.card-title {font-size: 1.2rem;margin-bottom: 15px;padding-bottom: 10px;border-bottom: 1px solid #eee;display: flex;justify-content: space-between;align-items: center;}.futures-list {max-height: 600px;overflow-y: auto;}.futures-item {padding: 12px 15px;border-bottom: 1px solid #f0f0f0;cursor: pointer;transition: background 0.2s;}.futures-item:hover {background: #f9f9f9;}.futures-item.active {background: #e3f2fd;border-left: 3px solid var(--primary-color);}.symbol {font-weight: bold;display: flex;justify-content: space-between;}.name {font-size: 0.9rem;color: var(--text-secondary);margin-top: 5px;}.price-up {color: var(--positive-color);}.price-down {color: var(--negative-color);}.price-container {display: grid;grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));gap: 15px;margin-bottom: 20px;}.price-card {padding: 15px;border-radius: 8px;text-align: center;background: #f8f9fa;}.price-value {font-size: 1.8rem;font-weight: bold;margin: 10px 0;}.price-change {font-size: 1rem;}.chart-container {height: 400px;position: relative;}.controls {display: flex;gap: 10px;margin-bottom: 15px;flex-wrap: wrap;}.interval-btn {padding: 5px 10px;background: #e0e0e0;border: none;border-radius: 4px;cursor: pointer;transition: background 0.2s;}.interval-btn.active {background: var(--primary-color);color: white;}.status-bar {display: flex;justify-content: space-between;align-items: center;padding: 10px 15px;background: #e8f5e9;border-radius: 5px;margin-top: 20px;font-size: 0.9rem;}.connection-status {display: flex;align-items: center;}.status-dot {width: 10px;height: 10px;border-radius: 50%;margin-right: 8px;}.connected {background: var(--positive-color);}.disconnected {background: var(--negative-color);}.last-update {color: var(--text-secondary);}.error-message {background: #ffebee;color: var(--negative-color);padding: 10px 15px;border-radius: 5px;margin: 10px 0;display: none;}.loading {text-align: center;padding: 20px;color: var(--text-secondary);}</style>
</head>
<body><div class="container"><header><h1>期货实时行情监控系统</h1><p class="subtitle">基于StockTV API的实时期货数据展示</p></header><div class="error-message" id="errorMessage"></div><div class="grid-container"><!-- 左侧期货列表 --><div class="futures-list card"><div class="card-title"><span>期货品种</span><span id="listCount">0个品种</span></div><div id="futuresList"><div class="loading">加载中...</div></div></div><!-- 右侧主要内容 --><div><!-- 价格卡片 --><div class="price-container" id="priceContainer"><div class="price-card"><div>最新价</div><div class="price-value" id="lastPrice">--</div><div class="price-change" id="priceChange">--</div></div><div class="price-card"><div>涨跌幅</div><div class="price-value" id="changePercent">--</div><div>较前收盘</div></div><div class="price-card"><div>最高价</div><div class="price-value" id="highPrice">--</div><div>最低价 <span id="lowPrice">--</span></div></div><div class="price-card"><div>成交量</div><div class="price-value" id="volume">--</div><div>开盘价 <span id="openPrice">--</span></div></div></div><!-- K线图 --><div class="card"><div class="card-title"><span id="chartTitle">K线图</span><div class="controls"><button class="interval-btn active" data-interval="1">1分</button><button class="interval-btn" data-interval="5">5分</button><button class="interval-btn" data-interval="15">15分</button><button class="interval-btn" data-interval="60">1小时</button><button class="interval-btn" data-interval="1d">日线</button></div></div><div class="chart-container"><canvas id="klineChart"></canvas></div></div></div></div><div class="status-bar"><div class="connection-status"><div class="status-dot disconnected" id="statusDot"></div><span id="connectionStatus">未连接</span></div><div class="last-update">最后更新: <span id="lastUpdateTime">--</span></div></div></div><script>// 配置信息const CONFIG = {API_BASE_URL: 'https://api.stocktv.top',WS_URL: 'wss://ws-api.stocktv.top/connect',API_KEY: 'YOUR_API_KEY', // 请替换为您的实际API密钥HEARTBEAT_INTERVAL: 30000, // 心跳间隔30秒RECONNECT_DELAY: 5000, // 重连延迟5秒KLINE_LIMIT: 100 // K线数据条数};// 状态变量let state = {currentSymbol: null,futuresData: [],klineData: [],wsConnection: null,chart: null,isConnected: false,reconnectAttempts: 0,maxReconnectAttempts: 5};// 主要期货品种(根据StockTV API支持的品种)const MAJOR_FUTURES = [{ symbol: 'CL', name: 'WTI原油期货', exchange: 'NYMEX' },{ symbol: 'GC', name: 'COMEX黄金', exchange: 'COMEX' },{ symbol: 'SI', name: 'COMEX白银', exchange: 'COMEX' },{ symbol: 'HG', name: 'COMEX铜', exchange: 'COMEX' },{ symbol: 'NG', name: '天然气', exchange: 'NYMEX' },{ symbol: 'ZS', name: '芝加哥大豆', exchange: 'CBOT' },{ symbol: 'ZC', name: '芝加哥玉米', exchange: 'CBOT' },{ symbol: 'FEF', name: '新加坡铁矿石', exchange: 'SGX' },{ symbol: 'FCPO', name: '马棕油', exchange: 'BMD' }];// DOM加载完成后初始化document.addEventListener('DOMContentLoaded', function() {initEventListeners();loadFuturesList();});// 初始化事件监听器function initEventListeners() {// K线周期按钮事件document.querySelectorAll('.interval-btn').forEach(btn => {btn.addEventListener('click', function() {document.querySelectorAll('.interval-btn').forEach(b => b.classList.remove('active'));this.classList.add('active');const interval = this.getAttribute('data-interval');if (state.currentSymbol) {loadKlineData(state.currentSymbol, interval);}});});}// 加载期货列表async function loadFuturesList() {showError('');try {// 这里使用模拟数据,实际应用中应调用StockTV API// const response = await fetch(`${CONFIG.API_BASE_URL}/futures/list?key=${CONFIG.API_KEY}`);// const data = await response.json();// 模拟API响应setTimeout(() => {const mockData = {code: 200,data: MAJOR_FUTURES.map(future => ({symbol: future.symbol,name: future.name,exchange: future.exchange,lastPrice: (Math.random() * 100 + 50).toFixed(2),change: (Math.random() * 2 - 1).toFixed(2),changePercent: (Math.random() * 4 - 2).toFixed(2),volume: Math.floor(Math.random() * 10000)}))};displayFuturesList(mockData.data);state.futuresData = mockData.data;// 默认选择第一个品种if (mockData.data.length > 0) {selectFuture(mockData.data[0].symbol);}}, 500);} catch (error) {showError('获取期货列表失败: ' + error.message);console.error('Error loading futures list:', error);}}// 显示期货列表function displayFuturesList(futures) {const listContainer = document.getElementById('futuresList');document.getElementById('listCount').textContent = `${futures.length}个品种`;if (futures.length === 0) {listContainer.innerHTML = '<div class="loading">暂无数据</div>';return;}listContainer.innerHTML = futures.map(future => `<div class="futures-item" data-symbol="${future.symbol}"><div class="symbol"><span>${future.symbol}</span><span class="${future.changePercent >= 0 ? 'price-up' : 'price-down'}">${future.lastPrice}</span></div><div class="name">${future.name} · ${future.exchange}</div><div class="${future.changePercent >= 0 ? 'price-up' : 'price-down'}">${future.changePercent >= 0 ? '+' : ''}${future.changePercent}%</div></div>`).join('');// 添加点击事件listContainer.querySelectorAll('.futures-item').forEach(item => {item.addEventListener('click', function() {const symbol = this.getAttribute('data-symbol');selectFuture(symbol);});});}// 选择期货品种function selectFuture(symbol) {// 更新UI状态document.querySelectorAll('.futures-item').forEach(item => {item.classList.remove('active');});document.querySelector(`.futures-item[data-symbol="${symbol}"]`).classList.add('active');state.currentSymbol = symbol;// 更新标题const future = state.futuresData.find(f => f.symbol === symbol) || MAJOR_FUTURES.find(f => f.symbol === symbol);document.getElementById('chartTitle').textContent = `${future.name} (${future.symbol}) K线图`;// 加载详细行情和K线数据loadFutureDetail(symbol);const activeInterval = document.querySelector('.interval-btn.active').getAttribute('data-interval');loadKlineData(symbol, activeInterval);// 通过WebSocket订阅实时数据subscribeRealtimeData(symbol);}// 加载期货详情async function loadFutureDetail(symbol) {try {// 这里使用模拟数据,实际应用中应调用StockTV API// const response = await fetch(`${CONFIG.API_BASE_URL}/futures/querySymbol?key=${CONFIG.API_KEY}&symbol=${symbol}`);// const data = await response.json();// 模拟API响应setTimeout(() => {const future = MAJOR_FUTURES.find(f => f.symbol === symbol);const mockData = {code: 200,data: {symbol: symbol,name: future.name,lastPrice: (Math.random() * 100 + 50).toFixed(2),change: (Math.random() * 2 - 1).toFixed(2),changePercent: (Math.random() * 4 - 2).toFixed(2),high: (Math.random() * 5 + 100).toFixed(2),low: (Math.random() * 5 + 95).toFixed(2),open: (Math.random() * 5 + 98).toFixed(2),volume: Math.floor(Math.random() * 10000),time: new Date().toLocaleString()}};updatePriceDisplay(mockData.data);}, 300);} catch (error) {console.error('Error loading future detail:', error);}}// 更新价格显示function updatePriceDisplay(data) {const isPositive = parseFloat(data.changePercent) >= 0;const changeClass = isPositive ? 'price-up' : 'price-down';const changeSign = isPositive ? '+' : '';document.getElementById('lastPrice').textContent = data.lastPrice;document.getElementById('lastPrice').className = `price-value ${changeClass}`;document.getElementById('changePercent').textContent = `${changeSign}${data.changePercent}%`;document.getElementById('changePercent').className = `price-value ${changeClass}`;document.getElementById('priceChange').innerHTML = `${changeSign}${data.change} <span class="${changeClass}">${changeSign}${data.changePercent}%</span>`;document.getElementById('highPrice').textContent = data.high;document.getElementById('lowPrice').textContent = data.low;document.getElementById('openPrice').textContent = data.open;document.getElementById('volume').textContent = data.volume.toLocaleString();document.getElementById('lastUpdateTime').textContent = new Date().toLocaleTimeString();}// 加载K线数据async function loadKlineData(symbol, interval) {showError('');try {// 这里使用模拟数据,实际应用中应调用StockTV API// const response = await fetch(// `${CONFIG.API_BASE_URL}/futures/kline?key=${CONFIG.API_KEY}&symbol=${symbol}&interval=${interval}&limit=${CONFIG.KLINE_LIMIT}`// );// const data = await response.json();// 模拟K线数据生成const mockKlineData = generateMockKlineData(50);state.klineData = mockKlineData;renderKlineChart(mockKlineData);} catch (error) {showError('获取K线数据失败: ' + error.message);console.error('Error loading K-line data:', error);}}// 生成模拟K线数据function generateMockKlineData(count) {const data = [];let basePrice = 100;let timestamp = Date.now() - count * 60000; // 从当前时间往前推for (let i = 0; i < count; i++) {const open = basePrice;const change = (Math.random() - 0.5) * 4;const close = open + change;const high = Math.max(open, close) + Math.random() * 2;const low = Math.min(open, close) - Math.random() * 2;const volume = Math.floor(Math.random() * 1000) + 100;data.push({timestamp: timestamp + i * 60000,open: open,high: high,low: low,close: close,volume: volume});basePrice = close;}return data;}// 渲染K线图function renderKlineChart(klineData) {const ctx = document.getElementById('klineChart').getContext('2d');// 销毁现有图表if (state.chart) {state.chart.destroy();}// 准备图表数据const labels = klineData.map((_, i) => {const date = new Date(klineData[i].timestamp);return date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});});const ohlcData = klineData.map(d => ({x: new Date(d.timestamp),o: d.open,h: d.high,l: d.low,c: d.close}));// 创建图表state.chart = new Chart(ctx, {type: 'candlestick',data: {labels: labels,datasets: [{label: '价格',data: ohlcData,borderColor: '#1e88e5',borderWidth: 1,color: {up: '#4caf50',down: '#f44336',unchanged: '#999'}}]},options: {responsive: true,maintainAspectRatio: false,scales: {x: {type: 'time',time: {unit: 'minute'},ticks: {maxTicksLimit: 10}},y: {ticks: {callback: function(value) {return value.toFixed(2);}}}},plugins: {legend: {display: false},tooltip: {mode: 'index',intersect: false,callbacks: {label: function(context) {const point = context.raw;return [`开盘: ${point.o}`,`最高: ${point.h}`,`最低: ${point.l}`,`收盘: ${point.c}`];}}}}}});}// WebSocket连接与实时数据订阅function connectWebSocket() {try {state.wsConnection = new WebSocket(`${CONFIG.WS_URL}?key=${CONFIG.API_KEY}`);state.wsConnection.onopen = function() {console.log('WebSocket连接已建立');state.isConnected = true;state.reconnectAttempts = 0;updateConnectionStatus(true);// 启动心跳机制startHeartbeat();};state.wsConnection.onmessage = function(event) {const data = JSON.parse(event.data);// 处理心跳响应if (data.action === 'pong') {return;}// 处理实时行情数据handleRealtimeData(data);};state.wsConnection.onclose = function() {console.log('WebSocket连接已关闭');state.isConnected = false;updateConnectionStatus(false);// 尝试重连if (state.reconnectAttempts < state.maxReconnectAttempts) {setTimeout(() => {state.reconnectAttempts++;console.log(`尝试重连 (${state.reconnectAttempts}/${state.maxReconnectAttempts})`);connectWebSocket();}, CONFIG.RECONNECT_DELAY);}};state.wsConnection.onerror = function(error) {console.error('WebSocket错误:', error);showError('WebSocket连接错误');};} catch (error) {console.error('创建WebSocket连接失败:', error);showError('创建WebSocket连接失败: ' + error.message);}}// 启动心跳机制function startHeartbeat() {setInterval(() => {if (state.isConnected && state.wsConnection.readyState === WebSocket.OPEN) {state.wsConnection.send(JSON.stringify({ action: 'ping' }));}}, CONFIG.HEARTBEAT_INTERVAL);}// 订阅实时数据function subscribeRealtimeData(symbol) {if (state.isConnected && state.wsConnection.readyState === WebSocket.OPEN) {const subscribeMsg = {action: 'subscribe',symbol: symbol};state.wsConnection.send(JSON.stringify(subscribeMsg));}}// 处理实时数据function handleRealtimeData(data) {// 更新价格显示if (data.last_numeric) {// 查找当前品种在列表中的位置const futureIndex = state.futuresData.findIndex(f => f.symbol === state.currentSymbol);if (futureIndex !== -1) {// 更新数据const future = state.futuresData[futureIndex];const lastPrice = parseFloat(data.last_numeric);const change = lastPrice - parseFloat(future.lastPrice);const changePercent = (change / parseFloat(future.lastPrice)) * 100;future.lastPrice = lastPrice.toFixed(2);future.change = change.toFixed(2);future.changePercent = changePercent.toFixed(2);// 更新UIupdatePriceDisplay({lastPrice: future.lastPrice,change: future.change,changePercent: future.changePercent,high: data.high || future.high,low: data.low || future.low,open: data.open || future.open,volume: data.volume || future.volume,time: new Date().toLocaleString()});// 更新列表中的显示const listItem = document.querySelector(`.futures-item[data-symbol="${state.currentSymbol}"]`);if (listItem) {const priceElement = listItem.querySelector('.symbol span:last-child');const changeElement = listItem.querySelector('.price-up, .price-down');priceElement.textContent = future.lastPrice;priceElement.className = change >= 0 ? 'price-up' : 'price-down';changeElement.textContent = `${change >= 0 ? '+' : ''}${future.changePercent}%`;changeElement.className = change >= 0 ? 'price-up' : 'price-down';}}}}// 更新连接状态显示function updateConnectionStatus(connected) {const statusDot = document.getElementById('statusDot');const statusText = document.getElementById('connectionStatus');if (connected) {statusDot.className = 'status-dot connected';statusText.textContent = '已连接';} else {statusDot.className = 'status-dot disconnected';statusText.textContent = '未连接';}}// 显示错误信息function showError(message) {const errorElement = document.getElementById('errorMessage');if (message) {errorElement.textContent = message;errorElement.style.display = 'block';} else {errorElement.style.display = 'none';}}// 初始化WebSocket连接connectWebSocket();</script>
</body>
</html>
核心功能说明
1. 数据获取与展示
- 期货列表:通过
/futures/list接口获取可用期货品种 - 实时行情:通过
/futures/querySymbol接口获取特定品种行情 - K线数据:通过
/futures/kline接口获取历史K线数据
2. 实时数据更新
- 使用WebSocket建立长连接,接收实时行情推送
- 实现心跳机制保持连接活跃
- 自动重连机制确保连接稳定性
3. 数据可视化
- 使用Chart.js绘制K线图
- 响应式设计适应不同设备
- 多时间周期切换(1分/5分/15分/1小时/日线)
使用说明
- 将代码中的
YOUR_API_KEY替换为您的实际StockTV API密钥 - 根据需要调整期货品种配置(修改
MAJOR_FUTURES数组) - 可根据实际API响应结构调整数据解析逻辑
注意事项
- 本示例使用了模拟数据,实际应用中需对接真实StockTV API
- 生产环境需添加更完善的错误处理和加载状态管理
- 注意API调用频率限制,合理使用缓存策略
- 期货合约存在到期日,需要注意主力合约切换
此实现提供了一个完整的期货数据展示前端解决方案,您可以根据实际需求进一步扩展功能或调整界面设计。
