Multi-Period Budget Optimization - User Guide

Overview

Multi-period budget optimization extends AMMM’s budget allocation capabilities to plan across multiple time periods (e.g., 13 weeks, 6 months) rather than optimizing for a single aggregated period. This enables:

  • Time-varying budget allocation - Allocate different budgets to different periods based on expected effectiveness

  • Seasonality integration - Automatically adjust for seasonal variations in channel effectiveness

  • Realistic planning - Align with business planning cycles (quarterly, annual)

  • Constraint flexibility - Set per-period budget limits while optimizing total allocation

When to Use Multi-Period Optimization

Use multi-period optimization when:

  • You need to plan budgets for multiple weeks/months ahead

  • Your business has significant seasonality (e.g., retail, travel)

  • You want to optimize across a planning horizon (e.g., Q1, full year)

  • You need to respect time-varying budget constraints

Use single-period optimization when:

  • You’re optimizing for a single aggregated time period

  • You want quick “what-if” scenarios with fixed total budgets

  • Seasonality is not a major concern

Quick Start

Basic Multi-Period Optimization (13 weeks)

import src as ammm
from src.driver import MMMBaseDriverV2

# Fit model (standard pipeline)
driver = MMMBaseDriverV2('config.yml', 'data.csv', 'holidays.xlsx', 'results')
driver.main()

# Run 13-week multi-period optimization
ammm.optimize_marketing_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir=driver.results_dir,
    n_time_periods=13,           # 13 weeks
    multiperiod_mode=True,       # Enable multi-period
    use_seasonality=True,        # Apply seasonal adjustments
    frequency='W'                # Weekly planning
)

Output:

  • CSV file: results/multiperiod_optimization_results.csv

  • Console summary with total budget, contribution, and ROI

Usage Examples

Example 1: Quarterly Planning (13 weeks)

ammm.optimize_marketing_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir='results',
    n_time_periods=13,
    multiperiod_mode=True,
    use_seasonality=True,
    frequency='W',
    start_date='2025-01-06'     # Q1 start
)

Example 2: Custom Budget with Seasonality

ammm.optimize_marketing_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir='results',
    n_time_periods=13,
    total_budget=12_500_000,    # £12.5M for Q1
    multiperiod_mode=True,
    use_seasonality=True,
    frequency='W',
    start_date='2025-01-06'
)

Example 3: Monthly Planning (12 months)

ammm.optimize_marketing_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir='results',
    n_time_periods=12,
    multiperiod_mode=True,
    use_seasonality=True,
    frequency='M',              # Monthly frequency
    start_date='2025-01-01'
)

Example 4: With Per-Period Budget Constraints

# Direct call to multi-period function for advanced options
from src.driver.opt import optimize_multiperiod_budget

results_df = optimize_multiperiod_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir='results',
    n_periods=13,
    total_budget=12_500_000,
    period_budget_limits=(800_000, 1_200_000),  # £800K-£1.2M per week
    use_seasonality=True,
    frequency='W',
    start_date='2025-01-06'
)

Parameters Reference

Main Function: optimize_marketing_budget()

Parameter

Type

Default

Description

model

BaseMMM

Required

Fitted MMM model

data

DataFrame

Required

Historical data

config

Dict

Required

Configuration dictionary

results_dir

str

Required

Output directory

n_time_periods

int

1

Number of periods to optimize

multiperiod_mode

bool

False

Enable multi-period optimization

use_seasonality

bool

False

Apply seasonal adjustments

frequency

str

‘M’

Time frequency (‘W’, ‘M’, ‘D’)

start_date

str

None

Planning start date (YYYY-MM-DD)

total_budget

float

None

Total budget (auto-calculated if None)

Advanced Function: optimize_multiperiod_budget()

Additional parameters for fine-grained control:

Parameter

Type

Default

Description

period_budget_limits

Tuple[float, float]

None

(min, max) budget per period

lower_bound_pct

float

0.20

% below mean for channel lower bounds

upper_bound_pct

float

0.20

% above mean for channel upper bounds

Output Format

CSV File: multiperiod_optimization_results.csv

Column

Description

period

Period index (0-indexed)

period_date

Actual date for period start

channel

Channel name (or ‘TOTAL’)

budget

Allocated budget for this period/channel

contribution

Expected contribution (with seasonal adjustment)

seasonal_multiplier

Seasonal effectiveness multiplier (1.0 = baseline)

roi

