StockTV美股实时数据对接与前端展示

StockTV美股实时数据对接与前端展示

下面是一个完整的StockTV美国股票实时数据对接方案,包含可直接运行的前端代码。

接口对接概述

StockTV提供全面的美国股票市场数据API,支持实时行情、历史数据、指数信息等核心功能。

主要特性

  • 实时行情:毫秒级延迟的实时股价数据
  • 多交易所支持:NYSE、NASDAQ、AMEX等主要交易所
  • 丰富数据类型:实时价格、涨跌幅、成交量、K线数据等
  • WebSocket支持:实时数据推送

前端实现代码

以下是一个简单但功能完整的股票行情展示页面,使用纯HTML/CSS/JavaScript实现:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>美股实时行情 - StockTV API对接</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a3a, #0d1b2a);
            color: #e0e0e0;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        
        header {
            text-align: center;
            margin-bottom: 30px;
            padding: 20px;
            background: rgba(255, 255, 255, 0.05);
            border-radius: 10px;
            backdrop-filter: blur(10px);
        }
        
        h1 {
            color: #4fc3f7;
            margin-bottom: 10px;
            font-size: 2.5rem;
        }
        
        .subtitle {
            color: #90a4ae;
            font-size: 1.1rem;
        }
        
        .controls {
            display: flex;
            justify-content: center;
            gap: 15px;
            margin-bottom: 30px;
            flex-wrap: wrap;
        }
        
        button {
            background: #1565c0;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.3s;
            font-size: 1rem;
        }
        
        button:hover {
            background: #1976d2;
            transform: translateY(-2px);
        }
        
        button:disabled {
            background: #546e7a;
            cursor: not-allowed;
            transform: none;
        }
        
        input {
            padding: 10px 15px;
            border: 1px solid #37474f;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.1);
            color: white;
            width: 200px;
            font-size: 1rem;
        }
        
        input::placeholder {
            color: #90a4ae;
        }
        
        .status {
            text-align: center;
            padding: 10px;
            margin: 15px 0;
            border-radius: 5px;
            font-weight: bold;
        }
        
        .status.connected {
            background: rgba(76, 175, 80, 0.2);
            color: #4caf50;
        }
        
        .status.disconnected {
            background: rgba(244, 67, 54, 0.2);
            color: #f44336;
        }
        
        .data-section {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .card {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 10px;
            padding: 20px;
            transition: transform 0.3s, box-shadow 0.3s;
            border: 1px solid rgba(255, 255, 255, 0.1);
        }
        
        .card:hover {
            transform: translateY(-5px);
            box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
        }
        
        .card-header {
            display: flex;
            justify-content: between;
            align-items: center;
            margin-bottom: 15px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            padding-bottom: 10px;
        }
        
        .symbol {
            font-size: 1.5rem;
            font-weight: bold;
            color: #4fc3f7;
        }
        
        .price {
            font-size: 1.8rem;
            font-weight: bold;
            margin: 10px 0;
        }
        
        .change.positive {
            color: #4caf50;
        }
        
        .change.negative {
            color: #f44336;
        }
        
        .details {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            margin-top: 15px;
        }
        
        .detail-item {
            display: flex;
            justify-content: space-between;
            padding: 5px 0;
            border-bottom: 1px dashed rgba(255, 255, 255, 0.05);
        }
        
        .detail-label {
            color: #90a4ae;
        }
        
        .index-section {
            background: rgba(255, 255, 255, 0.05);
            border-radius: 10px;
            padding: 20px;
            margin-top: 30px;
        }
        
        .index-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
            gap: 15px;
            margin-top: 15px;
        }
        
        .index-item {
            padding: 15px;
            background: rgba(255, 255, 255, 0.03);
            border-radius: 8px;
            text-align: center;
        }
        
        .last-update {
            text-align: center;
            margin-top: 20px;
            color: #90a4ae;
            font-size: 0.9rem;
        }
        
        @media (max-width: 768px) {
            .data-section {
                grid-template-columns: 1fr;
            }
            
            .index-grid {
                grid-template-columns: 1fr 1fr;
            }
            
            h1 {
                font-size: 2rem;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>美股实时行情</h1>
            <p class="subtitle">基于StockTV API的实时数据对接演示</p>
        </header>
        
        <div class="controls">
            <input type="text" id="symbolInput" placeholder="输入股票代码,如:AAPL">
            <button id="addStockBtn">添加股票</button>
            <button id="connectBtn">连接实时数据</button>
            <button id="disconnectBtn" disabled>断开连接</button>
            <button id="resetBtn">重置</button>
        </div>
        
        <div id="status" class="status disconnected">未连接</div>
        
        <div class="data-section" id="stocksContainer">
            <!-- 股票卡片将通过JavaScript动态生成 -->
        </div>
        
        <div class="index-section">
            <h2>美国主要指数</h2>
            <div class="index-grid" id="indicesContainer">
                <!-- 指数数据将通过JavaScript动态生成 -->
            </div>
        </div>
        
        <div class="last-update" id="lastUpdate">
            最后更新: <span id="updateTime">--</span>
        </div>
    </div>

    <script>
        // StockTV API配置
        const API_CONFIG = {
            baseUrl: 'https://api.stocktv.top',
            // 请替换为您的实际API密钥
            apiKey: 'YOUR_API_KEY_HERE',
            // 默认跟踪的股票
            defaultSymbols: ['AAPL', 'MSFT', 'GOOGL', 'AMZN', 'TSLA'],
            // 美国主要指数
            indices: [
                { symbol: 'DJI', name: '道琼斯指数' },
                { symbol: 'SPX', name: '标普500' },
                { symbol: 'IXIC', name: '纳斯达克' }
            ]
        };

        // 应用状态
        let appState = {
            connected: false,
            stocks: [],
            indices: [],
            ws: null,
            updateInterval: null
        };

        // DOM元素
        const elements = {
            stocksContainer: document.getElementById('stocksContainer'),
            indicesContainer: document.getElementById('indicesContainer'),
            status: document.getElementById('status'),
            connectBtn: document.getElementById('connectBtn'),
            disconnectBtn: document.getElementById('disconnectBtn'),
            addStockBtn: document.getElementById('addStockBtn'),
            resetBtn: document.getElementById('resetBtn'),
            symbolInput: document.getElementById('symbolInput'),
            updateTime: document.getElementById('updateTime')
        };

        // 初始化应用
        function initApp() {
            // 加载默认股票
            API_CONFIG.defaultSymbols.forEach(symbol => {
                addStock(symbol);
            });
            
            // 加载指数数据
            loadIndices();
            
            // 设置事件监听
            setupEventListeners();
            
            // 初始数据加载
            refreshAllData();
        }

        // 设置事件监听器
        function setupEventListeners() {
            elements.connectBtn.addEventListener('click', connectWebSocket);
            elements.disconnectBtn.addEventListener('click', disconnectWebSocket);
            elements.addStockBtn.addEventListener('click', addStockFromInput);
            elements.resetBtn.addEventListener('click', resetApp);
            elements.symbolInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') addStockFromInput();
            });
        }

        // 从输入框添加股票
        function addStockFromInput() {
            const symbol = elements.symbolInput.value.trim().toUpperCase();
            if (symbol) {
                addStock(symbol);
                elements.symbolInput.value = '';
            }
        }

        // 添加股票到跟踪列表
        function addStock(symbol) {
            if (!appState.stocks.find(stock => stock.symbol === symbol)) {
                appState.stocks.push({
                    symbol: symbol,
                    data: null,
                    lastUpdated: null
                });
                
                // 立即获取数据
                fetchStockData(symbol);
                
                // 更新UI
                renderStocks();
            }
        }

        // 获取股票数据
        async function fetchStockData(symbol) {
            try {
                // 使用StockTV的股票查询接口
                const response = await fetch(
                    `${API_CONFIG.baseUrl}/stock/queryStocks?symbol=${symbol}&key=${API_CONFIG.apiKey}`
                );
                
                if (!response.ok) throw new Error('网络响应不正常');
                
                const data = await response.json();
                
                if (data.code === 200 && data.data && data.data.length > 0) {
                    const stockData = data.data[0];
                    updateStockData(symbol, stockData);
                } else {
                    console.error(`获取 ${symbol} 数据失败:`, data.message);
                }
            } catch (error) {
                console.error(`获取 ${symbol} 数据时出错:`, error);
            }
        }

        // 更新股票数据
        function updateStockData(symbol, newData) {
            const stockIndex = appState.stocks.findIndex(stock => stock.symbol === symbol);
            if (stockIndex !== -1) {
                appState.stocks[stockIndex].data = newData;
                appState.stocks[stockIndex].lastUpdated = new Date();
                renderStocks();
                updateTimestamp();
            }
        }

        // 加载指数数据
        async function loadIndices() {
            try {
                // 使用StockTV的指数接口
                const response = await fetch(
                    `${API_CONFIG.baseUrl}/stock/indices?countryId=1&key=${API_CONFIG.apiKey}`
                );
                
                if (!response.ok) throw new Error('网络响应不正常');
                
                const data = await response.json();
                
                if (data.code === 200 && data.data) {
                    appState.indices = data.data;
                    renderIndices();
                }
            } catch (error) {
                console.error('获取指数数据时出错:', error);
            }
        }

        // 渲染股票卡片
        function renderStocks() {
            elements.stocksContainer.innerHTML = '';
            
            appState.stocks.forEach(stock => {
                if (!stock.data) return;
                
                const change = stock.data.last - stock.data.prevClose;
                const changePercent = (change / stock.data.prevClose) * 100;
                const isPositive = change >= 0;
                
                const stockCard = document.createElement('div');
                stockCard.className = 'card';
                stockCard.innerHTML = `
                    <div class="card-header">
                        <div class="symbol">${stock.symbol}</div>
                        <div class="name">${stock.data.name || 'N/A'}</div>
                    </div>
                    <div class="price">$${stock.data.last.toFixed(2)}</div>
                    <div class="change ${isPositive ? 'positive' : 'negative'}">
                        ${isPositive ? '+' : ''}${change.toFixed(2)} (${isPositive ? '+' : ''}${changePercent.toFixed(2)}%)
                    </div>
                    <div class="details">
                        <div class="detail-item">
                            <span class="detail-label">开盘:</span>
                            <span>$${stock.data.open?.toFixed(2) || 'N/A'}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">最高:</span>
                            <span>$${stock.data.high?.toFixed(2) || 'N/A'}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">最低:</span>
                            <span>$${stock.data.low?.toFixed(2) || 'N/A'}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">成交量:</span>
                            <span>${formatNumber(stock.data.volume)}</span>
                        </div>
                    </div>
                `;
                
                elements.stocksContainer.appendChild(stockCard);
            });
        }

        // 渲染指数数据
        function renderIndices() {
            elements.indicesContainer.innerHTML = '';
            
            appState.indices.forEach(index => {
                const change = index.change || index.last - index.prevClose;
                const changePercent = index.changePct || (change / index.prevClose) * 100;
                const isPositive = change >= 0;
                
                const indexItem = document.createElement('div');
                indexItem.className = 'index-item';
                indexItem.innerHTML = `
                    <div style="font-weight: bold; margin-bottom: 5px;">${index.symbol}</div>
                    <div style="font-size: 1.2rem; margin-bottom: 5px;">${index.last.toFixed(2)}</div>
                    <div class="${isPositive ? 'positive' : 'negative'}" style="font-size: 0.9rem;">
                        ${isPositive ? '+' : ''}${change.toFixed(2)} (${isPositive ? '+' : ''}${changePercent.toFixed(2)}%)
                    </div>
                    <div style="font-size: 0.8rem; color: #90a4ae; margin-top: 5px;">${index.name}</div>
                `;
                
                elements.indicesContainer.appendChild(indexItem);
            });
        }

        // 连接WebSocket实时数据
        function connectWebSocket() {
            if (appState.connected) return;
            
            try {
                // 创建WebSocket连接
                appState.ws = new WebSocket(`wss://ws-api.stocktv.top/connect?key=${API_CONFIG.apiKey}`);
                
                appState.ws.onopen = () => {
                    appState.connected = true;
                    updateStatus('已连接实时数据', true);
                    
                    // 订阅当前跟踪的股票
                    const symbols = appState.stocks.map(stock => stock.symbol);
                    if (symbols.length > 0) {
                        const subscribeMsg = {
                            action: 'subscribe',
                            symbols: symbols
                        };
                        appState.ws.send(JSON.stringify(subscribeMsg));
                    }
                };
                
                appState.ws.onmessage = (event) => {
                    try {
                        const data = JSON.parse(event.data);
                        
                        // 处理实时股票数据
                        if (data.symbol && data.price !== undefined) {
                            updateStockData(data.symbol, {
                                last: data.price,
                                change: data.change,
                                changePercent: data.changePercent,
                                // 其他字段可以根据需要补充
                            });
                        }
                    } catch (error) {
                        console.error('解析WebSocket消息错误:', error);
                    }
                };
                
                appState.ws.onclose = () => {
                    appState.connected = false;
                    updateStatus('连接已断开', false);
                };
                
                appState.ws.onerror = (error) => {
                    console.error('WebSocket错误:', error);
                    updateStatus('连接错误', false);
                };
                
            } catch (error) {
                console.error('创建WebSocket连接失败:', error);
                updateStatus('连接失败', false);
            }
        }

        // 断开WebSocket连接
        function disconnectWebSocket() {
            if (appState.ws) {
                appState.ws.close();
                appState.ws = null;
            }
            appState.connected = false;
            updateStatus('未连接', false);
        }

        // 更新状态显示
        function updateStatus(message, isConnected) {
            elements.status.textContent = message;
            elements.status.className = `status ${isConnected ? 'connected' : 'disconnected'}`;
            
            elements.connectBtn.disabled = isConnected;
            elements.disconnectBtn.disabled = !isConnected;
        }

        // 刷新所有数据
        function refreshAllData() {
            appState.stocks.forEach(stock => {
                fetchStockData(stock.symbol);
            });
            loadIndices();
        }

        // 更新最后刷新时间
        function updateTimestamp() {
            const now = new Date();
            elements.updateTime.textContent = now.toLocaleTimeString();
        }

        // 格式化大数字
        function formatNumber(num) {
            if (num >= 1000000) {
                return (num / 1000000).toFixed(2) + 'M';
            } else if (num >= 1000) {
                return (num / 1000).toFixed(2) + 'K';
            }
            return num.toString();
        }

        // 重置应用
        function resetApp() {
            disconnectWebSocket();
            appState.stocks = [];
            API_CONFIG.defaultSymbols.forEach(symbol => {
                addStock(symbol);
            });
            renderStocks();
        }

        // 页面加载完成后初始化应用
        document.addEventListener('DOMContentLoaded', initApp);
        
        // 设置定时刷新(每30秒刷新一次非实时数据)
        setInterval(() => {
            if (!appState.connected) {
                refreshAllData();
            }
        }, 30000);
    </script>
