[Adwords Script] Automate Audience bidding in Adwords

Audiences are a great way to improve performance on Google Ads by allowing you to target people who have previously been on your website, or people who are on your customer list.

This is the third article in our series on bid modifiers that will allow you to avoid having to do the tedious task of going through on a weekly or monthly basis, setting bid modifiers for each audience.

We love automating tedious tasks at Clicteq, so we wrote a script that automatically goes through and calculates then sets device-level bids, significantly reducing the time required to do this task, and removing any human error!

How the script works

This script makes bid adjustments based on the performance of the audience that a user is in, whether that be RLSA, a custom audience, or a ‘similar to’ audience. It will automatically calculate and increase bids if performance on a specific audience is better than average, or decrease bids if performance on that audience is below average.

It also works well in conjunction with this script that sets modifiers for in-market audiences.

The logic behind the script is as follows: it analyses your campaigns over a specified time period (e.g. a week, or a month), and then applies a formula to calculate the bids as follows:

Bid modifiers = (Campaign CPA / Audience CPA) – 1 * 100

When there are already modifiers in place, the script will take these into account when making its calculations.

By default, the script will iterate through campaigns one at a time and adjust bids at campaign level, before going through each ad group and then calculating the audience bids at that level.

To ensure statistical significance, within the control panel, you can specify a minimum number of conversions required per audience at campaign and ad group level for the script to work.

Finally, when setting up the script, we would suggest that you set it to run monthly if you have set the lookback window to ‘month’, or weekly if you’ve set the lookback window to ‘week’.

How to use the script

There are four variables to set in the user area at the top of the script:

  • Lookback window: This is the time period during which you want the script to analyse. It can be set to ‘week’ (the last 7 days) or ‘month’ (the last 30 days).
  • Conversions limit: This is the number of conversions required for the account-level modifiers to be applied, as opposed to using the campaign-level (default). E.g. if the conversions limit is set to ‘5’, any campaigns with fewer than 5 conversions will have the account-level modifiers applied.
  • Campaigns to include: This is where you define which campaigns you want to be included, which are specified in a list. E.g. [“campaign1”,”campaign2”]. If you want all campaigns to be included, leave the brackets empty, i.e. []
  • Campaigns to exclude: This is where you define the list of campaigns you want to be excluded, e.g. [“campaign3”,”campaign4”]. If you don’t want any campaigns to be excluded, leave the brackets empty, i.e. []
/**
*
* The script will iterate through audiences at campaign and ad group level  
* And set the bid modifer based on the following formula
* Bid modifier = Campaign CPA / Audience CPA - 1 * 100
*
* Version: 6.0
* maintained by Clicteq
*
**/

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

//Options


//Select look back window. Available values 'week' = last 7 days, 'month' = last 30 days.

USERLOOKBACK = 'month' 

//Select conversions limit. Each campaign with less conversions than conversions limit on device level will have the account-level modifiers applied.

CONVERSIONSLIMIT = 0

//Select which campaigns to include. Put [] to include all campaigns. Pass a list, for example, ["campaign1","campaign2"] to include certain campaigns.


INCLUDEDCAMPAIGNS = ['IT_Routes_^_IT_IT_^_Exact'] 

//Select which campaigns to exclude. Put [] not to exclude any campaigns. Pass a list, for example, ["campaign1","campaign2"] to exclude certain campaigns.

EXCLUDEDCAMPAIGNS = []

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//



function prepareInput()
{
  //Prepares input
  
  if(USERLOOKBACK == 'month')
  {
  TIMERANGE = 'LAST_30_DAYS'
  }
  
  else if(USERLOOKBACK = 'week')
  {
    TIMERANGE = 'LAST_7_DAYS'
  }
  
  
  for(var i = 0; i < INCLUDEDCAMPAIGNS.length; i ++)
  {
    INCLUDEDCAMPAIGNS[i] = "'" + INCLUDEDCAMPAIGNS[i] + "'"
    
  }
  
  for(var i = 0; i < EXCLUDEDCAMPAIGNS.length; i ++)
  {
    EXCLUDEDCAMPAIGNS[i] = "'" + EXCLUDEDCAMPAIGNS[i] + "'"
    
  }
  
  
  
  INCLUDEDPARSED = "[" + INCLUDEDCAMPAIGNS.toString() + "]"
  EXCLUDEDPARSED = "[" + EXCLUDEDCAMPAIGNS.toString() + "]"

  
}