Return on investment for this period/channel

Example Output

period,period_date,channel,budget,contribution,seasonal_multiplier,roi
0,2025-01-06,google,12500,60000,0.92,4.8
0,2025-01-06,facebook,28000,420000,0.88,15.0
0,2025-01-06,tv,95000,550000,1.15,5.8
0,2025-01-06,TOTAL,965000,1306000,0.98,1.35
1,2025-01-13,google,13200,63800,0.94,4.8
...

Console Summary

Multi-Period Optimization Summary (13 periods)
============================================================
Total Budget: 12,500,000.00
Total Contribution: 95,750,000.00
Overall ROI: 7.66×

Prophet Seasonality Integration

How It Works

Multi-period optimization uses Prophet’s seasonal decomposition from your fitted MMM model to adjust channel effectiveness over time.

Seasonal Components Used:

  • Yearly seasonality - Annual patterns (e.g., holiday peaks, summer lulls)

  • Weekly seasonality - Day-of-week patterns (e.g., weekend vs weekday)

  • Both combined - Additive effect of yearly + weekly

Example:

  • Week of December 15 (pre-Christmas Friday):

    • Yearly: +0.5 (holiday shopping season)

    • Weekly: +0.2 (Friday/weekend effect)

    • Combined: +0.7

    • Multiplier: ~1.07 (7% effectiveness boost)

Configuring Prophet Seasonality in Your Model

Prophet seasonality is configured when fitting your MMM model. Check your model config:

# In your config.yml
prophet:
  yearly_seasonality: true    # Enable yearly patterns
  weekly_seasonality: true    # Enable weekly patterns
  daily_seasonality: false    # Usually false for weekly/monthly data
  
  # Optional: Custom seasonality
  seasonalities:
    - name: 'monthly'
      period: 30.5
      fourier_order: 5

Interpreting Seasonal Multipliers

  • 1.0 - Baseline effectiveness (no seasonal adjustment)

  • >1.0 - Channel more effective (e.g., 1.08 = 8% boost)

  • <1.0 - Channel less effective (e.g., 0.92 = 8% reduction)

Example: If Google Ads normally generates £5 contribution per £1 spend:

  • Week with multiplier 1.08: £5.40 contribution per £1 spend

  • Week with multiplier 0.92: £4.60 contribution per £1 spend

Checking Seasonal Patterns

Inspect what seasonality your model has:

# After model fitting
prophet_model = driver.model.prophet_model
forecast = prophet_model.predict(prophet_model.history)

print(forecast.columns)
# Output: ['ds', 'trend', 'yhat', 'yearly', 'weekly', 'holidays', ...]

# Plot seasonal components
prophet_model.plot_components(forecast)

Holiday Event Planning & Marketing Theory

Overview

Multi-period optimization with seasonality is particularly powerful for planning around major retail events like Black Friday, Christmas, Prime Day, or other promotional periods. This section explains how AMMM handles holiday events and provides guidance based on marketing science research.

How Holidays Are Captured

Holiday effects appear in Prophet’s holidays component when:

  1. Holidays file is configured - Your holidays.xlsx contains the event dates

  2. Events fall within planning window - The holiday date is within your optimization horizon

  3. Historical data shows patterns - Prophet learns the effect from your training data

Example:

Planning window: 13 weeks starting Oct 17, 2025
Black Friday: Nov 28, 2025 (6 weeks ahead)
Result: Black Friday IS captured (within window)

Planning window: 13 weeks starting Oct 17, 2025  
Christmas: Dec 25, 2025 (10 weeks ahead)
Result: Christmas IS captured (within window)

Planning window: 13 weeks starting Oct 17, 2025
Easter 2026: April 20, 2026 (26 weeks ahead)
Result: Easter NOT captured (beyond window) ⚠️

To include more holidays: Extend your planning window

# 26 weeks captures Q4 holidays + Q1
n_time_periods=26  

Configuring Holidays

Add events to your holidays.xlsx file:

ds,holiday,country,year
2024-11-29,Black_Friday,US,2024
2025-11-28,Black_Friday,US,2025
2026-11-27,Black_Friday,US,2026
2024-12-25,Christmas,US,2024
2025-12-25,Christmas,US,2025

Prophet will learn the average effect of each holiday from your historical data and project it forward.

Checking Holiday Effects

Verify Prophet has captured your holidays:

# After model fitting
prophet_model = driver.model.prophet_model
forecast = prophet_model.predict(prophet_model.history)

