素材表现 — 第 5 轮,_260730 全球投放 · 一个 CBO 广告组里的三条素材

归类:参考。 数据截至 2026-08-01 16:00 PT。广告系列 bailingxia_meituan_ww_cvr_260730,单个广告组 ww_broad_purchase_adv_cbo150,CBO,Purchase 事件,全球投放,Android。三条素材:ww_bodysuit_10sww_bikini_10sww_pool5_15s。 投放期 2026-07-30 15:00 PT 到 2026-08-01。三个自然日里大约 $1,072 的媒体花费

这份报告走的是广告账户自己的时钟,UTC-7,对它要测的东西来说这是对的。 每日投放报告在 2026-08-05 改用 UTC 日,为的是和 Adjust 对齐,所以从那边过来的读者会发现这里的 数字用的是另一套口径。本报告的一切都建立在预算区间和按账户时钟切出来的分小时投放数据上, 而一次预算变动就是那个时钟上某个钟点时刻发生的事件 (daily_report_format.md §「报表时钟」)。

这个测试设计时要回答的问题: 三条素材里哪一条赢?

重点creative_value_model.md 的哪些部分适用):第 7 节, 合并投放的广告组里做实验和算法择优(bandit)之间的冲突。在这份报告里,那一节不再是一条边界说明, 它就是全部结论。方法里排序的那几节故意没有用,因为它们预设的前提在这里不成立,而且这一点是 能测出来的:它们假定各个实验臂拿到了可比的投放。替代它们的是一套建立在新计量口径(第 4 节)上的 同期群测量,这是这个账户里第一次能把收入挂回产生它的那批流量。

1一句话结论

这个测试回答不了它自己提的问题,原因不是样本量。 Meta 给三条素材的投放窗口互不重叠,所以这个 设计产出的每一个单素材数字,CTR、单次购买成本、ROAS 以及估算器的 Net per $,说的都是 Meta 什么时候选择跑这条广告,而不是这条广告有多好。在唯一一个两条素材跑了相同小时、两个同期群的钱 也都已经到齐的窗口里,它们分不开

不过这个测试确实产出了三样比它本来要找的答案更值钱的东西:一套测出来的分配机制(第 2 节)、 一个能用的按用户归因的计量口径(第 4 节),以及四个被推翻、并且错法完全一样的结论(第 6 节)。

2快照

ww_bodysuit_10sww_bikini_10sww_pool5_15s
全周期花费$457.15$285.63$36.63
占广告组花费58.7%36.6%4.7%
已归因安装2,337822157
2026-08-01 17:00 PT 的投放状态已关闭已关闭在投,只剩它一条

整段跑下来 pool5 只拿到 4.7% 的钱,而且从 07-30 21:00 之后再没有过投放。

3实际发生的事:逐小时的分配

时间(PT)发生了什么
07-30 15:00–16:00三条素材同时开跑,预算 $150/天
07-30 21:00pool5 bikini 最后一个有投放的小时
07-30 22:02预算 $150 → $250
07-30 22:00 → 07-31 17:00只有 bodysuit 在跑,占 100% 投放,约 19 小时
07-31 07:19预算 $250 → $500
07-31 17:21预算 $500 → $875
07-31 18:00bikini 重新有投放,在 $875 那一步之后 39 分钟,当天拿走 27%
08-01 00:00–04:10bikini 拿走这一段的约 73%
08-01 04:10预算 $875 → $500;03:40 到 06:58 之间有五次开关切换
08-01 06:55bikini 被关掉(人工)
08-01 约 17:00bodysuit 被关掉(人工),只剩 pool5

这条时间线在 2026-08-01 按 Ads Manager 的操作历史做了更正。 本报告和另外三份文档此前带的版本 是 「07-31 07:19 从 $150 直接到 $875」,数值、次数和时间全错。预算历史从任何导出文件里都读不 出来Ad set budget 这一列填的是当前状态,每一条历史行上都是同一个值,所以今天拉的导出会把 875 Daily 一路报到 07-01,那时广告系列还不存在。它只在 manage/campaigns/history 里 (选中广告系列后点 See history 或按 Ctrl+I)。三天的分析建立在一条没有核实过的预算时间线上。

