Files
Quant_Code/999.账户相关/simnow_trader/traderdata/templates/kline6.html

402 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-Content-Type-Options" content="nosniff">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>实时K线图</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<style>
#kline-chart {
width: 100%;
height: 800px;
margin: 20px auto;
}
.symbol-selector {
margin: 20px;
text-align: center;
}
button {
margin: 10px;
padding: 10px;
cursor: pointer;
}
.active-symbol {
background-color: #e0e0e0;
}
body {
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
</style>
</head>
<body>
<div class="symbol-selector" id="symbol-buttons">
<!-- 动态生成按钮 -->
</div>
<div id="kline-chart"></div>
<script>
let currentSymbol = null;
const socket = io();
const symbolButtons = document.getElementById('symbol-buttons');
let chart = null;
// 初始化图表
function initChart() {
if (!chart) {
chart = echarts.init(document.getElementById('kline-chart'));
}
}
// 初始化数据
fetch('/api/data')
.then(response => response.json())
.then(data => {
updateSymbolButtons(data);
if (Object.keys(data).length > 0) {
currentSymbol = Object.keys(data)[0];
updateChart(data[currentSymbol]);
}
})
.catch(error => {
console.error('Error fetching data:', error);
});
// WebSocket事件处理
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('data_update', (data) => {
updateSymbolButtons(data);
if (currentSymbol && data[currentSymbol]) {
updateChart(data[currentSymbol]);
}
});
function updateSymbolButtons(data) {
symbolButtons.innerHTML = '';
Object.keys(data).forEach(symbol => {
const button = document.createElement('button');
button.textContent = symbol;
button.onclick = () => {
currentSymbol = symbol;
updateChart(data[symbol]);
};
if (symbol === currentSymbol) {
button.classList.add('active-symbol');
}
symbolButtons.appendChild(button);
});
}
function updateChart(data) {
initChart();
// 准备数据
const dates = data.map(item => item.datetime);
const klineData = data.map(item => [
parseFloat(item.open),
parseFloat(item.close),
parseFloat(item.low),
parseFloat(item.high)
]);
const volumes = data.map(item => parseFloat(item.volume));
const ultimateValues = data.map(item => parseFloat(item.终极平滑值));
// 处理POC数据将缺值替换为前一个有效值
let pocValues = data.map(item => item.POC);
let lastValidPoc = null;
pocValues = pocValues.map(value => {
if (value === '缺值') {
return lastValidPoc;
} else {
lastValidPoc = parseFloat(value);
return lastValidPoc;
}
});
// 处理delta累计数据将缺值替换为前一个有效值
let deltaSumValues = data.map(item => item.delta累计);
let lastValidDelta = null;
deltaSumValues = deltaSumValues.map(value => {
if (value === '缺值') {
return lastValidDelta;
} else {
lastValidDelta = parseFloat(value);
return lastValidDelta;
}
});
// 处理dj数据将缺值替换为前一个有效值
let djValues = data.map(item => {
const dj = item.dj;
return dj === '缺值' ? null : parseFloat(dj);
});
// 计算240日均线
const closes = data.map(item => parseFloat(item.close));
const ma240 = calculateMA(closes, 240);
// 配置图表选项
const option = {
title: {
text: `${currentSymbol} K线图`,
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
legend: {
data: ['K线', '240日均线', '终极平滑值', 'POC', 'Delta累计'],
top: 30
},
grid: [
{
left: '10%',
right: '8%',
height: '60%',
top: '5%'
},
{
left: '10%',
right: '8%',
top: '70%',
height: '15%'
},
{
left: '10%',
right: '8%',
top: '90%',
height: '15%'
}
],
xAxis: [
{
type: 'category',
data: dates,
scale: true,
boundaryGap: false,
axisLine: {onZero: false},
splitLine: {show: false},
splitNumber: 20,
gridIndex: 0,
axisLabel: {
formatter: function(value) {
return value.split(' ')[0];
}
}
},
{
type: 'category',
gridIndex: 1,
data: dates,
axisLabel: {show: false},
splitLine: {show: false},
axisLine: {show: false},
splitNumber: 20
},
{
type: 'category',
gridIndex: 2,
data: dates,
axisLabel: {show: false},
splitLine: {show: false},
axisLine: {show: false},
splitNumber: 20
}
],
yAxis: [
{
scale: true,
splitArea: {
show: true
},
gridIndex: 0,
position: 'right'
},
{
scale: true,
gridIndex: 1,
splitNumber: 2,
axisLabel: {show: false},
axisLine: {show: false},
splitLine: {show: false}
},
{
scale: true,
gridIndex: 2,
splitNumber: 2,
axisLabel: {show: true},
axisLine: {show: true},
splitLine: {show: false},
position: 'right'
}
],
dataZoom: [
{
type: 'inside',
xAxisIndex: [0, 1, 2],
start: 50,
end: 100,
bottom: '5%'
},
{
show: true,
xAxisIndex: [0, 1, 2],
type: 'slider',
bottom: '5%',
start: 50,
end: 100
}
],
series: [
{
name: 'K线',
type: 'candlestick',
data: klineData,
itemStyle: {
color: '#ef232a',
color0: '#14b143',
borderColor: '#ef232a',
borderColor0: '#14b143'
},
barWidth: '80%'
},
{
name: '240日均线',
type: 'line',
data: ma240,
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: '终极平滑值',
type: 'line',
data: ultimateValues,
smooth: true,
lineStyle: {
opacity: 0.5
}
},
{
name: 'POC',
type: 'line',
data: pocValues,
smooth: true,
lineStyle: {
color: '#FFD700',
width: 2,
opacity: 0.8
},
symbol: 'circle',
symbolSize: 6
},
{
name: '成交量',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 1,
data: volumes
},
{
name: 'Delta累计',
type: 'line',
xAxisIndex: 2,
yAxisIndex: 2,
data: deltaSumValues,
smooth: true,
lineStyle: {
color: '#4169E1',
width: 2,
opacity: 0.8
},
markLine: {
silent: true,
data: [
{
yAxis: 0,
lineStyle: {
color: '#999',
type: 'dashed'
}
}
]
}
},
{
name: 'dj值',
type: 'custom',
renderItem: function (params, api) {
const dataIndex = params.dataIndex;
const djValue = djValues[dataIndex];
if (djValue === null || djValue === undefined) return;
const pos = api.coord([dataIndex, klineData[dataIndex][3]]);
if (!pos) return;
const color = djValue >= 8 ? '#ef232a' :
djValue <= -8 ? '#14b143' :
'#FFD700';
return {
type: 'text',
style: {
text: `dj=${djValue}`,
textAlign: 'center',
textVerticalAlign: 'middle',
fill: color,
fontSize: 12,
fontWeight: 'bold'
},
position: [pos[0], pos[1] + 20]
};
},
data: klineData.map((item, index) => index)
}
]
};
// 使用配置项显示图表
chart.setOption(option);
}
function calculateMA(data, dayCount) {
const result = [];
for (let i = 0, len = data.length; i < len; i++) {
if (i < dayCount - 1) {
result.push('-');
continue;
}
let sum = 0;
for (let j = 0; j < dayCount; j++) {
sum += data[i - j];
}
result.push(+(sum / dayCount).toFixed(2));
}
return result;
}
// 响应窗口大小变化
window.addEventListener('resize', function() {
if (chart) {
chart.resize();
}
});
// 初始化图表
initChart();
</script>
</body>
</html>