印度尼西亚股票多时间框架K线数据可视化页面

印度尼西亚股票多时间框架K线数据可视化页面

下面我将为您创建一个直观展示印尼股票多时间框架K线数据的HTML页面,支持1分钟到1个月的不同时间周期切换。

设计思路

首先分析需求:需要展示印尼股票的K线数据,支持1分钟、5分钟、15分钟、30分钟、1小时、1日和1月等不同时间框架的切换。页面需要直观易用,包含K线图、技术指标和交互控制面板。 以下是完整的实现代码:

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>印尼股票多时间框架K线数据</title>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a3a 0%, #0d1b2a 100%);
            color: #e0e0e0;
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
        }
        
        header {
            text-align: center;
            margin-bottom: 20px;
            padding: 20px;
            background: rgba(16, 30, 44, 0.8);
            border-radius: 10px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        h1 {
            color: #4fc3f7;
            margin-bottom: 10px;
            font-size: 2.2rem;
        }
        
        .subtitle {
            color: #90a4ae;
            font-size: 1.1rem;
        }
        
        .content {
            display: flex;
            gap: 20px;
        }
        
        .chart-container {
            flex: 1;
            background: rgba(16, 30, 44, 0.8);
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        .panel {
            width: 280px;
            background: rgba(16, 30, 44, 0.8);
            border-radius: 10px;
            padding: 20px;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
        }
        
        .interval-selector {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
            margin-bottom: 20px;
        }
        
        .interval-btn {
            padding: 12px 8px;
            background: #1e3a5c;
            border: none;
            border-radius: 6px;
            color: #bbdefb;
            cursor: pointer;
            transition: all 0.3s;
            font-weight: 600;
            text-align: center;
        }
        
        .interval-btn:hover {
            background: #2a4d7a;
            transform: translateY(-2px);
        }
        
        .interval-btn.active {
            background: #1565c0;
            color: white;
            box-shadow: 0 2px 8px rgba(21, 101, 192, 0.4);
        }
        
        .stock-selector {
            margin-bottom: 20px;
        }
        
        .stock-selector select {
            width: 100%;
            padding: 12px;
            background: #1e3a5c;
            border: none;
            border-radius: 6px;
            color: #e0e0e0;
            font-size: 1rem;
        }
        
        .info-card {
            background: #1e3a5c;
            border-radius: 8px;
            padding: 15px;
            margin-bottom: 15px;
        }
        
        .info-card h3 {
            color: #4fc3f7;
            margin-bottom: 10px;
            font-size: 1.1rem;
        }
        
        .price {
            font-size: 1.8rem;
            font-weight: bold;
            color: #4caf50;
            margin: 10px 0;
        }
        
        .change.positive {
            color: #4caf50;
        }
        
        .change.negative {
            color: #f44336;
        }
        
        .stats {
            display: flex;
            justify-content: space-between;
            margin-top: 10px;
        }
        
        .stat-item {
            text-align: center;
        }
        
        .stat-value {
            font-size: 1.2rem;
            font-weight: bold;
        }
        
        .stat-label {
            font-size: 0.8rem;
            color: #90a4ae;
        }
        
        #chart {
            width: 100%;
            height: 600px;
        }
        
        .time-info {
            text-align: center;
            margin-top: 10px;
            color: #90a4ae;
            font-size: 0.9rem;
        }
        
        .indicator-selector {
            margin: 20px 0;
        }
        
        .indicator-btns {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
        }
        
        .indicator-btn {
            padding: 8px 12px;
            background: #1a2a3a;
            border: 1px solid #2a4d7a;
            border-radius: 4px;
            color: #bbdefb;
            cursor: pointer;
            transition: all 0.2s;
            font-size: 0.9rem;
        }
        
        .indicator-btn.active {
            background: #1565c0;
            border-color: #4fc3f7;
        }
        
        footer {
            text-align: center;
            margin-top: 20px;
            padding: 15px;
            color: #90a4ae;
            font-size: 0.9rem;
        }
        
        @media (max-width: 1024px) {
            .content {
                flex-direction: column;
            }
            
            .panel {
                width: 100%;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <header>
            <h1>印度尼西亚股票多时间框架K线数据</h1>
            <p class="subtitle">实时展示印尼雅加达综合指数(JCI)及主要股票的多周期K线图</p>
        </header>
        
        <div class="content">
                <div class="chart-container">
                    <div id="chart"></div>
                    <div class="time-info" id="timeInfo">数据更新时间: --</div>
                </div>
                
                <div class="panel">
                    <div class="stock-selector">
                        <select id="stockSelect">
                            <option value="JCI">雅加达综合指数 (JCI)</option>
                            <option value="BBCA">中亚银行 (BBCA)</option>
                            <option value="BBRI">印尼人民银行 (BBRI)</option>
                            <option value="TLKM">印尼电信 (TLKM)</option>
                            <option value="UNVR">联合利华印尼 (UNVR)</option>
                        </select>
                    </div>
                    
                    <div class="interval-selector">
                        <button class="interval-btn" data-interval="1m">1分钟</button>
                        <button class="interval-btn" data-interval="5m">5分钟</button>
                        <button class="interval-btn" data-interval="15m">15分钟</button>
                        <button class="interval-btn" data-interval="30m">30分钟</button>
                        <button class="interval-btn active" data-interval="1h">1小时</button>
                        <button class="interval-btn" data-interval="1d">日线</button>
                        <button class="interval-btn" data-interval="1w">周线</button>
                        <button class="interval-btn" data-interval="1mo">月线</button>
                    </div>
                    
                    <div class="info-card">
                        <h3>当前价格</h3>
                        <div class="price" id="currentPrice">--</div>
                        <div class="change" id="priceChange">-- (--%)</div>
                        <div class="stats">
                            <div class="stat-item">
                                <div class="stat-value" id="openPrice">--</div>
                                <div class="stat-label">开盘</div>
                            </div>
                            <div class="stat-item">
                                <div class="stat-value" id="highPrice">--</div>
                                <div class="stat-label">最高</div>
                            </div>
                            <div class="stat-item">
                                <div class="stat-value" id="lowPrice">--</div>
                                <div class="stat-label">最低</div>
                            </div>
                        </div>
                    </div>
                    
                    <div class="indicator-selector">
                        <h3>技术指标</h3>
                        <div class="indicator-btns">
                            <button class="indicator-btn active" data-indicator="ma">MA</button>
                            <button class="indicator-btn" data-indicator="ema">EMA</button>
                            <button class="indicator-btn" data-indicator="macd">MACD</button>
                            <button class="indicator-btn" data-indicator="rsi">RSI</button>
                            <button class="indicator-btn" data-indicator="boll">布林带</button>
                        </div>
                    </div>
                    
                    <div class="info-card">
                        <h3>市场信息</h3>
                        <p>印尼交易所交易时间: 09:30-12:00, 13:30-16:00 (UTC+7)</p>
                        <p>货币: 印尼盾 (IDR)</p>
                        <p>主要板块: 金融、资源、电信</p>
                    </div>
                </div>
            </div>
        
        <footer>
            <p>数据来源: StockTV API | 本页面仅用于演示目的,不构成投资建议</p>
        </footer>
    </div>

    <script>
        // 初始化ECharts实例
        const chartDom = document.getElementById('chart');
        const myChart = echarts.init(chartDom);
        
        // 当前选中的时间框架和股票
        let currentInterval = '1h';
        let currentStock = 'JCI';
        let currentIndicator = 'ma';
        
        // 模拟K线数据生成函数(实际应用中应从API获取)
        function generateKLineData(interval, count = 100) {
            const basePrice = 7000 + Math.random() * 1000;
            const data = [];
            let timestamp = new Date().getTime();
            
            // 根据时间间隔设置时间步长(毫秒)
            const intervalMs = {
                '1m': 60 * 1000,
                '5m': 5 * 60 * 1000,
                '15m': 15 * 60 * 1000,
                '30m': 30 * 60 * 1000,
                '1h': 60 * 60 * 1000,
                '1d': 24 * 60 * 60 * 1000,
                '1w': 7 * 24 * 60 * 60 * 1000,
                '1mo': 30 * 24 * 60 * 60 * 1000
            }[interval] || 60 * 60 * 1000;
            
            // 生成倒序数据(最新的数据在最后)
            for (let i = count - 1; i >= 0; i--) {
                const time = timestamp - i * intervalMs;
                const open = i === count - 1 ? basePrice : data[data.length - 1][1];
                const volatility = 10 + Math.random() * 20;
                const change = (Math.random() - 0.5) * volatility;
                const close = open + change;
                const high = Math.max(open, close) + Math.random() * volatility / 2;
                const low = Math.min(open, close) - Math.random() * volatility / 2;
                const volume = Math.floor(Math.random() * 1000000) + 100000;
                
                data.push([
                    echarts.format.formatTime('yyyy-MM-dd HH:mm', time),
                    open,
                    close,
                    low,
                    high,
                    volume
                ]);
            }
            
            return data;
        }
        
        // 计算移动平均线
        function calculateMA(dayCount, data) {
            const result = [];
            for (let i = 0; i < data.length; i++) {
                if (i < dayCount) {
                    result.push('-');
                    continue;
                }
                let sum = 0;
                for (let j = 0; j < dayCount; j++) {
                    sum += data[i - j][2]; // 收盘价
                }
                result.push(+(sum / dayCount).toFixed(2));
            }
            return result;
        }
        
        // 更新图表
        function updateChart() {
            const klineData = generateKLineData(currentInterval);
            
            // 更新信息面板
            const latestData = klineData[klineData.length - 1];
            document.getElementById('currentPrice').textContent = latestData[2].toLocaleString();
            document.getElementById('openPrice').textContent = latestData[1].toLocaleString();
            document.getElementById('highPrice').textContent = latestData[4].toLocaleString();
            document.getElementById('lowPrice').textContent = latestData[3].toLocaleString();
            
            const change = latestData[2] - latestData[1];
            const changePercent = (change / latestData[1] * 100).toFixed(2);
            const changeElement = document.getElementById('priceChange');
            changeElement.textContent = `${change >= 0 ? '+' : ''}${change.toFixed(2)} (${changePercent}%)`;
            changeElement.className = `change ${change >= 0 ? 'positive' : 'negative'}`;
            
            // 更新时间信息
            document.getElementById('timeInfo').textContent = `数据更新时间: ${new Date().toLocaleString()}`;
            
            // 计算技术指标
            const ma5 = calculateMA(5, klineData);
            const ma10 = calculateMA(10, klineData);
            const ma20 = calculateMA(20, klineData);
            
            // 配置图表选项
            const option = {
                backgroundColor: 'transparent',
                animation: false,
                legend: {
                    top: 10,
                    left: 'center',
                    data: [currentStock, 'MA5', 'MA10', 'MA20'],
                    textStyle: {
                        color: '#e0e0e0'
                    }
                },
                tooltip: {
                    trigger: 'axis',
                    axisPointer: {
                        type: 'cross'
                    },
                    borderWidth: 1,
                    borderColor: '#2a4d7a',
                    backgroundColor: 'rgba(16, 30, 44, 0.9)',
                    textStyle: {
                        color: '#e0e0e0'
                    },
                    formatter: function (params) {
                        const data = params[0].data;
                        return `
                            <div>时间: ${data[0]}</div>
                            <div>开盘: ${data[1]}</div>
                            <div>收盘: ${data[2]}</div>
                            <div>最低: ${data[3]}</div>
                            <div>最高: ${data[4]}</div>
                            <div>成交量: ${data[5].toLocaleString()}</div>
                        `;
                    }
                },
                grid: {
                    top: 80,
                    left: 50,
                    right: 50,
                    bottom: 80
                },
                xAxis: {
                    type: 'category',
                    data: klineData.map(item => item[0]),
                    scale: true,
                    boundaryGap: false,
                    axisLine: {onZero: false},
                    splitLine: {show: false},
                    splitNumber: 20,
                    axisLabel: {
                        color: '#90a4ae',
                        formatter: function (value) {
                            const date = new Date(value);
                            if (currentInterval === '1d' || currentInterval === '1w' || currentInterval === '1mo') {
                                return echarts.format.formatTime('MM-dd', date);
                            } else {
                                return echarts.format.formatTime('HH:mm', date);
                            }
                        }
                    }
                },
                yAxis: {
                    scale: true,
                    splitArea: {
                        show: true,
                        areaStyle: {
                            color: ['rgba(255,255,255,0.02)', 'rgba(0,0,0,0.02)']
                        }
                    },
                    axisLabel: {
                        color: '#90a4ae',
                        formatter: function (value) {
                            return value.toLocaleString();
                        }
                    }
                },
                dataZoom: [
                    {
                        type: 'inside',
                        start: 50,
                        end: 100
                    },
                    {
                        show: true,
                        type: 'slider',
                        top: '90%',
                        height: 40,
                        borderColor: '#1a2a3a',
                        textStyle: {
                            color: '#90a4ae'
                        },
                        handleStyle: {
                            color: '#2a4d7a'
                        }
                    }
                ],
                series: [
                    {
                        name: currentStock,
                        type: 'candlestick',
                        data: klineData,
                        itemStyle: {
                            color: '#ef5350',
                            color0: '#26a69a',
                            borderColor: '#ef5350',
                            borderColor0: '#26a69a'
                        },
                        markPoint: {
                            label: {
                                formatter: function (param) {
                                    return param != null ? Math.round(param.value) : '';
                                }
                            },
                            data: [
                                {
                                    name: '最高值',
                                    type: 'max',
                                    valueDim: 'highest'
                                },
                                {
                                    name: '最低值',
                                    type: 'min',
                                    valueDim: 'lowest'
                                }
                            ]
                        }
                    },
                    {
                        name: 'MA5',
                        type: 'line',
                        data: ma5,
                        smooth: true,
                        showSymbol: false,
                        lineStyle: {
                            width: 1,
                            color: '#4fc3f7'
                        }
                    },
                    {
                        name: 'MA10',
                        type: 'line',
                        data: ma10,
                        smooth: true,
                        showSymbol: false,
                        lineStyle: {
                            width: 1,
                            color: '#fdd835'
                        }
                    },
                    {
                        name: 'MA20',
                        type: 'line',
                        data: ma20,
                        smooth: true,
                        showSymbol: false,
                        lineStyle: {
                            width: 1,
                            color: '#ab47bc'
                        }
                    },
                    {
                        name: '成交量',
                        type: 'bar',
                        xAxisIndex: 1,
                        yAxisIndex: 1,
                        data: klineData.map(item => item[5]),
                        itemStyle: {
                            color: function(params) {
                                const data = klineData[params.dataIndex];
                                return data[2] >= data[1] ? '#26a69a' : '#ef5350';
                            }
                        }
                    }
                ]
            };
            
            // 设置图表选项
            myChart.setOption(option);
        }
        
        // 初始化事件监听
        function initEventListeners() {
            // 时间框架切换
            document.querySelectorAll('.interval-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('.interval-btn').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    currentInterval = this.getAttribute('data-interval');
                    updateChart();
                });
            });
            
            // 股票选择
            document.getElementById('stockSelect').addEventListener('change', function() {
                currentStock = this.value;
                updateChart();
            });
            
            // 技术指标切换
            document.querySelectorAll('.indicator-btn').forEach(btn => {
                btn.addEventListener('click', function() {
                    document.querySelectorAll('.indicator-btn').forEach(b => b.classList.remove('active'));
                    this.classList.add('active');
                    currentIndicator = this.getAttribute('data-indicator');
                    // 这里可以添加切换技术指标的逻辑
                });
            });
            
            // 窗口大小变化时调整图表大小
            window.addEventListener('resize', function() {
                myChart.resize();
            });
        }
        
        // 页面加载完成后初始化
        document.addEventListener('DOMContentLoaded', function() {
            initEventListeners();
            updateChart();
        });
    </script>