择优发生在 07-30,不是预算那一步造成的。 pool5bikini 都在 07-30 21:00 归零,比预算 变动早十个小时。Meta 在 $150/天 的时候就已经收敛到一个赢家,并且一路带着它跑过了那次提预算。

广告组内部赢家通吃是常态,而对标账户比我们更集中。 SeedMotion 三个广告组里,头号广告分别拿走 全周期花费的 85.1%、85.9% 和 79.4%,我们是 58.7%;他们的 0726-9 掉到 0.9%,然后连续三天 彻底归零,和 pool5 一模一样。他们的账户看起来分散,只是因为那是三场并行的赢家通吃, 每场每天喂 $50–148。

预算决定的是 Meta 要在一份已经定死的排序里往下够多远。 07-31 Meta 把 bodysuit 反复推给同一批 人,它的频次从 1.16 涨到 1.51;就算有 bikini 帮忙,广告系列也只花出去 $875 里的 $525。 赢家吃不下这个预算,溢出的部分就流向了优化器还留着的那条广告,而它当时只占 5–10%。在只有一个 广告组的账户里,你的第二名就是还活着的那条;在有三个广告组的账户里,第二名是另一个广告组里已经 跑出来的赢家。

4为什么这个设计给不出可靠的单素材结论

三条素材直到 08-01 都没有共享过投放窗口。凡是在各自窗口上算出来的主要数字,带的都是窗口, 不是素材:

结论支撑它的数字它实际测的是什么
bikini 的 CTR 低两个百分点07-31 的 8.39% 对 10.41%bodysuit 跑满 24 小时,bikini 只跑 18:00–23:59。按两条在跑的小时对齐:8.39% 对 8.92%,差 0.5 个百分点
bikini 全周期回报 0.93×全周期 ROAS几乎全部来自 07-31 晚间,那是当天最差的窗口,而 bodysuit 并没有被关在那个窗口里
bikini 吃的是便宜流量18:00–23:59 的 CPM 塌陷十个共同投放小时里有九个 bikini 付的是溢价;这个溢价跟着 Meta 推它的力度走(每小时 $1.6 时 0.98×,每小时 $20 以上 1.43×,R² = 0.79),不是素材本身的属性。见第 6 节的附注
pool5 因为受众太窄被放弃CTR 最高=受众最窄07-30 bodysuit 的 CTR 是 14.04%pool5 是 13.59%。CTR 最高的那条正是 Meta 留下的那条
pool5 被 15 秒时长卡在门外它一直没有投放版位 × 日期导出:它的版位结构和两条 10 秒的一致(FB Reels 34.3%,对 35.7%/37.8%)。18 个版位里它跑了 13 个,漏掉的 5 个在任何一条广告的展示里都不到 1.5%

衰减是真的。它既不是版位结构,也不是竞价深度,而是国家结构,预算就是调它的旋钮。

同一条素材,一天中相同的钟点,安装量相当,只有预算不同:

预算区间印度占安装的比例
$250/天,夜间(R2)19.5%
$500/天,白天(R3 R5,两个互相独立的日子)30.8% / 31.0%
$875/天,夜间(R4)41.3%

从 $250 到 $875,印度翻了一倍多,西班牙和智利归,土耳其和印度尼西亚大致减半。把预算调回 $500,印度回到前一天同样预算下的位置,相差在 0.3pp 以内;整个国家结构在约 2.5pp 以内复现。 这是一个旋钮,而且它会回来。

本来被当成三件互不相干的怪事,机制只有一个。印度是我们最差的市场(排第 42:d7 $1.57, 美国 $4.05),所以往它那边挪会同时压低 CPM(流量便宜)、CTR(人群不同)和每安装收入。 到了 $875,Meta 没法用 $500 时买的那套结构把预算花完,就往量最便宜的地方去。