# Check if holidays component exists and has variation
if 'holidays' in forecast.columns:
    print(f"Holiday effect range: {forecast['holidays'].min():.2f} to {forecast['holidays'].max():.2f}")
    
    # Show which dates have holiday effects
    holiday_dates = forecast[forecast['holidays'] != 0][['ds', 'holidays']]
    print(holiday_dates)

The Black Friday Planning Problem: Buildup vs Event Allocation

The Strategic Question: Should you allocate more budget to the weeks before Black Friday or during Black Friday week itself?

The Short Answer: Marketing science suggests weighted toward the buildup (2-3 weeks before), but the optimal split depends on your brand. Let your MMM discover the answer empirically.

Marketing Theory & Research

1. The Carryover Effect (Adstock)

Principle: Advertising has lagged effects. Spend in weeks 1-3 before Black Friday continues to influence purchases during the event.

Research:

  • Naik, Mantrala & Sawyer (1998) - “Planning Media Schedules in the Presence of Dynamic Advertising Quality”

    • Pulsing strategies (concentrated bursts before events) outperform continuous spending

    • Pre-event concentration builds awareness that converts during the event

  • Sethuraman, Tellis & Briesch (2011) - Meta-analysis of advertising elasticity

    • Advertising effects peak 0-1 weeks after exposure but persist for several weeks

    • Carryover effects mean early spending has compounding impact

Implication for AMMM: If your MMM shows strong carryover parameters (high adstock), the optimization will naturally shift budget earlier to capture these compounding effects.

2. The Customer Journey

Consumer behaviour research shows:

3-4 weeks before Black Friday:

  • Research phase - Consumers browse deals, create wish lists

  • Ad effectiveness: Brand awareness advertising has high ROI

  • Channel focus: Upper-funnel (display, video, social brand)

1-2 weeks before:

  • Consideration phase - Comparing options, checking reviews

  • Ad effectiveness: Persuasion advertising critical

  • Channel focus: Mid-funnel (search, comparison shopping, retargeting)

Black Friday week:

  • Conversion phase - Executing planned purchases

  • Ad effectiveness: Reminder advertising, but decisions largely made

  • Channel focus: Lower-funnel (branded search, remarketing)

Research:

  • Google/Ipsos (2019) - “Black Friday Shopping behaviour”

    • 72% of Black Friday shoppers research 2+ weeks in advance

    • 54% create shopping lists 1-2 weeks before

    • Only 28% make spontaneous same-day decisions

Implication: The majority of purchasing decisions are made before Black Friday, suggesting buildup spending is critical.

3. Competitive Saturation

During Black Friday itself:

  • Extreme competition for attention (every brand advertising heavily)

  • Diminishing returns kick in faster due to saturation

  • Cost inflation (higher CPMs/CPCs during peak periods)

Research:

  • Bruce, Foutz & Kolsarici (2012) - “Dynamic Effectiveness of Advertising and Word of Mouth in Sequential Distribution of Short Lifecycle Products”, Journal of Marketing Research

    • Advertising effectiveness varies significantly based on competitive intensity

    • Early timing in product lifecycle (or event window) can establish stronger market position

    • Word-of-mouth effects amplify early advertising investments

Implication: Your advertising may face less competition and achieve stronger effectiveness in the buildup weeks compared to the highly saturated Black Friday period itself.

Typical Optimal Allocation Pattern

Based on marketing literature and industry practice:

Week -4 to -3:  15-20% of total budget (awareness building)
Week -2 to -1:  30-35% of total budget (peak consideration)  
Week 0 (BF):    25-30% of total budget (conversion + share of voice)
Week +1:        10-15% of total budget (Cyber Monday + stragglers)

However: This is a starting point. Your MMM will discover the optimal pattern for YOUR brand from YOUR data.

How AMMM Discovers Optimal Timing

Your multi-period optimization will empirically determine the best allocation by:

  1. Prophet learns lag structures from historical data:

    • If you historically spent more in buildup weeks and saw strong Black Friday sales

    • Prophet encodes that lag relationship in its forecast

  2. Response curves capture dynamics:

    • The saturation curves show how spending in week T affects outcomes in weeks T, T+1, T+2

    • Multi-period optimization accounts for these cross-period effects

  3. Seasonal multipliers show when channels are most effective:

    • If buildup weeks have higher multipliers than Black Friday week

    • Optimization allocates more budget to those higher-ROI weeks

