SPB Git

spb/wp27_uqo

Public

design runner: resume only on identical config hash; prompt decision interval; docstrings

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Oct 27, 2026) parent c1fdf6c Browse files

37 changed files +1,505 −301

37 changed files
Unified Split
modified docs/literature_ledger.md +3 −0
@@ -210,3 +210,6 @@ Claim-to-citation ledger. Every row was verified on 2026-10-27 against Crossref
210 210 | Thurner, Farmer & Geanakoplos — guessed DOI 10.1080/14697688.2011.586336 | Crossref | not found; corrected to 10.1080/14697688.2012.674301 (now `thurner2012`) |
211 211 | Pasquariello — guessed DOI 10.1093/rfs/hhl046 | Crossref | not found; corrected to 10.1093/rfs/hhl010 (now `pasquariello2007`) |
212 212 | Chiarella–Iori–Perelló, Back–Cao–Willard, Foster–Viswanathan, Ladley, Hasbrouck 1991, Kondor 2012 — first-guess DOIs | Crossref | each first guess resolved to a *different* paper; all corrected by Crossref bibliographic query and re-verified (DOIs in table above) |
213 +
214 +
215 +| 144 | feedback-delong | Positive-feedback traders can destabilize prices and rational speculators may amplify rather than offset them | delong1990 | 10.1111/j.1540-6261.1990.tb03695.x | 1990 | Crossref (title/authors/venue matched) | 2026-10-27 |
modified docs/research_log.md +1 −0
@@ -17,6 +17,7 @@ between the pre-analysis plan and the confirmatory runs, so that nothing is revi
17 17 | 10 | 21:10 | 1→2 | LLM slots inherit the replaced slot's information set (`llm.informed_share: null`); within-tick shuffle moved to its own RNG stream | methodologist audit: treatment bundled information; activations diverged across arms | matched-pair CRN now exact for every non-replaced agent |
18 18 | 11 | 21:12 | — | First git commit; pilot and confirmatory designs launched on the frozen revision | manifests now carry `git_revision` | **freeze** |
19 19 | 12 | — | 5 | Pilot (3 seeds × {5, 25, 50 %}, gpt-5.4-nano) runs concurrently with the confirmatory design on the same revision; pilot seeds (1–3) are never pooled with confirmatory seeds (201–207) | time budget | the pilot's role is reduced to cost/validity/variance reporting; the seed count (7) was fixed by the USD cap, not by the pilot variance |
20 +| 13 | 22:05 | 6 | **Override bug**: `deep_update` merged leaf dictionaries, so `provider_mix: {openai: 1.0}` on the baseline `{mock: 1.0}` became a 50/50 mock/OpenAI mixture in every live cell (caught by a co-author reading run manifests). Leaf dicts now replace; regression test added; all live and classical designs discarded and relaunched on commit c1fdf6c | half of the "LLM" slots were the mock rule | **new freeze** (c1fdf6c); nothing from the discarded runs is used |
20 21
21 22 Known deviations from `docs/identification.md` / `docs/protocol.md` (written before items 8–12):
22 23 - Noisy dividends (`jump_prob = 1`) were proposed to make V^pub ≠ V; item 8 achieves this through the timing of information instead (no jumps in the baseline).
modified paper/appendices/hyperparameters.tex +243 −1
@@ -1 +1,243 @@
1 −% hyperparameters — to be written
1 +\section{Full hyperparameters}
2 +\label{app:hyperparameters}
3 +
4 +Table~\ref{tab:hp_baseline} lists every parameter of \code{configs/baseline.yaml} together with the defaults of the configuration schema (\code{experiments/config.py}) that the file does not set explicitly. Tables~\ref{tab:hp_classical}--\ref{tab:hp_pilot} list the overrides of every cell of the four design files; a cell inherits everything else from the baseline. Table~\ref{tab:hp_models} summarises the model cards (\code{docs/model\_cards.md}).
5 +
6 +\begin{small}
7 +\begin{longtable}{@{}p{5.0cm} p{3.4cm} p{6.6cm}@{}}
8 +\caption{Baseline configuration (\code{configs/baseline.yaml}; schema defaults in italics)}
9 +\label{tab:hp_baseline}\\
10 +\toprule
11 +Parameter & Value & Meaning \\
12 +\midrule
13 +\endfirsthead
14 +\toprule
15 +Parameter & Value & Meaning \\
16 +\midrule
17 +\endhead
18 +\bottomrule
19 +\endfoot
20 +\multicolumn{3}{@{}l}{\emph{Run}}\\
21 +\code{name} & baseline & run label (design/cell in designs) \\
22 +\code{seed} & 1 & master seed of all streams \\
23 +\code{n\_ticks} & 600 & ticks per run \\
24 +\code{warmup} & 100 & ticks excluded from outcomes; LLM decisions start at \code{warmup}/2 \\
25 +\multicolumn{3}{@{}l}{\emph{assets}}\\
26 +\code{n\_assets} & 1 & number of risky assets \\
27 +\code{tick\_cents} & 1 & tick size in cents \\
28 +\code{initial\_price\_cents} & 20000 & reference price $=\mu/r$ (200.00) \\
29 +\code{price\_band\_pct} & 0.5 & limit prices farther than $\pm50\%$ from the midpoint are rejected \\
30 +\code{max\_order\_qty} & 10000 & maximum lots per order at the exchange \\
31 +\multicolumn{3}{@{}l}{\emph{fundamentals}}\\
32 +\code{div\_interval} & 20 & ticks between dividends ($\Delta$) \\
33 +\code{r\_period} & 0.0025 & discount rate per dividend period ($r$) \\
34 +\code{mu} & [50.0] & mean dividend in cents, regime 0, per asset ($\mu$) \\
35 +\code{mu\_shift} & 0.0 & regime-1 mean $=\mu(1+\text{mu\_shift})$; 0 disables regimes \\
36 +\code{p\_switch} & 0.0 & per-period regime switch probability \\
37 +\code{rho} & 0.98 & persistence of the deviation ($\rho$) \\
38 +\code{sigma} & 2.0 & innovation standard deviation in cents ($\sigma$) \\
39 +\code{jump\_prob} & 0.0 & per-period jump probability (dividend noise) \\
40 +\code{jump\_sigma} & 0.0 & jump standard deviation in cents \\
41 +\code{factor\_loading} & [0.0] & loading $b_k$ on the common factor \\
42 +\code{x0} & 0.0 & initial revealed deviation \\
43 +\multicolumn{3}{@{}l}{\emph{news}}\\
44 +\code{rate} & 0.02 & per-tick probability of a public item per asset \\
45 +\code{noise\_sd} & 1.0 & stated noise of credible items (cents) \\
46 +\code{misleading\_share} & 0.0 & share of biased items \\
47 +\code{misleading\_bias} & 10.0 & bias of a misleading item (cents, random sign) \\
48 +\code{window} & 20 & ticks of news visible in the observation \\
49 +\multicolumn{3}{@{}l}{\emph{exchange}}\\
50 +\code{maker\_fee\_bps} & 0.0 & maker fee (negative = rebate) \\
51 +\code{taker\_fee\_bps} & 2.0 & taker fee in basis points of notional \\
52 +\code{min\_latency} & 0.05 & floor on per-agent latency (ticks) \\
53 +\code{halt\_threshold} & null & circuit breaker off (treatment: 0.05) \\
54 +\code{halt\_window} & 10 & ticks over which the move is measured \\
55 +\code{halt\_duration} & 20 & halt length in ticks \\
56 +\code{halt\_cancel\_resting} & true & cancel resting orders at a halt \\
57 +\code{allow\_short} & false & short selling switch (inert: every account has \code{short\_allowed=false}) \\
58 +\code{max\_short\_lots} & 0 & short limit when shorting is allowed \\
59 +\code{margin\_check} & true & end-of-tick margin and bankruptcy check \\
60 +\code{liquidation\_fraction} & 0.5 & share of each position sold on a margin call \\
61 +\code{order\_ttl} & 50 & resting orders expire after this many ticks \\
62 +\code{position\_limit} & \emph{null} & max $|$position$|$ in lots (treatment: 300) \\
63 +\code{call\_auction\_interval} & \emph{null} & batch clearing every $k$ ticks (treatment: 5); collar 0.02 fixed in \code{ExchangeConfig} \\
64 +\multicolumn{3}{@{}l}{\emph{population}}\\
65 +\code{n\_traders} & 100 & trader slots ($N$) \\
66 +\code{mix} & fund.\ 0.25, trend 0.06, meanrev 0.10, noise 0.50, MM 0.05, statarb 0.0, leveraged 0.04 & shares of slots by policy (calibrated 2026-10-27) \\
67 +\code{activity} & fund.\ 0.20, trend 0.20, meanrev 0.20, noise 0.30, MM 0.5, statarb 0.2, leveraged 0.2 & per-tick activation probability \\
68 +\code{policy\_kwargs} & fundamentalist \{aggressiveness: 20.0\}; noise \{p\_market: 0.6\}; market\_maker \{quote\_qty: 20\} & calibrated policy parameters (others at code defaults, Table~\ref{tab:policies}) \\
69 +\code{llm\_share} & 0.0 & fraction of slots controlled by the LLM policy ($\varphi$) \\
70 +\code{llm\_replace} & proportional & stratified replacement across types (or one policy name) \\
71 +\code{llm\_keep\_market\_makers} & true & market makers are never replaced \\
72 +\code{informed\_share} & 0.3 & share of fundamentalists receiving private signals \\
73 +\code{precision} & 2.0 & signal precision $\pi$ of informed fundamentalists \\
74 +\code{leverage\_cap} & 3.0 & gross exposure / equity cap of leveraged slots ($L$) \\
75 +\code{maintenance\_margin} & 0.25 & margin-call threshold on equity / gross ($m$) \\
76 +\code{endowment.cash\_cents} & 2000000 & base cash (20,000.00) \\
77 +\code{endowment.shares} & 100 & base lots per asset \\
78 +\code{endowment.wealth\_dispersion} & 0.3 & lognormal sd of the wealth multiplier \\
79 +\code{latency.min}, \code{latency.max} & 0.05, 1.0 & uniform latency range (ticks) \\
80 +\code{latency.llm\_latency} & null & matched-latency override for LLM slots \\
81 +\multicolumn{3}{@{}l}{\emph{population.llm}}\\
82 +\code{provider\_mix} & \{mock: 1.0\} & provider $\to$ share of LLM slots \\
83 +\code{models} & mock: mock-v1; openai: gpt-5.4-nano; anthropic: claude-haiku-5-5 & model id per provider alias \\
84 +\code{prompt\_set} & single & single (generalist) or diverse (8 personas) \\
85 +\code{prompt\_version} & v1 & frozen prompt version \\
86 +\code{memory} & 0 & past own decisions shown (treatment: 5) \\
87 +\code{channel} & false & cohort communication channel \\
88 +\code{cohort\_size} & 5 & cohort size and channel length \\
89 +\code{decision\_interval} & 20 & ticks between LLM decisions \\
90 +\code{stagger}, \code{stagger\_slots} & true, 4 & offsets $\{0,5,10,15\}$ \\
91 +\code{informed\_share} & null & inherit the replaced slot's information (a number overrides) \\
92 +\code{precision} & 2.0 & precision used when \code{informed\_share} is a number \\
93 +\code{max\_order\_qty} & 50 & cap on LLM order size (lots) \\
94 +\code{reasoning\_effort} & low & OpenAI \code{reasoning.effort} / Anthropic \code{effort} \\
95 +\code{max\_output\_tokens} & 1200 & output cap per call (includes reasoning tokens) \\
96 +\code{concurrency} & 8 & parallel requests per decision batch \\
97 +\code{usd\_cap} & 5.0 & hard USD cap per run \\
98 +\code{cache} & true & reuse identical (provider, model, prompt) decisions \\
99 +\code{temperature} & \emph{null} & never sent to gpt-5 / o-series models \\
100 +\code{timeout\_s}, \code{max\_retries} & \emph{60.0, 3} & client timeout and retries \\
101 +\code{token\_cap}, \code{decision\_cap} & \emph{null, null} & optional total-token and per-agent decision caps \\
102 +\code{frozen\_library} & \emph{null} & path of the recorded decision library (provider \code{library}) \\
103 +\code{adaptive\_prompt} & \emph{false} & P\&L feedback line in the prompt \\
104 +\code{price\_assumptions} & \emph{Table~\ref{tab:hp_models}} & USD per 1M tokens by model id \\
105 +\multicolumn{3}{@{}l}{\emph{shocks}}\\
106 +shock 1 & \{time: 300, kind: fundamental\_jump, size: 10.0\} & $+10$ cents on the latent deviation ($+2.2\%$ of $V$) \\
107 +shock 2 & \{time: 450, kind: placebo\} & logged, no effect \\
108 +\multicolumn{3}{@{}l}{\emph{output}}\\
109 +\code{events} & false & write the full event log \\
110 +\code{trades}, \code{llm\_log} & true, true & write trade and LLM-decision frames \\
111 +\code{lookback} & 60 & ticks of midpoint history in the observation \\
112 +\end{longtable}
113 +\end{small}
114 +
115 +\begin{small}
116 +\begin{longtable}{@{}p{4.3cm} p{10.7cm}@{}}
117 +\caption{Cells of \code{configs/classical\_confirmatory.yaml} (seeds 101--130; design overrides: \code{provider\_mix} \{mock: 1.0\}, \code{usd\_cap} 0.0)}
118 +\label{tab:hp_classical}\\
119 +\toprule
120 +Cell & Overrides (dotted paths relative to the baseline) \\
121 +\midrule
122 +\endfirsthead
123 +\toprule
124 +Cell & Overrides \\
125 +\midrule
126 +\endhead
127 +\bottomrule
128 +\endfoot
129 +control & none \\
130 +placebo\_shock & shocks: placebo at 300 and at 450 (no jump) \\
131 +trend\_heavy & population.mix: trend 0.16, noise 0.40 (others as baseline) \\
132 +leverage\_heavy & population.mix: leveraged 0.19, noise 0.35 \\
133 +leverage\_heavy\_funding\_shock & population.mix as leverage\_heavy; shocks: fundamental\_jump $-10.0$ at 300, funding 0.5 at 320 \\
134 +liquidity\_shock & shocks: fundamental\_jump 10.0 at 300, liquidity at 300, liquidity\_end at 360 \\
135 +info\_low & population.precision 0.5 \\
136 +info\_high & population.precision 8.0 \\
137 +misleading\_news & news.misleading\_share 0.5 \\
138 +breaker\_5 & exchange.halt\_threshold 0.05 \\
139 +poslimit\_300 & exchange.position\_limit 300 \\
140 +auction\_5 & exchange.call\_auction\_interval 5 \\
141 +mock\_llm\_25\_indep & population.llm\_share 0.25 (mock provider) \\
142 +mock\_llm\_50\_indep & population.llm\_share 0.50 (mock provider) \\
143 +k2\_control & assets.n\_assets 2; fundamentals.mu [50, 50]; factor\_loading [0.7, 0.7]; population.mix: noise 0.45, statarb 0.05 \\
144 +k2\_leverage & as k2\_control with leveraged 0.19, noise 0.30; shocks: fundamental\_jump $-10.0$ on asset 0 at 300, funding 0.5 at 320 \\
145 +\end{longtable}
146 +\end{small}
147 +
148 +\begin{small}
149 +\begin{longtable}{@{}p{4.3cm} p{10.7cm}@{}}
150 +\caption{Cells of \code{configs/confirmatory\_openai.yaml} (seeds 201--207; design overrides: \code{provider\_mix} \{openai: 1.0\}, \code{models} \{openai: gpt-5.4-nano, openai\_mini: gpt-5.4-mini, mock: mock-v1, anthropic: claude-haiku-5-5\}, \code{usd\_cap} 2.0, \code{concurrency} 8)}
151 +\label{tab:hp_confirmatory}\\
152 +\toprule
153 +Cell & Overrides \\
154 +\midrule
155 +\endfirsthead
156 +\toprule
157 +Cell & Overrides \\
158 +\midrule
159 +\endhead
160 +\bottomrule
161 +\endfoot
162 +dose\_000 / 005 / 010 / 025 / 050 / 100 & population.llm\_share 0.00 / 0.05 / 0.10 / 0.25 / 0.50 / 1.00 (single generalist prompt, gpt-5.4-nano) \\
163 +div\_prompts\_025 & llm\_share 0.25; llm.prompt\_set diverse \\
164 +div\_models\_025 & llm\_share 0.25; llm.prompt\_set diverse; llm.provider\_mix \{openai: 0.5, openai\_mini: 0.5\} \\
165 +memory\_025 & llm\_share 0.25; llm.memory 5 \\
166 +channel\_025 & llm\_share 0.25; llm.channel true \\
167 +adaptive\_025 & llm\_share 0.25; llm.adaptive\_prompt true \\
168 +leverage\_025 & llm\_share 0.25; population.mix: leveraged 0.19, noise 0.35 \\
169 +breaker\_025 & llm\_share 0.25; exchange.halt\_threshold 0.05 \\
170 +poslimit\_025 & llm\_share 0.25; exchange.position\_limit 300 \\
171 +fair\_mini\_005 & llm\_share 0.05; llm.provider\_mix \{openai\_mini: 1.0\} (gpt-5.4-mini; equal-USD comparison with dose\_025) \\
172 +fair\_latency\_025 & llm\_share 0.25; population.latency.llm\_latency 0.05 \\
173 +\end{longtable}
174 +\end{small}
175 +
176 +\begin{small}
177 +\begin{longtable}{@{}p{4.3cm} p{10.7cm}@{}}
178 +\caption{Cells of \code{configs/exploratory.yaml} (mock dry run, seeds 1--3; design overrides: \code{provider\_mix} \{mock: 1.0\}, \code{usd\_cap} 0.0)}
179 +\label{tab:hp_exploratory}\\
180 +\toprule
181 +Cell & Overrides \\
182 +\midrule
183 +\endfirsthead
184 +\toprule
185 +Cell & Overrides \\
186 +\midrule
187 +\endhead
188 +\bottomrule
189 +\endfoot
190 +share\_0 / 5 / 25 / 50 & population.llm\_share 0.0 / 0.05 / 0.25 / 0.50 \\
191 +diverse\_25 & llm\_share 0.25; llm.prompt\_set diverse \\
192 +memory\_25 & llm\_share 0.25; llm.memory 5 \\
193 +channel\_25 & llm\_share 0.25; llm.channel true \\
194 +leverage\_25 & llm\_share 0.25; population.mix: leveraged 0.19, noise 0.35 \\
195 +breaker\_25 & llm\_share 0.25; exchange.halt\_threshold 0.05 \\
196 +poslimit\_25 & llm\_share 0.25; exchange.position\_limit 300 \\
197 +auction\_25 & llm\_share 0.25; exchange.call\_auction\_interval 5 \\
198 +k2\_25 & llm\_share 0.25; assets.n\_assets 2; fundamentals.mu [50, 50]; factor\_loading [0.7, 0.7]; population.mix: noise 0.45, statarb 0.05 \\
199 +\end{longtable}
200 +\end{small}
201 +
202 +\begin{small}
203 +\begin{longtable}{@{}p{4.3cm} p{10.7cm}@{}}
204 +\caption{Cells of \code{configs/pilot\_openai.yaml} (seeds 1--3; design overrides: \code{provider\_mix} \{openai: 1.0\}, \code{models} \{openai: gpt-5.4-nano, mock: mock-v1, anthropic: claude-haiku-5-5\}, \code{usd\_cap} 1.5, \code{concurrency} 8)}
205 +\label{tab:hp_pilot}\\
206 +\toprule
207 +Cell & Overrides \\
208 +\midrule
209 +\endfirsthead
210 +\toprule
211 +Cell & Overrides \\
212 +\midrule
213 +\endhead
214 +\bottomrule
215 +\endfoot
216 +share\_5 / 25 / 50 & population.llm\_share 0.05 / 0.25 / 0.50 (gpt-5.4-nano, single prompt) \\
217 +\end{longtable}
218 +\end{small}
219 +
220 +\begin{table}[H]
221 +\centering
222 +\caption{Model cards summary (prices in USD per 1M tokens: input / cached input / output; source: provider pricing pages fetched 2026-10-27 for OpenAI, Anthropic's published table; identical to \code{LLMCfg.price\_assumptions})}
223 +\label{tab:hp_models}
224 +\footnotesize
225 +\begin{tabular}{@{}p{2.7cm} p{2.6cm} p{2.3cm} p{1.6cm} p{5.6cm}@{}}
226 +\toprule
227 +Model id & Price & Reasoning effort & Output cap & Status / role \\
228 +\midrule
229 +gpt-5.4-nano & 0.20 / 0.02 / 1.25 & low & 1,200 & live; primary model (all dose, mechanism, safeguard and fairness cells; library source) \\
230 +gpt-5.4-mini & 0.75 / 0.075 / 4.50 & low & 1,200 & live-capable; two-model mix and equal-USD cells \\
231 +gpt-5-nano & 0.05 / 0.005 / 0.40 & low & 1,200 & priced; optional replication, not planned \\
232 +gpt-5-mini & 0.25 / 0.025 / 2.00 & low & 1,200 & priced only \\
233 +gpt-4.1-nano & 0.10 / 0.025 / 0.40 & n/a & 1,200 & priced only \\
234 +gpt-4.1-mini & 0.40 / 0.10 / 1.60 & n/a & 1,200 & priced only \\
235 +claude-haiku-5-5 & 0.10 / 0.01 / 0.50 & low (\code{effort}) & 1,200 & blocked (Anthropic key rejected, HTTP 401); mock dry run only \\
236 +claude-sonnet-5-5 & 2.00 / 0.20 / 10.00 & low & 1,200 & blocked; mock dry run only \\
237 +mock-v1 & 0 / 0 / 0 & n/a & n/a & mock (seeded value rule, 5\% invalid); not evidence about LLMs \\
238 +library (knn-replay) & 0 & n/a & n/a & frozen nearest-neighbour replay of recorded decisions; non-adaptive; reported separately \\
239 +\bottomrule
240 +\end{tabular}
241 +\end{table}
242 +
243 +\noindent The Anthropic adapter sends \code{max\_tokens} $=\max(\texttt{max\_output\_tokens},256)$ and \code{output\_config.effort}. Reasoning-token accounting exists only in the OpenAI adapter; the Anthropic adapter reports input, cached-input and output tokens. An unknown model id is priced at the most expensive known output price. Every manifest stores the price table in force when the run was made.
modified paper/appendices/prompts.tex +115 −1
@@ -1 +1,115 @@
1 −% prompts — to be written
1 +\section{Prompts}
2 +\label{app:prompts}
3 +
4 +This appendix reproduces verbatim the frozen prompt version \code{v1} of \code{llm/prompting.py}, one rendered example, the JSON schema sent to the providers, and the validation rules of \code{llm/policy.py}. The system prompt is the persona sentence followed by the rules block; the user message is a deterministic rendering of the agent's \code{Observation}. Nothing in either text depends on the latent state.
5 +
6 +\lstset{literate={≈}{{$\approx$ }}2}
7 +
8 +\subsection*{Personas (\code{ROLE\_PERSONAS})}
9 +The single-prompt design uses \code{generalist} for every LLM slot; the diverse-prompt design assigns the eight personas cyclically in slot order.
10 +
11 +\begin{lstlisting}
12 +generalist : You are a professional trader managing your own account in a single-stock electronic market.
13 +value : You are a value investor: you compare the price to the present value of expected dividends and trade mispricings patiently.
14 +momentum : You are a momentum trader: you follow recent price trends and cut positions when the trend reverses.
15 +contrarian : You are a contrarian trader: you fade sharp moves that are not justified by dividend news.
16 +liquidity : You are a liquidity provider: you prefer resting limit orders near the best quotes and keep your inventory small.
17 +risk_averse: You are a risk-averse trader: you keep positions small, avoid market orders and prefer to hold when uncertain.
18 +macro : You are a news-driven trader: you weigh public announcements and dividend surprises and act when the market has not yet reacted.
19 +quant : You are a quantitative trader: you reason about expected value per share, spread costs and inventory before each order.
20 +\end{lstlisting}
21 +
22 +\subsection*{Rules template (\code{RULES})}
23 +Placeholders in braces are filled from the configuration and the observation (\code{div\_interval}, \code{mu}, \code{r\_pct}, \code{pv\_mean}, \code{rho}, \code{tick}, \code{taker\_bps}, \code{ttl}, \code{max\_qty}, \code{interval}); doubled braces are Python format escapes and render as single braces.
24 +
25 +\begin{lstlisting}
26 +Market mechanics (read carefully):
27 +- One risky stock paying a cash dividend every {div_interval} ticks; the long-run mean dividend is {mu:.2f} per share per period and the per-period discount rate is {r_pct:.2f}%, so a share paying the mean dividend forever is worth about {pv_mean:.2f}.
28 +- Dividends deviate persistently from the mean (autocorrelation {rho:.2f} per period). The deviation of the NEXT dividend is not yet announced; public news and, when you have one, your private signal are noisy estimates of it (in cents). The present value of a share rises by about 1/(1+r-rho) ≈ 44 cents per cent of next-dividend deviation.
29 +- Prices are in currency units with a tick of {tick:.2f}. Limit orders rest in the book; market orders execute immediately against the best quotes (taker fee {taker_bps:.1f} bps). Orders may be partially filled. Unfilled limit orders expire after {ttl} ticks.
30 +- You cannot short sell and cannot spend more than your free cash. Maximum order size: {max_qty} shares.
31 +- You decide every {interval} ticks. Between decisions the market keeps trading.
32 +
33 +Respond with ONLY a JSON object with exactly these keys:
34 +{{"action": "hold"|"place"|"cancel", "side": "buy"|"sell"|null, "order_type": "limit"|"market"|null, "quantity": integer|null, "limit_price": number|null, "cancel_order_ids": [integers], "confidence": number in [0,1]|null, "rationale": "one sentence, max 200 characters"}}
35 +"place" requires side, order_type and quantity (and limit_price for a limit order). "cancel" requires cancel_order_ids. Anything else is treated as hold.
36 +\end{lstlisting}
37 +
38 +\noindent The placeholder \code{interval} is filled with the dividend interval (\code{obs.div\_interval}), not with \code{decision\_interval}; the two coincide (20) in every reported design.
39 +
40 +\subsection*{Rendered example}
41 +The following system prompt and user message were produced by running the prompt builder on the observation of an informed LLM slot (agent~9, persona \code{generalist}, tick~110) in a mock run of the baseline configuration with $\varphi=0.25$, seed~1.\footnote{Generated with \code{system\_prompt()} and \code{user\_message()} of \code{llm/prompting.py}; the observation was captured from \code{Simulation.\_observe}. The mid-price history is sub-sampled to every sixth tick of the 61-tick window.}
42 +
43 +\begin{lstlisting}
44 +You are a professional trader managing your own account in a single-stock electronic market.
45 +
46 +Market mechanics (read carefully):
47 +- One risky stock paying a cash dividend every 20 ticks; the long-run mean dividend is 0.50 per share per period and the per-period discount rate is 0.25%, so a share paying the mean dividend forever is worth about 200.00.
48 +- Dividends deviate persistently from the mean (autocorrelation 0.98 per period). The deviation of the NEXT dividend is not yet announced; public news and, when you have one, your private signal are noisy estimates of it (in cents). The present value of a share rises by about 1/(1+r-rho) ≈ 44 cents per cent of next-dividend deviation.
49 +- Prices are in currency units with a tick of 0.01. Limit orders rest in the book; market orders execute immediately against the best quotes (taker fee 2.0 bps). Orders may be partially filled. Unfilled limit orders expire after 50 ticks.
50 +- You cannot short sell and cannot spend more than your free cash. Maximum order size: 50 shares.
51 +- You decide every 20 ticks. Between decisions the market keeps trading.
52 +
53 +Respond with ONLY a JSON object with exactly these keys:
54 +{"action": "hold"|"place"|"cancel", "side": "buy"|"sell"|null, "order_type": "limit"|"market"|null, "quantity": integer|null, "limit_price": number|null, "cancel_order_ids": [integers], "confidence": number in [0,1]|null, "rationale": "one sentence, max 200 characters"}
55 +"place" requires side, order_type and quantity (and limit_price for a limit order). "cancel" requires cancel_order_ids. Anything else is treated as hold.
56 +\end{lstlisting}
57 +
58 +\begin{lstlisting}
59 +Time: tick 110. Next dividend in 10 ticks.
60 +Best bid 200.80 / best ask 201.12 / mid 200.96 / last trade 201.12.
61 +Bid depth (price x shares): 200.80x14, 200.70x28, 200.69x13, 200.68x14, 200.65x12. Ask depth: 201.12x14, 201.15x4, 201.19x8, 201.20x14, 201.23x4.
62 +Mid-price history (oldest to newest, 61 ticks sampled): 200.20, 200.29, 200.48, 200.09, 200.54, 200.66, 200.64, 200.64, 200.60, 200.73, 200.96.
63 +Change over the window: +0.38%. Recent volatility (std of tick returns): 0.042%.
64 +Last 30 trades: volume 168 shares, 57% buyer-initiated.
65 +Last dividend paid: 0.55 per share. Public estimate of the next dividend's deviation from its mean: public estimate +5.0 cents. Present value of expected dividends using only public information: 202.23.
66 +Your private signal: next dividend deviation estimated at +3.6 cents (signal precision 2.0; higher is more reliable).
67 +Your account: cash 18634.09 (free 18634.09), holdings 92 shares, equity 37122.41.
68 +You have no open orders.
69 +Decide now. Reply with the JSON object only.
70 +\end{lstlisting}
71 +
72 +\noindent\sloppy Optional lines, in the order in which they are inserted when the corresponding treatment is active: \code{TRADING IS HALTED on this stock (orders will be rejected).} after the first line; up to three \code{Public news (tick $t$): \dots} lines after the public-value line; \code{Your open orders: id \dots: buy/sell remaining/qty @ price (age)} (at most six) instead of \code{You have no open orders.}; \code{Feedback: your equity changed by $\pm x$\% since your last decision (cumulative $\pm y$\%).} (adaptive feedback); \code{Your recent decisions and outcomes:} followed by up to five bullet lines (memory); \code{Messages from other traders in your group (unverified opinions):} followed by up to five bullet lines (communication channel).
73 +
74 +\subsection*{JSON schema (\code{DECISION\_JSON\_SCHEMA})}
75 +Sent as \code{text.format} (OpenAI, \code{strict: true}) and \code{output\_config.format} (Anthropic). Every key is required; \code{null} is allowed where indicated; no additional properties.
76 +
77 +\begin{lstlisting}
78 +{
79 + "type": "object",
80 + "properties": {
81 + "action": {"type": "string", "enum": ["hold", "place", "cancel"]},
82 + "side": {"type": ["string", "null"], "enum": ["buy", "sell", null]},
83 + "order_type": {"type": ["string", "null"], "enum": ["limit", "market", null]},
84 + "quantity": {"type": ["integer", "null"]},
85 + "limit_price": {"type": ["number", "null"]},
86 + "cancel_order_ids": {"type": "array", "items": {"type": "integer"}},
87 + "confidence": {"type": ["number", "null"]},
88 + "rationale": {"type": "string"}
89 + },
90 + "required": ["action", "side", "order_type", "quantity", "limit_price",
91 + "cancel_order_ids", "confidence", "rationale"],
92 + "additionalProperties": false
93 +}
94 +\end{lstlisting}
95 +
96 +\subsection*{Parsing and validation rules}
97 +The reply is first parsed, then validated against the pydantic model \code{Decision}, then checked for feasibility by \code{LLMPolicy.to\_action}. Any failure yields a hold action with \code{valid = False} and the reason string below; a provider error or a budget halt yields a hold with the error string as reason.
98 +
99 +\begin{enumerate}\sloppy
100 +\item \textbf{Parsing} (\code{parse\_decision}): whitespace and a surrounding \code{```}/\code{```json} fence are stripped; if the text is not valid JSON, the first \code{\{\dots\}} block is tried; failure $\to$ reason \code{json: <message>}.
101 +\item \textbf{Schema} (\code{Decision}): \code{action} $\in$ \{hold, place, cancel\}; \code{side} $\in$ \{buy, sell, null\}; \code{order\_type} $\in$ \{limit, market, null\}; \code{quantity} integer $\ge 0$ or null; \code{limit\_price} number $>0$ or null (currency units); \code{cancel\_order\_ids} list of integers; \code{confidence} in $[0,1]$ or null; \code{rationale} trimmed to 400 characters; failure $\to$ reason \code{schema: <field>: <message>} (first three errors).
102 +\item \textbf{hold}: always valid; no intent.
103 +\item \textbf{cancel}: the ids are intersected with the agent's open orders (resting and in flight); empty intersection $\to$ \code{cancel: no valid order ids}; otherwise one cancel intent per surviving id.
104 +\item \textbf{place}, in this order:
105 + \begin{enumerate}
106 + \item side, order type and quantity must all be present (\code{place: missing side/order\_type/quantity});
107 + \item quantity $>0$ (\code{place: quantity <= 0}) and $\le$ \code{max\_order\_qty} $=50$ (\code{place: quantity q > max 50});
108 + \item the asset must not be halted (\code{place: market halted});
109 + \item limit orders need a price (\code{place: limit without price}); the price is converted to ticks by $\operatorname{round}(100\,\text{price}/\text{tick\_cents})$ and must be positive and within $\pm20\%$ of the midpoint (\code{place: limit price outside ±20\% of mid});
110 + \item market orders need a non-empty opposite side (\code{place: market order with empty opposite side});
111 + \item buys: $\lfloor \text{ref}\times\text{tick\_cents}\times q\times1.001\rfloor \le$ free cash, where ref is the limit price or else the best ask or midpoint (\code{place: cost c > free cash f});
112 + \item sells: $q$ is clipped to holdings minus the remaining quantity of the agent's resting sells; if nothing is free the decision is invalid (\code{place: no free shares to sell}); a clipped sell is recorded as valid.
113 + \end{enumerate}
114 +\item The surviving intent is tagged \code{llm} and submitted to the exchange, which applies its own checks (price band, cash reservation, position limit, leverage); an exchange rejection is logged in the event log, not in the decision log.
115 +\end{enumerate}
modified paper/appendices/proofs.tex +1 −1
@@ -203,7 +203,7 @@ i.e.\ $\Delta P_0<-b/X$, which fails. For $a>1$ and $b>0$ the same algebra shows
203 203 \emph{does} lie in $R$ whenever $\Delta P_0>-b/X$ (dividing by $1-a<0$ reverses the inequality): a
204 204 second, self-fulfilling fixed point exists in which the cohort is forced to sell because the price has
205 205 fallen enough to exhaust the buffer, and the price has fallen because the cohort sells. This is the
206 −multiplicity familiar from \citet{brunnermeierpedersen2009}. We select the cascade limit, which is
206 +multiplicity familiar from \citet{brunnermeier2009}. We select the cascade limit, which is
207 207 $\Delta P_0$; the simulator's forced-liquidation rule is likewise sequential and cannot jump to the
208 208 self-fulfilling point.
209 209
modified paper/appendices/pseudocode.tex +152 −1
@@ -1 +1,152 @@
1 −% pseudocode — to be written
1 +\section{Pseudocode}
2 +\label{app:pseudocode}
3 +
4 +The five algorithms below follow the control flow of the code line by line; names in typewriter font are the corresponding functions. $\Delta$ is the dividend interval, $T$ the number of ticks.
5 +
6 +\begin{algorithm}[H]
7 +\caption{Tick loop (\code{Simulation.run}, \code{experiments/runner.py})}
8 +\label{alg:tick}
9 +\footnotesize\begin{algorithmic}[1]
10 +\State build streams from the master seed; draw fundamentals, population, accounts, latencies; register accounts
11 +\State $\text{pending\_liq} \gets \emptyset$; record $P_0$
12 +\For{$t = 0,\dots,T-1$}
13 + \State $\text{exchange.time} \gets t$
14 + \If{$t>0$ and $t \bmod \Delta = 0$}
15 + \State $D \gets \code{fund.step()}$ \Comment{pay $D_n$, reveal $x_n$, update public filter, draw latent $x_{n+1}$}
16 + \State pay $\operatorname{round}(D_k)$ cents per lot of every asset $k$; check conservation
17 + \EndIf
18 + \State \code{apply\_shocks}$(t)$ \Comment{after the dividend: a jump stays latent until news or the next dividend}
19 + \ForAll{news items drawn this tick} \State update the public filter with the item's value and stated noise \EndFor
20 + \ForAll{$(i,\text{act}) \in \text{pending\_liq}$} \State \code{exchange.submit}$(i,\text{act},t)$ \EndFor; $\text{pending\_liq}\gets\emptyset$
21 + \State $u \gets$ \code{activation} stream, one uniform per slot
22 + \State $A_c \gets \{i \text{ classical}: u_i < \text{activity}_i\}$; shuffle $A_c$ with the \code{shuffle} stream
23 + \State $A_\ell \gets \{i \text{ LLM}: (t-\text{offset}_i) \bmod \text{interval} = 0,\ t \ge \texttt{warmup}/2\}$, minus agents at their decision cap
24 + \State $\text{tw} \gets$ last 30 trades per asset
25 + \ForAll{$i \in A_c$}
26 + \State $o \gets$ \code{observe}$(i,t,\text{tw})$ \Comment{book depth 5, 60-tick mids, trades, dividend, $V^{\mathrm{pub}}$, $\hat x$, news, fresh private signal, account, open orders}
27 + \State $a \gets \text{policy}_i.\code{decide}(o,\text{rng}_i)$; \textbf{if} $a$ is not hold \textbf{then} \code{exchange.submit}$(i,a,t)$
28 + \EndFor
29 + \If{$A_\ell \neq \emptyset$}
30 + \State build observations for all $i\in A_\ell$; update each agent's equity-change history
31 + \State $(a_i, \text{rec}_i)_{i\in A_\ell} \gets$ \code{llm\_policy.decide\_batch} \Comment{Algorithm~\ref{alg:llm}, thread pool of width 8}
32 + \ForAll{$i \in A_\ell$} \State submit $a_i$ if not hold; update memory and cohort channel from $\text{rec}_i$ \EndFor
33 + \EndIf
34 + \State \code{exchange.process\_until}$(t+1)$ \Comment{arrivals with time $<t+1$ in (arrival, sequence) order: Algorithm~\ref{alg:order}}
35 + \State $\text{pending\_liq} \gets$ \code{exchange.margin\_check()} \Comment{Algorithm~\ref{alg:margin}}
36 + \State \code{exchange.end\_of\_tick}$(t)$ \Comment{call auction if enabled and $(t+1)\bmod k=0$; append midpoint; expire orders with $t-\text{submit}\ge$ TTL; log tick}
37 + \State record $P_{t+1}$ and the tick row (mid, quotes, depth, volume, LLM volume, $V_t$, $V^{\mathrm{pub}}_t$, $x$, halts, effective spread, total equity)
38 +\EndFor
39 +\State final conservation check; write manifest, frames and outcomes
40 +\end{algorithmic}
41 +\end{algorithm}
42 +
43 +\begin{algorithm}[H]
44 +\caption{Order arrival (\code{Exchange.\_on\_order}, \code{exchange/engine.py})}
45 +\label{alg:order}
46 +\footnotesize\begin{algorithmic}[1]
47 +\Require order $o$ (agent $i$, asset $k$, side, type, $q$, price $p$ or none, tag), account $c_i$, book $B_k$
48 +\State remove $o$ from the agent's in-flight set
49 +\State \textbf{reject} \code{frozen} if $c_i$ is frozen and tag $\ne$ liquidation; \code{halted} if $k$ is halted; \code{bad\_qty} unless $1\le q\le$ max order
50 +\If{limit} \State \textbf{reject} \code{bad\_price} if $p\le0$; \code{price\_band} if $|p-\text{mid}_k| > 0.5\,\text{mid}_k$ \EndIf
51 +\If{buy and limit}
52 + \State $\text{need} \gets p\,q + \max(\text{fee}_{\rm taker},\text{fee}_{\rm maker}) + q$; \textbf{reject} \code{insufficient\_cash} if need $>$ buying power
53 + \State reserve need cents on $c_i$ and on $o$ \Comment{buying power = free cash, or free cash $+(L-1)\,$equity if leveraged}
54 +\ElsIf{buy and market}
55 + \State \textbf{reject} \code{no\_liquidity} if no ask; $q \gets$ largest $q'\le q$ whose notional plus taker fee on the total fits in buying power, walking the asks; \textbf{reject} \code{insufficient\_cash} if $q=0$
56 +\Else \Comment{sell}
57 + \State $h \gets$ free (unreserved) holdings; \textbf{if} $q>h$ \textbf{then} \textbf{reject} \code{insufficient\_shares} if $h\le0$ else $q\gets h$ \Comment{no short selling}
58 + \State \textbf{reject} \code{no\_liquidity} if market and no bid; reserve $q$ lots
59 +\EndIf
60 +\If{position limit set and tag $\ne$ liquidation}
61 + \State \textbf{release and reject} \code{position\_limit} if $|\text{pos}+\text{side}\,(\text{same-side resting}+q)| >$ limit
62 +\EndIf
63 +\If{margin check on, $L_i>1$ and tag $\ne$ liquidation}
64 + \State gross$' \gets$ gross exposure with asset $k$ revalued at the post-trade position and price ($p$ or mid)
65 + \State \textbf{release and reject} \code{leverage\_cap} if equity $\le0$ or gross$' > L_i\,$equity
66 +\EndIf
67 +\State log \code{accept}
68 +\If{call-auction mode}
69 + \State \textbf{if} market \textbf{then} convert to a limit at the band edge, tag \code{ioc}, reserve cash for buys; rest $o$ in $B_k$; \textbf{return}
70 +\EndIf
71 +\State fills $\gets B_k.\code{match}(o)$ \Comment{walk the opposite side while crossing; price--time priority; resting price}
72 +\ForAll{fill $(r, p', q')$}
73 + \State fees: taker $=\operatorname{round}(p'q'\cdot 2/10^4)$ on $o$, maker $=0$ on $r$; move $p'q'$ cents and $q'$ lots; release the reservations consumed (all of it on the last fill); log \code{trade}; update last price and tick volume; check conservation
74 +\EndFor
75 +\If{$o$ has remaining quantity}
76 + \State \textbf{if} limit \textbf{then} rest $o$ in $B_k$ \textbf{else} release the reservation, cancel the remainder (\code{ioc}); count a rejection if nothing filled
77 +\EndIf
78 +\State \code{maybe\_halt}$(k)$: if a breaker is set and $|\text{last}/\text{mid}_{t-10}-1|>$ threshold, halt $k$ for 20 ticks and cancel all resting orders (releasing reservations)
79 +\end{algorithmic}
80 +\end{algorithm}
81 +
82 +\begin{algorithm}[H]
83 +\caption{Margin check and liquidation (\code{Exchange.margin\_check}, \code{\_liquidate}; submitted by the runner at $t+1$)}
84 +\label{alg:margin}
85 +\footnotesize\begin{algorithmic}[1]
86 +\State marks $\gets$ midpoint of every asset (fallback: last trade, reference)
87 +\ForAll{accounts $c$ not frozen}
88 + \State $E \gets$ cash $+\sum_k Q_k\,\text{mark}_k$; $G \gets \sum_k |Q_k|\,\text{mark}_k$; \textbf{continue} if $G\le0$
89 + \If{$E\le0$}
90 + \State freeze $c$; log \code{bankrupt}; $f\gets1$ \Comment{full liquidation; no further orders accepted}
91 + \ElsIf{$L_c>1$ and $E/G < m$ ($m=0.25$)}
92 + \State log \code{margin\_call}; $f\gets$ liquidation fraction ($0.5$)
93 + \Else \State \textbf{continue}
94 + \EndIf
95 + \State act $\gets$ cancel every resting order of $c$; for each asset with $Q_k\ne0$ add a market order for $\max(1,\lfloor f|Q_k|\rfloor)$ lots on the side that reduces $|Q_k|$, tagged \code{liquidation}
96 + \State log \code{liquidate}; append $(c,\text{act})$ to the list returned to the runner
97 +\EndFor
98 +\State \Return list \Comment{the runner submits these at the start of tick $t+1$; liquidation orders bypass the frozen, position-limit and leverage checks}
99 +\end{algorithmic}
100 +\end{algorithm}
101 +
102 +\begin{algorithm}[H]
103 +\caption{LLM decision pipeline (\code{LLMPolicy.decide\_one}, \code{\_call}, \code{to\_action}, \code{llm/policy.py})}
104 +\label{alg:llm}
105 +\footnotesize\begin{algorithmic}[1]
106 +\Require observation $o$, provider $\pi$, model $M$, prompt id $\psi$
107 +\State $S \gets$ \code{system\_prompt}$(\psi, o)$; $U \gets$ \code{user\_message}$(o, \text{adaptive})$ \Comment{Appendix~\ref{app:prompts}}
108 +\If{$\pi=$ library}
109 + \State text $\gets$ recorded decision of a random one of the 5 nearest neighbours of \code{features}$(o)$, limit price rescaled to the midpoint; cached $\gets$ true
110 +\Else
111 + \State key $\gets$ SHA-256$(\pi\,|\,M\,|\,\text{version}\,|\,S\,|\,U)$
112 + \If{cache enabled and key present} \State text $\gets$ cached response; cached $\gets$ true
113 + \ElsIf{frozen-only or halted for budget} \State error $\gets$ \code{frozen\_miss} / \code{budget\_halt}
114 + \ElsIf{budget cap reached} \State halt for budget; error $\gets$ \code{USD cap reached}
115 + \Else
116 + \State result $\gets$ provider$(\pi).\code{complete}(S,U,M,\text{effort}=\text{low},\text{max\_output}=1200,\text{cache\_key}=\code{ma-v1-}\psi)$
117 + \State \textbf{if} text non-empty and no error \textbf{then} store (key, $S$, $U$, text, usage) in the cache
118 + \EndIf
119 +\EndIf
120 +\State cost $\gets$ \code{budget.record}(tokens, latency, cached) \Comment{0 for cached hits; priced from the model table otherwise}
121 +\State \textbf{if} error \textbf{then} dec $\gets$ none \textbf{else} (dec, err) $\gets$ \code{parse\_decision}(text) \Comment{lenient JSON, strict pydantic schema}
122 +\State (action, valid, reason) $\gets$ \code{to\_action}(dec, $o$) if dec else (hold, false, err) \Comment{rules of Appendix~\ref{app:prompts}}
123 +\State count invalid and failures
124 +\State record $(t, i, \pi, M, \psi, \text{valid}, \text{reason}, \text{decision fields}, \text{tokens}, \text{latency}, \text{cost}, \text{cached}, \text{error})$
125 +\Statex \hspace{\algorithmicindent} together with the prompt SHA, the request id, \code{features}$(o)$ and the relative limit price
126 +\State \Return (action with meta \{valid, reason, rationale, confidence, llm\_action, provider, model\}, record)
127 +\end{algorithmic}
128 +\end{algorithm}
129 +
130 +\begin{algorithm}[H]
131 +\caption{Matched-seed design runner (\code{run\_design}, \code{cell\_config}, \code{\_run\_one}, \code{experiments/design.py})}
132 +\label{alg:design}
133 +\footnotesize\begin{algorithmic}[1]
134 +\Require design file with \code{base}, \code{seeds}, design-level \code{overrides}, \code{cells} (name, overrides, treatment) and optional \code{factors}
135 +\State cells $\gets$ listed cells $\cup$ full factorial product of the factor levels (overrides merged, labels joined); keep only \code{--cells} if given
136 +\State seeds $\gets$ explicit list or $\{\text{start},\dots,\text{start}+n-1\}$; truncate to \code{--max-seeds} if given
137 +\ForAll{cell $c$, seed $s$}
138 + \State cfg $\gets$ base configuration; apply design overrides then cell overrides (dotted keys; sections merged, leaf dictionaries replaced); set name $\gets$ design/cell, seed $\gets s$, treatment $\gets$ design labels $\cup$ cell labels
139 + \State job $\gets$ (cfg as JSON, \code{results/runs/<design>/<cell>/seed<s>}, cache path)
140 +\EndFor
141 +\ForAll{jobs, sequentially if workers $\le1$ else in a process pool of the given width}
142 + \If{\code{outcomes.json} exists in the run directory} \State reuse it \Comment{an interrupted design resumes without new API calls}
143 + \Else
144 + \State build the LLM policy (shared SQLite cache) if \code{llm\_share}$>0$; run Algorithm~\ref{alg:tick}; save config, manifest and frames
145 + \State outcomes $\gets$ \code{run\_outcomes}(result); write \code{outcomes.json}
146 + \EndIf
147 +\EndFor
148 +\State write \code{outcomes.parquet} (one row per run) and \code{design.json} (expanded cells, seeds, run count, wall-clock)
149 +\end{algorithmic}
150 +\end{algorithm}
151 +
152 +\noindent Because every cell of a design is run on the same seed list and the only streams touched by $\varphi$ are the LLM assignment and the LLM agents' own generators, the control and each treatment of a given seed share the dividend path, the news, the endowments, latencies and activation draws of every non-replaced slot (common random numbers); the paired estimators of the analysis exploit this matching seed by seed.
modified paper/appendices/reproducibility.tex +29 −1
@@ -1 +1,29 @@
1 −% reproducibility — to be written
1 +\section{Reproducibility and replication package}
2 +\label{app:repro}
3 +
4 +\paragraph{Environment.}
5 +The package requires Python $\ge 3.12$ and is installed with \code{uv sync --extra dev} from the committed lock file \code{uv.lock}; the runs reported here used Python 3.12.13 and uv 0.11.7 on macOS (arm64). Locked versions of the libraries that affect numerical results or provider calls are numpy 2.5.4, pandas 3.1.0, scipy 1.18.1, statsmodels 0.15.0, pyarrow 26.0.0, duckdb 2.0.0, sortedcontainers 2.4.0, pydantic 2.14.1, pyyaml 6.0.3, openai 3.40.0, anthropic 2.3.0, with pytest 9.2.0 and hypothesis 6.170.12 for the test suite. API keys are read from a local \code{.env} file (\code{OPENAI\_API\_KEY}, \code{ANTHROPIC\_API\_KEY}) by the \code{ma} command line and never appear in configurations, manifests or logs.
6 +
7 +\paragraph{One-command replication without an API key.}
8 +\code{make replicate} runs, in order, the test suite (\code{make test}, 24 tests), the smoke test (two 300-tick runs with 100 agents at $\varphi=0$ and $\varphi=0.25$ with the mock provider, accounting verified), the classical confirmatory design (16 cells $\times$ 30 seeds) and the mock exploratory design (12 cells $\times$ 3 seeds), the numerical theory checks, the figures and the tables, writing \code{results/runs/<design>/outcomes.parquet}, \code{results/figures/} and \code{results/tables/}; \code{make paper} copies tables and figures into \code{paper/} and compiles \code{main.tex} with \code{latexmk}. The classical designs take about a minute on six worker processes (\code{W=6}); every number in the classical tables is reproduced bit for bit because those runs are deterministic functions of the seed.
9 +
10 +\paragraph{Paid designs and cost estimates.}\sloppy
11 +\code{make pilot} and \code{make confirmatory} first print the \code{ma budget} estimate and then run the design with the shared decision cache \code{results/cache/llm\_cache.sqlite}. With the planning token profile of 760 input and 230 output tokens per call, \code{ma budget} reports 6,480 calls and 2.85~USD for \code{pilot\_openai.yaml} (3 cells $\times$ 3 seeds, per-run cap 1.5~USD) and 79,380 calls and 38.74~USD for \code{confirmatory\_openai.yaml} (16 cells $\times$ 7 seeds, per-run cap 2.0~USD; 41.65~USD at the default 800/250 profile); the mock designs cost nothing. The realised profile of gpt-5.4-nano at prompt version v1 in the live runs on disk is about 850--870 input tokens (no provider-side cache hits were reported), 320--460 output tokens of which 240--380 are reasoning tokens, 2.5--3.2~s of latency and 0.00057--0.00075~USD per decision, i.e.\ 0.08--0.10~USD per run at $\varphi=0.05$ and about 0.22~USD at $\varphi=0.25$.\footnote{Means over the online gpt-5.4-nano decisions in the \code{llm\_decisions.parquet} frames of the pilot \code{share\_5} runs (seeds 1--3) and of the confirmatory \code{dose\_005}, \code{dose\_010} and \code{dose\_025} runs completed at the time of writing; the planning figure of 0.00044~USD per decision in \code{docs/model\_cards.md} was measured on shorter smoke-test prompts.} The project-wide ceiling for live calls is 40~USD; per-run caps are set at least twice the expected run cost so that no confirmatory run halts for budget.
12 +
13 +\paragraph{Where things live.}
14 +Each run writes \code{results/runs/<design>/<cell>/seed<NNNN>/} containing \code{config.yaml} (the fully resolved configuration), \code{manifest.json}, \code{ticks.parquet} (one row per tick and asset), \code{trades.parquet}, \code{agents.parquet} (one row per slot with policy, replaced type, provider, model, prompt id, cohort, stratum, latency, precision, leverage cap, initial and final wealth, fees, dividends, order counts and the frozen flag), \code{llm\_decisions.parquet} (one row per LLM decision, Section~\ref{sec:implementation}) and \code{outcomes.json}; optionally \code{events.parquet}. The design level adds \code{outcomes.parquet} (one row per run with all outcome metrics and treatment labels) and \code{design.json}. The decision cache is a single SQLite table keyed by the SHA-256 of (provider, model, prompt version, system prompt, user prompt) storing both prompts, the raw response text, the token usage and a timestamp; the frozen library built from the recorded decisions is \code{results/library/gpt-5.4-nano\_single.npz} (feature matrix plus JSON decisions), produced and replayed by \code{scripts/60\_frozen\_library.py}. Calibration artefacts are in \code{results/calibration/} (\code{sweep.parquet}, \code{sweep\_summary.csv}, \code{scale\_benchmark.json}).
15 +
16 +\paragraph{Provider non-determinism.}
17 +OpenAI reasoning models are sampled at the provider and expose no seed; identical prompts can yield different decisions across calls, and the Anthropic API is likewise unseeded. Three mechanisms bound the consequences. First, every online decision is written to the cache before it is used, so a re-run of the same design with the same cache replays every recorded decision exactly, performs zero calls and costs nothing; cache hits are counted separately (\code{cached\_hits} in the manifest, \code{cached\_hit} per decision, \code{llm\_cached\_hits} in the outcomes) so that online and replayed runs are never confused. Second, the request id and the dated model snapshot are stored per decision and the price table per run, so that a provider-side model change is visible. Third, the mock and library providers are fully seeded and serve as the deterministic reference for the pipeline; the frozen library is a non-adaptive replay and its runs are labelled provider \code{library}. The git revision in each manifest carries a \code{-dirty} suffix when the working tree had uncommitted changes at the time of the run; the record of every change made between the pre-analysis plan and the confirmatory runs is kept in \code{docs/research\_log.md}.
18 +
19 +\paragraph{Replication checklist.}
20 +\begin{enumerate}\sloppy
21 +\item Check out the revision named in the manifests of the design to be replicated (\code{git\_revision}, without the \code{-dirty} suffix) and install with \code{uv sync --extra dev}; run \code{make test} (24 tests pass).
22 +\item Run \code{make smoke}; the printed conservation flag is \code{True} and the artefacts appear in \code{results/runs/smoke/}.
23 +\item Run \code{make replicate}; compare \code{results/runs/classical\_confirmatory/outcomes.parquet} and \code{results/runs/exploratory\_mock/outcomes.parquet} with the published copies (identical up to floating-point formatting).
24 +\item For the live designs, place the published \code{results/cache/llm\_cache.sqlite} in \code{results/cache/} and run \code{make pilot} and \code{make confirmatory}: every manifest then reports \code{cached\_hits} equal to the number of calls and zero USD. Without the cache, the same commands reproduce the design with fresh provider samples at the cost printed by \code{ma budget}.
25 +\item Verify that every run directory carries a \code{manifest.json} with \code{config\_hash}, \code{git\_revision}, \code{prompt\_version}, \code{price\_assumptions}, \code{llm\_usage}, \code{shocks} and \code{final\_conservation\_ok}, and that the \code{providers} list of every live cell contains only the providers named in the design file.
26 +\item Regenerate figures and tables with \code{make figures tables}; the analysis scripts read only the parquet frames and never re-simulate. The threshold sensitivity grid (bubble 3/5/8\%, crash 7/10/15\%) is obtained by re-running \code{run\_outcomes} on the saved tick frames.
27 +\item Compile the paper with \code{make paper}; the sections that quote numbers from result files cite those files in footnotes so that each figure can be traced to a frame and a script.
28 +\item Record any deviation from \code{docs/protocol.md} in \code{docs/research\_log.md} with its date and reason before analysing new runs.
29 +\end{enumerate}
modified paper/references.bib +13 −2
@@ -836,7 +836,7 @@
836 836 @article{lillo2004,
837 837 author = {Lillo, Fabrizio and Farmer, J. Doyne},
838 838 title = {The Long Memory of the Efficient Market},
839 − journal = {Studies in Nonlinear Dynamics & Econometrics},
839 + journal = {Studies in Nonlinear Dynamics \& Econometrics},
840 840 year = {2004},
841 841 volume = {8},
842 842 number = {3},
@@ -1136,7 +1136,7 @@
1136 1136 @article{danielsson2022,
1137 1137 author = {Daníelsson, Jón and Macrae, Robert and Uthemann, Andreas},
1138 1138 title = {Artificial intelligence and systemic risk},
1139 − journal = {Journal of Banking & Finance},
1139 + journal = {Journal of Banking \& Finance},
1140 1140 year = {2022},
1141 1141 volume = {140},
1142 1142 pages = {106290},
@@ -1524,3 +1524,14 @@
1524 1524 month = {November},
1525 1525 url = {https://www.fsb.org/uploads/P14112024.pdf}
1526 1526 }
1527 +
1528 +@article{delong1990,
1529 + author = {De Long, J. Bradford and Shleifer, Andrei and Summers, Lawrence H. and Waldmann, Robert J.},
1530 + title = {Positive Feedback Investment Strategies and Destabilizing Rational Speculation},
1531 + journal = {The Journal of Finance},
1532 + year = {1990},
1533 + volume = {45},
1534 + number = {2},
1535 + pages = {379--395},
1536 + doi = {10.1111/j.1540-6261.1990.tb03695.x}
1537 +}
modified paper/sections/identification.tex +291 −1
@@ -1 +1,291 @@
1 −% identification — to be written
1 +% paper/sections/identification.tex — UQO WP 27, "The Invisible Hand of Machines"
2 +% Section: Experimental identification. Mirrors docs/identification.md, docs/protocol.md and the
3 +% deviations recorded in docs/research_log.md. Hypotheses H1–H6 are those of Table~\ref{tab:predictions}.
4 +\section{Experimental identification}
5 +\label{sec:identification}
6 +
7 +The simulator is an experimental apparatus and we describe it as one. Causal language refers to effects
8 +\emph{inside the simulator}: the object identified is the effect of replacing a share of rule-based
9 +trader slots by LLM-controlled slots in this market mechanism, with these model snapshots and these
10 +prompts.
11 +
12 +\subsection{Unit of observation and randomization}
13 +\label{sec:identification:unit}
14 +
15 +The unit of observation is one run: $N=100$ trader slots simulated for 600 ticks, with outcomes
16 +computed on $t\ge100$. A run is indexed by a seed $s$ and a cell $z$, a vector of design settings---the
17 +LLM share $\phi$ (\code{llm\_share}), the provider and model mix, the prompt set, memory, the
18 +communication channel, the leverage composition, the safeguards and the fairness regime---and $Y_s(z)$
19 +is the potential outcome of seed $s$ under cell $z$. All exogenous randomness derives from a master
20 +seed through independent named streams: \code{fundamentals} (dividend path), \code{news}, \code{slots}
21 +(wealth multipliers, latencies, informed draws), \code{assign} (the classical policy of each slot),
22 +\code{llm\_assign} (which eligible slots become LLM-controlled, their provider and decision offsets),
23 +\code{activation}, \code{tiebreak}, \code{shuffle} (within-tick processing order), and one generator
24 +per agent for policy randomness and private-signal noise. Slot assignment is stratified: slots are
25 +ranked by wealth multiplier and the policy types are dealt down the ranking, so each wealth tercile
26 +receives the same policy composition. The treatment replaces a fraction $\phi$ of eligible slots with
27 +proportional quotas within each policy type; market makers are never replaced, so liquidity supply is
28 +held fixed and $\phi=1$ means 95 of 100 slots. A replaced slot keeps its cash, shares, latency
29 +and---after the change described in Section~\ref{sec:identification:changes}---its information set: an
30 +LLM agent is informed, at the same precision, if and only if the slot it replaces was. The treatment
31 +changes the decision policy of a stratified subset of slots and nothing else.
32 +
33 +In a matched pair $\{(s,0),(s,z)\}$ the dividend path and latent value, the news, the shock at $t=300$
34 +and the placebo at $t=450$, every endowment, latency and informed status, the policy of every
35 +non-replaced slot, the activation and tie-break draws, and the random numbers of every non-replaced
36 +agent are identical by construction. What is \emph{not} held fixed is what the treatment is meant to
37 +change: the decisions of the LLM slots, including the provider's sampling noise, which is not seedable
38 +and is treated as part of the treatment, and the market's reaction to them---the book, the fills of
39 +every other agent, and hence the subsequent decisions of the rule-based agents, which condition on the
40 +book. Common random numbers remove the variance of the environment from the paired difference, not
41 +the equilibrium response of the market.
42 +
43 +\subsection{Causal structure}
44 +\label{sec:identification:dag}
45 +
46 +The cell label sets the treatment $Z$; with the \code{llm\_assign} stream and the provider noise, $Z$
47 +determines the LLM slot set and its policies. Those policies generate, inside the run, a set of
48 +mediators $M$---pairwise order-flow correlation among LLM agents, synchrony, aggressiveness, validity
49 +rate and realized dose---and outcomes $Y$ are produced jointly by $M$, by the exogenous streams, and by
50 +a direct path from $Z$ that bypasses LLM behaviour (fewer heuristic agents, halts, position limits). The
51 +effect of $Z$ on $Y$ is identified because $Z$ is set by the design file and the only random input
52 +that depends on it, \code{llm\_assign}, is independent of every other stream by construction; the
53 +effect of $Z$ on each mediator is identified for the same reason, which is how H2 and H5 are tested.
54 +Mediation is \emph{not} identified: $M$ and $Y$ are generated by the same path of draws and provider
55 +noise, so sequential ignorability---independence of $M$ and the potential outcomes given $Z$---fails;
56 +a volatile fundamental path raises both the correlation of LLM orders and the pricing error. We report
57 +mediation only as a descriptive decomposition and treat each mediator as an outcome. $Z$ also bundles
58 +model, prompt, decision frequency and the rule that invalid decisions become holds; the estimand is
59 +intention-to-treat, ``deploy this agent as configured''.
60 +
61 +\subsection{Estimands}
62 +\label{sec:identification:estimands}
63 +
64 +Let $\tau_z=\E_s[Y_s(z)-Y_s(0)]$ be the paired average treatment effect of cell $z$ against the control
65 +of the same seed. In order of priority: (E1) $\tau_z$ at each share against $\phi=0$, the primary
66 +confirmatory contrast being $\phi=0.25$ with \code{gpt-5.4-nano} and the single prompt, $\phi=0.50$ the
67 +secondary; (E2) the dose--response $\tau(\phi)$ over $\phi\in\{0.05,0.10,0.25,0.50,1.00\}$, summarized
68 +by a linear slope and an ordered trend test; (E3) diversity at equal share,
69 +$\E[Y(\text{diverse},0.25)-Y(\text{single},0.25)]$ for prompt diversity at a fixed model and
70 +$\E[Y(\text{mix},0.25)-Y(\text{diverse},0.25)]$ for a two-model mix against the single-model
71 +diverse-prompt cell; (E4) cognition contrasts at $\phi=0.25$---memory of five decisions, a rationale
72 +channel in cohorts of five, adaptive profit-and-loss feedback---each against the single-prompt cell at
73 +the same share; (E5) the interaction with leverage,
74 +$[Y(0.25,\text{lev}_{\text{hi}})-Y(0,\text{lev}_{\text{hi}})]-[Y(0.25,\text{lev}_{\text{base}})-Y(0,\text{lev}_{\text{base}})]$,
75 +and the safeguard contrasts (circuit breaker, position limit) at $\phi=0.25$; (E6) the fairness
76 +contrasts, a more capable model at the share that equalizes the dollar budget with the primary cell,
77 +and a matched-latency cell in which LLM orders arrive with the minimum latency. The leveraged control
78 +at $\phi=0$ is a classical run, free of API cost and deterministic given the seed, so it is produced on
79 +the matched seeds and the second difference of E5 is also within-seed. Two further objects are
80 +descriptive: a local projection of the log price--value gap around the latent shock, and $\tau_z$
81 +divided by the mean realized order share, a rescaling and not a causal effect per order.
82 +
83 +\subsection{Pre-registered outcomes, hypotheses and thresholds}
84 +\label{sec:identification:outcomes}
85 +
86 +Table~\ref{tab:outcomes} lists the outcome families, their lead variables and the sign predicted by the
87 +hypotheses of Table~\ref{tab:predictions}. The coordination statistics exist only when $\phi>0$, so H2
88 +is tested as a dose--response among treated cells plus a comparison with the same statistic computed on
89 +the aggressor-signed trades of classical agents of the replaced types. Thresholds were fixed before any
90 +live call: a bubble is $|P/V-1|>5\,\%$ for at least ten ticks, a crash a drop of at least 10\,\% from
91 +the trailing 20-tick maximum within 20 ticks. A sensitivity grid---bubbles at 3, 5 and 8\,\%, crashes
92 +at 7, 10 and 15\,\%---is reported for every instability result by re-scoring saved price paths; no
93 +threshold is re-selected after the fact. The classical control produces no crash at 10\,\%, so a floor
94 +effect is anticipated and the 7\,\% variant is the pre-specified fallback for H3. \code{msd\_log} is
95 +analysed on the $\log_{10}$ scale, \code{time\_to\_absorb} with rank-based inference, and counts by
96 +mean differences with randomization inference.
97 +
98 +\begin{table}[htbp]
99 +\centering
100 +\caption{Pre-registered outcome families, lead variables and predicted signs.}
101 +\label{tab:outcomes}
102 +\footnotesize
103 +\begin{tabular}{@{}p{1.7cm}p{3.2cm}p{5.0cm}p{3.4cm}p{1.3cm}@{}}
104 +\toprule
105 +Family & Variable & Definition & Predicted sign & H \\
106 +\midrule
107 +Efficiency & \code{msd\_log} \newline (lead) & mean $(\log P-\log V)^2$ & $\uparrow$ in $\phi$; $\downarrow$ with diversity & H1, H4 \\
108 + & \code{mad\_pct} & mean $100\,|\log P-\log V|$ & $\uparrow$ in $\phi$ & H1 \\
109 + & \code{time\_to\_absorb} & first $h$ after $t=300$ with $|P/V-1|<1\,\%$ for 5 ticks (censored at 100) & $\uparrow$ in $\phi$; $\uparrow$ with safeguards & H1, H6 \\
110 + & \code{response\_ratio} & $(P_{t+20}-P_{t-1})/(V_{t+20}-V_{t-1})$ at the shock & $|RR-1|$ $\uparrow$ (two-sided) & H1 \\
111 + & \code{vr5} & variance ratio of log mid at lag 5 & $|VR-1|$ $\uparrow$ (two-sided) & H1 \\
112 +Liquidity & \code{spread\_bp} \newline (lead) & mean quoted spread / mid, bp & $\uparrow$ in $\phi$ & H1b \\
113 + & \code{eff\_spread\_bp} & $2\,|\text{trade}-\text{mid}|/\text{mid}$, bp & $\uparrow$ in $\phi$ & H1b \\
114 + & \code{depth5} & mean lots within 5 levels, both sides & $\downarrow$ in $\phi$ & H1b \\
115 + & \code{lambda\_bp\_100} & slope of $\Delta\log$ mid (bp) on signed volume / 100 & two-sided ($\lambda$ falls as flow gets noisier) & H1b \\
116 + & \code{resiliency} & half-life of log-spread deviations, ticks & $\uparrow$ in $\phi$ & H1b \\
117 +Instability & \code{n\_crashes} \newline (lead H3) & drops $\ge10\,\%$ from the trailing 20-tick max & $\uparrow$ in $\phi\times$leverage & H3 \\
118 + & \code{max\_dd} \newline (lead H6) & maximum drawdown of mid & $\uparrow$ in $\phi$; $\downarrow$ with safeguards & H3, H6 \\
119 + & \code{n\_bubbles}, \newline \code{bubble\_amp} & episodes $|P/V-1|>5\,\%$ for $\ge10$ ticks; max amplitude & $\uparrow$ in $\phi\times$leverage & H3 \\
120 + & \code{acf\_absr\_1\_10} & $\sum_{k=1}^{10}\mathrm{ACF}_k(|r|)$ & $\uparrow$ in $\phi$ & H3 \\
121 + & \code{kurtosis} & excess kurtosis of tick returns & $\uparrow$ in $\phi$ & H3 \\
122 +Coordination & \code{llm\_pairwise\_corr} \newline (lead H2) & mean pairwise corr.\ of LLM net order signs, 20-tick windows & $\uparrow$ in $\phi$; $\downarrow$ with diversity & H2, H4 \\
123 + & \code{llm\_synchrony} \newline (lead H5) & mean $|\text{mean sign}|$ per decision round & $\uparrow$ with channel & H2, H5 \\
124 +\bottomrule
125 +\end{tabular}
126 +\end{table}
127 +
128 +\subsection{Estimators}
129 +\label{sec:identification:estimators}
130 +
131 +For each cell and seed we form $\Delta_s=Y_s(z)-Y_s(0)$ and report its mean, the paired-$t$ 95\,\%
132 +confidence interval and Cohen's $d_z=\bar\Delta/\mathrm{SD}(\Delta)$, with Glass's $\Delta$ against the
133 +control standard deviation from the 30 free classical seeds for comparability across cells. The primary
134 +$p$-value is from randomization inference under the sharp null: within a pair the sign of $\Delta_s$ is
135 +exchangeable, and the $p$-value is the share of the $2^n$ sign assignments whose absolute mean is at
136 +least the observed one; the Wilcoxon signed-rank test is a secondary distribution-free check.
137 +Multi-cell arms and the dose--response use a seed fixed-effects regression,
138 +$Y_{s,z}=\alpha_s+\sum_k\tau_k\mathbf 1[z=k]+\varepsilon_{s,z}$, with standard errors clustered by
139 +seed; because there are at most seven live clusters, inference rests on the wild cluster bootstrap with
140 +Rademacher weights and, for the ordered share, on Page's $L$ trend test for randomized blocks. Within
141 +each family the Holm step-down procedure controls the family-wise error rate at 5\,\%, the lead outcome
142 +is reported adjusted and unadjusted, all tests are two-sided and each sign is compared with its
143 +prediction. For the shock we estimate local projections of the gap
144 +$g_s(z,h)=\log P_{300+h}-\log V_{300+h}$: the coefficient at horizon $h\in[-20,100]$ is the seed mean
145 +of $g_s(z,h)-g_s(0,h)$, with pointwise paired-$t$ bands and a bootstrap sup-$t$ band; the same
146 +projection around the placebo tick must be flat. Descriptive mediation regresses $Y$ on each mediator
147 +with seed and cell fixed effects and reports the product-of-coefficients share of $\tau_z$ associated
148 +with it, as a decomposition and not a causal path.
149 +
150 +\subsection{Power and the number of seeds}
151 +\label{sec:identification:power}
152 +
153 +For a paired design with standardized effect $d=\bar\Delta/\mathrm{SD}(\Delta)$, two-sided
154 +$\alpha=0.05$ and power $0.80$, the normal approximation gives
155 +\begin{equation}
156 + n\;\approx\;\frac{(z_{0.975}+z_{0.80})^2}{d^2}\;=\;\frac{(1.96+0.84)^2}{d^2}\;=\;\frac{7.85}{d^2},
157 + \label{eq:power}
158 +\end{equation}
159 +so $d=0.5$ requires 32 pairs, $d=0.8$ requires 13 and $d=1$ requires 8. The live design has seven
160 +matched seeds. Inverting \eqref{eq:power} gives a minimum detectable paired effect of $d\approx1.06$,
161 +and the exact noncentral-$t$ calculation with six degrees of freedom gives $d\approx1.27$; we state
162 +plainly that only paired effects of roughly $1.2$ standard deviations or more are detectable at 80\,\%
163 +power in the live cells. The seed count was fixed by the dollar cap on live calls, not by the pilot
164 +variance, and the pilot's three seeds are never pooled with the confirmatory seeds. Effects below the
165 +minimum detectable effect are not thereby absent: a lead outcome is reported as \emph{refuted} if the
166 +confidence interval of $d$ excludes $d\ge0.5$ in the predicted direction, as showing \emph{no large
167 +effect} if the interval contains zero and excludes the minimum detectable effect, and as
168 +\emph{inconclusive} if it contains both. The classical design, which costs nothing, uses 30 matched
169 +seeds and detects $d\approx0.5$; it carries the stylized-fact validation, the mock shared-model knob,
170 +the leverage, safeguard and information comparative statics and the placebo controls.
171 +
172 +\subsection{Design matrix}
173 +\label{sec:identification:design}
174 +
175 +Table~\ref{tab:design} lists the cells of the two confirmatory designs. The live design runs every cell
176 +on seeds 201--207 with \code{gpt-5.4-nano} and a single generalist prompt unless stated, a per-run
177 +dollar cap well above the expected spend so that no run halts for budget, and cached responses so that
178 +every run replays exactly. The classical design runs on seeds 101--130 with the mock provider, a seeded
179 +rule that is not evidence about LLM behaviour. Both share the baseline economy: 600 ticks, one asset, a
180 +latent $+10$-cent shock at $t=300$ and a placebo at $t=450$.
181 +
182 +\begin{table}[htbp]
183 +\centering
184 +\caption{Design matrix of the confirmatory runs.}
185 +\label{tab:design}
186 +\footnotesize
187 +\begin{threeparttable}
188 +\begin{tabular}{@{}lp{4.4cm}p{4.6cm}lr@{}}
189 +\toprule
190 +Arm & Cell & Treatment & Seeds & Runs \\
191 +\midrule
192 +\multicolumn{5}{@{}l}{\emph{Live design}: \code{confirmatory\_openai.yaml} (gpt-5.4-nano, single prompt unless stated)} \\
193 +Dose & \code{dose\_000} & $\phi=0$ (control; no API cost) & 201--207 & 7 \\
194 +Dose & \code{dose\_005} & $\phi=0.05$ & 201--207 & 7 \\
195 +Dose & \code{dose\_010} & $\phi=0.10$ & 201--207 & 7 \\
196 +Dose & \code{dose\_025} & $\phi=0.25$ (primary contrast) & 201--207 & 7 \\
197 +Dose & \code{dose\_050} & $\phi=0.50$ (secondary contrast) & 201--207 & 7 \\
198 +Dose & \code{dose\_100} & $\phi=1.00$ (95 eligible slots) & 201--207 & 7 \\
199 +Diversity & \code{div\_prompts\_025} & $\phi=0.25$, eight diverse persona prompts & 201--207 & 7 \\
200 +Diversity & \code{div\_models\_025} & $\phi=0.25$, diverse prompts, nano/mini 50/50 & 201--207 & 7 \\
201 +Cognition & \code{memory\_025} & $\phi=0.25$, memory of five past decisions & 201--207 & 7 \\
202 +Cognition & \code{channel\_025} & $\phi=0.25$, rationale channel in cohorts of five & 201--207 & 7 \\
203 +Cognition & \code{adaptive\_025} & $\phi=0.25$, adaptive P\&L feedback in the prompt & 201--207 & 7 \\
204 +Leverage & \code{leverage\_025} & $\phi=0.25$, leveraged slots 19\,\% (trend 6\,\%) & 201--207 & 7 \\
205 +Safeguard & \code{breaker\_025} & $\phi=0.25$, halt when the 10-tick move exceeds 5\,\% & 201--207 & 7 \\
206 +Safeguard & \code{poslimit\_025} & $\phi=0.25$, position limit 300 lots & 201--207 & 7 \\
207 +Fairness & \code{fair\_mini\_005} & $\phi=0.05$, gpt-5.4-mini (equal USD to \code{dose\_025}) & 201--207 & 7 \\
208 +Fairness & \code{fair\_latency\_025} & $\phi=0.25$, LLM latency fixed at the minimum & 201--207 & 7 \\
209 +\midrule
210 +\multicolumn{5}{@{}l}{\emph{Classical design}: \code{classical\_confirmatory.yaml} (mock provider, no API cost)} \\
211 +Control & \code{control} & baseline economy & 101--130 & 30 \\
212 +Placebo & \code{placebo\_shock} & placebo events at $t=300$ and $t=450$ & 101--130 & 30 \\
213 +Feedback & \code{trend\_heavy} & trend followers 16\,\% & 101--130 & 30 \\
214 +Leverage & \code{leverage\_heavy} & leveraged slots 19\,\% & 101--130 & 30 \\
215 +Leverage & \code{leverage\_heavy\_} \newline \code{funding\_shock} & leveraged 19\,\%; $-10$-cent shock, then funding shock & 101--130 & 30 \\
216 +Liquidity & \code{liquidity\_shock} & market makers withdrawn for 60 ticks after the shock & 101--130 & 30 \\
217 +Information & \code{info\_low}, \code{info\_high} & private-signal precision 0.5, 8 & 101--130 & 60 \\
218 +Information & \code{misleading\_news} & half of the news items misleading & 101--130 & 30 \\
219 +Safeguard & \code{breaker\_5}, \code{poslimit\_300} & halt at 5\,\%; position limit 300 lots & 101--130 & 60 \\
220 +Mechanism & \code{auction\_5} & call auction every five ticks & 101--130 & 30 \\
221 +Mock herd & \code{mock\_llm\_25\_indep}, \newline \code{mock\_llm\_50\_indep} & mock LLM slots 25\,\%, 50\,\%, independent errors & 101--130 & 60 \\
222 +Contagion & \code{k2\_control}, \code{k2\_leverage} & two assets, factor loading 0.7; with leverage and funding shock & 101--130 & 60 \\
223 +\midrule
224 +\multicolumn{4}{@{}l}{Total} & 592 \\
225 +\bottomrule
226 +\end{tabular}
227 +\begin{tablenotes}
228 +\item Every live cell is paired with \code{dose\_000} and every classical cell with \code{control} on
229 +the same seed. The leveraged $\phi=0$ control for the E5 interaction is the \code{leverage\_heavy}
230 +configuration re-run on seeds 201--207 (deterministic, no API cost). A pilot on seeds 1--3 at
231 +$\phi\in\{0.05,0.25,0.50\}$ served cost, validity and variance reporting only.
232 +\end{tablenotes}
233 +\end{threeparttable}
234 +\end{table}
235 +
236 +\subsection{What was frozen and what changed}
237 +\label{sec:identification:changes}
238 +
239 +The plan fixed the prompt text and schema, the baseline configuration and its hash, the cell and seed
240 +lists, the outcome definitions, thresholds and sensitivity grid, the estimators, the budget tiers, the
241 +model identifiers and price table, and the code revision recorded in every manifest. Four changes were
242 +made after the plan and before any confirmatory run was analysed; each is logged with its date and
243 +reason. First, the timing of fundamental information was restructured: in the
244 +plan's economy the announced dividend revealed the cash-flow state exactly, so the public value
245 +coincided with the latent value and private signals and news were redundant, leaving H1 unidentified;
246 +in the frozen economy the deviation of the upcoming dividend is latent during the period, the public
247 +value is a filtered estimate updated by news, and private signals concern the latent deviation. Because
248 +no confirmatory run had been analysed, this is the pre-registered economy rather than a deviation, and
249 +the noisy dividends originally proposed for the same purpose were not used. Second, shocks are applied
250 +after the dividend step and the latent jump was reduced from 40 to 10 cents, about $+2.2\,\%$ of value,
251 +so that a lagged adjustment is not counted as a 5\,\% bubble; thresholds were unchanged. Third, two
252 +leaks of common random numbers were closed: LLM slots now inherit the information set of the slot they
253 +replace instead of drawing their own informed status, which had given the treated arm more private
254 +information than the control, and the within-tick processing order was moved to its own stream so that
255 +activation draws no longer diverge across arms. Fourth, the first commit marks the freeze; the pilot
256 +ran concurrently with the confirmatory design on the same revision, its role confined to cost, validity
257 +and variance reporting, and the seed count was set by the budget rather than by the pilot. Two
258 +limitations follow from budget and environment rather than choice: the mini-only cell that would let the
259 +two-model mix be decomposed net of composition was not run, so the mix is compared with the
260 +single-model diverse-prompt cell only; and the Anthropic cells could not be run because the available
261 +key was rejected, so the adapter is dry-run tested with the mock provider and no Claude result is
262 +reported.
263 +
264 +\subsection{Assigned versus realized dose}
265 +\label{sec:identification:dose}
266 +
267 +The treatment is defined on slots, but LLM agents decide once every 20 ticks at staggered offsets,
268 +whereas rule-based agents are activated with probability $0.2$ to $0.5$ per tick. The realized share of
269 +order flow originated by LLM agents is therefore much smaller than the slot share: in the pilot, a
270 +25\,\% slot share produced about 4\,\% of orders and 12 to 16\,\% of traded volume. Every manifest
271 +records both, and estimates are reported against the assigned dose with the realized dose alongside:
272 +the assigned dose is what a deployer chooses, while the realized dose is itself an outcome, since an
273 +agent that holds or returns an invalid decision originates no order. This asymmetry is why the fairness
274 +cells exist: equal decision opportunities is the default, equal dollars is implemented by a per-run
275 +budget cap, and matched latency removes the one speed disadvantage the LLM agents would otherwise
276 +carry.
277 +
278 +\subsection{Internal and external validity}
279 +\label{sec:identification:validity}
280 +
281 +Internal validity concerns inference inside the simulator. Its threats are provider non-determinism,
282 +treated as part of the treatment variance, with exact replay only from the cache; cache replay across
283 +cells, which turns identical early prompts into shared decisions across arms and whose share we report;
284 +the one-sided invalid-decision rule, whose rate is itself an outcome; and model drift, bounded by
285 +calling within a short window and recording the model snapshot. External validity is a different
286 +matter and we do not claim it. The rule-based traders are documented heuristics and the LLM agents are
287 +prompted policies without risk management, regulation or learning across runs; there is one asset in
288 +the prompt, model time has no calendar mapping, and fees and latencies are stylized. The findings are
289 +statements about this market mechanism and these model snapshots: signs and dose--response shapes may
290 +transfer to markets in which many participants obtain their signals from a common model; magnitudes
291 +will not.
modified paper/sections/implementation.tex +48 −1
@@ -1 +1,48 @@
1 −% implementation — to be written
1 +\section{Implementation and validation}
2 +\label{sec:implementation}
3 +
4 +\paragraph{Architecture.}
5 +The simulator is a pure-Python package (\code{market\_agents}, about 3,900 lines, Python~3.12) organised in six layers: \code{assets/} (dividend process and the two value benchmarks), \code{signals/} (news feed and shock schedule), \code{exchange/} (typed order records, the limit order book, the integer ledger, the engine with latency queue, fees, risk checks and halts, and the call auction), \code{agents/} (the \code{Observation} interface and the classical policies), \code{llm/} (decision schema, prompt builder, policy with validation, budget tracker, SQLite cache, frozen library, and the provider adapters), and \code{experiments/} (pydantic configuration, population builder, single-run \code{Simulation}, and the design runner), with \code{metrics/outcomes.py} computing every run-level outcome from the saved frames and \code{analysis/} holding the estimators and plots. The exchange is \emph{event-sourced}: every state change (\code{submit}, \code{accept}, \code{reject}, \code{trade}, \code{cancel}, \code{expire}, \code{halt}, \code{resume}, \code{dividend}, \code{margin\_call}, \code{liquidate}, \code{bankrupt}, \code{auction}, \code{shock}, \code{tick}) is appended to a sequence-numbered log with a flat scalar payload, and the engine exposes a hashable \code{snapshot} of all balances, positions, resting orders and counters.\footnote{\code{exchange/orders.py}, \code{Event}; \code{exchange/engine.py}, \code{Exchange.snapshot}. The full event log is written to \code{events.parquet} when \code{output.events} is on; by default a run saves the per-tick, trade, agent and LLM-decision frames.} All randomness flows from one master seed through named \code{SeedSequence} children (fundamentals, news, slots, assignment, LLM assignment, activation, tie-break, mock LLM, within-tick shuffle) plus one generator per agent, so a run is a deterministic function of (configuration, seed) up to the provider's sampling; two executions of the same classical configuration produce identical event logs, tick frames and trade frames. Every run writes a \code{manifest.json} with the configuration hash (first 16 hex digits of the SHA-256 of the canonical JSON configuration), the package version, the git revision (suffixed \code{-dirty} when the working tree had uncommitted changes), the seed and stream names, the trader and realised LLM shares of traders, orders and volume, the number and validity rate of LLM decisions, token and USD usage by model, the provider and model identifiers, the prompt version, the full price table used by the budget tracker, the treatment labels, the shock log, the number of trades, halts and bankruptcies, and the wall-clock time.\footnote{\code{experiments/runner.py}, \code{Simulation.\_finish}; \code{utils/hashing.py}.}
6 +
7 +\paragraph{Test suite.}
8 +The package ships 24 tests in three groups.\footnote{\code{tests/unit}, \code{tests/property}, \code{tests/integration}; run with \code{make test}.} \emph{Unit tests} cover the book (price--time priority, limit orders never cross beyond their price, market orders walk the book and stop when it is empty, cancel and depth), the engine (a fill moves exactly the notional and fees, latency orders arrivals, each rejection reason, cancel releases the reservation, a market buy is capped by cash, dividends preserve conservation, a halt rejects new orders, a margin breach generates a liquidation), the LLM layer (lenient parsing versus strict schema, each validation rule, the budget cap halting calls) and the configuration override semantics. \emph{Property tests} use Hypothesis to generate random streams of up to 120 intents from six agents with partial activations, cancels, a maker rebate and a short time-to-live, and assert after every processed tick that the book is uncrossed and indexed consistently, that the ledger identities \eqref{eq:conservation} hold, that each account's reserved cash and lots equal the sums over its resting orders, and that no position is negative; a second property asserts that two executions of the same stream yield identical snapshots and identical event logs (determinism and replay). \emph{Integration tests} run the full simulation: an end-to-end classical run with 100 agents writing all artefacts, bit-for-bit determinism across runs, common random numbers across \code{llm\_share} (identical latent value paths and identical endowments and latencies of every non-replaced slot), a mock-LLM run whose realised shares and validity rate are within the expected bands, the \emph{zero-trade edge case} (a population of inactive market makers produces 30 ticks with a well-defined midpoint and no trades), and the logging and application of each shock kind.
9 +
10 +\paragraph{Computational performance.}
11 +Table~\ref{tab:scale} reports the wall-clock time of a 600-tick baseline run as the population grows from 100 to 10,000 traders with the per-type activity scaled by $100/N$ so that order flow per tick is constant.\footnote{\code{scripts/11\_scale\_benchmark.py}; numbers from \code{results/calibration/scale\_benchmark.json}, single process on an Apple-silicon (arm64) laptop. The benchmark was produced at the calibration stage, before the final revision of the dividend timing; the engine code it exercises is unchanged.} A run takes 0.6~s at $N=100$ (three seeds: 0.57, 0.58, 0.58~s), 2.7~s at $N=1{,}000$ and 23.7~s at $N=10{,}000$, i.e.\ roughly linear in $N$ at fixed order flow, with 13,300--15,400 orders and 10,300--12,200 trades per run. The 480-run classical confirmatory design (16 cells $\times$ 30 seeds) completes in about one minute on six worker processes. Live LLM cells are bound by provider latency: with 25 LLM agents in four staggered batches a run makes about 690 calls at 2.5--3.2~s each, so a $\varphi=0.25$ run takes 6--8 minutes with eight concurrent requests (Appendix~\ref{app:repro}).
12 +
13 +\begin{table}[t]
14 +\centering
15 +\caption{Computational performance of the simulator at constant order flow}
16 +\label{tab:scale}
17 +\small
18 +\input{tables/tab_scale}
19 +\begin{minipage}{0.95\linewidth}\footnotesize\vspace{2pt}
20 +Baseline configuration, 600 ticks, activities scaled by $100/N$; generated by \code{scripts/50\_tables.py} from \code{results/calibration/scale\_benchmark.json}. Mean over seeds.
21 +\end{minipage}
22 +\end{table}
23 +
24 +\paragraph{Calibration.}
25 +The classical baseline was calibrated once, before any LLM run, by a sweep over four behavioural parameters: fundamentalist aggressiveness $\{10,20,40\}$ lots per percent of mispricing, the combined trend-plus-leveraged share $\{0.10,0.15,0.25\}$ (split 60/40 between the two types and rebalanced against noise traders), the noise traders' market-order probability $\{0.3,0.6\}$ and the market makers' quote size $\{10,20\}$ lots---36 cells $\times$ 10 seeds $=360$ runs of 600 ticks.\footnote{\code{scripts/10\_calibration\_sweep.py}; cell means in \code{results/calibration/sweep\_summary.csv}.} The pre-stated criteria were those of the stylized-facts literature \citep{cont2001}: first-order return autocorrelation close to zero, excess kurtosis above~3, positive volatility clustering (positive sum of the first ten autocorrelations of absolute returns), a quoted spread of 10--30 basis points, and a tracking error $\overline{|P/V-1|}$ near 1\%. The retained cell (aggressiveness 20, trend-plus-leveraged share 0.10, market-order probability 0.6, quote size 20) has ACF$_1=0.015$, variance ratio VR(5) $=1.30$, excess kurtosis 26.6, $\sum_{1}^{10}\mathrm{ACF}|r|=2.71$, tick volatility 30~bp, quoted spread 24.1~bp, depth 141 lots, price impact 15.6~bp per 100 lots, 0.8 bubble episodes and a maximum drawdown of 5.2\% on average over ten seeds. The alternative with market-order probability 0.3 produced spreads of 6--12~bp but tracking errors of 2--3\% and ACF$_1$ of 0.2--0.3; the aggressiveness-40 cells reduced the tracking error below 1\% at the cost of positive return autocorrelation and larger drawdowns. The retained mix (25/6/10/50/5/4\%) and the three parameters are frozen in \code{configs/baseline.yaml} and were not revisited after the LLM pilot.
26 +
27 +\paragraph{Stylized facts at the frozen revision.}
28 +Table~\ref{tab:stylized_facts} summarises the 30 control runs of the classical confirmatory design (seeds 101--130) at the frozen code revision. Returns of the midpoint are uncorrelated at lag one (mean ACF$_1=-0.007$) with a variance ratio near one (VR(5) $=1.18$), heavy-tailed (excess kurtosis 20.4) and volatility-clustered ($\sum_1^{10}\mathrm{ACF}|r|=2.02$); the quoted spread averages 20.7~bp and the effective spread 30.4~bp, depth within five levels is 139 lots, price impact is 10.8~bp per 100 lots, the tracking error is 0.63\% and no run contains a bubble episode under the pre-specified 5\%/10-tick rule. Market makers earn 4.9\% over the run and the wealth Gini coefficient stays at 0.285. These are the properties against which every treatment is compared.
29 +
30 +\begin{table}[t]
31 +\centering
32 +\caption{Stylized facts of the classical baseline (control cell, 30 seeds)}
33 +\label{tab:stylized_facts}
34 +\small
35 +\input{tables/tab_stylized_facts}
36 +\begin{minipage}{0.95\linewidth}\footnotesize\vspace{2pt}
37 +Outcomes on ticks $100$--$599$ of the \code{control} cell of \code{configs/classical\_confirmatory.yaml}; generated by \code{scripts/50\_tables.py} from \code{results/runs/classical\_confirmatory/outcomes.parquet}.
38 +\end{minipage}
39 +\end{table}
40 +
41 +\paragraph{LLM adapters.}
42 +Three provider adapters share one interface, \code{complete(system, user, model, \dots)}~$\to$~\code{ProviderResult} (text, input, cached-input, output and reasoning tokens, latency, model, request id, error).\footnote{\code{llm/providers/}.} The \emph{OpenAI} adapter calls the Responses API with the frozen system prompt as \code{instructions}, the snapshot as the single user input, \code{max\_output\_tokens} $=1{,}200$ (raised from 400 because reasoning tokens count against the cap), \code{text.format} $=$ \{\code{json\_schema}, name \code{trading\_decision}, \code{strict: true}\} with the schema of Appendix~\ref{app:prompts}, \code{store=False} so that no response is retained in the account's response store, \code{reasoning.effort} $=$ \code{low} for the gpt-5 and o-series models (temperature is never sent to them), and \code{prompt\_cache\_key} $=$ \code{ma-v1-<prompt\_id>} so that the provider can reuse the cached prefix across agents sharing a persona; the client is configured with a 60-second timeout and three retries, and rate-limit, status and connection errors are returned as error strings rather than raised. The \emph{Anthropic} adapter calls the Messages API with the system prompt marked \code{cache\_control: ephemeral}, \code{output\_config.format} $=$ \{\code{json\_schema}, schema\} and \code{output\_config.effort} $=$ \code{low}, no \code{thinking} parameter, \code{max\_tokens} $=\max(1{,}200, 256)$, and maps \code{stop\_reason = refusal} to an invalid decision; it is implemented and dry-run tested but, because the key available on 2026-10-27 is rejected (HTTP~401), no Claude decision enters the reported results. The \emph{mock} adapter parses the midpoint, public value, public estimate, private signal, holdings and free cash out of the user message and applies a seeded value rule (buy or sell when $|\hat V/P-1|>0.5\%$, 60\% limit and 40\% market orders) with a 5\% share of deliberately malformed answers (half non-JSON, half schema-invalid) and an optional common tilt (\code{herd\_bias}); its tokens are word counts priced at zero. It is used for dry runs, tests and the mock cells, and is not evidence about LLM behaviour.
43 +
44 +\paragraph{Budget and cost accounting.}
45 +A \code{BudgetTracker} per run prices every call from the table of USD per million tokens stored in the configuration (input, cached input, output) and reproduced in the manifest; an unknown model id is priced at the most expensive known output price so that the cap is conservative. The cap is checked before each call; once reached, the policy is halted for the rest of the run and every subsequent decision becomes a logged hold with error \code{budget\_halt}. Cached hits are counted (\code{cached\_hits}) but contribute no tokens and no cost; failures and invalid decisions are counted separately. Optional caps on total tokens and on decision opportunities per agent (\code{decision\_cap}) implement the fairness designs. Before any live design, \code{ma budget} enumerates the calls of every cell $\times$ seed (number of LLM slots $\times\lfloor(T-\texttt{warmup}/2)/20\rfloor$) and prices them at a stated token profile.\footnote{\code{llm/budget.py}; \code{cli.py}, \code{budget}.}
46 +
47 +\paragraph{Decision log.}
48 +Each decision appends one row to \code{llm\_decisions.parquet}: tick, agent id, provider, model, prompt id, validity flag and reason, the parsed action, side, order type, quantity, limit price, confidence and rationale, input, cached-input, output and reasoning tokens, latency in seconds, USD cost, whether it was a cache hit, the provider error string, the first 16 hex digits of the SHA-256 of the concatenated system and user prompts, the provider request id, the seven-dimensional feature vector of the observation used by the frozen library, and the limit price relative to the midpoint.\footnote{\code{llm/policy.py}, \code{LLMDecisionRecord}.} Raw prompts are not stored in the decision log (they are reconstructible from the saved run and are stored verbatim in the SQLite cache together with the raw response text and token usage).
modified paper/sections/limitations.tex +63 −1
@@ -1 +1,63 @@
1 −% limitations — to be written
1 +\section{Limitations}
2 +\label{sec:limitations}
3 +
4 +The results of this paper are causal statements about a simulator. Every limitation below bounds
5 +what they can say about actual markets, and several bound what they can say about the simulator itself.
6 +
7 +\paragraph{Internal validity.}
8 +The confirmatory design uses seven matched seeds per live cell. With paired inference this gives
9 +80\,\% power only for standardized effects of roughly $d \approx 1.2$ (Section~\ref{sec:identification});
10 +smaller effects are reported with their confidence intervals and must be read as inconclusive rather
11 +than absent. The seed count was fixed by the monetary cap on API calls (about 40~USD for the whole
12 +project), not by a pilot variance estimate, and the pilot ran concurrently with the confirmatory
13 +design on the same frozen code revision; the pilot therefore informs cost, validity and realized-dose
14 +reporting but did not feed back into the design. The thresholds that define bubbles and crashes were
15 +pre-registered, and the control economy exhibits a floor effect at the crash threshold (no crashes in
16 +30 control seeds), so crash frequencies can only increase under treatment; the sensitivity grid in
17 +Section~\ref{sec:robustness} reports how the conclusions move with the thresholds.
18 +
19 +\paragraph{Provider coverage.}
20 +All live cells use one provider family (OpenAI, \code{gpt-5.4-nano} as the primary model and
21 +\code{gpt-5.4-mini} in the two-model and equal-budget arms). The Anthropic adapter is implemented and
22 +exercised with the mock provider, but the key available when the experiments were run was rejected by
23 +the API, so no Claude-populated market is reported. The cross-provider comparison that the research
24 +questions call for is therefore reduced to a within-provider comparison of two model sizes, and the
25 +``shared-model'' mechanism is tested through prompt diversity and the model mix rather than through
26 +genuinely different foundation-model families. The two-model arm is compared with the single-model
27 +diverse-prompt arm only; the mini-only cell needed to decompose a model-mix effect into a composition
28 +effect and a diversity effect was not run.
29 +
30 +\paragraph{What the LLM agents are.}
31 +An LLM agent here is a small reasoning model answering a short, fixed prompt every twenty ticks with a
32 +strictly validated JSON action. It has no tools, no retrieval, no fine-tuning and, outside the memory
33 +and channel arms, no state. The rationale field is logged but is never treated as evidence about the
34 +model's internal reasoning. Decision frequency is deliberately separated from event frequency: an LLM
35 +agent makes 27~decisions in a 600-tick run while classical agents act on roughly one tick in five, so
36 +the realized share of orders and volume is far below the assigned share of trader slots; both are
37 +reported and the dose--response is read on the assigned share only. Provider outputs are not
38 +deterministic across repeated calls; recorded decisions are cached and replayed exactly, and cache hits
39 +are counted separately from online calls. The frozen nearest-neighbour library used to scale the
40 +population to 1,000 traders is a non-adaptive approximation of recorded behaviour and is reported as a
41 +separate arm, never pooled with online runs.
42 +
43 +\paragraph{The economy.}
44 +The exchange is a partial-equilibrium sandbox: dividends, discount rate, endowments and noise trading
45 +are exogenous; there is no entry, no learning across runs, no short selling by default, and the
46 +market makers follow a bounded inventory rule rather than an optimizing strategy. The stylized model of
47 +Section~\ref{sec:theory} shares these restrictions: linear order submission, a competitive risk-neutral
48 +market maker, Gaussian primitives and a fixed pricing schedule in the feedback extension. Its
49 +propositions are statements about that model; the simulator tests their comparative statics, not their
50 +closed forms. The classical population was calibrated to reproduce a short list of stylized facts
51 +(near-zero return autocorrelation, fat tails, volatility clustering, spreads of 10--30 basis points) at
52 +$N = 100$, and the same activity rates produce a somewhat different microstructure at $N = 1{,}000$ and
53 +$N = 10{,}000$ unless the activity rate is scaled; all penetration experiments are run at $N = 100$.
54 +
55 +\paragraph{External validity.}
56 +Simulated LLM traders are not institutions, and the market here has no regulation, no intermediaries
57 +beyond five quoting agents, no overnight, and no cross-market linkages beyond the two-asset arm. Nothing
58 +in the paper should be read as evidence about how deployed trading systems behave. The claim is narrower:
59 +within a transparent, replayable economy, raising the share of traders driven by one language model
60 +changes price discovery and coordination in the direction, and through the channel, that a
61 +correlated-error model predicts, and those changes can be undone or amplified by interventions that
62 +real markets can also implement. Whether the same mechanism operates at scale in live markets is an
63 +empirical question this design cannot answer.
modified paper/sections/literature.tex +206 −256
@@ -4,271 +4,222 @@
4 4 \section{Related literature}
5 5 \label{sec:literature}
6 6
7 −The paper sits at the intersection of six literatures: agent-based and heterogeneous-agent models of
8 −financial markets; the microstructure of double auctions and the measurement of price discovery;
9 −information aggregation with correlated signals, herding and feedback trading; liquidity spirals,
10 −fire sales and the measurement of contagion; algorithmic trading, flash crashes, algorithmic
11 −monoculture and algorithmic collusion; and, closest to our question, the rapidly growing work that
12 −places large language model (LLM) agents in economic experiments and market simulations. We review
13 −each in turn and close with a statement of what the present paper adds and where it overlaps with
14 −existing work.
7 +The paper draws on six literatures: agent-based models of financial markets; double-auction
8 +microstructure and the measurement of price discovery; information aggregation with correlated
9 +signals, herding and feedback; liquidity spirals, fire sales and contagion; algorithmic trading,
10 +monoculture and algorithmic collusion; and, closest to our question, LLM agents in economic experiments
11 +and market simulations. We close with what this paper adds and where it overlaps with existing work.
15 12
16 −\subsection{Agent-based and heterogeneous-agent models of financial markets}
13 +\subsection{Agent-based and heterogeneous-agent models}
17 14 \label{sec:literature:abm}
18 15
19 −Our baseline population of rule-based traders descends from agent-based computational finance, which
20 −showed that interacting heterogeneous rules reproduce the fat tails, volatility clustering and bubbles
21 −of real markets \citep{lebaron2006}. In the Santa Fe artificial stock market the speed of belief
22 −updating governs whether prices track fundamentals or display excess volatility and technical-trading
23 −regimes \citep{lebaron1999}; in adaptive belief systems, discrete-choice switching between predictors
24 −produces increasingly complex price dynamics as the intensity of choice rises \citep{brock1997}, and
25 −the share of trend followers determines whether the fundamental price is stable \citep{brock1998}.
26 −Interacting noise traders who switch between optimistic, pessimistic and fundamentalist states generate
27 −scaling laws and clustered volatility endogenously \citep{lux1999,lux2000}; herd behaviour among
28 −speculators yields bubbles and crashes in a mean-field model of contagious optimism \citep{lux1995}; and
29 −recruitment-type opinion dynamics generate persistent herding states without any informational reason
30 −\citep{kirman1993}. These mechanisms matter for us because they describe what a \emph{classical}
31 −population does on its own, before any LLM is introduced, and because trend following and herding are
32 −precisely the channels through which a correlated LLM order flow can be amplified.
33 −
34 −Closer to our exchange design, \citet{chiarella2002} simulate a double auction populated by
35 −fundamentalist, chartist and noise traders and reproduce order-book regularities, and
36 −\citet{chiarella2009} show that heterogeneous rules in a limit-order book generate fat tails,
37 −volatility clustering and order-flow autocorrelation, with the chartist share driving excess
38 −volatility. Leverage in an agent-based model produces fat tails and clustered volatility through
39 −synchronized margin-driven selling \citep{thurner2012}, which is the closest antecedent of our leverage
40 −treatment. Multi-agent reinforcement-learning markets reproduce the same stylized facts
41 −\citep{lussange2021} and are a precursor of LLM-populated markets. The surveys of \citet{hommes2006}
42 −and \citet{hommes2021} summarize the laboratory and agent-based evidence that expectation heterogeneity
43 −drives aggregate instability; we follow their lead in treating an LLM policy as one more expectation
44 −type. \citet{farmer2009} argue that agent-based models are the appropriate tool for out-of-equilibrium
45 −dynamics and for policy counterfactuals that cannot be solved analytically; \citet{axtell2025}
46 −identify validation, calibration and scale as the open frontiers, which our matched-seed design and
47 −power analysis address directly. For calibration and validation we use the moment set of structural
48 −stochastic volatility models \citep{franke2012} and the catalogue of stylized facts: heavy tails
49 −\citep{mandelbrot1963,cont2001}, conditional heteroskedasticity \citep{engle1982,bollerslev1986}, the
50 −long memory of absolute returns \citep{ding1993}, and the long memory of order-flow signs coexisting
51 −with nearly efficient prices \citep{lillo2004}.
52 −
53 −\subsection{Double auctions, zero intelligence and the measurement of price discovery}
16 +Our rule-based population descends from agent-based computational finance, in which interacting
17 +heterogeneous rules reproduce fat tails, volatility clustering and bubbles \citep{lebaron2006}. In the
18 +Santa Fe artificial stock market the speed of belief updating governs whether prices track fundamentals
19 +or display excess volatility and technical-trading regimes \citep{lebaron1999}; in adaptive belief
20 +systems, switching between predictors produces increasingly complex dynamics as the intensity of choice
21 +rises \citep{brock1997}, and the share of trend followers determines whether the fundamental price is
22 +stable \citep{brock1998}. Noise traders switching between optimistic, pessimistic and fundamentalist
23 +states generate scaling laws and clustered volatility endogenously \citep{lux1999,lux2000}, contagious
24 +optimism yields bubbles and crashes \citep{lux1995}, and recruitment dynamics generate persistent
25 +herding without any informational reason \citep{kirman1993}. These are the channels through which a
26 +correlated LLM order flow can be amplified by a classical population. Closer to our exchange,
27 +\citet{chiarella2002} reproduce order-book regularities with fundamentalist, chartist and noise traders
28 +in a double auction, and \citet{chiarella2009} show that heterogeneous rules in a limit-order book
29 +generate fat tails, volatility clustering and order-flow autocorrelation, with the chartist share
30 +driving excess volatility. Leverage in an agent-based model produces fat tails and clustered volatility
31 +through synchronized margin-driven selling \citep{thurner2012}, the closest antecedent of our leverage
32 +treatment, and multi-agent reinforcement-learning markets reproduce the same facts
33 +\citep{lussange2021}. Following the surveys of \citet{hommes2006} and \citet{hommes2021}, we treat an
34 +LLM policy as one more expectation type; following \citet{farmer2009} and \citet{axtell2025}, we use
35 +the agent-based method for counterfactuals that cannot be solved analytically and address its open
36 +frontiers---validation, calibration, scale---with matched seeds and a power analysis. Validation
37 +targets are the moment set of \citet{franke2012} and the stylized facts of heavy tails
38 +\citep{mandelbrot1963,cont2001}, conditional heteroskedasticity \citep{engle1982,bollerslev1986} and the
39 +long memory of absolute returns \citep{ding1993}.
40 +
41 +\subsection{Double auctions, zero intelligence and price-discovery measurement}
54 42 \label{sec:literature:lob}
55 43
56 −The continuous double auction is the institution we study, and its experimental pedigree is long:
57 44 \citet{smith1962} showed that double auctions with induced values converge to competitive equilibrium
58 −with few traders, and \citet{smith1988} showed that experimental asset markets with known dividend
59 −streams nevertheless produce bubbles and crashes; our dividend-paying asset and bubble metrics follow
60 −that design. The zero-intelligence tradition established that market structure, not trader
61 −rationality, drives allocative efficiency \citep{gode1993}, a point reviewed by \citet{ladley2012}. A
62 −zero-intelligence model of the continuous double auction predicts spread and price impact from
63 −order-flow rates alone \citep{farmer2005}, and the statistical theory of \citet{smith2003} ties
64 −spread, depth and price diffusion to the same parameters. These results fix the benchmark against
65 −which any effect of ``intelligence'' must be measured: a change in the composition of a population can
66 −move liquidity merely by changing order-flow rates, which is why we report realized order and volume
67 −shares alongside the assigned treatment. On the empirical side we rely on the order-book facts of
68 −\citet{bouchaud2002}, the Markovian book model of \citet{cont2010}, the linear relation between
69 −order-flow imbalance and short-horizon price changes \citep{cont2014}, the survey of
70 −\citet{gould2013}, and the treatment of impact and the slow digestion of order flow in
71 −\citet{bouchaud2018}. Our event-driven exchange, with per-agent latency and a price--time matching
72 −engine, follows the design of ABIDES \citep{byrd2020}, and our realism checklist is that of
73 −\citet{vyetrenko2020}.
74 −
75 −The outcome variables are standard measures of price discovery and market quality. The price-impact
76 −coefficient $\lambda=\Cov(v,Y)/\Var(Y)$ of \citet{kyle1985} and the adverse-selection spread of
77 −\citet{glosten1985} tie liquidity to the information content of order flow. \citet{hasbrouck1991}
78 −measures that content by the permanent price impact in a VAR of quote revisions and trades;
79 −\citet{hasbrouck1993} decomposes the transaction price into an efficient random-walk component and a
80 −pricing error whose standard deviation is a market-quality metric; \citet{hasbrouck1995} attributes
81 −price discovery across venues through information shares, which we adapt to LLM versus rule-based
82 −order flow. Variance ratios reject the random walk under autocorrelation \citep{lo1988}, the effective
83 −spread can be inferred from serial covariance \citep{roll1984}, and illiquidity can be measured as
84 −absolute return per unit of volume \citep{amihud2002}. \citet{bai2016} define informativeness by how
85 −well prices forecast future fundamentals, and \citet{bond2012} argue that informativeness is the
86 −welfare-relevant object because real decisions feed on prices. \citet{hendershott2014} separate
87 −transitory price pressure from permanent information effects, which justifies our separation of the
88 −pricing error from the efficient-price variance. Because the simulator observes the latent value, we
89 −can compute these statistics against the truth rather than against a proxy, which is the main
90 −advantage of a simulated economy for this question \citep{fama1970}.
91 −
92 −\subsection{Information aggregation with correlated signals, herding and feedback}
45 +with few traders, and \citet{smith1988} that experimental asset markets with known dividend streams
46 +nevertheless produce bubbles and crashes; our dividend-paying asset and bubble metrics follow that
47 +design. Zero-intelligence traders constrained by budgets reach near-full allocative efficiency, so
48 +market structure rather than rationality drives efficiency \citep{gode1993,ladley2012}, and a
49 +zero-intelligence model of the double auction predicts spread and price impact from order-flow rates
50 +alone \citep{farmer2005,smith2003}. This fixes the benchmark against which any effect of
51 +``intelligence'' must be read: changing the composition of a population can move liquidity merely by
52 +changing order-flow rates, which is why we report realized order and volume shares alongside the
53 +assigned treatment. The book is parametrized from the empirical regularities in \citet{bouchaud2002}, \citet{cont2010},
54 +\citet{cont2014}, \citet{gould2013} and \citet{bouchaud2018}; the event-driven exchange with per-agent
55 +latency follows ABIDES \citep{byrd2020} and the realism checklist is that of \citet{vyetrenko2020}.
56 +
57 +The outcomes are standard measures of price discovery. The price-impact coefficient
58 +$\lambda=\Cov(v,Y)/\Var(Y)$ of \citet{kyle1985} and the adverse-selection spread of \citet{glosten1985}
59 +tie liquidity to the information in order flow; \citet{hasbrouck1991} measures that information by the
60 +permanent price impact in a VAR of quotes and trades, \citet{hasbrouck1993} decomposes the transaction
61 +price into an efficient random-walk component and a pricing error, and \citet{hasbrouck1995}
62 +attributes price discovery through information shares, which we adapt to LLM versus rule-based flow.
63 +Variance ratios test the random walk \citep{lo1988}, the effective spread follows from serial
64 +covariance \citep{roll1984}, and illiquidity is absolute return per unit of volume \citep{amihud2002}.
65 +\citet{bai2016} define informativeness by how well prices forecast future fundamentals,
66 +\citet{bond2012} argue that it is the welfare-relevant object because real decisions feed on prices,
67 +and \citet{hendershott2014} separate transitory price pressure from permanent information effects,
68 +which justifies separating the pricing error from the efficient-price variance. Because the simulator
69 +observes the latent value, these statistics are computed against the truth rather than a proxy, which
70 +is how we operationalize the efficiency notions of \citet{fama1970}.
71 +
72 +\subsection{Correlated signals, herding and feedback}
93 73 \label{sec:literature:info}
94 74
95 −The theoretical core of the paper is the aggregation of \emph{correlated} private signals.
96 −\citet{hellwig1980} shows that competitive prices aggregate dispersed private signals imperfectly
97 −because of noise supply, and \citet{grossman1980} that prices cannot be fully informationally
98 −efficient when information is costly. With strategic informed traders, \citet{foster1996} show that
99 −the correlation structure of signals, not only their precision, governs trading intensity and price
100 −informativeness, and \citet{back2000} that more correlated information leads to faster revelation.
101 −\citet{goldstein2015} show that information diversity across traders is complementary, so that a loss
102 −of diversity reduces informativeness about some components of value; \citet{dugast2018} show that
103 −abundant cheap data can lower informativeness by crowding out costly deep information, a close parallel
104 −to shared-model crowding; and \citet{kondor2012} warn that public information can raise disagreement
105 −through higher-order beliefs, so that shared inputs do not trivially homogenize behaviour.
106 −\citet{pasquariello2007} ties information heterogeneity among strategic speculators to contagion and
107 −shows that homogeneity reduces it. Our stylized model (Section~\ref{sec:theory}) borrows the
108 −correlated-error structure of these papers and reinterprets the common component as the signature of a
109 −shared foundation model or prompt family.
110 −
111 −Herding supplies the behavioural counterpart. Sequential decision-makers who follow their predecessors
112 −destroy information aggregation \citep{banerjee1992}, informational cascades become more likely as
113 −signals become more correlated \citep{bikhchandani1992}, reputational concerns make managers mimic one
114 −another \citep{scharfstein1990}, and short-horizon speculators herd on the same information source
115 −even when it is unrelated to fundamentals \citep{froot1992}. The buy-side imbalance statistic of
116 −\citet{lakonishok1992} is the standard empirical measure of institutional herding, and we compute it
117 −for LLM cohorts. Feedback trading closes the loop: the share of trend followers determines the
118 −stability of the fundamental price in heterogeneous-belief models \citep{brock1998}, and
119 −contagious optimism among speculators produces bubbles and crashes \citep{lux1995}. Our feedback
120 −extension (Proposition~\ref{prop:feedback}) is a mechanical version of this amplification.
121 −
122 −\subsection{Liquidity spirals, fire sales, leverage and contagion measurement}
75 +The theoretical core of the paper is the aggregation of \emph{correlated} private signals. Competitive
76 +prices aggregate dispersed signals imperfectly because of noise supply \citep{hellwig1980} and cannot
77 +be fully efficient when information is costly \citep{grossman1980}. With strategic informed traders,
78 +the correlation structure of signals, not only their precision, governs trading intensity and
79 +informativeness \citep{foster1996}, and more correlated information leads to faster revelation
80 +\citep{back2000}. Information diversity across traders is complementary, so a loss of diversity
81 +reduces informativeness about some components of value \citep{goldstein2015}; abundant cheap data can
82 +lower informativeness by crowding out costly deep information \citep{dugast2018}, a close parallel to
83 +shared-model crowding; public information can raise disagreement through higher-order beliefs, so
84 +shared inputs do not trivially homogenize behaviour \citep{kondor2012}; and information heterogeneity
85 +among speculators generates contagion while homogeneity reduces it \citep{pasquariello2007}. Our
86 +stylized model (Section~\ref{sec:theory}) borrows this correlated-error structure and reinterprets the
87 +common component as the signature of a shared foundation model. Herding supplies the behavioural
88 +counterpart: sequential decision-makers who follow predecessors destroy information aggregation
89 +\citep{banerjee1992}, cascades become more likely as signals become more correlated
90 +\citep{bikhchandani1992}, reputational concerns make managers mimic one another
91 +\citep{scharfstein1990}, and short-horizon speculators herd on the same information source even when it
92 +is unrelated to fundamentals \citep{froot1992}; the buy-side imbalance statistic of
93 +\citet{lakonishok1992} is the standard measure and we compute it for LLM cohorts. Feedback trading
94 +closes the loop, since the share of trend followers governs the stability of the fundamental price
95 +\citep{brock1998}; Proposition~\ref{prop:feedback} is a mechanical version of this amplification.
96 +
97 +\subsection{Liquidity spirals, fire sales and contagion}
123 98 \label{sec:literature:spirals}
124 99
125 −The leverage treatment and the H3 and H6 hypotheses draw on the literature on funding constraints.
126 −\citet{brunnermeier2009} show that market liquidity and funding liquidity reinforce each other through
127 −margins, producing liquidity spirals; our margin-call mechanism follows this logic. Liquidation values
128 −fall when potential buyers are themselves constrained \citep{shleifer1992}, constrained arbitrageurs can
129 −amplify rather than correct mispricing \citep{shleifer1997}, forced sales push prices below
130 −fundamentals and propagate losses \citep{shleifer2011}, and financially constrained arbitrageurs may
131 −under-provide liquidity \citep{gromb2002}. Endogenous margins amplify booms and crashes in the leverage
132 −cycle, with leverage caps as a policy lever \citep{geanakoplos2010}, and procyclical intermediary
133 −leverage amplifies price moves \citep{adrian2010}. In the agent-based setting, \citet{thurner2012}
134 −obtain fat tails and clustered volatility from synchronized margin-driven selling. Across assets,
135 −losses of convergence traders propagate through reduced risk-bearing capacity \citep{kyle2001},
136 −distressed selling induces endogenous correlation \citep{cont2013}, fire-sale spillovers scale with
137 −common exposures and leverage \citep{greenwood2015}, cross-market rebalancing transmits shocks between
138 −assets with unrelated fundamentals \citep{kodres2002}, and dense networks are robust to small shocks
139 −but fragile to large ones \citep{acemoglu2015}. For measurement we use the spillover index of
140 −\citet{diebold2009}, its generalized directional form \citep{diebold2012} and its network
141 −interpretation \citep{diebold2014}, and we heed the warning of \citet{forbes2002} that correlation-based
142 −contagion tests must adjust for heteroskedasticity.
143 −
144 −\subsection{Algorithmic trading, flash crashes, monoculture and algorithmic collusion}
100 +Hypotheses H3 and H6 draw on the literature on funding constraints. Market liquidity and funding
101 +liquidity reinforce each other through margins, producing liquidity spirals \citep{brunnermeier2009};
102 +our margin-call mechanism follows this logic. Liquidation values fall when potential buyers are
103 +themselves constrained \citep{shleifer1992}, constrained arbitrageurs can amplify rather than correct
104 +mispricing \citep{shleifer1997} and may under-provide liquidity \citep{gromb2002}, forced sales push
105 +prices below fundamentals and propagate losses \citep{shleifer2011}, endogenous margins amplify the
106 +leverage cycle with leverage caps as a policy lever \citep{geanakoplos2010}, and procyclical
107 +intermediary leverage amplifies price moves \citep{adrian2010}. Across assets, losses propagate through reduced risk-bearing capacity \citep{kyle2001}, distressed
108 +selling induces endogenous correlation \citep{cont2013}, fire-sale spillovers scale with common
109 +exposures and leverage \citep{greenwood2015}, rebalancing transmits shocks between unrelated assets
110 +\citep{kodres2002}, and dense networks are fragile to large shocks \citep{acemoglu2015}. For measurement we use the spillover index of \citet{diebold2009}, its
111 +generalized directional form \citep{diebold2012} and network interpretation \citep{diebold2014}, and we
112 +heed the warning of \citet{forbes2002} that correlation-based contagion tests must adjust for
113 +heteroskedasticity.
114 +
115 +\subsection{Algorithmic trading, monoculture and algorithmic collusion}
145 116 \label{sec:literature:algo}
146 117
147 −The evidence on algorithmic and high-frequency trading is, on average, favourable to liquidity and
148 −price discovery: algorithmic trading improves liquidity \citep{hendershott2011}, high-frequency traders
149 −trade in the direction of permanent price changes and against transitory pricing errors
150 −\citep{brogaard2014}, and algorithmic strategies in foreign exchange improve price efficiency even
151 −though they are more correlated with each other than human trades \citep{chaboud2014}. That last
152 −finding is the one we take most seriously: correlation among algorithms is a documented fact, and its
153 −consequences depend on what the algorithms are correlated \emph{on}. High-frequency market makers
154 −behave as new market makers with tight inventory control \citep{menkveld2013}, a feature we build into
155 −the market-maker policy; \citet{menkveld2016} reviews the speed, adverse-selection and liquidity
156 −consequences, and \citet{foucault2016} show how fast traders reacting to common news signals affect
157 −informativeness and liquidity. The darker evidence concerns stress. During the Flash Crash,
158 −high-frequency traders did not trigger the crash but their synchronized inventory management amplified
159 −it \citep{kirilenko2017}; order-flow toxicity rose before the crash and liquidity provision withdrew
160 −when flow became toxic \citep{easley2011}; the August 2007 quant unwind revealed that common strategies
161 −produce simultaneous deleveraging \citep{khandani2011}; and ultrafast extreme events proliferate in a
162 −machine ecology operating beyond human response time \citep{johnson2013}. \citet{budish2015} show that
163 −continuous-time trading creates a latency arms race with correlated sniping and propose frequent batch
164 −auctions, which motivates our latency-matching treatment and call-auction robustness arm.
165 −
166 −The algorithmic-monoculture literature gives the homogenization mechanism a name. \citet{kleinberg2021}
167 −show that when many decision-makers rely on the same algorithm, outcomes can be worse even if that
168 −algorithm is individually more accurate; \citet{bommasani2021} identify homogenization as a
169 −structural risk of foundation models, and \citet{bommasani2022} document outcome homogenization
170 −across deployers of shared models. On the output side, generative AI raises individual creativity but
171 −reduces collective diversity \citep{doshi2024}, standard LLM outputs show diminished diversity of
172 −thought relative to human samples \citep{park2024brm}, and LLM assistance homogenizes human ideation
173 −\citep{anderson2024}; conversely, ensembles of twelve LLMs match human crowd forecasting accuracy
174 −\citep{schoenegger2024}, which illustrates the diversity benefit our H4 tests in a market. The
175 −algorithmic-collusion literature shows that learning algorithms can coordinate without being told to:
176 −Q-learning pricing algorithms learn tacit collusion \citep{calvano2020,klein2021}, algorithmic pricing
177 −raised margins in German retail gasoline \citep{assad2024}, asymmetric adoption softens competition
178 −\citep{brown2023}, LLM pricing agents reach supracompetitive prices with a sensitivity to prompt
179 −wording \citep{fish2024}, and LLM sellers in continuous double auctions collude more when they can
180 −communicate \citep{agrawal2025}. In securities markets, \citet{colliard2026} show that Q-learning market
181 −makers learn adverse-selection pricing but quote markups that shrink with the number of algorithms,
182 −and \citet{dou2025} show that reinforcement-learning informed speculators in a Kyle-type market sustain
183 −supracompetitive profits through price-trigger strategies or homogenized learning biases, lowering
184 −price informativeness and liquidity. \citet{dou2025} is the nearest theoretical neighbour of our
185 −mechanism; the difference is that their channel is strategic, whereas ours is a shared-error structure
186 −in signals, and that they have no language models.
187 −
188 −The policy literature has already drawn the systemic conclusion. \citet{gensler2020} argue that deep
189 −learning increases uniformity and interconnectedness because models built on the same data generate
190 −correlated predictions; \citet{danielsson2022} show that AI in risk management can create endogenous
191 −systemic risk through common models and procyclical responses; the \citet{fsb2024} warns that common AI
192 −models and data sources could raise correlations in trading, lending and pricing;
193 −\citet{danielsson2025} model AI as amplifying existing vulnerabilities and making crises faster;
194 −\citet{aldasoro2025} list monoculture and correlation among the financial-stability concerns of an
195 −intelligent financial system; and \citet{meng2026} propose a unified model in which the AI adoption
118 +The average evidence on algorithmic trading is favourable: it improves liquidity
119 +\citep{hendershott2011}, high-frequency traders trade with permanent price changes and against
120 +transitory errors \citep{brogaard2014}, and algorithmic strategies in foreign exchange improve price
121 +efficiency even though they are more correlated with each other than human trades
122 +\citep{chaboud2014}---the finding we take most seriously, since correlation among algorithms is a
123 +documented fact whose consequences depend on what they are correlated \emph{on}. High-frequency
124 +market makers keep tight inventory control \citep{menkveld2013}, a feature of our market-maker policy;
125 +\citet{menkveld2016} reviews the speed and adverse-selection consequences and \citet{foucault2016} show
126 +how fast traders reacting to common news affect informativeness and liquidity. Under stress the picture darkens: during the Flash Crash, high-frequency traders did not
127 +trigger the crash but their synchronized inventory management amplified it \citep{kirilenko2017};
128 +order-flow toxicity rose and liquidity withdrew when flow became toxic \citep{easley2011}; the August
129 +2007 quant unwind showed common strategies deleveraging simultaneously \citep{khandani2011}; and
130 +ultrafast extreme events proliferate in a machine ecology beyond human response time
131 +\citep{johnson2013}. \citet{budish2015} show that continuous trading creates a latency arms race with
132 +correlated sniping and propose frequent batch auctions, which motivates our matched-latency treatment
133 +and call-auction robustness arm.
134 +
135 +Algorithmic monoculture names the homogenization mechanism: when many decision-makers rely on the same
136 +algorithm, outcomes can be worse even if it is individually more accurate \citep{kleinberg2021};
137 +foundation models carry a homogenization risk because downstream systems inherit the same failure modes
138 +\citep{bommasani2021}, and shared models mis-serve the same individuals across deployers
139 +\citep{bommasani2022}. Generative AI raises individual creativity but reduces collective diversity
140 +\citep{doshi2024}, standard LLM outputs show diminished diversity of thought \citep{park2024brm}, and
141 +LLM assistance homogenizes human ideation \citep{anderson2024}; conversely, ensembles of twelve LLMs
142 +match human crowd forecasting accuracy \citep{schoenegger2024}, the diversity benefit that H4 tests in a
143 +market. Learning algorithms also coordinate without being told to: Q-learning pricing algorithms learn
144 +tacit collusion \citep{calvano2020,klein2021}, algorithmic pricing raised retail gasoline margins
145 +\citep{assad2024}, asymmetric adoption softens competition \citep{brown2023}, LLM pricing agents reach
146 +supracompetitive prices with a sensitivity to prompt wording \citep{fish2024}, and LLM sellers in double
147 +auctions collude more when they can communicate \citep{agrawal2025}. In securities markets, Q-learning
148 +market makers quote markups that shrink with the number of algorithms \citep{colliard2026}, and
149 +reinforcement-learning speculators in a Kyle-type market sustain supracompetitive profits through
150 +price-trigger strategies or homogenized learning biases, lowering informativeness and liquidity
151 +\citep{dou2025}. \citet{dou2025} is the nearest theoretical neighbour of our mechanism: their channel is
152 +strategic, ours is a shared-error structure in signals, and they have no language models. The policy
153 +literature has drawn the systemic conclusion: deep learning increases uniformity because models built
154 +on the same data generate correlated predictions \citep{gensler2020}; common AI models and data sources
155 +could raise correlations in trading, lending and pricing \citep{fsb2024}; AI amplifies existing
156 +vulnerabilities and makes crises faster \citep{danielsson2025}; monoculture and correlation are among
157 +the stability concerns of an intelligent financial system \citep{aldasoro2025}; and the AI adoption
196 158 share and signal correlation raise systemic coupling convexly, with 13F evidence of tail-loss
197 −amplification. None of these provides a controlled experiment; the present paper is an attempt to
198 −supply one.
159 +amplification \citep{meng2026}. None of these provides a controlled experiment.
199 160
200 161 \subsection{LLM agents in economics and markets}
201 162 \label{sec:literature:llm}
202 163
203 −\paragraph{LLMs as simulated subjects.}
204 −\citet{horton2023} proposed using LLMs as simulated economic agents to replicate classic behavioural
205 −experiments at low cost; \citet{aher2023} showed that LLMs can simulate multiple humans and replicate
206 −human-subject studies with fidelity that varies by model and task; and \citet{argyle2023} showed that
207 −conditioning on demographic backstories produces ``silicon samples'' matching sub-population response
208 −distributions. GPT choices in budgetary tasks score higher on rationality than humans and are largely
209 −stable across demographic prompts \citep{chen2023pnas}; chatbot choices in behavioural games fall within
210 −the human distribution and are more cooperative than the median human \citep{mei2024}; in repeated
211 −games LLMs cooperate well but coordinate poorly \citep{akata2025}; and expectations elicited from an
212 −LLM reading newspaper articles track professional forecasters and are extrapolative
213 −\citep{bybee2023}. \citet{manning2024} automate hypothesis generation and testing with LLM subjects, and
214 −\citet{immorlica2024} propose a framework in which generative agents are economic agents with new
215 −primitives such as prompts and context. Generative agents with memory, reflection and planning produce
216 −believable emergent social behaviour \citep{park2023}, social-network simulations reproduce
217 −information propagation \citep{gao2023s3}, and EconAgent produces realistic macroeconomic dynamics
218 −\citep{li2024econagent}. \citet{anthis2025} conclude that LLM social simulations are a promising
219 −method provided that validity concerns---homogeneity, sycophancy, drift---are addressed; homogeneity is
220 −the concern this paper turns into a treatment.
164 +\paragraph{Simulated subjects.}
165 +\citet{horton2023} proposed LLMs as simulated economic agents for replicating behavioural experiments
166 +at low cost; \citet{aher2023} showed that they can simulate multiple humans with fidelity varying by
167 +model and task, and \citet{argyle2023} that demographic backstories yield silicon samples matching
168 +sub-population responses. GPT choices score higher on rationality than humans and are stable across
169 +demographic prompts \citep{chen2023pnas}, chatbot choices in behavioural games fall within the human
170 +distribution \citep{mei2024}, LLMs cooperate well but coordinate poorly in repeated games
171 +\citep{akata2025}, and LLM-elicited expectations are extrapolative \citep{bybee2023}. Generative agents
172 +with memory and reflection produce believable emergent behaviour \citep{park2023,gao2023s3}, EconAgent
173 +produces realistic macroeconomic dynamics \citep{li2024econagent}, and \citet{immorlica2024} treat
174 +generative agents as economic agents with prompts as primitives \citep[see also][]{manning2024}.
175 +\citet{anthis2025} conclude that LLM social simulations are promising provided that homogeneity,
176 +sycophancy and drift are addressed; homogeneity is the concern this paper turns into a treatment.
221 177
222 178 \paragraph{Single-agent trading and backtests.}
223 −ChatGPT-derived sentiment on headlines predicts next-day returns \citep{lopezlira2023chatgpt}.
224 −FinMem \citep{yu2023finmem}, TradingGPT \citep{li2023tradinggpt} and FinAgent \citep{zhang2024finagent}
225 −evaluate one LLM agent, or a small team with layered memory and distinct characters, by backtesting on
226 −historical prices. In these designs the market does not react to the agent, so nothing can be learned
227 −about equilibrium prices, and \citet{lopezlira2025memorization} show that such backtests are
228 −contaminated by memorization of the historical period. Both points motivate a synthetic economy with
229 −an endogenous price and no history to memorize.
179 +ChatGPT-derived headline sentiment predicts next-day returns \citep{lopezlira2023chatgpt}. FinMem
180 +\citep{yu2023finmem}, TradingGPT \citep{li2023tradinggpt} and FinAgent \citep{zhang2024finagent}
181 +evaluate one agent, or a small team with layered memory, by backtesting on historical prices. The
182 +market does not react to the agent, so nothing is learned about equilibrium prices, and such backtests
183 +are contaminated by memorization of the historical period \citep{lopezlira2025memorization}---two
184 +reasons for a synthetic economy with an endogenous price and no history to memorize.
230 185
231 186 \paragraph{LLM-populated market simulators.}
232 −A second generation places many LLM agents on a matching engine. ASFM couples LLM traders to a real
233 −order-matching system and finds reactions to rate and inflation shocks consistent with economic priors
234 −\citep{gao2024asfm}; StockAgent adds bulletin-board communication and reports that GPT- and
235 −Gemini-driven agents differ systematically in trading behaviour \citep{zhang2024stockagent};
236 −TwinMarket scales to a social-media-connected population, reproduces bubbles and crashes, and reports
237 −that agents of one model family are more homogeneous \citep{yang2025twinmarket}; FCLAgent combines
238 −LLM-decided direction with rule-based sizing and reproduces path-dependent patterns absent from
239 −conventional agent-based models \citep{hashimoto2025}; StockSim provides an open order-level simulator
240 −with latency, slippage and price--time matching \citep{papadakis2025}; and \citet{li2026switch} find
241 −that LLM agents' trading-style switching in a year-long simulation is only partially consistent with
242 −behavioural-finance theory. \citet{lopezlira2025trade} is the closest in spirit: LLM agents in a
243 −persistent order-book simulation adhere to assigned strategies, generate price discovery and bubbles,
244 −and are analysed with partial-dependence-style perturbations. These papers establish that LLM markets
245 −\emph{can} produce the patterns we study. They are descriptive in the sense that the population
246 −composition is fixed, the information set of LLM agents is not held equal to that of the agents they
247 −replace, and the fundamental value, the efficient price and the transaction price are not separated.
187 +A second generation places many LLM agents on a matching engine. ASFM finds reactions to rate and
188 +inflation shocks consistent with economic priors \citep{gao2024asfm}; StockAgent adds bulletin-board
189 +communication and reports that GPT- and Gemini-driven agents differ systematically
190 +\citep{zhang2024stockagent}; TwinMarket scales to a social-media-connected population, reproduces
191 +bubbles and crashes, and reports that agents of one model family are more homogeneous
192 +\citep{yang2025twinmarket}; FCLAgent reproduces path-dependent patterns absent from conventional
193 +agent-based models \citep{hashimoto2025}; StockSim is an open order-level simulator with latency and
194 +price--time matching \citep{papadakis2025}; and trading-style switching in a year-long simulation is only
195 +partially consistent with behavioural-finance theory \citep{li2026switch}. \citet{lopezlira2025trade} is
196 +closest in spirit: LLM agents in a persistent order book adhere to assigned strategies, generate price
197 +discovery and bubbles, and are analysed with partial-dependence-style perturbations. These papers show
198 +that LLM markets \emph{can} produce the patterns we study; they hold the population composition fixed,
199 +do not equalize the information set of LLM agents with that of the agents they replace, and do not
200 +separate the fundamental value, the efficient price and the transaction price.
248 201
249 202 \paragraph{Experimental-finance replications.}
250 −A third strand replicates laboratory markets with LLM subjects. In Smith--Suchanek--Williams-type
251 −markets, LLM agents price near fundamentals and form only muted bubbles, with lower variance in their
252 −strategy text than humans \citep{henning2025}. In a simulated call auction, LLM agents exhibit
253 −disposition effects and extrapolative beliefs, and prompt interventions causally change bubble size
254 −\citep{ouyang2026}. Across fifteen models, \citet{saxena2026} find behaviour ranging from fundamental
255 −pricing to human-like bubbles, with mixed populations unstable and advanced models adapting to exploit
256 −weaker ones. Replications of \citet{smith1962} find slower or absent convergence with model-family
257 −differences \citep{struski2026}, and the trading institution alone moves the efficiency of identical
258 −LLM agents from 56\,\% to 89\,\% \citep{chupilkin2026}. These studies answer the question of
259 −behavioural fidelity---whether LLMs behave like humans---and their answers conflict. Our results do not
260 −require fidelity: they require only that errors be correlated within a model.
203 +In Smith--Suchanek--Williams-type markets, LLM agents price near fundamentals and form only muted
204 +bubbles, with less variable strategy text than humans \citep{henning2025}; in a call auction they
205 +exhibit disposition effects and extrapolative beliefs, and prompt interventions causally change bubble
206 +size \citep{ouyang2026}; across fifteen models, behaviour ranges from fundamental pricing to human-like
207 +bubbles, mixed populations are unstable, and advanced models adapt to exploit weaker ones
208 +\citep{saxena2026}; replications of \citet{smith1962} converge more slowly or not at all, with
209 +model-family differences \citep{struski2026}; and the institution alone moves the efficiency of
210 +identical agents from 56\,\% to 89\,\% \citep{chupilkin2026}. These studies ask whether LLMs behave
211 +like humans and their answers conflict. Our results do not require fidelity, only that errors be
212 +correlated within a model.
261 213
262 214 \paragraph{Correlated behaviour from shared inputs.}
263 −The single paper that measures correlated trading from shared inputs is \citet{song2026}: overlap in
215 +The one paper that measures correlated trading from shared inputs is \citet{song2026}: overlap in
264 216 retrieved documents among retrieval-augmented trading agents raises mean pairwise action correlation
265 −from 0.04 to 0.41 and increases position volatility without raising profits. That design is a backtest
266 −without market feedback, so it cannot speak to informativeness or liquidity. \citet{cho2025} document
267 −peer conformity in LLM multi-agent systems, with the confidence gap and format of peer information
268 −controlling its strength. In LLM re-creations of herding experiments, \citet{hansen2025} find that AI
269 −agents rely more on private signals than humans but can be prompted to herd when maximizing profit.
270 −Finally, LLM service behaviour drifts over months \citep{chen2024hdsr}, which is why we cache every
271 −response and replay from a frozen decision library.
217 +from 0.04 to 0.41 and increases position volatility without raising profits. That is a backtest without
218 +market feedback, so it cannot speak to informativeness or liquidity. \citet{cho2025} document peer
219 +conformity in LLM multi-agent systems, and \citet{hansen2025} find in re-created herding experiments
220 +that AI agents rely more on private signals than humans but can be prompted to herd when maximizing
221 +profit. LLM service behaviour drifts over months \citep{chen2024hdsr}, which is why every response is
222 +cached and replayed from a frozen library.
272 223
273 224 \subsection{What this paper adds}
274 225 \label{sec:literature:adds}
@@ -277,22 +228,21 @@ We should be candid about overlap. The hypothesis that shared models correlate t
277 228 is stated by \citet{gensler2020}, the \citet{fsb2024} and \citet{danielsson2025}, modelled as
278 229 homogenized learning biases by \citet{dou2025}, and measured in a backtest by \citet{song2026}.
279 230 Model-family homogeneity inside LLM markets has been observed by \citet{yang2025twinmarket},
280 −\citet{zhang2024stockagent} and \citet{saxena2026}. LLM-populated order-book simulators exist and are
281 −open source \citep{lopezlira2025trade,papadakis2025,gao2024asfm}. Our simulator is therefore not the
282 −contribution; the design is. First, we treat the LLM penetration share as a randomized treatment
283 −assigned to trader slots by an independent seed stream, so that matched pairs across the share share
284 −every other stream---fundamentals, news, endowments, latencies, policy assignment, activation,
285 −tie-breaks---and differences in outcomes are causal effects on the same market history
286 −(Section~\ref{sec:identification}). Second, we separate three prices: the latent value known to the
287 −simulator only, the public value implied by announced dividends and news, and the transaction price, so
288 −that an information-efficiency loss can be distinguished from transitory pressure
289 −\citep{hasbrouck1993,hendershott2014}. Third, we derive the shared-error mechanism in a Kyle--Hellwig
290 −setting with a model-specific common error and an effective number of models, and then test it by
291 −intervention---prompt diversity at equal share, a two-model mix, inherited information sets, memory,
292 −communication cohorts---so that the effective number of models is manipulated rather than observed.
293 −Fourth, we report the realized dose (the share of orders and volume actually originated by LLM agents)
294 −alongside the assigned share, and we run fairness designs with equal decision opportunities, equal
295 −dollar budgets and matched latency, because naive comparisons confound ``being an LLM'' with being slow
296 −and infrequent. Fifth, the leverage interaction and the safeguard counterfactuals connect the LLM
231 +\citet{zhang2024stockagent} and \citet{saxena2026}, and open LLM order-book simulators exist
232 +\citep{lopezlira2025trade,papadakis2025,gao2024asfm}. Our simulator is not the contribution; the design
233 +is. First, the LLM penetration share is a randomized treatment assigned to trader slots by an
234 +independent seed stream, so that matched pairs share every other stream---fundamentals, news,
235 +endowments, latencies, policy assignment, activation, tie-breaks---and paired differences are causal
236 +effects on the same market history (Section~\ref{sec:identification}). Second, we separate three
237 +prices---the latent value known to the simulator only, the public value implied by announced dividends
238 +and news, and the transaction price---so that an efficiency loss can be distinguished from transitory
239 +pressure \citep{hasbrouck1993,hendershott2014}. Third, we derive the shared-error mechanism in a
240 +Kyle--Hellwig setting with a model-specific common error and an effective number of models, then test
241 +it by intervention---prompt diversity at equal share, a two-model mix, inherited information sets,
242 +memory, communication cohorts---so that the effective number of models is manipulated rather than
243 +observed. Fourth, we report the realized dose, the share of orders and volume actually originated by
244 +LLM agents, alongside the assigned share, and run fairness designs with equal decision opportunities,
245 +equal dollar budgets and matched latency, because naive comparisons confound being an LLM with being
246 +slow and infrequent. Fifth, the leverage interaction and safeguard counterfactuals connect the LLM
297 247 literature to \citet{brunnermeier2009}, \citet{thurner2012} and \citet{kirilenko2017}, with thresholds,
298 248 estimators and seed counts fixed before the first confirmatory call.
modified paper/sections/model.tex +140 −1
@@ -1 +1,140 @@
1 −% model — to be written
1 +\section{The experimental economy}
2 +\label{sec:model}
3 +
4 +This section describes the simulated economy exactly as it is implemented in the \code{market\_agents} package.\footnote{Source files are cited in footnotes as \code{module.py}; all paths are relative to \code{src/market\_agents/}. Default parameter values are those of \code{configs/baseline.yaml}; every value is listed again in Appendix~\ref{app:hyperparameters}.} The economy has one risky asset (optionally $K\in\{2,\dots,5\}$ assets with a common factor), riskless cash, a continuous double auction with explicit latency, fees and risk limits, a population of trader slots filled by seven documented classical policies, and a treatment that replaces a fraction of those slots by a large-language-model (LLM) policy. Nothing in the simulator imposes an equilibrium: prices are whatever the order book produces.
5 +
6 +\paragraph{Time, units and accounting.}
7 +Model time is a discrete tick $t=0,1,\dots,T-1$ with $T=600$ in the baseline; outcome statistics are computed on $t\ge 100$. There is no calendar mapping. Prices are integers in ticks (one tick $=1$~cent), quantities are integers in lots, and cash is an integer in cents. Every trade moves exactly $p\,q$ cents from buyer to seller and $q$ lots from seller to buyer; fees are integers computed as $\operatorname{round}(p\,q\cdot \text{bps}/10^4)$ and are paid to the exchange. After every fill and every dividend the ledger asserts the two conservation identities
8 +\begin{equation}
9 +\label{eq:conservation}
10 +\sum_{i} C_{i,t} + F_t \;=\; \sum_i C_{i,0} + \sum_{s\le t} D^{\text{paid}}_s,
11 +\qquad
12 +\sum_i Q^{k}_{i,t} \;=\; \sum_i Q^{k}_{i,0}\quad (k=1,\dots,K),
13 +\end{equation}
14 +where $C_{i,t}$ is agent $i$'s cash, $F_t$ the cumulative fees collected by the exchange, $D^{\text{paid}}_s$ the cash injected by dividends and $Q^k_{i,t}$ the lots of asset $k$ held.\footnote{\code{exchange/accounts.py}, \code{Ledger.check\_conservation}; the check also verifies that reserved cash and reserved lots never exceed holdings for unleveraged agents.} The exchange never creates shares and there is no outside money other than dividends, so wealth is redistributed, not created, by trading.
15 +
16 +\paragraph{Dividends and the latent-timing convention.}
17 +Asset $k$ pays a cash dividend every $\Delta=20$ ticks. Index dividend dates by $n=1,2,\dots$ (paid at $t=n\Delta$) and write
18 +\begin{equation}
19 +\label{eq:dividend}
20 +D_n \;=\; \mu_{s_n} + x_n + J_n,
21 +\qquad
22 +x_{n+1} \;=\; \rho\, x_n + \sigma\, \varepsilon_{n+1},\quad \varepsilon_{n+1}\sim N(0,1),
23 +\end{equation}
24 +with mean dividend $\mu=50$~cents, persistence $\rho=0.98$ and innovation scale $\sigma=2$~cents per period. The regime $s_n\in\{0,1\}$ (a two-state Markov chain shifting $\mu$ by a factor $1+\texttt{mu\_shift}$) and the compound-Poisson jump $J_n$ are available but switched off in every design reported here ($\texttt{mu\_shift}=\texttt{p\_switch}=\texttt{jump\_prob}=0$). With $K>1$ assets the innovations load on a common factor, $\varepsilon_k = b_k f + \sqrt{1-b_k^2}\,u_k$, with $b_k=0.7$ in the two-asset cells. The convention that matters for information is the \emph{timing}: when $D_n$ is paid at $t=n\Delta$ the deviation $x_n$ becomes public, and the deviation of the \emph{next} dividend, $x_{n+1}$, is drawn immediately but remains latent until $t=(n+1)\Delta$.\footnote{\code{assets/fundamentals.py}, \code{Fundamentals.step} and \code{\_draw\_next}: the dividend is paid, \code{x\_revealed} is set, the public filter is updated, then $x_{n+1}$ is drawn. The initial deviation $x_1$ is latent from $t=0$.} During the period $n\Delta\le t<(n+1)\Delta$ the state of the economy is therefore $x_{n+1}$, and all information (news, private signals) is about this already-drawn but unannounced number. No agent ever observes a future innovation.
25 +
26 +\paragraph{Two value benchmarks.}
27 +Let $r=0.25\%$ be the discount rate per dividend period. Discounting the expected dividend stream given the latent state gives the \emph{latent fundamental value}
28 +\begin{equation}
29 +\label{eq:V}
30 +V_t \;=\; \frac{\mu}{r} + \frac{x_{n(t)+1}}{1+r-\rho},
31 +\qquad n(t)=\lfloor t/\Delta\rfloor,
32 +\end{equation}
33 +because $\mathbb{E}[x_{n+k}\mid x_{n+1}]=\rho^{k-1}x_{n+1}$ and $\sum_{k\ge1}\rho^{k-1}(1+r)^{-k}=1/(1+r-\rho)$. At the baseline $\mu/r=20{,}000$ cents, so $V\approx 200.00$ currency units and the price--dividend ratio is about~400; one cent of next-dividend deviation is worth $1/(1+r-\rho)\approx 44.4$ cents of value. The \emph{public value} applies the same closed form to the public posterior mean $\hat{x}_t$ of the latent deviation,
34 +\begin{equation}
35 +\label{eq:Vpub}
36 +V^{\mathrm{pub}}_t \;=\; \frac{\mu}{r} + \frac{\hat{x}_t}{1+r-\rho},
37 +\end{equation}
38 +where $\hat x_t$ is maintained by a Kalman filter: after the dividend at $n\Delta$ the prior is $\hat x=\rho x_n$ with variance $\Sigma=\sigma^2$ (the dividend reveals $x_n$ exactly when jumps are off), and each public news item $y$ with stated noise $\eta$ updates $\hat x\leftarrow \hat x + \kappa\,(y-\hat x)$, $\Sigma\leftarrow(1-\kappa)\Sigma$, $\kappa=\Sigma/(\Sigma+\max(\eta,0.1)^2)$.\footnote{\code{assets/fundamentals.py}, \code{public\_observe}; the regime and jump branches of \code{\_update\_public} are a two-state mixture filter and a Kalman step on a noisy dividend, unused in the reported designs.} Hence $V^{\mathrm{pub}}_t\neq V_t$ in general: the two coincide only at dividend dates, and they diverge most right after a latent shock.
39 +
40 +Four price concepts are kept apart throughout the paper. (i) The \emph{fundamental value} $V_t$ of \eqref{eq:V} is latent and is never shown to any agent; it is the benchmark for pricing error, bubbles and absorption. (ii) The \emph{efficient (public) price} $V^{\mathrm{pub}}_t$ of \eqref{eq:Vpub} is the value a risk-neutral agent would assign using public information only; it is shown to every agent. (iii) The \emph{transaction price} is the price of the last trade, in ticks. (iv) The \emph{quote midpoint} $P_t=(a_t+b_t)/2$ is the price used in every outcome statistic; when one side of the book is empty the engine falls back to the last trade price and then to the reference price.\footnote{\code{exchange/engine.py}, \code{Exchange.mid\_ticks}; in call-auction mode the midpoint is replaced by the last clearing price because the book may be crossed between auctions.} Deviations are measured as $\log P_t-\log V_t$ (and, as a robustness check, relative to $V^{\mathrm{pub}}_t$).
41 +
42 +\paragraph{Information.}
43 +Three channels carry information about the latent deviation. \emph{Public news} arrives each tick with probability $0.02$ per asset. An item reports $y=x_{n+1}+\nu+\beta$, with credible noise $\nu\sim N(0,\eta^2)$, $\eta=1$~cent, and a bias $\beta=\pm10$~cents (random sign) with probability \code{misleading\_share} ($0$ in the baseline, $0.5$ in the misleading-news cell). Every item is rendered as one sentence (``Analyst note: next dividend expected $|y|$ cents above/below its long-run mean'') and updates the public filter with the \emph{stated} noise $\eta$ whether or not it is biased, so misleading items fool $V^{\mathrm{pub}}$ by construction.\footnote{\code{signals/news.py}, \code{NewsFeed.step}.} Agents see the items of the last 20 ticks. \emph{Private signals} are drawn at every activation of an informed agent, $s_i=x_{n+1}+\xi_i$, $\xi_i\sim N(0,\sigma^2/\pi_i)$, with precision $\pi_i=2$ (signal standard deviation $\sigma/\sqrt{2}\approx1.4$~cents) for the 30\% of fundamentalist slots that are informed and $\pi_i=0$ otherwise; the draw is about the current latent state, never about future innovations.\footnote{\code{assets/fundamentals.py}, \code{private\_signal}; a new draw is made at each activation from the agent's own random stream.} \emph{Announced dividends} reveal $x_n$ at each dividend date. All three channels, plus the order book, trade tape, own account and open orders, are packaged into a single \code{Observation} object that is the only interface between the market and any policy, classical or LLM.\footnote{\code{agents/base.py}; the LLM prompt is a deterministic rendering of the same object (Appendix~\ref{app:prompts}).}
44 +
45 +\paragraph{The exchange.}
46 +Each asset trades on a continuous double auction with price--time priority.\footnote{\code{exchange/book.py} (matching), \code{exchange/engine.py} (latency, fees, risk, halts, event log).} Agents submit \emph{limit} orders (price in ticks), \emph{market} orders, and \emph{cancels} of their own resting orders. An order submitted at decision time $t$ by agent $i$ arrives at $t+\ell_i$, where the latency $\ell_i\sim U[0.05,1]$ tick is drawn once per slot and fixed for the run; the exchange processes arrivals in $(\text{arrival time},\ \text{sequence})$ order, so agents deciding at the same tick are ordered by latency and then by submission sequence. On arrival the engine checks, in order: the account is not frozen, the asset is not halted, $1\le q\le 10{,}000$ lots, and for limit orders a positive price within $\pm50\%$ of the current midpoint (price band). Limit bids reserve the notional plus the worst-case fee plus one cent per lot; market bids are capped to the largest quantity whose notional plus taker fee fits in free cash while walking the ask side; sells are clipped to free (unreserved) holdings and reserve them. A market order executes against the best quotes until filled or the opposite side is empty; the unfilled remainder is cancelled (immediate-or-cancel). Limit orders execute against crossing quotes and rest for the remainder. Partial fills are the norm. The taker pays $2$~basis points of notional, the maker pays nothing. Resting orders expire 50 ticks after submission. Short selling is disabled: a sell larger than free holdings is clipped, and a sell with no holdings is rejected.\footnote{\code{ExchangeConfig.allow\_short} and \code{Account.short\_allowed} both exist, but the population builder sets \code{short\_allowed=False} for every slot, so the \code{allow\_short} switch has no effect in the current code; see Appendix~\ref{app:hyperparameters}.}
47 +
48 +Three safeguards are implemented as treatments and are off in the baseline. A \emph{circuit breaker} halts an asset for 20 ticks when the last trade price has moved by more than 5\% relative to the midpoint 10 ticks earlier; resting orders are cancelled at the halt and arriving orders are rejected while halted. A \emph{position limit} rejects any order that would push $|{\rm position}+{\rm same\text{-}side\ resting}+q|$ above 300 lots. A \emph{periodic call auction} replaces continuous matching: orders rest without matching and every 5 ticks the book clears at the uniform price that maximises executable volume among limit prices within $\pm2\%$ of the last clearing price (ties go to the price nearest the reference); market orders are converted to band-edge limits for one auction and cancelled afterwards.\footnote{\code{exchange/call\_auction.py}; the later-submitted of two crossing resting orders is treated as the taker for fee purposes.} This last variant is a robustness check run with the mock provider only.
49 +
50 +\paragraph{Leverage, margin and bankruptcy.}
51 +Unleveraged slots can only spend free cash. ``Leveraged'' slots have a cap $L=3$ on gross exposure over equity: their buying power is free cash plus $(L-1)\times$equity, and an order is rejected if the post-trade gross exposure (valued at the limit price or the midpoint) would exceed $L\times$equity. At the end of every tick the exchange marks every account to the midpoint. A leveraged account whose equity-to-gross-exposure ratio falls below the maintenance margin $m=0.25$ receives a margin call: all its resting orders are cancelled and a market order for $50\%$ of each position is submitted at the start of the next tick, tagged \code{liquidation} so that it bypasses the position and leverage checks. Any account with non-positive equity is declared bankrupt, frozen (no further orders accepted), and liquidated in full.\footnote{\code{exchange/engine.py}, \code{Exchange.margin\_check}, \code{\_liquidate}; liquidation orders are submitted by the runner at $t+1$.} Dividends on short positions would be charged, but shorts never arise.
52 +
53 +\paragraph{Classical policies.}
54 +Trader slots are filled by seven heuristics, summarised in Table~\ref{tab:policies}; each sees only the \code{Observation} and returns at most a handful of intents.\footnote{\code{agents/classical.py}. Order sizes are capped by the policy's \code{max\_qty} and by what free cash or holdings allow (\code{\_affordable} divides free cash by $1.001\times$ the reference price).} Each classical agent is activated independently each tick with its type's probability (``activity''). Fundamentalists form a posterior value $\hat V_i=V^{\mathrm{pub}}+w_i\,(s_i-\hat x)/(1+r-\rho)$ with $w_i=\pi_i/(1+\pi_i)$ (so uninformed fundamentalists use $V^{\mathrm{pub}}$ exactly), trade when $|\hat V_i/P-1|$ exceeds 0.5\%, and size the order at 20 lots per percent of mispricing; a mispricing beyond twice the threshold makes the limit order marketable. Trend followers compare the mean midpoint over the last 5 ticks with the mean over the last 30 and trade in the direction of the difference once it exceeds 0.2\%; mean reverters trade against the $z$-score of the midpoint relative to its 40-tick mean when $|z|>1.5$. Noise traders are zero-intelligence-plus liquidity demanders in the spirit of \citet{gode1993}: random side, size $1+\mathrm{Geometric}(1/8)$, a market order with probability 0.6 or otherwise a limit order placed on the passive side at a distance $|N(0,(0.001\,P)^2)|$ from the best quote. Market makers are inventory-aware two-sided quoters: with inventory deviation $I$ from the endowment they quote around the reservation price $P\,(1-0.02\,I/200)$ with half-spread $\operatorname{clip}(8+\hat\sigma,1,60)$ ticks, where $\hat\sigma$ is the standard deviation of tick-to-tick midpoint changes over 30 ticks, 20 lots per side, re-quoting (cancelling all) at every activation; they stop quoting on one side when $|I|$ reaches 200 lots and withdraw entirely during a liquidity shock. They are endowed with five times the standard endowment and are never replaced by LLM agents. Leveraged traders run the trend rule with size 30 and market-order probability 0.7 on an account with $L=3$. Cross-asset arbitrageurs trade the residual of a 60-tick log-price regression between the two first assets when its $z$-score exceeds 1.5, and are inactive in single-asset runs. The baseline mix and the behavioural parameters were frozen by the calibration sweep of Section~\ref{sec:implementation}.
55 +
56 +\begin{table}[t]
57 +\centering
58 +\caption{Classical policies, baseline shares and parameters}
59 +\label{tab:policies}
60 +\small
61 +\begin{threeparttable}
62 +\begin{tabular}{@{}l r r p{4.7cm} p{4.1cm}@{}}
63 +\toprule
64 +Type & Share & Activity & Rule & Key parameters \\
65 +\midrule
66 +Fundamentalist & 25\% & 0.20 & Trade toward $\hat V_i=V^{\mathrm{pub}}+w_i(s_i-\hat x)/(1+r-\rho)$ when $|\hat V_i/P-1|>$ threshold; marketable limit beyond $2\times$ threshold & threshold 0.5\%; 20 lots per 1\% mispricing; $p_{\rm mkt}=0.3$; max 50; improve 1 tick; 30\% informed, $\pi=2$ \\
67 +Trend follower & 6\% & 0.20 & Sign of $\bar P_{5}/\bar P_{30}-1$ when $|\cdot|>$ threshold; size scales with $\min(|s|/\text{thr},3)$ & threshold 0.2\%; size 20; $p_{\rm mkt}=0.6$; max 50 \\
68 +Mean reverter & 10\% & 0.20 & Fade $z=(P-\bar P_{40})/\mathrm{sd}_{40}$ when $|z|>$ threshold; limit at best quote $\pm1$ tick & threshold 1.5; size 15; $p_{\rm mkt}=0.2$; max 50 \\
69 +Noise trader & 50\% & 0.30 & Random side; size $1+\mathrm{Geom}(1/8)$; market order or passive limit at $|N(0,(10^{-3}P)^2)|$ from the best quote; cancels when $>3$ open orders & $p_{\rm mkt}=0.6$ (calibrated); max 40 \\
70 +Market maker & 5\% & 0.50 & Two-sided quotes around $P(1-0.02\,I/200)$, half-spread $\operatorname{clip}(8+\hat\sigma_{30},1,60)$ ticks; re-quotes every activation & 20 lots per side; max inventory 200; endowment $\times5$; never replaced \\
71 +Leveraged & 4\% & 0.20 & Trend rule on a leveraged account & size 30; $p_{\rm mkt}=0.7$; $L=3$; $m=0.25$; liquidation 50\% \\
72 +Cross-asset arb. & 0\% (5\% if $K=2$) & 0.20 & Pairs trade on the residual of a 60-tick log-price regression when $|z|>1.5$ & size 10; max 40; inactive if $K=1$ \\
73 +\bottomrule
74 +\end{tabular}
75 +\begin{tablenotes}\footnotesize
76 +\item Shares and activities from \code{configs/baseline.yaml} (\code{population.mix}, \code{population.activity}, \code{policy\_kwargs}); rules and defaults from \code{agents/classical.py}. $P$ is the midpoint, $\bar P_w$ its mean over the last $w$ ticks, $p_{\rm mkt}$ the probability that an order is a market order. With $N=100$ the integer counts are 25/6/10/50/5/4.
77 +\end{tablenotes}
78 +\end{threeparttable}
79 +\end{table}
80 +
81 +\paragraph{The LLM policy.}
82 +An LLM-controlled slot decides every 20 ticks from $t=50$ onward, at one of four staggered offsets $\{0,5,10,15\}$ drawn per slot, so that 27--28 decisions are made per agent in a 600-tick run and roughly a quarter of the LLM population decides in each batch.\footnote{\code{experiments/runner.py}, \code{Simulation.run}: an LLM slot is active when $(t-\text{offset})\bmod 20=0$ and $t\ge \texttt{warmup}/2=50$; batches are sent to the provider through a thread pool of width 8.} Each decision is built from a canonical snapshot: the five best levels of each side of the book, best bid/ask, midpoint and last trade; the midpoint history over the last 60 ticks (sub-sampled to ten points in the prompt, with the window return and the standard deviation of tick returns); the last 30 trades (volume and buyer-initiated share); the last dividend, the public posterior $\hat x_t$ and $V^{\mathrm{pub}}_t$; the news items of the last 20 ticks (the last three are shown); the agent's own private signal and precision if it is informed; its cash, free cash, holdings and marked equity; and its open orders. The system prompt states the market mechanics (dividend interval, $\mu$, $r$, $\mu/r$, $\rho$, the $1/(1+r-\rho)$ conversion, tick, taker fee, time-to-live, no-short rule, the 50-lot cap and the decision interval) and asks for a JSON object with exactly eight keys: \code{action}~$\in\{$hold, place, cancel$\}$, \code{side}, \code{order\_type}, \code{quantity}, \code{limit\_price}, \code{cancel\_order\_ids}, \code{confidence} and a one-sentence \code{rationale}. The reply is parsed leniently (code fences and surrounding text are stripped) and validated strictly: a \code{place} must carry side, type and a quantity in $[1,50]$; a limit price must lie within $\pm20\%$ of the midpoint; a market order needs a non-empty opposite side; a buy must cost at most free cash at the reference price $\times1.001$; a sell is clipped to holdings net of resting sells and is invalid when nothing is free; a \code{cancel} must name at least one order the agent owns; trading must not be halted. Any parse, schema or feasibility failure, provider error, or budget halt becomes a \emph{hold} that is logged with its reason, so the estimand is intention-to-treat on the slot rather than on successful calls.\footnote{\code{llm/schema.py}, \code{llm/policy.py} (\code{to\_action}); the validation rules are reproduced in Appendix~\ref{app:prompts}.} Realised shares of LLM agents, orders and volume are reported for every run.
83 +
84 +Four cognition treatments change only the prompt. \emph{Personas}: the single ``generalist'' system prompt, or eight diverse personas (value, momentum, contrarian, liquidity, risk-averse, macro, quant, generalist) assigned cyclically to LLM slots. \emph{Memory}: the last 5 own decisions (action, side, quantity, type, price, rejection reason, the midpoint and equity at the time) are appended. \emph{Communication channel}: LLM slots are grouped in cohorts of 5 in slot order, and the last 5 rationales posted in the cohort are shown as ``unverified opinions''. \emph{Adaptive feedback}: the percentage change in equity since the last decision and its cumulative sum are appended. Provider adapters cover the OpenAI Responses API, the Anthropic Messages API, a seeded mock that parses the user message and applies a value rule with a 5\% share of deliberately invalid answers, and a \emph{frozen library} provider that returns the recorded decision of one of the five nearest neighbours of the current observation in a seven-dimensional standardised feature space (mispricing versus $V^{\mathrm{pub}}$, window momentum, signal, inventory and cash ratios, spread, open-order flag), rescaling the limit price to the current midpoint. The library is a non-adaptive, non-reasoning replay of recorded decisions, used only to scale the population at zero cost; runs that use it are labelled provider \code{library} and are never pooled with online runs. Identical (provider, model, prompt version, system, user) requests are served from a SQLite cache and counted separately as cached hits; a per-run USD cap (and optional token and decision caps) halts further calls, after which every decision is a logged hold.\footnote{\code{llm/cache.py}, \code{llm/budget.py}, \code{llm/library.py}, \code{llm/providers/}.}
85 +
86 +\paragraph{Endowments.}
87 +Slot $i$ receives cash $C_i=\lfloor 2{,}000{,}000\,w_i\rfloor$ cents and $\operatorname{round}(100\,w_i)$ lots of each asset, where $w_i=\exp(0.3\,z_i)/\overline{\exp(0.3 z)}$, $z_i\sim N(0,1)$, is a lognormal wealth multiplier normalised to mean one across the population; at the initial price $\mu/r=200.00$ the endowment is split evenly between cash and stock. Market-maker slots receive five times these amounts. The initial reference price equals $\mu/r$, so the economy starts at the latent value when $x_1=0$.
88 +
89 +\paragraph{Population builder and matched replacement.}
90 +Slot characteristics (wealth multiplier, latency, information draw) are generated from the \code{slots} stream; the classical policy deck---the integer counts implied by the mix---is shuffled by the \code{assign} stream and dealt down the wealth ranking, which stratifies policy types across wealth terciles.\footnote{\code{experiments/population.py}, \code{build\_population}; streams are spawned from the master seed by \code{numpy.random.SeedSequence} (\code{utils/rng.py}): \code{fundamentals}, \code{news}, \code{slots}, \code{assign}, \code{llm\_assign}, \code{activation}, \code{tiebreak}, \code{mock\_llm}, \code{shuffle}, plus one generator per agent.} The LLM treatment then replaces $\operatorname{round}(\varphi N)$ of the non-market-maker slots, chosen by the dedicated \code{llm\_assign} stream with quotas proportional to each policy type's count among eligible slots (remainder at random), so the composition of the replaced population is held constant along the dose and $\varphi=1$ means 95 of 100 slots. A replaced slot keeps its endowment, latency, leverage cap and---crucially---its information set: an LLM agent that replaces an informed fundamentalist receives the same private signal precision, and one that replaces any other type receives none.\footnote{\code{LLMCfg.informed\_share: null}; a number instead overrides the inherited status (information-equalisation variant). The matched-latency variant sets \code{latency.llm\_latency}.} Providers are allocated to LLM slots in proportion to \code{provider\_mix} and shuffled; prompt personas, stagger offsets and cohorts are assigned from the same stream. Because activation draws, within-tick ordering and all other streams are untouched by $\varphi$, a treatment and its control share the dividend path, the news, every classical agent's endowment, latency, activations and signals; the only difference is the policy of the replaced slots.
91 +
92 +\paragraph{Shocks.}
93 +Scheduled events are applied after the dividend step of the tick at which they occur and are written to the event log so that matched pairs share their timing.\footnote{\code{experiments/runner.py}, \code{\_apply\_shocks}; \code{signals/news.py}, \code{Shock}.} A \emph{fundamental jump} adds \code{size} cents to the latent deviation $x_{n+1}$; the baseline applies $+10$~cents at $t=300$, which raises $V_t$ by $10/(1+r-\rho)\approx 4.44$ currency units ($+2.2\%$), while $V^{\mathrm{pub}}_t$ moves only when news reports the new state or the dividend at $t=320$ reveals it. A \emph{rate} shock adds \code{size} to $r$; a \emph{funding} shock multiplies every leverage cap by $1-\text{size}$ (0.5 in the leverage cells, i.e.\ $3\times\to1.5\times$); a \emph{liquidity} shock makes all market makers withdraw their quotes until the matching \code{liquidity\_end} event (60 ticks in the liquidity cell); a \emph{news burst} injects a credible item with noise 0.5 reporting \code{size} and updates the public filter; a \emph{placebo} is logged and changes nothing. The placebo at $t=450$ is present in every baseline-derived cell and the placebo-only cell replaces the jump with a second placebo.
94 +
95 +\paragraph{Implementation assumptions and their checks.}
96 +Table~\ref{tab:assumptions} condenses the assumption register maintained with the code (\code{docs/assumptions.md}). Each row states an assumption as implemented and the pre-specified check that probes it; the register's identifiers are reproduced with the prefix ``I'' to avoid confusion with the theoretical Assumptions A1--A7 of Section~\ref{sec:theory}.
97 +
98 +\begin{small}
99 +\begin{longtable}{@{}p{0.7cm} p{8.6cm} p{5.6cm}@{}}
100 +\caption{Implementation assumptions (register \code{docs/assumptions.md}) and robustness checks}
101 +\label{tab:assumptions}\\
102 +\toprule
103 +ID & Assumption as implemented & Robustness check \\
104 +\midrule
105 +\endfirsthead
106 +\toprule
107 +ID & Assumption as implemented & Robustness check \\
108 +\midrule
109 +\endhead
110 +\bottomrule
111 +\endfoot
112 +I1 & 600 ticks, outcomes on $t\ge100$; no calendar mapping & $T=1{,}000$ on the control \\
113 +I2 & Dividend every 20 ticks, eq.~\eqref{eq:dividend} with $\mu=50$c, $\rho=0.98$, $\sigma=2$c, $r=0.25\%$; one asset & $\rho\in\{0.95,0.98\}$, $\sigma\in\{1,2,4\}$ \\
114 +I3 & Benchmark = latent value $V_t$ of eq.~\eqref{eq:V}, risk neutral, constant $r$ & Deviations from $V^{\mathrm{pub}}$ and from the end-of-run mean price also reported \\
115 +I4 & Public value = Kalman posterior from dividends and news (timing convention above); $V^{\mathrm{pub}}\ne V$ within a period & Noisy-dividend variant (\code{jump\_prob} 1, \code{jump\_sigma} 4c) \\
116 +I5 & No look-ahead: signals and news concern the latent current state & Unit test of \code{private\_signal}; prompt built from \code{Observation} only \\
117 +I6 & Latent jump $+10$c at $t=300$ ($+2.2\%$ of $V$), placebo at $t=450$ & Sizes 10/20/40c; \code{news\_burst} variant \\
118 +I7 & Continuous double auction, price--time priority, limit/market/cancel, partial fills, IOC remainder, integer ticks and lots & Call auction every 5 ticks, $\pm2\%$ collar (mock only) \\
119 +I8 & Latency $U[0.05,1]$ per agent, fixed; arrival-order processing; LLM slots draw like others & Matched latency \code{llm\_latency} 0.05 \\
120 +I9 & Taker 2 bp, maker 0, fees leave the economy; conservation asserted after every fill & Taker 0 and 5 bp \\
121 +I10 & TTL 50 ticks; price band $\pm50\%$; max order 10,000 lots (policies cap at 40--50) & TTL 20/100 \\
122 +I11 & No short selling; buys limited by free (unreserved) cash & \code{allow\_short} variant (currently inert, see footnote in the exchange paragraph) \\
123 +I12 & Leverage only for leveraged slots: cap $3\times$, maintenance 25\%, forced sale of 50\% by market order next tick, bankrupt agents frozen & Cap $2\times/5\times$; maintenance 15\%/35\% \\
124 +I13 & Safeguards off by default; breaker 5\% over 10 ticks, 20-tick halt, resting cancelled; position limit 300 & Halt 3\%/8\%; limit 150/600 \\
125 +I14 & Five bounded inventory-skewed market makers (half-spread 8 ticks $+$ volatility, skew 2\% at 200 lots), endowed $\times5$, never replaced & \code{quote\_qty} 10/20; liquidity shock \\
126 +I15 & No rationality or equilibrium assumption: classical agents are documented heuristics, LLM agents output whatever the model returns & Calibration sweep (360 runs) \\
127 +I16 & Mix 25/6/10/50/5/4\% (fundamentalist/trend/mean-rev/noise/MM/leveraged), stratified by wealth; endowment $20{,}000+100$ shares $\times$ lognormal(0.3) & Trend share 10--25\% in the sweep; dispersion 0/0.6 \\
128 +I17 & Classical activation Bernoulli(0.2--0.5) per tick; LLM decisions every 20 ticks from $t=50$, staggered & Realised order/volume shares reported; \code{decision\_interval} 10 (mock) \\
129 +I18 & 30\% of fundamentalists informed ($\pi=2$); an LLM slot inherits the information of the slot it replaces & Stratum composition reported; \code{llm.informed\_share} 0 variant \\
130 +I19 & Stratified replacement, market makers excluded (95 eligible slots) & \code{llm\_replace: noise} at $\varphi=0.25$ (mock) \\
131 +I20 & LLM observation = canonical snapshot (5 levels, 60-tick history, 30 trades, last dividend, $V^{\mathrm{pub}}$, $\le3$ news, own signal, account, open orders); prompt v1 frozen & Prompt audit \\
132 +I21 & Invalid or infeasible decisions become hold (quantity, $\pm20\%$ price window, cash/holdings, owned cancel ids); sells clipped & Validity reported per cell; cells $<80\%$ flagged \\
133 +I22 & Provider sampling is not seeded; cached responses replay exactly; the library is a nearest-neighbour map, not an agent & Cache-hit share and online/replay flag per run \\
134 +I23 & Equal decision opportunities by default (27--28 per agent); equal USD via per-run \code{usd\_cap} & Fairness cell \\
135 +I24 & Midpoint is the price in every metric (fallback to last trade); effective spread uses the start-of-tick midpoint & \code{msd\_log} on last trade prices \\
136 +I25 & Bubble: $|P/V-1|>5\%$ for $\ge10$ ticks; crash: drop $\ge10\%$ from the trailing 20-tick max; absorption: $|P/V-1|<1\%$ for 5 ticks, censored at 100 & Grid 3/5/8\% and 7/10/15\% on saved ticks \\
137 +I26 & Coordination measured among LLM agents (sign correlation in 20-tick windows; synchrony per decision round) & Classical analogue from signed trades (\code{flow\_corr\_by\_policy}) \\
138 +I27 & Two-asset variant: factor loading 0.7; LLM prompt covers asset 0 only & Spillover reported from the LLM-traded to the classical-only asset \\
139 +\end{longtable}
140 +\end{small}
modified paper/sections/theory.tex +4 −4
@@ -8,8 +8,8 @@ their trading signal from the \emph{same} model, their errors stop averaging out
8 8 informative, orders become synchronized, and leverage-driven feedback becomes harder to contain. The
9 9 model is deliberately stylized. It is a one-period, partial-equilibrium pricing exercise in the spirit of
10 10 \citet{kyle1985} and \citet{hellwig1980}, with the correlated-signal structure of
11 −\citet{fosterviswanathan1996} reinterpreted as the signature of a shared foundation model or prompt
12 −family \citep{kleinbergraghavan2021}. Order submission is mechanical (linear in the signal) and the
11 +\citet{foster1996} reinterpreted as the signature of a shared foundation model or prompt
12 +family \citep{kleinberg2021}. Order submission is mechanical (linear in the signal) and the
13 13 response intensity is exogenous; the propositions below are therefore statements about how an
14 14 \emph{aggregation} mechanism transmits correlated errors into prices, not general-equilibrium results.
15 15 We state this limitation plainly wherever it matters.
@@ -53,7 +53,7 @@ A2 mirrors rule-like policies: an LLM agent given a snapshot returns an order wh
53 53 the strength of the signal it extracts, and it does not condition on the price impact of other agents
54 54 using the same model. The intensity $\beta$ is a primitive here, not an equilibrium object; a
55 55 CARA/price-taking version in which $\beta$ depends on $\lambda$ and on risk aversion
56 −\citep{hellwig1980,grossmanstiglitz1980} is possible but is not needed for the comparative statics
56 +\citep{hellwig1980,grossman1980} is possible but is not needed for the comparative statics
57 57 we test, and we do not pursue it.
58 58
59 59 \begin{assumption}[A3: competitive risk-neutral market maker]
@@ -288,7 +288,7 @@ traders, but the source here is mechanical.
288 288 \subsection{Liquidity spiral}
289 289 \label{sec:theory:spiral}
290 290
291 −Finally, we add leverage, following the margin-spiral logic of \citet{brunnermeierpedersen2009}. A
291 +Finally, we add leverage, following the margin-spiral logic of \citet{brunnermeier2009}. A
292 292 cohort $L$ of $N_L$ leveraged traders holds the aggregate position $X$ (in lots) financed with equity
293 293 $W$ under a maintenance requirement of $m>0$ units of equity per lot held.\footnote{If the simulator's
294 294 maintenance margin rate is $\hat m$ and the pre-shock price is $\bar P$, then $m=\hat m\bar P$ with the
modified results/figures/fig_baseline_paths.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_lambda_bp_100.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_mad_pct.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_max_dd.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_n_bubbles.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_spread_bp.pdf +0 −0

Binary file not shown.

modified results/figures/fig_classical_time_to_absorb.pdf +0 −0

Binary file not shown.

modified results/figures/fig_contagion_excess.pdf +0 −0

Binary file not shown.

modified results/figures/fig_contagion_spillover.pdf +0 −0

Binary file not shown.

modified results/figures/fig_wealth_depth.pdf +0 −0

Binary file not shown.

modified results/tables/tab_classical_effects.csv +20 −20
@@ -1,66 +1,66 @@
1 1 outcome,level,n,control_mean,treat_mean,diff,se,ci_lo,ci_hi,t,p,p_wilcoxon,cohen_d,pct_change,cell,p_holm
2 −mad_pct,placebo_shock,30,0.6282540919469869,0.5228496884134598,-0.105404403533527,0.0260831529140309,-0.158750441033583,-0.052058366033470985,-4.041091346622702,0.0003577844519032219,0.00012333691120147705,-0.7377989624947274,-16.777352489161533,placebo_shock,0.0010733533557096657
2 +mad_pct,placebo_shock,30,0.6282540919469869,0.5228496884134599,-0.10540440353352701,0.026083152914030898,-0.15875044103358302,-0.052058366033471005,-4.041091346622704,0.0003577844519032193,0.00012333691120147705,-0.7377989624947277,-16.777352489161533,placebo_shock,0.0010733533557096579
3 3 time_to_absorb,placebo_shock,0,,,,,,,,,,,,placebo_shock,
4 4 spread_bp,placebo_shock,30,20.68987407852468,19.02562407892965,-1.664249999595028,0.9104903111467902,-3.526411772827072,0.19791177363701595,-1.8278612954143953,0.07787609209897943,0.09194962680339813,-0.3337202878296929,-8.043789891029146,placebo_shock,0.15575218419795886
5 5 n_bubbles,placebo_shock,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,placebo_shock,
6 6 max_dd,placebo_shock,30,0.03313559734901605,0.03124532261713216,-0.0018902747318838898,0.0019984533683765375,-0.005977570799307531,0.002197021335539752,-0.9458688212572467,0.35203053516393307,0.5676916025573617,-0.17269122994780536,-5.704664720462692,placebo_shock,0.35203053516393307
7 −mad_pct,trend_heavy,30,0.6282540919469868,1.4894389768807208,0.8611848849337337,0.1246024914283476,0.606344175980891,1.1160255938865764,6.911457989818415,1.3536543920906025e-07,5.587935447692871e-09,1.2618538154242858,137.07588951229656,trend_heavy,5.41461756836241e-07
7 +mad_pct,trend_heavy,30,0.6282540919469869,1.4894389768807208,0.8611848849337337,0.12460249142834758,0.6063441759808911,1.1160255938865764,6.911457989818417,1.353654392090594e-07,5.587935447692871e-09,1.261853815424286,137.07588951229653,trend_heavy,5.414617568362376e-07
8 8 time_to_absorb,trend_heavy,30,25.266666666666666,34.46666666666667,9.2,5.806140229775379,-2.6748901043157947,21.07489010431579,1.584529418152878,0.12391908710163213,0.5270892568655381,0.28929416845095574,36.41160949868073,trend_heavy,0.12391908710163213
9 9 spread_bp,trend_heavy,30,20.68987407852468,34.62717383838306,13.937299759858377,2.3683605689552474,9.09345852097283,18.781140998743922,5.884787959464519,2.185900359452944e-06,6.917864084243774e-06,1.0744103705111714,67.36290277534737,trend_heavy,6.557701078358832e-06
10 10 n_bubbles,trend_heavy,30,0.0,0.13333333333333333,0.13333333333333333,0.06312427686319992,0.004229691154525234,0.2624369755121414,2.112235418114766,0.04339741781077554,0.045500263896358396,0.38563966175427117,,trend_heavy,0.08679483562155108
11 11 max_dd,trend_heavy,30,0.03313559734901605,0.06073696070483838,0.027601363355822337,0.0035535803541489716,0.02033347547981643,0.03486925123182825,7.767198319744326,1.4490308139866929e-08,6.910413503646851e-07,1.4180899094467303,83.29822174351703,trend_heavy,7.245154069933464e-08
12 −mad_pct,leverage_heavy,30,0.6282540919469867,2.7377706861587074,2.109516594211721,0.33054271789106565,1.4334808295898054,2.7855523588336366,6.381978727805274,5.61666122443336e-07,3.725290298461914e-09,1.1651845702456904,335.77442968564475,leverage_heavy,2.246664489773344e-06
12 +mad_pct,leverage_heavy,30,0.6282540919469868,2.7377706861587074,2.109516594211721,0.3305427178910657,1.4334808295898052,2.7855523588336366,6.381978727805273,5.616661224433371e-07,3.725290298461914e-09,1.1651845702456902,335.7744296856447,leverage_heavy,2.2466644897733486e-06
13 13 time_to_absorb,leverage_heavy,30,25.266666666666666,52.8,27.533333333333335,7.952550857058357,11.268540589909744,43.79812607675693,3.4622014782710733,0.001683414812682758,0.006827030024128609,0.6321086160922663,108.97097625329816,leverage_heavy,0.001683414812682758
14 −spread_bp,leverage_heavy,30,20.68987407852468,36.25765496390989,15.567780885385222,3.372307612659998,8.67063739358322,22.464924377187224,4.616358492013639,7.358646567477612e-05,0.00013739429414272308,0.842827893202134,75.24347816859843,leverage_heavy,0.00022075939702432835
14 +spread_bp,leverage_heavy,30,20.68987407852468,36.2576549639099,15.56778088538522,3.372307612659998,8.67063739358322,22.46492437718722,4.616358492013638,7.358646567477631e-05,0.00013739429414272308,0.8428278932021339,75.24347816859841,leverage_heavy,0.00022075939702432895
15 15 n_bubbles,leverage_heavy,30,0.0,0.9,0.9,0.22154110176243813,0.4468975717247238,1.3531024282752764,4.062451585011451,0.00033757404082763944,0.0004730724812796508,0.741698790627796,,leverage_heavy,0.0006751480816552789
16 −max_dd,leverage_heavy,30,0.03313559734901605,0.07811242732953337,0.04497682998051735,0.005317411213479523,0.0341015029473002,0.0558521570137345,8.458407329209757,2.5464721711326925e-09,2.60770320892334e-08,1.5442868315917366,135.73568481889137,leverage_heavy,1.2732360855663462e-08
17 −mad_pct,leverage_heavy_funding_shock,30,0.6282540919469867,2.958604840512614,2.3303507485656274,0.3309132677965215,1.6535571242931841,3.007144372838071,7.042179856017618,9.569332641420283e-08,1.862645149230957e-09,1.285720253716444,370.9248818967131,leverage_heavy_funding_shock,3.827733056568113e-07
16 +max_dd,leverage_heavy,30,0.03313559734901605,0.07811242732953337,0.04497682998051735,0.0053174112134795224,0.0341015029473002,0.0558521570137345,8.45840732920976,2.5464721711326792e-09,2.60770320892334e-08,1.5442868315917369,135.73568481889137,leverage_heavy,1.2732360855663396e-08
17 +mad_pct,leverage_heavy_funding_shock,30,0.6282540919469867,2.9586048405126144,2.3303507485656274,0.3309132677965215,1.6535571242931841,3.007144372838071,7.042179856017618,9.569332641420283e-08,1.862645149230957e-09,1.285720253716444,370.9248818967131,leverage_heavy_funding_shock,3.827733056568113e-07
18 18 time_to_absorb,leverage_heavy_funding_shock,30,25.266666666666666,60.43333333333333,35.166666666666664,7.943924640712425,18.91951651661322,51.41381681672011,4.426863075517904,0.00012425575522840936,0.000614000783560083,0.8082309218159505,139.1820580474934,leverage_heavy_funding_shock,0.00012425575522840936
19 19 spread_bp,leverage_heavy_funding_shock,30,20.68987407852468,38.00473078793235,17.31485670940766,3.345013937297895,10.47353505149898,24.156178367316343,5.176318255760278,1.553831088819794e-05,7.910653948783875e-05,0.9450620911685667,83.68758864211667,leverage_heavy_funding_shock,3.107662177639588e-05
20 20 n_bubbles,leverage_heavy_funding_shock,30,0.0,1.3666666666666667,1.3666666666666667,0.24203796288790566,0.8716434504469067,1.8616898828864268,5.6464971459853475,4.2177639491799116e-06,4.338803172973628e-05,1.0309046192482385,,leverage_heavy_funding_shock,1.2653291847539736e-05
21 21 max_dd,leverage_heavy_funding_shock,30,0.03313559734901605,0.094022027434633,0.06088643008561697,0.006712607068457487,0.047157607133218205,0.07461525303801574,9.070459430244629,5.752487239564372e-10,9.313225746154785e-09,1.6560317456268137,183.749305752066,leverage_heavy_funding_shock,2.876243619782186e-09
22 −mad_pct,liquidity_shock,30,0.6282540919469867,0.6375685616065419,0.009314469659554982,0.027251007041271744,-0.04642009771922102,0.06504903703833098,0.3418027688095411,0.7349639130942708,0.8871948085725307,0.06240436223156962,1.4825959399148578,liquidity_shock,0.7349639130942708
22 +mad_pct,liquidity_shock,30,0.6282540919469867,0.6375685616065419,0.00931446965955498,0.027251007041271744,-0.04642009771922102,0.06504903703833098,0.341802768809541,0.734963913094271,0.8871948085725307,0.062404362231569616,1.4825959399148576,liquidity_shock,0.734963913094271
23 23 time_to_absorb,liquidity_shock,30,25.266666666666666,33.166666666666664,7.9,3.8561340893364338,0.013320256450725765,15.786679743549275,2.0486839453654575,0.04963907844851497,0.015751121568152216,0.3740368033584474,31.266490765171504,liquidity_shock,0.19855631379405989
24 −spread_bp,liquidity_shock,30,20.68987407852468,19.38476399998455,-1.3051100785401253,0.9903887839086477,-3.3306825766258523,0.720462419545602,-1.3177755036657475,0.19789462166450567,0.23665234446525574,-0.24059178969515385,-6.3079653050898985,liquidity_shock,0.39578924332901133
24 +spread_bp,liquidity_shock,30,20.68987407852468,19.38476399998455,-1.305110078540125,0.9903887839086476,-3.3306825766258523,0.7204624195456022,-1.3177755036657472,0.1978946216645057,0.23665234446525574,-0.24059178969515382,-6.3079653050898985,liquidity_shock,0.3957892433290114
25 25 n_bubbles,liquidity_shock,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,liquidity_shock,
26 26 max_dd,liquidity_shock,30,0.03313559734901605,0.03023922492546186,-0.0028963724235541942,0.0018099280248392202,-0.006598090870082065,0.0008053460229736757,-1.6002693940337684,0.12037802875949395,0.09749059620220792,-0.29216788173247266,-8.740969396286442,liquidity_shock,0.36113408627848187
27 27 mad_pct,info_low,30,0.6282540919469867,0.6026463991451105,-0.025607692801876355,0.02365127473041428,-0.07397998095474381,0.022764595350991106,-1.0827193499615573,0.28785378862941297,0.370740607380867,-0.1976766038070917,-4.076008915838018,info_low,0.8635613658882388
28 28 time_to_absorb,info_low,30,25.266666666666666,24.966666666666665,-0.3,2.7780543923574554,-5.981759190706424,5.381759190706425,-0.1079892462960094,0.914747917936226,0.34541581215055495,-0.019716048721435186,-1.187335092348285,info_low,1.0
29 29 spread_bp,info_low,30,20.68987407852468,20.27325361817352,-0.4166204603511574,0.9304469506093098,-2.3195981441693014,1.4863572234669868,-0.4477637979019981,0.6576507775114152,0.761065473780036,-0.08175011084836957,-2.0136442530773735,info_low,1.0
30 30 n_bubbles,info_low,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,info_low,
31 −max_dd,info_low,30,0.03313559734901605,0.030426155138508,-0.0027094422105080505,0.0015618758372030646,-0.005903836970086592,0.00048495254907049033,-1.7347359796281854,0.09340609319392706,0.19807579927146435,-0.3167180091193932,-8.176832250735044,info_low,0.37362437277570826
32 −mad_pct,info_high,30,0.6282540919469867,0.583635110753795,-0.04461898119319179,0.030384585406036172,-0.10676243592952972,0.017524473543146145,-1.4684742476139836,0.1527397481733936,0.06056523136794567,-0.2681054901778685,-7.102059782040676,info_high,0.3054794963467872
31 +max_dd,info_low,30,0.03313559734901605,0.030426155138508,-0.0027094422105080505,0.0015618758372030641,-0.00590383697008659,0.00048495254907048946,-1.7347359796281858,0.0934060931939269,0.19807579927146435,-0.31671800911939324,-8.176832250735044,info_low,0.3736243727757076
32 +mad_pct,info_high,30,0.6282540919469868,0.5836351107537951,-0.044618981193191805,0.030384585406036172,-0.10676243592952975,0.01752447354314613,-1.468474247613984,0.15273974817339347,0.06056523136794567,-0.2681054901778686,-7.102059782040678,info_high,0.30547949634678695
33 33 time_to_absorb,info_high,30,25.266666666666666,26.2,0.9333333333333333,3.646737900920172,-6.525083118717398,8.391749785384064,0.25593649960361226,0.7998066240890291,0.8192500254019224,0.046727398040603475,3.6939313984168867,info_high,0.7998066240890291
34 34 spread_bp,info_high,30,20.68987407852468,17.674358574644632,-3.01551550388005,0.8436325288226298,-4.740937758895465,-1.2900932488646346,-3.574441952930017,0.0012527919846749588,0.0008718203753232956,-0.6526008293708632,-14.574837393573327,info_high,0.005011167938699835
35 35 n_bubbles,info_high,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,info_high,
36 36 max_dd,info_high,30,0.03313559734901605,0.02785980850434493,-0.005275788844671122,0.002186873838565948,-0.009748448042910727,-0.0008031296464315165,-2.412479746948157,0.02239567174887383,0.005382779985666275,-0.44045652564262017,-15.92181601285599,info_high,0.06718701524662149
37 −mad_pct,misleading_news,30,0.6282540919469868,0.707292854995019,0.07903876304803209,0.01962027022094286,0.038910804805506176,0.119166721290558,4.028423775920547,0.0003703271034647764,0.0009753641365557986,0.7354861910872733,12.5807000799768,misleading_news,0.0014813084138591056
37 +mad_pct,misleading_news,30,0.6282540919469868,0.7072928549950189,0.07903876304803209,0.01962027022094286,0.038910804805506176,0.119166721290558,4.028423775920547,0.0003703271034647764,0.0009753641365557986,0.7354861910872733,12.5807000799768,misleading_news,0.0014813084138591056
38 38 time_to_absorb,misleading_news,30,25.266666666666666,23.166666666666668,-2.1,1.8403835182518,-5.864006924421055,1.6640069244210554,-1.1410665109600702,0.263177805181749,0.281084103394311,-0.2083292892221821,-8.311345646437996,misleading_news,0.263177805181749
39 −spread_bp,misleading_news,30,20.68987407852468,23.51591601826055,2.8260419397358745,1.0597430147248355,0.6586241129775665,4.993459766494182,2.6667238193305405,0.012395591106970309,0.03692078702122783,0.48687493016122274,13.659058189576903,misleading_news,0.024791182213940617
39 +spread_bp,misleading_news,30,20.68987407852468,23.515916018260548,2.8260419397358745,1.0597430147248355,0.6586241129775665,4.993459766494182,2.6667238193305405,0.012395591106970309,0.03692078702122783,0.48687493016122274,13.659058189576903,misleading_news,0.024791182213940617
40 40 n_bubbles,misleading_news,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,misleading_news,
41 −max_dd,misleading_news,30,0.03313559734901605,0.038069095171641455,0.004933497822625399,0.0016643866992440016,0.001529444809360157,0.00833755083589064,2.964153597758438,0.006012508226009909,0.014990431977029232,0.541177929800797,14.888815103168488,misleading_news,0.01803752467802973
42 −mad_pct,breaker_5,30,0.6282540919469869,0.6436703744684232,0.015416282521436609,0.008871426884037416,-0.0027278227098096845,0.0335603877526829,1.7377455422842427,0.09286600685468777,0.10880943004054569,0.31726747757104234,2.453829225952333,breaker_5,0.37146402741875106
41 +max_dd,misleading_news,30,0.03313559734901605,0.038069095171641455,0.004933497822625399,0.0016643866992440011,0.001529444809360158,0.008337550835890639,2.964153597758439,0.006012508226009911,0.014990431977029232,0.5411779298007972,14.888815103168488,misleading_news,0.018037524678029732
42 +mad_pct,breaker_5,30,0.6282540919469868,0.6436703744684233,0.015416282521436609,0.008871426884037416,-0.0027278227098096845,0.0335603877526829,1.7377455422842427,0.09286600685468777,0.10880943004054569,0.31726747757104234,2.4538292259523335,breaker_5,0.37146402741875106
43 43 time_to_absorb,breaker_5,30,25.266666666666666,25.133333333333333,-0.13333333333333333,0.1333333333333333,-0.4060306189510271,0.13936395228436047,-1.0000000000000002,0.3255819880161932,0.31731050786291415,-0.1825741858350554,-0.5277044854881267,breaker_5,0.6511639760323864
44 44 spread_bp,breaker_5,30,20.68987407852468,20.599037031113088,-0.09083704741159479,0.059877925033521344,-0.2133011545995525,0.03162705977636293,-1.5170373282097143,0.14008374994513234,0.10880943004054569,-0.27697185507927624,-0.43904108389852536,breaker_5,0.420251249835397
45 45 n_bubbles,breaker_5,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,breaker_5,
46 −max_dd,breaker_5,30,0.03313559734901605,0.0331720767406367,3.64793916206511e-05,0.0003926976674050563,-0.0007666775181525398,0.000839636301393842,0.09289434251470523,0.9266262966626592,1.0,0.01696010895330508,0.1100912448820976,breaker_5,0.9266262966626592
47 −mad_pct,poslimit_300,30,0.6282540919469869,1.8911586792602286,1.2629045873132414,0.19593886537029576,0.8621646118120634,1.6636445628144194,6.445401145538619,4.729705740517956e-07,9.313225746154785e-09,1.1767638665270463,201.01812363839045,poslimit_300,1.8918822962071823e-06
48 −time_to_absorb,poslimit_300,30,25.266666666666666,53.3,28.033333333333335,8.112780230299235,11.440834726217158,44.625831940449515,3.455453314097644,0.0017134343335242206,0.00545559119110836,0.630876575512421,110.94986807387863,poslimit_300,0.005140303000572662
46 +max_dd,breaker_5,30,0.03313559734901605,0.0331720767406367,3.64793916206511e-05,0.0003926976674050562,-0.0007666775181525395,0.0008396363013938418,0.09289434251470526,0.9266262966626592,1.0,0.016960108953305082,0.1100912448820976,breaker_5,0.9266262966626592
47 +mad_pct,poslimit_300,30,0.6282540919469867,1.8911586792602284,1.2629045873132414,0.19593886537029576,0.8621646118120634,1.6636445628144194,6.445401145538619,4.729705740517956e-07,9.313225746154785e-09,1.1767638665270463,201.01812363839053,poslimit_300,1.8918822962071823e-06
48 +time_to_absorb,poslimit_300,30,25.266666666666666,53.3,28.033333333333335,8.112780230299235,11.440834726217158,44.625831940449515,3.455453314097644,0.0017134343335242206,0.00545559119110836,0.6308765755124212,110.94986807387863,poslimit_300,0.005140303000572662
49 49 spread_bp,poslimit_300,30,20.68987407852468,38.48234651034019,17.792472431815515,1.9105986967985755,13.884859342902955,21.700085520728077,9.312511550242768,3.2378780191024443e-10,4.6566128730773926e-08,1.7002242143651225,85.99604020927048,poslimit_300,1.6189390095512222e-09
50 50 n_bubbles,poslimit_300,30,0.0,0.6333333333333333,0.6333333333333333,0.1946605258045745,0.23520785580467946,1.031458810861987,3.2535272917589646,0.0028932275163866,0.004508698364904228,0.5940100963850257,,poslimit_300,0.0057864550327732
51 51 max_dd,poslimit_300,30,0.03313559734901605,0.034456638636437525,0.0013210412874214686,0.002740865520307808,-0.004284658119811537,0.006926740694654474,0.4819796074026687,0.6334381365601258,0.4771064817905426,0.08799703441064186,3.9867737210438325,poslimit_300,0.6334381365601258
52 52 mad_pct,auction_5,30,0.6282540919469867,1.35564882733449,0.7273947353875033,0.11832214381516283,0.4853987795360435,0.9693906912389632,6.147579074664201,1.0633232074358769e-06,1.862645149230957e-09,1.1223892444134396,115.78034185710999,auction_5,3.1899696223076306e-06
53 53 time_to_absorb,auction_5,30,25.266666666666666,25.1,-0.16666666666666666,3.980724630312361,-8.308162677749255,7.974829344415922,-0.04186842400439756,0.9668905278378153,0.2664233269499078,-0.007644093424799773,-0.6596306068601583,auction_5,0.9668905278378153
54 54 spread_bp,auction_5,0,,,,,,,,,,,,auction_5,
55 −n_bubbles,auction_5,30,0.0,0.13333333333333333,0.13333333333333333,0.07926868804398468,-0.028789337147194394,0.29545600381386106,1.6820428926406503,0.10329970911247394,0.10247043485974941,0.3070976116635082,,auction_5,0.20659941822494787
55 +n_bubbles,auction_5,30,0.0,0.13333333333333333,0.13333333333333333,0.0792686880439847,-0.02878933714719442,0.29545600381386106,1.68204289264065,0.10329970911247405,0.10247043485974941,0.30709761166350813,,auction_5,0.2065994182249481
56 56 max_dd,auction_5,30,0.03313559734901605,0.05628606196832761,0.02315046461931156,0.0032313309119088966,0.016541650854735784,0.029759278383887335,7.164374448309136,6.931526079709137e-08,5.587935447692871e-09,1.3080298319175145,69.86584359855824,auction_5,2.7726104318836547e-07
57 57 mad_pct,mock_llm_25_indep,30,0.6282540919469867,0.6573609826050393,0.02910689065805242,0.04227381377767932,-0.05735276636605522,0.11556654768216007,0.6885324047441619,0.4965892162378058,0.5978077705949545,0.12570824321721819,4.632980673130024,mock_llm_25_indep,0.9931784324756116
58 58 time_to_absorb,mock_llm_25_indep,30,25.266666666666666,28.233333333333334,2.966666666666667,2.9774954117853922,-3.1229952088309387,9.056328542164273,0.9963631362534218,0.3273152497245478,0.8483154780154493,0.1819101883975308,11.741424802110819,mock_llm_25_indep,0.9819457491736434
59 59 spread_bp,mock_llm_25_indep,30,20.68987407852468,16.25631641329572,-4.43355766522896,0.9814521456395995,-6.440852685825813,-2.426262644632108,-4.5173447171381635,9.678202704795945e-05,0.00013739429414272308,-0.8247505338677887,-21.428635323744324,mock_llm_25_indep,0.0003871281081918378
60 60 n_bubbles,mock_llm_25_indep,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,mock_llm_25_indep,
61 61 max_dd,mock_llm_25_indep,30,0.03313559734901605,0.032746308923070254,-0.0003892884259457959,0.0021208214839804843,-0.0047268553906545515,0.00394827853876296,-0.18355548964694385,0.8556395780450193,0.7000329215079546,-0.03351249407784571,-1.174834489462903,mock_llm_25_indep,0.9931784324756116
62 −mad_pct,mock_llm_50_indep,30,0.6282540919469867,0.6798607327887491,0.05160664084176236,0.031913313378619,-0.01366341365885941,0.11687669534238414,1.6170881484320372,0.11668682433457427,0.06989291124045849,0.29523855212349637,8.214294423746791,mock_llm_50_indep,0.28389459081403096
62 +mad_pct,mock_llm_50_indep,30,0.6282540919469867,0.6798607327887491,0.05160664084176238,0.031913313378619,-0.013663413658859397,0.11687669534238415,1.6170881484320376,0.11668682433457425,0.06989291124045849,0.2952385521234964,8.214294423746795,mock_llm_50_indep,0.28389459081403096
63 63 time_to_absorb,mock_llm_50_indep,30,25.266666666666666,28.3,3.033333333333333,1.7554403667530416,-0.5569453397462927,6.623612006412959,1.7279614795140847,0.09463153027134365,0.11816765662651728,0.3154811602766217,12.00527704485488,mock_llm_50_indep,0.28389459081403096
64 −spread_bp,mock_llm_50_indep,30,20.68987407852468,13.5671243158387,-7.1227497626859755,0.8152976245444828,-8.790220631564733,-5.455278893807219,-8.736379879268686,1.2878226187043162e-09,1.862645149230957e-09,-1.5950374436032397,-34.42625960724973,mock_llm_50_indep,5.151290474817265e-09
64 +spread_bp,mock_llm_50_indep,30,20.68987407852468,13.567124315838702,-7.1227497626859755,0.815297624544483,-8.790220631564733,-5.455278893807219,-8.736379879268684,1.2878226187043234e-09,1.862645149230957e-09,-1.5950374436032393,-34.42625960724973,mock_llm_50_indep,5.151290474817294e-09
65 65 n_bubbles,mock_llm_50_indep,30,0.0,0.0,0.0,0.0,0.0,0.0,,,1.0,,,mock_llm_50_indep,
66 66 max_dd,mock_llm_50_indep,30,0.03313559734901605,0.03175647106215273,-0.0013791262868633192,0.002565621448445398,-0.00662641132371529,0.003868158749988652,-0.5375408315591457,0.5949953367044932,1.0,-0.09814107967500967,-4.162068582428232,mock_llm_50_indep,0.5949953367044932
added results/tables/tab_dose_pilot.tex +23 −0
@@ -0,0 +1,23 @@
1 +\begin{tabular}{lrrr}
2 +\toprule
3 +Outcome & 5\% & 25\% & 50\% \\
4 +\midrule
5 +Sq. log pricing error ($\times 10^{4}$) & 0.66 & 0.55 & 2.15 \\
6 +Mean $|P/V-1|$ (\%) & 0.54 & 0.48 & 0.69 \\
7 +Time to absorb (ticks) & 21.67 & 27.00 & 22.00 \\
8 +Quoted spread (bp) & 17.93 & 11.58 & 11.27 \\
9 +Price impact (bp/100 lots) & 10.34 & 8.08 & 13.41 \\
10 +Bubble episodes & 0.00 & 0.00 & 1.00 \\
11 +Max drawdown & 0.03 & 0.02 & 0.03 \\
12 +Vol. clustering $\sum_{1}^{10}\mathrm{ACF}|r|$ & 2.79 & 2.34 & 3.83 \\
13 +LLM order-flow corr. & 0.38 & 0.41 & 0.20 \\
14 +LLM valid-decision rate & 0.92 & 0.96 & 0.93 \\
15 +\midrule
16 +Realized share of traders & 0.050 & 0.250 & 0.500 \\
17 +Realized share of orders & 0.008 & 0.046 & 0.086 \\
18 +Realized share of volume & 0.052 & 0.253 & 0.366 \\
19 +API cost per run (USD) & 0.088 & 0.436 & 0.462 \\
20 +\midrule
21 +Seeds per cell & 3 & & \\
22 +\bottomrule
23 +\end{tabular}
\ No newline at end of file
added results/tables/tab_llm_reliability_pilot.csv +4 −0
@@ -0,0 +1,4 @@
1 +cell,n,valid,hold,place,market,json_err,schema_err,feas_err,api_err,lat,in_tok,out_tok,reason_tok,usd
2 +share_25,2066,0.9583736689254598,0.12391093901258471,0.7613746369796709,0.04017424975798645,0.0,0.0,0.041626331074540175,0.0,2.843962665003406,855.4244917715392,369.75556631171344,289.44433688286546,0.4361183833333333
3 +share_5,411,0.9221411192214112,0.13138686131386862,0.8272506082725061,0.0535279805352798,0.0,0.0,0.07785888077858881,0.0,2.8592208875806318,855.5206812652068,375.4963503649635,295.345498783455,0.08774501666666668
4 +share_50,4128,0.9338662790697675,0.3793604651162791,0.5625,0.09278100775193798,0.013565891472868217,0.008478682170542635,0.04408914728682171,0.0,1.4735181390779597,622.8725775193799,208.65213178294573,159.38735465116278,0.46151500000000006
added results/tables/tab_llm_reliability_pilot.tex +9 −0
@@ -0,0 +1,9 @@
1 +\begin{tabular}{lrrrrrrrrrr}
2 +\toprule
3 +Cell & Decisions & Valid & Hold & Market & JSON err. & Schema err. & Infeasible & API err. & Latency (s) & USD/run \\
4 +\midrule
5 +share\_25 & 2,066 & 0.958 & 0.12 & 0.04 & 0.000 & 0.000 & 0.042 & 0.000 & 2.84 & 0.44 \\
6 +share\_5 & 411 & 0.922 & 0.13 & 0.05 & 0.000 & 0.000 & 0.078 & 0.000 & 2.86 & 0.09 \\
7 +share\_50 & 4,128 & 0.934 & 0.38 & 0.09 & 0.014 & 0.008 & 0.044 & 0.000 & 1.47 & 0.46 \\
8 +\bottomrule
9 +\end{tabular}
\ No newline at end of file
modified results/tables/tab_threshold_sensitivity.tex +2 −0
@@ -10,5 +10,7 @@ classical confirmatory & mock llm 50 indep & n bubbles & +0.00 & +0.00 & +0.00 &
10 10 classical confirmatory & mock llm 50 indep & n crashes & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 \\
11 11 classical confirmatory & trend heavy & n bubbles & +1.10 & +1.10 & +1.10 & +0.13 & +0.13 & +0.13 & +0.00 & +0.00 & +0.00 \\
12 12 classical confirmatory & trend heavy & n crashes & +0.07 & +0.00 & +0.00 & +0.07 & +0.00 & +0.00 & +0.07 & +0.00 & +0.00 \\
13 +confirmatory openai & dose 025 & n bubbles & -0.17 & -0.17 & -0.17 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 \\
14 +confirmatory openai & dose 025 & n crashes & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 & +0.00 \\
13 15 \bottomrule
14 16 \end{tabular}
\ No newline at end of file
modified results/tables/threshold_sensitivity.csv +27 −0
@@ -107,3 +107,30 @@ classical_confirmatory,0.08,0.15,trend_heavy,n_crashes,0.0,0.0,,30
107 107 classical_confirmatory,0.08,0.15,mock_llm_50_indep,n_bubbles,0.0,0.0,,30
108 108 classical_confirmatory,0.08,0.15,mock_llm_50_indep,bubble_amp,0.0,0.0,,30
109 109 classical_confirmatory,0.08,0.15,mock_llm_50_indep,n_crashes,0.0,0.0,,30
110 +confirmatory_openai,0.03,0.07,dose_025,n_bubbles,0.16666666666666666,-0.16666666666666666,0.3632174676491226,6
111 +confirmatory_openai,0.03,0.07,dose_025,bubble_amp,0.005669657734009163,-0.005669657734009163,0.3632174676491227,6
112 +confirmatory_openai,0.03,0.07,dose_025,n_crashes,0.0,0.0,,6
113 +confirmatory_openai,0.03,0.1,dose_025,n_bubbles,0.16666666666666666,-0.16666666666666666,0.3632174676491226,6
114 +confirmatory_openai,0.03,0.1,dose_025,bubble_amp,0.005669657734009163,-0.005669657734009163,0.3632174676491227,6
115 +confirmatory_openai,0.03,0.1,dose_025,n_crashes,0.0,0.0,,6
116 +confirmatory_openai,0.03,0.15,dose_025,n_bubbles,0.16666666666666666,-0.16666666666666666,0.3632174676491226,6
117 +confirmatory_openai,0.03,0.15,dose_025,bubble_amp,0.005669657734009163,-0.005669657734009163,0.3632174676491227,6
118 +confirmatory_openai,0.03,0.15,dose_025,n_crashes,0.0,0.0,,6
119 +confirmatory_openai,0.05,0.07,dose_025,n_bubbles,0.0,0.0,,6
120 +confirmatory_openai,0.05,0.07,dose_025,bubble_amp,0.0,0.0,,6
121 +confirmatory_openai,0.05,0.07,dose_025,n_crashes,0.0,0.0,,6
122 +confirmatory_openai,0.05,0.1,dose_025,n_bubbles,0.0,0.0,,6
123 +confirmatory_openai,0.05,0.1,dose_025,bubble_amp,0.0,0.0,,6
124 +confirmatory_openai,0.05,0.1,dose_025,n_crashes,0.0,0.0,,6
125 +confirmatory_openai,0.05,0.15,dose_025,n_bubbles,0.0,0.0,,6
126 +confirmatory_openai,0.05,0.15,dose_025,bubble_amp,0.0,0.0,,6
127 +confirmatory_openai,0.05,0.15,dose_025,n_crashes,0.0,0.0,,6
128 +confirmatory_openai,0.08,0.07,dose_025,n_bubbles,0.0,0.0,,6
129 +confirmatory_openai,0.08,0.07,dose_025,bubble_amp,0.0,0.0,,6
130 +confirmatory_openai,0.08,0.07,dose_025,n_crashes,0.0,0.0,,6
131 +confirmatory_openai,0.08,0.1,dose_025,n_bubbles,0.0,0.0,,6
132 +confirmatory_openai,0.08,0.1,dose_025,bubble_amp,0.0,0.0,,6
133 +confirmatory_openai,0.08,0.1,dose_025,n_crashes,0.0,0.0,,6
134 +confirmatory_openai,0.08,0.15,dose_025,n_bubbles,0.0,0.0,,6
135 +confirmatory_openai,0.08,0.15,dose_025,bubble_amp,0.0,0.0,,6
136 +confirmatory_openai,0.08,0.15,dose_025,n_crashes,0.0,0.0,,6
added scripts/47_mechanisms.py +91 −0
@@ -0,0 +1,91 @@
1 +"""Mechanism checks (descriptive, pre-registered as non-identified mediation):
2 +(1) coordination rises with the LLM share (H2): llm_pairwise_corr, llm_synchrony and the classical analogue flow_corr_llm vs share;
3 +(2) within each share, runs with more correlated LLM order flow have worse price discovery / more instability (seed-FE OLS, descriptive);
4 +(3) diversity arms: coordination and outcomes for single vs diverse prompts vs two models at 25 % (paired);
5 +(4) theory comparative statics: sign of the dose effect on informativeness proxies vs Table tab:predictions.
6 +Writes results/tables/tab_mechanisms.tex and mechanisms.csv."""
7 +from __future__ import annotations
8 +
9 +import sys
10 +from pathlib import Path
11 +
12 +import numpy as np
13 +import pandas as pd
14 +import statsmodels.formula.api as smf
15 +
16 +ROOT = Path(__file__).resolve().parents[1]
17 +sys.path.insert(0, str(ROOT / "src"))
18 +from market_agents.analysis.estimators import paired_effects # noqa: E402
19 +
20 +p = ROOT / "results/runs/confirmatory_openai/outcomes.parquet"
21 +if not p.exists():
22 + raise SystemExit("confirmatory design not run yet")
23 +df = pd.read_parquet(p)
24 +df["cell"] = df.name.str.split("/").str[-1]
25 +OUT = ROOT / "results/tables"
26 +rows = []
27 +
28 +# (1) coordination vs share
29 +d = df[(df.tr_arm == "dose") & (df.llm_share > 0)].copy()
30 +for oc in ("llm_pairwise_corr", "llm_synchrony", "flow_corr_llm", "llm_hold_rate", "llm_valid_rate"):
31 + if oc in d:
32 + g = d.groupby("llm_share")[oc].agg(["mean", "std", "size"])
33 + for s, r in g.iterrows():
34 + rows.append({"block": "coordination_by_share", "share": s, "stat": oc, "mean": r["mean"], "se": r["std"] / np.sqrt(r["size"]), "n": r["size"]})
35 + # trend test: OLS of stat on share with seed FE
36 + m = smf.ols(f"{oc} ~ llm_share + C(seed)", data=d.dropna(subset=[oc])).fit(cov_type="cluster", cov_kwds={"groups": d.dropna(subset=[oc])["seed"]})
37 + rows.append({"block": "trend", "stat": oc, "beta_share": m.params["llm_share"], "se": m.bse["llm_share"], "p": m.pvalues["llm_share"], "n": int(m.nobs)})
38 +
39 +# classical analogue: flow correlation of classical groups in the same runs
40 +for grp in ("fundamentalist", "trend", "noise", "market_maker"):
41 + c = f"flow_corr_{grp}"
42 + if c in df:
43 + rows.append({"block": "classical_flow_corr", "stat": c, "mean": df[df.cell == "dose_000"][c].mean(), "n": int(df[df.cell == "dose_000"][c].notna().sum())})
44 +
45 +# (2) within-share association between coordination and outcomes (descriptive)
46 +for oc in ("mad_pct", "msd_log", "time_to_absorb", "n_bubbles", "max_dd", "spread_bp", "lambda_bp_100"):
47 + for med in ("llm_pairwise_corr", "llm_synchrony"):
48 + dd = d.dropna(subset=[oc, med])
49 + if len(dd) < 8 or dd[med].std() == 0:
50 + continue
51 + z = dd.copy()
52 + z["med_z"] = (z[med] - z[med].mean()) / z[med].std()
53 + m = smf.ols(f"{oc} ~ med_z + C(llm_share) + C(seed)", data=z).fit(cov_type="cluster", cov_kwds={"groups": z["seed"]})
54 + rows.append({"block": "within_share_association", "stat": oc, "mediator": med, "beta_per_sd": m.params["med_z"], "se": m.bse["med_z"], "p": m.pvalues["med_z"], "n": int(m.nobs)})
55 +
56 +# (3) diversity arms at 25 %
57 +for cell in ("div_prompts_025", "div_models_025", "memory_025", "channel_025", "adaptive_025", "fair_latency_025"):
58 + for oc in ("llm_pairwise_corr", "llm_synchrony", "mad_pct", "time_to_absorb", "n_bubbles", "max_dd", "spread_bp"):
59 + if cell in set(df.cell) and oc in df:
60 + e = paired_effects(df, oc, "cell", cell, "dose_025")
61 + rows.append({"block": "arm_vs_dose_025", "cell": cell, "stat": oc, "control_mean": e.get("control_mean"), "diff": e.get("diff"), "ci_lo": e.get("ci_lo"), "ci_hi": e.get("ci_hi"), "p": e.get("p"), "cohen_d": e.get("cohen_d"), "n": e.get("n")})
62 +
63 +t = pd.DataFrame(rows)
64 +t.to_csv(OUT / "mechanisms.csv", index=False)
65 +
66 +# LaTeX: coordination by share + trend
67 +lines = ["\\begin{tabular}{lrrrrrr}", "\\toprule", "Statistic & 5\\% & 10\\% & 25\\% & 50\\% & 100\\% & Trend per unit share (SE) \\\\", "\\midrule"]
68 +for oc, lab in (("llm_pairwise_corr", "Pairwise order-flow correlation (LLM)"), ("llm_synchrony", "Synchrony (LLM)"), ("flow_corr_llm", "Flow correlation from trades (LLM)"), ("llm_hold_rate", "Hold rate"), ("llm_valid_rate", "Valid-decision rate")):
69 + c = t[(t.block == "coordination_by_share") & (t.stat == oc)].set_index("share")
70 + tr = t[(t.block == "trend") & (t.stat == oc)]
71 + if len(c) == 0:
72 + continue
73 + vals = " & ".join(f"{c.loc[s, 'mean']:.3f}" if s in c.index else "--" for s in (0.05, 0.10, 0.25, 0.50, 1.00))
74 + trs = f"{tr.iloc[0].beta_share:+.3f} ({tr.iloc[0].se:.3f})" + ("$^{***}$" if tr.iloc[0].p < 0.01 else "$^{**}$" if tr.iloc[0].p < 0.05 else "$^{*}$" if tr.iloc[0].p < 0.1 else "") if len(tr) else "--"
75 + lines.append(f"{lab} & {vals} & {trs} \\\\")
76 +cl = t[t.block == "classical_flow_corr"]
77 +if len(cl):
78 + lines.append("\\midrule")
79 + for r in cl.itertuples():
80 + lines.append(f"Flow correlation, {r.stat.replace('flow_corr_', '').replace('_', ' ')} (0\\% cell) & \\multicolumn{{5}}{{r}}{{{r.mean:.3f}}} & \\\\")
81 +lines += ["\\bottomrule", "\\end{tabular}"]
82 +(OUT / "tab_coordination.tex").write_text("\n".join(lines))
83 +
84 +w = t[t.block == "within_share_association"]
85 +if len(w):
86 + lines = ["\\begin{tabular}{llrrr}", "\\toprule", "Outcome & Mediator (per 1 SD) & Coefficient & SE & $p$ \\\\", "\\midrule"]
87 + for r in w.itertuples():
88 + lines.append(f"{r.stat.replace('_', ' ')} & {r.mediator.replace('llm_', '').replace('_', ' ')} & {r.beta_per_sd:+.3f} & {r.se:.3f} & {r.p:.3f} \\\\")
89 + lines += ["\\bottomrule", "\\end{tabular}"]
90 + (OUT / "tab_mechanism_association.tex").write_text("\n".join(lines))
91 +print(t.to_string(max_rows=80))
modified scripts/50_tables.py +5 −1
@@ -151,7 +151,11 @@ def tab_dose(design: str, name: str):
151 151 lines = ["\\begin{tabular}{l" + "r" * len(levels) + "}", "\\toprule", "Outcome & " + " & ".join(f"{int(l * 100)}\\%" for l in levels) + " \\\\", "\\midrule"]
152 152 for o in outs:
153 153 sc = SCALE.get(o, 1.0)
154 − dr = dose_response(d, o, "share", 0.0).set_index("level") if o not in ("llm_pairwise_corr", "llm_valid_rate") else None
154 + dr = None
155 + if o not in ("llm_pairwise_corr", "llm_valid_rate") and 0.0 in levels:
156 + dr = dose_response(d, o, "share", 0.0).set_index("level")
157 + if "p_holm" not in dr:
158 + dr = None
155 159 cells = []
156 160 for l in levels:
157 161 m = d[d.share == l][o].astype(float).mean() * sc
modified src/market_agents/agents/classical.py +2 −2
@@ -28,7 +28,7 @@ def _affordable(obs: Observation, price_ticks: float, tick_cents: int) -> int:
28 28 @dataclass
29 29 class Fundamentalist:
30 30 """Value trader. Posterior value = public PV adjusted by the private signal (if informed):
31 − V_hat = public_value + w * (signal - x_hat_public) * rho/(1+r-rho) with w = prec/(1+prec).
31 + V_hat = public_value + w * (signal - x_hat_public) / (1+r-rho) with w = prec/(1+prec).
32 32 Trades toward the value when the relative mispricing exceeds `threshold`; order size scales with
33 33 the mispricing (capped) and with available cash/shares. The signal adjustment is relative to the
34 34 public posterior mean x_hat (shown in the Observation), scaled by 1/(1+r-rho)."""
@@ -93,7 +93,7 @@ class Fundamentalist:
93 93
94 94 @dataclass
95 95 class TrendFollower:
96 − """Momentum: signal = (EMA_short - EMA_long)/mid over `mid_history`; buys when positive."""
96 + """Momentum: signal = mean(mid, last 5) / mean(mid, last 30) - 1 (simple moving averages); buys when positive."""
97 97 name: str = "trend"
98 98 short_window: int = 5
99 99 long_window: int = 30
modified src/market_agents/experiments/design.py +8 −3
@@ -60,9 +60,14 @@ def _run_one(args):
60 60 cfg_json, out_dir, cache_path = args
61 61 cfg = RunConfig.model_validate(cfg_json)
62 62 out = Path(out_dir)
63 − if (out / "outcomes.json").exists():
64 − with open(out / "outcomes.json") as f:
65 − return json.load(f)
63 + if (out / "outcomes.json").exists() and (out / "manifest.json").exists():
64 + with open(out / "manifest.json") as f:
65 + man = json.load(f)
66 + if man.get("config_hash") == cfg.config_hash(): # resume only an identical configuration
67 + with open(out / "outcomes.json") as f:
68 + return json.load(f)
69 + import shutil
70 + shutil.rmtree(out)
66 71 res = run_config(cfg, out_dir=out, cache_path=cache_path)
67 72 oc = run_outcomes(res)
68 73 oc["run_dir"] = str(out)
modified src/market_agents/experiments/runner.py +1 −1
@@ -344,7 +344,7 @@ def make_llm_policy(cfg: RunConfig, cache_path: str | None = None, mock_kwargs:
344 344 max_output_tokens=l.max_output_tokens, concurrency=l.concurrency, timeout_s=l.timeout_s, max_retries=l.max_retries,
345 345 usd_cap=l.usd_cap, token_cap=l.token_cap, cache_path=cache_path, use_cache=l.cache, frozen_only=frozen_only,
346 346 adaptive_prompt=l.adaptive_prompt, mu_cents=cfg.fundamentals.mu[0], taker_bps=cfg.exchange.taker_fee_bps,
347 − order_ttl=cfg.exchange.order_ttl, price_assumptions=l.price_assumptions, library_path=l.frozen_library,
347 + order_ttl=cfg.exchange.order_ttl, decision_interval=l.decision_interval, price_assumptions=l.price_assumptions, library_path=l.frozen_library,
348 348 mock_kwargs={"seed": cfg.seed, **(mock_kwargs or {})}))
349 349
350 350
modified src/market_agents/llm/policy.py +2 −1
@@ -71,6 +71,7 @@ class LLMPolicyConfig:
71 71 mu_cents: float = 50.0
72 72 taker_bps: float = 2.0
73 73 order_ttl: int | None = 50
74 + decision_interval: int = 20
74 75 price_assumptions: dict[str, dict[str, float]] = field(default_factory=dict)
75 76 mock_kwargs: dict = field(default_factory=dict)
76 77 provider_kwargs: dict = field(default_factory=dict)
@@ -124,7 +125,7 @@ class LLMPolicy:
124 125 return res, False
125 126
126 127 def decide_one(self, obs: Observation, provider: str, model: str, prompt_id: str) -> tuple[Action, LLMDecisionRecord]:
127 − system = system_prompt(prompt_id, obs, self.cfg.mu_cents, self.cfg.taker_bps, self.cfg.order_ttl)
128 + system = system_prompt(prompt_id, obs, self.cfg.mu_cents, self.cfg.taker_bps, self.cfg.order_ttl, self.cfg.decision_interval)
128 129 user = user_message(obs, 0, adaptive=self.cfg.adaptive_prompt)
129 130 if provider == "library":
130 131 mid_units = obs.books[0].mid * obs.tick_cents[0] / 100.0
modified src/market_agents/llm/prompting.py +2 −2
@@ -31,14 +31,14 @@ Respond with ONLY a JSON object with exactly these keys:
31 31 """.strip()
32 32
33 33
34 −def system_prompt(prompt_id: str, obs: Observation, mu_cents: float, taker_bps: float, ttl: int | None) -> str:
34 +def system_prompt(prompt_id: str, obs: Observation, mu_cents: float, taker_bps: float, ttl: int | None, decision_interval: int | None = None) -> str:
35 35 persona = ROLE_PERSONAS.get(prompt_id, ROLE_PERSONAS["generalist"])
36 36 tick = obs.tick_cents[0] / 100.0
37 37 pv_mean = (mu_cents / obs.r_period) / 100.0
38 38 return persona + "\n\n" + RULES.format(
39 39 div_interval=obs.div_interval, mu=mu_cents / 100.0, r_pct=obs.r_period * 100.0, pv_mean=pv_mean,
40 40 rho=0.98, tick=tick, taker_bps=taker_bps, ttl=ttl if ttl is not None else 10_000,
41 − max_qty=obs.max_order_qty, interval=max(1, obs.div_interval))
41 + max_qty=obs.max_order_qty, interval=max(1, decision_interval or obs.div_interval))
42 42
43 43
44 44 def user_message(obs: Observation, asset: int = 0, adaptive: bool = False) -> str:
45 45
Menu

Theme