所以这个广告系列从来没有「衰减」过。 是预算把它推进了一个更差的国家结构,又把它推了回来。 要解释这三天的下滑,不需要素材疲劳,也不需要学习期受损;预算上限是一个控制国家结构的手段, 把预算压在 $500–600 的理由,这一条比 DNU 上限更站得住。

版位结构也在动,但它不是那个机制:固定住素材,bodysuit 的 Facebook 占比走 40.6% → 56.2% → 63.5%,Instagram 走 59.3% → 42.2% → 33.6%,而 CTR 在每个平台内部各自下滑 (Facebook 15.73% → 10.93% → 7.29%,Instagram 11.37% → 8.21% → 6.59%),并且漂移的方向是 朝着 Facebook,也就是 CTR 更高的那一边。

⚠ 而这会混淆本报告里每一个单素材对比。 bikini_10s 几乎整段都跑在 R4 里,也就是印度占 41% 的那个预算区间;bodysuit_10s 更好看的全周期数字很大一部分来自 R2 和 R3,那里印度占约 20–31%。 「bikini 不如 bodysuit」里有相当一部分可能是「R4 不如 R2」。这两条从来没有在同一个国家结构里比过, 素材这个问题重新打开了,没有定论,见第 7 节。

5这次测试产出的计量口径

要回答上面任何一个问题,都需要把收入挂回产生它的那次安装,而任何 Meta 导出都做不到:Meta 的 Purchases 列走的是它自己的 7 天点击归因,不带点击时间戳。可用的来源有两个,其中一个已经死了。

ad_ops/lum_notifications/parse_export.py 把两边都还原出来,并按 load_payments() 要的字段结构 输出 payments_for_estimator.csv696 笔付款里有 695 笔能挂回它的原始安装。 有四个坑必须处理, 每一个都不会报错,只会悄悄把结果弄脏:

  1. 每笔付款都会被播报两次,一条长格式带 账户ID,一条单行短格式带 用户ID 和 tracker。 直接数行数会把收入算成两倍。
  2. 单行格式是逗号分隔的,所以一个只以 \n 结尾的字段正则会让第一个字段把后面所有字段都吞进去。 这弄脏了 transaction_id,第 1 条的去重也就跟着失效。
  3. 金额按付款人的本地币种到账,一共 33 种,光 INR 就占 60% 的交易,而且 SKU 是分地区定价的。 把原始金额直接加总没有意义;做汇率换算才对,在大样本上它与 Meta 的美元转化价值之比落在 0.91–1.06,通过口径校验。
  4. Telegram Desktop 打的是固定的标准时偏移,不认夏令时。 全部 23,522 条消息都写着 UTC-08:00,包括七月和八月,而这两个月是 PDT(UTC-07:00)。按它写的偏移换算,每一行都会晚 一个小时。asset_estimatorpayments_timebase_lag() 正好抓到 −1 h,r = 0.9947; 现在读数是 +0 h,r = 0.9903。这一个小时不是无伤大雅的:修正之后,实验臂之间付费率的齐性 检验从 p = 0.018 变成 p = 0.136,也就是说,那一个小时凭空造出了一个并不存在的显著差异。

到账累积曲线,以及为什么只能在完整同期群上测

只用安装满 72 小时的用户来建:年轻同期群只贡献第一个小时的付款,后面的还没发生,把它们放进来会让 曲线前重后轻,并且低估从它推出来的每一个预测值。

第几小时收入累计占比付费用户累计占比
137.3%56.8%
964.3%73.6%
2475.2%79.1%
4884.1%86.8%
7288.7%90.9%

四分之一的收入在 24 小时之后才到。 任何按当日窗口读出来的数字都是被截过的,而哪个实验臂还在 买量,它被截掉的部分就最多。