Validating Your Black Friday Strategy

After running your multi-period optimization, validate the results:

import pandas as pd
import matplotlib.pyplot as plt

# Load results
df = pd.read_csv('results/csv/multiperiod_optimization_results.csv')

# Extract Black Friday window (adjust period numbers to your dates)
bf_window = df[(df['period'] >= 4) & (df['period'] <= 8)]  

# Check budget allocation pattern
budget_by_period = bf_window.groupby('period')['budget'].sum()
print("\nBudget allocation around Black Friday:")
print(budget_by_period)

# Check seasonal multipliers
multipliers = bf_window[bf_window['channel']=='google'][['period', 'period_date', 'seasonal_multiplier']]
print("\nSeasonal effectiveness:")
print(multipliers)

# Visualize
plt.figure(figsize=(10, 6))
budget_by_period.plot(kind='bar')
plt.title('Budget Allocation: Black Friday Window')
plt.xlabel('Week')
plt.ylabel('Total Budget')
plt.show()

Questions to ask:

  • Is budget ramping up in weeks leading to Black Friday?

  • Where is the peak allocation - before or during the event?

  • How do seasonal multipliers change across the window?

Case Study: 26-Week Black Friday Planning

Scenario: Retail brand planning from mid-October through Q1

# Capture full Black Friday season + post-holiday period
results = ammm.optimize_marketing_budget(
    model=driver.model,
    data=driver.processed_data,
    config=driver.config,
    results_dir='results',
    n_time_periods=26,           # Oct through April
    multiperiod_mode=True,
    use_seasonality=True,
    frequency='W',
    start_date='2025-10-17'      # Mid-October
)

Expected seasonal pattern:

  • Weeks 0-5 (Oct-Nov): Baseline/ramping (multipliers ~0.95-1.05)

  • Weeks 6-7 (Black Friday/Cyber Monday): Peak event (multipliers >1.10)

  • Weeks 8-10 (Dec - Christmas): Sustained high (multipliers 1.05-1.15)

  • Weeks 11-12 (Post-Christmas): Drop-off (multipliers ~0.90-0.95)

  • Weeks 13-26 (Jan-Apr): Q1 baseline (multipliers 0.85-1.00)

Optimization will:

  • Front-load budget to Q4 (higher seasonal multipliers)

  • Concentrate spend 2-3 weeks before Black Friday (based on learned carryover)

  • Reduce budget post-holidays (lower multipliers)

Channel-Specific Holiday Strategies

Different channels may have different optimal timing:

Search (Google, Bing):

  • Peak effectiveness: During event (high-intent queries spike)

  • Strategy: Moderate buildup, heavy during Black Friday

Social (Facebook, TikTok, Instagram):

  • Peak effectiveness: 2-3 weeks before (awareness and consideration)

  • Strategy: Heavy buildup, maintain during event

TV/Display:

  • Peak effectiveness: 3-4 weeks before (brand awareness, long lead)

  • Strategy: Very heavy buildup, reduce during event

Your MMM learns this automatically from historical data and will allocate accordingly.

Extending Beyond Black Friday

The same principles apply to other retail events:

Prime Day (Amazon):

  • Shorter customer journey (impulse/deal-driven)

  • Suggest: 60% buildup (1-2 weeks), 40% during event

Christmas:

  • Very long planning window (starts October)

  • Suggest: Steady buildup from Oct, peak mid-December

Back to School:

  • Medium planning window (3-4 weeks)

  • Suggest: 70% buildup, 30% during peak weeks

Let your MMM’s optimization discover the optimal timing for each event based on your specific customer behaviour.

Further Reading

Peer-Reviewed Academic Research:

  1. Naik, Mantrala & Sawyer (1998) - “Planning Media Schedules in the Presence of Dynamic Advertising Quality”, Marketing Science

  2. Sethuraman, Tellis & Briesch (2011) - “How Well Does Advertising Work?”, Journal of Marketing Research

  3. Tellis & Weiss (1995) - “Does TV Advertising Really Affect Sales?”, Journal of Marketing

  4. Bruce, Foutz & Kolsarici (2012) - “Dynamic Effectiveness of Advertising and Word of Mouth in Sequential Distribution of Short Lifecycle Products”, Journal of Marketing Research

