feat(selection): show gold brick chart below volume in stock detail panel

Add an optional brick_chart series to the selection chart API, computed
from the shared gold-brick formula when strategy=gold_brick, and render
a fourth grid with red/green brick bars between the volume and J grids.
This commit is contained in:
yuxuanhui
2026-09-05 13:41:25 +08:00
parent 79476252b1
commit 3b50e75ef8
13 changed files with 249 additions and 36 deletions
@@ -16,6 +16,9 @@ import type { SelectionChartPoint } from "../api/selection.types"
const DEFAULT_VISIBLE_POINTS = 120
const BRICK_UP_COLOR = "#dc2626"
const BRICK_DOWN_COLOR = "#16a34a"
const zhixingLineSeries = [
{ color: "#a8a8a8", key: "trend_white", name: "知行白线" },
{ color: "#eab308", key: "trend_yellow", name: "知行黄线" },
@@ -33,9 +36,12 @@ export type SelectionChartOption = ComposeOption<
>
/**
* Build the three-grid financial chart configuration from aligned API points.
* Build the financial chart configuration from aligned API points.
*
* @param points Ascending qfq OHLCV, J, and Zhixing-line points from the API.
* When any point carries gold-brick values, a brick grid is inserted below
* the volume grid; otherwise the original three-grid layout is kept.
*
* @param points Ascending qfq OHLCV, J, Zhixing-line, and brick points.
* @returns One ECharts option with synchronized axes and an exact 120-point window.
*/
export function buildSelectionChartOption(
@@ -46,6 +52,10 @@ export function buildSelectionChartOption(
const startValue = dates[startIndex]
const endValue = dates.at(-1)
const zoomRange = { endValue, startValue }
const showBrickChart = points.some(
(point) => point.brick_chart !== null && point.brick_chart !== undefined,
)
const axisCount = showBrickChart ? 4 : 3
return {
animation: false,
@@ -53,21 +63,28 @@ export function buildSelectionChartOption(
{
...zoomRange,
type: "inside",
xAxisIndex: [0, 1, 2],
xAxisIndex: [...Array(axisCount).keys()],
},
{
...zoomRange,
bottom: 4,
height: 20,
type: "slider",
xAxisIndex: [0, 1, 2],
xAxisIndex: [...Array(axisCount).keys()],
},
],
grid: [
{ bottom: "46%", left: 64, right: 24, top: 36 },
{ bottom: "28%", height: "14%", left: 64, right: 24 },
{ bottom: 48, height: "20%", left: 64, right: 24 },
],
grid: showBrickChart
? [
{ bottom: "52%", left: 64, right: 24, top: 36 },
{ bottom: "37%", height: "12%", left: 64, right: 24 },
{ bottom: "24%", height: "10%", left: 64, right: 24 },
{ bottom: 48, height: "13%", left: 64, right: 24 },
]
: [
{ bottom: "46%", left: 64, right: 24, top: 36 },
{ bottom: "28%", height: "14%", left: 64, right: 24 },
{ bottom: 48, height: "20%", left: 64, right: 24 },
],
legend: [
{
data: zhixingLineSeries.map((line) => line.name),
@@ -75,9 +92,9 @@ export function buildSelectionChartOption(
top: 8,
},
{
data: ["J"],
data: showBrickChart ? ["金砖", "J"] : ["J"],
right: 20,
top: "73%",
top: showBrickChart ? "77%" : "73%",
},
],
series: [
@@ -126,6 +143,7 @@ export function buildSelectionChartOption(
xAxisIndex: 1,
yAxisIndex: 1,
},
...(showBrickChart ? [buildBrickSeries(points)] : []),
{
connectNulls: false,
data: points.map((point) => point.j),
@@ -137,32 +155,65 @@ export function buildSelectionChartOption(
showSymbol: false,
smooth: false,
type: "line",
xAxisIndex: 2,
yAxisIndex: 2,
xAxisIndex: axisCount - 1,
yAxisIndex: axisCount - 1,
},
],
title: [
{ left: 12, text: "日线", textStyle: { fontSize: 12 }, top: 8 },
{ left: 72, text: "成交量", textStyle: { fontSize: 12 }, top: "54%" },
{ left: 72, text: "J", textStyle: { fontSize: 12 }, top: "72%" },
],
title: showBrickChart
? [
{ left: 12, text: "日线", textStyle: { fontSize: 12 }, top: 8 },
{ left: 72, text: "成交量", textStyle: { fontSize: 12 }, top: "50%" },
{ left: 72, text: "金砖", textStyle: { fontSize: 12 }, top: "65%" },
{ left: 72, text: "J", textStyle: { fontSize: 12 }, top: "77%" },
]
: [
{ left: 12, text: "日线", textStyle: { fontSize: 12 }, top: 8 },
{ left: 72, text: "成交量", textStyle: { fontSize: 12 }, top: "54%" },
{ left: 72, text: "J", textStyle: { fontSize: 12 }, top: "72%" },
],
tooltip: {
axisPointer: { type: "cross" },
trigger: "axis",
},
xAxis: [
buildCategoryAxis(dates, 0, false),
buildCategoryAxis(dates, 1, false),
buildCategoryAxis(dates, 2, true),
...Array.from({ length: axisCount - 1 }, (_, gridIndex) =>
buildCategoryAxis(dates, gridIndex, false),
),
buildCategoryAxis(dates, axisCount - 1, true),
],
yAxis: [
buildValueAxis(0),
buildValueAxis(1),
buildValueAxis(2, { max: 120, min: -20 }),
...(showBrickChart ? [buildValueAxis(2)] : []),
showBrickChart
? buildValueAxis(3, { max: 120, min: -20 })
: buildValueAxis(2, { max: 120, min: -20 }),
],
}
}
function buildBrickSeries(points: SelectionChartPoint[]): BarSeriesOption {
return {
data: points.map((point, index) => {
const previous = points[index - 1]
const rising =
previous?.brick_chart != null &&
point.brick_chart != null &&
point.brick_chart > previous.brick_chart
return {
itemStyle: {
color: rising ? BRICK_UP_COLOR : BRICK_DOWN_COLOR,
},
value: point.brick_chart ?? 0,
}
}),
name: "金砖",
type: "bar",
xAxisIndex: 2,
yAxisIndex: 2,
}
}
function toCandlestickValue(point: SelectionChartPoint) {
if (
point.open === null ||