Bidadjustments for locations

Apply bid adjustments to the added locations in all campaigns based on the CPA or ROAS.

Start Now!
Bidadjustments for locations
Bidadjustments Get started!

By adding as many locations as possible to your campaign, you gain more insight into the performance in the target area. That way you can make bid adjustments to reduce the cost per conversion at locations that are too expensive.

We also have scripts for bidadjustments for audiences, devices or adschedule. Start automating all of your bidadjustments today.

Bid adjustments on CPA or ROAS

With the latest adjustments in this script, it is possible to calculate the bid adjustments based on CPA or ROAS. The CPA or ROAS of a location is compared to the CPA or ROAS of the corresponding campaign.

Settings

In the script you can only adjust the following things:

  • LOG: Specify whether the script should report the intermediate steps by adjusting the value to 'true'.
  • DATE_RANGE: The script looks at the statistics over this period.
  • MINIMUM_CONVERSIONS: Set a minimum number of conversions for a location. If a location has less conversions, we won't set a bidadjustment.
  • MINIMUM_CONVERSIONVALUE: Set a minimum conversionvalue for a location. If a location has less revenue, we won't set a bidadjustment.
  • MINIMUM_COST: Set a minimum amount of cost for a location. If a location has spend less, we won't set a bidadjustment.
  • MINIMUM_CLICKS: Set a minimum number of clicks for a location. If a location has less clicks, we won't set a bidadjustment.
  • KPI: Calculate the bidadjustment based on 'CPA' or 'ROAS'.
  • CAMPAIGN_LABEL: Select campaigns by using a label.
  • MAX_BID: The bidadjustment will not be higher than this. 1.3 = +30% or 2 = +100%.
  • MIN_BID: The bidadjustment will not be lower than this. 0.75 = -25% or 0.5 = -50%.

Scheduling: To generate sufficient data we advise to run the script once a day or once a week.

The script
// Copyright 2021. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
// 
// Created: 20-07-2018
// Last update: 23-08-2021 - Arjan Schoorl fixed the campaignIterator
//
// ABOUT THE SCRIPT
// With this script we adjust the biddings for locations in 
// active campaigns.
//
////////////////////////////////////////////////////////////////////

var config = {

  LOG : true,
  DATE_RANGE : "LAST_30_DAYS",

  // Optional: Use minimum conversions and/or cost to select targetlocations
  // 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 = {};
  
  // Collecting all targetlocation data
  var report = AdsApp.report(
    "SELECT Id, CampaignName, CampaignId, BidModifier, Conversions, ConversionValue, Cost, Clicks " +
    "FROM CAMPAIGN_LOCATION_TARGET_REPORT " +
    "WHERE IsNegative = FALSE " +
    "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;
      
    }
    
    // Skip campaign when not set to manual bidding. And check label.
    var campaignIterator = AdsApp.campaigns().withIds([row["CampaignId"]]).get();
    while(campaignIterator.hasNext()) { 
      var campaign = campaignIterator.next();       
      
      if(campaign.bidding().getStrategyType() != "MANUAL_CPC" && campaign.bidding().getStrategyType() != "MANUAL_CPM" && campaign.bidding().getStrategyType() != "MANUAL_CPV") continue;
      if(config.CAMPAIGN_LABEL != ""){ while(!campaign.labels().withCondition("Name = '" + config.CAMPAIGN_LABEL + "'").get().hasNext()) continue; }
      
      var campaignKpi = parseFloat(getCampaignKpi(campaign.getId())).toFixed(2);
      var oldBid = parseFloat(1 + (row["BidModifier"].split("%")[0] / 100)).toFixed(2);
    }
    
    // Calculate KPI's and new bid
    if(config.KPI == "CPA"){ var locationKpi = parseFloat(row["Cost"] / row["Conversions"]).toFixed(2); var newBid = parseFloat(campaignKpi / locationKpi).toFixed(2); }
    if(config.KPI == "ROAS"){ var locationKpi = parseFloat(row["ConversionValue"] / row["Cost"]).toFixed(2); var newBid = parseFloat(locationKpi / campaignKpi).toFixed(2); }
    
    // If there is a CPA or ROAS, we will set a bidadjustment
    if(isNaN(newBid) === false && isFinite(locationKpi) === 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["Id"], row["CampaignName"], newBid]);
      
          if(config.LOG === true){
            Logger.log("Location " + row["Id"] + " in " + row["CampaignName"]);
            Logger.log("Current bidadjustment: " + row["BidModifier"] + " = " + oldBid);
            Logger.log("Location " + config.KPI + ": " + locationKpi + ", Campain " + config.KPI + ": " + campaignKpi + ", New bid: " + newBid);
            Logger.log("------------");
          }   
      }

    } // Check bid
    
  } // rowIterator
  
  if(config.LOG === true){
    Logger.log(" ");
    Logger.log("...Loop through locations");
    Logger.log(" ");
  }
  
  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 locationIterator = campaign
        .targeting()
        .targetedLocations()
        .withIds([ids])
        .get();

      while(locationIterator.hasNext()){
        var location = locationIterator.next();
        
        location.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]);
        
      } // locationIterator
      
    } // campaignIterator
    
  } // keyIterator
  
} // function main()

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

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 is all about sharing knowledge. In the current market, PPC specialists like to keep their knowledge and experience to themselves. We're convinced that sharing knowledge can ensure that everyone gets better at their work. We want to change this by sharing our knowledge about scripts with everyone.

Do you also want to contribute? We are open to new ideas and feedback on everything you find on Adsscripts.com.

Contact us

Training &
Workshop
Contact us!
Adsscripts Training & Workshop