function getReport(level)
{

  
  
  
if(level == 'detailag')
  {
var query = "SELECT CampaignName,AdGroupName,Id,Cost,Conversions,CriterionAttachmentLevel FROM AUDIENCE_PERFORMANCE_REPORT WHERE Cost > 0 AND Criteria DOES_NOT_CONTAIN 'uservertical::80'"
}
  
  else if (level == 'detailc')
  {var query = "SELECT CampaignName,Id,Cost,Conversions,CriterionAttachmentLevel FROM AUDIENCE_PERFORMANCE_REPORT WHERE Cost > 0 AND Criteria DOES_NOT_CONTAIN 'uservertical::80'"}
  
  else if (level == 'account')
  {
    var query = "SELECT Id,Cost,Conversions FROM AUDIENCE_PERFORMANCE_REPORT WHERE Cost > 0 AND Criteria DOES_NOT_CONTAIN 'uservertical::80' DURING "+TIMERANGE
    var report = AdWordsApp.report(query)
    return report.rows()
  }
  
  
  else if (level == 'accountTotal')
  {
    var query = "SELECT AccountDescriptiveName, Cost,Conversions FROM ACCOUNT_PERFORMANCE_REPORT WHERE Cost > 0 DURING "+TIMERANGE
    var report = AdWordsApp.report(query)
    return report.rows()
  }
  
  if(INCLUDEDCAMPAIGNS.length == 0 && EXCLUDEDCAMPAIGNS.length == 0)
  {
    query = query + " DURING " +TIMERANGE

    }
  else if (INCLUDEDCAMPAIGNS.length >0)
  {
    query = query + " AND CampaignName IN " + INCLUDEDPARSED +" DURING " +TIMERANGE
    }
  else if (EXCLUDEDCAMPAIGNS.length >0)
  {
    query = query + " AND CampaignName NOT IN " + EXCLUDEDPARSED +" DURING " +TIMERANGE
    }  


var report = AdWordsApp.report(query)
return report.rows()
   
  
}



function calcAccountCpa()
{
  
  var query = "SELECT AccountDescriptiveName, Cost,Conversions FROM ACCOUNT_PERFORMANCE_REPORT WHERE Cost > 0 DURING " +TIMERANGE
  var report = AdWordsApp.report(query)
  var rows = report.rows().next().formatForUpload()
  var conv = parseFloat(rows['Conv. (opt.)'].replace(",",""))
  var cost = parseFloat(rows['Cost'].replace(",",""))
  return cost/conv
}


function calcAccountLevelCpas()
{
  var rows = getReport('account')
  var accountLevelCpas = {}
  var audiences = {}
  
  
  
  while(rows.hasNext())
  {
    var row = rows.next().formatForUpload()
    
    var audience = row['Criterion ID']
    var cost = row['Cost']
    var cost = cost.replace(",","")
    var cost = parseFloat(cost)
    
    var conv = row['Conv. (opt.)']
    var conv = conv.replace(",","")
    var conv = parseFloat(conv)
    

    var existingAudiences = Object.keys(audiences)
    

    
    if(existingAudiences.indexOf(audience) == -1)
    {
      audiences[audience] = {}
      audiences[audience]['cost'] = cost
      audiences[audience]['conv'] = conv
    }
    
    else
      
    {
      audiences[audience]['cost'] = audiences[audience]['cost'] + cost
      audiences[audience]['conv'] = audiences[audience]['conv'] + conv
      
    }
    
    
    

  }
  
  
  var audienceCpas = {}
  
  for(var i = 0; i<existingAudiences.length; i++)
  {
    var singleAudience = existingAudiences[i]
    var singleCost = audiences[singleAudience]['cost']
    var singleConv = audiences[singleAudience]['conv']
    
      
    
    if(singleConv > CONVERSIONSLIMIT)
    {
    audienceCpas[singleAudience] = singleCost/singleConv
    }
    else
    {
  audienceCpas[singleAudience] = 'skip'
    }
  }

  return audienceCpas
}




