《独立开发者的商业指南·收入与财务工程》4.3 一次性收入与经常性收入的估值差

同样的收入规模,结构不同,估值可能差一倍。本节把 LedgerLite 的 3.2× ARR、Tidecast 的 1.8× 收入和 Nord 的 4.5× SDE 统一换算成收入倍数,演示同一收入规模下经常性与一次性结构的估值区间差,并解释为什么倍数不可直接比较、分母不同时应该先换算再谈价格。

以下情境为教学假设,人物与数字不代表经核实的真实案例。

三个案例的估值锚看上去差距很大:LedgerLite 是 3.2× ARR,Tidecast 是 1.8× 收入,Nord Studio 是 4.5× SDE。3.2、1.8、4.5 这三个数字放在一起,很容易得出「Nord 最贵、Tidecast 最便宜」的结论。这个结论是错的,因为三个倍数的分母根本不是同一个东西。

这一节做三件事:先把三个锚换算成同一个分母,再在相同收入规模下比较经常性与一次性结构的估值区间,最后解释为什么 Nord 的 4.5× SDE 换算成收入只有约 1.0×。核心口径只有一句:倍数不能直接比较,先换分母,再谈价格。

先看三个锚的原始定义:

案例披露锚分母分母金额估值金额
LedgerLite3.2× ARRARR$240,240$768,768
Tidecast1.8× 收入净收入$138,171$248,708
Nord Studio4.5× SDESDE$34,320$154,440
Nord Studio(折价)3.0× SDESDE$34,320$102,960

这张表里已经藏了三个不可比因素。第一,LedgerLite 的分母是 ARR,Tidecast 是净收入,Nord 是 SDE;ARR 只含经常性收入,净收入已经扣了退款和平台分成,SDE 是毛利减运营费用。第二,三个分母的金额不同,直接比较倍数等于忽略了规模。第三,估值金额的口径也可能不同,有的是资产收购价,有的是股权价;本节只比较「估值金额 ÷ 分母」这一层,不展开交易结构。

三个锚,三个分母

LedgerLite 的 3.2× ARR 最接近「收入倍数」的直觉,因为 ARR 就是年度经常性收入:

ARR = MRR × 12 = 20,020 × 12 = $240,240
估值 = 3.2 × 240,240 = $768,768

Tidecast 的 1.8× 收入用的是净得,不是标价收入:

标价收入 = 19.99 × 9,600 = $191,904
净得 = 191,904 − 退款 7,676 − 平台分成 44,215 − 支付费 1,842
     = $138,171
估值 = 1.8 × 138,171 = $248,708(1.8 × 138,171 = 248,707.8)

Nord Studio 的 4.5× SDE 用的既不是收入,也不是利润,而是 SDE:

SDE = 毛利 − 运营费用 = 130,320 − 96,000 = $34,320
估值 = 4.5 × 34,320 = $154,440

SDE 是「卖方自由现金流」的近似,已经把两人工资 $96,000 扣掉。它衡量的是「买方接手后,在支付管理人员工资之前能拿到的现金」。用 SDE 做分母,适合利润薄、老板薪酬影响大的小生意;用 ARR 做分母,适合经常性收入主导的订阅生意。两种分母服务两种买家,不能直接比。

把倍数换成同一把尺

要比较三个锚,最直接的办法是把它们都换算成「估值 ÷ 收入」:

案例估值收入口径收入金额换算后的收入倍数
LedgerLite$768,768ARR$240,2403.200×
Tidecast$248,708净收入$138,1711.800×
Nord Studio$154,440收入$154,3201.001×
Nord Studio(折价)$102,960收入$154,3200.667×

换算公式就是:

收入倍数 = 估值 ÷ 收入

LedgerLite 是 768,768 ÷ 240,240 = 3.2;Tidecast 是 248,708 ÷ 138,171 ≈ 1.8;Nord 是 154,440 ÷ 154,320 ≈ 1.001;折价后的 Nord 是 102,960 ÷ 154,320 ≈ 0.667。

换算之后,结论完全变了:Nord 的 4.5× SDE 听起来最高,换算成收入只有约 1.0×;Tidecast 的 1.8× 反而高于 Nord。这不是说 Nord 一定比 Tidecast 便宜,而是说「4.5 比 1.8 大」这个比较没有意义,因为分母不同。

换算之后仍然不能直接横比,因为三个「收入」的口径也不同:

收入口径包含什么不包含什么
ARR年度经常性订阅收入一次性收入、服务收入
净收入扣退款、平台分成、支付费后的收入未扣营销、服务器、工具
收入咨询和订阅的合同收入未扣退款、支付费、直接成本

