# Environmental Market Simulation ## Goal Visualize how environmental commodity prices emerge from decentralized trader activity — not from a central authority or random walk. The simulation demonstrates market microstructure (order books, bid/ask spreads, limit orders, fills) in the context of Providence, RI environmental markets. ## What It Is A single-file HTML/Canvas simulation. No dependencies. Markets form an outer ring, traders move inside. Everything runs in the browser at 60fps. ## Markets 20 markets across 5 types: Water, Ecological Restoration, Energy, Regulatory, and Credit. Each market maintains its own order book (sorted bids and asks). Price is the midpoint of the best bid and best ask — it emerges from trader activity, not a formula. ### Market Ring Markets sit on a colored ring (15px wide) with segments colored by market type. Price and market name are rendered outside the ring to prevent overlap with order book arcs. Bid/ask prices appear near the market center. ### Order Book Arcs Radially-aligned arc bands show order book depth. Green arcs (left of radial midpoint) = bids, red arcs (right) = asks. Arc width is proportional to order quantity. Scaled to 70% of original size (bandW=15, bandGap=2.5, arcSpan=0.6pi). ## Traders 8 autonomous agents (configurable). Each has cash, a portfolio of holdings, and a randomized target allocation (preference vector) across markets. Every tick, a subset of traders pick a market (weighted by preference vector) and post a limit order: - **Buy or sell?** Each trader has target portfolio weights (`Math.random() ** 4`, spiky — concentrates on 2-3 markets). If underweight in a market, they lean toward buying; if overweight, they lean toward selling. Probability is clamped (15%-85%) so both sides always have activity. Traders reshuffle their target preferences every ~33 seconds. - **Market selection** is weighted by the trader's preference vector, not uniform random. - **Price?** Near the current mid, offset by a random amount controlled by the spread slider. 30% of orders are "aggressive" (cross the spread to fill immediately). - **Short selling** is allowed — traders can post asks without holdings. - **Initial endowments** are random (2-4 random markets, 2-8 units each), deliberately mismatched with preferences. This creates an Edgeworth box dynamic where traders are better off by trading toward their preferred allocation. ### Trader Visualization - **Head from above** — circle with directional arrow (nose) pointing toward goal position (preference-weighted centroid of market positions). - **Fill flash** animation on trade execution. - **Portfolio-weighted centroid** positioning — traders gravitate toward markets they hold, no physics simulation or drift. - **Position freeze** during fill animations (posFreeze counter) — deferred position updates give visual conservation of momentum. - **Inspector panel** — click a trader to see detailed info (holdings, cash, open orders, deviation from target weights) in the left drawer. First trader is auto-selected on init. ## Matching Engine When a new order arrives, it checks if it crosses the opposite side. If bid >= best ask (or ask <= best bid), a fill occurs: cash and holdings transfer between buyer and seller. ## Fill Animation (FillAnim) Two-phase animation on each fill: 1. **Vertex lerp** (~25 frames): A vertex starts at the market and lerps to the midpoint between buyer and seller. Two-segment line drawn: seller-vertex (red) and vertex-buyer (green), with price label at vertex. 2. **Particle travel** (~50 frames): A green particle travels from seller through vertex to buyer, representing commodity flow. Position effects: both traders' positions are frozen during the animation. Seller unfreezes when the particle departs (phase transition), buyer unfreezes when the particle arrives (animation end). ## Order Lifecycle - **Filled orders** trigger FillAnim (vertex lerp + inter-trader particle). - **Expired orders** (~3 seconds / 180 frames) become a FadingArc — a dashed outlined arc at the order's book position that fades out over ~25 frames. No fill, just stroke. - **Edges** (green = bids, red = asks) connect traders to markets for open orders. Width proportional to quantity. ## Visualization - **Edges**: Green lines = open bids, red lines = open asks. Width proportional to order quantity. These represent "potential energy." - **FillAnim**: Two-segment line (red seller-side, green buyer-side) with vertex dot + price label, followed by commodity particle. Represents the fill event. - **FadingArcs**: Dashed outlined arcs at expired order positions. Represent dissipated potential energy. - **Chart**: Right drawer shows average price by market type over time. - **Drawers**: Collapsible left (parameters + trader inspector) and right (chart + model info) drawers, both open by default. Touch and mouse support, responsive to viewport resizing. ## Controls - Spread % — how far from mid traders place orders - Trade Frequency — how many trades per frame - Price Volatility — unused in current code - Num Traders — agent count - Book Depth — visible order book levels ## Key Design Decisions - Early versions used a synthetic market maker that wiped and rebuilt order books every 60 frames with a random-walk price. This was replaced with fully trader-driven order flow where price discovery emerges from supply and demand. The `_v001.html` backup preserves the synthetic version. - Trader Drift slider was removed — traders now position via portfolio-weighted centroid with no random drift or physics. - Order expiry visualization went through several iterations (dissipate particles, arc swelling) before settling on FadingArc (dashed outline fade). - Initial endowments changed from preference-matched to random, creating Edgeworth box dynamics where traders gain from trade. - Trader visualization changed from mini pie chart to abstract head-from-above (circle + directional nose arrow facing goal position). - Fill animation evolved from instant two-particle to FillAnim class: vertex lerp from market to trader midpoint, then inter-trader particle with deferred position updates (posFreeze). - Desire vectors removed — replaced by open order edges and directional trader heads. - Order book arcs scaled to 70% for visual balance with larger trader heads. - The `_v002.html` backup preserves the state before FillAnim and Edgeworth box changes.