Bodaanpassingen voor doelgroepen

Pas automatisch bodaanpassingen toe op doelgroepen in campagnes. 

Start nu!
Bodaanpassingen voor doelgroepen
Bodaanpassingen Get started!

Om meer inzicht te verkrijgen in de prestaties van je bezoekers en klanten, voeg ik altijd doelgroepen toe aan campagnes. Hiermee kan je dan bodaanpassingen doen op basis van de prestaties van deze doelgroepen. Afhankelijk van de klant hebben we soms 100 verschillende doelgroepen toegevoegd aan een campagne. In plaats van deze doelgroepen elke week na te lopen en bodaanpassingen te veranderen, hebben we dit proces geautomatiseerd.

Bodaanpassing op basis van CPA of ROAS

Met de laatste aanpassingen in het script is het mogelijk om de berekening van de bodaanpassing te baseren op CPA of ROAS. De bodaanpassing wordt berekent door de CPA of ROAS van een doelgroep te vergelijken met de CPA of ROAS van de bijbehorende campagne.

Vergeet ook niet om de filters (MINIMUM_*) in te vullen. De doelgroepen moeten aan alle minimum waarden voldoen, voordat er een bodaanpassing op doorgevoerd wordt.

We hebben ook scripts voor bodaanpassingen op locaties, apparaten en voor elk uur van de dag. Start gelijk met het automatiseren van alle bodaanpassingen in jouw accounts!

Instellingen

In het script kan je slechts de volgende zaken aanpassen:

  • LOG: Geef aan of het script de tussenstappen moet rapporteren, door de waarde aan te passen naar 'true'.
  • DATE_RANGE: Het script kijkt naar de conversies en kosten over deze periode.
  • MINIMUM_CONVERSIONS: Minimaal aantal conversies. Wanneer een doelgroep minder conversies heeft, wordt er geen bodaanpassing gedaan.
  • MINIMUM_CONVERSIONVALUE: Minimale conversiewaarde. Wanneer een doelgroep minder conversiewaarde heeft, wordt er geen bodaanpassing gedaan.
  • MINIMUM_COST: Minimale kosten voor een doelgroep. Wanneer een doelgroep minder uitgaven heeft, wordt er geen bodaanpassing gedaan.
  • MINIMUM_CLICKS: Minimaal aantal klikken. Wanneer een doelgroep minder klikken heeft, wordt er geen bodaanpassing gedaan.
  • KPI: Kies om de bodaanpassing te berekenen op basis van 'CPA' of 'ROAS'
  • CAMPAIGN_LABEL: Gebruik een label om campagnes te selecteren
  • MAX_BID: De bodaanpassing wordt nooit hoger dan hier ingesteld.
  • MIN_BID: De bodaanpassing wordt nooit lager dan hier ingesteld.

Frequentie: Om voldoende data te genereren adviseren we om het script eenmaal per dag of eenmaal per week te draaien.

The script
// Copyright 2021. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
// 
// Created: 01-03-2019
// Last update: 23-03-2021
//
// ABOUT THE SCRIPT
// With this script we adjust the biddings for audiences in 
// active campaigns.
//
////////////////////////////////////////////////////////////////////

var config = {

  LOG : true,
  DATE_RANGE : "LAST_30_DAYS",

  // Optional: Use minimum conversions and/or cost to select audiences
  // Leave empty to skip
  MINIMUM_CONVERSIONS : "",
  MINIMUM_CONVERSIONVALUE : "",
  MINIMUM_COST : "",
  MINIMUM_CLICKS : "",
  
  // Set bidadjustments based on CPA or ROAS
  KPI : "ROAS",
  
  // Optional: Use a campaignlabel to make a selection.
  // Leave empty to skip
  CAMPAIGN_LABEL : "",
  
  // Bidadjustments won't be higher then MAX_BID and not lower then MIN_BID
  // Examples: 1.2 = +20%, 0.8 = -20%, 2 = +100%
  MAX_BID : 1.2,
  MIN_BID : 0.8
  
}


////////////////////////////////////////////////////////////////////

