11.3 CAC 与买量口径

CAC 与 ROI 是唯一不能在本机复现的一组数——它们来自真实买量后台。本节把「口径与计算」拆出来:用 config/n11_ltv.json 的 LTV 曲线与三种 CAC 情景,算回本周期与 LTV/CAC 比值,跑出自然量 2.28、混合 1.43、买量为主 0.81,并逐处划清哪些是设计假设、哪些是命令行可复现的换算。

11.2 算出了 LTV 曲线:D180 467 分、整生命周期 570 分。但 LTV 只是一个「值多少」的数,它要和一个「花多少」的数相除,才能回答生意划不划算——那个数就是 CAC(获客成本)。本节必须先把一件事说在前面:CAC 与 ROI 是本卷唯一一组命令行不可复现的数。它们来自真实买量后台(各渠道的实时出价、竞价环境、投放人群),需真实业务环境,命令行不可复现。本节能做的是把这件事拆成两半:不可复现的输入(真实 CAC、真实 ROI,属 X 层)和可复现的计算(给定 CAC 假设,算回本周期与 LTV/CAC 比值,属 S 层)。分不清这两半,就会把「假设」当成「实测」。

CAC 是唯一不在游戏里、却在决定生死的数

游戏里的一切数值——留存、ARPPU、LTV——都能在本地仿真里推出来。唯独 CAC 不在游戏里:它是「买来一个安装花了多少钱」,由外部投放市场决定。同一款游戏,在自然量占比高的渠道 CAC 可能只有 2.5 元,在竞争激烈的买量渠道可能到 7 元。产品团队无法优化 CAC,只能优化 LTV——但 CAC 决定了 LTV 要多高才够。

衡量这门生意的核心比值是 LTV / CAC:

LTV/CAC = 整生命周期 LTV / 每安装成本 CAC

行业里常说「LTV/CAC 要大于 3 才健康」,这个 3 是跨品类的经验值,不是定理。对 SLG 这种回本周期以月计的品类,更实用的判据是「回本周期」本身:CAC 多久能被 LTV 追平。回本周期太长,钱烧完了还没回来,比值再漂亮也撑不住现金流。

口径:三个数怎么算

把买量口径拆成三个可算的数(全部整数,单位分):

  • CAC(每安装成本)= 一段时间的买量花费 / 该渠道带来的安装数。真实的 CAC 需真实业务环境,命令行不可复现;本节用 config/n11_ltv.json 里的 scenarios 作为设计假设。
  • 回本周期(payback day)= 累计 LTV 第一次 ≥ CAC 的天数(11.2 的 payback_day)。
  • LTV/CAC 比值(千分比)= 整生命周期 LTV × 1000 / CAC。用千分比而不是倍数,是为了和本套其他比率口径统一。

这三个数里,只有 CAC 是 X 层输入,后两个都是纯计算。也就是说:真实 CAC 拿不到,但「给定 CAC,回本周期是多少」这件事,命令行完全能复现。这就是本节 S 层脚本要做的。

算例:把三个数代进去

拿「混合量」情景把三个公式走一遍(全部整数,单位分):

  • 输入:整生命周期 LTV = 570 分,CAC = 400 分。
  • 回本周期:从 11.2 的曲线里找累计 LTV 首次 ≥ 400 的天数,得 D116。
  • LTV/CAC:570 × 1000 / 400 = 1425,即 1.425。
  • 是否达标:1425 低于目标 2000,不达标。

一个反例说明「口径选错会怎样」:如果只看 D7 的 LTV(87 分)去除 CAC(400 分),得到 87 × 1000 / 400 = 217,也就是 LTV/CAC = 0.217——看起来是「血亏」。但 D7 的 LTV 只是整条曲线的 15%,拿早期 LTV 判买量,几乎一定会把好生意误判成坏生意。SLG 的正确口径是整生命周期(本套取 365 天),不是 D7。

仿真:给定 CAC,算回本与比值

下面这段是 S 层仿真脚本。它读 11.2 那条 LTV 曲线与配置里的三种 CAC 情景,对每个情景算回本周期、LTV/CAC 比值与是否达标,并算出「最大可承受 CAC」与「达标所需 CAC」:

