유연한 예산 - 단일 계정

도구 아이콘을 클릭합니다.

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_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}`);
}