Budget flessibili - Account amministratore

Seleziona l'icona degli strumenti

Questo script estende i budget flessibili in modo che vengano eseguiti per più account in un unico account amministratore. I budget flessibili possono modificare dinamicamente il budget della campagna ogni giorno con uno schema di distribuzione del budget personalizzato.

Lo script legge un foglio di lavoro per ogni account/campagna e budget corrispondente (associato alle date di inizio e fine), trova la campagna, calcola il budget per il giorno corrente, lo imposta come budget giornaliero della campagna e registra il risultato nel foglio di lavoro. Non tocca le campagne non specificate nel foglio di lavoro.

Come funziona

Lo script funziona nello stesso modo dello script Budget flessibile per un singolo account. L'unica funzionalità aggiuntiva è che supporta più account tramite il foglio di lavoro specificato.

Le prime due colonne specificano la campagna per cui calcolare un budget, le tre successive specificano le informazioni sul budget e le ultime tre registrano il risultato dell'esecuzione.

L'ID account deve essere un account inserzionista, non un account amministratore.

Puoi avere più budget per lo stesso account o la stessa campagna, ma assicurati di avere un solo budget attivo alla volta, altrimenti un calcolo del budget più recente potrebbe sovrascrivere quello precedente

Se un account o una campagna non sono specificati nel foglio di lavoro, lo script non imposterà un budget flessibile.

Test delle strategie di budget

Lo script include un codice di test per simulare gli effetti dell'esecuzione per più giorni. In questo modo puoi avere un'idea migliore di cosa succede quando lo script viene pianificato per essere eseguito ogni giorno in un determinato periodo di tempo.

Per impostazione predefinita, questo script simula una distribuzione del budget uniforme di 500 $spesi in 10 giorni.

Puoi eseguire il codice di test chiamando testBudgetStrategy anziché setNewBudget nel metodo principale:

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

La chiamata di funzione setNewBudget è commentata, indicando che lo script sta eseguendo il codice di test. Ecco l'output dell'esempio:

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

Ogni giorno viene calcolato un nuovo budget per garantire che venga speso in modo uniforme. Una volta superato l'allocazione iniziale del budget, quest'ultimo viene impostato su zero per interrompere la spesa.

Puoi cambiare la strategia di budget utilizzata cambiando la funzione utilizzata o modificando la funzione stessa. Lo script include due strategie predefinite: calculateBudgetEvenly e calculateBudgetWeighted. L'esempio precedente ha appena testato la prima. Aggiorna la riga testBudgetStrategy per utilizzare la seconda:

testBudgetStrategy(calculateBudgetWeighted, 10, 500);

Fai clic su Anteprima e controlla l'output del logger. Tieni presente che questa strategia di budget assegna meno budget all'inizio del periodo e lo aumenta nei giorni successivi.

Puoi utilizzare questo metodo di test per simulare modifiche alle funzioni di calcolo del budget e provare il tuo approccio alla distribuzione del budget.

Allocazione del budget

Diamo un'occhiata più da vicino alla strategia di budget 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);
  }
}

Questa funzione accetta i seguenti argomenti:

  • costSoFar: i costi accumulati da questa campagna dal startDate a oggi.
  • totalBudget: quanto spendere da startDate a endDate.
  • daysSoFar: quanti giorni sono trascorsi dal giorno startDate a oggi.
  • totalDays: numero totale di giorni compresi tra startDate e endDate.

Puoi scrivere una tua funzione purché siano necessari questi argomenti. Utilizzando questi valori, puoi confrontare l'importo speso finora con la spesa complessiva e determinare dove ti trovi attualmente all'interno del periodo di tempo per l'intero budget.

In particolare, questa strategia di budget determina il budget residuo (totalBudget - costSoFar) e lo divide per il doppio del numero di giorni rimanenti. Questo valore pondera la distribuzione del budget verso la fine della campagna. Utilizzando il costo dal giorno startDate, vengono tenuti in considerazione anche i "giorni di lentezza" in cui non spendi l'intero budget impostato.

Definire il budget in produzione

Se la tua strategia di budget ti soddisfa, devi apportare alcune modifiche prima di poter pianificare l'esecuzione giornaliera di questo script.

Innanzitutto, aggiorna il foglio di lavoro specificando account, campagna, budget, data di inizio e data di fine, ovvero una riga per ogni budget della campagna.

  • ID account: l'ID account (nel formato xxx-xxx-xxxx) della campagna a cui applicare la strategia di budget.
  • Nome campagna: il nome della campagna a cui applicare la strategia di budget.
  • Data di inizio: data di inizio della strategia di budget. Deve essere la data corrente o un giorno nel passato.
  • Data di fine: l'ultimo giorno in cui vuoi fare pubblicità con questo budget.
  • Budget totale: l'importo totale che stai cercando di spendere. Questo valore è nella valuta dell'account e può essere superato a seconda di quando è pianificata l'esecuzione dello script.

Successivamente, disattiva il test e attiva la logica in modo che modifichi effettivamente il budget:

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

Il risultato per ogni campagna viene registrato nella colonna Risultato esecuzione.

Programmazione

Pianifica questo script in modo che venga eseguito ogni giorno, a mezzanotte o poco dopo nel fuso orario locale, in modo da gestire il più possibile il budget del giorno successivo. Tieni presente, tuttavia, che i dati dei report recuperati, come il costo, potrebbero subire un ritardo di circa tre ore, quindi il parametro costSoFar potrebbe fare riferimento al totale di ieri per uno script pianificato per l'esecuzione dopo mezzanotte.

Configurazione

Codice sorgente

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