23 · Auctions, payment rules, and bidding
Chapter 18 remains the overview; this chapter isolates one question. The formal 200k experiment is preserved. Teaching runs use separate records in the shared registry.
Predict first: is the winner's bid its actual spend?
This chapter uses an independent simulated market, never Criteo counterfactual revenue. Per-impression bid b=p_hat×fictional click value×multiplier is currency per impression, not CPC or eCPM. Under the second-price rule, the winner pays the maximum of the next eligible internal bid and the external bid.
Calculate one request: estimated CTRs .5/.3, click values both 2, bids 1/.6, external price .2. Who wins and pays how much? Does a simple truthfulness claim apply unchanged when budgets exist?
English companion. All instructional Markdown is translated. Experiment code, Chinese language examples, execution counts, and saved outputs remain as in the source edition; historical output text may therefore be Chinese. The initial setup cell anchors the working directory and Python import path for this companion's deeper folder; it does not change the experiment. Saved outputs are historical evidence, not a new run. Run from the top after preparing the required data and models.
# English companions live one directory deeper; keep the original experiment code unchanged.
from pathlib import Path as _TranslationPath
import os as _translation_os
import sys as _translation_sys
_translation_root = next((p for p in [_TranslationPath.cwd(), *_TranslationPath.cwd().parents]
if (p / "reclab").is_dir()), None)
if _translation_root is None:
raise RuntimeError("Open this notebook from inside the Search Recommendation Ads Lab checkout.")
_translation_os.chdir(_translation_root)
if str(_translation_root) not in _translation_sys.path:
_translation_sys.path.insert(0, str(_translation_root))
from pathlib import Path
import sys
ROOT=Path.cwd()
if not (ROOT/'reclab').is_dir(): ROOT=ROOT.parent
sys.path.insert(0,str(ROOT))
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import display
from reclab.ads import latest, load_data, split_groups, NUM, CAT, FEATURES
from reclab.paths import ARTIFACTS
from reclab.probability import evaluate
preview=ARTIFACTS/'notebook-previews';preview.mkdir(parents=True,exist_ok=True)
from reclab.auction import simulate,market
one={'estimate':np.array([[.5,.3]]),'true_p':np.array([[.4,.2]]),'value':np.array([2.,2.]),'clicks':np.array([[True,False]]),'external':np.array([.2])}
print('Inputs:',one['estimate'].shape,'bid:',one['estimate']*one['value'])
answer=simulate(one,budget=2.)
assert np.isclose(answer['spend'],.6) and answer['wins']==1
print({k:answer[k] for k in ['spend','expected_click_value','realized_click_value','expected_advertiser_surplus']})Saved notebook output · not executed on this page
Inputs: (1, 2) bid: [[1. 0.6]]
{'spend': 0.6, 'expected_click_value': 0.8, 'realized_click_value': 2.0, 'expected_advertiser_surplus': 0.20000000000000007}
Change one variable: external competition price
Keep internal bids, potential clicks, and budgets fixed; raise the external bid to 1.1. The highest internal bid of 1 cannot win: no internal allocation or payment occurs. External allocation is outside the model. This prevents treating every request as an internal ad impression.
Second-price payment does not exceed the winning bid, but budgets still matter. Eligibility conservatively requires the full bid to be affordable before the auction, rather than deciding after observing the price. This explicit assumption may exclude otherwise affordable requests.
external_changed={**one,'external':np.array([1.1])}
no_sale=simulate(external_changed,budget=2.)
assert no_sale['wins']==0 and no_sale['spend']==0
print('External price changed only: wins',no_sale['wins'],'spend',no_sale['spend'])
small_budget=simulate(one,budget=.7)
print('Budget .7 excludes bid1 even though hypothetical second price .6 would be affordable:',small_budget['advertisers'])Saved notebook output · not executed on this page
External price changed only: wins 0 spend 0.0
Budget .7 excludes bid1 even though hypothetical second price .6 would be affordable: [{'spend': 0.0, 'wins': 0, 'clicks': 0}, {'spend': 0.2, 'wins': 1, 'clicks': 0}]
Run: keep the potential market fixed, vary only the bid multiplier
There are 6,000 requests and three advertisers. true_p follows constructed Beta probabilities, estimates have multiplicative noise, and potential clicks are generated once. Every strategy shares this market, so differences do not come from resampling clicks. More valuable second-half traffic is assumed in advance.
Fix budget 60 and pacing; vary only .25/.5/1/2 multipliers. This is sensitivity analysis, not online bid optimization. All internal advertisers change together while external bids remain fixed; real competitors need not behave that way.
synthetic=market(seed=42)
print('Estimated/true probabilities:',synthetic['estimate'].shape,'potential clicks:',synthetic['clicks'].shape)
rows=[]
for multiplier in [.25,.5,1.,2.]:
r=simulate(synthetic,multiplier=multiplier,paced=True)
rows.append({'multiplier':multiplier,**{k:r[k] for k in ['spend','wins','clicks','expected_click_value','expected_advertiser_surplus','budget_ok']}})
table=pd.DataFrame(rows);display(table)
assert table.budget_ok.all()Saved notebook output · not executed on this page
Estimated/true probabilities: (6000, 3) potential clicks: (6000, 3)
| multiplier | spend | wins | clicks | expected_click_value | expected_advertiser_surplus | budget_ok | |
|---|---|---|---|---|---|---|---|
| 0 | 0.25 | 179.795277 | 2822 | 498 | 981.983513 | 802.188236 | True |
| 1 | 0.50 | 179.667495 | 2744 | 297 | 594.607116 | 414.939622 | True |
| 2 | 1.00 | 179.352946 | 2522 | 181 | 363.687988 | 184.335042 | True |
| 3 | 2.00 | 178.952032 | 2281 | 109 | 233.588903 | 54.636870 | True |
Explain: revenue, value, and surplus are different objectives
Simulated platform revenue is total payment. Advertiser expected click value uses constructed true_p×click value. Expected surplus=value−payment; realized click value also includes Bernoulli noise. Similar nearly exhausted budgets may purchase very different impressions.
Spending curves alone cannot judge a strategy. Plot simulated spending and expected click value together, explicitly in simulated currency. This is not real ROI and includes no conversion, profit, or delayed attribution.
fig,axes=plt.subplots(1,2,figsize=(10,3));axes[0].plot(table.multiplier,table.spend,'o-');axes[1].plot(table.multiplier,table.expected_click_value,'o-')
axes[0].set(xlabel='Shared bid multiplier',ylabel='Simulated spend');axes[1].set(xlabel='Shared bid multiplier',ylabel='Synthetic expected click value')
fig.tight_layout();fig.savefig(preview/'23_bids.png',dpi=130);plt.show()
print('No causal or revenue claim is inferred from public click logs.')Saved notebook output · not executed on this page
<Figure size 1000x300 with 2 Axes>

No causal or revenue claim is inferred from public click logs.
Exercise: fix one strategy and inspect randomness
Change only seed, retaining multiplier 1, budget 60, and pacing. Within-seed strategy comparisons are paired; differences across seeds reflect finite simulated-market variation. Three seeds do not establish stable industrial gains.
Explain payment, no-allocation conditions, budget eligibility, and the roles of true_p and estimate. Why does better probability calibration not automatically imply better returns from a particular auction policy?
display(pd.DataFrame([{'seed':seed,**{k:r[k] for k in ['spend','expected_click_value','realized_click_value','budget_ok']}} for seed in [41,42,43] for r in [simulate(market(seed),paced=True)]]))
print('Real prediction probabilities and synthetic auction outcomes remain separate evidence layers.')Saved notebook output · not executed on this page
| seed | spend | expected_click_value | realized_click_value | budget_ok | |
|---|---|---|---|---|---|
| 0 | 41 | 179.523495 | 362.738625 | 359.0 | True |
| 1 | 42 | 179.352946 | 363.687988 | 359.5 | True |
| 2 | 43 | 179.534714 | 362.467222 | 368.0 | True |
Real prediction probabilities and synthetic auction outcomes remain separate evidence layers.