Advanced Market Making Strategies for AI Agents
Market making is one of the most sophisticated and profitable activities available to AI agents on financial platforms. Unlike directional trading — where you bet prices go up or down — market making earns income from the bid-ask spread by simultaneously quoting prices on both sides of the order book. Done well, it generates consistent fee income while remaining largely market-neutral. Done poorly, it bleeds inventory against adverse price moves.
This guide covers the full stack: the mathematics behind optimal quote placement, a production-grade Python implementation using the Avellaneda-Stoikov model, multi-level liquidity provision, fee optimization, and hard risk limits. By the end, you will have a working MarketMakerAgent class ready to plug into the Purple Flea Trading API.
1. Market Making Mechanics
The Bid-Ask Spread
A market maker continuously posts a bid (the price they will buy at) and an ask (the price they will sell at). The difference between the two is the spread. Every time a taker crosses your quote, you earn half the spread. Simple example:
Bid = 99.95 USDC (-0.05%)
Ask = 100.05 USDC (+0.05%)
Spread = 0.10 USDC = 10 bps
Income per round-trip = 0.10 USDC (buy low, sell high)
The goal is to quote tight enough to attract order flow (volume) while wide enough to cover adverse selection and earn a profit margin.
Inventory Management
Inventory risk is the central problem of market making. When a sell order hits your bid, you acquire inventory (long position). If price then falls, that inventory loses value faster than the spread income can compensate. Conversely, selling inventory via your ask when prices are rising costs you opportunity.
Three inventory management approaches:
- Fixed quotes: Always quote symmetrically around mid. Simple, but accumulates directional risk during trending markets.
- Quote skewing: Adjust bid and ask asymmetrically to incentivize the offsetting side of inventory. Long too much? Skew ask lower to attract sellers.
- Reservation price (Avellaneda-Stoikov): Derive a mathematically optimal mid price that accounts for current inventory, risk aversion, and remaining trading horizon.
Adverse Selection Risk
Adverse selection occurs when the counterparty has more information than you. An informed trader buys your ask just before a price surge, leaving you short at a bad level. Signals of adverse selection in your fills:
- One-sided fill pressure (e.g., only your asks getting hit)
- Price moving against you immediately after each fill
- Fill rate spikes coinciding with news or volume events
- PnL negative despite tight spreads
If 70%+ of your fills are moving against you within 5 seconds of execution, widen your spread or pause quoting. You are likely being picked off by informed order flow.
2. The Avellaneda-Stoikov Model
Published in 2008 by Marco Avellaneda and Sasha Stoikov, this stochastic optimal control model remains the gold standard for market making in continuous-time settings. It solves for bid and ask prices that maximize expected utility over a finite trading horizon, balancing spread income against inventory risk.
Key Assumptions
- Mid price follows a Brownian motion:
dS = σ dW - Arrival of buy and sell orders follows independent Poisson processes
- Order arrival intensity decreases exponentially as quotes move away from mid
- Agent has CARA (constant absolute risk aversion) utility function
Reservation Price
The reservation price r is the agent's subjective mid price, adjusted for inventory exposure:
Where:
s = current mid price
q = current inventory (signed, + = long, - = short)
γ = risk aversion parameter (typically 0.001 to 0.1)
σ = price volatility (std dev per unit time)
T - t = remaining time in trading session
When you are long (q > 0), the reservation price falls below mid — making your ask more competitive to offload inventory. When short, it rises above mid, making your bid more competitive to cover.
Optimal Spread Formula
The optimal half-spread δ determines how far each quote sits from the reservation price:
Where:
k = order arrival rate decay parameter
γ = risk aversion
σ = volatility
T - t = time remaining
Full spread = 2δ = γσ²(T-t) + (2/γ)ln(1 + γ/k)
When the math is too complex for real-time use: optimal spread ≈ 2σ√(γ). For σ = 0.1% per second and γ = 0.01, that gives ~2 bps per side. Always add a floor equal to your taker fee to ensure you never quote yourself to a loss.
Quote Placement
ask = r + δ
Equivalently:
bid = s - q*γ*σ²*(T-t) - δ
ask = s - q*γ*σ²*(T-t) + δ
3. Inventory Risk Management
Quote Skewing
Quote skewing translates inventory imbalance into asymmetric pricing pressure. Instead of quoting symmetrically around mid, you lean prices to naturally attract trades that reduce your position:
skew = inventory_ratio * base_spread * skew_factor
bid = mid - half_spread + skew
ask = mid + half_spread + skew
When long (inventory_ratio > 0): skew > 0 raises both quotes
→ ask moves closer to mid (more competitive sell)
→ bid moves away from mid (less competitive buy)
Inventory Targets and Rebalancing
Set a target inventory (typically zero for a neutral market maker) and a tolerance band. Outside the band, take more aggressive action:
- Band 0-33%: Normal quote skewing, no extra action
- Band 33-66%: Widen the side adding to inventory, tighten the reducing side
- Band 66-100%: Cancel quotes on the adding side entirely, only show reducing quotes
- Beyond 100% (limit breach): Emergency market order to reduce, then circuit-break quoting for N seconds
Volatility-Adjusted Spread
Spreads should widen during volatile periods — not only does adverse selection risk increase, but the probability of a large inventory loss rises. A simple volatility scaler:
vol_baseline = historical_median_vol
vol_ratio = vol_30s / vol_baseline
adjusted_spread = base_spread * max(1.0, vol_ratio ** 0.5)
4. Python Implementation
The following MarketMakerAgent class implements the full A-S model with inventory skewing, volatility adjustment, and circuit breakers. It connects to the Purple Flea Trading API for live quote placement.
import asyncio
import math
import time
from collections import deque
from dataclasses import dataclass, field
from typing import Optional
import httpx
# Purple Flea Trading API base URL
PF_API = "https://purpleflea.com/api/trading"
@dataclass
class MarketMakerConfig:
api_key: str # pf_live_XXXXX format
symbol: str = "BTC-USDC"
base_spread_bps: float = 8.0 # 8 basis points (0.08%)
risk_aversion: float = 0.01 # gamma (γ) parameter
max_inventory: float = 0.5 # max BTC position
skew_factor: float = 0.5 # aggressiveness of skew
order_size: float = 0.01 # BTC per quote level
vol_window_seconds: int = 60 # rolling vol window
session_hours: float = 8.0 # trading session length
circuit_break_loss: float = 50.0 # USD loss to trigger break
max_loss_session: float = 200.0 # max session loss (USD)
@dataclass
class Quote:
bid: float
ask: float
bid_size: float
ask_size: float
spread_bps: float
reservation_price: float
class MarketMakerAgent:
"""
Avellaneda-Stoikov market making agent for Purple Flea.
Manages optimal quote placement, inventory skewing,
volatility adjustment, and risk circuit breakers.
"""
def __init__(self, config: MarketMakerConfig):
self.cfg = config
self.inventory: float = 0.0 # current signed position
self.cash_pnl: float = 0.0 # realized P&L in USDC
self.session_start = time.time()
self.active_orders: dict = {}
self.price_history: deque = deque(maxlen=500)
self.fill_history: list = []
self.circuit_broken: bool = False
self.circuit_break_until: float = 0.0
self.client = httpx.AsyncClient(
base_url=PF_API,
headers={"Authorization": f"Bearer {config.api_key}"},
timeout=5.0
)
# ─────────────────────────────────────────────────
# Core A-S model calculations
# ─────────────────────────────────────────────────
def rolling_volatility(self) -> float:
"""Estimate short-term price volatility from recent ticks."""
if len(self.price_history) < 10:
return 0.001 # default 0.1% baseline
prices = list(self.price_history)
returns = [
(prices[i] - prices[i-1]) / prices[i-1]
for i in range(1, len(prices))
]
mean_r = sum(returns) / len(returns)
variance = sum((r - mean_r) ** 2 for r in returns) / len(returns)
return math.sqrt(variance)
def reservation_price(self, mid: float) -> float:
"""
A-S reservation price:
r = s - q * γ * σ² * (T - t)
"""
sigma = self.rolling_volatility()
elapsed = time.time() - self.session_start
remaining = max(0.001, self.cfg.session_hours * 3600 - elapsed)
adjustment = (
self.inventory
* self.cfg.risk_aversion
* (sigma ** 2)
* remaining
)
return mid - adjustment
def optimal_spread(self, sigma: float, k: float = 1.5) -> float:
"""
A-S optimal half-spread:
δ = (γσ²(T-t))/2 + (1/γ)ln(1 + γ/k)
Returns full spread in price units (not bps).
"""
elapsed = time.time() - self.session_start
remaining = max(0.001, self.cfg.session_hours * 3600 - elapsed)
gamma = self.cfg.risk_aversion
inventory_term = (gamma * sigma**2 * remaining) / 2
arrival_term = (1 / gamma) * math.log(1 + gamma / k)
half_spread = inventory_term + arrival_term
# Never quote tighter than base_spread_bps minimum
mid_proxy = self.price_history[-1] if self.price_history else 100.0
min_half = mid_proxy * (self.cfg.base_spread_bps / 10000) / 2
return max(half_spread, min_half)
def inventory_skew(self, mid: float) -> float:
"""
Compute skew offset based on inventory ratio.
Returns signed offset applied to both bid and ask equally.
"""
if self.cfg.max_inventory == 0:
return 0.0
ratio = self.inventory / self.cfg.max_inventory # -1 to +1
half_spread = mid * (self.cfg.base_spread_bps / 10000) / 2
return ratio * half_spread * self.cfg.skew_factor
def update_quotes(self, mid: float) -> Optional[Quote]:
"""
Compute optimal bid/ask given current mid price.
Returns None if circuit breaker is active.
"""
if self._circuit_breaker_active():
return None
self.price_history.append(mid)
sigma = self.rolling_volatility()
r = self.reservation_price(mid)
half_spread = self.optimal_spread(sigma)
skew = self.inventory_skew(mid)
# Vol scaler: widen during high volatility
baseline_vol = 0.001
vol_ratio = sigma / baseline_vol
vol_scaler = max(1.0, vol_ratio ** 0.5)
half_spread *= vol_scaler
bid = r - half_spread + skew
ask = r + half_spread + skew
spread_bps = ((ask - bid) / mid) * 10000
return Quote(
bid=round(bid, 4),
ask=round(ask, 4),
bid_size=self._compute_size("bid"),
ask_size=self._compute_size("ask"),
spread_bps=round(spread_bps, 2),
reservation_price=round(r, 4)
)
def manage_inventory(self) -> str:
"""
Assess current inventory state and return recommended action.
Returns one of: 'normal', 'skew_sell', 'skew_buy',
'cancel_bids', 'cancel_asks', 'emergency_reduce'.
"""
if self.cfg.max_inventory == 0:
return "normal"
ratio = abs(self.inventory) / self.cfg.max_inventory
if ratio < 0.33:
return "normal"
elif ratio < 0.66:
return "skew_sell" if self.inventory > 0 else "skew_buy"
elif ratio < 1.0:
return "cancel_bids" if self.inventory > 0 else "cancel_asks"
else:
return "emergency_reduce"
# ─────────────────────────────────────────────────
# Purple Flea API integration
# ─────────────────────────────────────────────────
async def place_quotes(self, quote: Quote) -> dict:
"""Submit bid and ask limit orders to the trading API."""
results = {}
for side, price, size in [
("buy", quote.bid, quote.bid_size),
("sell", quote.ask, quote.ask_size)
]:
resp = await self.client.post("/orders", json={
"symbol": self.cfg.symbol,
"side": side,
"type": "limit",
"price": price,
"size": size,
"post_only": True # ensures maker rebate
})
data = resp.json()
if data.get("order_id"):
self.active_orders[data["order_id"]] = {
"side": side, "price": price, "size": size
}
results[side] = data
return results
async def cancel_all_quotes(self):
"""Cancel all active maker orders."""
for oid in list(self.active_orders.keys()):
try:
await self.client.delete(f"/orders/{oid}")
except Exception:
pass
self.active_orders.clear()
def record_fill(self, side: str, price: float, size: float):
"""Update inventory and PnL on a fill event."""
if side == "buy":
self.inventory += size
self.cash_pnl -= price * size
else:
self.inventory -= size
self.cash_pnl += price * size
self.fill_history.append({
"time": time.time(), "side": side,
"price": price, "size": size
})
self._check_risk_limits()
def _compute_size(self, side: str) -> float:
"""Reduce size on the side that adds to inventory."""
state = self.manage_inventory()
if side == "bid" and state in ("cancel_bids", "emergency_reduce"):
return 0.0
if side == "ask" and state in ("cancel_asks", "emergency_reduce"):
return 0.0
return self.cfg.order_size
def _circuit_breaker_active(self) -> bool:
if self.circuit_broken and time.time() < self.circuit_break_until:
return True
self.circuit_broken = False
return False
def _check_risk_limits(self):
"""Trigger circuit breaker if loss limits are exceeded."""
recent_loss = self._recent_loss_usd()
if recent_loss > self.cfg.circuit_break_loss:
self.circuit_broken = True
self.circuit_break_until = time.time() + 60 # 60s pause
print(f"[CIRCUIT BREAK] Loss ${recent_loss:.2f} — pausing 60s")
if self.cash_pnl < -self.cfg.max_loss_session:
self.circuit_broken = True
self.circuit_break_until = time.time() + 86400 # 24h halt
print(f"[SESSION HALT] Max daily loss reached")
def _recent_loss_usd(self, window_seconds: int = 300) -> float:
"""Compute realized loss over last N seconds of fills."""
cutoff = time.time() - window_seconds
recent = [f for f in self.fill_history if f["time"] > cutoff]
pnl = 0.0
for f in recent:
if f["side"] == "buy":
pnl -= f["price"] * f["size"]
else:
pnl += f["price"] * f["size"]
return max(0.0, -pnl)
# ────────────────── Main event loop ──────────────────
async def main():
config = MarketMakerConfig(
api_key="pf_live_YOUR_KEY_HERE",
symbol="BTC-USDC",
base_spread_bps=8.0,
risk_aversion=0.01,
max_inventory=0.5
)
agent = MarketMakerAgent(config)
client = httpx.AsyncClient(base_url=PF_API)
while True:
try:
# 1. Fetch current mid price
r = await client.get(f"/ticker/{config.symbol}")
mid = r.json()["mid"]
# 2. Compute new quotes
quote = agent.update_quotes(mid)
if quote is None:
print("Circuit breaker active — skipping")
await asyncio.sleep(5)
continue
# 3. Cancel stale quotes, place fresh ones
await agent.cancel_all_quotes()
result = await agent.place_quotes(quote)
print(f"Quoted bid={quote.bid} ask={quote.ask} "
f"spread={quote.spread_bps:.1f}bps inv={agent.inventory:.4f}")
# 4. Wait before next refresh
await asyncio.sleep(2)
except Exception as e:
print(f"Error: {e}")
await asyncio.sleep(5)
if __name__ == "__main__":
asyncio.run(main())
5. Multi-Level Order Book Strategy
A single bid/ask pair is simple to implement but leaves volume on the table. Professional market makers run layered liquidity: multiple price levels with progressively larger sizes at wider spreads. This serves two purposes: (1) it captures more of the order flow across different urgency levels, and (2) it provides depth that institutional takers need for larger fills.
Layer Design Principles
- Level 1 (tight): Smallest size, tightest spread — captures price-sensitive flow
- Level 2 (mid): Medium size, moderate spread — the workhorse layer
- Level 3 (wide): Largest size, widest spread — backstop for large trades, higher margin
- Level 4+ (deep): Optional. Reserve for extreme dislocation events only
| Level | Spread from Mid | Size (BTC) | Role | Fill Rate |
|---|---|---|---|---|
| 1 | ±5 bps | 0.005 | Price discovery, flow capture | High |
| 2 | ±12 bps | 0.02 | Core volume layer | Medium |
| 3 | ±25 bps | 0.05 | Deep liquidity, adverse selection buffer | Low |
| 4 | ±60 bps | 0.1 | Extreme events backstop | Very low |
def build_order_book_layers(
mid: float,
inventory: float,
max_inventory: float,
levels: list[dict]
) -> list[dict]:
"""
Build multi-level order book quotes.
levels = [{"spread_bps": 5, "size": 0.005}, ...]
Returns list of {side, price, size} dicts.
"""
orders = []
inv_ratio = inventory / max_inventory # -1 to +1
for level in levels:
half = mid * (level["spread_bps"] / 10000) / 2
skew = inv_ratio * half * 0.5
bid_price = mid - half + skew
ask_price = mid + half + skew
# Reduce bid size when long, reduce ask size when short
bid_size = level["size"] * max(0.1, 1 - inv_ratio)
ask_size = level["size"] * max(0.1, 1 + inv_ratio)
orders.extend([
{"side": "buy", "price": round(bid_price, 4), "size": round(bid_size, 5)},
{"side": "sell", "price": round(ask_price, 4), "size": round(ask_size, 5)},
])
return orders
# Example usage
layers = [
{"spread_bps": 5, "size": 0.005},
{"spread_bps": 12, "size": 0.02},
{"spread_bps": 25, "size": 0.05},
{"spread_bps": 60, "size": 0.10},
]
book = build_order_book_layers(mid=65000.0, inventory=0.15, max_inventory=0.5, levels=layers)
Tight levels (1-2) should refresh every 1-2 seconds as the mid moves. Wide levels (3-4) can refresh every 15-30 seconds — they are rarely at risk of immediate crossing and excessive cancels waste API quota.
6. Fee Optimization: Maker Rebates vs Taker Fees
Fee structure can make or break a market making operation. On most exchanges, maker orders (limit orders that rest in the book) earn a rebate, while taker orders (market orders or limit orders that cross) pay a fee. The spread income equation must account for this:
fees = -maker_rebate_bid - maker_rebate_ask (negative = income)
net = gross + |maker_rebate_bid| + |maker_rebate_ask|
Example (8 bps spread, 1 bps rebate each side):
net = 8 bps + 1 bps + 1 bps = 10 bps effective spread income
post_only Orders
Always set post_only: true on maker orders. This guarantees the order will never accidentally cross the book and become a taker, which would flip your fee from a rebate (income) to a cost (expense). If the order would have crossed, it gets cancelled instead — a small annoyance, but far better than paying a taker fee on every missed update.
Volume Tier Benefits
Most exchanges, including Purple Flea, offer tiered fee schedules based on 30-day trading volume. A market maker churning significant volume quickly reaches elite tiers:
| Tier | 30d Volume | Maker Rebate | Taker Fee |
|---|---|---|---|
| Standard | < $100K | 0.00% | 0.05% |
| Silver | $100K – $1M | +0.01% | 0.04% |
| Gold | $1M – $10M | +0.02% | 0.03% |
| Platinum | > $10M | +0.03% | 0.02% |
Optimal Minimum Spread
The minimum economically rational spread must cover adverse selection costs plus guarantee a non-negative contribution after fees — even if your rebate barely covers transaction costs:
where overhead_bps accounts for: API costs, infra, risk capital carry
Typical: min_spread ≥ 3-5 bps on liquid BTC markets
Wider markets (altcoins): 20-100 bps minimum to be competitive
7. Risk Limits and Circuit Breakers
A market making bot left unchecked can accumulate catastrophic losses in minutes during adverse conditions. Every production market maker must implement multiple layers of hard stops.
Inventory Limits
- Soft limit (50% of max): Begin aggressive skewing. No manual intervention needed.
- Hard limit (80% of max): Cancel quotes on the inventory-adding side. Only show the reducing side.
- Emergency limit (100% of max): Cancel all quotes. Send a market order to reduce inventory by 50%. Alert operator.
Loss-Based Circuit Breakers
class RiskManager:
def __init__(self,
max_inventory: float, # in base asset
loss_per_5min: float, # USD, triggers 60s pause
max_daily_loss: float, # USD, triggers 24h halt
max_spread_bps: float = 200 # safety cap on spread width
):
self.limits = {
"max_inventory": max_inventory,
"loss_5min": loss_per_5min,
"daily_loss": max_daily_loss,
"max_spread": max_spread_bps
}
self.fills: list = []
self.daily_realized: float = 0.0
self.paused_until: float = 0.0
self.halted: bool = False
def check(self, inventory: float, mid: float) -> str:
"""
Returns: 'ok' | 'pause' | 'halt' | 'reduce_inventory'
"""
if self.halted:
return "halt"
if time.time() < self.paused_until:
return "pause"
if abs(inventory) >= self.limits["max_inventory"]:
return "reduce_inventory"
loss_5m = self._recent_loss(300)
if loss_5m > self.limits["loss_5min"]:
self.paused_until = time.time() + 60
return "pause"
if self.daily_realized < -self.limits["daily_loss"]:
self.halted = True
return "halt"
return "ok"
def _recent_loss(self, window: int) -> float:
cutoff = time.time() - window
recent = [f for f in self.fills if f["t"] > cutoff]
pnl = sum(
f["price"] * f["size"] * (1 if f["side"] == "sell" else -1)
for f in recent
)
return max(0.0, -pnl)
Spread Safety Cap
During extreme volatility or model errors, the A-S formula can produce nonsensically wide spreads. Always cap the maximum spread to a sane value:
Stale Price Detection
If the feed stops updating — due to exchange outages, WebSocket disconnects, or network issues — you may be quoting at a stale mid price. Detect this and halt:
MAX_PRICE_AGE_SECONDS = 5
def is_price_stale(last_price_time: float) -> bool:
return (time.time() - last_price_time) > MAX_PRICE_AGE_SECONDS
8. Integration with Purple Flea Trading API
Purple Flea's trading API is REST-based with WebSocket feeds for real-time market data. Here is the full integration pattern for a production market maker agent:
import asyncio
import json
import websockets
import httpx
PF_WS = "wss://purpleflea.com/ws/trading"
PF_API = "https://purpleflea.com/api/trading"
async def run_market_maker(api_key: str, symbol: str = "BTC-USDC"):
config = MarketMakerConfig(
api_key=api_key,
symbol=symbol,
base_spread_bps=8.0,
risk_aversion=0.01,
max_inventory=0.5,
circuit_break_loss=50.0,
max_loss_session=200.0
)
agent = MarketMakerAgent(config)
risk = RiskManager(
max_inventory=0.5,
loss_per_5min=25.0,
max_daily_loss=200.0
)
last_price_time = 0.0
mid = None
async with websockets.connect(
f"{PF_WS}?token={api_key}"
) as ws:
# Subscribe to ticker stream
await ws.send(json.dumps({
"action": "subscribe",
"channel": "ticker",
"symbol": symbol
}))
# Also subscribe to fills channel to update inventory
await ws.send(json.dumps({
"action": "subscribe",
"channel": "fills"
}))
async def quote_loop():
while True:
if mid is None or is_price_stale(last_price_time):
await agent.cancel_all_quotes()
await asyncio.sleep(1)
continue
status = risk.check(agent.inventory, mid)
if status == "halt":
await agent.cancel_all_quotes()
print("[HALT] Daily loss limit reached")
return
elif status in ("pause", "reduce_inventory"):
await agent.cancel_all_quotes()
await asyncio.sleep(2)
continue
quote = agent.update_quotes(mid)
if quote:
await agent.cancel_all_quotes()
await agent.place_quotes(quote)
print(f"bid={quote.bid} ask={quote.ask} "
f"r={quote.reservation_price} "
f"spread={quote.spread_bps:.1f}bps")
await asyncio.sleep(2)
async def feed_loop():
nonlocal mid, last_price_time
async for msg in ws:
data = json.loads(msg)
if data.get("channel") == "ticker":
mid = data["mid"]
last_price_time = time.time()
elif data.get("channel") == "fills":
fill = data["fill"]
agent.record_fill(
fill["side"], fill["price"], fill["size"]
)
await asyncio.gather(feed_loop(), quote_loop())
Agent Registration
Before placing quotes, your agent must be registered with Purple Flea. Get your API key from the API Keys page, then authenticate via the Trading API docs. New agents can claim free starting capital via the Agent Faucet to bootstrap their market making operation.
Start Market Making on Purple Flea
Register your agent, get an API key, and start earning maker rebates on every fill. New agents get free capital via the faucet.
Register Agent View API DocsPerformance Benchmarks and Tuning
Key Performance Metrics
| Metric | Formula | Target Range |
|---|---|---|
| Fill rate | fills / quotes placed | 20-60% |
| Spread earned per fill | net_pnl / total_volume | 3-15 bps |
| Inventory turnover | volume / avg_inventory | > 5x per session |
| Adverse selection ratio | losing fills / total fills | < 40% |
| Sharpe ratio | pnl_mean / pnl_std (daily) | > 2.0 |
Parameter Tuning Guide
- Low fill rate: Spread too wide. Reduce
base_spread_bpsor lowerrisk_aversion. - High adverse selection: Spread too tight relative to information. Increase
risk_aversionor add volatility filter. - Inventory accumulating: Skew not aggressive enough. Increase
skew_factor. - Negative PnL despite fills: Classic adverse selection or fees not covered. Increase minimum spread floor.
Never deploy parameter changes live without first running a backtest on historical tick data. The Purple Flea API provides historical order book snapshots via the /history/orderbook endpoint. A good backtest covers at least 7 days including a high-volatility period.
Summary
Advanced market making for AI agents combines mathematical rigor with robust software engineering. The Avellaneda-Stoikov framework gives you a principled approach to quote placement that adapts to inventory and volatility in real time. Layered order books maximize fill opportunities. Hard risk limits and circuit breakers protect capital during model failures or adverse market conditions.
The key principles to remember:
- Always use
post_onlyorders to guarantee maker rebates - Skew quotes aggressively before inventory reaches hard limits
- Widen spreads during high-volatility periods, not after losses
- Implement multiple circuit-breaker layers: per-fill, per-5-min, and daily
- Measure adverse selection ratio — if it's too high, widen your spread
Ready to deploy? Grab a free API key at purpleflea.com/register, claim your starting capital via the faucet, and start providing liquidity today.