Flexible Budgets - Single Account

ツールアイコン

Google 広告 では、キャンペーンごとに 1 日の予算額を設定することができます。ただし、マーケティング施策によっては、固定費用が発生する場合があります(例: 「秋のセールに向けて 5,000 ドルを費やしたい」)。入札戦略を使用すると、1 日の予算の使い方はある程度制御できますが、キャンペーン期間中の予算の使い方は制御できません。

たとえば、秋のセールの宣伝に 5,000 ドルのみを費やし、10 日間宣伝したい場合は、1 日の予算を 500 ドルに設定して、予算を使い切ることができます。ただし、この場合、毎日全額を費やし、均等に費やすことが前提となります。予算の大部分を最後の数日間で費やしたいことを Google 広告に伝えることはできません。

このスクリプトでは、独自の予算配分スキームを使用して、キャンペーンの予算を日単位で動的に調整することができます。

仕組み

予算設定方法のテスト

このスクリプトには、複数日実行した場合の効果をシミュレートするテストコードが含まれています。これにより、スクリプトを一定期間毎日実行した場合に何が起こるかを把握できます。

デフォルトでは、このスクリプトは 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 という 2 つの事前構築済みの戦略が用意されています。加重テスト予算設定方法を設定するには、次のように 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 の間の合計日数。

関数は独自に記述できますが、これらの引数は必ず渡す必要があります。これらの値を使用すると、これまでに費やした金額と全体で費やす金額を比較して、予算全体のタイムラインにおける現在位置を判断できます。

特に、この予算設定方法では、残りの予算(totalBudget - costSoFar)を計算し、それを残りの日数の 2 倍で割ります。これにより、キャンペーンの終了に向けて予算配分が重くなります。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);
}

スケジュール

ローカル タイムゾーンの深夜 0 時かその直後に毎日スクリプトが実行されるようスケジュールを設定して、翌日にできるだけ多くの予算が配分されるようにします。ただし、費用などの取得したレポートデータは 3 時間ほど遅れる可能性があるため、深夜 0 時以降に実行されるようにスケジュール設定されたスクリプトの場合、costSoFar パラメータは前日の合計を参照している可能性があります。

設定

  • ボタンをクリックして、Google 広告アカウントにスクリプトを作成します。

    スクリプト テンプレートをインストールする

  • スクリプトを保存して、[プレビュー] ボタンをクリックします。このスクリプトは(デフォルトで)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}`);
}