Industry Research (Non-Peer-Reviewed):

  • Google/Ipsos (2019) - “Black Friday Shopping Behaviour” - Consumer research study

  • Google Retail Insights - Annual Black Friday shopper behaviour studies

  • Facebook Holiday Marketing Guide - Customer journey research

  • Kantar - Seasonal advertising effectiveness studies

Key Takeaway: Multi-period optimization with seasonality lets your MMM empirically determine optimal timing based on YOUR data, rather than relying solely on industry averages.


Troubleshooting

Issue: “No Prophet model found”

Cause: Your MMM model doesn’t have Prophet seasonality fitted.

Solution: Ensure Prophet is configured in your model config:

baseline_model: 'prophet'  # Use Prophet for baseline/trend

Workaround: Run without seasonality:

use_seasonality=False

Issue: Optimization fails with “Infeasible constraints”

Cause: Per-period budget limits are too restrictive given total budget.

Example:

  • Total budget: £500K

  • Per-period limits: (£200K, £300K) per week

  • 4 weeks: Minimum needed = 4 × £200K = £800K (exceeds £500K!)

Solution: Adjust constraints:

period_budget_limits=(100_000, 200_000)  # More flexible

Or increase total budget.


Issue: Results show same budget for all periods

Cause: Seasonality not enabled or no seasonal variation in data.

Solutions:

  1. Enable seasonality: use_seasonality=True

  2. Check if Prophet has seasonal components (see “Checking Seasonal Patterns” above)

  3. If data truly has no seasonality, this is expected behaviour


Issue: Budget allocation seems unrealistic

Cause: Budget bounds may be too wide or too narrow.

Solution: Adjust bounds:

from src.driver.opt import optimize_multiperiod_budget

results = optimize_multiperiod_budget(
    ...,
    lower_bound_pct=0.10,  # Channels can go 10% below historical mean
    upper_bound_pct=0.30   # Channels can go 30% above historical mean
)

Issue: Slow optimization (>5 minutes)

Cause: Large number of periods × channels creates many variables.

Example: 52 weeks × 20 channels = 1,040 optimization variables

Solutions:

  1. Reduce planning horizon: n_periods=13 instead of 52

  2. Use faster frequency: monthly instead of weekly

  3. Simplify constraints (remove per-period limits)

Advanced Topics

Channel-Specific Budget Bounds

Currently all channels use the same bound percentages (±20%). To customize per-channel:

# Directly call core function with custom bounds
from src.core.opt import optimize_multiperiod_budget_distribution

budget_ranges = {
    'google': (5000, 25000),     # Google: £5K-£25K per period
    'facebook': (10000, 50000),  # Facebook: £10K-£50K per period
    'tv': (50000, 200000)        # TV: £50K-£200K per period
}

optimized_budget = optimize_multiperiod_budget_distribution(
    method='sigmoid',
    total_budget=12_500_000,
    budget_ranges=budget_ranges,
    parameters=sigmoid_params,
    channels=list(budget_ranges.keys()),
    n_periods=13,
    seasonal_effects=seasonal_effects
)

Equal Budget Per Period

To force equal budget across all periods:

period_budget = total_budget / n_periods
period_budget_limits=(period_budget, period_budget)  # Force exact amount

Weekly vs Monthly Planning

Weekly Planning (more granular):

  • Pros: Accounts for within-month variation, precise allocation

  • Cons: More variables (slower), requires weekly data

Monthly Planning (aggregated):

  • Pros: Faster optimization, simpler to execute

  • Cons: Misses intra-month patterns, less flexible

Best Practices

  1. Start with fewer periods - Test with 4-8 periods before running full year

  2. Validate seasonality - Check Prophet components make sense for your business

  3. Compare with/without seasonality - See impact of seasonal adjustments

  4. Monitor constraint feasibility - Ensure constraints don’t over-restrict optimization

  5. Cross-validate results - Compare multi-period results with historical performance

Integration with Main Pipeline

Multi-period optimization is integrated into runme.py and can be enabled via CLI:

# Enable multi-period mode (13 weeks, with seasonality)
python runme.py --multiperiod

# Or modify runme.py directly for custom settings

See runme.py for full integration example.

Examples in Code

See test_multiperiod.py for comprehensive examples testing:

  • Basic multi-period optimization

  • With seasonality adjustments

  • With per-period budget constraints

  • Validation of results

Run tests:

python test_multiperiod.py

Further Reading

Support

For issues or questions:

  1. Check the Troubleshooting section above

  2. Review GitHub Issues

  3. Create new issue with reproducible example