function calcAccountLevelModifiers(accountCpa, audienceCpas)
{

  var audiences = Object.keys(audienceCpas)
  var accountModifiers = {}
  
  for(var i = 0; i < audiences.length; i++)
  {
    var audience = audiences[i]
   accountModifiers[audience] = calcModifier(accountCpa, audienceCpas[audience])  
  }
  
  return accountModifiers
  
}

function calcModifier(cpaHigher,cpaLower)
{
  //Calcuates a bid modifier
  cpaHigher = parseFloat(cpaHigher)
  cpaLower = parseFloat(cpaLower)
  return ((((cpaHigher/cpaLower) - 1) * 100).toFixed(0))/100
}

function calcLowerLevel(audienceCpas,accountModifiers)

{
  
  var accountLevel = {}
  var campaignLevel = {}
  var adGroupLevel = {}
  var rowsag = getReport('detailag')
  var rowsc = getReport('detailc')
  

  
  
  while (rowsag.hasNext())
  {
    
    var row = rowsag.next().formatForUpload()
    var adGroup = row['Ad group']
    var audience = row['Criterion ID']
    
    var cost = row['Cost']
    var cost = cost.replace(",","")
    var cost = parseFloat(cost)
    
    var conv = row['Conv. (opt.)']
    var conv = conv.replace(",","")
    var conv = parseFloat(conv)
    
    var level = row['Level']
    var cpa = cost/conv
    
    
    

    var existingsAgs = Object.keys(adGroupLevel)
    var audienceAccountCpa = audienceCpas[audience]
    
    
      
     if(level == 'Ad group')
      {

      if(existingsAgs.indexOf(adGroup) == -1)
      {
          adGroupLevel[adGroup] = {}
          adGroupLevel[adGroup][audience] = {}
      }

      else

      {	adGroupLevel[adGroup][audience] = {}
 


      }
       

      if(conv > CONVERSIONSLIMIT)
      {        

        adGroupLevel[adGroup][audience]['cpa'] = cpa
        adGroupLevel[adGroup][audience]['conv'] = conv
        
      }
      else
      {

        adGroupLevel[adGroup][audience]['modifier'] = 'acc'
        adGroupLevel[adGroup][audience]['conv'] = conv
      }

      
            
      }
    
  }
  
    while (rowsc.hasNext())
  {
        var row = rowsc.next().formatForUpload()
    var campaign = row['Campaign']
    var audience = row['Criterion ID']
    
    var cost = row['Cost']
    var cost = cost.replace(",","")
    var cost = parseFloat(cost)
    
    var conv = row['Conv. (opt.)']
    var conv = conv.replace(",","")
    var conv = parseFloat(conv)
    
    var level = row['Level']
    var cpa = cost/conv
    
    var existingCampaigns = Object.keys(campaignLevel)
    var audienceAccountCpa = audienceCpas[audience]
    
    if(level == 'Campaign')
    {
    
      if(existingCampaigns.indexOf(campaign) == -1)
      {
          campaignLevel[campaign] = {}
          campaignLevel[campaign][audience] = {}
          
          
      }

      else

      {	
        campaignLevel[campaign][audience] = {}   
      }
      
      
      if(conv > CONVERSIONSLIMIT)
      {
        campaignLevel[campaign][audience]['cpa'] = cpa
        campaignLevel[campaign][audience]['conv'] = conv
      }
      else
      {
        campaignLevel[campaign][audience]['modifier'] = 'acc'
        campaignLevel[campaign][audience]['conv'] = conv
      }
      }
  }


  

  return [campaignLevel,adGroupLevel]
  
  
}