function main() {
  
  if(config.KPI != "CPA" && config.KPI != "ROAS"){
    throw Error("Set KPI to 'CPA' or 'ROAS' only!");
  }
  
  var map = {};
  
  // Depending on use of campaign label, a function is triggered
  if(config.CAMPAIGN_LABEL == ""){ map = collectAudiences(map) }
  else{ map = collectAudiencesLabel(map) }
  
  if(config.LOG === true){
    Logger.log(" ");
    Logger.log("...Loop through audiences");
    Logger.log(Object.keys(map).length + " items found");
    Logger.log(" ");
  }
  
  
  // Set bidadjustments
  for (var key in map) {
    
    var campaignIterator = AdsApp
      .campaigns()
      .withIds([key.split(",")[1]])
      .get();
    
    while(campaignIterator.hasNext()){
      var campaign = campaignIterator.next();
      
        var ids = [];
        ids.push(key.split(",")[1]);
        ids.push(key.split(",")[0]);
      
      var audienceIterator = campaign
        .targeting()
        .audiences()
        .withIds([ids])
        .get();

      while(audienceIterator.hasNext()){
        var audience = audienceIterator.next();

        audience.bidding().setBidModifier(parseFloat(map[key][0][2])); 
        Logger.log("Bidadjustment of " + map[key][0][2] + " set for " + map[key][0][0] + " in " + map[key][0][1]);
        
      } // audienceIterator
      
    } // campaignIterator
    
  } // keyIterator
  
} // function main()

////////////////////////////////////////////////////////////////////

function collectAudiences(map){
  
  var report = AdsApp.report(
   "SELECT Id, UserListName, CampaignName, CampaignId, BidModifier, Conversions, ConversionValue, Cost, Clicks " +
    "FROM AUDIENCE_PERFORMANCE_REPORT " +
    "WHERE BiddingStrategyType IN ['MANUAL_CPC','MANUAL_CPV','MANUAL_CPM'] " +
    "AND CampaignStatus = ENABLED " +
    "DURING " + config.DATE_RANGE
  );
  
  var rows = report.rows();
  while(rows.hasNext()){
    var row = rows.next();
    
    // Check thresholds
    if(config.MINIMUM_CONVERSIONS != "" || config.MINIMUM_CONVERSIONVALUE != "" ||config.MINIMUM_COST != "" || config.MINIMUM_CLICKS != ""){      
      if(row["Conversions"] < config.MINIMUM_CONVERSIONS || row["ConversionValue"] < config.MINIMUM_CONVERSIONVALUE || row["Cost"] < config.MINIMUM_COST || row["Clicks"] < config.MINIMUM_CLICKS) continue;
    }
    
    var campaignKpi = parseFloat(getCampaignKpi(row["CampaignId"])).toFixed(2);
    var oldBid = parseFloat(1 + (row["BidModifier"].split("%")[0] / 100)).toFixed(2);
    
    // Calculate KPI's and new bid
    if(config.KPI == "CPA"){ var audienceKpi = parseFloat(row["Cost"] / row["Conversions"]).toFixed(2); var newBid = parseFloat(campaignKpi / audienceKpi).toFixed(2); }
    if(config.KPI == "ROAS"){ var audienceKpi = parseFloat(row["ConversionValue"] / row["Cost"]).toFixed(2); var newBid = parseFloat(audienceKpi / campaignKpi).toFixed(2); }
    
    // If there is a CPA, we will set a bidadjustment
    if(isNaN(newBid) === false && isFinite(audienceKpi) === true) {
      if(newBid < config.MIN_BID) { newBid = parseFloat(config.MIN_BID).toFixed(2); }
      if(newBid > config.MAX_BID) { newBid = parseFloat(config.MAX_BID).toFixed(2); }  
      
      // Add to map
      if(newBid != oldBid) {
        if(map[row["Id"] + "," + row["CampaignId"]] == null) {
          map[row["Id"] + "," + row["CampaignId"]] = [];
        }
        map[row["Id"] + "," + row["CampaignId"]].push([row["UserListName"], row["CampaignName"], newBid]);
      
          if(config.LOG === true){
            Logger.log("Audience " + row["UserListName"] + " in " + row["CampaignName"]);
            Logger.log("Current bidadjustment: " + row["BidModifier"] + " = " + oldBid);
            Logger.log("Audience " + config.KPI + ": " + audienceKpi + ", Campain " + config.KPI + ": " + campaignKpi + ", New bid: " + newBid);
            Logger.log("------------");
          }   
      }

    } // Check bid
    
  } // rowIterator 
  
  return map
  
}

////////////////////////////////////////////////////////////////////

