Simulation and optimization toolkit for publisher-side ad serving.
Model auctions. Optimize yield. Simulate before you ship.
Installation · Quickstart · Features · API Reference
Ad serving systems make millions of allocation decisions per day — each one a revenue-impacting tradeoff between fill rate, floor price, pacing, and campaign priority. Yet most teams build these systems by trial and error in production, with no way to simulate the revenue impact of a configuration change before it goes live.
bidscape gives you a simulation environment for your ad server. Test floor price strategies, compare auction types, validate pacing algorithms, and forecast inventory — all without touching production traffic.
| Without bidscape | With bidscape |
|---|---|
| Guess floor prices, lose revenue or fill rate | yield_opt.optimize_floor_price(bids) — data-driven optimal floor |
| Ship pacing changes blind, overshoot budgets | pacing.pid_pacing(...) — simulate pacing over the full flight |
| No way to compare allocation strategies | simulate.compare_strategies(...) — side-by-side revenue comparison |
| Estimate inventory on spreadsheets | forecast.ets_forecast(history, horizon=7) — statistical forecasting |
| Debug auction logic in production | auction.first_price_auction(bids, floor) — unit-testable auction mechanics |
| Manual yield management | allocation.dynamic_allocation(...) — real-time direct vs programmatic decisions |
- Ad platform engineers — prototype and validate serving logic before production deployment
- Yield management teams — optimize floor prices, allocation splits, and pacing parameters with simulation data
- Ad tech startups — build on battle-tested auction and pacing primitives instead of from scratch
- CTV/streaming ad teams — model inventory scenarios specific to content-driven ad delivery
- Students and researchers — learn publisher-side ad tech through clean, readable implementations
pip install bidscapeRequires Python 3.10+. Dependencies: NumPy, SciPy, Pandas, Plotly.
import bidscape
# Simulate 10,000 ad requests with first-price auction
sim = bidscape.simulate.run_simulation(
bidscape.SimConfig(
n_requests=10_000,
floor_price=5.0,
auction_type="first_price",
n_bidders=5,
bid_mean=8.0,
bid_std=3.0,
seed=42,
)
)
print(sim.summary())
# Simulation Results:
# Fill rate: 100.0%
# Total revenue: $117.30
# eCPM: $11.73
# Optimize floor price from observed bids
floor_opt = bidscape.yield_opt.optimize_floor_price(sim.clearing_prices)
print(floor_opt.summary())
# Floor Optimization:
# Optimal floor: $7.80
# Expected fill rate: 95.0%
# Compare strategies
results = bidscape.simulate.compare_strategies(
bidscape.SimConfig(n_requests=5000, seed=42),
{
"conservative": {"floor_price": 3.0},
"aggressive": {"floor_price": 10.0},
"optimized": {"floor_price": floor_opt.optimal_floor},
},
n_runs=5,
)
for name, m in results.items():
print(f"{name}: revenue=${m['revenue_mean']:.2f}, fill={m['fill_rate_mean']:.1%}")First-price, second-price, and header bidding auctions with configurable floor prices.
from bidscape import Bid
bids = [Bid("dsp_a", 10.0), Bid("dsp_b", 8.5), Bid("dsp_c", 12.0)]
# First-price: winner pays their bid
result = bidscape.auction.first_price_auction(bids, floor_price=5.0)
# Second-price: winner pays second-highest bid
result = bidscape.auction.second_price_auction(bids, floor_price=5.0)
# Header bidding: unified auction across multiple exchanges
result = bidscape.auction.header_bidding_auction({
"exchange_a": [Bid("buyer1", 10.0)],
"exchange_b": [Bid("buyer2", 12.0)],
})Three pacing algorithms used in production ad servers, from simple to sophisticated.
# Proportional pacing — straightforward ratio-based
state = bidscape.pacing.proportional_pacing(
budget_total=10_000, budget_spent=4_000,
impressions_delivered=20_000, impressions_target=50_000,
elapsed_fraction=0.5,
)
# PID pacing — smooth delivery with error correction
state, integral, error = bidscape.pacing.pid_pacing(
budget_total=10_000, budget_spent=4_000,
impressions_delivered=20_000, impressions_target=50_000,
elapsed_fraction=0.5,
error_integral=prev_integral, prev_error=prev_error,
)
# Multiplicative pacing — production-grade exponential adjustment
state = bidscape.pacing.multiplicative_pacing(
budget_total=10_000, budget_spent=4_000,
impressions_delivered=20_000, impressions_target=50_000,
elapsed_fraction=0.5, prev_throttle=0.8,
)Allocate impressions between direct-sold campaigns and programmatic demand.
# Priority-weighted allocation across direct and programmatic
result = bidscape.allocation.allocate_inventory(
total_impressions=1_000_000,
direct_campaigns={
"brand_deal": {"impressions": 200_000, "cpm": 25.0, "priority": 1},
"performance": {"impressions": 300_000, "cpm": 12.0, "priority": 2},
},
programmatic_ecpm=8.0,
)
# Real-time dynamic allocation decision for a single impression
winner, price = bidscape.allocation.dynamic_allocation(
direct_floor=5.0,
programmatic_bids=[8.0, 12.0, 6.0],
direct_value=10.0,
)Forecast future impressions to plan allocation, pricing, and campaign commitments.
# Holt's exponential smoothing (trend-aware)
forecast = bidscape.forecast.ets_forecast(daily_impressions, horizon=7)
# Seasonal naive (weekly patterns)
forecast = bidscape.forecast.seasonal_naive_forecast(daily_impressions, horizon=14)
# Moving average baseline
forecast = bidscape.forecast.moving_average_forecast(daily_impressions, horizon=7)Data-driven floor pricing and bid landscape analysis.
# Find the revenue-maximizing floor price
result = bidscape.yield_opt.optimize_floor_price(historical_bids)
print(f"Optimal floor: ${result.optimal_floor:.2f}")
print(f"Expected fill rate: {result.expected_fill_rate:.0%}")
# Fit bid landscape (log-normal model)
landscape = bidscape.yield_opt.fit_bid_landscape(historical_bids)
# Win rate curve: P(win | bid_price)
curve = bidscape.yield_opt.win_rate_curve(historical_bids)
# Revenue at a specific floor price
rev, fill, avg_price = bidscape.yield_opt.revenue_at_floor(historical_bids, 5.0)Full ad server simulation with configurable demand, supply, and auction parameters.
# Single simulation run
sim = bidscape.simulate.run_simulation(bidscape.SimConfig(
n_requests=50_000,
floor_price=5.0,
auction_type="first_price",
n_bidders=5,
bid_mean=8.0,
bid_std=3.0,
direct_campaigns=[
{"campaign_id": "brand", "impressions": 10_000, "cpm": 20.0},
],
seed=42,
))
# Compare multiple strategies with statistical confidence
results = bidscape.simulate.compare_strategies(
base_config, variations={"A": {...}, "B": {...}}, n_runs=10,
)Plotly-based charts for every stage of the ad serving pipeline.
bidscape.visualize.plot_revenue_curve(floor_opt_result) # Floor price vs revenue
bidscape.visualize.plot_bid_distribution(bids, floor=5.0) # Bid histogram with floor
bidscape.visualize.plot_pacing(elapsed, spent, throttle) # Pacing over time
bidscape.visualize.plot_forecast(forecast_result) # Inventory forecast
bidscape.visualize.plot_win_rate_curve(curve) # Win probability by bid
bidscape.visualize.plot_simulation_comparison(results) # Strategy comparison bars- Simulate before you ship — test configuration changes offline before they impact revenue
- Publisher-side focus — built for the sell side: floor pricing, yield, fill rate, allocation
- Composable — use individual modules (auctions, pacing) standalone or combine into full simulations
- Production-informed — pacing algorithms, auction types, and allocation strategies mirror real ad server architecture
- Tested — 90 tests, 99% coverage, seeded randomness for reproducible simulations
| Module | Functions |
|---|---|
bidscape.auction |
first_price_auction, second_price_auction, header_bidding_auction |
bidscape.pacing |
proportional_pacing, pid_pacing, multiplicative_pacing |
bidscape.allocation |
allocate_inventory, dynamic_allocation, optimal_fill_allocation |
bidscape.forecast |
ets_forecast, seasonal_naive_forecast, moving_average_forecast |
bidscape.yield_opt |
optimize_floor_price, fit_bid_landscape, win_rate_curve, revenue_at_floor |
bidscape.simulate |
run_simulation, compare_strategies |
bidscape.metrics |
compute_metrics, fill_rate, ecpm, bid_to_win_ratio, revenue_lift |
bidscape.visualize |
plot_revenue_curve, plot_bid_distribution, plot_pacing, plot_forecast, plot_win_rate_curve, plot_simulation_comparison |
MIT
