Methods · model assumptions

Every assumption, in one place

A simulation is an argument whose premises are code. This page lists the premises; the code is in spo/ and every number in this site can be regenerated with make reproduce.

Consumers

Each consumer i has a permanent valuation vi drawn from the configured family (lognormal by default: median 2.5, log-sd 0.5, marginal cost 1). Every other attribute is a noisy correlate of the standardised log valuation zi, so the seller's problem is genuinely one of prediction:

income
log-normal, correlation 0.5 with z; median 1
demographic-proxy group
Bernoulli with logit −1.5 × log income (30% overall, concentrated at low income). It carries no information about v beyond income — pricing on it is pure proxy discrimination
urgency, loyalty
probit-transformed correlates of z (0.4, 0.2)
browsing signals ×3
correlates of z (0.5) observed with noise sd 1.0 ÷ quality
purchase history
Poisson, rate increasing in income and loyalty
profile signal
z + N(0, 0.35 ÷ quality): the third-party "enriched" record available only in the individualized regime
dynamic features
exponentially-weighted purchase rate and a last-period-declined flag, updated from the consumer's own responses to this seller
period valuation
vit = vi × mean-one log-normal shock (sd 0.15)
vulnerable
bottom income tercile × top urgency tercile (a definition, used for measurement only)

Behaviour types (baseline shares)

myopic (50%)
buy iff p ≤ v
price-sensitive (15%)
buy iff p ≤ v and p ≤ 1.15 × last period's average price (reference-dependent refusal)
strategic (10%)
buy iff p ≤ 0.8 v: hold out to depress the seller's inference; costly today, may pay off through the dynamic features
privacy-aware (10%)
buy iff p ≤ v; behavioural and profile signals masked (population means) at a utility cost of 0.03 per period, zero under opt-out
switcher (10%)
buy iff p ≤ v; compares every seller under competition. Other types compare with probability 0.25
bounded (5%)
buy iff p ≤ v × exp(0.25 × logistic error): may overpay; negative surplus is counted

Sellers

Sellers observe only (features, price, bought) for offers they made. All model-based sellers price on a 40-point geometric grid from cost to the 99.5th valuation percentile, choosing the grid price that maximises (p − c)·P̂(buy | x, p). Exploration is ε-greedy with εt = max(0.03, 0.65t).

uniform
pooled logistic demand in price only; one price for everyone
segment
price-only logistic demand per (income tercile × group) cell, pooled fallback below 60 rows
regression
logistic demand linear in (x, p) — the classic WTP-regression pricer
boosting
histogram gradient boosting P̂(buy | x, p) with a monotone-decreasing constraint on price, 80 trees, refit every 2 periods
bandit
linear Thompson sampling over 12 price arms with context x (one Bayesian linear model per arm, batched posterior draws)
oracle
knows vi and prices against the period shock; the information upper bound, used for validation

Information regimes determine the feature set: uniform none; segment income tercile + group; behavioral + browsing ×3, history, loyalty, urgency signal, dynamic features (+ is-home under competition); individualized + the profile signal.

Competition

K sellers price every consumer each period. Consumer-seller valuations are vit × log-normal taste (sd 0.10); the home seller is the best taste match. Switchers, and other consumers with probability switch_prob, see every offer; the rest see only their home seller. The consumer picks the best considered option and applies their type rule. Each seller observes only the offers in a consumer's consideration set and learns from those. No seller observes rivals' prices; we make no claims about tacit collusion.

Welfare accounting

Per period: CS = Σ (vchosen − p)·1[buy] − privacy costs; PS = Σ (p − c)·1[buy]; TW = CS + PS; first best = Σ max(vbest − c, 0); DWL = first best − TW. Steady state = mean over periods 10–29. Distributional measures: Gini of consumer surplus (non-buyers at zero, negatives clipped to zero for the Gini only), surplus shares by income quintile, group price gap, vulnerable-group price-to-income ratio. Data-exposure index = share of sensitive signals the seller may use × share of consumers whose signals are visible.

Validation

