Bidadjustments for adschedules

Automatically make bidadjustments on the blocks in your adschedule per campaign. Based on CPA.

Start Now!
Bidadjustments for adschedules
Bidadjustments Get started!

If you think bidadjustments for every hour of each day is too much, this script offers the solution. Like the bidadjustment scripts on devices, locations and audiences, it automatically sets bidadjustments for all blocks in a campaign's adschedule. You can arrange this adschedule per campaign as desired.

In the script you can choose to include search, display and shopping campaigns in the bidadjustments. Display campaigns can only be included if search campaigns are also enabled. Filter blocks in the adscheduling of campaigns by specifying a minimum number of conversions and / or minimum expenses in the script. You determine the maximum and minimum bidadjustment yourself. In addition, the script only looks at campaigns with a manual bidstrategy, because bidadjustments at Smart Bidding are already done automatically.

Settings

  • LOG: Specify whether the script should report the intermediate steps, by changing the value to 'true'.
  • DATE_RANGE: Specify how many days the script should look back to calculate the CPA.
  • INCLUDE_SEARCH: Select if you want to run the script on your search campaigns.
  • INCLUDE_DISPLAY: Select if you want to run the script on your display campaigns. INCLUDE_SEARCH must be set to true.
  • INCLUDE_SHOPPING: Select if you want to run the script on your shopping campaigns.
  • MINIMUM_CONVERSIONS: Optional, select only blocks in your adschedule that have achieved at least this number of conversions.
  • MINIMUM_COST: Optional, select onlyblocks in your adschedule that have incurred at least these costs.
  • MINIMUM_CLICKS: Optional, select only blocks in your adschedule with this number of clicks.
  • MAX_BID: The bidadjustment will not exceed this set bid. 1.3 = + 30% or 2 = + 100%.
  • MIN_BID: The bidadjustment will not be lower than this set bid. 0.75 = -25% or 0.5 = -50%.
  • LABEL: Don't want to apply this script to all campaigns? Then label the campaigns.
The script
// Copyright 2020. Increase BV. All Rights Reserved.
//
// Created By: Tibbe van Asten
// for Increase B.V.
// 
// Created: 19-03-2020
// Last update: 19-03-2020
//
// ABOUT THE SCRIPT
// With this script we adjust the biddings for blocks in your adschedule 
// for all active campaigns in the account. Change will be based
// on the device statistics vs campaign statistics.
//
////////////////////////////////////////////////////////////////////

var config = {
  
    LOG : true,
    DATE_RANGE : last_n_days(60),

    // Set these to true if you want to include them.
    INCLUDE_SEARCH : true,		
    INCLUDE_DISPLAY : false,
    INCLUDE_SHOPPING : true,	
  
    // Optional: Use minimum conversions and/or cost to select devices.
    // Cost is in account currency. Leave empty to skip.
    MINIMUM_CONVERIONS : "2",
    MINIMUM_CLICKS : "100",
    MINIMUM_COST : "10",
  
    // 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.1,
    MIN_BID : 0.8,
	
    // Optional: Use a label to make a selection of campaigns.
    // Leave empty to skip.
    LABEL : ""

}

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

function main() {  

  // Starting with selecting Search campaigns, if included in the config.
  if(config.INCLUDE_SEARCH === true) {
    if(config.LABEL != ""){
      if(config.INCLUDE_DISPLAY === true) {      
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
        .get();
      } else {
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
        .withCondition("AdvertisingChannelType != DISPLAY") 
        .get();        
      }
    } else{
      if(config.INCLUDE_DISPLAY === true) {  
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .get();
      } else {
        var campaignIterator = AdsApp
        .campaigns()
        .withCondition("Status = ENABLED")
        .withCondition("AdvertisingChannelType != DISPLAY") 
        .get();
      }
    }

    // With campaigns selected, the bidadjustments will be set.
    bidAdjustments(campaignIterator);
  }

  // Select Shopping campaigns, if included in the config.
  if(config.INCLUDE_SHOPPING === true) {
    if(config.LABEL != ""){
      var campaignIterator = AdsApp
      .shoppingCampaigns()
      .withCondition("Status = ENABLED")
      .withCondition("LabelNames CONTAINS_ANY ['" + config.LABEL + "']")
      .get();
    } else{
      var campaignIterator = AdsApp
      .shoppingCampaigns()
      .withCondition("Status = ENABLED")
      .get();
    }

    // With campaigns selected, the bidadjustments will be set.
    bidAdjustments(campaignIterator);
  }    
  
  Logger.log("Thanks for using this custom script by Tibbe van Asten. Winning!")
  
} // function main()

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

