🤖 The Invisible Hand of Machines
UQO Working Paper No. 27 — Price Discovery in Markets Populated by LLM Agents
TL;DR — An event-sourced limit-order-book exchange with exact accounting and a latent dividend process (fundamental value ≠ public value ≠ price), seven calibrated classical trader types, and an adapter that lets a language model trade from a canonical snapshot through strictly validated JSON. Randomized matched-seed experiments assign 0–100 % of trader slots to
gpt-5.4-nano(7 seeds per live cell, 30 per classical cell, pre-registered plan). A stylized correlated-error model predicts that a shared model degrades informativeness and amplifies spirals; the simulator confirms the coordination and diversity predictions and rejects the price predictions: spreads fall from 19 to 6 bp, depth ×7, impact −60 %, pricing error unchanged, no bubble or crash in 112 live runs, and LLM agents dampen leverage-driven fire sales — while latent information is absorbed more slowly and returns become predictable (VR(5) 1.1 → 2.1, ACF₁ 0.02 → 0.32). Mechanism: patient, public-value-anchored liquidity provision by agents that act infrequently.
📌 Status
| Item | Value |
|---|---|
| Version | 1.0 (October 2026) |
| SSRN | 7679801 (submitted 2026-10-28, preliminary upload) |
| Journal | — (candidates: JEDC, Quantitative Finance, Journal of Financial Markets) |
| Code & data | fully reproducible without an API key (make replicate); live cells need OPENAI_API_KEY (≈ 60 USD for the full design); Anthropic cells implemented but not run (key rejected, HTTP 401) |
| Pre-registration | docs/identification.md, docs/protocol.md; every post-plan change in docs/research_log.md |
🎯 What this paper does
- Builds a sandbox exchange (
src/market_agents/exchange): continuous double auction, price–time priority, limit/market/cancel, partial fills, fees, per-agent latency, order TTL, price band, circuit breaker, position limit, periodic call auction, leverage/margin/forced liquidation, integer-cent accounting checked after every fill, append-only event log with deterministic replay (24 unit/property/integration tests). - Specifies the economy (
assets,signals): dividends every 20 ticks with a latent AR(1) deviation of the next dividend; latent value V = μ/r + x/(1+r−ρ); public value from a Kalman filter on announced dividends and (possibly misleading) news; private signals of known precision; macro, funding, liquidity and placebo shocks. - Populates it (
agents): fundamentalists, trend followers, mean-reverters, noise traders, bounded inventory-skew market makers, cross-asset arbitrageurs, leveraged traders — calibrated to stylized facts (ACF₁ ≈ 0, fat tails, volatility clustering, 10–30 bp spreads); 10,000 traders run in 24 s. - Adds LLM agents (
llm): OpenAI (Responses API, json_schema), Anthropic (Messages API, json_schema), mock; strict validation (invalid → hold, logged), bounded memory, cohort communication channel, adaptive feedback, prompt personas, USD/token caps, SQLite decision cache, frozen k-NN decision library for zero-cost scaling. - Derives the mechanisms (
paper/sections/theory.tex, proofs in the appendix, Monte Carlo checks inscripts/90_theory_checks.py): six propositions on informativeness vs shared-model share, order-flow correlation and synchrony, effective number of models, feedback amplification, leverage spirals, safeguards. - Runs randomized designs (
configs/*.yaml,experiments/design.py): common random numbers across matched seeds; LLM slots replace policy slots stratified by type and inherit their information set.
🏆 Headline results (confirmatory design, 7 matched seeds per cell)
| Outcome | 0 % LLM | 25 % | 95 % | Paired effect at 95 % |
|---|---|---|---|---|
| Quoted spread (bp) | 18.6 | 11.0 | 6.1 | −12.5 [−17.8, −7.3], d = −2.2 |
| Depth, 5 levels (lots) | 141 | 201 | 956 | +816 |
| Price impact (bp / 100 lots) | 9.3 | 5.2 | 3.6 | −5.7, Holm p = 0.01 |
| Mean |P/V − 1| (%) | 0.73 | 0.50 | 0.54 | −0.18 [−0.67, +0.30] (n.s.) |
| Variance ratio (5 ticks) | 1.14 | 1.55 | 2.12 | +0.97, d = 3.5 |
| Return ACF₁ | 0.02 | 0.15 | 0.32 | +0.30, d = 3.5 |
| Bubbles / crashes / halts | 0 | 0 | 0 | — |
| LLM decision synchrony / pairwise flow corr. | — | 0.95 / 0.40 | 0.90 / 0.34 | corr. falls with share |
| Leverage 19 %: tracking error (0 % vs 25 % LLM) | 2.16 % | 0.96 % | — | −1.2, p = 0.03; bubbles 0.7 → 0 |
| Diverse prompts / two models at 25 %: pairwise corr. | 0.40 | 0.32 / 0.30 | — | absorb shock 4 ticks faster, spread +0.5 / +4.6 bp |
| Communication channel at 25 %: pairwise corr. | 0.40 | 0.48 | — | tracking error +0.05 pp, d = 2.3 |
Classical design (16 cells × 30 seeds): leverage and trend-heavy populations create bubbles and crashes; misleading public news raises pricing error; a position limit harms price discovery; circuit breaker inert at a 2 % shock; two-asset contagion through common funding (asset-1 drawdown 3.6 → 11.6 % with untouched fundamentals).
🗂 Repository layout
├── CLAUDE.md # research protocol (the brief this repo executes)
├── Makefile · pyproject.toml · uv.lock · .env.example
├── configs/ # baseline.yaml, classical_confirmatory, exploratory (mock), pilot_openai, confirmatory_openai
├── src/market_agents/ # exchange/ assets/ signals/ agents/ llm/ experiments/ metrics/ analysis/ utils/ cli.py
├── tests/ # unit, property (hypothesis), integration
├── scripts/ # 10 calibration · 11 scale · 40 figures · 45 recompute · 46 thresholds · 47 mechanisms · 50 tables · 60 library · 90 theory checks · smoke_test
├── docs/ # research_plan, identification (pre-analysis plan), protocol, assumptions, model_cards, literature_ledger (143 verified refs), novelty_positioning, research_log
├── results/ # runs/<design>/<cell>/seed*/ (config, manifest, ticks/trades/agents/llm parquet), tables/, figures/, calibration/, library/
└── paper/ # main.tex, sections/, appendices/, tables/, figures/, references.bib → main.pdf🚀 Quick start
uv sync --extra dev # Python 3.12 environment from uv.lock
make test # 24 tests
make smoke # 100 agents, fixed seed, zero paid calls, accounting verified
make replicate # classical designs + theory checks + figures + tables (no API, ~5 min)
cp .env.example .env # add OPENAI_API_KEY (and ANTHROPIC_API_KEY) for live cells
make pilot # ≈ 4 USD ; make confirmatory # ≈ 60 USD, ~3 h with 5 workers
make library # frozen-library replay at N = 1,000 (no API)
make paper # latexmk → paper/main.pdfRuntime on an Apple-silicon laptop: one 100-trader run 0.6 s, 1,000 traders 2.6 s, 10,000 traders 24 s; a 25 % LLM run ≈ 7 min (API-latency bound).
🗃 Data
All data are synthetic and generated by the simulator; nothing is downloaded. Every run directory holds config.yaml, manifest.json (config hash, git revision, seeds, providers/models, price assumptions, prompt version, realized LLM shares, usage), ticks/trades/agents/llm_decisions.parquet and outcomes.json. Live decisions (80,031 in the confirmatory design, 35,652 in the dose arm) are cached in results/cache/llm_cache.sqlite (not committed; regenerate with the designs). Total API spend for the reported runs: 56 USD (plus ≈ 14 USD on runs discarded after bug fixes, see docs/research_log.md).
🔬 Methodology
- Common random numbers: one seed → named streams (fundamentals, news, slots, assignment, LLM slots, activation, tie-breaks, shuffle); matched pairs differ only in who decides (
utils/rng.py,experiments/population.py). - Outcomes at the run level (
metrics/outcomes.py): pricing error, time to absorb a latent shock, response ratio, variance ratios, spreads, depth, Kyle λ, resiliency, bubbles/crashes at pre-registered thresholds, volatility clustering, contagion metrics, LLM validity/synchrony/pairwise correlation, cost. - Inference (
analysis/estimators.py): paired t with 95 % CI, Cohen's d_z, Wilcoxon, seed-FE OLS with cluster-robust SE, Holm within families, local projections, threshold sensitivity grid. - Theory ↔ simulator mapping and H1–H6 in
paper/sections/theory.tex; pre-analysis plan indocs/identification.md.
🖼 Figures & tables inventory
| Figure | Content |
|---|---|
fig_baseline_paths |
mid vs latent/public value, three classical economies |
fig_classical_* |
classical mechanism cells (tracking error, absorption, drawdown, spread, impact, bubbles) |
fig_contagion_*, fig_wealth_depth |
two-asset comovement/spillover; P&L distribution and depth |
fig_dose_conf, fig_paths_conf, fig_realized_share_conf, fig_sync_conf, fig_lp_conf |
dose–response, price paths, realized dose, coordination, local projections |
fig_arms_conf_*, fig_cost_quality_conf |
25 % arms; cost–quality frontier |
Tables (paper/tables/*.tex, generated by scripts/50_tables.py): stylized facts, scale, classical cells/effects, contagion, dose (pilot and confirmatory), arms cells/effects, LLM reliability, coordination, mechanism association, library, threshold sensitivity; theory checks in results/tables/theory_checks.csv.
⚠️ Limitations
- Seven matched seeds per live cell: effects below d ≈ 1.2 are not detectable; nulls on pricing error, absorption and drawdown are inconclusive for small effects.
- One provider family (OpenAI nano/mini); Anthropic cells implemented but not run (key rejected on 2026-10-27).
- LLM agents decide every 20 ticks from a fixed prompt with no tools; realized order share is 5–10× below the assigned slot share.
- Partial-equilibrium sandbox; stylized model assumes market-order traders, the data point to limit-order liquidity providers.
- No claim about live markets.
📝 Citation
@techreport{boucher2026invisiblehand,
author = {Boucher, Simon-Pierre},
title = {The Invisible Hand of Machines: Price Discovery in Markets Populated by LLM Agents},
institution = {Universit\'e du Qu\'ebec en Outaouais, D\'epartement des sciences administratives},
type = {Working Paper},
number = {27},
year = {2026},
month = {October}
}👤 Author & contact
Simon-Pierre Boucher — Département des sciences administratives, Université du Québec en Outaouais (UQO), Gatineau, QC. 📧 simon-pierre.boucher@uqo.ca
🗒 Notes
- Built on 2026-10-27 from the protocol in
CLAUDE.md; numbers 25–26 of the series are unassigned. - Deviations from
Recherche-UQO/HARMONISATION.md:pyproject.toml+uv.lockinstead ofrequirements.txt; results and cache directories are not committed (regenerable).