6分素材,按修正后的口径

按安装同期群,收入和付费人数都做了成熟度修正。收入是汇率换算后的毛收入,没有扣应用商店抽成 (见「口径与边界」一节)。

素材安装日安装数付费率(修正后)同期群 ROAS 预测
pool5_15s07-301518.4%3.01×
bodysuit_10s07-30798.9%2.33×
bodysuit_10s07-311,2776.3%2.57×
bodysuit_10s08-019815.9%1.99×
bikini_10s07-313594.3%1.67×
bikini_10s08-014355.8%2.24×

整个测试里唯一干净的一次对比:08-01 PT 00:00–06:59 的安装,这是唯一一个两条素材跑相同小时、 两个同期群年龄也相同的窗口。

花费安装数付费率ARPUARPPUCPIROAS 预测
bodysuit_10s$64.472113.8% 实测$0.431$11.36$0.3062.02×
bikini_10s$160.434164.1% 实测$0.516$12.63$0.3861.91×

分不开:差 6%,而两边的付费用户只有 8 个和 17 个。

bodysuit 的下滑是真的,但它是一次阶跃,不是一路下滑:付费率跨过预算那一步从 8.9% 掉到 6.3%,之后稳在 5.9%。看上去还有第三段掉到 4.2%,那是最年轻的那批同期群还没付钱。

在所有能算的指标上 pool5 都是最好的一条:修正后付费率最高,安装最便宜,$0.243,预测回报 最高。它的底数是 151 次安装和 $36.63 的全周期花费,也就是 11 个付费用户,所以区间很宽,而且这个 数字是在 $150/天 的预算区间里挣来的。它是这个账户里最有希望、也最没被测过的东西。

7这次测试推翻的四个结论,以及同一个原因

结论被什么推翻它为什么错了
应该砍掉 bikini(CTR 低两个百分点)按小时对齐bodysuit 一整天去比 bikini 的一个晚上
应该留下 bikiniNet per $ 1.15,区间不碰 1.00)它自己的每安装收入Net per $ 是拿每条素材的漏斗去对同一 tier 内合并后的每安装收入打分。bikini 自己的是 $0.354,合并值是 $0.539,低 34%。修正后 0.73×,判 CUT
预算那一步是成本上升的主因(每付费用户成本 +54%)同一步上的 ARPPUARPPU 同时涨了 +58%。同期群 ROAS 是平的,1.91× → 1.96×
bodysuit 现在表现很差(付费率 7.6% → 4.2%)成熟度修正修正后是 8.9% → 6.3% → 5.9%。在预算变动处有一次阶跃,之后一直平着

四个都错在同一件事上:一个只读了一边的比率,而那个窗口是优化器自己挑的。

估算器因此在 2026-08-01 做了改动,在同一批数据上重跑,现在对两个在投的实验臂都返回 not comparable,而不是 KEEP。 一共四处修正:约 16 付费用户门槛现在数的是全周期付费 用户,不是窗口内的(bikini 明明有 32 个却被按 12 个卡住,就是这里出的错);门槛现在 拒绝给结论,而不是在 KEEP 旁边附一句人数不够;每安装收入改成按一个实测权重 (payer_rate × ARPPU)做收缩,而不是到某个阈值就整体切换,并且两个基数都打印出来delivery_overlap() 会把共同投放小时占各自投放小时不足 50% 的实验臂标成不可比。只做前三处 修正,bikini 就从 KEEP 变成 CUT;第四处才把它变成诚实的答案。每一道防护各自防的是哪一次 错误,写在 asset_estimator/README.md 里。这同时也回答了另一个问题:为什么 bikini 恰恰是在 当时可见的数据上看起来比留下来那条更好的时刻被砍掉的:

PT 06:55 时点,00:00–06:59 的安装花费当时可见收入当时可见 ROAS成熟后
bikini_10s$160.43$147.490.92×1.34×
bodysuit_10s$64.47$36.400.56×1.41×

