Budgets flexibles – Compte administrateur

Icône Outils

Ce script étend les budgets flexibles afin qu'ils puissent être exécutés pour plusieurs comptes associés à un seul compte administrateur. Avec les budgets flexibles, vous pouvez ajuster quotidiennement le budget de votre campagne de manière dynamique, à l'aide d'un schéma de répartition budgétaire personnalisé.

Le script lit une feuille de calcul pour chaque compte/campagne spécifié et le budget correspondant (associé aux dates de début et de fin), trouve la campagne, calcule le budget pour la journée en cours, le définit comme budget quotidien et enregistre le résultat dans la feuille de calcul. Il ne s'applique pas aux campagnes qui ne sont pas spécifiées dans la feuille de calcul.

Comment ça marche ?

Le script fonctionne de la même manière que le script de budget flexible pour un compte unique. La seule fonctionnalité supplémentaire est qu'il permet d'utiliser plusieurs comptes via la feuille de calcul spécifiée.

Les deux premières colonnes spécifient la campagne pour laquelle calculer le budget, les trois suivantes indiquent ses informations budgétaires et la dernière enregistre le résultat de l'exécution.

L'ID de compte doit correspondre à un compte d'annonceur, et non à un compte administrateur.

Vous pouvez définir plusieurs budgets pour le même compte ou la même campagne, mais assurez-vous de n'avoir qu'un seul budget actif à la fois. Sinon, un calcul plus récent risque d'écraser un ancien budget.

Si un compte ou une campagne n'est pas spécifié dans la feuille de calcul, le script ne définit pas de budget flexible pour celui-ci.

Tester des stratégies budgétaires

Le script inclut un code de test qui simule les effets d'une exécution sur plusieurs jours. Cela vous donne une meilleure idée de ce qui se passe lorsque l'exécution quotidienne du script est planifiée sur une période donnée.

Par défaut, ce script simule une répartition budgétaire égale de 500 $dépensés sur 10 jours.

Vous pouvez exécuter le code de test en appelant testBudgetStrategy au lieu de setNewBudget dans la méthode principale:

function main() {
  testBudgetStrategy(calculateBudgetEvenly, 10, 500);
  //  setNewBudget(calculateBudgetWeighted);
}

L'appel de fonction setNewBudget est commenté, ce qui indique que le script exécute le code de test. Voici le résultat de l'exemple :

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

Un nouveau budget est calculé quotidiennement afin de s'assurer qu'il est dépensé de manière uniforme chaque jour. Une fois la limite d'attribution initiale du budget dépassée, celui-ci est défini sur zéro pour interrompre les dépenses.

Vous pouvez modifier la stratégie budgétaire soit en changeant de fonction, soit en modifiant la fonction elle-même. Le script est fourni avec deux stratégies prédéfinies: calculateBudgetEvenly et calculateBudgetWeighted. L'exemple précédent vient de tester la première. Mettez à jour la ligne testBudgetStrategy pour utiliser la deuxième:

testBudgetStrategy(calculateBudgetWeighted, 10, 500);

Cliquez sur Preview (Aperçu) et vérifiez ce qui est consigné dans le journal de résultat. Notez que cette stratégie budgétaire alloue moins de budget au début de la période et l'augmente pendant les jours suivants.

Vous pouvez utiliser cette méthode de test pour simuler les modifications apportées aux fonctions de calcul du budget et tester votre propre approche de répartition du budget.

Répartition du budget

Examinons de plus près la stratégie budgétaire calculateBudgetWeighted:

// One calculation logic that distributes remaining budget in a weighted manner
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);
  }
}

Cette fonction utilise les arguments suivants :

  • costSoFar: coût de la campagne du startDate à aujourd'hui.
  • totalBudget: montant à dépenser entre le startDate et le endDate
  • daysSoFar: nombre de jours écoulés entre le startDate et aujourd'hui
  • totalDays: nombre total de jours entre startDate et endDate.

Vous avez la possibilité d'écrire votre propre fonction à condition d'inclure ces arguments. À l'aide de ces valeurs, vous pouvez comparer le montant dépensé jusqu'à présent et le montant à dépenser globalement, et déterminer où vous en êtes dans le calendrier pour l'ensemble du budget.

