feat: 增加简约行情展示
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
<html lang='zh'>
|
<html lang='zh'>
|
||||||
<head>
|
<head>
|
||||||
<meta charset='utf-8'/>
|
<meta charset='utf-8'/>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||||
<meta name='viewport' content='width=device-width, initial-scale=1.0'/>
|
<meta content='width=device-width, initial-scale=1.0' name='viewport'/>
|
||||||
<title>Strategy</title>
|
<title>Strategy</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/antd.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/helper.min.css" rel="stylesheet"/>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css"/>
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/iconfont.min.css" rel="stylesheet"/>
|
||||||
<style>
|
<style>
|
||||||
html, body, #root {
|
html, body, #root {
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -21,226 +21,145 @@
|
|||||||
<div id='root'></div>
|
<div id='root'></div>
|
||||||
</body>
|
</body>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/amis/6.13.0/sdk.min.js"></script>
|
||||||
<script type='text/javascript' th:inline="javascript">
|
<script th:inline="javascript" type='text/javascript'>
|
||||||
function candleChart(title, data) {
|
// Market data as KV: { 'YYYY-MM-DD': [open, close, low, high], ... }
|
||||||
return {
|
const data = {
|
||||||
type: 'service',
|
'2025-01-01': [100, 105, 98, 108],
|
||||||
data: data,
|
'2025-01-02': [105, 102, 100, 107],
|
||||||
body: {
|
'2025-01-03': [102, 110, 101, 112],
|
||||||
className: 'mt-2',
|
'2025-01-04': [110, 108, 106, 113],
|
||||||
type: 'chart',
|
'2025-01-05': [108, 115, 107, 116],
|
||||||
height: 800,
|
'2025-01-06': [115, 117, 114, 120],
|
||||||
config: {
|
'2025-01-07': [117, 112, 111, 119],
|
||||||
title: {
|
'2025-01-08': [112, 118, 110, 121],
|
||||||
text: title,
|
'2025-01-09': [118, 121, 117, 123],
|
||||||
},
|
'2025-01-10': [121, 119, 118, 122],
|
||||||
backgroundColor: '#fff',
|
};
|
||||||
animation: true,
|
|
||||||
animationDuration: 1000,
|
|
||||||
tooltip: {
|
|
||||||
trigger: 'axis',
|
|
||||||
axisPointer: {
|
|
||||||
type: 'cross',
|
|
||||||
},
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
||||||
borderColor: '#333',
|
|
||||||
borderWidth: 1,
|
|
||||||
textStyle: {
|
|
||||||
color: '#fff',
|
|
||||||
fontSize: 12,
|
|
||||||
},
|
|
||||||
padding: 12,
|
|
||||||
formatter: function (params) {
|
|
||||||
const param = params[0]
|
|
||||||
const open = parseFloat(param.data[1]).toFixed(2)
|
|
||||||
const close = parseFloat(param.data[2]).toFixed(2)
|
|
||||||
const lowest = parseFloat(param.data[3]).toFixed(2)
|
|
||||||
const highest = parseFloat(param.data[4]).toFixed(2)
|
|
||||||
|
|
||||||
return `<div class="text-center font-bold mb-2">${param.name}</div>
|
// Derive arrays for the chart from the KV map
|
||||||
<div class="text-center">
|
const dates = Object.keys(data).sort();
|
||||||
<span>开盘:</span>
|
// For custom rendering, augment with category index: [idx, open, close, low, high]
|
||||||
<span class="font-bold ml-4">${open}</span>
|
const ohlcData = dates.map((d, i) => [i, ...data[d]]);
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
// Styling and helpers
|
||||||
<span>收盘:</span>
|
const UP_COLOR = '#000000'; // up: black
|
||||||
<span class="font-bold ml-4">${close}</span>
|
const DOWN_COLOR = '#9e9e9e'; // down: gray
|
||||||
</div>
|
|
||||||
<div class="text-center">
|
function renderOHLCMinimal(params, api) {
|
||||||
<span>最低:</span>
|
var idx = api.value(0);
|
||||||
<span class="font-bold ml-4">${lowest}</span>
|
var open = api.value(1);
|
||||||
</div>
|
var close = api.value(2);
|
||||||
<div class="text-center">
|
var low = api.value(3);
|
||||||
<span>最高:</span>
|
var high = api.value(4);
|
||||||
<span class="font-bold ml-4">${highest}</span>
|
var up = close >= open;
|
||||||
</div>`
|
|
||||||
},
|
var x = api.coord([idx, 0])[0];
|
||||||
},
|
var highPoint = api.coord([idx, high]);
|
||||||
grid: {
|
var lowPoint = api.coord([idx, low]);
|
||||||
left: '2%',
|
var openPoint = api.coord([idx, open]);
|
||||||
right: '2%',
|
var closePoint = api.coord([idx, close]);
|
||||||
top: '15%',
|
|
||||||
bottom: '15%',
|
var band = api.size([1, 0])[0];
|
||||||
containLabel: true,
|
// Keep dash length unchanged from the previous baseline
|
||||||
},
|
var tick = Math.max(4, Math.min(10, band * 0.4));
|
||||||
xAxis: {
|
var color = up ? UP_COLOR : DOWN_COLOR;
|
||||||
data: '${xList || []}',
|
|
||||||
axisLine: {
|
return {
|
||||||
lineStyle: {
|
type: 'group',
|
||||||
color: '#e0e0e0',
|
children: [
|
||||||
},
|
{
|
||||||
},
|
type: 'line',
|
||||||
axisLabel: {
|
shape: { x1: x, y1: highPoint[1], x2: x, y2: lowPoint[1] },
|
||||||
color: '#666',
|
style: { stroke: color, lineWidth: 1, opacity: 0.9 },
|
||||||
fontWeight: 'bold',
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
yAxis: [
|
|
||||||
{
|
|
||||||
position: 'left',
|
|
||||||
scale: true,
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e0e0e0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#666',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
formatter: function (value) {
|
|
||||||
return value.toFixed(2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'dashed',
|
|
||||||
color: '#f0f0f0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
position: 'right',
|
|
||||||
scale: true,
|
|
||||||
axisLine: {
|
|
||||||
lineStyle: {
|
|
||||||
color: '#e0e0e0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: '#666',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
formatter: function (value) {
|
|
||||||
return value.toFixed(2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
lineStyle: {
|
|
||||||
type: 'dashed',
|
|
||||||
color: '#f0f0f0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
scale: true,
|
|
||||||
axisLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisTick: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
splitLine: {
|
|
||||||
show: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
dataZoom: [
|
|
||||||
{
|
|
||||||
type: 'inside',
|
|
||||||
start: 0,
|
|
||||||
end: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
show: true,
|
|
||||||
type: 'slider',
|
|
||||||
top: '90%',
|
|
||||||
start: 0,
|
|
||||||
end: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'candlestick',
|
|
||||||
data: '${yList || []}',
|
|
||||||
yAxisIndex: 0,
|
|
||||||
itemStyle: {
|
|
||||||
color: '#eb5454',
|
|
||||||
color0: '#4aaa93',
|
|
||||||
borderColor: '#eb5454',
|
|
||||||
borderColor0: '#4aaa93',
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
yAxisIndex: 0,
|
|
||||||
data: '${sma30 || []}',
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'none',
|
|
||||||
lineStyle: {
|
|
||||||
color: 'rgba(0,111,255,0.5)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
yAxisIndex: 0,
|
|
||||||
data: '${sma60 || []}',
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'none',
|
|
||||||
lineStyle: {
|
|
||||||
color: 'rgba(115,0,255,0.5)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'line',
|
|
||||||
yAxisIndex: 1,
|
|
||||||
data: '${sma30Slopes || []}',
|
|
||||||
smooth: true,
|
|
||||||
symbol: 'none',
|
|
||||||
lineStyle: {
|
|
||||||
color: 'rgba(0,255,81,0.5)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
}
|
type: 'line',
|
||||||
|
shape: { x1: x - tick, y1: openPoint[1], x2: x, y2: openPoint[1] },
|
||||||
|
style: { stroke: color, lineWidth: 1.2, opacity: 0.95 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'line',
|
||||||
|
shape: { x1: x, y1: closePoint[1], x2: x + tick, y2: closePoint[1] },
|
||||||
|
style: { stroke: color, lineWidth: 1.6, opacity: 0.95 },
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = /*[[${charts}]]*/ {};
|
function formatTooltip(params) {
|
||||||
|
var p = Array.isArray(params) ? params[0] : params;
|
||||||
|
var v = p.data; // [idx, open, close, low, high]
|
||||||
|
var d = dates[v[0]];
|
||||||
|
var o = v[1], c = v[2], l = v[3], h = v[4];
|
||||||
|
var chg = (c - o);
|
||||||
|
var chgPct = o ? (chg / o * 100) : 0;
|
||||||
|
var sign = chg >= 0 ? '+' : '';
|
||||||
|
return [
|
||||||
|
d,
|
||||||
|
'O: ' + o,
|
||||||
|
'C: ' + c,
|
||||||
|
'H: ' + h,
|
||||||
|
'L: ' + l,
|
||||||
|
'Chg: ' + sign + chg.toFixed(2) + ' (' + sign + chgPct.toFixed(2) + '%)'
|
||||||
|
].join('<br/>');
|
||||||
|
}
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
const amis = amisRequire('amis/embed')
|
const amis = amisRequire('amis/embed')
|
||||||
const amisJson = {
|
const amisJson = {
|
||||||
type: 'page',
|
type: 'page',
|
||||||
title: 'Strategy',
|
title: 'Strategy',
|
||||||
body: Object.keys(data)
|
body: {
|
||||||
.map(key => candleChart(key, data[key])),
|
type: 'tabs',
|
||||||
|
tabsMode: 'vertical',
|
||||||
|
// amis expects `tabs` to be an array. Using an object causes `.map` errors at runtime.
|
||||||
|
tabs: [
|
||||||
|
{
|
||||||
|
title: '测试',
|
||||||
|
body: {
|
||||||
|
type: 'chart',
|
||||||
|
height: 800,
|
||||||
|
// ECharts candlestick example
|
||||||
|
config: {
|
||||||
|
animation: false,
|
||||||
|
// Minimal style: hide legend for a cleaner look
|
||||||
|
legend: { show: false },
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'cross' }, formatter: formatTooltip },
|
||||||
|
grid: { left: '10%', right: '8%', top: 50, bottom: 80 },
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
// Use computed arrays directly to avoid template interpolation issues
|
||||||
|
data: dates,
|
||||||
|
boundaryGap: true,
|
||||||
|
axisLine: { onZero: false },
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
scale: true,
|
||||||
|
},
|
||||||
|
dataZoom: [
|
||||||
|
// Inside zoom for wheel/gesture; default selects all
|
||||||
|
{ type: 'inside', xAxisIndex: 0, start: 0, end: 100 },
|
||||||
|
// Slider placed under the x-axis; default selects all
|
||||||
|
{ show: true, type: 'slider', xAxisIndex: 0, bottom: 16, height: 26, start: 0, end: 100 },
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: 'ohlc',
|
||||||
|
type: 'custom',
|
||||||
|
// Minimal OHLC: neutral stem (high-low) + short dashes at open/close
|
||||||
|
renderItem: renderOHLCMinimal,
|
||||||
|
encode: { x: 0, y: [1, 2, 3, 4] },
|
||||||
|
data: ohlcData,
|
||||||
|
z: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
// `dates` 已直接在图表配置中使用,这里无需通过 amis data 传递
|
||||||
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
amis.embed('#root', amisJson, {}, {theme: 'antd'})
|
||||||
})()
|
})()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user