How to Build AI-Powered Ad Performance Predictions
Use historical data to predict which ads will perform before spending money.
Jay Banlasan
The AI Systems Guy
You can use AI to predict ad performance before launch by feeding historical patterns into a scoring model. I run every new ad concept through this before spending a dollar. It does not guarantee winners, but it filters out likely losers and saves real money.
The model works because ad performance is not random. Certain headline types, copy lengths, and visual styles consistently outperform others for a given audience.
What You Need Before Starting
- At least 50 historical ads with performance data
- Python 3.8+ with
anthropicandsqlite3 - A structured dataset of ad attributes and outcomes
- Clear success metrics (CTR, CPA, ROAS)
Step 1: Structure Your Historical Data
import sqlite3
def build_training_data(db_path):
conn = sqlite3.connect(db_path)
rows = conn.execute("""
SELECT ad_name,
SUM(spend) as spend, SUM(impressions) as impressions,
SUM(clicks) as clicks, SUM(leads) as leads,
CASE WHEN SUM(clicks) > 0 THEN SUM(clicks)*100.0/SUM(impressions) ELSE 0 END as ctr,
CASE WHEN SUM(leads) > 0 THEN SUM(spend)/SUM(leads) ELSE 999 END as cpa
FROM ad_daily
WHERE spend > 20
GROUP BY ad_name
HAVING SUM(impressions) > 500
""").fetchall()
conn.close()
ads = []
for r in rows:
ads.append({
"name": r[0], "spend": r[1], "impressions": r[2],
"clicks": r[3], "leads": r[4], "ctr": round(r[5], 2), "cpa": round(r[6], 2),
"winner": r[5] > 1.5 and r[6] < 30,
})
return ads
Step 2: Extract Ad Attributes
Parse your naming convention to get structured attributes:
def parse_ad_attributes(ad_name):
parts = [p.strip() for p in ad_name.split("|")]
attributes = {
"number": parts[0] if len(parts) > 0 else "",
"format": parts[1] if len(parts) > 1 else "",
"copy_framework": parts[2] if len(parts) > 2 else "",
"angle": parts[3] if len(parts) > 3 else "",
}
return attributes
Step 3: Build the Prediction Prompt
import anthropic
import json
def predict_performance(new_ad, historical_ads):
client = anthropic.Anthropic()
winners = [a for a in historical_ads if a["winner"]][:15]
losers = [a for a in historical_ads if not a["winner"]][:10]
winners_text = "\n".join([f"- {a['name']}: CTR {a['ctr']}%, CPA ${a['cpa']}" for a in winners])
losers_text = "\n".join([f"- {a['name']}: CTR {a['ctr']}%, CPA ${a['cpa']}" for a in losers])
prompt = f"""Predict the performance of a new ad based on historical patterns.
WINNING ADS (CTR > 1.5%, CPA < $30):
{winners_text}
LOSING ADS:
{losers_text}
NEW AD TO EVALUATE:
Name: {new_ad['name']}
Format: {new_ad.get('format', 'unknown')}
Copy Framework: {new_ad.get('copy_framework', 'unknown')}
Angle: {new_ad.get('angle', 'unknown')}
Headline: {new_ad.get('headline', 'N/A')}
Primary text preview: {new_ad.get('primary_text', 'N/A')[:200]}
Analyze patterns in winners vs losers. Then predict:
1. Predicted CTR range (low-high)
2. Predicted CPA range (low-high)
3. Confidence level (high/medium/low)
4. Key risk factors
5. Suggested improvements
Return JSON: {{"predicted_ctr_low": N, "predicted_ctr_high": N, "predicted_cpa_low": N, "predicted_cpa_high": N, "confidence": "...", "risks": [...], "improvements": [...]}}"""
resp = client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=800,
messages=[{"role": "user", "content": prompt}]
)
return json.loads(resp.content[0].text)
Step 4: Score and Rank New Concepts
def score_batch(new_ads, historical_ads):
results = []
for ad in new_ads:
prediction = predict_performance(ad, historical_ads)
prediction["ad_name"] = ad["name"]
# Simple composite score
avg_ctr = (prediction["predicted_ctr_low"] + prediction["predicted_ctr_high"]) / 2
avg_cpa = (prediction["predicted_cpa_low"] + prediction["predicted_cpa_high"]) / 2
confidence_mult = {"high": 1.0, "medium": 0.7, "low": 0.4}.get(prediction["confidence"], 0.5)
prediction["composite_score"] = round((avg_ctr * 10 - avg_cpa * 0.1) * confidence_mult, 2)
results.append(prediction)
results.sort(key=lambda x: x["composite_score"], reverse=True)
return results
Step 5: Compare Predictions to Actuals
After running ads, compare predictions to reality:
def evaluate_predictions(predictions, actuals):
for pred in predictions:
actual = actuals.get(pred["ad_name"])
if not actual:
continue
ctr_accurate = pred["predicted_ctr_low"] <= actual["ctr"] <= pred["predicted_ctr_high"]
cpa_accurate = pred["predicted_cpa_low"] <= actual["cpa"] <= pred["predicted_cpa_high"]
print(f"{pred['ad_name']}:")
print(f" CTR: Predicted {pred['predicted_ctr_low']}-{pred['predicted_ctr_high']}%, Actual {actual['ctr']}% {'CORRECT' if ctr_accurate else 'MISSED'}")
print(f" CPA: Predicted ${pred['predicted_cpa_low']}-${pred['predicted_cpa_high']}, Actual ${actual['cpa']} {'CORRECT' if cpa_accurate else 'MISSED'}")
The system gets better over time as you feed it more data. After 100+ ads, the predictions become surprisingly accurate for filtering out clear losers.
What to Build Next
Add image analysis to the prediction model by describing the visual treatment. Then build a recommendation engine that suggests which proven elements to combine for the next batch of ads.
Related Reading
- AI for Creative Strategy and Testing - creative strategy fundamentals
- The Data Flywheel Explained - how data improves predictions over time
- The Measurement Framework That Actually Works - measuring what matters
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