彈性預算 - 單一帳戶

工具圖示

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

指令碼每天都會計算新的預算,確保預算支出平均分配。達到分配的預算上限後,預算就會設為 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_DATE分配給 END_DATE
daysSoFar
START_DATE 到今天的間隔天數。
totalDays
START_DATEEND_DATE 的總天數。

You can write your own function as long as it takes these arguments. 您可以加入這些引數,自行撰寫函式。透過這些值,您可以比較您到目前為止花費的金額與整體支出,並判斷您目前在整筆預算的時間軸中處於哪個位置。

請特別注意,此預算策略會推算出剩餘的預算 (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}`);
}