灵活预算 - 单一账号

工具图标

在 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_DATE 之间的总天数。END_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}`);
}