灵活预算 - 单一账号

工具图标

在 Google Ads 中,您可以为每个广告系列设置每日预算金额。不过,有些营销计划会产生固定费用;例如,“我希望在秋季促销活动开始前花费 5,000 美元”。出价策略可让您在一定程度上控制每日预算的支出方式,但无法控制预算在整个广告系列期间的消耗方式。

例如,如果我们只想花费 5,000 美元来宣传秋季促销活动,并且希望宣传 10 天,那么我们可以将每日预算设置为 500 美元,以便用完整个预算。不过,这假设我们每天都会花完所有预算,并且希望均匀分配支出。您无法告知 Google Ads 您希望在最后几天内用掉大部分预算。

此脚本却可以按照自定义的预算分配方案每天动态调整广告系列预算。

工作原理

测试预算策略

该脚本包含一些测试代码,用于模拟运行多天的效果。这样,您就可以更好地了解脚本在一段时间内每天按计划运行时可能会发生什么情况。

默认情况下,此脚本会模拟 10 天内均匀分配的 500 美元预算。

function main() {
  testBudgetStrategy(calculateBudgetEvenly, 10, 500);
  // setNewBudget(calculateBudgetEvenly, CAMPAIGN_NAME, TOTAL_BUDGET, START_DATE, END_DATE);
}

setNewBudget 函数调用已注释掉,表示它只会运行测试代码。以下是上述示例的输出结果:

Day 1.0 of 10.0, new budget 50.0, cost so far 0.0
Day 2.0 of 10.0, new budget 50.0, cost so far 50.0
Day 3.0 of 10.0, new budget 50.0, cost so far 100.0
Day 4.0 of 10.0, new budget 50.0, cost so far 150.0
Day 5.0 of 10.0, new budget 50.0, cost so far 200.0
Day 6.0 of 10.0, new budget 50.0, cost so far 250.0
Day 7.0 of 10.0, new budget 50.0, cost so far 300.0
Day 8.0 of 10.0, new budget 50.0, cost so far 350.0
Day 9.0 of 10.0, new budget 50.0, cost so far 400.0
Day 10.0 of 10.0, new budget 50.0, cost so far 450.0
Day 11.0 of 10.0, new budget 0.0, cost so far 500.0

每天,脚本都会计算新的预算,以确保预算支出均匀分配。当达到分配的预算限额时,预算会设置为零,从而停止支出。

您可以通过更改所使用的函数或修改函数本身来更改所使用的预算策略。该脚本附带两种预构建的策略:calculateBudgetEvenlycalculateBudgetWeighted。如需设置加权测试预算策略,请按如下方式更改 testBudgetStrategy

testBudgetStrategy(calculateBudgetWeighted, 10, 500);

点击预览并检查记录器输出。请注意,此预算策略会在周期的早期分配较少的预算,而在最后几天分配较多的预算。

您可以使用这种测试方法来模拟预算计算函数的变更,并尝试用自己的方法分配预算。

分配预算

calculateBudgetWeighted 预算策略通过以下函数实现:

function calculateBudgetWeighted(costSoFar, totalBudget, daysSoFar, totalDays) {
  const daysRemaining = totalDays - daysSoFar;
  const budgetRemaining = totalBudget - costSoFar;
  if (daysRemaining <= 0) {
    return budgetRemaining;
  } else {
    return budgetRemaining / (2 * daysRemaining - 1) ;
  }
}

此函数接受以下实参:

costSoFar
相应广告系列自 START_DATE 起至今天的累计费用。
totalBudget
START_DATEEND_DATE 的分配支出。
daysSoFar
START_DATE 到今天,已过去
天。
totalDays
START_DATEEND_DATE 之间的总天数。

您可以编写自己的函数,但必须包含这些参数。使用这些值,您可以比较到目前为止已经支出的金额与总预算金额,并确定您当前在整个预算时间表内的位置。

具体而言,此预算策略会计算剩余预算 (totalBudget - costSoFar),然后将其除以剩余天数的两倍。这种策略会使预算分配偏向广告系列的后期。通过使用自 START_DATE 以来的费用,该指标还会考虑未完全用完所设预算的“低迷期”。

根据真实情况设置预算

确定预算策略后,您需要进行一些更改,然后才能安排此脚本每天运行。

首先,更新文件顶部的常量:

  • START_DATE:将其设置为预算策略的开始时间。应该是当前或过去的日期。
  • END_DATE:将此值设置为您要使用此预算投放广告的最后一天。
  • TOTAL_BUDGET:您预计支出的总金额。此值以账号币种为单位,实际支出可能会超出该值,具体取决于脚本计划运行的时间。
  • CAMPAIGN_NAME:要应用此预算策略的广告系列的名称。

接下来,停用测试并启用实际更改预算的逻辑:

function main() {
  // testBudgetStrategy(calculateBudgetEvenly, 10, 500);
  setNewBudget(calculateBudgetWeighted, CAMPAIGN_NAME, TOTAL_BUDGET, START_DATE, END_DATE);
}

时间安排

将此脚本设置为每天在本地时区的午夜或过后不久运行,从而尽可能地充分利用新的一天的预算。不过请注意,检索到的报告数据(例如费用)可能会延迟大约 3 小时,因此对于计划在午夜后运行的脚本,costSoFar 参数可能引用的是昨天的总费用。