Plus précisément, cette stratégie budgétaire détermine le montant restant du budget (totalBudget - costSoFar) et le divise par deux fois le nombre de jours restants. Cette valeur permet de pondérer la répartition du budget jusqu'à la fin de la campagne. En utilisant le coût depuis startDate, il tient également compte des "jours de ralentissement" pendant lesquels vous ne dépensez pas la totalité du budget que vous avez défini.

Budgétisation en production

Une fois que vous êtes satisfait de votre stratégie budgétaire, vous devez apporter quelques modifications avant de pouvoir planifier l'exécution quotidienne de ce script.

Tout d'abord, mettez à jour la feuille de calcul en indiquant le compte, la campagne, le budget, la date de début et la date de fin (une ligne pour chaque budget de campagne).

  • ID de compte: ID du compte (au format xxx-xxx-xxxx) de la campagne à laquelle appliquer la stratégie budgétaire.
  • Campaign Name: nom de la campagne à laquelle appliquer la stratégie budgétaire.
  • Date de début: date de début de votre stratégie budgétaire. Il doit s'agir de la date du jour ou d'une date antérieure.
  • Date de fin: dernier jour pendant lequel vous souhaitez diffuser des annonces avec ce budget.
  • Budget total: montant total que vous souhaitez dépenser. Cette valeur est exprimée dans la devise du compte et peut être dépassée selon la date d'exécution planifiée du script.

Ensuite, désactivez le test et activez la logique pour modifier le budget en situation réelle :

function main() {
  //  testBudgetStrategy(calculateBudgetEvenly, 10, 500);
  setNewBudget(calculateBudgetWeighted);
}

Le résultat de chaque campagne est enregistré dans la colonne Execution Result.

Planification

Planifiez l'exécution de ce script quotidiennement à minuit ou juste après minuit dans le fuseau horaire local, afin de gérer autant que possible votre budget du lendemain. Notez toutefois que la récupération des données de rapports, telles que le coût, peut être retardée d'environ trois heures. Par conséquent, le paramètre costSoFar peut faire référence au total de la veille pour un script dont l'exécution est planifiée après minuit.

Préparation

  • Cliquez sur le bouton ci-dessous pour créer le script dans votre compte Google Ads.

    Installer le modèle de script

  • Cliquez sur le bouton ci-dessous pour créer une copie du modèle de feuille de calcul.

    Copier le modèle de feuille de calcul

  • Mettez à jour spreadsheet_url dans votre script.

  • Planifiez l'exécution du script quotidiennement (Daily).

Code source

// 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 MCC Flexible Budgets
 *
 * @overview The MCC Flexible Budgets script dynamically adjusts campaign budget
 *     daily for accounts under an MCC account with a custom budget distribution
 *     scheme. See
 *     https://developers.google.com/google-ads/scripts/docs/solutions/manager-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.4
 *   - Add support for video and shopping campaigns.
 * - version 1.0.3
 *   - Added validation for external spreadsheet setup.
 * - version 1.0.2
 *   - Fix a minor bug in variable naming.
 *   - 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 = {
  // URL of the default spreadsheet template. This should be a copy of
  // https://docs.google.com/spreadsheets/d/17wocOgrLeRWF1Qi_BjEigCG0qVMebFHrbUS-Vk_kpLg/copy
  // Make sure the sheet is owned by or shared with same Google user executing the script
  'spreadsheet_url': 'YOUR_SPREADSHEET_URL',

  'advanced_options': {
    // Please fix the following variables if you need to reformat the
    // spreadsheet
    // column numbers of each config column. Column A in your spreadsheet has
    // column number of 1, B has number of 2, etc.
    'column': {
      'accountId': 2,
      'campaignName': 3,
      'startDate': 4,
      'endDate': 5,
      'totalBudget': 6,
      'results': 7
    },

    // Actual config (without header and margin) starts from this row
    'config_start_row': 5
  }
};

const SPREADSHEET_URL = CONFIG.spreadsheet_url;
const COLUMN = CONFIG.advanced_options.column;
const CONFIG_START_ROW = CONFIG.advanced_options.config_start_row;

