SPB Git

spb/wp27_uqo

Public
29.4 KB · 141 lines latex
1\section{The experimental economy}2\label{sec:model}34This 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.56\paragraph{Time, units and accounting.}7Model 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 identities8\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\qquad12\sum_i Q^{k}_{i,t} \;=\; \sum_i Q^{k}_{i,0}\quad (k=1,\dots,K),13\end{equation}14where $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.1516\paragraph{Dividends and the latent-timing convention.}17Asset $k$ pays a cash dividend every $\Delta=20$ ticks. Index dividend dates by $n=1,2,\dots$ (paid at $t=n\Delta$) and write18\begin{equation}19\label{eq:dividend}20D_n \;=\; \mu_{s_n} + x_n + J_n,21\qquad22x_{n+1} \;=\; \rho\, x_n + \sigma\, \varepsilon_{n+1},\quad \varepsilon_{n+1}\sim N(0,1),23\end{equation}24with 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.2526\paragraph{Two value benchmarks.}27Let $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}30V_t \;=\; \frac{\mu}{r} + \frac{x_{n(t)+1}}{1+r-\rho},31\qquad n(t)=\lfloor t/\Delta\rfloor,32\end{equation}33because $\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}36V^{\mathrm{pub}}_t \;=\; \frac{\mu}{r} + \frac{\hat{x}_t}{1+r-\rho},37\end{equation}38where $\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.3940Four 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$).4142\paragraph{Information.}43Three 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}).}4445\paragraph{The exchange.}46Each 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}.}4748Three 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.4950\paragraph{Leverage, margin and bankruptcy.}51Unleveraged 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.5253\paragraph{Classical policies.}54Trader 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}.5556\begin{table}[t]57\centering58\caption{Classical policies, baseline shares and parameters}59\label{tab:policies}60\small61\begin{threeparttable}62\begin{tabular}{@{}l r r p{4.7cm} p{4.1cm}@{}}63\toprule64Type & Share & Activity & Rule & Key parameters \\65\midrule66Fundamentalist & 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$ \\67Trend 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 \\68Mean 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 \\69Noise 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 \\70Market 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 \\71Leveraged & 4\% & 0.20 & Trend rule on a leveraged account & size 30; $p_{\rm mkt}=0.7$; $L=3$; $m=0.25$; liquidation 50\% \\72Cross-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\bottomrule74\end{tabular}75\begin{tablenotes}\footnotesize76\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}8081\paragraph{The LLM policy.}82An 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.8384Four 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/}.}8586\paragraph{Endowments.}87Slot $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$.8889\paragraph{Population builder and matched replacement.}90Slot 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.9192\paragraph{Shocks.}93Scheduled 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.9495\paragraph{Implementation assumptions and their checks.}96Table~\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}.9798\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\toprule103ID & Assumption as implemented & Robustness check \\104\midrule105\endfirsthead106\toprule107ID & Assumption as implemented & Robustness check \\108\midrule109\endhead110\bottomrule111\endfoot112I1 & 600 ticks, outcomes on $t\ge100$; no calendar mapping & $T=1{,}000$ on the control \\113I2 & 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\}$ \\114I3 & 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 \\115I4 & 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) \\116I5 & No look-ahead: signals and news concern the latent current state & Unit test of \code{private\_signal}; prompt built from \code{Observation} only \\117I6 & Latent jump $+10$c at $t=300$ ($+2.2\%$ of $V$), placebo at $t=450$ & Sizes 10/20/40c; \code{news\_burst} variant \\118I7 & 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) \\119I8 & Latency $U[0.05,1]$ per agent, fixed; arrival-order processing; LLM slots draw like others & Matched latency \code{llm\_latency} 0.05 \\120I9 & Taker 2 bp, maker 0, fees leave the economy; conservation asserted after every fill & Taker 0 and 5 bp \\121I10 & TTL 50 ticks; price band $\pm50\%$; max order 10,000 lots (policies cap at 40--50) & TTL 20/100 \\122I11 & No short selling; buys limited by free (unreserved) cash & \code{allow\_short} variant (currently inert, see footnote in the exchange paragraph) \\123I12 & 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\% \\124I13 & Safeguards off by default; breaker 5\% over 10 ticks, 20-tick halt, resting cancelled; position limit 300 & Halt 3\%/8\%; limit 150/600 \\125I14 & 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 \\126I15 & No rationality or equilibrium assumption: classical agents are documented heuristics, LLM agents output whatever the model returns & Calibration sweep (360 runs) \\127I16 & 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 \\128I17 & 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) \\129I18 & 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 \\130I19 & Stratified replacement, market makers excluded (95 eligible slots) & \code{llm\_replace: noise} at $\varphi=0.25$ (mock) \\131I20 & 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 \\132I21 & Invalid or infeasible decisions become hold (quantity, $\pm20\%$ price window, cash/holdings, owned cancel ids); sells clipped & Validity reported per cell; cells $<80\%$ flagged \\133I22 & 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 \\134I23 & Equal decision opportunities by default (27--28 per agent); equal USD via per-run \code{usd\_cap} & Fairness cell \\135I24 & 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 \\136I25 & 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 \\137I26 & 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}) \\138I27 & 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}141
Files · main
Menu

Theme