# file: sim/n11_cac_caliber.py
# tier: S
# verify: python3 sim/n11_cac_caliber.py --config config/ --out out/
"""买量口径计算(可复现部分):读 config/n11_ltv.json 的 LTV 曲线与 CAC 情景假设,对每个
情景算回本周期(天)、整生命周期 LTV/CAC 比值(千分比)与是否达标,并算最大可承受 CAC 与
达标所需 CAC。CAC 输入本身是设计假设(X 层),本脚本只做可复现的换算。
产出 out/n11_cac.json + out/n11_cac.csv + out/n11_cac.svg。"""

from __future__ import annotations

import argparse
import csv
import json
import os

from n11_funnel_retention import build_retention_micro, load
from n11_ltv_payback import ltv_curve, payback_day
from svg_line import render


def main() -> int:
    here = os.path.dirname(os.path.abspath(__file__))
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", default=os.path.join(here, "..", "config"))
    ap.add_argument("--out", default=os.path.join(here, "..", "out"))
    args = ap.parse_args()
    os.makedirs(args.out, exist_ok=True)

    cfg = load(args.config, "n11_ltv")
    curve = ltv_curve(cfg, build_retention_micro(cfg))
    acq, horizon = cfg["acquisition"], cfg["retention"]["horizonDays"]
    ltv_full = curve[horizon]

    rows = []
    for sc in acq["scenarios"]:
        cac = sc["cacCentsPerInstall"]
        ratio = ltv_full * 1000 // cac
        rows.append({"id": sc["id"], "name": sc["name"], "cacCentsPerInstall": cac,
                     "paybackDay": payback_day(curve, cac), "ltvCacPermille": ratio,
                     "meetsTarget": ratio >= acq["targetLtvCacPermille"]})

    with open(os.path.join(args.out, "n11_cac.csv"), "w", encoding="utf-8", newline="") as fh:
        w = csv.writer(fh)
        w.writerow(["id", "name", "cacCentsPerInstall", "paybackDay", "ltvCacPermille", "meetsTarget"])
        for r in rows:
            w.writerow([r["id"], r["name"], r["cacCentsPerInstall"], r["paybackDay"], r["ltvCacPermille"], r["meetsTarget"]])
    render([r["ltvCacPermille"] for r in rows], os.path.join(args.out, "n11_cac.svg"),
           "LTV/CAC permille by CAC scenario")

    report = {"version": 1, "horizonDays": horizon, "ltvFullCents": ltv_full,
              "targetLtvCacPermille": acq["targetLtvCacPermille"], "maxAffordableCacCents": ltv_full,
              "cacForTargetCents": ltv_full * 1000 // acq["targetLtvCacPermille"], "scenarios": rows}
    with open(os.path.join(args.out, "n11_cac.json"), "w", encoding="utf-8") as fh:
        json.dump(report, fh, ensure_ascii=False, indent=2)
    print(json.dumps(report, ensure_ascii=False))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

本机实跑(--config config/ --out out/)输出:

{"version": 1, "horizonDays": 365, "ltvFullCents": 570, "targetLtvCacPermille": 2000,
 "maxAffordableCacCents": 570, "cacForTargetCents": 285,
 "scenarios": [
   {"id": "organic", "cacCentsPerInstall": 250, "paybackDay": 42, "ltvCacPermille": 2280, "meetsTarget": true},
   {"id": "mixed", "cacCentsPerInstall": 400, "paybackDay": 116, "ltvCacPermille": 1425, "meetsTarget": false},
   {"id": "paid_heavy", "cacCentsPerInstall": 700, "paybackDay": 0, "ltvCacPermille": 814, "meetsTarget": false}]}

对应 CSV:

id,name,cacCentsPerInstall,paybackDay,ltvCacPermille,meetsTarget
organic,自然量为主,250,42,2280,True
mixed,混合量,400,116,1425,False
paid_heavy,买量为主,700,0,814,False

三种买量情景:自然量 / 混合 / 买量为主

三个情景只改了一个输入——CAC,结果却天差地别:

情景CAC(分)回本周期LTV/CAC达标
自然量为主250D422.28是
混合量400D1161.43否
买量为主700365 天内不回本0.81否

读法:

  • 自然量为主(CAC 2.5 元):D42 回本,LTV/CAC 2.28,唯一达标的情景。自然量的 CAC 低,是因为没有竞价成本,玩家靠口碑与推荐进来。
  • 混合量(CAC 4 元):D116 回本,LTV/CAC 1.43。这条线以下,生意还能做,但已经很紧——四个月才回本,对现金流是不小的压力。
  • 买量为主(CAC 7 元):LTV/CAC 只有 0.81,365 天内根本回不了本。这意味着在纯买量渠道上,这款游戏是亏的。

脚本还算了两个临界值:最大可承受 CAC = 570 分(LTV 全额,回本周期 = 生命周期,比值恰为 1);达标所需 CAC = 285 分(要让 LTV/CAC 达到目标 2.0,CAC 不能超过 2.85 元)。这两个数才是买量决策真正要盯的:投放团队每买一个安装,成本只要低于 285 分就达标,高于 570 分就注定亏。

敏感性:CAC 每涨一点,回本周期涨多少

回本周期对 CAC 的敏感度不是线性的。把 CAC 从 200 分扫到 700 分:

CAC(分)回本周期LTV/CAC
200D272.85
250D422.28
300D601.90
350D841.63
400D1161.43
450D1611.27
500D2261.14
570D3631.00
600365 天内不回本0.95
700365 天内不回本0.81

这张表最该看的是右半边:CAC 从 400 涨到 500(+25%),回本周期从 D116 涨到 D226(+95%)——回本周期比 CAC 涨得快得多。原因是 LTV 曲线越往后越平:越贵的 CAC 要靠越晚的收入去覆盖,而越晚的收入越稀薄。CAC 一旦越过 570 分的临界,回本周期直接从「一年内」跳到「永远」。这条非线性,是买量预算最容易被低估的风险。

口径陷阱:三个最容易算错的数

  1. 回本周期 ≠ D7/D30 回本。 买量团队常按 D7、D30 看回本,那是休闲游戏的口径。SLG 的 D7 LTV 只有 87 分、D30 只有 212 分,远不够覆盖任何真实 CAC。用早期回本判 SLG,结论永远是「亏」。
  2. 自然量与买量不能混算。 本节的 maxAffordableCacCents = 570 是「所有渠道统一口径」的临界值。真实投放里,自然量的 CAC 低、买量的 CAC 高,必须分渠道各算一条回本曲线,否则会被自然量的低 CAC 拉出一个虚假的健康数字。真实的渠道 CAC 拆分需真实业务环境,命令行不可复现。
  3. 归因窗口决定 LTV 的分子。 LTV/CAC 的分子是「归因到这次买量的收入」,而收入要不要算进一个玩家 180 天后的付费,取决于归因窗口。窗口越长,LTV 越高、比值越好看——这是可以调的参数,不是客观事实。真实归因口径需真实业务环境,命令行不可复现。

为什么 CAC 不能「优化」

产品团队常问「CAC 能不能降」。答案要分两层:

  • 能降的部分:素材质量、人群定向、落地页转化、商店素材——这些属于买量运营,降的是「同样的出价买到更多有效安装」。这部分需真实业务环境,命令行不可复现。
  • 不能降的部分:竞价市场的整体价格、竞争对手的出价、渠道的流量供给——这些是外部市场,产品方无法左右。

产品团队真正能控制的是 LTV 这一头:提高 D1 留存(11.1 的漏斗最陡处)、提高 ARPPU(第 9 章的付费点)、拉长长尾(第 12 章的留存预警)。LTV 每提高一档,能承受的 CAC 就高一档——这才是产品对买量决策的真实贡献。

这里还有一个常被忽略的角度:UA(user acquisition,买量)的效率会反向影响 LTV。买来的人群质量差,留存与付费都低,整条 LTV 曲线会整体下移——于是「CAC 高」和「LTV 低」同时发生,LTV/CAC 比单独看两头更糟。产品和买量不是两条独立的线,它们通过人群质量耦合。真实的人群质量分层与定向效果需真实业务环境,命令行不可复现。

所以买量口径的正确用法,是先分人群、再算回本:同一款游戏对不同渠道、不同定向包的人群,应该各跑一条 LTV 曲线。本节的三个情景是「全人群统一口径」的简化,真实投放里这一层简化必须拆开。

从回本周期到现金流:D116 意味着什么

回本周期不只是一个天数,它直接决定要垫多少钱。假设某天买进 100000 个安装,CAC 400 分:

  • 当天支出:100000 × 400 = 40,000,000 分,即 40 万元。
  • 整生命周期总收入:100000 × 570 = 57,000,000 分,即 57 万元。
  • 净利:17 万元,LTV/CAC = 1.425。

账面是赚的,但这 40 万元要先垫出去,收入分 365 天慢慢回来,第 116 天才回本。这意味着至少要备 40 万元现金才能跑这一波买量,且在第 116 天之前账上一直是净流出。回本周期越长,要垫的资金越多、风险越大——这就是为什么「回本周期」比「LTV/CAC 比值」更硬:比值说赚不赚,回本周期说撑不撑得住。

真实的资金占用还要叠加账期、渠道结算周期、退款率,这些需真实业务环境,命令行不可复现。本节能给的是回本周期这条曲线,不能给的是你的现金流表。

口径统一:全站只认一条 LTV 曲线

买量分析里最隐蔽的错误,是不同团队用不同的 LTV:投放看 D7 LTV、财务看 365 天 LTV、产品看 180 天 LTV。三个数各说各话,会开到最后谁也说服不了谁。

本套的做法是全站锁一条曲线:config/n11_ltv.json 一份、horizonDays = 365 一个口径、LTV/CAC 一律用整生命周期。11.1、11.2、11.3 三节的脚本读的是同一份配置、同一条曲线——口径统一不是格式问题,是让「回本周期 D116」这句话在所有人嘴里是同一个数。

三章回顾:一条从留存到回本的链

第 11 章三节是一条完整的链:

  • 11.1 漏斗与留存曲线:把「有多少人、待多久」变成表——漏斗六段、D1 450‰、D7 200‰、D30 90‰。
  • 11.2 LTV 建模与回本周期:把留存曲线折成每安装收入——LTV 570 分、回本 D116。
  • 11.3 CAC 与买量口径:把「花多少」拆成可复现的计算与不可复现的输入——LTV/CAC 2.28 / 1.43 / 0.81。

三节的边界要记牢:曲线(11.1)、建模(11.2)、买量口径(11.3)。付费点设计在第 9 章,留存预警在第 12 章,本章只做「把人变成钱」这一段。

不可复现的边界

本节最重要的不是那三个情景,而是它们是怎么来的。必须逐处说清:

  • 情景里的 CAC 数值(2.5 / 4 / 7 元)是设计假设,不是实测。 真实的每渠道 CAC 由竞价市场实时决定,需真实业务环境,命令行不可复现。本节把它们写成配置,是为了演示口径,不是为了给某个具体渠道定价。
  • 真实 ROI(广告投入产出比)同样不可复现。 ROI 要的是「某笔投放花了多少钱、带来多少收入」的真实流水,需真实业务环境,命令行不可复现。本节的 LTV/CAC 是 ROI 的上限代理——它假设全部收入都归因于该次买量,真实归因要打折扣。
  • LTV 曲线本身也是假设。 留存曲线的分段 keep、ARPDAU、ARPPU 都是设计假设(11.1、11.2 已说明)。它们是可复现的「模型」,不是可复现的「真相」。

所以本节脚本的定位要精确表述:它做的是「给定 CAC 假设,算回本周期与 LTV/CAC」——这步完全可复现;它做不了的是「告诉你真实 CAC 是 2.5 还是 7」——那步需真实业务环境,命令行不可复现。把这两件事混在一起,是买量分析里最常见的错误:拿模型算出的 LTV/CAC 当成实测 ROI 去决策。

小结

CAC 与买量口径这一节,核心是把「不可复现的输入」和「可复现的计算」切开。LTV 曲线(570 分)、回本周期(D42 / D116 / 不回本)、LTV/CAC(2.28 / 1.43 / 0.81)都是命令行可复现的输出;而真实 CAC、真实 ROI 属 X 层——需真实业务环境,命令行不可复现。产品能改的是 LTV 曲线,买量能改的是 CAC,回本周期是这两条线的交点,也是这门生意最硬的判据。

第 11 章到此结束:11.1 给曲线、11.2 给 LTV 与回本、11.3 给买量口径。下一章换个角度回到「人」——新手漏斗与 D1/D7/D30 怎么拆、流失预警怎么做、留存与付费怎么联合分析。

阅读导航:上一节:11.2 LTV 建模与回本周期 · 下一节:12.1 新手漏斗与 D1/D7/D30 。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「slg」更多文章

  1. 12.3 留存与付费的联合分析
  2. 12.2 流失预警与召回
  3. 附录 C:全书公式、参数与数据集索引