增加交易策略、交易指标、量化库代码等文件夹
This commit is contained in:
393
999.账户相关/simnow_trader/traderdata/templates/kline5.html
Normal file
393
999.账户相关/simnow_trader/traderdata/templates/kline5.html
Normal file
@@ -0,0 +1,393 @@
|
||||
<!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: '50%'
|
||||
},
|
||||
{
|
||||
left: '10%',
|
||||
right: '8%',
|
||||
top: '65%',
|
||||
height: '15%'
|
||||
},
|
||||
{
|
||||
left: '10%',
|
||||
right: '8%',
|
||||
top: '85%',
|
||||
height: '15%'
|
||||
}
|
||||
],
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: dates,
|
||||
scale: true,
|
||||
boundaryGap: false,
|
||||
axisLine: {onZero: false},
|
||||
splitLine: {show: false},
|
||||
splitNumber: 20,
|
||||
gridIndex: 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
|
||||
},
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
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'
|
||||
}
|
||||
},
|
||||
{
|
||||
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>
|
||||
Reference in New Issue
Block a user