LedgerLite 的 ARR 是「已锁定的年度经常性收入」,质量最高;Tidecast 的净收入已经扣掉渠道成本,更接近「能留下的收入」;Nord 的收入是合同金额,还没有扣退款和直接成本。三种口径都能叫「收入」,但含金量不同。比较之前至少要问:这个收入是毛的还是净的、是合同额还是已确认额、含不含一次性项目。

同一收入规模下的结构差

为了只比较结构,把三个案例都放到同一个收入规模 $240,240 上,并采用一组教学参数:

结构收入倍数区间说明
100% 经常性2.5×–4.0×以 LedgerLite 的 3.2× 为区间中点附近的锚
0% 经常性1.0×–2.0×以 Tidecast 的 1.8× 为区间上沿附近的锚
27.3% 经常性1.409×–2.546×按经常性占比在两组区间之间线性插值

这三个区间是教学参数,不是市场报价。2.5×–4.0× 和 1.0×–2.0× 的作用是给「结构差」一个可计算的尺度;真实交易里,倍数还会被增长、毛利、集中度、买方类型和交易结构改变,这些在第 11 章展开。

按同一收入 $240,240 计算:

结构低值中值高值
100% 经常性$600,600$780,780$960,960
27.3% 经常性$338,594$475,099$611,603
0% 经常性$240,240$360,360$480,480

中值的差距是:

经常性中值 = 240,240 × 3.25 = $780,780
一次性中值 = 240,240 × 1.50 = $360,360
中值差 = 780,780 − 360,360 = $420,420

420,420 ÷ 240,240 = 1.75,也就是 1.75× 收入。也就是说,在收入规模、毛利和其他条件都被假设相同的情况下,单是「经常性 vs 一次性」的结构差,就能让中值估值相差 1.75 倍收入。低值差是 $360,360,高值差是 $480,480;区间越宽,结构差越大。

这个计算也解释了为什么 LedgerLite 的 3.2× ARR 和 Tidecast 的 1.8× 收入会同时存在:它们分别落在各自结构的区间里。LedgerLite 的 3.2× 在 2.5×–4.0× 之内,Tidecast 的 1.8× 在 1.0×–2.0× 之内。结构不能解释全部价格,但能解释「为什么同样的收入,倍数可以差一倍」。

为什么 Nord 的 4.5× SDE 只值约 1.0× 收入

Nord Studio 是最容易被误读的案例。它的披露锚是 4.5× SDE = $154,440,按收入换算只有约 1.001×,低于线性插值给出的 1.409×–2.546× 区间。原因不在结构公式,而在 SDE 的质量:

项金额或比例含义
收入$154,320咨询 72.7%、产品 27.3%
SDE$34,320已扣两人工资 $96,000
SDE 利润率34,320 ÷ 154,320 = 22.2%每美元收入只留下 22.2 美分 SDE
服务收入占比72.7%依赖两位合伙人的时间和关系
前 3 家客户48%客户集中度较高
折价后倍数3.0× SDE = $102,960买方因服务依赖下调报价

Nord 的 SDE 利润率只有 22.2%,而 LedgerLite 是 54.2%,Tidecast 是 87.7%:

LedgerLite = 130,240 ÷ 240,240 = 54.2%
Tidecast   = 121,171 ÷ 138,171 = 87.7%
Nord       = 34,320 ÷ 154,320 = 22.2%

对一家利润这么薄、又依赖两位合伙人的服务生意,买方不会用收入倍数来定价,而会用 SDE 倍数;4.5× SDE 听起来高,但乘出来的绝对值只有 $154,440,换算成收入约 1.0×。折价后的 3.0× SDE 只有 $102,960,换算成收入约 0.667×。

这说明两件事。第一,倍数的高低必须和分母的绝对额一起看:4.5× 一个很小的 SDE,可能比 1.8× 一个较大的净收入更便宜。第二,结构模型只能解释「经常性占比」这一层;Nord 的低估值还来自低 SDE 利润率、合伙人人身依赖和客户集中度,这些是结构公式之外的折价因素。第 11.2 节会把增长、毛利、集中度对倍数的影响展开,见 11.2 增长、毛利、集中度对倍数的影响 。

还有一个容易忽略的时点问题:LedgerLite 的年付预收会让现金领先于收入,年末递延收入 $22,000,占 ARR 的 9.2%。买方在尽调时会把递延收入当成一项服务义务,可能要求从价格中扣除,也可能因为现金充裕而给出溢价。估值倍数用的是收入,但交易价格用的是现金和负债,两者之间的桥接见 4.1 预收、递延与确认时点 。

把估值差写成代码

下面的 valuation-gap.ts 把本节的口径固化成四个函数:blendedBand 按经常性占比混合两组倍数区间,valuationRange 把区间落到具体收入上,valuationGap 计算同一收入下两种结构的差额,impliedRevenueMultiple 把任意估值锚换算成收入倍数。所有倍数用基点,金额用整数美分。

