Files
smart-fin-chart/js/charts/fx.js
T
2026-08-23 21:23:41 +08:00

188 lines
5.7 KiB
JavaScript
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.
import {
PALETTE, FONT, axisCommon, categoryAxis, titleStyle,
lineSeries, footnote, watermarks,
} from '../theme.js';
/** 圆角卡片:ECharts 的 graphic.group + rect + 多段文字 */
function infoCard({ left, top, width, title, lines, fill, stroke, titleColor }) {
const children = [
{
type: 'rect',
shape: { x: 0, y: 0, width, height: 108, r: 8 },
style: { fill, stroke, lineWidth: 1 },
},
{
type: 'text',
style: {
x: 20, y: 20,
text: title,
fill: titleColor,
fontSize: 15,
fontWeight: 'bold',
fontFamily: FONT,
},
},
];
lines.forEach((line, i) => {
children.push({
type: 'text',
style: {
x: 20, y: 52 + i * 22,
text: line,
fill: '#555',
fontSize: 12.5,
fontFamily: FONT,
},
});
});
return { type: 'group', left, top, silent: true, children };
}
/**
* 图 5:人民币兑美元两种口径对比
* 只有两个时间点,重点在文字说明和端点标注,不在曲线本身
*/
export function fxChart(rows, { width, height }) {
const stages = rows.map((r) => r.stage);
const mid = rows.map((r) => r.mid_price);
const spot = rows.map((r) => r.onshore_spot);
// label.position 不接受函数(写函数会被静默忽略,退化成默认位置),
// 想让首尾点位置不同只能逐点覆盖:起点标在上方,末点标在右侧
const endpointLabels = (values) =>
values.map((v, i) => ({
value: v,
label: { position: i === values.length - 1 ? 'right' : 'top' },
}));
// 偏离点数 = (中间价 - 即期)× 10000
const gapStart = Math.round((mid[0] - spot[0]) * 10000);
const gapEnd = Math.round((mid[1] - spot[1]) * 10000);
const midGain = Math.round((mid[0] - mid[1]) * 10000);
const spotGain = Math.round((spot[0] - spot[1]) * 10000);
const midPct = (((mid[0] - mid[1]) / mid[0]) * 100).toFixed(1);
const spotPct = (((spot[0] - spot[1]) / spot[0]) * 100).toFixed(1);
const cardWidth = 340;
const cardTop = 132;
return {
backgroundColor: '#fff',
title: {
...titleStyle,
text: '人民币兑美元的两种「升值」口径:中间价 vs 在岸即期',
subtext: '口径区别在「谁定的价」:中间价是央行每日公布的锚,即期是市场在锚附近交易出来的成交价',
top: 20,
},
grid: { left: 96, right: 110, top: 340, bottom: 88 },
tooltip: { trigger: 'axis', textStyle: { fontFamily: FONT } },
xAxis: {
...categoryAxis,
boundaryGap: ['12%', '12%'],
data: stages,
},
yAxis: {
...axisCommon,
type: 'value',
name: 'USDCNY(数值越小=人民币越强)',
nameLocation: 'middle',
nameGap: 62,
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
min: 6.75,
max: 7.05,
interval: 0.1,
axisLabel: {
...axisCommon.axisLabel,
formatter: (v) => v.toFixed(2),
},
},
series: [
{
...lineSeries({
name: '中间价',
data: mid,
color: '#3b6ea5',
formatter: (p) => p.value.toFixed(4),
}),
data: endpointLabels(mid),
symbolSize: 11,
itemStyle: { color: '#3b6ea5' },
label: {
show: true,
fontSize: 15,
fontWeight: 'bold',
color: '#3b6ea5',
fontFamily: FONT,
distance: 10,
formatter: (p) => p.value.toFixed(4),
},
},
{
...lineSeries({
name: '在岸即期',
data: spot,
color: PALETTE.coral,
formatter: (p) => p.value.toFixed(4),
}),
data: endpointLabels(spot),
symbolSize: 11,
itemStyle: { color: PALETTE.coral },
label: {
show: true,
fontSize: 15,
fontWeight: 'bold',
color: PALETTE.coral,
fontFamily: FONT,
distance: 10,
formatter: (p) => p.value.toFixed(4),
},
},
],
graphic: [
...watermarks(width, height),
infoCard({
left: 88, top: cardTop, width: cardWidth,
title: '中间价(央行公布的「锚」)',
lines: [
'每交易日 9:15 由央行公布:做市商报价',
'加权+逆周期因子,是当日波动中心与政策信号',
],
fill: '#eef4fa', stroke: '#c7dcf0', titleColor: '#2f5f8f',
}),
infoCard({
left: 88 + cardWidth + 24, top: cardTop, width: cardWidth,
title: '在岸即期(市场成交价)',
lines: [
'银行间外汇市场实时成交形成,16:30 收盘价',
'最常被引用;每日只能在中间价 ±2% 区间内波动',
],
fill: '#fdf1ed', stroke: '#f5d3c8', titleColor: '#c05a45',
}),
// 图例改成两行文字说明,信息量比色块图例大
{
type: 'text',
left: 108, top: 272, silent: true,
style: {
text: `● 中间价:${mid[0]}${mid[1]},累计升值 ${midGain}点(+${midPct}%`,
fill: '#3b6ea5', fontSize: 14, fontFamily: FONT,
},
},
{
type: 'text',
left: 108, top: 296, silent: true,
style: {
text: `● 在岸即期:${spot[0]}${spot[1]},累计升值 ${spotGain}点(+${spotPct}%`,
fill: PALETTE.coral, fontSize: 14, fontFamily: FONT,
},
},
footnote(
`注:涨幅差${midGain - spotGain}点=期初偏离${gapStart}−期末偏离${gapEnd}(即期偏强收窄)|` +
'汇兑重估用市场汇率而非中间价|数据:新华财经|Joey 谨制'
),
],
};
}