function bidAdjustments(iterator) {
  
  var from = config.DATE_RANGE[0];
  var to = config.DATE_RANGE[1];
  
  while(iterator.hasNext()){
    var campaign = iterator.next();
    
    // We only want to alter the campaigns and adgroups with manual bidding strategies.
    if(campaign.bidding().getStrategyType() == "MANUAL_CPC" || campaign.bidding().getStrategyType() == "MANUAL_CPM" || campaign.bidding().getStrategyType() == "MANUAL_CPV"){
      
      var campaignCpa = campaign.getStatsFor(from, to).getCost() / campaign.getStatsFor(from, to).getConversions();

      	if(config.LOG === true){
          Logger.log("Checking " + campaign.getName());
          Logger.log("-----");
        }
      
      // Within the campaign, we will check for devices.
      var scheduleIterator = campaign
        .targeting()
        .adSchedules()
        .get();
      
      while(scheduleIterator.hasNext()){
        var schedule = scheduleIterator.next();
        
        var scheduleConversions = schedule.getStatsFor(from, to).getConversions();
        var scheduleCost = schedule.getStatsFor(from, to).getCost();
        var scheduleClicks = schedule.getStatsFor(from, to).getClicks();
        
          if(config.LOG === true){
            Logger.log("Schedule: " + schedule.getDayOfWeek() + " " + schedule.getStartHour() + "-" + schedule.getEndHour());
            Logger.log("Conversions: " + scheduleConversions);
            Logger.log("Cost: " + scheduleCost);
            Logger.log("Clicks: " + scheduleClicks);
          }
        
        // Only change devices that have the required conversions, cost or clicks
        if(config.MINIMUM_CONVERIONS != "" || config.MINIMUM_COST != "" || config.MINIMUM_CLICKS != ""){
          
          if(scheduleConversions < config.MINIMUM_CONVERIONS && scheduleCost < config.MINIMUM_COST && scheduleClicks < config.MINIMUM_CLICKS) continue;
          
        } // check minimums        
        
        var scheduleCpa = scheduleCost / scheduleConversions;
        var newScheduleBid = campaignCpa / scheduleCpa;

        // If there is an audience CPA, we will place a bidadjustment.
        if(isNaN(newScheduleBid) === false && isFinite(scheduleCpa) === true && schedule.getBidModifier() !== newScheduleBid) {
          if(newScheduleBid < config.MIN_BID) { newScheduleBid = config.MIN_BID; }
          if(newScheduleBid > config.MAX_BID) { newScheduleBid = config.MAX_BID; }

          schedule.setBidModifier(newScheduleBid);  

          if(config.LOG === true){
            Logger.log("Bidadjustment " + schedule.getDayOfWeek() + " " + schedule.getStartHour() + "-" + schedule.getEndHour() + " changed to " + schedule.getBidModifier());
          }
        }        

        // Negative bidadjustment when conversions = 0.
        if(scheduleConversions == 0 && scheduleCost > config.MINIMUM_COST && isNaN(newScheduleBid) == false){
          schedule.setBidModifier(config.MIN_BID);

          if(config.LOG === true){
            Logger.log("Bidadjustment " + schedule.getDayOfWeek() + " " + schedule.getStartHour() + "-" + schedule.getEndHour() + " changed to " + schedule.getBidModifier());
          }
        }
        
        if(config.LOG === true){
          Logger.log(" ");
        }

      } // scheduleIterator   
      
      if(config.LOG === true){
        Logger.log(" ");
      }
            
    } // manual bidding
    
  } // campaignIterator
  
} // function bidAdjustments()

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

function last_n_days(n) {
  
	var	from = new Date(), to = new Date();
	to.setUTCDate(from.getUTCDate() - n);
  
	return googleDateRange(from, to);
  
} // function last_n_days()

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

function googleDateRange(from, to) {
  
	function googleFormat(date) {
    
		var date_array = [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()];
    
		if (date_array[1] < 10) date_array[1] = '0' + date_array[1];
		if (date_array[2] < 10) date_array[2] = '0' + date_array[2];
    
		return date_array.join('');
	}
  
	var inverse = (from > to);
	from = googleFormat(from);
	to = googleFormat(to);
  
	var result = [from, to];
	
  if (inverse) {
		result = [to, from];
	}
  
	return result;
  
} // function googleDateRange()
Show whole script!
The Experts
Tibbe van Asten Head of PPC @ Increase
Nils Rooijmans Water Cooler Topics
Martijn Kraan Freelance PPC Specialist
Bas Baudoin Teamlead SEA @ Happy Leads
How about you? JOIN US!
Sharing Knowledge
Caring

Sharing Knowledge

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