// file: src/fin/valuation-gap.ts
// tier: L1
// verify: node --test test/fin/valuation-gap.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";

export type MultipleBand = {
  lowBps: Bps;
  highBps: Bps;
};

export type ValuationRange = {
  lowBps: Bps;
  midBps: Bps;
  highBps: Bps;
  lowCents: Cents;
  midCents: Cents;
  highCents: Cents;
};

export type ValuationGap = {
  first: ValuationRange;
  second: ValuationRange;
  lowGapCents: Cents;
  midGapCents: Cents;
  highGapCents: Cents;
};

export const RECURRING_BAND: MultipleBand = {
  lowBps: 25000,
  highBps: 40000,
};

export const ONE_TIME_BAND: MultipleBand = {
  lowBps: 10000,
  highBps: 20000,
};

function checkBand(band: MultipleBand): string | null {
  for (const value of [band.lowBps, band.highBps]) {
    if (!Number.isInteger(value) || value < 0) {
      return "multiple must be a non-negative integer in basis points";
    }
  }
  if (band.lowBps > band.highBps) {
    return "low multiple must not exceed high multiple";
  }
  return null;
}

function checkShare(shareBps: Bps): string | null {
  if (!Number.isInteger(shareBps) || shareBps < 0 || shareBps > 10000) {
    return "recurring share must be between 0 and 10000 bps";
  }
  return null;
}

function checkRevenue(revenueCents: Cents): string | null {
  if (!Number.isInteger(revenueCents) || revenueCents <= 0) {
    return "revenue must be a positive integer amount in cents";
  }
  return null;
}

/**
 * Linear blend between the one-time band (0% recurring) and the recurring band
 * (100% recurring). The bands are teaching parameters, not observed prices.
 */
export function blendedBand(
  recurringShareBps: Bps,
  recurring: MultipleBand = RECURRING_BAND,
  oneTime: MultipleBand = ONE_TIME_BAND,
): Result<MultipleBand> {
  const shareError = checkShare(recurringShareBps);
  if (shareError) {
    return fail(shareError);
  }
  const recurringError = checkBand(recurring);
  if (recurringError) {
    return fail(recurringError);
  }
  const oneTimeError = checkBand(oneTime);
  if (oneTimeError) {
    return fail(oneTimeError);
  }
  const low =
    oneTime.lowBps +
    Math.round(
      ((recurring.lowBps - oneTime.lowBps) * recurringShareBps) / 10000,
    );
  const high =
    oneTime.highBps +
    Math.round(
      ((recurring.highBps - oneTime.highBps) * recurringShareBps) / 10000,
    );
  return ok({ lowBps: low, highBps: high });
}

export function valuationRange(
  revenueCents: Cents,
  band: MultipleBand,
): Result<ValuationRange> {
  const revenueError = checkRevenue(revenueCents);
  if (revenueError) {
    return fail(revenueError);
  }
  const bandError = checkBand(band);
  if (bandError) {
    return fail(bandError);
  }
  const midBps = Math.round((band.lowBps + band.highBps) / 2);
  return ok({
    lowBps: band.lowBps,
    midBps,
    highBps: band.highBps,
    lowCents: applyRate(revenueCents, band.lowBps),
    midCents: applyRate(revenueCents, midBps),
    highCents: applyRate(revenueCents, band.highBps),
  });
}

/**
 * Two structures valued on the same revenue. Positive gaps mean the first
 * structure is worth more; the sign is meaningful.
 */
export function valuationGap(
  revenueCents: Cents,
  firstShareBps: Bps,
  secondShareBps: Bps,
): Result<ValuationGap> {
  const firstBand = blendedBand(firstShareBps);
  if (!firstBand.ok) {
    return firstBand;
  }
  const secondBand = blendedBand(secondShareBps);
  if (!secondBand.ok) {
    return secondBand;
  }
  const first = valuationRange(revenueCents, firstBand.value);
  if (!first.ok) {
    return first;
  }
  const second = valuationRange(revenueCents, secondBand.value);
  if (!second.ok) {
    return second;
  }
  return ok({
    first: first.value,
    second: second.value,
    lowGapCents: first.value.lowCents - second.value.lowCents,
    midGapCents: first.value.midCents - second.value.midCents,
    highGapCents: first.value.highCents - second.value.highCents,
  });
}

/** Convert any valuation anchor into a multiple of revenue for comparison. */
export function impliedRevenueMultiple(
  valuationCents: Cents,
  revenueCents: Cents,
): Result<Bps> {
  if (!Number.isInteger(valuationCents) || valuationCents < 0) {
    return fail("valuation must be a non-negative integer amount in cents");
  }
  const revenueError = checkRevenue(revenueCents);
  if (revenueError) {
    return fail(revenueError);
  }
  return ok(divide(valuationCents, revenueCents));
}

代码里有三个细节。

