The fundamental insight behind market making is elegant: you don't need to know which direction the price will move. You only need to earn more from the bid-ask spread on completed trades than you lose from adverse price moves on your inventory. A casino doesn't need to predict which players will win — it just needs the house edge to be positive over enough rounds. A market maker doesn't need directional conviction — it just needs the spread income to exceed the inventory risk over enough trades.
This analogy is not coincidental. The Purple Flea Casino API operates on exactly the same mathematical principle: each game is designed with a positive expected value for the house. The house edge — the percentage of each bet the casino keeps in expectation — is the market maker's spread equivalent. Both systems profit from volume, not from correct prediction of outcomes. This guide explores how AI agents can apply casino-style house-edge thinking to on-chain market making, and how to build a production AMM agent using the Purple Flea Trading API.
The Casino Model Applied to Market Making
Consider a simple casino game: a player bets $100 on a coin flip with 49% win probability (the house keeps a 2% edge). Over one bet, the outcome is random. Over 10,000 bets, the casino's profit converges tightly to $2 per $100 wagered. The law of large numbers is the casino's protection against variance — enough volume makes the edge nearly deterministic.
Market making works identically. If you post a bid at $100 and an ask at $100.20 for an ETH pair, you're offering a $0.20 spread (0.2%). If a trader buys from you at $100.20 and another trader immediately sells to you at $100, you've earned $0.20 without any directional exposure. Do this 10,000 times per day, and your gross income is $2,000/day from spread alone. The risk — the equivalent of the casino's "big jackpot" risk — is that you accumulate a large one-sided position during a directional price move, and that inventory loses value before you can liquidate or rebalance it. This is the inventory risk problem.
The goal of a sophisticated market making agent is to maximize spread income while minimizing inventory risk — widening spreads (reducing trade frequency but increasing per-trade profit) during volatile periods when adverse selection is high, and tightening spreads (increasing trade volume) during calm periods when the informativeness of each trade is low.
Bid-Ask Spread Optimization
The canonical academic reference for spread optimization is the Avellaneda-Stoikov model (2008), which derives the optimal quotes for a market maker who wants to maximize expected utility of terminal wealth while managing inventory and volatility risk. The key insight: the optimal spread widens with volatility and with how far the current inventory deviates from neutral.
The Avellaneda-Stoikov reservation price formula:
Because the agent is net short 0.8 ETH, it adjusts its reservation price upward slightly to bias toward buying. This naturally skews the agent's quotes to accumulate the asset it's short, correcting the inventory imbalance without any explicit rebalancing trade. The spread around this reservation price is:
In practice, the model parameters (gamma, k) are calibrated against historical fill rates and inventory behavior. Lower gamma = more aggressive quoting with higher inventory risk tolerance. Higher k = thinner order book depth = wider optimal spread.
Order Book Visualization: Your Agent's View
A healthy market maker's order book looks symmetric around mid-price. Here is an example of an ETH/USDC order book with agent quotes posted:
The agent posts 3.5 ETH of quotes on each side at $2,845.29 and $2,848.00. If market volume is 500 ETH/day and the agent captures 5% of trades, expected daily spread income is approximately 500 * 0.05 * $2.71 / 2 = $33.88. At scale (50,000 ETH daily volume), this grows to $3,388/day from spread alone — before accounting for inventory risk losses.
JavaScript: Production AMM Agent Core
The following complete JavaScript module implements the Avellaneda-Stoikov quoting engine, posts orders via the Purple Flea Trading API, monitors fills, and manages inventory by skewing quotes dynamically.
/** * Avellaneda-Stoikov Market Making Agent * Posts bid/ask quotes on ETH/USDC, manages inventory risk, * and executes via Purple Flea Trading API. */ const PF_TRADING = 'https://purpleflea.com/trading-api' const API_KEY = 'your_api_key' // Model parameters const CONFIG = { gamma: 0.10, // risk aversion (0 = neutral, 1 = very risk averse) kappa: 1.50, // order book density param quoteSize: 0.50, // ETH per quote level quoteLevels: 3, // number of bid/ask levels to post levelSpacing: 0.0003, // 0.03% between levels maxInventory: 5.0, // max ±ETH position before quoting halts targetInventory:0.0, // desired neutral inventory horizonSec: 300, // rebalance horizon (5 minutes) refreshSec: 5, // how often to refresh quotes } // Shared state let state = { midPrice: 0, sigma: 0.015, // realized volatility (updated dynamically) inventory: 0, // current ETH inventory activeOrders: [], totalSpreadPnl: 0, totalInventoryPnl: 0, } /** * Avellaneda-Stoikov reservation price and optimal spread. */ function computeQuotes(midPrice, inventory, sigma, tRemain) { const { gamma, kappa } = CONFIG // Reservation price: biased away from current inventory const reservationPrice = midPrice - inventory * gamma * sigma * sigma * tRemain // Optimal half-spread const spread = gamma * sigma * sigma * tRemain + (2 / gamma) * Math.log(1 + gamma / kappa) return { bid: reservationPrice - spread / 2, ask: reservationPrice + spread / 2, spread, reservationPrice, } } /** * Compute realized volatility from recent price ticks. * @param {number[]} priceTicks - array of recent mid prices */ function computeRealizedVol(priceTicks) { if (priceTicks.length < 2) return 0.015 const returns = [] for (let i = 1; i < priceTicks.length; i++) { returns.push(Math.log(priceTicks[i] / priceTicks[i - 1])) } const mean = returns.reduce((a, b) => a + b, 0) / returns.length const variance = returns.reduce((s, r) => s + (r - mean) ** 2, 0) / returns.length return Math.sqrt(variance) // per-tick vol; scale by sqrt(ticks_per_unit_time) } /** * Cancel all active orders and post fresh quotes. */ async function refreshQuotes(wallet) { const tRemain = CONFIG.horizonSec / 86400 // fraction of trading day const { bid, ask, spread } = computeQuotes( state.midPrice, state.inventory - CONFIG.targetInventory, state.sigma, tRemain ) // Halt quoting if inventory limit breached if (Math.abs(state.inventory) >= CONFIG.maxInventory) { console.warn(`Inventory limit reached (${state.inventory} ETH) — pausing quotes`) await cancelAllOrders(wallet) return } // Cancel existing orders await cancelAllOrders(wallet) // Post layered quotes at multiple levels const newOrders = [] for (let i = 0; i < CONFIG.quoteLevels; i++) { const offset = i * CONFIG.levelSpacing * state.midPrice newOrders.push( postOrder(wallet, 'buy', (bid - offset).toFixed(2), CONFIG.quoteSize), postOrder(wallet, 'sell', (ask + offset).toFixed(2), CONFIG.quoteSize) ) } const results = await Promise.all(newOrders) state.activeOrders = results.map(r => r.order_id).filter(Boolean) console.log(`Quotes refreshed | Bid: $${bid.toFixed(2)} | Ask: $${ask.toFixed(2)} | Spread: $${spread.toFixed(2)} | Inv: ${state.inventory.toFixed(3)} ETH`) } async function postOrder(wallet, side, price, size) { const res = await fetch(`${PF_TRADING}/v1/orders/limit`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY }, body: JSON.stringify({ wallet, side, price, size, pair: 'ETH/USDC', time_in_force: 'GTC', post_only: true // ensure maker fee tier, never take liquidity }) }) return res.json() } async function cancelAllOrders(wallet) { if (!state.activeOrders.length) return await fetch(`${PF_TRADING}/v1/orders/cancel-batch`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY }, body: JSON.stringify({ wallet, order_ids: state.activeOrders }) }) state.activeOrders = [] } /** * Process a fill notification from the exchange websocket. * Updates inventory and PnL tracking. */ function processFill(fill) { const { side, size, price } = fill if (side === 'buy') state.inventory += parseFloat(size) if (side === 'sell') state.inventory -= parseFloat(size) // Spread PnL approximation: half the spread per fill const spreadPerFill = Math.abs(parseFloat(price) - state.midPrice) state.totalSpreadPnl += spreadPerFill * parseFloat(size) console.log(`Fill: ${side} ${size} ETH @ $${price} | Inv: ${state.inventory.toFixed(3)} ETH | Spread PnL: $${state.totalSpreadPnl.toFixed(2)}`) } /** * Main agent loop. */ async function runAMMAgent(wallet) { const priceTicks = [] setInterval(async () => { // 1. Fetch current mid price const tickRes = await fetch(`${PF_TRADING}/v1/ticker/ETH-USDC`, { headers: { 'X-API-Key': API_KEY } }) const ticker = await tickRes.json() state.midPrice = (ticker.best_bid + ticker.best_ask) / 2 priceTicks.push(state.midPrice) if (priceTicks.length > 100) priceTicks.shift() // 2. Update realized volatility state.sigma = computeRealizedVol(priceTicks) // 3. Check for fills and update inventory const fillsRes = await fetch(`${PF_TRADING}/v1/fills/recent`, { headers: { 'X-API-Key': API_KEY }, }) const { fills } = await fillsRes.json() fills.forEach(processFill) // 4. Refresh quotes with updated parameters await refreshQuotes(wallet) }, CONFIG.refreshSec * 1000) console.log(`AMM Agent started for wallet ${wallet}`) } runAMMAgent('0xYourAgentWallet')
Inventory Risk and Impermanent Loss Hedging
The largest risk for a market making agent is accumulating a large inventory position in a directionally moving market. If ETH is falling and your agent keeps buying ETH from panicking sellers (filling your bid orders), you accumulate long ETH exposure that's losing value faster than your spread income compensates.
Dynamic spread widening
As inventory diverges from neutral, widen spreads to slow fill rate on the side that's building inventory. If long 3 ETH in a falling market, widen bids (making them less competitive) and tighten asks (making them more attractive). This naturally attracts sellers to restore your inventory toward neutral without any explicit sell order.
Perpetual hedge
For significant inventory positions (above 2 ETH, for example), open a countervailing position in a perpetual futures market. If long 3 ETH from market making, short 3 ETH perp on Hyperliquid. The spread income continues to accrue on the spot position, while the perp short eliminates directional exposure. The cost of this hedge is the funding rate — if funding is positive (longs pay shorts), you earn funding income on top of spread income.
Impermanent loss (Uniswap V3 LP context)
When operating as a Uniswap V3 LP (a passive AMM rather than an active market maker), the equivalent risk is impermanent loss. For a concentrated liquidity position in an active range, IL can be hedged with delta-neutral options positions on the base asset. The cost of the hedge (options premium) must be weighed against the expected fee income from the LP position.
| Hedge Strategy | Cost | Coverage | Best For |
|---|---|---|---|
| Dynamic spread widening | Reduced fill rate | Slows accumulation | Low-vol markets, small inventory |
| Perp hedge (delta neutral) | Funding rate (±0.01-0.1%/day) | Full directional hedge | High-conviction inventory risk |
| Options collar | Net premium (0.5-2% of position) | Bounded downside | Large positions, low-liquidity markets |
| Cross-venue netting | Gas / bridge fees | Venue-level balance | Multi-venue MM operations |
Python: Impermanent Loss Calculator and Hedge Optimizer
import math import httpx import asyncio from dataclasses import dataclass PF_TRADING = "https://purpleflea.com/trading-api" API_KEY = "your_api_key" @dataclass class LPPosition: entry_price: float # ETH price at position entry tick_lower: float # lower price bound of concentrated range tick_upper: float # upper price bound of concentrated range liquidity_usd: float # total USD value at entry def compute_il(pos: LPPosition, current_price: float) -> float: """ Compute impermanent loss for a Uniswap V3 concentrated LP position. Returns IL as a fraction (negative = loss relative to HODL). Uses V3 formula accounting for concentrated range. """ p0 = pos.entry_price p1 = current_price pa = pos.tick_lower pb = pos.tick_upper # Handle out-of-range: full exposure to one asset if p1 <= pa: # Fully in token0 (ETH): all assets are ETH eth_ratio = 1.0 # Value of LP if HODL'd: 50% ETH + 50% USDC at entry hodl_value = 0.5 * pos.liquidity_usd * (p1 / p0) + 0.5 * pos.liquidity_usd lp_value = pos.liquidity_usd * (p1 / p0) # all became ETH, now worth p1/p0 return (lp_value - hodl_value) / hodl_value elif p1 >= pb: # Fully in token1 (USDC): no price exposure hodl_value = 0.5 * pos.liquidity_usd * (p1 / p0) + 0.5 * pos.liquidity_usd lp_value = pos.liquidity_usd # all USDC, no change return (lp_value - hodl_value) / hodl_value # In-range: standard V3 IL formula sq_p0, sq_p1 = math.sqrt(p0), math.sqrt(p1) sq_pa, sq_pb = math.sqrt(pa), math.sqrt(pb) # LP value ratio (normalized) lp_ratio = ( (2 * math.sqrt(sq_p0 * sq_p1) - sq_p0 - sq_p1) / (2 * math.sqrt(sq_p0 * sq_pb) - sq_p0 - sq_pb) ) hold_ratio = (p1 / p0 + 1) / 2 return (lp_ratio / hold_ratio) - 1 def optimal_hedge_size( pos: LPPosition, current_price: float, perp_funding_rate: float # daily funding rate ) -> dict: """ Calculate optimal delta hedge size for an LP position. Only hedge if IL risk exceeds hedge cost. """ il_pct = abs(compute_il(pos, current_price)) * 100 # Estimate delta exposure: how much ETH does LP "hold" at current price? # For in-range V3 position, delta ≈ 0.5 at midpoint of range range_center = math.sqrt(pos.tick_lower * pos.tick_upper) relative_pos = (current_price - pos.tick_lower) / (pos.tick_upper - pos.tick_lower) delta_fraction = max(0, min(1, 1 - relative_pos)) # ETH fraction eth_exposure = (pos.liquidity_usd * delta_fraction) / current_price hedge_cost_pct = abs(perp_funding_rate) * 30 # 30-day cost should_hedge = il_pct > hedge_cost_pct * 2 # hedge only if IL > 2x cost return { "il_pct": round(il_pct, 4), "eth_delta": round(eth_exposure, 4), "hedge_short_eth": round(eth_exposure, 4) if should_hedge else 0, "hedge_cost_pct_30d": round(hedge_cost_pct, 4), "should_hedge": should_hedge, } async def open_perp_hedge(wallet: str, short_eth: float): """Open a short perp position to hedge LP delta.""" async with httpx.AsyncClient() as client: res = await client.post( f"{PF_TRADING}/v1/perp/open", json={ "wallet": wallet, "asset": "ETH", "side": "short", "size": short_eth, "leverage": 2, "order_type": "market", }, headers={"X-API-Key": API_KEY} ) result = res.json() print(f"Hedge opened: short {short_eth:.4f} ETH perp | txid: {result.get('tx_hash','')[:16]}") // Example usage pos = LPPosition( entry_price=2800, tick_lower=2500, tick_upper=3200, liquidity_usd=100_000 ) current_price = 2600 funding = 0.0001 # 0.01% daily funding hedge = optimal_hedge_size(pos, current_price, funding) print(f"IL: {hedge['il_pct']:.2f}% | Delta: {hedge['eth_delta']:.3f} ETH | Hedge needed: {hedge['should_hedge']}")
The Casino House Edge Model: Steady-State Revenue
The reason the Purple Flea Casino API is architecturally similar to a market making system is that both rely on the same statistical guarantee: over sufficient volume, a positive edge converts to reliable revenue. The Casino API's house edge on provably fair games ranges from 1-5% depending on the game. The market maker's spread edge might be 0.05-0.2% per trade. The magnitudes differ, but the business model is identical: never bet on outcomes, just charge for participation.
An AI agent can be a participant on both sides of this model. It can run games through the Casino API (as a player exploiting variance, or as a house agent with its own edge), and simultaneously market-make on the trading side. The diversification across both revenue streams — casino variance and trading spread — smooths the overall PnL profile and increases Sharpe ratio for the combined operation.
Typical combined agent P&L breakdown (monthly, $200k deployed):
Spread income (market making): +$4,200
Yield farming income: +$1,800
Casino net P&L (provably fair, house side): +$600
Inventory losses (market making): -$800
Gas + fees: -$350
Net: +$5,450/month (32.7% annualized)
Running the Agent: Infrastructure Requirements
A production market making agent has strict infrastructure requirements that differ from most DeFi bots:
- Low-latency execution: Quotes older than 5-10 seconds in a volatile market are stale. The agent must refresh orders faster than the market moves. Colocated infrastructure near exchange nodes (or using low-latency RPC endpoints) is essential.
- Websocket fills feed: Polling for fills every 5 seconds is not enough. Use the exchange's websocket fills feed to process each fill immediately and update inventory in real time.
- Graceful shutdown: On SIGTERM or crash, the agent must cancel all open orders before exiting. An agent that crashes with 10 open limit orders leaves those orders live, accumulating inventory without any management — a recipe for large losses in a moving market.
- Circuit breakers: Halt all quoting if any of: market price moves more than 3% in 60 seconds; inventory exceeds max threshold; exchange API returns errors 3x in a row; account balance falls below minimum operating level.
Critical risk reminder: Market making agents are not "set and forget." During extreme market events (flash crashes, exchange outages, oracle attacks), a market maker can accumulate devastating inventory losses in seconds. Always implement hard position limits, circuit breakers, and maintain a human emergency contact channel. Start with small position sizes (under $10,000) and expand only after validating behavior through multiple volatile market sessions.
Start Market Making with Purple Flea
Use the Trading API for order placement and fills, Casino API for understanding house-edge revenue models, and the Faucet to get free USDC for initial capital deployment.