feat: 增加 DCF 等估值工具
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
const fmt = value => new Intl.NumberFormat('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2}).format(value);
|
||||
|
||||
/** Pure option builder; the calculator owns chart mounting, resizing and disposal. */
|
||||
export function buildDCFPriceOption({range, price, colors, width, reducedMotion = false}) {
|
||||
const {low, high, perShare} = range;
|
||||
const max = Math.max(high * 1.3, perShare * 1.6, price === undefined ? 0 : price * 1.2);
|
||||
if (!Number.isFinite(max)) throw new Error('股价超出有效图表范围');
|
||||
const plotWidth = Math.max(1, width - 40);
|
||||
const label = (text, amount) => ({show: amount / max * plotWidth > 40, position: 'inside', formatter: text, color: colors.ink, fontSize: 12});
|
||||
const series = [
|
||||
{name: `低估:低于 ${fmt(low)} 元`, type: 'bar', stack: 'zone', data: [low], barWidth: 42,
|
||||
itemStyle: {color: colors.low}, label: label('低估', low)},
|
||||
{name: `合理:${fmt(low)}—${fmt(high)} 元`, type: 'bar', stack: 'zone', data: [high - low],
|
||||
itemStyle: {color: colors.fair}, label: label('合理', high - low),
|
||||
markLine: {silent: true, symbol: ['none', 'none'], lineStyle: {type: 'dashed', color: colors.accent, width: 1.5},
|
||||
label: {show: true, position: 'end', formatter: `DCF ${fmt(perShare)}`, align: perShare / max < .18 ? 'left' : 'center', color: colors.ink, fontSize: 12},
|
||||
data: [{xAxis: perShare}]}},
|
||||
{name: `高估:高于 ${fmt(high)} 元`, type: 'bar', stack: 'zone', data: [max - high],
|
||||
itemStyle: {color: colors.high}, label: label('高估', max - high)},
|
||||
];
|
||||
if (price !== undefined) series.push({name: '当前股价', type: 'scatter', data: [[price, 0]], symbol: 'triangle', symbolSize: 13,
|
||||
symbolOffset: [0, 31], itemStyle: {color: colors.accent}, z: 6,
|
||||
label: {show: true, position: price / max < .12 ? 'right' : 'bottom', distance: 5, formatter: fmt(price), color: colors.ink, fontSize: 12}});
|
||||
return {
|
||||
animation: !reducedMotion, animationDuration: 0, animationDurationUpdate: 260,
|
||||
grid: {left: 16, right: 24, top: 45, bottom: 65},
|
||||
tooltip: {trigger: 'item', confine: true, backgroundColor: colors.paper, borderColor: colors.line,
|
||||
textStyle: {color: colors.ink, fontSize: 12}, formatter: params => params.seriesType === 'scatter' ? `当前股价:${fmt(price)} 元/股` : params.seriesName},
|
||||
xAxis: {type: 'value', min: 0, max, splitNumber: width < 400 ? 3 : 5, name: '股价(元/股)', nameLocation: 'middle', nameGap: 42,
|
||||
nameTextStyle: {color: colors.muted, fontSize: 11}, axisLine: {show: true, lineStyle: {color: colors.line}},
|
||||
axisTick: {show: false}, splitLine: {show: false},
|
||||
axisLabel: {color: colors.muted, fontSize: 11, formatter: value => Number(value).toFixed(0), hideOverlap: true}},
|
||||
yAxis: {type: 'category', data: ['价格区间'], axisLine: {show: false}, axisTick: {show: false}, axisLabel: {show: false}},
|
||||
series,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user