Run before any result was interpreted (make validate, and make test): no sale below cost; no rational type buys above its valuation; CS + PS = TW and TW + DWL = first best in every period; the oracle with myopic consumers and no shock leaves zero consumer surplus and under 1% deadweight loss; the learned uniform price converges to the analytic monopoly optimum for lognormal, uniform and normal valuations; producer surplus is ordered oracle ≥ individualized ≥ uniform with myopic consumers; price caps and parity constraints hold in every period.

Validation report (make validate) — all pass: True
checkresultdetail
impossible_transactions_monopolypass{"transactions": 7914, "below_cost": 0, "above_value_rational": 0}
impossible_transactions_duopolypass{"transactions": 10313, "below_cost": 0, "above_value_rational": 0}
surplus_identity_monopolypass{"max_err_cs_ps_tw": 0.0, "max_err_tw_dwl_fb": 0.0}
surplus_identity_duopolypass{"max_err_cs_ps_tw": 0.0, "max_err_tw_dwl_fb": 0.0}
oracle_toypass{"cs": 0.0, "dwl": 0.0067, "fb": 1093.4823}
uniform_analytic_lognormalpass{"analytic": 2.5496, "learned": 2.6467, "rel_log_err": 0.0374, "grid_log_step": 0.0596, "profit_pc": 0.7716, "analytic_profit_pc": 0.7505}
uniform_analytic_uniformpass{"analytic": 2.3748, "learned": 2.2129, "rel_log_err": 0.0706, "grid_log_step": 0.0369, "profit_pc": 0.7515, "analytic_profit_pc": 0.7562}
monotone_informationpass{"uniform": {"ps": 1132.4504434183555, "cs": 1021.0516580927153}, "regression": {"ps": 1809.3263865630831, "cs": 347.80247728902594}, "oracle": {"ps": 2733.4617136396464, "cs": 0.0}}
policy_invariantspass{"price_cap_max_offer": 2.0, "price_cap_pass": true, "parity_max_rel_gap": 0.02, "parity_pass": true}

Econometric recovery

See the Econometrics page for the data-generating process. Estimators are implemented with statsmodels (OLS, HC1 errors), hand-written 2SLS, a within-transformed TWFE with market-clustered errors, and a clean-control DiD with a Welch-type standard error.

Reviewer attack — and responses

Agents are unrealistic
True and deliberate: each type is a one-line rule so its effect is attributable. The consumer-mix experiment varies shares from 0 to 40%; results that survive all six mixes are reported as robust, the rest as behaviour-dependent.
Welfare weights are arbitrary
Unweighted surplus is the transparent default. Every distributional cut is reported so a reader can re-weight; we do not choose for them.
Missing competition
Included as an extension with 1–5 sellers and two switching intensities; not a full oligopoly equilibrium (sellers learn myopically), and we say so.
Policies are oversimplified
Each is one exact rule; the frontier experiment sweeps its parameter. The alternative — simulating legal ambiguity — would not be more informative.
External validity
Calibrated only in sign and order of magnitude against Dubé & Misra (2023) and Shiller (2014); see docs/literature.md. No claim is made about any specific market.
Fairness definitions
Two are used: an input rule (feature restriction) and an outcome rule (mean-price parity). They give different answers, which is the point.
Sensitivity to distributions
Five valuation families and three dispersions in the robustness sweep.
Causal language
In the simulation every effect is causal by construction. The econometrics page is about whether an empiricist could say the same, and mostly they could not.

Ethics

All consumers are synthetic. The "demographic proxy" group is a generic construct correlated with income so that proxy discrimination can be measured; it is not any real protected class. The repository contains no code that scores real people, no scraped data, and no interface to a live pricing system. The purpose is to audit what such systems do and which rules constrain them. The literature review (docs/literature.md) explains why no real dataset is used.

Reproduce: make test validate simulate-smoke simulate-main simulate-extensions robustness econometrics analyze figures paper site (or make reproduce). Configuration in configs/, pre-registration in hypotheses.json, manifests in runs/.