# πŸ€– The Invisible Hand of Machines **UQO Working Paper No. 27** β€” *Price Discovery in Markets Populated by LLM Agents* [![Paper](https://img.shields.io/badge/paper-PDF%20(101%20p.)-b31b1b?logo=latex&logoColor=white)](paper/main.pdf) [![Python](https://img.shields.io/badge/python-3.12-3776AB?logo=python&logoColor=white)](pyproject.toml) [![Data](https://img.shields.io/badge/data-synthetic%20%C2%B7%20611%20runs%20%C2%B7%2080k%20LLM%20decisions-4051b5)](results/) [![Reproducible](https://img.shields.io/badge/reproducible-make%20replicate-2e7d32)](Makefile) [![Institution](https://img.shields.io/badge/UQO-D%C3%A9pt.%20des%20sciences%20administratives-16365c)](https://uqo.ca) [![Author](https://img.shields.io/badge/author-Simon--Pierre%20Boucher-16365c)](mailto:simon-pierre.boucher@uqo.ca) > **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](https://papers.ssrn.com/abstract=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 1. **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). 2. **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. 3. **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. 4. **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. 5. **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. 6. **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///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 ```bash 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.pdf ``` Runtime 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 1. 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`). 2. 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. 3. 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. 4. Theory ↔ simulator mapping and H1–H6 in `paper/sections/theory.tex`; pre-analysis plan in `docs/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 ```bibtex @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](mailto: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.lock` instead of `requirements.txt`; results and cache directories are not committed (regenerable).