1<!-- Author: Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca -->2# 🤖 The Invisible Hand of Machines34**UQO Working Paper No. 27** — *Price Discovery in Markets Populated by LLM Agents*56[-b31b1b?logo=latex&logoColor=white)](paper/main.pdf)7[](pyproject.toml)8[](results/)9[](Makefile)10[](https://uqo.ca)11[](mailto:simon-pierre.boucher@uqo.ca)1213> **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.1415---1617## 📌 Status1819| Item | Value |20|---|---|21| Version | 1.0 (October 2026) |22| SSRN | [7679801](https://papers.ssrn.com/abstract=7679801) (submitted 2026-10-28, preliminary upload) |23| Journal | — (candidates: JEDC, Quantitative Finance, Journal of Financial Markets) |24| 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) |25| Pre-registration | `docs/identification.md`, `docs/protocol.md`; every post-plan change in `docs/research_log.md` |2627## 🎯 What this paper does28291. **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).302. **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.313. **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.324. **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.335. **Derives the mechanisms** (`paper/sections/theory.tex`, proofs in the appendix, Monte Carlo checks in `scripts/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.346. **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.3536## 🏆 Headline results (confirmatory design, 7 matched seeds per cell)3738| Outcome | 0 % LLM | 25 % | 95 % | Paired effect at 95 % |39|---|---|---|---|---|40| Quoted spread (bp) | 18.6 | 11.0 | 6.1 | −12.5 [−17.8, −7.3], d = −2.2 |41| Depth, 5 levels (lots) | 141 | 201 | 956 | +816 |42| Price impact (bp / 100 lots) | 9.3 | 5.2 | 3.6 | −5.7, Holm p = 0.01 |43| Mean \|P/V − 1\| (%) | 0.73 | 0.50 | 0.54 | −0.18 [−0.67, +0.30] (n.s.) |44| Variance ratio (5 ticks) | 1.14 | 1.55 | 2.12 | +0.97, d = 3.5 |45| Return ACF₁ | 0.02 | 0.15 | 0.32 | +0.30, d = 3.5 |46| Bubbles / crashes / halts | 0 | 0 | 0 | — |47| LLM decision synchrony / pairwise flow corr. | — | 0.95 / 0.40 | 0.90 / 0.34 | corr. falls with share |48| Leverage 19 %: tracking error (0 % vs 25 % LLM) | 2.16 % | 0.96 % | — | −1.2, p = 0.03; bubbles 0.7 → 0 |49| 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 |50| Communication channel at 25 %: pairwise corr. | 0.40 | 0.48 | — | tracking error +0.05 pp, d = 2.3 |5152Classical 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).5354## 🗂 Repository layout5556```57├── CLAUDE.md # research protocol (the brief this repo executes)58├── Makefile · pyproject.toml · uv.lock · .env.example59├── configs/ # baseline.yaml, classical_confirmatory, exploratory (mock), pilot_openai, confirmatory_openai60├── src/market_agents/ # exchange/ assets/ signals/ agents/ llm/ experiments/ metrics/ analysis/ utils/ cli.py61├── tests/ # unit, property (hypothesis), integration62├── scripts/ # 10 calibration · 11 scale · 40 figures · 45 recompute · 46 thresholds · 47 mechanisms · 50 tables · 60 library · 90 theory checks · smoke_test63├── docs/ # research_plan, identification (pre-analysis plan), protocol, assumptions, model_cards, literature_ledger (143 verified refs), novelty_positioning, research_log64├── results/ # runs/<design>/<cell>/seed*/ (config, manifest, ticks/trades/agents/llm parquet), tables/, figures/, calibration/, library/65└── paper/ # main.tex, sections/, appendices/, tables/, figures/, references.bib → main.pdf66```6768## 🚀 Quick start6970```bash71uv sync --extra dev # Python 3.12 environment from uv.lock72make test # 24 tests73make smoke # 100 agents, fixed seed, zero paid calls, accounting verified74make replicate # classical designs + theory checks + figures + tables (no API, ~5 min)75cp .env.example .env # add OPENAI_API_KEY (and ANTHROPIC_API_KEY) for live cells76make pilot # ≈ 4 USD ; make confirmatory # ≈ 60 USD, ~3 h with 5 workers77make library # frozen-library replay at N = 1,000 (no API)78make paper # latexmk → paper/main.pdf79```8081Runtime 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).8283## 🗃 Data8485All 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`).8687## 🔬 Methodology88891. 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`).902. 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.913. 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.924. Theory ↔ simulator mapping and H1–H6 in `paper/sections/theory.tex`; pre-analysis plan in `docs/identification.md`.9394## 🖼 Figures & tables inventory9596| Figure | Content |97|---|---|98| `fig_baseline_paths` | mid vs latent/public value, three classical economies |99| `fig_classical_*` | classical mechanism cells (tracking error, absorption, drawdown, spread, impact, bubbles) |100| `fig_contagion_*`, `fig_wealth_depth` | two-asset comovement/spillover; P&L distribution and depth |101| `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 |102| `fig_arms_conf_*`, `fig_cost_quality_conf` | 25 % arms; cost–quality frontier |103104Tables (`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`.105106## ⚠️ Limitations107108- 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.109- One provider family (OpenAI nano/mini); Anthropic cells implemented but not run (key rejected on 2026-10-27).110- LLM agents decide every 20 ticks from a fixed prompt with no tools; realized order share is 5–10× below the assigned slot share.111- Partial-equilibrium sandbox; stylized model assumes market-order traders, the data point to limit-order liquidity providers.112- No claim about live markets.113114## 📝 Citation115116```bibtex117@techreport{boucher2026invisiblehand,118 author = {Boucher, Simon-Pierre},119 title = {The Invisible Hand of Machines: Price Discovery in Markets Populated by LLM Agents},120 institution = {Universit\'e du Qu\'ebec en Outaouais, D\'epartement des sciences administratives},121 type = {Working Paper},122 number = {27},123 year = {2026},124 month = {October}125}126```127128## 👤 Author & contact129130**Simon-Pierre Boucher** — Département des sciences administratives, Université du Québec en Outaouais (UQO), Gatineau, QC.131📧 [simon-pierre.boucher@uqo.ca](mailto:simon-pierre.boucher@uqo.ca)132133## 🗒 Notes134135- Built on 2026-10-27 from the protocol in `CLAUDE.md`; numbers 25–26 of the series are unassigned.136- Deviations from `Recherche-UQO/HARMONISATION.md`: `pyproject.toml` + `uv.lock` instead of `requirements.txt`; results and cache directories are not committed (regenerable).137