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

148 lines
6.7 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
"""甜蜜击球区 · 腾讯控股(00700.HK):股价 → 估值指标 投影表生成器
数据层定位:本脚本是 data/sweetspot/tencent_*.csv 的唯一生成入口(CSV 是运行时投影,
禁止手工编辑)。上游底稿是资料库节点「腾讯控股(00700.HK)甜蜜击球区检验」
(https://www.workbuddy.cn/space/d/Ey5NeiPls71FC12zn0ieSX,2026-09-17 版)。
与宁德那套(build_sweet_spot_catl.py)的两处口径差异,都是腾讯特有的:
1. 纵轴是 EV/EBIT,不是 PE。腾讯的「类现金」是 5,260 亿投资组合(股权、会波动),
只扣净现金(582 亿)几乎不改变倍数 —— 必须连投资组合一起扣,才是真实的经营资产价格。
2. 三情景是 EBIT(悲观 2,400 / 基准 2,585 / 乐观 2,900 亿元),不是归母净利。
分子分母成对:分母扣了投资组合,分子就不能含投资收益,所以用经营盈利口径。
计算基准(改这几个常量就能整表重算):
总股本 90.95 亿股
汇率 1 HKD = 0.86154 CNY(中国外汇交易中心 2026-09-17 中间价)
净现金 581.9 亿元
上市投资 4,872.0 亿元(公允价值)
非上市投资 387.9 亿元(账面)
非经营资产 5,841.8 亿元(= 净现金 + 上市 + 非上市,占市值 17.5%)
TTM EBIT 2,585.43 亿元(= 2,415.62 − 1,176.70 + 1,346.51)
折旧情景 5 年直线 → 稳态 EBIT 2,247 亿元(占当前 EBIT 13.1%)
现价 426.8 港元(2026-09-17 收盘)
甜蜜区 8x 白送 / 10x 甜点 / 12x 上沿(巴菲特 10x 税前锚)
目标价 12x 上沿 470.5 / 10x 甜点 404.5 港元(基准 EBIT,不给增长)
用法:
python3 scripts/build_sweet_spot_tencent.py
"""
from pathlib import Path
SHARES = 90.95 # 亿股
FX = 0.86154 # 1 HKD = ? CNY
NET_CASH = 581.9 # 亿元
LISTED = 4872.0 # 亿元(上市投资,公允价值)
UNLISTED = 387.9 # 亿元(非上市投资,账面)
NON_OP = NET_CASH + LISTED + UNLISTED # 5,841.8 亿元
EBIT = {"bear": 2400.0, "base": 2585.43, "bull": 2900.0}
EBIT_DEP = 2247.0 # 5 年折旧情景下的稳态 EBIT(亿元)
NOW = 426.8 # 港元
EXIT_MULT = {"high": 12.0, "mid": 10.0} # 三年后退出倍数(甜蜜区上沿 / 甜点)
PRICE_BOTTOM = 240 # 港元,数据区下限
PRICE_TOP = 640 # 港元,数据区上限
OUT_DIR = Path(__file__).resolve().parent.parent / "data" / "sweetspot"
# 数据点里必须出现的价位:甜蜜区标尺 5 档 + 现价。
# 它们要能被折线的数据标签命中(label.position 不接受函数,只能逐点覆盖 data)。
KEY_PRICES = (
8 * EBIT["base"] + NON_OP,
9 * EBIT["base"] + NON_OP,
10 * EBIT["base"] + NON_OP,
NOW * SHARES * FX,
12 * EBIT["base"] + NON_OP,
)
KEY_PRICES = tuple(value / (SHARES * FX) for value in KEY_PRICES)
def price_grid(top, step=20, keys=()):
"""等距网格 + 关键价位去重后升序,保证关键价位本身就是数据点(可被打标签)。"""
prices = {float(p) for p in range(PRICE_BOTTOM, top + 1, step)}
prices.update(round(float(k), 1) for k in keys)
return sorted(prices)
def price_to_hkd(value):
"""目标价公式:股价 =(目标倍数 × EBIT + 非经营资产)÷ 股本 ÷ 汇率。"""
return value / (SHARES * FX)
def mult_at(price, ebit, deduct=NON_OP):
"""EV/EBIT =(市值 − 扣除的非经营资产)÷ EBIT。deduct=0 即不扣任何资产。"""
return (price * SHARES * FX - deduct) / ebit
def main():
OUT_DIR.mkdir(parents=True, exist_ok=True)
header = (
"# 由 scripts/build_sweet_spot_tencent.py 生成,请勿手工编辑。\n"
"# 口径:总股本 90.95 亿股 · 汇率 0.86154 · 非经营资产 5,841.8 亿元"
"(净现金 581.9 + 上市投资 4,872.0 + 非上市投资 387.9)· "
"TTM EBIT 2,585.43 亿元 · 三情景 EBIT 2,400/2,585/2,900 亿元 · 现价 426.8 港元(2026-09-17)\n"
"# 倍数与年化收益保留两位小数(与宁德两份表同精度;倍数一位会把 10.68x 与 10.7x 揉成一个读数)\n"
)
# ---------- 1) 股价 → 倍数对照(图 1、图 2 共用) ----------
rows = price_grid(PRICE_TOP, 20, KEY_PRICES)
lines = [
"price,market_cap,mult_bear,mult_base,mult_bull,mult_net_cash,mult_gross,non_op_pct"
]
for px in rows:
cap = px * SHARES * FX
lines.append(
",".join([
f"{px:.2f}",
f"{cap:.1f}",
f"{mult_at(px, EBIT['bear']):.2f}",
f"{mult_at(px, EBIT['base']):.2f}",
f"{mult_at(px, EBIT['bull']):.2f}",
f"{mult_at(px, EBIT['base'], NET_CASH):.2f}",
f"{mult_at(px, EBIT['base'], 0):.2f}",
f"{NON_OP / cap * 100:.1f}",
])
)
path = OUT_DIR / "tencent_price_ev.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) ----------
# 目标价三年不变(不给增长):退出在甜蜜区上沿 / 甜点 / 折旧后的上沿,年化只由买入价决定。
targets = {
"cagr_12x": price_to_hkd(EXIT_MULT["high"] * EBIT["base"] + NON_OP),
"cagr_10x": price_to_hkd(EXIT_MULT["mid"] * EBIT["base"] + NON_OP),
"cagr_12x_dep": price_to_hkd(EXIT_MULT["high"] * EBIT_DEP + NON_OP),
}
rows = price_grid(PRICE_TOP, 20, KEY_PRICES)
lines = ["price,market_cap,mult_base,cagr_12x,cagr_10x,cagr_12x_dep"]
for px in rows:
cap = px * SHARES * FX
lines.append(
",".join([
f"{px:.2f}",
f"{cap:.1f}",
f"{mult_at(px, EBIT['base']):.2f}",
*[f"{((value / px) ** (1 / 3) - 1) * 100:.2f}" for value in (
targets["cagr_12x"], targets["cagr_10x"], targets["cagr_12x_dep"],
)],
])
)
path = OUT_DIR / "tencent_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)} 行")
print("\n目标价(港元):" + " | ".join(f"{k} {v:.1f}" for k, v in targets.items()))
print("现价倍数:" + " | ".join([
f"悲观 {mult_at(NOW, EBIT['bear']):.2f}x",
f"基准 {mult_at(NOW, EBIT['base']):.2f}x",
f"乐观 {mult_at(NOW, EBIT['bull']):.2f}x",
f"只扣净现金 {mult_at(NOW, EBIT['base'], NET_CASH):.2f}x",
f"不扣 {mult_at(NOW, EBIT['base'], 0):.2f}x",
]))
if __name__ == "__main__":
main()