以下情境为教学假设,人物与数字不代表经核实的真实案例。
第 2 章算过单位经济,第 11 章算过估值区间。两章之间隔了十节,读者容易把它们当成两件事:单位经济是运营指标,估值是交易指标。实际上它们是一条链上的两段。买方为什么给 3.2 倍 ARR 而不是 2.0 倍?因为单位经济证明每个客户在两三个月内回本、生命周期毛利是获客成本的三十倍。单位经济不直接乘出估值,但它决定了倍数能不能站住。
这一节把链子接起来。第一步,把三案例的单位指标放到同一张表;第二步,把基准倍数展开成区间;第三步,用敏感性表说明改善哪几个指标能移动谈判位置。
三案例的单位指标
单位经济的标准算法只适用于订阅。买断制与服务型生意要换单位,否则会算出没有意义的 LTV。
| 指标 | LedgerLite | Tidecast | Nord Studio(产品线) |
|---|---|---|---|
| 单位 | 客户·月 | 份 | 客户·月 |
| 单位收入 | $77.00 | $14.39(净得) | $39.00 |
| 单位毛利 | $64.80 | $12.62(SDE/份) | $32.92 |
| 月流失 | 1.5% | 不适用 | 3.0% |
| CAC | $140 | 不适用 | 不适用 |
| 回本周期 | 2.2 个月 | 不适用 | 不适用 |
| LTV | $4,320 | 单份净贡献 | $1,097 |
| LTV/CAC | 30.9 | 不适用 | 不适用 |
LedgerLite 的三个数互相印证:月毛利 64.80 美元、CAC 140 美元、回本 2.2 个月;按 1.5% 月流失,LTV 4,320 美元,LTV/CAC 30.9。这三个数一起出现时,单位经济是健康的。
Tidecast 的单位是「份」。9,600 份卖完,每份净得 14.39 美元,扣除营销、服务器、工具后每份贡献 SDE 12.62 美元。它没有「生命周期」,所以没有 LTV,也没有回本周期。硬套订阅公式会得到「LTV = 12.62 ÷ 0 = 无穷大」这种荒谬结果。
Nord Studio 的产品线可以算单位经济:39 美元 × 84.4% = 32.92 美元月毛利,月流失 3.0%,LTV 1,097 美元。但 CAC 没有单独给出——它的客户主要来自转介绍,获客成本混在合伙人的时间里。没有 CAC,就没有 LTV/CAC,也就无法判断产品线的单位经济是否健康。这是服务型生意的常见问题:成本算得清,获客算不清。
估值基数与区间
第 11 章讲过,倍数的分母必须与生意形态匹配。这一节把三案例的基准倍数展开成三档,得到区间。
| 案例 | 基数 | 低 | 基准 | 高 | 区间宽度 |
|---|---|---|---|---|---|
| LedgerLite | ARR $240,240 | 2.8× = $672,672 | 3.2× = $768,768 | 3.6× = $864,864 | 25.0% |
| Tidecast | 净得 $138,171 | 1.5× = $207,257 | 1.8× = $248,708 | 2.1× = $290,159 | 33.3% |
| Nord Studio | SDE $34,320 | 3.0× = $102,960 | 4.5× = $154,440 | 5.0× = $171,600 | 44.4% |
三档的 spread 不是行业统计,而是教学设定:订阅读数的确定性最高,区间最窄;买断制依赖续作与季节,区间居中;服务型生意依赖人,区间最宽,且悲观档直接落到买方折价后的 3.0 倍。
区间宽度本身是信息。LedgerLite 的 25.0% 说明买卖双方对未来十二个月的 ARR 分歧不大;Nord 的 44.4% 说明分歧很大,而分歧的来源不是数字算错,而是「两位合伙人走了之后收入还剩多少」这个问题没有答案。
区间不是「取中间值成交」。真实交易里,基准档是卖方报价,悲观档是买方还价,最终价格由谈判与结构条款决定。区间的用途是让双方知道自己在争什么:争的是倍数,还是争的是把哪一档当成基准。
单位经济如何移动区间
单位经济不直接乘出估值,但它改变基准倍数的谈判位置。用 LedgerLite 做一次敏感性:固定 ARR 与区间宽度,只改月流失率。
| 月流失 | LTV | LTV/CAC | 回本 | 对基准倍数的含义 |
|---|---|---|---|---|
| 1.5% | $4,320 | 30.9 | 2.2 个月 | 基准 3.2× 可站住 |
| 1.2% | $5,400 | 38.6 | 2.2 个月 | 客户留存更强,卖方有理由要求高于 3.2× |
| 1.0% | $6,480 | 46.3 | 2.2 个月 | 单位经济显著优于基准,买方更难压价 |
| 2.0% | $3,240 | 23.1 | 2.2 个月 | LTV/CAC 仍健康,但留存恶化会被追问 |
| 3.0% | $2,160 | 15.4 | 2.2 个月 | 接近警戒线,买方会把基准倍数往下压 |
回本周期不变,因为月毛利与 CAC 没变;变的是 LTV,因为 LTV 由月流失决定。这张表说明一件事:改善留存比降低 CAC 更能移动估值,因为留存同时影响 LTV 与「收入的可持续性」,而 CAC 只影响回本速度。
同样的逻辑适用于 Nord:产品线的月流失从 3.0% 降到 2.0%,LTV 会从 1,097 升到 1,646,但服务收入占比 72.7% 的结构没变,估值折价的核心原因仍在。单位经济改善能修好「产品线」这一小块,修不好「依赖两位合伙人」这个结构问题。
把它接成一条链
把三节的内容连起来,得到一条完整的链:
- 单位经济:每个客户或每份产品贡献多少毛利,多久回本。
- 结构指标:经常性收入占比、毛利率、集中度、创始人依赖。
- 估值基数:ARR、净得收入或 SDE。
- 基准倍数:由结构指标与单位经济共同支撑。
- 区间与结构:低、基准、高三档,再叠加 earn-out、托管与营运资金调整。
链上任何一环缺失,估值就会变成拍脑袋。缺单位经济,倍数没有支撑;缺结构指标,倍数无法解释折价;缺区间,价格变成一个没有误差范围的点;缺结构条款,headline 价格不等于净得。
unit-val.ts 把前四环写成了函数:unitMetrics 算回本、LTV、LTV/CAC 与健康度,unitValuation 拒绝在没有经常性收入时套用 ARR 倍数,并把基准倍数展开成区间。它的一个明确边界是:当基数为零却使用 arr 口径时直接报错,而不是返回一个看起来正常的零。
结构条款改写净得
区间给出的是 headline 价格,不是卖方最终拿到的钱。同样的 768,768 美元,结构不同,净得可以差出十几万。
| 案例 | Headline | 结构 | 净得区间(教学假设) |
|---|---|---|---|
| LedgerLite | $768,768 | 现金 70%、earn-out 30%、托管 10%、营运资金下调 $3,000 | $650,453 – $765,768 |
| Tidecast | $248,708 整体出售;或 3 年授权 $180,000 保底 + 8% 分成 | 二选一 | 授权约 $213,161 |
| Nord Studio | $154,440 标准锚;买方 $102,960;合伙人退出 $77,220 | 折价或分期 | $77,220 – $102,960 |
LedgerLite 的下限算法是:现金部分先扣托管 76,877 美元,earn-out 只兑现一半(115,315 美元),再减营运资金调整 3,000 美元,得到 650,453 美元。上限是 earn-out 与托管都全额兑现,769,768 减 3,000 得 765,768 美元。两个数之间的 115,315 美元,就是 earn-out 条款的价格。
Tidecast 的授权路径要单独算:3 年保底 180,000 美元,加上按年净得 138,171 美元的 8% 计算、三年合计 33,161 美元,得到 213,161 美元,比整体出售的 248,708 美元少 35,547 美元。授权不是「更稳所以更贵」,在本案例里它比出售便宜,换的是保留 IP 与续作空间。
Nord 的合伙人退出报价 77,220 美元,是 50% × 4.5 × SDE,用的是标准锚;买方整体收购 102,960 美元,用的是折价锚。同一年、同一门生意,两个价格用了两套倍数。这不是矛盾,而是交易对象不同:合伙人买的是内部权益,买方买的是可转移的生意。
把结构条款放回区间,才能回答「这笔交易值不值」。只看 headline,LedgerLite 比 Tidecast 贵三倍;看净得下限,差距会缩小,因为 earn-out 与托管都在打折。
这也是卷三要把交易拆成估值、尽调、结构、交割、过渡五段的原因:任何一段省略,净得都会算错。
小结
单位经济与估值区间的关系不是乘法,而是支撑关系。LedgerLite 的 3.2 倍 ARR 站得住,因为每个客户 2.2 个月回本、LTV/CAC 30.9;Nord 的 4.5 倍 SDE 被压到 3.0 倍,因为服务收入占 72.7%、交付依赖两位合伙人。改善留存能移动基准倍数,但修不好结构性问题。
区间宽度是风险信号:25.0%、33.3%、44.4% 的差异,对应三种不同的收入确定性。谈判时先确认双方争的是哪一档,再谈倍数与结构条款。
阅读导航:上一节:13.1 三张表的横向对照 · 下一节:13.3 卷一评审与进入卷二 。
常见错误与边界
- 给买断制算 LTV。 没有生命周期就没有 LTV,硬套公式会得到无穷大或零。
- 拿 LTV/CAC 直接乘出估值。 LTV/CAC 是健康度指标,不是倍数。它影响倍数能否站住,不决定倍数是多少。
- 忽略 CAC 缺失。 服务型生意的获客成本常混在人力里,缺失时不能默认它为零,应标注「不可计算」。
- 把区间中点当成交价。 区间是谈判范围,成交价由基准档、结构与双方议价决定。
- 只改善一个指标就要求提价。 留存改善能移动倍数,但集中度与创始人依赖不变时,折价仍然存在。
- 用同一 spread 套所有案例。 收入确定性不同,区间宽度就不同。统一 spread 会掩盖风险差异。
边界:本节的三档 spread 是教学设定,不是市场统计。真实交易中,倍数区间应由可比交易、买方战略与尽调结果共同决定。
// file: src/value/unit-val.ts
// tier: L1
// verify: node --test test/value/unit-val.test.ts
import { type Cents } from "../core/money.ts";
import { type Bps, applyRate, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
import { type ScenarioRange, scenarioRange } from "./range.ts";
export type UnitEconomics = {
monthlyGrossProfitPerCustomerCents: Cents;
cacCents: Cents;
monthlyChurnBps: Bps;
};
export type UnitMetrics = {
paybackMonthsBps: Bps;
ltvCents: Cents;
ltvToCacBps: Bps;
verdict: "healthy" | "watch" | "fragile";
};
export type ValuationBasis = "arr" | "sde" | "revenue";
export type UnitValuation = {
basis: ValuationBasis;
baseCents: Cents;
range: ScenarioRange;
impliedLtvMultipleBps: Bps;
};
function checkPositive(value: Cents, label: string): string | null {
if (!Number.isInteger(value) || value <= 0) {
return `${label} must be a positive integer amount in cents`;
}
return null;
}
/**
* Payback is expressed in months, multiplied by 10000 so 2.2 months reads as
* 22000 bps. LTV uses gross profit and monthly churn, the only combination
* that stays comparable across the subscription cases in this book.
*/
export function unitMetrics(input: UnitEconomics): Result<UnitMetrics> {
const grossError = checkPositive(
input.monthlyGrossProfitPerCustomerCents,
"monthly gross profit per customer",
);
if (grossError) {
return fail(grossError);
}
const cacError = checkPositive(input.cacCents, "CAC");
if (cacError) {
return fail(cacError);
}
if (
!Number.isInteger(input.monthlyChurnBps) ||
input.monthlyChurnBps <= 0 ||
input.monthlyChurnBps > 10000
) {
return fail("monthly churn must be between 1 and 10000 bps");
}
const paybackMonthsBps = divide(input.cacCents, input.monthlyGrossProfitPerCustomerCents);
// divide(a, b) already returns a / b * 10000, so dividing cents by churn in
// bps yields cents: gross * 10000 / churnBps.
const ltvCents = divide(
input.monthlyGrossProfitPerCustomerCents,
input.monthlyChurnBps,
);
const ltvToCacBps = divide(ltvCents, input.cacCents);
let verdict: UnitMetrics["verdict"] = "healthy";
if (ltvToCacBps < 30000 || paybackMonthsBps > 120000) {
verdict = "fragile";
} else if (ltvToCacBps < 90000 || paybackMonthsBps > 60000) {
verdict = "watch";
}
return ok({ paybackMonthsBps, ltvCents, ltvToCacBps, verdict });
}
/**
* A valuation multiple only means something next to its base. This function
* refuses to build a range when the basis is `arr` but the base value is zero,
* which is exactly the mistake made when a one-off product is labelled SaaS.
*/
export function unitValuation(
basis: ValuationBasis,
baseCents: Cents,
baseMultipleBps: Bps,
spreadBps: Bps,
ltvCents: Cents,
): Result<UnitValuation> {
const baseError = checkPositive(baseCents, "valuation base");
if (baseError) {
return fail(baseError);
}
if (!Number.isInteger(ltvCents) || ltvCents <= 0) {
return fail("LTV must be a positive integer amount in cents");
}
if (basis === "arr" && baseCents === 0) {
return fail("ARR basis requires a recurring revenue base");
}
const range = scenarioRange(baseCents, baseMultipleBps, spreadBps);
if (!range.ok) {
return range;
}
return ok({
basis,
baseCents,
range: range.value,
impliedLtvMultipleBps: divide(range.value.baseCents, ltvCents),
});
}
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。