</body>
</html>

使用说明

1. 获取API密钥

在使用此代码前,您需要从StockTV获取有效的API密钥。将代码中的YOUR_API_KEY_HERE替换为您的实际API密钥。

2. 功能特点

  • 实时数据展示:显示股票实时价格、涨跌幅、成交量等信息
  • WebSocket支持:可连接实时数据流,获取毫秒级更新
  • 多股票跟踪:支持同时查看多只股票行情
  • 主要指数显示:展示道琼斯、标普500、纳斯达克等主要指数
  • 响应式设计:适配不同屏幕尺寸

3. 操作方式

  • 在输入框中输入股票代码(如AAPL、MSFT)并点击"添加股票"按钮
  • 点击"连接实时数据"启用WebSocket实时更新
  • 点击"断开连接"切换回定期轮询模式
  • 点击"重置"恢复默认股票列表

4. 数据说明

此演示使用了StockTV提供的以下API接口:

  • /stock/queryStocks - 获取股票实时数据
  • /stock/indices - 获取指数数据
  • WebSocket连接 - 实时数据推送

注意事项

  1. API限制:请注意StockTV的API调用频率限制,基础版为100次/分钟
  2. 数据延迟:实时行情延迟≤200ms,历史数据无延迟
  3. 错误处理:代码包含了基本的错误处理,但实际应用中可能需要更完善的异常处理
  4. API密钥安全:在实际生产环境中,不应将API密钥直接暴露在前端代码中

将上述代码保存为HTML文件并在浏览器中打开即可运行这个美股实时行情展示应用。记得替换为您自己的StockTV API密钥以获得完整功能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值