function collectAudiencesLabel(map){
  
  var campaignIterator = AdsApp
    .campaigns()
    .withCondition("Status = ENABLED")
    .withCondition("LabelNames CONTAINS_ANY ['" + config.CAMPAIGN_LABEL + "']")
    .get();
  
  while(campaignIterator.hasNext()){
    var campaign = campaignIterator.next();
    
    // Collecting all audience data
    var report = AdsApp.report(
      "SELECT Id, UserListName, CampaignName, CampaignId, BidModifier, Conversions, ConversionValue, Cost, Clicks " +
      "FROM AUDIENCE_PERFORMANCE_REPORT " +
      "WHERE BiddingStrategyType IN ['MANUAL_CPC','MANUAL_CPV','MANUAL_CPM'] " +
      "AND CampaignId = " + campaign.getId() +
      " DURING " + config.DATE_RANGE
    );
    
    var rows = report.rows();
    while(rows.hasNext()){
      var row = rows.next();

      // Check thresholds
      if(config.MINIMUM_CONVERSIONS != "" || config.MINIMUM_CONVERSIONVALUE != "" ||config.MINIMUM_COST != "" || config.MINIMUM_CLICKS != ""){
        if(row["Conversions"] < config.MINIMUM_CONVERSIONS || row["ConversionValue"] < config.MINIMUM_CONVERSIONVALUE || row["Cost"] < config.MINIMUM_COST || row["Clicks"] < config.MINIMUM_CLICKS) continue;
      }
      
      // Campaign performance
      var campaignKpi = parseFloat(getCampaignKpi(row["CampaignId"])).toFixed(2);
      var oldBid = parseFloat(1 + (row["BidModifier"].split("%")[0] / 100)).toFixed(2);

      // Calculate KPI's and new bid
      if(config.KPI == "CPA"){ var audienceKpi = parseFloat(row["Cost"] / row["Conversions"]).toFixed(2); var newBid = parseFloat(campaignKpi / audienceKpi).toFixed(2); }
      if(config.KPI == "ROAS"){ var audienceKpi = parseFloat(row["ConversionValue"] / row["Cost"]).toFixed(2); var newBid = parseFloat(audienceKpi / campaignKpi).toFixed(2); }

      // If there is a CPA, we will set a bidadjustment
      if(isNaN(newBid) === false && isFinite(audienceKpi) === true) {
        if(newBid < config.MIN_BID) { newBid = parseFloat(config.MIN_BID).toFixed(2); }
        if(newBid > config.MAX_BID) { newBid = parseFloat(config.MAX_BID).toFixed(2); }  

        // Add to map
        if(newBid != oldBid) {
          if(map[row["Id"] + "," + row["CampaignId"]] == null) {
            map[row["Id"] + "," + row["CampaignId"]] = [];
          }
          map[row["Id"] + "," + row["CampaignId"]].push([row["UserListName"], row["CampaignName"], newBid]);

            if(config.LOG === true){
              Logger.log("Audience " + row["UserListName"] + " in " + row["CampaignName"]);
              Logger.log("Current bidadjustment: " + row["BidModifier"] + " = " + oldBid);
              Logger.log("Audience " + config.KPI + ": " + audienceKpi + ", Campain " + config.KPI + ": " + campaignKpi + ", New bid: " + newBid);
              Logger.log("------------");
            }   
        }

      } // Check bid

    } // rowIterator     
    
  } // campaignIterator
  
  return map
  
}

////////////////////////////////////////////////////////////////////

function getCampaignKpi(campaignId){
  
  var report = AdsApp.report(
    "SELECT Cost, Conversions, ConversionValue " +
    "FROM CAMPAIGN_PERFORMANCE_REPORT " +
    "WHERE CampaignId = '" + campaignId + "' " +
    "DURING " + config.DATE_RANGE
  );
  
  var rows = report.rows();
  while(rows.hasNext()){
    var campaign = rows.next();
    
    if(config.KPI == "CPA"){ var campaignKpi = campaign["Cost"] / campaign["Conversions"]; }
    if(config.KPI == "ROAS"){ var campaignKpi = campaign["ConversionValue"] / campaign["Cost"]; }
    
  } // campaignIterator 
  
  return campaignKpi;
  
} // function getCampaignKpi()
Show whole script!
Loading Comments
The Experts
Tibbe van Asten Team Lead Performance Marketing
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
Jermaya Leijen Digital Marketing Strategist
Krzysztof Bycina PPC Specialist from Poland
How about you? JOIN US!
Sharing Knowledge
Caring

Adsscripts.com staat voor het delen van kennis. In de huidige markt houden SEA-specialisten de kennis en ervaring graag voor zich. Wij zijn er van overtuigd dat het delen van kennis ervoor kan zorgen dat iedereen beter wordt in haar of zijn werk. Daarom lopen wij hier graag in voorop, door onze kennis over scripts te delen met iedereen.

Wil jij ook graag een bijdrage leveren? Wij staan open voor nieuwe ideeën en feedback op alles wat je op Adsscripts.com vindt.

Neem contact op

Training &
Workshop
Neem contact op!
Adsscripts Training & Workshop