# lonja

[![ci](https://github.com/JP7599/lonja/actions/workflows/ci.yml/badge.svg)](https://github.com/JP7599/lonja/actions/workflows/ci.yml)
[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
![dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg)

A small artificial stock market that runs in the browser. Real limit order
book with price-time priority, five kinds of trader, a hidden true value the
price is supposed to discover, and buttons to sabotage all of it: value
shocks, liquidity drains, momentum floods, a transaction tax.

![lonja running the full zoo](docs/screenshot.png)

Open `index.html` and it runs. No build, no server, no dependencies. If your
browser is strict about `file://` URLs, `python3 -m http.server` in this
folder does it.

## Why this exists

Where does a price come from? The strangest answer economics has produced is
Gode and Sunder (1993): take traders with zero intelligence, bidding literally
at random, give them one constraint (never trade at a personal loss) and put
them in a double-auction order book. Allocative efficiency comes out near
100%, roughly matching experiments with human subjects. The intelligence is
not in the traders. It lives in the institution.

I wanted to watch that emerge in an engine written from scratch, and then find
out what breaks it. Both happened. The claim runs as an automated test
(`node tests.mjs`, 18 assertions, deterministic seeds):

| population | RMS tracking error | allocative efficiency |
|---|---|---|
| 60 x ZI constrained (random, but no losses) | 1.8% | 100% |
| 60 x ZI unconstrained (pure noise) | 28-44% | n/a |

Same coin-flippers. The only difference is a budget constraint, and removing
it makes pricing roughly fifteen times worse.

## The species

| name in the UI | who they are | what they know | what tends to happen |
|---|---|---|---|
| ZI constrained | random, refuses losses | their own private value | quietly make the market work |
| ZI chaos | pure noise | nothing | litter the book with absurd quotes |
| Fundamentalist | sees true value, blurry | V plus noise | profit from everyone's lag |
| Momentum | chases recent returns | the tape only | buy tops, sell bottoms, bleed |
| Market maker | quotes both sides | the tape only | harvest the spread, die in trends |

## Things to try

1. Preset "Gode-Sunder 1993": only constrained coin-flippers. Tracking error
   falls to one or two percent and efficiency pins near 100. Nobody in this
   market knows anything. The price knows.
2. Preset "Pure chaos": the same agents without the constraint. The mid
   wanders tens of percent from value. This is what a market without budget
   constraints looks like.
3. "value shock": jump the true value by five to ten percent and watch the
   discovery lag. Fundamentalists move first, makers get run over holding
   inventory, momentum arrives late and donates.
4. "drain liquidity": silence the makers. The spread blows out, and the same
   momentum order that used to move price five cents now gouges a hole
   through the book.
5. "momentum flood": thirty chasers join. If the book is thin you get real
   flash crashes: a market order sweeps through the sparse zone, prints at a
   silly depth (the tooltip shows last vs mid), then snaps back. That is the
   2010 stub-quote crash, showing up uninvited out of two hundred lines of
   agent rules.
6. The transaction tax, 20bps on takers. Volume thins, spreads widen. Decide
   for yourself whether the cure is worth it.

## Things I did not program in

- Flash crashes emerged on their own, thin-book sweeps printing far from mid
  and recovering. I had to switch the chart from last print to mid just to
  keep it readable.
- The chaos agents sometimes get rich. Their absurd resting bids sit deep
  below the market and catch panic sell-offs at a discount, accidental
  liquidity providers of last resort. Real exchanges banned stub quotes after
  2010 for exactly this shape of reason.
- Makers suffer adverse selection. Against informed fundamentalists the
  spread income is not enough; they win in noise and bleed in trends. Nobody
  told them that. It is in the structure.
- Endowments are policy. Early on, agents held twenty times more share-wealth
  than cash and the price sat a persistent five percent under true value,
  because sell-side firepower outgunned buy-side. Balancing endowments fixed
  it. "The market price" quietly encodes who has the money.

## How it is built

- `js/engine.js`: the order book. Price-time priority, partial fills,
  cancels, TTL expiry, self-trade prevention. Trades execute at the resting
  order's price, like a real exchange.
- `js/market.js`: settlement with escrow accounting. Resting bids lock cash,
  resting asks lock shares, refunds on cancel or expiry, so budgets bind
  exactly. The tests prove cash and shares are conserved to the tick across
  30k-tick runs, that no agent ever overdraws, and that P&L sums to exactly
  zero with fees as the only sink.
- `js/agents.js`: the five species, each talking to the market only through
  orders, with the same information limits as the real thing.
- Metrics use a strict both-sides mid with carry-forward, so a one-sided book
  during a sweep cannot poison them.
- `tests.mjs`: engine invariants through to the reproduced science. Run
  `node tests.mjs` to check me.

## Not in v0.1

- Humans. The obvious missing feature is a trade-it-yourself mode where you
  click into the book against the zoo, with a leaderboard of you versus the
  coin-flippers.
- Short selling and margin, and therefore squeezes and death spirals.
- Adaptive agents that learn, and evolving populations.
- The stylized-facts panel: order-flow imbalance, trade-sign autocorrelation,
  volatility clustering.

Everything is vanilla JavaScript on canvas, with light and dark themes picked
by hand. MIT licensed.