function getCampaigns(campaignTypes)
{
  //Gets a campaign iterator using the conditions from user input
  
  
  
  if(campaignTypes == 'normal')
  {
  
      if(INCLUDEDCAMPAIGNS.length == 0 && EXCLUDEDCAMPAIGNS.length == 0)
    {
    var campaigns = AdWordsApp.campaigns().get()
    }
    else if (INCLUDEDCAMPAIGNS.length >0)
    {
      var campaigns = AdWordsApp.campaigns().withCondition("CampaignName IN "+ INCLUDEDPARSED).get()
      
    }
    else if (EXCLUDEDCAMPAIGNS.length >0)
    {
      var campaigns = AdWordsApp.campaigns().withCondition("CampaignName NOT_IN "+ EXCLUDEDPARSED).get()
    }
  }
  
  else if(campaignTypes == 'shopping')
  {
    if(INCLUDEDCAMPAIGNS.length == 0 && EXCLUDEDCAMPAIGNS.length == 0)
    {
    var campaigns = AdWordsApp.shoppingCampaigns().get()
    }
    else if (INCLUDEDCAMPAIGNS.length >0)
    {
      var campaigns = AdWordsApp.shoppingCampaigns().withCondition("CampaignName IN "+ INCLUDEDPARSED).get()
      
    }
    else if (EXCLUDEDCAMPAIGNS.length >0)
    {
      var campaigns = AdWordsApp.shoppingCampaigns().withCondition("CampaignName NOT_IN "+ EXCLUDEDPARSED).get()
    }
    
    
  }
  
  return campaigns
}

function setBids(entities,accountModifiers, lowerModifiers)
{

  
  
  
while(entities.hasNext())
{
  var entity = entities.next()
  var audiences = entity.targeting().audiences().get()
  var name = entity.getName()
  var cpa = (entity.getStatsFor(TIMERANGE).getCost()/entity.getStatsFor(TIMERANGE).getConversions())
  
  Logger.log("----------------------------------------")
  Logger.log(name)
  Logger.log("----------------------------------------")
  

  while(audiences.hasNext())
  {
    
    var audience = audiences.next()
    var currentBid = audience.bidding().getBidModifier()
    var id = audience.getId()
  try
    {var conv = lowerModifiers[name][id]['conv']}
    catch(e)
    {

      continue
    }
    
    var audienceName = audience.getName()
    
    
    if(conv > CONVERSIONSLIMIT)
    {
      var audienceCpa = parseFloat(lowerModifiers[name][id]['cpa'])
      var modifier = calcModifier(cpa,audienceCpa)

    }
    
    else
    {
      var modifier = accountModifiers[id]
    }
    
    if(isNaN(modifier))
    {continue}
    
  var newBid = parseFloat(currentBid) + parseFloat(modifier)
    newBid = newBid.toFixed(2)
    

    
    if(newBid < 0)
    {
      newBid = 0
    }
    
    Logger.log(audienceName + "  |  " + "New modifier: "  + newBid + "  Old modifier: "  + currentBid)
    audience.bidding().setBidModifier(parseFloat(newBid))
    
    
  }
  
  Logger.log("----------------------------------------")
  
}

}


function main()

{
  
  prepareInput()
  
  var accountCpa = calcAccountCpa()
  var audienceCpas = calcAccountLevelCpas()
  var accountModifiers = calcAccountLevelModifiers(accountCpa, audienceCpas)
  var lowerModifiers = calcLowerLevel(audienceCpas, accountModifiers)
  var campaigns = getCampaigns('normal')
  
  setBids(campaigns,accountModifiers, lowerModifiers[0])

  var campaigns = getCampaigns('normal')
  
  while(campaigns.hasNext())
  {
   var entities = campaigns.next().adGroups().get()
  setBids(entities,accountModifiers, lowerModifiers[1])
  }
  
  var campaigns = getCampaigns('shopping')
  
  setBids(campaigns,accountModifiers, lowerModifiers[0])
  
  var campaigns = getCampaigns('shopping')
  
  while(campaigns.hasNext())
  {
   var entities = campaigns.next().adGroups().get()
  setBids(entities,accountModifiers, lowerModifiers[1])
  }
}

This script is provided without guarantee.

wesley parker
About wesley parker

Wesley is Founder and CEO at Clicteq. He currently manages a £6 Mil Adwords portfolio across a range of different sectors. He regulally features in leading search publications such as Search Engine Journal, Econsultancy and Certified Knowledge. You can follow him on Twitter or connect with him on Linkedin

Leave a Reply

Your email address will not be published. Required fields are marked *