</body>
</html>

功能说明

这个HTML页面提供了以下功能:

  1. 多时间框架切换:支持1分钟、5分钟、15分钟、30分钟、1小时、日线、周线和月线视图
  2. 股票选择:可以查看雅加达综合指数(JCI)和印尼主要股票(如BBCA、BBRI等)
  3. 技术指标:包含MA(移动平均线)等常用技术指标
  4. 实时数据展示:显示当前价格、涨跌幅、开盘价、最高价和最低价
  5. 交互功能:支持图表缩放、平移等操作

技术实现

页面使用ECharts库绘制K线图,这是一个功能强大的数据可视化库。通过JavaScript生成模拟的K线数据(实际应用中应替换为真实的API调用)。 页面设计采用暗色主题,符合金融数据展示的常见风格,确保数据可视性并减少视觉疲劳。

如何使用

  1. 将上述代码保存为HTML文件
  2. 在浏览器中打开该文件
  3. 使用左侧面板切换不同的时间框架和股票
  4. 通过图表下方的滑块缩放和平移K线图

在实际应用中,您需要将模拟数据生成部分替换为真实的API调用,例如使用StockTV等金融数据服务商提供的接口。

相关推荐
小满zs18 小时前
Next.js第十四章(缓存策略)
前端
低保和光头哪个先来18 小时前
CSS+JS实现单例老虎机切换图片动画
前端·javascript·css
坐公交也用券18 小时前
适用于vue3+pnpm项目自动化类型检查及构建的Python脚本
开发语言·javascript·python·typescript·自动化
IT_陈寒19 小时前
Vue3性能优化实战:这5个技巧让我的应用加载速度提升了40%
前端·人工智能·后端
长征coder19 小时前
SpringCloud服务优雅下线LoadBalancer 缓存配置方案
java·后端·spring
小小鸟00819 小时前
Vue响应式原理
前端·javascript·vue.js
lee57619 小时前
鄙人的 Vue 3.0 商业级开源甘特图已经发布到 npm
前端·vue.js·npm·开源·甘特图
前端老曹19 小时前
vue3 三级路由无法缓存的终终终终终终极解决方案
前端·javascript·vue.js
零Suger19 小时前
React Router v7数据模式使用指南
javascript·笔记·react.js
1024小神19 小时前
uniapp + vue3 + scss 定义全局样式变量,并使用
前端·uni-app·scss