How to Automate Ad Spend Pacing and Budget Allocation
Automatically adjust daily budgets to hit monthly spend targets evenly.
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
- Meta Ads API access with
ads_managementpermission - Monthly budget targets per campaign
- Python 3.8+ with
requestsinstalled - At least 7 days of historical spend data
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
- Why Monitoring Is Not Optional - the foundation of automated monitoring
- The Automation Decision Tree - choosing what to automate
- Identifying Your Biggest Bottleneck - finding the biggest operational pain point
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