function main() {
  // Uncomment the following function to test your budget strategy function
  // testBudgetStrategy(calculateBudgetEvenly, 10, 500);
  setNewBudget(calculateBudgetWeighted);
}

// Core logic for calculating and setting campaign daily budget
function setNewBudget(budgetFunc) {
  console.log(`Using spreadsheet - ${SPREADSHEET_URL}.`);
  const spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
  spreadsheet.setSpreadsheetTimeZone(AdsApp.currentAccount().getTimeZone());
  const sheet = spreadsheet.getSheets()[0];

  const endRow = sheet.getLastRow();

  const mccAccount = AdsApp.currentAccount();
  sheet.getRange(2, 6, 1, 2).setValue(mccAccount.getCustomerId());

  const today = new Date();

  for (let i = CONFIG_START_ROW; i <= endRow; i++) {
    console.log(`Processing row ${i}`);

    const accountId = sheet.getRange(i, COLUMN.accountId).getValue();
    const campaignName = sheet.getRange(i, COLUMN.campaignName).getValue();
    const startDate = new Date(sheet.getRange(i, COLUMN.startDate).getValue());
    const endDate = new Date(sheet.getRange(i, COLUMN.endDate).getValue());
    const totalBudget = sheet.getRange(i, COLUMN.totalBudget).getValue();
    const resultCell = sheet.getRange(i, COLUMN.results);

    const accountIter = AdsManagerApp.accounts().withIds([accountId]).get();
    if (!accountIter.hasNext()) {
      resultCell.setValue('Unknown account');
      continue;
    }
    const account = accountIter.next();
    AdsManagerApp.select(account);

    const campaign = getCampaign(campaignName);
    if (!campaign) {
      resultCell.setValue('Unknown campaign');
      continue;
    }

    if (today < startDate) {
      resultCell.setValue('Budget not started yet');
      continue;
    }
    if (today > endDate) {
      resultCell.setValue('Budget already finished');
      continue;
    }

    const costSoFar = campaign
                          .getStatsFor(
                              getDateStringInTimeZone('yyyyMMdd', startDate),
                              getDateStringInTimeZone('yyyyMMdd', endDate))
                          .getCost();
    const daysSoFar = datediff(startDate, today);
    const totalDays = datediff(startDate, endDate);
    const newBudget = budgetFunc(costSoFar, totalBudget, daysSoFar, totalDays);
    campaign.getBudget().setAmount(newBudget);
    console.log(
        `AccountId=${accountId}, CampaignName=${campaignName}, ` +
        `StartDate=${startDate}, EndDate=${endDate}, ` +
        `CostSoFar=${costSoFar}, DaysSoFar=${daysSoFar}, ` +
        `TotalDays=${totalDays}, NewBudget=${newBudget}'`);
    resultCell.setValue(`Set today's budget to ${newBudget}`);
  }

  // update "Last execution" timestamp
  sheet.getRange(1, 3).setValue(today);
  AdsManagerApp.select(mccAccount);
}

// One calculation logic that distributes remaining budget evenly
function calculateBudgetEvenly(costSoFar, totalBudget, daysSoFar, totalDays) {
  const daysRemaining = totalDays - daysSoFar;
  const budgetRemaining = totalBudget - costSoFar;
  if (daysRemaining <= 0) {
    return budgetRemaining;
  } else {
    return budgetRemaining / daysRemaining;
  }
}

// One calculation logic that distributes remaining budget in a weighted manner
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);
  }
}

// Test function to verify budget calculation logic
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;
  }
}

// Return 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);
}

// Produces a formatted string representing a given date in a given time zone.
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();
    }
  }
  return null;
}

/**
 * Validates the provided spreadsheet URL to make sure that it's set up
 * properly. Throws a descriptive error message if validation fails.
 *
 * @param {string} spreadsheeturl The URL of the spreadsheet to open.
 * @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
 * @throws {Error} If the spreadsheet URL hasn't been set
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
    throw new Error(
        'Please specify a valid Spreadsheet URL. You can find' +
        ' a link to a template in the associated guide for this script.');
  }
  return SpreadsheetApp.openByUrl(spreadsheeturl);
}