设置

  • 点击下方的按钮,在您的 Google Ads 账号中创建脚本。

    安装脚本模板

  • 保存脚本并点击预览按钮。此脚本(默认情况下)会模拟 10 天内预算为 500 美元的预算策略。记录器输出反映了模拟的日期、当天的分配预算以及迄今为止的总支出金额。

源代码

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * @name Flexible Budgets
 *
 * @overview The Flexible budgets script dynamically adjusts campaign budget for
 *     an advertiser account with a custom budget distribution scheme on a daily
 *     basis. See
 *     https://developers.google.com/google-ads/scripts/docs/solutions/flexible-budgets
 *     for more details.
 *
 * @author Google Ads Scripts Team [adwords-scripts@googlegroups.com]
 *
 * @version 2.1
 *
 * @changelog
 * - version 2.1
 *   - Split into info, config, and code.
 * - version 2.0
 *   - Updated to use new Google Ads scripts features.
 * - version 1.0.3
 *   - Add support for video and shopping campaigns.
 * - version 1.0.2
 *   - Use setAmount on the budget instead of campaign.setBudget.
 * - version 1.0.1
 *   - Improvements to time zone handling.
 * - version 1.0
 *   - Released initial version.
 */

/**
 * Configuration to be used for the Flexible Budgets script.
 */

CONFIG = {
  'total_budget': 500,
  'campaign_name': 'Special Promotion',
  'start_date': 'November 1, 2021 0:00:00 -0500',
  'end_date': 'December 1, 2021 0:00:00 -0500'
};

const TOTAL_BUDGET = CONFIG.total_budget;
const CAMPAIGN_NAME = CONFIG.campaign_name;
const START_DATE = new Date(CONFIG.start_date);
const END_DATE = new Date(CONFIG.end_date);

function main() {
  testBudgetStrategy(calculateBudgetEvenly, 10, 500);
//  setNewBudget(calculateBudgetEvenly, CAMPAIGN_NAME, TOTAL_BUDGET,
//      START_DATE, END_DATE);
}

function setNewBudget(budgetFunction, campaignName, totalBudget, start, end) {
  const today = new Date();
  if (today < start) {
    console.log('Not ready to set budget yet');
    return;
  }
  const campaign = getCampaign(campaignName);
  const costSoFar = campaign.getStatsFor(
        getDateStringInTimeZone('yyyyMMdd', start),
        getDateStringInTimeZone('yyyyMMdd', end)).getCost();
  const daysSoFar = datediff(start, today);
  const totalDays = datediff(start, end);
  const newBudget = budgetFunction(costSoFar, totalBudget, daysSoFar,
                                   totalDays);
  campaign.getBudget().setAmount(newBudget);
}

function calculateBudgetEvenly(costSoFar, totalBudget, daysSoFar, totalDays) {
  const daysRemaining = totalDays - daysSoFar;
  const budgetRemaining = totalBudget - costSoFar;
  if (daysRemaining <= 0) {
    return budgetRemaining;
  } else {
    return budgetRemaining / daysRemaining;
  }
}

function calculateBudgetWeighted(costSoFar, totalBudget, daysSoFar,
    totalDays) {
  const daysRemaining = totalDays - daysSoFar;
  const budgetRemaining = totalBudget - costSoFar;
  if (daysRemaining <= 0) {
    return budgetRemaining;
  } else {
    return budgetRemaining / (2 * daysRemaining - 1);
  }
}

function testBudgetStrategy(budgetFunc, totalDays, totalBudget) {
  let daysSoFar = 0;
  let costSoFar = 0;
  while (daysSoFar <= totalDays + 2) {
    const newBudget = budgetFunc(costSoFar, totalBudget, daysSoFar, totalDays);
    console.log(`Day ${daysSoFar + 1} of ${totalDays}, new budget ` +
                `${newBudget}, cost so far ${costSoFar}`);
    costSoFar += newBudget;
    daysSoFar += 1;
  }
}

/**
 * Returns number of days between two dates, rounded up to nearest whole day.
 */
function datediff(from, to) {
  const millisPerDay = 1000 * 60 * 60 * 24;
  return Math.ceil((to - from) / millisPerDay);
}

function getDateStringInTimeZone(format, date, timeZone) {
  date = date || new Date();
  timeZone = timeZone || AdsApp.currentAccount().getTimeZone();
  return Utilities.formatDate(date, timeZone, format);
}

/**
 * Finds a campaign by name, whether it is a regular, video, or shopping
 * campaign, by trying all in sequence until it finds one.
 *
 * @param {string} campaignName The campaign name to find.
 * @return {Object} The campaign found, or null if none was found.
 */
function getCampaign(campaignName) {
  const selectors = [AdsApp.campaigns(), AdsApp.videoCampaigns(),
      AdsApp.shoppingCampaigns()];
  for (const selector of selectors) {
    const campaignIter = selector
        .withCondition(`CampaignName = "${campaignName}"`)
        .get();
    if (campaignIter.hasNext()) {
      return campaignIter.next();
    }
  }
  throw new Error(`Could not find specified campaign: ${campaignName}`);
}