第一,blendedBand 在「0% 经常性」和「100% 经常性」两组区间之间做线性插值。27.3% 的经常性占比对应 1.4094×–2.5458×;这不是说真实市场会精确按比例定价,而是说在只有结构信息时,线性插值是一个可复算的基准。

第二,valuationGap 的差额带符号。用 100% 经常性减去 0% 经常性,中值差是正 $420,420;反过来调用会得到负值。符号有意义,不要取绝对值之后再解释。

第三,impliedRevenueMultiple 只做除法,不做「可比公司调整」。它回答的是「这个估值相当于多少倍收入」,不回答「这个倍数是否合理」。合理性问题要回到增长、毛利、集中度和买方视角,见 11.1 三种估值口径 与 11.3 估值区间与谈判区间 。

用三个案例调用,结果是:LedgerLite 的 $768,768 ÷ $240,240 = 32000 个基点(3.2×);Tidecast 的 $248,708 ÷ $138,171 = 18000 个基点(1.8×);Nord 的 $154,440 ÷ $154,320 = 10008 个基点(约 1.001×);折价后的 Nord 是 6672 个基点(约 0.667×)。这些结果与上面的换算表一致。

小结

  • 3.2× ARR、1.8× 收入、4.5× SDE 的分母不同,不能直接比较;先换算成同一分母,再谈价格。
  • 换算成收入倍数后:LedgerLite 3.2×、Tidecast 1.8×、Nord 约 1.001×、折价后的 Nord 约 0.667×。
  • 同一收入 $240,240 下,100% 经常性区间 2.5×–4.0×,0% 经常性区间 1.0×–2.0×,中值差 $420,420,相当于 1.75× 收入。
  • 27.3% 经常性按线性插值对应 1.409×–2.546×;Nord 的实际锚低于这个区间,因为 SDE 利润率只有 22.2%,且依赖两位合伙人和前 3 家客户。
  • 倍数高低必须和分母的绝对额一起看:4.5× 一个很小的 SDE,可能比 1.8× 一个较大的净收入更便宜。
  • 估值倍数用收入,交易价格用现金和负债;递延收入、应收和退款准备都会改变两者之间的桥。

阅读导航:上一节:4.2 收入质量:集中度、续费、退款 · 下一节:5.1 自上而下与自下而上的预算 。

常见错误与边界

  1. 直接比较 3.2、1.8、4.5。 三个倍数的分母分别是 ARR、净收入和 SDE;不换分母就比大小,等于拿长度比重量。
  2. 把 ARR 当收入。 ARR 只含经常性收入;Nord 的咨询收入和 Tidecast 的买断收入都不在 ARR 里,用 ARR 做分母会低估或高估。
  3. 把净收入当标价收入。 Tidecast 的 1.8× 用的是扣退款、平台分成和支付费后的 $138,171,不是标价 $191,904;用标价收入算会低估倍数。
  4. 把 SDE 当净利润。 SDE 还没扣买方接手后的管理人员工资;4.5× SDE 不等于 4.5× 净利润。
  5. 只看倍数不看绝对值。 4.5× 一个 $34,320 的 SDE 只有 $154,440;1.8× 一个 $138,171 的净收入是 $248,708;倍数低的绝对额可能更大。
  6. 把教学区间当市场报价。 2.5×–4.0× 和 1.0×–2.0× 是可复算的基准,不是行业标准;真实交易会被增长、毛利、集中度和交易结构改变。
  7. 忽略收入口径的毛净差。 ARR、净收入、合同收入三种口径都能叫「收入」,含金量不同;比较前先问「扣了什么」。
  8. 忽略递延收入。 LedgerLite 年末递延 $22,000,占 ARR 9.2%;买方可能把它当服务义务从价格中扣除。
  9. 把结构差当全部折价。 Nord 的 4.5× SDE 只值约 1.0× 收入,除了结构,还有低 SDE 利润率、合伙人人身依赖和客户集中度。
  10. 忘了交易结构。 资产收购、股权收购、earn-out、托管和营运资金调整都会改变最终到手金额;本节只比较「估值 ÷ 分母」这一层。

4.3 把前两节的确认和质量翻译成价格:4.1 决定收入什么时候进入报表,4.2 决定这些收入有多可靠,4.3 决定同样规模的收入在买方眼里值多少。第 5 章回到经营本身,讨论预算怎么从收入目标倒推成本和现金需求。倍数只是估值的入口,不是估值的终点;真正的价格还要经过第 11 章的区间和第 12 章的尽调。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《移动游戏产品的架构·商业化与发行工程》附录 C 商业化与发行评审清单
  2. 《移动游戏产品的架构·商业化与发行工程》附录 B 双案例商业化事实表
  3. 《移动游戏产品的架构·商业化与发行工程》附录 A 商业化与发行术语速查