bikini 最终的付费用户里已经有 68% 付过钱,这个同期群够成熟了。砍掉它依据的是全周期的 0.93×,那是把小时不对齐造成的混淆一路带下来的结果。Meta 自己的头号指标出于另一个独立的原因, 也指向同一个错误方向:Purchases 是交易笔数。 按每美元购买笔数看,bodysuit 好 37%; 按每美元收入看,bikini 好 64%。因为 $2.99 的点数包和 $48.99 的年订阅在计数上完全一样,而 bikini 更偏大额 SKU(pack_b 加 pack_c 占它交易的 22%,bodysuit 是 6%)。在这个账户里按 单次购买成本优化,就是在跟自己的收入结构对着干。

86b. 修正后的计量口径说了什么,什么还没定下来

按固定年龄的分小时同期群重建(第 4 节),以每条广告自己第一个有投放的小时对齐,原始值和成熟度 预测值并排,覆盖率标出:

各自开跑后的前 18 个投放小时花费付费用户ROAS@6hROAS@24h48h 预测
bodysuit_10s$177.26391.96×2.14×2.38×
bikini_10s$309.40300.98×1.26×(97% 为预测1.59×

在两条都有投放的那一段连续时间里,07-31 18:00 到 08-01 03:59,也就是整个 R4,在完全相同的小时、 完全相同的年龄上,bikini1.13×bodysuit1.45×。而且它被砍掉的那一刻正在 往下走:滚动 3 小时 ROAS 在 02:00 前后见顶 1.54,到 03:00 掉到 0.91。

所以砍掉这个动作是合理的,为它引用的那个数字不合理。 bikini 过了回本线,大约落后 bodysuit 20–25%,它从来不是那个用来关掉它的 0.82×。

还没定下来的部分,也是这份报告没法把素材问题结掉的原因: 上面两个数字都跨预算区间合并,而这 两条广告跑的并不是同一批区间。要补的测试是每条广告只在 R4 里的国家结构。如果在那里两边一致, 素材对比还站得住;如果 bikini 就算在同一个区间内部也更偏印度,那它就站不住,这个账户里每一个 素材结论都只是顶着素材名字的地域结论。

这个测试已经在 2026-08-01 跑过,并把本节结掉了regime_matched_redo_2026-08-01.mdDECISION_LOG.md #48)。 在 R4 内部,bikini 买到的印度占 31.8%,bodysuit 是 39.3%(−7.5pp,p = 0.007), 它买我们最差市场的比例反而更低。而这点结构差异折成钱是 −0.9%,95% 置信区间 [−21.2%, +27.5%]:结构上是真的,折成钱和零分不开。地域假设被否掉了,两个方向上都没有需要 施加的修正。

它同时把「落后 20–25%」这个数字撤回,两个方向都撤。这里每安装收入的变异系数是 6.68; 要分辨出 −19% 需要每个实验臂约 19,000 次安装,而这个窗口只有 732 和 488,bootstrap 出来的 ROAS 是 1.18× [0.64–1.82]1.45× [0.54–2.58]。第 5 节在 8 个和 17 个付费 用户上给出的「分不开」是对的,而且它适用于本报告里每一个单素材 ROAS,包括按修正口径算出来的 那些。

投放这一侧同样分不开。 在素材自己能控制的每一个环节上两条都分不开:CTR −7.0% [−24.9%, +15.1%],点击→安装 −14.9% [−33.7%, +9.3%],付费率 p = 0.31。唯一能把它们分开的是 CPM,而 CPM 由竞价决定,并且在 Meta 给一条广告加量时出现,停下来就消失:每小时 $1.6 时 0.98×,每小时 $20 以上 1.43×,对广告自己的分小时花费回归 R² = 0.79。Meta 自己的质量排名在 每一份导出里都是空的。

所以砍掉这个动作在两个方向上都没有证据支撑。 它可能是对的,但没有被论证过。控制地区之后的 付费率偏向另一边(MH OR 3.17,p = 0.096),而 bodysuit 的优势来自 BE/SI/CH 的三个付费用户, 他们占它 R4 收入的 55%。

第 2 节的分配时间线也不应该当成判断来读。在 $875 那一步,Meta 手上有两个被搁置的实验臂, pool5(13 次购买,ROAS 1.67)和 bikini(1 次,0.58);它重新启用了 bikini,没有再给 pool5 任何量,并且在 bikini 累计 ROAS 只有 0.21 的时候把它推到 76% 的预算。那是产能问题: $875/天 需要每小时 $36.5,而 bodysuit 的峰值是 $28.5。

9建议

  1. 不要再在 CBO 广告组里做素材对比。 择优只靠 CTR 就能在大约六小时内收敛,那时一笔购买都还 没回传,而且收敛之后很难翻盘。要读一条新素材,需要一个强制等预算的测试广告组,或者给它自己的 广告系列。
  2. pool5 自己的广告系列和自己的预算,$150/天,也就是它那个 3.01× 被测出来的预算区间, 这样结果是可比的,而不是一个全新的未知数。把它开在现有广告组里是测不出来的;它已经开了两天, 量是零。

2026-08-01 修订。 预算在这里的分量远比上面假设的小:用全账户的国家价值给人群估值, $150 区间的人群对应 1.48 的预期 ROAS,$500 区间是 1.45,差 2%。pool5 的数字不是预算 区间造成的假象,也不需要为了可比而在 $150 上重跑一次。它同样还不是一个结果:07-30 那批 同期群现在已经实现 2.52×($36.62 的花费挣回 $92.23,基本走完,100% 的收入在 48 小时内 到账),底数是 11 个付费用户,其中一个账户占了 26% 的收入

  1. 预算设成赢家素材吃得下的量。 设高了,Meta 就会把它已经降权的广告重新拉回来,价格还更差, 第 2 节讲的就是这一整套机制。
  2. 不要再看单次购买成本。 用每美元收入和修正后的付费率。

2026-08-01 被 #48 取代。 在这个账户的量级上,每美元收入同样排不出素材次序,见第 6 节的 附注。用 CPM 和 CTR 来排序,它们在一个晚上的展示量上就能分辨 0.5pp 的差别;收入用来判断 这个账户值不值得跑,那是一个数字,不是两者之间的比较。设计测试之前先跑 ad_ops/lum_notifications/test_power.py

  1. 修好 adjust_sink,第一件事是去 Adjust 后台检查回调 URL。在那之前,这一类分析都要靠手动 导出 Telegram,而且只能回溯到 2026-07-11。

10口径与边界:什么会改变这些答案

11复现

# 1. 还原通知频道(LumUser 与 LumPayment 的 Telegram Desktop HTML 导出)
python ad_ops/lum_notifications/parse_export.py <LumPayment_dir> <LumUser_dir> \
    --out _runs/<run>/lum --tz America/Los_Angeles

# 2. 挂上付费用户跑估算器
export ADJUST_API_TOKEN=$(bwx field suite.adjust.com api_token | tail -1)
python -m ad_ops.asset_estimator \
  --export ad_ops/_data/b1_meta_exports/B1-Ads-Jul-31-2026-Jul-31-2026.csv \
  --pt-day 2026-07-31 \
  --payments _runs/<run>/lum/payments_for_estimator.csv \
  --tiers '{"WW broad": "bailingxia_meituan_ww_cvr_260730"}' --out _runs/<run>/est_0731

在信任下游任何东西之前,先确认 time-base check vs Adjust: best lag +0 h。同期群、成熟度和 砍掉那一刻的分析在 _runs/2026-08-01_account_recheck/lum_*.py。这个窗口的 Meta 导出, 包括版位 × 日期的那一份,在 _data/b1_meta_exports/

Internal — noindex. Not for distribution outside the team.