diff --git a/leopard-strategy/src/main/resources/templates/report.html b/leopard-strategy/src/main/resources/templates/report.html
index d11a9c7..367d3b7 100644
--- a/leopard-strategy/src/main/resources/templates/report.html
+++ b/leopard-strategy/src/main/resources/templates/report.html
@@ -34,74 +34,163 @@
'2025-01-08': [112, 118, 110, 121],
'2025-01-09': [118, 121, 117, 123],
'2025-01-10': [121, 119, 118, 122],
- };
-
- // Derive arrays for the chart from the KV map
- const dates = Object.keys(data).sort();
- // For custom rendering, augment with category index: [idx, open, close, low, high]
- const ohlcData = dates.map((d, i) => [i, ...data[d]]);
-
- // Styling and helpers
- const UP_COLOR = '#000000'; // up: black
- const DOWN_COLOR = '#9e9e9e'; // down: gray
-
- function renderOHLCMinimal(params, api) {
- var idx = api.value(0);
- var open = api.value(1);
- var close = api.value(2);
- var low = api.value(3);
- var high = api.value(4);
- var up = close >= open;
-
- var x = api.coord([idx, 0])[0];
- var highPoint = api.coord([idx, high]);
- var lowPoint = api.coord([idx, low]);
- var openPoint = api.coord([idx, open]);
- var closePoint = api.coord([idx, close]);
-
- var band = api.size([1, 0])[0];
- // Keep dash length unchanged from the previous baseline
- var tick = Math.max(4, Math.min(10, band * 0.4));
- var color = up ? UP_COLOR : DOWN_COLOR;
-
- return {
- type: 'group',
- children: [
- {
- type: 'line',
- shape: { x1: x, y1: highPoint[1], x2: x, y2: lowPoint[1] },
- style: { stroke: color, lineWidth: 1, opacity: 0.9 },
- },
- {
- 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 },
- },
- ]
- };
}
- 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('
');
+ // 全局配置(颜色、尺寸、间距等),集中管理,便于统一调整
+ const CONFIG = {
+ colors: {up: '#000000FF', down: '#00000045'},
+ grid: {left: '2%', right: '2%', top: 40, bottom: 110},
+ zoom: {bottom: 16, height: 50},
+ linewidth: {stem: 1.5, openTick: 1.2, closeTick: 1.6, closeLine: 1.5},
+ tick: {min: 4, max: 10, scale: 0.4},
+ }
+
+ // 通用 tooltip 格式化(按索引回读原始 O/H/L/C)
+ function makeTooltipFormatter(dataMap, dates) {
+ return function (params) {
+ let p = Array.isArray(params) ? params[0] : params
+ let idx = p.dataIndex
+ let d = dates[idx]
+ let ohlc = dataMap[d] || []
+ let o = ohlc[0], c = ohlc[1], l = ohlc[2], h = ohlc[3]
+ let chg = (c - o)
+ let chgPct = o ? (chg / o * 100) : 0
+ let sign = chg >= 0 ? '+' : ''
+ return [
+ d,
+ 'O: ' + o,
+ 'C: ' + c,
+ 'H: ' + h,
+ 'L: ' + l,
+ 'Chg: ' + sign + chg.toFixed(2) + ' (' + sign + chgPct.toFixed(2) + '%)',
+ ].join('
')
+ }
+ }
+
+ // 通用基础配置构建(legend/tooltip/grid/xAxis/yAxis/dataZoom)
+ function buildBaseOption(dates, series, formatter) {
+ return {
+ animation: false,
+ legend: {show: false},
+ tooltip: {trigger: 'axis', axisPointer: {type: 'cross'}, formatter},
+ grid: CONFIG.grid,
+ xAxis: {type: 'category', data: dates, boundaryGap: true, axisLine: {onZero: false}},
+ yAxis: {scale: true},
+ dataZoom: [
+ {type: 'inside', xAxisIndex: 0, start: 0, end: 100},
+ {
+ show: true,
+ type: 'slider',
+ xAxisIndex: 0,
+ bottom: CONFIG.zoom.bottom,
+ height: CONFIG.zoom.height,
+ start: 0,
+ end: 100,
+ },
+ ],
+ series,
+ }
+ }
+
+ // Range Band + Close Line(高低区间带 + 收盘线):趋势与波动范围直观
+ function buildRangeCloseOption(dataMap) {
+ const dates = Object.keys(dataMap).sort()
+ const lowArr = dates.map(d => dataMap[d][2])
+ const highArr = dates.map(d => dataMap[d][3])
+ const closeArr = dates.map(d => dataMap[d][1])
+ const rangeArr = highArr.map((h, i) => h - lowArr[i])
+
+ const series = [
+ {
+ name: 'Low',
+ type: 'line',
+ data: lowArr,
+ stack: 'range',
+ symbol: 'none',
+ lineStyle: {width: 0},
+ emphasis: {disabled: true},
+ },
+ {
+ name: 'Range',
+ type: 'line',
+ data: rangeArr,
+ stack: 'range',
+ symbol: 'none',
+ lineStyle: {width: 0},
+ areaStyle: {color: CONFIG.colors.down, opacity: 0.6},
+ z: 2,
+ },
+ {
+ name: 'Close',
+ type: 'line',
+ data: closeArr,
+ symbol: 'none',
+ lineStyle: {color: CONFIG.colors.up, width: CONFIG.linewidth.closeLine},
+ z: 3,
+ },
+ ]
+
+ return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
+ }
+
+ // Minimal OHLC(竖线 + 左/右短横):信息等价于K线但形态简洁
+ function buildOHLCMinimalOption(dataMap) {
+ const dates = Object.keys(dataMap).sort()
+ const ohlcData = dates.map((d, i) => [i, ...dataMap[d]])
+
+ // 自定义渲染:竖线=高低区间;左短横=开盘;右短横=收盘;颜色=涨跌
+ function renderOHLCMinimal(params, api) {
+ let idx = api.value(0)
+ let open = api.value(1)
+ let close = api.value(2)
+ let low = api.value(3)
+ let high = api.value(4)
+ let up = close >= open
+
+ let x = api.coord([idx, 0])[0]
+ let highPoint = api.coord([idx, high])
+ let lowPoint = api.coord([idx, low])
+ let openPoint = api.coord([idx, open])
+ let closePoint = api.coord([idx, close])
+
+ let band = api.size([1, 0])[0]
+ let tick = Math.max(CONFIG.tick.min, Math.min(CONFIG.tick.max, band * CONFIG.tick.scale))
+ let color = up ? CONFIG.colors.up : CONFIG.colors.down
+
+ return {
+ type: 'group',
+ children: [
+ {
+ type: 'line',
+ shape: {x1: x, y1: highPoint[1], x2: x, y2: lowPoint[1]},
+ style: {stroke: color, lineWidth: CONFIG.linewidth.stem, opacity: 0.9},
+ },
+ {
+ type: 'line',
+ shape: {x1: x - tick, y1: openPoint[1], x2: x, y2: openPoint[1]},
+ style: {stroke: color, lineWidth: CONFIG.linewidth.openTick, opacity: 0.95},
+ },
+ {
+ type: 'line',
+ shape: {x1: x, y1: closePoint[1], x2: x + tick, y2: closePoint[1]},
+ style: {stroke: color, lineWidth: CONFIG.linewidth.closeTick, opacity: 0.95},
+ },
+ ],
+ }
+ }
+
+ const series = [
+ {
+ name: 'ohlc',
+ type: 'custom',
+ renderItem: renderOHLCMinimal,
+ encode: {x: 0, y: [1, 2, 3, 4]},
+ data: ohlcData,
+ z: 10,
+ },
+ ]
+
+ return buildBaseOption(dates, series, makeTooltipFormatter(dataMap, dates))
}
(function () {
@@ -112,49 +201,22 @@
body: {
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 },
+ title: 'Charts',
+ // 在一个 Tab 中展示两张图,便于同屏对比
+ body: [
+ {
+ type: 'chart',
+ height: 500,
+ config: buildRangeCloseOption(data),
},
- yAxis: {
- scale: true,
+ {
+ type: 'chart',
+ height: 500,
+ config: buildOHLCMinimalOption(data),
},
- 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,
- },
- ],
- },
- },
+ ],
},
],
},