feat: 增加宁德时代图表
This commit is contained in:
+1040
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,15 @@ import {
|
||||
moutaiBaseWineVolumeChart, moutaiFlyingPriceChart, moutaiPriceAdjustmentChart,
|
||||
moutaiTonPriceChart, moutaiDividendChart, moutaiDistributorsChart,
|
||||
} from './moutai.js';
|
||||
import {
|
||||
peerInstallChart, peerUtilizationChart, peerShareChart,
|
||||
peerGpPerWhChart, peerAspChart, peerMarginChart, peer2026h1Chart,
|
||||
peerGpGapChart, peerGpChangeChart, catlUnitEconomicsChart, catlVolumeGpChart,
|
||||
globalCrChart, globalTop10Chart, globalRegionShareChart,
|
||||
} from './catl.js';
|
||||
import {
|
||||
sweetCatlPricePeChart, sweetCatlNetCashChart, sweetCatlAnyPriceChart,
|
||||
} from './sweet_spot.js';
|
||||
|
||||
/** 图表构建函数注册表。manifest 只引用这里的 key,不直接引用 JS 函数。 */
|
||||
export const chartRegistry = {
|
||||
@@ -114,6 +123,23 @@ export const chartRegistry = {
|
||||
'moutai-ton-price': moutaiTonPriceChart,
|
||||
'moutai-dividend': moutaiDividendChart,
|
||||
'moutai-distributors': moutaiDistributorsChart,
|
||||
'catl-unit-economics': catlUnitEconomicsChart,
|
||||
'catl-volume-gp': catlVolumeGpChart,
|
||||
'peer-install': peerInstallChart,
|
||||
'peer-utilization': peerUtilizationChart,
|
||||
'peer-share': peerShareChart,
|
||||
'peer-gp-per-wh': peerGpPerWhChart,
|
||||
'peer-asp': peerAspChart,
|
||||
'peer-margin': peerMarginChart,
|
||||
'peer-2026h1': peer2026h1Chart,
|
||||
'peer-gp-gap': peerGpGapChart,
|
||||
'peer-gp-change': peerGpChangeChart,
|
||||
'global-cr': globalCrChart,
|
||||
'global-top10': globalTop10Chart,
|
||||
'global-region-share': globalRegionShareChart,
|
||||
'sweet-catl-price-pe': sweetCatlPricePeChart,
|
||||
'sweet-catl-net-cash': sweetCatlNetCashChart,
|
||||
'sweet-catl-any-price': sweetCatlAnyPriceChart,
|
||||
};
|
||||
|
||||
export function getChartBuilder(key) {
|
||||
|
||||
@@ -0,0 +1,421 @@
|
||||
import {
|
||||
PALETTE, FONT, FINANCIAL_COLORS, axisCommon, titleStyle, standardLegend,
|
||||
lineSeries, footnote, watermarks, LAYOUT, tooltipOf,
|
||||
} from '../theme.js';
|
||||
|
||||
/**
|
||||
* 甜蜜击球区 · 宁德时代(300750)—— 股价 ↔ 2026E 估值 的换算图表
|
||||
*
|
||||
* 上游底稿:资料库节点「宁德时代_股价PE对照表」
|
||||
* https://www.workbuddy.cn/space/d/EsAWMaLKt4ECKPDvESKKvT(2026-09-15 版)
|
||||
* 数据由 scripts/build_sweet_spot_catl.py 投影成 data/sweetspot/*.csv。
|
||||
*
|
||||
* 【口径】所有 PE 都是**静态预期 PE**:市值 ÷ 2026E 归母净利(悲观 900 / 中性 964 /
|
||||
* 乐观 1,000 亿元,65 家机构一致预期)。扣现金 PE =(市值 − 净现金 2,263.8 亿元)÷
|
||||
* 2026E 净利,即"只给主营电池业务定价"。股息率按 2023–2025 年实际分红率 50% 测算。
|
||||
*
|
||||
* 【甜蜜区框架】8x 白送 · 10x 甜点 · 12x 上沿 是巴菲特锚;资料库原表用的是五档
|
||||
* 更宽的区域(按中性情景 PE 划分),因为它的分子是"含净现金的市值"。两套档位不要
|
||||
* 混读:本文所有区域带一律用原表的五档,定义集中在 PE_ZONES,改档位只改这一处。
|
||||
*
|
||||
* 【图 1】股价 → PE 对照(PE 轴上的横向区域带)
|
||||
* 【图 2】含现金 PE vs 扣现金 PE(沙色带 = 净现金的厚度)
|
||||
* 【图 3】任意股价试算(股价轴上的纵向区域带 + 三年预期年化收益)
|
||||
*
|
||||
* 精度例外:PE 保留两位小数。项目默认一位,但"现价落在买入区还是合理区"取决于
|
||||
* 15.18x 与 15.5x 这条边界,一位小数会把 15.18 读成 15.2、与 15.5 分不清。
|
||||
*
|
||||
* 硬编码说明:区域边界的**股价**不是写死的,而是从 CSV 的 pe_base 反解(peToPrice),
|
||||
* 所以改股本或利润预期后纵向区域带会自动跟着走。本文只有两个手工常量:现价 NOW 与
|
||||
* 净现金 NET_CASH(仅用于文案),都与 build_sweet_spot_catl.py 同源。
|
||||
*/
|
||||
|
||||
/** 现价(2026-09-15 收盘)。与生成脚本同源,重跑脚本时要同步。 */
|
||||
const NOW = 316.21;
|
||||
|
||||
/** 净现金(亿元)。仅用于图 2 的文案;沙色带的几何量取自 CSV 的 pe_base − pe_ex_cash。 */
|
||||
const NET_CASH = 2263.8;
|
||||
|
||||
/** 2028E 目标价(1,400 亿 × 20x ÷ 46.27 亿股),图 3 的年化收益基准。 */
|
||||
const TARGET_2028 = 605;
|
||||
|
||||
/**
|
||||
* 五档估值区域(按中性情景 964 亿 PE 的上沿划分)。
|
||||
* 原表把 15.5–25x 细分为「合理偏低」与「合理」两档、但共用同一个配色,
|
||||
* 这里并为一条色带,避免在 PE 轴上叠出两块无差别的底色。
|
||||
*/
|
||||
const PE_ZONES = [
|
||||
{ name: '击球区', upTo: 13, color: PALETTE.green, note: '<13x' },
|
||||
{ name: '买入区', upTo: 15.5, color: PALETTE.blue, note: '13–15.5x' },
|
||||
{ name: '合理', upTo: 25, color: PALETTE.orange, note: '15.5–25x' },
|
||||
{ name: '偏贵', upTo: 30, color: PALETTE.red, note: '25–30x' },
|
||||
{ name: '卖出区', upTo: Infinity, color: PALETTE.purple, note: '>30x' },
|
||||
];
|
||||
|
||||
const PE_MIN = 8;
|
||||
const PE_MAX = 38;
|
||||
const PRICE_MIN = 200;
|
||||
const PRICE_MAX = 720;
|
||||
|
||||
const SOURCE_NOTE = '数据来源:宁德时代 2026 中报(本地财报库)· 65 家机构 2026E 一致预期';
|
||||
|
||||
/** 十六进制转 rgba —— 区域带要低透明度叠在网格上,不能直接铺饱和色 */
|
||||
function tint(hex, alpha) {
|
||||
const value = parseInt(hex.slice(1), 16);
|
||||
return `rgba(${(value >> 16) & 255},${(value >> 8) & 255},${value & 255},${alpha})`;
|
||||
}
|
||||
|
||||
/** 取某价位的某个指标;缺失返回 NaN,让标签渲染成 NaN 而不是抛错 */
|
||||
function metricAt(rows, price, key) {
|
||||
const row = rows.find((item) => item.price === price);
|
||||
return row ? row[key] : Number.NaN;
|
||||
}
|
||||
|
||||
/** 数值轴(PE / 收益率共用),与 catl.js 的 valueAxis 同一套样式 */
|
||||
const valueAxis = (name, extra = {}) => ({
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name,
|
||||
nameLocation: 'middle',
|
||||
nameGap: 56,
|
||||
nameTextStyle: { color: PALETTE.grey, fontSize: 13, fontFamily: FONT },
|
||||
...extra,
|
||||
});
|
||||
|
||||
/**
|
||||
* 股价轴:数值轴。
|
||||
* 不画竖向网格(规范只保留浅色横线)—— 图 3 的纵向区域带本身就是参考线,
|
||||
* 图 1、图 2 靠「现价」竖线定位。
|
||||
*/
|
||||
const priceAxis = (extra = {}) => ({
|
||||
...axisCommon,
|
||||
type: 'value',
|
||||
name: '股价(元)',
|
||||
nameLocation: 'middle',
|
||||
nameGap: 34,
|
||||
min: PRICE_MIN,
|
||||
max: PRICE_MAX,
|
||||
axisLine: { show: true, lineStyle: { color: PALETTE.axisLine } },
|
||||
splitLine: { show: false },
|
||||
...extra,
|
||||
});
|
||||
|
||||
const GRID = { left: 78, right: 78, top: LAYOUT.gridTop, bottom: 80 };
|
||||
|
||||
function baseOption({
|
||||
title, subtitle, xAxis, yAxis, series, legend, width, height,
|
||||
tooltipFormatter, extraNote, sourceNote, grid, extraGraphics = [],
|
||||
}) {
|
||||
const notes = [sourceNote ?? SOURCE_NOTE];
|
||||
if (extraNote) notes.push(extraNote);
|
||||
return {
|
||||
backgroundColor: '#fff',
|
||||
title: { ...titleStyle, text: title, subtext: subtitle },
|
||||
legend: standardLegend(legend, { itemGap: 22 }),
|
||||
// 三行脚注(来源 + 两行口径)会顶到 height-66,而横轴刻度底边在 height-52。
|
||||
// 本项目这几张图的横轴带轴名(「股价(元)」),它比刻度再低约 32px;实测
|
||||
// grid.bottom=118 时轴名与脚注只剩 7px 净空(同站三行脚注的图通常有 23px),
|
||||
// 所以统一提到 134 —— 改脚注行数时这里要跟着重算。
|
||||
grid: { ...GRID, ...grid },
|
||||
tooltip: tooltipOf(series, tooltipFormatter),
|
||||
xAxis,
|
||||
yAxis,
|
||||
series,
|
||||
graphic: [...watermarks(width, height), ...extraGraphics, footnote(notes.join('\n') + ' | @Joey知行记')],
|
||||
};
|
||||
}
|
||||
|
||||
/** 反解 PE 阈值对应的股价:在 pe_base 单调递增的序列上线性插值 */
|
||||
function peToPrice(rows, pe) {
|
||||
for (let i = 1; i < rows.length; i += 1) {
|
||||
const prev = rows[i - 1];
|
||||
const cur = rows[i];
|
||||
if ((prev.pe_base - pe) * (cur.pe_base - pe) <= 0) {
|
||||
const ratio = (pe - prev.pe_base) / (cur.pe_base - prev.pe_base);
|
||||
return prev.price + ratio * (cur.price - prev.price);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* PE 轴上的横向区域带(图 1、图 2 共用)。
|
||||
* 必须挂在**第一个系列**上:markArea 在系列自己的分组里、先于该系列图元绘制,
|
||||
* 而系列分组按声明顺序绘制 —— 挂在后面的系列上会盖住前一条线。
|
||||
*/
|
||||
function peBandMarkAreas() {
|
||||
let low = PE_MIN;
|
||||
return PE_ZONES.map((zone) => {
|
||||
const band = [{
|
||||
yAxis: low,
|
||||
itemStyle: { color: tint(zone.color, 0.07) },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideTopLeft',
|
||||
distance: 10,
|
||||
formatter: `${zone.name} ${zone.note}`,
|
||||
color: zone.color,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
}, { yAxis: Math.min(zone.upTo, PE_MAX) }];
|
||||
low = zone.upTo;
|
||||
return band;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 股价轴上的纵向区域带(图 3 用)。
|
||||
* 标签贴底:顶部留给折线标签与「现价」竖线的标注,贴底才不会撞。
|
||||
*/
|
||||
function priceBandMarkAreas(rows) {
|
||||
const edges = [PRICE_MIN, ...PE_ZONES
|
||||
.map((zone) => peToPrice(rows, zone.upTo))
|
||||
.filter((price) => price !== null), PRICE_MAX];
|
||||
return PE_ZONES.map((zone, index) => [{
|
||||
xAxis: edges[index],
|
||||
itemStyle: { color: tint(zone.color, 0.08) },
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideBottom',
|
||||
distance: 12,
|
||||
formatter: zone.name,
|
||||
color: zone.color,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
fontFamily: FONT,
|
||||
},
|
||||
}, { xAxis: edges[index + 1] }]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 股价 → 指标折线。
|
||||
* 只在 labeledPrices 上的点标数值:30+ 个数据点全标会糊成一片,
|
||||
* 而这几个点正好是决策价位。
|
||||
*/
|
||||
function priceLine({
|
||||
name, rows, key, color, width = 2.6, labeledPrices = [],
|
||||
unit = '', decimals = 2, position = 'right', distance = 8,
|
||||
}) {
|
||||
const line = lineSeries({ name, data: [], color, width });
|
||||
return {
|
||||
...line,
|
||||
label: { show: false },
|
||||
data: rows.map((row) => ({
|
||||
value: [row.price, row[key]],
|
||||
label: labeledPrices.includes(row.price)
|
||||
? {
|
||||
...line.label,
|
||||
show: true,
|
||||
position,
|
||||
distance,
|
||||
formatter: `${row[key].toFixed(decimals)}${unit}`,
|
||||
}
|
||||
: { show: false },
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 现价竖线:三张图共用。
|
||||
* label 一律关掉 —— markLine 的标签会**跟着竖线旋转 90°**,长串中文变成一列竖排字,
|
||||
* 极难读。文字改用 nowGraphics() 以 graphic 单独排,保持水平。
|
||||
*/
|
||||
function nowMarkLine() {
|
||||
return {
|
||||
silent: true,
|
||||
symbol: 'none',
|
||||
lineStyle: { type: 'dashed', width: 1.6, color: PALETTE.brown },
|
||||
label: { show: false },
|
||||
data: [{ xAxis: NOW }],
|
||||
};
|
||||
}
|
||||
|
||||
/** 现价竖线上方的水平标注,位置由 grid 反推(图表尺寸变化时会重算) */
|
||||
function nowGraphics(width, text) {
|
||||
const x = GRID.left
|
||||
+ ((NOW - PRICE_MIN) / (PRICE_MAX - PRICE_MIN)) * (width - GRID.left - GRID.right);
|
||||
return [{
|
||||
type: 'text',
|
||||
left: Math.round(x + 8),
|
||||
top: LAYOUT.gridTop + 4,
|
||||
silent: true,
|
||||
style: { text, fill: PALETTE.brown, fontSize: 12, fontWeight: 600, fontFamily: FONT },
|
||||
}];
|
||||
}
|
||||
|
||||
/**
|
||||
* 图 1:股价 → 2026E PE 对照 —— 资料库原表第 1 节的图形版
|
||||
*
|
||||
* 一条直线就是一个情景:PE 与股价严格成正比,所以三档利润在图上表现为三条**平行**的
|
||||
* 窄带(±6% 的利润预期落到 PE 上不足 1.7x)。这张图能回答"现价落在哪个区域",
|
||||
* 但回答不了"贵还是便宜"—— 那取决于利润能不能兑现,属于分母问题,不在本图射程内。
|
||||
*/
|
||||
export function sweetCatlPricePeChart(rows, { width, height }) {
|
||||
const nowPe = metricAt(rows, NOW, 'pe_base');
|
||||
const hero = priceLine({
|
||||
name: '中性 964 亿',
|
||||
rows,
|
||||
key: 'pe_base',
|
||||
color: FINANCIAL_COLORS.netProfit,
|
||||
width: 3.5,
|
||||
labeledPrices: [265, NOW, 480, 605],
|
||||
unit: 'x',
|
||||
position: 'right',
|
||||
});
|
||||
hero.markLine = nowMarkLine();
|
||||
|
||||
const bear = {
|
||||
...lineSeries({ name: '悲观 900 亿', data: [], color: FINANCIAL_COLORS.operatingCashFlow, width: 2.4 }),
|
||||
label: { show: false },
|
||||
data: rows.map((row) => [row.price, row.pe_bear]),
|
||||
};
|
||||
// markArea 挂在第一个系列上(见 peBandMarkAreas 注释)
|
||||
bear.markArea = { silent: true, data: peBandMarkAreas() };
|
||||
|
||||
const bull = {
|
||||
...lineSeries({ name: '乐观 1,000 亿', data: [], color: FINANCIAL_COLORS.investingCashFlow, width: 2.4 }),
|
||||
label: { show: false },
|
||||
data: rows.map((row) => [row.price, row.pe_bull]),
|
||||
};
|
||||
|
||||
return baseOption({
|
||||
title: '股价 → 2026E PE 对照',
|
||||
subtitle: `单位:倍 · 三档利润情景 · 区域底带按中性 964 亿划分 · 现价 ${NOW} 元`,
|
||||
xAxis: priceAxis(),
|
||||
yAxis: valueAxis('PE(倍)', { min: PE_MIN, max: PE_MAX, interval: 5 }),
|
||||
series: [bear, hero, bull],
|
||||
legend: ['悲观 900 亿', '中性 964 亿', '乐观 1,000 亿'],
|
||||
width,
|
||||
height,
|
||||
grid: { bottom: 134 },
|
||||
extraGraphics: nowGraphics(width, `现价 ${NOW} 元 = 中性 ${nowPe.toFixed(2)}x`),
|
||||
tooltipFormatter: (value) => `${Number(Array.isArray(value) ? value[1] : value).toFixed(2)}x`,
|
||||
extraNote: 'PE = 市值 ÷ 2026E 归母净利,与股价严格成正比;三档情景只差 ±6% 利润,落到 PE 上不足 1.7x\n'
|
||||
+ '原表把 15.5–25x 细分为「合理偏低」与「合理」,此处并为一条色带;扣现金 PE 见图 2',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 图 2:净现金让 PE 便宜了多少 —— 资料库原表第 2 节右图的图形版
|
||||
*
|
||||
* 含现金 PE 与扣现金 PE 是两条**严格平行**的线,间距恒为 净现金 ÷ 2026E 净利
|
||||
* (约 2.35x)—— 间距与股价无关,但折价占市值的比例随股价下降而放大
|
||||
* (220 元 22.2% → 700 元 7.0%)。所以这张图要看的不是两条线的高低,
|
||||
* 而是中间那层沙色:它是一块不产生经营利润、但实实在在归股东的资产。
|
||||
*
|
||||
* 实现说明:ECharts 没有"两个系列之间填充",只能用 stack —— 下段是扣现金 PE、
|
||||
* 上段是差额,差额段线宽 0、面积填沙色,上沿即含现金 PE。stack 的叠放顺序不能改
|
||||
* (改了基准就错),于是用 z 控制绘制层级,防止沙色覆盖掉扣现金那条实线。
|
||||
*/
|
||||
export function sweetCatlNetCashChart(rows, { width, height }) {
|
||||
const gap = rows[0].pe_base - rows[0].pe_ex_cash;
|
||||
const nowEx = metricAt(rows, NOW, 'pe_ex_cash');
|
||||
|
||||
const cashLine = {
|
||||
...lineSeries({ name: '扣现金 PE', data: [], color: PALETTE.brown, width: 3.5 }),
|
||||
label: { show: false },
|
||||
stack: 'pe',
|
||||
z: 3,
|
||||
areaStyle: { opacity: 0 },
|
||||
data: rows.map((row) => [row.price, row.pe_ex_cash]),
|
||||
};
|
||||
cashLine.markLine = nowMarkLine();
|
||||
cashLine.markArea = { silent: true, data: peBandMarkAreas() };
|
||||
|
||||
const band = {
|
||||
...lineSeries({ name: '净现金差额', data: [], color: PALETTE.sand, width: 0 }),
|
||||
label: { show: false },
|
||||
stack: 'pe',
|
||||
z: 2,
|
||||
lineStyle: { color: PALETTE.sand, width: 0, opacity: 0 },
|
||||
itemStyle: { color: PALETTE.sand },
|
||||
showSymbol: false,
|
||||
// opacity 必须显式写:areaStyle 的默认 opacity 是 0.7,只给 rgba 底色会被再乘一次
|
||||
areaStyle: { color: PALETTE.sand, opacity: 0.7 },
|
||||
data: rows.map((row) => [row.price, row.pe_base - row.pe_ex_cash]),
|
||||
};
|
||||
|
||||
const gross = {
|
||||
...lineSeries({ name: '含现金 PE', data: [], color: FINANCIAL_COLORS.investingCashFlow, width: 3 }),
|
||||
label: { show: false },
|
||||
z: 4,
|
||||
data: rows.map((row) => [row.price, row.pe_base]),
|
||||
};
|
||||
|
||||
return baseOption({
|
||||
title: '净现金让 PE 便宜了多少',
|
||||
subtitle: `单位:倍 · 中性 964 亿口径 · 沙色带即净现金 ${NET_CASH.toLocaleString('zh-CN')} 亿元对应的 PE`,
|
||||
xAxis: priceAxis(),
|
||||
yAxis: valueAxis('PE(倍)', { min: PE_MIN, max: PE_MAX, interval: 5 }),
|
||||
series: [cashLine, band, gross],
|
||||
// 「净现金差额」不进图例(线宽 0,图例色块会是空的),靠副标题与脚注交代
|
||||
legend: ['含现金 PE', '扣现金 PE'],
|
||||
width,
|
||||
height,
|
||||
grid: { bottom: 134 },
|
||||
extraGraphics: nowGraphics(width, `现价 ${NOW} 元 = 扣现金 ${nowEx.toFixed(2)}x`),
|
||||
tooltipFormatter: (value) => `${Number(Array.isArray(value) ? value[1] : value).toFixed(2)}x`,
|
||||
extraNote: '含现金 PE = 市值 ÷ 2026E 净利;扣现金 PE =(市值 − 净现金)÷ 2026E 净利,即只给主营电池业务定价\n'
|
||||
+ `两条线严格平行:差额恒为 ${gap.toFixed(2)}x,但占市值比例从 220 元的 22.2% 收窄到 700 元的 7.0% —— 股价越低,这层折价越厚`,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 图 3:任意股价试算 —— 资料库原表第 5 节计算器的图形版
|
||||
*
|
||||
* 计算器要输入一个价格才出结果,图表反过来:220–700 元全档都画出来,任意价位
|
||||
* 沿竖线直接读。两条线是同一个目标价(605 元 = 2028E 1,400 亿 × 20x)下的三年
|
||||
* 年化收益,差额即 50% 分红率的贡献。纵轴穿过 0 的位置就是"三年白干"—— 正好
|
||||
* 是 605 元这条卖点线,所以零线本身就是一条结论。
|
||||
*
|
||||
* 纵向底带把估值区域直接画在股价轴上:横带看 PE、竖带看股价,是同一套档位。
|
||||
* 击球区上沿 270.9 元、买入区上沿 322.9 元、合理区上沿 520.9 元(由 pe_base 反解)。
|
||||
*/
|
||||
export function sweetCatlAnyPriceChart(rows, { width, height }) {
|
||||
const hero = priceLine({
|
||||
name: '含分红年化',
|
||||
rows,
|
||||
key: 'cagr_total',
|
||||
color: FINANCIAL_COLORS.netProfit,
|
||||
width: 3.5,
|
||||
labeledPrices: [265, NOW, 450, TARGET_2028],
|
||||
unit: '%',
|
||||
decimals: 1,
|
||||
position: 'top',
|
||||
distance: 10,
|
||||
});
|
||||
hero.markLine = nowMarkLine();
|
||||
// markArea 挂在第一个系列上,否则会盖住同图上先声明的折线
|
||||
hero.markArea = { silent: true, data: priceBandMarkAreas(rows) };
|
||||
|
||||
const priceOnly = priceLine({
|
||||
name: '仅价差年化',
|
||||
rows,
|
||||
key: 'cagr_price',
|
||||
color: FINANCIAL_COLORS.investingCashFlow,
|
||||
width: 2.6,
|
||||
labeledPrices: [265, NOW, 450],
|
||||
unit: '%',
|
||||
decimals: 1,
|
||||
position: 'bottom',
|
||||
distance: 10,
|
||||
});
|
||||
|
||||
return baseOption({
|
||||
title: '任意股价试算:三年预期年化收益',
|
||||
subtitle: '单位:% · 目标价 605 元(2028E 1,400 亿 × 20x)· 纵带为估值区域',
|
||||
xAxis: priceAxis(),
|
||||
yAxis: valueAxis('年化收益(%)', { min: -10, max: 50, interval: 10 }),
|
||||
series: [hero, priceOnly],
|
||||
legend: ['含分红年化', '仅价差年化'],
|
||||
width,
|
||||
height,
|
||||
grid: { bottom: 134 },
|
||||
extraGraphics: nowGraphics(width, `现价 ${NOW} 元 = 含分红年化 ${metricAt(rows, NOW, 'cagr_total').toFixed(1)}%`),
|
||||
tooltipFormatter: (value) => `${Number(Array.isArray(value) ? value[1] : value).toFixed(1)}%`,
|
||||
extraNote: `年化收益 =(${TARGET_2028} ÷ 股价)^(1/3) − 1;含分红口径再乘 (1 + 股息率),股息按当前价每年派现、不复投\n`
|
||||
+ '纵带边界由 pe_base 反解:击球区上沿 270.9 元 | 买入区上沿 322.9 元 | 合理区上沿 520.9 元',
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user