Systems Library / Marketing Automation / How to Automate Ad Spend Pacing and Budget Allocation
Marketing Automation paid advertising

How to Automate Ad Spend Pacing and Budget Allocation

Automatically adjust daily budgets to hit monthly spend targets evenly.

Jay Banlasan

Jay Banlasan

The AI Systems Guy

When you automate ad spend budget allocation and pacing, you stop ending every month either underspent or scrambling to spend down. I use this system to keep every account on track. It checks daily spend against the monthly target and adjusts budgets so you land within 2% of your goal.

Manual pacing means someone has to check every account every day. Automated pacing does it before anyone wakes up.

What You Need Before Starting

Step 1: Define Monthly Targets

from datetime import datetime, timedelta
import calendar

MONTHLY_BUDGETS = {
    "JB | Lead | UK | Assessment": 600,
    "JB | Lead | US | Quiz": 1500,
    "JB | Purchase | US | Book": 900,
}

def get_pacing_info(campaign_name):
    monthly_budget = MONTHLY_BUDGETS.get(campaign_name, 0)
    today = datetime.now()
    days_in_month = calendar.monthrange(today.year, today.month)[1]
    days_remaining = days_in_month - today.day + 1
    
    return {
        "monthly_budget": monthly_budget,
        "days_in_month": days_in_month,
        "days_remaining": days_remaining,
        "ideal_daily": monthly_budget / days_in_month,
    }

Step 2: Calculate Current Spend and Pace

import sqlite3

def get_mtd_spend(db_path, campaign_name):
    conn = sqlite3.connect(db_path)
    today = datetime.now()
    month_start = today.replace(day=1).strftime("%Y-%m-%d")
    
    row = conn.execute("""
        SELECT COALESCE(SUM(spend), 0) 
        FROM ad_daily 
        WHERE campaign_name = ? AND date >= ?
    """, (campaign_name, month_start)).fetchone()
    conn.close()
    return row[0]

def calculate_pace(campaign_name, db_path):
    info = get_pacing_info(campaign_name)
    mtd_spend = get_mtd_spend(db_path, campaign_name)
    
    budget_remaining = info["monthly_budget"] - mtd_spend
    recommended_daily = budget_remaining / info["days_remaining"] if info["days_remaining"] > 0 else 0
    
    pace_pct = (mtd_spend / (info["ideal_daily"] * (info["days_in_month"] - info["days_remaining"]))) * 100 if info["ideal_daily"] > 0 else 0
    
    return {
        "campaign": campaign_name,
        "monthly_budget": info["monthly_budget"],
        "mtd_spend": mtd_spend,
        "budget_remaining": budget_remaining,
        "recommended_daily": round(recommended_daily, 2),
        "current_ideal_daily": round(info["ideal_daily"], 2),
        "pace_pct": round(pace_pct, 1),
    }

Step 3: Adjust Budgets via API

import requests
import os

def adjust_campaign_budget(campaign_id, new_daily_budget):
    token = os.getenv("META_ACCESS_TOKEN")
    
    # Get current budget
    resp = requests.get(f"https://graph.facebook.com/v19.0/{campaign_id}",
        params={"access_token": token, "fields": "daily_budget,name"})
    current = resp.json()
    current_budget = int(current.get("daily_budget", 0)) / 100
    
    # Only adjust if change is more than 10%
    change_pct = abs(new_daily_budget - current_budget) / current_budget * 100 if current_budget > 0 else 100
    if change_pct < 10:
        return {"action": "no_change", "reason": "Change too small"}
    
    # Cap adjustments at 20% per day to avoid performance disruption
    max_increase = current_budget * 1.2
    max_decrease = current_budget * 0.8
    adjusted = max(max_decrease, min(max_increase, new_daily_budget))
    
    resp = requests.post(f"https://graph.facebook.com/v19.0/{campaign_id}",
        params={"access_token": token, "daily_budget": int(adjusted * 100)})
    
    return {
        "action": "adjusted",
        "from": current_budget,
        "to": adjusted,
        "requested": new_daily_budget,
    }

Step 4: Build the Pacing Report

def pacing_report(db_path):
    report = []
    for campaign_name in MONTHLY_BUDGETS:
        pace = calculate_pace(campaign_name, db_path)
        status = "on_track"
        if pace["pace_pct"] > 110:
            status = "overpacing"
        elif pace["pace_pct"] < 90:
            status = "underpacing"
        
        pace["status"] = status
        report.append(pace)
        
        print(f"{campaign_name}: {status.upper()}")
        print(f"  MTD: ${pace['mtd_spend']:.2f} / ${pace['monthly_budget']:.2f}")
        print(f"  Recommended daily: ${pace['recommended_daily']:.2f}")
        print(f"  Pace: {pace['pace_pct']}%\n")
    
    return report

Step 5: Schedule Daily Pacing Adjustments

# Run at 6 AM before campaigns ramp up
0 6 * * * cd /path/to/project && python3 pace_budgets.py >> /var/log/pacing.log 2>&1

The system calculates where you should be, compares it to where you are, and adjusts. Campaigns that underspend get a budget bump. Campaigns that overspend get pulled back. You hit your monthly target every time.

What to Build Next

Add a Slack report that shows pacing status for every campaign each Monday morning. Then add end-of-month acceleration logic for campaigns that are significantly under-paced with 5 or fewer days left.

Related Reading

Want this system built for your business?

Get a free assessment. We will map every system your business needs and show you the ROI.

Get Your Free Assessment

Related Systems