Files
smart-fin-chart/scripts/build_sweet_spot_catl.py
T
2026-09-24 17:14:03 +08:00

111 lines
4.5 KiB
Python
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.
#!/usr/bin/env python3
"""甜蜜击球区 · 宁德时代(300750):股价 → 估值指标 投影表生成器
数据层定位:本脚本是 data/sweetspot/*.csv 的唯一生成入口(CSV 是运行时投影,
禁止手工编辑)。上游底稿是资料库节点「宁德时代_股价PE对照表」
(https://www.workbuddy.cn/space/d/EsAWMaLKt4ECKPDvESKKvT,2026-09-15 版)。
计算基准(改这几个常量就能整表重算):
总股本 46.27 亿股
净现金 2,263.8 亿元 (每股 48.93 元)
2026E 归母 悲观 900 / 中性 964 / 乐观 1,000 亿元(65 家机构一致预期)
分红率 50%(2023–2025 年实际值)
现价 316.21 元(2026-09-15 收盘)
2028E 目标 1,400 亿 × 20x → 605 元(老唐法三年后合理估值,用于试算年化收益)
无风险利率 1.68%(10 年期国债,仅用于股息率倍数的脚注说明)
精度例外:PE 与股息率保留两位小数。CLAUDE.md 默认一位,但 PE 一位会把
15.18x 与 15.2x 揉成同一个读数,而「现价到底落在哪个区间」正是这张表要回答的
问题(买入区上沿 15.5x 与合理区下沿贴得极近),故按行业惯例取两位。
用法:
python3 scripts/build_sweet_spot_catl.py
"""
from pathlib import Path
SHARES = 46.27 # 亿股
NET_CASH = 2263.8 # 亿元
NOW = 316.21 # 元
PAYOUT = 0.50
NI = {"bear": 900.0, "base": 964.0, "bull": 1000.0}
TARGET_2028 = 605.0 # 元(2028E 1,400 亿 × 20x ÷ 46.27 亿股)
OUT_DIR = Path(__file__).resolve().parent.parent / "data" / "sweetspot"
def price_grid(top, step=20, keys=()):
"""等距网格 + 关键价位去重后升序,保证关键价位本身就是数据点(可被打标签)。"""
prices = {float(p) for p in range(220, top + 1, step)}
prices.update(float(k) for k in keys)
return sorted(prices)
def pct(value):
return value * 100
def main():
OUT_DIR.mkdir(parents=True, exist_ok=True)
header = (
"# 由 scripts/build_sweet_spot_catl.py 生成,请勿手工编辑。\n"
"# 口径:总股本 46.27 亿股 · 净现金 2,263.8 亿元(每股 48.93 元)· "
"2026E 归母 900/964/1,000 亿元 · 分红率 50% · 现价 316.21 元(2026-09-15)\n"
"# PE 与股息率保留两位小数(见脚本 docstring 的精度例外说明)\n"
)
# ---------- 1) 股价 → PE 对照(图 1、图 2 共用) ----------
rows = price_grid(700, 20, (265, 290, 303, NOW, 378, 605, 617))
lines = [
"price,market_cap,pe_bear,pe_base,pe_bull,pe_ex_cash,dividend_yield,net_cash_pct"
]
for px in rows:
cap = px * SHARES
lines.append(
",".join([
f"{px:.2f}",
f"{cap:.1f}",
f"{cap / NI['bear']:.2f}",
f"{cap / NI['base']:.2f}",
f"{cap / NI['bull']:.2f}",
f"{(cap - NET_CASH) / NI['base']:.2f}",
f"{pct(NI['base'] * PAYOUT / cap):.2f}",
f"{pct(NET_CASH / cap):.1f}",
])
)
path = OUT_DIR / "catl_price_pe.csv"
path.write_text(header + "\n".join(lines) + "\n", encoding="utf-8")
print(f"{path.relative_to(OUT_DIR.parent.parent)} {len(rows)} 行")
# ---------- 2) 股价 → 试算(图 3) ----------
# 价格区间与表 1 保持一致(220–700):站内「现价」输入框是三张图共用的,
# 若这里只到 620,手输 650 时图 3 会取不到值(插值落在数据区外 → NaN)。
rows = price_grid(700, 20, (265, 290, 303, NOW, 378, 450, TARGET_2028))
lines = [
"price,market_cap,pe_base,dividend_yield,cagr_price,cagr_total"
]
for px in rows:
cap = px * SHARES
dy = NI["base"] * PAYOUT / cap
cagr = (TARGET_2028 / px) ** (1 / 3) - 1
# 总收益按 (1 + 价差年化) × (1 + 股息率) − 1:股息以买入价口径年年复投,
# 与资料库试算器的「未含分红」口径并列展示,两者差额即分红的贡献。
lines.append(
",".join([
f"{px:.2f}",
f"{cap:.1f}",
f"{cap / NI['base']:.2f}",
f"{pct(dy):.2f}",
f"{pct(cagr):.1f}",
f"{pct((1 + cagr) * (1 + dy) - 1):.1f}",
])
)
path = OUT_DIR / "catl_price_returns.csv"
path.write_text(header + "\n".join(lines) + "\n", encoding="utf-8")
print(f"{path.relative_to(OUT_DIR.parent.parent)} {len(rows)} 行")
if __name__ == "__main__":
main()